/*  BMP - Cross-platform multimedia player
 *  Copyright (C) 2003-2004  BMP development team.
 *
 *  Based on XMMS:
 *  Copyright (C) 1998-2003  XMMS development team.
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */


#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>

#include <X11/Xlib.h>
#include <glib.h>
#include <glib/gi18n.h>
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <glib/gprintf.h>



#include "libbeep/util.h"
#include "lyricwin.h"

#include "dnd.h"
#include "dock.h"
#include "hints.h"
#include "input.h"
#include "main.h"
#include "mainwin.h"
#include "playback.h"
#include "lyricwin.h"
#include "playlist_slider.h"
#include "playlist_popup.h"
#include "pbutton.h"
#include "sbutton.h"
#include "skin.h"
#include "textbox.h"
#include "util.h"

#include "pixmaps.h"
#include "images/bmp_playlist.xpm"

#define MAX_SEEK_NUM 20


struct context
{
    gint  num;                  /* how many times this text was used */
    gchar *text;
};

typedef struct context context;


typedef struct lyric_list lyric_list;
struct lyric_list
{
    gint seek;                  /* time */
    context *lyric;
    lyric_list *next, *prev;    /* double link */
};

GtkWidget *lyricwin;

PlayList_List *lyricwin_list = NULL;

/* lyric list head,   current lyric node point */
/* for lrc_curr is a trick: next keep the curr, seek keep the pos */
static lyric_list *lrc_list = NULL, lrc_curr;

//lyricwin_open button for open filebrowser to select and load lyric files
PButton *lyricwin_open, *lyricwin_close;
static PlaylistSlider *lyricwin_slider = NULL;

static gboolean lyricwin_resizing = FALSE;


static GdkPixmap *lyricwin_bg;
static GdkBitmap *lyricwin_mask = NULL;
static GdkGC *lyricwin_gc;

static GtkAccelGroup *lyricwin_accel;
static GList *lyricwin_wlist = NULL;


static void lyricwin_draw_frame(void);

PlayList_List * create_lyric_box(GList ** wlist,
                                 GdkPixmap * parent,
                                 GdkGC * gc,
                                 gint x, gint y,
                                 gint w, gint h);


PlaylistSlider * create_lyricslider(GList ** wlist,
                                    GdkPixmap * parent,
                                    GdkGC * gc, gint x, gint y, gint h,
                                    PlayList_List * list);

void draw_lyric_window(gboolean force);
static void lyricslider_set_pos(PlaylistSlider * ps, gint y) ;
void lyricslider_draw(Widget * w);


gint
lyricwin_get_width(void)
{
    return cfg.lyric_width;
}

gint
lyricwin_get_height_unshaded(void)
{
    return cfg.lyric_height;
}

gint
lyricwin_get_height(void)
{
    return lyricwin_get_height_unshaded();
}

void
lyricwin_get_size(gint * width, gint * height)
{
    if (width)
        *width = lyricwin_get_width();

    if (height)
        *height = lyricwin_get_height();
}

void
lyric_move_dir(const char *filename)
{
    PlaylistEntry *entry = playlist_get_curr_entry();
    g_return_if_fail(entry!=NULL);
    gchar *director = g_path_get_dirname(entry->filename);
    gchar dir[BUFFSIZE], file[BUFFSIZE];
    g_sprintf(dir, "%s/lrc", director);
    g_free(director);

    int src, dst;
    void  *sm,  *dm; 
    struct  stat  statbuf;
    
    //build the lrc path
    if (access(dir, F_OK) == -1 && mkdir(dir, 0755) < 0)
    {
        //if failed copy lrc file to default lyric path
        g_sprintf(file, "%s/%s.lrc", cfg.lyric_path, entry->title);
        dst = open(file, O_RDWR|O_CREAT|O_TRUNC, 0600);
        g_return_if_fail(dst>0);
    }
    else
    {
        g_sprintf(file, "%s/%s.lrc", dir, entry->title);
        dst = open(file, O_RDWR|O_CREAT|O_TRUNC, 0600);
        g_return_if_fail(dst>0);
    }        


    src=open( filename, O_RDONLY );
    g_return_if_fail(src>0);
    fstat(src,  &statbuf);
     
     
    if(lseek(dst, statbuf.st_size-1, SEEK_SET)<0)
    {
        perror("lseek target");
        return;
    } 
    if(write(dst,  &statbuf,  1)!=1)
    {
        perror(  " write target "  );
        return;
    } 
    
    /*  读的时候指定 MAP_PRIVATE 即可  */
    sm  =  mmap(  0 , ( size_t )statbuf.st_size, PROT_READ,
                  MAP_PRIVATE  |  MAP_NORESERVE, src,  0  );
    if  ( MAP_FAILED  ==  sm )
    {
        perror(  " mmap source "  );
        return;
    }
    /*  这里必须指定 MAP_SHARED 才可能真正改变静态文件  */
    dm  =  mmap(  0 , ( size_t )statbuf.st_size, PROT_WRITE,
                  MAP_SHARED, dst,  0  );
    if  ( MAP_FAILED  ==  dm )
    {
        perror(  " mmap target "  );
        return;
    }
    memcpy( dm, sm, ( size_t )statbuf.st_size );
    /*
     *
     * msync( dm, ( size_t )statbuf.st_size, MS_SYNC );
     */
    munmap(sm, statbuf.st_size);
    close(src);
    munmap(dm, statbuf.st_size);
    close(dst);
    return;
}

