# Copyright (c) 1993 by Sanjay Ghemawat
##############################################################################
# DayView
#
# DESCRIPTION
# ===========
# A DayView shows the notices and appointments for one day.

# Global clipboard
set dayview_clipboard ""

# Count of open views
set dayview_count 0

# Global state for menus.
set dayview_state(junk) ""
unset dayview_state(junk)

# Last print response
set dayview_print "lpr"

# Autoload support
proc DayView {} {}

class DayView {} {
    global dayview_count
    incr dayview_count

    global dayview_state
    set dayview_state($self,remind) -1

    set name .$self
    set slot(window) $name
    set slot(focus) ""
    set slot(focuswindow) ""

    # XXX Ignore any geometry default because toplevel does not understand ita
    toplevel $name -bd 0 -class Dayview -geometry ""

    focus_interest $name
    bind $name <Any-Enter> [list focus_restrict $name]

    $self build_menu

    set slot(apptlist) [ApptList $name.al $self]
    set slot(notelist) [NoteList $name.nl $self]
    set slot(dateeditor) [DateEditor $name.de [date today] [list $self set_date]]

    label $name.cal -relief flat -text ""
    label $name.rep -relief flat -text ""
    frame $name.status -relief raised -bd 1
    pack append $name.status $name.cal {left}
    pack append $name.status $name.rep {right}

    pack append $name $name.menu {top fillx}
    pack append $name $name.status {bottom fillx}

    if [pref vertical] {
	# Layout vertically
	pack append $name $name.al {bottom expand fill}
    } else {
	# Layout horizontally
	pack append $name $name.al {right expand fill}
    }

    pack append $name $name.nl {bottom expand fill}
    pack append $name $name.de {top fillx}

    wm title $name Calendar
    wm iconname $name ical
    wm protocol $name WM_DELETE_WINDOW [list $self close]

    # Geometry management
    set widthUnit [expr [pref itemWindowWidth]/10]
    wm grid $name\
	10\
	[pref apptLines]\
	$widthUnit\
	[pref itemLineHeight]
    wm minsize $name 5 10
    wm maxsize $name 15 48

    # Set-up geometry
    set g [option get $name geometry Geometry]
    if {$g != ""} {
	wm geometry $name $g
    }

    $self set_date [date today]

    bind $name <Any-KeyPress> [list $self key %A]

    # Useful for updating status window.
    # Save is not useful because dayview never keeps unsaved changes.
    # Add/delete/include/exclude are not useful because we are only
    # interested in the currently selected item, and one of our
    # subwindows is going to tell us about it anyway.
    trigger on change	[list $self change]
}

method DayView window {} {
    return $slot(window)
}

method DayView dateeditor {} {
    return $slot(dateeditor)
}

method DayView cleanup {} {
    global dayview_count
    incr dayview_count -1
    if {$dayview_count <= 0} {
	destroy .
    } else {
	focus_disinterest $slot(window)
	focus_unrestrict $slot(window)

	global dayview_state
	unset dayview_state($self,remind)

	trigger remove change [list $self change]
	class_kill $slot(apptlist)
	class_kill $slot(notelist)
	class_kill $slot(dateeditor)
	destroy $slot(window)
    }
}

method DayView set_date {date} {
    set slot(date) $date
    $slot(dateeditor) set_date $date
    $slot(apptlist) set_date $date
    $slot(notelist) set_date $date
}

method DayView focus {item itemwindow} {
    set slot(focus) $item
    set slot(focuswindow) $itemwindow

    global dayview_state
    set dayview_state($self,remind) [$item earlywarning]

    global dayview_item_entries
    foreach entry $dayview_item_entries {
	$slot(window).menu.[lindex $entry 0].m enable [lindex $entry 1]
    }

    $self update_status
}

method DayView unfocus {} {
    set slot(focus) ""
    set slot(focuswindow) ""

    global dayview_state
    set dayview_state($self,remind) -1

    global dayview_item_entries
    foreach entry $dayview_item_entries {
	$slot(window).menu.[lindex $entry 0].m disable [lindex $entry 1]
    }

    $self update_status
}

