1 /* nautilus-search-popover.c
2  *
3  * Copyright (C) 2015 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include "nautilus-enum-types.h"
20 #include "nautilus-search-popover.h"
21 #include "nautilus-mime-actions.h"
22 
23 #include <glib/gi18n.h>
24 #include "nautilus-file.h"
25 #include "nautilus-ui-utilities.h"
26 #include "nautilus-global-preferences.h"
27 
28  #define SEARCH_FILTER_MAX_YEARS 5
29 
30 struct _NautilusSearchPopover
31 {
32     GtkPopover parent;
33 
34     GtkWidget *around_revealer;
35     GtkWidget *around_stack;
36     GtkWidget *calendar;
37     GtkWidget *clear_date_button;
38     GtkWidget *dates_listbox;
39     GtkWidget *date_entry;
40     GtkWidget *date_stack;
41     GtkWidget *select_date_button;
42     GtkWidget *select_date_button_label;
43     GtkWidget *type_label;
44     GtkWidget *type_listbox;
45     GtkWidget *type_stack;
46     GtkWidget *last_used_button;
47     GtkWidget *last_modified_button;
48     GtkWidget *full_text_search_button;
49     GtkWidget *filename_search_button;
50 
51     NautilusQuery *query;
52 
53     gboolean fts_enabled;
54 };
55 
56 static void          show_date_selection_widgets (NautilusSearchPopover *popover,
57                                                   gboolean               visible);
58 
59 static void          show_other_types_dialog (NautilusSearchPopover *popover);
60 
61 static void          update_date_label (NautilusSearchPopover *popover,
62                                         GPtrArray             *date_range);
63 
64 G_DEFINE_TYPE (NautilusSearchPopover, nautilus_search_popover, GTK_TYPE_POPOVER)
65 
66 enum
67 {
68     PROP_0,
69     PROP_QUERY,
70     PROP_FTS_ENABLED,
71     LAST_PROP
72 };
73 
74 enum
75 {
76     MIME_TYPE,
77     TIME_TYPE,
78     DATE_RANGE,
79     LAST_SIGNAL
80 };
81 
82 static guint signals[LAST_SIGNAL];
83 
84 
85 /* Callbacks */
86 
87 static void
calendar_day_selected(GtkCalendar * calendar,NautilusSearchPopover * popover)88 calendar_day_selected (GtkCalendar           *calendar,
89                        NautilusSearchPopover *popover)
90 {
91     GDateTime *date;
92     guint year, month, day;
93     GPtrArray *date_range;
94 
95     gtk_calendar_get_date (calendar, &year, &month, &day);
96 
97     date = g_date_time_new_local (year, month + 1, day, 0, 0, 0);
98 
99     date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
100     g_ptr_array_add (date_range, g_date_time_ref (date));
101     g_ptr_array_add (date_range, g_date_time_ref (date));
102     update_date_label (popover, date_range);
103     g_signal_emit_by_name (popover, "date-range", date_range);
104 
105     g_ptr_array_unref (date_range);
106     g_date_time_unref (date);
107 }
108 
109 /* Range on dates are partially implemented. For now just use it for differentation
110  * between a exact day or a range of a first day until now.
111  */
112 static void
setup_date(NautilusSearchPopover * popover,NautilusQuery * query)113 setup_date (NautilusSearchPopover *popover,
114             NautilusQuery         *query)
115 {
116     GPtrArray *date_range;
117     GDateTime *date_initial;
118 
119     date_range = nautilus_query_get_date_range (query);
120 
121     if (date_range)
122     {
123         date_initial = g_ptr_array_index (date_range, 0);
124 
125         g_signal_handlers_block_by_func (popover->calendar, calendar_day_selected, popover);
126 
127         gtk_calendar_select_month (GTK_CALENDAR (popover->calendar),
128                                    g_date_time_get_month (date_initial) - 1,
129                                    g_date_time_get_year (date_initial));
130 
131         gtk_calendar_select_day (GTK_CALENDAR (popover->calendar),
132                                  g_date_time_get_day_of_month (date_initial));
133 
134         update_date_label (popover, date_range);
135 
136         g_signal_handlers_unblock_by_func (popover->calendar, calendar_day_selected, popover);
137     }
138 }
139 
140 static void
query_date_changed(GObject * object,GParamSpec * pspec,NautilusSearchPopover * popover)141 query_date_changed (GObject               *object,
142                     GParamSpec            *pspec,
143                     NautilusSearchPopover *popover)
144 {
145     setup_date (popover, NAUTILUS_QUERY (object));
146 }
147 
148 static void
clear_date_button_clicked(GtkButton * button,NautilusSearchPopover * popover)149 clear_date_button_clicked (GtkButton             *button,
150                            NautilusSearchPopover *popover)
151 {
152     nautilus_search_popover_reset_date_range (popover);
153 }
154 
155 static void
date_entry_activate(GtkEntry * entry,NautilusSearchPopover * popover)156 date_entry_activate (GtkEntry              *entry,
157                      NautilusSearchPopover *popover)
158 {
159     if (gtk_entry_get_text_length (entry) > 0)
160     {
161         GDateTime *now;
162         GDateTime *date_time;
163         GDate *date;
164 
165         date = g_date_new ();
166         g_date_set_parse (date, gtk_entry_get_text (entry));
167 
168         /* Invalid date silently does nothing */
169         if (!g_date_valid (date))
170         {
171             g_date_free (date);
172             return;
173         }
174 
175         now = g_date_time_new_now_local ();
176         date_time = g_date_time_new_local (g_date_get_year (date),
177                                            g_date_get_month (date),
178                                            g_date_get_day (date),
179                                            0,
180                                            0,
181                                            0);
182 
183         /* Future dates also silently fails */
184         if (g_date_time_compare (date_time, now) != 1)
185         {
186             GPtrArray *date_range;
187 
188             date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
189             g_ptr_array_add (date_range, g_date_time_ref (date_time));
190             g_ptr_array_add (date_range, g_date_time_ref (date_time));
191             update_date_label (popover, date_range);
192             show_date_selection_widgets (popover, FALSE);
193             g_signal_emit_by_name (popover, "date-range", date_range);
194 
195             g_ptr_array_unref (date_range);
196         }
197 
198         g_date_time_unref (now);
199         g_date_time_unref (date_time);
200         g_date_free (date);
201     }
202 }
203 
204 static void
dates_listbox_row_activated(GtkListBox * listbox,GtkListBoxRow * row,NautilusSearchPopover * popover)205 dates_listbox_row_activated (GtkListBox            *listbox,
206                              GtkListBoxRow         *row,
207                              NautilusSearchPopover *popover)
208 {
209     GDateTime *date;
210     GDateTime *now;
211     GPtrArray *date_range = NULL;
212 
213     now = g_date_time_new_now_local ();
214     date = g_object_get_data (G_OBJECT (row), "date");
215     if (date)
216     {
217         date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
218         g_ptr_array_add (date_range, g_date_time_ref (date));
219         g_ptr_array_add (date_range, g_date_time_ref (now));
220     }
221     update_date_label (popover, date_range);
222     show_date_selection_widgets (popover, FALSE);
223     g_signal_emit_by_name (popover, "date-range", date_range);
224 
225     if (date_range)
226     {
227         g_ptr_array_unref (date_range);
228     }
229     g_date_time_unref (now);
230 }
231 
232 static void
listbox_header_func(GtkListBoxRow * row,GtkListBoxRow * before,NautilusSearchPopover * popover)233 listbox_header_func (GtkListBoxRow         *row,
234                      GtkListBoxRow         *before,
235                      NautilusSearchPopover *popover)
236 {
237     gboolean show_separator;
238 
239     show_separator = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "show-separator"));
240 
241     if (show_separator)
242     {
243         GtkWidget *separator;
244 
245         separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
246         gtk_widget_show (separator);
247 
248         gtk_list_box_row_set_header (row, separator);
249     }
250 }
251 
252 static void
select_date_button_clicked(GtkButton * button,NautilusSearchPopover * popover)253 select_date_button_clicked (GtkButton             *button,
254                             NautilusSearchPopover *popover)
255 {
256     /* Hide the type selection widgets when date selection
257      * widgets are shown.
258      */
259     gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
260 
261     show_date_selection_widgets (popover, TRUE);
262 }
263 
264 static void
select_type_button_clicked(GtkButton * button,NautilusSearchPopover * popover)265 select_type_button_clicked (GtkButton             *button,
266                             NautilusSearchPopover *popover)
267 {
268     GtkListBoxRow *selected_row;
269 
270     selected_row = gtk_list_box_get_selected_row (GTK_LIST_BOX (popover->type_listbox));
271 
272     gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-list");
273     if (selected_row != NULL)
274     {
275         gtk_widget_grab_focus (GTK_WIDGET (selected_row));
276     }
277 
278     /* Hide the date selection widgets when the type selection
279      * listbox is shown.
280      */
281     show_date_selection_widgets (popover, FALSE);
282 }
283 
284 static void
toggle_calendar_icon_clicked(GtkEntry * entry,GtkEntryIconPosition position,GdkEvent * event,NautilusSearchPopover * popover)285 toggle_calendar_icon_clicked (GtkEntry              *entry,
286                               GtkEntryIconPosition   position,
287                               GdkEvent              *event,
288                               NautilusSearchPopover *popover)
289 {
290     const gchar *current_visible_child;
291     const gchar *child;
292     const gchar *icon_name;
293     const gchar *tooltip;
294 
295     current_visible_child = gtk_stack_get_visible_child_name (GTK_STACK (popover->around_stack));
296 
297     if (g_strcmp0 (current_visible_child, "date-list") == 0)
298     {
299         child = "date-calendar";
300         icon_name = "view-list-symbolic";
301         tooltip = _("Show a list to select the date");
302     }
303     else
304     {
305         child = "date-list";
306         icon_name = "x-office-calendar-symbolic";
307         tooltip = _("Show a calendar to select the date");
308     }
309 
310     gtk_stack_set_visible_child_name (GTK_STACK (popover->around_stack), child);
311     gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY, icon_name);
312     gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY, tooltip);
313 }
314 
315 static void
types_listbox_row_activated(GtkListBox * listbox,GtkListBoxRow * row,NautilusSearchPopover * popover)316 types_listbox_row_activated (GtkListBox            *listbox,
317                              GtkListBoxRow         *row,
318                              NautilusSearchPopover *popover)
319 {
320     gint group;
321 
322     group = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "mimetype-group"));
323 
324     /* The -1 group stands for the "Other Types" group, for which
325      * we should show the mimetype dialog.
326      */
327     if (group == -1)
328     {
329         show_other_types_dialog (popover);
330     }
331     else
332     {
333         gtk_label_set_label (GTK_LABEL (popover->type_label),
334                              nautilus_mime_types_group_get_name (group));
335 
336         g_signal_emit_by_name (popover, "mime-type", group, NULL);
337     }
338 
339     gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
340 }
341 
342 static void
search_time_type_changed(GtkToggleButton * button,NautilusSearchPopover * popover)343 search_time_type_changed (GtkToggleButton       *button,
344                           NautilusSearchPopover *popover)
345 {
346     NautilusQuerySearchType type = -1;
347 
348     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->last_modified_button)))
349     {
350         type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED;
351     }
352     else
353     {
354         type = NAUTILUS_QUERY_SEARCH_TYPE_LAST_ACCESS;
355     }
356 
357     g_settings_set_enum (nautilus_preferences, "search-filter-time-type", type);
358 
359     g_signal_emit_by_name (popover, "time-type", type, NULL);
360 }
361 
362 static void
search_fts_mode_changed(GtkToggleButton * button,NautilusSearchPopover * popover)363 search_fts_mode_changed (GtkToggleButton       *button,
364                          NautilusSearchPopover *popover)
365 {
366     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->full_text_search_button)) &&
367         popover->fts_enabled == FALSE)
368     {
369         popover->fts_enabled = TRUE;
370         g_settings_set_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_FTS_ENABLED, TRUE);
371         g_object_notify (G_OBJECT (popover), "fts-enabled");
372     }
373     else if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (popover->filename_search_button)) &&
374              popover->fts_enabled == TRUE)
375     {
376         popover->fts_enabled = FALSE;
377         g_settings_set_boolean (nautilus_preferences, NAUTILUS_PREFERENCES_FTS_ENABLED, FALSE);
378         g_object_notify (G_OBJECT (popover), "fts-enabled");
379     }
380 }
381 
382 /* Auxiliary methods */
383 
384 static GtkWidget *
create_row_for_label(const gchar * text,gboolean show_separator)385 create_row_for_label (const gchar *text,
386                       gboolean     show_separator)
387 {
388     GtkWidget *row;
389     GtkWidget *label;
390 
391     row = gtk_list_box_row_new ();
392 
393     g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
394 
395     label = g_object_new (GTK_TYPE_LABEL,
396                           "label", text,
397                           "hexpand", TRUE,
398                           "xalign", 0.0,
399                           "margin-start", 6,
400                           NULL);
401 
402     gtk_container_add (GTK_CONTAINER (row), label);
403     gtk_widget_show_all (row);
404 
405     return row;
406 }
407 
408 static void
fill_fuzzy_dates_listbox(NautilusSearchPopover * popover)409 fill_fuzzy_dates_listbox (NautilusSearchPopover *popover)
410 {
411     GDateTime *maximum_dt, *now;
412     GtkWidget *row;
413     GDateTime *current_date;
414     GPtrArray *date_range;
415     gint days, max_days;
416 
417     days = 1;
418     maximum_dt = g_date_time_new_from_unix_local (0);
419     now = g_date_time_new_now_local ();
420     max_days = SEARCH_FILTER_MAX_YEARS * 365;
421 
422     /* Add the no date filter element first */
423     row = create_row_for_label (_("Any time"), TRUE);
424     gtk_container_add (GTK_CONTAINER (popover->dates_listbox), row);
425 
426     /* This is a tricky loop. The main intention here is that each
427      * timeslice (day, week, month) have 2 or 3 entries.
428      *
429      * For the first appearance of each timeslice, there is made a
430      * check in order to be sure that there is no offset added to days.
431      */
432     while (days <= max_days)
433     {
434         gchar *label;
435         gint normalized;
436         gint step;
437 
438         if (days < 7)
439         {
440             /* days */
441             normalized = days;
442             step = 2;
443         }
444         else if (days < 30)
445         {
446             /* weeks */
447             normalized = days / 7;
448             if (normalized == 1)
449             {
450                 days = 7;
451             }
452             step = 7;
453         }
454         else if (days < 365)
455         {
456             /* months */
457             normalized = days / 30;
458             if (normalized == 1)
459             {
460                 days = 30;
461             }
462             step = 90;
463         }
464         else
465         {
466             /* years */
467             normalized = days / 365;
468             if (normalized == 1)
469             {
470                 days = 365;
471             }
472             step = 365;
473         }
474 
475         current_date = g_date_time_add_days (now, -days);
476         date_range = g_ptr_array_new_full (2, (GDestroyNotify) g_date_time_unref);
477         g_ptr_array_add (date_range, g_date_time_ref (current_date));
478         g_ptr_array_add (date_range, g_date_time_ref (now));
479         label = get_text_for_date_range (date_range, FALSE);
480         row = create_row_for_label (label, normalized == 1);
481         g_object_set_data_full (G_OBJECT (row),
482                                 "date",
483                                 g_date_time_ref (current_date),
484                                 (GDestroyNotify) g_date_time_unref);
485 
486         gtk_container_add (GTK_CONTAINER (popover->dates_listbox), row);
487 
488         g_free (label);
489         g_date_time_unref (current_date);
490         g_ptr_array_unref (date_range);
491 
492         days += step;
493     }
494 
495     g_date_time_unref (maximum_dt);
496     g_date_time_unref (now);
497 }
498 
499 static void
fill_types_listbox(NautilusSearchPopover * popover)500 fill_types_listbox (NautilusSearchPopover *popover)
501 {
502     GtkWidget *row;
503     int i;
504     gint n_groups;
505 
506     n_groups = nautilus_mime_types_get_number_of_groups ();
507     /* Mimetypes */
508     for (i = 0; i < n_groups; i++)
509     {
510         /* On the third row, which is right below "Folders", there should be an
511          * separator to logically group the types.
512          */
513         row = create_row_for_label (nautilus_mime_types_group_get_name (i), i == 3);
514         g_object_set_data (G_OBJECT (row), "mimetype-group", GINT_TO_POINTER (i));
515 
516         gtk_container_add (GTK_CONTAINER (popover->type_listbox), row);
517     }
518 
519     /* Other types */
520     row = create_row_for_label (_("Other Type…"), TRUE);
521     g_object_set_data (G_OBJECT (row), "mimetype-group", GINT_TO_POINTER (-1));
522     gtk_container_add (GTK_CONTAINER (popover->type_listbox), row);
523 }
524 
525 static void
show_date_selection_widgets(NautilusSearchPopover * popover,gboolean visible)526 show_date_selection_widgets (NautilusSearchPopover *popover,
527                              gboolean               visible)
528 {
529     gtk_stack_set_visible_child_name (GTK_STACK (popover->date_stack),
530                                       visible ? "date-entry" : "date-button");
531     gtk_stack_set_visible_child_name (GTK_STACK (popover->around_stack), "date-list");
532     gtk_entry_set_icon_from_icon_name (GTK_ENTRY (popover->date_entry),
533                                        GTK_ENTRY_ICON_SECONDARY,
534                                        "x-office-calendar-symbolic");
535 
536     gtk_widget_set_visible (popover->around_revealer, visible);
537 
538     gtk_revealer_set_reveal_child (GTK_REVEALER (popover->around_revealer), visible);
539 }
540 
541 static void
show_other_types_dialog(NautilusSearchPopover * popover)542 show_other_types_dialog (NautilusSearchPopover *popover)
543 {
544     GList *mime_infos, *l;
545     GtkWidget *dialog;
546     GtkWidget *scrolled, *treeview;
547     GtkListStore *store;
548     GtkTreeViewColumn *column;
549     GtkCellRenderer *renderer;
550     GtkWidget *toplevel;
551     GtkTreeSelection *selection;
552 
553     mime_infos = g_content_types_get_registered ();
554 
555     store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
556     for (l = mime_infos; l != NULL; l = l->next)
557     {
558         GtkTreeIter iter;
559         char *mime_type = l->data;
560         char *description;
561 
562         description = g_content_type_get_description (mime_type);
563         if (description == NULL)
564         {
565             description = g_strdup (mime_type);
566         }
567 
568         gtk_list_store_append (store, &iter);
569         gtk_list_store_set (store, &iter,
570                             0, description,
571                             1, mime_type,
572                             -1);
573 
574         g_free (mime_type);
575         g_free (description);
576     }
577     g_list_free (mime_infos);
578 
579     toplevel = gtk_widget_get_toplevel (GTK_WIDGET (popover));
580     dialog = gtk_dialog_new_with_buttons (_("Select type"),
581                                           GTK_WINDOW (toplevel),
582                                           GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_USE_HEADER_BAR,
583                                           _("_Cancel"), GTK_RESPONSE_CANCEL,
584                                           _("Select"), GTK_RESPONSE_OK,
585                                           NULL);
586     gtk_window_set_default_size (GTK_WINDOW (dialog), 400, 600);
587 
588     scrolled = gtk_scrolled_window_new (NULL, NULL);
589     gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
590                                     GTK_POLICY_AUTOMATIC,
591                                     GTK_POLICY_AUTOMATIC);
592 
593     gtk_widget_show (scrolled);
594     gtk_container_set_border_width (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), 0);
595     gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), scrolled, TRUE, TRUE, 0);
596 
597     treeview = gtk_tree_view_new ();
598     gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), GTK_TREE_MODEL (store));
599     gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), 0, GTK_SORT_ASCENDING);
600 
601     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
602     gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
603 
604 
605     renderer = gtk_cell_renderer_text_new ();
606     column = gtk_tree_view_column_new_with_attributes ("Name",
607                                                        renderer,
608                                                        "text",
609                                                        0,
610                                                        NULL);
611     gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
612     gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
613 
614     gtk_widget_show (treeview);
615     gtk_container_add (GTK_CONTAINER (scrolled), treeview);
616 
617     if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK)
618     {
619         GtkTreeIter iter;
620         char *mimetype;
621         char *description;
622 
623         gtk_tree_selection_get_selected (selection, NULL, &iter);
624         gtk_tree_model_get (GTK_TREE_MODEL (store), &iter,
625                             0, &description,
626                             1, &mimetype,
627                             -1);
628 
629         gtk_label_set_label (GTK_LABEL (popover->type_label), description);
630 
631         g_signal_emit_by_name (popover, "mime-type", -1, mimetype);
632 
633         gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
634     }
635 
636     gtk_widget_destroy (dialog);
637 }
638 
639 static void
update_date_label(NautilusSearchPopover * popover,GPtrArray * date_range)640 update_date_label (NautilusSearchPopover *popover,
641                    GPtrArray             *date_range)
642 {
643     if (date_range)
644     {
645         gint days;
646         GDateTime *initial_date;
647         GDateTime *end_date;
648         GDateTime *now;
649         gchar *label;
650 
651         now = g_date_time_new_now_local ();
652         initial_date = g_ptr_array_index (date_range, 0);
653         end_date = g_ptr_array_index (date_range, 0);
654         days = g_date_time_difference (end_date, initial_date) / G_TIME_SPAN_DAY;
655 
656         label = get_text_for_date_range (date_range, TRUE);
657 
658         gtk_entry_set_text (GTK_ENTRY (popover->date_entry), days < 1 ? label : "");
659 
660         gtk_widget_show (popover->clear_date_button);
661         gtk_label_set_label (GTK_LABEL (popover->select_date_button_label), label);
662 
663         g_date_time_unref (now);
664         g_free (label);
665     }
666     else
667     {
668         gtk_label_set_label (GTK_LABEL (popover->select_date_button_label),
669                              _("Select Dates…"));
670         gtk_entry_set_text (GTK_ENTRY (popover->date_entry), "");
671         gtk_widget_hide (popover->clear_date_button);
672     }
673 }
674 
675 void
nautilus_search_popover_set_fts_sensitive(NautilusSearchPopover * popover,gboolean sensitive)676 nautilus_search_popover_set_fts_sensitive (NautilusSearchPopover *popover,
677                                            gboolean               sensitive)
678 {
679     gtk_widget_set_sensitive (popover->full_text_search_button, sensitive);
680     gtk_widget_set_sensitive (popover->filename_search_button, sensitive);
681 }
682 
683 static void
nautilus_search_popover_closed(GtkPopover * popover)684 nautilus_search_popover_closed (GtkPopover *popover)
685 {
686     NautilusSearchPopover *self = NAUTILUS_SEARCH_POPOVER (popover);
687     GDateTime *now;
688 
689     /* Always switch back to the initial states */
690     gtk_stack_set_visible_child_name (GTK_STACK (self->type_stack), "type-button");
691     show_date_selection_widgets (self, FALSE);
692 
693     /* If we're closing an ongoing query, the popover must not
694      * clear the current settings.
695      */
696     if (self->query)
697     {
698         return;
699     }
700 
701     now = g_date_time_new_now_local ();
702 
703     /* Reselect today at the calendar */
704     g_signal_handlers_block_by_func (self->calendar, calendar_day_selected, self);
705 
706     gtk_calendar_select_month (GTK_CALENDAR (self->calendar),
707                                g_date_time_get_month (now) - 1,
708                                g_date_time_get_year (now));
709 
710     gtk_calendar_select_day (GTK_CALENDAR (self->calendar),
711                              g_date_time_get_day_of_month (now));
712 
713     g_signal_handlers_unblock_by_func (self->calendar, calendar_day_selected, self);
714 }
715 
716 static void
nautilus_search_popover_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)717 nautilus_search_popover_get_property (GObject    *object,
718                                       guint       prop_id,
719                                       GValue     *value,
720                                       GParamSpec *pspec)
721 {
722     NautilusSearchPopover *self;
723 
724     self = NAUTILUS_SEARCH_POPOVER (object);
725 
726     switch (prop_id)
727     {
728         case PROP_QUERY:
729         {
730             g_value_set_object (value, self->query);
731         }
732         break;
733 
734         case PROP_FTS_ENABLED:
735         {
736             g_value_set_boolean (value, self->fts_enabled);
737         }
738         break;
739 
740         default:
741         {
742             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
743         }
744     }
745 }
746 
747 static void
nautilus_search_popover_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)748 nautilus_search_popover_set_property (GObject      *object,
749                                       guint         prop_id,
750                                       const GValue *value,
751                                       GParamSpec   *pspec)
752 {
753     NautilusSearchPopover *self;
754 
755     self = NAUTILUS_SEARCH_POPOVER (object);
756 
757     switch (prop_id)
758     {
759         case PROP_QUERY:
760         {
761             nautilus_search_popover_set_query (self, g_value_get_object (value));
762         }
763         break;
764 
765         case PROP_FTS_ENABLED:
766         {
767             self->fts_enabled = g_value_get_boolean (value);
768         }
769         break;
770 
771         default:
772         {
773             G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
774         }
775     }
776 }
777 
778 
779 static void
nautilus_search_popover_class_init(NautilusSearchPopoverClass * klass)780 nautilus_search_popover_class_init (NautilusSearchPopoverClass *klass)
781 {
782     GtkPopoverClass *popover_class = GTK_POPOVER_CLASS (klass);
783     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
784     GObjectClass *object_class = G_OBJECT_CLASS (klass);
785 
786     object_class->get_property = nautilus_search_popover_get_property;
787     object_class->set_property = nautilus_search_popover_set_property;
788 
789     popover_class->closed = nautilus_search_popover_closed;
790 
791     signals[DATE_RANGE] = g_signal_new ("date-range",
792                                         NAUTILUS_TYPE_SEARCH_POPOVER,
793                                         G_SIGNAL_RUN_LAST,
794                                         0,
795                                         NULL,
796                                         NULL,
797                                         g_cclosure_marshal_generic,
798                                         G_TYPE_NONE,
799                                         1,
800                                         G_TYPE_PTR_ARRAY);
801 
802     signals[MIME_TYPE] = g_signal_new ("mime-type",
803                                        NAUTILUS_TYPE_SEARCH_POPOVER,
804                                        G_SIGNAL_RUN_LAST,
805                                        0,
806                                        NULL,
807                                        NULL,
808                                        g_cclosure_marshal_generic,
809                                        G_TYPE_NONE,
810                                        2,
811                                        G_TYPE_INT,
812                                        G_TYPE_STRING);
813 
814     signals[TIME_TYPE] = g_signal_new ("time-type",
815                                        NAUTILUS_TYPE_SEARCH_POPOVER,
816                                        G_SIGNAL_RUN_LAST,
817                                        0,
818                                        NULL,
819                                        NULL,
820                                        g_cclosure_marshal_generic,
821                                        G_TYPE_NONE,
822                                        1,
823                                        G_TYPE_INT);
824 
825     /**
826      * NautilusSearchPopover::query:
827      *
828      * The current #NautilusQuery being edited.
829      */
830     g_object_class_install_property (object_class,
831                                      PROP_QUERY,
832                                      g_param_spec_object ("query",
833                                                           "Query of the popover",
834                                                           "The current query being edited",
835                                                           NAUTILUS_TYPE_QUERY,
836                                                           G_PARAM_READWRITE));
837 
838     g_object_class_install_property (object_class,
839                                      PROP_FTS_ENABLED,
840                                      g_param_spec_boolean ("fts-enabled",
841                                                            "fts enabled",
842                                                            "fts enabled",
843                                                            FALSE,
844                                                            G_PARAM_READWRITE));
845 
846     gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-search-popover.ui");
847 
848     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, around_revealer);
849     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, around_stack);
850     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, clear_date_button);
851     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, calendar);
852     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, dates_listbox);
853     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, date_entry);
854     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, date_stack);
855     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, select_date_button);
856     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, select_date_button_label);
857     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_label);
858     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_listbox);
859     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, type_stack);
860     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, last_used_button);
861     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, last_modified_button);
862     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, full_text_search_button);
863     gtk_widget_class_bind_template_child (widget_class, NautilusSearchPopover, filename_search_button);
864 
865     gtk_widget_class_bind_template_callback (widget_class, calendar_day_selected);
866     gtk_widget_class_bind_template_callback (widget_class, clear_date_button_clicked);
867     gtk_widget_class_bind_template_callback (widget_class, date_entry_activate);
868     gtk_widget_class_bind_template_callback (widget_class, dates_listbox_row_activated);
869     gtk_widget_class_bind_template_callback (widget_class, select_date_button_clicked);
870     gtk_widget_class_bind_template_callback (widget_class, select_type_button_clicked);
871     gtk_widget_class_bind_template_callback (widget_class, toggle_calendar_icon_clicked);
872     gtk_widget_class_bind_template_callback (widget_class, types_listbox_row_activated);
873     gtk_widget_class_bind_template_callback (widget_class, search_time_type_changed);
874     gtk_widget_class_bind_template_callback (widget_class, search_fts_mode_changed);
875 }
876 
877 static void
nautilus_search_popover_init(NautilusSearchPopover * self)878 nautilus_search_popover_init (NautilusSearchPopover *self)
879 {
880     NautilusQuerySearchType filter_time_type;
881 
882     gtk_widget_init_template (GTK_WIDGET (self));
883 
884     /* Fuzzy dates listbox */
885     gtk_list_box_set_header_func (GTK_LIST_BOX (self->dates_listbox),
886                                   (GtkListBoxUpdateHeaderFunc) listbox_header_func,
887                                   self,
888                                   NULL);
889 
890     fill_fuzzy_dates_listbox (self);
891 
892     /* Types listbox */
893     gtk_list_box_set_header_func (GTK_LIST_BOX (self->type_listbox),
894                                   (GtkListBoxUpdateHeaderFunc) listbox_header_func,
895                                   self,
896                                   NULL);
897 
898     fill_types_listbox (self);
899 
900     gtk_list_box_select_row (GTK_LIST_BOX (self->type_listbox),
901                              gtk_list_box_get_row_at_index (GTK_LIST_BOX (self->type_listbox), 0));
902 
903     filter_time_type = g_settings_get_enum (nautilus_preferences, "search-filter-time-type");
904     if (filter_time_type == NAUTILUS_QUERY_SEARCH_TYPE_LAST_MODIFIED)
905     {
906         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->last_modified_button), TRUE);
907         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->last_used_button), FALSE);
908     }
909     else
910     {
911         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->last_modified_button), FALSE);
912         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->last_used_button), TRUE);
913     }
914 
915     self->fts_enabled = g_settings_get_boolean (nautilus_preferences,
916                                                 NAUTILUS_PREFERENCES_FTS_ENABLED);
917     if (self->fts_enabled)
918     {
919         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->full_text_search_button), TRUE);
920     }
921     else
922     {
923         gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (self->filename_search_button), TRUE);
924     }
925 }
926 
927 GtkWidget *
nautilus_search_popover_new(void)928 nautilus_search_popover_new (void)
929 {
930     return g_object_new (NAUTILUS_TYPE_SEARCH_POPOVER, NULL);
931 }
932 
933 /**
934  * nautilus_search_popover_get_query:
935  * @popover: a #NautilusSearchPopover
936  *
937  * Gets the current query for @popover.
938  *
939  * Returns: (transfer none): the current #NautilusQuery from @popover.
940  */
941 NautilusQuery *
nautilus_search_popover_get_query(NautilusSearchPopover * popover)942 nautilus_search_popover_get_query (NautilusSearchPopover *popover)
943 {
944     g_return_val_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover), NULL);
945 
946     return popover->query;
947 }
948 
949 /**
950  * nautilus_search_popover_set_query:
951  * @popover: a #NautilusSearchPopover
952  * @query (nullable): a #NautilusQuery
953  *
954  * Sets the current query for @popover.
955  *
956  * Returns:
957  */
958 void
nautilus_search_popover_set_query(NautilusSearchPopover * popover,NautilusQuery * query)959 nautilus_search_popover_set_query (NautilusSearchPopover *popover,
960                                    NautilusQuery         *query)
961 {
962     NautilusQuery *previous_query;
963 
964     g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover));
965 
966     previous_query = popover->query;
967 
968     if (popover->query != query)
969     {
970         /* Disconnect signals and bindings from the old query */
971         if (previous_query)
972         {
973             g_signal_handlers_disconnect_by_func (previous_query, query_date_changed, popover);
974         }
975 
976         g_set_object (&popover->query, query);
977 
978         if (query)
979         {
980             /* Date */
981             setup_date (popover, query);
982 
983             g_signal_connect (query,
984                               "notify::date",
985                               G_CALLBACK (query_date_changed),
986                               popover);
987         }
988         else
989         {
990             nautilus_search_popover_reset_mime_types (popover);
991             nautilus_search_popover_reset_date_range (popover);
992         }
993     }
994 }
995 
996 void
nautilus_search_popover_reset_mime_types(NautilusSearchPopover * popover)997 nautilus_search_popover_reset_mime_types (NautilusSearchPopover *popover)
998 {
999     g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover));
1000 
1001     gtk_list_box_select_row (GTK_LIST_BOX (popover->type_listbox),
1002                              gtk_list_box_get_row_at_index (GTK_LIST_BOX (popover->type_listbox), 0));
1003 
1004     gtk_label_set_label (GTK_LABEL (popover->type_label),
1005                          nautilus_mime_types_group_get_name (0));
1006     g_signal_emit_by_name (popover, "mime-type", 0, NULL);
1007     gtk_stack_set_visible_child_name (GTK_STACK (popover->type_stack), "type-button");
1008 }
1009 
1010 void
nautilus_search_popover_reset_date_range(NautilusSearchPopover * popover)1011 nautilus_search_popover_reset_date_range (NautilusSearchPopover *popover)
1012 {
1013     g_return_if_fail (NAUTILUS_IS_SEARCH_POPOVER (popover));
1014 
1015     gtk_list_box_select_row (GTK_LIST_BOX (popover->dates_listbox),
1016                              gtk_list_box_get_row_at_index (GTK_LIST_BOX (popover->dates_listbox), 0));
1017 
1018     update_date_label (popover, NULL);
1019     show_date_selection_widgets (popover, FALSE);
1020     g_signal_emit_by_name (popover, "date-range", NULL);
1021 }
1022 
1023 gboolean
nautilus_search_popover_get_fts_enabled(NautilusSearchPopover * popover)1024 nautilus_search_popover_get_fts_enabled (NautilusSearchPopover *popover)
1025 {
1026     return popover->fts_enabled;
1027 }
1028