File: procs.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 (740 lines) | stat: -rw-r--r-- 42,532 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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
# 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


# various procedures mostly used in moodss.tcl

# $Id: procs.tcl,v 1.95 2005/02/19 21:10:34 jfontain Exp $


set dialogBoxErrorIcon [image create photo -data {
    R0lGODlhIAAgAOfJAIkAAIoAAIsAAIwAAI4AAI8AAJAAAJEAAJIAAJQAAJUAAJYAAJcAAJMCApgAAJkAAJoAAJsAAJwAAJMEBJ0AAJ4AAJ8AAKAAAKEAAKMA
    AJcGBqUAAKYAAKcAAKgAAKkAAKoAAKsAAKwAAJ8GBq0AAK4AAK8AALEAAKEHB7IAAKkEBLMAAJwKCrQAALUAALYAALcAALgAALkAALUCAroAAKELC7sAALwA
    AL0AAL4AAL8AAMAAAMEAAKoKCsIAAMMAAMQAAMUAAMYAAMgAAMkAAMoAAMsAAMwAALwHB80AAM4AANAAALcLC9UAANgAANoAANwAAN4AAN8AALcSEs0JCeIA
    AK0XF64XF68XF9AJCeYAALEXF+4AAN0JCfIAAMoSEvQAAPcAANcODuoGBvoAAPkBAf4AAP0BAf8CAvkICP8HB+4VFfkREf8QEMcpKfUWFv8SEvEZGfQYGM0p
    Kc4pKfUZGesfH9YpKfkaGv8dHfUiIvkiIu8oKP8iIvgmJvomJuouLvcpKf8oKPcvL/gwMPowMPE2NvQ2Nv8zM/80NPM6Ov81Ne0/P/M+Pu5DQ+pFRexFRf8/
    P/9FRf9KSv9RUftTU/dWVvpVVftVVf9WVvdcXP9nZ/hra/9sbPVxcfZ0dPl0dP5ycvxzc/9ycvl7e/95ef+Kiv6QkP+amvqfn/yfn/2fn/6fn/+fn/6goP+g
    oPykpP+jo/+np/ysrP+rq/+trfyvr/+xsf+4uP+8vP++vv6/v//Bwf/Fxf/Hx//Kyv/MzP3Nzf7Nzf/Ozv/U1P/b2//c3P/h4f/r6///////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////yH5BAEKAP8ALAAAAAAgACAAAAj+AP8JHEiwoMGD
    CBEKCfLDxw4dOGwknCiQyJCFQpQkOWJEB40YMFxQNFhkyBEodjSRWkmKE6AmOFqkGCnQSBEjUizZQsazJ89goMS4MEGCos0lfGAZW8q06VJhjmiIAJGwSBEn
    jXYB28q1q1dPQT50QFhkiaJcaH+hXcs2l1q0n1xwOEjEyBpXtGgBS3Ysr1+/x5IB88voQ4aCFqWIQoWKV7LHxRhLRlXscTJejE9lwXCBoEk9pky9svyYWOjQ
    xEgnexX6EogKBIVEqVSq9l7Sw2oPUw0sFKZBcoZIGLhwzKbjx3up9uVL9axAXqJ7qRLhgUAgQt5k2s5dl2rVqaT+i7fgQOAPJXskqV8vCdf3x+HFSy+xQCCP
    I3gQ6d+v/9b3+PJFR0ICAu1gBBt9JKhggrX8F6B0IiAgUA5FpNHGhRi2Ict7yaQCxocgfhjCAQLdoEMZaKSo4mgcJqNKGDDGGAYDBghkgwxcnKGjjq6otsoq
    PpIhpJBaFEDAQDE8YcaSZrCiWitLtqIaK0yaIcSRA72QgxpcLqJaLFxyGYtqi4QpwQAEtbBCF3C02YllsrQpJxwbPtaJnFQMIEBBJ8xQRx6AjpIMLYAWWigt
    yYxSaBwNBHBQCUj8IciklExq6aWCVDqpHyg4epAIIXxRSCKklmrqqYkQMkUAACT0gQdOTBwSyay01lqrITWwShEHG6hwxyTABisssHNMoCtNnPVAByTccfeI
    GywM4ClNAllAQQQQjLAFFldYoYGR0lJ70AMMKJAAAgcYKe66NAUEADs=
}]

