~ubuntu-branches/ubuntu/trusty/diffuse/trusty

« back to all changes in this revision

Viewing changes to src/usr/bin/diffuse

  • Committer: Bazaar Package Importer
  • Author(s): Philipp Huebner
  • Date: 2009-07-07 15:11:56 UTC
  • mfrom: (1.1.5 upstream)
  • Revision ID: james.westby@ubuntu.com-20090707151156-120zntpv8yjdu90z
Tags: 0.3.4-1
* New upstream release
* Removed typofix.dpatch as it is now fixed upstream
* Removed maintainer scripts (not needed anymore)
* Removed Suggests on scrollkeeper (not needed anymore)
* Added minor bugfix from upstream
* Added gettext to Build-Depends-Indep
* Added compilation of diffuse.po to debian/rules
* Updated maintainer's mail address
* Updated Standards-Version: 3.8.2 (no changes needed)

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
21
21
 
22
22
import gettext
 
23
import locale
 
24
import os
23
25
import sys
24
26
 
 
27
# use the program's location as a starting place to search for supporting files
 
28
# such as icon and help documentation
 
29
if hasattr(sys, 'frozen'):
 
30
    app_path = sys.executable
 
31
else:
 
32
    app_path = sys.argv[0]
 
33
bin_dir = os.path.dirname(app_path)
 
34
 
 
35
# platform test
 
36
def isWindows():
 
37
    return os.name == 'nt'
 
38
 
25
39
# translation location: '../share/locale/<LANG>/LC_MESSAGES/diffuse.mo'
26
40
# where '<LANG>' is the language key
 
41
if isWindows():
 
42
    # gettext looks for the language using environment variables which
 
43
    # are normally not set on Windows so we try setting it for them
 
44
    for lang in 'LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG':
 
45
       if os.environ.has_key(lang):
 
46
           break
 
47
    else:
 
48
       lang = locale.getdefaultlocale()[0]
 
49
       if lang is not None:
 
50
           os.environ['LANG'] = lang
 
51
    del lang
 
52
    locale_dir = 'locale'
 
53
else:
 
54
    locale_dir = '../share/locale'
 
55
locale_dir = os.path.join(bin_dir, locale_dir)
 
56
gettext.bindtextdomain('diffuse', locale_dir)
 
57
 
27
58
gettext.textdomain('diffuse')
28
59
_ = gettext.gettext
29
60
 
30
61
APP_NAME = 'Diffuse'
31
 
VERSION = '0.3.3'
 
62
VERSION = '0.3.4'
32
63
COPYRIGHT = _('Copyright © 2006-2009 Derrick Moser')
33
64
WEBSITE = 'http://diffuse.sourceforge.net/'
34
65
 
72
103
  ( -B | --ignore-blank-lines )    Ignore changes in blank lines
73
104
  ( -E | --ignore-end-of-line )    Ignore end of line differences
74
105
  ( -i | --ignore-case )           Ignore case differences
75
 
  ( -w | --ignore-all-space )      Ignore white space differences
76
 
 
77
 
Interactive Mode Navigation:
78
 
  Line Editing Mode
79
 
    <enter>   - Enter character editing mode
80
 
    <space>   - Enter alignment editing mode
81
 
  Character Editing Mode
82
 
    <escape>  - Return to line editing mode
83
 
  Alignment Editing Mode
84
 
    <space>   - Align and return to line editing mode
85
 
    <escape>  - Return to line editing mode""")
 
106
  ( -w | --ignore-all-space )      Ignore white space differences""")
86
107
        sys.exit(0)
87
108
 
88
109
import pygtk
94
115
import encodings
95
116
import glob
96
117
import gobject
97
 
import locale
98
 
import os
99
118
import pango
100
119
import re
101
120
import shlex
104
123
import subprocess
105
124
import webbrowser
106
125
 
107
 
# platform tests
108
 
def isWindows():
109
 
    return os.name == 'nt'
110
 
 
111
126
if not hasattr(__builtins__, 'WindowsError'):
112
127
    # define 'WindowsError' so 'except' statements will work on all platforms
113
128
    WindowsError = IOError
114
129
 
115
 
# use the program's location as a starting place to search for supporting files
116
 
# such as icon and help documentation
117
 
if hasattr(sys, 'frozen'):
118
 
    app_path = sys.executable
119
 
else:
120
 
    app_path = sys.argv[0]
121
 
bin_dir = os.path.dirname(app_path)
122
 
 
123
130
# avoid some dictionary lookups when string.whitespace is used in loops
124
131
whitespace = string.whitespace
125
132
 
531
538
        name = os.path.basename(name)
532
539
        for key in self.syntax_file_patterns.keys():
533
540
            if self.syntax_file_patterns[key].search(name):
534
 
                return self.getSyntax(key)
 
541
                return key
535
542
 
536
543
    # parse resource files
537
544
    def parse(self, file_name):
543
550
                ss = readlines(f)
544
551
                f.close()
545
552
            except IOError:
546
 
                logError(_('Error reading "%s".') % (file_name, ))
 
553
                logError(_('Error reading %s.') % (file_name, ))
547
554
                return
548
555
 
549
556
            # FIXME: improve validation
626
633
                           raise ValueError()
627
634
                   except: # Grr... the 're' module throws weird errors
628
635
                   #except ValueError:
629
 
                       logError(_('Error parsing line %(line)d of "%(file)s".') % { 'line': i + 1, 'file': file_name })
 
636
                       logError(_('Error parsing line %(line)d of %(file)s.') % { 'line': i + 1, 'file': file_name })
630
637
 
631
638
theResources = Resources()
632
639
 
726
733
              [ 'Integer', 'display_tab_width', 8, _('Tab width'), 1, 1024 ],
727
734
              [ 'Boolean', 'display_ignore_whitespace', False, _('Ignore white space differences') ],
728
735
              [ 'Boolean', 'display_ignore_whitespace_changes', False, _('Ignore changes to white space') ],
729
 
              [ 'Boolean', 'display_ignore_blanklines', False, _('Ignore blank lines') ],
730
 
              [ 'Boolean', 'display_ignore_case', False, _('Ignore case') ],
 
736
              [ 'Boolean', 'display_ignore_blanklines', False, _('Ignore blank line differences') ],
 
737
              [ 'Boolean', 'display_ignore_case', False, _('Ignore case differences') ],
731
738
              [ 'Boolean', 'display_ignore_endofline', False, _('Ignore end of line differences') ],
732
739
              [ 'Boolean', 'display_hide_endofline', True, _('Hide end of line characters') ]
733
740
            ],
749
756
            [ 'List',
750
757
              [ 'Integer', 'tabs_default_panes', 2, _('Default panes'), 2, 16 ],
751
758
              [ 'Boolean', 'tabs_always_show', False, _('Always show the tab bar') ],
752
 
              [ 'Boolean', 'tabs_closing_last_quits', False, _('Closing the last tab quits %s' % APP_NAME) ]
 
759
              [ 'Boolean', 'tabs_closing_last_quits', False, _('Closing the last tab quits %s') % APP_NAME ]
753
760
            ],
754
761
            _('Regional Settings'),
755
762
            [ 'List',
815
822
                            else:
816
823
                                raise ValueError()
817
824
                    except ValueError:
818
 
                        logError(_('Error parsing line %(line)d of "%(file)s".') % { 'line': j + 1, 'file': self.path })
 
825
                        logError(_('Error parsing line %(line)d of %(file)s.') % { 'line': j + 1, 'file': self.path })
819
826
            except IOError:
820
 
                logError(_('Error reading "%s".') % (self.path, ))
 
827
                logError(_('Error reading %s.') % (self.path, ))
821
828
 
822
829
    # recursively traverses 'template' to discover the preferences and
823
830
    # initialise their default values in self.bool_prefs, self.int_prefs, and
866
873
                    f.write(s)
867
874
                f.close()
868
875
            except IOError:
869
 
                logError(_('Error writing "%s".') % (self.path, ))
870
 
                m = MessageDialog(parent, gtk.MESSAGE_ERROR, _('Error writing %s.') % (repr(self.path), ))
 
876
                m = MessageDialog(parent, gtk.MESSAGE_ERROR, _('Error writing %s.') % (self.path, ))
871
877
                m.run()
872
878
                m.destroy()
873
879
            for k in self.bool_prefs.keys():
1296
1302
                if len(s) > 2 and s[0] in 'ACMR':
1297
1303
                    k = os.path.join(self.root, prefs.convertToNativePath(s[2:]))
1298
1304
                    if not isabs:
1299
 
                        k = replpath(pwd, k)
 
1305
                        k = relpath(pwd, k)
1300
1306
                    r[k] = s[0]
1301
1307
            for k in sorted(r.keys()):
1302
1308
                v = r[k]
1530
1536
                args.append(safeRelativePath(self.root, name, prefs, 'svn_cygwin'))
1531
1537
            for s in strip_eols(popenReadLines(self.root, args, prefs, 'svn_bash')):
1532
1538
                if len(s) > 7 and s[0] in 'ACDMR':
1533
 
                    k = os.path.join(self.root, prefs.convertToNativePath(s[7:]))
 
1539
                    # subversion 1.6 adds a new column
 
1540
                    k = 7
 
1541
                    if k < len(s) and s[k] == ' ':
 
1542
                        k += 1
 
1543
                    k = os.path.join(self.root, prefs.convertToNativePath(s[k:]))
1534
1544
                    if not isabs:
1535
1545
                        k = relpath(pwd, k)
1536
1546
                    # FIXME: prune dropped directories from the results
1613
1623
                        break
1614
1624
                    name = newname
1615
1625
            except IOError:
1616
 
                logError(_('Error parsing "%s".') % (config, ))
 
1626
                logError(_('Error parsing %s.') % (config, ))
1617
1627
        return False
1618
1628
 
1619
1629
    class Svk:
2007
2017
                return self.modified_text
2008
2018
            return self.text
2009
2019
 
2010
 
    def __init__(self, headers, prefs):
 
2020
    def __init__(self, headers, footers, prefs):
2011
2021
        # figure out how many pane panes we should have
2012
2022
        n = len(headers)
2013
2023
        if n < 2:
2014
2024
            raise ValueError('Invalid number of panes')
2015
2025
 
2016
 
        gtk.Table.__init__(self, 2, n + 1)
 
2026
        gtk.Table.__init__(self, 3, n + 1)
2017
2027
        self.set_flags(gtk.CAN_FOCUS)
2018
2028
        self.prefs = prefs
2019
2029
 
2042
2052
 
2043
2053
        # keybindings
2044
2054
        self._line_mode_actions = {
2045
 
                'enter_align_mode': self._line_mode_enter_align_mode,
2046
 
                'enter_character_mode': self.setCharMode,
2047
 
                'first_line': self._first_line,
2048
 
                'extend_first_line': self._extend_first_line,
2049
 
                'last_line': self._last_line,
2050
 
                'extend_last_line': self._extend_last_line,
2051
 
                'up': self._line_mode_up,
2052
 
                'extend_up': self._line_mode_extend_up,
2053
 
                'down': self._line_mode_down,
2054
 
                'extend_down': self._line_mode_extend_down,
2055
 
                'left': self._line_mode_left,
2056
 
                'extend_left': self._line_mode_extend_left,
2057
 
                'right': self._line_mode_right,
2058
 
                'extend_right': self._line_mode_extend_right,
2059
 
                'page_up': self._line_mode_page_up,
2060
 
                'extend_page_up': self._line_mode_extend_page_up,
2061
 
                'page_down': self._line_mode_page_down,
2062
 
                'extend_page_down': self._line_mode_extend_page_down,
2063
 
                'delete_text': self._delete_text,
2064
 
                'clear_edits': self._clear_edits,
2065
 
                'isolate': self._isolate,
2066
 
                'first_difference': self._first_difference,
2067
 
                'previous_difference': self._previous_difference,
2068
 
                'next_difference': self._next_difference,
2069
 
                'last_difference': self._last_difference,
2070
 
                'merge_from_left': self._merge_from_left,
2071
 
                'merge_from_right': self._merge_from_right,
2072
 
                'merge_from_left_then_right': self._merge_from_left_then_right,
2073
 
                'merge_from_right_then_left': self._merge_from_right_then_left }
 
2055
            'enter_align_mode': self._line_mode_enter_align_mode,
 
2056
            'enter_character_mode': self.setCharMode,
 
2057
            'first_line': self._first_line,
 
2058
            'extend_first_line': self._extend_first_line,
 
2059
            'last_line': self._last_line,
 
2060
            'extend_last_line': self._extend_last_line,
 
2061
            'up': self._line_mode_up,
 
2062
            'extend_up': self._line_mode_extend_up,
 
2063
            'down': self._line_mode_down,
 
2064
            'extend_down': self._line_mode_extend_down,
 
2065
            'left': self._line_mode_left,
 
2066
            'extend_left': self._line_mode_extend_left,
 
2067
            'right': self._line_mode_right,
 
2068
            'extend_right': self._line_mode_extend_right,
 
2069
            'page_up': self._line_mode_page_up,
 
2070
            'extend_page_up': self._line_mode_extend_page_up,
 
2071
            'page_down': self._line_mode_page_down,
 
2072
            'extend_page_down': self._line_mode_extend_page_down,
 
2073
            'delete_text': self._delete_text,
 
2074
            'clear_edits': self.clear_edits,
 
2075
            'isolate': self.isolate,
 
2076
            'first_difference': self.first_difference,
 
2077
            'previous_difference': self.previous_difference,
 
2078
            'next_difference': self.next_difference,
 
2079
            'last_difference': self.last_difference,
 
2080
            'merge_from_left': self.merge_from_left,
 
2081
            'merge_from_right': self.merge_from_right,
 
2082
            'merge_from_left_then_right': self.merge_from_left_then_right,
 
2083
            'merge_from_right_then_left': self.merge_from_right_then_left }
