git-gui: update status bar to track operations

Update the status bar to track updates as individual "operations" that
can overlap. Update all call sites to interact with the new status bar
mechanism. Update initialization to explicitly clear status text,
since otherwise it may persist across future operations.

Signed-off-by: Jonathan Gilbert <JonathanG@iQmetrix.com>
Signed-off-by: Pratyush Yadav <me@yadavpratyush.com>
This commit is contained in:
Jonathan Gilbert 2019-12-01 02:28:32 +00:00 committed by Pratyush Yadav
parent 29a9366052
commit d9c6469f38
7 changed files with 354 additions and 108 deletions

View file

@ -1797,10 +1797,10 @@ proc ui_status {msg} {
} }
} }
proc ui_ready {{test {}}} { proc ui_ready {} {
global main_status global main_status
if {[info exists main_status]} { if {[info exists main_status]} {
$main_status show [mc "Ready."] $test $main_status show [mc "Ready."]
} }
} }
@ -2150,8 +2150,6 @@ proc incr_font_size {font {amt 1}} {
## ##
## ui commands ## ui commands
set starting_gitk_msg [mc "Starting gitk... please wait..."]
proc do_gitk {revs {is_submodule false}} { proc do_gitk {revs {is_submodule false}} {
global current_diff_path file_states current_diff_side ui_index global current_diff_path file_states current_diff_side ui_index
global _gitdir _gitworktree global _gitdir _gitworktree
@ -2206,10 +2204,11 @@ proc do_gitk {revs {is_submodule false}} {
set env(GIT_WORK_TREE) $_gitworktree set env(GIT_WORK_TREE) $_gitworktree
cd $pwd cd $pwd
ui_status $::starting_gitk_msg set status_operation [$::main_status \
after 10000 { start \
ui_ready $starting_gitk_msg [mc "Starting %s... please wait..." "gitk"]]
}
after 3500 [list $status_operation stop]
} }
} }
@ -2240,10 +2239,11 @@ proc do_git_gui {} {
set env(GIT_WORK_TREE) $_gitworktree set env(GIT_WORK_TREE) $_gitworktree
cd $pwd cd $pwd
ui_status $::starting_gitk_msg set status_operation [$::main_status \
after 10000 { start \
ui_ready $starting_gitk_msg [mc "Starting %s... please wait..." "git-gui"]]
}
after 3500 [list $status_operation stop]
} }
} }
@ -4159,6 +4159,9 @@ if {$picked && [is_config_true gui.autoexplore]} {
do_explore do_explore
} }
# Clear "Initializing..." status
after 500 {$main_status show ""}
# Local variables: # Local variables:
# mode: tcl # mode: tcl
# indent-tabs-mode: t # indent-tabs-mode: t

View file

