#!/bin/sh
# the next line restarts using wish \
exec wish "$0" "$@" -name ximp3shell

#
# ximp3shell v@XIMP3SHELL_VERSION@  A simple GUI for the ximp3 MP3 player
#
# Copyright (C) 2001 Mats Peterson
#
# 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
#
# Please send any comments/bug reports to
# matsp888@yahoo.com  (Mats Peterson)
#

if {[info tclversion] < 8.3} {
    puts "You must have Tcl/Tk version 8.3 or higher to run this program."
    exit
}

if {! [file exists ~/.ximp3shellrc]} {
    puts "ERROR: missing .ximp3shellrc file"
    exit
}
source ~/.ximp3shellrc

set mpg(version) @XIMP3SHELL_VERSION@

set exec_prefix @prefix@
set mpg(pkgdir) @libdir@

if {[lsearch -exact $auto_path $mpg(pkgdir)] == -1} {
    lappend auto_path $mpg(pkgdir)
}

package require mpTk @MPTK_VERSION@

namespace import mpTk::Msg::errmsg
namespace import mpTk::List::listscroll
namespace import mpTk::Text::textscroll



proc mpgGetInput {} {
    global mpg

    if {[eof $mpg(fd)]} {
	mpgStop
	return
    }

    set type [string trim [read $mpg(fd) 3]]

    if {! [string length $type]} {
	return
    }

    switch $type {
        @I {
	    set id3 [read $mpg(fd) 4]
            if {[string match {ID3:} $id3]} {
		set s [read $mpg(fd) 124]
		set artist [string trim [string range $s 30 59]]
                set title [string trim [string range $s 0 29]]
                set album [string trim [string range $s 60 89]]
                set mpg(title) "$artist\n$title\n$album"
		# Read genre
		gets $mpg(fd) s
	    } else {
		gets $mpg(fd) s
                set mpg(title) [string trim [string range "$id3$s" 0 29]]
            }
        }
        @S {
	    gets $mpg(fd) s
            set mpg(format) $s
        }
	@F {
	    gets $mpg(fd) s
	    set floatsec [lindex $s 1]
            set sec [expr {int($floatsec)}]
            if {$sec != $mpg(old_sec)} {
                set tmp "[expr {$sec / 60}]:"
                append tmp "[format "%02d" [expr {$sec % 60}]]"
                set mpg(time) $tmp
                set mpg(old_sec) $sec
            }
	}
	@E {
            gets $mpg(fd) s
	    set mpg(format) $s
        }
	default {
	    gets $mpg(fd) s
    	    mpgStop
	    errmsg "$type$s"
	}
    }
    update idletasks
}


proc mpgOpenFile {} {
    global mpg
    
    switch $mpg(curtype) {
        0 { set types {
	    {{MP3 files}     {.mp3 .MP3}}
            {{Playlists}     {.m3u .M3U}}
	    {{All Files}     *}
	    }
        }
        1 { set types {
            {{Playlists}     {.m3u .M3U}}
            {{MP3 files}     {.mp3 .MP3}}
	    {{All Files}     *}
            }
        }
    }
    return [tk_getOpenFile -filetypes $types]
}


proc mpgDoPlay {fname ftype shuffle loop} {
    global mpg
    
    update
    update
    mpgStop
    set cmd [list |ximp3 -d $mpg(device)]
    if {$shuffle && $ftype} {
	lappend cmd -s
    }
    if {$loop} {
	lappend cmd -l
    }
    lappend cmd -r $fname 2>@ stdout
    if {[catch {set mpg(fd) [open $cmd r]} r]} {
	errmsg $r
	return
    }
    fileevent $mpg(fd) readable mpgGetInput
    set mpg(runstatus) "Playing"
    set mpg(running) 1
}


proc mpgPlay {} {
    global mpg

    set fname [mpgOpenFile]
    if {$fname == ""} return

    switch -regexp $fname {
        {\.[Mm][Pp]3$} {
            set mpg(curtype) 0
        }
        {\.[Mm]3[Uu]$} {
            set mpg(curtype) 1
        }
        default {
            errmsg "Invalid file type"
            return
        }
    }

    mpgDoPlay $fname $mpg(curtype) $mpg(shuffle) $mpg(loop)
}


