# Copyright (c) 1994 by Sanjay Ghemawat
#############################################################################
#
# Various ical actions invoked by menus and key bindings.
#
# The actions all take place in the context of a "view".  A "view"
# object should provide the following operations:
#
#	<view> window
#	<view> date
#	<view> set_date <date>
#	<view> unselect <item>
#	<view> select <item>
#	<view> itemwindow <item>

#### Helper routines ####

proc ical_with_view {view body} {
    global ical_state

    set old $ical_state(view)
    set ical_state(view) $view

    # XXX Error handling?
    uplevel $body

    set ical_state(view) $old
}

proc ical_action {view action args} {
    global ical_state

    set old $ical_state(view)
    set ical_state(view) $view
    eval [list ical_$action] $args
    set ical_state(view)  $old
}

proc ical_view {} {
    global ical_state
    return $ical_state(view)
}

proc ical_leader {} {
    global ical_state
    if ![string compare $ical_state(view) ""] {return ""}
    return [[ical_view] window]
}

proc ical_error {err} {
    error_notify [ical_leader] $err
}

proc ical_date {} {
    return [[ical_view] date]
}

proc ical_set_date {d} {
    [ical_view] set_date $d
}

proc ical_with_item {v} {
    global ical_state
    if [string compare $ical_state(selview) $ical_state(view)] {return 0}
    if ![string compare $ical_state(selitem) {}] {return 0}

    upvar $v i
    set i $ical_state(selitem)
    return 1
}

proc ical_with_mod_item {v} {
    if ![ical_with_item x] {return 0}

    if [cal readonly [$x calendar]] {
	ical_error "[$x calendar]: permission denied"
	return 0
    }

    upvar $v i
    set i $x
    return 1
}

proc ical_with_mod_single_item {v} {
    if ![ical_with_mod_item x] {return 0}

    set result [repeat_check [ical_leader] $x [ical_date]]
    if {$result == "cancel"} {return 0}

    upvar $v i
    set i $x
    return 1
}

proc ical_clip {i} {
    global ical_state
    set old $ical_state(clip)
    if [string compare $old ""] {
	$old delete
    }
    set ical_state(clip) $i
}

proc ical_clipped {} {
    global ical_state
    return $ical_state(clip)
}

#### Editing routines ####

# The following routines provide support for editing in item windows.
# The basic support is provided by the Canvas routines in
# "tcllib/edit.tcl".  Here we just introduce wrappers around
# modification operations

# called by generic editing code to delete a range of text.
proc icalitem_edit_delete {c i1 i2} {
    if ![ical_with_mod_single_item item] {return}

    global Canvas_Edit ical_state
    set iw $ical_state(seliw)
    if [string compare $c [$iw canvas]] return
    $Canvas_Edit(delete) $c $i1 $i2
    $iw save
}

# called by generic editing code to insert text.
proc icalitem_edit_insert {c t} {
    if ![ical_with_mod_single_item item] {return}

    global Canvas_Edit ical_state
    set iw $ical_state(seliw)
    if [string compare $c [$iw canvas]] return
    $Canvas_Edit(insert) $c $t
    $iw save
}

#### Action routines ####

proc ical_newview {} {
    global ical_state
    return [DayView]
}

proc ical_save {} {
    io_save [ical_leader]
}

proc ical_reread {} {
    io_read [ical_leader]
}

proc ical_exit {} {
    if ![io_save [ical_leader]] return

    run-hook ical-exit
    cal delete
    destroy .
}

proc ical_close {} {
    global ical_state
    if {[llength $ical_state(views)] < 2} {
	# Try and save --- last view is about to be closed
	if ![io_save [ical_leader]] return

	class_kill [ical_view]
	set ical_state(view) ""
	run-hook ical-exit
	cal delete
	destroy .
    } else {
	# Not the last view
	class_kill [ical_view]
	set ical_state(view) ""
    }
}

proc ical_cut_or_hide {} {
    if ![ical_with_item i] return

    if {[$i owned] || ([$i calendar] == [cal main])} {
	ical_cut
    } else {
	ical_hide
    }
}

