#!/usr/X386/bin/wish -file

# A simple interface to ease the creation of crontab jobs
# Cedric BEUST (beust@sophia.inria.fr)

# $Id: tkcron,v 1.18 93/09/21 10:25:32 beust Exp Locker: beust $
# This version is for Tk 3.3

# These booleans are set to 1 if the day is selected
set Sunday 0
set Monday 0
set Tuesday 0
set Wednesday 0
set Thursday 0
set Friday 0
set Saturday 0
set Noday 1

# Receive a string i-j, return a string i,i+1,...,j
proc extendDash {str} {
    set result ""
    set start [string index $str 0]
    set end [string index $str 2]
    set i $start
    if {[string index $str 1] != "-"} {
	set result $start
    } else {
	while {$i <= $end} {
	    if {[isStringEmpty $result]} {
		set result [format "%s" $i]
	    } else {
		set result [format "%s,%s" $result $i]
	    }
	    incr i
	}
    }
    return $result
}

# dow is a crontab entry : either a number, either several numbers separated
# by , or -. This function sets the booleans Sunday...Saturday appropriately
proc parseDayOfWeek {dow} {
    global Sunday Monday Tuesday Wednesday Thursday Friday Saturday Noday
    set Sunday 0
    set Monday 0
    set Tuesday 0
    set Wednesday 0
    set Thursday 0
    set Friday 0
    set Saturday 0
    set Noday 1

    if {[string compare "*" $dow] == 1} {
	return
    }

    while {[isStringEmpty $dow] == 0} {
	set c [string index $dow 0]
	set d [string index $dow 1]
	if {$d == "-"} {
	    set dow [format %s%s\
		            [extendDash [string range $dow 0 2]]\
		            [string range $dow 3 end]]
	}
	case $c {
	    0 { set Sunday 1 ; set Noday 0 }
	    1 { set Monday 1 ; set Noday 0 }
	    2 { set Tuesday 1 ; set Noday 0 }
	    3 { set Wednesday 1 ; set Noday 0 }
	    4 { set Thursday 1 ; set Noday 0 }
	    5 { set Friday 1 ;  set Noday 0 }
	    6 { set Saturday 1 ; set Noday 0 }
	}
	set dow [string range $dow 1 end]
    }
}
    

# 1 if string is empty
proc isStringEmpty {str} {
    if {[string length $str] == 0} {
	return 1
    } else {
	return 0
    }
}

#
# The following procedures create entry widgets with Emacs bindings.
# They are stolen from Zircon by Lindsay Marshall and Jon Harley
#
proc normal win {
    return [expr {[lindex [$win conf -state] 4] == "normal"}]
}

proc emacsInsertSelect {ent} {
    if {[normal $ent] && ![catch {selection get} bf] && $bf != ""} {
	$ent insert insert $bf
	tk_entrySeeCaret $ent
    }
}

proc emacsEntry {args} {
    set name [eval entry $args]
    bind $name <Control-a> { %W icursor 0 }
    bind $name <Control-d> { %W delete insert }
    bind $name <Control-e> { %W icursor end }
    bind $name <Control-k> { %W delete insert end }
    bind $name <Control-u> { %W delete 0 end }
    bind $name <ButtonPress-2> " emacsInsertSelect %W "
    bind $name <Delete> {tk_entryBackspace %W; tk_entrySeeCaret %W}
    bind $name <BackSpace> {tk_entryBackspace %W; tk_entrySeeCaret %W}
    bind $name <Control-h> {tk_entryBackspace %W; tk_entrySeeCaret %W}
    return $name
}

#
# End of stolen stuff :-)
#

# Add the entry in the listbox
proc addEntry {} {
    set lb .mw.lv.list.f2.lb
    $lb insert end [getEntryFromGadgets]
    clearGadgets
}