2074
2084
        self._align_mode_actions = {
2075
 
                'enter_line_mode': self._align_mode_enter_line_mode,
2076
 
                'enter_character_mode': self.setCharMode,
2077
 
                'first_line': self._first_line,
2078
 
                'last_line': self._last_line,
2079
 
                'up': self._line_mode_up,
2080
 
                'down': self._line_mode_down,
2081
 
                'left': self._line_mode_left,
2082
 
                'right': self._line_mode_right,
2083
 
                'page_up': self._line_mode_page_up,
2084
 
                'page_down': self._line_mode_page_down,
2085
 
                'align': self._align_text }
 
2085
            'enter_line_mode': self._align_mode_enter_line_mode,
 
2086
            'enter_character_mode': self.setCharMode,
 
2087
            'first_line': self._first_line,
 
2088
            'last_line': self._last_line,
 
2089
            'up': self._line_mode_up,
 
2090
            'down': self._line_mode_down,
 
2091
            'left': self._line_mode_left,
 
2092
            'right': self._line_mode_right,
 
2093
            'page_up': self._line_mode_page_up,
 
2094
            'page_down': self._line_mode_page_down,
 
2095
            'align': self._align_text }
2086
2096
        self._character_mode_actions = {
2087
 
                'enter_line_mode': self.setLineMode }
 
2097
            'enter_line_mode': self.setLineMode }
 
2098
        self._button_actions = {
 
2099
            'undo': self.undo,
 
2100
            'redo': self.redo,
 
2101
            'cut': self.cut,
 
2102
            'copy': self.copy,
 
2103
            'paste': self.paste,
 
2104
            'select_all': self.select_all,
 
2105
            'clear_edits': self.clear_edits,
 
2106
            'decrease_indenting': self.decrease_indenting,
 
2107
            'increase_indenting': self.increase_indenting,
 
2108
            'convert_to_dos': self.convert_to_dos,
 
2109
            'convert_to_mac': self.convert_to_mac,
 
2110
            'convert_to_unix': self.convert_to_unix,
 
2111
            'realign_all': self.realign_all,
 
2112
            'isolate': self.isolate,
 
2113
            'first_difference': self.first_difference,
 
2114
            'previous_difference': self.previous_difference,
 
2115
            'next_difference': self.next_difference,
 
2116
            'last_difference': self.last_difference,
 
2117
            'shift_pane_left': self.shift_pane_left,
 
2118
            'shift_pane_right': self.shift_pane_right,
 
2119
            'merge_from_left': self.merge_from_left,
 
2120
            'merge_from_right': self.merge_from_right,
 
2121
            'merge_from_left_then_right': self.merge_from_left_then_right,
 
2122
            'merge_from_right_then_left': self.merge_from_right_then_left }
2088
2123
 
2089
2124
        # create panes
2090
2125
        self.dareas = []
2114
2149
                sw.set_vadjustment(self.vadj)
2115
2150
            self.attach(sw, i, i + 1, 1, 2)
2116
2151
            sw.show()
 
2152
 
 
2153
            # pane footer
 
2154
            self.attach(footers[i], i, i + 1, 2, 3, gtk.FILL, gtk.FILL)
 
2155
 
2117
2156
        self.vadj.connect('value_changed', self.map_vadj_changed_cb)
2118
2157
 
2119
2158
        # add diff map
2139
2178
    # this must be connected with 'connect_after()' so the final widget sizes
2140
2179
    # are known and the scroll bar can be moved to the first difference
2141
2180
    def _realise_cb(self, widget):
2142
 
        self._first_difference()
 
2181
        self.first_difference()
 
2182
 
 
2183
    # callback for most menu items and buttons
 
2184
    def button_cb(self, widget, data):
 
2185
        self.openUndoBlock()
 
2186
        self._button_actions[data]()
 
2187
        self.closeUndoBlock()
2143
2188
 
2144
2189
    # updates the display font and resizes viewports as necessary
2145
2190
    def setFont(self, font):
2222
2267
            if v < 32:
2223
2268
                if c == '\t':
2224
2269
                    width = tab_width - col % tab_width
2225
 
                    result.append(width * ' ')
 
2270
                    result.append(width * u' ')
2226
2271
                else:
2227
 
                    result.append('^' + chr(v + 64))
 
2272
                    result.append(u'^' + chr(v + 64))
2228
2273
            else:
2229
2274
                result.append(c)
2230
2275
            col += self.characterWidth(col, c)
2232
2277
 
2233
2278
    # changes the viewer's mode to LINE_MODE
2234
2279
    def setLineMode(self):
2235
 
        if self.mode == CHAR_MODE:
2236
 
            self.dareas[self.current_pane].queue_draw()
2237
 
            self.current_char = 0
2238
 
            self.selection_char = 0
2239
 
        elif self.mode == ALIGN_MODE:
2240
 
            self.dareas[self.align_pane].queue_draw()
2241
 
            self.dareas[self.current_pane].queue_draw()
2242
 
            self.align_pane = 0
2243
 
            self.align_line = 0
2244
 
        self.mode = LINE_MODE
2245
 
        self.updatePrompt()
 
2280
        if self.mode != LINE_MODE:
 
2281
            if self.mode == CHAR_MODE:
 
2282
                self.dareas[self.current_pane].queue_draw()
 
2283
                self.current_char = 0
 
2284
                self.selection_char = 0
 
2285
            elif self.mode == ALIGN_MODE:
 
2286
                self.dareas[self.align_pane].queue_draw()
 
2287
                self.dareas[self.current_pane].queue_draw()
 
2288
                self.align_pane = 0
 
2289
                self.align_line = 0
 
2290
            self.mode = LINE_MODE
 
2291
            self.emit('cursor_changed')
 
2292
            self.emit('mode_changed')
2246
2293
 
2247
2294
    # changes the viewer's mode to CHAR_MODE
2248
2295
    def setCharMode(self):
2249
 
        if self.mode == LINE_MODE:
2250
 
            self.cursor_column = -1
2251
 
            self.setCurrentChar(self.current_line, 0)
2252
 
        elif self.mode == ALIGN_MODE:
2253
 
            self.dareas[self.align_pane].queue_draw()
2254
 
            self.cursor_column = -1
2255
 
            self.align_pane = 0
2256
 
            self.align_line = 0
2257
 
            self.setCurrentChar(self.current_line, 0)
2258
 
        self.mode = CHAR_MODE
2259
 
        self.updatePrompt()
 
2296
        if self.mode != CHAR_MODE:
 
2297
            if self.mode == LINE_MODE:
 
2298
                self.cursor_column = -1
 
2299
                self.setCurrentChar(self.current_line, 0)
 
2300
            elif self.mode == ALIGN_MODE:
 
2301
                self.dareas[self.align_pane].queue_draw()
 
2302
                self.cursor_column = -1
 
2303
                self.align_pane = 0
 
2304
                self.align_line = 0
 
2305
                self.setCurrentChar(self.current_line, 0)
 
2306
            self.mode = CHAR_MODE
 
2307
            self.emit('cursor_changed')
 
2308
            self.emit('mode_changed')
2260
2309
 
2261
2310
    # sets the syntax hightlighting rules
2262
 
    def setSyntax(self, syntax):
2263
 
        if self.syntax is not syntax:
2264
 
            self.syntax = syntax
 
2311
    def setSyntax(self, s):
 
2312
        if self.syntax is not s:
 
2313
            self.syntax = s
2265
2314
            # invalidate the syntax caches
2266
2315
            for pane in self.panes:
2267
2316
                pane.syntax_cache = []
 
2317
            self.emit('syntax_changed', s)
2268
2318
            # force all panes to redraw
