# # # add_file "AdvancedFind.pm" # content [def35842dde7df7a16b069185cdb148945dd2efa] # # add_file "ChangeLog.pm" # content [bbaeb9fe833b6c05b752ed17023c943946ed0837] # # add_file "ComboAutoCompletion.pm" # content [a45bbf62e7f2efa0377247431714280e5cfd48cd] # # add_file "Completion.pm" # content [1054100fadcf594ae9902d61f490abd7e7d718fd] # # add_file "Globals.pm" # content [6b2a79a7d7abec36a2b78963676c17f65661b130] # # add_file "History.pm" # content [b73d263a8bef77075f30a740a1da06bf21207277] # # add_file "Utilities.pm" # content [13b6314e75888c8e6431a0bdf11d12478c31a3b5] # # add_file "line.png" # content [824953bb8f5b100ff8a27235c97337998c35d73c] # # patch "mtn-browse" # from [2f9cc86df7157a7eb26bb354b04a351da03c4054] # to [900690ff1257eaa6bddc465bbc66c96cd85ed282] # # set "line.png" # attr "mtn:manual_merge" # value "true" # ============================================================ --- AdvancedFind.pm def35842dde7df7a16b069185cdb148945dd2efa +++ AdvancedFind.pm def35842dde7df7a16b069185cdb148945dd2efa @@ -0,0 +1,1053 @@ +############################################################################## +# +# File Name - AdvancedFind.pm +# +# Description - The advanced find module for the mtn-browse application. +# This module contains all the routines for implementing the +# advanced find dialog. +# +# Author - A.E.Cooper. +# +# Legal Stuff - Copyright (c) 2007 Anthony Edward Cooper +# . +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; +# either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public +# License along with this software; if not, write to the Free +# Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307 USA. +# +############################################################################## +# +############################################################################## +# +# GLOBAL DATA FOR THIS MODULE +# +############################################################################## + + + +# ***** DIRECTIVES ***** + +require 5.008; + +use strict; + +# ***** FUNCTIONAL PROTOTYPES FOR THIS FILE ***** + +# Public routines. + +sub advanced_find($$$); + +# Private routines. + +sub create_advanced_find_window(); +sub execute_button_clicked_cb($$); +sub populate_button_clicked_cb($$); +sub revisions_treeview_cursor_changed_cb($$); +sub revisions_treeview_row_activated_cb($$$$); +sub simple_query_radiobutton_toggled_cb($$); +sub term_combobox_changed_cb($$); +sub update_advanced_find_state($$); +# +############################################################################## +# +# Routine - advanced_find +# +# Description - Displays the advanced find dialog window and then gets the +# user to select the revision they want. +# +# Data - $browser : The browser instance that started the +# advanced find. +# $revision_id : A reference to a variable that is to contain +# the selected revision id. +# $branches : A reference to a list that is to contain the +# list of branches that the selected revision +# is on. +# Return Value : True if a revision has been selected, +# otherwise false. +# +############################################################################## + + + +sub advanced_find($$$) +{ + + my($browser, $revision_id, $branches) = @_; + + my($advanced_find, + $height, + $new, + $width); + + # Look for an unused window first. + + foreach my $window (@windows) + { + if ($window->{type} eq "advanced_find_window" + && ! $window->{window}->mapped()) + { + $advanced_find = $window; + last; + } + } + + # Create a new advanced find window if an unused one wasn't found, + # otherwise reuse the existing unused one. + + if (defined($advanced_find)) + { + $new = 0; + } + else + { + $advanced_find = create_advanced_find_window(); + $new = 1; + } + + $advanced_find->{mtn} = $browser->{mtn}; + + # Update the window's internal state. + + { + local $advanced_find->{in_cb} = 1; + $advanced_find->{selected} = 0; + + # Reset the window contents, then show it. + + $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(); + $advanced_find->{window}->resize($width, $height); + $advanced_find->{window}->show_all(); + + # If necessary, setup the list of windows that can be made busy for + # this application window. + + if ($new) + { + $advanced_find->{busy_windows} = []; + push(@{$advanced_find->{busy_windows}}, + $advanced_find->{window}->window()); + push(@{$advanced_find->{busy_windows}}, + $advanced_find->{details_textview}->get_window("text")); + + push(@windows, $advanced_find); + } + + # Now actually update it with any preset values. + + $advanced_find->{branch_combo_details}->{preset} = 1; + $advanced_find->{branch_combo_details}->{completed} = + $browser->{branch_combo_details}->{completed}; + $advanced_find->{branch_combo_details}->{value} = + $browser->{branch_combo_details}->{value}; + + $advanced_find->{revision_combo_details}->{preset} = 1; + $advanced_find->{revision_combo_details}->{completed} = + $browser->{revision_combo_details}->{completed}; + $advanced_find->{revision_combo_details}->{value} = + $browser->{revision_combo_details}->{value}; + + $advanced_find->{tagged_tick}-> + set_active($browser->{tagged_tick}->get_active()); + + &{$advanced_find->{update_handler}}($advanced_find, NEW_FIND); + } + + # Handle all events until the dialog is dismissed. + + $advanced_find->{done} = 0; + while (! $advanced_find->{done}) + { + Gtk2->main_iteration(); + } + $advanced_find->{window}->hide(); + + # Deal with the result. + + @$branches = (); + $$revision_id = ""; + if ($advanced_find->{selected}) + { + my($branch_list, + @certs_list, + $found); + + $$revision_id = $advanced_find->{revisions_treeview_details}->{value}; + + # Build up a list of branches that the selected revision is on, putting + # the branch named in the branch combo box at the head if it is still + # applicable. + + $advanced_find->{mtn}->certs(address@hidden, $$revision_id); + $found = 0; + foreach my $cert (@certs_list) + { + if ($cert->{name} eq "branch") + { + if ($cert->{value} + ne $advanced_find->{branch_combo_details}->{value}) + { + push(@$branches, $cert->{value}); + } + else + { + $found = 1; + } + } + } + unshift(@$branches, $advanced_find->{branch_combo_details}->{value}) + if ($found); + push(@$branches, "") if (scalar(@$branches) == 0); + + return 1; + } + else + { + return; + } + +} +# +############################################################################## +# +# Routine - create_advanced_find_window +# +# Description - Creates the advanced find dialog window. +# +# Data - Return Value : A reference to the newly created advanced +# find instance record. +# +############################################################################## + + + +sub create_advanced_find_window() +{ + + my(@branch_list, + $font, + $instance, + $renderer, + $tv_column); + + $instance = {}; + $instance->{type} = "advanced_find_window"; + $instance->{glade} = + Gtk2::GladeXML->new("../mtn-browse.glade", "advanced_find_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); + + # Link in the update handler for the advanced find window. + + $instance->{update_handler} = \&update_advanced_find_state; + + # Get the widgets that we are interested in. + + $instance->{window} = + $instance->{glade}->get_widget("advanced_find_window"); + $instance->{window}->set_icon($app_icon); + $instance->{appbar} = $instance->{glade}->get_widget("appbar"); + $instance->{simple_query_radiobutton} = + $instance->{glade}->get_widget("simple_query_radiobutton"); + $instance->{simple_frame} = $instance->{glade}->get_widget("simple_frame"); + $instance->{advanced_frame} = + $instance->{glade}->get_widget("advanced_frame"); + $instance->{branch_combo} = + $instance->{glade}->get_widget("branch_comboboxentry"); + $instance->{revision_combo} = + $instance->{glade}->get_widget("revision_comboboxentry"); + $instance->{tagged_tick} = + $instance->{glade}->get_widget("tagged_checkbutton"); + $instance->{search_term_combo} = + $instance->{glade}->get_widget("search_term_comboboxentry"); + $instance->{term_combo} = $instance->{glade}->get_widget("term_combobox"); + $instance->{argument_entry} = + $instance->{glade}->get_widget("argument_entry"); + $instance->{date_dateedit} = + $instance->{glade}->get_widget("date_dateedit"); + $instance->{revisions_treeview} = + $instance->{glade}->get_widget("revisions_treeview"); + $instance->{details_textview} = + $instance->{glade}->get_widget("details_textview"); + $instance->{details_scrolledwindow} = + $instance->{glade}->get_widget("details_scrolledwindow"); + $instance->{selected_branch_label} = + $instance->{glade}->get_widget("selected_branch_value_label"); + $instance->{selected_revision_label} = + $instance->{glade}->get_widget("selected_revision_value_label"); + $instance->{ok_button} = $instance->{glade}->get_widget("ok_button"); + + # Setup the advanced find window deletion handlers. + + $instance->{window}->signal_connect + ("delete_event", + sub { $_[2]->{done} = 1 unless ($_[2]->{in_cb}); return TRUE; }, + $instance); + $instance->{glade}->get_widget("cancel_button")->signal_connect + ("clicked", sub { $_[1]->{done} = 1; }, $instance); + $instance->{glade}->get_widget("ok_button")->signal_connect + ("clicked", sub { $_[1]->{done} = $_[1]->{selected} = 1; }, $instance); + + # Setup the comboboxentry key release signal handlers. + + $instance->{branch_combo}->child()-> + signal_connect("key_release_event", + \&combo_key_release_event_cb, + $instance); + $instance->{revision_combo}->child()-> + signal_connect("key_release_event", + \&combo_key_release_event_cb, + $instance); + + # Setup the comboboxes. + + $instance->{branch_combo}->set_model(Gtk2::ListStore->new("Glib::String")); + $instance->{branch_combo}->set_text_column(0); + $instance->{branch_combo}->set_wrap_width(2); + $instance->{revision_combo}-> + set_model(Gtk2::ListStore->new("Glib::String")); + $instance->{revision_combo}->set_text_column(0); + $instance->{revision_combo}->set_wrap_width(2); + $instance->{search_term_combo}-> + set_model(Gtk2::ListStore->new("Glib::String")); + $instance->{search_term_combo}->set_text_column(0); + $instance->{query_history} = []; + $instance->{term_combo}->set_active(0); + + # Setup the revisions list browser. + + $instance->{revisions_liststore} = Gtk2::ListStore->new("Glib::String"); + $instance->{revisions_treeview}-> + set_model($instance->{revisions_liststore}); + $tv_column = Gtk2::TreeViewColumn->new(); + $tv_column->set_title("Matching Revision Ids"); + $tv_column->set_sort_column_id(0); + $renderer = Gtk2::CellRendererText->new(); + $tv_column->pack_start($renderer, FALSE); + $tv_column->set_attributes($renderer, "text" => 0); + $instance->{revisions_treeview}->append_column($tv_column); + $instance->{revisions_treeview}->set_search_column(0); + + # Setup the revision details viewer. + + $instance->{details_buffer} = $instance->{details_textview}->get_buffer(); + create_format_tags($instance->{details_textview}->get_buffer()); + $font = Gtk2::Pango::FontDescription->from_string("monospace 10"); + $instance->{details_textview}->modify_font($font) if (defined($font)); + + return $instance; + +} +# +############################################################################## +# +# Routine - simple_query_radiobutton_toggled_cb +# +# Description - Callback routine called when the user changes the advanced +# find mode radio button. +# +# Data - $widget : The widget object that received the +# signal. +# $advanced_find : The advanced find dialog window instance +# that is associated with this widget. +# +############################################################################## + + + +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); + + # Simply enable the relevant find widgets depending upon whether simple or + # advanced mode is selected. + + if ($advanced_find->{simple_query_radiobutton}->get_active()) + { + $advanced_find->{simple_frame}->set_sensitive(TRUE); + $advanced_find->{advanced_frame}->set_sensitive(FALSE); + } + else + { + $advanced_find->{simple_frame}->set_sensitive(FALSE); + $advanced_find->{advanced_frame}->set_sensitive(TRUE); + } + +} +# +############################################################################## +# +# Routine - execute_button_clicked_cb +# +# 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. +# $advanced_find : The advanced find dialog window instance +# that is associated with this widget. +# +############################################################################## + + + +sub execute_button_clicked_cb($$) +{ + + my($widget, $advanced_find) = @_; + + return if ($advanced_find->{in_cb}); + local $advanced_find->{in_cb} = 1; + + # Simply let the update handler deal with it. + + &{$advanced_find->{update_handler}}($advanced_find, REVISION_CHANGED); + +} +# +############################################################################## +# +# Routine - populate_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# populate selector button in the advanved find window. +# +# Data - $widget : The widget object that received the +# signal. +# $advanced_find : The advanced find dialog window instance +# that is associated with this widget. +# +############################################################################## + + + +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); + + # Simply get the currently selected selector and then insert it into the + # user's query string. + + $selector = $advanced_find->{term_combo}->get_model()->get + ($advanced_find->{term_combo}->get_active_iter(), 0); + $arg = $advanced_find->{argument_entry}->get_text(); + $time_val = strftime("%Y-%m-%dT%H:%M:%S", + localtime($advanced_find->{date_dateedit}-> + get_time())); + $to_insert = ""; + if ($selector eq "Author") + { + $to_insert = "a:" . (($arg eq "") ? "" : $arg); + } + elsif ($selector eq "Branch") + { + $to_insert = "b:" . (($arg eq "") ? "" : $arg); + } + elsif ($selector eq "Cert") + { + $to_insert = "c:" . (($arg eq "") ? "" : $arg); + } + elsif ($selector eq "Date (=)") + { + $to_insert = "d:" . $time_val; + } + elsif ($selector eq "Date (<=)") + { + $to_insert = "e:" . $time_val; + } + elsif ($selector eq "Date (>)") + { + $to_insert = "l:" . $time_val; + } + elsif ($selector eq "Head Revision") + { + $to_insert = "h:"; + } + elsif ($selector eq "Identifier") + { + $to_insert = "i:" . (($arg eq "") ? "" : $arg); + } + elsif ($selector eq "Parent") + { + $to_insert = "p:" . (($arg eq "") ? "" : $arg); + } + elsif ($selector eq "Separator") + { + $to_insert = "/"; + } + elsif ($selector eq "Tag") + { + $to_insert = "t:" . (($arg eq "") ? "" : $arg); + } + + $pos = $advanced_find->{search_term_combo}->child()->get_position(); + $advanced_find->{search_term_combo}->child()->insert_text + ($to_insert, $pos); + $advanced_find->{search_term_combo}->child()->set_position + ($pos + length($to_insert)); + +} +# +############################################################################## +# +# 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 in the advanced find window. +# +# Data - $widget : The widget object that received the signal. +# $instance : The window instance that is associated with +# this widget. +# +############################################################################## + + + +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); + + # Simply get the currently selected term and then enable/disable the text + # entry and date entry widgets accordingly. + + $selector = $advanced_find->{term_combo}->get_model()->get + ($advanced_find->{term_combo}->get_active_iter(), 0); + + if ($selector =~ m/^Date .*$/o) + { + $advanced_find->{argument_entry}->set_sensitive(FALSE); + $advanced_find->{date_dateedit}->set_sensitive(TRUE); + } + elsif ($selector eq "Head" || $selector eq "Separator") + { + $advanced_find->{argument_entry}->set_sensitive(FALSE); + $advanced_find->{date_dateedit}->set_sensitive(FALSE); + } + else + { + $advanced_find->{argument_entry}->set_sensitive(TRUE); + $advanced_find->{date_dateedit}->set_sensitive(FALSE); + } + +} +# +############################################################################## +# +# Routine - revisions_treeview_cursor_changed_cb +# +# Description - Callback routine called when the user selects an entry in +# the revisions treeview in the advanced find window. +# +# Data - $widget : The widget object that received the +# signal. +# $advanced_find : The advanced find dialog window instance +# that is associated with this widget. +# +############################################################################## + + + +sub revisions_treeview_cursor_changed_cb($$) +{ + + my($widget, $advanced_find) = @_; + + 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 + (sub { + my($model, $path, $iter) = @_; + $revision_id = $model->get($iter, 0); }); + + if (defined($revision_id) + && $revision_id + ne $advanced_find->{revisions_treeview_details}->{value}) + { + $advanced_find->{revisions_treeview_details}->{value} = $revision_id; + $advanced_find->{appbar}->clear_stack(); + &{$advanced_find->{update_handler}}($advanced_find, + SELECTED_REVISION_CHANGED); + } + +} +# +############################################################################## +# +# Routine - revisions_treeview_row_activated_cb +# +# Description - Callback routine called when the user double clicks on an +# entry in the revisions treeview in the advanced find +# window. +# +# Data - $widget : The widget object that received the +# signal. +# $tree_path : A Gtk2::TreePath object for the +# selected item. +# $tree_view_column : A Gtk2::TreeViewColumn object for the +# selected item. +# $advanced_find : The advanced find dialog window +# instance that is associated with this +# widget. +# +############################################################################## + + + +sub revisions_treeview_row_activated_cb($$$$) +{ + + my($widget, $tree_path, $tree_view_column, $advanced_find) = @_; + + 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 + (sub { + my($model, $path, $iter) = @_; + $revision_id = $model->get($iter, 0); }); + + if (defined($revision_id)) + { + $advanced_find->{revisions_treeview_details}->{value} = $revision_id; + $advanced_find->{appbar}->clear_stack(); + $advanced_find->{selected} = 1; + $advanced_find->{done} = 1; + } + +} +# +############################################################################## +# +# Routine - update_advanced_find_state +# +# Description - Update the display of the specified advanced find dialog +# window instance according to the specified state. +# +# Data - $advanced_find : The advanced find dialog window instance +# that is to have its state updated. +# $changed : What the user has changed. +# +############################################################################## + + + +sub update_advanced_find_state($$) +{ + + my($advanced_find, $changed) = @_; + + my $made_busy = 0; + + if ($advanced_find->{window}->realized()) + { + $made_busy = 1; + make_busy($advanced_find, 1); + } + $advanced_find->{appbar}->push(""); + gtk2_update(); + + # The list of available branches has changed. + + if ($changed & BRANCH) + { + + my @branch_list; + + # Reset the query mode. + + $advanced_find->{simple_query_radiobutton}->set_active(TRUE); + $advanced_find->{simple_frame}->set_sensitive(TRUE); + $advanced_find->{advanced_frame}->set_sensitive(FALSE); + + # Reset the branch selection. + + $advanced_find->{branch_combo_details}->{completion} = undef; + if (! $advanced_find->{branch_combo_details}->{preset}) + { + $advanced_find->{branch_combo_details}->{completed} = 0; + $advanced_find->{branch_combo_details}->{value} = ""; + } + $advanced_find->{branch_combo_details}->{preset} = 0; + + # Get the new list of branches. + + $advanced_find->{appbar}->set_status("Fetching branch list"); + gtk2_update(); + $advanced_find->{mtn}->branches(address@hidden) + if (defined($advanced_find->{mtn})); + $advanced_find->{branch_combo_details}->{list} = address@hidden; + + # Update the branch list combobox. + + $advanced_find->{appbar}->set_status("Populating branch list"); + gtk2_update(); + my $counter = 1; + $advanced_find->{branch_combo}->get_model()->clear(); + foreach my $branch (@branch_list) + { + $advanced_find->{branch_combo}->append_text($branch); + $advanced_find->{appbar}->set_progress_percentage + ($counter ++ / scalar(@branch_list)); + gtk2_update(); + } + $advanced_find->{branch_combo}->child()-> + set_text($advanced_find->{branch_combo_details}->{value}); + $advanced_find->{appbar}->set_progress_percentage(0); + $advanced_find->{appbar}->set_status(""); + gtk2_update(); + + } + + # The list of available revisions has changed. + + if ($changed & REVISION) + { + + my @revision_list; + + # Reset the revision selection. + + $advanced_find->{revision_combo_details}->{completion} = undef; + if (! $advanced_find->{revision_combo_details}->{preset}) + { + $advanced_find->{revision_combo_details}->{completed} = 0; + $advanced_find->{revision_combo_details}->{value} = ""; + } + $advanced_find->{revision_combo_details}->{preset} = 0; + + # Get the new list of revisions. + + if ($advanced_find->{branch_combo_details}->{completed}) + { + $advanced_find->{appbar}->set_status("Fetching revision list"); + gtk2_update(); + + # Get either a list of tags or revision ids depending upon what the + # user has chosen. + + if ($advanced_find->{tagged_tick}->get_active()) + { + my(%dup_list, + @list); + $advanced_find->{mtn}-> + tags(address@hidden, + $advanced_find->{branch_combo_details}->{value}); + $advanced_find->{appbar}->set_progress_percentage(0.5); + gtk2_update(); + foreach my $item (@list) + { + if (! exists($dup_list{$item->{tag}})) + { + push(@revision_list, $item->{tag}); + $dup_list{$item->{tag}} = 1; + } + } + } + else + { + $advanced_find->{mtn}-> + select(address@hidden, + "b:" . $advanced_find->{branch_combo_details}-> + {value}); + $advanced_find->{appbar}->set_progress_percentage(0.33); + gtk2_update(); + $advanced_find->{mtn}->toposort(address@hidden, + @revision_list); + $advanced_find->{appbar}->set_progress_percentage(0.66); + gtk2_update(); + splice(@revision_list, 0, scalar(@revision_list) - 100); + @revision_list = reverse(@revision_list); + } + $advanced_find->{appbar}->set_progress_percentage(1); + gtk2_update(); + } + $advanced_find->{revision_combo_details}->{list} = address@hidden; + + # Update the revision list combobox. + + $advanced_find->{appbar}->set_progress_percentage(0); + $advanced_find->{appbar}->set_status("Populating revision list"); + gtk2_update(); + my $counter = 1; + $advanced_find->{revision_combo}->get_model()->clear(); + foreach my $revision (@revision_list) + { + $advanced_find->{revision_combo}->append_text($revision); + $advanced_find->{appbar}->set_progress_percentage + ($counter ++ / scalar(@revision_list)); + gtk2_update(); + } + $advanced_find->{revision_combo}->child()-> + set_text($advanced_find->{revision_combo_details}->{value}); + $advanced_find->{appbar}->set_progress_percentage(0); + $advanced_find->{appbar}->set_status(""); + gtk2_update(); + + } + + # The list of displayed revisions has changed. + + if ($changed & REVISION_LIST) + { + + my($counter, + @revision_ids); + + # Reset the revisions tree view. + + $advanced_find->{revisions_liststore}->clear(); + $advanced_find->{revisions_treeview_details}->{value} = ""; + + # Get the list of matching revisions. + + $advanced_find->{appbar}->set_status("Finding revisions"); + gtk2_update(); + if ($advanced_find->{simple_query_radiobutton}->get_active()) + { + if ($advanced_find->{revision_combo_details}->{completed}) + { + get_revision_ids($advanced_find, address@hidden); + } + } + else + { + my $query; + $query = $advanced_find->{search_term_combo}->child()->get_text(); + + # Remember the user can type in any old rubbish with advanced + # queries! So protect ourselves. + + Monotone::AutomateStdio->register_error_handler + ("both", + sub { + my($severity, $message) = @_; + my $dialog; + $dialog = Gtk2::MessageDialog->new_with_markup + ($advanced_find->{window}, + ["modal"], + "warning", + "close", + sprintf("Problem with your query, Monotone " + . "gave:\n%s", + Glib::Markup::escape_text($message))); + $dialog->run(); + $dialog->destroy(); + die("Bad query"); }); + eval + { + $advanced_find->{mtn}->select(address@hidden, $query); + }; + + # If the query was valid the store it in the history. + + if (! $@) + { + my $found; + if (scalar(@revision_ids) == 0) + { + my $dialog; + $dialog = Gtk2::MessageDialog->new + ($advanced_find->{window}, + ["modal"], + "info", + "close", + "No revisions matched your query"); + $dialog->run(); + $dialog->destroy(); + } + $found = 0; + foreach my $entry (@{$advanced_find->{query_history}}) + { + if ($entry eq $query) + { + $found = 1; + last; + } + } + if (! $found) + { + if (unshift(@{$advanced_find->{query_history}}, $query) + > 20) + { + pop(@{$advanced_find->{query_history}}); + } + $advanced_find->{search_term_combo}->get_model()->clear(); + foreach my $entry (@{$advanced_find->{query_history}}) + { + $advanced_find->{search_term_combo}-> + append_text($entry); + } + } + } + Monotone::AutomateStdio->register_error_handler + ("both", \&mtn_error_handler); + + } + $advanced_find->{mtn}->toposort(address@hidden, @revision_ids); + @revision_ids = reverse(@revision_ids); + + # Update the revisions tree view. + + $advanced_find->{appbar}->set_status("Populating revision details"); + $counter = 1; + foreach my $item (@revision_ids) + { + $advanced_find->{revisions_liststore}-> + set($advanced_find->{revisions_liststore}->append(), + 0, $item); + $advanced_find->{appbar}->set_progress_percentage + ($counter ++ / scalar(@revision_ids)); + gtk2_update(); + } + $advanced_find->{revisions_treeview}->scroll_to_point(0, 0) + if ($advanced_find->{revisions_treeview}->realized()); + + $advanced_find->{appbar}->set_progress_percentage(0); + $advanced_find->{appbar}->set_status(""); + gtk2_update(); + + } + + # The selected revision has changed. + + if ($changed & REVISION_DETAILS) + { + + if ($advanced_find->{revisions_treeview_details}->{value} ne "") + { + if ($advanced_find->{selected_revision_label}->get_text() + ne $advanced_find->{revisions_treeview_details}->{value}) + { + my ($branch, + @certs_list, + @revision_details); + + $advanced_find->{details_buffer}->set_text(""); + $advanced_find->{mtn}->certs + (address@hidden, + $advanced_find->{revisions_treeview_details}->{value}); + $advanced_find->{mtn}->get_revision + (address@hidden, + $advanced_find->{revisions_treeview_details}->{value}); + generate_revision_report + ($advanced_find->{details_buffer}, + $advanced_find->{revisions_treeview_details}->{value}, + address@hidden, + "", + address@hidden); + + # Scroll back up to the top left. + + if ($advanced_find->{details_scrolledwindow}->realized()) + { + $advanced_find->{details_scrolledwindow}-> + get_vadjustment()->set_value(0); + $advanced_find->{details_scrolledwindow}-> + get_hadjustment()->set_value(0); + } + + # Update the selected branch and revision labels. + + $branch = ""; + foreach my $cert (@certs_list) + { + if ($cert->{name} eq "branch") + { + $branch = $cert->{value}; + last; + } + } + set_label_value($advanced_find->{selected_branch_label}, + $branch); + set_label_value($advanced_find->{selected_revision_label}, + $advanced_find->{revisions_treeview_details}-> + {value}); + + $advanced_find->{ok_button}->set_sensitive(TRUE); + } + } + else + { + $advanced_find->{ok_button}->set_sensitive(FALSE); + $advanced_find->{details_buffer}->set_text(""); + set_label_value($advanced_find->{selected_branch_label}, ""); + set_label_value($advanced_find->{selected_revision_label}, ""); + } + + } + + $advanced_find->{appbar}->pop(); + make_busy($advanced_find, 0) if ($made_busy); + +} + +1; ============================================================ --- ChangeLog.pm bbaeb9fe833b6c05b752ed17023c943946ed0837 +++ ChangeLog.pm bbaeb9fe833b6c05b752ed17023c943946ed0837 @@ -0,0 +1,173 @@ +############################################################################## +# +# File Name - ChangeLog.pm +# +# Description - The change log module for the mtn-browse application. This +# module contains all the routines for implementing the +# change log window. +# +# Author - A.E.Cooper. +# +# Legal Stuff - Copyright (c) 2007 Anthony Edward Cooper +# . +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; +# either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public +# License along with this software; if not, write to the Free +# Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307 USA. +# +############################################################################## +# +############################################################################## +# +# GLOBAL DATA FOR THIS MODULE +# +############################################################################## + + + +# ***** DIRECTIVES ***** + +require 5.008; + +use lib "/home/aecoope/perl"; +use strict; + +# ***** FUNCTIONAL PROTOTYPES FOR THIS FILE ***** + +# Public routines. + +sub get_change_log_window(); +# +############################################################################## +# +# Routine - get_change_log_window +# +# Description - Creates or prepares an existing a change log window for +# use. +# +# Data - Return Value : A reference to the newly created or unused +# change log instance record. +# +############################################################################## + + + +sub get_change_log_window() +{ + + my ($font, + $height, + $instance, + $width); + + foreach my $window (@windows) + { + if ($window->{type} eq "change_log_window" + && ! $window->{window}->mapped()) + { + $instance = $window; + last; + } + } + + # Create a new change log window if an unused one wasn't found, otherwise + # reuse an existing unused one. + + if (! defined($instance)) + { + $instance = {}; + $instance->{type} = "change_log_window"; + $instance->{glade} = + Gtk2::GladeXML->new("../mtn-browse.glade", "changelog_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("changelog_window"); + $instance->{window}->set_icon($app_icon); + $instance->{changelog_textview} = + $instance->{glade}->get_widget("changelog_textview"); + $instance->{changelog_scrolledwindow} = + $instance->{glade}->get_widget("changelog_scrolledwindow"); + + # Setup the changelog window deletion handler. + + $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->{changelog_buffer}->set_text(""); + return TRUE; + }, + $instance); + + # Setup the revision changelog viewer. + + $instance->{changelog_buffer} = + $instance->{changelog_textview}->get_buffer(); + create_format_tags($instance->{changelog_buffer}); + $font = Gtk2::Pango::FontDescription->from_string("monospace 10"); + $instance->{changelog_textview}->modify_font($font) + if (defined($font)); + + $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); + } + + # Empty out the contents. + + $instance->{changelog_buffer}->set_text(""); + + return $instance; + +} + +1; ============================================================ --- ComboAutoCompletion.pm a45bbf62e7f2efa0377247431714280e5cfd48cd +++ ComboAutoCompletion.pm a45bbf62e7f2efa0377247431714280e5cfd48cd @@ -0,0 +1,263 @@ +############################################################################## +# +# File Name - ComboAutoCompletion.pm +# +# Description - The combo box auto-completion utilities module for the +# mtn-browse application. This module contains assorted +# routines that implement auto-completion for alll branch and +# revision combo boxes. +# +# Author - A.E.Cooper. +# +# Legal Stuff - Copyright (c) 2007 Anthony Edward Cooper +# . +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; +# either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public +# License along with this software; if not, write to the Free +# Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307 USA. +# +############################################################################## +# +############################################################################## +# +# GLOBAL DATA FOR THIS MODULE +# +############################################################################## + + + +# ***** DIRECTIVES ***** + +require 5.008; + +use strict; + +# ***** FUNCTIONAL PROTOTYPES FOR THIS FILE ***** + +# Public routines. + +sub combo_changed_cb($$); +sub combo_key_release_event_cb($$$); +# +############################################################################## +# +# Routine - combo_changed_cb +# +# Description - Callback routine called when the user changes the value of +# 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 +# this widget. +# +############################################################################## + + + +sub combo_changed_cb($$) +{ + + my($widget, $instance) = @_; + + return if ($instance->{in_cb}); + local $instance->{in_cb} = 1; + + my ($change_state, + $combo_details, + $item, + $value); + + if ($widget == $instance->{branch_combo}) + { + $change_state = BRANCH_CHANGED; + $combo_details = $instance->{branch_combo_details}; + } + elsif ($widget == $instance->{revision_combo}) + { + $change_state = REVISION_CHANGED; + $combo_details = $instance->{revision_combo_details}; + } + elsif ($widget == $instance->{directory_combo}) + { + $change_state = DIRECTORY_CHANGED; + $combo_details = $instance->{directory_combo_details}; + } + else + { + return; + } + + # 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; + $instance->{appbar}->clear_stack(); + &{$instance->{update_handler}}($instance, $change_state); + last; + } + } + +} +# +############################################################################## +# +# Routine - combo_key_release_event_cb +# +# Description - Callback routine called when the user changes the value of +# 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 +# event that has occurred. +# $instance : The window 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 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, + $completed, + $completion, + $item, + $len, + $name, + $old_completed, + $old_value, + $value); + + if ($widget == $instance->{branch_combo}->child()) + { + $combo = $instance->{branch_combo}; + $change_state = BRANCH_CHANGED; + $combo_details = $instance->{branch_combo_details}; + $name = "branch"; + } + elsif ($widget == $instance->{revision_combo}->child()) + { + $combo = $instance->{revision_combo}; + $change_state = REVISION_CHANGED; + $combo_details = $instance->{revision_combo_details}; + $name = "revision"; + } + elsif ($widget == $instance->{directory_combo}->child()) + { + $combo = $instance->{directory_combo}; + $change_state = DIRECTORY_CHANGED; + $combo_details = $instance->{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}; + $old_value = $combo_details->{value}; + $value = $widget->get_text(); + if ($value ne $old_value) + { + + # Don't auto-complete if the user is simply deleting from the extreme + # right. + + $len = length($value); + if ($value ne substr($old_value, 0, $len)) + { + + # So that the spacebar triggers auto-complete. + + $value =~ s/\s+$//o; + $len = length($value); + + $combo_details->{completion} = + Completion->new($combo_details->{list}) + if (! defined($combo_details->{completion})); + + if ($combo_details->{completion}->get_completion($value, + \$completion, + \$completed)) + { + $instance->{appbar}->clear_stack(); + } + else + { + $instance->{appbar}-> + push("Invalid " . $name . " name`" . $value . "'"); + } + $value = $completion; + $len = length($value); + $widget->set_text($value); + $widget->set_position(-1); + + } + $combo_details->{value} = $value; + $combo_details->{last_typed_len} = length($value); + $combo_details->{completed} = $completed; + + # Update the pulldown choices. + + $combo->get_model()->clear(); + foreach $item (@{$combo_details->{list}}) + { + $combo->append_text($item) if ($value eq substr($item, 0, $len)); + $combo_details->{completed} = 1 + if (! $completed && $value eq $item); + } + + # Update the window state on a significant change. + + &{$instance->{update_handler}}($instance, $change_state) + if ($combo_details->{completed} != $old_completed + || $combo_details->{value} ne $old_value); + + } + + return FALSE; + +} + +1; ============================================================ --- Completion.pm 1054100fadcf594ae9902d61f490abd7e7d718fd +++ Completion.pm 1054100fadcf594ae9902d61f490abd7e7d718fd @@ -0,0 +1,202 @@ +############################################################################## +# +# File Name - Completion.pm +# +# Description - Class module that provides a basic auto-completion +# mechanism. +# +# Author - A.E.Cooper. +# +# Legal Stuff - Copyright (c) 2007 Anthony Edward Cooper +# . +# +# This library is free software; you can redistribute it +# and/or modify it under the terms of the GNU Lesser General +# Public License as published by the Free Software +# Foundation; either version 3 of the License, or (at your +# option) any later version. +# +# This library is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU Lesser General Public License for +# more details. +# +# You should have received a copy of the GNU Lesser General +# Public License along with this library; if not, write to +# the Free Software Foundation, Inc., 59 Temple Place - Suite +# 330, Boston, MA 02111-1307 USA. +# +############################################################################## +# +############################################################################## +# +# Package - Completion +# +# Description - A class for using Monotone's automate stdio interface. +# +############################################################################## + + + +# ***** PACKAGE DECLARATION ***** + +package Completion; + +# ***** DIRECTIVES ***** + +require 5.008; + +use strict; +use integer; + +# ***** PACKAGE INFORMATION ***** + +# We are just a base class. + +use base qw(Exporter); + +our @EXPORT = qw(); +our @EXPORT_OK = qw(); +our $VERSION = 0.1; + +# ***** FUNCTIONAL PROTOTYPES FOR THIS FILE ***** + +# Public methods. + +sub get_completion($$$$); +sub new($@); +# +############################################################################## +# +# Routine - new +# +# Description - Class constructor. +# +# Data - $class : Either the name of the class that is to be +# created or an object of that class. +# $list : A reference to a list of all possible +# completions. +# Return Value : A reference to the newly created object. +# +############################################################################## + + + +sub new($@) +{ + + my ($class, $list) = @_; + $class = ref($class) if ref($class); + + my($char, + $item, + $level, + $this); + + $this = {}; + $this->{tree} = {}; + + # Build up a hash tree for the list of possible items. + + foreach $item (@$list) + { + + # Build up nodes for an item. + + $level = $this->{tree}; + foreach $char (split(//o, $item)) + { + if (! exists($level->{$char})) + { + $level->{$char} = {}; + } + $level = $level->{$char}; + } + + # By adding this dummy node here it stops the auto-complete moving too + # far should another item extend beyond this point. I.e. auto + # completion stops at `net.venge.monotone.contrib' and not + # `net.venge.monotone.contrib.'. You could simply think of this node as + # an `end of string' token if you prefer. + + $level->{""} = ""; + + } + + return bless($this, $class); + +} +# +############################################################################## +# +# Routine - get_completion +# +# Description - Given a value, work out the largest unique match. +# +# Data - $this : The object. +# $value : The value to be completed. +# $result : A reference to a buffer that is to contain +# the result. +# $complete : A reference to a boolean that is to contain +# a `result is complete' indicator. +# Return Value : True if $value was expanded, otherwise false +# if $value had to be truncated due to no +# match (the maximum valid completion is still +# returned in $result). +# +############################################################################## + + + +sub get_completion($$$$) +{ + + my($this, $value, $result, $complete) = @_; + + my($char, + $level); + + # Lookup value, stopping when it becomes ambiguous or we get to the end of + # $value. + + $level = $this->{tree}; + $$result = ""; + foreach $char (split(//o, $value)) + { + last unless (exists($level->{$char})); + $level = $level->{$char}; + $$result .= $char; + } + + # Detect truncations. + + return if (length($value) > length($$result)); + + # Now try and expand it further. + + while (defined(%$level) && keys(%$level) == 1) + { + ($char) = keys(%$level); + $$result .= $char; + $level = $level->{$char}; + } + + # Detect complete completions (doesn't mean to say that it can't be + # extended, just that as it stands at the moment $$result does contain a + # valid unique value). + + if (! defined(%$level) || exists($level->{""})) + { + $$complete = 1; + } + else + { + $$complete = 0; + } + + return 1; + +} + +1; ============================================================ --- Globals.pm 6b2a79a7d7abec36a2b78963676c17f65661b130 +++ Globals.pm 6b2a79a7d7abec36a2b78963676c17f65661b130 @@ -0,0 +1,182 @@ +############################################################################## +# +# File Name - Globals.pm +# +# Description - The global data module for the mtn-browse application. This +# module contains all the global constants and variables used +# throughout the application. +# +# Author - A.E.Cooper. +# +# Legal Stuff - Copyright (c) 2007 Anthony Edward Cooper +# . +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; +# either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public +# License along with this software; if not, write to the Free +# Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307 USA. +# +############################################################################## +# +############################################################################## +# +# Package - Globals +# +# Description - See above. +# +############################################################################## + + + +# ***** PACKAGE DECLARATION ***** + +package Globals; + +# ***** DIRECTIVES ***** + +require 5.008; + +use strict; + +# ***** GLOBAL DATA DECLARATIONS ***** + +# Constants used to represent the different groups of widgets. + +use constant BRANCH => 0x01; +use constant DIRECTORY => 0x02; +use constant DIRECTORY_VIEW => 0x04; +use constant DISPLAY_OF_FILE => 0x08; +use constant FILE => 0x10; +use constant REVISION => 0x20; +use constant REVISION_LIST => 0x02; +use constant REVISION_DETAILS => 0x04; + +# 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 + | DIRECTORY_VIEW | FILE + | DISPLAY_OF_FILE); +use constant DATABASE_CHANGED => 0xff; +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 NEW_FIND => 0xff; +use constant REVISION_CHANGED => (DIRECTORY | REVISION_LIST + | DIRECTORY_VIEW | FILE + | DISPLAY_OF_FILE); +use constant SELECTED_REVISION_CHANGED => (REVISION_DETAILS); + +# Text viewable application mime types. + +our @text_viewable_app_mime_types = + qw(postscript + rtf + x-awk + x-cgi + x-csh + x-glade + x-java + x-javascript + x-jbuilder-project + x-perl + x-php + x-python + x-shellscript + x-troff-man + x-troff + xhtml+xml); + +# Supported text mime types (used for syntax highlighting. + +our @text_mime_types = + ( + { + pattern => qr/.*\.c$/o, + type => "text/x-csrc" + }, + { + pattern => qr/.*\.(C)|(cc)|(cp)|(cpp)|(CPP)|(cxx)|(c\+\+)$/o, + type => "text/x-c++src" + }, + { + pattern => qr/.*\.(h)|(hh)|(H)$/o, + type => "text/x-c++hdr" + }, + { + pattern => qr/.*\.h$/o, + type => "text/x-chdr" + }, + { + pattern => qr/(^[Mm]akefile(\.[^.]+)?)|(.*\.mk)$/o, + type => "text/x-makefile" + }, + { + pattern => qr/.*/o, + type => "text/plain" + } + ); + +# List of window instances. + +our @windows; + +# Assorted pixmaps. + +our($app_icon, + $line_image); + +# The busy cursor to use for the mouse. + +our $busy_cursor; + +# The tooltips widget. + +our $tooltips; + +# ***** PACKAGE INFORMATION ***** + +use base qw(Exporter); + +our %EXPORT_TAGS = ( + constants => [qw(BRANCH + BRANCH_CHANGED + DATABASE_CHANGED + DIRECTORY + DIRECTORY_CHANGED + DIRECTORY_VIEW + DISPLAY_OF_FILE + DISPLAY_OF_FILE_CHANGED + FILE + FILE_CHANGED + NEW_FIND + REVISION + REVISION_CHANGED + REVISION_DETAILS + REVISION_LIST + SELECTED_REVISION_CHANGED)], + variables => [qw($app_icon + $busy_cursor + $line_image + $tooltips + @text_mime_types + @text_viewable_app_mime_types + @windows)]); +our @EXPORT = qw(); +Exporter::export_ok_tags(qw(constants variables)); +our $VERSION = 0.1; + +1; ============================================================ --- History.pm b73d263a8bef77075f30a740a1da06bf21207277 +++ History.pm b73d263a8bef77075f30a740a1da06bf21207277 @@ -0,0 +1,1366 @@ +############################################################################## +# +# File Name - mtn-browse +# +# Description - The history module for the mtn-browse application. This +# module contains all the routines for implementing the +# revision and file history windows. +# +# Author - A.E.Cooper. +# +# Legal Stuff - Copyright (c) 2007 Anthony Edward Cooper +# . +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; +# either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public +# License along with this software; if not, write to the Free +# Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307 USA. +# +############################################################################## +# +############################################################################## +# +# GLOBAL DATA FOR THIS MODULE +# +############################################################################## + + + +# ***** DIRECTIVES ***** + +require 5.008; + +use strict; + +# ***** GLOBAL DATA DECLARATIONS ***** + +# Constants for the columns within the comparison files ListStore widget. + +use constant CLS_NAME_COLUMN => 0; +use constant CLS_LINE_NR_COLUMN => 1; + +# ***** FUNCTIONAL PROTOTYPES FOR THIS FILE ***** + +# Public routines. + +sub display_file_change_history($); +sub display_revision_change_history($); + +# Private routines. + +sub coloured_revision_change_log_button_clicked_cb($$); +sub compare_button_clicked_cb($$); +sub compare_revisions($$$;$); +sub file_comparison_combobox_changed_cb($$); +sub get_file_history_helper($$$); +sub get_history_window(); +sub get_revision_comparison_window(); +sub get_revision_history_helper($$$); +sub history_list_button_clicked_cb($$); +sub mtn_diff($$$$;$); +# +############################################################################## +# +# Routine - display_revision_change_history +# +# Description - Display a revision's change history, complete with +# selection and comparison buttons. +# +# Data - $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub display_revision_change_history($) +{ + + my $browser = $_[0]; + + my ($button, + @certs_list, + $counter, + $file_name, + %history_hash, + $instance, + @revision_ids); + + $instance = get_history_window(); + local $instance->{in_cb} = 1; + + $instance->{mtn} = $browser->{mtn}; + $instance->{file_name} = undef; + get_revision_ids($browser, address@hidden); + $instance->{first_revision_id} = ""; + $instance->{second_revision_id} = ""; + $instance->{window}->set_title("Revision History For " . $revision_ids[0]); + $instance->{history_label}->set_markup("Revision History"); + $instance->{window}->show_all(); + + make_busy($instance, 1); + $instance->{appbar}->push(""); + gtk2_update(); + + $instance->{stop_button}->set_sensitive(TRUE); + + # Get the list of file change revisions. Remember to include the current + # revision in the history. + + $instance->{appbar}->set_progress_percentage(0); + $instance->{appbar}->set_status("Fetching revision list"); + gtk2_update(); + $history_hash{$revision_ids[0]} = 1; + get_revision_history_helper($instance, \%history_hash, $revision_ids[0]); + + # Sort the list. + + $instance->{appbar}->set_status("Sorting revision list"); + gtk2_update(); + $instance->{history} = []; + $instance->{mtn}->toposort($instance->{history}, keys(%history_hash)); + %history_hash = (); + @{$instance->{history}} = reverse(@{$instance->{history}}); + + # Display the file's history. + + $instance->{appbar}->set_progress_percentage(0); + $instance->{appbar}->set_status("Displaying file history"); + gtk2_update(); + $counter = 1; + $instance->{stop} = 0; + $instance->{history_buffer}->set_text(""); + for my $revision_id (@{$instance->{history}}) + { + + # Print out the revision summary. + + $instance->{mtn}->certs(address@hidden, $revision_id); + generate_revision_report($instance->{history_buffer}, + $revision_id, + address@hidden, + ""); + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), "\n\n "); + + # Add the buttons. + + $button = Gtk2::Button->new("Select As Revision 1"); + $button->signal_connect("clicked", + \&history_list_button_clicked_cb, + {instance => $instance, + revision_id => $revision_id, + button_type => "1"}); + $tooltips->set_tip($button, + "Select this revision for comparison\n" + . "as the first revision"); + $instance->{history_textview}->add_child_at_anchor + ($button, + $instance->{history_buffer}-> + create_child_anchor($instance->{history_buffer}-> + get_end_iter())); + $button->show_all(); + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), " "); + + $button = Gtk2::Button->new("Select As Revision 2"); + $button->signal_connect("clicked", + \&history_list_button_clicked_cb, + {instance => $instance, + revision_id => $revision_id, + button_type => "2"}); + $tooltips->set_tip($button, + "Select this revision for comparison\n" + . "as the second revision"); + $instance->{history_textview}->add_child_at_anchor + ($button, + $instance->{history_buffer}-> + create_child_anchor($instance->{history_buffer}-> + get_end_iter())); + $button->show_all(); + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), " "); + + $button = Gtk2::Button->new("View Full Revision Change Log"); + $button->signal_connect("clicked", + \&history_list_button_clicked_cb, + {instance => $instance, + revision_id => $revision_id, + button_type => "revision-changelog"}); + $tooltips->set_tip($button, "View the revision's full change log"); + $instance->{history_textview}->add_child_at_anchor + ($button, + $instance->{history_buffer}-> + create_child_anchor($instance->{history_buffer}-> + get_end_iter())); + $button->show_all(); + + # Stop if the user wants to. + + last if ($instance->{stop}); + + # If we aren't at the end, print out the revision separator. + + if ($counter < scalar(@{$instance->{history}})) + { + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), "\n"); + $instance->{history_buffer}-> + insert_pixbuf($instance->{history_buffer}->get_end_iter(), + $line_image); + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), "\n"); + } + + if ($counter % 100 == 0) + { + $instance->{appbar}->set_progress_percentage + ($counter / scalar(@{$instance->{history}})); + gtk2_update(); + } + ++ $counter; + + } + + $instance->{stop_button}->set_sensitive(FALSE); + set_label_value($instance->{numbers_label}, $counter) + if ($instance->{stop}); + + # Make sure we are at the top. + + $instance->{history_scrolledwindow}->get_vadjustment()->set_value(0); + $instance->{history_scrolledwindow}->get_hadjustment()->set_value(0); + $instance->{appbar}->set_progress_percentage(0); + $instance->{appbar}->set_status(""); + gtk2_update(); + + $instance->{appbar}->pop(); + make_busy($instance, 0); + +} +# +############################################################################## +# +# Routine - display_file_change_history +# +# Description - Display a file's change history, complete with selection +# and comparison buttons. +# +# Data - $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub display_file_change_history($) +{ + + my $browser = $_[0]; + + my ($button, + @certs_list, + $counter, + $file_name, + %history_hash, + $instance, + @revision_ids); + + $instance = get_history_window(); + local $instance->{in_cb} = 1; + + $instance->{mtn} = $browser->{mtn}; + $instance->{file_name} = + $browser->{file_being_viewed}->{manifest_entry}->{name}; + $instance->{first_revision_id} = ""; + $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); + $instance->{appbar}->push(""); + gtk2_update(); + + # Get the list of file change revisions. Remember that a warning is + # generated when one goes back beyond a file's addition revision, so + # temporarily disable the warning handler. + + $instance->{appbar}->set_progress_percentage(0); + $instance->{appbar}->set_status("Fetching revision list"); + $instance->{stop_button}->set_sensitive(TRUE); + gtk2_update(); + 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); + $instance->{stop_button}->set_sensitive(FALSE); + + # Sort the list. + + $instance->{appbar}->set_status("Sorting revision list"); + gtk2_update(); + $instance->{history} = []; + $instance->{mtn}->toposort($instance->{history}, keys(%history_hash)); + %history_hash = (); + @{$instance->{history}} = reverse(@{$instance->{history}}); + + # Display the file's history. + + $instance->{appbar}->set_progress_percentage(0); + $instance->{appbar}->set_status("Displaying file history"); + gtk2_update(); + $counter = 1; + $instance->{history_buffer}->set_text(""); + for my $revision_id (@{$instance->{history}}) + { + + # Print out the revision summary. + + $instance->{mtn}->certs(address@hidden, $revision_id); + generate_revision_report($instance->{history_buffer}, + $revision_id, + address@hidden, + ""); + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), "\n\n "); + + # Add the buttons. + + $button = Gtk2::Button->new("Select As File 1"); + $button->signal_connect("clicked", + \&history_list_button_clicked_cb, + {instance => $instance, + revision_id => $revision_id, + button_type => "1"}); + $tooltips->set_tip($button, + "Select this file revision for\n" + . "comparison as the first file"); + $instance->{history_textview}->add_child_at_anchor + ($button, + $instance->{history_buffer}-> + create_child_anchor($instance->{history_buffer}-> + get_end_iter())); + $button->show_all(); + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), " "); + + $button = Gtk2::Button->new("Select As File 2"); + $button->signal_connect("clicked", + \&history_list_button_clicked_cb, + {instance => $instance, + revision_id => $revision_id, + button_type => "2"}); + $tooltips->set_tip($button, + "Select this file revision for\n" + . "comparison as the second file"); + $instance->{history_textview}->add_child_at_anchor + ($button, + $instance->{history_buffer}-> + create_child_anchor($instance->{history_buffer}-> + get_end_iter())); + $button->show_all(); + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), " "); + + $button = Gtk2::Button->new("View Full Revision Change Log"); + $button->signal_connect("clicked", + \&history_list_button_clicked_cb, + {instance => $instance, + revision_id => $revision_id, + button_type => "revision-changelog"}); + $tooltips->set_tip($button, "View the revision's full change log"); + $instance->{history_textview}->add_child_at_anchor + ($button, + $instance->{history_buffer}-> + create_child_anchor($instance->{history_buffer}-> + get_end_iter())); + $button->show_all(); + + # If we aren't at the end, print out the revision separator. + + if ($counter < scalar(@{$instance->{history}})) + { + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), "\n"); + $instance->{history_buffer}-> + insert_pixbuf($instance->{history_buffer}->get_end_iter(), + $line_image); + $instance->{history_buffer}-> + insert($instance->{history_buffer}->get_end_iter(), "\n"); + } + + $instance->{appbar}->set_progress_percentage + ($counter ++ / scalar(@{$instance->{history}})); + gtk2_update(); + + } + + # Make sure we are at the top. + + $instance->{history_scrolledwindow}->get_vadjustment()->set_value(0); + $instance->{history_scrolledwindow}->get_hadjustment()->set_value(0); + $instance->{appbar}->set_progress_percentage(0); + $instance->{appbar}->set_status(""); + gtk2_update(); + + $instance->{appbar}->pop(); + make_busy($instance, 0); + +} +# +############################################################################## +# +# 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 "1" || $details->{button_type} eq "2") + { + if ($details->{button_type} eq "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, + $colour, + @revision_details, + $revision_id); + + # Work out what revision id to use. + + if ($widget == $instance->{red_revision_button}) + { + $revision_id = $instance->{red_revision_id}; + $colour = "red"; + } + else + { + $revision_id = $instance->{green_revision_id}; + $colour = "green"; + } + + # 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, + $colour, + 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 - get_history_window +# +# Description - Creates or prepares an existing history window for use. +# +# Data - Return Value : A reference to the newly created or unused +# history instance record. +# +############################################################################## + + + +sub get_history_window() +{ + + my ($font, + $height, + $instance, + $width); + + # Look for an unused window first. + + foreach my $window (@windows) + { + if ($window->{type} eq "history_window" + && ! $window->{window}->mapped()) + { + $instance = $window; + last; + } + } + + # Create a new file history window if an unused one wasn't found, otherwise + # reuse an existing unused one. + + if (! defined($instance)) + { + $instance = {}; + $instance->{type} = "history_window"; + $instance->{glade} = + Gtk2::GladeXML->new("../mtn-browse.glade", "history_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("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} = + $instance->{glade}->get_widget("history_scrolledwindow"); + $instance->{stop_button} = + $instance->{glade}->get_widget("stop_button"); + $instance->{compare_button} = + $instance->{glade}->get_widget("compare_button"); + $instance->{numbers_label} = + $instance->{glade}->get_widget("numbers_value_label"); + $instance->{revision_id_1_label} = + $instance->{glade}->get_widget("revision_id_1_value_label"); + $instance->{revision_id_2_label} = + $instance->{glade}->get_widget("revision_id_2_value_label"); + + # 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->{history_buffer}->set_text(""); + return TRUE; + }, + $instance); + $instance->{stop_button}->signal_connect + ("clicked", sub { $_[1]->{stop} = 1; }, $instance); + + # Setup the file history viewer. + + $instance->{history_buffer} = + $instance->{history_textview}->get_buffer(); + create_format_tags($instance->{history_buffer}); + $font = Gtk2::Pango::FontDescription->from_string("monospace 10"); + $instance->{history_textview}->modify_font($font) if (defined($font)); + + # Make the stop button the grab widget when busy, this is so the user + # can interrupt the history gathering process. + + $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); + $instance->{compare_button}->set_sensitive(FALSE); + set_label_value($instance->{numbers_label}, ""); + set_label_value($instance->{revision_id_1_label}, ""); + set_label_value($instance->{revision_id_2_label}, ""); + } + + $instance->{stop} = 0; + + # Empty out the contents. + + $instance->{history_buffer}->set_text(""); + + return $instance; + +} +# +############################################################################## +# +# Routine - get_file_history_helper +# +# Description - Recursive routine for getting the revisions in a file's +# change history. +# +# Data - $instance : The file history window instance. +# $hash : A reference to a hash that is to contain the +# list of revision ids. +# $revision_id : The revision id from where the search is to +# commence. +# +############################################################################## + + + +sub get_file_history_helper($$$) +{ + + my($instance, $hash, $revision_id) = @_; + + return if ($instance->{stop}); + + my(@change_parents, + @parents); + + $instance->{mtn}->get_content_changed(address@hidden, + $revision_id, + $instance->{file_name}); + foreach my $revision (@change_parents) + { + if (! exists($hash->{$revision})) + { + $hash->{$revision} = 1; + set_label_value($instance->{numbers_label}, scalar(keys(%$hash))); + gtk2_update(); + @parents = (); + $instance->{mtn}->parents(address@hidden, $revision); + foreach my $parent (@parents) + { + get_file_history_helper($instance, $hash, $parent); + } + } + } + +} +# +############################################################################## +# +# Routine - get_revision_history_helper +# +# Description - Recursive routine for getting the revisions in a revision's +# change history. +# +# Data - $instance : The revision history window instance. +# $hash : A reference to a hash that is to contain the +# list of revision ids. +# $revision_id : The revision id from where the search is to +# commence. +# +############################################################################## + + + +sub get_revision_history_helper($$$) +{ + + my($instance, $hash, $revision_id) = @_; + + return if ($instance->{stop}); + + my @parents; + + $instance->{mtn}->parents(address@hidden, $revision_id); + foreach my $parent (@parents) + { + if (! exists($hash->{$parent})) + { + $hash->{$parent} = 1; + set_label_value($instance->{numbers_label}, scalar(keys(%$hash))); + gtk2_update(); + get_revision_history_helper($instance, $hash, $parent); + } + } + +} +# +############################################################################## +# +# 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 differeneces 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 - 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; + +} + +1; ============================================================ --- Utilities.pm 13b6314e75888c8e6431a0bdf11d12478c31a3b5 +++ Utilities.pm 13b6314e75888c8e6431a0bdf11d12478c31a3b5 @@ -0,0 +1,694 @@ +############################################################################## +# +# File Name - Utilities.pm +# +# Description - The utilities module for the mtn-browse application. This +# module contains assorted general purpose routines used +# throughout the application. +# +# Author - A.E.Cooper. +# +# Legal Stuff - Copyright (c) 2007 Anthony Edward Cooper +# . +# +# This program is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation; +# either version 3 of the License, or (at your option) any +# later version. +# +# This program is distributed in the hope that it will be +# useful, but WITHOUT ANY WARRANTY; without even the implied +# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +# PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public +# License along with this software; if not, write to the Free +# Software Foundation, Inc., 59 Temple Place - Suite 330, +# Boston, MA 02111-1307 USA. +# +############################################################################## +# +############################################################################## +# +# GLOBAL DATA FOR THIS MODULE +# +############################################################################## + + + +# ***** DIRECTIVES ***** + +require 5.008; + +use strict; + +# ***** FUNCTIONAL PROTOTYPES FOR THIS FILE ***** + +# Public routines. + +sub create_format_tags($); +sub generate_revision_report($$$$;$); +sub get_dir_contents($$$); +sub get_revision_ids($$); +sub gtk2_update(); +sub make_busy($$); +sub run_command($@); +sub set_label_value($$); +# +############################################################################## +# +# 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(). +# $colour : One of "red, "green" or "" depending +# upon the desired colour of the text. +# $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, $colour, $revision_details) + = @_; + + my($bold, + $change_log, + $italics, + $manifest_id, + $normal, + @parent_revision_ids, + %revision_data, + %seen, + @unique); + my @types = + ("Added", "Removed", "Changed", "Renamed", "Attributes"); + + # Sort out colour attributes. + + if ($colour ne "") + { + $normal = $colour; + $bold = "bold-" . $colour; + $italics = "italics-" . $colour; + } + else + { + $normal = "normal"; + $bold = "bold"; + $italics = "italics"; + } + + # Revision id. + + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + "Revision id: ", + $bold); + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + $revision_id . "\n\n", + $normal); + + # 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_with_tags_by_name + ($text_buffer->get_end_iter(), + sprintf("%s\n", $cert->{value}), + $normal); + } + } + + # Change log. + + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + "\nChange Log:\n", + $bold); + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + sprintf("%s", $change_log), + $normal); + + # 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_with_tags_by_name + ($text_buffer->get_end_iter(), + "\t" . $line . "\n", + $normal); + } + } + } + + # 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_with_tags_by_name + ($text_buffer->get_end_iter(), + join(" ", @parent_revision_ids) . "\n", + $normal); + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + "Manifest id:\t\t", + $bold); + $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), + $manifest_id, + $normal); + + } + +} +# +############################################################################## +# +# 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 +# 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($$$) +{ + + 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}; + } + } + +} +# +############################################################################## +# +# Routine - get_revision_ids +# +# Description - Return the currently selected revision id, whether this is +# specified via a tag or as a revision id. +# +# Data - $instance : The window instance. +# $revision_ids : The list of selected revision ids. Normally +# the list will have at most one element but +# may contain more if the tag isn't unique on +# the current branch. +# +############################################################################## + + + +sub get_revision_ids($$) +{ + + my($instance, $revision_ids) = @_; + + @$revision_ids=(); + return unless ($instance->{revision_combo_details}->{completed}); + if ($instance->{tagged_tick}->get_active()) + { + $instance->{mtn}-> + select($revision_ids, + "t:" . $instance->{revision_combo_details}->{value}); + } + else + { + push(@$revision_ids, $instance->{revision_combo_details}->{value}); + } + +} +# +############################################################################## +# +# Routine - make_busy +# +# Description - This routine simply makes the main window busy or active. +# +# Data - $instance : The window instance. +# $busy : True if the window is to be made busy, +# otherwise false if the window is to be made +# active. +# +############################################################################## + + + +sub make_busy($$) +{ + + my($instance, $busy) = @_; + + # Create and store the cursors if we haven't done so already. + + $busy_cursor = Gtk2::Gdk::Cursor->new("watch") + unless (defined($busy_cursor)); + + # Do it. Make the application bar grab the input when the window is busy, + # that way we gobble up keyboard and mouse events that could muck up the + # application state. + + if ($busy) + { + if (exists($instance->{grab_widget})) + { + Gtk2->grab_add($instance->{grab_widget}); + } + else + { + Gtk2->grab_add($instance->{appbar}); + } + foreach my $instance (@windows) + { + foreach my $window (@{$instance->{busy_windows}}) + { + $window->set_cursor($busy_cursor); + } + } + } + else + { + if (exists($instance->{grab_widget})) + { + Gtk2->grab_remove($instance->{grab_widget}); + } + else + { + Gtk2->grab_remove($instance->{appbar}); + } + foreach my $instance (@windows) + { + foreach my $window (@{$instance->{busy_windows}}) + { + $window->set_cursor(undef); + } + } + } + +} +# +############################################################################## +# +# Routine - gtk2_update +# +# Description - Process all outstanding Gtk2 toolkit events. This is used +# to update the GUI whilst the application is busy doing +# something. +# +# Data - None. +# +############################################################################## + + + +sub gtk2_update() +{ + + return if (Gtk2->main_level() == 0); + while (Gtk2->events_pending()) + { + Gtk2->main_iteration(); + } + +} +# +############################################################################## +# +# Routine - create_format_tags +# +# Description - Creates the Gtk2::TextBuffer tags that are used to pretty +# print stuff. +# +# Data - $text_view : The GTK2::TextBuffer widget that is to have +# its tags created. +# +############################################################################## + + + +sub create_format_tags($) +{ + + my ($text_buffer) = @_; + + $text_buffer->create_tag("normal", "weight" => PANGO_WEIGHT_NORMAL); + + $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"); + + $text_buffer->create_tag("green", "foreground" => "DarkGreen"); + $text_buffer->create_tag("bold-green", + "weight" => PANGO_WEIGHT_BOLD, + "foreground" => "DarkGreen"); + $text_buffer->create_tag("italics-green", + "style" => "italic", + "foreground" => "DarkGreen"); + $text_buffer->create_tag("bold-italics-green", + "weight" => PANGO_WEIGHT_BOLD, + "style" => "italic", + "foreground" => "DarkGreen"); + + $text_buffer->create_tag("red", "foreground" => "DarkRed"); + $text_buffer->create_tag("bold-red", + "weight" => PANGO_WEIGHT_BOLD, + "foreground" => "DarkRed"); + $text_buffer->create_tag("italics-red", + "style" => "italic", + "foreground" => "DarkRed"); + $text_buffer->create_tag("bold-italics-red", + "weight" => PANGO_WEIGHT_BOLD, + "style" => "italic", + "foreground" => "DarkRed"); + + $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"); + +} +# +############################################################################## +# +# Routine - set_label_value +# +# Description - Set the text for the given label and the tooltip for the +# parent widget, assumed to be an event box, to the specified +# text. +# +# Data - $widget : The label widget that has an event box as its +# parent. +# $value : The text that the label and tooltip are to be set +# to. +# +############################################################################## + + + +sub set_label_value($$) +{ + + my($widget, $value) = @_; + + $widget->set_text($value); + $tooltips->set_tip($widget->parent(), $value); + +} + +1; ============================================================ # line.png is binary ============================================================ --- mtn-browse 2f9cc86df7157a7eb26bb354b04a351da03c4054 +++ mtn-browse 900690ff1257eaa6bddc465bbc66c96cd85ed282 @@ -39,15 +39,17 @@ -# ***** REQUIRED VERSION OF PERL ***** +# ***** DIRECTIVES ***** require 5.008; +use lib "/home/aecoope/perl"; +use strict; + # ***** REQUIRED PACKAGES ***** -use lib "/home/aecoope/perl"; +# Standard Perl and CPAN modules. -use strict; use Glib qw(FALSE TRUE); use Gnome2; use Gnome2::VFS -init; @@ -69,36 +71,18 @@ use Data::Dumper; use Data::Dumper; -# ***** GLOBAL DATA DECLARATIONS ***** +# Modules specific to this application. -# Constants used to represent the different groups of widgets. +use Globals qw(:constants :variables); +use AdvancedFind; +use ChangeLog; +use ComboAutoCompletion; +use Completion; +use History; +use Utilities; -use constant BRANCH => 0x01; -use constant DIRECTORY => 0x02; -use constant DIRECTORY_VIEW => 0x04; -use constant DISPLAY_OF_FILE => 0x08; -use constant FILE => 0x10; -use constant REVISION => 0x20; -use constant REVISION_LIST => 0x02; -use constant REVISION_DETAILS => 0x04; +# ***** GLOBAL DATA DECLARATIONS ***** -# 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 - | DIRECTORY_VIEW | FILE - | DISPLAY_OF_FILE); -use constant DATABASE_CHANGED => 0xff; -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 NEW_FIND => 0xff; -use constant REVISION_CHANGED => (DIRECTORY | REVISION_LIST - | DIRECTORY_VIEW | FILE - | DISPLAY_OF_FILE); -use constant SELECTED_REVISION_CHANGED => (REVISION_DETAILS); - # Constants for the columns within the manifest ListStore widget. use constant MLS_ICON_COLUMN => 0; @@ -107,138 +91,23 @@ use constant MLS_MANIFEST_ENTRY_COLUMN = use constant MLS_AUTHOR_COLUMN => 3; use constant MLS_MANIFEST_ENTRY_COLUMN => 4; -# Constants for the columns within the details ListStore widget. - -use constant DLS_ICON_COLUMN => 0; -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 = - qw(postscript - rtf - x-awk - x-cgi - x-csh - x-glade - x-java - x-javascript - x-jbuilder-project - x-perl - x-php - x-python - x-shellscript - x-troff-man - x-troff - xhtml+xml); - -# Supported text mime types (used for syntax highlighting. - -my @text_mime_types = - ( - { - pattern => qr/.*\.c$/o, - type => "text/x-csrc" - }, - { - pattern => qr/.*\.(C)|(cc)|(cp)|(cpp)|(CPP)|(cxx)|(c\+\+)$/o, - type => "text/x-c++src" - }, - { - pattern => qr/.*\.(h)|(hh)|(H)$/o, - type => "text/x-c++hdr" - }, - { - pattern => qr/.*\.h$/o, - type => "text/x-chdr" - }, - { - pattern => qr/(^[Mm]akefile(\.[^.]+)?)|(.*\.mk)$/o, - type => "text/x-makefile" - }, - { - pattern => qr/.*/o, - type => "text/plain" - } - ); - -# A boolean to signal whether this process has received a SIGCHLD signal or -# not. - -# List of window instances. - -my @windows; - -# The advanced find window management record. - -my $advanced_find; - -# Assorted pixmaps. - -my($app_icon, - $line_image); - -# The busy cursor to use for the mouse. - -my $busy_cursor; - -# The tooltips widget. - -my $tooltips; - # ***** FUNCTIONAL PROTOTYPES FOR THIS FILE ***** # Private routines. -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_history_button_clicked_cb($$); -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_history_window(); -sub get_revision_comparison_window(); -sub get_revision_history_helper($$$); -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 revision_change_history_button_clicked_cb($$); sub revision_change_log_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(); -sub simple_query_radiobutton_toggled_cb($$); -sub term_combobox_changed_cb($$); -sub update_advanced_find_state($$); sub update_browser_state($$); # ############################################################################## @@ -273,18 +142,13 @@ sub update_browser_state($$); # Cleanup. - if (defined($advanced_find)) - { - $advanced_find->{window}->destroy(); - $advanced_find = undef; - } foreach my $window (@windows) { $window->{window}->destroy(); } + @windows = (); Gnome2::VFS->shutdown(); $SIG{CHLD} = "IGNORE"; - @windows = (); exit(0); @@ -292,212 +156,6 @@ sub update_browser_state($$); # ############################################################################## # -# Routine - combo_changed_cb -# -# Description - Callback routine called when the user changes the value of -# 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 -# this widget. -# -############################################################################## - - - -sub combo_changed_cb($$) -{ - - my($widget, $instance) = @_; - - return if ($instance->{in_cb}); - local $instance->{in_cb} = 1; - - my ($change_state, - $combo_details, - $item, - $value); - - if ($widget == $instance->{branch_combo}) - { - $change_state = BRANCH_CHANGED; - $combo_details = $instance->{branch_combo_details}; - } - elsif ($widget == $instance->{revision_combo}) - { - $change_state = REVISION_CHANGED; - $combo_details = $instance->{revision_combo_details}; - } - elsif ($widget == $instance->{directory_combo}) - { - $change_state = DIRECTORY_CHANGED; - $combo_details = $instance->{directory_combo_details}; - } - else - { - return; - } - - # 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; - $instance->{appbar}->clear_stack(); - &{$instance->{update_handler}}($instance, $change_state); - last; - } - } - -} -# -############################################################################## -# -# Routine - combo_key_release_event_cb -# -# Description - Callback routine called when the user changes the value of -# 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 -# event that has occurred. -# $instance : The window 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 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, - $completed, - $completion, - $item, - $len, - $name, - $old_completed, - $old_value, - $value); - - if ($widget == $instance->{branch_combo}->child()) - { - $combo = $instance->{branch_combo}; - $change_state = BRANCH_CHANGED; - $combo_details = $instance->{branch_combo_details}; - $name = "branch"; - } - elsif ($widget == $instance->{revision_combo}->child()) - { - $combo = $instance->{revision_combo}; - $change_state = REVISION_CHANGED; - $combo_details = $instance->{revision_combo_details}; - $name = "revision"; - } - elsif ($widget == $instance->{directory_combo}->child()) - { - $combo = $instance->{directory_combo}; - $change_state = DIRECTORY_CHANGED; - $combo_details = $instance->{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}; - $old_value = $combo_details->{value}; - $value = $widget->get_text(); - if ($value ne $old_value) - { - - # Don't auto-complete if the user is simply deleting from the extreme - # right. - - $len = length($value); - if ($value ne substr($old_value, 0, $len)) - { - - # So that the spacebar triggers auto-complete. - - $value =~ s/\s+$//o; - $len = length($value); - - if (get_completion($value, - $combo_details->{list}, - \$completion, - \$completed, - $combo_details->{completion_cache})) - { - $instance->{appbar}->clear_stack(); - } - else - { - $instance->{appbar}-> - push("Invalid " . $name . " name`" . $value . "'"); - } - $value = $completion; - $len = length($value); - $widget->set_text($value); - $widget->set_position(-1); - - } - $combo_details->{value} = $value; - $combo_details->{last_typed_len} = length($value); - $combo_details->{completed} = $completed; - - # Update the pulldown choices. - - $combo->get_model()->clear(); - foreach $item (@{$combo_details->{list}}) - { - $combo->append_text($item) if ($value eq substr($item, 0, $len)); - $combo_details->{completed} = 1 - if (! $completed && $value eq $item); - } - - # Update the window state on a significant change. - - &{$instance->{update_handler}}($instance, $change_state) - if ($combo_details->{completed} != $old_completed - || $combo_details->{value} ne $old_value); - - } - - return FALSE; - -} -# -############################################################################## -# # Routine - tagged_checkbutton_toggled_cb # # Description - Callback routine called when the user changes the value of @@ -619,165 +277,8 @@ sub revision_change_history_button_click return if ($browser->{in_cb}); local $browser->{in_cb} = 1; - my ($button, - @certs_list, - $counter, - $file_name, - %history_hash, - $instance, - @revision_ids); + display_revision_change_history($browser); - $instance = get_history_window(); - local $instance->{in_cb} = 1; - - $instance->{mtn} = $browser->{mtn}; - $instance->{file_name} = undef; - get_revision_ids($browser, address@hidden); - $instance->{first_revision_id} = ""; - $instance->{second_revision_id} = ""; - $instance->{window}->set_title("Revision History For " . $revision_ids[0]); - $instance->{history_label}->set_markup("Revision History"); - $instance->{window}->show_all(); - - make_busy($instance, 1); - $instance->{appbar}->push(""); - gtk2_update(); - - $instance->{stop_button}->set_sensitive(TRUE); - - # Get the list of file change revisions. Remember to include the current - # revision in the history. - - $instance->{appbar}->set_progress_percentage(0); - $instance->{appbar}->set_status("Fetching revision list"); - gtk2_update(); - $history_hash{$revision_ids[0]} = 1; - get_revision_history_helper($instance, \%history_hash, $revision_ids[0]); - - # Sort the list. - - $instance->{appbar}->set_status("Sorting revision list"); - gtk2_update(); - $instance->{history} = []; - $instance->{mtn}->toposort($instance->{history}, keys(%history_hash)); - %history_hash = (); - @{$instance->{history}} = reverse(@{$instance->{history}}); - - # Display the file's history. - - $instance->{appbar}->set_progress_percentage(0); - $instance->{appbar}->set_status("Displaying file history"); - gtk2_update(); - $counter = 1; - $instance->{stop} = 0; - $instance->{history_buffer}->set_text(""); - for my $revision_id (@{$instance->{history}}) - { - - # Print out the revision summary. - - $instance->{mtn}->certs(address@hidden, $revision_id); - generate_revision_report($instance->{history_buffer}, - $revision_id, - address@hidden, - ""); - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), "\n\n "); - - # Add the buttons. - - $button = Gtk2::Button->new("Select As Revision 1"); - $button->signal_connect("clicked", - \&history_list_button_clicked_cb, - {instance => $instance, - revision_id => $revision_id, - button_type => "1"}); - $tooltips->set_tip($button, - "Select this revision for comparison\n" - . "as the first revision"); - $instance->{history_textview}->add_child_at_anchor - ($button, - $instance->{history_buffer}-> - create_child_anchor($instance->{history_buffer}-> - get_end_iter())); - $button->show_all(); - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), " "); - - $button = Gtk2::Button->new("Select As Revision 2"); - $button->signal_connect("clicked", - \&history_list_button_clicked_cb, - {instance => $instance, - revision_id => $revision_id, - button_type => "2"}); - $tooltips->set_tip($button, - "Select this revision for comparison\n" - . "as the second revision"); - $instance->{history_textview}->add_child_at_anchor - ($button, - $instance->{history_buffer}-> - create_child_anchor($instance->{history_buffer}-> - get_end_iter())); - $button->show_all(); - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), " "); - - $button = Gtk2::Button->new("View Full Revision Change Log"); - $button->signal_connect("clicked", - \&history_list_button_clicked_cb, - {instance => $instance, - revision_id => $revision_id, - button_type => "revision-changelog"}); - $tooltips->set_tip($button, "View the revision's full change log"); - $instance->{history_textview}->add_child_at_anchor - ($button, - $instance->{history_buffer}-> - create_child_anchor($instance->{history_buffer}-> - get_end_iter())); - $button->show_all(); - - # Stop if the user wants to. - - last if ($instance->{stop}); - - # If we aren't at the end, print out the revision separator. - - if ($counter < scalar(@{$instance->{history}})) - { - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), "\n"); - $instance->{history_buffer}-> - insert_pixbuf($instance->{history_buffer}->get_end_iter(), - $line_image); - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), "\n"); - } - - if ($counter % 100 == 0) - { - $instance->{appbar}->set_progress_percentage - ($counter / scalar(@{$instance->{history}})); - gtk2_update(); - } - ++ $counter; - - } - - $instance->{stop_button}->set_sensitive(FALSE); - set_label_value($instance->{numbers_label}, $counter) - if ($instance->{stop}); - - # Make sure we are at the top. - - $instance->{history_scrolledwindow}->get_vadjustment()->set_value(0); - $instance->{history_scrolledwindow}->get_hadjustment()->set_value(0); - $instance->{appbar}->set_progress_percentage(0); - $instance->{appbar}->set_status(""); - gtk2_update(); - - $instance->{appbar}->pop(); - make_busy($instance, 0); - } # ############################################################################## @@ -925,157 +426,8 @@ sub file_change_history_button_clicked_c return if ($browser->{in_cb}); local $browser->{in_cb} = 1; - my ($button, - @certs_list, - $counter, - $file_name, - %history_hash, - $instance, - @revision_ids); + display_file_change_history($browser); - $instance = get_history_window(); - local $instance->{in_cb} = 1; - - $instance->{mtn} = $browser->{mtn}; - $instance->{file_name} = - $browser->{file_being_viewed}->{manifest_entry}->{name}; - $instance->{first_revision_id} = ""; - $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); - $instance->{appbar}->push(""); - gtk2_update(); - - # Get the list of file change revisions. Remember that a warning is - # generated when one goes back beyond a file's addition revision, so - # temporarily disable the warning handler. - - $instance->{appbar}->set_progress_percentage(0); - $instance->{appbar}->set_status("Fetching revision list"); - $instance->{stop_button}->set_sensitive(TRUE); - gtk2_update(); - 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); - $instance->{stop_button}->set_sensitive(FALSE); - - # Sort the list. - - $instance->{appbar}->set_status("Sorting revision list"); - gtk2_update(); - $instance->{history} = []; - $instance->{mtn}->toposort($instance->{history}, keys(%history_hash)); - %history_hash = (); - @{$instance->{history}} = reverse(@{$instance->{history}}); - - # Display the file's history. - - $instance->{appbar}->set_progress_percentage(0); - $instance->{appbar}->set_status("Displaying file history"); - gtk2_update(); - $counter = 1; - $instance->{history_buffer}->set_text(""); - for my $revision_id (@{$instance->{history}}) - { - - # Print out the revision summary. - - $instance->{mtn}->certs(address@hidden, $revision_id); - generate_revision_report($instance->{history_buffer}, - $revision_id, - address@hidden, - ""); - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), "\n\n "); - - # Add the buttons. - - $button = Gtk2::Button->new("Select As File 1"); - $button->signal_connect("clicked", - \&history_list_button_clicked_cb, - {instance => $instance, - revision_id => $revision_id, - button_type => "1"}); - $tooltips->set_tip($button, - "Select this file revision for\n" - . "comparison as the first file"); - $instance->{history_textview}->add_child_at_anchor - ($button, - $instance->{history_buffer}-> - create_child_anchor($instance->{history_buffer}-> - get_end_iter())); - $button->show_all(); - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), " "); - - $button = Gtk2::Button->new("Select As File 2"); - $button->signal_connect("clicked", - \&history_list_button_clicked_cb, - {instance => $instance, - revision_id => $revision_id, - button_type => "2"}); - $tooltips->set_tip($button, - "Select this file revision for\n" - . "comparison as the second file"); - $instance->{history_textview}->add_child_at_anchor - ($button, - $instance->{history_buffer}-> - create_child_anchor($instance->{history_buffer}-> - get_end_iter())); - $button->show_all(); - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), " "); - - $button = Gtk2::Button->new("View Full Revision Change Log"); - $button->signal_connect("clicked", - \&history_list_button_clicked_cb, - {instance => $instance, - revision_id => $revision_id, - button_type => "revision-changelog"}); - $tooltips->set_tip($button, "View the revision's full change log"); - $instance->{history_textview}->add_child_at_anchor - ($button, - $instance->{history_buffer}-> - create_child_anchor($instance->{history_buffer}-> - get_end_iter())); - $button->show_all(); - - # If we aren't at the end, print out the revision separator. - - if ($counter < scalar(@{$instance->{history}})) - { - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), "\n"); - $instance->{history_buffer}-> - insert_pixbuf($instance->{history_buffer}->get_end_iter(), - $line_image); - $instance->{history_buffer}-> - insert($instance->{history_buffer}->get_end_iter(), "\n"); - } - - $instance->{appbar}->set_progress_percentage - ($counter ++ / scalar(@{$instance->{history}})); - gtk2_update(); - - } - - # Make sure we are at the top. - - $instance->{history_scrolledwindow}->get_vadjustment()->set_value(0); - $instance->{history_scrolledwindow}->get_hadjustment()->set_value(0); - $instance->{appbar}->set_progress_percentage(0); - $instance->{appbar}->set_status(""); - gtk2_update(); - - $instance->{appbar}->pop(); - make_busy($instance, 0); - } # ############################################################################## @@ -1183,560 +535,6 @@ sub manifest_browser_treeview_row_activa # ############################################################################## # -# Routine - simple_query_radiobutton_toggled_cb -# -# Description - Callback routine called when the user changes the advanced -# find mode radio button. -# -# Data - $widget : The widget object that received the -# signal. -# $advanced_find : The advanced find dialog window instance -# that is associated with this widget. -# -############################################################################## - - - -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); - - # Simply enable the relevant find widgets depending upon whether simple or - # advanced mode is selected. - - if ($advanced_find->{simple_query_radiobutton}->get_active()) - { - $advanced_find->{simple_frame}->set_sensitive(TRUE); - $advanced_find->{advanced_frame}->set_sensitive(FALSE); - } - else - { - $advanced_find->{simple_frame}->set_sensitive(FALSE); - $advanced_find->{advanced_frame}->set_sensitive(TRUE); - } - -} -# -############################################################################## -# -# Routine - execute_button_clicked_cb -# -# 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. -# $advanced_find : The advanced find dialog window instance -# that is associated with this widget. -# -############################################################################## - - - -sub execute_button_clicked_cb($$) -{ - - my($widget, $advanced_find) = @_; - - return if ($advanced_find->{in_cb}); - local $advanced_find->{in_cb} = 1; - - # Simply let the update handler deal with it. - - &{$advanced_find->{update_handler}}($advanced_find, REVISION_CHANGED); - -} -# -############################################################################## -# -# Routine - populate_button_clicked_cb -# -# Description - Callback routine called when the user clicks on the -# populate selector button in the advanved find window. -# -# Data - $widget : The widget object that received the -# signal. -# $advanced_find : The advanced find dialog window instance -# that is associated with this widget. -# -############################################################################## - - - -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); - - # Simply get the currently selected selector and then insert it into the - # user's query string. - - $selector = $advanced_find->{term_combo}->get_model()->get - ($advanced_find->{term_combo}->get_active_iter(), 0); - $arg = $advanced_find->{argument_entry}->get_text(); - $time_val = strftime("%Y-%m-%dT%H:%M:%S", - localtime($advanced_find->{date_dateedit}-> - get_time())); - $to_insert = ""; - if ($selector eq "Author") - { - $to_insert = "a:" . (($arg eq "") ? "" : $arg); - } - elsif ($selector eq "Branch") - { - $to_insert = "b:" . (($arg eq "") ? "" : $arg); - } - elsif ($selector eq "Cert") - { - $to_insert = "c:" . (($arg eq "") ? "" : $arg); - } - elsif ($selector eq "Date (=)") - { - $to_insert = "d:" . $time_val; - } - elsif ($selector eq "Date (<=)") - { - $to_insert = "e:" . $time_val; - } - elsif ($selector eq "Date (>)") - { - $to_insert = "l:" . $time_val; - } - elsif ($selector eq "Head Revision") - { - $to_insert = "h:"; - } - elsif ($selector eq "Identifier") - { - $to_insert = "i:" . (($arg eq "") ? "" : $arg); - } - elsif ($selector eq "Parent") - { - $to_insert = "p:" . (($arg eq "") ? "" : $arg); - } - elsif ($selector eq "Separator") - { - $to_insert = "/"; - } - elsif ($selector eq "Tag") - { - $to_insert = "t:" . (($arg eq "") ? "" : $arg); - } - - $pos = $advanced_find->{search_term_combo}->child()->get_position(); - $advanced_find->{search_term_combo}->child()->insert_text - ($to_insert, $pos); - $advanced_find->{search_term_combo}->child()->set_position - ($pos + length($to_insert)); - -} -# -############################################################################## -# -# 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 in the advanced find window. -# -# Data - $widget : The widget object that received the signal. -# $instance : The window instance that is associated with -# this widget. -# -############################################################################## - - - -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); - - # Simply get the currently selected term and then enable/disable the text - # entry and date entry widgets accordingly. - - $selector = $advanced_find->{term_combo}->get_model()->get - ($advanced_find->{term_combo}->get_active_iter(), 0); - - if ($selector =~ m/^Date .*$/o) - { - $advanced_find->{argument_entry}->set_sensitive(FALSE); - $advanced_find->{date_dateedit}->set_sensitive(TRUE); - } - elsif ($selector eq "Head" || $selector eq "Separator") - { - $advanced_find->{argument_entry}->set_sensitive(FALSE); - $advanced_find->{date_dateedit}->set_sensitive(FALSE); - } - else - { - $advanced_find->{argument_entry}->set_sensitive(TRUE); - $advanced_find->{date_dateedit}->set_sensitive(FALSE); - } - -} -# -############################################################################## -# -# Routine - revisions_treeview_cursor_changed_cb -# -# Description - Callback routine called when the user selects an entry in -# the revisions treeview in the advanced find window. -# -# Data - $widget : The widget object that received the -# signal. -# $advanced_find : The advanced find dialog window instance -# that is associated with this widget. -# -############################################################################## - - - -sub revisions_treeview_cursor_changed_cb($$) -{ - - my($widget, $advanced_find) = @_; - - 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 - (sub { - my($model, $path, $iter) = @_; - $revision_id = $model->get($iter, 0); }); - - if (defined($revision_id) - && $revision_id - ne $advanced_find->{revisions_treeview_details}->{value}) - { - $advanced_find->{revisions_treeview_details}->{value} = $revision_id; - $advanced_find->{appbar}->clear_stack(); - &{$advanced_find->{update_handler}}($advanced_find, - SELECTED_REVISION_CHANGED); - } - -} -# -############################################################################## -# -# Routine - revisions_treeview_row_activated_cb -# -# Description - Callback routine called when the user double clicks on an -# entry in the revisions treeview in the advanced find -# window. -# -# Data - $widget : The widget object that received the -# signal. -# $tree_path : A Gtk2::TreePath object for the -# selected item. -# $tree_view_column : A Gtk2::TreeViewColumn object for the -# selected item. -# $advanced_find : The advanced find dialog window -# instance that is associated with this -# widget. -# -############################################################################## - - - -sub revisions_treeview_row_activated_cb($$$$) -{ - - my($widget, $tree_path, $tree_view_column, $advanced_find) = @_; - - 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 - (sub { - my($model, $path, $iter) = @_; - $revision_id = $model->get($iter, 0); }); - - if (defined($revision_id)) - { - $advanced_find->{revisions_treeview_details}->{value} = $revision_id; - $advanced_find->{appbar}->clear_stack(); - $advanced_find->{selected} = 1; - $advanced_find->{done} = 1; - } - -} -# -############################################################################## -# -# 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 "1" || $details->{button_type} eq "2") - { - if ($details->{button_type} eq "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, - $colour, - @revision_details, - $revision_id); - - # Work out what revision id to use. - - if ($widget == $instance->{red_revision_button}) - { - $revision_id = $instance->{red_revision_id}; - $colour = "red"; - } - else - { - $revision_id = $instance->{green_revision_id}; - $colour = "green"; - } - - # 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, - $colour, - 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 @@ -2042,1249 +840,6 @@ sub new_browser_instance() # ############################################################################## # -# Routine - advanced_find -# -# Description - Displays the advanced find dialog window and then gets the -# user to select the revision they want. -# -# Data - $browser : The browser instance that started the -# advanced find. -# $revision_id : A reference to a variable that is to contain -# the selected revision id. -# $branches : A reference to a list that is to contain the -# list of branches that the selected revision -# is on. -# Return Value : True if a revision has been selected, -# otherwise false. -# -############################################################################## - - - -sub advanced_find($$$) -{ - - my($browser, $revision_id, $branches) = @_; - - my($height, - $width); - - $advanced_find = create_advanced_find_window() - unless (defined($advanced_find)); - - $advanced_find->{mtn} = $browser->{mtn}; - - # Update the window's internal state. - - { - local $advanced_find->{in_cb} = 1; - $advanced_find->{selected} = 0; - - # Reset the window contents, then show it. - - $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(); - $advanced_find->{window}->resize($width, $height); - $advanced_find->{window}->show_all(); - - # Now actually update it with any preset values. - - $advanced_find->{branch_combo_details}->{preset} = 1; - $advanced_find->{branch_combo_details}->{completed} = - $browser->{branch_combo_details}->{completed}; - $advanced_find->{branch_combo_details}->{value} = - $browser->{branch_combo_details}->{value}; - - $advanced_find->{revision_combo_details}->{preset} = 1; - $advanced_find->{revision_combo_details}->{completed} = - $browser->{revision_combo_details}->{completed}; - $advanced_find->{revision_combo_details}->{value} = - $browser->{revision_combo_details}->{value}; - - $advanced_find->{tagged_tick}-> - set_active($browser->{tagged_tick}->get_active()); - - &{$advanced_find->{update_handler}}($advanced_find, NEW_FIND); - } - - # Handle all events until the dialog is dismissed. - - $advanced_find->{done} = 0; - while (! $advanced_find->{done}) - { - Gtk2->main_iteration(); - } - $advanced_find->{window}->hide(); - - # Deal with the result. - - @$branches = (); - $$revision_id = ""; - if ($advanced_find->{selected}) - { - my($branch_list, - @certs_list, - $found); - - $$revision_id = $advanced_find->{revisions_treeview_details}->{value}; - - # Build up a list of branches that the selected revision is on, putting - # the branch named in the branch combo box at the head if it is still - # applicable. - - $advanced_find->{mtn}->certs(address@hidden, $$revision_id); - $found = 0; - foreach my $cert (@certs_list) - { - if ($cert->{name} eq "branch") - { - if ($cert->{value} - ne $advanced_find->{branch_combo_details}->{value}) - { - push(@$branches, $cert->{value}); - } - else - { - $found = 1; - } - } - } - unshift(@$branches, $advanced_find->{branch_combo_details}->{value}) - if ($found); - push(@$branches, "") if (scalar(@$branches) == 0); - - return 1; - } - else - { - return; - } - -} -# -############################################################################## -# -# Routine - create_advanced_find_window -# -# Description - Creates the advanced find dialog window. -# -# Data - Return Value : A reference to the newly created advanced -# find instance record. -# -############################################################################## - - - -sub create_advanced_find_window() -{ - - my(@branch_list, - $font, - $instance, - $renderer, - $tv_column); - - $instance = {}; - $instance->{glade} = - Gtk2::GladeXML->new("../mtn-browse.glade", "advanced_find_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); - - # Link in the update handler for the advanced find window. - - $instance->{update_handler} = \&update_advanced_find_state; - - # Get the widgets that we are interested in. - - $instance->{window} = - $instance->{glade}->get_widget("advanced_find_window"); - $instance->{window}->set_icon($app_icon); - $instance->{appbar} = $instance->{glade}->get_widget("appbar"); - $instance->{simple_query_radiobutton} = - $instance->{glade}->get_widget("simple_query_radiobutton"); - $instance->{simple_frame} = $instance->{glade}->get_widget("simple_frame"); - $instance->{advanced_frame} = - $instance->{glade}->get_widget("advanced_frame"); - $instance->{branch_combo} = - $instance->{glade}->get_widget("branch_comboboxentry"); - $instance->{revision_combo} = - $instance->{glade}->get_widget("revision_comboboxentry"); - $instance->{tagged_tick} = - $instance->{glade}->get_widget("tagged_checkbutton"); - $instance->{search_term_combo} = - $instance->{glade}->get_widget("search_term_comboboxentry"); - $instance->{term_combo} = $instance->{glade}->get_widget("term_combobox"); - $instance->{argument_entry} = - $instance->{glade}->get_widget("argument_entry"); - $instance->{date_dateedit} = - $instance->{glade}->get_widget("date_dateedit"); - $instance->{revisions_treeview} = - $instance->{glade}->get_widget("revisions_treeview"); - $instance->{details_textview} = - $instance->{glade}->get_widget("details_textview"); - $instance->{details_scrolledwindow} = - $instance->{glade}->get_widget("details_scrolledwindow"); - $instance->{selected_branch_label} = - $instance->{glade}->get_widget("selected_branch_value_label"); - $instance->{selected_revision_label} = - $instance->{glade}->get_widget("selected_revision_value_label"); - $instance->{ok_button} = $instance->{glade}->get_widget("ok_button"); - - # Setup the advanced find window deletion handlers. - - $instance->{window}->signal_connect - ("delete_event", - sub { $_[2]->{done} = 1 unless ($_[2]->{in_cb}); return TRUE; }, - $instance); - $instance->{glade}->get_widget("cancel_button")->signal_connect - ("clicked", sub { $_[1]->{done} = 1; }, $instance); - $instance->{glade}->get_widget("ok_button")->signal_connect - ("clicked", sub { $_[1]->{done} = $_[1]->{selected} = 1; }, $instance); - - # Setup the comboboxentry key release signal handlers. - - $instance->{branch_combo}->child()-> - signal_connect("key_release_event", - \&combo_key_release_event_cb, - $instance); - $instance->{revision_combo}->child()-> - signal_connect("key_release_event", - \&combo_key_release_event_cb, - $instance); - - # Setup the comboboxes. - - $instance->{branch_combo}->set_model(Gtk2::ListStore->new("Glib::String")); - $instance->{branch_combo}->set_text_column(0); - $instance->{branch_combo}->set_wrap_width(2); - $instance->{revision_combo}-> - set_model(Gtk2::ListStore->new("Glib::String")); - $instance->{revision_combo}->set_text_column(0); - $instance->{revision_combo}->set_wrap_width(2); - $instance->{search_term_combo}-> - set_model(Gtk2::ListStore->new("Glib::String")); - $instance->{search_term_combo}->set_text_column(0); - $instance->{query_history} = []; - $instance->{term_combo}->set_active(0); - - # Setup the revisions list browser. - - $instance->{revisions_liststore} = Gtk2::ListStore->new("Glib::String"); - $instance->{revisions_treeview}-> - set_model($instance->{revisions_liststore}); - $tv_column = Gtk2::TreeViewColumn->new(); - $tv_column->set_title("Matching Revision Ids"); - $tv_column->set_sort_column_id(0); - $renderer = Gtk2::CellRendererText->new(); - $tv_column->pack_start($renderer, FALSE); - $tv_column->set_attributes($renderer, "text" => 0); - $instance->{revisions_treeview}->append_column($tv_column); - $instance->{revisions_treeview}->set_search_column(0); - - # Setup the revision details viewer. - - $instance->{details_buffer} = $instance->{details_textview}->get_buffer(); - create_format_tags($instance->{details_textview}->get_buffer()); - $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; - -} -# -############################################################################## -# -# Routine - get_change_log_window -# -# Description - Creates or prepares an existing a change log window for -# use. -# -# Data - Return Value : A reference to the newly created or unused -# change log instance record. -# -############################################################################## - - - -sub get_change_log_window() -{ - - my ($font, - $height, - $instance, - $width); - - foreach my $window (@windows) - { - if ($window->{type} eq "change_log_window" - && ! $window->{window}->mapped()) - { - $instance = $window; - last; - } - } - - # Create a new change log window if an unused one wasn't found, otherwise - # reuse an existing unused one. - - if (! defined($instance)) - { - $instance = {}; - $instance->{type} = "change_log_window"; - $instance->{glade} = - Gtk2::GladeXML->new("../mtn-browse.glade", "changelog_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("changelog_window"); - $instance->{window}->set_icon($app_icon); - $instance->{changelog_textview} = - $instance->{glade}->get_widget("changelog_textview"); - $instance->{changelog_scrolledwindow} = - $instance->{glade}->get_widget("changelog_scrolledwindow"); - - # Setup the changelog window deletion handler. - - $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->{changelog_buffer}->set_text(""); - return TRUE; - }, - $instance); - - # Setup the revision changelog viewer. - - $instance->{changelog_buffer} = - $instance->{changelog_textview}->get_buffer(); - create_format_tags($instance->{changelog_buffer}); - $font = Gtk2::Pango::FontDescription->from_string("monospace 10"); - $instance->{changelog_textview}->modify_font($font) - if (defined($font)); - - $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); - } - - # Empty out the contents. - - $instance->{changelog_buffer}->set_text(""); - - return $instance; - -} -# -############################################################################## -# -# Routine - get_history_window -# -# 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. -# -############################################################################## - - - -sub get_history_window() -{ - - my ($font, - $height, - $instance, - $width); - - # Look for an unused window first. - - foreach my $window (@windows) - { - if ($window->{type} eq "history_window" - && ! $window->{window}->mapped()) - { - $instance = $window; - last; - } - } - - # Create a new file history window if an unused one wasn't found, otherwise - # reuse an existing unused one. - - if (! defined($instance)) - { - $instance = {}; - $instance->{type} = "history_window"; - $instance->{glade} = - Gtk2::GladeXML->new("../mtn-browse.glade", "history_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("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} = - $instance->{glade}->get_widget("history_scrolledwindow"); - $instance->{stop_button} = - $instance->{glade}->get_widget("stop_button"); - $instance->{compare_button} = - $instance->{glade}->get_widget("compare_button"); - $instance->{numbers_label} = - $instance->{glade}->get_widget("numbers_value_label"); - $instance->{revision_id_1_label} = - $instance->{glade}->get_widget("revision_id_1_value_label"); - $instance->{revision_id_2_label} = - $instance->{glade}->get_widget("revision_id_2_value_label"); - - # 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->{history_buffer}->set_text(""); - return TRUE; - }, - $instance); - $instance->{stop_button}->signal_connect - ("clicked", sub { $_[1]->{stop} = 1; }, $instance); - - # Setup the file history viewer. - - $instance->{history_buffer} = - $instance->{history_textview}->get_buffer(); - create_format_tags($instance->{history_buffer}); - $font = Gtk2::Pango::FontDescription->from_string("monospace 10"); - $instance->{history_textview}->modify_font($font) if (defined($font)); - - # Make the stop button the grab widget when busy, this is so the user - # can interrupt the history gathering process. - - $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); - $instance->{compare_button}->set_sensitive(FALSE); - set_label_value($instance->{numbers_label}, ""); - set_label_value($instance->{revision_id_1_label}, ""); - set_label_value($instance->{revision_id_2_label}, ""); - } - - $instance->{stop} = 0; - - # Empty out the contents. - - $instance->{history_buffer}->set_text(""); - - return $instance; - -} -# -############################################################################## -# -# Routine - get_file_history_helper -# -# Description - Recursive routine for getting the revisions in a file's -# change history. -# -# Data - $instance : The file history window instance. -# $hash : A reference to a hash that is to contain the -# list of revision ids. -# $revision_id : The revision id from where the search is to -# commence. -# -############################################################################## - - - -sub get_file_history_helper($$$) -{ - - my($instance, $hash, $revision_id) = @_; - - return if ($instance->{stop}); - - my(@change_parents, - @parents); - - $instance->{mtn}->get_content_changed(address@hidden, - $revision_id, - $instance->{file_name}); - foreach my $revision (@change_parents) - { - if (! exists($hash->{$revision})) - { - $hash->{$revision} = 1; - set_label_value($instance->{numbers_label}, scalar(keys(%$hash))); - gtk2_update(); - @parents = (); - $instance->{mtn}->parents(address@hidden, $revision); - foreach my $parent (@parents) - { - get_file_history_helper($instance, $hash, $parent); - } - } - } - -} -# -############################################################################## -# -# Routine - get_revision_history_helper -# -# Description - Recursive routine for getting the revisions in a revision's -# change history. -# -# Data - $instance : The revision history window instance. -# $hash : A reference to a hash that is to contain the -# list of revision ids. -# $revision_id : The revision id from where the search is to -# commence. -# -############################################################################## - - - -sub get_revision_history_helper($$$) -{ - - my($instance, $hash, $revision_id) = @_; - - return if ($instance->{stop}); - - my @parents; - - $instance->{mtn}->parents(address@hidden, $revision_id); - foreach my $parent (@parents) - { - if (! exists($hash->{$parent})) - { - $hash->{$parent} = 1; - set_label_value($instance->{numbers_label}, scalar(keys(%$hash))); - gtk2_update(); - get_revision_history_helper($instance, $hash, $parent); - } - } - -} -# -############################################################################## -# -# 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(). -# $colour : One of "red, "green" or "" depending -# upon the desired colour of the text. -# $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, $colour, $revision_details) - = @_; - - my($bold, - $change_log, - $italics, - $manifest_id, - $normal, - @parent_revision_ids, - %revision_data, - %seen, - @unique); - my @types = - ("Added", "Removed", "Changed", "Renamed", "Attributes"); - - # Sort out colour attributes. - - if ($colour ne "") - { - $normal = $colour; - $bold = "bold-" . $colour; - $italics = "italics-" . $colour; - } - else - { - $normal = "normal"; - $bold = "bold"; - $italics = "italics"; - } - - # Revision id. - - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - "Revision id: ", - $bold); - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - $revision_id . "\n\n", - $normal); - - # 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_with_tags_by_name - ($text_buffer->get_end_iter(), - sprintf("%s\n", $cert->{value}), - $normal); - } - } - - # Change log. - - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - "\nChange Log:\n", - $bold); - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - sprintf("%s", $change_log), - $normal); - - # 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_with_tags_by_name - ($text_buffer->get_end_iter(), - "\t" . $line . "\n", - $normal); - } - } - } - - # 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_with_tags_by_name - ($text_buffer->get_end_iter(), - join(" ", @parent_revision_ids) . "\n", - $normal); - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - "Manifest id:\t\t", - $bold); - $text_buffer->insert_with_tags_by_name($text_buffer->get_end_iter(), - $manifest_id, - $normal); - - } - -} -# -############################################################################## -# # Routine - update_browser_state # # Description - Update the display of the specified browser instance @@ -3316,7 +871,7 @@ sub update_browser_state($$) # Reset the branch selection. - $browser->{branch_combo_details}->{completion_cache} = {}; + $browser->{branch_combo_details}->{completion} = undef; if (! $browser->{branch_combo_details}->{preset}) { $browser->{branch_combo_details}->{completed} = 0; @@ -3362,7 +917,7 @@ sub update_browser_state($$) # Reset the revision selection. - $browser->{revision_combo_details}->{completion_cache} = {}; + $browser->{revision_combo_details}->{completion} = undef; if (! $browser->{revision_combo_details}->{preset}) { $browser->{revision_combo_details}->{completed} = 0; @@ -3447,7 +1002,7 @@ sub update_browser_state($$) # Reset the directory combo. - $browser->{directory_combo_details}->{completion_cache} = {}; + $browser->{directory_combo_details}->{completion} = undef; $browser->{directory_combo_details}->{completed} = 0; $browser->{directory_combo_details}->{last_typed_len} = 0; $browser->{directory_combo_details}->{value} = ""; @@ -3881,744 +1436,6 @@ sub update_browser_state($$) # ############################################################################## # -# Routine - update_advanced_find_state -# -# Description - Update the display of the specified advanced find dialog -# window instance according to the specified state. -# -# Data - $advanced_find : The advanced find dialog window instance -# that is to have its state updated. -# $changed : What the user has changed. -# -############################################################################## - - - -sub update_advanced_find_state($$) -{ - - my($advanced_find, $changed) = @_; - - my $made_busy = 0; - - if ($advanced_find->{window}->realized()) - { - $made_busy = 1; - make_busy($advanced_find, 1); - } - $advanced_find->{appbar}->push(""); - gtk2_update(); - - # The list of available branches has changed. - - if ($changed & BRANCH) - { - - my @branch_list; - - # Reset the query mode. - - $advanced_find->{simple_query_radiobutton}->set_active(TRUE); - $advanced_find->{simple_frame}->set_sensitive(TRUE); - $advanced_find->{advanced_frame}->set_sensitive(FALSE); - - # Reset the branch selection. - - $advanced_find->{branch_combo_details}->{completion_cache} = {}; - if (! $advanced_find->{branch_combo_details}->{preset}) - { - $advanced_find->{branch_combo_details}->{completed} = 0; - $advanced_find->{branch_combo_details}->{value} = ""; - } - $advanced_find->{branch_combo_details}->{preset} = 0; - - # Get the new list of branches. - - $advanced_find->{appbar}->set_status("Fetching branch list"); - gtk2_update(); - $advanced_find->{mtn}->branches(address@hidden) - if (defined($advanced_find->{mtn})); - $advanced_find->{branch_combo_details}->{list} = address@hidden; - - # Update the branch list combobox. - - $advanced_find->{appbar}->set_status("Populating branch list"); - gtk2_update(); - my $counter = 1; - $advanced_find->{branch_combo}->get_model()->clear(); - foreach my $branch (@branch_list) - { - $advanced_find->{branch_combo}->append_text($branch); - $advanced_find->{appbar}->set_progress_percentage - ($counter ++ / scalar(@branch_list)); - gtk2_update(); - } - $advanced_find->{branch_combo}->child()-> - set_text($advanced_find->{branch_combo_details}->{value}); - $advanced_find->{appbar}->set_progress_percentage(0); - $advanced_find->{appbar}->set_status(""); - gtk2_update(); - - } - - # The list of available revisions has changed. - - if ($changed & REVISION) - { - - my @revision_list; - - # Reset the revision selection. - - $advanced_find->{revision_combo_details}->{completion_cache} = {}; - if (! $advanced_find->{revision_combo_details}->{preset}) - { - $advanced_find->{revision_combo_details}->{completed} = 0; - $advanced_find->{revision_combo_details}->{value} = ""; - } - $advanced_find->{revision_combo_details}->{preset} = 0; - - # Get the new list of revisions. - - if ($advanced_find->{branch_combo_details}->{completed}) - { - $advanced_find->{appbar}->set_status("Fetching revision list"); - gtk2_update(); - - # Get either a list of tags or revision ids depending upon what the - # user has chosen. - - if ($advanced_find->{tagged_tick}->get_active()) - { - my(%dup_list, - @list); - $advanced_find->{mtn}-> - tags(address@hidden, - $advanced_find->{branch_combo_details}->{value}); - $advanced_find->{appbar}->set_progress_percentage(0.5); - gtk2_update(); - foreach my $item (@list) - { - if (! exists($dup_list{$item->{tag}})) - { - push(@revision_list, $item->{tag}); - $dup_list{$item->{tag}} = 1; - } - } - } - else - { - $advanced_find->{mtn}-> - select(address@hidden, - "b:" . $advanced_find->{branch_combo_details}-> - {value}); - $advanced_find->{appbar}->set_progress_percentage(0.33); - gtk2_update(); - $advanced_find->{mtn}->toposort(address@hidden, - @revision_list); - $advanced_find->{appbar}->set_progress_percentage(0.66); - gtk2_update(); - splice(@revision_list, 0, scalar(@revision_list) - 100); - @revision_list = reverse(@revision_list); - } - $advanced_find->{appbar}->set_progress_percentage(1); - gtk2_update(); - } - $advanced_find->{revision_combo_details}->{list} = address@hidden; - - # Update the revision list combobox. - - $advanced_find->{appbar}->set_progress_percentage(0); - $advanced_find->{appbar}->set_status("Populating revision list"); - gtk2_update(); - my $counter = 1; - $advanced_find->{revision_combo}->get_model()->clear(); - foreach my $revision (@revision_list) - { - $advanced_find->{revision_combo}->append_text($revision); - $advanced_find->{appbar}->set_progress_percentage - ($counter ++ / scalar(@revision_list)); - gtk2_update(); - } - $advanced_find->{revision_combo}->child()-> - set_text($advanced_find->{revision_combo_details}->{value}); - $advanced_find->{appbar}->set_progress_percentage(0); - $advanced_find->{appbar}->set_status(""); - gtk2_update(); - - } - - # The list of displayed revisions has changed. - - if ($changed & REVISION_LIST) - { - - my($counter, - @revision_ids); - - # Reset the revisions tree view. - - $advanced_find->{revisions_liststore}->clear(); - $advanced_find->{revisions_treeview_details}->{value} = ""; - - # Get the list of matching revisions. - - $advanced_find->{appbar}->set_status("Finding revisions"); - gtk2_update(); - if ($advanced_find->{simple_query_radiobutton}->get_active()) - { - if ($advanced_find->{revision_combo_details}->{completed}) - { - get_revision_ids($advanced_find, address@hidden); - } - } - else - { - my $query; - $query = $advanced_find->{search_term_combo}->child()->get_text(); - - # Remember the user can type in any old rubbish with advanced - # queries! So protect ourselves. - - Monotone::AutomateStdio->register_error_handler - ("both", - sub { - my($severity, $message) = @_; - my $dialog; - $dialog = Gtk2::MessageDialog->new_with_markup - ($advanced_find->{window}, - ["modal"], - "warning", - "close", - sprintf("Problem with your query, Monotone " - . "gave:\n%s", - Glib::Markup::escape_text($message))); - $dialog->run(); - $dialog->destroy(); - die("Bad query"); }); - eval - { - $advanced_find->{mtn}->select(address@hidden, $query); - }; - - # If the query was valid the store it in the history. - - if (! $@) - { - my $found; - if (scalar(@revision_ids) == 0) - { - my $dialog; - $dialog = Gtk2::MessageDialog->new - ($advanced_find->{window}, - ["modal"], - "info", - "close", - "No revisions matched your query"); - $dialog->run(); - $dialog->destroy(); - } - $found = 0; - foreach my $entry (@{$advanced_find->{query_history}}) - { - if ($entry eq $query) - { - $found = 1; - last; - } - } - if (! $found) - { - if (unshift(@{$advanced_find->{query_history}}, $query) - > 20) - { - pop(@{$advanced_find->{query_history}}); - } - $advanced_find->{search_term_combo}->get_model()->clear(); - foreach my $entry (@{$advanced_find->{query_history}}) - { - $advanced_find->{search_term_combo}-> - append_text($entry); - } - } - } - Monotone::AutomateStdio->register_error_handler - ("both", \&mtn_error_handler); - - } - $advanced_find->{mtn}->toposort(address@hidden, @revision_ids); - @revision_ids = reverse(@revision_ids); - - # Update the revisions tree view. - - $advanced_find->{appbar}->set_status("Populating revision details"); - $counter = 1; - foreach my $item (@revision_ids) - { - $advanced_find->{revisions_liststore}-> - set($advanced_find->{revisions_liststore}->append(), - 0, $item); - $advanced_find->{appbar}->set_progress_percentage - ($counter ++ / scalar(@revision_ids)); - gtk2_update(); - } - $advanced_find->{revisions_treeview}->scroll_to_point(0, 0) - if ($advanced_find->{revisions_treeview}->realized()); - - $advanced_find->{appbar}->set_progress_percentage(0); - $advanced_find->{appbar}->set_status(""); - gtk2_update(); - - } - - # The selected revision has changed. - - if ($changed & REVISION_DETAILS) - { - - if ($advanced_find->{revisions_treeview_details}->{value} ne "") - { - if ($advanced_find->{selected_revision_label}->get_text() - ne $advanced_find->{revisions_treeview_details}->{value}) - { - my ($branch, - @certs_list, - @revision_details); - - $advanced_find->{details_buffer}->set_text(""); - $advanced_find->{mtn}->certs - (address@hidden, - $advanced_find->{revisions_treeview_details}->{value}); - $advanced_find->{mtn}->get_revision - (address@hidden, - $advanced_find->{revisions_treeview_details}->{value}); - generate_revision_report - ($advanced_find->{details_buffer}, - $advanced_find->{revisions_treeview_details}->{value}, - address@hidden, - "", - address@hidden); - - # Scroll back up to the top left. - - if ($advanced_find->{details_scrolledwindow}->realized()) - { - $advanced_find->{details_scrolledwindow}-> - get_vadjustment()->set_value(0); - $advanced_find->{details_scrolledwindow}-> - get_hadjustment()->set_value(0); - } - - # Update the selected branch and revision labels. - - $branch = ""; - foreach my $cert (@certs_list) - { - if ($cert->{name} eq "branch") - { - $branch = $cert->{value}; - last; - } - } - set_label_value($advanced_find->{selected_branch_label}, - $branch); - set_label_value($advanced_find->{selected_revision_label}, - $advanced_find->{revisions_treeview_details}-> - {value}); - - $advanced_find->{ok_button}->set_sensitive(TRUE); - } - } - else - { - $advanced_find->{ok_button}->set_sensitive(FALSE); - $advanced_find->{details_buffer}->set_text(""); - set_label_value($advanced_find->{selected_branch_label}, ""); - set_label_value($advanced_find->{selected_revision_label}, ""); - } - - } - - $advanced_find->{appbar}->pop(); - make_busy($advanced_find, 0) if ($made_busy); - -} -# -############################################################################## -# -# Routine - get_completion -# -# Description - Given a value and a list, work out the largest unique -# match. Used for auto completion. -# -# Data - $value : The value to be completed. -# $list : A reference to a list containing all -# possible completions. -# $result : A reference to a buffer that is to contain -# the result. -# $complete : A reference to a boolean that is to contain -# a `result is complete' indicator. -# $cache : An optional reference to a hash that will be -# used to cache the hash tree (saved -# recomputation). -# Return Value : True if $value was expanded, otherwise false -# if $value had to be truncated due to no -# match (the maximum valid completion is still -# returned in $result). -# -############################################################################## - - - -sub get_completion($$$$;$) -{ - - my($value, $list, $result, $complete, $cache) = @_; - - my($char, - $item, - $level, - %local_cache, - $tree); - - # Work out what cache we are to use. - - $tree = (defined($cache)) ? $cache : \%local_cache; - - # Unless we are given an already built hash tree, build one up for the list - # of possible items. - - if (scalar(keys(%$tree)) == 0) - { - foreach $item (@$list) - { - - # Build up nodes for an item. - - $level = $tree; - foreach $char (split(//o, $item)) - { - if (! exists($level->{$char})) - { - $level->{$char} = {}; - } - $level = $level->{$char}; - } - - # By adding this dummy node here it stops the auto-complete moving - # too far should another item extend beyond this point. I.e. auto - # completion stops at `net.venge.monotone.contrib' and not - # `net.venge.monotone.contrib.'. You could simply think of this - # node as an `end of string' token if you prefer. - - $level->{""} = ""; - - } - } - - # Lookup value, stopping when it becomes ambiguous or we get to the end of - # $value. - - $level = $tree; - $$result = ""; - foreach $char (split(//o, $value)) - { - last unless (exists($level->{$char})); - $level = $level->{$char}; - $$result .= $char; - } - - # Detect truncations. - - return if (length($value) > length($$result)); - - # Now try and expand it further. - - while (defined(%$level) && keys(%$level) == 1) - { - ($char) = keys(%$level); - $$result .= $char; - $level = $level->{$char}; - } - - # Detect complete completions (doesn't mean to say that it can't be - # extended, just that as it stands at the moment $$result does contain a - # valid unique value). - - if (! defined(%$level) || exists($level->{""})) - { - $$complete = 1; - } - else - { - $$complete = 0; - } - - return 1; - -} -# -############################################################################## -# -# 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 -# 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($$$) -{ - - 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}; - } - } - -} -# -############################################################################## -# # Routine - mtn_error_handler # # Description - This routine is called when ever there is a problem with @@ -4768,233 +1585,3 @@ sub setup_sigchld_handler($) return 1; }); } -# -############################################################################## -# -# Routine - get_revision_ids -# -# Description - Return the currently selected revision id, whether this is -# specified via a tag or as a revision id. -# -# Data - $instance : The window instance. -# $revision_ids : The list of selected revision ids. Normally -# the list will have at most one element but -# may contain more if the tag isn't unique on -# the current branch. -# -############################################################################## - - - -sub get_revision_ids($$) -{ - - my($instance, $revision_ids) = @_; - - @$revision_ids=(); - return unless ($instance->{revision_combo_details}->{completed}); - if ($instance->{tagged_tick}->get_active()) - { - $instance->{mtn}-> - select($revision_ids, - "t:" . $instance->{revision_combo_details}->{value}); - } - else - { - push(@$revision_ids, $instance->{revision_combo_details}->{value}); - } - -} -# -############################################################################## -# -# Routine - make_busy -# -# Description - This routine simply makes the main window busy or active. -# -# Data - $instance : The window instance. -# $busy : True if the window is to be made busy, -# otherwise false if the window is to be made -# active. -# -############################################################################## - - - -sub make_busy($$) -{ - - my($instance, $busy) = @_; - - # Create and store the cursors if we haven't done so already. - - $busy_cursor = Gtk2::Gdk::Cursor->new("watch") - unless (defined($busy_cursor)); - - # Do it. Make the application bar grab the input when the window is busy, - # that way we gobble up keyboard and mouse events that could muck up the - # application state. - - if ($busy) - { - if (exists($instance->{grab_widget})) - { - Gtk2->grab_add($instance->{grab_widget}); - } - else - { - Gtk2->grab_add($instance->{appbar}); - } - foreach my $instance (@windows) - { - foreach my $window (@{$instance->{busy_windows}}) - { - $window->set_cursor($busy_cursor); - } - } - } - else - { - if (exists($instance->{grab_widget})) - { - Gtk2->grab_remove($instance->{grab_widget}); - } - else - { - Gtk2->grab_remove($instance->{appbar}); - } - foreach my $instance (@windows) - { - foreach my $window (@{$instance->{busy_windows}}) - { - $window->set_cursor(undef); - } - } - } - -} -# -############################################################################## -# -# Routine - gtk2_update -# -# Description - Process all outstanding Gtk2 toolkit events. This is used -# to update the GUI whilst the application is busy doing -# something. -# -# Data - None. -# -############################################################################## - - - -sub gtk2_update() -{ - - return if (Gtk2->main_level() == 0); - while (Gtk2->events_pending()) - { - Gtk2->main_iteration(); - } - -} -# -############################################################################## -# -# Routine - create_format_tags -# -# Description - Creates the Gtk2::TextBuffer tags that are used to pretty -# print stuff. -# -# Data - $text_view : The GTK2::TextBuffer widget that is to have -# its tags created. -# -############################################################################## - - - -sub create_format_tags($) -{ - - my ($text_buffer) = @_; - - $text_buffer->create_tag("normal", "weight" => PANGO_WEIGHT_NORMAL); - - $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"); - - $text_buffer->create_tag("green", "foreground" => "DarkGreen"); - $text_buffer->create_tag("bold-green", - "weight" => PANGO_WEIGHT_BOLD, - "foreground" => "DarkGreen"); - $text_buffer->create_tag("italics-green", - "style" => "italic", - "foreground" => "DarkGreen"); - $text_buffer->create_tag("bold-italics-green", - "weight" => PANGO_WEIGHT_BOLD, - "style" => "italic", - "foreground" => "DarkGreen"); - - $text_buffer->create_tag("red", "foreground" => "DarkRed"); - $text_buffer->create_tag("bold-red", - "weight" => PANGO_WEIGHT_BOLD, - "foreground" => "DarkRed"); - $text_buffer->create_tag("italics-red", - "style" => "italic", - "foreground" => "DarkRed"); - $text_buffer->create_tag("bold-italics-red", - "weight" => PANGO_WEIGHT_BOLD, - "style" => "italic", - "foreground" => "DarkRed"); - - $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"); - -} -# -############################################################################## -# -# Routine - set_label_value -# -# Description - Set the text for the given label and the tooltip for the -# parent widget, assumed to be an event box, to the specified -# text. -# -# Data - $widget : The label widget that has an event box as its -# parent. -# $value : The text that the label and tooltip are to be set -# to. -# -############################################################################## - - - -sub set_label_value($$) -{ - - my($widget, $value) = @_; - - $widget->set_text($value); - $tooltips->set_tip($widget->parent(), $value); - -}