/* * moofilelist.c * * Copyright (C) 2004-2010 by Yevgen Muntyan * * This file is part of medit. medit 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.1 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public * License along with medit. If not, see . */ #include "config.h" #include "plugins/mooplugin-builtin.h" #include "mooedit/mooplugin-macro.h" #include "mooutils/mooi18n.h" #include "mooutils/mootype-macros.h" #include "mooutils/mooutils-misc.h" #include "mooutils/mooutils-treeview.h" #include "mooutils/mooutils-fs.h" #include "mooutils/mooatom.h" #include #include #include #define DEBUG_ASSERT(expr) g_assert (expr) #define FILE_LIST_PLUGIN_ID "MooFileList" #define WindowPlugin FileListWindowPlugin #define FILE_LIST(model) ((FileList*)model) #define GROUP_ITEM(itm) ((Group*)itm) #define FILE_ITEM(itm) ((File*)itm) #define ITEM(itm) ((Item*)itm) #define ITEM_IS_FILE(itm) ((itm) && ((Item*)(itm))->type == ITEM_FILE) #define ITEM_IS_GROUP(itm) ((itm) && ((Item*)(itm))->type == ITEM_GROUP) #define CONFIG_FILE "file-list-config.xml" #define ELM_CONFIG "file-list-config" #define PROP_VERSION "version" #define VALUE_VERSION "1.0" #define ELM_GROUP "group" #define ELM_FILE "file" #define ELM_UI "ui" #define PROP_NAME "name" #define PROP_URI "uri" #define PROP_EXPANDED_ROWS "expanded-rows" #define PROP_SELECTED_ROW "selected-row" typedef struct Item Item; typedef struct Group Group; typedef struct File File; typedef struct FileListWindowPlugin FileListWindowPlugin; enum { COLUMN_ITEM, COLUMN_TOOLTIP }; typedef enum { ITEM_FILE, ITEM_GROUP } ItemType; struct Item { ItemType type; guint ref_count; }; struct File { Item base; char *uri; char *display_basename; char *display_name; MooEdit *doc; }; struct Group { Item base; char *name; }; typedef struct { GtkTreeStore base; int n_user_items; GSList *docs; FileListWindowPlugin *plugin; } FileList; typedef struct { GtkTreeStoreClass base_class; } FileListClass; typedef struct { MooPlugin parent; } FileListPlugin; typedef struct { GSList *expanded_rows; GtkTreePath *selected_row; } UIConfig; struct FileListWindowPlugin { MooWinPlugin parent; FileList *list; GtkTreeView *treeview; GtkTreeViewColumn *column; GtkCellRenderer *text_cell; gboolean first_time_show; guint update_idle; guint update_ui_idle; char *filename; UIConfig *ui_config; }; #define TREE_MODEL_ROW_ATOM (tree_model_row_atom ()) MOO_DEFINE_ATOM (GTK_TREE_MODEL_ROW, tree_model_row) MOO_DEFINE_QUARK_STATIC (moo-file-list-plugin-model-row, file_list_row_quark) #define FILE_LIST_QUARK (file_list_quark ()) MOO_DEFINE_QUARK_STATIC (moo-file-list-plugin, file_list_quark) const ObjectDataAccessor file_list_row_data(file_list_row_quark()); static GType item_get_type (void) G_GNUC_CONST; static Item *item_ref (Item *item); static void item_unref (Item *item); static void file_list_load_config (FileList *list, const char *filename, UIConfig **ui_config); static void file_list_save_config (FileList *list, const char *filename, UIConfig *ui_config); static void file_list_update (FileList *list, GSList *docs); static void file_list_shutdown (FileList *list); static void window_plugin_queue_update (WindowPlugin *plugin); static void window_plugin_queue_update_ui (WindowPlugin *plugin); static void file_list_drag_source_iface_init (GtkTreeDragSourceIface *iface); static void file_list_drag_dest_iface_init (GtkTreeDragDestIface *iface); static gboolean drag_source_row_draggable (GtkTreeDragSource *drag_source, GtkTreePath *path); static gboolean drag_source_drag_data_get (GtkTreeDragSource *drag_source, GtkTreePath *path, GtkSelectionData *selection_data); static gboolean drag_source_drag_data_delete (GtkTreeDragSource *drag_source, GtkTreePath *path); static gboolean drag_dest_drag_data_received (GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data); static gboolean drag_dest_row_drop_possible (GtkTreeDragDest *drag_dest, GtkTreePath *dest_path, GtkSelectionData *selection_data); MOO_DEFINE_BOXED_TYPE_STATIC_R (MooFileListItem, item) MOO_DEFINE_TYPE_STATIC_WITH_CODE (FileList, file_list, GTK_TYPE_TREE_STORE, G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, file_list_drag_source_iface_init) G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_DEST, file_list_drag_dest_iface_init)) static void file_list_drag_source_iface_init (GtkTreeDragSourceIface *iface) { iface->row_draggable = drag_source_row_draggable; iface->drag_data_get = drag_source_drag_data_get; iface->drag_data_delete = drag_source_drag_data_delete; } static void file_list_drag_dest_iface_init (GtkTreeDragDestIface *iface) { iface->drag_data_received = drag_dest_drag_data_received; iface->row_drop_possible = drag_dest_row_drop_possible; } static void file_list_init (FileList *list) { GType types[2]; types[COLUMN_ITEM] = item_get_type (); types[COLUMN_TOOLTIP] = G_TYPE_STRING; gtk_tree_store_set_column_types (GTK_TREE_STORE (list), 2, types); list->n_user_items = 0; list->docs = nullptr; } static void file_list_finalize (GObject *object) { DEBUG_ASSERT (!FILE_LIST (object)->docs); G_OBJECT_CLASS (file_list_parent_class)->finalize (object); } static void file_list_class_init (FileListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = file_list_finalize; } static Item * get_item_at_iter (FileList *list, GtkTreeIter *iter) { Item *item = nullptr; gtk_tree_model_get (GTK_TREE_MODEL (list), iter, COLUMN_ITEM, &item, -1); if (item) item_unref (item); return item; } static Item * get_item_at_path (FileList *list, GtkTreePath *path) { GtkTreeIter iter; if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, path)) return nullptr; else return get_item_at_iter (list, &iter); } static Group * group_new (const char *name) { Group *grp = g_slice_new0 (Group); ITEM (grp)->ref_count = 1; ITEM (grp)->type = ITEM_GROUP; grp->name = g_strdup (name); return grp; } static void group_free (Group *grp) { if (grp) { g_free (grp->name); g_slice_free (Group, grp); } } static char * uri_get_basename (const char *uri) { const char *last_slash = strrchr (uri, '/'); if (last_slash && last_slash[1]) return g_strdup (last_slash + 1); else return g_strdup (uri); } static gstr file_get_uri (File *file) { if (file->uri) return gstr(file->uri); else return gstr::take(moo_edit_get_uri (file->doc)); } static void file_update (File *file) { if (!file->uri && file->doc) { g_free (file->display_name); g_free (file->display_basename); file->display_name = g_strdup (moo_edit_get_display_name (file->doc)); file->display_basename = g_strdup (moo_edit_get_display_basename (file->doc)); } } static void file_set_doc (File *file, MooEdit *doc) { if (doc) g_object_ref (doc); if (file->doc) g_object_unref (file->doc); file->doc = doc; file_update (file); } static File * file_new (void) { File *file = g_slice_new0 (File); ITEM (file)->ref_count = 1; ITEM (file)->type = ITEM_FILE; file->uri = nullptr; file->doc = nullptr; file->display_name = nullptr; file->display_basename = nullptr; return file; } static Item * file_new_doc (MooEdit *doc) { File *file; g_return_val_if_fail (MOO_IS_EDIT (doc), nullptr); file = file_new (); file_set_doc (file, doc); return ITEM (file); } static void file_set_uri (File *file, const char *uri) { char *tmp; char *filename; g_return_if_fail (file != nullptr); g_return_if_fail (uri != nullptr); tmp = file->uri; file->uri = g_strdup (uri); g_free (tmp); g_free (file->display_name); g_free (file->display_basename); filename = g_filename_from_uri (uri, nullptr, nullptr); if (filename) { file->display_name = g_filename_display_name (filename); file->display_basename = g_filename_display_basename (filename); } else { file->display_name = g_strdup (uri); file->display_basename = uri_get_basename (uri); } g_free (filename); } static Item * file_new_uri (const char *uri) { File *file; g_return_val_if_fail (uri != nullptr, nullptr); file = file_new (); file_set_uri (file, uri); return ITEM (file); } static void file_free (File *file) { if (file) { if (file->doc) g_object_unref (file->doc); g_free (file->uri); g_free (file->display_name); g_free (file->display_basename); g_slice_free (File, file); } } static const char * item_get_tooltip (Item *item) { if (ITEM_IS_FILE (item)) return FILE_ITEM (item)->display_name; else return nullptr; } static Item * item_ref (Item *item) { if (item) item->ref_count += 1; return item; } static void item_unref (Item *item) { if (item && !--item->ref_count) { switch (item->type) { case ITEM_FILE: file_free (FILE_ITEM (item)); break; case ITEM_GROUP: group_free (GROUP_ITEM (item)); break; } } } static gboolean file_list_iter_is_auto (FileList *list, GtkTreeIter *iter) { gboolean retval; GtkTreePath *path; path = gtk_tree_model_get_path (GTK_TREE_MODEL (list), iter); retval = gtk_tree_path_get_depth (path) == 1 && gtk_tree_path_get_indices (path)[0] >= list->n_user_items; gtk_tree_path_free (path); return retval; } static void check_list (G_GNUC_UNUSED FileList *list) { #ifdef DEBUG GtkTreeIter iter; int index = 0; if (gtk_tree_model_iter_children (GTK_TREE_MODEL (list), &iter, nullptr)) { do { Item *item = get_item_at_iter (list, &iter); if (list->n_user_items) { g_assert (index == list->n_user_items || item != nullptr); g_assert (index != list->n_user_items || item == nullptr); } else { g_assert (item != nullptr); } index += 1; } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list), &iter)); } g_assert (list->n_user_items == 0 || list->n_user_items < gtk_tree_model_iter_n_children (GTK_TREE_MODEL (list), nullptr)); #endif } static gboolean iter_find_uri (FileList *list, const char *uri, GtkTreeIter *parent, GtkTreeIter *dest) { GtkTreeIter iter; if (parent) { Item *item = get_item_at_iter (list, parent); if (ITEM_IS_FILE (item) && FILE_ITEM (item)->uri && strcmp (FILE_ITEM (item)->uri, uri) == 0) { *dest = *parent; return TRUE; } } if (gtk_tree_model_iter_children (GTK_TREE_MODEL (list), &iter, parent)) do { if (iter_find_uri (list, uri, &iter, dest)) return TRUE; } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list), &iter)); return FALSE; } static gboolean file_list_find_uri (FileList *list, const char *uri, GtkTreeIter *iter) { return iter_find_uri (list, uri, nullptr, iter); } static void file_list_remove_row (FileList *list, GtkTreeIter *iter) { GtkTreeIter parent; gboolean last_user_item = FALSE; if (!gtk_tree_model_iter_parent (GTK_TREE_MODEL (list), &parent, iter) && !file_list_iter_is_auto (list, iter)) { DEBUG_ASSERT (list->n_user_items > 0); list->n_user_items -= 1; last_user_item = list->n_user_items == 0; } gtk_tree_store_remove (GTK_TREE_STORE (list), iter); if (last_user_item) { GtkTreeIter sep; gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (list), &sep, nullptr, 0); gtk_tree_store_remove (GTK_TREE_STORE (list), &sep); } check_list (list); } static void file_list_append_row (FileList *list, Item *item, GtkTreeIter *iter) { gtk_tree_store_append (GTK_TREE_STORE (list), iter, nullptr); gtk_tree_store_set (GTK_TREE_STORE (list), iter, COLUMN_ITEM, item, COLUMN_TOOLTIP, item_get_tooltip (item), -1); } static void file_list_insert_row (FileList *list, Item *item, GtkTreeIter *iter, GtkTreeIter *parent_iter, int index) { gboolean first_user_item = FALSE; if (index < 0) { if (!parent_iter) index = list->n_user_items; else index = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (list), parent_iter); } if (!parent_iter) { index = MIN (index, list->n_user_items); list->n_user_items += 1; first_user_item = list->n_user_items == 1; } gtk_tree_store_insert (GTK_TREE_STORE (list), iter, parent_iter, index); gtk_tree_store_set (GTK_TREE_STORE (list), iter, COLUMN_ITEM, item, COLUMN_TOOLTIP, item_get_tooltip (item), -1); if (first_user_item) { GtkTreeIter sep; gtk_tree_store_insert (GTK_TREE_STORE (list), &sep, nullptr, list->n_user_items); gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (list), iter, parent_iter, index); } check_list (list); } static void doc_filename_changed (FileList *list) { if (list->plugin) window_plugin_queue_update (list->plugin); } static void connect_doc (FileList *list, MooEdit *doc) { DEBUG_ASSERT (!g_slist_find (list->docs, doc)); list->docs = g_slist_prepend (list->docs, g_object_ref (doc)); g_signal_connect_swapped (doc, "filename-changed", G_CALLBACK (doc_filename_changed), list); } static void disconnect_doc (FileList *list, MooEdit *doc) { DEBUG_ASSERT (g_slist_find (list->docs, doc)); list->docs = g_slist_remove (list->docs, doc); g_signal_handlers_disconnect_by_func (doc, (gpointer) doc_filename_changed, list); g_object_unref (doc); } static GtkTreeRowReference * get_doc_row (FileList *list, MooEdit *doc) { GtkTreeRowReference *row = file_list_row_data.get(doc); if (row && g_object_get_qdata (G_OBJECT (doc), FILE_LIST_QUARK) != list->plugin) row = nullptr; return row; } static void file_list_add_doc (FileList *list, MooEdit *doc, gboolean new_) { GtkTreeIter iter; Item *item; GtkTreeRowReference *row; GtkTreePath *path; DEBUG_ASSERT (!new_ || !get_doc_row (list, doc)); DEBUG_ASSERT (new_ == !g_slist_find (list->docs, doc)); gstr uri = gstr::take (moo_edit_get_uri (doc)); if (!uri.empty() && file_list_find_uri (list, uri.get(), &iter)) { item = get_item_at_iter (list, &iter); DEBUG_ASSERT (ITEM_IS_FILE (item) && !FILE_ITEM (item)->doc); file_set_doc (FILE_ITEM (item), doc); } else { item = file_new_doc (doc); file_list_append_row (list, item, &iter); item_unref (item); } path = gtk_tree_model_get_path (GTK_TREE_MODEL (list), &iter); row = gtk_tree_row_reference_new (GTK_TREE_MODEL (list), path); file_list_row_data.set(doc, row, (GDestroyNotify) gtk_tree_row_reference_free); g_object_set_qdata (G_OBJECT (doc), FILE_LIST_QUARK, list->plugin); if (new_) connect_doc (list, doc); gtk_tree_path_free (path); } static gboolean doc_get_list_iter (FileList *list, MooEdit *doc, GtkTreeIter *iter) { GtkTreeRowReference *row; GtkTreePath *path; row = get_doc_row (list, doc); if (!row || !gtk_tree_row_reference_valid (row)) return FALSE; path = gtk_tree_row_reference_get_path (row); gtk_tree_model_get_iter (GTK_TREE_MODEL (list), iter, path); gtk_tree_path_free (path); return TRUE; } static void file_list_update_doc (FileList *list, MooEdit *doc) { GtkTreeIter iter; Item *item; if (!g_slist_find (list->docs, doc)) { file_list_add_doc (list, doc, TRUE); return; } if (!doc_get_list_iter (list, doc, &iter)) { file_list_add_doc (list, doc, FALSE); return; } DEBUG_ASSERT (!file_list_iter_is_auto (list, &iter)); item = get_item_at_iter (list, &iter); DEBUG_ASSERT (ITEM_IS_FILE (item) && FILE_ITEM (item)->doc == doc); DEBUG_ASSERT (FILE_ITEM (item)->uri != nullptr); gstr new_uri = gstr::take (moo_edit_get_uri (doc)); if (new_uri.empty() || strcmp (new_uri.get(), FILE_ITEM (item)->uri) != 0) { file_list_row_data.set(doc, nullptr); file_set_doc (FILE_ITEM (item), nullptr); file_list_add_doc (list, doc, FALSE); } } static void file_list_remove_doc (FileList *list, MooEdit *doc) { GtkTreeIter iter; DEBUG_ASSERT (g_slist_find (list->docs, doc) != nullptr); if (doc_get_list_iter (list, doc, &iter)) { Item *item; item = get_item_at_iter (list, &iter); DEBUG_ASSERT (ITEM_IS_FILE (item) && FILE_ITEM (item)->doc == doc); DEBUG_ASSERT (file_list_iter_is_auto (list, &iter) == !FILE_ITEM (item)->uri); if (file_list_iter_is_auto (list, &iter)) file_list_remove_row (list, &iter); else file_set_doc (FILE_ITEM (item), nullptr); } if (get_doc_row (list, doc)) { file_list_row_data.set(doc, nullptr); g_object_set_qdata (G_OBJECT (doc), FILE_LIST_QUARK, nullptr); } disconnect_doc (list, doc); } static void remove_auto_items (FileList *list) { GtkTreeIter iter; if (list->n_user_items) while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (list), &iter, nullptr, list->n_user_items + 1)) gtk_tree_store_remove (GTK_TREE_STORE (list), &iter); else while (gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (list), &iter, nullptr, 0)) gtk_tree_store_remove (GTK_TREE_STORE (list), &iter); } static void file_list_update (FileList *list, GSList *docs) { GSList *l; GSList *old_docs; remove_auto_items (list); for (l = docs; l != nullptr; l = l->next) file_list_update_doc (list, (MooEdit*) l->data); old_docs = g_slist_copy (list->docs); for (l = docs; l != nullptr; l = l->next) old_docs = g_slist_remove (old_docs, l->data); for (l = old_docs; l != nullptr; l = l->next) file_list_remove_doc (list, (MooEdit*) l->data); check_list (list); g_slist_free (old_docs); } static void file_list_shutdown (FileList *list) { while (list->docs) { GtkTreeIter iter; MooEdit *doc; doc = (MooEdit*) list->docs->data; if (doc_get_list_iter (list, doc, &iter)) { Item *item = get_item_at_iter (list, &iter); DEBUG_ASSERT (ITEM_IS_FILE (item)); file_set_doc (FILE_ITEM (item), nullptr); } if (get_doc_row (list, doc)) { file_list_row_data.set(doc, nullptr); g_object_set_qdata (G_OBJECT (doc), FILE_LIST_QUARK, nullptr); } disconnect_doc (list, doc); } } static UIConfig * ui_config_new (void) { UIConfig *cfg = g_new0 (UIConfig, 1); cfg->expanded_rows = nullptr; cfg->selected_row = nullptr; return cfg; } static void ui_config_free (UIConfig *cfg) { if (cfg) { g_slist_foreach (cfg->expanded_rows, (GFunc) gtk_tree_path_free, nullptr); g_slist_free (cfg->expanded_rows); gtk_tree_path_free (cfg->selected_row); g_free (cfg); } } static void parse_node (FileList *list, MooMarkupNode *elm, GtkTreeIter *parent, const char *filename, UIConfig **ui_config) { if (strcmp (elm->name, ELM_GROUP) == 0) { GtkTreeIter iter; const char *name; MooMarkupNode *child; Group *group; if (!(name = moo_markup_get_prop (elm, PROP_NAME)) || !name[0]) { g_critical ("in file %s, element %s: name missing", filename, elm->name); return; } group = group_new (name); file_list_insert_row (list, ITEM (group), &iter, parent, -1); item_unref (ITEM (group)); for (child = elm->children; child != nullptr; child = child->next) if (MOO_MARKUP_IS_ELEMENT (child)) parse_node (list, child, &iter, filename, ui_config); } else if (strcmp (elm->name, ELM_FILE) == 0) { const char *uri; Item *item; GtkTreeIter iter; if (!(uri = moo_markup_get_prop (elm, PROP_URI)) || !uri[0]) { g_critical ("in file %s, element %s: uri missing", filename, elm->name); return; } item = file_new_uri (uri); file_list_insert_row (list, item, &iter, parent, -1); item_unref (item); } else if (strcmp (elm->name, ELM_UI) == 0) { const char *expanded_rows; const char *selected_row; UIConfig *config; char **rows = nullptr, **p; if (*ui_config) { g_critical ("in file %s, duplicated element %s", filename, elm->name); return; } *ui_config = config = ui_config_new (); expanded_rows = moo_markup_get_prop (elm, PROP_EXPANDED_ROWS); selected_row = moo_markup_get_prop (elm, PROP_SELECTED_ROW); if (expanded_rows) rows = g_strsplit (expanded_rows, ";", 0); for (p = rows; p && *p; ++p) { GtkTreePath *path = gtk_tree_path_new_from_string (*p); if (path) config->expanded_rows = g_slist_prepend (config->expanded_rows, path); } config->expanded_rows = g_slist_reverse (config->expanded_rows); if (selected_row) config->selected_row = gtk_tree_path_new_from_string (selected_row); g_strfreev (rows); } else { g_critical ("in file %s: unexpected element '%s'", filename, elm->name); } } static void file_list_load_config (FileList *list, const char *filename, UIConfig **ui_configp) { MooMarkupDoc *doc; GError *error = nullptr; MooMarkupNode *root, *node; const char *version; *ui_configp = nullptr; if (!g_file_test (filename, G_FILE_TEST_EXISTS)) return; doc = moo_markup_parse_file (filename, &error); if (!doc) { g_critical ("%s: could not open file %s: %s", G_STRFUNC, filename, moo_error_message (error)); g_error_free (error); return; } if (!(root = moo_markup_get_root_element (doc, ELM_CONFIG))) { g_critical ("%s: in file %s: missing element '%s'", G_STRFUNC, filename, ELM_CONFIG); goto out; } if (!(version = moo_markup_get_prop (root, PROP_VERSION)) || strcmp (version, VALUE_VERSION) != 0) { g_critical ("%s: in file %s: invalid version '%s'", G_STRFUNC, filename, VALUE_VERSION); goto out; } for (node = root->children; node != nullptr; node = node->next) { if (!MOO_MARKUP_IS_ELEMENT (node)) continue; parse_node (list, node, nullptr, filename, ui_configp); } out: moo_markup_doc_unref (doc); } static void format_item (FileList *list, GtkTreeIter *iter, GString *buffer, guint indent) { Item *item; char *indent_s; item = get_item_at_iter (list, iter); indent_s = g_strnfill (indent, ' '); if (ITEM_IS_FILE (item)) { char *uri_escaped = g_markup_escape_text (FILE_ITEM (item)->uri, -1); if (uri_escaped) g_string_append_printf (buffer, "%s<%s %s=\"%s\"/>\n", indent_s, ELM_FILE, PROP_URI, uri_escaped); g_free (uri_escaped); } else if (ITEM_IS_GROUP (item)) { char *name_escaped = g_markup_escape_text (GROUP_ITEM (item)->name, -1); if (name_escaped) { GtkTreeIter child; if (gtk_tree_model_iter_children (GTK_TREE_MODEL (list), &child, iter)) { g_string_append_printf (buffer, "%s<%s %s=\"%s\">\n", indent_s, ELM_GROUP, PROP_NAME, name_escaped); do { format_item (list, &child, buffer, indent + 2); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list), &child)); g_string_append_printf (buffer, "%s\n", indent_s, ELM_GROUP); } else { g_string_append_printf (buffer, "%s<%s %s=\"%s\"/>\n", indent_s, ELM_GROUP, PROP_NAME, name_escaped); } } g_free (name_escaped); } else { g_critical ("oops"); } g_free (indent_s); } static void file_list_save_config (FileList *list, const char *filename, UIConfig *ui_config) { GtkTreeIter iter; GString *buffer; GError *error = nullptr; if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list), &iter)) { mgw_errno_t err; mgw_unlink (filename, &err); return; } buffer = g_string_new ("\n"); g_string_append (buffer, "<" ELM_CONFIG " " PROP_VERSION "=\"" VALUE_VERSION "\">\n"); if (ui_config && (ui_config->expanded_rows || ui_config->selected_row)) { g_string_append (buffer, " <" ELM_UI); if (ui_config->expanded_rows) { GSList *l; g_string_append (buffer, " " PROP_EXPANDED_ROWS "=\""); for (l = ui_config->expanded_rows; l != nullptr; l = l->next) { GtkTreePath *path = (GtkTreePath*) l->data; char *tmp = gtk_tree_path_to_string (path); if (l != ui_config->expanded_rows) g_string_append (buffer, ";"); if (tmp) g_string_append (buffer, tmp); g_free (tmp); } g_string_append (buffer, "\""); } if (ui_config->selected_row) { char *tmp = gtk_tree_path_to_string (ui_config->selected_row); if (tmp) g_string_append_printf (buffer, " " PROP_SELECTED_ROW "=\"%s\"", tmp); g_free (tmp); } g_string_append (buffer, "/>\n"); } do { if (file_list_iter_is_auto (list, &iter)) break; format_item (list, &iter, buffer, 2); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list), &iter)); g_string_append (buffer, "\n"); if (!moo_save_config_file (filename, buffer->str, buffer->len, &error)) { g_critical ("could not save file %s: %s", filename, moo_error_message (error)); g_error_free (error); } g_string_free (buffer, TRUE); } static GtkTreePath * file_list_add_group (FileList *list, GtkTreePath *path) { GtkTreePath *parent = nullptr; GtkTreeIter parent_iter, new_iter; GtkTreeIter *piter = nullptr; Group *group; int index = -1; if (path) { GtkTreeIter iter; Item *item; gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, path); item = get_item_at_iter (list, &iter); if (ITEM_IS_GROUP (item)) { parent = gtk_tree_path_copy (path); index = 0; } else if (!file_list_iter_is_auto (list, &iter)) { GtkTreeIter parent_iter; if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (list), &parent_iter, &iter)) { parent = gtk_tree_path_copy (path); gtk_tree_path_up (parent); index = gtk_tree_path_get_indices (path)[gtk_tree_path_get_depth (path)-1] + 1; } else { index = gtk_tree_path_get_indices (path)[0] + 1; } } } if (parent) { gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &parent_iter, parent); piter = &parent_iter; } if (index < 0) index = list->n_user_items; group = group_new ("Group"); file_list_insert_row (list, ITEM (group), &new_iter, piter, index); item_unref (ITEM (group)); return gtk_tree_model_get_path (GTK_TREE_MODEL (list), &new_iter); } static void file_list_try_remove_item (FileList *list, GtkTreePath *path) { GtkTreeIter iter; if (gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, path) && !file_list_iter_is_auto (list, &iter)) { file_list_remove_row (list, &iter); window_plugin_queue_update (list->plugin); } } static void file_list_remove_items (FileList *list, GList *paths) { GSList *rows = nullptr; while (paths) { GtkTreeRowReference *row; row = gtk_tree_row_reference_new (GTK_TREE_MODEL (list), (GtkTreePath*) paths->data); if (row) rows = g_slist_prepend (rows, row); paths = paths->next; } rows = g_slist_reverse (rows); while (rows) { GtkTreePath *path = nullptr; if (gtk_tree_row_reference_valid ((GtkTreeRowReference*) rows->data)) path = gtk_tree_row_reference_get_path ((GtkTreeRowReference*) rows->data); if (path) { file_list_try_remove_item (list, path); gtk_tree_path_free (path); } gtk_tree_row_reference_free ((GtkTreeRowReference*) rows->data); rows = g_slist_delete_link (rows, rows); } } static gboolean drag_source_row_draggable (G_GNUC_UNUSED GtkTreeDragSource *drag_source, G_GNUC_UNUSED GtkTreePath *path) { return TRUE; } static gboolean drag_source_drag_data_get (GtkTreeDragSource *drag_source, GtkTreePath *path, GtkSelectionData *selection_data) { if (selection_data->target == TREE_MODEL_ROW_ATOM) { gtk_tree_set_row_drag_data (selection_data, GTK_TREE_MODEL (drag_source), path); return TRUE; } else if (selection_data->target == moo_atom_uri_list ()) { Item *item = get_item_at_path (FILE_LIST (drag_source), path); gstr uri; const char *uris[2] = { nullptr, nullptr }; if (ITEM_IS_FILE (item)) { uri = file_get_uri (FILE_ITEM (item)); uris[0] = !uri.empty() ? uri.get() : nullptr; } if (uris[0]) { gtk_selection_data_set_uris (selection_data, (char**) uris); return TRUE; } } return FALSE; } static gboolean drag_source_drag_data_delete (G_GNUC_UNUSED GtkTreeDragSource *drag_source, G_GNUC_UNUSED GtkTreePath *path) { return FALSE; } static void copy_row_children (FileList *list, GtkTreeIter *source, GtkTreeIter *dest) { GtkTreeIter child; if (gtk_tree_model_iter_children (GTK_TREE_MODEL (list), &child, source)) do { GtkTreeIter iter; Item *item = get_item_at_iter (list, &child); gtk_tree_store_append (GTK_TREE_STORE (list), &iter, dest); gtk_tree_store_set (GTK_TREE_STORE (list), &iter, COLUMN_ITEM, item, COLUMN_TOOLTIP, item_get_tooltip (item), -1); copy_row_children (list, &child, &iter); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (list), &child)); } static void copy_row (FileList *list, GtkTreePath *source, GtkTreePath *parent, int index) { GtkTreeRowReference *source_row; GtkTreeIter iter, parent_iter; GtkTreeIter *piter = nullptr; Item *item; gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, source); item = get_item_at_iter (list, &iter); g_return_if_fail (item != nullptr); source_row = gtk_tree_row_reference_new (GTK_TREE_MODEL (list), source); if (parent) { gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &parent_iter, parent); piter = &parent_iter; } file_list_insert_row (list, item, &iter, piter, index); parent_iter = iter; source = gtk_tree_row_reference_get_path (source_row); gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, source); copy_row_children (list, &iter, &parent_iter); gtk_tree_path_free (source); } static gboolean move_row (FileList *list, GtkTreePath *source, GtkTreePath *parent, int index) { GtkTreePath *source_parent = nullptr; gboolean same_parent = FALSE; GtkTreeIter iter; Item *item; gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, source); item = get_item_at_iter (list, &iter); if (ITEM_IS_FILE (item) && FILE_ITEM (item)->doc) { gstr uri = file_get_uri (FILE_ITEM (item)); if (uri.empty()) return FALSE; if (!FILE_ITEM (item)->uri) file_set_uri (FILE_ITEM (item), uri.get()); file_list_row_data.set(FILE_ITEM (item)->doc, nullptr); file_set_doc (FILE_ITEM (item), nullptr); } if (gtk_tree_path_get_depth (source) > 1) { source_parent = gtk_tree_path_copy (source); gtk_tree_path_up (source_parent); } if (!source_parent && !parent) same_parent = TRUE; else if (source_parent && parent && gtk_tree_path_compare (source_parent, parent) == 0) same_parent = TRUE; if (same_parent) { GtkTreeIter *piter = nullptr; gboolean first_user = FALSE; if (parent) { gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, parent); piter = &iter; } else if (file_list_iter_is_auto (list, &iter)) { list->n_user_items += 1; first_user = list->n_user_items == 1; } if (index >= gtk_tree_model_iter_n_children (GTK_TREE_MODEL (list), piter) || index < 0) { gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, source); gtk_tree_store_move_before (GTK_TREE_STORE (list), &iter, nullptr); } else { GtkTreeIter ch_iter; gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (list), &ch_iter, piter, index); gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, source); gtk_tree_store_move_before (GTK_TREE_STORE (list), &iter, &ch_iter); } if (first_user) gtk_tree_store_insert (GTK_TREE_STORE (list), &iter, nullptr, list->n_user_items); } else { GtkTreeRowReference *row; row = gtk_tree_row_reference_new (GTK_TREE_MODEL (list), source); copy_row (list, source, parent, index); source = gtk_tree_row_reference_get_path (row); gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, source); file_list_remove_row (list, &iter); gtk_tree_path_free (source); gtk_tree_row_reference_free (row); } if (source_parent) gtk_tree_path_free (source_parent); check_list (list); return TRUE; } static gboolean uri_is_directory (const char *uri) { char *filename; gboolean retval = FALSE; filename = g_filename_from_uri (uri, nullptr, nullptr); if (filename) retval = g_file_test (filename, G_FILE_TEST_IS_DIR); g_free (filename); return retval; } static void add_row_from_dir_uri (G_GNUC_UNUSED FileList *list, G_GNUC_UNUSED const char *uri, G_GNUC_UNUSED GtkTreeIter *iter, G_GNUC_UNUSED GtkTreeIter *parent, G_GNUC_UNUSED int index) { #if 0 /* TODO read files */ Group *grp; char *basename; return; basename = uri_get_basename (uri); grp = group_new (basename); file_list_insert_row (list, ITEM (grp), iter, parent, index); item_unref (ITEM (grp)); g_free (basename); #endif } static void add_row_from_file_uri (FileList *list, const char *uri, GtkTreeIter *iter, GtkTreeIter *parent, int index) { Item *item = file_new_uri (uri); file_list_insert_row (list, item, iter, parent, index); item_unref (item); } static gboolean add_row_from_uri (FileList *list, const char *uri, GtkTreePath *parent, int index) { GtkTreeIter iter, dummy; GtkTreeIter *piter = nullptr; g_return_val_if_fail (uri != nullptr, FALSE); if (parent) { gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, parent); piter = &iter; } if (uri_is_directory (uri)) add_row_from_dir_uri (list, uri, &dummy, piter, index); else add_row_from_file_uri (list, uri, &dummy, piter, index); return TRUE; } static gboolean find_drop_destination (FileList *list, GtkTreePath *dest, GtkTreePath **parent_path, int *index) { int n_children; Group *parent_group = nullptr; GtkTreeIter parent_iter; *parent_path = nullptr; *index = 0; if (gtk_tree_path_get_depth (dest) > 1) { GtkTreePath *parent; Item *parent_item; parent = gtk_tree_path_copy (dest); gtk_tree_path_up (parent); if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &parent_iter, parent)) { gtk_tree_path_free (parent); return FALSE; } parent_item = get_item_at_iter (list, &parent_iter); if (ITEM_IS_GROUP (parent_item)) { parent_group = GROUP_ITEM (parent_item); *index = gtk_tree_path_get_indices (dest)[gtk_tree_path_get_depth (dest) - 1]; } else if (gtk_tree_path_get_depth (parent) > 1) { *index = gtk_tree_path_get_indices (parent)[gtk_tree_path_get_depth (parent) - 1]; gtk_tree_path_up (parent); if (!gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &parent_iter, parent)) { gtk_tree_path_free (parent); g_return_val_if_reached (FALSE); } parent_item = get_item_at_iter (list, &parent_iter); if (!ITEM_IS_GROUP (parent_item)) { gtk_tree_path_free (parent); g_return_val_if_reached (FALSE); } parent_group = GROUP_ITEM (parent_item); } else { parent_group = nullptr; *index = gtk_tree_path_get_indices (parent)[0]; } gtk_tree_path_free (parent); } else { *index = gtk_tree_path_get_indices (dest)[0]; } if (parent_group) { n_children = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (list), &parent_iter); *parent_path = gtk_tree_model_get_path (GTK_TREE_MODEL (list), &parent_iter); } else { n_children = list->n_user_items; } *index = CLAMP (*index, 0, n_children); return TRUE; } static gboolean path_is_descendant (GtkTreePath *path, GtkTreePath *ancestor) { return gtk_tree_path_compare (path, ancestor) == 0 || gtk_tree_path_is_descendant (path, ancestor); } static gboolean drop_uris (FileList *list, GtkTreePath *dest, char **uris) { GtkTreePath *parent_path = nullptr; int index = 0; if (!find_drop_destination (list, dest, &parent_path, &index)) return FALSE; for ( ; uris && *uris; ++uris) { GtkTreeIter iter; if (!file_list_find_uri (list, *uris, &iter) && add_row_from_uri (list, *uris, parent_path, index)) { index += 1; } } if (parent_path) gtk_tree_path_free (parent_path); return TRUE; } static gboolean drop_tree_model_row (FileList *list, GtkTreePath *dest, GtkTreePath *source) { GtkTreePath *parent_path = nullptr; int index; gboolean retval; if (!get_item_at_path (list, source)) return FALSE; if (!find_drop_destination (list, dest, &parent_path, &index)) return FALSE; if (parent_path && path_is_descendant (parent_path, source)) { gtk_tree_path_free (parent_path); return FALSE; } retval = move_row (list, source, parent_path, index); if (parent_path) gtk_tree_path_free (parent_path); return retval; } static int cmp_uris (const void *p1, const void *p2) { const char *const *s1 = (const char *const *) p1; const char *const *s2 = (const char *const *) p2; return strcmp (*s1, *s2); } static gboolean drag_dest_drag_data_received (GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data) { if (selection_data->target == TREE_MODEL_ROW_ATOM) { GtkTreePath *path = nullptr; gboolean retval; if (!gtk_tree_get_row_drag_data (selection_data, nullptr, &path)) return FALSE; if ((retval = drop_tree_model_row (FILE_LIST (drag_dest), dest, path))) window_plugin_queue_update (FILE_LIST (drag_dest)->plugin); gtk_tree_path_free (path); return retval; } else if (selection_data->target == moo_atom_uri_list ()) { char **uris; gboolean retval = FALSE; if (!(uris = gtk_selection_data_get_uris (selection_data))) return FALSE; if (uris[0]) { qsort (uris, g_strv_length (uris), sizeof (char*), cmp_uris); if ((retval = drop_uris (FILE_LIST (drag_dest), dest, uris))) window_plugin_queue_update (FILE_LIST (drag_dest)->plugin); } g_strfreev (uris); return retval; } return FALSE; } static gboolean drag_dest_row_drop_possible (G_GNUC_UNUSED GtkTreeDragDest *drag_dest, G_GNUC_UNUSED GtkTreePath *dest_path, G_GNUC_UNUSED GtkSelectionData *selection_data) { return TRUE; } gboolean _moo_str_semicase_compare (const char *string, const char *key) { gboolean has_upper; const char *p; g_return_val_if_fail (string != nullptr, FALSE); g_return_val_if_fail (key != nullptr, FALSE); for (p = key, has_upper = FALSE; *p && !has_upper; ++p) has_upper = g_ascii_isupper (*p); if (has_upper) return strncmp (string, key, strlen (key)) == 0; else return g_ascii_strncasecmp (string, key, strlen (key)) == 0; } static gboolean row_separator_func (GtkTreeModel *model, GtkTreeIter *iter) { Item *item = get_item_at_iter (FILE_LIST (model), iter); return item == nullptr; } static gboolean tree_view_search_equal_func (GtkTreeModel *model, G_GNUC_UNUSED int column, const char *key, GtkTreeIter *iter) { const char *compare_with = nullptr; Item *item; item = get_item_at_iter (FILE_LIST (model), iter); if (ITEM_IS_FILE (item)) compare_with = FILE_ITEM (item)->display_basename; else if (ITEM_IS_GROUP (item)) compare_with = GROUP_ITEM (item)->name; if (compare_with) return !_moo_str_semicase_compare (compare_with, key); else return TRUE; } static void pixbuf_data_func (G_GNUC_UNUSED GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter) { Item *item = get_item_at_iter (FILE_LIST (model), iter); if (ITEM_IS_GROUP (item)) g_object_set (cell, "stock-id", GTK_STOCK_DIRECTORY, nullptr); else if (ITEM_IS_FILE (item)) g_object_set (cell, "stock-id", GTK_STOCK_FILE, nullptr); } static void text_data_func (G_GNUC_UNUSED GtkTreeViewColumn *column, GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter) { Item *item = get_item_at_iter (FILE_LIST (model), iter); if (ITEM_IS_GROUP (item)) g_object_set (cell, "text", GROUP_ITEM (item)->name, nullptr); else if (ITEM_IS_FILE (item)) g_object_set (cell, "text", FILE_ITEM (item)->display_basename, nullptr); } static void text_cell_edited (GtkCellRenderer *cell, const char *path_string, const char *new_text, WindowPlugin *plugin) { GtkTreeIter iter; Item *item; GtkTreePath *path; g_object_set (cell, "editable", FALSE, nullptr); path = gtk_tree_path_new_from_string (path_string); g_return_if_fail (path != nullptr); gtk_tree_model_get_iter (GTK_TREE_MODEL (plugin->list), &iter, path); item = get_item_at_iter (plugin->list, &iter); g_return_if_fail (ITEM_IS_GROUP (item)); MOO_ASSIGN_STRING (GROUP_ITEM (item)->name, new_text); gtk_tree_model_row_changed (GTK_TREE_MODEL (plugin->list), path, &iter); gtk_tree_path_free (path); } static void text_cell_editing_canceled (GtkCellRenderer *cell) { g_object_set (cell, "editable", FALSE, nullptr); } static void start_edit (WindowPlugin *plugin, GtkTreePath *path) { g_object_set (plugin->text_cell, "editable", TRUE, nullptr); gtk_tree_view_set_cursor_on_cell (plugin->treeview, path, plugin->column, plugin->text_cell, TRUE); } static GList * get_selected_rows (WindowPlugin *plugin) { GtkTreeSelection *selection; selection = gtk_tree_view_get_selection (plugin->treeview); return gtk_tree_selection_get_selected_rows (selection, nullptr); } static void path_list_free (GList *paths) { g_list_foreach (paths, (GFunc) gtk_tree_path_free, nullptr); g_list_free (paths); } static void rename_activated (G_GNUC_UNUSED GtkWidget *menuitem, WindowPlugin *plugin) { GList *selected; Item *item; selected = get_selected_rows (plugin); if (!selected || selected->next) { path_list_free (selected); g_return_if_fail (selected && !selected->next); } item = get_item_at_path (plugin->list, (GtkTreePath*) selected->data); if (!ITEM_IS_GROUP (item)) { path_list_free (selected); g_return_if_fail (ITEM_IS_GROUP (item)); } start_edit (plugin, (GtkTreePath*) selected->data); path_list_free (selected); } static void add_group_activated (G_GNUC_UNUSED GtkWidget *menuitem, WindowPlugin *plugin) { GtkTreePath *new_path, *path; GList *selected; selected = get_selected_rows (plugin); if (selected) path = (GtkTreePath*) g_list_last (selected)->data; else path = nullptr; new_path = file_list_add_group (plugin->list, path); if (new_path) { GtkTreeIter iter, parent; gtk_tree_model_get_iter (GTK_TREE_MODEL (plugin->list), &iter, new_path); if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (plugin->list), &parent, &iter)) { GtkTreePath *parent_path = gtk_tree_model_get_path (GTK_TREE_MODEL (plugin->list), &parent); if (!gtk_tree_view_row_expanded (plugin->treeview, parent_path)) gtk_tree_view_expand_row (plugin->treeview, parent_path, FALSE); gtk_tree_path_free (parent_path); } start_edit (plugin, new_path); gtk_tree_path_free (new_path); } path_list_free (selected); } static void remove_activated (G_GNUC_UNUSED GtkWidget *menuitem, WindowPlugin *plugin) { GList *selected; selected = get_selected_rows (plugin); file_list_remove_items (plugin->list, selected); window_plugin_queue_update (plugin); path_list_free (selected); } static void open_file (WindowPlugin *plugin, GtkTreePath *path) { Item *item; item = get_item_at_path (plugin->list, path); g_return_if_fail (item != nullptr); if (ITEM_IS_FILE (item)) { if (FILE_ITEM (item)->doc) { moo_editor_set_active_doc (moo_editor_instance (), FILE_ITEM (item)->doc); gtk_widget_grab_focus (GTK_WIDGET (FILE_ITEM (item)->doc)); } else { moo_editor_open_uri (moo_editor_instance (), FILE_ITEM (item)->uri, nullptr, -1, MOO_WIN_PLUGIN (plugin)->window); } } else if (ITEM_IS_GROUP (item)) { GtkTreeIter iter, child; gtk_tree_model_get_iter (GTK_TREE_MODEL (plugin->list), &iter, path); if (gtk_tree_model_iter_children (GTK_TREE_MODEL (plugin->list), &child, &iter)) { do { GtkTreePath *child_path; child_path = gtk_tree_model_get_path (GTK_TREE_MODEL (plugin->list), &child); open_file (plugin, child_path); gtk_tree_path_free (child_path); } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (plugin->list), &child)); } } else { g_return_if_reached (); } } static void open_activated (G_GNUC_UNUSED GtkWidget *menuitem, WindowPlugin *plugin) { GList *selected, *l; selected = get_selected_rows (plugin); for (l = selected; l != nullptr; l = l->next) open_file (plugin, (GtkTreePath*) l->data); path_list_free (selected); } static gboolean can_open (G_GNUC_UNUSED FileList *list, GList *paths) { /* XXX */ return paths != nullptr; } static gboolean can_remove (FileList *list, GList *paths) { while (paths) { GtkTreeIter iter; GtkTreePath *path = (GtkTreePath*) paths->data; gtk_tree_model_get_iter (GTK_TREE_MODEL (list), &iter, path); if (!file_list_iter_is_auto (list, &iter)) return TRUE; paths = paths->next; } return FALSE; } static void popup_menu (WindowPlugin *plugin, GList *selected, int button, guint32 time) { GtkWidget *menu, *menuitem; GtkTreePath *single_path; Item *single_item; single_path = (selected && !selected->next) ? (GtkTreePath*) selected->data : nullptr; single_item = single_path ? get_item_at_path (plugin->list, single_path) : nullptr; menu = gtk_menu_new (); if (can_open (plugin->list, selected)) { menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_OPEN, nullptr); g_signal_connect (menuitem, "activate", G_CALLBACK (open_activated), plugin); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); } menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_ADD, nullptr); gtk_label_set_text (GTK_LABEL (GTK_BIN (menuitem)->child), "Add Group"); g_signal_connect (menuitem, "activate", G_CALLBACK (add_group_activated), plugin); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); if (selected) { menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_REMOVE, nullptr); g_signal_connect (menuitem, "activate", G_CALLBACK (remove_activated), plugin); gtk_widget_set_sensitive (menuitem, can_remove (plugin->list, selected)); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); } if (single_item && ITEM_IS_GROUP (single_item)) { menuitem = gtk_image_menu_item_new_from_stock (GTK_STOCK_EDIT, nullptr); gtk_label_set_text (GTK_LABEL (GTK_BIN (menuitem)->child), "Rename"); g_signal_connect (menuitem, "activate", G_CALLBACK (rename_activated), plugin); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); } gtk_widget_show_all (menu); gtk_menu_popup (GTK_MENU (menu), nullptr, nullptr, nullptr, nullptr, button, time); } static gboolean treeview_button_press (GtkTreeView *treeview, GdkEventButton *event, WindowPlugin *plugin) { GtkTreeSelection *selection; GtkTreePath *path = nullptr; GList *selected; int x, y; if (event->type != GDK_BUTTON_PRESS || event->button != 3) return FALSE; gtk_tree_view_get_path_at_pos (treeview, (int) event->x, (int) event->y, &path, nullptr, &x, &y); selection = gtk_tree_view_get_selection (treeview); if (!path) gtk_tree_selection_unselect_all (selection); else if (!gtk_tree_selection_path_is_selected (selection, path)) gtk_tree_view_set_cursor (treeview, path, plugin->column, FALSE); selected = gtk_tree_selection_get_selected_rows (selection, nullptr); popup_menu (plugin, selected, event->button, event->time); if (path) gtk_tree_path_free (path); g_list_foreach (selected, (GFunc) gtk_tree_path_free, nullptr); g_list_free (selected); return TRUE; } static void treeview_row_activated (WindowPlugin *plugin, GtkTreePath *path) { Item *item; item = get_item_at_path (plugin->list, path); g_return_if_fail (item != nullptr); if (ITEM_IS_FILE (item)) open_file (plugin, path); } static void create_treeview (WindowPlugin *plugin) { GtkTreeSelection *selection; GtkCellRenderer *cell; GtkTargetEntry targets[] = { { (char*) "GTK_TREE_MODEL_ROW", GTK_TARGET_SAME_WIDGET, 0 }, { (char*) "text/uri-list", 0, 0 } }; plugin->treeview = GTK_TREE_VIEW (gtk_tree_view_new ()); gtk_tree_view_set_headers_visible (plugin->treeview, FALSE); gtk_tree_view_set_row_separator_func (plugin->treeview, (GtkTreeViewRowSeparatorFunc) row_separator_func, nullptr, nullptr); gtk_tree_view_set_search_equal_func (plugin->treeview, (GtkTreeViewSearchEqualFunc) tree_view_search_equal_func, nullptr, nullptr); gtk_tree_view_set_tooltip_column (plugin->treeview, COLUMN_TOOLTIP); selection = gtk_tree_view_get_selection (plugin->treeview); gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); g_signal_connect (plugin->treeview, "button-press-event", G_CALLBACK (treeview_button_press), plugin); g_signal_connect_swapped (plugin->treeview, "row-activated", G_CALLBACK (treeview_row_activated), plugin); g_signal_connect_swapped (plugin->treeview, "row-expanded", G_CALLBACK (window_plugin_queue_update_ui), plugin); g_signal_connect_swapped (plugin->treeview, "row-collapsed", G_CALLBACK (window_plugin_queue_update_ui), plugin); gtk_tree_view_enable_model_drag_dest (plugin->treeview, targets, G_N_ELEMENTS (targets), GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_PRIVATE); gtk_tree_view_enable_model_drag_source (plugin->treeview, GDK_BUTTON1_MASK, targets, G_N_ELEMENTS (targets), GDK_ACTION_COPY | GDK_ACTION_MOVE | GDK_ACTION_LINK | GDK_ACTION_PRIVATE); plugin->column = gtk_tree_view_column_new (); gtk_tree_view_append_column (plugin->treeview, plugin->column); _moo_tree_view_setup_expander (plugin->treeview, plugin->column); cell = gtk_cell_renderer_pixbuf_new (); gtk_tree_view_column_pack_start (plugin->column, cell, FALSE); gtk_tree_view_column_set_cell_data_func (plugin->column, cell, (GtkTreeCellDataFunc) pixbuf_data_func, nullptr, nullptr); plugin->text_cell = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (plugin->column, plugin->text_cell, TRUE); gtk_tree_view_column_set_cell_data_func (plugin->column, plugin->text_cell, (GtkTreeCellDataFunc) text_data_func, nullptr, nullptr); g_signal_connect (plugin->text_cell, "edited", G_CALLBACK (text_cell_edited), plugin); g_signal_connect (plugin->text_cell, "editing-canceled", G_CALLBACK (text_cell_editing_canceled), plugin); } static void create_model (WindowPlugin *plugin) { plugin->list = (FileList*) g_object_new (file_list_get_type (), (const char*) nullptr); plugin->list->plugin = plugin; file_list_load_config (plugin->list, plugin->filename, &plugin->ui_config); gtk_tree_view_set_model (plugin->treeview, GTK_TREE_MODEL (plugin->list)); } static int compare_docs_by_name (MooEdit *doc1, MooEdit *doc2) { const char *name1, *name2; char *key1, *key2; int retval; name1 = moo_edit_get_display_basename (doc1); name2 = moo_edit_get_display_basename (doc2); key1 = g_utf8_collate_key_for_filename (name1, -1); key2 = g_utf8_collate_key_for_filename (name2, -1); retval = strcmp (key1, key2); g_free (key2); g_free (key1); return retval; } static gboolean do_update (WindowPlugin *plugin) { MooEditArray *docs; GSList *list; guint i; plugin->update_idle = 0; docs = moo_edit_window_get_docs (MOO_WIN_PLUGIN (plugin)->window); for (i = 0, list = nullptr; i < docs->n_elms; ++i) list = g_slist_prepend (list, docs->elms[i]); list = g_slist_sort (list, (GCompareFunc) compare_docs_by_name); file_list_update (plugin->list, list); g_slist_free (list); moo_edit_array_free (docs); return FALSE; } static void window_plugin_queue_update (WindowPlugin *plugin) { g_return_if_fail (plugin != nullptr); if (!plugin->update_idle) plugin->update_idle = g_idle_add ((GSourceFunc) do_update, plugin); window_plugin_queue_update_ui (plugin); } static void load_ui_config (WindowPlugin *plugin) { if (plugin->ui_config) { UIConfig *cfg = plugin->ui_config; plugin->ui_config = nullptr; while (cfg->expanded_rows) { GtkTreePath *path = (GtkTreePath*) cfg->expanded_rows->data; gtk_tree_view_expand_row (plugin->treeview, path, FALSE); gtk_tree_path_free (path); cfg->expanded_rows = g_slist_delete_link (cfg->expanded_rows, cfg->expanded_rows); } if (cfg->selected_row) gtk_tree_view_set_cursor (plugin->treeview, cfg->selected_row, nullptr, FALSE); else _moo_tree_view_select_first (plugin->treeview); gtk_tree_path_free (cfg->selected_row); g_free (cfg); } else { _moo_tree_view_select_first (plugin->treeview); } } static gboolean check_row_expanded (G_GNUC_UNUSED GtkTreeModel *model, GtkTreePath *path, G_GNUC_UNUSED GtkTreeIter *iter, WindowPlugin *plugin) { if (gtk_tree_view_row_expanded (plugin->treeview, path)) plugin->ui_config->expanded_rows = g_slist_prepend (plugin->ui_config->expanded_rows, gtk_tree_path_copy (path)); return FALSE; } static gboolean do_update_ui (WindowPlugin *plugin) { plugin->update_ui_idle = 0; if (plugin->first_time_show) { plugin->first_time_show = FALSE; load_ui_config (plugin); } else { ui_config_free (plugin->ui_config); plugin->ui_config = ui_config_new (); gtk_tree_model_foreach (GTK_TREE_MODEL (plugin->list), (GtkTreeModelForeachFunc) check_row_expanded, plugin); plugin->ui_config->expanded_rows = g_slist_reverse (plugin->ui_config->expanded_rows); } return FALSE; } static void window_plugin_queue_update_ui (WindowPlugin *plugin) { g_return_if_fail (plugin != nullptr); if (!plugin->update_ui_idle) plugin->update_ui_idle = g_idle_add ((GSourceFunc) do_update_ui, plugin); } static gboolean file_list_window_plugin_create (WindowPlugin *plugin) { MooPane *pane; MooPaneLabel *label; GtkWidget *scrolled_window; MooEditWindow *window; window = MOO_WIN_PLUGIN (plugin)->window; g_return_val_if_fail (MOO_IS_EDIT_WINDOW (window), FALSE); plugin->filename = moo_get_named_user_data_file (CONFIG_FILE); create_treeview (plugin); create_model (plugin); scrolled_window = gtk_scrolled_window_new (nullptr, nullptr); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (plugin->treeview)); gtk_widget_show_all (scrolled_window); label = moo_pane_label_new (GTK_STOCK_DIRECTORY, nullptr, _("File List"), _("File List")); moo_edit_window_add_pane (window, FILE_LIST_PLUGIN_ID, scrolled_window, label, MOO_PANE_POS_RIGHT); moo_pane_label_free (label); pane = moo_big_paned_find_pane (window->paned, GTK_WIDGET (scrolled_window), nullptr); moo_pane_set_drag_dest (pane); plugin->first_time_show = TRUE; g_signal_connect_swapped (window, "new-doc", G_CALLBACK (window_plugin_queue_update), plugin); g_signal_connect_swapped (window, "close-doc", G_CALLBACK (window_plugin_queue_update), plugin); window_plugin_queue_update (plugin); return TRUE; } static void file_list_window_plugin_destroy (WindowPlugin *plugin) { if (plugin->update_idle) g_source_remove (plugin->update_idle); if (plugin->update_ui_idle) g_source_remove (plugin->update_ui_idle); file_list_save_config (plugin->list, plugin->filename, plugin->ui_config); file_list_shutdown (plugin->list); g_object_unref (plugin->list); moo_edit_window_remove_pane (MOO_WIN_PLUGIN (plugin)->window, FILE_LIST_PLUGIN_ID); g_signal_handlers_disconnect_by_func (MOO_WIN_PLUGIN (plugin)->window, (gpointer) window_plugin_queue_update, plugin); ui_config_free (plugin->ui_config); g_free (plugin->filename); } static gboolean file_list_plugin_init (G_GNUC_UNUSED FileListPlugin *plugin) { return TRUE; } static void file_list_plugin_deinit (G_GNUC_UNUSED FileListPlugin *plugin) { } MOO_PLUGIN_DEFINE_INFO (file_list, N_("File List"), N_("List of files"), "Yevgen Muntyan ", MOO_VERSION) MOO_WIN_PLUGIN_DEFINE (FileList, file_list) MOO_PLUGIN_DEFINE (FileList, file_list, nullptr, nullptr, nullptr, nullptr, nullptr, file_list_window_plugin_get_type (), 0) gboolean _moo_file_list_plugin_init (void) { MooPluginParams params = {TRUE, TRUE}; return moo_plugin_register (FILE_LIST_PLUGIN_ID, file_list_plugin_get_type (), &file_list_plugin_info, ¶ms); }