Commit 88524ca6 authored by Eric Gregory's avatar Eric Gregory

#2846 #2647 view collection now copied when entering single photo mode

parent 90cf27c2
......@@ -1707,10 +1707,14 @@ public abstract class ViewManager {
public abstract DataView create_view(DataSource source);
}
// CreateView is a construction delegate used when mirroring a ViewCollection in another
// ViewCollection.
// CreateView is a construction delegate used when mirroring or copying a ViewCollection
// in another ViewCollection.
public delegate DataView CreateView(DataSource source);
// CreateViewPredicate is a filter delegate used when copy a ViewCollection in another
// ViewCollection.
public delegate bool CreateViewPredicate(DataSource source);
// A ViewFilter allows for items in a ViewCollection to be shown or hidden depending on the
// supplied predicate method. For now, only one ViewFilter may be installed, although this may
// change in the future. The ViewFilter is used whenever an object is added to the collection
......@@ -1776,10 +1780,6 @@ public class ViewCollection : DataCollection {
private DataSet visible = null;
private Gee.HashSet<DataView> frozen_views_altered = null;
private Gee.HashSet<DataView> frozen_geometries_altered = null;
private int view_lock_count = 0;
// if true the items needs to be shown, if false, it needs to be hidden
private Gee.HashMap<DataView, bool> locked_filter_items = new Gee.HashMap<DataView, bool>();
private Gee.HashSet<DataView> locked_unlinked_items = new Gee.HashSet<DataView>();
// TODO: source-to-view mapping ... for now, only one view is allowed for each source.
// This may need to change in the future.
......@@ -1890,17 +1890,7 @@ public class ViewCollection : DataCollection {
return;
}
// Unlock and relock ViewCollection if needed.
int original_lock_count = view_lock_count;
while (is_view_locked()) {
unlock_view();
}
base.clear();
for (int i = 0; i < original_lock_count; i++) {
lock_view();
}
}
public override void close() {
......@@ -1971,6 +1961,19 @@ public class ViewCollection : DataCollection {
mirroring = null;
}
public void copy_into(ViewCollection to_copy, CreateView copying_ctor,
CreateViewPredicate should_copy) {
// Copy into self.
Gee.ArrayList<DataObject> copy_view = new Gee.ArrayList<DataObject>();
foreach (DataObject object in to_copy.get_all()) {
DataView view = (DataView) object;
if (should_copy(view.get_source())) {
copy_view.add(copying_ctor(view.get_source()));
}
}
add_many(copy_view);
}
public void install_view_filter(ViewFilter filter) {
if (this.filter == filter)
return;
......@@ -2266,14 +2269,14 @@ public class ViewCollection : DataCollection {
foreach (DataView view in views) {
if (filter(view)) {
if (locked_filter_items.has_key(view) || !view.is_visible()) {
if (!view.is_visible()) {
if (to_show == null)
to_show = new Gee.ArrayList<DataView>();
to_show.add(view);
}
} else {
if (locked_filter_items.has_key(view) || view.is_visible()) {
if (view.is_visible()) {
if (to_hide == null)
to_hide = new Gee.ArrayList<DataView>();
......@@ -2281,21 +2284,6 @@ public class ViewCollection : DataCollection {
}
}
}
if (view_lock_count > 0) {
if (to_show != null) {
foreach (DataView view in to_show) {
locked_filter_items.set(view, true);
}
}
if (to_hide != null) {
foreach (DataView view in to_hide) {
locked_filter_items.set(view, false);
}
}
return;
}
if (to_show != null)
show_items(to_show);
......@@ -2310,62 +2298,6 @@ public class ViewCollection : DataCollection {
base.items_altered(map);
}
public bool is_view_locked() {
return view_lock_count > 0;
}
public void lock_view() {
view_lock_count++;
}
public void unlock_view() {
assert(view_lock_count > 0);
if (--view_lock_count != 0)
return;
// remove everything unlinked while locked, being sure to remove them from the locked
// visible lists as well
foreach (DataView view in locked_unlinked_items)
locked_filter_items.unset(view);
// Because verify_remove will be called inside remove_marked, want to have the
// locked_unlinked_items collection cleared at that point
Marker marker = mark_many(locked_unlinked_items);
locked_unlinked_items.clear();
remove_marked(marker);
// everything remaining on the visible list update as well
Gee.ArrayList<DataView> to_show = new Gee.ArrayList<DataView>();
Gee.ArrayList<DataView> to_hide = new Gee.ArrayList<DataView>();
foreach (DataView key in locked_filter_items.keys) {
if (locked_filter_items[key] && !key.is_visible())
to_show.add(key);
else if (!locked_filter_items[key] && key.is_visible())
to_hide.add(key);
}
if (to_show.size > 0)
show_items(to_show);
if (to_hide.size > 0)
hide_items(to_hide);
locked_filter_items.clear();
}
public override void remove_marked(Marker marker) {
// if locked, block removal of unlinked DataViews
if (locked_unlinked_items.size > 0) {
foreach (DataObject object in marker.get_all()) {
if (locked_unlinked_items.contains((DataView) object))
marker.unmark(object);
}
}
base.remove_marked(marker);
}
public override void set_comparator(Comparator comparator, ComparatorPredicate? predicate) {
selected.set_comparator(comparator, predicate);
if (visible != null)
......@@ -2791,22 +2723,6 @@ public class ViewCollection : DataCollection {
}
}
// This is only called by DataView.
public void internal_notify_unlinking(DataView view, SourceCollection sources) {
if (view_lock_count > 0) {
bool added = locked_unlinked_items.add(view);
assert(added);
}
}
// This is only called by DataView.
public void internal_notify_relinked(DataView view, SourceCollection sources) {
// don't assert if removed because it's possible the relink is happening in a session
// after the unlink
if (view_lock_count > 0)
locked_unlinked_items.remove(view);
}
protected override void notify_thawed() {
if (frozen_views_altered != null) {
foreach (DataView view in frozen_views_altered)
......
......@@ -646,8 +646,6 @@ public abstract class DataSource : DataObject {
unlinked_from_collection = collection;
backlinks = new Gee.HashMap<string, Gee.List<string>>();
contact_subscribers(subscriber_unlinking);
}
// This method is called by DataSource. It should not be called otherwise.
......@@ -661,10 +659,6 @@ public abstract class DataSource : DataObject {
commit_backlinks(unlinked_from_collection, dehydrate_backlinks());
}
private void subscriber_unlinking(DataView view) {
view.notify_source_unlinking(unlinked_from_collection);
}
// This method is called by SourceCollection. It should not be called otherwise.
public virtual void notify_relinking(SourceCollection collection) {
assert((backlinks != null) && (unlinked_from_collection == collection));
......@@ -678,16 +672,11 @@ public abstract class DataSource : DataObject {
backlinks = null;
unlinked_from_collection = null;
relinked(relinked_to);
contact_subscribers(subscriber_relinked);
// have the DataSource delete any persisted link state
commit_backlinks(null, null);
}
private void subscriber_relinked(DataView view) {
view.notify_source_relinked((SourceCollection) get_membership());
}
// Each DataSource has a unique typename. All DataSources of the same type should have the
// same typename. This method should be thread-safe.
//
......@@ -1348,20 +1337,6 @@ public class DataView : DataObject {
public virtual void notify_unsubscribed(DataSource source) {
unsubscribed(source);
}
// This is only called by DataSource
public virtual void notify_source_unlinking(SourceCollection sources) {
ViewCollection? membership = (ViewCollection?) get_membership();
if (membership != null)
membership.internal_notify_unlinking(this, sources);
}
// This is only called by DataSource
public virtual void notify_source_relinked(SourceCollection sources) {
ViewCollection? membership = (ViewCollection?) get_membership();
if (membership != null)
membership.internal_notify_relinked(this, sources);
}
}
public class ThumbnailView : DataView {
......
......@@ -218,6 +218,10 @@ public abstract class Page : Gtk.ScrolledWindow, SidebarPage {
return page_name;
}
public string to_string() {
return page_name;
}
public virtual bool is_renameable() {
return false;
}
......
......@@ -375,7 +375,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
}
private SourceCollection sources;
private ViewCollection controller = null;
private ViewCollection? parent_view = null;
private Gdk.Pixbuf swapped = null;
private bool pixbuf_dirty = true;
private Gtk.ToolButton rotate_button = null;
......@@ -741,44 +741,47 @@ public abstract class EditingHostPage : SinglePhotoPage {
dnd_handler = new DragAndDropHandler(this);
}
public override ViewCollection get_controller() {
return controller;
public ViewCollection? get_parent_view() {
return parent_view;
}
public bool has_photo() {
// ViewCollection should have either zero or one photos in it at all times
assert(get_view().get_count() <= 1);
return get_view().get_count() == 1;
return get_photo() != null;
}
public Photo? get_photo() {
// use the photo stored in our ViewCollection ... should either be zero or one in the
// collection at all times
assert(get_view().get_count() <= 1);
// If there is currently no selected photo, return null.
if (get_view().get_selected_count() == 0)
return null;
return (get_view().get_count() > 0)
? (Photo?) ((PhotoView) get_view().get_at(0)).get_source()
: null;
// Use the selected photo. There should only ever be one selected photo,
// which is the currently displayed photo.
assert(get_view().get_selected_count() == 1);
return (Photo) get_view().get_selected_at(0).get_source();
}
private void set_photo(Photo photo) {
zoom_slider.value_changed.disconnect(on_zoom_slider_value_changed);
zoom_slider.set_value(0.0);
zoom_slider.value_changed.connect(on_zoom_slider_value_changed);
// clear out the collection and use this as its sole member, selecting it so it's seen
// as the item to be operated upon by various observers (including drag-and-drop)
get_view().clear();
get_view().add(new PhotoView(photo));
get_view().select_all();
// also select it in the controller's collection, so when the user returns to that view
DataView view = get_view().get_view_for_source(photo);
assert(view != null);
// Select photo.
get_view().unselect_all();
Marker marker = get_view().mark(view);
get_view().select_marked(marker);
// also select it in the parent view's collection, so when the user returns to that view
// it's apparent which one was being viewed here
if (controller != null) {
controller.unselect_all();
Marker marker = controller.mark(controller.get_view_for_source(photo));
controller.select_marked(marker);
if (parent_view != null) {
parent_view.unselect_all();
DataView view_in_parent = parent_view.get_view_for_source(photo);
if (null != view_in_parent) {
Marker parent_marker = parent_view.mark(view_in_parent);
parent_view.select_marked(parent_marker);
}
}
}
......@@ -789,7 +792,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
// check if the photo altered while away
if (has_photo() && pixbuf_dirty)
replace_photo(controller, get_photo());
replace_photo(get_photo());
}
public override void switching_from() {
......@@ -800,6 +803,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
deactivate_tool();
parent_view = null;
get_view().clear();
}
......@@ -828,7 +832,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
private void on_selection_changed(Gee.Iterable<DataView> selected) {
foreach (DataView view in selected) {
replace_photo(controller, (Photo) view.get_source());
replace_photo((Photo) view.get_source());
break;
}
}
......@@ -856,7 +860,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
ORIGINAL_PIXBUF_CACHE_COUNT, master_cache_filter);
if (has_photo())
prefetch_neighbors(controller, get_photo());
prefetch_neighbors(get_view(), get_photo());
}
private bool master_cache_filter(Photo photo) {
......@@ -959,10 +963,23 @@ public abstract class EditingHostPage : SinglePhotoPage {
}
}
private DataView create_photo_view(DataSource source) {
return new PhotoView((PhotoSource) source);
}
private bool is_photo(DataSource source) {
return source is PhotoSource;
}
protected void display(ViewCollection controller, Photo photo) {
assert(controller.get_view_for_source(photo) != null);
if (controller != get_view() && controller != parent_view) {
get_view().clear();
get_view().copy_into(controller, create_photo_view, is_photo);
parent_view = controller;
}
replace_photo(controller, photo);
replace_photo(photo);
}
protected virtual void update_ui(Photo photo, bool missing) {
......@@ -1051,10 +1068,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
return pixbuf;
}
protected void replace_photo(ViewCollection new_controller, Photo new_photo) {
ViewCollection old_controller = this.controller;
controller = new_controller;
private void replace_photo(Photo new_photo) {
// if it's the same Photo object, the scaling hasn't changed, and the photo's file
// has not gone missing or re-appeared, there's nothing to do otherwise,
// just need to reload the image for the proper scaling. Of course, the photo's pixels
......@@ -1089,7 +1103,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
rebuild_caches("replace_photo");
if (old_photo != null)
cancel_prefetch_neighbors(old_controller, old_photo, new_controller, new_photo);
cancel_prefetch_neighbors(get_view(), old_photo, get_view(), new_photo);
cancel_zoom();
......@@ -1097,7 +1111,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
quick_update_pixbuf();
prefetch_neighbors(new_controller, new_photo);
prefetch_neighbors(get_view(), new_photo);
update_toolbar();
}
......@@ -1210,7 +1224,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
private void update_toolbar() {
bool multiple_photos = false;
int photo_ticker = 0;
foreach (DataSource source in controller.get_sources()) {
foreach (DataSource source in get_view().get_sources()) {
if (source is Photo)
photo_ticker++;
......@@ -2029,14 +2043,14 @@ public abstract class EditingHostPage : SinglePhotoPage {
Photo? current_photo = get_photo();
assert(current_photo != null);
DataView current = controller.get_view_for_source(get_photo());
DataView current = get_view().get_view_for_source(get_photo());
if (current == null)
return;
// search through the collection until the next photo is found or back at the starting point
DataView? next = current;
for (;;) {
next = controller.get_next(next);
next = get_view().get_next(next);
if (next == null)
break;
......@@ -2047,7 +2061,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
if (next_photo == current_photo)
break;
replace_photo(controller, next_photo);
replace_photo(next_photo);
break;
}
......@@ -2062,14 +2076,14 @@ public abstract class EditingHostPage : SinglePhotoPage {
Photo? current_photo = get_photo();
assert(current_photo != null);
DataView current = controller.get_view_for_source(get_photo());
DataView current = get_view().get_view_for_source(get_photo());
if (current == null)
return;
// loop until a previous photo is found or back at the starting point
DataView? previous = current;
for (;;) {
previous = controller.get_previous(previous);
previous = get_view().get_previous(previous);
if (previous == null)
break;
......@@ -2080,7 +2094,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
if (previous_photo == current_photo)
break;
replace_photo(controller, previous_photo);
replace_photo(previous_photo);
break;
}
......@@ -2089,6 +2103,10 @@ public abstract class EditingHostPage : SinglePhotoPage {
public bool has_current_tool() {
return (current_tool != null);
}
protected void unset_view_collection() {
parent_view = null;
}
}
//
......@@ -2097,7 +2115,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
public class LibraryPhotoPage : EditingHostPage {
private Gtk.Menu context_menu;
private CollectionPage return_page = null;
private CollectionPage? return_page = null;
private bool return_to_collection_on_release = false;
public LibraryPhotoPage() {
......@@ -2119,6 +2137,11 @@ public class LibraryPhotoPage : EditingHostPage {
// watch for updates to the external app settings
Config.get_instance().external_app_changed.connect(on_external_app_changed);
// Filter out trashed files.
get_view().install_view_filter(not_trashed_view_filter);
LibraryPhoto.global.items_unlinking.connect(on_photo_unlinking);
LibraryPhoto.global.items_relinked.connect(on_photo_relinked);
}
~LibraryPhotoPage() {
......@@ -2127,6 +2150,18 @@ public class LibraryPhotoPage : EditingHostPage {
Config.get_instance().external_app_changed.disconnect(on_external_app_changed);
}
public bool not_trashed_view_filter(DataView view) {
return !((MediaSource) view.get_source()).is_trashed();
}
private void on_photo_unlinking(Gee.Collection<DataSource> unlinking) {
get_view().reapply_view_filter();
}
private void on_photo_relinked(Gee.Collection<DataSource> relinked) {
get_view().reapply_view_filter();
}
protected override string? get_menubar_path() {
return "/PhotoMenuBar";
}
......@@ -2516,11 +2551,19 @@ public class LibraryPhotoPage : EditingHostPage {
public void display_for_collection(CollectionPage return_page, Photo photo) {
this.return_page = return_page;
return_page.destroy.connect(on_page_destroyed);
display(return_page.get_view(), photo);
}
public CollectionPage get_controller_page() {
public void on_page_destroyed() {
// The parent page was removed, so drop the reference to the page and
// its view collection.
return_page = null;
unset_view_collection();
}
public CollectionPage? get_controller_page() {
return return_page;
}
......@@ -2530,8 +2573,6 @@ public class LibraryPhotoPage : EditingHostPage {
// switched_to call.
assert(get_photo() != null);
lock_controller();
base.switched_to();
update_zoom_menu_item_sensitivity();
......@@ -2539,30 +2580,6 @@ public class LibraryPhotoPage : EditingHostPage {
set_display_ratings(Config.get_instance().get_display_photo_ratings());
}
public override void switching_from() {
base.switching_from();
unlock_controller();
}
private void on_controller_page_destroyed() {
unlock_controller();
}
private void lock_controller() {
get_controller().lock_view();
get_controller_page().destroy.connect(on_controller_page_destroyed);
get_controller().items_removed.connect(on_photos_removed);
}
private void unlock_controller() {
if (get_controller().is_view_locked()) {
get_controller().unlock_view();
get_controller_page().destroy.disconnect(on_controller_page_destroyed);
get_controller().items_removed.disconnect(on_photos_removed);
}
}
protected override Gdk.Pixbuf? get_bottom_left_trinket(int scale) {
if (!has_photo() || !Config.get_instance().get_display_photo_ratings())
......@@ -2780,14 +2797,11 @@ public class LibraryPhotoPage : EditingHostPage {
}
private void return_to_collection() {
ViewCollection controller = get_controller();
if (controller != null && has_photo()) {
controller.unselect_all();
return_page.set_cursor((CheckerboardItem) controller.get_view_for_source(get_photo()));
}
LibraryWindow.get_app().switch_to_page(return_page);
// Return to the previous page if it exists.
if (null != return_page)
LibraryWindow.get_app().switch_to_page(return_page);
else
LibraryWindow.get_app().switch_to_library_page();
}
private void on_remove_from_library() {
......@@ -2821,7 +2835,9 @@ public class LibraryPhotoPage : EditingHostPage {
private void on_flag_unflag() {
if (has_photo()) {
get_command_manager().execute(new FlagUnflagCommand(get_view().get_sources(),
Gee.ArrayList<DataSource> photo_list = new Gee.ArrayList<DataSource>();
photo_list.add(get_photo());
get_command_manager().execute(new FlagUnflagCommand(photo_list,
!((LibraryPhoto) get_photo()).is_flagged()));
}
}
......@@ -2830,11 +2846,6 @@ public class LibraryPhotoPage : EditingHostPage {
on_photo_removed((LibraryPhoto) source);
}
private void on_photos_removed(Gee.Iterable<DataObject> removed) {
foreach (DataObject object in removed)
on_photo_removed((LibraryPhoto) ((DataView) object).get_source());
}
private void on_photo_removed(LibraryPhoto photo) {
// only interested in current photo
if (photo == null || !photo.equals(get_photo()))
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment