/b-gtk/fix-viz

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/b-gtk/fix-viz

« back to all changes in this revision

Viewing changes to olive/__init__.py

  • Committer: Aaron Bentley
  • Date: 2007-08-16 14:31:02 UTC
  • Revision ID: abentley@panoramicfeedback.com-20070816143102-27yzfhdgdv3ggo6e
Fix hue selection to use author in gannotate

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006 by Szilveszter Farkas (Phanatic) <szilveszter.farkas@gmail.com>
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
import os
 
18
import sys
 
19
import time
 
20
 
 
21
# gettext support
 
22
import gettext
 
23
gettext.install('olive-gtk')
 
24
 
 
25
try:
 
26
    import pygtk
 
27
    pygtk.require("2.0")
 
28
except:
 
29
    pass
 
30
 
 
31
import gobject
 
32
import gtk
 
33
import gtk.gdk
 
34
import gtk.glade
 
35
 
 
36
from bzrlib.branch import Branch
 
37
import bzrlib.errors as bzrerrors
 
38
from bzrlib.lazy_import import lazy_import
 
39
from bzrlib.ui import ui_factory
 
40
from bzrlib.workingtree import WorkingTree
 
41
 
 
42
from bzrlib.plugins.gtk.dialog import error_dialog, info_dialog, warning_dialog
 
43
from bzrlib.plugins.gtk.errors import show_bzr_error
 
44
from guifiles import GLADEFILENAME
 
45
 
 
46
from bzrlib.plugins.gtk.diff import DiffWindow
 
47
lazy_import(globals(), """
 
48
from bzrlib.plugins.gtk.viz import branchwin
 
49
""")
 
50
from bzrlib.plugins.gtk.annotate.gannotate import GAnnotateWindow
 
51
from bzrlib.plugins.gtk.annotate.config import GAnnotateConfig
 
52
from bzrlib.plugins.gtk.commit import CommitDialog
 
53
from bzrlib.plugins.gtk.conflicts import ConflictsDialog
 
54
from bzrlib.plugins.gtk.initialize import InitDialog
 
55
from bzrlib.plugins.gtk.push import PushDialog
 
56
from bzrlib.plugins.gtk.revbrowser import RevisionBrowser
 
57
 
 
58
def about():
 
59
    """ Display the AboutDialog. """
 
60
    from bzrlib.plugins.gtk import __version__
 
61
    from bzrlib.plugins.gtk.olive.guifiles import GLADEFILENAME
 
62
 
 
63
    # Load AboutDialog description
 
64
    dglade = gtk.glade.XML(GLADEFILENAME, 'aboutdialog')
 
65
    dialog = dglade.get_widget('aboutdialog')
 
66
 
 
67
    # Set version
 
68
    dialog.set_version(__version__)
 
69
    dialog.set_artists(["Simon Pascal Klein"])
 
70
 
 
71
    dialog.run()
 
72
    # Destroy the dialog
 
73
    dialog.destroy()
 
74
 
 
75
class OliveGtk:
 
76
    """ The main Olive GTK frontend class. This is called when launching the
 
77
    program. """
 
78
    
 
79
    def __init__(self):
 
80
        self.toplevel = gtk.glade.XML(GLADEFILENAME, 'window_main', 'olive-gtk')
 
81
        self.window = self.toplevel.get_widget('window_main')
 
82
        self.pref = Preferences()
 
83
        self.path = None
 
84
 
 
85
        # Initialize the statusbar
 
86
        self.statusbar = self.toplevel.get_widget('statusbar')
 
87
        self.context_id = self.statusbar.get_context_id('olive')
 
88
        
 
89
        # Get the main window
 
90
        self.window_main = self.toplevel.get_widget('window_main')
 
91
        # Get the HPaned
 
92
        self.hpaned_main = self.toplevel.get_widget('hpaned_main')
 
93
        # Get the TreeViews
 
94
        self.treeview_left = self.toplevel.get_widget('treeview_left')
 
95
        self.treeview_right = self.toplevel.get_widget('treeview_right')
 
96
        # Get some important menu items
 
97
        self.menuitem_add_files = self.toplevel.get_widget('menuitem_add_files')
 
98
        self.menuitem_remove_files = self.toplevel.get_widget('menuitem_remove_file')
 
99
        self.menuitem_file_make_directory = self.toplevel.get_widget('menuitem_file_make_directory')
 
100
        self.menuitem_file_rename = self.toplevel.get_widget('menuitem_file_rename')
 
101
        self.menuitem_file_move = self.toplevel.get_widget('menuitem_file_move')
 
102
        self.menuitem_file_annotate = self.toplevel.get_widget('menuitem_file_annotate')
 
103
        self.menuitem_view_show_hidden_files = self.toplevel.get_widget('menuitem_view_show_hidden_files')
 
104
        self.menuitem_branch = self.toplevel.get_widget('menuitem_branch')
 
105
        self.menuitem_branch_init = self.toplevel.get_widget('menuitem_branch_initialize')
 
106
        self.menuitem_branch_get = self.toplevel.get_widget('menuitem_branch_get')
 
107
        self.menuitem_branch_checkout = self.toplevel.get_widget('menuitem_branch_checkout')
 
108
        self.menuitem_branch_pull = self.toplevel.get_widget('menuitem_branch_pull')
 
109
        self.menuitem_branch_push = self.toplevel.get_widget('menuitem_branch_push')
 
110
        self.menuitem_branch_revert = self.toplevel.get_widget('menuitem_branch_revert')
 
111
        self.menuitem_branch_merge = self.toplevel.get_widget('menuitem_branch_merge')
 
112
        self.menuitem_branch_commit = self.toplevel.get_widget('menuitem_branch_commit')
 
113
        self.menuitem_branch_tags = self.toplevel.get_widget('menuitem_branch_tags')
 
114
        self.menuitem_branch_status = self.toplevel.get_widget('menuitem_branch_status')
 
115
        self.menuitem_branch_missing = self.toplevel.get_widget('menuitem_branch_missing_revisions')
 
116
        self.menuitem_branch_conflicts = self.toplevel.get_widget('menuitem_branch_conflicts')
 
117
        self.menuitem_stats = self.toplevel.get_widget('menuitem_stats')
 
118
        self.menuitem_stats_diff = self.toplevel.get_widget('menuitem_stats_diff')
 
119
        self.menuitem_stats_log = self.toplevel.get_widget('menuitem_stats_log')
 
120
        # Get some toolbuttons
 
121
        #self.menutoolbutton_diff = self.toplevel.get_widget('menutoolbutton_diff')
 
122
        self.toolbutton_diff = self.toplevel.get_widget('toolbutton_diff')
 
123
        self.toolbutton_log = self.toplevel.get_widget('toolbutton_log')
 
124
        self.toolbutton_commit = self.toplevel.get_widget('toolbutton_commit')
 
125
        self.toolbutton_pull = self.toplevel.get_widget('toolbutton_pull')
 
126
        self.toolbutton_push = self.toplevel.get_widget('toolbutton_push')
 
127
        # Get the drive selector
 
128
        self.combobox_drive = gtk.combo_box_new_text()
 
129
        self.combobox_drive.connect("changed", self._refresh_drives)
 
130
        
 
131
        # Get the navigation widgets
 
132
        self.hbox_location = self.toplevel.get_widget('hbox_location')
 
133
        self.button_location_up = self.toplevel.get_widget('button_location_up')
 
134
        self.button_location_jump = self.toplevel.get_widget('button_location_jump')
 
135
        self.entry_location = self.toplevel.get_widget('entry_location')
 
136
        self.image_location_error = self.toplevel.get_widget('image_location_error')
 
137
        
 
138
        # Get the History widgets
 
139
        self.check_history = self.toplevel.get_widget('checkbutton_history')
 
140
        self.entry_history = self.toplevel.get_widget('entry_history_revno')
 
141
        self.button_history = self.toplevel.get_widget('button_history_browse')
 
142
        
 
143
        self.vbox_main_right = self.toplevel.get_widget('vbox_main_right')
 
144
        
 
145
        # Dictionary for signal_autoconnect
 