void
lyric_insert_list(lyric_list *head, lyric_list *node)
{
    lyric_list *p=head;
    while (p && p->seek <= node->seek)
    {
        if (!p->next || node->seek < p->next->seek)
            break;
        p = p->next;
    }
    if (p->next)
        p->next->prev = node;
    node->next = p->next;
    p->next = node;
    node->prev = p;
}


static gint
lyric_get_time(const gchar *buf)
{
    gint m, ret;
    gchar t[7] = {0};
    ret = sscanf(buf, "[%2d:%s]", &m, t);
    if (ret==2)
    {
        double dret = strtod(t, NULL);
        if (dret > 0.0F)
            ret = (gint)(m*60.0F + dret)*1000.0;        /* return seconds */
    }
    else
        ret = -1;
    return ret;
}


static gint
lyric_get(const gchar *buf, gint bufsize, gint seek[MAX_SEEK_NUM], gint size, gint *begin)
{
    gint i = 0, j=0, k=-1, time;          /* TRUE: useful lyric line, FALSE: skip this line */
    gchar tmp[256];
    *begin = 0;
    /* 
     * while(buf[i] && !isgraph(buf[i])) ++i;
     * if (buf[i]) return i;
     */
    while (i<bufsize && buf[i])
    {
        if (buf[i]=='[')
        {
            for (j=i+1; buf[j]; ++j)
            {
                if (buf[j] == ']')
                {
                    *begin = j;
                    strncpy(tmp, buf+i, j-i+1);
                    tmp[j-i+1] = '\0';
                    time = lyric_get_time(tmp);
                    if (time < 0) break;
                    for (k=0; k<size && seek[k]; ++k)
                        ;
                    if (k >= MAX_SEEK_NUM) return -1;
                    seek[k] = time;
                    break;
                }
            }
        }
        ++i;
    }

    return k+1;
}

static lyric_list *
lyricwin_load_lyric(FILE * fd)
{
    
    lyric_list *head = g_new0(lyric_list, 1);
    g_return_val_if_fail(head!=NULL, NULL);

    head->seek = 0;
    head->next = NULL;
    head->prev = NULL;
    head->lyric = g_new0(context, 1);
    head->lyric->num = 0;

    lyric_list *p;
    context *pt = NULL;
    gchar buf[BUFFSIZE];
    gint i=0, j, begin, len=1, seek[MAX_SEEK_NUM];
    while(fgets(buf,BUFFSIZE, fd))
    {
        memset(seek, 0, MAX_SEEK_NUM);
        
        i=lyric_get(buf, BUFFSIZE,  seek, MAX_SEEK_NUM, &begin);
        if (i)
        {
            pt =  g_new0(context, 1);
            pt->text = str_to_utf8(buf+begin+1);
            pt -> num = 0;
        }
        /* this lyric appear i times,  keep all the time */
        /* and the first node keep the i, so i+1 */
        for (j=0; j<i; ++j)
        {
            p = g_new0(lyric_list, 1);
            p->seek = seek[j];
            p->lyric = pt;
            pt -> num++;
            lyric_insert_list(head, p);
            ++len;
        }
        
    }
    head->seek = len;           /* first node keep the length of the list */
    return head;
}

gint
lyric_get_length(lyric_list *lrc_list)
{
    if (!lrc_list)
        return 0;
    return lrc_list->seek;
}

void
lyric_list_free()
{
    lyric_list *p = lrc_list;
    while (p)
    {
        lrc_list = p->next;
        if (p->lyric->num < 2)
        {
            free(p->lyric->text);
            free(p->lyric);
        }
        else
            p->lyric->num --;
        free(p);
        p = lrc_list;
    }
    return;
}

static void
lyricwin_update_info(void)
{

    PlaylistEntry *entry = playlist_get_curr_entry();
    g_return_if_fail(entry != NULL);
    
    if (lrc_list && strcmp(lrc_list->lyric->text, entry->title))
    {                           /* have already context and diff with before */
        lyric_list_free();
        lrc_list = NULL;
        lrc_curr.next = NULL;
        lrc_curr.seek = 0;
    }
    if (!lrc_list)
    {
        //strict mode only
        //entry->filename: path/audio.file
        //entry->title
        //lyric file:  path/lrc/title.lrc
        gchar *director = g_path_get_dirname(entry->filename);
        gchar file[1024];
        g_sprintf(file, "%s/lrc/%s.lrc", director, entry->title);
        g_free(director);

        FILE *fd = fopen(file, "r");
        if (!fd)
        {
            g_sprintf(file, "%s/%s.lrc", cfg.lyric_path, entry->title);
            fd = fopen(file, "r");
            g_return_if_fail(fd != NULL);
        }
        
        lrc_list =lyricwin_load_lyric(fd);
        if (lrc_list)
            lrc_list->lyric->text = g_strdup(entry->title);      /* first line set title */
    }
    lrc_curr.next = lrc_list;
    lrc_curr.seek =  0;     /* first nots */
}


void
lyricwin_update_lyric(void)
{
    g_return_if_fail(lyricwin != NULL);

    widget_draw(WIDGET(lyricwin_list));
    widget_draw(WIDGET(lyricwin_slider));
    lyricwin_update_info();
    draw_lyric_window(TRUE);
    
}


