summaryrefslogtreecommitdiff
path: root/src/merge.tcl
blob: 9088c31f6cd9cb5acac491e6eaea6b4c5a063baa (plain) (blame)
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
# Show details of a 3-way merge operation.  The left-most column is the
# common ancestor.  The next two columns are edits of that common ancestor.
# The right-most column is the result of the merge.
#
# Several variables will have been initialized:
#
#    ncontext            The number of lines of context to show on each change
#
#    fossilexe           Pathname of the fossil program
#
#    filelist            A list of "merge-type filename" pairs.
#
#    darkmode            Boolean.  True for dark mode
#
#    debug               Boolean.  True for debugging output
#
# If the "filelist" global variable is defined, then it is a list of
# alternating "merge-type names" (ex: UPDATE, MERGE, CONFLICT, ERROR) and
# filenames.  In that case, the initial display shows the changes for
# the first pair on the list and there is a optionmenu that allows the
# user to select other fiels on the list.
#
# This header comment is stripped off by the "mkbuiltin.c" program.
#
package require Tk

array set CFG_light {
  TITLE      {Fossil Merge}
  LN_COL_BG  #dddddd
  LN_COL_FG  #444444
  TXT_COL_BG #ffffff
  TXT_COL_FG #000000
  MKR_COL_BG #444444
  MKR_COL_FG #dddddd
  CHNG_BG    #d0d070
  ADD_BG     #c0ffc0
  RM_BG      #ffc0c0
  HR_FG      #444444
  HR_PAD_TOP 4
  HR_PAD_BTM 8
  FN_BG      #444444
  FN_FG      #ffffff
  FN_PAD     5
  ERR_FG     #ee0000
  PADX       5
  WIDTH      80
  HEIGHT     45
  LB_HEIGHT  25
}

array set CFG_dark {
  TITLE      {Fossil Merge}
  LN_COL_BG  #dddddd
  LN_COL_FG  #444444
  TXT_COL_BG #3f3f3f
  TXT_COL_FG #dcdccc
  MKR_COL_BG #444444
  MKR_COL_FG #dddddd
  CHNG_BG    #6a6a00
  ADD_BG     #57934c
  RM_BG      #ef6767
  HR_FG      #444444
  HR_PAD_TOP 4
  HR_PAD_BTM 8
  FN_BG      #5e5e5e
  FN_FG      #ffffff
  FN_PAD     5
  ERR_FG     #ee0000
  PADX       5
  WIDTH      80
  HEIGHT     45
  LB_HEIGHT  25
}

array set CFG_arr {
  0          CFG_light
  1          CFG_dark
}

array set CFG [array get $CFG_arr($darkmode)]

if {![namespace exists ttk]} {
  interp alias {} ::ttk::scrollbar {} ::scrollbar
  interp alias {} ::ttk::menubutton {} ::menubutton
}