146
        dic = { "on_window_main_destroy": gtk.main_quit,
 
147
                "on_window_main_delete_event": self.on_window_main_delete_event,
 
148
                "on_quit_activate": self.on_window_main_delete_event,
 
149
                "on_about_activate": self.on_about_activate,
 
150
                "on_menuitem_add_files_activate": self.on_menuitem_add_files_activate,
 
151
                "on_menuitem_remove_file_activate": self.on_menuitem_remove_file_activate,
 
152
                "on_menuitem_file_make_directory_activate": self.on_menuitem_file_make_directory_activate,
 
153
                "on_menuitem_file_move_activate": self.on_menuitem_file_move_activate,
 
154
                "on_menuitem_file_rename_activate": self.on_menuitem_file_rename_activate,
 
155
                "on_menuitem_file_annotate_activate": self.on_menuitem_file_annotate_activate,
 
156
                "on_menuitem_view_show_hidden_files_activate": self.on_menuitem_view_show_hidden_files_activate,
 
157
                "on_menuitem_view_refresh_activate": self.on_menuitem_view_refresh_activate,
 
158
                "on_menuitem_branch_initialize_activate": self.on_menuitem_branch_initialize_activate,
 
159
                "on_menuitem_branch_get_activate": self.on_menuitem_branch_get_activate,
 
160
                "on_menuitem_branch_checkout_activate": self.on_menuitem_branch_checkout_activate,
 
161
                "on_menuitem_branch_revert_activate": self.on_menuitem_branch_revert_activate,
 
162
                "on_menuitem_branch_merge_activate": self.on_menuitem_branch_merge_activate,
 
163
                "on_menuitem_branch_commit_activate": self.on_menuitem_branch_commit_activate,
 
164
                "on_menuitem_branch_push_activate": self.on_menuitem_branch_push_activate,
 
165
                "on_menuitem_branch_pull_activate": self.on_menuitem_branch_pull_activate,
 
166
                "on_menuitem_branch_tags_activate": self.on_menuitem_branch_tags_activate,
 
167
                "on_menuitem_branch_status_activate": self.on_menuitem_branch_status_activate,
 
168
                "on_menuitem_branch_missing_revisions_activate": self.on_menuitem_branch_missing_revisions_activate,
 
169
                "on_menuitem_branch_conflicts_activate": self.on_menuitem_branch_conflicts_activate,
 
170
                "on_menuitem_stats_diff_activate": self.on_menuitem_stats_diff_activate,
 
171
                "on_menuitem_stats_log_activate": self.on_menuitem_stats_log_activate,
 
172
                "on_menuitem_stats_infos_activate": self.on_menuitem_stats_infos_activate,
 
173
                "on_toolbutton_refresh_clicked": self.on_menuitem_view_refresh_activate,
 
174
                "on_toolbutton_log_clicked": self.on_menuitem_stats_log_activate,
 
175
                #"on_menutoolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
 
176
                "on_toolbutton_diff_clicked": self.on_menuitem_stats_diff_activate,
 
177
                "on_toolbutton_commit_clicked": self.on_menuitem_branch_commit_activate,
 
178
                "on_toolbutton_pull_clicked": self.on_menuitem_branch_pull_activate,
 
179
                "on_toolbutton_push_clicked": self.on_menuitem_branch_push_activate,
 
180
                "on_treeview_right_button_press_event": self.on_treeview_right_button_press_event,
 
181
                "on_treeview_right_row_activated": self.on_treeview_right_row_activated,
 
182
                "on_treeview_left_button_press_event": self.on_treeview_left_button_press_event,
 
183
                "on_treeview_left_row_activated": self.on_treeview_left_row_activated,
 
184
                "on_button_location_up_clicked": self.on_button_location_up_clicked,
 
185
                "on_button_location_jump_clicked": self.on_button_location_jump_clicked,
 
186
                "on_entry_location_key_press_event": self.on_entry_location_key_press_event,
 
187
                "on_checkbutton_history_toggled": self.on_checkbutton_history_toggled,
 
188
                "on_entry_history_revno_key_press_event": self.on_entry_history_revno_key_press_event,
 
189
                "on_button_history_browse_clicked": self.on_button_history_browse_clicked
 
190
            }
 
191
        
 
192
        # Connect the signals to the handlers
 
193
        self.toplevel.signal_autoconnect(dic)
 
194
        
 
195
        self._just_started = True
 
196
        
 
197
        # Apply window size and position
 
198
        width = self.pref.get_preference('window_width', 'int')
 
199
        height = self.pref.get_preference('window_height', 'int')
 
200
        self.window.resize(width, height)
 
201
        x = self.pref.get_preference('window_x', 'int')
 
202
        y = self.pref.get_preference('window_y', 'int')
 
203
        self.window.move(x, y)
 
204
        # Apply paned position
 
205
        pos = self.pref.get_preference('paned_position', 'int')
 
206
        self.hpaned_main.set_position(pos)
 
207
        
 
208
        # Apply menu to the toolbutton
 
209
        #menubutton = self.toplevel.get_widget('menutoolbutton_diff')
 
210
        #menubutton.set_menu(handler.menu.toolbar_diff)
 
211
        
 
212
        # Now we can show the window
 
213
        self.window.show()
 
214
        
 
215
        # Show drive selector if under Win32
 
216
        if sys.platform == 'win32':
 
217
            self.hbox_location.pack_start(self.combobox_drive, False, False, 0)
 
218
            self.hbox_location.reorder_child(self.combobox_drive, 1)
 
219
            self.combobox_drive.show()
 
220
            self.gen_hard_selector()
 
221
        
 
222
        self._load_left()
 
223
 
 
224
        # Apply menu state
 
225
        self.menuitem_view_show_hidden_files.set_active(self.pref.get_preference('dotted_files', 'bool'))
 
226
 
 
227
        # We're starting local
 
228
        self.remote = False
 
229
        self.remote_branch = None
 
230
        self.remote_path = None
 
231
        self.remote_revision = None
 
232
        
 
233
        self.set_path(os.getcwd())
 
234
        self._load_right()
 
235
        
 
236
        self._just_started = False
 
237
 
 
238
    def set_path(self, path, force_remote=False):
 
239
        self.notbranch = False
 
240
        
 
241
        if force_remote:
 
242
            # Forcing remote mode (reading data from inventory)
 
243
            self._show_stock_image(gtk.STOCK_DISCONNECT)
 
244
            try:
 
245
                br = Branch.open_containing(path)[0]
 
246
            except bzrerrors.NotBranchError:
 
247
                self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
248
                self.check_history.set_active(False)
 
249
                self.check_history.set_sensitive(False)
 
250
                return False
 
251
            except bzrerrors.UnsupportedProtocol:
 
252
                self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
253
                self.check_history.set_active(False)
 
254
                self.check_history.set_sensitive(False)
 
255
                return False
 
256
            
 
257
            self._show_stock_image(gtk.STOCK_CONNECT)
 
258
            
 
259
            self.remote = True
 
260
           
 
261
            # We're remote
 
262
            self.remote_branch, self.remote_path = Branch.open_containing(path)
 
263
            
 
264
            if self.remote_revision is None:
 
265
                self.remote_revision = self.remote_branch.last_revision()
 
266
            
 
267
            self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
 
268
            
 
269
            if len(self.remote_path) == 0:
 
270
                self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
 
271
            else:
 
272
                for (name, type) in self.remote_entries:
 
273
                    if name == self.remote_path:
 
274
                        self.remote_parent = type.file_id
 
275
                        break
 
276
            
 
277
            if not path.endswith('/'):
 
278
                path += '/'
 
279
            
 
280
            if self.remote_branch.base == path:
 
281
                self.button_location_up.set_sensitive(False)
 
282
            else:
 
283
                self.button_location_up.set_sensitive(True)
 
284
        else:
 
285
            if os.path.isdir(path):
 
286
                self.image_location_error.destroy()
 
287
                self.remote = False
 
288
                
 
289
                # We're local
 
290
                try:
 
291
                    self.wt, self.wtpath = WorkingTree.open_containing(path)
 
292
                except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
 
293
                    self.notbranch = True
 
294
                
 
295
                # If we're in the root, we cannot go up anymore
 
296
                if sys.platform == 'win32':
 
297
                    drive, tail = os.path.splitdrive(path)
 
298
                    if tail in ('', '/', '\\'):
 
299
                        self.button_location_up.set_sensitive(False)
 
300
                    else:
 
301
                        self.button_location_up.set_sensitive(True)
 
302
                else:
 
303
                    if self.path == '/':
 
304
                        self.button_location_up.set_sensitive(False)
 
305
                    else:
 
306
                        self.button_location_up.set_sensitive(True)
 
307
            elif not os.path.isfile(path):
 
308
                # Doesn't seem to be a file nor a directory, trying to open a
 
309
                # remote location
 
310
                self._show_stock_image(gtk.STOCK_DISCONNECT)
 
311
                try:
 
312
                    br = Branch.open_containing(path)[0]
 
313
                except bzrerrors.NotBranchError:
 
314
                    self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
315
                    self.check_history.set_active(False)
 
316
                    self.check_history.set_sensitive(False)
 
317
                    return False
 
318
                except bzrerrors.UnsupportedProtocol:
 
319
                    self._show_stock_image(gtk.STOCK_DIALOG_ERROR)
 
320
                    self.check_history.set_active(False)
 
321
                    self.check_history.set_sensitive(False)
 
322
                    return False
 
323
                
 
324
                self._show_stock_image(gtk.STOCK_CONNECT)
 
325
                
 
326
                self.remote = True
 
327
               
 
328
                # We're remote
 
329
                self.remote_branch, self.remote_path = Branch.open_containing(path)
 
330
                
 
331
                if self.remote_revision is None:
 
332
                    self.remote_revision = self.remote_branch.last_revision()
 
333
                
 
334
                self.remote_entries = self.remote_branch.repository.get_inventory(self.remote_revision).entries()
 
335
                
 
336
                if len(self.remote_path) == 0:
 