method DayView change {item} {
    if {$slot(focus) == $item} {
	global dayview_state
	set dayview_state($self,remind) [$item earlywarning]

	$self update_status
    }
}

method DayView update_status {} {
    set item $slot(focus)
    if {$item == ""} {
	$slot(window).cal configure -text ""
	$slot(window).rep configure -text ""
    } else {
	$slot(window).cal configure\
	    -text "Calendar: [file tail [$item calendar]]"
	$slot(window).rep configure -text [$item type]
    }
}

method DayView key {str} {
    if {$str == "\003"} {
	# Control-c
	$self copy
	return
    }

    if {$str == "\026"} {
	# Control-v
	$self paste
	return
    }

    if {$str == "\030"} {
	# Control-x
	$self cut
	return
    }

    # Pass event down to focus window if any
    if {$slot(focuswindow) != ""} {
	$slot(focuswindow) key $str
    }
}

method DayView save {} {
    trigger fire save
    foreach file [calendar_filenames] {
	if ![cal dirty $file] {continue}

	if [cal stale $file] {
	    set query\
		[yes_no_cancel $slot(window)\
		 "$file has been modified since last read. Write anyway?"]
	    if {$query == "cancel"} {
		return 0
	    }
	    
	    if {$query == "no"} {
		continue
	    }

	    # If user said "yes", fall through and save.
	}
	
	if [catch {cal save $file} error] {
	    error_notify $slot(window) "$file\n\n$error"
	    return 0
	}
    }
    return 1
}

method DayView reread {} {
    trigger fire save

    set result 1
    foreach file [calendar_filenames] {
	if ![cal stale $file] {continue}

	if [cal dirty $file] {
	    set query\
		[yes_no_cancel $slot(window)\
		 "$file has been modified locally. Discard changes?"]
	    if {$query == "cancel"} {
		set result 0
		break
	    }
	    
	    if {$query == "no"} {
		continue
	    }
	}

	trigger fire exclude $file
	if [catch {cal reread $file} error] {
	    trigger fire include $file
	    error_notify $slot(window) "$file\n\n$error"
	    set result 0
	    break
	}
	trigger fire include $file
    }

    return $result
}

method DayView exit {} {
    if ![$self save] return
    destroy .
}

method DayView newview {} {
    DayView
}

method DayView close {} {
    global dayview_count
    if {$dayview_count <= 1} {
	# Last view about to be closed - try and save.
	if ![$self save] return
    }
    class_kill $self
}

method DayView cut {} {
    set item [$self mod_single_selection]
    if {$item != ""} {
	global dayview_clipboard
	if {$dayview_clipboard != ""} {$dayview_clipboard delete}
	set dayview_clipboard [$item clone]

	trigger fire delete $item
	cal remove $item
    }
}

method DayView copy {} {
    set item [$self selection]
    if {$item != ""} {
	global dayview_clipboard
	if {$dayview_clipboard != ""} {$dayview_clipboard delete}
	set dayview_clipboard [$item clone]
    }
}

method DayView paste {} {
    if [cal readonly] {
	error_notify $slot(window) "Permission denied"
	return
    }

    global dayview_clipboard
    if {$dayview_clipboard == ""} {
	error_notify $slot(window) "No item in clipboard"
	return
    }

    set item [$dayview_clipboard clone]
    $item date $slot(date)
    cal add $item
    trigger fire add $item
}

method DayView makeunique {} {
    set item [$self mod_selection]
    if {$item != ""} {
	if ![$item repeats] {
	    error_notify $slot(window) "Item does not repeat."
	    return
	}

	set clone [$item clone]
	$clone date $slot(date)
	$item deleteon $slot(date)

	cal add $clone [$item calendar]
	cal changed $item

	trigger fire change $item
	trigger fire add $clone
    }
}

method DayView moveitem {calendar} {
    if [cal readonly $calendar] {
	error_notify $slot(window) "$calendar: permission denied"
	return
    }

    set item [$self mod_single_selection]
    if {$item != ""} {
	set clone [$item clone]
	cal add $clone $calendar
	trigger fire add $clone

	trigger fire delete $item
	cal remove $item
    }
}

