Improve keyboard navigation with Up/Down arrow keys between Search entry and the search results list. Pressing Arrow Down in search entry will now move focus to search list, and when pressing Arrow Up on the first list result will move back focus to Search entry.
919 lines
27 KiB
C
919 lines
27 KiB
C
/*
|
|
* Copyright (c) 2009, 2010 Intel, Inc.
|
|
* Copyright (c) 2010 Red Hat, Inc.
|
|
* Copyright (c) 2016 Endless, Inc.
|
|
*
|
|
* The Control Center 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.
|
|
*
|
|
* The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Author: Thomas Wood <thos@gnome.org>
|
|
*/
|
|
|
|
#define G_LOG_DOMAIN "cc-window"
|
|
|
|
#include <config.h>
|
|
|
|
#include "cc-log.h"
|
|
#include "cc-window.h"
|
|
|
|
#include <glib/gi18n.h>
|
|
#include <gio/gio.h>
|
|
#include <gio/gdesktopappinfo.h>
|
|
#include <gtk/gtk.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
|
|
#include "cc-application.h"
|
|
#include "cc-panel-private.h"
|
|
#include "cc-shell.h"
|
|
#include "cc-shell-model.h"
|
|
#include "cc-panel-list.h"
|
|
#include "cc-panel-loader.h"
|
|
#include "cc-util.h"
|
|
|
|
#define MOUSE_BACK_BUTTON 8
|
|
|
|
#define DEFAULT_WINDOW_ICON_NAME "gnome-control-center"
|
|
|
|
struct _CcWindow
|
|
{
|
|
AdwApplicationWindow parent;
|
|
|
|
GtkMessageDialog *development_warning_dialog;
|
|
AdwHeaderBar *header;
|
|
AdwLeaflet *main_leaflet;
|
|
CcPanelList *panel_list;
|
|
GtkButton *previous_button;
|
|
GtkSearchBar *search_bar;
|
|
GtkToggleButton *search_button;
|
|
GtkSearchEntry *search_entry;
|
|
GtkBox *sidebar_box;
|
|
AdwWindowTitle *sidebar_title_widget;
|
|
GtkStack *stack;
|
|
|
|
GtkWidget *old_panel;
|
|
GtkWidget *current_panel;
|
|
char *current_panel_id;
|
|
GQueue *previous_panels;
|
|
|
|
GtkWidget *custom_titlebar;
|
|
|
|
CcShellModel *store;
|
|
|
|
CcPanel *active_panel;
|
|
GSettings *settings;
|
|
|
|
gboolean folded;
|
|
|
|
CcPanelListView previous_list_view;
|
|
};
|
|
|
|
static void cc_shell_iface_init (CcShellInterface *iface);
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (CcWindow, cc_window, ADW_TYPE_APPLICATION_WINDOW,
|
|
G_IMPLEMENT_INTERFACE (CC_TYPE_SHELL, cc_shell_iface_init))
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_ACTIVE_PANEL,
|
|
PROP_MODEL,
|
|
PROP_FOLDED,
|
|
};
|
|
|
|
/* Auxiliary methods */
|
|
static void
|
|
load_window_state (CcWindow *self)
|
|
{
|
|
gint current_width = -1;
|
|
gint current_height = -1;
|
|
gboolean maximized = FALSE;
|
|
|
|
g_settings_get (self->settings,
|
|
"window-state",
|
|
"(iib)",
|
|
¤t_width,
|
|
¤t_height,
|
|
&maximized);
|
|
|
|
if (current_width != -1 && current_height != -1)
|
|
gtk_window_set_default_size (GTK_WINDOW (self), current_width, current_height);
|
|
if (maximized)
|
|
gtk_window_maximize (GTK_WINDOW (self));
|
|
}
|
|
|
|
static gboolean
|
|
in_flatpak_sandbox (void)
|
|
{
|
|
return g_strcmp0 (PROFILE, "development") == 0;
|
|
}
|
|
|
|
static void
|
|
on_sidebar_activated_cb (CcWindow *self)
|
|
{
|
|
adw_leaflet_navigate (self->main_leaflet, ADW_NAVIGATION_DIRECTION_FORWARD);
|
|
}
|
|
|
|
static gboolean
|
|
activate_panel (CcWindow *self,
|
|
const gchar *id,
|
|
GVariant *parameters,
|
|
const gchar *name,
|
|
GIcon *gicon,
|
|
CcPanelVisibility visibility)
|
|
{
|
|
g_autoptr(GTimer) timer = NULL;
|
|
GtkWidget *sidebar_widget;
|
|
gdouble ellapsed_time;
|
|
|
|
CC_ENTRY;
|
|
|
|
if (!id)
|
|
CC_RETURN (FALSE);
|
|
|
|
if (visibility == CC_PANEL_HIDDEN)
|
|
CC_RETURN (FALSE);
|
|
|
|
timer = g_timer_new ();
|
|
|
|
/* Begin the profile */
|
|
g_timer_start (timer);
|
|
|
|
if (self->current_panel)
|
|
g_signal_handlers_disconnect_by_data (self->current_panel, self);
|
|
self->current_panel = GTK_WIDGET (cc_panel_loader_load_by_name (CC_SHELL (self), id, name, parameters));
|
|
cc_panel_set_folded (CC_PANEL (self->current_panel), adw_leaflet_get_folded (self->main_leaflet));
|
|
cc_shell_set_active_panel (CC_SHELL (self), CC_PANEL (self->current_panel));
|
|
|
|
gtk_stack_add_named (self->stack, self->current_panel, id);
|
|
|
|
/* switch to the new panel */
|
|
gtk_stack_set_visible_child_name (self->stack, id);
|
|
|
|
sidebar_widget = cc_panel_get_sidebar_widget (CC_PANEL (self->current_panel));
|
|
cc_panel_list_add_sidebar_widget (self->panel_list, sidebar_widget);
|
|
/* Ensure we show the panel when the leaflet is folded and a sidebar widget's
|
|
* row is activated.
|
|
*/
|
|
g_signal_connect_object (self->current_panel, "sidebar-activated", G_CALLBACK (on_sidebar_activated_cb), self, G_CONNECT_SWAPPED);
|
|
|
|
/* Finish profiling */
|
|
g_timer_stop (timer);
|
|
|
|
ellapsed_time = g_timer_elapsed (timer, NULL);
|
|
|
|
g_debug ("Time to open panel '%s': %lfs", name, ellapsed_time);
|
|
|
|
g_settings_set_string (self->settings, "last-panel", id);
|
|
|
|
CC_RETURN (TRUE);
|
|
}
|
|
|
|
static void
|
|
add_current_panel_to_history (CcWindow *self,
|
|
const char *start_id)
|
|
{
|
|
g_return_if_fail (start_id != NULL);
|
|
|
|
if (!self->current_panel_id || g_strcmp0 (self->current_panel_id, start_id) == 0)
|
|
return;
|
|
|
|
g_queue_push_head (self->previous_panels, g_strdup (self->current_panel_id));
|
|
g_debug ("Added '%s' to the previous panels", self->current_panel_id);
|
|
}
|
|
|
|
static gboolean
|
|
find_iter_for_panel_id (CcWindow *self,
|
|
const gchar *panel_id,
|
|
GtkTreeIter *out_iter)
|
|
{
|
|
GtkTreeIter iter;
|
|
gboolean valid;
|
|
|
|
valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter);
|
|
|
|
while (valid)
|
|
{
|
|
g_autofree gchar *id = NULL;
|
|
|
|
gtk_tree_model_get (GTK_TREE_MODEL (self->store),
|
|
&iter,
|
|
COL_ID, &id,
|
|
-1);
|
|
|
|
if (g_strcmp0 (id, panel_id) == 0)
|
|
break;
|
|
|
|
valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (self->store), &iter);
|
|
}
|
|
|
|
g_assert (out_iter != NULL);
|
|
*out_iter = iter;
|
|
|
|
return valid;
|
|
}
|
|
|
|
static void
|
|
update_list_title (CcWindow *self)
|
|
{
|
|
CcPanelListView view;
|
|
GtkTreeIter iter;
|
|
g_autofree gchar *title = NULL;
|
|
|
|
CC_ENTRY;
|
|
|
|
view = cc_panel_list_get_view (self->panel_list);
|
|
title = NULL;
|
|
|
|
switch (view)
|
|
{
|
|
case CC_PANEL_LIST_PRIVACY:
|
|
title = g_strdup (_("Privacy"));
|
|
break;
|
|
|
|
case CC_PANEL_LIST_MAIN:
|
|
title = g_strdup (_("Settings"));
|
|
break;
|
|
|
|
case CC_PANEL_LIST_WIDGET:
|
|
find_iter_for_panel_id (self, self->current_panel_id, &iter);
|
|
gtk_tree_model_get (GTK_TREE_MODEL (self->store),
|
|
&iter,
|
|
COL_NAME, &title,
|
|
-1);
|
|
break;
|
|
|
|
case CC_PANEL_LIST_SEARCH:
|
|
title = NULL;
|
|
break;
|
|
}
|
|
|
|
if (title)
|
|
adw_window_title_set_title (self->sidebar_title_widget, title);
|
|
|
|
CC_EXIT;
|
|
}
|
|
|
|
static void
|
|
on_row_changed_cb (CcWindow *self,
|
|
GtkTreePath *path,
|
|
GtkTreeIter *iter,
|
|
GtkTreeModel *model)
|
|
{
|
|
g_autofree gchar *id = NULL;
|
|
CcPanelVisibility visibility;
|
|
|
|
gtk_tree_model_get (model, iter,
|
|
COL_ID, &id,
|
|
COL_VISIBILITY, &visibility,
|
|
-1);
|
|
|
|
cc_panel_list_set_panel_visibility (self->panel_list, id, visibility);
|
|
}
|
|
|
|
static void
|
|
setup_model (CcWindow *self)
|
|
{
|
|
GtkTreeModel *model;
|
|
GtkTreeIter iter;
|
|
gboolean valid;
|
|
|
|
/* CcApplication must have a valid model at this point */
|
|
g_assert (self->store != NULL);
|
|
|
|
model = GTK_TREE_MODEL (self->store);
|
|
|
|
cc_panel_loader_fill_model (self->store);
|
|
|
|
/* Create a row for each panel */
|
|
valid = gtk_tree_model_get_iter_first (model, &iter);
|
|
|
|
while (valid)
|
|
{
|
|
CcPanelCategory category;
|
|
g_autoptr(GIcon) icon = NULL;
|
|
g_autofree gchar *name = NULL;
|
|
g_autofree gchar *description = NULL;
|
|
g_autofree gchar *id = NULL;
|
|
g_auto(GStrv) keywords = NULL;
|
|
CcPanelVisibility visibility;
|
|
gboolean has_sidebar;
|
|
const gchar *icon_name = NULL;
|
|
|
|
gtk_tree_model_get (model, &iter,
|
|
COL_CATEGORY, &category,
|
|
COL_DESCRIPTION, &description,
|
|
COL_GICON, &icon,
|
|
COL_ID, &id,
|
|
COL_NAME, &name,
|
|
COL_KEYWORDS, &keywords,
|
|
COL_VISIBILITY, &visibility,
|
|
COL_HAS_SIDEBAR, &has_sidebar,
|
|
-1);
|
|
|
|
if (G_IS_THEMED_ICON (icon))
|
|
icon_name = g_themed_icon_get_names (G_THEMED_ICON (icon))[0];
|
|
|
|
cc_panel_list_add_panel (self->panel_list,
|
|
category,
|
|
id,
|
|
name,
|
|
description,
|
|
keywords,
|
|
icon_name,
|
|
visibility,
|
|
has_sidebar);
|
|
|
|
valid = gtk_tree_model_iter_next (model, &iter);
|
|
}
|
|
|
|
/* React to visibility changes */
|
|
g_signal_connect_object (model, "row-changed", G_CALLBACK (on_row_changed_cb), self, G_CONNECT_SWAPPED);
|
|
}
|
|
|
|
static void
|
|
update_headerbar_buttons (CcWindow *self)
|
|
{
|
|
gboolean is_main_view;
|
|
|
|
CC_ENTRY;
|
|
|
|
is_main_view = cc_panel_list_get_view (self->panel_list) == CC_PANEL_LIST_MAIN;
|
|
|
|
gtk_widget_set_visible (GTK_WIDGET (self->previous_button), !is_main_view);
|
|
gtk_widget_set_visible (GTK_WIDGET (self->search_button), is_main_view);
|
|
gtk_search_bar_set_key_capture_widget (self->search_bar, is_main_view ? GTK_WIDGET (self) : NULL);
|
|
|
|
update_list_title (self);
|
|
|
|
CC_EXIT;
|
|
}
|
|
|
|
static gboolean
|
|
set_active_panel_from_id (CcWindow *self,
|
|
const gchar *start_id,
|
|
GVariant *parameters,
|
|
gboolean add_to_history,
|
|
gboolean force_moving_to_the_panel,
|
|
GError **error)
|
|
{
|
|
g_autoptr(GIcon) gicon = NULL;
|
|
g_autofree gchar *name = NULL;
|
|
CcPanelVisibility visibility;
|
|
GtkTreeIter iter;
|
|
CcPanelListView view;
|
|
gboolean activated;
|
|
gboolean found;
|
|
|
|
CC_ENTRY;
|
|
|
|
view = cc_panel_list_get_view (self->panel_list);
|
|
|
|
/* When loading the same panel again, just set its parameters */
|
|
if (g_strcmp0 (self->current_panel_id, start_id) == 0)
|
|
{
|
|
g_object_set (G_OBJECT (self->current_panel), "parameters", parameters, NULL);
|
|
if (force_moving_to_the_panel || self->previous_list_view == view)
|
|
adw_leaflet_navigate (self->main_leaflet, ADW_NAVIGATION_DIRECTION_FORWARD);
|
|
self->previous_list_view = view;
|
|
CC_RETURN (TRUE);
|
|
}
|
|
|
|
found = find_iter_for_panel_id (self, start_id, &iter);
|
|
if (!found)
|
|
{
|
|
g_warning ("Could not find settings panel \"%s\"", start_id);
|
|
CC_RETURN (TRUE);
|
|
}
|
|
|
|
if (self->old_panel)
|
|
gtk_stack_remove (self->stack, g_steal_pointer (&self->old_panel));
|
|
|
|
/* old_panel will be removed by the on_stack_transition_running_changed_cb
|
|
* callback - or, if panels changed before the transition ended, by the code
|
|
* just above.
|
|
*/
|
|
self->old_panel = self->current_panel;
|
|
if (self->old_panel)
|
|
cc_panel_deactivate (CC_PANEL (self->old_panel));
|
|
|
|
gtk_tree_model_get (GTK_TREE_MODEL (self->store),
|
|
&iter,
|
|
COL_NAME, &name,
|
|
COL_GICON, &gicon,
|
|
COL_VISIBILITY, &visibility,
|
|
-1);
|
|
|
|
/* Activate the panel */
|
|
activated = activate_panel (self, start_id, parameters, name, gicon, visibility);
|
|
|
|
/* Failed to activate the panel for some reason, let's keep the old
|
|
* panel around instead */
|
|
if (!activated)
|
|
{
|
|
g_debug ("Failed to activate panel");
|
|
CC_RETURN (TRUE);
|
|
}
|
|
|
|
if (add_to_history)
|
|
add_current_panel_to_history (self, start_id);
|
|
|
|
if (force_moving_to_the_panel)
|
|
adw_leaflet_navigate (self->main_leaflet, ADW_NAVIGATION_DIRECTION_FORWARD);
|
|
|
|
g_free (self->current_panel_id);
|
|
self->current_panel_id = g_strdup (start_id);
|
|
|
|
CC_TRACE_MSG ("Current panel id: %s", start_id);
|
|
|
|
cc_panel_list_set_active_panel (self->panel_list, start_id);
|
|
|
|
update_headerbar_buttons (self);
|
|
|
|
CC_RETURN (TRUE);
|
|
}
|
|
|
|
static void
|
|
set_active_panel (CcWindow *self,
|
|
CcPanel *panel)
|
|
{
|
|
g_return_if_fail (CC_IS_SHELL (self));
|
|
g_return_if_fail (panel == NULL || CC_IS_PANEL (panel));
|
|
|
|
if (panel != self->active_panel)
|
|
{
|
|
/* remove the old panel */
|
|
g_clear_object (&self->active_panel);
|
|
|
|
/* set the new panel */
|
|
if (panel)
|
|
self->active_panel = g_object_ref (panel);
|
|
|
|
g_object_notify (G_OBJECT (self), "active-panel");
|
|
}
|
|
}
|
|
|
|
static void
|
|
switch_to_previous_panel (CcWindow *self)
|
|
{
|
|
g_autofree gchar *previous_panel_id = NULL;
|
|
|
|
CC_ENTRY;
|
|
|
|
if (g_queue_get_length (self->previous_panels) == 0)
|
|
CC_RETURN ();
|
|
|
|
previous_panel_id = g_queue_pop_head (self->previous_panels);
|
|
|
|
g_debug ("Going to previous panel (%s)", previous_panel_id);
|
|
|
|
set_active_panel_from_id (self, previous_panel_id, NULL, FALSE, FALSE, NULL);
|
|
|
|
CC_EXIT;
|
|
}
|
|
|
|
/* Callbacks */
|
|
|
|
static void
|
|
navigate_action_cb (GtkWidget *widget,
|
|
const gchar *action_name,
|
|
GVariant *parameter)
|
|
{
|
|
CcWindow *self = CC_WINDOW (widget);
|
|
|
|
adw_leaflet_navigate (self->main_leaflet, g_variant_get_int32 (parameter));
|
|
}
|
|
|
|
static void
|
|
on_main_leaflet_folded_changed_cb (CcWindow *self)
|
|
{
|
|
GtkSelectionMode selection_mode;
|
|
gboolean folded;
|
|
|
|
g_assert (CC_IS_WINDOW (self));
|
|
|
|
folded = adw_leaflet_get_folded (self->main_leaflet);
|
|
|
|
selection_mode = folded ? GTK_SELECTION_NONE : GTK_SELECTION_SINGLE;
|
|
cc_panel_list_set_selection_mode (self->panel_list, selection_mode);
|
|
|
|
cc_panel_set_folded (CC_PANEL (self->current_panel), folded);
|
|
}
|
|
|
|
static void
|
|
show_panel_cb (CcWindow *self,
|
|
const gchar *panel_id)
|
|
{
|
|
if (!panel_id)
|
|
return;
|
|
|
|
set_active_panel_from_id (self, panel_id, NULL, TRUE, FALSE, NULL);
|
|
}
|
|
|
|
static void
|
|
search_entry_activate_cb (CcWindow *self)
|
|
{
|
|
gboolean changed;
|
|
|
|
if (cc_panel_list_get_view (self->panel_list) != CC_PANEL_LIST_SEARCH)
|
|
return;
|
|
|
|
changed = cc_panel_list_activate (self->panel_list);
|
|
|
|
gtk_search_bar_set_search_mode (self->search_bar, !changed);
|
|
}
|
|
|
|
static void
|
|
previous_button_clicked_cb (CcWindow *self)
|
|
{
|
|
g_debug ("Num previous panels? %d", g_queue_get_length (self->previous_panels));
|
|
|
|
/* When in search, simply unset the search mode */
|
|
if (gtk_search_bar_get_search_mode (self->search_bar))
|
|
gtk_search_bar_set_search_mode (self->search_bar, FALSE);
|
|
else
|
|
cc_panel_list_go_previous (self->panel_list);
|
|
|
|
update_headerbar_buttons (self);
|
|
}
|
|
|
|
static gboolean
|
|
go_back_shortcut_cb (GtkWidget *widget,
|
|
GVariant *args,
|
|
gpointer user_data)
|
|
{
|
|
g_debug ("Going to previous panel");
|
|
switch_to_previous_panel (CC_WINDOW (widget));
|
|
|
|
return GDK_EVENT_STOP;
|
|
}
|
|
|
|
static gboolean
|
|
search_shortcut_cb (GtkWidget *widget,
|
|
GVariant *args,
|
|
gpointer user_data)
|
|
{
|
|
CcPanelListView view;
|
|
CcWindow *self;
|
|
gboolean search;
|
|
|
|
self = CC_WINDOW (widget);
|
|
view = cc_panel_list_get_view (self->panel_list);
|
|
|
|
/* The search only happens when we're in the MAIN view */
|
|
if (view != CC_PANEL_LIST_MAIN && view != CC_PANEL_LIST_SEARCH)
|
|
return GDK_EVENT_PROPAGATE;
|
|
|
|
search = !gtk_search_bar_get_search_mode (self->search_bar);
|
|
gtk_search_bar_set_search_mode (self->search_bar, search);
|
|
if (search)
|
|
gtk_widget_grab_focus (GTK_WIDGET (self->search_entry));
|
|
|
|
return GDK_EVENT_STOP;
|
|
}
|
|
|
|
static void
|
|
on_development_warning_dialog_responded_cb (CcWindow *self)
|
|
{
|
|
g_debug ("Disabling development build warning dialog");
|
|
g_settings_set_boolean (self->settings, "show-development-warning", FALSE);
|
|
|
|
gtk_window_close (GTK_WINDOW (self->development_warning_dialog));
|
|
}
|
|
|
|
static void
|
|
on_stack_transition_running_changed_cb (GtkStack *stack,
|
|
GParamSpec *pspec,
|
|
CcWindow *self)
|
|
{
|
|
gboolean transition_running;
|
|
|
|
CC_ENTRY;
|
|
|
|
transition_running = gtk_stack_get_transition_running (stack);
|
|
|
|
if (!transition_running && self->old_panel)
|
|
gtk_stack_remove (self->stack, g_steal_pointer (&self->old_panel));
|
|
|
|
CC_EXIT;
|
|
}
|
|
|
|
/* CcShell implementation */
|
|
static gboolean
|
|
cc_window_set_active_panel_from_id (CcShell *shell,
|
|
const gchar *start_id,
|
|
GVariant *parameters,
|
|
GError **error)
|
|
{
|
|
return set_active_panel_from_id (CC_WINDOW (shell), start_id, parameters, TRUE, TRUE, error);
|
|
}
|
|
|
|
static GtkWidget *
|
|
cc_window_get_toplevel (CcShell *self)
|
|
{
|
|
return GTK_WIDGET (self);
|
|
}
|
|
|
|
static void
|
|
cc_shell_iface_init (CcShellInterface *iface)
|
|
{
|
|
iface->set_active_panel_from_id = cc_window_set_active_panel_from_id;
|
|
iface->get_toplevel = cc_window_get_toplevel;
|
|
}
|
|
|
|
/* GtkWidget overrides */
|
|
static void
|
|
cc_window_map (GtkWidget *widget)
|
|
{
|
|
CcWindow *self = (CcWindow *) widget;
|
|
|
|
GTK_WIDGET_CLASS (cc_window_parent_class)->map (widget);
|
|
|
|
/* Show a warning for Flatpak builds */
|
|
if (in_flatpak_sandbox () && g_settings_get_boolean (self->settings, "show-development-warning"))
|
|
gtk_window_present (GTK_WINDOW (self->development_warning_dialog));
|
|
}
|
|
|
|
static void
|
|
cc_window_unmap (GtkWidget *widget)
|
|
{
|
|
CcWindow *self = CC_WINDOW (widget);
|
|
gboolean maximized;
|
|
gint height;
|
|
gint width;
|
|
|
|
maximized = gtk_window_is_maximized (GTK_WINDOW (self));
|
|
gtk_window_get_default_size (GTK_WINDOW (self), &width, &height);
|
|
|
|
g_settings_set (self->settings,
|
|
"window-state",
|
|
"(iib)",
|
|
width,
|
|
height,
|
|
maximized);
|
|
|
|
GTK_WIDGET_CLASS (cc_window_parent_class)->unmap (widget);
|
|
}
|
|
|
|
/* GObject Implementation */
|
|
static void
|
|
cc_window_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
CcWindow *self = CC_WINDOW (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_ACTIVE_PANEL:
|
|
g_value_set_object (value, self->active_panel);
|
|
break;
|
|
|
|
case PROP_MODEL:
|
|
g_value_set_object (value, self->store);
|
|
break;
|
|
|
|
case PROP_FOLDED:
|
|
g_value_set_boolean (value, self->folded);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
cc_window_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
CcWindow *self = CC_WINDOW (object);
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_ACTIVE_PANEL:
|
|
set_active_panel (self, g_value_get_object (value));
|
|
break;
|
|
|
|
case PROP_MODEL:
|
|
g_assert (self->store == NULL);
|
|
self->store = g_value_dup_object (value);
|
|
break;
|
|
|
|
case PROP_FOLDED:
|
|
self->folded = g_value_get_boolean (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
cc_window_constructed (GObject *object)
|
|
{
|
|
CcWindow *self = CC_WINDOW (object);
|
|
g_autofree char *id = NULL;
|
|
|
|
load_window_state (self);
|
|
|
|
/* Add the panels */
|
|
setup_model (self);
|
|
|
|
/* After everything is loaded, select the last used panel, if any,
|
|
* or the first visible panel */
|
|
id = g_settings_get_string (self->settings, "last-panel");
|
|
if (id != NULL && cc_shell_model_has_panel (self->store, id))
|
|
cc_panel_list_set_active_panel (self->panel_list, id);
|
|
else
|
|
cc_panel_list_activate (self->panel_list);
|
|
|
|
g_signal_connect_swapped (self->panel_list,
|
|
"notify::view",
|
|
G_CALLBACK (update_headerbar_buttons),
|
|
self);
|
|
|
|
update_headerbar_buttons (self);
|
|
adw_leaflet_set_visible_child (self->main_leaflet,
|
|
GTK_WIDGET (self->sidebar_box));
|
|
|
|
G_OBJECT_CLASS (cc_window_parent_class)->constructed (object);
|
|
}
|
|
|
|
static void
|
|
cc_window_dispose (GObject *object)
|
|
{
|
|
CcWindow *self = CC_WINDOW (object);
|
|
|
|
g_clear_pointer (&self->current_panel_id, g_free);
|
|
g_clear_object (&self->store);
|
|
g_clear_object (&self->active_panel);
|
|
|
|
G_OBJECT_CLASS (cc_window_parent_class)->dispose (object);
|
|
}
|
|
|
|
static void
|
|
cc_window_finalize (GObject *object)
|
|
{
|
|
CcWindow *self = CC_WINDOW (object);
|
|
|
|
if (self->previous_panels)
|
|
{
|
|
g_queue_free_full (self->previous_panels, g_free);
|
|
self->previous_panels = NULL;
|
|
}
|
|
|
|
g_clear_object (&self->settings);
|
|
|
|
G_OBJECT_CLASS (cc_window_parent_class)->finalize (object);
|
|
}
|
|
|
|
static gboolean
|
|
search_entry_key_pressed_cb (CcWindow *self,
|
|
guint keyval,
|
|
guint keycode,
|
|
GdkModifierType state,
|
|
GtkEventControllerKey *key_controller)
|
|
{
|
|
GtkWidget *toplevel;
|
|
|
|
/* When pressing Arrow Down on the entry we move focus to match results list */
|
|
if (keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down)
|
|
{
|
|
toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (self)));
|
|
|
|
if (!toplevel)
|
|
return FALSE;
|
|
|
|
return gtk_widget_child_focus (toplevel, GTK_DIR_TAB_FORWARD);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
cc_window_class_init (CcWindowClass *klass)
|
|
{
|
|
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->get_property = cc_window_get_property;
|
|
object_class->set_property = cc_window_set_property;
|
|
object_class->constructed = cc_window_constructed;
|
|
object_class->dispose = cc_window_dispose;
|
|
object_class->finalize = cc_window_finalize;
|
|
|
|
widget_class->map = cc_window_map;
|
|
widget_class->unmap = cc_window_unmap;
|
|
|
|
g_object_class_override_property (object_class, PROP_ACTIVE_PANEL, "active-panel");
|
|
|
|
g_object_class_install_property (object_class,
|
|
PROP_MODEL,
|
|
g_param_spec_object ("model",
|
|
"Model",
|
|
"The CcShellModel of this application",
|
|
CC_TYPE_SHELL_MODEL,
|
|
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
|
|
|
g_object_class_install_property (object_class,
|
|
PROP_FOLDED,
|
|
g_param_spec_boolean ("folded",
|
|
"Folded",
|
|
"Whether the window is foled",
|
|
FALSE,
|
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
|
|
|
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Settings/gtk/cc-window.ui");
|
|
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, development_warning_dialog);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, header);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, main_leaflet);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, panel_list);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, previous_button);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, search_bar);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, search_button);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, search_entry);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, sidebar_box);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, sidebar_title_widget);
|
|
gtk_widget_class_bind_template_child (widget_class, CcWindow, stack);
|
|
|
|
gtk_widget_class_bind_template_callback (widget_class, on_main_leaflet_folded_changed_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, on_development_warning_dialog_responded_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, on_stack_transition_running_changed_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, previous_button_clicked_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, search_entry_activate_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, show_panel_cb);
|
|
gtk_widget_class_bind_template_callback (widget_class, update_list_title);
|
|
gtk_widget_class_bind_template_callback (widget_class, search_entry_key_pressed_cb);
|
|
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_Left, GDK_ALT_MASK, go_back_shortcut_cb, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_f, GDK_CONTROL_MASK, search_shortcut_cb, NULL);
|
|
gtk_widget_class_add_binding (widget_class, GDK_KEY_F, GDK_CONTROL_MASK, search_shortcut_cb, NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_q, GDK_CONTROL_MASK, "window.close", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_Q, GDK_CONTROL_MASK, "window.close", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_w, GDK_CONTROL_MASK, "window.close", NULL);
|
|
gtk_widget_class_add_binding_action (widget_class, GDK_KEY_W, GDK_CONTROL_MASK, "window.close", NULL);
|
|
|
|
gtk_widget_class_install_action (widget_class, "window.navigate", "i", navigate_action_cb);
|
|
|
|
g_type_ensure (CC_TYPE_PANEL_LIST);
|
|
}
|
|
|
|
static void
|
|
cc_window_init (CcWindow *self)
|
|
{
|
|
gtk_widget_init_template (GTK_WIDGET (self));
|
|
|
|
self->settings = g_settings_new ("org.gnome.Settings");
|
|
self->previous_panels = g_queue_new ();
|
|
self->previous_list_view = cc_panel_list_get_view (self->panel_list);
|
|
|
|
g_object_bind_property (self->main_leaflet,
|
|
"folded",
|
|
self,
|
|
"folded",
|
|
G_BINDING_SYNC_CREATE);
|
|
|
|
/* Add a custom CSS class on development builds */
|
|
if (in_flatpak_sandbox ())
|
|
gtk_widget_add_css_class (GTK_WIDGET (self), "devel");
|
|
}
|
|
|
|
CcWindow *
|
|
cc_window_new (GtkApplication *application,
|
|
CcShellModel *model)
|
|
{
|
|
g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
|
|
|
|
return g_object_new (CC_TYPE_WINDOW,
|
|
"application", application,
|
|
"resizable", TRUE,
|
|
"title", _("Settings"),
|
|
"icon-name", DEFAULT_WINDOW_ICON_NAME,
|
|
"show-menubar", FALSE,
|
|
"model", model,
|
|
NULL);
|
|
}
|
|
|
|
void
|
|
cc_window_set_search_item (CcWindow *center,
|
|
const char *search)
|
|
{
|
|
gtk_search_bar_set_search_mode (center->search_bar, TRUE);
|
|
gtk_editable_set_text (GTK_EDITABLE (center->search_entry), search);
|
|
gtk_editable_set_position (GTK_EDITABLE (center->search_entry), -1);
|
|
}
|