/*******************************************************************************
*                         Goggles Music Manager                                *
********************************************************************************
*           Copyright (C) 2006-2011 by Sander Jansen. All Rights Reserved      *
*                               ---                                            *
* 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 3 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.           *
********************************************************************************/
#include "gmdefs.h"
#include "GMList.h"
#include "GMDatabase.h"
#include "GMTrackDatabase.h"
#include "GMTrackList.h"
#include "GMTrackItem.h"
#include "GMTrackView.h"
#include "GMWindow.h"
#include "GMSource.h"
#include "GMSourceView.h"
#include "GMClipboard.h"
#include "GMDatabaseSource.h"
#include "GMPlayListSource.h"
#include "GMPlayerManager.h"
#include "GMTag.h"
#include "GMIconTheme.h"
#include "GMFilename.h"
#include "GMThread.h"
#include "GMSearch.h"
#include "GMAudioScrobbler.h"
#include "GMImportDialog.h"
#include <FXPNGIcon.h>
#include "icons.h"

static void updateTrackFilenames(GMTrackDatabase * db,FXIntList & tracks) {
  register FXint i=0;
  FXint numchanges=0;
  FXString mrl;
  GMTrack trackinfo;
  FXStringList newmrls;
  FXStringList oldmrls;

  if (!GMPlayerManager::instance()->getPreferences().export_format_template.contains("%T")) {
    FXMessageBox::error(GMPlayerManager::instance()->getMainWindow(),MBOX_OK,fxtr("Invalid Template"),fxtr("The provided template is invalid. The track title %%T needs to be specified.\nPlease fix the filename template in the preference panel."));
    return;
    }

  FXTextCodec * codec = GMFilename::findcodec(GMPlayerManager::instance()->getPreferences().export_encoding);

  FXuint options=0;

  if (GMPlayerManager::instance()->getPreferences().export_lowercase)
    options|=GMFilename::LOWERCASE;

  if (GMPlayerManager::instance()->getPreferences().export_lowercase_extension)
    options|=GMFilename::LOWERCASE_EXTENSION;

  if (GMPlayerManager::instance()->getPreferences().export_underscore)
    options|=GMFilename::NOSPACES;

  /// Create New Mrls.
  for (i=0;i<tracks.no();i++) {
    if (!db->getTrack(tracks[i],trackinfo)) {
      FXMessageBox::error(GMPlayerManager::instance()->getMainWindow(),MBOX_OK,fxtr("Database Error"),fxtr("Oops. Database Error"));
      return;
      }
    if (GMFilename::create(mrl,trackinfo,GMPlayerManager::instance()->getPreferences().export_format_template,GMPlayerManager::instance()->getPreferences().export_character_filter,options,codec) && mrl!=trackinfo.mrl) {
      newmrls.append(mrl);
      oldmrls.append(trackinfo.mrl);
      numchanges++;
      }
    else {
      newmrls.append(FXString::null);
      oldmrls.append(FXString::null);
      }
    }

  if (numchanges==0){
    FXMessageBox::information(GMPlayerManager::instance()->getMainWindow(),MBOX_OK,fxtr("No changes"),fxtr("Filenames did not require any changes"));
    delete codec;
    return;
    }


  /// Ask For Permission
  FXDialogBox dialog(GMPlayerManager::instance()->getMainWindow(),fxtr("Rename Audio Files?"),DECOR_TITLE|DECOR_BORDER|DECOR_RESIZE|DECOR_CLOSE,0,0,600,400,0,0,0,0,0,0);
  GMPlayerManager::instance()->getMainWindow()->create_dialog_header(&dialog,fxtr("Renaming Audio Files…"),fxtr("The following audio files are going to be renamed"));
  FXHorizontalFrame *closebox=new FXHorizontalFrame(&dialog,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,0,0,0,0);
  new GMButton(closebox,fxtr("&Rename"),NULL,&dialog,FXDialogBox::ID_ACCEPT,BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new GMButton(closebox,fxtr("&Cancel"),NULL,&dialog,FXDialogBox::ID_CANCEL,BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new FXSeparator(&dialog,SEPARATOR_GROOVE|LAYOUT_FILL_X|LAYOUT_SIDE_BOTTOM);

  FXVerticalFrame * main = new FXVerticalFrame(&dialog,LAYOUT_FILL_X|LAYOUT_FILL_Y);
  GMScrollFrame * sunken = new GMScrollFrame(main);
  GMList * list = new GMList(sunken,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_Y);

  for (i=0;i<tracks.no();i++) {
    if (!newmrls[i].empty()) {
      list->appendItem(newmrls[i]);
      }
    }

  if (dialog.execute()) {
    for (i=0;i<tracks.no();i++){
      if (!newmrls[i].empty()) {
        if (gm_make_path(FXPath::directory(newmrls[i]))) {
          if (!FXStat::exists(newmrls[i])) {
            if (FXFile::rename(oldmrls[i],newmrls[i])){
              db->setTrackFilename(tracks[i],newmrls[i]);
              }
            else {
              if (i+1<tracks.no()) {
                if (FXMessageBox::error(GMPlayerManager::instance()->getMainWindow(),MBOX_YES_NO,fxtr("Unable to rename file"),fxtrformat("Unable to rename:\n%s\n\nto:%s\nContinue renaming files?"),oldmrls[i].text(),newmrls[i].text())==MBOX_CLICKED_NO)
                  break;
                }
              else {
                FXMessageBox::error(GMPlayerManager::instance()->getMainWindow(),MBOX_OK,fxtr("Unable to rename file"),fxtrformat("Unable to rename:\n%s\n\nto:%s"),oldmrls[i].text(),newmrls[i].text());
                }
              }
            }
          }
        }
      }
    }
  delete codec;
  }


class GMFilenameTemplateDialog : public FXDialogBox {
FXDECLARE(GMFilenameTemplateDialog)
protected:
  FXFontPtr    font_fixed;
  FXDataTarget target_format_template;
  FXDataTarget target_export_lowercase;
  FXDataTarget target_export_lowercase_extension;
  FXDataTarget target_export_underscore;
  FXDataTarget target_export_encoding;
  FXDataTarget target_export_filter;
protected:
  GMFilenameTemplateDialog(){}
private:
  GMFilenameTemplateDialog(const GMFilenameTemplateDialog&);
  GMFilenameTemplateDialog &operator=(const GMFilenameTemplateDialog&);
public:
  GMFilenameTemplateDialog(FXWindow*);
  ~GMFilenameTemplateDialog();
  };

FXIMPLEMENT(GMFilenameTemplateDialog,FXDialogBox,0,0);

GMFilenameTemplateDialog::GMFilenameTemplateDialog(FXWindow*p) : FXDialogBox(p,FXString::null,DECOR_TITLE|DECOR_BORDER,0,0,0,0,0,0,0,0,0,0) {
  setTitle(tr("Filename Template"));

  const FXuint labelstyle=LAYOUT_CENTER_Y|LABEL_NORMAL|LAYOUT_RIGHT;

  target_format_template.connect(GMPlayerManager::instance()->getPreferences().export_format_template);
  target_export_lowercase.connect(GMPlayerManager::instance()->getPreferences().export_lowercase);
  target_export_lowercase_extension.connect(GMPlayerManager::instance()->getPreferences().export_lowercase_extension);
  target_export_underscore.connect(GMPlayerManager::instance()->getPreferences().export_underscore);
  target_export_encoding.connect(GMPlayerManager::instance()->getPreferences().export_encoding);
  target_export_filter.connect(GMPlayerManager::instance()->getPreferences().export_character_filter);

  /// Create a fixed font, about the same size as the normal font
  FXint size = FXApp::instance()->getNormalFont()->getSize();
  font_fixed = new FXFont(FXApp::instance(),"mono",(int)size/10,FXFont::Normal,FXFont::Straight,FONTENCODING_UNICODE,FXFont::NonExpanded,FXFont::Modern|FXFont::Fixed);

  FXVerticalFrame * main=new FXVerticalFrame(this,LAYOUT_FILL_X|LAYOUT_FILL_Y);

  new FXLabel(main,tr("Template may contain absolute or relative path, environment variables\nand ~. Relative paths are based on the location of the original file. The\nfile extension gets automatically added. The following macros\nmay be used:"),NULL,JUSTIFY_LEFT);
  FXLabel * label = new FXLabel(main,tr("%T - title                   %A - album name\n"
                                        "%P - album artist name       %p - track artist name\n"
                                        "%y - year                    %d - disc number\n"
                                        "%N - track number (2 digits) %n - track number      \n%G - genre"
                                        ),NULL,JUSTIFY_LEFT,0,0,0,0,30);
  label->setFont(font_fixed);

  new FXLabel(main,tr("Conditions may be used as well:"),NULL,JUSTIFY_LEFT);
  label = new FXLabel(main,tr("?c<a|b> - display a if c is not empty else display b.\n"
                                        "?c      - display c if not empty\n"
                                        ),NULL,JUSTIFY_LEFT,0,0,0,0,30);
  label->setFont(font_fixed);


  new FXSeparator(main,SEPARATOR_GROOVE|LAYOUT_FILL_X);

  FXMatrix * matrix = new FXMatrix(main,2,MATRIX_BY_COLUMNS|LAYOUT_FILL_X,0,0,0,0,0,0,4,0);
  new FXLabel(matrix,tr("Template:"),NULL,labelstyle);
  FXTextField * textfield = new GMTextField(matrix,20,&target_format_template,FXDataTarget::ID_VALUE,LAYOUT_FILL_X|TEXTFIELD_ENTER_ONLY|FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_COLUMN);
  textfield->setFont(font_fixed);

  new FXLabel(matrix,tr("Encoding:"),NULL,labelstyle);
  GMListBox * list_codecs = new GMListBox(matrix,&target_export_encoding,FXDataTarget::ID_VALUE,FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_COLUMN);
  for (int i=0;gmcodecnames[i]!=NULL;i++)
    list_codecs->appendItem(gmcodecnames[i]);
  list_codecs->setNumVisible(9);

  new FXLabel(matrix,tr("Exclude:"),NULL,labelstyle);
  textfield = new GMTextField(matrix,15,&target_export_filter,FXDataTarget::ID_VALUE,LAYOUT_FILL_X|TEXTFIELD_ENTER_ONLY|FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_COLUMN);
  textfield->setFont(font_fixed);

  new FXLabel(matrix,tr("Options:"),NULL,labelstyle);
  new GMCheckButton(matrix,tr("Replace spaces with underscores"),&target_export_underscore,FXDataTarget::ID_VALUE,LAYOUT_FILL_COLUMN|CHECKBUTTON_NORMAL);
  new FXFrame(matrix,FRAME_NONE);
  new GMCheckButton(matrix,fxtr("Lower case"),&target_export_lowercase,FXDataTarget::ID_VALUE,LAYOUT_FILL_COLUMN|CHECKBUTTON_NORMAL);
  new FXFrame(matrix,FRAME_NONE);
  new GMCheckButton(matrix,fxtr("Lower case extension"),&target_export_lowercase_extension,FXDataTarget::ID_VALUE,LAYOUT_FILL_COLUMN|CHECKBUTTON_NORMAL);
  new FXSeparator(main,SEPARATOR_GROOVE|LAYOUT_FILL_X);

  FXHorizontalFrame *closebox=new FXHorizontalFrame(main,LAYOUT_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,0,0,0,0,0,0,0,0);
  new GMButton(closebox,fxtr("&Close"),NULL,this,FXDialogBox::ID_ACCEPT,BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0,20,20);
  }

GMFilenameTemplateDialog::~GMFilenameTemplateDialog(){
  GMPlayerManager::instance()->getPreferences().export_format_template.trim();
  }



FXbool GMDatabaseClipboardData::request(FXDragType target,GMClipboard * clipboard){
  if (target==GMClipboard::urilistType){
    FXString uri;
    FXStringList filenames;
    db->getTrackFilenames(tracks,filenames);
    gm_convert_filenames_to_uri(filenames,uri);
    clipboard->setDNDData(FROM_CLIPBOARD,target,uri);
    return true;
    }
  else if (target==GMClipboard::kdeclipboard){
    clipboard->setDNDData(FROM_CLIPBOARD,target,"0");
    return true;
    }
  else if (target==GMClipboard::gnomeclipboard){
    FXString clipdata;
    FXStringList filenames;
    db->getTrackFilenames(tracks,filenames);
    gm_convert_filenames_to_gnomeclipboard(filenames,clipdata);
    clipboard->setDNDData(FROM_CLIPBOARD,target,clipdata);
    return true;
    }
  return false;
  }


FXDEFMAP(GMDatabaseSource) GMDatabaseSourceMap[]={
//  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_EDIT_GENRE,GMDatabaseSource::onCmdEditGenre),
//  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_EDIT_ARTIST,GMDatabaseSource::onCmdEditArtist),
//  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_EDIT_ALBUM,GMDatabaseSource::onCmdEditAlbum),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_EDIT_TRACK,GMDatabaseSource::onCmdEditTrack),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_DELETE_GENRE,GMDatabaseSource::onCmdDelete),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_DELETE_ARTIST,GMDatabaseSource::onCmdDelete),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_DELETE_ALBUM,GMDatabaseSource::onCmdDelete),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_DELETE_TRACK,GMDatabaseSource::onCmdDelete),

  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_EXPORT_GENRE,GMDatabaseSource::onCmdExportTracks),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_EXPORT_ARTIST,GMDatabaseSource::onCmdExportTracks),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_EXPORT_ALBUM,GMDatabaseSource::onCmdExportTracks),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_EXPORT_TRACK,GMDatabaseSource::onCmdExportTracks),

  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_COPY_ARTIST,GMDatabaseSource::onCmdCopyArtistAlbum),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_COPY_ALBUM,GMDatabaseSource::onCmdCopyArtistAlbum),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_COPY_TRACK,GMDatabaseSource::onCmdCopyTrack),
  FXMAPFUNC(SEL_DND_REQUEST,GMDatabaseSource::ID_COPY_ARTIST,GMDatabaseSource::onCmdRequestArtistAlbum),
  FXMAPFUNC(SEL_DND_REQUEST,GMDatabaseSource::ID_COPY_ALBUM,GMDatabaseSource::onCmdRequestArtistAlbum),
  FXMAPFUNC(SEL_DND_REQUEST,GMDatabaseSource::ID_COPY_TRACK,GMDatabaseSource::onCmdRequestTrack),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_EXPORT,GMDatabaseSource::onCmdExport),
  FXMAPFUNC(SEL_UPDATE,GMDatabaseSource::ID_EXPORT,GMDatabaseSource::onUpdExport),

  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_NEW_PLAYLIST,GMDatabaseSource::onCmdNewPlayList),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_IMPORT_PLAYLIST,GMDatabaseSource::onCmdImportPlayList),

  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_CLEAR,GMDatabaseSource::onCmdClear),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_INFO,GMDatabaseSource::onCmdInfo),
  FXMAPFUNC(SEL_DND_DROP,GMDatabaseSource::ID_DROP,GMDatabaseSource::onCmdDrop),

  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_PASTE,GMDatabaseSource::onCmdPaste),
  FXMAPFUNC(SEL_UPDATE,GMDatabaseSource::ID_PASTE,GMDatabaseSource::onUpdPaste),
  FXMAPFUNC(SEL_TIMEOUT,GMDatabaseSource::ID_TRACK_PLAYED,GMDatabaseSource::onCmdTrackPlayed),

  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_FILENAME_TEMPLATE,GMDatabaseSource::onCmdFilenameTemplate),
  FXMAPFUNC(SEL_COMMAND,GMDatabaseSource::ID_OPEN_FOLDER,GMDatabaseSource::onCmdOpenFolder)
  };