proc printUsage {exitCode} {
    puts stderr [format [mc {Usage: %s [OPTION]... [MODULE] [OPTION]... [MODULE]...}] $::argv0]
    puts stderr [mc {  --debug          module errors verbose reporting}]
    puts stderr [mc {  -f, --file       dashboard file name}]
    puts stderr [mc {  -h, --help       display this help and exit}]
    puts stderr [mc {  -p, --poll-time  poll time in seconds}]
    puts stderr [mc {  -r, --read-only  disable viewer creation, editing, ...}]
    puts stderr [mc {  -S, --static     disable internal window manager sizing and moving}]
    puts stderr [mc {  --show-modules   try to find available moodss modules}]
    puts stderr [mc {  --version        output version information and exit}]
    exit $exitCode
}

proc printVersion {} {
    puts [format [mc {moodss (Modular Object Oriented Dynamic SpreadSheet) version %s}] $global::applicationVersion]
}

proc loadFromFile {name} {                                       ;# eventually unload all existing modules and load from a save file
    clearModulesAndViewers
    set global::saveFile $name
    set global::fileDirectory [file dirname $name]
    set initializer [new record -file $name]
    record::read $initializer
    configuration::load [record::configurationData $initializer]                                 ;# set global values from save file
    modules::parse [record::modulesWithArguments $initializer]                                                          ;# recursive
    set modules::(initialized) [record::modules $initializer]
    return $initializer
}

proc clearModulesAndViewers {} {
    foreach viewer $viewer::(list) {                                         ;# delete all existing viewers except thresholds viewer
        set class [classof $viewer]
        switch $class {
            ::store - ::store::dialog - ::thresholdLabel - ::thresholds {
                ${class}::reset $viewer                                         ;# make sure to remove now obsolete existing entries
            }
            default {
                delete $viewer                  ;# note: in formulas table case, that also unloads the corresponding formulas module
            }
        }
    }
    if {[info exists databaseInstances::singleton]} {
        delete $databaseInstances::singleton
    }
    foreach instance [modules::instancesWithout formulas] {
        dynamicallyUnloadModule $modules::instance::($instance,namespace)
    }
    if {[llength [modules::instancesWithout]] > 0} {
        error {internal moodss error: please report to author with error trace}
    }
}

proc clear {} {                  ;# return true if user went ahead and cleared main window or return false in case of change in mind
    static busy 0

    if {$busy} return             ;# protection against overlapping invocations from fast clicking, as a lot of updates occur within
    if {[needsSaving]} {                             ;# see if there are changes that the user may want to be saved before reloading
        switch [inquireSaving] {
            yes {
                save
                if {[needsSaving]} {return 0}                        ;# data was not saved: assume the user wants to abort reloading
            }
            cancel {return 0}
        }
    }
    set busy 1
    if {[info exists ::initializer]} {
        delete $::initializer
        unset ::initializer
    }
    clearModulesAndViewers
    databaseConnection 0                                                                                 ;# disconnect from database
    set global::saveFile {}
    updateFileSaveHelp {}                                                                      ;# to reflect save file disappearance
    updateTitle
    updateMenuWidget
    updateToolBar
    updateDragAndDropZone
    configuration::load [preferences::read]                                                             ;# reinitialize from rc file
    record::snapshot   ;# take a snapshot of new configuration os that user is not asked to save wnen trying to open a new file next
    set busy 0
    return 1
}

proc reload {} {                                                                      ;# restart by loading modules from a save file
    if {[needsSaving]} {                                    ;# there are changes that the user may want to be saved before reloading
        switch [inquireSaving] {
            yes {
                save
                if {[needsSaving]} return                            ;# data was not saved: assume the user wants to abort reloading
            }
            cancel return
        }
    }
    set file [tk_getOpenFile\
        -title [mc {moodss: Open}] -initialdir $global::fileDirectory -defaultextension .moo\
        -filetypes [list [list [mc {moodss dashboard}] .moo]]\
    ]
    if {[string length $file] == 0} return                                                                  ;# user canceled loading
    databaseConnection 0                                                                                 ;# disconnect from database
    set global::fileDirectory [file dirname $file]
    if {[info exists ::initializer]} {
        delete $::initializer
        unset ::initializer
    }
    updateCanvasImage {}; set global::canvasImageFile {}                                       ;# possibly clear existing background
    set ::initializer [loadFromFile $file]
    $global::canvas configure -background $global::canvasBackground
    wm geometry . {}
    foreach {width height} [record::sizes $::initializer] {}                                                 ;# used stored geometry
    composite::configure $global::scroll -width $width -height $height
    updateCanvasImage $global::canvasImageFile 1
    modules::initialize 0 initializationErrorMessageBox
    modules::setPollTimes [record::pollTime $::initializer]
    foreach instance [modules::instancesWithout formulas] {
        displayModule $instance $::draggable
    }
    summaryTable::reset; currentValueTable::reset; formulas::table::reset  ;# so that data names are generated as when first started
    createSavedImages $::initializer     ;# note: existing images, if any, were deleted at the same time as their associated viewers
    createSavedViewers $::initializer
    if {[pages::current] == 0} {                                                                                         ;# no pages
        manageScrolledCanvas 1
    } else {
        pages::manageScrolledCanvas 1
    }
    updateTitle
    updateMenuWidget
    updateToolBar
    updateDragAndDropZone
    updateFileSaveHelp $file                                                                  ;# since current save file has changed
    updateCanvasImagesPosition
    refresh                                                                               ;# make sure data is immediately displayed
    update                                               ;# required so that table and viewer windows sizes are correct for snapshot
    record::snapshot                       ;# take a snap shot of new configuration so any future changes are detected as meaningful
}