337
                    self.remote_parent = self.remote_branch.repository.get_inventory(self.remote_branch.last_revision()).iter_entries_by_dir().next()[1].file_id
 
338
                else:
 
339
                    for (name, type) in self.remote_entries:
 
340
                        if name == self.remote_path:
 
341
                            self.remote_parent = type.file_id
 
342
                            break
 
343
                
 
344
                if not path.endswith('/'):
 
345
                    path += '/'
 
346
                
 
347
                if self.remote_branch.base == path:
 
348
                    self.button_location_up.set_sensitive(False)
 
349
                else:
 
350
                    self.button_location_up.set_sensitive(True)
 
351
        
 
352
        if self.notbranch:
 
353
            self.check_history.set_active(False)
 
354
            self.check_history.set_sensitive(False)
 
355
        else:
 
356
            self.check_history.set_sensitive(True)
 
357
        
 
358
        self.statusbar.push(self.context_id, path)
 
359
        self.entry_location.set_text(path)
 
360
        self.path = path
 
361
        return True
 
362
 
 
363
    def get_path(self):
 
364
        if not self.remote:
 
365
            return self.path
 
366
        else:
 
367
            # Remote mode
 
368
            if len(self.remote_path) > 0:
 
369
                return self.remote_branch.base + self.remote_path + '/'
 
370
            else:
 
371
                return self.remote_branch.base
 
372
   
 
373
    def on_about_activate(self, widget):
 
374
        about()
 
375
    
 
376
    def on_button_history_browse_clicked(self, widget):
 
377
        """ Browse for revision button handler. """
 
378
        if self.remote:
 
379
            br = self.remote_branch
 
380
        else:
 
381
            br = self.wt.branch
 
382
            
 
383
        revb = RevisionBrowser(br, self.window)
 
384
        response = revb.run()
 
385
        if response != gtk.RESPONSE_NONE:
 
386
            revb.hide()
 
387
        
 
388
            if response == gtk.RESPONSE_OK:
 
389
                if revb.selected_revno is not None:
 
390
                    self.entry_history.set_text(revb.selected_revno)
 
391
            
 
392
            revb.destroy()
 
393
    
 
394
    def on_button_location_jump_clicked(self, widget):
 
395
        """ Location Jump button handler. """
 
396
        location = self.entry_location.get_text()
 
397
        
 
398
        if self.set_path(location):
 
399
            self.refresh_right()
 
400
    
 
401
    def on_button_location_up_clicked(self, widget):
 
402
        """ Location Up button handler. """
 
403
        if not self.remote:
 
404
            # Local mode
 
405
            self.set_path(os.path.split(self.get_path())[0])
 
406
        else:
 
407
            # Remote mode
 
408
            delim = '/'
 
409
            newpath = delim.join(self.get_path().split(delim)[:-2])
 
410
            newpath += '/'
 
411
            self.set_path(newpath)
 
412
 
 
413
        self.refresh_right()
 
414
    
 
415
    def on_checkbutton_history_toggled(self, widget):
 
416
        """ History Mode toggle handler. """
 
417
        if self.check_history.get_active():
 
418
            # History Mode activated
 
419
            self.entry_history.set_sensitive(True)
 
420
            self.button_history.set_sensitive(True)
 
421
        else:
 
422
            # History Mode deactivated
 
423
            self.entry_history.set_sensitive(False)
 
424
            self.button_history.set_sensitive(False)
 
425
    
 
426
    @show_bzr_error
 
427
    def on_entry_history_revno_key_press_event(self, widget, event):
 
428
        """ Key pressed handler for the history entry. """
 
429
        if event.keyval == 65293:
 
430
            # Return was hit, so we have to load that specific revision
 
431
            # Emulate being remote, so inventory should be used
 
432
            path = self.get_path()
 
433
            if not self.remote:
 
434
                self.remote = True
 
435
                self.remote_branch = self.wt.branch
 
436
            
 
437
            revno = int(self.entry_history.get_text())
 
438
            self.remote_revision = self.remote_branch.get_rev_id(revno)
 
439
            if self.set_path(path, True):
 
440
                self.refresh_right()
 
441
    
 
442
    def on_entry_location_key_press_event(self, widget, event):
 
443
        """ Key pressed handler for the location entry. """
 
444
        if event.keyval == 65293:
 
445
            # Return was hit, so we have to jump
 
446
            self.on_button_location_jump_clicked(widget)
 
447
    
 
448
    def on_menuitem_add_files_activate(self, widget):
 
449
        """ Add file(s)... menu handler. """
 
450
        from add import OliveAdd
 
451
        add = OliveAdd(self.wt, self.wtpath, self.get_selected_right())
 
452
        add.display()
 
453
    
 
454
    def on_menuitem_branch_get_activate(self, widget):
 
455
        """ Branch/Get... menu handler. """
 
456
        from bzrlib.plugins.gtk.branch import BranchDialog
 
457
        
 
458
        if self.remote:
 
459
            branch = BranchDialog(os.getcwd(), self.window, self.remote_branch.base)
 
460
        else:
 
461
            branch = BranchDialog(self.get_path(), self.window)
 
462
        response = branch.run()
 
463
        if response != gtk.RESPONSE_NONE:
 
464
            branch.hide()
 
465
            
 
466
            if response == gtk.RESPONSE_OK:
 
467
                self.refresh_right()
 
468
            
 
469
            branch.destroy()
 
470
    
 
471
    def on_menuitem_branch_checkout_activate(self, widget):
 
472
        """ Branch/Checkout... menu handler. """
 
473
        from bzrlib.plugins.gtk.checkout import CheckoutDialog
 
474
        
 
475
        if self.remote:
 
476
            checkout = CheckoutDialog(os.getcwd(), self.window, self.remote_branch.base)
 
477
        else:
 
478
            checkout = CheckoutDialog(self.get_path(), self.window)
 
479
        response = checkout.run()
 
480
        if response != gtk.RESPONSE_NONE:
 
481
            checkout.hide()
 
482
        
 
483
            if response == gtk.RESPONSE_OK:
 
484
                self.refresh_right()
 
485
            
 
486
            checkout.destroy()
 
487
    
 
488
    @show_bzr_error
 
489
    def on_menuitem_branch_commit_activate(self, widget):
 
490
        """ Branch/Commit... menu handler. """
 
491
        commit = CommitDialog(self.wt, self.wtpath, self.notbranch, self.get_selected_right(), self.window)
 
492
        response = commit.run()
 
493
        if response != gtk.RESPONSE_NONE:
 
494
            commit.hide()
 
495
        
 
496
            if response == gtk.RESPONSE_OK:
 
497
                self.refresh_right()
 
498
            
 
499
            commit.destroy()
 
500
    
 
501
    def on_menuitem_branch_conflicts_activate(self, widget):
 
502
        """ Branch/Conflicts... menu handler. """
 
503
        conflicts = ConflictsDialog(self.wt, self.window)
 
504
        response = conflicts.run()
 
505
        if response != gtk.RESPONSE_NONE:
 
506
            conflicts.destroy()
 
507
    
 
508
    def on_menuitem_branch_merge_activate(self, widget):
 
509
        """ Branch/Merge... menu handler. """
 
510
        from bzrlib.plugins.gtk.merge import MergeDialog
 
511
        
 
512
        if self.check_for_changes():
 
513
            error_dialog(_('There are local changes in the branch'),
 
514
                         _('Please commit or revert the changes before merging.'))
 
515
        else:
 
516
            merge = MergeDialog(self.wt, self.wtpath)
 
517
            merge.display()
 
518
 
 
519
    @show_bzr_error
 
520
    def on_menuitem_branch_missing_revisions_activate(self, widget):
 
521
        """ Branch/Missing revisions menu handler. """
 
522
        local_branch = self.wt.branch
 
523
        
 
524
        other_branch = local_branch.get_parent()
 
525
        if other_branch is None:
 
526
            error_dialog(_('Parent location is unknown'),
 
527
                         _('Cannot determine missing revisions if no parent location is known.'))
 
528
            return
 
529
        
 
530
        remote_branch = Branch.open(other_branch)
 
531
        
 
532
        if remote_branch.base == local_branch.base:
 
533
            remote_branch = local_branch
 
534
 
 
535
        ret = len(local_branch.missing_revisions(remote_branch))
 
536
 
 
537
        if ret > 0:
 
538
            info_dialog(_('There are missing revisions'),
 
539
                        _('%d revision(s) missing.') % ret)
 
540
        else:
 
541
            info_dialog(_('Local branch up to date'),
 
542
                        _('There are no missing revisions.'))
 
543
 
 
544
    @show_bzr_error
 
545
    def on_menuitem_branch_pull_activate(self, widget):
 
546
        """ Branch/Pull menu handler. """
 
547
        branch_to = self.wt.branch
 
548
 
 
549
        location = branch_to.get_parent()
 
550
        if location is None:
 
551
            error_dialog(_('Parent location is unknown'),
 
552
                                     _('Pulling is not possible until there is a parent location.'))
 
553
            return
 
554
 
 
555
        branch_from = Branch.open(location)
 
556
 
 
557
        if branch_to.get_parent() is None:
 
