~ubuntu-branches/debian/sid/tortoisehg/sid

« back to all changes in this revision

Viewing changes to tortoisehg/hgtk/thgconfig.py

  • Committer: Bazaar Package Importer
  • Author(s): Ludovico Cavedon
  • Date: 2011-03-18 19:57:13 UTC
  • mfrom: (1.2.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20110318195713-nh62nv9y26rrdtmh
Tags: 2.0.2-1
* New upstream release.
* Update copyright file.
* Depend on PyQt4 libraries rather than on PyGtk.
* Rename manpage from hgtk to thg.
* No longer include _hgtk zsh completion file.
* Install wrapper script hgtk and add manpage link for it (LP: #732328).
* Update manpage with new options and commands.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# thgconfig.py - Configuration dialog for TortoiseHg and Mercurial
2
 
#
3
 
# Copyright 2007 Steve Borho <steve@borho.org>
4
 
#
5
 
# This software may be used and distributed according to the terms of the
6
 
# GNU General Public License version 2, incorporated herein by reference.
7
 
 
8
 
import gtk
9
 
import os
10
 
import sys
11
 
import re
12
 
import urlparse
13
 
 
14
 
from mercurial import hg, ui, util, url, filemerge, error, extensions
15
 
 
16
 
from tortoisehg.util.i18n import _
17
 
from tortoisehg.util import hglib, settings, paths, i18n
18
 
 
19
 
from tortoisehg.hgtk import dialog, gdialog, gtklib, hgcmd
20
 
 
21
 
try:
22
 
    from tortoisehg.hgtk import bugtraq
23
 
    bugtraq.get_issue_plugins_with_names()
24
 
except ImportError:
25
 
    bugtraq = None
26
 
 
27
 
try:
28
 
    from iniparse.config import Undefined
29
 
except ImportError:
30
 
    class Undefined(object):
31
 
        pass
32
 
 
33
 
_unspecstr = _('<unspecified>')
34
 
_unspeclocalstr = hglib.fromutf(_unspecstr)
35
 
 
36
 
_pwfields = ('http_proxy.passwd', 'smtp.password')
37
 
 
38
 
# options available only on global settings
39
 
_norepofields = ('tortoisehg.ui.language',)
40
 
 
41
 
INFO = (
42
 
({'name': 'general', 'label': 'TortoiseHg', 'icon': 'thg_logo.ico'}, (
43
 
    (_('UI Language'), 'tortoisehg.ui.language', i18n.availablelanguages(),
44
 
        _('Specify your preferred user interface language (restart needed)')),
45
 
    (_('Three-way Merge Tool'), 'ui.merge', [],
46
 
        _('Graphical merge program for resolving merge conflicts.  If left'
47
 
        ' unspecified, Mercurial will use the first applicable tool it finds'
48
 
        ' on your system or use its internal merge tool that leaves conflict'
49
 
        ' markers in place.  Chose internal:merge to force conflict markers,'
50
 
        ' internal:prompt to always select local or other, or internal:dump'
51
 
        ' to leave files in the working directory for manual merging')),
52
 
    (_('Visual Diff Tool'), 'tortoisehg.vdiff', [],
53
 
        _('Specify visual diff tool, as described in the [merge-tools]'
54
 
          ' section of your Mercurial configuration files.  If left'
55
 
          ' unspecified, TortoiseHg will use the selected merge tool.'
56
 
          ' Failing that it uses the first applicable tool it finds.')),
57
 
    (_('Visual Editor'), 'tortoisehg.editor', [],
58
 
        _('Specify the visual editor used to view files, etc')),
59
 
    (_('CLI Editor'), 'ui.editor', [],
60
 
        _('The editor to use during a commit and other instances where'
61
 
        ' Mercurial needs multiline input from the user.  Used by'
62
 
        ' command line commands, including patch import.')),
63
 
    (_('Tab Width'), 'tortoisehg.tabwidth', [],
64
 
        _('Specify the number of spaces that tabs expand to in various'
65
 
        ' TortoiseHg windows.'
66
 
        ' Default: Not expanded')),
67
 
    (_('Max Diff Size'), 'tortoisehg.maxdiff', ['1024', '0'],
68
 
        _('The maximum size file (in KB) that TortoiseHg will '
69
 
        'show changes for in the changelog, status, and commit windows.'
70
 
        ' A value of zero implies no limit.  Default: 1024 (1MB)')),
71
 
    (_('Bottom Diffs'), 'gtools.diffbottom', ['False', 'True'],
72
 
        _('Show the diff panel below the file list in status, shelve, and'
73
 
        ' commit dialogs.'
74
 
        ' Default: False (show diffs to right of file list)')),
75
 
    (_('Capture stderr'), 'tortoisehg.stderrcapt', ['True', 'False'],
76
 
        _('Redirect stderr to a buffer which is parsed at the end of'
77
 
        ' the process for runtime errors. Default: True')),
78
 
    (_('Fork hgtk'), 'tortoisehg.hgtkfork', ['True', 'False'],
79
 
        _('When running hgtk from the command line, fork a background'
80
 
        ' process to run graphical dialogs.  Default: True')),
81
 
    (_('Full Path Title'), 'tortoisehg.fullpath', ['False', 'True'],
82
 
        _('Show a full directory path of the repository in the dialog title'
83
 
        ' instead of just the root directory name.  Default: False')),
84
 
    ) + (gtklib.hasspellcheck() and
85
 
    ((_('Spell Check Language'), 'tortoisehg.spellcheck', [],
86
 
        _('Default language for spell check. System language is'
87
 
        ' used if not specified. Examples: en, en_GB, en_US')),) or ())),
88
 
 
89
 
({'name': 'commit', 'label': _('Commit'), 'icon': 'menucommit.ico'}, (
90
 
    (_('Username'), 'ui.username', [],
91
 
        _('Name associated with commits')),
92
 
    (_('Summary Line Length'), 'tortoisehg.summarylen', ['0', '70'],
93
 
       _('Maximum length of the commit message summary line.'
94
 
         ' If set, TortoiseHg will issue a warning if the'
95
 
         ' summary line is too long or not separated by a'
96
 
         ' blank line. Default: 0 (unenforced)')),
97
 
    (_('Message Line Length'), 'tortoisehg.messagewrap', ['0', '80'],
98
 
       _('Word wrap length of the commit message.  If'
99
 
         ' set, the popup menu can be used to format'
100
 
         ' the message and a warning will be issued'
101
 
         ' if any lines are too long at commit.'
102
 
         '  Default: 0 (unenforced)')),
103
 
    (_('Close After Commit'), 'tortoisehg.closeci', ['False', 'True'],
104
 
        _('Close the commit tool after every successful'
105
 
          ' commit.  Default: False')),
106
 
    (_('Push After Commit'), 'tortoisehg.pushafterci', ['False', 'True'],
107
 
        _('Attempt to push to default push target after every successful'
108
 
          ' commit.  Default: False')),
109
 
    (_('Auto Commit List'), 'tortoisehg.autoinc', [],
110
 
       _('Comma separated list of files that are automatically included'
111
 
         ' in every commit.  Intended for use only as a repository setting.'
112
 
         '  Default: None (leave blank)')),
113
 
    (_('Auto Exclude List'), 'tortoisehg.ciexclude', [],
114
 
       _('Comma separated list of files that are automatically unchecked'
115
 
         ' when the status, commit, and shelve dialogs are opened.'
116
 
         '  Default: None (leave blank)')),
117
 
    (_('English Messages'), 'tortoisehg.engmsg', ['False', 'True'],
118
 
       _('Generate English commit messages even if LANGUAGE or LANG'
119
 
         ' environment variables are set to a non-English language.'
120
 
         ' This setting is used by the Merge, Tag and Backout dialogs.'
121
 
         '  Default: False')),
122
 
    (_('Default Tab'), 'tortoisehg.statustab', ['0', '1', '2'],
123
 
        _('The tab on which the status and commit tools will open.'
124
 
          ' 0 - TextDiff, 1 - Hunk Selection, 2 - Commit Preview.'
125
 
          '  Default: 0')),
126
 
    )),
127
 
 
128
 
({'name': 'log', 'label': _('Repository Explorer'),
129
 
  'icon': 'menulog.ico'}, (
130
 
    (_('Author Coloring'), 'tortoisehg.authorcolor', ['False', 'True'],
131
 
        _('Color changesets by author name.  If not enabled,'
132
 
        ' the changes are colored green for merge, red for'
133
 
        ' non-trivial parents, black for normal.'
134
 
        ' Default: False')),
135
 
    (_('Long Summary'), 'tortoisehg.longsummary', ['False', 'True'],
136
 
        _('If true, concatenate multiple lines of changeset summary'
137
 
        ' until they reach 80 characters.'
138
 
        ' Default: False')),
139
 
    (_('Log Batch Size'), 'tortoisehg.graphlimit', ['500'],
140
 
        _('The number of revisions to read and display in the'
141
 
        ' changelog viewer in a single batch.'
142
 
        ' Default: 500')),
143
 
    (_('Dead Branches'), 'tortoisehg.deadbranch', [],
144
 
        _('Comma separated list of branch names that should be ignored'
145
 
        ' when building a list of branch names for a repository.'
146
 
        ' Default: None (leave blank)')),
147
 
    (_('Branch Colors'), 'tortoisehg.branchcolors', [],
148
 
        _('Space separated list of branch names and colors of the form'
149
 
        ' branch:#XXXXXX. Spaces and colons in the branch name must be'
150
 
        ' escaped using a backslash (\\). Likewise some other characters'
151
 
        ' can be escaped in this way, e.g. \\u0040 will be decoded to the'
152
 
        ' @ character, and \\n to a linefeed.'
153
 
        ' Default: None (leave blank)')),
154
 
    (_('Hide Tags'), 'tortoisehg.hidetags', [],
155
 
        _('Space separated list of tags that will not be shown.'
156
 
        ' Useful example: Specify "qbase qparent qtip" to hide the'
157
 
        ' standard tags inserted by the Mercurial Queues Extension.' 
158
 
        ' Default: None (leave blank)')),
159
 
    (_('Use Expander'), 'tortoisehg.changeset-expander', ['False', 'True'],
160
 
        _('Show changeset details with an expander')),
161
 
    (_('Toolbar Style'), 'tortoisehg.logtbarstyle',
162
 
        ['small', 'large', 'theme'],
163
 
        _('Adjust the display of the main toolbar in the Repository'
164
 
        ' Explorer.  Values: small, large, or theme.  Default: theme')),
165
 
#    (_('F/S Encodings'), 'tortoisehg.fsencodings', [],
166
 
#        _('Comma separated list of encodings used for filenames'
167
 
#          ' on this computer. Default: none')),
168
 
    )),
169
 
 
170
 
({'name': 'sync', 'label': _('Synchronize'), 'icon': 'menusynch.ico',
171
 
  'extra': True}, (
172
 
    (_('After Pull Operation'), 'tortoisehg.postpull',
173
 
        ['none', 'update', 'fetch', 'rebase'],
174
 
        _('Operation which is performed directly after a successful pull.'
175
 
        ' update equates to pull --update, fetch equates to the fetch'
176
 
        ' extension, rebase equates to pull --rebase.  Default: none')),
177
 
    )),
178
 
 
179
 
({'name': 'web', 'label': _('Web Server'), 'icon': 'proxy.ico'}, (
180
 
    (_('Name'), 'web.name', ['unknown'],
181
 
        _('Repository name to use in the web interface.'
182
 
        ' Default is the working directory.')),
183
 
    (_('Description'), 'web.description', ['unknown'],
184
 
        _("Textual description of the repository's purpose or"
185
 
        ' contents.')),
186
 
    (_('Contact'), 'web.contact', ['unknown'],
187
 
        _('Name or email address of the person in charge of the'
188
 
        ' repository.')),
189
 
    (_('Style'), 'web.style',
190
 
        ['paper', 'monoblue', 'coal', 'spartan', 'gitweb', 'old'],
191
 
        _('Which template map style to use')),
192
 
    (_('Archive Formats'), 'web.allow_archive', ['bz2', 'gz', 'zip'],
193
 
        _('Comma separated list of archive formats allowed for'
194
 
        ' downloading')),
195
 
    (_('Port'), 'web.port', ['8000'], _('Port to listen on')),
196
 
    (_('Push Requires SSL'), 'web.push_ssl', ['True', 'False'],
197
 
        _('Whether to require that inbound pushes be transported'
198
 
        ' over SSL to prevent password sniffing.')),
199
 
    (_('Stripes'), 'web.stripes', ['1', '0'],
200
 
        _('How many lines a "zebra stripe" should span in multiline output.'
201
 
        ' Default is 1; set to 0 to disable.')),
202
 
    (_('Max Files'), 'web.maxfiles', ['10'],
203
 
        _('Maximum number of files to list per changeset.')),
204
 
    (_('Max Changes'), 'web.maxchanges', ['10'],
205
 
        _('Maximum number of changes to list on the changelog.')),
206
 
    (_('Allow Push'), 'web.allow_push', ['*'],
207
 
        _('Whether to allow pushing to the repository. If empty or not'
208
 
        ' set, push is not allowed. If the special value "*", any remote'
209
 
        ' user can push, including unauthenticated users. Otherwise, the'
210
 
        ' remote user must have been authenticated, and the authenticated'
211
 
        ' user name must be present in this list (separated by whitespace'
212
 
        ' or ","). The contents of the allow_push list are examined after'
213
 
        ' the deny_push list.')),
214
 
    (_('Deny Push'), 'web.deny_push', ['*'],
215
 
        _('Whether to deny pushing to the repository. If empty or not set,'
216
 
        ' push is not denied. If the special value "*", all remote users'
217
 
        ' are denied push. Otherwise, unauthenticated users are all'
218
 
        ' denied, and any authenticated user name present in this list'
219
 
        ' (separated by whitespace or ",") is also denied. The contents'
220
 
        ' of the deny_push list are examined before the allow_push list.')),
221
 
    (_('Encoding'), 'web.encoding', ['UTF-8'],
222
 
        _('Character encoding name')),
223
 
    )),
224
 
 
225
 
({'name': 'proxy', 'label': _('Proxy'), 'icon': 'general.ico'}, (
226
 
    (_('Host'), 'http_proxy.host', [],
227
 
        _('Host name and (optional) port of proxy server, for'
228
 
        ' example "myproxy:8000"')),
229
 
    (_('Bypass List'), 'http_proxy.no', [],
230
 
        _('Optional. Comma-separated list of host names that'
231
 
        ' should bypass the proxy')),
232
 
    (_('User'), 'http_proxy.user', [],
233
 
        _('Optional. User name to authenticate with at the proxy server')),
234
 
    (_('Password'), 'http_proxy.passwd', [],
235
 
        _('Optional. Password to authenticate with at the proxy server')),
236
 
    )),
237
 
 
238
 
({'name': 'email', 'label': _('Email'), 'icon': gtk.STOCK_GOTO_LAST}, (
239
 
    (_('From'), 'email.from', [],
240
 
        _('Email address to use in the "From" header and for'
241
 
        ' the SMTP envelope')),
242
 
    (_('To'), 'email.to', [],
243
 
        _('Comma-separated list of recipient email addresses')),
244
 
    (_('Cc'), 'email.cc', [],
245
 
        _('Comma-separated list of carbon copy recipient email addresses')),
246
 
    (_('Bcc'), 'email.bcc', [],
247
 
        _('Comma-separated list of blind carbon copy recipient'
248
 
        ' email addresses')),
249
 
    (_('method'), 'email.method', ['smtp'],
250
 
        _('Optional. Method to use to send email messages. If value is'
251
 
        ' "smtp" (default), use SMTP (configured below).  Otherwise, use as'
252
 
        ' name of program to run that acts like sendmail (takes "-f" option'
253
 
        ' for sender, list of recipients on command line, message on stdin).'
254
 
        ' Normally, setting this to "sendmail" or "/usr/sbin/sendmail"'
255
 
        ' is enough to use sendmail to send messages.')),
256
 
    (_('SMTP Host'), 'smtp.host', [], _('Host name of mail server')),
257
 
    (_('SMTP Port'), 'smtp.port', ['25'],
258
 
        _('Port to connect to on mail server.'
259
 
        ' Default: 25')),
260
 
    (_('SMTP TLS'), 'smtp.tls', ['False', 'True'],
261
 
        _('Connect to mail server using TLS.'
262
 
        ' Default: False')),
263
 
    (_('SMTP Username'), 'smtp.username', [],
264
 
        _('Username to authenticate to mail server with')),
265
 
    (_('SMTP Password'), 'smtp.password', [],
266
 
        _('Password to authenticate to mail server with')),
267
 
    (_('Local Hostname'), 'smtp.local_hostname', [],
268
 
        _('Hostname the sender can use to identify itself to the'
269
 
        ' mail server.')),
270
 
    )),
271
 
 
272
 
({'name': 'diff', 'label': _('Diff'), 'icon': gtk.STOCK_JUSTIFY_FILL}, (
273
 
    (_('Patch EOL'), 'patch.eol', ['auto', 'strict', 'crlf', 'lf'],
274
 
        _('Normalize file line endings during and after patch to lf or'
275
 
        ' crlf.  Strict does no normalization.  Auto does per-file'
276
 
        ' detection, and is the recommended setting.'
277
 
        ' Default: strict')),
278
 
    (_('Git Format'), 'diff.git', ['False', 'True'],
279
 
        _('Use git extended diff header format.'
280
 
        ' Default: False')),
281
 
    (_('No Dates'), 'diff.nodates', ['False', 'True'],
282
 
        _('Do not include modification dates in diff headers.'
283
 
        ' Default: False')),
284
 
    (_('Show Function'), 'diff.showfunc', ['False', 'True'],
285
 
        _('Show which function each change is in.'
286
 
        ' Default: False')),
287
 
    (_('Ignore White Space'), 'diff.ignorews', ['False', 'True'],
288
 
        _('Ignore white space when comparing lines.'
289
 
        ' Default: False')),
290
 
    (_('Ignore WS Amount'), 'diff.ignorewsamount', ['False', 'True'],
291
 
        _('Ignore changes in the amount of white space.'
292
 
        ' Default: False')),
293
 
    (_('Ignore Blank Lines'), 'diff.ignoreblanklines', ['False', 'True'],
294
 
        _('Ignore changes whose lines are all blank.'
295
 
        ' Default: False')),
296
 
    (_('Coloring Style'), 'tortoisehg.diffcolorstyle',
297
 
        ['none', 'foreground', 'background'],
298
 
        _('Adjust the coloring style of diff lines in the changeset'
299
 
        ' viewer. Default: foreground')),
300
 
    )),
301
 
 
302
 
({'name': 'font', 'label': _('Font'), 'icon': gtk.STOCK_SELECT_FONT,
303
 
  'extra': True, 'width': 16}, (
304
 
    (_('Commit Message'), 'gtools.fontcomment', [],
305
 
        _('Font used in changeset viewer and commit log text.'
306
 
        ' Default: monospace 10')),
307
 
    (_('Diff Text'), 'gtools.fontdiff', [],
308
 
        _('Font used for diffs in status and commit tools.'
309
 
        ' Default: monospace 10')),
310
 
    (_('File List'), 'gtools.fontlist', [],
311
 
        _('Font used in file lists in status and commit tools.'
312
 
        ' Default: sans 9')),
313
 
    (_('Command Output'), 'gtools.fontlog', [],
314
 
        _('Font used in command output window.'
315
 
        ' Default: monospace 10')),
316
 
    )),
317
 
 
318
 
({'name': 'extensions', 'label': _('Extensions'), 'icon': gtk.STOCK_EXECUTE,
319
 
  'extra': True}, ()),
320
 
 
321
 
({'name': 'issue', 'label': _('Issue Tracking'), 'icon': gtk.STOCK_EDIT,
322
 
  'extra': True}, (
323
 
    (_('Issue Regex'), 'tortoisehg.issue.regex', [],
324
 
        _('Defines the regex to match when picking up issue numbers.')),
325
 
    (_('Issue Link'), 'tortoisehg.issue.link', [],
326
 
        _('Defines the command to run when an issue number is recognized. '
327
 
          'You may include groups in issue.regex, and corresponding {n} '
328
 
          'tokens in issue.link (where n is a non-negative integer). '
329
 
          '{0} refers to the entire string matched by issue.regex, '
330
 
          'while {1} refers to the first group and so on. If no {n} tokens'
331
 
          'are found in issue.link, the entire matched string is appended '
332
 
          'instead.')),
333
 
    (_('Mandatory Issue Reference'), 'tortoisehg.issue.linkmandatory',
334
 
        ['False', 'True'],
335
 
        _('When commiting an issue, force the user to specify a reference '
336
 
            'to an issue. '
337
 
          'If enabled, the regex configured in \'Issue Regex\' must find a '
338
 
          'match in the commit message')),
339
 
    )),
340
 
)
341
 
 
342
 
_font_presets = {
343
 
    'win-ja': (_('Japanese on Windows'), {
344
 
        'gtools.fontcomment': 'MS Gothic 11',
345
 
        'gtools.fontdiff': 'MS Gothic 10',
346
 
        'gtools.fontlist': 'MS UI Gothic 9',
347
 
        'gtools.fontlog': 'MS Gothic 10'})}
348
 
 
349
 
class PathEditDialog(gtk.Dialog):
350
 
    _protocols = (('ssh', _('ssh')), ('http', _('http')),
351
 
                  ('https', _('https')), ('local', _('local')))
352
 
 
353
 
    def __init__(self, path, alias, list):
354
 
        gtk.Dialog.__init__(self, parent=None, flags=gtk.DIALOG_MODAL,
355
 
                          buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
356
 
                              gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
357
 
        gtklib.set_tortoise_keys(self)
358
 
        self.connect('response', self.response)
359
 
        self.connect('key-press-event', self.key_press)
360
 
        self.set_title(_('Edit remote repository path'))
361
 
        self.set_has_separator(False)
362
 
        self.set_resizable(False)
363
 
        self.newpath, self.newalias = None, None
364
 
        self.list = list
365
 
 
366
 
        self.entries = {}
367
 
        # Tuple: (internal name, translated name)
368
 
        for name in (('URL', _('URL')), ('Port', _('Port')),
369
 
                     ('Folder', _('Folder')), ('Host', _('Host')),
370
 
                     ('User', _('User')), ('Password', _('Password')),
371
 
                     ('Alias', _('Alias'))):
372
 
            entry = gtk.Entry()
373
 
            entry.set_alignment(0)
374
 
            label = gtk.Label(name[1])
375
 
            label.set_alignment(1, 0.5)
376
 
            self.entries[name[0]] = [entry, label, None]
377
 
 
378
 
        # persistent settings
379
 
        self.settings = settings.Settings('pathedit')
380
 
 
381
 
        # configure individual widgets
382
 
        self.entries['Alias'][0].set_width_chars(18)
383
 
        self.entries['URL'][0].set_width_chars(60)
384
 
        self.entries['Port'][0].set_width_chars(8)
385
 
        self.entries['User'][0].set_width_chars(18)
386
 
        self.entries['Password'][0].set_width_chars(24)
387
 
        self.entries['Password'][0].set_visibility(False)
388
 
 
389
 
        # table for main entries
390
 
        toptable = gtklib.LayoutTable()
391
 
        self.vbox.pack_start(toptable, False, False, 2)
392
 
 
393
 
        ## alias (and 'Browse...' button)
394
 
        browse = gtk.Button(_('Browse...'))
395
 
        browse.connect('clicked', self.browse_clicked)
396
 
        ealias = self.entries['Alias']
397
 
        toptable.add_row(ealias[1], ealias[0], None, browse, expand=0)
398
 
 
399
 
        ## final URL
400
 
        eurl = self.entries['URL']
401
 
        toptable.add_row(eurl[1], eurl[0], padding=False)
402
 
 
403
 
        self.expander = expander = gtk.Expander(_('URL Details'))
404
 
        self.vbox.pack_start(expander, True, True, 2)
405
 
 
406
 
        # table for separated entries
407
 
        entrytable = gtklib.LayoutTable()
408
 
        expander.add(entrytable)
409
 
 
410
 
        ## path type
411
 
        self.protocolcombo = gtk.combo_box_new_text()
412
 
        for name, label in self._protocols:
413
 
            self.protocolcombo.append_text(label)
414
 
        toptable.add_row(_('Type'), self.protocolcombo)
415
 
 
416
 
        ## host & port
417
 
        ehost, eport = self.entries['Host'], self.entries['Port']
418
 
        entrytable.add_row(ehost[1], ehost[0], 8, eport[1], eport[0])
419
 
 
420
 
        ## folder
421
 
        efolder = self.entries['Folder']
422
 
        entrytable.add_row(efolder[1], efolder[0], padding=False)
423
 
 
424
 
        ## username & password
425
 
        euser, epasswd = self.entries['User'], self.entries['Password']
426
 
        entrytable.add_row(euser[1], euser[0], 8, epasswd[1], epasswd[0])
427
 
 
428
 
        # layout group
429
 
        group = gtklib.LayoutGroup()
430
 
        group.add(toptable, entrytable)
431
 
 
432
 
        # prepare to show
433
 
        self.load_settings()
434
 
        self.setentries(path, alias)
435
 
        self.sethandlers()
436
 
        self.lastproto = None
437
 
        self.update_sensitive()
438
 
        self.show_all()
439
 
 
440
 
    def protocolindex(self, pname):
441
 
        for (i, (name, label)) in enumerate(self._protocols):
442
 
            if name == pname:
443
 
                return i
444
 
        return None
445
 
 
446
 
    def protocolname(self, plabel):
447
 
        for (name, label) in self._protocols:
448
 
            if label == plabel:
449
 
                return name
450
 
        return None
451
 
 
452
 
    def sethandlers(self, enable=True):
453
 
        # protocol combobox
454
 
        if enable:
455
 
            self.pcombo_hid = self.protocolcombo.connect('changed', self.changed)
456
 
        else:
457
 
            h = self.pcombo_hid
458
 
            if h and self.protocolcombo.handler_is_connected(h):
459
 
                self.protocolcombo.disconnect(h)
460
 
 
461
 
        # other entries
462
 
        for n, (e, l, h) in self.entries.iteritems():
463
 
            if enable:
464
 
                handler = (n == 'URL' and self.changedurl or self.changed)
465
 
                self.entries[n][2] = e.connect('changed', handler)
466
 
            else:
467
 
                if e.handler_is_connected(h):
468
 
                    e.disconnect(h)
469
 
 
470
 
    def urlparse(self, path):
471
 
        m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
472
 
        if m:
473
 
            user = m.group(2)
474
 
            host = m.group(3)
475
 
            port = m.group(5)
476
 
            folder = m.group(7) or '.'
477
 
            passwd = ''
478
 
            scheme = 'ssh'
479
 
        elif path.startswith('http://') or path.startswith('https://'):
480
 
            snpaqf = urlparse.urlparse(path)
481
 
            scheme, netloc, folder, params, query, fragment = snpaqf
482
 
            host, port, user, passwd = url.netlocsplit(netloc)
483
 
            if folder.startswith('/'): folder = folder[1:]
484
 
        else:
485
 
            user, host, port, passwd = [''] * 4
486
 
            folder = path
487
 
            scheme = 'local'
488
 
        return user, host, port, folder, passwd, scheme
489
 
 
490
 
    def setentries(self, path, alias=None):
491
 
        if alias == None:
492
 
            alias = self.entries['Alias'][0].get_text()
493
 
 
494
 
        user, host, port, folder, pw, scheme = self.urlparse(path)
495
 
 
496
 
        self.entries['Alias'][0].set_text(alias)
497
 
        if scheme == 'local':
498
 
            self.entries['URL'][0].set_text(path)
499
 
        else:
500
 
            self.entries['URL'][0].set_text(url.hidepassword(path))
501
 
        self.entries['User'][0].set_text(user or '')
502
 
        self.entries['Host'][0].set_text(host or '')
503
 
        self.entries['Port'][0].set_text(port or '')
504
 
        self.entries['Folder'][0].set_text(folder or '')
505
 
        self.entries['Password'][0].set_text(pw or '')
506
 
 
507
 
        self.protocolcombo.set_active(self.protocolindex(scheme) or 0)
508
 
 
509
 
    def update_sensitive(self):
510
 
        proto = self.protocolname(self.protocolcombo.get_active_text())
511
 
        if proto == self.lastproto:
512
 
            return
513
 
        self.lastproto = proto
514
 
        if proto == 'local':
515
 
            for n in ('User', 'Password', 'Port', 'Host'):
516
 
                self.entries[n][0].set_sensitive(False)
517
 
                self.entries[n][1].set_sensitive(False)
518
 
        elif proto == 'ssh':
519
 
            for n in ('User', 'Port', 'Host'):
520
 
                self.entries[n][0].set_sensitive(True)
521
 
                self.entries[n][1].set_sensitive(True)
522
 
            self.entries['Password'][0].set_sensitive(False)
523
 
            self.entries['Password'][1].set_sensitive(False)
524
 
        else:
525
 
            for n in ('User', 'Password', 'Port', 'Host'):
526
 
                self.entries[n][0].set_sensitive(True)
527
 
                self.entries[n][1].set_sensitive(True)
528
 
 
529
 
    def load_settings(self):
530
 
        expanded = self.settings.get_value('expanded', False, True)
531
 
        self.expander.set_property('expanded', expanded)
532
 
 
533
 
    def store_settings(self):
534
 
        expanded = self.expander.get_property('expanded')
535
 
        self.settings.set_value('expanded', expanded)
536
 
        self.settings.write()
537
 
 
538
 
    def browse_clicked(self, button):
539
 
        if self.protocolname(self.protocolcombo.get_active_text()) == 'local':
540
 
            initial = self.entries['URL'][0].get_text()
541
 
        else:
542
 
            initial = None
543
 
        path = gtklib.NativeFolderSelectDialog(initial=initial,
544
 
                          title=_('Select Local Folder')).run()
545
 
        if path:
546
 
            self.entries['URL'][0].set_text(path)
547
 
 
548
 
    def changed(self, combo):
549
 
        newurl = self.buildurl()
550
 
        self.sethandlers(False)
551
 
        self.entries['URL'][0].set_text(url.hidepassword(newurl))
552
 
        self.sethandlers(True)
553
 
        self.update_sensitive()
554
 
 
555
 
    def changedurl(self, combo):
556
 
        self.sethandlers(False)
557
 
        self.setentries(self.entries['URL'][0].get_text())
558
 
        self.sethandlers(True)
559
 
        self.update_sensitive()
560
 
 
561
 
    def response(self, widget, response_id):
562
 
        if response_id != gtk.RESPONSE_OK:
563
 
            self.store_settings()
564
 
            self.destroy()
565
 
            return
566
 
        aliasinput = self.entries['Alias'][0]
567
 
        newalias = aliasinput.get_text()
568
 
        if newalias == '':
569
 
            ret = dialog.error_dialog(self, _('Alias name is empty'),
570
 
                    _('Please enter alias name'))
571
 
            aliasinput.grab_focus()
572
 
            return
573
 
        if newalias in self.list:
574
 
            ret = gdialog.Confirm(_('Confirm Overwrite'), [], self,
575
 
                   _("Overwrite existing '%s' path?") % newalias).run()
576
 
            if ret != gtk.RESPONSE_YES:
577
 
                return
578
 
        self.newpath = self.buildurl()
579
 
        self.newalias = newalias
580
 
        self.store_settings()
581
 
        self.destroy()
582
 
 
583
 
    def key_press(self, widget, event):
584
 
        if event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
585
 
            self.response(widget, gtk.RESPONSE_OK)
586
 
 
587
 
    def buildurl(self):
588
 
        proto = self.protocolname(self.protocolcombo.get_active_text())
589
 
        host = self.entries['Host'][0].get_text()
590
 
        port = self.entries['Port'][0].get_text()
591
 
        folder = self.entries['Folder'][0].get_text()
592
 
        user = self.entries['User'][0].get_text()
593
 
        pwd = self.entries['Password'][0].get_text()
594
 
        if proto == 'ssh':
595
 
            ret = 'ssh://'
596
 
            if user:
597
 
                ret += user + '@'
598
 
            ret += host
599
 
            if port:
600
 
                ret += ':' + port
601
 
            ret += '/' + folder
602
 
        elif proto == 'local':
603
 
            ret = folder
604
 
        else:
605
 
            ret = proto + '://'
606
 
            netloc = url.netlocunsplit(host, port, user, pwd)
607
 
            ret += netloc + '/' + folder
608
 
        return ret
609
 
 
610
 
CONF_GLOBAL = 0
611
 
CONF_REPO   = 1
612
 
 
613
 
class ConfigDialog(gtk.Dialog):
614
 
    def __init__(self, configrepo=False, focus=None):
615
 
        """ Initialize the Dialog. """
616
 
        gtk.Dialog.__init__(self, parent=None, flags=0,
617
 
                            buttons=(gtk.STOCK_OK, gtk.RESPONSE_OK,
618
 
                                     gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
619
 
                                     gtk.STOCK_APPLY, gtk.RESPONSE_APPLY))
620
 
        gtklib.set_tortoise_keys(self)
621
 
        self._btn_apply = self.action_area.get_children()[0]
622
 
        self.set_has_separator(False)
623
 
        self.set_default_size(-1, 500)
624
 
 
625
 
        self.ui = ui.ui()
626
 
        try:
627
 
            root = paths.find_root()
628
 
            if root:
629
 
                repo = hg.repository(self.ui, root)
630
 
                name = hglib.get_reponame(repo)
631
 
                self.ui = repo.ui
632
 
            else:
633
 
                repo = None
634
 
            self.root = root
635
 
        except error.RepoError:
636
 
            repo = None
637
 
            if configrepo:
638
 
                dialog.error_dialog(self, _('No repository found'),
639
 
                             _('no repo at ') + root)
640
 
                self.destroy()
641
 
                return
642
 
 
643
 
        try:
644
 
            import iniparse
645
 
            iniparse.INIConfig
646
 
            self.readonly = False
647
 
        except ImportError:
648
 
            dialog.error_dialog(self, _('Iniparse package not found'),
649
 
                         _("Can't change settings without iniparse package - "
650
 
                           'view is readonly.'))
651
 
            print 'Please install http://code.google.com/p/iniparse/'
652
 
            self.readonly = True
653
 
 
654
 
        # Catch close events
655
 
        self.connect('response', self.should_live)
656
 
        self.connect('thg-accept', self.thgaccept)
657
 
        self.connect('delete-event', self.delete_event)
658
 
 
659
 
        # wrapper box for padding
660
 
        wrapbox = gtk.VBox()
661
 
        wrapbox.set_border_width(5)
662
 
        self.vbox.pack_start(wrapbox)
663
 
 
664
 
        # create combo to select the target
665
 
        hbox = gtk.HBox()
666
 
        wrapbox.pack_start(hbox, False, False, 1)
667
 
        combo = gtk.combo_box_new_text()
668
 
        hbox.pack_start(combo, False, False)
669
 
        combo.append_text(_('User global settings'))
670
 
        if repo:
671
 
            combo.append_text(_('%s repository settings') % hglib.toutf(name))
672
 
        combo.connect('changed', self.fileselect)
673
 
        self.confcombo = combo
674
 
 
675
 
        # command buttons
676
 
        edit = gtk.Button(_('Edit File'))
677
 
        hbox.pack_start(edit, False, False, 6)
678
 
        edit.connect('clicked', self.edit_clicked)
679
 
 
680
 
        reload = gtk.Button(_('Reload'))
681
 
        hbox.pack_start(reload, False, False)
682
 
        reload.connect('clicked', self.reload_clicked)
683
 
 
684
 
        # insert padding of between combo and middle pane
685
 
        wrapbox.pack_start(gtk.VBox(), False, False, 4)
686
 
 
687
 
        # hbox for splitting treeview and main pane (notebook + desc)
688
 
        sidebox = gtk.HBox()
689
 
        wrapbox.pack_start(sidebox)
690
 
 
691
 
        # create treeview for selecting configuration page
692
 
        self.confmodel = gtk.ListStore(gtk.gdk.Pixbuf, # icon
693
 
                                       str, # label text
694
 
                                       str) # page id
695
 
        self.confview = gtk.TreeView(self.confmodel)
696
 
        self.confview.set_headers_visible(False)
697
 
        self.confview.set_size_request(140, -1)
698
 
        self.confview.connect('cursor-changed', self.cursor_changed)
699
 
 
700
 
        tvframe = gtk.Frame()
701
 
        sidebox.pack_start(tvframe, False, False)
702
 
        tvframe.set_shadow_type(gtk.SHADOW_IN)
703
 
        tvframe.add(self.confview)
704
 
 
705
 
        col = gtk.TreeViewColumn()
706
 
        self.confview.append_column(col)
707
 
        cell = gtk.CellRendererPixbuf()
708
 
        col.pack_start(cell, True)
709
 
        col.add_attribute(cell, 'pixbuf', 0)
710
 
 
711
 
        col = gtk.TreeViewColumn(None, gtk.CellRendererText(), text=1)
712
 
        self.confview.append_column(col)
713
 
 
714
 
        # insert padding of between treeview and main pane
715
 
        sidebox.pack_start(gtk.HBox(), False, False, 4)
716
 
 
717
 
        # vbox for splitting notebook and desc frame
718
 
        mainbox = gtk.VBox()
719
 
        sidebox.pack_start(mainbox)
720
 
 
721
 
        # create a new notebook, place the position of the tabs
722
 
        self.notebook = notebook = gtk.Notebook()
723
 
        notebook.set_show_tabs(False)
724
 
        notebook.set_show_border(False)
725
 
        notebook.show()
726
 
        mainbox.pack_start(notebook)
727
 
 
728
 
        self.dirty = False
729
 
        self.pages = {}
730
 
        self.tooltips = gtklib.Tooltips()
731
 
        self.history = settings.Settings('thgconfig')
732
 
 
733
 
        # add page items to treeview
734
 
        for meta, info in INFO:
735
 
            if meta['name'] == 'issue' and bugtraq is None:
736
 
                continue
737
 
            pixbuf = gtklib.get_icon_pixbuf(meta['icon'],
738
 
                            gtk.ICON_SIZE_LARGE_TOOLBAR)
739
 
            self.confmodel.append((pixbuf, meta['label'], meta['name']))
740
 
 
741
 
        # insert padding of between notebook and common desc frame
742
 
        mainbox.pack_start(gtk.VBox(), False, False, 2)
743
 
 
744
 
        # common description frame
745
 
        descframe = gtk.Frame(_('Description'))
746
 
        mainbox.pack_start(descframe, False, False)
747
 
        desctext = gtk.TextView()
748
 
        desctext.set_wrap_mode(gtk.WRAP_WORD)
749
 
        desctext.set_editable(False)
750
 
        desctext.set_sensitive(False)
751
 
        scrolled = gtk.ScrolledWindow()
752
 
        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
753
 
        scrolled.set_border_width(4)
754
 
        scrolled.add(desctext)
755
 
        descframe.add(scrolled)
756
 
        self.descbuffer = desctext.get_buffer()
757
 
 
758
 
        # Force dialog into clean state in the beginning
759
 
        self._btn_apply.set_sensitive(False)
760
 
        self.dirty = False
761
 
        combo.set_active(configrepo and CONF_REPO or CONF_GLOBAL)
762
 
 
763
 
        # focus 'general' page or specified field
764
 
        if focus:
765
 
            self.focus_field(focus)
766
 
        else:
767
 
            self.show_page('general')
768
 
            self.confview.grab_focus()
769
 
 
770
 
    def fileselect(self, combo):
771
 
        'select another hgrc file'
772
 
        if self.dirty:
773
 
            ret = gdialog.CustomPrompt(_('Confirm Switch'),
774
 
                    _('Switch after saving changes?'), self,
775
 
                    (_('&Save'), _('&Discard'), _('&Cancel')),
776
 
                    default=2, esc=2).run()
777
 
            if ret == 2:
778
 
                combo.handler_block_by_func(self.fileselect)
779
 
                repo = combo.get_active() == CONF_GLOBAL
780
 
                combo.set_active(repo and CONF_REPO or CONF_GLOBAL)
781
 
                combo.handler_unblock_by_func(self.fileselect)
782
 
                return
783
 
            elif ret == 0:
784
 
                self.apply_changes()
785
 
        self.refresh()
786
 
 
787
 
    def refresh(self):
788
 
        # determine target config file
789
 
        if self.confcombo.get_active() == CONF_REPO:
790
 
            repo = hg.repository(ui.ui(), self.root)
791
 
            name = hglib.get_reponame(repo)
792
 
            self.rcpath = [os.sep.join([repo.root, '.hg', 'hgrc'])]
793
 
            self.set_title(_('TortoiseHg Configure Repository - ') + \
794
 
                           hglib.toutf(name))
795
 
            gtklib.set_tortoise_icon(self, 'settings_repo.ico')
796
 
        else:
797
 
            self.rcpath = util.user_rcpath()
798
 
            self.set_title(_('TortoiseHg Configure User-Global Settings'))
799
 
            gtklib.set_tortoise_icon(self, 'settings_user.ico')
800
 
 
801
 
        # refresh config values
802
 
        self.ini = self.load_config(self.rcpath)
803
 
        self.refresh_vlist()
804
 
 
805
 
        # clear modification status
806
 
        self._btn_apply.set_sensitive(False)
807
 
        self.dirty = False
808
 
 
809
 
    def edit_clicked(self, button):
810
 
        # reload configs, in case they have been written since opened
811
 
        if self.confcombo.get_active() == CONF_REPO:
812
 
            repo = hg.repository(ui.ui(), self.root)
813
 
            u = repo.ui
814
 
        else:
815
 
            u = ui.ui()
816
 
        # open config file with visual editor
817
 
        if not gtklib.open_with_editor(u, self.fn, self):
818
 
            self.focus_field('tortoisehg.editor')
819
 
 
820
 
    def reload_clicked(self, button):
821
 
        if self.dirty:
822
 
            ret = gdialog.Confirm(_('Confirm Reload'), [], self,
823
 
                                  _('Unsaved changes will be lost.\n'
824
 
                                    'Do you want to reload?')).run()
825
 
            if ret != gtk.RESPONSE_YES:
826
 
                return
827
 
        self.refresh()
828
 
 
829
 
    def delete_event(self, dlg, event):
830
 
        return True
831
 
 
832
 
    def thgaccept(self, dlg):
833
 
        self.response(gtk.RESPONSE_OK)
834
 
 
835
 
    def should_live(self, dialog=None, resp=None):
836
 
        if resp == gtk.RESPONSE_APPLY:
837
 
            self.apply_changes()
838
 
            self.emit_stop_by_name('response')
839
 
            return True
840
 
        elif resp == gtk.RESPONSE_CANCEL:
841
 
            return False
842
 
        if self.dirty and not self.readonly:
843
 
            if resp == gtk.RESPONSE_OK:
844
 
                ret = 0
845
 
            else:
846
 
                ret = gdialog.CustomPrompt(_('Confirm Exit'),
847
 
                        _('Exit after saving changes?'), self,
848
 
                        (_('&Yes'), _('&No (discard changes)'),
849
 
                         _('&Cancel')), default=2, esc=2).run()
850
 
            if ret == 2:
851
 
                if resp is not None:
852
 
                    self.emit_stop_by_name('response')
853
 
                return True
854
 
            elif ret == 0:
855
 
                self.apply_changes()
856
 
        return False
857
 
 
858
 
    def cursor_changed(self, treeview):
859
 
        path, focus = self.confview.get_cursor()
860
 
        if not path:
861
 
            return
862
 
        name = self.confmodel[path][2]
863
 
        if not self.pages.has_key(name):
864
 
            self.add_page(name)
865
 
        page_num, info, vbox, widgets = self.pages[name]
866
 
        self.notebook.set_current_page(page_num)
867
 
 
868
 
    def show_page(self, name):
869
 
        '''Show page by activating treeview item'''
870
 
        for row in self.confmodel:
871
 
            if name == row[2]:
872
 
                path = row.path
873
 
                break
874
 
        else:
875
 
            return
876
 
        self.confview.set_cursor(path)
877
 
 
878
 
    def focus_field(self, focusfield):
879
 
        '''Set page and focus to requested datum'''
880
 
        for meta, info in INFO:
881
 
            for n, (label, cpath, values, tip) in enumerate(info):
882
 
                if cpath == focusfield:
883
 
                    name = meta['name']
884
 
                    self.show_page(name)
885
 
                    widgets = self.pages[name][3]
886
 
                    widgets[n].grab_focus()
887
 
                    return
888
 
 
889
 
    def new_path(self, newpath, alias='new'):
890
 
        '''Add a new path to [paths], give default name, focus'''
891
 
        # This method may be called from hgtk.sync, so ensure page is visible
892
 
        self.show_page('sync')
893
 
        i = self.pathdata.insert_before(None, None)
894
 
        safepath = url.hidepassword(newpath)
895
 
        if alias in [row[0] for row in self.pathdata]:
896
 
            num = 0
897
 
            while len([row for row in self.pathdata if row[0] == alias]) > 0:
898
 
                num += 1
899
 
                alias = 'new_%d' % num
900
 
        self.pathdata.set_value(i, 0, alias)
901
 
        self.pathdata.set_value(i, 1, '%s' % hglib.toutf(safepath))
902
 
        self.pathdata.set_value(i, 2, '%s' % hglib.toutf(newpath))
903
 
        self.pathtree.get_selection().select_iter(i)
904
 
        self.pathtree.set_cursor(
905
 
                self.pathdata.get_path(i),
906
 
                self.pathtree.get_column(0))
907
 
        self.refresh_path_list()
908
 
        self.dirty_event()
909
 
 
910
 
    def dirty_event(self, *args):
911
 
        if not self.dirty:
912
 
            self._btn_apply.set_sensitive(not self.readonly)
913
 
            self.dirty = True
914
 
 
915
 
    def refresh_path_list(self):
916
 
        """Update sensitivity of buttons"""
917
 
        selection = self.pathtree.get_selection()
918
 
        path_selected = (len(self.pathdata) > 0
919
 
            and selection.count_selected_rows() > 0)
920
 
        repo_available = self.root is not None
921
 
        if path_selected:
922
 
            model, path = selection.get_selected()
923
 
            default_path = model[path][0] == 'default'
924
 
        else:
925
 
            default_path = False
926
 
        self.editpathbtn.set_sensitive(path_selected)
927
 
        self.delpathbtn.set_sensitive(path_selected)
928
 
        self.testpathbtn.set_sensitive(repo_available and path_selected)
929
 
        self.defaultpathbtn.set_sensitive(not default_path and path_selected)
930
 
 
931
 
    def fill_sync_frame(self, parent, table):
932
 
        # add table
933
 
        parent.pack_start(table, False, False)
934
 
 
935
 
        # insert padding
936
 
        parent.pack_start(gtk.VBox(), False, False, 2)
937
 
 
938
 
        # paths frame
939
 
        frame = gtk.Frame(_('Remote repository paths'))
940
 
        parent.pack_start(frame, True, True, 2)
941
 
        vbox = gtk.VBox()
942
 
        vbox.set_border_width(4)
943
 
        frame.add(vbox)
944
 
 
945
 
        # Initialize data model for 'Paths' tab
946
 
        self.pathdata = gtk.ListStore(str, str, str)
947
 
 
948
 
        # Define view model for 'Paths' tab
949
 
        self.pathtree = gtk.TreeView(self.pathdata)
950
 
        self.pathtree.set_enable_search(False)
951
 
        self.pathtree.add_events(gtk.gdk.BUTTON_PRESS_MASK)
952
 
 
953
 
        renderer = gtk.CellRendererText()
954
 
        column = gtk.TreeViewColumn(_('Alias'), renderer, text=0)
955
 
        self.pathtree.append_column(column)
956
 
 
957
 
        renderer = gtk.CellRendererText()
958
 
        column = gtk.TreeViewColumn(_('Repository Path'), renderer, text=1)
959
 
        self.pathtree.append_column(column)
960
 
 
961
 
        scrolled = gtk.ScrolledWindow()
962
 
        scrolled.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
963
 
        scrolled.add(self.pathtree)
964
 
 
965
 
        treeframe = gtk.Frame()
966
 
        treeframe.set_border_width(2)
967
 
        treeframe.set_shadow_type(gtk.SHADOW_IN)
968
 
        treeframe.add(scrolled)
969
 
        vbox.add(treeframe)
970
 
 
971
 
        # bottom command buttons
972
 
        bottombox = gtk.HBox()
973
 
        vbox.pack_start(bottombox, False, False, 4)
974
 
 
975
 
        addpathbtn = gtk.Button(_('_Add'))
976
 
        addpathbtn.set_use_underline(True)
977
 
        bottombox.pack_start(addpathbtn, True, True, 2)
978
 
 
979
 
        self.editpathbtn = gtk.Button(_('_Edit'))
980
 
        self.editpathbtn.set_use_underline(True)
981
 
        bottombox.pack_start(self.editpathbtn, True, True, 2)
982
 
 
983
 
        self.delpathbtn = gtk.Button(_('_Remove'))
984
 
        self.delpathbtn.set_use_underline(True)
985
 
        bottombox.pack_start(self.delpathbtn, True, True, 2)
986
 
 
987
 
        self.testpathbtn = gtk.Button(_('_Test'))
988
 
        self.testpathbtn.set_use_underline(True)
989
 
        bottombox.pack_start(self.testpathbtn, True, True, 2)
990
 
 
991
 
        self.defaultpathbtn = gtk.Button(_('Set as _default'))
992
 
        self.defaultpathbtn.set_use_underline(True)
993
 
        bottombox.pack_start(self.defaultpathbtn, True, True, 2)
994
 
 
995
 
        # signal handlers
996
 
        def pathtree_changed(sel):
997
 
            self.refresh_path_list()
998
 
        def pathtree_pressed(widget, event):
999
 
            if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
1000
 
                x, y = int(event.x), int(event.y)
1001
 
                pathinfo = self.pathtree.get_path_at_pos(x, y)
1002
 
                if pathinfo is not None:
1003
 
                    edit_path()
1004
 
            elif event.button == 1:
1005
 
                selection = self.pathtree.get_selection()
1006
 
                selection.unselect_all()
1007
 
                self.refresh_path_list()
1008
 
        def edit_path(*args, **opts):
1009
 
            selection = self.pathtree.get_selection()
1010
 
            if not selection.count_selected_rows():
1011
 
                return
1012
 
            model, path = selection.get_selected()
1013
 
            dialog = PathEditDialog(model[path][2], model[path][0],
1014
 
                    [p[0] for p in self.pathdata if p[0] != model[path][0]])
1015
 
            dialog.set_transient_for(self)
1016
 
            dialog.run()
1017
 
            if dialog.newpath:
1018
 
                if model[path][0] != dialog.newalias:
1019
 
                    # remove existing path
1020
 
                    rows = [row for row in model if row[0] == dialog.newalias]
1021
 
                    if len(rows) > 0:
1022
 
                        del model[rows[0].iter]
1023
 
                # update path info
1024
 
                model[path][0] = dialog.newalias
1025
 
                model[path][1] = url.hidepassword(dialog.newpath)
1026
 
                model[path][2] = dialog.newpath
1027
 
                self.dirty_event()
1028
 
            elif opts.has_key('new') and opts['new'] == True:
1029
 
                del self.pathdata[path]
1030
 
                self.refresh_path_list()
1031
 
                self.dirty_event()
1032
 
 
1033
 
        def add_path(*args):
1034
 
            self.new_path('http://')
1035
 
            edit_path(new=True)
1036
 
        def remove_path(*args):
1037
 
            selection = self.pathtree.get_selection()
1038
 
            if not selection.count_selected_rows():
1039
 
                return
1040
 
            model, path = selection.get_selected()
1041
 
            next_iter = self.pathdata.iter_next(path)
1042
 
            del self.pathdata[path]
1043
 
            if next_iter:
1044
 
                selection.select_iter(next_iter)
1045
 
            elif len(self.pathdata):
1046
 
                selection.select_path(len(self.pathdata) - 1)
1047
 
            self.refresh_path_list()
1048
 
            self.dirty_event()
1049
 
        def test_path(*args):
1050
 
            selection = self.pathtree.get_selection()
1051
 
            if not selection.count_selected_rows():
1052
 
                return
1053
 
            if not self.root:
1054
 
                dialog.error_dialog(self, _('No Repository Found'),
1055
 
                        _('Path testing cannot work without a repository'))
1056
 
                return
1057
 
            model, path = selection.get_selected()
1058
 
            testpath = hglib.fromutf(model[path][2])
1059
 
            if not testpath:
1060
 
                return
1061
 
            if testpath[0] == '~':
1062
 
                testpath = os.path.expanduser(testpath)
1063
 
            cmdline = ['hg', 'incoming', '--verbose', '--', testpath]
1064
 
            dlg = hgcmd.CmdDialog(cmdline, text='hg incoming')
1065
 
            dlg.run()
1066
 
            dlg.hide()
1067
 
        def make_default(*args):
1068
 
            selection = self.pathtree.get_selection()
1069
 
            if not selection.count_selected_rows():
1070
 
                return
1071
 
            model, path = selection.get_selected()
1072
 
            if model[path][0] == 'default':
1073
 
                return
1074
 
            # collect rows has 'default' alias
1075
 
            rows = [row for row in model if row[0] == 'default']
1076
 
            if len(rows) > 0:
1077
 
                ret = gdialog.Confirm(_('Confirm Overwrite'), [], self,
1078
 
                       _("Overwrite existing '%s' path?") % 'default').run()
1079
 
                if ret != gtk.RESPONSE_YES:
1080
 
                    return
1081
 
                # remove old default path
1082
 
                default_iter = rows[0].iter
1083
 
                del model[default_iter]
1084
 
            # set 'default' alias to selected path
1085
 
            model[path][0] = 'default'
1086
 
            self.refresh_path_list()
1087
 
            self.dirty_event()
1088
 
 
1089
 
        # connect handlers
1090
 
        self.pathtree.connect('cursor-changed', pathtree_changed)
1091
 
        self.pathtree.connect('button-press-event', pathtree_pressed)
1092
 
        addpathbtn.connect('clicked', add_path)
1093
 
        self.editpathbtn.connect('clicked', edit_path)
1094
 
        self.delpathbtn.connect('clicked', remove_path)
1095
 
        self.testpathbtn.connect('clicked', test_path)
1096
 
        self.defaultpathbtn.connect('clicked', make_default)
1097
 
 
1098
 
    def refresh_sync_frame(self):
1099
 
        self.pathdata.clear()
1100
 
        if 'paths' in self.ini:
1101
 
            for name in self.ini['paths']:
1102
 
                path = self.ini['paths'][name]
1103
 
                safepath = hglib.toutf(url.hidepassword(path))
1104
 
                self.pathdata.append([hglib.toutf(name), safepath,
1105
 
                                      hglib.toutf(path)])
1106
 
        self.refresh_path_list()
1107
 
 
1108
 
    def fill_font_frame(self, parent, table):
1109
 
        # layout table
1110
 
        layout = gtklib.LayoutTable()
1111
 
        parent.pack_start(layout, False, False, 2)
1112
 
        parent.reorder_child(layout, 0)
1113
 
 
1114
 
        # radio buttons
1115
 
        defaultradio = gtk.RadioButton(None, _('Theme default fonts'))
1116
 
        presetradio = gtk.RadioButton(defaultradio, _('Preset fonts:'))
1117
 
        customradio = gtk.RadioButton(defaultradio, _('Custom fonts:'))
1118
 
        self.defaultradio = defaultradio
1119
 
        self.presetradio = presetradio
1120
 
        self.customradio = customradio
1121
 
 
1122
 
        # preset list
1123
 
        presetmodel = gtk.ListStore(str, # internal name
1124
 
                                    str) # GUI label
1125
 
        presetmodel.append((None, _(' - Select Preset -')))
1126
 
        for name, (label, preset) in _font_presets.items():
1127
 
            presetmodel.append((name, label))
1128
 
        presetcombo = gtk.ComboBox(presetmodel)
1129
 
        cell = gtk.CellRendererText()
1130
 
        presetcombo.pack_start(cell)
1131
 
        presetcombo.add_attribute(cell, 'text', 1)
1132
 
        self.presetcombo = presetcombo
1133
 
 
1134
 
        # layouting
1135
 
        layout.add_row(defaultradio)
1136
 
        layout.add_row(presetradio, presetcombo)
1137
 
        vbox = gtk.VBox()
1138
 
        vbox.pack_start(customradio, False, False, 4)
1139
 
        vbox.pack_start(gtk.VBox())
1140
 
        layout.add_row(vbox, table, yhopt=gtk.FILL|gtk.EXPAND)
1141
 
 
1142
 
        # signal handlers
1143
 
        def combo_changed(combo):
1144
 
            # configure for each item
1145
 
            model = combo.get_model()
1146
 
            name = model[combo.get_active()][0] # internal name
1147
 
            if name is None:
1148
 
                return
1149
 
            preset = _font_presets[name][1] # preset data
1150
 
            pagenum, info, vbox, widgets = self.pages['font']
1151
 
            for row, (label, cpath, vals, tip) in enumerate(info):
1152
 
                itemcombo = widgets[row]
1153
 
                itemcombo.child.set_text(preset[cpath])
1154
 
        presetcombo.connect('changed', combo_changed)
1155
 
        def radio_activated(radio, init=False):
1156
 
            if not radio.get_active():
1157
 
                return
1158
 
            presetcombo.set_sensitive(presetradio.get_active())
1159
 
            table.set_sensitive(customradio.get_active())
1160
 
            if init:
1161
 
                return
1162
 
            if radio == defaultradio:
1163
 
                pagenum, info, vbox, widgets = self.pages['font']
1164
 
                for row, (label, cpath, vals, tip) in enumerate(info):
1165
 
                    itemcombo = widgets[row]
1166
 
                    itemcombo.set_active(0) # unspecified
1167
 
            if radio != presetradio:
1168
 
                presetcombo.set_active(0)
1169
 
        defaultradio.connect('toggled', radio_activated)
1170
 
        presetradio.connect('toggled', radio_activated)
1171
 
        customradio.connect('toggled', radio_activated)
1172
 
 
1173
 
        # prepare to show
1174
 
        radio_activated(defaultradio, init=True)
1175
 
 
1176
 
    def refresh_font_frame(self):
1177
 
        model = self.presetcombo.get_model()
1178
 
        cpaths = [info[1] for info in self.pages['font'][1]]
1179
 
        defaulfonts = True
1180
 
        for cpath in cpaths:
1181
 
            value = self.get_ini_config(cpath)
1182
 
            if value is not None:
1183
 
                defaulfonts = False
1184
 
        if defaulfonts:
1185
 
            # theme default fonts
1186
 
            self.defaultradio.set_active(True)
1187
 
            self.presetcombo.set_active(0)
1188
 
        else:
1189
 
            for name, (label, preset) in _font_presets.items():
1190
 
                for cpath in cpaths:
1191
 
                    if self.get_ini_config(cpath) != preset[cpath]:
1192
 
                        break
1193
 
                else:
1194
 
                    # preset fonts
1195
 
                    rows = [row for row in model if row[0] == name]
1196
 
                    if rows:
1197
 
                        self.presetcombo.set_active_iter(rows[0].iter)
1198
 
                    self.presetradio.set_active(True)
1199
 
                    break
1200
 
            else:
1201
 
                # custom fonts
1202
 
                self.customradio.set_active(True)
1203
 
                self.presetcombo.set_active(0)
1204
 
 
1205
 
    def fill_extensions_frame(self, parent, table):
1206
 
        allexts = hglib.allextensions()
1207
 
 
1208
 
        MAXCOLUMNS = 3
1209
 
        maxrows = (len(allexts) + MAXCOLUMNS - 1) / MAXCOLUMNS
1210
 
        extstable = gtk.Table()
1211
 
        parent.pack_start(extstable, False, False)
1212
 
 
1213
 
        self.extensionschecks = {}
1214
 
        for i, name in enumerate(sorted(allexts)):
1215
 
            shortdesc = allexts[name]
1216
 
            ck = gtk.CheckButton(name, use_underline=False)
1217
 
            ck.connect('toggled', self.dirty_event)
1218
 
            ck.connect('toggled', self._validateextensions)
1219
 
            ck.connect('focus-in-event', self.set_help,
1220
 
                       hglib.toutf(shortdesc))
1221
 
            col, row = i / maxrows, i % maxrows
1222
 
            extstable.attach(ck, col, col + 1, row, row + 1)
1223
 
            self.extensionschecks[name] = ck
1224
 
 
1225
 
    def _enabledextensions(self):
1226
 
        """list up to-be enabled extensions by reading fresh setting"""
1227
 
        from mercurial import config
1228
 
        cfg = config.config()
1229
 
        for f in util.rcpath():
1230
 
            if os.path.realpath(f) == os.path.realpath(self.fn):
1231
 
                continue  # can be modified by user; read from self.ini
1232
 
            try:
1233
 
                cfg.read(f)
1234
 
            except IOError:
1235
 
                pass
1236
 
 
1237
 
        if 'extensions' in self.ini:
1238
 
            for k in self.ini['extensions']:
1239
 
                cfg.set('extensions', k, self.ini['extensions'][k])
1240
 
 
1241
 
        return set(name for name, path in cfg.items('extensions')
1242
 
                   if not path.startswith('!'))
1243
 
 
1244
 
    def refresh_extensions_frame(self):
1245
 
        enabledexts = self._enabledextensions()
1246
 
        for name, ck in self.extensionschecks.iteritems():
1247
 
            ck.set_active(name in enabledexts)
1248
 
 
1249
 
        self._validateextensions()
1250
 
 
1251
 
    def _validateextensions(self, *args):
1252
 
        enabledexts = self._enabledextensions()
1253
 
        selectedexts = set(name for name, ck
1254
 
                           in self.extensionschecks.iteritems()
1255
 
                           if ck.get_active())
1256
 
        invalidexts = hglib.validateextensions(selectedexts)
1257
 
 
1258
 
        def getinival(name):
1259
 
            if 'extensions' not in self.ini:
1260
 
                return None
1261
 
            for cand in (name, 'hgext.%s' % name, 'hgext/%s' % name):
1262
 
                try:
1263
 
                    v = self.ini['extensions'][cand]
1264
 
                    if not isinstance(v, Undefined):
1265
 
                        return v
1266
 
                except KeyError:
1267
 
                    pass
1268
 
 
1269
 
        def changable(name):
1270
 
            curval = getinival(name)
1271
 
            if curval not in ('', None):
1272
 
                # enabled or unspecified, official extensions only
1273
 
                return False
1274
 
            elif name in enabledexts and curval is None:
1275
 
                # re-disabling ext is not supported
1276
 
                return False
1277
 
            elif name in invalidexts and name not in selectedexts:
1278
 
                # disallow to enable bad exts, but allow to disable it
1279
 
                return False
1280
 
            else:
1281
 
                return True
1282
 
 
1283
 
        allexts = hglib.allextensions()
1284
 
        for name, ck in self.extensionschecks.iteritems():
1285
 
            ck.set_sensitive(changable(name))
1286
 
            self.tooltips.set_tip(ck, invalidexts.get(name)
1287
 
                                  or hglib.toutf(allexts[name]))
1288
 
 
1289
 
    def apply_extensions_changes(self):
1290
 
        enabledexts = self._enabledextensions()
1291
 
        for name, ck in self.extensionschecks.iteritems():
1292
 
            if ck.get_active() == (name in enabledexts):
1293
 
                continue  # unchanged
1294
 
 
1295
 
            if 'extensions' not in self.ini:
1296
 
                new_namespace = getattr(self.ini, '_new_namespace',
1297
 
                                        self.ini.new_namespace)
1298
 
                new_namespace('extensions')
1299
 
 
1300
 
            if ck.get_active():
1301
 
                self.ini['extensions'][name] = ''
1302
 
            else:
1303
 
                for cand in (name, 'hgext.%s' % name, 'hgext/%s' % name):
1304
 
                    try:
1305
 
                        del self.ini['extensions'][cand]
1306
 
                    except KeyError:
1307
 
                        pass
1308
 
 
1309
 
    def set_help(self, widget, event, tooltip):
1310
 
        text = ' '.join(tooltip.splitlines())
1311
 
        self.descbuffer.set_text(text)
1312
 
 
1313
 
    def fill_issue_frame(self, parent, table):
1314
 
        parent.pack_start(table, False, False)
1315
 
 
1316
 
        issuetrackmodel = gtk.ListStore(str, # internal name
1317
 
                                    str) # GUI label
1318
 
        issuetrackmodel.append((None, _(' - Select Issue Tracker -')))
1319
 
        for (guid, label) in bugtraq.get_issue_plugins_with_names():
1320
 
            issuetrackmodel.append((guid, label))
1321
 
        issuetrackcombo = gtk.ComboBox(issuetrackmodel)
1322
 
        cell = gtk.CellRendererText()
1323
 
        issuetrackcombo.pack_start(cell)
1324
 
        issuetrackcombo.add_attribute(cell, 'text', 1)
1325
 
        self.issuetrackcombo = issuetrackcombo
1326
 
 
1327
 
        table.add_row('Issue plugin:', issuetrackcombo, padding=False)
1328
 
 
1329
 
        tooltip = _('Select issue tracker plugin to use. '
1330
 
            'Links to plugins can be found at '
1331
 
            'http://tortoisesvn.net/issuetrackerplugins')
1332
 
        self.tooltips.set_tip(issuetrackcombo, tooltip)
1333
 
 
1334
 
        issuetrackcombo.connect('popup', self.set_help, '', tooltip)
1335
 
 
1336
 
        configurepluginbutton = gtk.Button(_('Configure Plugin'))
1337
 
        table.add_row('', configurepluginbutton, padding=False)
1338
 
        self.configurepluginbutton = configurepluginbutton
1339
 
 
1340
 
        def combo_changed(combo):
1341
 
            model = combo.get_model()
1342
 
            newvalue = ""
1343
 
            selection = model[self.issuetrackcombo.get_active()]
1344
 
            if not selection[0] == None:
1345
 
                newvalue = selection[0] + ' ' + selection[1]
1346
 
            self.record_new_value("tortoisehg.issue.bugtraqplugin",
1347
 
                    newvalue)
1348
 
            self.dirty_event()
1349
 
            self.configurepluginbutton.set_sensitive(newvalue != "")
1350
 
 
1351
 
        def configure_bugtraq_plugin(button):
1352
 
            value = self.get_ini_config("tortoisehg.issue.bugtraqplugin")
1353
 
            if value == None or value == "":
1354
 
                return
1355
 
 
1356
 
            guid = value.split(' ', 1)[0]
1357
 
            value = self.get_ini_config("tortoisehg.issue.bugtraqparameters")
1358
 
            if value == None:
1359
 
                value = ""
1360
 
 
1361
 
            bugtr = bugtraq.BugTraq(guid)
1362
 
            newvalue = bugtr.show_options_dialog(value)
1363
 
 
1364
 
            if value != newvalue:
1365
 
                self.record_new_value("tortoisehg.issue.bugtraqparameters", newvalue)
1366
 
                self.dirty_event()
1367
 
 
1368
 
        issuetrackcombo.connect('changed', combo_changed)
1369
 
        configurepluginbutton.connect('clicked', configure_bugtraq_plugin)
1370
 
 
1371
 
    def refresh_issue_frame(self):
1372
 
        value = self.get_ini_config("tortoisehg.issue.bugtraqplugin")
1373
 
        configuredguid = ""
1374
 
        if not value == None:
1375
 
            configuredguid = re.sub("[-{}]", "", value.split(' ', 1)[0])
1376
 
 
1377
 
        model = self.issuetrackcombo.get_model()
1378
 
        index = 0
1379
 
        found = False
1380
 
        for (guid, label) in model:
1381
 
            if not guid == None:
1382
 
                if re.sub("[-{}]", "", guid) == configuredguid:
1383
 
                    found = True
1384
 
                    break
1385
 
            index = index + 1
1386
 
        if not found:
1387
 
            index = 0
1388
 
        self.issuetrackcombo.set_active(index)
1389
 
        self.configurepluginbutton.set_sensitive(configuredguid != "")
1390
 
 
1391
 
    def fill_frame(self, frame, info, build=True, width=32):
1392
 
        widgets = []
1393
 
 
1394
 
        table = gtklib.LayoutTable()
1395
 
 
1396
 
        vbox = gtk.VBox()
1397
 
        if build:
1398
 
            vbox.pack_start(table, False, False)
1399
 
 
1400
 
        scrolled = gtk.ScrolledWindow()
1401
 
        scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
1402
 
        scrolled.add_with_viewport(vbox)
1403
 
        scrolled.child.set_shadow_type(gtk.SHADOW_NONE)
1404
 
        frame.pack_start(scrolled)
1405
 
 
1406
 
        for row, (label, cpath, values, tooltip) in enumerate(info):
1407
 
            vlist = gtk.ListStore(str, bool)
1408
 
            combo = gtk.ComboBoxEntry(vlist, 0)
1409
 
            combo.connect('changed', self.dirty_event)
1410
 
            combo.child.connect('focus-in-event', self.set_help, tooltip)
1411
 
            combo.set_row_separator_func(lambda model, path: model[path][1])
1412
 
            combo.child.set_width_chars(width)
1413
 
            if cpath in _pwfields:
1414
 
                combo.child.set_visibility(False)
1415
 
            widgets.append(combo)
1416
 
 
1417
 
            table.add_row(label + ':', combo, padding=False)
1418
 
            self.tooltips.set_tip(combo, tooltip)
1419
 
 
1420
 
        return vbox, table, widgets
1421
 
 
1422
 
    def refresh_vlist(self, pagename=None):
1423
 
        # sotre modification status
1424
 
        prev_dirty = self.dirty
1425
 
 
1426
 
        # update configured values
1427
 
        if pagename is None:
1428
 
            pages = self.pages.values()
1429
 
            pages = [(key,) + data for key, data in self.pages.items()]
1430
 
        else:
1431
 
            pages = ((pagename,) + self.pages[pagename],)
1432
 
        for name, page_num, info, vbox, widgets in pages:
1433
 
 
1434
 
            # standard configs
1435
 
            for row, (label, cpath, values, tooltip) in enumerate(info):
1436
 
                ispw = cpath in _pwfields
1437
 
                combo = widgets[row]
1438
 
                vlist = combo.get_model()
1439
 
                vlist.clear()
1440
 
 
1441
 
                # Get currently configured value from this config file
1442
 
                curvalue = self.get_ini_config(cpath)
1443
 
 
1444
 
                if cpath == 'tortoisehg.vdiff':
1445
 
                    tools = hglib.difftools(self.ui)
1446
 
                    for key in tools.keys():
1447
 
                        if key not in values:
1448
 
                            values.append(key)
1449
 
                elif cpath == 'ui.merge':
1450
 
                    # Special case, add [merge-tools] to possible values
1451
 
                    hglib.mergetools(self.ui, values)
1452
 
 
1453
 
                if cpath in _norepofields:
1454
 
                    combo.set_sensitive(
1455
 
                        self.confcombo.get_active() != CONF_REPO)
1456
 
 
1457
 
                currow = None
1458
 
                if not ispw:
1459
 
                    vlist.append([_unspecstr, False])
1460
 
                if values:
1461
 
                    vlist.append([_('Suggested'), True])
1462
 
                    for v in values:
1463
 
                        vlist.append([hglib.toutf(v), False])
1464
 
                        if v == curvalue:
1465
 
                            currow = len(vlist) - 1
1466
 
                if cpath in self.history.get_keys() and not ispw:
1467
 
                    separator = False
1468
 
                    for v in self.history.mrul(cpath):
1469
 
                        if v in values: continue
1470
 
                        if not separator:
1471
 
                            vlist.append([_('History'), True])
1472
 
                            separator = True
1473
 
                        vlist.append([hglib.toutf(v), False])
1474
 
                        if v == curvalue:
1475
 
                            currow = len(vlist) - 1
1476
 
 
1477
 
                if curvalue is None and len(vlist):
1478
 
                    combo.set_active(0)
1479
 
                elif currow is None and curvalue:
1480
 
                    combo.child.set_text(hglib.toutf(curvalue))
1481
 
                elif currow:
1482
 
                    combo.set_active(currow)
1483
 
 
1484
 
            # extra configs
1485
 
            func_name = 'refresh_%s_frame' % name
1486
 
            if hasattr(self, func_name):
1487
 
                getattr(self, func_name)()
1488
 
 
1489
 
        # clear modification status forcibly if need
1490
 
        if self.dirty != prev_dirty:
1491
 
            self._btn_apply.set_sensitive(False)
1492
 
            self.dirty = False
1493
 
 
1494
 
    def add_page(self, name):
1495
 
        for data in INFO:
1496
 
            if name == data[0]['name']:
1497
 
                meta, info = data
1498
 
                break
1499
 
        else:
1500
 
            return
1501
 
        extra, width = meta.get('extra', False), meta.get('width', 32)
1502
 
 
1503
 
        # setup frame and content
1504
 
        frame = gtk.VBox()
1505
 
        frame.show()
1506
 
 
1507
 
        vbox, table, widgets = self.fill_frame(frame, info, not extra, width)
1508
 
        if extra:
1509
 
            func = getattr(self, 'fill_%s_frame' % name, None)
1510
 
            if func:
1511
 
                func(vbox, table)
1512
 
 
1513
 
        # add to notebook
1514
 
        pagenum = self.notebook.append_page(frame)
1515
 
        self.pages[name] = (pagenum, info, vbox, widgets)
1516
 
 
1517
 
        # prepare to show
1518
 
        self.refresh_vlist(name)
1519
 
        frame.show_all()
1520
 
 
1521
 
    def get_ini_config(self, cpath):
1522
 
        '''Retrieve a value from the parsed config file'''
1523
 
        try:
1524
 
            # Presumes single section/key level depth
1525
 
            section, key = cpath.split('.', 1)
1526
 
            if section not in self.ini:
1527
 
                return None
1528
 
            val = self.ini[section][key]
1529
 
            if isinstance(val, Undefined):
1530
 
                return None
1531
 
            return val
1532
 
        except KeyError:
1533
 
            return None
1534
 
 
1535
 
    def load_config(self, rcpath):
1536
 
        for fn in rcpath:
1537
 
            if os.path.exists(fn):
1538
 
                break
1539
 
        else:
1540
 
            for fn in rcpath:
1541
 
                # Try to create a file from rcpath
1542
 
                try:
1543
 
                    f = open(fn, 'w')
1544
 
                    f.write('# Generated by TortoiseHg setting dialog\n')
1545
 
                    f.close()
1546
 
                    break
1547
 
                except (IOError, OSError):
1548
 
                    pass
1549
 
            else:
1550
 
                gdialog.Prompt(_('Unable to create a Mercurial.ini file'),
1551
 
                       _('Insufficient access rights, reverting to read-only'
1552
 
                         'mode.'), self).run()
1553
 
                from mercurial import config
1554
 
                self.fn = rcpath[0]
1555
 
                cfg = config.config()
1556
 
                self.readonly = True
1557
 
                return cfg
1558
 
        self.fn = fn
1559
 
        try:
1560
 
            import iniparse
1561
 
            # Monkypatch this regex to prevent iniparse from considering
1562
 
            # 'rem' as a comment
1563
 
            iniparse.ini.CommentLine.regex = \
1564
 
                       re.compile(r'^(?P<csep>[%;#])(?P<comment>.*)$')
1565
 
            return iniparse.INIConfig(file(fn), optionxformvalue=None)
1566
 
        except ImportError:
1567
 
            from mercurial import config
1568
 
            cfg = config.config()
1569
 
            cfg.read(fn)
1570
 
            self.readonly = True
1571
 
            return cfg
1572
 
        except Exception, e:
1573
 
            gdialog.Prompt(_('Unable to parse a config file'),
1574
 
                    _('%s\nReverting to read-only mode.') % str(e), self).run()
1575
 
            from mercurial import config
1576
 
            cfg = config.config()
1577
 
            cfg.read(fn)
1578
 
            self.readonly = True
1579
 
            return cfg
1580
 
 
1581
 
    def record_new_value(self, cpath, newvalue, keephistory=True):
1582
 
        # 'newvalue' is converted to local encoding
1583
 
        section, key = cpath.split('.', 1)
1584
 
        if newvalue == _unspeclocalstr or newvalue == '':
1585
 
            if section not in self.ini:
1586
 
                return
1587
 
            try:
1588
 
                del self.ini[section][key]
1589
 
            except KeyError:
1590
 
                pass
1591
 
            return
1592
 
        if section not in self.ini:
1593
 
            if hasattr(self.ini, '_new_namespace'):
1594
 
                self.ini._new_namespace(section)
1595
 
            else:
1596
 
                self.ini.new_namespace(section)
1597
 
        self.ini[section][key] = newvalue
1598
 
        if not keephistory:
1599
 
            return
1600
 
        if cpath not in self.history.get_keys():
1601
 
            self.history.set_value(cpath, [])
1602
 
        elif newvalue in self.history.get_keys():
1603
 
            self.history.get_value(cpath).remove(newvalue)
1604
 
        self.history.mrul(cpath).add(newvalue)
1605
 
 
1606
 
    def apply_changes(self):
1607
 
        if self.readonly:
1608
 
            #dialog? Read only access, please install ...
1609
 
            return
1610
 
        # Reload history, since it may have been modified externally
1611
 
        self.history.read()
1612
 
 
1613
 
        # flush changes on paths page
1614
 
        if self.pages.has_key('sync'):
1615
 
            if len(self.pathdata):
1616
 
                refreshlist = []
1617
 
                for row in self.pathdata:
1618
 
                    name = hglib.fromutf(row[0])
1619
 
                    path = hglib.fromutf(row[2])
1620
 
                    if not name:
1621
 
                        gdialog.Prompt(_('Invalid path'),
1622
 
                                _('Skipped saving path with no alias'),
1623
 
                                self).run()
1624
 
                        continue
1625
 
                    cpath = '.'.join(['paths', name])
1626
 
                    self.record_new_value(cpath, path, False)
1627
 
                    refreshlist.append(name)
1628
 
                for name in self.ini.paths:
1629
 
                    if name not in refreshlist:
1630
 
                        del self.ini['paths'][name]
1631
 
            elif 'paths' in list(self.ini):
1632
 
                for name in list(self.ini.paths):
1633
 
                    del self.ini['paths'][name]
1634
 
 
1635
 
        if 'extensions' in self.pages:
1636
 
            self.apply_extensions_changes()
1637
 
 
1638
 
        # Flush changes on all pages
1639
 
        for page_num, info, vbox, widgets in self.pages.values():
1640
 
            for n, (label, cpath, values, tip) in enumerate(info):
1641
 
                newvalue = hglib.fromutf(widgets[n].child.get_text())
1642
 
                self.record_new_value(cpath, newvalue)
1643
 
 
1644
 
        self.history.write()
1645
 
        self.refresh_vlist()
1646
 
 
1647
 
        try:
1648
 
            f = util.atomictempfile(self.fn, 'w', createmode=None)
1649
 
            f.write(str(self.ini))
1650
 
            f.rename()
1651
 
            self._btn_apply.set_sensitive(False)
1652
 
            self.dirty = False
1653
 
        except IOError, e:
1654
 
            dialog.error_dialog(self, _('Unable to write configuration file'),
1655
 
                                hglib.tounicode(str(e)))
1656
 
 
1657
 
        return 0
1658
 
 
1659
 
def run(ui, *pats, **opts):
1660
 
    dlg = ConfigDialog(opts.get('alias') == 'repoconfig',
1661
 
                       focus=opts.get('focus'))
1662
 
    return dlg