1 from __future__
import absolute_import
, division
, print_function
, unicode_literals
2 from functools
import partial
5 from qtpy
.QtCore
import Qt
6 from qtpy
import QtWidgets
9 from ..widgets
import standard
11 from .. import qtutils
12 from .toolbarcmds
import COMMANDS
16 'Others': ['Others::LaunchEditor', 'Others::RevertUnstagedEdits'],
20 'File::OpenRepoNewWindow',
23 'File::RecentModified',
26 'File::ExportPatches',
34 'Actions::CherryPick',
36 'Actions::AbortMerge',
37 'Actions::UpdateSubmodules',
44 'Commit::UndoLastCommit',
48 'Commit::LoadCommitMessage',
49 'Commit::GetCommitMessageTemplate',
51 'Diff': ['Diff::Difftool', 'Diff::Expression', 'Diff::Branches', 'Diff::Diffstat'],
57 'Branch::DeleteRemote',
59 'Branch::BrowseCurrent',
60 'Branch::BrowseOther',
61 'Branch::VisualizeCurrent',
62 'Branch::VisualizeAll',
65 'Commit::UndoLastCommit',
68 'Actions::ResetMixed',
69 'Actions::RestoreWorktree',
73 'View': ['View::DAG', 'View::FileBrowser'],
77 def configure(toolbar
, parent
=None):
78 """Launches the Toolbar configure dialog"""
80 parent
= qtutils
.active_window()
81 view
= ToolbarView(toolbar
, parent
)
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
)
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
106 def apply_state(self
, toolbars
):
107 context
= self
.context
110 for data
in toolbars
:
111 toolbar
= ToolBar
.create(context
, data
['name'])
112 toolbar
.load_items(data
['items'])
114 toolbar
.set_toolbar_style(data
['toolbar_style'])
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
)
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'])
127 widget
.addToolBarBreak(toolbar_area
)
128 widget
.addToolBar(toolbar_area
, toolbar
)
130 # floating toolbars must be set after added
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
137 def export_state(self
):
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()]
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(),
169 class ToolBar(QtWidgets
.QToolBar
):
170 SEPARATOR
= 'Separator'
171 STYLE_FOLLOW_SYSTEM
= 'follow-system'
172 STYLE_ICON_ONLY
= 'icon'
173 STYLE_TEXT_ONLY
= 'text'
174 STYLE_TEXT_BESIDE_ICON
= 'text-beside-icon'
175 STYLE_TEXT_UNDER_ICON
= 'text-under-icon'
177 N_('Follow System Style'),
180 N_('Text Beside Icon'),
181 N_('Text Under Icon'),
187 STYLE_TEXT_BESIDE_ICON
,
188 STYLE_TEXT_UNDER_ICON
,
192 def create(context
, name
):
193 return ToolBar(context
, name
, TREE_LAYOUT
, COMMANDS
)
195 def __init__(self
, context
, title
, tree_layout
, toolbar_commands
):
196 QtWidgets
.QToolBar
.__init
__(self
)
197 self
.setWindowTitle(title
)
198 self
.setObjectName(title
)
199 self
.setToolButtonStyle(Qt
.ToolButtonFollowStyle
)
201 self
.context
= context
202 self
.tree_layout
= tree_layout
203 self
.commands
= toolbar_commands
205 def set_toolbar_style(self
, style_id
):
207 self
.STYLE_FOLLOW_SYSTEM
: Qt
.ToolButtonFollowStyle
,
208 self
.STYLE_ICON_ONLY
: Qt
.ToolButtonIconOnly
,
209 self
.STYLE_TEXT_ONLY
: Qt
.ToolButtonTextOnly
,
210 self
.STYLE_TEXT_BESIDE_ICON
: Qt
.ToolButtonTextBesideIcon
,
211 self
.STYLE_TEXT_UNDER_ICON
: Qt
.ToolButtonTextUnderIcon
,
213 default
= Qt
.ToolButtonFollowStyle
214 return self
.setToolButtonStyle(style_to_qt
.get(style_id
, default
))
216 def toolbar_style(self
):
218 Qt
.ToolButtonFollowStyle
: self
.STYLE_FOLLOW_SYSTEM
,
219 Qt
.ToolButtonIconOnly
: self
.STYLE_ICON_ONLY
,
220 Qt
.ToolButtonTextOnly
: self
.STYLE_TEXT_ONLY
,
221 Qt
.ToolButtonTextBesideIcon
: self
.STYLE_TEXT_BESIDE_ICON
,
222 Qt
.ToolButtonTextUnderIcon
: self
.STYLE_TEXT_UNDER_ICON
,
224 default
= self
.STYLE_FOLLOW_SYSTEM
225 return qt_to_style
.get(self
.toolButtonStyle(), default
)
227 def load_items(self
, items
):
229 self
.add_action_from_data(data
)
231 def add_action_from_data(self
, data
):
232 parent
= data
['parent']
233 child
= data
['child']
235 if child
== self
.SEPARATOR
:
236 toolbar_action
= self
.addSeparator()
237 toolbar_action
.setData(data
)
240 tree_items
= self
.tree_layout
.get(parent
, [])
241 if child
in tree_items
and child
in self
.commands
:
242 command
= self
.commands
[child
]
243 title
= N_(command
['title'])
244 callback
= partial(command
['action'], self
.context
)
247 command_icon
= command
.get('icon', None)
249 icon
= getattr(icons
, command_icon
, None)
253 toolbar_action
= self
.addAction(icon
, title
, callback
)
255 toolbar_action
= self
.addAction(title
, callback
)
257 toolbar_action
.setData(data
)
259 tooltip
= command
.get('tooltip', None)
261 toolbar_action
.setToolTip('%s\n%s' % (title
, tooltip
))
263 def delete_toolbar(self
):
264 self
.parent().removeToolBar(self
)
266 def contextMenuEvent(self
, event
):
267 menu
= QtWidgets
.QMenu()
268 tool_config
= menu
.addAction(N_('Configure Toolbar'), partial(configure
, self
))
269 tool_config
.setIcon(icons
.configure())
270 tool_delete
= menu
.addAction(N_('Delete Toolbar'), self
.delete_toolbar
)
271 tool_delete
.setIcon(icons
.remove())
273 menu
.exec_(event
.globalPos())
276 def encode_toolbar_area(toolbar_area
):
277 """Encode a Qt::ToolBarArea as a string"""
280 Qt
.LeftToolBarArea
: 'left',
281 Qt
.RightToolBarArea
: 'right',
282 Qt
.TopToolBarArea
: 'top',
283 Qt
.BottomToolBarArea
: 'bottom',
284 }.get(toolbar_area
, default
)
287 def decode_toolbar_area(string
):
288 """Decode an encoded toolbar area string into a Qt::ToolBarArea"""
289 default
= Qt
.BottomToolBarArea
291 'left': Qt
.LeftToolBarArea
,
292 'right': Qt
.RightToolBarArea
,
293 'top': Qt
.TopToolBarArea
,
294 'bottom': Qt
.BottomToolBarArea
,
295 }.get(string
, default
)
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
.left_list
= ToolbarTreeWidget(self
)
309 self
.right_list
= DraggableListWidget(self
)
310 self
.text_toolbar_name
= QtWidgets
.QLabel()
311 self
.text_toolbar_name
.setText(N_('Name'))
312 self
.toolbar_name
= QtWidgets
.QLineEdit()
313 self
.toolbar_name
.setText(toolbar
.windowTitle())
314 self
.add_separator
= qtutils
.create_button(N_('Add Separator'))
315 self
.remove_item
= qtutils
.create_button(N_('Remove Element'))
316 self
.toolbar_style_label
= QtWidgets
.QLabel(N_('Toolbar Style:'))
317 self
.toolbar_style
= QtWidgets
.QComboBox()
318 for style_name
in ToolBar
.STYLE_NAMES
:
319 self
.toolbar_style
.addItem(style_name
)
320 style_idx
= get_index_from_style(toolbar
.toolbar_style())
321 self
.toolbar_style
.setCurrentIndex(style_idx
)
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(
342 self
.toolbar_style_label
,
348 self
.main_layout
= qtutils
.vbox(
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():
371 if data
['child'] == self
.toolbar
.SEPARATOR
:
372 self
.add_separator_action()
375 child_data
= data
['child']
376 command
= commands
[child_data
]
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
]:
390 command
= commands
[item
]
393 icon
= command
.get('icon', None)
394 tooltip
= command
.get('tooltip', None)
395 child
= create_child(parent
, item
, command
['title'], tooltip
, icon
)
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()
408 self
.right_list
.takeItem(self
.right_list
.row(item
))
410 def apply_action(self
):
412 style
= get_style_from_index(self
.toolbar_style
.currentIndex())
413 self
.toolbar
.set_toolbar_style(style
)
414 self
.toolbar
.setWindowTitle(self
.toolbar_name
.text())
416 for item
in self
.right_list
.get_items():
417 data
= item
.data(Qt
.UserRole
)
418 self
.toolbar
.add_action_from_data(data
)
421 def get_style_from_index(index
):
422 """Return the symbolic toolbar style name for the given (combobox) index"""
423 return ToolBar
.STYLE_SYMBOLS
[index
]
426 def get_index_from_style(style
):
427 """Return the toolbar style (combobox) index for the symbolic name"""
428 return ToolBar
.STYLE_SYMBOLS
.index(style
)
431 class DraggableListMixin(object):
434 def __init__(self
, widget
, Base
):
438 widget
.setAcceptDrops(True)
439 widget
.setSelectionMode(widget
.SingleSelection
)
440 widget
.setDragEnabled(True)
441 widget
.setDropIndicatorShown(True)
443 def dragEnterEvent(self
, event
):
445 self
.Base
.dragEnterEvent(widget
, event
)
447 def dragMoveEvent(self
, event
):
449 self
.Base
.dragMoveEvent(widget
, event
)
451 def dragLeaveEvent(self
, event
):
453 self
.Base
.dragLeaveEvent(widget
, event
)
455 def dropEvent(self
, event
):
457 event
.setDropAction(Qt
.MoveAction
)
458 self
.Base
.dropEvent(widget
, event
)
463 items
= [base
.item(widget
, i
) for i
in range(base
.count(widget
))]
468 # pylint: disable=too-many-ancestors
469 class DraggableListWidget(QtWidgets
.QListWidget
):
470 Mixin
= DraggableListMixin
472 def __init__(self
, parent
=None):
473 QtWidgets
.QListWidget
.__init
__(self
, parent
)
475 self
.setAcceptDrops(True)
476 self
.setSelectionMode(self
.SingleSelection
)
477 self
.setDragEnabled(True)
478 self
.setDropIndicatorShown(True)
480 self
._mixin
= self
.Mixin(self
, QtWidgets
.QListWidget
)
482 def dragEnterEvent(self
, event
):
483 return self
._mixin
.dragEnterEvent(event
)
485 def dragMoveEvent(self
, event
):
486 return self
._mixin
.dragMoveEvent(event
)
488 def dropEvent(self
, event
):
489 return self
._mixin
.dropEvent(event
)
491 def add_separator(self
, title
, data
):
492 item
= QtWidgets
.QListWidgetItem()
494 item
.setData(Qt
.UserRole
, data
)
498 def add_item(self
, title
, tooltip
, data
, icon
):
499 item
= QtWidgets
.QListWidgetItem()
500 item
.setText(N_(title
))
501 item
.setData(Qt
.UserRole
, data
)
503 item
.setToolTip(tooltip
)
506 icon_func
= getattr(icons
, icon
)
507 item
.setIcon(icon_func())
512 return self
._mixin
.get_items()
515 # pylint: disable=too-many-ancestors
516 class ToolbarTreeWidget(standard
.TreeView
):
517 def __init__(self
, parent
):
518 standard
.TreeView
.__init
__(self
, parent
)
520 self
.setDragEnabled(True)
521 self
.setDragDropMode(QtWidgets
.QAbstractItemView
.DragOnly
)
522 self
.setSelectionMode(QtWidgets
.QAbstractItemView
.SingleSelection
)
523 self
.setDropIndicatorShown(True)
524 self
.setRootIsDecorated(True)
525 self
.setHeaderHidden(True)
526 self
.setAlternatingRowColors(False)
527 self
.setSortingEnabled(False)
529 self
.setModel(QtGui
.QStandardItemModel())
531 def insert_top(self
, title
):
532 item
= create_item(title
, title
)
533 item
.setFlags(Qt
.ItemIsSelectable | Qt
.ItemIsEnabled
)
535 self
.model().insertRow(0, item
)
541 def create_child(parent
, child
, title
, tooltip
, icon
):
542 data
= {'parent': parent
, 'child': child
}
543 item
= create_item(title
, data
)
545 item
.setToolTip(tooltip
)
547 icon_func
= getattr(icons
, icon
, None)
548 item
.setIcon(icon_func())
553 def create_item(name
, data
):
554 item
= QtGui
.QStandardItem()
556 item
.setEditable(False)
557 item
.setDragEnabled(True)
558 item
.setText(N_(name
))
559 item
.setData(data
, Qt
.UserRole
)