Spaces:
Running
Running
# git-gui status bar mega-widget | |
# Copyright (C) 2007 Shawn Pearce | |
# The status_bar class manages the entire status bar. It is possible for | |
# multiple overlapping asynchronous operations to want to display status | |
# simultaneously. Each one receives a status_bar_operation when it calls the | |
# start method, and the status bar combines all active operations into the | |
# line of text it displays. Most of the time, there will be at most one | |
# ongoing operation. | |
# | |
# Note that the entire status bar can be either in single-line or two-line | |
# mode, depending on the constructor. Multiple active operations are only | |
# supported for single-line status bars. | |
class status_bar { | |
field allow_multiple ; # configured at construction | |
field w ; # our own window path | |
field w_l ; # text widget we draw messages into | |
field w_c ; # canvas we draw a progress bar into | |
field c_pack ; # script to pack the canvas with | |
field baseline_text ; # text to show if there are no operations | |
field status_bar_text ; # combined text for all operations | |
field operations ; # list of current ongoing operations | |
# The status bar can display a progress bar, updated when consumers call the | |
# update method on their status_bar_operation. When there are multiple | |
# operations, the status bar shows the combined status of all operations. | |
# | |
# When an overlapping operation completes, the progress bar is going to | |
# abruptly have one fewer operation in the calculation, causing a discontinuity. | |
# Therefore, whenever an operation completes, if it is not the last operation, | |
# this counter is increased, and the progress bar is calculated as though there | |
# were still another operation at 100%. When the last operation completes, this | |
# is reset to 0. | |
field completed_operation_count | |
constructor new {path} { | |
global use_ttk NS | |
set w $path | |
set w_l $w.l | |
set w_c $w.c | |
# Standard single-line status bar: Permit overlapping operations | |
set allow_multiple 1 | |
set baseline_text "" | |
set operations [list] | |
set completed_operation_count 0 | |
${NS}::frame $w | |
if {!$use_ttk} { | |
$w configure -borderwidth 1 -relief sunken | |
} | |
${NS}::label $w_l \ | |
-textvariable @status_bar_text \ | |
-anchor w \ | |
-justify left | |
pack $w_l -side left | |
set c_pack [cb _oneline_pack] | |
bind $w <Destroy> [cb _delete %W] | |
return $this | |
} | |
method _oneline_pack {} { | |
$w_c conf -width 100 | |
pack $w_c -side right | |
} | |
constructor two_line {path} { | |
global NS | |
set w $path | |
set w_l $w.l | |
set w_c $w.c | |
# Two-line status bar: Only one ongoing operation permitted. | |
set allow_multiple 0 | |
set baseline_text "" | |
set operations [list] | |
set completed_operation_count 0 | |
${NS}::frame $w | |
${NS}::label $w_l \ | |
-textvariable @status_bar_text \ | |
-anchor w \ | |
-justify left | |
pack $w_l -anchor w -fill x | |
set c_pack [list pack $w_c -fill x] | |
bind $w <Destroy> [cb _delete %W] | |
return $this | |
} | |
method ensure_canvas {} { | |
if {[winfo exists $w_c]} { | |
$w_c coords bar 0 0 0 20 | |
} else { | |
canvas $w_c \ | |
-height [expr {int([winfo reqheight $w_l] * 0.6)}] \ | |
-borderwidth 1 \ | |
-relief groove \ | |
-highlightt 0 | |
$w_c create rectangle 0 0 0 20 -tags bar -fill navy | |
eval $c_pack | |
} | |
} | |
method show {msg} { | |
$this ensure_canvas | |
set baseline_text $msg | |
$this refresh | |
} | |
method start {msg {uds {}}} { | |
set baseline_text "" | |
if {!$allow_multiple && [llength $operations]} { | |
return [lindex $operations 0] | |
} | |
$this ensure_canvas | |
set operation [status_bar_operation::new $this $msg $uds] | |
lappend operations $operation | |
$this refresh | |
return $operation | |
} | |
method refresh {} { | |
set new_text "" | |
set total [expr $completed_operation_count * 100] | |
set have $total | |
foreach operation $operations { | |
if {$new_text != ""} { | |
append new_text " / " | |
} | |
append new_text [$operation get_status] | |
set total [expr $total + 100] | |
set have [expr $have + [$operation get_progress]] | |
} | |
if {$new_text == ""} { | |
set new_text $baseline_text | |
} | |
set status_bar_text $new_text | |
if {[winfo exists $w_c]} { | |
set pixel_width 0 | |
if {$have > 0} { | |
set pixel_width [expr {[winfo width $w_c] * $have / $total}] | |
} | |
$w_c coords bar 0 0 $pixel_width 20 | |
} | |
} | |
method stop {operation stop_msg} { | |
set idx [lsearch $operations $operation] | |
if {$idx >= 0} { | |
set operations [lreplace $operations $idx $idx] | |
set completed_operation_count [expr \ | |
$completed_operation_count + 1] | |
if {[llength $operations] == 0} { | |
set completed_operation_count 0 | |
destroy $w_c | |
if {$stop_msg ne {}} { | |
set baseline_text $stop_msg | |
} | |
} | |
$this refresh | |
} | |
} | |
method stop_all {{stop_msg {}}} { | |
# This makes the operation's call to stop a no-op. | |
set operations_copy $operations | |
set operations [list] | |
foreach operation $operations_copy { | |
$operation stop | |
} | |
if {$stop_msg ne {}} { | |
set baseline_text $stop_msg | |
} | |
$this refresh | |
} | |
method _delete {current} { | |
if {$current eq $w} { | |
delete_this | |
} | |
} | |
} | |
# The status_bar_operation class tracks a single consumer's ongoing status bar | |
# activity, with the context that there are a few situations where multiple | |
# overlapping asynchronous operations might want to display status information | |
# simultaneously. Instances of status_bar_operation are created by calling | |
# start on the status_bar, and when the caller is done with its stauts bar | |
# operation, it calls stop on the operation. | |
class status_bar_operation { | |
field status_bar; # reference back to the status_bar that owns this object | |
field is_active; | |
field status {}; # single line of text we show | |
field progress {}; # current progress (0 to 100) | |
field prefix {}; # text we format into status | |
field units {}; # unit of progress | |
field meter {}; # current core git progress meter (if active) | |
constructor new {owner msg uds} { | |
set status_bar $owner | |
set status $msg | |
set progress 0 | |
set prefix $msg | |
set units $uds | |
set meter {} | |
set is_active 1 | |
return $this | |
} | |
method get_is_active {} { return $is_active } | |
method get_status {} { return $status } | |
method get_progress {} { return $progress } | |
method update {have total} { | |
if {!$is_active} { return } | |
set progress 0 | |
if {$total > 0} { | |
set progress [expr {100 * $have / $total}] | |
} | |
set prec [string length [format %i $total]] | |
set status [mc "%s ... %*i of %*i %s (%3i%%)" \ | |
$prefix \ | |
$prec $have \ | |
$prec $total \ | |
$units $progress] | |
$status_bar refresh | |
} | |
method update_meter {buf} { | |
if {!$is_active} { return } | |
append meter $buf | |
set r [string last "\r" $meter] | |
if {$r == -1} { | |
return | |
} | |
set prior [string range $meter 0 $r] | |
set meter [string range $meter [expr {$r + 1}] end] | |
set p "\\((\\d+)/(\\d+)\\)" | |
if {[regexp ":\\s*\\d+% $p\(?:, done.\\s*\n|\\s*\r)\$" $prior _j a b]} { | |
update $this $a $b | |
} elseif {[regexp "$p\\s+done\r\$" $prior _j a b]} { | |
update $this $a $b | |
} | |
} | |
method stop {{stop_msg {}}} { | |
if {$is_active} { | |
set is_active 0 | |
$status_bar stop $this $stop_msg | |
} | |
} | |
method restart {msg} { | |
if {!$is_active} { return } | |
set status $msg | |
set prefix $msg | |
set meter {} | |
$status_bar refresh | |
} | |
method _delete {} { | |
stop | |
delete_this | |
} | |
} | |