Tabbed/Notebook Menus

WJG (03/03/18) At home my family prefer Word to LibreOffice, never mind. The main reason they prefer it is nothing at all to with functionality. They don't remember where embedded menu options are so prefer the switchable toolbars that Word has. So, I thought that I'd put something together like this as a simple text editor in Gnocl. At first I wasn't convinced of the speed benefits but now I am. Less clutter on screen, more space for the edit widget and no waiting for menus to build themselves. Added to this, the programming was so much less complicated. I saw this mixture of tabbed menus and pull-down menus first in the Maya CGI animation system back in the 1990s, an application with a lot of widgets. So, perhaps this UI paradigm is worth a second glance.

The application uses Gnocl rather than Tk to create the UI. The script takes advantage of the widget -name option to specify objects rather than using their widget-ids. Makes life a lot easier! Bye-bye global declarations and namespaces.

Finally, no need to create custom tags; the Gnocl text widget does it for you with the -useMarkup option to create a key set of Pango/HTML markup tags.

WikiDBImage tabbedMenu.png

##  test-menu-tabs.tcl

#!/bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" "$@"

if { [catch { package present Gnocl } ] } { package require Gnocl }


#---------------
# Application tooltips
#---------------
# 
array set tooltips {
        
        FILE,NEW                 "Create new file"
        FILE,OPEN                "Open an existing project file"        
        FILE,OPEN,ARROW          "Select a recent file"        
        FILE,SAVE                "Save current document"        
        FILE,SAVEAS              "Save document with a different name"
        FILE,QUIT                "End the application"        

        EDIT,UNDO                "Undo last edit"
        EDIT,REDO                "Redo last change"
        EDIT,CUT                 "Cut selected text, copy to clipboard"
        EDIT,COPY                "Copy selected text to the clipboard (no markup)"
        EDIT,PASTE               "Paste clipboard text in the document"
        EDIT,DELETE              "Delete selected text"
        EDIT,SELECTALL           "Select all text in the document"
        EDIT,PREFERENCES         "Set application preferences"        

        HELP,ABOUT               "Show details about this application"
        HELP,HELP                "Load application help file"        

}


