Merge lp:~doxxx/qbzr/mergetools into lp:qbzr

Proposed by Gordon Tyler
Status: Rejected
Rejected by: Gordon Tyler
Proposed branch: lp:~doxxx/qbzr/mergetools
Merge into: lp:qbzr
Diff against target: 926 lines (+564/-205)
5 files modified
NEWS.txt (+2/-0)
lib/config.py (+254/-99)
lib/conflicts.py (+42/-106)
lib/ui_merge_config.py (+97/-0)
ui/merge_config.ui (+169/-0)
To merge this branch: bzr merge lp:~doxxx/qbzr/mergetools
Reviewer Review Type Date Requested Status
Gary van der Merwe Needs Fixing
Review via email: mp+38664@code.launchpad.net

Commit message

(doxxx) Use bzrlib.mergetools for managing and using external merge tools in qconfig and qconflicts.

Description of the change

This merge proposal changes qconfig and qconflicts to make use of the bzrlib.mergetools module added by lp:~doxxx/bzr/mergetools. It uses the API to display and edit the list of external merge tools in the Merge tab of qconfig, to display a dropdown selector of an external merge tool in qconflicts and to invoke the selected external merge tool from qconflicts.

To post a comment you must log in.
Revision history for this message
Gordon Tyler (doxxx) wrote :

Almost ready for review, just need to tweak the way error messages are displayed in qconflicts.

Revision history for this message
Gordon Tyler (doxxx) wrote :

Could I get some feedback on this? Any suggestions for how to better handle notification of problems with the selected merge tool in qconflicts?

lp:~doxxx/qbzr/mergetools updated
1313. By Gordon Tyler

Updated for API change in bzrlib.mergetools.

1314. By Gordon Tyler

Use missing gettext call for a user-visible string.

1315. By Gordon Tyler

Replaced Merge config tab contents with new UI.

Revision history for this message
Gordon Tyler (doxxx) wrote :

I've redesigned the UI for the Merge tab in qconfig to allow editing the name and commandline of merge tools.

lp:~doxxx/qbzr/mergetools updated
1316. By Gary van der Merwe

qconfig: Don't crash if no default is set.

Revision history for this message
Gary van der Merwe (garyvdm) wrote :

Sorry about the delay in looking at this. Been busy at work.

Thank you for tackling this.

* qconfig and qconflicts break if using using a old bzr. I would like to keep some level of backwards compatibility. So if running an old bzr, qconfig should load, but the merge tab should show a error message. qconflicts should load, show conflicts, but you won't be able to launch a ext merges app.

* I would prefer for the qconfig merge tab to be a simple list, like the diff tab, rather than the list + form. My reasons for this are:
  + It's easier to code. With the list + form code, you have to do a lot of work to synchronize the selection in the list, and the form item displayed. (see bug below.)
  + Less widgets = Cleaner look.

Anyway - this is bike shedding. So if you disagree, I don't mind. What I do want is for the diff and merge tabs to share the same design, and code.

* I would be nice to give help on then merge parameters (%b %T %o, etc..). Maybe a link to qhelp, like the qgetnew dialog.

* First time I ran qconfig, I got the following error, because I did not have a default set.

bzr: ERROR: exceptions.IndexError: list index out of range

Traceback (most recent call last):
  File "/home/garyvdm/qbzr/mergetools/lib/commands.py", line 166, in run
    ret_code = self._qbzr_run(*args, **kwargs)
  File "/home/garyvdm/qbzr/mergetools/lib/commands.py", line 504, in _qbzr_run
    window = QBzrConfigWindow()
  File "/home/garyvdm/bzr/mergetools/bzrlib/lazy_import.py", line 128, in __call__
    return obj(*args, **kwargs)
  File "/home/garyvdm/qbzr/mergetools/lib/config.py", line 247, in __init__
    self.load()
  File "/home/garyvdm/qbzr/mergetools/lib/config.py", line 378, in load
    self.merge_tools_list_model.set_merge_tools(definedMergeTools, defaultMergeTool)
  File "/home/garyvdm/qbzr/mergetools/lib/config.py", line 743, in set_merge_tools
    self._default = [mt for mt in self._merge_tools if mt == default][0]
IndexError: list index out of range

I've fixed this and pushed to lp:~qbzr-dev/qbzr/mergetools. I've added you to ~qbzr-dev, so if you want, you can also push there.

* In qconfig, if you click on then ... button for the command line, and click cancel, it changes the command line. It should not.

* I saw this bug: steps to reproduce:
Click on a merge tool.
Click detect. (There is now no selection in the list box, but the form on the left stays populated.)
Edit the command line.
press tab (or otherwise let the command line text box lose focus.)

Actual result: get this error:
bzr: ERROR: exceptions.AssertionError:

Traceback (most recent call last):
  File "/home/garyvdm/qbzr/mergetools/lib/config.py", line 606, in merge_tool_name_changed
    assert sel_model.hasSelection()
AssertionError

* The qconficts code seems pretty solid.

I've been wanting to change qconficts to use the treewidget for a while. I'll see if I can work on it shortly.

I also want to make a merge tool menu, like then diff tool menu, so you can select the merge tool in qcommit, qbrowse, be wt view, etc.

review: Needs Fixing
lp:~doxxx/qbzr/mergetools updated
1317. By Gary van der Merwe

Merge Trunk.

Revision history for this message
Gordon Tyler (doxxx) wrote :

> Sorry about the delay in looking at this. Been busy at work.

No worries, thanks for the review.

> * qconfig and qconflicts break if using using a old bzr. I would like to keep
> some level of backwards compatibility. So if running an old bzr, qconfig
> should load, but the merge tab should show a error message. qconflicts should
> load, show conflicts, but you won't be able to launch a ext merges app.

Okay.

> * I would prefer for the qconfig merge tab to be a simple list, like the diff
> tab, rather than the list + form. My reasons for this are:
> + It's easier to code. With the list + form code, you have to do a lot of
> work to synchronize the selection in the list, and the form item displayed.
> (see bug below.)
> + Less widgets = Cleaner look.
>
> Anyway - this is bike shedding. So if you disagree, I don't mind. What I do
> want is for the diff and merge tabs to share the same design, and code.

I went with a separate list and edit form design because I thought it would be easier to use, which I felt was a drawback of the previous design. For instance, there's no obvious clue that the radio button of the previous design indicates that it marks the default tool.

