Files
evolution/shell/e-shell-searchbar.c
Matthew Barnes 7afed37074 EShellSearchbar: Put focus on search results after changing filter.
Direct the focus away from the filter combo box so the next keyboard
event doesn't change the selected filter.  The user is probably trying
to navigate search results.
2013-08-22 10:14:44 -04:00

1601 lines
41 KiB
C

/*
* e-shell-searchbar.c
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) version 3.
*
* 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with the program; if not, see <http://www.gnu.org/licenses/>
*
*
* Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
*
*/
/**
* SECTION: e-shell-searchbar
* @short_description: quick search interface
* @include: shell/e-shell-searchbar.h
**/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include "e-shell-searchbar.h"
#include <glib/gi18n-lib.h>
#include <libebackend/libebackend.h>
#include "e-shell-window-actions.h"
#define E_SHELL_SEARCHBAR_GET_PRIVATE(obj) \
(G_TYPE_INSTANCE_GET_PRIVATE \
((obj), E_TYPE_SHELL_SEARCHBAR, EShellSearchbarPrivate))
/* spacing between "groups" on the search bar */
#define COLUMN_SPACING 24
#define SEARCH_OPTION_ADVANCED (-1)
/* Default "state key file" group: [Search Bar] */
#define STATE_GROUP_DEFAULT "Search Bar"
#define STATE_KEY_SEARCH_FILTER "SearchFilter"
#define STATE_KEY_SEARCH_OPTION "SearchOption"
#define STATE_KEY_SEARCH_SCOPE "SearchScope"
#define STATE_KEY_SEARCH_TEXT "SearchText"
struct _EShellSearchbarPrivate {
gpointer shell_view; /* weak pointer */
GtkRadioAction *search_option;
EFilterRule *search_rule;
GtkCssProvider *css_provider;
/* Child Widgets (not referenced) */
GtkWidget *filter_combo_box;
GtkWidget *search_entry;
GtkWidget *scope_combo_box;
/* Child widget containers (referenced) */
GQueue child_containers;
guint resize_idle_id;
/* State Key File */
gchar *state_group;
gboolean scope_visible;
gboolean state_dirty;
};
enum {
PROP_0,
PROP_FILTER_COMBO_BOX,
PROP_SEARCH_HINT,
PROP_SEARCH_OPTION,
PROP_SEARCH_TEXT,
PROP_SCOPE_COMBO_BOX,
PROP_SCOPE_VISIBLE,
PROP_SHELL_VIEW,
PROP_STATE_GROUP
};
G_DEFINE_TYPE_WITH_CODE (
EShellSearchbar,
e_shell_searchbar,
GTK_TYPE_GRID,
G_IMPLEMENT_INTERFACE (
E_TYPE_EXTENSIBLE, NULL))
static void
shell_searchbar_save_search_filter (EShellSearchbar *searchbar)
{
EShellView *shell_view;
EActionComboBox *action_combo_box;
GtkRadioAction *radio_action;
GKeyFile *key_file;
const gchar *action_name;
const gchar *state_group;
const gchar *key;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
state_group = e_shell_searchbar_get_state_group (searchbar);
g_return_if_fail (state_group != NULL);
key = STATE_KEY_SEARCH_FILTER;
key_file = e_shell_view_get_state_key_file (shell_view);
action_combo_box = e_shell_searchbar_get_filter_combo_box (searchbar);
radio_action = e_action_combo_box_get_action (action_combo_box);
if (radio_action != NULL)
radio_action = e_radio_action_get_current_action (radio_action);
if (radio_action != NULL) {
action_name = gtk_action_get_name (GTK_ACTION (radio_action));
g_key_file_set_string (key_file, state_group, key, action_name);
} else
g_key_file_remove_key (key_file, state_group, key, NULL);
e_shell_view_set_state_dirty (shell_view);
}
static void
shell_searchbar_save_search_option (EShellSearchbar *searchbar)
{
EShellView *shell_view;
GtkRadioAction *radio_action;
GKeyFile *key_file;
const gchar *action_name;
const gchar *state_group;
const gchar *key;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
state_group = e_shell_searchbar_get_state_group (searchbar);
g_return_if_fail (state_group != NULL);
key = STATE_KEY_SEARCH_OPTION;
key_file = e_shell_view_get_state_key_file (shell_view);
radio_action = e_shell_searchbar_get_search_option (searchbar);
if (radio_action != NULL)
radio_action = e_radio_action_get_current_action (radio_action);
if (radio_action != NULL) {
action_name = gtk_action_get_name (GTK_ACTION (radio_action));
g_key_file_set_string (key_file, state_group, key, action_name);
} else
g_key_file_remove_key (key_file, state_group, key, NULL);
e_shell_view_set_state_dirty (shell_view);
}
static void
shell_searchbar_save_search_text (EShellSearchbar *searchbar)
{
EShellView *shell_view;
GKeyFile *key_file;
const gchar *search_text;
const gchar *state_group;
const gchar *key;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
state_group = e_shell_searchbar_get_state_group (searchbar);
g_return_if_fail (state_group != NULL);
key = STATE_KEY_SEARCH_TEXT;
key_file = e_shell_view_get_state_key_file (shell_view);
search_text = e_shell_searchbar_get_search_text (searchbar);
if (search_text != NULL && *search_text != '\0')
g_key_file_set_string (key_file, state_group, key, search_text);
else
g_key_file_remove_key (key_file, state_group, key, NULL);
e_shell_view_set_state_dirty (shell_view);
}
static void
shell_searchbar_save_search_scope (EShellSearchbar *searchbar)
{
EShellView *shell_view;
EActionComboBox *action_combo_box;
GtkRadioAction *radio_action;
GKeyFile *key_file;
const gchar *action_name;
const gchar *state_group;
const gchar *key;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
/* Search scope is hard-coded to the default state group. */
state_group = STATE_GROUP_DEFAULT;
key = STATE_KEY_SEARCH_SCOPE;
key_file = e_shell_view_get_state_key_file (shell_view);
action_combo_box = e_shell_searchbar_get_scope_combo_box (searchbar);
radio_action = e_action_combo_box_get_action (action_combo_box);
if (radio_action != NULL)
radio_action = e_radio_action_get_current_action (radio_action);
if (radio_action != NULL) {
action_name = gtk_action_get_name (GTK_ACTION (radio_action));
g_key_file_set_string (key_file, state_group, key, action_name);
} else
g_key_file_remove_key (key_file, state_group, key, NULL);
e_shell_view_set_state_dirty (shell_view);
}
static void
shell_searchbar_update_search_widgets (EShellSearchbar *searchbar)
{
EShellView *shell_view;
EShellWindow *shell_window;
GtkAction *action;
GtkWidget *widget;
const gchar *search_text;
gboolean sensitive;
/* EShellView subclasses are responsible for actually
* executing the search. This is all cosmetic stuff. */
widget = searchbar->priv->search_entry;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
shell_window = e_shell_view_get_shell_window (shell_view);
search_text = e_shell_searchbar_get_search_text (searchbar);
sensitive =
(search_text != NULL && *search_text != '\0') ||
(e_shell_view_get_search_rule (shell_view) != NULL);
if (sensitive) {
GtkStyleContext *style;
GdkRGBA bg, fg;
gchar *css;
style = gtk_widget_get_style_context (widget);
gtk_style_context_get_background_color (
style, GTK_STATE_FLAG_SELECTED, &bg);
gtk_style_context_get_color (
style, GTK_STATE_FLAG_SELECTED, &fg);
css = g_strdup_printf (
"GtkEntry#searchbar_searchentry_active { "
" background:none; "
" background-color:#%06x; "
" color:#%06x; "
"}",
e_rgba_to_value (&bg),
e_rgba_to_value (&fg));
gtk_css_provider_load_from_data (
searchbar->priv->css_provider, css, -1, NULL);
g_free (css);
gtk_widget_set_name (widget, "searchbar_searchentry_active");
} else {
gtk_widget_set_name (widget, "searchbar_searchentry");
}
action = E_SHELL_WINDOW_ACTION_SEARCH_CLEAR (shell_window);
gtk_action_set_sensitive (action, sensitive);
action = E_SHELL_WINDOW_ACTION_SEARCH_SAVE (shell_window);
gtk_action_set_sensitive (action, sensitive);
}
static void
shell_searchbar_clear_search_cb (EShellView *shell_view,
EShellSearchbar *searchbar)
{
GtkRadioAction *search_option;
gint current_value;
e_shell_searchbar_set_search_text (searchbar, NULL);
search_option = e_shell_searchbar_get_search_option (searchbar);
if (search_option == NULL)
return;
/* Reset the search option if it's set to advanced search. */
current_value = gtk_radio_action_get_current_value (search_option);
if (current_value == SEARCH_OPTION_ADVANCED)
gtk_radio_action_set_current_value (search_option, 0);
}
static void
shell_searchbar_custom_search_cb (EShellView *shell_view,
EFilterRule *custom_rule,
EShellSearchbar *searchbar)
{
GtkRadioAction *search_option;
gint value = SEARCH_OPTION_ADVANCED;
e_shell_searchbar_set_search_text (searchbar, NULL);
search_option = e_shell_searchbar_get_search_option (searchbar);
if (search_option != NULL)
gtk_radio_action_set_current_value (search_option, value);
}
static void
shell_searchbar_execute_search_cb (EShellView *shell_view,
EShellSearchbar *searchbar)
{
EShellContent *shell_content;
shell_searchbar_update_search_widgets (searchbar);
e_shell_searchbar_save_state (searchbar);
if (!e_shell_view_is_active (shell_view))
return;
/* Direct the focus away from the search entry, so that a
* focus-in event is required before the text can be changed.
* This will reset the entry to the appropriate visual state. */
if (gtk_widget_is_focus (searchbar->priv->search_entry)) {
shell_content = e_shell_view_get_shell_content (shell_view);
e_shell_content_focus_search_results (shell_content);
}
}
static void
shell_searchbar_filter_changed_cb (GtkComboBox *filter_combo_box,
EShellSearchbar *searchbar)
{
EShellView *shell_view;
EShellContent *shell_content;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
e_shell_view_execute_search (shell_view);
/* Direct the focus away from the filter combo box so the
* next keyboard event doesn't change the selected filter.
* The user is probably trying to navigate search results. */
shell_content = e_shell_view_get_shell_content (shell_view);
e_shell_content_focus_search_results (shell_content);
}
static void
shell_searchbar_entry_activate_cb (EShellSearchbar *searchbar)
{
EShellView *shell_view;
EShellWindow *shell_window;
GtkAction *action;
const gchar *search_text;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
shell_window = e_shell_view_get_shell_window (shell_view);
search_text = e_shell_searchbar_get_search_text (searchbar);
if (search_text != NULL && *search_text != '\0')
action = E_SHELL_WINDOW_ACTION_SEARCH_QUICK (shell_window);
else
action = E_SHELL_WINDOW_ACTION_SEARCH_CLEAR (shell_window);
gtk_action_activate (action);
}
static void
shell_searchbar_entry_changed_cb (EShellSearchbar *searchbar)
{
EShellView *shell_view;
EShellWindow *shell_window;
GtkAction *action;
const gchar *search_text;
gboolean sensitive;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
shell_window = e_shell_view_get_shell_window (shell_view);
search_text = e_shell_searchbar_get_search_text (searchbar);
sensitive = (search_text != NULL && *search_text != '\0');
action = E_SHELL_WINDOW_ACTION_SEARCH_QUICK (shell_window);
gtk_action_set_sensitive (action, sensitive);
}
static void
shell_searchbar_entry_icon_press_cb (EShellSearchbar *searchbar,
GtkEntryIconPosition icon_pos,
GdkEvent *event)
{
EShellView *shell_view;
EShellWindow *shell_window;
GtkAction *action;
/* Show the search options menu when the icon is pressed. */
if (icon_pos != GTK_ENTRY_ICON_PRIMARY)
return;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
shell_window = e_shell_view_get_shell_window (shell_view);
action = E_SHELL_WINDOW_ACTION_SEARCH_OPTIONS (shell_window);
gtk_action_activate (action);
}
static void
shell_searchbar_entry_icon_release_cb (EShellSearchbar *searchbar,
GtkEntryIconPosition icon_pos,
GdkEvent *event)
{
EShellView *shell_view;
EShellWindow *shell_window;
GtkAction *action;
/* Clear the search when the icon is pressed and released. */
if (icon_pos != GTK_ENTRY_ICON_SECONDARY)
return;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
shell_window = e_shell_view_get_shell_window (shell_view);
action = E_SHELL_WINDOW_ACTION_SEARCH_CLEAR (shell_window);
gtk_action_activate (action);
}
static gboolean
shell_searchbar_entry_key_press_cb (EShellSearchbar *searchbar,
GdkEventKey *key_event,
GtkWindow *entry)
{
EShellView *shell_view;
EShellWindow *shell_window;
GtkAction *action;
guint mask;
mask = gtk_accelerator_get_default_mod_mask ();
if ((key_event->state & mask) != GDK_MOD1_MASK)
return FALSE;
if (key_event->keyval != GDK_KEY_Down)
return FALSE;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
shell_window = e_shell_view_get_shell_window (shell_view);
action = E_SHELL_WINDOW_ACTION_SEARCH_OPTIONS (shell_window);
gtk_action_activate (action);
return TRUE;
}
static void
shell_searchbar_option_changed_cb (GtkRadioAction *action,
GtkRadioAction *current,
EShellSearchbar *searchbar)
{
EShellView *shell_view;
const gchar *search_text;
const gchar *label;
gint current_value;
shell_view = e_shell_searchbar_get_shell_view (searchbar);
label = gtk_action_get_label (GTK_ACTION (current));
e_shell_searchbar_set_search_hint (searchbar, label);
current_value = gtk_radio_action_get_current_value (current);
search_text = e_shell_searchbar_get_search_text (searchbar);
if (current_value != SEARCH_OPTION_ADVANCED) {
e_shell_view_set_search_rule (shell_view, NULL);
e_shell_searchbar_set_search_text (searchbar, search_text);
if (search_text != NULL && *search_text != '\0') {
e_shell_view_execute_search (shell_view);
} else {
shell_searchbar_save_search_option (searchbar);
gtk_widget_grab_focus (searchbar->priv->search_entry);
}
} else if (search_text != NULL)
e_shell_searchbar_set_search_text (searchbar, NULL);
}
static gboolean
shell_searchbar_resize_idle_cb (gpointer user_data)
{
GtkWidget *widget;
EShellSearchbar *searchbar;
GQueue *child_containers;
GList *head, *link;
GArray *widths;
gint row = 0;
gint column = 0;
gint roww = 0;
gint maxw = 0;
gint child_left;
gint child_top;
gint allocated_width;
gboolean needs_reposition = FALSE;
widget = GTK_WIDGET (user_data);
allocated_width = gtk_widget_get_allocated_width (widget);
searchbar = E_SHELL_SEARCHBAR (widget);
child_containers = &searchbar->priv->child_containers;
head = g_queue_peek_head_link (child_containers);
widths = g_array_new (FALSE, FALSE, sizeof (gint));
for (link = head; link != NULL; link = g_list_next (link)) {
GtkWidget *child = GTK_WIDGET (link->data);
gint minw = -1;
if (!gtk_widget_get_visible (child))
minw = 0;
else
gtk_widget_get_preferred_width (child, &minw, NULL);
g_array_append_val (widths, minw);
if (roww && minw) {
roww += COLUMN_SPACING;
column++;
}
roww += minw;
if (minw > maxw)
maxw = minw;
if (roww > allocated_width) {
row++;
roww = minw;
column = 0;
}
gtk_container_child_get (
GTK_CONTAINER (widget), child,
"left-attach", &child_left,
"top-attach", &child_top,
NULL);
needs_reposition |=
(child_left != column) ||
(child_top != row);
if (column == 0 && row > 0 && roww < maxw) {
/* Columns have the same width, so use
* the wider widget for calculations. */
roww = maxw;
}
}
if (needs_reposition) {
guint ii = 0;
row = 0;
column = 0;
roww = 0;
g_warn_if_fail (child_containers->length == widths->len);
for (link = head; link != NULL; link = g_list_next (link))
gtk_container_remove (
GTK_CONTAINER (widget),
GTK_WIDGET (link->data));
for (link = head; link != NULL; link = g_list_next (link)) {
GtkWidget *child;
gint w;
child = GTK_WIDGET (link->data);
w = g_array_index (widths, gint, ii++);
if (roww && w) {
roww += COLUMN_SPACING;
column++;
}
roww += w;
if (roww > allocated_width) {
row++;
roww = w;
column = 0;
}
gtk_grid_attach (
GTK_GRID (widget), child, column, row, 1, 1);
if (column == 0 && row > 0 && roww < maxw)
roww = maxw;
}
}
g_array_free (widths, TRUE);
searchbar->priv->resize_idle_id = 0;
return FALSE;
}
static gboolean
shell_searchbar_entry_focus_in_cb (GtkWidget *entry,
GdkEvent *event,
EShellSearchbar *searchbar)
{
/* to not change background when user changes search entry content */
gtk_widget_set_name (entry, "searchbar_searchentry");
return FALSE;
}
static gboolean
shell_searchbar_entry_focus_out_cb (GtkWidget *entry,
GdkEvent *event,
EShellSearchbar *searchbar)
{
shell_searchbar_update_search_widgets (searchbar);
return FALSE;
}
static void
shell_searchbar_set_shell_view (EShellSearchbar *searchbar,
EShellView *shell_view)
{
g_return_if_fail (searchbar->priv->shell_view == NULL);
searchbar->priv->shell_view = shell_view;
g_object_add_weak_pointer (
G_OBJECT (shell_view),
&searchbar->priv->shell_view);
}
static void
shell_searchbar_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_SEARCH_HINT:
e_shell_searchbar_set_search_hint (
E_SHELL_SEARCHBAR (object),
g_value_get_string (value));
return;
case PROP_SEARCH_OPTION:
e_shell_searchbar_set_search_option (
E_SHELL_SEARCHBAR (object),
g_value_get_object (value));
return;
case PROP_SEARCH_TEXT:
e_shell_searchbar_set_search_text (
E_SHELL_SEARCHBAR (object),
g_value_get_string (value));
return;
case PROP_SCOPE_VISIBLE:
e_shell_searchbar_set_scope_visible (
E_SHELL_SEARCHBAR (object),
g_value_get_boolean (value));
return;
case PROP_SHELL_VIEW:
shell_searchbar_set_shell_view (
E_SHELL_SEARCHBAR (object),
g_value_get_object (value));
return;
case PROP_STATE_GROUP:
e_shell_searchbar_set_state_group (
E_SHELL_SEARCHBAR (object),
g_value_get_string (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
shell_searchbar_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FILTER_COMBO_BOX:
g_value_set_object (
value, e_shell_searchbar_get_filter_combo_box (
E_SHELL_SEARCHBAR (object)));
return;
case PROP_SEARCH_HINT:
g_value_set_string (
value, e_shell_searchbar_get_search_hint (
E_SHELL_SEARCHBAR (object)));
return;
case PROP_SEARCH_OPTION:
g_value_set_object (
value, e_shell_searchbar_get_search_option (
E_SHELL_SEARCHBAR (object)));
return;
case PROP_SEARCH_TEXT:
g_value_set_string (
value, e_shell_searchbar_get_search_text (
E_SHELL_SEARCHBAR (object)));
return;
case PROP_SCOPE_COMBO_BOX:
g_value_set_object (
value, e_shell_searchbar_get_scope_combo_box (
E_SHELL_SEARCHBAR (object)));
return;
case PROP_SCOPE_VISIBLE:
g_value_set_boolean (
value, e_shell_searchbar_get_scope_visible (
E_SHELL_SEARCHBAR (object)));
return;
case PROP_SHELL_VIEW:
g_value_set_object (
value, e_shell_searchbar_get_shell_view (
E_SHELL_SEARCHBAR (object)));
return;
case PROP_STATE_GROUP:
g_value_set_string (
value, e_shell_searchbar_get_state_group (
E_SHELL_SEARCHBAR (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
shell_searchbar_dispose (GObject *object)
{
EShellSearchbarPrivate *priv;
priv = E_SHELL_SEARCHBAR_GET_PRIVATE (object);
if (priv->resize_idle_id > 0) {
g_source_remove (priv->resize_idle_id);
priv->resize_idle_id = 0;
}
if (priv->shell_view != NULL) {
g_object_remove_weak_pointer (
G_OBJECT (priv->shell_view), &priv->shell_view);
priv->shell_view = NULL;
}
if (priv->search_option != NULL) {
g_signal_handlers_disconnect_matched (
priv->search_option, G_SIGNAL_MATCH_DATA,
0, 0, NULL, NULL, object);
g_clear_object (&priv->search_option);
}
g_clear_object (&priv->css_provider);
while (!g_queue_is_empty (&priv->child_containers))
g_object_unref (g_queue_pop_head (&priv->child_containers));
/* Chain up to parent's dispose() method. */
G_OBJECT_CLASS (e_shell_searchbar_parent_class)->dispose (object);
}
static void
shell_searchbar_finalize (GObject *object)
{
EShellSearchbarPrivate *priv;
priv = E_SHELL_SEARCHBAR_GET_PRIVATE (object);
g_free (priv->state_group);
/* Chain up to parent's finalize() method. */
G_OBJECT_CLASS (e_shell_searchbar_parent_class)->finalize (object);
}
static void
shell_searchbar_constructed (GObject *object)
{
EShellView *shell_view;
EShellWindow *shell_window;
EShellSearchbar *searchbar;
GtkSizeGroup *size_group;
GtkAction *action;
GtkWidget *widget;
searchbar = E_SHELL_SEARCHBAR (object);
shell_view = e_shell_searchbar_get_shell_view (searchbar);
shell_window = e_shell_view_get_shell_window (shell_view);
size_group = e_shell_view_get_size_group (shell_view);
g_signal_connect (
shell_view, "clear-search",
G_CALLBACK (shell_searchbar_clear_search_cb), searchbar);
g_signal_connect (
shell_view, "custom-search",
G_CALLBACK (shell_searchbar_custom_search_cb), searchbar);
g_signal_connect_after (
shell_view, "execute-search",
G_CALLBACK (shell_searchbar_execute_search_cb), searchbar);
widget = searchbar->priv->filter_combo_box;
g_signal_connect_swapped (
widget, "changed",
G_CALLBACK (e_shell_searchbar_set_state_dirty), searchbar);
/* Use G_CONNECT_AFTER here so the EActionComboBox has a
* chance to update its radio actions before we go sifting
* through the radio group for the current action. */
g_signal_connect_after (
widget, "changed",
G_CALLBACK (shell_searchbar_filter_changed_cb), searchbar);
searchbar->priv->css_provider = gtk_css_provider_new ();
widget = searchbar->priv->search_entry;
gtk_style_context_add_provider (
gtk_widget_get_style_context (widget),
GTK_STYLE_PROVIDER (searchbar->priv->css_provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
action = E_SHELL_WINDOW_ACTION_SEARCH_CLEAR (shell_window);
g_object_bind_property (
action, "sensitive",
widget, "secondary-icon-sensitive",
G_BINDING_SYNC_CREATE);
g_object_bind_property (
action, "stock-id",
widget, "secondary-icon-stock",
G_BINDING_SYNC_CREATE);
g_object_bind_property (
action, "tooltip",
widget, "secondary-icon-tooltip-text",
G_BINDING_SYNC_CREATE);
action = E_SHELL_WINDOW_ACTION_SEARCH_OPTIONS (shell_window);
g_object_bind_property (
action, "sensitive",
widget, "primary-icon-sensitive",
G_BINDING_SYNC_CREATE);
g_object_bind_property (
action, "stock-id",
widget, "primary-icon-stock",
G_BINDING_SYNC_CREATE);
g_object_bind_property (
action, "tooltip",
widget, "primary-icon-tooltip-text",
G_BINDING_SYNC_CREATE);
widget = GTK_WIDGET (searchbar);
gtk_size_group_add_widget (size_group, widget);
e_extensible_load_extensions (E_EXTENSIBLE (object));
/* Chain up to parent's constructed() method. */
G_OBJECT_CLASS (e_shell_searchbar_parent_class)->constructed (object);
}
static void
shell_searchbar_map (GtkWidget *widget)
{
/* Chain up to parent's map() method. */
GTK_WIDGET_CLASS (e_shell_searchbar_parent_class)->map (widget);
/* Load state after constructed() so we don't derail
* subclass initialization. We wait until map() so we
* have usable style colors for the entry box. */
e_shell_searchbar_load_state (E_SHELL_SEARCHBAR (widget));
}
static void
shell_searchbar_size_allocate (GtkWidget *widget,
GdkRectangle *allocation)
{
EShellSearchbarPrivate *priv;
priv = E_SHELL_SEARCHBAR_GET_PRIVATE (widget);
/* Chain up to parent's size_allocate() method. */
GTK_WIDGET_CLASS (e_shell_searchbar_parent_class)->
size_allocate (widget, allocation);
if (priv->resize_idle_id == 0)
priv->resize_idle_id = g_idle_add (
shell_searchbar_resize_idle_cb, widget);
}
static void
shell_searchbar_get_preferred_width (GtkWidget *widget,
gint *minimum_width,
gint *natural_width)
{
GList *children, *iter;
gint max_minimum = 0, max_natural = 0;
children = gtk_container_get_children (GTK_CONTAINER (widget));
for (iter = children; iter != NULL; iter = iter->next) {
GtkWidget *child = iter->data;
gint minimum = 0, natural = 0;
if (gtk_widget_get_visible (child)) {
gtk_widget_get_preferred_width (child, &minimum, &natural);
if (minimum > max_minimum)
max_minimum = minimum;
if (natural > max_natural)
max_natural = natural;
}
}
g_list_free (children);
*minimum_width = max_minimum + COLUMN_SPACING;
*natural_width = max_natural + COLUMN_SPACING;
}
static void
e_shell_searchbar_class_init (EShellSearchbarClass *class)
{
GObjectClass *object_class;
GtkWidgetClass *widget_class;
g_type_class_add_private (class, sizeof (EShellSearchbarPrivate));
object_class = G_OBJECT_CLASS (class);
object_class->set_property = shell_searchbar_set_property;
object_class->get_property = shell_searchbar_get_property;
object_class->dispose = shell_searchbar_dispose;
object_class->finalize = shell_searchbar_finalize;
object_class->constructed = shell_searchbar_constructed;
widget_class = GTK_WIDGET_CLASS (class);
widget_class->map = shell_searchbar_map;
widget_class->size_allocate = shell_searchbar_size_allocate;
widget_class->get_preferred_width = shell_searchbar_get_preferred_width;
g_object_class_install_property (
object_class,
PROP_FILTER_COMBO_BOX,
g_param_spec_object (
"filter-combo-box",
NULL,
NULL,
E_TYPE_ACTION_COMBO_BOX,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_SEARCH_HINT,
g_param_spec_string (
"search-hint",
NULL,
NULL,
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_SEARCH_OPTION,
g_param_spec_object (
"search-option",
NULL,
NULL,
GTK_TYPE_RADIO_ACTION,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_SEARCH_TEXT,
g_param_spec_string (
"search-text",
NULL,
NULL,
NULL,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_SCOPE_COMBO_BOX,
g_param_spec_object (
"scope-combo-box",
NULL,
NULL,
E_TYPE_ACTION_COMBO_BOX,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_SCOPE_VISIBLE,
g_param_spec_boolean (
"scope-visible",
NULL,
NULL,
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
/**
* EShellSearchbar:shell-view
*
* The #EShellView to which the searchbar widget belongs.
**/
g_object_class_install_property (
object_class,
PROP_SHELL_VIEW,
g_param_spec_object (
"shell-view",
NULL,
NULL,
E_TYPE_SHELL_VIEW,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS));
/**
* EShellSearchbar:state-group
*
* Key file group name to read and write search bar state.
**/
g_object_class_install_property (
object_class,
PROP_STATE_GROUP,
g_param_spec_string (
"state-group",
NULL,
NULL,
STATE_GROUP_DEFAULT,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_STATIC_STRINGS));
}
static void
e_shell_searchbar_init (EShellSearchbar *searchbar)
{
GtkGrid *grid;
GtkLabel *label;
GtkWidget *widget;
GQueue *child_containers;
searchbar->priv = E_SHELL_SEARCHBAR_GET_PRIVATE (searchbar);
child_containers = &searchbar->priv->child_containers;
gtk_grid_set_column_spacing (GTK_GRID (searchbar), COLUMN_SPACING);
gtk_grid_set_row_spacing (GTK_GRID (searchbar), 4);
/* Filter Combo Widgets */
grid = GTK_GRID (searchbar);
widget = gtk_grid_new ();
g_object_set (
G_OBJECT (widget),
"orientation", GTK_ORIENTATION_HORIZONTAL,
"border-width", 3,
"column-spacing", 3,
"valign", GTK_ALIGN_CENTER,
NULL);
gtk_grid_attach (grid, widget, 0, 0, 1, 1);
gtk_widget_show (widget);
g_queue_push_tail (child_containers, g_object_ref (widget));
grid = GTK_GRID (widget);
/* Translators: The "Show:" label precedes a combo box that
* allows the user to filter the current view. Examples of
* items that appear in the combo box are "Unread Messages",
* "Important Messages", or "Active Appointments". */
widget = gtk_label_new_with_mnemonic (_("Sho_w:"));
gtk_grid_attach (grid, widget, 0, 0, 1, 1);
gtk_widget_show (widget);
label = GTK_LABEL (widget);
widget = e_action_combo_box_new ();
gtk_label_set_mnemonic_widget (label, widget);
gtk_grid_attach (grid, widget, 1, 0, 1, 1);
searchbar->priv->filter_combo_box = widget;
gtk_widget_show (widget);
/* Search Entry Widgets */
grid = GTK_GRID (searchbar);
widget = gtk_grid_new ();
g_object_set (
G_OBJECT (widget),
"orientation", GTK_ORIENTATION_HORIZONTAL,
"column-spacing", 3,
"valign", GTK_ALIGN_CENTER,
"halign", GTK_ALIGN_FILL,
"hexpand", TRUE,
NULL);
gtk_grid_attach (grid, widget, 1, 0, 1, 1);
gtk_widget_show (widget);
g_queue_push_tail (child_containers, g_object_ref (widget));
grid = GTK_GRID (widget);
/* Translators: This is part of the quick search interface.
* example: Search: [_______________] in [ Current Folder ] */
widget = gtk_label_new_with_mnemonic (_("Sear_ch:"));
gtk_grid_attach (grid, widget, 0, 0, 1, 1);
gtk_widget_show (widget);
label = GTK_LABEL (widget);
widget = gtk_entry_new ();
gtk_label_set_mnemonic_widget (label, widget);
g_object_set (
G_OBJECT (widget),
"halign", GTK_ALIGN_FILL,
"hexpand", TRUE,
NULL);
gtk_grid_attach (grid, widget, 1, 0, 1, 1);
searchbar->priv->search_entry = widget;
gtk_widget_show (widget);
g_signal_connect_swapped (
widget, "activate",
G_CALLBACK (shell_searchbar_entry_activate_cb),
searchbar);
g_signal_connect_swapped (
widget, "changed",
G_CALLBACK (shell_searchbar_entry_changed_cb),
searchbar);
g_signal_connect_swapped (
widget, "changed",
G_CALLBACK (e_shell_searchbar_set_state_dirty),
searchbar);
g_signal_connect_swapped (
widget, "icon-press",
G_CALLBACK (shell_searchbar_entry_icon_press_cb),
searchbar);
g_signal_connect_swapped (
widget, "icon-release",
G_CALLBACK (shell_searchbar_entry_icon_release_cb),
searchbar);
g_signal_connect_swapped (
widget, "key-press-event",
G_CALLBACK (shell_searchbar_entry_key_press_cb),
searchbar);
g_signal_connect (
widget, "focus-in-event",
G_CALLBACK (shell_searchbar_entry_focus_in_cb),
searchbar);
g_signal_connect (
widget, "focus-out-event",
G_CALLBACK (shell_searchbar_entry_focus_out_cb),
searchbar);
/* Scope Combo Widgets */
grid = GTK_GRID (searchbar);
widget = gtk_grid_new ();
g_object_set (
G_OBJECT (widget),
"orientation", GTK_ORIENTATION_HORIZONTAL,
"column-spacing", 3,
"valign", GTK_ALIGN_CENTER,
NULL);
gtk_grid_attach (grid, widget, 2, 0, 1, 1);
g_queue_push_tail (child_containers, g_object_ref (widget));
g_object_bind_property (
searchbar, "scope-visible",
widget, "visible",
G_BINDING_SYNC_CREATE);
grid = GTK_GRID (widget);
/* Translators: This is part of the quick search interface.
* example: Search: [_______________] in [ Current Folder ] */
widget = gtk_label_new_with_mnemonic (_("i_n"));
gtk_grid_attach (grid, widget, 0, 0, 1, 1);
gtk_widget_show (widget);
label = GTK_LABEL (widget);
widget = e_action_combo_box_new ();
gtk_label_set_mnemonic_widget (label, widget);
gtk_grid_attach (grid, widget, 1, 0, 1, 1);
searchbar->priv->scope_combo_box = widget;
gtk_widget_show (widget);
/* Use G_CONNECT_AFTER here so the EActionComboBox has a
* chance to update its radio actions before we go sifting
* through the radio group for the current action. */
g_signal_connect_data (
widget, "changed",
G_CALLBACK (shell_searchbar_save_search_scope),
searchbar, (GClosureNotify) NULL,
G_CONNECT_AFTER | G_CONNECT_SWAPPED);
}
/**
* e_shell_searchbar_new:
* @shell_view: an #EShellView
*
* Creates a new #EShellSearchbar instance.
*
* Returns: a new #EShellSearchbar instance
**/
GtkWidget *
e_shell_searchbar_new (EShellView *shell_view)
{
g_return_val_if_fail (E_IS_SHELL_VIEW (shell_view), NULL);
return g_object_new (
E_TYPE_SHELL_SEARCHBAR,
"shell-view", shell_view,
"orientation", GTK_ORIENTATION_HORIZONTAL,
NULL);
}
/**
* e_shell_searchbar_get_shell_view:
* @searchbar: an #EShellSearchbar
*
* Returns the #EShellView that was passed to e_shell_searchbar_new().
*
* Returns: the #EShellView to which @searchbar belongs
**/
EShellView *
e_shell_searchbar_get_shell_view (EShellSearchbar *searchbar)
{
g_return_val_if_fail (E_IS_SHELL_SEARCHBAR (searchbar), NULL);
return E_SHELL_VIEW (searchbar->priv->shell_view);
}
EActionComboBox *
e_shell_searchbar_get_filter_combo_box (EShellSearchbar *searchbar)
{
g_return_val_if_fail (E_IS_SHELL_SEARCHBAR (searchbar), NULL);
return E_ACTION_COMBO_BOX (searchbar->priv->filter_combo_box);
}
const gchar *
e_shell_searchbar_get_search_hint (EShellSearchbar *searchbar)
{
GtkEntry *entry;
g_return_val_if_fail (E_IS_SHELL_SEARCHBAR (searchbar), NULL);
entry = GTK_ENTRY (searchbar->priv->search_entry);
return gtk_entry_get_placeholder_text (entry);
}
void
e_shell_searchbar_set_search_hint (EShellSearchbar *searchbar,
const gchar *search_hint)
{
GtkEntry *entry;
g_return_if_fail (E_IS_SHELL_SEARCHBAR (searchbar));
entry = GTK_ENTRY (searchbar->priv->search_entry);
if (g_strcmp0 (gtk_entry_get_placeholder_text (entry), search_hint) == 0)
return;
gtk_entry_set_placeholder_text (entry, search_hint);
g_object_notify (G_OBJECT (searchbar), "search-hint");
}
GtkRadioAction *
e_shell_searchbar_get_search_option (EShellSearchbar *searchbar)
{
g_return_val_if_fail (E_IS_SHELL_SEARCHBAR (searchbar), NULL);
return searchbar->priv->search_option;
}
void
e_shell_searchbar_set_search_option (EShellSearchbar *searchbar,
GtkRadioAction *search_option)
{
g_return_if_fail (E_IS_SHELL_SEARCHBAR (searchbar));
if (searchbar->priv->search_option == search_option)
return;
if (search_option != NULL) {
g_return_if_fail (GTK_IS_RADIO_ACTION (search_option));
g_object_ref (search_option);
}
if (searchbar->priv->search_option != NULL) {
g_signal_handlers_disconnect_matched (
searchbar->priv->search_option,
G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL,
searchbar);
g_object_unref (searchbar->priv->search_option);
}
searchbar->priv->search_option = search_option;
if (search_option != NULL)
g_signal_connect (
search_option, "changed",
G_CALLBACK (shell_searchbar_option_changed_cb),
searchbar);
g_object_notify (G_OBJECT (searchbar), "search-option");
}
const gchar *
e_shell_searchbar_get_search_text (EShellSearchbar *searchbar)
{
GtkEntry *entry;
g_return_val_if_fail (E_IS_SHELL_SEARCHBAR (searchbar), NULL);
entry = GTK_ENTRY (searchbar->priv->search_entry);
return gtk_entry_get_text (entry);
}
void
e_shell_searchbar_set_search_text (EShellSearchbar *searchbar,
const gchar *search_text)
{
GtkEntry *entry;
g_return_if_fail (E_IS_SHELL_SEARCHBAR (searchbar));
entry = GTK_ENTRY (searchbar->priv->search_entry);
/* XXX Really wish gtk_entry_set_text()
* would just learn to accept NULL. */
if (search_text == NULL)
search_text = "";
if (g_strcmp0 (gtk_entry_get_text (entry), search_text) == 0)
return;
gtk_entry_set_text (entry, search_text);
shell_searchbar_update_search_widgets (searchbar);
g_object_notify (G_OBJECT (searchbar), "search-text");
}
GtkWidget *
e_shell_searchbar_get_search_box (EShellSearchbar *searchbar)
{
g_return_val_if_fail (searchbar != NULL, NULL);
g_return_val_if_fail (searchbar->priv != NULL, NULL);
g_return_val_if_fail (searchbar->priv->search_entry != NULL, NULL);
return gtk_widget_get_parent (searchbar->priv->search_entry);
}
EActionComboBox *
e_shell_searchbar_get_scope_combo_box (EShellSearchbar *searchbar)
{
g_return_val_if_fail (E_IS_SHELL_SEARCHBAR (searchbar), NULL);
return E_ACTION_COMBO_BOX (searchbar->priv->scope_combo_box);
}
gboolean
e_shell_searchbar_get_scope_visible (EShellSearchbar *searchbar)
{
g_return_val_if_fail (E_IS_SHELL_SEARCHBAR (searchbar), FALSE);
return searchbar->priv->scope_visible;
}
void
e_shell_searchbar_set_scope_visible (EShellSearchbar *searchbar,
gboolean scope_visible)
{
g_return_if_fail (E_IS_SHELL_SEARCHBAR (searchbar));
if (searchbar->priv->scope_visible == scope_visible)
return;
searchbar->priv->scope_visible = scope_visible;
g_object_notify (G_OBJECT (searchbar), "scope-visible");
}
void
e_shell_searchbar_set_state_dirty (EShellSearchbar *searchbar)
{
g_return_if_fail (E_IS_SHELL_SEARCHBAR (searchbar));
searchbar->priv->state_dirty = TRUE;
}
const gchar *
e_shell_searchbar_get_state_group (EShellSearchbar *searchbar)
{
g_return_val_if_fail (E_IS_SHELL_SEARCHBAR (searchbar), NULL);
return searchbar->priv->state_group;
}
void
e_shell_searchbar_set_state_group (EShellSearchbar *searchbar,
const gchar *state_group)
{
g_return_if_fail (E_IS_SHELL_SEARCHBAR (searchbar));
if (state_group == NULL)
state_group = STATE_GROUP_DEFAULT;
if (g_strcmp0 (searchbar->priv->state_group, state_group) == 0)
return;
g_free (searchbar->priv->state_group);
searchbar->priv->state_group = g_strdup (state_group);
g_object_notify (G_OBJECT (searchbar), "state-group");
}
static gboolean
idle_execute_search (gpointer shell_view)
{
e_shell_view_execute_search (shell_view);
g_object_unref (shell_view);
return FALSE;
}
void
e_shell_searchbar_load_state (EShellSearchbar *searchbar)
{
EShellView *shell_view;
EShellWindow *shell_window;
GKeyFile *key_file;
GtkAction *action;
GtkWidget *widget;
const gchar *search_text;
const gchar *state_group;
const gchar *key;
gchar *string;
gint value = 0;
g_return_if_fail (E_IS_SHELL_SEARCHBAR (searchbar));
shell_view = e_shell_searchbar_get_shell_view (searchbar);
state_group = e_shell_searchbar_get_state_group (searchbar);
g_return_if_fail (state_group != NULL);
key_file = e_shell_view_get_state_key_file (shell_view);
shell_window = e_shell_view_get_shell_window (shell_view);
/* Changing the combo boxes triggers searches, so block
* the search action until the state is fully restored. */
action = E_SHELL_WINDOW_ACTION_SEARCH_QUICK (shell_window);
gtk_action_block_activate (action);
e_shell_view_block_execute_search (shell_view);
e_shell_view_set_search_rule (shell_view, NULL);
key = STATE_KEY_SEARCH_FILTER;
string = g_key_file_get_string (key_file, state_group, key, NULL);
if (string != NULL && *string != '\0')
action = e_shell_window_get_action (shell_window, string);
else
action = NULL;
if (GTK_IS_RADIO_ACTION (action))
gtk_action_activate (action);
else {
/* Pick the first combo box item. */
widget = searchbar->priv->filter_combo_box;
gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
}
g_free (string);
/* Avoid restoring to the "Advanced Search" option, since we
* don't currently save the search rule (TODO but we should). */
key = STATE_KEY_SEARCH_OPTION;
string = g_key_file_get_string (key_file, state_group, key, NULL);
if (string != NULL && *string != '\0')
action = e_shell_window_get_action (shell_window, string);
else
action = NULL;
if (GTK_IS_RADIO_ACTION (action))
g_object_get (action, "value", &value, NULL);
else
value = SEARCH_OPTION_ADVANCED;
if (value != SEARCH_OPTION_ADVANCED)
gtk_action_activate (action);
else if (searchbar->priv->search_option != NULL)
gtk_radio_action_set_current_value (
searchbar->priv->search_option, 0);
g_free (string);
key = STATE_KEY_SEARCH_TEXT;
string = g_key_file_get_string (key_file, state_group, key, NULL);
search_text = e_shell_searchbar_get_search_text (searchbar);
if (search_text != NULL && *search_text == '\0')
search_text = NULL;
if (g_strcmp0 (string, search_text) != 0)
e_shell_searchbar_set_search_text (searchbar, string);
g_free (string);
/* Search scope is hard-coded to the default state group. */
state_group = STATE_GROUP_DEFAULT;
key = STATE_KEY_SEARCH_SCOPE;
string = g_key_file_get_string (key_file, state_group, key, NULL);
if (string != NULL && *string != '\0')
action = e_shell_window_get_action (shell_window, string);
else
action = NULL;
if (GTK_IS_RADIO_ACTION (action))
gtk_action_activate (action);
else {
/* Pick the first combo box item. */
widget = searchbar->priv->scope_combo_box;
gtk_combo_box_set_active (GTK_COMBO_BOX (widget), 0);
}
g_free (string);
e_shell_view_unblock_execute_search (shell_view);
action = E_SHELL_WINDOW_ACTION_SEARCH_QUICK (shell_window);
gtk_action_unblock_activate (action);
/* Execute the search when we have time. */
g_object_ref (shell_view);
searchbar->priv->state_dirty = FALSE;
/* Prioritize ahead of GTK+ redraws. */
g_idle_add_full (
G_PRIORITY_HIGH_IDLE,
idle_execute_search, shell_view, NULL);
}
void
e_shell_searchbar_save_state (EShellSearchbar *searchbar)
{
g_return_if_fail (E_IS_SHELL_SEARCHBAR (searchbar));
/* Skip saving state if it hasn't changed since it was loaded. */
if (!searchbar->priv->state_dirty)
return;
shell_searchbar_save_search_filter (searchbar);
shell_searchbar_save_search_option (searchbar);
shell_searchbar_save_search_text (searchbar);
shell_searchbar_save_search_scope (searchbar);
searchbar->priv->state_dirty = FALSE;
}