diff --git a/katze/katze-array.c b/katze/katze-array.c index 602c164..7912d8e 100644 --- a/katze/katze-array.c +++ b/katze/katze-array.c @@ -42,6 +42,9 @@ struct _KatzeArrayClass (*add_item) (KatzeArray* array, gpointer item); void + (*update_item) (KatzeArray* array, + gpointer item); + void (*remove_item) (KatzeArray* array, gpointer item); void @@ -59,6 +62,7 @@ G_DEFINE_TYPE (KatzeArray, katze_array, KATZE_TYPE_ITEM); enum { ADD_ITEM, + UPDATE_ITEM, REMOVE_ITEM, MOVE_ITEM, CLEAR, @@ -95,6 +99,13 @@ _katze_array_add_item (KatzeArray* array, } static void +_katze_array_update_item (KatzeArray* array, + gpointer item) +{ + _katze_array_update (array); +} + +static void _katze_array_remove_item (KatzeArray* array, gpointer item) { @@ -147,6 +158,17 @@ katze_array_class_init (KatzeArrayClass* class) G_TYPE_NONE, 1, G_TYPE_POINTER); + signals[UPDATE_ITEM] = g_signal_new ( + "update-item", + G_TYPE_FROM_CLASS (class), + (GSignalFlags)(G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION), + G_STRUCT_OFFSET (KatzeArrayClass, add_item), + 0, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + signals[REMOVE_ITEM] = g_signal_new ( "remove-item", G_TYPE_FROM_CLASS (class), @@ -213,6 +235,7 @@ katze_array_class_init (KatzeArrayClass* class) gobject_class->finalize = katze_array_finalize; class->add_item = _katze_array_add_item; + class->update_item = _katze_array_update_item; class->remove_item = _katze_array_remove_item; class->move_item = _katze_array_move_item; class->clear = _katze_array_clear; @@ -301,6 +324,23 @@ katze_array_add_item (KatzeArray* array, } /** + * katze_array_update_item: + * @array: a #KatzeArray + * @item: an item + * + * Notify an update of the item of the array. + * + **/ +void +katze_array_update_item (KatzeArray* array, + gpointer item) +{ + g_return_if_fail (KATZE_IS_ARRAY (array)); + + g_signal_emit (array, signals[UPDATE_ITEM], 0, item); +} + +/** * katze_array_remove_item: * @array: a #KatzeArray * @item: an item diff --git a/katze/katze-array.h b/katze/katze-array.h index a2f601c..9472012 100644 --- a/katze/katze-array.h +++ b/katze/katze-array.h @@ -47,6 +47,10 @@ katze_array_add_item (KatzeArray* array, gpointer item); void +katze_array_update_item (KatzeArray* array, + gpointer item); + +void katze_array_remove_item (KatzeArray* array, gpointer item); diff --git a/katze/katze-item.c b/katze/katze-item.c index 062010f..1105158 100644 --- a/katze/katze-item.c +++ b/katze/katze-item.c @@ -314,11 +314,12 @@ void katze_item_set_name (KatzeItem* item, const gchar* name) { + gchar* pname = item->name; g_return_if_fail (KATZE_IS_ITEM (item)); katze_assign (item->name, g_strdup (name)); - if (item->parent) - katze_array_update ((KatzeArray*)item->parent); + if (item->parent && g_strcmp0(name, pname)) + katze_array_update((KatzeArray*)item->parent); g_object_notify (G_OBJECT (item), "name"); } @@ -418,11 +419,12 @@ void katze_item_set_icon (KatzeItem* item, const gchar* icon) { + gchar *picon = katze_item_get_meta_string (item, "icon"); g_return_if_fail (KATZE_IS_ITEM (item)); katze_item_set_meta_string (item, "icon", icon); - if (item->parent) - katze_array_update ((KatzeArray*)item->parent); + if (item->parent && g_strcmp0(icon, picon)) + katze_array_update_item ((KatzeArray*)item->parent, item); g_object_notify (G_OBJECT (item), "icon"); } diff --git a/katze/katze-utils.c b/katze/katze-utils.c index 39f65e8..4944aab 100644 --- a/katze/katze-utils.c +++ b/katze/katze-utils.c @@ -984,12 +984,99 @@ katze_tree_view_get_selected_iter (GtkTreeView* treeview, g_return_val_if_fail (GTK_IS_TREE_VIEW (treeview), FALSE); - if ((selection = gtk_tree_view_get_selection (treeview))) - if (gtk_tree_selection_get_selected (selection, model, iter)) - return TRUE; + selection = gtk_tree_view_get_selection(treeview); + + switch (gtk_tree_selection_get_mode (selection)) + { + default: + break; + + case GTK_SELECTION_SINGLE: + case GTK_SELECTION_BROWSE: + if (gtk_tree_selection_get_selected (selection, model, iter)) + return TRUE; + break; + + case GTK_SELECTION_MULTIPLE: + if (gtk_tree_selection_count_selected_rows (selection) == 1) + { + GtkTreeModel *stock_model; + GList *list = gtk_tree_selection_get_selected_rows (selection, &stock_model); + + if (model) + *model = stock_model; + + if (list) + { + GtkTreePath *path = (GtkTreePath *)g_list_nth_data (list, 0); + + if (path && (!iter || gtk_tree_model_get_iter (stock_model, iter, path))) + { + g_list_free_full(list, (GDestroyNotify) gtk_tree_path_free); + return TRUE; + } + + g_list_free_full(list, (GDestroyNotify) gtk_tree_path_free); + } + } + } + return FALSE; } +/** + * katze_tree_view_get_selected_rows: + * @treeview: a #GtkTreeView + * @model: a pointer to store the #GtkTreeModel, or %NULL + * @rows: a pointer to store the #GList of #GtkTreePath, or %NULL + * + * Determines whether there is a selection in @treeview + * and sets the @rows to the current selection. + * + * If there is a selection and @model is not %NULL, it is + * set to the model. If @model is %NULL, the @rows will be + * set to %NULL. + * + * Either @model or @rows or both can be %NULL in which case + * no value will be assigned in any case. + * + * When @rows is not %NULL it must be freed using: + * g_list_free_full (list, (GDestroyNotify) gtk_tree_path_free); + * + * Return value: the count of selected rows + * + * Since: 0.4.7.aau.1 + **/ + +gint +katze_tree_view_get_selected_rows (GtkTreeView* treeview, + GtkTreeModel** model, + GList** rows) +{ + gint count; + GtkTreeSelection* selection; + + if (model) + *model = NULL; + if (rows) + *rows = NULL; + + g_return_val_if_fail (GTK_IS_TREE_VIEW (treeview), 0); + + selection = gtk_tree_view_get_selection(treeview); + + count = gtk_tree_selection_count_selected_rows (selection); + if (count > 0) + { + if (model && rows) + { + *rows = gtk_tree_selection_get_selected_rows (selection, model); + } + } + + return count; +} + void katze_bookmark_populate_tree_view (KatzeArray* array, GtkTreeStore* model, diff --git a/katze/katze-utils.h b/katze/katze-utils.h index 0460331..558874a 100644 --- a/katze/katze-utils.h +++ b/katze/katze-utils.h @@ -88,6 +88,11 @@ katze_tree_view_get_selected_iter (GtkTreeView* treeview, GtkTreeModel** model, GtkTreeIter* iter); +gint +katze_tree_view_get_selected_rows (GtkTreeView* treeview, + GtkTreeModel** model, + GList** rows); + void katze_bookmark_populate_tree_view (KatzeArray* array, GtkTreeStore* model, diff --git a/midori/midori-bookmarks.c b/midori/midori-bookmarks.c index 41c4d50..91aca1c 100644 --- a/midori/midori-bookmarks.c +++ b/midori/midori-bookmarks.c @@ -32,7 +32,7 @@ midori_bookmarks_dbtracer (void* dummy, g_printerr ("%s\n", query); } -void +static void midori_bookmarks_add_item_cb (KatzeArray* array, KatzeItem* item, sqlite3* db) @@ -41,7 +41,58 @@ midori_bookmarks_add_item_cb (KatzeArray* array, katze_item_get_meta_integer (item, "parentid")); } -void +static void +midori_bookmarks_update_item_cb (KatzeArray* array, + KatzeItem* item, + sqlite3* db) +{ + midori_bookmarks_update_item_db (db, item); +} + +static void +midori_bookmarks_recursive_remove_childs_db (KatzeItem* item, + sqlite3* db) +{ + gint64 parentid = katze_item_get_meta_integer (item, "id"); + char* errmsg = NULL; + KatzeArray* array; + gchar* sqlcmd; + + sqlcmd = g_strdup_printf ("SELECT id FROM bookmarks" + "WHERE uri = '' AND parentid = %"G_GINT64_FORMAT ";", + parentid); + + array = katze_array_from_sqlite (db, sqlcmd); + g_free (sqlcmd); + + if (KATZE_IS_ARRAY (array)) + { + KatzeItem* child; + GList* list; + + KATZE_ARRAY_FOREACH_ITEM_L (child, array, list) + { + midori_bookmarks_recursive_remove_childs_db (child, db); + } + + g_list_free (list); + g_object_unref(array); + } + + sqlcmd = sqlite3_mprintf ( + "DELETE FROM bookmarks WHERE parentid = %" G_GINT64_FORMAT ";", + parentid); + + if (sqlite3_exec (db, sqlcmd, NULL, NULL, &errmsg) != SQLITE_OK) + { + g_printerr (_("Failed to remove bookmarks folder child items: %s\n"), errmsg); + sqlite3_free (errmsg); + } + + sqlite3_free (sqlcmd); +} + +static void midori_bookmarks_remove_item_cb (KatzeArray* array, KatzeItem* item, sqlite3* db) @@ -49,6 +100,8 @@ midori_bookmarks_remove_item_cb (KatzeArray* array, gchar* sqlcmd; char* errmsg = NULL; + if (KATZE_ITEM_IS_FOLDER(item)) + midori_bookmarks_recursive_remove_childs_db (item, db); sqlcmd = sqlite3_mprintf ( "DELETE FROM bookmarks WHERE id = %" G_GINT64_FORMAT ";", @@ -282,6 +335,8 @@ midori_bookmarks_new (char** errmsg) array = katze_array_new (KATZE_TYPE_ARRAY); g_signal_connect (array, "add-item", G_CALLBACK (midori_bookmarks_add_item_cb), db); + g_signal_connect (array, "update-item", + G_CALLBACK (midori_bookmarks_update_item_cb), db); g_signal_connect (array, "remove-item", G_CALLBACK (midori_bookmarks_remove_item_cb), db); g_object_set_data (G_OBJECT (array), "db", db); diff --git a/midori/midori-bookmarks.h b/midori/midori-bookmarks.h index 63eb033..d12946a 100644 --- a/midori/midori-bookmarks.h +++ b/midori/midori-bookmarks.h @@ -16,16 +16,6 @@ #include #include -void -midori_bookmarks_add_item_cb (KatzeArray* array, - KatzeItem* item, - sqlite3* db); - -void -midori_bookmarks_remove_item_cb (KatzeArray* array, - KatzeItem* item, - sqlite3* db); - KatzeArray* midori_bookmarks_new (char** errmsg); diff --git a/midori/midori-browser.c b/midori/midori-browser.c index b5ff23c..6439ca9 100644 --- a/midori/midori-browser.c +++ b/midori/midori-browser.c @@ -166,10 +166,6 @@ midori_bookmarks_import_array_db (sqlite3* db, KatzeArray* array, gint64 parentid); -gboolean -midori_bookmarks_update_item_db (sqlite3* db, - KatzeItem* item); - void midori_browser_open_bookmark (MidoriBrowser* browser, KatzeItem* item); @@ -484,6 +480,9 @@ midori_browser_update_history (KatzeItem* item, inter = ZEITGEIST_ZG_DELETE_EVENT; else g_assert_not_reached (); + g_assert (KATZE_IS_ITEM (item)); + if (KATZE_ITEM_IS_FOLDER (item)) + return; zeitgeist_log_insert_events_no_reply (zeitgeist_log_get_default (), zeitgeist_event_new_full (inter, ZEITGEIST_ZG_USER_ACTIVITY, "application://midori.desktop", @@ -1054,7 +1053,7 @@ midori_browser_edit_bookmark_dialog_new (MidoriBrowser* browser, { gint64 selected; - katze_item_set_name (bookmark, + katze_item_set_name (bookmark, gtk_entry_get_text (GTK_ENTRY (entry_title))); katze_item_set_meta_integer (bookmark, "toolbar", gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_toolbar))); @@ -1072,16 +1071,11 @@ midori_browser_edit_bookmark_dialog_new (MidoriBrowser* browser, if (new_bookmark) katze_array_add_item (browser->bookmarks, bookmark); else - midori_bookmarks_update_item_db (db, bookmark); - midori_browser_update_history (bookmark, "bookmark", new_bookmark ? "create" : "modify"); + katze_array_update_item (browser->bookmarks, bookmark); - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check_toolbar))) - if (!gtk_widget_get_visible (browser->bookmarkbar)) - _action_set_active (browser, "Bookmarkbar", TRUE); return_status = TRUE; } - if (gtk_widget_get_visible (browser->bookmarkbar)) - midori_bookmarkbar_populate (browser); + gtk_widget_destroy (dialog); return return_status; } @@ -4143,7 +4137,10 @@ midori_browser_bookmark_popup_item (GtkWidget* menu, else if (!KATZE_IS_ARRAY (item) && strcmp (stock_id, GTK_STOCK_DELETE)) gtk_widget_set_sensitive (menuitem, uri != NULL); g_object_set_data (G_OBJECT (menuitem), "KatzeItem", item); - g_signal_connect (menuitem, "activate", G_CALLBACK (callback), userdata); + if (callback) + g_signal_connect (menuitem, "activate", G_CALLBACK (callback), userdata); + else + gtk_widget_set_sensitive (menuitem, FALSE); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); gtk_widget_show (menuitem); } @@ -6978,6 +6975,30 @@ midori_bookmarkbar_insert_item (GtkWidget* toolbar, } static void +midori_bookmarkbar_add_item_cb (KatzeArray* bookmarks, + KatzeItem* item, + MidoriBrowser* browser) +{ + if (gtk_widget_get_visible (browser->bookmarkbar)) + midori_bookmarkbar_populate (browser); + else if (katze_item_get_meta_boolean (item, "toolbar")) + _action_set_active (browser, "Bookmarkbar", TRUE); + midori_browser_update_history (item, "bookmark", "created"); +} + +static void +midori_bookmarkbar_update_item_cb (KatzeArray* bookmarks, + KatzeItem* item, + MidoriBrowser* browser) +{ + if (gtk_widget_get_visible (browser->bookmarkbar)) + midori_bookmarkbar_populate (browser); + else if (katze_item_get_meta_boolean (item, "toolbar")) + _action_set_active (browser, "Bookmarkbar", TRUE); + midori_browser_update_history (item, "bookmark", "modify"); +} + +static void midori_bookmarkbar_remove_item_cb (KatzeArray* bookmarks, KatzeItem* item, MidoriBrowser* browser) @@ -7059,8 +7080,15 @@ midori_browser_set_bookmarks (MidoriBrowser* browser, MidoriWebSettings* settings; if (browser->bookmarks != NULL) + { + g_signal_handlers_disconnect_by_func (browser->bookmarks, + midori_bookmarkbar_add_item_cb, browser); + g_signal_handlers_disconnect_by_func (browser->bookmarks, + midori_bookmarkbar_update_item_cb, browser); g_signal_handlers_disconnect_by_func (browser->bookmarks, midori_bookmarkbar_remove_item_cb, browser); + } + settings = midori_browser_get_settings (browser); g_signal_handlers_disconnect_by_func (settings, midori_browser_show_bookmarkbar_notify_value_cb, browser); @@ -7089,6 +7117,10 @@ midori_browser_set_bookmarks (MidoriBrowser* browser, g_signal_connect (settings, "notify::show-bookmarkbar", G_CALLBACK (midori_browser_show_bookmarkbar_notify_value_cb), browser); g_object_notify (G_OBJECT (settings), "show-bookmarkbar"); + g_signal_connect_after (bookmarks, "add-item", + G_CALLBACK (midori_bookmarkbar_add_item_cb), browser); + g_signal_connect_after (bookmarks, "update-item", + G_CALLBACK (midori_bookmarkbar_update_item_cb), browser); g_signal_connect_after (bookmarks, "remove-item", G_CALLBACK (midori_bookmarkbar_remove_item_cb), browser); } diff --git a/panels/midori-bookmarks.c b/panels/midori-bookmarks.c index 8418ea2..e4afefb 100644 --- a/panels/midori-bookmarks.c +++ b/panels/midori-bookmarks.c @@ -25,6 +25,150 @@ #define COMPLETION_DELAY 200 +G_BEGIN_DECLS + +#define MIDORI_BOOKMARKS_TREE_STORE_TYPE \ + (midori_bookmarks_tree_store_get_type ()) +#define MIDORI_BOOKMARKS_TREE_STORE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), MIDORI_BOOKMARKS_TREE_STORE_TYPE, MidoriBookmarksTreeStore)) +#define MIDORI_BOOKMARKS_TREE_STORE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), MIDORI_BOOKMARKS_TREE_STORE_TYPE, MidoriBookmarksTreeStoreClass)) + +static gboolean +midori_bookmarks_tree_store_drag_data_get (GtkTreeDragSource* drag_source, + GtkTreePath* source_path, + GtkSelectionData* selection_data); +static gboolean +midori_bookmarks_tree_store_drag_data_delete (GtkTreeDragSource* drag_source, + GtkTreePath* source_path); +static gboolean +midori_bookmarks_tree_store_row_drop_possible (GtkTreeDragDest *drag_dest, + GtkTreePath *dest_path, + GtkSelectionData *selection_data); +static gboolean +midori_bookmarks_tree_store_drag_data_received (GtkTreeDragDest *drag_dest, + GtkTreePath *dest_path, + GtkSelectionData *selection_data); + +typedef struct _MidoriBookmarksTreeStore MidoriBookmarksTreeStore; +typedef struct _MidoriBookmarksTreeStoreClass MidoriBookmarksTreeStoreClass; +typedef struct _TreeRowData TreeRowData; + +struct _MidoriBookmarksTreeStore +{ + GtkTreeStore parent_instance; + + GList* stock_got_rows; + GtkTreeView *source_view; +}; + +struct _MidoriBookmarksTreeStoreClass +{ + GtkTreeStoreClass parent_class; +}; + +struct _TreeRowData +{ + GtkTreeModel *model; + gchar path[4]; +}; + +static GtkTreeDragSourceIface * +gtk_tree_store_gtk_tree_drag_source_iface = NULL; +static GtkTreeDragDestIface * +gtk_tree_store_gtk_tree_drag_dest_iface = NULL; + +static void +midori_bookmarks_tree_store_drag_source_init (GtkTreeDragSourceIface *iface) +{ + gtk_tree_store_gtk_tree_drag_source_iface = g_type_interface_peek_parent (iface); + + iface->drag_data_get = midori_bookmarks_tree_store_drag_data_get; + iface->drag_data_delete = midori_bookmarks_tree_store_drag_data_delete; +} + +static void +midori_bookmarks_tree_store_drag_dest_init (GtkTreeDragDestIface *iface) +{ + gtk_tree_store_gtk_tree_drag_dest_iface = g_type_interface_peek_parent (iface); + + iface->row_drop_possible = midori_bookmarks_tree_store_row_drop_possible; + iface->drag_data_received = midori_bookmarks_tree_store_drag_data_received; +} + +static void +midori_bookmarks_tree_store_init (MidoriBookmarksTreeStore* item) +{ + item->stock_got_rows = NULL; + item->source_view = NULL; +} + +static void +midori_bookmarks_tree_store_class_init (MidoriBookmarksTreeStoreClass *class) +{ +} + +G_DEFINE_TYPE_WITH_CODE (MidoriBookmarksTreeStore, + midori_bookmarks_tree_store, + GTK_TYPE_TREE_STORE, + G_IMPLEMENT_INTERFACE ( + GTK_TYPE_TREE_DRAG_SOURCE, + midori_bookmarks_tree_store_drag_source_init) + G_IMPLEMENT_INTERFACE ( + GTK_TYPE_TREE_DRAG_DEST, + midori_bookmarks_tree_store_drag_dest_init)); + + +GtkTreeStore* +midori_bookmarks_tree_store_new (gint n_columns, ...) +{ + GtkTreeStore* tree_store = GTK_TREE_STORE (g_object_new (MIDORI_BOOKMARKS_TREE_STORE_TYPE, NULL)); + va_list ap; + GType* types; + gint n; + + if (!tree_store) + return NULL; + + types = g_new (GType, n_columns); + + if (!types) + { + g_object_unref(tree_store); + return NULL; + } + + va_start(ap, n_columns); + for (n = 0; n < n_columns; n++) + { + types[n] = va_arg(ap, GType); + } + va_end(ap); + + gtk_tree_store_set_column_types (tree_store, + n_columns, + types); + + g_free (types); + return tree_store; +} + + +GtkTreeStore* +midori_bookmarks_tree_store_newv (gint n_columns, GType *types) +{ + GtkTreeStore* tree_store = GTK_TREE_STORE (g_object_new (MIDORI_BOOKMARKS_TREE_STORE_TYPE, NULL)); + + if (!tree_store) + return NULL; + + gtk_tree_store_set_column_types (tree_store, + n_columns, + types); + + return tree_store; +} + gboolean midori_browser_edit_bookmark_dialog_new (MidoriBrowser* browser, KatzeItem* bookmark, @@ -36,6 +180,12 @@ void midori_browser_open_bookmark (MidoriBrowser* browser, KatzeItem* item); +static void +midori_bookmarks_row_changed_cb (GtkTreeModel* model, + GtkTreePath* path, + GtkTreeIter* iter, + MidoriBookmarks* bookmarks); + struct _MidoriBookmarks { GtkVBox parent_instance; @@ -49,6 +199,13 @@ struct _MidoriBookmarks gint filter_timeout; gchar* filter; + + struct _stock_pending_event + { + gint x; + gint y; + } *pending_event, + stock_pending_event; }; struct _MidoriBookmarksClass @@ -86,6 +243,17 @@ midori_bookmarks_get_property (GObject* object, GParamSpec* pspec); static void +midori_bookmarks_add_item_cb (KatzeArray* array, + KatzeItem* item, + MidoriBookmarks* bookmarks); +static void +midori_bookmarks_update_cb (KatzeArray* array, + MidoriBookmarks* bookmarks); + +static void +midori_bookmarks_statusbar_update (MidoriBookmarks *bookmarks); + +static void midori_bookmarks_class_init (MidoriBookmarksClass* class) { GObjectClass* gobject_class; @@ -316,39 +484,761 @@ midori_bookmarks_insert_item_db (sqlite3* db, return seq; } +static gboolean +midori_bookmarks_reach_item_recurse (GtkTreeModel* model, + GtkTreeIter* iter, + gint64 id) +{ + do + { + GtkTreeIter child; + KatzeItem *item; + gint64 itemid = -1; + + gtk_tree_model_get (model, iter, 0, &item, -1); + + if (!KATZE_ITEM_IS_SEPARATOR(item)) + { + itemid = katze_item_get_meta_integer (item, "id"); + g_object_unref (item); + } + + if (id == itemid) + return TRUE; + + if (gtk_tree_model_iter_children (model, &child, iter)) + { + if (midori_bookmarks_reach_item_recurse (model, &child, id)) + { + *iter = child; + return TRUE; + } + } + } + while (gtk_tree_model_iter_next(model, iter)); + + return FALSE; +} + +static gboolean +midori_bookmarks_reach_item (GtkTreeModel* model, + GtkTreeIter* iter, + gint64 id) +{ + if (!gtk_tree_model_get_iter_first(model, iter)) + return FALSE; + + return midori_bookmarks_reach_item_recurse (model, iter, id); +} + +static void +midori_bookmarks_add_item_to_model(GtkTreeStore* model, + GtkTreeIter* parent, + KatzeItem* item) +{ + if (KATZE_ITEM_IS_BOOKMARK (item)) + { + gchar* tooltip = g_markup_escape_text (katze_item_get_uri (item), -1); + + gtk_tree_store_insert_with_values (model, NULL, parent, + 0, + 0, item, 1, tooltip, -1); + g_free (tooltip); + } + else + { + GtkTreeIter root_iter; + + gtk_tree_store_insert_with_values (model, &root_iter, parent, + 0, 0, item, -1); + + /* That's an invisible dummy, so we always have an expander */ + gtk_tree_store_insert_with_values (model, NULL, &root_iter, + 0, + 0, NULL, -1); + } +} + +static void +midori_bookmarks_update_item_in_model(MidoriBookmarks* bookmarks, + GtkTreeStore* model, + GtkTreeIter* iter, + KatzeItem* item) +{ + g_signal_handlers_block_by_func (model, + midori_bookmarks_row_changed_cb, + bookmarks); + + if (KATZE_ITEM_IS_BOOKMARK (item)) + { + gchar* tooltip = g_markup_escape_text (katze_item_get_uri (item), -1); + + gtk_tree_store_set(model, iter, + 0, item, 1, tooltip, -1); + + g_free (tooltip); + } + else + { + gtk_tree_store_set(model, iter, + 0, item, -1); + } + + g_signal_handlers_unblock_by_func (model, + midori_bookmarks_row_changed_cb, + bookmarks); + +} + static void midori_bookmarks_add_item_cb (KatzeArray* array, KatzeItem* item, MidoriBookmarks* bookmarks) { GtkTreeModel* model; + GtkTreeIter iter; + gint64 id; + gint64 parentid; + + g_assert (KATZE_IS_ITEM (item)); + + id = katze_item_get_meta_integer (item, "id"); + parentid = katze_item_get_meta_integer (item, "parentid"); model = gtk_tree_view_get_model (GTK_TREE_VIEW (bookmarks->treeview)); + + if (!parentid) + { + midori_bookmarks_add_item_to_model (GTK_TREE_STORE (model), NULL, item); + } + else if (midori_bookmarks_reach_item (model, &iter, parentid)) + { + GtkTreePath* path = gtk_tree_model_get_path(model, &iter); + + if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (bookmarks->treeview), path)) + { + midori_bookmarks_add_item_to_model (GTK_TREE_STORE (model), &iter, item); + } + + gtk_tree_path_free (path); + } +} + +static void +midori_bookmarks_update_item_cb (KatzeArray* array, + KatzeItem* item, + MidoriBookmarks* bookmarks) +{ + gint64 id = katze_item_get_meta_integer (item, "id"); + gint64 parentid = katze_item_get_meta_integer (item, "parentid"); + GtkTreeModel* model = gtk_tree_view_get_model (GTK_TREE_VIEW (bookmarks->treeview)); + GtkTreeIter iter; + + g_assert (KATZE_IS_ITEM (item)); + + if (midori_bookmarks_reach_item (model, &iter, id)) + { + gint64 old_parentid = 0; + GtkTreeIter parent; + + if (gtk_tree_model_iter_parent (model, &parent, &iter)) + { + KatzeItem* old_parent; + + gtk_tree_model_get (model, &parent, 0, &old_parent, -1); + + old_parentid = katze_item_get_meta_integer (old_parent, "id"); + + g_object_unref (old_parent); + + if (parentid == old_parentid) + { + midori_bookmarks_update_item_in_model (bookmarks, GTK_TREE_STORE (model), &iter, item); + } + else + { + gtk_tree_store_remove (GTK_TREE_STORE (model), &iter); + + if (!gtk_tree_model_iter_has_child (model, &parent)) + { + GtkTreePath* path = gtk_tree_model_get_path(model, &parent); + + if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (bookmarks->treeview), path)) + gtk_tree_view_collapse_row (GTK_TREE_VIEW (bookmarks->treeview), path); + + gtk_tree_path_free (path); + } + + midori_bookmarks_add_item_cb (array, item, bookmarks); + } + } + else if (parentid == 0) + { + midori_bookmarks_update_item_in_model (bookmarks, GTK_TREE_STORE (model), &iter, item); + } + else + { + gtk_tree_store_remove (GTK_TREE_STORE (model), &iter); + + midori_bookmarks_add_item_cb (array, item, bookmarks); + } + } + else + midori_bookmarks_add_item_cb (array, item, bookmarks); +} + +static void +midori_bookmarks_remove_item_cb (KatzeArray* array, + KatzeItem* item, + MidoriBookmarks* bookmarks) +{ + gint64 id = katze_item_get_meta_integer (item, "id"); + GtkTreeModel* model = gtk_tree_view_get_model (GTK_TREE_VIEW (bookmarks->treeview)); + GtkTreeIter iter; + + g_assert (KATZE_IS_ITEM (item)); + + if (midori_bookmarks_reach_item (model, &iter, id)) + { + GtkTreeIter parent; + + if (gtk_tree_model_iter_parent (model, &parent, &iter)) + { + gtk_tree_store_remove (GTK_TREE_STORE (model), &iter); + + if (!gtk_tree_model_iter_has_child (model, &parent)) + { + GtkTreePath* path = gtk_tree_model_get_path(model, &parent); + + if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (bookmarks->treeview), path)) + gtk_tree_view_collapse_row (GTK_TREE_VIEW (bookmarks->treeview), path); + + gtk_tree_path_free (path); + } + } + else + { + gtk_tree_store_remove (GTK_TREE_STORE (model), &iter); + } + + midori_bookmarks_statusbar_update (bookmarks); + } +} + + +static void +midori_bookmarks_update_cb (KatzeArray* array, + MidoriBookmarks* bookmarks) +{ + GtkTreeModel* model = gtk_tree_view_get_model (GTK_TREE_VIEW (bookmarks->treeview)); + gtk_tree_store_clear (GTK_TREE_STORE (model)); midori_bookmarks_read_from_db_to_model (bookmarks, GTK_TREE_STORE (model), NULL, 0, bookmarks->filter); + + midori_bookmarks_statusbar_update (bookmarks); +} + +gboolean +midori_bookmarks_tree_set_row_drag_data (GtkSelectionData *selection_data, + GtkTreeModel *tree_model, + GList* rows) +{ + TreeRowData *trd; + gint len; + gint struct_size; + gint length; + gint i; + GString *data; + + g_return_val_if_fail (selection_data != NULL, FALSE); + g_return_val_if_fail (GTK_IS_TREE_MODEL (tree_model), FALSE); + g_return_val_if_fail (rows != NULL, FALSE); + + data = g_string_new(""); + + length = g_list_length (rows); + for (i = 0; i < length; i++) + { + GtkTreePath *path = (GtkTreePath *)g_list_nth_data (rows, i); + gchar *path_str = gtk_tree_path_to_string (path); + + g_string_append (data, path_str); + if (i < length-1) + g_string_append_c (data, '\n'); + } + + len = data->len; + + /* the old allocate-end-of-struct-to-hold-string trick */ + struct_size = sizeof (TreeRowData) + len + 1 - + (sizeof (TreeRowData) - G_STRUCT_OFFSET (TreeRowData, path)); + + trd = g_malloc (struct_size); + + strcpy (trd->path, data->str); + + g_string_free (data, TRUE); + + trd->model = tree_model; + + gtk_selection_data_set (selection_data, + gdk_atom_intern_static_string ("GTK_TREE_MODEL_MULTI_ROWS"), + 8, /* bytes */ + (void*)trd, + struct_size); + + g_free (trd); + + return TRUE; +} + +static gboolean +midori_bookmarks_tree_store_drag_data_get (GtkTreeDragSource* drag_source, + GtkTreePath* source_path, + GtkSelectionData* selection_data) +{ + MidoriBookmarksTreeStore *tree_store; + gboolean status = FALSE; + + g_return_val_if_fail (selection_data != NULL, FALSE); + g_return_val_if_fail (GTK_IS_TREE_MODEL (drag_source), FALSE); + g_return_val_if_fail (source_path != NULL, FALSE); + + tree_store = MIDORI_BOOKMARKS_TREE_STORE(drag_source); + + if (tree_store->stock_got_rows) + { + g_list_free_full (tree_store->stock_got_rows, (GDestroyNotify) gtk_tree_path_free); + tree_store->stock_got_rows = NULL; + } + + if (selection_data->target == gdk_atom_intern_static_string ("GTK_TREE_MODEL_MULTI_ROWS")) + { + GtkTreeModel *model; + GList* rows; + if (katze_tree_view_get_selected_rows ( + tree_store->source_view, &model, &rows)) + { + status = midori_bookmarks_tree_set_row_drag_data (selection_data, model, rows); + + tree_store->stock_got_rows = rows; + } + } + + return status; +} + +static gboolean +midori_bookmarks_tree_store_drag_data_delete (GtkTreeDragSource* drag_source, + GtkTreePath* source_path) +{ + gboolean status = TRUE; + guint length; + guint i; + MidoriBookmarksTreeStore *tree_store = MIDORI_BOOKMARKS_TREE_STORE(drag_source); + GtkTreeModel* model = GTK_TREE_MODEL(drag_source); + + if (!tree_store->stock_got_rows) + return TRUE; + + length = g_list_length (tree_store->stock_got_rows); + for (i = 0; i < length; i++) + { + GtkTreePath *prev = (GtkTreePath *)g_list_nth_data (tree_store->stock_got_rows, i); + GtkTreeIter iter; + + if (gtk_tree_model_get_iter (model, &iter, prev)) + { + gint j; + gchar* src_path_str = gtk_tree_path_to_string (prev); + + /* remove item updating source paths */ + g_free(src_path_str); + + gtk_tree_store_remove (GTK_TREE_STORE (drag_source), &iter); + + + for (j = i+1; j < length; j++ ) + { + GtkTreePath *src_path_r = (GtkTreePath *)g_list_nth_data (tree_store->stock_got_rows, j); + gint la = gtk_tree_path_get_depth (prev); + gint lb = gtk_tree_path_get_depth (src_path_r); + gint *ia = gtk_tree_path_get_indices (prev); + gint *ib = gtk_tree_path_get_indices (src_path_r); + gint k; + + if (la > lb) /* removal was donne in a deeper branch than source */ + continue; + + if (ia[la-1] > ib[la-1]) /* removal was donne after source */ + continue; + + for (k = 0; k < la; k++) + { + if (ia[k] != ib[k]) break; + } + + if (k < la-1) /* removal and source are not in the same branch */ + continue; + + /* source at depth level of removal must be decremented due to the removal */ + ib[la-1] -= 1; + } + } + else + status = FALSE; + } + + g_list_free_full (tree_store->stock_got_rows, (GDestroyNotify) gtk_tree_path_free); + tree_store->stock_got_rows = NULL; + + return status; +} + +static gboolean +midori_bookmarks_tree_store_get_rows_drag_data (GtkSelectionData *selection_data, + GtkTreeModel **tree_model, + GList **rows) +{ + GList *list; + TreeRowData *trd; + + g_return_val_if_fail (selection_data != NULL, FALSE); + + if (tree_model) + *tree_model = NULL; + + if (rows) + *rows = NULL; + + if (selection_data->target != gdk_atom_intern_static_string ("GTK_TREE_MODEL_MULTI_ROWS")) + return FALSE; + + if (selection_data->length < 0) + return FALSE; + + trd = (void*) selection_data->data; + + if (tree_model) + *tree_model = trd->model; + + if (rows) + { + GList *list = NULL; + gchar *trd_path = g_strdup (trd->path); + gchar *path_str; + + path_str = strtok(trd_path, "\n"); + while (path_str && *path_str) + { + list = g_list_append (list, gtk_tree_path_new_from_string (path_str)); + path_str = strtok (NULL, "\n"); + } + + *rows = list; + g_free (trd_path); + } + + return TRUE; +} + +static gboolean +midori_bookmarks_tree_store_row_drop_possible (GtkTreeDragDest* drag_dest, + GtkTreePath* dest_path, + GtkSelectionData* selection_data) +{ + GtkTreeModel* dest_model = GTK_TREE_MODEL(drag_dest); + GtkTreeIter dest_parent; + GtkTreePath *parent = gtk_tree_path_copy (dest_path); + GtkTreeIter *dest_parent_p = NULL; + gboolean row_drop_possible = TRUE; + + if (gtk_tree_path_up (parent) && + gtk_tree_path_get_depth (parent) > 0) + { + gtk_tree_model_get_iter (dest_model, + &dest_parent, + parent); + dest_parent_p = &dest_parent; + } + gtk_tree_path_free (parent); + + if (dest_parent_p) + { + KatzeItem* item; + + gtk_tree_model_get (dest_model, dest_parent_p, 0, &item, -1); + + if (!KATZE_ITEM_IS_FOLDER(item)) + row_drop_possible = FALSE; + + if (item) + g_object_unref (item); + } + + if (!row_drop_possible) + return FALSE; + + if (selection_data->target == gdk_atom_intern_static_string ("GTK_TREE_MODEL_MULTI_ROWS")) + { + GtkTreeModel *model; + GList* rows; + + if (midori_bookmarks_tree_store_get_rows_drag_data (selection_data, + &model, + &rows)) + { + if (model == GTK_TREE_MODEL(drag_dest)) + { + gint length = g_list_length (rows); + gint i; + + for (i = 0; row_drop_possible && (i < length); i++) + { + gint j; + GtkTreePath *src_path = (GtkTreePath *)g_list_nth_data (rows, i); + + /* Can't drop into ourself. */ + if (gtk_tree_path_is_ancestor (src_path, + dest_path)) + row_drop_possible = FALSE; + + for (j = i+1; j < length; j++ ) + { + GtkTreePath *src_path_r = (GtkTreePath *)g_list_nth_data (rows, j); + gint la = gtk_tree_path_get_depth (dest_path); + gint lb = gtk_tree_path_get_depth (src_path_r); + gint *ia = gtk_tree_path_get_indices (dest_path); + gint *ib = gtk_tree_path_get_indices (src_path_r); + gint k; + + if (la > lb) /* insert was donne in a deeper branch than source */ + continue; + + if (ia[la-1] > ib[la-1]) /* insert was donne after source */ + continue; + + for (k = 0; k < la; k++) + { + if (ia[k] != ib[k]) break; + } + + if (k < la-1) /* insert and source are not in the same branch */ + continue; + + /* source at depth level of insert must be incremented due to the insert */ + ib[la-1] += 1; + } + } + } + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + } + else + return FALSE; + } + else + return FALSE; + + return row_drop_possible; } static void -midori_bookmarks_remove_item_cb (KatzeArray* array, - KatzeItem* item, - MidoriBookmarks* bookmarks) +copy_node_data (GtkTreeStore *src_store, + GtkTreeIter *src_iter, + GtkTreeStore *dest_store, + GtkTreeIter *dest_iter) { - GtkTreeModel* model = gtk_tree_view_get_model (GTK_TREE_VIEW (bookmarks->treeview)); - gtk_tree_store_clear (GTK_TREE_STORE (model)); - midori_bookmarks_read_from_db_to_model (bookmarks, - GTK_TREE_STORE (model), NULL, 0, bookmarks->filter); + gint i; + gint n_columns; + GtkTreeModel* src_model = GTK_TREE_MODEL (src_store); + GtkTreeModel* dest_model = GTK_TREE_MODEL (dest_store); + GtkTreePath* src_path = gtk_tree_model_get_path (src_model, src_iter); + GtkTreePath* dest_path = gtk_tree_model_get_path (dest_model, dest_iter); + gchar* src_path_str = gtk_tree_path_to_string (src_path); + gchar* dest_path_str = gtk_tree_path_to_string (dest_path); + + /* copy node data */ + + n_columns = gtk_tree_model_get_n_columns (src_model); + + for (i = 0; i < n_columns; i++) + { + void *item; + + gtk_tree_model_get (src_model, src_iter, i, &item, -1); + gtk_tree_store_set (dest_store, dest_iter, i, item, -1); + } + + g_free(src_path_str); + g_free(dest_path_str); + gtk_tree_path_free(src_path); + gtk_tree_path_free(dest_path); } static void -midori_bookmarks_update_cb (KatzeArray* array, - MidoriBookmarks* bookmarks) +recursive_node_copy (GtkTreeStore *src_store, + GtkTreeIter *src_iter, + GtkTreeStore *dest_store, + GtkTreeIter *dest_iter) { - GtkTreeModel* model = gtk_tree_view_get_model (GTK_TREE_VIEW (bookmarks->treeview)); - gtk_tree_store_clear (GTK_TREE_STORE (model)); - midori_bookmarks_read_from_db_to_model (bookmarks, - GTK_TREE_STORE (model), NULL, 0, bookmarks->filter); + GtkTreeIter child; + GtkTreeModel *src_model = GTK_TREE_MODEL (src_store); + + copy_node_data (src_store, src_iter, dest_store, dest_iter); + + if (gtk_tree_model_iter_children (src_model, &child, src_iter)) + { + /* Need to create children and recurse. Note our + * dependence on persistent iterators here. + */ + do + { + GtkTreeIter copy; + + /* Gee, a really slow algorithm... ;-) FIXME */ + gtk_tree_store_append (dest_store, + ©, + dest_iter); + + recursive_node_copy (src_store, &child, dest_store, ©); + } + while (gtk_tree_model_iter_next (src_model, &child)); + } } +static gboolean +midori_bookmarks_tree_store_drag_data_received (GtkTreeDragDest *drag_dest, + GtkTreePath *dest_path, + GtkSelectionData *selection_data) +{ + gboolean status = TRUE; + + g_return_val_if_fail (selection_data != NULL, FALSE); + g_return_val_if_fail (GTK_IS_TREE_MODEL (drag_dest), FALSE); + g_return_val_if_fail (dest_path != NULL, FALSE); + + if (selection_data->target == gdk_atom_intern_static_string ("GTK_TREE_MODEL_MULTI_ROWS")) + { + GtkTreeStore *dest_store = GTK_TREE_STORE (drag_dest); + GtkTreeModel *dest_model = GTK_TREE_MODEL (drag_dest); + GtkTreeModel *src_model; + GList* rows; + + if (midori_bookmarks_tree_store_get_rows_drag_data (selection_data, + &src_model, + &rows)) + { + GtkTreeStore *src_store = GTK_TREE_STORE (src_model); + GtkTreePath *prev = gtk_tree_path_copy (dest_path); + + gint count = 0; + gint length = g_list_length (rows); + gint i; + + for (i = 0; i < length; i++) + { + gint j; + GtkTreeIter dest_iter; + GtkTreeIter src_iter; + GtkTreePath *src_path = (GtkTreePath *)g_list_nth_data (rows, i); + + if (!gtk_tree_model_get_iter (src_model, &src_iter, src_path)) + continue; + + /* Get the path to insert _after_ (dest is the path to insert _before_) */ + if (i == 0) + { + if (!gtk_tree_path_prev (prev)) + { /* Get the parent, NULL if parent is the root */ + GtkTreeIter dest_parent; + GtkTreePath *parent = gtk_tree_path_copy (dest_path); + GtkTreeIter *dest_parent_p = NULL; + + if (gtk_tree_path_up (parent) && + gtk_tree_path_get_depth (parent) > 0) + { + gtk_tree_model_get_iter (dest_model, + &dest_parent, + parent); + dest_parent_p = &dest_parent; + } + gtk_tree_path_free (parent); + + gtk_tree_store_prepend (dest_store, &dest_iter, dest_parent_p); + } + else if (gtk_tree_model_get_iter (dest_model, &dest_iter, prev)) + { + GtkTreeIter tmp_iter = dest_iter; + + gtk_tree_store_insert_after (dest_store, &dest_iter, NULL, + &tmp_iter); + } + } + else if (gtk_tree_model_get_iter (dest_model, &dest_iter, prev)) + { + GtkTreeIter tmp_iter = dest_iter; + + gtk_tree_store_insert_after (dest_store, &dest_iter, NULL, + &tmp_iter); + } + + gtk_tree_path_free (prev); + + recursive_node_copy (src_store, &src_iter, dest_store, &dest_iter); + count++; + + prev = gtk_tree_model_get_path (dest_model, &dest_iter); + + if (src_store != dest_store) + continue; + + for (j = 0; j < length; j++ ) + { + GtkTreePath *src_path_r = (GtkTreePath *)g_list_nth_data (rows, j); + gint la = gtk_tree_path_get_depth (prev); + gint lb = gtk_tree_path_get_depth (src_path_r); + gint *ia = gtk_tree_path_get_indices (prev); + gint *ib = gtk_tree_path_get_indices (src_path_r); + gint k; + + if (la > lb) /* insert was donne in a deeper branch than source */ + continue; + + if (ia[la-1] > ib[la-1]) /* insert was donne after source */ + continue; + + for (k = 0; k < la; k++) + { + if (ia[k] != ib[k]) break; + } + + if (k < la-1) /* insert and source are not in the same branch */ + continue; + + /* source at depth level of insert must be incremented due to the insert */ + ib[la-1] += 1; + } + } + + gtk_tree_path_free (prev); + + g_assert (count == length); + + if (src_store == dest_store) + { + MidoriBookmarksTreeStore *tree_store = MIDORI_BOOKMARKS_TREE_STORE(src_store); + + g_list_free_full (tree_store->stock_got_rows, (GDestroyNotify) gtk_tree_path_free); + tree_store->stock_got_rows = rows; + } + else + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + } + } + + return status; +} static void midori_bookmarks_row_changed_cb (GtkTreeModel* model, @@ -358,31 +1248,42 @@ midori_bookmarks_row_changed_cb (GtkTreeModel* model, { KatzeItem* item; GtkTreeIter parent; - KatzeItem* new_parent = NULL; - gint64 parentid; + gint64 parentid = 0; gtk_tree_model_get (model, iter, 0, &item, -1); + if (!KATZE_IS_ITEM (item)) + return; + if (gtk_tree_model_iter_parent (model, &parent, iter)) { + KatzeItem* new_parent; + gtk_tree_model_get (model, &parent, 0, &new_parent, -1); - /* Bookmarks must not be moved into non-folder items */ - if (!KATZE_ITEM_IS_FOLDER (new_parent)) - parentid = 0; - else + /* Bookmarks cannot be moved into non-folder items */ + if (KATZE_ITEM_IS_FOLDER (new_parent)) + { parentid = katze_item_get_meta_integer (new_parent, "id"); + } + + if (new_parent) + g_object_unref (new_parent); } - else - parentid = 0; - katze_array_remove_item (bookmarks->array, item); katze_item_set_meta_integer (item, "parentid", parentid); - katze_array_add_item (bookmarks->array, item); + + g_signal_handlers_block_by_func (bookmarks->array, + midori_bookmarks_update_item_cb, + bookmarks); + + katze_array_update_item (bookmarks->array, item); + + g_signal_handlers_unblock_by_func (bookmarks->array, + midori_bookmarks_update_item_cb, + bookmarks); g_object_unref (item); - if (new_parent) - g_object_unref (new_parent); } static void @@ -419,13 +1320,6 @@ midori_bookmarks_edit_clicked_cb (GtkWidget* toolitem, midori_browser_edit_bookmark_dialog_new ( browser, item, FALSE, KATZE_ITEM_IS_FOLDER (item), NULL); - if (katze_item_get_meta_integer (item, "parentid") != parentid) - { - gtk_tree_store_clear (GTK_TREE_STORE (model)); - midori_bookmarks_read_from_db_to_model (bookmarks, GTK_TREE_STORE (model), - NULL, 0, NULL); - } - g_object_unref (item); } } @@ -433,12 +1327,102 @@ midori_bookmarks_edit_clicked_cb (GtkWidget* toolitem, static void midori_bookmarks_toolbar_update (MidoriBookmarks *bookmarks) { - gboolean selected; + gint selected; - selected = katze_tree_view_get_selected_iter ( + selected = katze_tree_view_get_selected_rows ( GTK_TREE_VIEW (bookmarks->treeview), NULL, NULL); - gtk_widget_set_sensitive (GTK_WIDGET (bookmarks->delete), selected); - gtk_widget_set_sensitive (GTK_WIDGET (bookmarks->edit), selected); + gtk_widget_set_sensitive ( + GTK_WIDGET (bookmarks->delete), (selected > 0 ? TRUE : FALSE)); + gtk_widget_set_sensitive ( + GTK_WIDGET (bookmarks->edit), (selected == 1 ? TRUE : FALSE)); +} + +static void +midori_bookmarks_statusbar_update (MidoriBookmarks *bookmarks) +{ + gchar* text = NULL; + GtkTreeModel* model; + GList *rows; + gint selected; + + selected = katze_tree_view_get_selected_rows ( + GTK_TREE_VIEW (bookmarks->treeview), &model, &rows); + + if (selected >= 1) + { + gint i; + gint selected_folders_count = 0; + gint selected_bookmarks_count = 0; + + for (i = 0 ; i < selected ; i++) + { + GtkTreeIter iter; + KatzeItem* item; + + g_return_if_fail (gtk_tree_model_get_iter ( + model, &iter, (GtkTreePath *)g_list_nth_data (rows, i))); + + gtk_tree_model_get (model, &iter, 0, &item, -1); + + g_assert (!KATZE_ITEM_IS_SEPARATOR (item)); + + if (KATZE_ITEM_IS_FOLDER (item)) + { + selected_folders_count++; + } + else + { + selected_bookmarks_count++; + } + } + + if (!selected_bookmarks_count && !selected_folders_count) + g_assert_not_reached (); + else if (!selected_bookmarks_count && (selected_folders_count == 1)) + text = g_strdup_printf( + _("one folder and no bookmark selected")); + else if (!selected_bookmarks_count && selected_folders_count) + text = g_strdup_printf( + _("%d folders and no bookmark selected"), + selected_folders_count); + else if ((selected_bookmarks_count == 1) && !selected_folders_count) + text = g_strdup_printf( + _("one bookmark selected")); + else if ((selected_bookmarks_count == 1) && (selected_folders_count == 1)) + text = g_strdup_printf( + _("one bookmark and one folder selected")); + else if ((selected_bookmarks_count == 1) && selected_folders_count) + text = g_strdup_printf( + _("one bookmark and %d folders selected"), + selected_folders_count); + else if (selected_bookmarks_count && !selected_folders_count) + text = g_strdup_printf( + _("%d bookmarks selected"), + selected_bookmarks_count); + else if (selected_bookmarks_count && (selected_folders_count == 1)) + text = g_strdup_printf( + _("%d bookmarks and one folder selected"), + selected_bookmarks_count); + else if (selected_bookmarks_count && selected_folders_count) + text = g_strdup_printf( + _("%d bookmarks and %d folders selected"), + selected_bookmarks_count, selected_folders_count); + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + } + else + { + text = g_strdup_printf(_("No selection")); + } + + if (text) + { + MidoriBrowser* browser = midori_browser_get_for_widget (bookmarks->treeview); + + g_object_set (browser, "statusbar-text", text, NULL); + + g_free(text); + } } gboolean @@ -487,23 +1471,38 @@ midori_bookmarks_delete_clicked_cb (GtkWidget* toolitem, MidoriBookmarks* bookmarks) { GtkTreeModel* model; - GtkTreeIter iter; + GList* rows; + gint length; - if (katze_tree_view_get_selected_iter (GTK_TREE_VIEW (bookmarks->treeview), - &model, &iter)) + length = katze_tree_view_get_selected_rows(GTK_TREE_VIEW (bookmarks->treeview), + &model, &rows); + while (length) { - KatzeItem* item; + gint new_length; + GtkTreeIter iter; - gtk_tree_model_get (model, &iter, 0, &item, -1); + if (gtk_tree_model_get_iter ( + model, &iter, (GtkTreePath *)g_list_nth_data (rows, 0))) + { + KatzeItem* item; - /* Manually remove the iter and block clearing the treeview */ - gtk_tree_store_remove (GTK_TREE_STORE (model), &iter); - g_signal_handlers_block_by_func (bookmarks->array, - midori_bookmarks_remove_item_cb, bookmarks); - katze_array_remove_item (bookmarks->array, item); - g_signal_handlers_unblock_by_func (bookmarks->array, - midori_bookmarks_remove_item_cb, bookmarks); - g_object_unref (item); + gtk_tree_model_get (model, &iter, 0, &item, -1); + + if (KATZE_IS_ITEM(item)) + { + katze_array_remove_item (bookmarks->array, item); + } + + g_object_unref (item); + } + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + new_length = katze_tree_view_get_selected_rows(GTK_TREE_VIEW (bookmarks->treeview), + &model, &rows); + + /* avoid infinite loop due to delete failure */ + length = (new_length == length) ? 0 : new_length; } } @@ -547,6 +1546,7 @@ midori_bookmarks_get_toolbar (MidoriViewable* viewable) gtk_widget_show (GTK_WIDGET (toolitem)); bookmarks->delete = GTK_WIDGET (toolitem); midori_bookmarks_toolbar_update (bookmarks); + midori_bookmarks_statusbar_update (bookmarks); toolitem = gtk_separator_tool_item_new (); gtk_separator_tool_item_set_draw (GTK_SEPARATOR_TOOL_ITEM (toolitem), FALSE); gtk_tool_item_set_expand (toolitem, TRUE); @@ -599,6 +1599,8 @@ midori_bookmarks_set_app (MidoriBookmarks* bookmarks, midori_bookmarks_read_from_db_to_model (bookmarks, GTK_TREE_STORE (model), NULL, 0, NULL); g_signal_connect_after (bookmarks->array, "add-item", G_CALLBACK (midori_bookmarks_add_item_cb), bookmarks); + g_signal_connect_after (bookmarks->array, "update-item", + G_CALLBACK (midori_bookmarks_update_item_cb), bookmarks); g_signal_connect (bookmarks->array, "remove-item", G_CALLBACK (midori_bookmarks_remove_item_cb), bookmarks); g_signal_connect (bookmarks->array, "update", @@ -745,7 +1747,36 @@ midori_bookmarks_popup_item (GtkWidget* menu, else if (!KATZE_ITEM_IS_FOLDER (item) && strcmp (stock_id, GTK_STOCK_DELETE)) gtk_widget_set_sensitive (menuitem, uri != NULL); g_object_set_data (G_OBJECT (menuitem), "KatzeItem", item); - g_signal_connect (menuitem, "activate", G_CALLBACK (callback), bookmarks); + if (callback) + g_signal_connect (menuitem, "activate", G_CALLBACK (callback), bookmarks); + else + gtk_widget_set_sensitive (menuitem, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + gtk_widget_show (menuitem); +} + +static void +midori_bookmarks_popup_selection (GtkWidget* menu, + const gchar* stock_id, + const gchar* label, + GtkWidget* treeview, + gpointer callback, + MidoriBookmarks* bookmarks) +{ + GtkWidget* menuitem; + + menuitem = gtk_image_menu_item_new_from_stock (stock_id, NULL); + if (label) + gtk_label_set_text_with_mnemonic (GTK_LABEL (gtk_bin_get_child ( + GTK_BIN (menuitem))), label); + g_object_set_data (G_OBJECT (menuitem), "TreeView", treeview); + if (callback) + { + gtk_widget_set_sensitive (menuitem, TRUE); + g_signal_connect (menuitem, "activate", G_CALLBACK (callback), bookmarks); + } + else + gtk_widget_set_sensitive (menuitem, FALSE); gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); gtk_widget_show (menuitem); } @@ -770,35 +1801,63 @@ static void midori_bookmarks_open_in_tab_activate_cb (GtkWidget* menuitem, MidoriBookmarks* bookmarks) { - KatzeItem* item; - const gchar* uri; + GtkTreeModel* model; + GList* rows; + gint length; + gint i; - item = (KatzeItem*)g_object_get_data (G_OBJECT (menuitem), "KatzeItem"); - if (KATZE_ITEM_IS_FOLDER (item)) + length = katze_tree_view_get_selected_rows(GTK_TREE_VIEW (bookmarks->treeview), + &model, &rows); + for (i = 0 ; i < length; i++) { - KatzeItem* child; - KatzeArray* array; + GtkTreeIter iter; - array = midori_bookmarks_read_from_db (bookmarks, - katze_item_get_meta_integer (item, "parentid"), NULL); - - g_return_if_fail (KATZE_IS_ARRAY (array)); - KATZE_ARRAY_FOREACH_ITEM (child, array) + if (gtk_tree_model_get_iter ( + model, &iter, (GtkTreePath *)g_list_nth_data (rows, i))) { - if ((uri = katze_item_get_uri (child)) && *uri) + KatzeItem* item; + const gchar* uri; + + gtk_tree_model_get (model, &iter, 0, &item, -1); + + if (KATZE_ITEM_IS_SEPARATOR(item)) + continue; + + if (KATZE_ITEM_IS_FOLDER (item)) + { + KatzeItem* child; + KatzeArray* array; + + array = midori_bookmarks_read_from_db (bookmarks, + katze_item_get_meta_integer (item, "id"), NULL); + + if (KATZE_IS_ARRAY (array)) + KATZE_ARRAY_FOREACH_ITEM (child, array) + { + if (!KATZE_IS_ITEM (child)) + continue; + + if ((uri = katze_item_get_uri (child)) && *uri) + { + MidoriBrowser* browser = midori_browser_get_for_widget (GTK_WIDGET (bookmarks)); + GtkWidget* view = midori_browser_add_item (browser, child); + + midori_browser_set_current_tab_smartly (browser, view); + } + } + } + else if ((uri = katze_item_get_uri (item)) && *uri) { MidoriBrowser* browser = midori_browser_get_for_widget (GTK_WIDGET (bookmarks)); - GtkWidget* view = midori_browser_add_item (browser, child); + GtkWidget* view = midori_browser_add_item (browser, item); midori_browser_set_current_tab_smartly (browser, view); } + + g_object_unref (item); } } - else if ((uri = katze_item_get_uri (item)) && *uri) - { - MidoriBrowser* browser = midori_browser_get_for_widget (GTK_WIDGET (bookmarks)); - GtkWidget* view = midori_browser_add_item (browser, item); - midori_browser_set_current_tab_smartly (browser, view); - } + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); } static void @@ -854,6 +1913,54 @@ midori_bookmarks_popup (GtkWidget* widget, katze_widget_popup (widget, GTK_MENU (menu), event, KATZE_MENU_POSITION_CURSOR); } +static void +midori_bookmarks_multi_popup (GtkWidget* widget, + GdkEventButton* event, + MidoriBookmarks* bookmarks, + GtkTreeModel* model, + gint count, + GList* rows) +{ + GtkWidget* menu; + GtkWidget* menuitem; + + menu = gtk_menu_new (); + + midori_bookmarks_popup_selection (menu, + STOCK_TAB_NEW, _("Open all in _Tabs"), + widget, midori_bookmarks_open_in_tab_activate_cb, bookmarks); + menuitem = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), menuitem); + gtk_widget_show (menuitem); + + midori_bookmarks_popup_selection (menu, GTK_STOCK_EDIT, NULL, + widget, NULL, bookmarks); + midori_bookmarks_popup_selection (menu, GTK_STOCK_DELETE, NULL, + widget, midori_bookmarks_delete_clicked_cb, bookmarks); + + katze_widget_popup (widget, GTK_MENU (menu), event, KATZE_MENU_POSITION_CURSOR); +} + +static gboolean +midori_bookmarks_do_block_selection (GtkTreeSelection *selection, + GtkTreeModel *model, + GtkTreePath *path, + gboolean path_currently_selected, + gpointer data) +{ + return FALSE; +} + +static gboolean +midori_bookmarks_do_not_block_selection (GtkTreeSelection *selection, + GtkTreeModel *model, + GtkTreePath *path, + gboolean path_currently_selected, + gpointer data) +{ + return TRUE; +} + static gboolean midori_bookmarks_button_release_event_cb (GtkWidget* widget, GdkEventButton* event, @@ -862,32 +1969,149 @@ midori_bookmarks_button_release_event_cb (GtkWidget* widget, GtkTreeModel* model; GtkTreeIter iter; - if (event->button != 2 && event->button != 3) + if (bookmarks->pending_event) + { + GtkTreeView* treeview = GTK_TREE_VIEW(widget); + GtkTreeSelection* selection = gtk_tree_view_get_selection (treeview); + gint x = bookmarks->stock_pending_event.x; + gint y = bookmarks->stock_pending_event.y; + + bookmarks->pending_event = NULL; + gtk_tree_selection_set_select_function ( + selection, midori_bookmarks_do_not_block_selection, NULL, NULL); + + if (x != event->x || y != event->y) + return TRUE; + } + + if (event->button == 3) + return TRUE; + + if (event->button != 2) return FALSE; if (katze_tree_view_get_selected_iter (GTK_TREE_VIEW (widget), &model, &iter)) { + gboolean caught = FALSE; KatzeItem* item; gtk_tree_model_get (model, &iter, 0, &item, -1); - if (event->button == 2) + if (KATZE_ITEM_IS_BOOKMARK (item)) { - const gchar* uri; - if (KATZE_ITEM_IS_BOOKMARK (item) && (uri = katze_item_get_uri (item)) && *uri) - { - MidoriBrowser* browser = midori_browser_get_for_widget (widget); - GtkWidget* view = midori_browser_add_uri (browser, uri); - midori_browser_set_current_tab (browser, view); - } + const gchar* uri = katze_item_get_uri (item); + MidoriBrowser* browser; + gint n; + + browser = midori_browser_get_for_widget (widget); + n = midori_browser_add_uri (browser, uri); + midori_browser_set_current_page (browser, n); + + caught = TRUE; } - else - midori_bookmarks_popup (widget, event, item, bookmarks); - if (item != NULL) - g_object_unref (item); + g_object_unref (item); + + return caught; + } + + return FALSE; +} + +static gboolean +midori_bookmarks_block_selection(GtkWidget* widget, + GdkEventButton* event, + MidoriBookmarks* bookmarks) +{ + GtkTreeView* treeview = GTK_TREE_VIEW(widget); + GtkTreePath* path; + GtkTreeSelection* selection; + gint cell_x; + gint cell_y; + + if (!gtk_tree_view_get_path_at_pos ( + treeview, event->x, event->y, + &path, NULL, &cell_x, &cell_y)) + return FALSE; + + gtk_widget_grab_focus (widget); + + selection = gtk_tree_view_get_selection (treeview); + + if (gtk_tree_selection_path_is_selected (selection, path) + && !(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK))) + { + bookmarks->pending_event = &bookmarks->stock_pending_event; + bookmarks->stock_pending_event.x = event->x; + bookmarks->stock_pending_event.y = event->y; + gtk_tree_selection_set_select_function ( + selection, midori_bookmarks_do_block_selection, NULL, NULL); + } + else + { + bookmarks->pending_event = NULL; + gtk_tree_selection_set_select_function ( + selection, midori_bookmarks_do_not_block_selection, NULL, NULL); + } + + return FALSE; +} + +static gboolean +midori_bookmarks_button_press_event_cb (GtkWidget* widget, + GdkEventButton* event, + MidoriBookmarks* bookmarks) +{ + GtkTreeModel* model; + gint selected; + GList* rows; + + if (event->button == 1) + return midori_bookmarks_block_selection (widget, event, bookmarks); + + if (event->button != 3) + return FALSE; + + selected = katze_tree_view_get_selected_rows(GTK_TREE_VIEW (widget), &model, &rows); + + if (selected == 1) + { + GtkTreeIter iter; + KatzeItem* item; + + if (!gtk_tree_model_get_iter ( + model, &iter, (GtkTreePath *)g_list_nth_data (rows, 0))) + { + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return FALSE; + } + + gtk_tree_model_get (model, &iter, 0, &item, -1); + + midori_bookmarks_popup (widget, event, item, bookmarks); + + g_object_unref (item); + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + return TRUE; } + + if (selected > 1) + { + midori_bookmarks_multi_popup (widget, + event, + bookmarks, + model, + selected, + rows); + + g_list_free_full (rows, (GDestroyNotify) gtk_tree_path_free); + + return TRUE; + } + return FALSE; } @@ -919,19 +2143,33 @@ midori_bookmarks_popup_menu_cb (GtkWidget* widget, } static void -midori_bookmarks_row_expanded_cb (GtkTreeView* treeview, +midori_bookmarks_test_expand_row_cb (GtkTreeView* treeview, GtkTreeIter* iter, GtkTreePath* path, MidoriBookmarks* bookmarks) { GtkTreeModel* model; + GtkTreeIter child; KatzeItem* item; + gint64 id; model = gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); gtk_tree_model_get (model, iter, 0, &item, -1); - midori_bookmarks_read_from_db_to_model (bookmarks, GTK_TREE_STORE (model), - iter, katze_item_get_meta_integer (item, "id"), NULL); + + g_return_if_fail (KATZE_IS_ITEM(item)); + + id = katze_item_get_meta_integer (item, "id"); + g_object_unref (item); + + while (gtk_tree_model_iter_children (model, &child, iter)) + gtk_tree_store_remove (GTK_TREE_STORE (model), &child); + /* That's an invisible dummy, so we always have an expander */ + gtk_tree_store_insert_with_values (GTK_TREE_STORE (model), &child, iter, + 0, 0, NULL, -1); + + midori_bookmarks_read_from_db_to_model (bookmarks, GTK_TREE_STORE (model), + iter, id, NULL); } static void @@ -958,6 +2196,7 @@ midori_bookmarks_selection_changed_cb (GtkTreeSelection *treeview, MidoriBookmarks *bookmarks) { midori_bookmarks_toolbar_update (bookmarks); + midori_bookmarks_statusbar_update (bookmarks); } static gboolean @@ -993,6 +2232,14 @@ midori_bookmarks_filter_entry_changed_cb (GtkEntry* entry, midori_bookmarks_filter_timeout_cb, bookmarks, NULL); } +static GtkTargetEntry midori_bookmarks_dnd_target_entries[]= +{ + {"GTK_TREE_MODEL_MULTI_ROWS", GTK_TARGET_SAME_WIDGET, 0}, +}; + +#define MIDORI_BOOKMARKS_DND_NB_TARGET_ENTRIES \ + G_N_ELEMENTS (midori_bookmarks_dnd_target_entries) + static void midori_bookmarks_init (MidoriBookmarks* bookmarks) { @@ -1005,6 +2252,8 @@ midori_bookmarks_init (MidoriBookmarks* bookmarks) GtkCellRenderer* renderer_text; GtkTreeSelection* selection; + bookmarks->pending_event = NULL; + /* Create the filter entry */ entry = sokoke_search_entry_new (_("Search Bookmarks")); g_signal_connect_after (entry, "changed", @@ -1015,7 +2264,7 @@ midori_bookmarks_init (MidoriBookmarks* bookmarks) gtk_box_pack_start (GTK_BOX (bookmarks), box, FALSE, FALSE, 5); /* Create the treeview */ - model = gtk_tree_store_new (2, KATZE_TYPE_ITEM, G_TYPE_STRING); + model = midori_bookmarks_tree_store_new (2, KATZE_TYPE_ITEM, G_TYPE_STRING); treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (treeview), 1); @@ -1032,23 +2281,40 @@ midori_bookmarks_init (MidoriBookmarks* bookmarks) (GtkTreeCellDataFunc)midori_bookmarks_treeview_render_text_cb, treeview, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column); - gtk_tree_view_set_reorderable (GTK_TREE_VIEW (treeview), TRUE); + gtk_tree_view_set_reorderable (GTK_TREE_VIEW (treeview), FALSE); + gtk_tree_view_enable_model_drag_source ( + GTK_TREE_VIEW (treeview), + GDK_BUTTON1_MASK, + midori_bookmarks_dnd_target_entries, + MIDORI_BOOKMARKS_DND_NB_TARGET_ENTRIES, + GDK_ACTION_MOVE|GDK_ACTION_LINK); + gtk_tree_view_enable_model_drag_dest ( + GTK_TREE_VIEW (treeview), + midori_bookmarks_dnd_target_entries, + MIDORI_BOOKMARKS_DND_NB_TARGET_ENTRIES, + GDK_ACTION_MOVE|GDK_ACTION_LINK); g_object_unref (model); g_object_connect (treeview, "signal::row-activated", midori_bookmarks_row_activated_cb, bookmarks, + "signal::button-press-event", + midori_bookmarks_button_press_event_cb, bookmarks, "signal::button-release-event", midori_bookmarks_button_release_event_cb, bookmarks, "signal::key-release-event", midori_bookmarks_key_release_event_cb, bookmarks, "signal::popup-menu", midori_bookmarks_popup_menu_cb, bookmarks, - "signal::row-expanded", - midori_bookmarks_row_expanded_cb, bookmarks, + "signal::test-expand-row", + midori_bookmarks_test_expand_row_cb, bookmarks, "signal::row-collapsed", midori_bookmarks_row_collapsed_cb, bookmarks, NULL); + + MIDORI_BOOKMARKS_TREE_STORE (model)->source_view = GTK_TREE_VIEW (treeview); + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview)); + gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE); g_signal_connect_after (selection, "changed", G_CALLBACK (midori_bookmarks_selection_changed_cb), bookmarks);