Bwidget Source Code
Artifact [ec119b1750]
Not logged in
EuroTcl/OpenACS 11 - 12 JULY 2024, VIENNA

Artifact ec119b1750aadcf9825cec64adfda3a296e0b701:

Attachment "bwidget-color.patch" to ticket [75101bf5ce] added by anonymous 2013-06-14 14:10:21. (unpublished)
diff -Naur original-bwidget-1.9.6/color.tcl patched-bwidget-1.9.6/color.tcl
--- original-bwidget-1.9.6/color.tcl	2005-11-02 00:09:00.000000000 +0000
+++ patched-bwidget-1.9.6/color.tcl	2013-06-13 19:00:09.000000000 +0100
@@ -1,12 +1,17 @@
 namespace eval SelectColor {
     Widget::define SelectColor color Dialog
 
+    # MODS - extra option -variable for tracing user choice
+    # MODS - extra option -background, useful on Aqua to override default
+
     Widget::declare SelectColor {
         {-title     String     "Select a color" 0}
         {-parent    String     ""               0}
+        {-variable  String     ""               1}
         {-color     TkResource ""               0 {label -background}}
 	{-type      Enum       "dialog"         1 {dialog popup}}
 	{-placement String     "center"         1}
+	{-background TkResource ""               0 {label -background}}
     }
 
     variable _baseColors {
@@ -31,6 +36,15 @@
     variable _wcolor
     variable _image
     variable _hsv
+
+    # MODS - extra variables:
+    variable _varName
+    variable _unsavedSelection
+    variable _oldColor
+    variable _entryColor
+    variable _bgColor
+    variable _fgColor
+    variable _rounds
 }
 
 proc SelectColor::create { path args } {
@@ -70,6 +84,10 @@
     variable _wcolor
     variable _selectype
     variable _selection
+    variable _varName
+    variable _unsavedSelection
+    variable _bgColor
+    variable _rounds
 
     Widget::init SelectColor $path $args
     set top [toplevel $path]
@@ -79,6 +97,16 @@
     wm overrideredirect $top 1
     catch { wm attributes $top -topmost 1 }
 
+    # MODS - initialize _varName
+    set _varName [Widget::cget $path -variable]
+    if {[string range $_varName 0 1] ne {::}} {
+        set _varName ::SelectColor::_unsavedSelection
+    }
+
+    # MODS - initialize _bgColor, _rounds
+    set _bgColor  [Widget::cget $path -background]
+    set _rounds   {}
+
     set frame [frame $top.frame \
                    -highlightthickness 0 \
                    -relief raised -borderwidth 2]
@@ -115,6 +143,14 @@
     bind $top <Escape> {set SelectColor::_selection -2}
     bind $top <FocusOut> [subst {if {"%W" == "$top"} \
 				     {set SelectColor::_selection -2}}]
+
+    # MODS - set background color for menu
+    $f     configure -bg $_bgColor
+    $frame configure -bg $_bgColor
+    foreach w [winfo children $frame] {
+        $w configure -highlightcolor $_bgColor -highlightbackground $_bgColor
+    }
+
     eval [list BWidget::place $top 0 0] $placement
 
     wm deiconify $top
@@ -145,12 +181,24 @@
 		lappend nativecmd $opts($key) $val
 	    }
 	    if {$native} {
+		# Call native dialog
 		return [eval $nativecmd]
 	    }
 	}
+	# Call BWidget dialog
 	return [eval [list dialog $path] $args]
     } else {
-        return [lindex $colors $_selection]
+        # The user has either selected one of the palette colors, or has
+        # cancelled.  The full BWidget/native dialog was not called.
+        # MODS - unless the user has cancelled, record the selected
+        #        color in $_varName which may be traced by the caller.
+        set tmpCol [lindex $colors $_selection]
+        if {$tmpCol eq {}} {
+            # User has cancelled - no need to set $_varName
+        } else {
+            set $_varName $tmpCol
+        }
+        return $tmpCol
     }
 }
 
@@ -162,6 +210,13 @@
     variable _selection
     variable _image
     variable _hsv