I will investigate an alternate design using a table though, to see if I can come up with something that is easier to use than the old design and can be re-used for the Diff tab.

> * I would be nice to give help on then merge parameters (%b %T %o, etc..).
> Maybe a link to qhelp, like the qgetnew dialog.

Okay.

> * First time I ran qconfig, I got the following error, because I did not have
> a default set.

> I've fixed this and pushed to lp:~qbzr-dev/qbzr/mergetools. I've added you to
> ~qbzr-dev, so if you want, you can also push there.

Cool. Thanks. Can I just pull/push --remember to that with my current branch and continue working like that? And if I do, should I resubmit this merge proposal using the new branch?

> * In qconfig, if you click on then ... button for the command line, and click
> cancel, it changes the command line. It should not.

Indeed it should not.

> * I saw this bug: steps to reproduce:
> Click on a merge tool.
> Click detect. (There is now no selection in the list box, but the form on the
> left stays populated.)
> Edit the command line.
> press tab (or otherwise let the command line text box lose focus.)
>
> Actual result: get this error:
> bzr: ERROR: exceptions.AssertionError:
>
> Traceback (most recent call last):
> File "/home/garyvdm/qbzr/mergetools/lib/config.py", line 606, in
> merge_tool_name_changed
> assert sel_model.hasSelection()
> AssertionError

Yeah, that's to catch code paths like the one you discovered when the list of merge tools has no selection but the edit form is still enabled. I'll investigate.

> I've been wanting to change qconficts to use the treewidget for a while. I'll
> see if I can work on it shortly.

You mean a treewidget to show the conflicts in the context of the working tree structure?

> I also want to make a merge tool menu, like then diff tool menu, so you can
> select the merge tool in qcommit, qbrowse, be wt view, etc.

Nice idea.

lp:~doxxx/qbzr/mergetools updated
1318. By Gordon Tyler

Fixed the other code site which modifies self._default.

1319. By Gordon Tyler

Improved how detected tools are added to the list so that its selection is not cleared.

Revision history for this message
Gary van der Merwe (garyvdm) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 09/11/2010 01:46, Gordon Tyler wrote:
>> I've fixed this and pushed to lp:~qbzr-dev/qbzr/mergetools. I've added you to
>> ~qbzr-dev, so if you want, you can also push there.
>
> Cool. Thanks. Can I just pull/push --remember to that with my current
> branch and continue working like that? And if I do, should I
> resubmit this merge proposal using the new branch?

Yes. Then it makes it easier for me to provide fixes.

>> I've been wanting to change qconficts to use the treewidget for a while. I'll
>> see if I can work on it shortly.
>
> You mean a treewidget to show the conflicts in the context of the working tree structure?

Yes - like qcommit/qrevet/qadd.

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkzY+Q0ACgkQd/3EdwGKOh2qiQCgg+HP/oPNqu0UC/AkX1ULyqaq
9QYAniJ50ZS/Ak9Ie/iI+qig6zzWmcQN
=svmx
-----END PGP SIGNATURE-----