#---------------
# Create tab menus
#---------------
# Arguments:
#        val
#        args        list of options passed to the proc
# Returns:
#        none
#
proc tabMenus { val args } {
        
        # set defaults and assign options values from args
        array set opt [list iconSize 1 style icons] 
        array set opt [string map {- {}} $args]
        
        set res ""

        set notebook [gnocl::notebook -name TABMENU -homogeneous 1 -borderWidth 0]

        $notebook addPage [gnocl::toolBar -name FILE -style $opt(style) -iconSize $opt(iconSize) ] %__File
        $notebook addPage [gnocl::richTextToolBar -name STYLES -iconSize $opt(iconSize)] %__Styles
        $notebook addPage [gnocl::toolBar -name EDIT -style $opt(style) -iconSize $opt(iconSize)] %__Edit
        $notebook addPage [gnocl::toolBar -name HELP -style $opt(style) -iconSize $opt(iconSize)] %__Help
        
        # do something!

        set rc_menu [gnocl::menuRecentChooser \
                -showNumbers 1 \
                -limit 5 \
                -showIcons 1 \
                -showNotFound 0 \
                -showPrivate 1 \
                -showTips 1 \
                -patterns "*.txt *.TXT" \
                -onClicked { 
                        if { ![file exists  %f ] } {
                                gnocl::dialog -title "FILE ERROR!" \
                                        -type warning \
                                        -text "%f\ndoes not exist\n" \
                                        -setSize 0.125 \
                                        -modal 1 \
                                        -onResponse { return }        
                                $app(status) push "Recent file load cancelled: file %f does not exist."
                                return
                        }
                        set app(fname) %f
                        openRecent } ]

        
        FILE add item -icon %#New  -name FILE,NEW  -tooltip $::tooltips(FILE,NEW)
        
        FILE add menuButton \
        -name FILE,OPEN \
        -icon %#Open \
        -text Open \
        -menu $rc_menu \
        -tooltip $::tooltips(FILE,OPEN) \
        -arrowTooltip $::tooltips(FILE,OPEN,ARROW) \
        -onClicked { openFile }
        
        
        #FILE add item -icon %#Open -name FILE,OPEN -tooltip $::tooltips(FILE,OPEN)
        FILE add item -icon %#Save -name FILE,SAVE -tooltip $::tooltips(FILE,OPEN)
        FILE add item -icon %#SaveAs -name FILE,SAVEAS -tooltip $::tooltips(FILE,SAVEAS)        
        FILE add separator                
        FILE add item -icon %#Quit -name FILE,QUIT  -tooltip $::tooltips(FILE,QUIT)         

        EDIT add item -icon %#Undo -name EDIT,UNDO -tooltip $::tooltips(EDIT,UNDO)
        EDIT add item -icon %#Redo -name EDIT,REDO -tooltip $::tooltips(EDIT,REDO)
        EDIT add separator                
        EDIT add item -icon %#Cut -name EDIT,CUT -tooltip $::tooltips(EDIT,CUT)
        EDIT add item -icon %#Copy -name EDIT,COPY -tooltip $::tooltips(EDIT,COPY)
        EDIT add item -icon %#Paste        -name EDIT,PASTE -tooltip $::tooltips(EDIT,PASTE)
        EDIT add item -icon %#Delete -name EDIT,DELETE -tooltip $::tooltips(EDIT,DELETE)        
        EDIT add separator        
        EDIT add item -icon %#SelectAll -name EDIT,SELECTALL -tooltip $::tooltips(EDIT,SELECTALL)        
        EDIT add separator        
        EDIT add item -icon %#Preferences -name EDIT,PREFERENCES -tooltip $::tooltips(EDIT,PREFERENCES)        
        
        HELP add item -icon %#Help -name HELP,HELP -tooltip $::tooltips(HELP,HELP)
        HELP add item -icon %#About        -name HELP,ABOUT -tooltip $::tooltips(HELP,ABOUT)
        
        return $notebook
}

#---------------
# Description
#---------------
# Arguments:
#        none
# Returns:
#        none
#
proc openFile {} {

        global app

        set myFilters {Text {*.txt *.TXT}  }

        set fname [gnocl::fileChooserDialog -title "Select File..." -fileFilters $myFilters  -currentFolder $::env(HOME)]

        if { $fname == "" } { 
                STATUS push "Operation \"Open file...\" cancelled."
                return } 


        set fp [open $fname "r"]
        TEXT clear
        TEXT insert end [read $fp]
        close $fp

        MAIN configure -title [file tail $fname]
        
        set app(fname) $fname
        
        STATUS push "File Open: $app(fname)"
        
}

#---------------
# Description
#---------------
# Arguments:
#        none
# Returns:
#        none
#
proc openRecent {} {

        global app

        set fp [open $app(fname) "r"]
        TEXT clear
        TEXT insert end [read $fp]
        close $fp
        
        MAIN configure -title [file tail $app(fname)]
        
        STATUS push "Open File Recent: [file tail $app(fname)] completed."
}


#---------------
# Description
#---------------
# Arguments:
#        none
# Returns:
#        none
#
proc saveFile {} {

        global app

        set fp [open $app(fname) "w"]
        puts $fp [TEXT get ]
        close $fp

        STATUS push "Operation \"Save...\" completed."

}

#---------------
# Description
#---------------
# Arguments:
#        val
#        args
# Returns:
#        res
#
proc saveAsFile {} {

        global app

        set myFilters {Text {*.txt *.TXT}  }

        set fname [gnocl::fileChooserDialog -action save -title "Save File As..." -fileFilters $myFilters -currentFolder $::env(HOME)]

        if { $fname == "" } { 
                STATUS push "Operation \"Save As...\" cancelled."
                return } 

        set fp [open $fname "w"]
        puts $fp [TEXT get ]
        close $fp

        set app(fname) $fname
        
        MAIN configure -title [file tail $app(fname) ]
}

