# # # patch "mtn-browse" # from [7faf3ef238c9a94a65ca075142ca202dbc820e52] # to [ef3dec36196d4a756d6cbc96976bb635b256df5d] # # patch "mtn-browse.glade" # from [227b6396b82d3f5b1f254c912ddb2eafcd5e9d87] # to [063bb2f85f9b906321f07e8b30ebd1d0c1eb6eff] # ============================================================ --- mtn-browse 7faf3ef238c9a94a65ca075142ca202dbc820e52 +++ mtn-browse ef3dec36196d4a756d6cbc96976bb635b256df5d @@ -62,6 +62,8 @@ use POSIX qw(strftime); use Monotone::AutomateStdio; use POSIX qw(:errno_h :sys_wait_h); use POSIX qw(strftime); +use Symbol qw(gensym); +use Text::Tabs; # Temporary debug stuff. @@ -111,6 +113,11 @@ use constant DLS_VALUE_COLUMN => 2; use constant DLS_NAME_COLUMN => 1; use constant DLS_VALUE_COLUMN => 2; +# Constants for the columns within the comparison files ListStore widget. + +use constant CLS_NAME_COLUMN => 0; +use constant CLS_LINE_NR_COLUMN => 1; + # Text viewable application mime types. my @text_viewable_app_mime_types = @@ -191,31 +198,38 @@ sub advanced_find_button_clicked_cb($$); sub advanced_find($$$); sub advanced_find_button_clicked_cb($$); +sub coloured_revision_change_log_button_clicked_cb($$); sub combo_changed_cb($$); sub combo_key_release_event_cb($$$); +sub compare_button_clicked_cb($$); +sub compare_revisions($$$;$); sub create_advanced_find_window(); sub create_format_tags($); sub delete_event_cb($$$); sub destroy_event_cb($$;$); sub directory_up_button_clicked_cb($$); sub file_change_log_button_clicked_cb($$); -sub file_history_button_clicked_cb($$); -sub generate_revision_report($$$$); +sub file_comparison_combobox_changed_cb($$); +sub generate_revision_report($$$;$); sub get_change_log_window(); sub get_completion($$$$;$); sub get_dir_contents($$$); sub get_file_history_helper($$$); -sub get_file_history_window(); +sub get_history_window(); +sub get_revision_comparison_window(); sub get_revision_ids($$); sub gtk2_update(); +sub history_list_button_clicked_cb($$); sub make_busy($$); sub manifest_browser_treeview_cursor_changed_cb($$); sub manifest_browser_treeview_row_activated_cb($$$$); +sub mtn_diff($$$$;$); sub mtn_error_handler($$); sub new_browser_instance(); sub populate_button_clicked_cb($$); sub revisions_treeview_cursor_changed_cb($$); sub revisions_treeview_row_activated_cb($$$$); +sub run_command($@); sub set_label_value($$); sub setup_sigchld_handler($); sub sigchld_handler(); @@ -279,8 +293,8 @@ sub update_browser_state($$); # Routine - combo_changed_cb # # Description - Callback routine called when the user changes the value of -# a ComboBoxEntry by selecting an entry from its pulldown -# list. +# a branch or revision comboboxentry by selecting an entry +# from its pulldown list. # # Data - $widget : The widget object that received the signal. # $instance : The window instance that is associated with @@ -295,14 +309,14 @@ sub combo_changed_cb($$) my($widget, $instance) = @_; + return if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + my ($change_state, $combo_details, $item, $value); - return if ($instance->{in_cb}); - local $instance->{in_cb} = 1; - if ($widget == $instance->{branch_combo}) { $change_state = BRANCH_CHANGED; @@ -350,8 +364,8 @@ sub combo_changed_cb($$) # Routine - combo_key_release_event_cb # # Description - Callback routine called when the user changes the value of -# a ComboBoxEntry by entering a character (key release -# event). +# a branch or revision 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 @@ -372,6 +386,9 @@ sub combo_key_release_event_cb($$$) my($widget, $event, $instance) = @_; + return FALSE if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + my ($change_state, $combo, $combo_details, @@ -384,9 +401,6 @@ sub combo_key_release_event_cb($$$) $old_value, $value); - return FALSE if ($instance->{in_cb}); - local $instance->{in_cb} = 1; - if ($widget == $instance->{branch_combo}->child()) { $combo = $instance->{branch_combo}; @@ -485,8 +499,7 @@ sub combo_key_release_event_cb($$$) # Routine - tagged_checkbutton_toggled_cb # # Description - Callback routine called when the user changes the value of -# the tagged_checkbutton. -# list. +# the tagged check button. # # Data - $widget : The widget object that received the signal. # $instance : The window instance that is associated with @@ -514,7 +527,7 @@ sub tagged_checkbutton_toggled_cb($$) # Routine - advanced_find_button_clicked_cb # # Description - Callback routine called when the user clicks on the -# advanced find button. +# advanced find button in a main browser window. # # Data - $widget : The widget object that received the signal. # $browser : The browser instance that is associated with @@ -529,14 +542,14 @@ sub advanced_find_button_clicked_cb($$) my($widget, $browser) = @_; + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + my(@branches, $preset_branch, $revision_id, $state); - return if ($browser->{in_cb}); - local $browser->{in_cb} = 1; - if (advanced_find($browser, \$revision_id, address@hidden)) { @@ -586,7 +599,7 @@ sub advanced_find_button_clicked_cb($$) # Routine - revision_change_log_button_clicked_cb # # Description - Callback routine called when the user clicks on the -# revision change log button. +# revision change log button in a main browser window. # # Data - $widget : The widget object that received the signal. # $browser : The browser instance that is associated with @@ -601,14 +614,14 @@ sub revision_change_log_button_clicked_c my($widget, $browser) = @_; + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + my (@certs_list, $instance, @revision_details, @revision_ids); - return if ($browser->{in_cb}); - local $browser->{in_cb} = 1; - $instance = get_change_log_window(); # Get the currently selected revision id. @@ -640,7 +653,7 @@ sub revision_change_log_button_clicked_c # Routine - directory_up_button_clicked_cb # # Description - Callback routine called when the user clicks on the up -# directory button. +# directory button in a main browser window. # # Data - $widget : The widget object that received the signal. # $browser : The browser instance that is associated with @@ -655,12 +668,12 @@ sub directory_up_button_clicked_cb($$) my($widget, $browser) = @_; + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + my($len, $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. @@ -707,7 +720,7 @@ sub directory_up_button_clicked_cb($$) # Routine - file_change_log_button_clicked_cb # # Description - Callback routine called when the user clicks on the file -# change log button. +# change log button in a main browser window. # # Data - $widget : The widget object that received the signal. # $browser : The browser instance that is associated with @@ -722,6 +735,9 @@ sub file_change_log_button_clicked_cb($$ my($widget, $browser) = @_; + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + my ($button, @certs_list, $counter, @@ -730,10 +746,7 @@ sub file_change_log_button_clicked_cb($$ $instance, @revision_ids); - return if ($browser->{in_cb}); - local $browser->{in_cb} = 1; - - $instance = get_file_history_window(); + $instance = get_history_window(); local $instance->{in_cb} = 1; $instance->{mtn} = $browser->{mtn}; @@ -743,6 +756,7 @@ sub file_change_log_button_clicked_cb($$ $instance->{second_revision_id} = ""; $instance->{window}->set_title ("File History For " . $instance->{file_name}); + $instance->{history_label}->set_markup("File History"); $instance->{window}->show_all(); make_busy($instance, 1); @@ -757,11 +771,11 @@ sub file_change_log_button_clicked_cb($$ $instance->{appbar}->set_status("Fetching revision list"); $instance->{stop_button}->set_sensitive(TRUE); gtk2_update(); - Monotone::AutomateStdio->register_error_handler("warning", undef); + Monotone::AutomateStdio->register_error_handler("warning"); get_revision_ids($browser, address@hidden); get_file_history_helper($instance, \%history_hash, $revision_ids[0]); - Monotone::AutomateStdio->register_error_handler - ("both", \&mtn_error_handler); + Monotone::AutomateStdio->register_error_handler("both", + \&mtn_error_handler); $instance->{stop_button}->set_sensitive(FALSE); # Sort the list. @@ -788,8 +802,7 @@ sub file_change_log_button_clicked_cb($$ $instance->{mtn}->certs(address@hidden, $revision_id); generate_revision_report($instance->{history_buffer}, $revision_id, - address@hidden, - undef); + address@hidden); $instance->{history_buffer}-> insert($instance->{history_buffer}->get_end_iter(), "\n\n "); @@ -797,7 +810,7 @@ sub file_change_log_button_clicked_cb($$ $button = Gtk2::Button->new("Select As File 1"); $button->signal_connect("clicked", - \&file_history_button_clicked_cb, + \&history_list_button_clicked_cb, {instance => $instance, revision_id => $revision_id, button_type => "file-1"}); @@ -815,7 +828,7 @@ sub file_change_log_button_clicked_cb($$ $button = Gtk2::Button->new("Select As File 2"); $button->signal_connect("clicked", - \&file_history_button_clicked_cb, + \&history_list_button_clicked_cb, {instance => $instance, revision_id => $revision_id, button_type => "file-2"}); @@ -831,12 +844,12 @@ sub file_change_log_button_clicked_cb($$ $instance->{history_buffer}-> insert($instance->{history_buffer}->get_end_iter(), " "); - $button = Gtk2::Button->new("Full Revision Information"); + $button = Gtk2::Button->new("Full Revision Change Log"); $button->signal_connect("clicked", - \&file_history_button_clicked_cb, + \&history_list_button_clicked_cb, {instance => $instance, revision_id => $revision_id, - button_type => "revision-info"}); + button_type => "revision-changelog"}); $tooltips->set_tip($button, "View the revision's change log"); $instance->{history_textview}->add_child_at_anchor ($button, @@ -879,106 +892,10 @@ sub file_change_log_button_clicked_cb($$ # ############################################################################## # -# Routine - file_history_button_clicked_cb -# -# Description - Callback routine called when the user clicks on any of the -# buttons displayed in a file's history list. -# -# Data - $widget : The widget object that received the signal. -# $details : A reference to an anonymous hash containing the -# window instance, revision and action that is -# associated with this widget. -# -############################################################################## - - - -sub file_history_button_clicked_cb($$) -{ - - my($widget, $details) = @_; - - my($cl_instance, - $instance, - $revision_id); - - $instance = $details->{instance}; - $revision_id = $details->{revision_id}; - - return if ($instance->{in_cb}); - local $instance->{in_cb} = 1; - - if ($details->{button_type} eq "file-1" - || $details->{button_type} eq "file-2") - { - if ($details->{button_type} eq "file-1") - { - $instance->{first_revision_id} = $revision_id; - set_label_value($instance->{revision_id_1_label}, $revision_id); - if ($instance->{first_revision_id} - eq $instance->{second_revision_id}) - { - $instance->{second_revision_id} = ""; - set_label_value($instance->{revision_id_2_label}, ""); - } - } - else - { - $instance->{second_revision_id} = $revision_id; - set_label_value($instance->{revision_id_2_label}, $revision_id); - if ($instance->{second_revision_id} - eq $instance->{first_revision_id}) - { - $instance->{first_revision_id} = ""; - set_label_value($instance->{revision_id_1_label}, ""); - } - } - if ($instance->{first_revision_id} ne "" - && $instance->{second_revision_id} ne "") - { - $instance->{compare_button}->set_sensitive(TRUE); - } - else - { - $instance->{compare_button}->set_sensitive(FALSE); - } - } - else - { - - my(@certs_list, - @revision_details); - - # Display the full revision change log. - - $cl_instance = get_change_log_window(); - $cl_instance->{changelog_buffer}->set_text(""); - $cl_instance->{window}->set_title("Revision " . $revision_id); - $instance->{mtn}->certs(address@hidden, $revision_id); - $instance->{mtn}->get_revision(address@hidden, $revision_id); - generate_revision_report($cl_instance->{changelog_buffer}, - $revision_id, - address@hidden, - address@hidden); - if ($cl_instance->{changelog_scrolledwindow}->realized()) - { - $cl_instance->{changelog_scrolledwindow}->get_vadjustment()-> - set_value(0); - $cl_instance->{changelog_scrolledwindow}->get_hadjustment()-> - set_value(0); - } - $cl_instance->{window}->show_all(); - - } - -} -# -############################################################################## -# # Routine - manifest_browser_treeview_cursor_changed_cb # # Description - Callback routine called when the user selects an entry in -# the manifest treeview. +# the manifest treeview in a main browser window. # # Data - $widget : The widget object that received the signal. # $browser : The browser instance that is associated with @@ -993,12 +910,12 @@ sub manifest_browser_treeview_cursor_cha my($widget, $browser) = @_; + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + my($manifest_entry, $short_name); - return if ($browser->{in_cb}); - local $browser->{in_cb} = 1; - # Get the manifest entry details for the item that was selected. $widget->get_selection()->selected_foreach @@ -1026,7 +943,7 @@ sub manifest_browser_treeview_cursor_cha # Routine - manifest_browser_treeview_row_activated_cb # # Description - Callback routine called when the user double clicks on an -# entry in the manifest treeview. +# entry in the manifest treeview in a main browser window. # # Data - $widget : The widget object that received the # signal. @@ -1046,12 +963,12 @@ sub manifest_browser_treeview_row_activa my($widget, $tree_path, $tree_view_column, $browser) = @_; + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + my($manifest_entry, $short_name); - return if ($browser->{in_cb}); - local $browser->{in_cb} = 1; - # Get the manifest entry details for the item that was double-clicked. $widget->get_selection()->selected_foreach @@ -1097,12 +1014,12 @@ sub simple_query_radiobutton_toggled_cb( my($widget, $advanced_find) = @_; + return if ($advanced_find->{in_cb}); + local $advanced_find->{in_cb} = 1; + my($len, $value); - return if ($advanced_find->{in_cb}); - local $advanced_find->{in_cb} = 1; - # Simply enable the relevant find widgets depending upon whether simple or # advanced mode is selected. @@ -1123,8 +1040,8 @@ sub simple_query_radiobutton_toggled_cb( # # Routine - execute_button_clicked_cb # -# Description - Callback routine called when the user clicks on the -# advanced find execute query button. +# Description - Callback routine called when the user clicks on the execute +# query button in the advanced find window. # # Data - $widget : The widget object that received the # signal. @@ -1154,7 +1071,7 @@ sub execute_button_clicked_cb($$) # Routine - populate_button_clicked_cb # # Description - Callback routine called when the user clicks on the -# advanced find populate selector button. +# populate selector button in the advanved find window. # # Data - $widget : The widget object that received the # signal. @@ -1170,15 +1087,15 @@ sub populate_button_clicked_cb($$) my($widget, $advanced_find) = @_; + return if ($advanced_find->{in_cb}); + local $advanced_find->{in_cb} = 1; + my($arg, $pos, $selector, $time_val, $to_insert); - return if ($advanced_find->{in_cb}); - local $advanced_find->{in_cb} = 1; - # Simply get the currently selected selector and then insert it into the # user's query string. @@ -1247,8 +1164,8 @@ sub populate_button_clicked_cb($$) # Routine - term_combobox_changed_cb # # Description - Callback routine called when the user changes the value of -# the term ComboBox by selecting an entry from its pulldown -# list. +# the term combobox by selecting an entry from its pulldown +# list in the advanced find window. # # Data - $widget : The widget object that received the signal. # $instance : The window instance that is associated with @@ -1263,15 +1180,15 @@ sub term_combobox_changed_cb($$) my($widget, $advanced_find) = @_; + return if ($advanced_find->{in_cb}); + local $advanced_find->{in_cb} = 1; + my($arg, $pos, $selector, $time_val, $to_insert); - return if ($advanced_find->{in_cb}); - local $advanced_find->{in_cb} = 1; - # Simply get the currently selected term and then enable/disable the text # entry and date entry widgets accordingly. @@ -1301,7 +1218,7 @@ sub term_combobox_changed_cb($$) # Routine - revisions_treeview_cursor_changed_cb # # Description - Callback routine called when the user selects an entry in -# the advanced find's revisions treeview. +# the revisions treeview in the advanced find window. # # Data - $widget : The widget object that received the # signal. @@ -1317,11 +1234,11 @@ sub revisions_treeview_cursor_changed_cb my($widget, $advanced_find) = @_; - my $revision_id; - return if ($advanced_find->{in_cb}); local $advanced_find->{in_cb} = 1; + my $revision_id; + # Get the selected revision id. $widget->get_selection()->selected_foreach @@ -1346,7 +1263,8 @@ sub revisions_treeview_cursor_changed_cb # Routine - revisions_treeview_row_activated_cb # # Description - Callback routine called when the user double clicks on an -# entry in the advanced find's revisions treeview. +# entry in the revisions treeview in the advanced find +# window. # # Data - $widget : The widget object that received the # signal. @@ -1367,11 +1285,11 @@ sub revisions_treeview_row_activated_cb( my($widget, $tree_path, $tree_view_column, $advanced_find) = @_; - my $revision_id; - return if ($advanced_find->{in_cb}); local $advanced_find->{in_cb} = 1; + my $revision_id; + # Get the selected revision id. $widget->get_selection()->selected_foreach @@ -1391,6 +1309,242 @@ sub revisions_treeview_row_activated_cb( # ############################################################################## # +# Routine - history_list_button_clicked_cb +# +# Description - Callback routine called when the user clicks on any of the +# buttons displayed in the history list in a history window. +# +# Data - $widget : The widget object that received the signal. +# $details : A reference to an anonymous hash containing the +# window instance, revision and action that is +# associated with this widget. +# +############################################################################## + + + +sub history_list_button_clicked_cb($$) +{ + + my($widget, $details) = @_; + + my($cl_instance, + $instance, + $revision_id); + + $instance = $details->{instance}; + $revision_id = $details->{revision_id}; + + return if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + + if ($details->{button_type} eq "file-1" + || $details->{button_type} eq "file-2") + { + if ($details->{button_type} eq "file-1") + { + $instance->{first_revision_id} = $revision_id; + set_label_value($instance->{revision_id_1_label}, $revision_id); + if ($instance->{first_revision_id} + eq $instance->{second_revision_id}) + { + $instance->{second_revision_id} = ""; + set_label_value($instance->{revision_id_2_label}, ""); + } + } + else + { + $instance->{second_revision_id} = $revision_id; + set_label_value($instance->{revision_id_2_label}, $revision_id); + if ($instance->{second_revision_id} + eq $instance->{first_revision_id}) + { + $instance->{first_revision_id} = ""; + set_label_value($instance->{revision_id_1_label}, ""); + } + } + if ($instance->{first_revision_id} ne "" + && $instance->{second_revision_id} ne "") + { + $instance->{compare_button}->set_sensitive(TRUE); + } + else + { + $instance->{compare_button}->set_sensitive(FALSE); + } + } + else + { + + my(@certs_list, + @revision_details); + + # Display the full revision change log. + + $cl_instance = get_change_log_window(); + $cl_instance->{changelog_buffer}->set_text(""); + $cl_instance->{window}->set_title("Revision " . $revision_id); + $instance->{mtn}->certs(address@hidden, $revision_id); + $instance->{mtn}->get_revision(address@hidden, $revision_id); + generate_revision_report($cl_instance->{changelog_buffer}, + $revision_id, + address@hidden, + address@hidden); + if ($cl_instance->{changelog_scrolledwindow}->realized()) + { + $cl_instance->{changelog_scrolledwindow}->get_vadjustment()-> + set_value(0); + $cl_instance->{changelog_scrolledwindow}->get_hadjustment()-> + set_value(0); + } + $cl_instance->{window}->show_all(); + + } + +} +# +############################################################################## +# +# Routine - compare_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# revision comparison button in a history window. +# +# Data - $widget : The widget object that received the signal. +# $instance : The window instance that is associated with +# this widget. +# +############################################################################## + + + +sub compare_button_clicked_cb($$) +{ + + my($widget, $instance) = @_; + + return if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + + my @revision_ids; + + # Sort the revisions by date, oldest first. + + @revision_ids = ($instance->{first_revision_id}, + $instance->{second_revision_id}); + $instance->{mtn}->toposort(address@hidden, @revision_ids); + + # Now compare them. + + compare_revisions($instance->{mtn}, + $revision_ids[0], + $revision_ids[1], + $instance->{file_name}); + +} +# +############################################################################## +# +# Routine - file_comparison_combobox_changed_cb +# +# Description - Callback routine called when the user changes the value of +# the file combobox by selecting an entry from its pulldown +# list in a revision comparison window. +# +# Data - $widget : The widget object that received the signal. +# $instance : The window instance that is associated with +# this widget. +# +############################################################################## + + + +sub file_comparison_combobox_changed_cb($$) +{ + + my($widget, $instance) = @_; + + return if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + + my($iter, + $line_nr); + + # Simply get the currently selected term and then enable/disable the text + # entry and date entry widgets accordingly. + + $line_nr = $instance->{file_combo}->get_model()->get + ($instance->{file_combo}->get_active_iter(), CLS_LINE_NR_COLUMN); + $iter = $instance->{comparison_buffer}->get_iter_at_line($line_nr); + $instance->{comparison_textview}->scroll_to_iter($iter, 0, TRUE, 0, 0); + +} +# +############################################################################## +# +# Routine - coloured_revision_change_log_button_clicked_cb +# +# Description - Callback routine called when the user clicks on either of +# the two coloured revision change log buttons in a revision +# comparison window. +# +# Data - $widget : The widget object that received the signal. +# $details : A reference to an anonymous hash containing the +# window instance, revision and action that is +# associated with this widget. +# +############################################################################## + + + +sub coloured_revision_change_log_button_clicked_cb($$) +{ + + my($widget, $instance) = @_; + + return if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + + my(@certs_list, + $cl_instance, + @revision_details, + $revision_id); + + # Work out what revision id to use. + + if ($widget == $instance->{red_revision_button}) + { + $revision_id = $instance->{red_revision_id}; + } + else + { + $revision_id = $instance->{green_revision_id}; + } + + # Display the full revision change log. + + $cl_instance = get_change_log_window(); + $cl_instance->{changelog_buffer}->set_text(""); + $cl_instance->{window}->set_title("Revision " . $revision_id); + $instance->{mtn}->certs(address@hidden, $revision_id); + $instance->{mtn}->get_revision(address@hidden, $revision_id); + generate_revision_report($cl_instance->{changelog_buffer}, + $revision_id, + address@hidden, + address@hidden); + if ($cl_instance->{changelog_scrolledwindow}->realized()) + { + $cl_instance->{changelog_scrolledwindow}->get_vadjustment()-> + set_value(0); + $cl_instance->{changelog_scrolledwindow}->get_hadjustment()-> + set_value(0); + } + $cl_instance->{window}->show_all(); + +} +# +############################################################################## +# # Routine - delete_event_cb # # Description - Callback routine called when the used has attempted to @@ -1553,13 +1707,13 @@ sub new_browser_instance() $browser->{text_file_sensitive_group} = (); foreach my $item ("search_text", "annotate") { - push(@{$browser->{text_file_sensitive_group}}, + push(@{$browser->{text_file_sensitive_group}}, $browser->{glade}->get_widget($item . "_button")); } $browser->{revision_sensitive_group} = (); foreach my $item ("revision_change_log", "revision_compare") { - push(@{$browser->{revision_sensitive_group}}, + push(@{$browser->{revision_sensitive_group}}, $browser->{glade}->get_widget($item . "_button")); } @@ -1680,6 +1834,14 @@ sub new_browser_instance() $browser->{revision_combo_details}->{preset} = 0; &{$browser->{update_handler}}($browser, DATABASE_CHANGED); + # Setup the list of windows that can be made busy for this application + # window. + + $browser->{busy_windows} = []; + push(@{$browser->{busy_windows}}, $browser->{window}->window()); + push(@{$browser->{busy_windows}}, + $browser->{file_view_sv}->get_window("text")); + push(@windows, $browser); return $browser; @@ -1731,6 +1893,7 @@ sub advanced_find($$$) $advanced_find->{window}->set_transient_for($browser->{window}); $advanced_find->{branch_combo_details}->{preset} = 0; $advanced_find->{revision_combo_details}->{preset} = 0; + $advanced_find->{appbar}->set_progress_percentage(0); $advanced_find->{appbar}->clear_stack(); &{$advanced_find->{update_handler}}($advanced_find, NEW_FIND); ($width, $height) = $advanced_find->{window}->get_default_size(); @@ -1952,6 +2115,14 @@ sub create_advanced_find_window() $font = Gtk2::Pango::FontDescription->from_string("monospace 10"); $instance->{details_textview}->modify_font($font) if (defined($font)); + # Setup the list of windows that can be made busy for this application + # window. + + $instance->{busy_windows} = []; + push(@{$instance->{busy_windows}}, $instance->{window}->window()); + push(@{$instance->{busy_windows}}, + $instance->{details_textview}->get_window("text")); + return $instance; } @@ -2029,7 +2200,14 @@ sub get_change_log_window() $instance->{window}->signal_connect ("delete_event", - sub { $_[0]->hide() unless ($_[2]->{in_cb}); return TRUE; }, + sub { + my($widget, $event, $instance) = @_; + return TRUE if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + $widget->hide(); + $instance->{changelog_buffer}->set_text(""); + return TRUE; + }, $instance); # Setup the revision changelog viewer. @@ -2043,10 +2221,19 @@ sub get_change_log_window() $instance->{grab_widget} = $instance->{window}; + # Setup the list of windows that can be made busy for this application + # window. + + $instance->{busy_windows} = []; + push(@{$instance->{busy_windows}}, $instance->{window}->window()); + push(@{$instance->{busy_windows}}, + $instance->{changelog_textview}->get_window("text")); + push(@windows, $instance); } else { + $instance->{in_cb} = 0; ($width, $height) = $instance->{window}->get_default_size(); $instance->{window}->resize($width, $height); } @@ -2061,10 +2248,9 @@ sub get_change_log_window() # ############################################################################## # -# Routine - get_file_history_window +# Routine - get_history_window # -# Description - Creates or prepares an existing a change log window for -# use. +# Description - Creates or prepares an existing change log window for use. # # Data - Return Value : A reference to the newly created or unused # change log instance record. @@ -2073,7 +2259,7 @@ sub get_change_log_window() -sub get_file_history_window() +sub get_history_window() { my ($font, @@ -2081,9 +2267,11 @@ sub get_file_history_window() $instance, $width); + # Look for an unused window first. + foreach my $window (@windows) { - if ($window->{type} eq "file_history_window" + if ($window->{type} eq "history_window" && ! $window->{window}->mapped()) { $instance = $window; @@ -2097,9 +2285,9 @@ sub get_file_history_window() if (! defined($instance)) { $instance = {}; - $instance->{type} = "file_history_window"; + $instance->{type} = "history_window"; $instance->{glade} = - Gtk2::GladeXML->new("../mtn-browse.glade", "file_history_window"); + Gtk2::GladeXML->new("../mtn-browse.glade", "history_window"); # Flag to stop recursive calling of callbacks. @@ -2120,10 +2308,11 @@ sub get_file_history_window() # Get the widgets that we are interested in. - $instance->{window} = - $instance->{glade}->get_widget("file_history_window"); + $instance->{window} = $instance->{glade}->get_widget("history_window"); $instance->{window}->set_icon($app_icon); $instance->{appbar} = $instance->{glade}->get_widget("appbar"); + $instance->{history_label} = + $instance->{glade}->get_widget("history_label"); $instance->{history_textview} = $instance->{glade}->get_widget("history_textview"); $instance->{history_scrolledwindow} = @@ -2143,7 +2332,14 @@ sub get_file_history_window() $instance->{window}->signal_connect ("delete_event", - sub { $_[0]->hide() unless ($_[2]->{in_cb}); return TRUE; }, + sub { + my($widget, $event, $instance) = @_; + return TRUE if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + $widget->hide(); + $instance->{history_buffer}->set_text(""); + return TRUE; + }, $instance); $instance->{stop_button}->signal_connect ("clicked", sub { $_[1]->{stop} = 1; }, $instance); @@ -2161,10 +2357,19 @@ sub get_file_history_window() $instance->{grab_widget} = $instance->{stop_button}; + # Setup the list of windows that can be made busy for this application + # window. + + $instance->{busy_windows} = []; + push(@{$instance->{busy_windows}}, $instance->{window}->window()); + push(@{$instance->{busy_windows}}, + $instance->{history_textview}->get_window("text")); + push(@windows, $instance); } else { + $instance->{in_cb} = 0; ($width, $height) = $instance->{window}->get_default_size(); $instance->{window}->resize($width, $height); $instance->{stop_button}->set_sensitive(FALSE); @@ -2234,6 +2439,586 @@ sub get_file_history_helper($$$) # ############################################################################## # +# Routine - get_revision_comparison_window +# +# Description - Creates or prepares an existing revision comparison window +# for use. +# +# Data - Return Value : A reference to the newly created or unused +# change log instance record. +# +############################################################################## + + + +sub get_revision_comparison_window() +{ + + my ($font, + $height, + $instance, + $renderer, + $width); + + # Look for an unused window first. + + foreach my $window (@windows) + { + if ($window->{type} eq "revision_comparison_window" + && ! $window->{window}->mapped()) + { + $instance = $window; + last; + } + } + + # Create a new revision comparison window if an unused one wasn't found, + # otherwise reuse an existing unused one. + + if (! defined($instance)) + { + $instance = {}; + $instance->{type} = "revision_comparison_window"; + $instance->{glade} = Gtk2::GladeXML->new("../mtn-browse.glade", + "revision_comparison_window"); + + # Flag to stop recursive calling of callbacks. + + $instance->{in_cb} = 0; + + # Connect Glade registered signal handlers. + + $instance->{glade}->signal_autoconnect + (sub { + my($callback_name, $widget, $signal_name, $signal_data, + $connect_object, $after, $user_data) = @_; + my $func = $after ? "signal_connect_after" : "signal_connect"; + $widget->$func($signal_name, + $callback_name, + $connect_object ? + $connect_object : $user_data); }, + $instance); + + # Get the widgets that we are interested in. + + $instance->{window} = + $instance->{glade}->get_widget("revision_comparison_window"); + $instance->{window}->set_icon($app_icon); + $instance->{appbar} = $instance->{glade}->get_widget("appbar"); + $instance->{comparison_label} = + $instance->{glade}->get_widget("comparison_label"); + $instance->{file_combo} = + $instance->{glade}->get_widget("file_comparison_combobox"); + $instance->{comparison_textview} = + $instance->{glade}->get_widget("comparison_textview"); + $instance->{comparison_scrolledwindow} = + $instance->{glade}->get_widget("comparison_scrolledwindow"); + $instance->{red_revision_button} = + $instance->{glade}->get_widget("red_revision_change_log_button"); + $instance->{green_revision_button} = + $instance->{glade}->get_widget("green_revision_change_log_button"); + + # Setup the file history callbacks. + + $instance->{window}->signal_connect + ("delete_event", + sub { + my($widget, $event, $instance) = @_; + return TRUE if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + $widget->hide(); + $instance->{file_combo}->get_model()->clear(); + $instance->{comparison_buffer}->set_text(""); + return TRUE; + }, + $instance); + + # Setup the file combobox. + + $instance->{file_combo}-> + set_model(Gtk2::ListStore->new("Glib::String", "Glib::Int")); + $renderer = Gtk2::CellRendererText->new(); + $instance->{file_combo}->pack_start($renderer, TRUE); + $instance->{file_combo}->add_attribute($renderer, "text" => 0); + $instance->{file_combo}->get_model()->set + ($instance->{file_combo}->get_model()->append(), + CLS_NAME_COLUMN, " ", + CLS_LINE_NR_COLUMN, 0); + + # Setup the revision comparison viewer. + + $instance->{comparison_buffer} = + $instance->{comparison_textview}->get_buffer(); + create_format_tags($instance->{comparison_buffer}); + $font = Gtk2::Pango::FontDescription->from_string("monospace 10"); + $instance->{comparison_textview}->modify_font($font) + if (defined($font)); + + # Setup the list of windows that can be made busy for this application + # window. + + $instance->{busy_windows} = []; + push(@{$instance->{busy_windows}}, $instance->{window}->window()); + push(@{$instance->{busy_windows}}, + $instance->{comparison_textview}->get_window("text")); + + push(@windows, $instance); + } + else + { + $instance->{in_cb} = 0; + local $instance->{in_cb} = 1; + ($width, $height) = $instance->{window}->get_default_size(); + $instance->{window}->resize($width, $height); + $instance->{file_combo}->get_model()->clear(); + $instance->{appbar}->set_progress_percentage(0); + $instance->{appbar}->clear_stack(); + } + + # Empty out the contents. + + $instance->{comparison_buffer}->set_text(""); + + return $instance; + +} +# +############################################################################## +# +# Routine - compare_revisions +# +# Description - Compares and then displays the differenece between the two +# specified revisions, optionally restricting it to the +# specified file. +# +# Data - $mtn : The Monotone instance handle that is to be +# used to do the comparison. +# $revision_id_1 : The first revision id that is to be +# compared. +# $revision_id_2 : The second revision id that is to be +# compared. +# $file_name : Either the name of the file that the +# revision comparison should be restricted +# to or undef for a full revision +# comparison. +# +############################################################################## + + + +sub compare_revisions($$$;$) +{ + + my($mtn, $revision_id_1, $revision_id_2, $file_name) = @_; + + my ($char, + @files, + $i, + $instance, + $is_binary, + $iter, + $len, + $line, + @lines, + $max_len, + $name, + $padding, + $rest); + + $instance = get_revision_comparison_window(); + local $instance->{in_cb} = 1; + + $instance->{window}->set_title("Differences Between Revisions " + . $revision_id_1 + . " And " + . $revision_id_2); + if (defined($file_name)) + { + $instance->{comparison_label}->set_markup("File Comparison"); + } + else + { + $instance->{comparison_label}-> + set_markup("Revision Comparison"); + } + $instance->{window}->show_all(); + gtk2_update(); + + make_busy($instance, 1); + $instance->{appbar}->push(""); + gtk2_update(); + + $instance->{mtn} = $mtn; + $instance->{red_revision_id} = $revision_id_1; + $instance->{green_revision_id} = $revision_id_2; + + # Get Monotone to do the comparison. + + $instance->{appbar}->set_status("Calculating differences"); + gtk2_update(); + mtn_diff(address@hidden, + $mtn->get_db_name(), + $revision_id_1, + $revision_id_2, + $file_name); + + # Find the longest line for future padding. + + $max_len = 0; + foreach my $line (@lines) + { + ($char, $rest) = unpack("a1a*", $line); + $rest =~ s/\s+$//o; + $rest = expand($rest); + $max_len = $len if (($len = length($rest)) > $max_len); + $line = $char . $rest; + } + + # Display the result, highlighting according to the diff output. Remember + # the first two lines are just empty comment lines. + + $instance->{appbar}->set_status("Formatting and displaying differences"); + gtk2_update(); + $padding = " " x $max_len; + $line = substr(" Summary" . $padding, 0, $max_len); + $instance->{comparison_buffer}->insert_with_tags_by_name + ($instance->{comparison_buffer}->get_end_iter(), + $line . "\n", + "compare-info"); + for ($i = 1; $i <= $#lines; ++ $i) + { + + # Deal with the initial comment lines that summarise the entire set of + # differences between the revisions. + + if ($lines[$i] =~ m/^\#/o) + { + $line = substr($lines[$i], 1); + $instance->{comparison_buffer}->insert + ($instance->{comparison_buffer}->get_end_iter(), + $line . "\n"); + } + + # Deal with lines that introduces a new file comparison. + + elsif ($lines[$i] =~ m/^==/o) + { + + # Print separator. + + $instance->{comparison_buffer}-> + insert_pixbuf($instance->{comparison_buffer}->get_end_iter(), + $line_image); + $instance->{comparison_buffer}-> + insert($instance->{comparison_buffer}->get_end_iter(), + "\n"); + + # Extract the file name, if this doesn't work then it is probably a + # comment stating that the file is binary. + + ++ $i; + ($name) = ($lines[$i] =~ m/^--- (.+)\s+[0-9a-f]{40}$/o); + if (defined($name)) + { + $is_binary = 0; + } + else + { + ($name) = ($lines[$i] =~ m/^\# (.+) is binary$/o); + $is_binary = 1; + } + + # Print out the details for the first file. + + $line = substr(substr($lines[$i], $is_binary ? 1 : 3) . $padding, + 0, + $max_len); + $instance->{comparison_buffer}->insert_with_tags_by_name + ($instance->{comparison_buffer}->get_end_iter(), + $line . "\n", + "compare-first-file-info"); + + # Store the file name and the starting line number so that the user + # can later jump straight to it using the file combobox. + + $iter = $instance->{comparison_buffer}->get_end_iter(); + $iter->backward_line(); + $instance->{comparison_buffer}->create_mark($name, $iter, FALSE); + push(@files, {file_name => $name, line_nr => $iter->get_line()}); + + # Print out the details for the second file if there is one. + + if (! $is_binary) + { + ++ $i; + $line = substr(substr($lines[$i], 3) . $padding, 0, $max_len); + $instance->{comparison_buffer}->insert_with_tags_by_name + ($instance->{comparison_buffer}->get_end_iter(), + $line . "\n", + "compare-second-file-info"); + } + + } + + # Deal with difference context lines. + + elsif ($lines[$i] =~ m/^@@/o) + { + $line = substr(substr($lines[$i], 2) . $padding, 0, $max_len); + $instance->{comparison_buffer}->insert_with_tags_by_name + ($instance->{comparison_buffer}->get_end_iter(), + $line . "\n", + "compare-info"); + } + + # Deal with - change lines. + + elsif ($lines[$i] =~ m/^-/o) + { + $line = substr(substr($lines[$i], 1) . $padding, 0, $max_len); + $instance->{comparison_buffer}->insert_with_tags_by_name + ($instance->{comparison_buffer}->get_end_iter(), + $line . "\n", + "compare-first-file"); + } + + # Deal with + change lines. + + elsif ($lines[$i] =~ m/^\+/o) + { + $line = substr(substr($lines[$i], 1) . $padding, 0, $max_len); + $instance->{comparison_buffer}->insert_with_tags_by_name + ($instance->{comparison_buffer}->get_end_iter(), + $line . "\n", + "compare-second-file"); + } + + # Print out the rest. + + else + { + $line = substr($lines[$i], 1); + $instance->{comparison_buffer}->insert + ($instance->{comparison_buffer}->get_end_iter(), + $line . "\n"); + } + + if (($i % 100) == 0) + { + $instance->{appbar}->set_progress_percentage + (($i + 1) / scalar(@lines)); + gtk2_update(); + } + + } + + # Delete the trailing newline. + + $iter = $instance->{comparison_buffer}->get_end_iter(); + $instance->{comparison_buffer}->delete + ($iter, $instance->{comparison_buffer}->get_end_iter()) + if ($iter->backward_char()); + + # Populate the file combobox. + + $instance->{appbar}->set_progress_percentage(0); + $instance->{appbar}->set_status("Populating file list"); + gtk2_update(); + @files = sort({ $a->{file_name} cmp $b->{file_name} } @files); + $i = 1; + $instance->{file_combo}->get_model()->clear(); + foreach my $file (@files) + { + $instance->{file_combo}->get_model()->set + ($instance->{file_combo}->get_model()->append(), + CLS_NAME_COLUMN, $file->{file_name}, + CLS_LINE_NR_COLUMN, $file->{line_nr}); + $instance->{appbar}->set_progress_percentage($i ++ / scalar(@files)); + gtk2_update(); + } + $instance->{appbar}->set_progress_percentage(0); + $instance->{appbar}->set_status(""); + gtk2_update(); + + # Make sure we are at the top. + + $instance->{comparison_scrolledwindow}->get_vadjustment()->set_value(0); + $instance->{comparison_scrolledwindow}->get_hadjustment()->set_value(0); + + $instance->{appbar}->pop(); + make_busy($instance, 0); + +} +# +############################################################################## +# +# Routine - generate_revision_report +# +# Description - Populate the specified Gtk2::TextBuffer with a pretty +# printed report on the specified revision. +# +# Data - $text_buffer : The Gtk2::TextBuffer that is to be +# populated. +# $revision_id : The id of the revision being reported +# on. +# $certs_list : A reference to a certs list as returned +# by $mtn->certs(). +# $revision_details : Either a reference to a revision +# details list as returned by +# $mtn->get_revision() if a detailed +# report is to be generated or undef if +# the report is to just be a summary. +# +############################################################################## + + + +sub generate_revision_report($$$;$) +{ + + my($text_buffer, $revision_id, $certs_list, $revision_details) = @_; + + my($change_log, + $manifest_id, + @parent_revision_ids, + %revision_data, + %seen, + @unique); + my @types = + ("Added", "Removed", "Changed", "Renamed", "Attributes"); + + # Revision id. + + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + "Revision id: ", + "bold"); + $text_buffer->insert($text_buffer->get_end_iter(), + $revision_id . "\n\n"); + + # Certs. + + foreach my $cert (@$certs_list) + { + if ($cert->{name} eq "changelog") + { + $change_log = $cert->{value}; + $change_log =~ s/\s+$//os; + } + else + { + $cert->{value} =~ s/T/ /o if ($cert->{name} eq "date"); + $text_buffer->insert_with_tags_by_name + ($text_buffer->get_end_iter(), + sprintf("%s:\t", ucfirst($cert->{name})), + "bold"); + $text_buffer->insert($text_buffer->get_end_iter(), + sprintf("%s\n", $cert->{value})); + } + } + + # Change log. + + $text_buffer->insert_with_tags_by_name + ($text_buffer->get_end_iter(), "\nChange Log:\n", "bold"); + $text_buffer->insert($text_buffer->get_end_iter(), + sprintf("%s", $change_log)); + + # The rest is only provided if it is a detailed report. + + if (defined($revision_details)) + { + + # Revision details. + + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + "\n\nChanges Made:\n", "bold"); + foreach my $type (@types) + { + $revision_data{$type} = []; + } + foreach my $change (@$revision_details) + { + if ($change->{type} eq "add_dir") + { + push(@{$revision_data{"Added"}}, $change->{name} . "/"); + } + elsif ($change->{type} eq "add_file") + { + push(@{$revision_data{"Added"}}, $change->{name}); + } + elsif ($change->{type} eq "delete") + { + push(@{$revision_data{"Removed"}}, $change->{name}); + } + elsif ($change->{type} eq "patch") + { + push(@{$revision_data{"Changed"}}, $change->{name}); + } + elsif ($change->{type} eq "rename") + { + push(@{$revision_data{"Renamed"}}, + $change->{from_name} . " -> " . $change->{to_name}); + } + elsif ($change->{type} eq "clear") + { + push(@{$revision_data{"Attributes"}}, + sprintf("%s: %s was cleared", + $change->{name}, + $change->{attribute})); + } + elsif ($change->{type} eq "clear" || $change->{type} eq "set") + { + push(@{$revision_data{"Attributes"}}, + sprintf("%s: %s = %s", + $change->{name}, + $change->{attribute}, + $change->{value})); + } + elsif ($change->{type} eq "old_revision") + { + push(@parent_revision_ids, $change->{revision_id}); + } + elsif ($change->{type} eq "new_manifest") + { + $manifest_id = $change->{manifest_id}; + } + } + foreach my $type (@types) + { + if (scalar(@{$revision_data{$type}}) > 0) + { + $text_buffer->insert_with_tags_by_name + ($text_buffer->get_end_iter(), + " " . $type . ":\n", "italics"); + %seen = (); + @unique = sort(grep { ! $seen{$_} ++ } + @{$revision_data{$type}}); + foreach my $line (@unique) + { + $text_buffer->insert($text_buffer->get_end_iter(), + "\t" . $line . "\n"); + } + } + } + + # Parent revision and manifest ids. + + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + "\nParent revision id(s):\t", + "bold"); + $text_buffer->insert($text_buffer->get_end_iter(), + join(" ", @parent_revision_ids) . "\n"); + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + "Manifest id:\t\t", + "bold"); + $text_buffer->insert($text_buffer->get_end_iter(), $manifest_id); + + } + +} +# +############################################################################## +# # Routine - update_browser_state # # Description - Update the display of the specified browser instance @@ -3095,6 +3880,8 @@ sub update_advanced_find_state($$) ("both", \&mtn_error_handler); } + $advanced_find->{mtn}->toposort(address@hidden, @revision_ids); + @revision_ids = reverse(@revision_ids); # Update the revisions tree view. @@ -3192,173 +3979,6 @@ sub update_advanced_find_state($$) # ############################################################################## # -# Routine - generate_revision_report -# -# Description - Populate the specified Gtk2::TextBuffer with a pretty -# printed report on the specified revision. -# -# Data - $text_buffer : The Gtk2::TextBuffer that is to be -# populated. -# $revision_id : The id of the revision being reported -# on. -# $certs_list : A reference to a certs list as returned -# by $mtn->certs(). -# $revision_details : Either a reference to a revision -# details list as returned by -# $mtn->get_revision() if a detailed -# report is to be generated or undef if -# the report is to just be a summary. -# -############################################################################## - - - -sub generate_revision_report($$$$) -{ - - my($text_buffer, $revision_id, $certs_list, $revision_details) = @_; - - my($change_log, - $manifest_id, - @parent_revision_ids, - %revision_data, - %seen, - @unique); - my @types = - ("Added", "Removed", "Changed", "Renamed", "Attributes"); - - # Revision id. - - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - "Revision id: ", - "bold"); - $text_buffer->insert($text_buffer->get_end_iter(), - $revision_id . "\n\n"); - - # Certs. - - foreach my $cert (@$certs_list) - { - if ($cert->{name} eq "changelog") - { - $change_log = $cert->{value}; - $change_log =~ s/\s+$//os; - } - else - { - $cert->{value} =~ s/T/ /o if ($cert->{name} eq "date"); - $text_buffer->insert_with_tags_by_name - ($text_buffer->get_end_iter(), - sprintf("%s:\t", ucfirst($cert->{name})), - "bold"); - $text_buffer->insert($text_buffer->get_end_iter(), - sprintf("%s\n", $cert->{value})); - } - } - - # Change log. - - $text_buffer->insert_with_tags_by_name - ($text_buffer->get_end_iter(), "\nChange Log:\n", "bold"); - $text_buffer->insert($text_buffer->get_end_iter(), - sprintf("%s", $change_log)); - - # The rest is only provided if it is a detailed report. - - if (defined($revision_details)) - { - - # Revision details. - - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - "\n\nChanges Made:\n", "bold"); - foreach my $type (@types) - { - $revision_data{$type} = []; - } - foreach my $change (@$revision_details) - { - if ($change->{type} eq "add_dir") - { - push(@{$revision_data{"Added"}}, $change->{name} . "/"); - } - elsif ($change->{type} eq "add_file") - { - push(@{$revision_data{"Added"}}, $change->{name}); - } - elsif ($change->{type} eq "delete") - { - push(@{$revision_data{"Removed"}}, $change->{name}); - } - elsif ($change->{type} eq "patch") - { - push(@{$revision_data{"Changed"}}, $change->{name}); - } - elsif ($change->{type} eq "rename") - { - push(@{$revision_data{"Renamed"}}, - $change->{from_name} . " -> " . $change->{to_name}); - } - elsif ($change->{type} eq "clear") - { - push(@{$revision_data{"Attributes"}}, - sprintf("%s: %s was cleared", - $change->{name}, - $change->{attribute})); - } - elsif ($change->{type} eq "clear" || $change->{type} eq "set") - { - push(@{$revision_data{"Attributes"}}, - sprintf("%s: %s = %s", - $change->{name}, - $change->{attribute}, - $change->{value})); - } - elsif ($change->{type} eq "old_revision") - { - push(@parent_revision_ids, $change->{revision_id}); - } - elsif ($change->{type} eq "new_manifest") - { - $manifest_id = $change->{manifest_id}; - } - } - foreach my $type (@types) - { - if (scalar(@{$revision_data{$type}}) > 0) - { - $text_buffer->insert_with_tags_by_name - ($text_buffer->get_end_iter(), - " " . $type . ":\n", "italics"); - %seen = (); - @unique = sort(grep { ! $seen{$_} ++ } - @{$revision_data{$type}}); - foreach my $line (@unique) - { - $text_buffer->insert($text_buffer->get_end_iter(), - "\t" . $line . "\n"); - } - } - } - - # Parent revision and manifest ids. - - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - "\nParent revision id(s):\t", - "bold"); - $text_buffer->insert($text_buffer->get_end_iter(), - join(" ", @parent_revision_ids) . "\n"); - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - "Manifest id:\t\t", - "bold"); - $text_buffer->insert($text_buffer->get_end_iter(), $manifest_id); - - } - -} -# -############################################################################## -# # Routine - get_completion # # Description - Given a value and a list, work out the largest unique @@ -3473,6 +4093,210 @@ sub get_completion($$$$;$) # ############################################################################## # +# Routine - mtn_diff +# +# Description - Compare two the specified two revisions, optionally +# restricting the comparison to the specified file. +# +# Data - $list : A reference to the list that is to contain +# the output from the diff command. +# $mtn : The Monotone database that is to be used +# or undef if the database associated with +# the current workspace is to be used. +# $revision_id_1 : The first revision id that is to be +# compared. +# $revision_id_2 : The second revision id that is to be +# compared. +# $file_name : Either the name of the file that the +# revision comparison should be restricted +# to or undef for a full revision +# comparison. +# Return Value : True if the comparison worked, otherwise +# false if something went wrong. +# +############################################################################## + + + +sub mtn_diff($$$$;$) +{ + + my($list, $mtn_db, $revision_id_1, $revision_id_2, $file_name) = @_; + + my($buffer, + @cmd); + + # Run mtn diff. + + @$list = (); + push(@cmd, "mtn"); + push(@cmd, "--db=" . $mtn_db) if (defined($mtn_db)); + push(@cmd, "diff"); + push(@cmd, "-r"); + push(@cmd, "i:" . $revision_id_1); + push(@cmd, "-r"); + push(@cmd, "i:" . $revision_id_2); + push(@cmd, $file_name) if (defined($file_name)); + run_command(\$buffer, @cmd) or return; + + # Break up the input into a list of lines. + + @$list = split(/\n/o, $buffer); + + return 1; + +} +# +############################################################################## +# +# Routine - run_command +# +# Description - Run the specified command and return its output. +# +# Data - $buffer : A reference to the buffer that is to contain +# the output from the command. +# $args : A list containing the command to run and its +# arguments. +# Return Value : True if the command worked, otherwise false +# if something went wrong. +# +############################################################################## + + + +sub run_command($@) +{ + + my($buffer, @args) = @_; + + my(@err, + $fd_err, + $fd_in, + $fd_out, + $pid, + $ret_val, + $status, + $stop, + $total_bytes, + $watcher); + + # Run the command. + + $fd_err = gensym(); + eval + { + $pid = open3($fd_in, $fd_out, $fd_err, @args); + }; + if ($@ ne "") + { + my $dialog = Gtk2::MessageDialog->new + (undef, + ["modal"], + "warning", + "close", + sprintf("The %s subprocess could not start,\n" + . "the system gave:\n%s", + Glib::Markup::escape_text($args[0]), + Glib::Markup::escape_text($@))); + $dialog->run(); + $dialog->destroy(); + return; + } + + # Setup a watch handler to get read our data and handle GTK2 events whilst + # the command is running. + + $stop = $total_bytes = 0; + $$buffer = ""; + $watcher = Gtk2::Helper->add_watch + (fileno($fd_out), "in", + sub { + my $bytes_read; + if (($bytes_read = sysread($fd_out, + $$buffer, + 32768, + $total_bytes)) + == 0) + { + $stop = 1; + } + else + { + $total_bytes += $bytes_read; + } + return TRUE; + }); + while (! $stop) + { + Gtk2->main_iteration(); + } + Gtk2::Helper->remove_watch($watcher); + + # Get any error output. + + @err = readline($fd_err); + + close($fd_in); + close($fd_out); + close($fd_err); + + # Reap the process and deal with any errors. + + if (($ret_val = waitpid($pid, 0)) == -1) + { + if ($! != ECHILD) + { + my $dialog = Gtk2::MessageDialog->new_with_markup + (undef, + ["modal"], + "warning", + "close", + sprintf("waitpid failed with:\n%s", + Glib::Markup::escape_text($!))); + $dialog->run(); + $dialog->destroy(); + return; + } + } + $status = $?; + if (WIFEXITED($status) && WEXITSTATUS($status) != 0) + { + my $dialog = Gtk2::MessageDialog->new_with_markup + (undef, + ["modal"], + "warning", + "close", + sprintf("The %s subprocess failed with an exit status\n" + . "of %d and printed the following on stderr:\n" + . "%s", + Glib::Markup::escape_text($args[0]), + WEXITSTATUS($status), + Glib::Markup::escape_text(join("", @err)))); + $dialog->run(); + $dialog->destroy(); + return; + } + elsif (WIFSIGNALED($status)) + { + my $dialog = Gtk2::MessageDialog->new + (undef, + ["modal"], + "warning", + "close", + sprintf("The %s subprocess was terminated by signal %d", + Glib::Markup::escape_text($args[0]), + WTERMSIG($status))); + $dialog->run(); + $dialog->destroy(); + return; + } + + return 1; + +} +# +############################################################################## +# # Routine - get_dir_contents # # Description - Given a path and a Monotone manifest, return a subset of @@ -3754,9 +4578,12 @@ sub make_busy($$) { Gtk2->grab_add($instance->{appbar}); } - foreach my $window (@windows) + foreach my $instance (@windows) { - $window->{window}->window()->set_cursor($busy_cursor); + foreach my $window (@{$instance->{busy_windows}}) + { + $window->set_cursor($busy_cursor); + } } } else @@ -3769,9 +4596,12 @@ sub make_busy($$) { Gtk2->grab_remove($instance->{appbar}); } - foreach my $window (@windows) + foreach my $instance (@windows) { - $window->{window}->window()->set_cursor(undef); + foreach my $window (@{$instance->{busy_windows}}) + { + $window->set_cursor(undef); + } } } @@ -3821,11 +4651,28 @@ sub create_format_tags($) my ($text_buffer) = @_; - $text_buffer->create_tag("bold", weight => PANGO_WEIGHT_BOLD); - $text_buffer->create_tag("italics", style => "italic"); + $text_buffer->create_tag("bold", "weight" => PANGO_WEIGHT_BOLD); + $text_buffer->create_tag("italics", "style" => "italic"); $text_buffer->create_tag("bold-italics", - weight => PANGO_WEIGHT_BOLD, - style => "italic"); + "weight" => PANGO_WEIGHT_BOLD, + "style" => "italic"); + $text_buffer->create_tag("compare-info", + "foreground" => "Yellow", + "background" => "LightSlateGrey"); + $text_buffer->create_tag("compare-first-file", + "foreground" => "DarkRed", + "background" => "MistyRose1"); + $text_buffer->create_tag("compare-first-file-info", + "weight" => PANGO_WEIGHT_BOLD, + "foreground" => "IndianRed1", + "background" => "DarkSlateGrey"); + $text_buffer->create_tag("compare-second-file", + "foreground" => "DarkGreen", + "background" => "DarkSeaGreen1"); + $text_buffer->create_tag("compare-second-file-info", + "weight" => PANGO_WEIGHT_BOLD, + "foreground" => "SpringGreen1", + "background" => "DarkSlateGrey"); } # ============================================================ --- mtn-browse.glade 227b6396b82d3f5b1f254c912ddb2eafcd5e9d87 +++ mtn-browse.glade 063bb2f85f9b906321f07e8b30ebd1d0c1eb6eff @@ -3234,7 +3234,7 @@ Tag - + True GTK_WINDOW_TOPLEVEL @@ -3473,7 +3473,7 @@ Tag True - <b>File History</b> + False True GTK_JUSTIFY_LEFT @@ -3523,7 +3523,7 @@ Tag 5 - + True 2 2 @@ -3747,6 +3747,7 @@ file version that is to be comparedTrue GTK_RELIEF_NORMAL True + @@ -3830,4 +3831,261 @@ file version that is to be compared + + True + + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + 750 + 550 + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + + + + True + False + 0 + + + + 5 + True + 0 + 0.5 + GTK_SHADOW_ETCHED_IN + + + + True + 0.5 + 0.5 + 1 + 1 + 0 + 0 + 0 + 0 + + + + 5 + True + False + 5 + + + + True + False + 5 + + + + True + Jump to the selected file + True + False + + + + True + Jump To File: + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + + + 0 + False + False + + + + + + True + + + + 0 + False + True + + + + + 0 + False + False + + + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + False + False + True + GTK_JUSTIFY_LEFT + GTK_WRAP_NONE + False + 0 + 0 + 0 + 0 + 0 + 0 + + + + + + 0 + True + True + + + + + + True + GTK_BUTTONBOX_SPREAD + 0 + + + + True + View the change log of the revision +that had the red highlighted text + True + True + GTK_RELIEF_NORMAL + True + + + + + True + <span foreground="DarkRed">Revision Change Log</span> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + + + + + + True + View the change log of the revision +that had the green highlighted text + True + True + GTK_RELIEF_NORMAL + True + + + + + True + <span foreground="SpringGreen4">Revision Change Log</span> + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + + + + + 0 + False + True + + + + + + + + + + True + + False + True + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + + + label_item + + + + + 0 + True + True + + + + + + True + True + True + + + 0 + False + True + + + + + +