Revision history for this message
Gordon Tyler (doxxx) wrote :

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS.txt'
2--- NEWS.txt 2010-11-08 14:03:03 +0000
3+++ NEWS.txt 2010-11-09 00:10:03 +0000
4@@ -24,6 +24,8 @@
5 (Bug #621934)
6 * qcommit: added option to load commit message from a file.
7 (Bug #640071, Philip Peitsch)
8+ * Use bzrlib.mergetools for managing and using external merge tools in qconfig
9+ and qconflicts. (Bug #489915, Gordon Tyler)
10
11
12 0.19.3 - Under Development
13
14=== modified file 'lib/config.py'
15--- lib/config.py 2010-09-06 06:56:31 +0000
16+++ lib/config.py 2010-11-09 00:10:03 +0000
17@@ -20,12 +20,14 @@
18 import re
19 import os.path
20 from PyQt4 import QtCore, QtGui
21+from PyQt4.Qt import Qt
22 from bzrlib.config import (
23 ensure_config_dir_exists,
24 extract_email_address,
25 )
26-from bzrlib import errors, trace
27+from bzrlib import errors, mergetools, trace
28
29+from bzrlib.plugins.qbzr.lib import ui_merge_config
30 from bzrlib.plugins.qbzr.lib.i18n import gettext, N_
31 from bzrlib.plugins.qbzr.lib.spellcheck import SpellChecker
32 from bzrlib.plugins.qbzr.lib.util import (
33@@ -188,34 +190,48 @@
34 diffLayout.addWidget(self.extDiffList)
35 diffLayout.addLayout(extDiffButtonsLayout)
36
37+ self.merge_ui = ui_merge_config.Ui_MergeConfig()
38 mergeWidget = QtGui.QWidget()
39-
40- label = QtGui.QLabel(gettext("External Merge Apps:"))
41- self.extMergeList = QtGui.QTreeWidget(mergeWidget)
42- self.extMergeList.setRootIsDecorated(False)
43- self.extMergeList.setHeaderLabels([gettext("Definition")])
44- self.extMergeList.setItemDelegateForColumn(0,
45- QRadioCheckItemDelegate(self.extMergeList))
46- self.connect(self.extMergeList, QtCore.SIGNAL("itemChanged (QTreeWidgetItem *,int)"),
47- self.extMergeListItemChanged)
48-
49- addExtMergeButton = QtGui.QPushButton(gettext("Add"), mergeWidget)
50- self.connect(addExtMergeButton, QtCore.SIGNAL("clicked()"),
51- self.addExtMerge)
52- removeExtMergeButton = QtGui.QPushButton(gettext("Remove"), mergeWidget)
53- self.connect(removeExtMergeButton, QtCore.SIGNAL("clicked()"),
54- self.removeExtMerge)
55-
56- extMergeButtonsLayout = QtGui.QHBoxLayout()
57- extMergeButtonsLayout.addWidget(addExtMergeButton)
58- extMergeButtonsLayout.addWidget(removeExtMergeButton)
59- extMergeButtonsLayout.addStretch()
60-
61- mergeLayout = QtGui.QVBoxLayout(mergeWidget)
62- mergeLayout.addWidget(label)
63- mergeLayout.addWidget(self.extMergeList)
64- mergeLayout.addLayout(extMergeButtonsLayout)
65-
66+ self.merge_ui.setupUi(mergeWidget)
67+ self.merge_ui.merge_tool_details.setEnabled(False)
68+
69+ self.merge_tools_list_model = MergeToolsListModel()
70+ self.merge_ui.merge_tools_list.setModel(
71+ self.merge_tools_list_model)
72+
73+ self.connect(self.merge_ui.merge_tools_list.selectionModel(),
74+ QtCore.SIGNAL("currentChanged(QModelIndex,QModelIndex)"),
75+ self.merge_tools_list_currentChanged)
76+ self.connect(self.merge_tools_list_model,
77+ QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
78+ self.merge_tools_list_dataChanged)
79+
80+ self.connect(self.merge_ui.merge_tools_add,
81+ QtCore.SIGNAL("clicked()"),
82+ self.merge_tools_add_clicked)
83+ self.connect(self.merge_ui.merge_tools_remove,
84+ QtCore.SIGNAL("clicked()"),
85+ self.merge_tools_remove_clicked)
86+ self.connect(self.merge_ui.merge_tools_detect,
87+ QtCore.SIGNAL("clicked()"),
88+ self.merge_tools_detect_clicked)
89+
90+ self.connect(self.merge_ui.merge_tool_name,
91+ QtCore.SIGNAL("editingFinished()"),
92+ self.merge_tool_name_changed)
93+
94+ self.connect(self.merge_ui.merge_tool_commandline,
95+ QtCore.SIGNAL("editingFinished()"),
96+ self.merge_tool_commandline_changed)
97+
98+ self.connect(self.merge_ui.merge_tool_default,
99+ QtCore.SIGNAL("toggled(bool)"),
100+ self.merge_tool_default_toggled)
101+
102+ self.connect(self.merge_ui.merge_tool_browse,
103+ QtCore.SIGNAL("clicked()"),
104+ self.merge_tool_browse_clicked)
105+
106 self.tabwidget.addTab(generalWidget, gettext("General"))
107 self.tabwidget.addTab(aliasesWidget, gettext("Aliases"))
108 self.tabwidget.addTab(bugTrackersWidget, gettext("Bug Trackers"))
109@@ -357,30 +373,10 @@
110 self.extDiffListIgnore = False
111
112 # Merge
113- bzr_config = get_global_config()
114- defaultMerge = bzr_config.get_user_option("external_merge")
115- if defaultMerge is None:
116- defaultMerge = ""
117-
118- self.extMergeListIgnore = True
119- def create_ext_merge_item(definition):
120- item = QtGui.QTreeWidgetItem(self.extMergeList)
121- item.setFlags(QtCore.Qt.ItemIsSelectable |
122- QtCore.Qt.ItemIsEditable |
123- QtCore.Qt.ItemIsEnabled |
124- QtCore.Qt.ItemIsUserCheckable)
125- if definition == defaultMerge:
126- item.setCheckState(0, QtCore.Qt.Checked)
127- else:
128- item.setCheckState(0, QtCore.Qt.Unchecked)
129-
130- item.setText(0, definition)
131- return item
132-
133- for name, value in parser.get('DEFAULT', {}).items():
134- if name == "external_merge":
135- create_ext_merge_item(value)
136- self.extMergeListIgnore = False
137+ definedMergeTools = mergetools.get_merge_tools(config)
138+ defaultMergeTool = mergetools.get_default_merge_tool(config)
139+ self.merge_tools_list_model.set_merge_tools(definedMergeTools, defaultMergeTool)
140+ self.merge_tools_list_model.sort(0, Qt.AscendingOrder)
141
142 def save(self):
143 """Save the configuration."""
144@@ -465,26 +461,20 @@
145 qconfig.set_option('default_diff',
146 defaultDiff)
147
148- # Merge
149- defaultMerge = None
150- for index in range(self.extMergeList.topLevelItemCount()):
151- item = self.extMergeList.topLevelItem(index)
152- definition = unicode(item.text(0))
153- if item.checkState(0) == QtCore.Qt.Checked:
154- defaultMerge = definition
155-
156- set_or_delete_option(parser, 'external_merge',
157- defaultMerge)
158
159 def save_config(config, parser):
160- ensure_config_dir_exists(os.path.dirname(config._get_filename()))
161- f = open(config._get_filename(), 'wb')
162+ ensure_config_dir_exists(os.path.dirname(config.file_name))
163+ f = open(config.file_name, 'wb')
164 parser.write(f)
165 f.close()
166
167 save_config(config, parser)
168 qconfig.save()
169
170+ # Merge
171+ mergetools.set_merge_tools(self.merge_tools_list_model.get_merge_tools())
172+ mergetools.set_default_merge_tool(self.merge_tools_list_model.get_default())
173+
174 def do_accept(self):
175 """Save changes and close the window."""
176 if not self.validate():
177@@ -578,42 +568,80 @@
178 changed_item.setCheckState(0, QtCore.Qt.Checked)
179 self.extDiffListIgnore = False
180
181- def addExtMerge(self):
182- item = QtGui.QTreeWidgetItem(self.extMergeList)
183- item.setFlags(QtCore.Qt.ItemIsSelectable |
184- QtCore.Qt.ItemIsEditable |
185- QtCore.Qt.ItemIsEnabled |
186- QtCore.Qt.ItemIsUserCheckable)
187- item.setCheckState(0, QtCore.Qt.Unchecked)
188- self.extMergeList.setCurrentItem(item)
189- self.extMergeList.editItem(item, 0)
190-
191- def removeExtMerge(self):
192- for item in self.extMergeList.selectedItems():
193- index = self.extMergeList.indexOfTopLevelItem(item)
194- self.extMergeList.takeTopLevelItem(index)
195-
196- def extMergeListItemChanged(self, changed_item, col):
197- if col == 0 and not self.extMergeListIgnore:
198- checked_count = 0
199- for index in range(self.extMergeList.topLevelItemCount()):
200- item = self.extMergeList.topLevelItem(index)
201- if item.checkState(0) == QtCore.Qt.Checked:
202- checked_count += 1
203+ def merge_tools_list_currentChanged(self, curr, prev):
204+ mt = self.merge_tools_list_model.get_merge_tool(curr)
205+ self.merge_ui.merge_tool_name.setText(mt.get_name())
206+ self.merge_ui.merge_tool_commandline.setText(
207+ mt.get_commandline(quote=True))
208+ default = self.merge_tools_list_model.get_default()
209+ self.merge_ui.merge_tool_default.blockSignals(True)
210+ self.merge_ui.merge_tool_default.setChecked(default is mt)
211+ self.merge_ui.merge_tool_default.blockSignals(False)
212+ self.merge_ui.merge_tool_details.setEnabled(True)
213+
214+ def merge_tools_list_dataChanged(self, first, last):
215+ sel_model = self.merge_ui.merge_tools_list.selectionModel()
216+ assert sel_model.hasSelection()
217+ curr = sel_model.currentIndex()
218+ if curr >= first and curr <= last:
219+ self.merge_tools_list_currentChanged(curr, None)
220+
221+ def merge_tools_add_clicked(self):
222+ index = self.merge_tools_list_model.new_merge_tool()
223+ sel_model = self.merge_ui.merge_tools_list.selectionModel()
224+ sel_model.setCurrentIndex(index, QtGui.QItemSelectionModel.ClearAndSelect)
225+ self.merge_ui.merge_tool_name.setFocus(Qt.OtherFocusReason)
226+ self.merge_ui.merge_tool_name.selectAll()
227+
228+ def merge_tools_remove_clicked(self):
229+ sel_model = self.merge_ui.merge_tools_list.selectionModel()
230+ assert sel_model.hasSelection()
231+ self.merge_tools_list_model.remove_merge_tool(sel_model.currentIndex())
232+
233+ def merge_tools_detect_clicked(self):
234+ self.merge_tools_list_model.detect_merge_tools()
235+
236+ def merge_tool_name_changed(self):
237+ sel_model = self.merge_ui.merge_tools_list.selectionModel()
238+ assert sel_model.hasSelection()
239+ text = unicode(self.merge_ui.merge_tool_name.text())
240+ self.merge_tools_list_model.set_merge_tool_name(
241+ sel_model.currentIndex(), text)
242+ self.merge_tools_list_model.sort(0, Qt.AscendingOrder)
243+
244+ def merge_tool_commandline_changed(self):
245+ sel_model = self.merge_ui.merge_tools_list.selectionModel()
246+ assert sel_model.hasSelection()
247+ curr = sel_model.currentIndex()
248+ mt = self.merge_tools_list_model.get_merge_tool(curr)
249+ text = unicode(self.merge_ui.merge_tool_commandline.text())
250+ mt.set_commandline(text)
251+
252+ def merge_tool_default_toggled(self, checked):
253+ sel_model = self.merge_ui.merge_tools_list.selectionModel()
254+ assert sel_model.hasSelection()
255+ if checked:
256+ new_default = self.merge_tools_list_model.get_merge_tool(
257+ sel_model.currentIndex())
258+ if self.merge_tools_list_model.get_default() is not new_default:
259+ self.merge_tools_list_model.set_default(new_default)
260+ else:
261+ self.merge_tools_list_model.set_default(None)
262
263- if checked_count == 0:
264- self.extMergeListIgnore = True
265- changed_item.setCheckState(0, QtCore.Qt.Checked)
266- self.extMergeListIgnore = False
267- elif checked_count > 1:
268- self.extMergeListIgnore = True
269- for index in range(self.extMergeList.topLevelItemCount()):
270- item = self.extMergeList.topLevelItem(index)
271- if item.checkState(0) == QtCore.Qt.Checked:
272- item.setCheckState(0, QtCore.Qt.Unchecked)
273- changed_item.setCheckState(0, QtCore.Qt.Checked)
274- self.extMergeListIgnore = False
275-
276+ def merge_tool_browse_clicked(self):
277+ sel_model = self.merge_ui.merge_tools_list.selectionModel()
278+ assert sel_model.hasSelection()
279+ self.merge_tool_commandline_changed() # force update of model
280+ filename = QtGui.QFileDialog.getOpenFileName(self,
281+ gettext('Select merge tool executable'),
282+ '/')
283+ if filename is not None:
284+ curr = sel_model.currentIndex()
285+ mt = self.merge_tools_list_model.get_merge_tool(curr)
286+ mt.set_executable(unicode(filename))
287+ self.merge_ui.merge_tool_commandline.setText(
288+ mt.get_commandline(quote=True))
289+
290 def browseEditor(self):
291 filename = QtGui.QFileDialog.getOpenFileName(self,
292 gettext('Select editor executable'),
293@@ -698,3 +726,130 @@
294
295 import socket
296 return realname, (username + '@' + socket.gethostname())
297+
298+class MergeToolsListModel(QtCore.QAbstractListModel):
299+ def __init__(self):
300+ super(MergeToolsListModel, self).__init__()
301+ self._merge_tools = []
302+ self._default = None
303+
304+ def get_merge_tools(self):
305+ return self._merge_tools
306+
307+ def set_merge_tools(self, merge_tools, default):
308+ self.beginResetModel()
309+ self._merge_tools = merge_tools
310+ # see set_default for explanation
311+ for mt in self._merge_tools:
312+ if mt == default:
313+ self._default = mt
314+ break
315+ self.endResetModel()
316+
317+ def get_default(self):
318+ return self._default
319+
320+ def set_default(self, new_default):
321+ old_index = None
322+ if self._default is not None:
323+ i = self._merge_tools.index(self._default)
324+ old_index = self.createIndex(i, i)
325+ new_index = None
326+ if new_default is not None:
327+ i = self._merge_tools.index(new_default)
328+ new_index = self.createIndex(i, i)
329+ # new_default may be == to an instance in self._merge_tools but not
330+ # the same instance. To preserve editing semantics in this model,
331+ # self._default must refer to an instance in self._merge_tools, so
332+ # we find the == instance in self._merge_tools and set self._default
333+ # to that instead.
334+ for mt in self._merge_tools:
335+ if mt == default:
336+ self._default = mt
337+ break
338+ else:
339+ self._default = None
340+ if old_index:
341+ self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
342+ old_index, old_index)
343+ if new_index:
344+ self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
345+ new_index, new_index)
346+
347+ def get_merge_tool(self, index):
348+ return self._merge_tools[index.row()]
349+
350+ def index_of(self, tool):
351+ return self.createIndex(self._merge_tools.index(tool), 0)
352+
353+ def new_merge_tool(self):
354+ index = self.createIndex(len(self._merge_tools), 0)
355+ self.beginInsertRows(QtCore.QModelIndex(), index.row(), index.row())
356+ self._merge_tools.append(mergetools.MergeTool(
357+ gettext('New Merge Tool'), ''))
358+ self.endInsertRows()
359+ return index
360+
361+ def remove_merge_tool(self, index):
362+ self.beginRemoveRows(QtCore.QModelIndex(), index.row(), index.row())
363+ if self._merge_tools[index.row()] == self._default:
364+ self._default = None
365+ del self._merge_tools[index.row()]
366+ self.endRemoveRows()
367+
368+ def detect_merge_tools(self):
369+ detected_tools = mergetools.detect_merge_tools()
370+ for mt in detected_tools:
371+ if mt not in self._merge_tools:
372+ row = len(self._merge_tools)
373+ self.beginInsertRows(QtCore.QModelIndex(), row, row)
374+ self._merge_tools.append(mt)
375+ self.endInsertRows()
376+ self.sort(0, Qt.AscendingOrder)
377+
378+ def set_merge_tool_name(self, index, name):
379+ self._merge_tools[index.row()].set_name(name)
380+ self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"), index,
381+ index)
382+
383+ def set_merge_tool_commandline(self, index, commandline):
384+ self._merge_tools[index.row()].set_commandline(commandline)
385+
386+ def set_merge_tool_executable(self, index, executable):
387+ self._merge_tools[index.row()].set_executable(executable)
388+
389+ def rowCount(self, parent):
390+ return len(self._merge_tools)
391+
392+ def data(self, index, role):
393+ if role == Qt.DisplayRole:
394+ name = self._merge_tools[index.row()].get_name()
395+ if self._default is not None and self._default.get_name() == name:
396+ name = '%s (%s)' % (name, gettext('default'))
397+ return QtCore.QVariant(name)
398+ elif role == Qt.EditRole:
399+ return QtCore.QVariant(self._merge_tools[index.row()].get_name())
400+ return QtCore.QVariant()
401+
402+ def setData(self, index, value, role):
403+ if role == Qt.EditRole:
404+ self._merge_tools[index.row()].set_name(unicode(value.toString()))
405+ self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
406+ index,index)
407+ self.sort(0, Qt.AscendingOrder)
408+ return True
409+ return False
410+
411+ def flags(self, index):
412+ return super(MergeToolsListModel, self).flags(index) | Qt.ItemIsEditable
413+
414+ def sort(self, column, sortOrder):
415+ self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
416+ index_map = self._merge_tools[:] # copy
417+ self._merge_tools.sort()
418+ for i in range(0, len(index_map)):
419+ index_map[i] = self._merge_tools.index(index_map[i])
420+ from_list = [self.createIndex(i, 0) for i in index_map]
421+ to_list = [self.createIndex(i, 0) for i in range(0, len(index_map))]
422+ self.changePersistentIndexList(from_list, to_list)
423+ self.emit(QtCore.SIGNAL("layoutChanged()"))
424
425=== modified file 'lib/conflicts.py'
426--- lib/conflicts.py 2010-09-01 07:30:29 +0000
427+++ lib/conflicts.py 2010-11-09 00:10:03 +0000
428@@ -20,6 +20,7 @@
429 from PyQt4 import QtCore, QtGui
430 from bzrlib.config import GlobalConfig
431 from bzrlib.conflicts import resolve
432+from bzrlib import mergetools
433 from bzrlib.workingtree import WorkingTree
434 from bzrlib.plugins.qbzr.lib.i18n import gettext, N_, ngettext
435 from bzrlib.plugins.qbzr.lib.util import (
436@@ -58,7 +59,7 @@
437 self.connect(
438 self.conflicts_list.selectionModel(),
439 QtCore.SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
440- self.update_selection)
441+ self.update_merge_tool_ui)
442 self.connect(
443 self.conflicts_list,
444 QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
445@@ -77,22 +78,14 @@
446 vbox.addWidget(self.conflicts_list)
447
448 hbox = QtGui.QHBoxLayout()
449- self.program_edit = QtGui.QLineEdit(self)
450- self.program_edit.setEnabled(False)
451- self.connect(
452- self.program_edit,
453- QtCore.SIGNAL("textChanged(QString)"),
454- self.check_merge_tool_edit)
455- self.program_extmerge_default_button = QtGui.QCheckBox(gettext("Use Configured Default"))
456- self.program_extmerge_default_button.setToolTip(gettext(
457- "The merge tool configured in qconfig under Merge' file.\n"
458- "It follows the convention used in the bzr plugin: extmerge\n"
459- "external_merge = kdiff3 --output %r %b %t %o\n"
460- "%r is output, %b is .BASE, %t is .THIS and %o is .OTHER file."))
461- self.connect(
462- self.program_extmerge_default_button,
463- QtCore.SIGNAL("clicked()"),
464- self.program_extmerge_default_clicked)
465+ self.merge_tools_combo = QtGui.QComboBox(self)
466+ self.merge_tools_combo.setEditable(False)
467+ self.connect(self.merge_tools_combo,
468+ QtCore.SIGNAL("currentIndexChanged(int)"),
469+ self.update_merge_tool_ui)
470+
471+ self.merge_tool_error = QtGui.QLabel('', self)
472+
473 self.program_launch_button = QtGui.QPushButton(gettext("&Launch..."), self)
474 self.program_launch_button.setEnabled(False)
475 self.connect(
476@@ -100,10 +93,11 @@
477 QtCore.SIGNAL("clicked()"),
478 self.launch_merge_tool)
479 self.program_label = QtGui.QLabel(gettext("M&erge tool:"), self)
480- self.program_label.setBuddy(self.program_edit)
481+ self.program_label.setBuddy(self.merge_tools_combo)
482 hbox.addWidget(self.program_label)
483- hbox.addWidget(self.program_edit)
484- hbox.addWidget(self.program_extmerge_default_button)
485+ hbox.addWidget(self.merge_tools_combo)
486+ hbox.addWidget(self.merge_tool_error)
487+ hbox.addStretch(1)
488 hbox.addWidget(self.program_launch_button)
489 vbox.addLayout(hbox)
490
491@@ -125,21 +119,13 @@
492 self.initialize_ui()
493
494 def initialize_ui(self):
495- merge_tool_extmerge = get_qbzr_config().get_option("merge_tool_extmerge")
496-
497- self.program_extmerge_default_button.setCheckState(QtCore.Qt.Unchecked)
498- if merge_tool_extmerge in ("True", "1"):
499- self.program_extmerge_default_button.setCheckState(QtCore.Qt.Checked)
500- self.program_extmerge_default_clicked()
501- enabled, error_msg = self.is_merge_tool_launchable()
502- self.update_program_edit_text(enabled, error_msg)
503- # if extmerge not configured then resort to using default
504- if not enabled and self.program_extmerge_default_button.isChecked():
505- bzr_config = GlobalConfig()
506- extmerge_tool = bzr_config.get_user_option("external_merge")
507- if not extmerge_tool:
508- self.program_extmerge_default_button.setCheckState(QtCore.Qt.Unchecked)
509- self.update_program_edit_text(False, "")
510+ defined_tools = mergetools.get_merge_tools()
511+ default_tool = mergetools.get_default_merge_tool()
512+ for merge_tool in defined_tools:
513+ self.merge_tools_combo.insertItem(self.merge_tools_combo.count(), merge_tool.get_name())
514+ if default_tool is not None:
515+ self.merge_tools_combo.setCurrentIndex(self.merge_tools_combo.findText(default_tool.get_name()))
516+ self.update_merge_tool_ui()
517
518 def create_context_menu(self):
519 self.context_menu = QtGui.QMenu(self.conflicts_list)
520@@ -190,56 +176,31 @@
521 gettext('&OK'))
522 self.close()
523
524- def update_selection(self, selected, deselected):
525+ def update_merge_tool_ui(self):
526 enabled, error_msg = self.is_merge_tool_launchable()
527- self.program_edit.setEnabled(enabled and not self.program_extmerge_default_button.isChecked())
528+ self.merge_tool_error.setText(error_msg)
529 self.program_launch_button.setEnabled(enabled)
530- self.update_program_edit_text(enabled, error_msg)
531- if enabled and self.program_extmerge_default_button.isChecked():
532- self.program_edit.setEnabled(False)
533 self.merge_action.setEnabled(enabled)
534
535- def check_merge_tool_edit(self, text):
536- enabled, error_msg = self.is_merge_tool_launchable()
537- self.program_launch_button.setEnabled(enabled)
538-
539 def launch_merge_tool(self):
540 items = self.conflicts_list.selectedItems()
541 enabled, error_msg = self.is_merge_tool_launchable()
542 if not enabled:
543 return
544- merge_tool = unicode(self.program_edit.text()).strip()
545- if not merge_tool:
546- return
547+ merge_tool = mergetools.find_merge_tool(self.merge_tools_combo.currentText())
548 file_id = str(items[0].data(0, QtCore.Qt.UserRole).toString())
549 file_name = self.wt.abspath(self.wt.id2path(file_id))
550- base_file_name = file_name + ".BASE"
551- this_file_name = file_name + ".THIS"
552- other_file_name = file_name + ".OTHER"
553- new_args = [base_file_name, this_file_name, other_file_name]
554- config = get_qbzr_config()
555- config.set_option("merge_tool_extmerge", False)
556-
557- if self.program_extmerge_default_button.isChecked():
558- bzr_config = GlobalConfig()
559- extmerge_tool = bzr_config.get_user_option("external_merge")
560- args = cmdline_split(extmerge_tool)
561- new_args = args[1:len(args)]
562- i = 0
563- while i < len(new_args):
564- new_args[i] = new_args[i].replace('%r', file_name)
565- new_args[i] = new_args[i].replace('%o', other_file_name)
566- new_args[i] = new_args[i].replace('%b', base_file_name)
567- new_args[i] = new_args[i].replace('%t', this_file_name)
568- i = i + 1
569- merge_tool = args[0]
570- config.set_option("merge_tool_extmerge", True)
571- else:
572- config.set_option("merge_tool", merge_tool)
573-
574 process = QtCore.QProcess(self)
575- self.connect(process, QtCore.SIGNAL("error(QProcess::ProcessError)"), self.show_merge_tool_error)
576- process.start(merge_tool, new_args)
577+ def qprocess_invoker(executable, args, cleanup):
578+ def qprocess_error(error):
579+ self.show_merge_tool_error(error)
580+ cleanup(process.exitCode())
581+ def qprocess_finished(exit_code, exit_status):
582+ cleanup(exit_code)
583+ self.connect(process, QtCore.SIGNAL("error(QProcess::ProcessError)"), qprocess_error)
584+ self.connect(process, QtCore.SIGNAL("finished(int,QProcess::ExitStatus)"), qprocess_finished)
585+ process.start(executable, args)
586+ merge_tool.invoke(file_name, qprocess_invoker)
587
588 def show_merge_tool_error(self, error):
589 msg = gettext("Error while running merge tool (code %d)") % error
590@@ -261,34 +222,20 @@
591 def show_context_menu(self, pos):
592 self.context_menu.popup(self.conflicts_list.viewport().mapToGlobal(pos))
593
594- def program_extmerge_default_clicked(self):
595- enabled, error_msg = self.is_merge_tool_launchable()
596- self.program_edit.setEnabled(enabled and not self.program_extmerge_default_button.isChecked())
597- self.program_launch_button.setEnabled(enabled)
598- self.update_program_edit_text(enabled, error_msg)
599- config = get_qbzr_config()
600- config.set_option("merge_tool_extmerge",
601- self.program_extmerge_default_button.isChecked())
602-
603 def is_merge_tool_launchable(self):
604 items = self.conflicts_list.selectedItems()
605 error_msg = ""
606 enabled = True
607 if len(items) != 1 or items[0].data(1, QtCore.Qt.UserRole).toString() != "text conflict":
608 enabled = False
609-
610- # check to see if the extmerge config is correct
611- if self.program_extmerge_default_button.isChecked():
612- bzr_config = GlobalConfig()
613- extmerge_tool = bzr_config.get_user_option("external_merge")
614- if not extmerge_tool:
615- error_msg = gettext("Set up external_merge app in qconfig under the Merge tab")
616- enabled = False
617- return enabled, error_msg
618- error = self.is_extmerge_definition_valid(False)
619- if len(error) > 0:
620- enabled = False
621- error_msg = error
622+ merge_tool = mergetools.find_merge_tool(self.merge_tools_combo.currentText())
623+ if merge_tool is None:
624+ error_msg = gettext("Set up external_merge app in qconfig under the Merge tab")
625+ enabled = False
626+ elif not merge_tool.is_available():
627+ enabled = False
628+ error_msg = gettext("External merge tool %(tool)s is not available") % \
629+ { 'tool': merge_tool.get_name() }
630 return enabled, error_msg
631
632 def is_extmerge_definition_valid(self, showErrorDialog):
633@@ -316,17 +263,6 @@
634 return gettext("Missing the flag: %s. Configure in qconfig under the merge tab.") % flags
635 return ""
636
637- def update_program_edit_text(self, enabled, error_msg):
638- if self.program_extmerge_default_button.isChecked():
639- if enabled or (len(error_msg) <= 0):
640- config = GlobalConfig()
641- extmerge = config.get_user_option("external_merge")
642- self.program_edit.setText(gettext("%s (Configured external merge definition in qconfig)") % extmerge)
643- else:
644- self.program_edit.setText(error_msg)
645- else:
646- config = get_qbzr_config()
647- self.program_edit.setText((config.get_user_option("merge_tool") or "").strip() or "meld")
648
649 if 0:
650 N_("path conflict")
651
652=== added file 'lib/ui_merge_config.py'
653--- lib/ui_merge_config.py 1970-01-01 00:00:00 +0000
654+++ lib/ui_merge_config.py 2010-11-09 00:10:03 +0000
655@@ -0,0 +1,97 @@
656+# -*- coding: utf-8 -*-
657+
658+# Form implementation generated from reading ui file 'ui/merge_config.ui'
659+#
660+# Created: Sun Nov 07 17:00:49 2010
661+# by: PyQt4 UI code generator 4.7.2
662+#
663+# WARNING! All changes made in this file will be lost!
664+
665+from PyQt4 import QtCore, QtGui
666+from bzrlib.plugins.qbzr.lib.i18n import gettext
667+
668+
669+class Ui_MergeConfig(object):
670+ def setupUi(self, MergeConfig):
671+ MergeConfig.setObjectName("MergeConfig")
672+ MergeConfig.resize(626, 297)
673+ self.verticalLayout_2 = QtGui.QVBoxLayout(MergeConfig)
674+ self.verticalLayout_2.setObjectName("verticalLayout_2")
675+ self.groupBox = QtGui.QGroupBox(MergeConfig)
676+ self.groupBox.setObjectName("groupBox")
677+ self.verticalLayout_3 = QtGui.QVBoxLayout(self.groupBox)
678+ self.verticalLayout_3.setObjectName("verticalLayout_3")
679+ self.splitter = QtGui.QSplitter(self.groupBox)
680+ self.splitter.setOrientation(QtCore.Qt.Horizontal)
681+ self.splitter.setChildrenCollapsible(False)
682+ self.splitter.setObjectName("splitter")
683+ self.layoutWidget = QtGui.QWidget(self.splitter)
684+ self.layoutWidget.setObjectName("layoutWidget")
685+ self.verticalLayout = QtGui.QVBoxLayout(self.layoutWidget)
686+ self.verticalLayout.setObjectName("verticalLayout")
687+ self.merge_tools_list = QtGui.QListView(self.layoutWidget)
688+ self.merge_tools_list.setObjectName("merge_tools_list")
689+ self.verticalLayout.addWidget(self.merge_tools_list)
690+ self.merge_tools_buttons = QtGui.QWidget(self.layoutWidget)
691+ self.merge_tools_buttons.setObjectName("merge_tools_buttons")
692+ self.horizontalLayout = QtGui.QHBoxLayout(self.merge_tools_buttons)
693+ self.horizontalLayout.setObjectName("horizontalLayout")
694+ spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
695+ self.horizontalLayout.addItem(spacerItem)
696+ self.merge_tools_add = QtGui.QPushButton(self.merge_tools_buttons)
697+ self.merge_tools_add.setObjectName("merge_tools_add")
698+ self.horizontalLayout.addWidget(self.merge_tools_add)
699+ self.merge_tools_remove = QtGui.QPushButton(self.merge_tools_buttons)
700+ self.merge_tools_remove.setObjectName("merge_tools_remove")
701+ self.horizontalLayout.addWidget(self.merge_tools_remove)
702+ self.merge_tools_detect = QtGui.QPushButton(self.merge_tools_buttons)
703+ self.merge_tools_detect.setObjectName("merge_tools_detect")
704+ self.horizontalLayout.addWidget(self.merge_tools_detect)
705+ spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
706+ self.horizontalLayout.addItem(spacerItem1)
707+ self.verticalLayout.addWidget(self.merge_tools_buttons)
708+ self.merge_tool_details = QtGui.QWidget(self.splitter)
709+ self.merge_tool_details.setMinimumSize(QtCore.QSize(300, 0))
710+ self.merge_tool_details.setObjectName("merge_tool_details")
711+ self.gridLayout = QtGui.QGridLayout(self.merge_tool_details)
712+ self.gridLayout.setContentsMargins(-1, 0, -1, 0)
713+ self.gridLayout.setObjectName("gridLayout")
714+ self.label = QtGui.QLabel(self.merge_tool_details)
715+ self.label.setObjectName("label")
716+ self.gridLayout.addWidget(self.label, 0, 0, 1, 1)
717+ self.merge_tool_name = QtGui.QLineEdit(self.merge_tool_details)
718+ self.merge_tool_name.setObjectName("merge_tool_name")
719+ self.gridLayout.addWidget(self.merge_tool_name, 1, 0, 1, 1)
720+ self.label_2 = QtGui.QLabel(self.merge_tool_details)
721+ self.label_2.setObjectName("label_2")
722+ self.gridLayout.addWidget(self.label_2, 2, 0, 1, 1)
723+ self.merge_tool_commandline = QtGui.QLineEdit(self.merge_tool_details)
724+ self.merge_tool_commandline.setObjectName("merge_tool_commandline")
725+ self.gridLayout.addWidget(self.merge_tool_commandline, 3, 0, 1, 1)
726+ self.merge_tool_browse = QtGui.QToolButton(self.merge_tool_details)
727+ self.merge_tool_browse.setObjectName("merge_tool_browse")
728+ self.gridLayout.addWidget(self.merge_tool_browse, 3, 1, 1, 1)
729+ spacerItem2 = QtGui.QSpacerItem(248, 130, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
730+ self.gridLayout.addItem(spacerItem2, 6, 0, 1, 1)
731+ self.merge_tool_default = QtGui.QCheckBox(self.merge_tool_details)
732+ self.merge_tool_default.setObjectName("merge_tool_default")
733+ self.gridLayout.addWidget(self.merge_tool_default, 4, 0, 1, 1)
734+ self.verticalLayout_3.addWidget(self.splitter)
735+ self.verticalLayout_2.addWidget(self.groupBox)
736+ self.label.setBuddy(self.merge_tool_name)
737+ self.label_2.setBuddy(self.merge_tool_commandline)
738+
739+ self.retranslateUi(MergeConfig)
740+ QtCore.QMetaObject.connectSlotsByName(MergeConfig)
741+
742+ def retranslateUi(self, MergeConfig):
743+ MergeConfig.setWindowTitle(gettext("External Merge Tools"))
744+ self.groupBox.setTitle(gettext("External Merge Tools"))
745+ self.merge_tools_add.setText(gettext("Add"))
746+ self.merge_tools_remove.setText(gettext("Remove"))
747+ self.merge_tools_detect.setText(gettext("Detect"))
748+ self.label.setText(gettext("Name:"))
749+ self.label_2.setText(gettext("Command line:"))
750+ self.merge_tool_browse.setText(gettext("..."))
751+ self.merge_tool_default.setText(gettext("Default"))
752+
753
754=== added file 'ui/merge_config.ui'
755--- ui/merge_config.ui 1970-01-01 00:00:00 +0000
756+++ ui/merge_config.ui 2010-11-09 00:10:03 +0000
757@@ -0,0 +1,169 @@
758+<?xml version="1.0" encoding="UTF-8"?>
759+<ui version="4.0">
760+ <class>MergeConfig</class>
761+ <widget class="QWidget" name="MergeConfig">
762+ <property name="geometry">
763+ <rect>
764+ <x>0</x>
765+ <y>0</y>
766+ <width>626</width>
767+ <height>297</height>
768+ </rect>
769+ </property>
770+ <property name="windowTitle">
771+ <string>External Merge Tools</string>
772+ </property>
773+ <layout class="QVBoxLayout" name="verticalLayout_2">
774+ <item>
775+ <widget class="QGroupBox" name="groupBox">
776+ <property name="title">
777+ <string>External Merge Tools</string>
778+ </property>
779+ <layout class="QVBoxLayout" name="verticalLayout_3">
780+ <item>
781+ <widget class="QSplitter" name="splitter">
782+ <property name="orientation">
783+ <enum>Qt::Horizontal</enum>
784+ </property>
785+ <property name="childrenCollapsible">
786+ <bool>false</bool>
787+ </property>
788+ <widget class="QWidget" name="layoutWidget">
789+ <layout class="QVBoxLayout" name="verticalLayout">
790+ <item>
791+ <widget class="QListView" name="merge_tools_list"/>
792+ </item>
793+ <item>
794+ <widget class="QWidget" name="merge_tools_buttons" native="true">
795+ <layout class="QHBoxLayout" name="horizontalLayout">
796+ <item>
797+ <spacer name="spacer">
798+ <property name="orientation">
799+ <enum>Qt::Horizontal</enum>
800+ </property>
801+ <property name="sizeHint" stdset="0">
802+ <size>
803+ <width>40</width>
804+ <height>20</height>
805+ </size>
806+ </property>
807+ </spacer>
808+ </item>
809+ <item>
810+ <widget class="QPushButton" name="merge_tools_add">
811+ <property name="text">
812+ <string>Add</string>
813+ </property>
814+ </widget>
815+ </item>
816+ <item>
817+ <widget class="QPushButton" name="merge_tools_remove">
818+ <property name="text">
819+ <string>Remove</string>
820+ </property>
821+ </widget>
822+ </item>
823+ <item>
824+ <widget class="QPushButton" name="merge_tools_detect">
825+ <property name="text">
826+ <string>Detect</string>
827+ </property>
828+ </widget>
829+ </item>
830+ <item>
831+ <spacer name="spacer_2">
832+ <property name="orientation">
833+ <enum>Qt::Horizontal</enum>
834+ </property>
835+ <property name="sizeHint" stdset="0">
836+ <size>
837+ <width>40</width>
838+ <height>20</height>
839+ </size>
840+ </property>
841+ </spacer>
842+ </item>
843+ </layout>
844+ </widget>
845+ </item>
846+ </layout>
847+ </widget>
848+ <widget class="QWidget" name="merge_tool_details" native="true">
849+ <property name="minimumSize">
850+ <size>
851+ <width>300</width>
852+ <height>0</height>
853+ </size>
854+ </property>
855+ <layout class="QGridLayout" name="gridLayout">
856+ <property name="topMargin">
857+ <number>0</number>
858+ </property>
859+ <property name="bottomMargin">
860+ <number>0</number>
861+ </property>
862+ <item row="0" column="0">
863+ <widget class="QLabel" name="label">
864+ <property name="text">
865+ <string>Name:</string>
866+ </property>
867+ <property name="buddy">
868+ <cstring>merge_tool_name</cstring>
869+ </property>
870+ </widget>
871+ </item>
872+ <item row="1" column="0">
873+ <widget class="QLineEdit" name="merge_tool_name"/>
874+ </item>
875+ <item row="2" column="0">
876+ <widget class="QLabel" name="label_2">
877+ <property name="text">
878+ <string>Command line:</string>
879+ </property>
880+ <property name="buddy">
881+ <cstring>merge_tool_commandline</cstring>
882+ </property>
883+ </widget>
884+ </item>
885+ <item row="3" column="0">
886+ <widget class="QLineEdit" name="merge_tool_commandline"/>
887+ </item>
888+ <item row="3" column="1">
889+ <widget class="QToolButton" name="merge_tool_browse">
890+ <property name="text">
891+ <string>...</string>
892+ </property>
893+ </widget>
894+ </item>
895+ <item row="6" column="0">
896+ <spacer name="spacer_3">
897+ <property name="orientation">
898+ <enum>Qt::Vertical</enum>
899+ </property>
900+ <property name="sizeHint" stdset="0">
901+ <size>
902+ <width>248</width>
903+ <height>130</height>
904+ </size>
905+ </property>
906+ </spacer>
907+ </item>
908+ <item row="4" column="0">
909+ <widget class="QCheckBox" name="merge_tool_default">
910+ <property name="text">
911+ <string>Default</string>
912+ </property>
913+ </widget>
914+ </item>
915+ </layout>
916+ </widget>
917+ </widget>
918+ </item>
919+ </layout>
920+ </widget>
921+ </item>
922+ </layout>
923+ </widget>
924+ <resources/>
925+ <connections/>
926+</ui>

Subscribers

People subscribed via source and target branches