static void
lyricwin_set_mask(void)
{
    GdkGC *gc;
    GdkColor pattern;

    if (lyricwin_mask)
        g_object_unref(lyricwin_mask);

    lyricwin_mask =
        gdk_pixmap_new(lyricwin->window, lyricwin_get_width(),
                       lyricwin_get_height(), 1);
    gc = gdk_gc_new(lyricwin_mask);
    pattern.pixel = 1;
    gdk_gc_set_foreground(gc, &pattern);
    gdk_draw_rectangle(lyricwin_mask, gc, TRUE, 0, 0,
                       lyricwin_get_width(), lyricwin_get_height());
    gdk_gc_destroy(gc);

    gtk_widget_shape_combine_mask(lyricwin, lyricwin_mask, 0, 0);
}

static void
lyricwin_set_geometry_hints(gboolean shaded)
{
    GdkGeometry geometry;
    GdkWindowHints mask;

    geometry.min_width = LYRICWIN_MIN_WIDTH;
    geometry.max_width = G_MAXUINT16;
    geometry.base_width = cfg.lyric_width;

    geometry.width_inc = LYRICWIN_WIDTH_SNAP;
    geometry.height_inc = LYRICWIN_HEIGHT_SNAP;

    geometry.min_height = LYRICWIN_MIN_HEIGHT;
    geometry.max_height = G_MAXUINT16;
    geometry.base_height = cfg.lyric_height;

    mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE | GDK_HINT_RESIZE_INC |
        GDK_HINT_BASE_SIZE;

    gtk_window_set_geometry_hints(GTK_WINDOW(lyricwin),
                                  lyricwin, &geometry, mask);
}


void
lyricwin_open_toggle(void)
{
    util_run_filebrowser(NO_PLAY_BUTTON);
}

static void
lyricwin_release(GtkWidget * widget,
                    GdkEventButton * event,
                    gpointer callback_data)
{
    if (event->button == 3)
        return;

    gdk_pointer_ungrab(GDK_CURRENT_TIME);
    lyricwin_resizing = FALSE;
    gdk_flush();

    if (dock_is_moving(GTK_WINDOW(lyricwin))) {
        dock_move_release(GTK_WINDOW(lyricwin));
#if 0
        if (cfg.playlist_transparent)
            lyricwin_update_info();
#endif
    }
    else {
        handle_release_cb(lyricwin_wlist, widget, event);
        //playlist_popup_destroy();
        draw_lyric_window(FALSE);
    }
}

void
lyricwin_scroll(gint num)
{
    lyricwin_list->pl_first += num;
    //lyricwin_update_info();
    draw_lyric_window(TRUE);
}


void
lyricwin_set_back_pixmap(void)
{
    gdk_window_set_back_pixmap(lyricwin->window, lyricwin_bg, 0);
    gdk_window_clear(lyricwin->window);
}


static void
lyricwin_resize(gint width, gint height)
{
    gboolean redraw;

    g_return_if_fail(width > 0 && height > 0);

    cfg.lyric_width = width;

    cfg.lyric_height = height;

    widget_resize(WIDGET(lyricwin_list), width - 31, height - 29);

    if (height > 2 * LYRICWIN_MIN_HEIGHT)
    {
        widget_move(WIDGET(lyricwin_slider), width - 15, 20);
        widget_resize(WIDGET(lyricwin_slider), 8, height - 29);
        widget_show(WIDGET(lyricwin_slider));
    }
    else
        widget_hide(WIDGET(lyricwin_slider));
    
    widget_move(WIDGET(lyricwin_open), width - 21, 3);
    widget_move(WIDGET(lyricwin_close), width - 11, 3);
    
    g_object_unref(lyricwin_bg);
    lyricwin_bg = gdk_pixmap_new(lyricwin->window, width, height, -1);
    lyricwin_set_mask();

    widget_list_lock(lyricwin_wlist);

    widget_list_change_pixmap(lyricwin_wlist, lyricwin_bg);
    lyricwin_draw_frame();
    widget_list_draw(lyricwin_wlist, &redraw, TRUE);
    widget_list_clear_redraw(lyricwin_wlist);

    widget_list_unlock(lyricwin_wlist);

    lyricwin_set_back_pixmap();

}



static void
lyricwin_motion(GtkWidget * widget,
                   GdkEventMotion * event,
                   gpointer callback_data)
{
    XEvent ev;

    if (dock_is_moving(GTK_WINDOW(lyricwin))) {
        dock_move_motion(GTK_WINDOW(lyricwin), event);
    }
    else {
        handle_motion_cb(lyricwin_wlist, widget, event);
        draw_lyric_window(FALSE);
    }
    gdk_flush();
    while (XCheckMaskEvent(GDK_DISPLAY(), ButtonMotionMask, &ev));
}


#if 0
static void
lyricwin_add_dir_handler(const gchar * dir)
{
    g_free(cfg.filesel_path);
    cfg.filesel_path = g_strdup(dir);
    //playlist_add_dir(dir);
}
#endif


static gboolean
inside_sensitive_widgets(gint x, gint y)
{
    return (widget_contains(WIDGET(lyricwin_list), x, y) ||
            widget_contains(WIDGET(lyricwin_open), x, y) ||
            widget_contains(WIDGET(lyricwin_slider), x, y) ||
            widget_contains(WIDGET(lyricwin_close), x, y));
}