#---------------
# Display details about the application
#---------------
# Arguments:
#        msg                String to display as the dialog title
# Returns:
#        none
#
proc about { {msg ""} } {

        gnocl::aboutDialog \
          -title $msg \
      -programName "Simple Editor" \
      -artists {artist1 artist2} \
      -authors {author1 author2} \
      -comments "This is a comment." \
      -copyright "Copyright." \
      -documenters {documenter1 documenter2} \
      -license "This is a license" \
      -version "V1.2.3" \
      -website "http://www.gnocl.org" \
      -websiteLabel "www.gnocl.org" \
      -logo %/gnocl_logo.png
}


#---------------
# Display not yet implemented message
#---------------
# Arguments:
#        msg                String to display as the dialog title
# Returns:
#        none
#
proc nyi { msg } {

        gnocl::dialog -title $msg -type info -text "Not yet implemented"
}

#---------------
# Exit the application
#---------------
# Arguments:
#        none
# Returns:
#        none
#
proc quit {} {

        exit
}

#---------------
# Script Main Function
#---------------
# Arguments:
#        args        not used
# Returns:
#        none
#
proc main { args } {

        global app

        set app(fname) $::env(HOME)/untitled.txt


        gnocl::statusBar -name STATUS
        gnocl::vBox -name CONTAINER -padding 0
        gnocl::text -name TEXT -useMarkup 1 -margins {4 4 0 4} -wrapMode word
        gnocl::window -name MAIN -child [CONTAINER] -setSize 0.5 -title [file tail $app(fname)]
        
        CONTAINER add [tabMenus a] -fill 1 -expand 0
        CONTAINER add [TEXT] -fill 1 -expand 1
        CONTAINER add [STATUS] -fill 1 -expand 0
        STYLES configure -text [TEXT] 
        
        FILE,OPEN configure -onClicked { openFile }
        FILE,SAVE configure -onClicked { saveFile }
        FILE,SAVEAS configure -onClicked { saveAsFile }

        
        FILE,NEW configure -onClicked { STATUS push NEWFILE ; TEXT clear} 
        FILE,QUIT configure -onClicked { quit } 

        EDIT,UNDO configure -onClicked { TEXT undo } 
        EDIT,REDO configure -onClicked { TEXT undo } 
        EDIT,CUT configure -onClicked { TEXT cut } 
        EDIT,COPY configure -onClicked { TEXT copy }
        EDIT,PASTE configure -onClicked { TEXT paste }        
        EDIT,DELETE configure -onClicked { TEXT erase selectionStart selectionEnd }        
        EDIT,SELECTALL configure -onClicked { TEXT select start end }        
        EDIT,PREFERENCES configure -onClicked { nyi "Set Preferences" }        
         
        HELP,HELP configure -onClicked { nyi "Application Help" }        
        HELP,ABOUT configure -onClicked { about "Simple Editor" }        

        # add keyboard accelerators
        set accelerators {
                <Ctrl>n { TEXT clear }
                <Ctrl>s saveFile
                <Ctrl>o openFile
                <Ctrl>q quit
        
                <Ctrl>a { TEXT select start end }
                <Ctrl>x { TEXT cut }
                <Ctrl>c { TEXT copy }
                <Ctrl>v { TEXT paste }
                <Ctrl>z { TEXT undo }        
                <Ctrl>y { TEXT redo }
                <Ctrl><Alt>p {nyi "Set Preferences"}

                <Ctrl>b { TEXT tag apply selectionStart selectionEnd -tags <b> } 
                <Ctrl>i { TEXT tag apply selectionStart selectionEnd -tags <i> }
                <Ctrl>u { TEXT tag apply selectionStart selectionEnd -tags <u> }        

                F1 {nyi "Help"}
        }
        
        foreach {a b} $accelerators {
                gnocl::acceleratorGroup -window [MAIN] \
                        -key $a -onPressed $b        
        }
         
        STATUS push "Ready!"
}

main