proc createNewCellsViewer {class cells draggable static {pollTime {}}} {
    switch $class {
        ::canvas::iconic {
            if {[string length [set name [canvas::iconic::chooseFile]]] == 0} return                                     ;# canceled
            foreach {left top right bottom} [$global::canvas cget -scrollregion] {}
            # place new icon at the top left corner of the current page
            set viewer [new $class $global::canvas -draggable $draggable -static $static -file $name -x $left -y $top]
            canvas::viewer::flash $viewer                                         ;# in case it is hidden by other graphical objects
        }
        ::currentValueTable {                                     ;# needs to know mode (real time or database) at construction time
            set viewer [new currentValueTable $global::canvas $global::pollTime -draggable $draggable -interval $pollTime]
        }
        ::dataGraph - ::dataStackedGraph - ::dataSideBarChart - ::dataStackedBarChart - ::dataOverlapBarChart {
            if {[string length $pollTime] == 0} {
                set viewer [new $class $global::canvas -labelsposition $global::graphLabelsPosition -draggable $draggable]
            } else {
                set viewer [new $class $global::canvas\
                    -labelsposition $global::graphLabelsPosition -draggable $draggable -interval $pollTime\
                ]
            }
        }
        default {
            if {[string length $pollTime] == 0} {
                set viewer [new $class $global::canvas -draggable $draggable]
            } else {
                set viewer [new $class $global::canvas -draggable $draggable -interval $pollTime]
            }
        }
    }
    if {[viewer::view $viewer $cells]} {
        if {[viewer::manageable $viewer]} {
            manageViewer $viewer 1 -static $static -dragobject $viewer
        }
        return $viewer
    } else {                                                                   ;# do not create new viewer if cells cannot be viewed
        delete $viewer
        return 0
    }
}

proc createNewFormulasViewer {object category draggable static} {
    set viewer [new formulas::table $global::canvas -draggable $draggable -object $object -category $category]
    manageViewer $viewer 1 -static $static -dragobject $viewer -title [formulas::table::title $viewer]
    return $viewer
}

proc manageViewer {viewer destroyable args} {        ;# viewer or table, arguments are window manager configuration switched options
    set path $widget::($viewer,path)
    canvasWindowManager::manage $global::windowManager $path $viewer
    eval canvasWindowManager::configure $global::windowManager $path $args
    if {$destroyable} {
        composite::configure $viewer -deletecommand "canvasWindowManager::unmanage $global::windowManager $path"
    }
}

proc save {{ask 0}} {                           ;# save current configuration in user defined file or currently defined storage file
    if {$ask || ([string length $global::saveFile] == 0)} {
        set file [tk_getSaveFile\
            -title [mc {moodss: Save}] -initialdir $global::fileDirectory -defaultextension .moo\
            -filetypes [list [list [mc {moodss dashboard}] .moo]] -initialfile $global::saveFile\
        ]
        if {[string length $file] == 0} return                                                               ;# user canceled saving
        set global::saveFile $file
        set global::fileDirectory [file dirname $file]
        updateFileSaveHelp $file
    }
    lifoLabel::push $global::messenger [format [mc {saving in %s...}] $global::saveFile]
    update idletasks                                                                                 ;# make sure message is visible
    set record [new record -file $global::saveFile]
    set error [catch {record::write $record} message]
    lifoLabel::pop $global::messenger
    if {$error} {
        tk_messageBox -title [mc {moodss: Save}] -type ok -icon error -message $message
    }
    delete $record
    if {!$error} record::snapshot                                                                  ;# remember current configuration
}

