Merge lp:~qbzr-dev/qbzr/mergetools into lp:qbzr
- mergetools
- Merge into trunk2a
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Alexander Belchenko | Approve | ||
Review via email: mp+40426@code.launchpad.net |
Commit message
Description of the change
This supersedes https:/
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.
Gordon Tyler (doxxx) wrote : | # |
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
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.
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?
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
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. :)
Alexander Belchenko (bialix) wrote : | # |
What's the status of this work now? What should be done?
Gordon Tyler (doxxx) wrote : | # |
It needs to fail gracefully in the absence of bzr mergetools module.
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?
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
Gordon Tyler (doxxx) wrote : | # |
Do you need anything more for this, Alex?
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.
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.
Alexander Belchenko (bialix) wrote : | # |
Thank you for your patience. It's landed now.
Preview Diff
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> |
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.