-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathCommandList.vala
233 lines (182 loc) · 7.26 KB
/
CommandList.vala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
/*
* Plotinus - A searchable command palette in every modern GTK+ application
*
* Copyright (c) 2016-2017 Philipp Emanuel Weidmann <pew@worldwidemann.com>
*
* Nemo vir est qui mundum non reddat meliorem.
*
* Released under the terms of the GNU General Public License, version 3
* (https://gnu.org/licenses/gpl.html)
*/
using Plotinus.Utilities;
class Plotinus.CommandList : Gtk.TreeView {
private class ListColumn : Gtk.TreeViewColumn {
public delegate string MarkupFunction(Command command);
public ListColumn(MarkupFunction markup_function, bool align_right, Gdk.RGBA? text_color, double font_scale = 1) {
var cell_renderer = new Gtk.CellRendererText();
if (align_right)
cell_renderer.xalign = 1;
if (text_color != null)
cell_renderer.foreground_rgba = text_color;
cell_renderer.scale = font_scale;
pack_start(cell_renderer, true);
set_cell_data_func(cell_renderer, (cell_layout, cell, tree_model, tree_iter) => {
(cell as Gtk.CellRendererText).markup = markup_function(get_iter_command(tree_model, tree_iter));
});
}
}
private class IconColumn : Gtk.TreeViewColumn {
public IconColumn() {
var toggle_renderer = new Gtk.CellRendererToggle();
var pixbuf_renderer = new Gtk.CellRendererPixbuf();
pack_start(toggle_renderer, false);
pack_start(pixbuf_renderer, false);
set_cell_data_func(toggle_renderer, (cell_layout, cell, tree_model, tree_iter) => {
var toggle = (cell as Gtk.CellRendererToggle);
var command = get_iter_command(tree_model, tree_iter);
toggle.visible = command.get_check_type() != Gtk.ButtonRole.NORMAL;
toggle.radio = command.get_check_type() == Gtk.ButtonRole.RADIO;
toggle.active = command.is_active();
});
set_cell_data_func(pixbuf_renderer, (cell_layout, cell, tree_model, tree_iter) => {
var pixbuf = (cell as Gtk.CellRendererPixbuf);
var command = get_iter_command(tree_model, tree_iter);
pixbuf.visible = command.set_image(pixbuf);
});
}
}
private const string COLUMN_PADDING = " ";
private string filter = "";
private string[] filter_words = {};
private Gtk.TreeModelFilter tree_model_filter;
private Gtk.TreeModelSort tree_model_sort;
public CommandList(Command[] commands) {
var list_store = new Gtk.ListStore(1, typeof(Command));
foreach (var command in commands) {
Gtk.TreeIter tree_iter;
list_store.append(out tree_iter);
list_store.set_value(tree_iter, 0, command);
}
tree_model_filter = new Gtk.TreeModelFilter(list_store, null);
tree_model_filter.set_visible_func((tree_model, tree_iter) => {
if (filter == "")
return true;
return get_command_score(get_iter_command(tree_model, tree_iter)) >= 0;
});
tree_model_sort = new Gtk.TreeModelSort.with_model(tree_model_filter);
model = tree_model_sort;
headers_visible = false;
// The theme's style context is reliably available only after the widget has been realized
realize.connect(() => {
var style_context = get_style_context();
var text_color = style_context.get_color(Gtk.StateFlags.NORMAL);
var selection_color = style_context.get_background_color(Gtk.StateFlags.SELECTED | Gtk.StateFlags.FOCUSED);
text_color.alpha = 0.4;
append_column(new ListColumn((command) => {
return COLUMN_PADDING +
highlight_words(string.joinv(" \u25B6 ", command.path), filter_words);
}, true, text_color));
append_column(new IconColumn());
append_column(new ListColumn((command) => {
return highlight_words(command.label, filter_words);
}, false, null, 1.4));
append_column(new ListColumn((command) => {
return Markup.escape_text(string.joinv(", ", map_string(command.accelerators, format_accelerator))) +
COLUMN_PADDING;
}, true, selection_color));
});
}
public void set_filter(string filter) {
// Preprocess filter string to simplify search
this.filter = /\s{2,}/.replace(filter, -1, 0, " ").strip().casefold();
filter_words = this.filter.split(" ");
tree_model_filter.refilter();
// TreeModelSort has no "resort" method, but reassigning the comparison function forces a resort
tree_model_sort.set_default_sort_func((tree_model, tree_iter_a, tree_iter_b) => {
var command_a = get_iter_command(tree_model, tree_iter_a);
var command_b = get_iter_command(tree_model, tree_iter_b);
// "The sort function used by TreeModelSort is not guaranteed to be stable" (GTK+ documentation),
// so the original order of commands is needed as a tie-breaker
var id_difference = command_a.id - command_b.id;
if (this.filter == "")
return id_difference;
var score_difference = get_command_score(command_a) - get_command_score(command_b);
return (score_difference != 0) ? score_difference : id_difference;
});
}
// Returns a score indicating how closely the command matches the filter.
// The lower the score, the better the match. A negative score means no match.
private int get_command_score(Command command) {
var label = command.label.casefold();
var path = string.joinv(" ", command.path).casefold();
int score = 0;
if (label.has_prefix(filter))
return score;
score++;
if (label.contains(filter))
return score;
score++;
if (contains_words(label, filter_words))
return score;
score++;
if (contains_words(label, filter_words, false))
return score;
score++;
if (contains_words(path, filter_words))
return score;
score++;
if (contains_words(path, filter_words, false))
return score;
return -1;
}
public Command? get_selected_command() {
var selected_iter = get_selected_iter();
if (selected_iter != null) {
return get_iter_command(model, selected_iter);
} else {
return null;
}
}
public void select_first_item() {
Gtk.TreeIter first_iter;
if (model.get_iter_first(out first_iter)) {
get_selection().select_iter(first_iter);
scroll_to_selected_item();
}
}
public void select_previous_item() {
var selected_iter = get_selected_iter();
if (selected_iter != null && model.iter_previous(ref selected_iter)) {
get_selection().select_iter(selected_iter);
scroll_to_selected_item();
}
}
public void select_next_item() {
var selected_iter = get_selected_iter();
if (selected_iter != null && model.iter_next(ref selected_iter)) {
get_selection().select_iter(selected_iter);
scroll_to_selected_item();
}
}
private Gtk.TreeIter? get_selected_iter() {
Gtk.TreeModel tree_model;
Gtk.TreeIter selected_iter;
if (get_selection().get_selected(out tree_model, out selected_iter)) {
return selected_iter;
} else {
return null;
}
}
private static Command get_iter_command(Gtk.TreeModel tree_model, Gtk.TreeIter tree_iter) {
Value command;
tree_model.get_value(tree_iter, 0, out command);
return (Command) command;
}
private void scroll_to_selected_item() {
var selected_iter = get_selected_iter();
if (selected_iter != null) {
var selected_path = model.get_path(selected_iter);
scroll_to_cell(selected_path, null, false, 0, 0);
}
}
}