diff options
| author | Jörg Frings-Fürst <debian@jff.email> | 2023-12-17 19:58:25 +0100 | 
|---|---|---|
| committer | Jörg Frings-Fürst <debian@jff.email> | 2023-12-17 19:58:25 +0100 | 
| commit | 4cb46f4de4b881e5b1f65af017dca6f3917e55e5 (patch) | |
| tree | fb359e210d2d9c30f5ad36a447ea29b62ae9bb56 /src | |
| parent | 841f952294b349b2b8e2afb5305ce34a3b59bb4b (diff) | |
| parent | f5a0cee8ccecc7b6c6c2d8e9fb6f6eecd53531fe (diff) | |
Update upstream source from tag 'upstream/0.32.4'
Update to upstream version '0.32.4'
with Debian dir 51798a6af1c9b7e48f979c020d72f3c5ddd964a6
Diffstat (limited to 'src')
| -rw-r--r-- | src/AppWindow.vala | 11 | ||||
| -rw-r--r-- | src/CollectionPage.vala | 46 | ||||
| -rw-r--r-- | src/DragAndDropHandler.vala | 10 | ||||
| -rw-r--r-- | src/Exporter.vala | 42 | ||||
| -rw-r--r-- | src/PhotoPage.vala | 6 | ||||
| -rw-r--r-- | src/PixbufCache.vala | 2 | ||||
| -rw-r--r-- | src/ProfileBrowser.vala | 2 | ||||
| -rw-r--r-- | src/Properties.vala | 4 | ||||
| -rw-r--r-- | src/SlideshowPage.vala | 43 | ||||
| -rw-r--r-- | src/config/ConfigurationInterfaces.vala | 25 | ||||
| -rw-r--r-- | src/config/GSettingsEngine.vala | 2 | ||||
| -rw-r--r-- | src/core/ViewCollection.vala | 51 | ||||
| -rw-r--r-- | src/dialogs/MultiTextEntryDialog.vala | 22 | ||||
| -rw-r--r-- | src/direct/DirectPhotoPage.vala | 6 | ||||
| -rw-r--r-- | src/editing_tools/EditingTools.vala | 10 | ||||
| -rw-r--r-- | src/library/LibraryWindow.vala | 16 | ||||
| -rw-r--r-- | src/main.vala | 63 | ||||
| -rw-r--r-- | src/photos/JfifSupport.vala | 2 | ||||
| -rw-r--r-- | src/video-support/meson.build | 4 | 
19 files changed, 308 insertions, 59 deletions
| diff --git a/src/AppWindow.vala b/src/AppWindow.vala index 1fb0515..7398c74 100644 --- a/src/AppWindow.vala +++ b/src/AppWindow.vala @@ -546,14 +546,21 @@ public abstract class AppWindow : PageWindow {      }  	public static int export_overwrite_or_replace_question(string message, -		string alt1, string alt2, string alt3, string alt4, string alt5, string alt6, +		string alt1, string alt2, string alt4, string alt6,          string? title = null, Gtk.Window? parent = null) {          Gtk.MessageDialog dialog = new Gtk.MessageDialog((parent != null) ? parent : get_instance(),              Gtk.DialogFlags.MODAL, Gtk.MessageType.QUESTION, Gtk.ButtonsType.NONE, "%s", message);          dialog.title = (title != null) ? title : Resources.APP_TITLE; -        dialog.add_buttons(alt1, 1, alt2, 2, alt3, 3, alt4, 4, alt5, 5, alt6, 6); +        var content = (Gtk.Box)dialog.get_message_area(); +        var c = new Gtk.CheckButton.with_label("Apply conflict resolution to all other conflicts"); +        c.show(); +        content.pack_end(c); +        dialog.add_buttons(alt1, 1, alt2, 2, alt4, 4, alt6, 6);          int response = dialog.run(); +        if (c.get_active()) { +            response |= 0x80; +        }          dialog.destroy(); diff --git a/src/CollectionPage.vala b/src/CollectionPage.vala index ac05f8b..9a96041 100644 --- a/src/CollectionPage.vala +++ b/src/CollectionPage.vala @@ -240,6 +240,7 @@ public abstract class CollectionPage : MediaPage {                  primary_is_video = true;          bool selection_has_videos = selection_has_video(); +        bool selection_has_photos = selection_has_photo();          bool page_has_photos = page_has_photo();          // don't allow duplication of the selection if it contains a video -- videos are huge and @@ -270,11 +271,14 @@ public abstract class CollectionPage : MediaPage {          set_action_sensitive("NewEvent", has_selected);          set_action_sensitive("AddTags", has_selected);          set_action_sensitive("ModifyTags", one_selected); -        set_action_sensitive("Slideshow", page_has_photos && (!primary_is_video)); + +        // Allow starting slideshow even if first selected item is a video. Otherwise the +        // behavior is quite confusing, it will start if you do not select anything and just skipt the video +        set_action_sensitive("Slideshow", (page_has_photos && !has_selected) || selection_has_photos);          set_action_sensitive("Print", (!selection_has_videos) && has_selected);          set_action_sensitive("Publish", has_selected); -        set_action_sensitive("SetBackground", (!selection_has_videos) && has_selected ); +        set_action_sensitive("SetBackground", has_selected && selection_has_photos);          if (has_selected) {              debug ("Setting action label for SetBackground...");              var label = one_selected @@ -679,23 +683,27 @@ public abstract class CollectionPage : MediaPage {          if (get_view().get_count() == 0)              return; -        // 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; -         -        LibraryPhoto? photo = thumbnail.get_media_source() as LibraryPhoto; -        if (photo == null) -            return; -         -        AppWindow.get_instance().go_fullscreen(new SlideshowPage(LibraryPhoto.global, get_view(), -            photo)); +        // check selection for valid starting photo, otherwise start at beginning of collection +        if (get_view().get_selected_count() > 0) { +            Gee.List<DataSource>? sources =  +                get_view().get_selected_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; +             +            LibraryPhoto? photo = thumbnail.get_media_source() as LibraryPhoto; +            if (photo == null) +                return; +             +            AppWindow.get_instance().go_fullscreen(new SlideshowPage(LibraryPhoto.global,  +                get_view(), photo)); +        } else { +            AppWindow.get_instance().go_fullscreen(new SlideshowPage(LibraryPhoto.global, +                get_view())); +        }      }      protected override bool on_ctrl_pressed(Gdk.EventKey? event) { diff --git a/src/DragAndDropHandler.vala b/src/DragAndDropHandler.vala index ece6d9d..9ac6e46 100644 --- a/src/DragAndDropHandler.vala +++ b/src/DragAndDropHandler.vala @@ -28,6 +28,7 @@ public class DragAndDropHandler {      private Gtk.Widget event_source;      private File? drag_destination = null;      private ExporterUI exporter = null; +    private Gdk.DragAction action = Gdk.DragAction.COPY;      public DragAndDropHandler(Page page) {          this.page = page; @@ -47,7 +48,7 @@ public class DragAndDropHandler {          // register what's available on this DnD Source          Gtk.drag_source_set(event_source, Gdk.ModifierType.BUTTON1_MASK, SOURCE_TARGET_ENTRIES, -            Gdk.DragAction.COPY); +            Gdk.DragAction.COPY | Gdk.DragAction.MOVE);          // attach to the event source's DnD signals, not the Page's, which is a NO_WINDOW widget          // and does not emit them @@ -100,6 +101,8 @@ public class DragAndDropHandler {          if (page == null || page.get_view().get_selected_count() == 0)              return; +        action = context.get_suggested_action(); +          switch (target_type) {              case TargetType.XDS:                  // Fetch the XDS property that has been set with the destination path @@ -147,7 +150,7 @@ public class DragAndDropHandler {              return;          } -        debug("Exporting to %s", drag_destination.get_path()); +        debug("Exporting to %s, mode %s", drag_destination.get_path(),  action == Gdk.DragAction.COPY ? "current" : "unmodified");           // drag-and-drop export doesn't pop up an export dialog, so use what are likely the          // most common export settings (the current -- or "working" -- file format, with @@ -155,7 +158,8 @@ public class DragAndDropHandler {          if (drag_destination.get_path() != null) {              exporter = new ExporterUI(new Exporter(                  (Gee.Collection<Photo>) page.get_view().get_selected_sources(), -                drag_destination, Scaling.for_original(), ExportFormatParameters.current())); +                drag_destination, Scaling.for_original(), action == Gdk.DragAction.COPY ? ExportFormatParameters.current()  +                                                                                        : ExportFormatParameters.unmodified()));              exporter.export(on_export_completed);          } else {              AppWindow.error_message(_("Photos cannot be exported to this directory.")); diff --git a/src/Exporter.vala b/src/Exporter.vala index a7f7b6b..cf63938 100644 --- a/src/Exporter.vala +++ b/src/Exporter.vala @@ -54,6 +54,7 @@ public class Exporter : Object {      public enum Overwrite {          YES,          NO, +        SKIP_ALL,          CANCEL,          REPLACE_ALL,          RENAME, @@ -270,6 +271,15 @@ public class Exporter : Object {                              return false; +                        case Overwrite.SKIP_ALL: +                            completed_count = to_export.size; +                            if (monitor != null) { +                                if (!monitor(completed_count, to_export.size)) { +                                    cancellable.cancel(); +                                     +                                } +                            } +                            return false;                          case Overwrite.NO:                          default:                              completed_count++; @@ -346,30 +356,40 @@ public class ExporterUI {          progress_dialog.set_modal(false);          string question = _("File %s already exists. Replace?").printf(file.get_basename());          int response = AppWindow.export_overwrite_or_replace_question(question,  -            _("_Skip"), _("Rename"), _("Rename All"),_("_Replace"), _("Replace _All"), _("_Cancel"), _("Export")); +            _("_Skip"), _("Rename"), _("_Replace"), _("_Cancel"), _("Export file conflict"));          progress_dialog.set_modal(true); +        var apply_all = (response & 0x80) != 0; +        response &= 0x0f; +          switch (response) {          case 2: -            return Exporter.Overwrite.RENAME; -             -        case 3: -            return Exporter.Overwrite.RENAME_ALL; -             +            if (apply_all) { +                return Exporter.Overwrite.RENAME_ALL; +            } +            else { +                return Exporter.Overwrite.RENAME; +            }          case 4: -            return Exporter.Overwrite.YES; -             -        case 5: -            return Exporter.Overwrite.REPLACE_ALL; +            if (apply_all) { +                return Exporter.Overwrite.REPLACE_ALL; +            } else { +                return Exporter.Overwrite.YES; +            }          case 6:              return Exporter.Overwrite.CANCEL;          case 1: +            if (apply_all) { +                return Exporter.Overwrite.SKIP_ALL; +            } else { +                return Exporter.Overwrite.NO; +            }          default:              return Exporter.Overwrite.NO; -        } +    }      }      private bool on_export_failed(Exporter exporter, File file, int remaining, Error err) { diff --git a/src/PhotoPage.vala b/src/PhotoPage.vala index a279d89..5e94c24 100644 --- a/src/PhotoPage.vala +++ b/src/PhotoPage.vala @@ -966,7 +966,7 @@ public abstract class EditingHostPage : SinglePhotoPage {          return photo.has_transformations() || photo.has_editable();      } -    private void on_pixbuf_fetched(Photo photo, Gdk.Pixbuf? pixbuf, Error? err) { +    private void on_pixbuf_fetched(Photo photo, owned Gdk.Pixbuf? pixbuf, Error? err) {          // if not of the current photo, nothing more to do          if (!photo.equals(get_photo()))              return; @@ -986,7 +986,7 @@ public abstract class EditingHostPage : SinglePhotoPage {                          photo, out tool_pixbuf_dim);                      if (tool_pixbuf != null) { -                         pixbuf = tool_pixbuf; +                        pixbuf = tool_pixbuf;                          max_dim = tool_pixbuf_dim;                      }                  } catch(Error err) { @@ -1410,7 +1410,7 @@ public abstract class EditingHostPage : SinglePhotoPage {          Gdk.Pixbuf original;          try {              original = get_photo().get_original_orientation().rotate_pixbuf( -                get_photo().get_prefetched_copy()); +                get_photo().get_master_pixbuf(cache.get_scaling()));          } catch (Error err) {              return;          } diff --git a/src/PixbufCache.vala b/src/PixbufCache.vala index 6ff740e..cee33c6 100644 --- a/src/PixbufCache.vala +++ b/src/PixbufCache.vala @@ -80,7 +80,7 @@ public class PixbufCache : Object {      private Gee.ArrayList<Photo> lru = new Gee.ArrayList<Photo>();      private Gee.HashMap<Photo, FetchJob> in_progress = new Gee.HashMap<Photo, FetchJob>(); -    public signal void fetched(Photo photo, Gdk.Pixbuf? pixbuf, Error? err); +    public signal void fetched(Photo photo, owned Gdk.Pixbuf? pixbuf, Error? err);      public PixbufCache(SourceCollection sources, PhotoType type, Scaling scaling, int max_count,          CacheFilter? filter = null) { diff --git a/src/ProfileBrowser.vala b/src/ProfileBrowser.vala index 1591fce..7331918 100644 --- a/src/ProfileBrowser.vala +++ b/src/ProfileBrowser.vala @@ -133,7 +133,7 @@ namespace Shotwell {              pack_end(revealer, true);              var label = new Gtk.Label(null); -            label.set_markup("<span weight=\"bold\">%s</span>".printf(profile.name)); +            label.set_markup("<span weight=\"bold\">%s</span>".printf(Markup.escape_text(profile.name)));              label.halign = Gtk.Align.START;              content.pack_start(label, true, true, 6); diff --git a/src/Properties.vala b/src/Properties.vala index b8c3e0d..7c6ab89 100644 --- a/src/Properties.vala +++ b/src/Properties.vala @@ -50,7 +50,7 @@ private abstract class Properties : Gtk.Box {              }              if (href == null) { -                info_label.set_text(is_string_empty(info_text) ? "" : info_text); +                info_label.set_markup(is_string_empty(info_text) ? "" : info_text);              } else {                  info_label.set_markup("<a href=\"%s\">%s</a>".printf(href, Markup.escape_text(info_text)));              } @@ -596,7 +596,7 @@ private class ExtendedProperties : Properties {              // nothing special to be done for now for Events          } else {              add_line(_("Location:"), (file_path != "" && file_path != null) ? -                file_path.replace("&", "&") : NO_VALUE); +                Markup.escape_text(file_path) : NO_VALUE);              add_line(_("File size:"), (filesize > 0) ?                  format_size((int64) filesize) : NO_VALUE); diff --git a/src/SlideshowPage.vala b/src/SlideshowPage.vala index adfec7f..94dd3ae 100644 --- a/src/SlideshowPage.vala +++ b/src/SlideshowPage.vala @@ -9,6 +9,7 @@ class SlideshowPage : SinglePhotoPage {      private const int CHECK_ADVANCE_MSEC = 250;      private SourceCollection sources; +    private ViewCollection controller_source;      private ViewCollection controller;      private Photo current;      private Gtk.ToolButton play_pause_button; @@ -17,6 +18,7 @@ class SlideshowPage : SinglePhotoPage {      private Timer timer = new Timer();      private bool playing = true;      private bool exiting = false; +    private bool shuffled;      private string[] transitions;      private Screensaver screensaver; @@ -39,6 +41,8 @@ class SlideshowPage : SinglePhotoPage {          unowned Gtk.Adjustment transition_effect_adjustment;          [GtkChild]          unowned Gtk.CheckButton show_title_button; +        [GtkChild] +        unowned Gtk.CheckButton shuffle_button;          public SettingsDialog() {              Object (use_header_bar: Resources.use_header_bar()); @@ -78,6 +82,9 @@ class SlideshowPage : SinglePhotoPage {              bool show_title = Config.Facade.get_instance().get_slideshow_show_title();              show_title_button.active = show_title; +            bool shuffle = Config.Facade.get_instance().get_slideshow_shuffle(); +            shuffle_button.active = shuffle; +                          on_transition_changed();          } @@ -111,13 +118,19 @@ class SlideshowPage : SinglePhotoPage {          public bool get_show_title() {              return show_title_button.active;          } +         +        public bool get_shuffle() { +            return shuffle_button.active; +        }      } -    public SlideshowPage(SourceCollection sources, ViewCollection controller, Photo start) { +    public SlideshowPage(SourceCollection sources, ViewCollection controller, Photo? start = null) {          base(_("Slideshow"), true);          this.sources = sources; -        this.controller = controller; +        controller_source = controller; +        shuffled = Config.Facade.get_instance().get_slideshow_shuffle(); +        this.controller = shuffled ? controller.shuffled_copy(start) : controller;          Gee.Collection<string> pluggables = TransitionEffectsManager.get_instance().get_effect_ids();          Gee.ArrayList<string> a = new Gee.ArrayList<string>(); @@ -125,7 +138,9 @@ class SlideshowPage : SinglePhotoPage {          a.remove(NullTransitionDescriptor.EFFECT_ID);          a.remove(RandomEffectDescriptor.EFFECT_ID);          transitions = a.to_array(); -        current = start; +         +        current = (start == null)  +            ? (Photo) this.controller.get_first_photo().get_source() : start;          update_transition_effect(); @@ -284,8 +299,13 @@ class SlideshowPage : SinglePhotoPage {      protected override void on_next_photo() {          DataView view = controller.get_view_for_source(current); +        bool wrapped;          Photo? next_photo = null; -        DataView? start_view = controller.get_next(view); +        DataView? start_view = controller.get_next(view, out wrapped); +        if (wrapped && shuffled) { +            controller = controller_source.shuffled_copy(); +            start_view = controller.get_first(); +        }          DataView? next_view = start_view;          while (next_view != null) { @@ -294,7 +314,11 @@ class SlideshowPage : SinglePhotoPage {                  break;              } -            next_view = controller.get_next(next_view); +            next_view = controller.get_next(next_view, out wrapped); +            if (wrapped && shuffled) { +                controller = controller_source.shuffled_copy(); +                start_view = controller.get_first(); +            }              if (next_view == start_view) {                  warning("on_next( ): can't advance to next photo: collection has only videos"); @@ -369,6 +393,7 @@ class SlideshowPage : SinglePhotoPage {          playing = false;          hide_toolbar();          suspend_cursor_hiding(); +        bool old_shuffled = Config.Facade.get_instance().get_slideshow_shuffle();          if (settings_dialog.run() == Gtk.ResponseType.OK) {              // sync with the config setting so it will persist @@ -378,9 +403,17 @@ class SlideshowPage : SinglePhotoPage {              Config.Facade.get_instance().set_slideshow_transition_effect_id(settings_dialog.get_transition_effect_id());              Config.Facade.get_instance().set_slideshow_show_title(settings_dialog.get_show_title()); +            shuffled = settings_dialog.get_shuffle(); +            Config.Facade.get_instance().set_slideshow_shuffle(shuffled); +                          update_transition_effect();          } +        if (old_shuffled && !shuffled) +            controller = controller_source; +        else if (!old_shuffled && shuffled) +            controller = controller_source.shuffled_copy(current); +                  settings_dialog.destroy();          restore_cursor_hiding();          playing = slideshow_playing; diff --git a/src/config/ConfigurationInterfaces.vala b/src/config/ConfigurationInterfaces.vala index 12c7da1..ffd0d31 100644 --- a/src/config/ConfigurationInterfaces.vala +++ b/src/config/ConfigurationInterfaces.vala @@ -89,6 +89,7 @@ public enum ConfigurableProperty {      SLIDESHOW_TRANSITION_DELAY,      SLIDESHOW_TRANSITION_EFFECT_ID,      SLIDESHOW_SHOW_TITLE, +    SLIDESHOW_SHUFFLE,      USE_24_HOUR_TIME,      USE_LOWERCASE_FILENAMES, @@ -304,6 +305,9 @@ public enum ConfigurableProperty {              case SLIDESHOW_SHOW_TITLE:                  return "SLIDESHOW_SHOW_TITLE"; +            case SLIDESHOW_SHUFFLE: +                return "SLIDESHOW_SHUFFLE"; +                              case USE_24_HOUR_TIME:                  return "USE_24_HOUR_TIME"; @@ -1782,6 +1786,27 @@ public abstract class ConfigurationFacade : Object {      }      // +    // Slideshow shuffle +    // +    public virtual bool get_slideshow_shuffle() { +        try { +            return get_engine().get_bool_property(ConfigurableProperty.SLIDESHOW_SHUFFLE); +        } catch (ConfigurationError err) { +            on_configuration_error(err); +             +            return false; +        } +    } +     +    public virtual void set_slideshow_shuffle(bool shuffle) { +        try { +            get_engine().set_bool_property(ConfigurableProperty.SLIDESHOW_SHUFFLE, shuffle); +        } catch (ConfigurationError err) { +            on_configuration_error(err); +        } +    } +     +    //      // use 24 hour time      //      public virtual bool get_use_24_hour_time() { diff --git a/src/config/GSettingsEngine.vala b/src/config/GSettingsEngine.vala index d4d95c6..8bf53ec 100644 --- a/src/config/GSettingsEngine.vala +++ b/src/config/GSettingsEngine.vala @@ -101,6 +101,7 @@ public class GSettingsConfigurationEngine : ConfigurationEngine, GLib.Object {          schema_names[ConfigurableProperty.SLIDESHOW_TRANSITION_DELAY] = SLIDESHOW_PREFS_SCHEMA_NAME;          schema_names[ConfigurableProperty.SLIDESHOW_TRANSITION_EFFECT_ID] = SLIDESHOW_PREFS_SCHEMA_NAME;          schema_names[ConfigurableProperty.SLIDESHOW_SHOW_TITLE] = SLIDESHOW_PREFS_SCHEMA_NAME; +        schema_names[ConfigurableProperty.SLIDESHOW_SHUFFLE] = SLIDESHOW_PREFS_SCHEMA_NAME;          schema_names[ConfigurableProperty.USE_24_HOUR_TIME] = UI_PREFS_SCHEMA_NAME;          schema_names[ConfigurableProperty.USE_LOWERCASE_FILENAMES] = FILES_PREFS_SCHEMA_NAME; @@ -175,6 +176,7 @@ public class GSettingsConfigurationEngine : ConfigurationEngine, GLib.Object {          key_names[ConfigurableProperty.SLIDESHOW_TRANSITION_DELAY] = "transition-delay";          key_names[ConfigurableProperty.SLIDESHOW_TRANSITION_EFFECT_ID] = "transition-effect-id";          key_names[ConfigurableProperty.SLIDESHOW_SHOW_TITLE] = "show-title"; +        key_names[ConfigurableProperty.SLIDESHOW_SHUFFLE] = "shuffle";          key_names[ConfigurableProperty.USE_24_HOUR_TIME] = "use-24-hour-time";          key_names[ConfigurableProperty.USE_LOWERCASE_FILENAMES] = "use-lowercase-filenames";      } diff --git a/src/core/ViewCollection.vala b/src/core/ViewCollection.vala index 7e13fb0..c3848c1 100644 --- a/src/core/ViewCollection.vala +++ b/src/core/ViewCollection.vala @@ -298,6 +298,33 @@ public class ViewCollection : DataCollection {          add_many(copy_view);      } +    // for use in shuffled slideshow +    public ViewCollection shuffled_copy(Photo? start = null) { +        ViewCollection shuffled = new ViewCollection("shuffled copy of " + to_string()); +         +        Gee.Collection<DataObject> data = get_all(); +        Gee.ArrayList<DataObject> data_copy = new Gee.ArrayList<DataObject>(); +        foreach (DataObject object in data) { +            DataView view = (DataView) object; +            if (view.get_source() is PhotoSource &&  +                (start == null || view.get_source() != start)) { +                    data_copy.add(new PhotoView((PhotoSource) ((DataView) object).get_source())); +            } +        } +         +        for (int ctr = data_copy.size - 1; ctr > 0; ctr--) { +            int rand = GLib.Random.int_range(0, ctr + 1); +            DataObject temp = data_copy.get(rand); +            data_copy.set(rand, data_copy.get(ctr)); +            data_copy.set(ctr, temp); +        } +         +        if (start != null) +            shuffled.add(new PhotoView(start)); +        shuffled.add_many(data_copy); +        return shuffled; +    } +          public bool is_view_filter_installed(ViewFilter f) {          return filters.contains(f);      } @@ -740,12 +767,30 @@ public class ViewCollection : DataCollection {          // _something_...          return get_first();      } +     +    public virtual DataView? get_first_photo() { +        if (get_count() < 1) +            return null; +             +        DataView dv = get_first(); +        int num_views = get_count(); +         +        while ((dv != null) && (index_of(dv) < (num_views))) { +            if (dv.get_source() is PhotoSource) +                return dv; +            else +                dv = get_next(dv); +        } +         +        return null; +    }      public virtual DataView? get_last() {          return (get_count() > 0) ? (DataView?) get_at(get_count() - 1) : null;      } -    public virtual DataView? get_next(DataView view) { +    public virtual DataView? get_next(DataView view, out bool? wrapped = null) { +        wrapped = false;          if (get_count() == 0)              return null; @@ -754,8 +799,10 @@ public class ViewCollection : DataCollection {              return null;          index++; -        if (index >= get_count()) +        if (index >= get_count()) {              index = 0; +            wrapped = true; +        }          return (DataView?) get_at(index);      } diff --git a/src/dialogs/MultiTextEntryDialog.vala b/src/dialogs/MultiTextEntryDialog.vala index ddbd59b..097dabf 100644 --- a/src/dialogs/MultiTextEntryDialog.vala +++ b/src/dialogs/MultiTextEntryDialog.vala @@ -5,16 +5,36 @@   * See the COPYING file in this distribution.   */ + class DismissableTextView : Gtk.TextView { +    public signal void edit_done(); + +    public override bool key_press_event(Gdk.EventKey ev) { +        if (!(Gdk.ModifierType.CONTROL_MASK in ev.state)) { +            return base.key_press_event(ev); +        } + +        if (Gdk.keyval_name(ev.keyval) == "KP_Enter" || +            Gdk.keyval_name(ev.keyval) == "Return") { +            edit_done(); +            return true; +        } + +        return base.key_press_event(ev); +    } + } +  [GtkTemplate (ui = "/org/gnome/Shotwell/ui/multitextentrydialog.ui")]  public class MultiTextEntryDialog : Gtk.Dialog {      public delegate bool OnModifyValidateType(string text);      private unowned OnModifyValidateType on_modify_validate;      [GtkChild] -    private unowned Gtk.TextView entry; +    private unowned DismissableTextView entry;      public MultiTextEntryDialog() {          Object (use_header_bar: Resources.use_header_bar()); + +        entry.edit_done.connect(() => {response(Gtk.ResponseType.OK);});      }      public void setup(OnModifyValidateType? modify_validate, string title, string label, string? initial_text) { diff --git a/src/direct/DirectPhotoPage.vala b/src/direct/DirectPhotoPage.vala index 50321e9..b79eb0b 100644 --- a/src/direct/DirectPhotoPage.vala +++ b/src/direct/DirectPhotoPage.vala @@ -411,18 +411,24 @@ public class DirectPhotoPage : EditingHostPage {          string[] output_format_extensions =              effective_export_format.get_properties().get_known_extensions();          Gtk.FileFilter output_format_filter = new Gtk.FileFilter(); +        output_format_filter.set_filter_name(_("Supported image formats"));          foreach(string extension in output_format_extensions) {              string uppercase_extension = extension.up();              output_format_filter.add_pattern("*." + extension);              output_format_filter.add_pattern("*." + uppercase_extension);          } +        Gtk.FileFilter all_files = new Gtk.FileFilter(); +        all_files.add_pattern("*"); +        all_files.set_filter_name(_("All files")); +          var save_as_dialog = new Gtk.FileChooserNative(_("Save As"),               AppWindow.get_instance(), Gtk.FileChooserAction.SAVE, Resources.OK_LABEL, Resources.CANCEL_LABEL);          save_as_dialog.set_select_multiple(false);          save_as_dialog.set_current_name(filename);          save_as_dialog.set_current_folder(current_save_dir.get_path());          save_as_dialog.add_filter(output_format_filter); +        save_as_dialog.add_filter(all_files);          save_as_dialog.set_do_overwrite_confirmation(true);          save_as_dialog.set_local_only(false); diff --git a/src/editing_tools/EditingTools.vala b/src/editing_tools/EditingTools.vala index 0042d57..3345a3f 100644 --- a/src/editing_tools/EditingTools.vala +++ b/src/editing_tools/EditingTools.vala @@ -1294,11 +1294,13 @@ public class CropTool : EditingTool {          // scaled_crop is not maintained relative to photo's position on canvas          Box offset_scaled_crop = scaled_crop.get_offset(scaled_pixbuf_pos.x, scaled_pixbuf_pos.y); +        var xmul = (int)Math.lround(x * Application.get_scale()); +        var ymul = (int)Math.lround(y * Application.get_scale()); +          // determine where the mouse down landed and store for future events -        in_manipulation = offset_scaled_crop.approx_location((int)Math.lround(x * Application.get_scale()), -        (int)Math.lround(y * Application.get_scale())); -        last_grab_x = x -= scaled_pixbuf_pos.x; -        last_grab_y = y -= scaled_pixbuf_pos.y; +        in_manipulation = offset_scaled_crop.approx_location(xmul, ymul); +        last_grab_x = xmul - scaled_pixbuf_pos.x; +        last_grab_y = ymul - scaled_pixbuf_pos.y;          // repaint because the crop changes on a mouse down          canvas.repaint(); diff --git a/src/library/LibraryWindow.vala b/src/library/LibraryWindow.vala index 849ae2e..280a50b 100644 --- a/src/library/LibraryWindow.vala +++ b/src/library/LibraryWindow.vala @@ -765,17 +765,27 @@ public class LibraryWindow : AppWindow {          }          Gee.ArrayList<FileImportJob> jobs = new Gee.ArrayList<FileImportJob>(); +        Gee.ArrayList<string> rejected = new Gee.ArrayList<string>();          foreach (string uri in uris) {              File file_or_dir = File.new_for_uri(uri); +              if (file_or_dir.get_path() == null) { -                // TODO: Specify which directory/file. -                AppWindow.error_message(_("Photos cannot be imported from this directory.")); -                 +                rejected.add(uri);                  continue;              }              jobs.add(new FileImportJob(file_or_dir, copy_to_library, recurse));          } + +        if (rejected.size > 0) { +            // TODO: Specify which directory/file. +            //var message = ngettext("Photos cannot be imported from this folder", "Photos cannot be imported from these folders", rejected.size) +            var message = _("Photos cannot be imported from this directory."); +            foreach (var uri in rejected) { +                message += uri; +            } +            AppWindow.error_message(message); +        }          if (jobs.size > 0) {              BatchImport batch_import = new BatchImport(jobs, job_name, import_reporter); diff --git a/src/main.vala b/src/main.vala index 32e3d83..d07b7f5 100644 --- a/src/main.vala +++ b/src/main.vala @@ -205,6 +205,67 @@ void library_exec(string[] mounts) {          run_system_pictures_import();      } +    bool heif = false; +    bool jxl = false; +    bool png = false; +    bool tiff = false; +    bool avif = false; +    bool bmp = false; +    bool gif = false; + +    var formats = Gdk.Pixbuf.get_formats(); +    foreach (var format in formats) { +        if ("image/heif" in format.get_mime_types()) { +            heif = true; +        } +        if ("image/jxl" in format.get_mime_types()) { +            jxl = true; +        } +        if ("image/png" in format.get_mime_types()) { +            png = true; +        } +        if ("image/tiff" in format.get_mime_types()) { +            tiff = true; +        } +        if ("image/avif" in format.get_mime_types()) { +            avif = true; +        } +        if ("image/bmp" in format.get_mime_types()) { +            bmp = true; +        } +        if ("image/gif" in format.get_mime_types()) { +            gif = true; +        } +    } + +    bool can_read_bmff = false; +    Bytes b = null; +    try { +        b = GLib.resources_lookup_data("/org/gnome/Shotwell/misc/canary.avif", GLib.ResourceLookupFlags.NONE); +    } catch (Error err) { +        error("Failed to look up mandatory resource: %s", err.message); +    } + +    try { +        var m = new GExiv2.Metadata(); +        m.open_buf(b.get_data()); +        can_read_bmff = true; +    } catch (Error err) { +        // Do nothing +    } + +    message("Supported codecs...."); +    message("  WEBP   : yes, builtin"); +    message("  RAW    : yes, builtin"); +    message("  CR3    : %s", can_read_bmff ? "yes" : "no"); +    message("  JPEG   : yes, gdk-pixbuf"); +    message("  PNG    : %s, gdk-pixbuf", png ? "yes" : "no"); +    message("  GIF    : %s, gdk-pixbuf", gif ? "yes" : "no"); +    message("  TIFF   : %s, gdk-pixbuf", tiff ? "yes" : "no"); +    message("  JPEG XL: %s, gdk-pixbuf, %s meta-data", jxl  ? "yes" : "no", can_read_bmff ? "yes" : "no"); +    message("  AVIF   : %s, gdk-pixbuf, %s meta-data", avif  ? "yes" : "no", can_read_bmff ? "yes" : "no"); +    message("  HEIF   : %s, gdk-pixbuf, %s meta-data", heif ?  "yes" : "no", can_read_bmff ? "yes" : "no"); +          debug("%lf seconds to Gtk.main()", startup_timer.elapsed());      Application.get_instance().start(); @@ -405,6 +466,8 @@ void main(string[] args) {          return;      } +    typeof(DismissableTextView).ensure(); +      if (CommandlineOptions.browse_profiles) {          var window = new Gtk.Dialog();          window.set_title (_("Choose Shotwell's profile")); diff --git a/src/photos/JfifSupport.vala b/src/photos/JfifSupport.vala index 27a8b11..fc43663 100644 --- a/src/photos/JfifSupport.vala +++ b/src/photos/JfifSupport.vala @@ -51,7 +51,7 @@ public class JfifFileFormatDriver : PhotoFileFormatDriver {  public class JfifFileFormatProperties : PhotoFileFormatProperties {      private static string[] KNOWN_EXTENSIONS = { -        "jpg", "jpeg", "jpe", "thm" +        "jpg", "jpeg", "jpe", "thm", "mpo"      };      private static string[] KNOWN_MIME_TYPES = { diff --git a/src/video-support/meson.build b/src/video-support/meson.build index da3f9d7..187d723 100644 --- a/src/video-support/meson.build +++ b/src/video-support/meson.build @@ -8,8 +8,10 @@ executable(          gstreamer,          gstreamer_pbu      ], -    c_args : '-DGST_PB_UTILS_IS_DISCOVERER_INFO=GST_IS_DISCOVERER_INFO'      # Work-around for wrong type-check macro generated by valac +    c_args : '-DGST_PB_UTILS_IS_DISCOVERER_INFO=GST_IS_DISCOVERER_INFO', +    install: true, +    install_dir : join_paths(get_option('libexecdir'), 'shotwell')  )  libvideometadata_handling = static_library( | 
