comparison src/skins/ui_skinned_playlist.c @ 2620:2d6e08c81c09

add playlistwindow
author Tomasz Mon <desowin@gmail.com>
date Wed, 21 May 2008 21:35:11 +0200
parents
children 2ce9e17525dc
comparison
equal deleted inserted replaced
2619:a066a10b2398 2620:2d6e08c81c09
1 /*
2 * Audacious - a cross-platform multimedia player
3 * Copyright (c) 2007 Tomasz Moń
4 * Copyright (c) 2008 William Pitcock
5 *
6 * Based on:
7 * BMP - Cross-platform multimedia player
8 * Copyright (C) 2003-2004 BMP development team.
9 *
10 * XMMS:
11 * Copyright (C) 1998-2003 XMMS development team.
12 *
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; under version 3 of the License.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses>.
24 *
25 * The Audacious team does not consider modular code linking to
26 * Audacious or using our public API to be a derived work.
27 */
28
29 /*
30 * A note about Pango and some funky spacey fonts: Weirdly baselined
31 * fonts, or fonts with weird ascents or descents _will_ display a
32 * little bit weird in the playlist widget, but the display engine
33 * won't make it look too bad, just a little deranged. I honestly
34 * don't think it's worth fixing (around...), it doesn't have to be
35 * perfectly fitting, just the general look has to be ok, which it
36 * IMHO is.
37 *
38 * A second note: The numbers aren't perfectly aligned, but in the
39 * end it looks better when using a single Pango layout for each
40 * number.
41 */
42
43 #include "ui_skinned_playlist.h"
44
45 #include "debug.h"
46 #if 0
47 #include "ui_fileinfopopup.h"
48 #endif
49 #include "ui_playlist.h"
50 #include "ui_manager.h"
51 #include "ui_skin.h"
52 #include "util.h"
53 #include "skins_cfg.h"
54 #include <audacious/plugin.h>
55
56 static PangoFontDescription *playlist_list_font = NULL;
57 static gint ascent, descent, width_delta_digit_one;
58 static gboolean has_slant;
59 static guint padding;
60
61 /* FIXME: the following globals should not be needed. */
62 static gint width_approx_letters;
63 static gint width_colon, width_colon_third;
64 static gint width_approx_digits, width_approx_digits_half;
65
66 #define UI_SKINNED_PLAYLIST_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), ui_skinned_playlist_get_type(), UiSkinnedPlaylistPrivate))
67 typedef struct _UiSkinnedPlaylistPrivate UiSkinnedPlaylistPrivate;
68
69 enum {
70 REDRAW,
71 LAST_SIGNAL
72 };
73
74 struct _UiSkinnedPlaylistPrivate {
75 SkinPixmapId skin_index;
76 gint width, height;
77 gint resize_width, resize_height;
78 gint drag_pos;
79 gboolean dragging, auto_drag_down, auto_drag_up;
80 gint auto_drag_up_tag, auto_drag_down_tag;
81 };
82
83 static void ui_skinned_playlist_class_init (UiSkinnedPlaylistClass *klass);
84 static void ui_skinned_playlist_init (UiSkinnedPlaylist *playlist);
85 static void ui_skinned_playlist_destroy (GtkObject *object);
86 static void ui_skinned_playlist_realize (GtkWidget *widget);
87 static void ui_skinned_playlist_size_request (GtkWidget *widget, GtkRequisition *requisition);
88 static void ui_skinned_playlist_size_allocate (GtkWidget *widget, GtkAllocation *allocation);
89 static gboolean ui_skinned_playlist_expose (GtkWidget *widget, GdkEventExpose *event);
90 static gboolean ui_skinned_playlist_button_press (GtkWidget *widget, GdkEventButton *event);
91 static gboolean ui_skinned_playlist_button_release (GtkWidget *widget, GdkEventButton *event);
92 static gboolean ui_skinned_playlist_motion_notify (GtkWidget *widget, GdkEventMotion *event);
93 static gboolean ui_skinned_playlist_leave_notify (GtkWidget *widget, GdkEventCrossing *event);
94 static void ui_skinned_playlist_redraw (UiSkinnedPlaylist *playlist);
95 static gboolean ui_skinned_playlist_popup_show (gpointer data);
96 static void ui_skinned_playlist_popup_hide (GtkWidget *widget);
97 static void ui_skinned_playlist_popup_timer_start (GtkWidget *widget);
98 static void ui_skinned_playlist_popup_timer_stop (GtkWidget *widget);
99
100 static GtkWidgetClass *parent_class = NULL;
101 static guint playlist_signals[LAST_SIGNAL] = { 0 };
102
103 GType ui_skinned_playlist_get_type() {
104 static GType playlist_type = 0;
105 if (!playlist_type) {
106 static const GTypeInfo playlist_info = {
107 sizeof (UiSkinnedPlaylistClass),
108 NULL,
109 NULL,
110 (GClassInitFunc) ui_skinned_playlist_class_init,
111 NULL,
112 NULL,
113 sizeof (UiSkinnedPlaylist),
114 0,
115 (GInstanceInitFunc) ui_skinned_playlist_init,
116 };
117 playlist_type = g_type_register_static (GTK_TYPE_WIDGET, "UiSkinnedPlaylist", &playlist_info, 0);
118 }
119
120 return playlist_type;
121 }
122
123 static void ui_skinned_playlist_class_init(UiSkinnedPlaylistClass *klass) {
124 GObjectClass *gobject_class;
125 GtkObjectClass *object_class;
126 GtkWidgetClass *widget_class;
127
128 gobject_class = G_OBJECT_CLASS(klass);
129 object_class = (GtkObjectClass*) klass;
130 widget_class = (GtkWidgetClass*) klass;
131 parent_class = gtk_type_class (gtk_widget_get_type ());
132
133 object_class->destroy = ui_skinned_playlist_destroy;
134
135 widget_class->realize = ui_skinned_playlist_realize;
136 widget_class->expose_event = ui_skinned_playlist_expose;
137 widget_class->size_request = ui_skinned_playlist_size_request;
138 widget_class->size_allocate = ui_skinned_playlist_size_allocate;
139 widget_class->button_press_event = ui_skinned_playlist_button_press;
140 widget_class->button_release_event = ui_skinned_playlist_button_release;
141 widget_class->motion_notify_event = ui_skinned_playlist_motion_notify;
142 widget_class->leave_notify_event = ui_skinned_playlist_leave_notify;
143
144 klass->redraw = ui_skinned_playlist_redraw;
145
146 playlist_signals[REDRAW] =
147 g_signal_new ("redraw", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION,
148 G_STRUCT_OFFSET (UiSkinnedPlaylistClass, redraw), NULL, NULL,
149 gtk_marshal_VOID__VOID, G_TYPE_NONE, 0);
150
151 g_type_class_add_private (gobject_class, sizeof (UiSkinnedPlaylistPrivate));
152 }
153
154 static void ui_skinned_playlist_init(UiSkinnedPlaylist *playlist) {
155 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(playlist);
156 playlist->pressed = FALSE;
157 priv->resize_width = 0;
158 priv->resize_height = 0;
159 playlist->prev_selected = -1;
160 playlist->prev_min = -1;
161 playlist->prev_max = -1;
162
163 g_object_set_data(G_OBJECT(playlist), "timer_id", GINT_TO_POINTER(0));
164 g_object_set_data(G_OBJECT(playlist), "timer_active", GINT_TO_POINTER(0));
165 #if 0
166 GtkWidget *popup = fileinfopopup_create();
167 g_object_set_data(G_OBJECT(playlist), "popup", popup);
168 g_object_set_data(G_OBJECT(playlist), "popup_active", GINT_TO_POINTER(0));
169 g_object_set_data(G_OBJECT(playlist), "popup_position", GINT_TO_POINTER(-1));
170 #endif
171 }
172
173 GtkWidget* ui_skinned_playlist_new(GtkWidget *fixed, gint x, gint y, gint w, gint h) {
174
175 UiSkinnedPlaylist *hs = g_object_new (ui_skinned_playlist_get_type (), NULL);
176 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(hs);
177
178 hs->x = x;
179 hs->y = y;
180 priv->width = w;
181 priv->height = h;
182 priv->skin_index = SKIN_PLEDIT;
183
184 gtk_fixed_put(GTK_FIXED(fixed), GTK_WIDGET(hs), hs->x, hs->y);
185 gtk_widget_set_double_buffered(GTK_WIDGET(hs), TRUE);
186
187 return GTK_WIDGET(hs);
188 }
189
190 static void ui_skinned_playlist_destroy(GtkObject *object) {
191 UiSkinnedPlaylist *playlist;
192
193 g_return_if_fail (object != NULL);
194 g_return_if_fail (UI_SKINNED_IS_PLAYLIST (object));
195
196 playlist = UI_SKINNED_PLAYLIST (object);
197
198 if (GTK_OBJECT_CLASS (parent_class)->destroy)
199 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
200 }
201
202 static void ui_skinned_playlist_realize(GtkWidget *widget) {
203 UiSkinnedPlaylist *playlist;
204 GdkWindowAttr attributes;
205 gint attributes_mask;
206
207 g_return_if_fail (widget != NULL);
208 g_return_if_fail (UI_SKINNED_IS_PLAYLIST(widget));
209
210 GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
211 playlist = UI_SKINNED_PLAYLIST(widget);
212
213 attributes.x = widget->allocation.x;
214 attributes.y = widget->allocation.y;
215 attributes.width = widget->allocation.width;
216 attributes.height = widget->allocation.height;
217 attributes.wclass = GDK_INPUT_OUTPUT;
218 attributes.window_type = GDK_WINDOW_CHILD;
219 attributes.event_mask = gtk_widget_get_events(widget);
220 attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK |
221 GDK_LEAVE_NOTIFY_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK;
222 attributes.visual = gtk_widget_get_visual(widget);
223 attributes.colormap = gtk_widget_get_colormap(widget);
224
225 attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
226 widget->window = gdk_window_new(widget->parent->window, &attributes, attributes_mask);
227
228 widget->style = gtk_style_attach(widget->style, widget->window);
229 gdk_window_set_user_data(widget->window, widget);
230 }
231
232 static void ui_skinned_playlist_size_request(GtkWidget *widget, GtkRequisition *requisition) {
233 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(widget);
234
235 requisition->width = priv->width;
236 requisition->height = priv->height;
237 }
238
239 static void ui_skinned_playlist_size_allocate(GtkWidget *widget, GtkAllocation *allocation) {
240 UiSkinnedPlaylist *playlist = UI_SKINNED_PLAYLIST (widget);
241 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(playlist);
242
243 widget->allocation = *allocation;
244 if (GTK_WIDGET_REALIZED (widget))
245 gdk_window_move_resize(widget->window, widget->allocation.x, widget->allocation.y, allocation->width, allocation->height);
246
247 playlist->x = widget->allocation.x;
248 playlist->y = widget->allocation.y;
249
250 if (priv->height != widget->allocation.height || priv->width != widget->allocation.width) {
251 priv->width = priv->width + priv->resize_width;
252 priv->height = priv->height + priv->resize_height;
253 priv->resize_width = 0;
254 priv->resize_height = 0;
255 gtk_widget_queue_draw(widget);
256 }
257 }
258
259 static gboolean ui_skinned_playlist_auto_drag_down_func(gpointer data) {
260 UiSkinnedPlaylist *pl = UI_SKINNED_PLAYLIST(data);
261 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(data);
262
263 if (priv->auto_drag_down) {
264 ui_skinned_playlist_move_down(pl);
265 pl->first++;
266 playlistwin_update_list(aud_playlist_get_active());
267 return TRUE;
268 }
269 return FALSE;
270 }
271
272 static gboolean ui_skinned_playlist_auto_drag_up_func(gpointer data) {
273 UiSkinnedPlaylist *pl = UI_SKINNED_PLAYLIST(data);
274 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(data);
275
276 if (priv->auto_drag_up) {
277 ui_skinned_playlist_move_up(pl);
278 pl->first--;
279 playlistwin_update_list(aud_playlist_get_active());
280 return TRUE;
281
282 }
283 return FALSE;
284 }
285
286 void ui_skinned_playlist_move_up(UiSkinnedPlaylist * pl) {
287 GList *list;
288 Playlist *playlist = aud_playlist_get_active();
289
290 if (!playlist)
291 return;
292
293 PLAYLIST_LOCK(playlist);
294 if ((list = playlist->entries) == NULL) {
295 PLAYLIST_UNLOCK(playlist);
296 return;
297 }
298 if (PLAYLIST_ENTRY(list->data)->selected) {
299 /* We are at the top */
300 PLAYLIST_UNLOCK(playlist);
301 return;
302 }
303 while (list) {
304 if (PLAYLIST_ENTRY(list->data)->selected)
305 glist_moveup(list);
306 list = g_list_next(list);
307 }
308 PLAYLIST_INCR_SERIAL(playlist);
309 PLAYLIST_UNLOCK(playlist);
310 if (pl->prev_selected != -1)
311 pl->prev_selected--;
312 if (pl->prev_min != -1)
313 pl->prev_min--;
314 if (pl->prev_max != -1)
315 pl->prev_max--;
316 }
317
318 void ui_skinned_playlist_move_down(UiSkinnedPlaylist * pl) {
319 GList *list;
320 Playlist *playlist = aud_playlist_get_active();
321
322 if (!playlist)
323 return;
324
325 PLAYLIST_LOCK(playlist);
326
327 if (!(list = g_list_last(playlist->entries))) {
328 PLAYLIST_UNLOCK(playlist);
329 return;
330 }
331
332 if (PLAYLIST_ENTRY(list->data)->selected) {
333 /* We are at the bottom */
334 PLAYLIST_UNLOCK(playlist);
335 return;
336 }
337
338 while (list) {
339 if (PLAYLIST_ENTRY(list->data)->selected)
340 glist_movedown(list);
341 list = g_list_previous(list);
342 }
343
344 PLAYLIST_INCR_SERIAL(playlist);
345 PLAYLIST_UNLOCK(playlist);
346
347 if (pl->prev_selected != -1)
348 pl->prev_selected++;
349 if (pl->prev_min != -1)
350 pl->prev_min++;
351 if (pl->prev_max != -1)
352 pl->prev_max++;
353 }
354
355 static void
356 playlist_list_draw_string(cairo_t *cr, UiSkinnedPlaylist *pl,
357 PangoFontDescription * font,
358 gint line,
359 gint width,
360 const gchar * text,
361 guint ppos)
362 {
363 guint plist_length_int;
364 Playlist *playlist = aud_playlist_get_active();
365 PangoLayout *layout;
366
367 REQUIRE_LOCK(playlist->mutex);
368
369 cairo_new_path(cr);
370
371 if (config.show_numbers_in_pl) {
372 gchar *pos_string = g_strdup_printf(config.show_separator_in_pl == TRUE ? "%d" : "%d.", ppos);
373 plist_length_int =
374 gint_count_digits(aud_playlist_get_length(playlist)) + !config.show_separator_in_pl + 1; /* cf.show_separator_in_pl will be 0 if false */
375
376 padding = plist_length_int;
377 padding = ((padding + 1) * width_approx_digits);
378
379 layout = gtk_widget_create_pango_layout(playlistwin, pos_string);
380 pango_layout_set_font_description(layout, playlist_list_font);
381 pango_layout_set_width(layout, plist_length_int * 100);
382
383 pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
384
385 cairo_move_to(cr, (width_approx_digits *
386 (-1 + plist_length_int - strlen(pos_string))) +
387 (width_approx_digits / 4), (line - 1) * pl->fheight +
388 ascent + abs(descent));
389 pango_cairo_show_layout(cr, layout);
390
391 g_free(pos_string);
392 g_object_unref(layout);
393
394 if (!config.show_separator_in_pl)
395 padding -= (width_approx_digits * 1.5);
396 } else {
397 padding = 3;
398 }
399
400 width -= padding;
401
402 layout = gtk_widget_create_pango_layout(playlistwin, text);
403
404 pango_layout_set_font_description(layout, playlist_list_font);
405 pango_layout_set_width(layout, width * PANGO_SCALE);
406 pango_layout_set_single_paragraph_mode(layout, TRUE);
407 pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
408
409 cairo_move_to(cr, padding + (width_approx_letters / 4),
410 (line - 1) * pl->fheight +
411 ascent + abs(descent));
412 pango_cairo_show_layout(cr, layout);
413
414 g_object_unref(layout);
415 }
416
417 static gboolean ui_skinned_playlist_expose(GtkWidget *widget, GdkEventExpose *event) {
418 UiSkinnedPlaylist *pl = UI_SKINNED_PLAYLIST (widget);
419 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(pl);
420 g_return_val_if_fail (priv->width > 0 && priv->height > 0, FALSE);
421
422 Playlist *playlist = aud_playlist_get_active();
423 PlaylistEntry *entry;
424 GList *list;
425 PangoLayout *layout;
426 gchar *title;
427 gint width, height;
428 gint i, max_first;
429 guint padding, padding_dwidth, padding_plength;
430 guint max_time_len = 0;
431 gfloat queue_tailpadding = 0;
432 gint tpadding;
433 gsize tpadding_dwidth = 0;
434 gint x, y;
435 guint tail_width;
436 guint tail_len;
437 gboolean in_selection = FALSE;
438
439 gchar tail[100];
440 gchar queuepos[255];
441 gchar length[40];
442
443 gchar **frags;
444 gchar *frag0;
445
446 gint plw_w, plw_h;
447
448 cairo_t *cr;
449 gint yc;
450 gint pos;
451 gdouble rounding_offset;
452
453 g_return_val_if_fail (widget != NULL, FALSE);
454 g_return_val_if_fail (UI_SKINNED_IS_PLAYLIST (widget), FALSE);
455 g_return_val_if_fail (event != NULL, FALSE);
456
457 cr = gdk_cairo_create(widget->window);
458
459 width = priv->width;
460 height = priv->height;
461
462 plw_w = playlistwin_get_width();
463 plw_h = playlistwin_get_height();
464
465 gdk_cairo_set_source_color(cr, skin_get_color(aud_active_skin, SKIN_PLEDIT_NORMALBG));
466
467 cairo_rectangle(cr, 0, 0, width, height);
468 cairo_paint(cr);
469
470 if (!playlist_list_font) {
471 g_critical("Couldn't open playlist font");
472 return FALSE;
473 }
474
475 pl->fheight = (ascent + abs(descent));
476 pl->num_visible = height / pl->fheight;
477
478 rounding_offset = pl->fheight / 3;
479
480 max_first = aud_playlist_get_length(playlist) - pl->num_visible;
481 max_first = MAX(max_first, 0);
482
483 pl->first = CLAMP(pl->first, 0, max_first);
484
485 PLAYLIST_LOCK(playlist);
486 list = playlist->entries;
487 list = g_list_nth(list, pl->first);
488
489 /* It sucks having to run the iteration twice but this is the only
490 way you can reliably get the maximum width so we can get our
491 playlist nice and aligned... -- plasmaroo */
492
493 for (i = pl->first;
494 list && i < pl->first + pl->num_visible;
495 list = g_list_next(list), i++) {
496 entry = list->data;
497
498 if (entry->length != -1)
499 {
500 g_snprintf(length, sizeof(length), "%d:%-2.2d",
501 entry->length / 60000, (entry->length / 1000) % 60);
502 tpadding_dwidth = MAX(tpadding_dwidth, strlen(length));
503 }
504 }
505
506 /* Reset */
507 list = playlist->entries;
508 list = g_list_nth(list, pl->first);
509
510 for (i = pl->first;
511 list && i < pl->first + pl->num_visible;
512 list = g_list_next(list), i++) {
513 entry = list->data;
514
515 if (entry->selected && !in_selection) {
516 yc = ((i - pl->first) * pl->fheight);
517
518 cairo_new_path(cr);
519
520 cairo_move_to(cr, 0, yc + (rounding_offset * 2));
521 cairo_curve_to(cr, 0, yc + rounding_offset, 0, yc + 0.5, 0 + rounding_offset, yc + 0.5);
522
523 cairo_line_to(cr, 0 + width - (rounding_offset * 2), yc + 0.5);
524 cairo_curve_to(cr, 0 + width - rounding_offset, yc + 0.5,
525 0 + width, yc + 0.5, 0 + width, yc + rounding_offset);
526
527 in_selection = TRUE;
528 }
529
530 if ((!entry->selected ||
531 (i == pl->first + pl->num_visible - 1) || !g_list_next(list))
532 && in_selection) {
533
534 if (!entry->selected)
535 yc = (((i - 1) - pl->first) * pl->fheight);
536 else /* last visible item */
537 yc = ((i - pl->first) * pl->fheight);
538
539 cairo_line_to(cr, 0 + width, yc + pl->fheight - (rounding_offset * 2));
540 cairo_curve_to (cr, 0 + width, yc + pl->fheight - rounding_offset,
541 0 + width, yc + pl->fheight - 0.5,
542 0 + width-rounding_offset, yc + pl->fheight - 0.5);
543
544 cairo_line_to (cr, 0 + (rounding_offset * 2), yc + pl->fheight - 0.5);
545 cairo_curve_to (cr, 0 + rounding_offset, yc + pl->fheight - 0.5,
546 0, yc + pl->fheight - 0.5,
547 0, yc + pl->fheight - rounding_offset);
548
549 cairo_close_path (cr);
550
551 gdk_cairo_set_source_color(cr, skin_get_color(aud_active_skin, SKIN_PLEDIT_SELECTEDBG));
552
553 cairo_fill(cr);
554
555 in_selection = FALSE;
556 }
557 }
558
559 list = playlist->entries;
560 list = g_list_nth(list, pl->first);
561
562 /* now draw the text */
563 for (i = pl->first;
564 list && i < pl->first + pl->num_visible;
565 list = g_list_next(list), i++) {
566 entry = list->data;
567
568 /* FIXME: entry->title should NEVER be NULL, and there should
569 NEVER be a need to do a UTF-8 conversion. Playlist title
570 strings should be kept properly. */
571
572 if (!entry->title) {
573 gchar *realfn = g_filename_from_uri(entry->filename, NULL, NULL);
574 gchar *basename = g_path_get_basename(realfn ? realfn : entry->filename);
575 title = aud_filename_to_utf8(basename);
576 g_free(basename); g_free(realfn);
577 }
578 else
579 title = aud_str_to_utf8(entry->title);
580
581 title = aud_convert_title_text(title);
582
583 pos = aud_playlist_get_queue_position(playlist, entry);
584
585 tail[0] = 0;
586 queuepos[0] = 0;
587 length[0] = 0;
588
589 if (pos != -1)
590 g_snprintf(queuepos, sizeof(queuepos), "%d", pos + 1);
591
592 if (entry->length != -1)
593 {
594 g_snprintf(length, sizeof(length), "%d:%-2.2d",
595 entry->length / 60000, (entry->length / 1000) % 60);
596 }
597
598 strncat(tail, length, sizeof(tail) - 1);
599 tail_len = strlen(tail);
600
601 max_time_len = MAX(max_time_len, tail_len);
602
603 if (pos != -1 && tpadding_dwidth <= 0)
604 tail_width = width - (width_approx_digits * (strlen(queuepos) + 2.25));
605 else if (pos != -1)
606 tail_width = width - (width_approx_digits * (tpadding_dwidth + strlen(queuepos) + 4));
607 else if (tpadding_dwidth > 0)
608 tail_width = width - (width_approx_digits * (tpadding_dwidth + 2.5));
609 else
610 tail_width = width;
611
612 if (i == aud_playlist_get_position_nolock(playlist))
613 gdk_cairo_set_source_color(cr, skin_get_color(aud_active_skin, SKIN_PLEDIT_CURRENT));
614 else
615 gdk_cairo_set_source_color(cr, skin_get_color(aud_active_skin, SKIN_PLEDIT_NORMAL));
616
617 playlist_list_draw_string(cr, pl, playlist_list_font,
618 i - pl->first, tail_width, title,
619 i + 1);
620
621 x = width - width_approx_digits * 2;
622 y = ((i - pl->first) - 1) * pl->fheight + ascent;
623
624 frags = NULL;
625 frag0 = NULL;
626
627 if ((strlen(tail) > 0) && (tail != NULL)) {
628 frags = g_strsplit(tail, ":", 0);
629 frag0 = g_strconcat(frags[0], ":", NULL);
630
631 layout = gtk_widget_create_pango_layout(playlistwin, frags[1]);
632 pango_layout_set_font_description(layout, playlist_list_font);
633 pango_layout_set_width(layout, tail_len * 100);
634 pango_layout_set_alignment(layout, PANGO_ALIGN_LEFT);
635
636 cairo_new_path(cr);
637 cairo_move_to(cr, x - (0.5 * width_approx_digits), y + abs(descent));
638 pango_cairo_show_layout(cr, layout);
639 g_object_unref(layout);
640
641 layout = gtk_widget_create_pango_layout(playlistwin, frag0);
642 pango_layout_set_font_description(layout, playlist_list_font);
643 pango_layout_set_width(layout, tail_len * 100);
644 pango_layout_set_alignment(layout, PANGO_ALIGN_RIGHT);
645
646 cairo_move_to(cr, x - (0.75 * width_approx_digits), y + abs(descent));
647 pango_cairo_show_layout(cr, layout);
648 g_object_unref(layout);
649
650 g_free(frag0);
651 g_strfreev(frags);
652 }
653
654 if (pos != -1) {
655 if (tpadding_dwidth > 0)
656 queue_tailpadding = tpadding_dwidth + 1;
657 else
658 queue_tailpadding = -0.75;
659
660 cairo_rectangle(cr,
661 x -
662 (((queue_tailpadding +
663 strlen(queuepos)) *
664 width_approx_digits) +
665 (width_approx_digits / 4)),
666 y + abs(descent),
667 (strlen(queuepos)) *
668 width_approx_digits +
669 (width_approx_digits / 2),
670 pl->fheight - 2);
671
672 layout =
673 gtk_widget_create_pango_layout(playlistwin, queuepos);
674 pango_layout_set_font_description(layout, playlist_list_font);
675 pango_layout_set_alignment(layout, PANGO_ALIGN_CENTER);
676
677 cairo_move_to(cr,
678 x -
679 ((queue_tailpadding +
680 strlen(queuepos)) * width_approx_digits) +
681 (width_approx_digits / 4),
682 y + abs(descent));
683 pango_cairo_show_layout(cr, layout);
684
685 g_object_unref(layout);
686 }
687
688 cairo_stroke(cr);
689
690 g_free(title);
691 }
692
693
694 /*
695 * Drop target hovering over the playlist, so draw some hint where the
696 * drop will occur.
697 *
698 * This is (currently? unfixably?) broken when dragging files from Qt/KDE apps,
699 * probably due to DnD signaling problems (actually i have no clue).
700 *
701 */
702
703 if (pl->drag_motion) {
704 guint pos, plength, lpadding;
705
706 if (config.show_numbers_in_pl) {
707 lpadding = gint_count_digits(aud_playlist_get_length(playlist)) + 1;
708 lpadding = ((lpadding + 1) * width_approx_digits);
709 }
710 else {
711 lpadding = 3;
712 };
713
714 /* We already hold the mutex and have the playlist locked, so call
715 the non-locking function. */
716 plength = aud_playlist_get_length(playlist);
717
718 x = pl->drag_motion_x;
719 y = pl->drag_motion_y;
720
721 if ((x > pl->x) && !(x > priv->width)) {
722
723 if ((y > pl->y)
724 && !(y > (priv->height + pl->y))) {
725
726 pos = (y / pl->fheight) +
727 pl->first;
728
729 if (pos > (plength)) {
730 pos = plength;
731 }
732
733 gdk_cairo_set_source_color(cr, skin_get_color(aud_active_skin, SKIN_PLEDIT_CURRENT));
734
735 cairo_new_path(cr);
736
737 cairo_move_to(cr, 0, ((pos - pl->first) * pl->fheight));
738 cairo_rel_line_to(cr, priv->width - 1, 0);
739
740 cairo_set_line_width(cr, 1);
741 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
742 cairo_stroke(cr);
743 }
744
745 }
746
747 /* When dropping on the borders of the playlist, outside the text area,
748 * files get appended at the end of the list. Show that too.
749 */
750
751 if ((y < pl->y) || (y > priv->height + pl->y)) {
752 if ((y >= 0) || (y <= (priv->height + pl->y))) {
753 pos = plength;
754
755 gdk_cairo_set_source_color(cr, skin_get_color(aud_active_skin, SKIN_PLEDIT_CURRENT));
756
757 cairo_new_path(cr);
758
759 cairo_move_to(cr, 0, ((pos - pl->first) * pl->fheight));
760 cairo_rel_line_to(cr, priv->width - 1, 0);
761
762 cairo_set_line_width(cr, 1);
763 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
764 cairo_stroke(cr);
765 }
766 }
767 }
768
769 gdk_cairo_set_source_color(cr, skin_get_color(aud_active_skin, SKIN_PLEDIT_NORMAL));
770 cairo_set_line_width(cr, 1);
771 cairo_set_antialias(cr, CAIRO_ANTIALIAS_NONE);
772
773 if (config.show_numbers_in_pl)
774 {
775 padding_plength = aud_playlist_get_length(playlist);
776
777 if (padding_plength == 0) {
778 padding_dwidth = 0;
779 }
780 else {
781 padding_dwidth = gint_count_digits(aud_playlist_get_length(playlist));
782 }
783
784 padding =
785 (padding_dwidth *
786 width_approx_digits) + width_approx_digits;
787
788
789 /* For italic or oblique fonts we add another half of the
790 * approximate width */
791 if (has_slant)
792 padding += width_approx_digits_half;
793
794 if (config.show_separator_in_pl) {
795 cairo_new_path(cr);
796
797 cairo_move_to(cr, padding, 0);
798 cairo_rel_line_to(cr, 0, priv->height - 1);
799
800 cairo_stroke(cr);
801 }
802 }
803
804 if (tpadding_dwidth != 0)
805 {
806 tpadding = (tpadding_dwidth * width_approx_digits) + (width_approx_digits * 1.5);
807
808 if (has_slant)
809 tpadding += width_approx_digits_half;
810
811 if (config.show_separator_in_pl) {
812 cairo_new_path(cr);
813
814 cairo_move_to(cr, priv->width - tpadding, 0);
815 cairo_rel_line_to(cr, 0, priv->height - 1);
816
817 cairo_stroke(cr);
818 }
819 }
820
821 PLAYLIST_UNLOCK(playlist);
822
823 cairo_destroy(cr);
824
825 return FALSE;
826 }
827
828 gint ui_skinned_playlist_get_position(GtkWidget *widget, gint x, gint y) {
829 gint iy, length;
830 gint ret;
831 Playlist *playlist = aud_playlist_get_active();
832 UiSkinnedPlaylist *pl = UI_SKINNED_PLAYLIST (widget);
833
834 if (!pl->fheight)
835 return -1;
836
837 if ((length = aud_playlist_get_length(playlist)) == 0)
838 return -1;
839 iy = y;
840
841 ret = (iy / pl->fheight) + pl->first;
842
843 if (ret > length - 1)
844 ret = -1;
845
846 return ret;
847 }
848
849 static gboolean ui_skinned_playlist_button_press(GtkWidget *widget, GdkEventButton *event) {
850 UiSkinnedPlaylist *pl = UI_SKINNED_PLAYLIST (widget);
851 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(widget);
852
853 gint nr;
854 Playlist *playlist = aud_playlist_get_active();
855
856 nr = ui_skinned_playlist_get_position(widget, event->x, event->y);
857 if (nr == -1)
858 return FALSE;
859
860 if (event->button == 3) {
861 ui_manager_popup_menu_show(GTK_MENU(playlistwin_popup_menu),
862 event->x_root, event->y_root + 5,
863 event->button, event->time);
864 GList* selection = aud_playlist_get_selected(playlist);
865 if (g_list_find(selection, GINT_TO_POINTER(nr)) == NULL) {
866 aud_playlist_select_all(playlist, FALSE);
867 aud_playlist_select_range(playlist, nr, nr, TRUE);
868 }
869 } else if (event->button == 1) {
870 if (!(event->state & GDK_CONTROL_MASK))
871 aud_playlist_select_all(playlist, FALSE);
872
873 if ((event->state & GDK_MOD1_MASK))
874 aud_playlist_queue_position(playlist, nr);
875
876 if (event->state & GDK_SHIFT_MASK && pl->prev_selected != -1) {
877 aud_playlist_select_range(playlist, pl->prev_selected, nr, TRUE);
878 pl->prev_min = pl->prev_selected;
879 pl->prev_max = nr;
880 priv->drag_pos = nr - pl->first;
881 }
882 else {
883 if (aud_playlist_select_invert(playlist, nr)) {
884 if (event->state & GDK_CONTROL_MASK) {
885 if (pl->prev_min == -1) {
886 pl->prev_min = pl->prev_selected;
887 pl->prev_max = pl->prev_selected;
888 }
889 if (nr < pl->prev_min)
890 pl->prev_min = nr;
891 else if (nr > pl->prev_max)
892 pl->prev_max = nr;
893 }
894 else
895 pl->prev_min = -1;
896 pl->prev_selected = nr;
897 priv->drag_pos = nr - pl->first;
898 }
899 }
900 if (event->type == GDK_2BUTTON_PRESS) {
901 /*
902 * Ungrab the pointer to prevent us from
903 * hanging on to it during the sometimes slow
904 * audacious_drct_initiate().
905 */
906 gdk_pointer_ungrab(GDK_CURRENT_TIME);
907 aud_playlist_set_position(playlist, nr);
908 if (!audacious_drct_get_playing())
909 audacious_drct_initiate();
910 }
911
912 priv->dragging = TRUE;
913 }
914 playlistwin_update_list(playlist);
915 ui_skinned_playlist_popup_hide(widget);
916 ui_skinned_playlist_popup_timer_stop(widget);
917
918 return TRUE;
919 }
920
921 static gboolean ui_skinned_playlist_button_release(GtkWidget *widget, GdkEventButton *event) {
922 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(widget);
923
924 priv->dragging = FALSE;
925 priv->auto_drag_down = FALSE;
926 priv->auto_drag_up = FALSE;
927 gtk_widget_queue_draw(widget);
928
929 ui_skinned_playlist_popup_hide(widget);
930 ui_skinned_playlist_popup_timer_stop(widget);
931 return TRUE;
932 }
933
934 static gboolean ui_skinned_playlist_motion_notify(GtkWidget *widget, GdkEventMotion *event) {
935 UiSkinnedPlaylist *pl = UI_SKINNED_PLAYLIST(widget);
936 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(widget);
937
938 gint nr, y, off, i;
939 if (priv->dragging) {
940 y = event->y;
941 nr = (y / pl->fheight);
942 if (nr < 0) {
943 nr = 0;
944 if (!priv->auto_drag_up) {
945 priv->auto_drag_up = TRUE;
946 priv->auto_drag_up_tag =
947 g_timeout_add(100, ui_skinned_playlist_auto_drag_up_func, pl);
948 }
949 }
950 else if (priv->auto_drag_up)
951 priv->auto_drag_up = FALSE;
952
953 if (nr >= pl->num_visible) {
954 nr = pl->num_visible - 1;
955 if (!priv->auto_drag_down) {
956 priv->auto_drag_down = TRUE;
957 priv->auto_drag_down_tag =
958 g_timeout_add(100, ui_skinned_playlist_auto_drag_down_func, pl);
959 }
960 }
961 else if (priv->auto_drag_down)
962 priv->auto_drag_down = FALSE;
963
964 off = nr - priv->drag_pos;
965 if (off) {
966 for (i = 0; i < abs(off); i++) {
967 if (off < 0)
968 ui_skinned_playlist_move_up(pl);
969 else
970 ui_skinned_playlist_move_down(pl);
971
972 }
973 playlistwin_update_list(aud_playlist_get_active());
974 }
975 priv->drag_pos = nr;
976 } else if (aud_cfg->show_filepopup_for_tuple) {
977 gint pos = ui_skinned_playlist_get_position(widget, event->x, event->y);
978 gint cur_pos = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "popup_position"));
979 if (pos != cur_pos) {
980 g_object_set_data(G_OBJECT(widget), "popup_position", GINT_TO_POINTER(pos));
981 ui_skinned_playlist_popup_hide(widget);
982 ui_skinned_playlist_popup_timer_stop(widget);
983 if (pos != -1)
984 ui_skinned_playlist_popup_timer_start(widget);
985 }
986 }
987
988 return TRUE;
989 }
990
991 static gboolean ui_skinned_playlist_leave_notify(GtkWidget *widget, GdkEventCrossing *event) {
992 ui_skinned_playlist_popup_hide(widget);
993 ui_skinned_playlist_popup_timer_stop(widget);
994
995 return FALSE;
996 }
997
998 static void ui_skinned_playlist_redraw(UiSkinnedPlaylist *playlist) {
999 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(playlist);
1000
1001 if (priv->resize_height || priv->resize_width)
1002 gtk_widget_set_size_request(GTK_WIDGET(playlist), priv->width+priv->resize_width, priv->height+priv->resize_height);
1003
1004 gtk_widget_queue_draw(GTK_WIDGET(playlist));
1005 }
1006
1007 void ui_skinned_playlist_set_font(const gchar * font) {
1008 /* Welcome to bad hack central 2k3 */
1009 gchar *font_lower;
1010 gint width_temp;
1011 gint width_temp_0;
1012
1013 playlist_list_font = pango_font_description_from_string(font);
1014
1015 text_get_extents(font,
1016 "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz ",
1017 &width_approx_letters, NULL, &ascent, &descent);
1018
1019 width_approx_letters = (width_approx_letters / 53);
1020
1021 /* Experimental: We don't weigh the 1 into total because it's width is almost always
1022 * very different from the rest
1023 */
1024 text_get_extents(font, "023456789", &width_approx_digits, NULL, NULL,
1025 NULL);
1026 width_approx_digits = (width_approx_digits / 9);
1027
1028 /* Precache some often used calculations */
1029 width_approx_digits_half = width_approx_digits / 2;
1030
1031 /* FIXME: We assume that any other number is broader than the "1" */
1032 text_get_extents(font, "1", &width_temp, NULL, NULL, NULL);
1033 text_get_extents(font, "2", &width_temp_0, NULL, NULL, NULL);
1034
1035 if (abs(width_temp_0 - width_temp) < 2) {
1036 width_delta_digit_one = 0;
1037 }
1038 else {
1039 width_delta_digit_one = ((width_temp_0 - width_temp) / 2) + 2;
1040 }
1041
1042 text_get_extents(font, ":", &width_colon, NULL, NULL, NULL);
1043 width_colon_third = width_colon / 4;
1044
1045 font_lower = g_utf8_strdown(font, strlen(font));
1046 /* This doesn't take any i18n into account, but i think there is none with TTF fonts
1047 * FIXME: This can probably be retrieved trough Pango too
1048 */
1049 has_slant = g_strstr_len(font_lower, strlen(font_lower), "oblique")
1050 || g_strstr_len(font_lower, strlen(font_lower), "italic");
1051
1052 g_free(font_lower);
1053 }
1054
1055 void ui_skinned_playlist_resize_relative(GtkWidget *widget, gint w, gint h) {
1056 UiSkinnedPlaylistPrivate *priv = UI_SKINNED_PLAYLIST_GET_PRIVATE(widget);
1057 priv->resize_width += w;
1058 priv->resize_height += h;
1059 }
1060
1061 static gboolean ui_skinned_playlist_popup_show(gpointer data) {
1062 #if 0
1063 GtkWidget *widget = data;
1064 gint pos = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "popup_position"));
1065
1066 if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "timer_active")) == 1 && pos != -1) {
1067 Tuple *tuple;
1068 Playlist *pl_active = aud_playlist_get_active();
1069 GtkWidget *popup = g_object_get_data(G_OBJECT(widget), "popup");
1070
1071 tuple = playlist_get_tuple(pl_active, pos);
1072 if ((tuple == NULL) || (tuple_get_int(tuple, FIELD_LENGTH, NULL) < 1)) {
1073 gchar *title = playlist_get_songtitle(pl_active, pos);
1074 fileinfopopup_show_from_title(popup, title);
1075 g_free(title);
1076 } else {
1077 fileinfopopup_show_from_tuple(popup , tuple);
1078 }
1079 g_object_set_data(G_OBJECT(widget), "popup_active" , GINT_TO_POINTER(1));
1080 }
1081
1082 ui_skinned_playlist_popup_timer_stop(widget);
1083 return FALSE;
1084 #endif
1085 }
1086
1087 static void ui_skinned_playlist_popup_hide(GtkWidget *widget) {
1088 #if 0
1089 if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "popup_active")) == 1) {
1090 GtkWidget *popup = g_object_get_data(G_OBJECT(widget), "popup");
1091 g_object_set_data(G_OBJECT(widget), "popup_active", GINT_TO_POINTER(0));
1092 fileinfopopup_hide(popup, NULL);
1093 }
1094 #endif
1095 }
1096
1097 static void ui_skinned_playlist_popup_timer_start(GtkWidget *widget) {
1098 gint timer_id = g_timeout_add(aud_cfg->filepopup_delay*100, ui_skinned_playlist_popup_show, widget);
1099 g_object_set_data(G_OBJECT(widget), "timer_id", GINT_TO_POINTER(timer_id));
1100 g_object_set_data(G_OBJECT(widget), "timer_active", GINT_TO_POINTER(1));
1101 }
1102
1103 static void ui_skinned_playlist_popup_timer_stop(GtkWidget *widget) {
1104 if (GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "timer_active")) == 1)
1105 g_source_remove(GPOINTER_TO_INT(g_object_get_data(G_OBJECT(widget), "timer_id")));
1106
1107 g_object_set_data(G_OBJECT(widget), "timer_id", GINT_TO_POINTER(0));
1108 g_object_set_data(G_OBJECT(widget), "timer_active", GINT_TO_POINTER(0));
1109 }