~jtaylor/ubuntu/natty/meld/meld-sru

« back to all changes in this revision

Viewing changes to meldapp.py

  • Committer: Bazaar Package Importer
  • Author(s): Balint Reczey, Josselin Mouette, Balint Reczey
  • Date: 2010-06-24 23:54:27 UTC
  • mfrom: (2.1.8 sid)
  • Revision ID: james.westby@ubuntu.com-20100624235427-h5419rmmin36qm8u
Tags: 1.3.2-1
[ Josselin Mouette ]
* vcs-crash.patch: stolen upstream. Fix a crash when using CVS, svn or
  git. Closes: #584554.

[ Balint Reczey ]
* New upstream release
  - Added support for diffing a file and a directory (Closes: #567340)
  - Translation updates (Thanks to Holger Wansing) (Closes: #313985)
  - vcs-crash.patch: removed, included upstream.
* run meld using /usr/bin/python instead of env python (Closes: #551189)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
### Copyright (C) 2002-2006 Stephen Kennedy <stevek@gnome.org>
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
 
# system
18
 
import sys
19
 
import os
20
 
import optparse
21
 
from gettext import gettext as _
22
 
 
23
 
# gtk
24
 
import gtk
25
 
import gtk.glade
26
 
import gobject
27
 
import pango
28
 
 
29
 
# Drag'N'Drop support needs gnomevfs
30
 
try:
31
 
    import gnomevfs
32
 
    gnomevfs_available = True
33
 
except ImportError:
34
 
    gnomevfs_available = False
35
 
 
36
 
# project
37
 
import paths
38
 
import prefs
39
 
import gnomeglade
40
 
import misc
41
 
import notebooklabel
42
 
import filediff
43
 
import vcview
44
 
import dirdiff
45
 
import task
46
 
import vc
47
 
 
48
 
from sourceviewer import srcviewer
49
 
 
50
 
version = "1.3.1"
51
 
 
52
 
################################################################################
53
 
#
54
 
# NewDocDialog
55
 
#
56
 
################################################################################
57
 
 
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()
70
 
 
71
 
    def on_entry_activate(self, entry):
72
 
        for el in self.entrylists:
73
 
            if entry in el:
74
 
                i = el.index(entry)
75
 
                if i == len(el) - 1:
76
 
                    self.button_ok.grab_focus()
77
 
                else:
78
 
                    el[i+1].focus_entry()
79
 
 
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()
84
 
 
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():
90
 
                paths.pop(0)
91
 
            for path in paths:
92
 
                self.entrylists[page][0].prepend_history(path)
93
 
            self.diff_methods[page](paths)
94
 
        self.widget.destroy()
95
 
 
96
 
################################################################################
97
 
#
98
 
# ListWidget
99
 
#
100
 
################################################################################
101
 
class ListWidget(gnomeglade.Component):
102
 
    def __init__(self, columns, prefs, key):
103
 
        gnomeglade.Component.__init__(self, paths.ui_dir("meldapp.glade"), "listwidget")
104
 
        self.prefs = prefs
105
 
        self.key = key
106
 
        self.treeview.set_model( gtk.ListStore( *[c[1] for c in columns] ) )
107
 
        view = self.treeview
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))):
128
 
            if c[1] == type(""):
129
 
                addTextCol(c[0], i)
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()
137
 
 
138
 
    def _update_sensitivity(self, *args):
139
 
        (model, it, path) = self._get_selected()
140
 
        if not it:
141
 
            self.item_delete.set_sensitive(False)
142
 
            self.item_up.set_sensitive(False)
143
 
            self.item_down.set_sensitive(False)
144
 
        else:
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)
148
 
 
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()
155
 
        if it:
156
 
            path = model.get_path(it)[0]
157
 
        else:
158
 
            path = None
159
 
        return (model, it, path)
160
 
    def on_item_delete_clicked(self, button):
161
 
        (model, it, path) = self._get_selected()
162
 
        model.remove(it)
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()
177
 
        pref = []
178
 
        for row in 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()
183
 
        model.clear()
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])
187
 
   
188
 
################################################################################
189
 
#
190
 
# PreferencesDialog
191
 
#
192
 
################################################################################
193
 
 
194
 
class PreferencesDialog(gnomeglade.Component):
195
 
 
196
 
    editor_radio_values = {"internal":0, "gnome":1, "custom":2}
197
 
 
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
202
 
        # editor
203
 
        self.map_widgets_into_lists( ["editor_command"] )