# Clear all the gadgets
proc clearGadgets {} {
    .mw.repeat.minhour.f1.t1 delete 0 end
    .mw.repeat.minhour.f1.t2 delete 0 end
    .mw.repeat.dayOfMonth.f1.t delete 0 end
    .mw.repeat.month.f1.t delete 0 end
    .mw.repeat.command.t delete 0 end
    set Noday 1
}
# Remove the entry from the listbox
proc removeEntry {} {
    set lb .mw.lv.list.f2.lb
    set sel [$lb curselection]
    if {$sel != ""} {
	set reverseSel ""
	# reverse the list since these are indexes
	foreach i $sel {
	    set reverseSel [linsert $reverseSel 0 $i]
	}
	foreach i $reverseSel {
	    $lb delete $i
	}
    }
    clearGadgets
}

# Read the crontab entry from the gadgets
proc getEntryFromGadgets {} {
    global Sunday Monday Tuesday Wednesday Thursday Friday Saturday Noday
    set result {}
    set min {}
    set hour {}
    set dayOfMonth {}
    set month {}
    set command {}
    set tx .mw.repeat
    set str [.mw.repeat.minhour.f1.t1 get]
    if {[isStringEmpty $str]} {
	set str "*"
    }
    set min $str
    set str [.mw.repeat.minhour.f1.t2 get]
    if {[isStringEmpty $str]} {
	set str "*"
    }
    set hour $str
    set str [.mw.repeat.dayOfMonth.f1.t get]
    if {[isStringEmpty $str]} {
	set str "*"
    }
    set dayOfMonth $str
    set str [.mw.repeat.month.f1.t get]
    if {[isStringEmpty $str]} {
	set str "*"
    }
    set month $str
    set str [.mw.repeat.command.t get]
    if {[isStringEmpty $str]} {
	set str "no_command"
    }
    set command $str
    set dow ""
    set j 0
    foreach i {Sunday Monday Tuesday Wednesday Thursday Friday Saturday} {
	if {[set $i] != 0} {
	    if {[isStringEmpty $dow]} {
		set dow [format "%s" $j]
	    } else {
		set dow [format "%s,%s" $dow $j]
	    }
	}
	incr j
    }
    if {[isStringEmpty $dow]} {
	set dow "*"
    }
    set result [format "%s %s %s %s %s %s" \
		    $min $hour $dayOfMonth $month $dow $command]
    return $result
}

# Return the crontab string as a list
proc parseCrontabFile {str} {
    set result [split $str \n]
    return $result
}

# Return the contents of the listbox
proc listboxContents {lb} {
    set result ""
    $lb select from 0
    $lb select to end
    set indices [$lb curselection]
    while {[llength $indices] != 0} {
	lappend result [$lb get [lindex $indices 0]]
	set indices [lrange $indices 1 end]
    }
    $lb select from 0
    return $result
}

# Read the listbox and return a string containing a valid listbox
proc getCrontabFromGadgets {} {
    set contents [listboxContents .mw.lv.list.f2.lb]
    set result [join $contents \n]
    return $result
}

###############
# Callbacks
###############

# Retrieve the entry double-clicked and display it in the gadgets
proc displayCrontabEntry {} {
    set lb .mw.lv.list.f2.lb
    set tx .mw.repeat
    set selection [$lb get [$lb curselection]]
    scan $selection "%s %s %s %s %s %\[^\n\]" min hour dayOfMonth month dow command

    clearGadgets
    .mw.repeat.minhour.f1.t1 insert 0 $min
    .mw.repeat.minhour.f1.t2 insert 0 $hour
    .mw.repeat.dayOfMonth.f1.t insert 0 $dayOfMonth
    .mw.repeat.month.f1.t insert 0 $month
    .mw.repeat.command.t insert 0 $command
    parseDayOfWeek $dow
}

proc installCrontab {} {
    set cron [getCrontabFromGadgets]
    if [string match "" $cron] {
        catch {exec crontab -r}
    } else {
        catch {exec crontab << $cron}
        puts stdout $cron
    }
    destroy .
}

proc cancelCrontab {} {
    destroy .
}

