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."""
448 def set_items(widget
, items
):
449 """Clear the existing widget contents and set the new items."""
451 add_items(widget
, items
)
455 def create_treeitem(filename
, staged
=False, deleted
=False, untracked
=False):
456 """Given a filename, return a TreeWidgetItem for a status widget
458 "staged", "deleted, and "untracked" control which icon is used.
461 icon_name
= icons
.status(filename
, deleted
, staged
, untracked
)
462 return TreeWidgetItem(filename
, resources
.icon(icon_name
), deleted
=deleted
)
465 def add_close_action(widget
):
466 """Adds close action and shortcuts to a widget."""
467 return add_action(widget
, N_('Close...'),
468 widget
.close
, hotkeys
.CLOSE
, hotkeys
.QUIT
)
471 def center_on_screen(widget
):
472 """Move widget to the center of the default screen"""
473 desktop
= QtGui
.QApplication
.instance().desktop()
474 rect
= desktop
.screenGeometry(QtGui
.QCursor().pos())
475 cy
= rect
.height()//2
477 widget
.move(cx
- widget
.width()//2, cy
- widget
.height()//2)
480 def default_size(parent
, width
, height
):
481 """Return the parent's size, or the provided defaults"""
482 if parent
is not None:
483 width
= parent
.width()
484 height
= parent
.height()
485 return (width
, height
)
488 def default_monospace_font():
491 if utils
.is_darwin():
493 font
.setFamily(family
)
498 font_str
= gitcfg
.current().get(prefs
.FONTDIFF
)
500 font
= default_monospace_font()
501 font_str
= ustr(font
.toString())
506 return font(diff_font_str())
511 font
.fromString(string
)
515 def create_button(text
='', layout
=None, tooltip
=None, icon
=None,
516 enabled
=True, default
=False):
517 """Create a button, set its title, and add it to the parent."""
518 button
= QtGui
.QPushButton()
519 button
.setCursor(Qt
.PointingHandCursor
)
524 button
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
525 if tooltip
is not None:
526 button
.setToolTip(tooltip
)
527 if layout
is not None:
528 layout
.addWidget(button
)
530 button
.setEnabled(False)
532 button
.setDefault(True)
536 def create_action_button(tooltip
=None, icon
=None):
537 button
= QtGui
.QPushButton()
538 button
.setCursor(Qt
.PointingHandCursor
)
540 if tooltip
is not None:
541 button
.setToolTip(tooltip
)
544 button
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
548 def ok_button(text
, default
=False, enabled
=True):
549 return create_button(text
=text
, icon
=icons
.ok(),
550 default
=default
, enabled
=enabled
)
554 return create_button(text
=N_('Close'), icon
=icons
.close())
557 def edit_button(enabled
=True, default
=False):
558 return create_button(text
=N_('Edit'), icon
=icons
.edit(),
559 enabled
=enabled
, default
=default
)
562 def refresh_button(enabled
=True, default
=False):
563 return create_button(text
=N_('Refresh'), icon
=icons
.sync(),
564 enabled
=enabled
, default
=default
)
567 def hide_button_menu_indicator(button
):
571 %(name)s::menu-indicator {
575 if name
== 'QPushButton':
581 button
.setStyleSheet(stylesheet
% {'name': name
})
584 def checkbox(text
='', tooltip
='', checked
=None):
585 cb
= QtGui
.QCheckBox()
589 cb
.setToolTip(tooltip
)
590 if checked
is not None:
591 cb
.setChecked(checked
)
593 url
= icons
.check_name()
595 QCheckBox::indicator {
599 QCheckBox::indicator::unchecked {
600 border: %(border)dpx solid #999;
603 QCheckBox::indicator::checked {
605 border: %(border)dpx solid black;
608 """ % dict(size
=defs
.checkbox
, border
=defs
.border
, url
=url
)
609 cb
.setStyleSheet(style
)
614 def radio(text
='', tooltip
='', checked
=None):
615 rb
= QtGui
.QRadioButton()
619 rb
.setToolTip(tooltip
)
620 if checked
is not None:
621 rb
.setChecked(checked
)
625 border
= defs
.radio_border
626 url
= icons
.dot_name()
628 QRadioButton::indicator {
632 QRadioButton::indicator::unchecked {
634 border: %(border)dpx solid #999;
635 border-radius: %(radius)dpx;
637 QRadioButton::indicator::checked {
640 border: %(border)dpx solid black;
641 border-radius: %(radius)dpx;
643 """ % dict(size
=size
, radius
=radius
, border
=border
, url
=url
)
644 rb
.setStyleSheet(style
)
649 class DockTitleBarWidget(QtGui
.QWidget
):
651 def __init__(self
, parent
, title
, stretch
=True):
652 QtGui
.QWidget
.__init
__(self
, parent
)
653 self
.label
= label
= QtGui
.QLabel()
658 label
.setCursor(Qt
.OpenHandCursor
)
660 self
.close_button
= create_action_button(
661 tooltip
=N_('Close'), icon
=icons
.close())
663 self
.toggle_button
= create_action_button(
664 tooltip
=N_('Detach'), icon
=icons
.external())
666 self
.corner_layout
= hbox(defs
.no_margin
, defs
.spacing
)
673 self
.main_layout
= hbox(defs
.small_margin
, defs
.spacing
,
674 label
, separator
, self
.corner_layout
,
675 self
.toggle_button
, self
.close_button
)
676 self
.setLayout(self
.main_layout
)
678 connect_button(self
.toggle_button
, self
.toggle_floating
)
679 connect_button(self
.close_button
, self
.toggle_visibility
)
681 def toggle_floating(self
):
682 self
.parent().setFloating(not self
.parent().isFloating())
683 self
.update_tooltips()
685 def toggle_visibility(self
):
686 self
.parent().toggleViewAction().trigger()
688 def set_title(self
, title
):
689 self
.label
.setText(title
)
691 def add_corner_widget(self
, widget
):
692 self
.corner_layout
.addWidget(widget
)
694 def update_tooltips(self
):
695 if self
.parent().isFloating():
696 tooltip
= N_('Attach')
698 tooltip
= N_('Detach')
699 self
.toggle_button
.setToolTip(tooltip
)
702 def create_dock(title
, parent
, stretch
=True):
703 """Create a dock widget and set it up accordingly."""
704 dock
= QtGui
.QDockWidget(parent
)
705 dock
.setWindowTitle(title
)
706 dock
.setObjectName(title
)
707 titlebar
= DockTitleBarWidget(dock
, title
, stretch
=stretch
)
708 dock
.setTitleBarWidget(titlebar
)
709 if hasattr(parent
, 'dockwidgets'):
710 parent
.dockwidgets
.append(dock
)
714 def create_menu(title
, parent
):
715 """Create a menu and set its title."""
716 qmenu
= QtGui
.QMenu(parent
)
717 qmenu
.setTitle(title
)
721 def create_toolbutton(text
=None, layout
=None, tooltip
=None, icon
=None):
722 button
= QtGui
.QToolButton()
723 button
.setAutoRaise(True)
724 button
.setAutoFillBackground(True)
725 button
.setCursor(Qt
.PointingHandCursor
)
728 button
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
731 button
.setToolButtonStyle(Qt
.ToolButtonTextBesideIcon
)
732 if tooltip
is not None:
733 button
.setToolTip(tooltip
)
734 if layout
is not None:
735 layout
.addWidget(button
)
739 def mimedata_from_paths(paths
):
740 """Return mimedata with a list of absolute path URLs"""
742 abspaths
= [core
.abspath(path
) for path
in paths
]
743 urls
= [QtCore
.QUrl
.fromLocalFile(path
) for path
in abspaths
]
745 mimedata
= QtCore
.QMimeData()
746 mimedata
.setUrls(urls
)
748 # The text/x-moz-list format is always included by Qt, and doing
749 # mimedata.removeFormat('text/x-moz-url') has no effect.
750 # C.f. http://www.qtcentre.org/threads/44643-Dragging-text-uri-list-Qt-inserts-garbage
752 # gnome-terminal expects utf-16 encoded text, but other terminals,
753 # e.g. terminator, prefer utf-8, so allow cola.dragencoding
754 # to override the default.
755 paths_text
= subprocess
.list2cmdline(abspaths
)
756 encoding
= gitcfg
.current().get('cola.dragencoding', 'utf-16')
757 moz_text
= core
.encode(paths_text
, encoding
=encoding
)
758 mimedata
.setData('text/x-moz-url', moz_text
)
763 def path_mimetypes():
764 return ['text/uri-list', 'text/x-moz-url']
767 class BlockSignals(object):
768 """Context manager for blocking a signals on a widget"""
770 def __init__(self
, *widgets
):
771 self
.widgets
= widgets
775 for w
in self
.widgets
:
776 self
.values
[w
] = w
.blockSignals(True)
779 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
780 for w
in self
.widgets
:
781 w
.blockSignals(self
.values
[w
])
784 class Task(QtCore
.QRunnable
):
785 """Disable auto-deletion to avoid gc issues
787 Python's garbage collector will try to double-free the task
788 once it's finished, so disable Qt's auto-deletion as a workaround.
792 FINISHED
= SIGNAL('TASK_FINISHED')
793 RESULT
= SIGNAL('TASK_RESULT')
795 def __init__(self
, parent
, *args
, **kwargs
):
796 QtCore
.QRunnable
.__init
__(self
)
798 self
.channel
= QtCore
.QObject(parent
)
800 self
.setAutoDelete(False)
803 self
.result
= self
.task()
804 self
.channel
.emit(self
.RESULT
, self
.result
)
811 self
.channel
.emit(self
.FINISHED
, self
)
813 def connect(self
, handler
):
814 self
.channel
.connect(self
.channel
, self
.RESULT
,
815 handler
, Qt
.QueuedConnection
)
818 class SimpleTask(Task
):
819 """Run a simple callable as a task"""
821 def __init__(self
, parent
, fn
, *args
, **kwargs
):
822 Task
.__init
__(self
, parent
)
829 return self
.fn(*self
.args
, **self
.kwargs
)
832 class RunTask(QtCore
.QObject
):
833 """Runs QRunnable instances and transfers control when they finish"""
835 def __init__(self
, parent
=None):
836 QtCore
.QObject
.__init
__(self
, parent
)
838 self
.task_details
= {}
839 self
.threadpool
= QtCore
.QThreadPool
.globalInstance()
841 def start(self
, task
, progress
=None, finish
=None):
842 """Start the task and register a callback"""
843 if progress
is not None:
845 # prevents garbage collection bugs in certain PyQt4 versions
846 self
.tasks
.append(task
)
848 self
.task_details
[task_id
] = (progress
, finish
)
850 self
.connect(task
.channel
, Task
.FINISHED
, self
.finish
,
852 self
.threadpool
.start(task
)
854 def finish(self
, task
, *args
, **kwargs
):
857 self
.tasks
.remove(task
)
861 progress
, finish
= self
.task_details
[task_id
]
862 del self
.task_details
[task_id
]
864 finish
= progress
= None
866 if progress
is not None:
869 if finish
is not None:
870 finish(task
, *args
, **kwargs
)
873 # Syntax highlighting
875 def rgba(r
, g
, b
, a
=255):
886 def make_format(fg
=None, bg
=None, bold
=False):
887 fmt
= QtGui
.QTextCharFormat()
889 fmt
.setForeground(fg
)
891 fmt
.setBackground(bg
)
893 fmt
.setFontWeight(QtGui
.QFont
.Bold
)
898 Interaction
.critical
= staticmethod(critical
)
899 Interaction
.confirm
= staticmethod(confirm
)
900 Interaction
.question
= staticmethod(question
)
901 Interaction
.information
= staticmethod(information
)