proc ical_cut {} {
    if ![ical_with_mod_single_item i] return

    ical_clip $i
    cal remove $i
}

proc ical_hide {} {
    if ![ical_with_item i] return

    # Since the hide entry will go in the main calendar,
    # check for permission there.
    if [cal readonly] {
	ical_error "Permission denied"
	return
    }

    # Try to avoid checking permission in the item calendar
    # unless we have to split a repeating item.

    if [$i repeats] {
	if [cal readonly [$i calendar]] {
	    # Tell user that the item cannot be split.
	    # See if all entries should be hidden.
	    if {![yes_or_no [ical_leader] [join {
		{This item repeats and you are not allowed to split it.}
		{Do you want to hide all occurrences of this item from}
		{your view?}
	    }]]} {
		return
	    }
	} else {
	    if ![ical_with_mod_single_item i] return
	}
    }

    ical_clip [$i clone]
    cal hide $i
}

proc ical_copy {} {
    if ![ical_with_item i] return
    ical_clip [$i clone]
}

proc ical_paste {} {
    if [cal readonly] {
	ical_error "Permission denied"
	return
    }

    set i [ical_clipped]
    if {$i == ""} {
	ical_error "No item in clipboard"
	return
    }

    set i [$i clone]
    $i date [ical_date]
    $i own
    $i todo 0

    cal add $i
}

proc ical_import {} {
    if [catch {set sel [selection get]}] {return}

    if ![cal readonly] {
	set i [item_parse $sel [ical_date]]
	run-hook item-create $i
	cal add $i
	ical_set_date [$i first]
    }
}

proc ical_insert_selection {} {
    if ![ical_with_mod_single_item i] return

    if [catch {ical_edit self-insert [selection get]}] {
	ical_edit yank
    }
}

proc ical_delete_selection {} {
    if ![ical_with_mod_single_item i] return
    ical_edit delete-selection
}

proc ical_makeunique {} {
    if ![ical_with_mod_item i] return
    if ![$i repeats] {
	ical_error "Item does not repeat"
	return
    }

    set c [$i clone]
    $c date [ical_date]
    $i deleteon [ical_date]
    cal add $c [$i calendar]
}

proc ical_moveitem {calendar} {
    if [cal readonly $calendar] {
	ical_error "$calendar: permission denied"
	return
    }

    if ![ical_with_mod_single_item i] return
    cal add $i $calendar
}

proc ical_addinclude {} {
    if [cal readonly] {
	ical_error "[cal main]: permission denied"
	return
    }

    if ![get_file_name [ical_leader] "Include Calendar"\
	     "Select calendar file to include." filename] return

    # Some sanity checking
    if [catch {set e [file exists $filename]} msg] {
	ical_error "$filename: $msg"
	return
    }

    if $e {
	if ![file isfile $filename] {
	    ical_error "$filename: not a regular file"
	    return
	}

	if ![file readable $filename] {
	    ical_error "$filename: permission denied"
	    return
	}
    }

    if [catch {cal include $filename} error] {
	ical_error $error
    }
}

proc ical_removeinc {calendar} {
    if [cal readonly] {
	ical_error "[cal main]: permission denied"
	return
    }

    if [catch {set dirty [cal dirty $calendar]}] {
	# Unknown calendar - probably because a tear-off menu
	# allowed multiple invocations of removeinc.
	return
    }

    if $dirty {
	set save 1
	if [cal stale $calendar] {
	    # Conflict!
	    set query [yes_no_cancel [ical_leader]\
		       "$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] {
		ical_error "$file\n\n$error"
		return
	    }
	}
    }

    # Remove it
    cal exclude $calendar
}

proc ical_norepeat {} {
    if ![ical_with_mod_item i] return
    $i date [ical_date]
}

proc ical_daily {} {
    if ![ical_with_mod_item i] return
    $i dayrepeat 1 [ical_date]
}

proc ical_monthly {} {
    if ![ical_with_mod_item i] return
    $i month_day [date monthday [ical_date]]
}

proc ical_annual {} {
    if ![ical_with_mod_item i] return
    $i month_day [date monthday [ical_date]] [ical_date] 12
}

