2006-08-11 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / widgets / rb-entry-view.c
blob6c269e0f17a0c14a814e2722f8229b6c42652314
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /*
3 * arch-tag: Implementation of widget to display RhythmDB entries
5 * Copyright (C) 2003 Colin Walters <walters@verbum.org>
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "config.h"
25 #include <string.h>
26 #include <stdlib.h>
27 #include <math.h>
28 #include <time.h>
30 #include <glib/gi18n.h>
31 #include <gtk/gtk.h>
32 #include <libgnomevfs/gnome-vfs-utils.h>
34 #include "rb-tree-dnd.h"
35 #include "rb-entry-view.h"
36 #include "rb-dialog.h"
37 #include "rb-debug.h"
38 #include "rb-util.h"
39 #include "rhythmdb.h"
40 #include "rhythmdb-query-model.h"
41 #include "rb-cell-renderer-pixbuf.h"
42 #include "rb-cell-renderer-rating.h"
43 #include "rb-stock-icons.h"
44 #include "rb-preferences.h"
45 #include "eel-gconf-extensions.h"
46 #include "rb-shell-player.h"
47 #include "rb-cut-and-paste-code.h"
49 static const GtkTargetEntry rb_entry_view_drag_types[] = {{ "text/uri-list", 0, 0 }};
51 struct RBEntryViewColumnSortData
53 GCompareDataFunc func;
54 gpointer data;
57 static void rb_entry_view_class_init (RBEntryViewClass *klass);
58 static void rb_entry_view_init (RBEntryView *view);
59 static GObject *rb_entry_view_constructor (GType type, guint n_construct_properties,
60 GObjectConstructParam *construct_properties);
61 static void rb_entry_view_finalize (GObject *object);
62 static void rb_entry_view_set_property (GObject *object,
63 guint prop_id,
64 const GValue *value,
65 GParamSpec *pspec);
66 static void rb_entry_view_get_property (GObject *object,
67 guint prop_id,
68 GValue *value,
69 GParamSpec *pspec);
70 static void rb_entry_view_selection_changed_cb (GtkTreeSelection *selection,
71 RBEntryView *view);
72 static void rb_entry_view_grab_focus (GtkWidget *widget);
73 static void rb_entry_view_row_activated_cb (GtkTreeView *treeview,
74 GtkTreePath *path,
75 GtkTreeViewColumn *column,
76 RBEntryView *view);
77 static void rb_entry_view_row_inserted_cb (GtkTreeModel *model,
78 GtkTreePath *path,
79 GtkTreeIter *iter,
80 RBEntryView *view);
81 static void rb_entry_view_row_deleted_cb (GtkTreeModel *model,
82 GtkTreePath *path,
83 RBEntryView *view);
84 static void rb_entry_view_rows_reordered_cb (GtkTreeModel *model,
85 GtkTreePath *path,
86 GtkTreeIter *iter,
87 gint *order,
88 RBEntryView *view);
89 static void rb_entry_view_sync_columns_visible (RBEntryView *view);
90 static void rb_entry_view_columns_config_changed_cb (GConfClient* client,
91 guint cnxn_id,
92 GConfEntry *entry,
93 gpointer user_data);
94 static void rb_entry_view_sort_key_changed_cb (GConfClient* client,
95 guint cnxn_id,
96 GConfEntry *entry,
97 gpointer user_data);
98 static void rb_entry_view_rated_cb (RBCellRendererRating *cellrating,
99 const char *path,
100 double rating,
101 RBEntryView *view);
102 static void rb_entry_view_pixbuf_clicked_cb (RBEntryView *view,
103 const char *path,
104 RBCellRendererPixbuf *cellpixbuf);
105 static gboolean rb_entry_view_button_press_cb (GtkTreeView *treeview,
106 GdkEventButton *event,
107 RBEntryView *view);
108 static gboolean rb_entry_view_popup_menu_cb (GtkTreeView *treeview,
109 RBEntryView *view);
110 static void rb_entry_view_entry_is_visible (RBEntryView *view, RhythmDBEntry *entry,
111 gboolean *realized, gboolean *visible,
112 GtkTreeIter *iter);
113 static void rb_entry_view_scroll_to_iter (RBEntryView *view,
114 GtkTreeIter *iter);
115 static gboolean rb_entry_view_emit_row_changed (RBEntryView *view,
116 RhythmDBEntry *entry);
117 static void rb_entry_view_playing_song_changed (RBShellPlayer *player,
118 RhythmDBEntry *entry,
119 RBEntryView *view);
121 struct RBEntryViewPrivate
123 RhythmDB *db;
124 RBShellPlayer *shell_player;
126 RhythmDBQueryModel *model;
128 GtkWidget *treeview;
129 GtkTreeSelection *selection;
131 RBEntryViewState playing_state;
132 RhythmDBQueryModel *playing_model;
133 RhythmDBEntry *playing_entry;
134 gboolean playing_entry_in_view;
135 guint selection_changed_id;
137 gboolean is_drag_source;
138 gboolean is_drag_dest;
140 GdkPixbuf *playing_pixbuf;
141 GdkPixbuf *paused_pixbuf;
142 GdkPixbuf *error_pixbuf;
144 char *sorting_key;
145 guint sorting_gconf_notification_id;
146 GtkTreeViewColumn *sorting_column;
147 gint sorting_order;
148 char *sorting_column_name;
150 gboolean have_selection, have_complete_selection;
152 GHashTable *column_key_map;
154 guint gconf_notification_id;
155 GHashTable *propid_column_map;
156 GHashTable *column_sort_data_map;
159 #define RB_ENTRY_VIEW_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_ENTRY_VIEW, RBEntryViewPrivate))
161 enum
163 ENTRY_ADDED,
164 ENTRY_DELETED,
165 ENTRIES_REPLACED,
166 SELECTION_CHANGED,
167 ENTRY_ACTIVATED,
168 SHOW_POPUP,
169 HAVE_SEL_CHANGED,
170 SORT_ORDER_CHANGED,
171 LAST_SIGNAL
174 enum
176 PROP_0,
177 PROP_DB,
178 PROP_SHELL_PLAYER,
179 PROP_MODEL,
180 PROP_SORTING_KEY,
181 PROP_IS_DRAG_SOURCE,
182 PROP_IS_DRAG_DEST,
183 PROP_PLAYING_STATE
186 G_DEFINE_TYPE (RBEntryView, rb_entry_view, GTK_TYPE_SCROLLED_WINDOW)
188 static guint rb_entry_view_signals[LAST_SIGNAL] = { 0 };
190 static GQuark rb_entry_view_column_always_visible;
192 static gboolean
193 type_ahead_search_func (GtkTreeModel *model,
194 gint column,
195 const gchar *key,
196 GtkTreeIter *iter,
197 gpointer search_data)
199 RhythmDBEntry *entry;
200 gchar *folded;
201 const gchar *entry_folded;
202 gboolean res;
204 gtk_tree_model_get (model, iter, 0, &entry, -1);
205 folded = rb_search_fold (key);
206 entry_folded = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_TITLE_FOLDED);
207 rhythmdb_entry_unref (entry);
209 if (entry_folded == NULL || folded == NULL)
210 return 1;
212 res = (strstr (entry_folded, folded) == NULL);
213 g_free (folded);
215 return res;
218 static void
219 rb_entry_view_class_init (RBEntryViewClass *klass)
221 GObjectClass *object_class = G_OBJECT_CLASS (klass);
222 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
224 object_class->finalize = rb_entry_view_finalize;
225 object_class->constructor = rb_entry_view_constructor;
227 object_class->set_property = rb_entry_view_set_property;
228 object_class->get_property = rb_entry_view_get_property;
230 widget_class->grab_focus = rb_entry_view_grab_focus;
232 g_object_class_install_property (object_class,
233 PROP_DB,
234 g_param_spec_object ("db",
235 "RhythmDB",
236 "RhythmDB database",
237 RHYTHMDB_TYPE,
238 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
239 g_object_class_install_property (object_class,
240 PROP_SHELL_PLAYER,
241 g_param_spec_object ("shell-player",
242 "RBShellPlayer",
243 "RBShellPlayer object",
244 RB_TYPE_SHELL_PLAYER,
245 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
246 g_object_class_install_property (object_class,
247 PROP_MODEL,
248 g_param_spec_object ("model",
249 "RhythmDBQueryModel",
250 "RhythmDBQueryModel",
251 RHYTHMDB_TYPE_QUERY_MODEL,
252 G_PARAM_READWRITE));
253 g_object_class_install_property (object_class,
254 PROP_SORTING_KEY,
255 g_param_spec_string ("sort-key",
256 "sorting key",
257 "sorting key",
259 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
260 g_object_class_install_property (object_class,
261 PROP_IS_DRAG_SOURCE,
262 g_param_spec_boolean ("is-drag-source",
263 "is drag source",
264 "whether or not this is a drag source",
265 FALSE,
266 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
267 g_object_class_install_property (object_class,
268 PROP_IS_DRAG_DEST,
269 g_param_spec_boolean ("is-drag-dest",
270 "is drag dest",
271 "whether or not this is a drag dest",
272 FALSE,
273 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
274 g_object_class_install_property (object_class,
275 PROP_PLAYING_STATE,
276 g_param_spec_int ("playing-state",
277 "playing state",
278 "playback state for this entry view",
279 RB_ENTRY_VIEW_NOT_PLAYING,
280 RB_ENTRY_VIEW_PAUSED,
281 RB_ENTRY_VIEW_NOT_PLAYING,
282 G_PARAM_READWRITE));
283 rb_entry_view_signals[ENTRY_ADDED] =
284 g_signal_new ("entry-added",
285 G_OBJECT_CLASS_TYPE (object_class),
286 G_SIGNAL_RUN_LAST,
287 G_STRUCT_OFFSET (RBEntryViewClass, entry_added),
288 NULL, NULL,
289 g_cclosure_marshal_VOID__BOXED,
290 G_TYPE_NONE,
292 RHYTHMDB_TYPE_ENTRY);
293 rb_entry_view_signals[ENTRY_DELETED] =
294 g_signal_new ("entry-deleted",
295 G_OBJECT_CLASS_TYPE (object_class),
296 G_SIGNAL_RUN_LAST,
297 G_STRUCT_OFFSET (RBEntryViewClass, entry_deleted),
298 NULL, NULL,
299 g_cclosure_marshal_VOID__BOXED,
300 G_TYPE_NONE,
302 RHYTHMDB_TYPE_ENTRY);
303 rb_entry_view_signals[ENTRIES_REPLACED] =
304 g_signal_new ("entries-replaced",
305 G_OBJECT_CLASS_TYPE (object_class),
306 G_SIGNAL_RUN_LAST,
307 G_STRUCT_OFFSET (RBEntryViewClass, entries_replaced),
308 NULL, NULL,
309 g_cclosure_marshal_VOID__VOID,
310 G_TYPE_NONE,
312 rb_entry_view_signals[ENTRY_ACTIVATED] =
313 g_signal_new ("entry-activated",
314 G_OBJECT_CLASS_TYPE (object_class),
315 G_SIGNAL_RUN_LAST,
316 G_STRUCT_OFFSET (RBEntryViewClass, entry_activated),
317 NULL, NULL,
318 g_cclosure_marshal_VOID__BOXED,
319 G_TYPE_NONE,
321 RHYTHMDB_TYPE_ENTRY);
322 rb_entry_view_signals[SELECTION_CHANGED] =
323 g_signal_new ("selection-changed",
324 G_OBJECT_CLASS_TYPE (object_class),
325 G_SIGNAL_RUN_LAST,
326 G_STRUCT_OFFSET (RBEntryViewClass, selection_changed),
327 NULL, NULL,
328 g_cclosure_marshal_VOID__VOID,
329 G_TYPE_NONE,
331 rb_entry_view_signals[SHOW_POPUP] =
332 g_signal_new ("show_popup",
333 G_OBJECT_CLASS_TYPE (object_class),
334 G_SIGNAL_RUN_LAST,
335 G_STRUCT_OFFSET (RBEntryViewClass, show_popup),
336 NULL, NULL,
337 g_cclosure_marshal_VOID__BOOLEAN,
338 G_TYPE_NONE,
340 G_TYPE_BOOLEAN);
341 rb_entry_view_signals[HAVE_SEL_CHANGED] =
342 g_signal_new ("have_selection_changed",
343 G_OBJECT_CLASS_TYPE (object_class),
344 G_SIGNAL_RUN_LAST,
345 G_STRUCT_OFFSET (RBEntryViewClass, have_selection_changed),
346 NULL, NULL,
347 g_cclosure_marshal_VOID__BOOLEAN,
348 G_TYPE_NONE,
350 G_TYPE_BOOLEAN);
351 rb_entry_view_signals[SORT_ORDER_CHANGED] =
352 g_signal_new ("sort-order-changed",
353 G_OBJECT_CLASS_TYPE (object_class),
354 G_SIGNAL_RUN_LAST,
355 G_STRUCT_OFFSET (RBEntryViewClass, sort_order_changed),
356 NULL, NULL,
357 g_cclosure_marshal_VOID__VOID,
358 G_TYPE_NONE,
361 g_type_class_add_private (klass, sizeof (RBEntryViewPrivate));
363 rb_entry_view_column_always_visible = g_quark_from_static_string ("rb_entry_view_column_always_visible");
366 static void
367 rb_entry_view_init (RBEntryView *view)
369 GtkIconTheme *icon_theme;
371 view->priv = RB_ENTRY_VIEW_GET_PRIVATE (view);
373 icon_theme = gtk_icon_theme_get_default ();
375 view->priv->playing_pixbuf = gtk_icon_theme_load_icon (icon_theme,
376 "stock_media-play",
379 NULL);
380 view->priv->paused_pixbuf = gtk_icon_theme_load_icon (icon_theme,
381 "stock_media-pause",
384 NULL);
385 view->priv->error_pixbuf = gtk_icon_theme_load_icon (icon_theme,
386 "stock_dialog-error",
389 NULL);
391 view->priv->propid_column_map = g_hash_table_new (NULL, NULL);
392 view->priv->column_sort_data_map = g_hash_table_new_full (NULL, NULL, NULL, g_free);
393 view->priv->column_key_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
396 static void
397 rb_entry_view_finalize (GObject *object)
399 RBEntryView *view;
401 g_return_if_fail (object != NULL);
402 g_return_if_fail (RB_IS_ENTRY_VIEW (object));
404 view = RB_ENTRY_VIEW (object);
406 g_return_if_fail (view->priv != NULL);
408 if (view->priv->gconf_notification_id > 0)
409 eel_gconf_notification_remove (view->priv->gconf_notification_id);
410 if (view->priv->sorting_gconf_notification_id > 0)
411 eel_gconf_notification_remove (view->priv->sorting_gconf_notification_id);
413 if (view->priv->selection_changed_id > 0)
414 g_source_remove (view->priv->selection_changed_id);
416 g_hash_table_destroy (view->priv->propid_column_map);
417 g_hash_table_destroy (view->priv->column_sort_data_map);
418 g_hash_table_destroy (view->priv->column_key_map);
420 if (view->priv->playing_pixbuf != NULL)
421 g_object_unref (view->priv->playing_pixbuf);
422 if (view->priv->paused_pixbuf != NULL)
423 g_object_unref (view->priv->paused_pixbuf);
424 if (view->priv->error_pixbuf != NULL)
425 g_object_unref (view->priv->error_pixbuf);
427 if (view->priv->playing_model != NULL) {
428 g_object_unref (view->priv->playing_model);
430 if (view->priv->model != NULL) {
431 g_object_unref (view->priv->model);
434 g_free (view->priv->sorting_key);
435 g_free (view->priv->sorting_column_name);
437 G_OBJECT_CLASS (rb_entry_view_parent_class)->finalize (object);
440 static void
441 rb_entry_view_set_shell_player_internal (RBEntryView *view,
442 RBShellPlayer *player)
444 if (view->priv->shell_player != NULL) {
445 g_signal_handlers_disconnect_by_func (view->priv->shell_player,
446 G_CALLBACK (rb_entry_view_playing_song_changed),
447 view);
450 view->priv->shell_player = player;
452 g_signal_connect_object (view->priv->shell_player,
453 "playing-song-changed",
454 G_CALLBACK (rb_entry_view_playing_song_changed),
455 view, 0);
458 static void
459 rb_entry_view_set_model_internal (RBEntryView *view,
460 RhythmDBQueryModel *model)
462 if (view->priv->model != NULL) {
463 g_signal_handlers_disconnect_by_func (view->priv->model,
464 G_CALLBACK (rb_entry_view_row_inserted_cb),
465 view);
466 g_signal_handlers_disconnect_by_func (view->priv->model,
467 G_CALLBACK (rb_entry_view_row_deleted_cb),
468 view);
469 g_signal_handlers_disconnect_by_func (view->priv->model,
470 G_CALLBACK (rb_entry_view_rows_reordered_cb),
471 view);
472 g_object_unref (view->priv->model);
475 gtk_tree_selection_unselect_all (view->priv->selection);
477 view->priv->model = model;
478 if (view->priv->model != NULL) {
479 g_object_ref (view->priv->model);
480 g_signal_connect_object (view->priv->model,
481 "row_inserted",
482 G_CALLBACK (rb_entry_view_row_inserted_cb),
483 view,
485 g_signal_connect_object (view->priv->model,
486 "row_deleted",
487 G_CALLBACK (rb_entry_view_row_deleted_cb),
488 view,
490 g_signal_connect_object (view->priv->model,
491 "rows_reordered",
492 G_CALLBACK (rb_entry_view_rows_reordered_cb),
493 view,
497 if (view->priv->sorting_column != NULL) {
498 rb_entry_view_resort_model (view);
501 if (view->priv->model != NULL) {
502 gtk_tree_view_set_model (GTK_TREE_VIEW (view->priv->treeview),
503 GTK_TREE_MODEL (view->priv->model));
506 view->priv->have_selection = FALSE;
507 view->priv->have_complete_selection = FALSE;
509 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRIES_REPLACED], 0);
512 static void
513 rb_entry_view_set_property (GObject *object,
514 guint prop_id,
515 const GValue *value,
516 GParamSpec *pspec)
518 RBEntryView *view = RB_ENTRY_VIEW (object);
520 switch (prop_id) {
521 case PROP_DB:
522 view->priv->db = g_value_get_object (value);
523 break;
524 case PROP_SHELL_PLAYER:
525 rb_entry_view_set_shell_player_internal (view, g_value_get_object (value));
526 break;
527 case PROP_SORTING_KEY:
528 g_free (view->priv->sorting_key);
529 view->priv->sorting_key = g_value_dup_string (value);
530 break;
531 case PROP_MODEL:
532 rb_entry_view_set_model_internal (view, g_value_get_object (value));
533 break;
534 case PROP_IS_DRAG_SOURCE:
535 view->priv->is_drag_source = g_value_get_boolean (value);
536 break;
537 case PROP_IS_DRAG_DEST:
538 view->priv->is_drag_dest = g_value_get_boolean (value);
539 break;
540 case PROP_PLAYING_STATE:
541 view->priv->playing_state = g_value_get_int (value);
543 /* redraw the playing entry, as the icon will have changed */
544 if (view->priv->playing_entry != NULL) {
545 rb_entry_view_emit_row_changed (view, view->priv->playing_entry);
547 break;
548 default:
549 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
550 break;
554 static void
555 rb_entry_view_get_property (GObject *object,
556 guint prop_id,
557 GValue *value,
558 GParamSpec *pspec)
560 RBEntryView *view = RB_ENTRY_VIEW (object);
562 switch (prop_id) {
563 case PROP_DB:
564 g_value_set_object (value, view->priv->db);
565 break;
566 case PROP_SHELL_PLAYER:
567 g_value_set_object (value, view->priv->shell_player);
568 break;
569 case PROP_SORTING_KEY:
570 g_value_set_string (value, view->priv->sorting_key);
571 break;
572 case PROP_IS_DRAG_SOURCE:
573 g_value_set_boolean (value, view->priv->is_drag_source);
574 break;
575 case PROP_IS_DRAG_DEST:
576 g_value_set_boolean (value, view->priv->is_drag_dest);
577 break;
578 case PROP_PLAYING_STATE:
579 g_value_set_int (value, view->priv->playing_state);
580 break;
581 default:
582 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
583 break;
587 RBEntryView *
588 rb_entry_view_new (RhythmDB *db,
589 GObject *shell_player,
590 const char *sort_key,
591 gboolean is_drag_source,
592 gboolean is_drag_dest)
594 RBEntryView *view;
596 view = RB_ENTRY_VIEW (g_object_new (RB_TYPE_ENTRY_VIEW,
597 "hadjustment", NULL,
598 "vadjustment", NULL,
599 "hscrollbar_policy", GTK_POLICY_AUTOMATIC,
600 "vscrollbar_policy", GTK_POLICY_ALWAYS,
601 "shadow_type", GTK_SHADOW_IN,
602 "db", db,
603 "shell-player", RB_SHELL_PLAYER (shell_player),
604 "sort-key", sort_key,
605 "is-drag-source", is_drag_source,
606 "is-drag-dest", is_drag_dest,
607 NULL));
609 g_return_val_if_fail (view->priv != NULL, NULL);
611 return view;
614 void
615 rb_entry_view_set_model (RBEntryView *view,
616 RhythmDBQueryModel *model)
618 g_object_set (view, "model", model, NULL);
621 /* Sweet name, eh? */
622 struct RBEntryViewCellDataFuncData {
623 RBEntryView *view;
624 RhythmDBPropType propid;
627 static void
628 rb_entry_view_playing_cell_data_func (GtkTreeViewColumn *column,
629 GtkCellRenderer *renderer,
630 GtkTreeModel *tree_model,
631 GtkTreeIter *iter,
632 RBEntryView *view)
634 RhythmDBEntry *entry;
635 GdkPixbuf *pixbuf = NULL;
637 entry = rhythmdb_query_model_iter_to_entry (view->priv->model, iter);
639 if (entry == NULL) {
640 return;
643 if (entry == view->priv->playing_entry) {
644 switch (view->priv->playing_state) {
645 case RB_ENTRY_VIEW_PLAYING:
646 pixbuf = view->priv->playing_pixbuf;
647 break;
648 case RB_ENTRY_VIEW_PAUSED:
649 pixbuf = view->priv->paused_pixbuf;
650 break;
651 default:
652 pixbuf = NULL;
653 break;
657 if (pixbuf == NULL && rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR)) {
658 pixbuf = view->priv->error_pixbuf;
661 g_object_set (renderer, "pixbuf", pixbuf, NULL);
663 rhythmdb_entry_unref (entry);
666 static void
667 rb_entry_view_rating_cell_data_func (GtkTreeViewColumn *column,
668 GtkCellRenderer *renderer,
669 GtkTreeModel *tree_model,
670 GtkTreeIter *iter,
671 RBEntryView *view)
673 RhythmDBEntry *entry;
675 entry = rhythmdb_query_model_iter_to_entry (view->priv->model, iter);
677 g_object_set (renderer,
678 "rating", rhythmdb_entry_get_double (entry, RHYTHMDB_PROP_RATING),
679 NULL);
681 rhythmdb_entry_unref (entry);
684 static void
685 rb_entry_view_long_cell_data_func (GtkTreeViewColumn *column,
686 GtkCellRenderer *renderer,
687 GtkTreeModel *tree_model,
688 GtkTreeIter *iter,
689 struct RBEntryViewCellDataFuncData *data)
691 RhythmDBEntry *entry;
692 char *str;
693 gulong val;
695 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
697 val = rhythmdb_entry_get_ulong (entry, data->propid);
699 if (val > 0)
700 str = g_strdup_printf ("%lu", val);
701 else
702 str = g_strdup ("");
704 g_object_set (renderer, "text", str, NULL);
705 g_free (str);
706 rhythmdb_entry_unref (entry);
709 static void
710 rb_entry_view_play_count_cell_data_func (GtkTreeViewColumn *column,
711 GtkCellRenderer *renderer,
712 GtkTreeModel *tree_model,
713 GtkTreeIter * iter,
714 struct RBEntryViewCellDataFuncData *data)
716 RhythmDBEntry *entry;
717 gulong i;
718 char *str;
720 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
722 i = rhythmdb_entry_get_ulong (entry, data->propid);
723 if (i == 0)
724 str = _("Never");
725 else
726 str = g_strdup_printf ("%ld", i);
728 g_object_set (renderer, "text", str, NULL);
729 if (i != 0)
730 g_free (str);
732 rhythmdb_entry_unref (entry);
735 static void
736 rb_entry_view_duration_cell_data_func (GtkTreeViewColumn *column,
737 GtkCellRenderer *renderer,
738 GtkTreeModel *tree_model,
739 GtkTreeIter *iter,
740 struct RBEntryViewCellDataFuncData *data)
742 RhythmDBEntry *entry;
743 gulong duration;
744 char *str;
746 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
747 duration = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DURATION);
749 str = rb_make_duration_string (duration);
750 g_object_set (renderer, "text", str, NULL);
751 g_free (str);
752 rhythmdb_entry_unref (entry);
755 static void
756 rb_entry_view_year_cell_data_func (GtkTreeViewColumn *column,
757 GtkCellRenderer *renderer,
758 GtkTreeModel *tree_model,
759 GtkTreeIter *iter,
760 struct RBEntryViewCellDataFuncData *data)
762 RhythmDBEntry *entry;
763 char str[255];
764 int julian;
765 GDate *date;
767 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
768 julian = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_DATE);
770 if (julian > 0) {
771 date = g_date_new_julian (julian);
772 g_date_strftime (str, sizeof (str), "%Y", date);
773 g_object_set (renderer, "text", str, NULL);
774 g_date_free (date);
775 } else {
776 g_object_set (renderer, "text", _("Unknown"), NULL);
779 rhythmdb_entry_unref (entry);
782 static void
783 rb_entry_view_quality_cell_data_func (GtkTreeViewColumn *column,
784 GtkCellRenderer *renderer,
785 GtkTreeModel *tree_model,
786 GtkTreeIter *iter,
787 struct RBEntryViewCellDataFuncData *data)
789 RhythmDBEntry *entry;
790 guint bitrate;
792 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
794 bitrate = rhythmdb_entry_get_ulong (entry, data->propid);
796 if (bitrate > 0) {
797 char *s = g_strdup_printf (_("%u kbps"), (guint)bitrate);
798 g_object_set (renderer, "text", s, NULL);
799 g_free (s);
800 } else {
801 g_object_set (renderer, "text", _("Unknown"), NULL);
804 rhythmdb_entry_unref (entry);
807 static void
808 rb_entry_view_location_cell_data_func (GtkTreeViewColumn *column,
809 GtkCellRenderer *renderer,
810 GtkTreeModel *tree_model,
811 GtkTreeIter *iter,
812 struct RBEntryViewCellDataFuncData *data)
814 RhythmDBEntry *entry;
815 const char *location;
816 char *str;
818 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
820 location = rhythmdb_entry_get_string (entry, data->propid);
821 str = gnome_vfs_unescape_string_for_display (location);
823 g_object_set (renderer, "text", str, NULL);
824 g_free (str);
826 rhythmdb_entry_unref (entry);
829 static void
830 rb_entry_view_string_cell_data_func (GtkTreeViewColumn *column,
831 GtkCellRenderer *renderer,
832 GtkTreeModel *tree_model,
833 GtkTreeIter *iter,
834 struct RBEntryViewCellDataFuncData *data)
836 RhythmDBEntry *entry;
837 const char *str;
839 entry = rhythmdb_query_model_iter_to_entry (data->view->priv->model, iter);
841 str = rhythmdb_entry_get_string (entry, data->propid);
842 if (str != NULL) {
843 g_object_set (renderer, "text", str, NULL);
846 rhythmdb_entry_unref (entry);
849 static void
850 rb_entry_view_sync_sorting (RBEntryView *view)
852 GtkTreeViewColumn *column;
853 gint direction;
854 char *column_name;
856 column_name = NULL;
857 rb_entry_view_get_sorting_order (view, &column_name, &direction);
859 if (column_name == NULL) {
860 return;
863 column = g_hash_table_lookup (view->priv->column_key_map, column_name);
864 if (column == NULL) {
865 g_free (column_name);
866 return;
869 rb_debug ("Updating EntryView sort order to %s:%d", column_name, direction);
871 /* remove the old sorting indicator */
872 if (view->priv->sorting_column)
873 gtk_tree_view_column_set_sort_indicator (view->priv->sorting_column, FALSE);
875 /* set the sorting order and indicator of the new sorting column */
876 view->priv->sorting_column = column;
877 gtk_tree_view_column_set_sort_indicator (column, TRUE);
878 gtk_tree_view_column_set_sort_order (column, direction);
880 rb_debug ("emitting sort order changed");
881 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SORT_ORDER_CHANGED], 0);
883 g_free (column_name);
886 const char *
887 rb_entry_view_get_sorting_type (RBEntryView *view)
889 char *sorttype;
890 GString *key = g_string_new (view->priv->sorting_column_name);
892 g_string_append_c (key, ',');
894 switch (view->priv->sorting_order)
896 case GTK_SORT_ASCENDING:
897 g_string_append (key, "ascending");
898 break;
899 case GTK_SORT_DESCENDING:
900 g_string_append (key, "descending");
901 break;
902 default:
903 g_assert_not_reached ();
906 sorttype = g_strdup(key->str);
907 g_string_free (key, TRUE);
909 return sorttype;
912 void
913 rb_entry_view_set_sorting_type (RBEntryView *view,
914 const char *sorttype)
916 char **strs;
918 if (!sorttype || !strchr (sorttype, ',')) {
919 rb_debug ("malformed sort data: %s", (sorttype) ? sorttype : "(null)");
920 return;
923 strs = g_strsplit (sorttype, ",", 0);
925 g_free (view->priv->sorting_column_name);
926 view->priv->sorting_column_name = g_strdup(strs[0]);
928 if (!strcmp ("ascending", strs[1]))
929 view->priv->sorting_order = GTK_SORT_ASCENDING;
930 else if (!strcmp ("descending", strs[1]))
931 view->priv->sorting_order = GTK_SORT_DESCENDING;
932 else {
933 g_warning ("atttempting to sort in unknown direction");
934 view->priv->sorting_order = GTK_SORT_ASCENDING;
937 g_strfreev (strs);
939 rb_entry_view_sync_sorting (view);
942 void
943 rb_entry_view_get_sorting_order (RBEntryView *view,
944 char **column_name,
945 gint *sort_order)
947 g_return_if_fail (RB_IS_ENTRY_VIEW (view));
949 if (column_name != NULL) {
950 *column_name = g_strdup (view->priv->sorting_column_name);
953 if (sort_order != NULL) {
954 *sort_order = view->priv->sorting_order;
958 void
959 rb_entry_view_set_sorting_order (RBEntryView *view,
960 const char *column_name,
961 gint sort_order)
963 if (column_name == NULL)
964 return;
966 g_free (view->priv->sorting_column_name);
967 view->priv->sorting_column_name = g_strdup (column_name);
968 view->priv->sorting_order = sort_order;
970 rb_entry_view_sync_sorting (view);
973 static void
974 rb_entry_view_column_clicked_cb (GtkTreeViewColumn *column, RBEntryView *view)
976 gint sort_order;
977 char *clicked_column;
979 rb_debug ("sorting on column %p", column);
981 /* identify the clicked column, and then update the sorting order */
982 clicked_column = (char*) g_object_get_data (G_OBJECT (column), "rb-entry-view-key");
983 sort_order = view->priv->sorting_order;
985 if (view->priv->sorting_column_name
986 && !strcmp(clicked_column, view->priv->sorting_column_name)
987 && (sort_order == GTK_SORT_ASCENDING))
988 sort_order = GTK_SORT_DESCENDING;
989 else
990 sort_order = GTK_SORT_ASCENDING;
992 rb_entry_view_set_sorting_order (view, clicked_column, sort_order);
994 /* update the sort order in GConf */
995 if (view->priv->sorting_key)
996 eel_gconf_set_string (view->priv->sorting_key, rb_entry_view_get_sorting_type(view));
999 void
1000 rb_entry_view_append_column (RBEntryView *view,
1001 RBEntryViewColumn coltype,
1002 gboolean always_visible)
1004 GtkTreeViewColumn *column;
1005 GtkCellRenderer *renderer = NULL;
1006 struct RBEntryViewCellDataFuncData *cell_data;
1007 const char *title = NULL;
1008 const char *key = NULL;
1009 const char *strings[4] = {0};
1010 GtkTreeCellDataFunc cell_data_func = NULL;
1011 GCompareDataFunc sort_func = NULL;
1012 RhythmDBPropType propid;
1013 RhythmDBPropType sort_propid = RHYTHMDB_NUM_PROPERTIES;
1014 gboolean ellipsize = FALSE;
1015 gboolean resizable = TRUE;
1016 gint column_width = -1;
1018 column = gtk_tree_view_column_new ();
1020 cell_data = g_new0 (struct RBEntryViewCellDataFuncData, 1);
1021 cell_data->view = view;
1023 switch (coltype) {
1024 case RB_ENTRY_VIEW_COL_TRACK_NUMBER:
1025 propid = RHYTHMDB_PROP_TRACK_NUMBER;
1026 cell_data->propid = propid;
1027 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_long_cell_data_func;
1028 sort_func = (GCompareDataFunc) rhythmdb_query_model_track_sort_func;
1029 title = _("Trac_k");
1030 key = "Track";
1031 strings[0] = title;
1032 strings[1] = "9999";
1033 break;
1034 case RB_ENTRY_VIEW_COL_TITLE:
1035 propid = RHYTHMDB_PROP_TITLE;
1036 cell_data->propid = propid;
1037 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1038 sort_propid = RHYTHMDB_PROP_TITLE_SORT_KEY;
1039 sort_func = (GCompareDataFunc) rhythmdb_query_model_string_sort_func;
1040 title = _("_Title");
1041 key = "Title";
1042 ellipsize = TRUE;
1043 break;
1044 case RB_ENTRY_VIEW_COL_ARTIST:
1045 propid = RHYTHMDB_PROP_ARTIST;
1046 cell_data->propid = propid;
1047 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1048 sort_propid = RHYTHMDB_PROP_ARTIST_SORT_KEY;
1049 sort_func = (GCompareDataFunc) rhythmdb_query_model_artist_sort_func;
1050 title = _("Art_ist");
1051 key = "Artist";
1052 ellipsize = TRUE;
1053 break;
1054 case RB_ENTRY_VIEW_COL_ALBUM:
1055 propid = RHYTHMDB_PROP_ALBUM;
1056 cell_data->propid = propid;
1057 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1058 sort_propid = RHYTHMDB_PROP_ALBUM_SORT_KEY;
1059 sort_func = (GCompareDataFunc) rhythmdb_query_model_album_sort_func;
1060 title = _("_Album");
1061 key = "Album";
1062 ellipsize = TRUE;
1063 break;
1064 case RB_ENTRY_VIEW_COL_GENRE:
1065 propid = RHYTHMDB_PROP_GENRE;
1066 cell_data->propid = propid;
1067 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1068 sort_propid = RHYTHMDB_PROP_GENRE_SORT_KEY;
1069 sort_func = (GCompareDataFunc) rhythmdb_query_model_genre_sort_func;
1070 title = _("_Genre");
1071 key = "Genre";
1072 ellipsize = TRUE;
1073 break;
1074 case RB_ENTRY_VIEW_COL_DURATION:
1075 propid = RHYTHMDB_PROP_DURATION;
1076 cell_data->propid = propid;
1077 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_duration_cell_data_func;
1078 sort_propid = cell_data->propid;
1079 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1080 title = _("Tim_e");
1081 key = "Time";
1082 strings[0] = title;
1083 strings[1] = "000:00";
1084 strings[2] = _("Unknown");
1085 break;
1086 case RB_ENTRY_VIEW_COL_YEAR:
1087 propid = RHYTHMDB_PROP_DATE;
1088 cell_data->propid = propid;
1089 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_year_cell_data_func;
1090 sort_propid = cell_data->propid;
1091 sort_func = (GCompareDataFunc) rhythmdb_query_model_date_sort_func;
1092 title = _("_Year");
1093 key = "Year";
1094 strings[0] = title;
1095 strings[1] = "0000";
1096 strings[2] = _("Unknown");
1097 break;
1098 case RB_ENTRY_VIEW_COL_QUALITY:
1099 propid = RHYTHMDB_PROP_BITRATE;
1100 cell_data->propid = propid;
1101 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_quality_cell_data_func;
1102 sort_propid = cell_data->propid;
1103 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1104 title = _("_Quality");
1105 key = "Quality";
1106 strings[0] = title;
1107 strings[1] = _("000 kbps");
1108 strings[2] = _("Unknown");
1109 break;
1110 case RB_ENTRY_VIEW_COL_RATING:
1111 propid = RHYTHMDB_PROP_RATING;
1112 sort_func = (GCompareDataFunc) rhythmdb_query_model_double_ceiling_sort_func;
1114 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &column_width, NULL);
1115 column_width = column_width * 5 + 5;
1116 resizable = FALSE;
1117 title = _("_Rating");
1118 key = "Rating";
1120 renderer = rb_cell_renderer_rating_new ();
1121 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1122 gtk_tree_view_column_set_cell_data_func (column, renderer,
1123 (GtkTreeCellDataFunc)
1124 rb_entry_view_rating_cell_data_func,
1125 view,
1126 NULL);
1127 g_signal_connect_object (renderer,
1128 "rated",
1129 G_CALLBACK (rb_entry_view_rated_cb),
1130 view,
1132 break;
1133 case RB_ENTRY_VIEW_COL_PLAY_COUNT:
1134 propid = RHYTHMDB_PROP_PLAY_COUNT;
1135 cell_data->propid = propid;
1136 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_play_count_cell_data_func;
1137 sort_propid = cell_data->propid;
1138 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1139 title = _("_Play Count");
1140 key = "PlayCount";
1141 strings[0] = title;
1142 strings[1] = _("Never");
1143 strings[2] = "9999";
1144 break;
1145 case RB_ENTRY_VIEW_COL_LAST_PLAYED:
1146 propid = RHYTHMDB_PROP_LAST_PLAYED;
1147 cell_data->propid = RHYTHMDB_PROP_LAST_PLAYED_STR;
1148 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1149 sort_propid = RHYTHMDB_PROP_LAST_PLAYED;
1150 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1151 title = _("_Last Played");
1152 key = "LastPlayed";
1153 strings[0] = title;
1154 strings[1] = rb_entry_view_get_time_date_column_sample ();
1155 strings[2] = _("Never");
1156 break;
1157 case RB_ENTRY_VIEW_COL_FIRST_SEEN:
1158 propid = RHYTHMDB_PROP_FIRST_SEEN;
1159 cell_data->propid = RHYTHMDB_PROP_FIRST_SEEN_STR;
1160 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1161 sort_propid = RHYTHMDB_PROP_FIRST_SEEN;
1162 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1163 title = _("_Date Added");
1164 key = "FirstSeen";
1165 strings[0] = title;
1166 strings[1] = rb_entry_view_get_time_date_column_sample ();
1167 break;
1168 case RB_ENTRY_VIEW_COL_LAST_SEEN:
1169 propid = RHYTHMDB_PROP_LAST_SEEN;
1170 cell_data->propid = RHYTHMDB_PROP_LAST_SEEN_STR;
1171 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1172 sort_propid = RHYTHMDB_PROP_LAST_SEEN;
1173 sort_func = (GCompareDataFunc) rhythmdb_query_model_ulong_sort_func;
1174 title = _("Last _Seen");
1175 key = "LastSeen";
1176 strings[0] = title;
1177 strings[1] = rb_entry_view_get_time_date_column_sample ();
1178 break;
1179 case RB_ENTRY_VIEW_COL_LOCATION:
1180 propid = RHYTHMDB_PROP_LOCATION;
1181 cell_data->propid = RHYTHMDB_PROP_LOCATION;
1182 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_location_cell_data_func;
1183 sort_propid = RHYTHMDB_PROP_LOCATION;
1184 sort_func = (GCompareDataFunc) rhythmdb_query_model_location_sort_func;
1185 title = _("L_ocation");
1186 key = "Location";
1187 ellipsize = TRUE;
1188 break;
1189 case RB_ENTRY_VIEW_COL_ERROR:
1190 propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
1191 cell_data->propid = RHYTHMDB_PROP_PLAYBACK_ERROR;
1192 cell_data_func = (GtkTreeCellDataFunc) rb_entry_view_string_cell_data_func;
1193 title = _("Error");
1194 key = "Error";
1195 ellipsize = TRUE;
1196 break;
1197 default:
1198 g_assert_not_reached ();
1199 propid = -1;
1200 break;
1203 if (sort_propid == RHYTHMDB_NUM_PROPERTIES)
1204 sort_propid = propid;
1206 if (renderer == NULL) {
1207 renderer = gtk_cell_renderer_text_new ();
1208 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1209 gtk_tree_view_column_set_cell_data_func (column, renderer,
1210 cell_data_func, cell_data, g_free);
1211 } else {
1212 g_free (cell_data);
1216 * Columns must either be expanding (ellipsized) or have a
1217 * fixed minimum width specified. Otherwise, gtk+ gives them a
1218 * width of 0.
1220 if (ellipsize) {
1221 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1222 gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
1223 } else if (column_width != -1) {
1224 gtk_tree_view_column_set_fixed_width (column, column_width);
1225 } else {
1226 rb_entry_view_set_fixed_column_width (view, column, renderer, strings);
1229 if (resizable)
1230 gtk_tree_view_column_set_resizable (column, TRUE);
1232 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1233 gtk_tree_view_column_set_clickable (column, TRUE);
1235 if (always_visible)
1236 g_object_set_qdata (G_OBJECT (column),
1237 rb_entry_view_column_always_visible,
1238 GINT_TO_POINTER (1));
1240 g_hash_table_insert (view->priv->propid_column_map, GINT_TO_POINTER (propid), column);
1242 rb_entry_view_append_column_custom (view, column, title, key, sort_func, GINT_TO_POINTER (sort_propid));
1245 void
1246 rb_entry_view_append_column_custom (RBEntryView *view,
1247 GtkTreeViewColumn *column,
1248 const char *title,
1249 const char *key,
1250 GCompareDataFunc sort_func,
1251 gpointer data)
1253 rb_entry_view_insert_column_custom (view, column, title, key, sort_func, data, -1);
1256 void
1257 rb_entry_view_insert_column_custom (RBEntryView *view,
1258 GtkTreeViewColumn *column,
1259 const char *title,
1260 const char *key,
1261 GCompareDataFunc sort_func,
1262 gpointer data,
1263 gint position)
1265 struct RBEntryViewColumnSortData *sortdata;
1267 gtk_tree_view_column_set_title (column, title);
1268 gtk_tree_view_column_set_reorderable (column, FALSE);
1270 g_signal_connect_object (column, "clicked",
1271 G_CALLBACK (rb_entry_view_column_clicked_cb),
1272 view, 0);
1274 g_object_set_data_full (G_OBJECT (column), "rb-entry-view-key",
1275 g_strdup (key), g_free);
1277 rb_debug ("appending column: %p (%s)", column, title);
1279 gtk_tree_view_insert_column (GTK_TREE_VIEW (view->priv->treeview), column, position);
1281 if (sort_func != NULL) {
1282 sortdata = g_new (struct RBEntryViewColumnSortData, 1);
1283 sortdata->func = (GCompareDataFunc) sort_func;
1284 sortdata->data = data;
1285 g_hash_table_insert (view->priv->column_sort_data_map, column, sortdata);
1287 g_hash_table_insert (view->priv->column_key_map, g_strdup (key), column);
1289 rb_entry_view_sync_columns_visible (view);
1290 rb_entry_view_sync_sorting (view);
1293 void
1294 rb_entry_view_set_columns_clickable (RBEntryView *view,
1295 gboolean clickable)
1297 GList *columns, *tem;
1299 columns = gtk_tree_view_get_columns (GTK_TREE_VIEW (view->priv->treeview));
1300 for (tem = columns; tem; tem = tem->next) {
1301 /* only columns we can sort on should be clickable */
1302 GtkTreeViewColumn *column = (GtkTreeViewColumn *) tem->data;
1303 if (g_hash_table_lookup (view->priv->column_sort_data_map, column) != NULL)
1304 gtk_tree_view_column_set_clickable (tem->data, clickable);
1306 g_list_free (columns);
1309 static GObject *
1310 rb_entry_view_constructor (GType type,
1311 guint n_construct_properties,
1312 GObjectConstructParam *construct_properties)
1314 RBEntryView *view;
1315 RBEntryViewClass *klass;
1316 klass = RB_ENTRY_VIEW_CLASS (g_type_class_peek (RB_TYPE_ENTRY_VIEW));
1318 view = RB_ENTRY_VIEW (G_OBJECT_CLASS (rb_entry_view_parent_class)
1319 ->constructor (type, n_construct_properties, construct_properties));
1321 view->priv->treeview = GTK_WIDGET (gtk_tree_view_new ());
1322 gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view->priv->treeview), TRUE);
1324 gtk_tree_view_set_search_equal_func (GTK_TREE_VIEW (view->priv->treeview),
1325 type_ahead_search_func,
1326 NULL, NULL);
1328 g_signal_connect_object (view->priv->treeview,
1329 "button_press_event",
1330 G_CALLBACK (rb_entry_view_button_press_cb),
1331 view,
1333 g_signal_connect_object (view->priv->treeview,
1334 "row_activated",
1335 G_CALLBACK (rb_entry_view_row_activated_cb),
1336 view,
1338 g_signal_connect_object (view->priv->treeview,
1339 "popup_menu",
1340 G_CALLBACK (rb_entry_view_popup_menu_cb),
1341 view,
1343 view->priv->selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view->priv->treeview));
1344 g_signal_connect_object (view->priv->selection,
1345 "changed",
1346 G_CALLBACK (rb_entry_view_selection_changed_cb),
1347 view,
1350 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view->priv->treeview), TRUE);
1351 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view->priv->treeview), TRUE);
1352 gtk_tree_selection_set_mode (view->priv->selection, GTK_SELECTION_MULTIPLE);
1354 if (view->priv->is_drag_source) {
1355 rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view->priv->treeview),
1356 GDK_BUTTON1_MASK,
1357 rb_entry_view_drag_types,
1358 G_N_ELEMENTS (rb_entry_view_drag_types),
1359 GDK_ACTION_COPY);
1362 if (view->priv->is_drag_dest) {
1363 rb_tree_dnd_add_drag_dest_support (GTK_TREE_VIEW (view->priv->treeview),
1364 RB_TREE_DEST_CAN_DROP_BETWEEN | RB_TREE_DEST_EMPTY_VIEW_DROP,
1365 rb_entry_view_drag_types,
1366 G_N_ELEMENTS (rb_entry_view_drag_types),
1367 GDK_ACTION_COPY | GDK_ACTION_MOVE);
1370 gtk_container_add (GTK_CONTAINER (view), view->priv->treeview);
1373 GtkTreeViewColumn *column;
1374 GtkTooltips *tooltip;
1375 GtkCellRenderer *renderer;
1376 GtkWidget *image_widget;
1377 gint width;
1379 tooltip = gtk_tooltips_new ();
1381 /* Playing icon column */
1382 column = GTK_TREE_VIEW_COLUMN (gtk_tree_view_column_new ());
1383 renderer = rb_cell_renderer_pixbuf_new ();
1384 gtk_tree_view_column_pack_start (column, renderer, TRUE);
1385 gtk_tree_view_column_set_cell_data_func (column, renderer,
1386 (GtkTreeCellDataFunc)
1387 rb_entry_view_playing_cell_data_func,
1388 view,
1389 NULL);
1391 image_widget = gtk_image_new_from_icon_name ("stock_volume-max", GTK_ICON_SIZE_MENU);
1392 gtk_tree_view_column_set_widget (column, image_widget);
1393 gtk_widget_show (image_widget);
1395 gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
1396 gtk_tree_view_column_set_clickable (column, FALSE);
1397 gtk_icon_size_lookup (GTK_ICON_SIZE_MENU, &width, NULL);
1398 gtk_tree_view_column_set_fixed_width (column, width + 5);
1399 gtk_tree_view_append_column (GTK_TREE_VIEW (view->priv->treeview), column);
1400 g_signal_connect_swapped (renderer,
1401 "pixbuf-clicked",
1402 G_CALLBACK (rb_entry_view_pixbuf_clicked_cb),
1403 view);
1405 gtk_tooltips_set_tip (GTK_TOOLTIPS (tooltip), GTK_WIDGET (column->button),
1406 _("Now Playing"), NULL);
1409 view->priv->gconf_notification_id =
1410 eel_gconf_notification_add (CONF_UI_COLUMNS_SETUP,
1411 rb_entry_view_columns_config_changed_cb,
1412 view);
1413 if (view->priv->sorting_key) {
1414 view->priv->sorting_gconf_notification_id =
1415 eel_gconf_notification_add (view->priv->sorting_key,
1416 rb_entry_view_sort_key_changed_cb,
1417 view);
1420 if (view->priv->sorting_key) {
1421 char *s = eel_gconf_get_string (view->priv->sorting_key);
1422 rb_entry_view_set_sorting_type (view, s);
1423 g_free (s);
1427 RhythmDBQueryModel *query_model;
1428 query_model = rhythmdb_query_model_new_empty (view->priv->db);
1429 rb_entry_view_set_model (view, RHYTHMDB_QUERY_MODEL (query_model));
1430 g_object_unref (query_model);
1433 return G_OBJECT (view);
1436 static void
1437 rb_entry_view_rated_cb (RBCellRendererRating *cellrating,
1438 const char *path_string,
1439 double rating,
1440 RBEntryView *view)
1442 GtkTreePath *path;
1443 RhythmDBEntry *entry;
1444 GValue value = { 0, };
1446 g_return_if_fail (rating >= 0 && rating <= 5 );
1447 g_return_if_fail (path_string != NULL);
1449 path = gtk_tree_path_new_from_string (path_string);
1450 entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1451 gtk_tree_path_free (path);
1453 g_value_init (&value, G_TYPE_DOUBLE);
1454 g_value_set_double (&value, rating);
1455 rhythmdb_entry_set (view->priv->db, entry, RHYTHMDB_PROP_RATING, &value);
1456 g_value_unset (&value);
1458 rhythmdb_commit (view->priv->db);
1460 rhythmdb_entry_unref (entry);
1463 static void
1464 rb_entry_view_pixbuf_clicked_cb (RBEntryView *view,
1465 const char *path_string,
1466 RBCellRendererPixbuf *cellpixbuf)
1468 GtkTreePath *path;
1469 RhythmDBEntry *entry;
1470 const gchar *error;
1472 g_return_if_fail (path_string != NULL);
1474 path = gtk_tree_path_new_from_string (path_string);
1475 entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1477 gtk_tree_path_free (path);
1479 error = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_PLAYBACK_ERROR);
1480 if (error) {
1481 rb_error_dialog (NULL, _("Playback Error"), "%s", error);
1484 rhythmdb_entry_unref (entry);
1487 static void
1488 rb_entry_view_playing_song_changed (RBShellPlayer *player,
1489 RhythmDBEntry *entry,
1490 RBEntryView *view)
1492 gboolean realized, visible;
1493 GtkTreeIter iter;
1495 g_return_if_fail (RB_IS_ENTRY_VIEW (view));
1497 if (view->priv->playing_entry != NULL) {
1498 if (view->priv->playing_state != RB_ENTRY_VIEW_NOT_PLAYING)
1499 rb_entry_view_emit_row_changed (view, view->priv->playing_entry);
1500 g_object_unref (view->priv->playing_model);
1503 view->priv->playing_entry = entry;
1504 view->priv->playing_model = view->priv->model;
1505 g_object_ref (view->priv->playing_model);
1507 if (view->priv->playing_state != RB_ENTRY_VIEW_NOT_PLAYING) {
1508 if (view->priv->playing_entry != NULL) {
1509 view->priv->playing_entry_in_view =
1510 rb_entry_view_emit_row_changed (view, view->priv->playing_entry);
1513 if (view->priv->playing_entry
1514 && view->priv->playing_entry_in_view) {
1515 rb_entry_view_entry_is_visible (view, view->priv->playing_entry,
1516 &realized, &visible, &iter);
1517 if (realized && !visible)
1518 rb_entry_view_scroll_to_iter (view, &iter);
1523 static gboolean
1524 harvest_entries (GtkTreeModel *model,
1525 GtkTreePath *path,
1526 GtkTreeIter *iter,
1527 GList **list)
1529 RhythmDBEntry *entry;
1531 gtk_tree_model_get (model, iter, 0, &entry, -1);
1533 *list = g_list_prepend (*list, entry);
1535 return FALSE;
1538 GList *
1539 rb_entry_view_get_selected_entries (RBEntryView *view)
1541 GList *list = NULL;
1543 gtk_tree_selection_selected_foreach (view->priv->selection,
1544 (GtkTreeSelectionForeachFunc) harvest_entries,
1545 (gpointer) &list);
1547 list = g_list_reverse (list);
1548 return list;
1551 static gboolean
1552 rb_entry_view_button_press_cb (GtkTreeView *treeview,
1553 GdkEventButton *event,
1554 RBEntryView *view)
1556 if (event->button == 3) {
1557 GtkTreePath *path;
1558 RhythmDBEntry *entry;
1560 gtk_tree_view_get_path_at_pos (treeview, event->x, event->y, &path, NULL, NULL, NULL);
1561 if (path != NULL) {
1562 GList *selected;
1563 entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1565 selected = rb_entry_view_get_selected_entries (view);
1567 if (!g_list_find (selected, entry))
1568 rb_entry_view_select_entry (view, entry);
1570 g_list_free (selected);
1572 rhythmdb_entry_unref (entry);
1574 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SHOW_POPUP], 0, (path != NULL));
1575 return TRUE;
1578 return FALSE;
1581 static gboolean
1582 rb_entry_view_popup_menu_cb (GtkTreeView *treeview,
1583 RBEntryView *view)
1585 if (gtk_tree_selection_count_selected_rows (gtk_tree_view_get_selection (treeview)) == 0)
1586 return FALSE;
1588 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SHOW_POPUP], 0);
1589 return TRUE;
1592 static gboolean
1593 rb_entry_view_emit_selection_changed (RBEntryView *view)
1595 gboolean available;
1596 gint sel_count;
1598 GDK_THREADS_ENTER ();
1599 sel_count = gtk_tree_selection_count_selected_rows (view->priv->selection);
1600 available = (sel_count > 0);
1602 if (available != view->priv->have_selection) {
1603 gint entry_count;
1605 entry_count = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (view->priv->model), NULL);
1606 view->priv->have_complete_selection = (sel_count == entry_count);
1608 view->priv->have_selection = available;
1610 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[HAVE_SEL_CHANGED], 0, available);
1613 view->priv->selection_changed_id = 0;
1614 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[SELECTION_CHANGED], 0);
1616 GDK_THREADS_LEAVE ();
1617 return FALSE;
1620 static void
1621 rb_entry_view_selection_changed_cb (GtkTreeSelection *selection,
1622 RBEntryView *view)
1624 if (view->priv->selection_changed_id == 0)
1625 view->priv->selection_changed_id = g_idle_add ((GSourceFunc)rb_entry_view_emit_selection_changed, view);
1628 gboolean
1629 rb_entry_view_have_selection (RBEntryView *view)
1631 return view->priv->have_selection;
1634 gboolean
1635 rb_entry_view_have_complete_selection (RBEntryView *view)
1637 return view->priv->have_complete_selection;
1640 static void
1641 rb_entry_view_row_activated_cb (GtkTreeView *treeview,
1642 GtkTreePath *path,
1643 GtkTreeViewColumn *column,
1644 RBEntryView *view)
1646 RhythmDBEntry *entry;
1648 rb_debug ("row activated");
1650 entry = rhythmdb_query_model_tree_path_to_entry (view->priv->model, path);
1652 rb_debug ("emitting entry activated");
1653 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRY_ACTIVATED], 0, entry);
1655 rhythmdb_entry_unref (entry);
1658 static void
1659 rb_entry_view_row_inserted_cb (GtkTreeModel *model,
1660 GtkTreePath *path,
1661 GtkTreeIter *iter,
1662 RBEntryView *view)
1664 RhythmDBEntry *entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model), path);
1666 rb_debug ("row added");
1667 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRY_ADDED], 0, entry);
1668 rhythmdb_entry_unref (entry);
1671 static void
1672 rb_entry_view_row_deleted_cb (GtkTreeModel *model,
1673 GtkTreePath *path,
1674 RBEntryView *view)
1676 RhythmDBEntry *entry = rhythmdb_query_model_tree_path_to_entry (RHYTHMDB_QUERY_MODEL (model), path);
1678 rb_debug ("row deleted");
1679 g_signal_emit (G_OBJECT (view), rb_entry_view_signals[ENTRY_DELETED], 0, entry);
1680 rhythmdb_entry_unref (entry);
1683 static void
1684 rb_entry_view_rows_reordered_cb (GtkTreeModel *model,
1685 GtkTreePath *path,
1686 GtkTreeIter *iter,
1687 gint *order,
1688 RBEntryView *view)
1690 GList *selected_rows;
1691 GList *i;
1692 gint model_size;
1693 gboolean scrolled = FALSE;
1695 rb_debug ("rows reordered");
1697 model_size = gtk_tree_model_iter_n_children (model, NULL);
1699 /* check if a selected row was moved; if so, we'll
1700 * need to move the selection too.
1702 selected_rows = gtk_tree_selection_get_selected_rows (view->priv->selection,
1703 NULL);
1704 for (i = selected_rows; i != NULL; i = i->next) {
1705 GtkTreePath *path = (GtkTreePath *)i->data;
1706 gint index = gtk_tree_path_get_indices (path)[0];
1707 gint newindex;
1708 if (order[index] != index) {
1709 GtkTreePath *newpath;
1710 gtk_tree_selection_unselect_path (view->priv->selection, path);
1712 for (newindex = 0; newindex < model_size; newindex++) {
1713 if (order[newindex] == index) {
1714 newpath = gtk_tree_path_new_from_indices (newindex, -1);
1715 gtk_tree_selection_select_path (view->priv->selection, newpath);
1716 if (!scrolled) {
1717 GtkTreeViewColumn *col;
1718 GtkTreeView *treeview = GTK_TREE_VIEW (view->priv->treeview);
1720 col = gtk_tree_view_get_column (treeview, 0);
1721 gtk_tree_view_scroll_to_cell (treeview, newpath, col, TRUE, 0.5, 0.0);
1722 scrolled = TRUE;
1724 gtk_tree_path_free (newpath);
1725 break;
1732 g_list_foreach (selected_rows, (GFunc) gtk_tree_path_free, NULL);
1733 g_list_free (selected_rows);
1735 gtk_widget_queue_draw (GTK_WIDGET (view));
1738 void
1739 rb_entry_view_select_all (RBEntryView *view)
1741 gtk_tree_selection_select_all (view->priv->selection);
1744 void
1745 rb_entry_view_select_none (RBEntryView *view)
1747 gtk_tree_selection_unselect_all (view->priv->selection);
1750 void
1751 rb_entry_view_select_entry (RBEntryView *view,
1752 RhythmDBEntry *entry)
1754 GtkTreeIter iter;
1756 if (entry == NULL)
1757 return;
1759 rb_entry_view_select_none (view);
1761 if (rhythmdb_query_model_entry_to_iter (view->priv->model,
1762 entry, &iter)) {
1763 gtk_tree_selection_select_iter (view->priv->selection, &iter);
1767 void
1768 rb_entry_view_scroll_to_entry (RBEntryView *view,
1769 RhythmDBEntry *entry)
1771 GtkTreeIter iter;
1773 if (rhythmdb_query_model_entry_to_iter (view->priv->model,
1774 entry, &iter)) {
1775 rb_entry_view_scroll_to_iter (view, &iter);
1779 static void
1780 rb_entry_view_scroll_to_iter (RBEntryView *view,
1781 GtkTreeIter *iter)
1783 GtkTreePath *path;
1785 /* It's possible to we can be asked to scroll the play queue's entry
1786 * view to the playing entry before the view has ever been displayed.
1787 * This will result in gtk+ warnings, so we avoid it in this case.
1789 if (!GTK_WIDGET_REALIZED (view))
1790 return;
1792 path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->model), iter);
1793 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (view->priv->treeview), path,
1794 gtk_tree_view_get_column (GTK_TREE_VIEW (view->priv->treeview), 0),
1795 TRUE, 0.5, 0.0);
1796 gtk_tree_view_set_cursor (GTK_TREE_VIEW (view->priv->treeview), path,
1797 gtk_tree_view_get_column (GTK_TREE_VIEW (view->priv->treeview), 0), FALSE);
1799 gtk_tree_path_free (path);
1802 gboolean
1803 rb_entry_view_get_entry_visible (RBEntryView *view,
1804 RhythmDBEntry *entry)
1806 GtkTreeIter unused;
1807 gboolean realized, visible;
1809 if (view->priv->playing_model != view->priv->model)
1810 return FALSE;
1812 rb_entry_view_entry_is_visible (view, entry, &realized, &visible,
1813 &unused);
1814 return realized && visible;
1817 gboolean
1818 rb_entry_view_get_entry_contained (RBEntryView *view,
1819 RhythmDBEntry *entry)
1821 GtkTreeIter unused;
1823 return rhythmdb_query_model_entry_to_iter (view->priv->model,
1824 entry, &unused);
1827 static void
1828 rb_entry_view_entry_is_visible (RBEntryView *view,
1829 RhythmDBEntry *entry,
1830 gboolean *realized,
1831 gboolean *visible,
1832 GtkTreeIter *iter)
1834 GtkTreePath *path;
1835 GdkRectangle rect;
1837 *realized = FALSE;
1838 *visible = FALSE;
1840 g_return_if_fail (entry != NULL);
1842 if (!GTK_WIDGET_REALIZED (view))
1843 return;
1845 *realized = TRUE;
1847 if (!rhythmdb_query_model_entry_to_iter (view->priv->model,
1848 entry, iter))
1849 return;
1851 path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->model), iter);
1852 gtk_tree_view_get_cell_area (GTK_TREE_VIEW (view->priv->treeview),
1853 path,
1854 gtk_tree_view_get_column (GTK_TREE_VIEW (view->priv->treeview), 0),
1855 &rect);
1857 gtk_tree_path_free (path);
1859 *visible = (rect.y != 0 && rect.height != 0);
1862 void
1863 rb_entry_view_enable_drag_source (RBEntryView *view,
1864 const GtkTargetEntry *targets,
1865 int n_targets)
1867 g_return_if_fail (view != NULL);
1869 rb_tree_dnd_add_drag_source_support (GTK_TREE_VIEW (view->priv->treeview),
1870 GDK_BUTTON1_MASK | GDK_BUTTON3_MASK,
1871 targets, n_targets, GDK_ACTION_COPY);
1874 static void
1875 rb_entry_view_sort_key_changed_cb (GConfClient* client,
1876 guint cnxn_id,
1877 GConfEntry *entry,
1878 gpointer user_data)
1880 RBEntryView *view = user_data;
1882 g_return_if_fail (RB_IS_ENTRY_VIEW (view));
1884 rb_entry_view_set_sorting_type (view, eel_gconf_get_string (view->priv->sorting_key));
1887 static void
1888 rb_entry_view_columns_config_changed_cb (GConfClient* client,
1889 guint cnxn_id,
1890 GConfEntry *entry,
1891 gpointer user_data)
1893 RBEntryView *view = user_data;
1895 g_return_if_fail (RB_IS_ENTRY_VIEW (view));
1897 rb_entry_view_sync_columns_visible (view);
1900 static gint
1901 propid_from_name (const char *name)
1903 GEnumClass *prop_class = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
1904 GEnumValue *ev;
1905 int ret;
1907 ev = g_enum_get_value_by_name (prop_class, name);
1908 if (ev)
1909 ret = ev->value;
1910 else
1911 ret = -1;
1912 return ret;
1915 static void
1916 set_column_visibility (guint propid,
1917 GtkTreeViewColumn *column,
1918 GList *visible_props)
1920 gboolean visible;
1922 if (g_object_get_qdata (G_OBJECT (column),
1923 rb_entry_view_column_always_visible) == GINT_TO_POINTER (1))
1924 visible = TRUE;
1925 else
1926 visible = (g_list_find (visible_props, GINT_TO_POINTER (propid)) != NULL);
1928 gtk_tree_view_column_set_visible (column, visible);
1931 static void
1932 rb_entry_view_sync_columns_visible (RBEntryView *view)
1934 char **items;
1935 GList *visible_properties = NULL;
1936 char *config = eel_gconf_get_string (CONF_UI_COLUMNS_SETUP);
1938 g_return_if_fail (view != NULL);
1939 g_return_if_fail (config != NULL);
1941 items = g_strsplit (config, ",", 0);
1942 if (items != NULL) {
1943 int i;
1944 for (i = 0; items[i] != NULL && *(items[i]); i++) {
1945 int value = propid_from_name (items[i]);
1947 if ((value >= 0) && (value < RHYTHMDB_NUM_PROPERTIES))
1948 visible_properties = g_list_prepend (visible_properties, GINT_TO_POINTER (value));
1950 g_strfreev (items);
1953 g_hash_table_foreach (view->priv->propid_column_map, (GHFunc) set_column_visibility, visible_properties);
1955 g_list_free (visible_properties);
1956 g_free (config);
1959 void
1960 rb_entry_view_set_state (RBEntryView *view,
1961 RBEntryViewState state)
1963 g_return_if_fail (RB_IS_ENTRY_VIEW (view));
1964 g_object_set (view, "playing-state", state, NULL);
1967 static void
1968 rb_entry_view_grab_focus (GtkWidget *widget)
1970 RBEntryView *view = RB_ENTRY_VIEW (widget);
1972 gtk_widget_grab_focus (GTK_WIDGET (view->priv->treeview));
1975 static gboolean
1976 rb_entry_view_emit_row_changed (RBEntryView *view,
1977 RhythmDBEntry *entry)
1979 GtkTreeIter iter;
1980 GtkTreePath *path;
1982 if (!rhythmdb_query_model_entry_to_iter (view->priv->model, entry, &iter))
1983 return FALSE;
1985 path = gtk_tree_model_get_path (GTK_TREE_MODEL (view->priv->model),
1986 &iter);
1987 gtk_tree_model_row_changed (GTK_TREE_MODEL (view->priv->model),
1988 path, &iter);
1989 gtk_tree_path_free (path);
1990 return TRUE;
1993 void
1994 rb_entry_view_set_fixed_column_width (RBEntryView *view,
1995 GtkTreeViewColumn *column,
1996 GtkCellRenderer *renderer,
1997 const gchar **strings)
1999 gint max_width = 0;
2000 int i = 0;
2002 while (strings[i] != NULL) {
2003 gint width;
2004 g_object_set (renderer, "text", strings[i], NULL);
2005 gtk_cell_renderer_get_size (renderer,
2006 view->priv->treeview,
2007 NULL,
2008 NULL, NULL,
2009 &width, NULL);
2011 if (width > max_width)
2012 max_width = width;
2014 i++;
2017 /* include some arbitrary amount of padding, just to be safeish */
2018 gtk_tree_view_column_set_fixed_width (column, max_width + 5);
2021 const char *
2022 rb_entry_view_get_time_date_column_sample ()
2024 static const char *sample = NULL;
2026 * Currently, Japanese is the only translation that uses
2027 * anything other than %Y, %m ,%d, %H, and %M to format dates.
2028 * It uses %B (month name) and %e (days), and the values for
2029 * the month name appear to all consist of the month number
2030 * followed by a single character, so they're of consistent
2031 * width. So, this approach should work for every locale.
2033 * Midnight on September 30th, 2000 is the widest date/time I
2034 * can think of. 2000-09-30 00:00.
2037 if (sample == NULL) {
2038 /* s m h d M Y dw dY x */
2039 struct tm someday = { 0, 0, 0, 30, 9, 100, 6, 274, 0};
2041 /* Translators: Please keep the translated date format
2042 * compact, and avoid variable-width items such as month and
2043 * day names wherever possible. This allows us to disable
2044 * column autosizing, which makes the Rhythmbox UI much faster.
2046 sample = eel_strdup_strftime (_("%Y-%m-%d %H:%M"), &someday);
2048 return sample;
2051 void
2052 rb_entry_view_resort_model (RBEntryView *view)
2054 struct RBEntryViewColumnSortData *sort_data;
2056 g_assert (view->priv->sorting_column);
2057 sort_data = g_hash_table_lookup (view->priv->column_sort_data_map,
2058 view->priv->sorting_column);
2059 g_assert (sort_data);
2061 rhythmdb_query_model_set_sort_order (view->priv->model,
2062 sort_data->func,
2063 sort_data->data,
2064 NULL,
2065 (view->priv->sorting_order == GTK_SORT_DESCENDING));
2068 /* This should really be standard. */
2069 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
2071 GType
2072 rb_entry_view_column_get_type (void)
2074 static GType etype = 0;
2076 if (etype == 0) {
2077 static const GEnumValue values[] = {
2078 ENUM_ENTRY (RB_ENTRY_VIEW_COL_TRACK_NUMBER, "Track Number"),
2079 ENUM_ENTRY (RB_ENTRY_VIEW_COL_TITLE, "Title"),
2080 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ARTIST, "Artist"),
2081 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ALBUM, "Album"),
2082 ENUM_ENTRY (RB_ENTRY_VIEW_COL_GENRE, "Genre"),
2083 ENUM_ENTRY (RB_ENTRY_VIEW_COL_DURATION, "Duration"),
2084 ENUM_ENTRY (RB_ENTRY_VIEW_COL_QUALITY, "Quality"),
2085 ENUM_ENTRY (RB_ENTRY_VIEW_COL_RATING, "Rating"),
2086 ENUM_ENTRY (RB_ENTRY_VIEW_COL_PLAY_COUNT, "Play Count"),
2087 ENUM_ENTRY (RB_ENTRY_VIEW_COL_YEAR, "Year"),
2088 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_PLAYED, "Last Played"),
2089 ENUM_ENTRY (RB_ENTRY_VIEW_COL_FIRST_SEEN, "First Seen"),
2090 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LAST_SEEN, "Last Seen"),
2091 ENUM_ENTRY (RB_ENTRY_VIEW_COL_LOCATION, "Location"),
2092 ENUM_ENTRY (RB_ENTRY_VIEW_COL_ERROR, "Error"),
2093 { 0, 0, 0 }
2096 etype = g_enum_register_static ("RBEntryViewColumn", values);
2099 return etype;
2102 GType
2103 rb_entry_view_state_get_type (void)
2105 static GType etype = 0;
2107 if (etype == 0) {
2108 static const GEnumValue values[] = {
2109 ENUM_ENTRY (RB_ENTRY_VIEW_NOT_PLAYING, "Not Playing"),
2110 ENUM_ENTRY (RB_ENTRY_VIEW_PLAYING, "Playing"),
2111 ENUM_ENTRY (RB_ENTRY_VIEW_PAUSED, "Paused"),
2112 { 0, 0, 0 }
2115 etype = g_enum_register_static ("RBEntryViewState", values);
2118 return etype;