Java tutorial
package ch.cyberduck.ui.cocoa; /* * Copyright (c) 2005 David Kocher. All rights reserved. * http://cyberduck.ch/ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Bug fixes, suggestions and comments should be sent to: * dkocher@cyberduck.ch */ import ch.cyberduck.binding.Action; import ch.cyberduck.binding.Delegate; import ch.cyberduck.binding.Outlet; import ch.cyberduck.binding.ProxyController; import ch.cyberduck.binding.SheetController; import ch.cyberduck.binding.WindowController; import ch.cyberduck.binding.application.*; import ch.cyberduck.binding.foundation.NSArray; import ch.cyberduck.binding.foundation.NSAttributedString; import ch.cyberduck.binding.foundation.NSDictionary; import ch.cyberduck.binding.foundation.NSEnumerator; import ch.cyberduck.binding.foundation.NSIndexSet; import ch.cyberduck.binding.foundation.NSNotification; import ch.cyberduck.binding.foundation.NSNotificationCenter; import ch.cyberduck.binding.foundation.NSObject; import ch.cyberduck.binding.foundation.NSRange; import ch.cyberduck.binding.foundation.NSString; import ch.cyberduck.core.*; import ch.cyberduck.core.aquaticprime.LicenseFactory; import ch.cyberduck.core.bonjour.RendezvousCollection; import ch.cyberduck.core.editor.DefaultEditorListener; import ch.cyberduck.core.editor.Editor; import ch.cyberduck.core.editor.EditorFactory; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.ConnectionCanceledException; import ch.cyberduck.core.features.Location; import ch.cyberduck.core.features.Move; import ch.cyberduck.core.features.Touch; import ch.cyberduck.core.local.Application; import ch.cyberduck.core.local.ApplicationQuitCallback; import ch.cyberduck.core.local.BrowserLauncherFactory; import ch.cyberduck.core.local.TemporaryFileServiceFactory; import ch.cyberduck.core.pasteboard.HostPasteboard; import ch.cyberduck.core.pasteboard.PathPasteboard; import ch.cyberduck.core.pasteboard.PathPasteboardFactory; import ch.cyberduck.core.preferences.Preferences; import ch.cyberduck.core.preferences.PreferencesFactory; import ch.cyberduck.core.resources.IconCacheFactory; import ch.cyberduck.core.serializer.HostDictionary; import ch.cyberduck.core.ssl.SSLSession; import ch.cyberduck.core.threading.BackgroundAction; import ch.cyberduck.core.threading.DefaultMainAction; import ch.cyberduck.core.threading.TransferBackgroundAction; import ch.cyberduck.core.threading.WindowMainAction; import ch.cyberduck.core.threading.WorkerBackgroundAction; import ch.cyberduck.core.transfer.DisabledTransferErrorCallback; import ch.cyberduck.core.transfer.DownloadTransfer; import ch.cyberduck.core.transfer.SyncTransfer; import ch.cyberduck.core.transfer.Transfer; import ch.cyberduck.core.transfer.TransferAction; import ch.cyberduck.core.transfer.TransferAdapter; import ch.cyberduck.core.transfer.TransferCallback; import ch.cyberduck.core.transfer.TransferItem; import ch.cyberduck.core.transfer.TransferOptions; import ch.cyberduck.core.transfer.TransferProgress; import ch.cyberduck.core.transfer.TransferPrompt; import ch.cyberduck.core.transfer.UploadTransfer; import ch.cyberduck.core.worker.DisconnectWorker; import ch.cyberduck.core.worker.MountWorker; import ch.cyberduck.core.worker.SearchWorker; import ch.cyberduck.core.worker.SessionListWorker; import ch.cyberduck.ui.browser.Column; import ch.cyberduck.ui.browser.DownloadDirectoryFinder; import ch.cyberduck.ui.browser.PathReloadFinder; import ch.cyberduck.ui.browser.RegexFilter; import ch.cyberduck.ui.browser.SearchFilterFactory; import ch.cyberduck.ui.browser.UploadDirectoryFinder; import ch.cyberduck.ui.browser.UploadTargetFinder; import ch.cyberduck.ui.cocoa.delegate.ArchiveMenuDelegate; import ch.cyberduck.ui.cocoa.delegate.CopyURLMenuDelegate; import ch.cyberduck.ui.cocoa.delegate.EditMenuDelegate; import ch.cyberduck.ui.cocoa.delegate.OpenURLMenuDelegate; import ch.cyberduck.ui.cocoa.delegate.URLMenuDelegate; import ch.cyberduck.ui.cocoa.quicklook.QLPreviewPanel; import ch.cyberduck.ui.cocoa.quicklook.QLPreviewPanelController; import ch.cyberduck.ui.cocoa.quicklook.QuickLook; import ch.cyberduck.ui.cocoa.quicklook.QuickLookFactory; import ch.cyberduck.ui.cocoa.view.BookmarkCell; import ch.cyberduck.ui.cocoa.view.OutlineCell; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.rococoa.Foundation; import org.rococoa.ID; import org.rococoa.Rococoa; import org.rococoa.Selector; import org.rococoa.cocoa.CGFloat; import org.rococoa.cocoa.foundation.NSInteger; import org.rococoa.cocoa.foundation.NSPoint; import org.rococoa.cocoa.foundation.NSRect; import org.rococoa.cocoa.foundation.NSSize; import org.rococoa.cocoa.foundation.NSUInteger; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; public class BrowserController extends WindowController implements ProgressListener, TranscriptListener, NSToolbar.Delegate, NSMenu.Validation, QLPreviewPanelController { private static Logger log = Logger.getLogger(BrowserController.class); private final BookmarkCollection bookmarks = BookmarkCollection.defaultCollection(); private final BrowserToolbarFactory browserToolbarFactory = new BrowserToolbarFactory(this); private final NSNotificationCenter notificationCenter = NSNotificationCenter.defaultCenter(); public final BrowserToolbarValidator browserToolbarValidator = new BrowserToolbarValidator(this); /** * */ private Session<?> session; /** * Log Drawer */ private TranscriptController transcript; private final QuickLook quicklook = QuickLookFactory.get(); private Preferences preferences = PreferencesFactory.get(); /** * Hide files beginning with '.' */ private boolean showHiddenFiles; private Filter<Path> filenameFilter; { if (PreferencesFactory.get().getBoolean("browser.showHidden")) { this.filenameFilter = new NullFilter<Path>(); this.showHiddenFiles = true; } else { this.filenameFilter = new RegexFilter(); this.showHiddenFiles = false; } } private final NSTextFieldCell outlineCellPrototype = OutlineCell.outlineCell(); private final NSImageCell imageCellPrototype = NSImageCell.imageCell(); private final NSTextFieldCell textCellPrototype = NSTextFieldCell.textFieldCell(); private final NSTextFieldCell filenameCellPrototype = NSTextFieldCell.textFieldCell(); private final TableColumnFactory browserListColumnsFactory = new TableColumnFactory(); private final TableColumnFactory browserOutlineColumnsFactory = new TableColumnFactory(); private final TableColumnFactory bookmarkTableColumnFactory = new TableColumnFactory(); // setting appearance attributes() private final NSLayoutManager layoutManager = NSLayoutManager.layoutManager(); @Delegate private BrowserOutlineViewModel browserOutlineModel; @Outlet private NSOutlineView browserOutlineView; @Delegate private AbstractBrowserTableDelegate browserOutlineViewDelegate; @Delegate private BrowserListViewModel browserListModel; @Outlet private NSTableView browserListView; @Delegate private AbstractBrowserTableDelegate browserListViewDelegate; private NSToolbar toolbar; private final Navigation navigation = new Navigation(); private PathPasteboard pasteboard; private ListProgressListener listener = new PromptLimitedListProgressListener(this); /** * Caching files listings of previously listed directories */ private PathCache cache = new PathCache(preferences.getInteger("browser.cache.size")); private List<Editor> editors = new ArrayList<Editor>(); public BrowserController() { this.loadBundle(); } @Override protected String getBundleName() { return "Browser"; } protected void validateToolbar() { toolbar.validateVisibleItems(); } public static void updateBookmarkTableRowHeight() { for (BrowserController controller : MainController.getBrowsers()) { controller._updateBookmarkCell(); } } public static void updateBrowserTableAttributes() { for (BrowserController controller : MainController.getBrowsers()) { controller._updateBrowserAttributes(controller.browserListView); controller._updateBrowserAttributes(controller.browserOutlineView); } } public static void updateBrowserTableColumns() { for (BrowserController controller : MainController.getBrowsers()) { controller._updateBrowserColumns(controller.browserListView, controller.browserListViewDelegate); controller._updateBrowserColumns(controller.browserOutlineView, controller.browserOutlineViewDelegate); } } @Override public void awakeFromNib() { super.awakeFromNib(); // Configure Toolbar this.toolbar = NSToolbar.toolbarWithIdentifier("Cyberduck Toolbar"); this.toolbar.setDelegate((this.id())); this.toolbar.setAllowsUserCustomization(true); this.toolbar.setAutosavesConfiguration(true); this.window.setToolbar(toolbar); this.window.makeFirstResponder(quickConnectPopup); this._updateBrowserColumns(browserListView, browserListViewDelegate); this._updateBrowserColumns(browserOutlineView, browserOutlineViewDelegate); if (preferences.getBoolean("browser.transcript.open")) { this.logDrawer.open(); } /*if(LicenseFactory.find().equals(LicenseFactory.EMPTY_LICENSE)) { this.addDonateWindowTitle(); }*/ this.selectBookmarks(BookmarkSwitchSegement.bookmarks); } protected Comparator<Path> getComparator() { return this.getSelectedBrowserDelegate().getSortingComparator(); } protected Filter<Path> getFilter() { return this.filenameFilter; } public PathPasteboard getPasteboard() { return pasteboard; } protected void setFilter(final Filter<Path> filter) { if (log.isDebugEnabled()) { log.debug(String.format("Set path filter to %s", filter)); } if (null == filter) { this.searchField.setStringValue(StringUtils.EMPTY); this.filenameFilter = SearchFilterFactory.create(this.showHiddenFiles); } else { this.filenameFilter = filter; } } public void setShowHiddenFiles(boolean showHidden) { if (showHidden) { this.filenameFilter = SearchFilterFactory.NULL_FILTER; this.showHiddenFiles = true; } else { this.filenameFilter = SearchFilterFactory.HIDDEN_FILTER; this.showHiddenFiles = false; } } public boolean isShowHiddenFiles() { return this.showHiddenFiles; } /** * Marks the current browser as the first responder */ private void getFocus() { NSView view; if (this.getSelectedTabView() == BrowserTab.bookmarks) { view = bookmarkTable; } else { if (this.isMounted()) { view = this.getSelectedBrowserView(); } else { view = quickConnectPopup; } } this.setStatus(); window.makeFirstResponder(view); } /** * Make the browser reload its content. Will make use of the cache. */ protected void reload() { if (this.isMounted()) { this.reload(workdir, Collections.singleton(workdir), this.getSelectedPaths(), false); } else { final NSTableView browser = this.getSelectedBrowserView(); final BrowserTableDataSource model = this.getSelectedBrowserModel(); model.render(browser, Collections.emptyList()); this.setStatus(); } } /** * Make the browser reload its content. Invalidates the cache. * * @param workdir Use working directory as the current root of the browser * @param selected The items to be selected */ protected void reload(final Path workdir, final List<Path> changed, final List<Path> selected) { this.reload(workdir, new PathReloadFinder().find(changed), selected, true); } /** * Make the browser reload its content. Invalidates the cache. * * @param workdir Use working directory as the current root of the browser * @param folders Folders to render * @param selected The items to be selected */ protected void reload(final Path workdir, final Set<Path> folders, final List<Path> selected) { this.reload(workdir, folders, selected, true); } /** * Make the browser reload its content. Invalidates the cache. * * @param workdir Use working directory as the current root of the browser * @param folders Folders to render * @param selected The items to be selected * @param invalidate Invalidate the cache before rendering */ protected void reload(final Path workdir, final Set<Path> folders, final List<Path> selected, final boolean invalidate) { if (log.isDebugEnabled()) { log.debug(String.format("Reload data with selected files %s", selected)); } final BrowserTableDataSource model = this.getSelectedBrowserModel(); final NSTableView browser = this.getSelectedBrowserView(); if (folders.isEmpty()) { // Render empty browser model.render(browser, Collections.emptyList()); } if (null == workdir) { this.setNavigation(false); // Render empty browser model.render(browser, Collections.emptyList()); } else { for (final Path folder : folders) { if (invalidate) { // Invalidate cache cache.invalidate(folder); } else { if (cache.isCached(folder)) { reload(browser, model, workdir, selected, folder); return; } } // Delay render until path is cached in the background this.background(new WorkerBackgroundAction<AttributedList<Path>>(this, session, cache, new SessionListWorker(cache, folder, listener) { @Override public void cleanup(final AttributedList<Path> list) { // Put into cache super.cleanup(list); // Update the working directory if listing is successful if (!(AttributedList.<Path>emptyList() == list)) { // Reload browser reload(browser, model, workdir, selected, folder); } } })); } this.setStatus(); } } /** * @param browser Browser view * @param model Browser Model * @param workdir Use working directory as the current root of the browser * @param selected Selected files in browser * @param folder Folder to render */ private void reload(final NSTableView browser, final BrowserTableDataSource model, final Path workdir, final List<Path> selected, final Path folder) { this.workdir = workdir; this.setNavigation(workdir != null); this.setStatus(); model.render(browser, Collections.singletonList(folder)); this.select(selected); } private void select(final List<Path> selected) { final NSTableView browser = this.getSelectedBrowserView(); if (CollectionUtils.isEqualCollection(this.getSelectedPaths(), selected)) { return; } browser.deselectAll(null); for (Path path : selected) { this.select(path, true, true); } } /** * @param file Path to select * @param expand Keep previous selection * @param scroll Scroll to selection */ private void select(final Path file, final boolean expand, final boolean scroll) { final NSTableView browser = this.getSelectedBrowserView(); final BrowserTableDataSource model = this.getSelectedBrowserModel(); if (log.isDebugEnabled()) { log.debug(String.format("Select row for reference %s", file)); } int row = model.indexOf(browser, file); if (-1 == row) { log.warn(String.format("Failed to find row for %s", file)); return; } final NSInteger index = new NSInteger(row); browser.selectRowIndexes(NSIndexSet.indexSetWithIndex(index), expand); if (scroll) { browser.scrollRowToVisible(index); } } private void updateQuickLookSelection(final List<Path> selected) { if (quicklook.isAvailable()) { final List<TransferItem> downloads = new ArrayList<TransferItem>(); for (Path path : selected) { if (!path.isFile()) { continue; } downloads.add(new TransferItem(path, TemporaryFileServiceFactory.get().create(session.getHost().getUuid(), path))); } if (downloads.size() > 0) { final Transfer download = new DownloadTransfer(session.getHost(), downloads); final TransferOptions options = new TransferOptions(); background(new TransferBackgroundAction(this, session, cache, new TransferAdapter() { @Override public void progress(final TransferProgress status) { message(status.getProgress()); } }, this, this, download, options, new TransferPrompt() { @Override public TransferAction prompt(final TransferItem item) { return TransferAction.comparison; } @Override public boolean isSelected(final TransferItem file) { return true; } @Override public void message(final String message) { BrowserController.this.message(message); } }, new DisabledTransferErrorCallback()) { @Override public void cleanup() { super.cleanup(); final List<Local> previews = new ArrayList<Local>(); for (TransferItem download : downloads) { previews.add(download.local); } // Change files in Quick Look quicklook.select(previews); // Open Quick Look Preview Panel quicklook.open(); } @Override public String getActivity() { return LocaleFactory.localizedString("Quick Look", "Status"); } }); } } } /** * @return The first selected path found or null if there is no selection */ protected Path getSelectedPath() { final List<Path> s = this.getSelectedPaths(); if (s.size() > 0) { return s.get(0); } return null; } /** * @return All selected paths or an empty list if there is no selection */ protected List<Path> getSelectedPaths() { final AbstractBrowserTableDelegate delegate = this.getSelectedBrowserDelegate(); final NSTableView view = this.getSelectedBrowserView(); final NSIndexSet iterator = view.selectedRowIndexes(); final List<Path> selected = new ArrayList<Path>(); for (NSUInteger index = iterator.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = iterator .indexGreaterThanIndex(index)) { final Path file = delegate.pathAtRow(index.intValue()); if (null == file) { break; } selected.add(file); } return selected; } protected int getSelectionCount() { return this.getSelectedBrowserView().numberOfSelectedRows().intValue(); } private static NSPoint cascade = new NSPoint(0, 0); @Override public void setWindow(NSWindow window) { // Save frame rectangle window.setFrameAutosaveName("Browser"); window.setTitle(preferences.getProperty("application.name")); //window.setMiniwindowImage(IconCacheFactory.<NSImage>get().iconNamed("cyberduck-document.icns")); window.setMiniwindowImage(IconCacheFactory.<NSImage>get().iconNamed("cloud.icns")); window.setMovableByWindowBackground(true); window.setCollectionBehavior(window.collectionBehavior() | NSWindow.NSWindowCollectionBehavior.NSWindowCollectionBehaviorFullScreenPrimary); window.setContentMinSize(new NSSize(600d, 200d)); super.setWindow(window); // Accept file promises from history tab window.registerForDraggedTypes(NSArray.arrayWithObject(NSPasteboard.FilesPromisePboardType)); cascade = this.cascade(cascade); } @Override public void windowWillClose(final NSNotification notification) { // Convert from lower left to top left coordinates cascade = new NSPoint(this.window().frame().origin.x.doubleValue(), this.window().frame().origin.y.doubleValue() + this.window().frame().size.height.doubleValue()); super.windowWillClose(notification); } /** * NSDraggingDestination protocol implementation * * @return NSDragOperation */ @Action public NSUInteger draggingEntered(final NSDraggingInfo sender) { return this.draggingUpdated(sender); } /** * NSDraggingDestination protocol implementation */ @Action public NSUInteger draggingUpdated(final NSDraggingInfo sender) { final NSPasteboard pasteboard = sender.draggingPasteboard(); if (pasteboard.types() .indexOfObject(NSString.stringWithString(NSPasteboard.FilesPromisePboardType)) != null) { final NSView hit = sender.draggingDestinationWindow().contentView().hitTest(sender.draggingLocation()); if (hit != null) { if (hit.equals(bookmarkSwitchView)) { return NSDraggingInfo.NSDragOperationCopy; } } } return NSDraggingInfo.NSDragOperationNone; } /** * NSDraggingDestination protocol implementation */ @Action public boolean prepareForDragOperation(final NSDraggingInfo sender) { // Continue to performDragOperation return true; } /** * NSDraggingDestination protocol implementation */ @Action public boolean performDragOperation(final NSDraggingInfo sender) { for (Host bookmark : HostPasteboard.getPasteboard()) { final Host duplicate = new HostDictionary().deserialize(bookmark.serialize(SerializerFactory.get())); // Make sure a new UUID is assigned for duplicate duplicate.setUuid(null); bookmarks.add(0, duplicate); } return true; } @Outlet private NSDrawer logDrawer; @Action public void drawerDidOpen(NSNotification notification) { preferences.setProperty("browser.transcript.open", true); } @Action public void drawerDidClose(NSNotification notification) { preferences.setProperty("browser.transcript.open", false); transcript.clear(); } @Action public NSSize drawerWillResizeContents_toSize(final NSDrawer sender, final NSSize contentSize) { return contentSize; } public void setLogDrawer(NSDrawer drawer) { this.logDrawer = drawer; this.transcript = new TranscriptController() { @Override public boolean isOpen() { return logDrawer.state() == NSDrawer.OpenState; } }; this.logDrawer.setContentView(this.transcript.getLogView()); this.logDrawer.setDelegate(this.id()); } private NSTitlebarAccessoryViewController accessoryView; public void setDonateButton(NSButton button) { /*if(!Factory.Platform.osversion.matches("10\\.(7|8|9).*")) { button.setTitle(LocaleFactory.localizedString("Get a donation key!", "License")); button.setAction(Foundation.selector("donateMenuClicked:")); button.sizeToFit(); NSView view = NSView.create(); view.setFrameSize(new NSSize(button.frame().size.width.doubleValue() + 10d, button.frame().size.height.doubleValue())); view.addSubview(button); accessoryView = NSTitlebarAccessoryViewController.create(); accessoryView.setLayoutAttribute(NSTitlebarAccessoryViewController.NSLayoutAttributeRight); accessoryView.setView(view); }*/ } private void addDonateWindowTitle() { /*if(!Factory.Platform.osversion.matches("10\\.(7|8|9).*")) { window.addTitlebarAccessoryViewController(accessoryView); }*/ } public void removeDonateWindowTitle() { if (!Factory.Platform.osversion.matches("10\\.(7|8|9).*")) { accessoryView.removeFromParentViewController(); } } protected enum BrowserTab { bookmarks, list, outline; public static BrowserTab byPosition(final int position) { return BrowserTab.values()[position]; } } protected BrowserTab getSelectedTabView() { return BrowserTab.byPosition(browserTabView.indexOfTabViewItem(browserTabView.selectedTabViewItem())); } private NSTabView browserTabView; public void setBrowserTabView(NSTabView browserTabView) { this.browserTabView = browserTabView; } /** * @return The currently selected browser view (which is either an outlineview or a plain tableview) */ public NSTableView getSelectedBrowserView() { switch (BrowserSwitchSegement.byPosition(preferences.getInteger("browser.view"))) { case list: return browserListView; case outline: default: return browserOutlineView; } } /** * @return The datasource of the currently selected browser view */ public BrowserTableDataSource getSelectedBrowserModel() { switch (BrowserSwitchSegement.byPosition(preferences.getInteger("browser.view"))) { case list: return browserListModel; case outline: default: return browserOutlineModel; } } public AbstractBrowserTableDelegate getSelectedBrowserDelegate() { switch (BrowserSwitchSegement.byPosition(preferences.getInteger("browser.view"))) { case list: return browserListViewDelegate; case outline: default: return browserOutlineViewDelegate; } } @Outlet private NSMenu editMenu; @Delegate private EditMenuDelegate editMenuDelegate; public void setEditMenu(NSMenu editMenu) { this.editMenu = editMenu; this.editMenuDelegate = new EditMenuDelegate() { @Override protected Path getEditable() { final Path selected = BrowserController.this.getSelectedPath(); if (null == selected) { return null; } if (isEditable(selected)) { return selected; } return null; } @Override protected ID getTarget() { return BrowserController.this.id(); } }; this.editMenu.setDelegate(editMenuDelegate.id()); } public EditMenuDelegate getEditMenuDelegate() { return editMenuDelegate; } @Outlet private NSMenu urlMenu; @Delegate private URLMenuDelegate urlMenuDelegate; public void setUrlMenu(NSMenu urlMenu) { this.urlMenu = urlMenu; this.urlMenuDelegate = new CopyURLMenuDelegate() { @Override protected Session<?> getSession() { return BrowserController.this.getSession(); } @Override protected List<Path> getSelected() { final List<Path> s = BrowserController.this.getSelectedPaths(); if (s.isEmpty()) { if (BrowserController.this.isMounted()) { return Collections.singletonList(BrowserController.this.workdir()); } } return s; } }; this.urlMenu.setDelegate(urlMenuDelegate.id()); } @Outlet private NSMenu openUrlMenu; @Delegate private URLMenuDelegate openUrlMenuDelegate; public void setOpenUrlMenu(NSMenu openUrlMenu) { this.openUrlMenu = openUrlMenu; this.openUrlMenuDelegate = new OpenURLMenuDelegate() { @Override protected Session<?> getSession() { return BrowserController.this.getSession(); } @Override protected List<Path> getSelected() { final List<Path> s = BrowserController.this.getSelectedPaths(); if (s.isEmpty()) { if (BrowserController.this.isMounted()) { return Collections.singletonList(BrowserController.this.workdir()); } } return s; } }; this.openUrlMenu.setDelegate(openUrlMenuDelegate.id()); } @Outlet private NSMenu archiveMenu; @Delegate private ArchiveMenuDelegate archiveMenuDelegate; public void setArchiveMenu(NSMenu archiveMenu) { this.archiveMenu = archiveMenu; this.archiveMenuDelegate = new ArchiveMenuDelegate(); this.archiveMenu.setDelegate(archiveMenuDelegate.id()); } private void setRecessedBezelStyle(final NSButton b) { b.setBezelStyle(NSButton.NSRecessedBezelStyle); b.setButtonType(NSButton.NSMomentaryPushButtonButton); b.setImagePosition(NSCell.NSImageLeft); b.setFont(NSFont.boldSystemFontOfSize(11f)); b.setShowsBorderOnlyWhileMouseInside(true); } @Action public void sortBookmarksByNickame(final ID sender) { bookmarks.sortByNickname(); this.reloadBookmarks(); } @Action public void sortBookmarksByHostname(final ID sender) { bookmarks.sortByHostname(); this.reloadBookmarks(); } @Action public void sortBookmarksByProtocol(final ID sender) { bookmarks.sortByProtocol(); this.reloadBookmarks(); } private NSSegmentedControl bookmarkSwitchView; private enum BookmarkSwitchSegement { browser { @Override public NSImage image() { final NSImage image = IconCacheFactory.<NSImage>get().iconNamed(String.format("%s.tiff", "outline"), 16); image.setTemplate(true); return image; } }, bookmarks, history, rendezvous; public NSImage image() { return IconCacheFactory.<NSImage>get().iconNamed(String.format("%s.tiff", name()), 16); } public static BookmarkSwitchSegement byPosition(final int position) { return BookmarkSwitchSegement.values()[position]; } } public void setBookmarkSwitchView(NSSegmentedControl bookmarkSwitchView) { this.bookmarkSwitchView = bookmarkSwitchView; this.bookmarkSwitchView.setSegmentCount(4); final NSSegmentedCell cell = Rococoa.cast(this.bookmarkSwitchView.cell(), NSSegmentedCell.class); cell.setToolTip_forSegment(LocaleFactory.localizedString("Browser"), BookmarkSwitchSegement.browser.ordinal()); this.bookmarkSwitchView.setImage_forSegment(BookmarkSwitchSegement.browser.image(), BookmarkSwitchSegement.browser.ordinal()); cell.setToolTip_forSegment(LocaleFactory.localizedString("Bookmarks"), BookmarkSwitchSegement.bookmarks.ordinal()); this.bookmarkSwitchView.setImage_forSegment(BookmarkSwitchSegement.bookmarks.image(), BookmarkSwitchSegement.bookmarks.ordinal()); cell.setToolTip_forSegment(LocaleFactory.localizedString("History"), BookmarkSwitchSegement.history.ordinal()); this.bookmarkSwitchView.setImage_forSegment(BookmarkSwitchSegement.history.image(), BookmarkSwitchSegement.history.ordinal()); cell.setToolTip_forSegment(LocaleFactory.localizedString("Bonjour"), BookmarkSwitchSegement.rendezvous.ordinal()); this.bookmarkSwitchView.setImage_forSegment(BookmarkSwitchSegement.rendezvous.image(), BookmarkSwitchSegement.rendezvous.ordinal()); this.bookmarkSwitchView.setTarget(this.id()); this.bookmarkSwitchView.setAction(Foundation.selector("bookmarkSwitchButtonClicked:")); } @Action public void bookmarkSwitchMenuClicked(final NSMenuItem sender) { switch (this.getSelectedTabView()) { case bookmarks: this.selectBrowser(BrowserSwitchSegement.byPosition(preferences.getInteger("browser.view"))); break; case list: case outline: this.selectBookmarks(BookmarkSwitchSegement.bookmarks); break; } } @Action public void bookmarkSwitchButtonClicked(final ID sender) { // Toggle final BookmarkSwitchSegement selected = BookmarkSwitchSegement .byPosition(bookmarkSwitchView.selectedSegment()); switch (selected) { case browser: this.selectBrowser(BrowserSwitchSegement.outline); break; case bookmarks: case history: case rendezvous: this.selectBookmarks(selected); break; } } protected enum BrowserSwitchSegement { list, outline; public NSImage image() { return IconCacheFactory.<NSImage>get().iconNamed(String.format("%s.tiff", name()), 16); } public static BrowserSwitchSegement byPosition(final int position) { return BrowserSwitchSegement.values()[position]; } } @Action public void browserSwitchButtonClicked(final NSSegmentedControl sender) { // Highlight selected browser view this.selectBrowser(BrowserSwitchSegement.byPosition(sender.selectedSegment())); } @Action public void browserSwitchMenuClicked(final NSMenuItem sender) { // Highlight selected browser view this.selectBrowser(BrowserSwitchSegement.byPosition(sender.tag())); } private void selectBrowser(final BrowserSwitchSegement selected) { bookmarkSwitchView.setSelectedSegment(BookmarkSwitchSegement.browser.ordinal()); this.setNavigation(this.isMounted()); switch (selected) { case list: browserTabView.selectTabViewItemAtIndex(BrowserTab.list.ordinal()); break; case outline: browserTabView.selectTabViewItemAtIndex(BrowserTab.outline.ordinal()); break; } // Save selected browser view preferences.setProperty("browser.view", selected.ordinal()); // Remove any custom file filter this.setFilter(null); // Update from model this.reload(); // Focus on browser view this.getFocus(); } private void selectBookmarks(final BookmarkSwitchSegement selected) { bookmarkSwitchView.setSelectedSegment(selected.ordinal()); this.setNavigation(false); // Display bookmarks browserTabView.selectTabViewItemAtIndex(BrowserTab.bookmarks.ordinal()); final AbstractHostCollection source; switch (selected) { case history: source = HistoryCollection.defaultCollection(); break; case rendezvous: source = RendezvousCollection.defaultCollection(); break; case bookmarks: default: source = bookmarks; break; } if (!source.isLoaded()) { browserSpinner.startAnimation(null); source.addListener(new AbstractCollectionListener<Host>() { @Override public void collectionLoaded() { invoke(new WindowMainAction(BrowserController.this) { @Override public void run() { browserSpinner.stopAnimation(null); bookmarkTable.setGridStyleMask(NSTableView.NSTableViewSolidHorizontalGridLineMask); } }); source.removeListener(this); } }); } else { browserSpinner.stopAnimation(null); bookmarkTable.setGridStyleMask(NSTableView.NSTableViewSolidHorizontalGridLineMask); } bookmarkModel.setSource(source); this.setBookmarkFilter(null); this.reloadBookmarks(); if (this.isMounted()) { int row = this.bookmarkModel.getSource().indexOf(session.getHost()); if (row != -1) { this.bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(new NSInteger(row)), false); this.bookmarkTable.scrollRowToVisible(new NSInteger(row)); } } this.getFocus(); } /** * Reload bookmark table from currently selected model */ public void reloadBookmarks() { bookmarkTable.reloadData(); this.setStatus(); } private abstract class AbstractBrowserOutlineViewDelegate extends AbstractBrowserTableDelegate implements NSOutlineView.Delegate { protected AbstractBrowserOutlineViewDelegate(final NSTableColumn selectedColumn) { super(selectedColumn); } @Action public String outlineView_toolTipForCell_rect_tableColumn_item_mouseLocation(NSOutlineView t, NSCell cell, ID rect, NSTableColumn c, NSObject item, NSPoint mouseLocation) { return this.tooltip(cache.lookup(new NSObjectPathReference(item))); } @Action public String outlineView_typeSelectStringForTableColumn_item(final NSOutlineView view, final NSTableColumn tableColumn, final NSObject item) { if (tableColumn.identifier().equals(Column.filename.name())) { return browserOutlineModel.outlineView_objectValueForTableColumn_byItem(view, tableColumn, item) .toString(); } return null; } @Override protected void setBrowserColumnSortingIndicator(NSImage image, String columnIdentifier) { browserOutlineView.setIndicatorImage_inTableColumn(image, browserOutlineView.tableColumnWithIdentifier(columnIdentifier)); } @Override protected Path pathAtRow(final int row) { if (row < browserOutlineView.numberOfRows().intValue()) { return cache.lookup(new NSObjectPathReference(browserOutlineView.itemAtRow(new NSInteger(row)))); } log.warn(String.format("No item at row %d", row)); return null; } } private abstract class AbstractBrowserListViewDelegate<E> extends AbstractBrowserTableDelegate implements NSTableView.Delegate { protected AbstractBrowserListViewDelegate(final NSTableColumn selectedColumn) { super(selectedColumn); } public String tableView_toolTipForCell_rect_tableColumn_row_mouseLocation(NSTableView t, NSCell cell, ID rect, NSTableColumn c, NSInteger row, NSPoint mouseLocation) { return this.tooltip(browserListModel.get(workdir()).get(row.intValue())); } @Override protected void setBrowserColumnSortingIndicator(NSImage image, String columnIdentifier) { browserListView.setIndicatorImage_inTableColumn(image, browserListView.tableColumnWithIdentifier(columnIdentifier)); } public String tableView_typeSelectStringForTableColumn_row(final NSTableView view, final NSTableColumn tableColumn, final NSInteger row) { if (tableColumn.identifier().equals(Column.filename.name())) { return browserListModel.tableView_objectValueForTableColumn_row(view, tableColumn, row).toString(); } return null; } @Override protected Path pathAtRow(int row) { final AttributedList<Path> children = browserListModel.get(workdir()); if (row < children.size()) { return children.get(row); } log.warn(String.format("No item at row %d", row)); return null; } } private abstract class AbstractBrowserTableDelegate extends AbstractPathTableDelegate { protected AbstractBrowserTableDelegate(final NSTableColumn selectedColumn) { super(selectedColumn); } @Override public boolean isColumnRowEditable(NSTableColumn column, int row) { if (preferences.getBoolean("browser.editable")) { return column.identifier().equals(Column.filename.name()); } return false; } @Override public void tableRowDoubleClicked(final ID sender) { BrowserController.this.insideButtonClicked(sender); } public void spaceKeyPressed(final ID sender) { quicklookButtonClicked(sender); } @Override public void deleteKeyPressed(final ID sender) { BrowserController.this.deleteFileButtonClicked(sender); } @Override public void tableColumnClicked(final NSTableView view, final NSTableColumn tableColumn) { if (this.selectedColumnIdentifier().equals(tableColumn.identifier())) { this.setSortedAscending(!this.isSortedAscending()); } else { // Remove sorting indicator on previously selected column this.setBrowserColumnSortingIndicator(null, this.selectedColumnIdentifier()); // Set the newly selected column this.setSelectedColumn(tableColumn); // Update the default value preferences.setProperty("browser.sort.column", this.selectedColumnIdentifier()); } this.setBrowserColumnSortingIndicator( this.isSortedAscending() ? IconCacheFactory.<NSImage>get().iconNamed("NSAscendingSortIndicator") : IconCacheFactory.<NSImage>get().iconNamed("NSDescendingSortIndicator"), tableColumn.identifier()); reload(); } @Override public void columnDidResize(final String columnIdentifier, final float width) { preferences.setProperty(String.format("browser.column.%s.width", columnIdentifier), width); } @Override public void selectionDidChange(NSNotification notification) { final List<Path> selected = getSelectedPaths(); if (quicklook.isOpen()) { updateQuickLookSelection(selected); } if (preferences.getBoolean("browser.info.inspector")) { InfoController c = InfoControllerFactory.get(BrowserController.this); if (null != c) { // Currently open info panel c.setFiles(selected); } } } protected abstract Path pathAtRow(int row); protected abstract void setBrowserColumnSortingIndicator(NSImage image, String columnIdentifier); private static final double kSwipeGestureLeft = 1.000000; private static final double kSwipeGestureRight = -1.000000; private static final double kSwipeGestureUp = 1.000000; private static final double kSwipeGestureDown = -1.000000; /** * Available in Mac OS X v10.6 and later. * * @param event Swipe event */ @Action public void swipeWithEvent(NSEvent event) { if (event.deltaX().doubleValue() == kSwipeGestureLeft) { BrowserController.this.backButtonClicked(event.id()); } else if (event.deltaX().doubleValue() == kSwipeGestureRight) { BrowserController.this.forwardButtonClicked(event.id()); } else if (event.deltaY().doubleValue() == kSwipeGestureUp) { NSInteger row = getSelectedBrowserView().selectedRow(); NSInteger next; if (-1 == row.intValue()) { // No current selection next = new NSInteger(0); } else { next = new NSInteger(row.longValue() - 1); } BrowserController.this.getSelectedBrowserView().selectRowIndexes(NSIndexSet.indexSetWithIndex(next), false); } else if (event.deltaY().doubleValue() == kSwipeGestureDown) { NSInteger row = getSelectedBrowserView().selectedRow(); NSInteger next; if (-1 == row.intValue()) { // No current selection next = new NSInteger(0); } else { next = new NSInteger(row.longValue() + 1); } BrowserController.this.getSelectedBrowserView().selectRowIndexes(NSIndexSet.indexSetWithIndex(next), false); } } } /** * QuickLook support for 10.6+ * * @param panel The Preview Panel looking for a controller. * @return * @ Sent to each object in the responder chain to find a controller. */ @Override public boolean acceptsPreviewPanelControl(QLPreviewPanel panel) { return true; } /** * QuickLook support for 10.6+ * The receiver should setup the preview panel (data source, delegate, binding, etc.) here. * * @param panel The Preview Panel the receiver will control. * @ Sent to the object taking control of the Preview Panel. */ @Override public void beginPreviewPanelControl(QLPreviewPanel panel) { quicklook.willBeginQuickLook(); } /** * QuickLook support for 10.6+ * The receiver should unsetup the preview panel (data source, delegate, binding, etc.) here. * * @param panel The Preview Panel that the receiver will stop controlling. * @ Sent to the object in control of the Preview Panel just before stopping its control. */ @Override public void endPreviewPanelControl(QLPreviewPanel panel) { quicklook.didEndQuickLook(); } public void setBrowserOutlineView(NSOutlineView view) { browserOutlineView = view; // receive drag events from types browserOutlineView.registerForDraggedTypes(NSArray.arrayWithObjects(NSPasteboard.URLPboardType, // Accept files dragged from the Finder for uploading NSPasteboard.FilenamesPboardType, // Accept file promises made myself NSPasteboard.FilesPromisePboardType)); // setting appearance attributes() this._updateBrowserAttributes(browserOutlineView); // selection properties browserOutlineView.setAllowsMultipleSelection(true); browserOutlineView.setAllowsEmptySelection(true); browserOutlineView.setAllowsColumnResizing(true); browserOutlineView.setAllowsColumnSelection(false); browserOutlineView.setAllowsColumnReordering(true); browserOutlineView .setRowHeight(new CGFloat(layoutManager .defaultLineHeightForFont( NSFont.systemFontOfSize(preferences.getFloat("browser.font.size"))) .intValue() + 2)); { NSTableColumn c = browserOutlineColumnsFactory.create(Column.filename.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Filename")); c.setMinWidth(new CGFloat(100)); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.filename.name()))); c.setMaxWidth(new CGFloat(1000)); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(outlineCellPrototype); browserOutlineView.addTableColumn(c); browserOutlineView.setOutlineTableColumn(c); } browserOutlineView.setDataSource((browserOutlineModel = new BrowserOutlineViewModel(this, cache)).id()); browserOutlineView.setDelegate((browserOutlineViewDelegate = new AbstractBrowserOutlineViewDelegate( browserOutlineView.tableColumnWithIdentifier(Column.filename.name())) { @Override public void enterKeyPressed(final ID sender) { if (preferences.getBoolean("browser.enterkey.rename")) { if (browserOutlineView.numberOfSelectedRows().intValue() == 1) { renameFileButtonClicked(sender); } } else { this.tableRowDoubleClicked(sender); } } /** * @see NSOutlineView.Delegate */ @Override public void outlineView_willDisplayCell_forTableColumn_item(NSOutlineView view, NSTextFieldCell cell, NSTableColumn tableColumn, NSObject item) { if (null == item) { return; } final Path path = cache.lookup(new NSObjectPathReference(item)); if (null == path) { return; } if (tableColumn.identifier().equals(Column.filename.name())) { cell.setEditable(session.getFeature(Move.class).isSupported(path)); (Rococoa.cast(cell, OutlineCell.class)).setIcon(browserOutlineModel.iconForPath(path)); } if (!BrowserController.this.isConnected() || !SearchFilterFactory.HIDDEN_FILTER.accept(path)) { cell.setTextColor(NSColor.disabledControlTextColor()); } else { cell.setTextColor(NSColor.controlTextColor()); } } /** * @see NSOutlineView.Delegate */ @Override public boolean outlineView_shouldExpandItem(final NSOutlineView view, final NSObject item) { NSEvent event = NSApplication.sharedApplication().currentEvent(); if (event != null) { if (NSEvent.NSLeftMouseDragged == event.type()) { if (!preferences.getBoolean("browser.view.autoexpand")) { if (log.isDebugEnabled()) { log.debug( "Returning false to #outlineViewShouldExpandItem while dragging because browser.view.autoexpand == false"); } // See tickets #98 and #633 return false; } final NSInteger draggingColumn = view .columnAtPoint(view.convertPoint_fromView(event.locationInWindow(), null)); if (draggingColumn.intValue() != 0) { if (log.isDebugEnabled()) { log.debug(String.format( "Returning false to #outlineViewShouldExpandItem for column %s", draggingColumn)); } // See ticket #60 return false; } } } return true; } @Override public boolean outlineView_isGroupItem(final NSOutlineView view, final NSObject item) { return false; } @Override public void outlineViewItemWillExpand(final NSNotification notification) { final NSObject object = Rococoa.cast(notification.userInfo(), NSDictionary.class) .objectForKey("NSObject"); final NSObjectPathReference reference = new NSObjectPathReference(object); final Path directory = cache.lookup(reference); if (null == directory) { return; } reload(workdir, Collections.singleton(directory), getSelectedPaths(), false); } /** * @see NSOutlineView.Delegate */ @Override public void outlineViewItemDidExpand(final NSNotification notification) { // } @Override public void outlineViewItemWillCollapse(final NSNotification notification) { // } /** * @see NSOutlineView.Delegate */ @Override public void outlineViewItemDidCollapse(final NSNotification notification) { setStatus(); } @Override protected boolean isTypeSelectSupported() { return true; } }).id()); } public void setBrowserListView(NSTableView view) { browserListView = view; // receive drag events from types browserListView.registerForDraggedTypes(NSArray.arrayWithObjects(NSPasteboard.URLPboardType, // Accept files dragged from the Finder for uploading NSPasteboard.FilenamesPboardType, // Accept file promises made myself NSPasteboard.FilesPromisePboardType)); // setting appearance attributes() this._updateBrowserAttributes(browserListView); // selection properties browserListView.setAllowsMultipleSelection(true); browserListView.setAllowsEmptySelection(true); browserListView.setAllowsColumnResizing(true); browserListView.setAllowsColumnSelection(false); browserListView.setAllowsColumnReordering(true); browserListView .setRowHeight(new CGFloat(layoutManager .defaultLineHeightForFont( NSFont.systemFontOfSize(preferences.getFloat("browser.font.size"))) .intValue() + 2)); { NSTableColumn c = browserListColumnsFactory.create(Column.icon.name()); c.headerCell().setStringValue(StringUtils.EMPTY); c.setMinWidth((20)); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.icon.name()))); c.setMaxWidth((20)); c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask); c.setDataCell(imageCellPrototype); c.dataCell().setAlignment(NSText.NSCenterTextAlignment); browserListView.addTableColumn(c); } { NSTableColumn c = browserListColumnsFactory.create(Column.filename.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Filename")); c.setMinWidth((100)); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.filename.name()))); c.setMaxWidth((1000)); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(filenameCellPrototype); this.browserListView.addTableColumn(c); } browserListView.setDataSource((browserListModel = new BrowserListViewModel(this, cache)).id()); browserListView.setDelegate((browserListViewDelegate = new AbstractBrowserListViewDelegate<Path>( browserListView.tableColumnWithIdentifier(Column.filename.name())) { @Override public void enterKeyPressed(final ID sender) { if (preferences.getBoolean("browser.enterkey.rename")) { if (browserListView.numberOfSelectedRows().intValue() == 1) { renameFileButtonClicked(sender); } } else { this.tableRowDoubleClicked(sender); } } @Override public void tableView_willDisplayCell_forTableColumn_row(NSTableView view, NSTextFieldCell cell, NSTableColumn tableColumn, NSInteger row) { final String identifier = tableColumn.identifier(); final Path path = browserListModel.get(BrowserController.this.workdir()).get(row.intValue()); if (identifier.equals(Column.filename.name())) { cell.setEditable(session.getFeature(Move.class).isSupported(path)); } if (cell.isKindOfClass(Foundation.getClass(NSTextFieldCell.class.getSimpleName()))) { if (!BrowserController.this.isConnected() || !SearchFilterFactory.HIDDEN_FILTER.accept(path)) { cell.setTextColor(NSColor.disabledControlTextColor()); } else { cell.setTextColor(NSColor.controlTextColor()); } } } @Override protected boolean isTypeSelectSupported() { return true; } }).id()); } protected void _updateBrowserAttributes(NSTableView tableView) { tableView.setUsesAlternatingRowBackgroundColors(preferences.getBoolean("browser.alternatingRows")); if (preferences.getBoolean("browser.horizontalLines") && preferences.getBoolean("browser.verticalLines")) { tableView.setGridStyleMask(new NSUInteger(NSTableView.NSTableViewSolidHorizontalGridLineMask.intValue() | NSTableView.NSTableViewSolidVerticalGridLineMask.intValue())); } else if (preferences.getBoolean("browser.verticalLines")) { tableView.setGridStyleMask(NSTableView.NSTableViewSolidVerticalGridLineMask); } else if (preferences.getBoolean("browser.horizontalLines")) { tableView.setGridStyleMask(NSTableView.NSTableViewSolidHorizontalGridLineMask); } else { tableView.setGridStyleMask(NSTableView.NSTableViewGridNone); } } protected void _updateBookmarkCell() { final int size = preferences.getInteger("bookmark.icon.size"); final double width = size * 1.5; final NSTableColumn c = bookmarkTable.tableColumnWithIdentifier(BookmarkTableDataSource.Column.icon.name()); c.setMinWidth(width); c.setMaxWidth(width); c.setWidth(width); // Notify the table about the changed row height. bookmarkTable.noteHeightOfRowsWithIndexesChanged(NSIndexSet.indexSetWithIndexesInRange( NSRange.NSMakeRange(new NSUInteger(0), new NSUInteger(bookmarkTable.numberOfRows())))); } private void _updateBrowserColumns(final NSTableView table, final AbstractBrowserTableDelegate delegate) { table.removeTableColumn(table.tableColumnWithIdentifier(Column.size.name())); if (preferences.getBoolean(String.format("browser.column.%s", Column.size.name()))) { NSTableColumn c = browserListColumnsFactory.create(Column.size.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Size")); c.setMinWidth(50f); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.size.name()))); c.setMaxWidth(150f); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(textCellPrototype); table.addTableColumn(c); } table.removeTableColumn(table.tableColumnWithIdentifier(Column.modified.name())); if (preferences.getBoolean(String.format("browser.column.%s", Column.modified.name()))) { NSTableColumn c = browserListColumnsFactory.create(Column.modified.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Modified")); c.setMinWidth(100f); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.modified.name()))); c.setMaxWidth(500); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(textCellPrototype); table.addTableColumn(c); } table.removeTableColumn(table.tableColumnWithIdentifier(Column.owner.name())); if (preferences.getBoolean(String.format("browser.column.%s", Column.owner.name()))) { NSTableColumn c = browserListColumnsFactory.create(Column.owner.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Owner")); c.setMinWidth(50); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.owner.name()))); c.setMaxWidth(500); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(textCellPrototype); table.addTableColumn(c); } table.removeTableColumn(table.tableColumnWithIdentifier(Column.group.name())); if (preferences.getBoolean(String.format("browser.column.%s", Column.group.name()))) { NSTableColumn c = browserListColumnsFactory.create(Column.group.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Group")); c.setMinWidth(50); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.group.name()))); c.setMaxWidth(500); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(textCellPrototype); table.addTableColumn(c); } table.removeTableColumn(table.tableColumnWithIdentifier(Column.permission.name())); if (preferences.getBoolean(String.format("browser.column.%s", Column.permission.name()))) { NSTableColumn c = browserListColumnsFactory.create(Column.permission.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Permissions")); c.setMinWidth(100); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.permission.name()))); c.setMaxWidth(800); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(textCellPrototype); table.addTableColumn(c); } table.removeTableColumn(table.tableColumnWithIdentifier(Column.kind.name())); if (preferences.getBoolean(String.format("browser.column.%s", Column.kind.name()))) { NSTableColumn c = browserListColumnsFactory.create(Column.kind.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Kind")); c.setMinWidth(50); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.kind.name()))); c.setMaxWidth(500); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(textCellPrototype); table.addTableColumn(c); } table.removeTableColumn(table.tableColumnWithIdentifier(Column.extension.name())); if (preferences.getBoolean(String.format("browser.column.%s", Column.extension.name()))) { NSTableColumn c = browserListColumnsFactory.create(Column.extension.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Extension")); c.setMinWidth(50); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.extension.name()))); c.setMaxWidth(500); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(textCellPrototype); table.addTableColumn(c); } table.removeTableColumn(table.tableColumnWithIdentifier(Column.region.name())); if (preferences.getBoolean(String.format("browser.column.%s", Column.region.name()))) { NSTableColumn c = browserListColumnsFactory.create(Column.region.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Region")); c.setMinWidth(50); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.region.name()))); c.setMaxWidth(500); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(textCellPrototype); table.addTableColumn(c); } table.removeTableColumn(table.tableColumnWithIdentifier(Column.version.name())); if (preferences.getBoolean(String.format("browser.column.%s", Column.version.name()))) { NSTableColumn c = browserListColumnsFactory.create(Column.version.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Version")); c.setMinWidth(50); c.setWidth(preferences.getFloat(String.format("browser.column.%s.width", Column.version.name()))); c.setMaxWidth(500); c.setResizingMask( NSTableColumn.NSTableColumnAutoresizingMask | NSTableColumn.NSTableColumnUserResizingMask); c.setDataCell(textCellPrototype); table.addTableColumn(c); } NSTableColumn selected = table.tableColumnWithIdentifier(preferences.getProperty("browser.sort.column")); if (null == selected) { selected = table.tableColumnWithIdentifier(Column.filename.name()); } delegate.setSelectedColumn(selected); table.setIndicatorImage_inTableColumn(this.getSelectedBrowserDelegate().isSortedAscending() ? IconCacheFactory.<NSImage>get().iconNamed("NSAscendingSortIndicator") : IconCacheFactory.<NSImage>get().iconNamed("NSDescendingSortIndicator"), selected); table.sizeToFit(); table.setAutosaveName("browser.autosave"); table.setAutosaveTableColumns(true); this.reload(); } @Delegate private BookmarkTableDataSource bookmarkModel; @Outlet private NSTableView bookmarkTable; @Delegate private AbstractTableDelegate<Host> bookmarkTableDelegate; public void setBookmarkTable(NSTableView view) { bookmarkTable = view; bookmarkTable.setSelectionHighlightStyle(NSTableView.NSTableViewSelectionHighlightStyleSourceList); bookmarkTable.setDataSource((this.bookmarkModel = new BookmarkTableDataSource(this)).id()); { NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkTableDataSource.Column.icon.name()); c.headerCell().setStringValue(StringUtils.EMPTY); c.setResizingMask(NSTableColumn.NSTableColumnNoResizing); c.setDataCell(imageCellPrototype); bookmarkTable.addTableColumn(c); } { NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkTableDataSource.Column.bookmark.name()); c.headerCell().setStringValue(LocaleFactory.localizedString("Bookmarks")); c.setMinWidth(150); c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask); c.setDataCell(BookmarkCell.bookmarkCell()); bookmarkTable.addTableColumn(c); } { NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkTableDataSource.Column.status.name()); c.headerCell().setStringValue(StringUtils.EMPTY); c.setMinWidth(40); c.setWidth(40); c.setMaxWidth(40); c.setResizingMask(NSTableColumn.NSTableColumnAutoresizingMask); c.setDataCell(imageCellPrototype); c.dataCell().setAlignment(NSText.NSCenterTextAlignment); bookmarkTable.addTableColumn(c); } bookmarkTable.setDelegate((bookmarkTableDelegate = new AbstractTableDelegate<Host>( bookmarkTable.tableColumnWithIdentifier(BookmarkTableDataSource.Column.bookmark.name())) { @Override public String tooltip(Host bookmark) { return new HostUrlProvider().get(bookmark); } @Override public void tableRowDoubleClicked(final ID sender) { BrowserController.this.connectBookmarkButtonClicked(sender); } @Override public void enterKeyPressed(final ID sender) { this.tableRowDoubleClicked(sender); } @Override public void deleteKeyPressed(final ID sender) { if (bookmarkModel.getSource().allowsDelete()) { BrowserController.this.deleteBookmarkButtonClicked(sender); } } @Override public void tableColumnClicked(NSTableView view, NSTableColumn tableColumn) { } @Override public void selectionDidChange(NSNotification notification) { addBookmarkButton.setEnabled(bookmarkModel.getSource().allowsAdd()); final int selected = bookmarkTable.numberOfSelectedRows().intValue(); editBookmarkButton.setEnabled(bookmarkModel.getSource().allowsEdit() && selected == 1); deleteBookmarkButton.setEnabled(bookmarkModel.getSource().allowsDelete() && selected > 0); } @Action public CGFloat tableView_heightOfRow(NSTableView view, NSInteger row) { final int size = preferences.getInteger("bookmark.icon.size"); if (BookmarkCell.SMALL_BOOKMARK_SIZE == size) { return new CGFloat(18); } if (BookmarkCell.MEDIUM_BOOKMARK_SIZE == size) { return new CGFloat(45); } return new CGFloat(70); } @Override public boolean isTypeSelectSupported() { return true; } @Action public String tableView_typeSelectStringForTableColumn_row(NSTableView view, NSTableColumn tableColumn, NSInteger row) { return BookmarkNameProvider.toString(bookmarkModel.getSource().get(row.intValue())); } @Action public boolean tableView_isGroupRow(NSTableView view, NSInteger row) { return false; } private static final double kSwipeGestureLeft = 1.000000; private static final double kSwipeGestureRight = -1.000000; private static final double kSwipeGestureUp = 1.000000; private static final double kSwipeGestureDown = -1.000000; /** * Available in Mac OS X v10.6 and later. * * @param event Swipe event */ @Action public void swipeWithEvent(NSEvent event) { if (event.deltaY().doubleValue() == kSwipeGestureUp) { NSInteger row = bookmarkTable.selectedRow(); NSInteger next; if (-1 == row.intValue()) { // No current selection next = new NSInteger(0); } else { next = new NSInteger(row.longValue() - 1); } bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(next), false); } else if (event.deltaY().doubleValue() == kSwipeGestureDown) { NSInteger row = bookmarkTable.selectedRow(); NSInteger next; if (-1 == row.intValue()) { // No current selection next = new NSInteger(0); } else { next = new NSInteger(row.longValue() + 1); } bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(next), false); } } }).id()); // receive drag events from types bookmarkTable.registerForDraggedTypes( NSArray.arrayWithObjects(NSPasteboard.URLPboardType, NSPasteboard.StringPboardType, // Accept bookmark files dragged from the Finder NSPasteboard.FilenamesPboardType, // Accept file promises made myself NSPasteboard.FilesPromisePboardType)); this._updateBookmarkCell(); final int size = preferences.getInteger("bookmark.icon.size"); if (BookmarkCell.SMALL_BOOKMARK_SIZE == size) { bookmarkTable.setRowHeight(new CGFloat(18)); } else if (BookmarkCell.MEDIUM_BOOKMARK_SIZE == size) { bookmarkTable.setRowHeight(new CGFloat(45)); } else { bookmarkTable.setRowHeight(new CGFloat(70)); } // setting appearance attributes() bookmarkTable.setUsesAlternatingRowBackgroundColors(preferences.getBoolean("browser.alternatingRows")); bookmarkTable.setGridStyleMask(NSTableView.NSTableViewGridNone); // selection properties bookmarkTable.setAllowsMultipleSelection(true); bookmarkTable.setAllowsEmptySelection(true); bookmarkTable.setAllowsColumnResizing(false); bookmarkTable.setAllowsColumnSelection(false); bookmarkTable.setAllowsColumnReordering(false); bookmarkTable.sizeToFit(); } public NSTableView getBookmarkTable() { return bookmarkTable; } public BookmarkTableDataSource getBookmarkModel() { return bookmarkModel; } @Outlet private NSComboBox quickConnectPopup; private ProxyController quickConnectPopupModel = new QuickConnectModel(); public void setQuickConnectPopup(NSComboBox quickConnectPopup) { this.quickConnectPopup = quickConnectPopup; this.quickConnectPopup.setTarget(this.id()); this.quickConnectPopup.setCompletes(true); this.quickConnectPopup.setAction(Foundation.selector("quickConnectSelectionChanged:")); // Make sure action is not sent twice. this.quickConnectPopup.cell().setSendsActionOnEndEditing(false); this.quickConnectPopup.setUsesDataSource(true); this.quickConnectPopup.setDataSource(quickConnectPopupModel.id()); this.quickConnectPopup.setFocusRingType(NSView.NSFocusRingType.NSFocusRingTypeNone.ordinal()); notificationCenter.addObserver(this.id(), Foundation.selector("quickConnectWillPopUp:"), NSComboBox.ComboBoxWillPopUpNotification, this.quickConnectPopup); this.quickConnectWillPopUp(null); } public NSComboBox getQuickConnectPopup() { return quickConnectPopup; } private class QuickConnectModel extends ProxyController implements NSComboBox.DataSource { @Override public NSInteger numberOfItemsInComboBox(final NSComboBox combo) { return new NSInteger(bookmarks.size()); } @Override public NSObject comboBox_objectValueForItemAtIndex(final NSComboBox sender, final NSInteger row) { return NSString.stringWithString(BookmarkNameProvider.toString(bookmarks.get(row.intValue()))); } } public void quickConnectWillPopUp(NSNotification notification) { int size = bookmarks.size(); quickConnectPopup.setNumberOfVisibleItems(size > 10 ? new NSInteger(10) : new NSInteger(size)); } @Action public void quickConnectSelectionChanged(final ID sender) { String input = quickConnectPopup.stringValue(); if (StringUtils.isBlank(input)) { return; } input = input.trim(); // First look for equivalent bookmarks for (Host h : bookmarks) { if (BookmarkNameProvider.toString(h).equals(input)) { this.mount(h); return; } } // Try to parse the input as a URL and extract protocol, hostname, username and password if any. this.mount(HostParser.parse(input)); } @Outlet private NSSearchField searchField; public void setSearchField(NSSearchField searchField) { this.searchField = searchField; if (this.searchField.respondsToSelector(Foundation.selector("setSendsWholeSearchString:"))) { // calls its search action method when the user clicks the search button (or presses Return) this.searchField.setSendsWholeSearchString(false); } if (this.searchField.respondsToSelector(Foundation.selector("setSendsSearchStringImmediately:"))) { this.searchField.setSendsSearchStringImmediately(false); } this.searchField.setTarget(this.id()); this.searchField.setAction(Foundation.selector("searchFieldTextDidChange:")); // Make sure action is not sent twice. this.searchField.cell().setSendsActionOnEndEditing(false); this.notificationCenter.addObserver(this.id(), Foundation.selector("searchFieldTextDidEndEditing:"), NSControl.NSControlTextDidEndEditingNotification, this.searchField); } @Action public void searchButtonClicked(final ID sender) { this.window().makeFirstResponder(searchField); } @Action public void searchFieldTextDidChange(NSNotification notification) { final String input = searchField.stringValue(); switch (this.getSelectedTabView()) { case bookmarks: this.setBookmarkFilter(input); break; case list: case outline: // Setup search filter this.setFilter(SearchFilterFactory.create(input, showHiddenFiles)); // Reload with current cache this.reload(); break; } } @Action public void searchFieldTextDidEndEditing(NSNotification notification) { switch (this.getSelectedTabView()) { case list: case outline: // Setup search filter final String input = searchField.stringValue(); // Setup search filter final Filter<Path> filter = SearchFilterFactory.create(input, showHiddenFiles); this.setFilter(filter); if (StringUtils.isBlank(input)) { // Reload with current cache this.reload(); } else { final NSObject action = notification.userInfo().objectForKey("NSTextMovement"); if (null == action) { return; } if (Integer.valueOf(action.toString()) == NSText.NSReturnTextMovement) { final NSAlert alert = NSAlert.alert( MessageFormat.format(LocaleFactory.localizedString("Search for {0}"), input), MessageFormat.format( LocaleFactory.localizedString("Do you want to search in {0} recursively?"), workdir.getName()), LocaleFactory.localizedString("Search"), LocaleFactory.localizedString("Cancel"), null); this.alert(alert, new SheetCallback() { @Override public void callback(int returncode) { if (returncode == DEFAULT_OPTION) { // Delay render until path is cached in the background background(new WorkerBackgroundAction<AttributedList<Path>>(BrowserController.this, session, cache, new SearchWorker(workdir, filenameFilter, cache, listener) { @Override public void cleanup(final AttributedList<Path> list) { // Reload browser reload(); } })); } } }); } } } } private void setBookmarkFilter(final String searchString) { if (StringUtils.isBlank(searchString)) { searchField.setStringValue(StringUtils.EMPTY); bookmarkModel.setFilter(null); } else { bookmarkModel.setFilter(new HostFilter() { @Override public boolean accept(Host host) { return StringUtils.lowerCase(BookmarkNameProvider.toString(host)) .contains(searchString.toLowerCase(Locale.ROOT)) || ((null != host.getComment()) && StringUtils.lowerCase(host.getComment()) .contains(searchString.toLowerCase(Locale.ROOT))) || ((null != host.getCredentials().getUsername()) && StringUtils.lowerCase(host.getCredentials().getUsername()) .contains(searchString.toLowerCase(Locale.ROOT))) || StringUtils.lowerCase(host.getHostname()) .contains(searchString.toLowerCase(Locale.ROOT)); } }); } this.reloadBookmarks(); } // ---------------------------------------------------------- // Manage Bookmarks // ---------------------------------------------------------- @Action public void connectBookmarkButtonClicked(final ID sender) { if (bookmarkTable.numberOfSelectedRows().intValue() == 1) { final Host selected = bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue()); this.mount(selected); } } @Outlet private NSButton editBookmarkButton; public void setEditBookmarkButton(NSButton editBookmarkButton) { this.editBookmarkButton = editBookmarkButton; this.editBookmarkButton.setEnabled(false); this.editBookmarkButton.setTarget(this.id()); this.editBookmarkButton.setAction(Foundation.selector("editBookmarkButtonClicked:")); } @Action public void editBookmarkButtonClicked(final ID sender) { final BookmarkController c = BookmarkControllerFactory .create(bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue())); c.window().makeKeyAndOrderFront(null); } @Action public void duplicateBookmarkButtonClicked(final ID sender) { final Host selected = bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue()); this.selectBookmarks(BookmarkSwitchSegement.bookmarks); final Host duplicate = new HostDictionary().deserialize(selected.serialize(SerializerFactory.get())); // Make sure a new UUID is asssigned for duplicate duplicate.setUuid(null); this.addBookmark(duplicate); } @Outlet private NSButton addBookmarkButton; public void setAddBookmarkButton(NSButton addBookmarkButton) { this.addBookmarkButton = addBookmarkButton; this.addBookmarkButton.setTarget(this.id()); this.addBookmarkButton.setAction(Foundation.selector("addBookmarkButtonClicked:")); } @Action public void addBookmarkButtonClicked(final ID sender) { final Host bookmark; if (this.isMounted()) { Path selected = this.getSelectedPath(); if (null == selected || !selected.isDirectory()) { selected = this.workdir(); } bookmark = new HostDictionary().deserialize(session.getHost().serialize(SerializerFactory.get())); // Make sure a new UUID is asssigned for duplicate bookmark.setUuid(null); bookmark.setDefaultPath(selected.getAbsolute()); } else { bookmark = new Host(ProtocolFactory.forName(preferences.getProperty("connection.protocol.default")), preferences.getProperty("connection.hostname.default"), preferences.getInteger("connection.port.default")); } this.selectBookmarks(BookmarkSwitchSegement.bookmarks); this.addBookmark(bookmark); } public void addBookmark(Host item) { bookmarkModel.setFilter(null); bookmarkModel.getSource().add(item); final int row = bookmarkModel.getSource().lastIndexOf(item); final NSInteger index = new NSInteger(row); bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(index), false); bookmarkTable.scrollRowToVisible(index); final BookmarkController c = BookmarkControllerFactory.create(item); c.window().makeKeyAndOrderFront(null); } @Outlet private NSButton deleteBookmarkButton; public void setDeleteBookmarkButton(NSButton deleteBookmarkButton) { this.deleteBookmarkButton = deleteBookmarkButton; this.deleteBookmarkButton.setEnabled(false); this.deleteBookmarkButton.setTarget(this.id()); this.deleteBookmarkButton.setAction(Foundation.selector("deleteBookmarkButtonClicked:")); } @Action public void deleteBookmarkButtonClicked(final ID sender) { NSIndexSet iterator = bookmarkTable.selectedRowIndexes(); final List<Host> selected = new ArrayList<Host>(); for (NSUInteger index = iterator.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = iterator .indexGreaterThanIndex(index)) { selected.add(bookmarkModel.getSource().get(index.intValue())); } StringBuilder alertText = new StringBuilder( LocaleFactory.localizedString("Do you want to delete the selected bookmark?")); int i = 0; Iterator<Host> iter = selected.iterator(); while (i < 10 && iter.hasNext()) { alertText.append("\n").append(Character.toString('\u2022')).append(" ") .append(BookmarkNameProvider.toString(iter.next())); i++; } if (iter.hasNext()) { alertText.append("\n").append(Character.toString('\u2022')).append(" " + ""); } final NSAlert alert = NSAlert.alert(LocaleFactory.localizedString("Delete Bookmark"), alertText.toString(), LocaleFactory.localizedString("Delete"), LocaleFactory.localizedString("Cancel"), null); this.alert(alert, new SheetCallback() { @Override public void callback(int returncode) { if (returncode == DEFAULT_OPTION) { bookmarkTable.deselectAll(null); bookmarkModel.getSource().removeAll(selected); } } }); } // ---------------------------------------------------------- // Browser navigation // ---------------------------------------------------------- public Navigation getNavigation() { return navigation; } private enum NavigationSegment { back(0), forward(1), up(0); private final int position; NavigationSegment(final int position) { this.position = position; } public int position() { return position; } public static NavigationSegment byPosition(final int position) { return NavigationSegment.values()[position]; } } private NSSegmentedControl navigationButton; public void setNavigationButton(NSSegmentedControl navigationButton) { this.navigationButton = navigationButton; this.navigationButton.setTarget(this.id()); this.navigationButton.setAction(Foundation.selector("navigationButtonClicked:")); final NSSegmentedCell cell = Rococoa.cast(this.navigationButton.cell(), NSSegmentedCell.class); this.navigationButton.setImage_forSegment(IconCacheFactory.<NSImage>get().iconNamed("nav-backward.tiff"), NavigationSegment.back.position()); cell.setToolTip_forSegment(LocaleFactory.localizedString("Back", "Main"), NavigationSegment.back.position()); this.navigationButton.setImage_forSegment(IconCacheFactory.<NSImage>get().iconNamed("nav-forward.tiff"), NavigationSegment.forward.position()); cell.setToolTip_forSegment(LocaleFactory.localizedString("Forward", "Main"), NavigationSegment.forward.position()); } @Action public void navigationButtonClicked(NSSegmentedControl sender) { switch (NavigationSegment.byPosition(sender.selectedSegment())) { case back: this.backButtonClicked(sender.id()); break; case forward: this.forwardButtonClicked(sender.id()); break; } } @Action public void backButtonClicked(final ID sender) { final Path selected = navigation.back(); if (selected != null) { final Path previous = this.workdir(); if (previous.getParent().equals(selected)) { this.setWorkdir(selected, previous); } else { this.setWorkdir(selected); } } } @Action public void forwardButtonClicked(final ID sender) { final Path selected = navigation.forward(); if (selected != null) { this.setWorkdir(selected); } } @Outlet private NSSegmentedControl upButton; public void setUpButton(NSSegmentedControl upButton) { this.upButton = upButton; this.upButton.setTarget(this.id()); this.upButton.setAction(Foundation.selector("upButtonClicked:")); this.upButton.setImage_forSegment(IconCacheFactory.<NSImage>get().iconNamed("nav-up.tiff"), NavigationSegment.up.position()); } @Action public void upButtonClicked(final ID sender) { final Path previous = this.workdir(); this.setWorkdir(previous.getParent(), previous); } private Path workdir; @Outlet private NSPopUpButton pathPopupButton; public void setPathPopup(NSPopUpButton pathPopupButton) { this.pathPopupButton = pathPopupButton; this.pathPopupButton.setTarget(this.id()); this.pathPopupButton.setAction(Foundation.selector("pathPopupSelectionChanged:")); } @Action public void pathPopupSelectionChanged(final NSPopUpButton sender) { final String selected = sender.selectedItem().representedObject(); if (selected != null) { final Path workdir = this.workdir(); Path p = workdir; while (!p.getAbsolute().equals(selected)) { p = p.getParent(); } this.setWorkdir(p); if (workdir.getParent().equals(p)) { this.setWorkdir(p, workdir); } else { this.setWorkdir(p); } } } @Outlet private NSPopUpButton encodingPopup; public void setEncodingPopup(NSPopUpButton encodingPopup) { this.encodingPopup = encodingPopup; this.encodingPopup.setTarget(this.id()); this.encodingPopup.setAction(Foundation.selector("encodingButtonClicked:")); this.encodingPopup.removeAllItems(); this.encodingPopup .addItemsWithTitles(NSArray.arrayWithObjects(new DefaultCharsetProvider().availableCharsets())); this.encodingPopup.selectItemWithTitle(preferences.getProperty("browser.charset.encoding")); } public NSPopUpButton getEncodingPopup() { return encodingPopup; } @Action public void encodingButtonClicked(final NSPopUpButton sender) { this.encodingChanged(sender.titleOfSelectedItem()); } @Action public void encodingMenuClicked(final NSMenuItem sender) { this.encodingChanged(sender.title()); } public void encodingChanged(final String encoding) { if (null == encoding) { return; } this.setEncoding(encoding); if (this.isMounted()) { if (session.getEncoding().equals(encoding)) { return; } session.getHost().setEncoding(encoding); this.mount(session.getHost()); } } /** * @param encoding Character encoding */ private void setEncoding(final String encoding) { this.encodingPopup.selectItemWithTitle(encoding); } // ---------------------------------------------------------- // Drawers // ---------------------------------------------------------- @Action public void toggleLogDrawer(final ID sender) { this.logDrawer.toggle(this.id()); } // ---------------------------------------------------------- // Status // ---------------------------------------------------------- @Outlet protected NSProgressIndicator statusSpinner; public void setStatusSpinner(NSProgressIndicator statusSpinner) { this.statusSpinner = statusSpinner; this.statusSpinner.setDisplayedWhenStopped(false); this.statusSpinner.setIndeterminate(true); } @Outlet protected NSProgressIndicator browserSpinner; public void setBrowserSpinner(NSProgressIndicator browserSpinner) { this.browserSpinner = browserSpinner; } public NSProgressIndicator getBrowserSpinner() { return browserSpinner; } @Outlet private NSTextField statusLabel; public void setStatusLabel(NSTextField statusLabel) { this.statusLabel = statusLabel; } public void setStatus() { final BackgroundAction current = this.getActions().getCurrent(); this.message(null != current ? current.getActivity() : null); } @Override public void stop(final BackgroundAction action) { this.invoke(new DefaultMainAction() { @Override public void run() { statusSpinner.stopAnimation(null); } }); super.stop(action); } @Override public void start(final BackgroundAction action) { this.invoke(new DefaultMainAction() { @Override public void run() { statusSpinner.startAnimation(null); } }); super.start(action); } /** * @param label Status message */ @Override public void message(final String label) { if (StringUtils.isNotBlank(label)) { // Update the status label at the bottom of the browser window statusLabel.setAttributedStringValue( NSAttributedString.attributedStringWithAttributes(label, TRUNCATE_MIDDLE_ATTRIBUTES)); } else { if (getSelectedTabView() == BrowserTab.bookmarks) { statusLabel .setAttributedStringValue( NSAttributedString.attributedStringWithAttributes( String.format("%s %s", bookmarkTable.numberOfRows(), LocaleFactory.localizedString("Bookmarks")), TRUNCATE_MIDDLE_ATTRIBUTES)); } else { // Browser view if (isConnected()) { statusLabel .setAttributedStringValue( NSAttributedString .attributedStringWithAttributes( MessageFormat.format(LocaleFactory.localizedString("{0} Files"), String.valueOf( getSelectedBrowserView().numberOfRows())), TRUNCATE_MIDDLE_ATTRIBUTES)); } else { statusLabel.setStringValue(StringUtils.EMPTY); } } } } @Override public void log(final Type request, final String message) { transcript.log(request, message); } @Outlet private NSButton securityLabel; public void setSecurityLabel(NSButton securityLabel) { this.securityLabel = securityLabel; this.securityLabel.setEnabled(false); this.securityLabel.setTarget(this.id()); this.securityLabel.setAction(Foundation.selector("securityLabelClicked:")); } @Action public void securityLabelClicked(final ID sender) { if (session instanceof SSLSession) { final SSLSession<?> secured = (SSLSession) session; final List<X509Certificate> certificates = secured.getAcceptedIssuers(); try { CertificateStoreFactory.get(this).display(certificates); } catch (CertificateException e) { log.warn(String.format("Failure decoding certificate %s", e.getMessage())); } } } // ---------------------------------------------------------- // Selector methods for the toolbar items // ---------------------------------------------------------- public void quicklookButtonClicked(final ID sender) { if (quicklook.isOpen()) { quicklook.close(); } else { this.updateQuickLookSelection(this.getSelectedPaths()); } } /** * Marks all expanded directories as invalid and tells the * browser table to reload its data * * @param sender Toolbar button */ @Action public void reloadButtonClicked(final ID sender) { if (this.isMounted()) { final Set<Path> folders = new HashSet<Path>(); switch (BrowserSwitchSegement.byPosition(preferences.getInteger("browser.view"))) { case outline: { for (int i = 0; i < browserOutlineView.numberOfRows().intValue(); i++) { final NSObject item = browserOutlineView.itemAtRow(new NSInteger(i)); if (browserOutlineView.isItemExpanded(item)) { final Path folder = cache.lookup(new NSObjectPathReference(item)); if (null == folder) { continue; } folders.add(folder); } } break; } } folders.add(workdir); this.reload(workdir, folders, this.getSelectedPaths(), true); } } /** * Open a new browser with the current selected folder as the working directory * * @param sender Toolbar button */ @Action public void newBrowserButtonClicked(final ID sender) { Path selected = this.getSelectedPath(); if (null == selected || !selected.isDirectory()) { selected = this.workdir(); } BrowserController c = MainController.newDocument(true); final Host host = new HostDictionary().deserialize(session.getHost().serialize(SerializerFactory.get())); host.setDefaultPath(selected.getAbsolute()); c.mount(host); } /** * @param selected File * @return True if the selected path is editable (not a directory and no known binary file) */ protected boolean isEditable(final Path selected) { if (this.isMounted()) { if (session.getHost().getCredentials().isAnonymousLogin()) { return false; } return selected.isFile(); } return false; } @Action public void gotoButtonClicked(final ID sender) { final SheetController sheet = new GotoController(this, cache); sheet.beginSheet(); } @Action public void createFileButtonClicked(final ID sender) { final SheetController sheet = new CreateFileController(this, cache); sheet.beginSheet(); } @Action public void createSymlinkButtonClicked(final ID sender) { final SheetController sheet = new CreateSymlinkController(this, cache); sheet.beginSheet(); } @Action public void duplicateFileButtonClicked(final ID sender) { final SheetController sheet = new DuplicateFileController(this, cache); sheet.beginSheet(); } @Action public void createFolderButtonClicked(final ID sender) { final Location feature = session.getFeature(Location.class); final SheetController sheet = new FolderController(this, cache, feature != null ? feature.getLocations() : Collections.emptySet()); sheet.beginSheet(); } @Action public void renameFileButtonClicked(final ID sender) { final NSTableView browser = this.getSelectedBrowserView(); browser.editRow(browser.columnWithIdentifier(Column.filename.name()), browser.selectedRow(), true); final Path selected = this.getSelectedPath(); if (StringUtils.isNotBlank(selected.getExtension())) { NSText view = browser.currentEditor(); int index = selected.getName().indexOf(selected.getExtension()) - 1; if (index > 0) { view.setSelectedRange(NSRange.NSMakeRange(new NSUInteger(0), new NSUInteger(index))); } } } @Action public void sendCustomCommandClicked(final ID sender) { SheetController sheet = new CommandController(this, session); sheet.beginSheet(); } @Action public void editMenuClicked(final NSMenuItem sender) { final EditorFactory factory = EditorFactory.instance(); for (Path selected : this.getSelectedPaths()) { final Editor editor = factory.create(this, session, new Application(sender.representedObject()), selected); this.edit(editor); } } @Action public void editButtonClicked(final ID sender) { for (Path selected : this.getSelectedPaths()) { this.edit(selected); } } protected void edit(final Path file) { this.edit(EditorFactory.instance().create(this, session, file)); } protected void edit(final Editor editor) { editors.add(editor); this.background( new WorkerBackgroundAction<Transfer>(this, session, editor.open(new ApplicationQuitCallback() { @Override public void callback() { editors.remove(editor); } }, new DisabledTransferErrorCallback(), new DefaultEditorListener(this, session, editor)))); } @Action public void openBrowserButtonClicked(final ID sender) { final DescriptiveUrlBag list; if (this.getSelectionCount() == 1) { list = session.getFeature(UrlProvider.class).toUrl(this.getSelectedPath()); } else { list = session.getFeature(UrlProvider.class).toUrl(this.workdir()); } if (!list.isEmpty()) { BrowserLauncherFactory.get().open(list.find(DescriptiveUrl.Type.http).getUrl()); } } @Action public void infoButtonClicked(final ID sender) { if (this.getSelectionCount() > 0) { InfoController c = InfoControllerFactory.create(this, this.getSelectedPaths()); c.window().makeKeyAndOrderFront(null); } } @Action public void revertFileButtonClicked(final ID sender) { new RevertController(this).revert(this.getSelectedPaths()); } @Action public void deleteFileButtonClicked(final ID sender) { new DeleteController(this).delete(this.getSelectedPaths()); } private NSOpenPanel downloadToPanel; @Action public void downloadToButtonClicked(final ID sender) { downloadToPanel = NSOpenPanel.openPanel(); downloadToPanel.setCanChooseDirectories(true); downloadToPanel.setCanCreateDirectories(true); downloadToPanel.setCanChooseFiles(false); downloadToPanel.setAllowsMultipleSelection(false); downloadToPanel.setPrompt(LocaleFactory.localizedString("Choose")); downloadToPanel.beginSheetForDirectory(new DownloadDirectoryFinder().find(session.getHost()).getAbsolute(), null, this.window, this.id(), Foundation.selector("downloadToPanelDidEnd:returnCode:contextInfo:"), null); } public void downloadToPanelDidEnd_returnCode_contextInfo(final NSOpenPanel sheet, final int returncode, final ID contextInfo) { sheet.orderOut(this.id()); if (returncode == SheetCallback.DEFAULT_OPTION) { if (sheet.filename() != null) { final Local target = LocalFactory.get(sheet.filename()); new DownloadDirectoryFinder().save(session.getHost(), target); final List<TransferItem> downloads = new ArrayList<TransferItem>(); for (Path file : this.getSelectedPaths()) { downloads.add(new TransferItem(file, LocalFactory.get(target, file.getName()))); } this.transfer(new DownloadTransfer(session.getHost(), downloads), Collections.emptyList()); } } downloadToPanel = null; } @Outlet private NSSavePanel downloadAsPanel; @Action public void downloadAsButtonClicked(final ID sender) { downloadAsPanel = NSSavePanel.savePanel(); downloadAsPanel.setMessage(LocaleFactory.localizedString("Download the selected file to")); downloadAsPanel.setNameFieldLabel(LocaleFactory.localizedString("Download As:")); downloadAsPanel.setPrompt(LocaleFactory.localizedString("Download", "Transfer")); downloadAsPanel.setCanCreateDirectories(true); downloadAsPanel.beginSheetForDirectory(new DownloadDirectoryFinder().find(session.getHost()).getAbsolute(), this.getSelectedPath().getName(), this.window, this.id(), Foundation.selector("downloadAsPanelDidEnd:returnCode:contextInfo:"), null); } public void downloadAsPanelDidEnd_returnCode_contextInfo(final NSSavePanel sheet, final int returncode, final ID contextInfo) { sheet.orderOut(this.id()); if (returncode == SheetCallback.DEFAULT_OPTION) { if (sheet.filename() != null) { final Local target = LocalFactory.get(sheet.filename()); new DownloadDirectoryFinder().save(session.getHost(), target.getParent()); final List<TransferItem> downloads = Collections .singletonList(new TransferItem(this.getSelectedPath(), target)); this.transfer(new DownloadTransfer(session.getHost(), downloads), Collections.emptyList()); } } } @Outlet private NSOpenPanel syncPanel; @Action public void syncButtonClicked(final ID sender) { final Path selection; if (this.getSelectionCount() == 1 && this.getSelectedPath().isDirectory()) { selection = this.getSelectedPath(); } else { selection = this.workdir(); } syncPanel = NSOpenPanel.openPanel(); syncPanel.setCanChooseDirectories(selection.isDirectory()); syncPanel.setTreatsFilePackagesAsDirectories(true); syncPanel.setCanChooseFiles(selection.isFile()); syncPanel.setCanCreateDirectories(true); syncPanel.setAllowsMultipleSelection(false); syncPanel.setMessage( MessageFormat.format(LocaleFactory.localizedString("Synchronize {0} with"), selection.getName())); syncPanel.setPrompt(LocaleFactory.localizedString("Choose")); syncPanel.beginSheetForDirectory(new UploadDirectoryFinder().find(session.getHost()).getAbsolute(), null, this.window, this.id(), Foundation.selector("syncPanelDidEnd:returnCode:contextInfo:"), null //context info ); } public void syncPanelDidEnd_returnCode_contextInfo(final NSOpenPanel sheet, final int returncode, final ID contextInfo) { sheet.orderOut(this.id()); if (returncode == SheetCallback.DEFAULT_OPTION) { if (sheet.filename() != null) { final Local target = LocalFactory.get(sheet.filename()); new UploadDirectoryFinder().save(session.getHost(), target.getParent()); final Path selected; if (this.getSelectionCount() == 1 && this.getSelectedPath().isDirectory()) { selected = this.getSelectedPath(); } else { selected = this.workdir(); } this.transfer(new SyncTransfer(session.getHost(), new TransferItem(selected, target))); } } } @Action public void downloadButtonClicked(final ID sender) { final List<TransferItem> downloads = new ArrayList<TransferItem>(); final Local folder = new DownloadDirectoryFinder().find(session.getHost()); for (Path file : this.getSelectedPaths()) { downloads.add(new TransferItem(file, LocalFactory.get(folder, file.getName()))); } this.transfer(new DownloadTransfer(session.getHost(), downloads), Collections.emptyList()); } private NSOpenPanel uploadPanel; private NSButton uploadPanelHiddenFilesCheckbox; @Action public void uploadButtonClicked(final ID sender) { uploadPanel = NSOpenPanel.openPanel(); uploadPanel.setCanChooseDirectories(true); uploadPanel.setCanChooseFiles(session.getFeature(Touch.class) .isSupported(new UploadTargetFinder(workdir).find(this.getSelectedPath()))); uploadPanel.setCanCreateDirectories(false); uploadPanel.setTreatsFilePackagesAsDirectories(true); uploadPanel.setAllowsMultipleSelection(true); uploadPanel.setPrompt(LocaleFactory.localizedString("Upload", "Transfer")); if (uploadPanel.respondsToSelector(Foundation.selector("setShowsHiddenFiles:"))) { uploadPanelHiddenFilesCheckbox = NSButton.buttonWithFrame(new NSRect(0, 0)); uploadPanelHiddenFilesCheckbox.setTitle(LocaleFactory.localizedString("Show Hidden Files")); uploadPanelHiddenFilesCheckbox.setTarget(this.id()); uploadPanelHiddenFilesCheckbox.setAction(Foundation.selector("uploadPanelSetShowHiddenFiles:")); uploadPanelHiddenFilesCheckbox.setButtonType(NSButton.NSSwitchButton); uploadPanelHiddenFilesCheckbox.setState(NSCell.NSOffState); uploadPanelHiddenFilesCheckbox.sizeToFit(); uploadPanel.setAccessoryView(uploadPanelHiddenFilesCheckbox); } uploadPanel.beginSheetForDirectory(new UploadDirectoryFinder().find(session.getHost()).getAbsolute(), null, this.window, this.id(), Foundation.selector("uploadPanelDidEnd:returnCode:contextInfo:"), null); } public void uploadPanelSetShowHiddenFiles(ID sender) { uploadPanel.setShowsHiddenFiles(uploadPanelHiddenFilesCheckbox.state() == NSCell.NSOnState); } public void uploadPanelDidEnd_returnCode_contextInfo(final NSOpenPanel sheet, final int returncode, final ID contextInfo) { sheet.orderOut(this.id()); if (returncode == SheetCallback.DEFAULT_OPTION) { final Path destination = new UploadTargetFinder(workdir).find(this.getSelectedPath()); // Selected files on the local filesystem final NSArray selected = sheet.filenames(); final NSEnumerator iterator = selected.objectEnumerator(); final List<TransferItem> uploads = new ArrayList<TransferItem>(); NSObject next; while ((next = iterator.nextObject()) != null) { final Local local = LocalFactory.get(next.toString()); new UploadDirectoryFinder().save(session.getHost(), local.getParent()); uploads.add(new TransferItem( new Path(destination, local.getName(), local.isDirectory() ? EnumSet.of(Path.Type.directory) : EnumSet.of(Path.Type.file)), local)); } this.transfer(new UploadTransfer(session.getHost(), uploads)); } uploadPanel = null; uploadPanelHiddenFilesCheckbox = null; } protected void transfer(final Transfer transfer) { final List<Path> selected = new ArrayList<Path>(); for (TransferItem i : transfer.getRoots()) { selected.add(i.remote); } this.transfer(transfer, selected); } /** * Transfers the files either using the queue or using the browser session if #connection.pool.max is 1 * * @param transfer Transfer Operation */ protected void transfer(final Transfer transfer, final List<Path> selected) { // Determine from current browser session if new connection should be opened for transfers this.transfer(transfer, selected, session.getTransferType().equals(Host.TransferType.browser)); } /** * @param transfer Transfer Operation * @param browser Transfer in browser window */ protected void transfer(final Transfer transfer, final List<Path> selected, boolean browser) { final TransferCallback callback = new TransferCallback() { @Override public void complete(final Transfer transfer) { invoke(new WindowMainAction(BrowserController.this) { @Override public void run() { reload(workdir, selected, selected); } }); } }; if (browser) { this.background(new TransferBackgroundAction(this, session, cache, new TransferAdapter() { @Override public void progress(final TransferProgress status) { message(status.getProgress()); } }, transfer, new TransferOptions()) { @Override public void finish() { if (transfer.isComplete()) { callback.complete(transfer); } super.finish(); } }); } else { TransferControllerFactory.get().start(transfer, new TransferOptions(), callback); } } @Action public void insideButtonClicked(final ID sender) { final Path selected = this.getSelectedPath(); //first row selected if (null == selected) { return; } if (selected.isDirectory()) { this.setWorkdir(selected); } else if (selected.isFile() || this.getSelectionCount() > 1) { if (preferences.getBoolean("browser.doubleclick.edit")) { this.editButtonClicked(null); } else { this.downloadButtonClicked(null); } } } @Action public void connectButtonClicked(final ID sender) { final SheetController controller = ConnectionControllerFactory.create(this); this.addListener(new WindowListener() { @Override public void windowWillClose() { controller.invalidate(); } }); controller.beginSheet(); } @Action public void disconnectButtonClicked(final ID sender) { if (this.isActivityRunning()) { // Remove all pending actions for (BackgroundAction action : this.getActions() .toArray(new BackgroundAction[this.getActions().size()])) { action.cancel(); } } this.disconnect(new Runnable() { @Override public void run() { if (preferences.getBoolean("browser.disconnect.bookmarks.show")) { selectBookmarks(BookmarkSwitchSegement.bookmarks); } else { selectBrowser(BrowserSwitchSegement.byPosition(preferences.getInteger("browser.view"))); } } }); } @Action public void showHiddenFilesClicked(final NSMenuItem sender) { if (sender.state() == NSCell.NSOnState) { this.setShowHiddenFiles(false); sender.setState(NSCell.NSOffState); } else if (sender.state() == NSCell.NSOffState) { this.setShowHiddenFiles(true); sender.setState(NSCell.NSOnState); } this.reload(); } /** * @return This browser's session or null if not mounted */ public Session<?> getSession() { return session; } public Cache<Path> getCache() { return cache; } /** * @return true if the remote file system has been mounted */ public boolean isMounted() { return session != null && workdir != null; } /** * @return true if mounted and the connection to the server is alive */ public boolean isConnected() { if (this.isMounted()) { return session.isConnected(); } return false; } /** * NSService * <p> * Indicates whether the receiver can send and receive the specified pasteboard types. * <p> * Either sendType or returnTypebut not bothmay be empty. If sendType is empty, * the service doesnt require input from the application requesting the service. * If returnType is empty, the service doesnt return data. * * @param sendType The pasteboard type the application needs to send. * @param returnType The pasteboard type the application needs to receive. * @return The object that can send and receive the specified types or nil * if the receiver knows of no object that can send and receive data of that type. */ public ID validRequestorForSendType_returnType(String sendType, String returnType) { log.debug("validRequestorForSendType_returnType:" + sendType + "," + returnType); if (StringUtils.isNotEmpty(sendType)) { // Cannot send any data type return null; } if (StringUtils.isNotEmpty(returnType)) { // Can receive filenames if (NSPasteboard.FilenamesPboardType.equals(sendType)) { return this.id(); } } return null; } /** * NSService * <p> * Reads data from the pasteboard and uses it to replace the current selection. * * @param pboard Pasteboard * @return YES if your implementation was able to read the pasteboard data successfully; otherwise, NO. */ public boolean readSelectionFromPasteboard(NSPasteboard pboard) { return this.upload(pboard); } /** * NSService * <p> * Writes the current selection to the pasteboard. * * @param pboard Pasteboard * @param types Types in pasteboard * @return YES if your implementation was able to write one or more types to the pasteboard; otherwise, NO. */ public boolean writeSelectionToPasteboard_types(NSPasteboard pboard, NSArray types) { return false; } @Action public void copy(final ID sender) { pasteboard.clear(); pasteboard.setCopy(true); final List<Path> s = this.getSelectedPaths(); for (Path p : s) { // Writing data for private use when the item gets dragged to the transfer queue. pasteboard.add(p); } final NSPasteboard clipboard = NSPasteboard.generalPasteboard(); if (s.size() == 0) { s.add(this.workdir()); } clipboard.declareTypes(NSArray.arrayWithObject(NSString.stringWithString(NSPasteboard.StringPboardType)), null); StringBuilder copy = new StringBuilder(); for (Iterator<Path> i = s.iterator(); i.hasNext();) { copy.append(i.next().getAbsolute()); if (i.hasNext()) { copy.append("\n"); } } if (!clipboard.setStringForType(copy.toString(), NSPasteboard.StringPboardType)) { log.error("Error writing to NSPasteboard.StringPboardType."); } } @Action public void cut(final ID sender) { pasteboard.clear(); pasteboard.setCut(true); for (Path s : this.getSelectedPaths()) { // Writing data for private use when the item gets dragged to the transfer queue. pasteboard.add(s); } final NSPasteboard clipboard = NSPasteboard.generalPasteboard(); clipboard.declareTypes(NSArray.arrayWithObject(NSString.stringWithString(NSPasteboard.StringPboardType)), null); if (!clipboard.setStringForType(this.getSelectedPath().getAbsolute(), NSPasteboard.StringPboardType)) { log.error("Error writing to NSPasteboard.StringPboardType."); } } @Action public void paste(final ID sender) { if (pasteboard.isEmpty()) { NSPasteboard pboard = NSPasteboard.generalPasteboard(); this.upload(pboard); } else { final Map<Path, Path> files = new HashMap<Path, Path>(); final Path parent = this.workdir(); for (final Path next : pasteboard) { Path renamed = new Path(parent, next.getName(), next.getType()); files.put(next, renamed); } pasteboard.clear(); if (pasteboard.isCut()) { new MoveController(this).rename(files); } if (pasteboard.isCopy()) { new DuplicateFileController(this, cache).duplicate(files); } } } /** * @param pboard Pasteboard with filenames * @return True if filenames are found in pasteboard and upload has started */ private boolean upload(NSPasteboard pboard) { if (!this.isMounted()) { return false; } if (pboard.availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) { NSObject o = pboard.propertyListForType(NSPasteboard.FilenamesPboardType); if (o != null) { if (o.isKindOfClass(Rococoa.createClass("NSArray", NSArray._Class.class))) { final NSArray elements = Rococoa.cast(o, NSArray.class); final Path workdir = this.workdir(); final List<TransferItem> uploads = new ArrayList<TransferItem>(); for (int i = 0; i < elements.count().intValue(); i++) { final Local local = LocalFactory.get(elements.objectAtIndex(new NSUInteger(i)).toString()); uploads.add(new TransferItem(new Path(workdir, local.getName(), local.isDirectory() ? EnumSet.of(Path.Type.directory) : EnumSet.of(Path.Type.file)), local)); } this.transfer(new UploadTransfer(session.getHost(), uploads)); } } } return false; } @Action public void openTerminalButtonClicked(final ID sender) { Path workdir = null; if (this.getSelectionCount() == 1) { Path selected = this.getSelectedPath(); if (selected.isDirectory()) { workdir = selected; } } if (null == workdir) { workdir = this.workdir(); } try { final TerminalService terminal = TerminalServiceFactory.get(); terminal.open(session.getHost(), workdir); } catch (AccessDeniedException e) { this.alert(session.getHost(), e, new StringBuilder()); } } @Action public void archiveMenuClicked(final NSMenuItem sender) { final Archive archive = Archive.forName(sender.representedObject()); this.archiveClicked(archive); } @Action public void archiveButtonClicked(final NSToolbarItem sender) { this.archiveClicked(Archive.TARGZ); } /** * @param format Archive format */ private void archiveClicked(final Archive format) { new ArchiveController(this).archive(format, this.getSelectedPaths()); } @Action public void unarchiveButtonClicked(final ID sender) { new ArchiveController(this).unarchive(this.getSelectedPaths()); } /** * Accessor to the working directory * * @return The current working directory or null if no file system is mounted */ protected Path workdir() { return workdir; } public void setWorkdir(final Path directory) { this.setWorkdir(directory, Collections.emptyList()); } public void setWorkdir(final Path directory, Path selected) { this.setWorkdir(directory, Collections.singletonList(selected)); } /** * Sets the current working directory. This will update the path selection menu and also add this path to the browsing history. * * @param directory The new working directory to display or null to detach any working directory from the browser * @param selected Selected files in browser */ public void setWorkdir(final Path directory, final List<Path> selected) { if (log.isDebugEnabled()) { log.debug(String.format("Set working directory to %s", directory)); } // Remove any custom file filter this.setFilter(null); final NSTableView browser = this.getSelectedBrowserView(); window.endEditingFor(browser); if (null == directory) { this.reload(null, Collections.emptySet(), selected, false); } else { this.reload(directory, Collections.singleton(directory), selected, false); } } private void setNavigation(boolean enabled) { if (!enabled) { searchField.setStringValue(StringUtils.EMPTY); } pathPopupButton.removeAllItems(); if (enabled) { // Update the current working directory navigation.add(workdir); Path p = workdir; do { this.addNavigation(p); p = p.getParent(); } while (!p.isRoot()); this.addNavigation(p); } pathPopupButton.setEnabled(enabled); navigationButton.setEnabled_forSegment(enabled && navigation.getBack().size() > 1, NavigationSegment.back.position()); navigationButton.setEnabled_forSegment(enabled && navigation.getForward().size() > 0, NavigationSegment.forward.position()); upButton.setEnabled_forSegment(enabled && !workdir.isRoot(), NavigationSegment.up.position()); } private void addNavigation(final Path p) { pathPopupButton.addItemWithTitle(p.getAbsolute()); pathPopupButton.lastItem().setRepresentedObject(p.getAbsolute()); if (p.isVolume()) { pathPopupButton.lastItem() .setImage(IconCacheFactory.<NSImage>get().volumeIcon(session.getHost().getProtocol(), 16)); } else { pathPopupButton.lastItem().setImage(IconCacheFactory.<NSImage>get().fileIcon(p, 16)); } } /** * Initializes a session for the passed host. Setting up the listeners and adding any callback * controllers needed for login, trust management and hostkey verification. * * @param host Bookmark * @return A session object bound to this browser controller */ private Session init(final Host host) { session = SessionFactory.create(host); transcript.clear(); navigation.clear(); pasteboard = PathPasteboardFactory.getPasteboard(session); this.setWorkdir(null); this.setEncoding(session.getEncoding()); return session; } /** * Open connection in browser * * @param host Bookmark */ public void mount(final Host host) { if (log.isDebugEnabled()) { log.debug(String.format("Mount session for %s", host)); } this.unmount(new Runnable() { @Override public void run() { // The browser has no session, we are allowed to proceed // Initialize the browser with the new session attaching all listeners final Session session = init(host); background(new WorkerBackgroundAction<Path>(BrowserController.this, session, cache, new MountWorker(host, cache, listener) { @Override public void cleanup(final Path workdir) { super.cleanup(workdir); if (null == workdir) { doUnmount(new Runnable() { @Override public void run() { // } }); } else { // Update status icon bookmarkTable.setNeedsDisplay(); // Set the working directory setWorkdir(workdir); // Close bookmarks selectBrowser(BrowserSwitchSegement .byPosition(preferences.getInteger("browser.view"))); // Set the window title //window.setRepresentedFilename(HistoryCollection.defaultCollection().getFile(host).getAbsolute()); if (preferences.getBoolean("browser.disconnect.confirm")) { window.setDocumentEdited(true); } securityLabel.setImage(session.isSecured() ? IconCacheFactory.<NSImage>get().iconNamed("NSLockLockedTemplate") : IconCacheFactory.<NSImage>get().iconNamed("NSLockUnlockedTemplate")); securityLabel.setEnabled(session instanceof SSLSession); } } }) { @Override public void init() { super.init(); window.setTitle(BookmarkNameProvider.toString(host, true)); window.setRepresentedFilename(StringUtils.EMPTY); // Update status icon bookmarkTable.setNeedsDisplay(); } }); } }); } /** * @param disconnected Callback after the session has been disconnected * @return True if the unmount process has finished, false if the user has to agree first * to close the connection */ public boolean unmount(final Runnable disconnected) { return this.unmount(new SheetCallback() { @Override public void callback(int returncode) { if (returncode == DEFAULT_OPTION) { doUnmount(disconnected); } } }, disconnected); } /** * @param callback Confirmation callback * @param disconnected Action to run after disconnected * @return True if succeeded */ public boolean unmount(final SheetCallback callback, final Runnable disconnected) { if (log.isDebugEnabled()) { log.debug(String.format("Unmount session %s", session)); } if (this.isConnected() || this.isActivityRunning()) { if (preferences.getBoolean("browser.disconnect.confirm")) { // Defer the unmount to the callback function final NSAlert alert = NSAlert.alert( MessageFormat.format(LocaleFactory.localizedString("Disconnect from {0}"), session.getHost().getHostname()), //title LocaleFactory.localizedString("The connection will be closed."), // message LocaleFactory.localizedString("Disconnect"), // defaultbutton LocaleFactory.localizedString("Cancel"), // alternate button null //other button ); alert.setShowsSuppressionButton(true); alert.suppressionButton() .setTitle(LocaleFactory.localizedString("Don't ask again", "Configuration")); this.alert(alert, new SheetCallback() { @Override public void callback(int returncode) { if (alert.suppressionButton().state() == NSCell.NSOnState) { // Never show again. preferences.setProperty("browser.disconnect.confirm", false); } callback.callback(returncode); } }); // No unmount yet return false; } } this.doUnmount(disconnected); // Unmount succeeded return true; } /** * @param disconnected Action to run after disconnected */ private void doUnmount(final Runnable disconnected) { this.disconnect(new Runnable() { @Override public void run() { session = null; editors.clear(); cache.clear(); setWorkdir(null); for (Editor e : editors) { e.delete(); } window.setTitle(preferences.getProperty("application.name")); window.setRepresentedFilename(StringUtils.EMPTY); disconnected.run(); } }); } /** * Unmount this session */ private void disconnect(final Runnable disconnected) { final InfoController c = InfoControllerFactory.get(BrowserController.this); if (null != c) { c.window().close(); } if (session != null) { this.background(new WorkerBackgroundAction<Void>(this, session, cache, new DisconnectWorker(session.getHost())) { @Override public void prepare() throws ConnectionCanceledException { if (!session.isConnected()) { throw new ConnectionCanceledException(); } super.prepare(); } @Override protected boolean connect(Session session) throws BackgroundException { return false; } @Override public void cleanup() { super.cleanup(); window.setDocumentEdited(false); disconnected.run(); } }); } else { disconnected.run(); } } @Action public void printDocument(final ID sender) { this.print(this.getSelectedBrowserView()); } /** * @param app Singleton * @return NSApplication.TerminateLater if the application should not yet be terminated */ public static NSUInteger applicationShouldTerminate(final NSApplication app) { // Determine if there are any open connections for (final BrowserController controller : MainController.getBrowsers()) { if (!controller.unmount(new SheetCallback() { @Override public void callback(final int returncode) { if (returncode == DEFAULT_OPTION) { //Disconnect controller.window().close(); if (NSApplication.NSTerminateNow .equals(BrowserController.applicationShouldTerminate(app))) { app.replyToApplicationShouldTerminate(true); } } else { app.replyToApplicationShouldTerminate(false); } } }, new Runnable() { @Override public void run() { // } })) { return NSApplication.NSTerminateCancel; } } return NSApplication.NSTerminateNow; } @Override public boolean windowShouldClose(final NSWindow sender) { return this.unmount(new Runnable() { @Override public void run() { sender.close(); } }); } /** * @param item Menu item * @return True if the menu should be enabled */ @Override public boolean validateMenuItem(final NSMenuItem item) { final Selector action = item.action(); if (action.equals(Foundation.selector("paste:"))) { final String title = "Paste {0}"; item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), StringUtils.EMPTY).trim()); if (this.isMounted()) { if (pasteboard.isEmpty()) { if (NSPasteboard.generalPasteboard().availableTypeFromArray( NSArray.arrayWithObject(NSPasteboard.FilenamesPboardType)) != null) { NSObject o = NSPasteboard.generalPasteboard() .propertyListForType(NSPasteboard.FilenamesPboardType); if (o != null) { if (o.isKindOfClass(Rococoa.createClass("NSArray", NSArray._Class.class))) { final NSArray elements = Rococoa.cast(o, NSArray.class); if (elements.count().intValue() == 1) { item.setTitle(MessageFormat .format(LocaleFactory.localizedString(title), "\"" + elements.objectAtIndex(new NSUInteger(0)) + "\"") .trim()); } else { item.setTitle( MessageFormat .format(LocaleFactory.localizedString(title), MessageFormat.format( LocaleFactory.localizedString("{0} Files"), String.valueOf(elements.count().intValue()))) .trim()); } } } } } else { if (pasteboard.size() == 1) { item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), "\"" + pasteboard.get(0).getName() + "\"").trim()); } else { item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), MessageFormat.format(LocaleFactory.localizedString("{0} Files"), String.valueOf(pasteboard.size()))) .trim()); } } } } else if (action.equals(Foundation.selector("cut:")) || action.equals(Foundation.selector("copy:"))) { String title = null; if (action.equals(Foundation.selector("cut:"))) { title = "Cut {0}"; } else if (action.equals(Foundation.selector("copy:"))) { title = "Copy {0}"; } if (this.isMounted()) { int count = this.getSelectionCount(); if (0 == count) { item.setTitle( MessageFormat.format(LocaleFactory.localizedString(title), StringUtils.EMPTY).trim()); } else if (1 == count) { item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), "\"" + this.getSelectedPath().getName() + "\"").trim()); } else { item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), MessageFormat.format( LocaleFactory.localizedString("{0} Files"), String.valueOf(this.getSelectionCount()))) .trim()); } } else { item.setTitle(MessageFormat.format(LocaleFactory.localizedString(title), StringUtils.EMPTY).trim()); } } else if (action.equals(Foundation.selector("showHiddenFilesClicked:"))) { item.setState(this.getFilter() instanceof NullFilter ? NSCell.NSOnState : NSCell.NSOffState); } else if (action.equals(Foundation.selector("encodingMenuClicked:"))) { if (this.isMounted()) { item.setState(session.getEncoding().equalsIgnoreCase(item.title()) ? NSCell.NSOnState : NSCell.NSOffState); } else { item.setState(preferences.getProperty("browser.charset.encoding").equalsIgnoreCase(item.title()) ? NSCell.NSOnState : NSCell.NSOffState); } } else if (action.equals(Foundation.selector("browserSwitchMenuClicked:"))) { if (item.tag() == preferences.getInteger("browser.view")) { item.setState(NSCell.NSOnState); } else { item.setState(NSCell.NSOffState); } } else if (action.equals(Foundation.selector("archiveMenuClicked:"))) { final Archive archive = Archive.forName(item.representedObject()); item.setTitle(archive.getTitle(this.getSelectedPaths())); } else if (action.equals(Foundation.selector("quicklookButtonClicked:"))) { item.setKeyEquivalent(" "); item.setKeyEquivalentModifierMask(0); } return this.validate(action); } private boolean validate(final Selector action) { return browserToolbarValidator.validate(action); } @Override public boolean validateToolbarItem(final NSToolbarItem item) { return browserToolbarValidator.validate(item); } @Override public NSToolbarItem toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar(final NSToolbar toolbar, final String itemIdentifier, boolean inserted) { if (log.isDebugEnabled()) { log.debug("toolbar_itemForItemIdentifier_willBeInsertedIntoToolbar:" + itemIdentifier); } return browserToolbarFactory.create(itemIdentifier); } /** * @param toolbar Window toolbar * @return The default configuration of toolbar items */ @Override public NSArray toolbarDefaultItemIdentifiers(final NSToolbar toolbar) { return browserToolbarFactory.getDefault(); } /** * @param toolbar Window toolbar * @return All available toolbar items */ @Override public NSArray toolbarAllowedItemIdentifiers(final NSToolbar toolbar) { return browserToolbarFactory.getAllowed(); } @Override public NSArray toolbarSelectableItemIdentifiers(NSToolbar toolbar) { return NSArray.array(); } /** * Overrriden to remove any listeners from the session */ @Override public void invalidate() { if (quicklook.isAvailable()) { if (quicklook.isOpen()) { quicklook.close(); } } bookmarkTable.setDelegate(null); bookmarkTable.setDataSource(null); bookmarkModel.invalidate(); browserListView.setDelegate(null); browserListView.setDataSource(null); browserListModel.invalidate(); browserOutlineView.setDelegate(null); browserOutlineView.setDataSource(null); browserOutlineModel.invalidate(); toolbar.setDelegate(null); browserListColumnsFactory.clear(); browserOutlineColumnsFactory.clear(); bookmarkTableColumnFactory.clear(); quickConnectPopup.setDelegate(null); quickConnectPopup.setDataSource(null); archiveMenu.setDelegate(null); editMenu.setDelegate(null); notificationCenter.removeObserver(this.id()); super.invalidate(); } }