558
            branch_to.set_parent(branch_from.base)
 
559
 
 
560
        ret = branch_to.pull(branch_from)
 
561
        
 
562
        info_dialog(_('Pull successful'), _('%d revision(s) pulled.') % ret)
 
563
    
 
564
    def on_menuitem_branch_push_activate(self, widget):
 
565
        """ Branch/Push... menu handler. """
 
566
        push = PushDialog(self.wt.branch, self.window)
 
567
        response = push.run()
 
568
        if response != gtk.RESPONSE_NONE:
 
569
            push.destroy()
 
570
    
 
571
    @show_bzr_error
 
572
    def on_menuitem_branch_revert_activate(self, widget):
 
573
        """ Branch/Revert all changes menu handler. """
 
574
        ret = self.wt.revert([])
 
575
        if ret:
 
576
            warning_dialog(_('Conflicts detected'),
 
577
                           _('Please have a look at the working tree before continuing.'))
 
578
        else:
 
579
            info_dialog(_('Revert successful'),
 
580
                        _('All files reverted to last revision.'))
 
581
        self.refresh_right()
 
582
    
 
583
    def on_menuitem_branch_status_activate(self, widget):
 
584
        """ Branch/Status... menu handler. """
 
585
        from bzrlib.plugins.gtk.status import StatusDialog
 
586
        status = StatusDialog(self.wt, self.wtpath)
 
587
        response = status.run()
 
588
        if response != gtk.RESPONSE_NONE:
 
589
            status.destroy()
 
590
    
 
591
    def on_menuitem_branch_initialize_activate(self, widget):
 
592
        """ Initialize current directory. """
 
593
        init = InitDialog(self.path, self.window)
 
594
        response = init.run()
 
595
        if response != gtk.RESPONSE_NONE:
 
596
            init.hide()
 
597
        
 
598
            if response == gtk.RESPONSE_OK:
 
599
                self.refresh_right()
 
600
            
 
601
            init.destroy()
 
602
        
 
603
    def on_menuitem_branch_tags_activate(self, widget):
 
604
        """ Branch/Tags... menu handler. """
 
605
        from bzrlib.plugins.gtk.tags import TagsWindow
 
606
        if not self.remote:
 
607
            window = TagsWindow(self.wt.branch, self.window)
 
608
        else:
 
609
            window = TagsWindow(self.remote_branch, self.window)
 
610
        window.show()
 
611
    
 
612
    def on_menuitem_file_annotate_activate(self, widget):
 
613
        """ File/Annotate... menu handler. """
 
614
        if self.get_selected_right() is None:
 
615
            error_dialog(_('No file was selected'),
 
616
                         _('Please select a file from the list.'))
 
617
            return
 
618
        
 
619
        branch = self.wt.branch
 
620
        file_id = self.wt.path2id(self.wt.relpath(os.path.join(self.path, self.get_selected_right())))
 
621
        
 
622
        window = GAnnotateWindow(all=False, plain=False)
 
623
        window.set_title(os.path.join(self.path, self.get_selected_right()) + " - Annotate")
 
624
        config = GAnnotateConfig(window)
 
625
        window.show()
 
626
        branch.lock_read()
 
627
        try:
 
628
            window.annotate(self.wt, branch, file_id)
 
629
        finally:
 
630
            branch.unlock()
 
631
    
 
632
    def on_menuitem_file_make_directory_activate(self, widget):
 
633
        """ File/Make directory... menu handler. """
 
634
        from mkdir import OliveMkdir
 
635
        mkdir = OliveMkdir(self.wt, self.wtpath)
 
636
        mkdir.display()
 
637
    
 
638
    def on_menuitem_file_move_activate(self, widget):
 
639
        """ File/Move... menu handler. """
 
640
        from move import OliveMove
 
641
        move = OliveMove(self.wt, self.wtpath, self.get_selected_right())
 
642
        move.display()
 
643
    
 
644
    def on_menuitem_file_rename_activate(self, widget):
 
645
        """ File/Rename... menu handler. """
 
646
        from rename import OliveRename
 
647
        rename = OliveRename(self.wt, self.wtpath, self.get_selected_right())
 
648
        rename.display()
 
649
 
 
650
    def on_menuitem_remove_file_activate(self, widget):
 
651
        """ Remove (unversion) selected file. """
 
652
        from remove import OliveRemoveDialog
 
653
        remove = OliveRemoveDialog(self.wt, self.wtpath,
 
654
                                   selected=self.get_selected_right(),
 
655
                                   parent=self.window)
 
656
        response = remove.run()
 
657
        
 
658
        if response != gtk.RESPONSE_NONE:
 
659
            remove.hide()
 
660
        
 
661
            if response == gtk.RESPONSE_OK:
 
662
                self.set_path(self.path)
 
663
                self.refresh_right()
 
664
            
 
665
            remove.destroy()
 
666
    
 
667
    def on_menuitem_stats_diff_activate(self, widget):
 
668
        """ Statistics/Differences... menu handler. """
 
669
        window = DiffWindow()
 
670
        parent_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
671
        window.set_diff(self.wt.branch.nick, self.wt, parent_tree)
 
672
        window.show()
 
673
    
 
674
    def on_menuitem_stats_infos_activate(self, widget):
 
675
        """ Statistics/Informations... menu handler. """
 
676
        from info import OliveInfo
 
677
        if self.remote:
 
678
            info = OliveInfo(self.remote_branch)
 
679
        else:
 
680
            info = OliveInfo(self.wt.branch)
 
681
        info.display()
 
682
    
 
683
    def on_menuitem_stats_log_activate(self, widget):
 
684
        """ Statistics/Log... menu handler. """
 
685
        window = branchwin.BranchWindow()
 
686
        if not self.remote:
 
687
            window.set_branch(self.wt.branch, self.wt.branch.last_revision(), None)
 
688
        else:
 
689
            window.set_branch(self.remote_branch, self.remote_branch.last_revision(), None)
 
690
        window.show()
 
691
    
 
692
    def on_menuitem_view_refresh_activate(self, widget):
 
693
        """ View/Refresh menu handler. """
 
694
        # Refresh the left pane
 
695
        self.refresh_left()
 
696
        # Refresh the right pane
 
697
        self.refresh_right()
 
698
   
 
699
    def on_menuitem_view_show_hidden_files_activate(self, widget):
 
700
        """ View/Show hidden files menu handler. """
 
701
        self.pref.set_preference('dotted_files', widget.get_active())
 
702
        if self.path is not None:
 
703
            self.refresh_right()
 
704
 
 
705
    def on_treeview_left_button_press_event(self, widget, event):
 
706
        """ Occurs when somebody right-clicks in the bookmark list. """
 
707
        if event.button == 3:
 
708
            # Don't show context with nothing selected
 
709
            if self.get_selected_left() == None:
 
710
                return
 
711
 
 
712
            # Create a menu
 
713
            from menu import OliveMenu
 
714
            menu = OliveMenu(path=self.get_path(),
 
715
                             selected=self.get_selected_left(),
 
716
                             app=self)
 
717
            
 
718
            menu.left_context_menu().popup(None, None, None, 0,
 
719
                                           event.time)
 
720
 
 
721
    def on_treeview_left_row_activated(self, treeview, path, view_column):
 
722
        """ Occurs when somebody double-clicks or enters an item in the
 
723
        bookmark list. """
 
724
 
 
725
        newdir = self.get_selected_left()
 
726
        if newdir == None:
 
727
            return
 
728
 
 
729
        if self.set_path(newdir):
 
730
            self.refresh_right()
 
731
 
 
732
    def on_treeview_right_button_press_event(self, widget, event):
 
733
        """ Occurs when somebody right-clicks in the file list. """
 
734
        if event.button == 3:
 
735
            # Create a menu
 
736
            from menu import OliveMenu
 
737
            menu = OliveMenu(path=self.get_path(),
 
738
                             selected=self.get_selected_right(),
 
739
                             app=self)
 
740
            # get the menu items
 
741
            m_open = menu.ui.get_widget('/context_right/open')
 
742
            m_add = menu.ui.get_widget('/context_right/add')
 
743
            m_remove = menu.ui.get_widget('/context_right/remove')
 
744
            m_rename = menu.ui.get_widget('/context_right/rename')
 
745
            m_revert = menu.ui.get_widget('/context_right/revert')
 
746
            m_commit = menu.ui.get_widget('/context_right/commit')
 
747
            m_annotate = menu.ui.get_widget('/context_right/annotate')
 
748
            m_diff = menu.ui.get_widget('/context_right/diff')
 
749
            # check if we're in a branch
 
750
            try:
 
751
                from bzrlib.branch import Branch
 
752
                Branch.open_containing(self.get_path())
 
753
                if self.remote:
 
754
                    m_open.set_sensitive(False)
 
755
                    m_add.set_sensitive(False)
 
756
                    m_remove.set_sensitive(False)
 
757
                    m_rename.set_sensitive(False)
 
758
                    m_revert.set_sensitive(False)
 
759
                    m_commit.set_sensitive(False)
 
760
                    m_annotate.set_sensitive(False)
 
