| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- #############################################################################
- ##
- ## Copyright (C) 2024 Riverbank Computing Limited.
- ## Copyright (C) 2006 Thorsten Marek.
- ## All right reserved.
- ##
- ## This file is part of PyQt.
- ##
- ## You may use this file under the terms of the GPL v2 or the revised BSD
- ## license as follows:
- ##
- ## "Redistribution and use in source and binary forms, with or without
- ## modification, are permitted provided that the following conditions are
- ## met:
- ## * Redistributions of source code must retain the above copyright
- ## notice, this list of conditions and the following disclaimer.
- ## * Redistributions in binary form must reproduce the above copyright
- ## notice, this list of conditions and the following disclaimer in
- ## the documentation and/or other materials provided with the
- ## distribution.
- ## * Neither the name of the Riverbank Computing Limited nor the names
- ## of its contributors may be used to endorse or promote products
- ## derived from this software without specific prior written
- ## permission.
- ##
- ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- ## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- ## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- ## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- ## OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- ## SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
- ## LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- ## DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- ## THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- ## (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- ## OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
- ##
- #############################################################################
- import logging
- import os.path
- import sys
- from .enum_map import EnumMap
- from .exceptions import NoSuchClassError, UnsupportedPropertyError
- from .icon_cache import IconCache
- logger = logging.getLogger(__name__)
- DEBUG = logger.debug
- QtCore = None
- QtGui = None
- QtWidgets = None
- def int_list(prop):
- return [int(child.text) for child in prop]
- def float_list(prop):
- return [float(child.text) for child in prop]
- bool_ = lambda v: v == "true"
- def qfont_style_strategy(v):
- return getattr(QtGui.QFont.StyleStrategy, v)
- def needsWidget(func):
- func.needsWidget = True
- return func
- # A translation table for converting ASCII lower case to upper case.
- _ascii_trans_table = bytes.maketrans(b'abcdefghijklmnopqrstuvwxyz',
- b'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
- def ascii_upper(s):
- """ Convert a string to ASCII upper case irrespective of the current
- locale.
- """
- return s.translate(_ascii_trans_table)
- class Properties(object):
- def __init__(self, factory, qtcore_module, qtgui_module, qtwidgets_module):
- self.factory = factory
- global QtCore, QtGui, QtWidgets
- QtCore = qtcore_module
- QtGui = qtgui_module
- QtWidgets = qtwidgets_module
- self._base_dir = ''
- self.reset()
- def set_base_dir(self, base_dir):
- """ Set the base directory to be used for all relative filenames. """
- self._base_dir = base_dir
- self.icon_cache.set_base_dir(base_dir)
- def reset(self):
- self.buddies = []
- self.delayed_props = []
- self.icon_cache = IconCache(self.factory, QtGui)
- def _pyEnumMember(self, cpp_name):
- if '::' not in cpp_name:
- cpp_name = 'Qt::' + cpp_name
- cpp_name = EnumMap.get(cpp_name, cpp_name)
- parts = cpp_name.split('::')
- if parts[0] == 'Qt':
- scope = QtCore.Qt
- else:
- scope = self.factory.findQObjectType(parts[0])
- if scope is None:
- raise NoSuchClassError(parts[0])
- for tail in parts[1:]:
- scope = getattr(scope, tail)
- return scope
- def _set(self, prop):
- expr = [self._pyEnumMember(v) for v in prop.text.split('|')]
- value = expr[0]
- for v in expr[1:]:
- value |= v
- return value
- def _enum(self, prop):
- return self._pyEnumMember(prop.text)
- def _number(self, prop):
- return int(prop.text)
- _UInt = _uInt = _longLong = _uLongLong = _number
- def _double(self, prop):
- return float(prop.text)
- def _bool(self, prop):
- return prop.text == 'true'
- def _stringlist(self, prop):
- return [self._string(p, notr='true') for p in prop]
- def _string(self, prop, notr=None):
- text = prop.text
- if text is None:
- return ""
- if prop.get('notr', notr) == 'true':
- return text
- disambig = prop.get('comment')
- return QtWidgets.QApplication.translate(self.uiname, text, disambig)
- _char = _string
- def _cstring(self, prop):
- return str(prop.text)
- def _color(self, prop):
- args = int_list(prop)
- # Handle the optional alpha component.
- alpha = int(prop.get("alpha", "255"))
- if alpha != 255:
- args.append(alpha)
- return QtGui.QColor(*args)
- def _point(self, prop):
- return QtCore.QPoint(*int_list(prop))
- def _pointf(self, prop):
- return QtCore.QPointF(*float_list(prop))
- def _rect(self, prop):
- return QtCore.QRect(*int_list(prop))
- def _rectf(self, prop):
- return QtCore.QRectF(*float_list(prop))
- def _size(self, prop):
- return QtCore.QSize(*int_list(prop))
- def _sizef(self, prop):
- return QtCore.QSizeF(*float_list(prop))
- def _pixmap(self, prop):
- if prop.text:
- fname = prop.text.replace("\\", "\\\\")
- if self._base_dir != '' and fname[0] != ':' and not os.path.isabs(fname):
- fname = os.path.join(self._base_dir, fname)
- return QtGui.QPixmap(fname)
- # Don't bother to set the property if the pixmap is empty.
- return None
- def _iconset(self, prop):
- return self.icon_cache.get_icon(prop)
- def _url(self, prop):
- return QtCore.QUrl(prop[0].text)
- def _locale(self, prop):
- lang = getattr(QtCore.QLocale.Language, prop.attrib['language'])
- country = getattr(QtCore.QLocale.Country, prop.attrib['country'])
- return QtCore.QLocale(lang, country)
- def _date(self, prop):
- return QtCore.QDate(*int_list(prop))
- def _datetime(self, prop):
- args = int_list(prop)
- return QtCore.QDateTime(QtCore.QDate(*args[-3:]), QtCore.QTime(*args[:-3]))
- def _time(self, prop):
- return QtCore.QTime(*int_list(prop))
- def _gradient(self, prop):
- name = 'gradient'
- # Create the specific gradient.
- gtype = prop.get('type', '')
- if gtype == 'LinearGradient':
- startx = float(prop.get('startx'))
- starty = float(prop.get('starty'))
- endx = float(prop.get('endx'))
- endy = float(prop.get('endy'))
- gradient = self.factory.createQtObject('QLinearGradient', name,
- ctor_args=(startx, starty, endx, endy), is_attribute=False)
- elif gtype == 'ConicalGradient':
- centralx = float(prop.get('centralx'))
- centraly = float(prop.get('centraly'))
- angle = float(prop.get('angle'))
- gradient = self.factory.createQtObject('QConicalGradient', name,
- ctor_args=(centralx, centraly, angle), is_attribute=False)
- elif gtype == 'RadialGradient':
- centralx = float(prop.get('centralx'))
- centraly = float(prop.get('centraly'))
- radius = float(prop.get('radius'))
- focalx = float(prop.get('focalx'))
- focaly = float(prop.get('focaly'))
- gradient = self.factory.createQtObject('QRadialGradient', name,
- ctor_args=(centralx, centraly, radius, focalx, focaly),
- is_attribute=False)
- else:
- raise UnsupportedPropertyError(prop.tag)
- # Set the common values.
- spread = prop.get('spread')
- if spread:
- gradient.setSpread(getattr(QtGui.QGradient.Spread, spread))
- cmode = prop.get('coordinatemode')
- if cmode:
- gradient.setCoordinateMode(
- getattr(QtGui.QGradient.CoordinateMode, cmode))
- # Get the gradient stops.
- for gstop in prop:
- if gstop.tag != 'gradientstop':
- raise UnsupportedPropertyError(gstop.tag)
- position = float(gstop.get('position'))
- color = self._color(gstop[0])
- gradient.setColorAt(position, color)
- return gradient
- def _palette(self, prop):
- palette = self.factory.createQtObject('QPalette', 'palette',
- is_attribute=False)
- for palette_elem in prop:
- sub_palette = getattr(QtGui.QPalette.ColorGroup,
- palette_elem.tag.title())
- for role, color in enumerate(palette_elem):
- if color.tag == 'color':
- # Handle simple colour descriptions where the role is
- # implied by the colour's position.
- palette.setColor(sub_palette,
- QtGui.QPalette.ColorRole(role), self._color(color))
- elif color.tag == 'colorrole':
- role = getattr(QtGui.QPalette.ColorRole, color.get('role'))
- brush = self._brush(color[0])
- palette.setBrush(sub_palette, role, brush)
- else:
- raise UnsupportedPropertyError(color.tag)
- return palette
- def _brush(self, prop):
- brushstyle = prop.get('brushstyle')
- if brushstyle in ('LinearGradientPattern', 'ConicalGradientPattern', 'RadialGradientPattern'):
- gradient = self._gradient(prop[0])
- brush = self.factory.createQtObject('QBrush', 'brush',
- ctor_args=(gradient, ), is_attribute=False)
- else:
- color = self._color(prop[0])
- brush = self.factory.createQtObject('QBrush', 'brush',
- ctor_args=(color, ), is_attribute=False)
- brushstyle = getattr(QtCore.Qt.BrushStyle, brushstyle)
- brush.setStyle(brushstyle)
- return brush
- #@needsWidget
- def _sizepolicy(self, prop, widget):
- values = [int(child.text) for child in prop]
- if len(values) == 2:
- # Qt v4.3.0 and later.
- horstretch, verstretch = values
- hsizetype = getattr(QtWidgets.QSizePolicy.Policy, prop.get('hsizetype'))
- vsizetype = getattr(QtWidgets.QSizePolicy.Policy, prop.get('vsizetype'))
- else:
- hsizetype, vsizetype, horstretch, verstretch = values
- hsizetype = QtWidgets.QSizePolicy.Policy(hsizetype)
- vsizetype = QtWidgets.QSizePolicy.Policy(vsizetype)
- sizePolicy = self.factory.createQtObject('QSizePolicy', 'sizePolicy',
- ctor_args=(hsizetype, vsizetype), is_attribute=False)
- sizePolicy.setHorizontalStretch(horstretch)
- sizePolicy.setVerticalStretch(verstretch)
- sizePolicy.setHeightForWidth(widget.sizePolicy().hasHeightForWidth())
- return sizePolicy
- _sizepolicy = needsWidget(_sizepolicy)
- # font needs special handling/conversion of all child elements.
- _font_attributes = (("Family", lambda s: s),
- ("PointSize", int),
- ("Bold", bool_),
- ("Italic", bool_),
- ("Underline", bool_),
- ("Weight", int),
- ("StrikeOut", bool_),
- ("Kerning", bool_),
- ("StyleStrategy", qfont_style_strategy))
- def _font(self, prop):
- newfont = self.factory.createQtObject('QFont', 'font',
- is_attribute=False)
- for attr, converter in self._font_attributes:
- v = prop.findtext("./%s" % (attr.lower(),))
- if v is None:
- continue
- getattr(newfont, "set%s" % (attr,))(converter(v))
- return newfont
- def _cursor(self, prop):
- return QtGui.QCursor(QtCore.Qt.CursorShape(int(prop.text)))
- def _cursorShape(self, prop):
- return QtGui.QCursor(getattr(QtCore.Qt.CursorShape, prop.text))
- def convert(self, prop, widget=None):
- try:
- func = getattr(self, "_" + prop[0].tag)
- except AttributeError:
- raise UnsupportedPropertyError(prop[0].tag)
- else:
- args = {}
- if getattr(func, "needsWidget", False):
- assert widget is not None
- args["widget"] = widget
- return func(prop[0], **args)
- def _getChild(self, elem_tag, elem, name, default=None):
- for prop in elem.findall(elem_tag):
- if prop.attrib["name"] == name:
- return self.convert(prop)
- else:
- return default
- def getProperty(self, elem, name, default=None):
- return self._getChild("property", elem, name, default)
- def getAttribute(self, elem, name, default=None):
- return self._getChild("attribute", elem, name, default)
- def setProperties(self, widget, elem):
- # Lines are sunken unless the frame shadow is explicitly set.
- set_sunken = (elem.attrib.get('class') == 'Line')
- for prop in elem.findall('property'):
- prop_name = prop.attrib['name']
- DEBUG("setting property %s" % (prop_name,))
- if prop_name == 'frameShadow':
- set_sunken = False
- try:
- stdset = bool(int(prop.attrib['stdset']))
- except KeyError:
- stdset = True
- if not stdset:
- self._setViaSetProperty(widget, prop)
- elif hasattr(self, prop_name):
- getattr(self, prop_name)(widget, prop)
- else:
- prop_value = self.convert(prop, widget)
- if prop_value is not None:
- getattr(widget, 'set%s%s' % (ascii_upper(prop_name[0]), prop_name[1:]))(prop_value)
- if set_sunken:
- widget.setFrameShadow(QtWidgets.QFrame.Shadow.Sunken)
- # SPECIAL PROPERTIES
- # If a property has a well-known value type but needs special,
- # context-dependent handling, the default behaviour can be overridden here.
- # Delayed properties will be set after the whole widget tree has been
- # populated.
- def _delayed_property(self, widget, prop):
- prop_value = self.convert(prop)
- if prop_value is not None:
- prop_name = prop.attrib["name"]
- self.delayed_props.append((widget, False,
- 'set%s%s' % (ascii_upper(prop_name[0]), prop_name[1:]),
- prop_value))
- # These properties will be set with a widget.setProperty call rather than
- # calling the set<property> function.
- def _setViaSetProperty(self, widget, prop):
- prop_value = self.convert(prop, widget)
- if prop_value is not None:
- prop_name = prop.attrib['name']
- # This appears to be a Designer/uic hack where stdset=0 means that
- # the viewport should be used.
- if prop[0].tag == 'cursorShape':
- widget.viewport().setProperty(prop_name, prop_value)
- else:
- widget.setProperty(prop_name, prop_value)
- # Ignore the property.
- def _ignore(self, widget, prop):
- pass
- # Define properties that use the canned handlers.
- currentIndex = _delayed_property
- currentRow = _delayed_property
- showDropIndicator = _setViaSetProperty
- intValue = _setViaSetProperty
- value = _setViaSetProperty
- objectName = _ignore
- margin = _ignore
- leftMargin = _ignore
- topMargin = _ignore
- rightMargin = _ignore
- bottomMargin = _ignore
- spacing = _ignore
- horizontalSpacing = _ignore
- verticalSpacing = _ignore
- # tabSpacing is actually the spacing property of the widget's layout.
- def tabSpacing(self, widget, prop):
- prop_value = self.convert(prop)
- if prop_value is not None:
- self.delayed_props.append((widget, True, 'setSpacing', prop_value))
- # buddy setting has to be done after the whole widget tree has been
- # populated. We can't use delay here because we cannot get the actual
- # buddy yet.
- def buddy(self, widget, prop):
- buddy_name = prop[0].text
- if buddy_name:
- self.buddies.append((widget, buddy_name))
- # geometry is handled specially if set on the toplevel widget.
- def geometry(self, widget, prop):
- if widget.objectName() == self.uiname:
- geom = int_list(prop[0])
- widget.resize(geom[2], geom[3])
- else:
- widget.setGeometry(self._rect(prop[0]))
- def orientation(self, widget, prop):
- # If the class is a QFrame, it's a line.
- if widget.metaObject().className() == 'QFrame':
- # Designer v6.7.0 and later use fully qualified enum names.
- widget.setFrameShape(
- {'Qt::Orientation::Horizontal': QtWidgets.QFrame.Shape.HLine,
- 'Qt::Horizontal': QtWidgets.QFrame.Shape.HLine,
- 'Qt::Orientation::Vertical' : QtWidgets.QFrame.Shape.VLine,
- 'Qt::Vertical' : QtWidgets.QFrame.Shape.VLine}[prop[0].text])
- else:
- widget.setOrientation(self._enum(prop[0]))
- # The isWrapping attribute of QListView is named inconsistently, it should
- # be wrapping.
- def isWrapping(self, widget, prop):
- widget.setWrapping(self.convert(prop))
- # This is a pseudo-property injected to deal with margins.
- def pyuicMargins(self, widget, prop):
- widget.setContentsMargins(*int_list(prop))
- # This is a pseudo-property injected to deal with spacing.
- def pyuicSpacing(self, widget, prop):
- horiz, vert = int_list(prop)
- if horiz == vert:
- widget.setSpacing(horiz)
- else:
- if horiz >= 0:
- widget.setHorizontalSpacing(horiz)
- if vert >= 0:
- widget.setVerticalSpacing(vert)
|