#define REGION_L(x1,x2,y1,y2)                   \
    (event->x >= (x1) && event->x < (x2) &&     \
     event->y >= cfg.lyric_height - (y1) &&  \
     event->y < cfg.lyric_height - (y2))

#define REGION_R(x1,x2,y1,y2)                      \
    (event->x >= lyricwin_get_width() - (x1) && \
     event->x < lyricwin_get_width() - (x2) &&  \
     event->y >= cfg.lyric_height - (y1) &&     \
     event->y < cfg.lyric_height - (y2))

static void
lyricwin_scrolled(GtkWidget * widget,
                     GdkEventScroll * event,
                     gpointer callback_data)
{

    if (event->direction == GDK_SCROLL_DOWN)
        lyricwin_scroll(cfg.scroll_pl_by);

    if (event->direction == GDK_SCROLL_UP)
        lyricwin_scroll(-cfg.scroll_pl_by);

}

static gboolean
lyricwin_press(GtkWidget * widget,
                  GdkEventButton * event,
                  gpointer callback_data)
{
    gboolean grab = TRUE;
    gint xpos, ypos;

    gtk_window_get_position(GTK_WINDOW(lyricwin), &xpos, &ypos);

    if (event->button == 1 && !cfg.show_wm_decorations &&
        ((event->x > lyricwin_get_width() - 20 &&
          event->y > cfg.lyric_height - 20)
         )) {

        /* NOTE: Workaround for bug #214 */
        if (event->type != GDK_2BUTTON_PRESS && 
            event->type != GDK_3BUTTON_PRESS) {
            /* resize area */
            lyricwin_resizing = TRUE;
            gtk_window_begin_resize_drag(GTK_WINDOW(widget),
                                         GDK_WINDOW_EDGE_SOUTH_EAST,
                                         event->button,
                                         event->x + xpos, event->y + ypos,
                                         event->time);
        }
        grab = FALSE;
    } else if (event->button == 1 && event->type == GDK_BUTTON_PRESS &&
             !inside_sensitive_widgets(event->x, event->y) && event->y < 14) {
        
        gdk_window_raise(lyricwin->window);
        dock_move_press(dock_window_list, GTK_WINDOW(lyricwin), event, FALSE);
        
    } else if (event->button == 1 && event->type == GDK_2BUTTON_PRESS &&
             !inside_sensitive_widgets(event->x, event->y)  && event->y < 14) {
        /* double click on title bar */
        //lyricwin_shade_toggle();
        if (dock_is_moving(GTK_WINDOW(lyricwin)))
            dock_move_release(GTK_WINDOW(lyricwin));
        return TRUE;
    } else {
        handle_press_cb(lyricwin_wlist, widget, event);
        draw_lyric_window(FALSE);
    }
    

    if (grab)
        gdk_pointer_grab(lyricwin->window, FALSE,
                         GDK_BUTTON_MOTION_MASK | GDK_BUTTON_RELEASE_MASK |
                         GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
                         GDK_BUTTON1_MOTION_MASK, NULL, NULL,
                         GDK_CURRENT_TIME);

    return FALSE;
}

static gboolean
lyricwin_focus_in(GtkWidget * widget, GdkEvent * event, gpointer data)
{
    lyricwin_close->pb_allow_draw = TRUE;
    lyricwin_open->pb_allow_draw = TRUE;
    draw_lyric_window(TRUE);
    return FALSE;
}

static gboolean
lyricwin_focus_out(GtkWidget * widget,
                      GdkEventButton * event, gpointer data)
{
    lyricwin_close->pb_allow_draw = FALSE;
    lyricwin_open->pb_allow_draw = FALSE;
    draw_lyric_window(TRUE);
    return FALSE;
}

static gboolean
lyricwin_configure(GtkWidget * window,
                      GdkEventConfigure * event, gpointer data)
{
    if (!GTK_WIDGET_VISIBLE(window))
        return FALSE;

    cfg.lyric_x = event->x;
    cfg.lyric_y = event->y;

    if (lyricwin_resizing) {
        if (event->width != lyricwin_get_width() || event->height != lyricwin_get_height())
            lyricwin_resize(event->width, event->height);
    }
    return TRUE;
}

static gboolean
lyricwin_delete(GtkWidget * w, gpointer data)
{
    lyricwin_hide();
    return TRUE;
}


static void
lyricwin_draw_frame(void)
{
    gboolean focus = (gtk_window_has_toplevel_focus(GTK_WINDOW(lyricwin)) || !cfg.dim_titlebar);
    skin_draw_lyricwin_frame(bmp_active_skin,
                             lyricwin_bg, lyricwin_gc,
                             lyricwin_get_width(),
                             cfg.lyric_height, focus);
}



void
draw_lyric_window(gboolean force)
{
    gboolean redraw;
    GList *wl;
    Widget *w;

    widget_list_lock(lyricwin_wlist);
    if (force)
        lyricwin_draw_frame();

    widget_list_draw(lyricwin_wlist, &redraw, force);
    
    if (redraw || force) {
        if (force) {
            gdk_window_clear(lyricwin->window);
        }
        else {
            for (wl = lyricwin_wlist; wl; wl = g_list_next(wl)) {
                w = WIDGET(wl->data);
                if (w->redraw && w->visible) {
                    gdk_window_clear_area(lyricwin->window, w->x, w->y,
                                          w->width, w->height);
                    w->redraw = FALSE;
                }
            }
        }
    
        gdk_flush();
    }
    
    widget_list_unlock(lyricwin_wlist);
}

