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

class DayView {} {
    global ical_state
    lappend ical_state(views) $self

    set n .$self
    set slot(window) $n
    set slot(focus) {}

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

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

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

    frame $n.status -relief raised -bd 1
    label $n.cal -relief flat -text ""
    label $n.rep -relief flat -text ""
    frame $n.menu -relief raised -bd 1

    global dv_state
    set dv_state(state:$self:remind)	-1
    set dv_state(state:$self:defremind) -1
    set dv_state(state:$self:hilite)	""
    set dv_state(state:$self:todo)	 0

    $self build_menu

    $n.menu.options.m configure -postcommand [list $self config_options]

    $self config_edit
    $self config_item
    $self config_repeat

    # Pack windows
    pack $n.cal		-in $n.status -side left
    pack $n.rep		-in $n.status -side right
    pack $n.menu	-side top -fill x
    pack $n.status	-side bottom -fill x
    pack $n.al		-side right -expand 1 -fill both
    pack $n.nl		-side bottom -expand 1 -fill both
    pack $n.de		-side top -fill x

    $self reconfig

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

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

    $self set_date [date today]

    # Set-up triggers.
    #
    # Save is not useful because dayview never keeps unsaved changes.
    # Add/delete/flush 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.
    #
    # Therefore, we just need to listen for "change" so that we can
    # update the status window, and to "reconfig" so that we
    # can obey option changes.  We also need to listen for "midnight"
    # to automatically switch to next day.

    trigger on change	[list $self change]
    trigger on reconfig	[list $self reconfig]
    trigger on midnight [list $self midnight]

    # User customization
    ical_with_view $self {run-hook dayview-startup $self}

    # Fixup the menu bar after user customizations
    eval tk_menuBar $n.menu [winfo children $n.menu]

    # Set-up key bindings
    $self key_bindings
}

method DayView destructor {} {
    ical_with_view $self {run-hook dayview-close [ical_view]}

    # Remove from list of registered views
    global ical_state
    lremove ical_state(views) $self

    focus_disinterest $slot(window)
    focus_unrestrict $slot(window)

    global dv_state
    unset dv_state(state:$self:remind)
    unset dv_state(state:$self:defremind)
    unset dv_state(state:$self:hilite)
    unset dv_state(state:$self:todo)

    trigger remove change   [list $self change]
    trigger remove reconfig [list $self reconfig]
    trigger remove midnight [list $self midnight]

    class_kill $slot(apptlist)
    class_kill $slot(notelist)
    class_kill $slot(dateeditor)
    destroy $slot(window)
}

##############################################################################
# Routines needed by action procs.

# Return the toplevel window for the view
method DayView window {} {
    return $slot(window)
}

# Return the displayed date
method DayView date {} {
    return $slot(date)
}

# Set the displayed date
method DayView set_date {date} {
    set slot(date) $date
    $slot(dateeditor) set_date $date
    $slot(apptlist) set_date $date
    $slot(notelist) set_date $date

    ical_with_view $self {run-hook dayview-set-date $self $date}
}

method DayView select {item} {
    set slot(focus) $item
    if [$item is appt] {
	$slot(apptlist) select $item
    } else {
	$slot(notelist) select $item
    }

    # Update variables for menu display
    global dv_state
    set dv_state(state:$self:remind) [$item earlywarning]
    set dv_state(state:$self:hilite) [$item hilite]
    set dv_state(state:$self:todo)   [$item todo]

    $self config_edit
    $self config_item
    $self config_repeat
    $self config_status

    ical_with_view $self {run-hook dayview-focus $self $item}
}

method DayView unselect {item} {
    # Do not look in item because it may be dead by now
    set slot(focus) {}
    $slot(apptlist) unselect $item
    $slot(notelist) unselect $item

    # Update variables for menu display
    global dv_state
    set dv_state(state:$self:remind) -1
    set dv_state(state:$self:hilite) ""
    set dv_state(state:$self:todo)    0

    $self config_edit
    $self config_item
    $self config_repeat
    $self config_status

    ical_with_view $self {run-hook dayview-unfocus $self}
}