proc ical_weekly {} {
    if ![ical_with_mod_item i] return
    $i weekdays [date weekday [ical_date]]
}

proc ical_edit_monthly {} {
    if ![ical_with_mod_item i] return
    monthrepeat [ical_leader] $i [ical_date]
}

proc ical_edit_weekly {} {
    if ![ical_with_mod_item i] return
    weekrepeat [ical_leader] $i [ical_date]
}

proc ical_set_range {} {
    if ![ical_with_mod_item i] return

    if ![$i repeats] {
	ical_error "Item does not repeat"
	return
    }

    set start [ical_date]
    set finish [ical_date]
    if ![get_daterange [ical_leader] start finish] return

    $i start $start
    $i finish $finish
}

proc ical_alarms {} {
    if ![ical_with_mod_item i] return

    if [catch {set alarms [$i alarms]}] {set alarms [cal option DefaultAlarms]}
    if ![alarm_set [ical_leader] {Item alarms (in minutes)} alarms $alarms] {
	return
    }

    # Make sure item still exists
    catch {$i alarms $alarms}
}

proc ical_remind {n} {
    if ![ical_with_mod_item i] return
    $i earlywarning $n
}

proc ical_set_owner {} {
    if ![ical_with_mod_single_item i] return

    if ![get_string [ical_leader] "Owner" "Enter owner name"\
	     [$i owner] result] return
    $i owner $result
}

proc ical_hilite {mode} {
    if ![ical_with_mod_single_item i] return
    $i hilite $mode
}

proc ical_toggle_todo {} {
    if ![ical_with_mod_single_item i] return
    $i todo [expr ![$i todo]]
}

proc ical_print {} {
    if [catch {print_calendar [ical_leader] [ical_date]} msg] {
	ical_error $msg
    }
}

proc ical_viewitems {calendar} {
    set l [ItemListing]
    $l calendar $calendar
}