static gboolean
lyricwin_key_press(GtkWidget * w, GdkEventKey * event, gpointer data)
{
    guint keyval;

    switch (keyval = event->keyval) {
    case GDK_Insert:
            util_run_filebrowser(NO_PLAY_BUTTON);
    }

    lyricwin_update_lyric();
    
    return TRUE;
}



static void
lyricwin_create_widgets(void)
{
    /* This function creates the custom widgets used by the playlist editor */

    /* text box for displaying song current Lyric in shaded mode */

    /* close window push button */
    lyricwin_close = create_pbutton(&lyricwin_wlist, lyricwin_bg, lyricwin_gc,
                                    lyricwin_get_width() - 11, 3, 9, 9,
                                    cfg.lyric_shaded ? 138 : 167,
                                    cfg.lyric_shaded ? 45 : 3, 52, 42,
                                    lyricwin_hide, SKIN_PLEDIT);
    lyricwin_close->pb_allow_draw = FALSE;
    
    lyricwin_open = create_pbutton(&lyricwin_wlist, lyricwin_bg,
                                    lyricwin_gc, lyricwin_get_width() - 21, 3,
                                    9, 9, 157, 3, 62, 42, lyricwin_open_toggle,
                                    SKIN_PLEDIT);
    lyricwin_open->pb_allow_draw = FALSE;
    

    lyricwin_list = create_lyric_box(&lyricwin_wlist, lyricwin_bg,
                                     lyricwin_gc, 12, 20,
                                     lyricwin_get_width() - 31,
                                     cfg.lyric_height - 29);
    
    playlist_list_set_font(cfg.playlist_font);

    /* playlist list box slider */
    lyricwin_slider =
        create_lyricslider(&lyricwin_wlist, lyricwin_bg,
                              lyricwin_gc, lyricwin_get_width() - 15,
                              20, cfg.lyric_height - 29, lyricwin_list);
    if (cfg.lyric_height <= LYRICWIN_MIN_HEIGHT * 2)
        widget_hide(WIDGET(lyricwin_slider));
    
}


static void
lyricwin_create_window(void)
{
    GdkPixbuf *icon;

    lyricwin = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(lyricwin), _("BMP Lyric Show"));
    gtk_window_set_role(GTK_WINDOW(lyricwin), "Lyric");
    gtk_window_set_default_size(GTK_WINDOW(lyricwin),
                                lyricwin_get_width(),
                                lyricwin_get_height());
    gtk_window_set_resizable(GTK_WINDOW(lyricwin), TRUE);
    lyricwin_set_geometry_hints(cfg.lyric_shaded);
    dock_window_list = dock_window_set_decorated(dock_window_list,
                                                 GTK_WINDOW(lyricwin),
                                                 cfg.show_wm_decorations);

    gtk_window_set_transient_for(GTK_WINDOW(lyricwin),
                                 GTK_WINDOW(mainwin));
    gtk_window_set_skip_taskbar_hint(GTK_WINDOW(lyricwin), TRUE);

    icon = gdk_pixbuf_new_from_xpm_data((const gchar **) bmp_playlist_icon);
    gtk_window_set_icon(GTK_WINDOW(lyricwin), icon);
    g_object_unref(icon);

    gtk_widget_set_app_paintable(lyricwin, TRUE);

    if (cfg.lyric_x != -1 && cfg.save_window_position)
        gtk_window_move(GTK_WINDOW(lyricwin),
                        cfg.lyric_x, cfg.lyric_y);

    gtk_widget_add_events(lyricwin,
                          GDK_FOCUS_CHANGE_MASK | GDK_BUTTON_MOTION_MASK |
                          GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
                          GDK_SCROLL_MASK | GDK_VISIBILITY_NOTIFY_MASK);
    gtk_widget_realize(lyricwin);

    util_set_cursor(lyricwin);

    g_signal_connect(lyricwin, "delete_event",
                     G_CALLBACK(lyricwin_delete), NULL);
    g_signal_connect(lyricwin, "button_press_event",
                     G_CALLBACK(lyricwin_press), NULL);
    g_signal_connect(lyricwin, "button_release_event",
                     G_CALLBACK(lyricwin_release), NULL);
    g_signal_connect(lyricwin, "scroll_event",
                     G_CALLBACK(lyricwin_scrolled), NULL);
    g_signal_connect(lyricwin, "motion_notify_event",
                     G_CALLBACK(lyricwin_motion), NULL);
    g_signal_connect_after(lyricwin, "focus_in_event",
                           G_CALLBACK(lyricwin_focus_in), NULL);
    g_signal_connect_after(lyricwin, "focus_out_event",
                           G_CALLBACK(lyricwin_focus_out), NULL);
    g_signal_connect(lyricwin, "configure_event",
                     G_CALLBACK(lyricwin_configure), NULL);
    g_signal_connect(lyricwin, "style_set",
                     G_CALLBACK(lyricwin_set_back_pixmap), NULL);


    g_signal_connect(lyricwin, "key_press_event",
                     G_CALLBACK(lyricwin_key_press), NULL);

    
    bmp_drag_dest_set(lyricwin);

    lyricwin_set_mask();
}