204
 
        if self.prefs.use_custom_font:
205
 
            self.radiobutton_custom_font.set_active(1)
206
 
        else:
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 )
210
 
        if srcviewer:
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 )
214
 
        else:
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([])) )
228
 
        # file filters
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)
233
 
        # text filters
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 )
238
 
        # encoding
239
 
        self.entry_text_codecs.set_text( self.prefs.text_codecs )
240
 
    #
241
 
    # editor
242
 
    #
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():
266
 
                if v == idx:
267
 
                    self.prefs.edit_command_type = k
268
 
                    break
269
 
    #
270
 
    # filters
271
 
    #
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()
276
 
 
277
 
    #
278
 
    # Save text entry values into preferences
279
 
    #
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()
285
 
 
286
 
################################################################################
287
 
#
288
 
# MeldStatusBar
289
 
#
290
 
################################################################################
291
 
 
292
 
class MeldStatusBar(object):
293
 
 
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
298
 
 
299
 
    def set_task_status(self, status):
300
 
        self.task_status.pop(1)
301
 
        self.task_status.push(1, status)
302
 
 
303
 
    def set_doc_status(self, status):
304
 
        self.doc_status.pop(1)
305
 
        self.doc_status.push(1, status)
306
 
 
307
 
################################################################################
308
 
#
309
 
# MeldPreferences
310
 
#
311
 
################################################################################
312
 
class MeldPreferences(prefs.Preferences):
313
 
    defaults = {
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)
363
 
    }
364
 
 
365
 
    def __init__(self):
366
 
        super(MeldPreferences, self).__init__("/apps/meld", self.defaults)
367
 
 
368
 
    def get_current_font(self):
369
 
        if self.use_custom_font:
370
 
            return self.custom_font
371
 
        else:
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"
375
 
 
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
384
 
                 }[style]
385
 
        return style
386
 
 
387
 
    def get_gnome_editor_command(self, files):
388
 
        if not hasattr(self, "_gconf"):
389
 
            return []
390
 
        argv = []
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")
394
 
            if texec:
395
 
                argv.append(texec)
396
 
                targ = self._gconf.get_string("/desktop/gnome/applications/terminal/exec_arg")
397
 
                if targ:
398
 
                    argv.append(targ)
399
 
            argv.append( "%s %s" % (editor, " ".join( [f.replace(" ","\\ ") for f in files]) ) )
400
 
        else:
401
 
            argv = [editor] + files
402
 
        return argv
403
 
 
404
 
    def get_custom_editor_command(self, files):
405
 
        return self.edit_command_custom.split() + files
406
 
 
407
 
 
408
 
################################################################################
409
 
#
410
 
# MeldApp
411
 
#
412
 
################################################################################
413
 
 
414
 
class MeldApp(gnomeglade.Component):
415
 
 
416
 
    #
417
 
    # init
418
 
    #
419
 
    def __init__(self):
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()
426
 
 
427
 
        actions = (
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),
434
 
 
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),
447
 
 
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),
455
 
 
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),
460
 
        )
461
 
        toggleactions = (
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)
465
 
        )
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)
476
 
 
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) ],
490
 
            gtk.gdk.ACTION_COPY)
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)
497
 
        self.idle_hooked = 0
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()
502
 
        self.widget.show()
503
 
        self.widget.connect('focus_in_event', self.on_focus_change)
504
 
        self.widget.connect('focus_out_event', self.on_focus_change)
505
 
 
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
512
 
        return False
513
 
 
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)
518
 
            return True
519
 
 
520
 
    def _on_uimanager_connect_proxy(self, ui, action, widget):
521
 
        tooltip = action.props.tooltip
522
 
        if not tooltip:
523
 
            return
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))
532
 
 
533
 
    def _on_uimanager_disconnect_proxy(self, ui, action, widget):
534
 
        cids = widget.get_data("meldapp::proxy-signal-ids")
535
 
        if not cids:
536
 
            return
537
 
        if isinstance(widget, gtk.ToolButton):
538
 
            widget = widget.child
539
 
        for cid in cids:
540
 
            widget.disconnect(cid)
541
 
 
542
 
    def _on_action_item_select_enter(self, item, tooltip):
543
 
        self.statusbar.doc_status.push(self._menu_context, tooltip)
544
 
 
545
 
    def _on_action_item_deselect_leave(self, item):
546
 
        self.statusbar.doc_status.pop(self._menu_context)
547
 
 
548
 
    def on_idle(self):
549
 
        ret = self.scheduler.iteration()
