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
= 0
174 STYLE_TEXT_BESIDE_ICON
= 3
175 STYLE_TEXT_UNDER_ICON
= 4
177 N_('Follow System Style'),
180 N_('Text Beside Icon'),
181 N_('Text Under Icon'),
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
):
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
):
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
):
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
)
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
)
239 command_icon
= command
.get('icon', None)
241 icon
= getattr(icons
, command_icon
, None)
245 toolbar_action
= self
.addAction(icon
, title
, callback
)
247 toolbar_action
= self
.addAction(title
, callback
)
249 toolbar_action
.setData(data
)
251 tooltip
= command
.get('tooltip', None)
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
:
272 elif toolbar_area
== Qt
.RightToolBarArea
:
274 elif toolbar_area
== Qt
.TopToolBarArea
:
276 elif toolbar_area
== Qt
.BottomToolBarArea
:
278 else: # fallback to "bottom"
283 def decode_toolbar_area(string
):
284 """Decode an encoded toolbar area string into a Qt::ToolBarArea"""
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
294 result
= Qt
.BottomToolBarArea
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(
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 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):
423 def __init__(self
, widget
, Base
):
427 widget
.setAcceptDrops(True)
428 widget
.setSelectionMode(widget
.SingleSelection
)
429 widget
.setDragEnabled(True)
430 widget
.setDropIndicatorShown(True)
432 def dragEnterEvent(self
, event
):
434 self
.Base
.dragEnterEvent(widget
, event
)
436 def dragMoveEvent(self
, event
):
438 self
.Base
.dragMoveEvent(widget
, event
)
440 def dragLeaveEvent(self
, event
):
442 self
.Base
.dragLeaveEvent(widget
, event
)
444 def dropEvent(self
, event
):
446 event
.setDropAction(Qt
.MoveAction
)
447 self
.Base
.dropEvent(widget
, event
)
452 items
= [base
.item(widget
, i
) for i
in range(base
.count(widget
))]
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()
483 item
.setData(Qt
.UserRole
, data
)
487 def add_item(self
, title
, tooltip
, data
, icon
):
488 item
= QtWidgets
.QListWidgetItem()
489 item
.setText(N_(title
))
490 item
.setData(Qt
.UserRole
, data
)
492 item
.setToolTip(tooltip
)
495 icon_func
= getattr(icons
, icon
)
496 item
.setIcon(icon_func())
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
)
530 def create_child(parent
, child
, title
, tooltip
, icon
):
531 data
= {'parent': parent
, 'child': child
}
532 item
= create_item(title
, data
)
534 item
.setToolTip(tooltip
)
536 icon_func
= getattr(icons
, icon
, None)
537 item
.setIcon(icon_func())
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
)