# Copyright (C) 2014 Dignity Health
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# NO CLINICAL USE. THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL PURPOSES
# AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES. THE
# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC
# PURPOSES. YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR
# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT
# LIMITED TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES. LICENSOR
# MAKES NO WARRANTY AND HAS NO LIABILITY ARISING FROM ANY USE OF THE
# SOFTWARE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITIES.
import os
import re
import math
import json
# gpi
import gpi
from gpi import QtCore, QtGui, Qimport
QtWebKit = Qimport("QtWebKit")
from .config import Config
from .defaultTypes import GPITYPE_PASS
from .defines import WidgetTYPE, GPI_FLOAT_MIN, GPI_FLOAT_MAX
from .defines import GPI_INT_MIN, GPI_INT_MAX, TranslateFileURI
from .defines import getKeyboardModifiers, printMouseEvent
from .logger import manager
from .sysspecs import Specs
from . import syntax
# start logger for this module
log = manager.getLogger(__name__)
# WIDGET ELEMENT
class BasicPushButton(QtGui.QWidget):
valueChanged = gpi.Signal(bool)
def __init__(self, parent=None):
super(BasicPushButton, self).__init__(parent)
button_title = ''
self.wdg = QtGui.QPushButton(button_title, self)
self.wdg.setCheckable(False)
self.wdg.clicked[bool].connect(self.setButtonON)
self.wdg.clicked[bool].connect(self.valueChanged)
self.wdg.setMinimumWidth(50)
wdgLayout = QtGui.QGridLayout()
wdgLayout.addWidget(self.wdg, 0, 0, 1, 3)
wdgLayout.setContentsMargins(0, 0, 0, 0) # no spaces around this item
wdgLayout.setSpacing(0)
self.setLayout(wdgLayout)
self._value = False
# setters
def set_toggle(self, val):
self.wdg.setCheckable(val)
def set_button_title(self, title):
self.wdg.setText(title)
def set_val(self, value):
if self.wdg.isCheckable():
self.wdg.setChecked(value)
self._value = bool(value)
def set_reset(self):
# don't reset if its a toggle
if not self.wdg.isCheckable():
self.set_val(False)
# getters
def get_toggle(self):
return self.wdg.isCheckable()
def get_button_title(self):
return self.wdg.text()
def get_val(self):
return self._value
# support
def setButtonON(self):
if self.wdg.isCheckable():
self._value = self.wdg.isChecked()
else:
self._value = True
# WIDGET ELEMENT
class GPIDoubleSpinBox(QtGui.QDoubleSpinBox):
def __init__(self, parent=None):
super(GPIDoubleSpinBox, self).__init__(parent)
# these variables have to be updated b/c the lineEdit().isModified()
# and lineEdit.returnPressed() don't work as part of the spinbox.
self._last_val = self.value()
self._valChanged = False
self._focusOutEvent = False
def focusOutEvent(self, event):
# keep track of this event as a focusOutEvent for downstream
# differentiation.
self._focusOutEvent = True
# determine whether there was a change to the lineEdit
if self._last_val == self.value():
self._valChanged = False
else:
self._valChanged = True
self._last_val = self.value()
super(GPIDoubleSpinBox, self).focusOutEvent(event)
def isFocusOutEvent(self):
return self._focusOutEvent
def valueDidChange(self):
return self._valChanged
def updateTrackedEvents(self):
self._valChanged = False
self._focusOutEvent = False
self._last_val = self.value()
def setValue(self, val):
self._last_val = val
super(GPIDoubleSpinBox, self).setValue(val)
class BasicDoubleSpinBox(QtGui.QWidget):
valueChanged = gpi.Signal(float)
def __init__(self, parent=None):
super(BasicDoubleSpinBox, self).__init__(parent)
self.spin_label = QtGui.QLabel()
self.spin_label.setAlignment(
QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.spin_label.hide()
self.curSpinBox = GPIDoubleSpinBox()
self.curSpinBox.setSingleStep(1)
self.curSpinBox.setKeyboardTracking(False)
wdgLayout = QtGui.QHBoxLayout()
wdgLayout.addWidget(self.spin_label)
wdgLayout.addWidget(self.curSpinBox)
wdgLayout.setContentsMargins(0, 0, 0, 0) # no spaces around this item
wdgLayout.setSpacing(0)
wdgLayout.setStretch(0, 0)
wdgLayout.setStretch(1, 0)
self.setLayout(wdgLayout)
# defaults
self.set_val(0)
self.set_max(GPI_FLOAT_MAX)
self.set_min(GPI_FLOAT_MIN)
self._immediate = False
self.curSpinBox.editingFinished.connect(self.finishedEditing)
self.curSpinBox.valueChanged.connect(self.finishedChanging)
# setters
def set_keyboardtracking(self, val):
self.curSpinBox.setKeyboardTracking(val)
def set_max(self, val):
self.curSpinBox.setMaximum(val)
def set_min(self, val):
self.curSpinBox.setMinimum(val)
def set_val(self, val):
self.curSpinBox.setValue(val)
def set_label(self, val):
if val != '':
self.spin_label.setText(val)
self.spin_label.setVisible(True)
else:
self.spin_label.setVisible(False)
def set_wrapping(self, val):
self.curSpinBox.setWrapping(val)
def set_decimals(self, val):
self.curSpinBox.setDecimals(val)
def set_singlestep(self, val):
self.curSpinBox.setSingleStep(val)
def set_immediate(self, val):
self._immediate = val
# getters
def get_keyboardtracking(self):
return self.curSpinBox.keyboardTracking()
def get_max(self):
return self.curSpinBox.maximum()
def get_min(self):
return self.curSpinBox.minimum()
def get_val(self):
return self.curSpinBox.value()
def get_label(self):
return self.spin_label.text()
def get_wrapping(self):
return self.curSpinBox.wrapping()
def get_decimals(self):
return self.curSpinBox.decimals()
def get_singlestep(self):
return self.curSpinBox.singleStep()
def get_immediate(self):
return self._immediate
# support
def finishedEditing(self):
if not self._immediate:
if self.curSpinBox.isFocusOutEvent():
if self.curSpinBox.valueDidChange():
self.valueChanged.emit(self.get_val())
else: # assume return pressed
self.valueChanged.emit(self.get_val())
self.curSpinBox.updateTrackedEvents()
def finishedChanging(self, val):
if self._immediate:
self.valueChanged.emit(val)
# WIDGET ELEMENT
class GPISpinBox(QtGui.QSpinBox):
def __init__(self, parent=None):
super(GPISpinBox, self).__init__(parent)
# these variables have to be updated b/c the lineEdit().isModified()
# and lineEdit.returnPressed() don't work as part of the spinbox.
self._last_val = self.value()
self._valChanged = False
self._focusOutEvent = False
def focusOutEvent(self, event):
# keep track of this event as a focusOutEvent for downstream
# differentiation.
self._focusOutEvent = True
# determine whether there was a change to the lineEdit
if self._last_val == self.value():
self._valChanged = False
else:
self._valChanged = True
self._last_val = self.value()
super(GPISpinBox, self).focusOutEvent(event)
def isFocusOutEvent(self):
return self._focusOutEvent
def valueDidChange(self):
return self._valChanged
def updateTrackedEvents(self):
self._valChanged = False
self._focusOutEvent = False
self._last_val = self.value()
def setValue(self, val):
self._last_val = val
super(GPISpinBox, self).setValue(val)
class BasicSpinBox(QtGui.QWidget):
valueChanged = gpi.Signal(int)
def __init__(self, parent=None):
super(BasicSpinBox, self).__init__(parent)
self.spin_label = QtGui.QLabel()
self.spin_label.setAlignment(
QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.spin_label.hide()
self.curSpinBox = GPISpinBox()
self.curSpinBox.setSingleStep(1)
self.curSpinBox.setKeyboardTracking(False)
wdgLayout = QtGui.QHBoxLayout()
wdgLayout.addWidget(self.spin_label)
wdgLayout.addWidget(self.curSpinBox)
wdgLayout.setContentsMargins(0, 0, 0, 0) # no spaces around this item
wdgLayout.setSpacing(0)
wdgLayout.setStretch(0, 0)
wdgLayout.setStretch(1, 0)
self.setLayout(wdgLayout)
# defaults
self.set_val(0)
self.set_max(GPI_INT_MAX)
self.set_min(GPI_INT_MIN)
self._immediate = False
self.curSpinBox.editingFinished.connect(self.finishedEditing)
self.curSpinBox.valueChanged.connect(self.finishedChanging)
# setters
def set_max(self, val):
self.curSpinBox.setMaximum(val)
def set_min(self, val):
self.curSpinBox.setMinimum(val)
def set_val(self, val):
self.curSpinBox.setValue(val)
def set_label(self, val):
if val != '':
self.spin_label.setText(val)
self.spin_label.setVisible(True)
else:
self.spin_label.setVisible(False)
def set_wrapping(self, val):
self.curSpinBox.setWrapping(val)
def set_singlestep(self, val):
self.curSpinBox.setSingleStep(val)
def set_immediate(self, val):
self._immediate = val
# getters
def get_max(self):
return self.curSpinBox.maximum()
def get_min(self):
return self.curSpinBox.minimum()
def get_val(self):
return self.curSpinBox.value()
def get_label(self):
return self.spin_label.text()
def get_wrapping(self):
return self.curSpinBox.wrapping()
def get_singlestep(self):
return self.curSpinBox.singleStep()
def get_immediate(self):
return self._immediate
# support
def finishedEditing(self):
if not self._immediate:
if self.curSpinBox.isFocusOutEvent():
if self.curSpinBox.valueDidChange():
self.valueChanged.emit(self.get_val())
else: # assume return pressed
self.valueChanged.emit(self.get_val())
self.curSpinBox.updateTrackedEvents()
def finishedChanging(self, val):
if self._immediate:
self.valueChanged.emit(val)
# WIDGET ELEMENT
class BasicSlider(QtGui.QWidget):
valueChanged = gpi.Signal(int)
def __init__(self, parent=None):
super(BasicSlider, self).__init__(parent)
# self.setSizePolicy(QtGui.QSizePolicy.Minimum,
# QtGui.QSizePolicy.Minimum)
# slider
self.sl = QtGui.QSlider(QtCore.Qt.Horizontal, self)
self.sl.setTickPosition(QtGui.QSlider.TicksBothSides)
self.sl.setTickInterval(10)
self.sl.setSingleStep(1)
self.sl.setFocusPolicy(QtCore.Qt.StrongFocus)
self.sl.setSizePolicy(
QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Fixed)
# spinbox
self.sp = QtGui.QSpinBox(self)
self.sp.setSingleStep(1)
self.sp.setSizePolicy(
QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
self.sp.setKeyboardTracking(False)
# labels
self.smin = QtGui.QLabel(self)
self.smax = QtGui.QLabel(self)
self.smin.setSizePolicy(
QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
self.smax.setSizePolicy(
QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
self.smin.setAlignment(QtCore.Qt.AlignCenter)
self.smax.setAlignment(QtCore.Qt.AlignCenter)
# cross the signals and set output signal
self.sl.valueChanged.connect(self.sp.setValue)
self.sp.valueChanged.connect(self.sl.setValue)
self.sl.valueChanged.connect(self.valueChanged)
hbox = QtGui.QHBoxLayout(self)
hbox.addWidget(self.smin)
hbox.addWidget(self.sl)
hbox.addWidget(self.smax)
hbox.addWidget(self.sp)
hbox.setContentsMargins(0, 0, 0, 0) # we don't need margins here
hbox.setSpacing(5) # horizontal between elems
self.setLayout(hbox)
# defaults
self.set_val(0)
self.set_max(100)
self.set_min(0)
# setters
def set_val(self, value):
self.sl.setValue(value)
def set_min(self, value):
self.sl.setMinimum(value)
self.sp.setMinimum(value)
self.smin.setText(str(value))
def set_max(self, value):
self.sl.setMaximum(value)
self.sp.setMaximum(value)
self.smax.setText(str(value))
# getters
def get_max(self):
return self.sl.maximum()
def get_min(self):
return self.sl.minimum()
def get_val(self):
return self.sl.value()
# WIDGET ELEMENT
class BasicCWFCSliders(QtGui.QWidget):
# Center, Width, Floor, Ceiling
valueChanged = gpi.Signal()
def __init__(self, parent=None):
super(BasicCWFCSliders, self).__init__(parent)
# sliders
self.scenter = BasicSlider()
self.swidth = BasicSlider()
self.sfloor = BasicSlider()
self.sceil = BasicSlider()
# support functions
self.scenter.valueChanged.connect(self.centerChanged)
self.swidth.valueChanged.connect(self.widthChanged)
self.sfloor.valueChanged.connect(self.floorChanged)
self.sceil.valueChanged.connect(self.ceilChanged)
# layout
wdgLayout = QtGui.QVBoxLayout()
wdgLayout.addWidget(self.scenter)
wdgLayout.addWidget(self.swidth)
wdgLayout.addWidget(self.sfloor)
wdgLayout.addWidget(self.sceil)
wdgLayout.setSpacing(0)
wdgLayout.setContentsMargins(0, 3, 0, 0) # top margin
self.setLayout(wdgLayout)
self._min_width = 0
# support
def checkCWbounds(self, c, w):
ci = c
wi = w
w2 = w//2
mx = self.scenter.get_max()
mn = self.scenter.get_min()
ct = (c+w2-(not w % 2))
if ct > mx:
c = mx-w2+(not w % 2)
if c-w2 < mn:
c = mn+w2
return c, w, ci == c, wi == w
def checkFCbounds(self, f, c, fchanged):
fi = f
ci = c
if fchanged:
c = max(f, c)
f = min(f, c)
else:
f = min(f, c)
c = max(f, c)
return f, c, fi == f, ci == c
def centerChanged(self, c):
self.blockSliderSignals(True)
w = self.swidth.get_val()
c, w, cc, wc = self.checkCWbounds(c, w)
self.scenter.set_val(c)
if cc or wc:
self.swidth.set_val(w)
self.cwChanged(c, w)
self.blockSliderSignals(False)
self.valueChanged.emit()
def widthChanged(self, w):
self.blockSliderSignals(True)
c = self.scenter.get_val()
c, w, cc, wc = self.checkCWbounds(c, w)
# if w < self._min_width:
if cc or wc:
self.scenter.set_val(c)
self.cwChanged(c, w)
self.blockSliderSignals(False)
self.valueChanged.emit()
def floorChanged(self, f):
self.blockSliderSignals(True)
c = self.sceil.get_val()
f, c, fc, cc = self.checkFCbounds(f, c, True)
if fc or cc:
self.sceil.set_val(c)
self.fcChanged(f, c)
self.blockSliderSignals(False)
self.valueChanged.emit()
def ceilChanged(self, c):
self.blockSliderSignals(True)
f = self.sfloor.get_val()
f, c, fc, cc = self.checkFCbounds(f, c, False)
if fc or cc:
self.sfloor.set_val(f)
self.fcChanged(f, c)
self.blockSliderSignals(False)
self.valueChanged.emit()
def cwChanged(self, c, w):
self.sfloor.set_val(c-w//2)
self.sceil.set_val(c+w//2-(not w % 2))
def fcChanged(self, f, c):
self.scenter.set_val((c-f)//2+f)
self.swidth.set_val(c-f+1)
def blockSliderSignals(self, val):
self.scenter.blockSignals(val)
self.swidth.blockSignals(val)
self.sfloor.blockSignals(val)
self.sceil.blockSignals(val)
# setters
def set_min_width(self, val):
self._min_width = val
def set_center(self, val):
self.scenter.set_val(val)
def set_width(self, val):
self.swidth.set_val(val)
def set_floor(self, val):
self.sfloor.set_val(val)
def set_ceiling(self, val):
self.sceil.set_val(val)
def set_min(self, val):
self.scenter.set_min(val)
self.swidth.set_min(val)
self.sfloor.set_min(val)
self.sceil.set_min(val)
def set_max(self, val):
self.scenter.set_max(val)
self.swidth.set_max(val)
self.sfloor.set_max(val)
self.sceil.set_max(val)
def set_cwvisible(self, val):
self.scenter.setVisible(val)
self.swidth.setVisible(val)
self.sfloor.setVisible(not val)
self.sceil.setVisible(not val)
def set_fcvisible(self, val):
self.scenter.setVisible(not val)
self.swidth.setVisible(not val)
self.sfloor.setVisible(val)
self.sceil.setVisible(val)
def set_slicevisible(self, val):
self.scenter.setVisible(val)
self.sfloor.setVisible(not val)
self.sceil.setVisible(not val)
self.swidth.setVisible(not val)
def set_allvisible(self, val):
self.scenter.setVisible(val)
self.sfloor.setVisible(val)
self.sceil.setVisible(val)
self.swidth.setVisible(val)
# getters
def get_min_width(self):
return self._min_width
def get_center(self):
return self.scenter.get_val()
def get_width(self):
return self.swidth.get_val()
def get_floor(self):
return self.sfloor.get_val()
def get_ceiling(self):
return self.sceil.get_val()
def get_min(self):
return self.scenter.get_min()
def get_max(self):
return self.scenter.get_max()
# WIDGET ELEMENT
class GPIFileDialog(QtGui.QFileDialog):
def __init__(self, parent=None, cur_fname='', **kwargs):
super(GPIFileDialog, self).__init__(parent, **kwargs)
self._cur_fname = cur_fname
# if there is an existing filename, then populate the line
if cur_fname != '':
self.selectFile(os.path.basename(cur_fname))
else:
self.selectFile('Untitled')
# set the mount or media directories for easy use
pos_uri = self._listMediaDirs() # needs to be done each time for changing media
cur_sidebar = self.sidebarUrls()
for uri in pos_uri:
if QtCore.QUrl(uri) not in cur_sidebar:
cur_sidebar.append(QtCore.QUrl(uri))
# since the sidebar is remembered, we have to remove non-existing paths
cur_sidebar = [uri for uri in cur_sidebar if os.path.isdir(uri.path())]
self.setSidebarUrls(cur_sidebar)
self.setOption(QtGui.QFileDialog.DontUseNativeDialog)
def selectedFilteredFiles(self):
# enforce the selected filter in the captured filename
fnames = self.selectedFiles()
if len(fnames) == 0:
# no files were selected
return []
fnames_flt = [] # output
for fname in fnames:
# the default filter is 'All Files (*)'
fnames_flt.append(self.applyFilterToPath(fname))
return fnames_flt
def _listMediaDirs(self):
if Specs.inOSX():
rdir = '/Volumes'
if os.path.isdir(rdir):
return ['file://'+rdir+'/'+p for p in os.listdir(rdir)]
elif Specs.inLinux():
rdir = '/media'
if os.path.isdir(rdir):
return ['file://'+rdir+'/'+p for p in os.listdir(rdir)]
rdir = '/mnt'
if os.path.isdir(rdir):
return ['file://'+rdir+'/'+p for p in os.listdir(rdir)]
return []
def applyFilterToPath(self, fname):
# Given a QFileDialog filter string, make sure the given path adheres
# to the filter and return the filtered path string.
flt = str(self.selectedFilter())
# Enforce the selected filter in the captured filename
# filters are strings with content of the type:
# 'Images (*.png *.xpm *.jpg);;Text files (*.txt);;XML files (*.xml)'
par = ''.join(re.findall('\([^()]*\)', str(flt))) # just take whats in parens
suf = ' '.join(re.split('[()]', par)) # split out parens
suf = suf.split() # split on whitespace
suf = [os.path.splitext(s)[-1] for s in suf] # remove asterisks
# check for a valid suffix
basename, ext = os.path.splitext(fname)
if ext in suf:
return fname
# take the first suffix if the filename doesn't match any in the list
# append to fname (as opposed to basename) to allow the user to include
# dots in the filename.
return fname+suf[0]
def runSaveFileDialog(self):
self.setAcceptMode(QtGui.QFileDialog.AcceptSave)
self.setFileMode(QtGui.QFileDialog.AnyFile)
self.setConfirmOverwrite(True)
self.exec_()
return self.result()
def runOpenFileDialog(self):
self.setAcceptMode(QtGui.QFileDialog.AcceptOpen)
self.setFileMode(QtGui.QFileDialog.ExistingFile)
if self._cur_fname != '':
if os.path.isfile(self._cur_fname):
self.selectFile(os.path.basename(self._cur_fname))
self.exec_()
return self.result()
# PARTIAL WIDGET
class HidableGroupBox(QtGui.QGroupBox):
collapseChanged = gpi.Signal()
def __init__(self, title, parent=None):
super(HidableGroupBox, self).__init__(title, parent)
self._isCollapsed = False
def set_collapsed(self, col=True):
self._isCollapsed = col
self.collapseChanged.emit()
for wdg in self.children():
if hasattr(wdg, 'setVisible'):
wdg.setVisible(not col)
def mouseDoubleClickEvent(self, event):
event.accept()
if self._isCollapsed:
self.set_collapsed(False)
else:
self.set_collapsed(True)
# WIDGET GROUP BOX
# WIDGET
[docs]class SaveFileBrowser(GenericWidgetGroup):
"""Provide a QFileDialog() at the push of a button.
"""
valueChanged = gpi.Signal()
def __init__(self, title, parent=None):
super(SaveFileBrowser, self).__init__(title, parent)
button_title = ''
self.pb = QtGui.QPushButton(button_title, self)
self.pb.setMinimumWidth(50)
self.pb.setCheckable(False)
self.pb.clicked.connect(self.launchBrowser)
self.le = QtGui.QLineEdit()
self.le.returnPressed.connect(self.textChanged)
wdgLayout = QtGui.QGridLayout()
wdgLayout.addWidget(self.pb, 0, 0, 1, 1)
wdgLayout.addWidget(self.le, 0, 1, 1, 2)
wdgLayout.setColumnStretch(1, 1)
self.setLayout(wdgLayout)
self._value = ''
self._filter = None
self._caption = None
self._directory = Config.GPI_DATA_PATH # start in usr chosen data dir
self._last = ''
# setters
[docs] def set_filter(self, val):
"""str | Set the file extension filter (e.g. \'io_field (*.fld);;all (*)\').
"""
self._filter = val
[docs] def set_directory(self, val):
"""str | Set the default directory (str)."""
if type(val) is str:
if Config.GPI_FOLLOW_CWD:
self._directory = TranslateFileURI(val)
[docs] def set_caption(self, val):
"""str | Set browser title-bar (str)."""
self._caption = val
[docs] def set_val(self, value):
"""str | The filename and path (str)."""
self.le.setText(value)
self._value = value
self._last = value
if Config.GPI_FOLLOW_CWD:
self._directory = os.path.dirname(value)
# getters
def get_val(self):
return self._value
def get_directory(self):
return self._directory
# support
def textChanged(self):
# if its been changed by the label widget
val = TranslateFileURI(str(self.le.text()))
if self._filter is not None:
val = GPIFileDialog(filter=self._filter).applyFilterToPath(val)
if val != self._last:
self.set_val(val)
self.valueChanged.emit()
def launchBrowser(self):
kwargs = {}
kwargs['cur_fname'] = self.get_val()
if self._filter:
kwargs['filter'] = self._filter
if self._caption:
kwargs['caption'] = self._caption
if self._directory:
kwargs['directory'] = self._directory
# create dialog box
dia = GPIFileDialog(self, **kwargs)
# don't run if cancelled
if dia.runSaveFileDialog():
# save the current directory for next browse
if Config.GPI_FOLLOW_CWD:
self._directory = str(dia.directory().path())
# enforce the selected filter in the captured filename
fname = dia.selectedFilteredFiles()[0]
# allow browser to overwrite file if the same one is chosen
# this way the user has to approve an overwrite
self.set_val(fname)
self.valueChanged.emit()
# WIDGET
[docs]class OpenFileBrowser(GenericWidgetGroup):
"""Provide a QFileDialog() at the push of a button.
"""
valueChanged = gpi.Signal()
def __init__(self, title, parent=None):
super(OpenFileBrowser, self).__init__(title, parent)
button_title = ''
self.pb = QtGui.QPushButton(button_title, self)
self.pb.setMinimumWidth(50)
self.pb.setCheckable(False)
self.pb.clicked.connect(self.launchBrowser)
self.le = QtGui.QLineEdit()
self.le.returnPressed.connect(self.textChanged)
wdgLayout = QtGui.QGridLayout()
wdgLayout.addWidget(self.pb, 0, 0, 1, 1)
wdgLayout.addWidget(self.le, 0, 1, 1, 2)
wdgLayout.setColumnStretch(1, 1)
self.setLayout(wdgLayout)
self._value = ''
self._filter = None
self._caption = None
self._directory = Config.GPI_DATA_PATH # start in usr chosen data dir
self._last = ''
# setters
[docs] def set_filter(self, val):
"""str | Set the file extension filter (e.g. \'io_field (*.fld);;all (*)\').
"""
self._filter = val
[docs] def set_directory(self, val):
"""str | Set the default directory (str)."""
if type(val) is str:
if Config.GPI_FOLLOW_CWD:
self._directory = TranslateFileURI(val)
[docs] def set_caption(self, val):
"""str | Set browser title-bar (str)."""
self._caption = val
[docs] def set_val(self, value):
"""str | The filename and path (str)."""
self.le.setText(value)
self._value = value
self._last = value
if Config.GPI_FOLLOW_CWD:
self._directory = os.path.dirname(value)
# getters
def get_val(self):
return self._value
def get_directory(self):
return self._directory
# support
def textChanged(self):
val = TranslateFileURI(str(self.le.text()))
if val != self._last:
self.set_val(val)
self.valueChanged.emit()
def launchBrowser(self):
kwargs = {}
kwargs['cur_fname'] = self.get_val()
if self._filter:
kwargs['filter'] = self._filter
if self._caption:
kwargs['caption'] = self._caption
if self._directory:
kwargs['directory'] = self._directory
# create dialog box
dia = GPIFileDialog(self, **kwargs)
# don't run if cancelled
if dia.runOpenFileDialog():
# save the current directory for next browse
if Config.GPI_FOLLOW_CWD:
self._directory = str(dia.directory().path())
fname = str(dia.selectedFiles()[0])
# allow the browser to re-open a file
self.set_val(fname)
self.valueChanged.emit()
# WIDGET
[docs]class TextEdit(GenericWidgetGroup):
"""Provides an editable text window with
scrollbar and python code syntax highlighting.
"""
valueChanged = gpi.Signal(int)
def __init__(self, title, parent=None):
super(TextEdit, self).__init__(title, parent)
self.wdg = QtGui.QTextEdit()
self.wdg.setTabStopWidth(16)
# self.wdg.setTextBackgroundColor(QtGui.QColor(QtCore.Qt.black))
# should check if the editor is going to be used on python code
self.highlighter = syntax.PythonHighlighter(self.wdg.document())
# self.wdg.setPlainText(val)
wdgLayout = QtGui.QGridLayout()
wdgLayout.addWidget(self.wdg, 0, 0, 5, 3)
wdgLayout.setRowStretch(0, 2)
self.setLayout(wdgLayout)
# setters
[docs] def set_val(self, value):
"""str | The full plain-text to be displayed (str)."""
self.wdg.setPlainText(value)
# getters
def get_val(self):
return str(self.wdg.toPlainText())
# WIDGET
[docs]class TextBox(GenericWidgetGroup):
"""Provides a multi-line plain-text display."""
valueChanged = gpi.Signal(int)
def __init__(self, title, parent=None):
super(TextBox, self).__init__(title, parent)
self.wdg = QtGui.QLabel()
self.wdg.setText('')
wdgLayout = QtGui.QGridLayout()
wdgLayout.addWidget(self.wdg, 0, 0, 1, 3)
wdgLayout.setRowStretch(0, 2)
self.setLayout(wdgLayout)
# setters
[docs] def set_val(self, val):
"""str | The full plain-text to be displayed (str)."""
self.wdg.setText(val)
[docs] def set_wordwrap(self, val):
"""bool | Turn on/off with boolean True/False."""
self.wdg.setWordWrap(val)
[docs] def set_openExternalLinks(self, val):
"""bool | Open hyperrefs"""
self.wdg.setOpenExternalLinks(val)
# getters
def get_val(self):
return str(self.wdg.text())
def get_wordwrap(self):
return self.wdg.wordWrap()
def get_openExternalLinks(self):
return self.wdg.openExternalLinks()
# WIDGET
class GPILabel(QtGui.QLabel):
'''For use with the DisplayBox widget
'''
annotationChanged = gpi.Signal()
def __init__(self, wdgGroup, parent=None):
super(GPILabel, self).__init__(parent=parent)
self._wg = wdgGroup
self._line_p1 = None
self._line_p2 = None
def paintEvent(self, event):
super(GPILabel, self).paintEvent(event)
if self._wg.isPointer():
self.drawPoint(event)
elif self._wg.isLine():
self.drawLine(event)
elif self._wg.isRectangle():
self.drawRectangle(event)
elif self._wg.isEllipse():
self.drawEllipse(event)
def drawPoint(self, event):
if self._line_p1:
s = self._wg._scaleFact
x = ("%.0f" % (self._line_p1.x()/s))
y = ("%.0f" % (self._line_p1.y()/s))
buf_p1 = "("+x+","+y+")"
p = QtGui.QPainter()
p.begin(self)
font = p.font()
font.setPointSize(14)
p.setFont(font)
fm = QtGui.QFontMetricsF(font)
bw_p1 = fm.width(buf_p1)
bh = fm.height()
p.setBrush(QtCore.Qt.NoBrush)
p.setPen(QtCore.Qt.green)
p.drawPoint(self._line_p1)
# buf p1
adj_x = 2
if self._line_p1.x() > self._wg.imageLabel.size().width()//2:
adj_x = -bw_p1 -2
adj_y = -2
if self._line_p1.y() < self._wg.imageLabel.size().height()//2:
adj_y = bh +2
p.drawText(self._line_p1 + QtCore.QPoint(adj_x, adj_y), buf_p1)
p.end()
def drawLine(self, event):
if self._line_p1 and self._line_p2:
s = self._wg._scaleFact
diff = self._line_p1 - self._line_p2
dist = math.sqrt(diff.x() * diff.x() + diff.y() * diff.y())
dist /= s
p = QtGui.QPainter()
p.begin(self)
x = ("%.0f" % (self._line_p1.x()/s))
y = ("%.0f" % (self._line_p1.y()/s))
buf_p1 = "("+x+","+y+")"
buf_p2 = "l = "+ ("%.1f" % dist)
x = ("%.0f" % (self._line_p2.x()/s))
y = ("%.0f" % (self._line_p2.y()/s))
buf_p2 += " ("+x+","+y+")"
font = p.font()
font.setPointSize(14)
p.setFont(font)
fm = QtGui.QFontMetricsF(font)
bw_p1 = fm.width(buf_p1)
bw_p2 = fm.width(buf_p2)
bh = fm.height()
p.setBrush(QtCore.Qt.NoBrush)
p.setPen(QtCore.Qt.green)
p.drawLine(self._line_p1, self._line_p2)
# buf p1
adj_x = 2
if self._line_p1.x() > self._wg.imageLabel.size().width()//2:
adj_x = -bw_p1 -2
adj_y = -2
if self._line_p1.y() < self._wg.imageLabel.size().height()//2:
adj_y = bh +2
p.drawText(self._line_p1 + QtCore.QPoint(adj_x, adj_y), buf_p1)
# buf p2
adj_x = 2
if self._line_p2.x() > self._wg.imageLabel.size().width()//2:
adj_x = -bw_p2 -2
adj_y = -2
if self._line_p2.y() < self._wg.imageLabel.size().height()//2:
adj_y = bh +2
p.drawText(self._line_p2 + QtCore.QPoint(adj_x, adj_y), buf_p2)
p.end()
def drawRectangle(self, event):
if self._line_p1 and self._line_p2:
s = self._wg._scaleFact
diff = self._line_p1 - self._line_p2
dist = math.sqrt(diff.x() * diff.x() + diff.y() * diff.y())
dist /= s
p = QtGui.QPainter()
p.begin(self)
x = ("%.0f" % (self._line_p1.x()/s))
y = ("%.0f" % (self._line_p1.y()/s))
buf_p1 = "("+x+","+y+")"
buf_p2 = "A = "+ ("%.1f" % math.fabs(diff.x()*diff.y()))
x = ("%.0f" % (self._line_p2.x()/s))
y = ("%.0f" % (self._line_p2.y()/s))
buf_p2 += " ("+x+","+y+")"
font = p.font()
font.setPointSize(14)
p.setFont(font)
fm = QtGui.QFontMetricsF(font)
bw_p1 = fm.width(buf_p1)
bw_p2 = fm.width(buf_p2)
bh = fm.height()
p.setBrush(QtCore.Qt.NoBrush)
p.setPen(QtCore.Qt.green)
p.drawRect(QtCore.QRectF(self._line_p1, self._line_p2))
# buf p1
adj_x = 2
if self._line_p1.x() > self._wg.imageLabel.size().width()//2:
adj_x = -bw_p1 -2
adj_y = -2
if self._line_p1.y() < self._wg.imageLabel.size().height()//2:
adj_y = bh +2
p.drawText(self._line_p1 + QtCore.QPoint(adj_x, adj_y), buf_p1)
# buf p2
adj_x = 2
if self._line_p2.x() > self._wg.imageLabel.size().width()//2:
adj_x = -bw_p2 -2
adj_y = -2
if self._line_p2.y() < self._wg.imageLabel.size().height()//2:
adj_y = bh +2
p.drawText(self._line_p2 + QtCore.QPoint(adj_x, adj_y), buf_p2)
p.end()
def drawEllipse(self, event):
if self._line_p1 and self._line_p2:
s = self._wg._scaleFact
diff = self._line_p1 - self._line_p2
dist = math.sqrt(diff.x() * diff.x() + diff.y() * diff.y())
dist /= s
p = QtGui.QPainter()
p.begin(self)
x = ("%.0f" % (self._line_p1.x()/s))
y = ("%.0f" % (self._line_p1.y()/s))
buf_p1 = "("+x+","+y+")"
buf_p2 = "A = "+ ("%.1f" % math.fabs(math.pi*diff.x()*diff.y()/4.0))
x = ("%.0f" % (self._line_p2.x()/s))
y = ("%.0f" % (self._line_p2.y()/s))
buf_p2 += " ("+x+","+y+")"
font = p.font()
font.setPointSize(14)
p.setFont(font)
fm = QtGui.QFontMetricsF(font)
bw_p1 = fm.width(buf_p1)
bw_p2 = fm.width(buf_p2)
bh = fm.height()
p.setBrush(QtCore.Qt.NoBrush)
p.setPen(QtCore.Qt.green)
p.drawEllipse(QtCore.QRectF(self._line_p1, self._line_p2))
# buf p1
adj_x = 2
if self._line_p1.x() > self._wg.imageLabel.size().width()//2:
adj_x = -bw_p1 -2
adj_y = -2
if self._line_p1.y() < self._wg.imageLabel.size().height()//2:
adj_y = bh +2
p.drawText(self._line_p1 + QtCore.QPoint(adj_x, adj_y), buf_p1)
# buf p2
adj_x = 2
if self._line_p2.x() > self._wg.imageLabel.size().width()//2:
adj_x = -bw_p2 -2
adj_y = -2
if self._line_p2.y() < self._wg.imageLabel.size().height()//2:
adj_y = bh +2
p.drawText(self._line_p2 + QtCore.QPoint(adj_x, adj_y), buf_p2)
p.end()
def mousePressEvent(self, event):
super(GPILabel, self).mousePressEvent(event)
self._line_p1 = event.pos()
self.update()
def mouseMoveEvent(self, event):
super(GPILabel, self).mouseMoveEvent(event)
if self._wg.isPointer():
self._line_p1 = event.pos()
else:
self._line_p2 = event.pos()
self.update()
def mouseReleaseEvent(self, event):
super(GPILabel, self).mouseReleaseEvent(event)
self._line_p2 = event.pos()
if not self._wg.isPointer():
if self._line_p1 == self._line_p2:
self._line_p1 = None
self._line_p2 = None
self.update()
self.annotationChanged.emit()
def getPoints(self):
'''return drawn line coords if present'''
if self._line_p1 and self._line_p2:
s = self._wg._scaleFact
return ((self._line_p1.x()/s, self._line_p1.y()/s), (self._line_p2.x()/s, self._line_p2.y()/s))
def getLine(self):
'''return drawn line coords if present'''
if self._line_p1 and self._line_p2:
s = self._wg._scaleFact
return ((self._line_p1.x()/s, self._line_p1.y()/s), (self._line_p2.x()/s, self._line_p2.y()/s))
def getPoint(self):
if self._line_p1:
s = self._wg._scaleFact
return (self._line_p1.x()/s, self._line_p1.y()/s)
[docs]class DisplayBox(GenericWidgetGroup):
"""A 2D QPixmap using a QLabel using built-in interpolation schemes.
"""
valueChanged = gpi.Signal(int)
def __init__(self, title, parent=None):
super(DisplayBox, self).__init__(title, parent)
self.collapsables = []
self.imageLabel = GPILabel(self)
self.imageLabel.annotationChanged.connect(self.somethingChanged)
self.imageLabel.setBackgroundRole(QtGui.QPalette.Base)
self.imageLabel.setSizePolicy(
QtGui.QSizePolicy.Ignored, QtGui.QSizePolicy.Ignored)
self.imageLabel.setScaledContents(True)
self.scrollArea = QtGui.QScrollArea()
self.scrollArea.setBackgroundRole(QtGui.QPalette.Dark)
self.scrollArea.setWidget(self.imageLabel)
self.scrollArea.setWidgetResizable(False)
self.factSpinBox = BasicDoubleSpinBox()
self.factSpinBox.set_label('Scale Factor:')
self.factSpinBox.set_min(0.001)
self.factSpinBox.set_max(100)
self.factSpinBox.set_singlestep(0.1)
self.factSpinBox.set_val(1.0)
self.factSpinBox.set_decimals(3)
self.factSpinBox.set_immediate(True)
self.factSpinBox.set_keyboardtracking(False)
self.factSpinBox.valueChanged.connect(self.setImageScale)
self.collapsables.append(self.factSpinBox)
self.scaleCheckBox = QtGui.QCheckBox('No Scrollbars')
self.scaleCheckBox.setCheckState(QtCore.Qt.Checked)
self.scaleCheckBox.stateChanged.connect(self.fitMinWindowSize)
self.collapsables.append(self.scaleCheckBox)
self.interpCheckBox = QtGui.QCheckBox('Interpolated Scaling')
self.interpCheckBox.setCheckState(QtCore.Qt.Unchecked)
self.interpCheckBox.stateChanged.connect(self.applyImageScale)
self.collapsables.append(self.interpCheckBox)
self._clipboard_btn = BasicPushButton()
self._clipboard_btn.set_button_title('Copy')
self._clipboard_btn.set_toggle(False)
self._clipboard_btn.valueChanged.connect(self.copytoclipboard)
self.collapsables.append(self._clipboard_btn)
self._savefile_btn = BasicPushButton()
self._savefile_btn.set_button_title('Save')
self._savefile_btn.set_toggle(False)
self._savefile_btn.valueChanged.connect(self.savetopng)
self.collapsables.append(self._savefile_btn)
self._directory = None
self._filter = 'Image (*.png)'
self._caption = 'Save to PNG'
self._cur_fname = ''
# COPY/SAVE btns
hbox_cpysv = QtGui.QHBoxLayout()
hbox_cpysv.addWidget(self._clipboard_btn)
hbox_cpysv.addWidget(self._savefile_btn)
btns = ['Pointer', 'Line', 'Rectangle', 'Ellipse']
self.ann_box = QtGui.QHBoxLayout()
for btnl in btns:
btn = QtGui.QCheckBox(btnl)
btn.setCheckable(True)
btn.setAutoExclusive(True)
if btnl == 'Line':
btn.setChecked(True)
else:
btn.setChecked(False)
btn.stateChanged.connect(self.annotationButton)
self.ann_box.addWidget(btn)
self.collapsables.append(btn)
self.ann_type = 'Line'
# LEFT PANEL
vbox_l = QtGui.QVBoxLayout()
vbox_l.addLayout(hbox_cpysv)
vbox_l.addWidget(self.factSpinBox)
# CENTER PANEL
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.scaleCheckBox)
vbox.addWidget(self.interpCheckBox)
vbox.addLayout(self.ann_box)
# RIGHT PANEL
#vbox_r = QtGui.QVBoxLayout()
#vbox_r.addWidget(self._clipboard_btn)
hboxGroup = QtGui.QHBoxLayout()
hboxGroup.addLayout(vbox_l)
hboxGroup.addLayout(vbox)
#hboxGroup.addLayout(vbox_r)
hboxGroup.setStretch(0, 0)
self.wdg = self.scrollArea
wdgLayout = QtGui.QGridLayout()
wdgLayout.addWidget(self.wdg, 1, 0, 3, 3)
wdgLayout.addLayout(hboxGroup, 0, 0)
wdgLayout.setRowStretch(1, 2)
self.setLayout(wdgLayout)
self._pixmap = None
self._value = None
self._scaleFact = 1.0
self.set_collapsed(True) # hide options by default
def savetopng(self):
if self._pixmap is None:
log.warn('DisplayBox: There is no image to save, skipping.')
return
kwargs = {}
kwargs['cur_fname'] = self._cur_fname
kwargs['filter'] = self._filter
kwargs['caption'] = self._caption
kwargs['directory'] = self._directory
# create dialog box
dia = GPIFileDialog(self, **kwargs)
# don't run if cancelled
if dia.runSaveFileDialog():
# save the current directory for next browse
if Config.GPI_FOLLOW_CWD:
self._directory = str(dia.directory().path())
# enforce the selected filter in the captured filename
self._cur_fname = dia.selectedFilteredFiles()[0]
if self._pixmap.save(self._cur_fname, format='PNG'):
log.dialog('Image successfully saved.')
else:
log.warn('Image failed to save.')
def annotationButton(self, value):
if value:
for i in range(self.ann_box.count()):
if self.ann_box.itemAt(i).widget().isChecked():
self.ann_type = str(self.ann_box.itemAt(i).widget().text())
def annType(self):
return self.ann_type
def isPointer(self):
return self.ann_type == 'Pointer'
def isLine(self):
return self.ann_type == 'Line'
def isRectangle(self):
return self.ann_type == 'Rectangle'
def isEllipse(self):
return self.ann_type == 'Ellipse'
def copytoclipboard(self):
if self._pixmap is not None:
QtGui.QApplication.clipboard().setPixmap(self._pixmap)
log.dialog('DisplayBox image copied to clipboard.')
else:
log.warn('DisplayBox: There is no image to copy to the clipboard, skipping.')
# setters
[docs] def set_collapsed(self, val):
"""bool | Only collapse the display options, not the QPixmap/QLabel window.
"""
self._isCollapsed = val
for wdg in self.collapsables:
if hasattr(wdg, 'setVisible'):
wdg.setVisible(not val)
[docs] def set_val(self, image):
'''QImage | Image is a QImage().
'''
self._value = image
# QImages get filtered out of network serialization
# this is a bandaid, this code should instead work
# with a serializeable object.
if image is not None:
self._pixmap = QtGui.QPixmap.fromImage(image)
self.setImageScale(self._scaleFact)
[docs] def set_interp(self, val):
"""bool | Interpolated scaling / nearest neighbor, boolean True/False."""
self.interpCheckBox.setChecked(val)
[docs] def set_scale(self, val):
"""float | Pre-defined image dimension scale (float)"""
self.factSpinBox.set_val(val)
[docs] def set_pixmap(self, val):
"""QPixmap | A QPixmap to be displayed."""
self._pixmap = val
self.setImageScale(self._scaleFact)
[docs] def set_line(self, val):
'''N/A | Doesn't do anything yet.
'''
pass
[docs] def set_points(self, val):
'''N/A | Doesn't do anything yet.
'''
pass
# getters
def get_scale(self):
return self.factSpinBox.get_val()
def get_interp(self):
return self.interpCheckBox.isChecked()
def get_noscroll(self):
return self.scaleCheckBox.isChecked()
def get_val(self):
# QImage is not serializable
#return self._value
pass
def get_line(self):
return self.imageLabel.getLine()
def get_points(self):
if self.isPointer():
return self.imageLabel.getPoint()
elif self.isLine():
return self.imageLabel.getLine()
elif self.isRectangle():
return self.imageLabel.getPoints()
elif self.isEllipse():
return self.imageLabel.getPoints()
# support
[docs] def somethingChanged(self):
'''send a downstream event.'''
self.valueChanged.emit(0)
def setImageScale(self, scale):
self._scaleFact = scale
if self._pixmap is not None:
self.applyImageScale()
self.fitMinWindowSize()
def applyImageScale(self):
if self.interpCheckBox.checkState():
scale_type = QtCore.Qt.SmoothTransformation
else:
scale_type = QtCore.Qt.FastTransformation
if self._pixmap is not None:
newpixmap = self._pixmap.scaled(self._pixmap.size() * self._scaleFact,
aspectRatioMode=QtCore.Qt.KeepAspectRatio,
transformMode=scale_type)
self.imageLabel.setPixmap(newpixmap)
self.imageLabel.adjustSize()
def adjustScrollBar(self, scrollBar, factor):
scrollBar.setValue(int(factor * scrollBar.value()
+ ((factor - 1) * scrollBar.pageStep()//2)))
def fitMinWindowSize(self):
labsize = self.imageLabel.size()
h = labsize.height()
w = labsize.width()
pad = 4 # scroll won't turn off unless
if self.scaleCheckBox.checkState():
self.wdg.resize(labsize)
self.wdg.setMinimumSize(QtCore.QSize(w+pad, h+pad))
else:
self.wdg.setMinimumSize(QtCore.QSize(0, 0))
self.wdg.setMaximumSize(QtCore.QSize(w+pad+1, h+pad+1))
# WIDGET
# WIDGET
# A simple tool for storing strings in a non-plaintext manner
# NOTE: NOT for strong security.
# Temporarily taken out since zlib.decompress causes segfault on otherside of fork.
#import zlib
#def unhash_String(s):
# return zlib.decompress(s)
#def hash_String(s):
# return zlib.compress(s)
def unhash_String(s):
return s
def hash_String(s):
return s
[docs]class StringBox(GenericWidgetGroup):
"""A simple single line string box."""
valueChanged = gpi.Signal()
def __init__(self, title, parent=None):
super(StringBox, self).__init__(title, parent)
self.wdg = QtGui.QLineEdit()
wdgLayout = QtGui.QGridLayout()
wdgLayout.addWidget(self.wdg, 0, 0, 1, 3)
self.setLayout(wdgLayout)
self.wdg.returnPressed.connect(self.valueChanged)
self._mask = False
# setters
[docs] def set_val(self, value):
"""str | Set the string (str)."""
self.wdg.setText(value)
[docs] def set_mask(self, val):
'''bool | Toggle input mask'''
self._mask = val
if val:
self.wdg.setEchoMode(QtGui.QLineEdit.Password)
else:
self.wdg.setEchoMode(QtGui.QLineEdit.Normal)
[docs] def set_maskedval(self, val):
'''None | No setter for storing.
'''
pass
[docs] def set_placeholder(self, val):
'''str | Set the placeholder text (str).'''
self.wdg.setPlaceholderText(val)
# getters
[docs] def get_val(self):
'''If its in masked mode then it must be
specifically handled thru get_maskedval().
'''
if self._mask:
return ''
return str(self.wdg.text())
[docs] def get_maskedval(self):
'''If the string box is acting as a password
then this getter must be used.
'''
return hash_String(str(self.wdg.text()))
def get_mask(self):
return self._mask
def get_placeholder(self):
return str(self.wdg.placeholderText())
# WIDGET
[docs]class DoubleSpinBox(GenericWidgetGroup):
"""Spin box for floating point values. """
valueChanged = gpi.Signal(float)
def __init__(self, title, parent=None):
super(DoubleSpinBox, self).__init__(title, parent)
self.wdg = BasicDoubleSpinBox()
wdgLayout = QtGui.QHBoxLayout()
wdgLayout.addWidget(self.wdg)
# wdgLayout.setVerticalSpacing(0)
wdgLayout.setContentsMargins(0, 0, 0, 0) # no spaces around this item
wdgLayout.setSpacing(0)
self.setLayout(wdgLayout)
self.wdg.valueChanged.connect(self.valueChanged)
# setters
[docs] def set_max(self, val):
"""float | Max float value (<=)."""
self.wdg.set_max(val)
[docs] def set_min(self, val):
"""float | Min float value (>=)."""
self.wdg.set_min(val)
[docs] def set_val(self, val):
"""float | The value (float)."""
self.wdg.set_val(val)
[docs] def set_label(self, val):
"""str | Set a text left of the spinbox (str)."""
self.wdg.set_label(val)
[docs] def set_wrapping(self, val):
"""bool | Allow the up/down buttons to cause a wrap
when exceeding max or min values (bool)."""
self.wdg.set_wrapping(val)
[docs] def set_decimals(self, val):
"""int | The number of displayed decimal places (int)."""
self.wdg.set_decimals(val)
[docs] def set_singlestep(self, val):
"""float | The stepsize of one up/down button click (float)."""
self.wdg.set_singlestep(val)
# getters
def get_immediate(self):
return self.wdg.get_immediate()
def get_max(self):
return self.wdg.get_max()
def get_min(self):
return self.wdg.get_min()
def get_val(self):
return self.wdg.get_val()
def get_label(self):
return self.wdg.get_label()
def get_wrapping(self):
return self.wdg.get_wrapping()
def get_decimals(self):
return self.wdg.get_decimals()
def get_singlestep(self):
return self.wdg.get_singlestep()
# WIDGET
[docs]class WebBox(GenericWidgetGroup):
"""For loading web urls. """
valueChanged = gpi.Signal(str)
def __init__(self, title, parent=None):
super(WebBox, self).__init__(title, parent)
self.wdg = QtWebKit.QWebView()
wdgLayout = QtGui.QHBoxLayout()
wdgLayout.addWidget(self.wdg)
wdgLayout.setStretch(0, 2)
# wdgLayout.setVerticalSpacing(0)
wdgLayout.setContentsMargins(0, 0, 0, 0) # no spaces around this item
#wdgLayout.setSpacing(0)
self.setLayout(wdgLayout)
self.wdg.urlChanged.connect(self.urlToString)
self._user = None
self._passwd = None
self._url = None
# setters
[docs] def set_val(self, val):
"""str | The url (str)."""
self._url = QtCore.QUrl(val)
if self._user:
self._url.setUserName(self._user)
if self._passwd:
self._url.setPassword(self._passwd)
if self._url.isValid():
self.wdg.load(self._url)
else:
self._url = None
[docs] def set_username(self, usr):
'''str | For login sites'''
self._user = usr
if self._user is None:
return
if self._url:
self._url.setUserName(self._user)
self.wdg.load(self._url)
[docs] def set_passwd(self, passwd):
'''str | For login sites'''
# can be used with masked_val from Stringbox
self._passwd = passwd
if self._passwd is None:
return
if self._url:
self._url.setPassword(unhash_String(self._passwd))
self.wdg.load(self._url)
# getters
def get_val(self):
url = QtCore.QUrl(self.wdg.url())
# wipe user and pass
url.setPassword('')
url.setUserName('')
return url.toString()
def get_username(self):
return None
def get_passwd(self):
return None
# support
def urlToString(self, url):
self.valueChanged.emit(url.toString())
# WIDGET
[docs]class SpinBox(GenericWidgetGroup):
"""Spin box for integer values. """
valueChanged = gpi.Signal(int)
def __init__(self, title, parent=None):
super(SpinBox, self).__init__(title, parent)
self.wdg = BasicSpinBox()
wdgLayout = QtGui.QHBoxLayout()
wdgLayout.addWidget(self.wdg)
# wdgLayout.setVerticalSpacing(0)
wdgLayout.setContentsMargins(0, 0, 0, 0) # no spaces around this item
wdgLayout.setSpacing(0)
self.setLayout(wdgLayout)
self.wdg.valueChanged.connect(self.valueChanged)
# setters
[docs] def set_max(self, val):
"""int | Max integer value (<=)."""
self.wdg.set_max(val)
[docs] def set_min(self, val):
"""int | Min integer value (>=)."""
self.wdg.set_min(val)
[docs] def set_val(self, val):
"""int | The value (int)."""
self.wdg.set_val(val)
[docs] def set_label(self, val):
"""str | Set a text left of the spinbox (str)."""
self.wdg.set_label(val)
[docs] def set_wrapping(self, val):
"""int | The number of displayed decimal places (int)."""
self.wdg.set_wrapping(val)
[docs] def set_singlestep(self, val):
"""int | The stepsize of one up/down button click (int)."""
self.wdg.set_singlestep(val)
# getters
def get_immediate(self):
return self.wdg.get_immediate()
def get_max(self):
return self.wdg.get_max()
def get_min(self):
return self.wdg.get_min()
def get_val(self):
return self.wdg.get_val()
def get_label(self):
return self.wdg.get_label()
def get_wrapping(self):
return self.wdg.get_wrapping()
def get_singlestep(self):
return self.wdg.get_singlestep()
# WIDGET
[docs]class Slider(GenericWidgetGroup):
"""A slider for integer values. """
valueChanged = gpi.Signal(int)
def __init__(self, title, parent=None):
super(Slider, self).__init__(title, parent)
self.wdg = BasicSlider()
wdgLayout = QtGui.QGridLayout()
wdgLayout.addWidget(self.wdg, 0, 0, 1, 4)
wdgLayout.setVerticalSpacing(0)
self.setLayout(wdgLayout)
self.wdg.valueChanged.connect(self.valueChanged)
# setters
[docs] def set_val(self, value):
"""int | The value (int)."""
self.wdg.set_val(value)
[docs] def set_min(self, value):
"""int | Min integer value (>=)."""
self.wdg.set_min(value)
[docs] def set_max(self, value):
"""int | Max integer value (<=)."""
self.wdg.set_max(value)
# getters
def get_max(self):
return self.wdg.get_max()
def get_min(self):
return self.wdg.get_min()
def get_val(self):
return self.wdg.get_val()
# WIDGET
# WIDGET
# JGP 13jun20
# RKR modified 13jul10
# WIDGET
[docs]class ComboBox(GenericWidgetGroup):
"""Provides a popup list for different labels.
"""
valueChanged = gpi.Signal(int)
def __init__(self, title, parent=None):
super(ComboBox, self).__init__(title, parent)
self.wdg = QtGui.QComboBox()
wdgLayout = QtGui.QGridLayout()
wdgLayout.addWidget(self.wdg)
wdgLayout.setVerticalSpacing(0)
self.setLayout(wdgLayout)
self.wdg.currentIndexChanged.connect(self.valueChanged)
# setters
[docs] def set_items(self, items):
'''list(str,str,...) | list of items to choose from. Only one item can be chosen from the list. '''
self.wdg.clear()
self.wdg.addItems(items)
[docs] def set_val(self, item_text):
'''int | item index'''
item_text_index = 0
nr_items = self.wdg.count()
for index in range(nr_items):
if item_text == str(self.wdg.itemText(index)):
item_text_index = index
self.wdg.setCurrentIndex(item_text_index)
[docs] def set_index(self, index):
'''int | gives the corresponding index of the chosen item, starting at 0. So, for example, self.getAttr('myfruitbox',index) would return a 2 if 'orange' were selected from the item list given in this table, while self.getVal('myfruitbox') would return the string 'orange'. '''
# protect against async widget loading and user input
if index < self.wdg.count() and index >= 0:
self.wdg.setCurrentIndex(index)
# getters
def get_items(self):
items = []
nr_items = self.wdg.count()
for index in range(nr_items):
items.append(str(self.wdg.itemText(index)))
return items
def get_val(self):
return str(self.wdg.currentText())
def get_index(self):
return self.wdg.currentIndex()
# WIDGET