maint: prefer functions over methods
[git-cola.git] / cola / widgets / toolbar.py
blob60fc9addb14f1568244bbac286e99aff7189a1ab
1 from __future__ import division, absolute_import, unicode_literals
2 from functools import partial
4 from qtpy import QtGui
5 from qtpy.QtCore import Qt
6 from qtpy import QtWidgets
8 from ..i18n import N_
9 from ..widgets import standard
10 from ..qtutils import get
11 from .. import icons
12 from .. import qtutils
13 from .toolbarcmds import COMMANDS
14 from . import defs
16 TREE_LAYOUT = {
17 'Others': [
18 'Others::LaunchEditor',
19 'Others::RevertUnstagedEdits'
21 'File': [
22 'File::NewRepo',
23 'File::OpenRepo',
24 'File::OpenRepoNewWindow',
25 'File::Refresh',
26 'File::EditRemotes',
27 'File::RecentModified',
28 'File::ApplyPatches',
29 'File::ExportPatches',
31 'Actions': [
32 'Actions::Fetch',
33 'Actions::Pull',
34 'Actions::Push',
35 'Actions::Stash',
36 'Actions::CreateTag',
37 'Actions::CherryPick',
38 'Actions::Merge',
39 'Actions::AbortMerge',
40 'Actions::ResetBranchHead',
41 'Actions::ResetWorktree',
42 'Actions::Grep',
43 'Actions::Search'
45 'Commit@@verb': [
46 'Commit::Stage',
47 'Commit::AmendLast',
48 'Commit::StageAll',
49 'Commit::UnstageAll',
50 'Commit::Unstage',
51 'Commit::LoadCommitMessage',
52 'Commit::GetCommitMessageTemplate',
54 'Diff': [
55 'Diff::Difftool',
56 'Diff::Expression',
57 'Diff::Branches',
58 'Diff::Diffstat'
60 'Branch': [
61 'Branch::Review',
62 'Branch::Create',
63 'Branch::Checkout',
64 'Branch::Delete',
65 'Branch::DeleteRemote',
66 'Branch::Rename',
67 'Branch::BrowseCurrent',
68 'Branch::BrowseOther',
69 'Branch::VisualizeCurrent',
70 'Branch::VisualizeAll'
72 'View': [
73 'View::DAG',
74 'View::FileBrowser',
79 def configure(toolbar, parent=None):
80 """Launches the Toolbar configure dialog"""
81 view = ToolbarView(toolbar, parent if parent else qtutils.active_window())
82 view.show()
83 return view
86 def get_toolbars(widget):
87 return widget.findChildren(ToolBar)
90 def add_toolbar(context, widget):
91 toolbars = get_toolbars(widget)
92 name = 'ToolBar%d' % (len(toolbars) + 1)
93 toolbar = ToolBar.create(context, name)
94 widget.addToolBar(toolbar)
95 configure(toolbar)
98 class ToolBarState(object):
99 """export_state() and apply_state() providers for toolbars"""
101 def __init__(self, context, widget):
102 """widget must be a QMainWindow for toolBarArea(), etc."""
103 self.context = context
104 self.widget = widget
106 def apply_state(self, toolbars):
107 context = self.context
108 widget = self.widget
110 for data in toolbars:
111 toolbar = ToolBar.create(context, data['name'])
112 toolbar.load_items(data['items'])
113 toolbar.set_show_icons(data['show_icons'])
114 toolbar.setVisible(data['visible'])
116 toolbar_area = decode_toolbar_area(data['area'])
117 if data['break']:
118 widget.addToolBarBreak(toolbar_area)
119 widget.addToolBar(toolbar_area, toolbar)
121 # floating toolbars must be set after added
122 if data['float']:
123 toolbar.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint)
124 toolbar.move(data['x'], data['y'])
125 # TODO: handle changed width when exists more than one toolbar in
126 # an area
128 def export_state(self):
129 result = []
130 widget = self.widget
131 toolbars = widget.findChildren(ToolBar)
133 for toolbar in toolbars:
134 toolbar_area = widget.toolBarArea(toolbar)
135 if toolbar_area == Qt.NoToolBarArea:
136 continue # filter out removed toolbars
137 items = [x.data() for x in toolbar.actions()]
139 result.append({
140 'name': toolbar.windowTitle(),
141 'area': encode_toolbar_area(toolbar_area),
142 'break': widget.toolBarBreak(toolbar),
143 'float': toolbar.isFloating(),
144 'x': toolbar.pos().x(),
145 'y': toolbar.pos().y(),
146 'width': toolbar.width(),
147 'height': toolbar.height(),
148 'show_icons': toolbar.show_icons(),
149 'visible': toolbar.isVisible(),
150 'items': items,
153 return result
156 class ToolBar(QtWidgets.QToolBar):
157 SEPARATOR = 'Separator'
159 @staticmethod
160 def create(context, name):
161 return ToolBar(context, name, TREE_LAYOUT, COMMANDS)
163 def __init__(self, context, title, tree_layout, toolbar_commands):
164 QtWidgets.QToolBar.__init__(self)
165 self.setWindowTitle(title)
166 self.setObjectName(title)
168 self.context = context
169 self.tree_layout = tree_layout
170 self.commands = toolbar_commands
172 def set_show_icons(self, show_icons):
173 if show_icons:
174 self.setToolButtonStyle(Qt.ToolButtonIconOnly)
175 else:
176 self.setToolButtonStyle(Qt.ToolButtonTextOnly)
178 def show_icons(self):
179 return self.toolButtonStyle() == Qt.ToolButtonIconOnly
181 def load_items(self, items):
182 for data in items:
183 self.add_action_from_data(data)
185 def add_action_from_data(self, data):
186 parent = data['parent']
187 child = data['child']
189 if child == self.SEPARATOR:
190 toolbar_action = self.addSeparator()
191 toolbar_action.setData(data)
192 return
194 tree_items = self.tree_layout.get(parent, [])
195 if child in tree_items and child in self.commands:
196 command = self.commands[child]
197 title = N_(command['title'])
198 callback = partial(command['action'], self.context)
200 icon = None
201 command_icon = command.get('icon', None)
202 if command_icon:
203 icon = getattr(icons, command_icon, None)
204 if callable(icon):
205 icon = icon()
206 if icon:
207 toolbar_action = self.addAction(icon, title, callback)
208 else:
209 toolbar_action = self.addAction(title, callback)
211 toolbar_action.setData(data)
213 def delete_toolbar(self):
214 self.parent().removeToolBar(self)
216 def contextMenuEvent(self, event):
217 menu = QtWidgets.QMenu()
218 menu.addAction(N_('Configure toolbar'), partial(configure, self))
219 menu.addAction(N_('Delete toolbar'), self.delete_toolbar)
221 menu.exec_(event.globalPos())
224 def encode_toolbar_area(toolbar_area):
225 """Encode a Qt::ToolBarArea as a string"""
226 if toolbar_area == Qt.LeftToolBarArea:
227 result = 'left'
228 elif toolbar_area == Qt.RightToolBarArea:
229 result = 'right'
230 elif toolbar_area == Qt.TopToolBarArea:
231 result = 'top'
232 elif toolbar_area == Qt.BottomToolBarArea:
233 result = 'bottom'
234 else: # fallback to "bottom"
235 result = 'bottom'
236 return result
239 def decode_toolbar_area(string):
240 """Decode an encoded toolbar area string into a Qt::ToolBarArea"""
241 if string == 'left':
242 result = Qt.LeftToolBarArea
243 elif string == 'right':
244 result = Qt.RightToolBarArea
245 elif string == 'top':
246 result = Qt.TopToolBarArea
247 elif string == 'bottom':
248 result = Qt.BottomToolBarArea
249 else:
250 result = Qt.BottomToolBarArea
251 return result
254 class ToolbarView(standard.Dialog):
255 """Provides the git-cola 'ToolBar' configure dialog"""
256 SEPARATOR_TEXT = '----------------------------'
258 def __init__(self, toolbar, parent=None):
259 standard.Dialog.__init__(self, parent)
260 self.setWindowTitle(N_('Configure toolbar'))
262 self.toolbar = toolbar
263 self.left_list = ToolbarTreeWidget(self)
264 self.right_list = DraggableListWidget(self)
265 self.text_toolbar_name = QtWidgets.QLabel()
266 self.text_toolbar_name.setText(N_('Name'))
267 self.toolbar_name = QtWidgets.QLineEdit()
268 self.toolbar_name.setText(toolbar.windowTitle())
269 self.add_separator = qtutils.create_button(N_('Add Separator'))
270 self.remove_item = qtutils.create_button(N_('Remove Element'))
271 checked = toolbar.show_icons()
272 checkbox_text = N_('Show icon? (if available)')
273 self.show_icon = qtutils.checkbox(
274 checkbox_text, checkbox_text, checked)
275 self.apply_button = qtutils.ok_button(N_('Apply'))
276 self.close_button = qtutils.close_button()
277 self.close_button.setDefault(True)
279 self.right_actions = qtutils.hbox(
280 defs.no_margin, defs.spacing,
281 self.add_separator,
282 self.remove_item)
283 self.name_layout = qtutils.hbox(
284 defs.no_margin, defs.spacing,
285 self.text_toolbar_name,
286 self.toolbar_name)
287 self.left_layout = qtutils.vbox(
288 defs.no_margin, defs.spacing,
289 self.left_list)
290 self.right_layout = qtutils.vbox(
291 defs.no_margin, defs.spacing,
292 self.right_list,
293 self.right_actions)
294 self.top_layout = qtutils.hbox(
295 defs.no_margin, defs.spacing,
296 self.left_layout,
297 self.right_layout)
298 self.actions_layout = qtutils.hbox(
299 defs.no_margin, defs.spacing,
300 self.show_icon,
301 qtutils.STRETCH,
302 self.close_button,
303 self.apply_button)
304 self.main_layout = qtutils.vbox(
305 defs.margin, defs.spacing,
306 self.name_layout,
307 self.top_layout,
308 self.actions_layout)
309 self.setLayout(self.main_layout)
311 qtutils.connect_button(self.add_separator, self.add_separator_action)
312 qtutils.connect_button(self.remove_item, self.remove_item_action)
313 qtutils.connect_button(self.apply_button, self.apply_action)
314 qtutils.connect_button(self.close_button, self.accept)
316 self.load_right_items()
317 self.load_left_items()
319 self.init_size(parent=parent)
321 def load_right_items(self):
322 for action in self.toolbar.actions():
323 data = action.data()
324 if data['child'] == self.toolbar.SEPARATOR:
325 self.add_separator_action()
326 else:
327 command = self.toolbar.commands[data['child']]
328 title = command['title']
329 icon = command['icon']
330 self.right_list.add_item(title, data, icon)
332 def load_left_items(self):
334 # def current_children(actions):
335 # result = []
336 # for action in actions:
337 # data = action.data()
338 # if data['child'] != self.toolbar.SEPARATOR:
339 # result.append(data['child'])
341 # return result
343 for parent in self.toolbar.tree_layout:
344 top = self.left_list.insert_top(parent)
345 # current_items = current_children(self.toolbar.actions())
346 for item in self.toolbar.tree_layout[parent]:
347 command = self.toolbar.commands[item]
348 child = create_child(parent, item,
349 command['title'], command['icon'])
350 top.appendRow(child)
352 top.sortChildren(0, Qt.AscendingOrder)
354 def add_separator_action(self):
355 data = {'parent': None, 'child': self.toolbar.SEPARATOR}
356 self.right_list.add_separator(self.SEPARATOR_TEXT, data)
358 def remove_item_action(self):
359 items = self.right_list.selectedItems()
361 for item in items:
362 self.right_list.takeItem(self.right_list.row(item))
364 def apply_action(self):
365 self.toolbar.clear()
366 self.toolbar.set_show_icons(get(self.show_icon))
367 self.toolbar.setWindowTitle(self.toolbar_name.text())
369 for item in self.right_list.get_items():
370 data = item.data(Qt.UserRole)
371 self.toolbar.add_action_from_data(data)
374 class DraggableListMixin(object):
375 items = []
377 def __init__(self, widget, Base):
378 self.widget = widget
379 self.Base = Base
381 widget.setAcceptDrops(True)
382 widget.setSelectionMode(widget.SingleSelection)
383 widget.setDragEnabled(True)
384 widget.setDropIndicatorShown(True)
386 def dragEnterEvent(self, event):
387 widget = self.widget
388 self.Base.dragEnterEvent(widget, event)
390 def dragMoveEvent(self, event):
391 widget = self.widget
392 self.Base.dragMoveEvent(widget, event)
394 def dragLeaveEvent(self, event):
395 widget = self.widget
396 self.Base.dragLeaveEvent(widget, event)
398 def dropEvent(self, event):
399 widget = self.widget
400 event.setDropAction(Qt.MoveAction)
401 self.Base.dropEvent(widget, event)
403 def get_items(self):
404 widget = self.widget
405 base = self.Base
406 items = [base.item(widget, i) for i in range(base.count(widget))]
408 return items
411 class DraggableListWidget(QtWidgets.QListWidget):
412 Mixin = DraggableListMixin
414 def __init__(self, parent=None):
415 QtWidgets.QListWidget.__init__(self, parent)
417 self.setAcceptDrops(True)
418 self.setSelectionMode(self.SingleSelection)
419 self.setDragEnabled(True)
420 self.setDropIndicatorShown(True)
422 self._mixin = self.Mixin(self, QtWidgets.QListWidget)
424 def dragEnterEvent(self, event):
425 return self._mixin.dragEnterEvent(event)
427 def dragMoveEvent(self, event):
428 return self._mixin.dragMoveEvent(event)
430 def dropEvent(self, event):
431 return self._mixin.dropEvent(event)
433 def add_separator(self, title, data):
434 item = QtWidgets.QListWidgetItem()
435 item.setText(title)
436 item.setData(Qt.UserRole, data)
438 self.addItem(item)
440 def add_item(self, title, data, icon_text=None):
441 item = QtWidgets.QListWidgetItem()
442 item.setText(N_(title))
443 item.setData(Qt.UserRole, data)
445 if icon_text is not None:
446 icon = getattr(icons, icon_text, None)
447 item.setIcon(icon())
449 self.addItem(item)
451 def get_items(self):
452 return self._mixin.get_items()
455 class ToolbarTreeWidget(standard.TreeView):
457 def __init__(self, parent):
458 standard.TreeView.__init__(self, parent)
460 self.setDragEnabled(True)
461 self.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
462 self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
463 self.setDropIndicatorShown(True)
464 self.setRootIsDecorated(True)
465 self.setHeaderHidden(True)
466 self.setAlternatingRowColors(False)
467 self.setSortingEnabled(False)
469 self.setModel(QtGui.QStandardItemModel())
471 def insert_top(self, title):
472 item = create_item(title, title)
473 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
475 self.model().insertRow(0, item)
476 self.model().sort(0)
478 return item
481 def create_child(parent, child, title, icon_text=None):
482 data = {'parent': parent, 'child': child}
483 item = create_item(title, data)
485 if icon_text is not None:
486 icon = getattr(icons, icon_text, None)
487 item.setIcon(icon())
489 return item
492 def create_item(name, data):
493 item = QtGui.QStandardItem()
495 item.setEditable(False)
496 item.setDragEnabled(True)
497 item.setText(N_(name))
498 item.setData(data, Qt.UserRole)
500 return item