##################################################
# Übung zur Vorlesung Benutzerschnittstellen
# Aufgabe 2: Virtueller Taschenrechner
#
# Autor: Stephan Brumme, 702544


# GUI aufräumen
proc Menu_Destroy {} {
    # alte Widgets zerstören
    destroy .input
    destroy .btn
    destroy .plot

    # Menüeintrage Load&Save sperren
    .menuframe.menufile.m entryconfigure 0 -state disabled
    .menuframe.menufile.m entryconfigure 1 -state disabled
}



proc Simple_Create {} {
    Menu_Destroy

    # Eingabefeld anlegen (Wert steht in $formula)
    entry .input -textvariable formula
    pack  .input -side top -fill x

    # Buttons anlegen
    frame .btn
    pack .btn -fill both

    set row 0
    foreach buttonrow {{1 2 3 +} {4 5 6 -} {7 8 9 *} {0 , C =}} {
        # zeilenweise erstellen
        set frm [frame .btn.frm$row]
        pack $frm
        incr row

        # Button erzeugen
        foreach singlebutton $buttonrow {
            button $frm.btn$singlebutton -text $singlebutton \
                    -command "append formula $singlebutton" \
                    -width 4 \
                    -height 1 \
                    -padx 5 \
                    -pady 5
            pack $frm.btn$singlebutton -side left -padx 1 -pady 1
        }
    }

    # Ausnahmen korrigieren
    $frm.btn, configure -command "append formula ."
    $frm.btn= configure -command {set formula [expr $formula]}
    $frm.btnC configure -command {set formula ""}
}


# History laden
proc Load {} {
    set hfile [open [tk_getOpenFile] r]
    .input.history insert end [read $hfile]
    close $hfile
}

# History speichern
proc Save {} {
    set hfile [open [tk_getSaveFile] w]
    puts $hfile [.input.history get 0.0 end]
    close $hfile
}

proc Advanced_Create {} {
    Menu_Destroy

    # Eingabefeld anlegen (Wert steht in $formula)
    frame .input
    pack  .input -side top -fill x

    # History soll schreibgeschützt sein => grau und Events abfangen
    text  .input.history -height 5 -width 25 -background gray
    bind  .input.history <Key> { break }
    entry .input.current -textvariable formula
    # Scrollbar für die History
    scrollbar .input.vscrollbar -command ".input.history yview" -orient vertical
    .input.history configure -yscrollcommand ".input.vscrollbar set"

    # alles darstellen
    pack  .input.current -side bottom -fill x
    pack  .input.vscrollbar -side right -fill y
    pack  .input.history -fill x

    # Menüeintrage Load&Save freischalten
    .menuframe.menufile.m entryconfigure 0 -state normal
    .menuframe.menufile.m entryconfigure 1 -state normal

    # Buttons anlegen
    frame .btn
    pack .btn -fill both

    set row 0
    foreach buttonrow {{sqrt pow exp log} {sin cos tan \(} {asin acos atan \)} \
                {1 2 3 +} {4 5 6 -} {7 8 9 *} {0 , C =}} {
        # zeilenweise erstellen
        set frm [frame .btn.frm$row]
        pack $frm
        incr row

        # Button erzeugen
        foreach singlebutton $buttonrow {
            button $frm.btn$singlebutton -text $singlebutton \
                    -command "append formula {$singlebutton}" \
                    -width 4 \
                    -height 1 \
                    -padx 5 \
                    -pady 5
            pack $frm.btn$singlebutton -side left -padx 1 -pady 1
        }
    }

    # Ausnahmen korrigieren
    $frm.btn, configure -command "append formula ."
    $frm.btn= configure -command {Advanced_Evaluate}
    $frm.btnC configure -command {set formula ""}
}


# Formelausdruck auswerten und in die History einfügen
proc Advanced_Evaluate {} {
    global formula

    # auswerten
    .input.history insert end $formula
    set formula [expr $formula]

    # Ergebnis zur History hinzufügen und ggf. Anzeige scrollen
    .input.history insert end "\n= $formula\n---------------\n"
    .input.history see end
}


