1 package ch
.cyberduck
.ui
.cocoa
;
4 * Copyright (c) 2005 David Kocher. All rights reserved.
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
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;
136 public static final int keyAEResult
= 757935405;
138 private final Preferences preferences
= PreferencesFactory
.get();
140 public MainController() {
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
);
161 log
.error("No URL parameter");
164 final String url
= param
.stringValue();
165 if(StringUtils
.isEmpty(url
)) {
166 log
.error("URL parameter is empty");
169 if("x-cyberduck-action:update".equals(url
)) {
170 this.updateMenuClicked(null);
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())));
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);
190 final BrowserController browser
= newDocument(false);
196 private Updater updater
;
199 public void updateMenuClicked(ID sender
) {
200 if(null == updater
) {
201 updater
= Updater
.create();
203 updater
.checkForUpdates(null);
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));
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));
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
);
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
);
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();
288 private NSMenu editMenu
;
291 private EditMenuDelegate editMenuDelegate
;
293 public void setEditMenu(NSMenu editMenu
) {
294 this.editMenu
= editMenu
;
295 this.editMenuDelegate
= new EditMenuDelegate() {
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
) {
305 if(controller
.isEditable(selected
)) {
315 protected ID
getTarget() {
316 return MainController
.getBrowser().id();
319 this.editMenu
.setDelegate(editMenuDelegate
.id());
323 private NSMenu urlMenu
;
326 private URLMenuDelegate urlMenuDelegate
;
328 public void setUrlMenu(NSMenu urlMenu
) {
329 this.urlMenu
= urlMenu
;
330 this.urlMenuDelegate
= new CopyURLMenuDelegate() {
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();
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());
358 return Collections
.emptyList();
361 this.urlMenu
.setDelegate(urlMenuDelegate
.id());
365 private NSMenu openUrlMenu
;
368 private URLMenuDelegate openUrlMenuDelegate
;
370 public void setOpenUrlMenu(NSMenu openUrlMenu
) {
371 this.openUrlMenu
= openUrlMenu
;
372 this.openUrlMenuDelegate
= new OpenURLMenuDelegate() {
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();
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());
400 return Collections
.emptyList();
403 this.openUrlMenu
.setDelegate(openUrlMenuDelegate
.id());
407 private NSMenu archiveMenu
;
410 private ArchiveMenuDelegate archiveMenuDelegate
;
412 public void setArchiveMenu(NSMenu archiveMenu
) {
413 this.archiveMenu
= archiveMenu
;
414 this.archiveMenuDelegate
= new ArchiveMenuDelegate() {
416 protected ID
getTarget() {
417 return MainController
.getBrowser().id();
420 this.archiveMenu
.setDelegate(archiveMenuDelegate
.id());
424 private NSMenu bookmarkMenu
;
427 private BookmarkMenuDelegate bookmarkMenuDelegate
;
429 public void setBookmarkMenu(NSMenu bookmarkMenu
) {
430 this.bookmarkMenu
= bookmarkMenu
;
431 this.bookmarkMenuDelegate
= new BookmarkMenuDelegate() {
433 protected ID
getTarget() {
434 return MainController
.getBrowser().id();
437 this.bookmarkMenu
.setDelegate(bookmarkMenuDelegate
.id());
441 private NSMenu historyMenu
;
444 private HistoryMenuDelegate historyMenuDelegate
;
446 public void setHistoryMenu(NSMenu historyMenu
) {
447 this.historyMenu
= historyMenu
;
448 this.historyMenuDelegate
= new HistoryMenuDelegate() {
450 protected ID
getTarget() {
451 return MainController
.getBrowser().id();
454 this.historyMenu
.setDelegate(historyMenuDelegate
.id());
458 private NSMenu rendezvousMenu
;
461 private RendezvousMenuDelegate rendezvousMenuDelegate
;
463 public void setRendezvousMenu(NSMenu rendezvousMenu
) {
464 this.rendezvousMenu
= rendezvousMenu
;
465 this.rendezvousMenuDelegate
= new RendezvousMenuDelegate() {
467 protected ID
getTarget() {
468 return MainController
.getBrowser().id();
471 this.rendezvousMenu
.setDelegate(rendezvousMenuDelegate
.id());
475 public void historyMenuClicked(NSMenuItem sender
) {
476 ApplicationLauncherFactory
.get().open(HistoryCollection
.defaultCollection().getFolder());
480 public void bugreportMenuClicked(final ID sender
) {
481 BrowserLauncherFactory
.get().open(
482 MessageFormat
.format(preferences
.getProperty("website.bug"),
483 preferences
.getProperty("application.version")));
487 public void helpMenuClicked(final ID sender
) {
488 BrowserLauncherFactory
.get().open(preferences
.getProperty("website.help"));
492 public void licenseMenuClicked(final ID sender
) {
493 ApplicationLauncherFactory
.get().open(
494 LocalFactory
.get(NSBundle
.mainBundle().pathForResource_ofType("License", "txt")));
498 public void acknowledgmentsMenuClicked(final ID sender
) {
499 ApplicationLauncherFactory
.get().open(
500 LocalFactory
.get(NSBundle
.mainBundle().pathForResource_ofType("Acknowledgments", "rtf")));
504 public void websiteMenuClicked(final ID sender
) {
505 BrowserLauncherFactory
.get().open(preferences
.getProperty("website.home"));
509 public void forumMenuClicked(final ID sender
) {
510 BrowserLauncherFactory
.get().open(preferences
.getProperty("website.forum"));
514 public void donateMenuClicked(final ID sender
) {
515 BrowserLauncherFactory
.get().open(preferences
.getProperty("website.donate"));
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(
527 "ApplicationVersion",
530 NSApplication
.sharedApplication().orderFrontStandardAboutPanelWithOptions(dict
);
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"));
540 public void preferencesMenuClicked(final ID sender
) {
541 PreferencesController controller
= PreferencesControllerFactory
.instance();
542 controller
.window().makeKeyAndOrderFront(null);
546 public void newDownloadMenuClicked(final ID sender
) {
547 this.showTransferQueueClicked(sender
);
548 SheetController c
= new DownloadController(TransferControllerFactory
.get());
553 public void newBrowserMenuClicked(final ID sender
) {
554 this.openDefaultBookmark(MainController
.newDocument(true));
558 public void showTransferQueueClicked(final ID sender
) {
559 TransferController c
= TransferControllerFactory
.get();
560 c
.window().makeKeyAndOrderFront(null);
564 public void showActivityWindowClicked(final ID sender
) {
565 ActivityController c
= ActivityControllerFactory
.get();
570 c
.window().orderFront(null);
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
);
581 if("duck".equals(f
.getExtension())) {
582 final Host bookmark
= HostReaderFactory
.get().read(f
);
583 if(null == bookmark
) {
586 MainController
.newDocument().mount(bookmark
);
589 else if("cyberducklicense".equals(f
.getExtension())) {
590 final License l
= LicenseFactory
.get(f
);
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(
600 LocaleFactory
.localizedString("Thanks for your support! Your contribution helps to further advance development to make Cyberduck even better.", "License")
602 + LocaleFactory
.localizedString("Your donation key has been copied to the Application Support folder.", "License"),
603 LocaleFactory
.localizedString("Continue", "License"), //default
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();
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
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());
637 else if("cyberduckprofile".equals(f
.getExtension())) {
638 final Protocol profile
= ProfileReaderFactory
.get().read(f
);
639 if(null == profile
) {
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"));
654 f
.copy(LocalFactory
.get(profiles
, f
.getName()));
656 catch(AccessDeniedException e
) {
657 log
.warn(e
.getMessage());
663 this.background(new AbstractBackgroundAction
<Void
>() {
665 public Void
run() throws BackgroundException
{
666 // Wait until bookmarks are loaded
668 bookmarksSemaphore
.await();
670 catch(InterruptedException e
) {
671 log
.error(String
.format("Error awaiting bookmarks to load %s", e
.getMessage()));
677 public void cleanup() {
682 public String
getActivity() {
692 private boolean upload(final Local f
) {
693 return this.upload(Collections
.singletonList(f
));
696 private boolean upload(final List
<Local
> files
) {
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
);
712 if(BookmarkCollection
.defaultCollection().isEmpty()) {
713 log
.warn("No bookmark for upload");
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
);
721 while(bookmarksPopup
.itemWithTitle(title
) != null) {
722 title
= BookmarkNameProvider
.toString(b
) + "-" + i
;
725 bookmarksPopup
.addItemWithTitle(title
);
726 bookmarksPopup
.lastItem().setImage(IconCacheFactory
.<NSImage
>get().iconNamed(b
.getProtocol().icon(), 16));
727 bookmarksPopup
.lastItem().setRepresentedObject(b
.getUuid());
729 bookmarksPopup
.selectItemAtIndex(bookmarksPopup
.indexOfItem(bookmarksPopup
.lastItem()));
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
));
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"),
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
);
778 // No mounted browser
779 if(StringUtils
.isNotBlank(bookmark
.getDefaultPath())) {
780 upload(bookmark
, files
, new Path(bookmark
.getDefaultPath(), EnumSet
.of(Path
.Type
.directory
)));
783 upload(bookmark
, files
, destination
);
793 protected boolean validateInput() {
794 return StringUtils
.isNotEmpty(bookmarksPopup
.selectedItem().representedObject());
797 alert
.setAccessoryView(bookmarksPopup
);
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.
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.
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.
843 public boolean applicationOpenUntitledFile(NSApplication app
) {
844 if(log
.isDebugEnabled()) {
845 log
.debug("applicationOpenUntitledFile");
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");
864 for(BrowserController browser
: getBrowsers()) {
865 if(browser
.getSession() != null) {
866 if(browser
.getSession().getHost().equals(bookmark
)) {
867 log
.debug("Default bookmark already mounted");
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.
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()) {
913 if(null == miniaturized
) {
914 miniaturized
= controller
.window();
917 if(null == miniaturized
) {
920 miniaturized
.deminiaturize(null);
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.)
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
>() {
948 public Void
run() throws BackgroundException
{
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());
966 // Load all bookmarks in background
967 this.background(new AbstractBackgroundAction
<Void
>() {
969 public Void
run() throws BackgroundException
{
970 final BookmarkCollection c
= BookmarkCollection
.defaultCollection();
972 bookmarksSemaphore
.countDown();
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());
988 public String
getActivity() {
989 return "Loading Bookmarks";
992 this.background(new AbstractBackgroundAction
<Void
>() {
994 public Void
run() throws BackgroundException
{
995 HistoryCollection
.defaultCollection().load();
1000 public String
getActivity() {
1001 return "Loading History";
1004 this.background(new AbstractBackgroundAction
<Void
>() {
1006 public Void
run() throws BackgroundException
{
1007 TransferCollection
.defaultCollection().load();
1012 public String
getActivity() {
1013 return "Loading Transfers";
1016 this.background(new AbstractBackgroundAction
<Void
>() {
1018 public Void
run() throws BackgroundException
{
1019 // Make sure we register to Growl first
1020 NotificationServiceFactory
.get().setup();
1025 public String
getActivity() {
1026 return "Registering Growl";
1029 final Rendezvous bonjour
= RendezvousFactory
.instance();
1030 bonjour
.addListener(new RendezvousListener() {
1032 public void serviceResolved(final String identifier
, final Host host
) {
1033 invoke(new DefaultMainAction() {
1036 NotificationServiceFactory
.get().notifyWithImage("Bonjour",
1037 bonjour
.getDisplayedName(identifier
), "rendezvous");
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
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
,
1084 NSWorkspace
.sharedWorkspace().notificationCenter().addObserver(this.id(),
1085 Foundation
.selector("workspaceWillLogout:"),
1086 NSWorkspace
.WorkspaceSessionDidResignActiveNotification
,
1088 NSWorkspace
.sharedWorkspace().notificationCenter().addObserver(this.id(),
1089 Foundation
.selector("workspaceWillSleep:"),
1090 NSWorkspace
.WorkspaceWillSleepNotification
,
1092 NSNotificationCenter
.defaultCenter().addObserver(this.id(),
1093 Foundation
.selector("applicationWillRestartAfterUpdate:"),
1094 "SUUpdaterWillRestartNotificationName",
1096 this.background(new AbstractBackgroundAction
<Void
>() {
1098 public Void
run() throws BackgroundException
{
1103 // Import thirdparty bookmarks.
1104 this.background(new AbstractBackgroundAction
<Void
>() {
1105 private List
<ThirdpartyBookmarkCollection
> thirdpartyBookmarkCollections
= Collections
.emptyList();
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()));
1120 catch(AccessDeniedException e
) {
1121 log
.warn(String
.format("Failure %s loading bookmarks from %s", e
, t
));
1125 preferences
.setProperty(t
.getConfiguration(), true);
1129 bookmarksSemaphore
.await();
1131 catch(InterruptedException e
) {
1132 log
.error(String
.format("Error awaiting bookmarks to load %s", e
.getMessage()));
1138 public void cleanup() {
1139 for(ThirdpartyBookmarkCollection t
: thirdpartyBookmarkCollections
) {
1140 final BookmarkCollection bookmarks
= BookmarkCollection
.defaultCollection();
1141 t
.filter(bookmarks
);
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
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
);
1162 preferences
.setProperty(t
.getConfiguration(), true);
1165 thirdpartySemaphore
.countDown();
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
);
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()));
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;
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.
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
)) {
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.
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
) {
1278 return this.applicationShouldTerminateAfterDonationPrompt(app
);
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();
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() {
1321 protected String
getBundleName() {
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
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"));
1355 public void windowWillClose(NSNotification notification
) {
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());
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
1384 public void applicationWillTerminate(NSNotification notification
) {
1385 if(log
.isDebugEnabled()) {
1386 log
.debug(String
.format("Application will quit with notification %s", notification
));
1390 //Terminating rendezvous discovery
1391 RendezvousFactory
.instance().quit();
1393 //Writing usage info
1394 preferences
.setProperty("uses", preferences
.getInteger("uses") + 1);
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() {
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()) {
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();
1482 for(BrowserController controller
: browsers
) {
1483 if(controller
.getSession() == null) {
1484 controller
.window().makeKeyAndOrderFront(null);
1489 final BrowserController controller
= new BrowserController();
1490 controller
.addListener(new WindowListener() {
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
);
1512 * We are not a Windows application. Long live the application wide menu bar.
1515 public boolean applicationShouldTerminateAfterLastWindowClosed(NSApplication app
) {
1520 protected String
getBundleName() {