2269
2319
            for darea in self.dareas:
2270
2320
                darea.queue_draw()
2271
2321
 
 
2322
    # gets the syntax
 
2323
    def getSyntax(self):
 
2324
        return self.syntax
 
2325
 
2272
2326
    # returns True if any pane contains edits
2273
2327
    def hasEdits(self):
2274
2328
        for pane in self.panes:
2304
2358
            self.undos.append(self.undoblock)
2305
2359
        self.undoblock = None
2306
2360
 
2307
 
    # undo the last block of changes on the undo stack
 
2361
    # 'undo' action
2308
2362
    def undo(self):
 
2363
        self.undoblock, old_block = None, self.undoblock
2309
2364
        if self.mode == LINE_MODE or self.mode == CHAR_MODE:
2310
2365
            if len(self.undos) > 0:
2311
2366
                # move the block to the redo stack
2314
2369
                # undo all changes in the block in reverse order
2315
2370
                for u in block[::-1]:
2316
2371
                    u.undo(self)
 
2372
        self.undoblock = old_block
2317
2373
 
2318
 
    # re-apply the block of changes on the redo stack
 
2374
    # 'redo' action
2319
2375
    def redo(self):
 
2376
        self.undoblock, old_block = None, self.undoblock
2320
2377
        if self.mode == LINE_MODE or self.mode == CHAR_MODE:
2321
2378
            if len(self.redos) > 0:
2322
2379
                # move the block to the undo stack
2325
2382
                # re-apply all changes in the block
2326
2383
                for u in block:
2327
2384
                    u.redo(self)
 
2385
        self.undoblock = old_block
2328
2386
 
2329
2387
    # returns the width of the viewport's line number column in pango units
2330
2388
    def getLineNumberWidth(self):
2414
2472
            # create an Undo object for the action
2415
2473
            self.addUndo(FileDiffViewer.SetFormatUndo(f, format, pane.format))
2416
2474
        pane.format = format
 
2475
        self.emit('format_changed', f, format)
2417
2476
 
2418
2477
    # Undo for the creation of Line objects
2419
2478
    class InstanceLineUndo:
3088
3147
    # this should be called before and after actions that also change the
3089
3148
    # selection
3090
3149
    def recordEditMode(self):
3091
 
        self.addUndo(FileDiffViewer.EditModeUndo(self.mode, self.current_pane, self.current_line, self.current_char, self.selection_line, self.selection_char, self.cursor_column))
 
3150
        if self.undoblock is not None:
 
3151
            self.addUndo(FileDiffViewer.EditModeUndo(self.mode, self.current_pane, self.current_line, self.current_char, self.selection_line, self.selection_char, self.cursor_column))
3092
3152
 
3093
3153
    # change the selection mode
3094
3154
    def setEditMode(self, mode, f, current_line, current_char, selection_line, selection_char, cursor_column):
3105
3165
            self.setCurrentChar(self.current_line, self.current_char, True)
3106
3166
        else:
3107
3167
            self.setCurrentLine(self.current_pane, self.current_line, True)
3108
 
        # some selections display information in the status bar
3109
 
        self.updatePrompt()
 
3168
        self.emit('cursor_changed')
 
3169
        self.emit('mode_changed')
3110
3170
        # queue a redraw to show the updated selection
3111
3171
        self.dareas[old_f].queue_draw()
3112
3172
 
3133
3193
            vadj.set_value(lower)
3134
3194
        elif upper > v + ps:
3135
3195
            vadj.set_value(upper - ps)
 
3196
        self.emit('cursor_changed')
3136
3197
        # queue redraw
3137
3198
        self.dareas[old_f].queue_draw()
3138
3199
        self.dareas[f].queue_draw()
3139
3200
 
3140
 
    # display the cursor's column position
3141
 
    def updatePrompt(self):
3142
 
        if self.mode == CHAR_MODE:
3143
 
            j = self.current_char
3144
 
            if j > 0:
3145
 
                text = self.getLineText(self.current_pane, self.current_line)[:j]
3146
 
                j = self.stringWidth(self.current_pane, text)
3147
 
            s = _('Column %d') % (j, )
3148
 
        else:
3149
 
            s = ''
3150
 
        self.emit('status_changed', s)
3151
 
 
3152
3201
    # change the current selection in CHAR_MODE
3153
3202
    # use extend=True to extend the selection
3154
3203
    def setCurrentChar(self, i, j, extend=False):
3190
3239
            hadj.set_value(lower)
3191
3240
        elif upper > v + ps:
3192
3241
            hadj.set_value(upper - ps)
 
3242
        self.emit('cursor_changed')
3193
3243
        # queue redraw
3194
3244
        self.dareas[f].queue_draw()
3195
 
        self.updatePrompt()
3196
3245
 
3197
3246
    # returns the currently selected text
3198
3247
    def getSelectedText(self):
3222
3271
        return ''.join([ s for s in ss if s is not None ])
3223
3272
 
3224
3273
    # expands the selection to include everything
3225
 
    def selectAll(self):
 
3274
    def select_all(self):
3226
3275
        if self.mode == LINE_MODE or self.mode == CHAR_MODE:
3227
3276
            f = self.current_pane
3228
3277
            self.selection_line = 0
3262
3311
            j = self._getPickedCharacter(f, text, x, True)
3263
3312
            self.setCurrentChar(i, j, extend)
3264
3313
        else:
3265
 
            self.setLineMode()
 
3314
            if self.mode == ALIGN_MODE:
 
3315
                extend = False
 
3316
            elif self.mode == CHAR_MODE:
 
3317
                self.setLineMode()
3266
3318
            self.setCurrentLine(f, i, extend and f == self.current_pane)
3267
3319
 
3268
3320
    # callback for mouse button presses in the text window
3276
3328
            # left mouse button
3277
3329
            if event.type == gtk.gdk._2BUTTON_PRESS:
3278
3330
                # double click
 
3331
                if self.mode == ALIGN_MODE:
 
3332
                    self.setLineMode()
3279
3333
                if self.mode == LINE_MODE:
3280
3334
                    # change to CHAR_MODE
3281
3335
                    self.setCurrentLine(f, i)
3282
3336
                    # silently switch mode so the viewer does not scroll yet.
3283
3337
                    self.mode = CHAR_MODE
3284
3338
                    self.button_press(f, x, y, False)
 
3339
                    self.emit('mode_changed')
3285
3340
                elif self.mode == CHAR_MODE and self.current_pane == f:
3286
3341
                    # select word
3287
3342
                    text = strip_eol(self.getLineText(f, i))
3316
3371
                gtk.clipboard_get(gtk.gdk.SELECTION_PRIMARY).request_text(self.receive_clipboard_text_cb)
3317
3372
        elif event.button == 3:
3318
3373
            # right mouse button, raise context sensitive menu
3319
 
            flag = (self.mode == LINE_MODE and (f == self.current_pane + 1 or f == self.current_pane - 1))
3320
 
            can_align = (flag or self.mode == ALIGN_MODE)
 
3374
            can_align = (self.mode == LINE_MODE and (f == self.current_pane + 1 or f == self.current_pane - 1))
3321
3375
            can_isolate = (self.mode == LINE_MODE and f == self.current_pane)
3322
3376
            can_merge = (self.mode == LINE_MODE and f != self.current_pane)
3323
3377
            can_select = ((self.mode == LINE_MODE or self.mode == CHAR_MODE) and f == self.current_pane)
3325
3379
 
3326
3380
            menu = createMenu(
3327
3381
                      [ [_('Align to Selection'), self.align_to_selection_cb, [f, i], gtk.STOCK_EXECUTE, None, can_align],
3328
 
                      [_('Isolate'), self.isolate_cb, None, None, None, can_isolate ],
 
3382
                      [_('Isolate'), self.button_cb, 'isolate', None, None, can_isolate ],
3329
3383
                      [_('Merge'), self.merge_lines_cb, f, None, None, can_merge],
3330
3384
                      [],
3331
 
                      [_('Cut'), self.cut_cb, None, gtk.STOCK_CUT, None, can_select],
3332
 
                      [_('Copy'), self.copy_cb, None, gtk.STOCK_COPY, None, can_select],
3333
 
                      [_('Paste'), self.paste_cb, None, gtk.STOCK_PASTE, None, can_select],
 
3385
                      [_('Cut'), self.button_cb, 'cut', gtk.STOCK_CUT, None, can_select],
 
3386
                      [_('Copy'), self.button_cb, 'copy', gtk.STOCK_COPY, None, can_select],
 
3387
                      [_('Paste'), self.button_cb, 'paste', gtk.STOCK_PASTE, None, can_select],
3334
3388
                      [],
3335
 
                      [_('Select All'), self.select_all_cb, None, None, None, can_select],
3336
 
                      [_('Clear Edits'), self.clear_edits_cb, None, gtk.STOCK_CLEAR, None, can_isolate],
 
3389
                      [_('Select All'), self.button_cb, 'select_all', None, None, can_select],
 
3390
                      [_('Clear Edits'), self.button_cb, 'clear_edits', gtk.STOCK_CLEAR, None, can_isolate],
3337
3391
                      [],
3338
3392
                      [_('Swap with Selected Pane'), self.swap_panes_cb, f, None, None, can_swap] ])
3339
3393
            menu.popup(None, None, None, event.button, event.time)
3468
3522
    # draw the text viewport
3469
3523
    def darea_expose_cb(self, widget, event, f):
3470
3524
        pane = self.panes[f]
3471
 
        syntax = self.syntax
 
3525
        syntax = theResources.getSyntax(self.syntax)
3472
3526
 
3473
3527
        offset_x, offset_y, width, height = event.area
3474
3528
        x = offset_x + int(self.hadj.get_value())
3907
3961
        self.selection_line = self.current_line
3908
3962
        self.align_pane = self.current_pane
3909
3963
        self.align_line = self.current_line
 
3964
        self.emit('mode_changed')
3910
3965
        self.dareas[self.align_pane].queue_draw()
3911
3966
 
3912
3967
    # 'first_line' keybinding action
4226
4281
        self.closeUndoBlock()
4227
4282
        return retval
4228
4283
 
4229
 
    # callback for copy menu item
4230
 
    def copy_cb(self, widget, data):
 
4284
    # 'copy' action
 
4285
    def copy(self):
4231
4286
        if self.mode == LINE_MODE or self.mode == CHAR_MODE:
4232
4287
            gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD).set_text(self.getSelectedText())
4233
4288
 
4234
 
    # callback for cut menu item
4235
 
    def cut_cb(self, widget, data):
 
4289
    # 'cut' action
 
4290
    def cut(self):
4236
4291
        if self.mode == LINE_MODE or self.mode == CHAR_MODE:
4237
 
            self.copy_cb(widget, data)
