From cb9bccd90c86dd701e214c458a9e1d3785653137 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Tue, 4 Aug 2020 11:05:02 +0200 Subject: [PATCH 1/8] Implement outline view for bookmarks. --- .../binding/AbstractTableDelegate.java | 2 +- .../cyberduck/core/DeserializerFactory.java | 4 + .../ch/cyberduck/core/SerializerFactory.java | 4 + .../cocoa/controller/BrowserController.java | 154 ++++++++--- .../controller/NSDictionaryHostFilter.java | 33 +++ .../datasource/BookmarkTableDataSource.java | 245 +++++++++--------- 6 files changed, 280 insertions(+), 162 deletions(-) create mode 100644 osx/src/main/java/ch/cyberduck/ui/cocoa/controller/NSDictionaryHostFilter.java diff --git a/binding/src/main/java/ch/cyberduck/binding/AbstractTableDelegate.java b/binding/src/main/java/ch/cyberduck/binding/AbstractTableDelegate.java index 28847aed4f6..4b1fc99f7b2 100644 --- a/binding/src/main/java/ch/cyberduck/binding/AbstractTableDelegate.java +++ b/binding/src/main/java/ch/cyberduck/binding/AbstractTableDelegate.java @@ -115,7 +115,7 @@ public boolean selectionShouldChangeInTableView(final NSTableView view) { /** * @see NSOutlineView.DataSource */ - public boolean selectionShouldChangeInOutlineView(final NSTableView view) { + public boolean selectionShouldChangeInOutlineView(final NSOutlineView view) { return this.selectionShouldChange(); } diff --git a/core/src/main/java/ch/cyberduck/core/DeserializerFactory.java b/core/src/main/java/ch/cyberduck/core/DeserializerFactory.java index 156bc022252..5190d18e6ba 100644 --- a/core/src/main/java/ch/cyberduck/core/DeserializerFactory.java +++ b/core/src/main/java/ch/cyberduck/core/DeserializerFactory.java @@ -34,6 +34,10 @@ public DeserializerFactory() { super("factory.deserializer.class"); } + public DeserializerFactory(final Class> clazz) { + super(clazz); + } + public Deserializer create(final T dict) { try { final Constructor> constructor = ConstructorUtils.getMatchingAccessibleConstructor(clazz, dict.getClass()); diff --git a/core/src/main/java/ch/cyberduck/core/SerializerFactory.java b/core/src/main/java/ch/cyberduck/core/SerializerFactory.java index 1d46e021113..01f7e4e9f26 100644 --- a/core/src/main/java/ch/cyberduck/core/SerializerFactory.java +++ b/core/src/main/java/ch/cyberduck/core/SerializerFactory.java @@ -29,6 +29,10 @@ public SerializerFactory() { super("factory.serializer.class"); } + public SerializerFactory(final Class> clazz) { + super(clazz); + } + public static Serializer get() { return new SerializerFactory().create(); } diff --git a/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BrowserController.java b/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BrowserController.java index 18a1956914a..7ae5bb70b2c 100644 --- a/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BrowserController.java +++ b/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/BrowserController.java @@ -70,6 +70,8 @@ import ch.cyberduck.core.preferences.PreferencesFactory; import ch.cyberduck.core.resources.IconCacheFactory; import ch.cyberduck.core.serializer.HostDictionary; +import ch.cyberduck.core.serializer.impl.jna.PlistDeserializer; +import ch.cyberduck.core.serializer.impl.jna.PlistSerializer; import ch.cyberduck.core.ssl.X509TrustManager; import ch.cyberduck.core.threading.BackgroundAction; import ch.cyberduck.core.threading.BrowserTransferBackgroundAction; @@ -268,9 +270,9 @@ public class BrowserController extends WindowController implements NSToolbar.Del @Delegate private BookmarkTableDataSource bookmarkModel; @Outlet - private NSTableView bookmarkTable; + private NSOutlineView bookmarkTable; @Delegate - private AbstractTableDelegate bookmarkTableDelegate; + private AbstractBookmarkTableDelegate bookmarkTableDelegate; @Outlet private NSSearchField searchField; @Outlet @@ -1024,10 +1026,10 @@ public void run() { this.setBookmarkFilter(null); this.reloadBookmarks(); if(this.isMounted()) { - int row = this.bookmarkModel.getSource().indexOf(pool.getHost()); - if(row != -1) { - this.bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(new NSInteger(row)), false); - this.bookmarkTable.scrollRowToVisible(new NSInteger(row)); + final NSInteger row = bookmarkTable.rowForItem(pool.getHost().serialize(new PlistSerializer())); + if(row.intValue() != -1) { + this.bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(row), false); + this.bookmarkTable.scrollRowToVisible(row); } else { this.bookmarkTable.selectRowIndexes(NSIndexSet.indexSetWithIndex(new NSInteger(0)), false); @@ -1046,6 +1048,7 @@ public void run() { */ public void reloadBookmarks() { bookmarkTable.reloadData(); +// bookmarkTable.reloadItem_reloadChildren(null, true); this.setStatus(); } @@ -1529,7 +1532,7 @@ private void _configureBrowserColumns(final NSTableView table, final AbstractBro this.reload(); } - public NSTableView getBookmarkTable() { + public NSOutlineView getBookmarkTable() { return bookmarkTable; } @@ -1538,7 +1541,7 @@ public NSTableView getBookmarkTable() { // ---------------------------------------------------------- @Action - public void setBookmarkTable(NSTableView view) { + public void setBookmarkTable(final NSOutlineView view) { bookmarkTable = view; bookmarkTable.setSelectionHighlightStyle(NSTableView.NSTableViewSelectionHighlightStyleSourceList); bookmarkTable.setDataSource((this.bookmarkModel = new BookmarkTableDataSource(this)).id()); @@ -1548,6 +1551,7 @@ public void setBookmarkTable(NSTableView view) { c.setResizingMask(NSTableColumn.NSTableColumnNoResizing); c.setDataCell(imageCellPrototype); bookmarkTable.addTableColumn(c); + bookmarkTable.setOutlineTableColumn(c); } { NSTableColumn c = bookmarkTableColumnFactory.create(BookmarkColumn.bookmark.name()); @@ -1568,7 +1572,7 @@ public void setBookmarkTable(NSTableView view) { c.dataCell().setAlignment(TEXT_ALIGNMENT_CENTER); bookmarkTable.addTableColumn(c); } - bookmarkTable.setDelegate((bookmarkTableDelegate = new AbstractTableDelegate( + bookmarkTable.setDelegate((bookmarkTableDelegate = new AbstractBookmarkTableDelegate( bookmarkTable.tableColumnWithIdentifier(BookmarkColumn.bookmark.name()) ) { private static final double kSwipeGestureLeft = 1.000000; @@ -1576,6 +1580,29 @@ public void setBookmarkTable(NSTableView view) { private static final double kSwipeGestureUp = 1.000000; private static final double kSwipeGestureDown = -1.000000; + public NSCell outlineView_dataCellForTableColumn_item(final NSOutlineView view, final NSTableColumn column, final NSObject item) { + if(null == item) { + return null; + } + if(null == column) { + if(outlineView_isGroupItem(view, item)) { + // When each row (identified by the item) is being drawn, this method is first called + // with a nil value for tableColumn. At this time, you can return a cell that is used to draw the entire row, acting like a group + return textCellPrototype; + } + return null; + } + return column.dataCell(); + } + + @Override + public boolean outlineView_shouldSelectItem(final NSOutlineView view, final NSObject item) { + if(this.outlineView_isGroupItem(view, item)) { + return false; + } + return true; + } + @Override public String tooltip(Host bookmark, final BookmarkColumn column) { return new HostUrlProvider().get(bookmark); @@ -1586,11 +1613,6 @@ 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()) { @@ -1598,11 +1620,6 @@ public void deleteKeyPressed(final ID sender) { } } - @Override - public void tableColumnClicked(NSTableView view, NSTableColumn tableColumn) { - - } - @Override public void selectionDidChange(final NSNotification notification) { addBookmarkButton.setEnabled(bookmarkModel.getSource().allowsAdd()); @@ -1612,7 +1629,10 @@ public void selectionDidChange(final NSNotification notification) { } @Action - public CGFloat tableView_heightOfRow(NSTableView view, NSInteger row) { + public CGFloat outlineView_heightOfRowByItem(final NSOutlineView view, final NSObject item) { + if(outlineView_isGroupItem(view, item)) { + return new CGFloat(18); + } final int size = preferences.getInteger("bookmark.icon.size"); if(BookmarkCell.SMALL_BOOKMARK_SIZE == size) { return new CGFloat(18); @@ -1628,15 +1648,19 @@ 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())); + public String outlineView_typeSelectStringForTableColumn_item(final NSOutlineView view, final NSTableColumn column, final NSObject item) { + final NSDictionary dict = Rococoa.cast(item, NSDictionary.class); + return BookmarkNameProvider.toString(new HostDictionary(new DeserializerFactory(PlistDeserializer.class)).deserialize(dict)); } - @Action - public boolean tableView_isGroupRow(NSTableView view, NSInteger row) { + @Override + public boolean outlineView_isGroupItem(final NSOutlineView view, final NSObject item) { + if(null == item) { + return false; + } + if(item.isKindOfClass(Rococoa.createClass("NSString", NSString._Class.class))) { + return true; + } return false; } @@ -1646,7 +1670,7 @@ public boolean tableView_isGroupRow(NSTableView view, NSInteger row) { * @param event Swipe event */ @Action - public void swipeWithEvent(NSEvent event) { + public void swipeWithEvent(final NSEvent event) { if(event.deltaY().doubleValue() == kSwipeGestureUp) { NSInteger row = bookmarkTable.selectedRow(); NSInteger next; @@ -1697,8 +1721,9 @@ else if(BookmarkCell.MEDIUM_BOOKMARK_SIZE == size) { bookmarkTable.setRowHeight(new CGFloat(70)); } - // setting appearance attributes() + // setting appearance attributes bookmarkTable.setUsesAlternatingRowBackgroundColors(preferences.getBoolean("browser.alternatingRows")); + // No grid lines until bookmarks are loaded bookmarkTable.setGridStyleMask(NSTableView.NSTableViewGridNone); // selection properties @@ -1707,6 +1732,7 @@ else if(BookmarkCell.MEDIUM_BOOKMARK_SIZE == size) { bookmarkTable.setAllowsColumnResizing(false); bookmarkTable.setAllowsColumnSelection(false); bookmarkTable.setAllowsColumnReordering(false); + bookmarkTable.setAutosaveExpandedItems(true); bookmarkTable.sizeToFit(); } @@ -1839,7 +1865,7 @@ public void cleanup(final AttributedList list) { private void setBookmarkFilter(final String searchString) { if(StringUtils.isBlank(searchString)) { searchField.setStringValue(StringUtils.EMPTY); - bookmarkModel.setFilter(null); + bookmarkModel.setFilter(HostFilter.NONE); } else { bookmarkModel.setFilter(new BookmarkSearchFilter(searchString)); @@ -1850,7 +1876,14 @@ private void setBookmarkFilter(final String searchString) { @Action public void connectBookmarkButtonClicked(final ID sender) { if(bookmarkTable.numberOfSelectedRows().intValue() == 1) { - final Host selected = bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue()); + final NSObject item = bookmarkTable.itemAtRow(bookmarkTable.selectedRow()); + if(bookmarkTableDelegate.outlineView_isGroupItem(bookmarkTable, item)) { + return; + } + final NSDictionaryHostFilter filter = new NSDictionaryHostFilter( + Rococoa.cast(item, NSDictionary.class) + ); + final Host selected = bookmarkModel.getSource().stream().filter(filter::accept).findFirst().orElse(null); this.mount(selected); } } @@ -1870,15 +1903,20 @@ public void setEditBookmarkButton(NSButton editBookmarkButton) { @Action public void editBookmarkButtonClicked(final ID sender) { - final BookmarkController c = BookmarkControllerFactory.create(bookmarks, - bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue()) + final NSDictionaryHostFilter filter = new NSDictionaryHostFilter( + Rococoa.cast(bookmarkTable.itemAtRow(bookmarkTable.selectedRow()), NSDictionary.class) ); + final Host selected = bookmarkModel.getSource().stream().filter(filter::accept).findFirst().orElse(null); + final BookmarkController c = BookmarkControllerFactory.create(bookmarks, selected); c.window().makeKeyAndOrderFront(null); } @Action public void duplicateBookmarkButtonClicked(final ID sender) { - final Host selected = bookmarkModel.getSource().get(bookmarkTable.selectedRow().intValue()); + final NSDictionaryHostFilter filter = new NSDictionaryHostFilter( + Rococoa.cast(bookmarkTable.itemAtRow(bookmarkTable.selectedRow()), NSDictionary.class) + ); + final Host selected = bookmarkModel.getSource().stream().filter(filter::accept).findFirst().orElse(null); this.selectBookmarks(BookmarkSwitchSegement.bookmarks); final Host duplicate = new HostDictionary<>().deserialize(selected.serialize(SerializerFactory.get())); // Make sure a new UUID is asssigned for duplicate @@ -1936,10 +1974,13 @@ public void setDeleteBookmarkButton(NSButton deleteBookmarkButton) { @Action public void deleteBookmarkButtonClicked(final ID sender) { - NSIndexSet iterator = bookmarkTable.selectedRowIndexes(); + final NSIndexSet iterator = bookmarkTable.selectedRowIndexes(); final List selected = new ArrayList<>(); for(NSUInteger index = iterator.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = iterator.indexGreaterThanIndex(index)) { - selected.add(bookmarkModel.getSource().get(index.intValue())); + final NSDictionaryHostFilter filter = new NSDictionaryHostFilter( + Rococoa.cast(bookmarkTable.itemAtRow(new NSInteger(index.intValue())), NSDictionary.class) + ); + selected.add(bookmarkModel.getSource().stream().filter(filter::accept).findFirst().orElse(null)); } StringBuilder alertText = new StringBuilder( LocaleFactory.localizedString("Do you want to delete the selected bookmark?")); @@ -3790,6 +3831,47 @@ protected Path pathAtRow(int row) { } } + private abstract static class AbstractBookmarkTableDelegate extends AbstractTableDelegate implements NSOutlineView.Delegate { + protected AbstractBookmarkTableDelegate(final NSTableColumn selectedColumn) { + super(selectedColumn); + } + + @Override + public void enterKeyPressed(final ID sender) { + this.tableRowDoubleClicked(sender); + } + + @Override + public void tableColumnClicked(NSTableView view, NSTableColumn tableColumn) { + + } + + @Override + public boolean outlineView_shouldExpandItem(final NSOutlineView view, final NSObject item) { + return true; + } + + @Override + public void outlineViewItemWillExpand(final NSNotification notification) { + + } + + @Override + public void outlineViewItemDidExpand(final NSNotification notification) { + + } + + @Override + public void outlineViewItemWillCollapse(final NSNotification notification) { + + } + + @Override + public void outlineViewItemDidCollapse(final NSNotification notification) { + + } + } + private abstract class AbstractBrowserTableDelegate extends AbstractPathTableDelegate { private static final double kSwipeGestureLeft = 1.000000; diff --git a/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/NSDictionaryHostFilter.java b/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/NSDictionaryHostFilter.java new file mode 100644 index 00000000000..981742111d9 --- /dev/null +++ b/osx/src/main/java/ch/cyberduck/ui/cocoa/controller/NSDictionaryHostFilter.java @@ -0,0 +1,33 @@ +package ch.cyberduck.ui.cocoa.controller; + +/* + * Copyright (c) 2002-2020 iterate GmbH. All rights reserved. + * https://cyberduck.io/ + * + * 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 3 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. + */ + +import ch.cyberduck.binding.foundation.NSDictionary; +import ch.cyberduck.core.Host; +import ch.cyberduck.core.HostFilter; + +public final class NSDictionaryHostFilter implements HostFilter { + private final NSDictionary item; + + public NSDictionaryHostFilter(final NSDictionary item) { + this.item = item; + } + + @Override + public boolean accept(final Host host) { + return host.getUuid().equals(item.objectForKey("UUID").toString()); + } +} diff --git a/osx/src/main/java/ch/cyberduck/ui/cocoa/datasource/BookmarkTableDataSource.java b/osx/src/main/java/ch/cyberduck/ui/cocoa/datasource/BookmarkTableDataSource.java index a53135f88cf..b9002f2363f 100644 --- a/osx/src/main/java/ch/cyberduck/ui/cocoa/datasource/BookmarkTableDataSource.java +++ b/osx/src/main/java/ch/cyberduck/ui/cocoa/datasource/BookmarkTableDataSource.java @@ -15,29 +15,35 @@ * GNU General Public License for more details. */ -import ch.cyberduck.binding.ListDataSource; +import ch.cyberduck.binding.OutlineDataSource; import ch.cyberduck.binding.application.NSApplication; import ch.cyberduck.binding.application.NSDraggingInfo; import ch.cyberduck.binding.application.NSDraggingSource; import ch.cyberduck.binding.application.NSEvent; import ch.cyberduck.binding.application.NSImage; +import ch.cyberduck.binding.application.NSOutlineView; import ch.cyberduck.binding.application.NSPasteboard; import ch.cyberduck.binding.application.NSTableColumn; import ch.cyberduck.binding.application.NSTableView; import ch.cyberduck.binding.foundation.NSArray; +import ch.cyberduck.binding.foundation.NSDictionary; import ch.cyberduck.binding.foundation.NSIndexSet; import ch.cyberduck.binding.foundation.NSMutableArray; import ch.cyberduck.binding.foundation.NSMutableDictionary; import ch.cyberduck.binding.foundation.NSObject; +import ch.cyberduck.binding.foundation.NSString; import ch.cyberduck.binding.foundation.NSURL; import ch.cyberduck.core.*; import ch.cyberduck.core.exception.AccessDeniedException; import ch.cyberduck.core.exception.HostParserException; import ch.cyberduck.core.pasteboard.HostPasteboard; import ch.cyberduck.core.pool.SessionPool; +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.serializer.impl.jna.PlistDeserializer; +import ch.cyberduck.core.serializer.impl.jna.PlistSerializer; import ch.cyberduck.core.threading.ScheduledThreadPool; import ch.cyberduck.core.threading.WindowMainAction; import ch.cyberduck.core.transfer.Transfer; @@ -59,91 +65,35 @@ import org.rococoa.cocoa.foundation.NSUInteger; import java.util.ArrayList; +import java.util.Collections; import java.util.EnumSet; import java.util.List; +import java.util.Map; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -public class BookmarkTableDataSource extends ListDataSource { +public class BookmarkTableDataSource extends OutlineDataSource { private static final Logger log = LogManager.getLogger(BookmarkTableDataSource.class); - protected final BrowserController controller; - - private HostFilter filter; - - private AbstractHostCollection source = AbstractHostCollection.empty(); - - /** - * Subset of the original source - */ - private AbstractHostCollection filtered; - - private CollectionListener listener; + private final Preferences preferences = PreferencesFactory.get(); + private final HostPasteboard pasteboard = HostPasteboard.getPasteboard(); + private final BrowserController controller; + private final CollectionListener listener = new BookmarkReloadListener(); private final ScheduledThreadPool timerPool = new ScheduledThreadPool(); - private final HostPasteboard pasteboard - = HostPasteboard.getPasteboard(); + private AbstractHostCollection source = AbstractHostCollection.empty(); + private Map> groups = Collections.emptyMap(); public BookmarkTableDataSource(final BrowserController controller) { this.controller = controller; } public void setSource(final AbstractHostCollection source) { - this.source.removeListener(listener); //Remove previous listener + this.source.removeListener(listener); this.source = source; - this.source.addListener(listener = new CollectionListener() { - private ScheduledFuture delayed = null; - - @Override - public void collectionLoaded() { - controller.invoke(new WindowMainAction(controller) { - @Override - public void run() { - controller.reloadBookmarks(); - } - }); - } - - @Override - public void collectionItemAdded(final Host item) { - controller.invoke(new WindowMainAction(controller) { - @Override - public void run() { - controller.reloadBookmarks(); - } - }); - } - - @Override - public void collectionItemRemoved(final Host item) { - controller.invoke(new WindowMainAction(controller) { - @Override - public void run() { - controller.reloadBookmarks(); - } - }); - } - - @Override - public void collectionItemChanged(final Host item) { - if(null != delayed) { - delayed.cancel(false); - } - // Delay to 1 second. When typing changes we don't have to save every iteration. - delayed = timerPool.schedule(new Runnable() { - public void run() { - controller.invoke(new WindowMainAction(controller) { - @Override - public void run() { - controller.reloadBookmarks(); - } - }); - } - }, 1L, TimeUnit.SECONDS); - } - }); - this.setFilter(null); + this.source.addListener(listener); + this.groups = source.groups(HostFilter.NONE); } @Override @@ -158,9 +108,8 @@ public void invalidate() { * * @param filter Filter for bookmarks */ - public void setFilter(HostFilter filter) { - this.filter = filter; - this.filtered = null; + public void setFilter(final HostFilter filter) { + this.groups = source.groups(filter); } /** @@ -169,44 +118,55 @@ public void setFilter(HostFilter filter) { * @see HostFilter */ public AbstractHostCollection getSource() { - if(null == filter) { - return source; - } - if(null == filtered) { - filtered = new FilterHostCollection(source, filter); + return source; + } + + @Override + public boolean outlineView_isItemExpandable(final NSOutlineView view, final NSObject item) { + return item.isKindOfClass(NSString.CLASS); + } + + @Override + public NSInteger outlineView_numberOfChildrenOfItem(final NSOutlineView view, final NSObject item) { + if(null == item) { + return new NSInteger(groups.size()); } - return filtered; + return new NSInteger(groups.get(item.toString()).size()); } @Override - public NSInteger numberOfRowsInTableView(NSTableView view) { - return new NSInteger(this.getSource().size()); + public NSObject outlineView_child_ofItem(final NSOutlineView outlineView, final NSInteger index, final NSObject item) { + if(null == item) { + final String label = new ArrayList<>(groups.keySet()).get(index.intValue()); + return NSString.stringWithString(label); + } + return groups.get(item.toString()).get(index.intValue()).serialize(new PlistSerializer()); } @Override - public NSObject tableView_objectValueForTableColumn_row(final NSTableView view, final NSTableColumn tableColumn, - final NSInteger row) { - if(row.intValue() >= this.numberOfRowsInTableView(view).intValue()) { + public NSObject outlineView_objectValueForTableColumn_byItem(final NSOutlineView view, final NSTableColumn column, final NSObject item) { + if(null == item) { return null; } - final String identifier = tableColumn.identifier(); - final Host host = this.getSource().get(row.intValue()); + if(null == column) { + // Group row) + if(StringUtils.isBlank(item.toString())) { + // Default group of bookmarks with no label assigned + return NSString.stringWithString(LocaleFactory.localizedString("Default")); + } + return item; + } + final NSMutableDictionary dict = Rococoa.cast(item, NSMutableDictionary.class); + final Host host = new HostDictionary(new DeserializerFactory(PlistDeserializer.class)).deserialize(dict); + final String identifier = column.identifier(); if(identifier.equals(BookmarkColumn.icon.name())) { - return IconCacheFactory.get().iconNamed(host.getProtocol().disk(), - PreferencesFactory.get().getInteger("bookmark.icon.size")); + return IconCacheFactory.get().iconNamed(host.getProtocol().disk(), preferences.getInteger("bookmark.icon.size")); } if(identifier.equals(BookmarkColumn.bookmark.name())) { - final NSMutableDictionary dict = NSMutableDictionary.dictionary(); - dict.setObjectForKey(BookmarkNameProvider.toString(host), "Nickname"); - dict.setObjectForKey(host.getHostname(), "Hostname"); - if(StringUtils.isNotBlank(host.getCredentials().getUsername())) { - dict.setObjectForKey(host.getCredentials().getUsername(), "Username"); - } - final String comment = this.getSource().getComment(host); - if(StringUtils.isNotBlank(comment)) { - dict.setObjectForKey(comment, "Comment"); + if(null == dict.objectForKey("Nickname")) { + dict.setObjectForKey(BookmarkNameProvider.toString(host), "Nickname"); } - return dict; + return item; } if(identifier.equals(BookmarkColumn.status.name())) { final SessionPool session = controller.getSession(); @@ -238,10 +198,9 @@ public boolean ignoreModifierKeysWhileDragging() { } @Override - public NSUInteger tableView_validateDrop_proposedRow_proposedDropOperation(final NSTableView view, final NSDraggingInfo info, - final NSInteger row, final NSUInteger operation) { + public NSUInteger outlineView_validateDrop_proposedItem_proposedChildIndex(final NSOutlineView view, final NSDraggingInfo info, final NSObject destination, final NSInteger row) { NSPasteboard draggingPasteboard = info.draggingPasteboard(); - if(!this.getSource().allowsEdit()) { + if(!source.allowsEdit()) { // Do not allow drags for non writable collections return NSDraggingInfo.NSDragOperationNone; } @@ -304,22 +263,13 @@ else if(!pasteboard.isEmpty()) { return NSDraggingInfo.NSDragOperationNone; } - /** - * @param info contains details on this dragging operation. - * @param row The proposed location is row and action is operation. The data source should incorporate the data - * from the dragging pasteboard at this time. - * @see NSTableView.DataSource Invoked by view when the mouse button is released over a table view that previously - * decided to allow a drop. - */ @Override - public boolean tableView_acceptDrop_row_dropOperation(final NSTableView view, final NSDraggingInfo info, - final NSInteger row, final NSUInteger operation) { - NSPasteboard draggingPasteboard = info.draggingPasteboard(); + public boolean outlineView_acceptDrop_item_childIndex(final NSOutlineView view, final NSDraggingInfo info, final NSObject item, final NSInteger row) { + final NSPasteboard draggingPasteboard = info.draggingPasteboard(); if(log.isDebugEnabled()) { log.debug(String.format("Accept drop at row %s", row)); } view.deselectAll(null); - final AbstractHostCollection source = this.getSource(); if(draggingPasteboard.availableTypeFromArray(NSArray.arrayWithObject(NSPasteboard.StringPboardType)) != null) { String o = draggingPasteboard.stringForType(NSPasteboard.StringPboardType); if(null == o) { @@ -344,7 +294,7 @@ else if(draggingPasteboard.availableTypeFromArray(NSArray.arrayWithObject(NSPast if(object.isKindOfClass(NSArray.CLASS)) { final NSArray elements = Rococoa.cast(object, NSArray.class); // If regular files are dropped, these will be uploaded to the dropped bookmark location - final List uploads = new ArrayList(); + final List uploads = new ArrayList<>(); Host host = null; for(int i = 0; i < elements.count().intValue(); i++) { final String filename = elements.objectAtIndex(new NSUInteger(i)).toString(); @@ -411,7 +361,7 @@ else if(draggingPasteboard.availableTypeFromArray(NSArray.arrayWithObject(NSPast } else if(!pasteboard.isEmpty()) { if(info.draggingSourceOperationMask().intValue() == NSDraggingInfo.NSDragOperationCopy.intValue()) { - List duplicates = new ArrayList(); + List duplicates = new ArrayList<>(); for(Host bookmark : pasteboard) { final Host duplicate = new HostDictionary<>().deserialize(bookmark.serialize(SerializerFactory.get())); // Make sure a new UUID is assigned for duplicate @@ -486,19 +436,13 @@ public NSUInteger draggingSourceOperationMaskForLocal(final boolean local) { return new NSUInteger(NSDraggingInfo.NSDragOperationCopy.intValue() | NSDraggingInfo.NSDragOperationDelete.intValue()); } - /** - * @param rowIndexes is the list of row numbers that will be participating in the drag. - * @return To refuse the drag, return false. To start a drag, return true and place the drag data onto pboard (data, - * owner, and so on). - * @see NSTableView.DataSource Invoked by view after it has been determined that a drag should begin, but before the - * drag has been started. The drag image and other drag-related information will be set up and provided by the table - * view once this call returns with true. - */ @Override - public boolean tableView_writeRowsWithIndexes_toPasteboard(final NSTableView view, final NSIndexSet rowIndexes, - final NSPasteboard pboard) { - for(NSUInteger index = rowIndexes.firstIndex(); !index.equals(NSIndexSet.NSNotFound); index = rowIndexes.indexGreaterThanIndex(index)) { - pasteboard.add(this.getSource().get(index.intValue())); + public boolean outlineView_writeItems_toPasteboard(final NSOutlineView view, final NSArray items, final NSPasteboard pboard) { + for(int i = 0; i < items.count().intValue(); i++) { + if(items.objectAtIndex(new NSUInteger(i)).isKindOfClass(NSDictionary.CLASS)) { + final NSDictionary dict = Rococoa.cast(items.objectAtIndex(new NSUInteger(i)), NSDictionary.class); + pasteboard.add(new HostDictionary(new DeserializerFactory(PlistDeserializer.class)).deserialize(dict)); + } } NSEvent event = NSApplication.sharedApplication().currentEvent(); if(event != null) { @@ -546,4 +490,55 @@ public NSArray namesOfPromisedFilesDroppedAtDestination(final NSURL dropDestinat return promisedDragNames; } + private final class BookmarkReloadListener implements CollectionListener { + private ScheduledFuture delayed = null; + + @Override + public void collectionLoaded() { + controller.invoke(new WindowMainAction(controller) { + @Override + public void run() { + controller.reloadBookmarks(); + } + }); + } + + @Override + public void collectionItemAdded(final Host item) { + controller.invoke(new WindowMainAction(controller) { + @Override + public void run() { + controller.reloadBookmarks(); + } + }); + } + + @Override + public void collectionItemRemoved(final Host item) { + controller.invoke(new WindowMainAction(controller) { + @Override + public void run() { + controller.reloadBookmarks(); + } + }); + } + + @Override + public void collectionItemChanged(final Host item) { + if(null != delayed) { + delayed.cancel(false); + } + // Delay to 1 second. When typing changes we don't have to save every iteration. + delayed = timerPool.schedule(new Runnable() { + public void run() { + controller.invoke(new WindowMainAction(controller) { + @Override + public void run() { + controller.reloadBookmarks(); + } + }); + } + }, 1L, TimeUnit.SECONDS); + } + } } From 031c23bb0f30b05433d0a58a14f06785c146f6c6 Mon Sep 17 00:00:00 2001 From: David Kocher Date: Sun, 13 Aug 2023 15:04:29 +0200 Subject: [PATCH 2/8] Set bookmark view to outline. --- i18n/src/main/resources/ar.lproj/Browser.xib | 42 +++++++++--------- i18n/src/main/resources/bg.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/ca.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/cs.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/cy.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/da.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/de.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/el.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/en.lproj/Browser.xib | 2 +- i18n/src/main/resources/es.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/et.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/fi.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/fr.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/he.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/hr.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/hu.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/it.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/ja.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/ka.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/ko.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/lv.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/nl.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/no.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/pl.lproj/Browser.xib | 44 +++++++++---------- .../main/resources/pt_BR.lproj/Browser.xib | 44 +++++++++---------- .../main/resources/pt_PT.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/ro.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/ru.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/sk.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/sl.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/sr.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/sv.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/th.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/tr.lproj/Browser.xib | 44 +++++++++---------- i18n/src/main/resources/uk.lproj/Browser.xib | 44 +++++++++---------- .../main/resources/zh_CN.lproj/Browser.xib | 44 +++++++++---------- .../main/resources/zh_TW.lproj/Browser.xib | 44 +++++++++---------- 37 files changed, 792 insertions(+), 792 deletions(-) diff --git a/i18n/src/main/resources/ar.lproj/Browser.xib b/i18n/src/main/resources/ar.lproj/Browser.xib index 50c3feaadc7..36a498f8112 100644 --- a/i18n/src/main/resources/ar.lproj/Browser.xib +++ b/i18n/src/main/resources/ar.lproj/Browser.xib @@ -1,7 +1,7 @@ - + - + @@ -58,7 +58,7 @@ - +