void
lyricwin_create_popup_menus(void)
{
    lyricwin_accel = gtk_accel_group_new();

}

void
lyricwin_create(void)
{
    lyricwin_create_window();
    //lyricwin_create_popup_menus();

    /* create GC and back pixmap for custom widget to draw on */
    lyricwin_gc = gdk_gc_new(lyricwin->window);
    lyricwin_bg = gdk_pixmap_new(lyricwin->window,
                                    lyricwin_get_width(),
                                    lyricwin_get_height_unshaded(), -1);
    gdk_window_set_back_pixmap(lyricwin->window, lyricwin_bg, 0);

    lyricwin_create_widgets();

    gtk_window_add_accel_group(GTK_WINDOW(lyricwin), lyricwin_accel);
    gtk_window_add_accel_group(GTK_WINDOW(lyricwin), mainwin_accel);
}


void
lyricwin_show(void)
{
    GtkWidget *item;

    item = gtk_item_factory_get_widget(mainwin_view_menu,
                                       "/Show Lyric Show");
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), TRUE);

    tbutton_set_toggled(mainwin_pl, TRUE);
    cfg.lyric_visible = TRUE;

    //lyricwin_set_toprow(0);
    //playlist_check_pos_current();

    gtk_widget_show(lyricwin);
}


void
lyricwin_hide(void)
{
    GtkWidget *item;

    item = gtk_item_factory_get_widget(mainwin_view_menu,
                                       "/Show Lyric Show");
    gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), FALSE);

    gtk_widget_hide(lyricwin);
    tbutton_set_toggled(mainwin_pl, FALSE);
    cfg.lyric_visible = FALSE;

    gtk_window_present(GTK_WINDOW(mainwin));
    gtk_widget_grab_focus(mainwin);
}


void
lyricwin_box_draw(Widget * w)
{
    PlayList_List *pl = PLAYLIST_LIST(w);
    GdkGC *gc;
    GdkPixmap *obj;
    gint width, height;
    gint i, max_first;

    gint plw_w, plw_h;

    GdkRectangle *playlist_rect;
    
    gc = pl->pl_widget.gc;

    width = pl->pl_widget.width;
    height = pl->pl_widget.height;

    obj = pl->pl_widget.parent;

    if (!playlist_list_font) {
        g_critical("Couldn't open playlist font");
        return;
    }
    
    gtk_window_get_size(GTK_WINDOW(lyricwin), &plw_w, &plw_h);

    playlist_rect = g_new0(GdkRectangle, 1);
    
    playlist_rect->x = 0;
    playlist_rect->y = 0;
    playlist_rect->width = plw_w - 17;
    playlist_rect->height = plw_h - 7;

    gdk_gc_set_clip_origin(gc, 31, 29);
    gdk_gc_set_clip_rectangle(gc, playlist_rect);
    
    gdk_gc_set_foreground(gc, skin_get_color(bmp_active_skin, SKIN_PLEDIT_NORMALBG));
    
    gdk_draw_rectangle(obj, gc, TRUE, pl->pl_widget.x, pl->pl_widget.y, width, height);


    pl->pl_fheight = (ascent + abs(descent));
    pl->pl_num_visible = height / pl->pl_fheight;

    max_first = lyric_get_length(lrc_list) - pl->pl_num_visible;
    max_first = MAX(max_first, 0);
    pl->pl_first = CLAMP(pl->pl_first, 0, max_first);

    lyric_list *p = lrc_list;
    for (i = 0; p && i < pl->pl_first; i++)
        p = p->next;

    for (i=0; p && i<pl->pl_num_visible; p=p->next)
    {
        gdk_gc_set_foreground(gc, skin_get_color(bmp_active_skin, SKIN_PLEDIT_NORMAL));
        playlist_list_draw_string(pl, playlist_list_font, i++, width, p->lyric->text, 0, FALSE);
    }

    /*
     * Drop target hovering over the playlist, so draw some hint where the
     * drop will occur.
     *
     * This is (currently? unfixably?) broken when dragging files from Qt/KDE apps,
     * probably due to DnD signaling problems (actually i have no clue).
     *
     */

    if (pl->pl_drag_motion)
    {
        guint pos, x, y, plx, ply, plength, lpadding;

        lpadding = 3;

        /* We already hold the mutex and have the playlist locked, so call
           the non-locking function. */
        //plength = playlist_get_length_nolock();
        plength = lyric_get_length(lrc_list);

        x = pl->drag_motion_x;
        y = pl->drag_motion_y;

        plx = pl->pl_widget.x;
        ply = pl->pl_widget.y;

        if ((pl->pl_widget.x < x) && (x <= pl->pl_widget.width) &&
            (pl->pl_widget.y < y) && (y <= pl->pl_widget.height + ply))
            {

                pos = ((y - ((Widget *) pl)->y) / pl->pl_fheight) + pl->pl_first;

                if (pos > (plength))
                    pos = plength;
                

                gdk_gc_set_foreground(gc, skin_get_color(bmp_active_skin, SKIN_PLEDIT_CURRENT));

                gdk_draw_line(obj, gc, pl->pl_widget.x,
                      /* pl->pl_widget.x + lpadding + (width_approx_letters / 4),*/
                              pl->pl_widget.y +
                              ((pos - pl->pl_first) * pl->pl_fheight),
                              pl->pl_widget.width + pl->pl_widget.x - 1,
                              pl->pl_widget.y +
                              ((pos - pl->pl_first) * pl->pl_fheight));

            }

        /* When dropping on the borders of the playlist, outside the text area,
         * files get appended at the end of the list. Show that too.
         */

        if ((y < ply || y > pl->pl_widget.height + ply) && (y >= 0 || y <= pl->pl_widget.height + ply))
        {
                pos = plength;
                gdk_gc_set_foreground(gc, skin_get_color(bmp_active_skin, SKIN_PLEDIT_CURRENT));

                gdk_draw_line(obj, gc, pl->pl_widget.x,
                /*	pl->pl_widget.x + lpadding + (width_approx_letters / 4), */
                              pl->pl_widget.y +
                              ((pos - pl->pl_first) * pl->pl_fheight),
                              pl->pl_widget.width + pl->pl_widget.x - 1,
                              pl->pl_widget.y +
                              ((pos - pl->pl_first) * pl->pl_fheight));
        }

    }

    gdk_gc_set_foreground(gc, skin_get_color(bmp_active_skin, SKIN_PLEDIT_NORMAL));

    playlist_rect->x = 0;
    playlist_rect->y = 0;
    playlist_rect->width = plw_w;
    playlist_rect->height = plw_h;

    gdk_gc_set_clip_origin(gc, 0, 0);
    gdk_gc_set_clip_rectangle(gc, NULL);
    
}


