2
* @file item_list_view.c presenting items in a GtkTreeView
4
* Copyright (C) 2004-2011 Lars Lindner <lars.lindner@gmail.com>
5
* Copyright (C) 2004-2006 Nathan J. Conrad <t98502@users.sourceforge.net>
7
* This program is free software; you can redistribute it and/or modify
8
* it under the terms of the GNU General Public License as published by
9
* the Free Software Foundation; either version 2 of the License, or
10
* (at your option) any later version.
12
* This program is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
* GNU General Public License for more details.
17
* You should have received a copy of the GNU General Public License
18
* along with this program; if not, write to the Free Software
19
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
#include "ui/item_list_view.h"
26
#include <gdk/gdkkeysyms.h>
39
#include "ui/browser_tabs.h"
41
#include "ui/liferea_shell.h"
42
#include "ui/popup_menu.h"
43
#include "ui/ui_common.h"
46
* Important performance considerations: Early versions had performance problems
47
* with the item list loading because of the following two problems:
49
* 1.) Mass-adding items to a sorting enabled tree store.
50
* 2.) Mass-loading items to an attached tree store.
52
* To avoid both problems we merge against a visible tree store only for single
53
* items that are added/removed by background updates and load complete feeds or
54
* collections of feeds only by adding items to a new unattached tree store.
57
/** Enumeration of the columns in the itemstore. */
59
IS_TIME, /**< Time of item creation */
60
IS_TIME_STR, /**< Time of item creation as a string*/
61
IS_LABEL, /**< Displayed name */
62
IS_STATEICON, /**< Pixbuf reference to the item's state icon */
63
IS_NR, /**< Item id, to lookup item ptr from parent feed */
64
IS_PARENT, /**< Parent node pointer */
65
IS_FAVICON, /**< Pixbuf reference to the item's feed's icon */
66
IS_ENCICON, /**< Pixbuf reference to the item's enclosure icon */
67
IS_ENCLOSURE, /**< Flag wether enclosure is attached or not */
68
IS_SOURCE, /**< Source node pointer */
69
IS_STATE, /**< Original item state (unread, flagged...) for sorting */
70
ITEMSTORE_UNREAD, /**< Flag whether "unread" icon is to be shown */
71
ITEMSTORE_ALIGN, /**< How to align title (RTL support) */
72
ITEMSTORE_LEN /**< Number of columns in the itemstore */
75
#define ITEM_LIST_VIEW_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), ITEM_LIST_VIEW_TYPE, ItemListViewPrivate))
77
struct ItemListViewPrivate {
78
GtkTreeView *treeview;
80
GHashTable *item_id_to_iter; /**< hash table used for fast item id->tree iter lookup */
82
gboolean batch_mode; /**< TRUE if we are in batch adding mode */
83
GtkTreeStore *batch_itemstore; /**< GtkTreeStore prepared unattached and to be set on update() */
86
static GObjectClass *parent_class = NULL;
88
G_DEFINE_TYPE (ItemListView, item_list_view, G_TYPE_OBJECT);
91
item_list_view_finalize (GObject *object)
93
ItemListViewPrivate *priv = ITEM_LIST_VIEW_GET_PRIVATE (object);
95
if (priv->item_id_to_iter)
96
g_hash_table_destroy (priv->item_id_to_iter);
97
if (priv->batch_itemstore)
98
g_object_unref (priv->batch_itemstore);
100
G_OBJECT_CLASS (parent_class)->finalize (object);
104
item_list_view_class_init (ItemListViewClass *klass)
106
GObjectClass *object_class = G_OBJECT_CLASS (klass);
108
parent_class = g_type_class_peek_parent (klass);
110
object_class->finalize = item_list_view_finalize;
112
g_type_class_add_private (object_class, sizeof(ItemListViewPrivate));
115
/* helper functions for item <-> iter conversion */
118
item_list_view_contains_id (ItemListView *ilv, gulong id)
120
return (NULL != g_hash_table_lookup (ilv->priv->item_id_to_iter, GUINT_TO_POINTER (id)));
124
item_list_view_iter_to_id (ItemListView *ilv, GtkTreeIter *iter)
128
gtk_tree_model_get (gtk_tree_view_get_model (ilv->priv->treeview), iter, IS_NR, &id, -1);
133
item_list_view_id_to_iter (ItemListView *ilv, gulong id, GtkTreeIter *iter)
135
GtkTreeIter *old_iter;
137
old_iter = g_hash_table_lookup (ilv->priv->item_id_to_iter, GUINT_TO_POINTER (id));
146
item_list_view_date_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
148
guint64 timea, timeb;
151
gtk_tree_model_get (model, a, IS_TIME, &timea, -1);
152
gtk_tree_model_get (model, b, IS_TIME, &timeb, -1);
153
diff = difftime ((time_t)timeb, (time_t)timea);
165
item_list_view_favicon_sort_func (GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data)
167
nodePtr node1, node2;
169
gtk_tree_model_get (model, a, IS_SOURCE, &node1, -1);
170
gtk_tree_model_get (model, b, IS_SOURCE, &node2, -1);
172
if (!node1->id || !node2->id)
175
return strcmp (node1->id, node2->id);
179
item_list_view_set_sort_column (ItemListView *ilv, nodeViewSortType sortType, gboolean sortReversed)
184
case NODE_VIEW_SORT_BY_TITLE:
185
sortColumn = IS_LABEL;
187
case NODE_VIEW_SORT_BY_PARENT:
188
sortColumn = IS_PARENT;
190
case NODE_VIEW_SORT_BY_STATE:
191
sortColumn = IS_STATE;
193
case NODE_VIEW_SORT_BY_TIME:
195
sortColumn = IS_TIME;
199
gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (gtk_tree_view_get_model (ilv->priv->treeview)),
201
sortReversed?GTK_SORT_DESCENDING:GTK_SORT_ASCENDING);
205
* Creates a GtkTreeStore to be filled with ui_itemlist_set_items
206
* and to be set with ui_itemlist_set_tree_store().
208
static GtkTreeStore *
209
item_list_view_create_tree_store (void)
211
return gtk_tree_store_new (ITEMSTORE_LEN,
212
G_TYPE_UINT64, /* IS_TIME */
213
G_TYPE_STRING, /* IS_TIME_STR */
214
G_TYPE_STRING, /* IS_LABEL */
215
GDK_TYPE_PIXBUF, /* IS_STATEICON */
216
G_TYPE_ULONG, /* IS_NR */
217
G_TYPE_POINTER, /* IS_PARENT */
218
GDK_TYPE_PIXBUF, /* IS_FAVICON */
219
GDK_TYPE_PIXBUF, /* IS_ENCICON */
220
G_TYPE_BOOLEAN, /* IS_ENCLOSURE */
221
G_TYPE_POINTER, /* IS_SOURCE */
222
G_TYPE_UINT, /* IS_STATE */
223
G_TYPE_INT, /* ITEMSTORE_UNREAD */
224
G_TYPE_FLOAT /* ITEMSTORE_ALIGN */
229
* Sets a GtkTreeView to the active GtkTreeView.
232
item_list_view_set_tree_store (ItemListView *ilv, GtkTreeStore *itemstore)
236
/* drop old tree store */
237
model = gtk_tree_view_get_model (ilv->priv->treeview);
238
gtk_tree_view_set_model (ilv->priv->treeview, NULL);
240
g_object_unref (model);
242
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (itemstore), IS_TIME, item_list_view_date_sort_func, NULL, NULL);
243
gtk_tree_sortable_set_sort_func (GTK_TREE_SORTABLE (itemstore), IS_SOURCE, item_list_view_favicon_sort_func, NULL, NULL);
244
g_signal_connect (G_OBJECT (itemstore), "sort-column-changed", G_CALLBACK (itemlist_sort_column_changed_cb), NULL);
246
gtk_tree_view_set_model (ilv->priv->treeview, GTK_TREE_MODEL (itemstore));
248
item_list_view_prefocus (ilv);
252
item_list_view_remove_item (ItemListView *ilv, itemPtr item)
256
g_assert (NULL != item);
257
iter = g_hash_table_lookup (ilv->priv->item_id_to_iter, GUINT_TO_POINTER (item->id));
259
/* Using the GtkTreeIter check if it is currently selected. If yes,
260
scroll down by one in the sorted GtkTreeView to ensure something
261
is selected after removing the GtkTreeIter */
262
if (gtk_tree_selection_iter_is_selected (gtk_tree_view_get_selection (ilv->priv->treeview), iter))
263
ui_common_treeview_move_cursor (ilv->priv->treeview, 1);
265
gtk_tree_store_remove (GTK_TREE_STORE (gtk_tree_view_get_model (ilv->priv->treeview)), iter);
266
g_hash_table_remove (ilv->priv->item_id_to_iter, GUINT_TO_POINTER (item->id));
268
g_warning ("Fatal: item to be removed not found in iter lookup hash!");
272
/* cleans up the item list, sets up the iter hash when called for the first time */
274
item_list_view_clear (ItemListView *ilv)
277
GtkTreeStore *itemstore;
279
itemstore = GTK_TREE_STORE (gtk_tree_view_get_model (ilv->priv->treeview));
281
/* unselecting all items is important to remove items
282
whose removal is deferred until unselecting */
283
gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (ilv->priv->treeview));
285
adj = gtk_tree_view_get_vadjustment (ilv->priv->treeview);
286
gtk_adjustment_set_value (adj, 0.0);
287
gtk_tree_view_set_vadjustment (ilv->priv->treeview, adj);
290
gtk_tree_store_clear (itemstore);
291
if (ilv->priv->item_id_to_iter)
292
g_hash_table_destroy (ilv->priv->item_id_to_iter);
294
ilv->priv->item_id_to_iter = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
296
/* enable batch mode for following item adds */
297
ilv->priv->batch_mode = TRUE;
298
ilv->priv->batch_itemstore = item_list_view_create_tree_store ();
302
item_list_title_alignment (gchar *title)
304
if (!title || strlen(title) == 0)
307
/* debug5 (DEBUG_HTML, "title ***%s*** first bytes %02hhx%02hhx%02hhx pango %d",
308
title, title[0], title[1], title[2], pango_find_base_dir (title, -1)); */
309
int txt_direction = pango_find_base_dir (title, -1);
310
int app_direction = gtk_widget_get_default_direction ();
311
if ((txt_direction == PANGO_DIRECTION_LTR &&
312
app_direction == GTK_TEXT_DIR_LTR) ||
313
(txt_direction == PANGO_DIRECTION_RTL &&
314
app_direction == GTK_TEXT_DIR_RTL))
315
return 0.; /* same direction, regular ("left") alignment */
321
item_list_view_update_item (ItemListView *ilv, itemPtr item)
323
GtkTreeStore *itemstore;
325
gchar *title, *time_str;
326
const GdkPixbuf *state_icon;
328
if (!item_list_view_id_to_iter (ilv, item->id, &iter))
331
time_str = (0 != item->time) ? date_format ((time_t)item->time, NULL) : g_strdup ("");
333
title = item->title && strlen (item->title) ? item->title : _("*** No title ***");
334
title = g_strstrip (g_strdup (title));
336
state_icon = item->flagStatus ? icon_get (ICON_FLAG) :
337
!item->readStatus ? icon_get (ICON_UNREAD) :
340
if (ilv->priv->batch_mode)
341
itemstore = ilv->priv->batch_itemstore;
343
itemstore = GTK_TREE_STORE (gtk_tree_view_get_model (ilv->priv->treeview));
344
gtk_tree_store_set (itemstore,
347
IS_TIME_STR, time_str,
348
IS_STATEICON, state_icon,
349
ITEMSTORE_UNREAD, item->readStatus ? PANGO_WEIGHT_NORMAL : PANGO_WEIGHT_BOLD,
350
ITEMSTORE_ALIGN, item_list_title_alignment (title),
358
item_list_view_update_item_foreach (gpointer key,
364
item = item_load (GPOINTER_TO_UINT (key) /* id */);
368
item_list_view_update_item (ITEM_LIST_VIEW (user_data), item);
374
item_list_view_update_all_items (ItemListView *ilv)
376
g_hash_table_foreach (ilv->priv->item_id_to_iter, item_list_view_update_item_foreach, (gpointer)ilv);
380
item_list_view_update (ItemListView *ilv, gboolean hasEnclosures)
382
/* we depend on the fact that the third column is the enclosure icon column!!! */
383
gtk_tree_view_column_set_visible (gtk_tree_view_get_column (ilv->priv->treeview, 1), hasEnclosures);
385
if (ilv->priv->batch_mode) {
386
item_list_view_set_tree_store (ilv, ilv->priv->batch_itemstore);
387
ilv->priv->batch_mode = FALSE;
389
/* Nothing to do in non-batch mode as items were added
390
and updated one-by-one in ui_itemlist_add_item() */
395
on_item_list_view_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer data)
397
if ((event->type == GDK_KEY_PRESS) && (event->state == 0) && (event->keyval == GDK_Delete))
398
on_remove_item_activate(NULL, NULL);
403
/* Show tooltip when headline's column text (IS_LABEL) is truncated. */
405
static gint get_cell_renderer_width (GtkWidget *widget, GtkCellRenderer *cell, const gchar *text, gint weight)
407
PangoLayout *layout = gtk_widget_create_pango_layout (widget, text);
408
PangoAttrList *attrbs = pango_attr_list_new();
409
pango_attr_list_insert (attrbs, pango_attr_weight_new (weight));
410
pango_layout_set_attributes (layout, attrbs);
411
pango_attr_list_unref (attrbs);
413
pango_layout_get_pixel_extents (layout, NULL, &rect);
414
g_object_unref (G_OBJECT (layout));
415
return (cell->xpad * 2) + rect.x + rect.width;
419
on_item_list_view_query_tooltip (GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, GtkTreeViewColumn *headline_column)
421
GtkTreeView *view = GTK_TREE_VIEW (widget);
422
GtkTreeModel *model; GtkTreePath *path; GtkTreeIter iter;
423
gboolean ret = FALSE;
425
if (gtk_tree_view_get_tooltip_context (view, &x, &y, keyboard_mode, &model, &path, &iter)) {
426
GtkTreeViewColumn *column;
428
gtk_tree_view_convert_widget_to_bin_window_coords (view, x, y, &bx, &by);
429
gtk_tree_view_get_path_at_pos (view, x, y, NULL, &column, NULL, NULL);
431
if (column == headline_column) {
432
GtkCellRenderer *cell;
433
GList *renderers = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (column));
434
cell = GTK_CELL_RENDERER (renderers->data);
435
g_list_free (renderers);
439
gtk_tree_model_get (model, &iter, IS_LABEL, &text, ITEMSTORE_UNREAD, &weight, -1);
441
gint full_width = get_cell_renderer_width (widget, cell, text, weight);
442
gint column_width = gtk_tree_view_column_get_width (column);
443
if (full_width > column_width) {
444
gtk_tooltip_set_text (tooltip, text);
450
gtk_tree_view_set_tooltip_row (view, tooltip, path);
451
gtk_tree_path_free (path);
457
on_item_list_view_button_press_event (GtkWidget *treeview, GdkEventButton *event, gpointer user_data)
459
ItemListView *ilv = ITEM_LIST_VIEW (user_data);
462
GtkTreeViewColumn *column;
464
gboolean result = FALSE;
466
if (event->type != GDK_BUTTON_PRESS)
469
/* avoid handling header clicks */
470
if (event->window != gtk_tree_view_get_bin_window (ilv->priv->treeview))
473
if (!gtk_tree_view_get_path_at_pos (ilv->priv->treeview, (gint)event->x, (gint)event->y, &path, NULL, NULL, NULL))
476
if (gtk_tree_model_get_iter (gtk_tree_view_get_model (ilv->priv->treeview), &iter, path))
477
item = item_load (item_list_view_iter_to_id (ilv, &iter));
479
gtk_tree_path_free (path);
482
GdkEventButton *eb = (GdkEventButton*)event;
483
switch (eb->button) {
485
column = gtk_tree_view_get_column (ilv->priv->treeview, 0);
487
/* Allow flag toggling when left clicking in the flagging column.
488
We depend on the fact that the state column is the first!!! */
489
if (event->x <= gtk_tree_view_column_get_width (column)) {
490
itemlist_toggle_flag (item);
496
/* Middle mouse click toggles read status... */
497
itemlist_toggle_read_status (item);
501
item_list_view_select (ilv, item);
502
ui_popup_item_menu (item, eb->button, eb->time);
513
on_item_list_view_popup_menu (GtkWidget *widget, gpointer user_data)
515
GtkTreeView *treeview = GTK_TREE_VIEW (widget);
519
if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (treeview), &model, &iter)) {
520
itemPtr item = item_load (item_list_view_iter_to_id (ITEM_LIST_VIEW (user_data), &iter));
521
ui_popup_item_menu (item, 3, 0);
530
item_list_view_get_widget (ItemListView *ilv)
532
return ITEM_LIST_VIEW_GET_PRIVATE (ilv)->treeview;
536
item_list_view_init (ItemListView *ilv)
538
ilv->priv = ITEM_LIST_VIEW_GET_PRIVATE (ilv);
539
ilv->priv->item_id_to_iter = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
543
item_list_view_create (GtkWidget *window)
546
GtkCellRenderer *renderer;
547
GtkTreeViewColumn *column, *headline_column;
548
GtkTreeSelection *select;
549
GtkWidget *ilscrolledwindow;
551
ilv = g_object_new (ITEM_LIST_VIEW_TYPE, NULL);
553
ilscrolledwindow = gtk_scrolled_window_new (NULL, NULL);
554
gtk_widget_show (ilscrolledwindow);
555
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (ilscrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
556
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (ilscrolledwindow), GTK_SHADOW_IN);
558
ilv->priv->treeview = GTK_TREE_VIEW (gtk_tree_view_new ());
559
gtk_container_add (GTK_CONTAINER (ilscrolledwindow), GTK_WIDGET (ilv->priv->treeview));
560
gtk_widget_show (GTK_WIDGET (ilv->priv->treeview));
561
gtk_widget_set_name (GTK_WIDGET (ilv->priv->treeview), "itemlist");
562
gtk_tree_view_set_rules_hint (ilv->priv->treeview, TRUE);
564
g_object_set_data (G_OBJECT (window), "itemlist", ilv->priv->treeview);
566
item_list_view_set_tree_store (ilv, item_list_view_create_tree_store ());
568
renderer = gtk_cell_renderer_pixbuf_new ();
569
column = gtk_tree_view_column_new_with_attributes ("", renderer, "pixbuf", IS_STATEICON, NULL);
570
gtk_tree_view_append_column (ilv->priv->treeview, column);
571
gtk_tree_view_column_set_sort_column_id (column, IS_STATE);
573
renderer = gtk_cell_renderer_pixbuf_new ();
574
column = gtk_tree_view_column_new_with_attributes ("", renderer, "pixbuf", IS_ENCICON, NULL);
575
gtk_tree_view_append_column (ilv->priv->treeview, column);
577
renderer = gtk_cell_renderer_text_new ();
578
column = gtk_tree_view_column_new_with_attributes (_("Date"), renderer,
580
"weight", ITEMSTORE_UNREAD,
582
gtk_tree_view_append_column (ilv->priv->treeview, column);
583
gtk_tree_view_column_set_sort_column_id(column, IS_TIME);
584
g_object_set (column, "resizable", TRUE, NULL);
586
renderer = gtk_cell_renderer_pixbuf_new ();
587
column = gtk_tree_view_column_new_with_attributes ("", renderer, "pixbuf", IS_FAVICON, NULL);
588
gtk_tree_view_column_set_sort_column_id (column, IS_SOURCE);
589
gtk_tree_view_append_column (ilv->priv->treeview, column);
591
renderer = gtk_cell_renderer_text_new ();
592
headline_column = gtk_tree_view_column_new_with_attributes (_("Headline"), renderer,
594
"weight", ITEMSTORE_UNREAD,
595
"xalign", ITEMSTORE_ALIGN,
597
gtk_tree_view_append_column (ilv->priv->treeview, headline_column);
598
gtk_tree_view_column_set_sort_column_id (headline_column, IS_LABEL);
599
g_object_set (headline_column, "resizable", TRUE, NULL);
600
g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
602
/* And connect signals */
603
g_signal_connect (G_OBJECT (ilv->priv->treeview), "button_press_event", G_CALLBACK (on_item_list_view_button_press_event), ilv);
604
g_signal_connect (G_OBJECT (ilv->priv->treeview), "row_activated", G_CALLBACK (on_Itemlist_row_activated), ilv);
605
g_signal_connect (G_OBJECT (ilv->priv->treeview), "key-press-event", G_CALLBACK (on_item_list_view_key_press_event), ilv);
606
g_signal_connect (G_OBJECT (ilv->priv->treeview), "popup_menu", G_CALLBACK (on_item_list_view_popup_menu), ilv);
608
gtk_widget_set_has_tooltip (GTK_WIDGET (ilv->priv->treeview), TRUE);
609
g_signal_connect (G_OBJECT (ilv->priv->treeview), "query-tooltip", G_CALLBACK (on_item_list_view_query_tooltip), headline_column);
611
/* Setup the selection handler */
612
select = gtk_tree_view_get_selection (ilv->priv->treeview);
613
gtk_tree_selection_set_mode (select, GTK_SELECTION_SINGLE);
614
g_signal_connect (G_OBJECT (select), "changed",
615
G_CALLBACK (on_itemlist_selection_changed), ilv);
620
/* typically called when filling the item tree view */
622
item_list_view_prefocus (ItemListView *ilv)
624
GtkWidget *focus_widget;
625
GtkTreeSelection *itemselection;
627
/* the following is important to prevent setting the unread
628
flag for the first item in the item list when the user does
629
the first click into the treeview, if we don't do a focus and
630
unselect, GTK would always (exception: clicking on first item)
631
generate two selection-change events (one for the clicked and
632
one for the selected item)!!! */
634
/* we need to restore the focus after we temporarily select the itemlist */
635
focus_widget = gtk_window_get_focus (GTK_WINDOW (liferea_shell_get_window ()));
637
/* prevent marking as unread before focussing, which leads to a selection */
638
gtk_widget_grab_focus (GTK_WIDGET (ilv->priv->treeview));
640
itemselection = gtk_tree_view_get_selection (ilv->priv->treeview);
642
gtk_tree_selection_unselect_all (itemselection);
645
gtk_widget_grab_focus (focus_widget);
649
item_list_view_add_item_to_tree_store (ItemListView *ilv, GtkTreeStore *itemstore, itemPtr item)
654
GtkTreeIter old_iter;
657
if (item->flagStatus)
659
if (!item->readStatus)
662
node = node_from_id (item->nodeId);
664
return; /* comment items do cause this... maybe filtering them earlier would be a good idea... */
666
exists = item_list_view_id_to_iter (ilv, item->id, &old_iter);
671
iter = g_new0 (GtkTreeIter, 1);
672
gtk_tree_store_prepend (itemstore, iter, NULL);
673
g_hash_table_insert (ilv->priv->item_id_to_iter, GUINT_TO_POINTER (item->id), (gpointer)iter);
676
gtk_tree_store_set (itemstore, iter,
677
IS_TIME, (guint64)item->time,
680
IS_FAVICON, node->icon,
681
IS_ENCICON, item->hasEnclosure?icon_get (ICON_ENCLOSURE):NULL,
682
IS_ENCLOSURE, item->hasEnclosure,
689
item_list_view_add_item (ItemListView *ilv, itemPtr item)
691
GtkTreeStore *itemstore;
693
if (ilv->priv->batch_mode) {
694
/* either merge to new unattached GtkTreeStore */
695
item_list_view_add_item_to_tree_store (ilv, ilv->priv->batch_itemstore, item);
697
/* or merge to visible item store */
698
itemstore = GTK_TREE_STORE (gtk_tree_view_get_model (ilv->priv->treeview));
699
item_list_view_add_item_to_tree_store (ilv, itemstore, item);
702
item_list_view_update_item (ilv, item);
706
item_list_view_enable_favicon_column (ItemListView *ilv, gboolean enabled)
708
/* we depend on the fact that the second column is the favicon column!!!
709
if we are in search mode or have a folder or vfolder we show the favicon
710
column to give a hint where the item comes from ... */
711
gtk_tree_view_column_set_visible (gtk_tree_view_get_column (ilv->priv->treeview, 3), enabled);
715
on_popup_launchitem_selected (void)
719
item = itemlist_get_selected ();
721
gchar *link = item_make_link (item);
723
itemview_launch_URL (link, FALSE);
728
liferea_shell_set_important_status_bar (_("No item has been selected"));
733
on_popup_launchitem_in_tab_selected (void)
738
item = itemlist_get_selected ();
740
link = item_make_link (item);
742
browser_tabs_add_new (link, link, FALSE);
745
ui_show_error_box (_("This item has no link specified!"));
749
liferea_shell_set_important_status_bar (_("No item has been selected"));
754
on_Itemlist_row_activated (GtkTreeView *treeview,
756
GtkTreeViewColumn *column,
760
on_popup_launchitem_selected ();
766
on_toggle_item_flag (GtkMenuItem *menuitem, gpointer user_data)
770
item = itemlist_get_selected ();
772
itemlist_toggle_flag (item);
778
on_popup_toggle_flag (void)
780
on_toggle_item_flag (NULL, NULL);
784
on_toggle_unread_status (GtkMenuItem *menuitem, gpointer user_data)
788
item = itemlist_get_selected ();
790
itemlist_toggle_read_status (item);
796
on_popup_toggle_read (void)
798
on_toggle_unread_status (NULL, NULL);
802
on_remove_items_activate (GtkMenuItem *menuitem, gpointer user_data)
806
node = feedlist_get_selected ();
807
// FIXME: use node type capability check
808
if (node && (IS_FEED (node) || IS_NEWSBIN (node)))
809
itemlist_remove_all_items (node);
811
ui_show_error_box (_("You must select a feed to delete its items!"));
815
on_remove_item_activate (GtkMenuItem *menuitem, gpointer user_data)
819
item = itemlist_get_selected ();
821
itemlist_remove_item (item);
823
liferea_shell_set_important_status_bar (_("No item has been selected"));
828
on_popup_remove_selected (void)
830
on_remove_item_activate (NULL, NULL);
834
item_list_view_select (ItemListView *ilv, itemPtr item)
836
GtkTreeView *treeview = ilv->priv->treeview;
837
GtkTreeSelection *selection;
839
selection = gtk_tree_view_get_selection (treeview);
845
if (!item_list_view_id_to_iter(ilv, item->id, &iter))
846
/* This is an evil hack to fix SF #1870052: crash
847
upon hitting <enter> when no headline selected.
848
FIXME: This code is rotten! Rewrite it! Now! */
849
itemlist_selection_changed (NULL);
851
path = gtk_tree_model_get_path (gtk_tree_view_get_model (treeview), &iter);
852
gtk_tree_view_set_cursor (treeview, path, NULL, FALSE);
853
gtk_tree_view_scroll_to_cell (treeview, path, NULL, FALSE, 0.0, 0.0);
854
gtk_tree_path_free (path);
856
gtk_tree_selection_unselect_all (selection);
861
item_list_view_find_unread_item (ItemListView *ilv, gulong startId)
865
gboolean valid = TRUE;
867
model = gtk_tree_view_get_model (ilv->priv->treeview);
870
valid = item_list_view_id_to_iter (ilv, startId, &iter);
872
valid = gtk_tree_model_get_iter_first (model, &iter);
875
itemPtr item = item_load (item_list_view_iter_to_id (ilv, &iter));
877
if (!item->readStatus)
881
valid = gtk_tree_model_iter_next (model, &iter);
888
on_next_unread_item_activate (GtkMenuItem *menuitem, gpointer user_data)
890
itemlist_select_next_unread ();
894
on_popup_next_unread_item_selected (gpointer callback_data, guint callback_action, GtkWidget *widget)
896
itemlist_select_next_unread ();
900
on_nextbtn_clicked (GtkButton *button, gpointer user_data)
902
itemlist_select_next_unread ();
906
on_popup_copy_URL_clipboard (void)
910
item = itemlist_get_selected ();
912
gchar *link = item_make_link (item);
914
gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY), link, -1);
915
gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), link, -1);
920
liferea_shell_set_important_status_bar (_("No item has been selected"));
925
on_popup_social_bm_item_selected (void)
929
item = itemlist_get_selected ();
931
social_add_bookmark (item);
935
liferea_shell_set_important_status_bar (_("No item has been selected"));
939
on_itemlist_selection_changed (GtkTreeSelection *selection, gpointer user_data)
945
if (gtk_tree_selection_get_selected (selection, &model, &iter))
946
item = item_load (item_list_view_iter_to_id (ITEM_LIST_VIEW (user_data), &iter));
948
liferea_shell_update_item_menu (NULL != item);
950
itemlist_selection_changed (item);
954
itemlist_sort_column_changed_cb (GtkTreeSortable *treesortable, gpointer user_data)
956
gint sortColumn, nodeSort;
957
GtkSortType sortType;
958
gboolean sorted, changed;
960
if (feedlist_get_selected () == NULL)
963
sorted = gtk_tree_sortable_get_sort_column_id (treesortable, &sortColumn, &sortType);
967
switch (sortColumn) {
970
nodeSort = NODE_VIEW_SORT_BY_TIME;
973
nodeSort = NODE_VIEW_SORT_BY_TITLE;
976
nodeSort = NODE_VIEW_SORT_BY_STATE;
980
nodeSort = NODE_VIEW_SORT_BY_PARENT;
984
changed = node_set_sort_column (feedlist_get_selected (), nodeSort, sortType == GTK_SORT_DESCENDING);
986
feedlist_schedule_save ();