Eskil

preprocess.tcl at trunk
Login

File src/preprocess.tcl artifact cf6cf540b5 on branch trunk


#---------------------------------------------------------- -*- tcl -*-
#  Eskil, Preprocess dialog
#
#  Copyright (c) 2004-2017, Peter Spjuth  (peter.spjuth@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; see the file COPYING.  If not, write to
#  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
#  Boston, MA 02111-1307, USA.
#
#----------------------------------------------------------------------
#
# The format of the ::Pref(preprocessn) is:
# Flat list of stride 2, (name data)
# Data dict elements: preprocess active save
# Preprocess element is a flat list of stride 3 (RE sub side)
# If side is a special word, it is a special shortcut format.
#
# There used to be a ::Pref(preprocess) with a different format, the new
# was named a bit different for compatibilty with saved preferences.

# Return active preprocess items as a flat list with stride 3
proc getActivePreprocess {top} {
    set res {}
    set count 0
    foreach {name data} $::Pref(preprocessn) {
        if {[dict get $data active]} {
            foreach {RE sub side} [dict get $data preprocess] {
                if {$side eq "Subst"} {
                    # Translate to Regexps
                    # Unique substitution
                    set pattern __$count[clock clicks -microseconds]__
                    incr count
                    lappend res $RE  $pattern left
                    lappend res $sub $pattern right
                } elseif {$side eq "Prefix"} {
                    # Translate to Regexp
                    set RE2 [string map [list % $RE] {^.*?\m(%\w+).*$}]
                    lappend res $RE2 {\1} ""
                } else {
                    # Generic
                    lappend res $RE $sub $side
                }
            }
        }
    }
    return $res
}

# Entry for adding preprocess from command line
proc addPreprocess {name RE sub side} {
    set data {}
    dict set data preprocess [list $RE $sub $side]
    dict set data active 1
    dict set data save 0
    lappend ::Pref(preprocessn) $name $data
}

# Get the value used when saving preferences
proc getPreprocessSave {} {
    set res {}
    foreach {name data} $::Pref(preprocessn) {
        if {[dict get $data save]} {
            # Always save with active 0 for now.
            # A user can edit the save file to have it by default.
            dict set data active 0
            lappend res $name $data
        }
    }
    return $res
}

# This is called when Ok or Apply is pressed.
# Update preference from dialog contents.
proc EditPrefRegsubOk {top W item {keep 0}} {
    set exa $::eskil($top,prefregexa)

    set result {}
    for {set t 1} {[info exists ::eskil($top,prefregexp$t)]} {incr t} {
        set RE $::eskil($top,prefregexp$t)
        set Sub $::eskil($top,prefregsub$t)
        set l $::eskil($top,prefregleft$t)
        set r $::eskil($top,prefregright$t)
        if {$RE eq ""} continue
        switch $::eskil($top,prefregtype$t) {
            Subst {
                lappend result $RE $Sub Subst
            }
            Prefix {
                lappend result $RE "" Prefix
            }
            default {
                set side ""
                if {$l && !$r} { set side left }
                if { ! $l && $r} { set side right }
                if { ! $l && !$r} { continue }

                if {[catch {regsub -all -- $RE $exa $Sub _} err]} {
                    return
                }
                lappend result $RE $Sub $side
            }
        }
    }

    set ::TmpPref(preprocess,re,$item) $result
    set ::TmpPref(preprocess,active,$item) 1

    if {$keep} {
        # Apply was pressed, also apply main dialog
        # TODO: Get widgets right. Right now it does not matter
        EditPrefPrepOk . $top 1
        return
    }
    destroy $W

    array unset ::eskil $top,prefregexp*
    array unset ::eskil $top,prefregsub*
    array unset ::eskil $top,prefregleft*
    array unset ::eskil $top,prefregright*
    array unset ::eskil $top,prefregtype*
}