proc refresh {} {
    static updateEvent

    catch {after cancel $updateEvent}                                                             ;# eventually cancel current event
    if {[llength $modules::(synchronous)] == 0} return                                                              ;# nothing to do
    foreach instance $modules::(synchronous) {
        set namespace $modules::instance::($instance,namespace)
        ${namespace}::update                                                                ;# ask module to update its dynamic data
    }
    foreach viewer $viewer::(list) {                                                                       ;# update formulas tables
        if {[string equal [classof $viewer] ::formulas::table]} {
            formulas::table::update $viewer                                                  ;# direct update bypassing viewer layer
        }
    }
    if {$global::pollTime > 0} {                                                                   ;# any synchronous modules loaded
        set updateEvent [after [expr {1000 * $global::pollTime}] refresh]                                 ;# convert to milliseconds
    }
}

# invoked by thresholds code when a threshold condition occurs (color and level are empty for resetting)
proc cellThresholdCondition {array row column color level summary} {    ;# summary is a short description of the threshold condition
    dataTable::cellThresholdCondition $array $row $column
    viewer::cellThresholdCondition $array $row $column $color $level $summary
}

proc inquireSaving {} {
    if {[string length $::global::saveFile] > 0} {                                                           ;# there is a save file
        set message [format [mc {There are unsaved configuration changes. Do you want them saved to file: %s?}] $::global::saveFile]
    } else {
        set message [mc {There are unsaved configuration changes. Do you want them saved to file?}]
    }
    array set answer {0 yes 1 no 2 cancel}
    return $answer([tk_dialog .saveorexit [mc {moodss: Save}] $message question 0 [mc Yes] [mc No] [mc Cancel]])
}

proc needsSaving {} {                                                              ;# no need to save if there are no loaded modules
    return [expr {[record::changed] && ([llength [modules::instancesWithout formulas]] > 0)}]
}

proc manageToolBar {{save 1}} {                                                              ;# whether to save state in preferences
    set bar [updateToolBar]
    if {$global::showToolBar} {
        grid $bar -row 0 -column 0 -sticky we
    } else {
        grid forget $bar
    }
    if {$save} {
        preferences::update
    }
}

proc createSavedImages {record} {
    foreach {file data} [record::imagesData $record] {
        images::load $file {} $data
    }
}

proc createSavedViewers {record} {                                                                   ;# strictly viewers, not tables
    if {[llength [set range [record::databaseRange $record]]] > 0} {                               ;# saved in database history mode
        monitorDatabaseInstances $range                                                    ;# display loaded instance modules viewer
    }
    set data [record::viewersData $record]
    foreach {class cells x y width height level xIcon yIcon switchedOptions} $data {
        # process formulas table viewers first since they create namespaces and thus data that other viewers may display
        if {![string equal $class ::formulas::table]} continue
        set viewer [eval new ::formulas::table $global::canvas $switchedOptions -draggable $::draggable]
        manageViewer $viewer 1 -static $global::static -setx $x -sety $y -setwidth $width -setheight $height -level $level\
            -dragobject $viewer -iconx $xIcon -icony $yIcon -title [formulas::table::title $viewer]             ;# always manageable
        set viewerCells($viewer) $cells                                                                              ;# gather cells
    }
    foreach {class cells x y width height level xIcon yIcon switchedOptions} $data {                    ;# process remaining viewers
        switch $class {
            ::formulas::table continue
            ::store - ::thresholds {
                set viewer [set ${class}::singleton]         ;# store and thresholds viewers are special cases and are not displayed
                eval switched::configure $viewer $switchedOptions
            }
            ::thresholdLabel {
                set viewer [set ${class}::singleton]                ;# threshold label viewer is a special case and is not displayed
                eval composite::configure $viewer $switchedOptions
            }
            ::currentValueTable {                                 ;# needs to know mode (real time or database) at construction time
                set viewer [eval new currentValueTable\
                    $global::canvas $global::pollTime $switchedOptions -interval $global::pollTime -draggable $::draggable\
                ]
                manageViewer $viewer 1 -static $global::static -setx $x -sety $y -setwidth $width -setheight $height\
                    -level $level -dragobject $viewer
            }
            ::canvas::iconic {
                set viewer [eval new $class $global::canvas $switchedOptions -draggable $::draggable -static $global::static]
            }
            ::page {
                set viewer [eval new $class $global::canvas $switchedOptions -draggable $::draggable]
                set background {}
                foreach {switch value} $switchedOptions {
                    if {[string equal $switch -background]} {
                        set background $value
                        break
                    }
                }
                if {[string length $background] == 0} {                                                   ;# pre-18.4 dashboard file
                    composite::configure $viewer -background $global::canvasBackground                     ;# use default background
                }
            }
            default {
                set viewer [eval new $class $global::canvas $switchedOptions -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
                    }
                }
                if {[viewer::manageable $viewer]} {
                    manageViewer $viewer 1 -static $global::static -setx $x -sety $y -setwidth $width -setheight $height\
                        -level $level -dragobject $viewer
                }
            }
        }
        set viewerCells($viewer) $cells                                                                              ;# gather cells
    }
    # monitor cells now that all viewers exist (for example, summary tables have their own data and thus need be created before
    # other viewers can reference them)
    foreach {viewer cells} [array get viewerCells] {
        viewer::view $viewer $cells
    }
}

