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 = >k_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 = >k_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 = >k_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 = >k_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