Merge lp:~qbzr-dev/qbzr/mergetools into lp:qbzr

Proposed by Gordon Tyler
Status: Merged
Merged at revision: 1401
Proposed branch: lp:~qbzr-dev/qbzr/mergetools
Merge into: lp:qbzr
Diff against target: 893 lines (+530/-204)
5 files modified
NEWS.txt (+2/-0)
lib/config.py (+276/-98)
lib/conflicts.py (+62/-106)
lib/ui_merge_config.py (+64/-0)
ui/merge_config.ui (+126/-0)
To merge this branch: bzr merge lp:~qbzr-dev/qbzr/mergetools
Reviewer Review Type Date Requested Status
Alexander Belchenko Approve
Review via email: mp+40426@code.launchpad.net

Description of the change

This supersedes https://code.launchpad.net/~doxxx/qbzr/mergetools/+merge/38664.

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 an external merge tool selector 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 :

I've redone the UI for the Merge tab to use a table instead of the list + edit form. I still need to figure out how to get a browse button into the cell for the command-line. I think have to create a custom editor widget. Failing that, I can add it to the button box below the table.

Revision history for this message
Martitza (martitzam) wrote :

After reading the notes for this MP and the one which it supersedes, I want to say I admire the objectives and quality of the discussion. I would also like to say tha the implementation as it was a month ago was already a huge improvement and usable in the field. Getting *something* out there may be the fastest path to continuous improvement.

~M

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

Just some notes for myself, still need to look into these points raised by Gary on the previous mp:

* 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 be nice to give help on then merge parameters (%b %T %o, etc..). Maybe a link to qhelp, like the qgetnew dialog.

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

I've been looking at the way the old merge tool system worked in qbzr and, quite frankly, it's a mess. How important is compatibility with older versions of bzr?

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

On Tue, Feb 1, 2011 at 7:01 AM, Gordon Tyler <email address hidden> wrote:
> I've been looking at the way the old merge tool system worked in qbzr and, quite frankly, it's a mess. How important is compatibility with older versions of bzr?

Not important. I just would like for qbzr to fail gracefully when run
with older bzr's

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

On 2/1/2011 2:43 AM, Gary van der Merwe wrote:
> On Tue, Feb 1, 2011 at 7:01 AM, Gordon Tyler <email address hidden> wrote:
>> I've been looking at the way the old merge tool system worked in qbzr and, quite frankly, it's a mess. How important is compatibility with older versions of bzr?
>
> Not important. I just would like for qbzr to fail gracefully when run
> with older bzr's

Okay. Failing gracefully I can do. :)

Revision history for this message
Alexander Belchenko (bialix) wrote :

What's the status of this work now? What should be done?

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

It needs to fail gracefully in the absence of bzr mergetools module.

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

3 months later :) I've made the changes to have qconfig and qconflicts fail gracefully when bzr lacks the mergetools module. Both display a message in the UI to the effect that bzr 2.4 or newer is required for external mergetools support.

Is it too late to get this into the qbzr release for bzr 2.4?

Revision history for this message
Alexander Belchenko (bialix) wrote :

10.05.2011 2:16, Gordon Tyler пишет:
> 3 months later :) I've made the changes to have qconfig and qconflicts fail gracefully when bzr lacks the mergetools module. Both display a message in the UI to the effect that bzr 2.4 or newer is required for external mergetools support.
>
> Is it too late to get this into the qbzr release for bzr 2.4?

qbzr 0.21 is still in beta phase. Not, it's not late yet.

--
All the dude wanted was his rug back

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

Do you need anything more for this, Alex?

Revision history for this message
Alexander Belchenko (bialix) wrote :

27.05.2011 23:39, Gordon Tyler пишет:
> Do you need anything more for this, Alex?

More hours in the day, please.

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

On 5/28/2011 4:58 AM, Alexander Belchenko wrote:
> 27.05.2011 23:39, Gordon Tyler пишет:
>> Do you need anything more for this, Alex?
> More hours in the day, please.

Heh. No worries. Just checking that I hadn't forgotten something.

Revision history for this message
Alexander Belchenko (bialix) wrote :

Thank you for your patience. It's landed now.

