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

#
# MP3Shell v@MP3SHELL_VERSION@  A simple GUI for the mpg123 MP3 player
#
# Copyright (C) 2000 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
# mats_peterson@swipnet.se  (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 ~/.mp3shellrc]} {
    puts "ERROR: missing .mp3shellrc file"
    exit
}
source ~/.mp3shellrc

set mpg(version) @MP3SHELL_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



proc mpgGetInput {} {
    global mpg

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

    switch $type {
        @R {
	    gets $mpg(fd) s
        }
        @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]]
            }

            set mpg(running) 1
            set mpg(length) ""
            set mpg(runstatus) ""
            set mpg(firstframe) 1
        }
        @F {
	    gets $mpg(fd) s
	    if {! $mpg(running)} return

	    set floatsec [expr {[lindex $s 2]}]
            if {$floatsec < 0.0} return

            if {$mpg(firstframe)} {
                set sec_left [expr {int([lindex $s 3])}]
                set tmp "Length  [expr {$sec_left / 60}]:"
                append tmp "[format "%02d" [expr {$sec_left % 60}]]"
                set mpg(length) $tmp
                set mpg(firstframe) 0
            }

            set sec [expr {int($floatsec)}]
            if {$sec != $mpg(old_sec)} {
                set tmp "[expr {$sec / 60}]:"
                append tmp "[format "%02d" [expr {$sec % 60}]]"
                set mpg(runstatus) $tmp
                set mpg(old_sec) $sec
            }
        }
        @P {
	    gets $mpg(fd) s
	    switch [string trim $s] {
                0  {
                    set mpg(stopped) 1
                    if {! $mpg(running)} return

                    if {$mpg(curtype)} {
                        set fullname [gets $mpg(plfd)]
                        if {$fullname == ""} {
                            close $mpg(plfd)
                            if {$mpg(loop)} {
                                set mpg(plfd) [open $mpg(playlist) r]
                                set fullname [gets $mpg(plfd)]
                            } else return
                        } 
                    } else {
                        if {! $mpg(loop)} return
                        set fullname $mpg(filename)
                    }
                    puts $mpg(fd) "LOAD $fullname"
                }
                1 {
                    set mpg(runstatus) "Paused"
                }
                2 {
                    set mpg(runstatus) ""
                }
            }
        }
        @S {
            set lstr(2) "II"
            set lstr(3) "III"

	    gets $mpg(fd) s
	    set type [lindex $s 0]
            set layer [lindex $s 1]
            set freq [lindex $s 2]
            set mode [lindex $s 3]
            set bitrate [lindex $s 10]
            set mpg(format) "MPEG $type layer $lstr($layer), $bitrate kbps, $freq Hz $mode"
        }
        @E {
	    errmsg [gets $mpg(fd)]
        }
        default {
	    gets $mpg(fd) s
	    fileevent $mpg(fd) readable {}
            catch {exec killall mpg123}
            catch {close $mpg(fd)}
            if {$mpg(curtype)} {close $mpg(plfd)}
            set mpg(running) 0
	    errmsg "$type$s"
	    if {$mpg(init)} {
		exit
	    }
            mpgInitMpeg
	}
    }
    set mpg(init) 0
}


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 mpgPlay {} {
    global mpg

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

    switch -regexp $fname {
        {\.[Mm][Pp]3$} {
            set mpg(filename) $fname
            set fullname $mpg(filename)
            set mpg(curtype) 0
        }
        {\.[Mm]3[Uu]$} {
            set mpg(playlist) $fname
            if {$mpg(shuffle)} {
                exec plshuf $mpg(playlist) >$mpg(shuflist)
                set mpg(playlist) $mpg(shuflist)
            }
            set mpg(plfd) [open $mpg(playlist) r]
            set fullname [gets $mpg(plfd)]
            set mpg(curtype) 1
        }
        default {
            errmsg "Invalid file type"
            return
        }
    }

    puts $mpg(fd) "LOAD $fullname"
}


proc mpgStop {} {
    global mpg

    if {! $mpg(running)} return
    set mpg(running) 0

    puts $mpg(fd) "STOP"

    set mpg(stopped) 0
    tkwait variable mpg(stopped)

    if {$mpg(curtype)} {close $mpg(plfd)}

    set mpg(title) ""
    set mpg(length) ""
    set mpg(format) ""
    set mpg(old_sec) -1
    set mpg(runstatus) "Stopped"
    update idletasks
}


proc mpgPause {} {
    global mpg

    if {! $mpg(running)} return
    puts $mpg(fd) "PAUSE"
}


proc mpgSetMute {mute} {
	global mpg

    if {$mute} {
        set mpg(volume) [eval $mpg(getvolcmd)]
        eval $mpg(setvolcmd) 0
    } else {
        eval $mpg(setvolcmd) $mpg(volume)
    }
}


proc mpgSetVol {dummy} {
    global mpg

    eval $mpg(setvolcmd) $mpg(volume)
}


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

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

    if {[file extension $fname] == ""} {
	set fname "$fname.m3u"
    }
       
    set f [open $fname w]
    for {set i 0} {$i < [.plwin.playlist.list size]} {incr i} {
        puts $f [.plwin.playlist.list get $i] 
    }
    close $f
}


proc mpgRefreshDir {up} {
    global mpg

    .plwin.dirlist.list delete 0 end
    set res [lrange [lsort -dictionary [glob -nocomplain .* *]] 1 end]

    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 -regexp $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 mpgClick {sel} {
    global mpg

    set mpg(old_active) [.plwin.dirlist.list index active]

    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]} {
            set f [open $sel r]
            while {[gets $f line] >= 0} {
                .plwin.playlist.list insert end $line
            }
            close $f
        } else {
            if {[regexp {\.[Mm][Pp]3$} $sel]} {
                .plwin.playlist.list insert end "[pwd]/$sel"
            }
        }
    }
}


