Commit 70215a71 authored by Jens Georg's avatar Jens Georg

Extract TextEntry dialog

parent 3b177a6a
......@@ -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>
......
......@@ -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);
......
/* 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 "";
}
}
}
/* 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()));
}
}
......@@ -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',
......
......@@ -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>
......
<?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>
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