FXIMPLEMENT(GMDatabaseSource,GMSource,GMDatabaseSourceMap,ARRAYNUMBER(GMDatabaseSourceMap));

GMDatabaseSource* GMDatabaseSource::filterowner=NULL;

GMDatabaseSource::GMDatabaseSource() : dbowned(true), db(NULL),filtermask(0),hasfilter(false) {
  sort_browse=GMDBTrackItem::browseSort;
  }

GMDatabaseSource::GMDatabaseSource(GMTrackDatabase * database) : dbowned(true), db(database),filtermask(FILTER_DEFAULT),hasfilter(false)  {
  FXASSERT(db);
  sort_browse=GMDBTrackItem::browseSort;
  }

GMDatabaseSource::~GMDatabaseSource() {
  clearAlbumIconCache();
  if (dbowned) delete db;
  }


#include <stdlib.h> // need this for rand()

//generates a psuedo-random integer between min and max
int randint(int min, int max,unsigned int * random_seed)
{
  return min+int( ((double)(max-min+1))*rand_r(random_seed)/(RAND_MAX+1.0));
}


void GMDatabaseSource::shuffle(GMTrackList*list,FXuint sort_seed) const {
  list->setSortFunc(GMDBTrackItem::ascendingAlbum);
  list->sortItems();
  list->setSortFunc(NULL);

  /// Initial Value comes from sort_seed (read from registry and such...)
  FXuint random_seed = sort_seed;
  rand_r(&random_seed);

  FXint n;
  FXint nitems=list->getNumItems()-1;
  for (FXint i=0;i<nitems;i++){
    n=randint(i,nitems,&random_seed);
    FXASSERT(n<list->getNumItems());
    list->moveItem(i,n);
    }
  }



void GMDatabaseSource::configure(GMColumnList& list) const {
  list.no(10);
  list[0]=GMColumn(notr("No."),HEADER_TRACK,GMDBTrackItem::ascendingTrack,GMDBTrackItem::descendingTrack,43,(getPlayList()==-1) ? true : false ,true,0);
  list[1]=GMColumn(notr("Queue"),HEADER_QUEUE,GMDBTrackItem::ascendingQueue,GMDBTrackItem::descendingQueue,60,(getPlayList()==-1) ? false : true,false,1);
  list[2]=GMColumn(notr("Title"),HEADER_TITLE,GMDBTrackItem::ascendingTitle,GMDBTrackItem::descendingTitle,360,true,true,2);
  list[3]=GMColumn(notr("Artist"),HEADER_ARTIST,GMDBTrackItem::ascendingArtist,GMDBTrackItem::descendingArtist,400,true,false,3);
  list[4]=GMColumn(notr("Album Artist"),HEADER_ALBUM_ARTIST,GMDBTrackItem::ascendingAlbumArtist,GMDBTrackItem::descendingAlbumArtist,200,true,false,4);
  list[5]=GMColumn(notr("Album"),HEADER_ALBUM,GMDBTrackItem::ascendingAlbum,GMDBTrackItem::descendingAlbum,200,true,false,5);
  list[6]=GMColumn(notr("Disc"),HEADER_DISC,GMDBTrackItem::ascendingDisc,GMDBTrackItem::descendingDisc,43,false,false,6);
  list[7]=GMColumn(notr("Genre"),HEADER_GENRE,GMDBTrackItem::ascendingGenre,GMDBTrackItem::descendingGenre,200,true,false,7);
  list[8]=GMColumn(notr("Year"),HEADER_YEAR,GMDBTrackItem::ascendingYear,GMDBTrackItem::descendingYear,60,true,true,8);
  list[9]=GMColumn(notr("Time"),HEADER_TIME,GMDBTrackItem::ascendingTime,GMDBTrackItem::descendingTime,60,true,true,9);
  }


FXbool GMDatabaseSource::hasCurrentTrack(GMSource * src) const {
  if (src==this) return true;
  return false;
  }

FXbool GMDatabaseSource::findCurrent(GMTrackList * list,GMSource * src) {
  GMDatabaseSource * db = dynamic_cast<GMDatabaseSource*>(src);
  if (db && db->getCurrentTrack()!=-1 )
    return GMSource::findCurrent(list,db);
  return false;
  }


FXbool GMDatabaseSource::findCurrentArtist(GMList * list,GMSource * src) {
  GMDatabaseSource * dbs = dynamic_cast<GMDatabaseSource*>(src);
  if (dbs && dbs->getCurrentTrack()!=-1 ){
    FXint artist,album;
    db->getTrackAssociation(dbs->getCurrentTrack(),artist,album);
    if (artist==-1) return false;
    for (FXint i=0;i<list->getNumItems();i++){
      if (artist==(FXint)(FXival)list->getItemData(i)){
        list->selectItem(i);
        list->makeItemVisible(i);
        return true;
        }
      }
    }
  return false;
  }


FXbool GMDatabaseSource::findCurrentAlbum(GMList * list,GMSource * src) {
  GMDatabaseSource * dbs = dynamic_cast<GMDatabaseSource*>(src);
  if (dbs && dbs->getCurrentTrack()!=-1 ){
    FXint artist=-1,album=-1,i,j;
    GMAlbumListItem * item=NULL;
    db->getTrackAssociation(dbs->getCurrentTrack(),artist,album);
    if (album==-1) return false;
    for (i=0;i<list->getNumItems();i++){
      item = dynamic_cast<GMAlbumListItem*>(list->getItem(i));
      if (!item) continue;
      if (album==(FXint)(FXival)list->getItemData(i)){
        list->selectItem(i);
        list->makeItemVisible(i);
        return true;
        }
      for (j=0;j<item->albums.no();j++){
        if (album==item->albums[j]){
          list->selectItem(i);
          list->makeItemVisible(i);
          return true;
          }
        }
      }
    }
  return false;
  }


FXIcon * GMDatabaseSource::getAlbumIcon(FXint aid,FXbool cacheonly){
  FXIconSource src(FXApp::instance());
  GMCover * cover=NULL;
  FXIcon * albumicon=NULL;
  FXString path;
  albumicon = (FXIcon*)albumicons.find((void*)(FXival)aid);
  if (albumicon==NULL && !cacheonly && db->getAlbumTrack(aid,path)) {
    cover = GMCover::fromTag(path,48);
    if (cover==NULL) {
      cover = GMCover::fromPath(FXPath::directory(path),48);
      }
    if (cover) {
      albumicon = GMCover::toIcon(cover);
      albumicon->create();
      albumicons.insert((void*)(FXival)aid,(void*)albumicon);
      }
    }
  return albumicon;
  }

FXIcon * GMDatabaseSource::getAlbumIcon(){
  return getAlbumIcon(db->getTrackAlbum(current_track),false);
  }


FXbool GMDatabaseSource::hasTrack(const FXString & mrl,FXint & id) {
    return db->hasTrack(mrl,id);
    }