761
                    m_diff.set_sensitive(False)
 
762
                else:
 
763
                    m_open.set_sensitive(True)
 
764
                    m_add.set_sensitive(True)
 
765
                    m_remove.set_sensitive(True)
 
766
                    m_rename.set_sensitive(True)
 
767
                    m_revert.set_sensitive(True)
 
768
                    m_commit.set_sensitive(True)
 
769
                    m_annotate.set_sensitive(True)
 
770
                    m_diff.set_sensitive(True)
 
771
            except bzrerrors.NotBranchError:
 
772
                m_open.set_sensitive(True)
 
773
                m_add.set_sensitive(False)
 
774
                m_remove.set_sensitive(False)
 
775
                m_rename.set_sensitive(False)
 
776
                m_revert.set_sensitive(False)
 
777
                m_commit.set_sensitive(False)
 
778
                m_annotate.set_sensitive(False)
 
779
                m_diff.set_sensitive(False)
 
780
 
 
781
            if not self.remote:
 
782
                menu.right_context_menu().popup(None, None, None, 0,
 
783
                                                event.time)
 
784
            else:
 
785
                menu.remote_context_menu().popup(None, None, None, 0,
 
786
                                                 event.time)
 
787
        
 
788
    def on_treeview_right_row_activated(self, treeview, path, view_column):
 
789
        """ Occurs when somebody double-clicks or enters an item in the
 
790
        file list. """
 
791
        from launch import launch
 
792
        
 
793
        newdir = self.get_selected_right()
 
794
        
 
795
        if not self.remote:
 
796
            # We're local
 
797
            if newdir == '..':
 
798
                self.set_path(os.path.split(self.get_path())[0])
 
799
            else:
 
800
                fullpath = os.path.join(self.get_path(), newdir)
 
801
                if os.path.isdir(fullpath):
 
802
                    # selected item is an existant directory
 
803
                    self.set_path(fullpath)
 
804
                else:
 
805
                    launch(fullpath)
 
806
        else:
 
807
            # We're remote
 
808
            if self._is_remote_dir(self.get_path() + newdir):
 
809
                self.set_path(self.get_path() + newdir)
 
810
        
 
811
        self.refresh_right()
 
812
    
 
813
    def on_window_main_delete_event(self, widget, event=None):
 
814
        """ Do some stuff before exiting. """
 
815
        width, height = self.window_main.get_size()
 
816
        self.pref.set_preference('window_width', width)
 
817
        self.pref.set_preference('window_height', height)
 
818
        x, y = self.window_main.get_position()
 
819
        self.pref.set_preference('window_x', x)
 
820
        self.pref.set_preference('window_y', y)
 
821
        self.pref.set_preference('paned_position',
 
822
                                 self.hpaned_main.get_position())
 
823
        
 
824
        self.pref.write()
 
825
        self.window_main.destroy()
 
826
        
 
827
    def _load_left(self):
 
828
        """ Load data into the left panel. (Bookmarks) """
 
829
        # Create TreeStore
 
830
        treestore = gtk.TreeStore(str, str)
 
831
        
 
832
        # Get bookmarks
 
833
        bookmarks = self.pref.get_bookmarks()
 
834
        
 
835
        # Add them to the TreeStore
 
836
        titer = treestore.append(None, [_('Bookmarks'), None])
 
837
        for item in bookmarks:
 
838
            title = self.pref.get_bookmark_title(item)
 
839
            treestore.append(titer, [title, item])
 
840
        
 
841
        # Create the column and add it to the TreeView
 
842
        self.treeview_left.set_model(treestore)
 
843
        tvcolumn_bookmark = gtk.TreeViewColumn(_('Bookmark'))
 
844
        self.treeview_left.append_column(tvcolumn_bookmark)
 
845
        
 
846
        # Set up the cells
 
847
        cell = gtk.CellRendererText()
 
848
        tvcolumn_bookmark.pack_start(cell, True)
 
849
        tvcolumn_bookmark.add_attribute(cell, 'text', 0)
 
850
        
 
851
        # Expand the tree
 
852
        self.treeview_left.expand_all()
 
853
 
 
854
    def _load_right(self):
 
855
        """ Load data into the right panel. (Filelist) """
 
856
        # Create ListStore
 
857
        # Model: [ icon, dir, name, status text, status, size (int), size (human), mtime (int), mtime (local), fileid ]
 
858
        liststore = gtk.ListStore(gobject.TYPE_STRING,
 
859
                                  gobject.TYPE_BOOLEAN,
 
860
                                  gobject.TYPE_STRING,
 
861
                                  gobject.TYPE_STRING,
 
862
                                  gobject.TYPE_STRING,
 
863
                                  gobject.TYPE_STRING,
 
864
                                  gobject.TYPE_STRING,
 
865
                                  gobject.TYPE_INT,
 
866
                                  gobject.TYPE_STRING,
 
867
                                  gobject.TYPE_STRING)
 
868
        
 
869
        dirs = []
 
870
        files = []
 
871
        
 
872
        # Fill the appropriate lists
 
873
        dotted_files = self.pref.get_preference('dotted_files', 'bool')
 
874
        for item in os.listdir(self.path):
 
875
            if not dotted_files and item[0] == '.':
 
876
                continue
 
877
            if os.path.isdir(self.path + os.sep + item):
 
878
                dirs.append(item)
 
879
            else:
 
880
                files.append(item)
 
881
        
 
882
        if not self.notbranch:
 
883
            branch = self.wt.branch
 
884
            tree2 = self.wt.branch.repository.revision_tree(branch.last_revision())
 
885
        
 
886
            delta = self.wt.changes_from(tree2, want_unchanged=True)
 
887
        
 
888
        # Add'em to the ListStore
 
889
        for item in dirs:
 
890
            statinfo = os.stat(self.path + os.sep + item)
 
891
            liststore.append([ gtk.STOCK_DIRECTORY,
 
892
                               True,
 
893
                               item,
 
894
                               '',
 
895
                               '',
 
896
                               "<DIR>",
 
897
                               "<DIR>",
 
898
                               statinfo.st_mtime,
 
899
                               self._format_date(statinfo.st_mtime),
 
900
                               ''])
 
901
        for item in files:
 
902
            status = 'unknown'
 
903
            fileid = ''
 
904
            if not self.notbranch:
 
905
                filename = self.wt.relpath(self.path + os.sep + item)
 
906
                
 
907
                try:
 
908
                    self.wt.lock_read()
 
909
                    
 
910
                    for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
 
911
                        if rpathnew == filename:
 
912
                            status = 'renamed'
 
913
                            fileid = id
 
914
                    for rpath, id, kind in delta.added:
 
915
                        if rpath == filename:
 
916
                            status = 'added'
 
917
                            fileid = id
 
918
                    for rpath, id, kind in delta.removed:
 
919
                        if rpath == filename:
 
920
                            status = 'removed'
 
921
                            fileid = id
 
922
                    for rpath, id, kind, text_modified, meta_modified in delta.modified:
 
923
                        if rpath == filename:
 
924
                            status = 'modified'
 
925
                            fileid = id
 
926
                    for rpath, id, kind in delta.unchanged:
 
927
                        if rpath == filename:
 
928
                            status = 'unchanged'
 
929
                            fileid = id
 
930
                    for rpath, file_class, kind, id, entry in self.wt.list_files():
 
931
                        if rpath == filename and file_class == 'I':
 
932
                            status = 'ignored'
 
933
                finally:
 
934
                    self.wt.unlock()
 
935
            
 
936
            if status == 'renamed':
 
937
                st = _('renamed')
 
938
            elif status == 'removed':
 
939
                st = _('removed')
 
940
            elif status == 'added':
 
941
                st = _('added')
 
942
            elif status == 'modified':
 
943
                st = _('modified')
 
944
            elif status == 'unchanged':
 
945
                st = _('unchanged')
 
946
            elif status == 'ignored':
 
947
                st = _('ignored')
 
948
            else:
 
949
                st = _('unknown')
 
950
            
 
951
            statinfo = os.stat(self.path + os.sep + item)
 
952
            liststore.append([gtk.STOCK_FILE,
 
953
                              False,
 
954
                              item,
 
955
                              st,
 
956
                              status,
 
957
                              str(statinfo.st_size), # NOTE: if int used there it will fail for large files (size expressed as long int)
 
958
                              self._format_size(statinfo.st_size),
 
959
                              statinfo.st_mtime,
 
960
                              self._format_date(statinfo.st_mtime),
 
961
                              fileid])
 
962
        
 
963
        # Create the columns and add them to the TreeView
 
964
        self.treeview_right.set_model(liststore)
 
965
        self._tvcolumn_filename = gtk.TreeViewColumn(_('Filename'))
 
966
        self._tvcolumn_status = gtk.TreeViewColumn(_('Status'))
 
967
        self._tvcolumn_size = gtk.TreeViewColumn(_('Size'))
 
968
        self._tvcolumn_mtime = gtk.TreeViewColumn(_('Last modified'))
 
969
        self.treeview_right.append_column(self._tvcolumn_filename)
 
