e: a packaged tiny editor for eTcl

This page extends e: a tiny editor plugin for eTcl to provide it as a stand-alone package. Apart from packaging correctly, I have done the following (tiny) modifications:

  • The editor starts automatically on an empty page (controlled by the autostart index in the global array).
  • The name of the main window is "obfuscated" to avoid obvious name conflicts.
  • The save procedure uses now a reference to the global array. I think that the previous code was broken since it did not modified dynamically the menu when serially editing several files.

Right now, the ActiveSync connection to my borrowed Qtek has stopped working, so I cannot test the code live on top of eTcl, but I see no reason why this would not work on the mobile device.

EF


 package require Tk

 namespace eval ::pocketeditor {
     variable g
     if {![info exists g]} {
         array set g {
             autostart   on
             mainwin     ".__pe"
         }
     }
     namespace export e
 }

 proc ::pocketeditor::e {{name ""}} {
     variable g

     if ![winfo exists $g(mainwin)] {
         toplevel $g(mainwin)
         text $g(mainwin).t -wrap word -yscr "$g(mainwin).y set" \
             -width 37 -bd 2 -undo 1
         scrollbar $g(mainwin).y -com "$g(mainwin).t yview"
         pack $g(mainwin).t $g(mainwin).y -side left -fill y
         set m [menu $g(mainwin).m]
         $g(mainwin) config -menu $m
         m+ $m File Open.. ::pocketeditor::open
         m+ $m File Save {::pocketeditor::save ::pocketeditor::g(filename)}
         m+ $m File "Save as.." ::pocketeditor::save
         m+ $m File ---
         m+ $m File Eval {eval [$::pocketeditor::g(mainwin).t get 1.0 end]}
         m+ $m File Clear {$::pocketeditor::g(mainwin).t delete 1.0 end}
         m+ $m File ok {console eval {raise .; focus -force .console}}
         foreach i {Cut Copy Paste} {
             m+ $m Edit $i [list event gen $g(mainwin).t <<$i>>]
         }
         m+ $m Edit ---
         m+ $m Edit Undo {$g(mainwin).t edit undo}
         m+ $m Edit Redo {$g(mainwin).t edit redo}
         m+ $m Goto Top    {$g(mainwin).t see 1.0}
         m+ $m Goto Cursor {$g(mainwin).t see insert}
         m+ $m Goto Bottom {$g(mainwin).t see end}
         $m add casc -label @ -menu [menu $m.dummy]
         bind $g(mainwin).t <ButtonRelease-1> {$::pocketeditor::g(mainwin).m entryconf @* -label @[$::pocketeditor::g(mainwin).t index current]; ::pocketeditor::xx}
         wm geo $g(mainwin) 240x189+0+25
         set ::g(dir) [pwd]
     }
     if {$name ne ""} {
         $g(mainwin).t delete 1.0 end
         $g(mainwin).t insert end [::pocketeditor::get $name]
         wm title $g(mainwin) [file tail $name]
     }
     raise $g(mainwin)
     focus -force $g(mainwin).t
 }

 proc ::pocketeditor::get name {
     variable g

     if [file exists $name] {
         set f [::open $name]
         set g(filename) $name
         ::pocketeditor::K [read $f] [close $f]
     } else {::pocketeditor::corp $name}
 }
 proc ::pocketeditor::K {a b} {set a}

 proc ::pocketeditor::corp name {
     set argl {}
     foreach a [info args $name] {
         if [info default $name $a def] {
             lappend a $def
         }
         lappend argl $a
     }
     list proc $name $argl [info body $name]
 }

 proc ::pocketeditor::open {} {
     variable g

     e [tk_getOpenFile -initialdir $::g(dir)]
 }
 proc ::pocketeditor::save {{file_p ""}} {
     variable g

     if {$file_p eq ""} {
         set file [tk_getSaveFile -initialdir $::g(dir)]
     } else {
         puts "$file_p"
         upvar \#0 $file_p file
         puts "Saving into $file"
     }
     if {$file ne ""} {
         set ::g(dir) [file dir [file join [pwd] $file]]
         set f [::open $file w]
         puts $f [$g(mainwin).t get 1.0 "end - 1c"]
         close $f
     }
 }
 proc ::pocketeditor::m+ {m head label {cmd ""}} {
     if ![winfo ex $m.m$head] {
         $m add casc -label $head -menu [menu $m.m$head -tearoff 0]
     }
     if {$label ne "---"} {
         $m.m$head add command -label $label -command $cmd
     } else {$m.m$head add separator}
 }
 proc ::pocketeditor::xx {} {
     variable g

     $g(mainwin) config -menu ""
     $g(mainwin) config -menu $g(mainwin).m
 }

 package provide pocketeditor 0.1

 if { [string is true $::pocketeditor::g(autostart)] } {
     ::pocketeditor::e
 }

JHJL 27/01/06: Can you give me a hint how to install this!


RS: Thanks for your interest, first of all. But consider that I tapped the e code on my cellphone, and still prefer to type

 e

instead of

 package require pocketeditor
 namespace export pocketeditor::*
 e

to finally get the editor up :^) You're right that namespaces are a good way of packaging software, and sort-of hiding internal details away, and avoiding name conflicts between different modules. But always spelling out fully-qualified names is a bit of an overkill - one design feature of namespaces is that "at home you don't need to say your family name":

 namespace eval foo {...}
 proc foo::bar {...} {
   grill ...
 }
 proc foo::grill {...} {
   bar
 }

will resolve grill and bar to the version in the own namespace as first choice.

And, also consider the usage scope: while pocketeditor::get and pocketeditor::save are specific to this little thing, and hence deserve to live in the same namespace, other functions are not:

  • m+ is generic for adding items to owned menus
  • xx is just a workaround as long as eTcl can't update menu titles well
  • corp is a very generic inverse of proc, can also be used e.g. to transfer procedures between interpreters
  • and K finally is the most generic combinator - to draw that into the ::pocketeditor namespace would just lead to code duplication...

In fact, some of the procs on e: a tiny editor plugin for eTcl are on my box located in other files. Just for stand-alone testing, and publication of the Wiki page, I pulled them together there.

I've been hacking more on my phone these days (see Sepp), and in that process clarified my thought on code organisation, at least for small-scale software like this:

  • packages are good for comprehensive, engineered libraries of general usability. The ones I use most often are BWidget, Img, tdom
  • namespaces are, apart from being used in packages, good for wrapping smaller units not for general use (internal functions and variables), down to single objects in OO
  • auto_index provides a gentler way of getting library functions. If you engineeringly start an app with a series of package requires, that code will be sourced even though it may not be used at all. With auto_index, source occurs on demand, and startup is faster.

But that's just my opinions - not everybody believes that "underengineering" is simple yet beautiful :^) See add for a satirical glance at overengineering...


MB: After RS and EF work, I extended the possibilities of the editor and released it as an open-source sourceforge project :

http://tclrep.wiki.sourceforge.net/PocketEditor