proc ical_list {n} {
    set start [ical_date]
    if {$n == "week"} {
	set start [expr $start+1-[date weekday $start]]
	if [cal option MondayFirst] {
	    incr start
	    if {$start > [ical_date]} {
		set start [expr $start - 7]
	    }
	}
	set n 7
    }
    if {$n == "month"} {
	set start [expr $start+1-[date monthday $start]]
	set n [date monthsize $start]
    }
    if {$n == "year"} {
	set start [date make 1 1 [date year $start]]
	set n [expr [date make 1 1 [expr [date year $start]+1]] - $start]
    }

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

proc ical_toggle_overflow {} {
    if [cal readonly] {return}
    cal option AllowOverflow [expr ![cal option AllowOverflow]]
}

proc ical_toggle_ampm {} {
    if [cal readonly] {return}

    cal option AmPm [expr ![cal option AmPm]]
    trigger fire reconfig
}

proc ical_toggle_monday {} {
    if [cal readonly] {return}

    cal option MondayFirst [expr ![cal option MondayFirst]]
    trigger fire reconfig
}

proc ical_timerange {} {
    if [cal readonly] {return}

    set start [cal option DayviewTimeStart]
    set finish [cal option DayviewTimeFinish]

    set msg [join {
	{Use the two sliders to change the range of time displayed by}
	{default in a Calendar window.}
    } "\n"]

    if [get_time_range [ical_leader] $msg start finish] {
	cal option DayviewTimeStart $start
	cal option DayviewTimeFinish $finish
	trigger fire reconfig
    }
}

proc ical_noticeheight {} {
    if [cal readonly] {return}

    set ht [cal option NoticeHeight]
    if [get_number [ical_leader] {Notice Window Height} {In centimeters}\
	    1 15 0 $ht ht] {
	cal option NoticeHeight $ht
	trigger fire reconfig
    }
}

proc ical_itemwidth {} {
    if [cal readonly] {return}

    set w [cal option ItemWidth]
    if [get_number [ical_leader] {Item Width} {In centimeters}\
	    5 15 0 $w w] {
	cal option ItemWidth $w
	trigger fire reconfig
    }
}

proc ical_defremind {n} {
    if [cal readonly] {return}
    cal option DefaultEarlyWarning $n
}

proc ical_defalarms {} {
    if [cal readonly] {return}

    set alarms [cal option DefaultAlarms]
    if {![alarm_set [ical_leader]\
	      {Default Alarms (in minutes)} alarms $alarms]} {
	return
    }

    cal option DefaultAlarms $alarms
    alarmer recompute
}

proc ical_gripe {} {
    global ical
    bug_notify $ical(mailer) $ical(author) {Gripe}
}

proc ical_edit {args} {
    global ical_state
    set fw $ical_state(seliw)
    if ![string compare $fw ""] {return}

    eval edit_action IcalItem_Edit [$fw canvas] $args
}

proc ical_help {} {
    global ical
    Ical_Doc $ical(doc)
}

#### Date change routines ####

proc ical_last_month {} {
    set split [date split [ical_date]]
    set month [lindex $split 2]
    set year [lindex $split 3]
    if {$month == 1} {
	set month 12
	incr year -1
    } else {
	incr month -1
    }

    # Handle range errors
    if [catch {set first [date make 1 $month $year]}] {return}

    # Adjust monthday to month size
    set day [lindex $split 0]
    if {$day > [date monthsize $first]} {set day [date monthsize $first]}

    ical_set_date [date make $day $month $year]
}

proc ical_last_month {} {
    set split [date split [ical_date]]
    set month [lindex $split 2]
    set year [lindex $split 3]
    if {$month == 1} {
	set month 12
	incr year -1
    } else {
	incr month -1
    }

    # Handle range errors
    if [catch {set first [date make 1 $month $year]}] {return}

    # Adjust monthday to month size
    set day [lindex $split 0]
    if {$day > [date monthsize $first]} {set day [date monthsize $first]}

    ical_set_date [date make $day $month $year]
}

proc ical_next_month {} {
    set split [date split [ical_date]]
    set month [lindex $split 2]
    set year [lindex $split 3]
    if {$month == 12} {
	set month 1
	incr year
    } else {
	incr month
    }

    # Handle range errors
    if [catch {set first [date make 1 $month $year]}] {return}

    # Adjust monthday to month size
    set day [lindex $split 0]
    if {$day > [date monthsize $first]} {set day [date monthsize $first]}

    ical_set_date [date make $day $month $year]
}

proc ical_last_year {} {
    set split [date split [ical_date]]
    set month [lindex $split 2]
    set year [lindex $split 3]
    incr year -1

    # Handle range errors
    if [catch {set first [date make 1 $month $year]}] {return}

    # Adjust monthday to month size
    set day [lindex $split 0]
    if {$day > [date monthsize $first]} {set day [date monthsize $first]}

    ical_set_date [date make $day $month $year]
}

proc ical_next_year {} {
    set split [date split [ical_date]]
    set month [lindex $split 2]
    set year [lindex $split 3]
    incr year

    # Handle range errors
    if [catch {set first [date make 1 $month $year]}] {return}

    # Adjust monthday to month size
    set day [lindex $split 0]
    if {$day > [date monthsize $first]} {set day [date monthsize $first]}

    ical_set_date [date make $day $month $year]
}

proc ical_today {} {
    ical_set_date [date today]
}

proc ical_last_day {} {
    set date [expr [ical_date]-1]
    if {$date < [date first]} {return}
    ical_set_date $date
}

proc ical_next_day {} {
    set date [expr [ical_date]+1]
    if {$date > [date last]} {return}
    ical_set_date $date
}

#### Item selection routines ####

proc ical_select {item} {
    ical_unselect

    global ical_state
    set view [ical_view]
    set ical_state(selview) $view
    set ical_state(selitem) $item
    set ical_state(seliw)   [$view itemwindow $item]
    $view select $item
}

proc ical_unselect {} {
    global ical_state
    if [string compare $ical_state(selitem) {}] {
	$ical_state(selview) unselect $ical_state(selitem)
    }
    set ical_state(selview) {}
    set ical_state(selitem) {}
    set ical_state(seliw)   {}
}
