1 # Copyright (c) 2008 David Aguilar
2 """This module provides miscellaneous Qt utility functions.
4 from __future__
import division
, absolute_import
, unicode_literals
8 from PyQt4
import QtGui
9 from PyQt4
import QtCore
10 from PyQt4
.QtCore
import Qt
11 from PyQt4
.QtCore
import SIGNAL
14 from cola
import gitcfg
15 from cola
import hotkeys
16 from cola
import icons
17 from cola
import utils
18 from cola
import resources
19 from cola
.i18n
import N_
20 from cola
.interaction
import Interaction
21 from cola
.compat
import ustr
22 from cola
.compat
import PY3
23 from cola
.models
import prefs
24 from cola
.widgets
import defs
27 def connect_action(action
, fn
):
28 """Connectc an action to a function"""
29 action
.connect(action
, SIGNAL('triggered()'), fn
)
32 def connect_action_bool(action
, fn
):
33 """Connect a triggered(bool) action to a function"""
34 action
.connect(action
, SIGNAL('triggered(bool)'), fn
)
37 def connect_button(button
, fn
):
38 """Connect a button to a function"""
39 button
.connect(button
, SIGNAL('clicked()'), fn
)
42 def button_action(button
, action
):
43 """Make a button trigger an action"""
44 connect_button(button
, action
.trigger
)
47 def connect_toggle(toggle
, fn
):
48 toggle
.connect(toggle
, SIGNAL('toggled(bool)'), fn
)
52 return QtGui
.QApplication
.activeWindow()
55 def hbox(margin
, spacing
, *items
):
56 return box(QtGui
.QHBoxLayout
, margin
, spacing
, *items
)
59 def vbox(margin
, spacing
, *items
):
60 return box(QtGui
.QVBoxLayout
, margin
, spacing
, *items
)
67 def box(cls
, margin
, spacing
, *items
):
71 layout
.setMargin(margin
)
72 layout
.setSpacing(spacing
)
77 int_types
= (int, long)
80 if isinstance(i
, QtGui
.QWidget
):
82 elif isinstance(i
, (QtGui
.QHBoxLayout
, QtGui
.QVBoxLayout
,
83 QtGui
.QFormLayout
, QtGui
.QLayout
)):
89 elif isinstance(i
, int_types
):
95 def form(margin
, spacing
, *widgets
):
96 layout
= QtGui
.QFormLayout()
97 layout
.setMargin(margin
)
98 layout
.setSpacing(spacing
)
99 layout
.setFieldGrowthPolicy(QtGui
.QFormLayout
.ExpandingFieldsGrow
)
101 for idx
, (label
, widget
) in enumerate(widgets
):
102 if isinstance(label
, (str, ustr
)):
103 layout
.addRow(label
, widget
)
105 layout
.setWidget(idx
, QtGui
.QFormLayout
.LabelRole
, label
)
106 layout
.setWidget(idx
, QtGui
.QFormLayout
.FieldRole
, widget
)
111 def grid(margin
, spacing
, *widgets
):
112 layout
= QtGui
.QGridLayout()
113 layout
.setMargin(defs
.no_margin
)
114 layout
.setSpacing(defs
.spacing
)
118 if isinstance(item
, QtGui
.QWidget
):
119 layout
.addWidget(*row
)
120 elif isinstance(item
, QtGui
.QLayoutItem
):
126 def splitter(orientation
, *widgets
):
127 layout
= QtGui
.QSplitter()
128 layout
.setOrientation(orientation
)
129 layout
.setHandleWidth(defs
.handle_width
)
130 layout
.setChildrenCollapsible(True)
131 for idx
, widget
in enumerate(widgets
):
132 layout
.addWidget(widget
)
133 layout
.setStretchFactor(idx
, 1)
138 def prompt(msg
, title
=None, text
=''):
139 """Presents the user with an input widget and returns the input."""
142 result
= QtGui
.QInputDialog
.getText(active_window(), msg
, title
,
143 QtGui
.QLineEdit
.Normal
, text
)
144 return (ustr(result
[0]), result
[1])
147 def create_listwidget_item(text
, filename
):
148 """Creates a QListWidgetItem with text and the icon at filename."""
149 item
= QtGui
.QListWidgetItem()
150 item
.setIcon(QtGui
.QIcon(filename
))
151 item
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
156 class TreeWidgetItem(QtGui
.QTreeWidgetItem
):
158 TYPE
= QtGui
.QStandardItem
.UserType
+ 101
160 def __init__(self
, path
, icon
, deleted
):
161 QtGui
.QTreeWidgetItem
.__init
__(self
)
163 self
.deleted
= deleted
164 self
.setIcon(0, icons
.from_name(icon
))
165 self
.setText(0, path
)
171 def paths_from_indexes(model
, indexes
,
172 item_type
=TreeWidgetItem
.TYPE
,
174 """Return paths from a list of QStandardItemModel indexes"""
175 items
= [model
.itemFromIndex(i
) for i
in indexes
]
176 return paths_from_items(items
, item_type
=item_type
, item_filter
=item_filter
)
179 def paths_from_items(items
,
180 item_type
=TreeWidgetItem
.TYPE
,
182 """Return a list of paths from a list of items"""
183 if item_filter
is None:
184 item_filter
= lambda x
: True
185 return [i
.path
for i
in items
186 if i
.type() == item_type
and item_filter(i
)]
189 def confirm(title
, text
, informative_text
, ok_text
,
190 icon
=None, default
=True,
191 cancel_text
=None, cancel_icon
=None):
192 """Confirm that an action should take place"""
193 msgbox
= QtGui
.QMessageBox(active_window())
194 msgbox
.setWindowModality(Qt
.WindowModal
)
195 msgbox
.setWindowTitle(title
)
197 msgbox
.setInformativeText(informative_text
)
199 icon
= icons
.mkicon(icon
, icons
.ok
)
200 ok
= msgbox
.addButton(ok_text
, QtGui
.QMessageBox
.ActionRole
)
203 cancel
= msgbox
.addButton(QtGui
.QMessageBox
.Cancel
)
204 cancel_icon
= icons
.mkicon(cancel_icon
, icons
.close
)
205 cancel
.setIcon(cancel_icon
)
207 cancel
.setText(cancel_text
)
210 msgbox
.setDefaultButton(ok
)
212 msgbox
.setDefaultButton(cancel
)
214 return msgbox
.clickedButton() == ok
217 class ResizeableMessageBox(QtGui
.QMessageBox
):
219 def __init__(self
, parent
):
220 QtGui
.QMessageBox
.__init
__(self
, parent
)
221 self
.setMouseTracking(True)
222 self
.setSizeGripEnabled(True)
224 def event(self
, event
):
225 res
= QtGui
.QMessageBox
.event(self
, event
)
226 event_type
= event
.type()
227 if (event_type
== QtCore
.QEvent
.MouseMove
or
228 event_type
== QtCore
.QEvent
.MouseButtonPress
):
229 maxi
= QtCore
.QSize(defs
.max_size
, defs
.max_size
)
230 self
.setMaximumSize(maxi
)
231 text
= self
.findChild(QtGui
.QTextEdit
)
233 expand
= QtGui
.QSizePolicy
.Expanding
234 text
.setSizePolicy(QtGui
.QSizePolicy(expand
, expand
))
235 text
.setMaximumSize(maxi
)
239 def critical(title
, message
=None, details
=None):
240 """Show a warning with the provided title and message."""
243 mbox
= ResizeableMessageBox(active_window())
244 mbox
.setWindowTitle(title
)
245 mbox
.setTextFormat(Qt
.PlainText
)
246 mbox
.setText(message
)
247 mbox
.setIcon(QtGui
.QMessageBox
.Critical
)
248 mbox
.setStandardButtons(QtGui
.QMessageBox
.Close
)
249 mbox
.setDefaultButton(QtGui
.QMessageBox
.Close
)
251 mbox
.setDetailedText(details
)
255 def information(title
, message
=None, details
=None, informative_text
=None):
256 """Show information with the provided title and message."""
259 mbox
= QtGui
.QMessageBox(active_window())
260 mbox
.setStandardButtons(QtGui
.QMessageBox
.Close
)
261 mbox
.setDefaultButton(QtGui
.QMessageBox
.Close
)
262 mbox
.setWindowTitle(title
)
263 mbox
.setWindowModality(Qt
.WindowModal
)
264 mbox
.setTextFormat(Qt
.PlainText
)
265 mbox
.setText(message
)
267 mbox
.setInformativeText(informative_text
)
269 mbox
.setDetailedText(details
)
270 # Render into a 1-inch wide pixmap
271 pixmap
= icons
.cola().pixmap(defs
.large_icon
)
272 mbox
.setIconPixmap(pixmap
)
276 def question(title
, msg
, default
=True):
277 """Launches a QMessageBox question with the provided title and message.
278 Passing "default=False" will make "No" the default choice."""
279 yes
= QtGui
.QMessageBox
.Yes
280 no
= QtGui
.QMessageBox
.No
286 result
= (QtGui
.QMessageBox
287 .question(active_window(), title
, msg
, buttons
, default
))
288 return result
== QtGui
.QMessageBox
.Yes
291 def tree_selection(tree_item
, items
):
292 """Returns an array of model items that correspond to the selected
293 QTreeWidgetItem children"""
295 count
= min(tree_item
.childCount(), len(items
))
296 for idx
in range(count
):
297 if tree_item
.child(idx
).isSelected():
298 selected
.append(items
[idx
])
303 def tree_selection_items(tree_item
):
304 """Returns selected widget items"""
306 for idx
in range(tree_item
.childCount()):
307 child
= tree_item
.child(idx
)
308 if child
.isSelected():
309 selected
.append(child
)
314 def selected_item(list_widget
, items
):
315 """Returns the model item that corresponds to the selected QListWidget
317 widget_items
= list_widget
.selectedItems()
320 widget_item
= widget_items
[0]
321 row
= list_widget
.row(widget_item
)
328 def selected_items(list_widget
, items
):
329 """Returns an array of model items that correspond to the selected
331 item_count
= len(items
)
333 for widget_item
in list_widget
.selectedItems():
334 row
= list_widget
.row(widget_item
)
336 selected
.append(items
[row
])
340 def open_file(title
, directory
=None):
341 """Creates an Open File dialog and returns a filename."""
342 return ustr(QtGui
.QFileDialog
343 .getOpenFileName(active_window(), title
, directory
))
346 def open_files(title
, directory
=None, filter=None):
347 """Creates an Open File dialog and returns a list of filenames."""
348 return (QtGui
.QFileDialog
349 .getOpenFileNames(active_window(), title
, directory
, filter))
352 def opendir_dialog(title
, path
):
353 """Prompts for a directory path"""
355 flags
= (QtGui
.QFileDialog
.ShowDirsOnly |
356 QtGui
.QFileDialog
.DontResolveSymlinks
)
357 return ustr(QtGui
.QFileDialog
358 .getExistingDirectory(active_window(),
362 def save_as(filename
, title
='Save As...'):
363 """Creates a Save File dialog and returns a filename."""
364 return ustr(QtGui
.QFileDialog
365 .getSaveFileName(active_window(), title
, filename
))
368 def copy_path(filename
, absolute
=True):
369 """Copy a filename to the clipboard"""
373 filename
= core
.abspath(filename
)
374 set_clipboard(filename
)
377 def set_clipboard(text
):
378 """Sets the copy/paste buffer to text."""
381 clipboard
= QtGui
.QApplication
.clipboard()
382 clipboard
.setText(text
, QtGui
.QClipboard
.Clipboard
)
383 clipboard
.setText(text
, QtGui
.QClipboard
.Selection
)
387 def persist_clipboard():
388 """Persist the clipboard
390 X11 stores only a reference to the clipboard data.
391 Send a clipboard event to force a copy of the clipboard to occur.
392 This ensures that the clipboard is present after git-cola exits.
393 Otherwise, the reference is destroyed on exit.
395 C.f. https://stackoverflow.com/questions/2007103/how-can-i-disable-clear-of-clipboard-on-exit-of-pyqt4-application
398 clipboard
= QtGui
.QApplication
.clipboard()
399 event
= QtCore
.QEvent(QtCore
.QEvent
.Clipboard
)
400 QtGui
.QApplication
.sendEvent(clipboard
, event
)
403 def add_action_bool(widget
, text
, fn
, checked
, *shortcuts
):
405 action
= _add_action(widget
, text
, tip
, fn
, connect_action_bool
, *shortcuts
)
406 action
.setCheckable(True)
407 action
.setChecked(checked
)
411 def add_action(widget
, text
, fn
, *shortcuts
):
413 return _add_action(widget
, text
, tip
, fn
, connect_action
, *shortcuts
)
416 def add_action_with_status_tip(widget
, text
, tip
, fn
, *shortcuts
):
417 return _add_action(widget
, text
, tip
, fn
, connect_action
, *shortcuts
)
420 def _add_action(widget
, text
, tip
, fn
, connect
, *shortcuts
):
421 action
= QtGui
.QAction(text
, widget
)
423 action
.setStatusTip(tip
)
426 action
.setShortcuts(shortcuts
)
427 if hasattr(Qt
, 'WidgetWithChildrenShortcut'):
428 action
.setShortcutContext(Qt
.WidgetWithChildrenShortcut
)
429 widget
.addAction(action
)
433 def set_selected_item(widget
, idx
):
434 """Sets a the currently selected item to the item at index idx."""
435 if type(widget
) is QtGui
.QTreeWidget
:
436 item
= widget
.topLevelItem(idx
)
438 widget
.setItemSelected(item
, True)
439 widget
.setCurrentItem(item
)
442 def add_items(widget
, items
):
443 """Adds items to a widget."""
450 def set_items(widget
, items
):
451 """Clear the existing widget contents and set the new items."""
453 add_items(widget
, items
)
457 def create_treeitem(filename
, staged
=False, deleted
=False, untracked
=False):
458 """Given a filename, return a TreeWidgetItem for a status widget
460 "staged", "deleted, and "untracked" control which icon is used.
463 icon_name
= icons
.status(filename
, deleted
, staged
, untracked
)
464 return TreeWidgetItem(filename
, resources
.icon(icon_name
), deleted
=deleted
)
467 def add_close_action(widget
):
468 """Adds close action and shortcuts to a widget."""
469 return add_action(widget
, N_('Close...'),
470 widget
.close
, hotkeys
.CLOSE
, hotkeys
.QUIT
)
473 def center_on_screen(widget
):
474 """Move widget to the center of the default screen"""
475 desktop
= QtGui
.QApplication
.instance().desktop()
476 rect
= desktop
.screenGeometry(QtGui
.QCursor().pos())
477 cy
= rect
.height()//2
479 widget
.move(cx
- widget
.width()//2, cy
- widget
.height()//2)
482 def default_size(parent
, width
, height
):
483 """Return the parent's size, or the provided defaults"""
484 if parent
is not None:
485 width
= parent
.width()
486 height
= parent
.height()
487 return (width
, height
)
490 def default_monospace_font():
493 if utils
.is_darwin():
495 font
.setFamily(family
)
500 font_str
= gitcfg
.current().get(prefs
.FONTDIFF
)
502 font
= default_monospace_font()
503 font_str
= ustr(font
.toString())
508 return font(diff_font_str())
513 font
.fromString(string
)
517 def create_button(text
='', layout
=None, tooltip
=None, icon
=None,
518 enabled
=True, default
=False):
519 """Create a button, set its title, and add it to the parent."""
520 button
= QtGui
.QPushButton()
521 button
.setCursor(Qt
.PointingHandCursor
)
526 button
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
527 if tooltip
is not None:
528 button
.setToolTip(tooltip
)
529 if layout
is not None:
530 layout
.addWidget(button
)
532 button
.setEnabled(False)
534 button
.setDefault(True)
538 def create_action_button(tooltip
=None, icon
=None):
539 button
= QtGui
.QPushButton()
540 button
.setCursor(Qt
.PointingHandCursor
)
542 if tooltip
is not None:
543 button
.setToolTip(tooltip
)
546 button
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
550 def ok_button(text
, default
=False, enabled
=True):
551 return create_button(text
=text
, icon
=icons
.ok(),
552 default
=default
, enabled
=enabled
)
556 return create_button(text
=N_('Close'), icon
=icons
.close())
559 def edit_button(enabled
=True, default
=False):
560 return create_button(text
=N_('Edit'), icon
=icons
.edit(),
561 enabled
=enabled
, default
=default
)
564 def refresh_button(enabled
=True, default
=False):
565 return create_button(text
=N_('Refresh'), icon
=icons
.sync(),
566 enabled
=enabled
, default
=default
)
569 def hide_button_menu_indicator(button
):
573 %(name)s::menu-indicator {
577 if name
== 'QPushButton':
583 button
.setStyleSheet(stylesheet
% {'name': name
})
586 def checkbox(text
='', tooltip
='', checked
=None):
587 cb
= QtGui
.QCheckBox()
591 cb
.setToolTip(tooltip
)
592 if checked
is not None:
593 cb
.setChecked(checked
)
595 url
= icons
.check_name()
597 QCheckBox::indicator {
601 QCheckBox::indicator::unchecked {
602 border: %(border)dpx solid #999;
605 QCheckBox::indicator::checked {
607 border: %(border)dpx solid black;
610 """ % dict(size
=defs
.checkbox
, border
=defs
.border
, url
=url
)
611 cb
.setStyleSheet(style
)
616 def radio(text
='', tooltip
='', checked
=None):
617 rb
= QtGui
.QRadioButton()
621 rb
.setToolTip(tooltip
)
622 if checked
is not None:
623 rb
.setChecked(checked
)
627 border
= defs
.radio_border
628 url
= icons
.dot_name()
630 QRadioButton::indicator {
634 QRadioButton::indicator::unchecked {
636 border: %(border)dpx solid #999;
637 border-radius: %(radius)dpx;
639 QRadioButton::indicator::checked {
642 border: %(border)dpx solid black;
643 border-radius: %(radius)dpx;
645 """ % dict(size
=size
, radius
=radius
, border
=border
, url
=url
)
646 rb
.setStyleSheet(style
)
651 class DockTitleBarWidget(QtGui
.QWidget
):
653 def __init__(self
, parent
, title
, stretch
=True):
654 QtGui
.QWidget
.__init
__(self
, parent
)
655 self
.label
= label
= QtGui
.QLabel()
660 label
.setCursor(Qt
.OpenHandCursor
)
662 self
.close_button
= create_action_button(
663 tooltip
=N_('Close'), icon
=icons
.close())
665 self
.toggle_button
= create_action_button(
666 tooltip
=N_('Detach'), icon
=icons
.external())
668 self
.corner_layout
= hbox(defs
.no_margin
, defs
.spacing
)
675 self
.main_layout
= hbox(defs
.small_margin
, defs
.spacing
,
676 label
, separator
, self
.corner_layout
,
677 self
.toggle_button
, self
.close_button
)
678 self
.setLayout(self
.main_layout
)
680 connect_button(self
.toggle_button
, self
.toggle_floating
)
681 connect_button(self
.close_button
, self
.toggle_visibility
)
683 def toggle_floating(self
):
684 self
.parent().setFloating(not self
.parent().isFloating())
685 self
.update_tooltips()
687 def toggle_visibility(self
):
688 self
.parent().toggleViewAction().trigger()
690 def set_title(self
, title
):
691 self
.label
.setText(title
)
693 def add_corner_widget(self
, widget
):
694 self
.corner_layout
.addWidget(widget
)
696 def update_tooltips(self
):
697 if self
.parent().isFloating():
698 tooltip
= N_('Attach')
700 tooltip
= N_('Detach')
701 self
.toggle_button
.setToolTip(tooltip
)
704 def create_dock(title
, parent
, stretch
=True):
705 """Create a dock widget and set it up accordingly."""
706 dock
= QtGui
.QDockWidget(parent
)
707 dock
.setWindowTitle(title
)
708 dock
.setObjectName(title
)
709 titlebar
= DockTitleBarWidget(dock
, title
, stretch
=stretch
)
710 dock
.setTitleBarWidget(titlebar
)
711 if hasattr(parent
, 'dockwidgets'):
712 parent
.dockwidgets
.append(dock
)
716 def create_menu(title
, parent
):
717 """Create a menu and set its title."""
718 qmenu
= QtGui
.QMenu(parent
)
719 qmenu
.setTitle(title
)
723 def create_toolbutton(text
=None, layout
=None, tooltip
=None, icon
=None):
724 button
= QtGui
.QToolButton()
725 button
.setAutoRaise(True)
726 button
.setAutoFillBackground(True)
727 button
.setCursor(Qt
.PointingHandCursor
)
730 button
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
733 button
.setToolButtonStyle(Qt
.ToolButtonTextBesideIcon
)
734 if tooltip
is not None:
735 button
.setToolTip(tooltip
)
736 if layout
is not None:
737 layout
.addWidget(button
)
741 def mimedata_from_paths(paths
):
742 """Return mimedata with a list of absolute path URLs"""
744 abspaths
= [core
.abspath(path
) for path
in paths
]
745 urls
= [QtCore
.QUrl
.fromLocalFile(path
) for path
in abspaths
]
747 mimedata
= QtCore
.QMimeData()
748 mimedata
.setUrls(urls
)
750 # The text/x-moz-list format is always included by Qt, and doing
751 # mimedata.removeFormat('text/x-moz-url') has no effect.
752 # C.f. http://www.qtcentre.org/threads/44643-Dragging-text-uri-list-Qt-inserts-garbage
754 # gnome-terminal expects utf-16 encoded text, but other terminals,
755 # e.g. terminator, prefer utf-8, so allow cola.dragencoding
756 # to override the default.
757 paths_text
= subprocess
.list2cmdline(abspaths
)
758 encoding
= gitcfg
.current().get('cola.dragencoding', 'utf-16')
759 moz_text
= core
.encode(paths_text
, encoding
=encoding
)
760 mimedata
.setData('text/x-moz-url', moz_text
)
765 def path_mimetypes():
766 return ['text/uri-list', 'text/x-moz-url']
769 class BlockSignals(object):
770 """Context manager for blocking a signals on a widget"""
772 def __init__(self
, *widgets
):
773 self
.widgets
= widgets
777 for w
in self
.widgets
:
778 self
.values
[w
] = w
.blockSignals(True)
781 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
782 for w
in self
.widgets
:
783 w
.blockSignals(self
.values
[w
])
786 class Task(QtCore
.QRunnable
):
787 """Disable auto-deletion to avoid gc issues
789 Python's garbage collector will try to double-free the task
790 once it's finished, so disable Qt's auto-deletion as a workaround.
794 FINISHED
= SIGNAL('TASK_FINISHED')
795 RESULT
= SIGNAL('TASK_RESULT')
797 def __init__(self
, parent
, *args
, **kwargs
):
798 QtCore
.QRunnable
.__init
__(self
)
800 self
.channel
= QtCore
.QObject(parent
)
802 self
.setAutoDelete(False)
805 self
.result
= self
.task()
806 self
.channel
.emit(self
.RESULT
, self
.result
)
813 self
.channel
.emit(self
.FINISHED
, self
)
815 def connect(self
, handler
):
816 self
.channel
.connect(self
.channel
, self
.RESULT
,
817 handler
, Qt
.QueuedConnection
)
820 class SimpleTask(Task
):
821 """Run a simple callable as a task"""
823 def __init__(self
, parent
, fn
, *args
, **kwargs
):
824 Task
.__init
__(self
, parent
)
831 return self
.fn(*self
.args
, **self
.kwargs
)
834 class RunTask(QtCore
.QObject
):
835 """Runs QRunnable instances and transfers control when they finish"""
837 def __init__(self
, parent
=None):
838 QtCore
.QObject
.__init
__(self
, parent
)
840 self
.task_details
= {}
841 self
.threadpool
= QtCore
.QThreadPool
.globalInstance()
843 def start(self
, task
, progress
=None, finish
=None):
844 """Start the task and register a callback"""
845 if progress
is not None:
847 # prevents garbage collection bugs in certain PyQt4 versions
848 self
.tasks
.append(task
)
850 self
.task_details
[task_id
] = (progress
, finish
)
852 self
.connect(task
.channel
, Task
.FINISHED
, self
.finish
,
854 self
.threadpool
.start(task
)
856 def finish(self
, task
, *args
, **kwargs
):
859 self
.tasks
.remove(task
)
863 progress
, finish
= self
.task_details
[task_id
]
864 del self
.task_details
[task_id
]
866 finish
= progress
= None
868 if progress
is not None:
871 if finish
is not None:
872 finish(task
, *args
, **kwargs
)
875 # Syntax highlighting
877 def rgba(r
, g
, b
, a
=255):
888 def make_format(fg
=None, bg
=None, bold
=False):
889 fmt
= QtGui
.QTextCharFormat()
891 fmt
.setForeground(fg
)
893 fmt
.setBackground(bg
)
895 fmt
.setFontWeight(QtGui
.QFont
.Bold
)
900 Interaction
.critical
= staticmethod(critical
)
901 Interaction
.confirm
= staticmethod(confirm
)
902 Interaction
.question
= staticmethod(question
)
903 Interaction
.information
= staticmethod(information
)