+    variable _varName
+    variable _unsavedSelection
+    variable _oldColor
+    variable _entryColor
+    variable _bgColor
+    variable _fgColor
+    variable _rounds
 
     Widget::init SelectColor $path:SelectColor $args
     set top   [Dialog::create $path \
@@ -174,6 +229,19 @@
     set desc  [list \
                    base _baseColors "Base colors" \
                    user _userColors "User colors"]
+
+    # MODS - initialize _varName
+    # The initial value of [$w cget -variable] is ignored and is
+    # overwritten with [$w cget -color].
+    set _varName [Widget::cget $path:SelectColor -variable]
+    if {[string range $_varName 0 1] ne {::}} {
+        set _varName ::SelectColor::_unsavedSelection
+    }
+
+    # MODS - initialize _bgColor and _rounds
+    set _bgColor [Widget::cget $path:SelectColor -background]
+    set _rounds  {}
+
     set count 0
     foreach {type varcol defTitle} $desc {
         set col   0
@@ -203,6 +271,9 @@
 	    bind $fcolor <Double-1> \
 	    	"SelectColor::_select_rgb [list $count]; [list $top] invoke 0"
 
+            # MODS - record list of $fround values in _rounds
+	    lappend _rounds $fround
+
             incr count
             if {[incr col] == 6} {
                 incr lin
@@ -211,6 +282,42 @@
         }
         pack $titf -anchor w -pady 2
     }
+
+    # MODS - record these colors for use later
+    set _fgColor [$fg.round0 cget -highlightcolor]
+
+    # MODS - add a TitleFrame $titf to wrap $fg.round and $fg.value
+    set titf  [TitleFrame $fg.choice -text {Your Selection}]
+    set subf  [$titf getframe]
+    pack $titf -anchor w -pady 2 -expand yes -fill both
+
+    # MODS - add an entry widget $fg.value for the #RRGGBB value
+    if {$::tk_version > 8.4} {
+        set fixedFont TkFixedFont
+    } else {
+        set fixedFont Courier
+    }
+    set subf2 $fg.vround
+    frame $subf2 -highlightthickness 0 -relief sunken -borderwidth 2
+    entry $fg.value -width 8 -relief sunken -bd 0 -highlightthickness 0 \
+            -bg white -textvariable ::SelectColor::_entryColor -font $fixedFont
+    pack  $subf2    -in $subf  -anchor w -side left
+    pack  $fg.value -in $subf2 -anchor w -side left
+
+    # Remove focus from the entry widget by clicking anywhere...
+    bind $top <1> [list ::SelectColor::_CheckFocus %W]
+
+    # ... or by pressing Return/Escape.
+    bind $fg.value <Return> [list ::SelectColor::_CheckFocus .]
+    bind $fg.value <Escape> [list ::SelectColor::_CheckFocus .]
+    bind $fg.value <Return> {+break}
+    bind $fg.value <Escape> {+break}
+    # Break so that the bindings to these events on the toplevel are not
+    # executed.
+
+    # MODS - record the Tk window path for the entry widget.
+    set _widget(en) $fg.value
+
     set fround [frame $fg.round \
                     -highlightthickness 0 \
                     -relief sunken -borderwidth 2]
@@ -219,9 +326,13 @@
                     -highlightthickness 0 \
                     -relief flat -borderwidth 0]
     pack $fcolor -in $fround -fill y -expand yes
-    pack $fround -anchor e -pady 2 -fill y -expand yes
+    pack $fround -in $subf -side right -anchor e -pady 2 -fill y -expand yes
+    # MODS - adapt the pack to the TitleFrame $titf/$subf.
 
-    set fd  [frame $dlgf.fd]
+    # MODS - add a TitleFrame $dlgf.fd to wrap the canvas selectors.  The
+    #        labels are referenced by the DynamicHelp tooltip.
+    set fd0 [TitleFrame $dlgf.fd -text {Color Selectors}]
+    set fd  [$fd0 getframe]
     set f1  [frame $fd.f1 -relief sunken -borderwidth 2]
     set f2  [frame $fd.f2 -relief sunken -borderwidth 2]
     set c1  [canvas $f1.c -width 200 -height 200 -bd 0 -highlightthickness 0]