proc dehtml {x} {
  set x [regsub -all {<[^>]*>} $x {}]
  return [string map {&amp; & &lt; < &gt; > &#39; ' &quot; \"} $x]
}

proc cols {} {
  return [list .lnA .txtA .lnB .txtB .lnC .txtC .lnD .txtD]
}

proc colType {c} {
  regexp {[a-z]+} $c type
  return $type
}

proc readMerge {args} {
  global fossilexe ncontext current_file debug
  if {$ncontext=="All"} {
    set cmd "| $fossilexe merge-info -c -1"
  } else {
    set cmd "| $fossilexe merge-info -c $ncontext"
  }
  if {[info exists current_file]} {
    regsub {^[A-Z]+ } $current_file {} fn
    lappend cmd -tcl $fn
  }
  if {$debug} {
    regsub {^\| +} $cmd {} cmd2
    puts $cmd2
    flush stdout
  }
  if {[catch {
    set in [open $cmd r]
    fconfigure $in -encoding utf-8
    set mergetxt [read $in]
    close $in
  } msg]} {
    tk_messageBox -message "Unable to run command: \"$cmd\""
    set mergetxt {}
  }
  foreach c [cols] {
    $c config -state normal
    $c delete 1.0 end
  }
  set lnA 1
  set lnB 1
  set lnC 1
  set lnD 1
  foreach {A B C D} $mergetxt {
    set key1 [string index $A 0]
    if {$key1=="S"} {
      scan [string range $A 1 end] "%d %d %d %d" nA nB nC nD
      foreach x {A B C D} {
        set N [set n$x]
        incr ln$x $N
        if {$N>0} {
          .ln$x insert end ...\n hrln
          .txt$x insert end [string repeat . 30]\n hrtxt
        } else {
          .ln$x insert end \n hrln
          .txt$x insert end \n hrtxt
        }
      }
      continue
    }
    set key2 [string index $B 0]
    set key3 [string index $C 0]
    set key4 [string index $D 0]
    if {$key1=="."} {
      .lnA insert end \n -
      .txtA insert end \n -
    } elseif {$key1=="N"} {
      .nameA config -text [string range $A 1 end]
    } else {
      .lnA insert end $lnA\n -
      incr lnA
      if {$key1=="X"} {
        .txtA insert end [string range $A 1 end]\n rm
      } else {
        .txtA insert end [string range $A 1 end]\n -
      }
    }
    if {$key2=="."} {
      .lnB insert end \n -
      .txtB insert end \n -
    } elseif {$key2=="N"} {
      .nameB config -text [string range $B 1 end]
    } else {
      .lnB insert end $lnB\n -
      incr lnB
      if {$key4=="2"} {set tag chng} {set tag -}
      if {$key2=="1"} {
        .txtB insert end [string range $A 1 end]\n $tag
      } elseif {$key2=="X"} {
        .txtB insert end [string range $B 1 end]\n rm
      } else {
        .txtB insert end [string range $B 1 end]\n $tag
      }
    }
    if {$key3=="."} {
      .lnC insert end \n -
      .txtC insert end \n -
    } elseif {$key3=="N"} {
      .nameC config -text [string range $C 1 end]
    } else {
      .lnC insert end $lnC\n -
      incr lnC
      if {$key4=="3"} {set tag add} {set tag -}
      if {$key3=="1"} {
        .txtC insert end [string range $A 1 end]\n $tag
      } elseif {$key3=="2"} {
        .txtC insert end [string range $B 1 end]\n chng
      } elseif {$key3=="X"} {
        .txtC insert end [string range $C 1 end]\n rm
      } else {
        .txtC insert end [string range $C 1 end]\n $tag
      }
    }
    if {$key4=="."} {
      .lnD insert end \n -
      .txtD insert end \n -
    } elseif {$key4=="N"} {
      .nameD config -text [string range $D 1 end]
    } else {
      .lnD insert end $lnD\n -
      incr lnD
      if {$key4=="1"} {
        .txtD insert end [string range $A 1 end]\n -
      } elseif {$key4=="2"} {
        .txtD insert end [string range $B 1 end]\n chng
      } elseif {$key4=="3"} {
        .txtD insert end [string range $C 1 end]\n add
      } elseif {$key4=="X"} {
        .txtD insert end [string range $D 1 end]\n rm
      } else {
        .txtD insert end [string range $D 1 end]\n -
      }
    }
  }
  foreach c [cols] {
    set type [colType $c]
    if {$type ne "txt"} {
      $c config -width 6; # $widths($type)
    }
    $c config -state disabled
  }
  set mx $lnA
  if {$lnB>$mx} {set mx $lnB}
  if {$lnC>$mx} {set mx $lnC}
  if {$lnD>$mx} {set mx $lnD}
  global lnWidth
  set lnWidth [string length [format +%d $mx]]
  .lnA config -width $lnWidth
  .lnB config -width $lnWidth
  .lnC config -width $lnWidth
  .lnD config -width $lnWidth
  grid columnconfig . {0 2 4 6} -minsize $lnWidth
}

proc viewDiff {idx} {
  .txtA yview $idx
  .txtA xview moveto 0
}

proc cycleDiffs {{reverse 0}} {
  if {$reverse} {
    set range [.txtA tag prevrange fn @0,0 1.0]
    if {$range eq ""} {
      viewDiff {fn.last -1c}
    } else {
      viewDiff [lindex $range 0]
    }
  } else {
    set range [.txtA tag nextrange fn {@0,0 +1c} end]
    if {$range eq "" || [lindex [.txtA yview] 1] == 1} {
      viewDiff fn.first
    } else {
      viewDiff [lindex $range 0]
    }
  }
}

proc xvis {col} {
  set view [$col xview]
  return [expr {[lindex $view 1]-[lindex $view 0]}]
}

proc scroll-x {args} {
  set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}]
  eval $c xview $args
}

interp alias {} scroll-y {} .txtA yview

proc noop {args} {}

