bb6966eb93a308983e98e5829a1acdce123ef406
   1# git-gui revision chooser
   2# Copyright (C) 2006, 2007 Shawn Pearce
   3
   4class choose_rev {
   5
   6image create photo ::choose_rev::img_find -data {R0lGODlhEAAQAIYAAPwCBCQmJDw+PBQSFAQCBMza3NTm5MTW1HyChOT29Ozq7MTq7Kze5Kzm7Oz6/NTy9Iza5GzGzKzS1Nzy9Nz29Kzq9HTGzHTK1Lza3AwKDLzu9JTi7HTW5GTCzITO1Mzq7Hza5FTK1ESyvHzKzKzW3DQyNDyqtDw6PIzW5HzGzAT+/Dw+RKyurNTOzMTGxMS+tJSGdATCxHRydLSqpLymnLSijBweHERCRNze3Pz69PTy9Oze1OTSxOTGrMSqlLy+vPTu5OzSvMymjNTGvNS+tMy2pMyunMSefAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAAAALAAAAAAQABAAAAe4gACCAAECA4OIiAIEBQYHBAKJgwIICQoLDA0IkZIECQ4PCxARCwSSAxITFA8VEBYXGBmJAQYLGhUbHB0eH7KIGRIMEBAgISIjJKaIJQQLFxERIialkieUGigpKRoIBCqJKyyLBwvJAioEyoICLS4v6QQwMQQyLuqLli8zNDU2BCf1lN3AkUPHDh49fAQAAEnGD1MCCALZEaSHkIUMBQS8wWMIkSJGhBzBmFEGgRsBUqpMiSgdAD+BAAAh/mhDcmVhdGVkIGJ5IEJNUFRvR0lGIFBybyB2ZXJzaW9uIDIuNQ0KqSBEZXZlbENvciAxOTk3LDE5OTguIEFsbCByaWdodHMgcmVzZXJ2ZWQuDQpodHRwOi8vd3d3LmRldmVsY29yLmNvbQA7}
   7
   8field w               ; # our megawidget path
   9field w_list          ; # list of currently filtered specs
  10field w_filter        ; # filter entry for $w_list
  11
  12field c_expr        {}; # current revision expression
  13field filter          ; # current filter string
  14field revtype     head; # type of revision chosen
  15field cur_specs [list]; # list of specs for $revtype
  16field spec_head       ; # list of all head specs
  17field spec_trck       ; # list of all tracking branch specs
  18field spec_tag        ; # list of all tag specs
  19field tip_data        ; # array of tip commit info by refname
  20
  21field tooltip_wm        {} ; # Current tooltip toplevel, if open
  22field tooltip_t         {} ; # Text widget in $tooltip_wm
  23field tooltip_timer     {} ; # Current timer event for our tooltip
  24
  25proc new {path {title {}}} {
  26        return [_new $path 0 $title]
  27}
  28
  29proc new_unmerged {path {title {}}} {
  30        return [_new $path 1 $title]
  31}
  32
  33constructor _new {path unmerged_only title} {
  34        global current_branch is_detached
  35
  36        set w $path
  37
  38        if {$title ne {}} {
  39                labelframe $w -text $title
  40        } else {
  41                frame $w
  42        }
  43        bind $w <Destroy> [cb _delete %W]
  44
  45        if {$is_detached} {
  46                radiobutton $w.detachedhead_r \
  47                        -anchor w \
  48                        -text {This Detached Checkout} \
  49                        -value HEAD \
  50                        -variable @revtype
  51                grid $w.detachedhead_r -sticky we -padx {0 5} -columnspan 2
  52        }
  53
  54        radiobutton $w.expr_r \
  55                -text {Revision Expression:} \
  56                -value expr \
  57                -variable @revtype
  58        entry $w.expr_t \
  59                -borderwidth 1 \
  60                -relief sunken \
  61                -width 50 \
  62                -textvariable @c_expr \
  63                -validate key \
  64                -validatecommand [cb _validate %d %S]
  65        grid $w.expr_r $w.expr_t -sticky we -padx {0 5}
  66
  67        frame $w.types
  68        radiobutton $w.types.head_r \
  69                -text {Local Branch} \
  70                -value head \
  71                -variable @revtype
  72        pack $w.types.head_r -side left
  73        radiobutton $w.types.trck_r \
  74                -text {Tracking Branch} \
  75                -value trck \
  76                -variable @revtype
  77        pack $w.types.trck_r -side left
  78        radiobutton $w.types.tag_r \
  79                -text {Tag} \
  80                -value tag \
  81                -variable @revtype
  82        pack $w.types.tag_r -side left
  83        set w_filter $w.types.filter
  84        entry $w_filter \
  85                -borderwidth 1 \
  86                -relief sunken \
  87                -width 12 \
  88                -textvariable @filter \
  89                -validate key \
  90                -validatecommand [cb _filter %P]
  91        pack $w_filter -side right
  92        pack [label $w.types.filter_icon \
  93                -image ::choose_rev::img_find \
  94                ] -side right
  95        grid $w.types -sticky we -padx {0 5} -columnspan 2
  96
  97        frame $w.list
  98        set w_list $w.list.l
  99        listbox $w_list \
 100                -font font_diff \
 101                -width 50 \
 102                -height 10 \
 103                -selectmode browse \
 104                -exportselection false \
 105                -xscrollcommand [cb _sb_set $w.list.sbx h] \
 106                -yscrollcommand [cb _sb_set $w.list.sby v]
 107        pack $w_list -fill both -expand 1
 108        grid $w.list -sticky nswe -padx {20 5} -columnspan 2
 109        bind $w_list <Any-Motion>  [cb _show_tooltip @%x,%y]
 110        bind $w_list <Any-Enter>   [cb _hide_tooltip]
 111        bind $w_list <Any-Leave>   [cb _hide_tooltip]
 112        bind $w_list <Destroy>     [cb _hide_tooltip]
 113
 114        grid columnconfigure $w 1 -weight 1
 115        if {$is_detached} {
 116                grid rowconfigure $w 3 -weight 1
 117        } else {
 118                grid rowconfigure $w 2 -weight 1
 119        }
 120
 121        trace add variable @revtype write [cb _select]
 122        bind $w_filter <Key-Return> [list focus $w_list]\;break
 123        bind $w_filter <Key-Down>   [list focus $w_list]
 124
 125        set fmt list
 126        append fmt { %(refname)}
 127        append fmt { [list}
 128        append fmt { %(objecttype)}
 129        append fmt { %(objectname)}
 130        append fmt { [concat %(taggername) %(authorname)]}
 131        append fmt { [concat %(taggerdate) %(authordate)]}
 132        append fmt { %(subject)}
 133        append fmt {] [list}
 134        append fmt { %(*objecttype)}
 135        append fmt { %(*objectname)}
 136        append fmt { %(*authorname)}
 137        append fmt { %(*authordate)}
 138        append fmt { %(*subject)}
 139        append fmt {]}
 140        set all_refn [list]
 141        set fr_fd [git_read for-each-ref \
 142                --tcl \
 143                --sort=-taggerdate \
 144                --format=$fmt \
 145                refs/heads \
 146                refs/remotes \
 147                refs/tags \
 148                ]
 149        fconfigure $fr_fd -translation lf -encoding utf-8
 150        while {[gets $fr_fd line] > 0} {
 151                set line [eval $line]
 152                if {[lindex $line 1 0] eq {tag}} {
 153                        if {[lindex $line 2 0] eq {commit}} {
 154                                set sha1 [lindex $line 2 1]
 155                        } else {
 156                                continue
 157                        }
 158                } elseif {[lindex $line 1 0] eq {commit}} {
 159                        set sha1 [lindex $line 1 1]
 160                } else {
 161                        continue
 162                }
 163                set refn [lindex $line 0]
 164                set tip_data($refn) [lrange $line 1 end]
 165                lappend cmt_refn($sha1) $refn
 166                lappend all_refn $refn
 167        }
 168        close $fr_fd
 169
 170        if {$unmerged_only} {
 171                set fr_fd [git_read rev-list --all ^$::HEAD]
 172                while {[gets $fr_fd sha1] > 0} {
 173                        if {[catch {set rlst $cmt_refn($sha1)}]} continue
 174                        foreach refn $rlst {
 175                                set inc($refn) 1
 176                        }
 177                }
 178                close $fr_fd
 179        } else {
 180                foreach refn $all_refn {
 181                        set inc($refn) 1
 182                }
 183        }
 184
 185        set spec_head [list]
 186        foreach name [load_all_heads] {
 187                set refn refs/heads/$name
 188                if {[info exists inc($refn)]} {
 189                        lappend spec_head [list $name $refn]
 190                }
 191        }
 192
 193        set spec_trck [list]
 194        foreach spec [all_tracking_branches] {
 195                set refn [lindex $spec 0]
 196                if {[info exists inc($refn)]} {
 197                        regsub ^refs/(heads|remotes)/ $refn {} name
 198                        lappend spec_trck [concat $name $spec]
 199                }
 200        }
 201
 202        set spec_tag [list]
 203        foreach name [load_all_tags] {
 204                set refn refs/tags/$name
 205                if {[info exists inc($refn)]} {
 206                        lappend spec_tag [list $name $refn]
 207                }
 208        }
 209
 210                  if {$is_detached}             { set revtype HEAD
 211        } elseif {[llength $spec_head] > 0} { set revtype head
 212        } elseif {[llength $spec_trck] > 0} { set revtype trck
 213        } elseif {[llength $spec_tag ] > 0} { set revtype tag
 214        } else {                              set revtype expr
 215        }
 216
 217        if {$revtype eq {head} && $current_branch ne {}} {
 218                set i 0
 219                foreach spec $spec_head {
 220                        if {[lindex $spec 0] eq $current_branch} {
 221                                $w_list selection clear 0 end
 222                                $w_list selection set $i
 223                                break
 224                        }
 225                        incr i
 226                }
 227        }
 228
 229        return $this
 230}
 231
 232method none {text} {
 233        if {![winfo exists $w.none_r]} {
 234                radiobutton $w.none_r \
 235                        -anchor w \
 236                        -value none \
 237                        -variable @revtype
 238                grid $w.none_r -sticky we -padx {0 5} -columnspan 2
 239        }
 240        $w.none_r configure -text $text
 241}
 242
 243method get {} {
 244        switch -- $revtype {
 245        head -
 246        trck -
 247        tag  {
 248                set i [$w_list curselection]
 249                if {$i ne {}} {
 250                        return [lindex $cur_specs $i 0]
 251                } else {
 252                        return {}
 253                }
 254        }
 255
 256        HEAD { return HEAD                     }
 257        expr { return $c_expr                  }
 258        none { return {}                       }
 259        default { error "unknown type of revision" }
 260        }
 261}
 262
 263method pick_tracking_branch {} {
 264        set revtype trck
 265}
 266
 267method focus_filter {} {
 268        if {[$w_filter cget -state] eq {normal}} {
 269                focus $w_filter
 270        }
 271}
 272
 273method bind_listbox {event script}  {
 274        bind $w_list $event $script
 275}
 276
 277method get_local_branch {} {
 278        if {$revtype eq {head}} {
 279                return [_expr $this]
 280        } else {
 281                return {}
 282        }
 283}
 284
 285method get_tracking_branch {} {
 286        set i [$w_list curselection]
 287        if {$i eq {} || $revtype ne {trck}} {
 288                return {}
 289        }
 290        return [lrange [lindex $cur_specs $i] 1 end]
 291}
 292
 293method get_commit {} {
 294        set e [_expr $this]
 295        if {$e eq {}} {
 296                return {}
 297        }
 298        return [git rev-parse --verify "$e^0"]
 299}
 300
 301method commit_or_die {} {
 302        if {[catch {set new [get_commit $this]} err]} {
 303
 304                # Cleanup the not-so-friendly error from rev-parse.
 305                #
 306                regsub {^fatal:\s*} $err {} err
 307                if {$err eq {Needed a single revision}} {
 308                        set err {}
 309                }
 310
 311                set top [winfo toplevel $w]
 312                set msg "Invalid revision: [get $this]\n\n$err"
 313                tk_messageBox \
 314                        -icon error \
 315                        -type ok \
 316                        -title [wm title $top] \
 317                        -parent $top \
 318                        -message $msg
 319                error $msg
 320        }
 321        return $new
 322}
 323
 324method _expr {} {
 325        switch -- $revtype {
 326        head -
 327        trck -
 328        tag  {
 329                set i [$w_list curselection]
 330                if {$i ne {}} {
 331                        return [lindex $cur_specs $i 1]
 332                } else {
 333                        error "No revision selected."
 334                }
 335        }
 336
 337        expr {
 338                if {$c_expr ne {}} {
 339                        return $c_expr
 340                } else {
 341                        error "Revision expression is empty."
 342                }
 343        }
 344        HEAD { return HEAD                     }
 345        none { return {}                       }
 346        default { error "unknown type of revision"      }
 347        }
 348}
 349
 350method _validate {d S} {
 351        if {$d == 1} {
 352                if {[regexp {\s} $S]} {
 353                        return 0
 354                }
 355                if {[string length $S] > 0} {
 356                        set revtype expr
 357                }
 358        }
 359        return 1
 360}
 361
 362method _filter {P} {
 363        if {[regexp {\s} $P]} {
 364                return 0
 365        }
 366        _rebuild $this $P
 367        return 1
 368}
 369
 370method _select {args} {
 371        _rebuild $this $filter
 372        focus_filter $this
 373}
 374
 375method _rebuild {pat} {
 376        set ste normal
 377        switch -- $revtype {
 378        head { set new $spec_head }
 379        trck { set new $spec_trck }
 380        tag  { set new $spec_tag  }
 381        expr -
 382        HEAD -
 383        none {
 384                set new [list]
 385                set ste disabled
 386        }
 387        }
 388
 389        if {[$w_list cget -state] eq {disabled}} {
 390                $w_list configure -state normal
 391        }
 392        $w_list delete 0 end
 393
 394        if {$pat ne {}} {
 395                set pat *${pat}*
 396        }
 397        set cur_specs [list]
 398        foreach spec $new {
 399                set txt [lindex $spec 0]
 400                if {$pat eq {} || [string match $pat $txt]} {
 401                        lappend cur_specs $spec
 402                        $w_list insert end $txt
 403                }
 404        }
 405        if {$cur_specs ne {}} {
 406                $w_list selection clear 0 end
 407                $w_list selection set 0
 408        }
 409
 410        if {[$w_filter cget -state] ne $ste} {
 411                $w_list   configure -state $ste
 412                $w_filter configure -state $ste
 413        }
 414}
 415
 416method _delete {current} {
 417        if {$current eq $w} {
 418                delete_this
 419        }
 420}
 421
 422method _sb_set {sb orient first last} {
 423        set old_focus [focus -lastfor $w]
 424
 425        if {$first == 0 && $last == 1} {
 426                if {[winfo exists $sb]} {
 427                        destroy $sb
 428                        if {$old_focus ne {}} {
 429                                update
 430                                focus $old_focus
 431                        }
 432                }
 433                return
 434        }
 435
 436        if {![winfo exists $sb]} {
 437                if {$orient eq {h}} {
 438                        scrollbar $sb -orient h -command [list $w_list xview]
 439                        pack $sb -fill x -side bottom -before $w_list
 440                } else {
 441                        scrollbar $sb -orient v -command [list $w_list yview]
 442                        pack $sb -fill y -side right -before $w_list
 443                }
 444                if {$old_focus ne {}} {
 445                        update
 446                        focus $old_focus
 447                }
 448        }
 449        $sb set $first $last
 450}
 451
 452method _show_tooltip {pos} {
 453        if {$tooltip_wm ne {}} {
 454                _open_tooltip $this
 455        } elseif {$tooltip_timer eq {}} {
 456                set tooltip_timer [after 1000 [cb _open_tooltip]]
 457        }
 458}
 459
 460method _open_tooltip {} {
 461        global remote_url
 462
 463        set tooltip_timer {}
 464        set pos_x [winfo pointerx $w_list]
 465        set pos_y [winfo pointery $w_list]
 466        if {[winfo containing $pos_x $pos_y] ne $w_list} {
 467                _hide_tooltip $this
 468                return
 469        }
 470
 471        set pos @[join [list \
 472                [expr {$pos_x - [winfo rootx $w_list]}] \
 473                [expr {$pos_y - [winfo rooty $w_list]}]] ,]
 474        set lno [$w_list index $pos]
 475        if {$lno eq {}} {
 476                _hide_tooltip $this
 477                return
 478        }
 479
 480        set spec [lindex $cur_specs $lno]
 481        set refn [lindex $spec 1]
 482        if {$refn eq {}} {
 483                _hide_tooltip $this
 484                return
 485        }
 486
 487        if {$tooltip_wm eq {}} {
 488                set tooltip_wm [toplevel $w_list.tooltip -borderwidth 1]
 489                wm overrideredirect $tooltip_wm 1
 490                wm transient $tooltip_wm [winfo toplevel $w_list]
 491                set tooltip_t $tooltip_wm.label
 492                text $tooltip_t \
 493                        -takefocus 0 \
 494                        -highlightthickness 0 \
 495                        -relief flat \
 496                        -borderwidth 0 \
 497                        -wrap none \
 498                        -background lightyellow \
 499                        -foreground black
 500                $tooltip_t tag conf section_header -font font_uibold
 501                bind $tooltip_wm <Escape> [cb _hide_tooltip]
 502                pack $tooltip_t
 503        } else {
 504                $tooltip_t conf -state normal
 505                $tooltip_t delete 0.0 end
 506        }
 507
 508        set data $tip_data($refn)
 509        if {[lindex $data 0 0] eq {tag}} {
 510                set tag  [lindex $data 0]
 511                if {[lindex $data 1 0] eq {commit}} {
 512                        set cmit [lindex $data 1]
 513                } else {
 514                        set cmit {}
 515                }
 516        } elseif {[lindex $data 0 0] eq {commit}} {
 517                set tag  {}
 518                set cmit [lindex $data 0]
 519        }
 520
 521        $tooltip_t insert end "[lindex $spec 0]\n"
 522
 523        if {$tag ne {}} {
 524                $tooltip_t insert end "\n"
 525                $tooltip_t insert end "tag" section_header
 526                $tooltip_t insert end "  [lindex $tag 1]\n"
 527                $tooltip_t insert end [lindex $tag 2]
 528                $tooltip_t insert end " ([lindex $tag 3])\n"
 529                $tooltip_t insert end [lindex $tag 4]
 530                $tooltip_t insert end "\n"
 531        }
 532
 533        if {$cmit ne {}} {
 534                $tooltip_t insert end "\n"
 535                $tooltip_t insert end "commit" section_header
 536                $tooltip_t insert end "  [lindex $cmit 1]\n"
 537                $tooltip_t insert end [lindex $cmit 2]
 538                $tooltip_t insert end " ([lindex $cmit 3])\n"
 539                $tooltip_t insert end [lindex $cmit 4]
 540        }
 541
 542        if {[llength $spec] > 2} {
 543                $tooltip_t insert end "\n"
 544                $tooltip_t insert end "remote" section_header
 545                $tooltip_t insert end "  [lindex $spec 2]\n"
 546                $tooltip_t insert end "url"
 547                $tooltip_t insert end " $remote_url([lindex $spec 2])\n"
 548                $tooltip_t insert end "branch"
 549                $tooltip_t insert end " [lindex $spec 3]"
 550        }
 551
 552        $tooltip_t conf -state disabled
 553        _position_tooltip $this
 554}
 555
 556method _position_tooltip {} {
 557        set max_h [lindex [split [$tooltip_t index end] .] 0]
 558        set max_w 0
 559        for {set i 1} {$i <= $max_h} {incr i} {
 560                set c [lindex [split [$tooltip_t index "$i.0 lineend"] .] 1]
 561                if {$c > $max_w} {set max_w $c}
 562        }
 563        $tooltip_t conf -width $max_w -height $max_h
 564
 565        set req_w [winfo reqwidth  $tooltip_t]
 566        set req_h [winfo reqheight $tooltip_t]
 567        set pos_x [expr {[winfo pointerx .] +  5}]
 568        set pos_y [expr {[winfo pointery .] + 10}]
 569
 570        set g "${req_w}x${req_h}"
 571        if {$pos_x >= 0} {append g +}
 572        append g $pos_x
 573        if {$pos_y >= 0} {append g +}
 574        append g $pos_y
 575
 576        wm geometry $tooltip_wm $g
 577        raise $tooltip_wm
 578}
 579
 580method _hide_tooltip {} {
 581        if {$tooltip_wm ne {}} {
 582                destroy $tooltip_wm
 583                set tooltip_wm {}
 584        }
 585        if {$tooltip_timer ne {}} {
 586                after cancel $tooltip_timer
 587                set tooltip_timer {}
 588        }
 589}
 590
 591}