diff options
Diffstat (limited to 'plugins/gtkui/gtkplaylist.c')
-rw-r--r-- | plugins/gtkui/gtkplaylist.c | 2031 |
1 files changed, 2031 insertions, 0 deletions
diff --git a/plugins/gtkui/gtkplaylist.c b/plugins/gtkui/gtkplaylist.c new file mode 100644 index 00000000..9bde04cf --- /dev/null +++ b/plugins/gtkui/gtkplaylist.c @@ -0,0 +1,2031 @@ +/* + DeaDBeeF - ultimate music player for GNU/Linux systems with X11 + Copyright (C) 2009 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, see <http://www.gnu.org/licenses/>. +*/ +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <math.h> +#include <stdlib.h> +#include <time.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> +#include <ctype.h> +#include <assert.h> +#include <sys/time.h> +#include "gtkplaylist.h" +#include "callbacks.h" +#include "interface.h" +#include "support.h" +#include "playlist.h" +#include "playback.h" +#include "codec.h" +#include "common.h" +#include "messagepump.h" +#include "streamer.h" +#include "search.h" +#include "progress.h" +#include "drawing.h" +#include "session.h" +#include "deadbeef.h" +#include "conf.h" +#include "timeline.h" + +//#define trace(...) { fprintf(stderr, __VA_ARGS__); } +#define trace(fmt,...) + +// debug function for gdk_draw_drawable +static inline void +draw_drawable (GdkDrawable *window, GdkGC *gc, GdkDrawable *drawable, int x1, int y1, int x2, int y2, int w, int h) { +// printf ("dd: %p %p %p %d %d %d %d %d %d\n", window, gc, drawable, x1, y1, x2, y2, w, h); + gdk_draw_drawable (window, gc, drawable, x1, y1, x2, y2, w, h); +} + + +extern GtkWidget *mainwin; +extern GtkStatusIcon *trayicon; +extern gtkplaylist_t main_playlist; + +// orange on dark color scheme +float colo_dark_orange[COLO_COUNT][3] = { + { 0x7f/255.f, 0x7f/255.f, 0x7f/255.f }, // cursor + { 0x1d/255.f, 0x1f/255.f, 0x1b/255.f }, // odd + { 0x21/255.f, 0x23/255.f, 0x1f/255.f }, // even + { 0xaf/255.f, 0xa7/255.f, 0x9e/255.f }, // sel odd + { 0xa7/255.f, 0x9f/255.f, 0x96/255.f }, // sel even + { 0xf4/255.f, 0x7e/255.f, 0x46/255.f }, // text + { 0, 0, 0 }, // sel text + { 0x1d/255.f, 0x1f/255.f, 0x1b/255.f }, // seekbar back + { 0xf4/255.f, 0x7e/255.f, 0x46/255.f }, // seekbar front + { 0x1d/255.f, 0x1f/255.f, 0x1b/255.f }, // volumebar back + { 0xf4/255.f, 0x7e/255.f, 0x46/255.f }, // volumebar front + { 0xf4/255.f, 0x7e/255.f, 0x46/255.f }, // dragdrop marker +}; + +float colo_white_blue[COLO_COUNT][3] = { + { 0x7f/255.f, 0x7f/255.f, 0x7f/255.f }, // cursor + { 1, 1, 1 }, // odd + { 0xea/255.f, 0xeb/255.f, 0xec/255.f }, // even + { 0x24/255.f, 0x89/255.f, 0xb8/255.f }, // sel odd + { 0x20/255.f, 0x85/255.f, 0xb4/255.f }, // sel even + { 0, 0, 0 }, // text + { 1, 1, 1 }, // sel text + { 0x1d/255.f, 0x1f/255.f, 0x1b/255.f }, // seekbar back + { 0x24/255.f, 0x89/255.f, 0xb8/255.f }, // seekbar front + { 0x1d/255.f, 0x1f/255.f, 0x1b/255.f }, // volumebar back + { 0x24/255.f, 0x89/255.f, 0xb8/255.f }, // volumebar front + { 0x09/255.f, 0x22/255.f, 0x3a/255.f }, // dragdrop marker +}; + +#define MIN_COLUMN_WIDTH 16 + +// current color scheme +float colo_current[COLO_COUNT][3]; + +// playlist row height +int rowheight = -1; + +// playlist scrolling during dragging +static int playlist_scroll_mode = 0; // 0=select, 1=dragndrop +static int playlist_scroll_pointer_y = -1; +static int playlist_scroll_direction = 0; +static int playlist_scroll_active = 0; +static struct timeval tm_prevscroll; +static float scroll_sleep_time = 0; + +static uintptr_t play16_pixbuf; +static uintptr_t pause16_pixbuf; +static uintptr_t buffering16_pixbuf; + +static GdkCursor* cursor_sz; +static GdkCursor* cursor_drag; +static int header_dragging = -1; +static int header_sizing = -1; +static int header_dragpt[2]; + +#define COLHDR_ANIM_TIME 0.2f + +typedef struct { + int c1; + int c2; + int x1, x2; + int dx1, dx2; + // animated values + int ax1, ax2; + timeline_t *timeline; + int anim_active; + gtkplaylist_t *pl; +} colhdr_animator_t; + +static colhdr_animator_t colhdr_anim; + +static gboolean +redraw_header (void *data) { + colhdr_animator_t *anim = (colhdr_animator_t *)data; + gtkpl_header_draw (anim->pl); + gtkpl_expose_header (anim->pl, 0, 0, anim->pl->header->allocation.width, anim->pl->header->allocation.height); + return FALSE; +} + +static int +colhdr_anim_cb (float _progress, int _last, void *_ctx) { + colhdr_animator_t *anim = (colhdr_animator_t *)_ctx; + anim->ax1 = anim->x1 + (float)(anim->dx1 - anim->x1) * _progress; + anim->ax2 = anim->x2 + (float)(anim->dx2 - anim->x2) * _progress; +// printf ("%f %d %d\n", _progress, anim->ax1, anim->ax2); + g_idle_add (redraw_header, anim); + if (_last) { + anim->anim_active = 0; + } + return 0; +} + +static void +colhdr_anim_swap (gtkplaylist_t *pl, int c1, int c2, int x1, int x2) { + // interrupt previous anim + if (!colhdr_anim.timeline) { + colhdr_anim.timeline = timeline_create (); + } + colhdr_anim.pl = pl; + + colhdr_anim.c1 = c1; + colhdr_anim.c2 = c2; + + // find c1 and c2 in column list and setup coords + // note: columns are already swapped, so their coords must be reversed, + // as if before swap + gtkpl_column_t *c; + int idx = 0; + int x = 0; + for (c = pl->columns; c; c = c->next, idx++) { + if (idx == c1) { + colhdr_anim.x1 = x1; + colhdr_anim.dx2 = x; + } + else if (idx == c2) { + colhdr_anim.x2 = x2; + colhdr_anim.dx1 = x; + } + x += c->width; + } + colhdr_anim.anim_active = 1; + timeline_stop (colhdr_anim.timeline, 0); + timeline_init (colhdr_anim.timeline, COLHDR_ANIM_TIME, 100, colhdr_anim_cb, &colhdr_anim); + timeline_start (colhdr_anim.timeline); +} + +// that must be called before gtk_init +void +gtkpl_init (void) { + //memcpy (colo_current, colo_system_gtk, sizeof (colo_current)); + //memcpy (colo_current, colo_dark_orange, sizeof (colo_current)); + play16_pixbuf = draw_load_pixbuf ("play_16.png"); + pause16_pixbuf = draw_load_pixbuf ("pause_16.png"); + buffering16_pixbuf = draw_load_pixbuf ("buffering_16.png"); + rowheight = draw_get_font_size () + 12; + memcpy (colo_current, colo_white_blue, sizeof (colo_current)); +} + +void +gtkpl_free (gtkplaylist_t *pl) { + if (colhdr_anim.timeline) { + timeline_free (colhdr_anim.timeline, 1); + colhdr_anim.timeline = 0; + } + while (pl->columns) { + gtkpl_column_t *next = pl->columns->next; + gtkpl_column_free (pl->columns); + pl->columns = next; + } +} + +void +theme_set_cairo_source_rgb (cairo_t *cr, int col) { + cairo_set_source_rgb (cr, colo_current[col][0], colo_current[col][1], colo_current[col][2]); +} + +void +theme_set_fg_color (int col) { + draw_set_fg_color (colo_current[col]); +} + +void +theme_set_bg_color (int col) { + draw_set_bg_color (colo_current[col]); +} + +void +gtkpl_setup_scrollbar (gtkplaylist_t *ps) { + GtkWidget *playlist = ps->playlist; + int h = playlist->allocation.height / rowheight; + int size = (*ps->pcount); + if (h >= size) { + size = 0; + } + GtkWidget *scroll = ps->scrollbar; + if (ps->row >= (*ps->pcount)) { + ps->row = (*ps->pcount) - 1; + } + if (ps->scrollpos > (*ps->pcount)-ps->nvisiblerows+1) { + int n = (*ps->pcount) - ps->nvisiblerows + 1; + ps->scrollpos = max (0, n); + gtk_range_set_value (GTK_RANGE (scroll), ps->scrollpos); + } + if (size == 0) { + gtk_widget_hide (scroll); + } + else { + GtkAdjustment *adj = (GtkAdjustment*)gtk_adjustment_new (gtk_range_get_value (GTK_RANGE (scroll)), 0, size, 1, h, h); + gtk_range_set_adjustment (GTK_RANGE (scroll), adj); + gtk_widget_show (scroll); + } +} + +void +gtkpl_setup_hscrollbar (gtkplaylist_t *ps) { + GtkWidget *playlist = ps->playlist; + int w = playlist->allocation.width; + int size = 0; + int i; + gtkpl_column_t *c; + for (c = ps->columns; c; c = c->next) { + size += c->width; + } + if (w >= size) { + size = 0; + } + GtkWidget *scroll = ps->hscrollbar; + if (ps->hscrollpos >= size-w) { + int n = size-w-1; + ps->hscrollpos = max (0, n); + gtk_range_set_value (GTK_RANGE (scroll), ps->hscrollpos); + } + if (size == 0) { + gtk_widget_hide (scroll); + } + else { + GtkAdjustment *adj = (GtkAdjustment*)gtk_adjustment_new (gtk_range_get_value (GTK_RANGE (scroll)), 0, size, 1, w, w); + gtk_range_set_adjustment (GTK_RANGE (scroll), adj); + gtk_widget_show (scroll); + } +} + +void +gtkpl_redraw_pl_row_novis (gtkplaylist_t *ps, int row, playItem_t *it) { + draw_begin ((uintptr_t)ps->backbuf); + gtkpl_draw_pl_row_back (ps, row, it); + if (it) { + gtkpl_draw_pl_row (ps, row, it); + } + draw_end (); +} + +gtkpl_redraw_pl_row (gtkplaylist_t *ps, int row, DB_playItem_t *it) { + if (row < ps->scrollpos || row >= ps->scrollpos+ps->nvisiblerows) { + return; + } + int x, y, w, h; + GtkWidget *widget = ps->playlist; + x = 0; + y = (row - ps->scrollpos) * rowheight; + w = widget->allocation.width; + h = rowheight; + + gtkpl_redraw_pl_row_novis (ps, row, it); + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, x, y, x, y, w, h); +} + +void +gtkpl_draw_pl_row_back (gtkplaylist_t *ps, int row, playItem_t *it) { + // draw background + float w; + int start, end; + int startx, endx; + int width, height; + draw_get_canvas_size ((uintptr_t)ps->backbuf, &width, &height); + w = width; + if (it && ((it->selected && ps->multisel) || (row == ps->row && !ps->multisel))) { + if (row % 2) { + theme_set_fg_color (COLO_PLAYLIST_SEL_EVEN); + } + else { + theme_set_fg_color (COLO_PLAYLIST_SEL_ODD); + } + draw_rect (0, row * rowheight - ps->scrollpos * rowheight, width, rowheight, 1); + } + else { + if (row % 2) { + theme_set_fg_color (COLO_PLAYLIST_EVEN); + } + else { + theme_set_fg_color (COLO_PLAYLIST_ODD); + } + draw_rect (0, row * rowheight - ps->scrollpos * rowheight, width, rowheight, 1); + } + if (row == ps->row) { + theme_set_fg_color (COLO_PLAYLIST_CURSOR); + draw_rect (0, row * rowheight - ps->scrollpos * rowheight, width, rowheight-1, 0); + } +} + +void +gtkpl_draw_pl_row (gtkplaylist_t *ps, int row, playItem_t *it) { + if (row-ps->scrollpos >= ps->nvisiblerows || row-ps->scrollpos < 0) { +// fprintf (stderr, "WARNING: attempt to draw row outside of screen bounds (%d)\n", row-ps->scrollpos); + return; + } + int width, height; + draw_get_canvas_size ((uintptr_t)ps->backbuf, &width, &height); + if (it && ((it->selected && ps->multisel) || (row == ps->row && !ps->multisel))) { + if (row % 2) { + theme_set_bg_color (COLO_PLAYLIST_SEL_EVEN); + } + else { + theme_set_bg_color (COLO_PLAYLIST_SEL_ODD); + } + theme_set_fg_color (COLO_PLAYLIST_SEL_TEXT); + } + else { + if (row % 2) { + theme_set_bg_color (COLO_PLAYLIST_EVEN); + } + else { + theme_set_bg_color (COLO_PLAYLIST_ODD); + } + theme_set_fg_color (COLO_PLAYLIST_TEXT); + } + // draw as columns + char dur[50]; + pl_format_title (it, dur, sizeof (dur), "%l"); + + const char *artist = pl_find_meta (it, "artist"); + if (!artist) { + artist = "?"; + } + const char *album = pl_find_meta (it, "album"); + if (!album) { + album = "?"; + } + const char *track = pl_find_meta (it, "track"); + if (!track) { + track = ""; + } + const char *title = pl_find_meta (it, "title"); + if (!title) { + title = "?"; + } + char artistalbum[1024]; + pl_format_title (it, artistalbum, sizeof (artistalbum), "%a - %b"); +#if 0 + const char *columns[pl_ncolumns] = { + "", + artistalbum, + track, + title, + dur + }; +#endif + int x = -ps->hscrollpos; + gtkpl_column_t *c; + for (c = ps->columns; c; c = c->next) { + if (it == playlist_current_ptr && c->id == DB_COLUMN_PLAYING/* && !p_isstopped ()*/) { + int paused = p_ispaused (); + int buffering = !streamer_ok_to_read (-1); + uintptr_t pixbuf; + if (paused) { + pixbuf = pause16_pixbuf; + } + else if (!buffering) { + pixbuf = play16_pixbuf; + } + else { + pixbuf = buffering16_pixbuf; + } + draw_pixbuf ((uintptr_t)ps->backbuf, pixbuf, x + c->width/2 - 8 - ps->hscrollpos, (row - ps->scrollpos) * rowheight + rowheight/2 - 8, 0, 0, 16, 16); + } + else { + char fmt_text[1024]; + const char *text = NULL; + if (c->id != -1) { + switch (c->id) { + case DB_COLUMN_ARTIST_ALBUM: + text = artistalbum; + break; + case DB_COLUMN_ARTIST: + text = artist; + break; + case DB_COLUMN_ALBUM: + text = album; + break; + case DB_COLUMN_TITLE: + text = title; + break; + case DB_COLUMN_DURATION: + text = dur; + break; + case DB_COLUMN_TRACK: + text = track; + break; + } + } + else if (c->format) { + pl_format_title (it, fmt_text, sizeof (fmt_text), c->format); + text = fmt_text; + } + if (text) { + if (c->align_right) { + draw_text_with_colors (x+5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, c->width-10, 1, text); + } + else { + draw_text_with_colors (x + 5, row * rowheight - ps->scrollpos * rowheight + rowheight/2 - draw_get_font_size ()/2 - 2, c->width-10, 0, text); + } + } + } + x += c->width; + } +} + + +void +gtkpl_draw_playlist (gtkplaylist_t *ps, int x, int y, int w, int h) { + GtkWidget *widget = ps->playlist; + if (!ps->backbuf) { + return; + } + draw_begin ((uintptr_t)ps->backbuf); + int row; + int row1; + int row2; + int row2_full; + row1 = max (0, y / rowheight + ps->scrollpos); + row2 = min ((*ps->pcount), (y+h) / rowheight + ps->scrollpos + 1); + row2_full = (y+h) / rowheight + ps->scrollpos + 1; + // draw background + playItem_t *it = gtkpl_get_for_idx (ps, ps->scrollpos); + playItem_t *it_copy = it; + for (row = row1; row < row2_full; row++) { + gtkpl_draw_pl_row_back (ps, row, it); + if (it) { + it = it->next[ps->iterator]; + } + } + it = it_copy; + int idx = 0; + for (row = row1; row < row2; row++, idx++) { + gtkpl_draw_pl_row (ps, row, it); + it = it->next[ps->iterator]; + } + + draw_end (); +} + +void +gtkpl_configure (gtkplaylist_t *ps) { + gtkpl_setup_scrollbar (ps); + gtkpl_setup_hscrollbar (ps); + GtkWidget *widget = ps->playlist; + if (ps->backbuf) { + g_object_unref (ps->backbuf); + ps->backbuf = NULL; + } + ps->nvisiblerows = ceil (widget->allocation.height / (float)rowheight); + ps->nvisiblefullrows = floor (widget->allocation.height / (float)rowheight); + ps->backbuf = gdk_pixmap_new (widget->window, widget->allocation.width, widget->allocation.height, -1); + + gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height); +} + +// change properties +gboolean +on_playlist_configure_event (GtkWidget *widget, + GdkEventConfigure *event, + gpointer user_data) +{ +// extern void main_playlist_init (GtkWidget *widget); +// main_playlist_init (widget); + GTKPL_PROLOGUE; + gtkpl_configure (ps); + return FALSE; +} + +void +gtkpl_expose (gtkplaylist_t *ps, int x, int y, int w, int h) { + GtkWidget *widget = ps->playlist; + if (widget->window) { + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, x, y, x, y, w, h); + } +} + +void +gtkpl_expose_header (gtkplaylist_t *ps, int x, int y, int w, int h) { + GtkWidget *widget = ps->header; + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf_header, x, y, x, y, w, h); +} + +void +gtkpl_select_single (gtkplaylist_t *ps, int sel) { + if (!ps->multisel) { + return; + } + int idx=0; + GtkWidget *widget = ps->playlist; + for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { + if (idx == sel) { + if (!it->selected) { + it->selected = 1; + gtkpl_redraw_pl_row (ps, idx, it); + } + } + else if (it->selected) { + it->selected = 0; + gtkpl_redraw_pl_row (ps, idx, it); + } + } +} + +// {{{ expected behaviour for mouse1 without modifiers: +// {{{ [+] if clicked unselected item: +// unselect all +// select clicked item +// ps->row = clicked +// redraw +// start 'area selection' mode +// }}} +// {{{ [+] if clicked selected item: +// ps->row = clicked +// redraw +// wait until next release or motion event, whichever is 1st +// if release is 1st: +// unselect all except clicked, redraw +// else if motion is 1st: +// enter drag-drop mode +// }}} +// }}} +static int areaselect = 0; +static int areaselect_x = -1; +static int areaselect_y = -1; +static int areaselect_dx = -1; +static int areaselect_dy = -1; +static int dragwait = 0; +static int shift_sel_anchor = -1; + +void +gtkpl_mouse1_pressed (gtkplaylist_t *ps, int state, int ex, int ey, double time) { + // cursor must be set here, but selection must be handled in keyrelease + if ((*ps->pcount) == 0) { + return; + } + GtkWidget *widget = ps->playlist; + // remember mouse coords for doubleclick detection + ps->lastpos[0] = ex; + ps->lastpos[1] = ey; + // select item + int y = ey/rowheight + ps->scrollpos; + if (y < 0 || y >= (*ps->pcount)) { + y = -1; + } + + if (time - ps->clicktime < 0.5 + && fabs(ps->lastpos[0] - ex) < 3 + && fabs(ps->lastpos[1] - ey) < 3) { + // doubleclick - play this item + if (ps->row != -1) { + playItem_t *it = gtkpl_get_for_idx (ps, ps->row); + it->selected = 1; + int r = pl_get_idx_of (it); + int prev = main_playlist.row; + if (prev != r) { + main_playlist.row = r; + if (prev != -1) { + gtkpl_redraw_pl_row (&main_playlist, prev, pl_get_for_idx (prev)); + } + if (r != -1) { + gtkpl_redraw_pl_row (&main_playlist, r, it); + } + } + messagepump_push (M_PLAYSONGNUM, 0, r, 0); + return; + } + + // prevent next click to trigger doubleclick + ps->clicktime = time-1; + } + else { + ps->clicktime = time; + } + + int sel = y; + if (y == -1) { + y = (*ps->pcount) - 1; + } + int prev = ps->row; + ps->row = y; + shift_sel_anchor = ps->row; + // handle multiple selection + if (ps->multisel) { + if (!(state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK))) + { + playItem_t *it = gtkpl_get_for_idx (ps, sel); + if (!it || !it->selected) { + // reset selection, and set it to single item + gtkpl_select_single (ps, sel); + areaselect = 1; + areaselect_x = ex; + areaselect_y = ey; + areaselect_dx = -1; + areaselect_dy = -1; + shift_sel_anchor = ps->row; + } + else { + dragwait = 1; + gtkpl_redraw_pl_row (ps, prev, gtkpl_get_for_idx (ps, prev)); + if (ps->row != prev) { + gtkpl_redraw_pl_row (ps, ps->row, gtkpl_get_for_idx (ps, ps->row)); + } + } + } + else if (state & GDK_CONTROL_MASK) { + // toggle selection + if (y != -1) { + playItem_t *it = gtkpl_get_for_idx (ps, y); + if (it) { + it->selected = 1 - it->selected; + gtkpl_redraw_pl_row (ps, y, it); + } + } + } + else if (state & GDK_SHIFT_MASK) { + // select range + int start = min (prev, ps->row); + int end = max (prev, ps->row); + int idx = 0; + for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { + if (idx >= start && idx <= end) { + if (!it->selected) { + it->selected = 1; + gtkpl_redraw_pl_row (ps, idx, it); + } + } + else { + if (it->selected) { + it->selected = 0; + gtkpl_redraw_pl_row (ps, idx, it); + } + } + } + } + if (ps->row != -1 && sel == -1) { + gtkpl_redraw_pl_row (ps, ps->row, gtkpl_get_for_idx (ps, ps->row)); + } + } + else { + if (ps->row != -1) { + gtkpl_redraw_pl_row (ps, ps->row, gtkpl_get_for_idx (ps, ps->row)); + } + } + if (prev != -1 && prev != ps->row) { + gtkpl_redraw_pl_row (ps, prev, gtkpl_get_for_idx (ps, prev)); + } + +} + +void +gtkpl_mouse1_released (gtkplaylist_t *ps, int state, int ex, int ey, double time) { + if (dragwait) { + dragwait = 0; + int y = ey/rowheight + ps->scrollpos; + gtkpl_select_single (ps, y); + } + else if (areaselect) { + playlist_scroll_direction = 0; + playlist_scroll_pointer_y = -1; + areaselect = 0; + } +} + +#if 0 +void +gtkpl_draw_areasel (GtkWidget *widget, int x, int y) { + // erase previous rect using 4 blits from ps->backbuffer + if (areaselect_dx != -1) { + int sx = min (areaselect_x, areaselect_dx); + int sy = min (areaselect_y, areaselect_dy); + int dx = max (areaselect_x, areaselect_dx); + int dy = max (areaselect_y, areaselect_dy); + int w = dx - sx + 1; + int h = dy - sy + 1; + //draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, sx, sy, sx, sy, dx - sx + 1, dy - sy + 1); + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, sx, sy, sx, sy, w, 1); + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, sx, sy, sx, sy, 1, h); + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, sx, sy + h - 1, sx, sy + h - 1, w, 1); + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, sx + w - 1, sy, sx + w - 1, sy, 1, h); + } + areaselect_dx = x; + areaselect_dy = y; + cairo_t *cr; + cr = gdk_cairo_create (widget->window); + if (!cr) { + return; + } + theme_set_fg_color (COLO_PLAYLIST_CURSOR); + cairo_set_antialias (cr, CAIRO_ANTIALIAS_NONE); + cairo_set_line_width (cr, 1); + int sx = min (areaselect_x, x); + int sy = min (areaselect_y, y); + int dx = max (areaselect_x, x); + int dy = max (areaselect_y, y); + cairo_rectangle (cr, sx, sy, dx-sx, dy-sy); + cairo_stroke (cr); + gtkpl_cairo_destroy (cr); +} +#endif + +static gboolean +gtkpl_scroll_playlist_cb (gpointer data) { + gtkplaylist_t *ps = (gtkplaylist_t *)data; + playlist_scroll_active = 1; + struct timeval tm; + gettimeofday (&tm, NULL); + if (tm.tv_sec - tm_prevscroll.tv_sec + (tm.tv_usec - tm_prevscroll.tv_usec) / 1000000.0 < scroll_sleep_time) { + return TRUE; + } + memcpy (&tm_prevscroll, &tm, sizeof (tm)); + if (playlist_scroll_pointer_y == -1) { + playlist_scroll_active = 0; + return FALSE; + } + if (playlist_scroll_direction == 0) { + playlist_scroll_active = 0; + return FALSE; + } + int sc = ps->scrollpos + playlist_scroll_direction; + if (sc < 0) { + playlist_scroll_active = 0; + return FALSE; + } + if (sc >= *ps->pcount) { + playlist_scroll_active = 0; + return FALSE; + } + GDK_THREADS_ENTER (); + gtk_range_set_value (GTK_RANGE (ps->scrollbar), sc); + if (playlist_scroll_mode == 0) { + GdkEventMotion ev; + ev.y = playlist_scroll_pointer_y; + gtkpl_mousemove (ps, &ev); + } + else if (playlist_scroll_mode == 1) { + gtkpl_track_dragdrop (ps, playlist_scroll_pointer_y); + } + GDK_THREADS_LEAVE (); + scroll_sleep_time -= 0.1; + if (scroll_sleep_time < 0.05) { + scroll_sleep_time = 0.05; + } + return TRUE; +} + +void +gtkpl_mousemove (gtkplaylist_t *ps, GdkEventMotion *event) { + if (dragwait) { + GtkWidget *widget = ps->playlist; + if (gtk_drag_check_threshold (widget, ps->lastpos[0], event->x, ps->lastpos[1], event->y)) { + dragwait = 0; + GtkTargetEntry entry = { + .target = "STRING", + .flags = GTK_TARGET_SAME_WIDGET, + .info = TARGET_SAMEWIDGET + }; + GtkTargetList *lst = gtk_target_list_new (&entry, 1); + gtk_drag_begin (widget, lst, GDK_ACTION_MOVE, TARGET_SAMEWIDGET, (GdkEvent *)event); + } + } + else if (areaselect) { + GtkWidget *widget = ps->playlist; + int y = event->y/rowheight + ps->scrollpos; + //if (y != shift_sel_anchor) + { + int start = min (y, shift_sel_anchor); + int end = max (y, shift_sel_anchor); + int idx=0; + for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { + if (idx >= start && idx <= end) { + if (!it->selected) { + it->selected = 1; + gtkpl_redraw_pl_row (ps, idx, it); + } + } + else if (it->selected) { + it->selected = 0; + gtkpl_redraw_pl_row (ps, idx, it); + } + } + } + + if (event->y < 10) { + playlist_scroll_mode = 0; + playlist_scroll_pointer_y = event->y; + playlist_scroll_direction = -1; + // start scrolling up + if (!playlist_scroll_active) { + scroll_sleep_time = 0.2; + gettimeofday (&tm_prevscroll, NULL); + g_idle_add (gtkpl_scroll_playlist_cb, ps); + } + } + else if (event->y > ps->playlist->allocation.height-10) { + playlist_scroll_mode = 0; + playlist_scroll_pointer_y = event->y; + playlist_scroll_direction = 1; + // start scrolling up + if (!playlist_scroll_active) { + scroll_sleep_time = 0.2; + gettimeofday (&tm_prevscroll, NULL); + g_idle_add (gtkpl_scroll_playlist_cb, ps); + } + } + else { + playlist_scroll_direction = 0; + playlist_scroll_pointer_y = -1; + } + // debug only + // gtkpl_draw_areasel (widget, event->x, event->y); + } +} + +void +gtkpl_handle_scroll_event (gtkplaylist_t *ps, int direction) { + GtkWidget *range = ps->scrollbar;; + GtkWidget *playlist = ps->playlist; + int h = playlist->allocation.height / rowheight; + int size = (*ps->pcount); + if (h >= size) { + size = 0; + } + if (size == 0) { + return; + } + // pass event to scrollbar + GtkAdjustment* adj = gtk_range_get_adjustment (GTK_RANGE (range)); + int newscroll = gtk_range_get_value (GTK_RANGE (range)); + if (direction == GDK_SCROLL_UP) { + newscroll -= 2; + } + else if (direction == GDK_SCROLL_DOWN) { + newscroll += 2; + } + gtk_range_set_value (GTK_RANGE (range), newscroll); +} + +void +gtkpl_scroll (gtkplaylist_t *ps, int newscroll) { + if (newscroll != ps->scrollpos) { + GtkWidget *widget = ps->playlist; + int di = newscroll - ps->scrollpos; + int d = abs (di); + if (d < ps->nvisiblerows) { + if (di > 0) { + draw_drawable (ps->backbuf, widget->style->black_gc, ps->backbuf, 0, d * rowheight, 0, 0, widget->allocation.width, widget->allocation.height-d * rowheight); + int i; + ps->scrollpos = newscroll; + int start = ps->nvisiblerows-d-1; + start = max (0, ps->nvisiblerows-d-1); + for (i = start; i <= ps->nvisiblerows; i++) { + gtkpl_redraw_pl_row_novis (ps, i+ps->scrollpos, gtkpl_get_for_idx (ps, i+ps->scrollpos)); + } + } + else { + draw_drawable (ps->backbuf, widget->style->black_gc, ps->backbuf, 0, 0, 0, d*rowheight, widget->allocation.width, widget->allocation.height); + ps->scrollpos = newscroll; + int i; + for (i = 0; i <= d+1; i++) { + gtkpl_redraw_pl_row_novis (ps, i+ps->scrollpos, gtkpl_get_for_idx (ps, i+ps->scrollpos)); + } + } + } + else { + ps->scrollpos = newscroll; + gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height); + } + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, 0, 0, 0, 0, widget->allocation.width, widget->allocation.height); + } +} + +void +gtkpl_hscroll (gtkplaylist_t *ps, int newscroll) { + if (newscroll != ps->hscrollpos) { + ps->hscrollpos = newscroll; + GtkWidget *widget = ps->playlist; + gtkpl_header_draw (ps); + gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height); + gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height); + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, 0, 0, 0, 0, widget->allocation.width, widget->allocation.height); + } +} + +void +gtkpl_randomsong (void) { +// <deprecated> +// p_stop (); +// pl_randomsong (); +} + +void +gtkpl_playsongnum (int idx) { +// <deprecated> +// p_stop (); +// streamer_set_nextsong (idx, 1); +} + +void +gtkpl_songchanged (gtkplaylist_t *ps, int from, int to) { + if (!dragwait && to != -1) { + GtkWidget *widget = ps->playlist; + if (conf_get_int ("playlist.scroll.followplayback", 0)) { + if (to < ps->scrollpos || to >= ps->scrollpos + ps->nvisiblefullrows) { + gtk_range_set_value (GTK_RANGE (ps->scrollbar), to - ps->nvisiblerows/2); + } + } + } + + if (from >= 0) { + gtkpl_redraw_pl_row (ps, from, gtkpl_get_for_idx (ps, from)); + } + if (to >= 0) { + gtkpl_redraw_pl_row (ps, to, gtkpl_get_for_idx (ps, to)); + } +} + +void +gtkpl_keypress (gtkplaylist_t *ps, int keyval, int state) { + GtkWidget *widget = ps->playlist; + GtkWidget *range = ps->scrollbar; + int prev = ps->row; + int newscroll = ps->scrollpos; +// C-f is now handled by gtk +// if ((keyval == GDK_F || keyval == GDK_f) && (state & GDK_CONTROL_MASK)) { +// search_start (); +// } +// else +// if ((keyval == GDK_A || keyval == GDK_a) && (state & GDK_CONTROL_MASK)) { +// // select all +// pl_select_all (); +// gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height); +// draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, 0, 0, 0, 0, widget->allocation.width, widget->allocation.height); +// return; +// } +// else if ((keyval == GDK_P || keyval == GDK_p) && (state & GDK_CONTROL_MASK)) { +// messagepump_push (M_PAUSESONG, 0, 0, 0); +// } +// else +// if (keyval == GDK_Return && ps->row != -1) { +// messagepump_push (M_PLAYSONGNUM, 0, ps->row, 0); +// return; +// } +// else +// if (keyval == GDK_Delete) { +// pl_delete_selected (); +// playlist_refresh (); +// return; +// } +// else + if (keyval == GDK_Down && ps->row < (*ps->pcount) - 1) { + ps->row++; + if (ps->row > ps->scrollpos + widget->allocation.height / rowheight - 1) { + newscroll = ps->row - widget->allocation.height / rowheight + 1; + } + } + else if (keyval == GDK_r) { + extern int replaygain; + replaygain = 1-replaygain; + fprintf (stderr, "replaygain=%d\n", replaygain); + } + else if (keyval == GDK_t) { + extern int replaygain_scale; + replaygain_scale = 1-replaygain_scale; + fprintf (stderr, "replaygain_scale=%d\n", replaygain_scale); + } + else if (keyval == GDK_Up && ps->row > 0) { + ps->row--; + if (ps->row < ps->scrollpos) { + newscroll = ps->row; + } + } + else if (keyval == GDK_Page_Down && ps->row < (*ps->pcount) - 1) { + ps->row += 10; + if (ps->row >= (*ps->pcount)) { + ps->row = (*ps->pcount) - 1; + } + if (ps->row > ps->scrollpos + widget->allocation.height / rowheight - 1) { + newscroll = ps->row - widget->allocation.height / rowheight + 1; + } + } + else if (keyval == GDK_Page_Up && ps->row > 0) { + ps->row -= 10; + if (ps->row < 0) { + ps->row = 0; + } + if (ps->row < ps->scrollpos) { + newscroll = ps->row; + } + } + else if (keyval == GDK_End && ps->row != (*ps->pcount) - 1) { + ps->row = (*ps->pcount) - 1; + if (ps->row > ps->scrollpos + widget->allocation.height / rowheight - 1) { + newscroll = ps->row - widget->allocation.height / rowheight + 1; + } + } + else if (keyval == GDK_Home && ps->row != 0) { + ps->row = 0; + if (ps->row < ps->scrollpos) { + newscroll = ps->row; + } + } + if (state & GDK_SHIFT_MASK) { + // select all between shift_sel_anchor and ps->row + if (prev != ps->row) { + int minvis = ps->scrollpos; + int maxvis = ps->scrollpos + ps->nvisiblerows-1; + int start = min (ps->row, shift_sel_anchor); + int end = max (ps->row, shift_sel_anchor); + int idx=0; + for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { + if (idx >= start && idx <= end) { + it->selected = 1; + if (idx >= minvis && idx <= maxvis) { + if (newscroll == ps->scrollpos) { + gtkpl_redraw_pl_row (ps, idx, it); + } + else { + gtkpl_redraw_pl_row_novis (ps, idx, it); + } + } + } + else if (it->selected) + { + it->selected = 0; + if (idx >= minvis && idx <= maxvis) { + if (newscroll == ps->scrollpos) { + gtkpl_redraw_pl_row (ps, idx, it); + } + else { + gtkpl_redraw_pl_row_novis (ps, idx, it); + } + } + } + } + } + } + else { + // reset selection, set new single cursor/selection + if (prev != ps->row) { + int minvis = ps->scrollpos; + int maxvis = ps->scrollpos + ps->nvisiblerows-1; + shift_sel_anchor = ps->row; + int idx=0; + for (playItem_t *it = playlist_head[ps->iterator]; it; it = it->next[ps->iterator], idx++) { + if (idx == ps->row) { + if (!it->selected) { + it->selected = 1; + if (idx >= minvis && idx <= maxvis) { + if (newscroll == ps->scrollpos) { + gtkpl_redraw_pl_row (ps, idx, it); + } + else { + gtkpl_redraw_pl_row_novis (ps, idx, it); + } + } + } + } + else if (it->selected) { + it->selected = 0; + if (idx >= minvis && idx <= maxvis) { + if (newscroll == ps->scrollpos) { + gtkpl_redraw_pl_row (ps, idx, it); + } + else { + gtkpl_redraw_pl_row_novis (ps, idx, it); + } + } + } + } + } + } + if (newscroll != ps->scrollpos) { + gtk_range_set_value (GTK_RANGE (range), newscroll); + } +} + +static int drag_motion_y = -1; + +void +gtkpl_track_dragdrop (gtkplaylist_t *ps, int y) { + GtkWidget *widget = ps->playlist; + if (drag_motion_y != -1) { + // erase previous track + draw_drawable (widget->window, widget->style->black_gc, ps->backbuf, 0, drag_motion_y * rowheight-3, 0, drag_motion_y * rowheight-3, widget->allocation.width, 7); + + } + if (y == -1) { + drag_motion_y = -1; + return; + } + draw_begin ((uintptr_t)widget->window); + drag_motion_y = y / rowheight; + + theme_set_fg_color (COLO_DRAGDROP_MARKER); + draw_rect (0, drag_motion_y * rowheight-1, widget->allocation.width, 3, 1); + draw_rect (0, drag_motion_y * rowheight-3, 3, 7, 1); + draw_rect (widget->allocation.width-3, drag_motion_y * rowheight-3, 3, 7, 1); + draw_end (); + if (y < 10) { + playlist_scroll_pointer_y = y; + playlist_scroll_direction = -1; + playlist_scroll_mode = 1; + // start scrolling up + if (!playlist_scroll_active) { + scroll_sleep_time = 0.2; + gettimeofday (&tm_prevscroll, NULL); + g_idle_add (gtkpl_scroll_playlist_cb, ps); + } + } + else if (y > ps->playlist->allocation.height-10) { + playlist_scroll_mode = 1; + playlist_scroll_pointer_y = y; + playlist_scroll_direction = 1; + // start scrolling up + if (!playlist_scroll_active) { + scroll_sleep_time = 0.2; + gettimeofday (&tm_prevscroll, NULL); + g_idle_add (gtkpl_scroll_playlist_cb, ps); + } + } + else { + playlist_scroll_direction = 0; + playlist_scroll_pointer_y = -1; + } +} + +void +gtkpl_handle_drag_drop (gtkplaylist_t *ps, int drop_y, uint32_t *d, int length) { + int drop_row = drop_y / rowheight + ps->scrollpos; + playItem_t *drop_before = gtkpl_get_for_idx (ps, drop_row); + while (drop_before && drop_before->selected) { + drop_before = drop_before->next[ps->iterator]; + } + // unlink items from playlist, and link together + playItem_t *head = NULL; + playItem_t *tail = NULL; + int processed = 0; + int idx = 0; + playItem_t *next = NULL; + for (playItem_t *it = playlist_head[ps->iterator]; it && processed < length; it = next, idx++) { + next = it->next[ps->iterator]; + if (idx == d[processed]) { + if (it->prev[ps->iterator]) { + it->prev[ps->iterator]->next[ps->iterator] = it->next[ps->iterator]; + } + else { + playlist_head[ps->iterator] = it->next[ps->iterator]; + } + if (it->next[ps->iterator]) { + it->next[ps->iterator]->prev[ps->iterator] = it->prev[ps->iterator]; + } + else { + playlist_tail[ps->iterator] = it->prev[ps->iterator]; + } + if (tail) { + tail->next[ps->iterator] = it; + it->prev[ps->iterator] = tail; + tail = it; + } + else { + head = tail = it; + it->prev[ps->iterator] = it->next[ps->iterator] = NULL; + } + processed++; + } + } + // find insertion point + playItem_t *drop_after = NULL; + if (drop_before) { + drop_after = drop_before->prev[ps->iterator]; + } + else { + drop_after = playlist_tail[ps->iterator]; + } + // insert in between + head->prev[ps->iterator] = drop_after; + if (drop_after) { + drop_after->next[ps->iterator] = head; + } + else { + playlist_head[ps->iterator] = head; + } + tail->next[ps->iterator] = drop_before; + if (drop_before) { + drop_before->prev[ps->iterator] = tail; + } + else { + playlist_tail[ps->iterator] = tail; + } +} + +void +on_playlist_drag_end (GtkWidget *widget, + GdkDragContext *drag_context, + gpointer user_data) +{ + GTKPL_PROLOGUE; + // invalidate entire cache - slow, but rare + gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height); + gtkpl_expose (ps, 0, 0, widget->allocation.width, widget->allocation.height); + playlist_scroll_direction = 0; + playlist_scroll_pointer_y = -1; +} + +void +strcopy_special (char *dest, const char *src, int len) { + while (len > 0) { + if (*src == '%' && len >= 3) { + int charcode = 0; + int byte; + byte = tolower (src[2]); + if (byte >= '0' && byte <= '9') { + charcode = byte - '0'; + } + else if (byte >= 'a' && byte <= 'f') { + charcode = byte - 'a' + 10; + } + else { + charcode = '?'; + } + if (charcode != '?') { + byte = tolower (src[1]); + if (byte >= '0' && byte <= '9') { + charcode |= (byte - '0') << 4; + } + else if (byte >= 'a' && byte <= 'f') { + charcode |= (byte - 'a' + 10) << 4; + } + else { + charcode = '?'; + } + } + *dest = charcode; + dest++; + src += 3; + len -= 3; + continue; + } + else { + *dest++ = *src++; + len--; + } + } + *dest = 0; +} + +int +gtkpl_add_file_info_cb (playItem_t *it, void *data) { + if (progress_is_aborted ()) { + return -1; + } + GDK_THREADS_ENTER(); + progress_settext (it->fname); + GDK_THREADS_LEAVE(); +#if 0 + GtkEntry *e = (GtkEntry *)data; + GDK_THREADS_ENTER(); + gtk_entry_set_text (GTK_ENTRY (e), it->fname); + GDK_THREADS_LEAVE(); + usleep (100); + countdown = 10; +#endif + return 0; +} + +void +gtkpl_add_fm_dropped_files (gtkplaylist_t *ps, char *ptr, int length, int drop_y) { + GDK_THREADS_ENTER(); + progress_show (); + GDK_THREADS_LEAVE(); + + int drop_row = drop_y / rowheight + ps->scrollpos; + playItem_t *drop_before = gtkpl_get_for_idx (ps, drop_row); + playItem_t *after = NULL; + if (drop_before) { + after = drop_before->prev[ps->iterator]; + } + else { + after = playlist_tail[ps->iterator]; + } + const uint8_t *p = (const uint8_t*)ptr; + while (*p) { + const uint8_t *pe = p; + while (*pe && *pe > ' ') { + pe++; + } + if (pe - p < 4096 && pe - p > 7) { + char fname[(int)(pe - p)]; + strcopy_special (fname, p, pe-p); + //strncpy (fname, p, pe - p); + //fname[pe - p] = 0; + int abort = 0; + playItem_t *inserted = pl_insert_dir (after, fname, &abort, gtkpl_add_file_info_cb, NULL); + if (!inserted && !abort) { + inserted = pl_insert_file (after, fname, &abort, gtkpl_add_file_info_cb, NULL); + } + if (inserted) { + after = inserted; + } + } + p = pe; + // skip whitespace + while (*p && *p <= ' ') { + p++; + } + } + free (ptr); + + GDK_THREADS_ENTER(); + progress_hide (); + playlist_refresh (); + GDK_THREADS_LEAVE(); +} + +void +gtkpl_handle_fm_drag_drop (gtkplaylist_t *ps, int drop_y, void *ptr, int length) { + // this happens when dropped from file manager + char *mem = malloc (length+1); + memcpy (mem, ptr, length); + mem[length] = 0; + // we don't pass control structure, but there's only one drag-drop view currently + messagepump_push (M_FMDRAGDROP, (uintptr_t)mem, length, drop_y); +} + +void +gtkpl_header_draw (gtkplaylist_t *ps) { + GtkWidget *widget = ps->header; + int x = -ps->hscrollpos; + int w = 100; + int h = widget->allocation.height; + const char *detail = "toolbar"; + + // fill background + gtk_paint_box (widget->style, ps->backbuf_header, GTK_STATE_NORMAL, GTK_SHADOW_OUT, NULL, NULL, detail, 0, 0, widget->allocation.width, widget->allocation.height); + draw_begin ((uintptr_t)ps->backbuf_header); + x = -ps->hscrollpos; + gtkpl_column_t *c; + int need_draw_moving = 0; + int idx = 0; + for (c = ps->columns; c; c = c->next, idx++) { + w = c->width; + int xx = x; + if (colhdr_anim.anim_active) { + if (idx == colhdr_anim.c2) { + xx = colhdr_anim.ax1; + } + else if (idx == colhdr_anim.c1) { + xx = colhdr_anim.ax2; + } + } + if (header_dragging < 0 || idx != header_dragging) { + if (xx >= widget->allocation.width) { + continue; + } + if (w > 0) { + gtk_paint_vline (widget->style, ps->backbuf_header, GTK_STATE_NORMAL, NULL, NULL, NULL, 0, h, xx+w - 2); + GdkColor *gdkfg = &widget->style->fg[0]; + float fg[3] = {(float)gdkfg->red/0xffff, (float)gdkfg->green/0xffff, (float)gdkfg->blue/0xffff}; + draw_set_fg_color (fg); + draw_text (xx + 5, h/2-draw_get_font_size()/2, c->width-10, 0, c->title); + } + } + else { + need_draw_moving = 1; + } + x += w; + } + if (need_draw_moving) { + x = -ps->hscrollpos; + idx = 0; + for (c = ps->columns; c; c = c->next, idx++) { + w = c->width; + if (idx == header_dragging) { + if (colhdr_anim.anim_active) { + if (idx == colhdr_anim.c2) { + x = colhdr_anim.ax1; + } + else if (idx == colhdr_anim.c1) { + x = colhdr_anim.ax2; + } + } + // draw empty slot + if (x < widget->allocation.width) { + gtk_paint_box (widget->style, ps->backbuf_header, GTK_STATE_ACTIVE, GTK_SHADOW_ETCHED_IN, NULL, NULL, "button", x, 0, w, h); + } + x = c->movepos; + if (x >= widget->allocation.width) { + break; + } + if (w > 0) { + gtk_paint_box (widget->style, ps->backbuf_header, GTK_STATE_SELECTED, GTK_SHADOW_OUT, NULL, NULL, "button", x, 0, w, h); + GdkColor *gdkfg = &widget->style->fg[0]; + float fg[3] = {(float)gdkfg->red/0xffff, (float)gdkfg->green/0xffff, (float)gdkfg->blue/0xffff}; + draw_set_fg_color (fg); + draw_text (x + 5, h/2-draw_get_font_size()/2, c->width-10, 0, c->title); + } + break; + } + x += w; + } + } + draw_end (); +} + +gboolean +on_header_expose_event (GtkWidget *widget, + GdkEventExpose *event, + gpointer user_data) +{ + GTKPL_PROLOGUE; + gtkpl_header_draw (ps); + gtkpl_expose_header (ps, event->area.x, event->area.y, event->area.width, event->area.height); + return FALSE; +} + + +gboolean +on_header_configure_event (GtkWidget *widget, + GdkEventConfigure *event, + gpointer user_data) +{ + GTKPL_PROLOGUE; + if (ps->backbuf_header) { + g_object_unref (ps->backbuf_header); + ps->backbuf_header = NULL; + } + ps->backbuf_header = gdk_pixmap_new (widget->window, widget->allocation.width, widget->allocation.height, -1); + gtkpl_header_draw (ps); + return FALSE; +} + + +void +on_header_realize (GtkWidget *widget, + gpointer user_data) +{ + // create cursor for sizing headers + int h = draw_get_font_size (); + gtk_widget_set_size_request (widget, -1, h + 10); + cursor_sz = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); + cursor_drag = gdk_cursor_new (GDK_FLEUR); +} + +float last_header_motion_ev = -1; +int prev_header_x = -1; + +gboolean +on_header_motion_notify_event (GtkWidget *widget, + GdkEventMotion *event, + gpointer user_data) +{ + GTKPL_PROLOGUE; + if (header_dragging >= 0) { + gdk_window_set_cursor (widget->window, cursor_drag); + gtkpl_column_t *c; + int i; + for (i = 0, c = ps->columns; i < header_dragging && c; c = c->next, i++); + c->movepos = event->x - header_dragpt[0]; + + // find closest column to the left + int inspos = -1; + gtkpl_column_t *cc; + int x = 0; + int idx = 0; + int x1 = -1, x2 = -1; + for (cc = ps->columns; cc; cc = cc->next, idx++) { + if (x < c->movepos && x + c->width > c->movepos) { + inspos = idx; + x1 = x; + } + else if (idx == header_dragging) { + x2 = x; + } + x += cc->width; + } + if (inspos >= 0 && inspos != header_dragging) { + int c1 = inspos; + int c2 = header_dragging; + // remove c from list + if (c == ps->columns) { + ps->columns = c->next; + } + else { + for (cc = ps->columns; cc; cc = cc->next) { + if (cc->next == c) { + cc->next = c->next; + } + } + } + c->next = NULL; + // reinsert c at position inspos update header_dragging to new idx + header_dragging = inspos; + if (inspos == 0) { + c->next = ps->columns; + ps->columns = c; + } + else { + idx = 0; + gtkpl_column_t *prev = NULL; + for (cc = ps->columns; cc; cc = cc->next, idx++, prev = cc) { + if (idx+1 == inspos) { + gtkpl_column_t *next = cc->next; + cc->next = c; + c->next = next; + break; + } + } + } +// colhdr_anim_swap (ps, c1, c2, x1, x2); + // force redraw of everything +// gtkpl_setup_hscrollbar (ps); + gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_column_update_config (ps, c, i); + } + else { + // only redraw that if not animating + gtkpl_header_draw (ps); + gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height); + } + } + else if (header_sizing >= 0) { + last_header_motion_ev = event->time; + prev_header_x = event->x; + gdk_window_set_cursor (widget->window, cursor_sz); + // get column start pos + int x = -ps->hscrollpos; + int i = 0; + gtkpl_column_t *c; + for (c = ps->columns; c && i < header_sizing; c = c->next, i++) { + x += c->width; + } + + int newx = event->x > x + MIN_COLUMN_WIDTH ? event->x : x + MIN_COLUMN_WIDTH; + c->width = newx - x; + gtkpl_setup_hscrollbar (ps); + gtkpl_header_draw (ps); + gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height); + gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_column_update_config (ps, c, i); + } + else { + int x = -ps->hscrollpos; + gtkpl_column_t *c; + for (c = ps->columns; c; c = c->next) { + int w = c->width; + if (w > 0) { // ignore collapsed columns (hack for search window) + if (event->x >= x + w - 2 && event->x <= x + w) { + gdk_window_set_cursor (widget->window, cursor_sz); + break; + } + else { + gdk_window_set_cursor (widget->window, NULL); + } + } + else { + gdk_window_set_cursor (widget->window, NULL); + } + x += w; + } + } + return FALSE; +} + +gboolean +on_header_button_press_event (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GTKPL_PROLOGUE; + if (event->button == 1) { + // start sizing/dragging + header_dragging = -1; + header_sizing = -1; + header_dragpt[0] = event->x; + header_dragpt[1] = event->y; + int x = -ps->hscrollpos; + int i = 0; + gtkpl_column_t *c; + for (c = ps->columns; c; c = c->next, i++) { + int w = c->width; + if (event->x >= x + w - 2 && event->x <= x + w) { + header_sizing = i; + header_dragging = -1; + break; + } + else if (event->x > x + 2 && event->x < x + w - 2) { + header_dragpt[0] = event->x - x; + header_dragging = i; + header_sizing = -1; + break; + } + x += w; + } + } + prev_header_x = -1; + last_header_motion_ev = -1; + return FALSE; +} + +gboolean +on_header_button_release_event (GtkWidget *widget, + GdkEventButton *event, + gpointer user_data) +{ + GTKPL_PROLOGUE; + if (event->button == 1) { + header_sizing = -1; + int x = 0; + gtkpl_column_t *c; + for (c = ps->columns; c; c = c->next) { + int w = c->width; + if (event->x >= x + w - 2 && event->x <= x + w) { + gdk_window_set_cursor (widget->window, cursor_sz); + break; + } + else { + gdk_window_set_cursor (widget->window, NULL); + } + x += w; + } + if (header_dragging >= 0) { + header_dragging = -1; + gtkpl_setup_hscrollbar (ps); + gtkpl_header_draw (ps); + gtkpl_expose_header (ps, 0, 0, ps->header->allocation.width, ps->header->allocation.height); + gtkpl_draw_playlist (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_expose (ps, 0, 0, ps->playlist->allocation.width, ps->playlist->allocation.height); + gtkpl_column_rewrite_config (ps); + } + } +// NOTE: disabled for 0.3.0 release +// else if (event->button == 3) { +// GtkWidget *menu = create_headermenu (); +// gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, widget, 0, gtk_get_current_event_time()); +// } + return FALSE; +} + +void +gtkpl_add_dir (gtkplaylist_t *ps, char *folder) { + GDK_THREADS_ENTER(); + progress_show (); + GDK_THREADS_LEAVE(); + pl_add_dir (folder, gtkpl_add_file_info_cb, NULL); + g_free (folder); + GDK_THREADS_ENTER(); + progress_hide (); + playlist_refresh (); + GDK_THREADS_LEAVE(); +} + +static void +gtkpl_adddir_cb (gpointer data, gpointer userdata) { + pl_add_dir (data, gtkpl_add_file_info_cb, userdata); + g_free (data); +} + +void +gtkpl_add_dirs (gtkplaylist_t *ps, GSList *lst) { + GDK_THREADS_ENTER(); + progress_show (); + GDK_THREADS_LEAVE(); + g_slist_foreach(lst, gtkpl_adddir_cb, NULL); + g_slist_free (lst); + GDK_THREADS_ENTER(); + progress_hide (); + playlist_refresh (); + GDK_THREADS_LEAVE(); +} + +static void +gtkpl_addfile_cb (gpointer data, gpointer userdata) { + pl_add_file (data, gtkpl_add_file_info_cb, userdata); + g_free (data); +} + +void +gtkpl_add_files (gtkplaylist_t *ps, GSList *lst) { + GDK_THREADS_ENTER(); + progress_show (); + GDK_THREADS_LEAVE(); + g_slist_foreach(lst, gtkpl_addfile_cb, NULL); + g_slist_free (lst); + GDK_THREADS_ENTER(); + progress_hide (); + playlist_refresh (); + GDK_THREADS_LEAVE(); +} + +int +gtkpl_get_idx_of (gtkplaylist_t *ps, playItem_t *it) { + playItem_t *c = playlist_head[ps->iterator]; + int idx = 0; + while (c && c != it) { + c = c->next[ps->iterator]; + idx++; + } + if (!c) { + return -1; + } + return idx; +} + +playItem_t * +gtkpl_get_for_idx (gtkplaylist_t *ps, int idx) { + playItem_t *it = playlist_head[ps->iterator]; + while (idx--) { + if (!it) + return NULL; + it = it->next[ps->iterator]; + } + return it; +} + +void +playlist_refresh (void) { + extern gtkplaylist_t main_playlist; + gtkplaylist_t *ps = &main_playlist; + gtkpl_setup_scrollbar (ps); + GtkWidget *widget = ps->playlist; + gtkpl_draw_playlist (ps, 0, 0, widget->allocation.width, widget->allocation.height); + gtkpl_expose (ps, 0, 0, widget->allocation.width, widget->allocation.height); + search_refresh (); +} + +gtkpl_column_t * +gtkpl_column_alloc (const char *title, int width, int id, const char *format, int align_right) { + gtkpl_column_t *c = malloc (sizeof (gtkpl_column_t)); + memset (c, 0, sizeof (gtkpl_column_t)); + c->title = strdup (title); + c->id = id; + c->format = format ? strdup (format) : NULL; + c->width = width; + c->align_right = align_right; + return c; +} + +void +gtkpl_column_append (gtkplaylist_t *pl, gtkpl_column_t *c) { + int idx = 0; + if (pl->columns) { + idx++; + gtkpl_column_t *tail = pl->columns; + while (tail->next) { + tail = tail->next; + idx++; + } + tail->next = c; + } + else { + pl->columns = c; + } + gtkpl_column_update_config (pl, c, idx); +} + +void +gtkpl_column_free (gtkpl_column_t *c) { + if (c->title) { + free (c->title); + } + if (c->format) { + free (c->format); + } + free (c); +} + +void +gtkpl_column_remove (gtkplaylist_t *pl, gtkpl_column_t *c) { + if (pl->columns == c) { + pl->columns = pl->columns->next; + gtkpl_column_free (c); + return; + } + gtkpl_column_t *cc = pl->columns; + while (cc) { + if (cc->next == c) { + cc->next = cc->next->next; + gtkpl_column_free (c); + return; + } + cc = cc->next; + } + assert (cc && "gtkpl: attempted to remove column that is not in list"); +} + +void +gtkpl_append_column_from_textdef (gtkplaylist_t *pl, const uint8_t *def) { + // syntax: "title" "format" id width alignright + char title[128]; + char format[128]; + int id; + int width; + int align_right; + // title + if (*def != '"') { + return; + } + def++; + if (*def == 0) { + return; + } + const uint8_t *e = def; + e++; + while (*e && *e != '"') { + e++; + } + if (*e == 0) { + return; + } + memcpy (title, def, e-def); + title[e-def] = 0; + // skip whitespace + def = e; + def++; + while (*def && *def <= ' ') { + def++; + } + if (*def == 0) { + return; + } + // format + if (*def != '"') { + return; + } + def++; + if (*def == 0) { + return; + } + e = def; + while (*e && *e != '"') { + e++; + } + if (*e == 0) { + return; + } + memcpy (format, def, e-def); + format[e-def] = 0; + // skip whitespace + def = e; + def++; + while (*def && *def <= ' ') { + def++; + } + if (*def == 0) { + return; + } + // id + e = def; + while (*e && (isdigit (*e) || *e == '-')) { + e++; + } + if (*e == 0) { + return; + } + { + char s[e-def+1]; + memcpy (s, def, e-def); + s[e-def] = 0; + id = atoi (s); + } + // skip whitespace + def = e; + def++; + while (*def && *def <= ' ') { + def++; + } + if (*def == 0) { + return; + } + // width + e = def; + while (*e && isdigit (*e)) { + e++; + } + if (*e == 0) { + return; + } + { + char s[e-def+1]; + memcpy (s, def, e-def); + s[e-def] = 0; + width = atoi (s); + } + // skip whitespace + def = e; + def++; + while (*def && *def <= ' ') { + def++; + } + if (*def == 0) { + return; + } + // align_right + e = def; + while (*e && isdigit (*e)) { + e++; + } + { + char s[e-def+1]; + memcpy (s, def, e-def); + s[e-def] = 0; + align_right = atoi (s); + } + gtkpl_column_append (pl, gtkpl_column_alloc (title, width, id, format[0] ? format : NULL, align_right)); +} + +void +gtkpl_column_update_config (gtkplaylist_t *pl, gtkpl_column_t *c, int idx) { + char key[128]; + char value[128]; + snprintf (key, sizeof (key), "%s.column.%d", pl->title, idx); + snprintf (value, sizeof (value), "\"%s\" \"%s\" %d %d %d", c->title, c->format ? c->format : "", c->id, c->width, c->align_right); + conf_set_str (key, value); +} + +void +gtkpl_column_rewrite_config (gtkplaylist_t *pl) { + char key[128]; + char value[128]; + snprintf (key, sizeof (key), "%s.column.", pl->title); + conf_remove_items (key); + + gtkpl_column_t *c; + int i = 0; + for (c = pl->columns; c; c = c->next, i++) { + snprintf (key, sizeof (key), "%s.column.%d", pl->title, i); + snprintf (value, sizeof (value), "\"%s\" \"%s\" %d %d %d", c->title, c->format ? c->format : "", c->id, c->width, c->align_right); + conf_set_str (key, value); + } +} + +void +set_tray_tooltip (const char *text) { +#if (GTK_MINOR_VERSION < 16) + gtk_status_icon_set_tooltip (trayicon, text); +#else + gtk_status_icon_set_tooltip_text (trayicon, text); +#endif +} + +void +gtkpl_current_track_changed (playItem_t *it) { + char str[600]; + char dname[512]; + pl_format_item_display_name (it, dname, 512); + snprintf (str, 600, "DeaDBeeF - %s", dname); + gtk_window_set_title (GTK_WINDOW (mainwin), str); + set_tray_tooltip (str); +} + +void +gtkpl_songchanged_wrapper (int from, int to) { + GDK_THREADS_ENTER (); + // update window title + if (from >= 0 || to >= 0) { + if (to >= 0) { + playItem_t *it = pl_get_for_idx (to); + if (it) { // it might have been deleted after event was sent + gtkpl_current_track_changed (it); + } + } + else { + gtk_window_set_title (GTK_WINDOW (mainwin), "DeaDBeeF"); + set_tray_tooltip ("DeaDBeeF"); + } + } + // update playlist view + gtkpl_songchanged (&main_playlist, from, to); + GDK_THREADS_LEAVE (); +} |