970
        self.treeview_right.append_column(self._tvcolumn_status)
 
971
        self.treeview_right.append_column(self._tvcolumn_size)
 
972
        self.treeview_right.append_column(self._tvcolumn_mtime)
 
973
        
 
974
        # Set up the cells
 
975
        cellpb = gtk.CellRendererPixbuf()
 
976
        cell = gtk.CellRendererText()
 
977
        self._tvcolumn_filename.pack_start(cellpb, False)
 
978
        self._tvcolumn_filename.pack_start(cell, True)
 
979
        self._tvcolumn_filename.set_attributes(cellpb, stock_id=0)
 
980
        self._tvcolumn_filename.add_attribute(cell, 'text', 2)
 
981
        self._tvcolumn_status.pack_start(cell, True)
 
982
        self._tvcolumn_status.add_attribute(cell, 'text', 3)
 
983
        self._tvcolumn_size.pack_start(cell, True)
 
984
        self._tvcolumn_size.add_attribute(cell, 'text', 6)
 
985
        self._tvcolumn_mtime.pack_start(cell, True)
 
986
        self._tvcolumn_mtime.add_attribute(cell, 'text', 8)
 
987
        
 
988
        # Set up the properties of the TreeView
 
989
        self.treeview_right.set_headers_visible(True)
 
990
        self.treeview_right.set_headers_clickable(True)
 
991
        self.treeview_right.set_search_column(1)
 
992
        self._tvcolumn_filename.set_resizable(True)
 
993
        self._tvcolumn_status.set_resizable(True)
 
994
        self._tvcolumn_size.set_resizable(True)
 
995
        self._tvcolumn_mtime.set_resizable(True)
 
996
        # Set up sorting
 
997
        liststore.set_sort_func(13, self._sort_filelist_callback, None)
 
998
        liststore.set_sort_column_id(13, gtk.SORT_ASCENDING)
 
999
        self._tvcolumn_filename.set_sort_column_id(13)
 
1000
        self._tvcolumn_status.set_sort_column_id(3)
 
1001
        self._tvcolumn_size.set_sort_column_id(5)
 
1002
        self._tvcolumn_mtime.set_sort_column_id(7)
 
1003
        
 
1004
        # Set sensitivity
 
1005
        self.set_sensitivity()
 
1006
        
 
1007
    def get_selected_fileid(self):
 
1008
        """ Get the file_id of the selected file. """
 
1009
        treeselection = self.treeview_right.get_selection()
 
1010
        (model, iter) = treeselection.get_selected()
 
1011
        
 
1012
        if iter is None:
 
1013
            return None
 
1014
        else:
 
1015
            return model.get_value(iter, 9)
 
1016
    
 
1017
    def get_selected_right(self):
 
1018
        """ Get the selected filename. """
 
1019
        treeselection = self.treeview_right.get_selection()
 
1020
        (model, iter) = treeselection.get_selected()
 
1021
        
 
1022
        if iter is None:
 
1023
            return None
 
1024
        else:
 
1025
            return model.get_value(iter, 2)
 
1026
    
 
1027
    def get_selected_left(self):
 
1028
        """ Get the selected bookmark. """
 
1029
        treeselection = self.treeview_left.get_selection()
 
1030
        (model, iter) = treeselection.get_selected()
 
1031
        
 
1032
        if iter is None:
 
1033
            return None
 
1034
        else:
 
1035
            return model.get_value(iter, 1)
 
1036
 
 
1037
    def set_statusbar(self, message):
 
1038
        """ Set the statusbar message. """
 
1039
        self.statusbar.push(self.context_id, message)
 
1040
    
 
1041
    def clear_statusbar(self):
 
1042
        """ Clean the last message from the statusbar. """
 
1043
        self.statusbar.pop(self.context_id)
 
1044
    
 
1045
    def set_sensitivity(self):
 
1046
        """ Set menu and toolbar sensitivity. """
 
1047
        if not self.remote:
 
1048
            # We're local
 
1049
            self.menuitem_branch_init.set_sensitive(self.notbranch)
 
1050
            self.menuitem_branch_get.set_sensitive(self.notbranch)
 
1051
            self.menuitem_branch_checkout.set_sensitive(self.notbranch)
 
1052
            self.menuitem_branch_pull.set_sensitive(not self.notbranch)
 
1053
            self.menuitem_branch_push.set_sensitive(not self.notbranch)
 
1054
            self.menuitem_branch_revert.set_sensitive(not self.notbranch)
 
1055
            self.menuitem_branch_merge.set_sensitive(not self.notbranch)
 
1056
            self.menuitem_branch_commit.set_sensitive(not self.notbranch)
 
1057
            self.menuitem_branch_tags.set_sensitive(not self.notbranch)
 
1058
            self.menuitem_branch_status.set_sensitive(not self.notbranch)
 
1059
            self.menuitem_branch_missing.set_sensitive(not self.notbranch)
 
1060
            self.menuitem_branch_conflicts.set_sensitive(not self.notbranch)
 
1061
            self.menuitem_stats.set_sensitive(not self.notbranch)
 
1062
            self.menuitem_stats_diff.set_sensitive(not self.notbranch)
 
1063
            self.menuitem_add_files.set_sensitive(not self.notbranch)
 
1064
            self.menuitem_remove_files.set_sensitive(not self.notbranch)
 
1065
            self.menuitem_file_make_directory.set_sensitive(not self.notbranch)
 
1066
            self.menuitem_file_rename.set_sensitive(not self.notbranch)
 
1067
            self.menuitem_file_move.set_sensitive(not self.notbranch)
 
1068
            self.menuitem_file_annotate.set_sensitive(not self.notbranch)
 
1069
            #self.menutoolbutton_diff.set_sensitive(True)
 
1070
            self.toolbutton_diff.set_sensitive(not self.notbranch)
 
1071
            self.toolbutton_log.set_sensitive(not self.notbranch)
 
1072
            self.toolbutton_commit.set_sensitive(not self.notbranch)
 
1073
            self.toolbutton_pull.set_sensitive(not self.notbranch)
 
1074
            self.toolbutton_push.set_sensitive(not self.notbranch)
 
1075
        else:
 
1076
            # We're remote
 
1077
            self.menuitem_branch_init.set_sensitive(False)
 
1078
            self.menuitem_branch_get.set_sensitive(True)
 
1079
            self.menuitem_branch_checkout.set_sensitive(True)
 
1080
            self.menuitem_branch_pull.set_sensitive(False)
 
1081
            self.menuitem_branch_push.set_sensitive(False)
 
1082
            self.menuitem_branch_revert.set_sensitive(False)
 
1083
            self.menuitem_branch_merge.set_sensitive(False)
 
1084
            self.menuitem_branch_commit.set_sensitive(False)
 
1085
            self.menuitem_branch_tags.set_sensitive(True)
 
1086
            self.menuitem_branch_status.set_sensitive(False)
 
1087
            self.menuitem_branch_missing.set_sensitive(False)
 
1088
            self.menuitem_branch_conflicts.set_sensitive(False)
 
1089
            self.menuitem_stats.set_sensitive(True)
 
1090
            self.menuitem_stats_diff.set_sensitive(False)
 
1091
            self.menuitem_add_files.set_sensitive(False)
 
1092
            self.menuitem_remove_files.set_sensitive(False)
 
1093
            self.menuitem_file_make_directory.set_sensitive(False)
 
1094
            self.menuitem_file_rename.set_sensitive(False)
 
1095
            self.menuitem_file_move.set_sensitive(False)
 
1096
            self.menuitem_file_annotate.set_sensitive(False)
 
1097
            #self.menutoolbutton_diff.set_sensitive(True)
 
1098
            self.toolbutton_diff.set_sensitive(False)
 
1099
            self.toolbutton_log.set_sensitive(True)
 
1100
            self.toolbutton_commit.set_sensitive(False)
 
1101
            self.toolbutton_pull.set_sensitive(False)
 
1102
            self.toolbutton_push.set_sensitive(False)
 
1103
    
 
1104
    def refresh_left(self):
 
1105
        """ Refresh the bookmark list. """
 
1106
        
 
1107
        # Get TreeStore and clear it
 
1108
        treestore = self.treeview_left.get_model()
 
1109
        treestore.clear()
 
1110
 
 
1111
        # Re-read preferences
 
1112
        self.pref.read()
 
1113
        
 
1114
        # Get bookmarks
 
1115
        bookmarks = self.pref.get_bookmarks()
 
1116
 
 
1117
        # Add them to the TreeStore
 
1118
        titer = treestore.append(None, [_('Bookmarks'), None])
 
1119
        for item in bookmarks:
 
1120
            title = self.pref.get_bookmark_title(item)
 
1121
            treestore.append(titer, [title, item])
 
1122
 
 
1123
        # Add the TreeStore to the TreeView
 
1124
        self.treeview_left.set_model(treestore)
 
1125
 
 
1126
        # Expand the tree
 
1127
        self.treeview_left.expand_all()
 
1128
 
 
1129
    def refresh_right(self, path=None):
 
1130
        """ Refresh the file list. """
 
1131
        if not self.remote:
 