# Update the example in the preprocess dialog
proc EditPrefRegsubUpdate {top args} {
    set exal $::eskil($top,prefregexa)
    set exar $::eskil($top,prefregexa)
    set exal2 $::eskil($top,prefregexa2)
    set exar2 $::eskil($top,prefregexa2)
    set ok $::widgets($top,prefRegsubOk)
    set app $::widgets($top,prefRegsubApply)

    set pp {}
    for {set t 1} {[info exists ::eskil($top,prefregexp$t)]} {incr t} {
        set RE $::eskil($top,prefregexp$t)
        set Sub $::eskil($top,prefregsub$t)
        set l $::eskil($top,prefregleft$t)
        set r $::eskil($top,prefregright$t)

        if {$RE eq ""} continue

        switch $::eskil($top,prefregtype$t) {
            Subst {
                set pattern __$t[clock clicks -microseconds]__
                lappend pp $RE  $pattern 1 0
                lappend pp $Sub $pattern 0 1
            }
            Prefix {
                set RE2 [string map [list % $RE] {^.*?\m(%\w+).*$}]
                lappend pp $RE2 {\1} 1 1
            }
            default {
                lappend pp $RE $Sub $l $r
            }
        }
    }
    foreach {RE Sub l r} $pp {
        if {$l} {
            if {[catch {regsub -all -- $RE $exal $Sub result} err]} {
                set ::eskil($top,prefregresultl) "$t ERROR: $err"
                $ok configure -state disabled
                $app configure -state disabled
                return
            } else {
                set exal $result
            }
            if {[catch {regsub -all -- $RE $exal2 $Sub result} err]} {
                set ::eskil($top,prefregresultl2) "$t ERROR: $err"
                $ok configure -state disabled
                $app configure -state disabled
                return
            } else {
                set exal2 $result
            }
        }
        if {$r} {
            if {[catch {regsub -all -- $RE $exar $Sub result} err]} {
                set ::eskil($top,prefregresultr) "$t ERROR: $err"
                $ok configure -state disabled
                $app configure -state disabled
                return
            } else {
                set exar $result
            }
            if {[catch {regsub -all -- $RE $exar2 $Sub result} err]} {
                set ::eskil($top,prefregresultr2) "$t ERROR: $err"
                $ok configure -state disabled
                $app configure -state disabled
                return
            } else {
                set exar2 $result
            }
        }
    }
    set ::eskil($top,prefregresultl2) $exal2
    set ::eskil($top,prefregresultr2) $exar2
    set ::eskil($top,prefregresultl) $exal
    set ::eskil($top,prefregresultr) $exar
    $ok configure -state normal
    $app configure -state normal
}

# Add a new entry in the preprocess dialog
proc AddPrefRegsub {top parent {type {}}} {
    # Figure out next number to use
    for {set t 1} {[winfo exists $parent.fr$t]} {incr t} {
        #Empty
    }
    # Default values
    if { ! [info exists ::eskil($top,prefregexp$t)]} {
        set ::eskil($top,prefregtype$t) Generic
        set ::eskil($top,prefregexp$t) ""
        set ::eskil($top,prefregexp$t) ""
        set ::eskil($top,prefregsub$t) ""
        set ::eskil($top,prefregleft$t) 1
        set ::eskil($top,prefregright$t) 1
    }
    # Override type if given
    if {$type ne ""} {
        set ::eskil($top,prefregtype$t) $type
    }

    set W [ttk::frame $parent.fr$t -borderwidth 2 -relief groove -padding 3]
    pack $W -side "top" -fill x -padx 3 -pady 3

    switch $::eskil($top,prefregtype$t) {
        Subst {
            ttk::label $W.l1 -text "Left:" -anchor w
            ttk::entryX $W.e1 -textvariable ::eskil($top,prefregexp$t) -width 20
            ttk::label $W.l2 -text "Right:" -anchor w
            ttk::entryX $W.e2 -textvariable ::eskil($top,prefregsub$t)
            grid $W.l1 $W.e1 $W.l2 $W.e2 -sticky we -padx 3 -pady 3
            grid columnconfigure $W {0 2} -uniform a
            grid columnconfigure $W {1 3} -weight 1 -uniform b

            addBalloon $W.l1 -fmt {
                Each pattern is applied to its side and substituted
                for a common unique string.
            }
        }
        Prefix {
            ttk::label $W.l1 -text "Prefix:" -anchor w
            ttk::entryX $W.e1 -textvariable ::eskil($top,prefregexp$t) -width 20
            grid $W.l1 $W.e1 -sticky we -padx 3 -pady 3
            grid columnconfigure $W 1 -weight 1
            addBalloon $W.l1 -fmt {
                Only one word that start with prefix is valid for line
                comparison.
            }
        }
        default {
            ttk::label $W.l1 -text "Regexp:" -anchor w
            ttk::entryX $W.e1 -textvariable ::eskil($top,prefregexp$t) -width 60
            ttk::label $W.l2 -text "Subst:" -anchor w
            ttk::entryX $W.e2 -textvariable ::eskil($top,prefregsub$t)
            ttk::checkbutton $W.cb1 -text "Left"  -variable ::eskil($top,prefregleft$t)
            ttk::checkbutton $W.cb2 -text "Right" -variable ::eskil($top,prefregright$t)
            addBalloon $W.cb1 "Apply to left file"
            addBalloon $W.cb2 "Apply to right file"

            grid $W.l1 $W.e1 $W.cb1 -sticky we -padx 3 -pady 3
            grid $W.l2 $W.e2 $W.cb2 -sticky we -padx 3 -pady 3
            grid columnconfigure $W 1 -weight 1
        }
    }

    trace add variable ::eskil($top,prefregexp$t) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::eskil($top,prefregsub$t) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::eskil($top,prefregleft$t) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::eskil($top,prefregright$t) write \
            [list EditPrefRegsubUpdate $top]
}