set points ""
set datasize 100
set formulaplot ""

# Initialsierung
proc Plotter_Create {} {
    Menu_Destroy

    # Plotterbereich
    frame .plot
    pack .plot -side right -fill both
    canvas .plot.function -bg white
    scale .plot.range -label Range -orient horizontal \
            -variable range \
            -resolution 0.1 -from 0.1 -to 20 \
            -command "Plotter_Scale"
    .plot.range set 1.0
    pack .plot.range -side bottom -fill x
    pack .plot.function -fill both


    # Eingabefeld anlegen (Wert steht in $formula)
    global formulaplot
    set formula $formulaplot
    entry .input -textvariable formula \
            -validatecommand { Plotter_Update %P; return 1 } \
            -validate all
    pack  .input -side top -fill x

    # Buttons anlegen
    frame .btn
    pack .btn

    set row 0
    foreach buttonrow {{x x^2 x^3 x^4} {1/x 1/x^2 1/x^3 1/x^4} \
                {sqrt pow exp log} {sin cos tan \(} {asin acos atan \)} \
                {1 2 3 +} {4 5 6 -} {7 8 9 *} {0 , C Plot}} {
        # zeilenweise erstellen
        set frm [frame .btn.frm$row]
        pack $frm
        incr row

        # Button erzeugen
        foreach singlebutton $buttonrow {
            # Dollarzeichen nicht bei Button-Beschriftung zeigen
            button $frm.btn$singlebutton -text $singlebutton \
                    -command "append formula {$singlebutton}" \
                    -width 4 \
                    -height 1 \
                    -padx 5 \
                    -pady 5
            pack $frm.btn$singlebutton -side left -padx 1 -pady 1
        }
    }

    # Ausnahmen korrigieren
    $frm.btn, configure -command "append formula ."
    $frm.btnPlot configure -command {Plotter_Evaluate}
    $frm.btnC configure -command {set formula ""}
    .btn.frm0.btnx^2 configure -command "append formula pow(x,2)"
    .btn.frm0.btnx^3 configure -command "append formula pow(x,3)"
    .btn.frm0.btnx^4 configure -command "append formula pow(x,4)"
    .btn.frm1.btn1/x^2 configure -command "append formula 1/pow(x,2)"
    .btn.frm1.btn1/x^3 configure -command "append formula 1/pow(x,3)"
    .btn.frm1.btn1/x^4 configure -command "append formula 1/pow(x,4)"

    # Mausbewegung abfangen
    bind .plot.function <Motion> { Plotter_Tooltip %x %y }
}


# Formelausdruck auswerten
proc Plotter_Evaluate {} {
    global formulaplot
    global range

    # Offset zwischen zwei x-Werten (entspr. 1 Pixel)
    global datasize
    set plot_width  [lindex [.plot.function configure -width] 4]
    set datasize $plot_width
    set dataoffset [expr 2*$range/($datasize-1)]

    # Kurve ermitteln
    global points
    set points ""
    set x [expr -$range]
    for {set i 0} {$i < $datasize} {incr i} {
        # Punkt berechnen (ungültige Berechnungen abfangen)
        if { [catch { set y [expr $formulaplot] }] } {
            set y 0
        }
        lappend points $x $y

        set x [expr $x+$dataoffset]
    }

    Plotter_Draw
}