1132
            # We're local
 
1133
            from bzrlib.workingtree import WorkingTree
 
1134
    
 
1135
            if path is None:
 
1136
                path = self.get_path()
 
1137
    
 
1138
            # A workaround for double-clicking Bookmarks
 
1139
            if not os.path.exists(path):
 
1140
                return
 
1141
    
 
1142
            # Get ListStore and clear it
 
1143
            liststore = self.treeview_right.get_model()
 
1144
            liststore.clear()
 
1145
            
 
1146
            # Show Status column
 
1147
            self._tvcolumn_status.set_visible(True)
 
1148
    
 
1149
            dirs = []
 
1150
            files = []
 
1151
    
 
1152
            # Fill the appropriate lists
 
1153
            dotted_files = self.pref.get_preference('dotted_files', 'bool')
 
1154
            for item in os.listdir(path):
 
1155
                if not dotted_files and item[0] == '.':
 
1156
                    continue
 
1157
                if os.path.isdir(path + os.sep + item):
 
1158
                    dirs.append(item)
 
1159
                else:
 
1160
                    files.append(item)
 
1161
            
 
1162
            # Try to open the working tree
 
1163
            notbranch = False
 
1164
            try:
 
1165
                tree1 = WorkingTree.open_containing(path)[0]
 
1166
            except (bzrerrors.NotBranchError, bzrerrors.NoWorkingTree):
 
1167
                notbranch = True
 
1168
            
 
1169
            if not notbranch:
 
1170
                branch = tree1.branch
 
1171
                tree2 = tree1.branch.repository.revision_tree(branch.last_revision())
 
1172
            
 
1173
                delta = tree1.changes_from(tree2, want_unchanged=True)
 
1174
                
 
1175
            # Add'em to the ListStore
 
1176
            for item in dirs:
 
1177
                statinfo = os.stat(self.path + os.sep + item)
 
1178
                liststore.append([gtk.STOCK_DIRECTORY,
 
1179
                                  True,
 
1180
                                  item,
 
1181
                                  '',
 
1182
                                  '',
 
1183
                                  "<DIR>",
 
1184
                                  "<DIR>",
 
1185
                                  statinfo.st_mtime,
 
1186
                                  self._format_date(statinfo.st_mtime),
 
1187
                                  ''])
 
1188
            for item in files:
 
1189
                status = 'unknown'
 
1190
                fileid = ''
 
1191
                if not notbranch:
 
1192
                    filename = tree1.relpath(path + os.sep + item)
 
1193
                    
 
1194
                    try:
 
1195
                        self.wt.lock_read()
 
1196
                        
 
1197
                        for rpath, rpathnew, id, kind, text_modified, meta_modified in delta.renamed:
 
1198
                            if rpathnew == filename:
 
1199
                                status = 'renamed'
 
1200
                                fileid = id
 
1201
                        for rpath, id, kind in delta.added:
 
1202
                            if rpath == filename:
 
1203
                                status = 'added'
 
1204
                                fileid = id
 
1205
                        for rpath, id, kind in delta.removed:
 
1206
                            if rpath == filename:
 
1207
                                status = 'removed'
 
1208
                                fileid = id
 
1209
                        for rpath, id, kind, text_modified, meta_modified in delta.modified:
 
1210
                            if rpath == filename:
 
1211
                                status = 'modified'
 
1212
                                fileid = id
 
1213
                        for rpath, id, kind in delta.unchanged:
 
1214
                            if rpath == filename:
 
1215
                                status = 'unchanged'
 
1216
                                fileid = id
 
1217
                        for rpath, file_class, kind, id, entry in self.wt.list_files():
 
1218
                            if rpath == filename and file_class == 'I':
 
1219
                                status = 'ignored'
 
1220
                    finally:
 
1221
                        self.wt.unlock()
 
1222
                
 
1223
                if status == 'renamed':
 
1224
                    st = _('renamed')
 
1225
                elif status == 'removed':
 
1226
                    st = _('removed')
 
1227
                elif status == 'added':
 
1228
                    st = _('added')
 
1229
                elif status == 'modified':
 
1230
                    st = _('modified')
 
1231
                elif status == 'unchanged':
 
1232
                    st = _('unchanged')
 
1233
                elif status == 'ignored':
 
1234
                    st = _('ignored')
 
1235
                else:
 
1236
                    st = _('unknown')
 
1237
                
 
1238
                statinfo = os.stat(self.path + os.sep + item)
 
1239
                liststore.append([gtk.STOCK_FILE,
 
1240
                                  False,
 
1241
                                  item,
 
1242
                                  st,
 
1243
                                  status,
 
1244
                                  str(statinfo.st_size),
 
1245
                                  self._format_size(statinfo.st_size),
 
1246
                                  statinfo.st_mtime,
 
1247
                                  self._format_date(statinfo.st_mtime),
 
1248
                                  fileid])
 
1249
        else:
 
1250
            # We're remote
 
1251
            
 
1252
            # Get ListStore and clear it
 
1253
            liststore = self.treeview_right.get_model()
 
1254
            liststore.clear()
 
1255
            
 
1256
            # Hide Status column
 
1257
            self._tvcolumn_status.set_visible(False)
 
1258
            
 
1259
            dirs = []
 
1260
            files = []
 
1261
            
 
1262
            self._show_stock_image(gtk.STOCK_REFRESH)
 
1263
            
 
1264
            for (name, type) in self.remote_entries:
 
1265
                if type.kind == 'directory':
 
1266
                    dirs.append(type)
 
1267
                elif type.kind == 'file':
 
1268
                    files.append(type)
 
1269
            
 
1270
            class HistoryCache:
 
1271
                """ Cache based on revision history. """
 
1272
                def __init__(self, history):
 
1273
                    self._history = history
 
1274
                
 
1275
                def _lookup_revision(self, revid):
 
1276
                    for r in self._history:
 
1277
                        if r.revision_id == revid:
 
1278
                            return r
 
1279
                    rev = repo.get_revision(revid)
 
1280
                    self._history.append(rev)
 
1281
                    return rev
 
1282
            
 
1283
            repo = self.remote_branch.repository
 
1284
            
 
1285
            revhistory = self.remote_branch.revision_history()
 
1286
            try:
 
1287
                revs = repo.get_revisions(revhistory)
 
1288
                cache = HistoryCache(revs)
 
1289
            except bzrerrors.InvalidHttpResponse:
 
1290
                # Fallback to dummy algorithm, because of LP: #115209
 
1291
                cache = HistoryCache([])
 
1292
            
 
1293
            for item in dirs:
 
1294
                if item.parent_id == self.remote_parent:
 
1295
                    rev = cache._lookup_revision(item.revision)
 
1296
                    liststore.append([ gtk.STOCK_DIRECTORY,
 
1297
                                       True,
 
1298
                                       item.name,
 
1299
                                       '',
 
1300
                                       '',
 
1301
                                       "<DIR>",
 
1302
                                       "<DIR>",
 
1303
                                       rev.timestamp,
 
1304
                                       self._format_date(rev.timestamp),
 
1305
                                       ''
 
1306
                                   ])
 
1307
                while gtk.events_pending():
 
1308
                    gtk.main_iteration()
 
1309
            
 
1310
            for item in files:
 
1311
                if item.parent_id == self.remote_parent:
 
1312
                    rev = cache._lookup_revision(item.revision)
 
1313
                    liststore.append([ gtk.STOCK_FILE,
 
1314
                                       False,
 
1315
                                       item.name,
 
1316
                                       '',
 
1317
                                       '',
 
1318
                                       str(item.text_size),
 
1319
                                       self._format_size(item.text_size),
 
1320
                                       rev.timestamp,
 
1321
                                       self._format_date(rev.timestamp),
 
1322
                                       item.file_id
 
1323
                                   ])
 
1324
                while gtk.events_pending():
 
1325
                    gtk.main_iteration()
 
1326
            
 
1327
            self.image_location_error.destroy()
 
1328
 
 
1329
        # Columns should auto-size
 
1330
        self.treeview_right.columns_autosize()
 
1331
        
 
1332
        # Set sensitivity
 
1333
        self.set_sensitivity()
 
1334
 
 
1335
    def _harddisks(self):
 
1336
        """ Returns hard drive letters under Win32. """
 
1337
        try:
 
1338
            import win32file
 
1339
            import string
 
1340
        except ImportError:
 
1341
            if sys.platform == 'win32':
 
1342
                print "pyWin32 modules needed to run Olive on Win32."
 
1343
                sys.exit(1)
 
1344
            else:
 
1345
                pass
 
1346
        
 
1347
        driveletters = []
 
1348
        for drive in string.ascii_uppercase:
 
1349
            if win32file.GetDriveType(drive+':') == win32file.DRIVE_FIXED:
 
1350
                driveletters.append(drive+':')
 
1351
        return driveletters
 
1352
    
 
1353
    def gen_hard_selector(self):
 
1354
        """ Generate the hard drive selector under Win32. """
 
1355
        drives = self._harddisks()
 
1356
        for drive in drives:
 
1357
            self.combobox_drive.append_text(drive)
 