proc mpgStop {} {
    global mpg

    if {! $mpg(running)} return
    set mpg(running) 0
    update idletasks
    catch {fileevent $mpg(fd) readable {}}
    catch {exec killall ximp3}
    while {1} {
	if {[catch {exec ps -xa | grep {[0-9][[:space:]]\+ximp3 -d}}]} {
	    break
	}
	after 100
    }
    catch {close $mpg(fd)}
    set mpg(time) {}
    set mpg(old_sec) -1
    if {$mpg(runstatus) != ""} {
	set mpg(runstatus) "Stopped"
    }
    update idletasks
}


proc mpgPlayPlaylist {} {
    global mpg

    if {! [llength $mpg(memlist)]} {
	return
    }
    if {[catch {set f [open $mpg(pwlist) w]} r]} {
	errmsg $r
	return
    }
    for {set i 0} {$i < [llength $mpg(memlist)]} {incr i} {
	puts $f [lindex $mpg(memlist) $i]
    }
    close $f

    mpgDoPlay $mpg(pwlist) 1 $mpg(pwshuffle) $mpg(pwloop)
}


proc mpgSavePlaylist {} {
    global mpg
    
    set types {
        {{Playlists}  {.m3u .M3U}}
        {{All Files}  *}
    }

    set fname [tk_getSaveFile -filetypes $types -parent .plwin]
    if {$fname == ""} return

    if {[file extension $fname] == ""} {
	set fname "$fname.m3u"
    }
       
    if {[catch {set f [open $fname w]} r]} {
	errmsg $r
	return
    }
    for {set i 0} {$i < [llength $mpg(memlist)]} {incr i} {
	puts $f [lindex $mpg(memlist) $i] 
    }
    close $f
}


proc mpgRefreshDir {up} {
    global mpg

    .plwin.dirlist.list delete 0 end

    set res [lsort -dictionary [glob -nocomplain .* *]]

    # Remove ".", and move ".." to the beginning of the list if it
    # isn't there already
    set n [lsearch $res "."]
    set res [lreplace $res $n $n]
    if {[set n [lsearch $res ".."]] != 0} {
	set res [lreplace $res $n $n]
	set res [lreplace $res 0 -1 ".."]
    }

    set dirs {}
    set files {}

    foreach i $res {
        if {[file isdirectory $i]} {
            lappend dirs "$i/"
        } else {
            if {[regexp {\.[Mm]3[Uu]$|\.[Mm][Pp]3$} $i]} {
                lappend files $i
            }
        }
    }

    set entries [concat $dirs $files]
    foreach i $entries {
        .plwin.dirlist.list insert end $i
    }

    if {$up} {
        set i [lsearch $entries "[file tail $mpg(old_dir)]*"]
        .plwin.dirlist.list activate $i
        .plwin.dirlist.list selection set $i $i
        .plwin.dirlist.list see $i
    } else {
        .plwin.dirlist.list activate 0
        .plwin.dirlist.list selection set 0 0
    }
    return 0
}


proc mpgAddToPlaylist {fname} {
    global mpg

    if {$mpg(fullpath)} {
	set line $fname
    } else {
	set i1 [string last / $fname]
	if {[set i2 [string last / [string range $fname \
		0 [expr {$i1 - 1}]]]] != -1} {
	    set tail [expr {$i2 + 1}]
	} else {
	    set tail [expr {$i1 + 1}]
	}
	set line [string range $fname $tail end]
    }
    .plwin.playlist.list insert end $line
}


proc mpgRemoveFromPlaylist {} {
    global mpg
    
    set n [.plwin.playlist.list index active]
    set mpg(memlist) [lreplace $mpg(memlist) $n $n]
    .plwin.playlist.list delete $n
    .plwin.playlist.list selection set active active
}


proc mpgClearPlaylist {} {
    global mpg
    
    set mpg(memlist) {}
    .plwin.playlist.list delete 0 end
}


