Fix transcript in transfer window.
[cyberduck.git] / source / ch / cyberduck / ui / cocoa / MainController.java
blobf7f952c2c9ea5f512d02ed60203d78b3eef25ca0
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.Rendezvous;
49 import ch.cyberduck.core.bonjour.RendezvousFactory;
50 import ch.cyberduck.core.bonjour.RendezvousListener;
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.threading.DefaultMainAction;
72 import ch.cyberduck.core.transfer.DownloadTransfer;
73 import ch.cyberduck.core.transfer.TransferItem;
74 import ch.cyberduck.core.transfer.UploadTransfer;
75 import ch.cyberduck.core.urlhandler.SchemeHandlerFactory;
76 import ch.cyberduck.ui.browser.Column;
77 import ch.cyberduck.ui.browser.DownloadDirectoryFinder;
78 import ch.cyberduck.ui.cocoa.delegate.ArchiveMenuDelegate;
79 import ch.cyberduck.ui.cocoa.delegate.BookmarkMenuDelegate;
80 import ch.cyberduck.ui.cocoa.delegate.CopyURLMenuDelegate;
81 import ch.cyberduck.ui.cocoa.delegate.EditMenuDelegate;
82 import ch.cyberduck.ui.cocoa.delegate.HistoryMenuDelegate;
83 import ch.cyberduck.ui.cocoa.delegate.OpenURLMenuDelegate;
84 import ch.cyberduck.ui.cocoa.delegate.RendezvousMenuDelegate;
85 import ch.cyberduck.ui.cocoa.delegate.URLMenuDelegate;
87 import org.apache.commons.lang3.StringUtils;
88 import org.apache.log4j.Logger;
89 import org.rococoa.Foundation;
90 import org.rococoa.ID;
91 import org.rococoa.Rococoa;
92 import org.rococoa.cocoa.foundation.NSInteger;
93 import org.rococoa.cocoa.foundation.NSRect;
94 import org.rococoa.cocoa.foundation.NSUInteger;
96 import java.text.MessageFormat;
97 import java.util.ArrayList;
98 import java.util.Arrays;
99 import java.util.Calendar;
100 import java.util.Collections;
101 import java.util.Date;
102 import java.util.EnumSet;
103 import java.util.HashMap;
104 import java.util.List;
105 import java.util.Map;
106 import java.util.concurrent.CountDownLatch;
109 * Setting the main menu and implements application delegate methods
111 * @version $Id$
113 public class MainController extends BundleController implements NSApplication.Delegate {
114 private static Logger log = Logger.getLogger(MainController.class);
117 * Apple event constants<br>
118 * **********************************************************************************************<br>
119 * <i>native declaration : /Developer/SDKs/MacOSX10.5.sdk/usr/include/AvailabilityMacros.h:117</i>
121 public static final int kInternetEventClass = 1196773964;
123 * Apple event constants<br>
124 * **********************************************************************************************<br>
125 * <i>native declaration : /Developer/SDKs/MacOSX10.5.sdk/usr/include/AvailabilityMacros.h:118</i>
127 public static final int kAEGetURL = 1196773964;
129 * Apple event constants<br>
130 * **********************************************************************************************<br>
131 * <i>native declaration : /Developer/SDKs/MacOSX10.5.sdk/usr/include/AvailabilityMacros.h:119</i>
133 public static final int kAEFetchURL = 1179996748;
135 /// 0x2d2d2d2d
136 public static final int keyAEResult = 757935405;
138 private final Preferences preferences = PreferencesFactory.get();
140 public MainController() {
141 this.loadBundle();
144 @Override
145 public void awakeFromNib() {
146 NSAppleEventManager.sharedAppleEventManager().setEventHandler_andSelector_forEventClass_andEventID(
147 this.id(), Foundation.selector("handleGetURLEvent:withReplyEvent:"), kInternetEventClass, kAEGetURL);
149 super.awakeFromNib();
152 private PathKindDetector detector = new DefaultPathKindDetector();
155 * Extract the URL from the Apple event and handle it here.
157 public void handleGetURLEvent_withReplyEvent(NSAppleEventDescriptor event, NSAppleEventDescriptor reply) {
158 log.debug("Received URL from Apple Event:" + event);
159 final NSAppleEventDescriptor param = event.paramDescriptorForKeyword(keyAEResult);
160 if(null == param) {
161 log.error("No URL parameter");
162 return;
164 final String url = param.stringValue();
165 if(StringUtils.isEmpty(url)) {
166 log.error("URL parameter is empty");
167 return;
169 if("x-cyberduck-action:update".equals(url)) {
170 this.updateMenuClicked(null);
172 else {
173 final Host h = HostParser.parse(url);
174 if(Path.Type.file == detector.detect(h.getDefaultPath())) {
175 final Path file = new Path(h.getDefaultPath(), EnumSet.of(Path.Type.file));
176 TransferControllerFactory.get().start(new DownloadTransfer(h, file,
177 LocalFactory.get(preferences.getProperty("queue.download.folder"), file.getName())));
179 else {
180 for(BrowserController browser : MainController.getBrowsers()) {
181 if(browser.isMounted()) {
182 if(new HostUrlProvider().get(browser.getSession().getHost()).equals(
183 new HostUrlProvider().get(h))) {
184 // Handle browser window already connected to the same host. #4215
185 browser.window().makeKeyAndOrderFront(null);
186 return;
190 final BrowserController browser = newDocument(false);
191 browser.mount(h);
196 private Updater updater;
198 @Action
199 public void updateMenuClicked(ID sender) {
200 if(null == updater) {
201 updater = Updater.create();
203 updater.checkForUpdates(null);
206 @Outlet
207 private NSMenu applicationMenu;
209 public void setApplicationMenu(NSMenu menu) {
210 this.applicationMenu = menu;
211 this.updateLicenseMenu();
212 this.updateUpdateMenu();
216 * Set name of key in menu item
218 private void updateLicenseMenu() {
219 final License key = LicenseFactory.find();
220 if(null == Updater.getFeed() && key.isReceipt()) {
221 this.applicationMenu.removeItemAtIndex(new NSInteger(5));
222 this.applicationMenu.removeItemAtIndex(new NSInteger(4));
224 else {
225 NSDictionary KEY_FONT_ATTRIBUTES = NSDictionary.dictionaryWithObjectsForKeys(
226 NSArray.arrayWithObjects(NSFont.userFontOfSize(NSFont.smallSystemFontSize()), NSColor.darkGrayColor()),
227 NSArray.arrayWithObjects(NSAttributedString.FontAttributeName, NSAttributedString.ForegroundColorAttributeName)
229 this.applicationMenu.itemAtIndex(new NSInteger(5)).setAttributedTitle(
230 NSAttributedString.attributedStringWithAttributes(key.toString(), KEY_FONT_ATTRIBUTES)
236 * Remove software update menu item if no update feed available
238 private void updateUpdateMenu() {
239 if(null == Updater.getFeed()) {
240 this.applicationMenu.removeItemAtIndex(new NSInteger(1));
244 @Outlet
245 private NSMenu encodingMenu;
247 public void setEncodingMenu(NSMenu encodingMenu) {
248 this.encodingMenu = encodingMenu;
249 for(String charset : new DefaultCharsetProvider().availableCharsets()) {
250 this.encodingMenu.addItemWithTitle_action_keyEquivalent(charset, Foundation.selector("encodingMenuClicked:"), StringUtils.EMPTY);
254 @Outlet
255 private NSMenu columnMenu;
257 public void setColumnMenu(NSMenu columnMenu) {
258 this.columnMenu = columnMenu;
259 Map<String, String> columns = new HashMap<String, String>();
260 columns.put(String.format("browser.column.%s", Column.kind.name()), LocaleFactory.localizedString("Kind"));
261 columns.put(String.format("browser.column.%s", Column.extension.name()), LocaleFactory.localizedString("Extension"));
262 columns.put(String.format("browser.column.%s", Column.size.name()), LocaleFactory.localizedString("Size"));
263 columns.put(String.format("browser.column.%s", Column.modified.name()), LocaleFactory.localizedString("Modified"));
264 columns.put(String.format("browser.column.%s", Column.owner.name()), LocaleFactory.localizedString("Owner"));
265 columns.put(String.format("browser.column.%s", Column.group.name()), LocaleFactory.localizedString("Group"));
266 columns.put(String.format("browser.column.%s", Column.permission.name()), LocaleFactory.localizedString("Permissions"));
267 columns.put(String.format("browser.column.%s", Column.region.name()), LocaleFactory.localizedString("Region"));
268 columns.put(String.format("browser.column.%s", Column.version.name()), LocaleFactory.localizedString("Version"));
269 for(Map.Entry<String, String> entry : columns.entrySet()) {
270 NSMenuItem item = this.columnMenu.addItemWithTitle_action_keyEquivalent(entry.getValue(),
271 Foundation.selector("columnMenuClicked:"), StringUtils.EMPTY);
272 final String identifier = entry.getKey();
273 item.setState(preferences.getBoolean(identifier) ? NSCell.NSOnState : NSCell.NSOffState);
274 item.setRepresentedObject(identifier);
278 @Action
279 public void columnMenuClicked(final NSMenuItem sender) {
280 final String identifier = sender.representedObject();
281 final boolean enabled = !preferences.getBoolean(identifier);
282 sender.setState(enabled ? NSCell.NSOnState : NSCell.NSOffState);
283 preferences.setProperty(identifier, enabled);
284 BrowserController.updateBrowserTableColumns();
287 @Outlet
288 private NSMenu editMenu;
290 @Delegate
291 private EditMenuDelegate editMenuDelegate;
293 public void setEditMenu(NSMenu editMenu) {
294 this.editMenu = editMenu;
295 this.editMenuDelegate = new EditMenuDelegate() {
296 @Override
297 protected Path getEditable() {
298 final List<BrowserController> b = MainController.getBrowsers();
299 for(BrowserController controller : b) {
300 if(controller.window().isKeyWindow()) {
301 final Path selected = controller.getSelectedPath();
302 if(null == selected) {
303 return null;
305 if(controller.isEditable(selected)) {
306 return selected;
308 return null;
311 return null;
314 @Override
315 protected ID getTarget() {
316 return MainController.getBrowser().id();
319 this.editMenu.setDelegate(editMenuDelegate.id());
322 @Outlet
323 private NSMenu urlMenu;
325 @Delegate
326 private URLMenuDelegate urlMenuDelegate;
328 public void setUrlMenu(NSMenu urlMenu) {
329 this.urlMenu = urlMenu;
330 this.urlMenuDelegate = new CopyURLMenuDelegate() {
331 @Override
332 protected Session<?> getSession() {
333 final List<BrowserController> b = MainController.getBrowsers();
334 for(BrowserController controller : b) {
335 if(controller.window().isKeyWindow()) {
336 if(controller.isMounted()) {
337 return controller.getSession();
341 return null;
344 @Override
345 protected List<Path> getSelected() {
346 final List<BrowserController> b = MainController.getBrowsers();
347 for(BrowserController controller : b) {
348 if(controller.window().isKeyWindow()) {
349 List<Path> selected = controller.getSelectedPaths();
350 if(selected.isEmpty()) {
351 if(controller.isMounted()) {
352 return Collections.singletonList(controller.workdir());
355 return selected;
358 return Collections.emptyList();
361 this.urlMenu.setDelegate(urlMenuDelegate.id());
364 @Outlet
365 private NSMenu openUrlMenu;
367 @Delegate
368 private URLMenuDelegate openUrlMenuDelegate;
370 public void setOpenUrlMenu(NSMenu openUrlMenu) {
371 this.openUrlMenu = openUrlMenu;
372 this.openUrlMenuDelegate = new OpenURLMenuDelegate() {
373 @Override
374 protected Session<?> getSession() {
375 final List<BrowserController> b = MainController.getBrowsers();
376 for(BrowserController controller : b) {
377 if(controller.window().isKeyWindow()) {
378 if(controller.isMounted()) {
379 return controller.getSession();
383 return null;
386 @Override
387 protected List<Path> getSelected() {
388 final List<BrowserController> b = MainController.getBrowsers();
389 for(BrowserController controller : b) {
390 if(controller.window().isKeyWindow()) {
391 List<Path> selected = controller.getSelectedPaths();
392 if(selected.isEmpty()) {
393 if(controller.isMounted()) {
394 return Collections.singletonList(controller.workdir());
397 return selected;
400 return Collections.emptyList();
403 this.openUrlMenu.setDelegate(openUrlMenuDelegate.id());
406 @Outlet
407 private NSMenu archiveMenu;
409 @Delegate
410 private ArchiveMenuDelegate archiveMenuDelegate;
412 public void setArchiveMenu(NSMenu archiveMenu) {
413 this.archiveMenu = archiveMenu;
414 this.archiveMenuDelegate = new ArchiveMenuDelegate() {
415 @Override
416 protected ID getTarget() {
417 return MainController.getBrowser().id();
420 this.archiveMenu.setDelegate(archiveMenuDelegate.id());
423 @Outlet
424 private NSMenu bookmarkMenu;
426 @Delegate
427 private BookmarkMenuDelegate bookmarkMenuDelegate;
429 public void setBookmarkMenu(NSMenu bookmarkMenu) {
430 this.bookmarkMenu = bookmarkMenu;
431 this.bookmarkMenuDelegate = new BookmarkMenuDelegate() {
432 @Override
433 protected ID getTarget() {
434 return MainController.getBrowser().id();
437 this.bookmarkMenu.setDelegate(bookmarkMenuDelegate.id());
440 @Outlet
441 private NSMenu historyMenu;
443 @Delegate
444 private HistoryMenuDelegate historyMenuDelegate;
446 public void setHistoryMenu(NSMenu historyMenu) {
447 this.historyMenu = historyMenu;
448 this.historyMenuDelegate = new HistoryMenuDelegate() {
449 @Override
450 protected ID getTarget() {
451 return MainController.getBrowser().id();
454 this.historyMenu.setDelegate(historyMenuDelegate.id());
457 @Outlet
458 private NSMenu rendezvousMenu;
460 @Delegate
461 private RendezvousMenuDelegate rendezvousMenuDelegate;
463 public void setRendezvousMenu(NSMenu rendezvousMenu) {
464 this.rendezvousMenu = rendezvousMenu;
465 this.rendezvousMenuDelegate = new RendezvousMenuDelegate() {
466 @Override
467 protected ID getTarget() {
468 return MainController.getBrowser().id();
471 this.rendezvousMenu.setDelegate(rendezvousMenuDelegate.id());
474 @Action
475 public void historyMenuClicked(NSMenuItem sender) {
476 ApplicationLauncherFactory.get().open(HistoryCollection.defaultCollection().getFolder());
479 @Action
480 public void bugreportMenuClicked(final ID sender) {
481 BrowserLauncherFactory.get().open(
482 MessageFormat.format(preferences.getProperty("website.bug"),
483 preferences.getProperty("application.version")));
486 @Action
487 public void helpMenuClicked(final ID sender) {
488 BrowserLauncherFactory.get().open(preferences.getProperty("website.help"));
491 @Action
492 public void licenseMenuClicked(final ID sender) {
493 ApplicationLauncherFactory.get().open(
494 LocalFactory.get(NSBundle.mainBundle().pathForResource_ofType("License", "txt")));
497 @Action
498 public void acknowledgmentsMenuClicked(final ID sender) {
499 ApplicationLauncherFactory.get().open(
500 LocalFactory.get(NSBundle.mainBundle().pathForResource_ofType("Acknowledgments", "rtf")));
503 @Action
504 public void websiteMenuClicked(final ID sender) {
505 BrowserLauncherFactory.get().open(preferences.getProperty("website.home"));
508 @Action
509 public void forumMenuClicked(final ID sender) {
510 BrowserLauncherFactory.get().open(preferences.getProperty("website.forum"));
513 @Action
514 public void donateMenuClicked(final ID sender) {
515 BrowserLauncherFactory.get().open(preferences.getProperty("website.donate"));
518 @Action
519 public void aboutMenuClicked(final ID sender) {
520 final NSDictionary dict = NSDictionary.dictionaryWithObjectsForKeys(
521 NSArray.arrayWithObjects(
522 preferences.getProperty("application.name"),
523 preferences.getProperty("application.version"),
524 preferences.getProperty("application.revision")),
525 NSArray.arrayWithObjects(
526 "ApplicationName",
527 "ApplicationVersion",
528 "Version")
530 NSApplication.sharedApplication().orderFrontStandardAboutPanelWithOptions(dict);
533 @Action
534 public void feedbackMenuClicked(final ID sender) {
535 BrowserLauncherFactory.get().open(preferences.getProperty("mail.feedback")
536 + "?subject=" + preferences.getProperty("application.name") + "-" + preferences.getProperty("application.version"));
539 @Action
540 public void preferencesMenuClicked(final ID sender) {
541 PreferencesController controller = PreferencesControllerFactory.instance();
542 controller.window().makeKeyAndOrderFront(null);
545 @Action
546 public void newDownloadMenuClicked(final ID sender) {
547 this.showTransferQueueClicked(sender);
548 SheetController c = new DownloadController(TransferControllerFactory.get());
549 c.beginSheet();
552 @Action
553 public void newBrowserMenuClicked(final ID sender) {
554 this.openDefaultBookmark(MainController.newDocument(true));
557 @Action
558 public void showTransferQueueClicked(final ID sender) {
559 TransferController c = TransferControllerFactory.get();
560 c.window().makeKeyAndOrderFront(null);
563 @Action
564 public void showActivityWindowClicked(final ID sender) {
565 ActivityController c = ActivityControllerFactory.get();
566 if(c.isVisible()) {
567 c.window().close();
569 else {
570 c.window().orderFront(null);
574 @Override
575 public boolean application_openFile(final NSApplication app, final String filename) {
576 if(log.isDebugEnabled()) {
577 log.debug(String.format("Open file %s", filename));
579 final Local f = LocalFactory.get(filename);
580 if(f.exists()) {
581 if("duck".equals(f.getExtension())) {
582 final Host bookmark = HostReaderFactory.get().read(f);
583 if(null == bookmark) {
584 return false;
586 MainController.newDocument().mount(bookmark);
587 return true;
589 else if("cyberducklicense".equals(f.getExtension())) {
590 final License l = LicenseFactory.get(f);
591 if(l.verify()) {
592 try {
593 f.copy(LocalFactory.get(preferences.getProperty("application.support.path"), f.getName()));
595 catch(AccessDeniedException e) {
596 log.warn(e.getMessage());
598 final NSAlert alert = NSAlert.alert(
599 l.toString(),
600 LocaleFactory.localizedString("Thanks for your support! Your contribution helps to further advance development to make Cyberduck even better.", "License")
601 + "\n\n"
602 + LocaleFactory.localizedString("Your donation key has been copied to the Application Support folder.", "License"),
603 LocaleFactory.localizedString("Continue", "License"), //default
604 null, //other
605 null
607 alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
608 if(this.alert(alert) == SheetCallback.DEFAULT_OPTION) {
609 for(BrowserController c : MainController.getBrowsers()) {
610 c.removeDonateWindowTitle();
612 this.updateLicenseMenu();
615 else {
616 final NSAlert alert = NSAlert.alert(
617 LocaleFactory.localizedString("Not a valid donation key", "License"),
618 LocaleFactory.localizedString("This donation key does not appear to be valid.", "License"),
619 LocaleFactory.localizedString("Continue", "License"), //default
620 null, //other
621 null);
622 alert.setAlertStyle(NSAlert.NSWarningAlertStyle);
623 alert.setShowsHelp(true);
624 alert.setDelegate(new ProxyController() {
625 public boolean alertShowHelp(NSAlert alert) {
626 StringBuilder site = new StringBuilder(preferences.getProperty("website.help"));
627 site.append("/").append("faq");
628 BrowserLauncherFactory.get().open(site.toString());
629 return true;
632 }.id());
633 this.alert(alert);
635 return true;
637 else if("cyberduckprofile".equals(f.getExtension())) {
638 final Protocol profile = ProfileReaderFactory.get().read(f);
639 if(null == profile) {
640 return false;
642 if(profile.isEnabled()) {
643 if(log.isDebugEnabled()) {
644 log.debug(String.format("Register profile %s", profile));
646 ProtocolFactory.register(profile);
647 final Host host = new Host(profile, profile.getDefaultHostname(), profile.getDefaultPort());
648 MainController.newDocument().addBookmark(host);
649 // Register in application support
650 final Local profiles = LocalFactory.get(preferences.getProperty("application.support.path"),
651 PreferencesFactory.get().getProperty("profiles.folder.name"));
652 try {
653 profiles.mkdir();
654 f.copy(LocalFactory.get(profiles, f.getName()));
656 catch(AccessDeniedException e) {
657 log.warn(e.getMessage());
661 else {
662 // Upload file
663 this.background(new AbstractBackgroundAction<Void>() {
664 @Override
665 public Void run() throws BackgroundException {
666 // Wait until bookmarks are loaded
667 try {
668 bookmarksSemaphore.await();
670 catch(InterruptedException e) {
671 log.error(String.format("Error awaiting bookmarks to load %s", e.getMessage()));
673 return null;
676 @Override
677 public void cleanup() {
678 upload(f);
681 @Override
682 public String getActivity() {
683 return "Open File";
686 return true;
689 return false;
692 private boolean upload(final Local f) {
693 return this.upload(Collections.singletonList(f));
696 private boolean upload(final List<Local> files) {
697 // Selected bookmark
698 Host open = null;
699 Path workdir = null;
700 for(BrowserController controller : MainController.getBrowsers()) {
701 if(controller.isMounted()) {
702 open = controller.getSession().getHost();
703 workdir = controller.workdir();
704 if(1 == MainController.getBrowsers().size()) {
705 // If only one browser window upload to current working directory with no bookmark selection
706 this.upload(open, files, workdir);
707 return true;
709 break;
712 if(BookmarkCollection.defaultCollection().isEmpty()) {
713 log.warn("No bookmark for upload");
714 return false;
716 final NSPopUpButton bookmarksPopup = NSPopUpButton.buttonWithFrame(new NSRect(0, 26));
717 bookmarksPopup.setToolTip(LocaleFactory.localizedString("Bookmarks"));
718 for(Host b : BookmarkCollection.defaultCollection()) {
719 String title = BookmarkNameProvider.toString(b);
720 int i = 1;
721 while(bookmarksPopup.itemWithTitle(title) != null) {
722 title = BookmarkNameProvider.toString(b) + "-" + i;
723 i++;
725 bookmarksPopup.addItemWithTitle(title);
726 bookmarksPopup.lastItem().setImage(IconCacheFactory.<NSImage>get().iconNamed(b.getProtocol().icon(), 16));
727 bookmarksPopup.lastItem().setRepresentedObject(b.getUuid());
728 if(b.equals(open)) {
729 bookmarksPopup.selectItemAtIndex(bookmarksPopup.indexOfItem(bookmarksPopup.lastItem()));
732 if(null == open) {
733 int i = 0;
734 for(Host bookmark : BookmarkCollection.defaultCollection()) {
735 boolean found = false;
736 // Pick the bookmark with the same download location
737 for(Local file : files) {
738 if(file.isChild(new DownloadDirectoryFinder().find(bookmark))) {
739 bookmarksPopup.selectItemAtIndex(new NSInteger(i));
740 found = true;
741 break;
744 if(found) {
745 break;
747 i++;
750 if(-1 == bookmarksPopup.indexOfSelectedItem().intValue()) {
751 // No bookmark for current browser found
752 bookmarksPopup.selectItemAtIndex(new NSInteger(0));
754 final TransferController t = TransferControllerFactory.get();
755 final Host mount = open;
756 final Path destination = workdir;
757 AlertController alert = new AlertController(t, NSAlert.alert("Select Bookmark",
758 MessageFormat.format("Upload {0} to the selected bookmark.",
759 files.size() == 1 ? files.iterator().next().getName()
760 : MessageFormat.format(LocaleFactory.localizedString("{0} Files"), String.valueOf(files.size()))
762 LocaleFactory.localizedString("Upload", "Transfer"),
763 LocaleFactory.localizedString("Cancel"),
764 null
765 )) {
766 @Override
767 public void callback(int returncode) {
768 if(DEFAULT_OPTION == returncode) {
769 final String selected = bookmarksPopup.selectedItem().representedObject();
770 for(Host bookmark : BookmarkCollection.defaultCollection()) {
771 // Determine selected bookmark
772 if(bookmark.getUuid().equals(selected)) {
773 if(bookmark.equals(mount)) {
774 // Use current working directory of browser for destination
775 upload(bookmark, files, destination);
777 else {
778 // No mounted browser
779 if(StringUtils.isNotBlank(bookmark.getDefaultPath())) {
780 upload(bookmark, files, new Path(bookmark.getDefaultPath(), EnumSet.of(Path.Type.directory)));
782 else {
783 upload(bookmark, files, destination);
786 break;
792 @Override
793 protected boolean validateInput() {
794 return StringUtils.isNotEmpty(bookmarksPopup.selectedItem().representedObject());
797 alert.setAccessoryView(bookmarksPopup);
798 alert.beginSheet();
799 return true;
802 private void upload(final Host bookmark, final List<Local> files, final Path destination) {
803 final List<TransferItem> roots = new ArrayList<TransferItem>();
804 for(Local file : files) {
805 roots.add(new TransferItem(new Path(destination, file.getName(),
806 file.isDirectory() ? EnumSet.of(Path.Type.directory) : EnumSet.of(Path.Type.file)), file));
808 final TransferController t = TransferControllerFactory.get();
809 t.start(new UploadTransfer(bookmark, roots));
813 * Sent directly by theApplication to the delegate. The method should attempt to open the file filename,
814 * returning true if the file is successfully opened, and false otherwise. By design, a
815 * file opened through this method is assumed to be temporary its the application's
816 * responsibility to remove the file at the appropriate time.
818 @Override
819 public boolean application_openTempFile(NSApplication app, String filename) {
820 if(log.isDebugEnabled()) {
821 log.debug("applicationOpenTempFile:" + filename);
823 return this.application_openFile(app, filename);
827 * Invoked immediately before opening an untitled file. Return false to prevent
828 * the application from opening an untitled file; return true otherwise.
829 * Note that applicationOpenUntitledFile is invoked if this method returns true.
831 @Override
832 public boolean applicationShouldOpenUntitledFile(NSApplication sender) {
833 if(log.isDebugEnabled()) {
834 log.debug("applicationShouldOpenUntitledFile");
836 return preferences.getBoolean("browser.open.untitled");
840 * @return true if the file was successfully opened, false otherwise.
842 @Override
843 public boolean applicationOpenUntitledFile(NSApplication app) {
844 if(log.isDebugEnabled()) {
845 log.debug("applicationOpenUntitledFile");
847 return false;
851 * Mounts the default bookmark if any
853 private void openDefaultBookmark(BrowserController controller) {
854 String defaultBookmark = preferences.getProperty("browser.open.bookmark.default");
855 if(null == defaultBookmark) {
856 log.info("No default bookmark configured");
857 return; //No default bookmark given
859 Host bookmark = BookmarkCollection.defaultCollection().lookup(defaultBookmark);
860 if(null == bookmark) {
861 log.info("Default bookmark no more available");
862 return;
864 for(BrowserController browser : getBrowsers()) {
865 if(browser.getSession() != null) {
866 if(browser.getSession().getHost().equals(bookmark)) {
867 log.debug("Default bookmark already mounted");
868 return;
872 if(log.isDebugEnabled()) {
873 log.debug(String.format("Mounting default bookmark %s", bookmark));
875 controller.mount(bookmark);
879 * These events are sent whenever the Finder reactivates an already running application
880 * because someone double-clicked it again or used the dock to activate it. By default
881 * the Application Kit will handle this event by checking whether there are any visible
882 * NSWindows (not NSPanels), and, if there are none, it goes through the standard untitled
883 * document creation (the same as it does if theApplication is launched without any document
884 * to open). For most document-based applications, an untitled document will be created.
885 * The application delegate will also get a chance to respond to the normal untitled document
886 * delegations. If you implement this method in your application delegate, it will be called
887 * before any of the default behavior happens. If you return true, then NSApplication will
888 * go on to do its normal thing. If you return false, then NSApplication will do nothing.
889 * So, you can either implement this method, do nothing, and return false if you do not
890 * want anything to happen at all (not recommended), or you can implement this method,
891 * handle the event yourself in some custom way, and return false.
893 @Override
894 public boolean applicationShouldHandleReopen_hasVisibleWindows(final NSApplication app, final boolean visibleWindowsFound) {
895 if(log.isDebugEnabled()) {
896 log.debug(String.format("Should handle reopen with windows %s", visibleWindowsFound));
897 } // While an application is open, the Dock icon has a symbol below it.
898 // When a user clicks an open application’s icon in the Dock, the application
899 // becomes active and all open unminimized windows are brought to the front;
900 // minimized document windows remain in the Dock. If there are no unminimized
901 // windows when the user clicks the Dock icon, the last minimized window should
902 // be expanded and made active. If no documents are open, the application should
903 // open a new window. (If your application is not document-based, display the
904 // application’s main window.)
905 if(MainController.getBrowsers().isEmpty() && !TransferControllerFactory.get().isVisible()) {
906 this.openDefaultBookmark(MainController.newDocument());
908 NSWindow miniaturized = null;
909 for(BrowserController controller : MainController.getBrowsers()) {
910 if(!controller.window().isMiniaturized()) {
911 return false;
913 if(null == miniaturized) {
914 miniaturized = controller.window();
917 if(null == miniaturized) {
918 return false;
920 miniaturized.deminiaturize(null);
921 return false;
924 // User bookmarks and thirdparty applications
925 private final CountDownLatch bookmarksSemaphore = new CountDownLatch(1);
926 private final CountDownLatch thirdpartySemaphore = new CountDownLatch(1);
929 * Sent by the default notification center after the application has been launched and initialized but
930 * before it has received its first event. aNotification is always an
931 * ApplicationDidFinishLaunchingNotification. You can retrieve the NSApplication
932 * object in question by sending object to aNotification. The delegate can implement
933 * this method to perform further initialization. If the user started up the application
934 * by double-clicking a file, the delegate receives the applicationOpenFile message before receiving
935 * applicationDidFinishLaunching. (applicationWillFinishLaunching is sent before applicationOpenFile.)
937 @Override
938 public void applicationDidFinishLaunching(NSNotification notification) {
939 if(preferences.getBoolean("browser.open.untitled")) {
940 MainController.newDocument();
942 if(preferences.getBoolean("queue.window.open.default")) {
943 this.showTransferQueueClicked(null);
945 if(preferences.getBoolean("browser.serialize")) {
946 this.background(new AbstractBackgroundAction<Void>() {
947 @Override
948 public Void run() throws BackgroundException {
949 sessions.load();
950 return null;
953 @Override
954 public void cleanup() {
955 for(Host host : sessions) {
956 if(log.isInfoEnabled()) {
957 log.info(String.format("New browser for saved session %s", host));
959 final BrowserController browser = MainController.newDocument(false, host.getUuid());
960 browser.mount(host);
962 sessions.clear();
966 // Load all bookmarks in background
967 this.background(new AbstractBackgroundAction<Void>() {
968 @Override
969 public Void run() throws BackgroundException {
970 final BookmarkCollection c = BookmarkCollection.defaultCollection();
971 c.load();
972 bookmarksSemaphore.countDown();
973 return null;
976 @Override
977 public void cleanup() {
978 if(preferences.getBoolean("browser.open.untitled")) {
979 if(preferences.getProperty("browser.open.bookmark.default") != null) {
980 openDefaultBookmark(MainController.newDocument());
983 // Set delegate for NSService
984 NSApplication.sharedApplication().setServicesProvider(MainController.this.id());
987 @Override
988 public String getActivity() {
989 return "Loading Bookmarks";
992 this.background(new AbstractBackgroundAction<Void>() {
993 @Override
994 public Void run() throws BackgroundException {
995 HistoryCollection.defaultCollection().load();
996 return null;
999 @Override
1000 public String getActivity() {
1001 return "Loading History";
1004 this.background(new AbstractBackgroundAction<Void>() {
1005 @Override
1006 public Void run() throws BackgroundException {
1007 TransferCollection.defaultCollection().load();
1008 return null;
1011 @Override
1012 public String getActivity() {
1013 return "Loading Transfers";
1016 this.background(new AbstractBackgroundAction<Void>() {
1017 @Override
1018 public Void run() throws BackgroundException {
1019 // Make sure we register to Growl first
1020 NotificationServiceFactory.get().setup();
1021 return null;
1024 @Override
1025 public String getActivity() {
1026 return "Registering Growl";
1029 final Rendezvous bonjour = RendezvousFactory.instance();
1030 bonjour.addListener(new RendezvousListener() {
1031 @Override
1032 public void serviceResolved(final String identifier, final Host host) {
1033 invoke(new DefaultMainAction() {
1034 @Override
1035 public void run() {
1036 NotificationServiceFactory.get().notifyWithImage("Bonjour",
1037 bonjour.getDisplayedName(identifier), "rendezvous");
1042 @Override
1043 public void serviceLost(final Host servicename) {
1047 if(preferences.getBoolean("defaulthandler.reminder")
1048 && preferences.getInteger("uses") > 0) {
1049 if(!SchemeHandlerFactory.get().isDefaultHandler(
1050 Arrays.asList(Scheme.ftp, Scheme.ftps, Scheme.sftp),
1051 new Application(NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleIdentifier").toString()))) {
1052 final NSAlert alert = NSAlert.alert(
1053 LocaleFactory.localizedString("Set Cyberduck as default application for FTP and SFTP locations?", "Configuration"),
1054 LocaleFactory.localizedString("As the default application, Cyberduck will open when you click on FTP or SFTP links " +
1055 "in other applications, such as your web browser. You can change this setting in the Preferences later.", "Configuration"),
1056 LocaleFactory.localizedString("Change", "Configuration"), //default
1057 null, //other
1058 LocaleFactory.localizedString("Cancel", "Configuration")
1060 alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
1061 alert.setShowsSuppressionButton(true);
1062 alert.suppressionButton().setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
1063 int choice = alert.runModal(); //alternate
1064 if(alert.suppressionButton().state() == NSCell.NSOnState) {
1065 // Never show again.
1066 preferences.setProperty("defaulthandler.reminder", false);
1068 if(choice == SheetCallback.DEFAULT_OPTION) {
1069 SchemeHandlerFactory.get().setDefaultHandler(
1070 Arrays.asList(Scheme.ftp, Scheme.ftps, Scheme.sftp),
1071 new Application(NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleIdentifier").toString())
1076 // NSWorkspace notifications are posted to a notification center provided by
1077 // the NSWorkspace object, instead of going through the application’s default
1078 // notification center as most notifications do. To receive NSWorkspace notifications,
1079 // your application must register an observer with the NSWorkspace notification center.
1080 NSWorkspace.sharedWorkspace().notificationCenter().addObserver(this.id(),
1081 Foundation.selector("workspaceWillPowerOff:"),
1082 NSWorkspace.WorkspaceWillPowerOffNotification,
1083 null);
1084 NSWorkspace.sharedWorkspace().notificationCenter().addObserver(this.id(),
1085 Foundation.selector("workspaceWillLogout:"),
1086 NSWorkspace.WorkspaceSessionDidResignActiveNotification,
1087 null);
1088 NSWorkspace.sharedWorkspace().notificationCenter().addObserver(this.id(),
1089 Foundation.selector("workspaceWillSleep:"),
1090 NSWorkspace.WorkspaceWillSleepNotification,
1091 null);
1092 NSNotificationCenter.defaultCenter().addObserver(this.id(),
1093 Foundation.selector("applicationWillRestartAfterUpdate:"),
1094 "SUUpdaterWillRestartNotificationName",
1095 null);
1096 this.background(new AbstractBackgroundAction<Void>() {
1097 @Override
1098 public Void run() throws BackgroundException {
1099 bonjour.init();
1100 return null;
1103 // Import thirdparty bookmarks.
1104 this.background(new AbstractBackgroundAction<Void>() {
1105 private List<ThirdpartyBookmarkCollection> thirdpartyBookmarkCollections = Collections.emptyList();
1107 @Override
1108 public Void run() {
1109 thirdpartyBookmarkCollections = this.getThirdpartyBookmarks();
1110 for(ThirdpartyBookmarkCollection t : thirdpartyBookmarkCollections) {
1111 if(!t.isInstalled()) {
1112 if(log.isInfoEnabled()) {
1113 log.info(String.format("No application installed for %s", t.getBundleIdentifier()));
1115 continue;
1117 try {
1118 t.load();
1120 catch(AccessDeniedException e) {
1121 log.warn(String.format("Failure %s loading bookmarks from %s", e, t));
1123 if(t.isEmpty()) {
1124 // Flag as imported
1125 preferences.setProperty(t.getConfiguration(), true);
1128 try {
1129 bookmarksSemaphore.await();
1131 catch(InterruptedException e) {
1132 log.error(String.format("Error awaiting bookmarks to load %s", e.getMessage()));
1134 return null;
1137 @Override
1138 public void cleanup() {
1139 for(ThirdpartyBookmarkCollection t : thirdpartyBookmarkCollections) {
1140 final BookmarkCollection bookmarks = BookmarkCollection.defaultCollection();
1141 t.filter(bookmarks);
1142 if(t.isEmpty()) {
1143 continue;
1145 final NSAlert alert = NSAlert.alert(
1146 MessageFormat.format(LocaleFactory.localizedString("Import {0} Bookmarks", "Configuration"), t.getName()),
1147 MessageFormat.format(LocaleFactory.localizedString("{0} bookmarks found. Do you want to add these to your bookmarks?", "Configuration"), t.size()),
1148 LocaleFactory.localizedString("Import", "Configuration"), //default
1149 null, //other
1150 LocaleFactory.localizedString("Cancel", "Configuration"));
1151 alert.setShowsSuppressionButton(true);
1152 alert.suppressionButton().setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
1153 alert.setAlertStyle(NSAlert.NSInformationalAlertStyle);
1154 int choice = alert.runModal(); //alternate
1155 if(alert.suppressionButton().state() == NSCell.NSOnState) {
1156 // Never show again.
1157 preferences.setProperty(t.getConfiguration(), true);
1159 if(choice == SheetCallback.DEFAULT_OPTION) {
1160 bookmarks.addAll(t);
1161 // Flag as imported
1162 preferences.setProperty(t.getConfiguration(), true);
1165 thirdpartySemaphore.countDown();
1168 @Override
1169 public String getActivity() {
1170 return "Loading thirdparty bookmarks";
1173 private List<ThirdpartyBookmarkCollection> getThirdpartyBookmarks() {
1174 return Arrays.asList(new TransmitBookmarkCollection(), new FilezillaBookmarkCollection(), new FetchBookmarkCollection(),
1175 new FlowBookmarkCollection(), new InterarchyBookmarkCollection(), new CrossFtpBookmarkCollection(), new FireFtpBookmarkCollection());
1181 * NSService implementation
1183 public void serviceUploadFileUrl_(final NSPasteboard pboard, final String userData) {
1184 if(log.isDebugEnabled()) {
1185 log.debug(String.format("serviceUploadFileUrl_: with user data %s", userData));
1187 if(pboard.availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) {
1188 NSObject o = pboard.propertyListForType(NSPasteboard.FilenamesPboardType);
1189 if(o != null) {
1190 if(o.isKindOfClass(Rococoa.createClass("NSArray", NSArray._Class.class))) {
1191 final NSArray elements = Rococoa.cast(o, NSArray.class);
1192 List<Local> files = new ArrayList<Local>();
1193 for(int i = 0; i < elements.count().intValue(); i++) {
1194 files.add(LocalFactory.get(elements.objectAtIndex(new NSUInteger(i)).toString()));
1196 this.upload(files);
1203 * Saved browsers
1205 private AbstractHostCollection sessions = new FolderBookmarkCollection(
1206 LocalFactory.get(preferences.getProperty("application.support.path"), "Sessions"), "session");
1209 * Display donation reminder dialog
1211 private boolean displayDonationPrompt = true;
1213 @Outlet
1214 private WindowController donationController;
1218 * Invoked from within the terminate method immediately before the
1219 * application terminates. sender is the NSApplication to be terminated.
1220 * If this method returns false, the application is not terminated,
1221 * and control returns to the main event loop.
1223 * @param app Application instance
1224 * @return Return true to allow the application to terminate.
1226 @Override
1227 public NSUInteger applicationShouldTerminate(final NSApplication app) {
1228 if(log.isDebugEnabled()) {
1229 log.debug("Application should quit with notification");
1231 // Determine if there are any running transfers
1232 final NSUInteger result = TransferControllerFactory.applicationShouldTerminate(app);
1233 if(!result.equals(NSApplication.NSTerminateNow)) {
1234 return result;
1236 // Determine if there are any open connections
1237 for(BrowserController browser : MainController.getBrowsers()) {
1238 if(preferences.getBoolean("browser.serialize")) {
1239 if(browser.isMounted()) {
1240 // The workspace should be saved. Serialize all open browser sessions
1241 final Host serialized
1242 = new HostDictionary().deserialize(browser.getSession().getHost().serialize(SerializerFactory.get()));
1243 serialized.setWorkdir(browser.workdir());
1244 sessions.add(serialized);
1245 browser.window().saveFrameUsingName(serialized.getUuid());
1248 if(browser.isConnected()) {
1249 if(preferences.getBoolean("browser.disconnect.confirm")) {
1250 final NSAlert alert = NSAlert.alert(LocaleFactory.localizedString("Quit"),
1251 LocaleFactory.localizedString("You are connected to at least one remote site. Do you want to review open browsers?"),
1252 LocaleFactory.localizedString("Quit Anyway"), //default
1253 LocaleFactory.localizedString("Cancel"), //other
1254 LocaleFactory.localizedString("Review…"));
1255 alert.setAlertStyle(NSAlert.NSWarningAlertStyle);
1256 alert.setShowsSuppressionButton(true);
1257 alert.suppressionButton().setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
1258 int choice = alert.runModal(); //alternate
1259 if(alert.suppressionButton().state() == NSCell.NSOnState) {
1260 // Never show again.
1261 preferences.setProperty("browser.disconnect.confirm", false);
1263 if(choice == SheetCallback.ALTERNATE_OPTION) {
1264 // Cancel. Quit has been interrupted. Delete any saved sessions so far.
1265 sessions.clear();
1266 return NSApplication.NSTerminateCancel;
1268 if(choice == SheetCallback.CANCEL_OPTION) {
1269 // Review if at least one window requested to terminate later, we shall wait.
1270 // This will iterate over all mounted browsers.
1271 if(NSApplication.NSTerminateNow.equals(BrowserController.applicationShouldTerminate(app))) {
1272 return this.applicationShouldTerminateAfterDonationPrompt(app);
1274 return NSApplication.NSTerminateLater;
1276 if(choice == SheetCallback.DEFAULT_OPTION) {
1277 // Quit immediatly
1278 return this.applicationShouldTerminateAfterDonationPrompt(app);
1281 else {
1282 browser.windowShouldClose(browser.window());
1286 return this.applicationShouldTerminateAfterDonationPrompt(app);
1289 public NSUInteger applicationShouldTerminateAfterDonationPrompt(final NSApplication app) {
1290 if(log.isDebugEnabled()) {
1291 log.debug("applicationShouldTerminateAfterDonationPrompt");
1293 if(!displayDonationPrompt) {
1294 // Already displayed
1295 return NSApplication.NSTerminateNow;
1297 final License l = LicenseFactory.find();
1298 if(!l.verify()) {
1299 final String lastversion = preferences.getProperty("donate.reminder");
1300 if(NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleShortVersionString").toString().equals(lastversion)) {
1301 // Do not display if same version is installed
1302 return NSApplication.NSTerminateNow;
1304 final Calendar nextreminder = Calendar.getInstance();
1305 nextreminder.setTimeInMillis(preferences.getLong("donate.reminder.date"));
1306 // Display donationPrompt every n days
1307 nextreminder.add(Calendar.DAY_OF_YEAR, preferences.getInteger("y"));
1308 if(log.isDebugEnabled()) {
1309 log.debug(String.format("Next reminder %s", nextreminder.getTime().toString()));
1311 // Display after upgrade
1312 if(nextreminder.getTime().after(new Date(System.currentTimeMillis()))) {
1313 // Do not display if shown in the reminder interval
1314 return NSApplication.NSTerminateNow;
1316 // Make sure prompt is not loaded twice upon next quit event
1317 displayDonationPrompt = false;
1318 final int uses = preferences.getInteger("uses");
1319 donationController = new WindowController() {
1320 @Override
1321 protected String getBundleName() {
1322 return "Donate";
1325 @Outlet
1326 private NSButton neverShowDonationCheckbox;
1328 public void setNeverShowDonationCheckbox(NSButton neverShowDonationCheckbox) {
1329 this.neverShowDonationCheckbox = neverShowDonationCheckbox;
1330 this.neverShowDonationCheckbox.setTarget(this.id());
1331 this.neverShowDonationCheckbox.setState(
1332 preferences.getProperty("donate.reminder").equals(
1333 NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleShortVersionString").toString())
1334 ? NSCell.NSOnState : NSCell.NSOffState
1338 @Override
1339 public void awakeFromNib() {
1340 this.window().setTitle(this.window().title() + " (" + uses + ")");
1341 this.window().center();
1342 this.window().makeKeyAndOrderFront(null);
1344 super.awakeFromNib();
1347 public void closeDonationSheet(final NSButton sender) {
1348 if(sender.tag() == SheetCallback.DEFAULT_OPTION) {
1349 BrowserLauncherFactory.get().open(preferences.getProperty("website.donate"));
1351 this.terminate();
1354 @Override
1355 public void windowWillClose(NSNotification notification) {
1356 this.terminate();
1357 super.windowWillClose(notification);
1360 private void terminate() {
1361 if(neverShowDonationCheckbox.state() == NSCell.NSOnState) {
1362 preferences.setProperty("donate.reminder",
1363 NSBundle.mainBundle().infoDictionary().objectForKey("CFBundleShortVersionString").toString());
1365 // Remember this reminder date
1366 preferences.setProperty("donate.reminder.date", System.currentTimeMillis());
1367 // Quit again
1368 app.replyToApplicationShouldTerminate(true);
1371 donationController.loadBundle();
1372 // Delay application termination. Dismissing the donation dialog will reply to quit.
1373 return NSApplication.NSTerminateLater;
1375 return NSApplication.NSTerminateNow;
1379 * Quits the Rendezvous daemon and saves all preferences
1381 * @param notification Notification name
1383 @Override
1384 public void applicationWillTerminate(NSNotification notification) {
1385 if(log.isDebugEnabled()) {
1386 log.debug(String.format("Application will quit with notification %s", notification));
1388 this.invalidate();
1390 //Terminating rendezvous discovery
1391 RendezvousFactory.instance().quit();
1393 //Writing usage info
1394 preferences.setProperty("uses", preferences.getInteger("uses") + 1);
1395 preferences.save();
1398 public void applicationWillRestartAfterUpdate(ID updater) {
1399 // Disable donation prompt after udpate install
1400 displayDonationPrompt = false;
1404 * Posted when the user has requested a logout or that the machine be powered off.
1406 * @param notification Notification name
1408 public void workspaceWillPowerOff(NSNotification notification) {
1409 if(log.isDebugEnabled()) {
1410 log.debug(String.format("Workspace will power off with notification %s", notification));
1415 * Posted before a user session is switched out. This allows an application to
1416 * disable some processing when its user session is switched out, and reenable when that
1417 * session gets switched back in, for example.
1419 * @param notification Notification name
1421 public void workspaceWillLogout(NSNotification notification) {
1422 if(log.isDebugEnabled()) {
1423 log.debug(String.format("Workspace will logout with notification %s", notification));
1427 public void workspaceWillSleep(NSNotification notification) {
1428 if(log.isDebugEnabled()) {
1429 log.debug(String.format("Workspace will sleep with notification %s", notification));
1434 * Makes a unmounted browser window the key window and brings it to the front
1436 * @return A reference to a browser window
1438 public static BrowserController newDocument() {
1439 return MainController.newDocument(false);
1445 private static List<BrowserController> browsers
1446 = new ArrayList<BrowserController>();
1448 public static List<BrowserController> getBrowsers() {
1449 return browsers;
1453 * Browser with key focus
1455 * @return Null if no browser window is open
1457 public static BrowserController getBrowser() {
1458 for(BrowserController browser : MainController.getBrowsers()) {
1459 if(browser.window().isKeyWindow()) {
1460 return browser;
1463 return null;
1468 * Makes a unmounted browser window the key window and brings it to the front
1470 * @param force If true, open a new browser regardless of any unused browser window
1472 public static BrowserController newDocument(final boolean force) {
1473 return newDocument(force, null);
1477 * @param frame Frame autosave name
1479 public static BrowserController newDocument(final boolean force, final String frame) {
1480 final List<BrowserController> browsers = MainController.getBrowsers();
1481 if(!force) {
1482 for(BrowserController controller : browsers) {
1483 if(controller.getSession() == null) {
1484 controller.window().makeKeyAndOrderFront(null);
1485 return controller;
1489 final BrowserController controller = new BrowserController();
1490 controller.addListener(new WindowListener() {
1491 @Override
1492 public void windowWillClose() {
1493 browsers.remove(controller);
1496 if(StringUtils.isNotBlank(frame)) {
1497 if(!controller.window().setFrameUsingName(frame)) {
1498 if(!browsers.isEmpty()) {
1499 controller.cascade();
1503 else if(!browsers.isEmpty()) {
1504 controller.cascade();
1506 controller.window().makeKeyAndOrderFront(null);
1507 browsers.add(controller);
1508 return controller;
1512 * We are not a Windows application. Long live the application wide menu bar.
1514 @Override
1515 public boolean applicationShouldTerminateAfterLastWindowClosed(NSApplication app) {
1516 return false;
1519 @Override
1520 protected String getBundleName() {
1521 return "Main";