# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import six
from six.moves import map, range # NOQA
from wbia.guitool.__PYQT__ import QtCore, QtGui
from wbia.guitool.__PYQT__ import QtWidgets
from wbia.guitool.__PYQT__.QtCore import Qt
from wbia.guitool.__PYQT__._internal import GUITOOL_PYQT_VERSION # NOQA
import utool as ut
from wbia.guitool import guitool_dialogs
import weakref
(print, rrr, profile) = ut.inject2(__name__)
DEBUG_WIDGET = ut.get_argflag(('--dbgwgt', '--debugwidget', '--debug-widget'))
if DEBUG_WIDGET:
WIDGET_BASE = QtWidgets.QFrame
else:
WIDGET_BASE = QtWidgets.QWidget
ALIGN_DICT = {
'center': Qt.AlignCenter,
'right': Qt.AlignRight | Qt.AlignVCenter,
'left': Qt.AlignLeft | Qt.AlignVCenter,
'justify': Qt.AlignJustify,
}
[docs]def rectifyQtEnum(type_, value, default=ut.NoParam):
"""
Args:
type_ (str): name of qt enum
value (int or str): string or integer value
default (int or str): default value
Returns:
int: rectified_value
TODO: move to qt_enums?
CommandLine:
python -m wbia.guitool.guitool_components rectifyQtEnum
Example:
>>> # DISABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> newvals = []
>>> newvals.append(rectifyQtEnum('Orientation', 'vert'))
>>> newvals.append(rectifyQtEnum('Orientation', 'Horizontal'))
>>> newvals.append(rectifyQtEnum('LayoutDirection', 'LeftToRight'))
>>> newvals.append(rectifyQtEnum('QSizePolicy', 'Expanding'))
>>> result = ut.repr4(newvals)
>>> print(result)
>>> assert all(isinstance(v, int) for v in newvals)
"""
if value is None:
if default is ut.NoParam:
raise ValueError('Cannot rectify None value for {}'.format(type_))
value = default
if isinstance(value, six.string_types):
if type_ == 'QSizePolicy':
valid_values = {
'Fixed',
'Minimum',
'Maximum',
'Preferred',
'Expanding',
'MinimumExpanding',
'Ignored',
}
enum_obj = QtWidgets.QSizePolicy
elif type_ == 'Alignment':
return {
'center': Qt.AlignCenter,
'right': Qt.AlignRight | Qt.AlignVCenter,
'left': Qt.AlignLeft | Qt.AlignVCenter,
'justify': Qt.AlignJustify,
}[value]
elif type_ == 'LayoutDirection':
valid_values = {'LeftToRight', 'RightToLeft'}
enum_obj = QtCore.Qt
elif type_ == 'Orientation':
# alias
if value.lower().startswith('vert'):
value = 'Vertical'
elif value.lower().startswith('horiz'):
value = 'Horizontal'
valid_values = {'Vertical', 'Horizontal'}
enum_obj = QtCore.Qt
elif value not in valid_values:
raise ValueError(
'Unknown enum {} for {}. Valid values are {}'.format(
value, type_, valid_values
)
)
rectified_value = getattr(enum_obj, value)
else:
rectified_value = value
return rectified_value
[docs]def rectifySizePolicy(policy=None, default='Expanding'):
return rectifyQtEnum('QSizePolicy', policy, default)
# policy_dict = {
# 'Fixed': QtWidgets.QSizePolicy.Fixed,
# 'Minimum': QtWidgets.QSizePolicy.Minimum,
# 'Maximum': QtWidgets.QSizePolicy.Maximum,
# 'Preferred': QtWidgets.QSizePolicy.Preferred,
# 'Expanding': QtWidgets.QSizePolicy.Expanding,
# 'MinimumExpanding': QtWidgets.QSizePolicy.MinimumExpanding,
# 'Ignored': QtWidgets.QSizePolicy.Ignored,
# }
# if policy is None:
# policy = default
# if isinstance(policy, six.string_types):
# policy = policy_dict[policy]
return policy
[docs]def adjustSizePolicy(widget, hPolicy=None, vPolicy=None, hStretch=None, vStretch=None):
policy = newSizePolicy(
widget,
hSizePolicy=hPolicy,
vSizePolicy=vPolicy,
hStretch=hStretch,
vStretch=vStretch,
)
widget.setSizePolicy(policy)
[docs]def newSizePolicy(
widget=None,
verticalSizePolicy=None,
horizontalSizePolicy=None,
horizontalStretch=None,
verticalStretch=None,
hSizePolicy=None,
vSizePolicy=None,
vStretch=None,
hStretch=None,
):
"""
References:
https://i.stack.imgur.com/Y8IDK.png
http://doc.qt.io/qt-4.8/qsizepolicy.html
http://doc.qt.io/qt-5/qsizepolicy.html
"""
# Alias
if horizontalStretch is not None:
hStretch = horizontalStretch
if verticalStretch is not None:
vStretch = verticalStretch
if verticalSizePolicy is not None:
vSizePolicy = verticalSizePolicy
if horizontalSizePolicy is not None:
hSizePolicy = horizontalSizePolicy
#
if widget is not None:
# use existing policy as default
old = widget.sizePolicy()
defaultHPolicy = old.horizontalPolicy()
defaultVPolicy = old.verticalPolicy()
defaultHStretch = old.horizontalStretch()
defaultVStretch = old.verticalStretch()
else:
defaultVPolicy = 'Expanding'
defaultHPolicy = 'Expanding'
defaultVStretch = 0
defaultHStretch = 0
hPolicy = rectifyQtEnum('QSizePolicy', hSizePolicy, defaultHPolicy)
vPolicy = rectifyQtEnum('QSizePolicy', vSizePolicy, defaultVPolicy)
if hStretch is None:
hStretch = defaultHStretch
if vStretch is None:
vStretch = defaultVStretch
sizePolicy = QtWidgets.QSizePolicy(hPolicy, vPolicy)
sizePolicy.setHorizontalStretch(hStretch)
sizePolicy.setVerticalStretch(vStretch)
# sizePolicy.setHeightForWidth(widget.sizePolicy().hasHeightForWidth())
return sizePolicy
[docs]def newSplitter(
widget=None, orientation=None, verticalStretch=1, horizontalStretch=None, ori=None
):
"""
input: widget - the central widget
"""
if orientation is not None:
ori = orientation
ori = rectifyQtEnum('Orientation', ori, 'horiz')
splitter = QtWidgets.QSplitter(ori, widget)
_inject_new_widget_methods(splitter)
# This line makes the splitter resize with the widget
adjustSizePolicy(splitter, hStretch=horizontalStretch, vStretch=verticalStretch)
return splitter
[docs]def newQPoint(x, y):
return QtCore.QPoint(int(round(x)), int(round(y)))
PROG_TEXT = ut.get_argflag('--progtext')
[docs]class GuiProgContext(object):
def __init__(ctx, title, prog_bar):
if PROG_TEXT:
print('[guitool] GuiProgContext.__init__')
ctx.prog_bar = prog_bar
ctx.title = title
ctx.total = None
@property
def prog_hook(ctx):
return ctx.prog_bar.utool_prog_hook
[docs] def set_progress(ctx, count=None, total=None, msg=None):
if total is None:
total = ctx.total
if count is None:
count = ctx.prog_hook.count + 1
ctx.prog_hook.set_progress(count, total)
if msg is not None:
ctx.prog_bar.setWindowTitle(ctx.title + ' ' + msg)
[docs] def set_total(ctx, total):
ctx.total = total
def __enter__(ctx):
if PROG_TEXT:
print('[guitool] GuiProgContext.__enter__')
ctx.prog_bar.setVisible(True)
ctx.prog_bar.setWindowTitle(ctx.title)
ctx.prog_hook.lbl = ctx.title
ctx.prog_bar.utool_prog_hook.set_progress(0)
# Doesn't seem to work correctly
# prog_bar.utool_prog_hook.show_indefinite_progress()
ctx.prog_bar.utool_prog_hook.force_event_update()
return ctx
def __exit__(ctx, type_, value, trace):
if PROG_TEXT:
print('[guitool] GuiProgContext.__exit__')
ctx.prog_bar.setVisible(False)
if trace is not None:
if ut.VERBOSE:
print('[back] Error in context manager!: ' + str(value))
return False # return a falsey value on error
[docs]class ProgHook(QtCore.QObject, ut.NiceRepr):
"""
hooks into utool.ProgressIterator.
A hook represents a fraction of a progress step.
Hooks can be divided recursively
TODO:
use signals and slots to connect to the progress bar
still doesn't work correctly even with signals and slots, probably
need to do task function in another thread
if False:
for x in ut.ProgIter(ut.expensive_task_gen(40000), length=40000,
prog_hook=ctx.prog_hook):
pass
References:
http://stackoverflow.com/questions/19442443/busy-indication-with-pyqt-progress-bar
Args:
prog_bar (Qt.QProgressBar):
substep_min (int): (default = 0)
substep_size (int): (default = 1)
level (int): (default = 0)
CommandLine:
python -m wbia.guitool.guitool_components ProgHook --show --progtext
Example:
>>> # ENABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> import wbia.guitool as gt
>>> app = gt.ensure_qtapp()[0]
>>> parent = newWidget()
>>> parent.show()
>>> parent.resize(600, 40)
>>> prog_bar = newProgressBar(parent, visible=True)
>>> hook = prog_bar.utool_prog_hook
>>> subhook_list = hook.subdivide(num=4)
>>> hook_0_25 = subhook_list[0]
>>> hook_0_25.length = 2
>>> print('hook_0_25 = %s' % (hook_0_25,))
>>> hook_0_25.set_progress(0)
>>> print('hook_0_25 = %s' % (hook_0_25,))
>>> hook_0_25.set_progress(1)
>>> print('hook_0_25 = %s' % (hook_0_25,))
>>> substep_hooks_0_25 = hook_0_25.make_substep_hooks(num=4)
>>> print('substep_hooks_0_25 = %s' % (ut.repr2(substep_hooks_0_25, strvals=True),))
>>> subhook = substep_hooks_0_25[0]
>>> progiter = ut.ProgIter(list(range(4)), prog_hook=subhook)
>>> iter_ = iter(progiter)
>>> six.next(iter_)
>>> hook(2, 2)
>>> subhook2 = substep_hooks_0_25[1]
>>> subsubhooks = subhook2.subdivide(num=2)
>>> subsubhooks[0](0, 3)
>>> subsubhooks[0](1, 3)
>>> subsubhooks[0](2, 3, 'special part')
>>> subsubhooks[0](3, 3, 'other part')
>>> app.processEvents()
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> import wbia.plottool as pt
>>> ut.show_if_requested()
"""
progress_changed_signal = QtCore.pyqtSignal(float, str)
show_indefinite_progress_signal = QtCore.pyqtSignal()
def __init__(hook, prog_bar=None, global_min=0, global_max=1, level=0):
super(ProgHook, hook).__init__()
if prog_bar is None:
hook.progressBarRef = None
else:
hook.progressBarRef = weakref.ref(prog_bar)
hook.global_min = global_min
hook.global_max = global_max
# hook.substep_min = substep_min
# hook.substep_size = substep_size
hook._count = 0
hook.length = 1
hook.progiter = None
hook.lbl = ''
hook.level = level
hook.child_hook_gen = None
hook.progress_changed_signal.connect(hook.on_progress_changed)
hook.show_indefinite_progress_signal.connect(hook.show_indefinite_progress_slot)
hook.show_text = PROG_TEXT
def __nice__(hook):
gmin, gmax = hook.global_bounds()
lbl = hook.lbl
gpos = hook.global_progress()
lpos = hook.local_progress()
return '(%s, [%r, %r ,%r], %r=%d/%d)' % (
lbl,
gmin,
gpos,
gmax,
lpos,
hook.count,
hook.length,
)
@property
def prog_bar(hook):
if hook.progressBarRef is None:
return None
prog_bar = hook.progressBarRef()
return prog_bar
@property
def count(hook):
# progiter = Noe
# if hook.progiter is not None:
# progiter = hook.progiter()
# if progiter is not None:
# # prog iter is one step ahead
# #count = max(progiter.count - 1, 0)
# count = progiter.count
# else:
count = hook._count
return count
[docs] def global_bounds(hook):
min_ = hook.global_min
max_ = hook.global_max
return (min_, max_)
[docs] def global_extent(hook):
min_, max_ = hook.global_bounds()
return max_ - min_
[docs] def register_progiter(hook, progiter):
hook.progiter = weakref.ref(progiter)
hook.length = hook.progiter().length
hook.lbl = hook.progiter().lbl
[docs] def initialize_subhooks(hook, num=None, spacing=None):
subhooks = hook.make_substep_hooks(num, spacing)
hook.child_hook_gen = iter(subhooks)
[docs] def next_subhook(hook):
return six.next(hook.child_hook_gen)
[docs] def subdivide(hook, num=None, spacing=None):
"""
Branches this hook into several new leafs.
Only progress leafs are used to indicate global progress.
"""
import numpy as np
if num is None:
num = len(spacing) - 1
if spacing is None:
# Assume uniform sub iterators
spacing = np.linspace(0, 1, num + 1)
spacing = np.array(spacing)
# min_, max_ = hook.global_bounds()
extent = hook.global_extent()
global_spacing = hook.global_min + (spacing * extent)
sub_min_list = global_spacing[:-1]
sub_max_list = global_spacing[1:]
prog_bar = hook.prog_bar
subhook_list = [
ProgHook(prog_bar, min_, max_, hook.level + 1)
for min_, max_ in zip(sub_min_list, sub_max_list)
]
return subhook_list
[docs] def make_substep_hooks(hook, num=None, spacing=None):
"""
This takes into account your current position, and gives you only
enough subhooks to complete a single step.
Need to know current count, stepsize, and total number of steps in this
subhook.
Example:
>>> # ENABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> import wbia.guitool as gt
>>> app = gt.ensure_qtapp()[0]
>>> hook = ProgHook(None)
>>> subhook_list = hook.subdivide(num=4)
>>> hook_0_25 = subhook_list[0]
>>> hook = hook_0_25
>>> hook(1, 2)
>>> print('hook = %r' % (hook,))
>>> subhook_list1 = hook.make_substep_hooks(1)
>>> subhook1 = subhook_list1[0]
>>> print('subhook1 = %r' % (subhook1,))
>>> subhook_list2 = hook.make_substep_hooks(2)
>>> subhook2 = subhook_list2[1]
>>> subhook2.show_text = True
>>> # Test progress iter
>>> progiter = ut.ProgIter(list(range(3)), lbl='foo', prog_hook=subhook2)
>>> iter_ = iter(progiter); print('subhook2 = %r' % (subhook2,))
>>> # Iter
>>> print(six.next(iter_)); print('subhook2 = %r' % (subhook2,))
>>> print(six.next(iter_)); print('subhook2 = %r' % (subhook2,))
>>> print(six.next(iter_)); print('subhook2 = %r' % (subhook2,))
"""
import numpy as np
if num is None:
num = len(spacing) - 1
if spacing is None:
spacing = np.linspace(0, 1, num + 1) # Assume uniform sub iterators
spacing = np.array(spacing)
length = hook.length
step_extent_local = 1 / length
step_extent_global = step_extent_local * hook.global_extent()
# assert hook.count < length, 'already finished this subhook'
count = hook.count
if count >= length:
# HACK
count = length - 1
step_min = count * step_extent_global + hook.global_min
global_spacing = step_min + (spacing * step_extent_global)
sub_min_list = global_spacing[:-1]
sub_max_list = global_spacing[1:]
hook.length / hook.global_extent()
prog_bar = hook.prog_bar
subhook_list = [
ProgHook(prog_bar, min_, max_, hook.level + 1)
for min_, max_ in zip(sub_min_list, sub_max_list)
]
return subhook_list
# subhook_list = [ProgHook(hook.progressBarRef(), substep_min, substep_size, hook.level + 1)
# for substep_min in substep_min_list]
# return subhook_list
# step_min = ((count - 1) / length) * hook.substep_size + hook.substep_min
# step_size = (1.0 / length) * hook.substep_size
# substep_size = step_size / num_substeps
# substep_min_list = [(step * substep_size) + step_min for step in range(num_substeps)]
# DEBUG = False
# if DEBUG:
# with ut.Indenter(' ' * 4 * length):
# print('\n')
# print('+____<NEW SUBSTEPS>____')
# print('Making %d substeps for hook.lbl = %s' % (num_substeps, hook.lbl,))
# print(' * step_min = %.2f' % (step_min,))
# print(' * step_size = %.2f' % (step_size,))
# print(' * substep_size = %.2f' % (substep_size,))
# print(' * substep_min_list = %r' % (substep_min_list,))
# print(r'L____</NEW SUBSTEPS>____')
# print('\n')
[docs] def set_progress(hook, count, length=None, lbl=None):
if length is None:
length = hook.length
if length is None:
length = 100
else:
hook.length = length
hook._count = count
if lbl is not None:
hook.lbl = lbl
global_fraction = hook.global_progress()
hook.progress_changed_signal.emit(global_fraction, hook.lbl)
# hook.on_progress_changed(global_fraction, hook.lbl)
def __call__(hook, count, length=None, lbl=None):
hook.set_progress(count, length, lbl)
[docs] def local_progress(hook):
""" percent done of this subhook """
length = hook.length
count = hook.count
local_fraction = (count) / length
return local_fraction
[docs] def global_progress(hook):
""" percent done of entire process """
local_fraction = hook.local_progress()
extent = hook.global_extent()
global_min = hook.global_min
global_fraction = global_min + (local_fraction * extent)
return global_fraction
[docs] @QtCore.pyqtSlot(float, str)
def on_progress_changed(hook, global_fraction, lbl):
if hook.show_text:
resolution = 75
num_full = int(round(global_fraction * resolution))
num_empty = resolution - num_full
print('\n')
print(
'['
+ '#' * num_full
+ '.' * num_empty
+ '] %7.3f%% %s' % (global_fraction * 100, hook.lbl)
)
print('\n')
prog_bar = hook.prog_bar
if prog_bar is not None:
prog_bar.setRange(0, 10000)
prog_bar.setMinimum(0)
prog_bar.setMaximum(10000)
value = int(round(prog_bar.maximum() * global_fraction))
prog_bar.setFormat(lbl + ' %p%')
prog_bar.setValue(value)
# prog_bar.setProperty('value', value)
# major hack
hook.force_event_update()
[docs] @QtCore.pyqtSlot()
def show_indefinite_progress_slot(hook):
prog_bar = hook.prog_bar
if prog_bar is not None:
prog_bar.reset()
prog_bar.setMaximum(0)
prog_bar.setProperty('value', 0)
hook.force_event_update()
[docs] def show_indefinite_progress(hook):
hook.show_indefinite_progress_signal.emit()
[docs] def force_event_update(hook):
# major hack
import wbia.guitool
qtapp = guitool.get_qtapp()
flag = QtCore.QEventLoop.ExcludeUserInputEvents
return_status = qtapp.processEvents(flag)
# print('(1)return_status = %r' % (return_status,))
if not return_status:
return_status = qtapp.processEvents(flag)
# print('(2)return_status = %r' % (return_status,))
[docs]def newProgressBar(parent, visible=True, verticalStretch=1):
r"""
Args:
parent (?):
visible (bool):
verticalStretch (int):
Returns:
QProgressBar: progressBar
CommandLine:
python -m wbia.guitool.guitool_components --test-newProgressBar:0
python -m wbia.guitool.guitool_components --test-newProgressBar:0 --show
python -m wbia.guitool.guitool_components --test-newProgressBar:1
python -m wbia.guitool.guitool_components --test-newProgressBar:2
python -m wbia.guitool.guitool_components --test-newProgressBar:1 --progtext
python -m wbia.guitool.guitool_components --test-newProgressBar:2 --progtext
Example:
>>> # GUI_DOCTEST
>>> # xdoctest: +REQUIRES(--gui)
>>> from wbia.guitool.guitool_components import * # NOQA
>>> # build test data
>>> import wbia.guitool
>>> guitool.ensure_qtapp()
>>> parent = None
>>> visible = True
>>> verticalStretch = 1
>>> # hook into utool progress iter
>>> progressBar = newProgressBar(parent, visible, verticalStretch)
>>> progressBar.show()
>>> progressBar.utool_prog_hook.show_indefinite_progress()
>>> #progressBar.utool_prog_hook.set_progress(0)
>>> #import time
>>> qtapp = guitool.get_qtapp()
>>> [(qtapp.processEvents(), ut.get_nth_prime_bruteforce(300)) for x in range(100)]
>>> #time.sleep(5)
>>> progiter = ut.ProgIter(range(100), freq=1, autoadjust=False, prog_hook=progressBar.utool_prog_hook)
>>> results1 = [ut.get_nth_prime_bruteforce(300) for x in progiter]
>>> # verify results
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> guitool.qtapp_loop(freq=10)
Example:
>>> # GUI_DOCTEST
>>> # xdoctest: +REQUIRES(--gui)
>>> from wbia.guitool.guitool_components import * # NOQA
>>> # build test data
>>> import wbia.guitool
>>> guitool.ensure_qtapp()
>>> parent = None
>>> visible = True
>>> verticalStretch = 1
>>> def complex_tasks(hook):
>>> progkw = dict(freq=1, backspace=False, autoadjust=False)
>>> num = 800
>>> for x in ut.ProgIter(range(2), lbl='TASK', prog_hook=hook, **progkw):
>>> ut.get_nth_prime_bruteforce(num)
>>> subhook1, subhook2 = hook.make_substep_hooks(2)
>>> for task1 in ut.ProgIter(range(2), lbl='task1.1', prog_hook=subhook1, **progkw):
>>> ut.get_nth_prime_bruteforce(num)
>>> for task2 in ut.ProgIter(range(2), lbl='task1.2', prog_hook=subhook2, **progkw):
>>> ut.get_nth_prime_bruteforce(num)
>>> # hook into utool progress iter
>>> progressBar = newProgressBar(parent, visible, verticalStretch)
>>> hook = progressBar.utool_prog_hook
>>> complex_tasks(hook)
>>> # verify results
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> guitool.qtapp_loop(freq=10)
Example:
>>> # GUI_DOCTEST
>>> # xdoctest: +REQUIRES(--gui)
>>> from wbia.guitool.guitool_components import * # NOQA
>>> # build test data
>>> import wbia.guitool
>>> guitool.ensure_qtapp()
>>> parent = None
>>> visible = True
>>> verticalStretch = 1
>>> def complex_tasks(hook):
>>> progkw = dict(freq=1, backspace=False, autoadjust=False)
>>> num = 800
>>> for x in ut.ProgIter(range(4), lbl='TASK', prog_hook=hook, **progkw):
>>> ut.get_nth_prime_bruteforce(num)
>>> subhook1, subhook2 = hook.make_substep_hooks(2)
>>> for task1 in ut.ProgIter(range(2), lbl='task1.1', prog_hook=subhook1, **progkw):
>>> ut.get_nth_prime_bruteforce(num)
>>> subsubhooks = subhook1.make_substep_hooks(3)
>>> for task1 in ut.ProgIter(range(7), lbl='task1.1.1', prog_hook=subsubhooks[0], **progkw):
>>> ut.get_nth_prime_bruteforce(num)
>>> for task1 in ut.ProgIter(range(11), lbl='task1.1.2', prog_hook=subsubhooks[1], **progkw):
>>> ut.get_nth_prime_bruteforce(num)
>>> for task1 in ut.ProgIter(range(3), lbl='task1.1.3', prog_hook=subsubhooks[2], **progkw):
>>> ut.get_nth_prime_bruteforce(num)
>>> for task2 in ut.ProgIter(range(10), lbl='task1.2', prog_hook=subhook2, **progkw):
>>> ut.get_nth_prime_bruteforce(num)
>>> # hook into utool progress iter
>>> progressBar = newProgressBar(parent, visible, verticalStretch)
>>> hook = progressBar.utool_prog_hook
>>> complex_tasks(hook)
>>> # verify results
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> guitool.qtapp_loop(freq=10)
Ignore:
from wbia.guitool.guitool_components import * # NOQA
# build test data
import wbia.guitool
guitool.ensure_qtapp()
"""
progressBar = QtWidgets.QProgressBar(parent)
sizePolicy = newSizePolicy(
progressBar,
verticalSizePolicy=QtWidgets.QSizePolicy.Maximum,
verticalStretch=verticalStretch,
)
progressBar.setSizePolicy(sizePolicy)
progressBar.setMaximum(10000)
progressBar.setProperty('value', 0)
# def utool_prog_hook(count, length):
# progressBar.setProperty('value', int(100 * count / length))
# # major hack
# import wbia.guitool
# qtapp = guitool.get_qtapp()
# qtapp.processEvents()
# pass
progressBar.utool_prog_hook = ProgHook(progressBar)
# progressBar.setTextVisible(False)
progressBar.setTextVisible(True)
progressBar.setFormat('%p%')
progressBar.setVisible(visible)
progressBar.setMinimumWidth(600)
setattr(progressBar, '_guitool_sizepolicy', sizePolicy)
if visible:
# hack to make progres bar show up immediately
import wbia.guitool
progressBar.show()
qtapp = guitool.get_qtapp()
qtapp.processEvents()
return progressBar
[docs]def newOutputLog(parent, pointSize=6, visible=True, verticalStretch=1):
from wbia.guitool.guitool_misc import QLoggedOutput
outputLog = QLoggedOutput(parent, visible=visible)
sizePolicy = newSizePolicy(
outputLog,
# verticalSizePolicy=QSizePolicy.Preferred,
verticalStretch=verticalStretch,
)
outputLog.setSizePolicy(sizePolicy)
outputLog.setAcceptRichText(False)
outputLog.setReadOnly(True)
# outputLog.setVisible(visible)
# outputLog.setFontPointSize(8)
outputLog.setFont(newFont('Courier New', pointSize))
setattr(outputLog, '_guitool_sizepolicy', sizePolicy)
return outputLog
[docs]def newLabel(parent=None, text='', align='center', gpath=None, fontkw={}, min_width=None):
r"""
Args:
parent (None): (default = None)
text (str): (default = '')
align (str): (default = 'center')
gpath (None): (default = None)
fontkw (dict): (default = {})
Kwargs:
parent, text, align, gpath, fontkw
Returns:
?: label
CommandLine:
python -m wbia.guitool.guitool_components --exec-newLabel --show
Example:
>>> # ENABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> import wbia.guitool
>>> guitool.ensure_qtapp()
>>> parent = None
>>> text = ''
>>> align = 'center'
>>> gpath = ut.grab_test_imgpath('lena.png')
>>> fontkw = {}
>>> label = newLabel(parent, text, align, gpath, fontkw)
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> label.show()
>>> guitool.qtapp_loop(qwin=label, freq=10)
"""
label = QtWidgets.QLabel(text, parent=parent)
# label.setAlignment(ALIGN_DICT[align])
align = rectifyQtEnum('Alignment', align)
# if isinstance(align, six.string_types):
# align = ALIGN_DICT[align]
label.setAlignment(align)
adjust_font(label, **fontkw)
if gpath is not None:
# http://stackoverflow.com/questions/8211982/qt-resizing-a-qlabel-containing-a-qpixmap-while-keeping-its-aspect-ratio
# TODO
label._orig_pixmap = QtGui.QPixmap(gpath)
label.setPixmap(
label._orig_pixmap.scaled(
label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
)
)
label.setScaledContents(True)
def _on_resize_slot():
# print('_on_resize_slot')
label.setPixmap(
label._orig_pixmap.scaled(
label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation
)
)
# label.setPixmap(label._orig_pixmap.scaled(label.size()))
label._on_resize_slot = _on_resize_slot
# ut.embed()
def setColorFG(self, fgcolor):
"""
fgcolor: a tuple or list of [R, G, B] in 255 format
"""
# current_sheet = self.styleSheet()
style_sheet_str = make_style_sheet(bgcolor=None, fgcolor=fgcolor)
if style_sheet_str is None:
style_sheet_str = ''
self.setStyleSheet(style_sheet_str)
def setColor(self, fgcolor=None, bgcolor=None):
"""
fgcolor: a tuple or list of [R, G, B] in 255 format
"""
# current_sheet = self.styleSheet()
style_sheet_str = make_style_sheet(bgcolor=bgcolor, fgcolor=fgcolor)
if style_sheet_str is None:
style_sheet_str = ''
self.setStyleSheet(style_sheet_str)
ut.inject_func_as_method(label, setColorFG)
ut.inject_func_as_method(label, setColor)
if min_width is not None:
label.setMinimumWidth(min_width)
return label
[docs]class ResizableTextEdit(QtWidgets.QTextEdit):
"""
http://stackoverflow.com/questions/3050537/resizing-qts-qtextedit-to-match-text-height-maximumviewportsize
"""
[docs] def sizeHint(self):
text = self.toPlainText()
font = self.document().defaultFont() # or another font if you change it
fontMetrics = QtGui.QFontMetrics(font) # a QFontMetrics based on our font
textSize = fontMetrics.size(0, text)
textWidth = textSize.width() + 30 # constant may need to be tweaked
textHeight = textSize.height() + 30 # constant may need to be tweaked
return (textWidth, textHeight)
[docs]def newTextEdit(
parent=None,
label=None,
visible=None,
label_pos='above',
align='left',
text=None,
enabled=True,
editable=True,
fit_to_text=False,
rich=False,
):
""" This is a text area """
# if fit_to_text:
# outputEdit = ResizableTextEdit(parent)
# else:
outputEdit = QtWidgets.QTextEdit(parent)
# sizePolicy = newSizePolicy(outputEdit, verticalStretch=1)
# outputEdit.setSizePolicy(sizePolicy)
outputEdit.setAcceptRichText(rich)
if visible is not None:
outputEdit.setVisible(visible)
outputEdit.setEnabled(enabled)
outputEdit.setReadOnly(not editable)
if text is not None:
outputEdit.setText(text)
align = rectifyQtEnum('Alignment', align)
# if isinstance(align, six.string_types):
# align = ALIGN_DICT[align]
outputEdit.setAlignment(align)
if label is None:
pass
if fit_to_text:
font = outputEdit.document().defaultFont() # or another font if you change it
fontMetrics = QtGui.QFontMetrics(font) # a QFontMetrics based on our font
textSize = fontMetrics.size(0, text)
textWidth = textSize.width() + 30 # constant may need to be tweaked
textHeight = textSize.height() + 30 # constant may need to be tweaked
outputEdit.setMinimumSize(textWidth, textHeight)
# else:
# outputEdit.setMinimumHeight(0)
# setattr(outputEdit, '_guitool_sizepolicy', sizePolicy)
return outputEdit
[docs]def newLineEdit(
parent,
text=None,
enabled=True,
align='center',
textChangedSlot=None,
textEditedSlot=None,
editingFinishedSlot=None,
visible=True,
readOnly=False,
editable=None,
verticalStretch=0,
fontkw={},
):
""" This is a text line
Example:
>>> # DISABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> parent = None
>>> text = None
>>> visible = True
>>> # execute function
>>> widget = newLineEdit(parent, text, visible)
>>> # verify results
>>> result = str(widget)
>>> print(result)
"""
if editable is not None:
readOnly = editable
widget = QtWidgets.QLineEdit(parent)
# sizePolicy = newSizePolicy(widget,
# verticalSizePolicy=QtWidgets.QSizePolicy.Fixed,
# verticalStretch=verticalStretch)
# widget.setSizePolicy(sizePolicy)
if text is not None:
widget.setText(text)
widget.setEnabled(enabled)
align = rectifyQtEnum('Alignment', align)
# if isinstance(align, six.string_types):
# align = ALIGN_DICT[align]
widget.setAlignment(align)
widget.setReadOnly(readOnly)
if textChangedSlot is not None:
widget.textChanged.connect(textChangedSlot)
if editingFinishedSlot is not None:
widget.editingFinished.connect(editingFinishedSlot)
if textEditedSlot is not None:
widget.textEdited.connect(textEditedSlot)
# outputEdit.setAcceptRichText(False)
# outputEdit.setVisible(visible)
adjust_font(widget, **fontkw)
# setattr(widget, '_guitool_sizepolicy', sizePolicy)
return widget
[docs]class TagEdit(QtWidgets.QLineEdit):
"""
Args:
self (?):
parent (None): (default = None)
tags (None): (default = None)
editor_mode (str): (default = 'line')
CommandLine:
python -m wbia.guitool.guitool_components TagEdit
Example:
>>> # DISABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> import wbia.guitool as gt
>>> gt.ensure_qtapp()
>>> self = TagEdit(tags=['a', 'b', 'c'])
>>> self.show()
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> gt.qtapp_loop(qwin=self, freq=10)
"""
def __init__(self, parent=None, tags=None, valid_tags=None):
super(TagEdit, self).__init__(parent)
# self.layout = QtWidgets.QHBoxLayout(self)
# if editor_mode == 'line':
# self.editor = QtWidgets.QLineEdit(parent=self)
# else:
# raise NotImplementedError('no fancy tag editing yet')
self.valid_tags = valid_tags
self._sep = ';'
self.setTags(tags)
# def setSizePolicy(self, *args):
# super(TagEdit, self).setSizePolicy(*args)
# self.editor.setSizePolicy(*args)
def _inject_new_widget_methods(self):
"""
helper for guitool widgets
adds the addNewXXX functions to the widget.
Bypasses having to set layouts. Can simply add widgets to other widgets.
Layouts are specified in the addNew constructors.
As such, this is less flexible, but quicker to get started.
"""
import wbia.guitool as gt
from wbia.guitool import PrefWidget2
# Creates addNewWidget and newWidget
def _make_new_widget_func(widget_cls):
def new_widget_maker(*args, **kwargs):
# verticalStretch = kwargs.pop('verticalStretch', 1)
vStretch = kwargs.pop('verticalStretch', None)
widget = widget_cls(*args, **kwargs)
_inject_new_widget_methods(widget)
# This line makes the widget resize with the widget
adjustSizePolicy(widget, vStretch=vStretch)
# sizePolicy = newSizePolicy(widget, verticalStretch=verticalStretch)
# widget.setSizePolicy(sizePolicy)
# setattr(widget, '_guitool_sizepolicy', sizePolicy)
return widget
return new_widget_maker
def _addnew_factory(self, newfunc):
""" helper for addNew guitool widgets """
def _addnew(self, *args, **kwargs):
name = kwargs.pop('name', None)
layout = self.layout()
layout_kw = {}
if isinstance(layout, QtWidgets.QGridLayout):
layout_kw.update(
{
'fromRow': kwargs.pop('fromRow', kwargs.pop('row', None)),
'fromColumn': kwargs.pop(
'fromColumn', kwargs.pop('column', None)
),
'rowSpan': kwargs.pop('rowSpan', 1),
'columnSpan': kwargs.pop('columnSpan', 1),
}
)
new_widget = newfunc(self, *args, **kwargs)
try:
self.addWidget(new_widget, **layout_kw)
except TypeError:
self.addWidget(new_widget)
if name is not None:
new_widget.setObjectName(name)
return new_widget
return _addnew
# Black magic
guitype_list = [
'Widget',
'Button',
'LineEdit',
'ComboBox',
'Label',
'Spoiler',
'CheckBox',
'TextEdit',
'Frame',
'Splitter',
'TabWidget',
'ProgressBar',
('RadioButton', QtWidgets.QRadioButton),
('RadioButtonGroup', RadioButtonGroup),
('EditConfigWidget', PrefWidget2.EditConfigWidget),
('TableWidget', QtWidgets.QTableWidget),
('TagEdit', TagEdit),
'ScrollArea',
# ('ScrollArea', QtWidgets.QScrollArea),
]
for guitype in guitype_list:
if isinstance(guitype, tuple):
guitype, widget_cls = guitype
newfunc = _make_new_widget_func(widget_cls)
else:
if hasattr(gt, 'new' + guitype):
newfunc = getattr(gt, 'new' + guitype)
ut.inject_func_as_method(self, newfunc, 'new' + guitype)
else:
newfunc = getattr(gt, guitype)
addnew_func = _addnew_factory(self, newfunc)
ut.inject_func_as_method(self, addnew_func, 'addNew' + guitype)
if not hasattr(self, 'addWidget'):
def _make_add_new_widgets():
def addWidget(self, widget, **kwargs):
# fromRow, fromColumn, rowSpan, columnSpan
layout = self.layout()
if isinstance(layout, QtWidgets.QGridLayout):
row = kwargs.pop('fromRow', kwargs.pop('row', None))
col = kwargs.pop('fromColumn', kwargs.pop('column', None))
rowSpan = kwargs.pop('rowSpan', 1)
columnSpan = kwargs.pop('columnSpan', 1)
if (row is None) != (col is None):
raise ValueError('only both or neither can be None')
if row is not None:
layout.addWidget(widget, row, col, rowSpan, columnSpan, **kwargs)
else:
if len(kwargs) == 0:
# qt weirdness
layout.addWidget(widget)
else:
layout.addWidget(widget, **kwargs)
else:
if len(kwargs) == 0:
# qt weirdness
layout.addWidget(widget)
else:
layout.addWidget(widget, **kwargs)
return widget
def addItem(self, layout_item, **kwargs):
layout = self.layout()
if isinstance(layout, QtWidgets.QGridLayout):
row = kwargs.pop('fromRow', kwargs.pop('row', None))
col = kwargs.pop('fromColumn', kwargs.pop('column', None))
if (row is None) != (col is None):
raise ValueError('only both or neither can be None')
if row is not None:
layout.addItem(layout_item, row, col, **kwargs)
else:
layout.addItem(layout_item, **kwargs)
else:
layout.addItem(layout_item, **kwargs)
# TODO: depricate in favor of addNewHWidget
def newHWidget(self, **kwargs):
return self.addNewWidget(orientation=Qt.Horizontal, **kwargs)
# TODO: depricate in favor of addNewVWidget
def newVWidget(self, **kwargs):
return self.addNewWidget(orientation=Qt.Vertical, **kwargs)
def addNewHWidget(self, **kwargs):
return self.addNewWidget(orientation=Qt.Horizontal, **kwargs)
def addNewVWidget(self, **kwargs):
return self.addNewWidget(orientation=Qt.Vertical, **kwargs)
return (
addWidget,
addItem,
newVWidget,
newHWidget,
addNewHWidget,
addNewVWidget,
)
for func in _make_add_new_widgets():
ut.inject_func_as_method(self, func, ut.get_funcname(func))
ut.inject_func_as_method(self, print_widget_heirarchy)
# Above code is the same as saying
# self.newButton = ut.partial(newButton, self)
# self.newWidget = ut.partial(newWidget, self)
# ... etc
[docs]def newLayout(parent=None, ori=None, spacing=None, margin=None):
if ori == 'grid':
layout = QtWidgets.QGridLayout(parent)
elif ori == 'flow':
layout = FlowLayout(parent)
else:
ori = rectifyQtEnum('Orientation', ori, Qt.Vertical)
if ori == Qt.Vertical:
layout = QtWidgets.QVBoxLayout(parent)
elif ori == Qt.Horizontal:
layout = QtWidgets.QHBoxLayout(parent)
else:
raise NotImplementedError('ori=%r' % (ori,))
if spacing is not None:
layout.setSpacing(spacing)
if margin is not None:
if hasattr(layout, 'setMargin'):
layout.setMargin(margin)
else:
layout.setContentsMargins(margin, margin, margin, margin)
return layout
[docs]def newFrame(*args, **kwargs):
widget = QtWidgets.QFrame()
ori = kwargs.get('ori', kwargs.get('orientation'))
spacing = kwargs.get('spacing')
margin = kwargs.get('margin')
layout = newLayout(parent=widget, ori=ori, spacing=spacing, margin=margin)
widget.setLayout(layout)
_inject_new_widget_methods(widget)
return widget
[docs]def newSpacer(w=0, h=0, hPolicy=None, vPolicy=None):
hPolicy = rectifySizePolicy(hPolicy, 'Expanding')
vPolicy = rectifySizePolicy(vPolicy, 'Expanding')
spacer = QtWidgets.QSpacerItem(w, h, hPolicy, vPolicy)
return spacer
# class GuitoolWidget(QtWidgets.QWidget):
[docs]def prop_text_map(prop, val):
if prop == 'QtWidgets.QSizePolicy':
pol_info = {
eval('QtWidgets.QSizePolicy.' + key): key
for key in [
'Fixed',
'Minimum',
'Maximum',
'Preferred',
'Expanding',
'MinimumExpanding',
'Ignored',
]
}
return pol_info[val]
else:
return val
[docs]def get_nested_attr(obj, attr):
"""
attr = 'sizePolicy().verticalPolicy()'
"""
attr_list = attr.split('.')
current = obj
for a in attr_list:
flag = a.endswith('()')
a_ = a[:-2] if flag else a
current = getattr(current, a_, None)
if current is None:
raise AttributeError(attr)
if flag:
current = current()
return current
[docs]def fix_child_attr_heirarchy(obj, attr, val):
if hasattr(obj, attr):
getattr(obj, attr)(val)
# obj.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
for child in obj.children():
fix_child_attr_heirarchy(child, attr, val)
[docs]def fix_child_size_heirarchy(obj, pol):
if hasattr(obj, 'sizePolicy'):
obj.setSizePolicy(
QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding
)
for child in obj.children():
fix_child_size_heirarchy(child, pol)
[docs]def get_widget_text_width(widget):
# http://stackoverflow.com/questions/14418375/shrink-a-button-width
text = widget.text()
double = text.count('&&')
text = text.replace('&', '') + ('&' * double)
text_width = widget.fontMetrics().boundingRect(text).width()
return text_width
[docs]def newComboBox(
parent=None,
options=None,
changed=None,
default=None,
visible=True,
enabled=True,
bgcolor=None,
fgcolor=None,
fontkw={},
editor_mode='combo',
num=2,
):
r"""
wrapper around QtWidgets.QComboBox
Args:
parent (None):
options (list): a list of tuples, which are a of the following form:
[
(visible text 1, backend value 1),
(visible text 2, backend value 2),
(visible text 3, backend value 3),
]
changed (None):
default (str): backend value of default item
visible (bool):
enabled (bool):
bgcolor (None):
fgcolor (None):
bold (bool):
Returns:
QtWidgets.QComboBox: combo
CommandLine:
python -m wbia.guitool.guitool_components --test-newComboBox --show
Example:
>>> # ENABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> import wbia.guitool as gt
>>> gt.ensure_qtapp()
>>> exec(ut.execstr_funckw(newComboBox), globals())
>>> parent = None
>>> options = ['red', 'blue']
>>> combo = newComboBox(parent, options)
>>> result = str(combo)
>>> print(result)
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> combo.show()
>>> gt.qtapp_loop(qwin=combo, freq=10)
"""
combo_kwargs = {
'parent': parent,
'options': options,
'default': default,
'changed': changed,
}
if editor_mode == 'combo':
combo = CustomComboBox(**combo_kwargs)
elif editor_mode == 'radio':
combo = RadioButtonGroup(**combo_kwargs)
elif editor_mode == 'hybrid':
combo = ComboRadioHybrid(num=num, **combo_kwargs)
else:
raise ValueError('editor_mode=%r' % (editor_mode,))
# if changed is None:
# enabled = False
combo.setVisible(visible)
combo.setEnabled(enabled)
adjust_font(combo, **fontkw)
return combo
[docs]class CustomComboBox(QtWidgets.QComboBox):
def __init__(combo, parent=None, default=None, options=None, changed=None):
super(CustomComboBox, combo).__init__(parent=parent)
# Check for tuple option formating
options = [
opt if isinstance(opt, tuple) and len(opt) == 2 else (str(opt), opt)
for opt in options
]
# QtWidgets.QComboBox.__init__(combo, parent)
combo.options = options
combo.changed = changed
combo.updateOptions()
# combo.allow_add = allow_add # TODO
# combo.setEditable(True)
# combo.setSizePolicy(QtWidgets.QSizePolicy.Preferred,
# QtWidgets.QSizePolicy.Preferred)
combo.setDefault(default)
combo.currentIndexChanged['int'].connect(combo.currentIndexChangedCustom)
[docs] def currentValue(combo):
index = combo.currentIndex()
opt = combo.options[index]
value = opt[1]
return value
[docs] def setOptions(combo, options):
flags = [isinstance(opt, tuple) and len(opt) == 2 for opt in options]
options = [opt if flag else (str(opt), opt) for flag, opt in zip(flags, options)]
combo.options = options
[docs] def updateOptions(combo, reselect=False, reselect_index=None):
if reselect_index is None:
reselect_index = combo.currentIndex()
combo.clear()
combo.addItems([option[0] for option in combo.options])
if reselect and reselect_index < len(combo.options):
combo.setCurrentIndex(reselect_index)
[docs] def setOptionText(combo, option_text_list):
for index, text in enumerate(option_text_list):
combo.setItemText(index, text)
# combo.removeItem()
[docs] def currentIndexChangedCustom(combo, index):
if combo.changed is not None:
combo.changed(index, combo.options[index][1])
[docs] def setDefault(combo, default=None):
if default is not None:
combo.setCurrentValue(default)
else:
combo.setCurrentIndex(0)
[docs] def setCurrentValue(combo, value):
index = combo.findValueIndex(value)
combo.setCurrentIndex(index)
[docs] def findValueIndex(combo, value):
""" finds index of backend value and sets the current index """
for index, (text, val) in enumerate(combo.options):
if value == val:
return index
else:
# Hack, try the text if value doesnt work
for index, (text, val) in enumerate(combo.options):
if value == text:
return index
else:
raise ValueError('No such option value=%r' % (value,))
[docs]def newCheckBox(
parent=None,
text=None,
changed=None,
checked=False,
visible=True,
enabled=True,
bgcolor=None,
fgcolor=None,
direction=None,
):
r"""
wrapper around QtWidgets.QCheckBox
CommandLine:
python -m wbia.guitool.guitool_components newCheckBox
Example:
>>> # ENABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> import wbia.guitool as gt
>>> app = gt.ensure_qtapp()[0]
>>> parent = newWidget()
>>> cb1 = gt.newCheckBox(parent, text='check_text1',
>>> direction='RightToLeft')
>>> parent.addWidget(cb1)
>>> cb2 = parent.addNewCheckBox(text='check_text2',
>>> direction='LeftToRight')
>>> parent.show()
>>> parent.resize(600, 40)
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> gt.print_widget_heirarchy(cb1)
>>> gt.qtapp_loop(qwin=parent, freq=10)
"""
if text is None:
text = '' if changed is None else ut.get_funcname(changed)
check_kwargs = {
'text': text,
'checked': checked,
'parent': parent,
'changed': changed,
}
checkbox = CustomCheckBox(**check_kwargs)
if direction is not None:
direction = rectifyQtEnum('LayoutDirection', direction)
checkbox.setLayoutDirection(direction)
# if changed is None:
# enabled = False
checkbox.setVisible(visible)
checkbox.setEnabled(enabled)
return checkbox
[docs]class CustomCheckBox(QtWidgets.QCheckBox):
def __init__(self, text='', parent=None, checked=False, changed=None):
# QtWidgets.QCheckBox.__init__(self, text, parent=parent)
super(CustomCheckBox, self).__init__(text, parent=parent)
self.changed = changed
if checked:
# 2 is equivelant to checked, 1 to partial, 0 to not checked
self.setCheckState(2)
else:
self.setCheckState(0)
self.stateChanged.connect(self.stateChangedCustom)
[docs] def stateChangedCustom(self, state):
if self.changed is not None:
self.changed(state == 2)
[docs]def newFont(fontname='Courier New', pointSize=-1, weight=-1, italic=False):
""" wrapper around QtGui.QFont """
# fontname = 'Courier New'
# pointSize = 8
# weight = -1
# italic = False
font = QtGui.QFont(fontname, pointSize=pointSize, weight=weight, italic=italic)
return font
[docs]def adjust_font(widget, bold=False, pointSize=None, italic=False):
if bold or pointSize is not None:
font = widget.font()
font.setBold(bold)
font.setItalic(italic)
if pointSize is not None:
font.setPointSize(pointSize)
widget.setFont(font)
[docs]def make_style_sheet(bgcolor=None, fgcolor=None):
style_list = []
fmtdict = {}
if bgcolor is not None:
if isinstance(bgcolor, six.string_types):
import wbia.plottool as pt
bgcolor = getattr(pt, bgcolor.upper())[0:3] * 255
style_list.append('background-color: rgb({bgcolor})')
fmtdict['bgcolor'] = ','.join(map(str, bgcolor))
if fgcolor is not None:
if isinstance(fgcolor, six.string_types):
import wbia.plottool as pt
fgcolor = getattr(pt, fgcolor.upper())[0:3] * 255
style_list.append('color: rgb({fgcolor})')
fmtdict['fgcolor'] = ','.join(map(str, fgcolor))
if len(style_list) > 0:
style_sheet_fmt = ';'.join(style_list)
style_sheet_str = style_sheet_fmt.format(**fmtdict)
return style_sheet_str
else:
return None
# def make_qstyle():
# style_factory = QtWidgets.QStyleFactory()
# style = style_factory.create('cleanlooks')
# #app_style = QtGui.Q Application.style()
[docs]def getAvailableFonts():
fontdb = QtGui.QFontDatabase()
available_fonts = list(map(str, list(fontdb.families())))
return available_fonts
[docs]def layoutSplitter(splitter):
old_sizes = splitter.sizes()
phi = ut.get_phi()
total = sum(old_sizes)
ratio = 1 / phi
sizes = []
for count, size in enumerate(old_sizes[:-1]):
new_size = int(round(total * ratio))
total -= new_size
sizes.append(new_size)
sizes.append(total)
splitter.setSizes(sizes)
[docs]def msg_event(title, msg):
""" Returns a message event slot """
return lambda: guitool_dialogs.msgbox(msg=msg, title=title)
[docs]class Spoiler(WIDGET_BASE):
r"""
References:
# Adapted from c++ version
http://stackoverflow.com/questions/32476006/how-to-make-an-expandable-collapsable-section-widget-in-qt
CommandLine:
python -m wbia.guitool.guitool_components Spoiler --show
Example:
>>> # ENABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> # build test data
>>> import wbia.guitool
>>> import wbia.guitool as gt
>>> guitool.ensure_qtapp()
>>> #ut.exec_funckw(newWidget, globals())
>>> parent = None
>>> widget1 = GuitoolWidget(parent)
>>> widget1.addWidget(gt.newButton(
>>> widget1, 'Print Hi', lambda: print('hi')))
>>> #widget2 = GuitoolWidget(parent)
>>> #widget2.addWidget(gt.newButton(
>>> # widget2, 'Popup Hi', lambda: gt.user_info(widget2, 'hi')))
>>> spoiler = Spoiler(title='spoiler title')
>>> widget1.layout().addWidget(spoiler)
>>> #top = widget1.addNewFrame()
>>> #top.layout().addWidget(spoiler)
>>> detailed_msg = 'Foo\nbar'
>>> child_widget = QtWidgets.QTextEdit()
>>> #child_widget.setWordWrap(True)
>>> #child_widget = QtWidgets.QPushButton()
>>> child_widget.setObjectName('child_widget')
>>> child_widget.setText(ut.lorium_ipsum() * 10)
>>> spoiler.setContentLayout(child_widget)
>>> widget1.print_widget_heirarchy()
>>> widget1.layout().setAlignment(Qt.AlignBottom)
>>> widget1.show()
>>> #widget1.resize(int(ut.PHI * 500), 500)
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> gt.qtapp_loop(qwin=widget1, freq=10)
"""
toggle_finished = QtCore.pyqtSignal(bool)
def __init__(
self,
parent=None,
title='',
animationDuration=300,
checked=False,
contentWidget=None,
):
super(Spoiler, self).__init__(parent=parent)
# Maps checked states to arrows and animation directions
self._arrow_states = {
False: QtCore.Qt.RightArrow,
True: QtCore.Qt.DownArrow,
}
self._animation_state = {
False: QtCore.QAbstractAnimation.Backward,
True: QtCore.QAbstractAnimation.Forward,
}
self.change_policy = False
#:
QSizePolicy = QtWidgets.QSizePolicy
self._header_size_policy_states = {
False: newSizePolicy(None, QSizePolicy.Expanding, QSizePolicy.Minimum),
True: newSizePolicy(None, QSizePolicy.Expanding, QSizePolicy.Expanding),
}
self._self_size_policy = {
False: newSizePolicy(None, QSizePolicy.Expanding, QSizePolicy.Minimum),
True: newSizePolicy(None, QSizePolicy.Expanding, QSizePolicy.Expanding),
}
self._scroll_size_policy_states = {
False: newSizePolicy(None, QSizePolicy.Expanding, QSizePolicy.Expanding),
True: newSizePolicy(None, QSizePolicy.Expanding, QSizePolicy.Expanding),
}
if not self.change_policy:
del self._header_size_policy_states[True]
del self._scroll_size_policy_states[True]
del self._scroll_size_policy_states[False]
self.checked = checked
self.animationDuration = 150
# 150
self.toggleButton = QtWidgets.QToolButton()
toggleButton = self.toggleButton
toggleButton.setStyleSheet('QToolButton { border: none; }')
toggleButton.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon)
toggleButton.setText(str(title))
toggleButton.setCheckable(True)
toggleButton.setArrowType(self._arrow_states[self.checked])
toggleButton.setChecked(self.checked)
toggleButton.clicked.connect(self.toggle_spoiler)
self.headerLine = QtWidgets.QFrame()
self.headerLine.setFrameShape(QtWidgets.QFrame.HLine)
self.headerLine.setFrameShadow(QtWidgets.QFrame.Sunken)
if self.change_policy:
self.headerLine.setSizePolicy(self._header_size_policy_states[self.checked])
else:
self.headerLine.setSizePolicy(self._header_size_policy_states[False])
if False:
if contentWidget is None:
self.contentWidget = QtWidgets.QScrollArea()
self.contentWidget.setStyleSheet(
'QScrollArea { background-color: white; border: none; }'
)
if self.change_policy:
self.contentWidget.setSizePolicy(
self._scroll_size_policy_states[self.checked]
)
else:
self.contentWidget.setSizePolicy(
self._scroll_size_policy_states[False]
)
self.contentWidget.setStyleSheet('QScrollArea { border: none; }')
# start out collapsed
self.contentWidget.setMaximumHeight(0)
self.contentWidget.setMinimumHeight(0)
self.contentWidget.setWidgetResizable(True)
else:
self.contentWidget = contentWidget
else:
self.contentWidget = None
# let the entire widget grow and shrink with its content
# The animation forces the minimum and maximum height to be equal
# By having the minimum and maximum height simultaniously
self.toggleAnimation = QtCore.QParallelAnimationGroup()
self.spoiler_animations = [
QtCore.QPropertyAnimation(self, six.b('minimumHeight')),
QtCore.QPropertyAnimation(self, six.b('maximumHeight')),
]
self.content_animations = [
# QtCore.QPropertyAnimation(self.contentWidget, 'maximumHeight')
]
for animation in self.spoiler_animations + self.content_animations:
self.toggleAnimation.addAnimation(animation)
# self.toggle_finished = self.toggleAnimation.finished
# don't waste space
self.mainLayout = QtWidgets.QGridLayout()
# self.mainLayout = QtWidgets.QVBoxLayout()
mainLayout = self.mainLayout
mainLayout.setVerticalSpacing(0)
mainLayout.setContentsMargins(0, 0, 0, 0)
# mainLayout.addWidget(self.toggleButton, alignment=QtCore.Qt.AlignLeft)
# mainLayout.addWidget(self.contentWidget)
mainLayout.addWidget(
self.toggleButton, 0, 0, 1, 1, QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop
)
# mainLayout.addWidget(self.headerLine, 1, 2, 1, 1)
# mainLayout.addWidget(self.contentWidget, 1, 0, 1, 3)
self.setLayout(self.mainLayout)
self.setMaximumHeight(16777215)
self.setMinimumHeight(0)
self.toggleAnimation.finished.connect(self.finalize_animation)
self.setSizePolicy(self._self_size_policy[self.checked])
if DEBUG_WIDGET:
# debug code
self.setStyleSheet(
'background-color: rgb(255,0,0); margin:5px; border:1px solid rgb(0, 255, 0); '
)
[docs] def finalize_animation(self):
if self.checked:
self.contentWidget.setMaximumHeight(16777215)
self.contentWidget.setMinimumHeight(0)
self.setMaximumHeight(16777215)
self.setMinimumHeight(0)
else:
self.contentWidget.setMaximumHeight(0)
self.contentWidget.setMinimumHeight(0)
# self.setMaximumHeight(0)
self.toggle_finished.emit(self.checked)
[docs] def toggle_spoiler(self, checked):
self.checked = checked
self.toggleButton.setArrowType(self._arrow_states[self.checked])
self.toggleAnimation.setDirection(self._animation_state[self.checked])
self.setSizePolicy(self._self_size_policy[self.checked])
if self.change_policy:
self.headerLine.setSizePolicy(self._header_size_policy_states[self.checked])
self.contentWidget.setSizePolicy(
self._scroll_size_policy_states[self.checked]
)
self.toggleAnimation.start()
[docs] def setContentLayout(self, contentLayout):
# Not sure if this is equivalent to self.contentWidget.destroy()
# self.contentWidget.destroy()
try:
self.contentWidget.setLayout(contentLayout)
except Exception:
# import utool
# utool.embed()
# HACKY
contentWidgetNew = contentLayout
contentWidgetOld = self.contentWidget
# self.contentWidget.setWidget(contentWidget)
if contentWidgetOld is not None:
# Replace existing scrollbar with something else
self.mainLayout.removeWidget(contentWidgetOld)
for animation in self.content_animations:
self.toggleAnimation.removeAnimation(animation)
self.contentWidget = contentWidgetNew
self.content_animations = [
QtCore.QPropertyAnimation(self.contentWidget, six.b('maximumHeight'))
]
for animation in self.content_animations:
self.toggleAnimation.addAnimation(animation)
self.contentWidget.setMaximumHeight(0)
self.contentWidget.setMinimumHeight(0)
self.mainLayout.addWidget(self.contentWidget, 1, 0, 1, 3)
# if False:
# if self.change_policy:
# self.contentWidget.setSizePolicy(self._scroll_size_policy_states[self.checked])
# else:
# self.contentWidget.setSizePolicy(self._scroll_size_policy_states[False])
# Find content height
collapsedConentHeight = 0
expandedContentHeight = contentLayout.sizeHint().height()
# Container height
collapsedSpoilerHeight = (
self.sizeHint().height() - self.contentWidget.maximumHeight()
)
expandedSpoilerHeight = collapsedSpoilerHeight + expandedContentHeight
contentStart = collapsedConentHeight
contentEnd = expandedContentHeight
spoilerStart = collapsedSpoilerHeight
spoilerEnd = expandedSpoilerHeight
if self.checked:
# Start expanded
spoilerStart, spoilerEnd = spoilerEnd, spoilerStart
contentStart, contentEnd = contentEnd, contentStart
self.contentWidget.setMinimumHeight(contentStart)
self.spoilerStart = spoilerStart
self.spoilerEnd = spoilerEnd
for spoilerAnimation in self.spoiler_animations:
spoilerAnimation.setDuration(self.animationDuration)
spoilerAnimation.setStartValue(spoilerStart)
spoilerAnimation.setEndValue(spoilerEnd)
for contentAnimation in self.content_animations:
contentAnimation.setDuration(self.animationDuration)
contentAnimation.setStartValue(contentStart)
contentAnimation.setEndValue(contentEnd)
[docs]class SimpleTree(QtCore.QObject):
"""
DEPRICATE IN FAVOR OF CONFIG WIDGETS? Need to make them heirarchical first
References:
http://stackoverflow.com/questions/12737721/developing-pyqt4-tree-widget
"""
def __init__(self, parent=None):
super(SimpleTree, self).__init__(parent)
self.tree = QtWidgets.QTreeWidget(parent)
if parent:
parent.addWidget(self.tree)
self.tree.setHeaderHidden(True)
self.root = self.tree.invisibleRootItem()
self.tree.itemChanged.connect(self.handleChanged)
# print('x = %r' % (x,))
# self.tree.itemClicked.connect(self.handleClicked)
self.callbacks = {}
[docs] def add_parent(self, parent=None, title='', data='ff'):
if parent is None:
parent = self.root
column = 0
item = QtWidgets.QTreeWidgetItem(parent, [title])
item.setData(column, QtCore.Qt.UserRole, data)
item.setChildIndicatorPolicy(QtWidgets.QTreeWidgetItem.ShowIndicator)
item.setExpanded(True)
return item
[docs] def add_checkbox(self, parent, title, data='ff', checked=False, changed=None):
column = 0
with BlockSignals(self.tree):
item = QtWidgets.QTreeWidgetItem(parent, [title])
item.setData(column, QtCore.Qt.UserRole, data)
item.setCheckState(column, Qt.Checked if checked else Qt.Unchecked)
if changed:
self.callbacks[item] = changed
# Inject helper method
def isChecked():
return item.checkState(column) == QtCore.Qt.Checked
def setChecked(flag):
return item.setCheckState(column, Qt.Checked if checked else Qt.Unchecked)
item.isChecked = isChecked
item.setChecked = setChecked
return item
[docs] def add_combobox(self, parent, title, data='ff', checked=False, changed=None):
column = 0
with BlockSignals(self.tree):
item = QtWidgets.QTreeWidgetItem(parent, [title])
item.setData(column, QtCore.Qt.UserRole, data)
item.setCheckState(column, Qt.Checked if checked else Qt.Unchecked)
if changed:
self.callbacks[item] = changed
# Inject helper method
def isChecked():
return item.checkState(column) == QtCore.Qt.Checked
def setChecked(flag):
return item.setCheckState(column, Qt.Checked if checked else Qt.Unchecked)
item.isChecked = isChecked
item.setChecked = setChecked
return item
# @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int)
# def handleClicked(self, item, column):
# pass
# # print('item = %r' % (item,))
[docs] @QtCore.pyqtSlot(QtWidgets.QTreeWidgetItem, int)
def handleChanged(self, item, column):
callback = self.callbacks.get(item, None)
if item.checkState(column) == QtCore.Qt.Checked:
state = True
if item.checkState(column) == QtCore.Qt.Unchecked:
state = False
if callback:
callback(state)
[docs]class BlockSignals(object):
def __init__(self, qobj):
self.qobj = qobj
self.prev = None
def __enter__(self):
self.prev = self.qobj.blockSignals(True)
def __exit__(self, tb, e, s):
self.qobj.blockSignals(self.prev)
#! /usr/bin/python2
# -*- coding: utf-8 -*-
[docs]class FlowLayout(QtWidgets.QLayout):
"""
References:
https://gist.github.com/Cysu/7461066
CommandLine:
python -m wbia.guitool.guitool_components FlowLayout
Ignore:
>>> import sys
>>> from wbia.guitool.guitool_components import * # NOQA
>>> import wbia.guitool as gt
>>> app = gt.ensure_qtapp()[0]
>>> #
>>> class Window(QtWidgets.QWidget):
>>> def __init__(self):
>>> super(Window, self).__init__()
>>> #
>>> flowLayout = FlowLayout()
>>> #flowLayout.setLayoutDirection(Qt.RightToLeft)
>>> flowLayout.addWidget(QtWidgets.QPushButton("Short"))
>>> flowLayout.addWidget(QtWidgets.QPushButton("Longer"))
>>> flowLayout.addWidget(QtWidgets.QPushButton("Different text"))
>>> flowLayout.addWidget(QtWidgets.QPushButton("More text"))
>>> flowLayout.addWidget(QtWidgets.QPushButton("Even longer button text"))
>>> self.setLayout(flowLayout)
>>> #
>>> self.setWindowTitle("Flow Layout")
>>> mainWin = Window()
>>> mainWin.show()
>>> gt.qtapp_loop(freq=10)
"""
def __init__(self, parent=None, margin=0, spacing=-1):
super(FlowLayout, self).__init__(parent)
if parent is not None:
if hasattr(self, 'setMargin'):
self.setMargin(margin)
else:
self.setContentsMargins(margin, margin, margin, margin)
self.setSpacing(spacing)
self.itemList = []
def __del__(self):
item = self.takeAt(0)
while item:
item = self.takeAt(0)
[docs] def addItem(self, item):
self.itemList.append(item)
[docs] def count(self):
return len(self.itemList)
[docs] def itemAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList[index]
return None
[docs] def takeAt(self, index):
if index >= 0 and index < len(self.itemList):
return self.itemList.pop(index)
return None
[docs] def expandingDirections(self):
return QtCore.Qt.Orientations(QtCore.Qt.Orientation(0))
[docs] def hasHeightForWidth(self):
return True
[docs] def heightForWidth(self, width):
height = self._doLayout(QtCore.QRect(0, 0, width, 0), True)
return height
[docs] def setGeometry(self, rect):
super(FlowLayout, self).setGeometry(rect)
self._doLayout(rect, False)
[docs] def sizeHint(self):
return self.minimumSize()
[docs] def minimumSize(self):
size = QtCore.QSize()
for item in self.itemList:
size = size.expandedTo(item.minimumSize())
if hasattr(self, 'contentsMargins'):
margin = self.contentsMargins()
margin = (margin.left() + margin.right() + margin.bottom() + margin.top()) / 4
else:
margin = self.margin()
size += QtCore.QSize(2 * margin, 2 * margin)
return size
def _doLayout(self, rect, testOnly):
x = rect.x()
y = rect.y()
lineHeight = 0
for item in self.itemList:
wid = item.widget()
spaceX = self.spacing() + wid.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
QtWidgets.QSizePolicy.PushButton,
QtCore.Qt.Horizontal,
)
spaceY = self.spacing() + wid.style().layoutSpacing(
QtWidgets.QSizePolicy.PushButton,
QtWidgets.QSizePolicy.PushButton,
QtCore.Qt.Vertical,
)
nextX = x + item.sizeHint().width() + spaceX
if nextX - spaceX > rect.right() and lineHeight > 0:
x = rect.x()
y = y + lineHeight + spaceY
nextX = x + item.sizeHint().width() + spaceX
lineHeight = 0
if not testOnly:
item.setGeometry(QtCore.QRect(QtCore.QPoint(x, y), item.sizeHint()))
x = nextX
lineHeight = max(lineHeight, item.sizeHint().height())
return y + lineHeight - rect.y()
[docs]class ComboRadioHybrid(GuitoolWidget):
"""
CommandLine:
python -m wbia.guitool.guitool_components ComboRadioHybrid --show
Example:
>>> # ENABLE_DOCTEST
>>> from wbia.guitool.guitool_components import * # NOQA
>>> import wbia.guitool as gt
>>> gt.ensure_qtapp()
>>> parent = None
>>> options = ['red', 'blue', 'green', 'blue', 'black', 'blah']
>>> options = [(a.title(), a) for a in options]
>>> self = ComboRadioHybrid(parent=parent, options=options)
>>> self.setCurrentValue('black')
>>> #self.print_widget_heirarchy(attrs=['sizePolicy'])
>>> # xdoctest: +REQUIRES(--show)
>>> ut.quit_if_noshow()
>>> self.show()
>>> print(self.currentText())
>>> gt.qtapp_loop(qwin=self, freq=10)
"""
def __init__(self, parent=None, **kwargs):
kwargs.pop('ori', kwargs.pop('orientation', 'vert'))
kwargs['ori'] = 'horiz'
kwargs['margin'] = 1
super(ComboRadioHybrid, self).__init__(parent=parent, **kwargs)
[docs] def on_toggle(self, button, checked):
pass
# if button is self.combo_rb:
# self.combo.setEnabled(checked)
[docs] def initialize(self, options=[], num=2, default=None, changed=None):
# Rectify options
options = [
opt if isinstance(opt, tuple) and len(opt) == 2 else (str(opt), opt)
for opt in options
]
if len(options) == num:
num += 1
self.radio_options = options[0:num]
self.combo_options = options[num:]
# Put all radio buttons in a group
self.group = QtWidgets.QButtonGroup(parent=self)
self.group.setExclusive(True)
self.group.buttonToggled.connect(self.on_toggle)
self.radio_buttons = ut.odict()
ori = 'grid'
for count, (nice, code) in enumerate(self.radio_options):
rb_widget = self.addNewWidget(
ori=ori, margin=1, spacing=1, name='opt_%d_widget' % (count,)
)
adjustSizePolicy(rb_widget, vPolicy='Fixed')
# rb_widget.addNewSpacer(row=0, column=0)
rb = rb_widget.addNewRadioButton(row=0, column=1, name='opt_%d_rb' % (count,))
adjustSizePolicy(rb, hPolicy='Fixed', vPolicy='Fixed')
# rb.setLayoutDirection(Qt.RightToLeft)
# rb_widget.addNewSpacer(row=0, column=2)
rb_widget.addNewLabel(
nice, row=1, column=0, columnSpan=3, name='opt_%d_label' % (count,)
)
# rb_widget.addNewSpacer(column=)
self.group.addButton(rb)
if code == default:
rb.setChecked(True)
self.radio_buttons[code] = rb
if len(self.combo_options) > 0:
rb_widget = self.addNewWidget(
ori=ori, margin=1, spacing=1, name='opt_rest_widget'
)
adjustSizePolicy(rb_widget, vPolicy='Fixed')
# rb_widget.addNewSpacer(row=0, column=0)
adjustSizePolicy(rb_widget, vPolicy='Fixed')
self.combo_rb = rb_widget.addNewRadioButton(
row=0, column=1, name='opt_rest_rb'
)
adjustSizePolicy(self.combo_rb, hPolicy='Fixed', vPolicy='Fixed')
# rb_widget.addNewSpacer(row=0, column=2)
self.combo = rb_widget.addNewComboBox(
options=self.combo_options,
row=1,
column=0,
columnSpan=3,
name='opt_rest_combo',
)
# model = self.combo.model()
if False:
# TODO: figure out how to align text without setting editable
model = self.combo.model()
for i in range(len(self.combo_options)):
self.combo.setItemData(i, Qt.AlignCenter, Qt.TextAlignmentRole)
for i in range(len(self.combo_options)):
print(self.combo.itemData(0))
print(model.itemData(model.index(i, 0)))
else:
self.combo.setEditable(True)
self.combo.lineEdit().setAlignment(QtCore.Qt.AlignCenter)
self.combo.lineEdit().setReadOnly(True)
# rb_widget.addNewSpacer()
self.group.addButton(self.combo_rb)
if default in ut.take_column(options, 1):
rb.setChecked(True)
# else:
# self.combo.setEnabled(False)
self.combo.activated.connect(lambda x: self.combo_rb.setChecked(True))
# self.combo.setStyleSheet(
# '''
# {
# text-align: center;
# }
# '''
# )
# self.combo.lineEdit().setAlignment(QtCore.Qt.AlignCenter)
# self.set_all_margins(1)
[docs] def setCurrentValue(self, value):
if value in self.radio_buttons:
self.radio_buttons[value].setChecked(True)
else:
self.combo.setCurrentValue(value)
self.combo_rb.setChecked(True)
[docs] def currentText(self):
if self.combo_rb.isChecked():
return self.combo.currentText()
for count, rb in enumerate(self.radio_buttons.values()):
if rb.isChecked():
return self.radio_options[count][0]
[docs] def currentValue(self):
if self.combo_rb.isChecked():
return self.combo.currentValue()
for count, rb in enumerate(self.radio_buttons.values()):
if rb.isChecked():
return self.radio_options[count][1]
if __name__ == '__main__':
"""
CommandLine:
python -m wbia.guitool.guitool_components
python -m wbia.guitool.guitool_components --allexamples
python -m wbia.guitool.guitool_components --allexamples --noface --nosrc
"""
import multiprocessing
multiprocessing.freeze_support() # for win32
import utool as ut # NOQA
ut.doctest_funcs()