method DayView addinclude {} {
    if [cal readonly] {
	error_notify $slot(window) "[cal main]: permission denied"
	return
    }

    if [get_file_name $slot(window) "Include Calendar"\
	"Select calendar file to include." filename] {

	# Some sanity checking
	if [file exists $filename] {
	    if ![file isfile $filename] {
		error_notify $slot(window) "$filename: not a regular file"
		return
	    }

	    if ![file readable $filename] {
		error_notify $slot(window) "$filename: permission denied"
		return
	    }
	}

	if [catch {cal include $filename} error] {
	    error_notify $slot(window) $error
	    return
	}
	trigger fire include $filename
    }
}

method DayView removeinc {calendar} {
    if [cal readonly] {
	error_notify $slot(window) "[cal main]: permission denied"
	return
    }

    if [cal dirty $calendar] {
	set save 1
	if [cal stale $calendar] {
	    # Conflict!
	    set query [yes_no_cancel $slot(window)\
		       "$calendar has been modified since last read. Save?"]
	    if ($query == "cancel") {
		return
	    }
	    if ($query == "no") {
		set save 0
	    }
	}

	if $save {
	    if [catch {cal save $calendar} error] {
		error_notify $slot(window) "$file\n\n$error"
		return
	    }
	}
    }

    # Remove it
    trigger fire exclude $calendar
    cal exclude $calendar
}

method DayView norepeat {} {
    set item [$self mod_selection]
    if {$item != ""} {
	$item date $slot(date)
	cal changed $item
	trigger fire change $item
    }
}

method DayView days {n} {
    set item [$self mod_selection]
    if {$item != ""} {
	$item dayrepeat $n $slot(date)
	cal changed $item
	trigger fire change $item
    }
}

method DayView months {n} {
    set item [$self mod_selection]
    if {$item != ""} {
	$item monthrepeat $n $slot(date)
	cal changed $item
	trigger fire change $item
    }
}

method DayView weekdays {} {
    set item [$self mod_selection]
    if {$item != ""} {
	if ![weekday_set $slot(window) result [date weekday $slot(date)]] {
	    return
	}

	if {[llength $result] < 1} {
	    error_notify $slot(window) "No weekday selected."
	    return
	}

	eval $item weekdays $result
	cal changed $item
	trigger fire change $item
    }
}

method DayView firstdate {} {
    set item [$self mod_selection]
    if {$item != ""} {
	$item start $slot(date)
	cal changed $item
	trigger fire change $item
    }
}

method DayView lastdate {} {
    set item [$self mod_selection]
    if {$item != ""} {
	$item finish $slot(date)
	cal changed $item
	trigger fire change $item
    }
}

method DayView remind {n} {
    set item [$self mod_selection]
    if {$item != ""} {
	$item earlywarning $n
	cal changed $item
	trigger fire change $item
    }
}

method DayView print {n} {
    case $n in {
	"month" {
	    set str [psmonth $slot(date)]
	}

	"1" {
	    set str [psdays $slot(date) 1 1 0]
	}

	"2" {
	    set str [psdays $slot(date) 2 2 1]
	}

	"4" {
	    set str [psdays $slot(date) 4 2 0]
	}

	"8" {
	    set str [psdays $slot(date) 8 4 1]
	}

	"10" {
	    set str [psdays $slot(date) 10 5 1]
	}

	default {
	    error_notify $slot(window) "Cannot print $n"
	    return
	}
    }

    if [catch {$self do_print $str} result] {
	error_notify $slot(window) $result
    }
}

method DayView do_print {str} {
    # Get print command
    global dayview_print
    if {![get_string $slot(window) "Print" "Enter print command"\
	  $dayview_print dayview_print]} {
	return
    }

    global libdir
    set header [open $libdir/header.ps r]
    if [catch {set head [read $header]} result] {
	catch {close $header}
	error $result
    }
    catch {close $header}

    set printer [open "|$dayview_print" w]
    if [catch {puts -nonewline $printer $head} result] {
	catch {close $printer}
	error $result
    }
    if [catch {puts -nonewline $printer $str} result] {
	catch {close $printer}
	error $result
    }
    close $printer
}

method DayView viewitems {calendar} {
    set l [ItemListing]
    $l calendar $calendar
}