/* this function draw the lyrics quick when playing */
/* it was called once per 10micro seconds, so the more quick, the better */
void
lyricwin_draw_curr_lyric(gint time)
{
    g_return_if_fail(lrc_list!=NULL);

    if (time < 0 || time > playlist_get_current_length())
        return;

    if (cfg.lyric_height > LYRICWIN_MIN_HEIGHT * 2)
        return ;
    
    lyric_list *p = lrc_curr.next;
    int pos=lrc_curr.seek;
    
    /*notes: we did not check the last end node */
    for ( ; pos<lrc_list->seek - 1; ++pos, p=p->next)
    {
        //if (p->seek <= time && (!p->next || time < p->next->seek))
        if (p->seek <= time && time < p->next->seek)
        {
            lrc_curr.next = p;  /* lrc_curr.next just means current lyric */
            lrc_curr.seek = pos; /* keep the n-th node */
            break;
        }
    }
    
    /* check if the end node */
    if (pos == lrc_list->seek -1)
    {
        if (p->seek <= time)
        {
            lrc_curr.seek = pos;
            lrc_curr.next = p;
        }
        else if (time < lrc_list->next->seek) /* the first node */
        {
            pos = 0;
        }
    }
    
    PlayList_List *pl = lyricwin_list;
    
    gdk_gc_set_foreground(pl->pl_widget.gc, skin_get_color(bmp_active_skin, SKIN_PLEDIT_NORMALBG));
    
    gdk_draw_rectangle(pl->pl_widget.parent, pl->pl_widget.gc, TRUE,
                       pl->pl_widget.x, pl->pl_widget.y, pl->pl_widget.width, pl->pl_widget.height);

    //double link
    gint mid = (pl->pl_num_visible-1)/2;
    gint hl_pos = mid;          /* hight light position  */
    if (pos >= lrc_list->seek -1 - mid)
        hl_pos = pl->pl_num_visible - 1 - (lrc_list->seek - 1 - pos);
    if (pos < hl_pos)
        hl_pos = pos;

    //hight current lyric
    gdk_gc_set_foreground(pl->pl_widget.gc, skin_get_color(bmp_active_skin, SKIN_PLEDIT_SELECTEDBG));
    playlist_list_draw_string(pl, playlist_list_font, hl_pos, pl->pl_widget.width, lrc_curr.next->lyric->text, 0, FALSE);

    //draw the remain lyrics
    int min_first = MAX(0, hl_pos - pl->pl_num_visible);
    int i = hl_pos -1;
    /* lrc_curr.next keep the current, go on draw prevs */
    for (p=(lrc_curr.next)->prev; i>=min_first; p = p->prev, --i)
    {
        gdk_gc_set_foreground(pl->pl_widget.gc, skin_get_color(bmp_active_skin, SKIN_PLEDIT_NORMAL));
        playlist_list_draw_string(pl, playlist_list_font, i, pl->pl_widget.width, p->lyric->text, 0, FALSE);
    }

    int max_last = MIN(pl->pl_num_visible, lrc_list->seek);
    /* lrc_curr.next keep the current, go on draw next */
    for (p=(lrc_curr.next)->next, i=hl_pos+1; i<max_last; p = p->next, i++)
    {
        gdk_gc_set_foreground(pl->pl_widget.gc, skin_get_color(bmp_active_skin, SKIN_PLEDIT_NORMAL));
        playlist_list_draw_string(pl, playlist_list_font, i, pl->pl_widget.width, p->lyric->text, 0, FALSE);
    }

    gdk_window_clear(lyricwin->window);
    
}



