You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
312 lines
6.9 KiB
312 lines
6.9 KiB
# 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 |
|
} |
|
|
|
}
|
|
|