proc mpgRefreshPlaylist {} {
    global mpg

    set a [.plwin.playlist.list index active]
    set s [.plwin.playlist.list curselection]

    .plwin.playlist.list delete 0 end
    for {set i 0} {$i < [llength $mpg(memlist)]} {incr i} {
	mpgAddToPlaylist [lindex $mpg(memlist) $i]
    }

    .plwin.playlist.list activate $a
    if {[string length $s]} {
	.plwin.playlist.list selection set $a $a
    }
    .plwin.playlist.list see $a
}


proc mpgClick {sel} {
    global mpg

    if {[file isdirectory $sel]} {
        set up 0
        if {[string match "../" $sel]} {
            set up 1
        }
        set mpg(old_dir) [pwd]
        if {[catch {cd $sel} r]} {
	    errmsg $r
	    return
	}
        if {[mpgRefreshDir $up]} {
            cd $mpg(old_dir)
        } else {
            set mpg(cur_dir) [pwd]
        }
    } else {
        if {[regexp {\.[Mm]3[Uu]$} $sel]} {
            if {[catch {set f [open $sel r]} r]} {
		errmsg $r
		return
	    }
            while {[gets $f line] >= 0} {
	        lappend mpg(memlist) $line
		mpgAddToPlaylist $line
            }
            close $f
        } else {
            if {[regexp {\.[Mm][Pp]3$} $sel]} {
	        lappend mpg(memlist) "[pwd]/$sel"
                mpgAddToPlaylist "[pwd]/$sel"
            }
        }
    }
}


proc mpgPlaylist {} {
    global mpg

    set w .plwin
    set mpg(old_focus) [focus]
    catch [list destroy $w]
    toplevel $w -relief raised -bd 2 -class Ximp3shell
    wm title $w "Playlist Manager"
    wm grid $w 75 1 7 34
    wm geometry $w 75x8

    frame $w.spacer -height 2m
    pack $w.spacer

    set f $w.dirhdr
    frame $f -bd 0
    pack $f -side top -fill x
    label $f.label -textvariable mpg(cur_dir) -anchor w
    pack $f.label -side left -padx 2m

    set f $w.dirlist
    frame $f -bd 2m
    pack $f -side top -expand yes -fill both
    listscroll $f 75 1 1
    $f.list configure -selectmode extended
    $f.xscroll configure -takefocus 0
    $f.yscroll configure -takefocus 0

    set f $w.plhdr
    frame $f -bd 0
    pack $f -side top -fill x
    label $f.label -text "Playlist" -anchor w
    checkbutton $f.fullpath -text "Show Full Path" -takefocus 0 \
	    -relief flat -variable mpg(fullpath) \
	    -command mpgRefreshPlaylist
    pack $f.label -side left -padx 2m
    pack $f.fullpath -side right -padx 2m

    set f $w.playlist
    frame $f -bd 2m
    pack $f -side top -expand yes -fill both
    listscroll $f 75 1 1
    $f.xscroll configure -takefocus 0
    $f.yscroll configure -takefocus 0

    set f $w.buttons
    frame $f
    pack $f -side bottom -pady 2m
    button $f.clear -text Clear -takefocus 0 \
            -command {mpgClearPlaylist}
    button $f.play -text Play -takefocus 0 \
            -command {mpgPlayPlaylist}
    button $f.stop -text Stop -takefocus 0 \
            -command {mpgStop}
    button $f.save -text Save -takefocus 0 \
            -command {mpgSavePlaylist}
    button $f.quit -text Quit -takefocus 0 \
            -command {focus $mpg(old_focus); destroy .plwin}
    checkbutton $f.shuffle -text "Shfl" -takefocus 0 \
	    -relief flat -variable mpg(pwshuffle)
    checkbutton $f.loop -text "Loop" -takefocus 0 \
	    -relief flat -variable mpg(pwloop)

    pack $f.clear $f.play $f.stop $f.save $f.quit -side left -padx 1.5m
    frame $f.spacer1 -width 5m; pack $f.spacer1 -side left
    pack $f.shuffle $f.loop -side left -padx 0.4m -pady 2m

    set dl $w.dirlist.list
    set pl $w.playlist.list
    
    bindtags $dl [linsert [bindtags $dl] 1 PlayListbox]
    bindtags $pl [linsert [bindtags $pl] 1 PlayListbox]

    bind $dl <Double-Button-1> {
        mpgClick [.plwin.dirlist.list get [.plwin.dirlist.list curselection]]
    }
    bind $dl <Button-3> {
        foreach i [.plwin.dirlist.list curselection] { 
            mpgClick [.plwin.dirlist.list get $i] 
        }
    }
    bind $dl <Return> {
        foreach i [.plwin.dirlist.list curselection] { 
            mpgClick [.plwin.dirlist.list get $i] 
        }
    }

    bind $pl <Double-Button-1> {mpgRemoveFromPlaylist}
    bind $pl <Delete> {mpgRemoveFromPlaylist}

    bind PlayListbox <Tab> {
        focus [set w [tk_focusNext %W]]
        $w selection set active active
    }
    bind PlayListbox <Home> {
        %W activate 0
        %W see 0
        %W selection clear 0 end
        %W selection set 0
    }
    bind PlayListbox <End> {
        %W activate end
        %W see end
        %W selection clear 0 end
        %W selection set end
    }
    bind PlayListbox <Insert> {
        tkListboxExtendUpDown %W 1
    }

    bind $w <c> {mpgClearPlaylist}
    bind $w <p> {mpgPlayPlaylist}
    bind $w <s> {mpgStop}
    bind $w <a> {mpgSavePlaylist}
    bind $w <q> {focus $mpg(old_focus); destroy .plwin}

    set mpg(memlist) {}

    cd $mpg(initdir)
    set mpg(cur_dir) $mpg(initdir)
    set mpg(old_dir) $mpg(cur_dir)
    mpgRefreshDir 0
    focus $dl
}