# Return ItemWindow for specified item
# Return "" if no such window exists
method DayView itemwindow {item} {
    if [$item is appt] {
	return [$slot(apptlist) itemwindow $item]
    } else {
	return [$slot(notelist) itemwindow $item]
    }
}

method DayView appt_list {} {return $slot(apptlist)}
method DayView note_list {} {return $slot(notelist)}

##############################################################################
# Trigger callbacks

# Called at midnight by a trigger.  Advance to today if appropriate.
method DayView midnight {} {
    # Only advance if at previous date
    set today [date today]
    if {$slot(date) == ($today-1)} {$self set_date $today}
}

method DayView change {item} {
    if {$slot(focus) == $item} {
	global dv_state
	set dv_state(state:$self:remind) [$item earlywarning]
	set dv_state(state:$self:hilite) [$item hilite]
	set dv_state(state:$self:todo)   [$item todo]

	# "Repeat" menu does not have to be updated?
	$self config_edit
	$self config_item
	$self config_status
    }
}

method DayView reconfig {} {
    set name .$self

    # Geometry management
    set width [winfo pixels $name "[cal option ItemWidth]c"]

    set start [cal option DayviewTimeStart]
    set finish [cal option DayviewTimeFinish]
    wm grid $name\
	1\
	[expr ($finish - $start) * 2]\
	$width\
	[$slot(apptlist) line_height]
    wm minsize $name 1 10
    wm maxsize $name 1 48
}

##############################################################################
# Internal helper procs

method DayView config_status {} {
    set item $slot(focus)
    if {$item == ""} {
	$slot(window).cal configure -text ""
	$slot(window).rep configure -text ""
    } else {
	set disp [ical_title [$item calendar]]

	if {[$item hilite] == "holiday"} {
	    set disp [format {%s Holiday} $disp]
	}

	set owner [$item owner]
	if {$owner != ""} {
	    set disp [format {%s [Owner %s]} $disp $owner]
	}

	set type ""
	if [string compare [$item type] ""] {
	    set type [$item describe_repeat]
	    if {[string length $type] > 30} {
		set type "[string range $type 0 26]..."
	    }
	}

	$slot(window).cal configure -text $disp
	$slot(window).rep configure -text $type
    }
}

# Install key bindings
method DayView key_bindings {} {
    global keymap

    set keys1 [edit_keymap default]
    set keys2 [edit_keymap emacs]
    set keys3 [edit_keymap ical]

    # XXX - Knows too much about children
    foreach w [list $slot(window) $slot(window).al.c $slot(window).nl.c] {
	keybindings_clear   $w
	keybindings_install $w $keys1 "ical_action $self edit %s"
	keybindings_install $w $keys2 "ical_action $self edit %s"
	keybindings_install $w $keys3 "ical_action $self %s"
    }

    # Update menu accelerator keys
    foreach m [winfo children $slot(window).menu] {
	set last [$m.m index last]
	for {set i 0} {$i <= $last} {incr i} {
	    catch {
		set cmd [lindex [$m.m entryconfig $i -command] 4]
		set act [lindex $cmd 2]
		$m.m entryconfig $i -acc "  [key_find_command $act $keys3]"
	    }
	}
    }
}