@@ -235,7 +346,9 @@
     pack $c1 $c2
     pack $f1 $f2 -side left -padx 10 -anchor n
 
-    pack $fg $fd -side left -anchor n -fill y
+    pack $fg $fd0 -side left -anchor n -fill y
+    pack configure $fd0 -pady 2 -padx {4 0}
+    # MODS - adapt the pack to the TitleFrame $fd0/$fd.
 
     bind $c1 <ButtonPress-1> [list SelectColor::_select_hue_sat %x %y]
     bind $c1 <B1-Motion>     [list SelectColor::_select_hue_sat %x %y]
@@ -269,18 +382,113 @@
     _set_hue_sat [lindex $_hsv 0] [lindex $_hsv 1]
     _set_value   [lindex $_hsv 2]
 
-    $top add -name ok
-    $top add -name cancel
+    # MODS - initialize _oldColor which is used to reset the traced -variable
+    #        if the user cancels.
+    # MODS - initialize the entry widget.
+    set _oldColor [set $_varName]
+    set tmp24     [::SelectColor::_24BitRgb $_oldColor]
+    if {[_ValidateColorEntry forced $tmp24]} {
+        set ::SelectColor::_entryColor $tmp24
+    } else {
+        # Value $tmp24 does not pass entry widget validation and if used
+        # would disable validation.  Use this default instead.
+        set _entryColor #
+    }
+
+    # MODS - validate input to the entry field.
+    # To avoid conflict with the entry -variable (_entryColor), do not set the
+    # latter directly (because a failed validation will switch off subsequent
+    # validations).  Either call _SetEntryValue, or set $_varName which triggers
+    # the trace.
+
+    $fg.value configure -validate all -validatecommand \
+            [list SelectColor::_ValidateColorEntry %V %P]
+
+    # MODS - trace $_varName
+    # Subsequent modifications to $_varName will update the entry widget, if the
+    # value is valid.
+    # From now on, this is the only way that:
+    # (1) ::SelectColor::_SetEntryValue is called
+    # (2) ::SelectColor::_entryColor is modified (except by the user typing in
+    #     the entry widget)
+
+    trace add variable $_varName write ::SelectColor::_SetEntryValue
+
+    # MODS - change case on button text
+    $top add -text OK
+    $top add -text Cancel
+
+    # MODS - add labels to offer DynamicHelp
+    set blurb {Hover for help with color selection by:}
+    frame $top.help
+    label $top.help.0 -text $blurb       -relief flat   -bd 2
+    label $top.help.1 -text { Mouse }    -relief groove -bd 2
+    label $top.help.2 -text { Keyboard } -relief groove -bd 2
+    pack  $top.help.0 $top.help.1 $top.help.2 \
+            -anchor center -pady 4 -side left -padx 5
+    DynamicHelp::add $top.help.1 -text [string map {{        } {}} \
+        {Click or drag the mouse in the Color Selectors to choose a color.
+        If the selected color remains black, regardless of what you
+        do in the left-hand Color Selector (for hue and saturation), check
+        the position of the pointer in the right-hand Color Selector
+        (for brightness).
+
+        Click one of the "Base colors" to read a value from this palette.
+
+        Click one of the "User colors" to read a value from this palette,
+        or to write to the palette if the color is blank.  If you then
+        use the Color Selectors to change the color, your choice will be
+        written to this (User) palette color until you select another
+        (Base or User) palette color.}]
+    DynamicHelp::add $top.help.2 -text [string map {{        } {}} \
+        {Click in the text entry window in the left of the "Your
+        Selection" area.
+
+        Type the color that you want in hexadecimal RGB format.
+        Whenever the number of hexadecimal digits is a multiple
+        of 3, the color value is valid and will be copied to the
+        other parts of the Color Selector.
+
+        Leave the text entry window by clicking anywhere else,
+        or by pressing the "Escape" or "Return" key.  The text
+        entry window will then display the color in 24-bit RGB
+        format, although internally the Color Selector uses
+        48-bit colors.
+
+        When the text entry widget does not have keyboard focus
+        (i.e. does not show a cursor), the "Return" and "Escape"
+        keys do the same as the "OK" and "Cancel" buttons,
+        respectively.}]
+    after idle [list pack $top.help]
+
+    # MODS - override background color
+    ReColor $path $_bgColor
+
     set res [$top draw]
     if {$res == 0} {
         set color [$fg.color cget -background]
     } else {
+        # MODS - user has cancelled - reset $_varName in case it is being traced
+        set $_varName $_oldColor
         set color ""
     }
