Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch 2nd-try
Excluding Merge-Ins
This is equivalent to a diff from
978111dbc0
to 4388cc91f2
2012-02-14
| | |
18:32 |
|
Leaf
check-in: 4388cc91f2 user: andreask tags: 2nd-try
|
07:56 |
|
check-in: 30768bcde4 user: aku tags: 2nd-try
|
2012-01-17
| | |
00:23 |
|
check-in: 2ab43342f3 user: andreask tags: 2nd-try
|
2010-12-17
| | |
00:25 |
|
Leaf
check-in: 978111dbc0 user: aku tags: trunk
|
2010-12-16
| | |
23:44 |
|
check-in: b2597a742e user: aku tags: trunk
|
| | |
Added attic/bookflow.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/bin/sh
## -*- tcl -*- \
exec tclsh "$0" ${1+"$@"}
# # ## ### ##### ######## ############# #####################
## Copyright (c) 2010 Andreas Kupries.
#
# This software is BSD licensed.
# # ## ### ##### ######## ############# #####################
## Command line application wrapped around the flow packages.
# # ## ### ##### ######## ############# #####################
## Requirements, extended package management for local packages.
lappend auto_path [file normalize [file join [file dirname [info script]] lib]]
#puts stdout *\t[join $::auto_path \n*\t]
package require Tcl 8.5 ; # Required runtime.
# # ## ### ##### ######## ############# #####################
## Global settings for tracing.
package require Thread
package require debug
::apply {{} {
set parts {}
append parts {[thread::id] | }
append parts {[clock format [clock seconds]] | }
append parts {[format %3d [info level]] | }
append parts {[string repeat { } [info level]] | }
debug prefix :: $parts
return
} ::}
debug off bookflow
#debug on bookflow
Debug.bookflow {Starting the application...}
# # ## ### ##### ######## ############# #####################
package require bookflow ; # Main functionality.
# # ## ### ##### ######## ############# #####################
## Execution
bookflow run $argv
exit 0
# # ## ### ##### ######## ############# #####################
|
Added attic/doc/Arch.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
Overview
========
Bookflow is an application processing the JPEG images found in
a directory into zero or more 'books'.
The directory is also called a 'project'.
Each project may contain zero or more books.
Syntax
======
bookflow <directory> ?... range of passes, other options...?
Overall behaviour
=================
(1) If the <directory> contains a file named BOOKFLOW:
(a) Check that it is a valid bookflow state file. [R1]
Report an error, if not.
(b) Run the specified passes. [R2]
If no passes where specified, run them all. [R3]
(2) The <directory> does not contain a file named BOOKFLOW:
Scan the directory for JPEG files. The scanning is not [R4]
recursive, i.e. only images in the directory itself
count. Subdirectories and their contents are ignored.
Report an error if none are present. [R5]
Create BOOKFLOW with the found JPEG files recorded [R6]
in it.
The BOOKFLOW file will contain, per JPEG image
= Name,
= Size
= SHA1 checksum.
Proceed with (1). [R6]
Validation [R1]
===============
A valid BOOKFLOW file is a sqlite3 database. [R11]
The database contains an entry for all JPEG files [R12]
found in the directory.
"No files were added since the last bookflow run"
The database contains no entries for which there [R13]
is no JPEG file in the directory.
"No files were removed since the last bookflow run"
The SHA1 checksums recorded for a JPEG file matches [R14]
the SHA1 checksum of the file in the directory
"No files were modified since the last bookflow run"
Passes, General
===============
Each pass has three phases, namely [R21]
initialization, execution, and finalization.
Passes come in monolithic and parallel varieties. [R22]
The first means that the actions of the pass for [R23]
each image in the BOOKFLOW are tied together and
cannot be separated.
Conversely the latter means that the actions of the [R24]
pass for each image in the BOOKFLOW can be separated
from each other and performed concurrently.
If the initialization phase of a pass is run, then [R25]
this is done before its execution and finalization
phases.
If the execution phase of a pass is run, then this [R26]
is done after its initialization and before its
finalization phases.
If the finalization phase of a pass is run, then [R27]
this is done after its initialization and execution
phases.
The passes of bookflow have a fixed order, which is
specified later.
For a monolithic pass A executed before a pass B all [R28]
phases of A which are run, are run before any of the
phases of B.
For a pass A executed before a monolithic pass B all [R29]
phases of A which are run, are run before any of the
phases of B.
For a parallel pass A executed before a parallel [R210]
pass B all the phases of A which are run for a
specific image, are run before any of the phases of B
for the same image.
When performing the passes from A to B, with A a pass
coming before B in the order of passes the following
phases are run, with their order constrained by the
rules above:
The initialization phases from the first [R211]
pass to pass B.
The finalization phases from pass A to the [R212]
last pass.
The execution phases from pass A to pass B. [R213]
Passes, Bookflow
================
Bookflow uses the following passes to process
the images in the directory/project.
A. Parallel.
Compute brightness of all images.
B. Monolithic.
Sort the brightness values into 3 classes based on
their, using k-Means classification.
The classes in question are:
- marker black
- marker white
- book page
C. Parallel.
Mark all images with their class.
D. Monolithic.
Use the image names to impose an order on the images,
then use the image class information to locate the
various multi-image markers, i.e.
black/black/white - SOB Start of Book, Even pages begin.
black/white/black - MOB Middle of Book, Odd pages begin.
white/black/black - EOB End of Book.
Reclassify the images as
- marker, ignored
- book page, even images between SOB and MOB
- book page, odd images between MOB and EOB
- ignored images between EOB and SOB
images before first SOB
images after last EOB.
and separate them into books (images between SOB and EOB).
Error conditions:
- No SOB, MOB, and EOB found.
- No MOB between SOB and EOB.
E. Parallel.
Rotate the book page images upright, with the rotation
dependent on the classification as even or odd.
Note: This modifies the images in the project directory.
We have to remember this in the project so that we
won't try to rotate them later again, and we have
to update the size/checksum info.
Alternative: The rotated images are stored in a sub-directory,
and the originals are left untouched. We still remember the
information in the bookflow file so that we can skip this
action when needed.
F. Parallel.
For each image generate a downsampled copy to make the later
passes faster (less pixels to process).
G. Parallel.
Determine the DPI of all images marked as book pages.
[[ Initially: Manual assigment, via cmdline, or GUI ]].
X. Manual classification (or heuristics:): inner marker =>
ignore previous image.
X. Have special image with DPI marker (color square/circle).
Maybe even in the regular marker panels
=> black! + red circle (The white marker is already the
lightfield, we cannot interfere with that.
X. Use white markers to compute light fields, and apply them
for regularization of the book pages.
X. Book Information
per book - title
- isbn
- author (list)
- publisher
- print year
- print edition
X. Use the even/odd information per book to arrange a final
order of display (page increasing), and separate the
front/back cover pages.
X. LAT (local adaptive thresholding).
=> global histogram for global threshold (median)
=> and per-pixel histogram (median => median filter)
======================================================================
Internal achitecture (modules and their interaction)
(1) Engine and Frontends are separate packages / libraries.
Two frontends are provided
(a) A pure command line.
(b) A graphical interface.
(2) Engine and Frontend are run in different threads.
Communication is handles via thread::send.
Bulk data (images) is communicated via the filesystem,
using file names in the commands issued through 'thread::send'.
(3) The engine has to be interuptible, for the graphical frontend
able to take control at an arbitrary point.
The ability to cancel a phase in progress is required too.
This should be built, if at all possible, into the phase
support- and execution framework, i.e. the phase manager.
(4) The engine may use additional, internal, threads to
concurrently perform actions. -- Threadpool.
======================================================================
User Experience
===============
(i) Start bookflow
(a) With a single argument - Open the GUI, see (1) for continued
behaviour
(b) With no argument
Open the GUI, see (1) for continued behaviour using the
current working directory as the argument.
(c) With more than one argument.
Throw an error for the user to acknowledge and abort.
- How to decide where to show the error, GUI or stdout ?
- Or treat as case (b) ?
- Or treat as case (a), ignoring the superfluous arguments ?
Vertical notebook:
Panel 1: Images
Panel 2+: Book Information. See above.
Including just the images in the book,
sorted and ordered by page number.
Show the images as thumbnails, in a grid, dynamically resizable.
The thumbnails display has to contain markers (icon, color, etc)
to make it easy to separate chaff/wheat.
===================================================================
bookflow <=> bookflow process CWD
bookflow <dir> <=> bookflow process <dir>
bookflow process <dir>
bookflow images
bookflow books
bookflow statistics
|
Added attic/doc/architecture.dia.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
south
box "Frontend (Thread)" "Cmdline | GUI" width [8 cm] fillcolor lightgreen
move
box "Engine (Thread)" width [8 cm] fillcolor lightyellow
group {
arrow \
from [0.33 between [[2nd last box] sw] [[2nd last box] se]] \
to [0.33 between [[last box] nw] [[last box] ne]] \
"Commands " rjust
arrow \
from [0.33 between [[last box] ne] [[last box] nw]] \
to [0.33 between [[2nd last box] se] [[2nd last box] sw]] \
" Responses" ljust
}
block {
set movelength [1 cm]
east
box "Worker-\nthread" fillcolor salmon
group { arrow <-> from [[last box] n] north }
move
box same
group { arrow <-> from [[last box] n] north }
move
box same
group { arrow <-> from [[last box] n] north }
set E [[last box] e]
set W [[3rd last box] w]
}
group {
east
arrow <-> from [[last box] e] stroke 4
box height [8 cm] width [4 cm] "Filesystem" fillcolor lightblue
arrow <-> stroke 4 from [[last block] E]
arrow <-> stroke 4 from [0.75 between [[1st box] ne] [[1st box] se]]
}
group {
west
arrow <-> from [[2nd last box] w] stroke 4
drum height [8 cm] width [4 cm] "BOOKFLOW" "(Database)" fillcolor lightblue aspect 0.1
arrow <-> stroke 4 from [[last block] W]
}
|
Added attic/doc/architecture.png.
cannot compute difference between binary files
Added attic/doc/erd.dia.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
proc t {name script args} {
block {
south
set fields [block {
circle radius 1 fillcolor red color red
eval $script
}]
box at [last block] \
width [expr {[[last block] width] + [5 mm]}] \
height [expr {[[last block] height] + [5 mm]}]
box text $name fillcolor white height [7 mm] with sw at [last box nw]
set X [[last box] e]
} {*}$args
}
proc f {type name notes args} {
set $name [text "$type :: $name ($notes)" with nw at [[last] sw] {*}$args]
}
proc n {text args} {
text "$text" textcolor red with nw at [[last] sw] {*}$args
}
proc pk {type name args} {
f $type $name [join $args {, }] textcolor blue
}
proc d {rows} {
block {
south
foreach r $rows {
block {
east
foreach c $r {
box height [7 mm] $c
}
}
}
}
}
##########################################
south
t bookflow {
f int dpi {}
}
move
t book {
pk int bid {not null, auto-increment}
f text name {unique, not null}
}
east
arrow <- bid above
set image [t image {
pk int iid {not null, auto-increment}
f text path {not null, unique}
f int bid {not null, references book}
f int ord {not null}
n "unique (bid, ord)"
}]
east
group {
arrow <- right right iid above
t is1 {
f int iid {not null}
f int sid {not null}
}
arrow right right sid above
t state1 {
pk int sid {not null}
f string label {not null, unique}
}
arrow from [[last block] X] right right right data above
d {
{0 "white"}
{1 "black"}
{2 "page"}
}
}
group {
arrow <- down down down right then right iid above
east
t is2 {
f int iid {not null}
f int sid {not null}
}
arrow right right sid above
t state2 {
pk int sid {not null}
f string label {not null, unique}
}
arrow from [[last block] X] right right right data above
d {
{ 0 "sob1" {! "black"}}
{ 1 "sob2" {! "black"}}
{ 2 "sob3" {! "white"}}
{ 3 "mob1" {! "black"}}
{ 4 "mob2" {! "white"}}
{ 5 "mob3" {! "black"}}
{ 6 "eob1" {! "white"}}
{ 7 "eob2" {! "black"}}
{ 8 "eob3" {! "black"}}
{ 9 "even" {! "page"}}
{10 "odd" {! "page"}}
{11 "none" {! "page"}}
}
}
group {
arrow <- down down down down down down right then right iid above
east
t it {
f int iid {not null}
f int tid {not null}
}
arrow right right tid above
t type {
pk int tid {not null}
f string label {not null, unique}
}
arrow from [[last block] X] right right right data above
d {
{ 0 "frontc" {! "odd"}}
{ 1 "backc" {! "even"}}
{ 2 "page" {! "page"}}
}
}
group {
arrow <- up up up right then right iid above
east
set istate [t brightness {
f int iid {not null}
f int value {not null}
}]
}
|
Added attic/doc/erd.png.
cannot compute difference between binary files
Added attic/doc/gui_book_tab.dia.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
######################################################################
proc portrait {h args} {
box height $h width [expr {0.75*$h}] {*}$args
}
proc landscape {w args} {
box width $w height [expr {0.75*$w}] {*}$args
}
proc thumb {args} {
landscape [16 mm] "Thumb" {*}$args
}
proc sthumb {args} {
thumb {*}$args stroke 3
}
proc ellipsis {} {
move same ; circle rad [1 mm] fillcolor black
move same ; circle same
move same ; circle same
}
proc leftarrow {args} {
box {*}$args ; group {
line \
from [[[last box] ne] by [2 mm] sw] \
then [[[last box] w] by [2 mm] e] \
then [[[last box] se] by [2 mm] nw] \
to [[[last box] ne] by [2 mm] sw]
}
}
proc rightarrow {args} {
box {*}$args ; group {
line \
from [[[last box] nw] by [2 mm] se] \
then [[[last box] e] by [2 mm] w] \
then [[[last box] sw] by [2 mm] ne] \
to [[[last box] nw] by [2 mm] se]
}
}
proc bseries {args} {
block {
block {
east
portrait [9 cm] "Left page" "Odd"
move right [5 mm]
portrait [9 cm] "Right page" "Even"
}
set sl [box with s at [[[last block] n] by [5 mm] n] width [[last block] width]]
block {
east ; thumb
move right [2 mm] ; thumb
ellipsis
move same ; sthumb
move same ; sthumb
ellipsis
move same ; thumb
move same ; thumb
} with c at [[last box] c]
leftarrow with e at [[$sl w] by [2 mm] w]
rightarrow with w at [[$sl e] by [2 mm] e]
} {*}$args
}
proc wrap {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left]] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
tabB Images
tab {Book 1}
tabA ...
tabA {Book N}
line to $x
}
proc tab {{text {}}} {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm] ; line
arc rad [5 mm] cw
return
}
proc tabB {{text {}}} {
group {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm]
}
line down [15 mm]
}
proc tabA {{text {}}} {
group {
west
arc rad [5 mm] from [[2nd last arc] end]
line down [5 mm]
arc rad [5 mm] ; line ; tablabel $text up
arc rad [5 mm] cw
}
}
proc tablabel {text {dir down}} {
if {$text eq {}} return
group {
text text $text with c at [[[last line] c] by [7.5 mm] $dir]
}
return
}
######################################################################
text "Notebook Page \"Book Image Series\""
move south [1 cm]
wrap [bseries]
move
|
Added attic/doc/gui_book_tab.png.
cannot compute difference between binary files
Added attic/doc/gui_framing.dia.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
######################################################################
proc nbpage {args} {
box width [18.4 cm] height [11.5 cm] {*}$args
}
proc wrap2 {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left] color red] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
line to $x
}
proc wrap {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left]] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
tabB Images
tab {Book 1}
tabA ...
tabA {Book N}
line to $x
}
proc tab {{text {}}} {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm] ; line
arc rad [5 mm] cw
return
}
proc tabB {{text {}}} {
group {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm]
}
line down [15 mm]
}
proc tabA {{text {}}} {
group {
west
arc rad [5 mm] from [[2nd last arc] end]
line down [5 mm]
arc rad [5 mm] ; line ; tablabel $text up
arc rad [5 mm] cw
}
}
proc tablabel {text {dir down}} {
if {$text eq {}} return
group {
text text $text with c at [[[last line] c] by [7.5 mm] $dir]
}
return
}
######################################################################
text "Overall gui, image notebook + rightside action log"
move south [1 cm]
wrap2 [block {
block { wrap [nbpage "Notebook page"] }
east
move east [5 mm]
box height [[last block] height] width [6 cm] "Log of Engine Activity"
}]
move
|
Added attic/doc/gui_framing.png.
cannot compute difference between binary files
Added attic/doc/gui_img_tab_a1.dia.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
######################################################################
proc portrait {h args} {
box height $h width [expr {0.75*$h}] {*}$args
}
proc landscape {w args} {
box width $w height [expr {0.75*$w}] {*}$args
}
proc thumb {args} {
landscape [32 mm] "Thumb" {*}$args
}
proc sthumb {args} {
thumb {*}$args stroke 3
}
proc ellipsis {} {
move same ; circle rad [1 mm] fillcolor black
move same ; circle same
move same ; circle same
}
proc iseries {args} {
block {
box width [12 cm] height [9 cm]
block {
east ; thumb
move right [2 mm] ; sthumb
ellipsis
} with nw at [[[last box] nw] by [5 mm] se]
block {
east ; ellipsis
move right [2 mm] ; thumb
move right [2 mm] ; thumb
} with se at [[[last box] se] by [5 mm] nw]
} {*}$args
}
proc wrap {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left]] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
tab Images
tabA {Book 1}
tabA ...
tabA {Book N}
line to $x
}
proc tab {{text {}}} {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm] ; line
arc rad [5 mm] cw
return
}
proc tabB {{text {}}} {
group {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm]
}
line down [15 mm]
}
proc tabA {{text {}}} {
group {
west
arc rad [5 mm] from [[2nd last arc] end]
line down [5 mm]
arc rad [5 mm] ; line ; tablabel $text up
arc rad [5 mm] cw
}
}
proc tablabel {text {dir down}} {
if {$text eq {}} return
group {
text text $text with c at [[[last line] c] by [7.5 mm] $dir]
}
return
}
######################################################################
text "Notebook Page \"Image Series\" (Alternative I)"
move south [1 cm]
wrap [iseries]
move
|
Added attic/doc/gui_img_tab_a1.png.
cannot compute difference between binary files
Added attic/doc/gui_img_tab_a2.dia.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
######################################################################
proc portrait {h args} {
box height $h width [expr {0.75*$h}] {*}$args
}
proc landscape {w args} {
box width $w height [expr {0.75*$w}] {*}$args
}
proc thumb {args} {
landscape [16 mm] "Thumb" {*}$args
}
proc sthumb {args} {
thumb {*}$args stroke 3
}
proc ellipsis {} {
move same ; circle rad [1 mm] fillcolor black
move same ; circle same
move same ; circle same
}
proc leftarrow {args} {
box {*}$args ; group {
line \
from [[[last box] ne] by [2 mm] sw] \
then [[[last box] w] by [2 mm] e] \
then [[[last box] se] by [2 mm] nw] \
to [[[last box] ne] by [2 mm] sw]
}
}
proc rightarrow {args} {
box {*}$args ; group {
line \
from [[[last box] nw] by [2 mm] se] \
then [[[last box] e] by [2 mm] w] \
then [[[last box] sw] by [2 mm] ne] \
to [[[last box] nw] by [2 mm] se]
}
}
proc iseries {args} {
block {
block {
east
move right [47.5 mm]
portrait [9 cm] "Current page"
move right [47.5 mm]
}
set sl [box with s at [[[last block] n] by [5 mm] n] width [[last block] width]]
block {
east ; thumb
move right [2 mm] ; thumb
ellipsis
move same ; sthumb
ellipsis
move same ; thumb
move same ; thumb
move same ; thumb
} with c at [[last box] c]
leftarrow with e at [[$sl w] by [2 mm] w]
rightarrow with w at [[$sl e] by [2 mm] e]
} {*}$args
}
proc wrap {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left]] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
tab Images
tabA {Book 1}
tabA ...
tabA {Book N}
line to $x
}
proc tab {{text {}}} {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm] ; line
arc rad [5 mm] cw
return
}
proc tabB {{text {}}} {
group {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm]
}
line down [15 mm]
}
proc tabA {{text {}}} {
group {
west
arc rad [5 mm] from [[2nd last arc] end]
line down [5 mm]
arc rad [5 mm] ; line ; tablabel $text up
arc rad [5 mm] cw
}
}
proc tablabel {text {dir down}} {
if {$text eq {}} return
group {
text text $text with c at [[[last line] c] by [7.5 mm] $dir]
}
return
}
######################################################################
text "Notebook Page \"Image Series\" (Alternative II)"
move south [1 cm]
wrap [iseries]
move
|
Added attic/doc/gui_img_tab_a2.png.
cannot compute difference between binary files
Added attic/doc/interaction_mvc_images.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
Interaction between a display of multiple images (view + controller)
and a model holding the images to show.
====================================================================
The model is a container of images, i.e.:
* It holds a list of images. Note that 'list' implies an order on the images.
* It has the following information per image (all optional (*))
- name of the image, relative to the project directory
- path of the thumbnail image, relative to the project directory
- classification 0: use/ignore
- classification 1: black/white/page
- classification 2: sob/mob/eob/even/odd
- classification 3: na/content/front/back
(*) To allow the use of placeholders for missing pieces, be they
pages or the various markers.
The model broadcasts events on changes to its contents, i.e:
* An image is added
* The state of an image changes
- name becomes known
- thumbnail becomes known or changes.
- classification X becomes known or changes.
Views for a model are driven by these events, having bound to the
model and them.
Notes on the information and their constraints:
(a) An image without name is a placeholder for missing data.
(b) A placeholder has the classifications which describe the type of
the missing piece.
(c) A missing thumbnail is a temporary condition the model will
rectify as fast as possible.
(d) Classification 0 is orthogonal to the classifications 1-3. Where
the latter describe what the image is, in increasing detail, this
one tells us whether to use the image later, or not.
(e) The classifications 1, 2, and 3 are building on each other,
i.e. the higher numbered classifications can be known if and
only if the lower-numbered classifications are available. In
addition a number of constraints are put on the values restricting
the set of legal combinations.
1-unknown => 2-unknown => 3-unknown
2-sob => 1-black|1-white
2-mob => 1-black|1-white
2-eob => 1-black|1-white
2-even => 1-page
2-odd => 1-page
3-content => 2-even|2-odd
3-front => 2-odd
3-back => 2-even
3-na => 2-sob|2-mob|2-eob
Based on these constraints the legal combinations are shown
below. On the right additional notes on how the combination is
shown by a view.
c1 c2 c3 view
------------------------ --------
* unknown unknown unknown plain name, thumbnail (when present)
------------------------ --------
black unknown unknown 3 pixel wide black border
---------------- --------
sob unknown 3 pixel wide green border
na ditto
---------------- --------
mob unknown 3 pixel wide yellow border
na ditto
---------------- --------
eob unknown 3 pixel wide magenta border
na ditto
------------------------ --------
white unknown unknown 3 pixel wide salmon border
---------------- --------
sob unknown 3 pixel wide green border
na ditto
---------------- --------
mob unknown 3 pixel wide yellow border
na ditto
---------------- --------
eob unknown 3 pixel wide magenta border
na ditto
------------------------ --------
* page unknown unknown plain name, thumbnail (when present)
---------------- --------
* even unknown plain name, thumbnail (when present)
content 3 pixel wide blue border
back 3 pixel wide orange border
---------------- --------
* odd unknown plain name, thumbnail (when present)
content 3 pixel wide blue border
front 3 pixel wide orange border
------------------------ --------
The starred entries are currently visually undistinguishable.
See if the treecontrol allows for dashed and dotted borders /
rectangles around items for additional ways of distinguishing
states.
Two open issues, which are related to each other
(1) How do we communicate the order of images in the model, and
(2) How do we communicate changes to the order between images.
====================================================================
The view is also a controller, i.e. actions taken by the user are
communicated to the
- The model has to announce the presence of new images
- The model has to annonce when the thumbnail for an image is available.
- The model has to announce when the thumbnail of an image is changed.
- The model has to announce the removal of images
- The model has to announce changes to the information about an image
(status, type, ...)
|
Added attic/doc/interaction_pci.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
Interactions between producers, users, and invalidators of data
===============================================================
Using the handling of thumbnail images as example and template for the
pattern.
Producer
--------
(1) The producer monitors the scoreboard (take) for the appearance of
tuples matching the pattern {!THUMBNAIL *}.
When appearing the second word of the taken tuple is treated as
the path of the image I whose thumbnail is to be invalidated.
The producer cleans up all data pertaining to the thumbnail of I,
ensuring that the next time the thumbnail for I is requested it
will be full regenerated from the base data, i.e. I itself.
Part of this cleanup is the removal of the {THUMBNAIL <I>} tuple
for this image. This action triggers (5), in the user, see below.
(2) The producer monitors the scoreboard (bind missing) for queries,
i.e. patterns of tuples matching the pattern {THUMBNAIL * *}.
(Missing events trigger when a pattern to 'take' and 'wpeek'
matches no tuple at all).
When a miss is reported the second word of the reported pattern is
treated as the path of the image I whose thumbnail has been
requested but not known.
The producer generates and places a tuple {THUMBNAIL <I> <T>} into
the scoreboard, fulfilling the request, with I the path of the
image and T the path of the thumbnail image to use. The generation
of this tuple is trivial if T already exists in the filesystem, a
simply packaging up of the information. Otherwise the producer
launches a task actually generating T, using CRIMP to scale down I
to thumbnail size.
Invalidator
-----------
(3) When actions by some task or other make the contents of the
thumbnail for image I obsolete the task or other places a tuple
matching {!THUMBNAIL <I>} into the scoreboard.
This then triggers (1), in the producer, see above.
User
----
(4) When the thumbnail T of an image I is required the user asks
(wpeek) for a tuple matching {THUMBNAIL <I> *}. If a matching
tuple is present its third word is treated as the path to the
requested thumbnail.
If it is not present the query triggers (2) in the producer, see
above, causing the tuple to be generated in time.
Because of the delay possible in fulfulling the request the user
should be prepared for the possibility that by the time the
request is actually fulfilled the need for the data has passed.
(5) The user monitors the scoreboard (bind take) for the removal
of {THUMBNAIL <I> *} tuples, signaling content invalidation.
When the removal is reported, and the user still has need of the
thumbnail then (4), see above, is invoked to request an updated
and valid thumbnail.
Notes
~~~~~
(a) The image paths mentioned in the various actions above are all
relative to the project directory.
(b) The parts of the system are not restricted to a single role in the
above. For example, the producer of brightness data for the images
is also the user of greyscale conversions of same images.
|
Added attic/doc/notes.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
Possible scan errors
====================
duplicate pages
missing markers - insert fake marker
missing pages - insert fake (empty) page/placeholder
missing cover - insert fake cover (see fake page)
missing lightfield - synthesize
cover scanned out of order (last instead of first, or in the middle).
Heuristics
==========
detect marker
detect lightfield
synthesize lightfield
page brightness (-> grey -> mean, or hsv -> value -> mean)
page color (-> hsv -> hue -> mean)
picture orientation
detect page number => orientation cue, even/odd cue, number itself for
order
compare pages (similarity = detect duplicate)
first order by image name
crimp - ppm file - save/read HSV!
crimp - up/down sample x/y separate
auto-dpi = 6 lines/height
auto-dpi via markers (square lines - also perspective warp, global)
auto-crop
---
scan tailor mixed mode tiff image
If I flip the pure-black pixels to white, I have the graphical version
of the image. If I flip non-pure-black pixels to white, I have the
textual version of the image. Yes?
== pure black = text
== grey-scale = grey images, never going up to pure black (255)
|
Added attic/doc/phases.dia.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
proc mbox {args} {
box width [8 cm] fillcolor lightgreen {*}$args
}
proc pbox {args} {
box width [8 cm] fillcolor lightyellow {*}$args
}
east
drum width [4 cm] height [8 cm] aspect 0.1 "BOOKFLOW DB" fillcolor lightblue
move ; move
set p [block {
south
set movelength [1 cm]
set sd [mbox "Scan Directory" "(Implied to have an order)"]
group {
southwest
arrow
pbox "Create thumbnail"
}
southeast
arrow
set gr1 [pbox "Convert to greyscale (I)"]
south
arrow
set cb [pbox "Compute brightness"]
arrow
set cl [mbox "Classify The Brightness"]
arrow
set ci [pbox "Classify By Brightness" "MarkerB | MarkerW | Page | Unknown"]
group {
southwest
arrow down left left
set bm [pbox "Detect SOB | MOB | EOB"]
group {
south
arrow
mbox "Separate multiple books"
arrow
mbox "Separate even|odd|not pages"
group { east ; line ; arrow }
arrow
mbox "Separate cover pages" "& reorder"
}
}
group {
south
arrow
set lf [pbox "Detect light field"]
arrow
set no [pbox "Normalize background"]
arrow
pbox "Rotate upright"
arrow
pbox "Unwarp perspective"
group {
southeast ; arrow down right right
pbox "Compute DPI"
}
arrow
set gr2 [pbox "Convert to greyscale (II)"]
arrow
set re [pbox "Reduce size"]
arrow
pbox "Determine rough page borders"
arrow
mbox "Inter-page border exchange"
arrow
pbox "Finalize page borders"
arrow
pbox "Segment page" "Text | Images | Lines"
arrow
pbox "Line shape"
arrow
pbox "Unwarp lines"
}
group {
southeast
arrow down right right
set dp [pbox "Find fiducials (DPI & perspective)" "(original image)"]
south
arrow down down down down down down then down left left left left left left
}
}]
move ; move
circle radius [4 cm] fillcolor grey "ScoreBoard" "(in memory)"
|
Added attic/doc/phases.png.
cannot compute difference between binary files
Added attic/doc/rescale_request_prioritization.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
Handling of regular images by the book manager.
===============================================
Two places/situations will request a regular sized page image.
(i) selection, i.e. when page X is selected, system gets its
image.
(ii) background pre-generation, i.e. for all images found we
request them once, to ensure that they are created if they do
not exist yet.
Of these two (i) is a high-priority thing, as the user wishes to see
the image. It is also something we must be able to cancel. I.e. when
the user switches to a different page and the image for the previously
current one has not arrived yet then this old request should either
get normal priority or not be done at all.
Situation (ii) on the other hand is something which can be defered
until after all the thumbnails have been done. This one should look
towards (i) to know which pages are already done while the user was
browsing.
The problem with (i) and cancellation is that the user is, in
principle, isolated from the internals of the producers. Miss the
requested tuple, and the producer automatically starts the generation
process. And the consumer automatically waits for the result/return
event.
As such a switch to a different image will simply make another
request, if the data was missing.
Prioritization has happen in the producer. I.e. the producer, knowing
that a particular request has priority then takes the necessary
actions to get it into the scaling tasks as fast as possible, if that
is required at all.
The dispatcher then also has to keep track of the requests waiting for
execution, so that it can take lower-priority request back to make
place for the high priority one. And putting them back when it knows
that the high-priority request is taken and executing.
... side note ... Make dataflow diagrams for the producer internals,
showing direct and indirect control flow ...
|
Added attic/doc/sb_semantics.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
Scoreboard API
==============
put <tuple>...
Places the specified tuples into the scoreboard.
May return before the tuples are fully stored.
May release 'take' requests waiting on a pattern matching any of the tuples.
May trigger 'added' notifications for patterns matching the tuples.
take <pattern> <cmd>
Asks the scoreboard to invoke <cmd> when a tuple matching the
<pattern> is present, with the matching tuple as argument.
At the time of invokation the tuple is removed from the
scoreboard.
Returns before <cmd> is invoked.
If no matching tuple is present the system will wait until
such a tuple exists. Possibly waiting indefinitely.
Multiple 'take' requests waiting on tuples are served in order
of arrival. I.e. the earliest request matching a tuple is
invoked, with the remainder waitng for the next tuple. As new
requests are adding to the end of this list each request R
will be served at some point if enough tuples matching its
pattern are added to the scoreboard. Matching requests coming
after R cannot pre-empt it.
May trigger 'removed' notifications, for patterns matching the
taken tuple.
May trigger 'missing' notifications, for patterns not matching
a tuple at the time of the request.
takeall <pattern> <cmd>
Like 'take', with two differences.
It doesn't wait for matching tuples to be present.
If none are there <cmd> is invoked with the empty list.
If tuples match however then all of them are removed
from the scoreboard and given to <cmd>.
May trigger 'removed' notifications for patterns matching the
taken tuples.
peek <pattern> <cmd>
Like 'takeall', except that the matching tuples are not
removed from the scoreboard. As such it will not generate
'take' notifications either.
wpeek <pattern> <cmd>
The 'waiting peek' is like peek in that it doesn't remove a
tuple matching the pattern. It is however like 'take', waiting
for the appearance of a matching tuple is no such is present
when the request is made.
bind put <pattern> <cmd>
bind take <pattern> <cmd>
bind missing <pattern> <cmd>
These methods bind a <cmd> callback to a particular action
(put/take) and tuple <pattern>. Each occurence of the action
for a tuple matching the pattern causes an invokation of the
callback.
The contents of the scoreboard are not modified.
In this manner it is possible to wait for a tuple to appear,
like 'take', but without actually removing the tuple.
Note that if a tuple is added via 'put' and immediately
'take'n two notifications may be generation, for both the
'put', and the 'take', in this order.
The 'missing' event is invoked if a 'take' or 'wpeek' had to
wait for a matching tuple, and the pattern, treated as tuple,
matched the pattern for the event.
unbind ...
Remove event bindings.
|
Added attic/doc/scoreboard.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# -*- tcl -*-
#
# Documentation of the tuples stored in the scoreboard, their
# meanings, and associated code, i.e. creators, users, etc.
tuple {PROJECT CREATE} {
Signal from the directory scanner to the creation task to generate
a new project (database).
} {
}
tuple {PROJECT VERIFY} {
Signal from the directory scanner to the verification task to
cross-check an existing project (database).
} {
}
tuple {PROJECT ERROR <msg>} {
Message for the user interface to post.
} {
}
tuple {PROJECT SERVER <thread>} {
Access to project database is mediated by the thread with id <thread>.
} {
}
tuple {AT <path>} {
The location of the current project (directory), as absolute path.
} {
}
tuple {DATABASE <name>} {
The name/path of the database file, relative to the project directory.
Also a signal to the project database access layer to provide access.
} {
}
tuple {FILE <path>} {
Name/path of an image file found by the scanner, relative to the project
directory. Used by either creation or verification task, i.e. make
them images, or compare to current images.
} {
}
tuple {BOOK <name>} {
Name of a book found in the project (database).
} {
}
tuple {IMAGE <path> <serial> <book>} {
Name/path of a verified page image file in the project,
with reference to the book it belongs to, and a serial
number providing the ordering within the book.
} {
}
tuple {!THUMBNAIL <path> <size>} {
Signal to invalidate the <size>d thumbnail of page
image <path>.
} {
}
tuple {THUMBNAIL <path> <size> <dstpath>} {
<dstpath> is the location of the <sized>d thumbnail for
page image <path>. All paths are relative to the project
(directory).
} {
}
tuple {SCALE <path> <size> <dst>} {
Order to resize page image <path> to <size>, and store the
result in <dst>.
} {
}
tuple {!GREYSCALE <path>} {
Signal to invalidate the greyscale derivation of page
image <path>.
} {
}
tuple {GREYSCALE <path> <dstpath>} {
<dstpath> is the location of the greyscale derivation of
page image <path>. All paths are relative to the project
(directory).
} {
}
tuple {GREYCONVERT <path> <dst>} {
Order to compute the greyscale of page image <path> and
store the result in <dst>.
} {
}
tuple {!STATISTICS <path>} {
Signal to invalidate the statistics of page image <path>.
} {
}
tuple {STATISTICS <path> <stats>} {
<stats> are the statistics of page image <path>.
} {
}
tuple {STATSQ <path>} {
Order to compute the statistics of page image <path>.
} {
}
|
Added attic/doc/tasks.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# -*- tcl -*-
document {
description {
Task Documentation.
Listing all tasks with the package implementing them, the
pre-conditions, i.e. scoreboard contents (tuple existence), it
triggers on, the results (new and removed tuples), again scoreboard
contents, and additional scoreboard data which is accessed during the
execution of the task.
}
task bookflow::scan {
description {
Scan the project directory, locate the project database and the
images to process. One shot task, exits after the scan is complete.
Initial task. Automatically triggered.
}
thread
trigger {}
behavior {
(1) {
action { Scan directory for database, images}
output {
add {AT <dir>}
}
}
(2) {
guard { Neither images nor project database found }
output {
add {PROJECT ERROR *}
}
}
(3) {
guard { Images found, but no project database }
output {
add {FILE *}
add {PROJECT CREATE}
}
}
(4) {
guard { Images and project database are found }
output {
add {FILE *}
add {DATABASE *}
add {PROJECT VERIFY}
}
}
}
}
task bookflow::error {
description {
Waits for other tasks to signal an error and reports it.
Continuous task.
}
event
trigger {
{PROJECT ERROR *}
}
behaviour {
(1) {
action { Report the error held by the tuple }
output {}
}
}
}
task bookflow::verify {
description {
Load the database and check its contents against
the set of images found by the scanner.
One shot task, exits after the check is done.
}
thread
trigger {
{PROJECT VERIFY}
}
behaviour {
(1) {
action {
{AT *}
{DATABASE *}
{FILE *}
Open database, load set of images known to it.
Get the set of found images.
Compare for missing and additional images.
}
}
(2) {
guard {
The set of images in the directory does not match
the set of images in the project.
}
output {
add {PROJECT ERROR *}
NOTE { --- Allow corrective action by the user ? --- }
NOTE { --- Auto-correction?
i.e. Ignore additional images
and. Mark missing images as such and ignore further.
}
}
}
(3) {
guard {
The set of images in the directory is consistent
with the set of images in the project.
}
action {
}
output {
remove {FILE *}
add {BOOK <name> <...>}
add {IMAGE <file> ...}
add {PART <book> <file>}
}
}
}
}
task bookflow::create {
description {
Create a fresh project database in the project directory
and populate it with the found images.
One shot task, exits after the creation is done.
}
thread
trigger {
{PROJECT CREATE}
}
behaviour {
(1) {
action {
{AT *}
{DATABASE *}
Get the set of found images.
Open database, write images and basic status to it.
Fill the scoreboard based on the information.
}
output {
remove {FILE *}
add {DATABASE *}
add {BOOK <name> <...>}
add {IMAGE <file> ...}
add {PART <book> <file>}
}
}
}
}
}
|
Added attic/doc/user_actions.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
While I want bookflow to be mostly automatic when identifying pages,
markers and processing everything, writing the automatics will take
time and I wish to process the books I have now. So, some commands
have to be implemented which go towards that goal.
This actually may have another advantage. Training data. Perfectly
labeled images which can used to train some type of system for the
image classification.
Most interactivity is through the keyboard, which is generally quicker.
Key Note Command Notes
--- ---- ------- -----
(i) SPACE show next
(ii) -> cursor show next change of selection, active item
(iii) <- cursor show previous s.a.
--- ---- ------- -----
(iv) b label as black marker
(v) w label as white marker = lightfield
(vi) c label as cover (front, back automatic based on the
section we are in)
--- ---- ------- ----
The commands (iv) and (v) are enough for the system to then
automatically determine the locations of the composite markers
delimiting the various sections (garbage, even, odd), and label the
pages in the sections. The command (vi) is needed to fix the pages
which are the covers and likely mislabled as plain pages.
When all pages (for a book) are labeled we can trigger the next phase,
which
(a) places them into a separate (new) book
(b) associates each page with the nearest preceding lightfield in
imaging order.
(c) re-orders them front to back
(d) rotates the derived images (thumbnail, page display) upright
NOTE: the base images are not modified.
NOTE: this is done by invalidating the data and then using the
labels in the scaler tasks to determine the use of rotations.
NOTE: rotate after scaling, less data to handle.
A problem, we have to note somewhere which thumbnails have been
rotated, and which don't. Likely in the project database, as an
annotation.
|
Added attic/lib/bookflow/bookflow.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Copyright (c) 2010 Andreas Kupries.
## BSD License
## Main package of the book scanning workflow application, aka
## bookflow.
# # ## ### ##### ######## ############# #####################
## Requirements
package require Tcl 8.5 ; # Required runtime.
package require Tk
package require blog ; # End-user visible activity logging,
package require widget::log ; # and the display for it.
package require widget::toolbar
package require scoreboard
package require bookflow::scan ; # Task. Scan project directory for images and database
package require bookflow::error ; # Task. Post error reports to the user.
package require bookflow::create ; # Task. Create project database when missing and images available.
package require bookflow::verify ; # Task. Verify project database when existing, and pre-load cached data.
package require bookflow::thumbnail ; # Task. Generate thumbnails for page images.
package require bookflow::greyscale ; # Task. Generate greyscale for page images.
package require bookflow::bright ; # Task. Compute brightness of page images.
package require bookflow::project::server ; # Task. In-application database server.
package require bookw ; # Book Display
namespace eval ::bookflow {}
# # ## ### ##### ######## ############# #####################
## API
proc ::bookflow::run {arguments} {
MakeGUI
after idle [list after 10 [namespace code [list Start $arguments]]]
vwait __forever
return
}
# # ## ### ##### ######## ############# #####################
## Internals
proc ::bookflow::MakeGUI {} {
wm withdraw .
Widgets
Layout
Bindings
wm deiconify .
return
}
proc ::bookflow::Start {arguments} {
variable project
Log.bookflow Booting...
if {![llength $arguments]} {
set project [pwd]
} else {
set project [lindex $arguments 0]
}
Log.bookflow {Project in $project}
bookflow::create ; # Watch for request to create new project database.
bookflow::verify ; # Watch for request to verify existing project database.
bookflow::error ; # Watch for error reports
bookflow::thumbnail ; # Watch for thumbnail generation requests.
bookflow::greyscale ; # Watch for greyscale generation requests.
bookflow::bright ; # Watch for brightness calculation requests.
bookflow::scan $project ; # Scan project directory
# TODO :: Launch the other tasklets monitoring the scoreboard for
# TODO :: their trigger conditions.
return
}
proc ::bookflow::Widgets {} {
# Re-style the notebook to use left-side tab-buttons
ttk::style configure VerticalTabsLeft.TNotebook -tabposition wn
widget::toolbar .toolbar
ttk::notebook .books -style VerticalTabsLeft.TNotebook
::widget::log .log -width 120 -height 2
.toolbar add button exit -text Exit -command ::exit -separator 1
return
}
proc ::bookflow::Layout {} {
pack .toolbar -side top -fill both -expand 0
pack .books -side top -fill both -expand 1
pack .log -side bottom -fill both -expand 0
return
}
proc ::bookflow::Bindings {} {
# Redirect log writing into the widget
::log on :: 0 .log
::log on bookflow
# Watch and react to scoreboard activity
# Here: Extend the notebook when new books are announced
scoreboard bind put {BOOK *} [namespace code BookNew]
return
}
# # ## ### ##### ######## ############# #####################
# TODO :: Analyse BookNew/Del for race conditions when a book B is
# TODO :: rapidly added and removed multiple times.
proc ::bookflow::BookNew {tuple} {
variable bookcounter
variable project
lassign $tuple _ name
set w .books.f$bookcounter
incr bookcounter
::bookw $w $name $project -log Log.bookflow
.books add $w -sticky nsew -text $name ; # TODO : -image book-icon -compound
# Watch and react to scoreboard activity
# Here: Update (shrink) the notebook when this book is removed.
scoreboard bind take [list BOOK $name] [namespace code [list BookDel $w]]
return
}
proc ::bookflow::BookDel {w tuple} {
# Drop the panel from the notebook, and remove the binding which invoked us.
.books forget $w
destroy $w
scoreboard unbind take [list BOOK $name] [namespace code [list BookDel $w]]
return
}
# # ## ### ##### ######## ############# #####################
## Ready
namespace eval ::bookflow {
namespace export {[a-z]*}
namespace ensemble create
variable bookcounter 0
variable project {}
}
package provide bookflow 1.0
return
|
Added attic/lib/bookflow/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow 1.0 [list source [file join $dir bookflow.tcl]]
|
Added attic/lib/bookw/bookw.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# The main window for each book found in the project.
# NOTES
# (1) Consider moving the chart and attendant structures and methods
# into its own megawidget.
# (2) Consider moving the thumbnail load handling into a helper class
# too. Re-usable for the regular images ?
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require Tk
package require snit
package require iq
package require scoreboard
package require img::strip ; # Strip of thumbnail images at the top.
package require img::page ; # Page spread, single or double.
package require debug
package require debug::snit
package require blog
package require img::png
package require rbc
package require uevent::onidle
package require struct::set
package require math::statistics
package require bookflow::thumbnail ; # Request encapsulation
# ### ### ### ######### ######### #########
## Tracing
debug prefix bookw {[::debug::snit::call] }
debug off bookw
#debug on bookw
# ### ### ### ######### ######### #########
## Implementation
snit::widgetadaptor ::bookw {
option -log -default {}
# ### ### ### ######### ######### #########
##
constructor {book project args} {
Debug.bookw {}
installhull using ttk::frame
install myrbright using uevent::onidle ${selfns}::RBG [mymethod RefreshBright]
install mytqueue using iq ${selfns}::QT 4 -emptycmd [mymethod Refill]
; # TODO : Query producer for allowed rate.
install mysqueue using iq ${selfns}::QB 4 ; # TODO : Query producer for allowed rate.
set myproject $project
set mybook $book
set mypattern [list IMAGE * $book]
$self Widgets
$self Layout
$self Bindings
# Note: We are peek'ing because at this time images for the
# named book might have already been added to the scoreboard,
# which won't be caught by the 'put' event we are registering.
scoreboard peek $mypattern [mymethod BookImages]
scoreboard bind put $mypattern [mymethod BookImageNew]
scoreboard bind take $mypattern [mymethod BookImageDel]
$self configurelist $args
Debug.bookw {/}
return
}
destructor {
Debug.bookw {}
scoreboard unbind put $mypattern [mymethod BookImageNew]
scoreboard unbind take $mypattern [mymethod BookImageDel]
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
##
method Widgets {} {
# Chart of brightness values for the page images.
rbc::graph $win.chart -height 200
#rbc::graph $win.chart -height 400
$win.chart axis configure y -min 0 -max 256
$win.chart axis configure y2 -hide 0
rbc::vector create ${selfns}::O ; # X-axis, page serial, ordering.
rbc::vector create ${selfns}::B ; # page brightness
rbc::vector create ${selfns}::D ; # page brightness differences
rbc::vector create ${selfns}::S ; # page brightness std deviation
# Chart: Page brightness
$win.chart element create b \
-xdata ${selfns}::O \
-ydata ${selfns}::B \
-color blue -symbol none -label B
# Chart: Page brightness delta to previous
$win.chart element create bd \
-xdata ${selfns}::O \
-ydata ${selfns}::D \
-mapy y2 -color red -symbol none -label D
# Chart: Page brightness standard deviation.
$win.chart element create bv \
-xdata ${selfns}::O \
-ydata ${selfns}::S \
-color orange -symbol none -label S
# Chart: Vertical line for current selection.
# Starting outside of the axes = invisible.
$win.chart marker create line -name selection \
-fill green -outline green \
-coords {-1 -Inf -1 Inf}
$win.chart marker create text -name tselectionr \
-coords {-1 10} -text {} -outline green -anchor w
$win.chart marker create text -name tselectionl \
-coords {-1 250} -text {} -outline green -anchor e
# Chart: Scatter plot for the points of interest. Enough for
# all the regular chart plots.
rbc::vector create ${selfns}::XB
rbc::vector create ${selfns}::YB
rbc::vector create ${selfns}::XD
rbc::vector create ${selfns}::YD
rbc::vector create ${selfns}::XV
rbc::vector create ${selfns}::YV
$win.chart element create boutlier \
-xdata ${selfns}::XB \
-ydata ${selfns}::YB \
-color blue -symbol circle -label {} \
-linewidth 0
$win.chart element create doutlier \
-xdata ${selfns}::XD \
-ydata ${selfns}::YD \
-color red -symbol square -label {} \
-linewidth 0 -mapy y2
$win.chart element create voutlier \
-xdata ${selfns}::XV \
-ydata ${selfns}::YV \
-color orange -symbol diamond -label {} \
-linewidth 0
# Strip of thumbnails for the page images.
img::strip $win.strip -orientation vertical
# Single/double page spread.
img::page $win.pages
return
}
method Layout {} {
pack $win.strip -side left -fill both -expand 0
pack $win.chart -side top -fill both -expand 0
#pack $win.strip -side top -fill both -expand 0
pack $win.pages -side top -fill both -expand 1
return
}
method Bindings {} {
bind $win.strip <<SelectionChanged>> \
[mymethod Selection %d]
bind $win.chart <1> [mymethod ChartSelection %x]
return
}
# ### ### ### ######### ######### #########
method Selection {selection} {
Debug.bookw {}
if {![llength $selection]} return
set token [lindex $selection 0]
set path $mypath($token)
set serial $myorder($path)
Debug.bookw { | $token -> $path -> $serial}
# Move the seletion marker and its associated texts (all in
# the chart) to the new location.
$win.chart marker configure selection \
-coords [list $serial -Inf $serial Inf]
$win.chart marker configure tselectionr \
-coords [list $serial 10] -text $serial
$win.chart marker configure tselectionl \
-coords [list $serial 250] -text $serial
$self Select $serial
Debug.bookw {/}
return
}
method ChartSelection {x} {
Debug.bookw {}
# Screen to graph coordinates, then select the associated image.
$self Select [expr {int([$win.chart axis invtransform x $x])}]
Debug.bookw {/}
return
}
method Select {serial} {
# x coordinate to image path, to the token used by the strip.
Debug.bookw {}
if {![info exists myopath($serial)]} {
after idle [list after 0 [info level 0]]
Debug.bookw {/ defered}
}
set path $myopath($serial)
set token $mytoken($path)
if {$myshown eq $path} return
set myshown $path
# Set the selection in the strip, this comes back to us via
# 'Selection' above, which then updates the chart.
$win.strip selection set $token
# Request the regular page (still scaled down) for the page
# spread underneath the chart, to the right of the strip.
$self GetRegular $path 1
Debug.bookw {/ shown = $myshown}
return
}
# ### ### ### ######### ######### #########
method BookImages {tuples} {
# tuples = list ((IMAGE path serial book)...)
Debug.bookw {}
# For ease of processing we simply run these through
# BookImageNew...
foreach t $tuples {
$self BookImageNew $t
}
Debug.bookw {/}
return
}
method BookImageNew {tuple} {
# tuple = (IMAGE path serial book)
Debug.bookw {}
lassign $tuple _ path serial book
# TODO : Should assert that book is the expected one.
incr mycountimages
$self Log "Book $book ($path /$mycountimages)"
set token [$win.strip new]
$win.strip itemconfigure $token \
-label "$path ($serial)" \
-order $serial \
-message {Creating thumbnail...}
set mytoken($path) $token
set mypath($token) $path
set myorder($path) $serial
set myopath($serial) $path
# Issue requests for the derived data needed by the widget.
$self GetThumbnail $path
$self GetStatistics $path
# Handling of the medium size thumbnail. First one request
# immediately for display. Also immediately if all small
# thumbnails known. Otherwise defer to to when the issue queue
# emptied (of small thumbnails).
if {$mycountimages < 2} {
after idle [mymethod Select 0]
} elseif {$mycountthumbsmall == $mycountimages} {
$self GetRegular $path 1
} else {
lappend mympending $path
}
$win.chart axis configure x -min 0 -max $mycountimages
Debug.bookw {/}
return
}
method BookImageDel {tuple} {
# tuple = (IMAGE path serial book)
Debug.bookw {}
lassign $tuple _ path serial book
# TODO : Should assert that book is the expected one.
incr mycountimages -1
incr mycountthumbsmall -1
incr mycountthumbmedium -1
incr mycountstat -1
$self Log "Book $book ($path /$mycountimages)"
# doc/interaction_pci.txt (5), release monitor
scoreboard unbind take [list THUMBNAIL $path *] [mymethod InvalidThumbnail]
# doc/interaction_pci.txt (4) - A waiting wpeek cannot released/canceled.
#scoreboard wpeek [list THUMBNAIL $path *] [mymethod HaveThumbnail]
# doc/interaction_pci.txt (5), release monitor
scoreboard unbind take [list STATISTICS $path *] [mymethod InvalidStatistics]
# doc/interaction_pci.txt (4) - A waiting wpeek cannot released/canceled.
#scoreboard wpeek [list STATISTICS $path *] [mymethod HaveThumbnail]
set token $mytoken($path)
set serial $myorder($path)
unset mytoken($path)
unset mypath($token)
unset myorder($path)
unset myopath($serial)
$win.strip drop $token
$myrbright request
$win.chart axis configure x -min 0 -max $mycountimages
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
method GetThumbnail {path} {
Debug.bookw {}
set request [bookflow::thumbnail::request $path 160];# x120
# doc/interaction_pci.txt (5).
scoreboard bind take $request [mymethod InvalidThumbnail]
# doc/interaction_pci.txt (4). Uses rate-limiting queue
$mytqueue put $request [mymethod HaveThumbnail]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (5).
method InvalidThumbnail {tuple} {
# tuple = (THUMBNAIL image-path size thumbnail-path)
Debug.bookw {}
lassign $tuple _ path size thumb
if {$size != 160} { error {Size mismatch} }
# Ignore invalidation of a small thumbnail when its image is
# not used here any longer.
if {![info exists mytoken($path)]} {
Debug.bookw {ignored/}
return
}
incr mycountthumbsmall -1
$self Log "Refresh TS $path $mycountthumbsmall/$mycountimages"
# Still using the image, therefore request a shiny new valid
# small thumbnail. doc/interaction_pci.txt (4).
$win.strip itemconfigure $mytoken($path) \
-message {Invalidated...}
$mytqueue put [bookflow::thumbnail::request $path $size] [mymethod HaveThumbnail]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (4).
method HaveThumbnail {tuple} {
# tuple = (THUMBNAIL image-path size thumbnail-path)
# Paths are relative to the project directory
Debug.bookw {}
lassign $tuple _ path size thumb
if {$size != 160} { error {Size mismatch} }
# Ignore the incoming thumbnail when its image is not used
# here any longer.
if {![info exists mytoken($path)]} {
Debug.bookw {ignored/}
return
}
incr mycountthumbsmall
$self Log "Thumbnail S $path $mycountthumbsmall/$mycountimages"
# Load small thumbnail and place it into the strip
# proper. Careful, retrieve and destroy any previously shown
# thumbnail first.
set photo [$win.strip itemcget $mytoken($path) -image]
if {$photo ne {}} {
image delete $photo
}
set photo [image create photo -file $myproject/$thumb]
$win.strip itemconfigure $mytoken($path) \
-image $photo \
-message {}
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
method Refill {args} {
if {![llength mympending]} return
foreach path $mympending {
$self GetRegular $path
}
set mympending {}
return
}
# ### ### ### ######### ######### #########
method GetRegular {path {fasttrack 0}} {
Debug.bookw {}
if {![string match {IMG_*} $path]} { error {Bad Path} }
set request [bookflow::thumbnail::request $path 800];# x600
# doc/interaction_pci.txt (5).
scoreboard bind take $request [mymethod InvalidRegular]
# doc/interaction_pci.txt (4). Uses rate-limiting queue. The
# same as the 160er thumbnails.
if {$fasttrack} {
# Bypass queue for fast track issue.
scoreboard wpeek $request [mymethod HaveRegular]
} else {
$mytqueue put $request [mymethod HaveRegular]
}
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (5).
method InvalidRegular {tuple} {
# tuple = (THUMBNAIL image-path size thumbnail-path)
Debug.bookw {}
lassign $tuple _ path size thumb
if {$size != 800} { error {Size mismatch} }
# Ignore invalidation of a medium thumbnail when its image is
# not used here any longer. Ditto if the image is used, but
# not shown.
if {![info exists mytoken($path)] ||
($myshown ne $path)} {
Debug.bookw {ignored/}
return
}
incr mycountthumbmedium -1
$self Log "Refresh TM $path $mycountthumbmedium/$mycountimages"
# Still using the image, therefore request a shiny new valid
# medium thumbnail. doc/interaction_pci.txt (4).
# TODO : Get and destroy currently shown image...
$win.pages even image {}
$win.pages even text {Invalidated...}
$mytqueue put [bookflow::thumbnail::request $path $size] [mymethod HaveRegular]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (4).
method HaveRegular {tuple} {
# tuple = (THUMBNAIL image-path size thumbnail-path)
# Paths are relative to the project directory.
Debug.bookw {}
lassign $tuple _ path size thumb
if {$size != 800} { error {Size mismatch} }
incr mycountthumbmedium
$self Log "Regular M $path $mycountthumbmedium/$mycountimages"
# Ignore the incoming medium thumbnail when its image is not
# used here any longer. Ditto if the image is used, but not
# shown.
if {![info exists mytoken($path)] ||
($myshown ne $path)} {
Debug.bookw {ignored/ [info exists mytoken($path)], ($myshown ne $path)? $myshown = $path}
return
}
# Load medium thumbnail and place it into the page spread
# proper. Careful, retrieve and destroy any previously shown
# image first.
# TODO - get and delte previous image
#set photo [$win.strip itemcget $mytoken($path) -image]
#if {$photo ne {}} { image delete $photo }
set photo [image create photo -file $myproject/$thumb]
$win.pages even text {}
$win.pages even image $photo
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
method GetStatistics {path} {
Debug.bookw {}
# doc/interaction_pci.txt (5).
scoreboard bind take [list STATISTICS $path *] [mymethod InvalidStatistics]
# doc/interaction_pci.txt (4). Uses rate-limiting queue
$mysqueue put [list STATISTICS $path *] [mymethod HaveStatistics]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (5).
method InvalidStatistics {tuple} {
# tuple = (STATISTICS image-path statistics)
Debug.bookw {}
lassign $tuple _ path statistics
# Ignore invalidation of statistics when its image is not used
# here any longer.
if {![info exists mytoken($path)]} {
Debug.bookw {/}
return
}
incr mycountstat -1
$self Log "Refresh S $path $mycountstat/$mycountimages"
# Still using the image, therefore request shiny new valid
# statistics for it. doc/interaction_pci.txt (4).
unset mystat($path)
$myrbright request
$mysqueue put [list STATISTICS $path *] [mymethod HaveStatistics]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (4).
method HaveStatistics {tuple} {
# tuple = (STATISTICS image-path statistics)
# Paths are relative to the project directory
Debug.bookw {}
lassign $tuple _ path statistics
# Ignore the incoming statistics when its image is not
# used here any longer.
if {![info exists mytoken($path)]} {
Debug.bookw {/}
return
}
incr mycountstat
$self Log "Statistics $path $mycountstat/$mycountimages"
set mystat($path) $statistics
$myrbright request
Debug.bookw {/}
return
}
method RefreshBright {} {
Debug.bookw {}
# Pull the currently known statistics out of our data
# structures, put the brightnesses into the proper order, then
# stuff the result into the chart.
set o {}
set b {}
set s {}
set d {}
set l {}
set bxy {}
foreach serial [lsort -dict [array names myopath]] {
set path $myopath($serial)
if {![info exists mystat($path)]} continue
lassign $mystat($path) _ _ mean _ _ stddev _ _
# brightness = mean.
lappend o $serial
lappend b $mean
lappend s $stddev
lappend d [expr {($l eq {}) ? 0 : ($mean - $l)}]
set l $mean
# dict form of x/y, mapping x to y, for the fusing below.
lappend bxy $serial $mean
}
Debug.bookw { O = ($o)}
Debug.bookw { B = ($b)}
Debug.bookw { D = ($d)}
Debug.bookw { S = ($s)}
${selfns}::O set $o
${selfns}::B set $b
${selfns}::D set $d
${selfns}::S set $s
# Outliers, computed from global statistics of the page brightness.
if {[llength $o]} {
# Get 2-sigma outliers for page brightness
lassign [Outlier $o $b] bx by
# Get 2-sigma outliers for page brightness differences
lassign [Outlier $o $d] dx dy
# Get 2-sigma outliers for page brightness stddev
lassign [DownOutlier $o $s] vx vy
# Fuse the results. Points of interest are the locations of
# stddev outliers and the locations where both brightness and
# brightness deltas indicate outliers. Compute the y locations
# for these using the bxy map.
set ix [lsort -integer [struct::set union $vx [struct::set intersect $bx $dx]]]
set iy {} ; foreach x $ix { lappend iy [dict get $bxy $x] }
${selfns}::XB set $ix
${selfns}::YB set $iy
#${selfns}::XD set $dx
#${selfns}::YD set $dy
#${selfns}::XV set $vx
#${selfns}::YV set $vy
}
Debug.bookw {/}
return
}
# Find the t-sigma outliers above and below the yseries average.
proc Outlier {xseries yseries {t 2}} {
lassign [math::statistics::basic-stats $yseries] \
avg min max n stddev var pstddev pvar
set t [expr {$t * $stddev}]
set xo {}
set yo {}
foreach x $xseries y $yseries {
if {abs($y - $avg) < $t} continue
lappend xo $x
lappend yo $y
}
return [list $xo $yo]
}
# Find the t-sigma outliers below the yseries average
proc DownOutlier {xseries yseries {t 2}} {
lassign [math::statistics::basic-stats $yseries] \
avg min max n stddev var pstddev pvar
set t [expr {$t * $stddev}]
set xo {}
set yo {}
foreach x $xseries y $yseries {
if {($avg - $y) < $t} continue
lappend xo $x
lappend yo $y
}
return [list $xo $yo]
}
# ### ### ### ######### ######### #########
method Log {text} {
if {$options(-log) eq {}} return
uplevel #0 [list {*}$options(-log) $text]
return
}
# ### ### ### ######### ######### #########
##
variable myproject ; # Path of project directory.
variable mybook ; # Name of the book this is connected to
variable mypattern ; # Scoreboard pattern for images of this book.
variable mytoken -array {} ; # Map image PATHs to the associated
# TOKEN in the strip of images.
variable mypath -array {} ; # Map tokens back to their image PATHs.
variable myorder -array {} ; # Map image PATHs to the associated
# order in the strip of images, and
# chart of page brightness,
variable myopath -array {} ; # Map serial order to image PATH.
variable mystat -array {} ; # Map image PATHs to the associated
# page statistics.
variable myrbright {} ; # onidle collator for brightness refresh
variable mytqueue {} ; # Issue queue for thumbnails
variable mysqueue {} ; # Issue queue for statistics
variable mycountimages 0 ; # Number of managed images
variable mycountthumbsmall 0 ; # Number of managed small thumbnails
variable mycountthumbmedium 0 ; # Number of managed medium thumbnails
variable mycountstat 0 ; # Number of managed brightness values
variable myshown {} ; # PATH of currently shown/selected page.
variable mympending {} ; # List of pages for which the medium
# sized thumbnails are pending.
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide bookw 0.1
return
|
Added attic/lib/bookw/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookw 0.1 [list source [file join $dir bookw.tcl]]
|
Added attic/lib/bright/bright.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task. Continuous.
# Calculating the basic statistica values for page images.
# Called 'brightness' for historical reasons. That was the only value
# computed here at first (mean).
# A producer in terms of "doc/interaction_pci.txt"
# A consumer as well, of page greyscale images.
#
# Calculated statistical values are cached in the project database.
# Limits itself to no more than four actual threads in flight,
# i.e. computing image statistics. The computing tasks do not exit on
# completion, but wait for more operations to perform. Communication
# and coordination is done through the scoreboard. As usual.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
package require scoreboard
package require bookflow::project
namespace eval ::bookflow::bright {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/bright
#debug on bookflow/bright
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::bright {} {
Debug.bookflow/bright {Bookflow::Bright Watch}
scoreboard wpeek {AT *} [namespace code bright::BEGIN]
Debug.bookflow/bright {/}
return
}
proc ::bookflow::bright::BEGIN {tuple} {
# tuple = (AT project)
Debug.bookflow/bright {Bookflow::Bright BEGIN <$tuple>}
lassign $tuple _ project
::bookflow::project::ok [namespace code [list INIT $project]]
Debug.bookflow/bright {Bookflow::Bright BEGIN/}
return
}
proc ::bookflow::bright::INIT {project} {
Debug.bookflow/bright {Bookflow::Bright INIT}
# Monitor for invalidation of statistics
# doc/interaction_pci.txt (1)
scoreboard take {!STATISTICS *} [namespace code INVALIDATE]
# Launch the tasks doing the actual resizing.
variable max
for {set i 0} {$i < $max} {incr i} {
task launch [list ::apply {{project} {
package require bookflow::bright
bookflow::bright::STATISTICS $project
}} $project]
}
# Monitor for bright creation requests.
# doc/interaction_pci.txt (2)
scoreboard bind missing {STATISTICS *} [namespace code MAKE]
Debug.bookflow/bright {Bookflow::Bright INIT/}
return
}
# ### ### ### ######### ######### #########
## Internals. Bright invalidation. See doc/interaction_pci.txt (1).
proc ::bookflow::bright::INVALIDATE {tuple} {
# tuple = (!STATISTICS path)
lassign $tuple _ path
Debug.bookflow/bright {Bookflow::Bright INVALIDATE $path}
scoreboard takeall [list STATISTICS $path *] [namespace code [list RETRACT $path]]
Debug.bookflow/bright {Bookflow::Bright INVALIDATE/}
return
}
proc ::bookflow::bright::RETRACT {path tuples} {
Debug.bookflow/bright {Bookflow::Bright RETRACT $path}
::bookflow::project statistics unset $path
# Look for more invalidation requests
scoreboard take {!STATISTICS *} [namespace code INVALIDATE]
Debug.bookflow/bright {Bookflow::Bright RETRACT/}
return
}
# ### ### ### ######### ######### #########
## Internals. Bright creation. See doc/interaction_pci.txt (2).
proc ::bookflow::bright::MAKE {pattern} {
# pattern = (STATISTICS path *)
Debug.bookflow/bright {Bookflow::Bright MAKE <$pattern>}
lassign $pattern _ path
set statistics [::bookflow::project statistics get $path]
if {$statistics ne {}} {
# The requested values already existed in the project database,
# simply make them available.
# TODO :: Have the verify task-to-be load existing brightness
# TODO :: information to shortcircuit even this fast bailout.
# TODO :: Note however that we will then need some way to
# TODO :: prevent the insertion of duplicate or similar tuples.
RESULT $path $statistics
} else {
# Statistics are not known. Put in a request for the computing
# tasks to generate them. This will also put the proper result
# into the scoreboard on completion.
scoreboard put [list STATSQ $path]
}
Debug.bookflow/bright {Bookflow::Bright MAKE/}
return
}
proc ::bookflow::bright::RESULT {path statistics} {
scoreboard put [list STATISTICS $path $statistics]
return
}
# ### ### ### ######### ######### #########
## Internals. Implementation of the calculation tasks.
proc ::bookflow::bright::STATISTICS {project} {
package require debug
Debug.bookflow/bright {Bookflow::Bright STATISTICS}
# Requisites for the task
package require bookflow::bright
package require bookflow::project
package require scoreboard
package require crimp ; wm withdraw .
package require fileutil
# Start waiting for requests.
::bookflow::project::ok [namespace code [list READY $project]]
Debug.bookflow/bright {Bookflow::Bright STATISTICS/}
return
}
proc ::bookflow::bright::READY {project} {
# Wait for more requests.
scoreboard take {STATSQ *} [namespace code [list STAT $project]]
return
}
proc ::bookflow::bright::STAT {project tuple} {
# tuple = (STATSQ path)
# Decode request
lassign $tuple _ path
Debug.bookflow/bright {Bookflow::Bright STAT $path}
# Get the greyscale form of the image
scoreboard take [list GREYSCALE $path *] [namespace code [list MEAN $project]]
Debug.bookflow/bright {Bookflow::Bright STAT/}
return
}
proc ::bookflow::bright::MEAN {project tuple} {
# tuple = (GREYSCALE path grey-path)
lassign $tuple _ path grey
Debug.bookflow/bright {Bookflow::Bright MEAN $path |$grey}
set data [fileutil::cat -translation binary $project/$grey]
Debug.bookflow/bright { read ok $path}
set image [crimp read pgm $data]
Debug.bookflow/bright { pgm read ok $path}
set stats [crimp statistics basic $image]
Debug.bookflow/bright { statistics ok $path}
array set s [dict get $stats channel luma]
Debug.bookflow/bright { statistics ok $path}
set statistics [list $s(min) $s(max) $s(mean) $s(middle) $s(median) $s(stddev) $s(variance) $s(hf)]
# Save/Cache result in the project.
::bookflow::project statistics set $path {*}$statistics
Debug.bookflow/bright { db ok $path}
# Push result
RESULT $path $statistics
# Wait for more requests.
READY $project
Debug.bookflow/bright {Bookflow::Bright MEAN $path = $statistics/}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow::bright {
# Number of parallel calculation tasks.
variable max 4
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::bright 0.1
return
|
Added attic/lib/bright/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::bright 0.1 [list source [file join $dir bright.tcl]]
|
Added attic/lib/create/create.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task.
# Waiting for requests to create an initial project database.
# Launches the task when the request is found.
# Creates the specified directory, looking for the BOOKFLOW database and
# JPEG images.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
namespace eval ::bookflow::create {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/create
#debug on bookflow/create
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::create {} {
Debug.bookflow/create {Bookflow::Create Watch}
scoreboard take {PROJECT CREATE} [namespace code create::RUN]
Debug.bookflow/create {/}
}
# ### ### ### ######### ######### #########
## Internals
proc ::bookflow::create::RUN {tuple} {
Debug.bookflow/create {Bookflow::Create RUN}
Log.bookflow {Creating project database...}
task launch [list ::apply {{} {
package require bookflow::create
bookflow::create::TASK
}}]
Debug.bookflow/create {Bookflow::Create RUN/}
return
}
proc ::bookflow::create::TASK {} {
package require debug
Debug.bookflow/create {Bookflow::Create TASK}
# Requisites for the task
package require scoreboard
package require bookflow::create
package require bookflow::project ; # client
scoreboard wpeek {AT *} [namespace code BEGIN]
Debug.bookflow/create {Bookflow::Create TASK/}
return
}
proc ::bookflow::create::BEGIN {tuple} {
# tuple = (AT project)
variable defaultfile
Debug.bookflow/create {Bookflow::Create BEGIN <$tuple>}
# Get the payload
lassign $tuple _ projectdir
# Declare database presence, triggers creation.
Log.bookflow {% Project database $defaultfile}
scoreboard put [list DATABASE $defaultfile]
# At this point the server thread will complete initialization and
# provide access to the database. We wait until it has done so:
::bookflow::project::ok [namespace code [list WaitForServerStart $projectdir]]
Debug.bookflow/create {Bookflow::Create BEGIN/}
return
}
proc ::bookflow::create::WaitForServerStart {project} {
Debug.bookflow/create {Bookflow::Create WaitForServerStart}
# Fill the database using the image files found by the scanner.
scoreboard takeall {FILE*} [namespace code [list FILES $project]]
Debug.bookflow/create {Bookflow::Create WaitForServerStart/}
return
}
proc ::bookflow::create::FILES {project tuples} {
Debug.bookflow/create {Bookflow::Create FILES}
# tuples = list ((FILE *)...)
# ... pull books out of the database and declare them ...
# ... push files into the @scratch book, and declare
# them as images, with book link ...
foreach b [::bookflow::project books] {
Debug.bookflow/create { BOOK $b}
scoreboard put [list BOOK $b]
}
# Sorted by file name (like IMG_nnnn), this is the initial order.
foreach def [lsort -dict -index 1 $tuples] {
lassign $def _ jpeg
set serial [::bookflow::project book extend @SCRATCH $jpeg \
[file mtime $project/$jpeg]]
Debug.bookflow/create { IMAGE $jpeg $serial @SCRATCH}
scoreboard put [list IMAGE $jpeg $serial @SCRATCH]
}
Debug.bookflow/create {Bookflow::Create FILES/}
task::exit
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow {
namespace export create
namespace ensemble create
namespace eval create {
variable defaultfile BOOKFLOW
}
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::create 0.1
return
|
Added attic/lib/create/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::create 0.1 [list source [file join $dir create.tcl]]
|
Added attic/lib/db/db.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Access to a bookflow database, file identification, creation, etc.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require debug::snit
package require snit
package require sqlite3
namespace eval ::bookflow::db {}
# ### ### ### ######### ######### #########
## Tracing
debug prefix bookflow/db {[::debug::snit::call] }
debug off bookflow/db
#debug on bookflow/db
# ### ### ### ######### ######### #########
## API & Implementation
snit::type ::bookflow::db {
# ### ### ### ######### ######### #########
typemethod isBookflow {path} {
if {![file exists $path]} { return 0 }
if {![file isfile $path]} { return 0 }
# FUTURE :: Extend fileutil::fileType
# readable, sqlite database ?
if {[catch {
set c [open $path r]
fconfigure $c -translation binary
}]} { return 0 }
set head [read $c 15]
close $c
if {$head ne {SQLite format 3}} { return 0 }
# check for the bookflow tables
set db ${type}::DB
sqlite3 $db $path
set ok [expr {[Has $db bookflow] &&
[Has $db book] &&
[Has $db image] &&
[Has $db statistics]}]
$db close
return $ok
}
proc Has {db table} {
return [llength [$db eval {
SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name = $table
;
}]]
}
# ### ### ### ######### ######### #########
typemethod new {path} {
Debug.bookflow/db { @ $path}
# Create the database file at the specified location, and fill
# it with the necessary tables.
set db ${type}::DB
sqlite3 $db $path
$db eval {
-- Global, per project information
CREATE TABLE bookflow (
dpi INTEGER NOT NULL -- dots per inch for the whole project.
);
-- A project is subdivided into one or more books.
-- Note that each project internally uses two standard
-- 'books'. These are the 'scratchpad' holding all
-- images not assigned to a user-created book, and the
-- 'trash' holding the data about images which are gone,
-- for their eventual resurrection.
CREATE TABLE book (
bid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
-- FUTURE : More book information, like author, isbn,
-- FUTURE : printing datum, etc. Possibly in a separate
-- FUTURE : table for meta data.
);
-- The @ character is illegal in user-specified book names,
-- ensuring that the standard books can never be in conflict
-- with the user's names.
INSERT INTO book VALUES (0,'@SCRATCH');
INSERT INTO book VALUES (1,'@TRASH');
-- All images, which always belong to a single book.
-- Images have an order imposed on them (see field 'ord'),
-- which is unique within a book.
CREATE TABLE image (
iid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL UNIQUE,
bid INTEGER NOT NULL REFERENCES book,
ord INTEGER NOT NULL,
mtime INTEGER NOT NULL,
UNIQUE (bid, ord)
);
-- Statistical data for all images. Used to classify
-- images, distinguishing markers from regular pages.
-- Actually the whole slew of basic statistics. Just in
-- case. (Machine-learning over lots of prjects ?!).
CREATE TABLE statistics (
iid INTEGER NOT NULL REFERENCES image,
min INTEGER NOT NULL,
max INTEGER NOT NULL,
mean REAL NOT NULL,
middle REAL NOT NULL,
median INTEGER NOT NULL,
stddev REAL NOT NULL,
variance REAL NOT NULL,
histogram TEXT NOT NULL,
UNIQUE (iid)
);
}
$db close
Debug.bookflow/db {}
return [$type create %AUTO% $path]
}
# ### ### ### ######### ######### #########
constructor {path} {
Debug.bookflow/db { @ $path}
set mydb ${selfns}::DB
sqlite3 $mydb $path
Debug.bookflow/db {}
return
}
# ### ### ### ######### ######### #########
method books {} {
Debug.bookflow/db {}
return [$mydb eval { SELECT name FROM book }]
}
method {book extend} {book file mtime} {
Debug.bookflow/db {}
$mydb transaction {
# Locate the named book, and retrieve its id.
set bid [lindex [$mydb eval {
SELECT bid FROM book WHERE name = $book
}] 0]
# Get the last (= highest) ordering number for images in this book.
set ord [lindex [$mydb eval {
SELECT MAX (ord) FROM image WHERE bid = $bid
}] 0]
# The new images is added behind the last-highest images.
if {$ord eq {}} { set ord -1 }
incr ord
Debug.bookflow/db { /book $bid, @$ord}
# And enter the image into the database.
$mydb eval {
INSERT INTO image
VALUES (NULL, $file, $bid, $ord, $mtime)
}
}
Debug.bookflow/db {/}
return $ord
}
method {book holding} {file} {
Debug.bookflow/db {}
return [lindex [$mydb eval {
SELECT name FROM book
WHERE bid = (SELECT bid FROM image
WHERE path = $file)
}] 0]
}
method {book files} {book} {
Debug.bookflow/db {}
return [$mydb eval {
SELECT path, ord
FROM image
WHERE bid = (SELECT bid FROM book
WHERE name = $book)
}]
}
# NOTE: Moves leave gaps in the serial numbering of the origin
# books. While this doesn't affect the ordering in itself, other
# parts using the serial number may assume that there are no
# gaps. Example: The book manager widget uses the serial numbers
# for the x-axis of the brightness chart, and gaps will show up
# there. Consider some mechanism to remove/prevent such gaps.
method {book move} {book file} {
Debug.bookflow/db {}
$mydb transaction {
# Locate the named book, and retrieve its id.
set bid [lindex [$mydb eval {
SELECT bid FROM book WHERE name = $book
}] 0]
# Get the last (= highest) ordering number for images in this book.
set ord [lindex [$mydb eval {
SELECT MAX (ord) FROM image WHERE bid = $bid
}] 0]
# The new images is added behind the last-highest images.
if {$ord eq {}} { set ord -1 }
incr ord
Debug.bookflow/db { /book $bid, @$ord}
# And change the image in the database.
$mydb eval {
UPDATE image
SET bid = $bid,
ord = $ord
WHERE path = $file
}
}
Debug.bookflow/db {/}
return $ord
}
method files {} {
Debug.bookflow/db {}
return [$mydb eval { SELECT path FROM image }]
}
method {file mtime} {file} {
Debug.bookflow/db {}
return [$mydb eval { SELECT mtime FROM image WHERE path = $file }]
}
method {statistics set} {file min max mean middle median stddev variance histogram} {
Debug.bookflow/db {}
$mydb transaction {
# Locate the id of the file.
set iid [lindex [$mydb eval {
SELECT iid
FROM image
WHERE path = $file
}] 0]
# And enter the value into the database.
$mydb eval {
INSERT INTO statistics
VALUES ($iid, $min, $max, $mean, $middle, $median, $stddev, $variance, $histogram)
}
}
Debug.bookflow/db {/}
return
}
method {statistics unset} {file} {
Debug.bookflow/db {}
$mydb transaction {
# Remove the statistics value.
$mydb eval {
DELETE FROM statistics
WHERE iid IN (SELECT iid FROM image WHERE path = $file)
}
}
Debug.bookflow/db {/}
return
}
method {statistics get} {file} {
Debug.bookflow/db {}
$mydb transaction {
set res [$mydb eval {
SELECT min, max, mean, middle, median, stddev, variance, histogram
FROM statistics
WHERE iid IN (SELECT iid FROM image WHERE path = $file)
}]
}
#lassign $res min max mean middle median stddev variance histogram
Debug.bookflow/db {= $res /}
return $res
}
### Accessors and manipulators
# ### ### ### ######### ######### #########
##
variable mydb ; # Handle of the sqlite database. Object command.
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::db 0.1
return
|
Added attic/lib/db/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::db 0.1 [list source [file join $dir db.tcl]]
|
Added attic/lib/debug/debug.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# Debug - a debug narrative logger -- Colin McCormack / Wub server utilities
#
# Debugging areas of interest are represented by 'tokens' which have
# independantly settable levels of interest (an integer, higher is more detailed)
#
# Debug narrative is provided as a tcl script whose value is [subst]ed in the
# caller's scope if and only if the current level of interest matches or exceeds
# the Debug call's level of detail. This is useful, as one can place arbitrarily
# complex narrative in code without unnecessarily evaluating it.
#
# TODO: potentially different streams for different areas of interest.
# (currently only stderr is used. there is some complexity in efficient
# cross-threaded streams.)
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
namespace eval ::debug {}
# ### ### ### ######### ######### #########
## API & Implementation
proc ::debug::noop {args} {}
proc ::debug::debug {tag message {level 1}} {
variable detail
if {$detail($tag) < $level} {
#puts stderr "$tag @@@ $detail($tag) >= $level"
return
}
variable prefix
variable fds
set fd $fds($tag)
# Integrate global and tag prefixes with the user message.
set themessage ""
if {[info exists prefix(::)]} { append themessage $prefix(::) }
if {[info exists prefix($tag)]} { append themessage $prefix($tag) }
append themessage $message
# Resolve variables references and command invokations embedded
# into the message with plain text.
set code [catch {
uplevel 1 [list ::subst -nobackslashes $themessage]
} result eo]
if {$code} {
if {[catch {
set x [info level -1]
}]} { set x GLOBAL }
puts -nonewline $fd @@[string map {\n \\n \r \\r} "(DebugError from $tag [if {[string length $x] < 1000} {set x} else {set x "[string range $x 0 200]...[string range $x end-200 end]"}] ($eo)):"]
} else {
if {[string length $result] > 4096} {
set result "[string range $result 0 2048]...(truncated) ... [string range $result end-2048 end]"
}
puts $fd "$tag | [join [split $result \n] "\n$tag | "]"
}
return
}
# names - return names of debug tags
proc ::debug::names {} {
variable detail
return [lsort [array names detail]]
}
proc ::debug::2array {} {
variable detail
set result {}
foreach n [lsort [array names detail]] {
if {[interp alias {} Debug.$n] ne "::Debug::noop"} {
lappend result $n $detail($n)
} else {
lappend result $n -$detail($n)
}
}
return $result
}
# level - set level and fd for tag
proc ::debug::level {tag {level ""} {fd stderr}} {
variable detail
if {$level ne ""} {
set detail($tag) $level
}
if {![info exists detail($tag)]} {
set detail($tag) 1
}
variable fds
set fds($tag) $fd
return $detail($tag)
}
# set prefix to use for tag.
# The global (tag-independent) prefix is adressed through tag == '::'`.
# This works because colon (:) is an illegal character for regular tags.
proc ::debug::prefix {tag {theprefix {}}} {
variable prefix
set prefix($tag) $theprefix
return
}
# turn on debugging for tag
proc ::debug::on {tag {level ""} {fd stderr}} {
variable active
set active($tag) 1
level $tag $level $fd
interp alias {} Debug.$tag {} ::debug::debug $tag
return
}
# turn off debugging for tag
proc ::debug::off {tag {level ""} {fd stderr}} {
variable active
set active($tag) 1
level $tag $level $fd
interp alias {} Debug.$tag {} ::debug::noop
return
}
proc ::debug::setting {args} {
if {[llength $args] == 1} {
set args [lindex $args 0]
}
set fd stderr
if {[llength $args]%2} {
set fd [lindex $args end]
set args [lrange $args 0 end-1]
}
foreach {tag level} $args {
if {$level > 0} {
level $tag $level $fd
interp alias {} Debug.$tag {} ::Debug::debug $tag
} else {
level $tag [expr {-$level}] $fd
interp alias {} Debug.$tag {} ::Debug::noop
}
}
return
}
namespace eval debug {
variable detail ; # map: TAG -> level of interest
variable prefix ; # map: TAG -> message prefix to use
variable fds ; # map: TAG -> handle of open channel to log to.
# Notes:
# The tag '::' is reserved, prefix() uses it to store the global message prefix.
namespace export -clear *
namespace ensemble create -subcommands {}
}
# ### ### ### ######### ######### #########
## Communication setup for concurrent tasks.
## Thread based.
namespace eval ::debug::thread {}
proc ::debug::thread::link {main} {
variable ::debug::detail
variable ::debug::prefix
# Import main's status.
array set detail [thread::send $main {array get ::debug::detail}]
array set prefix [thread::send $main {array get ::debug::prefix}]
array set active [thread::send $main {array get ::debug::active}]
# We do not import the channels. Cannot share them among threads,
# only transfer.
# Replicate (in)active status of the tags.
foreach {t a} [array get active] {
if {$a} {
interp alias {} Debug.$t {} ::debug::debug $t
} else {
interp alias {} Debug.$t {} ::debug::noop
}
}
return
}
# ### ### ### ######### ######### #########
## Look for the magic of package task, and if found import the main's
## status to configure our settings.
::apply {{} {
if {![info exists ::task::type]} return
::debug::${::task::type}::link $::task::main
return
}}
# ### ### ### ######### ######### #########
## Ready
package provide debug 1.0
return
|
Added attic/lib/debug/debug_snit.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
## Utility command for use as debug prefix command to un-mangle snit
## method calls.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
namespace eval ::debug::snit {}
# ### ### ### ######### ######### #########
## API & Implementation
proc ::debug::snit::call {} {
# For snit (type)methods, rework the command line to be more
# legible and in line with what the user would expect. To this end
# we pull the primary command out of the arguments, be it type or
# object, massage the command to match the original (type)method
# name, then resort and expand the words to match the call before
# snit got its claws into it.
set a [lassign [info level -1] m]
regsub {.*Snit_} $m {} m
switch -glob $m {
htypemethod* {
# primary = type, a = type
set a [lassign $a primary]
set m [string map {_ { }} [string range $m 11 end]]
}
typemethod* {
# primary = type, a = type
set a [lassign $a primary]
set m [string range $m 10 end]
}
hmethod* {
# primary = self, a = type selfns self win ...
set a [lassign $a _ _ primary _]
set m [string map {_ { }} [string range $m 7 end]]
}
method* {
# primary = self, a = type selfns self win ...
set a [lassign $a _ _ primary _]
set m [string range $m 6 end]
}
destructor -
constructor {
# primary = self, a = type selfns self win ...
set a [lassign $a _ _ primary _]
}
typeconstructor {
return [list {*}$a $m]
}
default {
# Unknown
return [list $m {*}$a]
}
}
return [list $primary {*}$m {*}$a]
}
# ### ######### ###########################
## Ready for use
package provide debug::snit 0.1
return
|
Added attic/lib/debug/pkgIndex.tcl.
|
|
>
>
>
|
1
2
3
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded debug 1.0 [list source [file join $dir debug.tcl]]
package ifneeded debug::snit 0.1 [list source [file join $dir debug_snit.tcl]]
|
Added attic/lib/error/error.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Error display. Watches the scoreboard for error messages and posts
# them as tk_message. Pseudo-task using events, i.e. CPS.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require scoreboard
namespace eval ::bookflow::error {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/error
#debug on bookflow/error
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::error {} {
Debug.bookflow/error {Bookflow::Error Watch}
scoreboard take {PROJECT ERROR *} [namespace code error::Post]
Debug.bookflow/error {/}
return
}
# ### ### ### ######### ######### #########
## Internals
proc ::bookflow::error::Post {tuple} {
tk_messageBox -type ok -icon error -parent . -title Error \
-message [lindex $tuple 2]
# Return to watching the scoreboard, there may be more messages.
after idle ::bookflow::error
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow {
namespace export error
namespace ensemble create
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::error 0.1
return
|
Added attic/lib/error/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::error 0.1 [list source [file join $dir error.tcl]]
|
Added attic/lib/grey/greyscale.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task. Continuous.
# Creating and invalidating greyscales of page images.
# A producer in terms of "doc/interaction_pci.txt"
#
# Generated greyscales are cached in the directory ".bookflow/grey" of
# the project directory.
# Limits itself to no more than four actual threads in flight,
# i.e. performing image scaling. The scaling tasks do not exit on
# completion, but wait for more operations to perform. Communication
# and coordination is done through the scoreboard. As usual.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
package require scoreboard
namespace eval ::bookflow::greyscale {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/greyscale
#debug on bookflow/greyscale
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::greyscale {} {
Debug.bookflow/greyscale {Bookflow::Greyscale Watch}
scoreboard wpeek {AT *} [namespace code greyscale::BEGIN]
Debug.bookflow/greyscale {/}
return
}
proc ::bookflow::greyscale::BEGIN {tuple} {
# tuple = (AT project)
Debug.bookflow/greyscale {Bookflow::Greyscale BEGIN <$tuple>}
lassign $tuple _ project
# Monitor for greyscale invalidation
# doc/interaction_pci.txt (1)
scoreboard take {!GREYSCALE *} [namespace code [list INVALIDATE $project]]
# Launch the tasks doing the actual conversion.
variable max
for {set i 0} {$i < $max} {incr i} {
task launch [list ::apply {{} {
package require bookflow::greyscale
bookflow::greyscale::CONVERT
}}]
}
# Monitor for greyscale creation requests.
# doc/interaction_pci.txt (2)
scoreboard bind missing {GREYSCALE *} [namespace code [list MAKE $project]]
Debug.bookflow/greyscale {Bookflow::Greyscale BEGIN/}
return
}
# ### ### ### ######### ######### #########
## Internals. Helper encapsulation directory structure.
proc ::bookflow::greyscale::GreyFullPath {project path} {
return $project/[GreyPath $path]
}
proc ::bookflow::greyscale::GreyPath {path} {
return .bookflow/grey/[file rootname $path].pgm
}
# ### ### ### ######### ######### #########
## Internals. Greyscale invalidation. See doc/interaction_pci.txt (1).
proc ::bookflow::greyscale::INVALIDATE {project tuple} {
# tuple = (!GREYSCALE path)
lassign $tuple _ path
Debug.bookflow/greyscale {Bookflow::Greyscale INVALIDATE $path}
scoreboard takeall [list GREYSCALE $path *] [namespace code [list RETRACT $project $path]]
Debug.bookflow/greyscale {Bookflow::Greyscale INVALIDATE/}
return
}
proc ::bookflow::greyscale::RETRACT {project path tuples} {
Debug.bookflow/greyscale {Bookflow::Greyscale RETRACT $path}
file delete [GreyFullPath $project $path]
# Look for more invalidation requests
scoreboard take {!GREYSCALE *} [namespace code [list INVALIDATE $project]]
Debug.bookflow/greyscale {Bookflow::Greyscale RETRACT/}
return
}
# ### ### ### ######### ######### #########
## Internals. Greyscale creation. See doc/interaction_pci.txt (2).
proc ::bookflow::greyscale::MAKE {project pattern} {
# pattern = (GREYSCALE path *)
lassign $pattern _ path
Debug.bookflow/greyscale {Bookflow::Greyscale MAKE $path}
set greyfull [GreyFullPath $project $path]
set grey [GreyPath $path]
if {[file exists $greyfull]} {
# Greyscale already exists in the filesystem cache, simply
# make it available.
scoreboard put [list GREYSCALE $path $grey]
} else {
# Greyscale not known. Put in a request for the converter
# tasks to generate it. This will also put the proper result
# into the scoreboard on completion.
set r [list GREYSCALE $path $grey]
scoreboard put [list GREYCONVERT $project/$path $greyfull $r]
}
Debug.bookflow/greyscale {Bookflow::Greyscale MAKE/}
return
}
# ### ### ### ######### ######### #########
## Internals. Implementation of the resizing tasks.
proc ::bookflow::greyscale::CONVERT {} {
package require debug
Debug.bookflow/greyscale {Bookflow::Greyscale CONVERT}
# Requisites for the task
package require bookflow::greyscale
package require scoreboard
package require crimp ; wm withdraw .
package require img::jpeg
# Start waiting for requests.
READY
Debug.bookflow/greyscale {Bookflow::Greyscale CONVERT/}
return
}
proc ::bookflow::greyscale::READY {} {
# Wait for more requests.
scoreboard take {GREYCONVERT *} [namespace code GCONV]
return
}
proc ::bookflow::greyscale::GCONV {tuple} {
# tuple = (GREYCONVERT path dstpath result)
# result = (GREYSCALE path dstpath)
# Decode request
lassign $tuple _ path dst result
Debug.bookflow/greyscale {Bookflow::Greyscale GCONV $path $dst}
# Perform the conversion, writing pgm, using crimp internally.
file mkdir [file dirname $dst]
set photo [image create photo -file $path]
crimp write 2file pgm-raw $dst [crimp convert 2grey8 [crimp read tk $photo]]
image delete $photo
# Push result
scoreboard put $result
# Wait for more requests.
READY
Debug.bookflow/greyscale {Bookflow::Greyscale GCONV $path = $dst /}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow::greyscale {
# Number of parallel conversion tasks.
variable max 4
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::greyscale 0.1
return
|
Added attic/lib/grey/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::greyscale 0.1 [list source [file join $dir greyscale.tcl]]
|
Added attic/lib/imgpage/imgpage.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Widget showing a single or double page spread, i.e. one or two
# images. Not specific to bookflow.
# ### ### ### ######### ######### #########
## Requisites
package require Tk 8.5
package require debug
package require debug::snit
package require snit
package require tooltip
package require widget::scrolledwindow
debug prefix img/page {[::debug::snit::call] }
debug off img/page
#debug on img/page
# ### ### ### ######### ######### #########
##
snit::widgetadaptor img::page {
# ### ### ### ######### ######### #########
##
delegate option -borderwidth to hull
delegate option -relief to hull
# ### ### ### ######### ######### #########
##
constructor {args} {
Debug.img/page {}
installhull using ttk::frame
$self Widgets
$self Layout
$self Bindings
$self configurelist $args
return
}
method {odd image} {image} { $self Image odd $image ; return }
method {even image} {image} { $self Image even $image ; return }
method {odd text} {text} { $self Text odd $text ; return }
method {even text} {text} { $self Text even $text ; return }
# ### ### ### ######### ######### #########
method Image {frame image} {
Debug.bookw {}
set mystate($frame,photo) [expr {$image ne {}}]
set w [image width $image]
set h [image height $image]
if {$h > $w} { set max $h } else { set max $w }
incr max 20
$win.$frame.plate configure -scrollregion [list 0 0 $max $max]
$win.$frame.plate itemconfigure PHOTO -image $image
$win.$frame.plate coords PHOTO [expr {$w/2 + 10}] [expr {$h/2 + 10}]
if {$image eq {}} {
$win.$frame.plate raise TEXT
} else {
$win.$frame.plate raise PHOTO
}
$self Relayout
Debug.bookw {/}
return
}
method Text {frame text} {
Debug.bookw {}
set mystate($frame,text) [expr {$text ne {}}]
$win.$frame.plate itemconfigure TEXT -text $text
if {$text eq {}} {
$win.$frame.plate raise PHOTO
} else {
$win.$frame.plate raise TEXT
}
$self Relayout
Debug.bookw {/}
return
}
method Relayout {} {
Debug.bookw {}
set odd [expr {$mystate(odd,photo) || $mystate(odd,text)}]
set even [expr {$mystate(even,photo) || $mystate(even,text)}]
if {$odd && $even} {
pack $win.odd -in $win -side left -fill both -expand 1
pack $win.even -in $win -side right -fill both -expand 1
} elseif {$odd} {
pack forget $win.even
pack $win.odd -in $win -side top -fill both -expand 1
} elseif {$even} {
pack forget $win.odd
pack $win.even -in $win -side top -fill both -expand 1
} else {
pack forget $win.odd
pack forget $win.even
}
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
method Context {x y} {
Debug.img/page {}
event generate $win <<Context>> -data [list $x $y $myimage]
return
}
# ### ### ### ######### ######### #########
##
method Widgets {} {
foreach frame {
odd
even
} {
widget::scrolledwindow $win.$frame
canvas $win.$frame.plate \
-scrollregion {0 0 1024 1024} \
-borderwidth 2 -relief sunken
$win.$frame setwidget $win.$frame.plate
$win.$frame.plate create image 10 10 -tags PHOTO
$win.$frame.plate create text 10 10 -tags TEXT -anchor nw -fill red -font {-size -16} -text "Undefined"
}
return
}
method Layout {} {
# Layout is dynamic, as images are assigned to the sides, odd
# packed left, even packed right, both expanding.
return
}
method Bindings {} {
bind $win.odd.plate <3> [mymethod Context %X %Y]
bind $win.even.plate <3> [mymethod Context %X %Y]
return
}
# ### ### ### ######### ######### #########
## State
variable mystate -array {
odd,photo 0
odd,text 0
even,photo 0
even,text 0
}
# ### ### ### ######### ######### #########
## Configuration
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide img::page 0.1
|
Added attic/lib/imgpage/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded img::page 0.1 [list source [file join $dir imgpage.tcl]]
|
Added attic/lib/imgstrip/imgstrip.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Widget showing a horizontal/vertical strip of images.
# Not specific to bookflow.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require widget::scrolledwindow
package require treectrl
package require snit
package require debug::snit
package require debug
package require syscolor
debug off img/strip
#debug on img/strip
debug prefix img/strip {[::debug::snit::call] }
# ### ### ### ######### ######### #########
##
snit::widgetadaptor ::img::strip {
# ### ### ### ######### ######### #########
##
option -orientation \
-default horizontal \
-configuremethod C-orient \
-type {snit::enum -values {horizontal vertical}}
# ### ### ### ######### ######### #########
##
delegate method * to mytree
delegate option * to mytree
delegate option -borderwidth to hull
delegate option -relief to hull
# ### ### ### ######### ######### #########
##
constructor {args} {
Debug.img/strip {}
installhull using widget::scrolledwindow -borderwidth 1 -relief sunken
$self Widgets
$self Layout
$self Bindings
$self S-orient horizontal
$self STYLE
$self configurelist $args
return
}
# Add an empty image to the widget. Displayed, but without text or
# image until such are configured. Returns a token to address the
# item with.
method new {} {
Debug.img/strip {}
set newitem [$mytree item create]
$mytree item lastchild 0 $newitem
$mytree item configure $newitem -button 0
$mytree item configure $newitem -visible 1
$mytree item style set $newitem 0 STYLE
$mytree collapse $newitem
$self Resort
$self DetermineHeight
$self DetermineWidth
Debug.img/strip {/}
return $newitem
}
method drop {token} {
Debug.img/strip {}
$mytree item delete $token
# Note: Resorting not needed, the other images are staying in
# their proper order.
Debug.img/strip {/}
return
}
method itemconfigure {token args} {
foreach {option value} $args {
$self ItemConfigure $option $token $value
}
return
}
method {ItemConfigure -message} {token string} {
Debug.img/strip {}
$mytree item element configure $token 0 eText -text $string
Debug.img/strip {/}
return
}
method {ItemConfigure -label} {token string} {
Debug.img/strip {}
$mytree item element configure $token 0 eLabel -text $string
Debug.img/strip {/}
return
}
method {ItemConfigure -order} {token string} {
Debug.img/strip {}
$mytree item element configure $token 0 eSerial -text $string
$self Resort
Debug.img/strip {/}
return
}
method {ItemConfigure -image} {token photo} {
Debug.img/strip {}
$mytree item element configure $token 0 eImage -image $photo
Debug.img/strip {/}
return
}
method itemcget {token option} {
return [$self ItemCget $option $token]
}
method {ItemCget -message} {token} {
Debug.img/strip {}
if {[catch {
set res [$mytree item element cget $token 0 eText -text]
}]} { set res {} }
Debug.img/strip {= $res /}
return $res
}
method {ItemCget -label} {token} {
Debug.img/strip {}
if {[catch {
set res [$mytree item element cget $token 0 eLabel -text]
}]} { set res {} }
Debug.img/strip {= $res /}
return $res
}
method {ItemCget -order} {token} {
Debug.img/strip {}
if {[catch {
set res [$mytree item element cget $token 0 eSerial -text]
}]} { set res {} }
Debug.img/strip {= $res /}
return $res
}
method {ItemCget -image} {token} {
Debug.img/strip {}
if {[catch {
set res [$mytree item element cget $token 0 eImage -image]
}]} { set res {} }
Debug.img/strip {= $res /}
return $res
}
method {selection set} {token} {
$mytree selection clear
$mytree selection add $token
$mytree activate $token
return
}
# ### ### ### ######### ######### #########
## Internals
method Widgets {} {
Debug.img/strip {}
install mytree using treectrl $win.tree \
-highlightthickness 0 \
-borderwidth 0 \
-showheader 1 \
-xscrollincrement 20
$mytree debug configure \
-enable no \
-display no \
-erasecolor pink \
-displaydelay 30
$mytree configure \
-showroot no \
-showbuttons no \
-showlines no \
-selectmode single \
-showheader no \
-scrollmargin 16 \
-xscrolldelay {500 50} \
-yscrolldelay {500 50}
return
}
method Layout {} {
Debug.img/strip {}
$hull setwidget $mytree
return
}
method Bindings {} {
Debug.img/strip {}
# Disable "scan" bindings on windows.
if {$::tcl_platform(platform) eq "windows"} {
bind $mytree <Control-ButtonPress-3> { }
}
bindtags $mytree [list $mytree TreeCtrl [winfo toplevel $mytree] all]
$mytree notify bind $mytree <ActiveItem> [mymethod ChangeActiveItem %p %c]
$mytree notify bind $mytree <Selection> [mymethod Selection]
bind $mytree <Double-1> [mymethod Action %x %y]
bind $mytree <3> [mymethod Context %X %Y %x %y]
bind $win <FocusIn> [mymethod Focus]
$mytree column create
return
}
method STYLE {} {
Debug.img/strip {}
# Style for the items used for the display of images.
#
# Elements
# ------------------------------------------------------------------------
# eImage : The image to show.
# eText : Transient text, feedback (like the status of image ops, etc.)
# eLabel : Textual label for the image.
# eFrame : Square rectangle around the image.
# eShadow : A small drop shadow around eFrame.
# eSerial : INVISIBLE text whose contents determine display order. I.e.
# this one is used to sort the items.
# ------------------------------------------------------------------------
$mytree element create eImage image -image {} -width $oursize -height $oursize
$mytree element create eText text -text {} -fill $ourtextfillcolor -justify center
$mytree element create eLabel text -text {} -fill $ourtextfillcolor -justify center
$mytree element create eFrame rect -outlinewidth 1 -fill $ourfillcolor -outline $ouroutlinecolor
$mytree element create eShadow rect -outlinewidth 2 -fill $ourfillcolor -outline gray \
-open wn -showfocus 1
$mytree element create eSerial text -text {}
$mytree style create STYLE -orient vertical
$mytree style elements STYLE {eShadow eLabel eFrame eImage eText eSerial}
$mytree style layout STYLE eLabel -ipady {2 0} -expand we
$mytree style layout STYLE eFrame -union { eImage eText }
$mytree style layout STYLE eImage -ipady $ourgap -ipadx $ourgap -expand swen
$mytree style layout STYLE eShadow -padx {1 2} -pady {1 2} -iexpand xy -detach yes
#$mytree style layout STYLE eLabel -visible 1
#$mytree style layout STYLE eImage -visible 1
$mytree style layout STYLE eSerial -visible 0
TreeCtrl::SetSensitive $mytree { {0 STYLE eShadow eLabel eFrame eImage eText} }
TreeCtrl::SetEditable $mytree { {0 STYLE} }
TreeCtrl::SetDragImage $mytree { {0 STYLE} }
bindtags $mytree \
[list \
$mytree \
TreeCtrlFileList \
TreeCtrl \
[winfo toplevel $mytree] \
all]
return
}
method Resort {} {
# Regenerate the display order of items.
# We sort them by the third text element, the invisible "eSerial".
$mytree item sort 0 -dict -element eSerial
return
}
# ### ### ### ######### ######### #########
##
method ChangeActiveItem {previous current} {
Debug.img/strip {}
$mytree see $current
return
}
method Focus {} {
Debug.img/strip {==> $mytree}
focus $mytree
return
}
method Context {x y wx wy} {
set idata [$mytree identify $wx $wy]
Debug.img/strip {[list ==> $idata]}
lassign $idata type id
event generate $win <<Context>> -data [list $x $y $id]
return
}
method Action {x y} {
set idata [$mytree identify $x $y]
Debug.img/strip {[list ==> $idata]}
lassign $idata type id
if {$type ne "item"} return
event generate $win <<Action>> -data $id
return
}
method Selection {} {
Debug.img/strip {}
event generate $win <<SelectionChanged>> \
-data [$mytree selection get]
return
}
# ### ### ### ######### ######### #########
method C-orient {o value} {
if {$options($o) eq $value} return
set options($o) $value
$self S-orient $value
return
}
method S-orient {value} {
switch -exact -- $value {
horizontal {
# Tree is horizontal, no wrapping is done.
# Each item is as high as myheight (to be determined
# after first item added).
# Indirectly derived from 'oursize', the w/h given to
# the eImage element.
# FUTURE: Pull this out of the actual image configured
# for the first item (max of all maybe ?)
$mytree configure -orient horizontal -wrap {}
$hull configure -scrollbar horizontal -auto horizontal
$self DetermineHeight
}
vertical {
# Tree is vertical, no wrapping is done.
# Each item is as wide as mywidth (to be determined
# after first item added).
# Indirectly derived from 'oursize', the w/h given to
# the eImage element.
# FUTURE: Pull this out of the actual image configured
# for the first item (max of all maybe ?)
$mytree configure -orient vertical -wrap {}
$hull configure -scrollbar vertical -auto vertical
$self DetermineWidth
}
}
return
}
method DetermineHeight {} {
if {![info exists options(-orientation)]} return
if {$options(-orientation) ne "horizontal"} return
if {$myheight eq {}} {
set items [$mytree item children 0]
if {![llength $items]} return
lassign [$mytree item bbox [lindex $items 0]] _ _ _ myheight
incr myheight 40
}
$mytree configure -height $myheight -width 0
return
}
method DetermineWidth {} {
if {![info exists options(-orientation)]} return
if {$options(-orientation) ne "vertical"} return
if {$mywidth eq {}} {
set items [$mytree item children 0]
if {![llength $items]} return
lassign [$mytree item bbox [lindex $items 0]] _ _ mywidth _
#incr mywidth 40
}
#$mytree column configure 0 -width $mywidth
$mytree configure -width $mywidth -height 0
return
}
# ### ### ### ######### ######### #########
## State
variable mywidth {} ; # Strip width, derived from first image
variable myheight {} ; # Strip height, derived from first image
component mytree
# ### ### ### ######### ######### #########
## Configuration
## TODO :: Make these configurable (on widget creation only).
typevariable oursize 160 ; # Maximal size of the images to expect (160x120 / 120x160)
typevariable ourgap 4 ; # Size of the gap to put between image and text.
typevariable ourselectcolor \#ffdc5a
typevariable ouroutlinecolor \#827878
typevariable ourfillcolor
typevariable ourtextfillcolor
typeconstructor {
set ourtextfillcolor [list [syscolor::highlightText] {selected focus}]
set ourfillcolor [list \
[syscolor::highlight] {selected focus} \
gray {selected !focus}]
set ourtextfillcolor [list [syscolor::highlightText] {selected focus}]
set ourfillcolor [list \
\#ff8800 {selected focus} \
gray {selected !focus}]
}
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide img::strip 0.1
|
Added attic/lib/imgstrip/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded img::strip 0.1 [list source [file join $dir imgstrip.tcl]]
|
Added attic/lib/iq/iq.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Issue Queue. Use it to limit the rate of issuing requests for data
# like thumbnails etc. Instead of directly issuing the query patterns
# to the scoreboard issue them to an instance of iq and the queue will
# issue them so that only a fixed (but configurable) number of queries
# have outstanding results.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require snit
package require scoreboard
package require debug
package require debug::snit
package require struct::queue
# ### ### ### ######### ######### #########
## Tracing
debug prefix iq {[::debug::snit::call] }
debug off iq
#debug on iq
# ### ### ### ######### ######### #########
## Implementation
snit::type ::iq {
# ### ### ### ######### ######### #########
##
option -emptycmd \
-default {}
# ### ### ### ######### ######### #########
##
constructor {limit args} {
Debug.iq {}
set mylimit $limit
set myqueue [struct::queue ${selfns}::Q]
$self configurelist $args
Debug.iq {/}
return
}
method put {pattern cmd} {
Debug.iq {}
if {$myflight >= $mylimit} {
$myqueue put [list $pattern $cmd]
Debug.iq {/}
return
}
$self Dispatch $pattern $cmd
Debug.iq {/}
return
}
# ### ### ### ######### ######### #########
##
method Dispatch {pattern cmd} {
Debug.iq {}
scoreboard wpeek $pattern [mymethod Have $cmd]
incr myflight
Debug.iq {/}
return
}
method Have {cmd tuple} {
Debug.iq {}
incr myflight -1
if {($myflight < $mylimit) && [$myqueue size]} {
lassign [$myqueue get] pattern newcmd
$self Dispatch $pattern $newcmd
$self NotifyEmpty
}
uplevel #0 [list {*}$cmd $tuple]
Debug.iq {/}
return
}
# ### ### ### ######### ######### #########
method NotifyEmpty {args} {
if {![$myqueue size]} return
if {![llength $options(-emptycmd)]} return
after idle [list after 0 [list {*}$options(-emptycmd) $self]]
return
}
# ### ### ### ######### ######### #########
##
variable myflight 0 ; # Number of requests waiting for results
variable mylimit 0 ; # Maximum number of requests we are allowed
# to keep in flight.
variable myqueue {} ; # Queue of requests waiting to be issued.
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide iq 0.1
return
|
Added attic/lib/iq/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded iq 0.1 [list source [file join $dir iq.tcl]]
|
Added attic/lib/log/log.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
# Log - A narrative logger, not for debugging by the developer, but
# end-user reporting of system activity.
# Derived from the debug logger.
#
# Logging areas of interest are represented by 'tokens' which have
# independantly settable levels of interest (an integer, higher is more detailed)
#
# Log narrative is provided as a tcl script whose value is [subst]ed in the
# caller's scope if and only if the current level of interest matches or exceeds
# the Log call's level of detail. This is useful, as one can place arbitrarily
# complex narrative in code without unnecessarily evaluating it.
#
# TODO: potentially different streams for different areas of interest.
# (currently only stderr is used. there is some complexity in efficient
# cross-threaded streams.)
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require debug
namespace eval ::log {}
debug off log
# ### ### ### ######### ######### #########
## API & Implementation
proc ::log::noop {args} {}
proc ::log::log {tag message {level 1}} {
variable detail
if {$detail($tag) < $level} {
#puts stderr "$tag @@@ $detail($tag) >= $level"
return
}
variable prefix
variable fds
# Determine the log command, based on tag, with fallback to a
# global setting.`
if {[catch {
set fd $fds($tag)
}]} {
set fd $fds(::)
}
# Integrate global and tag prefixes with the user message.
set themessage ""
if {[info exists prefix(::)]} { append themessage $prefix(::) }
if {[info exists prefix($tag)]} { append themessage $prefix($tag) }
append themessage $message
# Resolve variables references and command invokations embedded
# into the message with plain text.
set code [catch {
uplevel 1 [list ::subst -nobackslashes $themessage]
} result eo]
if {$code} {
return -code error $result
#set x [info level -1]
#set x [expr {[string length $x] < 1000 ? $x : "[string range $x 0 200]...[string range $x end-200 end]"}]
#{*}$fd puts* @@[string map {\n \\n \r \\r} "(LogError from $tag $x ($eo)):"]
} {
if {[string length $result] > 4096} {
set result "[string range $result 0 2048]...(truncated) ... [string range $result end-2048 end]"
}
set head $tag
set blank [regsub -all . $tag { }]
foreach line [split $result \n] {
#{*}$fd puts* $head
#{*}$fd puts* { | }
{*}$fd puts $line
set head $blank
}
}
return
}
# names - return names of log tags
proc ::log::names {} {
variable detail
return [lsort [array names detail]]
}
proc ::log::2array {} {
variable detail
set result {}
foreach n [lsort [array names detail]] {
if {[interp alias {} Log.$n] ne "::Log::noop"} {
lappend result $n $detail($n)
} else {
lappend result $n -$detail($n)
}
}
return $result
}
# level - set level and log command for tag
proc ::log::level {tag {level ""} {fd {}}} {
variable detail
if {$level ne ""} {
set detail($tag) $level
}
if {![info exists detail($tag)]} {
set detail($tag) 1
}
variable fds
if {$fd ne {}} {
set fds($tag) $fd
}
return $detail($tag)
}
# set prefix to use for tag.
# The global (tag-independent) prefix is adressed through tag == '::'`.
# This works because colon (:) is an illegal character for regular tags.
proc ::log::prefix {tag {theprefix {}}} {
variable prefix
set prefix($tag) $theprefix
return
}
# turn on logging for tag
proc ::log::on {tag {level ""} {fd {}}} {
variable active
set active($tag) 1
level $tag $level $fd
interp alias {} Log.$tag {} ::log::log $tag
return
}
# turn off logging for tag
proc ::log::off {tag {level ""} {fd {}}} {
variable active
set active($tag) 0
level $tag $level $fd
interp alias {} Log.$tag {} ::log::noop
return
}
proc ::log::setting {args} {
if {[llength $args] == 1} {
set args [lindex $args 0]
}
set fd {}
if {[llength $args]%2} {
set fd [lindex $args end]
set args [lrange $args 0 end-1]
}
foreach {tag level} $args {
if {$level > 0} {
level $tag $level $fd
interp alias {} Log.$tag {} ::Log::log $tag
} else {
level $tag [expr {-$level}] $fd
interp alias {} Log.$tag {} ::Log::noop
}
}
return
}
# ### ### ### ######### ######### #########
## Communication setup for concurrent tasks.
## Thread based.
namespace eval ::log::thread {}
proc ::log::thread::link {main} {
variable ::log::detail
variable ::log::prefix
variable ::log::fds
Debug.log { Setting up log for $main}
# Import main's status.
array set detail [thread::send $main {array get ::log::detail}]
array set prefix [thread::send $main {array get ::log::prefix}]
array set active [thread::send $main {array get ::log::active}]
# We do not import any custom write commands.
# Any writing goes through the global setting, which is
# reconfigured to perform the necessary inter-thread
# communication.
# Replicate (in)active status of the tags.
foreach {t a} [array get active] {
if {$a} {
interp alias {} Log.$t {} ::log::log $t
} else {
interp alias {} Log.$t {} ::log::noop
}
}
set fds(::) [list ::log::thread::ToMain $main]
return
}
proc ::log::thread::ToMain {main cmd text} {
upvar 1 tag tag
thread::send -async $main \
[list ::log::thread::FromTask $tag $cmd $text]
return
}
proc ::log::thread::FromTask {tag cmd text} {
# This is a variant of log::log without all the substitutions. It
# determines the actual write command per the tag and invokes it
# with the specifiec method and text.
# It is the receiver of messages coming from concurrently running
# tasks.
variable ::log::fds
if {[catch {
set fd $fds($tag)
}]} {
set fd $fds(::)
}
{*}$fd $cmd $text
return
}
# ### ### ### ######### ######### #########
## Standard log writer command
namespace eval ::log::Write {
namespace export puts puts*
namespace ensemble create
}
proc ::log::Write::puts {text} {
puts stderr $text
return
}
proc ::log::Write::puts* {text} {
puts stderr -nonewline $text
flush stderr
return
}
# ### ### ### ######### ######### #########
## State
namespace eval ::log {
variable detail ; # map: TAG -> level of interest
variable prefix ; # map: TAG -> message prefix to use
variable fds ; # map: TAG -> command prefix to use for writing the message.
variable active ; # map: TAG -> boolean flag, true if tag is active.
# Notes:
# The tag '::' is reserved.
# prefix() uses it to store the global message prefix.
# fds() uses it to store a global command prefix for writing messages.
set fds(::) ::log::Write
namespace export -clear *
namespace ensemble create -subcommands {}
}
# ### ### ### ######### ######### #########
## Look for the magic of package task, and if found, reconfigure
## ourselves to write to the main system. Do not forget to import the
## main's status as well.
::apply {{} {
if {![info exists ::task::type]} return
::log::${::task::type}::link $::task::main
return
}}
# ### ### ### ######### ######### #########
## Ready
package provide blog 1.0
return
|
Added attic/lib/log/pkgIndex.tcl.
|
|
>
>
>
|
1
2
3
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded blog 1.0 [list source [file join $dir log.tcl]]
|
Added attic/lib/project/p_client.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Access to the bookflow project database from any part of the
# application.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require scoreboard
namespace eval ::bookflow::project {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/project
#debug on bookflow/project
# ### ### ### ######### ######### #########
## API & Implementation
## Wait for the server thread to complete initialization
proc ::bookflow::project::ok {cmd} {
Debug.bookflow/project {OK <cmd>}
# Wait for the appearance of (PROJECT SERVER *)
scoreboard take {PROJECT SERVER *} [list ::apply {{cmd tuple} {
# Put tuple back for others.
scoreboard put $tuple
# Make delegation command usable, i.e. tell it which thread to
# send the commands to.
lassign $tuple _ _ thread
variable server $thread
# Tell the caller that the database server thread is (now)
# ready.
uplevel #0 $cmd
} ::bookflow::project} $cmd]
Debug.bookflow/project {OK/}
return
}
# ### ### ### ######### ######### #########
## API & Implementation
## Delegate all actions to the server thread. This serializes
## concurrent access by different parts of the application.
proc ::bookflow::project {args} {
variable project::server
return [thread::send $server [info level 0]]
}
# ### ### ### ######### ######### #########
namespace eval ::bookflow::project {
variable server
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::project 0.1
return
|
Added attic/lib/project/p_server.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Access to a bookflow project database. The actual access is through
# the bookflow::db package. This package simply wraps around it, to
# serialize any access from all the threads of the application, acting
# as an in-application server. This server runs in its own thread.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require bookflow::db
namespace eval ::bookflow::project {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/project
#debug on bookflow/project
# ### ### ### ######### ######### #########
::apply {{} {
task launch [list ::apply {{} {
package require scoreboard
# Wait for the appearance of (DATABASE *)
scoreboard wpeek {DATABASE *} {::apply {{tuple} {
lassign $tuple _ dbfile
# Pull the project location
scoreboard wpeek {AT *} [list ::apply {{dbfile tuple} {
lassign $tuple _ project
package require bookflow::db
set dbfile $project/$dbfile
if {![file exists $dbfile]} {
[bookflow::db new $dbfile] destroy
}
::bookflow::db ::bookflow::project $dbfile
set id [thread::id]
scoreboard put [list PROJECT SERVER $id]
return
}} $dbfile]
return
}}}
}}]
}}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::project::server 0.1
return
|
Added attic/lib/project/pkgIndex.tcl.
|
|
>
>
>
|
1
2
3
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::project 0.1 [list source [file join $dir p_client.tcl]]
package ifneeded bookflow::project::server 0.1 [list source [file join $dir p_server.tcl]]
|
Added attic/lib/sb/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded scoreboard 0.1 [list source [file join $dir scoreboard.tcl]]
|
Added attic/lib/sb/sb_client.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Scoreboard Client. Used by tasks (in threads) to talk to the actual
# scoreboard in the main thread. The commands are shims which redirect
# to the equivalent command in the main thread, possibly rewriting
# arguments to handle the proper back and forth for callbacks.
# ### ### ### ######### ######### #########
## API & Implementation
proc ::scoreboard::put {args} {
thread::send -async $::task::main [info level 0]
return
}
proc ::scoreboard::take {pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::takeall {pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::peek {pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::wpeek {pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::bind {event pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::unbind {event pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::scoreboard {
namespace export {[a-z]*}
namespace ensemble create
}
|
Added attic/lib/sb/sb_server.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Scoreboard, a singleton in-memory database used by the concurrent
# tasks and the main control to coordinate and communicate with each
# other. Actually a tuple-space with a bit of dressing disguising it.
# ### ### ### ######### ######### #########
## API & Implementation
proc ::scoreboard::put {args} {
variable db
if {![llength $args]} {
return -code error "wrong\#args: expected tuple..."
}
Debug.scoreboard {put <[join $args ">\nput <"]>}
foreach tuple $args {
incr db($tuple)
Notify put $tuple
}
Broadcast $args
Debug.scoreboard {put/}
return
}
proc ::scoreboard::take {pattern cmd} {
variable db
Debug.scoreboard {take <$pattern> (($cmd))}
set matches [array names db $pattern]
if {![llength $matches]} {
Debug.scoreboard { no matches, defer response}
Wait take $pattern $cmd
Debug.scoreboard {take/}
return
}
set tuple [lindex $matches 0]
Debug.scoreboard { matches = [llength $matches]}
Debug.scoreboard { taken <$tuple>}
Remove $tuple
Notify take $tuple
Call $cmd $tuple
Debug.scoreboard {take/}
return
}
proc ::scoreboard::takeall {pattern cmd} {
variable db
Debug.scoreboard {takeall <$pattern> (($cmd))}
set matches [array names db $pattern]
Debug.scoreboard { matches = [llength $matches]}
foreach tuple $matches {
Debug.scoreboard { taken <$tuple>}
Remove $tuple
Notify take $tuple
}
Call $cmd $matches
Debug.scoreboard {takeall/}
return
}
proc ::scoreboard::peek {pattern cmd} {
variable db
Debug.scoreboard {peek <$pattern> (($cmd))}
set matches [array names db $pattern]
Debug.scoreboard { matches = [llength $matches]}
Call $cmd $matches
Debug.scoreboard {peek/}
return
}
proc ::scoreboard::wpeek {pattern cmd} {
variable db
Debug.scoreboard {wpeek <$pattern> (($cmd))}
set matches [array names db $pattern]
if {![llength $matches]} {
Debug.scoreboard { no matches, defer response}
Wait peek $pattern $cmd
Debug.scoreboard {wpeek/}
return
}
set tuple [lindex $matches 0]
Debug.scoreboard { matches = [llength $matches]}
Debug.scoreboard { peeked <$tuple>}
Call $cmd $tuple
Debug.scoreboard {wpeek/}
return
}
proc ::scoreboard::bind {event pattern cmd} {
Debug.scoreboard {bind <$event <$pattern>> (($cmd))}
if {$event ni {put take missing}} {
return -code error "Bad event \"$event\", expected one of missing, put, or take"
}
variable bind
lappend bind($event) [list $pattern $cmd]
Debug.scoreboard {bind/}
return
}
proc ::scoreboard::unbind {event pattern cmd} {
Debug.scoreboard {unbind <$event <$pattern>> (($cmd))}
if {$event ni {put take missing}} {
return -code error "Bad event \"$event\", expected one of missing, put, or take"
}
variable bind
set k [list $pattern $cmd]
set pos [lsearch -exact $bind($event) $k]
if {$pos < 0} return
set bind($event) [lreplace $bind($event) $pos $pos]
Debug.scoreboard {unbind/}
return
}
# ### ### ### ######### ######### #########
## Internals
proc ::scoreboard::Return {thread cmd args} {
thread::send -async $thread [list {*}$cmd {*}$args]
return
}
proc ::scoreboard::Remove {tuple} {
variable db
incr db($tuple) -1
if {!$db($tuple)} { unset db($tuple) }
return
}
proc ::scoreboard::Wait {action pattern cmd} {
variable wait
lappend wait [list $action $pattern $cmd]
Notify missing $pattern
return
}
proc ::scoreboard::Broadcast {tuples} {
variable wait
Debug.scoreboard { Broadcast}
#Debug.scoreboard { [join $wait "\n "]}
set stillwaiting {}
foreach item $wait {
# Quick bail out if all tuples have been broadcast.
if {![llength $tuples]} {
lappend stillwaiting $item
continue
}
# Bail if the pattern of the waiting request doesn't match any
# tuple.
lassign $item action pattern cmd
set pos [lsearch -glob $tuples $pattern]
if {$pos < 0} {
lappend stillwaiting $item
continue
}
# This request matches and is now served. It doesn't go on the
# still-pending list. The tuple in question is removed, if and
# only if the action was 'take'.
Debug.scoreboard { Broadcast : Match <$pattern>}
set tuple [lindex $tuples $pos]
if {$action eq "take"} {
set tuples [lreplace $tuples $pos $pos]
Debug.scoreboard { taken <$tuple>}
Remove $tuple
} else {
Debug.scoreboard { peeked <$tuple>}
}
Call $cmd $tuple
}
set wait $stillwaiting
Debug.scoreboard { Broadcast/}
return
}
proc ::scoreboard::Call {cmd args} {
Debug.scoreboard { Call $cmd ($args)}
after idle [list after 1 [list {*}$cmd {*}$args]]
return
}
proc ::scoreboard::Notify {event tuple} {
Debug.scoreboard { Notify $event}
variable bind
foreach item $bind($event) {
lassign $item p c
if {![string match $p $tuple]} continue
Call $c $tuple
}
Debug.scoreboard { Notify $event/}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::scoreboard {
variable db ; # tuple array: tuple -> count of instances
variable wait {} ; # list of pending 'take's.
variable bind ; # List of bindings per event-type. Initially empty.
array set bind {
missing {}
put {}
take {}
}
namespace export {[a-z]*}
namespace ensemble create
}
|
Added attic/lib/sb/scoreboard.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Scoreboard, a singleton in-memory database used by the concurrent
# tasks and the main control to coordinate and communicate with each
# other. Actually a tuple-space with a bit of dressing disguising it.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require debug
namespace eval ::scoreboard {}
# ### ### ### ######### ######### #########
## Tracing
debug off scoreboard
#debug on scoreboard
# ### ### ### ######### ######### #########
##
# The code here checks wether the package is running in the main
# thread or a task thread, and loads the associated implementation.
::apply {{here} {
if {![info exists ::task::type]} {
source [file join $here sb_server.tcl]
} else {
switch -exact -- $::task::type {
thread {
source [file join $here sb_client.tcl]
}
default {
return -code error "Unable to handle ${::task::type}-based tasks"
}
}
}
return
}} [file dirname [file normalize [info script]]]
# ### ### ### ######### ######### #########
## Ready
package provide scoreboard 0.1
return
|
Added attic/lib/scan/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::scan 0.1 [list source [file join $dir scan.tcl]]
|
Added attic/lib/scan/scan.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task.
# Initial task.
# Scans the specified directory, looking for the BOOKFLOW database and
# JPEG images.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require task
namespace eval ::bookflow::scan {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/scan
#debug on bookflow/scan
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::scan {projectdir} {
Debug.bookflow/scan {Bookflow::Scan <$projectdir>}
task launch [list ::apply {{projectdir} {
package require bookflow::scan
bookflow::scan::TASK $projectdir
task::exit
}} $projectdir]
Debug.bookflow/scan {/}
return
}
# ### ### ### ######### ######### #########
## Internals
proc ::bookflow::scan::TASK {projectdir} {
package require debug
# Requisites for the task
package require blog
package require jpeg
package require fileutil
package require scoreboard
package require bookflow::db
#@SB AT *
scoreboard put [list AT $projectdir]
set dir [file normalize $projectdir]
set hasimages 0
set hasproject 0
# Iteratation over the files in the project directory.
# No traversal into subdirectories!
# Uses 'file'-like commands to determine the type of
# files (jpeg, bookflow database, other) for classification.
foreach f [lsort -dict [glob -nocomplain -directory $dir *]] {
Debug.bookflow/scan { Processing $f}
if {![file isfile $f]} {
Debug.bookflow/scan { Directory, ignored}
continue
}
set fx [fileutil::stripPath $dir $f]
if {[jpeg::isJPEG $f]} {
Debug.bookflow/scan { Image}
set hasimages 1
Log.bookflow {* Image $fx}
scoreboard put [list FILE $fx]
} elseif {[bookflow::db isBookflow $f]} {
Debug.bookflow/scan { Project database found}
set hasproject 1
Log.bookflow {% Project database $fx}
scoreboard put [list DATABASE $fx]
} else {
Debug.bookflow/scan { Ignored}
}
}
# Scan is complete, summarize the result. This triggers other
# tasks.
if {$hasproject} {
# We have a project. Might have images or not. Signal that
# this project needs verification, i.e. internal consistency
# check, and checking against the set of external images
# found.
Debug.bookflow/scan {Bookflow::Scan -> Verify project}
scoreboard put {PROJECT VERIFY}
} elseif {$hasimages} {
# While no project database is available, we have
# images. Signal that we should create a fresh project
# database from the images.
Debug.bookflow/scan {Bookflow::Scan -> Create project}
scoreboard put {PROJECT CREATE}
} else {
# Neither project, nor images were found. This is an abnormal
# situation. Signal the main controller to report on this.
Debug.bookflow/scan {Bookflow::Scan -> Nothing found}
set msg "The chosen project directory $projectdir contains neither images to process, nor a bookflow database.\n\nNothing will be done."
scoreboard put [list PROJECT ERROR $msg]
}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow {
namespace export scan
namespace ensemble create
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::scan 0.1
return
|
Added attic/lib/syscolor/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded syscolor 0.1 [list source [file join $dir syscolor.tcl]]
|
Added attic/lib/syscolor/syscolor.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Determine and save system colors for use by (mega)widgets to
# visually match an application's appearance to the environment.
# Not specific to bookflow.
# ### ### ### ######### ######### #########
## Requisites
package require Tk
namespace eval ::syscolor {}
# ### ### ### ######### ######### #########
## API
proc ::syscolor::buttonFace {} { variable buttonFace ; return $buttonFace }
proc ::syscolor::highlight {} { variable highlight ; return $highlight }
proc ::syscolor::highlightText {} { variable highlightText ; return $highlightText }
# ### ######### ###########################
## State
namespace eval ::syscolor {
variable buttonFace
variable highlight
variable highlightText
}
# ### ######### ###########################
## Initialization
::apply {{} {
set w [listbox .__syscolor__]
variable buttonFace [$w cget -highlightbackground]
variable highlight [$w cget -selectbackground]
variable highlightText [$w cget -selectforeground]
destroy $w
return
} ::syscolor}
# ### ######### ###########################
## Ready
package provide syscolor 0.1
return
|
Added attic/lib/task/pkgIndex.tcl.
|
|
>
>
>
|
1
2
3
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded task::thread 0.1 [list source [file join $dir task.tcl]]
package ifneeded task 0.1 {package require task::thread ; package provide task 0.1}
|
Added attic/lib/task/task.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Handling of (background) tasks running concurrently to the main
# system. This implementation uses thread, via package Thread.
# Alternate implementations could use sub-processses, or coroutines
# (green threads). The main difference between them all will be in
# the communication between main system and tasks, and between tasks,
# and setting up the per-task environment for this communication.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require Thread
namespace eval ::task {}
# ### ### ### ######### ######### #########
## Tracing
debug off task
#debug on task
# ### ### ### ######### ######### #########
## API & Implementation
proc ::task::launch {cmdprefix} {
# cmdprefix = The task to run concurrently.
Debug.task {Task <$cmdprefix>}
# Create thread for task
set id [thread::create]
Debug.task { Running in thread $id}
# Set magic information for communication with the main
# thread. The packages requiring special setup for proper
# communication will look for and recognize the magic and
# configure themselves accordingly.
Debug.task { Configure communication magic}
thread::send $id [list ::apply {{main ap} {
set ::auto_path $ap
namespace eval ::task {}
set ::task::type thread
set ::task::main $main
proc ::task::exit {} {
thread::exit
}
}} [thread::id] $::auto_path]
# And at last, launch the task
Debug.task { Launch...}
thread::send -async $id $cmdprefix
Debug.task {/}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::task {
namespace export -clear *
namespace ensemble create -subcommands {}
}
# ### ### ### ######### ######### #########
## Ready
package provide task::thread 0.1
return
|
Added attic/lib/thumbnail/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::thumbnail 0.1 [list source [file join $dir thumbnail.tcl]]
|
Added attic/lib/thumbnail/thumbnail.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task. Continuous.
# Creating and invalidating thumbnails.
# A producer in terms of "doc/interaction_pci.txt"
#
# Generated thumbnails are cached in the directory ".bookflow/thumb"
# of the project directory.
# Limits itself to no more than four actual threads in flight,
# i.e. performing image scaling. The scaling tasks do not exit on
# completion, but wait for more operations to perform. Communication
# and coordination is done through the scoreboard. As usual.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
package require scoreboard
namespace eval ::bookflow::thumbnail {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/thumbnail
#debug on bookflow/thumbnail
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::thumbnail {} {
Debug.bookflow/thumbnail {Bookflow::Thumbnail}
scoreboard wpeek {AT *} [namespace code thumbnail::Initialize]
Debug.bookflow/thumbnail {/}
return
}
proc ::bookflow::thumbnail::request {path size} {
return [list THUMBNAIL $path $size *]
}
# ### ### ### ######### ######### #########
## Internals. Process initialization
proc ::bookflow::thumbnail::Initialize {tuple} {
# tuple = (AT project)
lassign $tuple _ project
Debug.bookflow/thumbnail {Bookflow::Thumbnail Initialize <$project>}
# Monitor for thumbnail invalidation
WatchForInvalidation $project
# Launch the tasks doing the actual resizing.
variable max
for {set i 0} {$i < $max} {incr i} {
task launch [list ::apply {{project} {
package require bookflow::thumbnail
bookflow::thumbnail::ScalingTask $project
}} $project]
}
# Monitor for thumbnail creation requests.
WatchForMisses $project
Debug.bookflow/thumbnail {Bookflow::Thumbnail Initialize/}
return
}
# ### ### ### ######### ######### #########
## Internals. Invalidation processing. See doc/interaction_pci.txt (1).
proc ::bookflow::thumbnail::WatchForInvalidation {project} {
# doc/interaction_pci.txt (1)
Debug.bookflow/thumbnail {Bookflow::Thumbnail WatchForInvalidation}
scoreboard take {!THUMBNAIL *} [namespace code [list Invalidate $project]]
Debug.bookflow/thumbnail {Bookflow::Thumbnail WatchForInvalidation}
return
}
proc ::bookflow::thumbnail::Invalidate {project tuple} {
# tuple = (!THUMBNAIL path)
Debug.bookflow/thumbnail {Bookflow::Thumbnail Invalidate}
lassign $tuple _ path
scoreboard takeall [list THUMBNAIL $path *] [namespace code [list Cleanup $project $path]]
Debug.bookflow/thumbnail {Bookflow::Thumbnail Invalidate/}
return
}
proc ::bookflow::thumbnail::Cleanup {project path tuples} {
Debug.bookflow/thumbnail {Bookflow::Thumbnail Cleanup}
file delete [ThumbFullPath $project $path]
WatchForInvalidation $project
Debug.bookflow/thumbnail {Bookflow::Thumbnail Cleanup/}
return
}
# ### ### ### ######### ######### #########
## Internals. Creation request handling. See doc/interaction_pci.txt (2).
proc ::bookflow::thumbnail::WatchForMisses {project} {
Debug.bookflow/thumbnail {Bookflow::Thumbnail WatchForMisses}
# doc/interaction_pci.txt (2)
scoreboard bind missing {THUMBNAIL *} [namespace code [list MakeImage $project]]
Debug.bookflow/thumbnail {Bookflow::Thumbnail WatchForMisses}
return
}
proc ::bookflow::thumbnail::MakeImage {project pattern} {
# pattern = (THUMBNAIL path size *)
Debug.bookflow/thumbnail {Bookflow::Thumbnail MakeImage}
lassign $pattern _ path size
set dst [Path $path $size]
if {[file exists $project/$dst]} {
# The requested image already exists in the filesystem cache,
# simply make it available.
Return $path $size $dst
Debug.bookflow/thumbnail {Bookflow::Thumbnail MakeImage/}
return
}
# The image is not known yet. Forward the request to the scaling
# tasks to create the desired image.
RequestCreation $path $size $dst
Debug.bookflow/thumbnail {Bookflow::Thumbnail MakeImage/}
return
}
proc ::bookflow::thumbnail::Return {path size dst} {
scoreboard put [list THUMBNAIL $path $size $dst]
return
}
# ### ### ### ######### ######### #########
## Internals. Background tasks handling the actual scaling.
proc ::bookflow::thumbnail::RequestCreation {path size dst} {
scoreboard put [list SCALE $path $size $dst]
return
}
proc ::bookflow::thumbnail::ScalingTask {project} {
package require debug
Debug.bookflow/thumbnail {Bookflow::Thumbnail ScalingTask}
# Requisites for the task
package require bookflow::thumbnail
package require scoreboard
package require crimp ; wm withdraw .
package require img::png
package require img::jpeg
# Start waiting for requests.
ReadyForRequests $project
Debug.bookflow/thumbnail {Bookflow::Thumbnail ScalingTask/}
return
}
proc ::bookflow::thumbnail::ReadyForRequests {project} {
# Wait for more requests.
scoreboard take {SCALE *} [namespace code [list ScaleImage $project]]
return
}
proc ::bookflow::thumbnail::ScaleImage {project tuple} {
# tuple = (SCALE path size dstpath)
# result = (THUMBNAIL path dstpath)
Debug.bookflow/thumbnail {Bookflow::Thumbnail ScaleImage}
# Decode request
lassign $tuple _ path size dst
# Perform the scaling to requested size, reading jpeg, writing
# png, using crimp internally.
set photo [image create photo -file $project/$path]
set h [image height $photo]
set w [image width $photo]
if {$w > $h} {
# Landscape.
set h [expr {int($h*$size/$w)}]
set w $size
} else {
# Portrait.
set w [expr {int($w*$size/$h)}]
set h $size
}
crimp write 2tk $photo [crimp resize [crimp read tk $photo] $w $h]
file mkdir [file dirname $project/$dst]
$photo write $project/$dst -format png
image delete $photo
Return $path $size $dst
ReadyForRequests $project
Debug.bookflow/thumbnail {Bookflow::Thumbnail ScaleImage/}
return
}
# ### ### ### ######### ######### #########
## Internals. Path handling.
proc ::bookflow::thumbnail::Path {path size} {
return .bookflow/thumb$size/[file rootname $path].png
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow::thumbnail {
# Number of parallel scaling tasks.
variable max 4
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::thumbnail 0.1
return
|
Added attic/lib/verify/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::verify 0.1 [list source [file join $dir verify.tcl]]
|
Added attic/lib/verify/verify.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task.
# Waiting for requests to verify an exiting project database.
# Launches the task when the request is found.
# Compares found images with images in the database.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
namespace eval ::bookflow::verify {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/verify
#debug on bookflow/verify
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::verify {} {
Debug.bookflow/verify {Bookflow::Verify Watch}
scoreboard take {PROJECT VERIFY} [namespace code verify::RUN]
Debug.bookflow/verify {/}
}
# ### ### ### ######### ######### #########
## Internals
proc ::bookflow::verify::RUN {tuple} {
Debug.bookflow/verify {Bookflow::Verify RUN}
Log.bookflow {Verifying project database...}
task launch [list ::apply {{} {
package require bookflow::verify
bookflow::verify::TASK
}}]
Debug.bookflow/verify {Bookflow::Verify RUN/}
return
}
proc ::bookflow::verify::TASK {} {
package require debug
Debug.bookflow/verify {Bookflow::Verify TASK}
# Requisites for the task
package require scoreboard
package require struct::set
package require bookflow::verify
package require bookflow::project ; # client
scoreboard wpeek {AT *} [namespace code BEGIN]
Debug.bookflow/verify {Bookflow::Verify TASK/}
return
}
proc ::bookflow::verify::BEGIN {tuple} {
variable defaultfile
Debug.bookflow/verify {Bookflow::Verify BEGIN <$tuple>}
# tuple = (AT project)
# Get the payload
lassign $tuple _ projectdir
# We wait until the server thread has completed initialization and
# is providing access to the database.
::bookflow::project::ok [namespace code [list WaitForServerStart $projectdir]]
Debug.bookflow/verify {Bookflow::Verify BEGIN/}
return
}
proc ::bookflow::verify::WaitForServerStart {project} {
Debug.bookflow/verify {Bookflow::Verify WaitForServerStart}
# Fill the database using the image files found by the scanner.
scoreboard takeall {FILE*} [namespace code [list FILES $project]]
Debug.bookflow/verify {Bookflow::Verify WaitForServerStart/}
return
}
proc ::bookflow::verify::FILES {project tuples} {
Debug.bookflow/verify {Bookflow::Verify FILES}
# tuples = list ((FILE *)...)
# We now have the files found by the scanner...
set scanned {}
foreach def [lsort -dict -index 1 $tuples] {
lassign $def _ jpeg
lappend scanned $jpeg
}
# ... and the files known to the project.
set known [::bookflow::project files]
# Separate them into newly added, gone missing, and unchanged.
lassign [struct::set intersect3 $scanned $known] \
unchanged new del
# New files are handled like the create task does, i.e. they are
# added to the @SCRATCH book. NOTE that we are not adding them to
# the scoreboard yet. This is done later, when all books have been
# updated per the images.
foreach jpeg $new {
::bookflow::project book extend @SCRATCH $jpeg \
[file mtime $project/$jpeg]
}
# Removed files are moved from whereever they are into the @TRASH
# book. Except those which are already there.
foreach jpeg $new {
set jbook [::bookflow::project book holding $jpeg]
if {$jbook eq "@TRASH"} continue
::bookflow::project book move @TRASH $jpeg
}
# Unchanged files ... Those in @TRASH have apparently been
# restored as files, so these move to @SCRATCH. Even so, we cannot be sure that their derived data is ok,
# forcing us to invalidate them.
foreach jpeg $unchanged {
set jbook [::bookflow::project book holding $jpeg]
if {$jbook eq "@TRASH"} {
# FUTURE :: See if we can remember their old book
# FUTURE :: somewhere, and restore them to that.
::bookflow::project book move @SCRATCH $jpeg
set modified 1
} else {
# Ok, this file was present before, and is still present.
# Now let us check if it was modified since the project
# was used the last time. Because if so the derived data
# we have is useless and need to be regenerated.
set current [file mtime $project/$jpeg]
set last [::bookflow::project file mtime $jpeg]
set modified [expr {$current != $last}]
}
if {$modified} {
# Invalidation requests. We can do the statistics here
# because nobody is in a position to ask for it and we
# know how to do it. For the other things we rely on their
# producers for the invalidation.
::bookflow::project statistics unset $jpeg
scoreboard put [list !THUMBNAIL $jpeg]
scoreboard put [list !GREYSCALE $jpeg]
}
}
# Closing work ...
# ... pull books out of the database and declare them ...
foreach b [::bookflow::project books] {
Debug.bookflow/verify { BOOK $b}
scoreboard put [list BOOK $b]
# ... pull files out and declare them ...
foreach {jpeg serial} [::bookflow::project book files $b] {
Debug.bookflow/verify { IMAGE $jpeg $serial $b}
scoreboard put [list IMAGE $jpeg $serial $b]
# Pre-load any statistics information, shortcircuiting its
# producer.
set statistics [::bookflow::project statistics get $jpeg]
if {$statistics ne {}} {
scoreboard put [list STATISTICS $jpeg $statistics]
}
}
}
Debug.bookflow/verify {Bookflow::Verify FILES/}
task::exit
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow {
namespace export verify
namespace ensemble create
namespace eval verify {
variable defaultfile BOOKFLOW
}
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::verify 0.1
return
|
Added attic/lib/wlog/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded widget::log 0.1 [list source [file join $dir wlog.tcl]]
|
Added attic/lib/wlog/wlog.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# A simple log window where system activity can be shown to the end user.
# Not specific to bookflow.
# FUTURE expansion
# Tagging of messages, allowing for customization of appearance (like
# colorization).
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require Tk
package require snit
package require widget::scrolledwindow
# ### ### ### ######### ######### #########
## Tracing
# ### ### ### ######### ######### #########
## Implementation
snit::widgetadaptor ::widget::log {
delegate option * to mytext
constructor {args} {
installhull using widget::scrolledwindow \
-borderwidth 1 -relief sunken
set mytext [text $win.log -height 5 -width 80 -font {Helvetica -18}]
$hull setwidget $mytext
$self configurelist $args
return
}
method puts {text} {
$self puts* $text\n
return
}
method puts* {text} {
$mytext configure -state normal
$mytext insert end $text
$mytext see end
$mytext configure -state disabled
return
}
# ### ### ### ######### ######### #########
##
variable mytext
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide widget::log 0.1
return
|
Added attic/tools/doc_scoreboard.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/bin/sh
# -*- tcl -*- \
exec tclsh "\$0" ${1+"$@"}
# tools
# - scan the bookflow sources for scoreboard access and generate
# a database telling us who accesses what and how.
# ## ### ##### ######## ############# #####################
package require Tcl 8.5
package require fileutil
# ## ### ##### ######## ############# #####################
proc main {tooldir} {
dump [sbscan [file dirname $tooldir]]
return
}
proc sbscan {topdir} {
#puts Scanning\ $topdir...
set db {}
foreach f [fileutil::findByPattern $topdir -glob -- *.tcl] {
if {[file isdirectory $f]} continue
if {[string match *doc_scoreboard* $f]} continue
if {[string match *pkgIndex* $f]} continue
lappend db {*}[scansbfile $f [fileutil::stripPath $topdir $f]]
}
return $db
}
proc scansbfile {f fname} {
#puts \t$f...
array set t {}
set TUPLE {}
foreach line [split [fileutil::cat $f] \n] {
set line [string trim $line]
switch -glob -- $line {
\#* {
# ... pragmas
if {[string match {*@SB *} $line]} {
regexp {@SB (.*)$} $line -> TUPLE
}
}
package*provide* {
# might use this in future.
# for new we key on the file name.
lassign $line _ _ package _
}
scoreboard* {
#puts \t\t|$line|
word line ; # scoreboard
set method [word line]
switch -exact -- $method {
put {
# remainder = tuples
while {$line ne {}} {
set tuple [tuple line]
lappend t($tuple) $method
}
}
take -
takeall -
peek -
wpeek {
set tuple [tuple line]
lappend t($tuple) $method
}
unbind -
bind {
set event [word line]
set tuple [tuple line]
lappend t($tuple) [list $method $event]
}
default {
# unknown method.
puts \tUnknown\ method \"$method\" found
}
}
}
}
}
if {![array size t]} { return }
return [list $fname [array get t]]
# result = dict (file -> dict (tuple -> list (action...)))
}
proc tuple {svar} {
upvar 1 $svar string TUPLE TUPLE
set tuple [word string]
if {$TUPLE ne {}} {
set tuple $TUPLE
set TUPLE {}
}
return $tuple
}
proc word {svar} {
upvar 1 $svar string
set string [string trim $string]
#puts "\[word \"$string\"\]"
if {[string match "\$\{*" $string]} {
set c varb
regexp {(\${[^\}]+})[ ]+(.*)$} $string -> word remainder
} elseif {[string match "\$*" $string]} {
set c var
expr {[regexp {(\$[^ ]+)[ ]+(.*)$} $string -> word remainder] ||
[regexp {(\$[^ ]+)()$} $string -> word remainder]}
} elseif {[string match "\\\[*" $string]} {
set c cmd
set patterni "(\\\[\[^\]\]+\\\])\[ \]+(.*)$"
set patterne "(\\\[\[^\]\]+\\\])()$"
expr {[regexp $patterni $string -> word remainder] ||
[regexp $patterne $string -> word remainder]}
} elseif {[string match "\\\{*" $string]} {
set c w
set patterni "(\\\{\[^\}\]+\\\})\[ \]+(.*)$"
set patterne "(\\\{\[^\}\]+\\\})()$"
expr {[regexp $patterni $string -> word remainder] ||
[regexp $patterne $string -> word remainder]}
# strip the braces.
set word [string range $word 1 end-1]
} else {
set c w
regexp {([^ ]+)[ ]+(.*)$} $string -> word remainder
}
if {![info exists word]} {
error "word error ($string)"
}
#puts \t$c|$word|$remainder|
set string $remainder
return $word
}
proc dump {db} {
# db = dict (file -> dict (tuple -> list (action...)))
#array set d $db
#parray d
# Invert the structure to make the tuple (patterns) the major index.
# D = dict (tuple -> dict (action -> list (file...)))
set D {}
foreach {fname data} $db {
foreach {tuple actions} $data {
set actions [lsort -unique $actions]
set A {}
foreach a $actions {
dict lappend A $a $fname
}
dict lappend D $tuple $A
}
}
set db $D
set D {}
foreach {tuple data} $db {
# data = list (dict (action -> list(fname)))
array set X {}
foreach dict $data {
lassign $dict action files
lappend X($action) {*}$files
}
#parray X
lappend D $tuple [array get X]
array unset X
}
#puts $D
#return
# Write structure in machine- and human-readable form.
foreach {tuple fa} [dictsort $D] {
puts "\ntuple [list $tuple] \{"
# todo description - get via pragma's
puts "\} \{"
#puts "==== $fa ===="
foreach {action files} [dictsort $fa] {
set files [lsort -unique $files]
puts " $action \{\n\t[join $files "\n\t"]\n \}"
}
puts "\}"
}
#array set T $D
#parray T
return
}
proc dictsort {dict} {
array set a $dict
set out [list]
foreach key [lsort [array names a]] {
lappend out $key $a($key)
}
return $out
}
# ## ### ##### ######## ############# #####################
main [file dirname [file normalize [info script]]]
exit
|
Added bin/bookflow-flag.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/usr/bin/env tclsh
# -*- tcl -*-
# # ## ### ##### ######## ############# #####################
# Take the current project for the user to go over images and flag their properties
# (left/right, cover/content, exclude bad). Requires the medium size thumbnails for
# display (made by -> bookflow-gen-medium).
package require Tcl 8.5
::apply {{selfdir} {
# selfdir == bindir
lappend ::auto_path [file dirname $selfdir]/lib
return
}} [file dirname [file normalize [info script]]]
# TODO: Restrict to images of a certain size and/or make (camera type).
package require Tk 8.5
package require widget::toolbar
package require widget::statusbar
package require widget::scrolledwindow
# Would prefer to have a widget::progressbar
package require BWidget ; ProgressBar::use
package require bookflow::project
package require crimp::tk ;# crimp -> photo
package require crimp::ppm ;# crimp -> reading ppm
# XXX TODO: crimp::jpeg
package require action
package require famfamfam::silk
package require tooltip
# # ## ### ##### ######## ############# #####################
proc main {} {
if {[catch {
cmdline
view
controller
model
} msg]} {
puts stderr $msg
exit 1
}
# Begin event loop, and interaction
vwait ::forever
return
}
proc cmdline {} {
global argv argv0
if {[llength $argv] > 1} {
puts stderr "Usage: $argv0 ?projectdir?"
exit 1
}
if {[llength $argv] == 1} {
cd [lindex $argv 0]
}
# Open the project file.
bookflow::project BOOK [pwd]/BOOKFLOW
return
}
# # ## ### ##### ######## ############# #####################
## View
proc view {} {
wm withdraw .
view/widgets
view/layout
wm deiconify .
return
}
proc view/widgets {} {
# listbox, left or right, of all images (fast switching).
# various labels for status icons
# label displaying the current page image
# buttons: next, previous, first, last, exit - toolbar
# statusbar - messages ...
::widget::toolbar .tools
::widget::statusbar .status
ttk::label .status.message \
-textvariable ::vstatus \
-width 1 -anchor w
ProgressBar .status.progress \
-variable ::vprogress \
-type infinite -orient horizontal \
-bd 1 -relief sunken
widget::scrolledwindow .sw -borderwidth 1 -relief sunken
listbox .images \
-listvariable ::vimages \
-selectmode extended
label .page -bd 5
ttk::label .orient
ttk::label .left
ttk::label .right
ttk::label .attention
ttk::label .dropped
view/tool/add arrow_left |<-- First first
view/tool/add arrow_left <-- Previous previous
view/tool/add arrow_right --> Next next
view/tool/add arrow_right -->| Last last
view/tool/space
view/tool/add asterisk_orange Exit Exit exit
view/tag .
return
}
proc view/tag {w} {
bindtags $w [list _self {*}[bindtags $w]]
foreach c [winfo children $w] {
view/tag $c
}
return
}
proc view/tool/add {image label hint action args} {
set cmd {}
lappend cmd .tools add button $label
lappend cmd -text $label
lappend cmd -command [list action invoke $action] {*}$args
if {$image ne {}} {
lappend cmd -image [famfamfam silk get $image]
}
{*}$cmd
set w [.tools itemid $label]
tooltip::tooltip $w $hint
after 0 [list action link $action $w]
return
}
proc view/tool/space {} {
.tools add space ____ -separator 1
return
}
proc view/layout {} {
.sw setwidget .images
.status add .status.message -weight 1
.status add .status.progress
grid columnconfigure . 0 -weight 0
grid columnconfigure . 1 -weight 0
grid columnconfigure . 2 -weight 0
grid columnconfigure . 3 -weight 0
grid columnconfigure . 4 -weight 0
grid columnconfigure . 5 -weight 1
grid columnconfigure . 6 -weight 0
grid rowconfigure . 0 -weight 0
grid rowconfigure . 1 -weight 0
grid rowconfigure . 2 -weight 0
grid rowconfigure . 3 -weight 1
grid rowconfigure . 4 -weight 0
view/layout/at .tools 0 0 1 7
view/layout/at .sw 1 0 3 1
view/layout/at .orient 1 1 1 1
view/layout/at .left 1 2 1 1
view/layout/at .attention 1 3 1 1
view/layout/at .dropped 1 4 1 1
view/layout/at .right 1 6 1 1
view/layout/at .page 3 1 1 6
view/layout/at .status 4 0 1 7
return
}
proc view/layout/at {widget row col r c} {
grid $widget -row $row -column $col \
-sticky swen -rowspan $r -columnspan $c
return
}
# # ## ### ##### ######## ############# #####################
# # ## ### ##### ######## ############# #####################
# View. Entrypoints for the controller.
proc view/status {text} {
set ::vstatus $text
return
}
proc view/progress/tick {} {
.status add .status.progress
incr ::vprogress
return
}
proc view/progress/reset {} {
catch { .status remove .status.progress }
set ::vprogress 0
return
}
proc view/show {select index image used even content attention orientation} {
global oicon
#puts "v/s |$select $index $image i$used e$even c$content a$attention|"
catch { image delete [.page cget -image] }
.page configure -image $image
if {$attention} {
.attention configure -image [famfamfam silk get exclamation]
} else {
.attention configure -image {}
}
if {$used} {
.dropped configure -image {}
} else {
.dropped configure -image [famfamfam silk get cross]
}
if {$even} {
.left configure -image [famfamfam silk get asterisk_yellow]
.right configure -image {}
} else {
.left configure -image {}
.right configure -image [famfamfam silk get asterisk_yellow]
}
.orient configure -image [famfamfam silk get $oicon($orientation)]
if {$content} { ; # content page
.page configure -bg blue
} else { ; # cover page
.page configure -bg green
}
# List visualization of the flags
.images itemconfigure $index \
{*}[view/list/flags $used $even $content $attention $orientation]
# Modify list
if {$select} {
#puts XS=$index
.images selection clear 0 end
.images selection set $index
.images activate $index
c/selection
}
.images see $index
return
}
proc view/show/none {} {
image delete [.page cget -image]
.page configure -image {} -bg black
.orient configure -image {}
.attention configure -image {}
.dropped configure -image {}
.right configure -image {}
.left configure -image {}
return
}
proc view/list/fill {images iflags} {
set ::vimages $images
set k 0
foreach item $iflags {
lassign $item _ used even content attention orientation
.images itemconfigure $k \
{*}[view/list/flags $used $even $content $attention $orientation]
incr k
}
return
}
proc view/list/flags {used even content attention orientation} {
set options {}
if {$content} {
lappend options -fg black
} else {
lappend options -fg green
}
if {!$used} {
lappend options -bg red
} elseif {$attention} {
lappend options -bg yellow
} else {
lappend options -bg white
}
return $options
}
proc view/selection {} {
return [.images curselection]
}
# # ## ### ##### ######## ############# #####################
## Controller
proc controller {} {
c/actions
c/bindings
after 0 c/launch
return
}
proc c/actions {} {
action define exit ::exit
action define next c/show/next
action define previous c/show/previous
action define first c/show/first
action define last c/show/last
action define attention c/toggle/attention
action define drop c/toggle/used
action define front c/mark/cover_front
action define back c/mark/cover_back
action define left c/mark/even 1
action define right c/mark/even 0
action define cover c/mark/content 0
action define page c/mark/content 1
action define east c/orient east
action define south c/orient south
action define west c/orient west
action define north c/orient north
return
}
proc c/bindings {} {
# Leave
bind _self <q> {action invoke exit ; break}
# Navigation
bind _self <Left> {action invoke previous ; break}
bind _self <Right> {action invoke next ; break}
bind _self <Up> {action invoke previous ; break}
bind _self <Down> {action invoke next ; break}
bind _self <Prior> {action invoke previous ; break}
bind _self <Next> {action invoke next ; break}
bind _self <Home> {action invoke first ; break}
bind _self <End> {action invoke last ; break}
# Selection
bind .images <<ListboxSelect>> c/selection
# Flags
bind _self <exclam> {action invoke attention ; break}
bind _self <d> {action invoke drop ; break}
bind _self <f> {action invoke front ; break}
bind _self <b> {action invoke back ; break}
bind _self <l> {action invoke left ; break}
bind _self <r> {action invoke right ; break}
bind _self <c> {action invoke cover ; break}
bind _self <p> {action invoke page ; break}
# Orientation
bind _self <e> {action invoke east ; break}
bind _self <s> {action invoke south ; break}
bind _self <w> {action invoke west ; break}
bind _self <n> {action invoke north ; break}
return
}
proc c/launch {} {
action disable
action enable exit
m/initialize c/ready
return
}
proc c/ready {} {
global cimages cchosen
set cchosen {}
lassign [m/list] cimages iflags
view/list/fill $cimages $iflags
c/show/first
after 0 {action enable}
return
}
# # ## ### ##### ######## ############# #####################
## Controller state
global cimages ; # list of shown images
global cchosen ; # indices of the selected images
global cshown ; # index of the shown image
global ccurrent ; # path of the shown image
global cflags ; # flags of the shown image
# # ## ### ##### ######## ############# #####################
# # ## ### ##### ######## ############# #####################
# Implementations for the various actions
proc c/selection {} {
global cchosen
set current [view/selection]
#puts C=$current
#puts S=$cchosen
if {$current eq $cchosen} return
set cchosen $current
#puts S*$cchosen
if {[llength $cchosen]} {
c/show [lindex $cchosen 0] 0
} else {
c/show/none
}
return
}
proc c/load {index} {
global cshown ccurrent cimages cflags
# Locate image by index, then translate index (possibly symbolic,
# or relative) to a proper integer number.
set ccurrent [lindex $cimages $index]
set cshown [lsearch -exact $cimages $ccurrent]
set cflags [lassign [m/get $ccurrent] image]
return
}
proc c/show {index {select 1}} {
global cshown ccurrent cimages cflags
# Locate image by index, then translate index (possibly symbolic,
# or relative) to a proper integer number.
set ccurrent [lindex $cimages $index]
set cshown [lsearch -exact $cimages $ccurrent]
set cflags [lassign [m/get $ccurrent] image]
view/show $select $cshown $image {*}$cflags
view/status [expr {1+$cshown}]/[llength $cimages]
return
}
proc c/show/none {} {
view/show/none
view/status {}
return
}
proc c/show/first {} {
c/show 0
return
}
proc c/show/last {} {
c/show end
return
}
proc c/show/next {} {
c/show [c/advance]
return
}
proc c/show/previous {} {
c/show [c/advance -1]
return
}
proc c/advance {{step 1}} {
global cshown cimages
set n $cshown
incr n $step
if {$n < 0} {
set n [llength $cimages]
incr n -1
} elseif {$n >= [llength $cimages]} {
set n 0
}
return $n
}
proc c/apply/selection {label script} {
global cchosen cflags ccurrent cshown
if {![llength $cchosen]} return
action disable
set saved $cshown
set count 0
set max [llength $cchosen]
foreach sel $cchosen {
view/status "\[$label [expr {1+$sel}]\] [incr count]/$max"
view/progress/tick
update
c/load $sel
lassign $cflags u e c a o
eval $script
m/set $ccurrent $u $e $c $a $o
c/load $sel
}
c/show $saved 0
action enable
view/progress/reset
return
}
proc c/toggle/attention {} {
c/apply/selection {toggle attention} {
set a [expr {!$a}]
}
return
}
proc c/toggle/used {} {
c/apply/selection {toggle used} {
set u [expr {!$u}]
}
return
}
proc c/mark/cover_front {} {
c/apply/selection {front cover/west} {
set e 0 ; # odd == right == front
set c 0 ; # cover
set o 2 ; # west
}
return
}
proc c/mark/cover_back {} {
c/apply/selection {back cover/east} {
set e 1 ; # even == left == back
set c 0 ; # cover
set o 0 ; # east
}
return
}
proc c/mark/even {even} {
# Note: orientation derived from left/right
# Explicit orientation must be done after l/r classification
global omap
set w [expr {$even ? "left/even" : "right/odd"}]
set theorient [expr {$even ? "east" : "west"}]
set orient $omap($theorient)
c/apply/selection $w/$theorient {
upvar 1 even even orient orient
set e $even
set o $orient
}
return
}
proc c/mark/content {page} {
set w [expr {$page ? "content" : "cover"}]
c/apply/selection $w {
upvar 1 page page
set c $page
}
return
}
array set omap {
0 0 east 0
1 1 south 1
2 2 west 2
3 3 north 3
}
array set oicon {
0 arrow_right east arrow_right
1 arrow_down south arrow_down
2 arrow_left west arrow_left
3 arrow_up north arrow_up
}
proc c/orient {theorient} {
global omap
set orient $omap($theorient)
c/apply/selection $theorient {
upvar 1 orient orient
set o $orient
}
return
}
# # ## ### ##### ######## ############# #####################
## Model
proc model {} {
# nothing at the moment
return
}
proc m/initialize {args} {
global mimages
set mimages {}
foreach i [BOOK images-all] {
set flags [BOOK indicator? $i]
dict with flags {}
lappend mimages \
[list $i $used $even $content $attention]
}
after 0 $args
return
}
proc m/get {imgpath} {
set image [BOOK medium? $imgpath]
set flags [BOOK indicator? $imgpath]
dict with flags {}
return [list $image $used $even $content $attention $orientation]
}
proc m/set {imgpath used even content attention orientation} {
#puts "m/set |$imgpath i$used e$even c$content a$attention|"
BOOK indicator $imgpath \
[dict create \
used $used \
even $even \
content $content \
attention $attention \
orientation $orientation]
return
}
proc m/list {} {
global mimages
return [list [BOOK images-all] $mimages]
}
# # ## ### ##### ######## ############# #####################
main
exit
|
Added bin/bookflow-gen-medium.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/usr/bin/env tclsh
# -*- tcl -*-
# # ## ### ##### ######## ############# #####################
# Go through the images in the project and scale them down to a medium
# size 'thumbnail' (640x480) for display in GUI apps.
package require Tcl 8.5
::apply {{selfdir} {
# selfdir == bindir
lappend ::auto_path [file dirname $selfdir]/lib
return
}} [file dirname [file normalize [info script]]]
# TODO: Restrict to images of a certain size and/or make (camera type).
package require bookflow::project
package require crimp::tk ;# photo -> crimp
package require crimp::ppm ;# crimp -> writing ppm
package require img::jpeg ;# img -> photo
package require crimp ;# resizing
# XXX TODO: crimp::jpeg
# Disable anything from the GUI, there is nothing.
wm withdraw .
proc mi {} { return
set mi [split [memory info] \n]
return [list [lindex $mi 3 3] [lindex $mi 5 3]]
}
# # ## ### ##### ######## ############# #####################
proc main {} {
if {[catch {
cmdline
generate-medium
complete-project
} msg]} {
puts stderr $msg
exit 1
}
return
}
proc cmdline {} {
global argv argv0 w h
if {([llength $argv] > 2) && ([llength $argv] < 1)} {
puts stderr "Usage: $argv0 w ?projectdir?"
exit 1
}
set argv [lassign $argv w]
if {[llength $argv] == 1} {
cd [lindex $argv 0]
}
# Open the project file.
bookflow::project BOOK [pwd]/BOOKFLOW
set h [expr {3*$w/4}]
return
}
proc generate-medium {} {
global w h
set images [BOOK images-all]
if {![llength $images]} return
set pdir [BOOK where]
set k 0
set n [llength $images]
foreach image $images {
incr k
tell "\rMedium $image \[$k/$n\]"
# Load image, by way of Tk photo - crimp currently doesn't
# have a jpeg reader, yet.
tell " /load"
image create photo IMAGE -file $pdir/$image
set i [crimp convert 2rgb [crimp read tk IMAGE]]
image delete IMAGE
# We blur the image before scaling it down, so that the
# resampler has (indirect) access to the larger environment
# the pxel is composed of, and not just the 4 corners around
# the origin point.
# What sigma do we need ? This is scale dependent.
# Sigma is a third of the factor we are scaling down by.
# Because the factor gives us the radius of the environment,
# and for a given sigma the effective filter radius is 3 times
# that, conversely making sigma a third of the radius.
set iw [crimp width $i]
set sigma [expr {double($i)/(3*$w)}]
tell /blur ; set i [blur $i]
tell /resize ; set i [crimp::resize $i $w $h]
tell /write
crimp write 2file ppm-raw [BOOK medium-path $image] $i
tell " OK [mi]"
}
puts stderr ""
return
}
proc blur {i} {
set res {}
foreach c [crimp split $i] {
tell *
set c [crimp convert 2float $c]
set c [crimp gaussian_blur_float $c $sigma]
set c [crimp convert 2grey8 $c]
lappend res $c
}
return [crimp join 2rgb {*}$res]
}
proc complete-project {} {
BOOK destroy
return
}
proc tell {text} {
puts -nonewline stderr $text
flush stderr
}
# # ## ### ##### ######## ############# #####################
main
exit
|
Added bin/bookflow-match.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
|
#!/usr/bin/env tclsh
# -*- tcl -*-
# # ## ### ##### ######## ############# #####################
# Take the current project for the user to go over the images and
# match left and right sides of a double page to each other. As part
# of that image flags may be changed, i.e bad images removed and/or
# reoriented. Uses the upright images for display.
# Additional concepts here:
# - Blank pages : Content pgages without content. Can be excluded from
# any future processing.
#
# - Missing pages : Pages which should be present, but were either not
# photographed, or whose page images are not good
# enough for further processing. In the latter case
# the origin image will be marked as 'not used'.
#
# - Order : Before this point page order was heuristically derived
# from the image order, and the left/right annotations. From
# now on image and page order are known exactly.
# # ## ### ##### ######## ############# #####################
package require Tcl 8.5
::apply {{selfdir} {
# selfdir == bindir
lappend ::auto_path [file dirname $selfdir]/lib
return
}} [file dirname [file normalize [info script]]]
# TODO: Restrict to images of a certain size and/or make (camera type).
package require Tk 8.5
package require widget::toolbar
package require widget::statusbar
package require widget::scrolledwindow
# Would prefer to have a widget::progressbar
package require BWidget ; ProgressBar::use
package require bookflow::project
package require crimp::tk ;# crimp -> photo
package require crimp::ppm ;# crimp -> reading ppm
# XXX TODO: crimp::jpeg
package require action
package require famfamfam::silk
package require tooltip
# # ## ### ##### ######## ############# #####################
proc main {} {
if {[catch {
cmdline
view
controller
model
} msg]} {
puts $::errorInfo
puts stderr $msg
exit 1
}
# Begin event loop, and interaction
vwait ::forever
return
}
proc cmdline {} {
global argv argv0
if {[llength $argv] > 1} {
puts stderr "Usage: $argv0 ?projectdir?"
exit 1
}
if {[llength $argv] == 1} {
cd [lindex $argv 0]
}
# Open the project file.
bookflow::project BOOK [pwd]/BOOKFLOW
return
}
# # ## ### ##### ######## ############# #####################
## View
proc view {} {
wm withdraw .
view/widgets
view/layout
wm deiconify .
return
}
proc view/widgets {} {
# listbox, left or right, of all images (fast switching).
# various labels for status icons
# label displaying the current page image
# buttons: next, previous, first, last, exit - toolbar
# statusbar - messages ...
::widget::toolbar .tools
::widget::statusbar .status
ttk::label .status.message \
-textvariable ::vstatus \
-width 1 -anchor w
ProgressBar .status.progress \
-variable ::vprogress \
-type infinite -orient horizontal \
-bd 1 -relief sunken
widget::scrolledwindow .lsw -borderwidth 1 -relief sunken
listbox .limages \
-listvariable ::vimages(lpage) \
-selectmode single
widget::scrolledwindow .rsw -borderwidth 1 -relief sunken
listbox .rimages \
-listvariable ::vimages(rpage) \
-selectmode single
label .left -bd 5
label .right -bd 5
ttk::label .lorient
ttk::label .lleft
ttk::label .lright
ttk::label .lattention
ttk::label .ldropped
ttk::label .rorient
ttk::label .rleft
ttk::label .rright
ttk::label .rattention
ttk::label .rdropped
#view/tool/add arrow_left |<-- First first
#view/tool/add arrow_left <-- Previous previous
#view/tool/add arrow_right --> Next next
#view/tool/add arrow_right -->| Last last
view/tool/space
view/tool/add asterisk_orange Exit Exit exit
view/tag .
return
}
proc view/tag {w} {
bindtags $w [list _self {*}[bindtags $w]]
foreach c [winfo children $w] {
view/tag $c
}
return
}
proc view/tool/add {image label hint action args} {
set cmd {}
lappend cmd .tools add button $label
lappend cmd -text $label
lappend cmd -command [list action invoke $action] {*}$args
if {$image ne {}} {
lappend cmd -image [famfamfam silk get $image]
}
{*}$cmd
set w [.tools itemid $label]
tooltip::tooltip $w $hint
after 0 [list action link $action $w]
return
}
proc view/tool/space {} {
.tools add space ____ -separator 1
return
}
proc view/layout {} {
global lpage rpage
.lsw setwidget .limages
.rsw setwidget .rimages
.status add .status.message -weight 1
.status add .status.progress
grid columnconfigure . 0 -weight 0
grid columnconfigure . 1 -weight 0
grid columnconfigure . 2 -weight 0
grid columnconfigure . 3 -weight 0
grid columnconfigure . 4 -weight 0
grid columnconfigure . 5 -weight 1
grid columnconfigure . 6 -weight 0
grid columnconfigure . 7 -weight 0
grid columnconfigure . 8 -weight 1
grid columnconfigure . 9 -weight 0
grid columnconfigure . 10 -weight 0
grid columnconfigure . 11 -weight 0
grid columnconfigure . 12 -weight 0
grid columnconfigure . 13 -weight 0
grid rowconfigure . 0 -weight 0
grid rowconfigure . 1 -weight 0
grid rowconfigure . 2 -weight 0
grid rowconfigure . 3 -weight 1
grid rowconfigure . 4 -weight 0
view/layout/at .tools 0 0 1 14
view/layout/at .lsw 1 0 3 1
view/layout/at .lorient 1 1 1 1
view/layout/at .lleft 1 2 1 1
view/layout/at .lattention 1 3 1 1
view/layout/at .ldropped 1 4 1 1
view/layout/at .lright 1 6 1 1
view/layout/at .left 3 1 1 6
view/layout/at .right 3 7 1 6
view/layout/at .rorient 1 7 1 1
view/layout/at .rleft 1 8 1 1
view/layout/at .rattention 1 9 1 1
view/layout/at .rdropped 1 10 1 1
view/layout/at .rright 1 12 1 1
view/layout/at .rsw 1 13 3 1
view/layout/at .status 4 0 1 14
set lpage(images) .limages
set lpage(orient) .lorient
set lpage(left) .lleft
set lpage(attention) .lattention
set lpage(dropped) .ldropped
set lpage(right) .lright
set lpage(page) .left
set rpage(images) .rimages
set rpage(orient) .rorient
set rpage(left) .rleft
set rpage(attention) .rattention
set rpage(dropped) .rdropped
set rpage(right) .rright
set rpage(page) .right
return
}
proc view/layout/at {widget row col r c} {
grid $widget -row $row -column $col \
-sticky swen -rowspan $r -columnspan $c
return
}
# # ## ### ##### ######## ############# #####################
# # ## ### ##### ######## ############# #####################
# View. Entrypoints for the controller.
proc view/status {text} {
set ::vstatus $text
return
}
proc view/progress/tick {} {
.status add .status.progress
incr ::vprogress
return
}
proc view/progress/reset {} {
catch { .status remove .status.progress }
set ::vprogress 0
return
}
proc view/show {side select index image used even content attention orientation} {
global oicon
upvar #0 $side w
#puts "v/s |$select $index $image i$used e$even c$content a$attention|"
catch { image delete [$w(page) cget -image] }
$w(page) configure -image $image
if {$attention} {
$w(attention) configure -image [famfamfam silk get exclamation]
} else {
$w(attention) configure -image {}
}
if {$used} {
$w(dropped) configure -image {}
} else {
$w(dropped) configure -image [famfamfam silk get cross]
}
if {$even} {
$w(left) configure -image [famfamfam silk get asterisk_yellow]
$w(right) configure -image {}
} else {
$w(left) configure -image {}
$w(right) configure -image [famfamfam silk get asterisk_yellow]
}
$w(orient) configure -image [famfamfam silk get $oicon($orientation)]
if {$content} { ; # content page
$w(page) configure -bg blue
} else { ; # cover page
$w(page) configure -bg green
}
# List visualization of the flags
$w(images) itemconfigure $index \
{*}[view/list/flags $used $even $content $attention $orientation]
# Modify list
if {$select} {
#puts XS=$index
$w(images) selection clear 0 end
$w(images) selection set $index
$w(images) activate $index
c/selection $side
}
$w(images) see $index
return
}
proc view/show/none {side} {
upvar #0 $side w
image delete [$w(page) cget -image]
$w(page) configure -image {} -bg black
$w(orient) configure -image {}
$w(attention) configure -image {}
$w(dropped) configure -image {}
$w(right) configure -image {}
$w(left) configure -image {}
return
}
proc view/list/fill {side images iflags} {
upvar #0 $side w
upvar #0 vimages($side) vimages
set vimages $images
set k 0
foreach item $iflags {
lassign $item _ used even content attention orientation
$w(images) itemconfigure $k \
{*}[view/list/flags $used $even $content $attention $orientation]
incr k
}
return
}
proc view/list/flags {used even content attention orientation} {
set options {}
if {$content} {
lappend options -fg black
} else {
lappend options -fg green
}
if {!$used} {
lappend options -bg red
} elseif {$attention} {
lappend options -bg yellow
} else {
lappend options -bg white
}
return $options
}
proc view/selection {side} {
upvar #0 $side w
return [$w(images) curselection]
}
# # ## ### ##### ######## ############# #####################
## Controller
proc controller {} {
c/actions
c/bindings
after 0 c/launch
return
}
proc c/actions {} {
action define exit ::exit
action define left/next c/show/next lpage
action define left/previous c/show/previous lpage
action define left/first c/show/first lpage
action define left/last c/show/last lpage
action define right/next c/show/next rpage
action define right/previous c/show/previous rpage
action define right/first c/show/first rpage
action define right/last c/show/last rpage
#action define attention c/toggle/attention
#action define drop c/toggle/used
#action define front c/mark/cover_front
#action define back c/mark/cover_back
#action define left c/mark/even 1
#action define right c/mark/even 0
#action define cover c/mark/content 0
#action define page c/mark/content 1
#action define east c/orient east
#action define south c/orient south
#action define west c/orient west
#action define north c/orient north
return
}
proc c/bindings {} {
# Leave
bind _self <q> {action invoke exit ; break}
# Navigation, Left
bind _self <a> {action invoke left/previous ; break}
bind _self <s> {action invoke left/next ; break}
bind _self <w> {action invoke left/first ; break}
bind _self <z> {action invoke left/last ; break}
# Navigation, Right
bind _self <k> {action invoke right/previous ; break}
bind _self <l> {action invoke right/next ; break}
bind _self <o> {action invoke right/first ; break}
bind _self <m> {action invoke right/last ; break}
# Selection
bind .limages <<ListboxSelect>> {c/selection lpage}
bind .rimages <<ListboxSelect>> {c/selection rpage}
# Flags
#bind _self <exclam> {action invoke attention ; break}
#bind _self <d> {action invoke drop ; break}
#bind _self <f> {action invoke front ; break}
#bind _self <b> {action invoke back ; break}
#bind _self <l> {action invoke left ; break}
#bind _self <r> {action invoke right ; break}
#bind _self <c> {action invoke cover ; break}
#bind _self <p> {action invoke page ; break}
# Orientation
#bind _self <e> {action invoke east ; break}
#bind _self <s> {action invoke south ; break}
#bind _self <w> {action invoke west ; break}
#bind _self <n> {action invoke north ; break}
return
}
proc c/launch {} {
action disable
action enable exit
m/initialize c/ready
return
}
proc c/ready {} {
global cimages cchosen
set cchosen(lpage) {}
set cchosen(rpage) {}
lassign [m/list/left] cimages(lpage) liflags
lassign [m/list/right] cimages(rpage) riflags
view/list/fill lpage $cimages(lpage) $liflags
view/list/fill rpage $cimages(rpage) $riflags
c/show/first lpage
c/show/first rpage
after 0 {action enable}
return
}
# # ## ### ##### ######## ############# #####################
## Controller state, arrays, indexed by lpage, and rpage
global cimages ; # list of shown images, left side
global cchosen ; # index of the selected images
global cshown ; # index of the shown image
global ccurrent ; # path of the shown image
global cflags ; # flags of the shown image
# # ## ### ##### ######## ############# #####################
# # ## ### ##### ######## ############# #####################
# Implementations for the various actions
proc c/selection {side} {
upvar #0 cchosen($side) cchosen
set current [view/selection $side]
#puts C=$current
#puts S=$cchosen
if {$current eq $cchosen} return
set cchosen $current
#puts S*$cchosen
if {[llength $cchosen]} {
c/show $side [lindex $cchosen 0] 0
} else {
c/show/none $side
}
return
}
proc c/load {side index} {
upvar #0 \
cshown($side) cshown \
ccurrent($side) ccurrent \
cimages($side) cimages \
cflags($side) cflags
# Locate image by index, then translate index (possibly symbolic,
# or relative) to a proper integer number.
set ccurrent [lindex $cimages $index]
set cshown [lsearch -exact $cimages $ccurrent]
set cflags [lassign [m/get $ccurrent] image]
return
}
proc c/show {side index {select 1}} {
upvar #0 \
cshown($side) cshown \
ccurrent($side) ccurrent \
cimages($side) cimages \
cflags($side) cflags
# Locate image by index, then translate index (possibly symbolic,
# or relative) to a proper integer number.
set ccurrent [lindex $cimages $index]
set cshown [lsearch -exact $cimages $ccurrent]
set cflags [lassign [m/get $ccurrent] image]
view/show $side $select $cshown $image {*}$cflags
view/status [expr {1+$cshown}]/[llength $cimages]
return
}
proc c/show/none {side} {
view/show/none $side
view/status {}
return
}
proc c/show/first {side} {
c/show $side 0
return
}
proc c/show/last {side} {
c/show $side end
return
}
proc c/show/next {side} {
c/show $side [c/advance $side]
return
}
proc c/show/previous {side} {
c/show $side [c/advance $side -1]
return
}
proc c/advance {side {step 1}} {
upvar #0 \
cshown($side) cshown \
cimages($side) cimages
set n $cshown
incr n $step
if {$n < 0} {
set n [llength $cimages]
incr n -1
} elseif {$n >= [llength $cimages]} {
set n 0
}
return $n
}
# XXX
proc c/apply/selection {label script} {
global cchosen cflags ccurrent cshown
if {![llength $cchosen]} return
action disable
set saved $cshown
set count 0
set max [llength $cchosen]
foreach sel $cchosen {
view/status "\[$label [expr {1+$sel}]\] [incr count]/$max"
view/progress/tick
update
c/load $sel
lassign $cflags u e c a o
eval $script
m/set $ccurrent $u $e $c $a $o
c/load $sel
}
c/show $saved 0
action enable
view/progress/reset
return
}
# XXX
proc c/toggle/attention {} {
c/apply/selection {toggle attention} {
set a [expr {!$a}]
}
return
}
# XXX
proc c/toggle/used {} {
c/apply/selection {toggle used} {
set u [expr {!$u}]
}
return
}
# XXX
proc c/mark/cover_front {} {
c/apply/selection {front cover/west} {
set e 0 ; # odd == right == front
set c 0 ; # cover
set o 2 ; # west
}
return
}
# XXX
proc c/mark/cover_back {} {
c/apply/selection {back cover/east} {
set e 1 ; # even == left == back
set c 0 ; # cover
set o 0 ; # east
}
return
}
# XXX
proc c/mark/even {even} {
# Note: orientation derived from left/right
# Explicit orientation must be done after l/r classification
global omap
set w [expr {$even ? "left/even" : "right/odd"}]
set theorient [expr {$even ? "east" : "west"}]
set orient $omap($theorient)
c/apply/selection $w/$theorient {
upvar 1 even even orient orient
set e $even
set o $orient
}
return
}
# XXX
proc c/mark/content {page} {
set w [expr {$page ? "content" : "cover"}]
c/apply/selection $w {
upvar 1 page page
set c $page
}
return
}
array set omap {
0 0 east 0
1 1 south 1
2 2 west 2
3 3 north 3
}
array set oicon {
0 arrow_right east arrow_right
1 arrow_down south arrow_down
2 arrow_left west arrow_left
3 arrow_up north arrow_up
}
# XXX
proc c/orient {theorient} {
global omap
set orient $omap($theorient)
c/apply/selection $theorient {
upvar 1 orient orient
set o $orient
}
return
}
# # ## ### ##### ######## ############# #####################
## Model
proc model {} {
# nothing at the moment
return
}
proc m/initialize {args} {
after 0 $args
return
}
proc m/get {imgpath} {
set image [BOOK upright? $imgpath]
set flags [BOOK indicator? $imgpath]
dict with flags {}
return [list $image $used $even $content $attention $orientation]
}
proc m/set {imgpath used even content attention orientation} {
#puts "m/set |$imgpath i$used e$even c$content a$attention|"
BOOK indicator $imgpath \
[dict create \
used $used \
even $even \
content $content \
attention $attention \
orientation $orientation]
return
}
proc m/list/left {} {
set mimages {}
set images [BOOK images-left]
foreach i $images {
set flags [BOOK indicator? $i]
dict with flags {}
lappend mimages \
[list $i $used $even $content $attention]
}
return [list $images $mimages]
}
proc m/list/right {} {
set mimages {}
set images [lreverse [BOOK images-right]]
foreach i $images {
set flags [BOOK indicator? $i]
dict with flags {}
lappend mimages \
[list $i $used $even $content $attention]
}
return [list $images $mimages]
}
# # ## ### ##### ######## ############# #####################
main
exit
|
Added bin/bookflow-setup.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/usr/bin/env tclsh
# -*- tcl -*-
# # ## ### ##### ######## ############# #####################
# Scan the current directory for jpeg files and use them to initialize
# a book flow project.
package require Tcl 8.5
::apply {{selfdir} {
# selfdir == bindir
lappend ::auto_path [file dirname $selfdir]/lib
return
}} [file dirname [file normalize [info script]]]
# TODO: Restrict to images of a certain size and/or make (camera type).
package require fileutil
package require fileutil::traverse
package require jpeg
package require bookflow::project
# # ## ### ##### ######## ############# #####################
proc main {} {
if {[catch {
set imagedir [cmdline]
set images [find-images $imagedir]
generate-thumbnails $imagedir $images
complete-project
} msg]} {
puts stderr $msg
exit 1
}
return
}
proc cmdline {} {
global argv argv0
if {[llength $argv] > 1} {
puts stderr "Usage: $argv0 ?image-directory?"
exit 1
} elseif {[llength $argv] == 1} {
lassign $argv imagedir
} else {
set imagedir [pwd]
}
set imagedir [file dirname [file normalize $imagedir/___]]
# Set the project file up.
set pfile [pwd]/BOOKFLOW
puts stderr "Project file @ $pfile"
bookflow::project new $pfile $imagedir
bookflow::project BOOK $pfile
return $imagedir
}
proc find-images {imagedir} {
set here [pwd]
scan-init "Scan directory @ $imagedir : "
set images [lsort -dict [scan-path $imagedir]]
scan-done
puts stderr "Updating project with images: [llength $images]"
if {[llength $images]} {
BOOK add $images
}
return $images
}
proc generate-thumbnails {imagedir images} {
if {![llength $images]} return
set k 0
set n [llength $images]
foreach image $images {
incr k
puts -nonewline stderr "\rThumbnail $image \[$k/$n\]"
flush stderr
set thumb [jpeg::getThumbnail $imagedir/$image]
if {$thumb eq {}} {
puts -nonewline stderr " MISSING"
flush stderr
} else {
#BOOK thumbnail $image $thumb
file mkdir thumb
fileutil::writeFile -encoding binary -translation binary \
thumb/[file tail $image] $thumb
puts -nonewline stderr " OK"
flush stderr
}
}
puts stderr ""
return
}
proc complete-project {} {
puts stderr "Setup complete"
BOOK destroy
return
}
# # ## ### ##### ######## ############# #####################
# Frontend - Scanning
proc scan-init {prefix} {
global nfiles nimages pingprefix
set nfiles 0
set nimages 0
set pingprefix $prefix
return
}
proc scan-done {} {
global nfiles nimages
if {!$nfiles} return ; puts ""
#puts stderr [expr {$nfiles ? "\n":""}]
#___________________________________________
#puts stderr "\#Scanned: $nfiles, found $nimages"
}
proc scan-ping-file {} {
global nfiles nimages pingprefix
incr nfiles
puts -nonewline stderr \r$pingprefix$nfiles/$nimages
flush stderr
return
}
proc scan-ping-image {} {
global nfiles nimages pingprefix
incr nimages
puts -nonewline stderr \r$pingprefix$nfiles/$nimages
flush stderr
return
}
proc only-files {f} {
if {![file isfile $f]} { return 0 }
#if {![file size $f]} { return 0 }
#if {![jpeg::isJPEG $f]} { return 0 }
return 1
}
proc scan-path {path} {
set path [file dirname [file normalize $path/___]]
fileutil::traverse T $path -filter only-files
set result {}
T foreach f {
scan-ping-file
if {![jpeg::isJPEG $f]} continue
scan-ping-image
lappend result [fileutil::stripPath $path $f]
}
T destroy
return $result
}
# # ## ### ##### ######## ############# #####################
main
exit
|
Added bin/bookflow-upright.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/usr/bin/env tclsh
# -*- tcl -*-
# # ## ### ##### ######## ############# #####################
# Go through the medium images in the project and rotate them so that
# they are upright.
# NOTE That this code is NOT touching the original images at all.
# That is for a later phase when we have all the data needed to extract
# the content area of each page (i.e. rotation, chop, and warp data).
package require Tcl 8.5
::apply {{selfdir} {
# selfdir == bindir
lappend ::auto_path [file dirname $selfdir]/lib
return
}} [file dirname [file normalize [info script]]]
# TODO: Restrict to images of a certain size and/or make (camera type).
package require bookflow::project
package require fileutil
package require crimp::ppm ;# reading ppm -> crimp -> writing ppm
package require crimp ;# rotation
proc mi {} { return
set mi [split [memory info] \n]
return [list [lindex $mi 3 3] [lindex $mi 5 3]]
}
# # ## ### ##### ######## ############# #####################
proc main {} {
if {[catch {
cmdline
rotate-upright
complete-project
} msg]} {
puts $::errorInfo
puts stderr $msg
exit 1
}
return
}
proc cmdline {} {
global argv argv0
if {[llength $argv] > 1} {
puts stderr "Usage: $argv0 ?projectdir?"
exit 1
}
if {[llength $argv] == 1} {
cd [lindex $argv 0]
}
# Open the project file.
bookflow::project BOOK [pwd]/BOOKFLOW
return
}
array set omap {
0 east
1 south
2 west
3 north
}
proc rotate-upright {} {
global omap
set images [BOOK images-all]
if {![llength $images]} return
set pdir [BOOK where]
set k 0
set n [llength $images]
foreach image $images {
incr k
tell "\rUpright $image \[$k/$n\]"
set flags [BOOK indicator? $image]
dict with flags {}
set orientation $omap($orientation)
set src [BOOK medium-path $image]
set dst [BOOK upright-path $image]
# Nothing to be done but copying if the image is upright
# already.
if {$orientation eq "north"} {
tell "/copy "
file link -hard $dst $src
#file copy $src $dst
continue
}
# Load image, by way of Tk photo - crimp currently doesn't
# have a jpeg reader, yet.
tell " /load"
set i [crimp read ppm [fileutil::cat -translation binary $src]]
tell /rotate/$orientation
switch -exact -- $orientation {
east {
# 90 counter clockwise
tell /-90
set i [crimp rotate ccw $i]
}
south {
# 180 any direction
tell /180
set i [crimp rotate half $i]
}
west {
# 90 clock wise
tell /90
set i [crimp rotate cw $i]
}
default {
error "internal bad orientation $orientation"
}
}
tell /write
crimp write 2file ppm-raw $dst $i
tell " OK [mi]"
}
puts stderr ""
return
}
proc complete-project {} {
BOOK destroy
return
}
proc tell {text} {
puts -nonewline stderr $text
flush stderr
}
# # ## ### ##### ######## ############# #####################
main
exit
|
Added bin/bookflow-where.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/usr/bin/env tclsh
# -*- tcl -*-
# # ## ### ##### ######## ############# #####################
# Inspect the project database and determine the location of the image
# directory.
package require Tcl 8.5
::apply {{selfdir} {
# selfdir == bindir
lappend ::auto_path [file dirname $selfdir]/lib
return
}} [file dirname [file normalize [info script]]]
package require bookflow::project
# # ## ### ##### ######## ############# #####################
proc main {} {
if {[catch {
cmdline
puts [BOOK where]
BOOK destroy
} msg]} {
puts stderr $msg
exit 1
}
return
}
proc cmdline {} {
global argv argv0
if {[llength $argv] > 1} {
puts stderr "Usage: $argv0 ?projectdir?"
exit 1
}
if {[llength $argv] == 1} {
cd [lindex $argv 0]
}
# Open the project file.
bookflow::project BOOK [pwd]/BOOKFLOW
return
}
# # ## ### ##### ######## ############# #####################
main
exit
|
Added bin/fixup-add-match.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/usr/bin/env tclsh
# -*- tcl -*-
#########################
# Fix the schema of an existing bookflow database to contain the
# tables necessary for handling double-pages, i.e. left/right
# matchup.
lappend auto_path [file dirname [file dirname [file normalize [info script]]]]/lib
# TODO: Restrict to images of a certain size and/or make (camera type).
package require Tcl 8.5
package require fileutil
package require fileutil::traverse
package require jpeg
package require bookflow::project
#########################
proc main {} {
if {[catch {
cmdline
fix
} msg]} {
puts stderr $msg
exit 1
}
return
}
proc cmdline {} {
global argv argv0
if {[llength $argv] > 1} {
puts stderr "Usage: $argv0 ?projectdir?"
exit 1
}
if {[llength $argv] == 1} {
cd [lindex $argv 0]
}
# Open the project file.
sqlite3 BOOK [pwd]/BOOKFLOW
return
}
proc fix {} {
BOOK transaction {
BOOK eval {
-- Information about all double-pages, i.e. spreads in the
-- project. I.e which left and right images belong together, how they
-- are ordered, where pieces are missing or blank.
CREATE TABLE spread (
-- Basics: Id of the double page aka page spread, and the ordinal
-- specifying the ordering of spreads. Separating these two allows
-- changes to the ordering without regard to future references to
-- the table.
pid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ord INTEGER NOT NULL UNIQUE
-- The information about the spread, i.e. the left and right
-- images, and the page number of the spread (which is always
-- even, and thus is also always the page number of the left
-- image). Both image references can be NULL, indicating a missing
-- or blank page. The flags are used to distinguish the two cases.
left INTEGER REFERENCES image,
right INTEGER REFERENCES image,
page TEXT UNIQUE,
lstatus INTEGER NOT NULL REFERENCES pagestatus,
rstatus INTEGER NOT NULL REFERENCES pagestatus
);
-- Helper table for self-description. Names/labels for the image
-- orientations. Fixed content. Note: The order of orientation is
-- following the path of the sun in a day.
CREATE TABLE orientation (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
);
INSERT INTO orientation VALUES (0,'east');
INSERT INTO orientation VALUES (1,'south');
INSERT INTO orientation VALUES (2,'west');
INSERT INTO orientation VALUES (3,'north');
-- Helper table for self-description. Names/labels for the page stati in a spread.
-- Fixed content.
CREATE TABLE pagestatus (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
);
INSERT INTO pagestatus VALUES (0,'ok');
INSERT INTO pagestatus VALUES (1,'blank');
INSERT INTO pagestatus VALUES (2,'missing');
}
}
rename BOOK {}
return
}
#########################
main
exit
|
Added bin/fixup-add-orientation.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/usr/bin/env tclsh
# -*- tcl -*-
#########################
# Scan the current directory for jpeg files and use them to initialize
# a book flow project.
lappend auto_path [file dirname [file dirname [file normalize [info script]]]]/lib
# TODO: Restrict to images of a certain size and/or make (camera type).
package require Tcl 8.5
package require fileutil
package require fileutil::traverse
package require jpeg
package require bookflow::project
#########################
proc main {} {
if {[catch {
cmdline
fix
} msg]} {
puts stderr $msg
exit 1
}
return
}
proc cmdline {} {
global argv argv0
if {[llength $argv] > 1} {
puts stderr "Usage: $argv0 ?projectdir?"
exit 1
}
if {[llength $argv] == 1} {
cd [lindex $argv 0]
}
# Open the project file.
bookflow::project BOOK [pwd]/BOOKFLOW
return
}
proc fix {} {
set db [BOOK db]
$db transaction {
$db eval {
ALTER TABLE image
ADD COLUMN orientation
INTEGER NOT NULL DEFAULT 0; -- east
-- UPDATE image
-- SET orientation = 0 -- east
-- WHERE even = 0; -- right
UPDATE image
SET orientation = 2 -- west
WHERE even = 1; -- left
}
}
BOOK destroy
return
}
#########################
main
exit
|
Deleted bookflow.
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
|
#!/bin/sh
## -*- tcl -*- \
exec tclsh "$0" ${1+"$@"}
# # ## ### ##### ######## ############# #####################
## Copyright (c) 2010 Andreas Kupries.
#
# This software is BSD licensed.
# # ## ### ##### ######## ############# #####################
## Command line application wrapped around the flow packages.
# # ## ### ##### ######## ############# #####################
## Requirements, extended package management for local packages.
lappend auto_path [file normalize [file join [file dirname [info script]] lib]]
#puts stdout *\t[join $::auto_path \n*\t]
package require Tcl 8.5 ; # Required runtime.
# # ## ### ##### ######## ############# #####################
## Global settings for tracing.
package require Thread
package require debug
::apply {{} {
set parts {}
append parts {[thread::id] | }
append parts {[clock format [clock seconds]] | }
append parts {[format %3d [info level]] | }
append parts {[string repeat { } [info level]] | }
debug prefix :: $parts
return
} ::}
debug off bookflow
#debug on bookflow
Debug.bookflow {Starting the application...}
# # ## ### ##### ######## ############# #####################
package require bookflow ; # Main functionality.
# # ## ### ##### ######## ############# #####################
## Execution
bookflow run $argv
exit 0
# # ## ### ##### ######## ############# #####################
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Added build.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
#!/bin/sh
# -*- tcl -*- \
exec tclsh "$0" ${1+"$@"}
set me [file normalize [info script]]
proc main {} {
global argv
if {![llength $argv]} { set argv help}
if {[catch {
eval _$argv
}]} usage
exit 0
}
set packages {
{bfp bfp.tcl}
}
proc usage {{status 1}} {
global errorInfo
if {($errorInfo ne {}) &&
![string match {invalid command name "_*"*} $errorInfo]
} {
puts stderr $::errorInfo
exit
}
global argv0
set prefix "Usage: "
foreach c [lsort -dict [info commands _*]] {
set c [string range $c 1 end]
if {[catch {
H${c}
} res]} {
puts stderr "$prefix$argv0 $c args...\n"
} else {
puts stderr "$prefix$argv0 $c $res\n"
}
set prefix " "
}
exit $status
}
proc +x {path} {
catch { file attributes $path -permissions u+x }
return
}
proc grep {file pattern} {
set lines [split [read [set chan [open $file r]]] \n]
close $chan
return [lsearch -all -inline -glob $lines $pattern]
}
proc version {file} {
set provisions [grep $file {*package provide*}]
#puts /$provisions/
return [lindex $provisions 0 3]
}
proc Hhelp {} { return "\n\tPrint this help" }
proc _help {} {
usage 0
return
}
proc Hrecipes {} { return "\n\tList all brew commands, without details." }
proc _recipes {} {
set r {}
foreach c [info commands _*] {
lappend r [string range $c 1 end]
}
puts [lsort -dict $r]
return
}
proc Hinstall {} { return "?destination?\n\tInstall all packages, and application.\n\tdestination = path of package directory, default \[info library\]." }
proc _install {{dst {}}} {
global packages
if {[llength [info level 0]] < 2} {
set dstl [info library]
set dsta [file dirname [file normalize [info nameofexecutable]]]
} else {
set dstl $dst
set dsta [file dirname $dst]/bin
}
# Create directories, might not exist.
file mkdir $dstl
file mkdir $dsta
foreach item $packages {
# Package: /name/
if {[llength $item] == 3} {
foreach {dir vfile name} $item break
} elseif {[llength $item] == 1} {
set dir $item
set vfile {}
set name $item
} else {
foreach {dir vfile} $item break
set name $dir
}
if {$vfile ne {}} {
set version [version [file dirname $::me]/lib/$dir/$vfile]
} else {
set version {}
}
file copy -force [file dirname $::me]/lib/$dir $dstl/${name}-new
file delete -force $dstl/$name$version
file rename $dstl/${name}-new $dstl/$name$version
puts "Installed package: $dstl/$name$version"
}
# Applications: bookflow components.
foreach f [glob -directory [file dirname $::me]/bin *] {
set fx [file tail $f]
file copy $f $dsta
+x $dsta/$fx
puts "Installed application: $dsta/$fx"
}
return
}
proc Huninstall {} { return "?destination?\n\tRemove all packages, and application.\n\tdestination = path of package directory, default \[info library\]." }
proc _uninstall {{dst {}}} {
global packages
if {[llength [info level 0]] < 2} {
set dstl [info library]
set dsta [file dirname [file normalize [info nameofexecutable]]]
} else {
set dstl $dst
set dsta [file dirname $dst]/bin
}
foreach item $packages {
# Package: /name/
if {[llength $item] == 3} {
foreach {dir vfile name} $item break
} elseif {[llength $item] == 1} {
set dir $item
set vfile {}
set name $item
} else {
foreach {dir vfile} $item break
set name $dir
}
if {$vfile ne {}} {
set version [version [file dirname $::me]/lib/$dir/$vfile]
} else {
set version {}
}
file delete -force $dstl/$name$version
puts "Removed package: $dstl/$name$version"
}
# Applications: bookflow components.
foreach f [glob -directory [file dirname $::me]/bin *] {
set fx [file tail $f]
file delete $dsta/$fx
puts "Removed application: $dsta/$fx"
}
return
}
main
|
Deleted doc/Arch.txt.
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
|
Overview
========
Bookflow is an application processing the JPEG images found in
a directory into zero or more 'books'.
The directory is also called a 'project'.
Each project may contain zero or more books.
Syntax
======
bookflow <directory> ?... range of passes, other options...?
Overall behaviour
=================
(1) If the <directory> contains a file named BOOKFLOW:
(a) Check that it is a valid bookflow state file. [R1]
Report an error, if not.
(b) Run the specified passes. [R2]
If no passes where specified, run them all. [R3]
(2) The <directory> does not contain a file named BOOKFLOW:
Scan the directory for JPEG files. The scanning is not [R4]
recursive, i.e. only images in the directory itself
count. Subdirectories and their contents are ignored.
Report an error if none are present. [R5]
Create BOOKFLOW with the found JPEG files recorded [R6]
in it.
The BOOKFLOW file will contain, per JPEG image
= Name,
= Size
= SHA1 checksum.
Proceed with (1). [R6]
Validation [R1]
===============
A valid BOOKFLOW file is a sqlite3 database. [R11]
The database contains an entry for all JPEG files [R12]
found in the directory.
"No files were added since the last bookflow run"
The database contains no entries for which there [R13]
is no JPEG file in the directory.
"No files were removed since the last bookflow run"
The SHA1 checksums recorded for a JPEG file matches [R14]
the SHA1 checksum of the file in the directory
"No files were modified since the last bookflow run"
Passes, General
===============
Each pass has three phases, namely [R21]
initialization, execution, and finalization.
Passes come in monolithic and parallel varieties. [R22]
The first means that the actions of the pass for [R23]
each image in the BOOKFLOW are tied together and
cannot be separated.
Conversely the latter means that the actions of the [R24]
pass for each image in the BOOKFLOW can be separated
from each other and performed concurrently.
If the initialization phase of a pass is run, then [R25]
this is done before its execution and finalization
phases.
If the execution phase of a pass is run, then this [R26]
is done after its initialization and before its
finalization phases.
If the finalization phase of a pass is run, then [R27]
this is done after its initialization and execution
phases.
The passes of bookflow have a fixed order, which is
specified later.
For a monolithic pass A executed before a pass B all [R28]
phases of A which are run, are run before any of the
phases of B.
For a pass A executed before a monolithic pass B all [R29]
phases of A which are run, are run before any of the
phases of B.
For a parallel pass A executed before a parallel [R210]
pass B all the phases of A which are run for a
specific image, are run before any of the phases of B
for the same image.
When performing the passes from A to B, with A a pass
coming before B in the order of passes the following
phases are run, with their order constrained by the
rules above:
The initialization phases from the first [R211]
pass to pass B.
The finalization phases from pass A to the [R212]
last pass.
The execution phases from pass A to pass B. [R213]
Passes, Bookflow
================
Bookflow uses the following passes to process
the images in the directory/project.
A. Parallel.
Compute brightness of all images.
B. Monolithic.
Sort the brightness values into 3 classes based on
their, using k-Means classification.
The classes in question are:
- marker black
- marker white
- book page
C. Parallel.
Mark all images with their class.
D. Monolithic.
Use the image names to impose an order on the images,
then use the image class information to locate the
various multi-image markers, i.e.
black/black/white - SOB Start of Book, Even pages begin.
black/white/black - MOB Middle of Book, Odd pages begin.
white/black/black - EOB End of Book.
Reclassify the images as
- marker, ignored
- book page, even images between SOB and MOB
- book page, odd images between MOB and EOB
- ignored images between EOB and SOB
images before first SOB
images after last EOB.
and separate them into books (images between SOB and EOB).
Error conditions:
- No SOB, MOB, and EOB found.
- No MOB between SOB and EOB.
E. Parallel.
Rotate the book page images upright, with the rotation
dependent on the classification as even or odd.
Note: This modifies the images in the project directory.
We have to remember this in the project so that we
won't try to rotate them later again, and we have
to update the size/checksum info.
Alternative: The rotated images are stored in a sub-directory,
and the originals are left untouched. We still remember the
information in the bookflow file so that we can skip this
action when needed.
F. Parallel.
For each image generate a downsampled copy to make the later
passes faster (less pixels to process).
G. Parallel.
Determine the DPI of all images marked as book pages.
[[ Initially: Manual assigment, via cmdline, or GUI ]].
X. Manual classification (or heuristics:): inner marker =>
ignore previous image.
X. Have special image with DPI marker (color square/circle).
Maybe even in the regular marker panels
=> black! + red circle (The white marker is already the
lightfield, we cannot interfere with that.
X. Use white markers to compute light fields, and apply them
for regularization of the book pages.
X. Book Information
per book - title
- isbn
- author (list)
- publisher
- print year
- print edition
X. Use the even/odd information per book to arrange a final
order of display (page increasing), and separate the
front/back cover pages.
X. LAT (local adaptive thresholding).
=> global histogram for global threshold (median)
=> and per-pixel histogram (median => median filter)
======================================================================
Internal achitecture (modules and their interaction)
(1) Engine and Frontends are separate packages / libraries.
Two frontends are provided
(a) A pure command line.
(b) A graphical interface.
(2) Engine and Frontend are run in different threads.
Communication is handles via thread::send.
Bulk data (images) is communicated via the filesystem,
using file names in the commands issued through 'thread::send'.
(3) The engine has to be interuptible, for the graphical frontend
able to take control at an arbitrary point.
The ability to cancel a phase in progress is required too.
This should be built, if at all possible, into the phase
support- and execution framework, i.e. the phase manager.
(4) The engine may use additional, internal, threads to
concurrently perform actions. -- Threadpool.
======================================================================
User Experience
===============
(i) Start bookflow
(a) With a single argument - Open the GUI, see (1) for continued
behaviour
(b) With no argument
Open the GUI, see (1) for continued behaviour using the
current working directory as the argument.
(c) With more than one argument.
Throw an error for the user to acknowledge and abort.
- How to decide where to show the error, GUI or stdout ?
- Or treat as case (b) ?
- Or treat as case (a), ignoring the superfluous arguments ?
Vertical notebook:
Panel 1: Images
Panel 2+: Book Information. See above.
Including just the images in the book,
sorted and ordered by page number.
Show the images as thumbnails, in a grid, dynamically resizable.
The thumbnails display has to contain markers (icon, color, etc)
to make it easy to separate chaff/wheat.
===================================================================
bookflow <=> bookflow process CWD
bookflow <dir> <=> bookflow process <dir>
bookflow process <dir>
bookflow images
bookflow books
bookflow statistics
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/architecture.dia.
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
south
box "Frontend (Thread)" "Cmdline | GUI" width [8 cm] fillcolor lightgreen
move
box "Engine (Thread)" width [8 cm] fillcolor lightyellow
group {
arrow \
from [0.33 between [[2nd last box] sw] [[2nd last box] se]] \
to [0.33 between [[last box] nw] [[last box] ne]] \
"Commands " rjust
arrow \
from [0.33 between [[last box] ne] [[last box] nw]] \
to [0.33 between [[2nd last box] se] [[2nd last box] sw]] \
" Responses" ljust
}
block {
set movelength [1 cm]
east
box "Worker-\nthread" fillcolor salmon
group { arrow <-> from [[last box] n] north }
move
box same
group { arrow <-> from [[last box] n] north }
move
box same
group { arrow <-> from [[last box] n] north }
set E [[last box] e]
set W [[3rd last box] w]
}
group {
east
arrow <-> from [[last box] e] stroke 4
box height [8 cm] width [4 cm] "Filesystem" fillcolor lightblue
arrow <-> stroke 4 from [[last block] E]
arrow <-> stroke 4 from [0.75 between [[1st box] ne] [[1st box] se]]
}
group {
west
arrow <-> from [[2nd last box] w] stroke 4
drum height [8 cm] width [4 cm] "BOOKFLOW" "(Database)" fillcolor lightblue aspect 0.1
arrow <-> stroke 4 from [[last block] W]
}
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/architecture.png.
cannot compute difference between binary files
Deleted doc/erd.dia.
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
proc t {name script args} {
block {
south
set fields [block {
circle radius 1 fillcolor red color red
eval $script
}]
box at [last block] \
width [expr {[[last block] width] + [5 mm]}] \
height [expr {[[last block] height] + [5 mm]}]
box text $name fillcolor white height [7 mm] with sw at [last box nw]
set X [[last box] e]
} {*}$args
}
proc f {type name notes args} {
set $name [text "$type :: $name ($notes)" with nw at [[last] sw] {*}$args]
}
proc n {text args} {
text "$text" textcolor red with nw at [[last] sw] {*}$args
}
proc pk {type name args} {
f $type $name [join $args {, }] textcolor blue
}
proc d {rows} {
block {
south
foreach r $rows {
block {
east
foreach c $r {
box height [7 mm] $c
}
}
}
}
}
##########################################
south
t bookflow {
f int dpi {}
}
move
t book {
pk int bid {not null, auto-increment}
f text name {unique, not null}
}
east
arrow <- bid above
set image [t image {
pk int iid {not null, auto-increment}
f text path {not null, unique}
f int bid {not null, references book}
f int ord {not null}
n "unique (bid, ord)"
}]
east
group {
arrow <- right right iid above
t is1 {
f int iid {not null}
f int sid {not null}
}
arrow right right sid above
t state1 {
pk int sid {not null}
f string label {not null, unique}
}
arrow from [[last block] X] right right right data above
d {
{0 "white"}
{1 "black"}
{2 "page"}
}
}
group {
arrow <- down down down right then right iid above
east
t is2 {
f int iid {not null}
f int sid {not null}
}
arrow right right sid above
t state2 {
pk int sid {not null}
f string label {not null, unique}
}
arrow from [[last block] X] right right right data above
d {
{ 0 "sob1" {! "black"}}
{ 1 "sob2" {! "black"}}
{ 2 "sob3" {! "white"}}
{ 3 "mob1" {! "black"}}
{ 4 "mob2" {! "white"}}
{ 5 "mob3" {! "black"}}
{ 6 "eob1" {! "white"}}
{ 7 "eob2" {! "black"}}
{ 8 "eob3" {! "black"}}
{ 9 "even" {! "page"}}
{10 "odd" {! "page"}}
{11 "none" {! "page"}}
}
}
group {
arrow <- down down down down down down right then right iid above
east
t it {
f int iid {not null}
f int tid {not null}
}
arrow right right tid above
t type {
pk int tid {not null}
f string label {not null, unique}
}
arrow from [[last block] X] right right right data above
d {
{ 0 "frontc" {! "odd"}}
{ 1 "backc" {! "even"}}
{ 2 "page" {! "page"}}
}
}
group {
arrow <- up up up right then right iid above
east
set istate [t brightness {
f int iid {not null}
f int value {not null}
}]
}
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/erd.png.
cannot compute difference between binary files
Added doc/gui-flagging-images.txt.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
Display of images in the application allowing me to change their
flags, quickly.
=================================================================
(1) Strip of images, horizontal
or Matrix of images, left to right, top to bottom
(2) Per image entry
- Not in project - Covered by red X
- In project - Freely visible
- Image of page - Blue border
- Image of cover - Green border
- Attention on image - Show icon at bottom border, exclamation in triangle, reddish
- No attention - Free bottom border
- Even image (left side) - Show icon at top left corner, yellow star
- Odd image (right side) - Show icon at top right corner, yellow star
Look for fam fam icons matching my needs, per above.
Keyboard controls of the application. No button, no menu.
=================================================================
(a) General selection keys, per treectrl bindings.
(b) q Quit application
h Hide images not in the project. Default.
u Unhide. Needed to undo dropping of images.
! Toggle attention
d Toggle image in project ((un)drop). (x)
f Mark as front cover (combo r+c)
b Mark as back cover (combo l+c)
l Mark as left/even (default)
r Mark as right/odd
c Mark as cover
p Mark as page (default)
(No separate save, all changes apply immediately)
(No undo, all changes can be undone)
(Ad x) When an image is remove its immediate left and right
non-dropped! neighbours (as determined by file name in
dictonary order) are get attention set.
|
Deleted doc/gui_book_tab.dia.
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
######################################################################
proc portrait {h args} {
box height $h width [expr {0.75*$h}] {*}$args
}
proc landscape {w args} {
box width $w height [expr {0.75*$w}] {*}$args
}
proc thumb {args} {
landscape [16 mm] "Thumb" {*}$args
}
proc sthumb {args} {
thumb {*}$args stroke 3
}
proc ellipsis {} {
move same ; circle rad [1 mm] fillcolor black
move same ; circle same
move same ; circle same
}
proc leftarrow {args} {
box {*}$args ; group {
line \
from [[[last box] ne] by [2 mm] sw] \
then [[[last box] w] by [2 mm] e] \
then [[[last box] se] by [2 mm] nw] \
to [[[last box] ne] by [2 mm] sw]
}
}
proc rightarrow {args} {
box {*}$args ; group {
line \
from [[[last box] nw] by [2 mm] se] \
then [[[last box] e] by [2 mm] w] \
then [[[last box] sw] by [2 mm] ne] \
to [[[last box] nw] by [2 mm] se]
}
}
proc bseries {args} {
block {
block {
east
portrait [9 cm] "Left page" "Odd"
move right [5 mm]
portrait [9 cm] "Right page" "Even"
}
set sl [box with s at [[[last block] n] by [5 mm] n] width [[last block] width]]
block {
east ; thumb
move right [2 mm] ; thumb
ellipsis
move same ; sthumb
move same ; sthumb
ellipsis
move same ; thumb
move same ; thumb
} with c at [[last box] c]
leftarrow with e at [[$sl w] by [2 mm] w]
rightarrow with w at [[$sl e] by [2 mm] e]
} {*}$args
}
proc wrap {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left]] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
tabB Images
tab {Book 1}
tabA ...
tabA {Book N}
line to $x
}
proc tab {{text {}}} {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm] ; line
arc rad [5 mm] cw
return
}
proc tabB {{text {}}} {
group {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm]
}
line down [15 mm]
}
proc tabA {{text {}}} {
group {
west
arc rad [5 mm] from [[2nd last arc] end]
line down [5 mm]
arc rad [5 mm] ; line ; tablabel $text up
arc rad [5 mm] cw
}
}
proc tablabel {text {dir down}} {
if {$text eq {}} return
group {
text text $text with c at [[[last line] c] by [7.5 mm] $dir]
}
return
}
######################################################################
text "Notebook Page \"Book Image Series\""
move south [1 cm]
wrap [bseries]
move
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/gui_book_tab.png.
cannot compute difference between binary files
Deleted doc/gui_framing.dia.
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
######################################################################
proc nbpage {args} {
box width [18.4 cm] height [11.5 cm] {*}$args
}
proc wrap2 {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left] color red] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
line to $x
}
proc wrap {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left]] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
tabB Images
tab {Book 1}
tabA ...
tabA {Book N}
line to $x
}
proc tab {{text {}}} {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm] ; line
arc rad [5 mm] cw
return
}
proc tabB {{text {}}} {
group {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm]
}
line down [15 mm]
}
proc tabA {{text {}}} {
group {
west
arc rad [5 mm] from [[2nd last arc] end]
line down [5 mm]
arc rad [5 mm] ; line ; tablabel $text up
arc rad [5 mm] cw
}
}
proc tablabel {text {dir down}} {
if {$text eq {}} return
group {
text text $text with c at [[[last line] c] by [7.5 mm] $dir]
}
return
}
######################################################################
text "Overall gui, image notebook + rightside action log"
move south [1 cm]
wrap2 [block {
block { wrap [nbpage "Notebook page"] }
east
move east [5 mm]
box height [[last block] height] width [6 cm] "Log of Engine Activity"
}]
move
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/gui_framing.png.
cannot compute difference between binary files
Deleted doc/gui_img_tab_a1.dia.
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
######################################################################
proc portrait {h args} {
box height $h width [expr {0.75*$h}] {*}$args
}
proc landscape {w args} {
box width $w height [expr {0.75*$w}] {*}$args
}
proc thumb {args} {
landscape [32 mm] "Thumb" {*}$args
}
proc sthumb {args} {
thumb {*}$args stroke 3
}
proc ellipsis {} {
move same ; circle rad [1 mm] fillcolor black
move same ; circle same
move same ; circle same
}
proc iseries {args} {
block {
box width [12 cm] height [9 cm]
block {
east ; thumb
move right [2 mm] ; sthumb
ellipsis
} with nw at [[[last box] nw] by [5 mm] se]
block {
east ; ellipsis
move right [2 mm] ; thumb
move right [2 mm] ; thumb
} with se at [[[last box] se] by [5 mm] nw]
} {*}$args
}
proc wrap {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left]] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
tab Images
tabA {Book 1}
tabA ...
tabA {Book N}
line to $x
}
proc tab {{text {}}} {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm] ; line
arc rad [5 mm] cw
return
}
proc tabB {{text {}}} {
group {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm]
}
line down [15 mm]
}
proc tabA {{text {}}} {
group {
west
arc rad [5 mm] from [[2nd last arc] end]
line down [5 mm]
arc rad [5 mm] ; line ; tablabel $text up
arc rad [5 mm] cw
}
}
proc tablabel {text {dir down}} {
if {$text eq {}} return
group {
text text $text with c at [[[last line] c] by [7.5 mm] $dir]
}
return
}
######################################################################
text "Notebook Page \"Image Series\" (Alternative I)"
move south [1 cm]
wrap [iseries]
move
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/gui_img_tab_a1.png.
cannot compute difference between binary files
Deleted doc/gui_img_tab_a2.dia.
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
######################################################################
proc portrait {h args} {
box height $h width [expr {0.75*$h}] {*}$args
}
proc landscape {w args} {
box width $w height [expr {0.75*$w}] {*}$args
}
proc thumb {args} {
landscape [16 mm] "Thumb" {*}$args
}
proc sthumb {args} {
thumb {*}$args stroke 3
}
proc ellipsis {} {
move same ; circle rad [1 mm] fillcolor black
move same ; circle same
move same ; circle same
}
proc leftarrow {args} {
box {*}$args ; group {
line \
from [[[last box] ne] by [2 mm] sw] \
then [[[last box] w] by [2 mm] e] \
then [[[last box] se] by [2 mm] nw] \
to [[[last box] ne] by [2 mm] sw]
}
}
proc rightarrow {args} {
box {*}$args ; group {
line \
from [[[last box] nw] by [2 mm] se] \
then [[[last box] e] by [2 mm] w] \
then [[[last box] sw] by [2 mm] ne] \
to [[[last box] nw] by [2 mm] se]
}
}
proc iseries {args} {
block {
block {
east
move right [47.5 mm]
portrait [9 cm] "Current page"
move right [47.5 mm]
}
set sl [box with s at [[[last block] n] by [5 mm] n] width [[last block] width]]
block {
east ; thumb
move right [2 mm] ; thumb
ellipsis
move same ; sthumb
ellipsis
move same ; thumb
move same ; thumb
move same ; thumb
} with c at [[last box] c]
leftarrow with e at [[$sl w] by [2 mm] w]
rightarrow with w at [[$sl e] by [2 mm] e]
} {*}$args
}
proc wrap {e} {
# e = element to wrap.
set x [[arc rad [5 mm] from [[$e sw] by [5 mm] left]] start]
line right [$e width]
arc rad [5 mm]
line up [$e height]
arc rad [5 mm]
line left [$e width]
arc rad [5 mm]
tab Images
tabA {Book 1}
tabA ...
tabA {Book N}
line to $x
}
proc tab {{text {}}} {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm] ; line
arc rad [5 mm] cw
return
}
proc tabB {{text {}}} {
group {
arc rad [5 mm] cw ; line ; tablabel $text
arc rad [5 mm] ; line down [5 mm]
arc rad [5 mm]
}
line down [15 mm]
}
proc tabA {{text {}}} {
group {
west
arc rad [5 mm] from [[2nd last arc] end]
line down [5 mm]
arc rad [5 mm] ; line ; tablabel $text up
arc rad [5 mm] cw
}
}
proc tablabel {text {dir down}} {
if {$text eq {}} return
group {
text text $text with c at [[[last line] c] by [7.5 mm] $dir]
}
return
}
######################################################################
text "Notebook Page \"Image Series\" (Alternative II)"
move south [1 cm]
wrap [iseries]
move
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/gui_img_tab_a2.png.
cannot compute difference between binary files
Deleted doc/interaction_mvc_images.txt.
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
|
Interaction between a display of multiple images (view + controller)
and a model holding the images to show.
====================================================================
The model is a container of images, i.e.:
* It holds a list of images. Note that 'list' implies an order on the images.
* It has the following information per image (all optional (*))
- name of the image, relative to the project directory
- path of the thumbnail image, relative to the project directory
- classification 0: use/ignore
- classification 1: black/white/page
- classification 2: sob/mob/eob/even/odd
- classification 3: na/content/front/back
(*) To allow the use of placeholders for missing pieces, be they
pages or the various markers.
The model broadcasts events on changes to its contents, i.e:
* An image is added
* The state of an image changes
- name becomes known
- thumbnail becomes known or changes.
- classification X becomes known or changes.
Views for a model are driven by these events, having bound to the
model and them.
Notes on the information and their constraints:
(a) An image without name is a placeholder for missing data.
(b) A placeholder has the classifications which describe the type of
the missing piece.
(c) A missing thumbnail is a temporary condition the model will
rectify as fast as possible.
(d) Classification 0 is orthogonal to the classifications 1-3. Where
the latter describe what the image is, in increasing detail, this
one tells us whether to use the image later, or not.
(e) The classifications 1, 2, and 3 are building on each other,
i.e. the higher numbered classifications can be known if and
only if the lower-numbered classifications are available. In
addition a number of constraints are put on the values restricting
the set of legal combinations.
1-unknown => 2-unknown => 3-unknown
2-sob => 1-black|1-white
2-mob => 1-black|1-white
2-eob => 1-black|1-white
2-even => 1-page
2-odd => 1-page
3-content => 2-even|2-odd
3-front => 2-odd
3-back => 2-even
3-na => 2-sob|2-mob|2-eob
Based on these constraints the legal combinations are shown
below. On the right additional notes on how the combination is
shown by a view.
c1 c2 c3 view
------------------------ --------
* unknown unknown unknown plain name, thumbnail (when present)
------------------------ --------
black unknown unknown 3 pixel wide black border
---------------- --------
sob unknown 3 pixel wide green border
na ditto
---------------- --------
mob unknown 3 pixel wide yellow border
na ditto
---------------- --------
eob unknown 3 pixel wide magenta border
na ditto
------------------------ --------
white unknown unknown 3 pixel wide salmon border
---------------- --------
sob unknown 3 pixel wide green border
na ditto
---------------- --------
mob unknown 3 pixel wide yellow border
na ditto
---------------- --------
eob unknown 3 pixel wide magenta border
na ditto
------------------------ --------
* page unknown unknown plain name, thumbnail (when present)
---------------- --------
* even unknown plain name, thumbnail (when present)
content 3 pixel wide blue border
back 3 pixel wide orange border
---------------- --------
* odd unknown plain name, thumbnail (when present)
content 3 pixel wide blue border
front 3 pixel wide orange border
------------------------ --------
The starred entries are currently visually undistinguishable.
See if the treecontrol allows for dashed and dotted borders /
rectangles around items for additional ways of distinguishing
states.
Two open issues, which are related to each other
(1) How do we communicate the order of images in the model, and
(2) How do we communicate changes to the order between images.
====================================================================
The view is also a controller, i.e. actions taken by the user are
communicated to the
- The model has to announce the presence of new images
- The model has to annonce when the thumbnail for an image is available.
- The model has to announce when the thumbnail of an image is changed.
- The model has to announce the removal of images
- The model has to announce changes to the information about an image
(status, type, ...)
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/interaction_pci.txt.
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
|
Interactions between producers, users, and invalidators of data
===============================================================
Using the handling of thumbnail images as example and template for the
pattern.
Producer
--------
(1) The producer monitors the scoreboard (take) for the appearance of
tuples matching the pattern {!THUMBNAIL *}.
When appearing the second word of the taken tuple is treated as
the path of the image I whose thumbnail is to be invalidated.
The producer cleans up all data pertaining to the thumbnail of I,
ensuring that the next time the thumbnail for I is requested it
will be full regenerated from the base data, i.e. I itself.
Part of this cleanup is the removal of the {THUMBNAIL <I>} tuple
for this image. This action triggers (5), in the user, see below.
(2) The producer monitors the scoreboard (bind missing) for queries,
i.e. patterns of tuples matching the pattern {THUMBNAIL * *}.
(Missing events trigger when a pattern to 'take' and 'wpeek'
matches no tuple at all).
When a miss is reported the second word of the reported pattern is
treated as the path of the image I whose thumbnail has been
requested but not known.
The producer generates and places a tuple {THUMBNAIL <I> <T>} into
the scoreboard, fulfilling the request, with I the path of the
image and T the path of the thumbnail image to use. The generation
of this tuple is trivial if T already exists in the filesystem, a
simply packaging up of the information. Otherwise the producer
launches a task actually generating T, using CRIMP to scale down I
to thumbnail size.
Invalidator
-----------
(3) When actions by some task or other make the contents of the
thumbnail for image I obsolete the task or other places a tuple
matching {!THUMBNAIL <I>} into the scoreboard.
This then triggers (1), in the producer, see above.
User
----
(4) When the thumbnail T of an image I is required the user asks
(wpeek) for a tuple matching {THUMBNAIL <I> *}. If a matching
tuple is present its third word is treated as the path to the
requested thumbnail.
If it is not present the query triggers (2) in the producer, see
above, causing the tuple to be generated in time.
Because of the delay possible in fulfulling the request the user
should be prepared for the possibility that by the time the
request is actually fulfilled the need for the data has passed.
(5) The user monitors the scoreboard (bind take) for the removal
of {THUMBNAIL <I> *} tuples, signaling content invalidation.
When the removal is reported, and the user still has need of the
thumbnail then (4), see above, is invoked to request an updated
and valid thumbnail.
Notes
~~~~~
(a) The image paths mentioned in the various actions above are all
relative to the project directory.
(b) The parts of the system are not restricted to a single role in the
above. For example, the producer of brightness data for the images
is also the user of greyscale conversions of same images.
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/notes.txt.
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
|
Possible scan errors
====================
duplicate pages
missing markers - insert fake marker
missing pages - insert fake (empty) page/placeholder
missing cover - insert fake cover (see fake page)
missing lightfield - synthesize
cover scanned out of order (last instead of first, or in the middle).
Heuristics
==========
detect marker
detect lightfield
synthesize lightfield
page brightness (-> grey -> mean, or hsv -> value -> mean)
page color (-> hsv -> hue -> mean)
picture orientation
detect page number => orientation cue, even/odd cue, number itself for
order
compare pages (similarity = detect duplicate)
first order by image name
crimp - ppm file - save/read HSV!
crimp - up/down sample x/y separate
auto-dpi = 6 lines/height
auto-dpi via markers (square lines - also perspective warp, global)
auto-crop
---
scan tailor mixed mode tiff image
If I flip the pure-black pixels to white, I have the graphical version
of the image. If I flip non-pure-black pixels to white, I have the
textual version of the image. Yes?
== pure black = text
== grey-scale = grey images, never going up to pure black (255)
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/phases.dia.
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
|
# -*- tcl -*- tcl.tk//DSL diagram//EN//1.0
proc mbox {args} {
box width [8 cm] fillcolor lightgreen {*}$args
}
proc pbox {args} {
box width [8 cm] fillcolor lightyellow {*}$args
}
east
drum width [4 cm] height [8 cm] aspect 0.1 "BOOKFLOW DB" fillcolor lightblue
move ; move
set p [block {
south
set movelength [1 cm]
set sd [mbox "Scan Directory" "(Implied to have an order)"]
group {
southwest
arrow
pbox "Create thumbnail"
}
southeast
arrow
set gr1 [pbox "Convert to greyscale (I)"]
south
arrow
set cb [pbox "Compute brightness"]
arrow
set cl [mbox "Classify The Brightness"]
arrow
set ci [pbox "Classify By Brightness" "MarkerB | MarkerW | Page | Unknown"]
group {
southwest
arrow down left left
set bm [pbox "Detect SOB | MOB | EOB"]
group {
south
arrow
mbox "Separate multiple books"
arrow
mbox "Separate even|odd|not pages"
group { east ; line ; arrow }
arrow
mbox "Separate cover pages" "& reorder"
}
}
group {
south
arrow
set lf [pbox "Detect light field"]
arrow
set no [pbox "Normalize background"]
arrow
pbox "Rotate upright"
arrow
pbox "Unwarp perspective"
group {
southeast ; arrow down right right
pbox "Compute DPI"
}
arrow
set gr2 [pbox "Convert to greyscale (II)"]
arrow
set re [pbox "Reduce size"]
arrow
pbox "Determine rough page borders"
arrow
mbox "Inter-page border exchange"
arrow
pbox "Finalize page borders"
arrow
pbox "Segment page" "Text | Images | Lines"
arrow
pbox "Line shape"
arrow
pbox "Unwarp lines"
}
group {
southeast
arrow down right right
set dp [pbox "Find fiducials (DPI & perspective)" "(original image)"]
south
arrow down down down down down down then down left left left left left left
}
}]
move ; move
circle radius [4 cm] fillcolor grey "ScoreBoard" "(in memory)"
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/phases.png.
cannot compute difference between binary files
Deleted doc/rescale_request_prioritization.txt.
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
|
Handling of regular images by the book manager.
===============================================
Two places/situations will request a regular sized page image.
(i) selection, i.e. when page X is selected, system gets its
image.
(ii) background pre-generation, i.e. for all images found we
request them once, to ensure that they are created if they do
not exist yet.
Of these two (i) is a high-priority thing, as the user wishes to see
the image. It is also something we must be able to cancel. I.e. when
the user switches to a different page and the image for the previously
current one has not arrived yet then this old request should either
get normal priority or not be done at all.
Situation (ii) on the other hand is something which can be defered
until after all the thumbnails have been done. This one should look
towards (i) to know which pages are already done while the user was
browsing.
The problem with (i) and cancellation is that the user is, in
principle, isolated from the internals of the producers. Miss the
requested tuple, and the producer automatically starts the generation
process. And the consumer automatically waits for the result/return
event.
As such a switch to a different image will simply make another
request, if the data was missing.
Prioritization has happen in the producer. I.e. the producer, knowing
that a particular request has priority then takes the necessary
actions to get it into the scaling tasks as fast as possible, if that
is required at all.
The dispatcher then also has to keep track of the requests waiting for
execution, so that it can take lower-priority request back to make
place for the high priority one. And putting them back when it knows
that the high-priority request is taken and executing.
... side note ... Make dataflow diagrams for the producer internals,
showing direct and indirect control flow ...
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/sb_semantics.txt.
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
|
Scoreboard API
==============
put <tuple>...
Places the specified tuples into the scoreboard.
May return before the tuples are fully stored.
May release 'take' requests waiting on a pattern matching any of the tuples.
May trigger 'added' notifications for patterns matching the tuples.
take <pattern> <cmd>
Asks the scoreboard to invoke <cmd> when a tuple matching the
<pattern> is present, with the matching tuple as argument.
At the time of invokation the tuple is removed from the
scoreboard.
Returns before <cmd> is invoked.
If no matching tuple is present the system will wait until
such a tuple exists. Possibly waiting indefinitely.
Multiple 'take' requests waiting on tuples are served in order
of arrival. I.e. the earliest request matching a tuple is
invoked, with the remainder waitng for the next tuple. As new
requests are adding to the end of this list each request R
will be served at some point if enough tuples matching its
pattern are added to the scoreboard. Matching requests coming
after R cannot pre-empt it.
May trigger 'removed' notifications, for patterns matching the
taken tuple.
May trigger 'missing' notifications, for patterns not matching
a tuple at the time of the request.
takeall <pattern> <cmd>
Like 'take', with two differences.
It doesn't wait for matching tuples to be present.
If none are there <cmd> is invoked with the empty list.
If tuples match however then all of them are removed
from the scoreboard and given to <cmd>.
May trigger 'removed' notifications for patterns matching the
taken tuples.
peek <pattern> <cmd>
Like 'takeall', except that the matching tuples are not
removed from the scoreboard. As such it will not generate
'take' notifications either.
wpeek <pattern> <cmd>
The 'waiting peek' is like peek in that it doesn't remove a
tuple matching the pattern. It is however like 'take', waiting
for the appearance of a matching tuple is no such is present
when the request is made.
bind put <pattern> <cmd>
bind take <pattern> <cmd>
bind missing <pattern> <cmd>
These methods bind a <cmd> callback to a particular action
(put/take) and tuple <pattern>. Each occurence of the action
for a tuple matching the pattern causes an invokation of the
callback.
The contents of the scoreboard are not modified.
In this manner it is possible to wait for a tuple to appear,
like 'take', but without actually removing the tuple.
Note that if a tuple is added via 'put' and immediately
'take'n two notifications may be generation, for both the
'put', and the 'take', in this order.
The 'missing' event is invoked if a 'take' or 'wpeek' had to
wait for a matching tuple, and the pattern, treated as tuple,
matched the pattern for the event.
unbind ...
Remove event bindings.
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Added doc/schema.txt.
|
|
>
|
1
|
See lib/bfp/bfp-schema.sql
|
Deleted doc/scoreboard.txt.
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
|
# -*- tcl -*-
#
# Documentation of the tuples stored in the scoreboard, their
# meanings, and associated code, i.e. creators, users, etc.
tuple {PROJECT CREATE} {
Signal from the directory scanner to the creation task to generate
a new project (database).
} {
}
tuple {PROJECT VERIFY} {
Signal from the directory scanner to the verification task to
cross-check an existing project (database).
} {
}
tuple {PROJECT ERROR <msg>} {
Message for the user interface to post.
} {
}
tuple {PROJECT SERVER <thread>} {
Access to project database is mediated by the thread with id <thread>.
} {
}
tuple {AT <path>} {
The location of the current project (directory), as absolute path.
} {
}
tuple {DATABASE <name>} {
The name/path of the database file, relative to the project directory.
Also a signal to the project database access layer to provide access.
} {
}
tuple {FILE <path>} {
Name/path of an image file found by the scanner, relative to the project
directory. Used by either creation or verification task, i.e. make
them images, or compare to current images.
} {
}
tuple {BOOK <name>} {
Name of a book found in the project (database).
} {
}
tuple {IMAGE <path> <serial> <book>} {
Name/path of a verified page image file in the project,
with reference to the book it belongs to, and a serial
number providing the ordering within the book.
} {
}
tuple {!THUMBNAIL <path> <size>} {
Signal to invalidate the <size>d thumbnail of page
image <path>.
} {
}
tuple {THUMBNAIL <path> <size> <dstpath>} {
<dstpath> is the location of the <sized>d thumbnail for
page image <path>. All paths are relative to the project
(directory).
} {
}
tuple {SCALE <path> <size> <dst>} {
Order to resize page image <path> to <size>, and store the
result in <dst>.
} {
}
tuple {!GREYSCALE <path>} {
Signal to invalidate the greyscale derivation of page
image <path>.
} {
}
tuple {GREYSCALE <path> <dstpath>} {
<dstpath> is the location of the greyscale derivation of
page image <path>. All paths are relative to the project
(directory).
} {
}
tuple {GREYCONVERT <path> <dst>} {
Order to compute the greyscale of page image <path> and
store the result in <dst>.
} {
}
tuple {!STATISTICS <path>} {
Signal to invalidate the statistics of page image <path>.
} {
}
tuple {STATISTICS <path> <stats>} {
<stats> are the statistics of page image <path>.
} {
}
tuple {STATSQ <path>} {
Order to compute the statistics of page image <path>.
} {
}
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/tasks.txt.
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
|
# -*- tcl -*-
document {
description {
Task Documentation.
Listing all tasks with the package implementing them, the
pre-conditions, i.e. scoreboard contents (tuple existence), it
triggers on, the results (new and removed tuples), again scoreboard
contents, and additional scoreboard data which is accessed during the
execution of the task.
}
task bookflow::scan {
description {
Scan the project directory, locate the project database and the
images to process. One shot task, exits after the scan is complete.
Initial task. Automatically triggered.
}
thread
trigger {}
behavior {
(1) {
action { Scan directory for database, images}
output {
add {AT <dir>}
}
}
(2) {
guard { Neither images nor project database found }
output {
add {PROJECT ERROR *}
}
}
(3) {
guard { Images found, but no project database }
output {
add {FILE *}
add {PROJECT CREATE}
}
}
(4) {
guard { Images and project database are found }
output {
add {FILE *}
add {DATABASE *}
add {PROJECT VERIFY}
}
}
}
}
task bookflow::error {
description {
Waits for other tasks to signal an error and reports it.
Continuous task.
}
event
trigger {
{PROJECT ERROR *}
}
behaviour {
(1) {
action { Report the error held by the tuple }
output {}
}
}
}
task bookflow::verify {
description {
Load the database and check its contents against
the set of images found by the scanner.
One shot task, exits after the check is done.
}
thread
trigger {
{PROJECT VERIFY}
}
behaviour {
(1) {
action {
{AT *}
{DATABASE *}
{FILE *}
Open database, load set of images known to it.
Get the set of found images.
Compare for missing and additional images.
}
}
(2) {
guard {
The set of images in the directory does not match
the set of images in the project.
}
output {
add {PROJECT ERROR *}
NOTE { --- Allow corrective action by the user ? --- }
NOTE { --- Auto-correction?
i.e. Ignore additional images
and. Mark missing images as such and ignore further.
}
}
}
(3) {
guard {
The set of images in the directory is consistent
with the set of images in the project.
}
action {
}
output {
remove {FILE *}
add {BOOK <name> <...>}
add {IMAGE <file> ...}
add {PART <book> <file>}
}
}
}
}
task bookflow::create {
description {
Create a fresh project database in the project directory
and populate it with the found images.
One shot task, exits after the creation is done.
}
thread
trigger {
{PROJECT CREATE}
}
behaviour {
(1) {
action {
{AT *}
{DATABASE *}
Get the set of found images.
Open database, write images and basic status to it.
Fill the scoreboard based on the information.
}
output {
remove {FILE *}
add {DATABASE *}
add {BOOK <name> <...>}
add {IMAGE <file> ...}
add {PART <book> <file>}
}
}
}
}
}
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted doc/user_actions.txt.
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
|
While I want bookflow to be mostly automatic when identifying pages,
markers and processing everything, writing the automatics will take
time and I wish to process the books I have now. So, some commands
have to be implemented which go towards that goal.
This actually may have another advantage. Training data. Perfectly
labeled images which can used to train some type of system for the
image classification.
Most interactivity is through the keyboard, which is generally quicker.
Key Note Command Notes
--- ---- ------- -----
(i) SPACE show next
(ii) -> cursor show next change of selection, active item
(iii) <- cursor show previous s.a.
--- ---- ------- -----
(iv) b label as black marker
(v) w label as white marker = lightfield
(vi) c label as cover (front, back automatic based on the
section we are in)
--- ---- ------- ----
The commands (iv) and (v) are enough for the system to then
automatically determine the locations of the composite markers
delimiting the various sections (garbage, even, odd), and label the
pages in the sections. The command (vi) is needed to fix the pages
which are the covers and likely mislabled as plain pages.
When all pages (for a book) are labeled we can trigger the next phase,
which
(a) places them into a separate (new) book
(b) associates each page with the nearest preceding lightfield in
imaging order.
(c) re-orders them front to back
(d) rotates the derived images (thumbnail, page display) upright
NOTE: the base images are not modified.
NOTE: this is done by invalidating the data and then using the
labels in the scaler tasks to determine the use of rotations.
NOTE: rotate after scaling, less data to handle.
A problem, we have to note somewhere which thumbnails have been
rotated, and which don't. Likely in the project database, as an
annotation.
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Added lib/bfp/bfp-schema.sql.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
-- Global information about the project.
-- A simple key/value store.
--
-- Known keys and their meaning:
-- 'path' : Absolute path to the project directory.
CREATE TABLE global (
key TEXT NOT NULL PRIMARY KEY,
value TEXT NOT NULL
);
-- Information about all images in the directory hierarchy associated
-- with the project.
CREATE TABLE image (
-- Basic information: Row id, and path to the image file, relative
-- to the project directory.
iid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL UNIQUE,
-- Various classifications, stored as booleans.
--
-- used: true for images which do belong to the project.
-- false for images whioch don't
-- content: true for images which contain book content pages
-- false for images of the book covers
-- even: true for even-numbered (*) images (right of book spine)
-- false for odd-numbered images (left of book spine)
-- attention: true for images to look closely at. Mostly because
-- nearby images where special, like !used. May indicate
-- duplicated pages or similar.
--
-- Note: even/left cover = back cover
-- odd /right cover = front cover
-- orientation: which side of the image is the upper edge of the page.
-- See table 'orientation' for the encoding
--
-- In my setup orientation can normally be derived from even,
-- i.e. left/right:
--
-- even == left => east
-- odd == right => west
used INTEGER NOT NULL DEFAULT 0,
content INTEGER NOT NULL,
even INTEGER NOT NULL,
attention INTEGER NOT NULL,
orientation INTEGER NOT NULL REFERENCES orientation
);
-- Information about all double-pages, i.e. spreads in the
-- project. I.e which left and right images belong together, how they
-- are ordered, where pieces are missing or blank.
CREATE TABLE spread (
-- Basics: Id of the double page aka page spread, and the ordinal
-- specifying the ordering of spreads. Separating these two allows
-- changes to the ordering without regard to future references to
-- the table.
pid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
ord INTEGER NOT NULL UNIQUE
-- The information about the spread, i.e. the left and right
-- images, and the page number of the spread (which is always
-- even, and thus is also always the page number of the left
-- image). Both image references can be NULL, indicating a missing
-- or blank page. The flags are used to distinguish the two cases.
left INTEGER REFERENCES image,
right INTEGER REFERENCES image,
page TEXT UNIQUE,
lstatus INTEGER NOT NULL REFERENCES pagestatus,
rstatus INTEGER NOT NULL REFERENCES pagestatus
);
-- Helper table for self-description. Names/labels for the image
-- orientations. Fixed content. Note: The order of orientation is
-- following the path of the sun in a day.
CREATE TABLE orientation (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
);
INSERT INTO orientation VALUES (0,'east');
INSERT INTO orientation VALUES (1,'south');
INSERT INTO orientation VALUES (2,'west');
INSERT INTO orientation VALUES (3,'north');
-- Helper table for self-description. Names/labels for the page stati in a spread.
-- Fixed content.
CREATE TABLE pagestatus (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
);
INSERT INTO pagestatus VALUES (0,'ok');
INSERT INTO pagestatus VALUES (1,'blank');
INSERT INTO pagestatus VALUES (2,'missing');
|
Added lib/bfp/bfp.tcl.
|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
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
|
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
# Access to Bookflow Project Files
# Internally: sqlite3 database.
# # ## ### ##### ######## ############# #####################
## Requisites
package require Tcl 8.5
#package require debug
#package require debug::snit
package require fileutil
package require snit
package require sqlite3
namespace eval ::bookflow::project {
variable selfdir [file dirname [file normalize [info script]]]
}
# # ## ### ##### ######## ############# #####################
## Tracing
#debug prefix bookflow/project {[::debug::snit::call] }
#debug off bookflow/project
#debug on bookflow/project
# # ## ### ##### ######## ############# #####################
## API & Implementation
snit::type ::bookflow::project {
# # ## ### ##### ######## ############# #####################
typemethod isBookflow {path} {
if {![file exists $path]} { return 0 }
if {![file isfile $path]} { return 0 }
# FUTURE :: Extend fileutil::fileType
# readable, sqlite database ?
if {[catch {
set c [open $path r]
fconfigure $c -translation binary
}]} { return 0 }
set head [read $c 15]
close $c
if {$head ne {SQLite format 3}} { return 0 }
# check for the bookflow tables
set db ${type}::DB
sqlite3 $db $path
set ok true
foreach table $ourtables {
if {![Has $db $table]} {
set ok false
break
}
}
$db close
return $ok
}
proc Has {db table} {
return [llength [$db eval {
SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name = $table
;
}]]
}
# # ## ### ##### ######## ############# #####################
# List of expected database tables. Must match the schema.
typevariable ourtables {
global image spread orientation pagestatus
}
# Loaded from companion file.
typevariable ourschema {}
typemethod new {database project} {
#Debug.bookflow/project { @ $database $project}
# Create the database file at the specified location, and fill
# it with the necessary tables.
if {[$type isBookflow $database]} {
return -code error "Unable to overwrite existing bookflow project $database"
}
set db ${type}::DB
sqlite3 $db $database
$db transaction {
$db eval $ourschema
$db eval {
INSERT INTO global VALUES ('path',:project)
}
}
$db close
#Debug.bookflow/project {}
#return [$type create %AUTO% $database]
return
}
typeconstructor {
::variable selfdir
set ourschema [fileutil::cat $selfdir/bfp-schema.sql]
return
}
# # ## ### ##### ######## ############# #####################
method db {} { return $mydb }
constructor {database} {
#Debug.bookflow/project { @ $database $project}
if {![$type isBookflow $database]} {
return -code error "Not a bookflow project: $database"
}
set mydb ${selfns}::DB
sqlite3 $mydb $database
set mydir [$mydb eval {
SELECT value FROM global WHERE key = 'path'
}]
#Debug.bookflow/project {}
return
}
destructor {
if {$mydb eq {}} return
$mydb close
return
}
# # ## ### ##### ######## ############# #####################
## Public project methods
method where {} {
return $mydir
}
method add {images} {
#Debug.bookflow/project {}
$mydb transaction {
foreach image $images {
$mydb eval {
INSERT INTO image VALUES (NULL,:image,1,1,1,0,0)
-- flags => used, page, even, !attention, east
}
}
}
#Debug.bookflow/project {/}
return
}
method indicator {image flags} {
#Debug.bookflow/project {}
dict with flags {}
$mydb transaction {
$mydb eval {
UPDATE image
SET used = :used,
content = :content,
even = :even,
attention = :attention,
orientation = :orientation
WHERE path = :image
}
}
#Debug.bookflow/project {/}
return
}
method images-used {} {
$mydb transaction {
set images [$mydb eval {
SELECT path FROM image WHERE used = 1;
}]
}
return [lsort -dict $images]
}
method images-left {} {
$mydb transaction {
set images [$mydb eval {
SELECT path FROM image WHERE even = 1;
}]
}
return [lsort -dict $images]
}
method images-right {} {
$mydb transaction {
set images [$mydb eval {
SELECT path FROM image WHERE even = 0;
}]
}
return [lsort -dict $images]
}
method images-all {} {
$mydb transaction {
set images [$mydb eval {
SELECT path FROM image;
}]
}
return [lsort -dict $images]
}
method thumbnail? {image} {
#Debug.bookflow/project {}
return thumbnail/[file root $image]
}
method medium? {image} {
#Debug.bookflow/project {}
# XXX: Check that it is an image in the project?!
return [image create photo -file [$self medium-path $image]]
}
method medium-path {image} {
#Debug.bookflow/project {}
file mkdir medium
return medium/[file root [file tail $image]].ppm
}
method upright? {image} {
#Debug.bookflow/project {}
# XXX: Check that it is an image in the project?!
return [image create photo -file [$self upright-path $image]]
}
method upright-path {image} {
#Debug.bookflow/project {}
file mkdir upright
return upright/[file root [file tail $image]].ppm
}
method indicator? {image} {
#Debug.bookflow/project {}
$mydb transaction {
set data [$mydb eval {
SELECT used, content, even, attention, orientation
FROM image
WHERE path = :image
}]
}
lassign $data used content even attention orientation
#Debug.bookflow/project {/}
return [dict create \
used $used \
content $content \
even $even \
attention $attention \
orientation $orientation]
}
if 0 {method thumbnail {image thumbdata} {
#Debug.bookflow/project {}
$mydb transaction {
$mydb eval {
INSERT INTO thumb
VALUES ((SELECT iid FROM image
WHERE path = :image),:thumbdata)
}
}
#Debug.bookflow/project {/}
return
}
method thumbnail? {image} {
#Debug.bookflow/project {}
$mydb transaction {
set data [$mydb eval {
SELECT thumb FROM thumb
WHERE iid IN (SELECT iid FROM image
WHERE path = :image)
}]
}
#Debug.bookflow/project {/}
return $data
}}
### Accessors and manipulators
# # ## ### ##### ######## ############# #####################
##
variable mydb ; # Handle of the sqlite database. Object command.
variable mydir ; # Absolute path to the project directory (holding the images).
##
# # ## ### ##### ######## ############# #####################
}
# # ## ### ##### ######## ############# #####################
## Ready
package provide bookflow::project 0.1
return
|
Added lib/bfp/pkgIndex.tcl.
|
|
>
>
|
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::project 0.1 [list source [file join $dir bfp.tcl]]
|
Deleted lib/bookflow/bookflow.tcl.
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
|
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################
## Copyright (c) 2010 Andreas Kupries.
## BSD License
## Main package of the book scanning workflow application, aka
## bookflow.
# # ## ### ##### ######## ############# #####################
## Requirements
package require Tcl 8.5 ; # Required runtime.
package require Tk
package require blog ; # End-user visible activity logging,
package require widget::log ; # and the display for it.
package require widget::toolbar
package require scoreboard
package require bookflow::scan ; # Task. Scan project directory for images and database
package require bookflow::error ; # Task. Post error reports to the user.
package require bookflow::create ; # Task. Create project database when missing and images available.
package require bookflow::verify ; # Task. Verify project database when existing, and pre-load cached data.
package require bookflow::thumbnail ; # Task. Generate thumbnails for page images.
package require bookflow::greyscale ; # Task. Generate greyscale for page images.
package require bookflow::bright ; # Task. Compute brightness of page images.
package require bookflow::project::server ; # Task. In-application database server.
package require bookw ; # Book Display
namespace eval ::bookflow {}
# # ## ### ##### ######## ############# #####################
## API
proc ::bookflow::run {arguments} {
MakeGUI
after idle [list after 10 [namespace code [list Start $arguments]]]
vwait __forever
return
}
# # ## ### ##### ######## ############# #####################
## Internals
proc ::bookflow::MakeGUI {} {
wm withdraw .
Widgets
Layout
Bindings
wm deiconify .
return
}
proc ::bookflow::Start {arguments} {
variable project
Log.bookflow Booting...
if {![llength $arguments]} {
set project [pwd]
} else {
set project [lindex $arguments 0]
}
Log.bookflow {Project in $project}
bookflow::create ; # Watch for request to create new project database.
bookflow::verify ; # Watch for request to verify existing project database.
bookflow::error ; # Watch for error reports
bookflow::thumbnail ; # Watch for thumbnail generation requests.
bookflow::greyscale ; # Watch for greyscale generation requests.
bookflow::bright ; # Watch for brightness calculation requests.
bookflow::scan $project ; # Scan project directory
# TODO :: Launch the other tasklets monitoring the scoreboard for
# TODO :: their trigger conditions.
return
}
proc ::bookflow::Widgets {} {
# Re-style the notebook to use left-side tab-buttons
ttk::style configure VerticalTabsLeft.TNotebook -tabposition wn
widget::toolbar .toolbar
ttk::notebook .books -style VerticalTabsLeft.TNotebook
::widget::log .log -width 120 -height 2
.toolbar add button exit -text Exit -command ::exit -separator 1
return
}
proc ::bookflow::Layout {} {
pack .toolbar -side top -fill both -expand 0
pack .books -side top -fill both -expand 1
pack .log -side bottom -fill both -expand 0
return
}
proc ::bookflow::Bindings {} {
# Redirect log writing into the widget
::log on :: 0 .log
::log on bookflow
# Watch and react to scoreboard activity
# Here: Extend the notebook when new books are announced
scoreboard bind put {BOOK *} [namespace code BookNew]
return
}
# # ## ### ##### ######## ############# #####################
# TODO :: Analyse BookNew/Del for race conditions when a book B is
# TODO :: rapidly added and removed multiple times.
proc ::bookflow::BookNew {tuple} {
variable bookcounter
variable project
lassign $tuple _ name
set w .books.f$bookcounter
incr bookcounter
::bookw $w $name $project -log Log.bookflow
.books add $w -sticky nsew -text $name ; # TODO : -image book-icon -compound
# Watch and react to scoreboard activity
# Here: Update (shrink) the notebook when this book is removed.
scoreboard bind take [list BOOK $name] [namespace code [list BookDel $w]]
return
}
proc ::bookflow::BookDel {w tuple} {
# Drop the panel from the notebook, and remove the binding which invoked us.
.books forget $w
destroy $w
scoreboard unbind take [list BOOK $name] [namespace code [list BookDel $w]]
return
}
# # ## ### ##### ######## ############# #####################
## Ready
namespace eval ::bookflow {
namespace export {[a-z]*}
namespace ensemble create
variable bookcounter 0
variable project {}
}
package provide bookflow 1.0
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/bookflow/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow 1.0 [list source [file join $dir bookflow.tcl]]
|
<
<
|
|
|
Deleted lib/bookw/bookw.tcl.
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
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# The main window for each book found in the project.
# NOTES
# (1) Consider moving the chart and attendant structures and methods
# into its own megawidget.
# (2) Consider moving the thumbnail load handling into a helper class
# too. Re-usable for the regular images ?
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require Tk
package require snit
package require iq
package require scoreboard
package require img::strip ; # Strip of thumbnail images at the top.
package require img::page ; # Page spread, single or double.
package require debug
package require debug::snit
package require blog
package require img::png
package require rbc
package require uevent::onidle
package require struct::set
package require math::statistics
package require bookflow::thumbnail ; # Request encapsulation
# ### ### ### ######### ######### #########
## Tracing
debug prefix bookw {[::debug::snit::call] }
debug off bookw
#debug on bookw
# ### ### ### ######### ######### #########
## Implementation
snit::widgetadaptor ::bookw {
option -log -default {}
# ### ### ### ######### ######### #########
##
constructor {book project args} {
Debug.bookw {}
installhull using ttk::frame
install myrbright using uevent::onidle ${selfns}::RBG [mymethod RefreshBright]
install mytqueue using iq ${selfns}::QT 4 -emptycmd [mymethod Refill]
; # TODO : Query producer for allowed rate.
install mysqueue using iq ${selfns}::QB 4 ; # TODO : Query producer for allowed rate.
set myproject $project
set mybook $book
set mypattern [list IMAGE * $book]
$self Widgets
$self Layout
$self Bindings
# Note: We are peek'ing because at this time images for the
# named book might have already been added to the scoreboard,
# which won't be caught by the 'put' event we are registering.
scoreboard peek $mypattern [mymethod BookImages]
scoreboard bind put $mypattern [mymethod BookImageNew]
scoreboard bind take $mypattern [mymethod BookImageDel]
$self configurelist $args
Debug.bookw {/}
return
}
destructor {
Debug.bookw {}
scoreboard unbind put $mypattern [mymethod BookImageNew]
scoreboard unbind take $mypattern [mymethod BookImageDel]
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
##
method Widgets {} {
# Chart of brightness values for the page images.
rbc::graph $win.chart -height 200
#rbc::graph $win.chart -height 400
$win.chart axis configure y -min 0 -max 256
$win.chart axis configure y2 -hide 0
rbc::vector create ${selfns}::O ; # X-axis, page serial, ordering.
rbc::vector create ${selfns}::B ; # page brightness
rbc::vector create ${selfns}::D ; # page brightness differences
rbc::vector create ${selfns}::S ; # page brightness std deviation
# Chart: Page brightness
$win.chart element create b \
-xdata ${selfns}::O \
-ydata ${selfns}::B \
-color blue -symbol none -label B
# Chart: Page brightness delta to previous
$win.chart element create bd \
-xdata ${selfns}::O \
-ydata ${selfns}::D \
-mapy y2 -color red -symbol none -label D
# Chart: Page brightness standard deviation.
$win.chart element create bv \
-xdata ${selfns}::O \
-ydata ${selfns}::S \
-color orange -symbol none -label S
# Chart: Vertical line for current selection.
# Starting outside of the axes = invisible.
$win.chart marker create line -name selection \
-fill green -outline green \
-coords {-1 -Inf -1 Inf}
$win.chart marker create text -name tselectionr \
-coords {-1 10} -text {} -outline green -anchor w
$win.chart marker create text -name tselectionl \
-coords {-1 250} -text {} -outline green -anchor e
# Chart: Scatter plot for the points of interest. Enough for
# all the regular chart plots.
rbc::vector create ${selfns}::XB
rbc::vector create ${selfns}::YB
rbc::vector create ${selfns}::XD
rbc::vector create ${selfns}::YD
rbc::vector create ${selfns}::XV
rbc::vector create ${selfns}::YV
$win.chart element create boutlier \
-xdata ${selfns}::XB \
-ydata ${selfns}::YB \
-color blue -symbol circle -label {} \
-linewidth 0
$win.chart element create doutlier \
-xdata ${selfns}::XD \
-ydata ${selfns}::YD \
-color red -symbol square -label {} \
-linewidth 0 -mapy y2
$win.chart element create voutlier \
-xdata ${selfns}::XV \
-ydata ${selfns}::YV \
-color orange -symbol diamond -label {} \
-linewidth 0
# Strip of thumbnails for the page images.
img::strip $win.strip -orientation vertical
# Single/double page spread.
img::page $win.pages
return
}
method Layout {} {
pack $win.strip -side left -fill both -expand 0
pack $win.chart -side top -fill both -expand 0
#pack $win.strip -side top -fill both -expand 0
pack $win.pages -side top -fill both -expand 1
return
}
method Bindings {} {
bind $win.strip <<SelectionChanged>> \
[mymethod Selection %d]
bind $win.chart <1> [mymethod ChartSelection %x]
return
}
# ### ### ### ######### ######### #########
method Selection {selection} {
Debug.bookw {}
if {![llength $selection]} return
set token [lindex $selection 0]
set path $mypath($token)
set serial $myorder($path)
Debug.bookw { | $token -> $path -> $serial}
# Move the seletion marker and its associated texts (all in
# the chart) to the new location.
$win.chart marker configure selection \
-coords [list $serial -Inf $serial Inf]
$win.chart marker configure tselectionr \
-coords [list $serial 10] -text $serial
$win.chart marker configure tselectionl \
-coords [list $serial 250] -text $serial
$self Select $serial
Debug.bookw {/}
return
}
method ChartSelection {x} {
Debug.bookw {}
# Screen to graph coordinates, then select the associated image.
$self Select [expr {int([$win.chart axis invtransform x $x])}]
Debug.bookw {/}
return
}
method Select {serial} {
# x coordinate to image path, to the token used by the strip.
Debug.bookw {}
if {![info exists myopath($serial)]} {
after idle [list after 0 [info level 0]]
Debug.bookw {/ defered}
}
set path $myopath($serial)
set token $mytoken($path)
if {$myshown eq $path} return
set myshown $path
# Set the selection in the strip, this comes back to us via
# 'Selection' above, which then updates the chart.
$win.strip selection set $token
# Request the regular page (still scaled down) for the page
# spread underneath the chart, to the right of the strip.
$self GetRegular $path 1
Debug.bookw {/ shown = $myshown}
return
}
# ### ### ### ######### ######### #########
method BookImages {tuples} {
# tuples = list ((IMAGE path serial book)...)
Debug.bookw {}
# For ease of processing we simply run these through
# BookImageNew...
foreach t $tuples {
$self BookImageNew $t
}
Debug.bookw {/}
return
}
method BookImageNew {tuple} {
# tuple = (IMAGE path serial book)
Debug.bookw {}
lassign $tuple _ path serial book
# TODO : Should assert that book is the expected one.
incr mycountimages
$self Log "Book $book ($path /$mycountimages)"
set token [$win.strip new]
$win.strip itemconfigure $token \
-label "$path ($serial)" \
-order $serial \
-message {Creating thumbnail...}
set mytoken($path) $token
set mypath($token) $path
set myorder($path) $serial
set myopath($serial) $path
# Issue requests for the derived data needed by the widget.
$self GetThumbnail $path
$self GetStatistics $path
# Handling of the medium size thumbnail. First one request
# immediately for display. Also immediately if all small
# thumbnails known. Otherwise defer to to when the issue queue
# emptied (of small thumbnails).
if {$mycountimages < 2} {
after idle [mymethod Select 0]
} elseif {$mycountthumbsmall == $mycountimages} {
$self GetRegular $path 1
} else {
lappend mympending $path
}
$win.chart axis configure x -min 0 -max $mycountimages
Debug.bookw {/}
return
}
method BookImageDel {tuple} {
# tuple = (IMAGE path serial book)
Debug.bookw {}
lassign $tuple _ path serial book
# TODO : Should assert that book is the expected one.
incr mycountimages -1
incr mycountthumbsmall -1
incr mycountthumbmedium -1
incr mycountstat -1
$self Log "Book $book ($path /$mycountimages)"
# doc/interaction_pci.txt (5), release monitor
scoreboard unbind take [list THUMBNAIL $path *] [mymethod InvalidThumbnail]
# doc/interaction_pci.txt (4) - A waiting wpeek cannot released/canceled.
#scoreboard wpeek [list THUMBNAIL $path *] [mymethod HaveThumbnail]
# doc/interaction_pci.txt (5), release monitor
scoreboard unbind take [list STATISTICS $path *] [mymethod InvalidStatistics]
# doc/interaction_pci.txt (4) - A waiting wpeek cannot released/canceled.
#scoreboard wpeek [list STATISTICS $path *] [mymethod HaveThumbnail]
set token $mytoken($path)
set serial $myorder($path)
unset mytoken($path)
unset mypath($token)
unset myorder($path)
unset myopath($serial)
$win.strip drop $token
$myrbright request
$win.chart axis configure x -min 0 -max $mycountimages
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
method GetThumbnail {path} {
Debug.bookw {}
set request [bookflow::thumbnail::request $path 160];# x120
# doc/interaction_pci.txt (5).
scoreboard bind take $request [mymethod InvalidThumbnail]
# doc/interaction_pci.txt (4). Uses rate-limiting queue
$mytqueue put $request [mymethod HaveThumbnail]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (5).
method InvalidThumbnail {tuple} {
# tuple = (THUMBNAIL image-path size thumbnail-path)
Debug.bookw {}
lassign $tuple _ path size thumb
if {$size != 160} { error {Size mismatch} }
# Ignore invalidation of a small thumbnail when its image is
# not used here any longer.
if {![info exists mytoken($path)]} {
Debug.bookw {ignored/}
return
}
incr mycountthumbsmall -1
$self Log "Refresh TS $path $mycountthumbsmall/$mycountimages"
# Still using the image, therefore request a shiny new valid
# small thumbnail. doc/interaction_pci.txt (4).
$win.strip itemconfigure $mytoken($path) \
-message {Invalidated...}
$mytqueue put [bookflow::thumbnail::request $path $size] [mymethod HaveThumbnail]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (4).
method HaveThumbnail {tuple} {
# tuple = (THUMBNAIL image-path size thumbnail-path)
# Paths are relative to the project directory
Debug.bookw {}
lassign $tuple _ path size thumb
if {$size != 160} { error {Size mismatch} }
# Ignore the incoming thumbnail when its image is not used
# here any longer.
if {![info exists mytoken($path)]} {
Debug.bookw {ignored/}
return
}
incr mycountthumbsmall
$self Log "Thumbnail S $path $mycountthumbsmall/$mycountimages"
# Load small thumbnail and place it into the strip
# proper. Careful, retrieve and destroy any previously shown
# thumbnail first.
set photo [$win.strip itemcget $mytoken($path) -image]
if {$photo ne {}} {
image delete $photo
}
set photo [image create photo -file $myproject/$thumb]
$win.strip itemconfigure $mytoken($path) \
-image $photo \
-message {}
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
method Refill {args} {
if {![llength mympending]} return
foreach path $mympending {
$self GetRegular $path
}
set mympending {}
return
}
# ### ### ### ######### ######### #########
method GetRegular {path {fasttrack 0}} {
Debug.bookw {}
if {![string match {IMG_*} $path]} { error {Bad Path} }
set request [bookflow::thumbnail::request $path 800];# x600
# doc/interaction_pci.txt (5).
scoreboard bind take $request [mymethod InvalidRegular]
# doc/interaction_pci.txt (4). Uses rate-limiting queue. The
# same as the 160er thumbnails.
if {$fasttrack} {
# Bypass queue for fast track issue.
scoreboard wpeek $request [mymethod HaveRegular]
} else {
$mytqueue put $request [mymethod HaveRegular]
}
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (5).
method InvalidRegular {tuple} {
# tuple = (THUMBNAIL image-path size thumbnail-path)
Debug.bookw {}
lassign $tuple _ path size thumb
if {$size != 800} { error {Size mismatch} }
# Ignore invalidation of a medium thumbnail when its image is
# not used here any longer. Ditto if the image is used, but
# not shown.
if {![info exists mytoken($path)] ||
($myshown ne $path)} {
Debug.bookw {ignored/}
return
}
incr mycountthumbmedium -1
$self Log "Refresh TM $path $mycountthumbmedium/$mycountimages"
# Still using the image, therefore request a shiny new valid
# medium thumbnail. doc/interaction_pci.txt (4).
# TODO : Get and destroy currently shown image...
$win.pages even image {}
$win.pages even text {Invalidated...}
$mytqueue put [bookflow::thumbnail::request $path $size] [mymethod HaveRegular]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (4).
method HaveRegular {tuple} {
# tuple = (THUMBNAIL image-path size thumbnail-path)
# Paths are relative to the project directory.
Debug.bookw {}
lassign $tuple _ path size thumb
if {$size != 800} { error {Size mismatch} }
incr mycountthumbmedium
$self Log "Regular M $path $mycountthumbmedium/$mycountimages"
# Ignore the incoming medium thumbnail when its image is not
# used here any longer. Ditto if the image is used, but not
# shown.
if {![info exists mytoken($path)] ||
($myshown ne $path)} {
Debug.bookw {ignored/ [info exists mytoken($path)], ($myshown ne $path)? $myshown = $path}
return
}
# Load medium thumbnail and place it into the page spread
# proper. Careful, retrieve and destroy any previously shown
# image first.
# TODO - get and delte previous image
#set photo [$win.strip itemcget $mytoken($path) -image]
#if {$photo ne {}} { image delete $photo }
set photo [image create photo -file $myproject/$thumb]
$win.pages even text {}
$win.pages even image $photo
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
method GetStatistics {path} {
Debug.bookw {}
# doc/interaction_pci.txt (5).
scoreboard bind take [list STATISTICS $path *] [mymethod InvalidStatistics]
# doc/interaction_pci.txt (4). Uses rate-limiting queue
$mysqueue put [list STATISTICS $path *] [mymethod HaveStatistics]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (5).
method InvalidStatistics {tuple} {
# tuple = (STATISTICS image-path statistics)
Debug.bookw {}
lassign $tuple _ path statistics
# Ignore invalidation of statistics when its image is not used
# here any longer.
if {![info exists mytoken($path)]} {
Debug.bookw {/}
return
}
incr mycountstat -1
$self Log "Refresh S $path $mycountstat/$mycountimages"
# Still using the image, therefore request shiny new valid
# statistics for it. doc/interaction_pci.txt (4).
unset mystat($path)
$myrbright request
$mysqueue put [list STATISTICS $path *] [mymethod HaveStatistics]
Debug.bookw {/}
return
}
# doc/interaction_pci.txt (4).
method HaveStatistics {tuple} {
# tuple = (STATISTICS image-path statistics)
# Paths are relative to the project directory
Debug.bookw {}
lassign $tuple _ path statistics
# Ignore the incoming statistics when its image is not
# used here any longer.
if {![info exists mytoken($path)]} {
Debug.bookw {/}
return
}
incr mycountstat
$self Log "Statistics $path $mycountstat/$mycountimages"
set mystat($path) $statistics
$myrbright request
Debug.bookw {/}
return
}
method RefreshBright {} {
Debug.bookw {}
# Pull the currently known statistics out of our data
# structures, put the brightnesses into the proper order, then
# stuff the result into the chart.
set o {}
set b {}
set s {}
set d {}
set l {}
set bxy {}
foreach serial [lsort -dict [array names myopath]] {
set path $myopath($serial)
if {![info exists mystat($path)]} continue
lassign $mystat($path) _ _ mean _ _ stddev _ _
# brightness = mean.
lappend o $serial
lappend b $mean
lappend s $stddev
lappend d [expr {($l eq {}) ? 0 : ($mean - $l)}]
set l $mean
# dict form of x/y, mapping x to y, for the fusing below.
lappend bxy $serial $mean
}
Debug.bookw { O = ($o)}
Debug.bookw { B = ($b)}
Debug.bookw { D = ($d)}
Debug.bookw { S = ($s)}
${selfns}::O set $o
${selfns}::B set $b
${selfns}::D set $d
${selfns}::S set $s
# Outliers, computed from global statistics of the page brightness.
if {[llength $o]} {
# Get 2-sigma outliers for page brightness
lassign [Outlier $o $b] bx by
# Get 2-sigma outliers for page brightness differences
lassign [Outlier $o $d] dx dy
# Get 2-sigma outliers for page brightness stddev
lassign [DownOutlier $o $s] vx vy
# Fuse the results. Points of interest are the locations of
# stddev outliers and the locations where both brightness and
# brightness deltas indicate outliers. Compute the y locations
# for these using the bxy map.
set ix [lsort -integer [struct::set union $vx [struct::set intersect $bx $dx]]]
set iy {} ; foreach x $ix { lappend iy [dict get $bxy $x] }
${selfns}::XB set $ix
${selfns}::YB set $iy
#${selfns}::XD set $dx
#${selfns}::YD set $dy
#${selfns}::XV set $vx
#${selfns}::YV set $vy
}
Debug.bookw {/}
return
}
# Find the t-sigma outliers above and below the yseries average.
proc Outlier {xseries yseries {t 2}} {
lassign [math::statistics::basic-stats $yseries] \
avg min max n stddev var pstddev pvar
set t [expr {$t * $stddev}]
set xo {}
set yo {}
foreach x $xseries y $yseries {
if {abs($y - $avg) < $t} continue
lappend xo $x
lappend yo $y
}
return [list $xo $yo]
}
# Find the t-sigma outliers below the yseries average
proc DownOutlier {xseries yseries {t 2}} {
lassign [math::statistics::basic-stats $yseries] \
avg min max n stddev var pstddev pvar
set t [expr {$t * $stddev}]
set xo {}
set yo {}
foreach x $xseries y $yseries {
if {($avg - $y) < $t} continue
lappend xo $x
lappend yo $y
}
return [list $xo $yo]
}
# ### ### ### ######### ######### #########
method Log {text} {
if {$options(-log) eq {}} return
uplevel #0 [list {*}$options(-log) $text]
return
}
# ### ### ### ######### ######### #########
##
variable myproject ; # Path of project directory.
variable mybook ; # Name of the book this is connected to
variable mypattern ; # Scoreboard pattern for images of this book.
variable mytoken -array {} ; # Map image PATHs to the associated
# TOKEN in the strip of images.
variable mypath -array {} ; # Map tokens back to their image PATHs.
variable myorder -array {} ; # Map image PATHs to the associated
# order in the strip of images, and
# chart of page brightness,
variable myopath -array {} ; # Map serial order to image PATH.
variable mystat -array {} ; # Map image PATHs to the associated
# page statistics.
variable myrbright {} ; # onidle collator for brightness refresh
variable mytqueue {} ; # Issue queue for thumbnails
variable mysqueue {} ; # Issue queue for statistics
variable mycountimages 0 ; # Number of managed images
variable mycountthumbsmall 0 ; # Number of managed small thumbnails
variable mycountthumbmedium 0 ; # Number of managed medium thumbnails
variable mycountstat 0 ; # Number of managed brightness values
variable myshown {} ; # PATH of currently shown/selected page.
variable mympending {} ; # List of pages for which the medium
# sized thumbnails are pending.
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide bookw 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/bookw/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookw 0.1 [list source [file join $dir bookw.tcl]]
|
<
<
|
|
|
Deleted lib/bright/bright.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task. Continuous.
# Calculating the basic statistica values for page images.
# Called 'brightness' for historical reasons. That was the only value
# computed here at first (mean).
# A producer in terms of "doc/interaction_pci.txt"
# A consumer as well, of page greyscale images.
#
# Calculated statistical values are cached in the project database.
# Limits itself to no more than four actual threads in flight,
# i.e. computing image statistics. The computing tasks do not exit on
# completion, but wait for more operations to perform. Communication
# and coordination is done through the scoreboard. As usual.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
package require scoreboard
package require bookflow::project
namespace eval ::bookflow::bright {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/bright
#debug on bookflow/bright
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::bright {} {
Debug.bookflow/bright {Bookflow::Bright Watch}
scoreboard wpeek {AT *} [namespace code bright::BEGIN]
Debug.bookflow/bright {/}
return
}
proc ::bookflow::bright::BEGIN {tuple} {
# tuple = (AT project)
Debug.bookflow/bright {Bookflow::Bright BEGIN <$tuple>}
lassign $tuple _ project
::bookflow::project::ok [namespace code [list INIT $project]]
Debug.bookflow/bright {Bookflow::Bright BEGIN/}
return
}
proc ::bookflow::bright::INIT {project} {
Debug.bookflow/bright {Bookflow::Bright INIT}
# Monitor for invalidation of statistics
# doc/interaction_pci.txt (1)
scoreboard take {!STATISTICS *} [namespace code INVALIDATE]
# Launch the tasks doing the actual resizing.
variable max
for {set i 0} {$i < $max} {incr i} {
task launch [list ::apply {{project} {
package require bookflow::bright
bookflow::bright::STATISTICS $project
}} $project]
}
# Monitor for bright creation requests.
# doc/interaction_pci.txt (2)
scoreboard bind missing {STATISTICS *} [namespace code MAKE]
Debug.bookflow/bright {Bookflow::Bright INIT/}
return
}
# ### ### ### ######### ######### #########
## Internals. Bright invalidation. See doc/interaction_pci.txt (1).
proc ::bookflow::bright::INVALIDATE {tuple} {
# tuple = (!STATISTICS path)
lassign $tuple _ path
Debug.bookflow/bright {Bookflow::Bright INVALIDATE $path}
scoreboard takeall [list STATISTICS $path *] [namespace code [list RETRACT $path]]
Debug.bookflow/bright {Bookflow::Bright INVALIDATE/}
return
}
proc ::bookflow::bright::RETRACT {path tuples} {
Debug.bookflow/bright {Bookflow::Bright RETRACT $path}
::bookflow::project statistics unset $path
# Look for more invalidation requests
scoreboard take {!STATISTICS *} [namespace code INVALIDATE]
Debug.bookflow/bright {Bookflow::Bright RETRACT/}
return
}
# ### ### ### ######### ######### #########
## Internals. Bright creation. See doc/interaction_pci.txt (2).
proc ::bookflow::bright::MAKE {pattern} {
# pattern = (STATISTICS path *)
Debug.bookflow/bright {Bookflow::Bright MAKE <$pattern>}
lassign $pattern _ path
set statistics [::bookflow::project statistics get $path]
if {$statistics ne {}} {
# The requested values already existed in the project database,
# simply make them available.
# TODO :: Have the verify task-to-be load existing brightness
# TODO :: information to shortcircuit even this fast bailout.
# TODO :: Note however that we will then need some way to
# TODO :: prevent the insertion of duplicate or similar tuples.
RESULT $path $statistics
} else {
# Statistics are not known. Put in a request for the computing
# tasks to generate them. This will also put the proper result
# into the scoreboard on completion.
scoreboard put [list STATSQ $path]
}
Debug.bookflow/bright {Bookflow::Bright MAKE/}
return
}
proc ::bookflow::bright::RESULT {path statistics} {
scoreboard put [list STATISTICS $path $statistics]
return
}
# ### ### ### ######### ######### #########
## Internals. Implementation of the calculation tasks.
proc ::bookflow::bright::STATISTICS {project} {
package require debug
Debug.bookflow/bright {Bookflow::Bright STATISTICS}
# Requisites for the task
package require bookflow::bright
package require bookflow::project
package require scoreboard
package require crimp ; wm withdraw .
package require fileutil
# Start waiting for requests.
::bookflow::project::ok [namespace code [list READY $project]]
Debug.bookflow/bright {Bookflow::Bright STATISTICS/}
return
}
proc ::bookflow::bright::READY {project} {
# Wait for more requests.
scoreboard take {STATSQ *} [namespace code [list STAT $project]]
return
}
proc ::bookflow::bright::STAT {project tuple} {
# tuple = (STATSQ path)
# Decode request
lassign $tuple _ path
Debug.bookflow/bright {Bookflow::Bright STAT $path}
# Get the greyscale form of the image
scoreboard take [list GREYSCALE $path *] [namespace code [list MEAN $project]]
Debug.bookflow/bright {Bookflow::Bright STAT/}
return
}
proc ::bookflow::bright::MEAN {project tuple} {
# tuple = (GREYSCALE path grey-path)
lassign $tuple _ path grey
Debug.bookflow/bright {Bookflow::Bright MEAN $path |$grey}
set data [fileutil::cat -translation binary $project/$grey]
Debug.bookflow/bright { read ok $path}
set image [crimp read pgm $data]
Debug.bookflow/bright { pgm read ok $path}
set stats [crimp statistics basic $image]
Debug.bookflow/bright { statistics ok $path}
array set s [dict get $stats channel luma]
Debug.bookflow/bright { statistics ok $path}
set statistics [list $s(min) $s(max) $s(mean) $s(middle) $s(median) $s(stddev) $s(variance) $s(hf)]
# Save/Cache result in the project.
::bookflow::project statistics set $path {*}$statistics
Debug.bookflow/bright { db ok $path}
# Push result
RESULT $path $statistics
# Wait for more requests.
READY $project
Debug.bookflow/bright {Bookflow::Bright MEAN $path = $statistics/}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow::bright {
# Number of parallel calculation tasks.
variable max 4
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::bright 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/bright/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::bright 0.1 [list source [file join $dir bright.tcl]]
|
<
<
|
|
|
Deleted lib/create/create.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task.
# Waiting for requests to create an initial project database.
# Launches the task when the request is found.
# Creates the specified directory, looking for the BOOKFLOW database and
# JPEG images.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
namespace eval ::bookflow::create {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/create
#debug on bookflow/create
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::create {} {
Debug.bookflow/create {Bookflow::Create Watch}
scoreboard take {PROJECT CREATE} [namespace code create::RUN]
Debug.bookflow/create {/}
}
# ### ### ### ######### ######### #########
## Internals
proc ::bookflow::create::RUN {tuple} {
Debug.bookflow/create {Bookflow::Create RUN}
Log.bookflow {Creating project database...}
task launch [list ::apply {{} {
package require bookflow::create
bookflow::create::TASK
}}]
Debug.bookflow/create {Bookflow::Create RUN/}
return
}
proc ::bookflow::create::TASK {} {
package require debug
Debug.bookflow/create {Bookflow::Create TASK}
# Requisites for the task
package require scoreboard
package require bookflow::create
package require bookflow::project ; # client
scoreboard wpeek {AT *} [namespace code BEGIN]
Debug.bookflow/create {Bookflow::Create TASK/}
return
}
proc ::bookflow::create::BEGIN {tuple} {
# tuple = (AT project)
variable defaultfile
Debug.bookflow/create {Bookflow::Create BEGIN <$tuple>}
# Get the payload
lassign $tuple _ projectdir
# Declare database presence, triggers creation.
Log.bookflow {% Project database $defaultfile}
scoreboard put [list DATABASE $defaultfile]
# At this point the server thread will complete initialization and
# provide access to the database. We wait until it has done so:
::bookflow::project::ok [namespace code [list WaitForServerStart $projectdir]]
Debug.bookflow/create {Bookflow::Create BEGIN/}
return
}
proc ::bookflow::create::WaitForServerStart {project} {
Debug.bookflow/create {Bookflow::Create WaitForServerStart}
# Fill the database using the image files found by the scanner.
scoreboard takeall {FILE*} [namespace code [list FILES $project]]
Debug.bookflow/create {Bookflow::Create WaitForServerStart/}
return
}
proc ::bookflow::create::FILES {project tuples} {
Debug.bookflow/create {Bookflow::Create FILES}
# tuples = list ((FILE *)...)
# ... pull books out of the database and declare them ...
# ... push files into the @scratch book, and declare
# them as images, with book link ...
foreach b [::bookflow::project books] {
Debug.bookflow/create { BOOK $b}
scoreboard put [list BOOK $b]
}
# Sorted by file name (like IMG_nnnn), this is the initial order.
foreach def [lsort -dict -index 1 $tuples] {
lassign $def _ jpeg
set serial [::bookflow::project book extend @SCRATCH $jpeg \
[file mtime $project/$jpeg]]
Debug.bookflow/create { IMAGE $jpeg $serial @SCRATCH}
scoreboard put [list IMAGE $jpeg $serial @SCRATCH]
}
Debug.bookflow/create {Bookflow::Create FILES/}
task::exit
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow {
namespace export create
namespace ensemble create
namespace eval create {
variable defaultfile BOOKFLOW
}
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::create 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/create/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::create 0.1 [list source [file join $dir create.tcl]]
|
<
<
|
|
|
Deleted lib/db/db.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Access to a bookflow database, file identification, creation, etc.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require debug::snit
package require snit
package require sqlite3
namespace eval ::bookflow::db {}
# ### ### ### ######### ######### #########
## Tracing
debug prefix bookflow/db {[::debug::snit::call] }
debug off bookflow/db
#debug on bookflow/db
# ### ### ### ######### ######### #########
## API & Implementation
snit::type ::bookflow::db {
# ### ### ### ######### ######### #########
typemethod isBookflow {path} {
if {![file exists $path]} { return 0 }
if {![file isfile $path]} { return 0 }
# FUTURE :: Extend fileutil::fileType
# readable, sqlite database ?
if {[catch {
set c [open $path r]
fconfigure $c -translation binary
}]} { return 0 }
set head [read $c 15]
close $c
if {$head ne {SQLite format 3}} { return 0 }
# check for the bookflow tables
set db ${type}::DB
sqlite3 $db $path
set ok [expr {[Has $db bookflow] &&
[Has $db book] &&
[Has $db image] &&
[Has $db statistics]}]
$db close
return $ok
}
proc Has {db table} {
return [llength [$db eval {
SELECT name
FROM sqlite_master
WHERE type = 'table'
AND name = $table
;
}]]
}
# ### ### ### ######### ######### #########
typemethod new {path} {
Debug.bookflow/db { @ $path}
# Create the database file at the specified location, and fill
# it with the necessary tables.
set db ${type}::DB
sqlite3 $db $path
$db eval {
-- Global, per project information
CREATE TABLE bookflow (
dpi INTEGER NOT NULL -- dots per inch for the whole project.
);
-- A project is subdivided into one or more books.
-- Note that each project internally uses two standard
-- 'books'. These are the 'scratchpad' holding all
-- images not assigned to a user-created book, and the
-- 'trash' holding the data about images which are gone,
-- for their eventual resurrection.
CREATE TABLE book (
bid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
-- FUTURE : More book information, like author, isbn,
-- FUTURE : printing datum, etc. Possibly in a separate
-- FUTURE : table for meta data.
);
-- The @ character is illegal in user-specified book names,
-- ensuring that the standard books can never be in conflict
-- with the user's names.
INSERT INTO book VALUES (0,'@SCRATCH');
INSERT INTO book VALUES (1,'@TRASH');
-- All images, which always belong to a single book.
-- Images have an order imposed on them (see field 'ord'),
-- which is unique within a book.
CREATE TABLE image (
iid INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
path TEXT NOT NULL UNIQUE,
bid INTEGER NOT NULL REFERENCES book,
ord INTEGER NOT NULL,
mtime INTEGER NOT NULL,
UNIQUE (bid, ord)
);
-- Statistical data for all images. Used to classify
-- images, distinguishing markers from regular pages.
-- Actually the whole slew of basic statistics. Just in
-- case. (Machine-learning over lots of prjects ?!).
CREATE TABLE statistics (
iid INTEGER NOT NULL REFERENCES image,
min INTEGER NOT NULL,
max INTEGER NOT NULL,
mean REAL NOT NULL,
middle REAL NOT NULL,
median INTEGER NOT NULL,
stddev REAL NOT NULL,
variance REAL NOT NULL,
histogram TEXT NOT NULL,
UNIQUE (iid)
);
}
$db close
Debug.bookflow/db {}
return [$type create %AUTO% $path]
}
# ### ### ### ######### ######### #########
constructor {path} {
Debug.bookflow/db { @ $path}
set mydb ${selfns}::DB
sqlite3 $mydb $path
Debug.bookflow/db {}
return
}
# ### ### ### ######### ######### #########
method books {} {
Debug.bookflow/db {}
return [$mydb eval { SELECT name FROM book }]
}
method {book extend} {book file mtime} {
Debug.bookflow/db {}
$mydb transaction {
# Locate the named book, and retrieve its id.
set bid [lindex [$mydb eval {
SELECT bid FROM book WHERE name = $book
}] 0]
# Get the last (= highest) ordering number for images in this book.
set ord [lindex [$mydb eval {
SELECT MAX (ord) FROM image WHERE bid = $bid
}] 0]
# The new images is added behind the last-highest images.
if {$ord eq {}} { set ord -1 }
incr ord
Debug.bookflow/db { /book $bid, @$ord}
# And enter the image into the database.
$mydb eval {
INSERT INTO image
VALUES (NULL, $file, $bid, $ord, $mtime)
}
}
Debug.bookflow/db {/}
return $ord
}
method {book holding} {file} {
Debug.bookflow/db {}
return [lindex [$mydb eval {
SELECT name FROM book
WHERE bid = (SELECT bid FROM image
WHERE path = $file)
}] 0]
}
method {book files} {book} {
Debug.bookflow/db {}
return [$mydb eval {
SELECT path, ord
FROM image
WHERE bid = (SELECT bid FROM book
WHERE name = $book)
}]
}
# NOTE: Moves leave gaps in the serial numbering of the origin
# books. While this doesn't affect the ordering in itself, other
# parts using the serial number may assume that there are no
# gaps. Example: The book manager widget uses the serial numbers
# for the x-axis of the brightness chart, and gaps will show up
# there. Consider some mechanism to remove/prevent such gaps.
method {book move} {book file} {
Debug.bookflow/db {}
$mydb transaction {
# Locate the named book, and retrieve its id.
set bid [lindex [$mydb eval {
SELECT bid FROM book WHERE name = $book
}] 0]
# Get the last (= highest) ordering number for images in this book.
set ord [lindex [$mydb eval {
SELECT MAX (ord) FROM image WHERE bid = $bid
}] 0]
# The new images is added behind the last-highest images.
if {$ord eq {}} { set ord -1 }
incr ord
Debug.bookflow/db { /book $bid, @$ord}
# And change the image in the database.
$mydb eval {
UPDATE image
SET bid = $bid,
ord = $ord
WHERE path = $file
}
}
Debug.bookflow/db {/}
return $ord
}
method files {} {
Debug.bookflow/db {}
return [$mydb eval { SELECT path FROM image }]
}
method {file mtime} {file} {
Debug.bookflow/db {}
return [$mydb eval { SELECT mtime FROM image WHERE path = $file }]
}
method {statistics set} {file min max mean middle median stddev variance histogram} {
Debug.bookflow/db {}
$mydb transaction {
# Locate the id of the file.
set iid [lindex [$mydb eval {
SELECT iid
FROM image
WHERE path = $file
}] 0]
# And enter the value into the database.
$mydb eval {
INSERT INTO statistics
VALUES ($iid, $min, $max, $mean, $middle, $median, $stddev, $variance, $histogram)
}
}
Debug.bookflow/db {/}
return
}
method {statistics unset} {file} {
Debug.bookflow/db {}
$mydb transaction {
# Remove the statistics value.
$mydb eval {
DELETE FROM statistics
WHERE iid IN (SELECT iid FROM image WHERE path = $file)
}
}
Debug.bookflow/db {/}
return
}
method {statistics get} {file} {
Debug.bookflow/db {}
$mydb transaction {
set res [$mydb eval {
SELECT min, max, mean, middle, median, stddev, variance, histogram
FROM statistics
WHERE iid IN (SELECT iid FROM image WHERE path = $file)
}]
}
#lassign $res min max mean middle median stddev variance histogram
Debug.bookflow/db {= $res /}
return $res
}
### Accessors and manipulators
# ### ### ### ######### ######### #########
##
variable mydb ; # Handle of the sqlite database. Object command.
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::db 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/db/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::db 0.1 [list source [file join $dir db.tcl]]
|
<
<
|
|
|
Deleted lib/debug/debug.tcl.
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
|
# Debug - a debug narrative logger -- Colin McCormack / Wub server utilities
#
# Debugging areas of interest are represented by 'tokens' which have
# independantly settable levels of interest (an integer, higher is more detailed)
#
# Debug narrative is provided as a tcl script whose value is [subst]ed in the
# caller's scope if and only if the current level of interest matches or exceeds
# the Debug call's level of detail. This is useful, as one can place arbitrarily
# complex narrative in code without unnecessarily evaluating it.
#
# TODO: potentially different streams for different areas of interest.
# (currently only stderr is used. there is some complexity in efficient
# cross-threaded streams.)
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
namespace eval ::debug {}
# ### ### ### ######### ######### #########
## API & Implementation
proc ::debug::noop {args} {}
proc ::debug::debug {tag message {level 1}} {
variable detail
if {$detail($tag) < $level} {
#puts stderr "$tag @@@ $detail($tag) >= $level"
return
}
variable prefix
variable fds
set fd $fds($tag)
# Integrate global and tag prefixes with the user message.
set themessage ""
if {[info exists prefix(::)]} { append themessage $prefix(::) }
if {[info exists prefix($tag)]} { append themessage $prefix($tag) }
append themessage $message
# Resolve variables references and command invokations embedded
# into the message with plain text.
set code [catch {
uplevel 1 [list ::subst -nobackslashes $themessage]
} result eo]
if {$code} {
if {[catch {
set x [info level -1]
}]} { set x GLOBAL }
puts -nonewline $fd @@[string map {\n \\n \r \\r} "(DebugError from $tag [if {[string length $x] < 1000} {set x} else {set x "[string range $x 0 200]...[string range $x end-200 end]"}] ($eo)):"]
} else {
if {[string length $result] > 4096} {
set result "[string range $result 0 2048]...(truncated) ... [string range $result end-2048 end]"
}
puts $fd "$tag | [join [split $result \n] "\n$tag | "]"
}
return
}
# names - return names of debug tags
proc ::debug::names {} {
variable detail
return [lsort [array names detail]]
}
proc ::debug::2array {} {
variable detail
set result {}
foreach n [lsort [array names detail]] {
if {[interp alias {} Debug.$n] ne "::Debug::noop"} {
lappend result $n $detail($n)
} else {
lappend result $n -$detail($n)
}
}
return $result
}
# level - set level and fd for tag
proc ::debug::level {tag {level ""} {fd stderr}} {
variable detail
if {$level ne ""} {
set detail($tag) $level
}
if {![info exists detail($tag)]} {
set detail($tag) 1
}
variable fds
set fds($tag) $fd
return $detail($tag)
}
# set prefix to use for tag.
# The global (tag-independent) prefix is adressed through tag == '::'`.
# This works because colon (:) is an illegal character for regular tags.
proc ::debug::prefix {tag {theprefix {}}} {
variable prefix
set prefix($tag) $theprefix
return
}
# turn on debugging for tag
proc ::debug::on {tag {level ""} {fd stderr}} {
variable active
set active($tag) 1
level $tag $level $fd
interp alias {} Debug.$tag {} ::debug::debug $tag
return
}
# turn off debugging for tag
proc ::debug::off {tag {level ""} {fd stderr}} {
variable active
set active($tag) 1
level $tag $level $fd
interp alias {} Debug.$tag {} ::debug::noop
return
}
proc ::debug::setting {args} {
if {[llength $args] == 1} {
set args [lindex $args 0]
}
set fd stderr
if {[llength $args]%2} {
set fd [lindex $args end]
set args [lrange $args 0 end-1]
}
foreach {tag level} $args {
if {$level > 0} {
level $tag $level $fd
interp alias {} Debug.$tag {} ::Debug::debug $tag
} else {
level $tag [expr {-$level}] $fd
interp alias {} Debug.$tag {} ::Debug::noop
}
}
return
}
namespace eval debug {
variable detail ; # map: TAG -> level of interest
variable prefix ; # map: TAG -> message prefix to use
variable fds ; # map: TAG -> handle of open channel to log to.
# Notes:
# The tag '::' is reserved, prefix() uses it to store the global message prefix.
namespace export -clear *
namespace ensemble create -subcommands {}
}
# ### ### ### ######### ######### #########
## Communication setup for concurrent tasks.
## Thread based.
namespace eval ::debug::thread {}
proc ::debug::thread::link {main} {
variable ::debug::detail
variable ::debug::prefix
# Import main's status.
array set detail [thread::send $main {array get ::debug::detail}]
array set prefix [thread::send $main {array get ::debug::prefix}]
array set active [thread::send $main {array get ::debug::active}]
# We do not import the channels. Cannot share them among threads,
# only transfer.
# Replicate (in)active status of the tags.
foreach {t a} [array get active] {
if {$a} {
interp alias {} Debug.$t {} ::debug::debug $t
} else {
interp alias {} Debug.$t {} ::debug::noop
}
}
return
}
# ### ### ### ######### ######### #########
## Look for the magic of package task, and if found import the main's
## status to configure our settings.
::apply {{} {
if {![info exists ::task::type]} return
::debug::${::task::type}::link $::task::main
return
}}
# ### ### ### ######### ######### #########
## Ready
package provide debug 1.0
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/debug/debug_snit.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
## Utility command for use as debug prefix command to un-mangle snit
## method calls.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
namespace eval ::debug::snit {}
# ### ### ### ######### ######### #########
## API & Implementation
proc ::debug::snit::call {} {
# For snit (type)methods, rework the command line to be more
# legible and in line with what the user would expect. To this end
# we pull the primary command out of the arguments, be it type or
# object, massage the command to match the original (type)method
# name, then resort and expand the words to match the call before
# snit got its claws into it.
set a [lassign [info level -1] m]
regsub {.*Snit_} $m {} m
switch -glob $m {
htypemethod* {
# primary = type, a = type
set a [lassign $a primary]
set m [string map {_ { }} [string range $m 11 end]]
}
typemethod* {
# primary = type, a = type
set a [lassign $a primary]
set m [string range $m 10 end]
}
hmethod* {
# primary = self, a = type selfns self win ...
set a [lassign $a _ _ primary _]
set m [string map {_ { }} [string range $m 7 end]]
}
method* {
# primary = self, a = type selfns self win ...
set a [lassign $a _ _ primary _]
set m [string range $m 6 end]
}
destructor -
constructor {
# primary = self, a = type selfns self win ...
set a [lassign $a _ _ primary _]
}
typeconstructor {
return [list {*}$a $m]
}
default {
# Unknown
return [list $m {*}$a]
}
}
return [list $primary {*}$m {*}$a]
}
# ### ######### ###########################
## Ready for use
package provide debug::snit 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/debug/pkgIndex.tcl.
1
2
3
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded debug 1.0 [list source [file join $dir debug.tcl]]
package ifneeded debug::snit 0.1 [list source [file join $dir debug_snit.tcl]]
|
<
<
<
|
|
|
Deleted lib/error/error.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Error display. Watches the scoreboard for error messages and posts
# them as tk_message. Pseudo-task using events, i.e. CPS.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require scoreboard
namespace eval ::bookflow::error {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/error
#debug on bookflow/error
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::error {} {
Debug.bookflow/error {Bookflow::Error Watch}
scoreboard take {PROJECT ERROR *} [namespace code error::Post]
Debug.bookflow/error {/}
return
}
# ### ### ### ######### ######### #########
## Internals
proc ::bookflow::error::Post {tuple} {
tk_messageBox -type ok -icon error -parent . -title Error \
-message [lindex $tuple 2]
# Return to watching the scoreboard, there may be more messages.
after idle ::bookflow::error
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow {
namespace export error
namespace ensemble create
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::error 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/error/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::error 0.1 [list source [file join $dir error.tcl]]
|
<
<
|
|
|
Deleted lib/grey/greyscale.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task. Continuous.
# Creating and invalidating greyscales of page images.
# A producer in terms of "doc/interaction_pci.txt"
#
# Generated greyscales are cached in the directory ".bookflow/grey" of
# the project directory.
# Limits itself to no more than four actual threads in flight,
# i.e. performing image scaling. The scaling tasks do not exit on
# completion, but wait for more operations to perform. Communication
# and coordination is done through the scoreboard. As usual.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
package require scoreboard
namespace eval ::bookflow::greyscale {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/greyscale
#debug on bookflow/greyscale
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::greyscale {} {
Debug.bookflow/greyscale {Bookflow::Greyscale Watch}
scoreboard wpeek {AT *} [namespace code greyscale::BEGIN]
Debug.bookflow/greyscale {/}
return
}
proc ::bookflow::greyscale::BEGIN {tuple} {
# tuple = (AT project)
Debug.bookflow/greyscale {Bookflow::Greyscale BEGIN <$tuple>}
lassign $tuple _ project
# Monitor for greyscale invalidation
# doc/interaction_pci.txt (1)
scoreboard take {!GREYSCALE *} [namespace code [list INVALIDATE $project]]
# Launch the tasks doing the actual conversion.
variable max
for {set i 0} {$i < $max} {incr i} {
task launch [list ::apply {{} {
package require bookflow::greyscale
bookflow::greyscale::CONVERT
}}]
}
# Monitor for greyscale creation requests.
# doc/interaction_pci.txt (2)
scoreboard bind missing {GREYSCALE *} [namespace code [list MAKE $project]]
Debug.bookflow/greyscale {Bookflow::Greyscale BEGIN/}
return
}
# ### ### ### ######### ######### #########
## Internals. Helper encapsulation directory structure.
proc ::bookflow::greyscale::GreyFullPath {project path} {
return $project/[GreyPath $path]
}
proc ::bookflow::greyscale::GreyPath {path} {
return .bookflow/grey/[file rootname $path].pgm
}
# ### ### ### ######### ######### #########
## Internals. Greyscale invalidation. See doc/interaction_pci.txt (1).
proc ::bookflow::greyscale::INVALIDATE {project tuple} {
# tuple = (!GREYSCALE path)
lassign $tuple _ path
Debug.bookflow/greyscale {Bookflow::Greyscale INVALIDATE $path}
scoreboard takeall [list GREYSCALE $path *] [namespace code [list RETRACT $project $path]]
Debug.bookflow/greyscale {Bookflow::Greyscale INVALIDATE/}
return
}
proc ::bookflow::greyscale::RETRACT {project path tuples} {
Debug.bookflow/greyscale {Bookflow::Greyscale RETRACT $path}
file delete [GreyFullPath $project $path]
# Look for more invalidation requests
scoreboard take {!GREYSCALE *} [namespace code [list INVALIDATE $project]]
Debug.bookflow/greyscale {Bookflow::Greyscale RETRACT/}
return
}
# ### ### ### ######### ######### #########
## Internals. Greyscale creation. See doc/interaction_pci.txt (2).
proc ::bookflow::greyscale::MAKE {project pattern} {
# pattern = (GREYSCALE path *)
lassign $pattern _ path
Debug.bookflow/greyscale {Bookflow::Greyscale MAKE $path}
set greyfull [GreyFullPath $project $path]
set grey [GreyPath $path]
if {[file exists $greyfull]} {
# Greyscale already exists in the filesystem cache, simply
# make it available.
scoreboard put [list GREYSCALE $path $grey]
} else {
# Greyscale not known. Put in a request for the converter
# tasks to generate it. This will also put the proper result
# into the scoreboard on completion.
set r [list GREYSCALE $path $grey]
scoreboard put [list GREYCONVERT $project/$path $greyfull $r]
}
Debug.bookflow/greyscale {Bookflow::Greyscale MAKE/}
return
}
# ### ### ### ######### ######### #########
## Internals. Implementation of the resizing tasks.
proc ::bookflow::greyscale::CONVERT {} {
package require debug
Debug.bookflow/greyscale {Bookflow::Greyscale CONVERT}
# Requisites for the task
package require bookflow::greyscale
package require scoreboard
package require crimp ; wm withdraw .
package require img::jpeg
# Start waiting for requests.
READY
Debug.bookflow/greyscale {Bookflow::Greyscale CONVERT/}
return
}
proc ::bookflow::greyscale::READY {} {
# Wait for more requests.
scoreboard take {GREYCONVERT *} [namespace code GCONV]
return
}
proc ::bookflow::greyscale::GCONV {tuple} {
# tuple = (GREYCONVERT path dstpath result)
# result = (GREYSCALE path dstpath)
# Decode request
lassign $tuple _ path dst result
Debug.bookflow/greyscale {Bookflow::Greyscale GCONV $path $dst}
# Perform the conversion, writing pgm, using crimp internally.
file mkdir [file dirname $dst]
set photo [image create photo -file $path]
crimp write 2file pgm-raw $dst [crimp convert 2grey8 [crimp read tk $photo]]
image delete $photo
# Push result
scoreboard put $result
# Wait for more requests.
READY
Debug.bookflow/greyscale {Bookflow::Greyscale GCONV $path = $dst /}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow::greyscale {
# Number of parallel conversion tasks.
variable max 4
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::greyscale 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/grey/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::greyscale 0.1 [list source [file join $dir greyscale.tcl]]
|
<
<
|
|
|
Deleted lib/imgpage/imgpage.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Widget showing a single or double page spread, i.e. one or two
# images. Not specific to bookflow.
# ### ### ### ######### ######### #########
## Requisites
package require Tk 8.5
package require debug
package require debug::snit
package require snit
package require tooltip
package require widget::scrolledwindow
debug prefix img/page {[::debug::snit::call] }
debug off img/page
#debug on img/page
# ### ### ### ######### ######### #########
##
snit::widgetadaptor img::page {
# ### ### ### ######### ######### #########
##
delegate option -borderwidth to hull
delegate option -relief to hull
# ### ### ### ######### ######### #########
##
constructor {args} {
Debug.img/page {}
installhull using ttk::frame
$self Widgets
$self Layout
$self Bindings
$self configurelist $args
return
}
method {odd image} {image} { $self Image odd $image ; return }
method {even image} {image} { $self Image even $image ; return }
method {odd text} {text} { $self Text odd $text ; return }
method {even text} {text} { $self Text even $text ; return }
# ### ### ### ######### ######### #########
method Image {frame image} {
Debug.bookw {}
set mystate($frame,photo) [expr {$image ne {}}]
set w [image width $image]
set h [image height $image]
if {$h > $w} { set max $h } else { set max $w }
incr max 20
$win.$frame.plate configure -scrollregion [list 0 0 $max $max]
$win.$frame.plate itemconfigure PHOTO -image $image
$win.$frame.plate coords PHOTO [expr {$w/2 + 10}] [expr {$h/2 + 10}]
if {$image eq {}} {
$win.$frame.plate raise TEXT
} else {
$win.$frame.plate raise PHOTO
}
$self Relayout
Debug.bookw {/}
return
}
method Text {frame text} {
Debug.bookw {}
set mystate($frame,text) [expr {$text ne {}}]
$win.$frame.plate itemconfigure TEXT -text $text
if {$text eq {}} {
$win.$frame.plate raise PHOTO
} else {
$win.$frame.plate raise TEXT
}
$self Relayout
Debug.bookw {/}
return
}
method Relayout {} {
Debug.bookw {}
set odd [expr {$mystate(odd,photo) || $mystate(odd,text)}]
set even [expr {$mystate(even,photo) || $mystate(even,text)}]
if {$odd && $even} {
pack $win.odd -in $win -side left -fill both -expand 1
pack $win.even -in $win -side right -fill both -expand 1
} elseif {$odd} {
pack forget $win.even
pack $win.odd -in $win -side top -fill both -expand 1
} elseif {$even} {
pack forget $win.odd
pack $win.even -in $win -side top -fill both -expand 1
} else {
pack forget $win.odd
pack forget $win.even
}
Debug.bookw {/}
return
}
# ### ### ### ######### ######### #########
method Context {x y} {
Debug.img/page {}
event generate $win <<Context>> -data [list $x $y $myimage]
return
}
# ### ### ### ######### ######### #########
##
method Widgets {} {
foreach frame {
odd
even
} {
widget::scrolledwindow $win.$frame
canvas $win.$frame.plate \
-scrollregion {0 0 1024 1024} \
-borderwidth 2 -relief sunken
$win.$frame setwidget $win.$frame.plate
$win.$frame.plate create image 10 10 -tags PHOTO
$win.$frame.plate create text 10 10 -tags TEXT -anchor nw -fill red -font {-size -16} -text "Undefined"
}
return
}
method Layout {} {
# Layout is dynamic, as images are assigned to the sides, odd
# packed left, even packed right, both expanding.
return
}
method Bindings {} {
bind $win.odd.plate <3> [mymethod Context %X %Y]
bind $win.even.plate <3> [mymethod Context %X %Y]
return
}
# ### ### ### ######### ######### #########
## State
variable mystate -array {
odd,photo 0
odd,text 0
even,photo 0
even,text 0
}
# ### ### ### ######### ######### #########
## Configuration
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide img::page 0.1
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/imgpage/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded img::page 0.1 [list source [file join $dir imgpage.tcl]]
|
<
<
|
|
|
Deleted lib/imgstrip/imgstrip.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Widget showing a horizontal/vertical strip of images.
# Not specific to bookflow.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require widget::scrolledwindow
package require treectrl
package require snit
package require debug::snit
package require debug
package require syscolor
debug off img/strip
#debug on img/strip
debug prefix img/strip {[::debug::snit::call] }
# ### ### ### ######### ######### #########
##
snit::widgetadaptor ::img::strip {
# ### ### ### ######### ######### #########
##
option -orientation \
-default horizontal \
-configuremethod C-orient \
-type {snit::enum -values {horizontal vertical}}
# ### ### ### ######### ######### #########
##
delegate method * to mytree
delegate option * to mytree
delegate option -borderwidth to hull
delegate option -relief to hull
# ### ### ### ######### ######### #########
##
constructor {args} {
Debug.img/strip {}
installhull using widget::scrolledwindow -borderwidth 1 -relief sunken
$self Widgets
$self Layout
$self Bindings
$self S-orient horizontal
$self STYLE
$self configurelist $args
return
}
# Add an empty image to the widget. Displayed, but without text or
# image until such are configured. Returns a token to address the
# item with.
method new {} {
Debug.img/strip {}
set newitem [$mytree item create]
$mytree item lastchild 0 $newitem
$mytree item configure $newitem -button 0
$mytree item configure $newitem -visible 1
$mytree item style set $newitem 0 STYLE
$mytree collapse $newitem
$self Resort
$self DetermineHeight
$self DetermineWidth
Debug.img/strip {/}
return $newitem
}
method drop {token} {
Debug.img/strip {}
$mytree item delete $token
# Note: Resorting not needed, the other images are staying in
# their proper order.
Debug.img/strip {/}
return
}
method itemconfigure {token args} {
foreach {option value} $args {
$self ItemConfigure $option $token $value
}
return
}
method {ItemConfigure -message} {token string} {
Debug.img/strip {}
$mytree item element configure $token 0 eText -text $string
Debug.img/strip {/}
return
}
method {ItemConfigure -label} {token string} {
Debug.img/strip {}
$mytree item element configure $token 0 eLabel -text $string
Debug.img/strip {/}
return
}
method {ItemConfigure -order} {token string} {
Debug.img/strip {}
$mytree item element configure $token 0 eSerial -text $string
$self Resort
Debug.img/strip {/}
return
}
method {ItemConfigure -image} {token photo} {
Debug.img/strip {}
$mytree item element configure $token 0 eImage -image $photo
Debug.img/strip {/}
return
}
method itemcget {token option} {
return [$self ItemCget $option $token]
}
method {ItemCget -message} {token} {
Debug.img/strip {}
if {[catch {
set res [$mytree item element cget $token 0 eText -text]
}]} { set res {} }
Debug.img/strip {= $res /}
return $res
}
method {ItemCget -label} {token} {
Debug.img/strip {}
if {[catch {
set res [$mytree item element cget $token 0 eLabel -text]
}]} { set res {} }
Debug.img/strip {= $res /}
return $res
}
method {ItemCget -order} {token} {
Debug.img/strip {}
if {[catch {
set res [$mytree item element cget $token 0 eSerial -text]
}]} { set res {} }
Debug.img/strip {= $res /}
return $res
}
method {ItemCget -image} {token} {
Debug.img/strip {}
if {[catch {
set res [$mytree item element cget $token 0 eImage -image]
}]} { set res {} }
Debug.img/strip {= $res /}
return $res
}
method {selection set} {token} {
$mytree selection clear
$mytree selection add $token
$mytree activate $token
return
}
# ### ### ### ######### ######### #########
## Internals
method Widgets {} {
Debug.img/strip {}
install mytree using treectrl $win.tree \
-highlightthickness 0 \
-borderwidth 0 \
-showheader 1 \
-xscrollincrement 20
$mytree debug configure \
-enable no \
-display no \
-erasecolor pink \
-displaydelay 30
$mytree configure \
-showroot no \
-showbuttons no \
-showlines no \
-selectmode single \
-showheader no \
-scrollmargin 16 \
-xscrolldelay {500 50} \
-yscrolldelay {500 50}
return
}
method Layout {} {
Debug.img/strip {}
$hull setwidget $mytree
return
}
method Bindings {} {
Debug.img/strip {}
# Disable "scan" bindings on windows.
if {$::tcl_platform(platform) eq "windows"} {
bind $mytree <Control-ButtonPress-3> { }
}
bindtags $mytree [list $mytree TreeCtrl [winfo toplevel $mytree] all]
$mytree notify bind $mytree <ActiveItem> [mymethod ChangeActiveItem %p %c]
$mytree notify bind $mytree <Selection> [mymethod Selection]
bind $mytree <Double-1> [mymethod Action %x %y]
bind $mytree <3> [mymethod Context %X %Y %x %y]
bind $win <FocusIn> [mymethod Focus]
$mytree column create
return
}
method STYLE {} {
Debug.img/strip {}
# Style for the items used for the display of images.
#
# Elements
# ------------------------------------------------------------------------
# eImage : The image to show.
# eText : Transient text, feedback (like the status of image ops, etc.)
# eLabel : Textual label for the image.
# eFrame : Square rectangle around the image.
# eShadow : A small drop shadow around eFrame.
# eSerial : INVISIBLE text whose contents determine display order. I.e.
# this one is used to sort the items.
# ------------------------------------------------------------------------
$mytree element create eImage image -image {} -width $oursize -height $oursize
$mytree element create eText text -text {} -fill $ourtextfillcolor -justify center
$mytree element create eLabel text -text {} -fill $ourtextfillcolor -justify center
$mytree element create eFrame rect -outlinewidth 1 -fill $ourfillcolor -outline $ouroutlinecolor
$mytree element create eShadow rect -outlinewidth 2 -fill $ourfillcolor -outline gray \
-open wn -showfocus 1
$mytree element create eSerial text -text {}
$mytree style create STYLE -orient vertical
$mytree style elements STYLE {eShadow eLabel eFrame eImage eText eSerial}
$mytree style layout STYLE eLabel -ipady {2 0} -expand we
$mytree style layout STYLE eFrame -union { eImage eText }
$mytree style layout STYLE eImage -ipady $ourgap -ipadx $ourgap -expand swen
$mytree style layout STYLE eShadow -padx {1 2} -pady {1 2} -iexpand xy -detach yes
#$mytree style layout STYLE eLabel -visible 1
#$mytree style layout STYLE eImage -visible 1
$mytree style layout STYLE eSerial -visible 0
TreeCtrl::SetSensitive $mytree { {0 STYLE eShadow eLabel eFrame eImage eText} }
TreeCtrl::SetEditable $mytree { {0 STYLE} }
TreeCtrl::SetDragImage $mytree { {0 STYLE} }
bindtags $mytree \
[list \
$mytree \
TreeCtrlFileList \
TreeCtrl \
[winfo toplevel $mytree] \
all]
return
}
method Resort {} {
# Regenerate the display order of items.
# We sort them by the third text element, the invisible "eSerial".
$mytree item sort 0 -dict -element eSerial
return
}
# ### ### ### ######### ######### #########
##
method ChangeActiveItem {previous current} {
Debug.img/strip {}
$mytree see $current
return
}
method Focus {} {
Debug.img/strip {==> $mytree}
focus $mytree
return
}
method Context {x y wx wy} {
set idata [$mytree identify $wx $wy]
Debug.img/strip {[list ==> $idata]}
lassign $idata type id
event generate $win <<Context>> -data [list $x $y $id]
return
}
method Action {x y} {
set idata [$mytree identify $x $y]
Debug.img/strip {[list ==> $idata]}
lassign $idata type id
if {$type ne "item"} return
event generate $win <<Action>> -data $id
return
}
method Selection {} {
Debug.img/strip {}
event generate $win <<SelectionChanged>> \
-data [$mytree selection get]
return
}
# ### ### ### ######### ######### #########
method C-orient {o value} {
if {$options($o) eq $value} return
set options($o) $value
$self S-orient $value
return
}
method S-orient {value} {
switch -exact -- $value {
horizontal {
# Tree is horizontal, no wrapping is done.
# Each item is as high as myheight (to be determined
# after first item added).
# Indirectly derived from 'oursize', the w/h given to
# the eImage element.
# FUTURE: Pull this out of the actual image configured
# for the first item (max of all maybe ?)
$mytree configure -orient horizontal -wrap {}
$hull configure -scrollbar horizontal -auto horizontal
$self DetermineHeight
}
vertical {
# Tree is vertical, no wrapping is done.
# Each item is as wide as mywidth (to be determined
# after first item added).
# Indirectly derived from 'oursize', the w/h given to
# the eImage element.
# FUTURE: Pull this out of the actual image configured
# for the first item (max of all maybe ?)
$mytree configure -orient vertical -wrap {}
$hull configure -scrollbar vertical -auto vertical
$self DetermineWidth
}
}
return
}
method DetermineHeight {} {
if {![info exists options(-orientation)]} return
if {$options(-orientation) ne "horizontal"} return
if {$myheight eq {}} {
set items [$mytree item children 0]
if {![llength $items]} return
lassign [$mytree item bbox [lindex $items 0]] _ _ _ myheight
incr myheight 40
}
$mytree configure -height $myheight -width 0
return
}
method DetermineWidth {} {
if {![info exists options(-orientation)]} return
if {$options(-orientation) ne "vertical"} return
if {$mywidth eq {}} {
set items [$mytree item children 0]
if {![llength $items]} return
lassign [$mytree item bbox [lindex $items 0]] _ _ mywidth _
#incr mywidth 40
}
#$mytree column configure 0 -width $mywidth
$mytree configure -width $mywidth -height 0
return
}
# ### ### ### ######### ######### #########
## State
variable mywidth {} ; # Strip width, derived from first image
variable myheight {} ; # Strip height, derived from first image
component mytree
# ### ### ### ######### ######### #########
## Configuration
## TODO :: Make these configurable (on widget creation only).
typevariable oursize 160 ; # Maximal size of the images to expect (160x120 / 120x160)
typevariable ourgap 4 ; # Size of the gap to put between image and text.
typevariable ourselectcolor \#ffdc5a
typevariable ouroutlinecolor \#827878
typevariable ourfillcolor
typevariable ourtextfillcolor
typeconstructor {
set ourtextfillcolor [list [syscolor::highlightText] {selected focus}]
set ourfillcolor [list \
[syscolor::highlight] {selected focus} \
gray {selected !focus}]
set ourtextfillcolor [list [syscolor::highlightText] {selected focus}]
set ourfillcolor [list \
\#ff8800 {selected focus} \
gray {selected !focus}]
}
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide img::strip 0.1
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/imgstrip/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded img::strip 0.1 [list source [file join $dir imgstrip.tcl]]
|
<
<
|
|
|
Deleted lib/iq/iq.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Issue Queue. Use it to limit the rate of issuing requests for data
# like thumbnails etc. Instead of directly issuing the query patterns
# to the scoreboard issue them to an instance of iq and the queue will
# issue them so that only a fixed (but configurable) number of queries
# have outstanding results.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require snit
package require scoreboard
package require debug
package require debug::snit
package require struct::queue
# ### ### ### ######### ######### #########
## Tracing
debug prefix iq {[::debug::snit::call] }
debug off iq
#debug on iq
# ### ### ### ######### ######### #########
## Implementation
snit::type ::iq {
# ### ### ### ######### ######### #########
##
option -emptycmd \
-default {}
# ### ### ### ######### ######### #########
##
constructor {limit args} {
Debug.iq {}
set mylimit $limit
set myqueue [struct::queue ${selfns}::Q]
$self configurelist $args
Debug.iq {/}
return
}
method put {pattern cmd} {
Debug.iq {}
if {$myflight >= $mylimit} {
$myqueue put [list $pattern $cmd]
Debug.iq {/}
return
}
$self Dispatch $pattern $cmd
Debug.iq {/}
return
}
# ### ### ### ######### ######### #########
##
method Dispatch {pattern cmd} {
Debug.iq {}
scoreboard wpeek $pattern [mymethod Have $cmd]
incr myflight
Debug.iq {/}
return
}
method Have {cmd tuple} {
Debug.iq {}
incr myflight -1
if {($myflight < $mylimit) && [$myqueue size]} {
lassign [$myqueue get] pattern newcmd
$self Dispatch $pattern $newcmd
$self NotifyEmpty
}
uplevel #0 [list {*}$cmd $tuple]
Debug.iq {/}
return
}
# ### ### ### ######### ######### #########
method NotifyEmpty {args} {
if {![$myqueue size]} return
if {![llength $options(-emptycmd)]} return
after idle [list after 0 [list {*}$options(-emptycmd) $self]]
return
}
# ### ### ### ######### ######### #########
##
variable myflight 0 ; # Number of requests waiting for results
variable mylimit 0 ; # Maximum number of requests we are allowed
# to keep in flight.
variable myqueue {} ; # Queue of requests waiting to be issued.
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide iq 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/iq/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded iq 0.1 [list source [file join $dir iq.tcl]]
|
<
<
|
|
|
Deleted lib/log/log.tcl.
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
|
# Log - A narrative logger, not for debugging by the developer, but
# end-user reporting of system activity.
# Derived from the debug logger.
#
# Logging areas of interest are represented by 'tokens' which have
# independantly settable levels of interest (an integer, higher is more detailed)
#
# Log narrative is provided as a tcl script whose value is [subst]ed in the
# caller's scope if and only if the current level of interest matches or exceeds
# the Log call's level of detail. This is useful, as one can place arbitrarily
# complex narrative in code without unnecessarily evaluating it.
#
# TODO: potentially different streams for different areas of interest.
# (currently only stderr is used. there is some complexity in efficient
# cross-threaded streams.)
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require debug
namespace eval ::log {}
debug off log
# ### ### ### ######### ######### #########
## API & Implementation
proc ::log::noop {args} {}
proc ::log::log {tag message {level 1}} {
variable detail
if {$detail($tag) < $level} {
#puts stderr "$tag @@@ $detail($tag) >= $level"
return
}
variable prefix
variable fds
# Determine the log command, based on tag, with fallback to a
# global setting.`
if {[catch {
set fd $fds($tag)
}]} {
set fd $fds(::)
}
# Integrate global and tag prefixes with the user message.
set themessage ""
if {[info exists prefix(::)]} { append themessage $prefix(::) }
if {[info exists prefix($tag)]} { append themessage $prefix($tag) }
append themessage $message
# Resolve variables references and command invokations embedded
# into the message with plain text.
set code [catch {
uplevel 1 [list ::subst -nobackslashes $themessage]
} result eo]
if {$code} {
return -code error $result
#set x [info level -1]
#set x [expr {[string length $x] < 1000 ? $x : "[string range $x 0 200]...[string range $x end-200 end]"}]
#{*}$fd puts* @@[string map {\n \\n \r \\r} "(LogError from $tag $x ($eo)):"]
} {
if {[string length $result] > 4096} {
set result "[string range $result 0 2048]...(truncated) ... [string range $result end-2048 end]"
}
set head $tag
set blank [regsub -all . $tag { }]
foreach line [split $result \n] {
#{*}$fd puts* $head
#{*}$fd puts* { | }
{*}$fd puts $line
set head $blank
}
}
return
}
# names - return names of log tags
proc ::log::names {} {
variable detail
return [lsort [array names detail]]
}
proc ::log::2array {} {
variable detail
set result {}
foreach n [lsort [array names detail]] {
if {[interp alias {} Log.$n] ne "::Log::noop"} {
lappend result $n $detail($n)
} else {
lappend result $n -$detail($n)
}
}
return $result
}
# level - set level and log command for tag
proc ::log::level {tag {level ""} {fd {}}} {
variable detail
if {$level ne ""} {
set detail($tag) $level
}
if {![info exists detail($tag)]} {
set detail($tag) 1
}
variable fds
if {$fd ne {}} {
set fds($tag) $fd
}
return $detail($tag)
}
# set prefix to use for tag.
# The global (tag-independent) prefix is adressed through tag == '::'`.
# This works because colon (:) is an illegal character for regular tags.
proc ::log::prefix {tag {theprefix {}}} {
variable prefix
set prefix($tag) $theprefix
return
}
# turn on logging for tag
proc ::log::on {tag {level ""} {fd {}}} {
variable active
set active($tag) 1
level $tag $level $fd
interp alias {} Log.$tag {} ::log::log $tag
return
}
# turn off logging for tag
proc ::log::off {tag {level ""} {fd {}}} {
variable active
set active($tag) 0
level $tag $level $fd
interp alias {} Log.$tag {} ::log::noop
return
}
proc ::log::setting {args} {
if {[llength $args] == 1} {
set args [lindex $args 0]
}
set fd {}
if {[llength $args]%2} {
set fd [lindex $args end]
set args [lrange $args 0 end-1]
}
foreach {tag level} $args {
if {$level > 0} {
level $tag $level $fd
interp alias {} Log.$tag {} ::Log::log $tag
} else {
level $tag [expr {-$level}] $fd
interp alias {} Log.$tag {} ::Log::noop
}
}
return
}
# ### ### ### ######### ######### #########
## Communication setup for concurrent tasks.
## Thread based.
namespace eval ::log::thread {}
proc ::log::thread::link {main} {
variable ::log::detail
variable ::log::prefix
variable ::log::fds
Debug.log { Setting up log for $main}
# Import main's status.
array set detail [thread::send $main {array get ::log::detail}]
array set prefix [thread::send $main {array get ::log::prefix}]
array set active [thread::send $main {array get ::log::active}]
# We do not import any custom write commands.
# Any writing goes through the global setting, which is
# reconfigured to perform the necessary inter-thread
# communication.
# Replicate (in)active status of the tags.
foreach {t a} [array get active] {
if {$a} {
interp alias {} Log.$t {} ::log::log $t
} else {
interp alias {} Log.$t {} ::log::noop
}
}
set fds(::) [list ::log::thread::ToMain $main]
return
}
proc ::log::thread::ToMain {main cmd text} {
upvar 1 tag tag
thread::send -async $main \
[list ::log::thread::FromTask $tag $cmd $text]
return
}
proc ::log::thread::FromTask {tag cmd text} {
# This is a variant of log::log without all the substitutions. It
# determines the actual write command per the tag and invokes it
# with the specifiec method and text.
# It is the receiver of messages coming from concurrently running
# tasks.
variable ::log::fds
if {[catch {
set fd $fds($tag)
}]} {
set fd $fds(::)
}
{*}$fd $cmd $text
return
}
# ### ### ### ######### ######### #########
## Standard log writer command
namespace eval ::log::Write {
namespace export puts puts*
namespace ensemble create
}
proc ::log::Write::puts {text} {
puts stderr $text
return
}
proc ::log::Write::puts* {text} {
puts stderr -nonewline $text
flush stderr
return
}
# ### ### ### ######### ######### #########
## State
namespace eval ::log {
variable detail ; # map: TAG -> level of interest
variable prefix ; # map: TAG -> message prefix to use
variable fds ; # map: TAG -> command prefix to use for writing the message.
variable active ; # map: TAG -> boolean flag, true if tag is active.
# Notes:
# The tag '::' is reserved.
# prefix() uses it to store the global message prefix.
# fds() uses it to store a global command prefix for writing messages.
set fds(::) ::log::Write
namespace export -clear *
namespace ensemble create -subcommands {}
}
# ### ### ### ######### ######### #########
## Look for the magic of package task, and if found, reconfigure
## ourselves to write to the main system. Do not forget to import the
## main's status as well.
::apply {{} {
if {![info exists ::task::type]} return
::log::${::task::type}::link $::task::main
return
}}
# ### ### ### ######### ######### #########
## Ready
package provide blog 1.0
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/log/pkgIndex.tcl.
1
2
3
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded blog 1.0 [list source [file join $dir log.tcl]]
|
<
<
<
|
|
|
Deleted lib/project/p_client.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Access to the bookflow project database from any part of the
# application.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require scoreboard
namespace eval ::bookflow::project {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/project
#debug on bookflow/project
# ### ### ### ######### ######### #########
## API & Implementation
## Wait for the server thread to complete initialization
proc ::bookflow::project::ok {cmd} {
Debug.bookflow/project {OK <cmd>}
# Wait for the appearance of (PROJECT SERVER *)
scoreboard take {PROJECT SERVER *} [list ::apply {{cmd tuple} {
# Put tuple back for others.
scoreboard put $tuple
# Make delegation command usable, i.e. tell it which thread to
# send the commands to.
lassign $tuple _ _ thread
variable server $thread
# Tell the caller that the database server thread is (now)
# ready.
uplevel #0 $cmd
} ::bookflow::project} $cmd]
Debug.bookflow/project {OK/}
return
}
# ### ### ### ######### ######### #########
## API & Implementation
## Delegate all actions to the server thread. This serializes
## concurrent access by different parts of the application.
proc ::bookflow::project {args} {
variable project::server
return [thread::send $server [info level 0]]
}
# ### ### ### ######### ######### #########
namespace eval ::bookflow::project {
variable server
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::project 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/project/p_server.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Access to a bookflow project database. The actual access is through
# the bookflow::db package. This package simply wraps around it, to
# serialize any access from all the threads of the application, acting
# as an in-application server. This server runs in its own thread.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require bookflow::db
namespace eval ::bookflow::project {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/project
#debug on bookflow/project
# ### ### ### ######### ######### #########
::apply {{} {
task launch [list ::apply {{} {
package require scoreboard
# Wait for the appearance of (DATABASE *)
scoreboard wpeek {DATABASE *} {::apply {{tuple} {
lassign $tuple _ dbfile
# Pull the project location
scoreboard wpeek {AT *} [list ::apply {{dbfile tuple} {
lassign $tuple _ project
package require bookflow::db
set dbfile $project/$dbfile
if {![file exists $dbfile]} {
[bookflow::db new $dbfile] destroy
}
::bookflow::db ::bookflow::project $dbfile
set id [thread::id]
scoreboard put [list PROJECT SERVER $id]
return
}} $dbfile]
return
}}}
}}]
}}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::project::server 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/project/pkgIndex.tcl.
1
2
3
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::project 0.1 [list source [file join $dir p_client.tcl]]
package ifneeded bookflow::project::server 0.1 [list source [file join $dir p_server.tcl]]
|
<
<
<
|
|
|
Deleted lib/sb/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded scoreboard 0.1 [list source [file join $dir scoreboard.tcl]]
|
<
<
|
|
|
Deleted lib/sb/sb_client.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Scoreboard Client. Used by tasks (in threads) to talk to the actual
# scoreboard in the main thread. The commands are shims which redirect
# to the equivalent command in the main thread, possibly rewriting
# arguments to handle the proper back and forth for callbacks.
# ### ### ### ######### ######### #########
## API & Implementation
proc ::scoreboard::put {args} {
thread::send -async $::task::main [info level 0]
return
}
proc ::scoreboard::take {pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::takeall {pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::peek {pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::wpeek {pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::bind {event pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
proc ::scoreboard::unbind {event pattern cmd} {
set me [info level 0]
set me [lreplace $me end end [list ::scoreboard::Return [thread::id] [lindex $me end]]]
thread::send -async $::task::main $me
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::scoreboard {
namespace export {[a-z]*}
namespace ensemble create
}
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/sb/sb_server.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Scoreboard, a singleton in-memory database used by the concurrent
# tasks and the main control to coordinate and communicate with each
# other. Actually a tuple-space with a bit of dressing disguising it.
# ### ### ### ######### ######### #########
## API & Implementation
proc ::scoreboard::put {args} {
variable db
if {![llength $args]} {
return -code error "wrong\#args: expected tuple..."
}
Debug.scoreboard {put <[join $args ">\nput <"]>}
foreach tuple $args {
incr db($tuple)
Notify put $tuple
}
Broadcast $args
Debug.scoreboard {put/}
return
}
proc ::scoreboard::take {pattern cmd} {
variable db
Debug.scoreboard {take <$pattern> (($cmd))}
set matches [array names db $pattern]
if {![llength $matches]} {
Debug.scoreboard { no matches, defer response}
Wait take $pattern $cmd
Debug.scoreboard {take/}
return
}
set tuple [lindex $matches 0]
Debug.scoreboard { matches = [llength $matches]}
Debug.scoreboard { taken <$tuple>}
Remove $tuple
Notify take $tuple
Call $cmd $tuple
Debug.scoreboard {take/}
return
}
proc ::scoreboard::takeall {pattern cmd} {
variable db
Debug.scoreboard {takeall <$pattern> (($cmd))}
set matches [array names db $pattern]
Debug.scoreboard { matches = [llength $matches]}
foreach tuple $matches {
Debug.scoreboard { taken <$tuple>}
Remove $tuple
Notify take $tuple
}
Call $cmd $matches
Debug.scoreboard {takeall/}
return
}
proc ::scoreboard::peek {pattern cmd} {
variable db
Debug.scoreboard {peek <$pattern> (($cmd))}
set matches [array names db $pattern]
Debug.scoreboard { matches = [llength $matches]}
Call $cmd $matches
Debug.scoreboard {peek/}
return
}
proc ::scoreboard::wpeek {pattern cmd} {
variable db
Debug.scoreboard {wpeek <$pattern> (($cmd))}
set matches [array names db $pattern]
if {![llength $matches]} {
Debug.scoreboard { no matches, defer response}
Wait peek $pattern $cmd
Debug.scoreboard {wpeek/}
return
}
set tuple [lindex $matches 0]
Debug.scoreboard { matches = [llength $matches]}
Debug.scoreboard { peeked <$tuple>}
Call $cmd $tuple
Debug.scoreboard {wpeek/}
return
}
proc ::scoreboard::bind {event pattern cmd} {
Debug.scoreboard {bind <$event <$pattern>> (($cmd))}
if {$event ni {put take missing}} {
return -code error "Bad event \"$event\", expected one of missing, put, or take"
}
variable bind
lappend bind($event) [list $pattern $cmd]
Debug.scoreboard {bind/}
return
}
proc ::scoreboard::unbind {event pattern cmd} {
Debug.scoreboard {unbind <$event <$pattern>> (($cmd))}
if {$event ni {put take missing}} {
return -code error "Bad event \"$event\", expected one of missing, put, or take"
}
variable bind
set k [list $pattern $cmd]
set pos [lsearch -exact $bind($event) $k]
if {$pos < 0} return
set bind($event) [lreplace $bind($event) $pos $pos]
Debug.scoreboard {unbind/}
return
}
# ### ### ### ######### ######### #########
## Internals
proc ::scoreboard::Return {thread cmd args} {
thread::send -async $thread [list {*}$cmd {*}$args]
return
}
proc ::scoreboard::Remove {tuple} {
variable db
incr db($tuple) -1
if {!$db($tuple)} { unset db($tuple) }
return
}
proc ::scoreboard::Wait {action pattern cmd} {
variable wait
lappend wait [list $action $pattern $cmd]
Notify missing $pattern
return
}
proc ::scoreboard::Broadcast {tuples} {
variable wait
Debug.scoreboard { Broadcast}
#Debug.scoreboard { [join $wait "\n "]}
set stillwaiting {}
foreach item $wait {
# Quick bail out if all tuples have been broadcast.
if {![llength $tuples]} {
lappend stillwaiting $item
continue
}
# Bail if the pattern of the waiting request doesn't match any
# tuple.
lassign $item action pattern cmd
set pos [lsearch -glob $tuples $pattern]
if {$pos < 0} {
lappend stillwaiting $item
continue
}
# This request matches and is now served. It doesn't go on the
# still-pending list. The tuple in question is removed, if and
# only if the action was 'take'.
Debug.scoreboard { Broadcast : Match <$pattern>}
set tuple [lindex $tuples $pos]
if {$action eq "take"} {
set tuples [lreplace $tuples $pos $pos]
Debug.scoreboard { taken <$tuple>}
Remove $tuple
} else {
Debug.scoreboard { peeked <$tuple>}
}
Call $cmd $tuple
}
set wait $stillwaiting
Debug.scoreboard { Broadcast/}
return
}
proc ::scoreboard::Call {cmd args} {
Debug.scoreboard { Call $cmd ($args)}
after idle [list after 1 [list {*}$cmd {*}$args]]
return
}
proc ::scoreboard::Notify {event tuple} {
Debug.scoreboard { Notify $event}
variable bind
foreach item $bind($event) {
lassign $item p c
if {![string match $p $tuple]} continue
Call $c $tuple
}
Debug.scoreboard { Notify $event/}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::scoreboard {
variable db ; # tuple array: tuple -> count of instances
variable wait {} ; # list of pending 'take's.
variable bind ; # List of bindings per event-type. Initially empty.
array set bind {
missing {}
put {}
take {}
}
namespace export {[a-z]*}
namespace ensemble create
}
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/sb/scoreboard.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Scoreboard, a singleton in-memory database used by the concurrent
# tasks and the main control to coordinate and communicate with each
# other. Actually a tuple-space with a bit of dressing disguising it.
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require debug
namespace eval ::scoreboard {}
# ### ### ### ######### ######### #########
## Tracing
debug off scoreboard
#debug on scoreboard
# ### ### ### ######### ######### #########
##
# The code here checks wether the package is running in the main
# thread or a task thread, and loads the associated implementation.
::apply {{here} {
if {![info exists ::task::type]} {
source [file join $here sb_server.tcl]
} else {
switch -exact -- $::task::type {
thread {
source [file join $here sb_client.tcl]
}
default {
return -code error "Unable to handle ${::task::type}-based tasks"
}
}
}
return
}} [file dirname [file normalize [info script]]]
# ### ### ### ######### ######### #########
## Ready
package provide scoreboard 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/scan/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::scan 0.1 [list source [file join $dir scan.tcl]]
|
<
<
|
|
|
Deleted lib/scan/scan.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task.
# Initial task.
# Scans the specified directory, looking for the BOOKFLOW database and
# JPEG images.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require task
namespace eval ::bookflow::scan {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/scan
#debug on bookflow/scan
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::scan {projectdir} {
Debug.bookflow/scan {Bookflow::Scan <$projectdir>}
task launch [list ::apply {{projectdir} {
package require bookflow::scan
bookflow::scan::TASK $projectdir
task::exit
}} $projectdir]
Debug.bookflow/scan {/}
return
}
# ### ### ### ######### ######### #########
## Internals
proc ::bookflow::scan::TASK {projectdir} {
package require debug
# Requisites for the task
package require blog
package require jpeg
package require fileutil
package require scoreboard
package require bookflow::db
#@SB AT *
scoreboard put [list AT $projectdir]
set dir [file normalize $projectdir]
set hasimages 0
set hasproject 0
# Iteratation over the files in the project directory.
# No traversal into subdirectories!
# Uses 'file'-like commands to determine the type of
# files (jpeg, bookflow database, other) for classification.
foreach f [lsort -dict [glob -nocomplain -directory $dir *]] {
Debug.bookflow/scan { Processing $f}
if {![file isfile $f]} {
Debug.bookflow/scan { Directory, ignored}
continue
}
set fx [fileutil::stripPath $dir $f]
if {[jpeg::isJPEG $f]} {
Debug.bookflow/scan { Image}
set hasimages 1
Log.bookflow {* Image $fx}
scoreboard put [list FILE $fx]
} elseif {[bookflow::db isBookflow $f]} {
Debug.bookflow/scan { Project database found}
set hasproject 1
Log.bookflow {% Project database $fx}
scoreboard put [list DATABASE $fx]
} else {
Debug.bookflow/scan { Ignored}
}
}
# Scan is complete, summarize the result. This triggers other
# tasks.
if {$hasproject} {
# We have a project. Might have images or not. Signal that
# this project needs verification, i.e. internal consistency
# check, and checking against the set of external images
# found.
Debug.bookflow/scan {Bookflow::Scan -> Verify project}
scoreboard put {PROJECT VERIFY}
} elseif {$hasimages} {
# While no project database is available, we have
# images. Signal that we should create a fresh project
# database from the images.
Debug.bookflow/scan {Bookflow::Scan -> Create project}
scoreboard put {PROJECT CREATE}
} else {
# Neither project, nor images were found. This is an abnormal
# situation. Signal the main controller to report on this.
Debug.bookflow/scan {Bookflow::Scan -> Nothing found}
set msg "The chosen project directory $projectdir contains neither images to process, nor a bookflow database.\n\nNothing will be done."
scoreboard put [list PROJECT ERROR $msg]
}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow {
namespace export scan
namespace ensemble create
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::scan 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/syscolor/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded syscolor 0.1 [list source [file join $dir syscolor.tcl]]
|
<
<
|
|
|
Deleted lib/syscolor/syscolor.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Determine and save system colors for use by (mega)widgets to
# visually match an application's appearance to the environment.
# Not specific to bookflow.
# ### ### ### ######### ######### #########
## Requisites
package require Tk
namespace eval ::syscolor {}
# ### ### ### ######### ######### #########
## API
proc ::syscolor::buttonFace {} { variable buttonFace ; return $buttonFace }
proc ::syscolor::highlight {} { variable highlight ; return $highlight }
proc ::syscolor::highlightText {} { variable highlightText ; return $highlightText }
# ### ######### ###########################
## State
namespace eval ::syscolor {
variable buttonFace
variable highlight
variable highlightText
}
# ### ######### ###########################
## Initialization
::apply {{} {
set w [listbox .__syscolor__]
variable buttonFace [$w cget -highlightbackground]
variable highlight [$w cget -selectbackground]
variable highlightText [$w cget -selectforeground]
destroy $w
return
} ::syscolor}
# ### ######### ###########################
## Ready
package provide syscolor 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/task/pkgIndex.tcl.
1
2
3
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded task::thread 0.1 [list source [file join $dir task.tcl]]
package ifneeded task 0.1 {package require task::thread ; package provide task 0.1}
|
<
<
<
|
|
|
Deleted lib/task/task.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Handling of (background) tasks running concurrently to the main
# system. This implementation uses thread, via package Thread.
# Alternate implementations could use sub-processses, or coroutines
# (green threads). The main difference between them all will be in
# the communication between main system and tasks, and between tasks,
# and setting up the per-task environment for this communication.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require Thread
namespace eval ::task {}
# ### ### ### ######### ######### #########
## Tracing
debug off task
#debug on task
# ### ### ### ######### ######### #########
## API & Implementation
proc ::task::launch {cmdprefix} {
# cmdprefix = The task to run concurrently.
Debug.task {Task <$cmdprefix>}
# Create thread for task
set id [thread::create]
Debug.task { Running in thread $id}
# Set magic information for communication with the main
# thread. The packages requiring special setup for proper
# communication will look for and recognize the magic and
# configure themselves accordingly.
Debug.task { Configure communication magic}
thread::send $id [list ::apply {{main ap} {
set ::auto_path $ap
namespace eval ::task {}
set ::task::type thread
set ::task::main $main
proc ::task::exit {} {
thread::exit
}
}} [thread::id] $::auto_path]
# And at last, launch the task
Debug.task { Launch...}
thread::send -async $id $cmdprefix
Debug.task {/}
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::task {
namespace export -clear *
namespace ensemble create -subcommands {}
}
# ### ### ### ######### ######### #########
## Ready
package provide task::thread 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/thumbnail/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::thumbnail 0.1 [list source [file join $dir thumbnail.tcl]]
|
<
<
|
|
|
Deleted lib/thumbnail/thumbnail.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task. Continuous.
# Creating and invalidating thumbnails.
# A producer in terms of "doc/interaction_pci.txt"
#
# Generated thumbnails are cached in the directory ".bookflow/thumb"
# of the project directory.
# Limits itself to no more than four actual threads in flight,
# i.e. performing image scaling. The scaling tasks do not exit on
# completion, but wait for more operations to perform. Communication
# and coordination is done through the scoreboard. As usual.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
package require scoreboard
namespace eval ::bookflow::thumbnail {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/thumbnail
#debug on bookflow/thumbnail
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::thumbnail {} {
Debug.bookflow/thumbnail {Bookflow::Thumbnail}
scoreboard wpeek {AT *} [namespace code thumbnail::Initialize]
Debug.bookflow/thumbnail {/}
return
}
proc ::bookflow::thumbnail::request {path size} {
return [list THUMBNAIL $path $size *]
}
# ### ### ### ######### ######### #########
## Internals. Process initialization
proc ::bookflow::thumbnail::Initialize {tuple} {
# tuple = (AT project)
lassign $tuple _ project
Debug.bookflow/thumbnail {Bookflow::Thumbnail Initialize <$project>}
# Monitor for thumbnail invalidation
WatchForInvalidation $project
# Launch the tasks doing the actual resizing.
variable max
for {set i 0} {$i < $max} {incr i} {
task launch [list ::apply {{project} {
package require bookflow::thumbnail
bookflow::thumbnail::ScalingTask $project
}} $project]
}
# Monitor for thumbnail creation requests.
WatchForMisses $project
Debug.bookflow/thumbnail {Bookflow::Thumbnail Initialize/}
return
}
# ### ### ### ######### ######### #########
## Internals. Invalidation processing. See doc/interaction_pci.txt (1).
proc ::bookflow::thumbnail::WatchForInvalidation {project} {
# doc/interaction_pci.txt (1)
Debug.bookflow/thumbnail {Bookflow::Thumbnail WatchForInvalidation}
scoreboard take {!THUMBNAIL *} [namespace code [list Invalidate $project]]
Debug.bookflow/thumbnail {Bookflow::Thumbnail WatchForInvalidation}
return
}
proc ::bookflow::thumbnail::Invalidate {project tuple} {
# tuple = (!THUMBNAIL path)
Debug.bookflow/thumbnail {Bookflow::Thumbnail Invalidate}
lassign $tuple _ path
scoreboard takeall [list THUMBNAIL $path *] [namespace code [list Cleanup $project $path]]
Debug.bookflow/thumbnail {Bookflow::Thumbnail Invalidate/}
return
}
proc ::bookflow::thumbnail::Cleanup {project path tuples} {
Debug.bookflow/thumbnail {Bookflow::Thumbnail Cleanup}
file delete [ThumbFullPath $project $path]
WatchForInvalidation $project
Debug.bookflow/thumbnail {Bookflow::Thumbnail Cleanup/}
return
}
# ### ### ### ######### ######### #########
## Internals. Creation request handling. See doc/interaction_pci.txt (2).
proc ::bookflow::thumbnail::WatchForMisses {project} {
Debug.bookflow/thumbnail {Bookflow::Thumbnail WatchForMisses}
# doc/interaction_pci.txt (2)
scoreboard bind missing {THUMBNAIL *} [namespace code [list MakeImage $project]]
Debug.bookflow/thumbnail {Bookflow::Thumbnail WatchForMisses}
return
}
proc ::bookflow::thumbnail::MakeImage {project pattern} {
# pattern = (THUMBNAIL path size *)
Debug.bookflow/thumbnail {Bookflow::Thumbnail MakeImage}
lassign $pattern _ path size
set dst [Path $path $size]
if {[file exists $project/$dst]} {
# The requested image already exists in the filesystem cache,
# simply make it available.
Return $path $size $dst
Debug.bookflow/thumbnail {Bookflow::Thumbnail MakeImage/}
return
}
# The image is not known yet. Forward the request to the scaling
# tasks to create the desired image.
RequestCreation $path $size $dst
Debug.bookflow/thumbnail {Bookflow::Thumbnail MakeImage/}
return
}
proc ::bookflow::thumbnail::Return {path size dst} {
scoreboard put [list THUMBNAIL $path $size $dst]
return
}
# ### ### ### ######### ######### #########
## Internals. Background tasks handling the actual scaling.
proc ::bookflow::thumbnail::RequestCreation {path size dst} {
scoreboard put [list SCALE $path $size $dst]
return
}
proc ::bookflow::thumbnail::ScalingTask {project} {
package require debug
Debug.bookflow/thumbnail {Bookflow::Thumbnail ScalingTask}
# Requisites for the task
package require bookflow::thumbnail
package require scoreboard
package require crimp ; wm withdraw .
package require img::png
package require img::jpeg
# Start waiting for requests.
ReadyForRequests $project
Debug.bookflow/thumbnail {Bookflow::Thumbnail ScalingTask/}
return
}
proc ::bookflow::thumbnail::ReadyForRequests {project} {
# Wait for more requests.
scoreboard take {SCALE *} [namespace code [list ScaleImage $project]]
return
}
proc ::bookflow::thumbnail::ScaleImage {project tuple} {
# tuple = (SCALE path size dstpath)
# result = (THUMBNAIL path dstpath)
Debug.bookflow/thumbnail {Bookflow::Thumbnail ScaleImage}
# Decode request
lassign $tuple _ path size dst
# Perform the scaling to requested size, reading jpeg, writing
# png, using crimp internally.
set photo [image create photo -file $project/$path]
set h [image height $photo]
set w [image width $photo]
if {$w > $h} {
# Landscape.
set h [expr {int($h*$size/$w)}]
set w $size
} else {
# Portrait.
set w [expr {int($w*$size/$h)}]
set h $size
}
crimp write 2tk $photo [crimp resize [crimp read tk $photo] $w $h]
file mkdir [file dirname $project/$dst]
$photo write $project/$dst -format png
image delete $photo
Return $path $size $dst
ReadyForRequests $project
Debug.bookflow/thumbnail {Bookflow::Thumbnail ScaleImage/}
return
}
# ### ### ### ######### ######### #########
## Internals. Path handling.
proc ::bookflow::thumbnail::Path {path size} {
return .bookflow/thumb$size/[file rootname $path].png
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow::thumbnail {
# Number of parallel scaling tasks.
variable max 4
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::thumbnail 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/verify/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded bookflow::verify 0.1 [list source [file join $dir verify.tcl]]
|
<
<
|
|
|
Deleted lib/verify/verify.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# Background task.
# Waiting for requests to verify an exiting project database.
# Launches the task when the request is found.
# Compares found images with images in the database.
# ### ### ### ######### ######### #########
## Requisites
package require debug
package require blog
package require task
namespace eval ::bookflow::verify {}
# ### ### ### ######### ######### #########
## Tracing
debug off bookflow/verify
#debug on bookflow/verify
# ### ### ### ######### ######### #########
## API & Implementation
proc ::bookflow::verify {} {
Debug.bookflow/verify {Bookflow::Verify Watch}
scoreboard take {PROJECT VERIFY} [namespace code verify::RUN]
Debug.bookflow/verify {/}
}
# ### ### ### ######### ######### #########
## Internals
proc ::bookflow::verify::RUN {tuple} {
Debug.bookflow/verify {Bookflow::Verify RUN}
Log.bookflow {Verifying project database...}
task launch [list ::apply {{} {
package require bookflow::verify
bookflow::verify::TASK
}}]
Debug.bookflow/verify {Bookflow::Verify RUN/}
return
}
proc ::bookflow::verify::TASK {} {
package require debug
Debug.bookflow/verify {Bookflow::Verify TASK}
# Requisites for the task
package require scoreboard
package require struct::set
package require bookflow::verify
package require bookflow::project ; # client
scoreboard wpeek {AT *} [namespace code BEGIN]
Debug.bookflow/verify {Bookflow::Verify TASK/}
return
}
proc ::bookflow::verify::BEGIN {tuple} {
variable defaultfile
Debug.bookflow/verify {Bookflow::Verify BEGIN <$tuple>}
# tuple = (AT project)
# Get the payload
lassign $tuple _ projectdir
# We wait until the server thread has completed initialization and
# is providing access to the database.
::bookflow::project::ok [namespace code [list WaitForServerStart $projectdir]]
Debug.bookflow/verify {Bookflow::Verify BEGIN/}
return
}
proc ::bookflow::verify::WaitForServerStart {project} {
Debug.bookflow/verify {Bookflow::Verify WaitForServerStart}
# Fill the database using the image files found by the scanner.
scoreboard takeall {FILE*} [namespace code [list FILES $project]]
Debug.bookflow/verify {Bookflow::Verify WaitForServerStart/}
return
}
proc ::bookflow::verify::FILES {project tuples} {
Debug.bookflow/verify {Bookflow::Verify FILES}
# tuples = list ((FILE *)...)
# We now have the files found by the scanner...
set scanned {}
foreach def [lsort -dict -index 1 $tuples] {
lassign $def _ jpeg
lappend scanned $jpeg
}
# ... and the files known to the project.
set known [::bookflow::project files]
# Separate them into newly added, gone missing, and unchanged.
lassign [struct::set intersect3 $scanned $known] \
unchanged new del
# New files are handled like the create task does, i.e. they are
# added to the @SCRATCH book. NOTE that we are not adding them to
# the scoreboard yet. This is done later, when all books have been
# updated per the images.
foreach jpeg $new {
::bookflow::project book extend @SCRATCH $jpeg \
[file mtime $project/$jpeg]
}
# Removed files are moved from whereever they are into the @TRASH
# book. Except those which are already there.
foreach jpeg $new {
set jbook [::bookflow::project book holding $jpeg]
if {$jbook eq "@TRASH"} continue
::bookflow::project book move @TRASH $jpeg
}
# Unchanged files ... Those in @TRASH have apparently been
# restored as files, so these move to @SCRATCH. Even so, we cannot be sure that their derived data is ok,
# forcing us to invalidate them.
foreach jpeg $unchanged {
set jbook [::bookflow::project book holding $jpeg]
if {$jbook eq "@TRASH"} {
# FUTURE :: See if we can remember their old book
# FUTURE :: somewhere, and restore them to that.
::bookflow::project book move @SCRATCH $jpeg
set modified 1
} else {
# Ok, this file was present before, and is still present.
# Now let us check if it was modified since the project
# was used the last time. Because if so the derived data
# we have is useless and need to be regenerated.
set current [file mtime $project/$jpeg]
set last [::bookflow::project file mtime $jpeg]
set modified [expr {$current != $last}]
}
if {$modified} {
# Invalidation requests. We can do the statistics here
# because nobody is in a position to ask for it and we
# know how to do it. For the other things we rely on their
# producers for the invalidation.
::bookflow::project statistics unset $jpeg
scoreboard put [list !THUMBNAIL $jpeg]
scoreboard put [list !GREYSCALE $jpeg]
}
}
# Closing work ...
# ... pull books out of the database and declare them ...
foreach b [::bookflow::project books] {
Debug.bookflow/verify { BOOK $b}
scoreboard put [list BOOK $b]
# ... pull files out and declare them ...
foreach {jpeg serial} [::bookflow::project book files $b] {
Debug.bookflow/verify { IMAGE $jpeg $serial $b}
scoreboard put [list IMAGE $jpeg $serial $b]
# Pre-load any statistics information, shortcircuiting its
# producer.
set statistics [::bookflow::project statistics get $jpeg]
if {$statistics ne {}} {
scoreboard put [list STATISTICS $jpeg $statistics]
}
}
}
Debug.bookflow/verify {Bookflow::Verify FILES/}
task::exit
return
}
# ### ### ### ######### ######### #########
## Ready
namespace eval ::bookflow {
namespace export verify
namespace ensemble create
namespace eval verify {
variable defaultfile BOOKFLOW
}
}
# ### ### ### ######### ######### #########
## Ready
package provide bookflow::verify 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted lib/wlog/pkgIndex.tcl.
1
2
|
if {![package vsatisfies [package require Tcl] 8.5]} return
package ifneeded widget::log 0.1 [list source [file join $dir wlog.tcl]]
|
<
<
|
|
|
Deleted lib/wlog/wlog.tcl.
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
|
## -*- tcl -*-
# ### ### ### ######### ######### #########
# A simple log window where system activity can be shown to the end user.
# Not specific to bookflow.
# FUTURE expansion
# Tagging of messages, allowing for customization of appearance (like
# colorization).
# ### ### ### ######### ######### #########
## Requisites
package require Tcl 8.5
package require Tk
package require snit
package require widget::scrolledwindow
# ### ### ### ######### ######### #########
## Tracing
# ### ### ### ######### ######### #########
## Implementation
snit::widgetadaptor ::widget::log {
delegate option * to mytext
constructor {args} {
installhull using widget::scrolledwindow \
-borderwidth 1 -relief sunken
set mytext [text $win.log -height 5 -width 80 -font {Helvetica -18}]
$hull setwidget $mytext
$self configurelist $args
return
}
method puts {text} {
$self puts* $text\n
return
}
method puts* {text} {
$mytext configure -state normal
$mytext insert end $text
$mytext see end
$mytext configure -state disabled
return
}
# ### ### ### ######### ######### #########
##
variable mytext
##
# ### ### ### ######### ######### #########
}
# ### ### ### ######### ######### #########
## Ready
package provide widget::log 0.1
return
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|
Deleted tools/doc_scoreboard.tcl.
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
|
#!/bin/sh
# -*- tcl -*- \
exec tclsh "\$0" ${1+"$@"}
# tools
# - scan the bookflow sources for scoreboard access and generate
# a database telling us who accesses what and how.
# ## ### ##### ######## ############# #####################
package require Tcl 8.5
package require fileutil
# ## ### ##### ######## ############# #####################
proc main {tooldir} {
dump [sbscan [file dirname $tooldir]]
return
}
proc sbscan {topdir} {
#puts Scanning\ $topdir...
set db {}
foreach f [fileutil::findByPattern $topdir -glob -- *.tcl] {
if {[file isdirectory $f]} continue
if {[string match *doc_scoreboard* $f]} continue
if {[string match *pkgIndex* $f]} continue
lappend db {*}[scansbfile $f [fileutil::stripPath $topdir $f]]
}
return $db
}
proc scansbfile {f fname} {
#puts \t$f...
array set t {}
set TUPLE {}
foreach line [split [fileutil::cat $f] \n] {
set line [string trim $line]
switch -glob -- $line {
\#* {
# ... pragmas
if {[string match {*@SB *} $line]} {
regexp {@SB (.*)$} $line -> TUPLE
}
}
package*provide* {
# might use this in future.
# for new we key on the file name.
lassign $line _ _ package _
}
scoreboard* {
#puts \t\t|$line|
word line ; # scoreboard
set method [word line]
switch -exact -- $method {
put {
# remainder = tuples
while {$line ne {}} {
set tuple [tuple line]
lappend t($tuple) $method
}
}
take -
takeall -
peek -
wpeek {
set tuple [tuple line]
lappend t($tuple) $method
}
unbind -
bind {
set event [word line]
set tuple [tuple line]
lappend t($tuple) [list $method $event]
}
default {
# unknown method.
puts \tUnknown\ method \"$method\" found
}
}
}
}
}
if {![array size t]} { return }
return [list $fname [array get t]]
# result = dict (file -> dict (tuple -> list (action...)))
}
proc tuple {svar} {
upvar 1 $svar string TUPLE TUPLE
set tuple [word string]
if {$TUPLE ne {}} {
set tuple $TUPLE
set TUPLE {}
}
return $tuple
}
proc word {svar} {
upvar 1 $svar string
set string [string trim $string]
#puts "\[word \"$string\"\]"
if {[string match "\$\{*" $string]} {
set c varb
regexp {(\${[^\}]+})[ ]+(.*)$} $string -> word remainder
} elseif {[string match "\$*" $string]} {
set c var
expr {[regexp {(\$[^ ]+)[ ]+(.*)$} $string -> word remainder] ||
[regexp {(\$[^ ]+)()$} $string -> word remainder]}
} elseif {[string match "\\\[*" $string]} {
set c cmd
set patterni "(\\\[\[^\]\]+\\\])\[ \]+(.*)$"
set patterne "(\\\[\[^\]\]+\\\])()$"
expr {[regexp $patterni $string -> word remainder] ||
[regexp $patterne $string -> word remainder]}
} elseif {[string match "\\\{*" $string]} {
set c w
set patterni "(\\\{\[^\}\]+\\\})\[ \]+(.*)$"
set patterne "(\\\{\[^\}\]+\\\})()$"
expr {[regexp $patterni $string -> word remainder] ||
[regexp $patterne $string -> word remainder]}
# strip the braces.
set word [string range $word 1 end-1]
} else {
set c w
regexp {([^ ]+)[ ]+(.*)$} $string -> word remainder
}
if {![info exists word]} {
error "word error ($string)"
}
#puts \t$c|$word|$remainder|
set string $remainder
return $word
}
proc dump {db} {
# db = dict (file -> dict (tuple -> list (action...)))
#array set d $db
#parray d
# Invert the structure to make the tuple (patterns) the major index.
# D = dict (tuple -> dict (action -> list (file...)))
set D {}
foreach {fname data} $db {
foreach {tuple actions} $data {
set actions [lsort -unique $actions]
set A {}
foreach a $actions {
dict lappend A $a $fname
}
dict lappend D $tuple $A
}
}
set db $D
set D {}
foreach {tuple data} $db {
# data = list (dict (action -> list(fname)))
array set X {}
foreach dict $data {
lassign $dict action files
lappend X($action) {*}$files
}
#parray X
lappend D $tuple [array get X]
array unset X
}
#puts $D
#return
# Write structure in machine- and human-readable form.
foreach {tuple fa} [dictsort $D] {
puts "\ntuple [list $tuple] \{"
# todo description - get via pragma's
puts "\} \{"
#puts "==== $fa ===="
foreach {action files} [dictsort $fa] {
set files [lsort -unique $files]
puts " $action \{\n\t[join $files "\n\t"]\n \}"
}
puts "\}"
}
#array set T $D
#parray T
return
}
proc dictsort {dict} {
array set a $dict
set out [list]
foreach key [lsort [array names a]] {
lappend out $key $a($key)
}
return $out
}
# ## ### ##### ######## ############# #####################
main [file dirname [file normalize [info script]]]
exit
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
|