proc enableSync {axis} {
  update idletasks
  interp alias {} sync-$axis {}
  rename _sync-$axis sync-$axis
}

proc disableSync {axis} {
  rename sync-$axis _sync-$axis
  interp alias {} sync-$axis {} noop
}

proc sync-y {first last} {
  disableSync y
  foreach c [cols] {
    $c yview moveto $first
  }
  if {$first > 0 || $last < 1} {
    grid .sby
    .sby set $first $last
  } else {
    grid remove .sby
  }
  enableSync y
}

wm withdraw .
wm title . $CFG(TITLE)
wm iconname . $CFG(TITLE)
# Keystroke bindings for on the top-level window for navigation and
# control also fire when those same keystrokes are pressed in the
# Search entry box.  Disable them, to prevent the diff screen from
# disappearing abruptly and unexpectedly when searching for "q".
#
bind . <Control-q> exit
bind . <Control-p> {catch searchPrev; break}
bind . <Control-n> {catch searchNext; break}
bind . <Escape><Escape> exit
bind . <Destroy> {after 0 exit}
bind . <Tab> {cycleDiffs; break}
bind . <<PrevWindow>> {cycleDiffs 1; break}
bind . <Control-f> {searchOnOff; break}
bind . <Control-g> {catch searchNext; break}
bind . <Return> {
  event generate .bb.files <1>
  event generate .bb.files <ButtonRelease-1>
  break
}
foreach {key axis args} {
  Up    y {scroll -5 units}
  k     y {scroll -5 units}
  Down  y {scroll 5 units}
  j     y {scroll 5 units}
  Left  x {scroll -5 units}
  h     x {scroll -5 units}
  Right x {scroll 5 units}
  l     x {scroll 5 units}
  Prior y {scroll -1 page}
  b     y {scroll -1 page}
  Next  y {scroll 1 page}
  space y {scroll 1 page}
  Home  y {moveto 0}
  g     y {moveto 0}
  End   y {moveto 1}
} {
  bind . <$key> "scroll-$axis $args; break"
  bind . <Shift-$key> continue
}

frame .bb
::ttk::menubutton .bb.diff2 -text {2-way diff} -menu .bb.diff2.m
menu .bb.diff2.m -tearoff 0
.bb.diff2.m add command -label {baseline vs. local} -command {two-way 12}
.bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13}
.bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23}

# Bring up a separate two-way diff between a pair of columns
# the argument is one of:
#    12       Baseline versus Local
#    13       Baseline versus Merge-in
#    23       Local versus Merge-in
#
proc two-way {mode} {
  global current_file fossilexe debug darkmode ncontext
  regsub {^[A-Z]+ } $current_file {} fn
  set cmd $fossilexe
  lappend cmd merge-info --diff$mode $fn -c $ncontext
  if {$darkmode} {
    lappend cmd --dark
  }
  if {$debug} {
    lappend cmd --tkdebug
    puts $cmd
    flush stdout
  }
  exec {*}$cmd &
}

set useOptionMenu 1
if {[info exists filelist]} {
  set current_file "[lindex $filelist 0] [lindex $filelist 1]"
  if {[llength $filelist]>2} {
    trace add variable current_file write readMerge
  
    if {$tcl_platform(os)=="Darwin" || [llength $filelist]<30} {
      set fnlist {}
      foreach {op fn} $filelist {lappend fnlist "$op $fn"}
      tk_optionMenu .bb.files current_file {*}$fnlist
    } else {
      set useOptionMenu 0
      ::ttk::menubutton .bb.files -text $current_file
      if {[tk windowingsystem] eq "win32"} {
        ::ttk::style theme use winnative
        .bb.files configure -padding {20 1 10 2}
      }
      toplevel .wfiles
      wm withdraw .wfiles
      update idletasks
      wm transient .wfiles .
      wm overrideredirect .wfiles 1
      set ht [expr {[llength $filelist]/2}]
      if {$ht>$CFG(LB_HEIGHT)} {set ht $CFG(LB_HEIGHT)}
      listbox .wfiles.lb -width 0 -height $ht -activestyle none \
        -yscroll {.wfiles.sb set}
      set mx 1
      foreach {op fn} $filelist {
        set n [string length $fn]
        if {$n>$mx} {set mx $n}
        .wfiles.lb insert end "$op $fn"
      }
      .bb.files config -width $mx
      ::ttk::scrollbar .wfiles.sb -command {.wfiles.lb yview}
      grid .wfiles.lb .wfiles.sb -sticky ns
      bind .bb.files <1> {
        set x [winfo rootx %W]
        set y [expr {[winfo rooty %W]+[winfo height %W]}]
        wm geometry .wfiles +$x+$y
        wm deiconify .wfiles
        focus .wfiles.lb
      }
      bind .wfiles <FocusOut> {wm withdraw .wfiles}
      bind .wfiles <Escape> {focus .}
      foreach evt {1 Return} {
        bind .wfiles.lb <$evt> {
          set ii [%W curselection]
          set ::current_file [%W get $ii]
          .bb.files config -text $::current_file
          focus .
          break
        }
      }
      bind .wfiles.lb <Motion> {
        %W selection clear 0 end
        %W selection set @%x,%y
      }
    }
  }
}