# Build the menu
method DayView build_menu {} {
    set b $slot(window).menu
    set i ical_action
    set s $self

    menu-entry	$b File	Save			[list $i $s save]
    menu-entry	$b File	Re-Read			[list $i $s reread]
    menu-entry	$b File	Print			[list $i $s print]
    menu-sep	$b File
    menu-entry	$b File	{Include Calendar}	[list $i $s addinclude]
    menu-pull	$b File	{Remove Include}	[list $self fill_reminc]
    menu-sep	$b File
    menu-entry	$b File	{New Window}		[list $i $s newview]
    menu-entry	$b File	{Close Window}		[list $i $s close]
    menu-sep	$b File
    menu-entry	$b File	Exit			[list $i $s exit]

    menu-entry	$b Edit	Cut			[list $i $s cut_or_hide]
    menu-entry	$b Edit	Copy			[list $i $s copy]
    menu-entry	$b Edit	Paste			[list $i $s paste]
    menu-sep	$b Edit
    menu-entry	$b Edit	{Delete Text}		[list $i $s delete_selection]
    menu-entry	$b Edit	{Insert Text}		[list $i $s insert_selection]
    menu-sep	$b Edit
    menu-entry	$b Edit	{Import Text}		[list $i $s import]

    menu-bool	$b Item	Todo			[list $i $s toggle_todo]\
	dv_state(state:$self:todo)
    menu-sep	$b Item
    $self fill_hilite $b Item
    menu-sep	$b Item
    menu-entry	$b Item	{Change Alarms}		[list $i $s alarms]
    menu-pull	$b Item	{List Item}		[list $self fill_early]
    menu-pull	$b Item	{Move Item To}		[list $self fill_move]

    menu-entry	$b Repeat {Don't Repeat}	[list $i $s norepeat]
    menu-sep	$b Repeat
    menu-entry	$b Repeat {Daily}		[list $i $s daily]
    menu-entry	$b Repeat {Weekly}		[list $i $s weekly]
    menu-entry  $b Repeat {Monthly}		[list $i $s monthly]
    menu-entry  $b Repeat {Annually}		[list $i $s annual]
    menu-sep	$b Repeat
    menu-entry	$b Repeat {Edit Weekly...}	[list $i $s edit_weekly]
    menu-entry  $b Repeat {Edit Monthly...}	[list $i $s edit_monthly]
    menu-sep	$b Repeat
    menu-entry	$b Repeat {Set Range...}	[list $i $s set_range]
    menu-entry	$b Repeat {Make Unique}		[list $i $s makeunique]

    menu-entry	$b List	{One Day}		[list $i $s list 1]
    menu-entry	$b List	{Seven Days}		[list $i $s list 7]
    menu-entry	$b List	{Ten Days}		[list $i $s list 10]
    menu-entry	$b List	{Thirty Days}		[list $i $s list 30]
    menu-sep	$b List
    menu-entry	$b List	{Week}			[list $i $s list week]
    menu-entry	$b List	{Month}			[list $i $s list month]
    menu-entry	$b List	{Year}			[list $i $s list year]
    menu-sep	$b List
    menu-pull	$b List	{From Calendar}		[list $self fill_listinc]

    menu-entry	$b Options {Appointment Range}	  [list $i $s timerange]
    menu-entry	$b Options {Notice Window Height} [list $i $s noticeheight]
    menu-entry	$b Options {Item Width}		  [list $i $s itemwidth]
    menu-sep	$b Options
    menu-bool	$b Options {Allow Text Overflow}  [list $i $s toggle_overflow]\
	dv_state(state:$self:overflow)
    menu-bool	$b Options {Display Am/Pm}	  [list $i $s toggle_ampm]\
	dv_state(state:$self:ampm)
    menu-bool	$b Options {Start Week On Monday} [list $i $s toggle_monday]\
	dv_state(state:$self:mondayfirst)
    menu-sep	$b Options
    menu-entry	$b Options {Default Alarms}	  [list $i $s defalarms]
    menu-pull	$b Options {Default Listings}	  [list $self fill_defearly]

    menu-entry	$b Help {On Ical}		  [list $i $s help]
}

#############################################################################
# Menu configurations

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

    set itemstate	disabled
    set importstate	disabled

    if [string compare $slot(focus) ""] {set itemstate normal}
    if ![cal readonly] {set importstate normal}

    $menu entryconfigure {Cut}			-state $itemstate
    $menu entryconfigure {Copy}			-state $itemstate
    $menu entryconfigure {Delete Text}		-state $itemstate
    $menu entryconfigure {Insert Text}		-state $itemstate
    $menu entryconfigure {Import Text}		-state $importstate
}

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

    set itemstate	disabled
    set apptstate	disabled

    if [string compare $slot(focus) ""] {
	set itemstate normal
	if [$slot(focus) is appt] {set apptstate normal}
    }

    for {set i 0} {$i <= [$menu index last]} {incr i} {
	catch {$menu entryconfigure $i -state $itemstate}
    }

    $menu entryconfigure {Change Alarms}	-state $apptstate
}

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

    set itemstate disabled
    if [string compare $slot(focus) ""] {set itemstate normal}

    for {set i 0} {$i <= [$menu index last]} {incr i} {
	catch {$menu entryconfigure $i -state $itemstate}
    }
}