# Return a list of all the crontab entries
proc getCrontab {} {
    catch {exec crontab "-l"} cr
    .mw.lv.list.f2.lb delete 0 end
    if {[string match "*an't open*" $cr] == 1} {
	puts stdout "Mmmh... you don't seem to have a crontab on this machine"
	set result {}
    } else {
	set result [parseCrontabFile $cr]
	foreach i $result {
	    .mw.lv.list.f2.lb insert end $i
	}
    }
    return $result
}

##########
# Main
##########

proc main {} {
#    frame .mw -borderwidth 2 -relief raised
# Create a vertical frame that will show the crontab entry  (.mw.repeat)
    frame .mw.repeat -borderwidth 2 
    frame .mw.repeat.minhour -borderwidth 1
    frame .mw.repeat.dayOfMonth -borderwidth 1
    frame .mw.repeat.month -borderwidth 1
    frame .mw.repeat.dayOfWeek -borderwidth 1

    frame .mw.repeat.minhour.f1
    pack [label .mw.repeat.minhour.f1.l2 -text "Hour"] \
	[emacsEntry .mw.repeat.minhour.f1.t2 -relief sunken -borderwidth 2 \
	 -width 15] \
	[label .mw.repeat.minhour.f1.l1 -text "Min"] \
	[emacsEntry .mw.repeat.minhour.f1.t1 -relief sunken -borderwidth 2 \
	 -width 15] -side left


    bind .mw.repeat.minhour.f1.t1 <Tab> "focus .mw.repeat.dayOfMonth.f1.t"
    bind .mw.repeat.minhour.f1.t1 <Return> "focus .mw.repeat.dayOfMonth.f1.t"
    bind .mw.repeat.minhour.f1.t1 <Shift-Tab> "focus .mw.repeat.minhour.f1.t2"
    bind .mw.repeat.minhour.f1.t2 <Tab> "focus .mw.repeat.minhour.f1.t1"
    bind .mw.repeat.minhour.f1.t2 <Return> "focus .mw.repeat.minhour.f1.t1"
    bind .mw.repeat.minhour.f1.t2 <Shift-Tab> "focus .mw.repeat.command.t"

    frame .mw.repeat.dayOfMonth.f1
    pack\
	[label .mw.repeat.dayOfMonth.f1.l -text "Day of month"] \
	[emacsEntry .mw.repeat.dayOfMonth.f1.t -relief sunken -borderwidth 2 \
	 -width 15] -side left
    bind .mw.repeat.dayOfMonth.f1.t <Tab> "focus .mw.repeat.month.f1.t"
    bind .mw.repeat.dayOfMonth.f1.t <Return> "focus .mw.repeat.month.f1.t"
    bind .mw.repeat.dayOfMonth.f1.t <Shift-Tab> "focus .mw.repeat.minhour.f1.t1"

    frame .mw.repeat.month.f1
    pack \
	[label .mw.repeat.month.f1.l -text "Month"] \
	[emacsEntry .mw.repeat.month.f1.t -relief sunken -borderwidth 2 \
	 -width 15] -side left
    bind .mw.repeat.month.f1.t <Tab> "focus .mw.repeat.command.t"
    bind .mw.repeat.month.f1.t <Return> "focus .mw.repeat.command.t"
    bind .mw.repeat.month.f1.t <Shift-Tab> "focus .mw.repeat.dayOfMonth.f1.t"

    # put all the radios in a frame
    frame .mw.repeat.dayOfWeek.f1
    frame .mw.repeat.dayOfWeek.f1.radios -relief sunken -borderwidth 2
    frame .mw.repeat.dayOfWeek.f1.radios.r1
    frame .mw.repeat.dayOfWeek.f1.radios.r2

    pack \
	[checkbutton .mw.repeat.dayOfWeek.f1.radios.r1.sunday -text Sunday \
	 -relief flat -variable Sunday]\
	[checkbutton .mw.repeat.dayOfWeek.f1.radios.r1.monday -text Monday \
	 -relief flat -variable Monday]  \
	[checkbutton .mw.repeat.dayOfWeek.f1.radios.r1.tuesday -text Tuesday \
	 -relief flat -variable Tuesday]  \
	[checkbutton .mw.repeat.dayOfWeek.f1.radios.r1.wednesday -text Wednesday\
	 -relief flat -variable Wednesday] -side left

    pack \
	[checkbutton .mw.repeat.dayOfWeek.f1.radios.r2.thursday -text Thursday\
	 -relief flat -variable Thursday]  \
	[checkbutton .mw.repeat.dayOfWeek.f1.radios.r2.friday -text Friday\
	 -relief flat -variable Friday]  \
	[checkbutton .mw.repeat.dayOfWeek.f1.radios.r2.saturday -text Saturday\
	 -relief flat -variable Saturday]  \
	[checkbutton .mw.repeat.dayOfWeek.f1.radios.r2.any -text Any\
	 -relief flat -variable Noday ] -side left

    pack \
	 .mw.repeat.dayOfWeek.f1.radios.r1 \
	 .mw.repeat.dayOfWeek.f1.radios.r2 -side top

    pack\
	[label .mw.repeat.dayOfWeek.f1.l -text "Day of week"]\
	.mw.repeat.dayOfWeek.f1.radios -side left

    # Create the command (.mw.repeat.command)
    frame .mw.repeat.command -borderwidth 3
    pack \
	[label .mw.repeat.command.l -text "Command"]\
	[emacsEntry .mw.repeat.command.t -relief sunken -borderwidth 2 \
	 -width 60] -side left
    bind .mw.repeat.command.t <Return> addEntry
    bind .mw.repeat.command.t <Tab> "focus .mw.repeat.minhour.f1.t2"
    bind .mw.repeat.command.t <Shift-Tab> "focus .mw.repeat.month.f1.t"


    pack .mw.repeat.minhour.f1
    pack .mw.repeat.dayOfMonth.f1
    pack .mw.repeat.month.f1
    pack .mw.repeat.dayOfWeek.f1 
    pack .mw.repeat.command -side bottom

    pack \
	.mw.repeat.minhour \
	.mw.repeat.dayOfMonth \
	.mw.repeat.month \
	.mw.repeat.dayOfWeek -side top -anchor e

# Another vertical frame that will contain the list view (.mw.lv) and command
    frame .mw.lv  -background blue
    frame .mw.lv.list
    frame .mw.lv.command -borderwidth 1

    # the buttons
    frame .mw.lv.list.f1
    button  .mw.lv.list.f1.b1 -text Add -command addEntry
    button .mw.lv.list.f1.b2 -text Remove -command removeEntry
    pack .mw.lv.list.f1.b1 \
	        .mw.lv.list.f1.b2 -side left -expand yes -fill x

    # create the listbox (.mw.lv.list.f2)
    frame .mw.lv.list.f2  -borderwidth 1 -relief raised
    pack \
	[listbox .mw.lv.list.f2.lb -geometry 60x10 -yscrollcommand ".mw.lv.list.f2.sb set"] \
	[scrollbar .mw.lv.list.f2.sb -command ".mw.lv.list.f2.lb yview"] \
           -side right -fill y
    bind .mw.lv.list.f2.lb <Double-1> displayCrontabEntry
    .mw.lv.list.f2.lb select from 0


    # stack the 3 frames vertically
    pack .mw.lv.list.f2 \
         .mw.lv.list.f1

    pack .mw.lv.list


# And a horizontal one to hold the Action buttons (.mw.actions)
    frame .mw.actions
    pack \
	[button .mw.actions.clear -text "Clear gadgets" -command clearGadgets] \
	[button .mw.actions.install -text "Install" -command installCrontab] \
	[button .mw.actions.getActive -text "Get active crontab" -command getCrontab] \
	[button .mw.actions.cancel -text "Cancel" -command cancelCrontab] \
	-side left -ipadx 3m -ipady 2m -padx 20 -expand yes

# Pack the three main frames
    pack .mw.actions -side bottom -pady 30
    pack .mw.lv -side top -pady 30
    pack .mw.repeat -side top -side top

# Focus on the first gadget
    focus .mw.repeat.minhour.f1.t2
}

frame .mw -borderwidth 2 -relief raised
wm title . "TkCron 1.18"
main
pack .mw
getCrontab