label .bb.ctxtag -text "Context:"
set context_choices {3 6 12 25 50 100 All}
if {$ncontext<0} {set ncontext All}
trace add variable ncontext write readMerge
if {$tcl_platform(os)=="Darwin" || $useOptionMenu} {
  tk_optionMenu .bb.ctx ncontext {*}$context_choices
} else {
  ::ttk::menubutton .bb.ctx -text $ncontext
  if {[tk windowingsystem] eq "win32"} {
    ::ttk::style theme use winnative
    .bb.ctx configure -padding {20 1 10 2}
  }
  toplevel .wctx
  wm withdraw .wctx
  update idletasks
  wm transient .wctx .
  wm overrideredirect .wctx 1
  listbox .wctx.lb -width 0 -height 7 -activestyle none
  .wctx.lb insert end {*}$context_choices
  pack .wctx.lb
  bind .bb.ctx <1> {
    set x [winfo rootx %W]
    set y [expr {[winfo rooty %W]+[winfo height %W]}]
    wm geometry .wctx +$x+$y
    wm deiconify .wctx
    focus .wctx.lb
  }
  bind .wctx <FocusOut> {wm withdraw .wctx}
  bind .wctx <Escape> {focus .}
  foreach evt {1 Return} {
    bind .wctx.lb <$evt> {
      set ::ncontext [lindex $::context_choices [%W curselection]]
      .bb.ctx config -text $::ncontext
      focus .
      break
    }
  }
  bind .wctx.lb <Motion> {
    %W selection clear 0 end
    %W selection set @%x,%y
  }
}

foreach {side syncCol} {A .txtA B .txtB C .txtC D .txtD} {
  set ln .ln$side
  text $ln -width 6
  $ln tag config - -justify right

  set txt .txt$side
  text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
    -xscroll ".sbx$side set"
  catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5
  foreach tag {add rm chng} {
    $txt tag config $tag -background $CFG([string toupper $tag]_BG)
    $txt tag lower $tag
  }
  $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \
    -justify center
  $txt tag config err -foreground $CFG(ERR_FG)
}
text .mkr

set mxwidth [lindex [wm maxsize .] 0]
while {$CFG(WIDTH)>=40} {
  set wanted [expr {([winfo reqwidth .lnA]+[winfo reqwidth .txtA])*4+30}]
  if {$wanted<=$mxwidth} break
  incr CFG(WIDTH) -10
  .txtA config -width $CFG(WIDTH)
  .txtB config -width $CFG(WIDTH)
  .txtC config -width $CFG(WIDTH)
  .txtD config -width $CFG(WIDTH)
}

foreach c [cols] {
  set keyPrefix [string toupper [colType $c]]_COL_
  if {[tk windowingsystem] eq "win32"} {$c config -font {courier 9}}
  $c config -bg $CFG(${keyPrefix}BG) -fg $CFG(${keyPrefix}FG) -borderwidth 0 \
    -padx $CFG(PADX) -yscroll sync-y
  $c tag config hrln -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
     -foreground $CFG(HR_FG) -justify right
  $c tag config hrtxt  -spacing1 $CFG(HR_PAD_TOP) -spacing3 $CFG(HR_PAD_BTM) \
     -foreground $CFG(HR_FG) -justify center
  $c tag config fn -spacing1 $CFG(FN_PAD) -spacing3 $CFG(FN_PAD)
  bindtags $c ". $c Text all"
  bind $c <1> {focus %W}
}