# effects - Configure the customization menu
method DayView config_options {} {
    set menu $slot(window).menu.options.m

    set state normal
    if [cal readonly] {set state disabled}

    for {set i 0} {$i <= [$menu index last]} {incr i} {
	catch {$menu entryconfigure $i -state $state}
    }

    global dv_state
    set dv_state(state:$self:overflow)	  [cal option AllowOverflow]
    set dv_state(state:$self:ampm)	  [cal option AmPm]
    set dv_state(state:$self:mondayfirst) [cal option MondayFirst]
    set dv_state(state:$self:defremind)	  [cal option DefaultEarlyWarning]
}

#############################################################################
# Commands to fill cascading menus

# effects - Fill menu with calendar names.
#	    Invoke "ical_action $self <action> <calendar file>" when
#	    menu entry is selected.

method DayView fill_includes {menu action} {
    set list {}
    cal forincludes file {
	lappend list $file
    }

    # Add menu separator whenever directory changes.
    set last_dir {}
    foreach f [lsort $list] {
	set d [file dirname $f]
	if [string compare $last_dir $d] {
	    if [string compare $last_dir {}] {$menu add separator}
	    set last_dir $d
	}
	$menu add command -label [ical_title $f]\
	    -command [list ical_action $self $action $f]
    }
}

# effects - Fill remove-include menu
method DayView fill_reminc {menu} {
    $menu delete 0 last
    $self fill_includes $menu removeinc
}

# effects - Fill move-to-include menu
method DayView fill_move {menu} {
    $menu delete 0 last

    $menu add command -label {Main Calendar}\
	-command [list ical_action $self moveitem [cal main]]

    $menu add separator

    $self fill_includes $menu moveitem
}

# effects - Fill list-include menu
method DayView fill_listinc {menu} {
    $menu delete 0 last

    $menu add command -label {Main Calendar}\
	-command [list ical_action $self viewitems [cal main]]

    $menu add separator

    $self fill_includes $menu viewitems
}

# effects Fill early warning menu
method DayView fill_early {menu} {
    $self make_early_menu $menu remind remind

    # No need to re-execute the -postcommand
    $menu configure -postcommand ""
}

# effects Fill default early warning menu
method DayView fill_defearly {menu} {
    $self make_early_menu $menu defremind defremind

    # No need to re-execute the -postcommand
    $menu configure -postcommand ""
}

# effects Fill early warning menu with actual commands
method DayView make_early_menu {menu var method} {
    set entries {
	{ {On Occurrence}	0 }
	{ {A Day Early}		1 }
	{ {Two Days Early}	2 }
	{ {Five Days Early}	5 }
	{ {Ten Days Early}	10 }
	{ {Fifteen Days Early}	15 }
    }

    foreach e $entries {
	$menu add radiobutton\
	    -label [lindex $e 0]\
	    -variable dv_state(state:$self:$var)\
	    -value [lindex $e 1]\
	    -command [list ical_action $self $method [lindex $e 1]]
    }
}

# effects Fill hilite menu entries
method DayView fill_hilite {b m} {
    set entries {
	{ {Always Highlight}	{always}	}
	{ {Never Highlight}	{never}		}
	{ {Highlight Future}	{expire}	}
	{ {Holiday}		{holiday}	}
    }

    foreach e $entries {
	menu-oneof $b $m\
	    [lindex $e 0]\
	    [list ical_action $self hilite [lindex $e 1]]\
	    dv_state(state:$self:hilite)\
	    [lindex $e 1]
    }
}
