1 # Copyright (c) 2008 David Aguilar
2 """This module provides miscellaneous Qt utility functions.
4 from __future__
import division
, absolute_import
, unicode_literals
6 from PyQt4
import QtGui
7 from PyQt4
import QtCore
8 from PyQt4
.QtCore
import Qt
9 from PyQt4
.QtCore
import SIGNAL
12 from cola
import gitcfg
13 from cola
import hotkeys
14 from cola
import icons
15 from cola
import utils
16 from cola
.i18n
import N_
17 from cola
.interaction
import Interaction
18 from cola
.compat
import int_types
19 from cola
.compat
import ustr
20 from cola
.models
import prefs
21 from cola
.widgets
import defs
24 def connect_action(action
, fn
):
25 """Connect an action to a function"""
26 action
.connect(action
, SIGNAL('triggered()'), fn
)
29 def connect_action_bool(action
, fn
):
30 """Connect a triggered(bool) action to a function"""
31 action
.connect(action
, SIGNAL('triggered(bool)'), fn
)
34 def connect_button(button
, fn
):
35 """Connect a button to a function"""
36 button
.connect(button
, SIGNAL('clicked()'), fn
)
39 def button_action(button
, action
):
40 """Make a button trigger an action"""
41 connect_button(button
, action
.trigger
)
44 def connect_toggle(toggle
, fn
):
45 toggle
.connect(toggle
, SIGNAL('toggled(bool)'), fn
)
49 return QtGui
.QApplication
.activeWindow()
52 def hbox(margin
, spacing
, *items
):
53 return box(QtGui
.QHBoxLayout
, margin
, spacing
, *items
)
56 def vbox(margin
, spacing
, *items
):
57 return box(QtGui
.QVBoxLayout
, margin
, spacing
, *items
)
60 def buttongroup(*items
):
61 group
= QtGui
.QButtonGroup()
71 def box(cls
, margin
, spacing
, *items
):
75 layout
.setMargin(margin
)
76 layout
.setSpacing(spacing
)
79 if isinstance(i
, QtGui
.QWidget
):
81 elif isinstance(i
, (QtGui
.QHBoxLayout
, QtGui
.QVBoxLayout
,
82 QtGui
.QFormLayout
, QtGui
.QLayout
)):
88 elif isinstance(i
, int_types
):
94 def form(margin
, spacing
, *widgets
):
95 layout
= QtGui
.QFormLayout()
96 layout
.setMargin(margin
)
97 layout
.setSpacing(spacing
)
98 layout
.setFieldGrowthPolicy(QtGui
.QFormLayout
.ExpandingFieldsGrow
)
100 for idx
, (label
, widget
) in enumerate(widgets
):
101 if isinstance(label
, (str, ustr
)):
102 layout
.addRow(label
, widget
)
104 layout
.setWidget(idx
, QtGui
.QFormLayout
.LabelRole
, label
)
105 layout
.setWidget(idx
, QtGui
.QFormLayout
.FieldRole
, widget
)
110 def grid(margin
, spacing
, *widgets
):
111 layout
= QtGui
.QGridLayout()
112 layout
.setMargin(defs
.no_margin
)
113 layout
.setSpacing(defs
.spacing
)
117 if isinstance(item
, QtGui
.QWidget
):
118 layout
.addWidget(*row
)
119 elif isinstance(item
, QtGui
.QLayoutItem
):
125 def splitter(orientation
, *widgets
):
126 layout
= QtGui
.QSplitter()
127 layout
.setOrientation(orientation
)
128 layout
.setHandleWidth(defs
.handle_width
)
129 layout
.setChildrenCollapsible(True)
130 for idx
, widget
in enumerate(widgets
):
131 layout
.addWidget(widget
)
132 layout
.setStretchFactor(idx
, 1)
137 def prompt(msg
, title
=None, text
=''):
138 """Presents the user with an input widget and returns the input."""
141 result
= QtGui
.QInputDialog
.getText(active_window(), msg
, title
,
142 QtGui
.QLineEdit
.Normal
, text
)
143 return (result
[0], result
[1])
146 class TreeWidgetItem(QtGui
.QTreeWidgetItem
):
148 TYPE
= QtGui
.QStandardItem
.UserType
+ 101
150 def __init__(self
, path
, icon
, deleted
):
151 QtGui
.QTreeWidgetItem
.__init
__(self
)
153 self
.deleted
= deleted
154 self
.setIcon(0, icons
.from_name(icon
))
155 self
.setText(0, path
)
161 def paths_from_indexes(model
, indexes
,
162 item_type
=TreeWidgetItem
.TYPE
,
164 """Return paths from a list of QStandardItemModel indexes"""
165 items
= [model
.itemFromIndex(i
) for i
in indexes
]
166 return paths_from_items(items
, item_type
=item_type
, item_filter
=item_filter
)
173 def paths_from_items(items
,
174 item_type
=TreeWidgetItem
.TYPE
,
176 """Return a list of paths from a list of items"""
177 if item_filter
is None:
178 item_filter
= _true_filter
179 return [i
.path
for i
in items
180 if i
.type() == item_type
and item_filter(i
)]
183 def confirm(title
, text
, informative_text
, ok_text
,
184 icon
=None, default
=True,
185 cancel_text
=None, cancel_icon
=None):
186 """Confirm that an action should take place"""
187 msgbox
= QtGui
.QMessageBox(active_window())
188 msgbox
.setWindowModality(Qt
.WindowModal
)
189 msgbox
.setWindowTitle(title
)
191 msgbox
.setInformativeText(informative_text
)
193 icon
= icons
.mkicon(icon
, icons
.ok
)
194 ok
= msgbox
.addButton(ok_text
, QtGui
.QMessageBox
.ActionRole
)
197 cancel
= msgbox
.addButton(QtGui
.QMessageBox
.Cancel
)
198 cancel_icon
= icons
.mkicon(cancel_icon
, icons
.close
)
199 cancel
.setIcon(cancel_icon
)
201 cancel
.setText(cancel_text
)
204 msgbox
.setDefaultButton(ok
)
206 msgbox
.setDefaultButton(cancel
)
208 return msgbox
.clickedButton() == ok
211 class ResizeableMessageBox(QtGui
.QMessageBox
):
213 def __init__(self
, parent
):
214 QtGui
.QMessageBox
.__init
__(self
, parent
)
215 self
.setMouseTracking(True)
216 self
.setSizeGripEnabled(True)
218 def event(self
, event
):
219 res
= QtGui
.QMessageBox
.event(self
, event
)
220 event_type
= event
.type()
221 if (event_type
== QtCore
.QEvent
.MouseMove
or
222 event_type
== QtCore
.QEvent
.MouseButtonPress
):
223 maxi
= QtCore
.QSize(defs
.max_size
, defs
.max_size
)
224 self
.setMaximumSize(maxi
)
225 text
= self
.findChild(QtGui
.QTextEdit
)
227 expand
= QtGui
.QSizePolicy
.Expanding
228 text
.setSizePolicy(QtGui
.QSizePolicy(expand
, expand
))
229 text
.setMaximumSize(maxi
)
233 def critical(title
, message
=None, details
=None):
234 """Show a warning with the provided title and message."""
237 mbox
= ResizeableMessageBox(active_window())
238 mbox
.setWindowTitle(title
)
239 mbox
.setTextFormat(Qt
.PlainText
)
240 mbox
.setText(message
)
241 mbox
.setIcon(QtGui
.QMessageBox
.Critical
)
242 mbox
.setStandardButtons(QtGui
.QMessageBox
.Close
)
243 mbox
.setDefaultButton(QtGui
.QMessageBox
.Close
)
245 mbox
.setDetailedText(details
)
249 def information(title
, message
=None, details
=None, informative_text
=None):
250 """Show information with the provided title and message."""
253 mbox
= QtGui
.QMessageBox(active_window())
254 mbox
.setStandardButtons(QtGui
.QMessageBox
.Close
)
255 mbox
.setDefaultButton(QtGui
.QMessageBox
.Close
)
256 mbox
.setWindowTitle(title
)
257 mbox
.setWindowModality(Qt
.WindowModal
)
258 mbox
.setTextFormat(Qt
.PlainText
)
259 mbox
.setText(message
)
261 mbox
.setInformativeText(informative_text
)
263 mbox
.setDetailedText(details
)
264 # Render into a 1-inch wide pixmap
265 pixmap
= icons
.cola().pixmap(defs
.large_icon
)
266 mbox
.setIconPixmap(pixmap
)
270 def question(title
, msg
, default
=True):
271 """Launches a QMessageBox question with the provided title and message.
272 Passing "default=False" will make "No" the default choice."""
273 yes
= QtGui
.QMessageBox
.Yes
274 no
= QtGui
.QMessageBox
.No
280 result
= (QtGui
.QMessageBox
281 .question(active_window(), title
, msg
, buttons
, default
))
282 return result
== QtGui
.QMessageBox
.Yes
285 def tree_selection(tree_item
, items
):
286 """Returns an array of model items that correspond to the selected
287 QTreeWidgetItem children"""
289 count
= min(tree_item
.childCount(), len(items
))
290 for idx
in range(count
):
291 if tree_item
.child(idx
).isSelected():
292 selected
.append(items
[idx
])
297 def tree_selection_items(tree_item
):
298 """Returns selected widget items"""
300 for idx
in range(tree_item
.childCount()):
301 child
= tree_item
.child(idx
)
302 if child
.isSelected():
303 selected
.append(child
)
308 def selected_item(list_widget
, items
):
309 """Returns the model item that corresponds to the selected QListWidget
311 widget_items
= list_widget
.selectedItems()
314 widget_item
= widget_items
[0]
315 row
= list_widget
.row(widget_item
)
322 def selected_items(list_widget
, items
):
323 """Returns an array of model items that correspond to the selected
325 item_count
= len(items
)
327 for widget_item
in list_widget
.selectedItems():
328 row
= list_widget
.row(widget_item
)
330 selected
.append(items
[row
])
334 def open_file(title
, directory
=None):
335 """Creates an Open File dialog and returns a filename."""
336 return (QtGui
.QFileDialog
337 .getOpenFileName(active_window(), title
, directory
))
340 def open_files(title
, directory
=None, filter=None):
341 """Creates an Open File dialog and returns a list of filenames."""
342 return (QtGui
.QFileDialog
343 .getOpenFileNames(active_window(), title
, directory
, filter))
346 def opendir_dialog(title
, path
):
347 """Prompts for a directory path"""
349 flags
= (QtGui
.QFileDialog
.ShowDirsOnly |
350 QtGui
.QFileDialog
.DontResolveSymlinks
)
351 return (QtGui
.QFileDialog
352 .getExistingDirectory(active_window(), title
, path
, flags
))
355 def save_as(filename
, title
='Save As...'):
356 """Creates a Save File dialog and returns a filename."""
357 return (QtGui
.QFileDialog
358 .getSaveFileName(active_window(), title
, filename
))
361 def copy_path(filename
, absolute
=True):
362 """Copy a filename to the clipboard"""
366 filename
= core
.abspath(filename
)
367 set_clipboard(filename
)
370 def set_clipboard(text
):
371 """Sets the copy/paste buffer to text."""
374 clipboard
= QtGui
.QApplication
.clipboard()
375 clipboard
.setText(text
, QtGui
.QClipboard
.Clipboard
)
376 clipboard
.setText(text
, QtGui
.QClipboard
.Selection
)
380 def persist_clipboard():
381 """Persist the clipboard
383 X11 stores only a reference to the clipboard data.
384 Send a clipboard event to force a copy of the clipboard to occur.
385 This ensures that the clipboard is present after git-cola exits.
386 Otherwise, the reference is destroyed on exit.
388 C.f. https://stackoverflow.com/questions/2007103/how-can-i-disable-clear-of-clipboard-on-exit-of-pyqt4-application
391 clipboard
= QtGui
.QApplication
.clipboard()
392 event
= QtCore
.QEvent(QtCore
.QEvent
.Clipboard
)
393 QtGui
.QApplication
.sendEvent(clipboard
, event
)
396 def add_action_bool(widget
, text
, fn
, checked
, *shortcuts
):
398 action
= _add_action(widget
, text
, tip
, fn
, connect_action_bool
, *shortcuts
)
399 action
.setCheckable(True)
400 action
.setChecked(checked
)
404 def add_action(widget
, text
, fn
, *shortcuts
):
406 return _add_action(widget
, text
, tip
, fn
, connect_action
, *shortcuts
)
409 def add_action_with_status_tip(widget
, text
, tip
, fn
, *shortcuts
):
410 return _add_action(widget
, text
, tip
, fn
, connect_action
, *shortcuts
)
413 def _add_action(widget
, text
, tip
, fn
, connect
, *shortcuts
):
414 action
= QtGui
.QAction(text
, widget
)
415 if hasattr(action
, 'setIconVisibleInMenu'):
416 action
.setIconVisibleInMenu(True)
418 action
.setStatusTip(tip
)
421 action
.setShortcuts(shortcuts
)
422 if hasattr(Qt
, 'WidgetWithChildrenShortcut'):
423 action
.setShortcutContext(Qt
.WidgetWithChildrenShortcut
)
424 widget
.addAction(action
)
428 def set_selected_item(widget
, idx
):
429 """Sets a the currently selected item to the item at index idx."""
430 if type(widget
) is QtGui
.QTreeWidget
:
431 item
= widget
.topLevelItem(idx
)
433 widget
.setItemSelected(item
, True)
434 widget
.setCurrentItem(item
)
437 def add_items(widget
, items
):
438 """Adds items to a widget."""
445 def set_items(widget
, items
):
446 """Clear the existing widget contents and set the new items."""
448 add_items(widget
, items
)
451 def create_treeitem(filename
, staged
=False, deleted
=False, untracked
=False):
452 """Given a filename, return a TreeWidgetItem for a status widget
454 "staged", "deleted, and "untracked" control which icon is used.
457 icon_name
= icons
.status(filename
, deleted
, staged
, untracked
)
458 return TreeWidgetItem(filename
, icons
.name_from_basename(icon_name
),
462 def add_close_action(widget
):
463 """Adds close action and shortcuts to a widget."""
464 return add_action(widget
, N_('Close...'),
465 widget
.close
, hotkeys
.CLOSE
, hotkeys
.QUIT
)
468 def center_on_screen(widget
):
469 """Move widget to the center of the default screen"""
470 desktop
= QtGui
.QApplication
.instance().desktop()
471 rect
= desktop
.screenGeometry(QtGui
.QCursor().pos())
472 cy
= rect
.height()//2
474 widget
.move(cx
- widget
.width()//2, cy
- widget
.height()//2)
477 def default_size(parent
, width
, height
):
478 """Return the parent's size, or the provided defaults"""
479 if parent
is not None:
480 width
= parent
.width()
481 height
= parent
.height()
482 return (width
, height
)
485 def default_monospace_font():
488 if utils
.is_darwin():
490 font
.setFamily(family
)
495 font_str
= gitcfg
.current().get(prefs
.FONTDIFF
)
497 font_str
= default_monospace_font().toString()
502 return font(diff_font_str())
507 font
.fromString(string
)
511 def create_button(text
='', layout
=None, tooltip
=None, icon
=None,
512 enabled
=True, default
=False):
513 """Create a button, set its title, and add it to the parent."""
514 button
= QtGui
.QPushButton()
515 button
.setCursor(Qt
.PointingHandCursor
)
520 button
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
521 if tooltip
is not None:
522 button
.setToolTip(tooltip
)
523 if layout
is not None:
524 layout
.addWidget(button
)
526 button
.setEnabled(False)
528 button
.setDefault(True)
532 def create_action_button(tooltip
=None, icon
=None):
533 button
= QtGui
.QPushButton()
534 button
.setCursor(Qt
.PointingHandCursor
)
536 if tooltip
is not None:
537 button
.setToolTip(tooltip
)
540 button
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
544 def ok_button(text
, default
=False, enabled
=True):
545 return create_button(text
=text
, icon
=icons
.ok(),
546 default
=default
, enabled
=enabled
)
550 return create_button(text
=N_('Close'), icon
=icons
.close())
553 def edit_button(enabled
=True, default
=False):
554 return create_button(text
=N_('Edit'), icon
=icons
.edit(),
555 enabled
=enabled
, default
=default
)
558 def refresh_button(enabled
=True, default
=False):
559 return create_button(text
=N_('Refresh'), icon
=icons
.sync(),
560 enabled
=enabled
, default
=default
)
563 def hide_button_menu_indicator(button
):
567 %(name)s::menu-indicator {
571 if name
== 'QPushButton':
577 button
.setStyleSheet(stylesheet
% {'name': name
})
580 def checkbox(text
='', tooltip
='', checked
=None):
581 cb
= QtGui
.QCheckBox()
585 cb
.setToolTip(tooltip
)
586 if checked
is not None:
587 cb
.setChecked(checked
)
589 url
= icons
.check_name()
591 QCheckBox::indicator {
595 QCheckBox::indicator::unchecked {
596 border: %(border)dpx solid #999;
599 QCheckBox::indicator::checked {
601 border: %(border)dpx solid black;
604 """ % dict(size
=defs
.checkbox
, border
=defs
.border
, url
=url
)
605 cb
.setStyleSheet(style
)
610 def radio(text
='', tooltip
='', checked
=None):
611 rb
= QtGui
.QRadioButton()
615 rb
.setToolTip(tooltip
)
616 if checked
is not None:
617 rb
.setChecked(checked
)
621 border
= defs
.radio_border
622 url
= icons
.dot_name()
624 QRadioButton::indicator {
628 QRadioButton::indicator::unchecked {
630 border: %(border)dpx solid #999;
631 border-radius: %(radius)dpx;
633 QRadioButton::indicator::checked {
636 border: %(border)dpx solid black;
637 border-radius: %(radius)dpx;
639 """ % dict(size
=size
, radius
=radius
, border
=border
, url
=url
)
640 rb
.setStyleSheet(style
)
645 class DockTitleBarWidget(QtGui
.QWidget
):
647 def __init__(self
, parent
, title
, stretch
=True):
648 QtGui
.QWidget
.__init
__(self
, parent
)
649 self
.label
= label
= QtGui
.QLabel()
654 label
.setCursor(Qt
.OpenHandCursor
)
656 self
.close_button
= create_action_button(
657 tooltip
=N_('Close'), icon
=icons
.close())
659 self
.toggle_button
= create_action_button(
660 tooltip
=N_('Detach'), icon
=icons
.external())
662 self
.corner_layout
= hbox(defs
.no_margin
, defs
.spacing
)
669 self
.main_layout
= hbox(defs
.small_margin
, defs
.spacing
,
670 label
, separator
, self
.corner_layout
,
671 self
.toggle_button
, self
.close_button
)
672 self
.setLayout(self
.main_layout
)
674 connect_button(self
.toggle_button
, self
.toggle_floating
)
675 connect_button(self
.close_button
, self
.toggle_visibility
)
677 def toggle_floating(self
):
678 self
.parent().setFloating(not self
.parent().isFloating())
679 self
.update_tooltips()
681 def toggle_visibility(self
):
682 self
.parent().toggleViewAction().trigger()
684 def set_title(self
, title
):
685 self
.label
.setText(title
)
687 def add_corner_widget(self
, widget
):
688 self
.corner_layout
.addWidget(widget
)
690 def update_tooltips(self
):
691 if self
.parent().isFloating():
692 tooltip
= N_('Attach')
694 tooltip
= N_('Detach')
695 self
.toggle_button
.setToolTip(tooltip
)
698 def create_dock(title
, parent
, stretch
=True):
699 """Create a dock widget and set it up accordingly."""
700 dock
= QtGui
.QDockWidget(parent
)
701 dock
.setWindowTitle(title
)
702 dock
.setObjectName(title
)
703 titlebar
= DockTitleBarWidget(dock
, title
, stretch
=stretch
)
704 dock
.setTitleBarWidget(titlebar
)
705 if hasattr(parent
, 'dockwidgets'):
706 parent
.dockwidgets
.append(dock
)
710 def create_menu(title
, parent
):
711 """Create a menu and set its title."""
712 qmenu
= QtGui
.QMenu(title
, parent
)
716 def create_toolbutton(text
=None, layout
=None, tooltip
=None, icon
=None):
717 button
= QtGui
.QToolButton()
718 button
.setAutoRaise(True)
719 button
.setAutoFillBackground(True)
720 button
.setCursor(Qt
.PointingHandCursor
)
723 button
.setIconSize(QtCore
.QSize(defs
.small_icon
, defs
.small_icon
))
726 button
.setToolButtonStyle(Qt
.ToolButtonTextBesideIcon
)
727 if tooltip
is not None:
728 button
.setToolTip(tooltip
)
729 if layout
is not None:
730 layout
.addWidget(button
)
734 def mimedata_from_paths(paths
):
735 """Return mimedata with a list of absolute path URLs"""
737 abspaths
= [core
.abspath(path
) for path
in paths
]
738 urls
= [QtCore
.QUrl
.fromLocalFile(path
) for path
in abspaths
]
740 mimedata
= QtCore
.QMimeData()
741 mimedata
.setUrls(urls
)
743 # The text/x-moz-list format is always included by Qt, and doing
744 # mimedata.removeFormat('text/x-moz-url') has no effect.
745 # C.f. http://www.qtcentre.org/threads/44643-Dragging-text-uri-list-Qt-inserts-garbage
747 # gnome-terminal expects utf-16 encoded text, but other terminals,
748 # e.g. terminator, prefer utf-8, so allow cola.dragencoding
749 # to override the default.
750 paths_text
= core
.list2cmdline(abspaths
)
751 encoding
= gitcfg
.current().get('cola.dragencoding', 'utf-16')
752 moz_text
= core
.encode(paths_text
, encoding
=encoding
)
753 mimedata
.setData('text/x-moz-url', moz_text
)
758 def path_mimetypes():
759 return ['text/uri-list', 'text/x-moz-url']
762 class BlockSignals(object):
763 """Context manager for blocking a signals on a widget"""
765 def __init__(self
, *widgets
):
766 self
.widgets
= widgets
770 for w
in self
.widgets
:
771 self
.values
[w
] = w
.blockSignals(True)
774 def __exit__(self
, exc_type
, exc_val
, exc_tb
):
775 for w
in self
.widgets
:
776 w
.blockSignals(self
.values
[w
])
779 class Task(QtCore
.QRunnable
):
780 """Disable auto-deletion to avoid gc issues
782 Python's garbage collector will try to double-free the task
783 once it's finished, so disable Qt's auto-deletion as a workaround.
787 FINISHED
= SIGNAL('TASK_FINISHED')
788 RESULT
= SIGNAL('TASK_RESULT')
790 def __init__(self
, parent
, *args
, **kwargs
):
791 QtCore
.QRunnable
.__init
__(self
)
793 self
.channel
= QtCore
.QObject(parent
)
795 self
.setAutoDelete(False)
798 self
.result
= self
.task()
799 self
.channel
.emit(self
.RESULT
, self
.result
)
806 self
.channel
.emit(self
.FINISHED
, self
)
808 def connect(self
, handler
):
809 self
.channel
.connect(self
.channel
, self
.RESULT
,
810 handler
, Qt
.QueuedConnection
)
813 class SimpleTask(Task
):
814 """Run a simple callable as a task"""
816 def __init__(self
, parent
, fn
, *args
, **kwargs
):
817 Task
.__init
__(self
, parent
)
824 return self
.fn(*self
.args
, **self
.kwargs
)
827 class RunTask(QtCore
.QObject
):
828 """Runs QRunnable instances and transfers control when they finish"""
830 def __init__(self
, parent
=None):
831 QtCore
.QObject
.__init
__(self
, parent
)
833 self
.task_details
= {}
834 self
.threadpool
= QtCore
.QThreadPool
.globalInstance()
836 def start(self
, task
, progress
=None, finish
=None):
837 """Start the task and register a callback"""
838 if progress
is not None:
840 # prevents garbage collection bugs in certain PyQt4 versions
841 self
.tasks
.append(task
)
843 self
.task_details
[task_id
] = (progress
, finish
)
845 self
.connect(task
.channel
, Task
.FINISHED
, self
.finish
,
847 self
.threadpool
.start(task
)
849 def finish(self
, task
, *args
, **kwargs
):
852 self
.tasks
.remove(task
)
856 progress
, finish
= self
.task_details
[task_id
]
857 del self
.task_details
[task_id
]
859 finish
= progress
= None
861 if progress
is not None:
864 if finish
is not None:
865 finish(task
, *args
, **kwargs
)
868 # Syntax highlighting
870 def rgba(r
, g
, b
, a
=255):
881 def make_format(fg
=None, bg
=None, bold
=False):
882 fmt
= QtGui
.QTextCharFormat()
884 fmt
.setForeground(fg
)
886 fmt
.setBackground(bg
)
888 fmt
.setFontWeight(QtGui
.QFont
.Bold
)
893 Interaction
.critical
= staticmethod(critical
)
894 Interaction
.confirm
= staticmethod(confirm
)
895 Interaction
.question
= staticmethod(question
)
896 Interaction
.information
= staticmethod(information
)