# Editor for one item in ::Pref(preprocessn)
proc EditPrefRegsub {top item} {
    set W $top.prefregsub

    ToplevelForce $W "Preferences: Preprocess group"

    # Buttons
    ttk::frame $W.fb1 -padding 3
    ttk::button $W.fb1.b1 -text "Add" -command [list AddPrefRegsub $top $W Generic]
    addBalloon $W.fb1.b1 "Add generic pattern"
    ttk::button $W.fb1.b2 -text "Add Subst" -command [list AddPrefRegsub $top $W Subst]
    addBalloon $W.fb1.b2 "Add using substitution shortcut"
    ttk::button $W.fb1.b3 -text "Add Prefix" -command [list AddPrefRegsub $top $W Prefix]
    addBalloon $W.fb1.b3 "Add using prefix shortcut"
    grid $W.fb1.b1 $W.fb1.b2 $W.fb1.b3 -sticky we -ipadx 5 -padx 3 -pady 3
    grid columnconfigure $W.fb1 all -uniform a
    grid anchor $W.fb1 w

    # Result example part
    if { ! [info exists ::eskil($top,prefregexa)]} {
        set ::eskil($top,prefregexa) \
                "An example TextString FOR_REGSUB /* Comment */"
        set ::eskil($top,prefregexa2) \
                "An example TextString FOR_REGSUB /* Comment */"
    }
    ttk::labelframe $W.res -text "Preprocessing result" -padding 3
    ttk::label $W.res.l3 -text "Example 1:" -anchor w
    ttk::entryX $W.res.e3 -textvariable ::eskil($top,prefregexa) -width 60
    ttk::label $W.res.l4l -text "Result 1 L:" -anchor w
    ttk::label $W.res.l4r -text "Result 1 R:" -anchor w
    ttk::label $W.res.e4l -textvariable ::eskil($top,prefregresultl) \
            -anchor w -width 10
    ttk::label $W.res.e4r -textvariable ::eskil($top,prefregresultr) \
            -anchor w -width 10
    ttk::label $W.res.l5 -text "Example 2:" -anchor w
    ttk::entryX $W.res.e5 -textvariable ::eskil($top,prefregexa2)
    ttk::label $W.res.l6l -text "Result 2 L:" -anchor w
    ttk::label $W.res.l6r -text "Result 2 R:" -anchor w
    ttk::label $W.res.e6l -textvariable ::eskil($top,prefregresultl2) \
            -anchor w -width 10
    ttk::label $W.res.e6r -textvariable ::eskil($top,prefregresultr2) \
            -anchor w -width 10

    grid $W.res.l3  $W.res.e3  -sticky we -padx 3 -pady 3
    grid $W.res.l4l $W.res.e4l -sticky we -padx 3 -pady 3
    grid $W.res.l4r $W.res.e4r -sticky we -padx 3 -pady 3
    grid $W.res.l5  $W.res.e5  -sticky we -padx 3 -pady 3
    grid $W.res.l6l $W.res.e6l -sticky we -padx 3 -pady 3
    grid $W.res.l6r $W.res.e6r -sticky we -padx 3 -pady 3
    grid columnconfigure $W.res 1 -weight 1

    # Buttons
    ttk::frame $W.fb -padding 3
    ttk::button $W.fb.b1 -text "Ok"     -command [list EditPrefRegsubOk $top $W $item]
    ttk::button $W.fb.b2 -text "Apply"  -command [list EditPrefRegsubOk $top $W $item 1]
    ttk::button $W.fb.b3 -text "Cancel" -command [list destroy $W]
    set ::widgets($top,prefRegsubOk) $W.fb.b1
    set ::widgets($top,prefRegsubApply) $W.fb.b2

    grid $W.fb.b1 x $W.fb.b2 x $W.fb.b3 -sticky we
    grid columnconfigure $W.fb {0 2 4} -uniform a
    grid columnconfigure $W.fb {1 3} -weight 1

    # Top layout
    pack $W.fb1 -side "top" -fill x -padx 3 -pady 3
    pack $W.fb $W.res -side bottom -fill x -padx 3 -pady 3

    # Fill in existing or an empty line
    set preprocess $::TmpPref(preprocess,re,$item)

    if {[llength $preprocess] == 0} {
        AddPrefRegsub $top $W Generic
    } else {
        set t 1
        foreach {RE Sub side} $preprocess {
            set ::eskil($top,prefregexp$t) $RE
            set ::eskil($top,prefregsub$t) $Sub
            set ::eskil($top,prefregleft$t) 0
            set ::eskil($top,prefregright$t) 0
            set ::eskil($top,prefregtype$t) Generic
            if {$side in {Subst Prefix}} {
                set ::eskil($top,prefregtype$t) $side
            } else {
                if {$side eq "" || $side eq "left"} {
                    set ::eskil($top,prefregleft$t) 1
                }
                if {$side eq "" || $side eq "right"} {
                    set ::eskil($top,prefregright$t) 1
                }
            }
            AddPrefRegsub $top $W
            incr t
        }
    }

    trace add variable ::eskil($top,prefregexa) write \
            [list EditPrefRegsubUpdate $top]
    trace add variable ::eskil($top,prefregexa2) write \
            [list EditPrefRegsubUpdate $top]
    EditPrefRegsubUpdate $top
}