@ -24,6 +24,7 @@ field w_cviewer ; # pane showing commit message
field finder ; # find mini-dialog frame field finder ; # find mini-dialog frame
field gotoline ; # line goto mini-dialog frame field gotoline ; # line goto mini-dialog frame
field status ; # status mega-widget instance field status ; # status mega-widget instance
field status_operation ; # operation displayed by status mega-widget
field old_height ; # last known height of $w.file_pane field old_height ; # last known height of $w.file_pane
@ -274,6 +275,7 @@ constructor new {i_commit i_path i_jump} {
pack $w_cviewer -expand 1 -fill both pack $w_cviewer -expand 1 -fill both
set status [::status_bar::new $w.status] set status [::status_bar::new $w.status]
set status_operation {}
menu $w.ctxm -tearoff 0 menu $w.ctxm -tearoff 0
$w.ctxm add command \ $w.ctxm add command \
@ -602,16 +604,23 @@ method _exec_blame {cur_w cur_d options cur_s} {
} else { } else {
lappend options $commit lappend options $commit
} }
# We may recurse in from another call to _exec_blame and already have
# a status operation.
if {$status_operation == {}} {
set status_operation [$status start \
$cur_s \
[mc "lines annotated"]]
} else {
$status_operation restart $cur_s
}
lappend options -- $path lappend options -- $path
set fd [eval git_read --nice blame $options] set fd [eval git_read --nice blame $options]
fconfigure $fd -blocking 0 -translation lf -encoding utf-8 fconfigure $fd -blocking 0 -translation lf -encoding utf-8
fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d] fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d]
set current_fd $fd set current_fd $fd
set blame_lines 0 set blame_lines 0
$status start \
$cur_s \
[mc "lines annotated"]
} }
method _read_blame {fd cur_w cur_d} { method _read_blame {fd cur_w cur_d} {
@ -806,10 +815,11 @@ method _read_blame {fd cur_w cur_d} {
[mc "Loading original location annotations..."] [mc "Loading original location annotations..."]
} else { } else {
set current_fd {} set current_fd {}
$status stop [mc "Annotation complete."] $status_operation stop [mc "Annotation complete."]
set status_operation {}
} }
} else { } else {
$status update $blame_lines $total_lines $status_operation update $blame_lines $total_lines
} }
} ifdeleted { catch {close $fd} } } ifdeleted { catch {close $fd} }
@ -1124,7 +1134,7 @@ method _blameparent {} {
set diffcmd [list diff-tree --unified=0 $cparent $cmit -- $new_path] set diffcmd [list diff-tree --unified=0 $cparent $cmit -- $new_path]
} }
if {[catch {set fd [eval git_read $diffcmd]} err]} { if {[catch {set fd [eval git_read $diffcmd]} err]} {
$status stop [mc "Unable to display parent"] $status_operation stop [mc "Unable to display parent"]
error_popup [strcat [mc "Error loading diff:"] "\n\n$err"] error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
return return
} }

View file