proc mpgPlaylist {} {
    global mpg

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

    frame .plwin.spacer -height 2m
    pack .plwin.spacer

    label .plwin.label1 -textvariable mpg(cur_dir) -anchor w
    pack .plwin.label1 -fill x -padx 2m

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

    label .plwin.label2 -text "Playlist" 
    pack .plwin.label2 -side top 

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

    frame .plwin.buttons
    pack .plwin.buttons -side bottom -pady 2m
    button .plwin.buttons.clear -text Clear -takefocus 0 \
            -command {.plwin.playlist.list delete 0 end}
    button .plwin.buttons.save -text Save -takefocus 0 \
            -command {mpgSavePlaylist}
    button .plwin.buttons.quit -text Quit -takefocus 0 \
            -command {focus $mpg(old_focus); destroy .plwin}
    pack .plwin.buttons.clear .plwin.buttons.save .plwin.buttons.quit \
            -side left -padx 2m

    bindtags .plwin.dirlist.list \
            [linsert [bindtags .plwin.dirlist.list] 1 PlayListbox]
    bindtags .plwin.playlist.list \
            [linsert [bindtags .plwin.playlist.list] 1 PlayListbox]

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

    bind .plwin.playlist.list <Double-Button-1> {
        .plwin.playlist.list delete [.plwin.playlist.list index active] 
        .plwin.playlist.list selection set active active
    }      
    bind .plwin.playlist.list <Delete> {
        .plwin.playlist.list delete [.plwin.playlist.list index active] 
        .plwin.playlist.list selection set active active
    }

    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 .plwin <c> {.plwin.playlist.list delete 0 end}
    bind .plwin <s> {mpgSavePlaylist}
    bind .plwin <q> {focus $mpg(old_focus); destroy .plwin}

    cd $mpg(initdir)
    set mpg(cur_dir) $mpg(initdir)
    set mpg(old_dir) $mpg(cur_dir)
    mpgRefreshDir 0
    focus .plwin.dirlist.list
}


proc mpgInitMpeg {} {
    global mpg

    set mpg(fd) [open "|mpg123 $mpg(params) -R - 2>@ stdout" r+]
    fconfigure $mpg(fd) -buffering line
    fileevent $mpg(fd) readable mpgGetInput
}


proc mpgQuit {} {
    global mpg

    catch {exec killall mpg123}
    catch {close $mpg(fd)}
    exit
}


proc mpgJump {n} {
    global mpg

    switch $n {
        0 {
            puts $mpg(fd) "JUMP 0"
        }
        1 {
            puts $mpg(fd) "JUMP -512"
        }
        2 {
            puts $mpg(fd) "JUMP +512"
        }
    }
}


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


set mpg(init) 1
mpgInitMpeg

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

frame .buttons -relief raised -bd 3
pack .buttons -side top

button .buttons.play -width 4 -text "Play" -command mpgPlay
button .buttons.stop -width 4 -text "Stop" -command mpgStop
button .buttons.pause -width 4 -text "Pause" -command mpgPause

button .buttons.plist -width 4 -text "PList" -command mpgPlaylist
button .buttons.quit -width 4 -text "Quit" -command mpgQuit

checkbutton .buttons.shuffle -text "Shfl" -variable mpg(shuffle) -relief flat
checkbutton .buttons.loop -text "Loop" -variable mpg(loop) -relief flat
if {$mpg(mute_func)} {
    checkbutton .buttons.mute -text "Mute" -variable mpg(mute) \
            -relief flat -command {mpgSetMute $mpg(mute)}
}

pack .buttons.play .buttons.stop .buttons.pause \
        .buttons.plist .buttons.quit -side left -padx 0.2m -pady 2m
frame .buttons.spacer1 -width 3m; pack .buttons.spacer1 -side left
pack .buttons.shuffle .buttons.loop -side left -padx 0.4m -pady 2m
if {$mpg(mute_func)} {
    pack .buttons.mute -side left -padx 0.4m -pady 2m
}
frame .buttons.spacer2 -width 1m; pack .buttons.spacer2 -side left

frame .songinfo
pack .songinfo -fill x
label .songinfo.title -height 3 -anchor nw -justify left \
	-textvariable mpg(title)
label .songinfo.length -anchor n -textvariable mpg(length) 
pack .songinfo.title -side left -padx 4m -pady 2m
pack .songinfo.length -side right -padx 4m -pady 2m -fill y
frame .miscinfo
pack .miscinfo -fill x
label .miscinfo.format -textvariable mpg(format)
label .miscinfo.runstatus -textvariable mpg(runstatus)
pack .miscinfo.format -side left -padx 4m -pady 2m
pack .miscinfo.runstatus -side right -padx 4m -pady 2m

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

bind . <Left> {mpgJump 1}
bind . <Right> {mpgJump 2}
bind . <Home> {mpgJump 0}

bind . <space> {mpgPause}

if {$mpg(mute_func)} {
    bind . <m> {set mpg(mute) [expr {!$mpg(mute)}]; mpgSetMute $mpg(mute)}
}
bind . <q> {mpgQuit}

set mpg(running) 0
set mpg(curtype) 0

set mpg(shuflist) /tmp/shuflist.sndshell

set mpg(old_sec) -1
set mpg(runstatus) ""
cd $mpg(initdir)
.