proc mpgQuit {} {
    global mpg

    mpgStop
    if {[file exists $mpg(pwlist)]} {
        catch {file delete $mpg(pwlist)}
    }
    if {[file exists $mpg(shuflist)]} {
        catch {file delete $mpg(shuflist)}
    }
    exit
}


###########################################################################


wm title . "ximp3shell v$mpg(version)"
wm resizable . no no
wm protocol . WM_DELETE_WINDOW mpgQuit

set f .buttons
frame $f -relief raised -bd 3
pack $f -side top

button $f.play -width 4 -text "Play" -command mpgPlay
button $f.stop -width 4 -text "Stop" -command mpgStop
button $f.plist -width 4 -text "PList" -command mpgPlaylist
button $f.quit -width 4 -text "Quit" -command mpgQuit
checkbutton $f.shuffle -text "Shfl" -variable mpg(shuffle) -relief flat
checkbutton $f.loop -text "Loop" -variable mpg(loop) -relief flat

pack $f.play $f.stop $f.plist $f.quit -side left -padx 0.2m -pady 2m
frame $f.spacer1 -width 3m; pack $f.spacer1 -side left
pack $f.shuffle $f.loop -side left -padx 0.4m -pady 2m
frame $f.spacer2 -width 1m; pack $f.spacer2 -side left

set f .info1
frame $f
pack $f -fill x
label $f.title -height 3 -anchor nw -justify left \
	-textvariable mpg(title)
label $f.time -anchor n -textvariable mpg(time)
pack $f.title -side left -padx 4m -pady 2m
pack $f.time -side right -padx 4m -pady 2m -fill y
set f .info2
frame $f
pack $f -fill x
label $f.format -textvariable mpg(format)
label $f.runstatus -textvariable mpg(runstatus)
pack $f.format -side left -padx 4m -pady 2m
pack $f.runstatus -side right -padx 4m -pady 2m

bind . <p> {mpgPlay}
bind . <s> {mpgStop}
bind . <l> {mpgPlaylist}
bind . <q> {mpgQuit}

set mpg(running) 0
set mpg(curtype) 0
set mpg(pwlist) "$env(HOME)/.ximp3shell_pwlist.m3u"
set mpg(shuflist) "$env(HOME)/.ximp3shell_shuflist.m3u"
set mpg(old_sec) -1
set mpg(runstatus) {}

cd $mpg(initdir)
.