+
+    # MODS - remove the trace on $_varName
+    trace remove variable $_varName write ::SelectColor::_SetEntryValue
+
     destroy $top
     return $color
 }
 
+
+# MODS - new command setbasecolor, "exported", to allow the caller to set the
+#        base colors of the palette.
+
+proc SelectColor::setbasecolor { idx color } {
+    variable _baseColors
+    set _baseColors [lreplace $_baseColors $idx $idx $color]
+}
+
 proc SelectColor::setcolor { idx color } {
     variable _userColors
     set _userColors [lreplace $_userColors $idx $idx $color]
@@ -292,15 +500,22 @@
     variable _selection
     variable _widget
     variable _hsv
+    variable _varName
+    variable _bgColor
+    variable _fgColor
 
     set frame $_widget(fcolor)
+
+    # MODS Use highlight color instead of focus to identify the selected
+    #      palette color.  Tab traversal of focus now works correctly.
     if {$_selection >= 0} {
         $frame.round$_selection configure \
-            -relief sunken -highlightthickness 1 -borderwidth 2
+            -relief sunken -highlightthickness 1 -borderwidth 2 \
+            -highlightbackground $_bgColor
     }
     $frame.round$count configure \
-        -relief flat -highlightthickness 2 -borderwidth 1
-    focus $frame.round$count
+        -relief flat -highlightthickness 2 -borderwidth 1 \
+        -highlightbackground $_fgColor
     set _selection $count
     set bg   [$frame.color$count cget -background]
     set user [expr {$_selection-[llength $_baseColors]}]
@@ -316,6 +531,10 @@
         _set_hue_sat [lindex $_hsv 0] [lindex $_hsv 1]
         _set_value   [lindex $_hsv 2]
         $frame.color configure -background $bg
+
+        # MODS - display selected color in entry widget, and notify
+        #        caller (if the variable is being traced).
+        set $_varName $bg
     }
 }
 
@@ -325,9 +544,14 @@
     variable _baseColors
     variable _userColors
     variable _widget
+    variable _varName
 
     set frame $_widget(fcolor)
     $frame.color configure -background $rgb
+
+    # MODS - display selected color in entry widget, and notify
+    #        caller (if the variable is being traced).
+    set $_varName $rgb
     set user [expr {$_selection-[llength $_baseColors]}]
     if {$user >= 0} {
         $frame.color$_selection configure -background $rgb
@@ -491,3 +715,230 @@
     return [list $hue $sat [expr {$max/65535}]]
 }
 
