1
### Copyright (C) 2002-2006 Stephen Kennedy <stevek@gnome.org>
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.
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.
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
21
from gettext import gettext as _
29
# Drag'N'Drop support needs gnomevfs
32
gnomevfs_available = True
34
gnomevfs_available = False
48
from sourceviewer import srcviewer
52
################################################################################
56
################################################################################
58
class NewDocDialog(gnomeglade.Component):
59
def __init__(self, parentapp):
60
gnomeglade.Component.__init__(self, paths.ui_dir("meldapp.glade"), "newdialog")
61
self.map_widgets_into_lists(["fileentry", "direntry", "vcentry", "three_way_compare"])
62
self.entrylists = self.fileentry, self.direntry, self.vcentry
63
self.widget.set_transient_for(parentapp.widget)
64
self.fileentry[0].set_sensitive(self.three_way_compare[0].get_active())
65
self.direntry[0].set_sensitive(self.three_way_compare[1].get_active())
66
self.diff_methods = (parentapp.append_filediff,
67
parentapp.append_dirdiff,
68
parentapp.append_vcview)
69
self.widget.show_all()
71
def on_entry_activate(self, entry):
72
for el in self.entrylists:
76
self.button_ok.grab_focus()
80
def on_three_way_toggled(self, button):
81
page = self.three_way_compare.index(button)
82
self.entrylists[page][0].set_sensitive( button.get_active() )
83
self.entrylists[page][not button.get_active()].focus_entry()
85
def on_response(self, dialog, arg):
86
if arg == gtk.RESPONSE_OK:
87
page = self.notebook.get_current_page()
88
paths = [e.get_full_path() or "" for e in self.entrylists[page]]
89
if page < 2 and not self.three_way_compare[page].get_active():
92
self.entrylists[page][0].prepend_history(path)
93
self.diff_methods[page](paths)
96
################################################################################
100
################################################################################
101
class ListWidget(gnomeglade.Component):
102
def __init__(self, columns, prefs, key):
103
gnomeglade.Component.__init__(self, paths.ui_dir("meldapp.glade"), "listwidget")
106
self.treeview.set_model( gtk.ListStore( *[c[1] for c in columns] ) )
108
def addTextCol(label, colnum):
109
model = view.get_model()
110
rentext = gtk.CellRendererText()
111
rentext.props.editable = 1
112
def change_text(ren, path, text):
113
model[path][colnum] = text
114
self._update_filter_string()
115
rentext.connect("edited", change_text)
116
column = gtk.TreeViewColumn(label, rentext, text=colnum)
117
view.append_column(column)
118
def addToggleCol(label, colnum):
119
model = view.get_model()
120
rentoggle = gtk.CellRendererToggle()
121
def change_toggle(ren, path):
122
model[path][colnum] = not ren.get_active()
123
self._update_filter_string()
124
rentoggle.connect("toggled", change_toggle)
125
column = gtk.TreeViewColumn(label, rentoggle, active=colnum)
126
view.append_column(column)
127
for c,i in zip( columns, range(len(columns))):
130
elif c[1] == type(0):
131
addToggleCol( c[0], 1)
132
view.get_selection().connect('changed', self._update_sensitivity)
133
view.get_model().connect('row-inserted', self._update_sensitivity)
134
view.get_model().connect('rows-reordered', self._update_sensitivity)
135
self._update_sensitivity()
136
self._update_filter_model()
138
def _update_sensitivity(self, *args):
139
(model, it, path) = self._get_selected()
141
self.item_delete.set_sensitive(False)
142
self.item_up.set_sensitive(False)
143
self.item_down.set_sensitive(False)
145
self.item_delete.set_sensitive(True)
146
self.item_up.set_sensitive(path > 0)
147
self.item_down.set_sensitive(path < len(model) - 1)
149
def on_item_new_clicked(self, button):
150
model = self.treeview.get_model()
151
model.append([_("label"), 0, _("pattern")])
152
self._update_filter_string()
153
def _get_selected(self):
154
(model, it) = self.treeview.get_selection().get_selected()
156
path = model.get_path(it)[0]
159
return (model, it, path)
160
def on_item_delete_clicked(self, button):
161
(model, it, path) = self._get_selected()
163
self._update_filter_string()
164
def on_item_up_clicked(self, button):
165
(model, it, path) = self._get_selected()
166
model.swap(it, model.get_iter(path - 1))
167
self._update_filter_string()
168
def on_item_down_clicked(self, button):
169
(model, it, path) = self._get_selected()
170
model.swap(it, model.get_iter(path + 1))
171
self._update_filter_string()
172
def on_items_revert_clicked(self, button):
173
setattr( self.prefs, self.key, self.prefs.get_default(self.key) )
174
self._update_filter_model()
175
def _update_filter_string(self):
176
model = self.treeview.get_model()
179
pref.append("%s\t%s\t%s" % (row[0], row[1], row[2]))
180
setattr( self.prefs, self.key, "\n".join(pref) )
181
def _update_filter_model(self):
182
model = self.treeview.get_model()
184
for filtstring in getattr( self.prefs, self.key).split("\n"):
185
filt = misc.ListItem(filtstring)
186
model.append([filt.name, filt.active, filt.value])
188
################################################################################
192
################################################################################
194
class PreferencesDialog(gnomeglade.Component):
196
editor_radio_values = {"internal":0, "gnome":1, "custom":2}
198
def __init__(self, parentapp):
199
gnomeglade.Component.__init__(self, paths.ui_dir("meldapp.glade"), "preferencesdialog")
200
self.widget.set_transient_for(parentapp.widget)
201
self.prefs = parentapp.prefs
203
self.map_widgets_into_lists( ["editor_command"] )
204
if self.prefs.use_custom_font:
205
self.radiobutton_custom_font.set_active(1)
207
self.radiobutton_gnome_font.set_active(1)
208
self.fontpicker.set_font_name( self.prefs.custom_font )
209
self.spinbutton_tabsize.set_value( self.prefs.tab_size )
211
self.checkbutton_spaces_instead_of_tabs.set_active( self.prefs.spaces_instead_of_tabs )
212
self.checkbutton_show_line_numbers.set_active( self.prefs.show_line_numbers )
213
self.checkbutton_use_syntax_highlighting.set_active( self.prefs.use_syntax_highlighting )
215
self.checkbutton_spaces_instead_of_tabs.set_sensitive(False)
216
self.checkbutton_show_line_numbers.set_sensitive(False)
217
self.checkbutton_use_syntax_highlighting.set_sensitive(False)
218
if gtk.pygtk_version >= (2, 12, 0):
219
no_sourceview_text = _("Only available if you have python-gtksourceview2 installed")
220
self.checkbutton_spaces_instead_of_tabs.set_tooltip_text(no_sourceview_text)
221
self.checkbutton_show_line_numbers.set_tooltip_text(no_sourceview_text)
222
self.checkbutton_use_syntax_highlighting.set_tooltip_text(no_sourceview_text)
223
self.option_wrap_lines.set_history( self.prefs.edit_wrap_lines )
224
self.checkbutton_supply_newline.set_active( self.prefs.supply_newline )
225
self.editor_command[ self.editor_radio_values.get(self.prefs.edit_command_type, "internal") ].set_active(1)
226
self.gnome_default_editor_label.set_text( "(%s)" % " ".join(self.prefs.get_gnome_editor_command([])) )
227
self.custom_edit_command_entry.set_text( " ".join(self.prefs.get_custom_editor_command([])) )
229
cols = [ (_("Name"), type("")), (_("Active"), type(0)), (_("Pattern"), type("")) ]
230
self.filefilter = ListWidget( cols, self.prefs, "filters")
231
self.file_filters_tab.pack_start(self.filefilter.widget)
232
self.checkbutton_ignore_symlinks.set_active( self.prefs.ignore_symlinks)
234
cols = [ (_("Name"), type("")), (_("Active"), type(0)), (_("Regex"), type("")) ]
235
self.textfilter = ListWidget( cols, self.prefs, "regexes")
236
self.text_filters_tab.pack_start(self.textfilter.widget)
237
self.checkbutton_ignore_blank_lines.set_active( self.prefs.ignore_blank_lines )
239
self.entry_text_codecs.set_text( self.prefs.text_codecs )
243
def on_fontpicker_font_set(self, picker):
244
self.prefs.custom_font = picker.get_font_name()
245
def on_radiobutton_font_toggled(self, radio):
246
if radio.get_active():
247
custom = radio == self.radiobutton_custom_font
248
self.fontpicker.set_sensitive(custom)
249
self.prefs.use_custom_font = custom
250
def on_spinbutton_tabsize_changed(self, spin):
251
self.prefs.tab_size = int(spin.get_value())
252
def on_checkbutton_spaces_instead_of_tabs_toggled(self, check):
253
self.prefs.spaces_instead_of_tabs = check.get_active()
254
def on_option_wrap_lines_changed(self, option):
255
self.prefs.edit_wrap_lines = option.get_history()
256
def on_checkbutton_supply_newline_toggled(self, check):
257
self.prefs.supply_newline = check.get_active()
258
def on_checkbutton_show_line_numbers_toggled(self, check):
259
self.prefs.show_line_numbers = check.get_active()
260
def on_checkbutton_use_syntax_highlighting_toggled(self, check):
261
self.prefs.use_syntax_highlighting = check.get_active()
262
def on_editor_command_toggled(self, radio):
263
if radio.get_active():
264
idx = self.editor_command.index(radio)
265
for k,v in self.editor_radio_values.items():
267
self.prefs.edit_command_type = k
272
def on_checkbutton_ignore_symlinks_toggled(self, check):
273
self.prefs.ignore_symlinks = check.get_active()
274
def on_checkbutton_ignore_blank_lines_toggled(self, check):
275
self.prefs.ignore_blank_lines = check.get_active()
278
# Save text entry values into preferences
280
def on_response(self, dialog, arg):
281
if arg==gtk.RESPONSE_CLOSE:
282
self.prefs.text_codecs = self.entry_text_codecs.props.text
283
self.prefs.edit_command_custom = self.custom_edit_command_entry.props.text
284
self.widget.destroy()
286
################################################################################
290
################################################################################
292
class MeldStatusBar(object):
294
def __init__(self, task_progress, task_status, doc_status):
295
self.task_progress = task_progress
296
self.task_status = task_status
297
self.doc_status = doc_status
299
def set_task_status(self, status):
300
self.task_status.pop(1)
301
self.task_status.push(1, status)
303
def set_doc_status(self, status):
304
self.doc_status.pop(1)
305
self.doc_status.push(1, status)
307
################################################################################
311
################################################################################
312
class MeldPreferences(prefs.Preferences):
314
"window_size_x": prefs.Value(prefs.INT, 600),
315
"window_size_y": prefs.Value(prefs.INT, 600),
316
"use_custom_font": prefs.Value(prefs.BOOL,0),
317
"custom_font": prefs.Value(prefs.STRING,"monospace, 14"),
318
"tab_size": prefs.Value(prefs.INT, 4),
319
"spaces_instead_of_tabs": prefs.Value(prefs.BOOL, False),
320
"show_line_numbers": prefs.Value(prefs.BOOL, 0),
321
"use_syntax_highlighting": prefs.Value(prefs.BOOL, 0),
322
"edit_wrap_lines" : prefs.Value(prefs.INT, 0),
323
"edit_command_type" : prefs.Value(prefs.STRING, "internal"), #internal, gnome, custom
324
"edit_command_custom" : prefs.Value(prefs.STRING, "gedit"),
325
"supply_newline": prefs.Value(prefs.BOOL, False),
326
"text_codecs": prefs.Value(prefs.STRING, "utf8 latin1"),
327
"ignore_symlinks": prefs.Value(prefs.BOOL,0),
328
"vc_console_visible": prefs.Value(prefs.BOOL, 0),
329
"color_delete_bg" : prefs.Value(prefs.STRING, "DarkSeaGreen1"),
330
"color_delete_fg" : prefs.Value(prefs.STRING, "Red"),
331
"color_replace_bg" : prefs.Value(prefs.STRING, "#ddeeff"),
332
"color_replace_fg" : prefs.Value(prefs.STRING, "Black"),
333
"color_conflict_bg" : prefs.Value(prefs.STRING, "Pink"),
334
"color_conflict_fg" : prefs.Value(prefs.STRING, "Black"),
335
"color_inline_bg" : prefs.Value(prefs.STRING, "LightSteelBlue2"),
336
"color_inline_fg" : prefs.Value(prefs.STRING, "Red"),
337
"color_edited_bg" : prefs.Value(prefs.STRING, "gray90"),
338
"color_edited_fg" : prefs.Value(prefs.STRING, "Black"),
339
"filters" : prefs.Value(prefs.STRING,
340
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
341
_("Backups\t1\t#*# .#* ~* *~ *.{orig,bak,swp}\n") + \
342
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
343
_("Version Control\t1\t%s\n") % misc.shell_escape(' '.join(vc.get_plugins_metadata())) + \
344
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
345
_("Binaries\t1\t*.{pyc,a,obj,o,so,la,lib,dll}\n") + \
346
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
347
_("Media\t0\t*.{jpg,gif,png,wav,mp3,ogg,xcf,xpm}")),
348
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
349
"regexes" : prefs.Value(prefs.STRING, _("CVS keywords\t0\t\$\\w+(:[^\\n$]+)?\$\n") + \
350
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
351
_("C++ comment\t0\t//.*\n") + \
352
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
353
_("C comment\t0\t/\*.*?\*/\n") + \
354
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
355
_("All whitespace\t0\t[ \\t\\r\\f\\v]*\n") + \
356
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
357
_("Leading whitespace\t0\t^[ \\t\\r\\f\\v]*\n") + \
358
#TRANSLATORS: translate this string ONLY to the first "\t", leave it and the following parts intact
359
_("Script comment\t0\t#.*")),
360
"ignore_blank_lines" : prefs.Value(prefs.BOOL, False),
361
"toolbar_visible" : prefs.Value(prefs.BOOL, True),
362
"statusbar_visible" : prefs.Value(prefs.BOOL, True)
366
super(MeldPreferences, self).__init__("/apps/meld", self.defaults)
368
def get_current_font(self):
369
if self.use_custom_font:
370
return self.custom_font
372
if not hasattr(self, "_gconf"):
373
return "Monospace 10"
374
return self._gconf.get_string('/desktop/gnome/interface/monospace_font_name') or "Monospace 10"
376
def get_toolbar_style(self):
377
if not hasattr(self, "_gconf"):
378
return gtk.TOOLBAR_BOTH
379
style = self._gconf.get_string('/desktop/gnome/interface/toolbar_style') or "both"
380
style = {"both":gtk.TOOLBAR_BOTH, "text":gtk.TOOLBAR_TEXT,
381
"icon":gtk.TOOLBAR_ICONS, "icons":gtk.TOOLBAR_ICONS,
382
"both_horiz":gtk.TOOLBAR_BOTH_HORIZ,
383
"both-horiz":gtk.TOOLBAR_BOTH_HORIZ
387
def get_gnome_editor_command(self, files):
388
if not hasattr(self, "_gconf"):
391
editor = self._gconf.get_string('/desktop/gnome/applications/editor/exec') or "gedit"
392
if self._gconf.get_bool("/desktop/gnome/applications/editor/needs_term"):
393
texec = self._gconf.get_string("/desktop/gnome/applications/terminal/exec")
396
targ = self._gconf.get_string("/desktop/gnome/applications/terminal/exec_arg")
399
argv.append( "%s %s" % (editor, " ".join( [f.replace(" ","\\ ") for f in files]) ) )
401
argv = [editor] + files
404
def get_custom_editor_command(self, files):
405
return self.edit_command_custom.split() + files
408
################################################################################
412
################################################################################
414
class MeldApp(gnomeglade.Component):
420
gladefile = paths.ui_dir("meldapp.glade")
421
gtk.window_set_default_icon_name("icon")
422
if getattr(gobject, "pygobject_version", ()) >= (2, 16, 0):
423
gobject.set_application_name("Meld")
424
gnomeglade.Component.__init__(self, gladefile, "meldapp")
425
self.prefs = MeldPreferences()
428
("FileMenu", None, _("_File")),
429
("New", gtk.STOCK_NEW, _("_New..."), "<control>N", _("Start a new comparison"), self.on_menu_file_new_activate),
430
("Save", gtk.STOCK_SAVE, None, None, _("Save the current file"), self.on_menu_save_activate),
431
("SaveAs", gtk.STOCK_SAVE_AS, None, "<control><shift>S", "Save the current file with a different name", self.on_menu_save_as_activate),
432
("Close", gtk.STOCK_CLOSE, None, None, _("Close the current file"), self.on_menu_close_activate),
433
("Quit", gtk.STOCK_QUIT, None, None, _("Quit the program"), self.on_menu_quit_activate),
435
("EditMenu", None, _("_Edit")),
436
("Undo", gtk.STOCK_UNDO, None, "<control>Z", _("Undo the last action"), self.on_menu_undo_activate),
437
("Redo", gtk.STOCK_REDO, None, "<control><shift>Z", _("Redo the last undone action"), self.on_menu_redo_activate),
438
("Cut", gtk.STOCK_CUT, None, None, _("Cut the selection"), self.on_menu_cut_activate),
439
("Copy", gtk.STOCK_COPY, None, None, _("Copy the selection"), self.on_menu_copy_activate),
440
("Paste", gtk.STOCK_PASTE, None, None, _("Paste the clipboard"), self.on_menu_paste_activate),
441
("Find", gtk.STOCK_FIND, None, None, _("Search for text"), self.on_menu_find_activate),
442
("FindNext", None, _("Find Ne_xt"), "<control>G", _("Search forwards for the same text"), self.on_menu_find_next_activate),
443
("Replace", gtk.STOCK_FIND_AND_REPLACE, _("_Replace"), "<control>H", _("Find and replace text"), self.on_menu_replace_activate),
444
("Down", gtk.STOCK_GO_DOWN, None, "<control>D", _("Go to the next difference"), self.on_menu_edit_down_activate),
445
("Up", gtk.STOCK_GO_UP, None, "<control>E", _("Go to the previous difference"), self.on_menu_edit_up_activate),
446
("Preferences", gtk.STOCK_PREFERENCES, _("Prefere_nces"), None, _("Configure the application"), self.on_menu_preferences_activate),
448
("ViewMenu", None, _("_View")),
449
("FileStatus", None, _("File status")),
450
("VcStatus", None, _("Version status")),
451
("FileFilters", None, _("File filters")),
452
("Stop", gtk.STOCK_STOP, None, "Escape", _("Stop the current action"), self.on_toolbar_stop_clicked),
453
("Refresh", gtk.STOCK_REFRESH, None, "<control>R", _("Refresh the view"), self.on_menu_refresh_activate),
454
("Reload", gtk.STOCK_REFRESH, _("Reload"), "<control><shift>R", _("Reload the comparison"), self.on_menu_reload_activate),
456
("HelpMenu", None, _("_Help")),
457
("Help", gtk.STOCK_HELP, _("_Contents"), "F1", _("Open the Meld manual"), self.on_menu_help_activate),
458
("BugReport", gtk.STOCK_DIALOG_WARNING, _("Report _Bug"), None, _("Report a bug in Meld"), self.on_menu_help_bug_activate),
459
("About", gtk.STOCK_ABOUT, None, None, _("About this program"), self.on_menu_about_activate),
462
("Fullscreen", None, _("Full Screen"), "F11", _("View the comparison in full screen"), self.on_action_fullscreen_toggled, False),
463
("ToolbarVisible", None, _("_Toolbar"), None, _("Show or hide the toolbar"), self.on_menu_toolbar_toggled, self.prefs.toolbar_visible),
464
("StatusbarVisible", None, _("_Statusbar"), None, _("Show or hide the statusbar"), self.on_menu_statusbar_toggled, self.prefs.statusbar_visible)
466
ui_file = paths.ui_dir("meldapp-ui.xml")
467
self.actiongroup = gtk.ActionGroup('MainActions')
468
self.actiongroup.set_translation_domain("meld")
469
self.actiongroup.add_actions(actions)
470
self.actiongroup.add_toggle_actions(toggleactions)
471
self.ui = gtk.UIManager()
472
self.ui.insert_action_group(self.actiongroup, 0)
473
self.ui.add_ui_from_file(ui_file)
474
self.ui.connect("connect-proxy", self._on_uimanager_connect_proxy)
475
self.ui.connect("disconnect-proxy", self._on_uimanager_disconnect_proxy)
477
for menuitem in ("Save", "Undo"):
478
self.actiongroup.get_action(menuitem).props.is_important = True
479
self.widget.add_accel_group(self.ui.get_accel_group())
480
self.menubar = self.ui.get_widget('/Menubar')
481
self.toolbar = self.ui.get_widget('/Toolbar')
482
self.appvbox.pack_start(self.menubar, expand=False)
483
self.appvbox.pack_start(self.toolbar, expand=False)
484
# TODO: should possibly use something other than doc_status
485
self._menu_context = self.doc_status.get_context_id("Tooltips")
486
self.statusbar = MeldStatusBar(self.task_progress, self.task_status, self.doc_status)
487
self.widget.drag_dest_set(
488
gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP,
489
[ ('text/uri-list', 0, 0) ],
491
if gnomevfs_available:
492
self.widget.connect('drag_data_received', self.on_widget_drag_data_received)
493
self.toolbar.set_style( self.prefs.get_toolbar_style() )
494
self.toolbar.props.visible = self.prefs.toolbar_visible
495
self.status_box.props.visible = self.prefs.statusbar_visible
496
self.prefs.notify_add(self.on_preference_changed)
498
self.scheduler = task.LifoScheduler()
499
self.scheduler.connect("runnable", self.on_scheduler_runnable )
500
self.widget.set_default_size(self.prefs.window_size_x, self.prefs.window_size_y)
501
self.ui.ensure_update()
503
self.widget.connect('focus_in_event', self.on_focus_change)
504
self.widget.connect('focus_out_event', self.on_focus_change)
506
def on_focus_change(self, widget, event, callback_data = None):
507
for idx in range(self.notebook.get_n_pages()):
508
w = self.notebook.get_nth_page(idx)
509
if hasattr(w.get_data("pyobject"), 'on_focus_change'):
510
w.get_data("pyobject").on_focus_change()
511
# Let the rest of the stack know about this event
514
def on_widget_drag_data_received(self, wid, context, x, y, selection_data, info, time):
515
if len(selection_data.get_uris()) != 0:
516
paths = [gnomevfs.get_local_path_from_uri(u) for u in selection_data.get_uris()]
517
self.open_paths(paths)
520
def _on_uimanager_connect_proxy(self, ui, action, widget):
521
tooltip = action.props.tooltip
524
if isinstance(widget, gtk.MenuItem):
525
cid = widget.connect("select", self._on_action_item_select_enter, tooltip)
526
cid2 = widget.connect("deselect", self._on_action_item_deselect_leave)
527
widget.set_data("meldapp::proxy-signal-ids", (cid, cid2))
528
elif isinstance(widget, gtk.ToolButton):
529
cid = widget.child.connect("enter", self._on_action_item_select_enter, tooltip)
530
cid2 = widget.child.connect("leave", self._on_action_item_deselect_leave)
531
widget.set_data("meldapp::proxy-signal-ids", (cid, cid2))
533
def _on_uimanager_disconnect_proxy(self, ui, action, widget):
534
cids = widget.get_data("meldapp::proxy-signal-ids")
537
if isinstance(widget, gtk.ToolButton):
538
widget = widget.child
540
widget.disconnect(cid)
542
def _on_action_item_select_enter(self, item, tooltip):
543
self.statusbar.doc_status.push(self._menu_context, tooltip)
545
def _on_action_item_deselect_leave(self, item):
546
self.statusbar.doc_status.pop(self._menu_context)
549
ret = self.scheduler.iteration()
551
if type(ret) in (type(""), type(u"")):
552
self.statusbar.set_task_status(ret)
553
elif type(ret) == type(0.0):
554
self.statusbar.task_progress.set_fraction(ret)
556
self.statusbar.task_progress.pulse()
558
self.statusbar.task_progress.set_fraction(0)
559
if self.scheduler.tasks_pending():
560
self.actiongroup.get_action("Stop").set_sensitive(True)
563
self.statusbar.set_task_status("")
565
self.actiongroup.get_action("Stop").set_sensitive(False)
568
def on_scheduler_runnable(self, sched):
569
if not self.idle_hooked:
571
gobject.idle_add( self.on_idle )
573
def on_preference_changed(self, key, value):
574
if key == "toolbar_style":
575
self.toolbar.set_style( self.prefs.get_toolbar_style() )
576
elif key == "statusbar_visible":
577
self.status_box.props.visible = self.prefs.statusbar_visible
578
elif key == "toolbar_visible":
579
self.toolbar.props.visible = self.prefs.toolbar_visible
582
# General events and callbacks
584
def on_delete_event(self, *extra):
585
return self.on_menu_quit_activate()
587
def on_switch_page(self, notebook, page, which):
588
newdoc = notebook.get_nth_page(which).get_data("pyobject")
589
newseq = newdoc.undosequence
590
oldidx = notebook.get_current_page()
592
olddoc = notebook.get_nth_page(oldidx).get_data("pyobject")
593
olddoc.on_container_switch_out_event(self.ui)
594
self.actiongroup.get_action("Undo").set_sensitive(newseq.can_undo())
595
self.actiongroup.get_action("Redo").set_sensitive(newseq.can_redo())
596
nbl = self.notebook.get_tab_label( newdoc.widget )
597
self.widget.set_title(nbl.get_label_text() + " - Meld")
598
self.statusbar.set_doc_status("")
599
newdoc.on_container_switch_in_event(self.ui)
600
self.scheduler.add_task( newdoc.scheduler )
602
def on_notebook_label_changed(self, component, text):
603
nbl = self.notebook.get_tab_label( component.widget )
604
nbl.set_label_text(text)
605
self.widget.set_title(text + " - Meld")
606
self.notebook.child_set_property(component.widget, "menu-label", text)
608
def on_can_undo(self, undosequence, can):
609
self.actiongroup.get_action("Undo").set_sensitive(can)
611
def on_can_redo(self, undosequence, can):
612
self.actiongroup.get_action("Redo").set_sensitive(can)
614
def on_size_allocate(self, window, rect):
615
self.prefs.window_size_x = rect.width
616
self.prefs.window_size_y = rect.height
619
# Toolbar and menu items (file)
621
def on_menu_file_new_activate(self, menuitem):
624
def on_menu_save_activate(self, menuitem):
625
self.current_doc().save()
627
def on_menu_save_as_activate(self, menuitem):
628
self.current_doc().save_as()
630
def on_menu_close_activate(self, *extra):
631
i = self.notebook.get_current_page()
633
page = self.notebook.get_nth_page(i).get_data("pyobject")
634
self.try_remove_page(page)
636
def on_menu_quit_activate(self, *extra):
637
for c in self.notebook.get_children():
638
response = c.get_data("pyobject").on_delete_event(appquit=1)
639
if response == gtk.RESPONSE_CANCEL:
640
return gtk.RESPONSE_CANCEL
641
elif response == gtk.RESPONSE_CLOSE:
643
for c in self.notebook.get_children():
644
c.get_data("pyobject").on_quit_event()
646
return gtk.RESPONSE_CLOSE
649
# Toolbar and menu items (edit)
651
def on_menu_undo_activate(self, *extra):
652
self.current_doc().on_undo_activate()
654
def on_menu_redo_activate(self, *extra):
655
self.current_doc().on_redo_activate()
657
def on_menu_refresh_activate(self, *extra):
658
self.current_doc().on_refresh_activate()
660
def on_menu_reload_activate(self, *extra):
661
self.current_doc().on_reload_activate()
663
def on_menu_find_activate(self, *extra):
664
self.current_doc().on_find_activate()
666
def on_menu_find_next_activate(self, *extra):
667
self.current_doc().on_find_next_activate()
669
def on_menu_replace_activate(self, *extra):
670
self.current_doc().on_replace_activate()
672
def on_menu_copy_activate(self, *extra):
673
widget = self.widget.get_focus()
674
if isinstance(widget, gtk.Editable):
675
widget.copy_clipboard()
676
elif isinstance(widget, gtk.TextView):
677
widget.emit("copy-clipboard")
679
def on_menu_cut_activate(self, *extra):
680
widget = self.widget.get_focus()
681
if isinstance(widget, gtk.Editable):
682
widget.cut_clipboard()
683
elif isinstance(widget, gtk.TextView):
684
widget.emit("cut-clipboard")
686
def on_menu_paste_activate(self, *extra):
687
widget = self.widget.get_focus()
688
if isinstance(widget, gtk.Editable):
689
widget.paste_clipboard()
690
elif isinstance(widget, gtk.TextView):
691
widget.emit("paste-clipboard")
694
# Toolbar and menu items (settings)
696
def on_menu_preferences_activate(self, item):
697
PreferencesDialog(self)
699
def on_action_fullscreen_toggled(self, widget):
700
is_full = self.widget.window.get_state() & gtk.gdk.WINDOW_STATE_FULLSCREEN
701
if widget.get_active() and not is_full:
702
self.widget.fullscreen()
704
self.widget.unfullscreen()
706
def on_menu_toolbar_toggled(self, widget):
707
self.prefs.toolbar_visible = widget.get_active()
709
def on_menu_statusbar_toggled(self, widget):
710
self.prefs.statusbar_visible = widget.get_active()
713
# Toolbar and menu items (help)
715
def on_menu_help_activate(self, button):
716
misc.open_uri("ghelp:///"+os.path.abspath(paths.help_dir("C/meld.xml")))
718
def on_menu_help_bug_activate(self, button):
719
misc.open_uri("http://bugzilla.gnome.org/buglist.cgi?query=product%3Ameld")
721
def on_menu_about_activate(self, *extra):
722
gtk.about_dialog_set_url_hook(lambda dialog, uri: misc.open_uri(uri))
723
about = gtk.glade.XML(paths.ui_dir("meldapp.glade"), "about").get_widget("about")
724
about.props.version = version
725
about.set_transient_for(self.widget)
730
# Toolbar and menu items (misc)
732
def on_menu_edit_down_activate(self, *args):
733
self.current_doc().next_diff(gtk.gdk.SCROLL_DOWN)
735
def on_menu_edit_up_activate(self, *args):
736
self.current_doc().next_diff(gtk.gdk.SCROLL_UP)
738
def on_toolbar_stop_clicked(self, *args):
739
self.current_doc().stop()
741
def try_remove_page(self, page):
742
"See if a page will allow itself to be removed"
743
if page.on_delete_event() != gtk.RESPONSE_CANCEL:
744
self.scheduler.remove_scheduler( page.scheduler )
745
i = self.notebook.page_num( page.widget )
747
# If the page we're removing is the current page, we need to trigger a switch out
748
if self.notebook.get_current_page() == i:
749
page.on_container_switch_out_event(self.ui)
750
self.notebook.remove_page(i)
751
if self.notebook.get_n_pages() == 0:
752
self.widget.set_title("Meld")
754
def on_file_changed(self, srcpage, filename):
755
for c in self.notebook.get_children():
756
page = c.get_data("pyobject")
758
page.on_file_changed(filename)
760
def _append_page(self, page, icon):
761
nbl = notebooklabel.NotebookLabel(icon, "", lambda b: self.try_remove_page(page))
762
self.notebook.append_page( page.widget, nbl)
763
self.notebook.set_current_page( self.notebook.page_num(page.widget) )
764
self.scheduler.add_scheduler(page.scheduler)
765
page.connect("label-changed", self.on_notebook_label_changed)
766
page.connect("file-changed", self.on_file_changed)
767
page.connect("create-diff", lambda obj,arg: self.append_diff(arg) )
768
page.connect("status-changed", lambda junk,arg: self.statusbar.set_doc_status(arg) )
770
def append_dirdiff(self, dirs, auto_compare=False):
771
assert len(dirs) in (1,2,3)
772
doc = dirdiff.DirDiff(self.prefs, len(dirs))
773
self._append_page(doc, "tree-folder-normal")
774
doc.set_locations(dirs)
775
# FIXME: This doesn't work, as dirdiff behaves differently to vcview
777
doc.on_button_diff_clicked(None)
780
def append_filediff(self, files):
781
assert len(files) in (1,2,3)
782
doc = filediff.FileDiff(self.prefs, len(files))
783
seq = doc.undosequence
785
seq.connect("can-undo", self.on_can_undo)
786
seq.connect("can-redo", self.on_can_redo)
787
self._append_page(doc, "tree-file-normal")
791
def append_diff(self, paths, auto_compare=False):
792
aredirs = [ os.path.isdir(p) for p in paths ]
793
arefiles = [ os.path.isfile(p) for p in paths ]
794
if (1 in aredirs) and (1 in arefiles):
795
misc.run_dialog( _("Cannot compare a mixture of files and directories.\n"),
797
buttonstype = gtk.BUTTONS_OK)
799
return self.append_dirdiff(paths, auto_compare)
801
return self.append_filediff(paths)
803
def append_vcview(self, locations, auto_compare=False):
804
assert len(locations) in (1,)
805
location = locations[0]
806
doc = vcview.VcView(self.prefs)
807
self._append_page(doc, "vc-icon")
808
doc.set_location(location)
810
doc.on_button_diff_clicked(None)
814
# Current doc actions
816
def current_doc(self):
817
"Get the current doc or a dummy object if there is no current"
818
index = self.notebook.get_current_page()
820
return self.notebook.get_nth_page(index).get_data("pyobject")
821
class DummyDoc(object):
822
def __getattr__(self, a): return lambda *x: None
828
def usage(self, msg):
829
response = misc.run_dialog(msg,
833
[(gtk.STOCK_QUIT, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)] )
834
if response == gtk.RESPONSE_CANCEL:
838
usage_file = "<%s>" % _("file")
839
usage_dir = "<%s>" % _("dir")
840
usage_3files = "%s %s [%s]" % ((usage_file,)*3)
841
usage_3dirs = "%s %s [%s]" % ((usage_dir,)*3)
842
pad_args_fmt = "%-" + str( max( len(usage_3files), len(usage_3dirs))) + "s %s"
844
("", _("Start with no window open")),
845
(usage_dir, _("Start with Version Control browser in '%s'")%_("dir")),
846
(usage_file, _("Start with Version Control diff of '%s'")%_("file")),
847
(usage_3files, _("Start with 2 or 3 way file comparison")),
848
(usage_3dirs, _("Start with 2 or 3 way directory comparison"))]
849
return "\n" + "\n".join( ["%prog " + pad_args_fmt % u for u in usages] )
851
def parse_args(self, rawargs):
852
parser = optparse.OptionParser(
853
option_class=misc.MeldOption,
854
usage=self.usage_msg(),
855
description=_("Meld is a file and directory comparison tool."),
856
version="%prog " + version)
857
parser.add_option("-L", "--label", action="append", default=[],
858
help=_("Set label to use instead of file name"))
859
parser.add_option("-a", "--auto-compare", action="store_true", default=False,
860
help=_("Automatically compare all differing files on startup"))
861
parser.add_option("-u", "--unified", action="store_true", help=_("Ignored for compatibility"))
862
parser.add_option("-c", "--context", action="store_true", help=_("Ignored for compatibility"))
863
parser.add_option("-e", "--ed", action="store_true", help=_("Ignored for compatibility"))
864
parser.add_option("-r", "--recursive", action="store_true", help=_("Ignored for compatibility"))
865
parser.add_option("", "--diff", action="diff_files", dest='diff',
867
help=_("Creates a diff tab for up to 3 supplied files or directories."))
868
options, args = parser.parse_args(rawargs)
869
for files in options.diff:
870
if len(files) not in (1, 2, 3):
871
self.usage(_("Invalid number of arguments supplied for --diff."))
872
self.append_diff(files)
873
if len(args) not in (0, 1, 2, 3):
874
self.usage(_("Wrong number of arguments (Got %i)") % len(args))
876
tab = self.open_paths(args, options.auto_compare)
878
tab.set_labels(options.label)
880
def _single_file_open(self, path):
881
doc = vcview.VcView(self.prefs)
883
self.scheduler.remove_scheduler(doc.scheduler)
884
self.scheduler.add_task(cleanup)
885
self.scheduler.add_scheduler(doc.scheduler)
886
doc.set_location(os.path.dirname(path))
887
doc.connect("create-diff", lambda obj,arg: self.append_diff(arg))
890
def open_paths(self, paths, auto_compare=False):
894
if os.path.isfile(a):
895
self._single_file_open(a)
897
tab = self.append_vcview([a], auto_compare)
899
elif len(paths) in (2,3):
900
tab = self.append_diff(paths, auto_compare)
904
################################################################################
908
################################################################################
910
class Unbuffered(object):
911
def __init__(self, file):
913
def write(self, arg):
916
def __getattr__(self, attr):
917
return getattr(self.file, attr)
918
sys.stdout = Unbuffered(sys.stdout)
920
gtk.icon_theme_get_default().append_search_path(paths.icon_dir())
922
app.parse_args(sys.argv[1:])