Merge pull request #64 in ITERATE/cyberduck from feature/windows/9074 to master
[cyberduck.git] / source / ch / cyberduck / ui / cocoa / MainController.java
blob294655a38a46d85a112eb03b3af5b1f7b3720486
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.ProxyController;
22 import ch.cyberduck.binding.application.NSAlert;
23 import ch.cyberduck.binding.application.NSApplication;
24 import ch.cyberduck.binding.application.NSButton;
25 import ch.cyberduck.binding.application.NSCell;
26 import ch.cyberduck.binding.application.NSColor;
27 import ch.cyberduck.binding.application.NSFont;
28 import ch.cyberduck.binding.application.NSImage;
29 import ch.cyberduck.binding.application.NSMenu;
30 import ch.cyberduck.binding.application.NSMenuItem;
31 import ch.cyberduck.binding.application.NSPasteboard;
32 import ch.cyberduck.binding.application.NSPopUpButton;
33 import ch.cyberduck.binding.application.NSWindow;
34 import ch.cyberduck.binding.application.NSWorkspace;
35 import ch.cyberduck.binding.application.SheetCallback;
36 import ch.cyberduck.binding.foundation.NSAppleEventDescriptor;
37 import ch.cyberduck.binding.foundation.NSAppleEventManager;
38 import ch.cyberduck.binding.foundation.NSArray;
39 import ch.cyberduck.binding.foundation.NSAttributedString;
40 import ch.cyberduck.binding.foundation.NSBundle;
41 import ch.cyberduck.binding.foundation.NSDictionary;
42 import ch.cyberduck.binding.foundation.NSNotification;
43 import ch.cyberduck.binding.foundation.NSNotificationCenter;
44 import ch.cyberduck.binding.foundation.NSObject;
45 import ch.cyberduck.core.*;
46 import ch.cyberduck.core.aquaticprime.License;
47 import ch.cyberduck.core.aquaticprime.LicenseFactory;
48 import ch.cyberduck.core.bonjour.NotificationRendezvousListener;
49 import ch.cyberduck.core.bonjour.Rendezvous;
50 import ch.cyberduck.core.bonjour.RendezvousFactory;
51 import ch.cyberduck.core.exception.AccessDeniedException;
52 import ch.cyberduck.core.exception.BackgroundException;
53 import ch.cyberduck.core.importer.CrossFtpBookmarkCollection;
54 import ch.cyberduck.core.importer.FetchBookmarkCollection;
55 import ch.cyberduck.core.importer.FilezillaBookmarkCollection;
56 import ch.cyberduck.core.importer.FireFtpBookmarkCollection;
57 import ch.cyberduck.core.importer.FlowBookmarkCollection;
58 import ch.cyberduck.core.importer.InterarchyBookmarkCollection;
59 import ch.cyberduck.core.importer.ThirdpartyBookmarkCollection;
60 import ch.cyberduck.core.importer.TransmitBookmarkCollection;
61 import ch.cyberduck.core.local.Application;
62 import ch.cyberduck.core.local.ApplicationLauncherFactory;
63 import ch.cyberduck.core.local.BrowserLauncherFactory;
64 import ch.cyberduck.core.notification.NotificationServiceFactory;
65 import ch.cyberduck.core.preferences.Preferences;
66 import ch.cyberduck.core.preferences.PreferencesFactory;
67 import ch.cyberduck.core.resources.IconCacheFactory;
68 import ch.cyberduck.core.serializer.HostDictionary;
69 import ch.cyberduck.core.sparkle.Updater;
70 import ch.cyberduck.core.threading.AbstractBackgroundAction;
71 import ch.cyberduck.core.transfer.DownloadTransfer;
72 import ch.cyberduck.core.transfer.TransferItem;
73 import ch.cyberduck.core.transfer.UploadTransfer;
74 import ch.cyberduck.core.urlhandler.SchemeHandlerFactory;
75 import ch.cyberduck.ui.browser.Column;
76 import ch.cyberduck.ui.browser.DownloadDirectoryFinder;
77 import ch.cyberduck.ui.cocoa.delegate.ArchiveMenuDelegate;
78 import ch.cyberduck.ui.cocoa.delegate.BookmarkMenuDelegate;
79 import ch.cyberduck.ui.cocoa.delegate.CopyURLMenuDelegate;
80 import ch.cyberduck.ui.cocoa.delegate.EditMenuDelegate;
81 import ch.cyberduck.ui.cocoa.delegate.OpenURLMenuDelegate;
82 import ch.cyberduck.ui.cocoa.delegate.URLMenuDelegate;
84 import org.apache.commons.lang3.StringUtils;
85 import org.apache.log4j.Logger;
86 import org.rococoa.Foundation;
87 import org.rococoa.ID;
88 import org.rococoa.Rococoa;
89 import org.rococoa.cocoa.foundation.NSInteger;
90 import org.rococoa.cocoa.foundation.NSRect;
91 import org.rococoa.cocoa.foundation.NSUInteger;
93 import java.text.MessageFormat;
94 import java.util.ArrayList;
95 import java.util.Arrays;
96 import java.util.Calendar;
97 import java.util.Collections;
98 import java.util.Date;
99 import java.util.EnumSet;
100 import java.util.HashMap;
101 import java.util.List;
102 import java.util.Map;
103 import java.util.concurrent.CountDownLatch;
106 * Setting the main menu and implements application delegate methods
108 * @version $Id$
110 public class MainController extends BundleController implements NSApplication.Delegate {
111 private static Logger log = Logger.getLogger(MainController.class);
114 * Apple event constants<br>
115 * **********************************************************************************************<br>
116 * <i>native declaration : /Developer/SDKs/MacOSX10.5.sdk/usr/include/AvailabilityMacros.h:117</i>
118 public static final int kInternetEventClass = 1196773964;
120 * Apple event constants<br>
121 * **********************************************************************************************<br>
122 * <i>native declaration : /Developer/SDKs/MacOSX10.5.sdk/usr/include/AvailabilityMacros.h:118</i>
124 public static final int kAEGetURL = 1196773964;
126 * Apple event constants<br>
127 * **********************************************************************************************<br>
128 * <i>native declaration : /Developer/SDKs/MacOSX10.5.sdk/usr/include/AvailabilityMacros.h:119</i>
130 public static final int kAEFetchURL = 1179996748;
132 /// 0x2d2d2d2d
133 public static final int keyAEResult = 757935405;
135 private final Preferences preferences = PreferencesFactory.get();
137 public MainController() {
138 this.loadBundle();
141 @Override
142 public void awakeFromNib() {
143 NSAppleEventManager.sharedAppleEventManager().setEventHandler_andSelector_forEventClass_andEventID(
144 this.id(), Foundation.selector("handleGetURLEvent:withReplyEvent:"), kInternetEventClass, kAEGetURL);
146 super.awakeFromNib();
149 private PathKindDetector detector = new DefaultPathKindDetector();
152 * Extract the URL from the Apple event and handle it here.
154 public void handleGetURLEvent_withReplyEvent(NSAppleEventDescriptor event, NSAppleEventDescriptor reply) {
155 log.debug("Received URL from Apple Event:" + event);
156 final NSAppleEventDescriptor param = event.paramDescriptorForKeyword(keyAEResult);
157 if(null == param) {
158 log.error("No URL parameter");
159 return;
161 final String url = param.stringValue();
162 if(StringUtils.isEmpty(url)) {
163 log.error("URL parameter is empty");
164 return;
166 if("x-cyberduck-action:update".equals(url)) {
167 this.updateMenuClicked(null);
169 else {
170 final Host h = HostParser.parse(url);
171 if(Path.Type.file == detector.detect(h.getDefaultPath())) {
172 final Path file = new Path(h.getDefaultPath(), EnumSet.of(Path.Type.file));
173 TransferControllerFactory.get().start(new DownloadTransfer(h, file,
174 LocalFactory.get(preferences.getProperty("queue.download.folder"), file.getName())));
176 else {
177 for(BrowserController browser : MainController.getBrowsers()) {
178 if(browser.isMounted()) {
179 if(new HostUrlProvider().get(browser.getSession().getHost()).equals(
180 new HostUrlProvider().get(h))) {
181 // Handle browser window already connected to the same host. #4215
182 browser.window().makeKeyAndOrderFront(null);
183 return;
187 final BrowserController browser = newDocument(false);
188 browser.mount(h);
193 private Updater updater;
195 @Action
196 public void updateMenuClicked(ID sender) {
197 if(null == updater) {
198 updater = Updater.create();
200 updater.checkForUpdates(null);
203 @Outlet
204 private NSMenu applicationMenu;
206 public void setApplicationMenu(NSMenu menu) {
207 this.applicationMenu = menu;
208 this.updateLicenseMenu();
209 this.updateUpdateMenu();
213 * Set name of key in menu item
215 private void updateLicenseMenu() {
216 final License key = LicenseFactory.find();
217 if(null == Updater.getFeed() && key.isReceipt()) {
218 this.applicationMenu.removeItemAtIndex(new NSInteger(5));
219 this.applicationMenu.removeItemAtIndex(new NSInteger(4));
221 else {
222 NSDictionary KEY_FONT_ATTRIBUTES = NSDictionary.dictionaryWithObjectsForKeys(
223 NSArray.arrayWithObjects(NSFont.userFontOfSize(NSFont.smallSystemFontSize()), NSColor.darkGrayColor()),
224 NSArray.arrayWithObjects(NSAttributedString.FontAttributeName, NSAttributedString.ForegroundColorAttributeName)
226 this.applicationMenu.itemAtIndex(new NSInteger(5)).setAttributedTitle(
227 NSAttributedString.attributedStringWithAttributes(key.toString(), KEY_FONT_ATTRIBUTES)
233 * Remove software update menu item if no update feed available
235 private void updateUpdateMenu() {
236 if(null == Updater.getFeed()) {
237 this.applicationMenu.removeItemAtIndex(new NSInteger(1));
241 @Outlet
242 private NSMenu encodingMenu;
244 public void setEncodingMenu(NSMenu encodingMenu) {
245 this.encodingMenu = encodingMenu;
246 for(String charset : new DefaultCharsetProvider().availableCharsets()) {
247 this.encodingMenu.addItemWithTitle_action_keyEquivalent(charset, Foundation.selector("encodingMenuClicked:"), StringUtils.EMPTY);
251 @Outlet
252 private NSMenu columnMenu;
254 public void setColumnMenu(NSMenu columnMenu) {
255 this.columnMenu = columnMenu;
256 Map<String, String> columns = new HashMap<String, String>();
257 columns.put(String.format("browser.column.%s", Column.kind.name()), LocaleFactory.localizedString("Kind"));
258 columns.put(String.format("browser.column.%s", Column.extension.name()), LocaleFactory.localizedString("Extension"));
259 columns.put(String.format("browser.column.%s", Column.size.name()), LocaleFactory.localizedString("Size"));
260 columns.put(String.format("browser.column.%s", Column.modified.name()), LocaleFactory.localizedString("Modified"));
261 columns.put(String.format("browser.column.%s", Column.owner.name()), LocaleFactory.localizedString("Owner"));
262 columns.put(String.format("browser.column.%s", Column.group.name()), LocaleFactory.localizedString("Group"));
263 columns.put(String.format("browser.column.%s", Column.permission.name()), LocaleFactory.localizedString("Permissions"));
264 columns.put(String.format("browser.column.%s", Column.region.name()), LocaleFactory.localizedString("Region"));
265 columns.put(String.format("browser.column.%s", Column.version.name()), LocaleFactory.localizedString("Version"));
266 for(Map.Entry<String, String> entry : columns.entrySet()) {
267 NSMenuItem item = this.columnMenu.addItemWithTitle_action_keyEquivalent(entry.getValue(),
268 Foundation.selector("columnMenuClicked:"), StringUtils.EMPTY);
269 final String identifier = entry.getKey();
270 item.setState(preferences.getBoolean(identifier) ? NSCell.NSOnState : NSCell.NSOffState);
271 item.setRepresentedObject(identifier);
275 @Action
276 public void columnMenuClicked(final NSMenuItem sender) {
277 final String identifier = sender.representedObject();
278 final boolean enabled = !preferences.getBoolean(identifier);
279 sender.setState(enabled ? NSCell.NSOnState : NSCell.NSOffState);
280 preferences.setProperty(identifier, enabled);
281 BrowserController.updateBrowserTableColumns();
284 @Outlet
285 private NSMenu editMenu;
287 @Delegate
288 private EditMenuDelegate editMenuDelegate;
290 public void setEditMenu(NSMenu editMenu) {
291 this.editMenu = editMenu;
292 this.editMenuDelegate = new EditMenuDelegate() {
293 @Override
294 protected Path getEditable() {
295 final List<BrowserController> b = MainController.getBrowsers();
296 for(BrowserController controller : b) {
297 if(controller.window().isKeyWindow()) {
298 final Path selected = controller.getSelectedPath();
299 if(null == selected) {
300 return null;
302 if(controller.isEditable(selected)) {
303 return selected;
305 return null;
308 return null;
311 @Override
312 protected ID getTarget() {
313 return MainController.getBrowser().id();
316 this.editMenu.setDelegate(editMenuDelegate.id());
319 @Outlet
320 private NSMenu urlMenu;
322 @Delegate
323 private URLMenuDelegate urlMenuDelegate;
325 public void setUrlMenu(NSMenu urlMenu) {
326 this.urlMenu = urlMenu;
327 this.urlMenuDelegate = new CopyURLMenuDelegate() {
328 @Override
329 protected Session<?> getSession() {
330 final List<BrowserController> b = MainController.getBrowsers();
331 for(BrowserController controller : b) {
332 if(controller.window().isKeyWindow()) {
333 if(controller.isMounted()) {
334 return controller.getSession();
338 return null;
341 @Override
342 protected List<Path> getSelected() {
343 final List<BrowserController> b = MainController.getBrowsers();
344 for(BrowserController controller : b) {
345 if(controller.window().isKeyWindow()) {
346 List<Path> selected = controller.getSelectedPaths();
347 if(selected.isEmpty()) {
348 if(controller.isMounted()) {
349 return Collections.singletonList(controller.workdir());
352 return selected;
355 return Collections.emptyList();
358 this.urlMenu.setDelegate(urlMenuDelegate.id());
361 @Outlet
362 private NSMenu openUrlMenu;
364 @Delegate
365 private URLMenuDelegate openUrlMenuDelegate;
367 public void setOpenUrlMenu(NSMenu openUrlMenu) {
368 this.openUrlMenu = openUrlMenu;
369 this.openUrlMenuDelegate = new OpenURLMenuDelegate() {
370 @Override
371 protected Session<?> getSession() {
372 final List<BrowserController> b = MainController.getBrowsers();
373 for(BrowserController controller : b) {
374 if(controller.window().isKeyWindow()) {
375 if(controller.isMounted()) {
376 return controller.getSession();
380 return null;
383 @Override
384 protected List<Path> getSelected() {
385 final List<BrowserController> b = MainController.getBrowsers();
386 for(BrowserController controller : b) {
387 if(controller.window().isKeyWindow()) {
388 List<Path> selected = controller.getSelectedPaths();
389 if(selected.isEmpty()) {
390 if(controller.isMounted()) {
391 return Collections.singletonList(controller.workdir());
394 return selected;
397 return Collections.emptyList();
400 this.openUrlMenu.setDelegate(openUrlMenuDelegate.id());
403 @Outlet
404 private NSMenu archiveMenu;
406 @Delegate
407 private ArchiveMenuDelegate archiveMenuDelegate;
409 public void setArchiveMenu(NSMenu archiveMenu) {
410 this.archiveMenu = archiveMenu;
411 this.archiveMenuDelegate = new ArchiveMenuDelegate();
412 this.archiveMenu.setDelegate(archiveMenuDelegate.id());
415 @Outlet
416 private NSMenu bookmarkMenu;
418 @Delegate
419 private BookmarkMenuDelegate bookmarkMenuDelegate;
421 public void setBookmarkMenu(NSMenu bookmarkMenu) {
422 this.bookmarkMenu = bookmarkMenu;
423 this.bookmarkMenuDelegate = new BookmarkMenuDelegate();
424 this.bookmarkMenu.setDelegate(bookmarkMenuDelegate.id());
427 @Action
428 public void bugreportMenuClicked(final ID sender) {
429 BrowserLauncherFactory.get().open(
430 MessageFormat.format(preferences.getProperty("website.bug"),
431 preferences.getProperty("application.version")));
434 @Action
435 public void helpMenuClicked(final ID sender) {
436 BrowserLauncherFactory.get().open(preferences.getProperty("website.help"));
439 @Action
440 public void licenseMenuClicked(final ID sender) {
441 ApplicationLauncherFactory.get().open(
442 LocalFactory.get(NSBundle.mainBundle().pathForResource_ofType("License", "txt")));
445 @Action
446 public void acknowledgmentsMenuClicked(final ID sender) {
447 ApplicationLauncherFactory.get().open(
448 LocalFactory.get(NSBundle.mainBundle().pathForResource_ofType("Acknowledgments", "rtf")));
451 @Action
452 public void websiteMenuClicked(final ID sender) {
453 BrowserLauncherFactory.get().open(preferences.getProperty("website.home"));
456 @Action
457 public void forumMenuClicked(final ID sender) {
458 BrowserLauncherFactory.get().open(preferences.getProperty("website.forum"));
461 @Action
462 public void donateMenuClicked(final ID sender) {
463 BrowserLauncherFactory.get().open(preferences.getProperty("website.donate"));
466 @Action
467 public void aboutMenuClicked(final ID sender) {
468 final NSDictionary dict = NSDictionary.dictionaryWithObjectsForKeys(
469 NSArray.arrayWithObjects(
470 preferences.getProperty("application.name"),
471 preferences.getProperty("application.version"),
472 preferences.getProperty("application.revision")),
473 NSArray.arrayWithObjects(
474 "ApplicationName",
475 "ApplicationVersion",
476 "Version")
478 NSApplication.sharedApplication().orderFrontStandardAboutPanelWithOptions(dict);
481 @Action
482 public void feedbackMenuClicked(final ID sender) {
483 BrowserLauncherFactory.get().open(preferences.getProperty("mail.feedback")
484 + "?subject=" + preferences.getProperty("application.name") + "-" + preferences.getProperty("application.version"));
487 @Action
488 public void preferencesMenuClicked(final ID sender) {
489 PreferencesController controller = PreferencesControllerFactory.instance();
490 controller.window().makeKeyAndOrderFront(null);
493 @Action
494 public void newDownloadMenuClicked(final ID sender) {
495 this.showTransferQueueClicked(sender);
496 SheetController c = new DownloadController(TransferControllerFactory.get());
497 c.beginSheet();
500 @Action
501 public void newBrowserMenuClicked(final ID sender) {
502 this.openDefaultBookmark(MainController.newDocument(true));
505 @Action
506 public void showTransferQueueClicked(final ID sender) {
507 TransferController c = TransferControllerFactory.get();
508 c.window().makeKeyAndOrderFront(null);
511 @Action
512 public void showActivityWindowClicked(final ID sender) {
513 ActivityController c = ActivityControllerFactory.get();
514 if(c.isVisible()) {
515 c.window().close();
517 else {
518 c.window().orderFront(null);
522 @Override
523 public boolean application_openFile(final NSApplication app, final String filename) {
524 if(log.isDebugEnabled()) {
525 log.debug(String.format("Open file %s", filename));
527 final Local f = LocalFactory.get(filename);
528 if(f.exists()) {
529 if("duck".equals(f.getExtension())) {
530 final Host bookmark;
531 try {
532 bookmark = HostReaderFactory.get().read(f);
533 if(null == bookmark) {
534 return false;
536 MainController.newDocument().mount(bookmark);
537 return true;
539 catch(AccessDeniedException e) {
540 log.error(e.getMessage());
541 return false;
544 else if("cyberducklicense".equals(f.getExtension())) {
545 final License l = LicenseFactory.get(f);
546 if(l.verify()) {
547 try {
548 f.copy(LocalFactory.get(preferences.getProperty("application.support.path"), f.getName()));
549 final NSAlert alert = NSAlert.alert(
550 l.toString(),
551 LocaleFactory.localizedString("Thanks for your support! Your contribution helps to further advance development to make Cyberduck even better.", "License")
552 + "\n\n"
553 + LocaleFactory.localizedString("Your donation key has been copied to the Application Support folder.", "License"),
554 LocaleFactory.localizedString("Continue", "License"), //default
555 null, //other
556 null
558 alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
559 if(this.alert(alert) == SheetCallback.DEFAULT_OPTION) {
560 for(BrowserController c : MainController.getBrowsers()) {
561 c.removeDonateWindowTitle();
563 this.updateLicenseMenu();
566 catch(AccessDeniedException e) {
567 log.error(e.getMessage());
568 return false;
571 else {
572 final NSAlert alert = NSAlert.alert(
573 LocaleFactory.localizedString("Not a valid donation key", "License"),
574 LocaleFactory.localizedString("This donation key does not appear to be valid.", "License"),
575 LocaleFactory.localizedString("Continue", "License"), //default
576 null, //other
577 null);
578 alert.setAlertStyle(NSAlert.NSWarningAlertStyle);
579 alert.setShowsHelp(true);
580 alert.setDelegate(new ProxyController() {
581 @Action
582 public boolean alertShowHelp(NSAlert alert) {
583 StringBuilder site = new StringBuilder(preferences.getProperty("website.help"));
584 site.append("/").append("faq");
585 BrowserLauncherFactory.get().open(site.toString());
586 return true;
589 }.id());
590 this.alert(alert);
592 return true;
594 else if("cyberduckprofile".equals(f.getExtension())) {
595 try {
596 final Protocol profile = ProfileReaderFactory.get().read(f);
597 if(null == profile) {
598 return false;
600 if(profile.isEnabled()) {
601 if(log.isDebugEnabled()) {
602 log.debug(String.format("Register profile %s", profile));
604 ProtocolFactory.register(profile);
605 final Host host = new Host(profile, profile.getDefaultHostname(), profile.getDefaultPort());
606 MainController.newDocument().addBookmark(host);
607 // Register in application support
608 final Local profiles = LocalFactory.get(preferences.getProperty("application.support.path"),
609 PreferencesFactory.get().getProperty("profiles.folder.name"));
610 profiles.mkdir();
611 f.copy(LocalFactory.get(profiles, f.getName()));
614 catch(AccessDeniedException e) {
615 log.error(e.getMessage());
616 return false;
619 else {
620 // Upload file
621 this.background(new AbstractBackgroundAction<Void>() {
622 @Override
623 public Void run() throws BackgroundException {
624 // Wait until bookmarks are loaded
625 try {
626 bookmarksSemaphore.await();
628 catch(InterruptedException e) {
629 log.error(String.format("Error awaiting bookmarks to load %s", e.getMessage()));
631 return null;
634 @Override
635 public void cleanup() {
636 upload(f);
639 @Override
640 public String getActivity() {
641 return "Open File";
644 return true;
647 return false;
650 private boolean upload(final Local f) {
651 return this.upload(Collections.singletonList(f));
654 private boolean upload(final List<Local> files) {
655 // Selected bookmark
656 Host open = null;
657 Path workdir = null;
658 for(BrowserController controller : MainController.getBrowsers()) {
659 if(controller.isMounted()) {
660 open = controller.getSession().getHost();
661 workdir = controller.workdir();
662 if(1 == MainController.getBrowsers().size()) {
663 // If only one browser window upload to current working directory with no bookmark selection
664 this.upload(open, files, workdir);
665 return true;
667 break;
670 if(BookmarkCollection.defaultCollection().isEmpty()) {
671 log.warn("No bookmark for upload");
672 return false;
674 final NSPopUpButton bookmarksPopup = NSPopUpButton.buttonWithFrame(new NSRect(0, 26));
675 bookmarksPopup.setToolTip(LocaleFactory.localizedString("Bookmarks"));
676 for(Host b : BookmarkCollection.defaultCollection()) {
677 String title = BookmarkNameProvider.toString(b);
678 int i = 1;
679 while(bookmarksPopup.itemWithTitle(title) != null) {
680 title = BookmarkNameProvider.toString(b) + "-" + i;
681 i++;
683 bookmarksPopup.addItemWithTitle(title);
684 bookmarksPopup.lastItem().setImage(IconCacheFactory.<NSImage>get().iconNamed(b.getProtocol().icon(), 16));
685 bookmarksPopup.lastItem().setRepresentedObject(b.getUuid());
686 if(b.equals(open)) {
687 bookmarksPopup.selectItemAtIndex(bookmarksPopup.indexOfItem(bookmarksPopup.lastItem()));
690 if(null == open) {
691 int i = 0;
692 for(Host bookmark : BookmarkCollection.defaultCollection()) {
693 boolean found = false;
694 // Pick the bookmark with the same download location
695 for(Local file : files) {
696 if(file.isChild(new DownloadDirectoryFinder().find(bookmark))) {
697 bookmarksPopup.selectItemAtIndex(new NSInteger(i));
698 found = true;
699 break;
702 if(found) {
703 break;
705 i++;
708 if(-1 == bookmarksPopup.indexOfSelectedItem().intValue()) {
709 // No bookmark for current browser found
710 bookmarksPopup.selectItemAtIndex(new NSInteger(0));
712 final TransferController t = TransferControllerFactory.get();
713 final Host mount = open;
714 final Path destination = workdir;
715 AlertController alert = new AlertController(t, NSAlert.alert("Select Bookmark",
716 MessageFormat.format("Upload {0} to the selected bookmark.",
717 files.size() == 1 ? files.iterator().next().getName()
718 : MessageFormat.format(LocaleFactory.localizedString("{0} Files"), String.valueOf(files.size()))
720 LocaleFactory.localizedString("Upload", "Transfer"),
721 LocaleFactory.localizedString("Cancel"),
722 null
723 )) {
724 @Override
725 public void callback(int returncode) {
726 if(DEFAULT_OPTION == returncode) {
727 final String selected = bookmarksPopup.selectedItem().representedObject();
728 for(Host bookmark : BookmarkCollection.defaultCollection()) {
729 // Determine selected bookmark
730 if(bookmark.getUuid().equals(selected)) {
731 if(bookmark.equals(mount)) {
732 // Use current working directory of browser for destination
733 upload(bookmark, files, destination);
735 else {
736 // No mounted browser
737 if(StringUtils.isNotBlank(bookmark.getDefaultPath())) {
738 upload(bookmark, files, new Path(bookmark.getDefaultPath(), EnumSet.of(Path.Type.directory)));
740 else {
741 upload(bookmark, files, destination);
744 break;
750 @Override
751 protected boolean validateInput() {
752 return StringUtils.isNotEmpty(bookmarksPopup.selectedItem().representedObject());
755 alert.setAccessoryView(bookmarksPopup);
756 alert.beginSheet();
757 return true;
760 private void upload(final Host bookmark, final List<Local> files, final Path destination) {
761 final List<TransferItem> roots = new ArrayList<TransferItem>();
762 for(Local file : files) {
763 roots.add(new TransferItem(new Path(destination, file.getName(),
764 file.isDirectory() ? EnumSet.of(Path.Type.directory) : EnumSet.of(Path.Type.file)), file));
766 final TransferController t = TransferControllerFactory.get();
767 t.start(new UploadTransfer(bookmark, roots));
771 * Sent directly by theApplication to the delegate. The method should attempt to open the file filename,
772 * returning true if the file is successfully opened, and false otherwise. By design, a
773 * file opened through this method is assumed to be temporary its the application's
774 * responsibility to remove the file at the appropriate time.
776 @Override
777 public boolean application_openTempFile(NSApplication app, String filename) {
778 if(log.isDebugEnabled()) {
779 log.debug("applicationOpenTempFile:" + filename);
781 return this.application_openFile(app, filename);
785 * Invoked immediately before opening an untitled file. Return false to prevent
786 * the application from opening an untitled file; return true otherwise.
787 * Note that applicationOpenUntitledFile is invoked if this method returns true.
789 @Override
790 public boolean applicationShouldOpenUntitledFile(NSApplication sender) {
791 if(log.isDebugEnabled()) {
792 log.debug("applicationShouldOpenUntitledFile");
794 return preferences.getBoolean("browser.open.untitled");
798 * @return true if the file was successfully opened, false otherwise.
800 @Override
801 public boolean applicationOpenUntitledFile(NSApplication app) {
802 if(log.isDebugEnabled()) {
803 log.debug("applicationOpenUntitledFile");
805 return false;
809 * Mounts the default bookmark if any
811 private void openDefaultBookmark(BrowserController controller) {
812 String defaultBookmark = preferences.getProperty("browser.open.bookmark.default");
813 if(null == defaultBookmark) {
814 log.info("No default bookmark configured");
815 return; //No default bookmark given
817 Host bookmark = BookmarkCollection.defaultCollection().lookup(defaultBookmark);
818 if(null == bookmark) {
819 log.info("Default bookmark no more available");
820 return;
822 for(BrowserController browser : getBrowsers()) {
823 if(browser.getSession() != null) {
824 if(browser.getSession().getHost().equals(bookmark)) {
825 log.debug("Default bookmark already mounted");
826 return;
830 if(log.isDebugEnabled()) {
831 log.debug(String.format("Mounting default bookmark %s", bookmark));
833 controller.mount(bookmark);
837 * These events are sent whenever the Finder reactivates an already running application
838 * because someone double-clicked it again or used the dock to activate it. By default
839 * the Application Kit will handle this event by checking whether there are any visible
840 * NSWindows (not NSPanels), and, if there are none, it goes through the standard untitled
841 * document creation (the same as it does if theApplication is launched without any document
842 * to open). For most document-based applications, an untitled document will be created.
843 * The application delegate will also get a chance to respond to the normal untitled document
844 * delegations. If you implement this method in your application delegate, it will be called
845 * before any of the default behavior happens. If you return true, then NSApplication will
846 * go on to do its normal thing. If you return false, then NSApplication will do nothing.
847 * So, you can either implement this method, do nothing, and return false if you do not
848 * want anything to happen at all (not recommended), or you can implement this method,
849 * handle the event yourself in some custom way, and return false.
851 @Override
852 public boolean applicationShouldHandleReopen_hasVisibleWindows(final NSApplication app, final boolean visibleWindowsFound) {
853 if(log.isDebugEnabled()) {
854 log.debug(String.format("Should handle reopen with windows %s", visibleWindowsFound));
855 } // While an application is open, the Dock icon has a symbol below it.
856 // When a user clicks an open application’s icon in the Dock, the application
857 // becomes active and all open unminimized windows are brought to the front;
858 // minimized document windows remain in the Dock. If there are no unminimized
859 // windows when the user clicks the Dock icon, the last minimized window should
860 // be expanded and made active. If no documents are open, the application should
861 // open a new window. (If your application is not document-based, display the
862 // application’s main window.)
863 if(MainController.getBrowsers().isEmpty() && !TransferControllerFactory.get().isVisible()) {
864 this.openDefaultBookmark(MainController.newDocument());
866 NSWindow miniaturized = null;
867 for(BrowserController controller : MainController.getBrowsers()) {
868 if(!controller.window().isMiniaturized()) {
869 return false;
871 if(null == miniaturized) {
872 miniaturized = controller.window();
875 if(null == miniaturized) {
876 return false;
878 miniaturized.deminiaturize(null);
879 return false;
882 // User bookmarks and thirdparty applications
883 private final CountDownLatch bookmarksSemaphore = new CountDownLatch(1);
884 private final CountDownLatch thirdpartySemaphore = new CountDownLatch(1);
887 * Sent by the default notification center after the application has been launched and initialized but
888 * before it has received its first event. aNotification is always an
889 * ApplicationDidFinishLaunchingNotification. You can retrieve the NSApplication
890 * object in question by sending object to aNotification. The delegate can implement
891 * this method to perform further initialization. If the user started up the application
892 * by double-clicking a file, the delegate receives the applicationOpenFile message before receiving
893 * applicationDidFinishLaunching. (applicationWillFinishLaunching is sent before applicationOpenFile.)
895 @Override
896 public void applicationDidFinishLaunching(NSNotification notification) {
897 if(preferences.getBoolean("browser.open.untitled")) {
898 MainController.newDocument();
900 if(preferences.getBoolean("queue.window.open.default")) {
901 this.showTransferQueueClicked(null);
903 if(preferences.getBoolean("browser.serialize")) {
904 this.background(new AbstractBackgroundAction<Void>() {
905 @Override
906 public Void run() throws BackgroundException {
907 sessions.load();
908 return null;
911 @Override
912 public void cleanup() {
913 for(Host host : sessions) {
914 if(log.isInfoEnabled()) {
915 log.info(String.format("New browser for saved session %s", host));
917 final BrowserController browser = MainController.newDocument(false, host.getUuid());
918 browser.mount(host);
920 sessions.clear();
924 // Load all bookmarks in background
925 this.background(new AbstractBackgroundAction<Void>() {
926 @Override
927 public Void run() throws BackgroundException {
928 final BookmarkCollection c = BookmarkCollection.defaultCollection();
929 c.load();
930 bookmarksSemaphore.countDown();
931 return null;
934 @Override
935 public void cleanup() {
936 if(preferences.getBoolean("browser.open.untitled")) {
937 if(preferences.getProperty("browser.open.bookmark.default") != null) {
938 openDefaultBookmark(MainController.newDocument());
941 // Set delegate for NSService
942 NSApplication.sharedApplication().setServicesProvider(MainController.this.id());
945 @Override
946 public String getActivity() {
947 return "Loading Bookmarks";
950 this.background(new AbstractBackgroundAction<Void>() {
951 @Override
952 public Void run() throws BackgroundException {
953 HistoryCollection.defaultCollection().load();
954 return null;
957 @Override
958 public String getActivity() {
959 return "Loading History";
962 this.background(new AbstractBackgroundAction<Void>() {
963 @Override
964 public Void run() throws BackgroundException {
965 TransferCollection.defaultCollection().load();
966 return null;
969 @Override
970 public String getActivity() {
971 return "Loading Transfers";
974 this.background(new AbstractBackgroundAction<Void>() {
975 @Override
976 public Void run() throws BackgroundException {
977 // Make sure we register to Growl first
978 NotificationServiceFactory.get().setup();
979 return null;
982 @Override
983 public String getActivity() {
984 return "Registering Growl";
987 final Rendezvous bonjour = RendezvousFactory.instance();
988 bonjour.addListener(new NotificationRendezvousListener(bonjour));
989 if(preferences.getBoolean("defaulthandler.reminder")
990 && preferences.getInteger("uses") > 0) {
991 if(!SchemeHandlerFactory.get().isDefaultHandler(
992 Arrays.asList(Scheme.ftp, Scheme.ftps, Scheme.sftp),
993 new Application(NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleIdentifier").toString()))) {
994 final NSAlert alert = NSAlert.alert(
995 LocaleFactory.localizedString("Set Cyberduck as default application for FTP and SFTP locations?", "Configuration"),
996 LocaleFactory.localizedString("As the default application, Cyberduck will open when you click on FTP or SFTP links " +
997 "in other applications, such as your web browser. You can change this setting in the Preferences later.", "Configuration"),
998 LocaleFactory.localizedString("Change", "Configuration"), //default
999 null, //other
1000 LocaleFactory.localizedString("Cancel", "Configuration")
1002 alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
1003 alert.setShowsSuppressionButton(true);
1004 alert.suppressionButton().setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
1005 int choice = alert.runModal(); //alternate
1006 if(alert.suppressionButton().state() == NSCell.NSOnState) {
1007 // Never show again.
1008 preferences.setProperty("defaulthandler.reminder", false);
1010 if(choice == SheetCallback.DEFAULT_OPTION) {
1011 SchemeHandlerFactory.get().setDefaultHandler(
1012 Arrays.asList(Scheme.ftp, Scheme.ftps, Scheme.sftp),
1013 new Application(NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleIdentifier").toString())
1018 // NSWorkspace notifications are posted to a notification center provided by
1019 // the NSWorkspace object, instead of going through the application’s default
1020 // notification center as most notifications do. To receive NSWorkspace notifications,
1021 // your application must register an observer with the NSWorkspace notification center.
1022 NSWorkspace.sharedWorkspace().notificationCenter().addObserver(this.id(),
1023 Foundation.selector("workspaceWillPowerOff:"),
1024 NSWorkspace.WorkspaceWillPowerOffNotification,
1025 null);
1026 NSWorkspace.sharedWorkspace().notificationCenter().addObserver(this.id(),
1027 Foundation.selector("workspaceWillLogout:"),
1028 NSWorkspace.WorkspaceSessionDidResignActiveNotification,
1029 null);
1030 NSWorkspace.sharedWorkspace().notificationCenter().addObserver(this.id(),
1031 Foundation.selector("workspaceWillSleep:"),
1032 NSWorkspace.WorkspaceWillSleepNotification,
1033 null);
1034 NSNotificationCenter.defaultCenter().addObserver(this.id(),
1035 Foundation.selector("applicationWillRestartAfterUpdate:"),
1036 "SUUpdaterWillRestartNotificationName",
1037 null);
1038 this.background(new AbstractBackgroundAction<Void>() {
1039 @Override
1040 public Void run() throws BackgroundException {
1041 bonjour.init();
1042 return null;
1045 // Import thirdparty bookmarks.
1046 this.background(new AbstractBackgroundAction<Void>() {
1047 private List<ThirdpartyBookmarkCollection> thirdpartyBookmarkCollections = Collections.emptyList();
1049 @Override
1050 public Void run() {
1051 thirdpartyBookmarkCollections = this.getThirdpartyBookmarks();
1052 for(ThirdpartyBookmarkCollection t : thirdpartyBookmarkCollections) {
1053 if(!t.isInstalled()) {
1054 if(log.isInfoEnabled()) {
1055 log.info(String.format("No application installed for %s", t.getBundleIdentifier()));
1057 continue;
1059 try {
1060 t.load();
1062 catch(AccessDeniedException e) {
1063 log.warn(String.format("Failure %s loading bookmarks from %s", e, t));
1065 if(t.isEmpty()) {
1066 // Flag as imported
1067 preferences.setProperty(t.getConfiguration(), true);
1070 try {
1071 bookmarksSemaphore.await();
1073 catch(InterruptedException e) {
1074 log.error(String.format("Error awaiting bookmarks to load %s", e.getMessage()));
1076 return null;
1079 @Override
1080 public void cleanup() {
1081 for(ThirdpartyBookmarkCollection t : thirdpartyBookmarkCollections) {
1082 final BookmarkCollection bookmarks = BookmarkCollection.defaultCollection();
1083 t.filter(bookmarks);
1084 if(t.isEmpty()) {
1085 preferences.setProperty(t.getConfiguration(), true);
1086 continue;
1088 final NSAlert alert = NSAlert.alert(
1089 MessageFormat.format(LocaleFactory.localizedString("Import {0} Bookmarks", "Configuration"), t.getName()),
1090 MessageFormat.format(LocaleFactory.localizedString("{0} bookmarks found. Do you want to add these to your bookmarks?", "Configuration"), t.size()),
1091 LocaleFactory.localizedString("Import", "Configuration"), //default
1092 null, //other
1093 LocaleFactory.localizedString("Cancel", "Configuration"));
1094 alert.setShowsSuppressionButton(true);
1095 alert.suppressionButton().setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
1096 alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
1097 int choice = alert.runModal(); //alternate
1098 if(alert.suppressionButton().state() == NSCell.NSOnState) {
1099 // Never show again.
1100 preferences.setProperty(t.getConfiguration(), true);
1102 if(choice == SheetCallback.DEFAULT_OPTION) {
1103 bookmarks.addAll(t);
1104 // Flag as imported
1105 preferences.setProperty(t.getConfiguration(), true);
1108 thirdpartySemaphore.countDown();
1111 @Override
1112 public String getActivity() {
1113 return "Loading thirdparty bookmarks";
1116 private List<ThirdpartyBookmarkCollection> getThirdpartyBookmarks() {
1117 return Arrays.asList(new TransmitBookmarkCollection(), new FilezillaBookmarkCollection(), new FetchBookmarkCollection(),
1118 new FlowBookmarkCollection(), new InterarchyBookmarkCollection(), new CrossFtpBookmarkCollection(), new FireFtpBookmarkCollection());
1124 * NSService implementation
1126 public void serviceUploadFileUrl_(final NSPasteboard pboard, final String userData) {
1127 if(log.isDebugEnabled()) {
1128 log.debug(String.format("serviceUploadFileUrl_: with user data %s", userData));
1130 if(pboard.availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) {
1131 NSObject o = pboard.propertyListForType(NSPasteboard.FilenamesPboardType);
1132 if(o != null) {
1133 if(o.isKindOfClass(Rococoa.createClass("NSArray", NSArray._Class.class))) {
1134 final NSArray elements = Rococoa.cast(o, NSArray.class);
1135 List<Local> files = new ArrayList<Local>();
1136 for(int i = 0; i < elements.count().intValue(); i++) {
1137 files.add(LocalFactory.get(elements.objectAtIndex(new NSUInteger(i)).toString()));
1139 this.upload(files);
1146 * Saved browsers
1148 private AbstractHostCollection sessions = new FolderBookmarkCollection(
1149 LocalFactory.get(preferences.getProperty("application.support.path"), "Sessions"), "session");
1152 * Display donation reminder dialog
1154 private boolean displayDonationPrompt = true;
1156 @Outlet
1157 private WindowController donationController;
1161 * Invoked from within the terminate method immediately before the
1162 * application terminates. sender is the NSApplication to be terminated.
1163 * If this method returns false, the application is not terminated,
1164 * and control returns to the main event loop.
1166 * @param app Application instance
1167 * @return Return true to allow the application to terminate.
1169 @Override
1170 public NSUInteger applicationShouldTerminate(final NSApplication app) {
1171 if(log.isDebugEnabled()) {
1172 log.debug("Application should quit with notification");
1174 // Determine if there are any running transfers
1175 final NSUInteger result = TransferControllerFactory.applicationShouldTerminate(app);
1176 if(!result.equals(NSApplication.NSTerminateNow)) {
1177 return result;
1179 // Determine if there are any open connections
1180 for(BrowserController browser : MainController.getBrowsers()) {
1181 if(preferences.getBoolean("browser.serialize")) {
1182 if(browser.isMounted()) {
1183 // The workspace should be saved. Serialize all open browser sessions
1184 final Host serialized
1185 = new HostDictionary().deserialize(browser.getSession().getHost().serialize(SerializerFactory.get()));
1186 serialized.setWorkdir(browser.workdir());
1187 sessions.add(serialized);
1188 browser.window().saveFrameUsingName(serialized.getUuid());
1191 if(browser.isConnected()) {
1192 if(preferences.getBoolean("browser.disconnect.confirm")) {
1193 final NSAlert alert = NSAlert.alert(LocaleFactory.localizedString("Quit"),
1194 LocaleFactory.localizedString("You are connected to at least one remote site. Do you want to review open browsers?"),
1195 LocaleFactory.localizedString("Quit Anyway"), //default
1196 LocaleFactory.localizedString("Cancel"), //other
1197 LocaleFactory.localizedString("Review…"));
1198 alert.setAlertStyle(NSAlert.NSWarningAlertStyle);
1199 alert.setShowsSuppressionButton(true);
1200 alert.suppressionButton().setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
1201 int choice = alert.runModal(); //alternate
1202 if(alert.suppressionButton().state() == NSCell.NSOnState) {
1203 // Never show again.
1204 preferences.setProperty("browser.disconnect.confirm", false);
1206 if(choice == SheetCallback.ALTERNATE_OPTION) {
1207 // Cancel. Quit has been interrupted. Delete any saved sessions so far.
1208 sessions.clear();
1209 return NSApplication.NSTerminateCancel;
1211 if(choice == SheetCallback.CANCEL_OPTION) {
1212 // Review if at least one window requested to terminate later, we shall wait.
1213 // This will iterate over all mounted browsers.
1214 if(NSApplication.NSTerminateNow.equals(BrowserController.applicationShouldTerminate(app))) {
1215 return this.applicationShouldTerminateAfterDonationPrompt(app);
1217 return NSApplication.NSTerminateLater;
1219 if(choice == SheetCallback.DEFAULT_OPTION) {
1220 // Quit immediatly
1221 return this.applicationShouldTerminateAfterDonationPrompt(app);
1224 else {
1225 browser.windowShouldClose(browser.window());
1229 return this.applicationShouldTerminateAfterDonationPrompt(app);
1232 public NSUInteger applicationShouldTerminateAfterDonationPrompt(final NSApplication app) {
1233 if(log.isDebugEnabled()) {
1234 log.debug("applicationShouldTerminateAfterDonationPrompt");
1236 if(!displayDonationPrompt) {
1237 // Already displayed
1238 return NSApplication.NSTerminateNow;
1240 final License l = LicenseFactory.find();
1241 if(!l.verify()) {
1242 final String lastversion = preferences.getProperty("donate.reminder");
1243 if(NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleShortVersionString").toString().equals(lastversion)) {
1244 // Do not display if same version is installed
1245 return NSApplication.NSTerminateNow;
1247 final Calendar nextreminder = Calendar.getInstance();
1248 nextreminder.setTimeInMillis(preferences.getLong("donate.reminder.date"));
1249 // Display donationPrompt every n days
1250 nextreminder.add(Calendar.DAY_OF_YEAR, preferences.getInteger("y"));
1251 if(log.isDebugEnabled()) {
1252 log.debug(String.format("Next reminder %s", nextreminder.getTime().toString()));
1254 // Display after upgrade
1255 if(nextreminder.getTime().after(new Date(System.currentTimeMillis()))) {
1256 // Do not display if shown in the reminder interval
1257 return NSApplication.NSTerminateNow;
1259 // Make sure prompt is not loaded twice upon next quit event
1260 displayDonationPrompt = false;
1261 final int uses = preferences.getInteger("uses");
1262 donationController = new WindowController() {
1263 @Override
1264 protected String getBundleName() {
1265 return "Donate";
1268 @Outlet
1269 private NSButton neverShowDonationCheckbox;
1271 public void setNeverShowDonationCheckbox(NSButton neverShowDonationCheckbox) {
1272 this.neverShowDonationCheckbox = neverShowDonationCheckbox;
1273 this.neverShowDonationCheckbox.setTarget(this.id());
1274 this.neverShowDonationCheckbox.setState(
1275 preferences.getProperty("donate.reminder").equals(
1276 NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleShortVersionString").toString())
1277 ? NSCell.NSOnState : NSCell.NSOffState
1281 @Override
1282 public void awakeFromNib() {
1283 this.window().setTitle(this.window().title() + " (" + uses + ")");
1284 this.window().center();
1285 this.window().makeKeyAndOrderFront(null);
1287 super.awakeFromNib();
1290 public void closeDonationSheet(final NSButton sender) {
1291 if(sender.tag() == SheetCallback.DEFAULT_OPTION) {
1292 BrowserLauncherFactory.get().open(preferences.getProperty("website.donate"));
1294 this.terminate();
1297 @Override
1298 public void windowWillClose(NSNotification notification) {
1299 this.terminate();
1300 super.windowWillClose(notification);
1303 private void terminate() {
1304 if(neverShowDonationCheckbox.state() == NSCell.NSOnState) {
1305 preferences.setProperty("donate.reminder",
1306 NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleShortVersionString").toString());
1308 // Remember this reminder date
1309 preferences.setProperty("donate.reminder.date", System.currentTimeMillis());
1310 // Quit again
1311 app.replyToApplicationShouldTerminate(true);
1314 donationController.loadBundle();
1315 // Delay application termination. Dismissing the donation dialog will reply to quit.
1316 return NSApplication.NSTerminateLater;
1318 return NSApplication.NSTerminateNow;
1322 * Quits the Rendezvous daemon and saves all preferences
1324 * @param notification Notification name
1326 @Override
1327 public void applicationWillTerminate(NSNotification notification) {
1328 if(log.isDebugEnabled()) {
1329 log.debug(String.format("Application will quit with notification %s", notification));
1331 this.invalidate();
1333 //Terminating rendezvous discovery
1334 RendezvousFactory.instance().quit();
1336 //Writing usage info
1337 preferences.setProperty("uses", preferences.getInteger("uses") + 1);
1338 preferences.save();
1341 public void applicationWillRestartAfterUpdate(ID updater) {
1342 // Disable donation prompt after udpate install
1343 displayDonationPrompt = false;
1347 * Posted when the user has requested a logout or that the machine be powered off.
1349 * @param notification Notification name
1351 public void workspaceWillPowerOff(NSNotification notification) {
1352 if(log.isDebugEnabled()) {
1353 log.debug(String.format("Workspace will power off with notification %s", notification));
1358 * Posted before a user session is switched out. This allows an application to
1359 * disable some processing when its user session is switched out, and reenable when that
1360 * session gets switched back in, for example.
1362 * @param notification Notification name
1364 public void workspaceWillLogout(NSNotification notification) {
1365 if(log.isDebugEnabled()) {
1366 log.debug(String.format("Workspace will logout with notification %s", notification));
1370 public void workspaceWillSleep(NSNotification notification) {
1371 if(log.isDebugEnabled()) {
1372 log.debug(String.format("Workspace will sleep with notification %s", notification));
1377 * Makes a unmounted browser window the key window and brings it to the front
1379 * @return A reference to a browser window
1381 public static BrowserController newDocument() {
1382 return MainController.newDocument(false);
1388 private static List<BrowserController> browsers
1389 = new ArrayList<BrowserController>();
1391 public static List<BrowserController> getBrowsers() {
1392 return browsers;
1396 * Browser with key focus
1398 * @return Null if no browser window is open
1400 public static BrowserController getBrowser() {
1401 for(BrowserController browser : MainController.getBrowsers()) {
1402 if(browser.window().isKeyWindow()) {
1403 return browser;
1406 return null;
1411 * Makes a unmounted browser window the key window and brings it to the front
1413 * @param force If true, open a new browser regardless of any unused browser window
1415 public static BrowserController newDocument(final boolean force) {
1416 return newDocument(force, null);
1420 * @param frame Frame autosave name
1422 public static BrowserController newDocument(final boolean force, final String frame) {
1423 final List<BrowserController> browsers = MainController.getBrowsers();
1424 if(!force) {
1425 for(BrowserController controller : browsers) {
1426 if(controller.getSession() == null) {
1427 controller.window().makeKeyAndOrderFront(null);
1428 return controller;
1432 final BrowserController controller = new BrowserController();
1433 controller.addListener(new WindowListener() {
1434 @Override
1435 public void windowWillClose() {
1436 browsers.remove(controller);
1439 if(StringUtils.isNotBlank(frame)) {
1440 controller.window().setFrameUsingName(frame);
1442 controller.window().makeKeyAndOrderFront(null);
1443 browsers.add(controller);
1444 return controller;
1448 * We are not a Windows application. Long live the application wide menu bar.
1450 @Override
1451 public boolean applicationShouldTerminateAfterLastWindowClosed(NSApplication app) {
1452 return false;
1455 @Override
1456 protected String getBundleName() {
1457 return "Main";