# from [feffa0508a0303b105286867de349ee6a898aa6c] # to [d0a7568e48ad476cd5ed7112b37f938008f8d988] # # patch "mtn-browse" # from [dfbc7c887a2b1f9c0e9adfabb67e6b9c6417bce9] # to [01602d56f6e53ef077f711b064395fd89a075f30] --- +++ @@ -0,0 +1,1873 @@ + + + + + + ] +> + + + + + + + + + + + + + Monotone Browser Manual + + + 2009 + Anthony Cooper + + + + Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. You may obtain a copy of the GNU Free Documentation License from the Free Software Foundation by visiting their Web site or by writing to: Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + Many of the names used by companies to distinguish their products and services are claimed as trademarks. Where those names appear in any &APP; documentation, and those trademarks are made aware to the members of the &APP; Project, the names have been printed in caps or initial caps. + + + + + Anthony + Cooper + + CooSoft +
address@hidden
+
+
+ + + +
+ + + + + + &APP; Manual V0.01 + 02 May 2009 + + Anthony Cooper + CooSoft + + + + &APP; Manual V0.02 + 08 November 2009 + + Anthony Cooper + CooSoft + + + + &APP; Manual V&VERSION; + 23 May 2010 + + Anthony Cooper + CooSoft + + + + This is version &VERSION; of the &APP; manual. + + Feedback + To report a fault or make a suggestion regarding the &APP; application or this manual, please email address@hidden. + + + +
+ + + + + Introduction + + &APP; is a Gnome application for browsing Monotone VCS (Version Control System) databases. There is no need to have a workspace, one can simply open the database directly with this application and start browsing straight away. + + A sample of what one can do with &APP; includes: + + + + Easily select a revision from within a branch. + + + Find a revision using complex queries. + + + Navigate the contents of a revision using a built in file manager. + + + Display file contents, either using the internal viewer or an external helper application. + + + Compare the changes between different revisions or versions of a file either using the internal difference viewer or an external application. + + + Find files within a revision based on detailed search criteria. + + + Display file annotations and easily refer back to the corresponding change documentation. + + + Save files to disk. + + + + + Monotone + + Monotone is a distributed VCS that supports change set check-ins, disconnected operation with an efficient synchronisation mechanism, history sensitive merging and many more features. It provides a simple, clean and consistent interface to the user. + + For more general information please go to Monotone's web site. There is also an extensive online user guide. + + + + + + + + + Getting Started + + This chapter covers the basic usage of &APP;. For more advanced usage topics please look at the subsequent chapters. + + + Opening A Database + + When &APP; first starts up, nearly everything is greyed out. You need to select a Monotone database by using the Open tool-bar button or the FileOpen menu option. Use the Open Database dialog window to select the database and left click on the Open button to finish. + +
+ Opening A Monotone Database + + Opening a Monotone database using the Open Database dialog window. + + +
+ +
+ + + Monotone Browser Window - At A Glance + + Once a database has been opened you will be presented with a window like the one shown below. + +
+ An Empty Monotone Browser Window + + An empty Monotone Browser window before anything has been selected. + + +
+ + Below the tool-bar the main part of the window is split into four distinctive areas: + + + + View - This panel allows one to select the project branch and revision that you wish to browse. The buttons to the right act on the selected revision. + + + Revision Browser - This panel allows you to navigate around the selected revision and search for specific files. + + + File Viewer - This panel displays details about the selected file, including its contents. The buttons to the right act on the selected file. + + + Details - This panel displays general details about the database and revision. + + + +
+ + + Browsing The Database + + This section covers how to quickly select a branch and revision and then get at the files within that revision. + + + Selecting A Branch And Revision + + Before any information can be viewed, one has to select not only the database to use, but also the branch and revision that you wish to view. This is done by using the Branch and Revision combobox entry fields inside the View panel. There are a number of things that &APP; will do to help you select the item you want. + + For example, when entering the branch name one can simply select it from the pull down list that is provided when you left click on the   button just to the right of the combobox entry field. However, the list of branch names can be very long and so it may be easier to start typing it in directly. As you start typing, &APP; will attempt to auto-complete it for you and update the pull down list so as to only contain those remaining valid completions. + + So if you typed in net.venge.monotone.contrib.lib.automate-stdio as the branch and then left clicked on the   button then you would have something looking like that shown in the figure below. + +
+ A Branch Combobox Entry Field Showing Its Completion List + + Branch combobox entry field showing a reduced set of completion options via its pull down list. + + +
+ + + + + Entering a space will trigger auto-completion. This is especially useful when starting off with a blank combobox entry field or when a value was pasted in and you want to update the display. + + + With a very large number of branches, performance can suffer. There is a preference option that can be used to prevent the updates to the pull down lists (see ). + + + + + Entering a revision id is done in much the same way as with a branch name. The revision ids are limited to those that are on the selected branch. One can also select a revision by its tag name simply by making sure that Tagged is ticked. + + + If you wish to select a revision that is not on a branch or select a specific revision out of several labelled with the same tag on the same branch then one has to use the Advanced Find dialog window (see ). + + +
+ + + Selecting A File To Display + + Once a revision has been displayed then the contents of that revision will be displayed in the Revision Browser panel. This acts as a simple file manager. Single left clicking on a file will display its contents and double left clicking on a directory will change into that directory. + + Basically anything that could be accessed in a clean Monotone workspace can be accessed here. The file viewer is capable of displaying some image files, text files with syntax highlighting and if it is a file format that cannot be handled internally then it will display its MIME type followed by a hexadecimal dump of the file's contents. This behaviour can be controlled through a preference option (see ). + +
+ A Monotone Browser Window Displaying A Text File + + A Monotone Browser window displaying a text file with syntax highlighting. + + +
+ +
+ A Monotone Browser Window Displaying An Image File + + A Monotone Browser window displaying an image (.png) file. + + +
+ +
+ A Monotone Browser Window Displaying A Hex Dump + + A Monotone Browser window displaying a hex dump of a file it cannot display natively. + + +
+ +
+ +
+ + + Monotone Browser Window - A Closer Look + + This section looks at the Monotone Browser window in greater detail, going through the menu options and buttons. + + + Menus + + The menus on a Monotone Browser window largely provide an alternative means of accessing the functions provided by the tool-bar buttons. For example the FileOpen menu option does exactly the same thing as the Open tool-bar button. As the tool-bar buttons are discussed in the next section, I will not bother to elaborate on their menu counterparts here. However there are a few extra menu options that do not appear anywhere else: + + + + FileNew Blank - Displays a new Monotone Browser window but in its closed state rather than initially duplicating the contents of the existing window. This is particularly useful if the first thing you are going to do is to open a completely different database. + + + FileConnect To Server - Presents the user with a remote Monotone server bookmark menu from which they can select a server to connect to. There is also the Manage Server Bookmarks menu option for managing these bookmarks (see ). + + + FileQuit - This completely quits out of the application regardless of what windows you may have open. Normally the application only exits when all of its Monotone Browser windows have been closed. + + + ViewFile Encoding - Allows the user to select the character encoding for the files that they are going to display in &APP;. The most popular encodings are listed first but there is also the All Encodings submenu containing all of the encodings known to your system. The default character encoding used by &APP; is UTF-8. + + + ToolsChoose Authentication Key - Allows the user to select the local authentication key that is to be used when connecting to remote Monotone servers. This menu is only useful if you happen to have more than one local authentication key, which is not usually the case. The Unspecified menu option can be used to reinstate the default behaviour where Monotone is not told which authentication key to use. + + + ToolsCompare Arbitrary Revisions - Displays a Compare Arbitrary Revisions window that allows the user to compare any two revisions in the database (see ). + + + HelpHelp On Window - Displays the relevant section in the &APP; manual for the current window. + + + HelpContext Help - This puts the application into a context sensitive help mode where one can select a specific widget on a window and display the relevant section in the &APP; manual for that widget or group of widgets. + + + HelpHome Page - Displays the home page for the &APP; application. + + + HelpAbout - Displays the obligatory About window that nurses the ego of the author and rightly mentions those people kind enough to contribute their time and effort to this project. + + + + + + + The keyboard shortcuts shown against the Help menu options work on nearly all &APP; windows. + + + The FileConnect To Server submenu is only available if your version of Monotone supports this feature. + + + Connecting to a Monotone server over a network is not only slower but also has no support for running Monotone Viz or doing file annotations. + + + + + + + + Tool-Bar + + The tool-bar provides the following buttons: + + + + + + Button + Description + + + + + + Displays a new Monotone Browser window with the same branch and revision pre-selected. If you want to open another database in the new window then you may wish to use the FileNew Blank menu option instead, as this way no time is wasted needlessly selecting something you do not want. + + + + Allows you to open a different database via the Open Database dialog window. Any database currently open in the browser window will automatically be closed first. + + + + Closes the database currently open in the Monotone Browser window. The vast majority of the window is disabled, greyed out, until another database is opened. + + + + Reopens the database currently open in the Monotone Browser window and then forcibly refreshes the window's contents. This is particularly useful if you have done an mtn sync on the command line and wish to update your list of branches. + + + + Displays the Preferences dialog window (see ). + + + + Displays the contents page of this help manual. + + + + + + + + + Browser Buttons + + + View Buttons + + These buttons are located on the right hand side of the View panel. They do the following: + + + + + + Button + Description + + + + + + Displays the Advanced Find dialog window that allows one to enter more complex database queries (see ). + + + + Runs Monotone Viz. Any selected branch and revision details will be passed to Monotone Viz so that it can graph the specific branch and select the revision on that branch. + + + + Displays a Revision History window for the selected revision, showing its change history right back to the earliest revision on that path (see ). + + + + Displays a Change Log window for the selected revision, showing all the details about that revision (see ). + + + + + + + + + Revision Browser Buttons + + These buttons are located on the right hand side of the Revision Browser panel. They do the following: + + + + + + Button + Description + + + + + + Moves the revision browser up one directory level. + + + + Displays the Find Files window that allows one to search for files in the currently selected revision. Its capabilities are very similar to those found in most desktop file managers (see ). + + + + + + + + + File Viewer Buttons + + These buttons are located on the right hand side of the File Viewer panel. They do the following: + + + + + + Button + Description + + + + + + Toggles the displaying of line numbers in the file viewer. + + + + Displays a Find Text dialog window that allows one to search for text within the currently displayed file. Within the dialog window there are a number of options. The Case sensitive and Search backwards options affect the search in the way that one would expect. The Regular expression option, when ticked, allows one to enter search terms that are extended regular expressions (RE). Please see regex(7) for further details on regular expressions. + + + + Displays a standard Save As dialog window, allowing one to save the contents of the currently selected file to disk. + + + + Launches the desktop's helper application for the currently selected file. The helper application for a given MIME type can be specified or overridden in the Preferences dialog window (see ). + + + + Displays a Annotated Listing window for the currently selected file. This window shows who changed what, when they did it and the revision under which the change was made. + + + + Displays a File History window for the currently selected file, showing its change history right back to the earliest revision of that file (see ). + + + + + + + + + + + Information Fields + + + File Viewer Fields + + These fields are located at the top of the File Viewer panel and show the most up to date information about the file being shown, relative to the currently selected revision. They provide the following information: + + + + + + Field + Description + + + + + File + Displays the file's full path and file name. If one were looking for that file in a workspace then the path is of course relative to the workspace's base directory. + + + Author + Displays the author responsible for this version of the file. + + + Last Update + Displays the modification date and time when this version of the file was checked in. + + + Revision Id + Displays the id of the revision under which this version of the file was checked in. + + + + + + + + + General Details Fields + + These fields are located in the Details panel and show general information about the database and revision. They provide the following information: + + + + + + Field + Description + + + + + Database + Displays the the full path and file name of the monotone database that is currently being viewed. + + + Date + Displays the date and time when the currently selected revision was created. + + + Author + Displays the author responsible for the currently selected revision. + + + Change Log + Displays the first line of the change log for the currently selected revision. + + + + + + + + + + + Tooltips And Selection + + Tooltips in this application work as expected. For most widgets, hovering over them will display an explanatory tooltip after a few seconds. However, in the case of entry fields, in order not to interfere with typing, tooltips only come up when you hover over an entry field's label rather than the entry box itself. Tooltips are also used on the values displayed in information fields so that if they get truncated by the size of the window then one can always view their full value via a tooltip. + + Most text items can be selected in the normal way with the mouse. However, the values in all information fields can also be selected. In the case where the value gets truncated then one can still select the entire value by treble left clicking with the mouse. + + The following figure illustrates the two points made above in a rather artificial way. It shows a Monotone Browser window where the user has treble left clicked on the value inside the File field and then moved the mouse over to the Change Log field. + +
+ A Monotone Browser Window Showing Tooltips And Text Selection + + A Monotone Browser window showing a tooltip and the selection of text inside an information label. + + +
+ +
+ +
+ +
+ + + + + More Complex Queries + + This chapter covers the Advanced Find dialog window and how it can be used to perform more complex queries on a database. + + + This dialog window concerns itself with finding a particular revision in the database. If you want to find a particular file based upon some search criteria within a revision then, once the revision has been displayed, use the Monotone Browser window's Find Files button to actually search for the file(s) (see ). + + + + The Advanced Find Dialog Window + + When you left click on a Monotone Browser window's Advanced Find button you will be presented with a dialog window like the one shown below. + +
+ An Empty Advanced Find Dialog Window + + An empty Advanced Find dialog window. + + +
+ + Like the Monotone Browser window, this one is also split into five distinctive areas: + + + + Mode - This panel simply allows you to select the type of query that you wish to perform, either a simple query or an advanced one. + + + Simple Query - This panel operates in a very similar fashion to the View panel found on a Monotone Browser window (see ), apart from all the revision buttons are missing and it operates in a slightly less restrictive manor. + + + Advanced Query - This panel allows one to directly enter Monotone database queries, it also provides some tools to help those that are not familiar with Monotone's query syntax. + + + Revisions - This panel displays a list of revisions that match the query, along with details about the branches that they are on, their dates and their authors. + + + Details - This panel simply displays the full change log for the revision currently selected in the Revisions panel. + + + +
+ + + Simple Queries + + This is the default query mode. Having read the previous section, one would be forgiven for thinking what is the point?. Well the main reason for using it is if you want to look at a revision's change log before selecting it. This can be particularly useful when the tagged revision that you attempted to select in the parent Monotone Browser window is not unique on the currently selected branch and something like the following dialog window pops up. + +
+ A `Multiple Revisions' Dialog Window + + A multiple revisions dialog window. + + +
+ + Of course one could simply use the Revisions combobox in the above dialog window, but in most cases people would not know the id of the revision that they are after. + + Once you have completed your simple query, a list of matching revisions will be displayed in the Revisions panel. When one of these revisions is selected, its full change log is displayed in the Details panel on the right. Left clicking on the OK button will dismiss the Advanced Find dialog window and load the selected revision into the parent Monotone Browser window. + + + Double left clicking on a revision will select that revision and then proceed as if the OK button had been pressed. + + +
+ An Example Of A Simple Query + + An Advanced Find dialog window displayed in its simple query configuration showing a typical query. + + +
+ +
+ + + Advanced Queries + + This mode allows one to directly enter Monotone queries as would be given to an mtn automate select command. There are a number of helper tools that assist those users not familiar with the query syntax. + + + Helper Tools + + + Extra Buttons + + There are a number of extra buttons in this mode that do the following: + + + + + + Button + Description + + + + + + Runs the query entered into the Search term combobox entry field. + + + + Stops the currently running query. This is very useful when you make a mistake and accidentally enter a valid query that returns many hundreds of matches. + + + + Enters the currently selected selector term into the Search term combobox entry field. + + + + + + + + + Selector Terms + + The part of the window shown below is solely dedicated to helping the user to enter valid selector terms. If you are familiar with Monotone's query syntax then you can simply enter the query directly into the Search term combobox entry field. + +
+ Selector Term Helper + + The selector term helper. + + +
+ + The selector term combobox, the combobox that has Author showing in the above figure, has the following values: + + + + Author - This selector allows one to select revisions based upon their author. This is Monotone's a: selector. It takes one parameter, the name of an author, normally in the form of an email address (but this does depend upon your naming conventions). + + + Branch - This selector allows one to select revisions based upon which branches they belong to. This is Monotone's b: selector. It takes one parameter, the name of a branch, normally in the form of a full qualified reverse DNS style project path (but again this does depend upon your naming conventions). + + + Cert - This selector allows one to select revisions based upon whether they have a particular certificate. This is Monotone's c: selector. It takes one parameter, the name of a Monotone certificate. + + + Date (=) - This selector allows one to select revisions based upon their creation date. This is Monotone's d: selector. It takes one parameter, the date and time to be compared against. + + + Date (<=) - This selector allows one to select revisions based upon whether their creation date is less than or equal to the specified date. This is Monotone's e: selector. It takes one parameter, the date and time to be compared against. + + + Date (>) - This selector allows one to select revisions based upon whether their creation date is greater than the specified date. This is Monotone's l: selector. It takes one parameter, the date and time to be compared against. + + + Head Revision - This selector allows one to select revisions based upon whether they are the head revision of the specified branch. This is Monotone's h: selector. It takes one parameter, the name of a branch. If the branch name is not specified then the branch in the current workspace is assumed. + + + Identifier - This selector allows one to select revisions based upon their revision id. This is Monotone's i: selector. It takes one parameter, the id of a revision, in the form of a hexadecimal string. The revision id need not be complete. + + + Logical And - This is a logical AND operator. Currently Monotone does not directly support an OR operator. This is Monotone's / selector operator. It takes no parameters but instead combines two other selectors together. + + + Message - This selector allows one to select revisions based upon whether their changelog or comment certificates contain certain text phrases. This is Monotone's m: selector. It takes one parameter, the text that is to be matched, with * being a wildcard character. + + + Parent - This selector allows one to select revisions that are parents of the specified revision id. This is Monotone's p: selector. It takes one parameter, the id of a revision, in the form of a hexadecimal string. The revision id need not be complete. + + + Tag - This selector allows one to select revisions based upon whether they are tagged with a particular tag. This is Monotone's t: selector. It takes one parameter, the name of a tag. + + + + + + + If no argument is given to a selector term when it is inserted into the search term, then a comment will be inserted. This should be replaced with the actual argument or removed if the argument is optional. + + + When inserting dates and times into a search term, one can remove the time field if you wish to. For example, Monotone will accept 2009-02-22T16:47:00, 2009-02-22, 2009-02 and 2009 as valid date/time values. + + + Monotone's u: selector is not supported by &APP; as it can only be used from within a Monotone workspace, which are largely ignored by this application. + + + Some of the selectors mentioned above may not be available to you from the selector term combobox, as only those supported by your version of Monotone are listed. + + + + +
+ +
+ + + An Example + + Let us suppose that you want to find all of the revisions made by one of the Monotone developers, address@hidden, since the beginning of February 2009, but you do not understand Monotone's query syntax. This is how you would go about constructing the query: + + + + Make sure that Advanced query is selected. + + + Check that the Search term combobox entry field is empty. + + + Select Author in the selector term combobox. + + + Enter address@hidden into the adjacent argument entry field. + + + Left click on the   button to insert that selector term into the Search term combobox entry field. + + + Select Logical And in the selector term combobox. + + + Left click on the   button to insert that selector term into the Search term combobox entry field. + + + Select Date (>) in the selector term combobox. + + + Use the calendar to select any date in February 2009. + + + Left click on the   button to insert that selector term into the Search term combobox entry field. + + + Edit the Search term combobox entry field so as to remove the time and day fields and any trailing hyphen from the end of the value inserted by the previous step. + + + Left click on the   button to execute the query. + + + + Assuming that you then selected a revision listed in the Revisions panel, then you should end up with an Advanced Find dialog window looking something like that shown in the figure below. + +
+ An Example Of An Advanced Query + + An Advanced Find dialog window displayed in its advanced query configuration showing a typical query. + + +
+ +
+ +
+ +
+ + + + + Finding Files Within A Revision + + This chapter covers the Find Files window and how it can be used to find specific files within a revision. + + + The Find Files Window + + When you left click on a Monotone Browser window's Find Files button you will be presented with a window like the one shown below. + +
+ An Empty Find Files Window + + An empty Find Files window. + + +
+ + This window is split into two distinctive areas: + + + + Query - This panel allows one to enter fairly complex search criteria and perform file searches. The behaviour of this panel should be familiar to anyone that has used a similar facility inside a file manager. + + + Results - This panel displays a list of files that match the query. The information fields at the bottom display the same sort of information as that displayed in a Monotone Browser's File Viewer panel (see ) apart from the File id field, which displays the selected file's unique Monotone id. + + + +
+ + + Query Fields + + Query fields are grouped into three tabbed panes as follows: + + + + Name Tab + + + + + Field + Description + + + + + Searching from + Displays the directory from which the search will be conducted. This field will be blank if the search is being done from the upper most directory. + + + Files named + A combobox entry field where one can enter a file name pattern that is to be searched for. File name patterns can be those used with commands like ls(1). + + + Case sensitive + When ticked, make any file name pattern matching case sensitive, otherwise case is unimportant. + + + Search subdirectories + When ticked, recursively search all directories from the current one downwards, otherwise only search the current directory. + + + + + + + Contents Tab + + + + + Field + Description + + + + + Files containing + A combobox entry field where one can enter a text pattern that is to be searched for inside each file. + + + Case sensitive + When ticked, make any text pattern searching case sensitive, otherwise case is unimportant. + + + Regular expression + When ticked, treat any text pattern as an extended regular expression (RE), otherwise the text pattern is treated as an ordinary sequence of characters. Please see regex(7) for further details on regular expressions. + + + Search binary files + When ticked, search binary files as well as text files, otherwise only search text files. A file is deemed to be binary if it contains more than a small percentage of non-text data. + + + + + + + Properties Tab + + + + + Field + Description + + + + + Find all files created or modified + When ticked, enable the ability to search based on a file's modification time, otherwise modification time is unimportant. + + + between + When selected, one can enter a time range in which a file's modification time has to fall in order to be selected (i.e. select files changed between x and y). + + + during the previous + When selected, one can enter how recent a file's modification time has to be in order to be selected (i.e. select files changed within the last x minutes/hours/days/months/years). + + + File size is + When set to something other than Any Size, enable the ability to search based on a file's size, otherwise size is unimportant. + + + Files last modified by + A combobox entry field where one can enter the id of the author that is to be searched for on each file. + + + + + + + + + + + Query Buttons + + These buttons are located on the right hand side of the Query panel. They do the following: + + + + + + Button + Description + + + + + + Runs the query entered into the forms on the corresponding tabs. + + + + Stops the currently running query. This is very useful when you are performing a complex search on a revision containing many files. + + + + + + + + + An Example + + Let us suppose that you want to find all of the C++ source code files modified by one of the Monotone developers, address@hidden, in the last 10 years that contain the text roster (for the sake of argument we will assume that the Find Files button was clicked whilst viewing the top level directory of the revision tagged with monotone-0.42). This is how you would go about doing the search: + + + + Make sure that Name tab is visible. + + + Enter *.[cC][cp]* into the Files named combobox entry field. Although not fool proof, we will assume all C++ source code files have the extensions .cc, .cpp or .C which they seem to do on the Monotone project. + + + Check that Case sensitive is unticked and Search subdirectories is ticked. + + + Switch to the Contents tab. + + + Enter roster into the Files containing combobox entry field. + + + Check that Case sensitive, Regular expression and Search binary files are unticked. + + + Switch to the Properties tab. + + + Check that Find files created or modified is ticked and during the previous is selected. + + + Enter 10 into the during the previous entry field. + + + Select Year(s) in the adjacent combobox. + + + Make sure the File size is combobox is set to Any Size. + + + Enter address@hidden into the Files last modified by combobox entry field. + + + Left click on the   button to execute the query. + + + + Assuming that you then selected a file listed in the Results panel, then you should end up with a Find Files window looking something like that shown in the figure below. + +
+ An Example Of A Find Files Query + + An Find Files window showing a typical query. + + +
+ + + + + Double left clicking on a file will display the file's contents in a new Monotone Browser window. + + + Some queries can take a long time to execute depending upon the nature of the search and the number of files being searched. However the next time such a query is made, the speed will be greatly increased. This is because &APP; caches certain information after the first search. This cache is only cleared when the currently selected revision is replaced in the parent Monotone Browser window. + + + + +
+ +
+ + + + + Looking At Change Histories + + This chapter covers the examining of revision and file histories, doing comparisons between revisions and files and finding out who was responsible for a particular piece of text within a file. + + + The Revision And File History Windows + + In fact as you will see, these windows are nearly identical. Therefore I will only concentrate on the Revision History window. + + When you left click on a Monotone Browser window's Revision History button you will be presented with a window like the one shown below. + +
+ A Revision History Window + + A Revision History window. + + +
+ + The Revision History window shows the change history from the revision currently displayed in the parent Monotone Browser window right back to the base revision. Each revision has a summary change log displayed, separated from the next revision by a red line. + + + Whilst the Number of revisions field is pretty self explanatory, please remember that it shows the number of revisions actually displayed and may be less than what could have been found in the database if the Stop button was pressed. For example, above I pressed the Stop button early on but had I not done so then the number of revisions would have been more like eight thousand rather than one hundred. + + + + Do think twice before loading up revision histories that are very large. Normally one would only want to look at the first fifty or so items. Do make use of the Stop button. Although it will work as expected, displaying thousands of revisions will take a very long time in both gathering the information and cleaning up afterwards. + + + + History Buttons + + These buttons are located after each summary change log and just below the scrolling history window. They do the following: + + + + + + Button + Description + + + + + Select As Id 1 + Selects that revision as the first revision that is to be compared. It's revision id is displayed in the First revision id field at the bottom of the window. + + + Select As Id 2 + Selects that revision as the second revision that is to be compared. It's revision id is displayed in the Second revision id field at the bottom of the window. + + + Browse Revision + Displays a new Monotone Browser window showing the contents of that revision. This button is only displayed in Revision History windows. + + + Browse File + Displays a new Monotone Browser window showing the contents of that version of the file. This button is only displayed in File History windows. + + + Full Change Log + Displays a Change Log window containing the full change log for that revision. In addition to the summary information already displayed, a full change log also contains the list of files changed and the parent and manifest ids. + + + + When pressed the first time, this button stops the traversing of revision and file histories. When pressed a second time, it stops the gathering of information about each revision. This is very useful when looking at revision histories, as these can be massive, or when looking at file histories, as these take a long time to calculate where each change happened. + + + + Displays a Differences window that shows all of the differences between the two selected revisions. The two revisions in question are selected by using the Select As Id... buttons mentioned above (see ). + + + + + + + +
+ + + The Differences Window + + When two revisions or files are compared inside a Revision History window, you will be presented with a window like the one shown below. + +
+ A Differences Window + + A Differences window showing highlighted changes for a file. + + +
+ + The Differences window shows the differences between the two revisions or files selected in the parent Revision History or File History window. Additions are highlighted in green and deletions are highlighted in red. + + + + + Regardless of the order in which the revisions or files were selected, the text highlighted in green is always what is present in the more recent version of the file that is not present in the older version. Likewise the text highlighted in red is always what is not present in the more recent version of the file that is present in the older version. + + + Please remember that the highlighted changes could have occurred anywhere between the selected revisions or files. + + + + + + Do think twice before displaying very large differences as this can take a very long time in both formatting the information and cleaning up afterwards. Do make use of the Stop button. However, unlike viewing histories, if you really need to view very large comparisons then one always has the option of turning off the coloured formatting feature via the relevant preference option (see ). + + + + Differences Buttons + + These buttons are located at the top and bottom of the Differences window. They do the following: + + + + + + Button + Description + + + + + Jump to file + Allows one to navigate around the comparison results by jumping from one file to another. The special entry Summary will take you to the top of the results where a summary of the differences found is displayed. + + + + Performs a comparison of the file selected in the Jump to file button using the external helper application specified in the Preferences dialog window (see ). This is set to kompare by default. + + + + Displays a standard Save As dialog window, allowing one to save the entire comparison results in its original unified diff patch(1) format to disk. + + + + Stops the currently running comparison. This is very useful when you accidentally start a comparison that generates a much larger result than expected. + + + Revision Change Log + These buttons display Change Log windows for the two revisions on which the comparison was based. The red button displays the change log for the older revision that contains the text highlighted in red. Likewise the green button displays the change log for the newer revision that contains the text highlighted in green. + + + + + + + +
+ + + The Compare Arbitrary Revisions Window + + When the user selects the ToolsCompare Arbitrary Revisions menu option on a Monotone Browser window they will be presented with a Compare Arbitrary Revisions window. This allows them to select any two revisions from the database for comparison. A typical Compare Arbitrary Revisions window that has two revisions selected is shown below. + +
+ A Compare Arbitrary Revisions Window + + A Compare Arbitrary Revisions window showing two selected revisions. + + +
+ + + Depending upon what revisions are selected, you may end up with a massive result. Think about what you are doing before using this feature. + + + + Compare Arbitrary Revisions Buttons + + These buttons are located on the right hand side of the Compare Arbitrary Revisions window. They do the following: + + + + + + Button + Description + + + + + + Displays the Advanced Find dialog window that allows one to enter more complex database queries (see ). This window is used to select both revisions. + + + + Displays a Differences window that shows all of the differences between the two selected revisions. The two revisions in question are selected by using the   buttons mentioned above (see ). + + + + + + + +
+ + + The Change Log Window + + The Change Log window is displayed when ever a full change log entry is to be displayed. It simply shows some nicely formatted text describing the relevant change log. A typical Change Log window is shown below. + +
+ A Change Log Window + + A Change Log window showing the full change log for a revision. + + +
+ +
+ + + The Annotated Listing Window + + When you left click on a Monotone Browser window's Annotate File Contents button you will be presented with a window like the one shown below. + +
+ An Annotated Listing Window + + An Annotated Listing window showing what changes were done by whom and when to a file. + + +
+ + The Annotated Listing window shows the revision and author responsible for each line of text within a file, along with the date on which that change was made. If one right clicks on the window then a menu pops up that allows you to extract certain information about the revision responsible for the text directly under the mouse cursor. These menu options do the following: + + + + + + Menu Option + Description + + + + + Display Change Log + Displays the full change log for that revision. + + + Display File History + Displays the file's change history from that revision back to when the file was added. + + + Display Revision History + Displays the revision's history from that revision back to the first revision. + + + Compare File With Previous Version + Compares the version of the file found in that revision with the previous version of that file. + + + Compare Revision With Parent + Compares the revision with its parent. The comparison is not restricted to just the annotated file. + + + + + +
+ +
+ + + + + User Preferences + + This chapter covers the Preferences and Manage Server Bookmarks dialog windows. + + + Some preference settings only take effect when &APP; is restarted. + + + + The Preferences Dialog Window + + When you left click on a Monotone Browser window's Preferences tool-bar button you will be presented with a dialog window like the one shown below. + +
+ A Preferences Dialog Window + + A Preferences dialog window. + + +
+ + This window allows you to change the behaviour of &APP;. The preference settings are grouped into three tabbed panes as follows: + + + + General Tab + + + Startup Defaults Panel + + + + + Field + Description + + + + + Database to open + This entry field contains the default Monotone database that is to be opened automatically when &APP; starts. Left click on the Browse button to display an Open Database dialog window that will help you select a database. + + + Takes precedence + If this is ticked and &APP; is launched from within a Monotone workspace then the database details for that workspace take precedence over the default database. Otherwise the workspace is completely ignored. + + + Auto select branch and revision + If this is ticked and &APP; is launched from within a Monotone workspace then the workspace's branch and revision are selected and displayed by default in &APP;. Otherwise no branch and revision are selected. + + + + + + + Revision Selection Panel + + + + + Field + Description + + + + + Auto select head revision + If this is ticked then whenever a branch is selected, it's head revision is automatically selected as well. Otherwise the revision has to be selected manually.This option only works when revisions are selected by their id rather than by their tag name, i.e. Tagged is unticked in the Monotone Browser window. + + + + + + + ComboBox Settings Panel + + + + + Field + Description + + + + + Tagged lists + Controls the displaying of tagged revisions within a Revision combobox entry's pull down list. The Limit to entry field stipulates how many items will be displayed in a pull down list (if this is set to 0 then the list is unlimited). Sort chronologically and Sort by name determine whether the items are sorted by date or by their name respectively. + + + Id lists + Controls the displaying of revision ids within a Revision combobox entry's pull down list. The remaining fields have the same purpose as those discussed under Tagged lists, the only difference being that the sort by name option is called Sort by id rather than Sort by name. + + + History size + Determines how much history is retained for ordinary combobox entry's fields via their pull down lists. This history is saved to disk so that it is available the next time you run &APP;. This does not apply to Branch and Revision combobox entry fields as they are used to select items out of specific lists retrieved from the database. + + + Static lists + If this is ticked then the pull down lists on Branch, Revision and Directory combobox entry fields are not updated each time the user enters something into the corresponding entry field. Otherwise the pull down lists are updated on each key release. + + + Show tooltips + If this is ticked then each time an invalid character is entered into a Branch or Revision combobox entry field a completion tooltip is displayed just below it that shows the same explanatory text that is displayed in the status bar at the bottom of the window. Otherwise no completion style tooltips are displayed. + + + + + + + + Branch and Revision pull down lists display their items ordered from left to right and then top to bottom. + + + Histories are displayed with the most recent items uppermost. + + + + + + Browser Display Options Panel + + + + + Field + Description + + + + + Show suspended revisions + If this is ticked then the branches of suspended revisions are included in Branch combobox entry pull down lists. Otherwise only active branches are listed.Apart from the obvious use for this setting, it can be useful when there are a large number of branches in the database, as not bothering to weed out suspended branches speeds things up considerably. + + + Detailed file listings + If this is ticked then the Last Update and Author columns are shown in Revision Browser panels. Otherwise only the file's icon and name are shown. Not displaying these two columns speeds up the displaying of a directory's contents at the expense of less information. + + + Folders come first + If this is ticked then folders are displayed before any files in directory listings. Otherwise both folders and files are displayed in strict alphabetical order. + + + Show line numbers + If this is ticked then line numbers are shown by default when viewing the contents of files. Otherwise no line numbers are shown by default. + + + Binary threshold + This threshold is used to decide whether a file's contents are textual or binary in nature. When &APP; is determining the amount of non-printable characters in a file it breaks the data down into 10KB chunks and calculates the percentage of non-printable characters in that chunk. As soon as a chunk's percentage of non-printable characters is greater than this threshold the file is considered to be binary and not textual. This process is designed for efficiency as sometimes many hundreds of files need to be scanned per second. + + + + + + + List Options Panel + + + + + Field + Description + + + + + Search as regular expression + If this is ticked then whenever a table is searched, by entering CtrlF, the search term entered into the data entry field is treated as a regular expression. Otherwise the search term is treated as plain text. + + + + + + + External File Comparison Application Panel + + + + + Field + Description + + + + + Application + This entry field contains the name of the program that is to be used when comparing two files with the external helper application. Although &APP; normally uses Monotone to compare files internally, there are occasions when Monotone is unable to do this (due to files being renamed or moved) or when the user feels that comparisons are better viewed in a tool dedicated to that purpose. Under these circumstances the application specified here is use. {file1} and {file2} are special tokens that are replaced with the corresponding file names. + + + + If the Application entry field is left blank then all facilities associated with launching an external comparison tool are disabled. For example, the External Differences button on the Differences window will be permanently greyed out. + + + The {file1} and {file2} tokens are mandatory and it would probably be a good idea to enclose them in single quotes so that if a file name contains spaces then this does not cause a problem. + + + The file referred to by the {file1} token will be an older version to that referred to by the {file2} token. + + + + + + + + + + + + Appearance Tab + + + Fonts Panel + + + + + Field + Description + + + + + File content/report font + This font button specifies the font to use for displaying the contents of text files. When you left click on the button you are presented with a standard Pick A Font dialog window.It is best that the font you pick is a non-proportional or fixed space font (where each character has the same width) as this is what most text editors use. + + + + + + + Toolbar Settings Panel + + + + + Field + Description + + + + + Hide text + If this is ticked then the text just below the tool-bar buttons on a Monotone Browser window will not be displayed. Otherwise the explanatory text will be displayed. + + + Fixed + If this is ticked then the tool-bar on a Monotone Browser window will be fixed in position and will not be movable. Otherwise the tool-bar can be moved or made to float outside of any window. + + + + + + + File/Revision Comparison Panel + + + + + Field + Description + + + + + Coloured comparison listings + If this is ticked then revision and file comparisons are formatted using colour in order to make the differences stand out more clearly. Otherwise the comparison output is displayed as plain text in its original unified diff patch(1) format. + + + + + + + Colours Panel + + + + + Field + Description + + + + + Annotation prefix 1 down to Annotation text 2 + Determines the colours used when displaying text in an Annotated Listing window. As you will have observed, each block of labelled text is displayed in alternating colours in order to differentiate it from its neighbours above and below. In addition, the label, or prefix, on the left hand side of each text block is also displayed in its own colour so as to not only differentiate it from its neighbours but also from its accompanying text on the right. Therefore the colours for this window are broken down into four distinct groups, each having a foreground and background colour. When you left click on a colour button you are presented with a standard Pick A Colour dialog window. + + + Revision 1 and Revision 2 + Determines the colours used when displaying comparisons between revisions and files. As you will have observed, changes from each revision are displayed in different colours so as to highlight the differences. The foreground and background colours are used for showing the actual differences, whereas the highlight colours are used when displaying file context information in the dark grey status lines. When you left click on a colour button you are presented with a standard Pick A Colour dialog window. + + + + + + + + + MIME Types Tab + + + MIME Types Panel + This panel basically contains a list of MIME types known to &APP;, their associated file name patterns and a helper application that can handle that MIME type. Below this list is an entry field where one can enter new MIME types and then left click on the + button just to the right of this entry field to add it to the list above. Likewise, when you left click on the - then the currently selected MIME type is removed from the list. + When &APP; is first run, it initialises the MIME types list from the system's MIME type database, usually /usr/share/mime/globs. However, there is always a chance that you may need to add some extra MIME types yourself. + + + File Name Patterns Panel + This panel contains a list of file name patterns associated with the currently selected MIME type in the MIME Types panel. To the right of this list is an entry field where one can enter new file name patterns and then left click on the + button just to the right of this entry field to add it to the list . Likewise, when you left click on the - then the currently selected file name pattern is removed from the list. + + + File Actions Panel + This panel contains some settings for the currently selected MIME type in the MIME Types panel. + + + + + Field + Description + + + + + Display internally + If this is ticked then files of that MIME type will be displayed in &APP;'s file viewer when selected. Otherwise just the file's MIME type is displayed. If &APP; is not able to make sense of that particular MIME type then it will resort to displaying a hex dump of a file's contents instead. + + + Syntax highlight + If this is ticked then files of that MIME type will be syntax highlighted, if at all possible, when displayed in &APP;'s file viewer. Otherwise no syntax highlighting will take place and the text is just displayed in plain black. + + + Helper application + This entry field contains the name of the program that is to be used to display a file of this type when the user left clicks on a Monotone Browser window's Load Into Viewer button. {file} is a special token that is replaced with the corresponding file name. + + + + If the Helper application entry field is left blank then the file's default desktop application is used. + + + The {file} token is optional but if it is not present then it is automatically appended to the command in the entry field. If you do use this token then it would probably be a good idea to enclose it in single quotes so that if a file name contains spaces then this does not cause a problem. + + + + + + + + + + + + + When one left clicks on the Defaults button right at the bottom of the Preferences dialog window then all of the settings on the currently displayed tab are reset to their default values. + +
+ + + The Manage Server Bookmarks Dialog Window + + When you select the FileConnect To ServerManage Server Bookmarks menu option on a Monotone Browser window you will be presented with a dialog window like the one shown below. + +
+ A Manage Server Bookmarks Dialog Window + + A Manage Server Bookmarks dialog window. + + +
+ + This dialog window allows you to manage a list of Monotone servers that you can connect to remotely over the network. On the left hand side is the list of server names that appears in your bookmarks menu. To the right of this list is an entry field where one can enter new server names and then left click on the + button just to the right of this entry field to add it to the list . Likewise, when you left click on the - then the currently selected server name is removed from the list. + + Server names can be either a valid host name or an Internet address, optionally followed by a colon and the number of the port to connect to if the Monotone server is not listening on the standard port. 192.168.1.1, 192.168.1.1:5000, monotone.ca and venge.net:8000 are all examples of valid server name entries. + +
+ + + The Preferences File + + User preferences are stored in a file called .mtn-browser under the user's home directory. This file must remain writable for the user as it is updated every time a combobox entry field's history is changed. + + Should it become necessary to change the format of the preferences file in later versions of this application then &APP; will automatically upgrade any existing preferences file to the new format. However, once upgraded, the new format of preferences file cannot be used with older versions of &APP;. + + + Editing the preferences file by hand may cause &APP; to fail. If you must change it, take a copy of the preferences file beforehand just in case there are problems. + + + + +
+ + + + + Tips And Tricks + + This chapter covers some, hopefully helpful, tips and tricks that will enable you to get the most out of using &APP;. + + + Improving Performance + + Here are some basic tips that will hopefully help you improve performance of &APP; should you need to: + + + + Slow Startup - This could be because you are not running the dbus-daemon-1 daemon. Gnome applications, of which &APP; is one, use this message bus service to pass messages around. Or see the next tip. + + + Takes A Long Time To Get A List Of Branches - Try ticking Show suspended revisions under user preferences. Monotone does have to work quite hard to determine whether a branch is suspended or not and with this option ticked it can simply list every branch with the minimum of fuss. + + + Takes A Long Time To Display The Contents Of A Folder - Try unticking Detailed file listings under user preferences. For each file displayed, &APP; has to fetch modification times and author details. Depending upon the version of Monotone being used, this can be a costly process (Monotone versions 0.40 and 0.41 are particularly badly affected by this). + + + Takes A Long Time Searching For Files - The first time this is done on a revision, &APP; caches certain items of information about each scanned file. The next time a search is done it will be a lot faster. + + The file information cache is only kept whilst the current revision remains selected in that Monotone Browser window. + + + + Handling Revision Comparisons Is Very Slow - If it takes a long time during the Formatting and displaying differences stage then try unticking Coloured comparison listings under user preferences. If it takes a long time during the Calculating differences stage then that is down to the performance of Monotone rather than &APP;. + + + It Is Slow When Entering Branch, Revision And Directory Names - Try ticking Static lists under user preferences. Each time you enter a character into a Branch, Revision or Directory combobox entry field, &APP; revises the pull down list accordingly. For branch and directory lists this can take some time to do as their size is unrestricted. However, with this option ticked, pull down lists are left unaltered. + + + + + + + Finding Text + + + Inside Text Windows + + Only the File Viewer panel inside a Monotone Browser window has a Find Text button. However, it is possible to call up a Find Text dialog window on virtually any text window. Simply go to the text window that you want to search and either enter CtrlF or right click and select the Find menu option. A Find Text dialog window will then be displayed. + + + + + Inside Tables + + It is also possible to search for text inside tables (these are used to display the contents of folders, search results and MIME type file associations). Make sure that the table is selected, the easiest way to do this is to select a row inside it, and then enter CtrlF. A small text entry field will appear at the table's bottom right hand corner. When text is entered into this entry field, the first matching row in the table is searched for. The format of the search term can either be plain text or an extended regular expression (RE) depending upon what you have selected in the relevant preference option (see ). Please see regex(7) for further details on regular expressions. + + Only one column is selected for searching at any one time. Normally the most sensible column is automatically selected for you. However, if you want to search a different column then right click on the heading of the column that you wish to search and then select the Select As Search Column menu option. This will remain in effect until another column is selected or the window containing the table is dismissed. + + + + + + + Accessing Hidden Disk Files + + At some point you may want to open a file on disk that is hidden in some way, but you find that the standard `open file' dialog windows only show visible files. However, this can be changed. Simply right click on the list of files displayed in the table on the right hand side of the dialog window and then select the Show Hidden Files menu option. This is actually standard behaviour for all such dialog windows displayed using the Gnome toolkit. + + + + + Minimising File Information Cache Refreshes + + In order to speed things up, &APP; caches file information whenever it is fetched from Monotone. This helps to speed up certain actions such as displaying the contents of a folder or searching through files. Consequently the first time such an action is performed it will take the normal amount of time, but thereafter it will be much faster if not instantaneous. + + Each Monotone Browser window has its own cache to store information about the revision currently being viewed. If that revision changes or the user left licks on the Reload tool-bar button then the associated cache is emptied and any file information is re-fetched from Monotone. + + There may be times when you want to reload the database (such as to have access to new branches that have been added as a result of doing an mtn sync) but you do not want to invalidate your cache as you know it will come in useful later on. One way you can do this is to launch a new Monotone Browser window by left clicking on the New tool-bar button, within this new window left click on the Reload tool-bar button and then dismiss the window once it has finished reloading. This way the new database gets reloaded into &APP; without affecting the cache associated with the original Monotone Browser window. + + + + + + + Authors + + &APP; was written by Anthony Cooper and is currently maintained by him as well. To find more information about &APP;, please visit the official web page, www.coosoft.plus.com/software.html. Please send all comments, suggestions and problem reports to address@hidden. + + This manual was written by Anthony Cooper. Please send all comments and suggestions regarding this manual to address@hidden. + + + + + +
--- mtn-browse +++ mtn-browse @@ -0,0 +1,3896 @@ +#!/usr/bin/perl +############################################################################## +# +# File Name - mtn-browse +# +# Description - Perl GUI utility for browsing a Monotone database without a +# workspace. +# +# 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.008005; + +BEGIN +{ + use constant APPLICATION_NAME => "mtn-browse"; + use constant APPLICATION_VERSION => 0.71; + use constant FILE_COMPARE_CMD => "@INST:FILE_COMPARE_CMD@"; + use constant HTML_VIEWER_CMD => "@INST:HTML_VIEWER_CMD@"; + use constant LIB_DIR => "@INST:LIB_DIR@"; + use constant MIME_GLOB_FILE => "@INST:GLOBS_FILE@"; + use constant PREFIX_DIR => "@INST:PREFIX_DIR@"; +} +use locale; +use strict; +use warnings; + +# ***** REQUIRED PACKAGES ***** + +# Standard Perl and CPAN modules. + +use Cwd qw(getcwd); +use Data::Dumper; +use Encode; +use File::Basename; +use File::Spec; +use File::Temp qw(tempdir); +use Glib qw(FALSE TRUE); +use Gnome2; +use Gnome2::VFS -init; +use Gtk2 -init; +use Gtk2::Gdk::Keysyms; +use Gtk2::GladeXML; +use Gtk2::Helper; +use Gtk2::Pango; +use Gtk2::SourceView; +use IO::Dir; +use IO::File; +use IPC::Open3; +use Locale::TextDomain (APPLICATION_NAME, + File::Spec->catfile(PREFIX_DIR, "share", "locale")); +use POSIX qw(:errno_h :locale_h :sys_wait_h strftime); +use Symbol qw(gensym); +use Text::Tabs; +use Time::Local; + +# Add custom module directories to the module load search path. + +use lib File::Spec->catfile(LIB_DIR, "perl"); + +# Monotone AutomateStdio module (via derived caching class). + +use CachingAutomateStdio qw(:capabilities :severities); + +# Modules specific to this application. + +use Globals qw(:constants :variables); +use LocaleEnableUtf8; +use AdvancedFind; +use Annotate; +use ChangeLog; +use ComboAutoCompletion; +use Common; +use Completion; +use FindFiles; +use FindText; +use History; +use ManageServerBookmarks; +use MultipleRevisions; +use Preferences; +use WindowManager; + +# ***** GLOBAL DATA DECLARATIONS ***** + +# Constants for the columns within the manifest liststore widget. + +use constant MLS_ICON_COLUMN => 0; +use constant MLS_NAME_COLUMN => 1; +use constant MLS_DATE_COLUMN => 2; +use constant MLS_AUTHOR_COLUMN => 3; +use constant MLS_MANIFEST_ENTRY_COLUMN => 4; + +# Constants for various program names and file paths. + +use constant MONOTONE_VIZ => "monotone-viz"; +use constant UI_DIR => File::Spec->catfile(PREFIX_DIR, + "share", + APPLICATION_NAME, + "glade"); + +# The type of window that is going to be managed by this module. + +my $window_type = "main_window"; + +# The large logo used for the about dialog window. + +my $large_logo; + +# ***** FUNCTIONAL PROTOTYPES ***** + +# Private routines. + +sub about_activate_cb($$); +sub advanced_find_button_clicked_cb($$); +sub annotate_button_clicked_cb($$); +sub close_toolbutton_clicked_cb($$); +sub compare_arbitrary_revisions_activate_cb($$); +sub connect_to_server_activate_cb($$); +sub context_help_activate_cb($$); +sub create_browser_widgets(); +sub determine_mime_type($$$$); +sub directory_up_button_clicked_cb($$); +sub display_file($$); +sub file_change_history_button_clicked_cb($$); +sub find_files_button_clicked_cb($$); +sub get_browser_window(;$$$$$); +sub handle_advanced_find($); +sub help_on_window_activate_cb($$); +sub help_toolbutton_clicked_cb($$); +sub home_page_activate_cb($$); +sub main_window_delete_event_cb($$$); +sub manage_server_bookmarks_activate_cb($$); +sub manifest_browser_treeselection_changed_cb($$); +sub manifest_browser_treeview_row_activated_cb($$$$); +sub monotone_viz_button_clicked_cb($$); +sub mtn_db_locked_handler($$); +sub mtn_error_handler($$); +sub new_blank_menu_item_clicked_cb($$); +sub new_toolbutton_clicked_cb($$); +sub open_toolbutton_clicked_cb($$); +sub preferences_toolbutton_clicked_cb($$); +sub quit_activate_cb($$); +sub reload_toolbutton_clicked_cb($$); +sub revision_change_history_button_clicked_cb($$); +sub revision_change_log_button_clicked_cb($$); +sub save_as_button_clicked_cb($$); +sub search_text_button_clicked_cb($$); +sub setup_mtn_object($$); +sub setup_sigchld_handler($); +sub show_line_numbers_togglebutton_toggled_cb($$); +sub sigchld_handler(); +sub update_browser_state($$); +sub update_server_bookmarks_menu($); +sub view_button_clicked_cb($$); +# +############################################################################## +# +# Routine - Main Body Of Code +# +# Description - This is the main body of code for the mtn-browse script. +# +# Data - @_ : The command line arguments. +# Return Value : Unix exit code. +# +############################################################################## + + + +{ + + my($branch, + $browser, + $mtn, + $revision_id); + + # Initialise stuff. + + Gnome2::Program->init + (APPLICATION_NAME, + APPLICATION_VERSION, + undef, + app_datadir => File::Spec->catfile(PREFIX_DIR, "share")); + setup_sigchld_handler(\&sigchld_handler); + $glade_file = File::Spec->catfile(UI_DIR, "mtn-browse.glade"); + $tooltips = Gtk2::Tooltips->new(); + $line_image = Gtk2::Gdk::Pixbuf-> + new_from_file(File::Spec->catfile(UI_DIR, "line.png")); + + # Set up the default database locked and I/O wait handlers for the Monotone + # class. + + CachingAutomateStdio->register_db_locked_handler(\&mtn_db_locked_handler); + CachingAutomateStdio->register_io_wait_handler + (sub { WindowManager->instance()->update_gui(); }, 1); + + # Load in user preferences. + + eval + { + $user_preferences = load_preferences(); + $mime_match_table = + build_mime_match_table($user_preferences->{mime_table}); + }; + if ($@) + { + chomp($@); + my $dialog = Gtk2::MessageDialog->new + ($browser->{window}, + ["modal"], + "warning", + "close", + __x("Your preferences cannot be loaded:\n{error_message}", + error_message => $@)); + $dialog->run(); + $dialog->destroy(); + exit(1); + } + $mono_font = Gtk2::Pango::FontDescription-> + from_string($user_preferences->{fixed_font}); + + # Create the temporary working directory. + + eval + { + $tmp_dir = tempdir(APPLICATION_NAME . "_XXXXXXXXXX", + TMPDIR => 1, + CLEANUP => 1); + }; + if ($@) + { + my $dialog = Gtk2::MessageDialog->new + (undef, + ["modal"], + "error", + "close", + __x("{error_message}\nThis is fatal, I am going to exit.", + error_message => $@)); + $dialog->run(); + $dialog->destroy(); + exit(1); + } + + # Open a Monotone database. First attempt to open the current workspace's + # database. If this doesn't work or the database listed in the user's + # preferences file is to be used anyway then attempt to open that database + # instead. + + if ($user_preferences->{workspace}->{takes_precedence}) + { + + # Attempt to open the current workspace's database. If this works then + # get the workspace's details and then re-open the database but this + # time explicitly specifying the database itself. When given a database + # name, Monotone::AutomateStdio will make sure any current workspace + # will not `interfere' the with the mtn subprocess. One could use the + # "--no-workspace" option but this is only supported on later versions. + + eval + { + my $db; + $mtn = CachingAutomateStdio->new(); + $mtn->get_option(\$db, "database"); + if ($user_preferences->{workspace}->{auto_select}) + { + $mtn->get_option(\$branch, "branch"); + $mtn->get_base_revision_id(\$revision_id); + + # Unset the branch name if there is no revision associated with + # it (this can happen if we are in a new workspace on a new + # branch that doesn't exist in the database yet). + + $branch = undef if (! defined($revision_id)); + } + $mtn = undef; + $mtn = CachingAutomateStdio->new($db); + }; + + } + if (! defined($mtn) && $user_preferences->{default_mtn_db} ne "") + { + + # Attempt to open the database specified in the user's preferences. + + eval + { + $mtn = CachingAutomateStdio-> + new($user_preferences->{default_mtn_db}); + }; + if ($@) + { + my $dialog = Gtk2::MessageDialog->new + (undef, + ["modal"], + "warning", + "close", + __x("Cannot open database {database_name}.", + database_name => $user_preferences->{default_mtn_db})); + $dialog->run(); + $dialog->destroy(); + } + + } + + # Set up the error handlers for the Monotone class. + + CachingAutomateStdio->register_error_handler(MTN_SEVERITY_ALL, + \&mtn_error_handler); + + # Setup the character encodings and Monotone authentication keys lists, + # these both need to be set up before creating any windows. + + @encodings = sort(Encode->encodings(":all")); + if (defined($mtn)) + { + my @list; + $mtn->keys(address@hidden); + foreach my $key (@list) + { + my $keystore; + foreach my $location (@{$key->{private_location}}) + { + if ($location eq "keystore") + { + $keystore = 1; + last; + } + } + if ($keystore) + { + my $name; + if (exists($key->{name})) + { + $name = $key->{name}; + } + else + { + $name = $key->{local_name}; + } + eval + { + $name = decode_utf8($name, Encode::FB_CROAK); + }; + push(@keys, $name); + } + } + } + else + { + my $buffer; + if (run_command(\$buffer, undef, "mtn", "automate", "keys")) + { + my $name; + my @lines = split(/\n/, $buffer); + foreach my $line (@lines) + { + if ($line =~ m/^\s+(local_)?name\s+\"(.+)\"$/) + { + $name = $2; + } + elsif ($line =~ m/^\s*private_location\s+\"keystore\"$/) + { + eval + { + $name = decode_utf8($name, Encode::FB_CROAK); + }; + push(@keys, $name); + } + } + } + } + + # Create the browser window and display it. Please note that updating the + # browser to reflect the current database or workspace is done in an idle + # handler so that control can be handed over to Gtk2 before updating the + # display. + + $browser = get_browser_window(); + if (defined($mtn)) + { + setup_mtn_object($mtn, $browser->{window}); + $browser->{mtn} = $mtn; + $browser->{connect_to_server_menutiem}->set_sensitive(FALSE) + if (! $user_preferences->{remote_connections}); + Glib::Idle->add + (sub { + my $browser = $_[0]; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + if (defined($branch)) + { + $browser->{branch_combo_details}->{preset} = 1; + $browser->{branch_combo_details}->{value} = $branch; + $browser->{revision_combo_details}->{preset} = 1; + $browser->{revision_combo_details}->{value} = + $revision_id; + $browser->{tagged_checkbutton}->set_active(FALSE); + } + &{$browser->{update_handler}}($browser, DATABASE_CHANGED); + + return FALSE; + }, + $browser); + } + $mtn = undef; + + # Setup the callback for the accelerator key for all windows. + + WindowManager->instance()->help_connect(undef, + undef, + sub { display_help(); }); + + # Hand control over to Gtk2. + + Gtk2->main(); + + # Cleanup. + + WindowManager->instance()->cleanup(); + Gnome2::VFS->shutdown(); + $SIG{CHLD} = "IGNORE"; + + exit(0); + +} +# +############################################################################## +# +# Routine - connect_to_server_activate_cb +# +# Description - Callback routine called when the user selects a server +# bookmark menu option. +# +# Data - $widget : The widget object that received the signal. +# $details : A record containing the browser instance that is +# associated with this widget and the name of the +# server that is to be connected to. +# +############################################################################## + + + +sub connect_to_server_activate_cb($$) +{ + + my($widget, $details) = @_; + + return if ($details->{browser}->{in_cb}); + local $details->{browser}->{in_cb} = 1; + + my($exception, + $host, + $mtn); + my $browser = $details->{browser}; + my $wm = WindowManager->instance(); + + $wm->make_busy($browser, 1); + $browser->{appbar}->push($browser->{appbar}->get_status()->get_text()); + $wm->update_gui(); + + # Try connecting to the specified server. + + $host = $details->{server}; + $host =~ s/:\d+$//g; + CachingAutomateStdio->register_error_handler + (MTN_SEVERITY_ALL, + sub { + my($severity, $message) = @_; + my $dialog; + $message =~ s/mtn: warning: //g; + $message =~ s/mtn: error: //g; + $message =~ s/mtn: //g; + $message =~ s/^Corrupt\/missing mtn [^\n]+\n//g; + $message =~ s/ at .+ line \d+$//g; + $message =~ s/\s+$//g; + $message =~ s/\n/ /g; + $message .= "." unless ($message =~ m/.+\.$/); + $dialog = Gtk2::MessageDialog->new_with_markup + ($browser->{window}, + ["modal"], + "warning", + "close", + __x("There is a problem connecting to the server, the " + . "details are:\n{error_message}", + error_message => + Glib::Markup::escape_text($message))); + $wm->allow_input(sub { $dialog->run(); }); + $dialog->destroy(); + die("Bad open"); + }); + $browser->{appbar}->set_status(__x("Connecting to server `{server}'", + server => $host)); + eval + { + if (defined($mtn_key)) + { + $mtn = CachingAutomateStdio->new_from_service + ($details->{server}, ["--key" => $mtn_key]); + } + else + { + $mtn = CachingAutomateStdio->new_from_service($details->{server}); + } + + }; + $exception = $@; + CachingAutomateStdio->register_error_handler + (MTN_SEVERITY_ALL, \&mtn_error_handler); + if (! $exception) + { + + # Seems to be ok so set up the browser. + + setup_mtn_object($mtn, $browser->{window}); + $browser->{mtn} = $mtn; + $browser->{appbar}->pop(); + $browser->{appbar}->clear_stack(); + &{$browser->{update_handler}}($browser, DATABASE_CHANGED); + + } + else + { + $browser->{appbar}->pop(); + } + + $wm->make_busy($browser, 0); + +} +# +############################################################################## +# +# Routine - manage_server_bookmarks_activate_cb +# +# Description - Callback routine called when the user selects the manage +# server bookmarks menu option. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub manage_server_bookmarks_activate_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + if (manage_server_bookmarks($browser->{window}, + $user_preferences->{server_bookmarks})) + { + + eval + { + save_preferences($user_preferences); + }; + if ($@) + { + chomp($@); + my $dialog = Gtk2::MessageDialog->new + (undef, + ["modal"], + "warning", + "close", + __("Your preferences could not be saved:\n") . $@); + $dialog->run(); + $dialog->destroy(); + } + + # We now need to update all server bookmark menus. + + WindowManager->instance()->cond_find + ($window_type, + sub { + my $browser = $_[0]; + update_server_bookmarks_menu($browser); + return; + }); + + } + +} +# +############################################################################## +# +# Routine - quit_activate_cb +# +# Description - Callback routine called when the user selects the quit menu +# option. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub quit_activate_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + # Hide all the windows, close all the database handles and then exit Gtk2. + + WindowManager->instance()->cond_find + (undef, + sub { + my $instance = $_[0]; + $instance->{window}->hide() if ($instance->{window}->mapped()); + $instance->{mtn} = undef if (exists($instance->{mtn})); + return; + }); + Gtk2->main_quit(); + +} +# +############################################################################## +# +# Routine - compare_arbitrary_revisions_activate_cb +# +# Description - Callback routine called when the user selects the compare +# arbitrary revisions menu option. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub compare_arbitrary_revisions_activate_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + display_arbitrary_revision_comparison($browser->{mtn}); + +} +# +############################################################################## +# +# Routine - help_on_window_activate_cb +# +# Description - Callback routine called when the user selects the help on +# window menu option. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub help_on_window_activate_cb($$) +{ + + my($widget, $browser) = @_; + + # No need to protect against recursion here as this simply activates a help + # callback which deals with it. + + WindowManager->instance()->display_window_help($browser); + +} +# +############################################################################## +# +# Routine - context_help_activate_cb +# +# Description - Callback routine called when the user selects the context +# help menu option. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub context_help_activate_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + WindowManager->instance()->activate_context_sensitive_help(1); + +} +# +############################################################################## +# +# Routine - home_page_activate_cb +# +# Description - Callback routine called when the user selects the home page +# menu option. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub home_page_activate_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + eval + { + display_html("http://www.coosoft.plus.com/software.html"); + }; + if ($@) + { + my $dialog = Gtk2::MessageDialog->new_with_markup + ($browser->{window}, + ["modal"], + "warning", + "close", + __x("Gnome cannot display Monotone Browser's\n" + . "home page. Gnome gave:\n" + . "{gnome_error_message}", + gnome_error_message => $@)); + $dialog->run(); + $dialog->destroy(); + } + +} +# +############################################################################## +# +# Routine - about_activate_cb +# +# Description - Callback routine called when the user selects the about +# menu option. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub about_activate_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + $large_logo = Gtk2::Gdk::Pixbuf->new_from_file + (File::Spec->catfile(UI_DIR, "mtn-browse-large.png")) + if (! defined($large_logo)); + Gnome2::About->new + (APPLICATION_NAME, + APPLICATION_VERSION, + __("Copyright \xa9 2007-2010 Anthony Cooper"), + __("A graphical front-end browser for Monotone VCS databases"), + ["Anthony Cooper "], + __("Anthony Cooper "), + __("Thomas Keller (German translation)"), + $large_logo)->run(); + +} +# +############################################################################## +# +# Routine - new_toolbutton_clicked_cb +# +# Description - Callback routine called when the user clicks on the new +# toolbutton in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub new_toolbutton_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my @revision_ids; + + # Simply get a new/unused browser window and display it. + + if (! defined($browser->{mtn})) + { + get_browser_window(); + } + elsif (! $browser->{branch_combo_details}->{complete}) + { + get_browser_window($browser->{mtn}); + } + elsif (! $browser->{revision_combo_details}->{complete}) + { + get_browser_window($browser->{mtn}, + $browser->{branch_combo_details}->{value}); + } + else + { + get_revision_ids($browser, address@hidden); + get_browser_window($browser->{mtn}, + $browser->{branch_combo_details}->{value}, + $revision_ids[0]); + } + +} +# +############################################################################## +# +# Routine - new_blank_menu_item_clicked_cb +# +# Description - Callback routine called when the user clicks on the new +# blank menu item in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub new_blank_menu_item_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + # Simply get a new/unused browser window and display it. + + get_browser_window(); + +} +# +############################################################################## +# +# Routine - open_toolbutton_clicked_cb +# +# Description - Callback routine called when the user clicks on the open +# toolbutton in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub open_toolbutton_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my $mtn; + + if (open_database($browser->{window}, \$mtn, undef)) + { + setup_mtn_object($mtn, $browser->{window}); + $browser->{mtn} = $mtn; + $browser->{appbar}->clear_stack(); + &{$browser->{update_handler}}($browser, DATABASE_CHANGED); + } + +} +# +############################################################################## +# +# Routine - close_toolbutton_clicked_cb +# +# Description - Callback routine called when the user clicks on the close +# toolbutton in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub close_toolbutton_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + # Simply reset the browser's Monotone instance and update its display. + + $browser->{mtn} = undef; + $browser->{appbar}->clear_stack(); + &{$browser->{update_handler}}($browser, DATABASE_CHANGED); + +} +# +############################################################################## +# +# Routine - reload_toolbutton_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# reload toolbutton in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub reload_toolbutton_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + # Simply refresh the entire browser. + + if ($browser->{branch_combo_details}->{complete}) + { + $browser->{branch_combo_details}->{preset} = 1; + if ($browser->{revision_combo_details}->{complete}) + { + $browser->{revision_combo_details}->{preset} = 1; + if ($browser->{directory_combo_details}->{complete}) + { + $browser->{directory_combo_details}->{preset} = 1; + if (exists($browser->{file_being_viewed}->{manifest_entry})) + { + $browser->{file_being_viewed_preset_value} = + $browser->{file_being_viewed}->{short_name}; + } + } + } + } + $browser->{mtn}->closedown(); + $browser->{appbar}->clear_stack(); + &{$browser->{update_handler}}($browser, ALL_CHANGED); + +} +# +############################################################################## +# +# Routine - preferences_toolbutton_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# preferences toolbutton in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub preferences_toolbutton_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + if (preferences($browser)) + { + $user_preferences = load_preferences(); + $mime_match_table = + build_mime_match_table($user_preferences->{mime_table}); + } + +} +# +############################################################################## +# +# Routine - help_toolbutton_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# help toolbutton in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub help_toolbutton_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + display_help(); + +} +# +############################################################################## +# +# Routine - advanced_find_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# advanced find button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub advanced_find_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + handle_advanced_find($browser); + +} +# +############################################################################## +# +# Routine - revision_change_history_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# revision change history button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub revision_change_history_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my(@revision_ids, + $tag); + + get_revision_ids($browser, address@hidden, \$tag); + display_revision_change_history($browser->{mtn}, $tag, $revision_ids[0]); + +} +# +############################################################################## +# +# Routine - revision_change_log_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# revision change log button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub revision_change_log_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my(@revision_ids, + $tag); + + # Get the currently selected revision id and then display its change log. + + get_revision_ids($browser, address@hidden, \$tag); + display_change_log($browser->{mtn}, $revision_ids[0], "", $tag); + +} +# +############################################################################## +# +# Routine - monotone_viz_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# monotone-viz button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub monotone_viz_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my($cmd, + $db_name, + @revision_ids); + + # Make sure we have monotone-viz installed first. + + return unless (program_valid(MONOTONE_VIZ, $browser->{window})); + + # Build up the monotone-viz command, we need the database name and then + # optionally the branch and revision ids. + + $cmd = MONOTONE_VIZ . " "; + if (! defined($db_name = $browser->{mtn}->get_db_name())) + { + $browser->{mtn}->get_option(\$db_name, "database"); + } + $cmd .= $db_name; + if ($browser->{branch_combo_details}->{complete}) + { + $cmd .= " " . $browser->{branch_combo_details}->{value}; + if ($browser->{revision_combo_details}->{complete}) + { + get_revision_ids($browser, address@hidden); + $cmd .= " " . $revision_ids[0]; + } + } + + # Launch Monotone-Viz. + + system($cmd . " &"); + +} +# +############################################################################## +# +# Routine - directory_up_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the up +# directory button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub directory_up_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my $value; + + # Simply go up one directory level in the manifest if we aren't already at + # the top. + + $value = $browser->{directory_combo_details}->{value}; + if ($value ne "") + { + + my $wm = WindowManager->instance(); + + $wm->make_busy($browser, 1); + $browser->{appbar}->set_status(""); + $wm->update_gui(); + + $value = dirname($value); + $value = "" if ($value eq "."); + $browser->{directory_combo_details}->{value} = $value; + $browser->{directory_combo_details}->{complete} = 1; + + # Only update the comboboxentry's pulldown list if that is what the + # user wants. + + if (! $user_preferences->{static_lists}) + { + + my($counter, + @directory_list, + $len, + $update_interval); + + $len = length($value); + $browser->{appbar}->set_progress_percentage(0); + $browser->{appbar}->set_status(__("Populating directory list")); + $wm->update_gui(); + foreach my $item (@{$browser->{directory_combo_details}->{list}}) + { + push(@directory_list, $item) + if ($value eq substr($item, 0, $len)); + } + $counter = 1; + $update_interval = calculate_update_interval(address@hidden); + $browser->{directory_comboboxentry}->get_model()->clear(); + $browser->{directory_comboboxentry}->child()->set_text($value); + $browser->{directory_comboboxentry}->child()->set_position(-1); + foreach my $item (@directory_list) + { + $browser->{directory_comboboxentry}->append_text($item); + if (($counter % $update_interval) == 0) + { + $browser->{appbar}->set_progress_percentage + ($counter / scalar(@directory_list)); + $wm->update_gui(); + } + ++ $counter; + } + $browser->{appbar}->set_progress_percentage(1); + $wm->update_gui(); + $browser->{appbar}->set_progress_percentage(0); + + } + else + { + $browser->{directory_comboboxentry}->child()->set_text($value); + $browser->{directory_comboboxentry}->child()->set_position(-1); + } + + $browser->{appbar}->clear_stack(); + $wm->update_gui(); + + &{$browser->{update_handler}}($browser, DIRECTORY_CHANGED); + + $wm->make_busy($browser, 0); + + } + +} +# +############################################################################## +# +# Routine - find_files_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the find +# files button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub find_files_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my(@revision_ids, + $tag); + + get_revision_ids($browser, address@hidden, \$tag); + display_find_files($browser->{mtn}, + $tag, + $revision_ids[0], + $browser->{manifest}, + $browser->{directory_combo_details}->{value}); + +} +# +############################################################################## +# +# Routine - show_line_numbers_togglebutton_toggled_cb +# +# Description - Callback routine called when the user toggles the show line +# numbers button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub show_line_numbers_togglebutton_toggled_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + $browser->{file_view_sv}->set_show_line_numbers($widget->get_active()); + +} +# +############################################################################## +# +# Routine - search_text_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the search +# text button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub search_text_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + find_text($browser->{window}, $browser->{file_view_sv}); + +} +# +############################################################################## +# +# Routine - save_as_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the save as +# button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub save_as_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my $data; + + $browser->{mtn}->get_file(\$data, + $browser->{file_being_viewed}->{manifest_entry}-> + {file_id}); + save_as_file($browser->{window}, + $browser->{file_being_viewed}->{short_name}, + \$data); + +} +# +############################################################################## +# +# Routine - view_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the view +# button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub view_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my($app, + $data, + $fh, + $file_name, + $helper, + $mime_details, + $mime_obj, + $mime_type); + + # Generate temporary disk file name. + + if (! defined($file_name = + generate_tmp_path($browser->{file_being_viewed}-> + {short_name}))) + { + my $dialog = Gtk2::MessageDialog->new + ($browser->{window}, + ["modal"], + "warning", + "close", + __x("Cannot generate temporary file name:\n{error_message}.", + error_message => $!)); + $dialog->run(); + $dialog->destroy(); + return; + } + + # Attempt to save the contents to the file. + + if (! defined($fh = IO::File->new($file_name, "w"))) + { + my $dialog = Gtk2::MessageDialog->new + ($browser->{window}, + ["modal"], + "warning", + "close", + __x("{error_message}.", error_message => $!)); + $dialog->run(); + $dialog->destroy(); + return; + } + binmode($fh); + $browser->{mtn}->get_file(\$data, + $browser->{file_being_viewed}-> + {manifest_entry}->{file_id}); + $fh->print($data); + $fh->close(); + $fh = undef; + + # Get the user preference settings for this type of file. + + determine_mime_type($browser->{file_being_viewed}->{short_name}, + \$data, + \$mime_type, + \$mime_details); + + # If the user has specified a helper application then use that to view the + # file, otherwise use the default desktop settings. + + if (defined($mime_details) && $mime_details->{helper_application} ne "") + { + + # Use the specified helper application (having validated it first), + # replacing `{file}' with the real file name. + + $helper = $mime_details->{helper_application}; + return unless (program_valid((split(/[[:blank:]]/, $helper))[0], + $browser->{window})); + if ($helper =~ m/\{file\}/) + { + $helper =~ s/\{file\}/$file_name/g; + } + else + { + $helper .= " " . $file_name; + } + + # Launch it. + + system($helper . " &"); + + } + else + { + + # Use the desktop to load the file. + + # Use the appropriate application for this type of file, defaulting to + # Vi if necessary. + + if (! defined($mime_type = + Gnome2::VFS->get_mime_type("file://" . $file_name))) + { + my $dialog = Gtk2::MessageDialog->new + ($browser->{window}, + ["modal"], + "warning", + "close", + __("Unknown file type, not viewing.")); + $dialog->run(); + $dialog->destroy(); + return; + } + $app = $mime_obj->get_default_application() + if (defined($mime_obj = Gnome2::VFS::Mime::Type->new($mime_type))); + if (defined($app)) + { + + # Use the command attribute if it is present rather than the + # launch() method as this can crash on some systems. + + if (exists($app->{command})) + { + system($app->{command} . " " . $file_name . " &"); + } + else + { + my $status; + if (($status = $app->launch("file://" . $file_name)) ne "ok") + { + my $dialog = Gtk2::MessageDialog->new_with_markup + ($browser->{window}, + ["modal"], + "warning", + "close", + __x("Gnome cannot launch the helper application\n" + . "for MIME type {mime_type}.\nGnome gave:\n" + . "{gnome_error_message}", + mime_type => $mime_type, + gnome_error_message => $status)); + $dialog->run(); + $dialog->destroy(); + } + } + } + else + { + my $dialog = Gtk2::MessageDialog->new + ($browser->{window}, + ["modal"], + "info", + "close", + __x("No application is associated with\n" + . "MIME type {mime_type},\nusing Vi instead.", + mime_type => $mime_type)); + $dialog->run(); + $dialog->destroy(); + system("xterm -e vi " . $file_name . " &"); + } + + } + +} +# +############################################################################## +# +# Routine - annotate_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the +# annotate button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub annotate_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my @revision_ids; + + get_revision_ids($browser, address@hidden); + display_annotation($browser->{mtn}, + $revision_ids[0], + $browser->{file_being_viewed}->{manifest_entry}-> + {name}); + +} +# +############################################################################## +# +# Routine - file_change_history_button_clicked_cb +# +# Description - Callback routine called when the user clicks on the file +# change history button in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub file_change_history_button_clicked_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my @revision_ids; + + get_revision_ids($browser, address@hidden); + display_file_change_history($browser->{mtn}, + $revision_ids[0], + $browser->{file_being_viewed}-> + {manifest_entry}->{name}); + +} +# +############################################################################## +# +# Routine - manifest_browser_treeselection_changed_cb +# +# Description - Callback routine called when the user selects an entry in +# the manifest treeview in a main browser window. +# +# Data - $widget : The widget object that received the signal. +# $browser : The browser instance that is associated with +# this widget. +# +############################################################################## + + + +sub manifest_browser_treeselection_changed_cb($$) +{ + + my($widget, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my($manifest_entry, + $short_name); + + # Get the manifest entry details for the item that was selected. + + if ($widget->count_selected_rows() > 0) + { + my($iter, + $model); + ($model, $iter) = $widget->get_selected(); + $short_name = $model->get($iter, MLS_NAME_COLUMN); + $manifest_entry = $model->get($iter, MLS_MANIFEST_ENTRY_COLUMN); + } + + # If the item is a file then display its contents, otherwise if it is a + # directory then just ignore it. + + if (defined($manifest_entry) && $manifest_entry->{type} eq "file") + { + $browser->{file_being_viewed} = {short_name => $short_name, + manifest_entry => $manifest_entry}; + $browser->{appbar}->clear_stack(); + &{$browser->{update_handler}}($browser, FILE_CHANGED); + } + +} +# +############################################################################## +# +# Routine - manifest_browser_treeview_row_activated_cb +# +# Description - Callback routine called when the user double clicks on an +# entry in the manifest treeview in a main browser 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. +# $browser : The browser instance that is associated +# with this widget. +# +############################################################################## + + + +sub manifest_browser_treeview_row_activated_cb($$$$) +{ + + my($widget, $tree_path, $tree_view_column, $browser) = @_; + + return if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my $manifest_entry; + + # Get the manifest entry details for the item that was double-clicked. + + $widget->get_selection()->selected_foreach + (sub { + my($model, $path, $iter) = @_; + $manifest_entry = $model->get($iter, MLS_MANIFEST_ENTRY_COLUMN); + }); + + # If the item is a directory then change to it, otherwise if it is a file + # then just ignore it. + + if (defined($manifest_entry) && $manifest_entry->{type} eq "directory") + { + $browser->{directory_combo_details}->{value} = $manifest_entry->{name}; + $browser->{directory_combo_details}->{complete} = 1; + $browser->{directory_comboboxentry}->child()-> + set_text($manifest_entry->{name}); + $browser->{directory_comboboxentry}->child()->set_position(-1); + $browser->{appbar}->clear_stack(); + &{$browser->{update_handler}}($browser, DIRECTORY_CHANGED); + } + +} +# +############################################################################## +# +# Routine - main_window_delete_event_cb +# +# Description - Callback routine called when a main window is about to be +# dismissed. +# +# Data - $widget : The widget object that received the signal. +# $event : A Gtk2::Gdk::Event object describing the +# event that has occurred. +# $browser : The browser instance that is associated with +# this widget. +# Return Value : TRUE if the event has been handled and needs +# no further handling, otherwise FALSE if the +# event should carry on through the remaining +# event handling. +# +############################################################################## + + + +sub main_window_delete_event_cb($$$) +{ + + my($widget, $event, $browser) = @_; + + return TRUE if ($browser->{in_cb}); + local $browser->{in_cb} = 1; + + my $others_mapped; + + # Only exit if this is the only browser window currently being shown. + + $others_mapped = 0; + WindowManager->instance()->cond_find + ($window_type, + sub { + my $instance = $_[0]; + if ($instance != $browser && $instance->{window}->mapped()) + { + $others_mapped = 1; + return 1; + } + return; + }); + + hide_find_text($browser->{file_view_sv}); + $browser->{window}->hide(); + $browser->{mtn} = undef; + + # Flush out any cached information. + + $browser->{branch_combo_details}->{preset} = 0; + $browser->{revision_combo_details}->{preset} = 0; + $browser->{directory_combo_details}->{preset} = 0; + $browser->{file_being_viewed_preset_value} = ""; + &{$browser->{update_handler}}($browser, DATABASE_CHANGED); + + Gtk2->main_quit() unless ($others_mapped); + + return TRUE; + +} +# +############################################################################## +# +# Routine - get_browser_window +# +# Description - Creates or prepares an existing browser window for use. +# +# Data - $mtn : The Monotone::AutomateStdio object that is +# to be used for the browser window. If it is +# undef then no database is used and a blank +# browser window is displayed. +# $branch : The branch name that is to be preselected in +# the browser window. This is optional unless +# any of the following arguments are +# specified. +# $revision_id : The revision id that is to be preselected in +# the browser window. This is optional unless +# any of the following arguments are +# specified. +# $directory : The directory that is to be preselected in +# the browser window. This is optional unless +# the following argument is specified. +# $file : The file that is to be displayed in the +# browser window. This is optional. +# Return Value : A reference to the newly created or unused +# browser instance record. +# +############################################################################## + + + +sub get_browser_window(;$$$$$) +{ + + my($mtn, $branch, $revision_id, $directory, $file) = @_; + + my $browser; + my $wm = WindowManager->instance(); + + # Create a new browser window if an unused one wasn't found, otherwise + # reuse an existing unused one. + + if (! defined($browser = $wm->find_unused($window_type))) + { + + my($cb, + $image, + $menu_item, + $renderer, + $submenu, + $subsubmenu, + $tv_column); + + $browser = {}; + $browser->{mtn} = $mtn; + $browser->{glade} = create_browser_widgets(); + + # Flag to stop recursive calling of callbacks. + + $browser->{in_cb} = 0; + local $browser->{in_cb} = 1; + + # Connect Glade registered signal handlers. + + glade_signal_autoconnect($browser->{glade}, $browser); + + # Link in the update handler for the browser. + + $browser->{update_handler} = \&update_browser_state; + + # Get the widgets that we are interested in. + + $browser->{window} = $browser->{glade}->get_widget($window_type); + foreach my $widget ("appbar", + "connect_to_server_menutiem", + "close_menuitem", + "file_encoding_menuitem", + "choose_authentication_key_menuitem", + "compare_arbitrary_revisions_menuitem", + "main_vbox", + "browser_hpaned", + "close_toolbutton", + "reload_toolbutton", + "branch_comboboxentry", + "revision_comboboxentry", + "tagged_checkbutton", + "monotone_viz_button", + "directory_comboboxentry", + "directory_up_button", + "find_files_button", + "show_line_numbers_togglebutton", + "annotate_button", + "manifest_browser_treeview", + "file_view_scrolledwindow", + "file_name_value_label", + "file_author_value_label", + "last_update_value_label", + "file_revision_id_value_label", + "file_button_vbox", + "database_name_value_label", + "date_value_label", + "author_value_label", + "change_log_value_label") + { + $browser->{$widget} = $browser->{glade}->get_widget($widget); + } + + # Setup button sensitivity groups. + + $browser->{text_file_sensitive_group} = []; + foreach my $item ("search_text", "annotate") + { + push(@{$browser->{text_file_sensitive_group}}, + $browser->{glade}->get_widget($item . "_button")); + } + $browser->{revision_sensitive_group} = []; + foreach my $item ("revision_change_history", "revision_change_log") + { + push(@{$browser->{revision_sensitive_group}}, + $browser->{glade}->get_widget($item . "_button")); + } + + # Setup the connect to server submenu. + + update_server_bookmarks_menu($browser); + + # Setup the file encodings submenu. + + $submenu = Gtk2::Menu->new(); + $cb = sub + { + my($widget, $details) = @_; + return if ($details->{instance}->{in_cb}); + local $details->{instance}->{in_cb} = 1; + $details->{instance}->{appbar}->set_status + (__x("Changed file encoding from `{old_encoding}' to " + . "`{new_encoding}'", + old_encoding => __($file_encoding), + new_encoding => __($details->{encoding}))); + $file_encoding = $details->{encoding}; + }; + for my $encoding ("ascii", "utf8", "UTF-16") + { + $menu_item = Gtk2::MenuItem->new_with_label(__($encoding)); + $menu_item->signal_connect("activate", + $cb, + {instance => $browser, + encoding => $encoding}); + $menu_item->show(); + $submenu->append($menu_item); + } + $subsubmenu = Gtk2::Menu->new(); + for my $encoding (@encodings) + { + $menu_item = Gtk2::MenuItem->new_with_label(__($encoding)); + $menu_item->signal_connect("activate", + $cb, + {instance => $browser, + encoding => $encoding}); + $menu_item->show(); + $subsubmenu->append($menu_item); + } + $subsubmenu->show(); + $menu_item = Gtk2::MenuItem->new(__("All Encodings")); + $menu_item->set_submenu($subsubmenu); + $menu_item->show(); + $submenu->append($menu_item); + $submenu->show(); + $browser->{file_encoding_menuitem}->set_submenu($submenu); + + # Setup the authentication keys submenu. + + $submenu = Gtk2::Menu->new(); + $cb = sub + { + my($widget, $details) = @_; + return if ($details->{instance}->{in_cb}); + local $details->{instance}->{in_cb} = 1; + $details->{instance}->{appbar}->set_status + (__x("Changed authentication key from `{old_key}' to " + . "`{new_key}'", + old_key => defined($mtn_key) + ? $mtn_key : __("Unspecified"), + new_key => defined($details->{key}) + ? $details->{key} : __("Unspecified"))); + $mtn_key = $details->{key}; + }; + $menu_item = Gtk2::MenuItem->new(__("Unspecified")); + $menu_item->signal_connect("activate", + $cb, + {instance => $browser, + key => undef}); + $menu_item->show(); + $submenu->append($menu_item); + $menu_item = Gtk2::SeparatorMenuItem->new(); + $menu_item->show(); + $submenu->append($menu_item); + if (scalar(@keys) > 0) + { + for my $key (@keys) + { + $menu_item = Gtk2::MenuItem->new_with_label($key); + $menu_item->signal_connect("activate", + $cb, + {instance => $browser, + key => $key}); + $menu_item->show(); + $submenu->append($menu_item); + } + } + else + { + $menu_item = Gtk2::MenuItem->new(__("No Keys")); + $menu_item->set_sensitive(FALSE); + $menu_item->show(); + $submenu->append($menu_item); + } + $submenu->show(); + $browser->{choose_authentication_key_menuitem}->set_submenu($submenu); + + # Setup button sensitivity groups. + + $browser->{text_file_sensitive_group} = []; + foreach my $item ("search_text", "annotate") + { + push(@{$browser->{text_file_sensitive_group}}, + $browser->{glade}->get_widget($item . "_button")); + } + $browser->{revision_sensitive_group} = []; + foreach my $item ("revision_change_history", "revision_change_log") + { + push(@{$browser->{revision_sensitive_group}}, + $browser->{glade}->get_widget($item . "_button")); + } + + # Setup the comboboxes. + + $browser->{branch_comboboxentry}-> + set_model(Gtk2::ListStore->new("Glib::String")); + $browser->{branch_comboboxentry}->set_text_column(0); + $browser->{branch_comboboxentry}->set_wrap_width(2); + $browser->{revision_comboboxentry}-> + set_model(Gtk2::ListStore->new("Glib::String")); + $browser->{revision_comboboxentry}->set_text_column(0); + $browser->{revision_comboboxentry}->set_wrap_width(2); + $browser->{directory_comboboxentry}-> + set_model(Gtk2::ListStore->new("Glib::String")); + $browser->{directory_comboboxentry}->set_text_column(0); + $browser->{directory_comboboxentry}->set_wrap_width(2); + + # Setup the tree view manifest file browser. + + $browser->{manifest_liststore} = Gtk2::ListStore->new("Glib::String", + "Glib::String", + "Glib::String", + "Glib::String", + "Glib::Scalar"); + $browser->{manifest_browser_treeview}-> + set_model($browser->{manifest_liststore}); + + $tv_column = Gtk2::TreeViewColumn->new(); + $image = Gtk2::Image->new_from_stock("gtk-file", "menu"); + $image->show_all(); + $tv_column->set_widget($image); + $tv_column->set_resizable(FALSE); + $tv_column->set_sizing("fixed"); + $tv_column->set_fixed_width(25); + $tv_column->set_sort_column_id(MLS_ICON_COLUMN); + $renderer = Gtk2::CellRendererPixbuf->new(); + $tv_column->pack_start($renderer, TRUE); + $tv_column->set_attributes($renderer, "stock-id" => MLS_ICON_COLUMN); + $browser->{manifest_browser_treeview}->append_column($tv_column); + + $tv_column = Gtk2::TreeViewColumn->new(); + $tv_column->set_title(__("File Name")); + $tv_column->set_resizable(TRUE); + $tv_column->set_sizing("fixed"); + $tv_column->set_fixed_width(180); + $tv_column->set_sort_column_id(MLS_NAME_COLUMN); + $renderer = Gtk2::CellRendererText->new(); + $tv_column->pack_start($renderer, TRUE); + $tv_column->set_attributes($renderer, "text" => MLS_NAME_COLUMN); + $browser->{manifest_browser_treeview}->append_column($tv_column); + + # Only set up the remaining columns if that is what the user wants. + + if ($user_preferences->{show_file_details}) + { + $tv_column = Gtk2::TreeViewColumn->new(); + $tv_column->set_title(__("Last Update")); + $tv_column->set_resizable(TRUE); + $tv_column->set_sizing("grow-only"); + $tv_column->set_sort_column_id(MLS_DATE_COLUMN); + $renderer = Gtk2::CellRendererText->new(); + $tv_column->pack_start($renderer, TRUE); + $tv_column->set_attributes($renderer, "text" => MLS_DATE_COLUMN); + $browser->{manifest_browser_treeview}->append_column($tv_column); + + $tv_column = Gtk2::TreeViewColumn->new(); + $tv_column->set_title(__("Author")); + $tv_column->set_resizable(TRUE); + $tv_column->set_sizing("grow-only"); + $tv_column->set_sort_column_id(MLS_AUTHOR_COLUMN); + $renderer = Gtk2::CellRendererText->new(); + $tv_column->pack_start($renderer, TRUE); + $tv_column->set_attributes($renderer, "text" => MLS_AUTHOR_COLUMN); + $browser->{manifest_browser_treeview}->append_column($tv_column); + + treeview_setup_search_column_selection + ($browser->{manifest_browser_treeview}, 1 .. 3); + } + $browser->{show_file_details} = $user_preferences->{show_file_details}; + + $browser->{manifest_browser_treeview}-> + set_search_column(MLS_NAME_COLUMN); + $browser->{manifest_browser_treeview}-> + set_search_equal_func(\&treeview_column_searcher); + + $browser->{manifest_browser_treeview}->get_selection()-> + signal_connect("changed", + \&manifest_browser_treeselection_changed_cb, + $browser); + + # Setup the file file viewer (with syntax highlighting). + + $browser->{file_view_svbuffer} = Gtk2::SourceView::Buffer->new(undef); + $browser->{file_view_svbuffer}->set_max_undo_levels(0); + $browser->{file_view_svbuffer}->begin_not_undoable_action(); + $browser->{file_view_svlangmgr} = + Gtk2::SourceView::LanguagesManager->new(); + $browser->{file_view_sv} = Gtk2::SourceView::View-> + new_with_buffer($browser->{file_view_svbuffer}); + $browser->{file_view_sv}->modify_font($mono_font); + $browser->{file_view_sv}->set_cursor_visible(FALSE); + $browser->{file_view_sv}->set_editable(FALSE); + $browser->{file_view_scrolledwindow}->add($browser->{file_view_sv}); + $browser->{file_view_sv_populate_popup_handler} = + $browser->{file_view_sv}->signal_connect + ("populate_popup", + \&find_text_textview_populate_popup_cb, + $browser); + $browser->{file_view_sv_key_press_handler} = + $browser->{file_view_sv}->signal_connect + ("key_press_event", + \&find_text_textview_key_press_event_cb, + $browser); + $browser->{file_view_sv}->show_all(); + + # Enable the showing of line numbers by default if that is what the + # user wants. + + if ($user_preferences->{show_line_numbers}) + { + $browser->{show_line_numbers_togglebutton}->set_active(TRUE); + $browser->{file_view_sv}->set_show_line_numbers(TRUE); + } + + # Display the window. + + $browser->{window}->show_all(); + $browser->{window}->present(); + + # Register the window for management and set up the help callbacks. + + $wm->manage($browser, $window_type, $browser->{window}); + register_help_callbacks + ($browser, + {widget => "menubar_bonobodockitem", + help_ref => __("mtnb-gsc-menus")}, + {widget => "toolbar_bonobodockitem", + help_ref => __("mtnb-gsc-tool-bar")}, + {widget => "view_frame", + help_ref => __("mtnb-gsc-selecting-a-branch-and-revision")}, + {widget => "view_button_hbox", + help_ref => __("mtnb-gsc-browser-buttons")}, + {widget => "revision_browser_frame", + help_ref => __("mtnb-gsc-selecting-a-file-to-display")}, + {widget => "directory_button_hbox", + help_ref => __("mtnb-gsc-browser-buttons")}, + {widget => "file_viewer_frame", + help_ref => __("mtnb-gsc-selecting-a-file-to-display")}, + {widget => "file_details_hbox", + help_ref => __("mtnb-gsc-information-fields")}, + {widget => "file_button_vbox", + help_ref => __("mtnb-gsc-browser-buttons")}, + {widget => "details_frame", + help_ref => __("mtnb-gsc-information-fields")}, + {widget => undef, + help_ref => __("mtnb-gsc-monotone-browser-window-at-a-glance")}); + + # Update the browser's internal state. + + $browser->{branch_combo_details}->{preset} = 0; + $browser->{revision_combo_details}->{preset} = 0; + $browser->{directory_combo_details}->{preset} = 0; + $browser->{file_being_viewed_preset_value} = ""; + $browser->{file_displayed_id} = ""; + &{$browser->{update_handler}}($browser, ALL_CHANGED); + + # Now the browser instance is completely initialised, set up the + # branch, revision and directory comboboxentry widgets for + # auto-completion. + + activate_auto_completion($browser->{branch_comboboxentry}, $browser); + activate_auto_completion($browser->{revision_comboboxentry}, $browser); + activate_auto_completion($browser->{directory_comboboxentry}, + $browser); + + } + else + { + + my($height, + $width); + + $browser->{in_cb} = 0; + local $browser->{in_cb} = 1; + + # Reset the browser's state. + + ($width, $height) = $browser->{window}->get_default_size(); + $browser->{window}->resize($width, $height); + $browser->{browser_hpaned}->set_position(300); + $browser->{tagged_checkbutton}->set_active(FALSE); + $browser->{appbar}->set_progress_percentage(0); + $browser->{appbar}->clear_stack(); + $browser->{branch_combo_details}->{preset} = 0; + $browser->{revision_combo_details}->{preset} = 0; + $browser->{directory_combo_details}->{preset} = 0; + $browser->{file_being_viewed_preset_value} = ""; + $browser->{file_displayed_id} = ""; + $browser->{manifest_browser_treeview}-> + set_search_column(MLS_NAME_COLUMN); + if ($user_preferences->{show_line_numbers}) + { + $browser->{show_line_numbers_togglebutton}->set_active(TRUE); + $browser->{file_view_sv}->set_show_line_numbers(TRUE); + } + else + { + $browser->{show_line_numbers_togglebutton}->set_active(FALSE); + $browser->{file_view_sv}->set_show_line_numbers(FALSE); + } + &{$browser->{update_handler}}($browser, ALL_CHANGED); + $browser->{window}->show_all(); + $browser->{window}->present(); + + # Now update with the details of the specified database. + + if (defined($mtn)) + { + $browser->{mtn} = $mtn; + &{$browser->{update_handler}}($browser, DATABASE_CHANGED); + } + + } + + # Now deal with any presetting that is required. + + if (defined($branch)) + { + local $browser->{in_cb} = 1; + $browser->{branch_combo_details}->{preset} = 1; + $browser->{branch_combo_details}->{value} = $branch; + if (defined($revision_id)) + { + $browser->{revision_combo_details}->{preset} = 1; + $browser->{revision_combo_details}->{value} = $revision_id; + if (defined($directory)) + { + $browser->{directory_combo_details}->{preset} = 1; + $browser->{directory_combo_details}->{value} = $directory; + if (defined($file)) + { + $browser->{file_being_viewed_preset_value} = $file; + } + } + } + &{$browser->{update_handler}}($browser, ALL_CHANGED); + } + + # Make sure that the branch comboboxentry has the focus and not the bonobo + # dock. + + $browser->{branch_comboboxentry}->child()->grab_focus(); + $browser->{branch_comboboxentry}->child()->set_position(-1); + + return $browser; + +} +# +############################################################################## +# +# Routine - create_browser_widgets +# +# Description - Basically calls Gtk2::GladeXML->new() having first modified +# the Glade XML GUI specification data so as to take into +# account the user's toolbar appearance settings +# (unfortunately these settings have to be in place at +# creation time). +# +# Data - Return Value : The newly create Glade object. +# +############################################################################## + + + +sub create_browser_widgets() +{ + + # If the user's preferences indicate non-default toolbar settings then + # pre-edit the XML before passing it to Gtk2::GladeXML->new_from_buffer(), + # otherwise proceed as normal. + + if ($user_preferences->{toolbar_settings}->{hide_text} + || $user_preferences->{toolbar_settings}->{fixed}) + { + + my($file, + @glade_data, + $seen_toolbar); + my $fixed = $user_preferences->{toolbar_settings}->{fixed}; + my $hide_text = $user_preferences->{toolbar_settings}->{hide_text}; + + # Open the Glade file and read it into an array. + + die(__x("open failed: {error_message}\n", error_message => $!)) + unless (defined($file = IO::File->new($glade_file, "r"))); + @glade_data = $file->getlines(); + $file->close(); + + # Change the XML data depending upon the user's preferences. + + foreach my $line (@glade_data) + { + + # Deal with the toolbar's icon only or both setting. + + if ($hide_text && $line =~ m/>GTK_TOOLBAR_BOTHGTK_TOOLBAR_BOTHGTK_TOOLBAR_ICONS/) + { + $seen_toolbar = 1; + } + } + + # Deal with the toolbar's fixed or floating setting. + + if ($hide_text && $line =~ m/>GTK_TOOLBAR_BOTHGTK_TOOLBAR_BOTHGTK_TOOLBAR_ICONScatfile(UI_DIR, $2) . $3 + if ($line =~ m/^(.*>)([^<>]+\.png)(<.*)$/); + + } + + # Create and return the Glade object. + + return Gtk2::GladeXML->new_from_buffer(join("", @glade_data), + $window_type, + APPLICATION_NAME); + + } + else + { + return Gtk2::GladeXML->new($glade_file, + $window_type, + APPLICATION_NAME); + } + +} +# +############################################################################## +# +# Routine - update_server_bookmarks_menu +# +# Description - Creates or updates a server bookmarks menu for the +# specified browser window. +# +# Data - $browser : The browser instance that is to have its server +# bookmarks menu created or updated. +# +############################################################################## + + + +sub update_server_bookmarks_menu($) +{ + + my $browser = $_[0]; + + my($menu_item, + $old_menu, + $submenu); + + # Create a new connect to server submenu. + + $submenu = Gtk2::Menu->new(); + if (scalar(@{$user_preferences->{server_bookmarks}}) > 0) + { + for my $server (@{$user_preferences->{server_bookmarks}}) + { + $menu_item = Gtk2::MenuItem->new_with_label($server); + $menu_item->signal_connect("activate", + \&connect_to_server_activate_cb, + {browser => $browser, + server => $server}); + $menu_item->show(); + $submenu->append($menu_item); + } + } + else + { + $menu_item = Gtk2::MenuItem->new(__("No Bookmarks")); + $menu_item->set_sensitive(FALSE); + $menu_item->show(); + $submenu->append($menu_item); + } + $menu_item = Gtk2::SeparatorMenuItem->new(); + $menu_item->show(); + $submenu->append($menu_item); + $menu_item = Gtk2::MenuItem->new(__("Manage Server Bookmarks")); + $menu_item->signal_connect("activate", + \&manage_server_bookmarks_activate_cb, + $browser); + $menu_item->show(); + $submenu->append($menu_item); + $submenu->show(); + + # Replace the existing menu with the new one. + + $old_menu = $browser->{connect_to_server_menutiem}->get_submenu(); + $browser->{connect_to_server_menutiem}->set_submenu($submenu); + if (defined($old_menu)) + { + $old_menu->destroy() + } + +} +# +############################################################################## +# +# Routine - update_browser_state +# +# Description - Update the display of the specified browser instance +# according to the specified state. +# +# Data - $browser : The browser instance that is to have its state +# updated. +# $changed : What the user has changed. +# +############################################################################## + + + +sub update_browser_state($$) +{ + + my($browser, $changed) = @_; + + my $use_advanced_find; + my $wm = WindowManager->instance(); + + $wm->make_busy($browser, 1); + $browser->{appbar}->push($browser->{appbar}->get_status()->get_text()); + $wm->update_gui(); + + # The database has changed. + + if ($changed == DATABASE_CHANGED) + { + + my $db_name; + + if (! defined($browser->{mtn})) + { + + # Disable the browser as no database is associated with it. + + $browser->{close_menuitem}->set_sensitive(FALSE); + $browser->{compare_arbitrary_revisions_menuitem}-> + set_sensitive(FALSE); + $browser->{close_toolbutton}->set_sensitive(FALSE); + $browser->{reload_toolbutton}->set_sensitive(FALSE); + $browser->{main_vbox}->set_sensitive(FALSE); + set_label_value($browser->{database_name_value_label}, ""); + + } + else + { + + # Enable the browser as there is a database associated with it. + + $browser->{close_menuitem}->set_sensitive(TRUE); + $browser->{compare_arbitrary_revisions_menuitem}-> + set_sensitive(TRUE); + $browser->{close_toolbutton}->set_sensitive(TRUE); + $browser->{reload_toolbutton}->set_sensitive(TRUE); + $browser->{main_vbox}->set_sensitive(TRUE); + if (defined($browser->{mtn}->get_service_name())) + { + set_label_value + ($browser->{database_name_value_label}, + __x(" ({service_name})", + service_name => $browser->{mtn}->get_service_name())); + $browser->{monotone_viz_button}->set_sensitive(FALSE); + } + else + { + if (defined($browser->{mtn}->get_db_name())) + { + set_label_value($browser->{database_name_value_label}, + $browser->{mtn}->get_db_name()); + } + else + { + $browser->{mtn}->get_option(\$db_name, "database"); + set_label_value($browser->{database_name_value_label}, + __x(" ({database_name})", + database_name => $db_name)); + } + $browser->{monotone_viz_button}->set_sensitive(TRUE); + } + + # Make sure that the branch comboboxentry has the focus and not the + # bonobo dock. + + $browser->{branch_comboboxentry}->child()->grab_focus(); + $browser->{branch_comboboxentry}->child()->set_position(-1); + + } + + } + + # The list of available branches has changed. + + if ($changed & BRANCH) + { + + my(@branch_list, + $counter, + $update_interval); + + # Reset the branch selection. + + $browser->{branch_combo_details}->{completion} = undef; + if ($browser->{branch_combo_details}->{preset}) + { + $browser->{branch_combo_details}->{complete} = 1; + $browser->{branch_combo_details}->{preset} = 0; + } + else + { + $browser->{branch_combo_details}->{complete} = 0; + $browser->{branch_combo_details}->{value} = ""; + } + + # Get the new list of branches. + + $browser->{appbar}->set_status(__("Fetching branch list")); + $wm->update_gui(); + $browser->{mtn}->branches(address@hidden) if (defined($browser->{mtn})); + $browser->{branch_combo_details}->{list} = address@hidden; + + # Update the branch list combobox. + + $browser->{appbar}->set_status(__("Populating branch list")); + $wm->update_gui(); + $counter = 1; + $update_interval = calculate_update_interval(address@hidden); + $browser->{branch_comboboxentry}->get_model()->clear(); + foreach my $branch (@branch_list) + { + $browser->{branch_comboboxentry}->append_text($branch); + if (($counter % $update_interval) == 0) + { + $browser->{appbar}->set_progress_percentage + ($counter / scalar(@branch_list)); + $wm->update_gui(); + } + ++ $counter; + } + $browser->{appbar}->set_progress_percentage(1); + $wm->update_gui(); + $browser->{branch_comboboxentry}->child()-> + set_text($browser->{branch_combo_details}->{value}); + $browser->{branch_comboboxentry}->child()->set_position(-1); + $browser->{appbar}->set_progress_percentage(0); + $browser->{appbar}->set_status(""); + $wm->update_gui(); + + } + + # The list of available revisions has changed. + + if ($changed & REVISION) + { + + my($counter, + @revision_list, + $update_interval); + + # If auto-selection of the head revision is wanted by the user and it + # is appropriate then preset the revision id. + + if ($user_preferences->{auto_select_head} + && ! $browser->{tagged_checkbutton}->get_active() + && $browser->{branch_combo_details}->{complete} + && ! $browser->{revision_combo_details}->{preset}) + { + + my @revision_ids; + + # Get the head revision(s) for the branch. + + $browser->{appbar}->set_status(__("Auto selecting head revision")); + $wm->update_gui(); + $browser->{mtn}->select + (address@hidden, + "h:" . $browser->{branch_combo_details}->{value}); + + # One head on the branch. + + if (scalar(@revision_ids) == 1) + { + $browser->{revision_combo_details}->{preset} = 1; + $browser->{revision_combo_details}->{value} = $revision_ids[0]; + $browser->{tagged_checkbutton}->set_active(FALSE); + } + + # Multiple heads on the branch. + + elsif (scalar(@revision_ids) > 1) + { + + my($response, + $revision_id); + + # Ask the user what they want to do. + + $response = multiple_revisions_selection + ($browser->{window}, + __x("The `{branch}' branch\n" + . "has multiple heads and Auto select head " + . "revision is switched\n" + . "on. Please either select the revision by its " + . "id using the combobox\n" + . "below or use the Advanced Find " + . "feature.", + branch => $browser->{branch_combo_details}->{value}), + \$revision_id, + @revision_ids); + + # Deal with their response (either they have selected a + # revision, they have asked to use the advanced find dialog + # window or they have aborted in some way). + + if ($response eq "ok") + { + + # We have single revision id so simply preset the revision + # selection to that revision. + + $browser->{revision_combo_details}->{preset} = 1; + $browser->{revision_combo_details}->{value} = $revision_id; + $browser->{tagged_checkbutton}->set_active(FALSE); + + } + else + { + + # The user has either selected advanced find or aborted in + # some way. + + # If necessary flag the fact that the user wants to use + # advanced find. + + $use_advanced_find = 1 if ($response eq "advanced-find"); + + # Either way, no revision is currently selected so we need + # to blank out all the revision selection details and + # proceed as normal (this will blank out the rest of the + # browser). + + $browser->{revision_combo_details}->{complete} = 0; + $browser->{revision_combo_details}->{value} = ""; + $browser->{tagged_checkbutton}->set_active(FALSE); + + } + + } + + # No heads or revisions on the branch. + + else + { + $browser->{revision_combo_details}->{complete} = 0; + $browser->{revision_combo_details}->{value} = ""; + $browser->{tagged_checkbutton}->set_active(FALSE); + } + + $browser->{appbar}->set_status(""); + $wm->update_gui(); + + } + + # Reset the revision selection. + + $browser->{revision_combo_details}->{completion} = undef; + if ($browser->{revision_combo_details}->{preset}) + { + $browser->{revision_combo_details}->{complete} = 1; + $browser->{revision_combo_details}->{preset} = 0; + } + else + { + $browser->{revision_combo_details}->{complete} = 0; + $browser->{revision_combo_details}->{value} = ""; + } + + # Get the new list of revisions. + + if ($browser->{branch_combo_details}->{complete}) + { + $browser->{appbar}->set_status(__("Fetching revision list")); + $wm->update_gui(); + get_branch_revisions($browser->{mtn}, + $browser->{branch_combo_details}->{value}, + $browser->{tagged_checkbutton}->get_active(), + $browser->{appbar}, + address@hidden); + } + $browser->{revision_combo_details}->{list} = address@hidden; + + # Update the revision list combobox. + + $browser->{appbar}->set_progress_percentage(0); + $browser->{appbar}->set_status(__("Populating revision list")); + $wm->update_gui(); + $counter = 1; + $update_interval = calculate_update_interval(address@hidden); + $browser->{revision_comboboxentry}->get_model()->clear(); + foreach my $revision (@revision_list) + { + $browser->{revision_comboboxentry}->append_text($revision); + if (($counter % $update_interval) == 0) + { + $browser->{appbar}->set_progress_percentage + ($counter / scalar(@revision_list)); + $wm->update_gui(); + } + ++ $counter; + } + $browser->{appbar}->set_progress_percentage(1); + $wm->update_gui(); + $browser->{revision_comboboxentry}->child()-> + set_text($browser->{revision_combo_details}->{value}); + $browser->{appbar}->set_progress_percentage(0); + $browser->{appbar}->set_status(""); + $wm->update_gui(); + + } + + # The list of available files and directories has changed. + + if ($changed & DIRECTORY) + { + + my($counter, + @directory_list, + @manifest_list, + $update_interval); + + # Reset the directory combo. + + $browser->{directory_combo_details}->{completion} = undef; + if ($browser->{directory_combo_details}->{preset}) + { + $browser->{directory_combo_details}->{complete} = 1; + $browser->{directory_combo_details}->{preset} = 0; + } + else + { + $browser->{directory_combo_details}->{complete} = 1; + $browser->{directory_combo_details}->{value} = ""; + } + set_label_value($browser->{date_value_label}, ""); + set_label_value($browser->{author_value_label}, ""); + set_label_value($browser->{change_log_value_label}, ""); + + # Reset the name of the file being viewed. + + $browser->{file_being_viewed} = {}; + + # Get the new manifest. + + $browser->{appbar}->set_status(__("Fetching manifest")); + $wm->update_gui(); + if ($browser->{revision_combo_details}->{complete}) + { + + my($revision_id, + @revision_ids); + + # Get the revision id(s) that match what the user has selected. + # More than one revision id may be returned if the selected tag + # isn't unique on that branch. + + get_revision_ids($browser, address@hidden); + + # Deal with more than one revision id being selected. + + if (scalar(@revision_ids) > 1) + { + + my $response; + + # More than one revision matches the tag that was selected by + # the user. Ask them what they want to do. + + $response = multiple_revisions_selection + ($browser->{window}, + __x("The `{tag_name}' tag is not unique on this\n" + . "branch. Please either select the revision by " + . "its id using the\n" + . "combobox below or use the " + . "Advanced Find feature.", + tag_name => + $browser->{revision_combo_details}->{value}), + \$revision_id, + @revision_ids); + + # Deal with their response (either they have selected a + # revision, they have asked to use the advanced find dialog + # window or they have aborted in some way). + + if ($response eq "ok") + { + + # We have single revision id so we need to redo the + # revision selection bit but with tag names switched off. + # The easiest way to do this is to recursively call this + # routine with $changed set to just REVISION. Afterwards + # just carry on as if a revision id had been selected in + # the first place. + + $browser->{revision_combo_details}->{preset} = 1; + $browser->{revision_combo_details}->{value} = $revision_id; + $browser->{tagged_checkbutton}->set_active(FALSE); + &{$browser->{update_handler}}($browser, REVISION); + + } + else + { + + # The user has either selected advanced find or aborted in + # some way. + + # If necessary flag the fact that the user wants to use + # advanced find. + + $use_advanced_find = 1 if ($response eq "advanced-find"); + + # Either way, no revision is currently selected so we need + # to blank out all the revision selection details and + # proceed as normal (this will blank out the rest of the + # browser). + + $browser->{revision_combo_details}->{complete} = 0; + $browser->{revision_combo_details}->{value} = ""; + $browser->{revision_comboboxentry}->child()->set_text(""); + + } + + } + else + { + + # Only one revision id was selected. This is the norm. + + $revision_id = $revision_ids[0]; + + } + + # If we now have one revision selected then get its details. + + if (defined($revision_id)) + { + my($author, + @certs_list, + $change_log, + $date); + $browser->{mtn}->get_manifest_of(address@hidden, + $revision_id); + $browser->{mtn}->certs(address@hidden, $revision_id); + $author = $change_log = $date = ""; + foreach my $cert (@certs_list) + { + if ($cert->{name} eq "author") + { + $author = $cert->{value}; + } + elsif ($cert->{name} eq "changelog") + { + $change_log = $cert->{value}; + $change_log =~ s/\s+$//s; + } + elsif ($cert->{name} eq "date") + { + $date = $cert->{value}; + $date =~ s/T/ /; + } + } + set_label_value($browser->{date_value_label}, $date); + set_label_value($browser->{author_value_label}, $author); + set_label_value($browser->{change_log_value_label}, + $change_log); + } + + } + $browser->{manifest} = address@hidden; + + # Generate a simple list of directories for auto completion. + + $browser->{appbar}->set_progress_percentage(0.5); + $wm->update_gui(); + foreach my $item (@manifest_list) + { + push(@directory_list, $item->{name}) + if ($item->{type} eq "directory"); + } + $browser->{directory_combo_details}->{list} = address@hidden; + $browser->{appbar}->set_progress_percentage(1); + $wm->update_gui(); + + # Update the directory list combobox. + + $browser->{appbar}->set_progress_percentage(0); + $browser->{appbar}->set_status(__("Populating directory list")); + $wm->update_gui(); + $counter = 1; + $update_interval = calculate_update_interval(address@hidden); + $browser->{directory_comboboxentry}->get_model()->clear(); + foreach my $item (@directory_list) + { + $browser->{directory_comboboxentry}->append_text($item); + if (($counter % $update_interval) == 0) + { + $browser->{appbar}->set_progress_percentage + ($counter / scalar(@directory_list)); + $wm->update_gui(); + } + ++ $counter; + } + $browser->{appbar}->set_progress_percentage(1); + $wm->update_gui(); + $browser->{directory_comboboxentry}->child()-> + set_text($browser->{directory_combo_details}->{value}); + $browser->{directory_comboboxentry}->child()->set_position(-1); + $browser->{appbar}->set_progress_percentage(0); + $browser->{appbar}->set_status(""); + foreach my $widget (@{$browser->{revision_sensitive_group}}) + { + $widget-> + set_sensitive($browser->{revision_combo_details}->{complete} + ? TRUE : FALSE); + } + $wm->update_gui(); + + } + + # The list of displayed files and directories has changed. + + if ($changed & DIRECTORY_VIEW) + { + + my($author, + $counter, + @directory_entry_list, + $last_update, + @revision_ids, + $taking_our_time); + + # Reset the manifest tree view. + + $browser->{manifest_liststore}->clear(); + + # If we are currently at a valid directory then get its contents and + # enable the find files button. + + if ($browser->{revision_combo_details}->{complete} + && $browser->{directory_combo_details}->{complete}) + { + get_dir_contents($browser->{directory_combo_details}->{value}, + $browser->{manifest}, + address@hidden); + $browser->{find_files_button}->set_sensitive(TRUE); + } + else + { + $browser->{find_files_button}->set_sensitive(FALSE); + } + + # Disable the directory up button if we are already at the top level, + # otherwise make sure it is enabled. + + $browser->{directory_up_button}->set_sensitive + (($browser->{directory_combo_details}->{value} eq "") + ? FALSE : TRUE); + + # Update the directory tree view. + + $browser->{appbar}->set_status(__("Populating file details")); + $wm->update_gui(); + $counter = 1; + $taking_our_time = 0; + get_revision_ids($browser, address@hidden); + foreach my $item (@directory_entry_list) + { + + # Get the latest modification time and the author if that is what + # the user wants and the entry is a file (caching the result in the + # manifest for future reference if we have to work it out). + + if ($browser->{show_file_details} + && $item->{manifest_entry}->{type} eq "file") + { + if (! exists($item->{manifest_entry}->{author})) + { + $taking_our_time = 1; + cache_extra_file_info($browser->{mtn}, + $revision_ids[0], + $item->{manifest_entry}); + } + $author = $item->{manifest_entry}->{author}; + $last_update = $item->{manifest_entry}->{last_update}; + $last_update =~ s/T/ /; + } + else + { + $author = ""; + $last_update = ""; + } + + # Put the entry into the liststore. + + $browser->{manifest_liststore}-> + set($browser->{manifest_liststore}->append(), + MLS_ICON_COLUMN, + ($item->{manifest_entry}->{type} eq "directory") + ? "gtk-open" : "gtk-file", + MLS_NAME_COLUMN, $item->{name}, + MLS_DATE_COLUMN, $last_update, + MLS_AUTHOR_COLUMN, $author, + MLS_MANIFEST_ENTRY_COLUMN, $item->{manifest_entry}); + + if ($browser->{file_being_viewed_preset_value} ne "" + && $browser->{file_being_viewed_preset_value} eq $item->{name}) + { + $browser->{file_being_viewed} = + {short_name => $item->{name}, + manifest_entry => $item->{manifest_entry}}; + $browser->{file_being_viewed_preset_value} = ""; + } + + if ($taking_our_time && ($counter % 10) == 0) + { + $browser->{appbar}->set_progress_percentage + ($counter / scalar(@directory_entry_list)); + $wm->update_gui(); + } + ++ $counter; + + } + if ($taking_our_time) + { + $browser->{appbar}->set_progress_percentage(1); + $wm->update_gui(); + } + + $browser->{manifest_browser_treeview}->scroll_to_point(0, 0) + if ($browser->{manifest_browser_treeview}->realized()); + + $browser->{appbar}->set_progress_percentage(0); + $browser->{appbar}->set_status(""); + $wm->update_gui(); + + } + + # The displayed file contents has changed. + + if ($changed & DISPLAY_OF_FILE) + { + + # Load up the selected file's contents into the file viewer. + + if (exists($browser->{file_being_viewed}->{manifest_entry})) + { + + my $manifest_entry = + $browser->{file_being_viewed}->{manifest_entry}; + + # Only do anything if the selected file has changed. + + if ($browser->{file_displayed_id} ne $manifest_entry->{file_id}) + { + + my($last_update, + $textual_data); + + # Enable the file buttons, keeping the ones relating to text + # files disabled, and reset any associated find text window. + + $browser->{file_button_vbox}->set_sensitive(TRUE); + foreach my $widget (@{$browser->{text_file_sensitive_group}}) + { + $widget->set_sensitive(FALSE); + } + reset_find_text($browser->{file_view_sv}); + enable_find_text($browser->{file_view_sv}, 0); + + # Display the selected file's contents. + + display_file($browser, \$textual_data); + + # If we have just displayed a text file then enable the file + # buttons applicable to text files and enable any associated + # find text window that may be displayed. + + if ($textual_data) + { + foreach my $widget + (@{$browser->{text_file_sensitive_group}}) + { + $widget->set_sensitive(TRUE); + } + $browser->{annotate_button}->set_sensitive(FALSE) + if (defined($browser->{mtn}->get_service_name())); + enable_find_text($browser->{file_view_sv}, 1); + } + + # Update the file details labels. + + if (! exists($manifest_entry->{last_changed_revision})) + { + my @revision_ids; + get_revision_ids($browser, address@hidden); + cache_extra_file_info($browser->{mtn}, + $revision_ids[0], + $manifest_entry); + } + $last_update = $manifest_entry->{last_update}; + $last_update =~ s/T/ /; + set_label_value($browser->{file_name_value_label}, + $manifest_entry->{name}); + set_label_value($browser->{file_author_value_label}, + $manifest_entry->{author}); + set_label_value($browser->{last_update_value_label}, + $last_update); + set_label_value($browser->{file_revision_id_value_label}, + $manifest_entry->{last_changed_revision}); + + $browser->{file_displayed_id} = $manifest_entry->{file_id}; + + } + + } + else + { + + # Reset the file view buffer and the associated find text window. + + $browser->{file_displayed_id} = ""; + $browser->{file_button_vbox}->set_sensitive(FALSE); + $browser->{file_view_svbuffer}-> + place_cursor($browser->{file_view_svbuffer}->get_start_iter()); + $browser->{file_view_svbuffer}->set_text(""); + $browser->{file_view_svbuffer}->set("highlight", FALSE); + set_label_value($browser->{file_name_value_label}, ""); + set_label_value($browser->{file_author_value_label}, ""); + set_label_value($browser->{last_update_value_label}, ""); + set_label_value($browser->{file_revision_id_value_label}, ""); + enable_find_text($browser->{file_view_sv}, 0); + + } + + } + + $browser->{appbar}->pop(); + $wm->make_busy($browser, 0); + + # All done. Now check to see if during the update process the user asked to + # call up the advanced find dialog window, if so then display it now. + + handle_advanced_find($browser) if ($use_advanced_find); + +} +# +############################################################################## +# +# Routine - handle_advanced_find +# +# Description - Call up the advanced find dialog window for the specified +# browser and then populate that browser with any results +# that come back from that search. +# +# Data - $browser : The browser instance from which the advanced +# find is to be done. +# +############################################################################## + + + +sub handle_advanced_find($) +{ + + my $browser = $_[0]; + + my(@branches, + $preset_branch, + $revision_id, + $state); + + if (advanced_find($browser, \$revision_id, address@hidden)) + { + + # Preset branch name. If we already have a selected branch then try and + # match branch names, if that fails then just pick the first name. + + $preset_branch = 1; + $state = BRANCH_CHANGED; + if ($browser->{branch_combo_details}->{complete}) + { + foreach my $name (@branches) + { + if ($name eq $browser->{branch_combo_details}->{value}) + { + $preset_branch = 0; + last; + } + } + } + if ($preset_branch) + { + $browser->{branch_combo_details}->{preset} = 1; + $browser->{branch_combo_details}->{value} = + (scalar(@branches) > 0) ? $branches[0] : ""; + $state = ALL_CHANGED; + } + + # Preset revision id. + + $browser->{revision_combo_details}->{preset} = 1; + $browser->{revision_combo_details}->{value} = $revision_id; + + # A revision id is what is returned so switch off the listing of tag + # names. + + $browser->{tagged_checkbutton}->set_active(FALSE); + + $browser->{appbar}->clear_stack(); + &{$browser->{update_handler}}($browser, $state); + + } + +} +# +############################################################################## +# +# Routine - display_file +# +# Description - Display the currenty selected file in the sourceview +# textview. +# +# Data - $browser : The browser instance that is to display the +# file. +# $text_data : A reference to a variable that is to contain a +# boolean indicator as to whether the displayed +# file contains textual or binary data. +# +############################################################################## + + + +sub display_file($$) +{ + + my($browser, $textual_data) = @_; + + my($contents, + $iter, + $lang, + $mime_details, + $mime_type); + + $$textual_data = 0; + + # Reset the file view buffer. + + $browser->{file_view_svbuffer}-> + place_cursor($browser->{file_view_svbuffer}->get_start_iter()); + $browser->{file_view_svbuffer}->set_text(""); + $browser->{file_view_svbuffer}->set("highlight", FALSE); + + # Get contents. + + $browser->{mtn}->get_file(\$contents, + $browser->{file_being_viewed}->{manifest_entry}-> + {file_id}); + + # Try and work out the MIME type. + + determine_mime_type($browser->{file_being_viewed}->{short_name}, + \$contents, + \$mime_type, + \$mime_details); + + # Only attempt to render the file's contents if requested to do so. + + if (! defined($mime_type) + || (defined($mime_details) && ! $mime_details->{display_internally})) + { + + # The user doesn't want to display this type of file. + + $browser->{file_view_svbuffer}-> + set_text("<" + . (defined($mime_type) + ? $mime_type : __("Unknown Contents")) + . ">"); + + } + else + { + + # The user wants to display this type of file. + + # Image/non-image data? + + if ($mime_type =~ m/^image\/.+$/) + { + + # Image data. + + my $exception; + my $loader = Gtk2::Gdk::PixbufLoader->new(); + eval + { + $loader->write($contents); + }; + $exception = $@; + eval + { + $loader->close(); + }; + $exception = $@ if ($@); + if (! $exception) + { + $browser->{file_view_svbuffer}->insert_pixbuf + ($browser->{file_view_svbuffer}->get_start_iter(), + $loader->get_pixbuf()); + } + else + { + $browser->{file_view_svbuffer}-> + set_text("<" . $mime_type . ">"); + } + + } + else + { + + # Non-image data. + + # Binary/text data? + + if (data_is_binary(\$contents)) + { + + # Binary data. + + # We have been asked to display this data. The only thing we + # can do is to hex dump it out. + + $browser->{file_view_svbuffer}-> + set_text("<" . $mime_type . "> " . __("Hex dump:\n")); + $browser->{file_view_svbuffer}-> + insert($browser->{file_view_svbuffer}->get_end_iter(), + ${hex_dump(\$contents)}); + + } + else + { + + # Text data. + + $$textual_data = 1; + + # Enable syntax highlighting if the user wants it on and it is + # available for this type of file. + + if ((! defined($mime_details) + || (defined($mime_details) + && $mime_details->{syntax_highlight})) + && defined($lang = + $browser->{file_view_svlangmgr}-> + get_language_from_mime_type($mime_type))) + { + $browser->{file_view_svbuffer}->set("highlight", TRUE); + $browser->{file_view_svbuffer}->set_language($lang); + } + + # Load in the contents (ensuring UTF-8 encoding). + + eval + { + $contents = + decode($file_encoding, $contents, Encode::FB_CROAK); + }; + $browser->{file_view_svbuffer}->set_text($contents); + + } + + # Delete the trailing newline. + + $iter = $browser->{file_view_svbuffer}->get_end_iter(); + $browser->{file_view_svbuffer}->delete + ($iter, $browser->{file_view_svbuffer}->get_end_iter()) + if ($iter->backward_char()); + + } + + } + + # Scroll back up to the top left. + + $browser->{file_view_svbuffer}-> + place_cursor($browser->{file_view_svbuffer}->get_start_iter()); + if ($browser->{file_view_scrolledwindow}->realized()) + { + $browser->{file_view_scrolledwindow}->get_vadjustment()->set_value(0); + $browser->{file_view_scrolledwindow}->get_hadjustment()->set_value(0); + } + +} +# +############################################################################## +# +# Routine - determine_mime_type +# +# Description - Given a file name and its contents, determine and return +# its MIME type and the related entry in the MIME details +# table. +# +# Data - $file_name : The name of the file. +# $contents : A reference to a variable containing the +# contents of the file. +# $mime_type : A reference to a variable that is to +# contain the MIME type. This will be undef +# if no match can be found. +# $mime_details : A reference to a variable that is to +# contain a refernce to the related entry in +# the MIME information table. This will be +# undef if no match can be found. +# +############################################################################## + + + +sub determine_mime_type($$$$) +{ + + my($file_name, $contents, $mime_type, $mime_details) = @_; + + $$mime_type = $$mime_details = undef; + + # Try and work out the MIME type through file name pattern matching. + + foreach my $entry (@$mime_match_table) + { + if ($file_name =~ m/$entry->{re}/) + { + $$mime_type = $entry->{details}->{name}; + $$mime_details = $entry->{details}; + return; + } + } + + # If that didn't work then try determining the MIME type based upon its + # contents. + + $$mime_type = Gnome2::VFS->get_mime_type_for_data($contents); + + # If we now have a MIME type then do another lookup with it but by matching + # MIME type names and not file name patterns. + + if (defined($$mime_type)) + { + foreach my $entry (@{$user_preferences->{mime_table}}) + { + if ($$mime_type eq $entry->{name}) + { + $$mime_details = $entry; + return; + } + } + } + +} +# +############################################################################## +# +# Routine - setup_mtn_object +# +# Description - Sets up a new Monotone::AutomateStdio object so that it can +# be used in this application. +# +# Data - $mtn : The Monotone::AutomateStdio object that is to be +# setup. +# $parent : The parent window widget that is to be used for +# assorted dialog windows that may appear. +# +############################################################################## + + + +sub setup_mtn_object($$) +{ + + my($mtn, $parent) = @_; + + my $save_prefs; + my $wm = WindowManager->instance(); + + # Deal with branch suspend certificate settings. + + if ($user_preferences->{show_suspended}) + { + if ($mtn->supports(MTN_IGNORING_OF_SUSPEND_CERTS)) + { + $mtn->ignore_suspend_certs(1); + } + else + { + my $dialog = Gtk2::MessageDialog-> + new($parent, + ["modal"], + "info", + "close", + __("Your version of Monotone does not support\n" + . "suspend certificates. I will adjusted your\n" + . "preferences accordingly.")); + $wm->allow_input(sub { $dialog->run(); }); + $dialog->destroy(); + $user_preferences->{show_suspended} = 0; + $save_prefs = 1; + eval + { + save_preferences($user_preferences); + }; + if ($@) + { + chomp($@); + my $dialog = Gtk2::MessageDialog->new + (undef, + ["modal"], + "warning", + "close", + __("Your preferences could not be saved:\n") . $@); + $wm->allow_input(sub { $dialog->run(); }); + $dialog->destroy(); + } + } + } + + # Determine whether remote connections to servers are supported. + + if (($mtn->supports(MTN_REMOTE_CONNECTIONS) + && ! $user_preferences->{remote_connections}) + || (! $mtn->supports(MTN_REMOTE_CONNECTIONS) + && $user_preferences->{remote_connections})) + { + $user_preferences->{remote_connections} = + $mtn->supports(MTN_REMOTE_CONNECTIONS) ? 1 : 0; + $save_prefs = 1; + } + + # Save the user's preferences if they have been changed. + + if ($save_prefs) + { + eval + { + save_preferences($user_preferences); + }; + if ($@) + { + chomp($@); + my $dialog = Gtk2::MessageDialog->new + (undef, + ["modal"], + "warning", + "close", + __("Your preferences could not be saved:\n") . $@); + $wm->allow_input(sub { $dialog->run(); }); + $dialog->destroy(); + } + } + +} +# +############################################################################## +# +# Routine - mtn_db_locked_handler +# +# Description - This routine is called when ever a locked database +# condition is detected. +# +# Data - $mtn : The Monotone::AutomateStdio object that +# encountered the locked database. +# $parent : The parent window for any dialogs that are +# to be displayed. +# Return Value : True if the command is to be retried, +# otherwise false if the locked database +# should be reported as an error. +# +############################################################################## + + + +sub mtn_db_locked_handler($$) +{ + + my($mtn, $parent) = @_; + + my $dialog; + my $wm = WindowManager->instance(); + + $dialog = Gtk2::MessageDialog->new + ($parent, + ["modal"], + "info", + "ok", + __("The Monotone database is currently locked, please\n" + . "dismiss this dialog when this is no longer the case.")); + $wm->allow_input(sub { $dialog->run(); }); + $dialog->destroy(); + + return 1; + +} +# +############################################################################## +# +# Routine - mtn_error_handler +# +# Description - This routine is called when ever there is a problem with +# Monotone. +# +# Data - $severity : The severity of the error. +# $message : The error message. +# +############################################################################## + + + +sub mtn_error_handler($$) +{ + + my($severity, $message) = @_; + + my $dialog; + my $wm = WindowManager->instance(); + + if ($severity == MTN_SEVERITY_WARNING) + { + if (! $suppress_mtn_warnings) + { + $dialog = Gtk2::MessageDialog->new_with_markup + (undef, + ["modal"], + "warning", + "close", + __x("Problem with Monotone request, got:\n" + . "{error_message}\n" + . "This should not be happening!", + error_message => Glib::Markup::escape_text($message))); + $wm->allow_input(sub { $dialog->run(); }); + $dialog->destroy(); + $wm->reset_state(); + die($message); + } + } + else + { + $dialog = Gtk2::MessageDialog->new_with_markup + (undef, + ["modal"], + "error", + "close", + __x("The Monotone subprocess unexpectedly exited with:\n" + . "{error_message}\n" + . "This is fatal, I am going to exit.", + error_message => Glib::Markup::escape_text($message))); + $wm->allow_input(sub { $dialog->run(); }); + $dialog->destroy(); + Gtk2->main_quit() unless (Gtk2->main_level() == 0); + die($message); + } + +} +# +############################################################################## +# +# Routine - sigchld_handler +# +# Description - This routine is called when ever a subprocess exits. +# +# Data - None. +# +############################################################################## + + + +sub sigchld_handler() +{ + + my $pid; + + my $wm = WindowManager->instance(); + + while (($pid = waitpid(-1, WNOHANG)) > 0) + { + my $exit_status = $?; + if (WIFEXITED($exit_status) || WIFSIGNALED($exit_status)) + { + + # If it is an mtn process then close down the relevant object so + # that it will automatically restart when needed. + + $wm->cond_find + (undef, + sub { + my $instance = $_[0]; + if (exists($instance->{mtn}) && defined($instance->{mtn}) + && $instance->{mtn}->get_pid() == $pid) + { + my $message; + $instance->{mtn}->closedown(); + if (WIFSIGNALED($exit_status)) + { + $message = __x("terminated by signal {number}", + number => WTERMSIG($exit_status)); + } + else + { + $message = + __x("exited with status {number}", + number => WEXITSTATUS($exit_status)); + } + my $dialog = Gtk2::MessageDialog->new + (undef, + ["modal"], + "warning", + "close", + __x("The Monotone subprocess just unexpectedly\n" + . "exited ({error_message}).\n" + . "This should not be happening!\n" + . "It will be restarted when needed.", + error_message => $message)); + $wm->allow_input(sub { $dialog->run(); }); + $dialog->destroy(); + $wm->reset_state(); + return 1; + } + return; + }); + + } + } + warn(__x("waitpid failed: {error_message}", error_message => $!)) + if ($pid < 0 && $! != ECHILD); + +} +# +############################################################################## +# +# Routine - setup_sigchld_handler +# +# Description - This routine sets up the handler for SIGCHLD signals. +# +# Data - $handler - A reference to the SIGCHLD handler routine. +# +############################################################################## + + + +sub setup_sigchld_handler($) +{ + + my $handler = $_[0]; + + my($reader, + $writer); + + # Basically set up a SIGCHLD handler that simply writes a character down an + # anonymous pipe in order to wake up the actual handler that is registered + # with Gtk2 as a file activity handler. This is efficient and safer than + # some alternatives. + + die(__x("pipe failed: {error_message}", error_message => $!)) + unless (pipe($reader, $writer)); + $SIG{CHLD} = sub { syswrite($writer, "\n", 1); }; + Gtk2::Helper->add_watch(fileno($reader), "in", + sub { + my $buffer; + sysread($reader, $buffer, 1); + &$handler(); + return 1; + }); + +}