label .nameA
label .nameB
label .nameC
label .nameD -text {Merge Result}
::ttk::scrollbar .sby -command {.txtA yview} -orient vertical
::ttk::scrollbar .sbxA -command {.txtA xview} -orient horizontal
::ttk::scrollbar .sbxB -command {.txtB xview} -orient horizontal
::ttk::scrollbar .sbxC -command {.txtC xview} -orient horizontal
::ttk::scrollbar .sbxD -command {.txtD xview} -orient horizontal
frame .spacer

update idletasks

proc searchOnOff {} {
  if {[info exists ::search]} {
    unset ::search
    .txtA tag remove search 1.0 end
    .txtB tag remove search 1.0 end
    .txtC tag remove search 1.0 end
    .txtD tag remove search 1.0 end
    pack forget .bb.sframe
    focus .
  } else {
    set ::search .txtA
    if {![winfo exists .bb.sframe]} {
      frame .bb.sframe
      ::ttk::entry .bb.sframe.e -width 10
      pack .bb.sframe.e -side left -fill y -expand 1
      bind .bb.sframe.e <Return> {searchNext; break}
      ::ttk::button .bb.sframe.nx -text \u2193 -width 1 -command searchNext
      ::ttk::button .bb.sframe.pv -text \u2191 -width 1 -command searchPrev
      tk_optionMenu .bb.sframe.typ ::search_type \
           Exact {No Case} {RegExp} {Whole Word}
      .bb.sframe.typ config -width 10
      set ::search_type Exact
      pack .bb.sframe.nx .bb.sframe.pv .bb.sframe.typ -side left
    }
    pack .bb.sframe -side left
    after idle {focus .bb.sframe.e}
  }
}
proc searchNext {} {searchStep -forwards +1 1.0 end}
proc searchPrev {} {searchStep -backwards -1 end 1.0}
proc searchStep {direction incr start stop} {
  set pattern [.bb.sframe.e get]
  if {$pattern==""} return
  set count 0
  set w $::search
  switch $w {
    .txtA {set other .txtB}
    .txtB {set other .txtC}
    .txtC {set other .txtD}
    default {set other .txtA}
  }
  if {[lsearch [$w mark names] search]<0} {
    $w mark set search $start
  }
  switch $::search_type {
    Exact        {set st -exact}
    {No Case}    {set st -nocase}
    {RegExp}     {set st -regexp}
    {Whole Word} {set st -regexp; set pattern \\y$pattern\\y}
  }
  set idx [$w search -count count $direction $st -- \
              $pattern "search $incr chars" $stop]
  if {"$idx"==""} {
    set idx [$other search -count count $direction $st -- $pattern $start $stop]
    if {"$idx"!=""} {
      set this $w
      set w $other
      set other $this
    } else {
      set idx [$w search -count count $direction $st -- $pattern $start $stop]
    }
  }
  $w tag remove search 1.0 end
  $w mark unset search
  $other tag remove search 1.0 end
  $other mark unset search
  if {"$idx"!=""} {
    $w mark set search $idx
    $w yview -pickplace $idx
    $w tag add search search "$idx +$count chars"
    $w tag config search -background {#fcc000}
  }
  set ::search $w
}
::ttk::button .bb.quit -text {Quit} -command exit
::ttk::button .bb.search -text {Search} -command searchOnOff
pack .bb.quit -side left -fill y
pack .bb.diff2 -side left -fill y
if {[winfo exists .bb.files]} {
  pack .bb.files -side left -fill y
}
pack .bb.ctxtag .bb.ctx -side left -fill y
pack .bb.search -side left -fill y
grid rowconfigure . 1 -weight 1 -minsize [winfo reqheight .nameA]
grid rowconfigure . 2 -weight 100
readMerge
grid .bb -row 0 -columnspan 8
grid .nameA -row 1 -column 1 -sticky ew
grid .nameB -row 1 -column 3 -sticky ew
grid .nameC -row 1 -column 5 -sticky ew
grid .nameD -row 1 -column 7 -sticky ew
eval grid [cols] -row 2 -sticky nsew
grid .sby -row 2 -column 8 -sticky ns
grid .sbxA -row 3 -column 1 -sticky ew
grid .sbxB -row 3 -column 3 -sticky ew
grid .sbxC -row 3 -column 5 -sticky ew
grid .sbxD -row 3 -column 7 -sticky ew
grid columnconfigure . {0 2 4 6} \
   -weight 1 -uniform a -minsize [winfo reqwidth .lnA]
grid columnconfigure . {1 3 5 7} -weight 100 -uniform b

.spacer config -height [winfo height .sbxA]
wm deiconify .