# must be invoked only when the application is running, that is after all the save file and command line modules have been loaded
proc dynamicallyLoadModules {arguments} {     ;# arguments list format is: module [-option [value] -option ...] module [-option ...]
    set instances [modules::instancesWithout formulas]
    modules::parse $arguments
    modules::initialize                                               ;# initializes only modules that have not yet been initialized
    modules::setPollTimes                                            ;# recalculate valid poll times but do not change current value
    set first 1
    foreach instance [modules::instancesWithout formulas] {
        if {[lsearch -exact $instances $instance] >= 0} continue
        # new module or instance:
        if {$first} {                ;# reset next module table coordinates so that successively loaded modules do not go off screen
            displayModule $instance $::draggable 1
            set first 0
        } else {
            displayModule $instance $::draggable
        }
    }
    updateTitle
    updateMenuWidget
    updateToolBar
    refresh                                                                               ;# make sure data is immediately displayed
}

proc dynamicallyUnloadModule {namespace} {
    foreach instance [modules::instancesWithout formulas] {
        if {[string equal $modules::instance::($instance,namespace) $namespace]} break                                      ;# found
    }
    if {[lindex $modules::instance::($instance,times) 0] >= 0} {                                    ;# then if module is synchronous
        ldelete modules::(synchronous) $instance                                                              ;# remove it from list
    }
    foreach table $dataTable::(list) {                                                                      ;# delete related tables
        if {[string equal [modules::namespaceFromArray [composite::cget $table -data]] $namespace]} {
            canvasWindowManager::unmanage $global::windowManager $widget::($table,path)
            delete $table
        }
    }
    modules::instance::empty $instance                             ;# empty module data so that related viewers can show empty cells
    modules::unload $instance
    modules::setPollTimes                                            ;# recalculate valid poll times but do not change current value
    updateTitle
    updateMenuWidget
    updateToolBar
}

proc residentTraceModule {display} {                                      ;# initialize and eventually display resident trace module
    if {![winfo exists .trace]} {
        toplevel .trace
        wm withdraw .trace
        wm group .trace .                                            ;# for proper window manager (windowmaker for example) behavior
        wm title .trace [mc {moodss: Trace}]
        set namespace $modules::instance::($modules::(trace),namespace)
        set table [new dataTable .trace -data ${namespace}::data]
        dataTable::update $table                                      ;# force refreshing of the display so data appears immediately
        # handle closing via window manager:
        wm protocol .trace WM_DELETE_WINDOW "wm withdraw .trace; set global::showTrace 0"
        pack $widget::($table,path) -fill both -expand 1
    }
    if {$display} {
        wm deiconify .trace
    } else {
        wm withdraw .trace
    }
    after idle {focus .}                                                                        ;# keep the focus on the main window
}

