/*
* gedit-open-document-selector.c
* This file is part of gedit
*
* Copyright (C) 2014 - Sébastien Lafargue
*
* gedit 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.
*
* gedit 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 gedit. If not, see <http://www.gnu.org/licenses/>.
*/
#include "gedit-open-document-selector.h"
#include "gedit-open-document-selector-store.h"
#include "gedit-open-document-selector-helper.h"
#include <time.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <glib/gstdio.h>
#include <gio/gio.h>
#include "gedit-recent.h"
#include "gedit-utils.h"
#include "gedit-window.h"
#include "gedit-debug.h"
struct _GeditOpenDocumentSelector
{
GtkBox parent_instance;
GeditWindow *window;
GtkWidget *search_entry;
GtkWidget *open_button;
GtkWidget *treeview;
GtkListStore *liststore;
GtkCellRenderer *name_renderer;
GtkCellRenderer *path_renderer;
GtkWidget *placeholder_box;
GtkWidget *scrolled_window;
GdkRGBA name_label_color;
PangoFontDescription *name_font;
GdkRGBA path_label_color;
PangoFontDescription *path_font;
GeditOpenDocumentSelectorStore *selector_store;
GList *recent_items;
GList *home_dir_items;
GList *desktop_dir_items;
GList *local_bookmarks_dir_items;
GList *file_browser_root_items;
GList *active_doc_dir_items;
GList *current_docs_items;
GList *all_items;
guint populate_liststore_is_idle : 1;
guint populate_scheduled : 1;
};
typedef enum
{
SELECTOR_TAG_NONE,
SELECTOR_TAG_MATCH
} SelectorTag;
enum
{
NAME_COLUMN,
PATH_COLUMN,
URI_COLUMN,
N_COLUMNS
};
enum
{
PROP_0,
PROP_WINDOW,
LAST_PROP
};
static GParamSpec *properties[LAST_PROP];
enum
{
SELECTOR_FILE_ACTIVATED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
/* Value 0xFF is reserved to mark the end of the array */
#define BYTE_ARRAY_END 0xFF
#define OPEN_DOCUMENT_SELECTOR_WIDTH 400
#define OPEN_DOCUMENT_SELECTOR_MAX_VISIBLE_ROWS 10
G_DEFINE_TYPE (GeditOpenDocumentSelector, gedit_open_document_selector, GTK_TYPE_BOX)
static inline const guint8 *
get_byte_run (const guint8 *byte_array,
gsize *count,
SelectorTag *tag)
{
guint8 tag_found;
gsize c = 1;
tag_found = *byte_array++;
while ( *byte_array != BYTE_ARRAY_END && *byte_array == tag_found)
{
c++;
byte_array++;
}
*count = c;
*tag = tag_found;
return ( *byte_array != BYTE_ARRAY_END) ? byte_array : NULL;
}
static gchar*
get_markup_from_tagged_byte_array (const gchar *str,
const guint8 *byte_array)
{
gchar *txt;
GString *string;
gchar *result_str;
SelectorTag tag;
gsize count;
string = g_string_sized_new (255);
while (TRUE)
{
byte_array = get_byte_run (byte_array, &count, &tag);
txt = g_markup_escape_text (str, count);
if (tag == SELECTOR_TAG_MATCH)
{
g_string_append_printf (string, "<span weight =\"heavy\" color =\"black\">%s</span>", txt);
}
else
{
g_string_append (string, txt);
}
g_free (txt);
if (!byte_array)
{
break;
}
str = (const gchar *)((gsize)str + count);
}
result_str = g_string_free (string, FALSE);
return result_str;
}
static guint8 *
get_tagged_byte_array (const gchar *uri,
GRegex *filter_regex)
{
guint8 *byte_array;
gsize uri_len;
GMatchInfo *match_info;
gboolean no_match = TRUE;
g_return_val_if_fail (uri != NULL, NULL);
uri_len = strlen (uri);
byte_array = g_malloc0 (uri_len + 1);
byte_array[uri_len] = BYTE_ARRAY_END;
if (g_regex_match (filter_regex, uri, 0, &match_info) == TRUE)
{
while (g_match_info_matches (match_info) == TRUE)
{
guint8 *p;
gint match_len;
gint start_pos;
gint end_pos;
if (g_match_info_fetch_pos (match_info, 0, &start_pos, &end_pos) == TRUE)
{
match_len = end_pos - start_pos;
no_match = FALSE;
p = (guint8 *)((gsize)byte_array + start_pos);
memset (p, SELECTOR_TAG_MATCH, match_len);
}
g_match_info_next (match_info, NULL);
}
}
g_match_info_free (match_info);
if (no_match)
{
g_free (byte_array);
return NULL;
}
return byte_array;
}
static void
get_markup_for_path_and_name (GRegex *filter_regex,
const gchar *src_path,
const gchar *src_name,
gchar **dst_path,
gchar **dst_name)
{
gchar *filename;
gsize path_len;
gsize name_len;
gsize path_separator_len;
guint8 *byte_array;
guint8 *path_byte_array;
guint8 *name_byte_array;
filename = g_build_filename (src_path, src_name, NULL);
path_len = g_utf8_strlen (src_path, -1);
name_len = g_utf8_strlen (src_name, -1);
path_separator_len = g_utf8_strlen (filename, -1) - ( path_len + name_len);
byte_array = get_tagged_byte_array (filename, filter_regex);
if (byte_array)
{
path_byte_array = g_memdup (byte_array, path_len + 1);
path_byte_array[path_len] = BYTE_ARRAY_END;
/* name_byte_array is part of byte_array, so released with it */
name_byte_array = (guint8 *)((gsize)byte_array + path_len + path_separator_len);
*dst_path = get_markup_from_tagged_byte_array (src_path, path_byte_array);
*dst_name = get_markup_from_tagged_byte_array (src_name, name_byte_array);
g_free (byte_array);
g_free (path_byte_array);
}
else
{
*dst_path = g_strdup (src_path);
*dst_name = g_strdup (src_name);
}
g_free (filename);
}
static void
create_row (GeditOpenDocumentSelector *selector,
const FileItem *item,
GRegex *filter_regex)
{
GtkTreeIter iter;
gchar *uri;
gchar *dst_path;
gchar *dst_name;
uri =item->uri;
if (filter_regex)
{
get_markup_for_path_and_name (filter_regex,
(const gchar *)item->path,
(const gchar *)item->name,
&dst_path,
&dst_name);
}
else
{
dst_path = g_markup_escape_text (item->path, -1);
dst_name = g_markup_escape_text (item->name, -1);
}
gtk_list_store_append (selector->liststore, &iter);
gtk_list_store_set (selector->liststore, &iter,
URI_COLUMN, uri,
NAME_COLUMN, dst_name,
PATH_COLUMN, dst_path,
-1);
g_free (dst_path);
g_free (dst_name);
}
static gint
sort_items_by_mru (FileItem *a,
FileItem *b,
gpointer unused G_GNUC_UNUSED)
{
glong diff;
g_assert (a != NULL && b != NULL);
diff = b->access_time.tv_sec - a->access_time.tv_sec;
if (diff == 0)
{
return (b->access_time.tv_usec - a->access_time.tv_usec);
}
else
{
return diff;
}
}
static GList *
compute_all_items_list (GeditOpenDocumentSelector *selector)
{
GList *recent_items;
GList *home_dir_items;
GList *desktop_dir_items;
GList *local_bookmarks_dir_items;
GList *file_browser_root_items;
GList *active_doc_dir_items;
GList *current_docs_items;
GList *all_items = NULL;
/* Copy/concat the whole list */
recent_items = gedit_open_document_selector_copy_file_items_list ((const GList *)selector->recent_items);
home_dir_items = gedit_open_document_selector_copy_file_items_list ((const GList *)selector->home_dir_items);
desktop_dir_items = gedit_open_document_selector_copy_file_items_list ((const GList *)selector->desktop_dir_items);
local_bookmarks_dir_items = gedit_open_document_selector_copy_file_items_list ((const GList *)selector->local_bookmarks_dir_items);
file_browser_root_items = gedit_open_document_selector_copy_file_items_list ((const GList *)selector->file_browser_root_items);
active_doc_dir_items = gedit_open_document_selector_copy_file_items_list ((const GList *)selector->active_doc_dir_items);
current_docs_items = gedit_open_document_selector_copy_file_items_list ((const GList *)selector->current_docs_items);
if (selector->all_items)
{
gedit_open_document_selector_free_file_items_list (selector->all_items);
selector->all_items = NULL;
}
all_items = g_list_concat (all_items, recent_items);
all_items = g_list_concat (all_items, home_dir_items);
all_items = g_list_concat (all_items, desktop_dir_items);
all_items = g_list_concat (all_items, local_bookmarks_dir_items);
all_items = g_list_concat (all_items, file_browser_root_items);
all_items = g_list_concat (all_items, active_doc_dir_items);
all_items = g_list_concat (all_items, current_docs_items);
return all_items;
}
static GList *
clamp_recent_items_list (GList *recent_items,
gint limit)
{
GList *recent_items_capped = NULL;
GList *l;
FileItem *item;
l = recent_items;
while (limit > 0 && l != NULL)
{
item = gedit_open_document_selector_copy_fileitem_item (l->data);
recent_items_capped = g_list_prepend (recent_items_capped, item);
l = l->next;
limit -= 1;
}
recent_items_capped = g_list_reverse (recent_items_capped);
return recent_items_capped;
}
/* Setup the fileitem, depending uri's scheme
* Return a string to search in.
*/
static gchar *
fileitem_setup (FileItem *item)
{
gchar *scheme;
gchar *filename;
gchar *normalized_filename = NULL;
gchar *candidate = NULL;
gchar *path;
gchar *name;
scheme = g_uri_parse_scheme (item->uri);
if (g_strcmp0 (scheme, "file") == 0)
{
filename = g_filename_from_uri ((const gchar *)item->uri, NULL, NULL);
if (filename)
{
path = g_path_get_dirname (filename);
item->path = g_filename_to_utf8 (path, -1, NULL, NULL, NULL);
g_free (path);
name = g_path_get_basename (filename);
item->name = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
g_free (name);
normalized_filename = g_utf8_normalize (filename, -1, G_NORMALIZE_ALL);
g_free (filename);
}
}
else
{
GFile *file;
gchar *parse_name;
file = g_file_new_for_uri (item->uri);
item->path = gedit_utils_location_get_dirname_for_display (file);
item->name = gedit_utils_basename_for_display (file);
parse_name = g_file_get_parse_name (file);
g_object_unref (file);
normalized_filename = g_utf8_normalize (parse_name, -1, G_NORMALIZE_ALL);
g_free (parse_name);
}
if (normalized_filename)
{
candidate = g_utf8_casefold (normalized_filename, -1);
g_free (normalized_filename);
}
g_free (scheme);
return candidate;
}
/* If filter == NULL then items are
* not checked against the filter.
*/
static GList *
fileitem_list_filter (GList *items,
const gchar *filter)
{
GList *new_items = NULL;
GList *l;
for (l = items; l != NULL; l = l->next)
{
FileItem *item;
gchar *candidate;
item = l->data;
candidate = fileitem_setup (item);
if (candidate && (filter == NULL || strstr (candidate, filter)))
{
new_items = g_list_prepend (new_items,
gedit_open_document_selector_copy_fileitem_item (item));
}
g_free (candidate);
}
new_items = g_list_reverse (new_items);
return new_items;
}
/* Remove duplicated, the HEAD of the list never change,
* the list passed in is modified.
*/
static void
fileitem_list_remove_duplicates (GList *items)
{
GList *l;
G_GNUC_UNUSED GList *dummy_ptr;
l = items;
while (l != NULL)
{
gchar *l_uri, *l1_uri;
GList *l1;
if ((l1 = l->next) == NULL)
{
break;
}
l_uri = ((FileItem *)l->data)->uri;
l1_uri = ((FileItem *)l1->data)->uri;
if (g_strcmp0 (l_uri, l1_uri) == 0)
{
gedit_open_document_selector_free_fileitem_item ((FileItem *)l1->data);
dummy_ptr = g_list_delete_link (items, l1);
}
else
{
l = l->next;
}
}
}
static gboolean
real_populate_liststore (gpointer data)
{
GeditOpenDocumentSelector *selector = GEDIT_OPEN_DOCUMENT_SELECTOR (data);
GeditOpenDocumentSelectorStore *selector_store;
GList *l;
GList *filter_items = NULL;
gchar *filter;
GRegex *filter_regex = NULL;
selector->populate_liststore_is_idle = FALSE;
DEBUG_SELECTOR_TIMER_DECL
DEBUG_SELECTOR_TIMER_NEW
gtk_list_store_clear (selector->liststore);
selector_store = selector->selector_store;
filter = gedit_open_document_selector_store_get_filter (selector_store);
if (filter && *filter != '\0')
{
DEBUG_SELECTOR (g_print ("Selector(%p): populate liststore: all lists\n", selector););
filter_items = fileitem_list_filter (selector->all_items, (const gchar *)filter);
filter_items = g_list_sort_with_data (filter_items, (GCompareDataFunc)sort_items_by_mru, NULL);
fileitem_list_remove_duplicates (filter_items);
filter_regex = g_regex_new (filter, G_REGEX_CASELESS, 0, NULL);
}
else
{
gint recent_limit;
GList *recent_items;
DEBUG_SELECTOR (g_print ("Selector(%p): populate liststore: recent files list\n", selector););
recent_limit = gedit_open_document_selector_store_get_recent_limit (selector_store);
if (recent_limit > 0 )
{
recent_items = fileitem_list_filter (selector->recent_items, NULL);
filter_items = clamp_recent_items_list (recent_items, recent_limit);
gedit_open_document_selector_free_file_items_list (recent_items);
}
else
{
filter_items = fileitem_list_filter (selector->recent_items, NULL);
}
}
g_free (filter);
DEBUG_SELECTOR (g_print ("Selector(%p): populate liststore: length:%i\n",
selector, g_list_length (filter_items)););
/* Show the placeholder if no results, show the treeview otherwise */
gtk_widget_set_visible (selector->scrolled_window, (filter_items != NULL));
gtk_widget_set_visible (selector->placeholder_box, (filter_items == NULL));
for (l = filter_items; l != NULL; l = l->next)
{
FileItem *item;
item = l->data;
create_row (selector, (const FileItem *)item, filter_regex);
}
if (filter_regex)
{
g_regex_unref (filter_regex);
}
gedit_open_document_selector_free_file_items_list (filter_items);
DEBUG_SELECTOR (g_print ("Selector(%p): populate liststore: time:%lf\n\n",
selector, DEBUG_SELECTOR_TIMER_GET););
DEBUG_SELECTOR_TIMER_DESTROY
if (selector->populate_scheduled)
{
selector->populate_scheduled = FALSE;
return G_SOURCE_CONTINUE;
}
else
{
return G_SOURCE_REMOVE;
}
}
static void
populate_liststore (GeditOpenDocumentSelector *selector)
{
/* Populate requests are compressed */
if (selector->populate_liststore_is_idle)
{
DEBUG_SELECTOR (g_print ("Selector(%p): populate liststore: idle\n", selector););
selector->populate_scheduled = TRUE;
return;
}
DEBUG_SELECTOR (g_print ("Selector(%p): populate liststore: scheduled\n", selector););
selector->populate_liststore_is_idle = TRUE;
gdk_threads_add_idle_full (G_PRIORITY_HIGH_IDLE + 30, real_populate_liststore, selector, NULL);
}
static gboolean
on_treeview_key_press (GtkTreeView *treeview,
GdkEventKey *event,
GeditOpenDocumentSelector *selector)
{
guint keyval;
gboolean is_control_pressed;
GtkTreeSelection *tree_selection;
GtkTreePath *root_path;
GdkModifierType modifiers;
if (gdk_event_get_keyval ((GdkEvent *)event, &keyval) == TRUE)
{
tree_selection = gtk_tree_view_get_selection (treeview);
root_path = gtk_tree_path_new_from_string ("0");
modifiers = gtk_accelerator_get_default_mod_mask ();
is_control_pressed = (event->state & modifiers) == GDK_CONTROL_MASK;
if ((keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up) &&
!is_control_pressed)
{
if (gtk_tree_selection_path_is_selected (tree_selection, root_path))
{
gtk_tree_selection_unselect_all (tree_selection);
gtk_widget_grab_focus (selector->search_entry);
return GDK_EVENT_STOP;
}
}
}
return GDK_EVENT_PROPAGATE;
}
static void
on_entry_changed (GtkEntry *entry,
GeditOpenDocumentSelector *selector)
{
const gchar *entry_text;
entry_text = gtk_entry_get_text (entry);
gedit_open_document_selector_store_set_filter (selector->selector_store,
entry_text);
if (gtk_widget_get_mapped ( GTK_WIDGET (selector)))
{
populate_liststore (selector);
}
}
static void
on_entry_activated (GtkEntry *entry,
GeditOpenDocumentSelector *selector)
{
const gchar *entry_text;
GtkTreeSelection *selection;
gchar *uri;
GFile *file;
gchar *scheme;
entry_text = gtk_entry_get_text (entry);
scheme = g_uri_parse_scheme (entry_text);
if (!scheme)
{
const gchar *home_dir = g_get_home_dir ();
if ( home_dir != NULL && g_str_has_prefix (entry_text, "~/"))
{
uri = g_strconcat ("file://", home_dir, "/", entry_text + 2, NULL);
}
else
{
uri = g_strconcat ("file://", entry_text, NULL);
}
}
else
{
g_free (scheme);
uri = g_strdup (entry_text);
}
file = g_file_new_for_uri (uri);
if (g_file_query_exists (file, NULL))
{
DEBUG_SELECTOR (g_print ("Selector(%p): search entry activated : loading '%s'\n",
selector, uri););
gtk_entry_set_text (entry, "");
selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (selector->treeview));
gtk_tree_selection_unselect_all (selection);
g_signal_emit (G_OBJECT (selector), signals[SELECTOR_FILE_ACTIVATED], 0, uri);
}
g_object_unref (file);
}
static void
gedit_open_document_selector_dispose (GObject *object)
{
GeditOpenDocumentSelector *selector = GEDIT_OPEN_DOCUMENT_SELECTOR (object);
while (TRUE)
{
if (!g_idle_remove_by_data (selector))
{
break;
}
}
g_clear_pointer (&selector->name_font, pango_font_description_free);
g_clear_pointer (&selector->path_font, pango_font_description_free);
if (selector->recent_items)
{
gedit_open_document_selector_free_file_items_list (selector->recent_items);
selector->recent_items = NULL;
}
if (selector->home_dir_items)
{
gedit_open_document_selector_free_file_items_list (selector->home_dir_items);
selector->home_dir_items = NULL;
}
if (selector->desktop_dir_items)
{
gedit_open_document_selector_free_file_items_list (selector->desktop_dir_items);
selector->desktop_dir_items = NULL;
}
if (selector->local_bookmarks_dir_items)
{
gedit_open_document_selector_free_file_items_list (selector->local_bookmarks_dir_items);
selector->local_bookmarks_dir_items = NULL;
}
if (selector->file_browser_root_items)
{
gedit_open_document_selector_free_file_items_list (selector->file_browser_root_items);
selector->file_browser_root_items = NULL;
}
if (selector->active_doc_dir_items)
{
gedit_open_document_selector_free_file_items_list (selector->active_doc_dir_items);
selector->active_doc_dir_items = NULL;
}
if (selector->current_docs_items)
{
gedit_open_document_selector_free_file_items_list (selector->current_docs_items);
selector->current_docs_items = NULL;
}
if (selector->all_items)
{
gedit_open_document_selector_free_file_items_list (selector->all_items);
selector->all_items = NULL;
}
G_OBJECT_CLASS (gedit_open_document_selector_parent_class)->dispose (object);
}
static void
on_row_activated (GtkTreeView *treeview,
GtkTreePath *path,
GtkTreeViewColumn *column G_GNUC_UNUSED,
GeditOpenDocumentSelector *selector)
{
GtkTreeModel *liststore = GTK_TREE_MODEL (selector->liststore);
GtkTreeSelection *selection;
GtkTreeIter iter;
gchar *uri;
g_return_if_fail (gtk_tree_model_get_iter (liststore, &iter, path));
gtk_tree_model_get (liststore, &iter,
URI_COLUMN, &uri,
-1);
selection = gtk_tree_view_get_selection (treeview);
gtk_tree_selection_unselect_all (selection);
/* Leak of uri */
g_signal_emit (G_OBJECT (selector), signals[SELECTOR_FILE_ACTIVATED], 0, uri);
}
static void
update_list_cb (GeditOpenDocumentSelectorStore *selector_store,
GAsyncResult *res,
gpointer user_data G_GNUC_UNUSED)
{
GList *list;
GError *error;
PushMessage *message;
ListType type;
GeditOpenDocumentSelector *selector;
list = gedit_open_document_selector_store_update_list_finish (selector_store, res, &error);
message = g_task_get_task_data (G_TASK (res));
selector = message->selector;
type = message->type;
DEBUG_SELECTOR (g_print ("Selector(%p): update_list_cb - type:%s, length:%i\n",
selector, list_type_string[type], g_list_length (list)););
switch (type)
{
case GEDIT_OPEN_DOCUMENT_SELECTOR_RECENT_FILES_LIST:
gedit_open_document_selector_free_file_items_list (selector->recent_items);
selector->recent_items = list;
break;
case GEDIT_OPEN_DOCUMENT_SELECTOR_HOME_DIR_LIST:
gedit_open_document_selector_free_file_items_list (selector->home_dir_items);
selector->home_dir_items = list;
break;
case GEDIT_OPEN_DOCUMENT_SELECTOR_DESKTOP_DIR_LIST:
gedit_open_document_selector_free_file_items_list (selector->desktop_dir_items);
selector->desktop_dir_items = list;
break;
case GEDIT_OPEN_DOCUMENT_SELECTOR_LOCAL_BOOKMARKS_DIR_LIST:
gedit_open_document_selector_free_file_items_list (selector->local_bookmarks_dir_items);
selector->local_bookmarks_dir_items = list;
break;
case GEDIT_OPEN_DOCUMENT_SELECTOR_FILE_BROWSER_ROOT_DIR_LIST:
gedit_open_document_selector_free_file_items_list (selector->file_browser_root_items);
selector->file_browser_root_items = list;
break;
case GEDIT_OPEN_DOCUMENT_SELECTOR_ACTIVE_DOC_DIR_LIST:
gedit_open_document_selector_free_file_items_list (selector->active_doc_dir_items);
selector->active_doc_dir_items = list;
break;
case GEDIT_OPEN_DOCUMENT_SELECTOR_CURRENT_DOCS_LIST:
gedit_open_document_selector_free_file_items_list (selector->current_docs_items);
selector->current_docs_items = list;
break;
default:
g_return_if_reached ();
}
selector->all_items = compute_all_items_list (selector);
populate_liststore (selector);
}
static void
gedit_open_document_selector_constructed (GObject *object)
{
GeditOpenDocumentSelector *selector = GEDIT_OPEN_DOCUMENT_SELECTOR (object);
G_OBJECT_CLASS (gedit_open_document_selector_parent_class)->constructed (object);
DEBUG_SELECTOR (g_print ("Selector(%p): constructed - ask recent file list\n", selector););
gedit_open_document_selector_store_update_list_async (selector->selector_store,
selector,
NULL,
(GAsyncReadyCallback)update_list_cb,
GEDIT_OPEN_DOCUMENT_SELECTOR_RECENT_FILES_LIST,
selector);
}
static void
gedit_open_document_selector_mapped (GtkWidget *widget)
{
GeditOpenDocumentSelector *selector = GEDIT_OPEN_DOCUMENT_SELECTOR (widget);
ListType list_number;
/* We update all the lists */
DEBUG_SELECTOR (g_print ("Selector(%p): mapped - ask all lists\n", selector););
for (list_number = 0; list_number < GEDIT_OPEN_DOCUMENT_SELECTOR_LIST_TYPE_NUM_OF_LISTS; list_number++)
{
gedit_open_document_selector_store_update_list_async (selector->selector_store,
selector,
NULL,
(GAsyncReadyCallback)update_list_cb,
list_number,
selector);
}
GTK_WIDGET_CLASS (gedit_open_document_selector_parent_class)->map (widget);
}
static GtkSizeRequestMode
gedit_open_document_selector_get_request_mode (GtkWidget *widget G_GNUC_UNUSED)
{
return GTK_SIZE_REQUEST_CONSTANT_SIZE;
}
static void
gedit_open_document_selector_get_preferred_width (GtkWidget *widget G_GNUC_UNUSED,
gint *minimum_width,
gint *natural_width)
{
*minimum_width = *natural_width = OPEN_DOCUMENT_SELECTOR_WIDTH;
}
static void
gedit_open_document_selector_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GeditOpenDocumentSelector *selector = GEDIT_OPEN_DOCUMENT_SELECTOR (object);
switch (prop_id)
{
case PROP_WINDOW:
selector->window = g_value_get_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gedit_open_document_selector_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GeditOpenDocumentSelector *selector = GEDIT_OPEN_DOCUMENT_SELECTOR (object);
switch (prop_id)
{
case PROP_WINDOW:
g_value_set_object (value, selector->window);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
gedit_open_document_selector_file_activated (GeditOpenDocumentSelector *selector G_GNUC_UNUSED,
const gchar *uri G_GNUC_UNUSED)
{
/* Do nothing in the default handler */
}
static void
gedit_open_document_selector_class_init (GeditOpenDocumentSelectorClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
object_class->constructed = gedit_open_document_selector_constructed;
object_class->dispose = gedit_open_document_selector_dispose;
object_class->get_property = gedit_open_document_selector_get_property;
object_class->set_property = gedit_open_document_selector_set_property;
widget_class->get_request_mode = gedit_open_document_selector_get_request_mode;
widget_class->get_preferred_width = gedit_open_document_selector_get_preferred_width;
widget_class->map = gedit_open_document_selector_mapped;
properties[PROP_WINDOW] =
g_param_spec_object ("window",
"Window",
"The GeditWindow this GeditOpenDocumentSelector is associated with",
GEDIT_TYPE_WINDOW,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, LAST_PROP, properties);
signals[SELECTOR_FILE_ACTIVATED] =
g_signal_new_class_handler ("file-activated",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (gedit_open_document_selector_file_activated),
NULL, NULL, NULL,
G_TYPE_NONE,
1,
G_TYPE_STRING);
gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/gedit/ui/gedit-open-document-selector.ui");
gtk_widget_class_bind_template_child (widget_class, GeditOpenDocumentSelector, open_button);
gtk_widget_class_bind_template_child (widget_class, GeditOpenDocumentSelector, treeview);
gtk_widget_class_bind_template_child (widget_class, GeditOpenDocumentSelector, placeholder_box);
gtk_widget_class_bind_template_child (widget_class, GeditOpenDocumentSelector, scrolled_window);
gtk_widget_class_bind_template_child (widget_class, GeditOpenDocumentSelector, search_entry);
}
static void
on_treeview_allocate (GtkWidget *widget G_GNUC_UNUSED,
GdkRectangle *allocation G_GNUC_UNUSED,
GeditOpenDocumentSelector *selector)
{
GeditOpenDocumentSelectorStore *selector_store;
GtkStyleContext *context;
gint name_renderer_natural_size;
gint path_renderer_natural_size;
GtkBorder padding;
gint ypad;
gint limit_capped;
gint treeview_height;
gint grid_line_width;
gint row_height;
gint recent_limit;
selector_store = selector->selector_store;
context = gtk_widget_get_style_context (selector->treeview);
gtk_style_context_get_padding (context,
gtk_style_context_get_state (context),
&padding);
/* Treeview height computation */
gtk_cell_renderer_get_preferred_height (selector->name_renderer,
selector->treeview,
NULL,
&name_renderer_natural_size);
gtk_cell_renderer_get_preferred_height (selector->path_renderer,
selector->treeview,
NULL,
&path_renderer_natural_size);
gtk_cell_renderer_get_padding (selector->name_renderer, NULL, &ypad);
gtk_widget_style_get (selector->treeview, "grid-line-width", &grid_line_width, NULL);
recent_limit = gedit_open_document_selector_store_get_recent_limit (selector_store);
limit_capped = (recent_limit > 0 ) ? MIN (recent_limit, OPEN_DOCUMENT_SELECTOR_MAX_VISIBLE_ROWS) :
OPEN_DOCUMENT_SELECTOR_MAX_VISIBLE_ROWS;
row_height = name_renderer_natural_size +
path_renderer_natural_size +
2 * (padding.top + padding.bottom) +
ypad +
grid_line_width;
treeview_height = row_height * limit_capped;
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (selector->scrolled_window),
treeview_height);
gtk_scrolled_window_set_max_content_height (GTK_SCROLLED_WINDOW (selector->scrolled_window),
treeview_height);
gtk_widget_set_size_request (selector->placeholder_box, -1, treeview_height);
}
static void
on_treeview_style_updated (GtkWidget *widget,
GeditOpenDocumentSelector *selector)
{
GtkStyleContext *context;
context = gtk_widget_get_style_context (widget);
/* Name label foreground and font size styling */
gtk_style_context_save (context);
gtk_style_context_add_class (context, "open-document-selector-name-label");
gtk_style_context_get_color (context,
gtk_style_context_get_state (context),
&selector->name_label_color);
g_clear_pointer (&selector->name_font, pango_font_description_free);
gtk_style_context_get (context,
gtk_style_context_get_state (context),
"font", &selector->name_font,
NULL);
gtk_style_context_restore (context);
/* Path label foreground and font size styling */
gtk_style_context_save (context);
gtk_style_context_add_class (context, "open-document-selector-path-label");
gtk_style_context_get_color (context,
gtk_style_context_get_state (context),
&selector->path_label_color);
g_clear_pointer (&selector->path_font, pango_font_description_free);
gtk_style_context_get (context,
gtk_style_context_get_state (context),
"font", &selector->path_font,
NULL);
gtk_style_context_restore (context);
}
static void
name_renderer_datafunc (GtkTreeViewColumn *column G_GNUC_UNUSED,
GtkCellRenderer *name_renderer G_GNUC_UNUSED,
GtkTreeModel *liststore G_GNUC_UNUSED,
GtkTreeIter *iter G_GNUC_UNUSED,
GeditOpenDocumentSelector *selector)
{
g_object_set (selector->name_renderer, "foreground-rgba", &selector->name_label_color, NULL);
g_object_set (selector->name_renderer, "font-desc", selector->name_font, NULL);
}
static void
path_renderer_datafunc (GtkTreeViewColumn *column G_GNUC_UNUSED,
GtkCellRenderer *path_renderer G_GNUC_UNUSED,
GtkTreeModel *liststore G_GNUC_UNUSED,
GtkTreeIter *iter G_GNUC_UNUSED,
GeditOpenDocumentSelector *selector)
{
g_object_set (selector->path_renderer, "foreground-rgba", &selector->path_label_color, NULL);
g_object_set (selector->path_renderer, "font-desc", selector->path_font, NULL);
}
static void
setup_treeview (GeditOpenDocumentSelector *selector)
{
GtkTreeViewColumn *column;
GtkCellArea *cell_area;
GtkStyleContext *context;
gtk_tree_view_set_model (GTK_TREE_VIEW (selector->treeview), GTK_TREE_MODEL (selector->liststore));
g_object_unref(GTK_TREE_MODEL (selector->liststore));
selector->name_renderer = gtk_cell_renderer_text_new ();
selector->path_renderer = gtk_cell_renderer_text_new ();
g_object_set (selector->name_renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
g_object_set (selector->path_renderer, "ellipsize", PANGO_ELLIPSIZE_START, NULL);
column = gtk_tree_view_column_new ();
gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
gtk_tree_view_column_pack_start (column, selector->name_renderer, TRUE);
gtk_tree_view_column_pack_start (column, selector->path_renderer, TRUE);
gtk_tree_view_column_set_attributes (column, selector->name_renderer, "markup", NAME_COLUMN, NULL);
gtk_tree_view_column_set_attributes (column, selector->path_renderer, "markup", PATH_COLUMN, NULL);
gtk_tree_view_append_column (GTK_TREE_VIEW (selector->treeview), column);
cell_area = gtk_cell_layout_get_area (GTK_CELL_LAYOUT (column));
gtk_orientable_set_orientation (GTK_ORIENTABLE (cell_area), GTK_ORIENTATION_VERTICAL);
context = gtk_widget_get_style_context (selector->treeview);
gtk_style_context_add_class (context, "open-document-selector-treeview");
gtk_tree_view_column_set_cell_data_func (column,
selector->name_renderer,
(GtkTreeCellDataFunc)name_renderer_datafunc,
selector,
NULL);
gtk_tree_view_column_set_cell_data_func (column,
selector->path_renderer,
(GtkTreeCellDataFunc)path_renderer_datafunc,
selector,
NULL);
}
static void
gedit_open_document_selector_init (GeditOpenDocumentSelector *selector)
{
gedit_debug (DEBUG_WINDOW);
gtk_widget_init_template (GTK_WIDGET (selector));
selector->selector_store = gedit_open_document_selector_store_get_default ();
selector->liststore = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
setup_treeview (selector);
g_signal_connect (selector->search_entry,
"changed",
G_CALLBACK (on_entry_changed),
selector);
g_signal_connect (selector->search_entry,
"activate",
G_CALLBACK (on_entry_activated),
selector);
g_signal_connect (selector->treeview,
"row-activated",
G_CALLBACK (on_row_activated),
selector);
g_signal_connect (selector->treeview,
"size-allocate",
G_CALLBACK (on_treeview_allocate),
selector);
g_signal_connect (selector->treeview,
"key-press-event",
G_CALLBACK (on_treeview_key_press),
selector);
g_signal_connect (selector->treeview,
"style-updated",
G_CALLBACK (on_treeview_style_updated),
selector);
}
GeditOpenDocumentSelector *
gedit_open_document_selector_new (GeditWindow *window)
{
g_return_val_if_fail (GEDIT_IS_WINDOW (window), NULL);
return g_object_new (GEDIT_TYPE_OPEN_DOCUMENT_SELECTOR,
"window", window,
NULL);
}
GeditWindow *
gedit_open_document_selector_get_window (GeditOpenDocumentSelector *selector)
{
g_return_val_if_fail (GEDIT_IS_OPEN_DOCUMENT_SELECTOR (selector), NULL);
return selector->window;
}
GtkWidget *
gedit_open_document_selector_get_search_entry (GeditOpenDocumentSelector *selector)
{
g_return_val_if_fail (GEDIT_IS_OPEN_DOCUMENT_SELECTOR (selector), NULL);
return selector->search_entry;
}
/* ex:set ts=8 noet: */