1 /*
2     DeaDBeeF -- the music player
3     Copyright (C) 2009-2015 Alexey Yakovenko and other contributors
4 
5     This software is provided 'as-is', without any express or implied
6     warranty.  In no event will the authors be held liable for any damages
7     arising from the use of this software.
8 
9     Permission is granted to anyone to use this software for any purpose,
10     including commercial applications, and to alter it and redistribute it
11     freely, subject to the following restrictions:
12 
13     1. The origin of this software must not be misrepresented; you must not
14      claim that you wrote the original software. If you use this software
15      in a product, an acknowledgment in the product documentation would be
16      appreciated but is not required.
17 
18     2. Altered source versions must be plainly marked as such, and must not be
19      misrepresented as being the original software.
20 
21     3. This notice may not be removed or altered from any source distribution.
22 */
23 
24 #ifdef HAVE_CONFIG_H
25 #  include "../../config.h"
26 #endif
27 
28 #include <gtk/gtk.h>
29 #include <math.h>
30 #include <stdlib.h>
31 #include <time.h>
32 #include <string.h>
33 #include <unistd.h>
34 #include <ctype.h>
35 #include <assert.h>
36 #include <sys/time.h>
37 #include "ddblistview.h"
38 #include "drawing.h"
39 #include "gtkui.h"
40 #include "support.h"
41 #include "callbacks.h"
42 #include "actionhandlers.h"
43 
44 #define min(x,y) ((x)<(y)?(x):(y))
45 #define max(x,y) ((x)>(y)?(x):(y))
46 
47 #define DEFAULT_GROUP_TITLE_HEIGHT 30
48 #define SCROLL_STEP 20
49 #define AUTOSCROLL_UPDATE_FREQ 0.01f
50 #define NUM_CHANGED_ROWS_BEFORE_FULL_REDRAW 10
51 
52 //#define trace(...) { fprintf(stderr, __VA_ARGS__); }
53 #define trace(fmt,...)
54 
55 #define PL_NEXT(it) (ps->binding->next (it))
56 #define PL_PREV(it) (ps->binding->prev (it))
57 //#define REF(it) {if (it) ps->binding->ref (it);}
58 #define UNREF(it) {if (it) ps->binding->unref(it);}
59 
60 // HACK!!
61 extern GtkWidget *theme_treeview;
62 extern GtkWidget *theme_button;
63 
64 G_DEFINE_TYPE (DdbListview, ddb_listview, GTK_TYPE_TABLE);
65 
66 struct _DdbListviewColumn {
67     char *title;
68     int width;
69     float fwidth; // only in autoresize mode
70     int minheight;
71     struct _DdbListviewColumn *next;
72     int color_override;
73     GdkColor color;
74     void *user_data;
75     unsigned align_right : 2; // 0=left, 1=right, 2=center
76     unsigned sort_order : 2; // 0=none, 1=asc, 2=desc
77 };
78 typedef struct _DdbListviewColumn DdbListviewColumn;
79 
80 static void ddb_listview_class_init(DdbListviewClass *klass);
81 static void ddb_listview_init(DdbListview *listview);
82 //static void ddb_listview_size_request(GtkWidget *widget, GtkRequisition *requisition);
83 //static void ddb_listview_size_allocate(GtkWidget *widget, GtkAllocation *allocation);
84 //static void ddb_listview_realize(GtkWidget *widget);
85 //static void ddb_listview_paint(GtkWidget *widget);
86 static void ddb_listview_destroy(GObject *object);
87 
88 void
89 ddb_listview_build_groups (DdbListview *listview);
90 
91 static void
92 ddb_listview_resize_groups (DdbListview *listview);
93 // fwd decls
94 void
95 ddb_listview_free_groups (DdbListview *listview);
96 
97 //static inline void
98 //draw_drawable (GdkDrawable *window, GdkGC *gc, GdkDrawable *drawable, int x1, int y1, int x2, int y2, int w, int h);
99 
100 ////// list functions ////
101 void
102 ddb_listview_list_render (DdbListview *ps, cairo_t *cr, int x, int y, int w, int h);
103 void
104 ddb_listview_list_render_row_background (DdbListview *ps, cairo_t *cr, DdbListviewIter it, int even, int cursor, int x, int y, int w, int h);
105 void
106 ddb_listview_list_render_row_foreground (DdbListview *ps, cairo_t *cr, DdbListviewIter it, int idx, int even, int cursor, int x, int y, int w, int h);
107 void
108 ddb_listview_list_render_album_art (DdbListview *ps, cairo_t *cr, DdbListviewIter group_it, int group_pinned, int grp_next_y, int x, int y, int w, int h);
109 void
110 ddb_listview_list_track_dragdrop (DdbListview *ps, int y);
111 int
112 ddb_listview_dragdrop_get_row_from_coord (DdbListview *listview, int y);
113 void
114 ddb_listview_list_mousemove (DdbListview *ps, GdkEventMotion *event, int x, int y);
115 void
116 ddb_listview_list_setup_vscroll (DdbListview *ps);
117 void
118 ddb_listview_list_setup_hscroll (DdbListview *ps);
119 void
120 ddb_listview_list_set_hscroll (DdbListview *ps, int newscroll);
121 void
122 ddb_listview_set_cursor (DdbListview *pl, int cursor);
123 int
124 ddb_listview_get_row_pos (DdbListview *listview, int pos);
125 
126 ////// header functions ////
127 void
128 ddb_listview_header_render (DdbListview *ps, cairo_t *cr);
129 
130 ////// column management functions ////
131 void
132 ddb_listview_column_move (DdbListview *listview, DdbListviewColumn *which, int inspos);
133 void
134 ddb_listview_column_free (DdbListview *listview, DdbListviewColumn *c);
135 
136 
137 // signal handlers
138 void
139 ddb_listview_vscroll_value_changed            (GtkRange        *widget,
140                                         gpointer         user_data);
141 void
142 ddb_listview_hscroll_value_changed           (GtkRange        *widget,
143                                         gpointer         user_data);
144 
145 void
146 ddb_listview_list_drag_data_received         (GtkWidget       *widget,
147                                         GdkDragContext  *drag_context,
148                                         gint             x,
149                                         gint             y,
150                                         GtkSelectionData *data,
151                                         guint            info,
152                                         guint            time,
153                                         gpointer         user_data);
154 
155 gboolean
156 ddb_listview_header_draw                 (GtkWidget       *widget,
157                                         cairo_t *cr,
158                                         gpointer         user_data);
159 #if !GTK_CHECK_VERSION(3,0,0)
160 
161 gboolean
162 ddb_listview_header_expose_event                 (GtkWidget       *widget,
163                                         GdkEventExpose  *event,
164                                         gpointer         user_data);
165 #endif
166 
167 gboolean
168 ddb_listview_header_configure_event              (GtkWidget       *widget,
169                                         GdkEventConfigure *event,
170                                         gpointer         user_data);
171 
172 void
173 ddb_listview_header_realize                      (GtkWidget       *widget,
174                                         gpointer         user_data);
175 
176 gboolean
177 ddb_listview_header_motion_notify_event          (GtkWidget       *widget,
178                                         GdkEventMotion  *event,
179                                         gpointer         user_data);
180 
181 gboolean
182 ddb_listview_header_button_press_event           (GtkWidget       *widget,
183                                         GdkEventButton  *event,
184                                         gpointer         user_data);
185 
186 gboolean
187 ddb_listview_header_button_release_event         (GtkWidget       *widget,
188                                         GdkEventButton  *event,
189                                         gpointer         user_data);
190 
191 gboolean
192 ddb_listview_list_configure_event            (GtkWidget       *widget,
193                                         GdkEventConfigure *event,
194                                         gpointer         user_data);
195 
196 gboolean
197 ddb_listview_list_expose_event               (GtkWidget       *widget,
198                                         GdkEventExpose  *event,
199                                         gpointer         user_data);
200 
201 gboolean
202 ddb_listview_list_draw               (GtkWidget       *widget,
203         cairo_t *cr,
204         gpointer         user_data);
205 
206 void
207 ddb_listview_list_realize                    (GtkWidget       *widget,
208                                         gpointer         user_data);
209 
210 gboolean
211 ddb_listview_list_button_press_event         (GtkWidget       *widget,
212                                         GdkEventButton  *event,
213                                         gpointer         user_data);
214 
215 gboolean
216 ddb_listview_list_popup_menu (GtkWidget *widget, gpointer user_data);
217 
218 gboolean
219 ddb_listview_list_drag_motion                (GtkWidget       *widget,
220                                         GdkDragContext  *drag_context,
221                                         gint             x,
222                                         gint             y,
223                                         guint            time,
224                                         gpointer         user_data);
225 
226 gboolean
227 ddb_listview_list_drag_drop                  (GtkWidget       *widget,
228                                         GdkDragContext  *drag_context,
229                                         gint             x,
230                                         gint             y,
231                                         guint            time,
232                                         gpointer         user_data);
233 
234 void
235 ddb_listview_list_drag_data_get              (GtkWidget       *widget,
236                                         GdkDragContext  *drag_context,
237                                         GtkSelectionData *data,
238                                         guint            info,
239                                         guint            time,
240                                         gpointer         user_data);
241 
242 void
243 ddb_listview_list_drag_end                   (GtkWidget       *widget,
244                                         GdkDragContext  *drag_context,
245                                         gpointer         user_data);
246 
247 gboolean
248 ddb_listview_list_drag_failed                (GtkWidget       *widget,
249                                         GdkDragContext  *arg1,
250                                         GtkDragResult    arg2,
251                                         gpointer         user_data);
252 
253 void
254 ddb_listview_list_drag_leave                 (GtkWidget       *widget,
255                                         GdkDragContext  *drag_context,
256                                         guint            time,
257                                         gpointer         user_data);
258 
259 gboolean
260 ddb_listview_list_button_release_event       (GtkWidget       *widget,
261                                         GdkEventButton  *event,
262                                         gpointer         user_data);
263 
264 gboolean
265 ddb_listview_motion_notify_event        (GtkWidget       *widget,
266                                         GdkEventMotion  *event,
267                                         gpointer         user_data);
268 gboolean
269 ddb_listview_list_button_press_event         (GtkWidget       *widget,
270                                         GdkEventButton  *event,
271                                         gpointer         user_data);
272 
273 gboolean
274 ddb_listview_vscroll_event               (GtkWidget       *widget,
275                                         GdkEvent        *event,
276                                         gpointer         user_data);
277 
278 gboolean
279 ddb_listview_list_button_release_event       (GtkWidget       *widget,
280                                         GdkEventButton  *event,
281                                         gpointer         user_data);
282 
283 gboolean
284 ddb_listview_motion_notify_event        (GtkWidget       *widget,
285                                         GdkEventMotion  *event,
286                                         gpointer         user_data);
287 
288 gboolean
289 ddb_listview_list_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data);
290 
291 static void
ddb_listview_class_init(DdbListviewClass * class)292 ddb_listview_class_init(DdbListviewClass *class)
293 {
294   GtkTableClass *widget_class = (GtkTableClass *) class;
295   GObjectClass *object_class = (GObjectClass *) class;
296   object_class->finalize = ddb_listview_destroy;
297 }
298 
299 static void
ddb_listview_init(DdbListview * listview)300 ddb_listview_init(DdbListview *listview)
301 {
302     // init instance - create all subwidgets, and insert into table
303     drawctx_init (&listview->listctx);
304     drawctx_init (&listview->grpctx);
305     drawctx_init (&listview->hdrctx);
306     listview->rowheight = -1;
307 
308     listview->col_movepos = -1;
309     listview->drag_motion_y = -1;
310 
311     listview->ref_point = -1;
312     listview->ref_point_offset = -1;
313 
314     listview->scroll_mode = 0;
315     listview->scroll_pointer_y = -1;
316     listview->scroll_direction = 0;
317     listview->scroll_active = 0;
318     memset (&listview->tm_prevscroll, 0, sizeof (listview->tm_prevscroll));
319     listview->scroll_sleep_time = 0;
320 
321     listview->areaselect = 0;
322 //    listview->areaselect_x = -1;
323     listview->areaselect_y = -1;
324 //    listview->areaselect_dx = -1;
325 //    listview->areaselect_dy = -1;
326     listview->dragwait = 0;
327     listview->drag_source_playlist = -1;
328     listview->shift_sel_anchor = -1;
329 
330     listview->header_dragging = -1;
331     listview->header_sizing = -1;
332     listview->header_dragpt[0] = 0;
333     listview->header_dragpt[1] = 0;
334     listview->last_header_motion_ev = -1; //is it subject to remove?
335     listview->prev_header_x = -1;
336     listview->header_prepare = 0;
337     listview->header_width = -1;
338 
339     listview->columns = NULL;
340     listview->lock_columns = 1;
341     listview->groups = NULL;
342     listview->plt = NULL;
343 
344     listview->block_redraw_on_scroll = 0;
345     listview->calculated_grouptitle_height = DEFAULT_GROUP_TITLE_HEIGHT;
346 
347     listview->cursor_sz = NULL;
348     listview->cursor_drag = NULL;
349 
350     listview->area_selection_start = 0;
351     listview->area_selection_end = 0;
352 
353     listview->cover_size = -1;
354     listview->new_cover_size = -1;
355     listview->cover_refresh_timeout_id = 0;
356     listview->tf_redraw_timeout_id = 0;
357     listview->tf_redraw_track_idx = -1;
358 
359     GtkWidget *hbox;
360     GtkWidget *vbox;
361 
362     gtk_table_resize (GTK_TABLE (listview), 2, 2);
363     listview->scrollbar = gtk_vscrollbar_new (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 1, 1, 0, 0)));
364     gtk_widget_show (listview->scrollbar);
365     gtk_table_attach (GTK_TABLE (listview), listview->scrollbar, 1, 2, 0, 1,
366             (GtkAttachOptions) (GTK_FILL),
367             (GtkAttachOptions) (GTK_FILL), 0, 0);
368 
369     hbox = gtk_hbox_new (FALSE, 0);
370     gtk_widget_show (hbox);
371     gtk_table_attach (GTK_TABLE (listview), hbox, 0, 1, 0, 1,
372             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
373             (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0);
374 
375     vbox = gtk_vbox_new (FALSE, 0);
376     gtk_widget_show (vbox);
377     gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0);
378 
379     GtkWidget *sepbox = gtk_vbox_new (FALSE, 0);
380     gtk_widget_show (sepbox);
381     gtk_container_set_border_width (GTK_CONTAINER (sepbox), 1);
382     gtk_box_pack_start (GTK_BOX (vbox), sepbox, FALSE, TRUE, 0);
383 
384     GtkWidget *hsep  = gtk_hseparator_new ();
385     gtk_widget_show (hsep);
386     gtk_box_pack_start (GTK_BOX (sepbox), hsep, FALSE, TRUE, 0);
387 
388     listview->header = gtk_drawing_area_new ();
389     gtk_widget_show (listview->header);
390     gtk_box_pack_start (GTK_BOX (vbox), listview->header, FALSE, TRUE, 0);
391     gtk_widget_set_events (listview->header, GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
392 
393     listview->list = gtk_drawing_area_new ();
394     gtk_widget_show (listview->list);
395     gtk_box_pack_start (GTK_BOX (vbox), listview->list, TRUE, TRUE, 0);
396     gtk_widget_set_can_focus (listview->list, TRUE);
397     gtk_widget_set_can_default (listview->list, TRUE);
398     int events = GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_BUTTON_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK | GDK_FOCUS_CHANGE_MASK;
399 #if GTK_CHECK_VERSION(3,0,0)
400     events |= GDK_SCROLL_MASK;
401 #endif
402 #if GTK_CHECK_VERSION(3,4,0)
403     events |= GDK_SMOOTH_SCROLL_MASK;
404 #endif
405     gtk_widget_set_events (listview->list, events);
406 
407     listview->hscrollbar = gtk_hscrollbar_new (GTK_ADJUSTMENT (gtk_adjustment_new (0, 0, 0, 0, 0, 0)));
408     gtk_widget_show (listview->hscrollbar);
409     gtk_table_attach (GTK_TABLE (listview), listview->hscrollbar, 0, 1, 1, 2,
410             (GtkAttachOptions) (GTK_FILL),
411             (GtkAttachOptions) (GTK_FILL), 0, 0);
412 
413 
414     g_object_set_data (G_OBJECT (listview->list), "owner", listview);
415     g_object_set_data (G_OBJECT (listview->header), "owner", listview);
416     g_object_set_data (G_OBJECT (listview->scrollbar), "owner", listview);
417     g_object_set_data (G_OBJECT (listview->hscrollbar), "owner", listview);
418 
419     g_signal_connect ((gpointer) listview->list, "configure_event",
420             G_CALLBACK (ddb_listview_list_configure_event),
421             NULL);
422 
423     g_signal_connect ((gpointer) listview->scrollbar, "value_changed",
424             G_CALLBACK (ddb_listview_vscroll_value_changed),
425             NULL);
426 #if !GTK_CHECK_VERSION(3,0,0)
427     g_signal_connect ((gpointer) listview->header, "expose_event",
428             G_CALLBACK (ddb_listview_header_expose_event),
429             NULL);
430 #else
431     g_signal_connect ((gpointer) listview->header, "draw",
432             G_CALLBACK (ddb_listview_header_draw),
433             NULL);
434 #endif
435     g_signal_connect ((gpointer) listview->header, "configure_event",
436             G_CALLBACK (ddb_listview_header_configure_event),
437             NULL);
438     g_signal_connect ((gpointer) listview->header, "realize",
439             G_CALLBACK (ddb_listview_header_realize),
440             NULL);
441     g_signal_connect ((gpointer) listview->header, "motion_notify_event",
442             G_CALLBACK (ddb_listview_header_motion_notify_event),
443             NULL);
444     g_signal_connect_after ((gpointer) listview->header, "button_press_event",
445             G_CALLBACK (ddb_listview_header_button_press_event),
446             NULL);
447     g_signal_connect ((gpointer) listview->header, "button_release_event",
448             G_CALLBACK (ddb_listview_header_button_release_event),
449             NULL);
450 #if !GTK_CHECK_VERSION(3,0,0)
451     g_signal_connect ((gpointer) listview->list, "expose_event",
452             G_CALLBACK (ddb_listview_list_expose_event),
453             NULL);
454 #else
455     g_signal_connect ((gpointer) listview->list, "draw",
456             G_CALLBACK (ddb_listview_list_draw),
457             NULL);
458 #endif
459     g_signal_connect ((gpointer) listview->list, "realize",
460             G_CALLBACK (ddb_listview_list_realize),
461             NULL);
462     g_signal_connect_after ((gpointer) listview->list, "button_press_event",
463             G_CALLBACK (ddb_listview_list_button_press_event),
464             NULL);
465     g_signal_connect ((gpointer) listview->list, "popup_menu",
466             G_CALLBACK (ddb_listview_list_popup_menu),
467             NULL);
468     g_signal_connect ((gpointer) listview->list, "scroll_event",
469             G_CALLBACK (ddb_listview_vscroll_event),
470             NULL);
471 //    g_signal_connect ((gpointer) listview->list, "drag_begin",
472 //            G_CALLBACK (on_list_drag_begin),
473 //            NULL);
474     g_signal_connect ((gpointer) listview->list, "drag_motion",
475             G_CALLBACK (ddb_listview_list_drag_motion),
476             NULL);
477     g_signal_connect ((gpointer) listview->list, "drag_drop",
478             G_CALLBACK (ddb_listview_list_drag_drop),
479             NULL);
480     g_signal_connect ((gpointer) listview->list, "drag_data_get",
481             G_CALLBACK (ddb_listview_list_drag_data_get),
482             NULL);
483     g_signal_connect ((gpointer) listview->list, "drag_end",
484             G_CALLBACK (ddb_listview_list_drag_end),
485             NULL);
486     g_signal_connect ((gpointer) listview->list, "drag_failed",
487             G_CALLBACK (ddb_listview_list_drag_failed),
488             NULL);
489     g_signal_connect ((gpointer) listview->list, "drag_leave",
490             G_CALLBACK (ddb_listview_list_drag_leave),
491             NULL);
492     g_signal_connect ((gpointer) listview->list, "button_release_event",
493             G_CALLBACK (ddb_listview_list_button_release_event),
494             NULL);
495     g_signal_connect ((gpointer) listview->list, "motion_notify_event",
496             G_CALLBACK (ddb_listview_motion_notify_event),
497             NULL);
498     g_signal_connect ((gpointer) listview->list, "drag_data_received",
499             G_CALLBACK (ddb_listview_list_drag_data_received),
500             NULL);
501     g_signal_connect ((gpointer) listview->hscrollbar, "value_changed",
502             G_CALLBACK (ddb_listview_hscroll_value_changed),
503             NULL);
504 
505     g_signal_connect ((gpointer)listview->list, "key_press_event", G_CALLBACK (ddb_listview_list_key_press_event), NULL);
506 }
507 
ddb_listview_new()508 GtkWidget * ddb_listview_new()
509 {
510    return g_object_newv (ddb_listview_get_type(), 0, NULL);//GTK_WIDGET(gtk_type_new(ddb_listview_get_type()));
511 }
512 
513 static void
ddb_listview_destroy(GObject * object)514 ddb_listview_destroy(GObject *object)
515 {
516   DdbListview *listview;
517 
518   g_return_if_fail(object != NULL);
519   g_return_if_fail(DDB_IS_LISTVIEW(object));
520 
521   listview = DDB_LISTVIEW(object);
522 
523   ddb_listview_free_groups (listview);
524 
525   while (listview->columns) {
526       DdbListviewColumn *next = listview->columns->next;
527       ddb_listview_column_free (listview, listview->columns);
528       listview->columns = next;
529   }
530 
531   if (listview->cursor_sz) {
532       gdk_cursor_unref (listview->cursor_sz);
533       listview->cursor_sz = NULL;
534   }
535   if (listview->cursor_drag) {
536       gdk_cursor_unref (listview->cursor_drag);
537       listview->cursor_drag = NULL;
538   }
539   if (listview->group_format) {
540       free (listview->group_format);
541       listview->group_format = NULL;
542   }
543   if (listview->group_title_bytecode) {
544       free (listview->group_title_bytecode);
545       listview->group_title_bytecode = NULL;
546   }
547   ddb_listview_cancel_autoredraw (listview);
548 
549   draw_free (&listview->listctx);
550   draw_free (&listview->grpctx);
551   draw_free (&listview->hdrctx);
552 }
553 
554 void
ddb_listview_refresh(DdbListview * listview,uint32_t flags)555 ddb_listview_refresh (DdbListview *listview, uint32_t flags) {
556     if (flags & DDB_LIST_CHANGED) {
557         ddb_listview_build_groups (listview);
558     }
559     if (flags & DDB_REFRESH_LIST) {
560         gtk_widget_queue_draw (listview->list);
561     }
562     if (flags & DDB_REFRESH_VSCROLL) {
563         ddb_listview_list_setup_vscroll (listview);
564     }
565     if (flags & DDB_REFRESH_HSCROLL) {
566         ddb_listview_list_setup_hscroll (listview);
567     }
568     if (flags & DDB_REFRESH_COLUMNS) {
569         gtk_widget_queue_draw (listview->header);
570     }
571 }
572 
573 void
ddb_listview_list_realize(GtkWidget * widget,gpointer user_data)574 ddb_listview_list_realize                    (GtkWidget       *widget,
575         gpointer         user_data)
576 {
577     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
578     if (!ps->binding->drag_n_drop) {
579         // playlist doesn't support drag and drop (e.g. searchlist)
580         return;
581     }
582     GtkTargetEntry entry = {
583         .target = TARGET_PLAYITEMS,
584         .flags = GTK_TARGET_SAME_APP,
585         .info = TARGET_SAMEWIDGET
586     };
587     // setup drag-drop target
588     gtk_drag_dest_set (widget, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, &entry, 1, GDK_ACTION_COPY | GDK_ACTION_MOVE);
589     gtk_drag_dest_add_uri_targets (widget);
590 //    gtk_drag_dest_set_track_motion (widget, TRUE);
591 }
592 
593 static gboolean
ddb_listview_reconf_scrolling(void * ps)594 ddb_listview_reconf_scrolling (void *ps) {
595     ddb_listview_list_setup_vscroll (ps);
596     ddb_listview_list_setup_hscroll (ps);
597     return FALSE;
598 }
599 
600 static void
ddb_listview_list_update_total_width(DdbListview * lv,int size)601 ddb_listview_list_update_total_width (DdbListview *lv, int size) {
602     GtkAllocation a;
603     gtk_widget_get_allocation (GTK_WIDGET (lv->list), &a);
604     lv->totalwidth = size;
605     if (lv->totalwidth < a.width) {
606         lv->totalwidth = a.width;
607     }
608 }
609 
610 gboolean
ddb_listview_list_configure_event(GtkWidget * widget,GdkEventConfigure * event,gpointer user_data)611 ddb_listview_list_configure_event            (GtkWidget       *widget,
612         GdkEventConfigure *event,
613         gpointer         user_data)
614 {
615     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
616 
617     draw_init_font (&ps->listctx, DDB_LIST_FONT, 1);
618     draw_init_font (&ps->grpctx, DDB_GROUP_FONT, 1);
619     ddb_listview_update_fonts (ps);
620 
621     return FALSE;
622 }
623 
624 void
ddb_listview_groupcheck(DdbListview * listview)625 ddb_listview_groupcheck (DdbListview *listview) {
626     int idx = listview->binding->modification_idx ();
627     if (idx != listview->groups_build_idx) {
628         ddb_listview_build_groups (listview);
629     }
630 }
631 
632 // returns 1 if X coordinate in list belongs to album art column and 0 if not
633 int
ddb_listview_is_album_art_column(DdbListview * listview,int x)634 ddb_listview_is_album_art_column (DdbListview *listview, int x)
635 {
636     int album_art_column = 0;
637     int col_x = -listview->hscrollpos;
638     int cnt = ddb_listview_column_get_count (listview);
639     for (int i = 0; i < cnt, col_x <= x; i++) {
640         const char *title;
641         int width;
642         int align_right;
643         col_info_t *info;
644         int minheight;
645         int color_override;
646         GdkColor color;
647         int res = ddb_listview_column_get_info (listview, i, &title, &width, &align_right, &minheight, &color_override, &color, (void **)&info);
648         if (res != -1 && x <= col_x + width && info->id == DB_COLUMN_ALBUM_ART) {
649             return 1;
650         }
651         col_x += width;
652     }
653     return 0;
654 }
655 
656 // returns 1 if column is album art column
657 int
ddb_listview_is_album_art_column_idx(DdbListview * listview,int cidx)658 ddb_listview_is_album_art_column_idx (DdbListview *listview, int cidx)
659 {
660     const char *title;
661     int width;
662     int align_right;
663     col_info_t *info;
664     int minheight;
665     int color_override;
666     GdkColor color;
667     int res = ddb_listview_column_get_info (listview, cidx, &title, &width, &align_right, &minheight, &color_override, &color, (void **)&info);
668     if (res != -1 && info->id == DB_COLUMN_ALBUM_ART) {
669         return 1;
670     }
671     return 0;
672 }
673 
674 // returns Y coordinate of an item by its index
675 int
ddb_listview_get_row_pos(DdbListview * listview,int row_idx)676 ddb_listview_get_row_pos (DdbListview *listview, int row_idx) {
677     int y = 0;
678     int idx = 0;
679     deadbeef->pl_lock ();
680     ddb_listview_groupcheck (listview);
681     DdbListviewGroup *grp = listview->groups;
682     while (grp) {
683         if (idx + grp->num_items > row_idx) {
684             int i = y + listview->grouptitle_height + (row_idx - idx) * listview->rowheight;
685             deadbeef->pl_unlock ();
686             return i;
687         }
688         y += grp->height;
689         idx += grp->num_items;
690         grp = grp->next;
691     }
692     deadbeef->pl_unlock ();
693     return y;
694 }
695 
696 // input: absolute y coord in list (not in window)
697 // returns -1 if nothing was hit, otherwise returns pointer to a group, and item idx
698 // item idx may be set to -1 if group title was hit
699 static int
ddb_listview_list_pickpoint_y(DdbListview * listview,int y,DdbListviewGroup ** group,int * group_idx,int * global_idx)700 ddb_listview_list_pickpoint_y (DdbListview *listview, int y, DdbListviewGroup **group, int *group_idx, int *global_idx) {
701     int idx = 0;
702     int grp_y = 0;
703     int gidx = 0;
704     deadbeef->pl_lock ();
705     ddb_listview_groupcheck (listview);
706     DdbListviewGroup *grp = listview->groups;
707     while (grp) {
708         int h = grp->height;
709         if (y >= grp_y && y < grp_y + h) {
710             *group = grp;
711             y -= grp_y;
712             if (y < listview->grouptitle_height) {
713                 *group_idx = -1;
714                 *global_idx = idx;
715             }
716             else if (y >= listview->grouptitle_height + grp->num_items * listview->rowheight) {
717                 *group_idx = (y - listview->grouptitle_height) / listview->rowheight;
718                 *global_idx = -1;
719             }
720             else {
721                 *group_idx = (y - listview->grouptitle_height) / listview->rowheight;
722                 *global_idx = idx + *group_idx;
723             }
724             deadbeef->pl_unlock ();
725             return 0;
726         }
727         grp_y += grp->height;
728         idx += grp->num_items;
729         grp = grp->next;
730         gidx++;
731     }
732     deadbeef->pl_unlock ();
733     return -1;
734 }
735 
736 int render_idx = 0;
737 
738 void
ddb_listview_list_render(DdbListview * listview,cairo_t * cr,int x,int y,int w,int h)739 ddb_listview_list_render (DdbListview *listview, cairo_t *cr, int x, int y, int w, int h) {
740     render_idx = 0;
741     cairo_set_line_width (cr, 1);
742     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
743     GtkWidget *treeview = theme_treeview;
744 
745 #if !GTK_CHECK_VERSION(3,0,0)
746 // FIXME?
747     if (gtk_widget_get_style (treeview)->depth == -1) {
748         return; // drawing was called too early
749     }
750 #endif
751     int idx = 0;
752     int abs_idx = 0;
753     deadbeef->pl_lock ();
754     ddb_listview_groupcheck (listview);
755     // find 1st group
756     DdbListviewGroup *grp = listview->groups;
757     int grp_y = 0;
758     int grp_next_y = 0;
759     DdbListviewGroup *pinned_grp = NULL;
760 
761     while (grp && grp_y + grp->height < y + listview->scrollpos) {
762         if (grp_y < listview->scrollpos && grp_y + grp->height >= listview->scrollpos) {
763             pinned_grp = grp;
764             grp->pinned = 1;
765         }
766         grp_y += grp->height;
767         idx += grp->num_items + 1;
768         abs_idx += grp->num_items;
769         grp = grp->next;
770     }
771 
772     draw_begin (&listview->listctx, cr);
773     draw_begin (&listview->grpctx, cr);
774 
775     if (grp && !pinned_grp && grp_y < listview->scrollpos) {
776         grp->pinned = 1;
777         pinned_grp = grp;
778     }
779     else if (grp && pinned_grp && pinned_grp->next == grp) {
780         grp->pinned = 2;
781     }
782 
783     int ii = 0;
784     while (grp && grp_y < y + h + listview->scrollpos) {
785         DdbListviewIter it = grp->head;
786         int grp_height = listview->grouptitle_height + grp->num_items * listview->rowheight;
787         int grp_height_total = grp->height;
788         int group_idx = idx;
789 
790         if (grp_y >= y + h + listview->scrollpos) {
791             break;
792         }
793         listview->binding->ref (it);
794 
795         grp_next_y = grp_y + grp_height_total;
796         for (int i = 0; i < grp->num_items; i++) {
797             ii++;
798             int grp_row_y = grp_y + listview->grouptitle_height + i * listview->rowheight;
799             if (grp_row_y >= y + h + listview->scrollpos) {
800                 break;
801             }
802             if (grp_y + listview->grouptitle_height + (i+1) * listview->rowheight >= y + listview->scrollpos
803                     && grp_row_y < y + h + listview->scrollpos) {
804                 GtkStyle *st = gtk_widget_get_style (listview->list);
805                 gdk_cairo_set_source_color (cr, &st->bg[GTK_STATE_NORMAL]);
806                 cairo_rectangle (cr, -listview->hscrollpos, grp_row_y - listview->scrollpos, listview->totalwidth, listview->rowheight);
807                 cairo_fill (cr);
808                 ddb_listview_list_render_row_background (listview, cr, it, i & 1, (abs_idx+i) == listview->binding->cursor () ? 1 : 0, -listview->hscrollpos, grp_row_y - listview->scrollpos, listview->totalwidth, listview->rowheight);
809                 ddb_listview_list_render_row_foreground (listview, cr, it, abs_idx + i, (idx + 1 + i) & 1, (idx+i) == listview->binding->cursor () ? 1 : 0, -listview->hscrollpos, grp_row_y - listview->scrollpos, listview->totalwidth, listview->rowheight);
810             }
811             DdbListviewIter next = listview->binding->next (it);
812             listview->binding->unref (it);
813             it = next;
814             if (!it) {
815                 break; // sanity check, in case groups were not rebuilt yet
816             }
817         }
818         idx += grp->num_items + 1;
819         abs_idx += grp->num_items;
820 
821         int filler = grp_height_total - (grp_height);
822         if (filler > 0) {
823             int theming = !gtkui_override_listview_colors ();
824             if (theming) {
825 #if GTK_CHECK_VERSION(3,0,0)
826                 gtk_paint_flat_box (gtk_widget_get_style (treeview), cr, GTK_STATE_NORMAL, GTK_SHADOW_NONE, treeview, "cell_even_ruled", x, grp_y - listview->scrollpos + grp_height, w, filler);
827 #else
828                 gtk_paint_flat_box (gtk_widget_get_style (treeview), gtk_widget_get_window (listview->list), GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, treeview, "cell_even_ruled", x, grp_y - listview->scrollpos + grp_height, w, filler);
829 #endif
830             }
831             else {
832                 GdkColor clr;
833                 gtkui_get_listview_even_row_color (&clr);
834                 gdk_cairo_set_source_color (cr, &clr);
835                 cairo_rectangle (cr, x, grp_y - listview->scrollpos + grp_height, w, filler);
836                 cairo_fill (cr);
837             }
838         }
839 
840         // draw album art
841         ddb_listview_list_render_album_art (listview, cr, grp->head, grp->pinned, grp_next_y - listview->scrollpos, -listview->hscrollpos, grp_y + listview->grouptitle_height - listview->scrollpos, listview->totalwidth, grp_height_total);
842         if (grp->pinned == 1 && gtkui_groups_pinned && y <= 0) {
843             // draw pinned group title
844             int pushback = 0;
845             if (grp_next_y - listview->scrollpos <= listview->grouptitle_height) {
846                 pushback = listview->grouptitle_height - (grp_next_y - listview->scrollpos);
847             }
848             ddb_listview_list_render_row_background (listview, cr, NULL, 1, 0, -listview->hscrollpos, y - pushback, listview->totalwidth, listview->grouptitle_height);
849             if (listview->binding->draw_group_title && listview->grouptitle_height > 0) {
850                 listview->binding->draw_group_title (listview, cr, grp->head, PL_MAIN, -listview->hscrollpos, y - pushback, listview->totalwidth, listview->grouptitle_height);
851             }
852         }
853         else if (grp_y + listview->grouptitle_height >= y + listview->scrollpos && grp_y < y + h + listview->scrollpos) {
854             // draw normal group title
855             ddb_listview_list_render_row_background (listview, cr, NULL, 1, 0, -listview->hscrollpos, grp_y - listview->scrollpos, listview->totalwidth, listview->grouptitle_height);
856             if (listview->binding->draw_group_title && listview->grouptitle_height > 0) {
857                 listview->binding->draw_group_title (listview, cr, grp->head, PL_MAIN, -listview->hscrollpos, grp_y - listview->scrollpos, listview->totalwidth, listview->grouptitle_height);
858             }
859         }
860 
861         if (it) {
862             listview->binding->unref (it);
863         }
864         grp_y += grp_height_total;
865         if (grp->pinned == 1) {
866             grp = grp->next;
867             if (grp) {
868                 grp->pinned = 2;
869             }
870         }
871         else {
872             grp = grp->next;
873             if (grp) {
874                 grp->pinned = 0;
875             }
876         }
877     }
878     if (grp_y < y + h + listview->scrollpos) {
879         int hh = y + h - (grp_y - listview->scrollpos);
880 //        gdk_draw_rectangle (listview->list->window, listview->list->style->bg_gc[GTK_STATE_NORMAL], TRUE, x, grp_y - listview->scrollpos, w, hh);
881         int theming = !gtkui_override_listview_colors ();
882         if (theming) {
883 #if GTK_CHECK_VERSION(3,0,0)
884             gtk_paint_flat_box (gtk_widget_get_style (treeview), cr, GTK_STATE_NORMAL, GTK_SHADOW_NONE, treeview, "cell_even_ruled", x, grp_y - listview->scrollpos, w, hh);
885 #else
886             gtk_paint_flat_box (gtk_widget_get_style (treeview), listview->list->window, GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, treeview, "cell_even_ruled", x, grp_y - listview->scrollpos, w, hh);
887 #endif
888         }
889         else {
890             GdkColor clr;
891             gtkui_get_listview_even_row_color (&clr);
892             cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f);
893             cairo_rectangle (cr, x, grp_y - listview->scrollpos, w, hh);
894             cairo_fill (cr);
895         }
896     }
897     deadbeef->pl_unlock ();
898     draw_end (&listview->listctx);
899     draw_end (&listview->grpctx);
900 }
901 
902 static void
ddb_listview_draw_dnd_marker(DdbListview * ps,cairo_t * cr)903 ddb_listview_draw_dnd_marker (DdbListview *ps, cairo_t *cr) {
904     if (ps->drag_motion_y < 0) {
905         return;
906     }
907     int drag_motion_y = ps->drag_motion_y - ps->scrollpos;
908 
909     GtkWidget *widget = ps->list;
910     GtkAllocation a;
911     gtk_widget_get_allocation (widget, &a);
912     GdkColor clr;
913     gtkui_get_listview_cursor_color (&clr);
914     cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.0);
915     cairo_rectangle (cr, 0, drag_motion_y-1, a.width, 3);
916     cairo_fill (cr);
917     cairo_rectangle (cr, 0, drag_motion_y-3, 3, 7);
918     cairo_fill (cr);
919     cairo_rectangle (cr, a.width-3, drag_motion_y-3, 3, 7);
920     cairo_fill (cr);
921 
922 }
923 
924 void
ddb_listview_update_fonts(DdbListview * ps)925 ddb_listview_update_fonts (DdbListview *ps)
926 {
927     draw_init_font (&ps->listctx, DDB_LIST_FONT, 1);
928     draw_init_font (&ps->grpctx, DDB_GROUP_FONT, 1);
929     int row_height = draw_get_listview_rowheight (&ps->listctx);
930     int grptitle_height = draw_get_listview_rowheight (&ps->grpctx);
931     if (row_height != ps->rowheight || grptitle_height != ps->calculated_grouptitle_height) {
932         ps->rowheight = row_height;
933         ps->calculated_grouptitle_height = grptitle_height;
934         ddb_listview_build_groups (ps);
935     }
936 
937     GtkAllocation a;
938     gtk_widget_get_allocation (ps->list, &a);
939     int w = a.width;
940     int size = 0;
941     DdbListviewColumn *c;
942     for (c = ps->columns; c; c = c->next) {
943         size += c->width;
944     }
945     ddb_listview_list_update_total_width (ps, size);
946     g_idle_add (ddb_listview_reconf_scrolling, ps);
947 }
948 
949 #if GTK_CHECK_VERSION(3,0,0)
950 gboolean
ddb_listview_list_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)951 ddb_listview_list_draw               (GtkWidget       *widget,
952         cairo_t *cr,
953         gpointer         user_data)
954 {
955     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
956     widget = ps->list;
957 
958     // FIXME: clip region
959     ddb_listview_list_render (ps, cr, 0, 0, gtk_widget_get_allocated_width (widget), gtk_widget_get_allocated_height (widget));
960     if (ps->drag_motion_y >= 0/* && ps->drag_motion_y-ps->scrollpos-3 < event->area.y+event->area.height && ps->drag_motion_y-ps->scrollpos+3 >= event->area.y*/) {
961         ddb_listview_draw_dnd_marker (ps, cr);
962     }
963     return FALSE;
964 }
965 #else
966 gboolean
ddb_listview_list_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)967 ddb_listview_list_expose_event               (GtkWidget       *widget,
968         GdkEventExpose  *event,
969         gpointer         user_data)
970 {
971     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
972     widget = ps->list;
973 
974     cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
975     ddb_listview_list_render (ps, cr, event->area.x, event->area.y, event->area.width, event->area.height);
976     if (ps->drag_motion_y >= 0 && ps->drag_motion_y-ps->scrollpos-3 < event->area.y+event->area.height && ps->drag_motion_y-ps->scrollpos+3 >= event->area.y) {
977         ddb_listview_draw_dnd_marker (ps, cr);
978     }
979     cairo_destroy (cr);
980     return FALSE;
981 }
982 #endif
983 
984 gboolean
ddb_listview_vscroll_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)985 ddb_listview_vscroll_event               (GtkWidget       *widget,
986                                         GdkEvent        *event,
987                                         gpointer         user_data)
988 {
989     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
990 
991 	GdkEventScroll *ev = (GdkEventScroll*)event;
992 
993     GtkWidget *rangeh = ps->hscrollbar;
994     GtkWidget *rangev = ps->scrollbar;
995 
996     gdouble deltah = SCROLL_STEP * 2;
997     gdouble deltav = SCROLL_STEP * 2;
998     gdouble scrollh = gtk_range_get_value (GTK_RANGE (rangeh));
999     gdouble scrollv = gtk_range_get_value (GTK_RANGE (rangev));
1000     // pass event to scrollbar
1001     if (ev->direction == GDK_SCROLL_UP) {
1002         gtk_range_set_value (GTK_RANGE (rangev), scrollv - deltav);
1003     }
1004     else if (ev->direction == GDK_SCROLL_DOWN) {
1005         gtk_range_set_value (GTK_RANGE (rangev), scrollv + deltav);
1006     }
1007     else if (ev->direction == GDK_SCROLL_LEFT) {
1008         gtk_range_set_value (GTK_RANGE (rangeh), scrollh - deltah);
1009     }
1010     else if (ev->direction == GDK_SCROLL_RIGHT) {
1011         gtk_range_set_value (GTK_RANGE (rangeh), scrollh + deltah);
1012     }
1013 #if GTK_CHECK_VERSION(3,4,0)
1014     else if (ev->direction == GDK_SCROLL_SMOOTH) {
1015         gdouble x, y;
1016         if (gdk_event_get_scroll_deltas(event, &x, &y)) {
1017             gtk_range_set_value (GTK_RANGE (rangeh), scrollh + deltah * x);
1018             gtk_range_set_value (GTK_RANGE (rangev), scrollv + deltav * y);
1019         }
1020     }
1021 #endif
1022 
1023     return FALSE;
1024 }
1025 
1026 void
ddb_listview_vscroll_value_changed(GtkRange * widget,gpointer user_data)1027 ddb_listview_vscroll_value_changed            (GtkRange        *widget,
1028                                         gpointer         user_data)
1029 {
1030     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
1031     int newscroll = gtk_range_get_value (GTK_RANGE (widget));
1032     if (ps->binding->vscroll_changed) {
1033         ps->binding->vscroll_changed (newscroll);
1034     }
1035     if (ps->block_redraw_on_scroll) {
1036         ps->scrollpos = newscroll;
1037         return;
1038     }
1039     if (newscroll != ps->scrollpos) {
1040         ps->scrollpos = newscroll;
1041         gtk_widget_queue_draw (ps->list);
1042     }
1043 }
1044 
1045 void
ddb_listview_hscroll_value_changed(GtkRange * widget,gpointer user_data)1046 ddb_listview_hscroll_value_changed           (GtkRange        *widget,
1047                                         gpointer         user_data)
1048 {
1049     DdbListview *pl = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
1050     int newscroll = gtk_range_get_value (GTK_RANGE (widget));
1051     ddb_listview_list_set_hscroll (pl, newscroll);
1052 }
1053 
1054 gboolean
ddb_listview_list_drag_motion(GtkWidget * widget,GdkDragContext * drag_context,gint x,gint y,guint time,gpointer user_data)1055 ddb_listview_list_drag_motion                (GtkWidget       *widget,
1056                                         GdkDragContext  *drag_context,
1057                                         gint             x,
1058                                         gint             y,
1059                                         guint            time,
1060                                         gpointer         user_data)
1061 {
1062     DdbListview *pl = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
1063     ddb_listview_list_track_dragdrop (pl, y);
1064     GList *targets = gdk_drag_context_list_targets (drag_context);
1065     int cnt = g_list_length (targets);
1066     int i;
1067     for (i = 0; i < cnt; i++) {
1068         GdkAtom a = GDK_POINTER_TO_ATOM (g_list_nth_data (targets, i));
1069         gchar *nm = gdk_atom_name (a);
1070         if (!strcmp (nm, "text/uri-list")) {
1071             g_free (nm);
1072             break;
1073         }
1074         g_free (nm);
1075     }
1076     if (i != cnt) {
1077         gdk_drag_status (drag_context, GDK_ACTION_COPY, time);
1078     }
1079     else {
1080         GdkModifierType mask;
1081 
1082         gdk_window_get_pointer (gtk_widget_get_window (widget),
1083                 NULL, NULL, &mask);
1084         if (mask & GDK_CONTROL_MASK) {
1085             gdk_drag_status (drag_context, GDK_ACTION_COPY, time);
1086         }
1087         else {
1088             gdk_drag_status (drag_context, GDK_ACTION_MOVE, time);
1089         }
1090     }
1091     return FALSE;
1092 }
1093 
1094 
1095 gboolean
ddb_listview_list_drag_drop(GtkWidget * widget,GdkDragContext * drag_context,gint x,gint y,guint time,gpointer user_data)1096 ddb_listview_list_drag_drop                  (GtkWidget       *widget,
1097                                         GdkDragContext  *drag_context,
1098                                         gint             x,
1099                                         gint             y,
1100                                         guint            time,
1101                                         gpointer         user_data)
1102 {
1103     return TRUE;
1104 }
1105 
1106 
1107 void
ddb_listview_list_drag_data_get(GtkWidget * widget,GdkDragContext * drag_context,GtkSelectionData * selection_data,guint target_type,guint time,gpointer user_data)1108 ddb_listview_list_drag_data_get              (GtkWidget       *widget,
1109                                         GdkDragContext  *drag_context,
1110                                         GtkSelectionData *selection_data,
1111                                         guint            target_type,
1112                                         guint            time,
1113                                         gpointer         user_data)
1114 {
1115     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
1116     switch (target_type) {
1117     case TARGET_SAMEWIDGET:
1118         {
1119             // format as "STRING" consisting of array of pointers
1120             int nsel = deadbeef->plt_get_sel_count (ps->drag_source_playlist);
1121             if (!nsel) {
1122                 break; // something wrong happened
1123             }
1124             uint32_t *ptr = malloc ((nsel+1) * sizeof (uint32_t));
1125             *ptr = ps->drag_source_playlist;
1126             int idx = 0;
1127             int i = 1;
1128             DdbListviewIter it = deadbeef->plt_get_head (ps->drag_source_playlist);
1129             for (; it; idx++) {
1130                 if (ps->binding->is_selected (it)) {
1131                     ptr[i] = idx;
1132                     i++;
1133                 }
1134                 DdbListviewIter next = ps->binding->next (it);
1135                 ps->binding->unref (it);
1136                 it = next;
1137             }
1138             GdkAtom target = gtk_selection_data_get_target (selection_data);
1139             gtk_selection_data_set (selection_data, target, sizeof (uint32_t) * 8, (gchar *)ptr, (nsel+1) * sizeof (uint32_t));
1140             free (ptr);
1141         }
1142         break;
1143     default:
1144         g_assert_not_reached ();
1145     }
1146 }
1147 
1148 
1149 void
ddb_listview_list_drag_data_received(GtkWidget * widget,GdkDragContext * drag_context,gint x,gint y,GtkSelectionData * data,guint target_type,guint time,gpointer user_data)1150 ddb_listview_list_drag_data_received         (GtkWidget       *widget,
1151                                         GdkDragContext  *drag_context,
1152                                         gint             x,
1153                                         gint             y,
1154                                         GtkSelectionData *data,
1155                                         guint            target_type,
1156                                         guint            time,
1157                                         gpointer         user_data)
1158 {
1159     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
1160     ps->scroll_direction = 0; // interrupt autoscrolling, if on
1161     ps->scroll_active = 0;
1162     ps->drag_motion_y = -1;
1163     if (!ps->binding->external_drag_n_drop || !ps->binding->drag_n_drop) {
1164         gtk_drag_finish (drag_context, TRUE, FALSE, time);
1165         return;
1166     }
1167     int sel = ddb_listview_dragdrop_get_row_from_coord (ps, y);
1168     DdbListviewIter it = NULL;
1169     if (sel == -1) {
1170         if (ps->binding->count () != 0) {
1171             sel = ps->binding->count ();
1172         }
1173     }
1174     if (sel != -1) {
1175         it = ps->binding->get_for_idx (sel);
1176     }
1177     gchar *ptr=(char*)gtk_selection_data_get_data (data);
1178     gint len = gtk_selection_data_get_length (data);
1179     if (target_type == TARGET_URILIST) { // uris
1180         // this happens when dropped from file manager
1181         char *mem = malloc (len+1);
1182         memcpy (mem, ptr, len);
1183         mem[len] = 0;
1184         // we don't pass control structure, but there's only one drag-drop view currently
1185         ps->binding->external_drag_n_drop (it, mem, len);
1186         if (it) {
1187             UNREF (it);
1188         }
1189     }
1190     else if (target_type == TARGET_SAMEWIDGET && gtk_selection_data_get_format(data) == 32) { // list of 32bit ints, DDB_URI_LIST target
1191         uint32_t *d= (uint32_t *)ptr;
1192         int plt = *d;
1193         d++;
1194         int length = (len/4)-1;
1195         DdbListviewIter drop_before = it;
1196         // find last selected
1197         if (plt == deadbeef->plt_get_curr_idx ()) {
1198             while (drop_before && ps->binding->is_selected (drop_before)) {
1199                 DdbListviewIter next = PL_NEXT(drop_before);
1200                 UNREF (drop_before);
1201                 drop_before = next;
1202             }
1203         }
1204         while (plt == deadbeef->plt_get_curr_idx () && drop_before && ps->binding->is_selected (drop_before)) {
1205         }
1206         ddb_playlist_t *p = deadbeef->plt_get_for_idx (plt);
1207         if (p) {
1208             // FIXME
1209             ps->binding->drag_n_drop (drop_before, p, d, length, gdk_drag_context_get_selected_action (drag_context) == GDK_ACTION_COPY ? 1 : 0);
1210             deadbeef->plt_unref (p);
1211         }
1212         if (drop_before) {
1213             UNREF (drop_before);
1214         }
1215     }
1216     gtk_drag_finish (drag_context, TRUE, FALSE, time);
1217 }
1218 
1219 gboolean
ddb_listview_list_drag_failed(GtkWidget * widget,GdkDragContext * arg1,GtkDragResult arg2,gpointer user_data)1220 ddb_listview_list_drag_failed                (GtkWidget       *widget,
1221                                         GdkDragContext  *arg1,
1222                                         GtkDragResult    arg2,
1223                                         gpointer         user_data)
1224 {
1225     return TRUE;
1226 }
1227 
1228 
1229 void
ddb_listview_list_drag_leave(GtkWidget * widget,GdkDragContext * drag_context,guint time,gpointer user_data)1230 ddb_listview_list_drag_leave                 (GtkWidget       *widget,
1231                                         GdkDragContext  *drag_context,
1232                                         guint            time,
1233                                         gpointer         user_data)
1234 {
1235     DdbListview *pl = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
1236     ddb_listview_list_track_dragdrop (pl, -1);
1237 }
1238 
1239 // debug function for gdk_draw_drawable
1240 //static inline void
1241 //draw_drawable (GdkDrawable *window, GdkGC *gc, GdkDrawable *drawable, int x1, int y1, int x2, int y2, int w, int h) {
1242 //    gint width1, height1;
1243 //    gint width2, height2;
1244 //    gdk_drawable_get_size (window, &width1, &height1);
1245 //    gdk_drawable_get_size (drawable, &width2, &height2);
1246 ////    assert (y1 >= 0 && y1 + h < height2);
1247 ////    assert (y2 >= 0 && y2 + h < height1);
1248 ////    printf ("dd: %p %p %p %d %d %d %d %d %d\n", window, gc, drawable, x1, y1, x2, y2, w, h);
1249 //    gdk_draw_drawable (window, gc, drawable, x1, y1, x2, y2, w, h);
1250 //}
1251 
1252 int
ddb_listview_get_vscroll_pos(DdbListview * listview)1253 ddb_listview_get_vscroll_pos (DdbListview *listview) {
1254     return listview->scrollpos;
1255 }
1256 
1257 int
ddb_listview_get_hscroll_pos(DdbListview * listview)1258 ddb_listview_get_hscroll_pos (DdbListview *listview) {
1259     return listview->hscrollpos;
1260 }
1261 
1262 #define MIN_COLUMN_WIDTH 16
1263 #define COLHDR_ANIM_TIME 0.2f
1264 
1265 void
ddb_listview_list_setup_vscroll(DdbListview * ps)1266 ddb_listview_list_setup_vscroll (DdbListview *ps) {
1267     ddb_listview_groupcheck (ps);
1268     GtkWidget *list = ps->list;
1269     GtkWidget *scroll = ps->scrollbar;
1270     int vheight = ps->fullheight;
1271     GtkAllocation a;
1272     gtk_widget_get_allocation (ps->list, &a);
1273     if (ps->fullheight <= a.height) {
1274         gtk_widget_hide (scroll);
1275         ps->scrollpos = 0;
1276         gtk_widget_queue_draw (ps->list);
1277     }
1278     else {
1279         gtk_widget_show (scroll);
1280         if (ps->scrollpos >= vheight - a.height) {
1281             ps->scrollpos = vheight - a.height;
1282         }
1283     }
1284     int h = a.height;
1285     GtkAdjustment *adj = (GtkAdjustment*)gtk_adjustment_new (gtk_range_get_value (GTK_RANGE (scroll)), 0, vheight, SCROLL_STEP, h/2, h);
1286     gtk_range_set_adjustment (GTK_RANGE (scroll), adj);
1287     gtk_range_set_value (GTK_RANGE (scroll), ps->scrollpos);
1288 }
1289 
1290 void
ddb_listview_list_setup_hscroll(DdbListview * ps)1291 ddb_listview_list_setup_hscroll (DdbListview *ps) {
1292     GtkWidget *list = ps->list;
1293     GtkAllocation a;
1294     gtk_widget_get_allocation (ps->list, &a);
1295     int w = a.width;
1296     int size = 0;
1297     DdbListviewColumn *c;
1298     for (c = ps->columns; c; c = c->next) {
1299         size += c->width;
1300     }
1301     ddb_listview_list_update_total_width (ps, size);
1302     GtkWidget *scroll = ps->hscrollbar;
1303     if (w >= size) {
1304         gtk_widget_hide (scroll);
1305         ps->hscrollpos = 0;
1306         gtk_widget_queue_draw (ps->list);
1307     }
1308     else {
1309         if (ps->hscrollpos >= size-w) {
1310             int n = size-w-1;
1311             ps->hscrollpos = max (0, n);
1312             gtk_range_set_value (GTK_RANGE (scroll), ps->hscrollpos);
1313         }
1314         gtk_widget_show (scroll);
1315     }
1316     GtkAdjustment *adj = (GtkAdjustment*)gtk_adjustment_new (gtk_range_get_value (GTK_RANGE (scroll)), 0, size, 1, w, w);
1317     gtk_range_set_adjustment (GTK_RANGE (scroll), adj);
1318 }
1319 
1320 // returns -1 if row not found
1321 int
ddb_listview_list_get_drawinfo(DdbListview * listview,int row,DdbListviewGroup ** pgrp,int * even,int * cursor,int * group_y,int * x,int * y,int * w,int * h)1322 ddb_listview_list_get_drawinfo (DdbListview *listview, int row, DdbListviewGroup **pgrp, int *even, int *cursor, int *group_y, int *x, int *y, int *w, int *h) {
1323     deadbeef->pl_lock ();
1324     ddb_listview_groupcheck (listview);
1325     DdbListviewGroup *grp = listview->groups;
1326     int idx = 0;
1327     int idx2 = 0;
1328     *y = -listview->scrollpos;
1329     while (grp) {
1330         int grpheight = grp->height;
1331         if (idx <= row && idx + grp->num_items > row) {
1332             // found
1333             int idx_in_group = row - idx;
1334             *pgrp = grp;
1335             *even = (idx2 + 1 + idx_in_group) & 1;
1336             *cursor = (row == listview->binding->cursor ()) ? 1 : 0;
1337             *group_y = idx_in_group * listview->rowheight;
1338             *x = -listview->hscrollpos;
1339             *y += listview->grouptitle_height + (row - idx) * listview->rowheight;
1340             *w = listview->totalwidth;
1341             *h = listview->rowheight;
1342             deadbeef->pl_unlock ();
1343             return 0;
1344         }
1345         *y += grpheight;
1346         idx += grp->num_items;
1347         idx2 += grp->num_items + 1;
1348         grp = grp->next;
1349     }
1350     deadbeef->pl_unlock ();
1351     return -1;
1352 }
1353 
1354 void
ddb_listview_draw_row(DdbListview * listview,int row,DdbListviewIter it)1355 ddb_listview_draw_row (DdbListview *listview, int row, DdbListviewIter it) {
1356     DdbListviewGroup *grp;
1357     int even;
1358     int cursor;
1359     int x, y, w, h;
1360     int group_y;
1361     if (ddb_listview_list_get_drawinfo (listview, row, &grp, &even, &cursor, &group_y, &x, &y, &w, &h) == -1) {
1362         return;
1363     }
1364 
1365     if (y + h <= 0) {
1366         return;
1367     }
1368 
1369     GtkAllocation a;
1370     gtk_widget_get_allocation (GTK_WIDGET (listview->list), &a);
1371 
1372     if (y > a.height) {
1373         return;
1374     }
1375     gtk_widget_queue_draw_area (listview->list, 0, y, a.width, h);
1376 }
1377 
1378 // coords passed are window-relative
1379 void
ddb_listview_list_render_row_background(DdbListview * ps,cairo_t * cr,DdbListviewIter it,int even,int cursor,int x,int y,int w,int h)1380 ddb_listview_list_render_row_background (DdbListview *ps, cairo_t *cr, DdbListviewIter it, int even, int cursor, int x, int y, int w, int h) {
1381     // draw background
1382     GtkWidget *treeview = theme_treeview;
1383     int theming = !gtkui_override_listview_colors ();
1384 
1385     if (theming) {
1386 #if !GTK_CHECK_VERSION(3,0,0)
1387         if (gtk_widget_get_style (treeview)->depth == -1) {
1388             return; // drawing was called too early
1389         }
1390 #endif
1391     }
1392     int sel = it && ps->binding->is_selected (it);
1393 
1394     if (theming || !sel) {
1395         if (theming) {
1396             // draw background for selection -- workaround for New Wave theme (translucency)
1397 #if GTK_CHECK_VERSION(3,0,0)
1398             gtk_paint_flat_box (gtk_widget_get_style (treeview), cr, GTK_STATE_NORMAL, GTK_SHADOW_NONE, treeview, even ? "cell_even_ruled" : "cell_odd_ruled", x, y, w, h);
1399 #else
1400             gtk_paint_flat_box (gtk_widget_get_style (treeview), ps->list->window, GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, treeview, even ? "cell_even_ruled" : "cell_odd_ruled", x, y, w, h);
1401 #endif
1402         }
1403         else {
1404             GdkColor clr;
1405             even ? gtkui_get_listview_even_row_color (&clr) : gtkui_get_listview_odd_row_color (&clr);
1406             gdk_cairo_set_source_color (cr, &clr);
1407             cairo_rectangle (cr, x, y, w, h);
1408             cairo_fill (cr);
1409         }
1410     }
1411 
1412     if (sel) {
1413         if (theming) {
1414 #if GTK_CHECK_VERSION(3,0,0)
1415             gtk_paint_flat_box (gtk_widget_get_style (treeview), cr, GTK_STATE_SELECTED, GTK_SHADOW_NONE, treeview, even ? "cell_even_ruled" : "cell_odd_ruled", x-1, y-1, w+1, h+1);
1416 #else
1417             gtk_paint_flat_box (gtk_widget_get_style (treeview), ps->list->window, GTK_STATE_SELECTED, GTK_SHADOW_NONE, NULL, treeview, even ? "cell_even_ruled" : "cell_odd_ruled", x, y, w, h);
1418             //            if (gtk_widget_has_focus (ps->list)) {
1419             //                gtk_paint_focus (gtk_widget_get_style (treeview), ps->list->window, GTK_STATE_SELECTED, NULL, treeview, "treeview", x, y, w, h);
1420             //            }
1421 #endif
1422         }
1423         else {
1424             GdkColor clr;
1425 #if !GTK_CHECK_VERSION(3,0,0)
1426             GdkGC *gc = gdk_gc_new (ps->list->window);
1427             gdk_gc_set_rgb_fg_color (gc, (gtkui_get_listview_selection_color (&clr), &clr));
1428             gdk_draw_rectangle (ps->list->window, gc, TRUE, x, y, w, h);
1429             g_object_unref (gc);
1430 
1431 #else
1432             gtkui_get_listview_selection_color (&clr);
1433             cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f);
1434             cairo_rectangle (cr, x, y, w, h);
1435             cairo_fill (cr);
1436 #endif
1437         }
1438     }
1439     if (cursor && gtk_widget_has_focus (ps->list)) {
1440         // not all gtk engines/themes render focus rectangle in treeviews
1441         // but we want it anyway
1442         //treeview->style->fg_gc[GTK_STATE_NORMAL]
1443         GdkColor clr;
1444 #if !GTK_CHECK_VERSION(3,0,0)
1445         GdkGC *gc = gdk_gc_new (ps->list->window);
1446         gdk_gc_set_rgb_fg_color (gc, (gtkui_get_listview_cursor_color (&clr), &clr));
1447         gdk_draw_rectangle (ps->list->window, gc, FALSE, x, y, w-1, h-1);
1448         g_object_unref (gc);
1449 #else
1450         gtkui_get_listview_cursor_color (&clr);
1451         cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f);
1452         cairo_rectangle (cr, x+1, y+1, w-1, h-1);
1453         cairo_stroke (cr);
1454 #endif
1455     }
1456 }
1457 
1458 void
ddb_listview_list_render_row_foreground(DdbListview * ps,cairo_t * cr,DdbListviewIter it,int idx,int even,int cursor,int x,int y,int w,int h)1459 ddb_listview_list_render_row_foreground (DdbListview *ps, cairo_t *cr, DdbListviewIter it, int idx, int even, int cursor, int x, int y, int w, int h) {
1460     int width, height;
1461     GtkAllocation a;
1462     gtk_widget_get_allocation (ps->list, &a);
1463     width = a.width;
1464     height = a.height;
1465     if (it && ps->binding->is_selected (it)) {
1466         GdkColor *clr = &gtk_widget_get_style (theme_treeview)->fg[GTK_STATE_SELECTED];
1467         float rgb[3] = { clr->red/65535.f, clr->green/65535.f, clr->blue/65535.f };
1468         draw_set_fg_color (&ps->listctx, rgb);
1469     }
1470     else {
1471         GdkColor *clr = &gtk_widget_get_style (theme_treeview)->fg[GTK_STATE_NORMAL];
1472         float rgb[3] = { clr->red/65535.f, clr->green/65535.f, clr->blue/65535.f };
1473         draw_set_fg_color (&ps->listctx, rgb);
1474     }
1475     DdbListviewColumn *c;
1476     int cidx = 0;
1477     for (c = ps->columns; c; c = c->next, cidx++) {
1478         int cw = c->width;
1479         if (!ddb_listview_is_album_art_column_idx (ps, cidx)) {
1480             ps->binding->draw_column_data (ps, cr, it, idx, cidx, PL_MAIN, x, y, cw, h);
1481         }
1482         x += cw;
1483     }
1484 }
1485 
1486 void
ddb_listview_list_render_album_art(DdbListview * ps,cairo_t * cr,DdbListviewIter group_it,int group_pinned,int grp_next_y,int x,int y,int w,int h)1487 ddb_listview_list_render_album_art (DdbListview *ps, cairo_t *cr, DdbListviewIter group_it, int group_pinned, int grp_next_y, int x, int y, int w, int h) {
1488     DdbListviewColumn *c;
1489     int cidx = 0;
1490     for (c = ps->columns; c; c = c->next, cidx++) {
1491         int cw = c->width;
1492         if (ddb_listview_is_album_art_column_idx (ps, cidx)) {
1493             ps->binding->draw_album_art (ps, cr, ps->grouptitle_height > 0 ? group_it : NULL, cidx, group_pinned, grp_next_y, x, y, cw, h);
1494         }
1495         x += cw;
1496     }
1497 }
1498 
1499 void
ddb_listview_header_expose(DdbListview * ps,cairo_t * cr,int x,int y,int w,int h)1500 ddb_listview_header_expose (DdbListview *ps, cairo_t *cr, int x, int y, int w, int h) {
1501     ddb_listview_header_render (ps, cr);
1502 }
1503 
1504 void
ddb_listview_select_single(DdbListview * ps,int sel)1505 ddb_listview_select_single (DdbListview *ps, int sel) {
1506     int nchanged = 0;
1507     deadbeef->pl_lock ();
1508 
1509     DdbListviewIter sel_it = ps->binding->get_for_idx (sel);
1510     if (!sel_it) {
1511         deadbeef->pl_unlock ();
1512         return;
1513     }
1514 
1515     DB_playItem_t *it = deadbeef->pl_get_first (PL_MAIN);
1516     while (it) {
1517         int selected = deadbeef->pl_is_selected (it);
1518         if (selected) {
1519             deadbeef->pl_set_selected (it, 0);
1520         }
1521         DB_playItem_t *next = deadbeef->pl_get_next (it, PL_MAIN);
1522         UNREF (it);
1523         it = next;
1524     }
1525     UNREF (it);
1526 
1527     ps->binding->select (sel_it, 1);
1528 
1529     UNREF (sel_it);
1530     deadbeef->pl_unlock ();
1531 
1532     ddb_listview_refresh (ps, DDB_REFRESH_LIST);
1533     ps->binding->selection_changed (ps, NULL, -1); // that means "selection changed a lot, redraw everything"
1534     ps->area_selection_start = sel;
1535     ps->area_selection_end = sel;
1536 }
1537 
1538 void
ddb_listview_click_selection(DdbListview * ps,int ex,int ey,DdbListviewGroup * grp,int grp_index,int sel,int dnd,int button)1539 ddb_listview_click_selection (DdbListview *ps, int ex, int ey, DdbListviewGroup *grp, int grp_index, int sel, int dnd, int button) {
1540     deadbeef->pl_lock ();
1541     ps->areaselect = 0;
1542     ddb_listview_groupcheck (ps);
1543 
1544     // clicked album art column?
1545     int album_art_column = ddb_listview_is_album_art_column (ps, ex);
1546 
1547     if (sel == -1 && !album_art_column && (!grp || (ey > ps->grouptitle_height && grp_index >= grp->num_items))) {
1548         // clicked empty space, deselect everything
1549         DdbListviewIter it;
1550         int idx = 0;
1551         for (it = ps->binding->head (); it; idx++) {
1552             if (ps->binding->is_selected (it)) {
1553                 ps->binding->select (it, 0);
1554                 ddb_listview_draw_row (ps, idx, it);
1555                 ps->binding->selection_changed (ps, it, idx);
1556             }
1557             DdbListviewIter next = ps->binding->next (it);
1558             ps->binding->unref (it);
1559             it = next;
1560         }
1561     }
1562     else if ((sel != -1 && grp && grp_index == -1) || (ey <= ps->grouptitle_height && gtkui_groups_pinned) || album_art_column) {
1563         // clicked group title, select group
1564         DdbListviewIter it;
1565         int idx = 0;
1566         int cnt = -1;
1567         for (it = ps->binding->head (); it; idx++) {
1568             if (it == grp->head) {
1569                 cnt = grp->num_items;
1570             }
1571             if (cnt > 0) {
1572                 if (!ps->binding->is_selected (it)) {
1573                     ps->binding->select (it, 1);
1574                     ddb_listview_draw_row (ps, idx, it);
1575                     ps->binding->selection_changed (ps, it, idx);
1576                 }
1577                 cnt--;
1578             }
1579             else {
1580                 if (ps->binding->is_selected (it)) {
1581                     ps->binding->select (it, 0);
1582                     ddb_listview_draw_row (ps, idx, it);
1583                     ps->binding->selection_changed (ps, it, idx);
1584                 }
1585             }
1586             DdbListviewIter next = ps->binding->next (it);
1587             ps->binding->unref (it);
1588             it = next;
1589         }
1590     }
1591     else {
1592         // clicked specific item - select, or start drag-n-drop
1593         DdbListviewIter it = ps->binding->get_for_idx (sel);
1594         if (!it || !ps->binding->is_selected (it)
1595                 || (!ps->binding->drag_n_drop && button == 1)) // HACK: don't reset selection by right click in search window
1596         {
1597             // reset selection, and set it to single item
1598             ddb_listview_select_single (ps, sel);
1599             if (dnd) {
1600                 ps->areaselect = 1;
1601                 ps->areaselect_y = ey + ps->scrollpos;
1602                 ps->shift_sel_anchor = ps->binding->cursor ();
1603             }
1604         }
1605         else if (dnd) {
1606             ps->dragwait = 1;
1607         }
1608         UNREF (it);
1609     }
1610     deadbeef->pl_unlock ();
1611 }
1612 
1613 // {{{ expected behaviour for mouse1 without modifiers:
1614 //   {{{ [+] if clicked unselected item:
1615 //       unselect all
1616 //       select clicked item
1617 //       deadbeef->pl_get_cursor (ps->iterator) = clicked
1618 //       redraw
1619 //       start 'area selection' mode
1620 //   }}}
1621 //   {{{ [+] if clicked selected item:
1622 //       deadbeef->pl_get_cursor (ps->iterator) = clicked
1623 //       redraw
1624 //       wait until next release or motion event, whichever is 1st
1625 //       if release is 1st:
1626 //           unselect all except clicked, redraw
1627 //       else if motion is 1st:
1628 //           enter drag-drop mode
1629 //   }}}
1630 // }}}
1631 void
ddb_listview_list_mouse1_pressed(DdbListview * ps,int state,int ex,int ey,GdkEventType type)1632 ddb_listview_list_mouse1_pressed (DdbListview *ps, int state, int ex, int ey, GdkEventType type) {
1633     // cursor must be set here, but selection must be handled in keyrelease
1634     deadbeef->pl_lock ();
1635     ddb_listview_groupcheck (ps);
1636     int cnt = ps->binding->count ();
1637     if (cnt == 0) {
1638         deadbeef->pl_unlock ();
1639         return;
1640     }
1641     // remember mouse coords for doubleclick detection
1642     ps->lastpos[0] = ex;
1643     ps->lastpos[1] = ey;
1644     // select item
1645     DdbListviewGroup *grp;
1646     int grp_index;
1647     int sel;
1648     if (ddb_listview_list_pickpoint_y (ps, ey + ps->scrollpos, &grp, &grp_index, &sel) == -1) {
1649         deadbeef->pl_unlock ();
1650         return;
1651     }
1652 
1653     int cursor = ps->binding->cursor ();
1654     if (type == GDK_2BUTTON_PRESS
1655             && fabs(ps->lastpos[0] - ex) < 3
1656             && fabs(ps->lastpos[1] - ey) < 3) {
1657         // doubleclick - play this item
1658         if (sel != -1 && cursor != -1) {
1659             int idx = cursor;
1660             DdbListviewIter it = ps->binding->get_for_idx (idx);
1661             if (ps->binding->handle_doubleclick && it) {
1662                 ps->binding->handle_doubleclick (ps, it, idx);
1663             }
1664             if (it) {
1665                 ps->binding->unref (it);
1666             }
1667             deadbeef->pl_unlock ();
1668             return;
1669         }
1670     }
1671 
1672     int prev = cursor;
1673     if (sel != -1) {
1674         // pick 1st item in group in case album art column was clicked
1675         if (ddb_listview_is_album_art_column (ps, ex) && grp_index != -1) {
1676             sel -= grp_index;
1677         }
1678 
1679         ps->binding->set_cursor (sel);
1680         DdbListviewIter it = ps->binding->get_for_idx (sel);
1681         if (it) {
1682             ddb_listview_draw_row (ps, sel, it);
1683             UNREF (it);
1684         }
1685         ps->shift_sel_anchor = ps->binding->cursor ();
1686     }
1687     // handle multiple selection
1688 #ifndef __APPLE__
1689     int selmask = GDK_CONTROL_MASK;
1690 #else
1691     int selmask = GDK_MOD2_MASK;
1692 #endif
1693     if (!(state & (selmask|GDK_SHIFT_MASK)))
1694     {
1695         ddb_listview_click_selection (ps, ex, ey, grp, grp_index, sel, 1, 1);
1696     }
1697     else if (state & selmask) {
1698         // toggle selection
1699         if (sel != -1) {
1700             DdbListviewIter it = ps->binding->get_for_idx (sel);
1701             if (it) {
1702                 ps->binding->select (it, 1 - ps->binding->is_selected (it));
1703                 ddb_listview_draw_row (ps, sel, it);
1704                 ps->binding->selection_changed (ps, it, sel);
1705                 UNREF (it);
1706             }
1707         }
1708     }
1709     else if (state & GDK_SHIFT_MASK) {
1710         // select range
1711         int cursor = sel;//ps->binding->cursor ();
1712         if (cursor == -1) {
1713             // find group
1714             DdbListviewGroup *g = ps->groups;
1715             int idx = 0;
1716             while (g) {
1717                 if (g == grp) {
1718                     cursor = idx - 1;
1719                     break;
1720                 }
1721                 idx += g->num_items;
1722                 g = g->next;
1723             }
1724         }
1725         int start = min (prev, cursor);
1726         int end = max (prev, cursor);
1727         int idx = 0;
1728         for (DdbListviewIter it = ps->binding->head (); it; idx++) {
1729             if (idx >= start && idx <= end) {
1730                 if (!ps->binding->is_selected (it)) {
1731                     ps->binding->select (it, 1);
1732                     ddb_listview_draw_row (ps, idx, it);
1733                     ps->binding->selection_changed (ps, it, idx);
1734                 }
1735             }
1736             else {
1737                 if (ps->binding->is_selected (it)) {
1738                     ps->binding->select (it, 0);
1739                     ddb_listview_draw_row (ps, idx, it);
1740                     ps->binding->selection_changed (ps, it, idx);
1741                 }
1742             }
1743             DdbListviewIter next = PL_NEXT (it);
1744             UNREF (it);
1745             it = next;
1746         }
1747     }
1748     cursor = ps->binding->cursor ();
1749     if (cursor != -1 && sel == -1) {
1750         DdbListviewIter it = ps->binding->get_for_idx (cursor);
1751         ddb_listview_draw_row (ps, cursor, it);
1752         UNREF (it);
1753     }
1754     if (prev != -1 && prev != cursor) {
1755         DdbListviewIter it = ps->binding->get_for_idx (prev);
1756         ddb_listview_draw_row (ps, prev, it);
1757         UNREF (it);
1758     }
1759     deadbeef->pl_unlock ();
1760 }
1761 
1762 void
ddb_listview_list_mouse1_released(DdbListview * ps,int state,int ex,int ey,double time)1763 ddb_listview_list_mouse1_released (DdbListview *ps, int state, int ex, int ey, double time) {
1764     if (ps->dragwait) {
1765         ps->dragwait = 0;
1766         DdbListviewGroup *grp;
1767         int grp_index;
1768         int sel;
1769         if (!ddb_listview_list_pickpoint_y (ps, ey + ps->scrollpos, &grp, &grp_index, &sel)) {
1770             ddb_listview_select_single (ps, sel);
1771         }
1772         else {
1773             ps->binding->set_cursor (-1);
1774             DdbListviewIter it = ps->binding->head ();
1775             int idx = 0;
1776             while (it) {
1777                 if (ps->binding->is_selected (it)) {
1778                     ps->binding->select (it, 0);
1779                     ddb_listview_draw_row (ps, idx, it);
1780                     ps->binding->selection_changed (ps, it, idx);
1781                     DdbListviewIter next = PL_NEXT(it);
1782                     UNREF (it);
1783                     it = next;
1784                 }
1785                 idx++;
1786             }
1787         }
1788     }
1789     else if (ps->areaselect) {
1790         ps->scroll_direction = 0;
1791         ps->scroll_pointer_y = -1;
1792         ps->areaselect = 0;
1793     }
1794 }
1795 
1796 #if 0
1797 void
1798 ddb_listview_list_dbg_draw_areasel (GtkWidget *widget, int x, int y) {
1799     // erase previous rect using 4 blits from ps->list->windowfer
1800     if (areaselect_dx != -1) {
1801         int sx = min (areaselect_x, areaselect_dx);
1802         int sy = min (areaselect_y, areaselect_dy);
1803         int dx = max (areaselect_x, areaselect_dx);
1804         int dy = max (areaselect_y, areaselect_dy);
1805         int w = dx - sx + 1;
1806         int h = dy - sy + 1;
1807         //draw_drawable (widget->window, widget->style->black_gc, ps->list->window, sx, sy, sx, sy, dx - sx + 1, dy - sy + 1);
1808         draw_drawable (widget->window, widget->style->black_gc, ps->list->window, sx, sy, sx, sy, w, 1);
1809         draw_drawable (widget->window, widget->style->black_gc, ps->list->window, sx, sy, sx, sy, 1, h);
1810         draw_drawable (widget->window, widget->style->black_gc, ps->list->window, sx, sy + h - 1, sx, sy + h - 1, w, 1);
1811         draw_drawable (widget->window, widget->style->black_gc, ps->list->window, sx + w - 1, sy, sx + w - 1, sy, 1, h);
1812     }
1813     areaselect_dx = x;
1814     areaselect_dy = y;
1815 	cairo_t *cr;
1816 	cr = gdk_cairo_create (widget->window);
1817 	if (!cr) {
1818 		return;
1819 	}
1820     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
1821     cairo_set_line_width (cr, 1);
1822     int sx = min (areaselect_x, x);
1823     int sy = min (areaselect_y, y);
1824     int dx = max (areaselect_x, x);
1825     int dy = max (areaselect_y, y);
1826     cairo_rectangle (cr, sx, sy, dx-sx, dy-sy);
1827     cairo_stroke (cr);
1828     cairo_destroy (cr);
1829 }
1830 #endif
1831 
1832 static gboolean
ddb_listview_list_scroll_cb(gpointer data)1833 ddb_listview_list_scroll_cb (gpointer data) {
1834     DdbListview *ps = (DdbListview *)data;
1835     ps->scroll_active = 1;
1836     struct timeval tm;
1837     gettimeofday (&tm, NULL);
1838     float dt = tm.tv_sec - ps->tm_prevscroll.tv_sec + (tm.tv_usec - ps->tm_prevscroll.tv_usec) / 1000000.0;
1839     if (dt < ps->scroll_sleep_time) {
1840         return TRUE;
1841     }
1842     memcpy (&ps->tm_prevscroll, &tm, sizeof (tm));
1843     if (ps->scroll_pointer_y == -1) {
1844         ps->scroll_active = 0;
1845         return FALSE;
1846     }
1847     if (ps->scroll_direction == 0) {
1848         ps->scroll_active = 0;
1849         return FALSE;
1850     }
1851     int sc = ps->scrollpos + (ps->scroll_direction * 100 * dt);
1852     if (sc < 0) {
1853         ps->scroll_active = 0;
1854         return FALSE;
1855     }
1856 //    trace ("scroll to %d speed %f\n", sc, ps->scroll_direction);
1857     gtk_range_set_value (GTK_RANGE (ps->scrollbar), sc);
1858     if (ps->scroll_mode == 0) {
1859         ddb_listview_list_mousemove (ps, NULL, 0, ps->scroll_pointer_y);
1860     }
1861     else if (ps->scroll_mode == 1) {
1862         ddb_listview_list_track_dragdrop (ps, ps->scroll_pointer_y);
1863     }
1864     if (ps->scroll_direction < 0) {
1865         ps->scroll_direction -= (10 * dt);
1866         if (ps->scroll_direction < -30) {
1867             ps->scroll_direction = -30;
1868         }
1869     }
1870     else {
1871         ps->scroll_direction += (10 * dt);
1872         if (ps->scroll_direction > 30) {
1873             ps->scroll_direction = 30;
1874         }
1875     }
1876     return TRUE;
1877 }
1878 
1879 void
ddb_listview_list_mousemove(DdbListview * ps,GdkEventMotion * ev,int ex,int ey)1880 ddb_listview_list_mousemove (DdbListview *ps, GdkEventMotion *ev, int ex, int ey) {
1881     deadbeef->pl_lock ();
1882     if (ps->dragwait) {
1883         GtkWidget *widget = ps->list;
1884         if (gtk_drag_check_threshold (widget, ps->lastpos[0], ps->lastpos[1], ex, ey)) {
1885             ps->dragwait = 0;
1886             ps->drag_source_playlist = deadbeef->plt_get_curr_idx ();
1887             GtkTargetEntry entry = {
1888                 .target = TARGET_PLAYITEMS,
1889                 .flags = GTK_TARGET_SAME_WIDGET,
1890                 .info = TARGET_SAMEWIDGET
1891             };
1892             GtkTargetList *lst = gtk_target_list_new (&entry, 1);
1893             gtk_drag_begin (widget, lst, GDK_ACTION_COPY | GDK_ACTION_MOVE, 1, (GdkEvent *)ev);
1894         }
1895     }
1896     else if (ps->areaselect) {
1897         DdbListviewGroup *grp;
1898         int grp_index;
1899         int sel;
1900         if (ddb_listview_list_pickpoint_y (ps, ey + ps->scrollpos, &grp, &grp_index, &sel) == -1) {
1901             // past playlist bounds -> set to last track
1902             sel = ps->binding->count () - 1;
1903         }
1904         else if (sel == -1) {
1905             if (grp_index == -1) {
1906                 if (ps->areaselect_y < ey + ps->scrollpos) {
1907                     // below anchor, take last track in prev group
1908                     sel = ps->binding->get_idx (grp->head) - 1;
1909                 }
1910                 else if (ps->areaselect_y > ey + ps->scrollpos) {
1911                     // above, select 1st track in group
1912                         sel = ps->binding->get_idx (grp->head);
1913                 }
1914                 else {
1915                     sel = ps->shift_sel_anchor;
1916                 }
1917             }
1918             else {
1919                 if (ps->areaselect_y < ey + ps->scrollpos) {
1920                     // below anchor, take last track in group
1921                     sel = ps->binding->get_idx (grp->head) + grp->num_items - 1;
1922                 }
1923                 else if (ps->areaselect_y > ey + ps->scrollpos) {
1924                     // above, select 1st track in next group
1925                     if (grp->next) {
1926                         sel = ps->binding->get_idx (grp->next->head);
1927                     }
1928                 }
1929                 else {
1930                     sel = ps->shift_sel_anchor;
1931                 }
1932             }
1933         }
1934         int prev = ps->binding->cursor ();
1935         if (sel != -1) {
1936             ps->binding->set_cursor (sel);
1937         }
1938         {
1939             // select range of items
1940             int y = sel;
1941             int idx = 0;
1942             if (y == -1) {
1943                 // find group
1944                 ddb_listview_groupcheck (ps);
1945                 DdbListviewGroup *g = ps->groups;
1946                 while (g) {
1947                     if (g == grp) {
1948                         y = idx - 1;
1949                         break;
1950                     }
1951                     idx += g->num_items;
1952                     g = g->next;
1953                 }
1954             }
1955             int start = min (y, ps->shift_sel_anchor);
1956             int end = max (y, ps->shift_sel_anchor);
1957 
1958             int nchanged = 0;
1959 
1960             // don't touch anything in process_start/end range
1961             int process_start = min (start, ps->area_selection_start);
1962             int process_end = max (end, ps->area_selection_end);
1963 
1964             idx=process_start;
1965             DdbListviewIter it = ps->binding->get_for_idx (idx);
1966             for (; it && idx <= process_end; idx++) {
1967                 int selected = ps->binding->is_selected (it);
1968                 if (idx >= start && idx <= end) {
1969                     if (!selected) {
1970                         ps->binding->select (it, 1);
1971                         nchanged++;
1972                         if (nchanged < NUM_CHANGED_ROWS_BEFORE_FULL_REDRAW) {
1973                             ddb_listview_draw_row (ps, idx, it);
1974                             ps->binding->selection_changed (ps, it, idx);
1975                         }
1976                     }
1977                 }
1978                 else if (selected) {
1979                     ps->binding->select (it, 0);
1980                     nchanged++;
1981                     if (nchanged < NUM_CHANGED_ROWS_BEFORE_FULL_REDRAW) {
1982                         ddb_listview_draw_row (ps, idx, it);
1983                         ps->binding->selection_changed (ps, it, idx);
1984                     }
1985                 }
1986                 DdbListviewIter next = PL_NEXT(it);
1987                 UNREF (it);
1988                 it = next;
1989             }
1990             UNREF (it);
1991             if (nchanged >= NUM_CHANGED_ROWS_BEFORE_FULL_REDRAW) {
1992                 ddb_listview_refresh (ps, DDB_REFRESH_LIST);
1993                 ps->binding->selection_changed (ps, it, -1); // that means "selection changed a lot, redraw everything"
1994             }
1995             ps->area_selection_start = start;
1996             ps->area_selection_end = end;
1997         }
1998         if (sel != -1 && sel != prev) {
1999             if (prev != -1) {
2000                 DdbListviewIter it = ps->binding->get_for_idx (prev);
2001                 if (it) {
2002                     ddb_listview_draw_row (ps, prev, it);
2003                     UNREF (it);
2004                 }
2005             }
2006             DdbListviewIter it = ps->binding->get_for_idx (sel);
2007             if (it) {
2008                 ddb_listview_draw_row (ps, sel, it);
2009                 UNREF (it);
2010             }
2011         }
2012 
2013         GtkAllocation a;
2014         gtk_widget_get_allocation (ps->list, &a);
2015 
2016         if (ey < 10) {
2017             ps->scroll_mode = 0;
2018             ps->scroll_pointer_y = ey;
2019             // start scrolling up
2020             if (!ps->scroll_active) {
2021                 ps->scroll_direction = -1;
2022                 ps->scroll_sleep_time = AUTOSCROLL_UPDATE_FREQ;
2023                 gettimeofday (&ps->tm_prevscroll, NULL);
2024                 g_idle_add (ddb_listview_list_scroll_cb, ps);
2025             }
2026         }
2027         else if (ey > a.height-10) {
2028             ps->scroll_mode = 0;
2029             ps->scroll_pointer_y = ey;
2030             // start scrolling down
2031             if (!ps->scroll_active) {
2032                 ps->scroll_direction = 1;
2033                 ps->scroll_sleep_time = AUTOSCROLL_UPDATE_FREQ;
2034                 gettimeofday (&ps->tm_prevscroll, NULL);
2035                 g_idle_add (ddb_listview_list_scroll_cb, ps);
2036             }
2037         }
2038         else {
2039             ps->scroll_direction = 0;
2040             ps->scroll_pointer_y = -1;
2041         }
2042         // debug only
2043         // ddb_listview_list_dbg_draw_areasel (widget, event->x, event->y);
2044     }
2045     deadbeef->pl_unlock ();
2046 }
2047 
2048 void
ddb_listview_list_set_hscroll(DdbListview * ps,int newscroll)2049 ddb_listview_list_set_hscroll (DdbListview *ps, int newscroll) {
2050     if (ps->block_redraw_on_scroll) {
2051         ps->hscrollpos = newscroll;
2052         return;
2053     }
2054 //    if (newscroll != ps->hscrollpos)
2055     // need to redraw because this might be window resize
2056     {
2057         ps->hscrollpos = newscroll;
2058         GtkWidget *widget = ps->list;
2059         gtk_widget_queue_draw (ps->header);
2060         gtk_widget_queue_draw (ps->list);
2061     }
2062 }
2063 
2064 gboolean
ddb_listview_handle_keypress(DdbListview * ps,int keyval,int state)2065 ddb_listview_handle_keypress (DdbListview *ps, int keyval, int state) {
2066     int prev = ps->binding->cursor ();
2067     int cursor = prev;
2068     GtkWidget *range = ps->scrollbar;
2069     GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (range));
2070 
2071     state &= (GDK_SHIFT_MASK|GDK_CONTROL_MASK|GDK_MOD1_MASK|GDK_MOD4_MASK);
2072 
2073     if (state & ~GDK_SHIFT_MASK) {
2074         return FALSE;
2075     }
2076 
2077     if (keyval == GDK_Down) {
2078         if (cursor < ps->binding->count () - 1) {
2079             cursor++;
2080         }
2081         else {
2082             gtk_range_set_value (GTK_RANGE (range), gtk_adjustment_get_upper (adj));
2083         }
2084     }
2085     else if (keyval == GDK_Up) {
2086         if (cursor > 0) {
2087             cursor--;
2088         }
2089         else {
2090             gtk_range_set_value (GTK_RANGE (range), gtk_adjustment_get_lower (adj));
2091             if (cursor < 0 && ps->binding->count () > 0) {
2092                 cursor = 0;
2093             }
2094         }
2095     }
2096     else if (keyval == GDK_Page_Down) {
2097         if (cursor < ps->binding->count () - 1) {
2098             cursor += 10;
2099             if (cursor >= ps->binding->count ()) {
2100                 cursor = ps->binding->count () - 1;
2101             }
2102         }
2103         else {
2104             gtk_range_set_value (GTK_RANGE (range), gtk_adjustment_get_upper (adj));
2105         }
2106     }
2107     else if (keyval == GDK_Page_Up) {
2108         if (cursor > 0) {
2109             cursor -= 10;
2110             if (cursor < 0) {
2111                 gtk_range_set_value (GTK_RANGE (range), gtk_adjustment_get_upper (adj));
2112                 cursor = 0;
2113             }
2114         }
2115         else {
2116             if (cursor < 0 && ps->binding->count () > 0) {
2117                 cursor = 0;
2118             }
2119             gtk_range_set_value (GTK_RANGE (range), gtk_adjustment_get_lower (adj));
2120         }
2121     }
2122     else if (keyval == GDK_End) {
2123         cursor = ps->binding->count () - 1;
2124         gtk_range_set_value (GTK_RANGE (range), gtk_adjustment_get_upper (adj));
2125     }
2126     else if (keyval == GDK_Home) {
2127         cursor = 0;
2128         gtk_range_set_value (GTK_RANGE (range), gtk_adjustment_get_lower (adj));
2129     }
2130     else {
2131         return FALSE;
2132     }
2133 
2134     if (state & GDK_SHIFT_MASK) {
2135         GtkAllocation a;
2136         gtk_widget_get_allocation (ps->list, &a);
2137         if (cursor != prev) {
2138             int newscroll = ps->scrollpos;
2139             int cursor_scroll = ddb_listview_get_row_pos (ps, cursor);
2140             if (cursor_scroll < ps->scrollpos) {
2141                 newscroll = cursor_scroll;
2142             }
2143             else if (cursor_scroll >= ps->scrollpos + a.height) {
2144                 newscroll = cursor_scroll - a.height + 1;
2145                 if (newscroll < 0) {
2146                     newscroll = 0;
2147                 }
2148             }
2149             if (ps->scrollpos != newscroll) {
2150                 GtkWidget *range = ps->scrollbar;
2151                 gtk_range_set_value (GTK_RANGE (range), newscroll);
2152             }
2153 
2154             ps->binding->set_cursor (cursor);
2155             // select all between shift_sel_anchor and deadbeef->pl_get_cursor (ps->iterator)
2156             int start = min (cursor, ps->shift_sel_anchor);
2157             int end = max (cursor, ps->shift_sel_anchor);
2158 
2159             int nchanged = 0;
2160             int idx=0;
2161 
2162             DdbListviewIter it;
2163             for (it = ps->binding->head (); it; idx++) {
2164                 if (idx >= start && idx <= end) {
2165                     ps->binding->select (it, 1);
2166                     if (nchanged < NUM_CHANGED_ROWS_BEFORE_FULL_REDRAW) {
2167                         ddb_listview_draw_row (ps, idx, it);
2168                         ps->binding->selection_changed (ps, it, idx);
2169                     }
2170                 }
2171                 else if (ps->binding->is_selected (it))
2172                 {
2173                     ps->binding->select (it, 0);
2174                     if (nchanged < NUM_CHANGED_ROWS_BEFORE_FULL_REDRAW) {
2175                         ddb_listview_draw_row (ps, idx, it);
2176                         ps->binding->selection_changed (ps, it, idx);
2177                     }
2178                 }
2179                 DdbListviewIter next = PL_NEXT(it);
2180                 UNREF (it);
2181                 it = next;
2182             }
2183             UNREF (it);
2184             if (nchanged >= NUM_CHANGED_ROWS_BEFORE_FULL_REDRAW) {
2185                 ddb_listview_refresh (ps, DDB_REFRESH_LIST);
2186                 ps->binding->selection_changed (ps, it, -1); // that means "selection changed a lot, redraw everything"
2187             }
2188         }
2189     }
2190     else {
2191         ps->shift_sel_anchor = cursor;
2192         ddb_listview_set_cursor (ps, cursor);
2193     }
2194     return TRUE;
2195 }
2196 
2197 gboolean
ddb_listview_list_popup_menu(GtkWidget * widget,gpointer user_data)2198 ddb_listview_list_popup_menu (GtkWidget *widget, gpointer user_data) {
2199     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2200     DdbListviewIter it = ps->binding->head ();
2201     while (it && !ps->binding->is_selected (it)) {
2202         DdbListviewIter next = ps->binding->next (it);
2203         ps->binding->unref (it);
2204         it = next;
2205     }
2206     if (it) {
2207         int sel = ps->binding->get_idx (it);
2208         ps->binding->list_context_menu (ps, it, sel);
2209         ps->binding->unref (it);
2210     }
2211     return TRUE;
2212 }
2213 
2214 int
ddb_listview_dragdrop_get_row_from_coord(DdbListview * listview,int y)2215 ddb_listview_dragdrop_get_row_from_coord (DdbListview *listview, int y) {
2216     if (y == -1) {
2217         return -1;
2218     }
2219     DdbListviewGroup *grp;
2220     int grp_index;
2221     int sel;
2222     if (ddb_listview_list_pickpoint_y (listview, y + listview->scrollpos, &grp, &grp_index, &sel) == -1) {
2223         return -1;
2224     }
2225     else {
2226         if (sel == -1) {
2227             if (grp_index == -1) {
2228                 sel = listview->binding->get_idx (grp->head);
2229             }
2230             else {
2231                 sel = listview->binding->get_idx (grp->head) + grp->num_items;
2232             }
2233         }
2234     }
2235     if (sel != -1) {
2236         int it_y = ddb_listview_get_row_pos (listview, sel) - listview->scrollpos;
2237         if (y > it_y + listview->rowheight/2 && y < it_y + listview->rowheight) {
2238             sel++;
2239         }
2240     }
2241     return sel;
2242 }
2243 
2244 void
ddb_listview_list_track_dragdrop(DdbListview * ps,int y)2245 ddb_listview_list_track_dragdrop (DdbListview *ps, int y) {
2246     GtkWidget *widget = ps->list;
2247     GtkAllocation a;
2248     gtk_widget_get_allocation (widget, &a);
2249     if (ps->drag_motion_y != -1) {
2250         // erase previous track
2251         gtk_widget_queue_draw_area (ps->list, 0, ps->drag_motion_y-ps->scrollpos-3, a.width, 7);
2252 
2253     }
2254     if (y == -1) {
2255         ps->drag_motion_y = -1;
2256         ps->scroll_active = 0;
2257         ps->scroll_direction = 0;
2258         return;
2259     }
2260     int sel = ddb_listview_dragdrop_get_row_from_coord (ps, y);
2261     if (sel == -1) {
2262         if (ps->binding->count () == 0) {
2263             ps->drag_motion_y = 0;
2264         }
2265         else {
2266             // after last row
2267             ps->drag_motion_y = ddb_listview_get_row_pos (ps, ps->binding->count ()-1) + ps->rowheight;
2268         }
2269     }
2270     else {
2271         ps->drag_motion_y = ddb_listview_get_row_pos (ps, sel);
2272     }
2273 
2274 #if !GTK_CHECK_VERSION(3,0,0)
2275     // FIXME
2276 //    ddb_listview_draw_dnd_marker (ps, cr);
2277 #endif
2278 
2279     if (y < 10) {
2280         ps->scroll_pointer_y = y;
2281         ps->scroll_mode = 1;
2282         // start scrolling up
2283         if (!ps->scroll_active) {
2284             ps->scroll_direction = -1;
2285             ps->scroll_sleep_time = AUTOSCROLL_UPDATE_FREQ;
2286             gettimeofday (&ps->tm_prevscroll, NULL);
2287             g_idle_add (ddb_listview_list_scroll_cb, ps);
2288         }
2289     }
2290     else if (y > a.height-10) {
2291         ps->scroll_mode = 1;
2292         ps->scroll_pointer_y = y;
2293         // start scrolling up
2294         if (!ps->scroll_active) {
2295             ps->scroll_direction = 1;
2296             ps->scroll_sleep_time = AUTOSCROLL_UPDATE_FREQ;
2297             gettimeofday (&ps->tm_prevscroll, NULL);
2298             g_idle_add (ddb_listview_list_scroll_cb, ps);
2299         }
2300     }
2301     else {
2302         ps->scroll_direction = 0;
2303         ps->scroll_pointer_y = -1;
2304     }
2305 }
2306 
2307 void
ddb_listview_list_drag_end(GtkWidget * widget,GdkDragContext * drag_context,gpointer user_data)2308 ddb_listview_list_drag_end                   (GtkWidget       *widget,
2309                                         GdkDragContext  *drag_context,
2310                                         gpointer         user_data)
2311 {
2312     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2313     deadbeef->sendmessage (DB_EV_PLAYLISTCHANGED, 0, DDB_PLAYLIST_CHANGE_CONTENT, 0);
2314     ps->scroll_direction = 0;
2315     ps->scroll_pointer_y = -1;
2316 }
2317 
2318 // #define HEADERS_GTKTHEME
2319 
2320 void
ddb_listview_header_render(DdbListview * ps,cairo_t * cr)2321 ddb_listview_header_render (DdbListview *ps, cairo_t *cr) {
2322     cairo_set_line_width (cr, 1);
2323     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
2324     GtkWidget *widget = ps->header;
2325     int x = -ps->hscrollpos;
2326     int w = 100;
2327     GtkAllocation a;
2328     gtk_widget_get_allocation (widget, &a);
2329     int h = a.height;
2330     const char *detail = "button";
2331 
2332     // fill background and draw bottom line
2333 #if !HEADERS_GTKTHEME
2334     GdkColor clr;
2335     gtkui_get_tabstrip_base_color (&clr);
2336     cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f);
2337     cairo_rectangle (cr, 0, 0,  a.width, a.height);
2338     cairo_fill (cr);
2339     gtkui_get_tabstrip_dark_color (&clr);
2340     cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f);
2341     cairo_move_to (cr, 0, a.height);
2342     cairo_line_to (cr, a.width, a.height);
2343     cairo_stroke (cr);
2344 #else
2345 #if GTK_CHECK_VERSION(3,0,0)
2346     gtk_paint_box (gtk_widget_get_style (theme_button), cr, GTK_STATE_NORMAL, GTK_SHADOW_OUT, widget, detail, -10, -10, a.width+20, a.height+20);
2347 #else
2348     gtk_paint_box (theme_button->style, ps->header->window, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, widget, detail, -10, -10, a.width+20, a.height+20);
2349 #endif
2350     clr = gtk_widget_get_style (widget)->mid[GTK_STATE_NORMAL]
2351     cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f);
2352     cairo_move_to (cr, 0, a.height-1);
2353     cairo_line_to (cr, a.width, a.height-1);
2354     cairo_stroke (cr);
2355 #endif
2356     draw_begin (&ps->hdrctx, cr);
2357     x = -ps->hscrollpos;
2358     DdbListviewColumn *c;
2359     int need_draw_moving = 0;
2360     int idx = 0;
2361     for (c = ps->columns; c; c = c->next, idx++) {
2362         w = c->width;
2363         int xx = x;
2364 #if 0
2365         if (colhdr_anim.anim_active) {
2366             if (idx == colhdr_anim.c2) {
2367                 xx = colhdr_anim.ax1;
2368             }
2369             else if (idx == colhdr_anim.c1) {
2370                 xx = colhdr_anim.ax2;
2371             }
2372         }
2373 #endif
2374         if (ps->header_dragging < 0 || idx != ps->header_dragging) {
2375             if (xx >= a.width) {
2376                 continue;
2377             }
2378             int arrow_sz = 10;
2379             int sort = c->sort_order;
2380             if (w > 0) {
2381 #if !HEADERS_GTKTHEME
2382                 gtkui_get_tabstrip_dark_color (&clr);
2383                 cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f);
2384                 cairo_move_to (cr, xx+w - 2, 2);
2385                 cairo_line_to (cr, xx+w - 2, h-4);
2386                 cairo_stroke (cr);
2387 
2388                 gtkui_get_tabstrip_light_color (&clr);
2389                 cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.f);
2390 
2391                 cairo_move_to (cr, xx+w - 1, 2);
2392                 cairo_line_to (cr, xx+w - 1, h-4);
2393                 cairo_stroke (cr);
2394 #else
2395 #if GTK_CHECK_VERSION(3,0,0)
2396                 gtk_paint_vline (gtk_widget_get_style (widget), cr, GTK_STATE_NORMAL, widget, NULL, 2, h-4, xx+w - 3);
2397 #else
2398                 gtk_paint_vline (widget->style, ps->header->window, GTK_STATE_NORMAL, NULL, widget, NULL, 2, h-4, xx+w - 3);
2399 #endif
2400 #endif
2401                 GdkColor *gdkfg;
2402                 if (!gtkui_override_listview_colors ()) {
2403                     gdkfg = &gtk_widget_get_style (theme_button)->fg[0];
2404                 }
2405                 else {
2406                     gdkfg = (gtkui_get_listview_column_text_color (&clr), &clr);
2407                 }
2408                 float fg[3] = {(float)gdkfg->red/0xffff, (float)gdkfg->green/0xffff, (float)gdkfg->blue/0xffff};
2409                 draw_set_fg_color (&ps->hdrctx, fg);
2410                 int ww = w-10;
2411                 if (sort) {
2412                     ww -= arrow_sz;
2413                     if (ww < 0) {
2414                         ww = 0;
2415                     }
2416                 }
2417                 draw_text_custom (&ps->hdrctx, xx + 5, 3, ww, 0, DDB_COLUMN_FONT, 0, 0, c->title);
2418             }
2419             if (sort) {
2420                 int dir = sort == 1 ? GTK_ARROW_DOWN : GTK_ARROW_UP;
2421 #if GTK_CHECK_VERSION(3,0,0)
2422                 gtk_paint_arrow (gtk_widget_get_style (widget), cr, GTK_STATE_NORMAL, GTK_SHADOW_NONE, widget, NULL, dir, TRUE, xx + w-arrow_sz-5, a.height/2-arrow_sz/2, arrow_sz, arrow_sz);
2423 #else
2424                 gtk_paint_arrow (widget->style, ps->header->window, GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, widget, NULL, dir, TRUE, xx + w-arrow_sz-5, a.height/2-arrow_sz/2, arrow_sz, arrow_sz);
2425 #endif
2426             }
2427         }
2428         else {
2429             need_draw_moving = 1;
2430         }
2431         x += w;
2432     }
2433     if (need_draw_moving) {
2434         x = -ps->hscrollpos;
2435         idx = 0;
2436         for (c = ps->columns; c; c = c->next, idx++) {
2437             w = c->width;
2438             if (idx == ps->header_dragging) {
2439 #if 0
2440                 if (colhdr_anim.anim_active) {
2441                     if (idx == colhdr_anim.c2) {
2442                         x = colhdr_anim.ax1;
2443                     }
2444                     else if (idx == colhdr_anim.c1) {
2445                         x = colhdr_anim.ax2;
2446                     }
2447                 }
2448 #endif
2449                 // draw empty slot
2450                 if (x < a.width) {
2451 #if GTK_CHECK_VERSION(3,0,0)
2452                     gtk_paint_box (gtk_widget_get_style (theme_button), cr, GTK_STATE_ACTIVE, GTK_SHADOW_ETCHED_IN, widget, "button", x, 0, w, h);
2453 #else
2454                     gtk_paint_box (theme_button->style, ps->header->window, GTK_STATE_ACTIVE, GTK_SHADOW_ETCHED_IN, NULL, widget, "button", x, 0, w, h);
2455 #endif
2456                 }
2457                 x = ps->col_movepos - ps->hscrollpos;
2458                 if (x >= a.width) {
2459                     break;
2460                 }
2461                 if (w > 0) {
2462 #if GTK_CHECK_VERSION(3,0,0)
2463                     gtk_paint_box (gtk_widget_get_style (theme_button), cr, GTK_STATE_SELECTED, GTK_SHADOW_OUT, widget, "button", x, 0, w, h);
2464 #else
2465                     gtk_paint_box (theme_button->style, ps->header->window, GTK_STATE_SELECTED, GTK_SHADOW_OUT, NULL, widget, "button", x, 0, w, h);
2466 #endif
2467                     GdkColor *gdkfg = &gtk_widget_get_style (theme_button)->fg[GTK_STATE_SELECTED];
2468                     float fg[3] = {(float)gdkfg->red/0xffff, (float)gdkfg->green/0xffff, (float)gdkfg->blue/0xffff};
2469                     draw_set_fg_color (&ps->hdrctx, fg);
2470                     draw_text_custom (&ps->hdrctx, x + 5, 3, c->width-10, 0, DDB_COLUMN_FONT, 0, 0, c->title);
2471                 }
2472                 break;
2473             }
2474             x += w;
2475         }
2476     }
2477     draw_end (&ps->hdrctx);
2478 }
2479 
2480 gboolean
ddb_listview_header_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)2481 ddb_listview_header_draw                 (GtkWidget       *widget,
2482                                         cairo_t *cr,
2483                                         gpointer         user_data) {
2484     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2485     // FIXME: clip region
2486     cairo_set_line_width (cr, 1);
2487     cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE);
2488     GtkAllocation a;
2489     gtk_widget_get_allocation (widget, &a);
2490     ddb_listview_header_expose (ps, cr, 0, 0, a.width, a.height);
2491     return FALSE;
2492 }
2493 
2494 
2495 #if !GTK_CHECK_VERSION(3,0,0)
2496 gboolean
ddb_listview_header_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer user_data)2497 ddb_listview_header_expose_event                 (GtkWidget       *widget,
2498                                         GdkEventExpose  *event,
2499                                         gpointer         user_data)
2500 {
2501     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2502     cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget));
2503     ddb_listview_header_expose (ps, cr, event->area.x, event->area.y, event->area.width, event->area.height);
2504     cairo_destroy (cr);
2505     return FALSE;
2506 }
2507 #endif
2508 
2509 void
ddb_listview_header_update_fonts(DdbListview * ps)2510 ddb_listview_header_update_fonts (DdbListview *ps)
2511 {
2512     draw_init_font (&ps->hdrctx, DDB_COLUMN_FONT, 1);
2513     int height = draw_get_listview_rowheight (&ps->hdrctx);
2514     GtkAllocation a;
2515     gtk_widget_get_allocation (ps->header, &a);
2516     if (height != a.height) {
2517         gtk_widget_set_size_request (ps->header, -1, height);
2518     }
2519 }
2520 
2521 void
ddb_listview_column_size_changed(DdbListview * listview,int col)2522 ddb_listview_column_size_changed (DdbListview *listview, int col)
2523 {
2524     if (ddb_listview_is_album_art_column_idx(listview, col)) {
2525         ddb_listview_resize_groups (listview);
2526         if (listview->scrollpos > 0) {
2527             int pos = ddb_listview_get_row_pos (listview, listview->ref_point);
2528             gtk_range_set_value (GTK_RANGE (listview->scrollbar), pos - listview->ref_point_offset);
2529         }
2530     }
2531 }
2532 
2533 void
ddb_listview_update_scroll_ref_point(DdbListview * ps)2534 ddb_listview_update_scroll_ref_point (DdbListview *ps)
2535 {
2536     ddb_listview_groupcheck (ps);
2537     DdbListviewGroup *grp = ps->groups;
2538     DdbListviewGroup *grp_next;
2539 
2540     if (grp && ps->scrollpos > 0) {
2541         int abs_idx = 0;
2542         int grp_y = 0;
2543 
2544         GtkAllocation a;
2545         gtk_widget_get_allocation (ps->list, &a);
2546         int cursor_pos = ddb_listview_get_row_pos (ps, ps->binding->cursor ());
2547         ps->ref_point = 0;
2548         ps->ref_point_offset = 0;
2549 
2550         // find 1st group
2551         while (grp && grp_y + grp->height < ps->scrollpos) {
2552             grp_y += grp->height;
2553             abs_idx += grp->num_items;
2554             grp = grp->next;
2555         }
2556         // choose cursor_pos as anchor
2557         if (ps->scrollpos < cursor_pos && cursor_pos < ps->scrollpos + a.height && cursor_pos < ps->fullheight) {
2558             ps->ref_point = ps->binding->cursor ();
2559             ps->ref_point_offset = cursor_pos - ps->scrollpos;
2560         }
2561         // choose first group as anchor
2562         else if (ps->scrollpos < grp_y + ps-> grouptitle_height + (grp->num_items * ps->rowheight) && grp_y + ps-> grouptitle_height + (grp->num_items * ps->rowheight) < ps->scrollpos + a.height) {
2563             ps->ref_point = abs_idx;
2564             ps->ref_point_offset = (grp_y + ps->grouptitle_height) - ps->scrollpos;
2565         }
2566         // choose next group as anchor
2567         else {
2568             grp_y += grp->height;
2569             abs_idx += grp->num_items;
2570             ps->ref_point = abs_idx;
2571             ps->ref_point_offset = (grp_y + ps->grouptitle_height) - ps->scrollpos;
2572         }
2573     }
2574 }
2575 
2576 void
ddb_listview_init_autoresize(DdbListview * ps,int totalwidth)2577 ddb_listview_init_autoresize (DdbListview *ps, int totalwidth)
2578 {
2579     if (totalwidth > 0) {
2580         DdbListviewColumn *c;
2581         if (!ps->col_autoresize) {
2582             for (c = ps->columns; c; c = c->next) {
2583                 c->fwidth = (float)c->width / (float)totalwidth;
2584             }
2585             ps->col_autoresize = 1;
2586         }
2587     }
2588 }
2589 
2590 gboolean
ddb_listview_header_configure_event(GtkWidget * widget,GdkEventConfigure * event,gpointer user_data)2591 ddb_listview_header_configure_event              (GtkWidget       *widget,
2592                                         GdkEventConfigure *event,
2593                                         gpointer         user_data)
2594 {
2595     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2596     ddb_listview_header_update_fonts (ps);
2597     GtkAllocation lva;
2598     gtk_widget_get_allocation (GTK_WIDGET (ps), &lva);
2599     int totalwidth = lva.width;
2600 
2601     // col_autoresize flag indicates whether fwidth is valid
2602     if (!ps->lock_columns) {
2603         DdbListviewColumn *c;
2604         if (deadbeef->conf_get_int ("gtkui.autoresize_columns", 0)) {
2605             if (ps->header_width != totalwidth) {
2606                 ddb_listview_update_scroll_ref_point (ps);
2607                 if (!ps->col_autoresize) {
2608                     for (c = ps->columns; c; c = c->next) {
2609                         c->fwidth = (float)c->width / (float)totalwidth;
2610                     }
2611                     ps->col_autoresize = 1;
2612                 }
2613                 // use the fwidth
2614                 int changed = 0;
2615                 int i = 0;
2616                 for (c = ps->columns; c; c = c->next, i++) {
2617                     int newwidth = totalwidth * c->fwidth;
2618                     if (newwidth != c->width) {
2619                         c->width = newwidth;
2620                         changed = 1;
2621                         ddb_listview_column_size_changed (ps, i);
2622                     }
2623                 }
2624                 if (changed) {
2625                     ps->binding->columns_changed (ps);
2626                 }
2627             }
2628         }
2629         else {
2630             for (c = ps->columns; c; c = c->next) {
2631                 c->fwidth = (float)c->width / (float)totalwidth;
2632             }
2633             ps->col_autoresize = 1;
2634         }
2635         ps->header_width = totalwidth;
2636     }
2637 
2638     return FALSE;
2639 }
2640 
2641 void
ddb_listview_lock_columns(DdbListview * lv,gboolean lock)2642 ddb_listview_lock_columns (DdbListview *lv, gboolean lock) {
2643     lv->lock_columns = lock;
2644 
2645     // NOTE: at this point, it's still not guaranteed that the allocation contains
2646     // the final size, so we don't calc initial autoresize state here
2647 }
2648 
2649 
2650 void
ddb_listview_header_realize(GtkWidget * widget,gpointer user_data)2651 ddb_listview_header_realize                      (GtkWidget       *widget,
2652                                         gpointer         user_data)
2653 {
2654     // create cursor for sizing headers
2655     DdbListview *listview = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2656     listview->cursor_sz = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
2657     listview->cursor_drag = gdk_cursor_new (GDK_FLEUR);
2658 }
2659 
2660 gboolean
ddb_listview_header_motion_notify_event(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)2661 ddb_listview_header_motion_notify_event          (GtkWidget       *widget,
2662                                         GdkEventMotion  *event,
2663                                         gpointer         user_data)
2664 {
2665     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2666     int ev_x, ev_y;
2667     GdkModifierType ev_state;
2668 
2669     ev_x = event->x;
2670     ev_y = event->y;
2671     ev_state = event->state;
2672 #if GTK_CHECK_VERSION(2,12,0)
2673     gdk_event_request_motions (event);
2674 #endif
2675 
2676     if ((ev_state & GDK_BUTTON1_MASK) && ps->header_prepare) {
2677         if (gtk_drag_check_threshold (widget, ev_x, ps->prev_header_x, 0, 0)) {
2678             ps->header_prepare = 0;
2679         }
2680     }
2681     if (!ps->header_prepare && ps->header_dragging >= 0) {
2682         gdk_window_set_cursor (gtk_widget_get_window (widget), ps->cursor_drag);
2683         DdbListviewColumn *c = ps->columns;
2684         for (int i = 0; c && i < ps->header_dragging; c = c->next, i++);
2685         const int left = ev_x - ps->header_dragpt[0] + ps->hscrollpos;
2686         const int right = left + c->width;
2687         DdbListviewColumn *cc = ps->columns;
2688         for (int xx = 0, ii = 0; cc; xx += cc->width, cc = cc->next, ii++) {
2689             if (ps->header_dragging > ii && left < xx + cc->width/2 || ps->header_dragging < ii && right > xx + cc->width/2) {
2690                 ddb_listview_column_move (ps, c, ii);
2691                 ps->header_dragging = ii;
2692                 gtk_widget_queue_draw (ps->list);
2693                 break;
2694             }
2695         }
2696         ps->col_movepos = left;
2697         gtk_widget_queue_draw (ps->header);
2698     }
2699     else if (ps->header_sizing >= 0) {
2700         ps->last_header_motion_ev = event->time;
2701         ps->prev_header_x = ev_x;
2702         gdk_window_set_cursor (gtk_widget_get_window (widget), ps->cursor_sz);
2703         // get column start pos
2704         int x = -ps->hscrollpos;
2705         int i = 0;
2706         int size = 0;
2707         DdbListviewColumn *c;
2708         for (c = ps->columns; c; c = c->next) {
2709             size += c->width;
2710         }
2711         for (c = ps->columns; c && i < ps->header_sizing; c = c->next, i++) {
2712             x += c->width;
2713         }
2714 
2715         int newx = ev_x > x + MIN_COLUMN_WIDTH ? ev_x : x + MIN_COLUMN_WIDTH;
2716         c->width = newx-x;
2717         if (ps->col_autoresize) {
2718             c->fwidth = (float)c->width / ps->header_width;
2719         }
2720         ps->block_redraw_on_scroll = 1;
2721         //ddb_listview_list_setup_vscroll (ps);
2722         ddb_listview_list_setup_hscroll (ps);
2723         ps->block_redraw_on_scroll = 0;
2724         ddb_listview_column_size_changed (ps, ps->header_sizing);
2725         ddb_listview_list_update_total_width (ps, size);
2726         gtk_widget_queue_draw (ps->header);
2727         gtk_widget_queue_draw (ps->list);
2728     }
2729     else {
2730         int x = -ps->hscrollpos;
2731         DdbListviewColumn *c;
2732         for (c = ps->columns; c; c = c->next) {
2733             int w = c->width;
2734             if (w > 0) { // ignore collapsed columns (hack for search window)
2735                 if (ev_x >= x + w - 4 && ev_x <= x + w) {
2736                     gdk_window_set_cursor (gtk_widget_get_window (widget), ps->cursor_sz);
2737                     break;
2738                 }
2739                 else {
2740                     gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
2741                 }
2742             }
2743             else {
2744                 gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
2745             }
2746             x += w;
2747         }
2748     }
2749     return FALSE;
2750 }
2751 
2752 static int
ddb_listview_header_get_column_idx_for_coord(DdbListview * pl,int click_x)2753 ddb_listview_header_get_column_idx_for_coord (DdbListview *pl, int click_x) {
2754     int x = -pl->hscrollpos;
2755     DdbListviewColumn *c;
2756     int idx = 0;
2757     for (c = pl->columns; c; c = c->next, idx++) {
2758         int w = c->width;
2759         if (click_x >= x && click_x < x + w) {
2760             return idx;
2761         }
2762         x += w;
2763     }
2764     return -1;
2765 }
2766 
2767 gboolean
ddb_listview_header_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)2768 ddb_listview_header_button_press_event           (GtkWidget       *widget,
2769                                         GdkEventButton  *event,
2770                                         gpointer         user_data)
2771 {
2772     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2773 //    ps->active_column = ddb_listview_header_get_column_for_coord (ps, event->x);
2774     if (TEST_LEFT_CLICK (event)) {
2775         ddb_listview_update_scroll_ref_point (ps);
2776 
2777         // start sizing/dragging
2778         ps->header_dragging = -1;
2779         ps->header_sizing = -1;
2780         ps->header_dragpt[0] = event->x;
2781         ps->header_dragpt[1] = event->y;
2782         int x = -ps->hscrollpos;
2783         int i = 0;
2784         DdbListviewColumn *c;
2785         for (c = ps->columns; c; c = c->next, i++) {
2786             int w = c->width;
2787             if (event->x >= x + w - 4 && event->x <= x + w) {
2788                 ps->header_sizing = i;
2789                 ps->header_dragging = -1;
2790                 break;
2791             }
2792             else if (event->x > x && event->x < x + w - 4) {
2793                 // prepare to drag or sort
2794                 ps->header_dragpt[0] = event->x - x;
2795                 ps->header_prepare = 1;
2796                 ps->header_dragging = i;
2797                 ps->header_sizing = -1;
2798                 ps->prev_header_x = event->x;
2799                 break;
2800             }
2801             x += w;
2802         }
2803     }
2804     else if (TEST_RIGHT_CLICK (event)) {
2805         int idx = ddb_listview_header_get_column_idx_for_coord (ps, event->x);
2806         ps->binding->header_context_menu (ps, idx);
2807     }
2808     ps->prev_header_x = -1;
2809     ps->last_header_motion_ev = -1;
2810     return TRUE;
2811 }
2812 
2813 gboolean
ddb_listview_header_button_release_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)2814 ddb_listview_header_button_release_event         (GtkWidget       *widget,
2815                                         GdkEventButton  *event,
2816                                         gpointer         user_data)
2817 {
2818     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2819     if (event->button == 1) {
2820         if (ps->header_prepare) {
2821             ps->header_sizing = -1;
2822             ps->header_dragging = -1;
2823             ps->header_prepare = 0;
2824             // sort
2825             DdbListviewColumn *c;
2826             int i = 0;
2827             int x = -ps->hscrollpos;
2828             int sorted = 0;
2829             for (c = ps->columns; c; c = c->next, i++) {
2830                 int w = c->width;
2831                 if (event->x > x + 2 && event->x < x + w - 2) {
2832                     int sort_order = c->sort_order;
2833                     if (!sort_order) {
2834                         c->sort_order = 1;
2835                     }
2836                     else if (sort_order == 1) {
2837                         c->sort_order = 2;
2838                     }
2839                     else if (sort_order == 2) {
2840                         c->sort_order = 1;
2841                     }
2842                     ps->binding->col_sort (i, c->sort_order-1, c->user_data);
2843                     sorted = 1;
2844                 }
2845                 else {
2846                     c->sort_order = 0;
2847                 }
2848                 x += w;
2849             }
2850             ddb_listview_refresh (ps, DDB_REFRESH_LIST | DDB_REFRESH_COLUMNS);
2851         }
2852         else {
2853             ps->header_sizing = -1;
2854             int x = 0;
2855             DdbListviewColumn *c;
2856             for (c = ps->columns; c; c = c->next) {
2857                 int w = c->width;
2858                 if (event->x >= x + w - 4 && event->x <= x + w) {
2859                     gdk_window_set_cursor (gtk_widget_get_window (widget), ps->cursor_sz);
2860                     break;
2861                 }
2862                 else {
2863                     gdk_window_set_cursor (gtk_widget_get_window (widget), NULL);
2864                 }
2865                 x += w;
2866             }
2867             if (ps->header_dragging >= 0) {
2868                 ps->header_dragging = -1;
2869                 ddb_listview_refresh (ps, DDB_REFRESH_LIST | DDB_REFRESH_COLUMNS | DDB_REFRESH_HSCROLL);
2870             }
2871         }
2872         ps->binding->columns_changed (ps);
2873         int size = 0;
2874         DdbListviewColumn *c;
2875         for (c = ps->columns; c; c = c->next) {
2876             size += c->width;
2877         }
2878         ddb_listview_list_update_total_width (ps, size);
2879     }
2880     return FALSE;
2881 }
2882 
2883 struct set_cursor_t {
2884     int cursor;
2885     int prev;
2886     DdbListview *pl;
2887     int noscroll;
2888 };
2889 
2890 static gboolean
ddb_listview_set_cursor_cb(gpointer data)2891 ddb_listview_set_cursor_cb (gpointer data) {
2892     struct set_cursor_t *sc = (struct set_cursor_t *)data;
2893 
2894     DdbListviewIter prev_it = sc->pl->binding->get_for_idx (sc->prev);
2895     sc->pl->binding->set_cursor (sc->cursor);
2896     int prev_selected = 0;
2897 
2898     if (prev_it) {
2899         prev_selected = sc->pl->binding->is_selected (prev_it);
2900     }
2901 
2902     ddb_listview_select_single (sc->pl, sc->cursor);
2903 
2904     if (prev_it && !prev_selected) {
2905         ddb_listview_draw_row (sc->pl, sc->prev, prev_it);
2906     }
2907 
2908     if (prev_it) {
2909         sc->pl->binding->unref (prev_it);
2910     }
2911 
2912     if (!sc->noscroll) {
2913         DdbListview *ps = sc->pl;
2914 
2915         int cursor_scroll = ddb_listview_get_row_pos (sc->pl, sc->cursor);
2916         int newscroll = sc->pl->scrollpos;
2917         GtkAllocation a;
2918         gtk_widget_get_allocation (sc->pl->list, &a);
2919         if (!gtkui_groups_pinned && cursor_scroll < sc->pl->scrollpos) {
2920              newscroll = cursor_scroll;
2921         }
2922         else if (gtkui_groups_pinned && cursor_scroll < sc->pl->scrollpos + ps->grouptitle_height) {
2923             newscroll = cursor_scroll - ps->grouptitle_height;
2924         }
2925         else if (cursor_scroll + sc->pl->rowheight >= sc->pl->scrollpos + a.height) {
2926             newscroll = cursor_scroll + sc->pl->rowheight - a.height + 1;
2927             if (newscroll < 0) {
2928                 newscroll = 0;
2929             }
2930         }
2931         if (sc->pl->scrollpos != newscroll) {
2932             GtkWidget *range = sc->pl->scrollbar;
2933             gtk_range_set_value (GTK_RANGE (range), newscroll);
2934         }
2935 
2936         free (data);
2937     }
2938     return FALSE;
2939 }
2940 
2941 void
ddb_listview_set_cursor(DdbListview * pl,int cursor)2942 ddb_listview_set_cursor (DdbListview *pl, int cursor) {
2943     int prev = pl->binding->cursor ();
2944     struct set_cursor_t *data = malloc (sizeof (struct set_cursor_t));
2945     data->prev = prev;
2946     data->cursor = cursor;
2947     data->pl = pl;
2948     data->noscroll = 0;
2949     g_idle_add (ddb_listview_set_cursor_cb, data);
2950 }
2951 
2952 void
ddb_listview_set_cursor_noscroll(DdbListview * pl,int cursor)2953 ddb_listview_set_cursor_noscroll (DdbListview *pl, int cursor) {
2954     int prev = pl->binding->cursor ();
2955     struct set_cursor_t *data = malloc (sizeof (struct set_cursor_t));
2956     data->prev = prev;
2957     data->cursor = cursor;
2958     data->pl = pl;
2959     data->noscroll = 1;
2960     g_idle_add (ddb_listview_set_cursor_cb, data);
2961 }
2962 
2963 gboolean
ddb_listview_list_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)2964 ddb_listview_list_button_press_event         (GtkWidget       *widget,
2965                                         GdkEventButton  *event,
2966                                         gpointer         user_data)
2967 {
2968     gtk_widget_grab_focus (widget);
2969     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
2970     if (TEST_LEFT_CLICK (event)) {
2971         ddb_listview_list_mouse1_pressed (ps, event->state, event->x, event->y, event->type);
2972     }
2973     else if (TEST_RIGHT_CLICK(event)) {
2974         // get item under cursor
2975         DdbListviewGroup *grp;
2976         int grp_index;
2977         int sel;
2978         DdbListviewIter it = NULL;
2979         int prev = ps->binding->cursor ();
2980         if (ddb_listview_list_pickpoint_y (ps, event->y + ps->scrollpos, &grp, &grp_index, &sel) != -1) {
2981             if (sel != -1) {
2982                 ps->binding->set_cursor (sel);
2983             }
2984             ddb_listview_click_selection (ps, event->x, event->y, grp, grp_index, sel, 0, event->button);
2985             if (sel == -1 && grp_index < grp->num_items) {
2986                 sel = ps->binding->get_idx (grp->head);
2987             }
2988             if (sel != -1) {
2989                 it = ps->binding->get_for_idx (sel);
2990             }
2991         }
2992         if (it) {
2993             ps->binding->list_context_menu (ps, it, sel);
2994         }
2995         int cursor = ps->binding->cursor ();
2996         if (cursor != -1 && sel != -1) {
2997             DdbListviewIter it = ps->binding->get_for_idx (cursor);
2998             ddb_listview_draw_row (ps, cursor, it);
2999             UNREF (it);
3000         }
3001         if (prev != -1 && prev != cursor) {
3002             DdbListviewIter it = ps->binding->get_for_idx (prev);
3003             ddb_listview_draw_row (ps, prev, it);
3004             UNREF (it);
3005         }
3006         if (it) {
3007             UNREF (it);
3008         }
3009     }
3010     return TRUE;
3011 }
3012 
3013 gboolean
ddb_listview_list_button_release_event(GtkWidget * widget,GdkEventButton * event,gpointer user_data)3014 ddb_listview_list_button_release_event       (GtkWidget       *widget,
3015                                         GdkEventButton  *event,
3016                                         gpointer         user_data)
3017 {
3018     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
3019     if (event->button == 1) {
3020         ddb_listview_list_mouse1_released (ps, event->state, event->x, event->y, event->time);
3021     }
3022     return FALSE;
3023 }
3024 
3025 gboolean
ddb_listview_motion_notify_event(GtkWidget * widget,GdkEventMotion * event,gpointer user_data)3026 ddb_listview_motion_notify_event        (GtkWidget       *widget,
3027                                         GdkEventMotion  *event,
3028                                         gpointer         user_data)
3029 {
3030     int x = event->x;
3031     int y = event->y;
3032 #if GTK_CHECK_VERSION(2,12,0)
3033     gdk_event_request_motions (event);
3034 #endif
3035     DdbListview *ps = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
3036     ddb_listview_list_mousemove (ps, event, x, y);
3037     return FALSE;
3038 }
3039 
3040 void
ddb_listview_set_binding(DdbListview * listview,DdbListviewBinding * binding)3041 ddb_listview_set_binding (DdbListview *listview, DdbListviewBinding *binding) {
3042     listview->binding = binding;
3043 }
3044 
3045 DdbListviewIter
ddb_listview_get_iter_from_coord(DdbListview * listview,int x,int y)3046 ddb_listview_get_iter_from_coord (DdbListview *listview, int x, int y) {
3047     DdbListviewGroup *grp;
3048     int grp_index;
3049     int sel;
3050     DdbListviewIter it = NULL;
3051     if (ddb_listview_list_pickpoint_y (listview, y + listview->scrollpos, &grp, &grp_index, &sel) != -1) {
3052         if (sel == -1) {
3053             sel = listview->binding->get_idx (grp->head);
3054         }
3055         it = listview->binding->get_for_idx (sel);
3056     }
3057     return it;
3058 }
3059 
3060 void
ddb_listview_scroll_to(DdbListview * listview,int pos)3061 ddb_listview_scroll_to (DdbListview *listview, int pos) {
3062     pos = ddb_listview_get_row_pos (listview, pos);
3063     GtkAllocation a;
3064     gtk_widget_get_allocation (listview->list, &a);
3065     if (pos < listview->scrollpos || pos + listview->rowheight >= listview->scrollpos + a.height) {
3066         gtk_range_set_value (GTK_RANGE (listview->scrollbar), pos - a.height/2);
3067     }
3068 }
3069 
3070 int
ddb_listview_is_scrolling(DdbListview * listview)3071 ddb_listview_is_scrolling (DdbListview *listview) {
3072     return listview->dragwait;
3073 }
3074 
3075 /////// column management code
3076 
3077 DdbListviewColumn *
ddb_listview_column_alloc(const char * title,int width,int align_right,int minheight,int color_override,GdkColor color,void * user_data)3078 ddb_listview_column_alloc (const char *title, int width, int align_right, int minheight, int color_override, GdkColor color, void *user_data) {
3079     DdbListviewColumn * c = malloc (sizeof (DdbListviewColumn));
3080     memset (c, 0, sizeof (DdbListviewColumn));
3081     c->title = strdup (title);
3082     c->width = width;
3083     c->align_right = align_right;
3084     c->color_override = color_override;
3085     c->color = color;
3086     c->minheight = minheight;
3087     c->user_data = user_data;
3088     return c;
3089 }
3090 
3091 int
ddb_listview_column_get_count(DdbListview * listview)3092 ddb_listview_column_get_count (DdbListview *listview) {
3093     int cnt = 0;
3094     DdbListviewColumn *c = listview->columns;
3095     while (c) {
3096         cnt++;
3097         c = c->next;
3098     }
3099     return cnt;
3100 }
3101 
3102 void
ddb_listview_column_append(DdbListview * listview,const char * title,int width,int align_right,int minheight,int color_override,GdkColor color,void * user_data)3103 ddb_listview_column_append (DdbListview *listview, const char *title, int width, int align_right, int minheight, int color_override, GdkColor color, void *user_data) {
3104     DdbListviewColumn* c = ddb_listview_column_alloc (title, width, align_right, minheight, color_override, color, user_data);
3105     if (listview->col_autoresize) {
3106         c->fwidth = (float)c->width / listview->header_width;
3107     }
3108     int idx = 0;
3109     DdbListviewColumn * columns = listview->columns;
3110     if (columns) {
3111         idx++;
3112         DdbListviewColumn * tail = listview->columns;
3113         while (tail->next) {
3114             tail = tail->next;
3115             idx++;
3116         }
3117         tail->next = c;
3118     }
3119     else {
3120         listview->columns = c;
3121     }
3122     listview->binding->columns_changed (listview);
3123 }
3124 
3125 void
ddb_listview_column_insert(DdbListview * listview,int before,const char * title,int width,int align_right,int minheight,int color_override,GdkColor color,void * user_data)3126 ddb_listview_column_insert (DdbListview *listview, int before, const char *title, int width, int align_right, int minheight, int color_override, GdkColor color, void *user_data) {
3127     DdbListviewColumn *c = ddb_listview_column_alloc (title, width, align_right, minheight, color_override, color, user_data);
3128     if (listview->col_autoresize) {
3129         c->fwidth = (float)c->width / listview->header_width;
3130     }
3131     if (listview->columns) {
3132         DdbListviewColumn * prev = NULL;
3133         DdbListviewColumn * next = listview->columns;
3134         int idx = 0;
3135         while (next) {
3136             if (idx == before) {
3137                 break;
3138             }
3139             prev = next;
3140             next = next->next;
3141             idx++;
3142         }
3143         c->next = next;
3144         if (prev) {
3145             prev->next = c;
3146         }
3147         else {
3148             listview->columns = c;
3149         }
3150     }
3151     else {
3152         listview->columns = c;
3153     }
3154     listview->binding->columns_changed (listview);
3155 }
3156 
3157 void
ddb_listview_column_free(DdbListview * listview,DdbListviewColumn * c)3158 ddb_listview_column_free (DdbListview *listview, DdbListviewColumn * c) {
3159     if (c->title) {
3160         free (c->title);
3161     }
3162     listview->binding->col_free_user_data (c->user_data);
3163     free (c);
3164 }
3165 
3166 void
ddb_listview_column_remove(DdbListview * listview,int idx)3167 ddb_listview_column_remove (DdbListview *listview, int idx) {
3168     DdbListviewColumn *c;
3169     if (idx == 0) {
3170         c = listview->columns;
3171         assert (c);
3172         listview->columns = c->next;
3173         ddb_listview_column_free (listview, c);
3174         listview->binding->columns_changed (listview);
3175         return;
3176     }
3177     c = listview->columns;
3178     int i = 0;
3179     while (c) {
3180         if (i+1 == idx) {
3181             assert (c->next);
3182             DdbListviewColumn *next = c->next->next;
3183             ddb_listview_column_free (listview, c->next);
3184             c->next = next;
3185             listview->binding->columns_changed (listview);
3186             return;
3187         }
3188         c = c->next;
3189         i++;
3190     }
3191 
3192     if (!c) {
3193         trace ("ddblv: attempted to remove column that is not in list\n");
3194     }
3195 }
3196 
3197 void
ddb_listview_column_move(DdbListview * listview,DdbListviewColumn * which,int inspos)3198 ddb_listview_column_move (DdbListview *listview, DdbListviewColumn *which, int inspos) {
3199     // remove c from list
3200     DdbListviewColumn *c = (DdbListviewColumn *)which;
3201     if (c == listview->columns) {
3202         listview->columns = c->next;
3203     }
3204     else {
3205         DdbListviewColumn *cc;
3206         for (cc = listview->columns; cc; cc = cc->next) {
3207             if (cc->next == c) {
3208                 cc->next = c->next;
3209                 break;
3210             }
3211         }
3212     }
3213     c->next = NULL;
3214     // reinsert c at position inspos update header_dragging to new idx
3215     if (inspos == 0) {
3216         c->next = listview->columns;
3217         listview->columns = c;
3218     }
3219     else {
3220         int idx = 0;
3221         DdbListviewColumn *prev = NULL;
3222         DdbListviewColumn *cc = NULL;
3223         for (cc = listview->columns; cc; cc = cc->next, idx++, prev = cc) {
3224             if (idx+1 == inspos) {
3225                 DdbListviewColumn *next = cc->next;
3226                 cc->next = c;
3227                 c->next = next;
3228                 break;
3229             }
3230         }
3231     }
3232     listview->binding->columns_changed (listview);
3233 }
3234 
3235 int
ddb_listview_column_get_info(DdbListview * listview,int col,const char ** title,int * width,int * align_right,int * minheight,int * color_override,GdkColor * color,void ** user_data)3236 ddb_listview_column_get_info (DdbListview *listview, int col, const char **title, int *width, int *align_right, int *minheight, int *color_override, GdkColor *color, void **user_data) {
3237     DdbListviewColumn *c;
3238     int idx = 0;
3239     for (c = listview->columns; c; c = c->next, idx++) {
3240         if (idx == col) {
3241             *title = c->title;
3242             *width = c->width;
3243             *align_right = c->align_right;
3244             *minheight = c->minheight;
3245             *color_override = c->color_override;
3246             *color = c->color;
3247             *user_data = c->user_data;
3248             return 0;
3249         }
3250     }
3251     return -1;
3252 }
3253 
3254 int
ddb_listview_column_set_info(DdbListview * listview,int col,const char * title,int width,int align_right,int minheight,int color_override,GdkColor color,void * user_data)3255 ddb_listview_column_set_info (DdbListview *listview, int col, const char *title, int width, int align_right, int minheight, int color_override, GdkColor color, void *user_data) {
3256     DdbListviewColumn *c;
3257     int idx = 0;
3258     for (c = listview->columns; c; c = c->next, idx++) {
3259         if (idx == col) {
3260             free (c->title);
3261             c->title = strdup (title);
3262             c->width = width;
3263             if (listview->col_autoresize) {
3264                 c->fwidth = (float)c->width / listview->header_width;
3265             }
3266             c->align_right = align_right;
3267             c->minheight = minheight;
3268             c->color_override = color_override;
3269             c->color = color;
3270             c->user_data = user_data;
3271             listview->binding->columns_changed (listview);
3272             return 0;
3273         }
3274     }
3275     return -1;
3276 }
3277 /////// end of column management code
3278 
3279 /////// grouping /////
3280 void
ddb_listview_free_groups(DdbListview * listview)3281 ddb_listview_free_groups (DdbListview *listview) {
3282     DdbListviewGroup *next;
3283     while (listview->groups) {
3284         next = listview->groups->next;
3285         if (listview->groups->head) {
3286             listview->binding->unref (listview->groups->head);
3287         }
3288         free (listview->groups);
3289         listview->groups = next;
3290     }
3291     if (listview->plt) {
3292         deadbeef->plt_unref (listview->plt);
3293         listview->plt = NULL;
3294     }
3295 }
3296 
3297 void
ddb_listview_build_groups(DdbListview * listview)3298 ddb_listview_build_groups (DdbListview *listview) {
3299     deadbeef->pl_lock ();
3300     int old_height = listview->fullheight;
3301     listview->groups_build_idx = listview->binding->modification_idx ();
3302     ddb_listview_free_groups (listview);
3303 
3304     listview->plt = deadbeef->plt_get_curr ();
3305 
3306     listview->fullheight = 0;
3307 
3308     DdbListviewGroup *grp = NULL;
3309     char str[1024];
3310     char curr[1024];
3311 
3312     int min_height= 0;
3313     DdbListviewColumn *c;
3314     for (c = listview->columns; c; c = c->next) {
3315         if (c->minheight && c->width > min_height) {
3316             min_height = c->width;
3317         }
3318     }
3319 
3320     listview->grouptitle_height = listview->calculated_grouptitle_height;
3321     DdbListviewIter it = listview->binding->head ();
3322     while (it) {
3323         int res = listview->binding->get_group (listview, it, curr, sizeof (curr));
3324         if (res == -1) {
3325             grp = malloc (sizeof (DdbListviewGroup));
3326             listview->groups = grp;
3327             memset (grp, 0, sizeof (DdbListviewGroup));
3328             grp->head = it;
3329             grp->num_items = listview->binding->count ();
3330             listview->grouptitle_height = 0;
3331             grp->height = listview->grouptitle_height + grp->num_items * listview->rowheight;
3332             listview->fullheight = grp->height;
3333             listview->fullheight += listview->grouptitle_height;
3334             deadbeef->pl_unlock ();
3335             if (old_height != listview->fullheight) {
3336                 ddb_listview_refresh (listview, DDB_REFRESH_VSCROLL);
3337             }
3338             return;
3339         }
3340         if (!grp || strcmp (str, curr)) {
3341             strcpy (str, curr);
3342             DdbListviewGroup *newgroup = malloc (sizeof (DdbListviewGroup));
3343             if (grp) {
3344                 if (grp->height - listview->grouptitle_height < min_height) {
3345                     grp->height = min_height + listview->grouptitle_height;
3346                 }
3347                 listview->fullheight += grp->height;
3348                 grp->next = newgroup;
3349             }
3350             else {
3351                 listview->groups = newgroup;
3352             }
3353             grp = newgroup;
3354             memset (grp, 0, sizeof (DdbListviewGroup));
3355             grp->head = it;
3356             listview->binding->ref (it);
3357             grp->num_items = 0;
3358             grp->height = listview->grouptitle_height;
3359         }
3360         grp->height += listview->rowheight;
3361         grp->num_items++;
3362         DdbListviewIter next = listview->binding->next (it);
3363         listview->binding->unref (it);
3364         it = next;
3365     }
3366     if (grp) {
3367         if (grp->height - listview->grouptitle_height < min_height) {
3368             grp->height = min_height + listview->grouptitle_height;
3369         }
3370         listview->fullheight += grp->height;
3371     }
3372     deadbeef->pl_unlock ();
3373     if (old_height != listview->fullheight) {
3374         ddb_listview_refresh (listview, DDB_REFRESH_VSCROLL);
3375     }
3376 }
3377 
3378 void
ddb_listview_resize_groups(DdbListview * listview)3379 ddb_listview_resize_groups (DdbListview *listview) {
3380     deadbeef->pl_lock ();
3381     int old_height = listview->fullheight;
3382     int grp_height_old = 0;
3383     listview->fullheight = 0;
3384 
3385     int min_height= 0;
3386     DdbListviewColumn *c;
3387     for (c = listview->columns; c; c = c->next) {
3388         if (c->minheight && c->width > min_height) {
3389             min_height = c->width;
3390         }
3391     }
3392 
3393     DdbListviewGroup *grp = listview->groups;
3394     while (grp) {
3395         grp->height = listview->grouptitle_height + grp->num_items * listview->rowheight;
3396         if (grp->height - listview->grouptitle_height < min_height) {
3397             grp_height_old = grp->height;
3398             grp->height = min_height + listview->grouptitle_height;
3399         }
3400         listview->fullheight += grp->height;
3401         grp = grp->next;
3402     }
3403 
3404     deadbeef->pl_unlock ();
3405     if (old_height != listview->fullheight) {
3406         ddb_listview_refresh (listview, DDB_REFRESH_VSCROLL);
3407     }
3408 }
3409 
3410 void
ddb_listview_set_vscroll(DdbListview * listview,int scroll)3411 ddb_listview_set_vscroll (DdbListview *listview, int scroll) {
3412     GtkAdjustment *adj = gtk_range_get_adjustment (GTK_RANGE (listview->scrollbar));
3413     gtk_range_set_value (GTK_RANGE (listview->scrollbar), scroll);
3414 }
3415 
3416 void
ddb_listview_show_header(DdbListview * listview,int show)3417 ddb_listview_show_header (DdbListview *listview, int show) {
3418     show ? gtk_widget_show (listview->header) : gtk_widget_hide (listview->header);
3419 }
3420 
3421 void
ddb_listview_clear_sort(DdbListview * listview)3422 ddb_listview_clear_sort (DdbListview *listview) {
3423     DdbListviewColumn *c;
3424     for (c = listview->columns; c; c = c->next) {
3425         c->sort_order = 0;
3426     }
3427     gtk_widget_queue_draw (listview->header);
3428 }
3429 
3430 gboolean
ddb_listview_list_key_press_event(GtkWidget * widget,GdkEventKey * event,gpointer user_data)3431 ddb_listview_list_key_press_event (GtkWidget *widget, GdkEventKey *event, gpointer user_data) {
3432     DdbListview *listview = DDB_LISTVIEW (g_object_get_data (G_OBJECT (widget), "owner"));
3433     if (!ddb_listview_handle_keypress (listview, event->keyval, event->state)) {
3434         return on_mainwin_key_press_event (widget, event, user_data);
3435     }
3436     return TRUE;
3437 }
3438 
3439 void
ddb_listview_cancel_autoredraw(DdbListview * listview)3440 ddb_listview_cancel_autoredraw (DdbListview *listview) {
3441     if (listview->tf_redraw_timeout_id) {
3442         g_source_remove (listview->tf_redraw_timeout_id);
3443         listview->tf_redraw_timeout_id = 0;
3444     }
3445     if (listview->tf_redraw_track) {
3446         listview->binding->unref (listview->tf_redraw_track);
3447         listview->tf_redraw_track = NULL;
3448     }
3449 }
3450