# This is called when Ok or Apply is pressed.
proc EditPrefPrepOk {top W {keep 0}} {
    # Update preference from dialog contents.
    set new {}
    for {set r 1} {$r <= $::TmpPref(preprocess,n)} {incr r} {
        set name $::TmpPref(preprocess,name,$r)
        set act $::TmpPref(preprocess,active,$r)
        set save $::TmpPref(preprocess,save,$r)
        set re $::TmpPref(preprocess,re,$r)
        lappend new $name
        lappend new [dict create active $act "save" $save preprocess $re]
    }
    set ::Pref(preprocessn) $new
    
    if {$keep} return
    destroy $W
}

# Create a toplevel, even if it exists
proc ToplevelForce {W title} {
    destroy $W
    ttk::toplevel $W -padx 3 -pady 3
    wm title $W $title
}

# Move an item one step up
proc EditPrefPreUp {rI} {
    #puts EditPrefPreUp$rI
    # Sanity check
    if {$rI <= 1 || $rI > $::TmpPref(preprocess,n)} {
        return
    }
    set pI [expr {$rI - 1}]
    foreach item {name active save re} {
        set tmp $::TmpPref(preprocess,$item,$rI)
        set ::TmpPref(preprocess,$item,$rI) $::TmpPref(preprocess,$item,$pI)
        set ::TmpPref(preprocess,$item,$pI) $tmp
    }
}