@ -341,9 +341,9 @@ method _readtree {} {
global HEAD global HEAD
set readtree_d {} set readtree_d {}
$::main_status start \ set status_bar_operation [$::main_status start \
[mc "Updating working directory to '%s'..." [_name $this]] \ [mc "Updating working directory to '%s'..." [_name $this]] \
[mc "files checked out"] [mc "files checked out"]]
set fd [git_read --stderr read-tree \ set fd [git_read --stderr read-tree \
-m \ -m \
@ -354,26 +354,27 @@ method _readtree {} {
$new_hash \ $new_hash \
] ]
fconfigure $fd -blocking 0 -translation binary fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable [cb _readtree_wait $fd] fileevent $fd readable [cb _readtree_wait $fd $status_bar_operation]
} }
method _readtree_wait {fd} { method _readtree_wait {fd status_bar_operation} {
global current_branch global current_branch
set buf [read $fd] set buf [read $fd]
$::main_status update_meter $buf $status_bar_operation update_meter $buf
append readtree_d $buf append readtree_d $buf
fconfigure $fd -blocking 1 fconfigure $fd -blocking 1
if {![eof $fd]} { if {![eof $fd]} {
fconfigure $fd -blocking 0 fconfigure $fd -blocking 0
$status_bar_operation stop
return return
} }
if {[catch {close $fd}]} { if {[catch {close $fd}]} {
set err $readtree_d set err $readtree_d
regsub {^fatal: } $err {} err regsub {^fatal: } $err {} err
$::main_status stop [mc "Aborted checkout of '%s' (file level merging is required)." [_name $this]] $status_bar_operation stop [mc "Aborted checkout of '%s' (file level merging is required)." [_name $this]]
warn_popup [strcat [mc "File level merge required."] " warn_popup [strcat [mc "File level merge required."] "
$err $err
@ -384,7 +385,7 @@ $err
return return
} }
$::main_status stop $status_bar_operation stop
_after_readtree $this _after_readtree $this
} }

View file

@ -9,6 +9,18 @@ field w_body ; # Widget holding the center content
field w_next ; # Next button field w_next ; # Next button
field w_quit ; # Quit button field w_quit ; # Quit button
field o_cons ; # Console object (if active) field o_cons ; # Console object (if active)
# Status mega-widget instance during _do_clone2 (used by _copy_files and
# _link_files). Widget is destroyed before _do_clone2 calls
# _do_clone_checkout
field o_status
# Operation displayed by status mega-widget during _do_clone_checkout =>
# _readtree_wait => _postcheckout_wait => _do_clone_submodules =>
# _do_validate_submodule_cloning. The status mega-widget is a different
# instance than that stored in $o_status in earlier operations.
field o_status_op
field w_types ; # List of type buttons in clone field w_types ; # List of type buttons in clone
field w_recentlist ; # Listbox containing recent repositories field w_recentlist ; # Listbox containing recent repositories
field w_localpath ; # Entry widget bound to local_path field w_localpath ; # Entry widget bound to local_path
@ -659,12 +671,12 @@ method _do_clone2 {} {
switch -exact -- $clone_type { switch -exact -- $clone_type {
hardlink { hardlink {
set o_cons [status_bar::two_line $w_body] set o_status [status_bar::two_line $w_body]
pack $w_body -fill x -padx 10 -pady 10 pack $w_body -fill x -padx 10 -pady 10
$o_cons start \ set status_op [$o_status start \
[mc "Counting objects"] \ [mc "Counting objects"] \
[mc "buckets"] [mc "buckets"]]
update update
if {[file exists [file join $objdir info alternates]]} { if {[file exists [file join $objdir info alternates]]} {
@ -689,6 +701,7 @@ method _do_clone2 {} {
} err]} { } err]} {
catch {cd $pwd} catch {cd $pwd}
_clone_failed $this [mc "Unable to copy objects/info/alternates: %s" $err] _clone_failed $this [mc "Unable to copy objects/info/alternates: %s" $err]
$status_op stop
return return
} }
} }
@ -700,7 +713,7 @@ method _do_clone2 {} {
-directory [file join $objdir] ??] -directory [file join $objdir] ??]
set bcnt [expr {[llength $buckets] + 2}] set bcnt [expr {[llength $buckets] + 2}]
set bcur 1 set bcur 1
$o_cons update $bcur $bcnt $status_op update $bcur $bcnt
update update
file mkdir [file join .git objects pack] file mkdir [file join .git objects pack]
@ -708,7 +721,7 @@ method _do_clone2 {} {
-directory [file join $objdir pack] *] { -directory [file join $objdir pack] *] {
lappend tolink [file join pack $i] lappend tolink [file join pack $i]
} }
$o_cons update [incr bcur] $bcnt $status_op update [incr bcur] $bcnt
update update
foreach i $buckets { foreach i $buckets {
@ -717,10 +730,10 @@ method _do_clone2 {} {
-directory [file join $objdir $i] *] { -directory [file join $objdir $i] *] {
lappend tolink [file join $i $j] lappend tolink [file join $i $j]
} }
$o_cons update [incr bcur] $bcnt $status_op update [incr bcur] $bcnt
update update
} }
$o_cons stop $status_op stop
if {$tolink eq {}} { if {$tolink eq {}} {
info_popup [strcat \ info_popup [strcat \
@ -747,6 +760,8 @@ method _do_clone2 {} {
if {!$i} return if {!$i} return
destroy $w_body destroy $w_body
set o_status {}
} }
full { full {
set o_cons [console::embed \ set o_cons [console::embed \
@ -781,9 +796,9 @@ method _do_clone2 {} {
} }
method _copy_files {objdir tocopy} { method _copy_files {objdir tocopy} {
$o_cons start \ set status_op [$o_status start \
[mc "Copying objects"] \ [mc "Copying objects"] \
[mc "KiB"] [mc "KiB"]]
set tot 0 set tot 0
set cmp 0 set cmp 0
foreach p $tocopy { foreach p $tocopy {
@ -798,7 +813,7 @@ method _copy_files {objdir tocopy} {
while {![eof $f_in]} { while {![eof $f_in]} {
incr cmp [fcopy $f_in $f_cp -size 16384] incr cmp [fcopy $f_in $f_cp -size 16384]
$o_cons update \ $status_op update \
[expr {$cmp / 1024}] \ [expr {$cmp / 1024}] \
[expr {$tot / 1024}] [expr {$tot / 1024}]
update update
@ -808,17 +823,19 @@ method _copy_files {objdir tocopy} {
close $f_cp close $f_cp
} err]} { } err]} {
_clone_failed $this [mc "Unable to copy object: %s" $err] _clone_failed $this [mc "Unable to copy object: %s" $err]
$status_op stop
return 0 return 0
} }
} }
$status_op stop
return 1 return 1
} }
method _link_files {objdir tolink} { method _link_files {objdir tolink} {
set total [llength $tolink] set total [llength $tolink]
$o_cons start \ set status_op [$o_status start \
[mc "Linking objects"] \ [mc "Linking objects"] \
[mc "objects"] [mc "objects"]]
for {set i 0} {$i < $total} {} { for {set i 0} {$i < $total} {} {
set p [lindex $tolink $i] set p [lindex $tolink $i]
if {[catch { if {[catch {
@ -827,15 +844,17 @@ method _link_files {objdir tolink} {
[file join $objdir $p] [file join $objdir $p]
} err]} { } err]} {
_clone_failed $this [mc "Unable to hardlink object: %s" $err] _clone_failed $this [mc "Unable to hardlink object: %s" $err]
$status_op stop
return 0 return 0
} }
incr i incr i
if {$i % 5 == 0} { if {$i % 5 == 0} {
$o_cons update $i $total $status_op update $i $total
update update
} }
} }
$status_op stop
return 1 return 1
} }
@ -958,11 +977,26 @@ method _do_clone_checkout {HEAD} {
return return
} }
set o_cons [status_bar::two_line $w_body] set status [status_bar::two_line $w_body]
pack $w_body -fill x -padx 10 -pady 10 pack $w_body -fill x -padx 10 -pady 10
$o_cons start \
# We start the status operation here.
#
# This function calls _readtree_wait as a callback.
#
# _readtree_wait in turn either calls _do_clone_submodules directly,
# or calls _postcheckout_wait as a callback which then calls
# _do_clone_submodules.
#
# _do_clone_submodules calls _do_validate_submodule_cloning.
#
# _do_validate_submodule_cloning stops the status operation.
#
# There are no other calls into this chain from other code.
set o_status_op [$status start \
[mc "Creating working directory"] \ [mc "Creating working directory"] \
[mc "files"] [mc "files"]]
set readtree_err {} set readtree_err {}
set fd [git_read --stderr read-tree \ set fd [git_read --stderr read-tree \
@ -976,33 +1010,9 @@ method _do_clone_checkout {HEAD} {
fileevent $fd readable [cb _readtree_wait $fd] fileevent $fd readable [cb _readtree_wait $fd]
} }
method _do_validate_submodule_cloning {ok} {
if {$ok} {
$o_cons done $ok
set done 1
} else {
_clone_failed $this [mc "Cannot clone submodules."]
}
}
method _do_clone_submodules {} {
if {$recursive eq {true}} {
destroy $w_body
set o_cons [console::embed \
$w_body \
[mc "Cloning submodules"]]
pack $w_body -fill both -expand 1 -padx 10
$o_cons exec \
[list git submodule update --init --recursive] \
[cb _do_validate_submodule_cloning]
} else {
set done 1
}
}
method _readtree_wait {fd} { method _readtree_wait {fd} {
set buf [read $fd] set buf [read $fd]
$o_cons update_meter $buf $o_status_op update_meter $buf
append readtree_err $buf append readtree_err $buf
fconfigure $fd -blocking 1 fconfigure $fd -blocking 1
@ -1050,6 +1060,34 @@ method _postcheckout_wait {fd_ph} {
fconfigure $fd_ph -blocking 0 fconfigure $fd_ph -blocking 0
} }
method _do_clone_submodules {} {
if {$recursive eq {true}} {
$o_status_op stop
set o_status_op {}
destroy $w_body
set o_cons [console::embed \
$w_body \
[mc "Cloning submodules"]]
pack $w_body -fill both -expand 1 -padx 10
$o_cons exec \
[list git submodule update --init --recursive] \
[cb _do_validate_submodule_cloning]
} else {
set done 1
}
}
method _do_validate_submodule_cloning {ok} {
if {$ok} {
$o_cons done $ok
set done 1
} else {
_clone_failed $this [mc "Cannot clone submodules."]
}
}
###################################################################### ######################################################################
## ##
## Open Existing Repository ## Open Existing Repository

View file

@ -45,13 +45,13 @@ proc _close_updateindex {fd after} {
wm deiconify $w wm deiconify $w
tkwait window $w tkwait window $w
$::main_status stop $::main_status stop_all
unlock_index unlock_index
rescan $after 0 rescan $after 0
return return
} }
$::main_status stop $::main_status stop_all
unlock_index unlock_index
uplevel #0 $after uplevel #0 $after
} }
@ -67,7 +67,7 @@ proc update_indexinfo {msg path_list after} {
set batch [expr {int($total_cnt * .01) + 1}] set batch [expr {int($total_cnt * .01) + 1}]
if {$batch > 25} {set batch 25} if {$batch > 25} {set batch 25}
$::main_status start $msg [mc "files"] set status_bar_operation [$::main_status start $msg [mc "files"]]
set fd [git_write update-index -z --index-info] set fd [git_write update-index -z --index-info]
fconfigure $fd \ fconfigure $fd \
-blocking 0 \ -blocking 0 \
@ -81,15 +81,18 @@ proc update_indexinfo {msg path_list after} {
$path_list \ $path_list \
$total_cnt \ $total_cnt \
$batch \ $batch \
$status_bar_operation \
$after \ $after \
] ]
} }
proc write_update_indexinfo {fd path_list total_cnt batch after} { proc write_update_indexinfo {fd path_list total_cnt batch status_bar_operation \
after} {
global update_index_cp global update_index_cp
global file_states current_diff_path global file_states current_diff_path
if {$update_index_cp >= $total_cnt} { if {$update_index_cp >= $total_cnt} {
$status_bar_operation stop
_close_updateindex $fd $after _close_updateindex $fd $after
return return
} }
@ -119,7 +122,7 @@ proc write_update_indexinfo {fd path_list total_cnt batch after} {
display_file $path $new display_file $path $new
} }
$::main_status update $update_index_cp $total_cnt $status_bar_operation update $update_index_cp $total_cnt
} }
proc update_index {msg path_list after} { proc update_index {msg path_list after} {
@ -133,7 +136,7 @@ proc update_index {msg path_list after} {
set batch [expr {int($total_cnt * .01) + 1}] set batch [expr {int($total_cnt * .01) + 1}]
if {$batch > 25} {set batch 25} if {$batch > 25} {set batch 25}
$::main_status start $msg [mc "files"] set status_bar_operation [$::main_status start $msg [mc "files"]]
set fd [git_write update-index --add --remove -z --stdin] set fd [git_write update-index --add --remove -z --stdin]
fconfigure $fd \ fconfigure $fd \
-blocking 0 \ -blocking 0 \
@ -147,15 +150,18 @@ proc update_index {msg path_list after} {
$path_list \ $path_list \
$total_cnt \ $total_cnt \
$batch \ $batch \
$status_bar_operation \
$after \ $after \
] ]
} }
proc write_update_index {fd path_list total_cnt batch after} { proc write_update_index {fd path_list total_cnt batch status_bar_operation \
after} {
global update_index_cp global update_index_cp
global file_states current_diff_path global file_states current_diff_path
if {$update_index_cp >= $total_cnt} { if {$update_index_cp >= $total_cnt} {
$status_bar_operation stop
_close_updateindex $fd $after _close_updateindex $fd $after
return return
} }
@ -190,7 +196,7 @@ proc write_update_index {fd path_list total_cnt batch after} {
display_file $path $new display_file $path $new
} }
$::main_status update $update_index_cp $total_cnt $status_bar_operation update $update_index_cp $total_cnt
} }
proc checkout_index {msg path_list after} { proc checkout_index {msg path_list after} {
@ -204,7 +210,7 @@ proc checkout_index {msg path_list after} {
set batch [expr {int($total_cnt * .01) + 1}] set batch [expr {int($total_cnt * .01) + 1}]
if {$batch > 25} {set batch 25} if {$batch > 25} {set batch 25}
$::main_status start $msg [mc "files"] set status_bar_operation [$::main_status start $msg [mc "files"]]
set fd [git_write checkout-index \ set fd [git_write checkout-index \
--index \ --index \
--quiet \ --quiet \
@ -224,15 +230,18 @@ proc checkout_index {msg path_list after} {
$path_list \ $path_list \
$total_cnt \ $total_cnt \
$batch \ $batch \
$status_bar_operation \
$after \ $after \
] ]
} }
proc write_checkout_index {fd path_list total_cnt batch after} { proc write_checkout_index {fd path_list total_cnt batch status_bar_operation \
after} {
global update_index_cp global update_index_cp
global file_states current_diff_path global file_states current_diff_path
if {$update_index_cp >= $total_cnt} { if {$update_index_cp >= $total_cnt} {
$status_bar_operation stop
_close_updateindex $fd $after _close_updateindex $fd $after
return return
} }
@ -253,7 +262,7 @@ proc write_checkout_index {fd path_list total_cnt batch after} {
} }
} }
$::main_status update $update_index_cp $total_cnt $status_bar_operation update $update_index_cp $total_cnt
} }
proc unstage_helper {txt paths} { proc unstage_helper {txt paths} {

View file

@ -241,23 +241,27 @@ Continue with resetting the current changes?"]
if {[ask_popup $op_question] eq {yes}} { if {[ask_popup $op_question] eq {yes}} {
set fd [git_read --stderr read-tree --reset -u -v HEAD] set fd [git_read --stderr read-tree --reset -u -v HEAD]
fconfigure $fd -blocking 0 -translation binary fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable [namespace code [list _reset_wait $fd]] set status_bar_operation [$::main_status \
$::main_status start [mc "Aborting"] [mc "files reset"] start \
[mc "Aborting"] \
[mc "files reset"]
fileevent $fd readable [namespace code [list \
_reset_wait $fd $status_bar_operation]]
} else { } else {
unlock_index unlock_index
} }
} }
proc _reset_wait {fd} { proc _reset_wait {fd status_bar_operation} {
global ui_comm global ui_comm
$::main_status update_meter [read $fd] $status_bar_operation update_meter [read $fd]
fconfigure $fd -blocking 1 fconfigure $fd -blocking 1
if {[eof $fd]} { if {[eof $fd]} {
set fail [catch {close $fd} err] set fail [catch {close $fd} err]
$::main_status stop
unlock_index unlock_index
$status_bar_operation stop
$ui_comm delete 0.0 end $ui_comm delete 0.0 end
$ui_comm edit modified false $ui_comm edit modified false

View file

@ -1,16 +1,42 @@
# git-gui status bar mega-widget # git-gui status bar mega-widget
# Copyright (C) 2007 Shawn Pearce # 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 { class status_bar {
field allow_multiple ; # configured at construction
field w ; # our own window path field w ; # our own window path
field w_l ; # text widget we draw messages into field w_l ; # text widget we draw messages into
field w_c ; # canvas we draw a progress bar into field w_c ; # canvas we draw a progress bar into
field c_pack ; # script to pack the canvas with field c_pack ; # script to pack the canvas with
field status {}; # single line of text we show
field prefix {}; # text we format into status field baseline_text ; # text to show if there are no operations
field units {}; # unit of progress field status_bar_text ; # combined text for all operations
field meter {}; # current core git progress meter (if active)
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} { constructor new {path} {
global use_ttk NS global use_ttk NS
@ -18,12 +44,19 @@ constructor new {path} {
set w_l $w.l set w_l $w.l
set w_c $w.c 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 ${NS}::frame $w
if {!$use_ttk} { if {!$use_ttk} {
$w configure -borderwidth 1 -relief sunken $w configure -borderwidth 1 -relief sunken
} }
${NS}::label $w_l \ ${NS}::label $w_l \
-textvariable @status \ -textvariable @status_bar_text \
-anchor w \ -anchor w \
-justify left -justify left
pack $w_l -side left pack $w_l -side left
@ -44,9 +77,16 @@ constructor two_line {path} {
set w_l $w.l set w_l $w.l
set w_c $w.c 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}::frame $w
${NS}::label $w_l \ ${NS}::label $w_l \
-textvariable @status \ -textvariable @status_bar_text \
-anchor w \ -anchor w \
-justify left -justify left
pack $w_l -anchor w -fill x pack $w_l -anchor w -fill x
@ -56,7 +96,7 @@ constructor two_line {path} {
return $this return $this
} }
method start {msg uds} { method ensure_canvas {} {
if {[winfo exists $w_c]} { if {[winfo exists $w_c]} {
$w_c coords bar 0 0 0 20 $w_c coords bar 0 0 0 20
} else { } else {
@ -68,31 +108,170 @@ method start {msg uds} {
$w_c create rectangle 0 0 0 20 -tags bar -fill navy $w_c create rectangle 0 0 0 20 -tags bar -fill navy
eval $c_pack 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 status $msg
set progress 0
set prefix $msg set prefix $msg
set units $uds set units $uds
set meter {} 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} { method update {have total} {
set pdone 0 if {!$is_active} { return }
set cdone 0
set progress 0
if {$total > 0} { if {$total > 0} {
set pdone [expr {100 * $have / $total}] set progress [expr {100 * $have / $total}]
set cdone [expr {[winfo width $w_c] * $have / $total}]
} }
set prec [string length [format %i $total]] set prec [string length [format %i $total]]
set status [mc "%s ... %*i of %*i %s (%3i%%)" \ set status [mc "%s ... %*i of %*i %s (%3i%%)" \
$prefix \ $prefix \
$prec $have \ $prec $have \
$prec $total \ $prec $total \
$units $pdone] $units $progress]
$w_c coords bar 0 0 $cdone 20
$status_bar refresh
} }
method update_meter {buf} { method update_meter {buf} {
if {!$is_active} { return }
append meter $buf append meter $buf
set r [string last "\r" $meter] set r [string last "\r" $meter]
if {$r == -1} { if {$r == -1} {
@ -109,23 +288,25 @@ method update_meter {buf} {
} }
} }
method stop {{msg {}}} { method stop {{stop_msg {}}} {
destroy $w_c if {$is_active} {
if {$msg ne {}} { set is_active 0
set status $msg $status_bar stop $this $stop_msg
} }
} }
method show {msg {test {}}} { method restart {msg} {
if {$test eq {} || $status eq $test} { if {!$is_active} { return }
set status $msg
} set status $msg
set prefix $msg
set meter {}
$status_bar refresh
} }
method _delete {current} { method _delete {} {
if {$current eq $w} { stop
delete_this delete_this
}
} }
} }