4238
 
            self.openUndoBlock()
 
4292
            self.copy()
4239
4293
            self.replaceText('')
4240
 
            self.closeUndoBlock()
4241
4294
 
4242
4295
    # callback used when receiving clipboard text
4243
4296
    def receive_clipboard_text_cb(self, clipboard, text, data):
4244
4297
        if self.mode == LINE_MODE or self.mode == CHAR_MODE:
4245
 
            self.openUndoBlock()
4246
4298
            self.replaceText(self.prefs.convertToUnicode([ text ])[0][0])
4247
 
            self.closeUndoBlock()
4248
4299
 
4249
 
    # callback for paste menu item
4250
 
    def paste_cb(self, widget, data):
 
4300
    # 'paste' action
 
4301
    def paste(self):
4251
4302
         gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD).request_text(self.receive_clipboard_text_cb)
4252
4303
 
4253
 
    # callback for select all menu item
4254
 
    def select_all_cb(self, widget, data):
4255
 
        if self.mode == LINE_MODE or self.mode == CHAR_MODE:
4256
 
            self.selectAll()
4257
 
 
4258
 
    # 'clear_edits' keybinding action
4259
 
    def _clear_edits(self):
 
4304
    # 'clear_edits' action
 
4305
    def clear_edits(self):
4260
4306
        self.setLineMode()
4261
4307
        self.recordEditMode()
4262
4308
        f = self.current_pane
4274
4320
                    self.instanceLine(f, i, True)
4275
4321
        self.recordEditMode()
4276
4322
 
4277
 
    # callback for clear edits menu item
4278
 
    def clear_edits_cb(self, widget, data):
4279
 
        self.openUndoBlock()
4280
 
        self._clear_edits()
4281
 
        self.closeUndoBlock()
4282
 
 
4283
4323
    # callback for find menu item
4284
4324
    def find(self, pattern, match_case, backwards, from_start):
4285
4325
        self.setCharMode()
4365
4405
                start, end = end, start
4366
4406
            end += 1
4367
4407
 
4368
 
            self.openUndoBlock()
4369
4408
            self.recordEditMode()
4370
4409
            for i in range(start, end):
4371
4410
                text = self.getLineText(f, i)
4391
4430
                self.setCurrentChar(self.selection_line, 0)
4392
4431
                self.setCurrentChar(i, 0, True)
4393
4432
            self.recordEditMode()
4394
 
            self.closeUndoBlock()
4395
4433
 
4396
 
    # callback for decrease indenting menu item
4397
 
    def decrease_indenting_cb(self, widget, data):
 
4434
    # 'decrease_indenting' action
 
4435
    def decrease_indenting(self):
4398
4436
        self._adjust_indenting(-1)
4399
4437
 
4400
 
    # callback for increase indenting menu item
4401
 
    def increase_indenting_cb(self, widget, data):
 
4438
    # 'increase_indenting' action
 
4439
    def increase_indenting(self):
4402
4440
        self._adjust_indenting(1)
4403
4441
 
4404
4442
    def convert_format(self, format):
4405
4443
        self.setLineMode()
4406
 
        self.openUndoBlock()
4407
4444
        self.recordEditMode()
4408
4445
        f = self.current_pane
4409
4446
        for i in range(len(self.panes[f].lines)):
4414
4451
                self.updateLineText(f, i, True, s)
4415
4452
        self.setFormat(f, format)
4416
4453
        self.recordEditMode()
4417
 
        self.closeUndoBlock()
4418
4454
 
4419
 
    # callback for convert to DOS format menu item
4420
 
    def convert_to_dos_cb(self, widget, data):
 
4455
    # 'convert_to_dos' action
 
4456
    def convert_to_dos(self):
4421
4457
        self.convert_format(DOS_FORMAT)
4422
4458
 
4423
 
    # callback for convert to Mac format menu item
4424
 
    def convert_to_mac_cb(self, widget, data):
 
4459
    # 'convert_to_mac' action
 
4460
    def convert_to_mac(self):
4425
4461
        self.convert_format(MAC_FORMAT)
4426
4462
 
4427
 
    # callback for convert to Unix format menu item
4428
 
    def convert_to_unix_cb(self, widget, data):
 
4463
    # 'convert_to_unix' action
 
4464
    def convert_to_unix(self):
4429
4465
        self.convert_format(UNIX_FORMAT)
4430
4466
 
4431
4467
    # recompute viewport size and redraw as the display preferences may have
4434
4470
        self.setFont(pango.FontDescription(self.prefs.getString('display_font')))
4435
4471
        for pane in self.panes:
4436
4472
            del pane.diff_cache[:]
 
4473
        # tab width may have changed
 
4474
        self.emit('cursor_changed')
4437
4475
        for darea in self.dareas:
4438
4476
            darea.queue_draw()
4439
4477
        self.map_cache = None
4440
4478
        self.map.queue_draw()
4441
 
        self.updatePrompt()
4442
4479
 
4443
 
    # 'realign_all' keybinding action
4444
 
    def realign_all_cb(self, widget, data):
 
4480
    # 'realign_all' action
 
4481
    def realign_all(self):
4445
4482
        self.setLineMode()
4446
4483
        f = self.current_pane
4447
 
        self.openUndoBlock()
4448
4484
        self.recordEditMode()
4449
4485
        lines = []
4450
4486
        blocks = []
4463
4499
        self.updateBlocks(blocks)
4464
4500
        self.setCurrentLine(f, min(self.current_line, len(self.panes[f].lines)))
4465
4501
        self.recordEditMode()
4466
 
        self.closeUndoBlock()
4467
4502
 
4468
4503
    # callback for the align to selection menu item
4469
4504
    def align_to_selection_cb(self, widget, data):
4481
4516
        self.recordEditMode()
4482
4517
        self.closeUndoBlock()
4483
4518
 
4484
 
    # 'isolate' keybinding action
4485
 
    def _isolate(self):
 
4519
    # 'isolate' action
 
4520
    def isolate(self):
4486
4521
        self.setLineMode()
 
4522
        self.recordEditMode()
4487
4523
        f = self.current_pane
4488
4524
        start, end = self.selection_line, self.current_line
4489
4525
        if end < start:
4519
4555
            pre.extend(middle)
4520
4556
            pre.extend(post)
4521
4557
            self.updateBlocks(pre)
4522
 
 
4523
 
    # callback for the isolate menu item
4524
 
    def isolate_cb(self, widget, data):
4525
 
        self.openUndoBlock()
4526
 
        self.recordEditMode()
4527
 
        self._isolate()
4528
 
        self.recordEditMode()
4529
 
        self.closeUndoBlock()
 
4558
        self.recordEditMode()
4530
4559
 
4531
4560
    # returns True if line 'i' in pane 'f' has an edit or is different from its
4532
4561
    # neighbour
4581
4610
            self.selection_line = i
4582
4611
            self.setCurrentLine(f, start, True)
4583
4612
 
4584
 
    # 'first_difference' keybinding action
4585
 
    def _first_difference(self):
 
4613
    # 'first_difference' action
 
4614
    def first_difference(self):
4586
4615
        self.setLineMode()
4587
4616
        self.goto_difference(0, 1)
4588
4617
 
4589
 
    # callback for the first difference menu item
4590
 
    def first_difference_cb(self, widget, data):
4591
 
        self._first_difference()
4592
 
 
4593
 
    # 'previous_difference' keybinding action
4594
 
    def _previous_difference(self):
 
4618
    # 'previous_difference' action
 
4619
    def previous_difference(self):
4595
4620
        self.setLineMode()
4596
4621
        i = min(self.current_line, self.selection_line) - 1
4597
4622
        self.goto_difference(i, -1)
4598
4623
 
4599
 
    # callback for the previous difference menu item
4600
 
    def previous_difference_cb(self, widget, data):
4601
 
        self._previous_difference()
4602
 
 
4603
 
    # 'next_difference' keybinding action
4604
 
    def _next_difference(self):
 
4624
    # 'next_difference' action
 
4625
    def next_difference(self):
4605
4626
        self.setLineMode()
4606
4627
        i = max(self.current_line, self.selection_line) + 1
4607
4628
        self.goto_difference(i, 1)
4608
4629
 
4609
 
    # callback for the next difference menu item
4610
 
    def next_difference_cb(self, widget, data):
4611
 
        self._next_difference()
4612
 
 
4613
 
    # 'last_difference' keybinding action
4614
 
    def _last_difference(self):
 
4630
    # 'last_difference' action
 
4631
    def last_difference(self):
4615
4632
        self.setLineMode()
4616
 
        f = self.current_pane
4617
 
        i = len(self.panes[f].lines)
 
4633
        i = len(self.panes[self.current_pane].lines)
4618
4634
        self.goto_difference(i, -1)
4619
4635
 
4620
 
    # callback for the last difference menu item
4621
 
    def last_difference_cb(self, widget, data):
4622
 
        self._last_difference()
4623
 
 
4624
4636
    # Undo for changes to the pane ordering
4625
4637
    class SwapPanesUndo:
4626
4638
        def __init__(self, f_dst, f_src):
4660
4672
        if f_dst >= 0 and f_dst < len(self.panes):
4661
4673
            if self.mode == ALIGN_MODE:
4662
4674
                self.setLineMode()
4663
 
            self.openUndoBlock()
4664
4675
            self.recordEditMode()
4665
4676
            self.swapPanes(f_dst, f_src)
4666
4677
            self.recordEditMode()
4667
 
            self.closeUndoBlock()
4668
4678
 
4669
4679
    # callback for swap panes menu item
4670
4680
    def swap_panes_cb(self, widget, data):
 
4681
        self.openUndoBlock()
4671
4682
        self.swap_panes(data, self.current_pane)
 
4683
        self.closeUndoBlock()
4672
4684
 
4673
 
    # callback for shift pane left menu item
4674
 
    def shift_pane_left_cb(self, widget, data):
 
4685
    # 'shift_pane_left' action
 
4686
    def shift_pane_left(self):
4675
4687
        f = self.current_pane
4676
4688
        self.swap_panes(f - 1, f)
4677
4689
 
4678
 
    # callback for shift pane right menu item
4679
 
    def shift_pane_right_cb(self, widget, data):
 
4690
    # 'shift_pane_right' action
 
4691
    def shift_pane_right(self):
4680
4692
        f = self.current_pane
4681
4693
        self.swap_panes(f + 1, f)
4682
4694
 
4683
4695
    # copies the selected range of lines from pane 'f_src' to 'f_dst'
4684
4696
    def merge_lines(self, f_dst, f_src):
 
4697
        self.recordEditMode()
4685
4698
        self.setLineMode()
4686
4699
        pane = self.panes[f_dst]
4687
4700
        start, end = self.selection_line, self.current_line
