Fix transcript in transfer window.
[cyberduck.git] / source / ch / cyberduck / ui / cocoa / BrowserController.java
blob93077fd3b990df3ec07d446e254866b5fc057538
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.Command;
43 import ch.cyberduck.core.features.Compress;
44 import ch.cyberduck.core.features.Location;
45 import ch.cyberduck.core.features.Move;
46 import ch.cyberduck.core.features.Symlink;
47 import ch.cyberduck.core.features.Touch;
48 import ch.cyberduck.core.features.Versioning;
49 import ch.cyberduck.core.local.Application;
50 import ch.cyberduck.core.local.ApplicationFinder;
51 import ch.cyberduck.core.local.ApplicationFinderFactory;
52 import ch.cyberduck.core.local.ApplicationQuitCallback;
53 import ch.cyberduck.core.local.BrowserLauncherFactory;
54 import ch.cyberduck.core.local.TemporaryFileServiceFactory;
55 import ch.cyberduck.core.pasteboard.HostPasteboard;
56 import ch.cyberduck.core.pasteboard.PathPasteboard;
57 import ch.cyberduck.core.pasteboard.PathPasteboardFactory;
58 import ch.cyberduck.core.preferences.Preferences;
59 import ch.cyberduck.core.preferences.PreferencesFactory;
60 import ch.cyberduck.core.resources.IconCacheFactory;
61 import ch.cyberduck.core.serializer.HostDictionary;
62 import ch.cyberduck.core.sftp.SFTPSession;
63 import ch.cyberduck.core.ssl.DefaultTrustManagerHostnameCallback;
64 import ch.cyberduck.core.ssl.KeychainX509KeyManager;
65 import ch.cyberduck.core.ssl.KeychainX509TrustManager;
66 import ch.cyberduck.core.ssl.SSLSession;
67 import ch.cyberduck.core.threading.BackgroundAction;
68 import ch.cyberduck.core.threading.DefaultMainAction;
69 import ch.cyberduck.core.threading.MainAction;
70 import ch.cyberduck.core.threading.TransferBackgroundAction;
71 import ch.cyberduck.core.threading.WorkerBackgroundAction;
72 import ch.cyberduck.core.transfer.CopyTransfer;
73 import ch.cyberduck.core.transfer.DisabledTransferErrorCallback;
74 import ch.cyberduck.core.transfer.DownloadTransfer;
75 import ch.cyberduck.core.transfer.SyncTransfer;
76 import ch.cyberduck.core.transfer.Transfer;
77 import ch.cyberduck.core.transfer.TransferAction;
78 import ch.cyberduck.core.transfer.TransferAdapter;
79 import ch.cyberduck.core.transfer.TransferCallback;
80 import ch.cyberduck.core.transfer.TransferItem;
81 import ch.cyberduck.core.transfer.TransferOptions;
82 import ch.cyberduck.core.transfer.TransferProgress;
83 import ch.cyberduck.core.transfer.TransferPrompt;
84 import ch.cyberduck.core.transfer.UploadTransfer;
85 import ch.cyberduck.core.urlhandler.SchemeHandlerFactory;
86 import ch.cyberduck.core.worker.DeleteWorker;
87 import ch.cyberduck.core.worker.DisconnectWorker;
88 import ch.cyberduck.core.worker.MountWorker;
89 import ch.cyberduck.core.worker.MoveWorker;
90 import ch.cyberduck.core.worker.RevertWorker;
91 import ch.cyberduck.core.worker.SessionListWorker;
92 import ch.cyberduck.ui.browser.Column;
93 import ch.cyberduck.ui.browser.DownloadDirectoryFinder;
94 import ch.cyberduck.ui.browser.PathReloadFinder;
95 import ch.cyberduck.ui.browser.RegexFilter;
96 import ch.cyberduck.ui.browser.SearchFilter;
97 import ch.cyberduck.ui.browser.UploadDirectoryFinder;
98 import ch.cyberduck.ui.browser.UploadTargetFinder;
99 import ch.cyberduck.ui.cocoa.delegate.ArchiveMenuDelegate;
100 import ch.cyberduck.ui.cocoa.delegate.CopyURLMenuDelegate;
101 import ch.cyberduck.ui.cocoa.delegate.EditMenuDelegate;
102 import ch.cyberduck.ui.cocoa.delegate.OpenURLMenuDelegate;
103 import ch.cyberduck.ui.cocoa.delegate.URLMenuDelegate;
104 import ch.cyberduck.ui.cocoa.quicklook.QLPreviewPanel;
105 import ch.cyberduck.ui.cocoa.quicklook.QLPreviewPanelController;
106 import ch.cyberduck.ui.cocoa.quicklook.QuickLook;
107 import ch.cyberduck.ui.cocoa.quicklook.QuickLookFactory;
108 import ch.cyberduck.ui.cocoa.threading.BrowserControllerBackgroundAction;
109 import ch.cyberduck.ui.cocoa.threading.WindowMainAction;
110 import ch.cyberduck.ui.cocoa.view.BookmarkCell;
111 import ch.cyberduck.ui.cocoa.view.OutlineCell;
113 import org.apache.commons.collections.CollectionUtils;
114 import org.apache.commons.lang3.StringUtils;
115 import org.apache.log4j.Logger;
116 import org.rococoa.Foundation;
117 import org.rococoa.ID;
118 import org.rococoa.Rococoa;
119 import org.rococoa.Selector;
120 import org.rococoa.cocoa.CGFloat;
121 import org.rococoa.cocoa.foundation.NSInteger;
122 import org.rococoa.cocoa.foundation.NSPoint;
123 import org.rococoa.cocoa.foundation.NSRect;
124 import org.rococoa.cocoa.foundation.NSSize;
125 import org.rococoa.cocoa.foundation.NSUInteger;
127 import java.security.cert.CertificateException;
128 import java.security.cert.X509Certificate;
129 import java.text.MessageFormat;
130 import java.util.ArrayList;
131 import java.util.Collections;
132 import java.util.Comparator;
133 import java.util.EnumSet;
134 import java.util.HashMap;
135 import java.util.HashSet;
136 import java.util.Iterator;
137 import java.util.List;
138 import java.util.Locale;
139 import java.util.Map;
140 import java.util.Set;
143 * @version $Id$
145 public class BrowserController extends WindowController
146 implements ProgressListener, TranscriptListener, NSToolbar.Delegate, QLPreviewPanelController {
147 private static Logger log = Logger.getLogger(BrowserController.class);
150 * No file filter.
152 private static final Filter<Path> NULL_FILTER = new NullFilter<Path>();
155 * Filter hidden files.
157 private static final Filter<Path> HIDDEN_FILTER = new RegexFilter();
159 private final BookmarkCollection bookmarks
160 = BookmarkCollection.defaultCollection();
165 private Session<?> session;
168 * Log Drawer
170 private TranscriptController transcript;
172 private final QuickLook quicklook = QuickLookFactory.get();
174 private Preferences preferences
175 = PreferencesFactory.get();
178 * Hide files beginning with '.'
180 private boolean showHiddenFiles;
182 private Filter<Path> filenameFilter;
185 if(PreferencesFactory.get().getBoolean("browser.showHidden")) {
186 this.filenameFilter = new NullFilter<Path>();
187 this.showHiddenFiles = true;
189 else {
190 this.filenameFilter = new RegexFilter();
191 this.showHiddenFiles = false;
195 private final NSTextFieldCell outlineCellPrototype = OutlineCell.outlineCell();
196 private final NSImageCell imageCellPrototype = NSImageCell.imageCell();
197 private final NSTextFieldCell textCellPrototype = NSTextFieldCell.textFieldCell();
198 private final NSTextFieldCell filenameCellPrototype = NSTextFieldCell.textFieldCell();
200 private final TableColumnFactory browserListColumnsFactory = new TableColumnFactory();
201 private final TableColumnFactory browserOutlineColumnsFactory = new TableColumnFactory();
202 private final TableColumnFactory bookmarkTableColumnFactory = new TableColumnFactory();
204 // setting appearance attributes()
205 private final NSLayoutManager layoutManager = NSLayoutManager.layoutManager();
207 @Delegate
208 private BrowserOutlineViewModel browserOutlineModel;
210 @Outlet
211 private NSOutlineView browserOutlineView;
213 @Delegate
214 private AbstractBrowserTableDelegate<Path> browserOutlineViewDelegate;
216 @Delegate
217 private BrowserListViewModel browserListModel;
219 @Outlet
220 private NSTableView browserListView;
222 @Delegate
223 private AbstractBrowserTableDelegate<Path> browserListViewDelegate;
225 private NSToolbar toolbar;
227 private final Navigation navigation = new Navigation();
229 private PathPasteboard pasteboard;
231 private ListProgressListener listener
232 = new PromptLimitedListProgressListener(this);
235 * Caching files listings of previously listed directories
237 private PathCache cache
238 = new PathCache(preferences.getInteger("browser.cache.size"));
240 private List<Editor> editors
241 = new ArrayList<Editor>();
243 public BrowserController() {
244 this.loadBundle();
247 @Override
248 protected String getBundleName() {
249 return "Browser";
252 protected void validateToolbar() {
253 this.window().toolbar().validateVisibleItems();
256 public static void updateBookmarkTableRowHeight() {
257 for(BrowserController controller : MainController.getBrowsers()) {
258 controller._updateBookmarkCell();
262 public static void updateBrowserTableAttributes() {
263 for(BrowserController controller : MainController.getBrowsers()) {
264 controller._updateBrowserAttributes(controller.browserListView);
265 controller._updateBrowserAttributes(controller.browserOutlineView);
269 public static void updateBrowserTableColumns() {
270 for(BrowserController controller : MainController.getBrowsers()) {
271 controller._updateBrowserColumns(controller.browserListView, controller.browserListViewDelegate);
272 controller._updateBrowserColumns(controller.browserOutlineView, controller.browserOutlineViewDelegate);
276 @Override
277 public void awakeFromNib() {
278 super.awakeFromNib();
279 // Configure Toolbar
280 this.toolbar = NSToolbar.toolbarWithIdentifier("Cyberduck Toolbar");
281 this.toolbar.setDelegate((this.id()));
282 this.toolbar.setAllowsUserCustomization(true);
283 this.toolbar.setAutosavesConfiguration(true);
284 this.window().setToolbar(toolbar);
285 this.window().makeFirstResponder(quickConnectPopup);
286 this._updateBrowserColumns(browserListView, browserListViewDelegate);
287 this._updateBrowserColumns(browserOutlineView, browserOutlineViewDelegate);
288 if(preferences.getBoolean("browser.transcript.open")) {
289 this.logDrawer.open();
291 if(LicenseFactory.find().equals(LicenseFactory.EMPTY_LICENSE)) {
292 this.addDonateWindowTitle();
294 this.setNavigation(false);
295 this.selectBookmarks();
298 protected Comparator<Path> getComparator() {
299 return this.getSelectedBrowserDelegate().getSortingComparator();
302 protected Filter<Path> getFilter() {
303 return this.filenameFilter;
306 public PathPasteboard getPasteboard() {
307 return pasteboard;
310 protected void setPathFilter(final String search) {
311 if(log.isDebugEnabled()) {
312 log.debug(String.format("Set path filter to %s", search));
314 if(StringUtils.isBlank(search)) {
315 this.searchField.setStringValue(StringUtils.EMPTY);
316 // Revert to the last used default filter
317 if(this.isShowHiddenFiles()) {
318 this.filenameFilter = NULL_FILTER;
320 else {
321 this.filenameFilter = HIDDEN_FILTER;
324 else {
325 // Setting up a custom filter for the directory listing
326 this.filenameFilter = new SearchFilter(search);
330 public void setShowHiddenFiles(boolean showHidden) {
331 if(showHidden) {
332 this.filenameFilter = NULL_FILTER;
333 this.showHiddenFiles = true;
335 else {
336 this.filenameFilter = HIDDEN_FILTER;
337 this.showHiddenFiles = false;
341 public boolean isShowHiddenFiles() {
342 return this.showHiddenFiles;
346 * Marks the current browser as the first responder
348 private void getFocus() {
349 NSView view;
350 if(this.getSelectedTabView() == TAB_BOOKMARKS) {
351 view = bookmarkTable;
353 else {
354 if(this.isMounted()) {
355 view = this.getSelectedBrowserView();
357 else {
358 view = quickConnectPopup;
361 this.setStatus();
362 this.window().makeFirstResponder(view);
367 protected void reload() {
368 if(this.isMounted()) {
369 this.reload(Collections.singleton(workdir), this.getSelectedPaths(), false);
371 else {
372 final NSTableView browser = this.getSelectedBrowserView();
373 final BrowserTableDataSource model = this.getSelectedBrowserModel();
374 model.render(browser, Collections.<Path>emptyList());
379 * Make the browser reload its content. Will make use of the cache.
381 * @param selected The items to be selected
383 protected void reload(final List<Path> changed, final List<Path> selected) {
384 this.reload(new PathReloadFinder().find(changed), selected, true);
388 * Make the browser reload its content. Will make use of the cache.
390 * @param folders Folders to render
391 * @param selected The items to be selected
393 protected void reload(final Set<Path> folders, final List<Path> selected) {
394 this.reload(folders, selected, true);
397 protected void reload(final Set<Path> folders, final List<Path> selected, final boolean invalidate) {
398 if(log.isDebugEnabled()) {
399 log.debug(String.format("Reload data with selected files %s", selected));
401 final BrowserTableDataSource model = this.getSelectedBrowserModel();
402 final NSTableView browser = this.getSelectedBrowserView();
403 if(folders.isEmpty()) {
404 // Render empty browser
405 model.render(browser, Collections.<Path>emptyList());
407 for(final Path folder : folders) {
408 if(invalidate) {
409 // Invalidate cache
410 cache.invalidate(folder);
412 else {
413 if(cache.isCached(folder)) {
414 model.render(browser, Collections.singletonList(folder));
415 select(selected);
416 return;
419 // Delay render until path is cached in the background
420 this.background(new WorkerBackgroundAction<AttributedList<Path>>(this, session, cache,
421 new SessionListWorker(session, cache, folder, listener) {
422 @Override
423 public void cleanup(final AttributedList<Path> list) {
424 model.render(browser, Collections.singletonList(folder));
425 select(selected);
433 private void select(final List<Path> selected) {
434 final NSTableView browser = this.getSelectedBrowserView();
435 if(CollectionUtils.isEqualCollection(this.getSelectedPaths(), selected)) {
436 return;
438 browser.deselectAll(null);
439 for(Path path : selected) {
440 select(path, true, true);
445 * @param file Path to select
446 * @param expand Keep previous selection
447 * @param scroll Scroll to selection
449 private void select(final Path file, final boolean expand, final boolean scroll) {
450 final NSTableView browser = this.getSelectedBrowserView();
451 final BrowserTableDataSource model = this.getSelectedBrowserModel();
452 if(log.isDebugEnabled()) {
453 log.debug(String.format("Select row for reference %s", file));
455 int row = model.indexOf(browser, file);
456 if(-1 == row) {
457 log.warn(String.format("Failed to find row for %s", file));
458 return;
460 final NSInteger index = new NSInteger(row);
461 browser.selectRowIndexes(NSIndexSet.indexSetWithIndex(index), expand);
462 if(scroll) {
463 browser.scrollRowToVisible(index);
467 private void updateQuickLookSelection(final List<Path> selected) {
468 if(quicklook.isAvailable()) {
469 final List<TransferItem> downloads = new ArrayList<TransferItem>();
470 for(Path path : selected) {
471 if(!path.isFile()) {
472 continue;
474 downloads.add(new TransferItem(
475 path, TemporaryFileServiceFactory.get().create(session.getHost().getUuid(), path)));
477 if(downloads.size() > 0) {
478 final Transfer download = new DownloadTransfer(session.getHost(), downloads);
479 final TransferOptions options = new TransferOptions();
480 background(new TransferBackgroundAction(this, session, cache, new TransferAdapter() {
481 @Override
482 public void progress(final TransferProgress status) {
483 message(status.getProgress());
485 }, this, this, download, options,
486 new TransferPrompt() {
487 @Override
488 public TransferAction prompt(final TransferItem item) {
489 return TransferAction.comparison;
492 @Override
493 public boolean isSelected(final TransferItem file) {
494 return true;
497 @Override
498 public void message(final String message) {
499 BrowserController.this.message(message);
501 }, new DisabledTransferErrorCallback()
503 @Override
504 public void cleanup() {
505 super.cleanup();
506 final List<Local> previews = new ArrayList<Local>();
507 for(TransferItem download : downloads) {
508 previews.add(download.local);
510 // Change files in Quick Look
511 quicklook.select(previews);
512 // Open Quick Look Preview Panel
513 quicklook.open();
516 @Override
517 public String getActivity() {
518 return LocaleFactory.localizedString("Quick Look", "Status");
526 * @return The first selected path found or null if there is no selection
528 protected Path getSelectedPath() {
529 final List<Path> s = this.getSelectedPaths();
530 if(s.size() > 0) {
531 return s.get(0);
533 return null;
537 * @return All selected paths or an empty list if there is no selection
539 protected List<Path> getSelectedPaths() {
540 final AbstractBrowserTableDelegate<Path> delegate = this.getSelectedBrowserDelegate();
541 final NSTableView view = this.getSelectedBrowserView();
542 final NSIndexSet iterator = view.selectedRowIndexes();
543 final List<Path> selected = new ArrayList<Path>();
544 for(NSUInteger index = iterator.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = iterator.indexGreaterThanIndex(index)) {
545 final Path file = delegate.pathAtRow(index.intValue());
546 if(null == file) {
547 break;
549 selected.add(file);
551 return selected;
554 protected int getSelectionCount() {
555 return this.getSelectedBrowserView().numberOfSelectedRows().intValue();
558 @Override
559 public void setWindow(NSWindow window) {
560 window.setTitle(preferences.getProperty("application.name"));
561 window.setMiniwindowImage(IconCacheFactory.<NSImage>get().iconNamed("cyberduck-document.icns"));
562 window.setMovableByWindowBackground(true);
563 window.setCollectionBehavior(window.collectionBehavior() | NSWindow.NSWindowCollectionBehavior.NSWindowCollectionBehaviorFullScreenPrimary);
564 window.setContentMinSize(new NSSize(400d, 200d));
565 // Accept file promises made myself
566 window.registerForDraggedTypes(NSArray.arrayWithObject(NSPasteboard.FilesPromisePboardType));
567 super.setWindow(window);
571 * @return NSDragOperation
573 public NSUInteger draggingEntered(final NSDraggingInfo sender) {
574 return this.draggingUpdated(sender);
577 public NSUInteger draggingUpdated(final NSDraggingInfo sender) {
578 final NSPasteboard pasteboard = sender.draggingPasteboard();
579 if(pasteboard.types().indexOfObject(NSString.stringWithString(NSPasteboard.FilesPromisePboardType)) != null) {
580 final NSView hit = sender.draggingDestinationWindow().contentView().hitTest(sender.draggingLocation());
581 if(hit != null) {
582 if(hit.equals(bookmarkButton)) {
583 if(historyButton.state() == NSCell.NSOnState
584 || bonjourButton.state() == NSCell.NSOnState) {
585 return NSDraggingInfo.NSDragOperationCopy;
590 return NSDraggingInfo.NSDragOperationNone;
593 public boolean prepareForDragOperation(final NSDraggingInfo sender) {
594 // Continue to performDragOperation
595 return true;
598 public boolean performDragOperation(final NSDraggingInfo sender) {
599 for(Host bookmark : HostPasteboard.getPasteboard()) {
600 final Host duplicate = new HostDictionary().deserialize(bookmark.serialize(SerializerFactory.get()));
601 // Make sure a new UUID is assigned for duplicate
602 duplicate.setUuid(null);
603 bookmarks.add(0, duplicate);
605 return true;
608 @Outlet
609 private NSDrawer logDrawer;
611 public void drawerDidOpen(NSNotification notification) {
612 preferences.setProperty("browser.transcript.open", true);
615 public void drawerDidClose(NSNotification notification) {
616 preferences.setProperty("browser.transcript.open", false);
617 transcript.clear();
620 public NSSize drawerWillResizeContents_toSize(final NSDrawer sender, final NSSize contentSize) {
621 return contentSize;
624 public void setLogDrawer(NSDrawer drawer) {
625 this.logDrawer = drawer;
626 this.transcript = new TranscriptController() {
627 @Override
628 public boolean isOpen() {
629 return logDrawer.state() == NSDrawer.OpenState;
632 this.logDrawer.setContentView(this.transcript.getLogView());
633 this.logDrawer.setDelegate(this.id());
636 private NSButton donateButton;
638 public void setDonateButton(NSButton donateButton) {
639 this.donateButton = donateButton;
640 this.donateButton.setTitle(LocaleFactory.localizedString("Get a donation key!", "License"));
641 this.donateButton.setAction(Foundation.selector("donateMenuClicked:"));
642 this.donateButton.sizeToFit();
645 private void addDonateWindowTitle() {
646 NSView parent = this.window().contentView().superview();
647 NSSize bounds = parent.frame().size;
648 NSSize size = donateButton.frame().size;
649 donateButton.setFrame(new NSRect(
650 new NSPoint(
651 bounds.width.intValue() - size.width.intValue() - 40,
652 bounds.height.intValue() - size.height.intValue() + 3),
653 new NSSize(
654 size.width.intValue(),
655 size.height.intValue())
658 donateButton.setAutoresizingMask(new NSUInteger(NSView.NSViewMinXMargin | NSView.NSViewMinYMargin));
659 parent.addSubview(donateButton);
662 public void removeDonateWindowTitle() {
663 donateButton.removeFromSuperview();
666 private static final int TAB_BOOKMARKS = 0;
667 private static final int TAB_LIST_VIEW = 1;
668 private static final int TAB_OUTLINE_VIEW = 2;
670 private int getSelectedTabView() {
671 return browserTabView.indexOfTabViewItem(browserTabView.selectedTabViewItem());
674 private NSTabView browserTabView;
676 public void setBrowserTabView(NSTabView browserTabView) {
677 this.browserTabView = browserTabView;
681 * @return The currently selected browser view (which is either an outlineview or a plain tableview)
683 public NSTableView getSelectedBrowserView() {
684 switch(preferences.getInteger("browser.view")) {
685 case SWITCH_LIST_VIEW: {
686 return browserListView;
688 case SWITCH_OUTLINE_VIEW: {
689 return browserOutlineView;
692 throw new FactoryException("No selected browser view");
696 * @return The datasource of the currently selected browser view
698 public BrowserTableDataSource getSelectedBrowserModel() {
699 switch(this.browserSwitchView.selectedSegment()) {
700 case SWITCH_LIST_VIEW: {
701 return browserListModel;
703 case SWITCH_OUTLINE_VIEW: {
704 return browserOutlineModel;
707 throw new FactoryException("No selected browser view");
710 public AbstractBrowserTableDelegate<Path> getSelectedBrowserDelegate() {
711 switch(this.browserSwitchView.selectedSegment()) {
712 case SWITCH_LIST_VIEW: {
713 return browserListViewDelegate;
715 case SWITCH_OUTLINE_VIEW: {
716 return browserOutlineViewDelegate;
719 throw new FactoryException("No selected browser view");
722 @Outlet
723 private NSMenu editMenu;
725 @Delegate
726 private EditMenuDelegate editMenuDelegate;
728 public void setEditMenu(NSMenu editMenu) {
729 this.editMenu = editMenu;
730 this.editMenuDelegate = new EditMenuDelegate() {
731 @Override
732 protected Path getEditable() {
733 final Path selected = BrowserController.this.getSelectedPath();
734 if(null == selected) {
735 return null;
737 if(isEditable(selected)) {
738 return selected;
740 return null;
743 @Override
744 protected ID getTarget() {
745 return BrowserController.this.id();
748 this.editMenu.setDelegate(editMenuDelegate.id());
751 @Outlet
752 private NSMenu urlMenu;
754 @Delegate
755 private URLMenuDelegate urlMenuDelegate;
757 public void setUrlMenu(NSMenu urlMenu) {
758 this.urlMenu = urlMenu;
759 this.urlMenuDelegate = new CopyURLMenuDelegate() {
760 @Override
761 protected Session<?> getSession() {
762 return BrowserController.this.getSession();
765 @Override
766 protected List<Path> getSelected() {
767 final List<Path> s = BrowserController.this.getSelectedPaths();
768 if(s.isEmpty()) {
769 if(BrowserController.this.isMounted()) {
770 return Collections.singletonList(BrowserController.this.workdir());
773 return s;
776 this.urlMenu.setDelegate(urlMenuDelegate.id());
779 @Outlet
780 private NSMenu openUrlMenu;
782 @Delegate
783 private URLMenuDelegate openUrlMenuDelegate;
785 public void setOpenUrlMenu(NSMenu openUrlMenu) {
786 this.openUrlMenu = openUrlMenu;
787 this.openUrlMenuDelegate = new OpenURLMenuDelegate() {
788 @Override
789 protected Session<?> getSession() {
790 return BrowserController.this.getSession();
793 @Override
794 protected List<Path> getSelected() {
795 final List<Path> s = BrowserController.this.getSelectedPaths();
796 if(s.isEmpty()) {
797 if(BrowserController.this.isMounted()) {
798 return Collections.singletonList(BrowserController.this.workdir());
801 return s;
804 this.openUrlMenu.setDelegate(openUrlMenuDelegate.id());
807 @Outlet
808 private NSMenu archiveMenu;
810 @Delegate
811 private ArchiveMenuDelegate archiveMenuDelegate;
813 public void setArchiveMenu(NSMenu archiveMenu) {
814 this.archiveMenu = archiveMenu;
815 this.archiveMenuDelegate = new ArchiveMenuDelegate();
816 this.archiveMenu.setDelegate(archiveMenuDelegate.id());
819 @Outlet
820 private NSButton bonjourButton;
822 public void setBonjourButton(NSButton bonjourButton) {
823 this.bonjourButton = bonjourButton;
824 NSImage img = IconCacheFactory.<NSImage>get().iconNamed("rendezvous.tiff", 16);
825 img.setTemplate(false);
826 this.bonjourButton.setImage(img);
827 this.setRecessedBezelStyle(this.bonjourButton);
828 this.bonjourButton.setTarget(this.id());
829 this.bonjourButton.setAction(Foundation.selector("bookmarkButtonClicked:"));
832 @Outlet
833 private NSButton historyButton;
835 public void setHistoryButton(NSButton historyButton) {
836 this.historyButton = historyButton;
837 NSImage img = IconCacheFactory.<NSImage>get().iconNamed("history.tiff", 16);
838 img.setTemplate(false);
839 this.historyButton.setImage(img);
840 this.setRecessedBezelStyle(this.historyButton);
841 this.historyButton.setTarget(this.id());
842 this.historyButton.setAction(Foundation.selector("bookmarkButtonClicked:"));
845 @Outlet
846 private NSButton bookmarkButton;
848 public void setBookmarkButton(NSButton bookmarkButton) {
849 this.bookmarkButton = bookmarkButton;
850 NSImage img = IconCacheFactory.<NSImage>get().iconNamed("bookmarks.tiff", 16);
851 img.setTemplate(false);
852 this.bookmarkButton.setImage(img);
853 this.setRecessedBezelStyle(this.bookmarkButton);
854 this.bookmarkButton.setTarget(this.id());
855 this.bookmarkButton.setAction(Foundation.selector("bookmarkButtonClicked:"));
856 this.bookmarkButton.setState(NSCell.NSOnState); // Set as default selected bookmark source
859 public void bookmarkButtonClicked(final NSButton sender) {
860 if(sender != bonjourButton) {
861 bonjourButton.setState(NSCell.NSOffState);
863 if(sender != historyButton) {
864 historyButton.setState(NSCell.NSOffState);
866 if(sender != bookmarkButton) {
867 bookmarkButton.setState(NSCell.NSOffState);
869 sender.setState(NSCell.NSOnState);
870 this.selectBookmarks();
873 private void setRecessedBezelStyle(final NSButton b) {
874 b.setBezelStyle(NSButton.NSRecessedBezelStyle);
875 b.setButtonType(NSButton.NSMomentaryPushButtonButton);
876 b.setImagePosition(NSCell.NSImageLeft);
877 b.setFont(NSFont.boldSystemFontOfSize(11f));
878 b.setShowsBorderOnlyWhileMouseInside(true);
881 public void sortBookmarksByNickame(final ID sender) {
882 bookmarks.sortByNickname();
883 this.reloadBookmarks();
886 public void sortBookmarksByHostname(final ID sender) {
887 bookmarks.sortByHostname();
888 this.reloadBookmarks();
891 public void sortBookmarksByProtocol(final ID sender) {
892 bookmarks.sortByProtocol();
893 this.reloadBookmarks();
896 private NSSegmentedControl bookmarkSwitchView;
898 private static final int SWITCH_BOOKMARK_VIEW = 0;
900 public void setBookmarkSwitchView(NSSegmentedControl bookmarkSwitchView) {
901 this.bookmarkSwitchView = bookmarkSwitchView;
902 this.bookmarkSwitchView.setSegmentCount(1);
903 this.bookmarkSwitchView.setToolTip(LocaleFactory.localizedString("Bookmarks"));
904 final NSImage image = IconCacheFactory.<NSImage>get().iconNamed("book.tiff");
905 this.bookmarkSwitchView.setImage_forSegment(image, SWITCH_BOOKMARK_VIEW);
906 final NSSegmentedCell cell = Rococoa.cast(this.bookmarkSwitchView.cell(), NSSegmentedCell.class);
907 cell.setTrackingMode(NSSegmentedCell.NSSegmentSwitchTrackingSelectAny);
908 cell.setControlSize(NSCell.NSRegularControlSize);
909 this.bookmarkSwitchView.setTarget(this.id());
910 this.bookmarkSwitchView.setAction(Foundation.selector("bookmarkSwitchClicked:"));
911 this.bookmarkSwitchView.setSelectedSegment(SWITCH_BOOKMARK_VIEW);
914 @Action
915 public void bookmarkSwitchClicked(final ID sender) {
916 // Toggle
917 final boolean open = this.getSelectedTabView() != TAB_BOOKMARKS;
918 bookmarkSwitchView.setSelected_forSegment(open, SWITCH_BOOKMARK_VIEW);
919 this.setNavigation(!open && this.isMounted());
920 if(open) {
921 this.selectBookmarks();
923 else {
924 this.selectBrowser(preferences.getInteger("browser.view"));
928 private NSSegmentedControl browserSwitchView;
930 private static final int SWITCH_LIST_VIEW = 0;
931 private static final int SWITCH_OUTLINE_VIEW = 1;
933 public void setBrowserSwitchView(NSSegmentedControl view) {
934 browserSwitchView = view;
935 browserSwitchView.setSegmentCount(2); // list, outline
936 final NSImage list = IconCacheFactory.<NSImage>get().iconNamed("list.tiff");
937 list.setTemplate(true);
938 browserSwitchView.setImage_forSegment(list, SWITCH_LIST_VIEW);
939 final NSImage outline = IconCacheFactory.<NSImage>get().iconNamed("outline.tiff");
940 outline.setTemplate(true);
941 browserSwitchView.setImage_forSegment(outline, SWITCH_OUTLINE_VIEW);
942 browserSwitchView.setTarget(this.id());
943 browserSwitchView.setAction(Foundation.selector("browserSwitchButtonClicked:"));
944 final NSSegmentedCell cell = Rococoa.cast(browserSwitchView.cell(), NSSegmentedCell.class);
945 cell.setTrackingMode(NSSegmentedCell.NSSegmentSwitchTrackingSelectOne);
946 cell.setControlSize(NSCell.NSRegularControlSize);
947 browserSwitchView.setSelectedSegment(preferences.getInteger("browser.view"));
950 @Action
951 public void browserSwitchButtonClicked(final NSSegmentedControl sender) {
952 // Highlight selected browser view
953 this.selectBrowser(sender.selectedSegment());
956 @Action
957 public void browserSwitchMenuClicked(final NSMenuItem sender) {
958 // Highlight selected browser view
959 this.selectBrowser(sender.tag());
962 private void selectBrowser(int selected) {
963 bookmarkSwitchView.setSelected_forSegment(false, SWITCH_BOOKMARK_VIEW);
964 browserSwitchView.setSelectedSegment(selected);
965 switch(selected) {
966 case SWITCH_LIST_VIEW:
967 browserTabView.selectTabViewItemAtIndex(TAB_LIST_VIEW);
968 break;
969 case SWITCH_OUTLINE_VIEW:
970 browserTabView.selectTabViewItemAtIndex(TAB_OUTLINE_VIEW);
971 break;
973 // Save selected browser view
974 preferences.setProperty("browser.view", selected);
975 // Remove any custom file filter
976 this.setPathFilter(null);
977 // Update from model
978 this.reload();
979 // Focus on browser view
980 this.getFocus();
983 private void selectBookmarks() {
984 bookmarkSwitchView.setSelected_forSegment(true, SWITCH_BOOKMARK_VIEW);
985 // Display bookmarks
986 browserTabView.selectTabViewItemAtIndex(TAB_BOOKMARKS);
987 final AbstractHostCollection source;
988 if(bookmarkButton.state() == NSCell.NSOnState) {
989 source = bookmarks;
991 else if(bonjourButton.state() == NSCell.NSOnState) {
992 source = RendezvousCollection.defaultCollection();
994 else if(historyButton.state() == NSCell.NSOnState) {
995 source = HistoryCollection.defaultCollection();
997 else {
998 source = AbstractHostCollection.empty();
1000 if(!source.isLoaded()) {
1001 browserSpinner.startAnimation(null);
1002 source.addListener(new AbstractCollectionListener<Host>() {
1003 @Override
1004 public void collectionLoaded() {
1005 invoke(new WindowMainAction(BrowserController.this) {
1006 @Override
1007 public void run() {
1008 browserSpinner.stopAnimation(null);
1009 bookmarkTable.setGridStyleMask(NSTableView.NSTableViewSolidHorizontalGridLineMask);
1012 source.removeListener(this);
1016 else {
1017 browserSpinner.stopAnimation(null);
1018 bookmarkTable.setGridStyleMask(NSTableView.NSTableViewSolidHorizontalGridLineMask);
1020 bookmarkModel.setSource(source);
1021 this.setBookmarkFilter(null);
1022 this.reloadBookmarks();
1023 if(this.isMounted()) {
1024 int row = this.bookmarkModel.getSource().indexOf(session.getHost());
1025 if(row != -1) {
1026 this.bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(new NSInteger(row)), false);
1027 this.bookmarkTable.scrollRowToVisible(new NSInteger(row));
1030 this.getFocus();
1034 * Reload bookmark table from currently selected model
1036 public void reloadBookmarks() {
1037 bookmarkTable.reloadData();
1038 this.setStatus();
1041 private abstract class AbstractBrowserOutlineViewDelegate<E> extends AbstractBrowserTableDelegate<E>
1042 implements NSOutlineView.Delegate {
1044 protected AbstractBrowserOutlineViewDelegate(final NSTableColumn selectedColumn) {
1045 super(selectedColumn);
1048 public String outlineView_toolTipForCell_rect_tableColumn_item_mouseLocation(NSOutlineView t, NSCell cell,
1049 ID rect, NSTableColumn c,
1050 NSObject item, NSPoint mouseLocation) {
1051 return this.tooltip(cache.lookup(new NSObjectPathReference(item)));
1054 public String outlineView_typeSelectStringForTableColumn_item(final NSOutlineView view,
1055 final NSTableColumn tableColumn,
1056 final NSObject item) {
1057 if(tableColumn.identifier().equals(Column.filename.name())) {
1058 return browserOutlineModel.outlineView_objectValueForTableColumn_byItem(view, tableColumn, item).toString();
1060 return null;
1063 @Override
1064 protected void setBrowserColumnSortingIndicator(NSImage image, String columnIdentifier) {
1065 browserOutlineView.setIndicatorImage_inTableColumn(image,
1066 browserOutlineView.tableColumnWithIdentifier(columnIdentifier));
1069 @Override
1070 protected Path pathAtRow(final int row) {
1071 if(row < browserOutlineView.numberOfRows().intValue()) {
1072 return cache.lookup(new NSObjectPathReference(browserOutlineView.itemAtRow(new NSInteger(row))));
1074 log.warn(String.format("No item at row %d", row));
1075 return null;
1079 private abstract class AbstractBrowserListViewDelegate<E> extends AbstractBrowserTableDelegate<E>
1080 implements NSTableView.Delegate {
1082 protected AbstractBrowserListViewDelegate(final NSTableColumn selectedColumn) {
1083 super(selectedColumn);
1086 public String tableView_toolTipForCell_rect_tableColumn_row_mouseLocation(NSTableView t, NSCell cell,
1087 ID rect, NSTableColumn c,
1088 NSInteger row, NSPoint mouseLocation) {
1089 return this.tooltip(browserListModel.get(workdir()).get(row.intValue()));
1092 @Override
1093 protected void setBrowserColumnSortingIndicator(NSImage image, String columnIdentifier) {
1094 browserListView.setIndicatorImage_inTableColumn(image,
1095 browserListView.tableColumnWithIdentifier(columnIdentifier));
1098 public String tableView_typeSelectStringForTableColumn_row(final NSTableView view,
1099 final NSTableColumn tableColumn,
1100 final NSInteger row) {
1101 if(tableColumn.identifier().equals(Column.filename.name())) {
1102 return browserListModel.tableView_objectValueForTableColumn_row(view, tableColumn, row).toString();
1104 return null;
1107 @Override
1108 protected Path pathAtRow(int row) {
1109 final AttributedList<Path> children = browserListModel.get(workdir());
1110 if(row < children.size()) {
1111 return children.get(row);
1113 log.warn(String.format("No item at row %d", row));
1114 return null;
1118 private abstract class AbstractBrowserTableDelegate<E> extends AbstractPathTableDelegate {
1120 protected AbstractBrowserTableDelegate(final NSTableColumn selectedColumn) {
1121 super(selectedColumn);
1124 @Override
1125 public boolean isColumnRowEditable(NSTableColumn column, int row) {
1126 if(preferences.getBoolean("browser.editable")) {
1127 return column.identifier().equals(Column.filename.name());
1129 return false;
1132 @Override
1133 public void tableRowDoubleClicked(final ID sender) {
1134 BrowserController.this.insideButtonClicked(sender);
1137 public void spaceKeyPressed(final ID sender) {
1138 quicklookButtonClicked(sender);
1141 @Override
1142 public void deleteKeyPressed(final ID sender) {
1143 BrowserController.this.deleteFileButtonClicked(sender);
1146 @Override
1147 public void tableColumnClicked(final NSTableView view, final NSTableColumn tableColumn) {
1148 if(this.selectedColumnIdentifier().equals(tableColumn.identifier())) {
1149 this.setSortedAscending(!this.isSortedAscending());
1151 else {
1152 // Remove sorting indicator on previously selected column
1153 this.setBrowserColumnSortingIndicator(null, this.selectedColumnIdentifier());
1154 // Set the newly selected column
1155 this.setSelectedColumn(tableColumn);
1156 // Update the default value
1157 preferences.setProperty("browser.sort.column", this.selectedColumnIdentifier());
1159 this.setBrowserColumnSortingIndicator(
1160 this.isSortedAscending() ?
1161 IconCacheFactory.<NSImage>get().iconNamed("NSAscendingSortIndicator") :
1162 IconCacheFactory.<NSImage>get().iconNamed("NSDescendingSortIndicator"),
1163 tableColumn.identifier()
1165 reload();
1168 @Override
1169 public void columnDidResize(final String columnIdentifier, final float width) {
1170 preferences.setProperty(String.format("browser.column.%s.width", columnIdentifier), width);
1173 @Override
1174 public void selectionDidChange(NSNotification notification) {
1175 final List<Path> selected = getSelectedPaths();
1176 if(quicklook.isOpen()) {
1177 updateQuickLookSelection(selected);
1179 if(preferences.getBoolean("browser.info.inspector")) {
1180 InfoController c = InfoControllerFactory.get(BrowserController.this);
1181 if(null != c) {
1182 // Currently open info panel
1183 c.setFiles(selected);
1188 protected abstract Path pathAtRow(int row);
1190 protected abstract void setBrowserColumnSortingIndicator(NSImage image, String columnIdentifier);
1192 private static final double kSwipeGestureLeft = 1.000000;
1193 private static final double kSwipeGestureRight = -1.000000;
1194 private static final double kSwipeGestureUp = 1.000000;
1195 private static final double kSwipeGestureDown = -1.000000;
1198 * Available in Mac OS X v10.6 and later.
1200 * @param event Swipe event
1202 @Action
1203 public void swipeWithEvent(NSEvent event) {
1204 if(event.deltaX().doubleValue() == kSwipeGestureLeft) {
1205 BrowserController.this.backButtonClicked(event.id());
1207 else if(event.deltaX().doubleValue() == kSwipeGestureRight) {
1208 BrowserController.this.forwardButtonClicked(event.id());
1210 else if(event.deltaY().doubleValue() == kSwipeGestureUp) {
1211 NSInteger row = getSelectedBrowserView().selectedRow();
1212 NSInteger next;
1213 if(-1 == row.intValue()) {
1214 // No current selection
1215 next = new NSInteger(0);
1217 else {
1218 next = new NSInteger(row.longValue() - 1);
1220 BrowserController.this.getSelectedBrowserView().selectRowIndexes(
1221 NSIndexSet.indexSetWithIndex(next), false);
1223 else if(event.deltaY().doubleValue() == kSwipeGestureDown) {
1224 NSInteger row = getSelectedBrowserView().selectedRow();
1225 NSInteger next;
1226 if(-1 == row.intValue()) {
1227 // No current selection
1228 next = new NSInteger(0);
1230 else {
1231 next = new NSInteger(row.longValue() + 1);
1233 BrowserController.this.getSelectedBrowserView().selectRowIndexes(
1234 NSIndexSet.indexSetWithIndex(next), false);
1240 * QuickLook support for 10.6+
1242 * @param panel The Preview Panel looking for a controller.
1243 * @return
1244 * @ Sent to each object in the responder chain to find a controller.
1246 @Override
1247 public boolean acceptsPreviewPanelControl(QLPreviewPanel panel) {
1248 return true;
1252 * QuickLook support for 10.6+
1253 * The receiver should setup the preview panel (data source, delegate, binding, etc.) here.
1255 * @param panel The Preview Panel the receiver will control.
1256 * @ Sent to the object taking control of the Preview Panel.
1258 @Override
1259 public void beginPreviewPanelControl(QLPreviewPanel panel) {
1260 quicklook.willBeginQuickLook();
1264 * QuickLook support for 10.6+
1265 * The receiver should unsetup the preview panel (data source, delegate, binding, etc.) here.
1267 * @param panel The Preview Panel that the receiver will stop controlling.
1268 * @ Sent to the object in control of the Preview Panel just before stopping its control.
1270 @Override
1271 public void endPreviewPanelControl(QLPreviewPanel panel) {
1272 quicklook.didEndQuickLook();
1275 public void setBrowserOutlineView(NSOutlineView view) {
1276 browserOutlineView = view;
1277 // receive drag events from types
1278 browserOutlineView.registerForDraggedTypes(NSArray.arrayWithObjects(
1279 NSPasteboard.URLPboardType,
1280 // Accept files dragged from the Finder for uploading
1281 NSPasteboard.FilenamesPboardType,
1282 // Accept file promises made myself
1283 NSPasteboard.FilesPromisePboardType
1285 // setting appearance attributes()
1286 this._updateBrowserAttributes(browserOutlineView);
1287 // selection properties
1288 browserOutlineView.setAllowsMultipleSelection(true);
1289 browserOutlineView.setAllowsEmptySelection(true);
1290 browserOutlineView.setAllowsColumnResizing(true);
1291 browserOutlineView.setAllowsColumnSelection(false);
1292 browserOutlineView.setAllowsColumnReordering(true);
1294 browserOutlineView.setRowHeight(new CGFloat(layoutManager.defaultLineHeightForFont(
1295 NSFont.systemFontOfSize(preferences.getFloat("browser.font.size"))).intValue() + 2));
1298 NSTableColumn c = browserOutlineColumnsFactory.create(Column.filename.name());
1299 c.headerCell().setStringValue(LocaleFactory.localizedString("Filename"));
1300 c.setMinWidth(new CGFloat(100));
1301 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1302 Column.filename.name())));
1303 c.setMaxWidth(new CGFloat(1000));
1304 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1305 c.setDataCell(outlineCellPrototype);
1306 browserOutlineView.addTableColumn(c);
1307 browserOutlineView.setOutlineTableColumn(c);
1309 browserOutlineView.setDataSource((browserOutlineModel = new BrowserOutlineViewModel(this, cache)).id());
1310 browserOutlineView.setDelegate((browserOutlineViewDelegate = new AbstractBrowserOutlineViewDelegate<Path>(
1311 browserOutlineView.tableColumnWithIdentifier(Column.filename.name())
1313 @Override
1314 public void enterKeyPressed(final ID sender) {
1315 if(preferences.getBoolean("browser.enterkey.rename")) {
1316 if(browserOutlineView.numberOfSelectedRows().intValue() == 1) {
1317 renameFileButtonClicked(sender);
1320 else {
1321 this.tableRowDoubleClicked(sender);
1326 * @see NSOutlineView.Delegate
1328 @Override
1329 public void outlineView_willDisplayCell_forTableColumn_item(NSOutlineView view, NSTextFieldCell cell,
1330 NSTableColumn tableColumn, NSObject item) {
1331 if(null == item) {
1332 return;
1334 final Path path = cache.lookup(new NSObjectPathReference(item));
1335 if(null == path) {
1336 return;
1338 if(tableColumn.identifier().equals(Column.filename.name())) {
1339 cell.setEditable(session.getFeature(Move.class).isSupported(path));
1340 (Rococoa.cast(cell, OutlineCell.class)).setIcon(browserOutlineModel.iconForPath(path));
1342 if(!BrowserController.this.isConnected() || !HIDDEN_FILTER.accept(path)) {
1343 cell.setTextColor(NSColor.disabledControlTextColor());
1345 else {
1346 cell.setTextColor(NSColor.controlTextColor());
1351 * @see NSOutlineView.Delegate
1353 @Override
1354 public boolean outlineView_shouldExpandItem(final NSOutlineView view, final NSObject item) {
1355 NSEvent event = NSApplication.sharedApplication().currentEvent();
1356 if(event != null) {
1357 if(NSEvent.NSLeftMouseDragged == event.type()) {
1358 if(!preferences.getBoolean("browser.view.autoexpand")) {
1359 if(log.isDebugEnabled()) {
1360 log.debug("Returning false to #outlineViewShouldExpandItem while dragging because browser.view.autoexpand == false");
1362 // See tickets #98 and #633
1363 return false;
1365 final NSInteger draggingColumn = view.columnAtPoint(view.convertPoint_fromView(event.locationInWindow(), null));
1366 if(draggingColumn.intValue() != 0) {
1367 if(log.isDebugEnabled()) {
1368 log.debug("Returning false to #outlineViewShouldExpandItem for column:" + draggingColumn);
1370 // See ticket #60
1371 return false;
1375 return true;
1378 @Override
1379 public boolean outlineView_isGroupItem(final NSOutlineView view, final NSObject item) {
1380 return false;
1383 @Override
1384 public void outlineViewItemWillExpand(final NSNotification notification) {
1385 final NSObject object = Rococoa.cast(notification.userInfo(), NSDictionary.class).objectForKey("NSObject");
1386 final NSObjectPathReference reference = new NSObjectPathReference(object);
1387 final Path directory = cache.lookup(reference);
1388 if(null == directory) {
1389 return;
1391 reload(Collections.singleton(directory), getSelectedPaths(), false);
1395 * @see NSOutlineView.Delegate
1397 @Override
1398 public void outlineViewItemDidExpand(final NSNotification notification) {
1402 @Override
1403 public void outlineViewItemWillCollapse(final NSNotification notification) {
1408 * @see NSOutlineView.Delegate
1410 @Override
1411 public void outlineViewItemDidCollapse(final NSNotification notification) {
1412 setStatus();
1415 @Override
1416 protected boolean isTypeSelectSupported() {
1417 return true;
1420 }).id());
1423 public void setBrowserListView(NSTableView view) {
1424 browserListView = view;
1425 // receive drag events from types
1426 browserListView.registerForDraggedTypes(NSArray.arrayWithObjects(
1427 NSPasteboard.URLPboardType,
1428 // Accept files dragged from the Finder for uploading
1429 NSPasteboard.FilenamesPboardType,
1430 // Accept file promises made myself
1431 NSPasteboard.FilesPromisePboardType
1433 // setting appearance attributes()
1434 this._updateBrowserAttributes(browserListView);
1435 // selection properties
1436 browserListView.setAllowsMultipleSelection(true);
1437 browserListView.setAllowsEmptySelection(true);
1438 browserListView.setAllowsColumnResizing(true);
1439 browserListView.setAllowsColumnSelection(false);
1440 browserListView.setAllowsColumnReordering(true);
1442 browserListView.setRowHeight(new CGFloat(layoutManager.defaultLineHeightForFont(
1443 NSFont.systemFontOfSize(preferences.getFloat("browser.font.size"))).intValue() + 2));
1446 NSTableColumn c = browserListColumnsFactory.create(Column.icon.name());
1447 c.headerCell().setStringValue(StringUtils.EMPTY);
1448 c.setMinWidth((20));
1449 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1450 Column.icon.name())));
1451 c.setMaxWidth((20));
1452 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask);
1453 c.setDataCell(imageCellPrototype);
1454 c.dataCell().setAlignment(NSText.NSCenterTextAlignment);
1455 browserListView.addTableColumn(c);
1458 NSTableColumn c = browserListColumnsFactory.create(Column.filename.name());
1459 c.headerCell().setStringValue(LocaleFactory.localizedString("Filename"));
1460 c.setMinWidth((100));
1461 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1462 Column.filename.name())));
1463 c.setMaxWidth((1000));
1464 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1465 c.setDataCell(filenameCellPrototype);
1466 this.browserListView.addTableColumn(c);
1469 browserListView.setDataSource((browserListModel = new BrowserListViewModel(this, cache)).id());
1470 browserListView.setDelegate((browserListViewDelegate = new AbstractBrowserListViewDelegate<Path>(
1471 browserListView.tableColumnWithIdentifier(Column.filename.name())
1473 @Override
1474 public void enterKeyPressed(final ID sender) {
1475 if(preferences.getBoolean("browser.enterkey.rename")) {
1476 if(browserListView.numberOfSelectedRows().intValue() == 1) {
1477 renameFileButtonClicked(sender);
1480 else {
1481 this.tableRowDoubleClicked(sender);
1485 @Override
1486 public void tableView_willDisplayCell_forTableColumn_row(NSTableView view, NSTextFieldCell cell, NSTableColumn tableColumn, NSInteger row) {
1487 final String identifier = tableColumn.identifier();
1488 final Path path = browserListModel.get(BrowserController.this.workdir()).get(row.intValue());
1489 if(identifier.equals(Column.filename.name())) {
1490 cell.setEditable(session.getFeature(Move.class).isSupported(path));
1492 if(cell.isKindOfClass(Foundation.getClass(NSTextFieldCell.class.getSimpleName()))) {
1493 if(!BrowserController.this.isConnected() || !HIDDEN_FILTER.accept(path)) {
1494 cell.setTextColor(NSColor.disabledControlTextColor());
1496 else {
1497 cell.setTextColor(NSColor.controlTextColor());
1502 @Override
1503 protected boolean isTypeSelectSupported() {
1504 return true;
1506 }).id());
1509 protected void _updateBrowserAttributes(NSTableView tableView) {
1510 tableView.setUsesAlternatingRowBackgroundColors(preferences.getBoolean("browser.alternatingRows"));
1511 if(preferences.getBoolean("browser.horizontalLines") && preferences.getBoolean("browser.verticalLines")) {
1512 tableView.setGridStyleMask(new NSUInteger(NSTableView.NSTableViewSolidHorizontalGridLineMask.intValue() | NSTableView.NSTableViewSolidVerticalGridLineMask.intValue()));
1514 else if(preferences.getBoolean("browser.verticalLines")) {
1515 tableView.setGridStyleMask(NSTableView.NSTableViewSolidVerticalGridLineMask);
1517 else if(preferences.getBoolean("browser.horizontalLines")) {
1518 tableView.setGridStyleMask(NSTableView.NSTableViewSolidHorizontalGridLineMask);
1520 else {
1521 tableView.setGridStyleMask(NSTableView.NSTableViewGridNone);
1525 protected void _updateBookmarkCell() {
1526 final int size = preferences.getInteger("bookmark.icon.size");
1527 final double width = size * 1.5;
1528 final NSTableColumn c = bookmarkTable.tableColumnWithIdentifier(BookmarkTableDataSource.Column.icon.name());
1529 c.setMinWidth(width);
1530 c.setMaxWidth(width);
1531 c.setWidth(width);
1532 // Notify the table about the changed row height.
1533 bookmarkTable.noteHeightOfRowsWithIndexesChanged(
1534 NSIndexSet.indexSetWithIndexesInRange(NSRange.NSMakeRange(new NSUInteger(0), new NSUInteger(bookmarkTable.numberOfRows()))));
1537 private void _updateBrowserColumns(final NSTableView table, final AbstractBrowserTableDelegate<Path> delegate) {
1538 table.removeTableColumn(table.tableColumnWithIdentifier(Column.size.name()));
1539 if(preferences.getBoolean(String.format("browser.column.%s", Column.size.name()))) {
1540 NSTableColumn c = browserListColumnsFactory.create(Column.size.name());
1541 c.headerCell().setStringValue(LocaleFactory.localizedString("Size"));
1542 c.setMinWidth(50f);
1543 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1544 Column.size.name())));
1545 c.setMaxWidth(150f);
1546 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1547 c.setDataCell(textCellPrototype);
1548 table.addTableColumn(c);
1550 table.removeTableColumn(table.tableColumnWithIdentifier(Column.modified.name()));
1551 if(preferences.getBoolean(String.format("browser.column.%s", Column.modified.name()))) {
1552 NSTableColumn c = browserListColumnsFactory.create(Column.modified.name());
1553 c.headerCell().setStringValue(LocaleFactory.localizedString("Modified"));
1554 c.setMinWidth(100f);
1555 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1556 Column.modified.name())));
1557 c.setMaxWidth(500);
1558 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1559 c.setDataCell(textCellPrototype);
1560 table.addTableColumn(c);
1562 table.removeTableColumn(table.tableColumnWithIdentifier(Column.owner.name()));
1563 if(preferences.getBoolean(String.format("browser.column.%s", Column.owner.name()))) {
1564 NSTableColumn c = browserListColumnsFactory.create(Column.owner.name());
1565 c.headerCell().setStringValue(LocaleFactory.localizedString("Owner"));
1566 c.setMinWidth(50);
1567 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1568 Column.owner.name())));
1569 c.setMaxWidth(500);
1570 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1571 c.setDataCell(textCellPrototype);
1572 table.addTableColumn(c);
1574 table.removeTableColumn(table.tableColumnWithIdentifier(Column.group.name()));
1575 if(preferences.getBoolean(String.format("browser.column.%s", Column.group.name()))) {
1576 NSTableColumn c = browserListColumnsFactory.create(Column.group.name());
1577 c.headerCell().setStringValue(LocaleFactory.localizedString("Group"));
1578 c.setMinWidth(50);
1579 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1580 Column.group.name())));
1581 c.setMaxWidth(500);
1582 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1583 c.setDataCell(textCellPrototype);
1584 table.addTableColumn(c);
1586 table.removeTableColumn(table.tableColumnWithIdentifier(Column.permission.name()));
1587 if(preferences.getBoolean(String.format("browser.column.%s", Column.permission.name()))) {
1588 NSTableColumn c = browserListColumnsFactory.create(Column.permission.name());
1589 c.headerCell().setStringValue(LocaleFactory.localizedString("Permissions"));
1590 c.setMinWidth(100);
1591 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1592 Column.permission.name())));
1593 c.setMaxWidth(800);
1594 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1595 c.setDataCell(textCellPrototype);
1596 table.addTableColumn(c);
1598 table.removeTableColumn(table.tableColumnWithIdentifier(Column.kind.name()));
1599 if(preferences.getBoolean(String.format("browser.column.%s", Column.kind.name()))) {
1600 NSTableColumn c = browserListColumnsFactory.create(Column.kind.name());
1601 c.headerCell().setStringValue(LocaleFactory.localizedString("Kind"));
1602 c.setMinWidth(50);
1603 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1604 Column.kind.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.extension.name()));
1611 if(preferences.getBoolean(String.format("browser.column.%s", Column.extension.name()))) {
1612 NSTableColumn c = browserListColumnsFactory.create(Column.extension.name());
1613 c.headerCell().setStringValue(LocaleFactory.localizedString("Extension"));
1614 c.setMinWidth(50);
1615 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1616 Column.extension.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.region.name()));
1623 if(preferences.getBoolean(String.format("browser.column.%s", Column.region.name()))) {
1624 NSTableColumn c = browserListColumnsFactory.create(Column.region.name());
1625 c.headerCell().setStringValue(LocaleFactory.localizedString("Region"));
1626 c.setMinWidth(50);
1627 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1628 Column.region.name())));
1629 c.setMaxWidth(500);
1630 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1631 c.setDataCell(textCellPrototype);
1632 table.addTableColumn(c);
1634 table.removeTableColumn(table.tableColumnWithIdentifier(Column.version.name()));
1635 if(preferences.getBoolean(String.format("browser.column.%s", Column.version.name()))) {
1636 NSTableColumn c = browserListColumnsFactory.create(Column.version.name());
1637 c.headerCell().setStringValue(LocaleFactory.localizedString("Version"));
1638 c.setMinWidth(50);
1639 c.setWidth(preferences.getFloat(String.format("browser.column.%s.width",
1640 Column.version.name())));
1641 c.setMaxWidth(500);
1642 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask);
1643 c.setDataCell(textCellPrototype);
1644 table.addTableColumn(c);
1646 NSTableColumn selected = table.tableColumnWithIdentifier(preferences.getProperty("browser.sort.column"));
1647 if(null == selected) {
1648 selected = table.tableColumnWithIdentifier(Column.filename.name());
1650 delegate.setSelectedColumn(selected);
1651 table.setIndicatorImage_inTableColumn(this.getSelectedBrowserDelegate().isSortedAscending() ?
1652 IconCacheFactory.<NSImage>get().iconNamed("NSAscendingSortIndicator") :
1653 IconCacheFactory.<NSImage>get().iconNamed("NSDescendingSortIndicator"),
1654 selected
1656 table.sizeToFit();
1657 table.setAutosaveName("browser.autosave");
1658 table.setAutosaveTableColumns(true);
1659 this.reload();
1662 @Delegate
1663 private BookmarkTableDataSource bookmarkModel;
1665 @Outlet
1666 private NSTableView bookmarkTable;
1668 @Delegate
1669 private AbstractTableDelegate<Host> bookmarkTableDelegate;
1671 public void setBookmarkTable(NSTableView view) {
1672 bookmarkTable = view;
1673 bookmarkTable.setSelectionHighlightStyle(NSTableView.NSTableViewSelectionHighlightStyleSourceList);
1674 bookmarkTable.setDataSource((this.bookmarkModel = new BookmarkTableDataSource(this)).id());
1676 NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkTableDataSource.Column.icon.name());
1677 c.headerCell().setStringValue(StringUtils.EMPTY);
1678 c.setResizingMask(NSTableColumn.NSTableColumnNoResizing);
1679 c.setDataCell(imageCellPrototype);
1680 bookmarkTable.addTableColumn(c);
1683 NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkTableDataSource.Column.bookmark.name());
1684 c.headerCell().setStringValue(LocaleFactory.localizedString("Bookmarks"));
1685 c.setMinWidth(150);
1686 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask);
1687 c.setDataCell(BookmarkCell.bookmarkCell());
1688 bookmarkTable.addTableColumn(c);
1691 NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkTableDataSource.Column.status.name());
1692 c.headerCell().setStringValue(StringUtils.EMPTY);
1693 c.setMinWidth(40);
1694 c.setWidth(40);
1695 c.setMaxWidth(40);
1696 c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask);
1697 c.setDataCell(imageCellPrototype);
1698 c.dataCell().setAlignment(NSText.NSCenterTextAlignment);
1699 bookmarkTable.addTableColumn(c);
1701 bookmarkTable.setDelegate((bookmarkTableDelegate = new AbstractTableDelegate<Host>(
1702 bookmarkTable.tableColumnWithIdentifier(BookmarkTableDataSource.Column.bookmark.name())
1704 @Override
1705 public String tooltip(Host bookmark) {
1706 return new HostUrlProvider().get(bookmark);
1709 @Override
1710 public void tableRowDoubleClicked(final ID sender) {
1711 BrowserController.this.connectBookmarkButtonClicked(sender);
1714 @Override
1715 public void enterKeyPressed(final ID sender) {
1716 this.tableRowDoubleClicked(sender);
1719 @Override
1720 public void deleteKeyPressed(final ID sender) {
1721 if(bookmarkModel.getSource().allowsDelete()) {
1722 BrowserController.this.deleteBookmarkButtonClicked(sender);
1726 @Override
1727 public void tableColumnClicked(NSTableView view, NSTableColumn tableColumn) {
1731 @Override
1732 public void selectionDidChange(NSNotification notification) {
1733 addBookmarkButton.setEnabled(bookmarkModel.getSource().allowsAdd());
1734 final int selected = bookmarkTable.numberOfSelectedRows().intValue();
1735 editBookmarkButton.setEnabled(bookmarkModel.getSource().allowsEdit() && selected == 1);
1736 deleteBookmarkButton.setEnabled(bookmarkModel.getSource().allowsDelete() && selected > 0);
1739 public CGFloat tableView_heightOfRow(NSTableView view, NSInteger row) {
1740 final int size = preferences.getInteger("bookmark.icon.size");
1741 if(BookmarkCell.SMALL_BOOKMARK_SIZE == size) {
1742 return new CGFloat(18);
1744 if(BookmarkCell.MEDIUM_BOOKMARK_SIZE == size) {
1745 return new CGFloat(45);
1747 return new CGFloat(70);
1750 @Override
1751 public boolean isTypeSelectSupported() {
1752 return true;
1755 public String tableView_typeSelectStringForTableColumn_row(NSTableView view,
1756 NSTableColumn tableColumn,
1757 NSInteger row) {
1758 return BookmarkNameProvider.toString(bookmarkModel.getSource().get(row.intValue()));
1761 public boolean tableView_isGroupRow(NSTableView view, NSInteger row) {
1762 return false;
1765 private static final double kSwipeGestureLeft = 1.000000;
1766 private static final double kSwipeGestureRight = -1.000000;
1767 private static final double kSwipeGestureUp = 1.000000;
1768 private static final double kSwipeGestureDown = -1.000000;
1771 * Available in Mac OS X v10.6 and later.
1773 * @param event Swipe event
1775 @Action
1776 public void swipeWithEvent(NSEvent event) {
1777 if(event.deltaY().doubleValue() == kSwipeGestureUp) {
1778 NSInteger row = bookmarkTable.selectedRow();
1779 NSInteger next;
1780 if(-1 == row.intValue()) {
1781 // No current selection
1782 next = new NSInteger(0);
1784 else {
1785 next = new NSInteger(row.longValue() - 1);
1787 bookmarkTable.selectRowIndexes(
1788 NSIndexSet.indexSetWithIndex(next), false);
1790 else if(event.deltaY().doubleValue() == kSwipeGestureDown) {
1791 NSInteger row = bookmarkTable.selectedRow();
1792 NSInteger next;
1793 if(-1 == row.intValue()) {
1794 // No current selection
1795 next = new NSInteger(0);
1797 else {
1798 next = new NSInteger(row.longValue() + 1);
1800 bookmarkTable.selectRowIndexes(
1801 NSIndexSet.indexSetWithIndex(next), false);
1804 }).id());
1805 // receive drag events from types
1806 bookmarkTable.registerForDraggedTypes(NSArray.arrayWithObjects(
1807 NSPasteboard.URLPboardType,
1808 NSPasteboard.StringPboardType,
1809 // Accept bookmark files dragged from the Finder
1810 NSPasteboard.FilenamesPboardType,
1811 // Accept file promises made myself
1812 NSPasteboard.FilesPromisePboardType
1814 this._updateBookmarkCell();
1816 final int size = preferences.getInteger("bookmark.icon.size");
1817 if(BookmarkCell.SMALL_BOOKMARK_SIZE == size) {
1818 bookmarkTable.setRowHeight(new CGFloat(18));
1820 else if(BookmarkCell.MEDIUM_BOOKMARK_SIZE == size) {
1821 bookmarkTable.setRowHeight(new CGFloat(45));
1823 else {
1824 bookmarkTable.setRowHeight(new CGFloat(70));
1827 // setting appearance attributes()
1828 bookmarkTable.setUsesAlternatingRowBackgroundColors(preferences.getBoolean("browser.alternatingRows"));
1829 bookmarkTable.setGridStyleMask(NSTableView.NSTableViewGridNone);
1831 // selection properties
1832 bookmarkTable.setAllowsMultipleSelection(true);
1833 bookmarkTable.setAllowsEmptySelection(true);
1834 bookmarkTable.setAllowsColumnResizing(false);
1835 bookmarkTable.setAllowsColumnSelection(false);
1836 bookmarkTable.setAllowsColumnReordering(false);
1837 bookmarkTable.sizeToFit();
1840 @Outlet
1841 private NSPopUpButton actionPopupButton;
1843 public void setActionPopupButton(NSPopUpButton actionPopupButton) {
1844 this.actionPopupButton = actionPopupButton;
1845 this.actionPopupButton.setPullsDown(true);
1846 this.actionPopupButton.setAutoenablesItems(true);
1849 @Outlet
1850 private NSComboBox quickConnectPopup;
1852 private ProxyController quickConnectPopupModel = new QuickConnectModel();
1854 public void setQuickConnectPopup(NSComboBox quickConnectPopup) {
1855 this.quickConnectPopup = quickConnectPopup;
1856 this.quickConnectPopup.setTarget(this.id());
1857 this.quickConnectPopup.setCompletes(true);
1858 this.quickConnectPopup.setAction(Foundation.selector("quickConnectSelectionChanged:"));
1859 // Make sure action is not sent twice.
1860 this.quickConnectPopup.cell().setSendsActionOnEndEditing(false);
1861 this.quickConnectPopup.setUsesDataSource(true);
1862 this.quickConnectPopup.setDataSource(quickConnectPopupModel.id());
1863 NSNotificationCenter.defaultCenter().addObserver(this.id(),
1864 Foundation.selector("quickConnectWillPopUp:"),
1865 NSComboBox.ComboBoxWillPopUpNotification,
1866 this.quickConnectPopup);
1867 this.quickConnectWillPopUp(null);
1870 private class QuickConnectModel extends ProxyController implements NSComboBox.DataSource {
1871 @Override
1872 public NSInteger numberOfItemsInComboBox(final NSComboBox combo) {
1873 return new NSInteger(bookmarks.size());
1876 @Override
1877 public NSObject comboBox_objectValueForItemAtIndex(final NSComboBox sender, final NSInteger row) {
1878 return NSString.stringWithString(
1879 BookmarkNameProvider.toString(bookmarks.get(row.intValue()))
1884 public void quickConnectWillPopUp(NSNotification notification) {
1885 int size = bookmarks.size();
1886 quickConnectPopup.setNumberOfVisibleItems(size > 10 ? new NSInteger(10) : new NSInteger(size));
1889 @Action
1890 public void quickConnectSelectionChanged(final ID sender) {
1891 String input = quickConnectPopup.stringValue();
1892 if(StringUtils.isBlank(input)) {
1893 return;
1895 input = input.trim();
1896 // First look for equivalent bookmarks
1897 for(Host h : bookmarks) {
1898 if(BookmarkNameProvider.toString(h).equals(input)) {
1899 this.mount(h);
1900 return;
1903 // Try to parse the input as a URL and extract protocol, hostname, username and password if any.
1904 this.mount(HostParser.parse(input));
1907 @Outlet
1908 private NSTextField searchField;
1910 public void setSearchField(NSTextField searchField) {
1911 this.searchField = searchField;
1912 NSNotificationCenter.defaultCenter().addObserver(this.id(),
1913 Foundation.selector("searchFieldTextDidChange:"),
1914 NSControl.NSControlTextDidChangeNotification,
1915 this.searchField);
1918 @Action
1919 public void searchButtonClicked(final ID sender) {
1920 this.window().makeFirstResponder(searchField);
1923 public void searchFieldTextDidChange(NSNotification notification) {
1924 if(this.getSelectedTabView() == TAB_BOOKMARKS) {
1925 this.setBookmarkFilter(searchField.stringValue());
1927 else { // TAB_LIST_VIEW || TAB_OUTLINE_VIEW
1928 this.setPathFilter(searchField.stringValue());
1929 this.reload();
1933 private void setBookmarkFilter(final String searchString) {
1934 if(StringUtils.isBlank(searchString)) {
1935 searchField.setStringValue(StringUtils.EMPTY);
1936 bookmarkModel.setFilter(null);
1938 else {
1939 bookmarkModel.setFilter(new HostFilter() {
1940 @Override
1941 public boolean accept(Host host) {
1942 return StringUtils.lowerCase(BookmarkNameProvider.toString(host)).contains(searchString.toLowerCase(Locale.ROOT))
1943 || ((null != host.getComment()) && StringUtils.lowerCase(host.getComment()).contains(searchString.toLowerCase(Locale.ROOT)))
1944 || ((null != host.getCredentials().getUsername()) && StringUtils.lowerCase(host.getCredentials().getUsername()).contains(searchString.toLowerCase(Locale.ROOT)))
1945 || StringUtils.lowerCase(host.getHostname()).contains(searchString.toLowerCase(Locale.ROOT));
1949 this.reloadBookmarks();
1952 // ----------------------------------------------------------
1953 // Manage Bookmarks
1954 // ----------------------------------------------------------
1956 @Action
1957 public void connectBookmarkButtonClicked(final ID sender) {
1958 if(bookmarkTable.numberOfSelectedRows().intValue() == 1) {
1959 final Host selected = bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue());
1960 this.mount(selected);
1964 @Outlet
1965 private NSButton editBookmarkButton;
1967 public void setEditBookmarkButton(NSButton editBookmarkButton) {
1968 this.editBookmarkButton = editBookmarkButton;
1969 this.editBookmarkButton.setEnabled(false);
1970 this.editBookmarkButton.setTarget(this.id());
1971 this.editBookmarkButton.setAction(Foundation.selector("editBookmarkButtonClicked:"));
1974 @Action
1975 public void editBookmarkButtonClicked(final ID sender) {
1976 final BookmarkController c = BookmarkControllerFactory.create(
1977 bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue())
1979 c.window().makeKeyAndOrderFront(null);
1982 @Action
1983 public void duplicateBookmarkButtonClicked(final ID sender) {
1984 final Host selected = bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue());
1985 this.selectBookmarks();
1986 final Host duplicate = new HostDictionary().deserialize(selected.serialize(SerializerFactory.get()));
1987 // Make sure a new UUID is asssigned for duplicate
1988 duplicate.setUuid(null);
1989 this.addBookmark(duplicate);
1992 @Outlet
1993 private NSButton addBookmarkButton;
1995 public void setAddBookmarkButton(NSButton addBookmarkButton) {
1996 this.addBookmarkButton = addBookmarkButton;
1997 this.addBookmarkButton.setTarget(this.id());
1998 this.addBookmarkButton.setAction(Foundation.selector("addBookmarkButtonClicked:"));
2001 @Action
2002 public void addBookmarkButtonClicked(final ID sender) {
2003 final Host bookmark;
2004 if(this.isMounted()) {
2005 Path selected = this.getSelectedPath();
2006 if(null == selected || !selected.isDirectory()) {
2007 selected = this.workdir();
2009 bookmark = new HostDictionary().deserialize(session.getHost().serialize(SerializerFactory.get()));
2010 // Make sure a new UUID is asssigned for duplicate
2011 bookmark.setUuid(null);
2012 bookmark.setDefaultPath(selected.getAbsolute());
2014 else {
2015 bookmark = new Host(ProtocolFactory.forName(preferences.getProperty("connection.protocol.default")),
2016 preferences.getProperty("connection.hostname.default"),
2017 preferences.getInteger("connection.port.default"));
2019 this.selectBookmarks();
2020 this.addBookmark(bookmark);
2023 public void addBookmark(Host item) {
2024 bookmarkModel.setFilter(null);
2025 bookmarkModel.getSource().add(item);
2026 final int row = bookmarkModel.getSource().lastIndexOf(item);
2027 final NSInteger index = new NSInteger(row);
2028 bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(index), false);
2029 bookmarkTable.scrollRowToVisible(index);
2030 final BookmarkController c = BookmarkControllerFactory.create(item);
2031 c.window().makeKeyAndOrderFront(null);
2034 @Outlet
2035 private NSButton deleteBookmarkButton;
2037 public void setDeleteBookmarkButton(NSButton deleteBookmarkButton) {
2038 this.deleteBookmarkButton = deleteBookmarkButton;
2039 this.deleteBookmarkButton.setEnabled(false);
2040 this.deleteBookmarkButton.setTarget(this.id());
2041 this.deleteBookmarkButton.setAction(Foundation.selector("deleteBookmarkButtonClicked:"));
2044 @Action
2045 public void deleteBookmarkButtonClicked(final ID sender) {
2046 NSIndexSet iterator = bookmarkTable.selectedRowIndexes();
2047 final List<Host> selected = new ArrayList<Host>();
2048 for(NSUInteger index = iterator.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = iterator.indexGreaterThanIndex(index)) {
2049 selected.add(bookmarkModel.getSource().get(index.intValue()));
2051 StringBuilder alertText = new StringBuilder(
2052 LocaleFactory.localizedString("Do you want to delete the selected bookmark?"));
2053 int i = 0;
2054 Iterator<Host> iter = selected.iterator();
2055 while(i < 10 && iter.hasNext()) {
2056 alertText.append("\n").append(Character.toString('\u2022')).append(" ").append(
2057 BookmarkNameProvider.toString(iter.next())
2059 i++;
2061 if(iter.hasNext()) {
2062 alertText.append("\n").append(Character.toString('\u2022')).append(" " + "…");
2064 final NSAlert alert = NSAlert.alert(LocaleFactory.localizedString("Delete Bookmark"),
2065 alertText.toString(),
2066 LocaleFactory.localizedString("Delete"),
2067 LocaleFactory.localizedString("Cancel"),
2068 null);
2069 this.alert(alert, new SheetCallback() {
2070 @Override
2071 public void callback(int returncode) {
2072 if(returncode == DEFAULT_OPTION) {
2073 bookmarkTable.deselectAll(null);
2074 bookmarkModel.getSource().removeAll(selected);
2080 // ----------------------------------------------------------
2081 // Browser navigation
2082 // ----------------------------------------------------------
2084 private static final int NAVIGATION_LEFT_SEGMENT_BUTTON = 0;
2085 private static final int NAVIGATION_RIGHT_SEGMENT_BUTTON = 1;
2087 private static final int NAVIGATION_UP_SEGMENT_BUTTON = 0;
2089 private NSSegmentedControl navigationButton;
2091 public void setNavigationButton(NSSegmentedControl navigationButton) {
2092 this.navigationButton = navigationButton;
2093 this.navigationButton.setTarget(this.id());
2094 this.navigationButton.setAction(Foundation.selector("navigationButtonClicked:"));
2095 this.navigationButton.setImage_forSegment(IconCacheFactory.<NSImage>get().iconNamed("nav-backward.tiff"),
2096 NAVIGATION_LEFT_SEGMENT_BUTTON);
2097 this.navigationButton.setImage_forSegment(IconCacheFactory.<NSImage>get().iconNamed("nav-forward.tiff"),
2098 NAVIGATION_RIGHT_SEGMENT_BUTTON);
2101 @Action
2102 public void navigationButtonClicked(NSSegmentedControl sender) {
2103 switch(sender.selectedSegment()) {
2104 case NAVIGATION_LEFT_SEGMENT_BUTTON: {
2105 this.backButtonClicked(sender.id());
2106 break;
2108 case NAVIGATION_RIGHT_SEGMENT_BUTTON: {
2109 this.forwardButtonClicked(sender.id());
2110 break;
2115 @Action
2116 public void backButtonClicked(final ID sender) {
2117 final Path selected = navigation.back();
2118 if(selected != null) {
2119 final Path previous = this.workdir();
2120 if(previous.getParent().equals(selected)) {
2121 this.setWorkdir(selected, previous);
2123 else {
2124 this.setWorkdir(selected);
2129 @Action
2130 public void forwardButtonClicked(final ID sender) {
2131 final Path selected = navigation.forward();
2132 if(selected != null) {
2133 this.setWorkdir(selected);
2137 @Outlet
2138 private NSSegmentedControl upButton;
2140 public void setUpButton(NSSegmentedControl upButton) {
2141 this.upButton = upButton;
2142 this.upButton.setTarget(this.id());
2143 this.upButton.setAction(Foundation.selector("upButtonClicked:"));
2144 this.upButton.setImage_forSegment(IconCacheFactory.<NSImage>get().iconNamed("nav-up.tiff"),
2145 NAVIGATION_UP_SEGMENT_BUTTON);
2148 public void upButtonClicked(final ID sender) {
2149 final Path previous = this.workdir();
2150 this.setWorkdir(previous.getParent(), previous);
2153 private Path workdir;
2155 @Outlet
2156 private NSPopUpButton pathPopupButton;
2158 public void setPathPopup(NSPopUpButton pathPopupButton) {
2159 this.pathPopupButton = pathPopupButton;
2160 this.pathPopupButton.setTarget(this.id());
2161 this.pathPopupButton.setAction(Foundation.selector("pathPopupSelectionChanged:"));
2164 @Action
2165 public void pathPopupSelectionChanged(final NSPopUpButton sender) {
2166 final String selected = sender.selectedItem().representedObject();
2167 if(selected != null) {
2168 final Path workdir = this.workdir();
2169 Path p = workdir;
2170 while(!p.getAbsolute().equals(selected)) {
2171 p = p.getParent();
2173 this.setWorkdir(p);
2174 if(workdir.getParent().equals(p)) {
2175 this.setWorkdir(p, workdir);
2177 else {
2178 this.setWorkdir(p);
2183 @Outlet
2184 private NSPopUpButton encodingPopup;
2186 public void setEncodingPopup(NSPopUpButton encodingPopup) {
2187 this.encodingPopup = encodingPopup;
2188 this.encodingPopup.setTarget(this.id());
2189 this.encodingPopup.setAction(Foundation.selector("encodingButtonClicked:"));
2190 this.encodingPopup.removeAllItems();
2191 this.encodingPopup.addItemsWithTitles(NSArray.arrayWithObjects(new DefaultCharsetProvider().availableCharsets()));
2192 this.encodingPopup.selectItemWithTitle(preferences.getProperty("browser.charset.encoding"));
2195 @Action
2196 public void encodingButtonClicked(final NSPopUpButton sender) {
2197 this.encodingChanged(sender.titleOfSelectedItem());
2200 @Action
2201 public void encodingMenuClicked(final NSMenuItem sender) {
2202 this.encodingChanged(sender.title());
2205 public void encodingChanged(final String encoding) {
2206 if(null == encoding) {
2207 return;
2209 this.setEncoding(encoding);
2210 if(this.isMounted()) {
2211 if(session.getEncoding().equals(encoding)) {
2212 return;
2214 session.getHost().setEncoding(encoding);
2215 this.mount(session.getHost());
2220 * @param encoding Character encoding
2222 private void setEncoding(final String encoding) {
2223 this.encodingPopup.selectItemWithTitle(encoding);
2226 // ----------------------------------------------------------
2227 // Drawers
2228 // ----------------------------------------------------------
2230 @Action
2231 public void toggleLogDrawer(final ID sender) {
2232 this.logDrawer.toggle(this.id());
2235 // ----------------------------------------------------------
2236 // Status
2237 // ----------------------------------------------------------
2239 @Outlet
2240 protected NSProgressIndicator statusSpinner;
2242 public void setStatusSpinner(NSProgressIndicator statusSpinner) {
2243 this.statusSpinner = statusSpinner;
2244 this.statusSpinner.setDisplayedWhenStopped(false);
2245 this.statusSpinner.setIndeterminate(true);
2248 @Outlet
2249 protected NSProgressIndicator browserSpinner;
2251 public void setBrowserSpinner(NSProgressIndicator browserSpinner) {
2252 this.browserSpinner = browserSpinner;
2255 public NSProgressIndicator getBrowserSpinner() {
2256 return browserSpinner;
2259 @Outlet
2260 private NSTextField statusLabel;
2262 public void setStatusLabel(NSTextField statusLabel) {
2263 this.statusLabel = statusLabel;
2266 public void setStatus() {
2267 final BackgroundAction current = this.getActions().getCurrent();
2268 this.message(null != current ? current.getActivity() : null);
2271 @Override
2272 public void stop(final BackgroundAction action) {
2273 statusSpinner.stopAnimation(null);
2276 @Override
2277 public void start(final BackgroundAction action) {
2278 statusSpinner.startAnimation(null);
2282 * @param label Status message
2284 @Override
2285 public void message(final String label) {
2286 if(StringUtils.isNotBlank(label)) {
2287 // Update the status label at the bottom of the browser window
2288 statusLabel.setAttributedStringValue(NSAttributedString.attributedStringWithAttributes(label,
2289 TRUNCATE_MIDDLE_ATTRIBUTES));
2291 else {
2292 if(getSelectedTabView() == TAB_BOOKMARKS) {
2293 statusLabel.setAttributedStringValue(
2294 NSAttributedString.attributedStringWithAttributes(String.format("%s %s", bookmarkTable.numberOfRows(),
2295 LocaleFactory.localizedString("Bookmarks")),
2296 TRUNCATE_MIDDLE_ATTRIBUTES
2300 else {
2301 // Browser view
2302 if(isConnected()) {
2303 statusLabel.setAttributedStringValue(
2304 NSAttributedString.attributedStringWithAttributes(MessageFormat.format(LocaleFactory.localizedString("{0} Files"),
2305 String.valueOf(getSelectedBrowserView().numberOfRows())),
2306 TRUNCATE_MIDDLE_ATTRIBUTES
2310 else {
2311 statusLabel.setStringValue(StringUtils.EMPTY);
2317 @Override
2318 public void log(final boolean request, final String message) {
2319 transcript.log(request, message);
2322 @Outlet
2323 private NSButton securityLabel;
2325 public void setSecurityLabel(NSButton securityLabel) {
2326 this.securityLabel = securityLabel;
2327 this.securityLabel.setEnabled(false);
2328 this.securityLabel.setTarget(this.id());
2329 this.securityLabel.setAction(Foundation.selector("securityLabelClicked:"));
2332 @Action
2333 public void securityLabelClicked(final ID sender) {
2334 if(session instanceof SSLSession) {
2335 final SSLSession<?> secured = (SSLSession) session;
2336 final List<X509Certificate> certificates = secured.getAcceptedIssuers();
2337 try {
2338 CertificateStoreFactory.get().display(certificates);
2340 catch(CertificateException e) {
2341 log.warn(String.format("Failure decoding certificate %s", e.getMessage()));
2346 // ----------------------------------------------------------
2347 // Selector methods for the toolbar items
2348 // ----------------------------------------------------------
2350 public void quicklookButtonClicked(final ID sender) {
2351 if(quicklook.isOpen()) {
2352 quicklook.close();
2354 else {
2355 this.updateQuickLookSelection(this.getSelectedPaths());
2360 * Marks all expanded directories as invalid and tells the
2361 * browser table to reload its data
2363 * @param sender Toolbar button
2365 @Action
2366 public void reloadButtonClicked(final ID sender) {
2367 if(this.isMounted()) {
2368 final Set<Path> folders = new HashSet<Path>();
2369 switch(browserSwitchView.selectedSegment()) {
2370 case SWITCH_OUTLINE_VIEW: {
2371 for(int i = 0; i < browserOutlineView.numberOfRows().intValue(); i++) {
2372 final NSObject item = browserOutlineView.itemAtRow(new NSInteger(i));
2373 if(browserOutlineView.isItemExpanded(item)) {
2374 final Path folder = cache.lookup(new NSObjectPathReference(item));
2375 if(null == folder) {
2376 continue;
2378 folders.add(folder);
2381 break;
2384 folders.add(workdir);
2385 this.reload(folders, this.getSelectedPaths(), true);
2390 * Open a new browser with the current selected folder as the working directory
2392 * @param sender Toolbar button
2394 @Action
2395 public void newBrowserButtonClicked(final ID sender) {
2396 Path selected = this.getSelectedPath();
2397 if(null == selected || !selected.isDirectory()) {
2398 selected = this.workdir();
2400 BrowserController c = MainController.newDocument(true);
2401 final Host host = new HostDictionary().deserialize(session.getHost().serialize(SerializerFactory.get()));
2402 host.setDefaultPath(selected.getAbsolute());
2403 c.mount(host);
2407 * @param source The original file to duplicate
2408 * @param destination The destination of the duplicated file
2410 protected void duplicatePath(final Path source, final Path destination) {
2411 this.duplicatePaths(Collections.singletonMap(source, destination));
2415 * @param selected A map with the original files as the key and the destination
2416 * files as the value
2418 protected void duplicatePaths(final Map<Path, Path> selected) {
2419 this.checkOverwrite(new ArrayList<Path>(selected.values()), new DefaultMainAction() {
2420 @Override
2421 public void run() {
2422 transfer(new CopyTransfer(session.getHost(), session.getHost(), selected),
2423 new ArrayList<Path>(selected.values()), true);
2429 * @param path The existing file
2430 * @param renamed The renamed file
2432 protected void renamePath(final Path path, final Path renamed) {
2433 this.renamePaths(Collections.singletonMap(path, renamed));
2437 * @param selected A map with the original files as the key and the destination
2438 * files as the value
2440 protected void renamePaths(final Map<Path, Path> selected) {
2441 this.checkMove(selected, new DefaultMainAction() {
2442 @Override
2443 public void run() {
2444 final ArrayList<Path> changed = new ArrayList<Path>();
2445 changed.addAll(selected.keySet());
2446 changed.addAll(selected.values());
2447 background(new WorkerBackgroundAction<Boolean>(BrowserController.this, session, cache,
2448 new MoveWorker(session, selected, BrowserController.this) {
2449 @Override
2450 public void cleanup(final Boolean result) {
2451 reload(changed, new ArrayList<Path>(selected.values()));
2461 * Displays a warning dialog about already existing files
2463 * @param selected The files to check
2465 private void checkOverwrite(final List<Path> selected, final MainAction action) {
2466 StringBuilder alertText = new StringBuilder(
2467 LocaleFactory.localizedString("A file with the same name already exists. Do you want to replace the existing file?"));
2468 int i = 0;
2469 Iterator<Path> iter;
2470 boolean shouldWarn = false;
2471 for(iter = selected.iterator(); iter.hasNext(); ) {
2472 final Path item = iter.next();
2473 if(cache.get(item.getParent()).contains(item)) {
2474 if(i < 10) {
2475 alertText.append("\n").append(Character.toString('\u2022')).append(" ").append(item.getName());
2477 shouldWarn = true;
2479 i++;
2481 if(i >= 10) {
2482 alertText.append("\n").append(Character.toString('\u2022')).append(" ...)");
2484 if(shouldWarn) {
2485 NSAlert alert = NSAlert.alert(
2486 LocaleFactory.localizedString("Overwrite"), //title
2487 alertText.toString(),
2488 LocaleFactory.localizedString("Overwrite"), // defaultbutton
2489 LocaleFactory.localizedString("Cancel"), //alternative button
2490 null //other button
2492 this.alert(alert, new SheetCallback() {
2493 @Override
2494 public void callback(final int returncode) {
2495 if(returncode == DEFAULT_OPTION) {
2496 action.run();
2501 else {
2502 action.run();
2507 * Displays a warning dialog about files to be moved
2509 * @param selected The files to check for existence
2511 private void checkMove(final Map<Path, Path> selected, final MainAction action) {
2512 if(preferences.getBoolean("browser.move.confirm")) {
2513 StringBuilder alertText = new StringBuilder(
2514 LocaleFactory.localizedString("Do you want to move the selected files?"));
2515 int i = 0;
2516 boolean rename = false;
2517 Iterator<Map.Entry<Path, Path>> iter;
2518 for(iter = selected.entrySet().iterator(); i < 10 && iter.hasNext(); ) {
2519 final Map.Entry<Path, Path> next = iter.next();
2520 if(next.getKey().getParent().equals(next.getValue().getParent())) {
2521 rename = true;
2523 alertText.append(String.format("\n%s %s", Character.toString('\u2022'), next.getKey().getName()));
2524 i++;
2526 if(iter.hasNext()) {
2527 alertText.append(String.format("\n%s ...)", Character.toString('\u2022')));
2529 final NSAlert alert = NSAlert.alert(
2530 rename ? LocaleFactory.localizedString("Rename") : LocaleFactory.localizedString("Move"), //title
2531 alertText.toString(),
2532 rename ? LocaleFactory.localizedString("Rename") : LocaleFactory.localizedString("Move"), // default button
2533 LocaleFactory.localizedString("Cancel"), //alternative button
2534 null //other button
2536 alert.setShowsSuppressionButton(true);
2537 alert.suppressionButton().setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
2538 this.alert(alert, new SheetCallback() {
2539 @Override
2540 public void callback(final int returncode) {
2541 if(alert.suppressionButton().state() == NSCell.NSOnState) {
2542 // Never show again.
2543 preferences.setProperty("browser.move.confirm", false);
2545 if(returncode == DEFAULT_OPTION) {
2546 checkOverwrite(new ArrayList<Path>(selected.values()), action);
2551 else {
2552 this.checkOverwrite(new ArrayList<Path>(selected.values()), action);
2557 * Recursively deletes the file
2559 * @param file File or directory
2561 public void deletePath(final Path file) {
2562 this.deletePaths(Collections.singletonList(file));
2566 * Recursively deletes the files
2568 * @param selected The files selected in the browser to delete
2570 public void deletePaths(final List<Path> selected) {
2571 final List<Path> normalized = PathNormalizer.normalize(selected);
2572 if(normalized.isEmpty()) {
2573 return;
2575 StringBuilder alertText =
2576 new StringBuilder(LocaleFactory.localizedString("Really delete the following files? This cannot be undone."));
2577 int i = 0;
2578 Iterator<Path> iter;
2579 for(iter = normalized.iterator(); i < 10 && iter.hasNext(); ) {
2580 alertText.append("\n").append(Character.toString('\u2022')).append(" ").append(iter.next().getName());
2581 i++;
2583 if(iter.hasNext()) {
2584 alertText.append("\n").append(Character.toString('\u2022')).append(" " + "…");
2586 NSAlert alert = NSAlert.alert(LocaleFactory.localizedString("Delete"), //title
2587 alertText.toString(),
2588 LocaleFactory.localizedString("Delete"), // defaultbutton
2589 LocaleFactory.localizedString("Cancel"), //alternative button
2590 null //other button
2592 this.alert(alert, new SheetCallback() {
2593 @Override
2594 public void callback(final int returncode) {
2595 if(returncode == DEFAULT_OPTION) {
2596 BrowserController.this.deletePathsImpl(normalized);
2602 private void deletePathsImpl(final List<Path> files) {
2603 this.background(new WorkerBackgroundAction<Boolean>(this, session, cache,
2604 new DeleteWorker(session, LoginCallbackFactory.get(BrowserController.this), files, this) {
2605 @Override
2606 public void cleanup(final Boolean result) {
2607 reload(files, Collections.<Path>emptyList());
2614 public void revertPaths(final List<Path> files) {
2615 this.background(new WorkerBackgroundAction<Boolean>(this, session, cache,
2616 new RevertWorker(session, files) {
2617 @Override
2618 public void cleanup(final Boolean result) {
2619 reload(files, files);
2626 * @param selected File
2627 * @return True if the selected path is editable (not a directory and no known binary file)
2629 protected boolean isEditable(final Path selected) {
2630 if(this.isMounted()) {
2631 if(session.getHost().getCredentials().isAnonymousLogin()) {
2632 return false;
2634 return selected.isFile();
2636 return false;
2639 @Action
2640 public void gotoButtonClicked(final ID sender) {
2641 SheetController sheet = new GotoController(this, cache);
2642 sheet.beginSheet();
2645 @Action
2646 public void createFileButtonClicked(final ID sender) {
2647 SheetController sheet = new CreateFileController(this, cache);
2648 sheet.beginSheet();
2651 @Action
2652 public void createSymlinkButtonClicked(final ID sender) {
2653 SheetController sheet = new CreateSymlinkController(this, cache);
2654 sheet.beginSheet();
2657 @Action
2658 public void duplicateFileButtonClicked(final ID sender) {
2659 SheetController sheet = new DuplicateFileController(this, cache);
2660 sheet.beginSheet();
2663 @Action
2664 public void createFolderButtonClicked(final ID sender) {
2665 final Location feature = session.getFeature(Location.class);
2666 SheetController sheet = new FolderController(this, cache,
2667 feature != null ? feature.getLocations() : Collections.<Location.Name>emptySet());
2668 sheet.beginSheet();
2671 @Action
2672 public void renameFileButtonClicked(final ID sender) {
2673 final NSTableView browser = this.getSelectedBrowserView();
2674 browser.editRow(browser.columnWithIdentifier(Column.filename.name()),
2675 browser.selectedRow(), true);
2676 final Path selected = this.getSelectedPath();
2677 if(StringUtils.isNotBlank(selected.getExtension())) {
2678 NSText view = browser.currentEditor();
2679 int index = selected.getName().indexOf(selected.getExtension()) - 1;
2680 if(index > 0) {
2681 view.setSelectedRange(NSRange.NSMakeRange(new NSUInteger(0), new NSUInteger(index)));
2686 @Action
2687 public void sendCustomCommandClicked(final ID sender) {
2688 SheetController sheet = new CommandController(this, session);
2689 sheet.beginSheet();
2692 @Action
2693 public void editMenuClicked(final NSMenuItem sender) {
2694 final EditorFactory factory = EditorFactory.instance();
2695 for(Path selected : this.getSelectedPaths()) {
2696 final Editor editor = factory.create(this, session,
2697 new Application(sender.representedObject()), selected);
2698 this.edit(editor);
2702 @Action
2703 public void editButtonClicked(final ID sender) {
2704 final EditorFactory factory = EditorFactory.instance();
2705 for(Path selected : this.getSelectedPaths()) {
2706 this.edit(selected);
2710 protected void edit(final Path file) {
2711 this.edit(EditorFactory.instance().create(this, session, file));
2714 protected void edit(final Editor editor) {
2715 editors.add(editor);
2716 this.background(new WorkerBackgroundAction<Transfer>(this, session, editor.open(new ApplicationQuitCallback() {
2717 @Override
2718 public void callback() {
2719 editors.remove(editor);
2721 }, new DisabledTransferErrorCallback(), new DefaultEditorListener(this, session, editor))));
2724 @Action
2725 public void openBrowserButtonClicked(final ID sender) {
2726 final DescriptiveUrlBag list;
2727 if(this.getSelectionCount() == 1) {
2728 list = session.getFeature(UrlProvider.class).toUrl(this.getSelectedPath());
2730 else {
2731 list = session.getFeature(UrlProvider.class).toUrl(this.workdir());
2733 if(!list.isEmpty()) {
2734 BrowserLauncherFactory.get().open(list.find(DescriptiveUrl.Type.http).getUrl());
2738 @Action
2739 public void infoButtonClicked(final ID sender) {
2740 if(this.getSelectionCount() > 0) {
2741 InfoController c = InfoControllerFactory.create(this, this.getSelectedPaths());
2742 c.window().makeKeyAndOrderFront(null);
2746 @Action
2747 public void revertFileButtonClicked(final ID sender) {
2748 this.revertPaths(this.getSelectedPaths());
2751 @Action
2752 public void deleteFileButtonClicked(final ID sender) {
2753 this.deletePaths(this.getSelectedPaths());
2756 private NSOpenPanel downloadToPanel;
2758 @Action
2759 public void downloadToButtonClicked(final ID sender) {
2760 downloadToPanel = NSOpenPanel.openPanel();
2761 downloadToPanel.setCanChooseDirectories(true);
2762 downloadToPanel.setCanCreateDirectories(true);
2763 downloadToPanel.setCanChooseFiles(false);
2764 downloadToPanel.setAllowsMultipleSelection(false);
2765 downloadToPanel.setPrompt(LocaleFactory.localizedString("Choose"));
2766 downloadToPanel.beginSheetForDirectory(new DownloadDirectoryFinder().find(session.getHost()).getAbsolute(),
2767 null, this.window, this.id(),
2768 Foundation.selector("downloadToPanelDidEnd:returnCode:contextInfo:"), null);
2771 public void downloadToPanelDidEnd_returnCode_contextInfo(final NSOpenPanel sheet, final int returncode, final ID contextInfo) {
2772 sheet.orderOut(this.id());
2773 if(returncode == SheetCallback.DEFAULT_OPTION) {
2774 if(sheet.filename() != null) {
2775 final Local target = LocalFactory.get(sheet.filename());
2776 new DownloadDirectoryFinder().save(session.getHost(), target);
2777 final List<TransferItem> downloads = new ArrayList<TransferItem>();
2778 for(Path file : this.getSelectedPaths()) {
2779 downloads.add(new TransferItem(file, LocalFactory.get(target, file.getName())));
2781 this.transfer(new DownloadTransfer(session.getHost(), downloads), Collections.<Path>emptyList());
2784 downloadToPanel = null;
2787 @Outlet
2788 private NSSavePanel downloadAsPanel;
2790 @Action
2791 public void downloadAsButtonClicked(final ID sender) {
2792 downloadAsPanel = NSSavePanel.savePanel();
2793 downloadAsPanel.setMessage(LocaleFactory.localizedString("Download the selected file to…"));
2794 downloadAsPanel.setNameFieldLabel(LocaleFactory.localizedString("Download As:"));
2795 downloadAsPanel.setPrompt(LocaleFactory.localizedString("Download", "Transfer"));
2796 downloadAsPanel.setCanCreateDirectories(true);
2797 downloadAsPanel.beginSheetForDirectory(new DownloadDirectoryFinder().find(session.getHost()).getAbsolute(),
2798 this.getSelectedPath().getName(), this.window, this.id(),
2799 Foundation.selector("downloadAsPanelDidEnd:returnCode:contextInfo:"), null);
2802 public void downloadAsPanelDidEnd_returnCode_contextInfo(final NSSavePanel sheet, final int returncode, final ID contextInfo) {
2803 sheet.orderOut(this.id());
2804 if(returncode == SheetCallback.DEFAULT_OPTION) {
2805 if(sheet.filename() != null) {
2806 final Local target = LocalFactory.get(sheet.filename());
2807 new DownloadDirectoryFinder().save(session.getHost(), target.getParent());
2808 final List<TransferItem> downloads
2809 = Collections.singletonList(new TransferItem(this.getSelectedPath(), target));
2810 this.transfer(new DownloadTransfer(session.getHost(), downloads), Collections.<Path>emptyList());
2815 @Outlet
2816 private NSOpenPanel syncPanel;
2818 @Action
2819 public void syncButtonClicked(final ID sender) {
2820 final Path selection;
2821 if(this.getSelectionCount() == 1 &&
2822 this.getSelectedPath().isDirectory()) {
2823 selection = this.getSelectedPath();
2825 else {
2826 selection = this.workdir();
2828 syncPanel = NSOpenPanel.openPanel();
2829 syncPanel.setCanChooseDirectories(selection.isDirectory());
2830 syncPanel.setTreatsFilePackagesAsDirectories(true);
2831 syncPanel.setCanChooseFiles(selection.isFile());
2832 syncPanel.setCanCreateDirectories(true);
2833 syncPanel.setAllowsMultipleSelection(false);
2834 syncPanel.setMessage(MessageFormat.format(LocaleFactory.localizedString("Synchronize {0} with"),
2835 selection.getName()));
2836 syncPanel.setPrompt(LocaleFactory.localizedString("Choose"));
2837 syncPanel.beginSheetForDirectory(new UploadDirectoryFinder().find(session.getHost()).getAbsolute(),
2838 null, this.window, this.id(),
2839 Foundation.selector("syncPanelDidEnd:returnCode:contextInfo:"), null //context info
2843 public void syncPanelDidEnd_returnCode_contextInfo(final NSOpenPanel sheet, final int returncode, final ID contextInfo) {
2844 sheet.orderOut(this.id());
2845 if(returncode == SheetCallback.DEFAULT_OPTION) {
2846 if(sheet.filename() != null) {
2847 final Local target = LocalFactory.get(sheet.filename());
2848 new UploadDirectoryFinder().save(session.getHost(), target.getParent());
2849 final Path selected;
2850 if(this.getSelectionCount() == 1 && this.getSelectedPath().isDirectory()) {
2851 selected = this.getSelectedPath();
2853 else {
2854 selected = this.workdir();
2856 this.transfer(new SyncTransfer(session.getHost(), new TransferItem(selected, target)));
2861 @Action
2862 public void downloadButtonClicked(final ID sender) {
2863 final List<TransferItem> downloads = new ArrayList<TransferItem>();
2864 final Local folder = new DownloadDirectoryFinder().find(session.getHost());
2865 for(Path file : this.getSelectedPaths()) {
2866 downloads.add(new TransferItem(
2867 file, LocalFactory.get(folder, file.getName())));
2869 this.transfer(new DownloadTransfer(session.getHost(), downloads), Collections.<Path>emptyList());
2872 private NSOpenPanel uploadPanel;
2874 private NSButton uploadPanelHiddenFilesCheckbox;
2876 @Action
2877 public void uploadButtonClicked(final ID sender) {
2878 uploadPanel = NSOpenPanel.openPanel();
2879 uploadPanel.setCanChooseDirectories(true);
2880 uploadPanel.setCanChooseFiles(session.getFeature(Touch.class).isSupported(
2881 new UploadTargetFinder(workdir).find(this.getSelectedPath())
2883 uploadPanel.setCanCreateDirectories(false);
2884 uploadPanel.setTreatsFilePackagesAsDirectories(true);
2885 uploadPanel.setAllowsMultipleSelection(true);
2886 uploadPanel.setPrompt(LocaleFactory.localizedString("Upload", "Transfer"));
2887 if(uploadPanel.respondsToSelector(Foundation.selector("setShowsHiddenFiles:"))) {
2888 uploadPanelHiddenFilesCheckbox = NSButton.buttonWithFrame(new NSRect(0, 0));
2889 uploadPanelHiddenFilesCheckbox.setTitle(LocaleFactory.localizedString("Show Hidden Files"));
2890 uploadPanelHiddenFilesCheckbox.setTarget(this.id());
2891 uploadPanelHiddenFilesCheckbox.setAction(Foundation.selector("uploadPanelSetShowHiddenFiles:"));
2892 uploadPanelHiddenFilesCheckbox.setButtonType(NSButton.NSSwitchButton);
2893 uploadPanelHiddenFilesCheckbox.setState(NSCell.NSOffState);
2894 uploadPanelHiddenFilesCheckbox.sizeToFit();
2895 uploadPanel.setAccessoryView(uploadPanelHiddenFilesCheckbox);
2897 uploadPanel.beginSheetForDirectory(new UploadDirectoryFinder().find(session.getHost()).getAbsolute(),
2898 null, this.window,
2899 this.id(),
2900 Foundation.selector("uploadPanelDidEnd:returnCode:contextInfo:"),
2901 null);
2904 public void uploadPanelSetShowHiddenFiles(ID sender) {
2905 uploadPanel.setShowsHiddenFiles(uploadPanelHiddenFilesCheckbox.state() == NSCell.NSOnState);
2908 public void uploadPanelDidEnd_returnCode_contextInfo(final NSOpenPanel sheet, final int returncode, final ID contextInfo) {
2909 sheet.orderOut(this.id());
2910 if(returncode == SheetCallback.DEFAULT_OPTION) {
2911 final Path destination = new UploadTargetFinder(workdir).find(this.getSelectedPath());
2912 // Selected files on the local filesystem
2913 final NSArray selected = sheet.filenames();
2914 final NSEnumerator iterator = selected.objectEnumerator();
2915 final List<TransferItem> uploads = new ArrayList<TransferItem>();
2916 NSObject next;
2917 while((next = iterator.nextObject()) != null) {
2918 final Local local = LocalFactory.get(next.toString());
2919 new UploadDirectoryFinder().save(session.getHost(), local.getParent());
2920 uploads.add(new TransferItem(
2921 new Path(destination, local.getName(),
2922 local.isDirectory() ? EnumSet.of(Path.Type.directory) : EnumSet.of(Path.Type.file)), local
2925 this.transfer(new UploadTransfer(session.getHost(), uploads));
2927 uploadPanel = null;
2928 uploadPanelHiddenFilesCheckbox = null;
2931 protected void transfer(final Transfer transfer) {
2932 final List<Path> selected = new ArrayList<Path>();
2933 for(TransferItem i : transfer.getRoots()) {
2934 selected.add(i.remote);
2936 this.transfer(transfer, selected);
2940 * Transfers the files either using the queue or using the browser session if #connection.pool.max is 1
2942 * @param transfer Transfer Operation
2944 protected void transfer(final Transfer transfer, final List<Path> selected) {
2945 // Determine from current browser session if new connection should be opened for transfers
2946 this.transfer(transfer, selected, session.getTransferType().equals(Host.TransferType.browser));
2950 * @param transfer Transfer Operation
2951 * @param browser Transfer in browser window
2953 protected void transfer(final Transfer transfer, final List<Path> selected, boolean browser) {
2954 final TransferCallback callback = new TransferCallback() {
2955 @Override
2956 public void complete(final Transfer transfer) {
2957 invoke(new WindowMainAction(BrowserController.this) {
2958 @Override
2959 public void run() {
2960 reload(selected, selected);
2965 if(browser) {
2966 this.background(new TransferBackgroundAction(this, session, cache, new TransferAdapter() {
2967 @Override
2968 public void progress(final TransferProgress status) {
2969 message(status.getProgress());
2971 }, transfer, new TransferOptions()) {
2972 @Override
2973 public void finish() {
2974 if(transfer.isComplete()) {
2975 callback.complete(transfer);
2977 super.finish();
2981 else {
2982 TransferControllerFactory.get().start(transfer, new TransferOptions(), callback);
2986 @Action
2987 public void insideButtonClicked(final ID sender) {
2988 final Path selected = this.getSelectedPath(); //first row selected
2989 if(null == selected) {
2990 return;
2992 if(selected.isDirectory()) {
2993 this.setWorkdir(selected);
2995 else if(selected.isFile() || this.getSelectionCount() > 1) {
2996 if(preferences.getBoolean("browser.doubleclick.edit")) {
2997 this.editButtonClicked(null);
2999 else {
3000 this.downloadButtonClicked(null);
3005 @Action
3006 public void connectButtonClicked(final ID sender) {
3007 final SheetController controller = ConnectionControllerFactory.create(this);
3008 this.addListener(new WindowListener() {
3009 @Override
3010 public void windowWillClose() {
3011 controller.invalidate();
3014 controller.beginSheet();
3017 @Action
3018 public void disconnectButtonClicked(final ID sender) {
3019 if(this.isActivityRunning()) {
3020 // Remove all pending actions
3021 for(BackgroundAction action : this.getActions().toArray(
3022 new BackgroundAction[this.getActions().size()])) {
3023 action.cancel();
3026 this.disconnect(new Runnable() {
3027 @Override
3028 public void run() {
3029 if(preferences.getBoolean("browser.disconnect.bookmarks.show")) {
3030 selectBookmarks();
3032 else {
3033 selectBrowser(preferences.getInteger("browser.view"));
3039 @Action
3040 public void showHiddenFilesClicked(final NSMenuItem sender) {
3041 if(sender.state() == NSCell.NSOnState) {
3042 this.setShowHiddenFiles(false);
3043 sender.setState(NSCell.NSOffState);
3045 else if(sender.state() == NSCell.NSOffState) {
3046 this.setShowHiddenFiles(true);
3047 sender.setState(NSCell.NSOnState);
3049 if(this.isMounted()) {
3050 this.reload();
3055 * @return This browser's session or null if not mounted
3057 public Session<?> getSession() {
3058 return session;
3061 public Cache<Path> getCache() {
3062 return cache;
3066 * @return true if the remote file system has been mounted
3068 public boolean isMounted() {
3069 return session != null && workdir != null;
3073 * @return true if mounted and the connection to the server is alive
3075 public boolean isConnected() {
3076 if(this.isMounted()) {
3077 return session.isConnected();
3079 return false;
3083 * NSService
3084 * <p/>
3085 * Indicates whether the receiver can send and receive the specified pasteboard types.
3086 * <p/>
3087 * Either sendType or returnType—but not both—may be empty. If sendType is empty,
3088 * the service doesn’t require input from the application requesting the service.
3089 * If returnType is empty, the service doesn’t return data.
3091 * @param sendType The pasteboard type the application needs to send.
3092 * @param returnType The pasteboard type the application needs to receive.
3093 * @return The object that can send and receive the specified types or nil
3094 * if the receiver knows of no object that can send and receive data of that type.
3096 public ID validRequestorForSendType_returnType(String sendType, String returnType) {
3097 log.debug("validRequestorForSendType_returnType:" + sendType + "," + returnType);
3098 if(StringUtils.isNotEmpty(sendType)) {
3099 // Cannot send any data type
3100 return null;
3102 if(StringUtils.isNotEmpty(returnType)) {
3103 // Can receive filenames
3104 if(NSPasteboard.FilenamesPboardType.equals(sendType)) {
3105 return this.id();
3108 return null;
3112 * NSService
3113 * <p/>
3114 * Reads data from the pasteboard and uses it to replace the current selection.
3116 * @param pboard Pasteboard
3117 * @return YES if your implementation was able to read the pasteboard data successfully; otherwise, NO.
3119 public boolean readSelectionFromPasteboard(NSPasteboard pboard) {
3120 return this.upload(pboard);
3124 * NSService
3125 * <p/>
3126 * Writes the current selection to the pasteboard.
3128 * @param pboard Pasteboard
3129 * @param types Types in pasteboard
3130 * @return YES if your implementation was able to write one or more types to the pasteboard; otherwise, NO.
3132 public boolean writeSelectionToPasteboard_types(NSPasteboard pboard, NSArray types) {
3133 return false;
3136 @Action
3137 public void copy(final ID sender) {
3138 pasteboard.clear();
3139 pasteboard.setCopy(true);
3140 final List<Path> s = this.getSelectedPaths();
3141 for(Path p : s) {
3142 // Writing data for private use when the item gets dragged to the transfer queue.
3143 pasteboard.add(p);
3145 final NSPasteboard clipboard = NSPasteboard.generalPasteboard();
3146 if(s.size() == 0) {
3147 s.add(this.workdir());
3149 clipboard.declareTypes(NSArray.arrayWithObject(
3150 NSString.stringWithString(NSPasteboard.StringPboardType)), null);
3151 StringBuilder copy = new StringBuilder();
3152 for(Iterator<Path> i = s.iterator(); i.hasNext(); ) {
3153 copy.append(i.next().getAbsolute());
3154 if(i.hasNext()) {
3155 copy.append("\n");
3158 if(!clipboard.setStringForType(copy.toString(), NSPasteboard.StringPboardType)) {
3159 log.error("Error writing to NSPasteboard.StringPboardType.");
3163 @Action
3164 public void cut(final ID sender) {
3165 pasteboard.clear();
3166 pasteboard.setCut(true);
3167 for(Path s : this.getSelectedPaths()) {
3168 // Writing data for private use when the item gets dragged to the transfer queue.
3169 pasteboard.add(s);
3171 final NSPasteboard clipboard = NSPasteboard.generalPasteboard();
3172 clipboard.declareTypes(NSArray.arrayWithObject(NSString.stringWithString(NSPasteboard.StringPboardType)), null);
3173 if(!clipboard.setStringForType(this.getSelectedPath().getAbsolute(), NSPasteboard.StringPboardType)) {
3174 log.error("Error writing to NSPasteboard.StringPboardType.");
3178 @Action
3179 public void paste(final ID sender) {
3180 if(pasteboard.isEmpty()) {
3181 NSPasteboard pboard = NSPasteboard.generalPasteboard();
3182 this.upload(pboard);
3184 else {
3185 final Map<Path, Path> files = new HashMap<Path, Path>();
3186 final Path parent = this.workdir();
3187 for(final Path next : pasteboard) {
3188 Path renamed = new Path(parent, next.getName(), next.getType());
3189 files.put(next, renamed);
3191 pasteboard.clear();
3192 if(pasteboard.isCut()) {
3193 this.renamePaths(files);
3195 if(pasteboard.isCopy()) {
3196 this.duplicatePaths(files);
3202 * @param pboard Pasteboard with filenames
3203 * @return True if filenames are found in pasteboard and upload has started
3205 private boolean upload(NSPasteboard pboard) {
3206 if(!this.isMounted()) {
3207 return false;
3209 if(pboard.availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) {
3210 NSObject o = pboard.propertyListForType(NSPasteboard.FilenamesPboardType);
3211 if(o != null) {
3212 if(o.isKindOfClass(Rococoa.createClass("NSArray", NSArray._Class.class))) {
3213 final NSArray elements = Rococoa.cast(o, NSArray.class);
3214 final Path workdir = this.workdir();
3215 final List<TransferItem> uploads = new ArrayList<TransferItem>();
3216 for(int i = 0; i < elements.count().intValue(); i++) {
3217 final Local local = LocalFactory.get(elements.objectAtIndex(new NSUInteger(i)).toString());
3218 uploads.add(new TransferItem(new Path(workdir, local.getName(),
3219 local.isDirectory() ? EnumSet.of(Path.Type.directory) : EnumSet.of(Path.Type.file)), local));
3221 this.transfer(new UploadTransfer(session.getHost(), uploads));
3225 return false;
3228 @Action
3229 public void openTerminalButtonClicked(final ID sender) {
3230 Path workdir = null;
3231 if(this.getSelectionCount() == 1) {
3232 Path selected = this.getSelectedPath();
3233 if(selected.isDirectory()) {
3234 workdir = selected;
3237 if(null == workdir) {
3238 workdir = this.workdir();
3240 try {
3241 TerminalServiceFactory.get().open(session.getHost(), workdir);
3243 catch(AccessDeniedException e) {
3244 this.alert(session.getHost(), e, new StringBuilder());
3248 @Action
3249 public void archiveMenuClicked(final NSMenuItem sender) {
3250 final Archive archive = Archive.forName(sender.representedObject());
3251 this.archiveClicked(archive);
3254 @Action
3255 public void archiveButtonClicked(final NSToolbarItem sender) {
3256 this.archiveClicked(Archive.TARGZ);
3260 * @param archive Archive format
3262 private void archiveClicked(final Archive archive) {
3263 final List<Path> changed = this.getSelectedPaths();
3264 this.checkOverwrite(Collections.singletonList(archive.getArchive(changed)), new DefaultMainAction() {
3265 @Override
3266 public void run() {
3267 background(new BrowserControllerBackgroundAction(BrowserController.this) {
3268 @Override
3269 public Boolean run() throws BackgroundException {
3270 final Compress feature = BrowserController.this.session.getFeature(Compress.class);
3271 feature.archive(archive, workdir, changed, this, transcript);
3272 return true;
3275 @Override
3276 public void cleanup() {
3277 super.cleanup();
3278 // Update Selection
3279 reload(changed, Collections.singletonList(archive.getArchive(changed)));
3282 @Override
3283 public String getActivity() {
3284 return archive.getCompressCommand(workdir, changed);
3291 @Action
3292 public void unarchiveButtonClicked(final ID sender) {
3293 final List<Path> expanded = new ArrayList<Path>();
3294 final List<Path> selected = this.getSelectedPaths();
3295 for(final Path s : selected) {
3296 final Archive archive = Archive.forName(s.getName());
3297 if(null == archive) {
3298 continue;
3300 this.checkOverwrite(archive.getExpanded(Collections.singletonList(s)), new DefaultMainAction() {
3301 @Override
3302 public void run() {
3303 background(new BrowserControllerBackgroundAction(BrowserController.this) {
3304 @Override
3305 public Boolean run() throws BackgroundException {
3306 final Compress feature = BrowserController.this.session.getFeature(Compress.class);
3307 feature.unarchive(archive, s, this, transcript);
3308 return true;
3311 @Override
3312 public void cleanup() {
3313 super.cleanup();
3314 expanded.addAll(archive.getExpanded(Collections.singletonList(s)));
3315 // Update Selection
3316 reload(selected, expanded);
3319 @Override
3320 public String getActivity() {
3321 return archive.getDecompressCommand(s);
3330 * Accessor to the working directory
3332 * @return The current working directory or null if no file system is mounted
3334 protected Path workdir() {
3335 return workdir;
3338 public void setWorkdir(final Path directory) {
3339 this.setWorkdir(directory, Collections.<Path>emptyList());
3342 public void setWorkdir(final Path directory, Path selected) {
3343 this.setWorkdir(directory, Collections.singletonList(selected));
3347 * Sets the current working directory. This will update the path selection menu and also add this path to the browsing history.
3349 * @param directory The new working directory to display or null to detach any working directory from the browser
3350 * @param selected Selected files in browser
3352 public void setWorkdir(final Path directory, final List<Path> selected) {
3353 if(log.isDebugEnabled()) {
3354 log.debug(String.format("Set working directory to %s", directory));
3356 // Remove any custom file filter
3357 this.setPathFilter(null);
3358 final NSTableView browser = this.getSelectedBrowserView();
3359 window.endEditingFor(browser);
3360 // Update the working directory if listing is successful
3361 workdir = directory;
3362 if(this.isMounted()) {
3363 this.reload(Collections.singleton(directory), selected, false);
3365 else {
3366 this.reload(Collections.<Path>emptySet(), selected, false);
3368 this.setNavigation(this.isMounted());
3371 private void setNavigation(boolean enabled) {
3372 if(!enabled) {
3373 searchField.setStringValue(StringUtils.EMPTY);
3375 pathPopupButton.removeAllItems();
3376 if(enabled) {
3377 // Update the current working directory
3378 navigation.add(workdir);
3379 Path p = workdir;
3380 do {
3381 this.addNavigation(p);
3382 p = p.getParent();
3384 while(!p.isRoot());
3385 this.addNavigation(p);
3387 pathPopupButton.setEnabled(enabled);
3388 navigationButton.setEnabled_forSegment(enabled && navigation.getBack().size() > 1, NAVIGATION_LEFT_SEGMENT_BUTTON);
3389 navigationButton.setEnabled_forSegment(enabled && navigation.getForward().size() > 0, NAVIGATION_RIGHT_SEGMENT_BUTTON);
3390 upButton.setEnabled_forSegment(enabled && !workdir.isRoot(), NAVIGATION_UP_SEGMENT_BUTTON);
3393 private void addNavigation(final Path p) {
3394 pathPopupButton.addItemWithTitle(p.getAbsolute());
3395 pathPopupButton.lastItem().setRepresentedObject(p.getAbsolute());
3396 if(p.isVolume()) {
3397 pathPopupButton.lastItem().setImage(IconCacheFactory.<NSImage>get().volumeIcon(session.getHost().getProtocol(), 16));
3399 else {
3400 pathPopupButton.lastItem().setImage(IconCacheFactory.<NSImage>get().fileIcon(p, 16));
3405 * Initializes a session for the passed host. Setting up the listeners and adding any callback
3406 * controllers needed for login, trust management and hostkey verification.
3408 * @param host Bookmark
3409 * @return A session object bound to this browser controller
3411 private Session init(final Host host) {
3412 session = SessionFactory.create(host,
3413 new KeychainX509TrustManager(new DefaultTrustManagerHostnameCallback(host)),
3414 new KeychainX509KeyManager());
3415 transcript.clear();
3416 navigation.clear();
3417 pasteboard = PathPasteboardFactory.getPasteboard(session);
3418 this.setWorkdir(null);
3419 this.setEncoding(session.getEncoding());
3420 return session;
3424 * Open connection in browser
3426 * @param host Bookmark
3428 public void mount(final Host host) {
3429 if(log.isDebugEnabled()) {
3430 log.debug(String.format("Mount session for %s", host));
3432 this.unmount(new Runnable() {
3433 @Override
3434 public void run() {
3435 // The browser has no session, we are allowed to proceed
3436 // Initialize the browser with the new session attaching all listeners
3437 final Session session = init(host);
3438 background(new WorkerBackgroundAction<Path>(BrowserController.this, session, cache,
3439 new MountWorker(session, cache, listener) {
3440 @Override
3441 public void cleanup(final Path workdir) {
3442 if(null == workdir) {
3443 unmount();
3445 else {
3446 // Update status icon
3447 bookmarkTable.setNeedsDisplay();
3448 // Set the working directory
3449 setWorkdir(workdir);
3450 // Close bookmarks
3451 selectBrowser(preferences.getInteger("browser.view"));
3452 // Set the window title
3453 window.setRepresentedFilename(HistoryCollection.defaultCollection().getFile(host).getAbsolute());
3454 if(preferences.getBoolean("browser.disconnect.confirm")) {
3455 window.setDocumentEdited(true);
3457 securityLabel.setImage(session.isSecured() ? IconCacheFactory.<NSImage>get().iconNamed("locked.tiff")
3458 : IconCacheFactory.<NSImage>get().iconNamed("unlocked.tiff"));
3459 securityLabel.setEnabled(session instanceof SSLSession);
3464 @Override
3465 public void init() {
3466 super.init();
3467 window.setTitle(BookmarkNameProvider.toString(host, true));
3468 window.setRepresentedFilename(StringUtils.EMPTY);
3469 // Update status icon
3470 bookmarkTable.setNeedsDisplay();
3478 * Close connection
3480 * @return True if succeeded
3482 public boolean unmount() {
3483 return this.unmount(new Runnable() {
3484 @Override
3485 public void run() {
3492 * @param disconnected Callback after the session has been disconnected
3493 * @return True if the unmount process has finished, false if the user has to agree first
3494 * to close the connection
3496 public boolean unmount(final Runnable disconnected) {
3497 return this.unmount(new SheetCallback() {
3498 @Override
3499 public void callback(int returncode) {
3500 if(returncode == DEFAULT_OPTION) {
3501 unmountImpl(disconnected);
3504 }, disconnected);
3508 * @param callback Confirmation callback
3509 * @param disconnected Action to run after disconnected
3510 * @return True if succeeded
3512 public boolean unmount(final SheetCallback callback, final Runnable disconnected) {
3513 if(log.isDebugEnabled()) {
3514 log.debug(String.format("Unmount session %s", session));
3516 if(this.isConnected() || this.isActivityRunning()) {
3517 if(preferences.getBoolean("browser.disconnect.confirm")) {
3518 // Defer the unmount to the callback function
3519 final NSAlert alert = NSAlert.alert(
3520 MessageFormat.format(LocaleFactory.localizedString("Disconnect from {0}"), session.getHost().getHostname()), //title
3521 LocaleFactory.localizedString("The connection will be closed."), // message
3522 LocaleFactory.localizedString("Disconnect"), // defaultbutton
3523 LocaleFactory.localizedString("Cancel"), // alternate button
3524 null //other button
3526 alert.setShowsSuppressionButton(true);
3527 alert.suppressionButton().setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration"));
3528 this.alert(alert, new SheetCallback() {
3529 @Override
3530 public void callback(int returncode) {
3531 if(alert.suppressionButton().state() == NSCell.NSOnState) {
3532 // Never show again.
3533 preferences.setProperty("browser.disconnect.confirm", false);
3535 callback.callback(returncode);
3538 // No unmount yet
3539 return false;
3542 this.unmountImpl(disconnected);
3543 // Unmount succeeded
3544 return true;
3548 * @param disconnected Action to run after disconnected
3550 private void unmountImpl(final Runnable disconnected) {
3551 final List<Editor> list = editors;
3552 this.disconnect(new Runnable() {
3553 @Override
3554 public void run() {
3555 session = null;
3556 for(Editor e : list) {
3557 e.delete();
3559 window.setTitle(preferences.getProperty("application.name"));
3560 window.setRepresentedFilename(StringUtils.EMPTY);
3561 disconnected.run();
3562 list.clear();
3563 cache.clear();
3569 * Unmount this session
3571 private void disconnect(final Runnable disconnected) {
3572 final InfoController c = InfoControllerFactory.get(BrowserController.this);
3573 if(null != c) {
3574 c.window().close();
3576 if(session != null) {
3577 this.background(new WorkerBackgroundAction<Void>(this, session, cache, new DisconnectWorker(session)) {
3578 @Override
3579 public void prepare() throws ConnectionCanceledException {
3580 if(!session.isConnected()) {
3581 throw new ConnectionCanceledException();
3583 super.prepare();
3586 @Override
3587 protected boolean connect(Session session) throws BackgroundException {
3588 return false;
3591 @Override
3592 public void cleanup() {
3593 super.cleanup();
3594 window.setDocumentEdited(false);
3595 disconnected.run();
3599 else {
3600 disconnected.run();
3604 @Action
3605 public void printDocument(final ID sender) {
3606 this.print(this.getSelectedBrowserView());
3610 * @param app Singleton
3611 * @return NSApplication.TerminateLater if the application should not yet be terminated
3613 public static NSUInteger applicationShouldTerminate(final NSApplication app) {
3614 // Determine if there are any open connections
3615 for(final BrowserController controller : MainController.getBrowsers()) {
3616 if(!controller.unmount(new SheetCallback() {
3617 @Override
3618 public void callback(final int returncode) {
3619 if(returncode == DEFAULT_OPTION) { //Disconnect
3620 controller.window().close();
3621 if(NSApplication.NSTerminateNow.equals(BrowserController.applicationShouldTerminate(app))) {
3622 app.replyToApplicationShouldTerminate(true);
3625 else {
3626 app.replyToApplicationShouldTerminate(false);
3630 }, new Runnable() {
3631 @Override
3632 public void run() {
3636 )) {
3637 return NSApplication.NSTerminateCancel;
3640 return NSApplication.NSTerminateNow;
3643 @Override
3644 public boolean windowShouldClose(final NSWindow sender) {
3645 return this.unmount(new Runnable() {
3646 @Override
3647 public void run() {
3648 sender.close();
3654 * @param item Menu item
3655 * @return True if the menu should be enabled
3657 public boolean validateMenuItem(NSMenuItem item) {
3658 final Selector action = item.action();
3659 if(action.equals(Foundation.selector("paste:"))) {
3660 final String title = "Paste {0}";
3661 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), StringUtils.EMPTY).trim());
3662 if(this.isMounted()) {
3663 if(pasteboard.isEmpty()) {
3664 if(NSPasteboard.generalPasteboard().availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) {
3665 NSObject o = NSPasteboard.generalPasteboard().propertyListForType(NSPasteboard.FilenamesPboardType);
3666 if(o != null) {
3667 if(o.isKindOfClass(Rococoa.createClass("NSArray", NSArray._Class.class))) {
3668 final NSArray elements = Rococoa.cast(o, NSArray.class);
3669 if(elements.count().intValue() == 1) {
3670 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3671 "\"" + elements.objectAtIndex(new NSUInteger(0)) + "\"").trim());
3673 else {
3674 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3675 MessageFormat.format(LocaleFactory.localizedString("{0} Files"),
3676 String.valueOf(elements.count().intValue()))
3677 ).trim());
3683 else {
3684 if(pasteboard.size() == 1) {
3685 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3686 "\"" + pasteboard.get(0).getName() + "\"").trim());
3688 else {
3689 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3690 MessageFormat.format(LocaleFactory.localizedString("{0} Files"), String.valueOf(pasteboard.size()))).trim());
3695 else if(action.equals(Foundation.selector("cut:")) || action.equals(Foundation.selector("copy:"))) {
3696 String title = null;
3697 if(action.equals(Foundation.selector("cut:"))) {
3698 title = "Cut {0}";
3700 else if(action.equals(Foundation.selector("copy:"))) {
3701 title = "Copy {0}";
3703 if(this.isMounted()) {
3704 int count = this.getSelectionCount();
3705 if(0 == count) {
3706 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), StringUtils.EMPTY).trim());
3708 else if(1 == count) {
3709 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3710 "\"" + this.getSelectedPath().getName() + "\"").trim());
3712 else {
3713 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title),
3714 MessageFormat.format(LocaleFactory.localizedString("{0} Files"), String.valueOf(this.getSelectionCount()))).trim());
3717 else {
3718 item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), StringUtils.EMPTY).trim());
3721 else if(action.equals(Foundation.selector("showHiddenFilesClicked:"))) {
3722 item.setState(this.getFilter() instanceof NullFilter ? NSCell.NSOnState : NSCell.NSOffState);
3724 else if(action.equals(Foundation.selector("encodingMenuClicked:"))) {
3725 if(this.isMounted()) {
3726 item.setState(session.getEncoding().equalsIgnoreCase(
3727 item.title()) ? NSCell.NSOnState : NSCell.NSOffState);
3729 else {
3730 item.setState(preferences.getProperty("browser.charset.encoding").equalsIgnoreCase(
3731 item.title()) ? NSCell.NSOnState : NSCell.NSOffState);
3734 else if(action.equals(Foundation.selector("browserSwitchMenuClicked:"))) {
3735 if(item.tag() == preferences.getInteger("browser.view")) {
3736 item.setState(NSCell.NSOnState);
3738 else {
3739 item.setState(NSCell.NSOffState);
3742 else if(action.equals(Foundation.selector("archiveMenuClicked:"))) {
3743 final Archive archive = Archive.forName(item.representedObject());
3744 item.setTitle(archive.getTitle(this.getSelectedPaths()));
3746 else if(action.equals(Foundation.selector("quicklookButtonClicked:"))) {
3747 item.setKeyEquivalent(" ");
3748 item.setKeyEquivalentModifierMask(0);
3750 return this.validateItem(action);
3754 * @return Browser tab active
3756 private boolean isBrowser() {
3757 return this.getSelectedTabView() == TAB_LIST_VIEW
3758 || this.getSelectedTabView() == TAB_OUTLINE_VIEW;
3762 * @return Bookmarks tab active
3764 private boolean isBookmarks() {
3765 return this.getSelectedTabView() == TAB_BOOKMARKS;
3769 * @param action the method selector
3770 * @return true if the item by that identifier should be enabled
3772 private boolean validateItem(final Selector action) {
3773 if(action.equals(Foundation.selector("cut:"))) {
3774 return this.isBrowser() && this.isMounted() && this.getSelectionCount() > 0;
3776 else if(action.equals(Foundation.selector("copy:"))) {
3777 return this.isBrowser() && this.isMounted() && this.getSelectionCount() > 0;
3779 else if(action.equals(Foundation.selector("paste:"))) {
3780 if(this.isBrowser() && this.isMounted()) {
3781 if(pasteboard.isEmpty()) {
3782 NSPasteboard pboard = NSPasteboard.generalPasteboard();
3783 if(pboard.availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) {
3784 Object o = pboard.propertyListForType(NSPasteboard.FilenamesPboardType);
3785 if(o != null) {
3786 return true;
3789 return false;
3791 return true;
3793 return false;
3795 else if(action.equals(Foundation.selector("encodingMenuClicked:"))) {
3796 return this.isBrowser() && !this.isActivityRunning();
3798 else if(action.equals(Foundation.selector("connectBookmarkButtonClicked:"))) {
3799 if(this.isBookmarks()) {
3800 return bookmarkTable.numberOfSelectedRows().intValue() == 1;
3802 return false;
3804 else if(action.equals(Foundation.selector("addBookmarkButtonClicked:"))) {
3805 if(this.isBookmarks()) {
3806 return bookmarkModel.getSource().allowsAdd();
3808 return true;
3810 else if(action.equals(Foundation.selector("deleteBookmarkButtonClicked:"))) {
3811 if(this.isBookmarks()) {
3812 return bookmarkModel.getSource().allowsDelete() && bookmarkTable.selectedRow().intValue() != -1;
3814 return false;
3816 else if(action.equals(Foundation.selector("duplicateBookmarkButtonClicked:"))) {
3817 if(this.isBookmarks()) {
3818 return bookmarkModel.getSource().allowsEdit() && bookmarkTable.numberOfSelectedRows().intValue() == 1;
3820 return false;
3822 else if(action.equals(Foundation.selector("editBookmarkButtonClicked:"))) {
3823 if(this.isBookmarks()) {
3824 return bookmarkModel.getSource().allowsEdit() && bookmarkTable.numberOfSelectedRows().intValue() == 1;
3826 return false;
3828 else if(action.equals(Foundation.selector("editButtonClicked:"))) {
3829 if(this.isBrowser() && this.isMounted() && this.getSelectionCount() > 0) {
3830 final EditorFactory factory = EditorFactory.instance();
3831 for(Path s : this.getSelectedPaths()) {
3832 if(!this.isEditable(s)) {
3833 return false;
3835 // Choose editor for selected file
3836 if(null == factory.getEditor(s.getName())) {
3837 return false;
3840 return true;
3842 return false;
3844 else if(action.equals(Foundation.selector("editMenuClicked:"))) {
3845 if(this.isBrowser() && this.isMounted() && this.getSelectionCount() > 0) {
3846 for(Path s : this.getSelectedPaths()) {
3847 if(!this.isEditable(s)) {
3848 return false;
3851 return true;
3853 return false;
3855 else if(action.equals(Foundation.selector("searchButtonClicked:"))) {
3856 return this.isMounted() || this.isBookmarks();
3858 else if(action.equals(Foundation.selector("quicklookButtonClicked:"))) {
3859 return this.isBrowser() && this.isMounted() && quicklook.isAvailable() && this.getSelectionCount() > 0;
3861 else if(action.equals(Foundation.selector("openBrowserButtonClicked:"))) {
3862 return this.isMounted();
3864 else if(action.equals(Foundation.selector("sendCustomCommandClicked:"))) {
3865 return this.isBrowser() && this.isMounted() && session.getFeature(Command.class) != null;
3867 else if(action.equals(Foundation.selector("gotoButtonClicked:"))) {
3868 return this.isBrowser() && this.isMounted();
3870 else if(action.equals(Foundation.selector("infoButtonClicked:"))) {
3871 return this.isBrowser() && this.isMounted() && this.getSelectionCount() > 0;
3873 else if(action.equals(Foundation.selector("createFolderButtonClicked:"))) {
3874 return this.isBrowser() && this.isMounted();
3876 else if(action.equals(Foundation.selector("createFileButtonClicked:"))) {
3877 return this.isBrowser() && this.isMounted() && session.getFeature(Touch.class).isSupported(
3878 new UploadTargetFinder(workdir).find(this.getSelectedPath())
3881 else if(action.equals(Foundation.selector("uploadButtonClicked:"))) {
3882 return this.isBrowser() && this.isMounted() && session.getFeature(Touch.class).isSupported(
3883 new UploadTargetFinder(workdir).find(this.getSelectedPath())
3886 else if(action.equals(Foundation.selector("createSymlinkButtonClicked:"))) {
3887 return this.isBrowser() && this.isMounted() && session.getFeature(Symlink.class) != null
3888 && this.getSelectionCount() == 1;
3890 else if(action.equals(Foundation.selector("duplicateFileButtonClicked:"))) {
3891 return this.isBrowser() && this.isMounted() && this.getSelectionCount() == 1;
3893 else if(action.equals(Foundation.selector("renameFileButtonClicked:"))) {
3894 if(this.isBrowser() && this.isMounted() && this.getSelectionCount() == 1) {
3895 final Path selected = this.getSelectedPath();
3896 if(null == selected) {
3897 return false;
3899 return session.getFeature(Move.class).isSupported(selected);
3901 return false;
3903 else if(action.equals(Foundation.selector("deleteFileButtonClicked:"))) {
3904 return this.isBrowser() && this.isMounted() && this.getSelectionCount() > 0;
3906 else if(action.equals(Foundation.selector("revertFileButtonClicked:"))) {
3907 if(this.isBrowser() && this.isMounted() && this.getSelectionCount() == 1) {
3908 return session.getFeature(Versioning.class) != null;
3910 return false;
3912 else if(action.equals(Foundation.selector("reloadButtonClicked:"))) {
3913 return this.isBrowser() && this.isMounted();
3915 else if(action.equals(Foundation.selector("newBrowserButtonClicked:"))) {
3916 return this.isMounted();
3918 else if(action.equals(Foundation.selector("syncButtonClicked:"))) {
3919 return this.isBrowser() && this.isMounted();
3921 else if(action.equals(Foundation.selector("downloadAsButtonClicked:"))) {
3922 return this.isBrowser() && this.isMounted() && this.getSelectionCount() == 1;
3924 else if(action.equals(Foundation.selector("downloadToButtonClicked:")) || action.equals(Foundation.selector("downloadButtonClicked:"))) {
3925 return this.isBrowser() && this.isMounted() && this.getSelectionCount() > 0;
3927 else if(action.equals(Foundation.selector("insideButtonClicked:"))) {
3928 return this.isBrowser() && this.isMounted() && this.getSelectionCount() > 0;
3930 else if(action.equals(Foundation.selector("upButtonClicked:"))) {
3931 return this.isBrowser() && this.isMounted() && !this.workdir().isRoot();
3933 else if(action.equals(Foundation.selector("backButtonClicked:"))) {
3934 return this.isBrowser() && this.isMounted() && navigation.getBack().size() > 1;
3936 else if(action.equals(Foundation.selector("forwardButtonClicked:"))) {
3937 return this.isBrowser() && this.isMounted() && navigation.getForward().size() > 0;
3939 else if(action.equals(Foundation.selector("printDocument:"))) {
3940 return this.isBrowser() && this.isMounted();
3942 else if(action.equals(Foundation.selector("disconnectButtonClicked:"))) {
3943 if(this.isBrowser()) {
3944 if(!this.isConnected()) {
3945 return this.isActivityRunning();
3947 return this.isConnected();
3950 else if(action.equals(Foundation.selector("gotofolderButtonClicked:"))) {
3951 return this.isBrowser() && this.isMounted();
3953 else if(action.equals(Foundation.selector("openTerminalButtonClicked:"))) {
3954 return this.isBrowser() && this.isMounted()
3955 && session instanceof SFTPSession && TerminalServiceFactory.get() != null;
3957 else if(action.equals(Foundation.selector("archiveButtonClicked:")) || action.equals(Foundation.selector("archiveMenuClicked:"))) {
3958 if(this.isBrowser() && this.isMounted()) {
3959 if(session.getFeature(Compress.class) == null) {
3960 return false;
3962 if(this.getSelectionCount() > 0) {
3963 for(Path s : this.getSelectedPaths()) {
3964 if(s.isFile() && Archive.isArchive(s.getName())) {
3965 // At least one file selected is already an archive. No distinct action possible
3966 return false;
3969 return true;
3972 return false;
3974 else if(action.equals(Foundation.selector("unarchiveButtonClicked:"))) {
3975 if(this.isBrowser() && this.isMounted()) {
3976 if(session.getFeature(Compress.class) == null) {
3977 return false;
3979 if(this.getSelectionCount() > 0) {
3980 for(Path s : this.getSelectedPaths()) {
3981 if(s.isDirectory()) {
3982 return false;
3984 if(!Archive.isArchive(s.getName())) {
3985 return false;
3988 return true;
3991 return false;
3993 return true; // by default everything is enabled
3996 private static final String TOOLBAR_NEW_CONNECTION = "New Connection";
3997 private static final String TOOLBAR_BROWSER_VIEW = "Browser View";
3998 private static final String TOOLBAR_TRANSFERS = "Transfers";
3999 private static final String TOOLBAR_QUICK_CONNECT = "Quick Connect";
4000 private static final String TOOLBAR_TOOLS = "Tools";
4001 private static final String TOOLBAR_REFRESH = "Refresh";
4002 private static final String TOOLBAR_ENCODING = "Encoding";
4003 private static final String TOOLBAR_SYNCHRONIZE = "Synchronize";
4004 private static final String TOOLBAR_DOWNLOAD = "Download";
4005 private static final String TOOLBAR_UPLOAD = "Upload";
4006 private static final String TOOLBAR_EDIT = "Edit";
4007 private static final String TOOLBAR_DELETE = "Delete";
4008 private static final String TOOLBAR_NEW_FOLDER = "New Folder";
4009 private static final String TOOLBAR_NEW_BOOKMARK = "New Bookmark";
4010 private static final String TOOLBAR_GET_INFO = "Get Info";
4011 private static final String TOOLBAR_WEBVIEW = "Open";
4012 private static final String TOOLBAR_DISCONNECT = "Disconnect";
4013 private static final String TOOLBAR_TERMINAL = "Terminal";
4014 private static final String TOOLBAR_ARCHIVE = "Archive";
4015 private static final String TOOLBAR_QUICKLOOK = "Quick Look";
4016 private static final String TOOLBAR_LOG = "Log";
4018 @Override
4019 public boolean validateToolbarItem(final NSToolbarItem item) {
4020 final String identifier = item.itemIdentifier();
4021 switch(identifier) {
4022 case TOOLBAR_EDIT: {
4023 Application editor = null;
4024 final Path selected = this.getSelectedPath();
4025 if(null != selected) {
4026 if(this.isEditable(selected)) {
4027 // Choose editor for selected file
4028 final EditorFactory factory = EditorFactory.instance();
4029 editor = factory.getEditor(selected.getName());
4032 if(null == editor) {
4033 // No editor found
4034 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("pencil.tiff", 32));
4036 else {
4037 item.setImage(IconCacheFactory.<NSImage>get().applicationIcon(editor, 32));
4039 break;
4041 case TOOLBAR_DISCONNECT:
4042 if(this.isActivityRunning()) {
4043 item.setLabel(LocaleFactory.localizedString("Stop"));
4044 item.setPaletteLabel(LocaleFactory.localizedString("Stop"));
4045 item.setToolTip(LocaleFactory.localizedString("Cancel current operation in progress"));
4046 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("stop", 32));
4048 else {
4049 item.setLabel(LocaleFactory.localizedString(TOOLBAR_DISCONNECT));
4050 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_DISCONNECT));
4051 item.setToolTip(LocaleFactory.localizedString("Disconnect from server"));
4052 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("eject.tiff", 32));
4054 break;
4055 case TOOLBAR_ARCHIVE: {
4056 final Path selected = getSelectedPath();
4057 if(null != selected) {
4058 if(Archive.isArchive(selected.getName())) {
4059 item.setLabel(LocaleFactory.localizedString("Unarchive", "Archive"));
4060 item.setPaletteLabel(LocaleFactory.localizedString("Unarchive"));
4061 item.setAction(Foundation.selector("unarchiveButtonClicked:"));
4063 else {
4064 item.setLabel(LocaleFactory.localizedString("Archive", "Archive"));
4065 item.setPaletteLabel(LocaleFactory.localizedString("Archive"));
4066 item.setAction(Foundation.selector("archiveButtonClicked:"));
4069 break;
4072 return validateItem(item.action());
4076 * Keep reference to weak toolbar items. A toolbar may ask again for a kind of toolbar
4077 * item already supplied to it, in which case this method may return the same toolbar
4078 * item it returned before
4080 private Map<String, NSToolbarItem> toolbarItems
4081 = new HashMap<String, NSToolbarItem>();
4083 @Override
4084 public NSToolbarItem toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar(NSToolbar toolbar, final String itemIdentifier, boolean inserted) {
4085 if(log.isDebugEnabled()) {
4086 log.debug("toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar:" + itemIdentifier);
4088 if(!toolbarItems.containsKey(itemIdentifier)) {
4089 toolbarItems.put(itemIdentifier, NSToolbarItem.itemWithIdentifier(itemIdentifier));
4091 final NSToolbarItem item = toolbarItems.get(itemIdentifier);
4092 switch(itemIdentifier) {
4093 case TOOLBAR_BROWSER_VIEW:
4094 item.setLabel(LocaleFactory.localizedString("View"));
4095 item.setPaletteLabel(LocaleFactory.localizedString("View"));
4096 item.setToolTip(LocaleFactory.localizedString("Switch Browser View"));
4097 item.setView(browserSwitchView);
4098 // Add a menu representation for text mode of toolbar
4099 NSMenuItem viewMenu = NSMenuItem.itemWithTitle(LocaleFactory.localizedString("View"), null, StringUtils.EMPTY);
4100 NSMenu viewSubmenu = NSMenu.menu();
4101 viewSubmenu.addItemWithTitle_action_keyEquivalent(LocaleFactory.localizedString("List"),
4102 Foundation.selector("browserSwitchMenuClicked:"), StringUtils.EMPTY);
4103 viewSubmenu.itemWithTitle(LocaleFactory.localizedString("List")).setTag(0);
4104 viewSubmenu.addItemWithTitle_action_keyEquivalent(LocaleFactory.localizedString("Outline"),
4105 Foundation.selector("browserSwitchMenuClicked:"), StringUtils.EMPTY);
4106 viewSubmenu.itemWithTitle(LocaleFactory.localizedString("Outline")).setTag(1);
4107 viewMenu.setSubmenu(viewSubmenu);
4108 item.setMenuFormRepresentation(viewMenu);
4109 return item;
4110 case TOOLBAR_NEW_CONNECTION:
4111 item.setLabel(LocaleFactory.localizedString(TOOLBAR_NEW_CONNECTION));
4112 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_NEW_CONNECTION));
4113 item.setToolTip(LocaleFactory.localizedString("Connect to server"));
4114 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("connect.tiff", 32));
4115 item.setTarget(this.id());
4116 item.setAction(Foundation.selector("connectButtonClicked:"));
4117 return item;
4118 case TOOLBAR_TRANSFERS:
4119 item.setLabel(LocaleFactory.localizedString(TOOLBAR_TRANSFERS));
4120 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_TRANSFERS));
4121 item.setToolTip(LocaleFactory.localizedString("Show Transfers window"));
4122 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("queue.tiff", 32));
4123 item.setAction(Foundation.selector("showTransferQueueClicked:"));
4124 return item;
4125 case TOOLBAR_TOOLS:
4126 item.setLabel(LocaleFactory.localizedString("Action"));
4127 item.setPaletteLabel(LocaleFactory.localizedString("Action"));
4128 if(inserted || !Factory.Platform.osversion.matches("10\\.5.*")) {
4129 final NSInteger index = new NSInteger(0);
4130 actionPopupButton.insertItemWithTitle_atIndex(StringUtils.EMPTY, index);
4131 actionPopupButton.itemAtIndex(index).setImage(IconCacheFactory.<NSImage>get().iconNamed("gear.tiff"));
4132 item.setView(actionPopupButton);
4133 // Add a menu representation for text mode of toolbar
4134 NSMenuItem toolMenu = NSMenuItem.itemWithTitle(LocaleFactory.localizedString("Action"), null, StringUtils.EMPTY);
4135 NSMenu toolSubmenu = NSMenu.menu();
4136 for(int i = 1; i < actionPopupButton.menu().numberOfItems().intValue(); i++) {
4137 NSMenuItem template = actionPopupButton.menu().itemAtIndex(new NSInteger(i));
4138 toolSubmenu.addItem(NSMenuItem.itemWithTitle(template.title(),
4139 template.action(),
4140 template.keyEquivalent()));
4142 toolMenu.setSubmenu(toolSubmenu);
4143 item.setMenuFormRepresentation(toolMenu);
4145 else {
4146 NSToolbarItem temporary = NSToolbarItem.itemWithIdentifier(itemIdentifier);
4147 temporary.setPaletteLabel(LocaleFactory.localizedString("Action"));
4148 temporary.setImage(IconCacheFactory.<NSImage>get().iconNamed("advanced.tiff", 32));
4149 return temporary;
4151 return item;
4152 case TOOLBAR_QUICK_CONNECT:
4153 item.setLabel(LocaleFactory.localizedString(TOOLBAR_QUICK_CONNECT));
4154 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_QUICK_CONNECT));
4155 item.setToolTip(LocaleFactory.localizedString("Connect to server"));
4156 item.setView(quickConnectPopup);
4157 return item;
4158 case TOOLBAR_ENCODING:
4159 item.setLabel(LocaleFactory.localizedString(TOOLBAR_ENCODING));
4160 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_ENCODING));
4161 item.setToolTip(LocaleFactory.localizedString("Character Encoding"));
4162 item.setView(this.encodingPopup);
4163 // Add a menu representation for text mode of toolbar
4164 NSMenuItem encodingMenu = NSMenuItem.itemWithTitle(LocaleFactory.localizedString(TOOLBAR_ENCODING),
4165 Foundation.selector("encodingMenuClicked:"), StringUtils.EMPTY);
4166 String[] charsets = new DefaultCharsetProvider().availableCharsets();
4167 NSMenu charsetMenu = NSMenu.menu();
4168 for(String charset : charsets) {
4169 charsetMenu.addItemWithTitle_action_keyEquivalent(charset, Foundation.selector("encodingMenuClicked:"), StringUtils.EMPTY);
4171 encodingMenu.setSubmenu(charsetMenu);
4172 item.setMenuFormRepresentation(encodingMenu);
4173 item.setMinSize(this.encodingPopup.frame().size);
4174 item.setMaxSize(this.encodingPopup.frame().size);
4175 return item;
4176 case TOOLBAR_REFRESH:
4177 item.setLabel(LocaleFactory.localizedString(TOOLBAR_REFRESH));
4178 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_REFRESH));
4179 item.setToolTip(LocaleFactory.localizedString("Refresh directory listing"));
4180 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("reload.tiff", 32));
4181 item.setTarget(this.id());
4182 item.setAction(Foundation.selector("reloadButtonClicked:"));
4183 return item;
4184 case TOOLBAR_DOWNLOAD:
4185 item.setLabel(LocaleFactory.localizedString(TOOLBAR_DOWNLOAD));
4186 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_DOWNLOAD));
4187 item.setToolTip(LocaleFactory.localizedString("Download file"));
4188 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("download.tiff", 32));
4189 item.setTarget(this.id());
4190 item.setAction(Foundation.selector("downloadButtonClicked:"));
4191 return item;
4192 case TOOLBAR_UPLOAD:
4193 item.setLabel(LocaleFactory.localizedString(TOOLBAR_UPLOAD));
4194 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_UPLOAD));
4195 item.setToolTip(LocaleFactory.localizedString("Upload local file to the remote host"));
4196 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("upload.tiff", 32));
4197 item.setTarget(this.id());
4198 item.setAction(Foundation.selector("uploadButtonClicked:"));
4199 return item;
4200 case TOOLBAR_SYNCHRONIZE:
4201 item.setLabel(LocaleFactory.localizedString(TOOLBAR_SYNCHRONIZE));
4202 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_SYNCHRONIZE));
4203 item.setToolTip(LocaleFactory.localizedString("Synchronize files"));
4204 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("sync.tiff", 32));
4205 item.setTarget(this.id());
4206 item.setAction(Foundation.selector("syncButtonClicked:"));
4207 return item;
4208 case TOOLBAR_GET_INFO:
4209 item.setLabel(LocaleFactory.localizedString(TOOLBAR_GET_INFO));
4210 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_GET_INFO));
4211 item.setToolTip(LocaleFactory.localizedString("Show file attributes"));
4212 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("info.tiff", 32));
4213 item.setTarget(this.id());
4214 item.setAction(Foundation.selector("infoButtonClicked:"));
4215 return item;
4216 case TOOLBAR_WEBVIEW:
4217 item.setLabel(LocaleFactory.localizedString(TOOLBAR_WEBVIEW));
4218 item.setPaletteLabel(LocaleFactory.localizedString("Open in Web Browser"));
4219 item.setToolTip(LocaleFactory.localizedString("Open in Web Browser"));
4220 final Application browser = SchemeHandlerFactory.get().getDefaultHandler(Scheme.http);
4221 if(Application.notfound.equals(browser)) {
4222 item.setEnabled(false);
4223 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("notfound.tiff", 32));
4225 else {
4226 item.setImage(IconCacheFactory.<NSImage>get().applicationIcon(browser, 32));
4228 item.setTarget(this.id());
4229 item.setAction(Foundation.selector("openBrowserButtonClicked:"));
4230 return item;
4231 case TOOLBAR_EDIT:
4232 item.setLabel(LocaleFactory.localizedString(TOOLBAR_EDIT));
4233 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_EDIT));
4234 item.setToolTip(LocaleFactory.localizedString("Edit file in external editor"));
4235 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("pencil.tiff"));
4236 item.setTarget(this.id());
4237 item.setAction(Foundation.selector("editButtonClicked:"));
4238 // Add a menu representation for text mode of toolbar
4239 NSMenuItem toolbarMenu = NSMenuItem.itemWithTitle(LocaleFactory.localizedString(TOOLBAR_EDIT),
4240 Foundation.selector("editButtonClicked:"), StringUtils.EMPTY);
4241 NSMenu editMenu = NSMenu.menu();
4242 editMenu.setAutoenablesItems(true);
4243 editMenu.setDelegate(editMenuDelegate.id());
4244 toolbarMenu.setSubmenu(editMenu);
4245 item.setMenuFormRepresentation(toolbarMenu);
4246 return item;
4247 case TOOLBAR_DELETE:
4248 item.setLabel(LocaleFactory.localizedString(TOOLBAR_DELETE));
4249 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_DELETE));
4250 item.setToolTip(LocaleFactory.localizedString("Delete file"));
4251 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("delete.tiff", 32));
4252 item.setTarget(this.id());
4253 item.setAction(Foundation.selector("deleteFileButtonClicked:"));
4254 return item;
4255 case TOOLBAR_NEW_FOLDER:
4256 item.setLabel(LocaleFactory.localizedString(TOOLBAR_NEW_FOLDER));
4257 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_NEW_FOLDER));
4258 item.setToolTip(LocaleFactory.localizedString("Create New Folder"));
4259 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("newfolder.tiff", 32));
4260 item.setTarget(this.id());
4261 item.setAction(Foundation.selector("createFolderButtonClicked:"));
4262 return item;
4263 case TOOLBAR_NEW_BOOKMARK:
4264 item.setLabel(LocaleFactory.localizedString(TOOLBAR_NEW_BOOKMARK));
4265 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_NEW_BOOKMARK));
4266 item.setToolTip(LocaleFactory.localizedString("New Bookmark"));
4267 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("bookmark", 32));
4268 item.setTarget(this.id());
4269 item.setAction(Foundation.selector("addBookmarkButtonClicked:"));
4270 return item;
4271 case TOOLBAR_DISCONNECT:
4272 item.setLabel(LocaleFactory.localizedString(TOOLBAR_DISCONNECT));
4273 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_DISCONNECT));
4274 item.setToolTip(LocaleFactory.localizedString("Disconnect from server"));
4275 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("eject.tiff", 32));
4276 item.setTarget(this.id());
4277 item.setAction(Foundation.selector("disconnectButtonClicked:"));
4278 return item;
4279 case TOOLBAR_TERMINAL:
4280 final ApplicationFinder finder = ApplicationFinderFactory.get();
4281 final Application application
4282 = finder.getDescription(preferences.getProperty("terminal.bundle.identifier"));
4283 item.setLabel(application.getName());
4284 item.setPaletteLabel(application.getName());
4285 item.setImage(IconCacheFactory.<NSImage>get().applicationIcon(application, 32));
4286 item.setTarget(this.id());
4287 item.setAction(Foundation.selector("openTerminalButtonClicked:"));
4288 return item;
4289 case TOOLBAR_ARCHIVE:
4290 item.setLabel(LocaleFactory.localizedString("Archive", "Archive"));
4291 item.setPaletteLabel(LocaleFactory.localizedString("Archive", "Archive"));
4292 item.setImage(IconCacheFactory.<NSImage>get().applicationIcon(new Application("com.apple.archiveutility"), 32));
4293 item.setTarget(this.id());
4294 item.setAction(Foundation.selector("archiveButtonClicked:"));
4295 return item;
4296 case TOOLBAR_QUICKLOOK:
4297 item.setLabel(LocaleFactory.localizedString(TOOLBAR_QUICKLOOK));
4298 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_QUICKLOOK));
4299 if(quicklook.isAvailable()) {
4300 quicklookButton = NSButton.buttonWithFrame(new NSRect(29, 23));
4301 quicklookButton.setBezelStyle(NSButtonCell.NSTexturedRoundedBezelStyle);
4302 quicklookButton.setImage(IconCacheFactory.<NSImage>get().iconNamed("NSQuickLookTemplate"));
4303 quicklookButton.sizeToFit();
4304 quicklookButton.setTarget(this.id());
4305 quicklookButton.setAction(Foundation.selector("quicklookButtonClicked:"));
4306 item.setView(quicklookButton);
4308 else {
4309 item.setEnabled(false);
4310 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("notfound.tiff", 32));
4312 return item;
4313 case TOOLBAR_LOG:
4314 item.setLabel(LocaleFactory.localizedString(TOOLBAR_LOG));
4315 item.setPaletteLabel(LocaleFactory.localizedString(TOOLBAR_LOG));
4316 item.setToolTip(LocaleFactory.localizedString("Toggle Log Drawer"));
4317 item.setImage(IconCacheFactory.<NSImage>get().iconNamed("log", 32));
4318 item.setTarget(this.id());
4319 item.setAction(Foundation.selector("toggleLogDrawer:"));
4320 return item;
4322 // Returning null will inform the toolbar this kind of item is not supported.
4323 return null;
4326 @Outlet
4327 private NSButton quicklookButton;
4330 * @param toolbar Window toolbar
4331 * @return The default configuration of toolbar items
4333 @Override
4334 public NSArray toolbarDefaultItemIdentifiers(NSToolbar toolbar) {
4335 return NSArray.arrayWithObjects(
4336 TOOLBAR_NEW_CONNECTION,
4337 NSToolbarItem.NSToolbarSeparatorItemIdentifier,
4338 TOOLBAR_QUICK_CONNECT,
4339 TOOLBAR_TOOLS,
4340 NSToolbarItem.NSToolbarSeparatorItemIdentifier,
4341 TOOLBAR_REFRESH,
4342 TOOLBAR_EDIT,
4343 NSToolbarItem.NSToolbarFlexibleSpaceItemIdentifier,
4344 TOOLBAR_DISCONNECT
4349 * @param toolbar Window toolbar
4350 * @return All available toolbar items
4352 @Override
4353 public NSArray toolbarAllowedItemIdentifiers(NSToolbar toolbar) {
4354 return NSArray.arrayWithObjects(
4355 TOOLBAR_NEW_CONNECTION,
4356 TOOLBAR_BROWSER_VIEW,
4357 TOOLBAR_TRANSFERS,
4358 TOOLBAR_QUICK_CONNECT,
4359 TOOLBAR_TOOLS,
4360 TOOLBAR_REFRESH,
4361 TOOLBAR_ENCODING,
4362 TOOLBAR_SYNCHRONIZE,
4363 TOOLBAR_DOWNLOAD,
4364 TOOLBAR_UPLOAD,
4365 TOOLBAR_EDIT,
4366 TOOLBAR_DELETE,
4367 TOOLBAR_NEW_FOLDER,
4368 TOOLBAR_NEW_BOOKMARK,
4369 TOOLBAR_GET_INFO,
4370 TOOLBAR_WEBVIEW,
4371 TOOLBAR_TERMINAL,
4372 TOOLBAR_ARCHIVE,
4373 TOOLBAR_QUICKLOOK,
4374 TOOLBAR_LOG,
4375 TOOLBAR_DISCONNECT,
4376 NSToolbarItem.NSToolbarCustomizeToolbarItemIdentifier,
4377 NSToolbarItem.NSToolbarSpaceItemIdentifier,
4378 NSToolbarItem.NSToolbarSeparatorItemIdentifier,
4379 NSToolbarItem.NSToolbarFlexibleSpaceItemIdentifier
4383 @Override
4384 public NSArray toolbarSelectableItemIdentifiers(NSToolbar toolbar) {
4385 return NSArray.array();
4389 * Overrriden to remove any listeners from the session
4391 @Override
4392 public void invalidate() {
4393 if(quicklook.isAvailable()) {
4394 if(quicklook.isOpen()) {
4395 quicklook.close();
4398 bookmarkTable.setDelegate(null);
4399 bookmarkTable.setDataSource(null);
4400 bookmarkModel.invalidate();
4402 browserListView.setDelegate(null);
4403 browserListView.setDataSource(null);
4404 browserListModel.invalidate();
4406 browserOutlineView.setDelegate(null);
4407 browserOutlineView.setDataSource(null);
4408 browserOutlineModel.invalidate();
4410 toolbar.setDelegate(null);
4411 toolbarItems.clear();
4413 browserListColumnsFactory.clear();
4414 browserOutlineColumnsFactory.clear();
4415 bookmarkTableColumnFactory.clear();
4417 quickConnectPopup.setDelegate(null);
4418 quickConnectPopup.setDataSource(null);
4420 archiveMenu.setDelegate(null);
4421 editMenu.setDelegate(null);
4423 super.invalidate();