proc EditPrefPreprocessAddItem {W autoEdit} {
    set r $::TmpPref(preprocess,n)
    incr r
    if { ! [info exists ::TmpPref(preprocess,name,$r)]} {
        set ::TmpPref(preprocess,name,$r) ""
        set ::TmpPref(preprocess,active,$r) 0
        set ::TmpPref(preprocess,save,$r) 0
        set ::TmpPref(preprocess,re,$r) ""
    }
    ttk::entry $W.fp.ne$r -textvariable ::TmpPref(preprocess,name,$r)
    addBalloon $W.fp.ne$r "Name of preprocess group (optional)"
    ttk::checkbutton $W.fp.cba$r -text "Active" \
            -variable ::TmpPref(preprocess,active,$r)
    addBalloon $W.fp.cba$r "Activate group for this session"
    ttk::checkbutton $W.fp.cbs$r -text "Save" \
            -variable ::TmpPref(preprocess,save,$r)
    addBalloon $W.fp.cbs$r "Save group when preferences are saved"
    ttk::button $W.fp.be$r -text "Edit" \
            -command [list EditPrefRegsub $W $r]
    addBalloon $W.fp.be$r "Edit the associated list of regexps"
    if {$autoEdit} {
        after idle [list after 50 [list $W.fp.be$r invoke]]
    }
    ttk::button $W.fp.bu$r -image $::img(up) \
            -command [list EditPrefPreUp $r]
    addBalloon $W.fp.bu$r "Move group up in list"
    grid $W.fp.ne$r $W.fp.cba$r $W.fp.cbs$r $W.fp.be$r $W.fp.bu$r -sticky we \
            -padx 3 -pady 3
    # Make buttons symmetric
    grid  $W.fp.be$r $W.fp.bu$r -sticky news

    set ::TmpPref(preprocess,n) $r
}

proc EditPrefPreprocess {top} {
    set W $top.prefpreprocess

    # Make a working copy more suitable for GUI connection
    set r 0
    foreach {name data} $::Pref(preprocessn) {
        incr r
        set ::TmpPref(preprocess,name,$r) $name
        set ::TmpPref(preprocess,active,$r) [dict get $data active]
        set ::TmpPref(preprocess,save,$r) [dict get $data save]
        set ::TmpPref(preprocess,re,$r) [dict get $data preprocess]
    }
    # Create one if there is none, to simplify GUI usage
    set autoEdit 0
    if {$r == 0} {
        set autoEdit 1
        incr r
    }
    set ::TmpPref(preprocess,n) 0
    set nItems $r

    ToplevelForce $W "Preferences: Preprocess"

    # Frame for List of preprocessing
    ttk::frame $W.fp -padding 3
    grid columnconfigure $W.fp 0 -weight 1
    
    for {set r 1} {$r <= $nItems} {incr r} {
        EditPrefPreprocessAddItem $W $autoEdit
    }

    # Frame for action buttons
    ttk::frame $W.fa -padding 3
    ttk::button $W.fa.b1 -text "Add" \
            -command [list EditPrefPreprocessAddItem $W 1]
    addBalloon $W.fa.b1 "Add a preprocess group"

    grid $W.fa.b1 -sticky we
    grid columnconfigure $W.fa {0 2 4} -uniform a
    grid columnconfigure $W.fa {1 3} -weight 1

    
    # Frame for dialog Buttons
    ttk::frame $W.fb -padding 3
    ttk::button $W.fb.b1 -text "Ok"     -command [list EditPrefPrepOk $top $W]
    ttk::button $W.fb.b2 -text "Apply"  -command [list EditPrefPrepOk $top $W 1]
    ttk::button $W.fb.b3 -text "Cancel" -command [list destroy $W]

    grid $W.fb.b1 x $W.fb.b2 x $W.fb.b3 -sticky we
    grid columnconfigure $W.fb {0 2 4} -uniform a
    grid columnconfigure $W.fb {1 3} -weight 1

    # Top layout
    pack $W.fb -side bottom -fill x
    pack $W.fa -side bottom -fill x
    pack $W.fp -side "top" -fill both -expand 1
}