4695
4708
            self.setFormat(f_dst, getFormat(ss))
4696
4709
        for i, s in enumerate(ss):
4697
4710
            self.updateText(f_dst, start + i, convert_to_format(s, pane.format))
 
4711
        self.recordEditMode()
4698
4712
 
4699
4713
    # callback for merge lines menu item
4700
4714
    def merge_lines_cb(self, widget, data):
4701
4715
        self.openUndoBlock()
4702
 
        self.recordEditMode()
4703
4716
        self.merge_lines(data, self.current_pane)
4704
 
        self.recordEditMode()
4705
4717
        self.closeUndoBlock()
4706
4718
 
4707
 
    # 'merge_from_left' keybinding action
4708
 
    def _merge_from_left(self):
 
4719
    # 'merge_from_left' action
 
4720
    def merge_from_left(self):
4709
4721
        f = self.current_pane
4710
4722
        if f > 0:
4711
4723
            self.merge_lines(f, f - 1)
4712
4724
 
4713
 
    # callback for merge from left menu item
4714
 
    def merge_from_left_cb(self, widget, data):
4715
 
        self.openUndoBlock()
4716
 
        self.recordEditMode()
4717
 
        self._merge_from_left()
4718
 
        self.recordEditMode()
4719
 
        self.closeUndoBlock()
4720
 
 
4721
 
    # 'merge_from_right' keybinding action
4722
 
    def _merge_from_right(self):
 
4725
    # 'merge_from_right' action
 
4726
    def merge_from_right(self):
4723
4727
        f = self.current_pane
4724
4728
        if f + 1 < len(self.panes):
4725
4729
            self.merge_lines(f, f + 1)
4726
4730
 
4727
 
    # callback for merge from right menu item
4728
 
    def merge_from_right_cb(self, widget, data):
4729
 
        self.openUndoBlock()
4730
 
        self.recordEditMode()
4731
 
        self._merge_from_right()
4732
 
        self.recordEditMode()
4733
 
        self.closeUndoBlock()
4734
 
 
4735
4731
    # merge from both left and right into the current pane
4736
4732
    def _mergeBoth(self, right_first):
 
4733
        self.recordEditMode()
4737
4734
        self.setLineMode()
4738
4735
        f = self.current_pane
4739
4736
        start, end = self.selection_line, self.current_line
4812
4809
                end -= 1
4813
4810
            self.selection_line = start
4814
4811
            self.setCurrentLine(f, end, True)
 
4812
        self.recordEditMode()
4815
4813
 
4816
4814
    # 'merge_from_left_then_right' keybinding action
4817
 
    def _merge_from_left_then_right(self):
 
4815
    def merge_from_left_then_right(self):
4818
4816
        self._mergeBoth(False)
4819
4817
 
4820
4818
    # 'merge_from_right_then_left' keybinding action
4821
 
    def _merge_from_right_then_left(self):
 
4819
    def merge_from_right_then_left(self):
4822
4820
        self._mergeBoth(True)
4823
4821
 
4824
 
    # callback for merge from left then right menu item
4825
 
    def merge_from_left_then_right_cb(self, widget, data):
4826
 
        self.openUndoBlock()
4827
 
        self.recordEditMode()
4828
 
        self._merge_from_left_then_right()
4829
 
        self.recordEditMode()
4830
 
        self.closeUndoBlock()
4831
 
 
4832
 
    # callback for merge from right then left menu item
4833
 
    def merge_from_right_then_left_cb(self, widget, data):
4834
 
        self.openUndoBlock()
4835
 
        self.recordEditMode()
4836
 
        self._merge_from_right_then_left()
4837
 
        self.recordEditMode()
4838
 
        self.closeUndoBlock()
4839
 
 
4840
4822
# create 'title_changed' signal for FileDiffViewer
4841
4823
gobject.signal_new('swapped_panes', FileDiffViewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (int, int))
4842
4824
gobject.signal_new('num_edits_changed', FileDiffViewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (int, ))
4843
 
gobject.signal_new('status_changed', FileDiffViewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, ))
 
