Commit dd601ce7 authored by Jim Nelson's avatar Jim Nelson

Sidebar refactoring. Large commit, to be sure, but this should make it much...

Sidebar refactoring.  Large commit, to be sure, but this should make it much easier to manage the sidebar going into the future as well as put the intelligence for managing each branch in a separate compartment, where it belongs.  Other minor bugs were discovered while testing the code, fixed as well.
parent 72aad88f
......@@ -52,7 +52,6 @@ UNUNITIZED_SRC_FILES = \
Dialogs.vala \
Resources.vala \
Debug.vala \
Sidebar.vala \
ColorTransformation.vala \
EditingTools.vala \
CameraTable.vala \
......
......@@ -375,32 +375,6 @@ public abstract class CollectionPage : MediaPage {
LibraryWindow.get_app().switch_to_photo_page(this, photo);
}
}
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_PHOTOS);
}
public override CheckerboardItem? get_fullscreen_photo() {
// if a selection, use first selected photo, otherwise, no go
if (get_view().get_selected_count() > 0) {
foreach (DataView view in get_view().get_selected()) {
Thumbnail thumbnail = (Thumbnail) view;
if (thumbnail.get_media_source() is Photo)
return thumbnail;
}
return null;
}
// no selection, so use first photo
foreach (DataObject object in get_view().get_all()) {
Thumbnail thumbnail = (Thumbnail) object;
if (thumbnail.get_media_source() is Photo)
return thumbnail;
}
return null;
}
protected override bool on_app_key_pressed(Gdk.EventKey event) {
bool handled = true;
......@@ -692,7 +666,14 @@ public abstract class CollectionPage : MediaPage {
if (get_view().get_count() == 0)
return;
Thumbnail thumbnail = (Thumbnail) get_fullscreen_photo();
// use first selected photo, else use first photo
Gee.List<DataSource>? sources = (get_view().get_selected_count() > 0)
? get_view().get_selected_sources_of_type(typeof(LibraryPhoto))
: get_view().get_sources_of_type(typeof(LibraryPhoto));
if (sources == null || sources.size == 0)
return;
Thumbnail? thumbnail = (Thumbnail?) get_view().get_view_for_source(sources[0]);
if (thumbnail == null)
return;
......@@ -727,20 +708,3 @@ public abstract class CollectionPage : MediaPage {
}
}
public class LibraryPage : CollectionPage {
public LibraryPage(ProgressMonitor? monitor = null) {
base(_("Library"));
foreach (MediaSourceCollection sources in MediaCollectionRegistry.get_instance().get_all())
get_view().monitor_source_collection(sources, new CollectionViewManager(this), null, null, monitor);
}
protected override void get_config_photos_sort(out bool sort_order, out int sort_by) {
Config.get_instance().get_library_photos_sort(out sort_order, out sort_by);
}
protected override void set_config_photos_sort(bool sort_order, int sort_by) {
Config.get_instance().set_library_photos_sort(sort_order, sort_by);
}
}
......@@ -1521,7 +1521,7 @@ public class TrashUntrashPhotosCommand : PageCommand {
public class FlagUnflagCommand : MultipleDataSourceAtOnceCommand {
private bool flag;
public FlagUnflagCommand(Gee.Collection<DataSource> sources, bool flag) {
public FlagUnflagCommand(Gee.Collection<MediaSource> sources, bool flag) {
base (sources,
flag ? _("Flag") : _("Unflag"),
flag ? _("Flag selected photos") : _("Unflag selected photos"));
......
......@@ -12,6 +12,7 @@ public class Config {
public const string BOOL_AUTO_IMPORT_FROM_LIBRARY = PATH_SHOTWELL_PREFS + "/files/auto_import";
public const string STRING_IMPORT_DIRECTORY = PATH_SHOTWELL_PREFS + "/files/import_dir";
public const string STRING_BG_COLOR = PATH_SHOTWELL_PREFS + "/ui/background_color";
public const string BOOL_SORT_EVENTS_ASCENDING = PATH_SHOTWELL_PREFS + "/ui/events_sort_ascending";
public const double SLIDESHOW_DELAY_MAX = 30.0;
public const double SLIDESHOW_DELAY_MIN = 1.0;
......@@ -608,11 +609,11 @@ public class Config {
}
public bool get_events_sort_ascending() {
return get_bool("/apps/shotwell/preferences/ui/events_sort_ascending", false);
return get_bool(BOOL_SORT_EVENTS_ASCENDING, false);
}
public bool set_events_sort_ascending(bool sort) {
return set_bool("/apps/shotwell/preferences/ui/events_sort_ascending", sort);
return set_bool(BOOL_SORT_EVENTS_ASCENDING, sort);
}
public void get_library_photos_sort(out bool sort_order, out int sort_by) {
......@@ -863,4 +864,4 @@ public class Config {
public void set_use_lowercase_filenames(bool b) {
set_bool(PATH_SHOTWELL_PREFS + "/files/user_lowercase_filenames", b);
}
}
\ No newline at end of file
}
......@@ -4,6 +4,23 @@
* See the COPYING file in this distribution.
*/
// namespace for future migration of AppWindow alert and other question dialogs into single
// place: http://trac.yorba.org/ticket/3452
namespace Dialogs {
public bool confirm_delete_tag(Tag tag) {
int count = tag.get_sources_count();
string msg = ngettext(
"This will remove the tag \"%s\" from one photo. Continue?",
"This will remove the tag \"%s\" from %d photos. Continue?",
count).printf(tag.get_name(), count);
return AppWindow.negate_affirm_question(msg, _("_Cancel"), _("_Delete"),
Resources.DELETE_TAG_TITLE);
}
}
namespace ExportUI {
private static File current_export_dir = null;
......
......@@ -316,42 +316,7 @@ public abstract class EventsDirectoryPage : CheckerboardPage {
EventDirectoryItem event = (EventDirectoryItem) item;
LibraryWindow.get_app().switch_to_event(event.event);
}
private EventDirectoryItem? get_fullscreen_item() {
// if there's a selection, use first selected event with a photo; otherwise, no go.
if (get_view().get_selected_count() > 0) {
foreach (DataView view in get_view().get_selected()) {
EventDirectoryItem item = (EventDirectoryItem) view;
if (item.event.contains_media_type(Photo.TYPENAME))
return item;
}
return null;
}
// no selection, so use first event with photo
foreach (DataObject object in get_view().get_all()) {
EventDirectoryItem item = (EventDirectoryItem) object;
if (item.event.contains_media_type(Photo.TYPENAME))
return item;
}
return null;
}
public EventPage? get_fullscreen_event() {
EventDirectoryItem item = get_fullscreen_item();
// Yeah, this sucks. We can do better.
return (item != null) ? LibraryWindow.get_app().load_event_page(item.event) : null;
}
public override CheckerboardItem? get_fullscreen_photo() {
EventPage page = get_fullscreen_event();
return (page != null) ? page.get_fullscreen_photo() : null;
}
private void on_sort_changed(Gtk.Action action, Gtk.Action c) {
Gtk.RadioAction current = (Gtk.RadioAction) c;
......@@ -390,30 +355,12 @@ public abstract class EventsDirectoryPage : CheckerboardPage {
}
public class NoEventPage : CollectionPage {
static const string NO_EVENT_PAGE_NAME = _("No Event");
public class Stub : PageStub {
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_MISSING_FILES);
}
public override string get_name() {
return NO_EVENT_PAGE_NAME;
}
public override bool is_renameable() {
return false;
}
protected override Page construct_page() {
return ((Page) new NoEventPage());
}
}
public const string NAME = _("No Event");
// This seems very similar to EventSourceCollection -> ViewManager
private class NoEventViewManager : CollectionViewManager {
public NoEventViewManager(NoEventPage page) {
base(page);
base (page);
}
// this is not threadsafe
......@@ -425,18 +372,14 @@ public class NoEventPage : CollectionPage {
private static Alteration no_event_page_alteration = new Alteration("metadata", "event");
private NoEventPage() {
base(NO_EVENT_PAGE_NAME);
public NoEventPage() {
base (NAME);
ViewManager filter = new NoEventViewManager(this);
get_view().monitor_source_collection(LibraryPhoto.global, filter, no_event_page_alteration);
get_view().monitor_source_collection(Video.global, filter, no_event_page_alteration);
}
public static Stub create_stub() {
return new Stub();
}
protected override void get_config_photos_sort(out bool sort_order, out int sort_by) {
Config.get_instance().get_event_photos_sort(out sort_order, out sort_by);
}
......@@ -448,39 +391,9 @@ public class NoEventPage : CollectionPage {
public class EventPage : CollectionPage {
public class Stub : PageStub {
public Event event;
public Stub(Event event) {
this.event = event;
}
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_ONE_EVENT);
}
public override string get_name() {
return event.get_name();
}
public override string get_tooltip() {
return (event.has_name())
? String.strip_leading_zeroes("%s - %s".printf(event.get_name(), event.get_formatted_daterange()))
: event.get_formatted_daterange();
}
public override bool is_renameable() {
return (event != null);
}
protected override Page construct_page() {
return ((Page) new EventPage(event));
}
}
public Event page_event;
private Event page_event;
private EventPage(Event page_event) {
public EventPage(Event page_event) {
base (page_event.get_name());
this.page_event = page_event;
......@@ -532,10 +445,6 @@ public class EventPage : CollectionPage {
base.update_actions(selected_count, count);
}
public static Stub create_stub(Event event) {
return new Stub(event);
}
protected override void get_config_photos_sort(out bool sort_order, out int sort_by) {
Config.get_instance().get_event_photos_sort(out sort_order, out sort_by);
}
......@@ -557,42 +466,15 @@ public class EventPage : CollectionPage {
}
private void on_rename() {
LibraryWindow.get_app().sidebar_rename_in_place(this);
}
public override void rename(string new_name) {
get_command_manager().execute(new RenameEventCommand(page_event, new_name));
LibraryWindow.get_app().rename_event_in_sidebar(page_event);
}
}
public class MasterEventsDirectoryPage : EventsDirectoryPage {
public class Stub : PageStub {
public Stub() {
}
protected override Page construct_page() {
return new MasterEventsDirectoryPage(get_name());
}
public override string get_name() {
return _("Events");
}
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_EVENTS);
}
public override bool is_renameable() {
return false;
}
}
private MasterEventsDirectoryPage(string name) {
base(name, new EventDirectoryManager(), (Gee.Collection<Event>) Event.global.get_all());
}
public const string NAME = _("Events");
public static Stub create_stub() {
return new Stub();
public MasterEventsDirectoryPage() {
base (NAME, new EventDirectoryManager(), (Gee.Collection<Event>) Event.global.get_all());
}
}
......@@ -603,61 +485,9 @@ public class SubEventsDirectoryPage : EventsDirectoryPage {
UNDATED;
}
public class Stub : PageStub {
public SubEventsDirectoryPage.DirectoryType type;
public Time time;
private string page_name;
public Stub(SubEventsDirectoryPage.DirectoryType type, Time time) {
if (type == SubEventsDirectoryPage.DirectoryType.UNDATED) {
this.page_name = _("Undated");
} else {
this.page_name = time.format((type == SubEventsDirectoryPage.DirectoryType.YEAR) ?
_("%Y") : _("%B"));
}
this.type = type;
this.time = time;
}
protected override Page construct_page() {
return new SubEventsDirectoryPage(type, time);
}
public int get_month() {
return (type == SubEventsDirectoryPage.DirectoryType.MONTH) ? time.month : 0;
}
public int get_year() {
return time.year;
}
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_FOLDER_CLOSED);
}
public override string get_name() {
return page_name;
}
public override bool is_renameable() {
return false;
}
public bool matches(SubEventsDirectoryPage.DirectoryType type, Time time) {
if (type != this.type)
return false;
if (type == SubEventsDirectoryPage.DirectoryType.UNDATED) {
return true;
} else if (type == SubEventsDirectoryPage.DirectoryType.MONTH) {
return time.year == this.time.year && time.month == this.time.month;
} else {
assert(type == SubEventsDirectoryPage.DirectoryType.YEAR);
return time.year == this.time.year;
}
}
}
public const string UNDATED_PAGE_NAME = _("Undated");
public const string YEAR_FORMAT = _("%Y");
public const string MONTH_FORMAT = _("%B");
private class SubEventDirectoryManager : EventsDirectoryPage.EventDirectoryManager {
private int month = 0;
......@@ -701,21 +531,17 @@ public class SubEventsDirectoryPage : EventsDirectoryPage {
}
}
private SubEventsDirectoryPage(DirectoryType type, Time time) {
public SubEventsDirectoryPage(DirectoryType type, Time time) {
string page_name;
if (type == SubEventsDirectoryPage.DirectoryType.UNDATED) {
page_name = _("Undated");
page_name = UNDATED_PAGE_NAME;
} else {
page_name = time.format((type == DirectoryType.YEAR) ? _("%Y") : _("%B"));
page_name = time.format((type == DirectoryType.YEAR) ? YEAR_FORMAT : MONTH_FORMAT);
}
base(page_name, new SubEventDirectoryManager(type, time), null);
}
public static Stub create_stub(DirectoryType type, Time time) {
return new Stub(type, time);
}
public int get_month() {
return ((SubEventDirectoryManager) view_manager).get_month();
}
......
......@@ -5,22 +5,7 @@
*/
public class FlaggedPage : CollectionPage {
public class Stub : PageStub {
public Stub() {
}
protected override Page construct_page() {
return new FlaggedPage(get_name());
}
public override string get_name() {
return _("Flagged");
}
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_FLAGGED_PAGE);
}
}
public const string NAME = _("Flagged");
private class FlaggedViewManager : CollectionViewManager {
public FlaggedViewManager(FlaggedPage owner) {
......@@ -45,8 +30,8 @@ public class FlaggedPage : CollectionPage {
private Alteration prereq = new Alteration("metadata", "flagged");
private FlaggedSearchViewFilter search_filter = new FlaggedSearchViewFilter();
private FlaggedPage(string name) {
base (name);
public FlaggedPage() {
base (NAME);
view_manager = new FlaggedViewManager(this);
......@@ -54,10 +39,6 @@ public class FlaggedPage : CollectionPage {
get_view().monitor_source_collection(sources, view_manager, prereq);
}
public static Stub create_stub() {
return new Stub();
}
protected override void get_config_photos_sort(out bool sort_order, out int sort_by) {
Config.get_instance().get_library_photos_sort(out sort_order, out sort_by);
}
......
......@@ -695,17 +695,13 @@ public class ImportPage : CheckerboardPage {
return tracker;
}
public override GLib.Icon? get_icon() {
return icon != null ? icon : new GLib.ThemedIcon(Resources.ICON_CAMERAS);
}
// Ticket #3304 - Import page shouldn't display confusing message
// prior to import.
// TODO: replace this with approved text for "talking to camera,
// please wait" once new strings are being accepted.
// please wait" once new strings are being accepted.
protected override string get_view_empty_message() {
return ("");
}
}
private static int64 preview_comparator(void *a, void *b) {
return ((ImportPreview *) a)->get_import_source().get_exposure_time()
......@@ -836,10 +832,6 @@ public class ImportPage : CheckerboardPage {
Config.get_instance().set_display_photo_titles(display);
}
public override CheckerboardItem? get_fullscreen_photo() {
return null;
}
public override void switched_to() {
set_display_titles(Config.get_instance().get_display_photo_titles());
......@@ -1479,6 +1471,8 @@ public class ImportPage : CheckerboardPage {
}
public class ImportQueuePage : SinglePhotoPage {
public const string NAME = _("Importing...");
private Gee.ArrayList<BatchImport> queue = new Gee.ArrayList<BatchImport>();
private Gee.HashSet<BatchImport> cancel_unallowed = new Gee.HashSet<BatchImport>();
private BatchImport current_batch = null;
......@@ -1490,7 +1484,7 @@ public class ImportQueuePage : SinglePhotoPage {
public signal void batch_removed(BatchImport batch_import);
public ImportQueuePage() {
base(_("Importing..."), false);
base (NAME, false);
// Set up toolbar
Gtk.Toolbar toolbar = get_toolbar();
......@@ -1671,9 +1665,5 @@ public class ImportQueuePage : SinglePhotoPage {
private void on_fatal_error(ImportResult result, string message) {
AppWindow.error_message(message);
}
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_IMPORTING);
}
}
......@@ -5,26 +5,7 @@
*/
public class LastImportPage : CollectionPage {
public class Stub : PageStub {
public Stub() {
}
protected override Page construct_page() {
return new LastImportPage(get_name());
}
public override string get_name() {
return _("Last Import");
}
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_LAST_IMPORT);
}
public override bool is_renameable() {
return false;
}
}
public const string NAME = _("Last Import");
private class LastImportViewManager : CollectionViewManager {
private ImportID import_id;
......@@ -43,8 +24,8 @@ public class LastImportPage : CollectionPage {
private ImportID last_import_id = ImportID();
private Alteration last_import_alteration = new Alteration("metadata", "import-id");
private LastImportPage(string name) {
base (name);
public LastImportPage() {
base (NAME);
// be notified when the import rolls change
foreach (MediaSourceCollection col in MediaCollectionRegistry.get_instance().get_all()) {
......@@ -61,10 +42,6 @@ public class LastImportPage : CollectionPage {
}
}
public static Stub create_stub() {
return new Stub();
}
private void on_import_rolls_altered() {
// see if there's a new last ImportID, or no last import at all
ImportID? current_last_import_id =
......
......@@ -873,11 +873,12 @@ public abstract class MediaPage : CheckerboardPage {
if (get_view().get_selected_count() == 0)
return;
Gee.Collection<DataSource> sources = get_view().get_selected_sources();
Gee.Collection<MediaSource> sources =
(Gee.Collection<MediaSource>) get_view().get_selected_sources_of_type(typeof(MediaSource));
// If all are flagged, then unflag, otherwise flag
bool flag = false;
foreach (DataSource source in sources) {
foreach (MediaSource source in sources) {
Flaggable? flaggable = source as Flaggable;
if (flaggable != null && !flaggable.is_flagged()) {
flag = true;
......
......@@ -5,22 +5,7 @@
*/
public class OfflinePage : CheckerboardPage {
public class Stub : PageStub {
public Stub() {
}
protected override Page construct_page() {
return new OfflinePage(get_name());
}
public override string get_name() {
return _("Missing Files");
}
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_MISSING_FILES);
}
}
public const string NAME = _("Missing Files");
private class OfflineView : Thumbnail {
public OfflineView(MediaSource source) {
......@@ -40,8 +25,8 @@ public class OfflinePage : CheckerboardPage {
private OfflineSearchViewFilter search_filter = new OfflineSearchViewFilter();
private MediaViewTracker tracker;
private OfflinePage(string name) {
base (name);
public OfflinePage() {
base (NAME);
init_item_context_menu("/OfflineContextMenu");
init_toolbar("/OfflineToolbar");
......@@ -95,10 +80,6 @@ public class OfflinePage : CheckerboardPage {
return actions;
}
public static Stub create_stub() {
return new Stub();
}
public override Core.ViewTracker? get_view_tracker() {
return tracker;
}
......@@ -158,14 +139,6 @@ public class OfflinePage : CheckerboardPage {
AppWindow.get_instance().set_normal_cursor();
}
public override GLib.Icon? get_icon() {
return new GLib.ThemedIcon(Resources.ICON_MISSING_FILES);
}
public override CheckerboardItem? get_fullscreen_photo() {
return null;
}
public override SearchViewFilter get_search_view_filter() {
return search_filter;
}
......
......@@ -4,94 +4,6 @@
* See the COPYING file in this distribution.
*/
// In order to prevent creating a slew of Pages at app startup, lazily create them as the
// user needs them ... this may be supplemented in the future to discard unused Pages (in
// a lifo). This class also provides a host of information for the Sidebar and the LibraryWindow
// to use.
public abstract class PageStub : Object, SidebarPage {
private Page page = null;
private SidebarMarker marker = null;
protected abstract Page construct_page();
public abstract string get_name();
public virtual string get_tooltip() {
return (page != null) ? page.get_tooltip() : get_name();
}
public virtual bool is_renameable() {
return false;
}
public virtual void rename(string new_name) {
get_page().rename(new_name);
}
public virtual bool is_destroyable() {
return false;
}
public virtual void destroy_source() {
get_page().destroy_source();
}
public virtual GLib.Icon? get_icon() {
return null;
}
public bool has_page() {
return page != null;
}
public Page get_page() {
if (page == null) {
// create the page and set its marker, if one has been supplied
page = construct_page();
debug("Created page %s", page.get_page_name());
if (marker != null)
page.set_marker(marker);
// add this to the notebook and tell the notebook to show it (as per DevHelp)
LibraryWindow.get_app().add_to_notebook(page);
}
return page;
}
public string get_sidebar_text() {
return (page != null) ? page.get_sidebar_text() : get_name();
}
public SidebarMarker? get_marker() {
return (page != null) ? page.get_marker() : marker;
}
public void set_marker(SidebarMarker marker) {
this.marker = marker;
if (page != null)