#!/usr/bin/tclsh # Part of MCU 8051 IDE ( http://mcu8051ide.sf.net ) ############################################################################ # Copyright (C) 2007-2009 by Martin Ošmera # # martin.osmera@gmail.com # # # # This program is free software; you can redistribute it and#or modify # # it under the terms of the GNU General Public License as published by # # the Free Software Foundation; either version 2 of the License, or # # (at your option) any later version. # # # # This program is distributed in the hope that it will be useful, # # but WITHOUT ANY WARRANTY; without even the implied warranty of # # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # # GNU General Public License for more details. # # # # You should have received a copy of the GNU General Public License # # along with this program; if not, write to the # # Free Software Foundation, Inc., # # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ############################################################################ # -------------------------------------------------------------------------- # DESCRIPTION # Provides autonome GUI component intented for writing ToDo list # -------------------------------------------------------------------------- class Todo { common buttonActiveBg {#2222FF} ;# Color for button representing currently active text tags common buttonSemiActBg {#8888FF} ;# - || - semi active tags # Normal font for messages text common todo_normal_font [font create \ -family ${Editor::fontFamily} \ -size -${Editor::fontSize} \ ] common normal_font [font create \ -family {helvetica} \ -size -12 -weight {normal} \ ] common bold_font [font create \ -family {helvetica} \ -size -12 -weight {bold} \ ] # List of used text tags common textTags { tag_bold tag_italic tag_overstrike tag_underline } # List of XML tags for text tags (above) common xmlTags { b i s u } # List "tagging" buttons common tagButtons { button_bold button_italic button_strike button_under } # List of self closing tags common selfCtags {bookmark} common set_shortcuts {} ;# Currently set shortcut bindigs common shortcuts_cat {todo} ;# Key shortcut categories related to this segment # ID of bookmark image common bookmarkImage ok # Definition of the popup menu common TODOMENU { {command {Undo} {Ctrl+Z} 0 "undo" {undo} "Undo last operaton"} {command {Redo} {Ctrl+Shift+Z} 2 "redo" {redo} "Take back last undo operation"} {separator} {command {Cut} {Ctrl+X} 2 "cut" {editcut} {}} {command {Copy} {Ctrl+C} 0 "copy" {editcopy} {}} {command {Paste} {Ctrl+V} 0 "paste" {editpaste} {}} {command {Clear} {} 1 "clear" {editdelete} {}} {separator} {command {Select all} {Ctrl+A} 0 "selectall" {} {}} {separator} {command {Find} {$todo:todo_find} 0 "find_dialog" {find} {}} {command {Find next} {$todo:todo_find_n} 5 "find_next" {down0} {}} {command {Find previous} {$todo:todo_find_p} 8 "find_prev" {up0} {}} {separator} {command {Bold text} {$todo:bold} 0 "bold" {text_bold} "Use bold font"} {command {Italic text} {$todo:italic} 0 "italic" {text_italic} "Use italic font"} {command {Striketrought text} {$todo:strike} 0 "strike" {text_strike} "Use striketrought font"} {command {Underline text} {$todo:under} 1 "under" {text_under} "Use underline font"} {separator} {command {Erase tags} {$todo:edrase} 0 "eraser" {eraser} "Clear rich text tags"} {command {Insert OK image} {$todo:insert} 1 "bookmark" {ok} "Insert image \"Ok\""} } # Variables related to object initialization private variable input_text ;# String: initial content of the text widget private variable gui_initialized 0 ;# Bool: GUI created # Other variables private variable main_frame ;# ID of main frame (text_widget; scrollbar; button_frame) private variable text_widget ;# ID of the text widget private variable scrollbar ;# ID of scrollbar private variable button_frame ;# ID of button frame (on left) private variable parent_container ;# ID of parent contaner (some frame) private variable button_bold ;# ID of button "Bold" private variable button_italic ;# ID of button "Italic" private variable button_strike ;# ID of button "Striketrought" private variable button_under ;# ID of button "Underline" private variable active_tags {} ;# Currently active tags private variable menu {} ;# ID of popup menu private variable editor_count 0 ;# Int: Counter of editor instances private variable file_notes_pagesManager ;# Widget: Pages Manager for the file notes private variable LIST_file_notes {} ;# List of Widget: Text widgets with the file notes private variable LIST_file_notes_name {} ;# List of Widget: Label with the file name private variable file_notes ;# Widget: File specific notepad private variable file_notes_name ;# Widget: Label above file specific notepad (file name) private variable file_notes_pages private variable notes_invisible_frm ;# Widget: Frame to show when file notes is hidden private variable paned_win ;# Widget: Paned window containing "File notes" and "Notes" private variable right_pane ;# Widget: Right part of the $paned_win private variable left_pane ;# Widget: Left part of the $paned_win # Bool: Paned window redraw procedure is in progress private variable redraw_pane_in_progress 0 # Bool: File Notes is visible private variable notes_visible [lindex $::CONFIG(FILE_NOTES) 0] # Int: File notes panel size private variable panel_size [lindex $::CONFIG(FILE_NOTES) 1] # Int: File notes panel size (value before it was hidden) private variable panel_size_last [lindex $::CONFIG(FILE_NOTES) 1] # Variables related to search bar private variable search_frame ;# Widget: Search bar frame private variable last_find_index ;# String: Index of last found occurence of the search string private variable search_string ;# String: Search string private variable search_string_length ;# Int: Length of the search string private variable search_entry ;# Widget: Search bar entry box private variable search_find_next ;# Widget: Button "Next" private variable search_find_prev ;# Widget: Button "Prev" ## Object constructor constructor {} { # Configure specific ttk styles ttk::style configure Todo_Active.TButton \ -background $buttonActiveBg \ -padding 0 \ -relief flat ttk::style map Todo_Active.TButton \ -relief [list active raised] \ -background [list disabled {#EEEEEE} active $buttonActiveBg] ttk::style configure Todo_SemiAct.TButton \ -background $buttonSemiActBg \ -padding 0 \ -relief flat ttk::style map Todo_SemiAct.TButton \ -relief [list active raised] \ -background [list disabled {#EEEEEE} active $buttonSemiActBg] } ## Object destructor destructor { # Remove dynamic help on status bar if {[winfo exists $menu]} { menu_Sbar_remove $menu } } ## Get configuration list for file specific notepad # @return void public method get_file_notes_config {} { if {$panel_size < $panel_size_last} { set panel_size_max $panel_size_last } { set panel_size_max $panel_size } return [list $notes_visible $panel_size_max] } ## Get contents of file specific notepad # @parm Int = - Index in $LIST_file_notes # @return void public method get_file_notes_data args { CreateTodoGUI if {$args == {}} { return [$file_notes get 1.0 {end-1l lineend}] } { set w [lindex $LIST_file_notes $args] if {$w == {}} { return {} } { return [$w get 1.0 {end-1l lineend}] } } } ## Set contents of file specific notepad # @parm String data - Text # @return void public method set_file_notes_data {data} { CreateTodoGUI $file_notes delete 0.0 end $file_notes insert end $data } ## Prepare object for creating its GUI # @parm Widget parentContainer - parent contaner (some frame) # @parm String inputText - initial content of the text widget # @return void public method PrepareTodo {parentContainer _input_text} { set parent_container $parentContainer set input_text $_input_text set gui_initialized 0 } ## Inform this tab than it has became active # @return void public method TodoTabRaised {} { focus $text_widget after idle "update; catch {$this todo_panel_redraw_pane}" } ## Add file specific notepad for a newly created editor # @parm String data - Text of the notes # @return void public method todo_add_Editor {data} { CreateTodoGUI # Create a new page in the pages manager for the file specific notepad set frm [$file_notes_pagesManager add $editor_count] # Create top and bottom frame set tf [frame $frm.t] set bf [frame $frm.b] # Create widgets for the top frame pack [label $tf.hl -text [mc "Notes for file:"] -font $normal_font] -side left set file_notes_name [label $tf.file_name -font $bold_font] pack $file_notes_name -side left pack [ttk::button $tf.hide_button \ -image ::ICONS::16::2rightarrow \ -command "$this todo_file_notes_show_hide 0" \ -style Flat.TButton \ ] -side right # Create widgets for the bottom frame set file_notes [text $bf.text \ -font $todo_normal_font -undo 1 -bg white \ -selectbackground {#AAFFAA} -selectborderwidth 1 \ -selectforeground {#000000} -highlightthickness 0 \ -yscrollcommand "$bf.scrollbar set" \ -wrap word -width 1 -height 1 -tabstyle wordprocessor \ ] pack $file_notes -fill both -expand 1 -side left pack [ttk::scrollbar $bf.scrollbar \ -command "$file_notes yview" \ -orient vertical \ ] -fill y -side right # Pack frames pack $tf -side top -anchor nw -fill x pack $bf -side bottom -fill both -expand 1 # Register the newly created widgets lappend file_notes_pages $editor_count lappend LIST_file_notes $file_notes lappend LIST_file_notes_name $file_notes_name set_file_notes_data $data incr editor_count } ## Redraw panel (move pane sash) acorning to current value of $PanelSize # @return void public method todo_panel_redraw_pane {} { if {!$gui_initialized} {return} if {$redraw_pane_in_progress} { after 50 "$this todo_panel_redraw_pane" return } set redraw_pane_in_progress 1 update $paned_win sash place 0 [expr {${::WIN_GEOMETRY_width} - $panel_size}] 0 update set redraw_pane_in_progress 0 } ## Set panel width acording to current sash position # @return void public method todo_panel_set_size {} { set panel_size [lindex [$paned_win sash coord 0] 0] set panel_size [expr {${::WIN_GEOMETRY_width} - $panel_size}] } ## Show or hide the file specific notepad # @parm Bool show__hide - 1 == Show; 0 == Hide # @return void public method todo_file_notes_show_hide {show__hide} { CreateTodoGUI set notes_visible $show__hide # Show if {$notes_visible} { pack forget $notes_invisible_frm pack $file_notes_pagesManager -fill both -expand 1 $paned_win paneconfigure $right_pane -minsize 200 set panel_size $panel_size_last # Hide } { pack forget $file_notes_pagesManager pack $notes_invisible_frm -fill y -anchor nw $paned_win paneconfigure $right_pane -minsize 20 set panel_size_last $panel_size set panel_size 60 } todo_panel_redraw_pane } ## Remove file specific notes # @parm Int idx - Editor index # @return void public method todo_remove_editor {idx} { CreateTodoGUI $file_notes_pagesManager delete [lindex $file_notes_pages $idx] set file_notes_pages [lreplace $file_notes_pages $idx $idx] set LIST_file_notes [lreplace $LIST_file_notes $idx $idx] set LIST_file_notes_name [lreplace $LIST_file_notes_name $idx $idx] } ## Switch file specific notes to another editor # @parm Int idx - Editor index # @return void public method todo_switch_editor {idx} { CreateTodoGUI set file_notes_name [lindex $LIST_file_notes_name $idx] set file_notes [lindex $LIST_file_notes $idx] $file_notes_pagesManager raise [lindex $file_notes_pages $idx] } ## Change file name shown in the file specific notes to another string # @parm Int idx - Editor index # @parm String newname - New file name # @return void public method todo_change_filename {idx newname} { $file_notes_name configure -text $newname } ## Swithc some variables related to the file specific notes to another editor # @parm Inr idx - Editor index # @return void public method todo_switch_editor_vars {idx} { CreateTodoGUI set file_notes_name [lindex $LIST_file_notes_name $idx] set file_notes [lindex $LIST_file_notes $idx] } ## Set text for thec urrent file specific notepad # @parm String text - The text # @return void public method todo_file_notes_set_text {text} { $file_notes delete 0.0 end $file_notes insert end $text } ## Get text from the file specific notepad # @return void public method todo_file_notes_get_text {} { return [$file_notes get 0.0 end] } ## Initialize todo text # @return void public method CreateTodoGUI {} { if {$gui_initialized} {return} set gui_initialized 1 set paned_win [panedwindow $parent_container.paned_win \ -sashwidth 4 -showhandle 0 -opaqueresize 1 -orient horizontal \ ] bind $paned_win "$this todo_panel_set_size" set left_pane [frame $paned_win.l] set right_pane [frame $paned_win.r] $paned_win add $left_pane $paned_win add $right_pane $paned_win paneconfigure $left_pane -minsize 200 $paned_win paneconfigure $right_pane -minsize 200 # # RIGHT PART # set file_notes_pagesManager [PagesManager $right_pane.pmgr -background {#eeeeee}] $file_notes_pagesManager compute_size set notes_invisible_frm [frame $right_pane.notes_invisible_frm] pack [ttk::button $notes_invisible_frm.hide_button \ -image ::ICONS::16::2leftarrow \ -command "$this todo_file_notes_show_hide 1" \ -style Flat.TButton \ ] -anchor nw todo_file_notes_show_hide $notes_visible # # LEFT PART # # Create GUI components in main frame set main_frame [frame $left_pane.main_frame] set text_widget [text $main_frame.todo_text \ -yscrollcommand "$main_frame.todo_text_scrl set" \ -font $todo_normal_font -undo 1 -bg white \ -selectbackground {#AAFFAA} -selectborderwidth 1 \ -selectforeground {#000000} -highlightthickness 0 \ -wrap word -width 1 -height 1 -tabstyle wordprocessor \ ] set scrollbar [ttk::scrollbar $main_frame.todo_text_scrl \ -command "$text_widget yview" \ -orient vertical \ ] set button_frame [frame $main_frame.todo_text_bframe] # Pack GUI of main frame pack $button_frame -side left -anchor n pack $text_widget -fill both -expand 1 -side left pack $scrollbar -fill y -side right ## Create GUI components in search bar frame set search_frame [frame $left_pane.search_frame] # Search entry box set search_entry [ttk::entry $search_frame.entry \ -width 30 \ -validate all \ -validatecommand "$this TodoProc_search %P" \ ] bind $search_entry "$this TodoProc_hide_find_dialog" # Button: "Next" set search_find_next [ttk::button $search_frame.find_next_but \ -image ::ICONS::16::down0 \ -command "$this TodoProc_find_next" \ -state disabled \ -style Flat.TButton \ ] DynamicHelp::add $search_frame.find_next_but \ -text [mc "Find next occurence of search string"] # Button: "Prev" set search_find_prev [ttk::button $search_frame.find_prev_but \ -image ::ICONS::16::up0 \ -command "$this TodoProc_find_prev" \ -state disabled \ -style Flat.TButton \ ] DynamicHelp::add $search_frame.find_prev_but \ -text [mc "Find previous occurence of search string"] # Button: "Close" pack [ttk::button $search_frame.close_but \ -image ::ICONS::16::button_cancel \ -command "$this TodoProc_hide_find_dialog" \ -style Flat.TButton \ ] -side left DynamicHelp::add $search_frame.close_but \ -text [mc "Hide search bar"] # Separator pack [ttk::separator $search_frame.sep \ -orient vertical \ ] -fill y -padx 5 -side left -pady 2 # Label: "Find" pack [label $search_frame.find_lbl \ -text [mc "Find:"] \ ] -side left # Pack entry and buttons next and prev pack $search_entry -side left pack $search_find_next -side left -padx 5 pack $search_find_prev -side left # Checkbutton: "Match case" pack [checkbutton $search_frame.match_case_chb \ -text [mc "Match case"] \ -variable ::Todo::match_case \ -command "$this TodoProc_perform_search 1 1.0" \ ] -side left -padx 5 # Pack main frame pack $main_frame -fill both -expand 1 # Adjust text widget parameters TodoProc_write_text_from_sgml $input_text unset input_text $text_widget edit modified 0 $text_widget edit reset # create events bindings foreach key [bind Text] { bind $text_widget $key {continue} } bind $text_widget <> "$this TodoProc_undo; break" bind $text_widget <> "$this TodoProc_redo; break" foreach key { } { bind $text_widget $key " [bind Text $key] $this recalc_left_panel insert break" } bind $text_widget "break" bind $text_widget "$this TodoProc_Key %A; break" bind $text_widget "$this TodoProc_leftClick %x %y" bind $text_widget "$this TodoProc_leftRelease" bind $text_widget "$this TodoProc_popupMenu %X %Y %x %y; break" bind $text_widget "$this TodoProc_Key_Menu; break" TodoProc_shortcuts_reevaluate ## Create button bar # Button "Bold" set button_bold [ttk::button $button_frame.todo_text_bB \ -image ::ICONS::16::text_bold \ -command "$this TodoProc_bold" \ -style Flat.TButton \ ] DynamicHelp::add $button_bold -text [mc "Bold font"] setStatusTip -widget $button_bold \ -text {Use bold font} # Button "Italic" set button_italic [ttk::button $button_frame.todo_text_bI \ -image ::ICONS::16::text_italic \ -command "$this TodoProc_italic" \ -style Flat.TButton \ ] DynamicHelp::add $button_italic \ -text [mc "Italic text"] setStatusTip -widget $button_italic \ -text {Use italic font} # Button "Striketrought" set button_strike [ttk::button $button_frame.todo_text_bS \ -image ::ICONS::16::text_strike \ -command "$this TodoProc_strike" \ -style Flat.TButton \ ] DynamicHelp::add $button_strike \ -text [mc "Striketrought font"] setStatusTip -widget $button_strike -text {Use striketrought font} # Button "Underline" set button_under [ttk::button $button_frame.todo_text_bU \ -image ::ICONS::16::text_under \ -command "$this TodoProc_under" \ -style Flat.TButton \ ] DynamicHelp::add $button_under \ -text [mc "Underline font"] setStatusTip -widget $button_under \ -text {Use underline font} # pack these buttons foreach wdg $tagButtons { pack [subst "\$$wdg"] } # Button "Eraser" pack [ttk::button $button_frame.todo_text_bE \ -image ::ICONS::16::eraser \ -command "$this TodoProc_eraser" \ -style Flat.TButton \ ] DynamicHelp::add $button_frame.todo_text_bE \ -text [mc "Erase text tags"] setStatusTip -widget $button_frame.todo_text_bE \ -text {Remove formating tags within selected area} # Button "Bookmark" pack [ttk::button $button_frame.todo_text_bBm \ -image ::ICONS::16::$bookmarkImage \ -command "$this TodoProc_bookmark" \ -style Flat.TButton \ ] DynamicHelp::add $button_frame.todo_text_bBm \ -text [mc "Insert OK image"] setStatusTip -widget $button_frame.todo_text_bBm \ -text [mc "Insert \"Ok\" image at the current cursor position"] # create popup menu set menu $text_widget.todo_menu TodoProc_makePopupMenu # Create text tags and set main font todo_refresh_font_settings pack $paned_win -fill both -expand 1 } ## Recreate all text tags and font font for the text widget # @return void public method todo_refresh_font_settings {} { if {!$gui_initialized} {CreateTodoGUI} $text_widget configure -font [font create \ -family ${Editor::fontFamily} \ -size -${Editor::fontSize} \ ] todo_create_tags } ## Create bindings for defined key shortcuts # @return void public method TodoProc_shortcuts_reevaluate {} { if {!$gui_initialized} {CreateTodoGUI} # Unset previous configuration foreach key $set_shortcuts { bind $text_widget <$key> {} } set set_shortcuts {} # Iterate over shortcuts definition foreach block ${::SHORTCUTS_LIST} { # Determinate category set category [lindex $block 0] if {[lsearch $shortcuts_cat $category] == -1} {continue} # Determinate definition list and its length set block [lreplace $block 0 2] set len [llength $block] # Iterate over definition list and create bindings for {set i 0; set j 1} {$i < $len} {incr i 2; incr j 2} { # Determinate key sequence set key [lindex $block $i] if {[catch { set key $::SHORTCUTS_DB($category:$key) }]} then { continue } if {$key == {}} {continue} # Create and register new binding lappend set_shortcuts $key set cmd [subst [lindex $block [list $j 1]]] append cmd {;break} bind $text_widget <$key> $cmd bind $search_entry <$key> $cmd } } } ## Create popup menu # @return void public method TodoProc_makePopupMenu {} { if {!$gui_initialized} {return} if {[winfo exists $menu]} { destroy $menu } menuFactory $TODOMENU $menu 0 "$this TodoProc_" 0 {} $menu entryconfigure [::mc "Find next"] -state disabled $menu entryconfigure [::mc "Find previous"] -state disabled } ## Create text tags in todo text widget # @return void private method todo_create_tags {} { # Tag "Bold" $text_widget tag configure tag_bold \ -font [font create -size -${Editor::fontSize} \ -weight bold -family ${Editor::fontFamily} \ ] # Tag "Italic" $text_widget tag configure tag_italic \ -font [font create -size -${Editor::fontSize} \ -slant italic -family ${Editor::fontFamily} \ ] # Tag "Underline" $text_widget tag configure tag_underline -underline 1 # Tag "Overstrike" $text_widget tag configure tag_overstrike -overstrike 1 } ## Invoke the popup menu # @parm Int X - Absolute x coordinate # @parm Int Y - Absolute y coordinate # @parm Int x - Relative x coordinate # @parm Int y - Relative y coordinate # @return void public method TodoProc_popupMenu {X Y x y} { popup_menu_disena tk_popup $menu $X $Y $text_widget mark set insert @$x,$y } ## Handles event: 'Menu' -- invoke popup menu # @return void public method TodoProc_Key_Menu {} { popup_menu_disena $text_widget see insert set bbox [$text_widget bbox [$text_widget index insert]] tk_popup $menu \ [expr {[winfo rootx $text_widget] + [lindex $bbox 0] + 10}] \ [expr {[winfo rooty $text_widget] + [lindex $bbox 1] + 10}] } ## Enable/Disable popup menu items acording to state of the text widget # Auxiliary procedure for 'TodoProc_popupMenu' and 'TodoProc_Key_Menu' # @return void private method popup_menu_disena {} { set state [$text_widget cget -state] if {[llength [$text_widget tag nextrange sel 1.0]]} { if {$state != {disabled}} { $menu entryconfigure [::mc "Cut"] -state normal } $menu entryconfigure [::mc "Copy"] -state normal } { $menu entryconfigure [::mc "Cut"] -state disabled $menu entryconfigure [::mc "Copy"] -state disabled } foreach entry {Undo Redo Paste Clear} { $menu entryconfigure [::mc $entry] -state $state } } ## Write text to the text widget from SGML formated data # @parm String inputData - SGML data # @return void public method TodoProc_write_text_from_sgml {inputData} { if {!$gui_initialized} {CreateTodoGUI} # Replace all \r\n shit with LF regsub -all {(\r)|(\r\n)} $inputData "\n" inputData # Insert plain text set plainText [regsub -all {<[^<>]*>} $inputData {}] regsub -all {<} $plainText {<} plainText regsub -all {>} $plainText {>} plainText $text_widget insert end $plainText # Convert entities to spaces regsub -all {<} $inputData { } inputData regsub -all {>} $inputData { } inputData ## Parse pair tags set lastStartIdx 0 set lastEndIdx 0 foreach xmltag $xmlTags texttag $textTags { # modify input data for later processing set data $inputData foreach tag $xmlTags { if {$tag == $xmltag} {continue} regsub -all "<$tag>" $data {} data regsub -all "" $data {} data regsub -all "" $data {} data } # Translate XML tags to Tk's native text tags set StartRow 1 set EndCol 0 while 1 { set SRow 0 set ERow 0 set tagLength [string length "<$xmltag>"] set startIdx [string first "<$xmltag>" $data] if {$startIdx == -1} {break} set LFidx 0 set LastLFidx $LFidx while 1 { set LFidx [string first "\n" $data $LFidx] if {($LFidx >= $startIdx) || ($LFidx == -1)} { set correction 0 if {$SRow == 0} { set correction $EndCol incr correction } set StartCol [expr {$startIdx - $LastLFidx - 1 + $correction}] set StartRow [expr {$StartRow + $SRow}] break } { set LastLFidx $LFidx incr SRow incr LFidx } } set EndRow $StartRow set data [string range $data [expr {$tagLength + $startIdx}] end] set endIdx [string first "" $data] if {$endIdx == -1} {break} set LFidx 0 set LastLFidx $LFidx while 1 { set LFidx [string first "\n" $data $LFidx] if {($LFidx >= $endIdx) || ($LFidx == -1)} { set correction 0 if {$ERow == 0} {set correction $StartCol} set EndCol [expr {$endIdx - $LastLFidx - $ERow + $correction}] set EndRow [expr {$EndRow + $ERow}] break } { set LastLFidx $LFidx incr ERow incr LFidx } } set data [string range $data [expr {$tagLength + $endIdx + 1}] end] if {($StartRow == $EndRow) && ($StartCol >= $EndCol)} {break} $text_widget tag add $texttag $StartRow.$StartCol $EndRow.$EndCol set StartRow $EndRow } } ## Parse non pair tags # modify input data for later processing set data $inputData regsub -all {<[^<>]*[^/]>} $data {} data append data "\n" foreach tag $selfCtags { set Row 1 set Col 0 while 1 { set tagIdx [string first "<$tag" $data] set tagEndIdx [string first "/>" $data] if {($tagEndIdx < $tagIdx) || ($tagIdx == -1)} {break} set tagEndIdx [expr {$tagEndIdx + 2}] set rowTmp 0 set LFidx 0 set LastLFidx $LFidx while 1 { set LFidx [string first "\n" $data $LFidx] if {$LFidx >= $tagIdx} { set correction 0 if {$rowTmp == 0} {set correction [expr {$Col + 2}]} set Col [expr {$tagIdx - $LastLFidx - 1 + $correction}] set Row [expr {$Row + $rowTmp}] break } { set LastLFidx $LFidx incr rowTmp incr LFidx } } set data [string range $data $tagEndIdx end] switch $tag { {bookmark} { $text_widget delete $Row.$Col $Row.$Col+1c $text_widget image create $Row.$Col -image ::ICONS::16::$bookmarkImage } } } } } ## Return content of text widget formated as SGML # @return String - SGML code public method TodoProc_read_text_as_sgml {} { if {!$gui_initialized} {return $input_text} # Determinate end index set textEnd [$text_widget index end] set textEnd [expr {int($textEnd)}] # Determinate length of each line set lineIndex(1) -1 set sum -1 for {set i 1; set i0 2} {$i < $textEnd} {incr i; incr i0} { set lineend [$text_widget index "$i.0 lineend"] regexp {\d+$} $lineend lineend incr sum [expr {$lineend + 1}] set lineIndex($i0) $sum } # Determinate tag indexes set tagList {} foreach xmltag $xmlTags texttag $textTags { set ranges [$text_widget tag ranges $texttag] set i 0 set index {} while 1 { set index [lindex $ranges $i] if {$index == {}} {break} lappend tagList "$index $xmltag" incr i lappend tagList "[lindex $ranges $i] /$xmltag" incr i } } # Extract plain text data from the text widget set data [$text_widget get 1.0 end] # Determinate images indexes and adjust lines idxs set imageIdxs {} foreach imageName [$text_widget image names] { lappend imageIdxs [$text_widget index $imageName] } set imageIdxs [lsort -command "$this read_text_as_sgml_aux_compare 0" $imageIdxs] set lastRow 0 set col 0 set row 0 set index 0 foreach imageIdx $imageIdxs { scan $imageIdx %d.%d row col set index [expr {$lineIndex($row) + $col}] set data [string replace $data $index $index "[string index $data $index] "] if {$row == $lastRow} { regexp {\d+$} $imageIdx col incr colCorrection -1 incr col $colCorrection set imageIdx "$row.$col" } { set lastRow $row set colCorrection 0 } lappend tagList "$imageIdx bookmark/" } # Special reverse sorting of tag list set tagList [lsort -command "$this read_text_as_sgml_aux_compare 1" -index 0 $tagList] # Traslate angle brackets to some special characters regsub -all {<} $data "\a" data ;# '<' -> alert regsub -all {>} $data "\b" data ;# '>' -> backspace # Insert SGML tags into plain text data foreach xmlTagIdx $tagList { set index [lindex $xmlTagIdx 0] set tag [lindex $xmlTagIdx 1] scan $index %d.%d row col set index [expr {$lineIndex($row) + $col}] if {$index == -1} { incr index set char [string index $data $index] set data [string replace $data $index $index "<$tag>$char"] } { set char [string index $data $index] set data [string replace $data $index $index "$char<$tag>"] } } # Traslate angle brackets back regsub -all {\a} $data {\<} data ;# alert -> '<' regsub -all {\b} $data {\>} data ;# backspace -> '> regsub -all -line {[  \t]+$} $data {} data regsub -all {<[^<>]+>\n]+>} $data "\n" data # return final SGML return $data } ## Special comparation for text indexes # @parm Bool reverse - Invert result # @parm TextIndex first - Firts index # @parm TextIndex second - Second index # @return Int - result, one of {-1 0 1} public method read_text_as_sgml_aux_compare {reverse first second} { # Set return values if {$reverse} { set A -1 set B 1 } { set A 1 set B -1 } # Determinate First/End Row/Column regexp {^\d+} $first FR ;# First Row regexp {\d+$} $first FC ;# First Column regexp {^\d+} $second ER ;# End Row regexp {\d+$} $second EC ;# End Column # Perform comparation if {$FR > $ER} { return $A } elseif {$FR == $ER} { if {$FC > $EC} { return $A } elseif {$FC == $EC} { return 0 } { return $B } } else { return $B } } ## Get content of text widget as plain text # @return String - result public method read_plain_text {} { if {!$gui_initialized} {CreateTodoGUI} return [$text_widget get 1.0] } ## Switch to bold font # @return void public method TodoProc_bold {} { addRemoveTag tag_bold $button_bold } ## Switch to italic font # @return void public method TodoProc_italic {} { addRemoveTag tag_italic $button_italic } ## Switch to striketrought font # @return void public method TodoProc_strike {} { addRemoveTag tag_overstrike $button_strike } ## Switch to underline font # @return void public method TodoProc_under {} { addRemoveTag tag_underline $button_under } ## Erase tags # @parm List idxs = {} - Indexes of selected area # @parm Bool reset = 1 - Reset font settings on left panel # @return Bool - result public method TodoProc_eraser args { if {$args == {}} { set idxs [getSelectionIdx] set reset 1 set active_tags {} } { set idxs [lindex $args 0] set reset [lindex $args 1] } if {$idxs == {}} { set active_tags {} reset_left_panel return 0 } foreach tag $textTags { $text_widget tag remove $tag [lindex $idxs 0] [lindex $idxs 1] } if {$reset} {reset_left_panel} return 1 } ## Select all text in the text widget # @return void public method TodoProc_selectAll {} { $text_widget tag add sel 1.0 end } ## Insert bookmark image at current cursor position # @return void public method TodoProc_bookmark {} { set idx [$text_widget index insert] $text_widget image create $idx -image ::ICONS::16::$bookmarkImage } ## Take back last operation # @return void public method TodoProc_undo {} { catch { $text_widget edit undo } } ## Take back last undo # @return void public method TodoProc_redo {} { catch { $text_widget edit redo } } ## Cut selected text # @return void public method TodoProc_cut {} { if {![llength [$text_widget tag nextrange sel 1.0]]} {return} clipboard clear clipboard append [$text_widget get sel.first sel.last] $text_widget delete sel.first sel.last } ## Copy selected text to clipboard # @return void public method TodoProc_copy {} { if {![llength [$text_widget tag nextrange sel 1.0]]} {return} clipboard clear clipboard append [$text_widget get sel.first sel.last] } ## Paste text from clipboard # @return void public method TodoProc_paste {} { if {[catch { set data [clipboard get] }]} { return } catch {$text_widget delete sel.first sel.last} $text_widget insert insert $data } ## Clear all text # @return void public method TodoProc_clear {} { if {!$gui_initialized} {CreateTodoGUI} catch {$text_widget delete 1.0 end} } ## Select all text # @return void public method TodoProc_selectall {} { catch {$text_widget tag add sel 1.0 end} } ## Handles key press # @parm String key - ID of pressed key # @return void public method TodoProc_Key {key} { # Skip values with no meaning if {[string length $key] != 1} {return} # Delete seleced text catch { $text_widget delete sel.first sel.last } # Get text index before change regexp {\d+$} [$text_widget index insert] col0 incr col0 # Change content of the text widget $text_widget insert insert $key # Get text index after change regexp {\d+$} [$text_widget index insert] col1 # Apply active tags if {$col0 == $col1} { foreach tag $active_tags { $text_widget tag add $tag {insert-1c} insert } } } ## Manage left panel, must be called after each click to the text widget # @parm Int x - relative x coordinate # @parm Int y - relative y coordinate # @return void public method TodoProc_leftClick {x y} { recalc_left_panel [$text_widget index @$x,$y] } ## Determinate active tags in the selected area, should be called after each LeftRelease # @return Bool - result public method TodoProc_leftRelease {} { # Determinate start and end index of selected region set idxs [getSelectionIdx] # If nothing selected -> return False if {$idxs == {}} {return 0} # Local variables regexp {^\d+} [lindex $idxs 0] StartRow ;# Row of start index regexp {^\d+} [lindex $idxs 1] EndRow ;# Row of end index regexp {\d+$} [lindex $idxs 0] StartCol ;# Column of start index regexp {\d+$} [lindex $idxs 1] EndCol ;# Column of end index # Object variables set active_tags {} ;# Curretly active tags set semiActiveTags {} ;# Currently semi-active tags # Iterate over rows of selected region for {set row $StartRow} {$row <= $EndRow} {incr row} { # Iterate over columns of selected reegion for {set col $StartCol} {$col <= $EndCol} {incr col} { # Determinate list of active tags at the current index set tags [$text_widget tag names $row.$col-1c] # Append these tags to content of object variable active_tags foreach tag $tags { if {$tag == {sel}} { continue } if {[lsearch $active_tags $tag] == -1 && [lsearch $semiActiveTags $tag] == -1} { lappend active_tags $tag } } # Determinate semi-active tags foreach tag $active_tags { if {[lsearch $tags $tag] == -1} { set i [lsearch $active_tags $tag] set repleceTag [lindex $active_tags $i] lappend semiActiveTags $tag set active_tags [lreplace $active_tags $i $i] } } } } # Restore highlight of left panel buttons reset_left_panel foreach tag $active_tags { set_button_bg_by_tag_name $tag 1 } foreach tag $semiActiveTags { set_button_bg_by_tag_name $tag 2 } # done ... return 1 } ## Set background color for button related to given text tag acording to given state # @parm String tag - name of text tag # @parm Int state - state number (0 == passive; 1 == active; 2 == semi-active) # @return void private method set_button_bg_by_tag_name {tag state} { switch $tag { {tag_bold} "setButtonBg $button_bold $state" {tag_italic} "setButtonBg $button_italic $state" {tag_overstrike} "setButtonBg $button_strike $state" {tag_underline} "setButtonBg $button_under $state" } } ## Reevaluate background color for each button on left panel # @parm TextIndex index - index in the text widget # @return void public method recalc_left_panel {index} { # Determinate list of active tags set active_tags [$text_widget tag names $index-1c] # Remove tag sel from the list set i [lsearch -ascii -exact $active_tags {sel}] if {$i != -1} { set active_tags [lreplace $active_tags $i $i] } # No active tags -> reset panel and return if {$active_tags == {}} { reset_left_panel return } # Determinate list of buttons related to active tags set affected {} foreach tag $active_tags { switch $tag { {tag_bold} { ;# Bold font if {[lsearch $affected button_bold] != -1} {continue} lappend affected button_bold } {tag_italic} { ;# Italic font if {[lsearch $affected button_italic] != -1} {continue} lappend affected button_italic } {tag_overstrike} { ;# Overstrike font if {[lsearch $affected button_strike] != -1} {continue} lappend affected button_strike } {tag_underline} { ;# Underline font if {[lsearch $affected button_under] != -1} {continue} lappend affected button_under } } } # Set background color for each button on left panel foreach button $tagButtons { # Determinate ID of button widget set buttonWdg [subst "\$$button"] # Determinate state number and set Bg if {[lsearch $affected $button] != -1} { setButtonBg $buttonWdg 1 } { setButtonBg $buttonWdg 0 } } } ## Set default background color for each button on the left panel # @return void private method reset_left_panel {} { foreach button $tagButtons { setButtonBg [subst "\$$button"] 0 } } ## Set background color for given button acording to given state # @parm Widget button - ID of button to modify # @parm Int state - state number (0 == passive; 1 == active; 2 == semi-active) # @return void private method setButtonBg {button state} { switch $state { 0 {set style {Flat.TButton}} 1 {set style {Todo_Active.TButton}} 2 {set style {Todo_SemiAct.TButton}} } $button configure -style $style } ## Use given text tag (for selection or next characters) # @parm String tagName - name of text tag # @parm Widget buttonName - ID of button related to that tag # @return void private method addRemoveTag {tagName buttonName} { # Index of given tag in list of active tags set tagIdx [lsearch $active_tags $tagName] # Tag is inactive -> add the tag if {$tagIdx == -1} { setButtonBg $buttonName 1 lappend active_tags $tagName # Tag is active -> remove the tag } { setButtonBg $buttonName 0 set active_tags [lreplace $active_tags $tagIdx $tagIdx] } # Modify selected area set idxs [getSelectionIdx] if {$idxs != {}} { TodoProc_eraser $idxs 0 foreach tag $active_tags { setTagAtSel $tag $idxs } } } ## Set given text tag for area determinated by given indexes # @parm String tagName - name of text tag to set # @parm List idxs = {} - target area {first_idx last_idx} # @return Bool - result private method setTagAtSel {tagName idxs} { if {$idxs == {}} {return 0} $text_widget tag add $tagName [lindex $idxs 0] [lindex $idxs 1] return 1 } ## Get list of indexes of selected area # @return List - text indexes '{first last}' or '{}' private method getSelectionIdx {} { # Try to determinate indexes catch { set start [$text_widget index sel.first] set end [$text_widget index sel.last] } # Return result if {[info exists start]} { return "$start $end" } { return {} } } ## Hide search bar # @return void public method TodoProc_hide_find_dialog {} { if {[winfo ismapped $search_frame]} { pack forget $search_frame } } ## Show search bar # @return void public method TodoProc_find_dialog {} { if {![winfo ismapped $search_frame]} { pack $search_frame -before $main_frame -side top -anchor w $search_entry delete 0 end focus -force $search_entry } { focus -force $search_entry } } ## Search for the given string within the text # @parm String string - Text to find # @return Bool - Always 1 public method TodoProc_search {string} { if {$string == {}} { $search_entry configure -style TEntry $search_find_next configure -state disabled $search_find_prev configure -state disabled $menu entryconfigure [::mc "Find next"] -state disabled $menu entryconfigure [::mc "Find previous"] -state disabled return 1 } set search_string $string TodoProc_perform_search 1 1.0 return 1 } ## Perform search for $search_string in the text widget # @parm Bool forw__back - 1 == Search forwards; 0 == Search backard # @parm String from - Start index # @return void public method TodoProc_perform_search {forw__back from} { if {$search_string == {}} {return} if {$forw__back} { set direction {-forwards} } { set direction {-backwards} } if {${::Todo::match_case}} { set last_find_index [$text_widget search $direction -- $search_string $from] } { set last_find_index [$text_widget search $direction -nocase -- $search_string $from] } if {$last_find_index == {}} { $search_entry configure -style StringNotFound.TEntry $search_find_next configure -state disabled $search_find_prev configure -state disabled $menu entryconfigure [::mc "Find next"] -state disabled $menu entryconfigure [::mc "Find previous"] -state disabled } { $search_entry configure -style StringFound.TEntry $search_find_next configure -state normal $search_find_prev configure -state normal $menu entryconfigure [::mc "Find next"] -state normal $menu entryconfigure [::mc "Find previous"] -state normal set search_string_length [string length $search_string] $text_widget see $last_find_index catch { $text_widget tag remove sel 0.0 end } $text_widget tag add sel $last_find_index $last_find_index+${search_string_length}c } } ## Find next occurence of the search string # @return void public method TodoProc_find_next {} { if {![winfo ismapped $search_frame]} { pack $search_frame -before $main_frame -side top -anchor w } if {$last_find_index == {}} { return } TodoProc_perform_search 1 $last_find_index+${search_string_length}c } ## Find previous occurence of the search string # @return void public method TodoProc_find_prev {} { if {![winfo ismapped $search_frame]} { pack $search_frame -before $main_frame -side top -anchor w } if {$last_find_index == {}} { return } TodoProc_perform_search 0 $last_find_index } }