Merge pull request #64 in ITERATE/cyberduck from feature/windows/9074 to master
[cyberduck.git] / source / ch / cyberduck / ui / cocoa / BrowserController.java
blobbacbcfe82cff1e3d5d13f0142620135e616b2684
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.*;
23 import ch.cyberduck.binding.foundation.NSArray;
24 import ch.cyberduck.binding.foundation.NSAttributedString;
25 import ch.cyberduck.binding.foundation.NSDictionary;
26 import ch.cyberduck.binding.foundation.NSEnumerator;
27 import ch.cyberduck.binding.foundation.NSIndexSet;
28 import ch.cyberduck.binding.foundation.NSNotification;
29 import ch.cyberduck.binding.foundation.NSNotificationCenter;
30 import ch.cyberduck.binding.foundation.NSObject;
31 import ch.cyberduck.binding.foundation.NSRange;
32 import ch.cyberduck.binding.foundation.NSString;
33 import ch.cyberduck.core.*;
34 import ch.cyberduck.core.aquaticprime.LicenseFactory;
35 import ch.cyberduck.core.bonjour.RendezvousCollection;
36 import ch.cyberduck.core.editor.DefaultEditorListener;
37 import ch.cyberduck.core.editor.Editor;
38 import ch.cyberduck.core.editor.EditorFactory;
39 import ch.cyberduck.core.exception.AccessDeniedException;
40 import ch.cyberduck.core.exception.BackgroundException;
41 import ch.cyberduck.core.exception.ConnectionCanceledException;
42 import ch.cyberduck.core.features.Location;
43 import ch.cyberduck.core.features.Move;
44 import ch.cyberduck.core.features.Touch;
45 import ch.cyberduck.core.local.Application;
46 import ch.cyberduck.core.local.ApplicationQuitCallback;
47 import ch.cyberduck.core.local.BrowserLauncherFactory;
48 import ch.cyberduck.core.local.TemporaryFileServiceFactory;
49 import ch.cyberduck.core.pasteboard.HostPasteboard;
50 import ch.cyberduck.core.pasteboard.PathPasteboard;
51 import ch.cyberduck.core.pasteboard.PathPasteboardFactory;
52 import ch.cyberduck.core.preferences.Preferences;
53 import ch.cyberduck.core.preferences.PreferencesFactory;
54 import ch.cyberduck.core.resources.IconCacheFactory;
55 import ch.cyberduck.core.serializer.HostDictionary;
56 import ch.cyberduck.core.ssl.DefaultTrustManagerHostnameCallback;
57 import ch.cyberduck.core.ssl.KeychainX509KeyManager;
58 import ch.cyberduck.core.ssl.KeychainX509TrustManager;
59 import ch.cyberduck.core.ssl.SSLSession;
60 import ch.cyberduck.core.threading.BackgroundAction;
61 import ch.cyberduck.core.threading.DefaultMainAction;
62 import ch.cyberduck.core.threading.TransferBackgroundAction;
63 import ch.cyberduck.core.threading.WorkerBackgroundAction;
64 import ch.cyberduck.core.transfer.DisabledTransferErrorCallback;
65 import ch.cyberduck.core.transfer.DownloadTransfer;
66 import ch.cyberduck.core.transfer.SyncTransfer;
67 import ch.cyberduck.core.transfer.Transfer;
68 import ch.cyberduck.core.transfer.TransferAction;
69 import ch.cyberduck.core.transfer.TransferAdapter;
70 import ch.cyberduck.core.transfer.TransferCallback;
71 import ch.cyberduck.core.transfer.TransferItem;
72 import ch.cyberduck.core.transfer.TransferOptions;
73 import ch.cyberduck.core.transfer.TransferProgress;
74 import ch.cyberduck.core.transfer.TransferPrompt;
75 import ch.cyberduck.core.transfer.UploadTransfer;
76 import ch.cyberduck.core.worker.DisconnectWorker;
77 import ch.cyberduck.core.worker.MountWorker;
78 import ch.cyberduck.core.worker.SessionListWorker;
79 import ch.cyberduck.ui.browser.Column;
80 import ch.cyberduck.ui.browser.DownloadDirectoryFinder;
81 import ch.cyberduck.ui.browser.PathReloadFinder;
82 import ch.cyberduck.ui.browser.RegexFilter;
83 import ch.cyberduck.ui.browser.SearchFilter;
84 import ch.cyberduck.ui.browser.UploadDirectoryFinder;
85 import ch.cyberduck.ui.browser.UploadTargetFinder;
86 import ch.cyberduck.ui.cocoa.delegate.ArchiveMenuDelegate;
87 import ch.cyberduck.ui.cocoa.delegate.CopyURLMenuDelegate;
88 import ch.cyberduck.ui.cocoa.delegate.EditMenuDelegate;
89 import ch.cyberduck.ui.cocoa.delegate.OpenURLMenuDelegate;
90 import ch.cyberduck.ui.cocoa.delegate.URLMenuDelegate;
91 import ch.cyberduck.ui.cocoa.quicklook.QLPreviewPanel;
92 import ch.cyberduck.ui.cocoa.quicklook.QLPreviewPanelController;
93 import ch.cyberduck.ui.cocoa.quicklook.QuickLook;
94 import ch.cyberduck.ui.cocoa.quicklook.QuickLookFactory;
95 import ch.cyberduck.ui.cocoa.threading.WindowMainAction;
96 import ch.cyberduck.ui.cocoa.view.BookmarkCell;
97 import ch.cyberduck.ui.cocoa.view.OutlineCell;
99 import org.apache.commons.collections.CollectionUtils;
100 import org.apache.commons.lang3.StringUtils;
101 import org.apache.log4j.Logger;
102 import org.rococoa.Foundation;
103 import org.rococoa.ID;
104 import org.rococoa.Rococoa;
105 import org.rococoa.Selector;
106 import org.rococoa.cocoa.CGFloat;
107 import org.rococoa.cocoa.foundation.NSInteger;
108 import org.rococoa.cocoa.foundation.NSPoint;
109 import org.rococoa.cocoa.foundation.NSRect;
110 import org.rococoa.cocoa.foundation.NSSize;
111 import org.rococoa.cocoa.foundation.NSUInteger;
113 import java.security.cert.CertificateException;
114 import java.security.cert.X509Certificate;
115 import java.text.MessageFormat;
116 import java.util.ArrayList;
117 import java.util.Collections;
118 import java.util.Comparator;
119 import java.util.EnumSet;
120 import java.util.HashMap;
121 import java.util.HashSet;
122 import java.util.Iterator;
123 import java.util.List;
124 import java.util.Locale;
125 import java.util.Map;
126 import java.util.Set;
129 * @version $Id$
131 public class BrowserController extends WindowController
132 implements ProgressListener, TranscriptListener, NSToolbar.Delegate, NSMenu.Validation, QLPreviewPanelController {
133 private static Logger log = Logger.getLogger(BrowserController.class);
136 * No file filter.
138 private static final Filter<Path> NULL_FILTER = new NullFilter<Path>();
141 * Filter hidden files.
143 private static final Filter<Path> HIDDEN_FILTER = new RegexFilter();
145 private final BookmarkCollection bookmarks
146 = BookmarkCollection.defaultCollection();
148 private final BrowserToolbarFactory browserToolbarFactory = new BrowserToolbarFactory(this);
150 private final NSNotificationCenter notificationCenter = NSNotificationCenter.defaultCenter();
155 private Session<?> session;
158 * Log Drawer
160 private TranscriptController transcript;
162 private final QuickLook quicklook = QuickLookFactory.get();
164 private Preferences preferences
165 = PreferencesFactory.get();
168 * Hide files beginning with '.'
170 private boolean showHiddenFiles;
172 private Filter<Path> filenameFilter;
175 if(PreferencesFactory.get().getBoolean("browser.showHidden")) {
176 this.filenameFilter = new NullFilter<Path>();
177 this.showHiddenFiles = true;
179 else {
180 this.filenameFilter = new RegexFilter();
181 this.showHiddenFiles = false;
185 private final NSTextFieldCell outlineCellPrototype = OutlineCell.outlineCell();
186 private final NSImageCell imageCellPrototype = NSImageCell.imageCell();
187 private final NSTextFieldCell textCellPrototype = NSTextFieldCell.textFieldCell();
188 private final NSTextFieldCell filenameCellPrototype = NSTextFieldCell.textFieldCell();
190 private final TableColumnFactory browserListColumnsFactory = new TableColumnFactory();
191 private final TableColumnFactory browserOutlineColumnsFactory = new TableColumnFactory();
192 private final TableColumnFactory bookmarkTableColumnFactory = new TableColumnFactory();
194 // setting appearance attributes()
195 private final NSLayoutManager layoutManager = NSLayoutManager.layoutManager();
197 @Delegate
198 private BrowserOutlineViewModel browserOutlineModel;
200 @Outlet
201 private NSOutlineView browserOutlineView;
203 @Delegate
204 private AbstractBrowserTableDelegate browserOutlineViewDelegate;
206 @Delegate
207 private BrowserListViewModel browserListModel;
209 @Outlet
210 private NSTableView browserListView;
212 @Delegate
213 private AbstractBrowserTableDelegate browserListViewDelegate;
215 private NSToolbar toolbar;
217 private final Navigation navigation = new Navigation();
219 private PathPasteboard pasteboard;
221 private ListProgressListener listener
222 = new PromptLimitedListProgressListener(this);
225 * Caching files listings of previously listed directories
227 private PathCache cache
228 = new PathCache(preferences.getInteger("browser.cache.size"));
230 private List<Editor> editors
231 = new ArrayList<Editor>();
233 public BrowserController() {
234 this.loadBundle();
237 @Override
238 protected String getBundleName() {
239 return "Browser";
242 protected void validateToolbar() {
243 this.window().toolbar().validateVisibleItems();
246 public static void updateBookmarkTableRowHeight() {
247 for(BrowserController controller : MainController.getBrowsers()) {
248 controller._updateBookmarkCell();
252 public static void updateBrowserTableAttributes() {
253 for(BrowserController controller : MainController.getBrowsers()) {
254 controller._updateBrowserAttributes(controller.browserListView);
255 controller._updateBrowserAttributes(controller.browserOutlineView);
259 public static void updateBrowserTableColumns() {
260 for(BrowserController controller : MainController.getBrowsers()) {
261 controller._updateBrowserColumns(controller.browserListView, controller.browserListViewDelegate);
262 controller._updateBrowserColumns(controller.browserOutlineView, controller.browserOutlineViewDelegate);
266 @Override
267 public void awakeFromNib() {
268 super.awakeFromNib();
269 // Configure Toolbar
270 this.toolbar = NSToolbar.toolbarWithIdentifier("Cyberduck Toolbar");
271 this.toolbar.setDelegate((this.id()));
272 this.toolbar.setAllowsUserCustomization(true);
273 this.toolbar.setAutosavesConfiguration(true);
274 this.window.setToolbar(toolbar);
275 this.window.makeFirstResponder(quickConnectPopup);
276 this._updateBrowserColumns(browserListView, browserListViewDelegate);
277 this._updateBrowserColumns(browserOutlineView, browserOutlineViewDelegate);
278 if(preferences.getBoolean("browser.transcript.open")) {
279 this.logDrawer.open();
281 if(LicenseFactory.find().equals(LicenseFactory.EMPTY_LICENSE)) {
282 this.addDonateWindowTitle();
284 this.setNavigation(false);
285 this.selectBookmarks();
288 protected Comparator<Path> getComparator() {
289 return this.getSelectedBrowserDelegate().getSortingComparator();
292 protected Filter<Path> getFilter() {
293 return this.filenameFilter;
296 public PathPasteboard getPasteboard() {
297 return pasteboard;
300 protected void setPathFilter(final String search) {
301 if(log.isDebugEnabled()) {
302 log.debug(String.format("Set path filter to %s", search));
304 if(StringUtils.isBlank(search)) {
305 this.searchField.setStringValue(StringUtils.EMPTY);
306 // Revert to the last used default filter
307 if(this.isShowHiddenFiles()) {
308 this.filenameFilter = NULL_FILTER;
310 else {
311 this.filenameFilter = HIDDEN_FILTER;
314 else {
315 // Setting up a custom filter for the directory listing
316 this.filenameFilter = new SearchFilter(search);
320 public void setShowHiddenFiles(boolean showHidden) {
321 if(showHidden) {
322 this.filenameFilter = NULL_FILTER;
323 this.showHiddenFiles = true;
325 else {
326 this.filenameFilter = HIDDEN_FILTER;
327 this.showHiddenFiles = false;
331 public boolean isShowHiddenFiles() {
332 return this.showHiddenFiles;
336 * Marks the current browser as the first responder
338 private void getFocus() {
339 NSView view;
340 if(this.getSelectedTabView() == TAB_BOOKMARKS) {
341 view = bookmarkTable;
343 else {
344 if(this.isMounted()) {
345 view = this.getSelectedBrowserView();
347 else {
348 view = quickConnectPopup;
351 this.setStatus();
352 window.makeFirstResponder(view);
356 * Make the browser reload its content. Will make use of the cache.
358 protected void reload() {
359 if(this.isMounted()) {
360 this.reload(workdir, Collections.singleton(workdir), this.getSelectedPaths(), false);
362 else {
363 final NSTableView browser = this.getSelectedBrowserView();
364 final BrowserTableDataSource model = this.getSelectedBrowserModel();
365 model.render(browser, Collections.<Path>emptyList());
366 this.setStatus();
371 * Make the browser reload its content. Invalidates the cache.
373 * @param workdir Use working directory as the current root of the browser
374 * @param selected The items to be selected
376 protected void reload(final Path workdir, final List<Path> changed, final List<Path> selected) {
377 this.reload(workdir, new PathReloadFinder().find(changed), selected, true);
381 * Make the browser reload its content. Invalidates the cache.
383 * @param workdir Use working directory as the current root of the browser
384 * @param folders Folders to render
385 * @param selected The items to be selected
387 protected void reload(final Path workdir, final Set<Path> folders, final List<Path> selected) {
388 this.reload(workdir, folders, selected, true);
392 * Make the browser reload its content. Invalidates the cache.
394 * @param workdir Use working directory as the current root of the browser
395 * @param folders Folders to render
396 * @param selected The items to be selected
397 * @param invalidate Invalidate the cache before rendering
399 protected void reload(final Path workdir, final Set<Path> folders, final List<Path> selected, final boolean invalidate) {
400 if(log.isDebugEnabled()) {
401 log.debug(String.format("Reload data with selected files %s", selected));
403 final BrowserTableDataSource model = this.getSelectedBrowserModel();
404 final NSTableView browser = this.getSelectedBrowserView();
405 if(folders.isEmpty()) {
406 // Render empty browser
407 model.render(browser, Collections.<Path>emptyList());
409 for(final Path folder : folders) {
410 if(invalidate) {
411 // Invalidate cache
412 cache.invalidate(folder);
414 else {
415 if(cache.isCached(folder)) {
416 reload(browser, model, workdir, selected, folder);
417 return;
420 // Delay render until path is cached in the background
421 this.background(new WorkerBackgroundAction<AttributedList<Path>>(this, session, cache,
422 new SessionListWorker(cache, folder, listener) {
423 @Override
424 public void cleanup(final AttributedList<Path> list) {
425 super.cleanup(list);
426 // Update the working directory if listing is successful
427 if(!(this.initialize() == list)) {
428 reload(browser, model, workdir, selected, folder);
435 this.setStatus();
439 * @param browser Browser view
440 * @param model Browser Model
441 * @param workdir Use working directory as the current root of the browser
442 * @param selected Selected files in browser
443 * @param folder Folder to render
445 private void reload(final NSTableView browser, final BrowserTableDataSource model, final Path workdir, final List<Path> selected, final Path folder) {
446 this.workdir = workdir;
447 this.setNavigation(workdir != null);
448 this.setStatus();
449 model.render(browser, Collections.singletonList(folder));
450 this.select(selected);
453 private void select(final List<Path> selected) {
454 final NSTableView browser = this.getSelectedBrowserView();
455 if(CollectionUtils.isEqualCollection(this.getSelectedPaths(), selected)) {
456 return;
458 browser.deselectAll(null);
459 for(Path path : selected) {
460 this.select(path, true, true);
465 * @param file Path to select
466 * @param expand Keep previous selection
467 * @param scroll Scroll to selection
469 private void select(final Path file, final boolean expand, final boolean scroll) {
470 final NSTableView browser = this.getSelectedBrowserView();
471 final BrowserTableDataSource model = this.getSelectedBrowserModel();
472 if(log.isDebugEnabled()) {
473 log.debug(String.format("Select row for reference %s", file));
475 int row = model.indexOf(browser, file);
476 if(-1 == row) {
477 log.warn(String.format("Failed to find row for %s", file));
478 return;
480 final NSInteger index = new NSInteger(row);
481 browser.selectRowIndexes(NSIndexSet.indexSetWithIndex(index), expand);
482 if(scroll) {
483 browser.scrollRowToVisible(index);
487 private void updateQuickLookSelection(final List<Path> selected) {
488 if(quicklook.isAvailable()) {
489 final List<TransferItem> downloads = new ArrayList<TransferItem>();
490 for(Path path : selected) {
491 if(!path.isFile()) {
492 continue;
494 downloads.add(new TransferItem(
495 path, TemporaryFileServiceFactory.get().create(session.getHost().getUuid(), path)));
497 if(downloads.size() > 0) {
498 final Transfer download = new DownloadTransfer(session.getHost(), downloads);
499 final TransferOptions options = new TransferOptions();
500 background(new TransferBackgroundAction(this, session, cache, new TransferAdapter() {
501 @Override
502 public void progress(final TransferProgress status) {
503 message(status.getProgress());
505 }, this, this, download, options,
506 new TransferPrompt() {
507 @Override
508 public TransferAction prompt(final TransferItem item) {
509 return TransferAction.comparison;
512 @Override
513 public boolean isSelected(final TransferItem file) {
514 return true;
517 @Override
518 public void message(final String message) {
519 BrowserController.this.message(message);
521 }, new DisabledTransferErrorCallback()
523 @Override
524 public void cleanup() {
525 super.cleanup();
526 final List<Local> previews = new ArrayList<Local>();
527 for(TransferItem download : downloads) {
528 previews.add(download.local);
530 // Change files in Quick Look
531 quicklook.select(previews);
532 // Open Quick Look Preview Panel
533 quicklook.open();
536 @Override
537 public String getActivity() {
538 return LocaleFactory.localizedString("Quick Look", "Status");
546 * @return The first selected path found or null if there is no selection
548 protected Path getSelectedPath() {
549 final List<Path> s = this.getSelectedPaths();
550 if(s.size() > 0) {
551 return s.get(0);
553 return null;
557 * @return All selected paths or an empty list if there is no selection
559 protected List<Path> getSelectedPaths() {
560 final AbstractBrowserTableDelegate delegate = this.getSelectedBrowserDelegate();
561 final NSTableView view = this.getSelectedBrowserView();
562 final NSIndexSet iterator = view.selectedRowIndexes();
563 final List<Path> selected = new ArrayList<Path>();
564 for(NSUInteger index = iterator.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = iterator.indexGreaterThanIndex(index)) {
565 final Path file = delegate.pathAtRow(index.intValue());
566 if(null == file) {
567 break;
569 selected.add(file);
571 return selected;
574 protected int getSelectionCount() {
575 return this.getSelectedBrowserView().numberOfSelectedRows().intValue();
578 private static NSPoint cascade = new NSPoint(0, 0);
580 @Override
581 public void setWindow(NSWindow window) {
582 // Save frame rectangle
583 window.setFrameAutosaveName("Browser");
584 window.setTitle(preferences.getProperty("application.name"));
585 window.setMiniwindowImage(IconCacheFactory.<NSImage>get().iconNamed("cyberduck-document.icns"));
586 window.setMovableByWindowBackground(true);
587 window.setCollectionBehavior(window.collectionBehavior() | NSWindow.NSWindowCollectionBehavior.NSWindowCollectionBehaviorFullScreenPrimary);
588 window.setContentMinSize(new NSSize(400d, 200d));
589 // Accept file promises made myself
590 window.registerForDraggedTypes(NSArray.arrayWithObject(NSPasteboard.FilesPromisePboardType));
591 super.setWindow(window);
592 cascade = this.cascade(cascade);
596 * @return NSDragOperation
598 public NSUInteger draggingEntered(final NSDraggingInfo sender) {
599 return this.draggingUpdated(sender);
602 public NSUInteger draggingUpdated(final NSDraggingInfo sender) {
603 final NSPasteboard pasteboard = sender.draggingPasteboard();
604 if(pasteboard.types().indexOfObject(NSString.stringWithString(NSPasteboard.FilesPromisePboardType)) != null) {
605 final NSView hit = sender.draggingDestinationWindow().contentView().hitTest(sender.draggingLocation());
606 if(hit != null) {
607 if(hit.equals(bookmarkButton)) {
608 if(historyButton.state() == NSCell.NSOnState
609 || bonjourButton.state() == NSCell.NSOnState) {
610 return NSDraggingInfo.NSDragOperationCopy;
615 return NSDraggingInfo.NSDragOperationNone;
618 public boolean prepareForDragOperation(final NSDraggingInfo sender) {
619 // Continue to performDragOperation
620 return true;
623 public boolean performDragOperation(final NSDraggingInfo sender) {
624 for(Host bookmark : HostPasteboard.getPasteboard()) {
625 final Host duplicate = new HostDictionary().deserialize(bookmark.serialize(SerializerFactory.get()));
626 // Make sure a new UUID is assigned for duplicate
627 duplicate.setUuid(null);
628 bookmarks.add(0, duplicate);
630 return true;
633 @Outlet
634 private NSDrawer logDrawer;
636 public void drawerDidOpen(NSNotification notification) {
637 preferences.setProperty("browser.transcript.open", true);
640 public void drawerDidClose(NSNotification notification) {
641 preferences.setProperty("browser.transcript.open", false);
642 transcript.clear();
645 public NSSize drawerWillResizeContents_toSize(final NSDrawer sender, final NSSize contentSize) {
646 return contentSize;
649 public void setLogDrawer(NSDrawer drawer) {
650 this.logDrawer = drawer;
651 this.transcript = new TranscriptController() {
652 @Override
653 public boolean isOpen() {
654 return logDrawer.state() == NSDrawer.OpenState;
657 this.logDrawer.setContentView(this.transcript.getLogView());
658 this.logDrawer.setDelegate(this.id());
661 private NSButton donateButton;
663 public void setDonateButton(NSButton donateButton) {
664 this.donateButton = donateButton;
665 this.donateButton.setTitle(LocaleFactory.localizedString("Get a donation key!", "License"));
666 this.donateButton.setAction(Foundation.selector("donateMenuClicked:"));
667 this.donateButton.sizeToFit();
670 private void addDonateWindowTitle() {
671 NSView parent = this.window().contentView().superview();
672 NSSize bounds = parent.frame().size;
673 NSSize size = donateButton.frame().size;
674 donateButton.setFrame(new NSRect(
675 new NSPoint(
676 bounds.width.intValue() - size.width.intValue() - 40,
677 bounds.height.intValue() - size.height.intValue() + 3),
678 new NSSize(
679 size.width.intValue(),
680 size.height.intValue())
683 donateButton.setAutoresizingMask(new NSUInteger(NSView.NSViewMinXMargin | NSView.NSViewMinYMargin));
684 parent.addSubview(donateButton);
687 public void removeDonateWindowTitle() {
688 donateButton.removeFromSuperview();
691 protected static final int TAB_BOOKMARKS = 0;
692 protected static final int TAB_LIST_VIEW = 1;
693 protected static final int TAB_OUTLINE_VIEW = 2;
695 protected int getSelectedTabView() {
696 return browserTabView.indexOfTabViewItem(browserTabView.selectedTabViewItem());
699 private NSTabView browserTabView;
701 public void setBrowserTabView(NSTabView browserTabView) {
702 this.browserTabView = browserTabView;
706 * @return The currently selected browser view (which is either an outlineview or a plain tableview)
708 public NSTableView getSelectedBrowserView() {
709 switch(preferences.getInteger("browser.view")) {
710 case SWITCH_LIST_VIEW: {
711 return browserListView;
713 case SWITCH_OUTLINE_VIEW: {
714 return browserOutlineView;
717 throw new FactoryException("No selected browser view");
721 * @return The datasource of the currently selected browser view
723 public BrowserTableDataSource getSelectedBrowserModel() {
724 switch(this.browserSwitchView.selectedSegment()) {
725 case SWITCH_LIST_VIEW: {
726 return browserListModel;
728 case SWITCH_OUTLINE_VIEW: {
729 return browserOutlineModel;
732 throw new FactoryException("No selected browser view");
735 public AbstractBrowserTableDelegate getSelectedBrowserDelegate() {
736 switch(this.browserSwitchView.selectedSegment()) {
737 case SWITCH_LIST_VIEW: {
738 return browserListViewDelegate;
740 case SWITCH_OUTLINE_VIEW: {
741 return browserOutlineViewDelegate;
744 throw new FactoryException("No selected browser view");
747 @Outlet
748 private NSMenu editMenu;
750 @Delegate
751 private EditMenuDelegate editMenuDelegate;
753 public void setEditMenu(NSMenu editMenu) {
754 this.editMenu = editMenu;
755 this.editMenuDelegate = new EditMenuDelegate() {
756 @Override
757 protected Path getEditable() {
758 final Path selected = BrowserController.this.getSelectedPath();
759 if(null == selected) {
760 return null;
762 if(isEditable(selected)) {
763 return selected;
765 return null;
768 @Override
769 protected ID getTarget() {
770 return BrowserController.this.id();
773 this.editMenu.setDelegate(editMenuDelegate.id());
776 public EditMenuDelegate getEditMenuDelegate() {
777 return editMenuDelegate;
780 @Outlet
781 private NSMenu urlMenu;
783 @Delegate
784 private URLMenuDelegate urlMenuDelegate;
786 public void setUrlMenu(NSMenu urlMenu) {
787 this.urlMenu = urlMenu;
788 this.urlMenuDelegate = new CopyURLMenuDelegate() {
789 @Override
790 protected Session<?> getSession() {
791 return BrowserController.this.getSession();
794 @Override
795 protected List<Path> getSelected() {
796 final List<Path> s = BrowserController.this.getSelectedPaths();
797 if(s.isEmpty()) {
798 if(BrowserController.this.isMounted()) {
799 return Collections.singletonList(BrowserController.this.workdir());
802 return s;
805 this.urlMenu.setDelegate(urlMenuDelegate.id());
808 @Outlet
809 private NSMenu openUrlMenu;
811 @Delegate
812 private URLMenuDelegate openUrlMenuDelegate;
814 public void setOpenUrlMenu(NSMenu openUrlMenu) {
815 this.openUrlMenu = openUrlMenu;
816 this.openUrlMenuDelegate = new OpenURLMenuDelegate() {
817 @Override
818 protected Session<?> getSession() {
819 return BrowserController.this.getSession();
822 @Override
823 protected List<Path> getSelected() {
824 final List<Path> s = BrowserController.this.getSelectedPaths();
825 if(s.isEmpty()) {
826 if(BrowserController.this.isMounted()) {
827 return Collections.singletonList(BrowserController.this.workdir());
830 return s;
833 this.openUrlMenu.setDelegate(openUrlMenuDelegate.id());
836 @Outlet
837 private NSMenu archiveMenu;
839 @Delegate
840 private ArchiveMenuDelegate archiveMenuDelegate;
842 public void setArchiveMenu(NSMenu archiveMenu) {
843 this.archiveMenu = archiveMenu;
844 this.archiveMenuDelegate = new ArchiveMenuDelegate();
845 this.archiveMenu.setDelegate(archiveMenuDelegate.id());
848 @Outlet
849 private NSButton bonjourButton;
851 public void setBonjourButton(NSButton bonjourButton) {
852 this.bonjourButton = bonjourButton;
853 NSImage img = IconCacheFactory.<NSImage>get().iconNamed("rendezvous.tiff", 16);
854 img.setTemplate(false);
855 this.bonjourButton.setImage(img);
856 this.setRecessedBezelStyle(this.bonjourButton);
857 this.bonjourButton.setTarget(this.id());
858 this.bonjourButton.setAction(Foundation.selector("bookmarkButtonClicked:"));
861 @Outlet
862 private NSButton historyButton;
864 public void setHistoryButton(NSButton historyButton) {
865 this.historyButton = historyButton;
866 NSImage img = IconCacheFactory.<NSImage>get().iconNamed("history.tiff", 16);
867 img.setTemplate(false);
868 this.historyButton.setImage(img);
869 this.setRecessedBezelStyle(this.historyButton);
870 this.historyButton.setTarget(this.id());
871 this.historyButton.setAction(Foundation.selector("bookmarkButtonClicked:"));
874 @Outlet
875 private NSButton bookmarkButton;
877 public void setBookmarkButton(NSButton bookmarkButton) {
878 this.bookmarkButton = bookmarkButton;
879 NSImage img = IconCacheFactory.<NSImage>get().iconNamed("bookmarks.tiff", 16);
880 img.setTemplate(false);
881 this.bookmarkButton.setImage(img);
882 this.setRecessedBezelStyle(this.bookmarkButton);
883 this.bookmarkButton.setTarget(this.id());
884 this.bookmarkButton.setAction(Foundation.selector("bookmarkButtonClicked:"));
885 this.bookmarkButton.setState(NSCell.NSOnState); // Set as default selected bookmark source
888 public void bookmarkButtonClicked(final NSButton sender) {
889 if(sender != bonjourButton) {
890 bonjourButton.setState(NSCell.NSOffState);
892 if(sender != historyButton) {
893 historyButton.setState(NSCell.NSOffState);
895 if(sender != bookmarkButton) {
896 bookmarkButton.setState(NSCell.NSOffState);
898 sender.setState(NSCell.NSOnState);
899 this.selectBookmarks();
902 private void setRecessedBezelStyle(final NSButton b) {
903 b.setBezelStyle(NSButton.NSRecessedBezelStyle);
904 b.setButtonType(NSButton.NSMomentaryPushButtonButton);
905 b.setImagePosition(NSCell.NSImageLeft);
906 b.setFont(NSFont.boldSystemFontOfSize(11f));
907 b.setShowsBorderOnlyWhileMouseInside(true);
910 @Action
911 public void sortBookmarksByNickame(final ID sender) {
912 bookmarks.sortByNickname();
913 this.reloadBookmarks();
916 @Action
917 public void sortBookmarksByHostname(final ID sender) {
918 bookmarks.sortByHostname();
919 this.reloadBookmarks();
922 @Action
923 public void sortBookmarksByProtocol(final ID sender) {
924 bookmarks.sortByProtocol();
925 this.reloadBookmarks();
928 private NSSegmentedControl bookmarkSwitchView;
930 private static final int SWITCH_BOOKMARK_VIEW = 0;
932 public void setBookmarkSwitchView(NSSegmentedControl bookmarkSwitchView) {
933 this.bookmarkSwitchView = bookmarkSwitchView;
934 this.bookmarkSwitchView.setSegmentCount(1);
935 this.bookmarkSwitchView.setToolTip(LocaleFactory.localizedString("Bookmarks"));
936 final NSImage image = IconCacheFactory.<NSImage>get().iconNamed("book.tiff");
937 this.bookmarkSwitchView.setImage_forSegment(image, SWITCH_BOOKMARK_VIEW);
938 final NSSegmentedCell cell = Rococoa.cast(this.bookmarkSwitchView.cell(), NSSegmentedCell.class);
939 cell.setTrackingMode(NSSegmentedCell.NSSegmentSwitchTrackingSelectAny);
940 cell.setControlSize(NSCell.NSRegularControlSize);
941 this.bookmarkSwitchView.setTarget(this.id());
942 this.bookmarkSwitchView.setAction(Foundation.selector("bookmarkSwitchClicked:"));
943 this.bookmarkSwitchView.setSelectedSegment(SWITCH_BOOKMARK_VIEW);
946 @Action
947 public void bookmarkSwitchClicked(final ID sender) {
948 // Toggle
949 final boolean open = this.getSelectedTabView() != TAB_BOOKMARKS;
950 bookmarkSwitchView.setSelected_forSegment(open, SWITCH_BOOKMARK_VIEW);
951 this.setNavigation(!open && this.isMounted());
952 if(open) {
953 this.selectBookmarks();
955 else {
956 this.selectBrowser(preferences.getInteger("browser.view"));
960 private NSSegmentedControl browserSwitchView;
962 private static final int SWITCH_LIST_VIEW = 0;
963 private static final int SWITCH_OUTLINE_VIEW = 1;
965 public void setBrowserSwitchView(NSSegmentedControl view) {
966 browserSwitchView = view;
967 browserSwitchView.setSegmentCount(2); // list, outline
968 final NSImage list = IconCacheFactory.<NSImage>get().iconNamed("list.tiff");
969 list.setTemplate(true);
970 browserSwitchView.setImage_forSegment(list, SWITCH_LIST_VIEW);
971 final NSImage outline = IconCacheFactory.<NSImage>get().iconNamed("outline.tiff");
972 outline.setTemplate(true);
973 browserSwitchView.setImage_forSegment(outline, SWITCH_OUTLINE_VIEW);
974 browserSwitchView.setTarget(this.id());
975 browserSwitchView.setAction(Foundation.selector("browserSwitchButtonClicked:"));
976 final NSSegmentedCell cell = Rococoa.cast(browserSwitchView.cell(), NSSegmentedCell.class);
977 cell.setTrackingMode(NSSegmentedCell.NSSegmentSwitchTrackingSelectOne);
978 cell.setControlSize(NSCell.NSRegularControlSize);
979 browserSwitchView.setSelectedSegment(preferences.getInteger("browser.view"));
982 public NSSegmentedControl getBrowserSwitchView() {
983 return browserSwitchView;
986 @Action
987 public void browserSwitchButtonClicked(final NSSegmentedControl sender) {
988 // Highlight selected browser view
989 this.selectBrowser(sender.selectedSegment());
992 @Action
993 public void browserSwitchMenuClicked(final NSMenuItem sender) {
994 // Highlight selected browser view
995 this.selectBrowser(sender.tag());
998 private void selectBrowser(int selected) {
999 bookmarkSwitchView.setSelected_forSegment(false, SWITCH_BOOKMARK_VIEW);
1000 browserSwitchView.setSelectedSegment(selected);
1001 switch(selected) {
1002 case SWITCH_LIST_VIEW:
1003 browserTabView.selectTabViewItemAtIndex(TAB_LIST_VIEW);
1004 break;
1005 case SWITCH_OUTLINE_VIEW:
1006 browserTabView.selectTabViewItemAtIndex(TAB_OUTLINE_VIEW);
1007 break;
1009 // Save selected browser view
1010 preferences.setProperty("browser.view", selected);
1011 // Remove any custom file filter
1012 this.setPathFilter(null);
1013 // Update from model
1014 this.reload();
1015 // Focus on browser view
1016 this.getFocus();
1019 private void selectBookmarks() {
1020 bookmarkSwitchView.setSelected_forSegment(true, SWITCH_BOOKMARK_VIEW);
1021 // Display bookmarks
1022 browserTabView.selectTabViewItemAtIndex(TAB_BOOKMARKS);
1023 final AbstractHostCollection source;
1024 if(bookmarkButton.state() == NSCell.NSOnState) {
1025 source = bookmarks;
1027 else if(bonjourButton.state() == NSCell.NSOnState) {
1028 source = RendezvousCollection.defaultCollection();
1030 else if(historyButton.state() == NSCell.NSOnState) {
1031 source = HistoryCollection.defaultCollection();
1033 else {
1034 source = AbstractHostCollection.empty();
1036 if(!source.isLoaded()) {
1037 browserSpinner.startAnimation(null);
1038 source.addListener(new AbstractCollectionListener<Host>() {
1039 @Override
1040 public void collectionLoaded() {
1041 invoke(new WindowMainAction(BrowserController.this) {
1042 @Override
1043 public void run() {
1044 browserSpinner.stopAnimation(null);
1045 bookmarkTable.setGridStyleMask(NSTableView.NSTableViewSolidHorizontalGridLineMask);
1048 source.removeListener(this);
1052 else {
1053 browserSpinner.stopAnimation(null);
1054 bookmarkTable.setGridStyleMask(NSTableView.NSTableViewSolidHorizontalGridLineMask);
1056 bookmarkModel.setSource(source);
1057 this.setBookmarkFilter(null);
1058 this.reloadBookmarks();
1059 if(this.isMounted()) {
1060 int row = this.bookmarkModel.getSource().indexOf(session.getHost());
1061 if(row != -1) {
1062 this.bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(new NSInteger(row)), false);
1063 this.bookmarkTable.scrollRowToVisible(new NSInteger(row));
1066 this.getFocus();
1070 * Reload bookmark table from currently selected model
1072 public void reloadBookmarks() {
1073 bookmarkTable.reloadData();
1074 this.setStatus();
1077 private abstract class AbstractBrowserOutlineViewDelegate extends AbstractBrowserTableDelegate
1078 implements NSOutlineView.Delegate {
1080 protected AbstractBrowserOutlineViewDelegate(final NSTableColumn selectedColumn) {
1081 super(selectedColumn);
1084 public String outlineView_toolTipForCell_rect_tableColumn_item_mouseLocation(NSOutlineView t, NSCell cell,
1085 ID rect, NSTableColumn c,
1086 NSObject item, NSPoint mouseLocation) {
1087 return this.tooltip(cache.lookup(new NSObjectPathReference(item)));
1090 public String outlineView_typeSelectStringForTableColumn_item(final NSOutlineView view,
1091 final NSTableColumn tableColumn,
1092 final NSObject item) {
1093 if(tableColumn.identifier().equals(Column.filename.name())) {
1094 return browserOutlineModel.outlineView_objectValueForTableColumn_byItem(view, tableColumn, item).toString();
1096 return null;
1099 @Override
1100 protected void setBrowserColumnSortingIndicator(NSImage image, String columnIdentifier) {
1101 browserOutlineView.setIndicatorImage_inTableColumn(image,
1102 browserOutlineView.tableColumnWithIdentifier(columnIdentifier));
1105 @Override
1106 protected Path pathAtRow(final int row) {
1107 if(row < browserOutlineView.numberOfRows().intValue()) {
1108 return cache.lookup(new NSObjectPathReference(browserOutlineView.itemAtRow(new NSInteger(row))));
1110 log.warn(String.format("No item at row %d", row));
1111 return null;
1115 private abstract class AbstractBrowserListViewDelegate<E> extends AbstractBrowserTableDelegate
1116 implements NSTableView.Delegate {
1118 protected AbstractBrowserListViewDelegate(final NSTableColumn selectedColumn) {
1119 super(selectedColumn);
1122 public String tableView_toolTipForCell_rect_tableColumn_row_mouseLocation(NSTableView t, NSCell cell,
1123 ID rect, NSTableColumn c,
1124 NSInteger row, NSPoint mouseLocation) {
1125 return this.tooltip(browserListModel.get(workdir()).get(row.intValue()));
1128 @Override
1129 protected void setBrowserColumnSortingIndicator(NSImage image, String columnIdentifier) {
1130 browserListView.setIndicatorImage_inTableColumn(image,
1131 browserListView.tableColumnWithIdentifier(columnIdentifier));
1134 public String tableView_typeSelectStringForTableColumn_row(final NSTableView view,
1135 final NSTableColumn tableColumn,
1136 final NSInteger row) {
1137 if(tableColumn.identifier().equals(Column.filename.name())) {
1138 return browserListModel.tableView_objectValueForTableColumn_row(view, tableColumn, row).toString();
1140 return null;
1143 @Override
1144 protected Path pathAtRow(int row) {
1145 final AttributedList<Path> children = browserListModel.get(workdir());
1146 if(row < children.size()) {
1147 return children.get(row);
1149 log.warn(String.format("No item at row %d", row));
1150 return null;
1154 private abstract class AbstractBrowserTableDelegate extends AbstractPathTableDelegate {
1156 protected AbstractBrowserTableDelegate(final NSTableColumn selectedColumn) {
1157 super(selectedColumn);
1160 @Override
1161 public boolean isColumnRowEditable(NSTableColumn column, int row) {
1162 if(preferences.getBoolean("browser.editable")) {
1163 return column.identifier().equals(Column.filename.name());
1165 return false;
1168 @Override
1169 public void tableRowDoubleClicked(final ID sender) {
1170 BrowserController.this.insideButtonClicked(sender);
1173 public void spaceKeyPressed(final ID sender) {
1174 quicklookButtonClicked(sender);
1177 @Override
1178 public void deleteKeyPressed(final ID sender) {
1179 BrowserController.this.deleteFileButtonClicked(sender);
1182 @Override
1183 public void tableColumnClicked(final NSTableView view, final NSTableColumn tableColumn) {
1184 if(this.selectedColumnIdentifier().equals(tableColumn.identifier())) {
1185 this.setSortedAscending(!this.isSortedAscending());
1187 else {
1188 // Remove sorting indicator on previously selected column
1189 this.setBrowserColumnSortingIndicator(null, this.selectedColumnIdentifier());
1190 // Set the newly selected column
1191 this.setSelectedColumn(tableColumn);
1192 // Update the default value
1193 preferences.setProperty("browser.sort.column", this.selectedColumnIdentifier());
1195 this.setBrowserColumnSortingIndicator(
1196 this.isSortedAscending() ?
1197 IconCacheFactory.<NSImage>get().iconNamed("NSAscendingSortIndicator") :
1198 IconCacheFactory.<NSImage>get().iconNamed("NSDescendingSortIndicator"),
1199 tableColumn.identifier()
1201 reload();
1204 @Override
1205 public void columnDidResize(final String columnIdentifier, final float width) {
1206 preferences.setProperty(String.format("browser.column.%s.width", columnIdentifier), width);
1209 @Override
1210 public void selectionDidChange(NSNotification notification) {
1211 final List<Path> selected = getSelectedPaths();
1212 if(quicklook.isOpen()) {
1213 updateQuickLookSelection(selected);
1215 if(preferences.getBoolean("browser.info.inspector")) {
1216 InfoController c = InfoControllerFactory.get(BrowserController.this);
1217 if(null != c) {
1218 // Currently open info panel
1219 c.setFiles(selected);
1224 protected abstract Path pathAtRow(int row);
1226 protected abstract void setBrowserColumnSortingIndicator(NSImage image, String columnIdentifier);
1228 private static final double kSwipeGestureLeft = 1.000000;
1229 private static final double kSwipeGestureRight = -1.000000;
1230 private static final double kSwipeGestureUp = 1.000000;
1231 private static final double kSwipeGestureDown = -1.000000;
1234 * Available in Mac OS X v10.6 and later.
1236 * @param event Swipe event
1238 @Action
1239 public void swipeWithEvent(NSEvent event) {
1240 if(event.deltaX().doubleValue() == kSwipeGestureLeft) {
1241 BrowserController.this.backButtonClicked(event.id());
1243 else if(event.deltaX().doubleValue() == kSwipeGestureRight) {
1244 BrowserController.this.forwardButtonClicked(event.id());
1246 else if(event.deltaY().doubleValue() == kSwipeGestureUp) {
1247 NSInteger row = getSelectedBrowserView().selectedRow();
1248 NSInteger next;
1249 if(-1 == row.intValue()) {
1250 // No current selection
1251 next = new NSInteger(0);
1253 else {
1254 next = new NSInteger(row.longValue() - 1);
1256 BrowserController.this.getSelectedBrowserView().selectRowIndexes(
1257 NSIndexSet.indexSetWithIndex(next), false);
1259 else if(event.deltaY().doubleValue() == kSwipeGestureDown) {
1260 NSInteger row = getSelectedBrowserView().selectedRow();
1261 NSInteger next;
1262 if(-1 == row.intValue()) {
1263 // No current selection
1264 next = new NSInteger(0);
1266 else {
1267 next = new NSInteger(row.longValue() + 1);
1269 BrowserController.this.getSelectedBrowserView().selectRowIndexes(
1270 NSIndexSet.indexSetWithIndex(next), false);
1276 * QuickLook support for 10.6+
1278 * @param panel The Preview Panel looking for a controller.
1279 * @return
1280 * @ Sent to each object in the responder chain to find a controller.
1282 @Override
1283 public boolean acceptsPreviewPanelControl(QLPreviewPanel panel) {
1284 return true;
1288 * QuickLook support for 10.6+
1289 * The receiver should setup the preview panel (data source, delegate, binding, etc.) here.
1291 * @param panel The Preview Panel the receiver will control.
1292 * @ Sent to the object taking control of the Preview Panel.
1294 @Override
1295 public void beginPreviewPanelControl(QLPreviewPanel panel) {
1296 quicklook.willBeginQuickLook();
1300 * QuickLook support for 10.6+
1301 * The receiver should unsetup the preview panel (data source, delegate, binding, etc.) here.
1303 * @param panel The Preview Panel that the receiver will stop controlling.
1304 * @ Sent to the object in control of the Preview Panel just before stopping its control.
1306 @Override
1307 public void endPreviewPanelControl(QLPreviewPanel panel) {
1308 quicklook.didEndQuickLook();
1311 public void setBrowserOutlineView(NSOutlineView view) {
1312 browserOutlineView = view;
1313 // receive drag events from types
1314 browserOutlineView.registerForDraggedTypes(NSArray.arrayWithObjects(
1315 NSPasteboard.URLPboardType,
1316 // Accept files dragged from the Finder for uploading
1317 NSPasteboard.FilenamesPboardType,
1318 // Accept file promises made myself
1319 NSPasteboard.FilesPromisePboardType
1321 // setting appearance attributes()
1322 this._updateBrowserAttributes(browserOutlineView);
1323 // selection properties
1324 browserOutlineView.setAllowsMultipleSelection(true);
1325 browserOutlineView.setAllowsEmptySelection(true);
1326 browserOutlineView.setAllowsColumnResizing(true);
1327 browserOutlineView.setAllowsColumnSelection(false);
1328 browserOutlineView.setAllowsColumnReordering(true);
1330 browserOutlineView.setRowHeight(new CGFloat(layoutManager.defaultLineHeightForFont(
1331 NSFont.systemFontOfSize(preferences.getFloat("browser.font.size"))).intValue() + 2));
1334 NSTableColumn c = browserOutlineColumnsFactory.create(Column.filename.name());
1335 c.headerCell().setStringValue(LocaleFactory.localizedString("Filename"));
1336 c.setMinWidth(new CGFloat(100));
1337 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1338 Column.filename.name())));
1339 c.setMaxWidth(new CGFloat(1000));
1340 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1341 c.setDataCell(outlineCellPrototype);
1342 browserOutlineView.addTableColumn(c);
1343 browserOutlineView.setOutlineTableColumn(c);
1345 browserOutlineView.setDataSource((browserOutlineModel = new BrowserOutlineViewModel(this, cache)).id());
1346 browserOutlineView.setDelegate((browserOutlineViewDelegate = new AbstractBrowserOutlineViewDelegate(
1347 browserOutlineView.tableColumnWithIdentifier(Column.filename.name())
1349 @Override
1350 public void enterKeyPressed(final ID sender) {
1351 if(preferences.getBoolean("browser.enterkey.rename")) {
1352 if(browserOutlineView.numberOfSelectedRows().intValue() == 1) {
1353 renameFileButtonClicked(sender);
1356 else {
1357 this.tableRowDoubleClicked(sender);
1362 * @see NSOutlineView.Delegate
1364 @Override
1365 public void outlineView_willDisplayCell_forTableColumn_item(NSOutlineView view, NSTextFieldCell cell,
1366 NSTableColumn tableColumn, NSObject item) {
1367 if(null == item) {
1368 return;
1370 final Path path = cache.lookup(new NSObjectPathReference(item));
1371 if(null == path) {
1372 return;
1374 if(tableColumn.identifier().equals(Column.filename.name())) {
1375 cell.setEditable(session.getFeature(Move.class).isSupported(path));
1376 (Rococoa.cast(cell, OutlineCell.class)).setIcon(browserOutlineModel.iconForPath(path));
1378 if(!BrowserController.this.isConnected() || !HIDDEN_FILTER.accept(path)) {
1379 cell.setTextColor(NSColor.disabledControlTextColor());
1381 else {
1382 cell.setTextColor(NSColor.controlTextColor());
1387 * @see NSOutlineView.Delegate
1389 @Override
1390 public boolean outlineView_shouldExpandItem(final NSOutlineView view, final NSObject item) {
1391 NSEvent event = NSApplication.sharedApplication().currentEvent();
1392 if(event != null) {
1393 if(NSEvent.NSLeftMouseDragged == event.type()) {
1394 if(!preferences.getBoolean("browser.view.autoexpand")) {
1395 if(log.isDebugEnabled()) {
1396 log.debug("Returning false to #outlineViewShouldExpandItem while dragging because browser.view.autoexpand == false");
1398 // See tickets #98 and #633
1399 return false;
1401 final NSInteger draggingColumn = view.columnAtPoint(view.convertPoint_fromView(event.locationInWindow(), null));
1402 if(draggingColumn.intValue() != 0) {
1403 if(log.isDebugEnabled()) {
1404 log.debug("Returning false to #outlineViewShouldExpandItem for column:" + draggingColumn);
1406 // See ticket #60
1407 return false;
1411 return true;
1414 @Override
1415 public boolean outlineView_isGroupItem(final NSOutlineView view, final NSObject item) {
1416 return false;
1419 @Override
1420 public void outlineViewItemWillExpand(final NSNotification notification) {
1421 final NSObject object = Rococoa.cast(notification.userInfo(), NSDictionary.class).objectForKey("NSObject");
1422 final NSObjectPathReference reference = new NSObjectPathReference(object);
1423 final Path directory = cache.lookup(reference);
1424 if(null == directory) {
1425 return;
1427 reload(workdir, Collections.singleton(directory), getSelectedPaths(), false);
1431 * @see NSOutlineView.Delegate
1433 @Override
1434 public void outlineViewItemDidExpand(final NSNotification notification) {
1438 @Override
1439 public void outlineViewItemWillCollapse(final NSNotification notification) {
1444 * @see NSOutlineView.Delegate
1446 @Override
1447 public void outlineViewItemDidCollapse(final NSNotification notification) {
1448 setStatus();
1451 @Override
1452 protected boolean isTypeSelectSupported() {
1453 return true;
1456 }).id());
1459 public void setBrowserListView(NSTableView view) {
1460 browserListView = view;
1461 // receive drag events from types
1462 browserListView.registerForDraggedTypes(NSArray.arrayWithObjects(
1463 NSPasteboard.URLPboardType,
1464 // Accept files dragged from the Finder for uploading
1465 NSPasteboard.FilenamesPboardType,
1466 // Accept file promises made myself
1467 NSPasteboard.FilesPromisePboardType
1469 // setting appearance attributes()
1470 this._updateBrowserAttributes(browserListView);
1471 // selection properties
1472 browserListView.setAllowsMultipleSelection(true);
1473 browserListView.setAllowsEmptySelection(true);
1474 browserListView.setAllowsColumnResizing(true);
1475 browserListView.setAllowsColumnSelection(false);
1476 browserListView.setAllowsColumnReordering(true);
1478 browserListView.setRowHeight(new CGFloat(layoutManager.defaultLineHeightForFont(
1479 NSFont.systemFontOfSize(preferences.getFloat("browser.font.size"))).intValue() + 2));
1482 NSTableColumn c = browserListColumnsFactory.create(Column.icon.name());
1483 c.headerCell().setStringValue(StringUtils.EMPTY);
1484 c.setMinWidth((20));
1485 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1486 Column.icon.name())));
1487 c.setMaxWidth((20));
1488 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask);
1489 c.setDataCell(imageCellPrototype);
1490 c.dataCell().setAlignment(NSText.NSCenterTextAlignment);
1491 browserListView.addTableColumn(c);
1494 NSTableColumn c = browserListColumnsFactory.create(Column.filename.name());
1495 c.headerCell().setStringValue(LocaleFactory.localizedString("Filename"));
1496 c.setMinWidth((100));
1497 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1498 Column.filename.name())));
1499 c.setMaxWidth((1000));
1500 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1501 c.setDataCell(filenameCellPrototype);
1502 this.browserListView.addTableColumn(c);
1505 browserListView.setDataSource((browserListModel = new BrowserListViewModel(this, cache)).id());
1506 browserListView.setDelegate((browserListViewDelegate = new AbstractBrowserListViewDelegate<Path>(
1507 browserListView.tableColumnWithIdentifier(Column.filename.name())
1509 @Override
1510 public void enterKeyPressed(final ID sender) {
1511 if(preferences.getBoolean("browser.enterkey.rename")) {
1512 if(browserListView.numberOfSelectedRows().intValue() == 1) {
1513 renameFileButtonClicked(sender);
1516 else {
1517 this.tableRowDoubleClicked(sender);
1521 @Override
1522 public void tableView_willDisplayCell_forTableColumn_row(NSTableView view, NSTextFieldCell cell, NSTableColumn tableColumn, NSInteger row) {
1523 final String identifier = tableColumn.identifier();
1524 final Path path = browserListModel.get(BrowserController.this.workdir()).get(row.intValue());
1525 if(identifier.equals(Column.filename.name())) {
1526 cell.setEditable(session.getFeature(Move.class).isSupported(path));
1528 if(cell.isKindOfClass(Foundation.getClass(NSTextFieldCell.class.getSimpleName()))) {
1529 if(!BrowserController.this.isConnected() || !HIDDEN_FILTER.accept(path)) {
1530 cell.setTextColor(NSColor.disabledControlTextColor());
1532 else {
1533 cell.setTextColor(NSColor.controlTextColor());
1538 @Override
1539 protected boolean isTypeSelectSupported() {
1540 return true;
1542 }).id());
1545 protected void _updateBrowserAttributes(NSTableView tableView) {
1546 tableView.setUsesAlternatingRowBackgroundColors(preferences.getBoolean("browser.alternatingRows"));
1547 if(preferences.getBoolean("browser.horizontalLines") && preferences.getBoolean("browser.verticalLines")) {
1548 tableView.setGridStyleMask(new NSUInteger(NSTableView.NSTableViewSolidHorizontalGridLineMask.intValue() | NSTableView.NSTableViewSolidVerticalGridLineMask.intValue()));
1550 else if(preferences.getBoolean("browser.verticalLines")) {
1551 tableView.setGridStyleMask(NSTableView.NSTableViewSolidVerticalGridLineMask);
1553 else if(preferences.getBoolean("browser.horizontalLines")) {
1554 tableView.setGridStyleMask(NSTableView.NSTableViewSolidHorizontalGridLineMask);
1556 else {
1557 tableView.setGridStyleMask(NSTableView.NSTableViewGridNone);
1561 protected void _updateBookmarkCell() {
1562 final int size = preferences.getInteger("bookmark.icon.size");
1563 final double width = size * 1.5;
1564 final NSTableColumn c = bookmarkTable.tableColumnWithIdentifier(BookmarkTableDataSource.Column.icon.name());
1565 c.setMinWidth(width);
1566 c.setMaxWidth(width);
1567 c.setWidth(width);
1568 // Notify the table about the changed row height.
1569 bookmarkTable.noteHeightOfRowsWithIndexesChanged(
1570 NSIndexSet.indexSetWithIndexesInRange(NSRange.NSMakeRange(new NSUInteger(0), new NSUInteger(bookmarkTable.numberOfRows()))));
1573 private void _updateBrowserColumns(final NSTableView table, final AbstractBrowserTableDelegate delegate) {
1574 table.removeTableColumn(table.tableColumnWithIdentifier(Column.size.name()));
1575 if(preferences.getBoolean(String.format("browser.column.%s", Column.size.name()))) {
1576 NSTableColumn c = browserListColumnsFactory.create(Column.size.name());
1577 c.headerCell().setStringValue(LocaleFactory.localizedString("Size"));
1578 c.setMinWidth(50f);
1579 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1580 Column.size.name())));
1581 c.setMaxWidth(150f);
1582 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1583 c.setDataCell(textCellPrototype);
1584 table.addTableColumn(c);
1586 table.removeTableColumn(table.tableColumnWithIdentifier(Column.modified.name()));
1587 if(preferences.getBoolean(String.format("browser.column.%s", Column.modified.name()))) {
1588 NSTableColumn c = browserListColumnsFactory.create(Column.modified.name());
1589 c.headerCell().setStringValue(LocaleFactory.localizedString("Modified"));
1590 c.setMinWidth(100f);
1591 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1592 Column.modified.name())));
1593 c.setMaxWidth(500);
1594 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1595 c.setDataCell(textCellPrototype);
1596 table.addTableColumn(c);
1598 table.removeTableColumn(table.tableColumnWithIdentifier(Column.owner.name()));
1599 if(preferences.getBoolean(String.format("browser.column.%s", Column.owner.name()))) {
1600 NSTableColumn c = browserListColumnsFactory.create(Column.owner.name());
1601 c.headerCell().setStringValue(LocaleFactory.localizedString("Owner"));
1602 c.setMinWidth(50);
1603 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1604 Column.owner.name())));
1605 c.setMaxWidth(500);
1606 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1607 c.setDataCell(textCellPrototype);
1608 table.addTableColumn(c);
1610 table.removeTableColumn(table.tableColumnWithIdentifier(Column.group.name()));
1611 if(preferences.getBoolean(String.format("browser.column.%s", Column.group.name()))) {
1612 NSTableColumn c = browserListColumnsFactory.create(Column.group.name());
1613 c.headerCell().setStringValue(LocaleFactory.localizedString("Group"));
1614 c.setMinWidth(50);
1615 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1616 Column.group.name())));
1617 c.setMaxWidth(500);
1618 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1619 c.setDataCell(textCellPrototype);
1620 table.addTableColumn(c);
1622 table.removeTableColumn(table.tableColumnWithIdentifier(Column.permission.name()));
1623 if(preferences.getBoolean(String.format("browser.column.%s", Column.permission.name()))) {
1624 NSTableColumn c = browserListColumnsFactory.create(Column.permission.name());
1625 c.headerCell().setStringValue(LocaleFactory.localizedString("Permissions"));
1626 c.setMinWidth(100);
1627 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1628 Column.permission.name())));
1629 c.setMaxWidth(800);
1630 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1631 c.setDataCell(textCellPrototype);
1632 table.addTableColumn(c);
1634 table.removeTableColumn(table.tableColumnWithIdentifier(Column.kind.name()));
1635 if(preferences.getBoolean(String.format("browser.column.%s", Column.kind.name()))) {
1636 NSTableColumn c = browserListColumnsFactory.create(Column.kind.name());
1637 c.headerCell().setStringValue(LocaleFactory.localizedString("Kind"));
1638 c.setMinWidth(50);
1639 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1640 Column.kind.name())));
1641 c.setMaxWidth(500);
1642 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1643 c.setDataCell(textCellPrototype);
1644 table.addTableColumn(c);
1646 table.removeTableColumn(table.tableColumnWithIdentifier(Column.extension.name()));
1647 if(preferences.getBoolean(String.format("browser.column.%s", Column.extension.name()))) {
1648 NSTableColumn c = browserListColumnsFactory.create(Column.extension.name());
1649 c.headerCell().setStringValue(LocaleFactory.localizedString("Extension"));
1650 c.setMinWidth(50);
1651 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1652 Column.extension.name())));
1653 c.setMaxWidth(500);
1654 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1655 c.setDataCell(textCellPrototype);
1656 table.addTableColumn(c);
1658 table.removeTableColumn(table.tableColumnWithIdentifier(Column.region.name()));
1659 if(preferences.getBoolean(String.format("browser.column.%s", Column.region.name()))) {
1660 NSTableColumn c = browserListColumnsFactory.create(Column.region.name());
1661 c.headerCell().setStringValue(LocaleFactory.localizedString("Region"));
1662 c.setMinWidth(50);
1663 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1664 Column.region.name())));
1665 c.setMaxWidth(500);
1666 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1667 c.setDataCell(textCellPrototype);
1668 table.addTableColumn(c);
1670 table.removeTableColumn(table.tableColumnWithIdentifier(Column.version.name()));
1671 if(preferences.getBoolean(String.format("browser.column.%s", Column.version.name()))) {
1672 NSTableColumn c = browserListColumnsFactory.create(Column.version.name());
1673 c.headerCell().setStringValue(LocaleFactory.localizedString("Version"));
1674 c.setMinWidth(50);
1675 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1676 Column.version.name())));
1677 c.setMaxWidth(500);
1678 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1679 c.setDataCell(textCellPrototype);
1680 table.addTableColumn(c);
1682 NSTableColumn selected = table.tableColumnWithIdentifier(preferences.getProperty("browser.sort.column"));
1683 if(null == selected) {
1684 selected = table.tableColumnWithIdentifier(Column.filename.name());
1686 delegate.setSelectedColumn(selected);
1687 table.setIndicatorImage_inTableColumn(this.getSelectedBrowserDelegate().isSortedAscending() ?
1688 IconCacheFactory.<NSImage>get().iconNamed("NSAscendingSortIndicator") :
1689 IconCacheFactory.<NSImage>get().iconNamed("NSDescendingSortIndicator"),
1690 selected
1692 table.sizeToFit();
1693 table.setAutosaveName("browser.autosave");
1694 table.setAutosaveTableColumns(true);
1695 this.reload();
1698 @Delegate
1699 private BookmarkTableDataSource bookmarkModel;
1701 @Outlet
1702 private NSTableView bookmarkTable;
1704 @Delegate
1705 private AbstractTableDelegate<Host> bookmarkTableDelegate;
1707 public void setBookmarkTable(NSTableView view) {
1708 bookmarkTable = view;
1709 bookmarkTable.setSelectionHighlightStyle(NSTableView.NSTableViewSelectionHighlightStyleSourceList);
1710 bookmarkTable.setDataSource((this.bookmarkModel = new BookmarkTableDataSource(this)).id());
1712 NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkTableDataSource.Column.icon.name());
1713 c.headerCell().setStringValue(StringUtils.EMPTY);
1714 c.setResizingMask(NSTableColumn.NSTableColumnNoResizing);
1715 c.setDataCell(imageCellPrototype);
1716 bookmarkTable.addTableColumn(c);
1719 NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkTableDataSource.Column.bookmark.name());
1720 c.headerCell().setStringValue(LocaleFactory.localizedString("Bookmarks"));
1721 c.setMinWidth(150);
1722 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask);
1723 c.setDataCell(BookmarkCell.bookmarkCell());
1724 bookmarkTable.addTableColumn(c);
1727 NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkTableDataSource.Column.status.name());
1728 c.headerCell().setStringValue(StringUtils.EMPTY);
1729 c.setMinWidth(40);
1730 c.setWidth(40);
1731 c.setMaxWidth(40);
1732 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask);
1733 c.setDataCell(imageCellPrototype);
1734 c.dataCell().setAlignment(NSText.NSCenterTextAlignment);
1735 bookmarkTable.addTableColumn(c);
1737 bookmarkTable.setDelegate((bookmarkTableDelegate = new AbstractTableDelegate<Host>(
1738 bookmarkTable.tableColumnWithIdentifier(BookmarkTableDataSource.Column.bookmark.name())
1740 @Override
1741 public String tooltip(Host bookmark) {
1742 return new HostUrlProvider().get(bookmark);
1745 @Override
1746 public void tableRowDoubleClicked(final ID sender) {
1747 BrowserController.this.connectBookmarkButtonClicked(sender);
1750 @Override
1751 public void enterKeyPressed(final ID sender) {
1752 this.tableRowDoubleClicked(sender);
1755 @Override
1756 public void deleteKeyPressed(final ID sender) {
1757 if(bookmarkModel.getSource().allowsDelete()) {
1758 BrowserController.this.deleteBookmarkButtonClicked(sender);
1762 @Override
1763 public void tableColumnClicked(NSTableView view, NSTableColumn tableColumn) {
1767 @Override
1768 public void selectionDidChange(NSNotification notification) {
1769 addBookmarkButton.setEnabled(bookmarkModel.getSource().allowsAdd());
1770 final int selected = bookmarkTable.numberOfSelectedRows().intValue();
1771 editBookmarkButton.setEnabled(bookmarkModel.getSource().allowsEdit() && selected == 1);
1772 deleteBookmarkButton.setEnabled(bookmarkModel.getSource().allowsDelete() && selected > 0);
1775 public CGFloat tableView_heightOfRow(NSTableView view, NSInteger row) {
1776 final int size = preferences.getInteger("bookmark.icon.size");
1777 if(BookmarkCell.SMALL_BOOKMARK_SIZE == size) {
1778 return new CGFloat(18);
1780 if(BookmarkCell.MEDIUM_BOOKMARK_SIZE == size) {
1781 return new CGFloat(45);
1783 return new CGFloat(70);
1786 @Override
1787 public boolean isTypeSelectSupported() {
1788 return true;
1791 public String tableView_typeSelectStringForTableColumn_row(NSTableView view,
1792 NSTableColumn tableColumn,
1793 NSInteger row) {
1794 return BookmarkNameProvider.toString(bookmarkModel.getSource().get(row.intValue()));
1797 public boolean tableView_isGroupRow(NSTableView view, NSInteger row) {
1798 return false;
1801 private static final double kSwipeGestureLeft = 1.000000;
1802 private static final double kSwipeGestureRight = -1.000000;
1803 private static final double kSwipeGestureUp = 1.000000;
1804 private static final double kSwipeGestureDown = -1.000000;
1807 * Available in Mac OS X v10.6 and later.
1809 * @param event Swipe event
1811 @Action
1812 public void swipeWithEvent(NSEvent event) {
1813 if(event.deltaY().doubleValue() == kSwipeGestureUp) {
1814 NSInteger row = bookmarkTable.selectedRow();
1815 NSInteger next;
1816 if(-1 == row.intValue()) {
1817 // No current selection
1818 next = new NSInteger(0);
1820 else {
1821 next = new NSInteger(row.longValue() - 1);
1823 bookmarkTable.selectRowIndexes(
1824 NSIndexSet.indexSetWithIndex(next), false);
1826 else if(event.deltaY().doubleValue() == kSwipeGestureDown) {
1827 NSInteger row = bookmarkTable.selectedRow();
1828 NSInteger next;
1829 if(-1 == row.intValue()) {
1830 // No current selection
1831 next = new NSInteger(0);
1833 else {
1834 next = new NSInteger(row.longValue() + 1);
1836 bookmarkTable.selectRowIndexes(
1837 NSIndexSet.indexSetWithIndex(next), false);
1840 }).id());
1841 // receive drag events from types
1842 bookmarkTable.registerForDraggedTypes(NSArray.arrayWithObjects(
1843 NSPasteboard.URLPboardType,
1844 NSPasteboard.StringPboardType,
1845 // Accept bookmark files dragged from the Finder
1846 NSPasteboard.FilenamesPboardType,
1847 // Accept file promises made myself
1848 NSPasteboard.FilesPromisePboardType
1850 this._updateBookmarkCell();
1852 final int size = preferences.getInteger("bookmark.icon.size");
1853 if(BookmarkCell.SMALL_BOOKMARK_SIZE == size) {
1854 bookmarkTable.setRowHeight(new CGFloat(18));
1856 else if(BookmarkCell.MEDIUM_BOOKMARK_SIZE == size) {
1857 bookmarkTable.setRowHeight(new CGFloat(45));
1859 else {
1860 bookmarkTable.setRowHeight(new CGFloat(70));
1863 // setting appearance attributes()
1864 bookmarkTable.setUsesAlternatingRowBackgroundColors(preferences.getBoolean("browser.alternatingRows"));
1865 bookmarkTable.setGridStyleMask(NSTableView.NSTableViewGridNone);
1867 // selection properties
1868 bookmarkTable.setAllowsMultipleSelection(true);
1869 bookmarkTable.setAllowsEmptySelection(true);
1870 bookmarkTable.setAllowsColumnResizing(false);
1871 bookmarkTable.setAllowsColumnSelection(false);
1872 bookmarkTable.setAllowsColumnReordering(false);
1873 bookmarkTable.sizeToFit();
1876 public NSTableView getBookmarkTable() {
1877 return bookmarkTable;
1880 public BookmarkTableDataSource getBookmarkModel() {
1881 return bookmarkModel;
1884 @Outlet
1885 private NSPopUpButton actionPopupButton;
1887 public void setActionPopupButton(NSPopUpButton actionPopupButton) {
1888 this.actionPopupButton = actionPopupButton;
1889 this.actionPopupButton.setPullsDown(true);
1890 this.actionPopupButton.setAutoenablesItems(true);
1893 public NSPopUpButton getActionPopupButton() {
1894 return actionPopupButton;
1897 @Outlet
1898 private NSComboBox quickConnectPopup;
1900 private ProxyController quickConnectPopupModel = new QuickConnectModel();
1902 public void setQuickConnectPopup(NSComboBox quickConnectPopup) {
1903 this.quickConnectPopup = quickConnectPopup;
1904 this.quickConnectPopup.setTarget(this.id());
1905 this.quickConnectPopup.setCompletes(true);
1906 this.quickConnectPopup.setAction(Foundation.selector("quickConnectSelectionChanged:"));
1907 // Make sure action is not sent twice.
1908 this.quickConnectPopup.cell().setSendsActionOnEndEditing(false);
1909 this.quickConnectPopup.setUsesDataSource(true);
1910 this.quickConnectPopup.setDataSource(quickConnectPopupModel.id());
1911 notificationCenter.addObserver(this.id(),
1912 Foundation.selector("quickConnectWillPopUp:"),
1913 NSComboBox.ComboBoxWillPopUpNotification,
1914 this.quickConnectPopup);
1915 this.quickConnectWillPopUp(null);
1918 public NSComboBox getQuickConnectPopup() {
1919 return quickConnectPopup;
1922 private class QuickConnectModel extends ProxyController implements NSComboBox.DataSource {
1923 @Override
1924 public NSInteger numberOfItemsInComboBox(final NSComboBox combo) {
1925 return new NSInteger(bookmarks.size());
1928 @Override
1929 public NSObject comboBox_objectValueForItemAtIndex(final NSComboBox sender, final NSInteger row) {
1930 return NSString.stringWithString(
1931 BookmarkNameProvider.toString(bookmarks.get(row.intValue()))
1936 public void quickConnectWillPopUp(NSNotification notification) {
1937 int size = bookmarks.size();
1938 quickConnectPopup.setNumberOfVisibleItems(size > 10 ? new NSInteger(10) : new NSInteger(size));
1941 @Action
1942 public void quickConnectSelectionChanged(final ID sender) {
1943 String input = quickConnectPopup.stringValue();
1944 if(StringUtils.isBlank(input)) {
1945 return;
1947 input = input.trim();
1948 // First look for equivalent bookmarks
1949 for(Host h : bookmarks) {
1950 if(BookmarkNameProvider.toString(h).equals(input)) {
1951 this.mount(h);
1952 return;
1955 // Try to parse the input as a URL and extract protocol, hostname, username and password if any.
1956 this.mount(HostParser.parse(input));
1959 @Outlet
1960 private NSTextField searchField;
1962 public void setSearchField(NSTextField searchField) {
1963 this.searchField = searchField;
1964 notificationCenter.addObserver(this.id(),
1965 Foundation.selector("searchFieldTextDidChange:"),
1966 NSControl.NSControlTextDidChangeNotification,
1967 this.searchField);
1970 @Action
1971 public void searchButtonClicked(final ID sender) {
1972 this.window().makeFirstResponder(searchField);
1975 public void searchFieldTextDidChange(NSNotification notification) {
1976 if(this.getSelectedTabView() == TAB_BOOKMARKS) {
1977 this.setBookmarkFilter(searchField.stringValue());
1979 else { // TAB_LIST_VIEW || TAB_OUTLINE_VIEW
1980 this.setPathFilter(searchField.stringValue());
1981 this.reload();
1985 private void setBookmarkFilter(final String searchString) {
1986 if(StringUtils.isBlank(searchString)) {
1987 searchField.setStringValue(StringUtils.EMPTY);
1988 bookmarkModel.setFilter(null);
1990 else {
1991 bookmarkModel.setFilter(new HostFilter() {
1992 @Override
1993 public boolean accept(Host host) {
1994 return StringUtils.lowerCase(BookmarkNameProvider.toString(host)).contains(searchString.toLowerCase(Locale.ROOT))
1995 || ((null != host.getComment()) && StringUtils.lowerCase(host.getComment()).contains(searchString.toLowerCase(Locale.ROOT)))
1996 || ((null != host.getCredentials().getUsername()) && StringUtils.lowerCase(host.getCredentials().getUsername()).contains(searchString.toLowerCase(Locale.ROOT)))
1997 || StringUtils.lowerCase(host.getHostname()).contains(searchString.toLowerCase(Locale.ROOT));
2001 this.reloadBookmarks();
2004 // ----------------------------------------------------------
2005 // Manage Bookmarks
2006 // ----------------------------------------------------------
2008 @Action
2009 public void connectBookmarkButtonClicked(final ID sender) {
2010 if(bookmarkTable.numberOfSelectedRows().intValue() == 1) {
2011 final Host selected = bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue());
2012 this.mount(selected);
2016 @Outlet
2017 private NSButton editBookmarkButton;
2019 public void setEditBookmarkButton(NSButton editBookmarkButton) {
2020 this.editBookmarkButton = editBookmarkButton;
2021 this.editBookmarkButton.setEnabled(false);
2022 this.editBookmarkButton.setTarget(this.id());
2023 this.editBookmarkButton.setAction(Foundation.selector("editBookmarkButtonClicked:"));
2026 @Action
2027 public void editBookmarkButtonClicked(final ID sender) {
2028 final BookmarkController c = BookmarkControllerFactory.create(
2029 bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue())
2031 c.window().makeKeyAndOrderFront(null);
2034 @Action
2035 public void duplicateBookmarkButtonClicked(final ID sender) {
2036 final Host selected = bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue());
2037 this.selectBookmarks();
2038 final Host duplicate = new HostDictionary().deserialize(selected.serialize(SerializerFactory.get()));
2039 // Make sure a new UUID is asssigned for duplicate
2040 duplicate.setUuid(null);
2041 this.addBookmark(duplicate);
2044 @Outlet
2045 private NSButton addBookmarkButton;
2047 public void setAddBookmarkButton(NSButton addBookmarkButton) {
2048 this.addBookmarkButton = addBookmarkButton;
2049 this.addBookmarkButton.setTarget(this.id());
2050 this.addBookmarkButton.setAction(Foundation.selector("addBookmarkButtonClicked:"));
2053 @Action
2054 public void addBookmarkButtonClicked(final ID sender) {
2055 final Host bookmark;
2056 if(this.isMounted()) {
2057 Path selected = this.getSelectedPath();
2058 if(null == selected || !selected.isDirectory()) {
2059 selected = this.workdir();
2061 bookmark = new HostDictionary().deserialize(session.getHost().serialize(SerializerFactory.get()));
2062 // Make sure a new UUID is asssigned for duplicate
2063 bookmark.setUuid(null);
2064 bookmark.setDefaultPath(selected.getAbsolute());
2066 else {
2067 bookmark = new Host(ProtocolFactory.forName(preferences.getProperty("connection.protocol.default")),
2068 preferences.getProperty("connection.hostname.default"),
2069 preferences.getInteger("connection.port.default"));
2071 this.selectBookmarks();
2072 this.addBookmark(bookmark);
2075 public void addBookmark(Host item) {
2076 bookmarkModel.setFilter(null);
2077 bookmarkModel.getSource().add(item);
2078 final int row = bookmarkModel.getSource().lastIndexOf(item);
2079 final NSInteger index = new NSInteger(row);
2080 bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(index), false);
2081 bookmarkTable.scrollRowToVisible(index);
2082 final BookmarkController c = BookmarkControllerFactory.create(item);
2083 c.window().makeKeyAndOrderFront(null);
2086 @Outlet
2087 private NSButton deleteBookmarkButton;
2089 public void setDeleteBookmarkButton(NSButton deleteBookmarkButton) {
2090 this.deleteBookmarkButton = deleteBookmarkButton;
2091 this.deleteBookmarkButton.setEnabled(false);
2092 this.deleteBookmarkButton.setTarget(this.id());
2093 this.deleteBookmarkButton.setAction(Foundation.selector("deleteBookmarkButtonClicked:"));
2096 @Action
2097 public void deleteBookmarkButtonClicked(final ID sender) {
2098 NSIndexSet iterator = bookmarkTable.selectedRowIndexes();
2099 final List<Host> selected = new ArrayList<Host>();
2100 for(NSUInteger index = iterator.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = iterator.indexGreaterThanIndex(index)) {
2101 selected.add(bookmarkModel.getSource().get(index.intValue()));
2103 StringBuilder alertText = new StringBuilder(
2104 LocaleFactory.localizedString("Do you want to delete the selected bookmark?"));
2105 int i = 0;
2106 Iterator<Host> iter = selected.iterator();
2107 while(i < 10 && iter.hasNext()) {
2108 alertText.append("\n").append(Character.toString('\u2022')).append(" ").append(
2109 BookmarkNameProvider.toString(iter.next())
2111 i++;
2113 if(iter.hasNext()) {
2114 alertText.append("\n").append(Character.toString('\u2022')).append(" " + "…");
2116 final NSAlert alert = NSAlert.alert(LocaleFactory.localizedString("Delete Bookmark"),
2117 alertText.toString(),
2118 LocaleFactory.localizedString("Delete"),
2119 LocaleFactory.localizedString("Cancel"),
2120 null);
2121 this.alert(alert, new SheetCallback() {
2122 @Override
2123 public void callback(int returncode) {
2124 if(returncode == DEFAULT_OPTION) {
2125 bookmarkTable.deselectAll(null);
2126 bookmarkModel.getSource().removeAll(selected);
2132 // ----------------------------------------------------------
2133 // Browser navigation
2134 // ----------------------------------------------------------
2136 public Navigation getNavigation() {
2137 return navigation;
2140 private static final int NAVIGATION_LEFT_SEGMENT_BUTTON = 0;
2141 private static final int NAVIGATION_RIGHT_SEGMENT_BUTTON = 1;
2143 private static final int NAVIGATION_UP_SEGMENT_BUTTON = 0;
2145 private NSSegmentedControl navigationButton;
2147 public void setNavigationButton(NSSegmentedControl navigationButton) {
2148 this.navigationButton = navigationButton;
2149 this.navigationButton.setTarget(this.id());
2150 this.navigationButton.setAction(Foundation.selector("navigationButtonClicked:"));
2151 this.navigationButton.setImage_forSegment(IconCacheFactory.<NSImage>get().iconNamed("nav-backward.tiff"),
2152 NAVIGATION_LEFT_SEGMENT_BUTTON);
2153 this.navigationButton.setImage_forSegment(IconCacheFactory.<NSImage>get().iconNamed("nav-forward.tiff"),
2154 NAVIGATION_RIGHT_SEGMENT_BUTTON);
2157 @Action
2158 public void navigationButtonClicked(NSSegmentedControl sender) {
2159 switch(sender.selectedSegment()) {
2160 case NAVIGATION_LEFT_SEGMENT_BUTTON: {
2161 this.backButtonClicked(sender.id());
2162 break;
2164 case NAVIGATION_RIGHT_SEGMENT_BUTTON: {
2165 this.forwardButtonClicked(sender.id());
2166 break;
2171 @Action
2172 public void backButtonClicked(final ID sender) {
2173 final Path selected = navigation.back();
2174 if(selected != null) {
2175 final Path previous = this.workdir();
2176 if(previous.getParent().equals(selected)) {
2177 this.setWorkdir(selected, previous);
2179 else {
2180 this.setWorkdir(selected);
2185 @Action
2186 public void forwardButtonClicked(final ID sender) {
2187 final Path selected = navigation.forward();
2188 if(selected != null) {
2189 this.setWorkdir(selected);
2193 @Outlet
2194 private NSSegmentedControl upButton;
2196 public void setUpButton(NSSegmentedControl upButton) {
2197 this.upButton = upButton;
2198 this.upButton.setTarget(this.id());
2199 this.upButton.setAction(Foundation.selector("upButtonClicked:"));
2200 this.upButton.setImage_forSegment(IconCacheFactory.<NSImage>get().iconNamed("nav-up.tiff"),
2201 NAVIGATION_UP_SEGMENT_BUTTON);
2204 @Action
2205 public void upButtonClicked(final ID sender) {
2206 final Path previous = this.workdir();
2207 this.setWorkdir(previous.getParent(), previous);
2210 private Path workdir;
2212 @Outlet
2213 private NSPopUpButton pathPopupButton;
2215 public void setPathPopup(NSPopUpButton pathPopupButton) {
2216 this.pathPopupButton = pathPopupButton;
2217 this.pathPopupButton.setTarget(this.id());
2218 this.pathPopupButton.setAction(Foundation.selector("pathPopupSelectionChanged:"));
2221 @Action
2222 public void pathPopupSelectionChanged(final NSPopUpButton sender) {
2223 final String selected = sender.selectedItem().representedObject();
2224 if(selected != null) {
2225 final Path workdir = this.workdir();
2226 Path p = workdir;
2227 while(!p.getAbsolute().equals(selected)) {
2228 p = p.getParent();
2230 this.setWorkdir(p);
2231 if(workdir.getParent().equals(p)) {
2232 this.setWorkdir(p, workdir);
2234 else {
2235 this.setWorkdir(p);
2240 @Outlet
2241 private NSPopUpButton encodingPopup;
2243 public void setEncodingPopup(NSPopUpButton encodingPopup) {
2244 this.encodingPopup = encodingPopup;
2245 this.encodingPopup.setTarget(this.id());
2246 this.encodingPopup.setAction(Foundation.selector("encodingButtonClicked:"));
2247 this.encodingPopup.removeAllItems();
2248 this.encodingPopup.addItemsWithTitles(NSArray.arrayWithObjects(new DefaultCharsetProvider().availableCharsets()));
2249 this.encodingPopup.selectItemWithTitle(preferences.getProperty("browser.charset.encoding"));
2252 public NSPopUpButton getEncodingPopup() {
2253 return encodingPopup;
2256 @Action
2257 public void encodingButtonClicked(final NSPopUpButton sender) {
2258 this.encodingChanged(sender.titleOfSelectedItem());
2261 @Action
2262 public void encodingMenuClicked(final NSMenuItem sender) {
2263 this.encodingChanged(sender.title());
2266 public void encodingChanged(final String encoding) {
2267 if(null == encoding) {
2268 return;
2270 this.setEncoding(encoding);
2271 if(this.isMounted()) {
2272 if(session.getEncoding().equals(encoding)) {
2273 return;
2275 session.getHost().setEncoding(encoding);
2276 this.mount(session.getHost());
2281 * @param encoding Character encoding
2283 private void setEncoding(final String encoding) {
2284 this.encodingPopup.selectItemWithTitle(encoding);
2287 // ----------------------------------------------------------
2288 // Drawers
2289 // ----------------------------------------------------------
2291 @Action
2292 public void toggleLogDrawer(final ID sender) {
2293 this.logDrawer.toggle(this.id());
2296 // ----------------------------------------------------------
2297 // Status
2298 // ----------------------------------------------------------
2300 @Outlet
2301 protected NSProgressIndicator statusSpinner;
2303 public void setStatusSpinner(NSProgressIndicator statusSpinner) {
2304 this.statusSpinner = statusSpinner;
2305 this.statusSpinner.setDisplayedWhenStopped(false);
2306 this.statusSpinner.setIndeterminate(true);
2309 @Outlet
2310 protected NSProgressIndicator browserSpinner;
2312 public void setBrowserSpinner(NSProgressIndicator browserSpinner) {
2313 this.browserSpinner = browserSpinner;
2316 public NSProgressIndicator getBrowserSpinner() {
2317 return browserSpinner;
2320 @Outlet
2321 private NSTextField statusLabel;
2323 public void setStatusLabel(NSTextField statusLabel) {
2324 this.statusLabel = statusLabel;
2327 public void setStatus() {
2328 final BackgroundAction current = this.getActions().getCurrent();
2329 this.message(null != current ? current.getActivity() : null);
2332 @Override
2333 public void stop(final BackgroundAction action) {
2334 this.invoke(new DefaultMainAction() {
2335 @Override
2336 public void run() {
2337 statusSpinner.stopAnimation(null);
2340 super.stop(action);
2343 @Override
2344 public void start(final BackgroundAction action) {
2345 this.invoke(new DefaultMainAction() {
2346 @Override
2347 public void run() {
2348 statusSpinner.startAnimation(null);
2351 super.start(action);
2355 * @param label Status message
2357 @Override
2358 public void message(final String label) {
2359 if(StringUtils.isNotBlank(label)) {
2360 // Update the status label at the bottom of the browser window
2361 statusLabel.setAttributedStringValue(NSAttributedString.attributedStringWithAttributes(label,
2362 TRUNCATE_MIDDLE_ATTRIBUTES));
2364 else {
2365 if(getSelectedTabView() == TAB_BOOKMARKS) {
2366 statusLabel.setAttributedStringValue(
2367 NSAttributedString.attributedStringWithAttributes(String.format("%s %s", bookmarkTable.numberOfRows(),
2368 LocaleFactory.localizedString("Bookmarks")),
2369 TRUNCATE_MIDDLE_ATTRIBUTES
2373 else {
2374 // Browser view
2375 if(isConnected()) {
2376 statusLabel.setAttributedStringValue(
2377 NSAttributedString.attributedStringWithAttributes(MessageFormat.format(LocaleFactory.localizedString("{0} Files"),
2378 String.valueOf(getSelectedBrowserView().numberOfRows())),
2379 TRUNCATE_MIDDLE_ATTRIBUTES
2383 else {
2384 statusLabel.setStringValue(StringUtils.EMPTY);
2390 @Override
2391 public void log(final boolean request, final String message) {
2392 transcript.log(request, message);
2395 @Outlet
2396 private NSButton securityLabel;
2398 public void setSecurityLabel(NSButton securityLabel) {
2399 this.securityLabel = securityLabel;
2400 this.securityLabel.setEnabled(false);
2401 this.securityLabel.setTarget(this.id());
2402 this.securityLabel.setAction(Foundation.selector("securityLabelClicked:"));
2405 @Action
2406 public void securityLabelClicked(final ID sender) {
2407 if(session instanceof SSLSession) {
2408 final SSLSession<?> secured = (SSLSession) session;
2409 final List<X509Certificate> certificates = secured.getAcceptedIssuers();
2410 try {
2411 CertificateStoreFactory.get(this).display(certificates);
2413 catch(CertificateException e) {
2414 log.warn(String.format("Failure decoding certificate %s", e.getMessage()));
2419 // ----------------------------------------------------------
2420 // Selector methods for the toolbar items
2421 // ----------------------------------------------------------
2423 public void quicklookButtonClicked(final ID sender) {
2424 if(quicklook.isOpen()) {
2425 quicklook.close();
2427 else {
2428 this.updateQuickLookSelection(this.getSelectedPaths());
2433 * Marks all expanded directories as invalid and tells the
2434 * browser table to reload its data
2436 * @param sender Toolbar button
2438 @Action
2439 public void reloadButtonClicked(final ID sender) {
2440 if(this.isMounted()) {
2441 final Set<Path> folders = new HashSet<Path>();
2442 switch(browserSwitchView.selectedSegment()) {
2443 case SWITCH_OUTLINE_VIEW: {
2444 for(int i = 0; i < browserOutlineView.numberOfRows().intValue(); i++) {
2445 final NSObject item = browserOutlineView.itemAtRow(new NSInteger(i));
2446 if(browserOutlineView.isItemExpanded(item)) {
2447 final Path folder = cache.lookup(new NSObjectPathReference(item));
2448 if(null == folder) {
2449 continue;
2451 folders.add(folder);
2454 break;
2457 folders.add(workdir);
2458 this.reload(workdir, folders, this.getSelectedPaths(), true);
2463 * Open a new browser with the current selected folder as the working directory
2465 * @param sender Toolbar button
2467 @Action
2468 public void newBrowserButtonClicked(final ID sender) {
2469 Path selected = this.getSelectedPath();
2470 if(null == selected || !selected.isDirectory()) {
2471 selected = this.workdir();
2473 BrowserController c = MainController.newDocument(true);
2474 final Host host = new HostDictionary().deserialize(session.getHost().serialize(SerializerFactory.get()));
2475 host.setDefaultPath(selected.getAbsolute());
2476 c.mount(host);
2480 * @param selected File
2481 * @return True if the selected path is editable (not a directory and no known binary file)
2483 protected boolean isEditable(final Path selected) {
2484 if(this.isMounted()) {
2485 if(session.getHost().getCredentials().isAnonymousLogin()) {
2486 return false;
2488 return selected.isFile();
2490 return false;
2493 @Action
2494 public void gotoButtonClicked(final ID sender) {
2495 final SheetController sheet = new GotoController(this, cache);
2496 sheet.beginSheet();
2499 @Action
2500 public void createFileButtonClicked(final ID sender) {
2501 final SheetController sheet = new CreateFileController(this, cache);
2502 sheet.beginSheet();
2505 @Action
2506 public void createSymlinkButtonClicked(final ID sender) {
2507 final SheetController sheet = new CreateSymlinkController(this, cache);
2508 sheet.beginSheet();
2511 @Action
2512 public void duplicateFileButtonClicked(final ID sender) {
2513 final SheetController sheet = new DuplicateFileController(this, cache);
2514 sheet.beginSheet();
2517 @Action
2518 public void createFolderButtonClicked(final ID sender) {
2519 final Location feature = session.getFeature(Location.class);
2520 final SheetController sheet = new FolderController(this, cache,
2521 feature != null ? feature.getLocations() : Collections.<Location.Name>emptySet());
2522 sheet.beginSheet();
2525 @Action
2526 public void renameFileButtonClicked(final ID sender) {
2527 final NSTableView browser = this.getSelectedBrowserView();
2528 browser.editRow(browser.columnWithIdentifier(Column.filename.name()),
2529 browser.selectedRow(), true);
2530 final Path selected = this.getSelectedPath();
2531 if(StringUtils.isNotBlank(selected.getExtension())) {
2532 NSText view = browser.currentEditor();
2533 int index = selected.getName().indexOf(selected.getExtension()) - 1;
2534 if(index > 0) {
2535 view.setSelectedRange(NSRange.NSMakeRange(new NSUInteger(0), new NSUInteger(index)));
2540 @Action
2541 public void sendCustomCommandClicked(final ID sender) {
2542 SheetController sheet = new CommandController(this, session);
2543 sheet.beginSheet();
2546 @Action
2547 public void editMenuClicked(final NSMenuItem sender) {
2548 final EditorFactory factory = EditorFactory.instance();
2549 for(Path selected : this.getSelectedPaths()) {
2550 final Editor editor = factory.create(this, session,
2551 new Application(sender.representedObject()), selected);
2552 this.edit(editor);
2556 @Action
2557 public void editButtonClicked(final ID sender) {
2558 final EditorFactory factory = EditorFactory.instance();
2559 for(Path selected : this.getSelectedPaths()) {
2560 this.edit(selected);
2564 protected void edit(final Path file) {
2565 this.edit(EditorFactory.instance().create(this, session, file));
2568 protected void edit(final Editor editor) {
2569 editors.add(editor);
2570 this.background(new WorkerBackgroundAction<Transfer>(this, session, editor.open(new ApplicationQuitCallback() {
2571 @Override
2572 public void callback() {
2573 editors.remove(editor);
2575 }, new DisabledTransferErrorCallback(), new DefaultEditorListener(this, session, editor))));
2578 @Action
2579 public void openBrowserButtonClicked(final ID sender) {
2580 final DescriptiveUrlBag list;
2581 if(this.getSelectionCount() == 1) {
2582 list = session.getFeature(UrlProvider.class).toUrl(this.getSelectedPath());
2584 else {
2585 list = session.getFeature(UrlProvider.class).toUrl(this.workdir());
2587 if(!list.isEmpty()) {
2588 BrowserLauncherFactory.get().open(list.find(DescriptiveUrl.Type.http).getUrl());
2592 @Action
2593 public void infoButtonClicked(final ID sender) {
2594 if(this.getSelectionCount() > 0) {
2595 InfoController c = InfoControllerFactory.create(this, this.getSelectedPaths());
2596 c.window().makeKeyAndOrderFront(null);
2600 @Action
2601 public void revertFileButtonClicked(final ID sender) {
2602 new RevertController(this).revert(this.getSelectedPaths());
2605 @Action
2606 public void deleteFileButtonClicked(final ID sender) {
2607 new DeleteController(this).delete(this.getSelectedPaths());
2610 private NSOpenPanel downloadToPanel;
2612 @Action
2613 public void downloadToButtonClicked(final ID sender) {
2614 downloadToPanel = NSOpenPanel.openPanel();
2615 downloadToPanel.setCanChooseDirectories(true);
2616 downloadToPanel.setCanCreateDirectories(true);
2617 downloadToPanel.setCanChooseFiles(false);
2618 downloadToPanel.setAllowsMultipleSelection(false);
2619 downloadToPanel.setPrompt(LocaleFactory.localizedString("Choose"));
2620 downloadToPanel.beginSheetForDirectory(new DownloadDirectoryFinder().find(session.getHost()).getAbsolute(),
2621 null, this.window, this.id(),
2622 Foundation.selector("downloadToPanelDidEnd:returnCode:contextInfo:"), null);
2625 public void downloadToPanelDidEnd_returnCode_contextInfo(final NSOpenPanel sheet, final int returncode, final ID contextInfo) {
2626 sheet.orderOut(this.id());
2627 if(returncode == SheetCallback.DEFAULT_OPTION) {
2628 if(sheet.filename() != null) {
2629 final Local target = LocalFactory.get(sheet.filename());
2630 new DownloadDirectoryFinder().save(session.getHost(), target);
2631 final List<TransferItem> downloads = new ArrayList<TransferItem>();
2632 for(Path file : this.getSelectedPaths()) {
2633 downloads.add(new TransferItem(file, LocalFactory.get(target, file.getName())));
2635 this.transfer(new DownloadTransfer(session.getHost(), downloads), Collections.<Path>emptyList());
2638 downloadToPanel = null;
2641 @Outlet
2642 private NSSavePanel downloadAsPanel;
2644 @Action
2645 public void downloadAsButtonClicked(final ID sender) {
2646 downloadAsPanel = NSSavePanel.savePanel();
2647 downloadAsPanel.setMessage(LocaleFactory.localizedString("Download the selected file to…"));
2648 downloadAsPanel.setNameFieldLabel(LocaleFactory.localizedString("Download As:"));
2649 downloadAsPanel.setPrompt(LocaleFactory.localizedString("Download", "Transfer"));
2650 downloadAsPanel.setCanCreateDirectories(true);
2651 downloadAsPanel.beginSheetForDirectory(new DownloadDirectoryFinder().find(session.getHost()).getAbsolute(),
2652 this.getSelectedPath().getName(), this.window, this.id(),
2653 Foundation.selector("downloadAsPanelDidEnd:returnCode:contextInfo:"), null);
2656 public void downloadAsPanelDidEnd_returnCode_contextInfo(final NSSavePanel sheet, final int returncode, final ID contextInfo) {
2657 sheet.orderOut(this.id());
2658 if(returncode == SheetCallback.DEFAULT_OPTION) {
2659 if(sheet.filename() != null) {
2660 final Local target = LocalFactory.get(sheet.filename());
2661 new DownloadDirectoryFinder().save(session.getHost(), target.getParent());
2662 final List<TransferItem> downloads
2663 = Collections.singletonList(new TransferItem(this.getSelectedPath(), target));
2664 this.transfer(new DownloadTransfer(session.getHost(), downloads), Collections.<Path>emptyList());
2669 @Outlet
2670 private NSOpenPanel syncPanel;
2672 @Action
2673 public void syncButtonClicked(final ID sender) {
2674 final Path selection;
2675 if(this.getSelectionCount() == 1 &&
2676 this.getSelectedPath().isDirectory()) {
2677 selection = this.getSelectedPath();
2679 else {
2680 selection = this.workdir();
2682 syncPanel = NSOpenPanel.openPanel();
2683 syncPanel.setCanChooseDirectories(selection.isDirectory());
2684 syncPanel.setTreatsFilePackagesAsDirectories(true);
2685 syncPanel.setCanChooseFiles(selection.isFile());
2686 syncPanel.setCanCreateDirectories(true);
2687 syncPanel.setAllowsMultipleSelection(false);
2688 syncPanel.setMessage(MessageFormat.format(LocaleFactory.localizedString("Synchronize {0} with"),
2689 selection.getName()));
2690 syncPanel.setPrompt(LocaleFactory.localizedString("Choose"));
2691 syncPanel.beginSheetForDirectory(new UploadDirectoryFinder().find(session.getHost()).getAbsolute(),
2692 null, this.window, this.id(),
2693 Foundation.selector("syncPanelDidEnd:returnCode:contextInfo:"), null //context info
2697 public void syncPanelDidEnd_returnCode_contextInfo(final NSOpenPanel sheet, final int returncode, final ID contextInfo) {
2698 sheet.orderOut(this.id());
2699 if(returncode == SheetCallback.DEFAULT_OPTION) {
2700 if(sheet.filename() != null) {
2701 final Local target = LocalFactory.get(sheet.filename());
2702 new UploadDirectoryFinder().save(session.getHost(), target.getParent());
2703 final Path selected;
2704 if(this.getSelectionCount() == 1 && this.getSelectedPath().isDirectory()) {
2705 selected = this.getSelectedPath();
2707 else {
2708 selected = this.workdir();
2710 this.transfer(new SyncTransfer(session.getHost(), new TransferItem(selected, target)));
2715 @Action
2716 public void downloadButtonClicked(final ID sender) {
2717 final List<TransferItem> downloads = new ArrayList<TransferItem>();
2718 final Local folder = new DownloadDirectoryFinder().find(session.getHost());
2719 for(Path file : this.getSelectedPaths()) {
2720 downloads.add(new TransferItem(
2721 file, LocalFactory.get(folder, file.getName())));
2723 this.transfer(new DownloadTransfer(session.getHost(), downloads), Collections.<Path>emptyList());
2726 private NSOpenPanel uploadPanel;
2728 private NSButton uploadPanelHiddenFilesCheckbox;
2730 @Action
2731 public void uploadButtonClicked(final ID sender) {
2732 uploadPanel = NSOpenPanel.openPanel();
2733 uploadPanel.setCanChooseDirectories(true);
2734 uploadPanel.setCanChooseFiles(session.getFeature(Touch.class).isSupported(
2735 new UploadTargetFinder(workdir).find(this.getSelectedPath())
2737 uploadPanel.setCanCreateDirectories(false);
2738 uploadPanel.setTreatsFilePackagesAsDirectories(true);
2739 uploadPanel.setAllowsMultipleSelection(true);
2740 uploadPanel.setPrompt(LocaleFactory.localizedString("Upload", "Transfer"));
2741 if(uploadPanel.respondsToSelector(Foundation.selector("setShowsHiddenFiles:"))) {
2742 uploadPanelHiddenFilesCheckbox = NSButton.buttonWithFrame(new NSRect(0, 0));
2743 uploadPanelHiddenFilesCheckbox.setTitle(LocaleFactory.localizedString("Show Hidden Files"));
2744 uploadPanelHiddenFilesCheckbox.setTarget(this.id());
2745 uploadPanelHiddenFilesCheckbox.setAction(Foundation.selector("uploadPanelSetShowHiddenFiles:"));
2746 uploadPanelHiddenFilesCheckbox.setButtonType(NSButton.NSSwitchButton);
2747 uploadPanelHiddenFilesCheckbox.setState(NSCell.NSOffState);
2748 uploadPanelHiddenFilesCheckbox.sizeToFit();
2749 uploadPanel.setAccessoryView(uploadPanelHiddenFilesCheckbox);
2751 uploadPanel.beginSheetForDirectory(new UploadDirectoryFinder().find(session.getHost()).getAbsolute(),
2752 null, this.window,
2753 this.id(),
2754 Foundation.selector("uploadPanelDidEnd:returnCode:contextInfo:"),
2755 null);
2758 public void uploadPanelSetShowHiddenFiles(ID sender) {
2759 uploadPanel.setShowsHiddenFiles(uploadPanelHiddenFilesCheckbox.state() == NSCell.NSOnState);
2762 public void uploadPanelDidEnd_returnCode_contextInfo(final NSOpenPanel sheet, final int returncode, final ID contextInfo) {
2763 sheet.orderOut(this.id());
2764 if(returncode == SheetCallback.DEFAULT_OPTION) {
2765 final Path destination = new UploadTargetFinder(workdir).find(this.getSelectedPath());
2766 // Selected files on the local filesystem
2767 final NSArray selected = sheet.filenames();
2768 final NSEnumerator iterator = selected.objectEnumerator();
2769 final List<TransferItem> uploads = new ArrayList<TransferItem>();
2770 NSObject next;
2771 while((next = iterator.nextObject()) != null) {
2772 final Local local = LocalFactory.get(next.toString());
2773 new UploadDirectoryFinder().save(session.getHost(), local.getParent());
2774 uploads.add(new TransferItem(
2775 new Path(destination, local.getName(),
2776 local.isDirectory() ? EnumSet.of(Path.Type.directory) : EnumSet.of(Path.Type.file)), local
2779 this.transfer(new UploadTransfer(session.getHost(), uploads));
2781 uploadPanel = null;
2782 uploadPanelHiddenFilesCheckbox = null;
2785 protected void transfer(final Transfer transfer) {
2786 final List<Path> selected = new ArrayList<Path>();
2787 for(TransferItem i : transfer.getRoots()) {
2788 selected.add(i.remote);
2790 this.transfer(transfer, selected);
2794 * Transfers the files either using the queue or using the browser session if #connection.pool.max is 1
2796 * @param transfer Transfer Operation
2798 protected void transfer(final Transfer transfer, final List<Path> selected) {
2799 // Determine from current browser session if new connection should be opened for transfers
2800 this.transfer(transfer, selected, session.getTransferType().equals(Host.TransferType.browser));
2804 * @param transfer Transfer Operation
2805 * @param browser Transfer in browser window
2807 protected void transfer(final Transfer transfer, final List<Path> selected, boolean browser) {
2808 final TransferCallback callback = new TransferCallback() {
2809 @Override
2810 public void complete(final Transfer transfer) {
2811 invoke(new WindowMainAction(BrowserController.this) {
2812 @Override
2813 public void run() {
2814 reload(workdir, selected, selected);
2819 if(browser) {
2820 this.background(new TransferBackgroundAction(this, session, cache, new TransferAdapter() {
2821 @Override
2822 public void progress(final TransferProgress status) {
2823 message(status.getProgress());
2825 }, transfer, new TransferOptions()) {
2826 @Override
2827 public void finish() {
2828 if(transfer.isComplete()) {
2829 callback.complete(transfer);
2831 super.finish();
2835 else {
2836 TransferControllerFactory.get().start(transfer, new TransferOptions(), callback);
2840 @Action
2841 public void insideButtonClicked(final ID sender) {
2842 final Path selected = this.getSelectedPath(); //first row selected
2843 if(null == selected) {
2844 return;
2846 if(selected.isDirectory()) {
2847 this.setWorkdir(selected);
2849 else if(selected.isFile() || this.getSelectionCount() > 1) {
2850 if(preferences.getBoolean("browser.doubleclick.edit")) {
2851 this.editButtonClicked(null);
2853 else {
2854 this.downloadButtonClicked(null);
2859 @Action
2860 public void connectButtonClicked(final ID sender) {
2861 final SheetController controller = ConnectionControllerFactory.create(this);
2862 this.addListener(new WindowListener() {
2863 @Override
2864 public void windowWillClose() {
2865 controller.invalidate();
2868 controller.beginSheet();
2871 @Action
2872 public void disconnectButtonClicked(final ID sender) {
2873 if(this.isActivityRunning()) {
2874 // Remove all pending actions
2875 for(BackgroundAction action : this.getActions().toArray(
2876 new BackgroundAction[this.getActions().size()])) {
2877 action.cancel();
2880 this.disconnect(new Runnable() {
2881 @Override
2882 public void run() {
2883 if(preferences.getBoolean("browser.disconnect.bookmarks.show")) {
2884 selectBookmarks();
2886 else {
2887 selectBrowser(preferences.getInteger("browser.view"));
2893 @Action
2894 public void showHiddenFilesClicked(final NSMenuItem sender) {
2895 if(sender.state() == NSCell.NSOnState) {
2896 this.setShowHiddenFiles(false);
2897 sender.setState(NSCell.NSOffState);
2899 else if(sender.state() == NSCell.NSOffState) {
2900 this.setShowHiddenFiles(true);
2901 sender.setState(NSCell.NSOnState);
2903 if(this.isMounted()) {
2904 this.reload();
2909 * @return This browser's session or null if not mounted
2911 public Session<?> getSession() {
2912 return session;
2915 public Cache<Path> getCache() {
2916 return cache;
2920 * @return true if the remote file system has been mounted
2922 public boolean isMounted() {
2923 return session != null && workdir != null;
2927 * @return true if mounted and the connection to the server is alive
2929 public boolean isConnected() {
2930 if(this.isMounted()) {
2931 return session.isConnected();
2933 return false;
2937 * NSService
2938 * <p>
2939 * Indicates whether the receiver can send and receive the specified pasteboard types.
2940 * <p>
2941 * Either sendType or returnType—but not both—may be empty. If sendType is empty,
2942 * the service doesn’t require input from the application requesting the service.
2943 * If returnType is empty, the service doesn’t return data.
2945 * @param sendType The pasteboard type the application needs to send.
2946 * @param returnType The pasteboard type the application needs to receive.
2947 * @return The object that can send and receive the specified types or nil
2948 * if the receiver knows of no object that can send and receive data of that type.
2950 public ID validRequestorForSendType_returnType(String sendType, String returnType) {
2951 log.debug("validRequestorForSendType_returnType:" + sendType + "," + returnType);
2952 if(StringUtils.isNotEmpty(sendType)) {
2953 // Cannot send any data type
2954 return null;
2956 if(StringUtils.isNotEmpty(returnType)) {
2957 // Can receive filenames
2958 if(NSPasteboard.FilenamesPboardType.equals(sendType)) {
2959 return this.id();
2962 return null;
2966 * NSService
2967 * <p>
2968 * Reads data from the pasteboard and uses it to replace the current selection.
2970 * @param pboard Pasteboard
2971 * @return YES if your implementation was able to read the pasteboard data successfully; otherwise, NO.
2973 public boolean readSelectionFromPasteboard(NSPasteboard pboard) {
2974 return this.upload(pboard);
2978 * NSService
2979 * <p>
2980 * Writes the current selection to the pasteboard.
2982 * @param pboard Pasteboard
2983 * @param types Types in pasteboard
2984 * @return YES if your implementation was able to write one or more types to the pasteboard; otherwise, NO.
2986 public boolean writeSelectionToPasteboard_types(NSPasteboard pboard, NSArray types) {
2987 return false;
2990 @Action
2991 public void copy(final ID sender) {
2992 pasteboard.clear();
2993 pasteboard.setCopy(true);
2994 final List<Path> s = this.getSelectedPaths();
2995 for(Path p : s) {
2996 // Writing data for private use when the item gets dragged to the transfer queue.
2997 pasteboard.add(p);
2999 final NSPasteboard clipboard = NSPasteboard.generalPasteboard();
3000 if(s.size() == 0) {
3001 s.add(this.workdir());
3003 clipboard.declareTypes(NSArray.arrayWithObject(
3004 NSString.stringWithString(NSPasteboard.StringPboardType)), null);
3005 StringBuilder copy = new StringBuilder();
3006 for(Iterator<Path> i = s.iterator(); i.hasNext(); ) {
3007 copy.append(i.next().getAbsolute());
3008 if(i.hasNext()) {
3009 copy.append("\n");
3012 if(!clipboard.setStringForType(copy.toString(), NSPasteboard.StringPboardType)) {
3013 log.error("Error writing to NSPasteboard.StringPboardType.");
3017 @Action
3018 public void cut(final ID sender) {
3019 pasteboard.clear();
3020 pasteboard.setCut(true);
3021 for(Path s : this.getSelectedPaths()) {
3022 // Writing data for private use when the item gets dragged to the transfer queue.
3023 pasteboard.add(s);
3025 final NSPasteboard clipboard = NSPasteboard.generalPasteboard();
3026 clipboard.declareTypes(NSArray.arrayWithObject(NSString.stringWithString(NSPasteboard.StringPboardType)), null);
3027 if(!clipboard.setStringForType(this.getSelectedPath().getAbsolute(), NSPasteboard.StringPboardType)) {
3028 log.error("Error writing to NSPasteboard.StringPboardType.");
3032 @Action
3033 public void paste(final ID sender) {
3034 if(pasteboard.isEmpty()) {
3035 NSPasteboard pboard = NSPasteboard.generalPasteboard();
3036 this.upload(pboard);
3038 else {
3039 final Map<Path, Path> files = new HashMap<Path, Path>();
3040 final Path parent = this.workdir();
3041 for(final Path next : pasteboard) {
3042 Path renamed = new Path(parent, next.getName(), next.getType());
3043 files.put(next, renamed);
3045 pasteboard.clear();
3046 if(pasteboard.isCut()) {
3047 new MoveController(this).rename(files);
3049 if(pasteboard.isCopy()) {
3050 new DuplicateFileController(this, cache).duplicate(files);
3056 * @param pboard Pasteboard with filenames
3057 * @return True if filenames are found in pasteboard and upload has started
3059 private boolean upload(NSPasteboard pboard) {
3060 if(!this.isMounted()) {
3061 return false;
3063 if(pboard.availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) {
3064 NSObject o = pboard.propertyListForType(NSPasteboard.FilenamesPboardType);
3065 if(o != null) {
3066 if(o.isKindOfClass(Rococoa.createClass("NSArray", NSArray._Class.class))) {
3067 final NSArray elements = Rococoa.cast(o, NSArray.class);
3068 final Path workdir = this.workdir();
3069 final List<TransferItem> uploads = new ArrayList<TransferItem>();
3070 for(int i = 0; i < elements.count().intValue(); i++) {
3071 final Local local = LocalFactory.get(elements.objectAtIndex(new NSUInteger(i)).toString());
3072 uploads.add(new TransferItem(new Path(workdir, local.getName(),
3073 local.isDirectory() ? EnumSet.of(Path.Type.directory) : EnumSet.of(Path.Type.file)), local));
3075 this.transfer(new UploadTransfer(session.getHost(), uploads));
3079 return false;
3082 @Action
3083 public void openTerminalButtonClicked(final ID sender) {
3084 Path workdir = null;
3085 if(this.getSelectionCount() == 1) {
3086 Path selected = this.getSelectedPath();
3087 if(selected.isDirectory()) {
3088 workdir = selected;
3091 if(null == workdir) {
3092 workdir = this.workdir();
3094 try {
3095 final TerminalService terminal = TerminalServiceFactory.get();
3096 terminal.open(session.getHost(), workdir);
3098 catch(AccessDeniedException e) {
3099 this.alert(session.getHost(), e, new StringBuilder());
3103 @Action
3104 public void archiveMenuClicked(final NSMenuItem sender) {
3105 final Archive archive = Archive.forName(sender.representedObject());
3106 this.archiveClicked(archive);
3109 @Action
3110 public void archiveButtonClicked(final NSToolbarItem sender) {
3111 this.archiveClicked(Archive.TARGZ);
3115 * @param format Archive format
3117 private void archiveClicked(final Archive format) {
3118 new ArchiveController(this).archive(format, this.getSelectedPaths());
3121 @Action
3122 public void unarchiveButtonClicked(final ID sender) {
3123 new ArchiveController(this).unarchive(this.getSelectedPaths());
3127 * Accessor to the working directory
3129 * @return The current working directory or null if no file system is mounted
3131 protected Path workdir() {
3132 return workdir;
3135 public void setWorkdir(final Path directory) {
3136 this.setWorkdir(directory, Collections.<Path>emptyList());
3139 public void setWorkdir(final Path directory, Path selected) {
3140 this.setWorkdir(directory, Collections.singletonList(selected));
3144 * Sets the current working directory. This will update the path selection menu and also add this path to the browsing history.
3146 * @param directory The new working directory to display or null to detach any working directory from the browser
3147 * @param selected Selected files in browser
3149 public void setWorkdir(final Path directory, final List<Path> selected) {
3150 if(log.isDebugEnabled()) {
3151 log.debug(String.format("Set working directory to %s", directory));
3153 // Remove any custom file filter
3154 this.setPathFilter(null);
3155 final NSTableView browser = this.getSelectedBrowserView();
3156 window.endEditingFor(browser);
3157 if(null == directory) {
3158 this.reload(null, Collections.<Path>emptySet(), selected, false);
3160 else {
3161 this.reload(directory, Collections.singleton(directory), selected, false);
3165 private void setNavigation(boolean enabled) {
3166 if(!enabled) {
3167 searchField.setStringValue(StringUtils.EMPTY);
3169 pathPopupButton.removeAllItems();
3170 if(enabled) {
3171 // Update the current working directory
3172 navigation.add(workdir);
3173 Path p = workdir;
3174 do {
3175 this.addNavigation(p);
3176 p = p.getParent();
3178 while(!p.isRoot());
3179 this.addNavigation(p);
3181 pathPopupButton.setEnabled(enabled);
3182 navigationButton.setEnabled_forSegment(enabled && navigation.getBack().size() > 1, NAVIGATION_LEFT_SEGMENT_BUTTON);
3183 navigationButton.setEnabled_forSegment(enabled && navigation.getForward().size() > 0, NAVIGATION_RIGHT_SEGMENT_BUTTON);
3184 upButton.setEnabled_forSegment(enabled && !workdir.isRoot(), NAVIGATION_UP_SEGMENT_BUTTON);
3187 private void addNavigation(final Path p) {
3188 pathPopupButton.addItemWithTitle(p.getAbsolute());
3189 pathPopupButton.lastItem().setRepresentedObject(p.getAbsolute());
3190 if(p.isVolume()) {
3191 pathPopupButton.lastItem().setImage(IconCacheFactory.<NSImage>get().volumeIcon(session.getHost().getProtocol(), 16));
3193 else {
3194 pathPopupButton.lastItem().setImage(IconCacheFactory.<NSImage>get().fileIcon(p, 16));
3199 * Initializes a session for the passed host. Setting up the listeners and adding any callback
3200 * controllers needed for login, trust management and hostkey verification.
3202 * @param host Bookmark
3203 * @return A session object bound to this browser controller
3205 private Session init(final Host host) {
3206 session = SessionFactory.create(host,
3207 new KeychainX509TrustManager(new DefaultTrustManagerHostnameCallback(host), this),
3208 new KeychainX509KeyManager(this));
3209 transcript.clear();
3210 navigation.clear();
3211 pasteboard = PathPasteboardFactory.getPasteboard(session);
3212 this.setWorkdir(null);
3213 this.setEncoding(session.getEncoding());
3214 return session;
3218 * Open connection in browser
3220 * @param host Bookmark
3222 public void mount(final Host host) {
3223 if(log.isDebugEnabled()) {
3224 log.debug(String.format("Mount session for %s", host));
3226 this.unmount(new Runnable() {
3227 @Override
3228 public void run() {
3229 // The browser has no session, we are allowed to proceed
3230 // Initialize the browser with the new session attaching all listeners
3231 final Session session = init(host);
3232 background(new WorkerBackgroundAction<Path>(BrowserController.this, session, cache,
3233 new MountWorker(host, cache, listener) {
3234 @Override
3235 public void cleanup(final Path workdir) {
3236 if(null == workdir) {
3237 unmount();
3239 else {
3240 // Update status icon
3241 bookmarkTable.setNeedsDisplay();
3242 // Set the working directory
3243 setWorkdir(workdir);
3244 // Close bookmarks
3245 selectBrowser(preferences.getInteger("browser.view"));
3246 // Set the window title
3247 window.setRepresentedFilename(HistoryCollection.defaultCollection().getFile(host).getAbsolute());
3248 if(preferences.getBoolean("browser.disconnect.confirm")) {
3249 window.setDocumentEdited(true);
3251 securityLabel.setImage(session.isSecured() ? IconCacheFactory.<NSImage>get().iconNamed("locked.tiff")
3252 : IconCacheFactory.<NSImage>get().iconNamed("unlocked.tiff"));
3253 securityLabel.setEnabled(session instanceof SSLSession);
3258 @Override
3259 public void init() {
3260 super.init();
3261 window.setTitle(BookmarkNameProvider.toString(host, true));
3262 window.setRepresentedFilename(StringUtils.EMPTY);
3263 // Update status icon
3264 bookmarkTable.setNeedsDisplay();
3272 * Close connection
3274 * @return True if succeeded
3276 public boolean unmount() {
3277 return this.unmount(new Runnable() {
3278 @Override
3279 public void run() {
3286 * @param disconnected Callback after the session has been disconnected
3287 * @return True if the unmount process has finished, false if the user has to agree first
3288 * to close the connection
3290 public boolean unmount(final Runnable disconnected) {
3291 return this.unmount(new SheetCallback() {
3292 @Override
3293 public void callback(int returncode) {
3294 if(returncode == DEFAULT_OPTION) {
3295 unmountImpl(disconnected);
3298 }, disconnected);
3302 * @param callback Confirmation callback
3303 * @param disconnected Action to run after disconnected
3304 * @return True if succeeded
3306 public boolean unmount(final SheetCallback callback, final Runnable disconnected) {
3307 if(log.isDebugEnabled()) {
3308 log.debug(String.format("Unmount session %s", session));
3310 if(this.isConnected() || this.isActivityRunning()) {
3311 if(preferences.getBoolean("browser.disconnect.confirm")) {
3312 // Defer the unmount to the callback function
3313 final NSAlert alert = NSAlert.alert(
3314 MessageFormat.format(LocaleFactory.localizedString("Disconnect from {0}"), session.getHost().getHostname()), //title
3315 LocaleFactory.localizedString("The connection will be closed."), // message
3316 LocaleFactory.localizedString("Disconnect"), // defaultbutton
3317 LocaleFactory.localizedString("Cancel"), // alternate button
3318 null //other button
3320 alert.setShowsSuppressionButton(true);
3321 alert.suppressionButton().setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
3322 this.alert(alert, new SheetCallback() {
3323 @Override
3324 public void callback(int returncode) {
3325 if(alert.suppressionButton().state() == NSCell.NSOnState) {
3326 // Never show again.
3327 preferences.setProperty("browser.disconnect.confirm", false);
3329 callback.callback(returncode);
3332 // No unmount yet
3333 return false;
3336 this.unmountImpl(disconnected);
3337 // Unmount succeeded
3338 return true;
3342 * @param disconnected Action to run after disconnected
3344 private void unmountImpl(final Runnable disconnected) {
3345 final List<Editor> list = editors;
3346 this.disconnect(new Runnable() {
3347 @Override
3348 public void run() {
3349 session = null;
3350 for(Editor e : list) {
3351 e.delete();
3353 window.setTitle(preferences.getProperty("application.name"));
3354 window.setRepresentedFilename(StringUtils.EMPTY);
3355 disconnected.run();
3356 list.clear();
3357 cache.clear();
3363 * Unmount this session
3365 private void disconnect(final Runnable disconnected) {
3366 final InfoController c = InfoControllerFactory.get(BrowserController.this);
3367 if(null != c) {
3368 c.window().close();
3370 if(session != null) {
3371 this.background(new WorkerBackgroundAction<Void>(this, session, cache, new DisconnectWorker(session.getHost())) {
3372 @Override
3373 public void prepare() throws ConnectionCanceledException {
3374 if(!session.isConnected()) {
3375 throw new ConnectionCanceledException();
3377 super.prepare();
3380 @Override
3381 protected boolean connect(Session session) throws BackgroundException {
3382 return false;
3385 @Override
3386 public void cleanup() {
3387 super.cleanup();
3388 window.setDocumentEdited(false);
3389 disconnected.run();
3393 else {
3394 disconnected.run();
3398 @Action
3399 public void printDocument(final ID sender) {
3400 this.print(this.getSelectedBrowserView());
3404 * @param app Singleton
3405 * @return NSApplication.TerminateLater if the application should not yet be terminated
3407 public static NSUInteger applicationShouldTerminate(final NSApplication app) {
3408 // Determine if there are any open connections
3409 for(final BrowserController controller : MainController.getBrowsers()) {
3410 if(!controller.unmount(new SheetCallback() {
3411 @Override
3412 public void callback(final int returncode) {
3413 if(returncode == DEFAULT_OPTION) { //Disconnect
3414 controller.window().close();
3415 if(NSApplication.NSTerminateNow.equals(BrowserController.applicationShouldTerminate(app))) {
3416 app.replyToApplicationShouldTerminate(true);
3419 else {
3420 app.replyToApplicationShouldTerminate(false);
3424 }, new Runnable() {
3425 @Override
3426 public void run() {
3430 )) {
3431 return NSApplication.NSTerminateCancel;
3434 return NSApplication.NSTerminateNow;
3437 @Override
3438 public boolean windowShouldClose(final NSWindow sender) {
3439 return this.unmount(new Runnable() {
3440 @Override
3441 public void run() {
3442 sender.close();
3448 * @param item Menu item
3449 * @return True if the menu should be enabled
3451 @Override
3452 public boolean validateMenuItem(final NSMenuItem item) {
3453 final Selector action = item.action();
3454 if(action.equals(Foundation.selector("paste:"))) {
3455 final String title = "Paste {0}";
3456 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), StringUtils.EMPTY).trim());
3457 if(this.isMounted()) {
3458 if(pasteboard.isEmpty()) {
3459 if(NSPasteboard.generalPasteboard().availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) {
3460 NSObject o = NSPasteboard.generalPasteboard().propertyListForType(NSPasteboard.FilenamesPboardType);
3461 if(o != null) {
3462 if(o.isKindOfClass(Rococoa.createClass("NSArray", NSArray._Class.class))) {
3463 final NSArray elements = Rococoa.cast(o, NSArray.class);
3464 if(elements.count().intValue() == 1) {
3465 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3466 "\"" + elements.objectAtIndex(new NSUInteger(0)) + "\"").trim());
3468 else {
3469 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3470 MessageFormat.format(LocaleFactory.localizedString("{0} Files"),
3471 String.valueOf(elements.count().intValue()))
3472 ).trim());
3478 else {
3479 if(pasteboard.size() == 1) {
3480 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3481 "\"" + pasteboard.get(0).getName() + "\"").trim());
3483 else {
3484 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3485 MessageFormat.format(LocaleFactory.localizedString("{0} Files"), String.valueOf(pasteboard.size()))).trim());
3490 else if(action.equals(Foundation.selector("cut:")) || action.equals(Foundation.selector("copy:"))) {
3491 String title = null;
3492 if(action.equals(Foundation.selector("cut:"))) {
3493 title = "Cut {0}";
3495 else if(action.equals(Foundation.selector("copy:"))) {
3496 title = "Copy {0}";
3498 if(this.isMounted()) {
3499 int count = this.getSelectionCount();
3500 if(0 == count) {
3501 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), StringUtils.EMPTY).trim());
3503 else if(1 == count) {
3504 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3505 "\"" + this.getSelectedPath().getName() + "\"").trim());
3507 else {
3508 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3509 MessageFormat.format(LocaleFactory.localizedString("{0} Files"), String.valueOf(this.getSelectionCount()))).trim());
3512 else {
3513 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), StringUtils.EMPTY).trim());
3516 else if(action.equals(Foundation.selector("showHiddenFilesClicked:"))) {
3517 item.setState(this.getFilter() instanceof NullFilter ? NSCell.NSOnState : NSCell.NSOffState);
3519 else if(action.equals(Foundation.selector("encodingMenuClicked:"))) {
3520 if(this.isMounted()) {
3521 item.setState(session.getEncoding().equalsIgnoreCase(
3522 item.title()) ? NSCell.NSOnState : NSCell.NSOffState);
3524 else {
3525 item.setState(preferences.getProperty("browser.charset.encoding").equalsIgnoreCase(
3526 item.title()) ? NSCell.NSOnState : NSCell.NSOffState);
3529 else if(action.equals(Foundation.selector("browserSwitchMenuClicked:"))) {
3530 if(item.tag() == preferences.getInteger("browser.view")) {
3531 item.setState(NSCell.NSOnState);
3533 else {
3534 item.setState(NSCell.NSOffState);
3537 else if(action.equals(Foundation.selector("archiveMenuClicked:"))) {
3538 final Archive archive = Archive.forName(item.representedObject());
3539 item.setTitle(archive.getTitle(this.getSelectedPaths()));
3541 else if(action.equals(Foundation.selector("quicklookButtonClicked:"))) {
3542 item.setKeyEquivalent(" ");
3543 item.setKeyEquivalentModifierMask(0);
3545 return this.validate(action);
3548 private boolean validate(final Selector action) {
3549 return new BrowserToolbarValidator(this).validate(action);
3552 @Override
3553 public boolean validateToolbarItem(final NSToolbarItem item) {
3554 return new BrowserToolbarValidator(this).validate(item);
3557 @Override
3558 public NSToolbarItem toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar(final NSToolbar toolbar, final String itemIdentifier, boolean inserted) {
3559 if(log.isDebugEnabled()) {
3560 log.debug("toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar:" + itemIdentifier);
3562 return browserToolbarFactory.create(itemIdentifier);
3566 * @param toolbar Window toolbar
3567 * @return The default configuration of toolbar items
3569 @Override
3570 public NSArray toolbarDefaultItemIdentifiers(final NSToolbar toolbar) {
3571 return browserToolbarFactory.getDefault();
3575 * @param toolbar Window toolbar
3576 * @return All available toolbar items
3578 @Override
3579 public NSArray toolbarAllowedItemIdentifiers(final NSToolbar toolbar) {
3580 return browserToolbarFactory.getAllowed();
3583 @Override
3584 public NSArray toolbarSelectableItemIdentifiers(NSToolbar toolbar) {
3585 return NSArray.array();
3589 * Overrriden to remove any listeners from the session
3591 @Override
3592 public void invalidate() {
3593 if(quicklook.isAvailable()) {
3594 if(quicklook.isOpen()) {
3595 quicklook.close();
3598 bookmarkTable.setDelegate(null);
3599 bookmarkTable.setDataSource(null);
3600 bookmarkModel.invalidate();
3602 browserListView.setDelegate(null);
3603 browserListView.setDataSource(null);
3604 browserListModel.invalidate();
3606 browserOutlineView.setDelegate(null);
3607 browserOutlineView.setDataSource(null);
3608 browserOutlineModel.invalidate();
3610 toolbar.setDelegate(null);
3612 browserListColumnsFactory.clear();
3613 browserOutlineColumnsFactory.clear();
3614 bookmarkTableColumnFactory.clear();
3616 quickConnectPopup.setDelegate(null);
3617 quickConnectPopup.setDataSource(null);
3619 archiveMenu.setDelegate(null);
3620 editMenu.setDelegate(null);
3622 notificationCenter.removeObserver(this.id());
3624 super.invalidate();