4825
gobject.signal_new('mode_changed', FileDiffViewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
 
4826
gobject.signal_new('cursor_changed', FileDiffViewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
 
4827
gobject.signal_new('syntax_changed', FileDiffViewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, ))
 
4828
gobject.signal_new('format_changed', FileDiffViewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (int, int))
4844
4829
 
4845
4830
# dialogue used to search for text
4846
4831
class SearchDialog(gtk.Dialog):
4992
4977
        self.set_copyright(COPYRIGHT)
4993
4978
        self.set_website(WEBSITE)
4994
4979
        self.set_authors([ 'Derrick Moser <derrick_moser@yahoo.com>' ])
 
4980
        self.set_translator_credits(_('translator_credits'))
4995
4981
        ss = [ APP_NAME + ' ' + VERSION + '\n',
4996
4982
               COPYRIGHT + '\n\n',
4997
4983
               _("""This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the licence, or (at your option) any later version.
5025
5011
        image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
5026
5012
        button.add(image)
5027
5013
        image.show()
 
5014
        if hasattr(button, 'set_tooltip_text'):
 
5015
            # only available in pygtk >= 2.12
 
5016
            button.set_tooltip_text(_('Close Tab'))
5028
5017
        self.pack_start(button, False, False, 0)
5029
5018
        button.show()
5030
5019
 
5052
5041
        # to warn about changes to file on disk
5053
5042
        self.last_stat = None
5054
5043
 
 
5044
# custom statusbar widget information about syntax highlighting
 
5045
class Statusbar(gtk.Statusbar):
 
5046
    def __init__(self):
 
5047
        gtk.Statusbar.__init__(self)
 
5048
        self.context = self.get_context_id('Message')
 
5049
 
 
5050
        separator = gtk.VSeparator()
 
5051
        self.pack_end(separator, False, False, 10)
 
5052
        separator.show()
 
5053
 
 
5054
        self.syntax = syntax = gtk.Label()
 
5055
        self.pack_end(syntax, False, False, 0)
 
5056
        syntax.show()
 
5057
 
 
5058
        separator = gtk.VSeparator()
 
5059
        self.pack_end(separator, False, False, 10)
 
5060
        separator.show()
 
5061
 
 
5062
    # set the status bar text
 
5063
    def setText(self, s):
 
5064
        if s is None:
 
5065
            s = ''
 
5066
        self.pop(self.context)
 
5067
        self.push(self.context, s)
 
5068
 
 
5069
    # set the syntax highlighting label
 
5070
    def setSyntax(self, s):
 
5071
        if s is None:
 
5072
            s = ''
 
5073
        self.syntax.set_text(s)
 
5074
 
5055
5075
# the main application class containing a set of file viewers
5056
5076
# this class displays tab for switching between viewers and dispatches menu
5057
5077
# commands to the current viewer
5102
5122
                    self.has_edits = has_edits
5103
5123
                    self.updateTitle()
5104
5124
 
 
5125
        # pane footer
 
5126
        class PaneFooter(gtk.HBox):
 
5127
            def __init__(self):
 
5128
                gtk.HBox.__init__(self)
 
5129
 
 
5130
                self.cursor = label = gtk.Label()
 
5131
                self.pack_start(label, False, False, 0)
 
5132
                label.show()
 
5133
 
 
5134
                separator = gtk.VSeparator()
 
5135
                self.pack_end(separator, False, False, 10)
 
5136
                separator.show()
 
5137
 
 
5138
                self.encoding = label = gtk.Label()
 
5139
                self.pack_end(label, False, False, 0)
 
5140
                label.show()
 
5141
 
 
5142
                separator = gtk.VSeparator()
 
5143
                self.pack_end(separator, False, False, 10)
 
5144
                separator.show()
 
5145
 
 
5146
                self.format = label = gtk.Label()
 
5147
                self.pack_end(label, False, False, 0)
 
5148
                label.show()
 
5149
 
 
5150
                separator = gtk.VSeparator()
 
5151
                self.pack_end(separator, False, False, 10)
 
5152
                separator.show()
 
5153
 
 
5154
                self.set_size_request(0, self.get_size_request()[1])
 
5155
 
 
5156
            # set the cursor label
 
5157
            def updateCursor(self, viewer, f):
 
5158
                if viewer.mode == CHAR_MODE and viewer.current_pane == f:
 
5159
                    j = viewer.current_char
 
5160
                    if j > 0:
 
5161
                        text = viewer.getLineText(viewer.current_pane, viewer.current_line)[:j]
 
5162
                        j = viewer.stringWidth(viewer.current_pane, text)
 
5163
                    s = _('Column %d') % (j, )
 
5164
                else:
 
5165
                    s = ''
 
5166
                self.cursor.set_text(s)
 
5167
 
 
5168
            # set the format label
 
5169
            def setFormat(self, s):
 
5170
                v = []
 
5171
                if s & DOS_FORMAT:
 
5172
                    v.append('DOS')
 
5173
                if s & MAC_FORMAT:
 
5174
                    v.append('Mac')
 
5175
                if s & UNIX_FORMAT:
 
5176
                    v.append('Unix')
 
5177
                self.format.set_text('/'.join(v))
 
5178
 
 
5179
            # set the format label
 
5180
            def setEncoding(self, s):
 
5181
                if s is None:
 
5182
                    s = ''
 
5183
                self.encoding.set_text(s)
 
5184
 
5105
5185
        def __init__(self, n, prefs, title):
5106
5186
            self.headers = headers = [ Diffuse.FileDiffViewer.PaneHeader() for i in range(n) ]
5107
 
            FileDiffViewer.__init__(self, headers, prefs)
 
5187
            self.footers = footers = [ Diffuse.FileDiffViewer.PaneFooter() for i in range(n) ]
 
5188
            FileDiffViewer.__init__(self, headers, footers, prefs)
5108
5189
            self.title = title
5109
5190
            self.status = ''
5110
5191
 
5111
5192
            self.connect('swapped_panes', self.swapped_panes_cb)
5112
5193
            self.connect('num_edits_changed', self.num_edits_changed_cb)
5113
 
            self.connect('status_changed', self.status_changed_cb)
 
5194
            self.connect('mode_changed', self.mode_changed_cb)
5114
5195
            for i, h in enumerate(headers):
5115
5196
                h.connect('title_changed', self.title_changed_cb)
5116
5197
                h.connect('open', self.open_file_button_cb, i)
5118
5199
                h.connect('save', self.save_file_button_cb, i)
5119
5200
                h.connect('save_as', self.save_file_as_button_cb, i)
5120
5201
                h.show()
 
5202
            for footer in footers:
 
5203
                self.connect('cursor_changed', self.cursor_changed_cb)
 
5204
                self.connect('format_changed', self.format_changed_cb)
 
5205
                footer.show()
5121
5206
            for i, darea in enumerate(self.dareas):
5122
5207
                darea.drag_dest_set(gtk.DEST_DEFAULT_ALL, [ ('text/uri-list', 0, 0) ], gtk.gdk.ACTION_COPY)
5123
5208
                darea.connect('drag_data_received', self.drag_data_received_cb, i)
 
5209
            # initialise status
 
5210
            self.updateStatus()
5124
5211
 
5125
5212
        # callback used when receiving drag-n-drop data
5126
5213
        def drag_data_received_cb(self, widget, context, x, y, selection, targettype, eventtime, f):
5158
5245
 
5159
5246
        # change the file info for pane 'f' to 'info'
5160
5247
        def setFileInfo(self, f, info):
5161
 
            h = self.headers[f]
 
5248
            h, footer = self.headers[f], self.footers[f]
5162
5249
            h.info = info
5163
5250
            h.updateTitle()
 
5251
            footer.setFormat(self.panes[f].format)
 
5252
            footer.setEncoding(info.encoding)
 
5253
            footer.updateCursor(self, f)
5164
5254
 
5165
5255
        # callback used when a pane header's title changes
5166
5256
        def title_changed_cb(self, widget):
5186
5276
                s += ' *'
5187
5277
            self.emit('title_changed', s)
5188
5278
 
 
5279
        def setEncoding(self, f, encoding):
 
5280
            h = self.headers[f]
 
5281
            h.info.encoding = encoding
 
5282
            self.footers[f].setEncoding(encoding)
 
5283
 
5189
5284
        # load a new file into pane 'f'
5190
5285
        # 'info' indicates the name of the file and how to retrieve it from the
5191
5286
        # version control system if applicable
5220
5315
                except (IOError, OSError, UnicodeDecodeError, WindowsError):
5221
5316
                    # FIXME: this can occur before the toplevel window is drawn
5222
5317
                    if rev is not None:
5223
 
                        msg = _('Error reading revision %(rev)s of "%(file)s".') % { 'rev': rev, 'file': name }
 
5318
                        msg = _('Error reading revision %(rev)s of %(file)s.') % { 'rev': rev, 'file': name }
5224
5319
                    else:
5225
 
                        msg = _('Error reading %s.') % (repr(name), )
 
5320
                        msg = _('Error reading %s.') % (name, )
5226
5321
                    dialog = MessageDialog(self.get_toplevel(), gtk.MESSAGE_ERROR, msg)
5227
5322
                    dialog.run()
5228
5323
                    dialog.destroy()
5298
5393
                        if info.last_stat[stat.ST_MTIME] < new_stat[stat.ST_MTIME]:
5299
5394
                            # update our notion of the most recent modification
5300
5395
                            info.last_stat = new_stat
5301
 
                            msg = _('The file "%s" changed on disk.  Do you want to reload the file?') % (info.name, )
 
5396
                            msg = _('The file %s changed on disk.  Do you want to reload the file?') % (info.name, )
5302
5397
                            dialog = MessageDialog(self.get_toplevel(), gtk.MESSAGE_QUESTION, msg)
5303
5398
                            ok = (dialog.run() == gtk.RESPONSE_OK)
5304
5399
                            dialog.destroy()
5335
5430
                # warn if we are about to overwrite an existing file
5336
5431
                if save_as:
5337
5432
                    if os.path.exists(name):
5338
 
                        msg = _('A file named "%s" already exists.  Do you want to overwrite it?') % (name, )
 
5433
                        msg = _('A file named %s already exists.  Do you want to overwrite it?') % (name, )
5339
5434
                # warn if we are about to overwrite a file that has changed
5340
5435
                # since we last read it
5341
5436
                elif info.stat is not None:
5342
5437
                    if info.stat[stat.ST_MTIME] < os.stat(name)[stat.ST_MTIME]:
5343
 
                        msg = _('The file "%s" has been modified by another process since reading it.  If you save, all the external changes could be lost.  Save anyways?') % (name, )
 
5438
                        msg = _('The file %s has been modified by another process since reading it.  If you save, all the external changes could be lost.  Save anyways?') % (name, )
5344
5439
                if msg is not None:
5345
5440
                    dialog = MessageDialog(self.get_toplevel(), gtk.MESSAGE_QUESTION, msg)
5346
5441
                    end = (dialog.run() != gtk.RESPONSE_OK)
5378
5473
                if syntax is not None:
5379
5474
                    self.setSyntax(syntax)
5380
5475
            except UnicodeEncodeError:
5381
 
                dialog = MessageDialog(self.get_toplevel(), gtk.MESSAGE_ERROR, _('Error encoding to %s.') % (repr(encoding), ))
 
5476
                dialog = MessageDialog(self.get_toplevel(), gtk.MESSAGE_ERROR, _('Error encoding to %s.') % (encoding, ))
5382
5477
                dialog.run()
5383
5478
                dialog.destroy()
5384
5479
            except IOError:
5385
 
                dialog = MessageDialog(self.get_toplevel(), gtk.MESSAGE_ERROR, _('Error writing %s.') % (repr(name), ))
 
5480
                dialog = MessageDialog(self.get_toplevel(), gtk.MESSAGE_ERROR, _('Error writing %s.') % (name, ))
5386
5481
                dialog.run()
5387
5482
                dialog.destroy()
5388
5483
 
5426
5521
        # callback to receive notification when the name of a file changes
5427
5522
        def swapped_panes_cb(self, widget, f_dst, f_src):
5428
5523
            f0, f1 = self.headers[f_dst], self.headers[f_src]
5429
 
            f0.info, f1.info = f1.info, f0.info
5430
5524
            f0.has_edits, f1.has_edits = f1.has_edits, f0.has_edits
5431
 
            f0.updateTitle()
5432
 
            f1.updateTitle()
 
5525
            info0, info1 = f1.info, f0.info
 
5526
            self.setFileInfo(f_dst, info0)
 
5527
            self.setFileInfo(f_src, info1)
5433
5528
 
5434
5529
        # callback to receive notification when the name of a file changes
5435
5530
        def num_edits_changed_cb(self, widget, f):
5436
5531
            self.headers[f].setEdits(self.panes[f].num_edits > 0)
5437
5532
 
5438
 
        # callback to record the viewer's current status message
5439
 
        def status_changed_cb(self, widget, s):
 
5533
        # callback to record changes to the viewer's mode
 
5534
        def mode_changed_cb(self, widget):
 
5535
            self.updateStatus()
 
5536
 
 
5537
        # update the viewer's current status message
 
5538
        def updateStatus(self):
 
5539
            if self.mode == LINE_MODE:
 
5540
                s = _('Press the enter key or double click to edit.  Press the space bar or use the RMB menu to manually align.')
 
5541
            elif self.mode == CHAR_MODE:
 
5542
                s = _('Press the escape key to finish editing.')
 
5543
            elif self.mode == ALIGN_MODE:
 
5544
                s = _('Select target line and press the space bar to align.  Press the escape key to cancel.')
 
5545
            else:
 
5546
                s = None
5440
5547
            self.status = s
 
5548
            self.emit('status_changed', s)
5441
5549
 
5442
5550
        # gets the status bar text
5443
5551
        def getStatus(self):
5444
5552
            return self.status
5445
5553
 
 
5554
        # callback to display the cursor in a pane
 
5555
        def cursor_changed_cb(self, widget):
 
5556
            for f, footer in enumerate(self.footers):
 
5557
                footer.updateCursor(self, f)
 
5558
 
 
5559
        # callback to display the format of a pane
 
5560
        def format_changed_cb(self, widget, f, format):
 
5561
            self.footers[f].setFormat(format)
 
5562
 
5446
5563
    def __init__(self, rc_dir):
5447
5564
        gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
5448
5565
 
5513
5630
                     [_('_Quit'), self.quit_cb, None, gtk.STOCK_QUIT, 'quit'] ] ])
5514
5631
 
5515
5632
        menuspecs.append([ _('_Edit'), [
5516
 
                     [_('_Undo'), self.undo_cb, None, gtk.STOCK_UNDO, 'undo'],
5517
 
                     [_('_Redo'), self.redo_cb, None, gtk.STOCK_REDO, 'redo'],
5518
 
                     [],
5519
 
                     [_('Cu_t'), self.cut_cb, None, gtk.STOCK_CUT, 'cut'],
5520
 
                     [_('_Copy'), self.copy_cb, None, gtk.STOCK_COPY, 'copy'],
5521
 
                     [_('_Paste'), self.paste_cb, None, gtk.STOCK_PASTE, 'paste'],
5522
 
                     [],
5523
 
                     [_('Select _All'), self.select_all_cb, None, None, 'select_all'],
5524
 
                     [_('C_lear Edits'), self.clear_edits_cb, None, gtk.STOCK_CLEAR, 'clear_edits'],
 
5633
                     [_('_Undo'), self.button_cb, 'undo', gtk.STOCK_UNDO, 'undo'],
 
5634
                     [_('_Redo'), self.button_cb, 'redo', gtk.STOCK_REDO, 'redo'],
 
5635
                     [],
 
5636
                     [_('Cu_t'), self.button_cb, 'cut', gtk.STOCK_CUT, 'cut'],
 
5637
                     [_('_Copy'), self.button_cb, 'copy', gtk.STOCK_COPY, 'copy'],
 
5638
                     [_('_Paste'), self.button_cb, 'paste', gtk.STOCK_PASTE, 'paste'],
 
5639
                     [],
 
5640
                     [_('Select _All'), self.button_cb, 'select_all', None, 'select_all'],
 
5641
                     [_('C_lear Edits'), self.button_cb, 'clear_edits', gtk.STOCK_CLEAR, 'clear_edits'],
5525
5642
                     [],
5526
5643
                     [_('_Find...'), self.find_cb, None, gtk.STOCK_FIND, 'find'],
5527
5644
                     [_('Find _Next'), self.find_next_cb, None, None, 'find_next'],
5528
5645
                     [_('Find Pre_vious'), self.find_previous_cb, None, None, 'find_previous'],
5529
5646
                     [_('_Go To Line...'), self.go_to_line_cb, None, gtk.STOCK_JUMP_TO, 'go_to_line'],
5530
5647
                     [],
5531
 
                     [_('Decrea_se Indenting'), self.decrease_indenting_cb, None, gtk.STOCK_UNINDENT, 'decrease_indenting'],
5532
 
                     [_('_Increase Indenting'), self.increase_indenting_cb, None, gtk.STOCK_INDENT, 'increase_indenting'],
 
5648
                     [_('Decrea_se Indenting'), self.button_cb, 'decrease_indenting', gtk.STOCK_UNINDENT, 'decrease_indenting'],
 
5649
                     [_('_Increase Indenting'), self.button_cb, 'increase_indenting', gtk.STOCK_INDENT, 'increase_indenting'],
5533
5650
                     [],
5534
 
                     [_('Convert to _DOS Format'), self.convert_to_dos_cb, None, None, 'convert_to_dos'],
5535
 
                     [_('Convert to _Mac Format'), self.convert_to_mac_cb, None, None, 'convert_to_mac'],
5536
 
                     [_('Convert to Uni_x Format'), self.convert_to_unix_cb, None, None, 'convert_to_unix'],
 
5651
                     [_('Convert to _DOS Format'), self.button_cb, 'convert_to_dos', None, 'convert_to_dos'],
 
5652
                     [_('Convert to _Mac Format'), self.button_cb, 'convert_to_mac', None, 'convert_to_mac'],
 
5653
                     [_('Convert to Uni_x Format'), self.button_cb, 'convert_to_unix', None, 'convert_to_unix'],
5537
5654
                     [],
5538
5655
                     [_('Pr_eferences...'), self.preferences_cb, None, gtk.STOCK_PREFERENCES, 'preferences'] ] ])
5539
5656
 
5548
5665
        menuspecs.append([ _('_View'), [
5549
5666
                     [_('_Syntax Highlighting'), None, None, None, None, True, submenudef],
5550
5667
                     [],
5551
 
                     [_('Re_align All'), self.realign_all_cb, None, gtk.STOCK_EXECUTE, 'realign_all'],
5552
 
                     [_('_Isolate'), self.isolate_cb, None, None, 'isolate'],
 
5668
                     [_('Re_align All'), self.button_cb, 'realign_all', gtk.STOCK_EXECUTE, 'realign_all'],
 
5669
                     [_('_Isolate'), self.button_cb, 'isolate', None, 'isolate'],
5553
5670
                     [],
5554
 
                     [_('_First Difference'), self.first_difference_cb, None, gtk.STOCK_GOTO_TOP, 'first_difference'],
5555
 
                     [_('_Previous Difference'), self.previous_difference_cb, None, gtk.STOCK_GO_UP, 'previous_difference'],
5556
 
                     [_('_Next Difference'), self.next_difference_cb, None, gtk.STOCK_GO_DOWN, 'next_difference'],
5557
 
                     [_('_Last Difference'), self.last_difference_cb, None, gtk.STOCK_GOTO_BOTTOM, 'last_difference'],
 
5671
                     [_('_First Difference'), self.button_cb, 'first_difference', gtk.STOCK_GOTO_TOP, 'first_difference'],
 
5672
                     [_('_Previous Difference'), self.button_cb, 'previous_difference', gtk.STOCK_GO_UP, 'previous_difference'],
 
5673
                     [_('_Next Difference'), self.button_cb, 'next_difference', gtk.STOCK_GO_DOWN, 'next_difference'],
 
5674
                     [_('_Last Difference'), self.button_cb, 'last_difference', gtk.STOCK_GOTO_BOTTOM, 'last_difference'],
5558
5675
                     [],
5559
5676
                     [_('Pre_vious Tab'), self.previous_tab_cb, None, None, 'previous_tab'],
5560
5677
                     [_('Next _Tab'), self.next_tab_cb, None, None, 'next_tab'],
5561
5678
                     [],
5562
 
                     [_('Shift Pane _Left'), self.shift_pane_left_cb, None, None, 'shift_pane_left'],
5563
 
                     [_('Shift Pane _Right'), self.shift_pane_right_cb, None, None, 'shift_pane_right'] ] ])
 
5679
                     [_('Shift Pane _Left'), self.button_cb, 'shift_pane_left', None, 'shift_pane_left'],
 
5680
                     [_('Shift Pane _Right'), self.button_cb, 'shift_pane_right', None, 'shift_pane_right'] ] ])
5564
5681
 
5565
5682
        menuspecs.append([ _('_Merge'), [
5566
 
                     [_('Merge From _Left'), self.merge_from_left_cb, None, gtk.STOCK_GO_FORWARD, 'merge_from_left'],
5567
 
                     [_('Merge From _Right'), self.merge_from_right_cb, None, gtk.STOCK_GO_BACK, 'merge_from_right'],
5568
 
                     [_('Merge From Left _Then Right'), self.merge_from_left_then_right_cb, None, DIFFUSE_STOCK_LEFT_RIGHT, 'merge_from_left_then_right'],
5569
 
                     [_('Merge From Right T_hen Left'), self.merge_from_right_then_left_cb, None, DIFFUSE_STOCK_RIGHT_LEFT, 'merge_from_right_then_left'] ] ])
 
5683
                     [_('Merge From _Left'), self.button_cb, 'merge_from_left', gtk.STOCK_GO_FORWARD, 'merge_from_left'],
 
5684
                     [_('Merge From _Right'), self.button_cb, 'merge_from_right', gtk.STOCK_GO_BACK, 'merge_from_right'],
 
5685
                     [_('Merge From Left _Then Right'), self.button_cb, 'merge_from_left_then_right', DIFFUSE_STOCK_LEFT_RIGHT, 'merge_from_left_then_right'],
 
5686
                     [_('Merge From Right T_hen Left'), self.button_cb, 'merge_from_right_then_left', DIFFUSE_STOCK_RIGHT_LEFT, 'merge_from_right_then_left'] ] ])
5570
5687
 
5571
5688
        menuspecs.append([ _('_Help'), [
5572
5689
                     [_('_Help Contents...'), self.help_contents_cb, None, gtk.STOCK_HELP, 'help_contents'],
5580
5697
        # create button bar
5581
5698
        hbox = gtk.HBox()
5582
5699
        appendButtons(hbox, gtk.ICON_SIZE_LARGE_TOOLBAR, [
5583
 
           [ gtk.STOCK_EXECUTE, self.realign_all_cb, None, _('Realign All') ],
5584
 
           [],
5585
 
           [ gtk.STOCK_GOTO_TOP, self.first_difference_cb, None, _('First Difference') ],
5586
 
           [ gtk.STOCK_GO_UP, self.previous_difference_cb, None, _('Previous Difference') ],
5587
 
           [ gtk.STOCK_GO_DOWN, self.next_difference_cb, None, _('Next Difference') ],
5588
 
           [ gtk.STOCK_GOTO_BOTTOM, self.last_difference_cb, None, _('Last Difference') ],
5589
 
           [],
5590
 
           [ gtk.STOCK_GO_FORWARD, self.merge_from_left_cb, None, _('Merge From Left') ],
5591
 
           [ gtk.STOCK_GO_BACK, self.merge_from_right_cb, None, _('Merge From Right') ],
5592
 
           [ DIFFUSE_STOCK_LEFT_RIGHT, self.merge_from_left_then_right_cb, None, _('Merge From Left Then Right') ],
5593
 
           [ DIFFUSE_STOCK_RIGHT_LEFT, self.merge_from_right_then_left_cb, None, _('Merge From Right Then Left') ],
5594
 
           [],
5595
 
           [ gtk.STOCK_CUT, self.cut_cb, None, _('Cut') ],
5596
 
           [ gtk.STOCK_COPY, self.copy_cb, None, _('Copy') ],
5597
 
           [ gtk.STOCK_PASTE, self.paste_cb, None, _('Paste') ],
5598
 
           [ gtk.STOCK_CLEAR, self.clear_edits_cb, None, _('Clear Edits') ] ])
 
5700
           [ gtk.STOCK_EXECUTE, self.button_cb, 'realign_all', _('Realign All') ],
 
5701
           [],
 
5702
           [ gtk.STOCK_GOTO_TOP, self.button_cb, 'first_difference', _('First Difference') ],
 
5703
           [ gtk.STOCK_GO_UP, self.button_cb, 'previous_difference', _('Previous Difference') ],
 
5704
           [ gtk.STOCK_GO_DOWN, self.button_cb, 'next_difference', _('Next Difference') ],
 
5705
           [ gtk.STOCK_GOTO_BOTTOM, self.button_cb, 'last_difference', _('Last Difference') ],
 
5706
           [],
 
5707
           [ gtk.STOCK_GO_FORWARD, self.button_cb, 'merge_from_left', _('Merge From Left') ],
 
5708
           [ gtk.STOCK_GO_BACK, self.button_cb, 'merge_from_right', _('Merge From Right') ],
 
5709
           [ DIFFUSE_STOCK_LEFT_RIGHT, self.button_cb, 'merge_from_left_then_right', _('Merge From Left Then Right') ],
 
5710
           [ DIFFUSE_STOCK_RIGHT_LEFT, self.button_cb, 'merge_from_right_then_left', _('Merge From Right Then Left') ],
 
5711
           [],
 
5712
           [ gtk.STOCK_UNDO, self.button_cb, 'undo', _('Undo') ],
 
5713
           [ gtk.STOCK_REDO, self.button_cb, 'redo', _('Redo') ],
 
5714
           [ gtk.STOCK_CUT, self.button_cb, 'cut', _('Cut') ],
 
5715
           [ gtk.STOCK_COPY, self.button_cb, 'copy', _('Copy') ],
 
5716
           [ gtk.STOCK_PASTE, self.button_cb, 'paste', _('Paste') ],
 
5717
           [ gtk.STOCK_CLEAR, self.button_cb, 'clear_edits', _('Clear Edits') ] ])
5599
5718
        vbox.pack_start(hbox, False, False, 0)
5600
5719
        hbox.show()
5601
5720
 
5606
5725
        notebook.show()
5607
5726
 
5608
5727
        # Add a status bar to the botton
5609
 
        self.statusbar = statusbar = gtk.Statusbar()
5610
 
        self.status_context = statusbar.get_context_id('Message')
 
5728
        self.statusbar = statusbar = Statusbar()
5611
5729
        vbox.pack_start(statusbar, False, False, 0)
5612
5730
        statusbar.show()
5613
5731
 
5653
5771
                            else:
5654
5772
                                raise ValueError()
5655
5773
                    except ValueError:
5656
 
                        logError(_('Error parsing line %(line)d of "%(file)s".') % { 'line': j + 1, 'file': configpath })
 
5774
                        logError(_('Error parsing line %(line)d of %(file)s.') % { 'line': j + 1, 'file': configpath })
5657
5775
            except IOError:
5658
 
                logError(_('Error reading "%s".') % (configpath, ))
 
5776
                logError(_('Error reading %s.') % (configpath, ))
5659
5777
 
5660
5778
        self.move(self.int_state['window_x'], self.int_state['window_y'])
5661
5779
        self.resize(self.int_state['window_width'], self.int_state['window_height'])
5677
5795
                f.write(s)
5678
5796
            f.close()
5679
5797
        except IOError:
5680
 
            logError(_('Error writing "%s".') % (configpath, ))
 
5798
            logError(_('Error writing %s.') % (configpath, ))
5681
5799
 
5682
5800
    # callback for the close button on each tab
5683
5801
    def remove_tab_cb(self, widget, data):
5695
5813
        self.set_title('%s - %s' % (title, APP_NAME))
5696
5814
 
5697
5815
    # update the message in the status bar
5698
 
    def setStatus(self, status):
5699
 
        self.statusbar.pop(self.status_context)
5700
 
        self.statusbar.push(self.status_context, status)
 
5816
    def setStatus(self, s):
 
5817
        self.statusbar.setText(s)
 
5818
 
 
5819
    # update the label in the status bar
 
5820
    def setSyntax(self, s):
 
5821
        self.statusbar.setSyntax(s)
5701
5822
 
5702
5823
    # callback used when switching notebook pages
5703
5824
    def switch_page_cb(self, widget, ptr, page_num):
5704
5825
        viewer = widget.get_nth_page(page_num)
5705
5826
        self.updateTitle(viewer)
5706
5827
        self.setStatus(viewer.getStatus())
 
5828
        self.setSyntax(viewer.getSyntax())
5707
5829
 
5708
5830
    # callback used when a viewer's title changes
5709
5831
    def title_changed_cb(self, widget, title):
5713
5835
            self.updateTitle(widget)
5714
5836
 
5715
5837
    # callback used when a viewer's status changes
5716
 
    def status_changed_cb(self, widget, status):
 
5838
    def status_changed_cb(self, widget, s):
5717
5839
        # update the label in the notebook's tab
5718
5840
        if widget is self.getCurrentViewer():
5719
 
            self.setStatus(status)
 
5841
            self.setStatus(s)
 
5842
 
 
5843
    # callback used when a viewer's syntax changes
 
5844
    def syntax_changed_cb(self, widget, s):
 
5845
        # update the label
 
5846
        if widget is self.getCurrentViewer():
 
5847
            self.setSyntax(s)
5720
5848
 
5721
5849
    # create an empty viewer with 'n' panes
5722
5850
    def newFileDiffViewer(self, n):
5734
5862
        self.notebook.set_show_tabs(self.prefs.getBool('tabs_always_show') or self.notebook.get_n_pages() > 1)
5735
5863
        viewer.connect('title_changed', self.title_changed_cb)
5736
5864
        viewer.connect('status_changed', self.status_changed_cb)
 
5865
        viewer.connect('syntax_changed', self.syntax_changed_cb)
5737
5866
        return viewer
5738
5867
 
5739
5868
    # create a new viewer to display 'items'
5809
5938
                    for template in vcs.getFolderTemplate(self.prefs, names):
5810
5939
                        self.newLoadedFileDiffViewer([ (name, [ (rev, encoding) ]) for name, rev in template ])
5811
5940
                except (IOError, OSError, WindowsError):
5812
 
                    dialog = MessageDialog(self.get_toplevel(), gtk.MESSAGE_ERROR, _('Error retrieving modifications for "%s".') % (dn, ))
 
5941
                    dialog = MessageDialog(self.get_toplevel(), gtk.MESSAGE_ERROR, _('Error retrieving modifications for %s.') % (dn, ))
5813
5942
                    dialog.run()
5814
5943
                    dialog.destroy()
5815
5944
 
5910
6039
        if self.confirmQuit():
5911
6040
            gtk.main_quit()
5912
6041
 
5913
 
    # callback for the undo menu item
5914
 
    def undo_cb(self, widget, data):
5915
 
        self.getCurrentViewer().undo()
5916
 
 
5917
 
    # callback for the redo menu item
5918
 
    def redo_cb(self, widget, data):
5919
 
        self.getCurrentViewer().redo()
5920
 
 
5921
 
    # callback for the cut menu item
5922
 
    def cut_cb(self, widget, data):
5923
 
        self.getCurrentViewer().cut_cb(widget, data)
5924
 
 
5925
 
    # callback for the copy menu item
5926
 
    def copy_cb(self, widget, data):
5927
 
        self.getCurrentViewer().copy_cb(widget, data)
5928
 
 
5929
 
    # callback for the paste menu item
5930
 
    def paste_cb(self, widget, data):
5931
 
        self.getCurrentViewer().paste_cb(widget, data)
5932
 
 
5933
 
    # callback for the select all menu item
5934
 
    def select_all_cb(self, widget, data):
5935
 
        self.getCurrentViewer().select_all_cb(widget, data)
5936
 
 
5937
 
    # callback for the clear edits menu item
5938
 
    def clear_edits_cb(self, widget, data):
5939
 
        self.getCurrentViewer().clear_edits_cb(widget, data)
5940
 
 
5941
6042
    # request search parameters if force=True and then perform a search in the
5942
6043
    # current viewer pane
5943
6044
    def find(self, force, reverse):
5997
6098
    def go_to_line_cb(self, widget, data):
5998
6099
        self.getCurrentViewer().go_to_line_cb(widget, data)
5999
6100
 
6000
 
    # callback for the decrease indenting menu item
6001
 
    def decrease_indenting_cb(self, widget, data):
6002
 
        self.getCurrentViewer().decrease_indenting_cb(widget, data)
6003
 
 
6004
 
    # callback for the increase indenting menu item
6005
 
    def increase_indenting_cb(self, widget, data):
6006
 
        self.getCurrentViewer().increase_indenting_cb(widget, data)
6007
 
 
6008
 
    # callback for the convert to DOS menu item
6009
 
    def convert_to_dos_cb(self, widget, data):
6010
 
        self.getCurrentViewer().convert_to_dos_cb(widget, data)
6011
 
 
6012
 
    # callback for the convert to Unix menu item
6013
 
    def convert_to_mac_cb(self, widget, data):
6014
 
        self.getCurrentViewer().convert_to_mac_cb(widget, data)
6015
 
 
6016
 
    # callback for the convert to Unix menu item
6017
 
    def convert_to_unix_cb(self, widget, data):
6018
 
        self.getCurrentViewer().convert_to_unix_cb(widget, data)
6019
 
 
6020
6101
    # notify all viewers of changes to the preferences
6021
6102
    def preferences_updated(self):
6022
6103
        n = self.notebook.get_n_pages()
6031
6112
 
6032
6113
    # callback for all of the syntax highlighting menu items
6033
6114
    def syntax_cb(self, widget, data):
6034
 
        self.getCurrentViewer().setSyntax(theResources.getSyntax(data))
6035
 
 
6036
 
    # callback for the realign all menu item
6037
 
    def realign_all_cb(self, widget, data):
6038
 
        self.getCurrentViewer().realign_all_cb(widget, data)
6039
 
 
6040
 
    # callback for the isolate menu item
6041
 
    def isolate_cb(self, widget, data):
6042
 
        self.getCurrentViewer().isolate_cb(widget, data)
6043
 
 
6044
 
    # callback for the first difference menu item
6045
 
    def first_difference_cb(self, widget, data):
6046
 
        self.getCurrentViewer().first_difference_cb(widget, data)
6047
 
 
6048
 
    # callback for the previous difference menu item
6049
 
    def previous_difference_cb(self, widget, data):
6050
 
        self.getCurrentViewer().previous_difference_cb(widget, data)
6051
 
 
6052
 
    # callback for the next difference menu item
6053
 
    def next_difference_cb(self, widget, data):
6054
 
        self.getCurrentViewer().next_difference_cb(widget, data)
6055
 
 
6056
 
    # callback for the last difference menu item
6057
 
    def last_difference_cb(self, widget, data):
6058
 
        self.getCurrentViewer().last_difference_cb(widget, data)
 
6115
        self.getCurrentViewer().setSyntax(data)
6059
6116
 
6060
6117
    # callback for the previous tab menu item
6061
6118
    def previous_tab_cb(self, widget, data):
6070
6127
        if i < n:
6071
6128
            self.notebook.set_current_page(i)
6072
6129
 
6073
 
    # callback for the shift pane left menu item
6074
 
    def shift_pane_left_cb(self, widget, data):
6075
 
        self.getCurrentViewer().shift_pane_left_cb(widget, data)
6076
 
 
6077
 
    # callback for the shift pane right menu item
6078
 
    def shift_pane_right_cb(self, widget, data):
6079
 
        self.getCurrentViewer().shift_pane_right_cb(widget, data)
6080
 
 
6081
 
    # callback for the merge from left menu item
6082
 
    def merge_from_left_cb(self, widget, data):
6083
 
        self.getCurrentViewer().merge_from_left_cb(widget, data)
6084
 
 
6085
 
    # callback for the merge from right menu item
6086
 
    def merge_from_right_cb(self, widget, data):
6087
 
        self.getCurrentViewer().merge_from_right_cb(widget, data)
6088
 
 
6089
 
    # callback for the merge from left then right menu item
6090
 
    def merge_from_left_then_right_cb(self, widget, data):
6091
 
        self.getCurrentViewer().merge_from_left_then_right_cb(widget, data)
6092
 
 
6093
 
    # callback for the merge from right then left menu item
6094
 
    def merge_from_right_then_left_cb(self, widget, data):
6095
 
        self.getCurrentViewer().merge_from_right_then_left_cb(widget, data)
 
6130
    # callback for most menu items and buttons
 
6131
    def button_cb(self, widget, data):
 
6132
        self.getCurrentViewer().button_cb(widget, data)
6096
6133
 
6097
6134
    # display help documenation
6098
6135
    def help_contents_cb(self, widget, data):
6152
6189
        dialog.destroy()
6153
6190
 
6154
6191
gobject.signal_new('title_changed', Diffuse.FileDiffViewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, ))
 
6192
gobject.signal_new('status_changed', Diffuse.FileDiffViewer, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str, ))
6155
6193
gobject.signal_new('title_changed', Diffuse.FileDiffViewer.PaneHeader, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
6156
6194
gobject.signal_new('open', Diffuse.FileDiffViewer.PaneHeader, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
6157
6195
gobject.signal_new('reload', Diffuse.FileDiffViewer.PaneHeader, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ())
6221
6259
                    r[-1] = str(int(r[-1]) - 1)
6222
6260
                    prev = '.'.join(r)
6223
6261
                except:
6224
 
                    logError(_('Error parsing revision "%s".') % (rev, ))
 
6262
                    logError(_('Error parsing revision %s.') % (rev, ))
6225
6263
                revs.append((prev, encoding))
6226
6264
                revs.append((rev, encoding))
6227
6265
            elif arg in [ '-D', '--close-if-same' ]:
6277
6315
                if len(specs) > 0:
6278
6316
                    filename = os.path.join(filename, os.path.basename(specs[-1][0]))
6279
6317
                else:
6280
 
                    logError(_('Unexpected directory "%s".') % (args[i], ))
 
6318
                    logError(_('Unexpected directory %s.') % (args[i], ))
6281
6319
                    filename = None
6282
6320
            if filename is not None:
6283
6321
                if len(revs) == 0: