[shotwell] Extract TextEntry dialog



commit 70215a717702713711332f9270255d4f8edd455c
Author: Jens Georg <mail jensge org>
Date:   Wed Dec 20 16:40:55 2017 +0100

    Extract TextEntry dialog

 org.gnome.Shotwell.gresource.xml      |    1 +
 src/Dialogs.vala                      |  166 ---------------------------------
 src/dialogs/EntryMultiCompletion.vala |   97 +++++++++++++++++++
 src/dialogs/TextEntry.vala            |   68 +++++++++++++
 src/meson.build                       |    2 +
 ui/shotwell.ui                        |   36 -------
 ui/textentrydialog.ui                 |  104 ++++++++++++++++++++
 7 files changed, 272 insertions(+), 202 deletions(-)
---
diff --git a/org.gnome.Shotwell.gresource.xml b/org.gnome.Shotwell.gresource.xml
index d68f8e6..3d432fc 100644
--- a/org.gnome.Shotwell.gresource.xml
+++ b/org.gnome.Shotwell.gresource.xml
@@ -26,6 +26,7 @@
       <file preprocess="xml-stripblanks">ui/slideshow_settings.ui</file>
       <file preprocess="xml-stripblanks">ui/tag_sidebar_context.ui</file>
       <file preprocess="xml-stripblanks">ui/tags.ui</file>
+      <file preprocess="xml-stripblanks">ui/textentrydialog.ui</file>
       <file preprocess="xml-stripblanks">ui/trash.ui</file>
       <!-- Icons -->
       <file>icons/about-aachen.jpg</file>
diff --git a/src/Dialogs.vala b/src/Dialogs.vala
index 21954e2..80c4fc0 100644
--- a/src/Dialogs.vala
+++ b/src/Dialogs.vala
@@ -939,10 +939,7 @@ public abstract class TextEntryDialogMediator {
     
     public TextEntryDialogMediator(string title, string label, string? initial_text = null,
         Gee.Collection<string>? completion_list = null, string? completion_delimiter = null) {
-        Gtk.Builder builder = AppWindow.create_builder();
         dialog = new TextEntryDialog();
-        dialog.get_content_area().add((Gtk.Box) builder.get_object("dialog-vbox2"));
-        dialog.set_builder(builder);
         dialog.setup(on_modify_validate, title, label, initial_text, completion_list, completion_delimiter);
     }
     
@@ -989,170 +986,7 @@ public string build_alert_body_text(string? primary_text, string? secondary_text
         guarded_markup_escape_text(primary_text), secondary_text);
 }
 
-// Entry completion for values separated by separators (e.g. comma in the case of tags)
-// Partly inspired by the class of the same name in gtkmm-utils by Marko Anastasov
-public class EntryMultiCompletion : Gtk.EntryCompletion {
-    private string delimiter;
-    
-    public EntryMultiCompletion(Gee.Collection<string> completion_list, string? delimiter) {
-        assert(delimiter == null || delimiter.length == 1);
-        this.delimiter = delimiter;
-        
-        set_model(create_completion_store(completion_list));
-        set_text_column(0);
-        set_match_func(match_func);
-    }
-    
-    private static Gtk.ListStore create_completion_store(Gee.Collection<string> completion_list) {
-        Gtk.ListStore completion_store = new Gtk.ListStore(1, typeof(string));
-        Gtk.TreeIter store_iter;
-        Gee.Iterator<string> completion_iter = completion_list.iterator();
-        while (completion_iter.next()) {
-            completion_store.append(out store_iter);
-            completion_store.set(store_iter, 0, completion_iter.get(), -1);
-        }
-        
-        return completion_store;
-    }
-    
-    private bool match_func(Gtk.EntryCompletion completion, string key, Gtk.TreeIter iter) {
-        Gtk.TreeModel model = completion.get_model();
-        string possible_match;
-        model.get(iter, 0, out possible_match);
-        
-        // Normalize key and possible matches to allow comparison of non-ASCII characters.
-        // Use a "COMPOSE" normalization to allow comparison to the position value returned by 
-        // Gtk.Entry, i.e. one character=one position. Using the default normalization a character
-        // like "é" or "ö" would have a length of two.
-        possible_match = possible_match.casefold().normalize(-1, NormalizeMode.ALL_COMPOSE);
-        string normed_key = key.normalize(-1, NormalizeMode.ALL_COMPOSE);
-        
-        if (delimiter == null) {
-            return possible_match.has_prefix(normed_key.strip());
-        } else {
-            if (normed_key.contains(delimiter)) {
-                // check whether cursor is before last delimiter
-                int offset = normed_key.char_count(normed_key.last_index_of_char(delimiter[0]));
-                int position = ((Gtk.Entry) get_entry()).get_position();
-                if (position <= offset)
-                    return false; // TODO: Autocompletion for tags not last in list
-            }
-            
-            string last_part = get_last_part(normed_key.strip(), delimiter);
-            
-            if (last_part.length == 0) 
-                return false; // need at least one character to show matches
-                
-            return possible_match.has_prefix(last_part.strip());
-        }
-    }
 
-    public override bool match_selected(Gtk.TreeModel model, Gtk.TreeIter iter) {
-        string match;
-        model.get(iter, 0, out match);
-        
-        Gtk.Entry entry = (Gtk.Entry)get_entry();
-        
-        string old_text = entry.get_text().normalize(-1, NormalizeMode.ALL_COMPOSE);
-        if (old_text.length > 0) {
-            if (old_text.contains(delimiter)) {
-                old_text = old_text.substring(0, old_text.last_index_of_char(delimiter[0]) + 1) + (delimiter 
!= " " ? " " : "");
-            } else
-                old_text = "";
-        }
-        
-        string new_text = old_text + match + delimiter + (delimiter != " " ? " " : "");
-        entry.set_text(new_text);
-        entry.set_position((int) new_text.length);
-        
-        return true;
-    }
-    
-    // Find last string after any delimiter
-    private static string get_last_part(string s, string delimiter) {
-        string[] split = s.split(delimiter);
-        
-        if((split != null) && (split[0] != null)) {
-            return split[split.length - 1];
-        } else {
-            return "";
-        }
-    }
-}
-
-
-
-
-
-public class TextEntryDialog : Gtk.Dialog {
-    public delegate bool OnModifyValidateType(string text);
-    
-    private unowned OnModifyValidateType on_modify_validate;
-    private Gtk.Entry entry;
-    private Gtk.Builder builder;
-    private Gtk.Button button1;
-    private Gtk.Button button2;
-    
-    public TextEntryDialog() {
-        bool use_header;
-        Gtk.Settings.get_default ().get ("gtk-dialogs-use-header", out use_header);
-        Object (use_header_bar: use_header ? 1 : 0);
-    }
-    
-    public void set_builder(Gtk.Builder builder) {
-        this.builder = builder;
-    }
-    
-    public void setup(OnModifyValidateType? modify_validate, string title, string label, 
-        string? initial_text, Gee.Collection<string>? completion_list, string? completion_delimiter) {
-        set_title(title);
-        set_resizable(true);
-        set_parent_window(AppWindow.get_instance().get_parent_window());
-        set_transient_for(AppWindow.get_instance());
-        on_modify_validate = modify_validate;
-
-        Gtk.Label name_label = builder.get_object("label") as Gtk.Label;
-        name_label.set_text(label);
-
-        entry = builder.get_object("entry") as Gtk.Entry;
-        entry.set_text(initial_text != null ? initial_text : "");
-        entry.grab_focus();
-        entry.changed.connect(on_entry_changed);
-        
-        button1 = (Gtk.Button) add_button(Resources.CANCEL_LABEL, Gtk.ResponseType.CANCEL);
-        button2 = (Gtk.Button) add_button(Resources.SAVE_LABEL, Gtk.ResponseType.OK);
-        set_default_response(Gtk.ResponseType.OK);
-        
-        if (completion_list != null) { // Textfield with autocompletion
-            EntryMultiCompletion completion = new EntryMultiCompletion(completion_list,
-                completion_delimiter);
-            entry.set_completion(completion);
-        }
-        
-        set_default_response(Gtk.ResponseType.OK);
-    }
-
-    public string? execute() {
-        string? text = null;
-        
-        // validate entry to start with
-        set_response_sensitive(Gtk.ResponseType.OK, on_modify_validate(entry.get_text()));
-        
-        show_all();
-        
-        if (run() == Gtk.ResponseType.OK)
-            text = entry.get_text();
-        
-        entry.changed.disconnect(on_entry_changed);
-        destroy();
-        
-        return text;
-    }
-    
-    public void on_entry_changed() {
-        set_response_sensitive(Gtk.ResponseType.OK, on_modify_validate(entry.get_text()));
-    }
-}
 
 public class MultiTextEntryDialog : Gtk.Dialog {
     public delegate bool OnModifyValidateType(string text);
diff --git a/src/dialogs/EntryMultiCompletion.vala b/src/dialogs/EntryMultiCompletion.vala
new file mode 100644
index 0000000..8700f21
--- /dev/null
+++ b/src/dialogs/EntryMultiCompletion.vala
@@ -0,0 +1,97 @@
+/* Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2017 Jens Georg <mail jensge org>
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+// Entry completion for values separated by separators (e.g. comma in the case of tags)
+// Partly inspired by the class of the same name in gtkmm-utils by Marko Anastasov
+public class EntryMultiCompletion : Gtk.EntryCompletion {
+    private string delimiter;
+
+    public EntryMultiCompletion(Gee.Collection<string> completion_list, string? delimiter) {
+        assert(delimiter == null || delimiter.length == 1);
+        this.delimiter = delimiter;
+
+        set_model(create_completion_store(completion_list));
+        set_text_column(0);
+        set_match_func(match_func);
+    }
+
+    private static Gtk.ListStore create_completion_store(Gee.Collection<string> completion_list) {
+        Gtk.ListStore completion_store = new Gtk.ListStore(1, typeof(string));
+        Gtk.TreeIter store_iter;
+        Gee.Iterator<string> completion_iter = completion_list.iterator();
+        while (completion_iter.next()) {
+            completion_store.append(out store_iter);
+            completion_store.set(store_iter, 0, completion_iter.get(), -1);
+        }
+
+        return completion_store;
+    }
+
+    private bool match_func(Gtk.EntryCompletion completion, string key, Gtk.TreeIter iter) {
+        Gtk.TreeModel model = completion.get_model();
+        string possible_match;
+        model.get(iter, 0, out possible_match);
+
+        // Normalize key and possible matches to allow comparison of non-ASCII characters.
+        // Use a "COMPOSE" normalization to allow comparison to the position value returned by
+        // Gtk.Entry, i.e. one character=one position. Using the default normalization a character
+        // like "é" or "ö" would have a length of two.
+        possible_match = possible_match.casefold().normalize(-1, NormalizeMode.ALL_COMPOSE);
+        string normed_key = key.normalize(-1, NormalizeMode.ALL_COMPOSE);
+
+        if (delimiter == null) {
+            return possible_match.has_prefix(normed_key.strip());
+        } else {
+            if (normed_key.contains(delimiter)) {
+                // check whether cursor is before last delimiter
+                int offset = normed_key.char_count(normed_key.last_index_of_char(delimiter[0]));
+                int position = ((Gtk.Entry) get_entry()).get_position();
+                if (position <= offset)
+                    return false; // TODO: Autocompletion for tags not last in list
+            }
+
+            string last_part = get_last_part(normed_key.strip(), delimiter);
+
+            if (last_part.length == 0)
+                return false; // need at least one character to show matches
+
+            return possible_match.has_prefix(last_part.strip());
+        }
+    }
+
+    public override bool match_selected(Gtk.TreeModel model, Gtk.TreeIter iter) {
+        string match;
+        model.get(iter, 0, out match);
+
+        Gtk.Entry entry = (Gtk.Entry)get_entry();
+
+        string old_text = entry.get_text().normalize(-1, NormalizeMode.ALL_COMPOSE);
+        if (old_text.length > 0) {
+            if (old_text.contains(delimiter)) {
+                old_text = old_text.substring(0, old_text.last_index_of_char(delimiter[0]) + 1) + (delimiter 
!= " " ? " " : "");
+            } else
+                old_text = "";
+        }
+
+        string new_text = old_text + match + delimiter + (delimiter != " " ? " " : "");
+        entry.set_text(new_text);
+        entry.set_position((int) new_text.length);
+
+        return true;
+    }
+
+    // Find last string after any delimiter
+    private static string get_last_part(string s, string delimiter) {
+        string[] split = s.split(delimiter);
+
+        if((split != null) && (split[0] != null)) {
+            return split[split.length - 1];
+        } else {
+            return "";
+        }
+    }
+}
diff --git a/src/dialogs/TextEntry.vala b/src/dialogs/TextEntry.vala
new file mode 100644
index 0000000..a133b66
--- /dev/null
+++ b/src/dialogs/TextEntry.vala
@@ -0,0 +1,68 @@
+/* Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2017 Jens Georg <mail jensge org>
+ *
+ * This software is licensed under the GNU LGPL (version 2.1 or later).
+ * See the COPYING file in this distribution.
+ */
+
+[GtkTemplate (ui = "/org/gnome/Shotwell/ui/textentrydialog.ui")]
+public class TextEntryDialog : Gtk.Dialog {
+    public delegate bool OnModifyValidateType(string text);
+
+    private unowned OnModifyValidateType on_modify_validate;
+
+    [GtkChild]
+    private Gtk.Entry entry;
+
+    [GtkChild]
+    private Gtk.Label label;
+
+    public TextEntryDialog() {
+        bool use_header;
+        Gtk.Settings.get_default ().get ("gtk-dialogs-use-header", out use_header);
+        Object (use_header_bar: use_header ? 1 : 0);
+    }
+
+    public void setup(OnModifyValidateType? modify_validate, string title, string label,
+        string? initial_text, Gee.Collection<string>? completion_list, string? completion_delimiter) {
+        set_title(title);
+        set_parent_window(AppWindow.get_instance().get_parent_window());
+        set_transient_for(AppWindow.get_instance());
+        on_modify_validate = modify_validate;
+
+        this.label.set_text(label);
+
+        entry.set_text(initial_text != null ? initial_text : "");
+        entry.grab_focus();
+        entry.changed.connect(on_entry_changed);
+
+        if (completion_list != null) { // Textfield with autocompletion
+            EntryMultiCompletion completion = new EntryMultiCompletion(completion_list,
+                completion_delimiter);
+            entry.set_completion(completion);
+        }
+
+        set_default_response(Gtk.ResponseType.OK);
+    }
+
+    public string? execute() {
+        string? text = null;
+
+        // validate entry to start with
+        set_response_sensitive(Gtk.ResponseType.OK, on_modify_validate(entry.get_text()));
+
+        show_all();
+
+        if (run() == Gtk.ResponseType.OK)
+            text = entry.get_text();
+
+        entry.changed.disconnect(on_entry_changed);
+        destroy();
+
+        return text;
+    }
+
+    public void on_entry_changed() {
+        set_response_sensitive(Gtk.ResponseType.OK, on_modify_validate(entry.get_text()));
+    }
+}
diff --git a/src/meson.build b/src/meson.build
index 6911cd5..d539586 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -192,8 +192,10 @@ executable('shotwell',
             'MediaViewTracker.vala',
             'UnityProgressBar.vala',
             'Upgrades.vala',
+            'dialogs/EntryMultiCompletion.vala',
             'dialogs/SetBackgroundSlideshow.vala',
             'dialogs/SetBackground.vala',
+            'dialogs/TextEntry.vala',
             '.unitize/_UnitInternals.vala',
             '.unitize/_UtilInternals.vala',
             '.unitize/_ThreadsInternals.vala',
diff --git a/ui/shotwell.ui b/ui/shotwell.ui
index 7eb7efe..332f3db 100644
--- a/ui/shotwell.ui
+++ b/ui/shotwell.ui
@@ -170,34 +170,6 @@
       </packing>
     </child>
   </object>
-  <object class="GtkBox" id="dialog-vbox2">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="border_width">3</property>
-    <property name="orientation">vertical</property>
-    <property name="spacing">3</property>
-    <child>
-      <object class="GtkLabel" id="label">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <property name="halign">start</property>
-        <property name="label" translatable="yes">label</property>
-      </object>
-      <packing>
-        <property name="expand">False</property>
-        <property name="fill">True</property>
-        <property name="position">0</property>
-      </packing>
-    </child>
-    <child>
-      <object class="GtkEntry" id="entry">
-        <property name="visible">True</property>
-        <property name="can_focus">True</property>
-        <property name="invisible_char">●</property>
-        <property name="activates_default">True</property>
-      </object>
-      <packing>
-        <property name="expand">False</property>
         <property name="fill">True</property>
         <property name="position">1</property>
       </packing>
@@ -231,14 +203,6 @@
       </object>
       <packing>
         <property name="expand">True</property>
-        <property name="fill">True</property>
-        <property name="position">1</property>
-      </packing>
-    </child>
-    <child>
-      <placeholder/>
-    </child>
-  </object>
   <object class="GtkBox" id="plugin-manifest">
     <property name="visible">True</property>
     <property name="can_focus">False</property>
diff --git a/ui/textentrydialog.ui b/ui/textentrydialog.ui
new file mode 100644
index 0000000..6a2dc78
--- /dev/null
+++ b/ui/textentrydialog.ui
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.0 -->
+<interface domain="shotwell">
+  <requires lib="gtk+" version="3.18"/>
+  <template class="TextEntryDialog" parent="GtkDialog">
+    <property name="can_focus">False</property>
+    <property name="type_hint">dialog</property>
+    <child internal-child="vbox">
+      <object class="GtkBox">
+        <property name="can_focus">False</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">2</property>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox">
+            <property name="can_focus">False</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="button1">
+                <property name="label" translatable="yes">_Cancel</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="button2">
+                <property name="label" translatable="yes">_Save</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="has_default">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox" id="dialog-vbox2">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <property name="border_width">3</property>
+            <property name="orientation">vertical</property>
+            <property name="spacing">3</property>
+            <child>
+              <object class="GtkLabel" id="label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="halign">start</property>
+                <property name="label" translatable="yes">label</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="invisible_char">●</property>
+                <property name="activates_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+            <child>
+              <placeholder/>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">button1</action-widget>
+      <action-widget response="-5">button2</action-widget>
+    </action-widgets>
+  </template>
+</interface>


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]