+
+# ------------------------------------------------------------------------------
+# MODS - Additional Commands
+# ------------------------------------------------------------------------------
+
+# ------------------------------------------------------------------------------
+#  Proc SelectColor::ReColor
+# ------------------------------------------------------------------------------
+# Command to change the background color for the dialog.
+#
+# FIXME Ideally this would be called by "$w configure -background $value".
+# Currently a "configure -background" command is passed to Dialog and Widget
+# but does not change SelectColor.
+# ------------------------------------------------------------------------------
+
+proc SelectColor::ReColor {path newColor} {
+    variable _bgColor
+    variable _rounds
+
+    set _bgColor $newColor
+
+    $path:cmd configure -bg $_bgColor
+
+    foreach child {
+        .sep           .help           .frame          .bbox
+        .help.0        .help.1         .help.2
+        .frame.fd      .frame.fd.f.f1  .frame.fd.f.f2
+        .frame.fg      .frame.fg.base  .frame.fg.choice
+        .frame.fg.user .frame.fg.round .frame.fg.vround
+    } {
+        $path$child configure -bg $_bgColor
+    }
+
+    # Special treatment for Aqua native buttons.
+    # FIXME implement a general fix for BWidget Button/ButtonBox/Dialog
+    if {[tk windowingsystem] eq "aqua"} {
+        foreach child {
+            .bbox.b0
+            .bbox.b1
+        } {
+            $path$child configure \
+                    -highlightbackground $_bgColor \
+                    -highlightthickness  0
+        }
+    }
+
+    foreach fround $_rounds {
+        $fround configure -highlightbackground $_bgColor -bg $_bgColor
+    }
+    
+    return
+}
+
+
+# ------------------------------------------------------------------------------
+#  Proc SelectColor::_24BitRgb
+# ------------------------------------------------------------------------------
+# Command to convert a hex 12n-bit RGB color to 24-bit, n > 0.
+# Convert anything else to {}.
+# Used to process the display in the entry widget.
+# ------------------------------------------------------------------------------
+
+proc SelectColor::_24BitRgb {col} {
+    set  lenny [string length $col]
+    incr lenny -1
+
+    if {    ($lenny % 3)
+         || ($lenny == 0)
+         || (![regexp {^#[a-fA-F0-9]*$} $col])
+    } {
+        # Not a multiple of 3, or not leading #, or nothing after #,
+        # or non-HEX digits.
+        return {}
+    } elseif {$lenny == 3} {
+        # 12-bit, pad to 24-bit
+        set val $col
+        set val [string replace $val 3 3 "[string index $val 3]0"]
+        set val [string replace $val 2 2 "[string index $val 2]0"]
+        set val [string replace $val 1 1 "[string index $val 1]0"]
+        return $val
+    } elseif {$lenny == 6} {
+        # 24-bit, return unchanged
+        return $col
+    } else {
+        # Truncate to 24-bit
+        set delta  [expr {$lenny / 3}]
+        set delta2 [expr {$delta * 2}]
+        set result #
+        append result [string range $col 1 2]
+        append result [string range $col $delta+1  $delta+2]
+        append result [string range $col $delta2+1 $delta2+2]
+        return $result
+    }
+}
+
+
+# ------------------------------------------------------------------------------
+#  Proc SelectColor::_SetEntryValue
+# ------------------------------------------------------------------------------
+# Command to update the (hexadecimal color displayed in the) entry widget
+# when there is a change in the color currently selected in the GUI, which is
+# stored in $::SelectColor::_varName.
+#
+# This command is called by a write trace on $::SelectColor::_varName; if the
+# value of this variable is a valid color (i.e. "#" followed by 3N hex digits),
+# this command converts the value to 24 bits and sets ::SelectColor::_entryColor
+# to the result, thereby displaying it in the entry widget.  Therefore,
+# when the user chooses a color by means other than the entry widget, this
+# command updates the entry widget.
+#
+# This command does not update the GUI when the user changes the value in the
+# entry widget: that is done instead by the -vcmd of the entry widget, which
+# is SelectColor::_ValidateColorEntry.  When the user chooses a color by typing
+# in the entry widget, the command _ValidateColorEntry copies the value to
+# $_varName if a keystroke in the widget makes its contents 3N hex digits long.
+# ------------------------------------------------------------------------------
+
+proc SelectColor::_SetEntryValue {argVarName var2 op} {
+    variable _entryColor
+    variable _varName
+
+    if {0} {
+    } elseif {$argVarName eq $_varName && $var2 eq {} && $op eq "write"} {
+        # OK
+    } else {
+        # Unexpected call
+        return -code error "Unexpected trace of variable\
+                \"$argVarName\", \"$var2\", \"$op\""
+    }
+
+    set col24bit [::SelectColor::_24BitRgb [set $argVarName]]
+
+    if {[_ValidateColorEntry forced $col24bit]} {
+        set ::SelectColor::_entryColor $col24bit
+    } else {
+        # Value is invalid, and if written to _entryColor this would disable
+        # validation.
+    }
+
+    return
+}
+
+
+# ------------------------------------------------------------------------------
+#  Proc SelectColor::_CheckFocus
+# ------------------------------------------------------------------------------
+# This command is called with argument %W as a binding to <1> on the toplevel.
+# It is also called with argument {.}, by bindings on the entry widget to
+# <Escape>, <Return>.
+#
+# The command does something only if the entry widget has focus, and the
+# argument (the clicked window) is the Tk window path of somewhere else.  Then,
+# the command removes focus from the entry widget to the default button.
+# ------------------------------------------------------------------------------
+
+proc SelectColor::_CheckFocus {w} {
+    variable _widget
+
+    if {($w ne $_widget(en)) && ([focus] eq $_widget(en))} {
+        set top [winfo toplevel $_widget(en)]
+        $top setfocus default
+    }
+
+    return
+}
+
+
+# ------------------------------------------------------------------------------
+#  Proc SelectColor::_ValidateColorEntry
+# ------------------------------------------------------------------------------
+# This command is the "-validate all -vcmd" of the entry widget.
+# It is also called by SelectColor::dialog and SelectColor::_SetEntryValue to
+# check values assigned to _entryColor.
+#
+# When the user chooses a color by typing in the entry widget, this command
+# copies the value to $_varName if a keystroke in the widget makes its contents
+# 3N hex digits long.
+# ------------------------------------------------------------------------------
+
+proc SelectColor::_ValidateColorEntry {percentV percentP} {
+    variable _varName
+
+    set result [regexp -- {^#[0-9a-fA-F]*$} $percentP]
+    set lenny  [string length $percentP]
+
+    if {$result} {
+        if {$percentV eq "forced"} {
+            # Validation only.  Don't want a loop.
+        } elseif {($percentV eq "key")} {
+            # Copy to GUI if a valid color.
+            if {($lenny - 1) % 3 || $lenny == 1} {
+                # Not a valid color, which needs 3n+1 characters, n > 0
+            } else {
+                after idle [list SelectColor::_SetWithoutTrace $percentP]
+            }
+        } elseif {($percentV eq "focusout")} {
+            # If the color is valid it will already have been copied to the GUI
+            # by the "key" validation above.
+            #
+            # The code below only needs to reset the value in the entry widget.
+            # Remove an invalid value, convert a valid one to 24-bit.
+            # Ignore $percentP, just fire the trace on $_varName.
+            after idle [list set $_varName [set $_varName]]
+        }
+    }
+
+    return $result
+}
+
+
+# ------------------------------------------------------------------------------
+#  Proc SelectColor::_SetWithoutTrace
+# ------------------------------------------------------------------------------
+# This command sets $_varName (using _set_rgb) without firing the trace that
+# copies the value to _entryColor.
+# The command is called by SelectColor::_ValidateColorEntry to avoid a loop.
+# ------------------------------------------------------------------------------
+
+proc SelectColor::_SetWithoutTrace {value} {
+    variable _varName
+
+    trace remove variable $_varName write ::SelectColor::_SetEntryValue
+    _set_rgb $value
+    trace add variable $_varName write ::SelectColor::_SetEntryValue
+    return
+}
+
diff -Naur original-bwidget-1.9.6/pkgIndex.tcl patched-bwidget-1.9.6/pkgIndex.tcl
--- original-bwidget-1.9.6/pkgIndex.tcl	2013-06-13 19:04:33.000000000 +0100
+++ patched-bwidget-1.9.6/pkgIndex.tcl	2013-06-13 19:06:14.000000000 +0100
@@ -30,7 +30,7 @@
 {mainframe.tcl source {MainFrame MainFrame::create MainFrame::use}}
 {listbox.tcl source {ListBox ListBox::create ListBox::use}}
 {tree.tcl source {Tree Tree::create Tree::use}}
-{color.tcl source {SelectColor SelectColor::menu SelectColor::dialog SelectColor::setcolor}}
+{color.tcl source {SelectColor SelectColor::menu SelectColor::dialog SelectColor::setcolor SelectColor::setbasecolor}}
 {dynhelp.tcl source {DynamicHelp::configure DynamicHelp::use DynamicHelp::register DynamicHelp::include DynamicHelp::add DynamicHelp::delete}}
 {dialog.tcl source {Dialog Dialog::create Dialog::use}}
 {messagedlg.tcl source {MessageDlg MessageDlg::create MessageDlg::use}}