proc displayModule {instance draggable {resetOrigin 0}} {
    static x
    static y

    if {![info exists x] || $resetOrigin} {
        foreach {x y dummy dummy} [$global::canvas cget -scrollregion] {}                           ;# next module table coordinates
    }
    if {[lindex $modules::instance::($instance,times) 0] >= 0} {                                         ;# if module is synchronous
### should be done in modules code ###
        lappend modules::(synchronous) $instance
    }
    if {[info exists modules::instance::($instance,views)]} {                             ;# check whether several views are defined
        set viewMembers $modules::instance::($instance,views)                             ;# create and manage a table for each view
    } else {
        set viewMembers {{}}                                                           ;# there is a single table (the default view)
    }
    set index 0
    set namespace $modules::instance::($instance,namespace)
    foreach members $viewMembers {
        set initialize [expr {[info exists ::initializer] && ([lsearch -exact $modules::(initialized) $namespace] >= 0)}]
        if {$initialize} {
            set arguments [record::tableOptions $::initializer $namespace $index]                ;# extra stored arguments for table
        } else {
            set arguments {}
        }
        if {![catch {set ${namespace}::data(resizableColumns)} resizable]} {
            lappend arguments -resizablecolumns $resizable
        }
        if {[llength $members] > 0} {                                                                                ;# it is a view
            array set ::view$instance $members
            set table\
                [eval new dataTable $global::canvas -data ${namespace}::data -view ::view$instance -draggable $draggable $arguments]
            unset ::view$instance
        } else {                                                                                     ;# use single default data view
            set table [eval new dataTable $global::canvas -data ${namespace}::data -draggable $draggable $arguments]
        }
        if {[info exists modules::instance::($instance,identifier)]} {                                 ;# set a title for data table
            set title $modules::instance::($instance,identifier)                                    ;# favor identifier if it exists
        } else {                                                                                           ;# simply use module name
            set title $namespace
        }
        regsub {<0>$} $title {} title                              ;# remove trailing namespace index for first instance of a module
        if {$initialize} {                                                                       ;# setup initialized modules tables
            # use stored window manager initialization data for table if it exists:
            set list [record::tableWindowManagerData $::initializer $namespace $index]
            if {[llength $list] > 0} {
                foreach {x y width height level xIcon yIcon} $list {}
                manageViewer $table 0 -static $global::static -setx $x -sety $y -setwidth $width -setheight $height\
                    -level $level -title $title -iconx $xIcon -icony $yIcon
            } else {
                manageViewer $table 0 -static $global::static -setx $x -sety $y -title $title
            }
        } else {
            manageViewer $table 0 -static $global::static -setx $x -sety $y -title $title
        }
        set x [expr {$x + $global::xWindowManagerInitialOffset}]                          ;# offset tables to achieve a nicer layout
        set y [expr {$y + $global::yWindowManagerInitialOffset}]
        incr index                                                                                      ;# next view for initializer
    }
}

proc initializationErrorMessageBox {namespace message} {                                                  ;# namespace of the module
    set top [new toplevel .]
    set path $widget::($top,path)
    wm transient $path .
    regsub {<0>$} $namespace {} namespace                          ;# remove trailing namespace index for first instance of a module
    wm title $path [format [mc {moodss: Error initializing module "%s"}] $namespace]
    wm group $path .                                                 ;# for proper window manager (windowmaker for example) behavior
    wm protocol $path WM_DELETE_WINDOW "delete $top"                                                                 ;# self cleanup
    set text [message $path.message -text $message -font $font::(mediumNormal) -justify left -width 640]
    grid rowconfigure $path 0 -weight 1
    grid columnconfigure $path 1 -weight 1
    grid [label $path.icon -image $::dialogBoxErrorIcon] -row 0 -column 0 -sticky nw -padx 2 -pady 2
    grid $text -row 0 -column 1 -sticky nw
    grid [frame $path.separator -relief sunken -borderwidth 1 -height 2] -row 1 -column 0  -columnspan 100 -stick we -pady 2
    grid [button $path.close -text [mc Close] -command "destroy $path"] -row 2 -column 0 -columnspan 100 -padx 2 -pady 2
}

proc databaseConnection {connect} {                                               ;# boolean: or disconnect, procedure is idempotent
    if {$connect} {
        if {$global::database != 0} return                                                                      ;# already connected
        lifoLabel::push $global::messenger [mc {connecting to database...}]
    } else {
        if {$global::database == 0} return                                                                   ;# already disconnected
        lifoLabel::push $global::messenger [mc {disconnecting from database...}]
    }
    busy 1 .
    if {$connect} {
        set database [eval new database $global::databaseOptions]
        if {[string length $database::($database,error)] > 0} {              ;# there was a problem probably due to misconfiguration
            tk_messageBox -title [mc {moodss: Database error}] -type ok -icon error -message $database::($database,error)
            delete $database
        } else {
            if {$database::($database,created)} {
                modules::trace {} moodss(database) [mc {created tables in moodss database}]
            }
            set global::database $database
        }
    } else {
        delete $global::database                                                                                       ;# disconnect
        set global::database 0
    }
    busy 0 .
    lifoLabel::pop $global::messenger
}

