Smooth text input field with autocompleted units?
- From: Dylan McCall <dylanmccall gmail com>
- To: "gtk-app-devel-list gnome org" <gtk-app-devel-list gnome org>
- Subject: Smooth text input field with autocompleted units?
- Date: Sun, 30 Oct 2011 10:26:50 -0700
I'm working on a settings dialog for a little app and I needed a custom
field for the user to enter a time, which can be either minutes, seconds
or hours. Also, I needed decent i18n. Finally, I'm really picky and I
didn't want a combobox to choose a unit.
I came up with an approach that uses a single GtkEntry with a
GtkEntryCompletion, and here it is in Valaâ
private class TimeEntryDialog : Gtk.Dialog {
private Gtk.Widget ok_button;
private Gtk.Entry time_entry;
private Gtk.ListStore completion_store;
public signal void time_entered(int time_seconds);
public TimeEntryDialog(Gtk.Window? parent, string title) {
Object();
this.set_title(title);
this.set_modal(true);
this.set_destroy_with_parent(true);
this.set_transient_for(parent);
this.ok_button = this.add_button(Gtk.Stock.OK, Gtk.ResponseType.OK);
this.response.connect((response_id) => {
if (response_id == Gtk.ResponseType.OK) this.submit();
});
Gtk.Container content_area = (Gtk.Container)this.get_content_area();
Gtk.Grid content_grid = new Gtk.Grid();
content_grid.margin = 6;
content_grid.set_row_spacing(4);
content_area.add(content_grid);
Gtk.Label entry_label = new Gtk.Label(title);
content_grid.attach(entry_label, 0, 0, 1, 1);
this.time_entry = new Gtk.Entry();
this.time_entry.activate.connect(this.submit);
this.time_entry.changed.connect(this.time_entry_changed);
content_grid.attach(this.time_entry, 0, 1, 1, 1);
Gtk.EntryCompletion completion = new Gtk.EntryCompletion();
this.completion_store = new Gtk.ListStore(1, typeof(string));
completion.set_model(this.completion_store);
completion.set_text_column(0);
completion.set_inline_completion(true);
completion.set_popup_completion(true);
completion.set_popup_single_match(false);
this.time_entry.set_completion(completion);
content_area.show_all();
}
public void time_entry_changed() {
string text = this.time_entry.get_text();
string[] text_parts = text.split(" ");
int time = 1;
// You might notice this is totally not good i18n.
// It'll use a regex in NaturalTime soon!
if (text_parts.length > 0) {
time = int.parse(text_parts[0]);
}
if (time < 1) time = 1;
// replace completion options without deleting rows
// if we delete rows, gtk throws some unhappy error messages
Gtk.TreeIter iter;
bool iter_valid = this.completion_store.get_iter_first(out iter);
if (!iter_valid) this.completion_store.append(out iter);
string[] completions = NaturalTime.get_completions_for_time(time);
foreach (string completion in completions) {
this.completion_store.set(iter, 0, completion, -1);
iter_valid = this.completion_store.iter_next(ref iter);
if (!iter_valid) this.completion_store.append(out iter);
}
if (NaturalTime.get_seconds_for_label(text) > 0) {
this.ok_button.set_sensitive(true);
} else {
this.ok_button.set_sensitive(false);
}
}
public void submit() {
int time = NaturalTime.get_seconds_for_label(this.time_entry.get_text());
if (time > 0) {
this.time_entered(time);
this.destroy();
} else {
Gdk.beep();
}
}
}
The full source belongs to break_settings in
<https://code.launchpad.net/~dylanmccall/brainbreak/trunk>.
Now, this feels pretty smooth in practice, but I'm not thrilled with how
it works. Whenever the time field is changed, time_entry_changed
generates a list of possible completions based on the time that has been
entered (if there is one). So, in English if you enter "12 " it'll add
"12 hours," "12 seconds" and "12 minutes" to the completion's model. The
main push of the ugliness comes when we're adding those completions to
the completion_store. If I remove a row that is being used for the
inline completion, I get an unhappy-looking error in the console. So, I
have a loop that manually goes through and _updates_ each row, adding
more rows as necessary. It feels pretty ugly, and it probably wastes a
few thousand CPU cycles.
I think this would be much tidier if I could do any of the following:
* Detect whether the EntryCompletion is already showing a match
(and therefore don't add to the model).
* Call Gtk.EntryCompletion.clear() at any time without making gtk
go âGLib-CRITICAL **: g_sequence_remove: assertion `iter !=
NULL' failedâ.
* Build an EntryCompletion that matches individual words instead
of the entire thing. Close enough to what I have here, and if I
put it in those terms maybe it'll light up some distant
recollections?
* Build an EntryCompletion that uses regex (or a wildcard) for its
matching, replacing a group with the user's input. I know we
have gtk_entry_completion_set_match_func but I don't see a way
to tell EntryCompletion to use a different string in its
completion list. It will just use exactly what is in that
TreeIter we returned true for.
If anyone has an idea, please share. Thank you :)
--
Dylan McCall
dylanmccall gmail com
http://www.dylanmccall.com
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]