1 /*
2 Kickshaw - A Menu Editor for Openbox
3
4 Copyright (c) 2010–2018 Marcus Schätzle
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License along
17 with Kickshaw. If not, see http://www.gnu.org/licenses/.
18 */
19
20 #include <gtk/gtk.h>
21
22 #include "definitions_and_enumerations.h"
23 #include "find.h"
24
25 void show_or_hide_find_grid (void);
26 void find_buttons_management (gchar *column_check_button_clicked);
27 static inline void clear_list_of_rows_with_found_occurrences (void);
28 static gboolean add_occurrence_to_list (G_GNUC_UNUSED GtkTreeModel *local_model,
29 GtkTreePath *local_path,
30 GtkTreeIter *local_iter);
31 GList *create_list_of_rows_with_found_occurrences (void);
32 gboolean check_for_match (GtkTreeIter *local_iter, guint8 column_number);
33 static gboolean ensure_visibility_of_match (G_GNUC_UNUSED GtkTreeModel *foreach_or_local_model,
34 GtkTreePath *foreach_or_local_path,
35 GtkTreeIter *foreach_or_local_iter);
36 void run_search (void);
37 void jump_to_previous_or_next_occurrence (gpointer direction_pointer);
38
39 /*
40
41 Shows or hides (and resets the settings of the elements inside) the find grid.
42
43 */
44
show_or_hide_find_grid(void)45 void show_or_hide_find_grid (void)
46 {
47 if (gtk_widget_get_visible (ks.find_grid)) {
48 guint8 columns_cnt;
49
50 gtk_widget_hide (ks.find_grid);
51 g_string_assign (ks.search_term, "");
52 clear_list_of_rows_with_found_occurrences ();
53 gtk_entry_set_text (GTK_ENTRY (ks.find_entry), "");
54 gtk_style_context_remove_class (gtk_widget_get_style_context(ks.find_entry), "mandatory_missing");
55 for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; columns_cnt++) {
56 gtk_style_context_remove_class (gtk_widget_get_style_context(ks.find_in_columns[columns_cnt]),
57 "mandatory_missing");
58 }
59 gtk_style_context_remove_class (gtk_widget_get_style_context(ks.find_in_all_columns), "mandatory_missing");
60 gtk_widget_set_sensitive (ks.find_entry_buttons[BACK], FALSE);
61 gtk_widget_set_sensitive (ks.find_entry_buttons[FORWARD], FALSE);
62 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.find_in_all_columns), TRUE);
63 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.find_match_case), FALSE);
64 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.find_regular_expression), TRUE);
65 gtk_widget_queue_draw (GTK_WIDGET (ks.treeview)); // Force redrawing of treeview.
66 }
67 else {
68 gtk_widget_show (ks.find_grid);
69 gtk_widget_grab_focus (ks.find_entry);
70 }
71 }
72
73 /*
74
75 (De)activates all other column check buttons if "All columns" is (un)selected.
76 Search results are updated for any change of the chosen columns and criteria ("match case" and "regular expression").
77
78 */
79
find_buttons_management(gchar * column_check_button_clicked)80 void find_buttons_management (gchar *column_check_button_clicked)
81 {
82 // TRUE if any find_in_columns check button or find_in_all_columns check button clicked.
83 if (column_check_button_clicked) {
84 gboolean marking_active;
85 gboolean all_columns_check_button_clicked = STREQ (column_check_button_clicked, "all");
86
87 marking_active = gtk_style_context_has_class (gtk_widget_get_style_context (ks.find_in_all_columns),
88 "mandatory_missing");
89
90 if (marking_active || all_columns_check_button_clicked) {
91 gboolean find_in_all_activated = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_in_all_columns));
92
93 guint8 columns_cnt;
94
95 for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; columns_cnt++) {
96 if (all_columns_check_button_clicked) {
97 g_signal_handler_block (ks.find_in_columns[columns_cnt], ks.handler_id_find_in_columns[columns_cnt]);
98 gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ks.find_in_columns[columns_cnt]), find_in_all_activated);
99 g_signal_handler_unblock (ks.find_in_columns[columns_cnt], ks.handler_id_find_in_columns[columns_cnt]);
100 gtk_widget_set_sensitive (ks.find_in_columns[columns_cnt], !find_in_all_activated);
101 }
102 if (marking_active) {
103 gtk_style_context_remove_class (gtk_widget_get_style_context(ks.find_in_columns[columns_cnt]),
104 "mandatory_missing");
105 }
106 }
107 if (marking_active) {
108 gtk_style_context_remove_class (gtk_widget_get_style_context(ks.find_in_all_columns), "mandatory_missing");
109 }
110 }
111 }
112
113 if (*ks.search_term->str) {
114 if (create_list_of_rows_with_found_occurrences ()) {
115 gtk_tree_model_foreach (ks.model, (GtkTreeModelForeachFunc) ensure_visibility_of_match, NULL);
116 }
117 row_selected (); // Reset status of forward and back buttons.
118 }
119
120 gtk_widget_queue_draw (GTK_WIDGET (ks.treeview)); // Force redrawing of treeview (for highlighting of search results).
121 }
122
123 /*
124
125 Clears the list of rows so it can be rebuild later.
126
127 */
128
clear_list_of_rows_with_found_occurrences(void)129 static inline void clear_list_of_rows_with_found_occurrences (void) {
130 g_list_free_full (ks.rows_with_found_occurrences, (GDestroyNotify) gtk_tree_path_free);
131 ks.rows_with_found_occurrences = NULL;
132 }
133
134 /*
135
136 Adds a row that contains a column matching the search term to a list.
137
138 */
139
add_occurrence_to_list(G_GNUC_UNUSED GtkTreeModel * local_model,GtkTreePath * local_path,GtkTreeIter * local_iter)140 static gboolean add_occurrence_to_list (G_GNUC_UNUSED GtkTreeModel *local_model,
141 GtkTreePath *local_path,
142 GtkTreeIter *local_iter)
143 {
144 guint8 columns_cnt;
145
146 for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; columns_cnt++) {
147 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_in_columns[columns_cnt])) &&
148 check_for_match (local_iter, columns_cnt)) {
149 // Row references are not used here, since the list is recreated everytime the treestore is changed.
150 ks.rows_with_found_occurrences = g_list_prepend (ks.rows_with_found_occurrences, gtk_tree_path_copy (local_path));
151 break;
152 }
153 }
154
155 return FALSE;
156 }
157
158 /*
159
160 Creates a list of all rows that contain at least one cell with the search term.
161
162 rows_with_found_occurrences is a global GList, the purpose of returning the pointer is that the function to
163 which is returned to can immediately check whether a list has been created or not, in the latter case the
164 return value is NULL.
165
166 */
167
create_list_of_rows_with_found_occurrences(void)168 GList *create_list_of_rows_with_found_occurrences (void)
169 {
170 clear_list_of_rows_with_found_occurrences ();
171 gtk_tree_model_foreach (ks.model, (GtkTreeModelForeachFunc) add_occurrence_to_list, NULL);
172 return ks.rows_with_found_occurrences = g_list_reverse (ks.rows_with_found_occurrences);
173 }
174
175 /*
176
177 Checks for each column if it contains the search term.
178
179 */
180
check_for_match(GtkTreeIter * local_iter,guint8 column_number)181 gboolean check_for_match (GtkTreeIter *local_iter,
182 guint8 column_number)
183 {
184 gboolean match_found = FALSE; // Default
185 gchar *current_column;
186
187 gtk_tree_model_get (ks.model, local_iter, column_number + TREEVIEW_COLUMN_OFFSET, ¤t_column, -1);
188 if (current_column) {
189 gchar *search_term_str_escaped = (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_regular_expression))) ?
190 NULL : g_regex_escape_string (ks.search_term->str, -1);
191
192 if (g_regex_match_simple ((search_term_str_escaped) ? search_term_str_escaped : ks.search_term->str,
193 current_column,
194 (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_match_case))) ?
195 0 : G_REGEX_CASELESS,
196 G_REGEX_MATCH_NOTEMPTY)) {
197 match_found = TRUE;
198 }
199
200 // Cleanup
201 g_free (current_column);
202 g_free (search_term_str_escaped);
203 }
204
205 return match_found;
206 }
207
208 /*
209
210 If the search term is contained inside...
211 ...a row whose parent is not expanded expand the latter.
212 ...a column which is hidden show this column.
213
214 */
215
ensure_visibility_of_match(G_GNUC_UNUSED GtkTreeModel * foreach_or_local_model,GtkTreePath * foreach_or_local_path,GtkTreeIter * foreach_or_local_iter)216 static gboolean ensure_visibility_of_match (G_GNUC_UNUSED GtkTreeModel *foreach_or_local_model,
217 GtkTreePath *foreach_or_local_path,
218 GtkTreeIter *foreach_or_local_iter)
219 {
220 guint8 columns_cnt;
221
222 for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; columns_cnt++) {
223 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_in_columns[columns_cnt])) &&
224 check_for_match (foreach_or_local_iter, columns_cnt)) {
225 if (gtk_tree_path_get_depth (foreach_or_local_path) > 1 &&
226 !gtk_tree_view_row_expanded (GTK_TREE_VIEW (ks.treeview), foreach_or_local_path)) {
227 gtk_tree_view_expand_to_path (GTK_TREE_VIEW (ks.treeview), foreach_or_local_path);
228 gtk_tree_view_collapse_row (GTK_TREE_VIEW (ks.treeview), foreach_or_local_path);
229 }
230 if (!gtk_tree_view_column_get_visible (ks.columns[columns_cnt])) {
231 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (ks.mb_view_and_options[(columns_cnt == COL_MENU_ID) ?
232 SHOW_MENU_ID_COL :
233 SHOW_EXECUTE_COL]),
234 TRUE);
235 }
236 }
237 }
238
239 return FALSE;
240 }
241
242 /*
243
244 Runs a search on the entered search term.
245
246 */
247
run_search(void)248 void run_search (void)
249 {
250 // If regular expressions are activated, check if the regular expression is valid.
251 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_regular_expression))) {
252 GError *error = NULL;
253 GRegex *regex = g_regex_new (gtk_entry_get_text (GTK_ENTRY (ks.find_entry)), 0, 0, &error);
254
255 if (error) {
256 show_errmsg (error->message);
257 // Cleanup
258 g_error_free (error);
259
260 return;
261 }
262
263 g_regex_unref (regex);
264 }
265
266 g_string_assign (ks.search_term, gtk_entry_get_text (GTK_ENTRY (ks.find_entry)));
267
268 gboolean no_find_in_columns_buttons_clicked = TRUE; // Default
269
270 guint8 columns_cnt;
271
272 if (*ks.search_term->str) {
273 gtk_style_context_remove_class (gtk_widget_get_style_context (ks.find_entry), "mandatory_missing");
274 }
275 else {
276 wrong_or_missing (ks.find_entry, ks.find_entry_css_provider);
277 }
278
279 for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; columns_cnt++) {
280 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (ks.find_in_columns[columns_cnt]))) {
281 no_find_in_columns_buttons_clicked = FALSE;
282 break;
283 }
284 }
285
286 if (!(*ks.search_term->str) || no_find_in_columns_buttons_clicked) {
287 if (no_find_in_columns_buttons_clicked) {
288 for (columns_cnt = 0; columns_cnt < COL_ELEMENT_VISIBILITY; columns_cnt++) {
289 wrong_or_missing (ks.find_in_columns[columns_cnt], ks.find_in_columns_css_providers[columns_cnt]);
290 }
291 wrong_or_missing (ks.find_in_all_columns, ks.find_in_all_columns_css_provider);
292 }
293 clear_list_of_rows_with_found_occurrences ();
294 }
295 else if (create_list_of_rows_with_found_occurrences ()) {
296 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
297
298 gtk_tree_model_foreach (ks.model, (GtkTreeModelForeachFunc) ensure_visibility_of_match, NULL);
299 gtk_tree_selection_unselect_all (selection);
300 gtk_tree_selection_select_path (selection, ks.rows_with_found_occurrences->data);
301 /*
302 There is no horizontically movement to a specific GtkTreeViewColumn; this is indicated by NULL.
303 Alignment arguments (row_align and col_align) aren't used; this is indicated by FALSE.
304 */
305 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (ks.treeview), ks.rows_with_found_occurrences->data, NULL, FALSE, 0, 0);
306 }
307
308 row_selected (); // Reset status of forward and back buttons.
309
310 gtk_widget_queue_draw (GTK_WIDGET (ks.treeview)); // Force redrawing of treeview (for highlighting of search results).
311 }
312
313 /*
314
315 Enables the possibility to move between the found occurrences.
316
317 */
318
jump_to_previous_or_next_occurrence(gpointer direction_pointer)319 void jump_to_previous_or_next_occurrence (gpointer direction_pointer)
320 {
321 gboolean forward = GPOINTER_TO_INT (direction_pointer);
322
323 GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ks.treeview));
324 GtkTreePath *path = gtk_tree_model_get_path (ks.model, &ks.iter), *path_of_occurrence;
325 GtkTreeIter iter_of_occurrence;
326
327 GList *rows_with_found_occurrences_loop;
328
329 for (rows_with_found_occurrences_loop = (forward) ?
330 ks.rows_with_found_occurrences : g_list_last (ks.rows_with_found_occurrences);
331 rows_with_found_occurrences_loop;
332 rows_with_found_occurrences_loop = (forward) ? rows_with_found_occurrences_loop->next :
333 rows_with_found_occurrences_loop->prev) {
334 if ((forward) ? (gtk_tree_path_compare (path, rows_with_found_occurrences_loop->data) < 0) :
335 (gtk_tree_path_compare (path, rows_with_found_occurrences_loop->data) > 0)) {
336 break;
337 }
338 }
339
340 path_of_occurrence = rows_with_found_occurrences_loop->data;
341 gtk_tree_model_get_iter (ks.model, &iter_of_occurrence, path_of_occurrence);
342
343 // Ensure_visibility_of_match is not called by gtk_tree_model_foreach; model is unused so this argument is set to NULL.
344 ensure_visibility_of_match (NULL, path_of_occurrence, &iter_of_occurrence);
345
346 gtk_tree_selection_unselect_all (selection);
347 gtk_tree_selection_select_path (selection, path_of_occurrence);
348 /*
349 There is no horizontically movement to a specific GtkTreeViewColumn; this is indicated by NULL.
350 Alignment arguments (row_align and col_align) aren't used; this is indicated by FALSE.
351 */
352 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (ks.treeview), path_of_occurrence, NULL, FALSE, 0, 0);
353
354 // Cleanup
355 gtk_tree_path_free (path);
356 }
357