proc loadFromDatabase {draggable static} {                                    ;# get ready to load data cell histories from database
    if {![info exists databaseInstances::singleton]} {                                               ;# not in database mode already
        if {![clear]} return                                                    ;# eventually unload all modules unless user aborted
        databaseConnection 1                                                                                  ;# connect to database
        if {$global::database == 0} return                                                             ;# database connection failed
    }
    database::displayAndSelectInstances             ;# show dialog box with modules instances, data cells histories, ... in database
    createInstancesViewer $draggable $static
    updateMenuWidget
    updateToolBar
    updateDragAndDropZone
    # monitor selected instance if any and eventually delete empty instances viewer when closed:
    switched::configure $database::(dialog) -command "databaseInstances::monitor $databaseInstances::singleton"\
       -deletecommand {after idle {databaseInstances::deleteEmpty}}  ;# note: avoid loop in mutual destruction with instances viewer
}

proc createInstancesViewer {draggable static} {
    if {[info exists databaseInstances::singleton]} return
    set instances [new databaseInstances $global::canvas -draggable $draggable]               ;# create database instances singleton
    set path $widget::($instances,path)
    canvasWindowManager::manage $global::windowManager $path $instances
    set title [mc {database module instances}]
    if {[info exists ::initializer] && ([llength [set list [record::databaseViewerWindowManagerData $::initializer]]] > 0)} {
        foreach {x y width height xIcon yIcon} $list {}                                     ;# recorded window manager configuration
        canvasWindowManager::configure $global::windowManager $path\
            -setx $x -sety $y -setwidth $width -setheight $height -iconx $xIcon -icony $yIcon\
            -static $static -title $title -level $global::32BitIntegerMinimum                ;# always stack below all other windows
    } else {
        canvasWindowManager::configure $global::windowManager $path\
            -static $static -title $title -level $global::32BitIntegerMinimum                ;# always stack below all other windows
    }
    # clear on self deletion to avoid recursion in clear{}, delayed to avoid loop in mutual destruction with instances dialog box
    composite::configure $instances -selfdeletecommand {after idle clear}\
        -deletecommand "canvasWindowManager::unmanage $global::windowManager $path; database::removeInstances"
}

proc databaseRecording {start} {                                                                           ;# boolean: start or stop
    if {$start} {
        databaseConnection 1                                                                                  ;# connect to database
        if {$global::database == 0} return                                                             ;# database connection failed
        refresh                         ;# attempt data storage from store class so that eventual errors can be detected immediately
    } else {
        databaseConnection 0                                                                             ;# disconnect from database
    }
    updateMenuWidget
    updateToolBar
}

proc monitorDatabaseInstances {presetRange} {                                  ;# used when loading a database history configuration
    databaseConnection 1                                                                                      ;# connect to database
    if {$global::database == 0} {exit 1}                                             ;# database connection failure is a fatal error
    createInstancesViewer [expr {!$global::readOnly}] $global::static
    foreach instance [modules::instancesWithout formulas] {                      ;# display all instance modules in instances viewer
        foreach {name index} [modules::decoded $modules::instance::($instance,namespace)] {}    ;# index is database instance number
        if {![string equal $name instance]} {error "not an instance module in history mode: $name"}
        array set option $modules::instance::($instance,arguments)
        databaseInstances::monitor $databaseInstances::singleton [list $index $name $option(-identifier) $option(-arguments)] 0
    }
    eval databaseInstances::setCursors $databaseInstances::singleton $presetRange
}

proc updateCanvasImage {file {initialize 0}} {                                ;# note: does not update canvas image global variables
    # use full file path, as current directory may change during the lifetime of the application or dashboard
    if {[package vcompare $::tcl_version 8.4] < 0} {
        if {[string length $file] > 0} {set file [file join [pwd] $file]}
    } else {
        set file [file normalize $file]
    }
    if {[string equal $file $global::canvasImageFile] && !$initialize} return                                           ;# no change
    if {([string length $global::canvasImageFile] > 0) && !$initialize} {
        images::release $global::canvasImageFile
    }
    if {[string length $file] == 0} {
        if {[info exists global::canvasImageItem]} {
            $global::canvas delete $global::canvasImageItem; unset global::canvasImageItem
        }
    } else {
        images::load $file $file {}
        set image [images::use $file]
        if {[info exists global::canvasImageItem]} {
            $global::canvas itemconfigure $global::canvasImageItem -image $image
        } else {
            set global::canvasImageItem [$global::canvas create image 0 0 -image $image]
        }
        $global::canvas lower $global::canvasImageItem                             ;# background image should be below anything else
    }
}

