Merge pull request #64 in ITERATE/cyberduck from feature/windows/9074 to master
[cyberduck.git] / source / ch / cyberduck / ui / cocoa / SheetController.java
blobef93b72d33190b55588d5c045fcc48ab6dd87431
1 package ch.cyberduck.ui.cocoa;
3 /*
4 * Copyright (c) 2005 David Kocher. All rights reserved.
5 * http://cyberduck.ch/
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * Bug fixes, suggestions and comments should be sent to:
18 * dkocher@cyberduck.ch
21 import ch.cyberduck.binding.application.AppKitFunctionsLibrary;
22 import ch.cyberduck.binding.application.NSApplication;
23 import ch.cyberduck.binding.application.NSButton;
24 import ch.cyberduck.binding.application.NSWindow;
25 import ch.cyberduck.binding.application.SheetCallback;
26 import ch.cyberduck.binding.foundation.NSThread;
27 import ch.cyberduck.core.threading.ControllerMainAction;
29 import org.apache.log4j.Logger;
30 import org.rococoa.Foundation;
31 import org.rococoa.ID;
33 import java.util.HashSet;
34 import java.util.Set;
35 import java.util.concurrent.CountDownLatch;
37 /**
38 * @version $Id$
40 public abstract class SheetController extends WindowController implements SheetCallback {
41 private static Logger log = Logger.getLogger(SheetController.class);
43 /**
44 * Keep a reference to the sheet to protect it from being
45 * deallocated as a weak reference before the callback from the runtime
47 protected static final Set<SheetController> sheetRegistry
48 = new HashSet<SheetController>();
50 /**
51 * The controller of the parent window
53 protected final WindowController parent;
55 /**
56 * Dismiss button clicked
58 private int returncode;
60 private CountDownLatch signal
61 = new CountDownLatch(1);
63 /**
64 * The sheet window must be provided later with #setWindow (usually called when loading the NIB file)
66 * @param parent The controller of the parent window
68 public SheetController(final WindowController parent) {
69 this.parent = parent;
70 sheetRegistry.add(this);
73 /**
74 * @return Null by default, a sheet with no custom NIB
76 @Override
77 protected String getBundleName() {
78 return null;
81 /**
82 * This must be the target action for any button in the sheet dialog. Will validate the input
83 * and close the sheet; #sheetDidClose will be called afterwards
85 * @param sender A button in the sheet dialog
87 @Action
88 public void closeSheet(final NSButton sender) {
89 if(log.isDebugEnabled()) {
90 log.debug(String.format("Close sheet with button %s", sender.title()));
92 final int option = new PanelReturnCodeMapper().getOption(sender);
93 if(option == DEFAULT_OPTION || option == ALTERNATE_OPTION) {
94 if(!this.validateInput()) {
95 AppKitFunctionsLibrary.beep();
96 return;
99 NSApplication.sharedApplication().endSheet(this.window(), option);
103 * @return The tag of the button this sheet was dismissed with
105 public int returnCode() {
106 return returncode;
110 * Check input fields for any errors
112 * @return true if a valid input has been given
114 protected boolean validateInput() {
115 return true;
118 public void beginSheet() {
119 synchronized(parent.window()) {
120 if(NSThread.isMainThread()) {
121 this.loadBundle();
122 // No need to call invoke on main thread
123 this.beginSheet(this.window());
125 else {
126 final SheetController controller = this;
127 invoke(new ControllerMainAction(this) {
128 @Override
129 public void run() {
130 controller.loadBundle();
131 //Invoke again on main thread
132 controller.beginSheet(controller.window());
134 }, true);
135 if(log.isDebugEnabled()) {
136 log.debug("Await sheet dismiss");
138 // Synchronize on parent controller. Only display one sheet at once.
139 try {
140 signal.await();
142 catch(InterruptedException e) {
143 log.error("Error waiting for sheet dismiss", e);
144 this.callback(CANCEL_OPTION);
150 protected void beginSheet(final NSWindow window) {
151 parent.window().makeKeyAndOrderFront(null);
152 NSApplication.sharedApplication().beginSheet(window, //sheet
153 parent.window(), // modalForWindow
154 this.id(), // modalDelegate
155 Foundation.selector("sheetDidClose:returnCode:contextInfo:"),
156 null); //context
160 * Called by the runtime after a sheet has been dismissed. Ends any modal session and
161 * sends the returncode to the callback implementation. Also invalidates this controller to be
162 * garbage collected and notifies the lock object
164 * @param sheet Sheet window
165 * @param returncode Identifier for the button clicked by the user
166 * @param contextInfo Not used
168 public void sheetDidClose_returnCode_contextInfo(final NSWindow sheet, final int returncode, ID contextInfo) {
169 sheet.orderOut(null);
170 this.returncode = returncode;
171 this.callback(returncode);
172 signal.countDown();
173 if(!this.isSingleton()) {
174 this.invalidate();
178 @Override
179 public void invalidate() {
180 sheetRegistry.remove(this);
181 super.invalidate();
185 * @return True if the class is a singleton and the object should
186 * not be invlidated upon the sheet is closed
187 * @see #sheetDidClose_returnCode_contextInfo(ch.cyberduck.binding.application.NSWindow, int, org.rococoa.ID)
189 @Override
190 public boolean isSingleton() {
191 return false;