1358
        self.combobox_drive.set_active(drives.index(os.getcwd()[0:2]))
 
1359
    
 
1360
    def _refresh_drives(self, combobox):
 
1361
        if self._just_started:
 
1362
            return
 
1363
        model = combobox.get_model()
 
1364
        active = combobox.get_active()
 
1365
        if active >= 0:
 
1366
            drive = model[active][0]
 
1367
            self.set_path(drive + '\\')
 
1368
            self.refresh_right(drive + '\\')
 
1369
    
 
1370
    def check_for_changes(self):
 
1371
        """ Check whether there were changes in the current working tree. """
 
1372
        old_tree = self.wt.branch.repository.revision_tree(self.wt.branch.last_revision())
 
1373
        delta = self.wt.changes_from(old_tree)
 
1374
 
 
1375
        changes = False
 
1376
        
 
1377
        if len(delta.added) or len(delta.removed) or len(delta.renamed) or len(delta.modified):
 
1378
            changes = True
 
1379
        
 
1380
        return changes
 
1381
    
 
1382
    def _sort_filelist_callback(self, model, iter1, iter2, data):
 
1383
        """ The sort callback for the file list, return values:
 
1384
        -1: iter1 < iter2
 
1385
        0: iter1 = iter2
 
1386
        1: iter1 > iter2
 
1387
        """
 
1388
        name1 = model.get_value(iter1, 2)
 
1389
        name2 = model.get_value(iter2, 2)
 
1390
        
 
1391
        if model.get_value(iter1, 1):
 
1392
            # item1 is a directory
 
1393
            if not model.get_value(iter2, 1):
 
1394
                # item2 isn't
 
1395
                return -1
 
1396
            else:
 
1397
                # both of them are directories, we compare their names
 
1398
                if name1 < name2:
 
1399
                    return -1
 
1400
                elif name1 == name2:
 
1401
                    return 0
 
1402
                else:
 
1403
                    return 1
 
1404
        else:
 
1405
            # item1 is not a directory
 
1406
            if model.get_value(iter2, 1):
 
1407
                # item2 is
 
1408
                return 1
 
1409
            else:
 
1410
                # both of them are files, compare them
 
1411
                if name1 < name2:
 
1412
                    return -1
 
1413
                elif name1 == name2:
 
1414
                    return 0
 
1415
                else:
 
1416
                    return 1
 
1417
    
 
1418
    def _format_size(self, size):
 
1419
        """ Format size to a human readable format. """
 
1420
        if size < 1000:
 
1421
            return "%d[B]" % (size,)
 
1422
        size = size / 1000.0
 
1423
        
 
1424
        for metric in ["kB","MB","GB","TB"]:
 
1425
            if size < 1000:
 
1426
                break
 
1427
            size = size / 1000.0
 
1428
        return "%.1f[%s]" % (size,metric) 
 
1429
    
 
1430
    def _format_date(self, timestamp):
 
1431
        """ Format the time (given in secs) to a human readable format. """
 
1432
        return time.ctime(timestamp)
 
1433
    
 
1434
    def _is_remote_dir(self, location):
 
1435
        """ Determine whether the given location is a directory or not. """
 
1436
        if not self.remote:
 
1437
            # We're in local mode
 
1438
            return False
 
1439
        else:
 
1440
            branch, path = Branch.open_containing(location)
 
1441
            for (name, type) in self.remote_entries:
 
1442
                if name == path and type.kind == 'directory':
 
1443
                    # We got it
 
1444
                    return True
 
1445
            # Either it's not a directory or not in the inventory
 
1446
            return False
 
1447
    
 
1448
    def _show_stock_image(self, stock_id):
 
1449
        """ Show a stock image next to the location entry. """
 
1450
        self.image_location_error.destroy()
 
1451
        self.image_location_error = gtk.image_new_from_stock(stock_id, gtk.ICON_SIZE_BUTTON)
 
1452
        self.hbox_location.pack_start(self.image_location_error, False, False, 0)
 
1453
        if sys.platform == 'win32':
 
1454
            self.hbox_location.reorder_child(self.image_location_error, 2)
 
1455
        else:
 
1456
            self.hbox_location.reorder_child(self.image_location_error, 1)
 
1457
        self.image_location_error.show()
 
1458
        while gtk.events_pending():
 
1459
            gtk.main_iteration()
 
1460
 
 
1461
import ConfigParser
 
1462
 
 
1463
class Preferences:
 
1464
    """ A class which handles Olive's preferences. """
 
1465
    def __init__(self, path=None):
 
1466
        """ Initialize the Preferences class. """
 
1467
        # Some default options
 
1468
        self.defaults = { 'strict_commit' : False,
 
1469
                          'dotted_files'  : False,
 
1470
                          'window_width'  : 700,
 
1471
                          'window_height' : 400,
 
1472
                          'window_x'      : 40,
 
1473
                          'window_y'      : 40,
 
1474
                          'paned_position': 200 }
 
1475
 
 
1476
        # Create a config parser object
 
1477
        self.config = ConfigParser.RawConfigParser()
 
1478
 
 
1479
        # Set filename
 
1480
        if path is None:
 
1481
            if sys.platform == 'win32':
 
1482
                # Windows - no dotted files
 
1483
                self._filename = os.path.expanduser('~/olive.conf')
 
1484
            else:
 
1485
                self._filename = os.path.expanduser('~/.olive.conf')
 
1486
        else:
 
1487
            self._filename = path
 
1488
        
 
1489
        # Load the configuration
 
1490
        self.read()
 
1491
        
 
1492
    def _get_default(self, option):
 
1493
        """ Get the default option for a preference. """
 
1494
        try:
 
1495
            ret = self.defaults[option]
 
1496
        except KeyError:
 
1497
            return None
 
1498
        else:
 
1499
            return ret
 
1500
 
 
1501
    def refresh(self):
 
1502
        """ Refresh the configuration. """
 
1503
        # First write out the changes
 
1504
        self.write()
 
1505
        # Then load the configuration again
 
1506
        self.read()
 
1507
 
 
1508
    def read(self):
 
1509
        """ Just read the configuration. """
 
1510
        # Re-initialize the config parser object to avoid some bugs
 
1511
        self.config = ConfigParser.RawConfigParser()
 
1512
        self.config.read([self._filename])
 
1513
    
 
1514
    def write(self):
 
1515
        """ Write the configuration to the appropriate files. """
 
1516
        fp = open(self._filename, 'w')
 
1517
        self.config.write(fp)
 
1518
        fp.close()
 
1519
 
 
1520
    def get_bookmarks(self):
 
1521
        """ Return the list of bookmarks. """
 
1522
        bookmarks = self.config.sections()
 
1523
        if self.config.has_section('preferences'):
 
1524
            bookmarks.remove('preferences')
 
1525
        return bookmarks
 
1526
 
 
1527
    def add_bookmark(self, path):
 
1528
        """ Add bookmark. """
 
1529
        try:
 
1530
            self.config.add_section(path)
 
1531
        except ConfigParser.DuplicateSectionError:
 
1532
            return False
 
1533
        else:
 
1534
            return True
 
1535
 
 
1536
    def get_bookmark_title(self, path):
 
1537
        """ Get bookmark title. """
 
1538
        try:
 
1539
            ret = self.config.get(path, 'title')
 
1540
        except ConfigParser.NoOptionError:
 
1541
            ret = path
 
1542
        
 
1543
        return ret
 
1544
    
 
1545
    def set_bookmark_title(self, path, title):
 
1546
        """ Set bookmark title. """
 
1547
        # FIXME: What if path isn't listed yet?
 
1548
        # FIXME: Canonicalize paths first?
 
1549
        self.config.set(path, 'title', title)
 
1550
    
 
1551
    def remove_bookmark(self, path):
 
1552
        """ Remove bookmark. """
 
1553
        return self.config.remove_section(path)
 
1554
 
 
1555
    def set_preference(self, option, value):
 
1556
        """ Set the value of the given option. """
 
1557
        if value is True:
 
1558
            value = 'yes'
 
1559
        elif value is False:
 
1560
            value = 'no'
 
1561
        
 
1562
        if self.config.has_section('preferences'):
 
1563
            self.config.set('preferences', option, value)
 
1564
        else:
 
1565
            self.config.add_section('preferences')
 
1566
            self.config.set('preferences', option, value)
 
1567
 
 
1568
    def get_preference(self, option, kind='str'):
 
1569
        """ Get the value of the given option.
 
1570
        
 
1571
        :param kind: str/bool/int/float. default: str
 
1572
        """
 
1573
        if self.config.has_option('preferences', option):
 
1574
            if kind == 'bool':
 
1575
                return self.config.getboolean('preferences', option)
 
1576
            elif kind == 'int':
 
1577
                return self.config.getint('preferences', option)
 
1578
            elif kind == 'float':
 
1579
                return self.config.getfloat('preferences', option)
 
1580
            else:
 
1581
                return self.config.get('preferences', option)
 
1582
        else:
 
1583
            try:
 
1584
                return self._get_default(option)
 
1585
            except KeyError:
 
1586
                return None
 
1587