proc updateCanvasImagePosition {item position {offset 0}} {
    set canvas $global::canvas
    foreach {left top right bottom} [bounds $canvas] {}                                                           ;# top is always 0
    switch $position {
        nw {
            $canvas itemconfigure $item -anchor nw
            $canvas coords $item $offset 0
        }
        default {                                                                                                          ;# center
            $canvas itemconfigure $item -anchor center
            $canvas coords $item [expr {$offset + (($right - $left) / 2.0)}] [expr {$bottom / 2.0}]
        }
    }
}

proc updateCanvasImagesPosition {} {
    if {[info exists global::canvasImageItem]} {
        updateCanvasImagePosition $global::canvasImageItem $global::canvasImagePosition
    }
    pages::updateImagesPositions
}

proc traceDialog {title message {exit 0}} {            ;# with text area so that message cutting and pasting is possible by the user
    set dialog [new dialogBox . -title $title -buttons x -default x -x [winfo pointerx .] -y [winfo pointery .]]
    if {$exit} {composite::configure $dialog -labels [list x [mc Exit]]}
    set frame [frame $widget::($dialog,path).frame]
    set scroll [new scroll text $frame -height 100]
    composite::configure $dialog -deletecommand "delete $scroll; set ::traceDialogDone {}"
    set text $composite::($scroll,scrolled,path)
    $text insert end $message
    $text configure -font $font::(mediumNormal) -wrap word -state disabled
    pack $widget::($scroll,path) -fill both -expand 1
    dialogBox::display $dialog $frame
    vwait ::traceDialogDone
}

proc manageScrolledCanvas {show} {
    set path $widget::($global::scroll,path)
    if {$show} {
        if {[llength [grid info $path]] == 0} {
            grid $path -row 2 -column 0 -sticky nsew
        }
    } else {
        grid forget $path
    }
}

proc createNewPage {} {
    updateCanvasImage {}; set global::canvasImageFile {}                                       ;# possibly clear existing background
    set pages [expr {[pages::current] != 0}]
    if {!$pages} {manageScrolledCanvas 0}                                                                              ;# first page
    set page [new page . -background $global::canvasBackground -draggable [expr {!$global::readOnly}]]
    if {!$pages} {pages::manageScrolledCanvas 1}
    pages::edit $page
}

proc formulasDialog {{formulasTable 0} {selectedFormula 0}} {
    if {[info exists global::formulasDialog]} {
        formulas::dialog::raise $global::formulasDialog                                                           ;# make it visible
    } else {
        set command "formulasDialogValidated $formulasTable"
        set deletion {unset global::formulasDialog}
        if {$formulasTable == 0} {
            set list {}
            set global::formulasDialog [new formulas::dialog -command $command -deletecommand $deletion]
        } else {
            composite::configure $formulasTable -state disabled         ;# so that user cannot modify table while it is being edited
            append deletion "; composite::configure $formulasTable -state normal"
            set list [formulas::table::formulas $formulasTable]
            set global::formulasDialog [new formulas::dialog\
                -command $command -deletecommand $deletion -formulas $list\
                -object [composite::cget $formulasTable -object] -category [composite::cget $formulasTable -category] -initial 0\
            ]
        }
        if {([llength $list] > 0) && ($selectedFormula > 0)} {
            formulas::dialog::select $global::formulasDialog $selectedFormula
        }
    }
}

proc formulasDialogValidated {table object category formulas} {
    if {$table == 0} {                                                                                         ;# create a new table
        formulas::table::manage [createNewFormulasViewer $object $category 1 $global::static] $formulas
    } else {                                                                                                ;# update existing table
        if {[formulas::table::manage $table $formulas 1]} {                   ;# the name of at least 1 existing formula was changed
            foreach viewer $viewer::(list) {
                viewer::updateLabels $viewer                  ;# update labels in case one pointed to one of the formulas data cells
            }
        }
    }
}

proc raiseExistingFormulasDialog {} {
    if {[info exists global::formulasDialog]} {
        formulas::dialog::raise $global::formulasDialog
    }
}

proc displayModuleState {namespace value} {
    # use grayish colors: yellow for busy, red for error and green for idle
    switch $value {
        busy {set color #C0C0C0}
        error {set color #D0A0A0}
        idle {set color #A0D0A0}
        default error
    }
    foreach table $dataTable::(list) {                                     ;# find all data tables for the specified module instance
        if {[string equal [modules::namespaceFromArray [composite::cget $table -data]] $namespace]} {
            canvasWindowManager::color $global::windowManager $widget::($table,path) $color
        }
    }
}