A library for GUI development with Tcl/Tk.
Docs:
########################################################### # Name: apavedialog.tcl # Author: Alex Plotnikov (aplsimple@gmail.com) # Date: 12/09/2021 # Brief: Handles standard dialogs with advanced features. # License: MIT. ########################################################### source [file join [file dirname [info script]] apavebase.tcl] # ________________________ apave NS _________________________ # namespace eval ::apave { variable querydlg {} variable msgarray; array set msgarray [list] set msgarray(savenot) {Don't save} set msgarray(savetext) {Save text} set msgarray(saveask) {Save changes made to the text?} set msgarray(find) {Find: } proc dlgPath {} { # Gets a current dialogue's path. # In fact, it does the same as [my dlgPath], but it can be # called outside of apave dialogue object (useful sometimes). return $::apave::querydlg } #_______________________ proc msgcatDialogs {} { # Prepares localized messages used in dialogues. variable msgarray foreach n [array names msgarray] { set msgarray($n) [msgcat::mc $msgarray($n)] } } ## ________________________ EONS apave _________________________ ## } # ________________________ APaveDialog class _________________________ # oo::class create ::apave::APaveDialog { superclass ::apave::APaveBase variable HLstring Winpath CheckNomore Foundstr Dlgpath Defb1 Defb2 Indexdlg #_______________________ constructor {{win ""} args} { # Creates APaveDialog object. # win - window's name (path) # args - additional arguments set Winpath $win ;# dialogs are bound to $win, default "" means . set Dlgpath {} ;# current dialog's path set Foundstr {} ;# current found string set HLstring {} ;# current selected string # Actions on closing the editor ; proc exitEditor {w resExit} { upvar $resExit res set wtxt [my TexM] if {[my askForSave $wtxt] && [$wtxt edit modified]} { set pdlg [::apave::APaveDialog new $w] set r [$pdlg misc warn $::apave::msgarray(savetext) \ "\n $::apave::msgarray(saveask) \n" \ [list Save 1 $::apave::msgarray(savenot) Close Cancel 0] \ 1 -focusback [my TexM] -centerme $w] if {$r==1} { set res 1 } elseif {$r eq "Close"} { set res 0 } $pdlg destroy } else { set res 0 } return } # end of APaveDialog constructor if {[llength [self next]]} { next {*}$args } } #_______________________ destructor { # Clears variables used in the object. if {[llength [self next]]} next } ## ________________________ Standard dialogs _________________________ ## # ok - dialog with button OK # okcancel - dialog with buttons OK, Cancel # yesno - dialog with buttons YES, NO # yesnocancel - dialog with buttons YES, NO, CANCEL # retrycancel - dialog with buttons RETRY, CANCEL # abortretrycancel - dialog with buttons ABORT, RETRY, CANCEL # misc - dialog with miscellaneous buttons # # Called as: # dialog icon ttl msg ?defb? ?args? # # Mandatory arguments of dialogs: # icon - icon name (info, warn, err, ques) # ttl - title # msg - message # Optional arguments: # defb - default button (OK, YES, NO, CANCEL, RETRY, ABORT) # args - options for Query #_______________________ method PrepArgs {args} { # Prepares a list of arguments. # Returns the list (wrapped in list) and a command for OK button. lassign [::apave::parseOptions $args -modal {} -ch {} -comOK {} -onclose {}] \ modal ch comOK onclose if {[string is true -strict $modal]} { set com 1 } elseif {$ch ne {}} { # some options are incompatible with -ch if {[string match *destroy* $onclose]} {set onclose {}} lappend args -modal 1 -onclose $onclose set com 1 } elseif {$comOK eq {}} { set com destroy ;# non-modal without -ch option } else { set com $comOK } list [list $args] $com } #_______________________ method ok {icon ttl msg args} { # Shows the *OK* dialog. # icon - icon # ttl - title # msg - message # args - options lassign [my PrepArgs {*}$args] args comOK my Query $icon $ttl $msg "ButOK OK $comOK" ButOK {} $args } #_______________________ method okcancel {icon ttl msg {defb OK} args} { # Shows the *OKCANCEL* dialog. # icon - icon # ttl - title # msg - message # defb - button to be selected # args - options lassign [my PrepArgs {*}$args] args my Query $icon $ttl $msg \ {ButOK OK 1 ButCANCEL Cancel 0} But$defb {} $args } #_______________________ method yesno {icon ttl msg {defb YES} args} { # Shows the *YESNO* dialog. # icon - icon # ttl - title # msg - message # defb - button to be selected # args - options lassign [my PrepArgs {*}$args] args my Query $icon $ttl $msg \ {ButYES Yes 1 ButNO No 0} But$defb {} $args } #_______________________ method yesnocancel {icon ttl msg {defb YES} args} { # Shows the *YESNOCANCEL* dialog. # icon - icon # ttl - title # msg - message # defb - button to be selected # args - options lassign [my PrepArgs {*}$args] args my Query $icon $ttl $msg \ {ButYES Yes 1 ButNO No 2 ButCANCEL Cancel 0} But$defb {} $args } #_______________________ method retrycancel {icon ttl msg {defb RETRY} args} { # Shows the *RETRYCANCEL* dialog. # icon - icon # ttl - title # msg - message # defb - button to be selected # args - options lassign [my PrepArgs {*}$args] args my Query $icon $ttl $msg \ {ButRETRY Retry 1 ButCANCEL Cancel 0} But$defb {} $args } #_______________________ method abortretrycancel {icon ttl msg {defb RETRY} args} { # Shows the *ABORTRETRYCANCEL* dialog. # icon - icon # ttl - title # msg - message # defb - button to be selected # args - options lassign [my PrepArgs {*}$args] args my Query $icon $ttl $msg \ {ButABORT Abort 1 ButRETRY Retry 2 ButCANCEL \ Cancel 0} But$defb {} $args } #_______________________ method misc {icon ttl msg butts {defb ""} args} { # Shows the *MISCELLANEOUS* dialog. # icon - icon # ttl - title # msg - message # butts - list of buttons # defb - button to be selected # args - options # The *butts* is a list of pairs "title of button" "number/ID of button" foreach {nam num} $butts { set but But[namespace tail $num] ;# for "num" set as a command lappend apave_msc_bttns $but "$nam" $num if {$defb eq {}} { set defb $num } } lassign [my PrepArgs {*}$args] args my Query $icon $ttl $msg $apave_msc_bttns But$defb {} $args } ## ________________________ Progress for splash _________________________ ## method progress_Begin {type wprn ttl msg1 msg2 maxvalue args} { # Creates and shows a progress window. Fit for splash screens. # type - any word(s) # wprn - parent window # ttl - title message # msg1 - top message # msg2 - bottom message # maxvalue - maximum value # args - additional attributes of the progress bar # If type={}, widgetType method participates too in progress_Go, and also # progress_End puts out a little statistics. # See also: APaveBase::widgetType, progress_Go, progress_End set ::apave::_AP_VARS(win) .proSplashScreen set qdlg $::apave::_AP_VARS(win) set atr1 "-maximum 100 -value 0 -mode determinate -length 300 -orient horizontal" set widlist [list \ "fra - - - - pack {-h 10}" \ ".Lab1SplashScreen - - - - pack {-t {$msg1}}" \ ".ProgSplashScreen - - - - pack {$atr1 $args}" \ ".Lab2SplashScreen - - - - {pack -anchor w} {-t {$msg2}}" \ ] set win [my makeWindow $qdlg.fra $ttl] set widlist [my paveWindow $qdlg.fra $widlist] ::tk::PlaceWindow $win widget $wprn my showWindow $win 0 1 update set ::apave::_AP_VARS(ProSplash,type) $type set ::apave::_AP_VARS(ProSplash,win) $win set ::apave::_AP_VARS(ProSplash,wid1) [my Lab1SplashScreen] set ::apave::_AP_VARS(ProSplash,wid2) [my ProgSplashScreen] set ::apave::_AP_VARS(ProSplash,wid3) [my Lab2SplashScreen] set ::apave::_AP_VARS(ProSplash,val1) 0 set ::apave::_AP_VARS(ProSplash,val2) 0 set ::apave::_AP_VARS(ProSplash,value) 0 set ::apave::_AP_VARS(ProSplash,curvalue) 0 set ::apave::_AP_VARS(ProSplash,maxvalue) $maxvalue set ::apave::_AP_VARS(ProSplash,after) [list] # 'after' should be postponed, as 'update' messes it up rename ::after ::ProSplash_after ; proc ::after {args} { lappend ::apave::_AP_VARS(ProSplash,after) $args } } #_______________________ method progress_Go {value {msg1 ""} {msg2 ""}} { # Updates a progress window. # value - current value of the progress bar # msg1 - top message # msg2 - bottom message # Returns current percents (value) of progress. # If it reaches 100, the progress_Go may continue from 0. # See also: progress_Begin set ::apave::_AP_VARS(ProSplash,val1) $value incr ::apave::_AP_VARS(ProSplash,val2) set val [expr {min(100,int(100*$value/$::apave::_AP_VARS(ProSplash,maxvalue)))}] if {$val!=$::apave::_AP_VARS(ProSplash,value)} { set ::apave::_AP_VARS(ProSplash,value) $val catch { ;# there might be no splash widgets, then let it run dry $::apave::_AP_VARS(ProSplash,wid2) configure -value $val if {$msg1 ne {}} { $::apave::_AP_VARS(ProSplash,wid1) configure -text $msg1 } if {$msg2 ne {}} { $::apave::_AP_VARS(ProSplash,wid3) configure -text $msg2 } update } } return $val } #_______________________ method progress_End {} { # Destroys a progress window. # See also: progress_Begin variable ::apave::_AP_VARS catch { destroy $::apave::_AP_VARS(ProSplash,win) rename ::after {} rename ::ProSplash_after ::after foreach aftargs $::apave::_AP_VARS(ProSplash,after) { after {*}$aftargs } if {$::apave::_AP_VARS(ProSplash,type) eq {}} { puts "Splash statistics: \ \n \"maxvalue\": $::apave::_AP_VARS(ProSplash,maxvalue) \ \n curr.value: $::apave::_AP_VARS(ProSplash,val1) \ \n steps made: $::apave::_AP_VARS(ProSplash,val2)" } unset ::apave::_AP_VARS(ProSplash,type) unset ::apave::_AP_VARS(ProSplash,win) unset ::apave::_AP_VARS(ProSplash,wid1) unset ::apave::_AP_VARS(ProSplash,wid2) unset ::apave::_AP_VARS(ProSplash,wid3) unset ::apave::_AP_VARS(ProSplash,val1) unset ::apave::_AP_VARS(ProSplash,val2) unset ::apave::_AP_VARS(ProSplash,value) unset ::apave::_AP_VARS(ProSplash,curvalue) unset ::apave::_AP_VARS(ProSplash,maxvalue) unset ::apave::_AP_VARS(ProSplash,after) } } ## ________________________ Text utilities _________________________ ## method pasteText {txt} { # Removes a selection at pasting. # txt - text's path # The absence of this feature is very perpendicular of Tk's paste. set err [catch {$txt tag ranges sel} sel] if {!$err && [llength $sel]==2} { lassign $sel pos1 pos2 set pos [$txt index insert] if {[$txt compare $pos >= $pos1] && [$txt compare $pos <= $pos2]} { $txt delete $pos1 $pos2 } } } #_______________________ method doubleText {txt {dobreak 1}} { # Doubles a current line or a selection of text widget. # txt - text's path # dobreak - if true, means "return -code break" # The *dobreak=true* allows to break the Tk processing of keypresses # such as Ctrl+D. # If not set, the text widget is identified as `my TexM`. if {$txt eq {}} {set txt [my TexM]} set err [catch {$txt tag ranges sel} sel] if {!$err && [llength $sel]==2} { lassign $sel pos pos2 set pos3 insert ;# single selection } else { lassign [my GetLinePosition $txt insert] pos pos2 ;# current line set pos3 $pos2 } set duptext [$txt get $pos $pos2] if {$pos3 ne {insert} && $pos2==[$txt index end]} { # current line is the last one: duplicate it properly set duptext \n[string range $duptext 0 end-1] } $txt insert $pos3 $duptext if {$dobreak} {return -code break} } #_______________________ method deleteLine {txt {dobreak 1}} { # Deletes a current line of text widget. # txt - text's path # dobreak - if true, means "return -code break" # The *dobreak=true* allows to break the Tk processing of keypresses # such as Ctrl+Y. # If not set, the text widget is identified as `my TexM`. if {$txt eq {}} {set txt [my TexM]} lassign [my GetLinePosition $txt insert] linestart lineend $txt delete $linestart $lineend if {$dobreak} {return -code break} } #_______________________ method linesMove {txt to {dobreak 1}} { # Moves a current line or lines of selection up/down. # txt - text's path # to - direction (-1 means "up", +1 means "down") # dobreak - if true, means "return -code break" # The *dobreak=true* allows to break the Tk processing of keypresses # such as Ctrl+Y. # If not set, the text widget is identified as `my TexM`. ; proc NewRow {ind rn} { set i [string first . $ind] set row [string range $ind 0 $i-1] return [incr row $rn][string range $ind $i end] } if {$txt eq {}} {set txt [my TexM]} set err [catch {$txt tag ranges sel} sel] lassign [$txt index insert] pos ;# position of caret if {[set issel [expr {!$err && [llength $sel]==2}]]} { lassign $sel pos1 pos2 ;# selection's start & end set l1 [expr {int($pos1)}] set l2 [expr {int($pos2)}] set pos21 [$txt index "$pos2 linestart"] if {[$txt get $pos21 $pos2] eq {}} {incr l2 -1} set lfrom [expr {$to>0 ? $l2+1 : $l1-1}] set lto [expr {$to>0 ? $l1-1 : $l2-1}] } else { set lcurr [expr {int($pos)}] set lfrom [expr {$to>0 ? $lcurr+1 : $lcurr-1}] set lto [expr {$to>0 ? $lcurr-1 : $lcurr-1}] } set lend [expr {int([$txt index end])}] if {$lfrom>0 && $lfrom<$lend} { incr lto lassign [my GetLinePosition $txt $lfrom.0] linestart lineend set duptext [$txt get $linestart $lineend] ::apave::undoIn $txt $txt delete $linestart $lineend $txt insert $lto.0 $duptext ::tk::TextSetCursor $txt [NewRow $pos $to] if {$issel} { $txt tag add sel [NewRow $pos1 $to] [NewRow $pos2 $to] } if {[lsearch -glob [$txt tag names] tagCOM*]>-1} { catch {::hl_tcl::my::Modified $txt insert $lto.0 $lto.end} } ::apave::undoOut $txt if {$dobreak} {return -code break} } } #_______________________ method selectedWordText {txt} { # Returns a word under the cursor or a selected text. # txt - the text's path set seltxt {} if {![catch {$txt tag ranges sel} seltxt]} { if {[set forword [expr {$seltxt eq {}}]]} { set pos [$txt index "insert wordstart"] set pos2 [$txt index "insert wordend"] set seltxt [string trim [$txt get $pos $pos2]] if {![string is wordchar -strict $seltxt]} { # when cursor just at the right of word: take the word at the left set pos [$txt index "insert -1 char wordstart"] set pos2 [$txt index "insert -1 char wordend"] } } else { lassign $seltxt pos pos2 } catch { set seltxt [$txt get $pos $pos2] if {[set sttrim [string trim $seltxt]] ne {}} { if {$forword} {set seltxt $sttrim} } } } return $seltxt } #_______________________ method InitFindInText { {ctrlf 0} {txt {}} } { # Initializes the search in the text. # ctrlf - "1" means that the method is called by Ctrl+F # txt - path to the text widget if {$txt eq {}} {set txt [my TexM]} if {$ctrlf} { ;# Ctrl+F moves cursor 1 char ahead ::tk::TextSetCursor $txt [$txt index "insert -1 char"] } if {[set seltxt [my selectedWordText $txt]] ne {}} { set Foundstr $seltxt } } #_______________________ method findInText {{donext 0} {txt ""} {varFind ""} {dobell yes}} { # Finds a string in text widget. # donext - "1" means 'from a current position' # txt - path to the text widget # varFind - variable # dobell - if yes, bells # Returns yes, if found (or nothing to find), otherwise returns "no"; # also, if there was a real search, the search string is added. if {$txt eq {}} { set txt [my TexM] set sel $Foundstr } elseif {$donext && [set sel [my get_HighlightedString]] ne {}} { # find a string got with alt+left/right } elseif {$varFind eq {}} { set sel $Foundstr } else { set sel [set $varFind] } if {$donext} { set pos [$txt index insert] if {{sel} in [$txt tag names $pos]} { set pos [$txt index "$pos + 1 chars"] } set pos [$txt search -- $sel $pos end] } else { set pos {} my set_HighlightedString {} } if {![string length "$pos"]} { set pos [$txt search -- $sel 1.0 end] } if {[string length "$pos"]} { ::tk::TextSetCursor $txt $pos $txt tag add sel $pos [$txt index "$pos + [string length $sel] chars"] focus $txt set res yes } else { if {$dobell} bell set res no } list $res $sel } #_______________________ method GetLinkLab {m} { # Gets a link for label. # m - label with possible link (between <link> and </link>) # Returns: list of "pure" message and link for label. if {[set i1 [string first "<link>" $m]]<0} { return [list $m] } set i2 [string first "</link>" $m] set link [string range $m $i1+6 $i2-1] set m [string range $m 0 $i1-1][string range $m $i2+7 end] list $m [list -link $link] } #_______________________ method popupFindCommands {pop {txt {}} {com1 ""} {com2 ""}} { # Returns find commands for a popup menu on a text. # pop - path to the menu # txt - path to the text # com1 - user's command "find first" # com2 - user's command "find next" set accF3 [::apave::KeyAccelerator [::apave::getTextHotkeys F3]] if {$com1 eq {}} {set com1 "[self] InitFindInText 0 $txt; focus \[[self] Entfind\]"} if {$com2 eq {}} {set com2 "[self] findInText 1 $txt"} return "\$pop add separator \$pop add command [my iconA find] -accelerator Ctrl+F -label \"Find First\" \\ -command {$com1} \$pop add command [my iconA none] -accelerator $accF3 -label \"Find Next\" \\ -command {$com2}" } #_______________________ method popupBlockCommands {pop {txt {}}} { # Returns block commands for a popup menu on a text. # pop - path to the menu # txt - path to the text set accD [::apave::KeyAccelerator [::apave::getTextHotkeys CtrlD]] set accY [::apave::KeyAccelerator [::apave::getTextHotkeys CtrlY]] return "\$pop add separator \$pop add command [my iconA add] -accelerator $accD -label \"Double Selection\" \\ -command \"[self] doubleText {$txt} 0\" \$pop add command [my iconA delete] -accelerator $accY -label \"Delete Line\" \\ -command \"[self] deleteLine {$txt} 0\" \$pop add command [my iconA up] -accelerator Alt+Up -label \"Line(s) Up\" \\ -command \"[self] linesMove {$txt} -1 0\" \$pop add command [my iconA down] -accelerator Alt+Down -label \"Line(s) Down\" \\ -command \"[self] linesMove {$txt} +1 0\"" } #_______________________ method askForSave {wtxt {doask ""}} { # For a text, sets/gets "ask for save changes" flag. # wtxt - text's path # doask - flag # If the flag argument omitted, returns the flag else sets it. # See also: constructor set prop _AskForSave_$wtxt if {$doask eq {}} { set res [::apave::getProperty $prop] if {![string is false -strict $res]} {set res 1} } else { set res [::apave::setProperty $prop $doask] } return $res } ## ________________________ Highlighting _________________________ ## method popupHighlightCommands {{pop ""} {txt ""}} { # Returns highlighting commands for a popup menu on a text. # pop - path to the menu # txt - path to the text set accQ [::apave::KeyAccelerator [::apave::getTextHotkeys AltQ]] set accW [::apave::KeyAccelerator [::apave::getTextHotkeys AltW]] set res "\$pop add separator \$pop add command [my iconA upload] -accelerator $accQ \\ -label \"Highlight First\" -command \"[self] seek_highlight %w 2\" \$pop add command [my iconA download] -accelerator $accW \\ -label \"Highlight Last\" -command \"[self] seek_highlight %w 3\" \$pop add command [my iconA previous] -accelerator Alt+Left \\ -label \"Highlight Previous\" -command \"[self] seek_highlight %w 0\" \$pop add command [my iconA next] -accelerator Alt+Right \\ -label \"Highlight Next\" -command \"[self] seek_highlight %w 1\" \$pop add command [my iconA none] -accelerator Dbl.Click \\ -label \"Highlight All\" -command \"[self] highlight_matches %w\"" if {$txt ne {}} {set res [string map [list %w $txt] $res]} return $res } #_______________________ method set_HighlightedString {sel} { # Saves a string got from highlighting by Alt+left/right/q/w. # sel - the string to be saved set HLstring $sel if {$sel ne {}} {set Foundstr $sel} } #_______________________ method get_HighlightedString {} { # Returns a string got from highlighting by Alt+left/right/q/w. if {[info exists HLstring]} { return $HLstring } return {} } #_______________________ method set_highlight_matches {w} { # Creates bindings to highlight matches in a text. # w - path to the text if {![winfo exists $w]} return $w tag configure hilited -foreground #1f0000 -background #ffa073 $w tag configure hilited2 -foreground #1f0000 -background #ff6b85 $w tag lower hilited sel bind $w <Double-ButtonPress-1> [list [self] highlight_matches $w] ::apave::bindToEvent $w <KeyRelease> [self] unhighlight_matches $w bind $w <Alt-Left> "[self] seek_highlight $w 0 ; break" bind $w <Alt-Right> "[self] seek_highlight $w 1 ; break" foreach k [::apave::getTextHotkeys AltQ] { bind $w <$k> [list [self] seek_highlight $w 2] } foreach k [::apave::getTextHotkeys AltW] { bind $w <$k> [list [self] seek_highlight $w 3] } } #_______________________ method get_highlighted {txt} { # Gets a selected word after double-clicking on a text. # w - path to the text set err [catch {$txt tag ranges sel} sel] lassign $sel pos pos2 if {!$err && [llength $sel]==2} { set sel [$txt get $pos $pos2] ;# single selection } else { if {$err || [string trim $sel] eq {}} { set pos [$txt index "insert wordstart"] set pos2 [$txt index "insert wordend"] set sel [string trim [$txt get $pos $pos2]] if {![string is wordchar -strict $sel]} { # when cursor just at the right of word: take the word at the left # e.g. if "_" stands for cursor then "word_" means selecting "word" set pos [$txt index "insert -1 char wordstart"] set pos2 [$txt index "insert -1 char wordend"] set sel [string trim [$txt get $pos $pos2]] } set slen [string length $sel] if {!$slen} {incr slen; set pos2 [$txt index "$pos2 +1c"]} set pos [$txt index "$pos2 -$slen char"] set sel [string trim [$txt get $pos $pos2]] } } list $sel $pos $pos2 } #_______________________ method highlight_matches {txt} { # Highlights matches of selected word in a text. # txt - path to the text lassign [my get_highlighted $txt] sel pos pos2 if {$sel eq {}} return after idle "[self] highlight_matches_real $txt $pos $pos2" my set_HighlightedString $sel set lenList {} set posList [$txt search -all -count lenList -- "$sel" 1.0 end] foreach pos2 $posList len $lenList { if {$len eq {}} {set len [string length $sel]} set pos3 [$txt index "$pos2 + $len chars"] if {$pos2 == $pos} { lappend matches2 $pos2 $pos3 } else { lappend matches1 $pos2 $pos3 } } catch { $txt tag remove hilited 1.0 end $txt tag remove hilited2 1.0 end $txt tag add hilited {*}$matches1 $txt tag add hilited2 {*}$matches2 } set ::apave::_AP_VARS(HILI,$txt) yes } #_______________________ method unhighlight_matches {txt} { # Unhighlights matches of selected word in a text. # w - path to the text if {[info exists ::apave::_AP_VARS(HILI,$txt)] && $::apave::_AP_VARS(HILI,$txt)} { $txt tag remove hilited 1.0 end $txt tag remove hilited2 1.0 end set ::apave::_AP_VARS(HILI,$txt) no } } #_______________________ method seek_highlight {txt mode} { # Seeks the selected word forward/backward/to first/to last in a text. # w - path to the text # mode - 0 (search backward), 1 (forward), 2 (first), 3 (last) my unhighlight_matches $txt lassign [my get_highlighted $txt] sel pos pos2 if {$sel eq {}} return my set_HighlightedString $sel switch $mode { 0 { ;# backward set nc [expr {[string length $sel] - 1}] set pos [$txt index "$pos - $nc chars"] set pos [$txt search -backwards -- $sel $pos 1.0] } 1 { ;# forward set pos [$txt search -- $sel $pos2 end] } 2 { ;# to first set pos [$txt search -- $sel 1.0 end] } 3 { ;# to last set pos [$txt search -backwards -- $sel end 1.0] } } if {[string length "$pos"]} { ::tk::TextSetCursor $txt $pos $txt tag add sel $pos [$txt index "$pos + [string length $sel] chars"] } } #_______________________ method highlight_matches_real {txt pos1 pos2} { # Highlights a selected word in a text, esp. fow Windows. # Windows thinks a word is edged by spaces only: not in real case. # txt - path to the text # pos1 - starting position of real selection # pos2 - ending position of real selection $txt tag remove sel 1.0 end if {[$txt get $pos1] eq "\n"} { # if a word at line start, Windows select an empty line above lassign [split $pos1 .] l c set pos1 [incr l].$c } catch {::tk::TextSetCursor $txt $pos1} $txt tag add sel $pos1 $pos2 } ## ________________________ Query's auxiliaries _________________________ ## method varName {wname} { # Gets a variable name associated with a widget's name of "input" dialogue. # wname - widget's name return [namespace current]::var$wname } #_______________________ method FieldName {name} { # Gets a field name. return fraM.fra$name.$name } #_______________________ method GetVarsValues {lwidgets} { # Gets values of entries passed (or set) in -tvar. # lwidgets - list of widget items set res [set vars [list]] foreach wl $lwidgets { set ownname [my ownWName [lindex $wl 0]] set vv [my varName $ownname] set attrs [lindex $wl 6] if {[string match "ra*" $ownname]} { # only for widgets with a common variable (e.g. radiobuttons): foreach t {-var -tvar} { if {[set v [::apave::getOption $t {*}$attrs]] ne {}} { array set a $attrs set vv $v } } } if {[info exist $vv] && [lsearch $vars $vv]==-1} { lappend res [set $vv] lappend vars $vv } } return $res } #_______________________ method SetGetTexts {oper w iopts lwidgets} { # Sets/gets contents of text fields. # oper - "set" to set, "get" to get contents of text field # w - window's name # iopts - equals to "" if no operation # lwidgets - list of widget items if {$iopts eq {}} return foreach widg $lwidgets { set wname [lindex $widg 0] set name [my ownWName $wname] if {[string range $name 0 1] eq "te"} { set vv [my varName $name] if {$oper eq "set"} { my displayText $w.$wname [set $vv] } else { set $vv [string trimright [$w.$wname get 1.0 end]] } } } } #_______________________ method GetLinePosition {txt ind} { # Gets a line's position. # txt - text widget # ind - index of the line # Returns a list containing a line start and a line end. set linestart [$txt index "$ind linestart"] set lineend [expr {$linestart + 1.0}] list $linestart $lineend } #_______________________ method AppendButtons {widlistName buttons neighbor pos defb timeout win modal ONCLOSE} { # Adds buttons to the widget list from a position of neighbor widget. # widlistName - variable name for widget list # buttons - buttons to add # neighbor - neighbor widget # pos - position of neighbor widget # defb - default button # timeout - timeout (to count down seconds and invoke a button) # win - dialogue's path # modal - yes if the window is modal # ONCLOSE - command to run at closing the dialog # Returns list of "Help" button's name and command. upvar $widlistName widlist set Defb1 [set Defb2 [set bhlist {}]] foreach {but txt res} $buttons { set com "[self] res $Dlgpath" if {[info commands $res] eq {}} { set com "$com $res" } else { if {$res eq {destroy}} { # for compatibility with old modal windows if {$modal} {set res "$com 0"} {set res "destroy $win"} } set com $res ;# "res" is set as a command } if {$but eq {butHELP}} { # Help button contains the command in "res" set com [string map "%w $win" $res] set bhlist [list $but $com] } elseif {$Defb1 eq {}} { set Defb1 $but } elseif {$Defb2 eq {}} { set Defb2 $but } if {$ONCLOSE ne {}} {append com " ; $ONCLOSE"} if {[set _ [string first "::" $txt]]>-1} { set tt " -tip {[string range $txt $_+2 end]}" set txt [string range $txt 0 $_-1] } else { set tt {} } if {$timeout ne {} && ($defb eq $but || $defb eq {})} { set tmo "-timeout {$timeout}" } else { set tmo {} } if {$but eq {butHELP}} { set neighbor [lindex $widlist end 1] set widlist [lreplace $widlist end end] lappend widlist [list $but $neighbor T 1 1 {-st w} \ "-t \"$txt\" -com \"$com\"$tt $tmo -tip F1"] set h h_Help lappend widlist [list $h $but L 1 94 {-st we}] set neighbor $h } else { lappend widlist [list $but $neighbor $pos 1 1 {-st we} \ "-t \"$txt\" -com \"$com\"$tt $tmo"] set neighbor $but } set pos L } lassign [my LowercaseWidgetName $Dlgpath.fra.$Defb1] Defb1 lassign [my LowercaseWidgetName $Dlgpath.fra.$Defb2] Defb2 return $bhlist } ## ________________________ Query the terrible _________________________ ## method Query {icon ttl msg buttons defb inopts argdia {precom ""} args} { # Makes a query (or a message) and gets the user's response. # icon - icon name (info, warn, ques, err) # ttl - title # msg - message # buttons - list of triples "button name, text, ID" # defb - default button (OK, YES, NO, CANCEL, RETRY, ABORT) # inopts - options for input dialog # argdia - list of dialog's options # precom - command(s) performed before showing the dialog # args - additional options (message's font etc.) # The *argdia* may contain additional options of the query, like these: # -checkbox text (-ch text) - makes the checkbox's text visible # -geometry +x+y (-g +x+y) - sets the geometry of dialog # -color cval (-c cval) - sets the color of message # If "-geometry" option is set (even equaling "") the Query procedure # returns a list with chosen button's ID and a new geometry. # Otherwise it returns only the chosen button's ID. # See also: # [aplsimple.github.io](https://aplsimple.github.io/en/tcl/pave/index.html) set wdia $Winpath.dia append wdia [lindex [split [self] :] end] ;# be unique per apave object set qdlg [set Dlgpath $wdia[incr Indexdlg]] # remember the focus (to restore it after closing the dialog) set focusback [focus] set focusmatch {} # options of dialog lassign {} chmsg geometry optsLabel optsMisc optsFont optsFontM optsHead \ root rotext head hsz binds postcom onclose ONCLOSE timeout tab2 \ tags cc themecolors optsGrid addpopup minsize savetext set wasgeo [set textmode [set stay [set waitvar 0]]] set readonly [set hidefind [set scroll [set modal 1]]] set curpos {1.0} set CheckNomore 0 foreach {opt val} {*}$argdia { if {$opt in {-c -color -fg -bg -fgS -bgS -cc -hfg -hbg}} { # take colors by their variables if {[info exist $val]} {set val [set $val]} } switch -- $opt { -H - -head { set head [string map {$ \$ \" \'\' \{ ( \} )} $val] } -help { set buttons "butHELP Help {$val} $buttons" } -ch - -checkbox {set chmsg "$val"} -g - -geometry { set geometry $val if {[set wasgeo [expr {[string first "pointer" $val]<0}]]} { lassign [::apave::splitGeometry $geometry] - - gx gy } } -c - -color {append optsLabel " -foreground {$val}"} -a { ;# additional grid options of message labels append optsGrid " $val" } -onclose { set ONCLOSE [string map [list %w $qdlg] $val] lappend args $opt $ONCLOSE } -centerme - -ontop - -themed - -resizable - -checkgeometry - -comOK - -transient { lappend args $opt $val ;# options delegated to showModal method } -parent - -root { ;# obsolete, used for compatibility lappend args -centerme $val } -t - -text {set textmode $val} -tags { upvar 2 $val _tags set tags $_tags } -ro - -readonly {set readonly [set hidefind $val]} -rotext {set hidefind 0; set rotext $val} -w - -width {set charwidth $val} -h - -height {set charheight $val} -fg {append optsMisc " -foreground {$val}"} -bg {append optsMisc " -background {$val}"} -fgS {append optsMisc " -selectforeground {$val}"} -bgS {append optsMisc " -selectbackground {$val}"} -cc {append optsMisc " -insertbackground {$val}"} -my - -myown {append optsMisc " -myown {$val}"} -pos {set curpos "$val"} -hfg {append optsHead " -foreground {$val}"} -hbg {append optsHead " -background {$val}"} -hsz {append hsz " -size $val"} -minsize {set minsize "-minsize {$val}"} -focus {set focusmatch "$val"} -theme {append themecolors " {$val}"} -post {set postcom $val} -popup {set addpopup [string map [list %w $qdlg.fra.texM] "$val"]} -timeout - -focusback - -scroll - -tab2 - -stay - -modal - -waitvar { set [string range $opt 1 end] $val } -savetext {set savetext $val} default { append optsFont " $opt $val" if {$opt ne "-family"} { append optsFontM " $opt $val" } } } } if {[set wprev [::apave::InfoFind $wdia $modal]] ne {}} { catch { wm withdraw $wprev wm deiconify $wprev puts "$wprev already exists: selected now" } return 0 } set optsFont [string trim $optsFont] set optsHeadFont $optsFont set fs [my basicFontSize] set textfont "-family {[my basicTextFont]}" if {$optsFont ne {}} { if {[string first "-size " $optsFont]<0} { append optsFont " -size $fs" } if {[string first "-size " $optsFontM]<0} { append optsFontM " -size $fs" } if {[string first "-family " $optsFont]>=0} { set optsFont "-font \{$optsFont" } else { set optsFont "-font \{$optsFont -family {[my basicDefFont]}" } append optsFont "\}" } else { set optsFont "-font {[font actual apaveFontDef] -size $fs}" set optsFontM "-size $fs" } set msgonly [expr {$readonly || $hidefind || $chmsg ne {}}] if {!$textmode || $msgonly} { set textfont "-family {[my basicDefFont]}" if {!$textmode} { set msg [string map [list \\ \\\\ \{ \\\\\{ \} \\\\\}] $msg] } } set optsFontM [string trim $optsFontM] set optsFontM "-font \{$optsFontM $textfont\}" # layout: add the icon if {$icon ni {{} -}} { set widlist [list [list labBimg - - 99 1 \ {-st n -pady 7} "-image [::apave::iconImage $icon]"]] set prevl labBimg } else { set widlist [list [list labimg - - 99 1]] set prevl labimg ;# this trick would hide the prevw at all } set prevw labBimg if {$head ne {}} { # set the dialog's heading (-head option) if {$optsHeadFont ne {} || $hsz ne {}} { if {$hsz eq {}} {set hsz "-size [::apave::obj basicFontSize]"} set optsHeadFont [string trim "$optsHeadFont $hsz"] set optsHeadFont "-font \"$optsHeadFont\"" } set optsFont {} set prevp L set head [string map {\\n \n} $head] foreach lh [split $head "\n"] { set labh "labheading[incr il]" lappend widlist [list $labh $prevw $prevp 1 99 {-st we} \ "-t \"$lh\" $optsHeadFont $optsHead"] set prevw [set prevh $labh] set prevp T } } else { # add the upper (before the message) blank frame lappend widlist [list h_1 $prevw L 1 1 {-pady 3}] set prevw [set prevh h_1] set prevp T } # add the message lines set il [set maxw 0] if {$readonly && $rotext eq {}} { # only for messaging (not for editing/viewing texts): set msg [string map {\\\\n \\n \\n \n} $msg] } foreach m [split $msg \n] { set m [string map {$ \$ \" \'\'} $m] if {[set mw [string length $m]] > $maxw} { set maxw $mw } incr il if {!$textmode} { lassign [my GetLinkLab $m] m link lappend widlist [list Lab$il $prevw $prevp 1 7 \ "-st w -rw 1 $optsGrid" "-t \"$m \" $optsLabel $optsFont $link"] } set prevw Lab$il set prevp T } if {$inopts ne {}} { # here are widgets for input (in fraM frame) set io0 [lindex $inopts 0] lset io0 1 $prevh lset inopts 0 $io0 foreach io $inopts { lappend widlist $io } set prevw fraM } elseif {$textmode} { # here is text widget (in fraM frame) ; proc vallimits {val lowlimit isset limits} { set val [expr {max($val,$lowlimit)}] if {$isset} { upvar $limits lim lassign $lim l1 l2 set val [expr {min($val,$l1)}] ;# forced low if {$l2 ne {}} {set val [expr {max($val,$l2)}]} ;# forced high } return $val } set il [vallimits $il 1 [info exists charheight] charheight] incr maxw set maxw [vallimits $maxw 20 [info exists charwidth] charwidth] rename vallimits {} lappend widlist [list fraM $prevh T 10 12 {-st nswe -pady 3 -rw 1}] lappend widlist [list TexM - - 1 12 {pack -side left -expand 1 -fill both -in \ $qdlg.fra.fraM} [list -h $il -w $maxw {*}$optsFontM {*}$optsMisc \ -wrap word -textpop 0 -tabnext "$qdlg.fra.[lindex $buttons 0] *but0"]] if {$scroll} { lappend widlist {sbv texM L 1 1 {pack -in $qdlg.fra.fraM}} } set prevw fraM } # add the lower (after the message) blank frame lappend widlist [list h_2 $prevw T 1 1 {-pady 0 -ipady 0 -csz 0}] # underline the message lappend widlist [list seh $prevl T 1 99 {-st ew}] # add left frames and checkbox (before buttons) lappend widlist [list h_3 seh T 1 1 {-pady 0 -ipady 0 -csz 0}] if {$textmode} { # binds to the special popup menu of the text widget set wt "\[[self] TexM\]" set binds "set pop $wt.popupMenu bind $wt <Button-3> \{[self] themePopup $wt.popupMenu; tk_popup $wt.popupMenu %X %Y \}" if {$msgonly} { append binds " menu \$pop \$pop add command [my iconA copy] -accelerator Ctrl+C -label \"Copy\" \\ -command \"event generate $wt <<Copy>>\"" if {$hidefind || $chmsg ne {}} { append binds " \$pop configure -tearoff 0 \$pop add separator \$pop add command [my iconA none] -accelerator Ctrl+A \\ -label \"Select All\" -command \"$wt tag add sel 1.0 end\" bind $wt <Control-a> \"$wt tag add sel 1.0 end; break\"" } } } set appendHL no if {$chmsg eq {}} { if {$textmode} { set noIMG "[my iconA none]" if {$hidefind} { lappend widlist [list h__ h_3 L 1 4 {-cw 1}] } else { lappend widlist [list labfnd h_3 L 1 1 "-st e" "-t {$::apave::msgarray(find)}"] lappend widlist [list Entfind labfnd L 1 1 \ {-st ew -cw 1} "-tvar [namespace current]::Foundstr -w 10"] lappend widlist [list labfnd2 Entfind L 1 1 "-cw 2" "-t {}"] lappend widlist [list h__ labfnd2 L 1 1] append binds " bind \[[self] Entfind\] <Return> {[self] findInText} bind \[[self] Entfind\] <KP_Enter> {[self] findInText} bind \[[self] Entfind\] <FocusIn> {\[[self] Entfind\] selection range 0 end} bind $qdlg <F3> {[self] findInText 1} bind $qdlg <Control-f> \"[self] InitFindInText 1; focus \[[self] Entfind\]; break\" bind $qdlg <Control-F> \"[self] InitFindInText 1; focus \[[self] Entfind\]; break\"" } if {$readonly} { if {!$hidefind} { append binds " \$pop add separator \$pop add command [my iconA find] -accelerator Ctrl+F -label \\ \"Find First\" -command \"[self] InitFindInText; focus \[[self] Entfind\]\" \$pop add command $noIMG -accelerator F3 -label \"Find Next\" \\ -command \"[self] findInText 1\" $addpopup \$pop add separator \$pop add command [my iconA exit] -accelerator Esc -label \"Close\" \\ -command \"\[[self] paveoptionValue Defb1\] invoke\" " } else { set appendHL yes } } else { # make bindings and popup menu for text widget after idle "[self] set_highlight_matches \[[self] TexM\]" append binds " [my setTextBinds $wt] menu \$pop \$pop add command [my iconA cut] -accelerator Ctrl+X -label \"Cut\" \\ -command \"event generate $wt <<Cut>>\" \$pop add command [my iconA copy] -accelerator Ctrl+C -label \"Copy\" \\ -command \"event generate $wt <<Copy>>\" \$pop add command [my iconA paste] -accelerator Ctrl+V -label \"Paste\" \\ -command \"event generate $wt <<Paste>>\" [[self] popupBlockCommands \$pop $wt] [[self] popupHighlightCommands \$pop $wt] [[self] popupFindCommands \$pop $wt] $addpopup \$pop add separator \$pop add command [my iconA SaveFile] -accelerator Ctrl+W \\ -label \"Save and Close\" -command \"[self] res $qdlg 1\" " } set onclose "[namespace current]::exitEditor $qdlg" oo::objdefine [self] export InitFindInText } else { lappend widlist [list h__ h_3 L 1 4 {-cw 1}] } } else { lappend widlist [list chb h_3 L 1 1 \ {-st w} "-t {$chmsg} -var [namespace current]::CheckNomore"] lappend widlist [list h_ chb L 1 1] lappend widlist [list sev h_ L 1 1 {-st nse -cw 1}] lappend widlist [list h__ sev L 1 1] set appendHL $textmode } if {$appendHL} { after idle "[self] set_highlight_matches $wt" append binds " [[self] popupHighlightCommands \$pop $wt]" } # add the buttons lassign [my AppendButtons widlist $buttons h__ L $defb $timeout $qdlg $modal $ONCLOSE] \ bhelp bcomm # make the dialog's window set wtop [my makeWindow $qdlg.fra $ttl] if {$bhelp ne {}} { bind $qdlg <F1> $bcomm } # pave the dialog's window if {$tab2 eq {}} { set widlist [my paveWindow $qdlg.fra $widlist] } else { # pave with the notebook tabs (titl1 title2 [title3...] widlist2 [widlist3...]) lassign $tab2 ttl1 ttl2 widlist2 ttl3 widlist3 ttl4 widlist4 ttl5 widlist5 foreach nt {3 4 5} { set ttl ttl$nt set wdl widlist$nt if {[set _ [set $ttl]] ne {}} { set $ttl [list f$nt "-t {$_}"] set $wdl [list $qdlg.fra.nbk.f$nt "[set $wdl]"] } } set widlist0 [list [list nbk - - - - {pack -side top -expand 1 -fill both} [list \ f1 "-t {$ttl1}" \ f2 "-t {$ttl2}" \ {*}$ttl3 \ {*}$ttl4 \ {*}$ttl5 \ ]]] set widlist1 [list] foreach it $widlist { lassign $it w nei pos r c opt atr set opt [string map {$qdlg.fra $qdlg.fra.nbk.f1} $opt] lappend widlist1 [list $w $nei $pos $r $c $opt $atr] } set widlist [my paveWindow $qdlg.fra $widlist0 \ $qdlg.fra.nbk.f1 $widlist1 \ $qdlg.fra.nbk.f2 $widlist2 \ {*}$widlist3 \ {*}$widlist4 \ {*}$widlist5 \ ] set tab2 nbk.f1. } if {$precom ne {}} { {*}$precom ;# actions before showModal } if {$themecolors ne {}} { # themed colors are set as sequentional '-theme' args if {[llength $themecolors]==2} { # when only 2 main fb/bg colors are set (esp. for TKE) lassign [::apave::parseOptions $optsMisc -foreground black \ -background white -selectforeground black \ -selectbackground gray -insertbackground black] v0 v1 v2 v3 v4 # the rest colors should be added, namely: # tfg2 tbg2 tfgS tbgS tfgD tbgD tcur bclr help fI bI fM bM fW bW bHL2 lappend themecolors $v0 $v1 $v2 $v3 $v3 $v1 $v4 $v4 $v3 $v2 $v3 $v0 $v1 black #ffff9e $v1 } catch { my themeWindow $qdlg $themecolors no } } # after creating widgets - show dialog texts if any my SetGetTexts set $qdlg.fra $inopts $widlist lassign [my LowercaseWidgetName $qdlg.fra.$tab2$defb] focusnow if {$textmode} { set wtxt [my TexM] my displayTaggedText $wtxt msg $tags if {$defb eq "ButTEXT"} { if {$readonly} { lassign [my LowercaseWidgetName $Defb1] focusnow } else { set focusnow $wtxt catch "::tk::TextSetCursor $focusnow $curpos" foreach k {w W} \ {catch "bind $focusnow <Control-$k> {[self] res $qdlg 1; break}"} } } if {$readonly} { my readonlyWidget ::$wtxt true false } if {$savetext ne {}} { my askForSave $wtxt $savetext } } if {$focusmatch ne {}} { foreach w $widlist { lassign $w widname lassign [my LowercaseWidgetName $widname] wn rn if {[string match $focusmatch $rn]} { lassign [my LowercaseWidgetName $qdlg.fra.$wn] focusnow break } } } catch "$binds" set args [::apave::removeOptions $args -focus] set ::apave::querydlg $qdlg my showModal $qdlg -modal $modal -waitvar $waitvar -onclose $onclose \ -focus $focusnow -geometry $geometry {*}$minsize {*}$args if {![winfo exists $qdlg] || (!$modal && !$waitvar)} { return 0 } set pdgeometry [wm geometry $qdlg] # the dialog's result is defined by "pave res" + checkbox's value set res [set result [my res $qdlg]] set chv $CheckNomore if { [string is integer $res] } { if {$res && $chv} { incr result 10 } } else { set res [expr {$result ne {} ? 1 : 0}] if {$res && $chv} { append result 10 } } if {$textmode && !$readonly} { set focusnow [my TexM] set textcont [$focusnow get 1.0 end] if {$res && $postcom ne {}} { {*}$postcom textcont [my TexM] ;# actions after showModal } set textcont " [$focusnow index insert] $textcont" } else { set textcont {} } if {$res && $inopts ne {}} { my SetGetTexts get $qdlg.fra $inopts $widlist set inopts " [my GetVarsValues $widlist]" } else { set inopts {} } if {$textmode && $rotext ne {}} { set $rotext [string trimright [[my TexM] get 1.0 end]] } if {!$stay} { destroy $qdlg update # pause a bit and restore the old focus if {$focusback ne {} && [winfo exists $focusback]} { set w ".[lindex [split $focusback .] 1]" after 50 [list if "\[winfo exist $focusback\]" "focus -force $focusback" elseif "\[winfo exist $w\]" "focus $w"] } else { after 50 list focus . } } if {$wasgeo} { lassign [::apave::splitGeometry $pdgeometry] w h x y catch { # geometry option can contain pointer/root etc. if {abs($x-$gx)<30} {set x $gx} if {abs($y-$gy)<30} {set y $gy} } return [list $result ${w}x$h$x$y $textcont [string trim $inopts]] } return "$result$textcont$inopts" } # ________________________ EOC APaveDialog _________________________ # } # _____________________________ EOF _____________________________________ #