void GMDatabaseSource::clearAlbumIconCache() {
  FXIcon * albumicon;
#if FOXVERSION < FXVERSION(1,7,0)
  for (FXint i=0;i<albumicons.size();i++){
#else
  for (FXuint i=0;i<albumicons.size();i++){
#endif
    if (!albumicons.empty(i)) {
      albumicon = (FXIcon*)albumicons.value(i);
      delete albumicon;
      }
    }
  albumicons.clear();
  }



void GMDatabaseSource::initPlaylists(GMSourceList & sources) {
  FXIntList playlists;
  if (db->listPlaylists(playlists)) {
    for (FXint i=0;i<playlists.no();i++) {
      sources.append(new GMPlayListSource(db,playlists[i]));
      }
    }
  }

FXint GMDatabaseSource::getNumTracks() const{
  return db->getNumTracks();
  }

FXint GMDatabaseSource::getNumStreams() const{
  return db->getNumStreams();
  }


FXString GMDatabaseSource::getTrackFilename(FXint id) const{
  return db->getTrackFilename(id);
  }

FXbool GMDatabaseSource::getTrack(GMTrack & info) const{
  return db->getTrack(current_track,info);
  }

FXbool GMDatabaseSource::genre_context_menu(FXMenuPane * pane) {
  //new FXMenuCommand(pane,fxtr("Edit…\tF2\tEdit Genre."),GMIconTheme::instance()->icon_edit,this,GMDatabaseSource::ID_EDIT_GENRE);
//  new FXMenuCommand(pane,"Export" … "\t\tCopy associated tracks to destination.",GMIconTheme::instance()->icon_export,this,ID_EXPORT_GENRE);
//  new FXMenuSeparator(pane);
  new GMMenuCommand(pane,fxtr("Remove…\tDel\tRemove Genre from Library."),GMIconTheme::instance()->icon_delete,this,GMSource::ID_DELETE_GENRE);
  return true;
  }

FXbool GMDatabaseSource::artist_context_menu(FXMenuPane * pane){
  //new FXMenuCommand(pane,fxtr("Edit…\tF2\tEdit Artist."),GMIconTheme::instance()->icon_edit,this,GMDatabaseSource::ID_EDIT_ARTIST);
  new GMMenuCommand(pane,fxtr("Copy\tCtrl-C\tCopy associated tracks to the clipboard."),GMIconTheme::instance()->icon_copy,this,ID_COPY_ARTIST);
//  new FXMenuCommand(pane,"Export" … "\t\tCopy associated tracks to destination.",GMIconTheme::instance()->icon_export,this,ID_EXPORT_ARTIST);
  new FXMenuSeparator(pane);
  new GMMenuCommand(pane,fxtr("Remove…\tDel\tRemove associated tracks from library."),GMIconTheme::instance()->icon_delete,this,GMSource::ID_DELETE_ARTIST);
  return true;
  }

FXbool GMDatabaseSource::album_context_menu(FXMenuPane * pane){
  //new FXMenuCommand(pane,fxtr("Edit…\tF2\tEdit Album."),GMIconTheme::instance()->icon_edit,this,GMDatabaseSource::ID_EDIT_ALBUM);
  new GMMenuCommand(pane,fxtr("Copy\tCtrl-C\tCopy associated tracks to the clipboard."),GMIconTheme::instance()->icon_copy,this,ID_COPY_ALBUM);
//  new FXMenuCommand(pane,"Export" … "\t\tCopy associated tracks to destination.",GMIconTheme::instance()->icon_export,this,ID_EXPORT_ALBUM);
  new FXMenuSeparator(pane);
  new GMMenuCommand(pane,fxtr("Remove…\tDel\tRemove associated tracks from library."),GMIconTheme::instance()->icon_delete,this,GMSource::ID_DELETE_ALBUM);
  return true;
  }

FXbool GMDatabaseSource::track_context_menu(FXMenuPane * pane){
  new GMMenuCommand(pane,fxtr("Edit…\tF2\tEdit Track Information."),GMIconTheme::instance()->icon_edit,this,GMDatabaseSource::ID_EDIT_TRACK);
  new GMMenuCommand(pane,fxtr("Copy\tCtrl-C\tCopy track(s) to clipboard."),GMIconTheme::instance()->icon_copy,this,ID_COPY_TRACK);
//  new FXMenuCommand(pane,"Export" … "\t\tCopy tracks to destination.",GMIconTheme::instance()->icon_export,this,ID_EXPORT_TRACK);
  new FXMenuSeparator(pane);
  if (GMPlayerManager::instance()->getTrackView()->numTrackSelected()==1)
    new GMMenuCommand(pane,fxtr("Open Folder Location\t\tOpen Folder Location."),NULL,this,ID_OPEN_FOLDER);


//  if (GMPlayerManager::instance()->getTrackView()->numTrackSelected()==1)
//    new FXMenuCommand(pane,"Copy Folder Location\t\tCopy Folder Location to clipboard.",GMIconTheme::instance()->icon_copy,this,ID_COPY_LOCATION);
  new GMMenuCommand(pane,fxtr("Remove…\tDel\tRemove track(s) from library."),GMIconTheme::instance()->icon_delete,this,GMSource::ID_DELETE_TRACK);

/*
    if (getCurrentSourceType()==SOURCE_PLAYLIST && !browserframe->shown()) {
      new FXMenuCommand(&pane,"Remove from Playlist\t\tRemove track(s) from playlist.",icon_delete,this,ID_PLAYLIST_DEL_TRACK);
      new FXMenuSeparator(&pane);
      new FXMenuCommand(&pane,tr("Reorder Playlist"),NULL,this,ID_REORDER_PLAYLIST);
      }
*/
  return true;
  }

FXbool GMDatabaseSource::source_context_menu(FXMenuPane * pane){
//  new FXMenuCommand(pane,fxtr("Import Folder…\tCtrl-O\tImport Music from folder into Library"),GMIconTheme::instance()->icon_import,GMPlayerManager::instance()->getMainWindow(),GMWindow::ID_IMPORT_DIRS);
  new GMMenuCommand(pane,fxtr("New Play List…\t\tCreate a new play list."),GMIconTheme::instance()->icon_playlist,this,GMDatabaseSource::ID_NEW_PLAYLIST);
  new FXMenuSeparator(pane);
  new GMMenuCommand(pane,fxtr("Export…"),GMIconTheme::instance()->icon_export,this,GMDatabaseSource::ID_EXPORT);
  new GMMenuCommand(pane,fxtr("Information…\t\tLibrary Statistics"),GMIconTheme::instance()->icon_info,this,GMDatabaseSource::ID_INFO);
  new FXMenuSeparator(pane);
  new GMMenuCommand(pane,fxtr("Remove All Tracks\t\tRemove all tracks from the library"),GMIconTheme::instance()->icon_delete,this,GMDatabaseSource::ID_CLEAR);
  return true;
  }



FXbool GMDatabaseSource::dnd_source_accepts(FXDragType*types,FXuint ntypes){
  if (FXApp::instance()->getDragWindow()) return false;
  for (FXuint i=0;i<ntypes;i++){
    if (types[i]==GMClipboard::kdeclipboard) return true;
    else if (types[i]==FXWindow::urilistType) return true;
    }
  return false;
  }




FXbool GMDatabaseSource::setFilter(const FXString & text,FXuint mask){
  if (filtermask==mask && filter==text && filterowner==this) return false;

  filter=text;
  filtermask=mask;

  FXStringList keywords;
  FXString word,filterquery;
  FXbool quotes=false;

  for (FXint i=0;i<text.length();i++){
    if (text[i]=='\\' && (i+1)<text.length() && text[i+1]=='\"'){
      word+=text[i+1];
      i++;
      }
    else if (text[i]=='\"') {
      quotes=!quotes;
      }
    else if (Ascii::isSpace(text[i]) && !quotes) {
      if (!word.empty()) {
        FXchar * sf = sqlite3_mprintf("LIKE '%%%q%%'",word.text());
        keywords.append(FXString(sf));
        word=FXString::null;
        sqlite3_free(sf);
        }
      }
    else {
      word+=text[i];
      }
    }

   if (!word.empty()) {
     FXchar * sf = sqlite3_mprintf("LIKE '%%%q%%'",word.text());
     keywords.append(FXString(sf));
     word=FXString::null;
     sqlite3_free(sf);
     }

  db->database()->execute("DROP VIEW IF EXISTS filtered;");
  if (keywords.no() && filtermask) {
    FXString query = "CREATE TEMP VIEW filtered AS SELECT tracks.id as track ,albumartists.id as artist ,albums.id as album ,genre FROM tracks,artists AS trackartists, artists AS albumartists,albums,genres WHERE tracks.album == albums.id AND albumartists.id == albums.artist AND trackartists.id == tracks.artist AND tracks.genre == genres.id ";
    if (getPlayList()!=-1) {
      query+=" AND tracks.id IN (SELECT DISTINCT(track) FROM playlist_tracks WHERE playlist == " + GMStringVal(getPlayList()) + ") ";
      }
    for (FXint i=0;i<keywords.no();i++){
      query+=" AND (";

      filterquery.clear();

      if (filtermask&FILTER_TRACK)
        filterquery+=" title " + keywords[i];

      if (filtermask&FILTER_ARTIST) {
        if (!filterquery.empty()) filterquery+="OR";
        filterquery+=" albumartists.name " + keywords[i] + " OR trackartists.name " + keywords[i];
        }

      if (filtermask&FILTER_ALBUM) {
        if (!filterquery.empty()) filterquery+="OR";
        filterquery+=" albums.name " + keywords[i] ;
        }

      if (filtermask&FILTER_GENRE) {
        if (!filterquery.empty()) filterquery+="OR";
        filterquery+=" genres.name " + keywords[i] ;
        }
      query+=filterquery+")";
      }
#ifdef DEBUG
    FXlong start = fxgetticks();
#endif
    db->database()->execute(query);
#ifdef DEBUG
    FXlong end = fxgetticks();
    fxmessage("setFilter(): %lld\n",end-start);
#endif
    hasfilter=true;
    }
  else {
#ifdef DEBUG
    fxmessage("No Filter\n");
#endif
    hasfilter=false;
    }
  filterowner=this;
  return true;
  }

FXbool GMDatabaseSource::listGenres(GMList * list,FXIcon * icon) {
  FXint id;
  const FXchar * name;
  GMQuery q;
  FXString query;

#ifdef DEBUG
  FXlong start = fxgetticks();
#endif

  try {
    if (getPlayList()==-1) {
      if (!hasFilter()) {
        while(db->list_genres.execute()) {
          db->list_genres.getResult(0,id);
          name = db->list_genres.getResult(1);
          list->appendItem(name,icon,(void*)(FXival)id);
          }
        db->list_genres.reset();
#ifdef DEBUG
        FXlong end = fxgetticks();
        fxmessage("listGenres(): %30lld\n",end-start);
#endif
        return true;
        }
      else {
        query = "SELECT genres.id,genres.name FROM genres WHERE id IN (SELECT genre FROM filtered); ";
        }
      }
    else {
      if (!hasFilter()) {
        query = "SELECT id,name "
                "FROM genres "
                "WHERE id IN ( "
                  "SELECT DISTINCT(genre) "
                  "FROM tracks,playlist_tracks "
                  "WHERE id == playlist_tracks.track AND playlist_tracks.playlist == " + GMStringVal(getPlayList()) +
                  ");";
        }
      else {
        query = "SELECT genres.id,genres.name FROM genres WHERE id IN (SELECT genre FROM filtered); ";
        }
      }

    q.compile(db->database(),query);
    while( q.execute()){
      q.getResult(0,id);
      name=q.getResult(1);
      list->appendItem(name,icon,(void*)(FXival)id);
      }
    q.reset();

    }
  catch(FXCompileException &){
    list->clearItems();
    return false;
    }
  catch(FXExecuteException &){
    list->clearItems();
    return false;
    }
#ifdef DEBUG
  FXlong end = fxgetticks();
  fxmessage("listGenres(): %30lld\n",end-start);
#endif
  return true;
  }


FXbool GMDatabaseSource::listArtists(GMList * list,FXIcon * icon,const FXIntList & genrelist){
  GMListItem * item;
  const FXchar * name;
  FXint id;
  GMQuery q;
  FXString query;
  FXString filterquery;
#ifdef  DEBUG
  FXlong start = fxgetticks();
#endif

  try {
    if (!hasFilter()){
      if (genrelist.no()==0) {
        if (getPlayList()==-1) {
          query = "SELECT id,name FROM artists WHERE id IN (SELECT DISTINCT(artist) FROM albums);";
          }
        else{
          query = "SELECT DISTINCT(artists.id), artists.name "
                  "FROM artists,albums "
                  "WHERE albums.artist == artists.id AND albums.id IN ( "
                    "SELECT DISTINCT(album) FROM tracks WHERE id IN ( "
                      "SELECT DISTINCT(track) FROM playlist_tracks WHERE playlist == " + GMStringVal(getPlayList()) + "));";
          }
        }
      else {
        if (getPlayList()==-1) {
          if (genrelist.no()>1) {
            query = "SELECT id, name FROM artists "
                    "WHERE id IN ( "
                      "SELECT DISTINCT(artist) "
                      "FROM albums WHERE id IN ( "
                        "SELECT DISTINCT(album) FROM tracks ";

            query+=" WHERE genre IN ( " + GMStringVal(genrelist[0]);
            for (FXint i=1;i<genrelist.no();i++){
              query+=","+GMStringVal(genrelist[i]);
              }
            query+=") ) );";
            }
          else {
            query = "SELECT id, name FROM artists "
                    "WHERE id IN ( "
                      "SELECT DISTINCT(artist) "
                      "FROM albums WHERE id IN ( "
                        "SELECT DISTINCT(album) FROM tracks "
                        "WHERE genre == " + GMStringVal(genrelist[0]) +
                        ")"
                      ");";
            }
          }
        else{
          if (genrelist.no()>1) {
            query = "SELECT DISTINCT(artists.id), artists.name "
                    "FROM artists,albums "
                    "WHERE albums.artist == artists.id AND albums.id IN ( "
                      "SELECT DISTINCT(album) "
                      "FROM tracks ";

            query+=" WHERE genre IN ( " + GMStringVal(genrelist[0]);
            for (FXint i=1;i<genrelist.no();i++){
              query+=","+GMStringVal(genrelist[i]);
              }

            query+= " ) AND id IN ( SELECT DISTINCT(track) FROM playlist_tracks WHERE playlist == " + GMStringVal(getPlayList()) + "));";
            }
          else {
            query = "SELECT DISTINCT(artists.id), artists.name "
                    "FROM artists,albums "
                    "WHERE albums.artist == artists.id AND albums.id IN ( "
                      "SELECT DISTINCT(album) "
                      "FROM tracks "
                      "WHERE genre==" + GMStringVal(genrelist[0]) + " AND id IN (" +
                        "SELECT DISTINCT(track) FROM playlist_tracks WHERE playlist == " + GMStringVal(getPlayList()) +
                        ")"
                      ");";
            }
          }
        }
      }
    else {
      query = "SELECT artists.id, artists.name FROM artists WHERE id IN (SELECT artist FROM filtered ";
      if (genrelist.no()) {
        if (genrelist.no()>1) {
          query+=" WHERE genre IN ( " + GMStringVal(genrelist[0]);
          for (FXint i=1;i<genrelist.no();i++){
            query+=","+GMStringVal(genrelist[i]);
            }
          query+=" )";
          }
        else {
          query+=" WHERE genre == " + GMStringVal(genrelist[0]);
          }

        }
      query+=");";
      }
//		fxmessage("query: %s\n",query.text());
    q.compile(db->database(),query);
    while( q.execute()){
      q.getResult(0,id);
      name=q.getResult(1);
      item = new GMListItem(name,icon,(void*)(FXival)id);
      item->setDraggable(true);
      list->appendItem(item);
      }
    q.reset();
    }
  catch(FXCompileException &){
    list->clearItems();
    return false;
    }
  catch(FXExecuteException &){
    list->clearItems();
    return false;
    }
#ifdef DEBUG
  FXlong end = fxgetticks();
  fxmessage("listArtist(): %30lld\n",end-start);
#endif
  return true;
  }


FXbool GMDatabaseSource::listAlbums(GMList * list,FXIcon * icon,const FXIntList & artistlist,const FXIntList & genrelist){
  const FXchar * c_name=NULL;
  FXint id;
  FXint year;
  FXString name;
  FXString query;
  FXIcon * albumicon=NULL;

#ifdef DEBUG
  FXlong start = fxgetticks();
#endif

  GMAlbumListItem * item=NULL;
  GMQuery q;
  try {
    if (hasFilter()){
      query = "SELECT albums.id,albums.name,albums.year FROM albums WHERE id IN (SELECT album FROM filtered ";
      if (artistlist.no()>1) {
        query+=" WHERE artist IN ( " + GMStringVal(artistlist[0]);
        for (FXint i=1;i<artistlist.no();i++){
          query+=","+GMStringVal(artistlist[i]);
          }
        if (genrelist.no()) {
          if (genrelist.no()>1) {
            query+=" AND genre IN ( " + GMStringVal(genrelist[0]);
            for (FXint i=1;i<genrelist.no();i++){
              query+=","+GMStringVal(genrelist[i]);
              }
            query+=")";
            }
          else {
            query+=" AND genre == " + GMStringVal(genrelist[0]);
            }
          }
        query+=" )";
        }
      else if (artistlist.no()==1) {
        query+=" WHERE artist == " + GMStringVal(artistlist[0]);
        if (genrelist.no()) {
          if (genrelist.no()>1) {
            query+=" AND genre IN ( " + GMStringVal(genrelist[0]);
            for (FXint i=1;i<genrelist.no();i++){
              query+=","+GMStringVal(genrelist[i]);
              }
            query+=")";
            }
          else {
            query+=" AND genre == " + GMStringVal(genrelist[0]);
            }
          }
        }
      else if (genrelist.no()){
        if (genrelist.no()>1) {
          query+=" WHERE genre IN ( " + GMStringVal(genrelist[0]);
          for (FXint i=1;i<genrelist.no();i++){
            query+=","+GMStringVal(genrelist[i]);
            }
          query+=")";
          }
        else {
          query+=" WHERE genre == " + GMStringVal(genrelist[0]);
          }
        }
      query+=");";
      }
    else {

      if (artistlist.no()==0 && genrelist.no()==0 && getPlayList()==-1){
        query = "SELECT id, name, year FROM albums ORDER BY name;";
        }
      else if (genrelist.no()==0 && getPlayList()==-1) {
        query = "SELECT albums.id,albums.name, albums.year FROM albums ";
        if (artistlist.no()>1) {
          query+=" WHERE albums.artist IN ( " + GMStringVal(artistlist[0]);
          for (FXint i=1;i<artistlist.no();i++){
            query+=","+GMStringVal(artistlist[i]);
            }
          query+=" )";
          }
        else {
          query+=" WHERE albums.artist == " + GMStringVal(artistlist[0]);
          }
        query+=" ORDER BY albums.name;";
        }
      else {
        query = "SELECT DISTINCT(albums.id), albums.name, albums.year  FROM albums,tracks";


///        if (artistlist.no() || !filter.empty())
///query+=",album_artist";
        if (getPlayList()>=0)
          query+=",playlist_tracks";

//        if (artistlist.no() || !filter.empty())
//          query+=" WHERE album_artist.album == tracks.album AND album_artist.album == albums.id";
//        else
          query+=" WHERE albums.id == tracks.album";

        if (getPlayList()>=0) {
          query+=" AND playlist_tracks.track == tracks.id";
          query+=" AND playlist_tracks.playlist == "+GMStringVal(getPlayList());
          }

        if (genrelist.no()) {
          if (genrelist.no()>1) {
            query+=" AND genre IN ( " + GMStringVal(genrelist[0]);
            for (FXint i=1;i<genrelist.no();i++){
              query+=","+GMStringVal(genrelist[i]);
              }
            query+=")";
            }
          else {
            query+=" AND genre == " + GMStringVal(genrelist[0]);
            }
          }
        if (artistlist.no()>1) {
          query+=" AND albums.artist IN ( " + GMStringVal(artistlist[0]);
          for (FXint i=1;i<artistlist.no();i++){
            query+=","+GMStringVal(artistlist[i]);
            }
          query+=" )";
          query+=" ORDER BY albums.name;";
          }
        else if (artistlist.no()==1){
          query+=" AND albums.artist == " + GMStringVal(artistlist[0]);
          query+=" ORDER BY albums.name;";
          }
        else {
          query+=" ORDER BY albums.name;";
          }
        }
      }

//    fxmessage("query: %s\n",query.text());


    q.compile(db->database(),query);
    while(q.execute()){
      q.getResult(0,id);
      c_name = q.getResult(1);
      q.getResult(2,year);
      if (c_name!=NULL && item && c_name==name) {
        FXASSERT(item);
        item->albums.append(id);
        }
      else {
        name=c_name;
        if (GMPlayerManager::instance()->getPreferences().gui_show_albumcovers){
          albumicon = getAlbumIcon(id,true);
          if (albumicon)
            item = new GMAlbumListItem(c_name,albumicon,id,year);
          else
            item = new GMAlbumListItem(c_name,icon,id,year);
          }
        else {
          item = new GMAlbumListItem(c_name,icon,id,year);
          }
        list->appendItem(item);
        }
      }
    }
  catch(FXCompileException &){
    list->clearItems();
    return false;
    }
  catch(FXExecuteException &){
    list->clearItems();
    return false;
    }
#ifdef DEBUG
  FXlong end = fxgetticks();
  fxmessage("listAlbums(): %30lld\n",end-start);
#endif
  return true;
  }


FXbool GMDatabaseSource::listTracks(GMTrackList * tracklist,const FXIntList & albumlist,const FXIntList & genrelist){
  GMQuery q;
  FXString query;
  const FXchar * c_artist;
  const FXchar * c_albumname;
  const FXchar * c_albumartist;
  const FXchar * c_title;
  const FXchar * c_genre;
  FXint time;
  FXuint no;
  FXint id;
  FXint queue=1;
  FXint year;
  FXint trackyear;
  GMDBTrackItem * item;

  GMDBTrackItem::max_queue=0;
  GMDBTrackItem::max_trackno=0;
  GMDBTrackItem::max_time=0;

#ifdef DEBUG
  FXlong start = fxgetticks();
#endif

  try {

    if (getPlayList()>=0) {
      query = "SELECT tracks.id,title,time,no,tracks.year,genres.name,a1.name,a2.name,albums.name,albums.year, playlist_tracks.queue "
              "FROM tracks, genres, albums, artists AS a1, artists AS a2, playlist_tracks ";
      }
    else {
      query = "SELECT tracks.id,title,time,no,tracks.year,genres.name,a1.name,a2.name,albums.name,albums.year "
              "FROM tracks, genres, albums, artists AS a1, artists AS a2 ";
      }

    query += " WHERE genres.id == tracks.genre AND "
                   "a1.id == albums.artist AND "
                   "a2.id == tracks.artist AND "
                   "tracks.album == albums.id";

    if (genrelist.no()) {
       if (genrelist.no()>1) {
         query+=" AND genre IN ( " + GMStringVal(genrelist[0]);
         for (FXint i=1;i<genrelist.no();i++){
           query+=","+GMStringVal(genrelist[i]);
           }
         query+=")";
         }
       else {
         query+=" AND genre == " + GMStringVal(genrelist[0]);
         }
       }

    if (getPlayList()>=0) {
      query+=" AND playlist_tracks.track == tracks.id";
      if (!hasFilter()) query+=" AND playlist_tracks.playlist == "+GMStringVal(getPlayList());
      }

    if (albumlist.no()) {
      if (albumlist.no()>1) {
        query+=" AND tracks.album IN ( ";
        query+=GMStringVal(albumlist[0]);

        for (FXint i=1;i<albumlist.no();i++){
          query+=","+GMStringVal(albumlist[i]);
          }
        query+=" )";
        }
      else {
        query+=" AND tracks.album == " + GMStringVal(albumlist[0]);
        }
      }

    if (hasFilter()) {
      query+=" AND tracks.id IN (SELECT track FROM filtered) ";
      }

    if (getPlayList()>=0)
      query+=" ORDER BY playlist_tracks.queue;";
    else
      query+=";";

    //fxmessage("query: %s\n",query.text());
    q.compile(db->database(),query);

   if (getPlayList()>=0) {
      while(q.execute()){
        q.getResult(0,id);
        c_title = q.getResult(1);
        q.getResult(2,time);
        q.getResult(3,no);
        q.getResult(4,year);
        c_genre = q.getResult(5);
        c_albumartist = q.getResult(6);
        c_artist = q.getResult(7);
        c_albumname = q.getResult(8);
        q.getResult(9,trackyear);
        q.getResult(10,queue);

        GMDBTrackItem::max_trackno=FXMAX(GMDBTrackItem::max_digits(GMTRACKNO(no)),GMDBTrackItem::max_trackno);
        GMDBTrackItem::max_queue=FXMAX(GMDBTrackItem::max_digits(queue),GMDBTrackItem::max_queue);
        item = new GMDBTrackItem(id,c_title,c_artist,c_albumartist,c_albumname,c_genre,time,no,queue,(FXushort)year,(FXushort)trackyear);
        tracklist->appendItem(item);
        }
      }
    else {
      while(q.execute()){
        q.getResult(0,id);
        c_title = q.getResult(1);
        q.getResult(2,time);
        q.getResult(3,no);
        q.getResult(4,year);
        c_genre = q.getResult(5);
        c_albumartist = q.getResult(6);
        c_artist = q.getResult(7);
        c_albumname = q.getResult(8);
        q.getResult(9,trackyear);

        GMDBTrackItem::max_trackno=FXMAX(GMDBTrackItem::max_digits(GMTRACKNO(no)),GMDBTrackItem::max_trackno);
        item = new GMDBTrackItem(id,c_title,c_artist,c_albumartist,c_albumname,c_genre,time,no,queue++,(FXushort)year,(FXushort)trackyear);
        tracklist->appendItem(item);
        }
      }
    GMDBTrackItem::max_trackno = tracklist->getFont()->getTextWidth(FXString('8',GMDBTrackItem::max_trackno));
    GMDBTrackItem::max_queue   = tracklist->getFont()->getTextWidth(FXString('8',GMDBTrackItem::max_digits(queue)));
    GMDBTrackItem::max_time    = tracklist->getFont()->getTextWidth("88:88",5);
    }
  catch(FXCompileException &){
    tracklist->clearItems();
    return false;
    }
  catch(FXExecuteException &){
    tracklist->clearItems();
    return false;
    }
#ifdef DEBUG
  FXlong end = fxgetticks();
  fxmessage("listTracks(): %30lld\n",end-start);
#endif
  return true;
  }


static const FXchar defaults_section[] = "dialog defaults";


class GMEditTrackDialog : public FXDialogBox {
FXDECLARE(GMEditTrackDialog)
protected:
  GMTrackDatabase * db;
  FXuint samemask;
  FXIntList tracks;
  GMTrack info;
public:
  GMComboBox    * trackartistbox;
  GMComboBox    * albumartistbox;
  GMComboBox    * genrebox;
  GMComboBox    * albumbox;
  FXTextField   * yearfield;
  FXSpinner     * discspinner;
  FXTextField   * discfield;
  FXTextField   * titlefield;
  FXCheckButton * updatetags;
  FXCheckButton * updatefilename;
  FXCheckButton * autonumber;
  FXSpinner     * autonumberoffset;
  FXSpinner     * trackspinner;
protected:
  GMEditTrackDialog(){}
private:
  GMEditTrackDialog(const GMEditTrackDialog&);
  GMEditTrackDialog &operator=(const GMEditTrackDialog&);
public:
  enum {
    ID_FILENAME_TEMPLATE=FXDialogBox::ID_LAST
    };
protected:
  enum {
    SAME_ALBUM      =0x1,
    SAME_ARTIST     =0x2,
    SAME_ALBUMARTIST=0x4,
    SAME_GENRE      =0x8,
    SAME_YEAR       =0x10,
    SAME_DISC       =0x20,
    };

public:
  long onCmdAccept(FXObject*,FXSelector,void*);
  long onCmdFilenameTemplate(FXObject*,FXSelector,void*);
public:
  GMEditTrackDialog(FXWindow *,GMTrackDatabase*);
  virtual ~GMEditTrackDialog();
  };




FXDEFMAP(GMEditTrackDialog) GMEditTrackDialogMap[]={
  FXMAPFUNC(SEL_COMMAND,GMEditTrackDialog::ID_FILENAME_TEMPLATE,GMEditTrackDialog::onCmdFilenameTemplate),
  FXMAPFUNC(SEL_COMMAND,GMEditTrackDialog::ID_ACCEPT,GMEditTrackDialog::onCmdAccept)
  };

FXIMPLEMENT(GMEditTrackDialog,FXDialogBox,GMEditTrackDialogMap,ARRAYNUMBER(GMEditTrackDialogMap));


static const FXchar update_tags_key[] = "track-update-tags";
static const FXchar update_filenames_key[] = "track-update-filenames";



GMEditTrackDialog::GMEditTrackDialog(FXWindow*p,GMTrackDatabase * d) : FXDialogBox(p,FXString::null,DECOR_TITLE|DECOR_BORDER|DECOR_RESIZE,0,0,0,0,0,0,0,0,0,0), db(d) {
  FXPacker * main=NULL;
  FXTextField * textfield = NULL;
  FXHorizontalFrame * hframe = NULL;
  GMTabBook * tabbook = NULL;
  FXMatrix * matrix = NULL;
  GMTabFrame * tabframe = NULL;

  titlefield=NULL;
  discfield=NULL;
  discspinner=NULL;


  setTitle(tr("Edit Track Information"));
  FXHorizontalFrame *closebox=new FXHorizontalFrame(this,LAYOUT_SIDE_BOTTOM|LAYOUT_RIGHT|PACK_UNIFORM_WIDTH,0,0,0,0);
  new GMButton(closebox,tr("&Cancel"),NULL,this,FXDialogBox::ID_CANCEL,BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new GMButton(closebox,tr("&Save"),NULL,this,FXDialogBox::ID_ACCEPT,BUTTON_INITIAL|BUTTON_DEFAULT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);

  GMTrack other;
  GMTag::Properties prop;


  GMPlayerManager::instance()->getTrackView()->getSelectedTracks(tracks);

  db->getTrack(tracks[0],info);
  if (tracks.no()==1)
    GMTag::properties(info.mrl,prop);


  samemask=SAME_ALBUM|SAME_ARTIST|SAME_ALBUMARTIST|SAME_GENRE|SAME_YEAR|SAME_DISC;

  if (tracks.no()>1) {
    for (FXint i=1;i<tracks.no() && samemask!=0 ;i++) {
      db->getTrack(tracks[i],other);
      if (other.album!=info.album) samemask&=~SAME_ALBUM;
      if (other.artist!=info.artist) samemask&=~SAME_ARTIST;
      if (other.album_artist!=info.album_artist) samemask&=~SAME_ALBUMARTIST;
      if (other.genre!=info.genre) samemask&=~SAME_GENRE;
      if (other.year!=info.year) samemask&=~SAME_YEAR;
      if (GMDISCNO(other.no)!=GMDISCNO(info.no)) samemask&=~SAME_DISC;
      }
    }


  if (tracks.no()==1) { /* only show spinner when one track is selected */
    main = new FXPacker(this,LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0,0,0,0,0);
    }
  else {
    new FXSeparator(this,SEPARATOR_GROOVE|LAYOUT_FILL_X|LAYOUT_SIDE_BOTTOM);
    main = new FXPacker(this,LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0,5,5,5,5);
    }

  if (tracks.no()==1) { /* only show spinner when one track is selected */

    tabbook = new GMTabBook(main,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_Y);

    new GMTabItem(tabbook,tr("&Tag"),NULL,TAB_TOP_NORMAL,0,0,0,0,5,5);
    tabframe = new GMTabFrame(tabbook);

    FXMatrix * tagmatrix = new FXMatrix(tabframe,2,LAYOUT_FILL_X|MATRIX_BY_COLUMNS,0,0,0,0,10,10,10,10);

    new GMTabItem(tabbook,tr("&Properties"),NULL,TAB_TOP_NORMAL,0,0,0,0,5,5);
    tabframe = new GMTabFrame(tabbook);

    matrix = new FXMatrix(tabframe,2,LAYOUT_FILL_X|MATRIX_BY_COLUMNS,0,0,0,0,10,10,10,10);

    new FXLabel(matrix,tr("Filename"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
    textfield = new GMTextField(matrix,30,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_SUNKEN|FRAME_THICK|TEXTFIELD_READONLY);
    textfield->setText(info.mrl);

    new FXLabel(matrix,tr("Type"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
    textfield = new GMTextField(matrix,20,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_SUNKEN|FRAME_THICK|TEXTFIELD_READONLY);
    textfield->setText(FXPath::extension(info.mrl).upper());

    new FXLabel(matrix,tr("Size"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
    textfield = new GMTextField(matrix,20,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_SUNKEN|FRAME_THICK|TEXTFIELD_READONLY);
#if defined(__LP64__) || defined(_LP64) || (_MIPS_SZLONG == 64) || (__WORDSIZE == 64)
      textfield->setText(GMStringFormat("%'ld",FXStat::size(info.mrl)));
#else
      textfield->setText(GMStringFormat("%'lld",FXStat::size(info.mrl)));
#endif

    new FXLabel(matrix,tr("Bitrate"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
    textfield = new GMTextField(matrix,20,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_SUNKEN|FRAME_THICK|TEXTFIELD_READONLY);
    textfield->setText(GMStringFormat("%dkbs",prop.bitrate));

    new FXLabel(matrix,tr("Samplerate"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
    textfield = new GMTextField(matrix,20,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_SUNKEN|FRAME_THICK|TEXTFIELD_READONLY);
    textfield->setText(GMStringFormat("%dHz",prop.samplerate));

    new FXLabel(matrix,tr("Channels"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
    textfield = new GMTextField(matrix,20,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_SUNKEN|FRAME_THICK|TEXTFIELD_READONLY);
    textfield->setText(GMStringFormat("%d",prop.channels));

    matrix = tagmatrix;

//    new FXFrame(matrix,FRAME_NONE,0,0,0,0,0,0,3);
//    new FXFrame(matrix,FRAME_NONE|LAYOUT_FILL_COLUMN,0,0,0,0,0,0,3);

    new FXLabel(matrix,tr("Track"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
    hframe = new FXHorizontalFrame(matrix,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN,0,0,0,0,0,0,0,0);

    trackspinner = new GMSpinner(hframe,4,NULL,0,FRAME_SUNKEN|FRAME_THICK|LAYOUT_LEFT);
    trackspinner->setRange(0,1000);

    new FXLabel(hframe,tr("Disc"),NULL,LABEL_NORMAL|LAYOUT_LEFT|LAYOUT_CENTER_Y);
    discspinner = new GMSpinner(hframe,3,NULL,0,FRAME_SUNKEN|FRAME_THICK|LAYOUT_LEFT);
    discspinner->setRange(0,100);

    yearfield = new GMTextField(hframe,4,NULL,0,FRAME_SUNKEN|FRAME_THICK|LAYOUT_RIGHT|TEXTFIELD_INTEGER|TEXTFIELD_LIMITED);
    new FXLabel(hframe,tr("Year"),NULL,LABEL_NORMAL|LAYOUT_CENTER_Y|LAYOUT_RIGHT);
    }
  else {
    matrix = new FXMatrix(main,2,LAYOUT_FILL_X|MATRIX_BY_COLUMNS,0,0,0,0,0,0,0,0);

    if (samemask&SAME_DISC) {
      new FXLabel(matrix,tr("Disc"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
      hframe = new FXHorizontalFrame(matrix,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN,0,0,0,0,0,0,0,0);
      discspinner = new GMSpinner(hframe,3,NULL,0,FRAME_SUNKEN|FRAME_THICK|LAYOUT_LEFT);
      discspinner->setRange(0,100);
      }
    else {
      new FXLabel(matrix,tr("Disc"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
      hframe = new FXHorizontalFrame(matrix,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN,0,0,0,0,0,0,0,0);
      discfield = new GMTextField(hframe,3,NULL,0,FRAME_SUNKEN|FRAME_THICK|LAYOUT_LEFT|TEXTFIELD_INTEGER|TEXTFIELD_LIMITED|JUSTIFY_RIGHT);
      }

    yearfield = new GMTextField(hframe,4,NULL,0,FRAME_SUNKEN|FRAME_THICK|LAYOUT_RIGHT|TEXTFIELD_INTEGER|TEXTFIELD_LIMITED|JUSTIFY_RIGHT);
    new FXLabel(hframe,tr("Year"),NULL,LABEL_NORMAL|LAYOUT_CENTER_Y|LAYOUT_RIGHT);
    }

  if (tracks.no()==1) {
    new FXLabel(matrix,tr("Title"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
    titlefield = new GMTextField(matrix,20,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_SUNKEN|FRAME_THICK);
    }

  new FXLabel(matrix,tr("&Artist"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
  trackartistbox = new GMComboBox(matrix,30,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_THICK|FRAME_SUNKEN);

  new FXLabel(matrix,tr("Album A&rtist"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
  albumartistbox = new GMComboBox(matrix,30,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_THICK|FRAME_SUNKEN);

  new FXLabel(matrix,tr("Album"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
  albumbox = new GMComboBox(matrix,30,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_THICK|FRAME_SUNKEN);

  new FXLabel(matrix,tr("Genre"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
  hframe = new FXHorizontalFrame(matrix,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN,0,0,0,0,0,0,0,0);

  genrebox = new GMComboBox(hframe,20,NULL,0,LAYOUT_FILL_X|FRAME_THICK|FRAME_SUNKEN);

  if (tracks.no()>1 && tracks.no()<=0xFFFF) {
    new FXFrame(matrix,FRAME_NONE);
    hframe = new FXHorizontalFrame(matrix,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN,0,0,0,0,0,0,0,0);
    autonumber = new GMCheckButton(hframe,tr("Auto track number. Offset:"),NULL,0,LAYOUT_FILL_COLUMN|CHECKBUTTON_NORMAL|LAYOUT_CENTER_Y);
    autonumberoffset = new GMSpinner(hframe,2,NULL,0,FRAME_SUNKEN|FRAME_THICK|LAYOUT_LEFT);
    autonumber->setTarget(autonumberoffset);
    autonumber->setSelector(FXWindow::ID_TOGGLEENABLED);
    autonumberoffset->disable();
    autonumberoffset->setRange(1,99);
    }

  new FXFrame(matrix,FRAME_NONE);
  updatetags = new GMCheckButton(matrix,tr("Update Tag in File"),NULL,0,LAYOUT_FILL_COLUMN|CHECKBUTTON_NORMAL);
  new FXFrame(matrix,FRAME_NONE);

  hframe = new FXHorizontalFrame(matrix,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN,0,0,0,0,0,0,0,0);

  updatefilename = new GMCheckButton(hframe,fxtr("Update Filename"),NULL,0,LAYOUT_FILL_COLUMN|CHECKBUTTON_NORMAL|LAYOUT_CENTER_Y);
  new GMButton(hframe,tr("Set export template…") ,NULL,this,ID_FILENAME_TEMPLATE,FRAME_RAISED,0,0,0,0);

  updatetags->setCheck(getApp()->reg().readBoolEntry(defaults_section,update_tags_key,false));
  updatefilename->setCheck(getApp()->reg().readBoolEntry(defaults_section,update_filenames_key,false));

  db->listAlbums(albumbox,db->getTrackAlbum(tracks[0]));
  albumbox->setSortFunc(album_list_sort);
  albumbox->setNumVisible(FXMIN(10,albumbox->getNumItems()));
  albumbox->sortItems();
  albumbox->setCurrentItem(-1);

  db->listArtists(trackartistbox);
  trackartistbox->setSortFunc(artist_list_sort);
  trackartistbox->setNumVisible(FXMIN(10,trackartistbox->getNumItems()));
  trackartistbox->sortItems();
  trackartistbox->setCurrentItem(-1);

  db->listArtists(albumartistbox);
  albumartistbox->setSortFunc(artist_list_sort);
  albumartistbox->setNumVisible(FXMIN(10,albumartistbox->getNumItems()));
  albumartistbox->sortItems();
  albumartistbox->setCurrentItem(-1);

  db->listGenres(genrebox);
  genrebox->setSortFunc(genre_list_sort);
  genrebox->setNumVisible(FXMIN(10,genrebox->getNumItems()));
  genrebox->sortItems();
  genrebox->setCurrentItem(-1);

  if (tracks.no()==1) {
    trackspinner->setValue(GMTRACKNO(info.no));
    albumbox->setCurrentItem(albumbox->findItem(info.album));
    trackartistbox->setCurrentItem(trackartistbox->findItem(info.artist));
    albumartistbox->setCurrentItem(albumartistbox->findItem(info.album_artist));
    genrebox->setCurrentItem(genrebox->findItem(info.genre));
    yearfield->setText(GMStringVal(info.year));
    titlefield->setText(info.title);
    discspinner->setValue(GMDISCNO(info.no));
    }
  else {
    if (samemask&SAME_ALBUM) albumbox->setCurrentItem(albumbox->findItem(info.album));
    if (samemask&SAME_ARTIST) trackartistbox->setCurrentItem(trackartistbox->findItem(info.artist));
    if (samemask&SAME_ALBUMARTIST) albumartistbox->setCurrentItem(albumartistbox->findItem(info.album_artist));
    if (samemask&SAME_GENRE) genrebox->setCurrentItem(genrebox->findItem(info.genre));
    if (samemask&SAME_YEAR) yearfield->setText(GMStringVal(info.year));
    if (samemask&SAME_DISC) discspinner->setValue(GMDISCNO(info.no));
    }
  }


GMEditTrackDialog::~GMEditTrackDialog() {
  }

long GMEditTrackDialog::onCmdFilenameTemplate(FXObject*,FXSelector,void*){
  GMFilenameTemplateDialog dialog(this);
  dialog.execute();
  return 1;
  }

long GMEditTrackDialog::onCmdAccept(FXObject*sender,FXSelector sel,void*ptr){
  FXbool changed=false;
  FXbool sync=false;
  FXint i;
  FXString field;
  FXString altfield;

  FXDialogBox::onCmdAccept(sender,sel,ptr);

  db->beginEdit();

  /// TITLE
  if (tracks.no()==1) {
    field=titlefield->getText().trim().simplify();
    if (!field.empty() && info.title!=field){
      db->setTrackTitle(tracks[0],field);
      changed=true;
      }
    }
  /// GENRE
  field=genrebox->getText().trim().simplify();
  if (( !field.empty())   && (
      ( tracks.no()>1 && ( (!(samemask&SAME_GENRE)) || field!=info.genre )) ||
      ( tracks.no()==1 && info.genre!=field ) )) {
    db->setTrackGenre(tracks,field);
    changed=true;
    sync=true;
    }

  /// ARTIST
  field=trackartistbox->getText().trim().simplify();
  if (( !field.empty())   && (
      ( tracks.no()>1 && ( (!(samemask&SAME_ARTIST)) || field!=info.artist )) ||
      ( tracks.no()==1 && info.artist!=field ) )) {
    db->setTrackArtist(tracks,field);
    changed=true;
    sync=true;
    }

  /// YEAR
  field=yearfield->getText().trim().simplify();
  if (!field.empty()){
#if FOXVERSION >= FXVERSION(1,7,12)
    FXint year=yearfield->getText().toInt();
#else
    FXint year=FXIntVal(yearfield->getText());
#endif
    if ( ( tracks.no()>1 && ( (!(samemask&SAME_YEAR)) || info.year!=year ) ) ||
         ( tracks.no()==1 && info.year!=year )) {
      db->setTrackYear(tracks,year);
      changed=true;
      }
    }

  /// DISC and TRACK number
  if (tracks.no()==1) {
    if (GMTRACKNO(info.no)!=trackspinner->getValue() || GMDISCNO(info.no)!=discspinner->getValue() ) {
      db->setTrackDiscNumber(tracks[0],GMALBUMNO(discspinner->getValue(),trackspinner->getValue()));
      changed=true;
      sync=true;
      }
    }
  else {
    if (autonumber->getCheck()) {
      if (discspinner) { /// all tracks had the same disc number
        for (i=0;i<tracks.no();i++){
          db->setTrackDiscNumber(tracks[i],GMALBUMNO(discspinner->getValue(),autonumberoffset->getValue()+i));
          }
        changed=true;
        sync=true;
        }
      else { /// disc field
        if (discfield->getText().empty()) {
          for (i=0;i<tracks.no();i++){
            db->setTrackNumber(tracks[i],autonumberoffset->getValue()+i);
            }
          changed=true;
          sync=true;
          }
        else {
          for (i=0;i<tracks.no();i++){
#if FOXVERSION >= FXVERSION(1,7,12)
            db->setTrackDiscNumber(tracks[i],GMALBUMNO(discfield->getText().toUInt(),autonumberoffset->getValue()+i));
#else
            db->setTrackDiscNumber(tracks[i],GMALBUMNO(FXUIntVal(discfield->getText()),autonumberoffset->getValue()+i));
#endif
            }
          changed=true;
          sync=true;
          }
        }
      }
    else {
      if (discspinner) {
        if (GMDISCNO(info.no)!=discspinner->getValue()){
          db->setTrackDisc(tracks,discspinner->getValue());
          changed=true;
          sync=true;
          }
        }
      else { // disc field
        field=discfield->getText().trim().simplify();
        if (!field.empty()) {
#if FOXVERSION >= FXVERSION(1,7,12)
          FXint disc=discfield->getText().toInt();
#else
          FXint disc=FXIntVal(discfield->getText());
#endif
          db->setTrackDisc(tracks,disc);
          changed=true;
          sync=true;
          }
        }
      }
    }

  field=albumartistbox->getText().trim().simplify();
  altfield=albumbox->getText().trim().simplify();

  /// ALBUM ARTIST
  if (( !field.empty()) && (
      ( tracks.no()>1 && ( (!(samemask&SAME_ALBUMARTIST)) || field!=info.album_artist )) ||
      ( tracks.no()==1 && info.album_artist!=field ) )) {

    if (altfield.empty() && (samemask&SAME_ALBUM)) {
      altfield=info.album;
      }
    db->setTrackAlbumArtist(tracks,field,altfield);
    changed=true;
    sync=true;
    }
  else if (( !altfield.empty()) && (
           ( tracks.no()>1 && ( (!(samemask&SAME_ALBUM)) || altfield!=info.album )) ||
           ( tracks.no()==1 && info.album!=altfield ) )) {
    db->setTrackAlbum(tracks,altfield,(samemask&SAME_ALBUMARTIST));
    changed=true;
    sync=true;
    }

  if (updatetags->getCheck()) {
    if (changed || (FXMessageBox::question(GMPlayerManager::instance()->getMainWindow(),MBOX_YES_NO,fxtr("Update Tags?"),fxtr("No tracks were updated.\nWould you still like to write the tags for the selected tracks?"))==MBOX_CLICKED_YES)) {
      GMTrack info;
      for (i=0;i<tracks.no();i++){
        if (!db->getTrack(tracks[i],info)) break;
        info.saveTag(info.mrl);
        db->setTrackImported(tracks[i],FXThread::time());
        }
      }
    }

  if (updatefilename->getCheck()) {
    updateTrackFilenames(db,tracks);
    }

  getApp()->reg().writeBoolEntry(defaults_section,update_tags_key,updatetags->getCheck());
  getApp()->reg().writeBoolEntry(defaults_section,update_filenames_key,updatefilename->getCheck());

  db->endEdit(sync);
  GMPlayerManager::instance()->getTrackView()->refresh();
  return 1;
  }


long GMDatabaseSource::onCmdEditTrack(FXObject*,FXSelector,void*){
  GMEditTrackDialog dialog(GMPlayerManager::instance()->getMainWindow(),db);
  dialog.execute();
  return 1;
  }












long GMDatabaseSource::onCmdFilenameTemplate(FXObject*,FXSelector,void*){
  GMFilenameTemplateDialog dialog(FXApp::instance()->getActiveWindow());
  dialog.execute();
  return 1;
  }

void GMDatabaseSource::getTrackFilenames(const FXIntList & tracks,FXStringList & files){
  files.no(tracks.no());
  for (int i=0;i<tracks.no();i++){
    files[i]=db->getTrackFilename(tracks[i]);
    }
  }


void GMDatabaseSource::removeFiles(const FXStringList & files) {
  FXDialogBox dialog(GMPlayerManager::instance()->getMainWindow(),fxtr("Remove Audio Files?"),DECOR_TITLE|DECOR_BORDER|DECOR_RESIZE|DECOR_CLOSE,0,0,600,400,0,0,0,0,0,0);
  GMPlayerManager::instance()->getMainWindow()->create_dialog_header(&dialog,fxtr("Remove Audio Files..."),fxtr("The following audio files are going to be removed"));
  FXHorizontalFrame *closebox=new FXHorizontalFrame(&dialog,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,0,0,0,0);
  new GMButton(closebox,fxtr("&Remove"),NULL,&dialog,FXDialogBox::ID_ACCEPT,BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new GMButton(closebox,fxtr("&Cancel"),NULL,&dialog,FXDialogBox::ID_CANCEL,BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new FXSeparator(&dialog,SEPARATOR_GROOVE|LAYOUT_FILL_X|LAYOUT_SIDE_BOTTOM);

  FXVerticalFrame * main = new FXVerticalFrame(&dialog,LAYOUT_FILL_X|LAYOUT_FILL_Y);
  GMScrollFrame * sunken = new GMScrollFrame(main);
  FXList * list = new GMList(sunken,NULL,0,LAYOUT_FILL_X|LAYOUT_FILL_Y);

  for (int i=0;i<files.no();i++) {
    list->appendItem(files[i]);
    }
  if (dialog.execute()) {
    for (int i=0;i<files.no();i++){
      FXFile::remove(files[i]);
      }
    }
  }




long GMDatabaseSource::onCmdExport(FXObject*,FXSelector,void*){
  const FXchar patterns[]="XML Shareable Playlist Format (*.xspf)\nPLS (*.pls)\nExtended M3U (*.m3u)\nM3U (*.m3u)\nText Comma-Separated (*.csv)";
  FXString searchdir = FXApp::instance()->reg().readStringEntry("Settings","last-export-directory",FXSystem::getHomeDirectory().text());
  FXString title;
  FXuint opts=0;
  if (getPlayList()==-1)
    title=fxtr("Export Main Library");
  else
    title=fxtr("Export Play List");

  GMExportDialog dialog(GMPlayerManager::instance()->getMainWindow(),title);
  dialog.setDirectory(searchdir);
  dialog.setSelectMode(SELECTFILE_ANY);
  dialog.setPatternList(patterns);
  dialog.setCurrentPattern(0);
#if FOXVERSION < FXVERSION(1,7,20)
  dialog.setMatchMode(FILEMATCH_CASEFOLD);
#else
  dialog.setMatchMode(FXPath::CaseFold);
 #endif

  dialog.setRelativePath(FXApp::instance()->reg().readBoolEntry("Settings","export-relative-paths",false));


  if (dialog.execute()){
    if (FXStat::exists(dialog.getFilename())){
      if (FXMessageBox::question(GMPlayerManager::instance()->getMainWindow(),MBOX_YES_NO,fxtr("Overwrite File?"),fxtr("File already exists. Would you like to overwrite it?"))!=MBOX_CLICKED_YES)
        return 1;
      }

    if (dialog.getRelativePath())
      opts|=PLAYLIST_OPTIONS_RELATIVE;

    FXApp::instance()->reg().writeStringEntry("Settings","last-export-directory",dialog.getDirectory().text());
    FXApp::instance()->reg().writeBoolEntry("Settings","export-relative-paths",dialog.getRelativePath());

    FXApp::instance()->beginWaitCursor();
    switch(dialog.getCurrentPattern()){
      case 0: db->exportList(dialog.getFilename(),getPlayList(),PLAYLIST_XSPF,opts); break;
      case 1: db->exportList(dialog.getFilename(),getPlayList(),PLAYLIST_PLS,opts); break;
      case 2: db->exportList(dialog.getFilename(),getPlayList(),PLAYLIST_M3U_EXTENDED,opts); break;
      case 3: db->exportList(dialog.getFilename(),getPlayList(),PLAYLIST_M3U,opts); break;
      case 4: db->exportList(dialog.getFilename(),getPlayList(),PLAYLIST_CSV,opts); break;
      }
    FXApp::instance()->endWaitCursor();
    }
  return 1;
  }

long GMDatabaseSource::onUpdExport(FXObject*sender,FXSelector,void*){
  sender->handle(this,FXSEL(SEL_COMMAND,FXWindow::ID_ENABLE),NULL);
  return 1;
  }



long GMDatabaseSource::onCmdExportTracks(FXObject*,FXSelector sel,void*){
  const FXuint labelstyle=LAYOUT_CENTER_Y|LABEL_NORMAL|LAYOUT_RIGHT;

  FXIntList tracks;
  if (FXSELID(sel)==ID_EXPORT_TRACK) {
    GMPlayerManager::instance()->getTrackView()->getSelectedTracks(tracks);
    }
  else {
    GMPlayerManager::instance()->getTrackView()->getTracks(tracks);
    }
  if (tracks.no()==0) return 1;

  FXString title;
  FXString subtitle;

  switch(FXSELID(sel)){
    case ID_EXPORT_GENRE: title=fxtr("Export Genre");
                          subtitle=fxtr("Export tracks with genre to destination directory.");
                          break;
    case ID_EXPORT_ARTIST:title=fxtr("Export Artists");
                          subtitle=fxtr("Export tracks from artist to destination directory.");
                          break;
    case ID_EXPORT_ALBUM: title=fxtr("Export Albums");
                          subtitle=fxtr("Export tracks from album to destination directory.");
                          break;
    case ID_EXPORT_TRACK: title=fxtr("Export Tracks");
                          subtitle=fxtr("Export tracks to destination directory.");
                          break;
    default: FXASSERT(0); break;
    }

//  FXTextField * textfield;
  FXDialogBox dialog(GMPlayerManager::instance()->getMainWindow(),title,DECOR_TITLE|DECOR_BORDER|DECOR_RESIZE|DECOR_CLOSE,0,0,0,0,0,0,0,0,0,0);
  GMPlayerManager::instance()->getMainWindow()->create_dialog_header(&dialog,title,subtitle,NULL);
  FXHorizontalFrame *closebox=new FXHorizontalFrame(&dialog,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,0,0,0,0);
  new GMButton(closebox,fxtr("&Export"),NULL,&dialog,FXDialogBox::ID_ACCEPT,BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new GMButton(closebox,fxtr("&Cancel"),NULL,&dialog,FXDialogBox::ID_CANCEL,BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new FXSeparator(&dialog,SEPARATOR_GROOVE|LAYOUT_FILL_X|LAYOUT_SIDE_BOTTOM);
  FXVerticalFrame * main = new FXVerticalFrame(&dialog,LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0,30,20,10,10);
  FXMatrix * matrix = new FXMatrix(main,2,MATRIX_BY_COLUMNS|LAYOUT_FILL_X,0,0,0,0,0,0,4,0);
/*
  new FXLabel(matrix,"Directory:",NULL,labelstyle);
  FXHorizontalFrame * hframe = new FXHorizontalFrame(matrix,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN,0,0,0,0,0,0,0,0);
  FXTextField * textfield = new FXTextField(hframe,20,NULL,0,LAYOUT_FILL_X|TEXTFIELD_ENTER_ONLY|FRAME_SUNKEN|FRAME_THICK);
  new FXButton(hframe,… "\tSelect Directory",NULL,NULL,0);

*/
  new FXLabel(matrix,fxtr("Template:"),NULL,labelstyle);
  new GMTextField(matrix,20,NULL,0,LAYOUT_FILL_X|TEXTFIELD_ENTER_ONLY|FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_COLUMN);
 // textfield->setFont(font_fixed);

  new FXLabel(matrix,fxtr("Encoding:"),NULL,labelstyle);
  GMListBox * list_codecs = new GMListBox(matrix,NULL,0,FRAME_SUNKEN|FRAME_THICK|LAYOUT_FILL_COLUMN);
  for (int i=0;gmcodecnames[i]!=NULL;i++)
    list_codecs->appendItem(gmcodecnames[i]);
  list_codecs->setNumVisible(9);

  new FXLabel(matrix,fxtr("Options:"),NULL,labelstyle);
  new GMCheckButton(matrix,fxtr("Replace spaces with underscores"),NULL,0,LAYOUT_FILL_COLUMN|CHECKBUTTON_NORMAL);
  new FXFrame(matrix,FRAME_NONE);
  new GMCheckButton(matrix,fxtr("Lower case"),NULL,0,LAYOUT_FILL_COLUMN|CHECKBUTTON_NORMAL);
  new FXFrame(matrix,FRAME_NONE);
  new GMCheckButton(matrix,fxtr("Lower case extension"),NULL,0,LAYOUT_FILL_COLUMN|CHECKBUTTON_NORMAL);

  if (dialog.execute()){
    }
  return 1;
  }

long GMDatabaseSource::onCmdDelete(FXObject*,FXSelector sel,void*){
  FXIntList tracks;
  FXIntList selected;
  FXStringList files;
  if (FXSELID(sel)==ID_DELETE_TRACK) {
    GMPlayerManager::instance()->getTrackView()->getSelectedTracks(tracks);
    }
  else {
    GMPlayerManager::instance()->getTrackView()->getTracks(tracks);
    }
  if (tracks.no()==0) return 1;

  FXString title;
  FXString subtitle;

  switch(FXSELID(sel)){
    case ID_DELETE_GENRE: title=fxtr("Remove Genre?");
                          subtitle=fxtr("Remove tracks with genre from library?");
                          GMPlayerManager::instance()->getTrackView()->getSelectedGenres(selected);
                          if (selected.no()==0) return 1;
                          break;
    case ID_DELETE_ARTIST:title=fxtr("Remove Artist?");
                          subtitle=fxtr("Remove tracks from artist from library?");
                          GMPlayerManager::instance()->getTrackView()->getSelectedArtists(selected);
                          if (selected.no()==0) return 1;
                          break;
    case ID_DELETE_ALBUM: title=fxtr("Remove Album?");
                          subtitle=fxtr("Remove tracks from album from library?");
                          GMPlayerManager::instance()->getTrackView()->getSelectedAlbums(selected);
                          if (selected.no()==0) return 1;
                          break;
    case ID_DELETE_TRACK: title=fxtr("Remove Track(s)?");
                          subtitle=fxtr("Remove track(s) from library?");
                          break;
    default: FXASSERT(0); break;
    }

  FXDialogBox dialog(GMPlayerManager::instance()->getMainWindow(),title,DECOR_TITLE|DECOR_BORDER|DECOR_RESIZE|DECOR_CLOSE,0,0,0,0,0,0,0,0,0,0);
  GMPlayerManager::instance()->getMainWindow()->create_dialog_header(&dialog,title,subtitle,NULL);
  FXHorizontalFrame *closebox=new FXHorizontalFrame(&dialog,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,0,0,0,0);
  new GMButton(closebox,fxtr("&Remove"),NULL,&dialog,FXDialogBox::ID_ACCEPT,BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new GMButton(closebox,fxtr("&Cancel"),NULL,&dialog,FXDialogBox::ID_CANCEL,BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new FXSeparator(&dialog,SEPARATOR_GROOVE|LAYOUT_FILL_X|LAYOUT_SIDE_BOTTOM);
  FXVerticalFrame * main = new FXVerticalFrame(&dialog,LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0,30,20,10,10);
  FXCheckButton * from_disk = new GMCheckButton(main,fxtr("Remove tracks from disk"));
  from_disk->setCheck(FXApp::instance()->reg().readBoolEntry("delete dialog","from-disk",false));

  if (dialog.execute()){

    db->beginDelete();

    if (from_disk->getCheck())
      getTrackFilenames(tracks,files);


    switch(FXSELID(sel)){
      case ID_DELETE_GENRE:
        if (!db->removeGenre(selected[0]))
          FXMessageBox::error(GMPlayerManager::instance()->getMainWindow(),MBOX_OK,fxtr("Library Error"),fxtr("Unable to remove genre from the library"));
        break;
      case ID_DELETE_ARTIST:
        if (!db->removeArtist(selected[0]))
          FXMessageBox::error(GMPlayerManager::instance()->getMainWindow(),MBOX_OK,fxtr("Library Error"),fxtr("Unable to remove artist from the library"));
        break;
      case ID_DELETE_ALBUM:
        if (!db->removeAlbum(selected[0]))
          FXMessageBox::error(GMPlayerManager::instance()->getMainWindow(),MBOX_OK,fxtr("Library Error"),fxtr("Unable to remove album from the library"));
        break;
      case ID_DELETE_TRACK:
        if (!db->removeTracks(tracks))
          FXMessageBox::error(GMPlayerManager::instance()->getMainWindow(),MBOX_OK,fxtr("Library Error"),fxtr("Unable to remove track from the library."));
        break;
      default: FXASSERT(0); break;
      }

    if (from_disk->getCheck())
      removeFiles(files);

    FXApp::instance()->reg().writeBoolEntry("delete dialog","from-disk",from_disk->getCheck());

    db->endDelete();
    GMPlayerManager::instance()->getTrackView()->refresh();
    }
  return 1;
  }


long GMDatabaseSource::onCmdCopyArtistAlbum(FXObject*,FXSelector,void*){
  FXDragType types[4]={GMClipboard::trackdatabase,GMClipboard::kdeclipboard,GMClipboard::gnomeclipboard,FXWindow::urilistType};
  GMDatabaseClipboardData * data = new GMDatabaseClipboardData;
  if (GMClipboard::instance()->acquire(this,types,4,data)){
    FXApp::instance()->beginWaitCursor();
    data->db=db;
    GMPlayerManager::instance()->getTrackView()->getTracks(data->tracks);
    FXApp::instance()->endWaitCursor();
    }
  else {
    delete data;
    FXApp::instance()->beep();
    }
  return 1;
  }

long GMDatabaseSource::onCmdCopyTrack(FXObject*,FXSelector,void*){
  FXDragType types[4]={GMClipboard::trackdatabase,GMClipboard::kdeclipboard,GMClipboard::gnomeclipboard,FXWindow::urilistType};
  GMDatabaseClipboardData * data = new GMDatabaseClipboardData;
  if (GMClipboard::instance()->acquire(this,types,4,data)){
    FXApp::instance()->beginWaitCursor();
    data->db=db;
    GMPlayerManager::instance()->getTrackView()->getSelectedTracks(data->tracks);
    FXApp::instance()->endWaitCursor();
    }
  else {
    delete data;
    FXApp::instance()->beep();
    }
  return 1;
  }



long GMDatabaseSource::onCmdRequestArtistAlbum(FXObject*sender,FXSelector,void*ptr){
  FXEvent* event=(FXEvent*)ptr;
  FXWindow*window=(FXWindow*)sender;
  if(event->target==GMClipboard::urilistType){
    FXStringList filenames;
    FXIntList tracks;
    FXString uri;
    GMPlayerManager::instance()->getTrackView()->getTracks(tracks);
    db->getTrackFilenames(tracks,filenames);
    gm_convert_filenames_to_uri(filenames,uri);
    window->setDNDData(FROM_DRAGNDROP,event->target,uri);
    return 1;
    }
  else if (event->target==GMClipboard::kdeclipboard){
    window->setDNDData(FROM_DRAGNDROP,event->target,"0"); // copy
    return 1;
    }
  return 0;
  }

long GMDatabaseSource::onCmdRequestTrack(FXObject*sender,FXSelector,void*ptr){
  FXEvent* event=(FXEvent*)ptr;
  FXWindow*window=(FXWindow*)sender;
  if(event->target==GMClipboard::urilistType){
    FXStringList filenames;
    FXIntList tracks;
    FXString uri;
    GMPlayerManager::instance()->getTrackView()->getSelectedTracks(tracks);
    db->getTrackFilenames(tracks,filenames);
    gm_convert_filenames_to_uri(filenames,uri);
    window->setDNDData(FROM_DRAGNDROP,event->target,uri);
    return 1;
    }
  else if (event->target==GMClipboard::kdeclipboard){
    window->setDNDData(FROM_DRAGNDROP,event->target,"0"); // copy
    return 1;
    }
  return 0;
  }


long GMDatabaseSource::onCmdDrop(FXObject*sender,FXSelector,void*){
  FXWindow * window=(FXWindow*)sender;
  FXString     files;
  FXStringList filelist;
//  FXbool from_kde=false;
  FXbool from_uri=false;
  FXDragType * types;
  FXuint       ntypes;

  if (getPlayList()==-1 && FXApp::instance()->getDragWindow()) return 0;

  if (window->inquireDNDTypes(FROM_DRAGNDROP,types,ntypes)){
    for (FXuint i=0;i<ntypes;i++){
//    fxmessage("[%d] %s\n",i,FXApp::instance()->getDragTypeName(types[i]).text());

//      if (types[i]==GMClipboard::kdeclipboard) from_kde=true;
     if (types[i]==FXWindow::urilistType) from_uri=true;
     }

//    else if (from_kde && from_uri) {
//      }
    if (from_uri) {
      if (window->getDNDData(FROM_DRAGNDROP,FXWindow::urilistType,files)){
        gm_convert_uri_to_filenames(files,filelist);
        }
      }

    window->dropFinished(DRAG_ACCEPT);

    if (filelist.no()) {
      GMImportDialog dialog(GMPlayerManager::instance()->getMainWindow(),IMPORT_FROMPASTE);
      if (dialog.execute()) {
        GMPlayerManager::instance()->stop();
        GMImportDatabase searchdialog(GMPlayerManager::instance()->getMainWindow(),filelist,GMPlayerManager::instance()->getPreferences().import,getPlayList(),GMPlayerManager::instance()->getMainWindow()->getThickFont());
        searchdialog.execute();
        GMPlayerManager::instance()->getTrackView()->refresh();
        }
      }
    else {
      FXApp::instance()->beep();
      }
    }
  return 1;
  }



long GMDatabaseSource::onCmdPaste(FXObject*,FXSelector,void*){
  FXString     files;
  FXStringList filelist;
  FXDragType * types;
  FXuint       num;
//  FXbool       from_db=false;
  FXbool       from_kde=false;
  FXbool       from_gnome=false;
  FXbool       from_uri=false;

  GMClipboard * clipboard = GMClipboard::instance();

  if (clipboard->inquireDNDTypes(FROM_CLIPBOARD,types,num)){

    for (FXuint i=0;i<num;i++) {
      if (types[i]==GMClipboard::kdeclipboard)
        from_kde=true;
      else if (types[i]==GMClipboard::gnomeclipboard)
        from_gnome=true;
      else if (types[i]==FXWindow::urilistType)
        from_uri=true;
      }

    freeElms(types);

    if ((from_gnome || from_uri || from_kde) && clipboard->hasClipboard() ) {
      return 1;
      }

    if (from_gnome) {
      if (clipboard->getDNDData(FROM_CLIPBOARD,GMClipboard::gnomeclipboard,files)){
        gm_convert_gnomeclipboard_to_filenames(files,filelist);
        }
      }
    else if (from_kde && from_uri) {
      if (clipboard->getDNDData(FROM_CLIPBOARD,GMClipboard::kdeclipboard,files)){
#ifdef DEBUG
        if (files=="1") {
          fxmessage("We do not cut files...\n");
          }
#endif
        }
      if (clipboard->getDNDData(FROM_CLIPBOARD,FXWindow::urilistType,files)){
        gm_convert_uri_to_filenames(files,filelist);
        }
      }

    if (filelist.no()){
      GMImportDialog dialog(GMPlayerManager::instance()->getMainWindow(),IMPORT_FROMPASTE);
      if (dialog.execute()) {
        GMPlayerManager::instance()->stop();
        GMImportDatabase searchdialog(GMPlayerManager::instance()->getMainWindow(),filelist,GMPlayerManager::instance()->getPreferences().import,getPlayList(),GMPlayerManager::instance()->getMainWindow()->getThickFont());
        searchdialog.execute();
        GMPlayerManager::instance()->getTrackView()->refresh();
        }
      }
    else {
      FXApp::instance()->beep();
      }

    return 1;
    }
  return 0;
  }

long GMDatabaseSource::onUpdPaste(FXObject*,FXSelector,void*){
  return 1;
  }


long GMDatabaseSource::onCmdNewPlayList(FXObject*,FXSelector,void*){
  FXDialogBox dialog(GMPlayerManager::instance()->getMainWindow(),fxtr("Create Playlist"),DECOR_TITLE|DECOR_BORDER|DECOR_RESIZE|DECOR_CLOSE,0,0,0,0,0,0,0,0,0,0);
  GMPlayerManager::instance()->getMainWindow()->create_dialog_header(&dialog,fxtr("Create Playlist"),fxtr("Specify name of the new playlist"),NULL);
  FXHorizontalFrame *closebox=new FXHorizontalFrame(&dialog,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,0,0,0,0);
  new GMButton(closebox,fxtr("&Create"),NULL,&dialog,FXDialogBox::ID_ACCEPT,BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new GMButton(closebox,fxtr("&Cancel"),NULL,&dialog,FXDialogBox::ID_CANCEL,BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new FXSeparator(&dialog,SEPARATOR_GROOVE|LAYOUT_FILL_X|LAYOUT_SIDE_BOTTOM);
  FXVerticalFrame * main = new FXVerticalFrame(&dialog,LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0,30,20,10,10);
  FXMatrix * matrix = new FXMatrix(main,2,LAYOUT_FILL_X|MATRIX_BY_COLUMNS);
  new FXLabel(matrix,fxtr("Name"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
  FXTextField * name_field = new GMTextField(matrix,20,&dialog,FXDialogBox::ID_ACCEPT,LAYOUT_FILL_X|LAYOUT_FILL_COLUMN|FRAME_SUNKEN|FRAME_THICK|TEXTFIELD_ENTER_ONLY);
  name_field->setText(fxtr("New Playlist"));
  name_field->setSelection(0,name_field->getText().length());
  dialog.create();
  gm_focus_and_select(name_field);
  if (dialog.execute()) {
    FXString label= name_field->getText().trim();
    if (!label.empty()) {
      FXint pl;
      db->insertPlaylist(label,pl);
      GMPlayerManager::instance()->insertSource(new GMPlayListSource(db,pl));
      GMPlayerManager::instance()->getSourceView()->refresh();
      }
    }
  return 1;
  }

long GMDatabaseSource::onCmdImportPlayList(FXObject*,FXSelector,void*){
  GMImportDialog dialog(GMPlayerManager::instance()->getMainWindow(),IMPORT_FROMFILE|IMPORT_PLAYLIST);
  if (dialog.execute()){
    FXString buffer;
    FXStringList urls;
    if (gm_buffer_file(dialog.getFilename(),buffer)) {
      FXString title;
      FXString extension = FXPath::extension(dialog.getFilename());

      if (comparecase(extension,"m3u")==0)
        gm_parse_m3u(buffer,urls);
      else if (comparecase(extension,"pls")==0)
        gm_parse_pls(buffer,urls);
      else
        gm_parse_xspf(buffer,urls,title);

      if (urls.no()) {

        gm_make_absolute_path(FXPath::directory(dialog.getFilename()),urls);

        title.trim();
        if (title.empty()) title = FXPath::title(dialog.getFilename());

        FXint pl = GMPlayerManager::instance()->createPlaylist(title);
        GMImportDatabase searchdialog(GMPlayerManager::instance()->getMainWindow(),urls,GMPlayerManager::instance()->getPreferences().import,pl,GMPlayerManager::instance()->getMainWindow()->getThickFont());
        searchdialog.execute();
        GMPlayerManager::instance()->getTrackView()->refresh();
        }
      }
    }
  return 1;
  }


long GMDatabaseSource::onCmdClear(FXObject*,FXSelector,void*){
  FXDialogBox dialog(GMPlayerManager::instance()->getMainWindow(),fxtr("Clear Music Library?"),DECOR_TITLE|DECOR_BORDER|DECOR_RESIZE|DECOR_CLOSE,0,0,0,0,0,0,0,0,0,0);
  GMPlayerManager::instance()->getMainWindow()->create_dialog_header(&dialog,fxtr("Clear Music Library?"),fxtr("Remove all tracks from the music library?"),NULL);
  FXHorizontalFrame *closebox=new FXHorizontalFrame(&dialog,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,0,0,0,0);
  new GMButton(closebox,fxtr("&Remove All"),NULL,&dialog,FXDialogBox::ID_ACCEPT,BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new GMButton(closebox,fxtr("&Cancel"),NULL,&dialog,FXDialogBox::ID_CANCEL,BUTTON_DEFAULT|LAYOUT_RIGHT|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new FXSeparator(&dialog,SEPARATOR_GROOVE|LAYOUT_FILL_X|LAYOUT_SIDE_BOTTOM);
  FXVerticalFrame * main = new FXVerticalFrame(&dialog,LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0,30,20,10,10);
  FXCheckButton * playlist_check = new GMCheckButton(main,fxtr("Keep play lists"));
  playlist_check->setCheck(FXApp::instance()->reg().readBoolEntry("clear dialog","keep-play-lists",true));
  if (dialog.execute()){
    GMPlayerManager::instance()->stop();
    db->clearTracks(!playlist_check->getCheck());
    GMPlayerManager::instance()->removePlayListSources();
    GMPlayerManager::instance()->getSourceView()->refresh();
    GMPlayerManager::instance()->getTrackView()->refresh();
    FXApp::instance()->reg().writeBoolEntry("clear dialog","keep-play-lists",playlist_check->getCheck());
    }
  return 1;
  }


long GMDatabaseSource::onCmdInfo(FXObject*,FXSelector,void*){
  FXint num_tracks = db->getNumTracks();
  FXint num_albums= db->getNumAlbums();
  FXint num_artists= db->getNumArtists();
  FXint time = db->getTotalTime();
  FXint	days = (FXint) floor((double)time/86400.0);
  time -= (FXint) (86400.0*days);
  FXint	hours = (FXint) floor((double)time/3600.0);
  time -= (FXint) (3600.0*hours);
  FXint	minutes = (FXint) floor((double)time/60.0);
  time -= (FXint) (60.0*minutes);
  FXint	seconds = (FXint) floor((double)time);

  FXDialogBox dialog(GMPlayerManager::instance()->getMainWindow(),fxtr("Music Library Information"),DECOR_TITLE|DECOR_BORDER|DECOR_CLOSE,0,0,0,0,0,0,0,0,0,0);
  GMPlayerManager::instance()->getMainWindow()->create_dialog_header(&dialog,fxtr("Music Library Information"),fxtr("You music collection consists of…"));
  FXHorizontalFrame *closebox=new FXHorizontalFrame(&dialog,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH,0,0,0,0);
  new GMButton(closebox,fxtr("&Close"),NULL,&dialog,FXDialogBox::ID_ACCEPT,BUTTON_INITIAL|BUTTON_DEFAULT|LAYOUT_CENTER_X|FRAME_RAISED|FRAME_THICK,0,0,0,0, 15,15);
  new FXSeparator(&dialog,SEPARATOR_GROOVE|LAYOUT_FILL_X|LAYOUT_SIDE_BOTTOM);


  FXVerticalFrame * main = new FXVerticalFrame(&dialog,LAYOUT_FILL_X|LAYOUT_FILL_Y,0,0,0,0,20,10,5,5);
  FXMatrix * matrix = new FXMatrix(main,2,LAYOUT_FILL_X|MATRIX_BY_COLUMNS);
  FXLabel * label = new FXLabel(matrix,fxtr("Tracks:"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);

  label = new FXLabel(matrix,GMStringVal(num_tracks),NULL,LABEL_NORMAL|JUSTIFY_LEFT);
  label->setTextColor(FXRGB(0,0,255));

  new FXLabel(matrix,fxtr("Artists:"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
  label = new FXLabel(matrix,GMStringVal(num_artists),NULL,LABEL_NORMAL|JUSTIFY_LEFT);
  label->setTextColor(FXRGB(0,0,255));

  new FXLabel(matrix,fxtr("Albums:"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
  label = new FXLabel(matrix,GMStringVal(num_albums),NULL,LABEL_NORMAL|JUSTIFY_LEFT);
  label->setTextColor(FXRGB(0,0,255));

  new FXLabel(matrix,fxtr("Total Time:"),NULL,LABEL_NORMAL|LAYOUT_RIGHT|LAYOUT_CENTER_Y);
  FXString duration;
  if (days) {
    if (days>=2)
      duration+=GMStringFormat("%d days",days);
    else
      duration+=GMStringFormat("%d day",days);

    if (seconds==0 && minutes==0 && hours) duration+="."; else duration+=", ";
    }
  if (hours) {
    if (hours>=2)
      duration+=GMStringFormat("%d hours",hours);
    else
      duration+=GMStringFormat("%d hour",hours);

    if (seconds==0 && minutes==0) duration+="."; else duration+=", ";
    }
  if (minutes) {
    if (minutes>=2)
      duration+=GMStringFormat("%d minutes",minutes);
    else
      duration+=GMStringFormat("%d minute",minutes);

    if (seconds==0) duration+="."; else duration+=", ";
    }
  if (seconds) {
    if (seconds>=2)
      duration+=GMStringFormat("%d seconds. ",seconds);
    else
      duration+=GMStringFormat("%d second.",seconds);
    }
  new FXLabel(matrix,duration,NULL,LABEL_NORMAL|JUSTIFY_LEFT);


  dialog.execute();
  return 1;
  }


long GMDatabaseSource::onCmdTrackPlayed(FXObject*,FXSelector,void*) {
  FXTRACE((60,"%s::onCmdTrackPlayed\n",getClassName()));
  FXASSERT(current_track>=0);
  FXlong timestamp = (FXlong)FXThread::time();
  db->playedTrack(current_track,timestamp);
  GMTrack info;
  if (getTrack(info) && GMPlayerManager::instance()->getAudioScrobbler())
    GMPlayerManager::instance()->getAudioScrobbler()->submit(timestamp,info);
  return 1;
  }

long GMDatabaseSource::onCmdOpenFolder(FXObject*,FXSelector,void*){
  FXIntList tracks;
  GMPlayerManager::instance()->getTrackView()->getSelectedTracks(tracks);
  if (tracks.no()==1) {
    gm_open_folder(FXPath::directory(db->getTrackFilename(tracks[0])));
    }
  return 1;
  }


