toolbar: fix the default values
[git-cola.git] / cola / widgets / toolbar.py
bloba2d479fabae5114dc13bd7cf8261798056c8f5bc
1 from __future__ import absolute_import, division, print_function, 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 .. import icons
11 from .. import qtutils
12 from .toolbarcmds import COMMANDS
13 from . import defs
15 TREE_LAYOUT = {
16 'Others': ['Others::LaunchEditor', 'Others::RevertUnstagedEdits'],
17 'File': [
18 'File::NewRepo',
19 'File::OpenRepo',
20 'File::OpenRepoNewWindow',
21 'File::Refresh',
22 'File::EditRemotes',
23 'File::RecentModified',
24 'File::SaveAsTarZip',
25 'File::ApplyPatches',
26 'File::ExportPatches',
28 'Actions': [
29 'Actions::Fetch',
30 'Actions::Pull',
31 'Actions::Push',
32 'Actions::Stash',
33 'Actions::CreateTag',
34 'Actions::CherryPick',
35 'Actions::Merge',
36 'Actions::AbortMerge',
37 'Actions::UpdateSubmodules',
38 'Actions::Grep',
39 'Actions::Search',
41 'Commit@@verb': [
42 'Commit::Stage',
43 'Commit::AmendLast',
44 'Commit::UndoLastCommit',
45 'Commit::StageAll',
46 'Commit::UnstageAll',
47 'Commit::Unstage',
48 'Commit::LoadCommitMessage',
49 'Commit::GetCommitMessageTemplate',
51 'Diff': ['Diff::Difftool', 'Diff::Expression', 'Diff::Branches', 'Diff::Diffstat'],
52 'Branch': [
53 'Branch::Review',
54 'Branch::Create',
55 'Branch::Checkout',
56 'Branch::Delete',
57 'Branch::DeleteRemote',
58 'Branch::Rename',
59 'Branch::BrowseCurrent',
60 'Branch::BrowseOther',
61 'Branch::VisualizeCurrent',
62 'Branch::VisualizeAll',
64 'Reset': [
65 'Commit::UndoLastCommit',
66 'Commit::UnstageAll',
67 'Actions::ResetSoft',
68 'Actions::ResetMixed',
69 'Actions::RestoreWorktree',
70 'Actions::ResetKeep',
71 'Actions::ResetHard',
73 'View': ['View::DAG', 'View::FileBrowser'],
77 def configure(toolbar, parent=None):
78 """Launches the Toolbar configure dialog"""
79 if not parent:
80 parent = qtutils.active_window()
81 view = ToolbarView(toolbar, parent)
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 try:
114 toolbar.set_toolbar_style(data['toolbar_style'])
115 except KeyError:
116 # Maintain compatibility for toolbars created in git-cola <= 3.11.0
117 if data['show_icons']:
118 data['toolbar_style'] = ToolBar.STYLE_FOLLOW_SYSTEM
119 toolbar.set_toolbar_style(ToolBar.STYLE_FOLLOW_SYSTEM)
120 else:
121 data['toolbar_style'] = ToolBar.STYLE_TEXT_ONLY
122 toolbar.set_toolbar_style(ToolBar.STYLE_TEXT_ONLY)
123 toolbar.setVisible(data['visible'])
125 toolbar_area = decode_toolbar_area(data['area'])
126 if data['break']:
127 widget.addToolBarBreak(toolbar_area)
128 widget.addToolBar(toolbar_area, toolbar)
130 # floating toolbars must be set after added
131 if data['float']:
132 toolbar.setWindowFlags(Qt.Tool | Qt.FramelessWindowHint)
133 toolbar.move(data['x'], data['y'])
134 # TODO: handle changed width when exists more than one toolbar in
135 # an area
137 def export_state(self):
138 result = []
139 widget = self.widget
140 toolbars = widget.findChildren(ToolBar)
142 for toolbar in toolbars:
143 toolbar_area = widget.toolBarArea(toolbar)
144 if toolbar_area == Qt.NoToolBarArea:
145 continue # filter out removed toolbars
146 items = [x.data() for x in toolbar.actions()]
148 result.append(
150 'name': toolbar.windowTitle(),
151 'area': encode_toolbar_area(toolbar_area),
152 'break': widget.toolBarBreak(toolbar),
153 'float': toolbar.isFloating(),
154 'x': toolbar.pos().x(),
155 'y': toolbar.pos().y(),
156 'width': toolbar.width(),
157 'height': toolbar.height(),
158 # show_icons kept for backwards compatibility in git-cola <= 3.11.0
159 'show_icons': toolbar.toolbar_style() != ToolBar.STYLE_TEXT_ONLY,
160 'toolbar_style': toolbar.toolbar_style(),
161 'visible': toolbar.isVisible(),
162 'items': items,
166 return result
169 class ToolBar(QtWidgets.QToolBar):
170 SEPARATOR = 'Separator'
171 STYLE_FOLLOW_SYSTEM = 0
172 STYLE_ICON_ONLY = 1
173 STYLE_TEXT_ONLY = 2
174 STYLE_TEXT_BESIDE_ICON = 3
175 STYLE_TEXT_UNDER_ICON = 4
176 STYLE_NAMES = [
177 N_('Follow System Style'),
178 N_('Icon Only'),
179 N_('Text Only'),
180 N_('Text Beside Icon'),
181 N_('Text Under Icon'),
184 @staticmethod
185 def create(context, name):
186 return ToolBar(context, name, TREE_LAYOUT, COMMANDS)
188 def __init__(self, context, title, tree_layout, toolbar_commands):
189 QtWidgets.QToolBar.__init__(self)
190 self.setWindowTitle(title)
191 self.setObjectName(title)
193 self.context = context
194 self.tree_layout = tree_layout
195 self.commands = toolbar_commands
197 def set_toolbar_style(self, style_id):
198 styles_to_qt = {
199 self.STYLE_FOLLOW_SYSTEM: Qt.ToolButtonFollowStyle,
200 self.STYLE_ICON_ONLY: Qt.ToolButtonIconOnly,
201 self.STYLE_TEXT_ONLY: Qt.ToolButtonTextOnly,
202 self.STYLE_TEXT_BESIDE_ICON: Qt.ToolButtonTextBesideIcon,
203 self.STYLE_TEXT_UNDER_ICON: Qt.ToolButtonTextUnderIcon,
205 default = Qt.ToolButtonFollowStyle
206 return self.setToolButtonStyle(styles_to_qt.get(style_id, default))
208 def toolbar_style(self):
209 styles_to_int = {
210 Qt.ToolButtonFollowStyle: self.STYLE_FOLLOW_SYSTEM,
211 Qt.ToolButtonIconOnly: self.STYLE_ICON_ONLY,
212 Qt.ToolButtonTextOnly: self.STYLE_TEXT_ONLY,
213 Qt.ToolButtonTextBesideIcon: self.STYLE_TEXT_BESIDE_ICON,
214 Qt.ToolButtonTextUnderIcon: self.STYLE_TEXT_UNDER_ICON,
216 default = self.STYLE_FOLLOW_SYSTEM
217 return styles_to_int.get(self.toolButtonStyle(), default)
219 def load_items(self, items):
220 for data in items:
221 self.add_action_from_data(data)
223 def add_action_from_data(self, data):
224 parent = data['parent']
225 child = data['child']
227 if child == self.SEPARATOR:
228 toolbar_action = self.addSeparator()
229 toolbar_action.setData(data)
230 return
232 tree_items = self.tree_layout.get(parent, [])
233 if child in tree_items and child in self.commands:
234 command = self.commands[child]
235 title = N_(command['title'])
236 callback = partial(command['action'], self.context)
238 icon = None
239 command_icon = command.get('icon', None)
240 if command_icon:
241 icon = getattr(icons, command_icon, None)
242 if callable(icon):
243 icon = icon()
244 if icon:
245 toolbar_action = self.addAction(icon, title, callback)
246 else:
247 toolbar_action = self.addAction(title, callback)
249 toolbar_action.setData(data)
251 tooltip = command.get('tooltip', None)
252 if tooltip:
253 toolbar_action.setToolTip('%s\n%s' % (title, tooltip))
255 def delete_toolbar(self):
256 self.parent().removeToolBar(self)
258 def contextMenuEvent(self, event):
259 menu = QtWidgets.QMenu()
260 tool_config = menu.addAction(N_('Configure Toolbar'), partial(configure, self))
261 tool_config.setIcon(icons.configure())
262 tool_delete = menu.addAction(N_('Delete Toolbar'), self.delete_toolbar)
263 tool_delete.setIcon(icons.remove())
265 menu.exec_(event.globalPos())
268 def encode_toolbar_area(toolbar_area):
269 """Encode a Qt::ToolBarArea as a string"""
270 if toolbar_area == Qt.LeftToolBarArea:
271 result = 'left'
272 elif toolbar_area == Qt.RightToolBarArea:
273 result = 'right'
274 elif toolbar_area == Qt.TopToolBarArea:
275 result = 'top'
276 elif toolbar_area == Qt.BottomToolBarArea:
277 result = 'bottom'
278 else: # fallback to "bottom"
279 result = 'bottom'
280 return result
283 def decode_toolbar_area(string):
284 """Decode an encoded toolbar area string into a Qt::ToolBarArea"""
285 if string == 'left':
286 result = Qt.LeftToolBarArea
287 elif string == 'right':
288 result = Qt.RightToolBarArea
289 elif string == 'top':
290 result = Qt.TopToolBarArea
291 elif string == 'bottom':
292 result = Qt.BottomToolBarArea
293 else:
294 result = Qt.BottomToolBarArea
295 return result
298 class ToolbarView(standard.Dialog):
299 """Provides the git-cola 'ToolBar' configure dialog"""
301 SEPARATOR_TEXT = '----------------------------'
303 def __init__(self, toolbar, parent=None):
304 standard.Dialog.__init__(self, parent)
305 self.setWindowTitle(N_('Configure Toolbar'))
307 self.toolbar = toolbar
308 self.toolbar.setToolButtonStyle(Qt.ToolButtonFollowStyle)
309 self.left_list = ToolbarTreeWidget(self)
310 self.right_list = DraggableListWidget(self)
311 self.text_toolbar_name = QtWidgets.QLabel()
312 self.text_toolbar_name.setText(N_('Name'))
313 self.toolbar_name = QtWidgets.QLineEdit()
314 self.toolbar_name.setText(toolbar.windowTitle())
315 self.add_separator = qtutils.create_button(N_('Add Separator'))
316 self.remove_item = qtutils.create_button(N_('Remove Element'))
317 self.toolbar_style_label = QtWidgets.QLabel(N_('Toolbar Style:'))
318 self.toolbar_style = QtWidgets.QComboBox()
319 for style_name in ToolBar.STYLE_NAMES:
320 self.toolbar_style.addItem(style_name)
321 self.toolbar_style.setCurrentIndex(toolbar.toolbar_style())
322 self.apply_button = qtutils.ok_button(N_('Apply'))
323 self.close_button = qtutils.close_button()
324 self.close_button.setDefault(True)
326 self.right_actions = qtutils.hbox(
327 defs.no_margin, defs.spacing, self.add_separator, self.remove_item
329 self.name_layout = qtutils.hbox(
330 defs.no_margin, defs.spacing, self.text_toolbar_name, self.toolbar_name
332 self.left_layout = qtutils.vbox(defs.no_margin, defs.spacing, self.left_list)
333 self.right_layout = qtutils.vbox(
334 defs.no_margin, defs.spacing, self.right_list, self.right_actions
336 self.top_layout = qtutils.hbox(
337 defs.no_margin, defs.spacing, self.left_layout, self.right_layout
339 self.actions_layout = qtutils.hbox(
340 defs.no_margin,
341 defs.spacing,
342 self.toolbar_style_label,
343 self.toolbar_style,
344 qtutils.STRETCH,
345 self.close_button,
346 self.apply_button,
348 self.main_layout = qtutils.vbox(
349 defs.margin,
350 defs.spacing,
351 self.name_layout,
352 self.top_layout,
353 self.actions_layout,
355 self.setLayout(self.main_layout)
357 qtutils.connect_button(self.add_separator, self.add_separator_action)
358 qtutils.connect_button(self.remove_item, self.remove_item_action)
359 qtutils.connect_button(self.apply_button, self.apply_action)
360 qtutils.connect_button(self.close_button, self.accept)
362 self.load_right_items()
363 self.load_left_items()
365 self.init_size(parent=parent)
367 def load_right_items(self):
368 commands = self.toolbar.commands
369 for action in self.toolbar.actions():
370 data = action.data()
371 if data['child'] == self.toolbar.SEPARATOR:
372 self.add_separator_action()
373 else:
374 try:
375 child_data = data['child']
376 command = commands[child_data]
377 except KeyError:
378 pass
379 title = command['title']
380 icon = command.get('icon', None)
381 tooltip = command.get('tooltip', None)
382 self.right_list.add_item(title, tooltip, data, icon)
384 def load_left_items(self):
385 commands = self.toolbar.commands
386 for parent in self.toolbar.tree_layout:
387 top = self.left_list.insert_top(parent)
388 for item in self.toolbar.tree_layout[parent]:
389 try:
390 command = commands[item]
391 except KeyError:
392 pass
393 icon = command.get('icon', None)
394 tooltip = command.get('tooltip', None)
395 child = create_child(parent, item, command['title'], tooltip, icon)
396 top.appendRow(child)
398 top.sortChildren(0, Qt.AscendingOrder)
400 def add_separator_action(self):
401 data = {'parent': None, 'child': self.toolbar.SEPARATOR}
402 self.right_list.add_separator(self.SEPARATOR_TEXT, data)
404 def remove_item_action(self):
405 items = self.right_list.selectedItems()
407 for item in items:
408 self.right_list.takeItem(self.right_list.row(item))
410 def apply_action(self):
411 self.toolbar.clear()
412 self.toolbar.set_toolbar_style(self.toolbar_style.currentIndex())
413 self.toolbar.setWindowTitle(self.toolbar_name.text())
415 for item in self.right_list.get_items():
416 data = item.data(Qt.UserRole)
417 self.toolbar.add_action_from_data(data)
420 class DraggableListMixin(object):
421 items = []
423 def __init__(self, widget, Base):
424 self.widget = widget
425 self.Base = Base
427 widget.setAcceptDrops(True)
428 widget.setSelectionMode(widget.SingleSelection)
429 widget.setDragEnabled(True)
430 widget.setDropIndicatorShown(True)
432 def dragEnterEvent(self, event):
433 widget = self.widget
434 self.Base.dragEnterEvent(widget, event)
436 def dragMoveEvent(self, event):
437 widget = self.widget
438 self.Base.dragMoveEvent(widget, event)
440 def dragLeaveEvent(self, event):
441 widget = self.widget
442 self.Base.dragLeaveEvent(widget, event)
444 def dropEvent(self, event):
445 widget = self.widget
446 event.setDropAction(Qt.MoveAction)
447 self.Base.dropEvent(widget, event)
449 def get_items(self):
450 widget = self.widget
451 base = self.Base
452 items = [base.item(widget, i) for i in range(base.count(widget))]
454 return items
457 # pylint: disable=too-many-ancestors
458 class DraggableListWidget(QtWidgets.QListWidget):
459 Mixin = DraggableListMixin
461 def __init__(self, parent=None):
462 QtWidgets.QListWidget.__init__(self, parent)
464 self.setAcceptDrops(True)
465 self.setSelectionMode(self.SingleSelection)
466 self.setDragEnabled(True)
467 self.setDropIndicatorShown(True)
469 self._mixin = self.Mixin(self, QtWidgets.QListWidget)
471 def dragEnterEvent(self, event):
472 return self._mixin.dragEnterEvent(event)
474 def dragMoveEvent(self, event):
475 return self._mixin.dragMoveEvent(event)
477 def dropEvent(self, event):
478 return self._mixin.dropEvent(event)
480 def add_separator(self, title, data):
481 item = QtWidgets.QListWidgetItem()
482 item.setText(title)
483 item.setData(Qt.UserRole, data)
485 self.addItem(item)
487 def add_item(self, title, tooltip, data, icon):
488 item = QtWidgets.QListWidgetItem()
489 item.setText(N_(title))
490 item.setData(Qt.UserRole, data)
491 if tooltip:
492 item.setToolTip(tooltip)
494 if icon:
495 icon_func = getattr(icons, icon)
496 item.setIcon(icon_func())
498 self.addItem(item)
500 def get_items(self):
501 return self._mixin.get_items()
504 # pylint: disable=too-many-ancestors
505 class ToolbarTreeWidget(standard.TreeView):
506 def __init__(self, parent):
507 standard.TreeView.__init__(self, parent)
509 self.setDragEnabled(True)
510 self.setDragDropMode(QtWidgets.QAbstractItemView.DragOnly)
511 self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
512 self.setDropIndicatorShown(True)
513 self.setRootIsDecorated(True)
514 self.setHeaderHidden(True)
515 self.setAlternatingRowColors(False)
516 self.setSortingEnabled(False)
518 self.setModel(QtGui.QStandardItemModel())
520 def insert_top(self, title):
521 item = create_item(title, title)
522 item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
524 self.model().insertRow(0, item)
525 self.model().sort(0)
527 return item
530 def create_child(parent, child, title, tooltip, icon):
531 data = {'parent': parent, 'child': child}
532 item = create_item(title, data)
533 if tooltip:
534 item.setToolTip(tooltip)
535 if icon:
536 icon_func = getattr(icons, icon, None)
537 item.setIcon(icon_func())
539 return item
542 def create_item(name, data):
543 item = QtGui.QStandardItem()
545 item.setEditable(False)
546 item.setDragEnabled(True)
547 item.setText(N_(name))
548 item.setData(data, Qt.UserRole)
550 return item