550
 
        if ret:
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)
555
 
            else:
556
 
                self.statusbar.task_progress.pulse()
557
 
        else:
558
 
            self.statusbar.task_progress.set_fraction(0)
559
 
        if self.scheduler.tasks_pending():
560
 
            self.actiongroup.get_action("Stop").set_sensitive(True)
561
 
            return 1
562
 
        else:
563
 
            self.statusbar.set_task_status("")
564
 
            self.idle_hooked = 0
565
 
            self.actiongroup.get_action("Stop").set_sensitive(False)
566
 
            return 0
567
 
 
568
 
    def on_scheduler_runnable(self, sched):
569
 
        if not self.idle_hooked:
570
 
            self.idle_hooked = 1
571
 
            gobject.idle_add( self.on_idle )
572
 
 
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
580
 
 
581
 
    #
582
 
    # General events and callbacks
583
 
    #
584
 
    def on_delete_event(self, *extra):
585
 
        return self.on_menu_quit_activate()
586
 
 
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()
591
 
        if oldidx >= 0:
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 )
601
 
 
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)
607
 
 
608
 
    def on_can_undo(self, undosequence, can):
609
 
        self.actiongroup.get_action("Undo").set_sensitive(can)
610
 
 
611
 
    def on_can_redo(self, undosequence, can):
612
 
        self.actiongroup.get_action("Redo").set_sensitive(can)
613
 
 
614
 
    def on_size_allocate(self, window, rect):
615
 
        self.prefs.window_size_x = rect.width
616
 
        self.prefs.window_size_y = rect.height
617
 
 
618
 
    #
619
 
    # Toolbar and menu items (file)
620
 
    #
621
 
    def on_menu_file_new_activate(self, menuitem):
622
 
        NewDocDialog(self)
623
 
 
624
 
    def on_menu_save_activate(self, menuitem):
625
 
        self.current_doc().save()
626
 
 
627
 
    def on_menu_save_as_activate(self, menuitem):
628
 
        self.current_doc().save_as()
629
 
 
630
 
    def on_menu_close_activate(self, *extra):
631
 
        i = self.notebook.get_current_page()
632
 
        if i >= 0:
633
 
            page = self.notebook.get_nth_page(i).get_data("pyobject")
634
 
            self.try_remove_page(page)
635
 
 
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:
642
 
                break
643
 
        for c in self.notebook.get_children():
644
 
            c.get_data("pyobject").on_quit_event()
645
 
        gtk.main_quit()
646
 
        return gtk.RESPONSE_CLOSE
647
 
 
648
 
    #
649
 
    # Toolbar and menu items (edit)
650
 
    #
651
 
    def on_menu_undo_activate(self, *extra):
652
 
        self.current_doc().on_undo_activate()
653
 
 
654
 
    def on_menu_redo_activate(self, *extra):
655
 
        self.current_doc().on_redo_activate()
656
 
 
657
 
    def on_menu_refresh_activate(self, *extra):
658
 
        self.current_doc().on_refresh_activate()
659
 
 
660
 
    def on_menu_reload_activate(self, *extra):
661
 
        self.current_doc().on_reload_activate()
662
 
  
663
 
    def on_menu_find_activate(self, *extra):
664
 
        self.current_doc().on_find_activate()
665
 
 
666
 
    def on_menu_find_next_activate(self, *extra):
667
 
        self.current_doc().on_find_next_activate()
668
 
 
669
 
    def on_menu_replace_activate(self, *extra):
670
 
        self.current_doc().on_replace_activate()
671
 
 
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")
678
 
 
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")
685
 
 
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")
692
 
 
693
 
    #
694
 
    # Toolbar and menu items (settings)
695
 
    #
696
 
    def on_menu_preferences_activate(self, item):
697
 
        PreferencesDialog(self)
698
 
 
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()
703
 
        elif is_full:
704
 
            self.widget.unfullscreen()
705
 
 
706
 
    def on_menu_toolbar_toggled(self, widget):
707
 
        self.prefs.toolbar_visible = widget.get_active()
708
 
 
709
 
    def on_menu_statusbar_toggled(self, widget):
710
 
        self.prefs.statusbar_visible = widget.get_active()
711
 
 
712
 
    #
713
 
    # Toolbar and menu items (help)
714
 
    #
715
 
    def on_menu_help_activate(self, button):
716
 
        misc.open_uri("ghelp:///"+os.path.abspath(paths.help_dir("C/meld.xml")))