PlayList_List *
create_lyric_box(GList ** wlist,
                     GdkPixmap * parent,
                     GdkGC * gc,
                     gint x, gint y,
                     gint w, gint h)
{
    PlayList_List *pl  = g_new0(PlayList_List, 1);
    
    widget_init(&pl->pl_widget, parent, gc, x, y, w, h, TRUE);

    pl->pl_widget.draw = lyricwin_box_draw;

    pl->pl_prev_selected = -1;
    pl->pl_prev_min = -1;
    pl->pl_prev_max = -1;

    widget_list_add(wlist, WIDGET(pl));

    return pl;
}


void
lyricslider_draw(Widget * w)
{
    PlaylistSlider *ps = (PlaylistSlider *) w;
    GdkPixmap *obj;
    gint y, skinx;

    g_return_if_fail(ps != NULL);
    g_return_if_fail(ps->ps_list != NULL);

    gint length = lyric_get_length(lrc_list);
    if (length > ps->ps_list->pl_num_visible)
        y = (ps->ps_list->pl_first * (ps->ps_widget.height - 19)) /
            (length - ps->ps_list->pl_num_visible);
    else
        y = 0;

    obj = ps->ps_widget.parent;

    if (ps->ps_back_image) {
        if (skin_get_id() != ps->ps_skin_id)
            ps->ps_skin_id = skin_get_id();
        else if (ps->ps_widget.height == ps->ps_prev_height)
            gdk_draw_image(obj, ps->ps_widget.gc,
                           ps->ps_back_image, 0, 0,
                           ps->ps_widget.x,
                           ps->ps_widget.y + ps->ps_prev_y, 8, 18);
        gdk_image_destroy(ps->ps_back_image);
    }

    ps->ps_prev_y = y;
    ps->ps_prev_height = ps->ps_widget.height;
    ps->ps_back_image = gdk_drawable_get_image(obj, ps->ps_widget.x,
                                               ps->ps_widget.y + y, 8, 18);
    if (ps->ps_is_draging)
        skinx = 61;
    else
        skinx = 52;

    skin_draw_pixmap(bmp_active_skin, obj, ps->ps_widget.gc, SKIN_PLEDIT,
                     skinx, 53, ps->ps_widget.x, ps->ps_widget.y + y, 8, 18);
}

void
lyricwin_set_toprow(gint toprow)
{
    if (lyricwin_list)
        lyricwin_list->pl_first = toprow;
    //lyricwin_update_info();
    draw_lyric_window(TRUE);
    //draw_lyric_only(TRUE);
    //lyricwin_box_draw(WIDGET(lyricwin_list));
    //widget_list_draw(lyricwin_wlist, &redraw, force);

}



static void
lyricslider_set_pos(PlaylistSlider * ps, gint y)
{
    gint pos;

    y = CLAMP(y, 0, ps->ps_widget.height - 19);

    pos = (y * (lyric_get_length(lrc_list) - ps->ps_list->pl_num_visible)) /
        (ps->ps_widget.height - 19);
    lyricwin_set_toprow(pos);
}


void
lyricslider_button_press_cb(GtkWidget * widget,
                               GdkEventButton * event, PlaylistSlider * ps)
{
    gint y = event->y - ps->ps_widget.y;

    if (!widget_contains(&ps->ps_widget, event->x, event->y))
        return;

    if (event->button != 1 && event->button != 2)
        return;

    if ((y >= ps->ps_prev_y && y < ps->ps_prev_y + 18)) {
        ps->ps_is_draging |= event->button;
        ps->ps_drag_y = y - ps->ps_prev_y;
        widget_draw(WIDGET(ps));
    }
    else if (event->button == 2) {
        lyricslider_set_pos(ps, y);
        ps->ps_is_draging |= event->button;
        ps->ps_drag_y = 0;
        widget_draw(WIDGET(ps));
    }
    else {
        gint n = ps->ps_list->pl_num_visible / 2;
        if (y < ps->ps_prev_y)
            n *= -1;
        lyricwin_scroll(n);
    }
}

void
lyricslider_button_release_cb(GtkWidget * widget,
                                 GdkEventButton * event,
                                 PlaylistSlider * ps)
{
    if (ps->ps_is_draging) {
        ps->ps_is_draging &= ~event->button;
        widget_draw(WIDGET(ps));
    }
}

void
lyricslider_motion_cb(GtkWidget * widget, GdkEventMotion * event,
                         PlaylistSlider * ps)
{
    gint y;

    if (!ps->ps_is_draging)
        return;

    y = event->y - ps->ps_widget.y - ps->ps_drag_y;
    lyricslider_set_pos(ps, y);
}

PlaylistSlider *
create_lyricslider(GList ** wlist, GdkPixmap * parent,
                      GdkGC * gc, gint x, gint y, gint h,
                      PlayList_List * list)
{
    PlaylistSlider *ps;

    ps = g_new0(PlaylistSlider, 1);
    widget_init(&ps->ps_widget, parent, gc, x, y, 8, h, 1);

    ps->ps_widget.button_press_cb =
        (void (*)(GtkWidget *, GdkEventButton *, gpointer))
        lyricslider_button_press_cb;

    ps->ps_widget.button_release_cb =
        (void (*)(GtkWidget *, GdkEventButton *, gpointer))
        lyricslider_button_release_cb;

    ps->ps_widget.motion_cb =
        (void (*)(GtkWidget *, GdkEventMotion *, gpointer))
        lyricslider_motion_cb;

    ps->ps_widget.draw = lyricslider_draw;
    ps->ps_list = list;

    widget_list_add(wlist, WIDGET(ps));
    return ps;
}