method DayView list {n} {
    set start $slot(date)
    if {$n == "week"} {
	set start [expr $slot(date)+1-[date weekday $slot(date)]]
	set n 7
    }
    if {$n == "month"} {
	set start [expr $slot(date)+1-[date monthday $slot(date)]]
	set n [date monthsize $start]
    }

    set l [ItemListing]
    $l dayrange $start [expr $start+$n-1]
}

method DayView build_menu {} {
    set m $slot(window).menu

    # Menu bar
    frame $m -relief raised -bd 1

    set menu_list ""
    set sep [winfo pixels . 0.2i]
    set pad 0

    global dayview_menus
    foreach menu $dayview_menus {
	set title [lindex $menu 0]
	set name [lindex $menu 1]
	set menu [lrange $menu 2 end]

	# Create menu
	lappend menu_list $m.$name
	menubutton $m.$name -bd 1 -text $title -menu $m.$name.m
	menu $m.$name.m -bd 1
	pack append $m $m.$name [list left padx $pad]
	set pad $sep

	# Fill menu
	foreach entry $menu {
	    if {[lindex $entry 0] == "-"} {
		$m.$name.m add separator
		continue
	    }

	    if {[lindex $entry 0] == ">"} {
		set x $m.$name.m.[lindex $entry 2]
		$m.$name.m add cascade\
		    -label [lindex $entry 1]\
		    -accelerator "=>"\
		    -menu $x
		menu $x -postcommand [concat $self [lindex $entry 3] $x]
		continue
	    }

	    if {[lindex $entry 0] == "="} {
		$m.$name.m add radiobutton\
		    -label [lindex $entry 1]\
		    -variable dayview_state($self,[lindex $entry 2])\
		    -value [lindex $entry 3]\
		    -command [concat $self [lindex $entry 4]]
		continue
	    }

	    $m.$name.m add command\
		-label [lindex $entry 0]\
		-accelerator [lindex $entry 1]\
		-command [concat $self [lindex $entry 2]]
	}
    }

    eval tk_menuBar $m $menu_list

    global dayview_item_entries
    foreach entry $dayview_item_entries {
	$m.[lindex $entry 0].m disable [lindex $entry 1]
    }
}

# Description of Menu entries
#
#	Separator
#	{ - }
#
#	Command
#	{ <label> <accelerator> <command> }
#
#	Cascade
#	{ > <label> <pulldown-menu-id> <command> }
#
#	Radiobutton
#	{ = <label> <variable-id> <value> <command> }

