# # # patch "mtn-browse" # from [e961b274ebebd3b84c9438a30ec320dafa6fbfb2] # to [96ddbb57a8aecd5db92c1635b9627410b6402ae7] # # patch "mtn-browse.glade" # from [db85aab77dba15a9926d2438b6cc20077013fdb9] # to [32a19a669843c4ba9f45293d822899dd11c5b1fc] # ============================================================ --- mtn-browse e961b274ebebd3b84c9438a30ec320dafa6fbfb2 +++ mtn-browse 96ddbb57a8aecd5db92c1635b9627410b6402ae7 @@ -49,13 +49,13 @@ use integer; use strict; use integer; -use IPC::Open3; use Glib qw(FALSE TRUE); use Gnome2; use Gtk2 -init; set_locale Gtk2; init Gtk2; use Gtk2::GladeXML; +use IPC::Open3; use Monotone::AutomateStdio; # Temporary debug stuff. @@ -68,21 +68,30 @@ use constant DIRECTORY => 0x02; use constant BRANCH => 0x01; use constant DIRECTORY => 0x02; -use constant DISPLAY_OF_FILE => 0x04; -use constant FILE => 0x08; -use constant REVISION => 0x10; +use constant DIRECTORY_VIEW => 0x04; +use constant DISPLAY_OF_FILE => 0x08; +use constant FILE => 0x10; +use constant REVISION => 0x20; # Constants used to represent the different state changes. Read this as # `what has just been changed' => `what needs to be updated'. -use constant BRANCH_CHANGED => (REVISION | DIRECTORY | FILE - | DISPLAY_OF_FILE); +use constant BRANCH_CHANGED => (REVISION | DIRECTORY | DIRECTORY_VIEW + | FILE | DISPLAY_OF_FILE); use constant DATABASE_CHANGED => 0xff; -use constant DIRECTORY_CHANGED => (FILE | DISPLAY_OF_FILE); +use constant DIRECTORY_CHANGED => (DIRECTORY_VIEW | FILE + | DISPLAY_OF_FILE); use constant DISPLAY_OF_FILE_CHANGED => 0x00; use constant FILE_CHANGED => (DISPLAY_OF_FILE); -use constant REVISION_CHANGED => (DIRECTORY | FILE | DISPLAY_OF_FILE); +use constant REVISION_CHANGED => (DIRECTORY | DIRECTORY_VIEW | FILE + | DISPLAY_OF_FILE); +# Constants for the columns within the manifest ListStore widget. + +use constant RLS_ICON_COLUMN => 0; +use constant RLS_NAME_COLUMN => 1; +use constant RLS_DATE_COLUMN => 2; + # The Glade generated widgets object. # ***** FUNCTIONAL PROTOTYPES FOR THIS FILE ***** @@ -94,6 +103,9 @@ sub get_completion(address@hidden;\%); sub delete_event_cb($$$); sub destroy_event_cb($$;$); sub get_completion(address@hidden;\%); +sub get_dir_contents(address@hidden@); +sub manifest_treeview_cursor_changed_cb($$); +sub manifest_treeview_row_activated_cb($$$$); sub new_browser_instance(); sub update_browser_state($$); # @@ -148,12 +160,18 @@ sub new_browser_instance() my(@branch_list, $browser, + $renderer, + $tv_column, $div); $browser = {}; $browser->{mtn} = Monotone::AutomateStdio->new(); $browser->{glade} = Gtk2::GladeXML->new("../mtn-browse.glade"); + # Flag to stop recursive calling of callbacks. + + $browser->{in_cb} = 0; + # Connect Glade registered signal handlers. $browser->{glade}->signal_autoconnect @@ -176,13 +194,30 @@ sub new_browser_instance() $browser->{glade}->get_widget("branch_comboboxentry"); $browser->{directory_combo} = $browser->{glade}->get_widget("directory_comboboxentry"); + $browser->{directory_up_button} = + $browser->{glade}->get_widget("directory_up_button"); $browser->{revision_combo} = $browser->{glade}->get_widget("revision_comboboxentry"); - $browser->{revision_treeview} = - $browser->{glade}->get_widget("revision_browser_treeview"); + $browser->{manifest_treeview} = + $browser->{glade}->get_widget("manifest_browser_treeview"); $browser->{tagged_tick} = $browser->{glade}->get_widget("tagged_only_checkbutton"); + # Setup the comboboxentry key release signal handlers. + + $browser->{branch_combo}->child()-> + signal_connect("key_release_event", + \&combo_key_release_event_cb, + $browser); + $browser->{directory_combo}->child()-> + signal_connect("key_release_event", + \&combo_key_release_event_cb, + $browser); + $browser->{revision_combo}->child()-> + signal_connect("key_release_event", + \&combo_key_release_event_cb, + $browser); + # Move the pane separator to a sensible position. $div = $browser->{glade}->get_widget("browser_hpaned"); @@ -196,7 +231,7 @@ sub new_browser_instance() $browser->{directory_combo}-> set_model(Gtk2::ListStore->new("Glib::String")); $browser->{directory_combo}->set_text_column(0); - $browser->{directory_combo}->set_wrap_width(1); + $browser->{directory_combo}->set_wrap_width(2); $browser->{revision_combo}-> set_model(Gtk2::ListStore->new("Glib::String")); $browser->{revision_combo}->set_text_column(0); @@ -204,8 +239,46 @@ sub new_browser_instance() # Setup the tree view file browser. - + $browser->{manifest_liststore} = + Gtk2::ListStore->new("Glib::String", "Glib::String", "Glib::String"); + $browser->{manifest_treeview}->set_model($browser->{manifest_liststore}); + $tv_column = Gtk2::TreeViewColumn->new(); + $tv_column->set_resizable(FALSE); + $tv_column->set_sizing("fixed"); + $tv_column->set_fixed_width(25); + $tv_column->set_sort_column_id(RLS_ICON_COLUMN); + $renderer = Gtk2::CellRendererPixbuf->new(); + $tv_column->pack_start($renderer, TRUE); + $tv_column->set_attributes($renderer, "stock-id" => RLS_ICON_COLUMN); + $browser->{manifest_treeview}->append_column($tv_column); + + $tv_column = Gtk2::TreeViewColumn->new(); + $tv_column->set_title("File Name"); + $tv_column->set_resizable(TRUE); + $tv_column->set_sizing("fixed"); + $tv_column->set_fixed_width(180); + $tv_column->set_sort_column_id(RLS_NAME_COLUMN); + $renderer = Gtk2::CellRendererText->new(); + $tv_column->pack_start($renderer, FALSE); + $tv_column->set_attributes($renderer, "text" => RLS_NAME_COLUMN); + $browser->{manifest_treeview}->append_column($tv_column); + + $tv_column = Gtk2::TreeViewColumn->new(); + $tv_column->set_title("Last Modified"); + $tv_column->set_resizable(TRUE); + $tv_column->set_sizing("fixed"); + $tv_column->set_sort_column_id(RLS_DATE_COLUMN); + $renderer = Gtk2::CellRendererText->new(); + $tv_column->pack_start($renderer, FALSE); + $tv_column->set_attributes($renderer, "markup" => RLS_DATE_COLUMN); + $browser->{manifest_treeview}->append_column($tv_column); + + # TODO $browser->{manifest_treeview}->set_enable_search(TRUE); + $browser->{manifest_treeview}->set_enable_search(FALSE); + $browser->{manifest_treeview}->set_search_equal_func + (sub { print "EQUAL\n"; }); + # Update the browser'a internal state. update_browser_state($browser, DATABASE_CHANGED); @@ -236,8 +309,13 @@ sub combo_changed_cb($$) my($widget, $browser) = @_; my ($change_state, - $combo_details); + $combo_details, + $item, + $value); + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + if ($widget == $browser->{branch_combo}) { $change_state = BRANCH_CHANGED; @@ -248,12 +326,36 @@ sub combo_changed_cb($$) $change_state = REVISION_CHANGED; $combo_details = $browser->{revision_combo_details}; } + elsif ($widget == $browser->{directory_combo}) + { + $change_state = DIRECTORY_CHANGED; + $combo_details = $browser->{directory_combo_details}; + } + else + { + return; + } - $combo_details->{value} = $widget->child()->get_text(); - $combo_details->{completed} = 1; - $browser->{main_appbar}->set_status(""); - update_browser_state($browser, $change_state); + # For some reason best known to itself, Gtk+ calls this callback when the + # user presses a key for the first time (but not subsequently) after a + # value is selected via the pulldown menu. So we have to guard against + # this. Under these circumstances the key release callback is also called. + # So, put simply, only do something inside this callback if the value is a + # direct match to one in our list. + $value = $widget->child()->get_text(); + foreach $item (@{$combo_details->{list}}) + { + if ($value eq $item) + { + $combo_details->{value} = $value; + $combo_details->{completed} = 1; + $browser->{main_appbar}->set_status(""); + update_browser_state($browser, $change_state); + last; + } + } + } # ############################################################################## @@ -284,6 +386,7 @@ sub combo_key_release_event_cb($$$) my($widget, $event, $browser) = @_; my ($change_state, + $combo, $combo_details, $completed, $completion, @@ -294,25 +397,42 @@ sub combo_key_release_event_cb($$$) $old_value, $value); - if ($widget == $browser->{branch_combo}) + return FALSE if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + if ($widget == $browser->{branch_combo}->child()) { + $combo = $browser->{branch_combo}; $change_state = BRANCH_CHANGED; $combo_details = $browser->{branch_combo_details}; $name = "branch"; } - elsif ($widget == $browser->{revision_combo}) + elsif ($widget == $browser->{revision_combo}->child()) { + $combo = $browser->{revision_combo}; $change_state = REVISION_CHANGED; $combo_details = $browser->{revision_combo_details}; $name = "revision"; } + elsif ($widget == $browser->{directory_combo}->child()) + { + $combo = $browser->{directory_combo}; + $change_state = DIRECTORY_CHANGED; + $combo_details = $browser->{directory_combo_details}; + $name = "directory"; + } + else + { + return FALSE; + } # The user has typed something in then validate it and auto-complete it if # necessary. $completed = 0; $old_completed = $combo_details->{completed}; - $value = $old_value = $widget->child()->get_text(); + $old_value = $combo_details->{value}; + $value = $widget->get_text(); if ($value ne $combo_details->{value}) { @@ -326,6 +446,7 @@ sub combo_key_release_event_cb($$$) # So that the spacebar triggers auto-complete. $value =~ s/\s+$//o; + $old_value = $value; $len = length($value); if (get_completion($value, @@ -343,8 +464,8 @@ sub combo_key_release_event_cb($$$) } $value = $completion; $len = length($value); - $widget->child()->set_text($value); - $widget->child()->set_position(-1); + $widget->set_text($value); + $widget->set_position(-1); } $combo_details->{value} = $value; @@ -352,10 +473,10 @@ sub combo_key_release_event_cb($$$) # Update the pulldown choices. - $widget->get_model()->clear(); + $combo->get_model()->clear(); foreach $item (@{$combo_details->{list}}) { - $widget->append_text($item) if ($value eq substr($item, 0, $len)); + $combo->append_text($item) if ($value eq substr($item, 0, $len)); $combo_details->{completed} = 1 if (! $completed && $value eq $item); } @@ -374,6 +495,163 @@ sub combo_key_release_event_cb($$$) # ############################################################################## # +# Routine - directory_up_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the up +# directory button. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub directory_up_button_clicked_cb +{ + + my($widget, $browser) = @_; + + my $value; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + # Simply go up one directory level in the manifest if we aren't already at + # the top. + + $value = $browser->{directory_combo_details}->{value}; + if ($value ne "") + { + if ($value =~ m/^.+\/.+/o) + { + ($value) = ($value =~ m/^(.+)\/[^\/]+$/o); + } + else + { + $value = ""; + } + $browser->{directory_combo_details}->{value} = $value; + $browser->{directory_combo_details}->{completed} = 1; + $browser->{directory_combo}->child()->set_text($value); + $browser->{main_appbar}->set_status(""); + update_browser_state($browser, DIRECTORY_CHANGED); + } + +} +# +############################################################################## +# +# Routine - manifest_treeview_cursor_changed_cb +# +# Description - Callback routine called when the user changes the value of +# a ComboBoxEntry by entering a character (key release +# event). +# +# Data - $widget : The widget object that received the signal. +# $event : A Gtk2::Gdk::Event object describing the +# event that has occurred. +# $browser : The browser instance that is associated with +# this widget. +# Return Value : TRUE if the event has been handled and needs +# no further handling, otherwise false if the +# event should carry on through the remaining +# event handling. +# +############################################################################## + + + +sub manifest_treeview_cursor_changed_cb($$) +{ + + my($widget, $browser) = @_; + + my $iter; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + $iter = $browser->{manifest_liststore}->append(); + $browser->{manifest_liststore}->set($iter, + RLS_ICON_COLUMN, "gtk-open", + RLS_NAME_COLUMN, "the-main-dir", + RLS_DATE_COLUMN, "2005-01-01"); + $iter = $browser->{manifest_liststore}->append(); + $browser->{manifest_liststore}->set($iter, + RLS_ICON_COLUMN, "gtk-open", + RLS_NAME_COLUMN, "another-main-dir", + RLS_DATE_COLUMN, "2005-02-01"); + foreach (1..5) + { + $iter = $browser->{manifest_liststore}->append(); + $browser->{manifest_liststore}->set($iter, + RLS_ICON_COLUMN, "gtk-file", + RLS_NAME_COLUMN, "filexxxxxxxxxxxxxxxxxxddddddddskjhdkshskjhdjshdjshdkshdkshdskjd", + RLS_DATE_COLUMN, "2005-02-01"); + } + + + + print "CC " . scalar(@_) . "\n"; + return FALSE; + print "CC : " . Dumper address@hidden; + return FALSE; + + +} +# +############################################################################## +# +# Routine - manifest_treeview_row_activated_cb +# +# Description - Callback routine called when the user changes the value of +# a ComboBoxEntry by entering a character (key release +# event). +# +# Data - $widget : The widget object that received the signal. +# $event : A Gtk2::Gdk::Event object describing the +# event that has occurred. +# $browser : The browser instance that is associated with +# this widget. +# Return Value : TRUE if the event has been handled and needs +# no further handling, otherwise false if the +# event should carry on through the remaining +# event handling. +# +############################################################################## + + + +sub manifest_treeview_row_activated_cb($$$$) +{ + + my($widget, $tree_path, $tree_view_column, $browser) = @_; + + my $iter; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + $browser->{manifest_liststore}->clear(); + + $iter = $browser->{manifest_liststore}->append(); + $browser->{manifest_liststore}->set($iter, + RLS_ICON_COLUMN, "gtk-open", + RLS_NAME_COLUMN, "the-main-dir", + RLS_DATE_COLUMN, "2005-01-01"); + $iter = $browser->{manifest_liststore}->append(); + + print "RA " . scalar(@_) . "\n"; + return FALSE; + print "RA : " . Dumper address@hidden; + return FALSE; + +} +# +############################################################################## +# # Routine - delete_event_cb # # Description - Callback routine called when the used has attempted to @@ -455,6 +733,8 @@ sub update_browser_state($$) my($browser, $changed) = @_; + # The list of available branches has changed. + if ($changed & BRANCH) { @@ -474,15 +754,17 @@ sub update_browser_state($$) # Update the branch list combobox. - $browser->{branch_combo}->child()->set_text(""); $browser->{branch_combo}->get_model()->clear(); foreach $branch (@branch_list) { $browser->{branch_combo}->append_text($branch); } + $browser->{branch_combo}->child()->set_text(""); } + # The list of available revisions has changed. + if ($changed & REVISION) { @@ -510,20 +792,107 @@ sub update_browser_state($$) # Update the revision list combobox. - $browser->{revision_combo}->child()->set_text(""); $browser->{revision_combo}->get_model()->clear(); foreach $revision (@revision_list) { $revision = "i:" . $revision; $browser->{revision_combo}->append_text($revision); } + $browser->{revision_combo}->child()->set_text(""); } + # The list of available files and directories has changed. + if ($changed & DIRECTORY) { + + my(@directory_list, + $item, + @manifest_list, + $revision); + + # Reset the directory combo. + + $browser->{directory_combo_details}->{completion_cache} = {}; + $browser->{directory_combo_details}->{completed} = 0; + $browser->{directory_combo_details}->{value} = ""; + + # Get the new manifest. + + if ($browser->{revision_combo_details}->{completed}) + { + $revision = $browser->{revision_combo_details}->{value}; + $revision =~ s/^i://o; + $browser->{mtn}->get_manifest_of(address@hidden, $revision); + } + $browser->{manifest} = address@hidden; + + # Generate a simple list of directories for auto completion. + + foreach $item (@manifest_list) + { + push(@directory_list, $item->{name}) + if ($item->{type} eq "directory"); + } + $browser->{directory_combo_details}->{list} = address@hidden; + + # Update the directory list combobox. + + $browser->{directory_combo}->get_model()->clear(); + foreach $item (@directory_list) + { + $browser->{directory_combo}->append_text($item); + } + $browser->{directory_combo}->child()->set_text(""); + } + # The list of displayed files and directories has changed. + + if ($changed & DIRECTORY_VIEW) + { + + my(@directory_entry_list, + $item); + + # Reset the manifest tree view. + + $browser->{manifest_liststore}->clear(); + + # Get the contents of the new directory. + + if ($browser->{directory_combo_details}->{completed} + || $browser->{directory_combo_details}->{value} eq "") + { + get_dir_contents($browser->{directory_combo_details}->{value}, + @{$browser->{manifest}}, + @directory_entry_list); + } + $browser->{directory_entry_list} = address@hidden; + + # Disable the directory up button if we are already at the top level, + # otherwise make sure it is enabled. + + $browser->{directory_up_button}->set_sensitive + (($browser->{directory_combo_details}->{value} eq "") + ? FALSE : TRUE); + + # Update the directory tree view. + + foreach $item (@directory_entry_list) + { + $browser->{manifest_liststore}-> + set($browser->{manifest_liststore}->append(), + RLS_ICON_COLUMN, + ($item->{manifest_entry}->{type} eq "directory") + ? "gtk-open" : "gtk-file", + RLS_NAME_COLUMN, $item->{name}, + RLS_DATE_COLUMN, "2005-01-01"); + } + + } + if ($changed & FILE) { } @@ -647,3 +1016,58 @@ sub get_completion(address@hidden;\%) return 1; } +# +############################################################################## +# +# Routine - get_dir_contents +# +# Description - Given a path and a Monotone manifest, return a subset of +# the manifest that represents the contents of just that +# directory along with the directory entry names. +# +# Data - $path : The path to the directory from the top level of +# the manifest. +# $manifest : A reference to a Monotone manifest. +# $result : A reference to a list that is to contain the +# result (a list of records containing the short +# directory entry name and a reference to the +# related manifest entry). +# +############################################################################## + + + +sub get_dir_contents(address@hidden@) +{ + + my($path, $manifest, $result) = @_; + + my($entry, + $extract_re, + $i, + $match_re, + $name); + + $i = 0; + if ($path eq "") + { + $match_re = qr/^[^\/]+$/; + $extract_re = qr/^([^\/]+)$/; + } + else + { + $match_re = qr/^${path}\/[^\/]+$/; + $extract_re = qr/^${path}\/([^\/]+)$/; + } + @$result = (); + foreach $entry (@$manifest) + { + if ($entry->{name} =~ m/$match_re/) + { + ($name) = ($entry->{name} =~ m/$extract_re/); + $$result[$i ++] = {manifest_entry => $entry, + name => $name}; + } + } + +} ============================================================ --- mtn-browse.glade db85aab77dba15a9926d2438b6cc20077013fdb9 +++ mtn-browse.glade 32a19a669843c4ba9f45293d822899dd11c5b1fc @@ -350,7 +350,6 @@ True - 0 @@ -402,7 +401,6 @@ True - 0 @@ -561,6 +559,7 @@ criteria for selecting a revision True + 0 @@ -587,6 +586,7 @@ criteria for selecting a revisionTrue GTK_RELIEF_NORMAL True + @@ -615,7 +615,7 @@ criteria for selecting a revision - + True True GTK_POLICY_ALWAYS @@ -624,13 +624,15 @@ criteria for selecting a revisionGTK_CORNER_TOP_LEFT - + True True True False - False + True True + + @@ -750,7 +752,7 @@ criteria for selecting a revisionName of the file being displayed 1 1 - 0 0 277 10 249.3 277 + 0 0 106 10 95.4 106 0 0 17 10 15.3 17 @@ -802,7 +804,7 @@ criteria for selecting a revisionDate of when file was last changed 1 1 - 0 0 277 10 249.3 277 + 0 0 106 10 95.4 106 0 0 17 10 15.3 17 @@ -918,7 +920,7 @@ criteria for selecting a revisionFile's unique id in database 1 1 - 0 0 283 10 254.7 283 + 0 0 112 10 100.8 112 0 0 17 10 15.3 17 @@ -971,7 +973,7 @@ file was last changed file was last changed 1 1 - 0 0 283 10 254.7 283 + 0 0 112 10 100.8 112 0 0 17 10 15.3 17