File: viewer.tcl

package info (click to toggle)
moomps 4.6-2
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 2,444 kB
  • ctags: 2,307
  • sloc: tcl: 34,882; sh: 167; makefile: 91
file content (362 lines) | stat: -rw-r--r-- 19,164 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# copyright (C) 1997-2005 Jean-Luc Fontaine (mailto:jfontain@free.fr)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu

# $Id: viewer.tcl,v 2.76 2005/02/13 16:46:22 jfontain Exp $


class viewer {                                                                             ;# handle viewers related functionalities
    # viewers do not derive from a common interface class but rather support a common set a options through the composite class

    set (list) {}
if {$global::withGUI} {
    set (background) $widget::option(label,background)              ;# so that all viewers have the same background on all platforms
    set (rightRedArrow) [image create photo -data {R0lGODlhBQAJAIAAAP8AAP8AACH5BAEKAAEALAAAAAAFAAkAAAIMRB5gp9v2YlJsJRQKADs=}]
    set (rightDarkGrayArrow) [image create photo -data {R0lGODlhBQAJAIAAAKCgoP///yH5BAEKAAEALAAAAAAFAAkAAAIMRB5gp9v2YlJsJRQKADs=}]
    set (limitAreaWidth) 40
    set (limitAreaHeight) 20
}

    proc viewer {this} {
        lappend (list) $this
    }

    proc ~viewer {this} {
        dataTrace::unregister $this                                                                       ;# remove all array traces
        if {[info exists ($this,drop)]} {
            delete $($this,drop)
        }
        ldelete (list) $this
if {$global::withGUI} {
        pages::monitorActiveCells                ;# refresh pages monitored cells since cells with thresholds could have disappeared
        thresholdLabel::monitorActiveCells                                               ;# refresh global thresholds viewer as well
}
    }

    virtual proc supportedTypes {this}

    proc view {this cells} {                                                                  ;# cells is a list of data array cells
        set list {}
        foreach cell $cells {
            parse $cell array row column type
            # no corresponding module (happens when loading a viewer from a save file without the corresponding module):
            if {![info exists $array]} continue
            if {[lsearch -exact [supportedTypes $this] $type] < 0} {                      ;# type must exist since cell array exists
if {$global::withGUI} {
                wrongTypeMessage $type
}
                return 0                                                                                     ;# give up on all cells
            }
            set update($array) {}
            lappend list $array $row $column
        }
        # warning: it is important to monitor cell in the original order as some viewers, such as freetext, expect it:
        foreach {array row column} $list {
            monitorCell $this $array $row $column
if {$global::withGUI} {
            # possibly initialize cell thresholds data:
            foreach {color level summary} [thresholds::cellData $array $row $column] {
                thresholdCondition $this $array $row $column $color $level $summary
            }
            setCellColor $this ${array}($row,$column) [cellThresholdColor $array $row $column]
}
        }
        foreach array [array names update] {                                        ;# update viewer with current values immediately
            update $this $array
        }
        return 1                                                                                       ;# cells accepted for viewing
    }

    virtual proc monitorCell {this array row column}

    # public procedure. note that type will be set only if the cell array actually exists when this procedure is invoked
    proc parse {dataCell arrayName rowName columnName typeName} {
        upvar 1 $arrayName cellArray $rowName cellRow $columnName cellColumn $typeName cellType

        if {([scan $dataCell {%[^(](%lu,%u)} array row column] != 3) || ($column < 0)} {
            error "\"$dataCell\" is not a valid array cell"
        }
        set cellArray $array; set cellRow $row; set cellColumn $column; catch {set cellType [set ${array}($column,type)]}
    }

    proc updateInterval {value} {                                             ;# static procedure for updating all viewers intervals
        foreach viewer $(list) {
            catch {composite::configure $viewer -interval $value}                 ;# some viewers do not support the interval option
        }
    }

    # Derive a label from cell using the tabular data index columns (array name must be qualified) and return it along with the
    # incomplete status, meaning that the label could not be fully determined, as can happen, for example, when the data cell no
    # longer exists.
    proc label {array row column {identify {}}} {                                                     ;# allow forcing module header
        set label {}
        if {[string length $identify] == 0} {
            set identify $global::cellsLabelModuleHeader                                                     ;# use default behavior
        }
        if {$identify} {
            set identifier [modules::identifier $array]                                         ;# see if array needs identification
            if {[string length $identifier] > 0} {               ;# data comes from a module array (could come from a summary table)
                regsub {<0>$} $identifier {} identifier            ;# remove trailing namespace index for first instance of a module
                set label "$identifier: "
            }
        }
        if {[catch {set ${array}(indexColumns)} columns]} {     ;# no index columns (note: they are used to generate the cell label)
            set columns 0                                                                 ;# use first column as single index column
        }
        foreach index $columns {                                                                      ;# use index columns for label
            if {[catch {set ${array}($row,$index)} value]} {
                append label {? }               ;# cell may no longer exist if originating from a save file, give user some feedback
            } elseif {[string length $value] > 0} {                                                            ;# ignore empty cells
                append label "$value "
            }
        }
        append label [set ${array}($column,label)]
        # consider the label incomplete if it contains any question mark characters, reflecting the presence of missing data rows,
        # void numeric data cells, or existing cells from a summary table pointing to void data. note that is valid data contains
        # question mark characters, then the incomplete status is incorrect, which should be rare and have only a minor impact on
        # performance (it causes the corresponding data labels to be updated at each poll in data viewers).
        return [list $label [string match {*\?*} $label]]
    }

    virtual proc update {this array}                                                              ;# update display using cells data

    proc registerTrace {this array {last 0}} {
        dataTrace::register $this $array "viewer::update $this $array" $last
    }

    proc unregisterTrace {this array} {
        dataTrace::unregister $this $array
    }

    # note: cells must always been returned in the same order (dictionary sorting suggested) so that record layer does not detect
    # erroneous changes, incorrectly requiring the user to save dashboard when exiting application
    virtual proc cells {this}

if {$global::withGUI} {

    virtual proc initializationConfiguration {this} { ;# configuration with switch / value option pairs for initialization from file
        return {}
    }

    proc setupDropSite {this path} {                                 ;# allow dropping of data cells, viewer mutation or kill action
        set ($this,drop) [new dropSite -path $path -formats {DATACELLS VIEWER KILL} -command "viewer::handleDrop $this"]
    }

    proc handleDrop {this} {
        if {![catch {set data $dragSite::data(DATACELLS)}]} {
            view $this $data
        } elseif {![catch {set data $dragSite::data(VIEWER)}]} {
            mutate $this $data
        } elseif {[info exists dragSite::data(KILL)]} {
            delete $this                                                                                           ;# self destructs
        }
    }

    proc mutate {this class} {
        if {[string equal $class [classof $this]]} return                            ;# no need to create a viewer of the same class
        set draggable [composite::cget $this -draggable]
        switch $class {
            ::currentValueTable {                                 ;# needs to know mode (real time or database) at construction time
                set viewer [new currentValueTable $global::canvas $global::pollTime -draggable $draggable]
            }
            ::canvas::iconic {
                if {[string length [set name [canvas::iconic::chooseFile]]] == 0} return                                 ;# canceled
                set viewer [new $class $global::canvas -draggable $draggable -static $global::static -file $name]
            }
            default {
                set viewer [new $class $global::canvas -draggable $draggable]
            }
        }
        foreach list [composite::configure $viewer] {
            if {[string equal [lindex $list 0] -interval]} {                                      ;# viewer supports interval option
                composite::configure $viewer -interval $global::pollTime                                  ;# so use current interval
                break                                                                                                        ;# done
            }
        }
        # only attempt to view cells that still exist (for example, some cells may come from a vanished summary table)
        set cells {}
        set count 0
        foreach cell [cells $this] {
            if {[info exists $cell]} {
                lappend cells $cell
            }
            incr count
        }
        if {[llength $cells] > 0} {
            view $viewer $cells
        }
        if {[llength $cells] < $count} {                                                                 ;# warn user when necessary
            lifoLabel::flash $global::messenger [mc {some data cells no longer exist}]
        }
        if {[manageable $this]} {
            foreach {x y width height} [canvasWindowManager::getGeometry $global::windowManager $widget::($this,path)] {}
            set level [canvasWindowManager::getStackLevel $global::windowManager $widget::($this,path)]
        } else {                                                                                          ;# must be a canvas viewer
            set x [composite::cget $this -x]; set y [composite::cget $this -y]                            ;# a viewer is a composite
            set width {}; set height {}; set level {}
        }
        delete $this                                                                                       ;# delete existing viewer
        if {[manageable $viewer]} {
            # viewer is as destroyable as previously deleted viewer
            manageViewer $viewer 1 -static $global::static -setx $x -sety $y -setwidth $width -setheight $height -level $level\
                -dragobject $viewer
        } else {                                                                                          ;# must be a canvas viewer
            composite::configure $viewer -x $x -y $y
        }
    }

    proc cellThresholdColor {array row column} {
        set manager [new thresholdsManager]
        set cell ${array}($row,$column)
        foreach {color level summary} [thresholds::cellData $array $row $column] {
            thresholdsManager::condition $manager $cell $color $level $summary
        }
        set color [lindex [lindex [thresholdsManager::colorsAndTexts $manager] 0] 0]
        delete $manager
        return $color
    }

    proc cellThresholdCondition {array row column color level summary} {
        set topColor [cellThresholdColor $array $row $column]
        foreach viewer $(list) {
            thresholdCondition $viewer $array $row $column $color $level $summary
            setCellColor $viewer ${array}($row,$column) $topColor
        }
    }

    # viewers should implement at most one of the following procedures:
    # note: viewers using a dataTable object might need none, as ::cellThresholdCondition{} may be enough to do the job

    # latest threshold condition (reset if summary is not defined (empty)):
    virtual proc thresholdCondition {this array row column color level summary} {}
    # gives the color of the most important and recent threshold (reset if color is empty):
    virtual proc setCellColor {this cell color} {}

    virtual proc manageable {this} {return 1}                         ;# whether it should be managed by the internal window manager

    proc monitoring {cell} {                                                                            ;# viewers monitoring a cell
        set viewers {}
        foreach viewer $(list) {
            if {[monitored $viewer $cell]} {
                lappend viewers $viewer
            }
        }
        return $viewers
    }

    virtual proc monitored {this cell}

    # If this is the first time a color is requested for that cell, give a new color, else give the color already used by that cell,
    # so that a cell always has the same color in all its container viewers, until it is no longer monitored by any viewer.
    # When the cell is no longer displayed by the viewer, the color must be returned using returnDisplayColor{}, so that the color
    # can be made available for other cells.
    proc getDisplayColor {cell} {
        variable colorIndex                                          ;# cell to color index mapping (index in global viewers colors)
        variable usageCount                                                   ;# number of times a cell is displayed using its color

        if {![info exists colorIndex($cell)]} {                           ;# return a new color, if possible, not used by any viewer
            set colors [llength $global::viewerColors]
            for {set index 0} {$index < $colors} {incr index} {
                set count($index) 0
            }
            foreach {name index} [array get colorIndex] {                                                        ;# scan used colors
                incr count($index)
            }
            set color 0
            set minimum $global::32BitIntegerMaximum
            for {set index 0} {$index < $colors} {incr index} {                                    ;# find the next least used color
                if {$count($index) < $minimum} {
                    set minimum $count($index)
                    set color $index
                }
            }
            set colorIndex($cell) $color
            set usageCount($cell) 0
        }
        incr usageCount($cell)
        return [lindex $global::viewerColors $colorIndex($cell)]
    }
    proc returnDisplayColor {cell} {                                 ;# use to return display color obtained using getDisplayColor{}
        variable colorIndex
        variable usageCount

        if {[catch {incr usageCount($cell) -1}]} return                                            ;# cell color came from save file
        if {$usageCount($cell) == 0} {                                                          ;# cell no longer displayed in color
            unset colorIndex($cell) usageCount($cell)
        }
    }

    proc limitEntry {this path anchor x y option name font command yCommand type} {                  ;# note: Y command may be empty
        set entry [entry $path.maximum -borderwidth 0 -highlightthickness 0 -width 10 -font $font]
        switch $type {
            float {setupEntryValidation $entry {{checkFloatingPoint %P}}; set type double}
            signed {setupEntryValidation $entry {{check32BitSignedInteger %P}}; set type integer}
            unsigned {setupEntryValidation $entry {{check31BitUnsignedInteger %P}}; set type integer}
            default error
        }
        lifoLabel::push $global::messenger\
            [format [mc {enter %s value (empty for automatic scale, Return to valid, Escape to abort)}] $name]
        # when Return or Enter is pressed, pass new value (if valid type) as limit, else if Escape is pressed, abort:
        foreach key {<KP_Enter> <Return>} {
            bind $entry $key "
                if {\[string is $type \[%W get\]\]} {
                    $command \[%W get\]
                    destroy %W
                    lifoLabel::pop $global::messenger
                }
            "
        }
        bind $entry <Escape> "destroy %W; lifoLabel::pop $global::messenger"
        $entry insert 0 [composite::cget $this $option]
        $entry selection range 0 end                                  ;# initially select all characters, which is a common behavior
        if {[string length $yCommand] > 0} {set y [uplevel #0 $yCommand]}                                  ;# allow dynamic ordinate
        place $entry -anchor $anchor -x $x -y $y
        focus $entry
        ::update idletasks                                                                     ;# so entry is visible and grab works
        grab $entry
    }

    proc wrongTypeMessage {type} {
        lifoLabel::flash $global::messenger [format [mc {cannot display data of type %s}] $type]
        bell
        if {![info exists (wrongTypeDrop)]} {
            set (wrongTypeDrop) {}
            tk_messageBox -title {moodss: drag and drop} -type ok -icon info -message [format [mc\
                {Some viewers only accept a limited set of data types (if this case, this viewer cannot display data of type %s)}\
            ] $type]
        }
    }

    proc createColorsMenu {parentPath command} {
        set menu [menu $parentPath.colorsMenu -tearoff 0]
        set spaces {   }                            ;### Tk bug: use 6 spaces for windows because otherwise labels look too thin ###
        if {[string equal $::tcl_platform(platform) windows]} {set spaces {      }}
        set rows 0
        set index 0
        foreach color $global::viewerColors {
            $menu add command -label $spaces -background $color -activebackground $color
            regsub -all %c $command $color string                                                            ;# use color in command
            $menu entryconfigure $index -hidemargin 1 -command $string
            if {$rows >= 3} {
                $menu entryconfigure $index -columnbreak 1
                set rows 0
            }
            incr rows
            incr index
        }
        return $menu
    }

    virtual proc updateLabels {this} {}

}

    proc numericType {type} {
        switch $type {
            integer - real {return 1}
            default {return 0}
        }
    }

    virtual proc saved {this} {return 1}                               ;# whether it is supposed to be saved in a configuration file

}