review: Approve

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 2011-05-03 09:21:56 +0000
3+++ NEWS.txt 2011-05-09 23:16:29 +0000
4@@ -119,6 +119,8 @@
5 message. (Gary van der Merwe)
6 * qpush: fixed smart suggestion of new push URLs for lp branches.
7 (Bug #710767, Alexander Belchenko)
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 - 2010/11/26
13
14=== modified file 'lib/config.py'
15--- lib/config.py 2011-04-26 08:26:28 +0000
16+++ lib/config.py 2011-05-09 23:16:29 +0000
17@@ -26,6 +26,7 @@
18 )
19 from bzrlib import errors, trace
20
21+from bzrlib.plugins.qbzr.lib import ui_merge_config
22 from bzrlib.plugins.qbzr.lib.i18n import gettext, N_
23 from bzrlib.plugins.qbzr.lib.spellcheck import SpellChecker
24 from bzrlib.plugins.qbzr.lib.util import (
25@@ -38,6 +39,11 @@
26 get_tab_width_chars,
27 )
28
29+try:
30+ from bzrlib import mergetools
31+except ImportError:
32+ mergetools = None
33+
34
35 _mail_clients = [
36 ('default', N_('Default')),
37@@ -200,33 +206,37 @@
38 diffLayout.addWidget(self.extDiffList)
39 diffLayout.addLayout(extDiffButtonsLayout)
40
41- mergeWidget = QtGui.QWidget()
42-
43- label = QtGui.QLabel(gettext("External Merge Apps:"))
44- self.extMergeList = QtGui.QTreeWidget(mergeWidget)
45- self.extMergeList.setRootIsDecorated(False)
46- self.extMergeList.setHeaderLabels([gettext("Definition")])
47- self.extMergeList.setItemDelegateForColumn(0,
48- QRadioCheckItemDelegate(self.extMergeList))
49- self.connect(self.extMergeList, QtCore.SIGNAL("itemChanged (QTreeWidgetItem *,int)"),
50- self.extMergeListItemChanged)
51-
52- addExtMergeButton = QtGui.QPushButton(gettext("Add"), mergeWidget)
53- self.connect(addExtMergeButton, QtCore.SIGNAL("clicked()"),
54- self.addExtMerge)
55- removeExtMergeButton = QtGui.QPushButton(gettext("Remove"), mergeWidget)
56- self.connect(removeExtMergeButton, QtCore.SIGNAL("clicked()"),
57- self.removeExtMerge)
58-
59- extMergeButtonsLayout = QtGui.QHBoxLayout()
60- extMergeButtonsLayout.addWidget(addExtMergeButton)
61- extMergeButtonsLayout.addWidget(removeExtMergeButton)
62- extMergeButtonsLayout.addStretch()
63-
64- mergeLayout = QtGui.QVBoxLayout(mergeWidget)
65- mergeLayout.addWidget(label)
66- mergeLayout.addWidget(self.extMergeList)
67- mergeLayout.addLayout(extMergeButtonsLayout)
68+ if mergetools is not None:
69+ mergeWidget = QtGui.QWidget()
70+ self.merge_ui = ui_merge_config.Ui_MergeConfig()
71+ self.merge_ui.setupUi(mergeWidget)
72+ self.merge_ui.tools.sortByColumn(0, QtCore.Qt.AscendingOrder)
73+ self.merge_ui.remove.setEnabled(False)
74+ self.merge_ui.set_default.setEnabled(False)
75+
76+ self.merge_tools_model = MergeToolsTableModel()
77+ self.merge_ui.tools.setModel(self.merge_tools_model)
78+
79+ self.connect(self.merge_tools_model,
80+ QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
81+ self.merge_tools_data_changed)
82+
83+ self.connect(self.merge_ui.tools.selectionModel(),
84+ QtCore.SIGNAL("selectionChanged(QItemSelection,QItemSelection)"),
85+ self.merge_tools_selectionChanged)
86+
87+ self.connect(self.merge_ui.add,
88+ QtCore.SIGNAL("clicked()"),
89+ self.merge_tools_add_clicked)
90+ self.connect(self.merge_ui.remove,
91+ QtCore.SIGNAL("clicked()"),
92+ self.merge_tools_remove_clicked)
93+ self.connect(self.merge_ui.set_default,
94+ QtCore.SIGNAL("clicked()"),
95+ self.merge_tools_set_default_clicked)
96+ else:
97+ mergeWidget = QtGui.QLabel(gettext("Bazaar 2.4 or newer is required to configure mergetools."))
98+ mergeWidget.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
99
100 self.tabwidget.addTab(generalWidget, gettext("General"))
101 self.tabwidget.addTab(aliasesWidget, gettext("Aliases"))
102@@ -372,30 +382,12 @@
103 self.extDiffListIgnore = False
104
105 # Merge
106- bzr_config = get_global_config()
107- defaultMerge = bzr_config.get_user_option("external_merge")
108- if defaultMerge is None:
109- defaultMerge = ""
110-
111- self.extMergeListIgnore = True
112- def create_ext_merge_item(definition):
113- item = QtGui.QTreeWidgetItem(self.extMergeList)
114- item.setFlags(QtCore.Qt.ItemIsSelectable |
115- QtCore.Qt.ItemIsEditable |
116- QtCore.Qt.ItemIsEnabled |
117- QtCore.Qt.ItemIsUserCheckable)
118- if definition == defaultMerge:
119- item.setCheckState(0, QtCore.Qt.Checked)
120- else:
121- item.setCheckState(0, QtCore.Qt.Unchecked)
122-
123- item.setText(0, definition)
124- return item
125-
126- for name, value in parser.get('DEFAULT', {}).items():
127- if name == "external_merge":
128- create_ext_merge_item(value)
129- self.extMergeListIgnore = False
130+ if mergetools is not None:
131+ user_merge_tools = config.get_merge_tools()
132+ default_merge_tool = config.get_user_option('bzr.default_mergetool')
133+ self.merge_tools_model.set_merge_tools(user_merge_tools,
134+ mergetools.known_merge_tools, default_merge_tool)
135+ self.merge_tools_model.sort(0, QtCore.Qt.AscendingOrder)
136
137 def save(self):
138 """Save the configuration."""
139@@ -483,16 +475,6 @@
140 qconfig.set_option('default_diff',
141 defaultDiff)
142
143- # Merge
144- defaultMerge = None
145- for index in range(self.extMergeList.topLevelItemCount()):
146- item = self.extMergeList.topLevelItem(index)
147- definition = unicode(item.text(0))
148- if item.checkState(0) == QtCore.Qt.Checked:
149- defaultMerge = definition
150-
151- set_or_delete_option(parser, 'external_merge',
152- defaultMerge)
153
154 if hasattr(config, 'file_name'):
155 file_name = config.file_name
156@@ -505,6 +487,19 @@
157
158 qconfig.save()
159
160+ # Merge
161+ if mergetools is not None:
162+ for name in self.merge_tools_model.get_removed_merge_tools():
163+ config.remove_user_option('bzr.mergetool.%s' % name)
164+ user_merge_tools = self.merge_tools_model.get_user_merge_tools()
165+ for name, cmdline in user_merge_tools.iteritems():
166+ orig_cmdline = config.find_merge_tool(name)
167+ if orig_cmdline is None or orig_cmdline != cmdline:
168+ config.set_user_option('bzr.mergetool.%s' % name, cmdline)
169+ default_mt = self.merge_tools_model.get_default()
170+ if default_mt is not None:
171+ config.set_user_option('bzr.default_mergetool', default_mt)
172+
173 def do_accept(self):
174 """Save changes and close the window."""
175 if not self.validate():
176@@ -597,43 +592,43 @@
177 item.setCheckState(0, QtCore.Qt.Unchecked)
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-
204- if checked_count == 0:
205- self.extMergeListIgnore = True
206- changed_item.setCheckState(0, QtCore.Qt.Checked)
207- self.extMergeListIgnore = False
208- elif checked_count > 1:
209- self.extMergeListIgnore = True
210- for index in range(self.extMergeList.topLevelItemCount()):
211- item = self.extMergeList.topLevelItem(index)
212- if item.checkState(0) == QtCore.Qt.Checked:
213- item.setCheckState(0, QtCore.Qt.Unchecked)
214- changed_item.setCheckState(0, QtCore.Qt.Checked)
215- self.extMergeListIgnore = False
216-
217+
218+ def get_selected_merge_tool(self):
219+ sel_model = self.merge_ui.tools.selectionModel()
220+ if len(sel_model.selectedRows()) == 0:
221+ return None
222+ row = sel_model.selectedRows()[0].row()
223+ return self.merge_tools_model.get_merge_tool_name(row)
224+
225+ def update_buttons(self):
226+ selected = self.get_selected_merge_tool()
227+ self.merge_ui.remove.setEnabled(selected is not None and
228+ self.merge_tools_model.is_user_merge_tool(selected))
229+ self.merge_ui.set_default.setEnabled(selected is not None and
230+ self.merge_tools_model.get_default() != selected)
231+
232+ def merge_tools_data_changed(self, top_left, bottom_right):
233+ self.update_buttons()
234+
235+ def merge_tools_selectionChanged(self, selected, deselected):
236+ self.update_buttons()
237+
238+ def merge_tools_add_clicked(self):
239+ index = self.merge_tools_model.new_merge_tool()
240+ sel_model = self.merge_ui.tools.selectionModel()
241+ sel_model.select(index, QtGui.QItemSelectionModel.ClearAndSelect)
242+ self.merge_ui.tools.edit(index)
243+
244+ def merge_tools_remove_clicked(self):
245+ sel_model = self.merge_ui.tools.selectionModel()
246+ assert len(sel_model.selectedRows()) > 0
247+ for index in sel_model.selectedRows():
248+ self.merge_tools_model.remove_merge_tool(index.row())
249+
250+ def merge_tools_set_default_clicked(self):
251+ sel_model = self.merge_ui.tools.selectionModel()
252+ self.merge_tools_model.set_default(self.get_selected_merge_tool().name)
253+
254 def browseEditor(self):
255 filename = QtGui.QFileDialog.getOpenFileName(self,
256 gettext('Select editor executable'),
257@@ -718,3 +713,186 @@
258
259 import socket
260 return realname, (username + '@' + socket.gethostname())
261+
262+
263+class MergeToolsTableModel(QtCore.QAbstractTableModel):
264+ COL_NAME = 0
265+ COL_COMMANDLINE = 1
266+ COL_COUNT = 2
267+
268+ def __init__(self):
269+ super(MergeToolsTableModel, self).__init__()
270+ self._order = []
271+ self._user = {}
272+ self._known = {}
273+ self._default = None
274+ self._removed = []
275+
276+ def get_user_merge_tools(self):
277+ return self._user
278+
279+ def set_merge_tools(self, user, known, default):
280+ self.beginResetModel()
281+ self._user = user
282+ self._known = known
283+ self._order = user.keys() + known.keys()
284+ self._default = default
285+ self.endResetModel()
286+
287+ def get_default(self):
288+ return self._default
289+
290+ def set_default(self, new_default):
291+ old_row = None
292+ if self._default is not None:
293+ old_row = self._order.index(self._default)
294+ new_row = None
295+ if new_default is not None:
296+ new_row = self._order.index(new_default)
297+ self._default = new_default
298+ else:
299+ self._default = None
300+ if old_row is not None:
301+ self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
302+ self.index(old_row, self.COL_NAME),
303+ self.index(old_row, self.COL_NAME))
304+ if new_row is not None:
305+ self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
306+ self.index(new_row, self.COL_NAME),
307+ self.index(new_row, self.COL_NAME))
308+
309+ def get_removed_merge_tools(self):
310+ return self._removed
311+
312+ def get_merge_tool_name(self, row):
313+ return self._order[row]
314+
315+ def get_merge_tool_command_line(self, row):
316+ name = self._order[row]
317+ return self._user.get(name, self._known.get(name, None))
318+
319+ def is_user_merge_tool(self, name):
320+ return name in self._user
321+
322+ def new_merge_tool(self):
323+ index = self.createIndex(len(self._order), 0)
324+ self.beginInsertRows(QtCore.QModelIndex(), index.row(), index.row())
325+ self._order.append('')
326+ self._user[''] = ''
327+ self.endInsertRows()
328+ return index
329+
330+ def remove_merge_tool(self, row):
331+ self.beginRemoveRows(QtCore.QModelIndex(), row, row)
332+ name = self._order[row]
333+ if name not in self._user:
334+ return
335+ self._removed.append(name)
336+ if name == self._default:
337+ self._default = None
338+ del self._order[row]
339+ del self._user[name]
340+ self.endRemoveRows()
341+
342+ def rowCount(self, parent):
343+ return len(self._order)
344+
345+ def columnCount(self, parent):
346+ return self.COL_COUNT
347+
348+ def data(self, index, role):
349+ name = self._order[index.row()]
350+ cmdline = self.get_merge_tool_command_line(index.row())
351+ if role == QtCore.Qt.DisplayRole:
352+ if index.column() == self.COL_NAME:
353+ return QtCore.QVariant(name)
354+ elif index.column() == self.COL_COMMANDLINE:
355+ return QtCore.QVariant(cmdline)
356+ elif role == QtCore.Qt.EditRole:
357+ if index.column() == self.COL_NAME:
358+ return QtCore.QVariant(name)
359+ elif index.column() == self.COL_COMMANDLINE:
360+ return QtCore.QVariant(cmdline)
361+ elif role == QtCore.Qt.CheckStateRole:
362+ if index.column() == self.COL_NAME:
363+ return self._default == name and QtCore.Qt.Checked or QtCore.Qt.Unchecked
364+ elif role == QtCore.Qt.BackgroundRole:
365+ if name in self._known:
366+ palette = QtGui.QApplication.palette()
367+ return palette.alternateBase()
368+ return QtCore.QVariant()
369+
370+ def setData(self, index, value, role):
371+ name = self._order[index.row()]
372+ if role == QtCore.Qt.EditRole:
373+ if index.column() == self.COL_NAME:
374+ # To properly update the config, renaming a merge tool must be
375+ # handled as a remove and add.
376+ cmdline = self.get_merge_tool_command_line(index.row())
377+ self._removed.append(name)
378+ del self._order[index.row()]
379+ del self._user[name]
380+ new_name = unicode(value.toString())
381+ self._order.insert(index.row(), new_name)
382+ self._user[new_name] = cmdline
383+ if self._default == name:
384+ self._default = new_name
385+ self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
386+ index, index)
387+ self.sort(self.COL_NAME, QtCore.Qt.AscendingOrder)
388+ return True
389+ elif index.column() == self.COL_COMMANDLINE:
390+ self._user[name] = unicode(value.toString())
391+ self.emit(QtCore.SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
392+ index, index)
393+ return True
394+ elif role == QtCore.Qt.CheckStateRole:
395+ if index.column() == self.COL_NAME:
396+ if value.toInt() == (QtCore.Qt.Checked, True):
397+ self.set_default(name)
398+ elif (value.toInt() == (QtCore.Qt.Unchecked, True) and
399+ self._default == name):
400+ self.set_default(None)
401+ return False
402+
403+ def flags(self, index):
404+ f = super(MergeToolsTableModel, self).flags(index)
405+ name = self._order[index.row()]
406+ if name not in self._known:
407+ f = f | QtCore.Qt.ItemIsEditable
408+ if index.column() == self.COL_NAME:
409+ f = f | QtCore.Qt.ItemIsUserCheckable
410+ return f
411+
412+ def headerData(self, section, orientation, role):
413+ if orientation == QtCore.Qt.Horizontal:
414+ if section == self.COL_NAME:
415+ if role == QtCore.Qt.DisplayRole:
416+ return QtCore.QVariant(gettext("Name"))
417+ elif section == self.COL_COMMANDLINE:
418+ if role == QtCore.Qt.DisplayRole:
419+ return QtCore.QVariant(gettext("Command Line"))
420+ return QtCore.QVariant()
421+
422+ def sort(self, column, sortOrder):
423+ self.emit(QtCore.SIGNAL("layoutAboutToBeChanged()"))
424+ index_map = self._order[:] # copy
425+ def tool_cmp(a, b):
426+ if column == self.COL_NAME:
427+ return cmp(a, b)
428+ elif column == self.COL_COMMANDLINE:
429+ return cmp(self.get_merge_tool_command_line(a),
430+ self.get_merge_tool_command_line(b))
431+ return 0
432+ self._order.sort(cmp=tool_cmp,
433+ reverse=sortOrder==QtCore.Qt.DescendingOrder)
434+ for i in range(0, len(index_map)):
435+ index_map[i] = self._order.index(index_map[i])
436+ from_list = []
437+ to_list = []
438+ for col in range(0, self.columnCount(None)):
439+ from_list.extend([self.index(i, col) for i in index_map])
440+ to_list.extend([self.index(i, col)
441+ for i in range(0, len(index_map))])
442+ self.changePersistentIndexList(from_list, to_list)
443+ self.emit(QtCore.SIGNAL("layoutChanged()"))
444
445=== modified file 'lib/conflicts.py'
446--- lib/conflicts.py 2011-04-29 13:51:57 +0000
447+++ lib/conflicts.py 2011-05-09 23:16:29 +0000
448@@ -34,6 +34,11 @@
449 except ImportError:
450 from bzrlib.commands import shlex_split_unicode as cmdline_split
451
452+try:
453+ from bzrlib import mergetools
454+except ImportError:
455+ mergetools = None
456+
457
458 class ConflictsWindow(QBzrWindow):
459
460@@ -58,7 +63,7 @@
461 self.connect(
462 self.conflicts_list.selectionModel(),
463 QtCore.SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
464- self.update_selection)
465+ self.update_merge_tool_ui)
466 self.connect(
467 self.conflicts_list,
468 QtCore.SIGNAL("customContextMenuRequested(QPoint)"),
469@@ -77,22 +82,14 @@
470 vbox.addWidget(self.conflicts_list)
471
472 hbox = QtGui.QHBoxLayout()
473- self.program_edit = QtGui.QLineEdit(self)
474- self.program_edit.setEnabled(False)
475- self.connect(
476- self.program_edit,
477- QtCore.SIGNAL("textChanged(QString)"),
478- self.check_merge_tool_edit)
479- self.program_extmerge_default_button = QtGui.QCheckBox(gettext("Use Configured Default"))
480- self.program_extmerge_default_button.setToolTip(gettext(
481- "The merge tool configured in qconfig under Merge' file.\n"
482- "It follows the convention used in the bzr plugin: extmerge\n"
483- "external_merge = kdiff3 --output %r %b %t %o\n"
484- "%r is output, %b is .BASE, %t is .THIS and %o is .OTHER file."))
485- self.connect(
486- self.program_extmerge_default_button,
487- QtCore.SIGNAL("clicked()"),
488- self.program_extmerge_default_clicked)
489+ self.merge_tools_combo = QtGui.QComboBox(self)
490+ self.merge_tools_combo.setEditable(False)
491+ self.connect(self.merge_tools_combo,
492+ QtCore.SIGNAL("currentIndexChanged(int)"),
493+ self.update_merge_tool_ui)
494+
495+ self.merge_tool_error = QtGui.QLabel('', self)
496+
497 self.program_launch_button = QtGui.QPushButton(gettext("&Launch..."), self)
498 self.program_launch_button.setEnabled(False)
499 self.connect(
500@@ -100,10 +97,11 @@
501 QtCore.SIGNAL("clicked()"),
502 self.launch_merge_tool)
503 self.program_label = QtGui.QLabel(gettext("M&erge tool:"), self)
504- self.program_label.setBuddy(self.program_edit)
505+ self.program_label.setBuddy(self.merge_tools_combo)
506 hbox.addWidget(self.program_label)
507- hbox.addWidget(self.program_edit)
508- hbox.addWidget(self.program_extmerge_default_button)
509+ hbox.addWidget(self.merge_tools_combo)
510+ hbox.addWidget(self.merge_tool_error)
511+ hbox.addStretch(1)
512 hbox.addWidget(self.program_launch_button)
513 vbox.addLayout(hbox)
514
515@@ -125,21 +123,24 @@
516 self.initialize_ui()
517
518 def initialize_ui(self):
519- merge_tool_extmerge = get_qbzr_config().get_option("merge_tool_extmerge")
520-
521- self.program_extmerge_default_button.setCheckState(QtCore.Qt.Unchecked)
522- if merge_tool_extmerge in ("True", "1"):
523- self.program_extmerge_default_button.setCheckState(QtCore.Qt.Checked)
524- self.program_extmerge_default_clicked()
525- enabled, error_msg = self.is_merge_tool_launchable()
526- self.update_program_edit_text(enabled, error_msg)
527- # if extmerge not configured then resort to using default
528- if not enabled and self.program_extmerge_default_button.isChecked():
529- bzr_config = GlobalConfig()
530- extmerge_tool = bzr_config.get_user_option("external_merge")
531- if not extmerge_tool:
532- self.program_extmerge_default_button.setCheckState(QtCore.Qt.Unchecked)
533- self.update_program_edit_text(False, "")
534+ config = GlobalConfig()
535+ if mergetools is not None:
536+ # get user-defined merge tools
537+ defined_tools = config.get_merge_tools().keys()
538+ # get predefined merge tools
539+ defined_tools += mergetools.known_merge_tools.keys()
540+ # sort them nicely
541+ defined_tools.sort()
542+ for merge_tool in defined_tools:
543+ self.merge_tools_combo.insertItem(
544+ self.merge_tools_combo.count(), merge_tool)
545+ default_tool = config.get_user_option('bzr.default_mergetool')
546+ if default_tool is not None:
547+ self.merge_tools_combo.setCurrentIndex(
548+ self.merge_tools_combo.findText(default_tool))
549+ # update_merge_tool_ui invokes is_merge_tool_launchable, which displays
550+ # error message if mergetools module is not available.
551+ self.update_merge_tool_ui()
552
553 def create_context_menu(self):
554 self.context_menu = QtGui.QMenu(self.conflicts_list)
555@@ -190,59 +191,35 @@
556 gettext('&OK'))
557 self.close()
558
559- def update_selection(self, selected, deselected):
560+ def update_merge_tool_ui(self):
561 enabled, error_msg = self.is_merge_tool_launchable()
562- self.program_edit.setEnabled(enabled and not self.program_extmerge_default_button.isChecked())
563+ self.merge_tool_error.setText(error_msg)
564 self.program_launch_button.setEnabled(enabled)
565- self.update_program_edit_text(enabled, error_msg)
566- if enabled and self.program_extmerge_default_button.isChecked():
567- self.program_edit.setEnabled(False)
568 self.merge_action.setEnabled(enabled)
569
570- def check_merge_tool_edit(self, text):
571- enabled, error_msg = self.is_merge_tool_launchable()
572- self.program_launch_button.setEnabled(enabled)
573-
574 def launch_merge_tool(self):
575 items = self.conflicts_list.selectedItems()
576 enabled, error_msg = self.is_merge_tool_launchable()
577 if not enabled:
578 return
579- merge_tool = unicode(self.program_edit.text()).strip()
580- if not merge_tool:
581- return
582+ config = GlobalConfig()
583+ cmdline = config.find_merge_tool(unicode(self.merge_tools_combo.currentText()))
584 file_id = str(items[0].data(0, QtCore.Qt.UserRole).toString())
585 if not file_id:
586 # bug https://bugs.launchpad.net/qbzr/+bug/655451
587 return
588 file_name = self.wt.abspath(self.wt.id2path(file_id))
589- base_file_name = file_name + ".BASE"
590- this_file_name = file_name + ".THIS"
591- other_file_name = file_name + ".OTHER"
592- new_args = [base_file_name, this_file_name, other_file_name]
593- config = get_qbzr_config()
594- config.set_option("merge_tool_extmerge", False)
595-
596- if self.program_extmerge_default_button.isChecked():
597- bzr_config = GlobalConfig()
598- extmerge_tool = bzr_config.get_user_option("external_merge")
599- args = cmdline_split(extmerge_tool)
600- new_args = args[1:len(args)]
601- i = 0
602- while i < len(new_args):
603- new_args[i] = new_args[i].replace('%r', file_name)
604- new_args[i] = new_args[i].replace('%o', other_file_name)
605- new_args[i] = new_args[i].replace('%b', base_file_name)
606- new_args[i] = new_args[i].replace('%t', this_file_name)
607- i = i + 1
608- merge_tool = args[0]
609- config.set_option("merge_tool_extmerge", True)
610- else:
611- config.set_option("merge_tool", merge_tool)
612-
613 process = QtCore.QProcess(self)
614- self.connect(process, QtCore.SIGNAL("error(QProcess::ProcessError)"), self.show_merge_tool_error)
615- process.start(merge_tool, new_args)
616+ def qprocess_invoker(executable, args, cleanup):
617+ def qprocess_error(error):
618+ self.show_merge_tool_error(error)
619+ cleanup(process.exitCode())
620+ def qprocess_finished(exit_code, exit_status):
621+ cleanup(exit_code)
622+ self.connect(process, QtCore.SIGNAL("error(QProcess::ProcessError)"), qprocess_error)
623+ self.connect(process, QtCore.SIGNAL("finished(int,QProcess::ExitStatus)"), qprocess_finished)
624+ process.start(executable, args)
625+ mergetools.invoke(cmdline, file_name, qprocess_invoker)
626
627 def show_merge_tool_error(self, error):
628 msg = gettext("Error while running merge tool (code %d)") % error
629@@ -264,34 +241,24 @@
630 def show_context_menu(self, pos):
631 self.context_menu.popup(self.conflicts_list.viewport().mapToGlobal(pos))
632
633- def program_extmerge_default_clicked(self):
634- enabled, error_msg = self.is_merge_tool_launchable()
635- self.program_edit.setEnabled(enabled and not self.program_extmerge_default_button.isChecked())
636- self.program_launch_button.setEnabled(enabled)
637- self.update_program_edit_text(enabled, error_msg)
638- config = get_qbzr_config()
639- config.set_option("merge_tool_extmerge",
640- self.program_extmerge_default_button.isChecked())
641-
642 def is_merge_tool_launchable(self):
643+ if mergetools is None:
644+ return False, gettext("Bazaar 2.4 or later is required for external mergetools support")
645 items = self.conflicts_list.selectedItems()
646 error_msg = ""
647 enabled = True
648 if len(items) != 1 or items[0].data(1, QtCore.Qt.UserRole).toString() != "text conflict":
649 enabled = False
650-
651- # check to see if the extmerge config is correct
652- if self.program_extmerge_default_button.isChecked():
653- bzr_config = GlobalConfig()
654- extmerge_tool = bzr_config.get_user_option("external_merge")
655- if not extmerge_tool:
656- error_msg = gettext("Set up external_merge app in qconfig under the Merge tab")
657- enabled = False
658- return enabled, error_msg
659- error = self.is_extmerge_definition_valid(False)
660- if len(error) > 0:
661- enabled = False
662- error_msg = error
663+ config = GlobalConfig()
664+ tool = unicode(self.merge_tools_combo.currentText())
665+ cmdline = config.find_merge_tool(tool)
666+ if cmdline is None:
667+ error_msg = gettext("Set up external_merge app in qconfig under the Merge tab")
668+ enabled = False
669+ elif not mergetools.check_availability(cmdline):
670+ enabled = False
671+ error_msg = gettext("External merge tool %(tool)s is not available") % \
672+ { 'tool': tool }
673 return enabled, error_msg
674
675 def is_extmerge_definition_valid(self, showErrorDialog):
676@@ -319,17 +286,6 @@
677 return gettext("Missing the flag: %s. Configure in qconfig under the merge tab.") % flags
678 return ""
679
680- def update_program_edit_text(self, enabled, error_msg):
681- if self.program_extmerge_default_button.isChecked():
682- if enabled or (len(error_msg) <= 0):
683- config = GlobalConfig()
684- extmerge = config.get_user_option("external_merge")
685- self.program_edit.setText(gettext("%s (Configured external merge definition in qconfig)") % extmerge)
686- else:
687- self.program_edit.setText(error_msg)
688- else:
689- config = get_qbzr_config()
690- self.program_edit.setText((config.get_option("merge_tool") or "").strip() or "meld")
691
692 if 0:
693 N_("path conflict")
694
695=== added file 'lib/ui_merge_config.py'
696--- lib/ui_merge_config.py 1970-01-01 00:00:00 +0000
697+++ lib/ui_merge_config.py 2011-05-09 23:16:29 +0000
698@@ -0,0 +1,64 @@
699+# -*- coding: utf-8 -*-
700+
701+# Form implementation generated from reading ui file 'ui/merge_config.ui'
702+#
703+# Created: Tue Dec 07 00:17:33 2010
704+# by: PyQt4 UI code generator 4.7.2
705+#
706+# WARNING! All changes made in this file will be lost!
707+
708+from PyQt4 import QtCore, QtGui
709+from bzrlib.plugins.qbzr.lib.i18n import gettext
710+
711+
712+class Ui_MergeConfig(object):
713+ def setupUi(self, MergeConfig):
714+ MergeConfig.setObjectName("MergeConfig")
715+ MergeConfig.resize(544, 330)
716+ self.verticalLayout = QtGui.QVBoxLayout(MergeConfig)
717+ self.verticalLayout.setObjectName("verticalLayout")
718+ self.groupBox = QtGui.QGroupBox(MergeConfig)
719+ self.groupBox.setObjectName("groupBox")
720+ self.verticalLayout_2 = QtGui.QVBoxLayout(self.groupBox)
721+ self.verticalLayout_2.setObjectName("verticalLayout_2")
722+ self.tools = QtGui.QTableView(self.groupBox)
723+ self.tools.setSelectionBehavior(QtGui.QAbstractItemView.SelectRows)
724+ self.tools.setShowGrid(False)
725+ self.tools.setObjectName("tools")
726+ self.tools.horizontalHeader().setHighlightSections(False)
727+ self.tools.horizontalHeader().setStretchLastSection(True)
728+ self.tools.verticalHeader().setVisible(False)
729+ self.tools.verticalHeader().setDefaultSectionSize(15)
730+ self.tools.verticalHeader().setMinimumSectionSize(15)
731+ self.verticalLayout_2.addWidget(self.tools)
732+ self.widget = QtGui.QWidget(self.groupBox)
733+ self.widget.setObjectName("widget")
734+ self.horizontalLayout = QtGui.QHBoxLayout(self.widget)
735+ self.horizontalLayout.setObjectName("horizontalLayout")
736+ spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
737+ self.horizontalLayout.addItem(spacerItem)
738+ self.add = QtGui.QPushButton(self.widget)
739+ self.add.setObjectName("add")
740+ self.horizontalLayout.addWidget(self.add)
741+ self.remove = QtGui.QPushButton(self.widget)
742+ self.remove.setObjectName("remove")
743+ self.horizontalLayout.addWidget(self.remove)
744+ self.set_default = QtGui.QPushButton(self.widget)
745+ self.set_default.setObjectName("set_default")
746+ self.horizontalLayout.addWidget(self.set_default)
747+ spacerItem1 = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
748+ self.horizontalLayout.addItem(spacerItem1)
749+ self.verticalLayout_2.addWidget(self.widget)
750+ self.verticalLayout.addWidget(self.groupBox)
751+
752+ self.retranslateUi(MergeConfig)
753+ QtCore.QMetaObject.connectSlotsByName(MergeConfig)
754+
755+ def retranslateUi(self, MergeConfig):
756+ MergeConfig.setWindowTitle(gettext("Form"))
757+ self.groupBox.setTitle(gettext("External Merge Tools"))
758+ self.add.setText(gettext("Add"))
759+ self.remove.setText(gettext("Remove"))
760+ self.set_default.setToolTip(gettext("Sets the selected merge tool as the default to use in qconflicts."))
761+ self.set_default.setText(gettext("Set Default"))
762+
763
764=== added file 'ui/merge_config.ui'
765--- ui/merge_config.ui 1970-01-01 00:00:00 +0000
766+++ ui/merge_config.ui 2011-05-09 23:16:29 +0000
767@@ -0,0 +1,126 @@
768+<?xml version="1.0" encoding="UTF-8"?>
769+<ui version="4.0">
770+ <class>MergeConfig</class>
771+ <widget class="QWidget" name="MergeConfig">
772+ <property name="geometry">
773+ <rect>
774+ <x>0</x>
775+ <y>0</y>
776+ <width>544</width>
777+ <height>330</height>
778+ </rect>
779+ </property>
780+ <property name="windowTitle">
781+ <string>Form</string>
782+ </property>
783+ <layout class="QVBoxLayout" name="verticalLayout">
784+ <item>
785+ <widget class="QGroupBox" name="groupBox">
786+ <property name="title">
787+ <string>External Merge Tools</string>
788+ </property>
789+ <layout class="QVBoxLayout" name="verticalLayout_2">
790+ <item>
791+ <widget class="QTableView" name="tools">
792+ <property name="selectionBehavior">
793+ <enum>QAbstractItemView::SelectRows</enum>
794+ </property>
795+ <property name="showGrid">
796+ <bool>false</bool>
797+ </property>
798+ <attribute name="horizontalHeaderHighlightSections">
799+ <bool>false</bool>
800+ </attribute>
801+ <attribute name="horizontalHeaderStretchLastSection">
802+ <bool>true</bool>
803+ </attribute>
804+ <attribute name="verticalHeaderVisible">
805+ <bool>false</bool>
806+ </attribute>
807+ <attribute name="verticalHeaderDefaultSectionSize">
808+ <number>15</number>
809+ </attribute>
810+ <attribute name="verticalHeaderMinimumSectionSize">
811+ <number>15</number>
812+ </attribute>
813+ <attribute name="verticalHeaderVisible">
814+ <bool>false</bool>
815+ </attribute>
816+ <attribute name="verticalHeaderDefaultSectionSize">
817+ <number>15</number>
818+ </attribute>
819+ <attribute name="horizontalHeaderStretchLastSection">
820+ <bool>true</bool>
821+ </attribute>
822+ <attribute name="verticalHeaderMinimumSectionSize">
823+ <number>15</number>
824+ </attribute>
825+ <attribute name="horizontalHeaderHighlightSections">
826+ <bool>false</bool>
827+ </attribute>
828+ </widget>
829+ </item>
830+ <item>
831+ <widget class="QWidget" name="widget" native="true">
832+ <layout class="QHBoxLayout" name="horizontalLayout">
833+ <item>
834+ <spacer name="spacer">
835+ <property name="orientation">
836+ <enum>Qt::Horizontal</enum>
837+ </property>
838+ <property name="sizeHint" stdset="0">
839+ <size>
840+ <width>40</width>
841+ <height>20</height>
842+ </size>
843+ </property>
844+ </spacer>
845+ </item>
846+ <item>
847+ <widget class="QPushButton" name="add">
848+ <property name="text">
849+ <string>Add</string>
850+ </property>
851+ </widget>
852+ </item>
853+ <item>
854+ <widget class="QPushButton" name="remove">
855+ <property name="text">
856+ <string>Remove</string>
857+ </property>
858+ </widget>
859+ </item>
860+ <item>
861+ <widget class="QPushButton" name="set_default">
862+ <property name="toolTip">
863+ <string>Sets the selected merge tool as the default to use in qconflicts.</string>
864+ </property>
865+ <property name="text">
866+ <string>Set Default</string>
867+ </property>
868+ </widget>
869+ </item>
870+ <item>
871+ <spacer name="spacer_2">
872+ <property name="orientation">
873+ <enum>Qt::Horizontal</enum>
874+ </property>
875+ <property name="sizeHint" stdset="0">
876+ <size>
877+ <width>40</width>
878+ <height>20</height>
879+ </size>
880+ </property>
881+ </spacer>
882+ </item>
883+ </layout>
884+ </widget>
885+ </item>
886+ </layout>
887+ </widget>
888+ </item>
889+ </layout>
890+ </widget>
891+ <resources/>
892+ <connections/>
893+</ui>

Subscribers

People subscribed via source and target branches