set dayview_menus {
    { Calendar cal
	{ Save			{}	{save}		}
	{ Re-Read		{}	{reread}	}
	{ -						}
	{ {Include Calendar}	{}	{addinclude}	}
	{ > {Remove Include} {remove} {fillinc other removeinc} }
	{ -						}
	{ {New Window}		{}	{newview}	}
	{ {Close Window}	{}	{close}		}
	{ -						}
	{ Exit			{}	{exit}		}
    }

    { Edit edit
	{ {Cut}			{^x}	{cut}		}
	{ {Copy}		{^c}	{copy}		}
	{ {Paste}		{^v}	{paste}		}
	{ -						}
	{ {Make Unique}		{}	{makeunique}	}
	{ > {Move Item To}   {move}   {fillinc all moveitem} }
    }

    { Repeat rep
	{ {Don't Repeat}	{}	{norepeat}	}
	{ {Daily}		{}	{days 1}	}
	{ {Weekly}		{}	{days 7}	}
	{ {Monthly}		{}	{months 1}	}
	{ {Annual}		{}	{months 12}	}
	{ -						}
	{ {Days of the Week}	{}	{weekdays}	}
	{ -						}
	{ {Every Two Weeks}	{}	{days 14}	}
	{ {Every Three Weeks}	{}	{days 21}	}
	{ {Every Four Weeks}	{}	{days 28}	}
	{ -						}
	{ {Every Two Months}	{}	{months 2}	}
	{ {Every Three Months}	{}	{months 3}	}
	{ {Every Four Months}	{}	{months 4}	}
	{ {Every Six Months}	{}	{months 6}	}
	{ -						}
	{ {Make First Date}	{}	{firstdate}	}
	{ {Make Last Date}	{}	{lastdate}	}
    }

    { Remind rem
	{ = {On Occurrence}		{remind}	{0}	{remind 0}  }
	{ = {A Day Early}		{remind}	{1}	{remind 1}  }
	{ = {Two Days Early}		{remind}	{2}	{remind 2}  }
	{ = {Five Days Early}		{remind}	{5}	{remind 5}  }
	{ = {Ten Days Early}		{remind}	{10}	{remind 10} }
	{ = {Fifteen Days Early}	{remind}	{15}	{remind 15} }
    }

    { List list
	{ {One Day}		{}	{list 1}	}
	{ {Seven Days}		{}	{list 7}	}
	{ {Ten Days}		{}	{list 10}	}
	{ {Thirty Days}		{}	{list 30}	}
	{ {Week}		{}	{list week}	}
	{ {Month}		{}	{list month}	}
	{ -						}
	{ > {From Calendar} {items}  {fillinc all viewitems} }
    }

    { Print print
	{ {One Day}		{}	{print 1}	}
	{ {Two Days}		{}	{print 2}	}
	{ {Four Days}		{}	{print 4}	}
	{ {Eight Days}		{}	{print 8}	}
	{ {Ten Days}		{}	{print 10}	}
	{ {Month}		{}	{print month}	}
    }
}

# List of <menu,entry> pairs for menu entries that should only be
# enabled when an item is selected.
set dayview_item_entries {
    { edit	{Cut}			}
    { edit	{Copy}			}
    { edit	{Make Unique}		}
    { edit	{Move Item To}		}
    { rep	{Don't Repeat}		}
    { rep	{Daily}			}
    { rep	{Weekly}		}
    { rep	{Monthly}		}
    { rep	{Annual}		}
    { rep	{Days of the Week}	}
    { rep	{Every Two Weeks}	}
    { rep	{Every Three Weeks}	}
    { rep	{Every Four Weeks}	}
    { rep	{Every Two Months}	}
    { rep	{Every Three Months}	}
    { rep	{Every Four Months}	}
    { rep	{Every Six Months}	}
    { rep	{Make First Date}	}
    { rep	{Make Last Date}	}
    { rem	{On Occurrence}		}
    { rem	{A Day Early}		}
    { rem	{Two Days Early}	}
    { rem	{Five Days Early}	}
    { rem	{Ten Days Early}	}
    { rem	{Fifteen Days Early}	}
}

# effects - Fill $menu with list of include file names.  If $type == all,
#	    also put main calendar into $menu.  Set-up the $menu entries
#	    so that they all execute "$self $cmd <include-file-name>".
method DayView fillinc {type cmd menu} {
    $menu delete 0 last
    if {$type == "all"} {
	$menu add command -label "Main Calendar"\
	    -command [concat $self $cmd [cal main]]
	$menu add separator
    }

    cal forincludes file {
	$menu add command -label [file tail $file]\
	    -command [concat $self $cmd $file]
    }
}

#############################################################################
# Helper methods

# effects - Check that item is currently selected and returns it.
#	    Warns user and returns "" if not selected.
method DayView selection {} {
    if {$slot(focus) == ""} {
	error_notify $slot(window) "No item selected."
	return ""
    }

    # Save pending edits
    trigger fire save

    return $slot(focus)
}

# effects - Check that item is currently selected and can be modified.
#	    Warns user and returns "" if not selected or immutable.
method DayView mod_selection {} {
    set focus [$self selection]
    if {$focus == ""} {return ""}

    if [cal readonly [$focus calendar]] {
	error_notify $slot(window) "[$focus calendar]: permission denied"
	return ""
    }
    return $focus
}

# effects - Check that item is currently selected, can be modified,
#	    and also performs a repeat_check.
#	    Warns user and returns "" if not selected or immutable.
#	    Returns "" if repeat_check fails.
method DayView mod_single_selection {} {
    set focus [$self mod_selection]
    if {$focus == ""} {return ""}

    # Repeat-modify-check
    if {[repeat_check $slot(window) $focus $slot(date)] == "cancel"} {
	return ""
    }
    return $focus
}