# und alles zeichnen
proc Plotter_Draw {} {
    global range

    # Canvas löschen
    .plot.function delete all

    # Ausmaße des Plot-Bereiches ermitteln
    set plot_width  [lindex [.plot.function configure -width] 4]
    set plot_height [lindex [.plot.function configure -height] 4]

    set left [expr -$range]
    set right $range
    set top $range
    set bottom [expr -$range]

    # Achsen zeichnen
    .plot.function create line $left 0 $right 0 \
            -fill gray -arrow last
    .plot.function create line 0 $top 0 $bottom \
            -fill gray -arrow first
    # Beschriftung
    .plot.function create text $range 0 -text "  x $range" -anchor ne
    .plot.function create text -$range 0 -text "   $range" -anchor nw
    .plot.function create text 0 $range -text "  y $range" -anchor nw
    .plot.function create text 0 -$range -text "  $range" -anchor sw
    .plot.function create text 0 0 -text "  0" -anchor nw

    # Kurve zeichnen
    global points
    global datasize
    eval .plot.function create line $points -fill red

    # alles skalieren und verschieben
    set scalex [expr 0.5*$plot_width/$range]
    set scaley [expr 0.5*$plot_height/$range]
    .plot.function scale all 0 0 $scalex -$scaley
    .plot.function move  all [expr $plot_width/2] [expr $plot_height/2]
}


# Wrapper für Skalierungsevents
proc Plotter_Scale {width} {
    Plotter_Evaluate
}


# Tooltip über der Zeichenfläche anzeigen
set cursor_id 0
set text_id 0
proc Plotter_Tooltip { eventx eventy } {
    global cursor_id
    global text_id
    global range
    global formulaplot

    # vertikale Linie
    .plot.function delete $cursor_id
    set cursor_id [.plot.function create line $eventx 0 $eventx [lindex [.plot.function configure -height] 4] -fill black]

    # Funktionswert an aktueller Cursorposition
    set plot_width  [lindex [.plot.function configure -width] 4]
    set x [expr 2*$eventx*$range/$plot_width-$range]
    .plot.function delete $text_id
    if {![catch { set y [expr $formulaplot] }]} {
        set ausgabe [format "(%.2f %.2f)" $x $y]
        set eventy [expr $eventy-5]
        # ... ausgeben
        set text_id [.plot.function create text $eventx $eventy -text $ausgabe]
    }
}


# während der Formeleingabe Graphen aktualisieren
proc Plotter_Update { newformula } {
    global formulaplot
    # sowohl x als auch $x erlauben
    regsub -all {\$x} $newformula {x} formulaplot
    regsub -all {\$} $formulaplot {x} formulaplot
    regsub -all {($x|x)} $formulaplot {$x} formulaplot
    # Graph neu berechnen
    Plotter_Evaluate
}


# Initialisierung
proc Menu_Create { } {
    # Frame zur Positionierung des Menüs
    frame .menuframe
    pack .menuframe -side top -fill x
    # Menü "File"
    menubutton .menuframe.menufile -text File -menu .menuframe.menufile.m
    pack .menuframe.menufile -side left -anchor w
    # nicht abreißbar
    menu .menuframe.menufile.m -tearoff 0
    # "Programm beenden" erlauben, den Rest deaktivieren (da Simple bei Beginn)
    .menuframe.menufile.m add command -label Load -command { Load } -state disabled
    .menuframe.menufile.m add command -label Save -command { Save } -state disabled
    .menuframe.menufile.m add separator
    .menuframe.menufile.m add command -label Quit -command { exit }

    # Menü "View"
    menubutton .menuframe.menuview -text View -menu .menuframe.menuview.m
    pack .menuframe.menuview -side left -anchor w
    # nicht abreißbar
    menu .menuframe.menuview.m -tearoff 0
    # alle 3 Darstellungsarten
    .menuframe.menuview.m add radio -label Simple   -command { Simple_Create }
    .menuframe.menuview.m add radio -label Advanced -command { Advanced_Create }
    .menuframe.menuview.m add radio -label Plotter  -command { Plotter_Create }
    # Simple aktivieren
    .menuframe.menuview.m invoke 0
}



##################################################
# GUI-Elemente erzeugen und anordnen
wm title . "Calculator, Stephan Brumme, 702544"
# Menü (startet automatisch Minimalfassung)
Menu_Create

set formula 0