717
 
 
718
 
    def on_menu_help_bug_activate(self, button):
719
 
        misc.open_uri("http://bugzilla.gnome.org/buglist.cgi?query=product%3Ameld")
720
 
 
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)
726
 
        about.run()
727
 
        about.hide()
728
 
 
729
 
    #
730
 
    # Toolbar and menu items (misc)
731
 
    #
732
 
    def on_menu_edit_down_activate(self, *args):
733
 
        self.current_doc().next_diff(gtk.gdk.SCROLL_DOWN)
734
 
 
735
 
    def on_menu_edit_up_activate(self, *args):
736
 
        self.current_doc().next_diff(gtk.gdk.SCROLL_UP)
737
 
 
738
 
    def on_toolbar_stop_clicked(self, *args):
739
 
        self.current_doc().stop()
740
 
 
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 )
746
 
            assert(i>=0)
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")
753
 
 
754
 
    def on_file_changed(self, srcpage, filename):
755
 
        for c in self.notebook.get_children():
756
 
            page = c.get_data("pyobject")
757
 
            if page != srcpage:
758
 
                page.on_file_changed(filename)
759
 
 
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) )
769
 
 
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
776
 
        if auto_compare:
777
 
            doc.on_button_diff_clicked(None)
778
 
        return doc
779
 
 
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
784
 
        seq.clear()
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")
788
 
        doc.set_files(files)
789
 
        return doc
790
 
 
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"),
796
 
                    parent = self,
797
 
                    buttonstype = gtk.BUTTONS_OK)
798
 
        elif 1 in aredirs:
799
 
            return self.append_dirdiff(paths, auto_compare)
800
 
        else:
801
 
            return self.append_filediff(paths)
802
 
 
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)
809
 
        if auto_compare:
810
 
            doc.on_button_diff_clicked(None)
811
 
        return doc
812
 
 
813
 
    #
814
 
    # Current doc actions
815
 
    #
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()
819
 
        if index >= 0:
820
 
            return self.notebook.get_nth_page(index).get_data("pyobject")
821
 
        class DummyDoc(object):
822
 
            def __getattr__(self, a): return lambda *x: None
823
 
        return DummyDoc()
824
 
 
825
 
    #
826
 
    # Usage
827
 
    #
828
 
    def usage(self, msg):
829
 
        response = misc.run_dialog(msg,
830
 
            self,
831
 
            gtk.MESSAGE_ERROR,
832
 
            gtk.BUTTONS_NONE,
833
 
            [(gtk.STOCK_QUIT, gtk.RESPONSE_CANCEL), (gtk.STOCK_OK, gtk.RESPONSE_OK)] )
834
 
        if response == gtk.RESPONSE_CANCEL:
835
 
            sys.exit(0)
836
 
 
837
 
    def usage_msg(self):
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"
843
 
        usages = [
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] )
850
 
 
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',
866
 
                          default=[],
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))
875
 
        else:
876
 
            tab = self.open_paths(args, options.auto_compare)
877
 
            if tab:
878
 
                tab.set_labels(options.label)
879
 
 
880
 
    def _single_file_open(self, path):
881
 
        doc = vcview.VcView(self.prefs)
882
 
        def cleanup():
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))
888
 
        doc.run_diff([path])
889
 
 
890
 
    def open_paths(self, paths, auto_compare=False):
891
 
        tab = None
892
 
        if len(paths) == 1:
893
 
            a = paths[0]
894
 
            if os.path.isfile(a):
895
 
                self._single_file_open(a)
896
 
            else:
897
 
                tab = self.append_vcview([a], auto_compare)
898
 
                    
899
 
        elif len(paths) in (2,3):
900
 
            tab = self.append_diff(paths, auto_compare)
901
 
        return tab
902
 
 
903
 
 
904
 
################################################################################
905
 
#
906
 
# Main
907
 
#
908
 
################################################################################
909
 
def main():
910
 
    class Unbuffered(object):
911
 
        def __init__(self, file):
912
 
            self.file = file
913
 
        def write(self, arg):
914
 
            self.file.write(arg)
915
 
            self.file.flush()
916
 
        def __getattr__(self, attr):
917
 
            return getattr(self.file, attr)
918
 
    sys.stdout = Unbuffered(sys.stdout)
919
 
 
920
 
    gtk.icon_theme_get_default().append_search_path(paths.icon_dir())
921
 
    app = MeldApp()
922
 
    app.parse_args(sys.argv[1:])
923
 
    gtk.main()