gnome-control-center/panels/search/cc-search-locations-dialog.c
Georges Basile Stavracas Neto 26630338ba search: Port to GTK4
This one was the first panel ported that used Drag n' Drop, and
we can see how much of an improvement GTK4 is compared to GTK3
when handling DnD.
2021-12-14 22:34:21 -03:00

746 lines
22 KiB
C

/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2012 Red Hat, Inc
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*
* Author: Cosimo Cecchi <cosimoc@gnome.org>
*/
#include "cc-search-locations-dialog.h"
#include <glib/gi18n.h>
#define TRACKER_SCHEMA "org.freedesktop.Tracker.Miner.Files"
#define TRACKER3_SCHEMA "org.freedesktop.Tracker3.Miner.Files"
#define TRACKER_KEY_RECURSIVE_DIRECTORIES "index-recursive-directories"
#define TRACKER_KEY_SINGLE_DIRECTORIES "index-single-directories"
typedef enum {
PLACE_XDG,
PLACE_BOOKMARKS,
PLACE_OTHER
} PlaceType;
typedef struct {
CcSearchLocationsDialog *dialog;
GFile *location;
gchar *display_name;
PlaceType place_type;
GCancellable *cancellable;
const gchar *settings_key;
} Place;
struct _CcSearchLocationsDialog {
GtkDialog parent;
GSettings *tracker_preferences;
GtkWidget *places_list;
GtkWidget *bookmarks_list;
GtkWidget *others_list;
GtkWidget *locations_add;
};
struct _CcSearchLocationsDialogClass {
GtkDialogClass parent_class;
};
G_DEFINE_TYPE (CcSearchLocationsDialog, cc_search_locations_dialog, GTK_TYPE_DIALOG)
static void
cc_search_locations_dialog_finalize (GObject *object)
{
CcSearchLocationsDialog *self = CC_SEARCH_LOCATIONS_DIALOG (object);
g_clear_object (&self->tracker_preferences);
G_OBJECT_CLASS (cc_search_locations_dialog_parent_class)->finalize (object);
}
static void
cc_search_locations_dialog_init (CcSearchLocationsDialog *self)
{
gtk_widget_init_template (GTK_WIDGET (self));
}
static Place *
place_new (CcSearchLocationsDialog *dialog,
GFile *location,
gchar *display_name,
PlaceType place_type)
{
Place *new_place = g_new0 (Place, 1);
new_place->dialog = dialog;
new_place->location = location;
if (display_name != NULL)
new_place->display_name = display_name;
else
new_place->display_name = g_file_get_basename (location);
if (g_strcmp0 (g_file_get_path (location), g_get_home_dir ()) == 0)
new_place->settings_key = TRACKER_KEY_SINGLE_DIRECTORIES;
else
new_place->settings_key = TRACKER_KEY_RECURSIVE_DIRECTORIES;
new_place->place_type = place_type;
return new_place;
}
static void
place_free (Place * p)
{
g_cancellable_cancel (p->cancellable);
g_clear_object (&p->cancellable);
g_object_unref (p->location);
g_free (p->display_name);
g_free (p);
}
G_DEFINE_AUTOPTR_CLEANUP_FUNC (Place, place_free)
static GList *
get_bookmarks (CcSearchLocationsDialog *self)
{
g_autoptr(GFile) file = NULL;
g_autofree gchar *contents = NULL;
g_autofree gchar *path = NULL;
GList *bookmarks = NULL;
GError *error = NULL;
path = g_build_filename (g_get_user_config_dir (), "gtk-3.0",
"bookmarks", NULL);
file = g_file_new_for_path (path);
if (g_file_load_contents (file, NULL, &contents, NULL, NULL, &error))
{
gint idx;
g_auto(GStrv) lines = NULL;
lines = g_strsplit (contents, "\n", -1);
for (idx = 0; lines[idx]; idx++)
{
/* Ignore empty or invalid lines that cannot be parsed properly */
if (lines[idx][0] != '\0' && lines[idx][0] != ' ')
{
/* gtk 2.7/2.8 might have labels appended to bookmarks which are separated by a space */
/* we must seperate the bookmark uri and the potential label */
char *space, *label;
Place *bookmark;
label = NULL;
space = strchr (lines[idx], ' ');
if (space)
{
*space = '\0';
label = g_strdup (space + 1);
}
bookmark = place_new (self,
g_file_new_for_uri (lines[idx]),
label,
PLACE_BOOKMARKS);
bookmarks = g_list_prepend (bookmarks, bookmark);
}
}
}
return g_list_reverse (bookmarks);
}
static const gchar *
get_user_special_dir_if_not_home (GUserDirectory idx)
{
const gchar *path;
path = g_get_user_special_dir (idx);
if (g_strcmp0 (path, g_get_home_dir ()) == 0)
return NULL;
return path;
}
static GList *
get_xdg_dirs (CcSearchLocationsDialog *self)
{
GList *xdg_dirs = NULL;
gint idx;
const gchar *path;
Place *xdg_dir;
for (idx = 0; idx < G_USER_N_DIRECTORIES; idx++)
{
path = get_user_special_dir_if_not_home (idx);
if (path == NULL)
continue;
if (idx == G_USER_DIRECTORY_TEMPLATES ||
idx == G_USER_DIRECTORY_PUBLIC_SHARE ||
idx == G_USER_DIRECTORY_DESKTOP)
continue;
xdg_dir = place_new (self,
g_file_new_for_path (path),
NULL,
PLACE_XDG);
xdg_dirs = g_list_prepend (xdg_dirs, xdg_dir);
}
return g_list_reverse (xdg_dirs);
}
static const gchar *
path_to_tracker_dir (const gchar *path)
{
const gchar *value;
if (g_strcmp0 (path, get_user_special_dir_if_not_home (G_USER_DIRECTORY_DESKTOP)) == 0)
value = "&DESKTOP";
else if (g_strcmp0 (path, get_user_special_dir_if_not_home (G_USER_DIRECTORY_DOCUMENTS)) == 0)
value = "&DOCUMENTS";
else if (g_strcmp0 (path, get_user_special_dir_if_not_home (G_USER_DIRECTORY_DOWNLOAD)) == 0)
value = "&DOWNLOAD";
else if (g_strcmp0 (path, get_user_special_dir_if_not_home (G_USER_DIRECTORY_MUSIC)) == 0)
value = "&MUSIC";
else if (g_strcmp0 (path, get_user_special_dir_if_not_home (G_USER_DIRECTORY_PICTURES)) == 0)
value = "&PICTURES";
else if (g_strcmp0 (path, get_user_special_dir_if_not_home (G_USER_DIRECTORY_PUBLIC_SHARE)) == 0)
value = "&PUBLIC_SHARE";
else if (g_strcmp0 (path, get_user_special_dir_if_not_home (G_USER_DIRECTORY_TEMPLATES)) == 0)
value = "&TEMPLATES";
else if (g_strcmp0 (path, get_user_special_dir_if_not_home (G_USER_DIRECTORY_VIDEOS)) == 0)
value = "&VIDEOS";
else if (g_strcmp0 (path, g_get_home_dir ()) == 0)
value = "$HOME";
else
value = path;
return value;
}
static const gchar *
path_from_tracker_dir (const gchar *value)
{
const gchar *path;
if (g_strcmp0 (value, "&DESKTOP") == 0)
path = get_user_special_dir_if_not_home (G_USER_DIRECTORY_DESKTOP);
else if (g_strcmp0 (value, "&DOCUMENTS") == 0)
path = get_user_special_dir_if_not_home (G_USER_DIRECTORY_DOCUMENTS);
else if (g_strcmp0 (value, "&DOWNLOAD") == 0)
path = get_user_special_dir_if_not_home (G_USER_DIRECTORY_DOWNLOAD);
else if (g_strcmp0 (value, "&MUSIC") == 0)
path = get_user_special_dir_if_not_home (G_USER_DIRECTORY_MUSIC);
else if (g_strcmp0 (value, "&PICTURES") == 0)
path = get_user_special_dir_if_not_home (G_USER_DIRECTORY_PICTURES);
else if (g_strcmp0 (value, "&PUBLIC_SHARE") == 0)
path = get_user_special_dir_if_not_home (G_USER_DIRECTORY_PUBLIC_SHARE);
else if (g_strcmp0 (value, "&TEMPLATES") == 0)
path = get_user_special_dir_if_not_home (G_USER_DIRECTORY_TEMPLATES);
else if (g_strcmp0 (value, "&VIDEOS") == 0)
path = get_user_special_dir_if_not_home (G_USER_DIRECTORY_VIDEOS);
else if (g_strcmp0 (value, "$HOME") == 0)
path = g_get_home_dir ();
else
path = value;
return path;
}
static GPtrArray *
place_get_new_settings_values (CcSearchLocationsDialog *self,
Place *place,
gboolean remove)
{
g_auto(GStrv) values = NULL;
g_autofree gchar *path = NULL;
GPtrArray *new_values;
const gchar *tracker_dir;
gboolean found;
gint idx;
new_values = g_ptr_array_new_with_free_func (g_free);
values = g_settings_get_strv (self->tracker_preferences, place->settings_key);
path = g_file_get_path (place->location);
tracker_dir = path_to_tracker_dir (path);
found = FALSE;
for (idx = 0; values[idx] != NULL; idx++)
{
if (g_strcmp0 (values[idx], tracker_dir) == 0)
{
found = TRUE;
if (remove)
continue;
}
g_ptr_array_add (new_values, g_strdup (values[idx]));
}
if (!found && !remove)
g_ptr_array_add (new_values, g_strdup (tracker_dir));
g_ptr_array_add (new_values, NULL);
return new_values;
}
static GList *
get_tracker_locations (CcSearchLocationsDialog *self)
{
g_auto(GStrv) locations = NULL;
GFile *file;
GList *list;
gint idx;
Place *location;
const gchar *path;
locations = g_settings_get_strv (self->tracker_preferences, TRACKER_KEY_RECURSIVE_DIRECTORIES);
list = NULL;
for (idx = 0; locations[idx] != NULL; idx++)
{
path = path_from_tracker_dir (locations[idx]);
file = g_file_new_for_commandline_arg (path);
location = place_new (self,
file,
NULL,
PLACE_OTHER);
if (file != NULL && g_file_query_exists (file, NULL))
{
list = g_list_prepend (list, location);
}
else
{
g_autoptr(GPtrArray) new_values = NULL;
new_values = place_get_new_settings_values (self, location, TRUE);
g_settings_set_strv (self->tracker_preferences,
TRACKER_KEY_RECURSIVE_DIRECTORIES,
(const gchar **) new_values->pdata);
}
}
return g_list_reverse (list);
}
static GList *
get_places_list (CcSearchLocationsDialog *self)
{
g_autoptr(GList) xdg_list = NULL;
g_autoptr(GList) tracker_list = NULL;
g_autoptr(GList) bookmark_list = NULL;
GList *l;
g_autoptr(GHashTable) places = NULL;
Place *place, *old_place;
GList *places_list;
places = g_hash_table_new_full (g_file_hash, (GEqualFunc) g_file_equal, NULL, (GDestroyNotify) place_free);
/* add home */
place = place_new (self,
g_file_new_for_path (g_get_home_dir ()),
g_strdup (_("Home")),
PLACE_XDG);
g_hash_table_insert (places, place->location, place);
/* first, load the XDG dirs */
xdg_list = get_xdg_dirs (self);
for (l = xdg_list; l != NULL; l = l->next)
{
place = l->data;
g_hash_table_insert (places, place->location, place);
}
/* then, insert all the tracker locations that are not XDG dirs */
tracker_list = get_tracker_locations (self);
for (l = tracker_list; l != NULL; l = l->next)
{
g_autoptr(Place) p = l->data;
old_place = g_hash_table_lookup (places, p->location);
if (old_place == NULL)
{
g_hash_table_insert (places, p->location, p);
g_steal_pointer (&p);
}
}
/* finally, load bookmarks, and possibly update attributes */
bookmark_list = get_bookmarks (self);
for (l = bookmark_list; l != NULL; l = l->next)
{
g_autoptr(Place) p = l->data;
old_place = g_hash_table_lookup (places, p->location);
if (old_place == NULL)
{
g_hash_table_insert (places, p->location, p);
g_steal_pointer (&p);
}
else
{
g_free (old_place->display_name);
old_place->display_name = g_strdup (p->display_name);
if (old_place->place_type == PLACE_OTHER)
old_place->place_type = PLACE_BOOKMARKS;
}
}
places_list = g_hash_table_get_values (places);
g_hash_table_steal_all (places);
return places_list;
}
static gboolean
switch_tracker_get_mapping (GValue *value,
GVariant *variant,
gpointer user_data)
{
Place *place = user_data;
g_autofree const gchar **locations = NULL;
GFile *location;
gint idx;
gboolean found;
found = FALSE;
locations = g_variant_get_strv (variant, NULL);
for (idx = 0; locations[idx] != NULL; idx++)
{
location = g_file_new_for_path (path_from_tracker_dir(locations[idx]));
found = g_file_equal (location, place->location);
g_object_unref (location);
if (found)
break;
}
g_value_set_boolean (value, found);
return TRUE;
}
static GVariant *
switch_tracker_set_mapping (const GValue *value,
const GVariantType *expected_type,
gpointer user_data)
{
Place *place = user_data;
g_autoptr(GPtrArray) new_values = NULL;
gboolean remove;
remove = !g_value_get_boolean (value);
new_values = place_get_new_settings_values (place->dialog, place, remove);
return g_variant_new_strv ((const gchar **) new_values->pdata, -1);
}
static void
place_query_info_ready (GObject *source,
GAsyncResult *res,
gpointer user_data)
{
g_autoptr(GFileInfo) info = NULL;
GtkWidget *row, *box, *w;
Place *place;
info = g_file_query_info_finish (G_FILE (source), res, NULL);
if (!info)
return;
row = user_data;
place = g_object_get_data (G_OBJECT (row), "place");
g_clear_object (&place->cancellable);
box = gtk_list_box_row_get_child (GTK_LIST_BOX_ROW (row));
w = gtk_switch_new ();
gtk_widget_set_valign (w, GTK_ALIGN_CENTER);
gtk_box_prepend (GTK_BOX (box), w);
g_settings_bind_with_mapping (place->dialog->tracker_preferences, place->settings_key,
w, "active",
G_SETTINGS_BIND_DEFAULT,
switch_tracker_get_mapping,
switch_tracker_set_mapping,
place, NULL);
w = gtk_label_new (place->display_name);
gtk_label_set_xalign (GTK_LABEL (w), 0.0);
gtk_widget_set_hexpand (w, TRUE);
gtk_box_prepend (GTK_BOX (box), w);
}
static void
remove_button_clicked (CcSearchLocationsDialog *self,
GtkWidget *button)
{
g_autoptr(GPtrArray) new_values = NULL;
Place *place;
place = g_object_get_data (G_OBJECT (button), "place");
new_values = place_get_new_settings_values (self, place, TRUE);
g_settings_set_strv (self->tracker_preferences, place->settings_key, (const gchar **) new_values->pdata);
}
static gint
place_compare_func (gconstpointer a,
gconstpointer b,
gpointer user_data)
{
GtkWidget *child_a, *child_b;
Place *place_a, *place_b;
g_autofree gchar *path = NULL;
gboolean is_home;
child_a = GTK_WIDGET (a);
child_b = GTK_WIDGET (b);
place_a = g_object_get_data (G_OBJECT (child_a), "place");
place_b = g_object_get_data (G_OBJECT (child_b), "place");
path = g_file_get_path (place_a->location);
is_home = (g_strcmp0 (path, g_get_home_dir ()) == 0);
if (is_home)
return -1;
if (place_a->place_type == place_b->place_type)
return g_utf8_collate (place_a->display_name, place_b->display_name);
if (place_a->place_type == PLACE_XDG)
return -1;
if ((place_a->place_type == PLACE_BOOKMARKS) && (place_b->place_type == PLACE_OTHER))
return -1;
return 1;
}
static GtkWidget *
create_row_for_place (CcSearchLocationsDialog *self, Place *place)
{
GtkWidget *child, *row, *remove_button;
row = gtk_list_box_row_new ();
gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), FALSE);
gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), FALSE);
child = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_list_box_row_set_child (GTK_LIST_BOX_ROW (row), child);
g_object_set (row,
"margin-top", 6,
"margin-bottom", 6,
"margin-start", 16,
"margin-end", 6,
NULL);
g_object_set_data_full (G_OBJECT (row), "place", place, (GDestroyNotify) place_free);
if (place->place_type == PLACE_OTHER)
{
remove_button = gtk_button_new_from_icon_name ("window-close-symbolic");
g_object_set_data (G_OBJECT (remove_button), "place", place);
gtk_style_context_add_class (gtk_widget_get_style_context (remove_button), "flat");
gtk_box_append (GTK_BOX (child), remove_button);
g_signal_connect_swapped (remove_button, "clicked",
G_CALLBACK (remove_button_clicked), self);
}
place->cancellable = g_cancellable_new ();
g_file_query_info_async (place->location, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT,
place->cancellable, place_query_info_ready, row);
return row;
}
static void
populate_list_boxes (CcSearchLocationsDialog *self)
{
g_autoptr(GList) places = NULL;
GList *l;
Place *place;
GtkWidget *row;
places = get_places_list (self);
for (l = places; l != NULL; l = l->next)
{
place = l->data;
row = create_row_for_place (self, place);
switch (place->place_type)
{
case PLACE_XDG:
gtk_list_box_append (GTK_LIST_BOX (self->places_list), row);
break;
case PLACE_BOOKMARKS:
gtk_list_box_append (GTK_LIST_BOX (self->bookmarks_list), row);
break;
case PLACE_OTHER:
gtk_list_box_append (GTK_LIST_BOX (self->others_list), row);
break;
default:
g_assert_not_reached ();
}
}
}
static void
add_file_chooser_response (CcSearchLocationsDialog *self,
GtkResponseType response,
GtkWidget *widget)
{
g_autoptr(Place) place = NULL;
g_autoptr(GPtrArray) new_values = NULL;
if (response != GTK_RESPONSE_OK)
{
gtk_window_destroy (GTK_WINDOW (widget));
return;
}
place = place_new (self,
gtk_file_chooser_get_file (GTK_FILE_CHOOSER (widget)),
NULL,
0);
place->settings_key = TRACKER_KEY_RECURSIVE_DIRECTORIES;
new_values = place_get_new_settings_values (self, place, FALSE);
g_settings_set_strv (self->tracker_preferences, place->settings_key, (const gchar **) new_values->pdata);
gtk_window_destroy (GTK_WINDOW (widget));
}
static void
add_button_clicked (CcSearchLocationsDialog *self)
{
GtkWidget *file_chooser;
file_chooser = gtk_file_chooser_dialog_new (_("Select Location"),
GTK_WINDOW (self),
GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
_("_Cancel"), GTK_RESPONSE_CANCEL,
_("_OK"), GTK_RESPONSE_OK,
NULL);
gtk_window_set_modal (GTK_WINDOW (file_chooser), TRUE);
g_signal_connect_swapped (file_chooser, "response",
G_CALLBACK (add_file_chooser_response), self);
gtk_window_present (GTK_WINDOW (file_chooser));
}
static void
other_places_refresh (CcSearchLocationsDialog *self)
{
g_autoptr(GList) places = NULL;
GList *l;
GtkWidget *widget;
while ((widget = gtk_widget_get_first_child (self->others_list)) != NULL)
gtk_list_box_remove (GTK_LIST_BOX (self->others_list), widget);
places = get_places_list (self);
for (l = places; l != NULL; l = l->next)
{
GtkWidget *row;
Place *place;
place = l->data;
if (place->place_type != PLACE_OTHER)
continue;
row = create_row_for_place (self, place);
gtk_list_box_append (GTK_LIST_BOX (self->others_list), row);
}
}
CcSearchLocationsDialog *
cc_search_locations_dialog_new (CcSearchPanel *panel)
{
CcSearchLocationsDialog *self;
GSettingsSchemaSource *source;
g_autoptr(GSettingsSchema) schema = NULL;
GtkWidget *toplevel;
CcShell *shell;
self = g_object_new (CC_SEARCH_LOCATIONS_DIALOG_TYPE,
"use-header-bar", TRUE,
NULL);
source = g_settings_schema_source_get_default ();
schema = g_settings_schema_source_lookup (source, TRACKER3_SCHEMA, TRUE);
if (schema)
self->tracker_preferences = g_settings_new (TRACKER3_SCHEMA);
else
self->tracker_preferences = g_settings_new (TRACKER_SCHEMA);
populate_list_boxes (self);
gtk_list_box_set_sort_func (GTK_LIST_BOX (self->others_list),
(GtkListBoxSortFunc)place_compare_func, NULL, NULL);
g_signal_connect_swapped (self->tracker_preferences, "changed::" TRACKER_KEY_RECURSIVE_DIRECTORIES,
G_CALLBACK (other_places_refresh), self);
shell = cc_panel_get_shell (CC_PANEL (panel));
toplevel = cc_shell_get_toplevel (shell);
gtk_window_set_transient_for (GTK_WINDOW (self), GTK_WINDOW (toplevel));
return self;
}
gboolean
cc_search_locations_dialog_is_available (void)
{
GSettingsSchemaSource *source;
g_autoptr(GSettingsSchema) schema = NULL;
source = g_settings_schema_source_get_default ();
if (!source)
return FALSE;
schema = g_settings_schema_source_lookup (source, TRACKER3_SCHEMA, TRUE);
if (schema)
return TRUE;
schema = g_settings_schema_source_lookup (source, TRACKER_SCHEMA, TRUE);
if (schema)
return TRUE;
return FALSE;
}
static void
cc_search_locations_dialog_class_init (CcSearchLocationsDialogClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->finalize = cc_search_locations_dialog_finalize;
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/control-center/search/cc-search-locations-dialog.ui");
gtk_widget_class_bind_template_child (widget_class, CcSearchLocationsDialog, places_list);
gtk_widget_class_bind_template_child (widget_class, CcSearchLocationsDialog, bookmarks_list);
gtk_widget_class_bind_template_child (widget_class, CcSearchLocationsDialog, others_list);
gtk_widget_class_bind_template_child (widget_class, CcSearchLocationsDialog, locations_add);
gtk_widget_class_bind_template_callback (widget_class, add_button_clicked);
}