/* DeaDBeeF - The Ultimate Music Player Copyright (C) 2009-2013 Alexey Yakovenko This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "ddbtabstrip.h" #include "drawing.h" #include "gtkui.h" #include "interface.h" #include "support.h" #include "mainplaylist.h" #define GLADE_HOOKUP_OBJECT(component,widget,name) \ g_object_set_data_full (G_OBJECT (component), name, \ g_object_ref (G_OBJECT (widget)), (GDestroyNotify) g_object_unref) #define GLADE_HOOKUP_OBJECT_NO_REF(component,widget,name) \ g_object_set_data (G_OBJECT (component), name, widget) G_DEFINE_TYPE (DdbTabStrip, ddb_tabstrip, GTK_TYPE_WIDGET); extern GtkWidget *theme_button; #define arrow_sz 10 #define arrow_widget_width (arrow_sz+4) static void ddb_tabstrip_send_configure (DdbTabStrip *darea) { GtkWidget *widget; GdkEvent *event = gdk_event_new (GDK_CONFIGURE); widget = GTK_WIDGET (darea); event->configure.window = g_object_ref (gtk_widget_get_window(widget)); event->configure.send_event = TRUE; GtkAllocation a; gtk_widget_get_allocation (widget, &a); event->configure.x = a.x; event->configure.y = a.y; event->configure.width = a.width; event->configure.height = a.height; gtk_widget_event (widget, event); gdk_event_free (event); } static void ddb_tabstrip_realize (GtkWidget *widget) { DdbTabStrip *darea = DDB_TABSTRIP (widget); GdkWindowAttr attributes; gint attributes_mask; if (!gtk_widget_get_has_window (widget)) { GTK_WIDGET_CLASS (ddb_tabstrip_parent_class)->realize (widget); } else { gtk_widget_set_realized (widget, TRUE); attributes.window_type = GDK_WINDOW_CHILD; GtkAllocation a; gtk_widget_get_allocation (widget, &a); attributes.x = a.x; attributes.y = a.y; attributes.width = a.width; attributes.height = a.height; attributes.wclass = GDK_INPUT_OUTPUT; attributes.visual = gtk_widget_get_visual (widget); #if !GTK_CHECK_VERSION(3,0,0) attributes.colormap = gtk_widget_get_colormap (widget); #endif attributes.event_mask = gtk_widget_get_events (widget); attributes.event_mask |= GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_POINTER_MOTION_MASK; attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; #if !GTK_CHECK_VERSION(3,0,0) attributes_mask |= GDK_WA_COLORMAP; #endif gtk_widget_set_window(widget, gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask)); gdk_window_set_user_data (gtk_widget_get_window(widget), darea); gtk_widget_set_style (widget, gtk_style_attach (gtk_widget_get_style (widget), gtk_widget_get_window(widget))); gtk_style_set_background (gtk_widget_get_style (widget), gtk_widget_get_window(widget), GTK_STATE_NORMAL); } ddb_tabstrip_send_configure (DDB_TABSTRIP (widget)); GtkTargetEntry entry = { .target = "STRING", .flags = GTK_TARGET_SAME_APP, TARGET_SAMEWIDGET }; gtk_drag_dest_set (widget, GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_DROP, &entry, 1, GDK_ACTION_COPY | GDK_ACTION_MOVE); gtk_drag_dest_add_uri_targets (widget); gtk_drag_dest_set_track_motion (widget, TRUE); } static void ddb_tabstrip_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { g_return_if_fail (DDB_IS_TABSTRIP (widget)); g_return_if_fail (allocation != NULL); gtk_widget_set_allocation (widget, allocation); if (gtk_widget_get_realized (widget)) { if (gtk_widget_get_has_window (widget)) gdk_window_move_resize (gtk_widget_get_window(widget), allocation->x, allocation->y, allocation->width, allocation->height); ddb_tabstrip_send_configure (DDB_TABSTRIP (widget)); } } gboolean on_tabstrip_scroll_event (GtkWidget *widget, GdkEventScroll *event); gboolean on_tabstrip_button_press_event (GtkWidget *widget, GdkEventButton *event); gboolean on_tabstrip_button_release_event (GtkWidget *widget, GdkEventButton *event); gboolean on_tabstrip_configure_event (GtkWidget *widget, GdkEventConfigure *event); gboolean on_tabstrip_draw (GtkWidget *widget, cairo_t *cr); gboolean on_tabstrip_expose_event (GtkWidget *widget, GdkEventExpose *event); gboolean on_tabstrip_motion_notify_event (GtkWidget *widget, GdkEventMotion *event); gboolean on_tabstrip_drag_motion_event (GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, guint time); gboolean on_tabstrip_drag_drop (GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, guint time); void on_tabstrip_drag_data_received (GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *data, guint target_type, guint time); void on_tabstrip_drag_leave (GtkWidget *widget, GdkDragContext *drag_context, guint time); void on_tabstrip_drag_end (GtkWidget *widget, GdkDragContext *drag_context); static int get_tab_under_cursor (DdbTabStrip *ts, int x); static void ddb_tabstrip_destroy(GObject *object) { DdbTabStrip *tabstrip; g_return_if_fail(object != NULL); g_return_if_fail(DDB_IS_TABSTRIP(object)); tabstrip = DDB_TABSTRIP (object); draw_free (&tabstrip->drawctx); } static void ddb_tabstrip_class_init(DdbTabStripClass *class) { GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); #if !GTK_CHECK_VERSION(3,0,0) widget_class->expose_event = on_tabstrip_expose_event; #else widget_class->draw = on_tabstrip_draw; #endif widget_class->realize = ddb_tabstrip_realize; widget_class->size_allocate = ddb_tabstrip_size_allocate; widget_class->button_press_event = on_tabstrip_button_press_event; widget_class->button_release_event = on_tabstrip_button_release_event; widget_class->configure_event = on_tabstrip_configure_event; widget_class->motion_notify_event = on_tabstrip_motion_notify_event; widget_class->scroll_event= on_tabstrip_scroll_event; widget_class->drag_motion = on_tabstrip_drag_motion_event; widget_class->drag_drop = on_tabstrip_drag_drop; widget_class->drag_end = on_tabstrip_drag_end; widget_class->drag_data_received = on_tabstrip_drag_data_received; widget_class->drag_leave = on_tabstrip_drag_leave; } gboolean on_tabstrip_drag_drop (GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, guint time) { return TRUE; } void on_tabstrip_drag_data_received (GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *data, guint target_type, guint time) { gchar *ptr=(char*)gtk_selection_data_get_data (data); int len = gtk_selection_data_get_length (data); if (target_type == 0) { // uris // this happens when dropped from file manager char *mem = malloc (len+1); memcpy (mem, ptr, len); mem[len] = 0; // we don't pass control structure, but there's only one drag-drop view currently gtkui_receive_fm_drop (NULL, mem, len); } else if (target_type == 1) { uint32_t *d= (uint32_t *)ptr; int plt = *d; d++; int length = (len/4)-1; ddb_playlist_t *p = deadbeef->plt_get_for_idx (plt); if (p) { main_drag_n_drop (NULL, p, d, length, gdk_drag_context_get_selected_action (drag_context) == GDK_ACTION_COPY ? 1 : 0); deadbeef->plt_unref (p); } } gtk_drag_finish (drag_context, TRUE, FALSE, time); } void on_tabstrip_drag_leave (GtkWidget *widget, GdkDragContext *drag_context, guint time) { } void on_tabstrip_drag_end (GtkWidget *widget, GdkDragContext *drag_context) { } GtkWidget * ddb_tabstrip_new() { return g_object_new (DDB_TYPE_TABSTRIP, NULL); } static void ddb_tabstrip_init(DdbTabStrip *tabstrip) { tabstrip->hscrollpos = 0; tabstrip->dragging = -1; tabstrip->prepare = 0; tabstrip->dragpt[0] = 0; tabstrip->dragpt[1] = 0; tabstrip->prev_x = 0; tabstrip->movepos = 0; drawctx_init (&tabstrip->drawctx); #if GTK_CHECK_VERSION(3,0,0) gtk_widget_set_events (GTK_WIDGET (tabstrip), gtk_widget_get_events (GTK_WIDGET (tabstrip)) | GDK_SCROLL_MASK); #endif } static int tab_clicked = -1; static int text_left_padding = 4; static int text_right_padding = 0; // calculated from widget height static int text_vert_offset = -2; static int tab_overlap_size = 0; // widget_height/2 static int tabs_left_margin = 4; static int min_tab_size = 80; static int max_tab_size = 200; static int tab_moved = 0; typedef struct { #if GTK_CHECK_VERSION(3,0,0) float x, y; #else int x, y; #endif } coord_t; static void cairo_draw_lines (cairo_t *cr, coord_t *pts, int cnt) { cairo_move_to (cr, pts[0].x+1, pts[0].y+1); for (int i = 1; i < cnt; i++) { cairo_line_to (cr, pts[i].x+1, pts[i].y+1); } } static void cairo_draw_poly (cairo_t *cr, coord_t *pts, int cnt) { cairo_move_to (cr, pts[0].x, pts[0].y); for (int i = 1; i < cnt; i++) { cairo_line_to (cr, pts[i].x, pts[i].y); } } void #if !GTK_CHECK_VERSION(3,0,0) ddb_tabstrip_draw_tab (GtkWidget *widget, GdkDrawable *drawable, int idx, int selected, int x, int y, int w, int h) { #else ddb_tabstrip_draw_tab (GtkWidget *widget, cairo_t *cr, int idx, int selected, int x, int y, int w, int h) { #endif coord_t points_filled[] = { { x+2, y + h }, { x+2, y + 2 }, { x + w - h + 1, y + 2 }, { x + w - 1 + 1, y + h } }; coord_t points_frame1[] = { { x, y + h-2 }, #if GTK_CHECK_VERSION(3,0,0) { x, y + 0.5 }, { x + 0.5, y }, #else { x, y + 1 }, { x + 1, y }, #endif { x + w - h - 1, y }, { x + w - h + 1, y + 1 }, { x + w - 3, y + h - 3 }, { x + w - 0, y + h - 2 }, }; coord_t points_frame2[] = { { x + 1, y + h -1 }, { x + 1, y + 1 }, { x + w - h - 1, y + 1 }, { x + w - h + 1, y + 2 }, { x + w - 3, y + h - 2 }, { x + w - 0, y + h - 1 }, }; #if !GTK_CHECK_VERSION(3,0,0) GdkGC *bg = gdk_gc_new (drawable); GdkGC *outer_frame = gdk_gc_new (drawable); GdkGC *inner_frame = gdk_gc_new (drawable); #endif GdkColor clr_bg; GdkColor clr_outer_frame; GdkColor clr_inner_frame; int fallback = 1; deadbeef->pl_lock (); ddb_playlist_t *plt = deadbeef->plt_get_for_idx (idx); const char *bgclr = deadbeef->plt_find_meta (plt, "gui.bgcolor"); deadbeef->plt_unref (plt); if (bgclr) { int r, g, b; if (3 == sscanf (bgclr, "%02x%02x%02x", &r, &g, &b)) { fallback = 0; clr_bg.red = r * 0x101; clr_bg.green = g * 0x101; clr_bg.blue = b * 0x101; } } deadbeef->pl_unlock (); if (selected) { if (fallback) { gtkui_get_tabstrip_base_color (&clr_bg); } #if !GTK_CHECK_VERSION(3,0,0) gdk_gc_set_rgb_fg_color (bg, &clr_bg); gdk_gc_set_rgb_fg_color (outer_frame, (gtkui_get_tabstrip_dark_color (&clr_outer_frame), &clr_outer_frame)); gdk_gc_set_rgb_fg_color (inner_frame, (gtkui_get_tabstrip_light_color (&clr_inner_frame), &clr_inner_frame)); #else gtkui_get_tabstrip_dark_color (&clr_outer_frame); gtkui_get_tabstrip_light_color (&clr_inner_frame); #endif } else { if (fallback) { gtkui_get_tabstrip_mid_color (&clr_bg); } #if !GTK_CHECK_VERSION(3,0,0) gdk_gc_set_rgb_fg_color (bg, &clr_bg); gdk_gc_set_rgb_fg_color (outer_frame, (gtkui_get_tabstrip_dark_color (&clr_outer_frame), &clr_outer_frame)); gdk_gc_set_rgb_fg_color (inner_frame, (gtkui_get_tabstrip_mid_color (&clr_inner_frame), &clr_inner_frame)); #else gtkui_get_tabstrip_dark_color (&clr_outer_frame); gtkui_get_tabstrip_mid_color (&clr_inner_frame); #endif } #if !GTK_CHECK_VERSION(3,0,0) gdk_draw_polygon (drawable, bg, TRUE, (GdkPoint*)points_filled, sizeof (points_filled)/sizeof(coord_t)); gdk_draw_lines (drawable, outer_frame, (GdkPoint*)points_frame1, sizeof (points_frame1)/sizeof(coord_t)); gdk_draw_lines (drawable, inner_frame, (GdkPoint*)points_frame2, sizeof (points_frame2)/sizeof(coord_t)); g_object_unref (bg); g_object_unref (outer_frame); g_object_unref (inner_frame); #else cairo_set_source_rgb (cr, clr_bg.red/65535.f, clr_bg.green/65535.f, clr_bg.blue/65535.0); cairo_new_path (cr); cairo_draw_poly (cr, points_filled, sizeof (points_filled)/sizeof(coord_t)); cairo_close_path (cr); cairo_fill (cr); cairo_set_source_rgb (cr, clr_outer_frame.red/65535.f, clr_outer_frame.green/65535.f, clr_outer_frame.blue/65535.0); cairo_draw_lines (cr, points_frame1, sizeof (points_frame1)/sizeof(coord_t)); cairo_stroke (cr); cairo_set_source_rgb (cr, clr_inner_frame.red/65535.f, clr_inner_frame.green/65535.f, clr_inner_frame.blue/65535.0); cairo_draw_lines (cr, points_frame2, sizeof (points_frame2)/sizeof(coord_t)); cairo_stroke (cr); #endif } int ddb_tabstrip_get_tab_width (DdbTabStrip *ts, int tab) { int width; char title[1000]; plt_get_title_wrapper (tab, title, sizeof (title)); int h = 0; draw_get_text_extents (&ts->drawctx, title, strlen (title), &width, &h); width += text_left_padding + text_right_padding; if (width < min_tab_size) { width = min_tab_size; } else if (width > max_tab_size) { width = max_tab_size; } return width; } int tabstrip_need_arrows (DdbTabStrip *ts) { GtkWidget *widget = GTK_WIDGET (ts); int cnt = deadbeef->plt_get_count (); int w = 0; GtkAllocation a; gtk_widget_get_allocation (widget, &a); for (int idx = 0; idx < cnt; idx++) { w += ddb_tabstrip_get_tab_width (ts, idx) - tab_overlap_size; if (w >= a.width) { return 1; } } w += tab_overlap_size + 3; if (w >= a.width) { return 1; } return 0; } static void tabstrip_scroll_to_tab_int (DdbTabStrip *ts, int tab, int redraw) { GtkWidget *widget = GTK_WIDGET (ts); int w = 0; int cnt = deadbeef->plt_get_count (); GtkAllocation a; gtk_widget_get_allocation (widget, &a); int boundary = a.width - arrow_widget_width*2 + ts->hscrollpos; for (int idx = 0; idx < cnt; idx++) { int tab_w = ddb_tabstrip_get_tab_width (ts, idx); if (idx == cnt-1) { tab_w += 3; } if (idx == tab) { if (w < ts->hscrollpos) { ts->hscrollpos = w; deadbeef->conf_set_int ("gtkui.tabscroll", ts->hscrollpos); if (redraw) { gtk_widget_queue_draw (widget); } } else if (w + tab_w >= boundary) { ts->hscrollpos += (w+tab_w) - boundary; deadbeef->conf_set_int ("gtkui.tabscroll", ts->hscrollpos); if (redraw) { gtk_widget_queue_draw (widget); } } break; } w += tab_w - tab_overlap_size; } } static void tabstrip_scroll_to_tab (DdbTabStrip *ts, int tab) { tabstrip_scroll_to_tab_int (ts, tab, 1); } void tabstrip_adjust_hscroll (DdbTabStrip *ts) { GtkWidget *widget = GTK_WIDGET (ts); ts->hscrollpos = deadbeef->conf_get_int ("gtkui.tabscroll", 0); if (deadbeef->plt_get_count () > 0) { int need_arrows = tabstrip_need_arrows (ts); if (need_arrows) { GtkAllocation a; gtk_widget_get_allocation (widget, &a); int w = 0; int cnt = deadbeef->plt_get_count (); for (int idx = 0; idx < cnt; idx++) { w += ddb_tabstrip_get_tab_width (ts, idx) - tab_overlap_size; } w += tab_overlap_size + 3; if (ts->hscrollpos > w - (a.width - arrow_widget_width*2)) { ts->hscrollpos = w - (a.width - arrow_widget_width*2); deadbeef->conf_set_int ("gtkui.tabscroll", ts->hscrollpos); } tabstrip_scroll_to_tab_int (ts, deadbeef->plt_get_curr_idx (), 0); } else { ts->hscrollpos = 0; deadbeef->conf_set_int ("gtkui.tabscroll", ts->hscrollpos); } } } void set_tab_text_color (DdbTabStrip *ts, int idx, int selected) { if (idx == -1) { return; } deadbeef->pl_lock (); ddb_playlist_t *plt = deadbeef->plt_get_for_idx (idx); int fallback = 1; const char *clr = deadbeef->plt_find_meta (plt, "gui.color"); if (clr) { int r, g, b; if (3 == sscanf (clr, "%02x%02x%02x", &r, &g, &b)) { fallback = 0; float fg[3] = {(float)r/0xff, (float)g/0xff, (float)b/0xff}; draw_set_fg_color (&ts->drawctx, fg); } } deadbeef->plt_unref (plt); if (fallback) { GdkColor color; gtkui_get_tabstrip_text_color (&color); float fg[3] = {(float)color.red/0xffff, (float)color.green/0xffff, (float)color.blue/0xffff}; draw_set_fg_color (&ts->drawctx, fg); } deadbeef->pl_unlock (); } void tabstrip_render (DdbTabStrip *ts, cairo_t *cr) { GtkWidget *widget = GTK_WIDGET (ts); #if !GTK_CHECK_VERSION(3,0,0) GdkDrawable *backbuf = gtk_widget_get_window (widget); #endif GtkAllocation a; gtk_widget_get_allocation (widget, &a); a.height = ts->calculated_height; tabstrip_adjust_hscroll (ts); cairo_set_line_width (cr, 1); #ifdef __APPLE__ cairo_set_antialias (cr, gtkui_is_retina ? CAIRO_ANTIALIAS_GRAY : CAIRO_ANTIALIAS_NONE); #else cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); #endif int cnt = deadbeef->plt_get_count (); int hscroll = ts->hscrollpos; int need_arrows = tabstrip_need_arrows (ts); if (need_arrows) { hscroll -= arrow_widget_width; } int x = -hscroll; int w = 0; int h = draw_get_font_size (&ts->drawctx); h = a.height; tab_overlap_size = (h-4)/2; text_right_padding = h - 3; const char *detail = "button"; int tab_selected = deadbeef->plt_get_curr_idx (); if (tab_selected == -1) { return; } #if !GTK_CHECK_VERSION(3,0,0) GdkGC *gc = gdk_gc_new (backbuf); // fill background GdkColor clr; gdk_gc_set_rgb_fg_color (gc, (gtkui_get_tabstrip_mid_color (&clr), &clr)); gdk_draw_rectangle (backbuf, gc, TRUE, 0, 0, widget->allocation.width, widget->allocation.height); gdk_gc_set_rgb_fg_color (gc, (gtkui_get_tabstrip_dark_color (&clr), &clr)); gdk_draw_line (backbuf, gc, 0, 0, widget->allocation.width, 0); #else // fill background GdkColor clr; gtkui_get_tabstrip_mid_color (&clr); cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.0); cairo_rectangle (cr, 0, 0, a.width, a.height); cairo_fill (cr); gtkui_get_tabstrip_dark_color (&clr); cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.0); cairo_move_to (cr, 0, 1); cairo_line_to (cr, a.width, 1); cairo_stroke (cr); #endif int y = 4; h = a.height - 4; draw_begin (&ts->drawctx, cr); int need_draw_moving = 0; int idx; int widths[cnt]; for (idx = 0; idx < cnt; idx++) { char title[1000]; plt_get_title_wrapper (idx, title, sizeof (title)); int h = 0; draw_get_text_extents (&ts->drawctx, title, strlen (title), &widths[idx], &h); widths[idx] += text_left_padding + text_right_padding; if (widths[idx] < min_tab_size) { widths[idx] = min_tab_size; } else if (widths[idx] > max_tab_size) { widths[idx] = max_tab_size; } } x = -hscroll + tabs_left_margin; for (idx = 0; idx < cnt; idx++) { w = widths[idx]; GdkRectangle area; area.x = x; area.y = 0; area.width = w; area.height = 24; if (idx != tab_selected) { #if !GTK_CHECK_VERSION(3,0,0) ddb_tabstrip_draw_tab (widget, backbuf, idx, idx == tab_selected, x, y, w, h); #else ddb_tabstrip_draw_tab (widget, cr, idx, idx == tab_selected, x, y, w, h); #endif char tab_title[1000]; plt_get_title_wrapper (idx, tab_title, sizeof (tab_title)); set_tab_text_color (ts, idx, tab_selected); draw_text (&ts->drawctx, x + text_left_padding, y - text_vert_offset, w - (text_left_padding + text_right_padding - 1), 0, tab_title); } x += w - tab_overlap_size; } #if !GTK_CHECK_VERSION(3,0,0) gdk_draw_line (backbuf, widget->style->dark_gc[GTK_STATE_NORMAL], 0, widget->allocation.height-2, widget->allocation.width, widget->allocation.height-2); gdk_draw_line (backbuf, widget->style->light_gc[GTK_STATE_NORMAL], 0, widget->allocation.height-1, widget->allocation.width, widget->allocation.height-1); #else GdkColor *pclr = >k_widget_get_style (widget)->dark[GTK_STATE_NORMAL]; cairo_set_source_rgb (cr, pclr->red/65535.f, pclr->green/65535.f, pclr->blue/65535.0); cairo_move_to (cr, 0, a.height-1); cairo_line_to (cr, a.width, a.height-1); cairo_stroke (cr); pclr = >k_widget_get_style (widget)->light[GTK_STATE_NORMAL]; cairo_set_source_rgb (cr, pclr->red/65535.f, pclr->green/65535.f, pclr->blue/65535.0); cairo_move_to (cr, 0, a.height); cairo_line_to (cr, a.width, a.height); cairo_stroke (cr); #endif // calc position for drawin selected tab x = -hscroll; for (idx = 0; idx < tab_selected; idx++) { x += widths[idx] - tab_overlap_size; } x += tabs_left_margin; // draw selected if (ts->dragging < 0 || ts->prepare || tab_selected != ts->dragging) { idx = tab_selected; w = widths[tab_selected]; GdkRectangle area; area.x = x; area.y = 0; area.width = w; area.height = 24; #if !GTK_CHECK_VERSION(3,0,0) ddb_tabstrip_draw_tab (widget, backbuf, idx, 1, x, y, w, h); #else ddb_tabstrip_draw_tab (widget, cr, idx, 1, x, y, w, h); #endif char tab_title[1000]; plt_get_title_wrapper (idx, tab_title, sizeof (tab_title)); set_tab_text_color (ts, idx, tab_selected); draw_text (&ts->drawctx, x + text_left_padding, y - text_vert_offset, w - (text_left_padding + text_right_padding - 1), 0, tab_title); } else { need_draw_moving = 1; } if (need_draw_moving) { x = -hscroll + tabs_left_margin; for (idx = 0; idx < cnt; idx++) { w = widths[idx]; if (idx == ts->dragging) { x = ts->movepos; if (x >= a.width) { break; } if (w > 0) { #if !GTK_CHECK_VERSION(3,0,0) ddb_tabstrip_draw_tab (widget, backbuf, idx, 1, x, y, w, h); #else ddb_tabstrip_draw_tab (widget, cr, idx, 1, x, y, w, h); #endif char tab_title[1000]; plt_get_title_wrapper (idx, tab_title, sizeof (tab_title)); set_tab_text_color (ts, idx, tab_selected); draw_text (&ts->drawctx, x + text_left_padding, y - text_vert_offset, w - (text_left_padding + text_right_padding - 1), 0, tab_title); } break; } x += w - tab_overlap_size; } } #if !GTK_CHECK_VERSION(3,0,0) if (need_arrows) { int sz = widget->allocation.height-3; GdkColor clr; gdk_gc_set_rgb_fg_color (gc, (gtkui_get_tabstrip_mid_color (&clr), &clr)); gdk_draw_rectangle (backbuf, gc, TRUE, 0, 1, arrow_widget_width, sz); gtk_paint_arrow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, widget, NULL, GTK_ARROW_LEFT, TRUE, 2, sz/2-arrow_sz/2, arrow_sz, arrow_sz); gdk_draw_rectangle (backbuf, gc, TRUE, widget->allocation.width-arrow_widget_width, 1, arrow_widget_width, sz); gtk_paint_arrow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, widget, NULL, GTK_ARROW_RIGHT, TRUE, widget->allocation.width-arrow_sz-2, 1+sz/2-arrow_sz/2, arrow_sz, arrow_sz); } #else if (need_arrows) { int sz = a.height-3; gtkui_get_tabstrip_mid_color (&clr); cairo_set_source_rgb (cr, clr.red/65535.f, clr.green/65535.f, clr.blue/65535.0); cairo_rectangle (cr, 0, 1, arrow_widget_width, sz); cairo_fill (cr); #if GTK_CHECK_VERSION(3,0,0) gtk_paint_arrow (gtk_widget_get_style (widget), cr, GTK_STATE_NORMAL, GTK_SHADOW_NONE, widget, NULL, GTK_ARROW_LEFT, TRUE, 2, sz/2-arrow_sz/2, arrow_sz, arrow_sz); #else gtk_paint_arrow (gtk_widget_get_style (widget), gtk_widget_get_window(widget), GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, widget, NULL, GTK_ARROW_LEFT, TRUE, 2, sz/2-arrow_sz/2, arrow_sz, arrow_sz); #endif cairo_rectangle (cr, a.width-arrow_widget_width, 1, arrow_widget_width, sz); cairo_fill (cr); #if GTK_CHECK_VERSION(3,0,0) gtk_paint_arrow (gtk_widget_get_style (widget), cr, GTK_STATE_NORMAL, GTK_SHADOW_NONE, widget, NULL, GTK_ARROW_RIGHT, TRUE, a.width-arrow_sz-2, 1+sz/2-arrow_sz/2, arrow_sz, arrow_sz); #else gtk_paint_arrow (gtk_widget_get_style (widget), gtk_widget_get_window(widget), GTK_STATE_NORMAL, GTK_SHADOW_NONE, NULL, widget, NULL, GTK_ARROW_RIGHT, TRUE, a.width-arrow_sz-2, 1+sz/2-arrow_sz/2, arrow_sz, arrow_sz); #endif } #endif draw_end (&ts->drawctx); } static int get_tab_under_cursor (DdbTabStrip *ts, int x) { int hscroll = ts->hscrollpos; int need_arrows = tabstrip_need_arrows (ts); if (need_arrows) { hscroll -= arrow_widget_width; } int idx; int cnt = deadbeef->plt_get_count (); int fw = tabs_left_margin - hscroll; int tab_selected = deadbeef->plt_get_curr_idx (); for (idx = 0; idx < cnt; idx++) { char title[1000]; plt_get_title_wrapper (idx, title, sizeof (title)); int w = 0; int h = 0; draw_get_text_extents (&ts->drawctx, title, strlen (title), &w, &h); w += text_left_padding + text_right_padding; if (w < min_tab_size) { w = min_tab_size; } else if (w > max_tab_size) { w = max_tab_size; } fw += w; fw -= tab_overlap_size; if (fw > x) { return idx; } } return -1; } static void tabstrip_scroll_left (DdbTabStrip *ts) { int tab = deadbeef->plt_get_curr_idx (); if (tab > 0) { tab--; gtkui_playlist_set_curr (tab); } tabstrip_scroll_to_tab (ts, tab); } static void tabstrip_scroll_right (DdbTabStrip *ts) { int tab = deadbeef->plt_get_curr_idx (); if (tab < deadbeef->plt_get_count ()-1) { tab++; gtkui_playlist_set_curr (tab); } tabstrip_scroll_to_tab (ts, tab); } gboolean tabstrip_scroll_cb (gpointer data) { DdbTabStrip *ts = DDB_TABSTRIP (data); if (ts->scroll_direction < 0) { tabstrip_scroll_left (ts); } else if (ts->scroll_direction > 0) { tabstrip_scroll_right (ts); } else { return FALSE; } return TRUE; } gboolean on_tabstrip_scroll_event(GtkWidget *widget, GdkEventScroll *event) { DdbTabStrip *ts = DDB_TABSTRIP (widget); if(event->direction == GDK_SCROLL_UP) { tabstrip_scroll_left(ts); } else if (event->direction == GDK_SCROLL_DOWN) { tabstrip_scroll_right(ts); } return TRUE; } gboolean on_tabstrip_button_press_event(GtkWidget *widget, GdkEventButton *event) { DdbTabStrip *ts = DDB_TABSTRIP (widget); tab_clicked = get_tab_under_cursor (ts, event->x); if (TEST_LEFT_CLICK(event)) { int need_arrows = tabstrip_need_arrows (ts); if (need_arrows) { GtkAllocation a; gtk_widget_get_allocation (widget, &a); if (event->x < arrow_widget_width) { if (event->type == GDK_BUTTON_PRESS) { tabstrip_scroll_left (ts); ts->scroll_direction = -1; ts->scroll_timer = g_timeout_add (300, tabstrip_scroll_cb, ts); } return TRUE; } else if (event->x >= a.width - arrow_widget_width) { if (event->type == GDK_BUTTON_PRESS) { tabstrip_scroll_right (ts); ts->scroll_direction = 1; ts->scroll_timer = g_timeout_add (300, tabstrip_scroll_cb, ts); } return TRUE; } } if (tab_clicked != -1) { gtkui_playlist_set_curr (tab_clicked); } else { if (event->type == GDK_2BUTTON_PRESS) { // new tab int playlist = gtkui_add_new_playlist (); if (playlist != -1) { gtkui_playlist_set_curr (playlist); } return TRUE; } return TRUE; } // adjust scroll if clicked tab spans border if (need_arrows) { tabstrip_scroll_to_tab (ts, tab_clicked); } int hscroll = ts->hscrollpos; if (need_arrows) { hscroll -= arrow_widget_width; } int x = -hscroll + tabs_left_margin; int idx; for (idx = 0; idx < tab_clicked; idx++) { int width = ddb_tabstrip_get_tab_width (ts, idx); x += width - tab_overlap_size; } ts->dragpt[0] = event->x - x; ts->dragpt[1] = event->y; ts->prepare = 1; ts->dragging = tab_clicked; ts->prev_x = event->x; tab_moved = 0; } else if (TEST_RIGHT_CLICK(event)) { GtkWidget *menu = gtkui_create_pltmenu (tab_clicked); gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, widget, 0, gtk_get_current_event_time()); } else if (event->button == 2) { if (tab_clicked == -1) { // new tab int playlist = gtkui_add_new_playlist (); if (playlist != -1) { gtkui_playlist_set_curr (playlist); } return TRUE; } else if (deadbeef->conf_get_int ("gtkui.mmb_delete_playlist", 1)) { if (tab_clicked != -1) { deadbeef->plt_remove (tab_clicked); // force invalidation of playlist cache search_refresh (); int playlist = deadbeef->plt_get_curr_idx (); deadbeef->conf_set_int ("playlist.current", playlist); } } } return TRUE; } gboolean on_tabstrip_button_release_event (GtkWidget *widget, GdkEventButton *event) { DdbTabStrip *ts = DDB_TABSTRIP (widget); if (event->button == 1) { if (ts->scroll_timer > 0) { ts->scroll_direction = 0; g_source_remove (ts->scroll_timer); ts->scroll_timer = 0; } if (ts->prepare || ts->dragging >= 0) { ts->dragging = -1; ts->prepare = 0; gtk_widget_queue_draw (widget); } } return FALSE; } gboolean on_tabstrip_configure_event (GtkWidget *widget, GdkEventConfigure *event) { DdbTabStrip *ts = DDB_TABSTRIP (widget); draw_init_font (&ts->drawctx, gtk_widget_get_style (widget)); tabstrip_adjust_hscroll (ts); int height = draw_get_listview_rowheight (&ts->drawctx) + 4; ts->calculated_height = height; GtkAllocation a; gtk_widget_get_allocation (widget, &a); if (height != a.height) { gtk_widget_set_size_request (widget, -1, height); } return FALSE; } gboolean on_tabstrip_draw (GtkWidget *widget, cairo_t *cr) { tabstrip_render (DDB_TABSTRIP (widget), cr); return FALSE; } gboolean on_tabstrip_expose_event (GtkWidget *widget, GdkEventExpose *event) { cairo_t *cr = gdk_cairo_create (gtk_widget_get_window (widget)); on_tabstrip_draw (widget, cr); cairo_destroy (cr); return FALSE; } gboolean on_tabstrip_motion_notify_event (GtkWidget *widget, GdkEventMotion *event) { DdbTabStrip *ts = DDB_TABSTRIP (widget); int ev_x, ev_y; GdkModifierType ev_state; ev_x = event->x; ev_y = event->y; ev_state = event->state; #if GTK_CHECK_VERSION(2,12,0) gdk_event_request_motions (event); #endif if ((ev_state & GDK_BUTTON1_MASK) && ts->prepare) { if (gtk_drag_check_threshold (widget, ev_x, ts->prev_x, 0, 0)) { ts->prepare = 0; } } if (!ts->prepare && ts->dragging >= 0) { // gdk_window_set_cursor (gtk_widget_get_window(widget), cursor_drag); ts->movepos = ev_x - ts->dragpt[0]; // find closest tab to the left int idx; int hscroll = ts->hscrollpos; int need_arrows = tabstrip_need_arrows (ts); if (need_arrows) { hscroll -= arrow_widget_width; } int x = -hscroll + tabs_left_margin; int inspos = -1; int cnt = deadbeef->plt_get_count (); for (idx = 0; idx < cnt; idx++) { int width = ddb_tabstrip_get_tab_width (ts, idx); if (idx != ts->dragging && x <= ts->movepos && x + width/2 - tab_overlap_size > ts->movepos) { inspos = idx; break; } x += width - tab_overlap_size; } if (inspos >= 0 && inspos != ts->dragging) { deadbeef->plt_move (ts->dragging, inspos); tab_moved = 1; ts->dragging = inspos; deadbeef->conf_set_int ("playlist.current", ts->dragging); } gtk_widget_queue_draw (widget); } else { int tab = get_tab_under_cursor (DDB_TABSTRIP (widget), event->x); if (tab >= 0) { char s[1000]; plt_get_title_wrapper (tab, s, sizeof (s)); int width; int height; draw_get_text_extents (&ts->drawctx, s, strlen (s), &width, &height); width += text_left_padding + text_right_padding; if (width > max_tab_size) { gtk_widget_set_tooltip_text (widget, s); gtk_widget_set_has_tooltip (widget, TRUE); } else { gtk_widget_set_has_tooltip (widget, FALSE); } } else { gtk_widget_set_has_tooltip (widget, FALSE); } } return FALSE; } gboolean on_tabstrip_drag_motion_event (GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, guint time) { int tab = get_tab_under_cursor (DDB_TABSTRIP (widget), x); int prev = deadbeef->plt_get_curr_idx (); if (tab != -1 && tab != prev) { gtkui_playlist_set_curr (tab); } GList *targets = gdk_drag_context_list_targets (drag_context); int cnt = g_list_length (targets); int i; for (i = 0; i < cnt; i++) { GdkAtom a = GDK_POINTER_TO_ATOM (g_list_nth_data (targets, i)); gchar *nm = gdk_atom_name (a); if (!strcmp (nm, "text/uri-list")) { g_free (nm); break; } g_free (nm); } if (i != cnt) { gdk_drag_status (drag_context, GDK_ACTION_COPY, time); } else { GdkModifierType mask; gdk_window_get_pointer (gtk_widget_get_window (widget), NULL, NULL, &mask); if (mask & GDK_CONTROL_MASK) { gdk_drag_status (drag_context, GDK_ACTION_COPY, time); } else { gdk_drag_status (drag_context, GDK_ACTION_MOVE, time); } } return FALSE; } void ddb_tabstrip_refresh (DdbTabStrip *ts) { gtk_widget_queue_draw (GTK_WIDGET (ts)); }