diff --git a/examples/window.cpp b/examples/window.cpp index d1dea9f..8e51aa8 100644 --- a/examples/window.cpp +++ b/examples/window.cpp @@ -1,430 +1,430 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur Copyright (C) 2008-2018 Jarosław Staniek Copyright (C) 2018 Dmitry Baryshev This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "window.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Window::Window() : QWidget() , m_set(this) , m_flatOption("flat", QCoreApplication::translate("main", "Flat display: do not display groups\n(useful for testing)")) , m_fontSizeOption("font-size", QCoreApplication::translate("main", "Set font size to (in points)\n(useful for testing whether editors keep the font settings)"), QCoreApplication::translate("main", "size")) , m_propertyOption("property", QCoreApplication::translate("main", "Display only specified property\n(useful when we want to focus on testing a single\nproperty editor)"), QCoreApplication::translate("main", "name")) , m_roOption("ro", QCoreApplication::translate("main", "Set all properties as read-only:\n(useful for testing read-only mode)")) { setObjectName("kpropertyexamplewindow"); setWindowIcon(QIcon::fromTheme("document-properties")); m_parser.setApplicationDescription(QCoreApplication::translate("main", "An example application for the KProperty library.")); m_parser.addHelpOption(); m_parser.addVersionOption(); parseCommandLine(); const QString singleProperty = m_parser.value(m_propertyOption); bool ok; const int fontSize = m_parser.value(m_fontSizeOption).toInt(&ok); if (fontSize > 0 && ok) { QFont f(font()); f.setPointSize(fontSize); setFont(f); } /* First, create a KPropertySet which will hold the properties. */ connect(&m_set, &KPropertySet::propertyChanged, this, &Window::propertyChanged); KProperty *p = nullptr; m_set.setReadOnly(m_parser.isSet(m_roOption)); QByteArray group; const bool addGroups = !m_parser.isSet(m_flatOption); if (addGroups) { group = "BasicGroup"; m_set.setGroupCaption(group, "Basic Properties"); } if (singleProperty.isEmpty() || singleProperty=="String") { m_set.addProperty(p = new KProperty("String", "String"), group); p->setValueSyncPolicy(KProperty::ValueSyncPolicy::Auto); p->setReadOnly(false); // this should not work: // - not needed if the property set is read-write // - ignored if the property set is read-only } if (singleProperty.isEmpty() || singleProperty=="MultiLine") { m_set.addProperty(p = new KProperty("MultiLine", "Multi\nLine\nContent"), group); p->setValueSyncPolicy(KProperty::ValueSyncPolicy::Auto); p->setOption("multiLine", true); } if (singleProperty.isEmpty() || singleProperty=="Int") { m_set.addProperty(new KProperty("Int", 2, "Int"), group); } if (singleProperty.isEmpty() || singleProperty=="Double") { m_set.addProperty(p = new KProperty("Double", 3.14159, "Double"), group); p->setOption("precision", 4); // will round to 3.1416 } if (singleProperty.isEmpty() || singleProperty=="Bool") { m_set.addProperty(new KProperty("Bool", QVariant(true), "Bool"), group); } if (singleProperty.isEmpty() || singleProperty=="NullBool") { m_set.addProperty(new KProperty("NullBool", QVariant(), "Null Bool", QString(), KProperty::Bool), group); } if (singleProperty.isEmpty() || singleProperty=="3-State") { m_set.addProperty(p = new KProperty("3-State", QVariant(), "3 States", QString(), KProperty::Bool), group); p->setOption("3State", true); } if (singleProperty.isEmpty() || singleProperty=="Date") { m_set.addProperty(p = new KProperty("Date", QDate::currentDate(), "Date"), group); p->setIconName("date"); } if (singleProperty.isEmpty() || singleProperty=="Time") { m_set.addProperty(new KProperty("Time", QTime::currentTime(), "Time"), group); } if (singleProperty.isEmpty() || singleProperty=="DateTime") { m_set.addProperty(new KProperty("DateTime", QDateTime::currentDateTime(), "Date/Time"), group); } QStringList name_list({"First Item", "Second Item", "Third Item"}); //strings if (singleProperty.isEmpty() || singleProperty=="List") { const QStringList keys({"1stitem", "2nditem", "3rditem"}); KPropertyListData *listData = new KPropertyListData(keys, name_list); m_set.addProperty(new KProperty("List", listData, QVariant(keys[1]), "List"), group); } QVariantList list_keys({1, 2, 3}); if (singleProperty.isEmpty() || singleProperty=="List2") { // A valueFromList property matching strings with ints (could be any type supported by QVariant) KPropertyListData *listData = new KPropertyListData; listData->setKeys(list_keys); listData->setNamesAsStringList(name_list); m_set.addProperty(new KProperty("List2", listData, list_keys.last(), "List 2"), group); } if (singleProperty.isEmpty() || singleProperty=="EditableList") { // A valueFromList property matching strings with ints (could be any type supported by QVariant) KPropertyListData *listData = new KPropertyListData; listData->setKeys(list_keys); listData->setNamesAsStringList(name_list); p = new KProperty("EditableList", listData, list_keys.first(), "Editable List"); p->setOption("extraValueAllowed", true); m_set.addProperty(p, group); } // Complex if (addGroups) { group = "ComposedGroup"; m_set.setGroupCaption(group, "Composed Properties"); } if (singleProperty.isEmpty() || singleProperty=="Point") { m_set.addProperty(new KProperty("Point", QPoint(3, 4), "Point"), group); } if (singleProperty.isEmpty() || singleProperty=="Size") { m_set.addProperty(new KProperty("Size", QSize(10, 20), "Size"), group); } if (singleProperty.isEmpty() || singleProperty=="Rect") { m_set.addProperty(new KProperty("Rect", QRect(5,11,100,200), "Rect"), group); } if (singleProperty.isEmpty() || singleProperty=="PointF") { m_set.addProperty(new KProperty("PointF", QPointF(3.14, 4.15), "PointF"), group); } if (singleProperty.isEmpty() || singleProperty=="SizeF") { m_set.addProperty(new KProperty("SizeF", QSizeF(1.1, 2.45), "SizeF"), group); } if (singleProperty.isEmpty() || singleProperty == "RectF") { m_set.addProperty( new KProperty("RectF", QRectF(0.1, 0.5, 10.72, 18.21), "RectF"), group); } // With suffixes and prefixes if (addGroups) { group = "PrefixesSuffixesGroup"; m_set.setGroupCaption(group, "Prefixes & Suffixes"); } if (singleProperty.isEmpty() || singleProperty=="dollars") { m_set.addProperty(p = new KProperty("dollars", 100, "Dollars"), group); p->setOption("prefix", "$"); } if (singleProperty.isEmpty() || singleProperty == "billions") { m_set.addProperty(p = new KProperty("billions", 5.0, "Billions"), group); p->setOption("prefix", "£"); p->setOption("suffix", "bn"); // default precision == 2 and step == 0.01 } if (singleProperty.isEmpty() || singleProperty == "PointF-mm") { m_set.addProperty( p = new KProperty("PointF-mm", QPointF(2.5, 3.5), "PointF [mm]"), group); p->setOption("suffix", "mm"); p->setOption("step", 0.1); p->setOption("precision", 2); } if (singleProperty.isEmpty() || singleProperty == "SizeF-dm") { m_set.addProperty( p = new KProperty("SizeF-dm", QSizeF(7.0, 6.5), "SizeF [dm]"), group); p->setOption("suffix", "dm"); p->setOption("step", 0.001); p->setOption("precision", 3); } if (singleProperty.isEmpty() || singleProperty=="RectF-px") { m_set.addProperty( p = new KProperty("RectF-px", QRectF(21.2, 22.2, 9.1, 1.0), "RectF [px]"), group); p->setOption("suffix", "px"); p->setOption("step", 0.1); p->setOption("precision", 1); } // Limits if (addGroups) { group = "LimitsGroup"; m_set.setGroupCaption(group, "Limits"); } if (singleProperty.isEmpty() || singleProperty=="uint") { m_set.addProperty(new KProperty("uint", 3, "Unsigned Int", QString(), KProperty::UInt), group); } if (singleProperty.isEmpty() || singleProperty=="uint+mintext") { m_set.addProperty(p = new KProperty("uint+mintext", 0, "Unsigned Int\n+min.text", QString(), KProperty::UInt), group); p->setOption("minValueText", "Minimum value"); } if (singleProperty.isEmpty() || singleProperty=="limit0_10") { m_set.addProperty(p = new KProperty("limit0_10", 9, "0..10", QString(), KProperty::UInt), group); p->setOption("max", 10); } if (singleProperty.isEmpty() || singleProperty == "limit1_5+mintext") { // This is a good test for fixing value out of range: "Could not assign value 9 larger than // maximum 5 -- setting to 5" warning is displayed and 5 is assigned. m_set.addProperty(p = new KProperty("limit1_5+mintext", 9, "1..5\n+mintext", QString(), KProperty::UInt), group); p->setOption("min", 1); p->setOption("max", 5); p->setOption("minValueText", "Minimum value"); } if (singleProperty.isEmpty() || singleProperty=="negative-int") { m_set.addProperty(p = new KProperty("negative-int", -2, "Negative Int", QString(), KProperty::Int), group); p->setOption("max", -1); } if (singleProperty.isEmpty() || singleProperty=="double-unlimited") { m_set.addProperty(p = new KProperty("double-unlimited", -1.11, "Double unlimited"), group); p->setOption("min", KPROPERTY_MIN_PRECISE_DOUBLE); p->setOption("precision", 1); p->setOption("step", 0.1); } if (singleProperty.isEmpty() || singleProperty=="double-negative") { m_set.addProperty(p = new KProperty("double-negative", -0.2, "Double negative"), group); p->setOption("min", KPROPERTY_MIN_PRECISE_DOUBLE); p->setOption("max", -0.1); p->setOption("precision", 1); p->setOption("step", 0.1); } if (singleProperty.isEmpty() || singleProperty=="double-minus1-0+mintext") { // This is a good test for fixing value out of range: "Could not assign value -9.0 smaller than // minimum -1.0 -- setting to -1.0" warning is displayed and -1.0 is assigned. m_set.addProperty(p = new KProperty("double-minus1-0+mintext", -9.0, "Double -1..0\n+mintext"), group); p->setOption("min", -1.0); p->setOption("max", 0.0); p->setOption("precision", 1); p->setOption("step", 0.1); p->setOption("minValueText", "Minimum value"); } // Appearance if (addGroups) { group = "AppearanceGroup"; - m_set.setGroupCaption(group, "Appearance Group"); + m_set.setGroupCaption(group, "Appearance"); m_set.setGroupIconName(group, "appearance"); } if (singleProperty.isEmpty() || singleProperty=="Color") { m_set.addProperty(new KProperty("Color", palette().color(QPalette::Active, QPalette::Background), "Color"), group); } if (singleProperty.isEmpty() || singleProperty=="Pixmap") { QPixmap pm(QIcon::fromTheme("network-wired").pixmap(QSize(64, 64))); m_set.addProperty(p = new KProperty("Pixmap", pm, "Pixmap"), group); p->setIconName("kpaint"); } if (singleProperty.isEmpty() || singleProperty=="Font") { QFont myFont("Times New Roman", 12); myFont.setUnderline(true); m_set.addProperty(p = new KProperty("Font", myFont, "Font"), group); p->setIconName("fonts"); } if (singleProperty.isEmpty() || singleProperty=="Cursor") { m_set.addProperty(new KProperty("Cursor", QCursor(Qt::WaitCursor), "Cursor"), group); } if (singleProperty.isEmpty() || singleProperty=="LineStyle") { m_set.addProperty(new KProperty("LineStyle", 3, "Line Style", QString(), KProperty::LineStyle), group); } if (singleProperty.isEmpty() || singleProperty=="SizePolicy") { QSizePolicy sp(sizePolicy()); sp.setHorizontalStretch(1); sp.setVerticalStretch(2); m_set.addProperty(new KProperty("SizePolicy", sp, "Size Policy"), group); } if (singleProperty.isEmpty() || singleProperty=="Invisible") { m_set.addProperty(p = new KProperty("Invisible", "I am invisible", "Invisible"), group); p->setVisible(false); } if (singleProperty.isEmpty() || singleProperty=="Url") { m_set.addProperty(p = new KProperty("Url", QUrl("https://community.kde.org/KProperty"), "Url"), group); } if (singleProperty.isEmpty() || singleProperty=="RelativeComposedUrl") { const KPropertyComposedUrl composedUrl(QUrl::fromLocalFile(QApplication::applicationDirPath()), QLatin1String("data/icons/kproperty_breeze.rcc")); p = new KProperty("RelativeComposedUrl", QVariant::fromValue(composedUrl), "ComposedUrl with\nrelative path", QString(), KProperty::ComposedUrl); m_set.addProperty(p, group); } if (singleProperty.isEmpty() || singleProperty=="AbsoluteComposedUrl") { const KPropertyComposedUrl composedUrl(QUrl::fromLocalFile(QApplication::applicationDirPath()), QUrl("https://community.kde.org/KProperty")); p = new KProperty("AbsoluteComposedUrl", QVariant::fromValue(composedUrl), "ComposedUrl with\nabsolute URL", QString(), KProperty::ComposedUrl); m_set.addProperty(p, group); } if (singleProperty.isEmpty() || singleProperty=="ExistingFile") { m_set.addProperty(p = new KProperty("ExistingFile", QUrl::fromLocalFile(QCoreApplication::applicationFilePath()), "Existing\nFile"), group); p->setOption("fileMode", "existingFile"); } if (singleProperty.isEmpty() || singleProperty=="OverwriteFile") { m_set.addProperty(p = new KProperty("OverwriteFile", QUrl::fromLocalFile(QDir::homePath() + "/" + QCoreApplication::applicationName() + ".txt"), "New File or\nOverwrite Existing"), group); p->setOption("confirmOverwrites", true); } if (singleProperty.isEmpty() || singleProperty=="Dir") { m_set.addProperty(p = new KProperty("Dir", QUrl::fromLocalFile(QDir::homePath()), "Dir"), group); p->setOption("fileMode", "dirsOnly"); } if (singleProperty.isEmpty() || singleProperty=="ReadOnly") { m_set.addProperty(p = new KProperty("Read-Only", "Read-only string"), group); p->setReadOnly(true); } // Properties for testing tooltips if (singleProperty.isEmpty() || singleProperty == "StaticToolTip") { m_set.addProperty(new KProperty("StaticToolTip", "Some value", "Static Tooltip", "This is a static tooltip based on value of KProperty::description()"), group); m_set.addProperty( m_dynamicToolTipProperty = new KProperty("DynamicToolTip", "Some dynamic value", "Dynamic Tooltip"), group); propertyChanged(m_set, *m_dynamicToolTipProperty); // this updates description } QVBoxLayout *lyr = new QVBoxLayout(this); m_editorView = new KPropertyEditorView(this); m_editorView->setToolTipsVisible(true); lyr->addWidget(m_editorView); m_editorView->changeSet(&m_set); m_editorView->resizeColumnToContents(0); lyr->addSpacing(lyr->spacing()); QHBoxLayout *hlyr = new QHBoxLayout; lyr->addLayout(hlyr); QCheckBox *showGrid = new QCheckBox("Show grid"); showGrid->setChecked(true); connect(showGrid, &QCheckBox::stateChanged, this, &Window::showGrid); hlyr->addWidget(showGrid); QCheckBox *showFrame = new QCheckBox("Show frame"); showFrame->setChecked(true); connect(showFrame, &QCheckBox::stateChanged, this, &Window::showFrame); hlyr->addWidget(showFrame); QCheckBox *showGroups = new QCheckBox("Show groups"); if (addGroups) { connect(showGroups, &QCheckBox::toggled, m_editorView, &KPropertyEditorView::setGroupsVisible); showGroups->setChecked(m_editorView->groupsVisible()); } else { showGroups->setEnabled(false); } hlyr->addWidget(showGroups); QCheckBox *showToolTips = new QCheckBox("Show tooltips"); connect(showToolTips, &QCheckBox::toggled, m_editorView, &KPropertyEditorView::setToolTipsVisible); showToolTips->setChecked(m_editorView->toolTipsVisible()); hlyr->addWidget(showToolTips); QCheckBox *readOnly = new QCheckBox("Read-only"); connect(readOnly, &QCheckBox::toggled, &m_set, &KPropertySet::setReadOnly); readOnly->setChecked(m_set.isReadOnly()); hlyr->addWidget(readOnly); hlyr->addStretch(1); resize(400, qApp->desktop()->height() - 200); m_editorView->setFocus(); qDebug() << m_set; } Window::~Window() { } void Window::parseCommandLine() { m_parser.addOption(m_flatOption); m_parser.addOption(m_fontSizeOption); m_parser.addOption(m_propertyOption); m_parser.addOption(m_roOption); m_parser.process(*(QCoreApplication::instance())); } void Window::showGrid(int state) { m_editorView->setGridLineColor( state == Qt::Checked ? KPropertyEditorView::defaultGridLineColor() : QColor()); } void Window::showFrame(int state) { m_editorView->setFrameStyle(state == Qt::Checked ? QFrame::Box : QFrame::NoFrame); } void Window::propertyChanged(KPropertySet& set, KProperty& property) { Q_UNUSED(set) if (&property == m_dynamicToolTipProperty) { property.setDescription(QString::fromLatin1("This is a dynamic tooltip based on value of " "\"%1\" property which is currently \"%2\"") .arg(property.caption(), property.value().toString())); } } diff --git a/src/KProperty.h b/src/KProperty.h index 220e7d6..a516b37 100644 --- a/src/KProperty.h +++ b/src/KProperty.h @@ -1,529 +1,531 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur Copyright (C) 2004 Alexander Dymo Copyright (C) 2004-2017 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KPROPERTY_PROPERTY_H #define KPROPERTY_PROPERTY_H #include #include #include #include #include #include #include "kpropertycore_export.h" class KComposedPropertyInterface; class KPropertyListData; class KPropertySet; class KPropertySetPrivate; /** * @brief Minimum double value working precisely. * * @since 3.1 */ #define KPROPERTY_MIN_PRECISE_DOUBLE (-pow(2, std::numeric_limits::digits)) /** * @brief Minimum double value working precisely * * Editor for double values (spin box) has localized contents and its code supports just this maximum. * For a 64-bit machine it's 2**53. * See also https://phabricator.kde.org/D5419#inline-22329 * * @since 3.1 */ #define KPROPERTY_MAX_PRECISE_DOUBLE (pow(2, std::numeric_limits::digits)) /*! \brief The base class representing a single property KProperty object can hold a property of given type supported by QVariant. Properties of custom types can be also created, see using KPropertyFactory. Composed or custom properties are not created using subclassing of KProperty but using @ref KComposedPropertyInterface. Each property stores old value to allows undoing that reverts the value to the old one. Property has a non-empty name (a QByteArray), a caption that is user-visible translated string displayed in property editor. Description is a translatable string that can be specified too in order to further explain meaning of the property. Propery also supports setting arbitrary number of options using KProperty::setOption() that allow to customize look or behavior of the property in the editor. @code // Creating a simple property: KProperty *property = new KProperty(name, value, caption, description); // name is a QByteArray, value is whatever type QVariant supports // Creating a property of type ValueFromList matching keys with names: QStringList keys({"one", "two", "three"}); // possible values of the property QStringList names({tr("One"), tr("Two"), tr("Three")}); // Names (possibly translated) shown in // the editor instead of the keys property = new KProperty(name, new KPropertyListData(keys, names), "two", caption); // Creating a property of type ValueFromList matching variant keys with names: KPropertyListData *listData = new KPropertyListData({1.1, tr("One")}, {2.5, tr("Two")}, {3., tr("Three")}}); propertySet->addProperty(new KProperty("List", listData, "otheritem", "List")); @endcode @note Sometimes it makes sense to split property captions that have with more words to multiple lines using a newline character, e.g. "Allow Zero Size" to "Allow Zero\nSize". This is suitable especially for the needs of property editor which can offer only limited area. The text of property caption containing newline characters is available in its original form using KProperty::captionForDisplaying(). KProperty::caption() returns modified caption text in which the newline characters are substituted with spaces and any trailing and leading whitespace is removed. */ class KPROPERTYCORE_EXPORT KProperty { public: /*! Defines types of properties. Properties defined by plugins should have a type number >= UserDefined .*/ enum Type { //standard supported QVariant types Auto = 0x00ffffff, Invalid = QVariant::Invalid, BitArray = QVariant::BitArray, Bitmap = QVariant::Bitmap, Bool = QVariant::Bool, Brush = QVariant::Brush, ByteArray = QVariant::ByteArray, Char = QVariant::Char, Color = QVariant::Color, Cursor = QVariant::Cursor, Date = QVariant::Date, DateTime = QVariant::DateTime, Double = QVariant::Double, Font = QVariant::Font, Icon = QVariant::Icon, Image = QVariant::Image, Int = QVariant::Int, KeySequence = QVariant::KeySequence, Line = QVariant::Line, LineF = QVariant::LineF, List = QVariant::List, Locale = QVariant::Locale, LongLong = QVariant::LongLong, Map = QVariant::Map, Matrix = QVariant::Matrix, Transform = QVariant::Transform, Palette = QVariant::Palette, Pen = QVariant::Pen, Pixmap = QVariant::Pixmap, Point = QVariant::Point, PointF = QVariant::PointF, Polygon = QVariant::Polygon, Rect = QVariant::Rect, RectF = QVariant::RectF, RegExp = QVariant::RegExp, Region = QVariant::Region, Size = QVariant::Size, SizeF = QVariant::SizeF, SizePolicy = QVariant::SizePolicy, String = QVariant::String, StringList = QVariant::StringList, TextFormat = QVariant::TextFormat, TextLength = QVariant::TextLength, Time = QVariant::Time, UInt = QVariant::UInt, ULongLong = QVariant::ULongLong, Url = QVariant::Url, //predefined custom types ValueFromList = 1000, /*** children() const; /*! \return a child property for \a name, or NULL if there is no property with that name. */ KProperty* child(const QByteArray &name); /*! \return parent property for this property, or NULL if there is no parent property. */ KProperty* parent() const; /*! \return the composed property for this property, or NULL if there was no composed property defined. */ KComposedPropertyInterface* composedProperty() const; /*! Sets composed property \a prop for this property. */ void setComposedProperty(KComposedPropertyInterface *prop); /*! \return true if this property is null. Property is null if it has empty name. */ bool isNull() const; /** * @brief Return @c true if value of this property or value of any child property is modified. * * @see clearModifiedFlag() */ bool isModified() const; /** * @brief Clears the "modified" flag for this property and all its child properties. * * After calling this method isModified() returs false for the property and all child * properties. * * @see isModified() */ void clearModifiedFlag(); /*! \return true if the property is read-only when used in a property editor. @c false by default. The property can be read-write but still not editable for the user if the parent property set's read-only flag is set. @see KPropertySet::isReadOnly() */ bool isReadOnly() const; /*! Sets this property to be read-only. @see isReadOnly() */ void setReadOnly(bool readOnly); /*! \return true if the property is visible. Only visible properties are displayed by the property editor view. */ bool isVisible() const; /*! Sets the visibility flag.*/ void setVisible(bool visible); /*! \return true if the property can be saved to a stream, xml, etc. There is a possibility to use "GUI" properties that aren't stored but used only in a GUI.*/ bool isStorable() const; /*! Sets "storable" flag for this property. @see isStorable() */ void setStorable(bool storable); //! Synchronization policy for property values //! @since 3.1 enum class ValueSyncPolicy { Editor, //!< Allow to synchronize by the property editor using its valueSync setting (default) FocusOut, //!< Synchronize the value when focus is out of the editor widget for this property //!< or when the user presses the Enter key Auto //!< Synchronize automatically as soon as the editor widget for this property signals //! (using commitData) that the value has been changed, e.g. when the user types //! another letter in a text box }; //! @return synchronization policy for property values of this property //! @since 3.1 ValueSyncPolicy valueSyncPolicy() const; //! Sets synchronization policy for property values of this property //! See ValueSyncPolicy for details. //! @since 3.1 void setValueSyncPolicy(ValueSyncPolicy policy); /*! Sets value \a val for option \a name. Options are used to override default settings of individual properties. They are most visible in property editor widgets. Option is set if it is not null. This means that empty string can be still a valid value. To unset given option, call setOption() with a null QVariant value. Currently supported options are:
  • min: value describing minimum value for properties of integer, double, date, date/time and time types. Default is 0 for double and unsigned integer types, -INT_MAX for signed integer type. Defaults for date, date/time and time types are specified in documentation of QDateEdit::minimumDate, QDateTimeEdit::minimumDateTime and QTime::minimumTime, respectively. The value specified for this option is accepted if: - it is not larger than the value of the "max" option - it is not smaller than KPROPERTY_MIN_PRECISE_DOUBLE (for double type) - it is not smaller than -INT_MAX (for integer type) - it is not smaller than 0 (for unsigned integer type).
  • minValueText: user-visible translated string to be displayed in editor for integer, double, date, date/time and time types when the value is equal to the value of "min" option. The value specified for this option is accepted if min option is supported for given type and is specified. @see QAbstractSpinBox::specialValueText
  • max: value describing minimum value for properties of integer type. Default is KPROPERTY_MAX_PRECISE_DOUBLE for double type (maximum precise value) and INT_MAX for integer type. Defaults for date, date/time and time types are specified in documentation of QDateEdit::maximumDate, QDateTimeEdit::maximumDateTime and QTime::maximumTime, respectively. The value is ignored if it is smaller than the value of "min" option.
  • The value specified for this option is accepted if: - it is not smaller than the value of the "min" option - it is not larger than KPROPERTY_MAX_PRECISE_DOUBLE (for double type) - it is not larger than INT_MAX (for integer and unsigned integer type).
  • precision: integer value >= 0 describing the number of decimals after the decimal point for double type. Default value is 2. @see QDoubleSpinBox::decimals
  • step: double value > 0.0 describing the size of the step that is taken when the user hits the up or down button of editor for double type. Default value is 0.01. @see QDoubleSpinBox::singleStep
  • 3State: boolean value used for boolean type; if @c true, the editor becomes a combobox - (instead of checkable button) and accepts the third "null" state.
  • + (instead of checkable button) and accepts the third "null" state. Otherwise the boolean + type only accepts @c true and @c false values, anything other, including invalid and null + values, is converted to @c false.
  • yesName: user-visible translated string used for boolean type (both 2- and 3-state) to visually represent the "true" value. If not present, tr("Yes") is used.
  • noName: user-visible translated string used for boolean type (both 2- and 3-state) to visually represent the "false" value. If not present, tr("No") is used.
  • 3rdStateName: user-visible translated string used for boolean type (both 2- and 3-state) to visually represent the third "null" value. If not present, tr("None") is used.
  • nullName: user-visible translated string used for boolean type to display the "null" value, if and only if the property accepts two states (i.e. when "3State" option is @c false). If the "nullName" option is not set, null values are displayed as @c false.
  • extraValueAllowed: boolean value, if @c true the user is able to manually add extra values to a combobox.
  • fileMode: string value that describes types of objects that can be selected by the url editor:
    • "dirsOnly": only display and allow to select existing directories; @see QFileDialog::getExistingDirectoryUrl()
    • "existingFile": only allow to select one existing file for opening, i.e. confirmation of overwriting is not performed; @see QFileDialog::getOpenFileUrl()
    • Any other value: any file is supported, whether it exists or not; if the file exists, "confirmOverwrites" option is honored; this mode is the only one supporting non-file protocols such as ftp or http; to use them user has to enter them explicitly, file protocol is still the default @see QFileDialog::getSaveFileUrl()
    @note Empty URLs are always allowed.
  • confirmOverwrites: boolean value supported by the url editor; if @c true and the "fileMode" option is not equal to "existingFile" nor "dirsOnly" user will be asked for confirmation of file overwriting if selected file exists. @c false by default. @note The line edit does not validate the content.
  • multiLine: boolean value used for string type. If @c true, a multi-line QPlainTextEdit-based widget is used for editor; otherwise a single-line QLineEdit widget is used. @c false by default. Added in version 3.1.
  • prefix: string to display before the value, e.g. '$'. Supported for double and integer types and composed types based on double and integer types (Point*, Size*, Rect*). @see QDoubleSpinBox::prefix QSpinBox::prefix
  • suffix: string to display after the value, e.g. unit such as 'mm'. Supported for double and integer types and composed types based on double and integer types (Point*, Size*, Rect*). Note that only display is affected, value is not converted to any unit. @see QDoubleSpinBox::suffix QSpinBox::suffix
*/ void setOption(const char* name, const QVariant& val); /*! @brief Returns value of given option * Option is set if returned value is not null. * If there is no option for @a name in given property and parent property is present (see parent()), * parent property is checked. If the parent property offers the option, the value * is returned. If it is not present there, @a defaultValue value is returned. * Looking at parent property is available since 3.1. * @note The lookup is performed recursively, first in parent, then grand parent, etc. * @see setOption */ QVariant option(const char* name, const QVariant& defaultValue = QVariant()) const; /*! @brief Returns @c true if at least one option is specified for this property * If there are no options defined @c true can be still returned if parent property * is present and it has at least one option specified. * Looking at parent property is available since 3.1. * @note The lookup is performed recursively, first in parent, then grand parent, etc. */ bool hasOptions() const; /*! Equivalent to setValue(const QVariant &) */ KProperty& operator= (const QVariant& val); /*! Assigns a deep copy of all attributes of \a property to this property. */ KProperty& operator= (const KProperty &property); /** * @return @c true if the property is equal to @a prop; otherwise returns @c false. * Two properties are equal if they have the same name and type. * @note All null properties are equal * @todo Compare properties deeper? */ bool operator==(const KProperty &prop) const; /** * @return @c true if the property is different from @a prop; otherwise returns @c false. * Two properties are different if they have different names or types. * @since 3.1 */ bool operator!=(const KProperty &prop) const; #if 0 /*! \return a key used for sorting. Usually its set by KPropertySet::addProperty() and KProperty::addChild() to a unique value, so that this property can be sorted in a property editor in original order. \see EditorItem::compare() */ int sortingKey() const; #endif private: //! Added only to help porting old code. Use public setValue() methods. void setValue(const QVariant &value, bool a1, bool a2 = true); class Private; Private * const d; friend class KPropertySet; friend class KPropertySetPrivate; friend class KPropertySetBuffer; friend KPROPERTYCORE_EXPORT QDebug operator<<(QDebug dbg, const KProperty &p); }; //! qDebug() stream operator. Writes property @a p to the debug output in a nicely formatted way. KPROPERTYCORE_EXPORT QDebug operator<<(QDebug dbg, const KProperty &p); Q_DECLARE_OPERATORS_FOR_FLAGS(KProperty::ValueOptions) #endif diff --git a/src/KPropertyEditorView.cpp b/src/KPropertyEditorView.cpp index 174986a..426cb14 100644 --- a/src/KPropertyEditorView.cpp +++ b/src/KPropertyEditorView.cpp @@ -1,745 +1,750 @@ /* This file is part of the KDE project Copyright (C) 2008-2018 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KPropertyEditorView.h" #include "KPropertyEditorDataModel_p.h" #include "KProperty.h" #include "KPropertySet.h" #include "KPropertyWidgetsFactory.h" #include "KPropertyWidgetsPluginManager.h" #include "kproperty_debug.h" #include "KPropertyUtils.h" #include "KPropertyUtils_p.h" #include #include #include #include #include #include #include #include #include #if 0 // not sure if we should use it, better to fix Oxygen? #include //! Used to alter the widget's style at design time class EditorViewStyle : public KexiUtils::StyleProxy { public: explicit EditorViewStyle(QStyle* parentStyle) : KexiUtils::StyleProxy(parentStyle) { } virtual void drawPrimitive(PrimitiveElement elem, const QStyleOption* option, QPainter* painter, const QWidget* widget) const { /* if (elem == PE_PanelLineEdit) { const QStyleOptionFrame *panel = qstyleoption_cast(option); if (panel) { QStyleOptionFrame alteredOption(*panel); alteredOption.lineWidth = 0; KexiUtils::StyleProxy::drawPrimitive(elem, &alteredOption, painter, widget); return; } }*/ KexiUtils::StyleProxy::drawPrimitive(elem, option, painter, widget); } }; #endif static bool effectiveValueSyncPolicy(const KProperty *property, bool defaultValue) { if (property->valueSyncPolicy() == KProperty::ValueSyncPolicy::Editor) { return defaultValue; } return property->valueSyncPolicy() == KProperty::ValueSyncPolicy::Auto; } //---------- class ItemDelegate : public QItemDelegate { public: explicit ItemDelegate(KPropertyEditorView *parent); ~ItemDelegate() override; void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; mutable QPointer m_currentEditor; }; ItemDelegate::ItemDelegate(KPropertyEditorView *parent) : QItemDelegate(parent) { } ItemDelegate::~ItemDelegate() { } static int getIconSize(int fontPixelSize) { return fontPixelSize * 0.85; } static int typeForProperty(const KProperty* prop) { if (prop->listData()) return KProperty::ValueFromList; else return prop->type(); } void ItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { QStyleOptionViewItem alteredOption(option); const KPropertyUtilsPrivate::PainterSaver saver(painter); const KPropertyEditorDataModel *editorModel = qobject_cast(index.model()); if (!editorModel) { return; } QRect r(option.rect); bool modified = false; const QColor gridLineColor(qobject_cast(parent())->gridLineColor()); if (gridLineColor.isValid()) { alteredOption.rect.setTop(alteredOption.rect.top() + 1); } if (index.column()==0) { r.setWidth(r.width() - 1); r.setLeft(-1); // to avoid displaying double left border QVariant modifiedVariant( editorModel->data(index, KPropertyEditorDataModel::PropertyModifiedRole) ); if (modifiedVariant.isValid() && modifiedVariant.toBool()) { modified = true; QFont font(alteredOption.font); font.setBold(true); alteredOption.font = font; } } else { r.setLeft(r.left()-1); } const int x2 = alteredOption.rect.right(); const int y2 = alteredOption.rect.bottom(); const int iconSize = getIconSize( alteredOption.font.pixelSize() ); if (modified) { alteredOption.rect.setRight( alteredOption.rect.right() - iconSize * 1 ); } const bool isGroupHeader(editorModel->data(index, KPropertyEditorDataModel::PropertyGroupRole).toBool()); if (!isGroupHeader) { KProperty *property = editorModel->propertyForIndex(index); const int t = typeForProperty( property ); bool useQItemDelegatePaint = true; // ValueDisplayInterface is used by default if (index.column() == 1 && KPropertyWidgetsPluginManager::self()->paint(t, painter, alteredOption, index)) { useQItemDelegatePaint = false; } if (useQItemDelegatePaint) { QItemDelegate::paint(painter, alteredOption, index); } if (modified) { alteredOption.rect.setRight( alteredOption.rect.right() - iconSize * 3 / 2 ); int y1 = alteredOption.rect.top(); QLinearGradient grad(x2 - iconSize * 2, y1, x2 - iconSize / 2, y1); QColor color( alteredOption.palette.color( (alteredOption.state & QStyle::State_Selected) ? QPalette::Highlight : QPalette::Base )); color.setAlpha(0); grad.setColorAt(0.0, color); color.setAlpha(255); grad.setColorAt(0.5, color); QBrush gradBrush(grad); painter->fillRect(x2 - iconSize * 2, y1, iconSize * 2, y2 - y1 + 1, gradBrush); //!TODO //QPixmap revertIcon(QIcon::fromTheme(QLatin1String("edit-undo")).pixmap(iconSize, iconSize)); //revertIcon = KIconEffect().apply(revertIcon, KIconEffect::Colorize, 1.0, // alteredOption.palette.color( // (alteredOption.state & QStyle::State_Selected) ? QPalette::HighlightedText : QPalette::Text ), false); //painter->drawPixmap( x2 - iconSize - 2, // y1 + 1 + (alteredOption.rect.height() - revertIcon.height()) / 2, revertIcon); } } if (gridLineColor.isValid()) { QPen pen(gridLineColor); painter->setPen(pen); painter->drawLine(r.topLeft(), r.topRight() + QPoint(1, 0)); painter->drawLine(r.bottomLeft() + QPoint(0, 1), r.bottomRight() + QPoint(1, 1)); if (!isGroupHeader) { painter->drawLine(r.topRight() + QPoint(1, 0), r.bottomRight() + QPoint(1, 1)); painter->drawLine(r.topLeft(), r.bottomLeft() + QPoint(0, 1)); } } else { QPen pen(alteredOption.palette.color(QPalette::AlternateBase)); painter->setPen(pen); painter->drawLine(r.topLeft(), r.topRight()); } //kprDebug()<<"rect:" << r << "viewport:" << painter->viewport() << "window:"<window(); } QSize ItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - return QItemDelegate::sizeHint(option, index) + QSize(0, 2); + QStyleOptionViewItem realOption(option); + if (index.column() == 0) { + // Measure for bold font because it might be used at any time if the property value is modified + realOption.font.setBold(true); + } + return QItemDelegate::sizeHint(realOption, index) + QSize(0, 2); } QWidget * ItemDelegate::createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const { if (!index.isValid()) return nullptr; const KProperty *property = KPropertyUtils::propertyForIndex(index); if (property && property->isReadOnly()) { return nullptr; } const int t = property ? typeForProperty(property) : KProperty::String; QStyleOptionViewItem alteredOption(option); alteredOption.rect.setHeight(alteredOption.rect.height()+3); QWidget *w = KPropertyWidgetsPluginManager::self()->createEditor(t, parent, alteredOption, index); if (!w) { // fall back to String type w = KPropertyWidgetsPluginManager::self()->createEditor(KProperty::String, parent, alteredOption, index); } if (w) { if (-1 != w->metaObject()->indexOfSignal(QMetaObject::normalizedSignature("commitData(QWidget*)").constData()) && property && !property->children()) { } } else { w = QItemDelegate::createEditor(parent, alteredOption, index); } QObject::disconnect(w, SIGNAL(commitData(QWidget*)), this, SIGNAL(commitData(QWidget*))); if (property && effectiveValueSyncPolicy(property, qobject_cast(this->parent())->isValueSyncEnabled())) { QObject::connect(w, SIGNAL(commitData(QWidget*)), this, SIGNAL(commitData(QWidget*)), Qt::UniqueConnection); } m_currentEditor = w; return w; } //---------- class Q_DECL_HIDDEN KPropertyEditorView::Private { public: explicit Private(KPropertyEditorView *view) : model(nullptr) , gridLineColor( KPropertyEditorView::defaultGridLineColor() ) , valueSync(true) , slotPropertyChangedEnabled(true) , q(view) { } //! Expands group and parent property items if needed (based on settings) void expandIfNeeded() { if (!model) { return; } const int rowCount = model->rowCount(); for (int row = 0; row < rowCount; row++) { expandChildItemsIfNeeded(model->index(row, 0)); } } //! Expands property child items in a subtree recursively if needed (based on settings) void expandChildItemsIfNeeded(const QModelIndex &parent) { if (!model) { return; } const bool isGroupHeader(model->data(parent, KPropertyEditorDataModel::PropertyGroupRole).toBool()); if (isGroupHeader) { if (groupItemsExpanded) { q->expand(parent); } } else { if (childPropertyItemsExpanded) { q->expand(parent); } } const int rowCount = model->rowCount(parent); for (int row = 0; row < rowCount; row++) { const QModelIndex child(model->index(row, 0, parent)); expandChildItemsIfNeeded(child); } } QPointer set; KPropertyEditorDataModel *model; ItemDelegate *itemDelegate; QColor gridLineColor; bool valueSync; bool slotPropertyChangedEnabled; bool childPropertyItemsExpanded = true; bool groupItemsExpanded = true; bool groupsVisible = true; bool toolTipsVisible = false; private: KPropertyEditorView * const q; }; KPropertyEditorView::KPropertyEditorView(QWidget* parent) : QTreeView(parent) , d(new Private(this)) { setObjectName(QLatin1String("KPropertyEditorView")); setAlternatingRowColors(true); setSelectionBehavior(QAbstractItemView::SelectRows); setSelectionMode(QAbstractItemView::SingleSelection); setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel); setAnimated(false); setAllColumnsShowFocus(true); header()->setSectionsMovable(false); setEditTriggers( QAbstractItemView::CurrentChanged | QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed | QAbstractItemView::AnyKeyPressed | QAbstractItemView::AllEditTriggers); setItemDelegate(d->itemDelegate = new ItemDelegate(this)); } KPropertyEditorView::~KPropertyEditorView() { delete d; } void KPropertyEditorView::changeSet(KPropertySet *set, SetOptions options) { changeSetInternal(set, options, QByteArray()); } void KPropertyEditorView::changeSet(KPropertySet *set, const QByteArray& propertyToSelect, SetOptions options) { changeSetInternal(set, options, propertyToSelect); } void KPropertyEditorView::changeSetInternal(KPropertySet *set, SetOptions options, const QByteArray& propertyToSelect) { //! @todo port?? #if 0 if (d->insideSlotValueChanged) { //changeSet() called from inside of slotValueChanged() //this is dangerous, because there can be pending events, //especially for the GUI stuff, so let's do delayed work d->setListLater_list = set; d->preservePrevSelection_preservePrevSelection = preservePrevSelection; d->preservePrevSelection_propertyToSelect = propertyToSelect; qApp->processEvents(QEventLoop::AllEvents); if (d->set) { //store prev. selection for this prop set if (d->currentItem) d->set->setPrevSelection(d->currentItem->property()->name()); kprDebug() << d->set->prevSelection(); } if (!d->setListLater_set) { d->setListLater_set = true; d->changeSetLaterTimer.setSingleShot(true); d->changeSetLaterTimer.start(10); } return; } #endif const bool setChanged = d->set != set; if (d->set) { acceptInput(); //store prev. selection for this prop set QModelIndex index = currentIndex(); if (index.isValid()) { //! @todo This crashes when changing the interpreter type in the script plugin #if 0 KProperty *property = d->model->propertyForIndex(index); //if (property->isNull()) // kprDebug() << "WTF? a NULL property?"; //else //d->set->setPreviousSelection(property->name()); #endif } else { d->set->setPreviousSelection(QByteArray()); } if (setChanged) { d->set->disconnect(this); } } QByteArray selectedPropertyName1 = propertyToSelect; QByteArray selectedPropertyName2 = propertyToSelect; if (options & SetOption::PreservePreviousSelection) { //try to find prev. selection: //1. in new list's prev. selection if (set) selectedPropertyName1 = set->previousSelection(); //2. in prev. list's current selection if (d->set) selectedPropertyName2 = d->set->previousSelection(); } if (setChanged) { d->set = set; } if (d->set && setChanged) { //receive property changes connect(d->set, SIGNAL(propertyChangedInternal(KPropertySet&,KProperty&)), this, SLOT(slotPropertyChanged(KPropertySet&,KProperty&))); connect(d->set, SIGNAL(propertyReset(KPropertySet&,KProperty&)), this, SLOT(slotPropertyReset(KPropertySet&,KProperty&))); connect(d->set, SIGNAL(aboutToBeCleared()), this, SLOT(slotSetWillBeCleared())); connect(d->set, SIGNAL(aboutToBeDeleted()), this, SLOT(slotSetWillBeDeleted())); connect(d->set, &KPropertySet::readOnlyFlagChanged, this, &KPropertyEditorView::slotReadOnlyFlagChanged); } KPropertyEditorDataModel *oldModel = d->model; const KPropertySetIterator::Order setOrder = (options & SetOption::AlphabeticalOrder) ? KPropertySetIterator::Order::Alphabetical : KPropertySetIterator::Order::Insertion; d->model = d->set ? new KPropertyEditorDataModel(this, setOrder) : nullptr; setModel( d->model ); delete oldModel; if (d->model && d->set && !d->set->isEmpty()) { d->expandIfNeeded(); } emit propertySetChanged(d->set); if (d->set) { //select prev. selected item QModelIndex index; if (!selectedPropertyName2.isEmpty()) //try other one for old prop set index = d->model->indexForPropertyName( selectedPropertyName2 ); if (!index.isValid() && !selectedPropertyName1.isEmpty()) //try old one for current prop set index = d->model->indexForPropertyName( selectedPropertyName1 ); if (index.isValid()) { setCurrentIndex(index); scrollTo(index); } } } void KPropertyEditorView::slotSetWillBeCleared() { changeSet(nullptr, QByteArray()); } void KPropertyEditorView::slotSetWillBeDeleted() { changeSet(nullptr, QByteArray()); } void KPropertyEditorView::slotReadOnlyFlagChanged() { const QModelIndex index = currentIndex(); setCurrentIndex(QModelIndex()); if (index.isValid()) { selectionModel()->select(index, QItemSelectionModel::Select); setCurrentIndex(index); } } void KPropertyEditorView::setValueSyncEnabled(bool set) { d->valueSync = set; } bool KPropertyEditorView::isValueSyncEnabled() const { return d->valueSync; } void KPropertyEditorView::setChildPropertyItemsExpanded(bool set) { d->childPropertyItemsExpanded = set; } bool KPropertyEditorView::childPropertyItemsExpanded() const { return d->childPropertyItemsExpanded; } void KPropertyEditorView::setGroupItemsExpanded(bool set) { d->groupItemsExpanded = set; } bool KPropertyEditorView::groupItemsExpanded() const { return d->groupItemsExpanded; } bool KPropertyEditorView::groupsVisible() const { return d->groupsVisible; } void KPropertyEditorView::setGroupsVisible(bool set) { if (d->groupsVisible == set) { return; } if (d->model) { d->model->updateGroupsVisibility(); d->expandIfNeeded(); } viewport()->update(); } void KPropertyEditorView::currentChanged( const QModelIndex & current, const QModelIndex & previous ) { QTreeView::currentChanged( current, previous ); } bool KPropertyEditorView::edit( const QModelIndex & index, EditTrigger trigger, QEvent * event ) { bool result; if (!d->set || d->set->isReadOnly()) { result = false; } else { result = QTreeView::edit(index, trigger, event); } if (result) { QLineEdit *lineEditEditor = qobject_cast(d->itemDelegate->m_currentEditor.data()); if (lineEditEditor) { lineEditEditor->deselect(); lineEditEditor->end(false); } } return result; } void KPropertyEditorView::drawBranches( QPainter * painter, const QRect & rect, const QModelIndex & index ) const { QTreeView::drawBranches( painter, rect, index ); } void KPropertyEditorView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { if (!d->model) { return; } const KPropertyUtilsPrivate::PainterSaver saver(painter); const bool isGroupHeader(d->model->data(index, KPropertyEditorDataModel::PropertyGroupRole).toBool()); QStyleOptionViewItem alteredOption(option); QTreeView::drawRow(painter, alteredOption, index); if (isGroupHeader) { // Special case: group header should be displayed over both columns. There's an issue with // alternate background which is painted over text in the 2nd column, so draw the text here // by hand. QFont font(alteredOption.font); font.setBold(true); alteredOption.font = font; painter->setFont(font); painter->drawText( alteredOption.rect.adjusted(style()->pixelMetric(QStyle::PM_TreeViewIndentation), 0, 0, 0), index.data(Qt::DisplayRole).toString(), Qt::AlignLeft | Qt::AlignVCenter); } } QRect KPropertyEditorView::revertButtonArea( const QModelIndex& index ) const { if (index.column() != 0 || !d->model) return QRect(); QVariant modifiedVariant( d->model->data(index, KPropertyEditorDataModel::PropertyModifiedRole) ); if (!modifiedVariant.isValid() || !modifiedVariant.toBool()) return QRect(); const int iconSize = getIconSize( fontInfo().pixelSize() ); int x2 = columnWidth(0); int x1 = x2 - iconSize - 2; QRect r(visualRect(index)); r.setLeft(x1); r.setRight(x2); return r; } bool KPropertyEditorView::withinRevertButtonArea( int x, const QModelIndex& index ) const { QRect r(revertButtonArea( index )); if (!r.isValid()) return false; return r.left() < x && x < r.right(); } void KPropertyEditorView::mousePressEvent ( QMouseEvent * event ) { QTreeView::mousePressEvent( event ); QModelIndex index = indexAt( event->pos() ); setCurrentIndex(index); if (withinRevertButtonArea( event->x(), index )) { undo(); } } void KPropertyEditorView::undo() { if (!d->set || d->set->isReadOnly() || !d->model) return; KProperty *property = d->model->propertyForIndex(currentIndex()); if (effectiveValueSyncPolicy(property, d->valueSync)) { property->resetValue(); } } void KPropertyEditorView::acceptInput() { //! @todo } void KPropertyEditorView::commitData( QWidget * editor ) { QAbstractItemView::commitData( editor ); } bool KPropertyEditorView::viewportEvent( QEvent * event ) { if (event->type() == QEvent::ToolTip) { QHelpEvent *hevent = static_cast(event); const QModelIndex index = indexAt(hevent->pos()); if (index.column() == 0 && withinRevertButtonArea( hevent->x(), index )) { QRect r(revertButtonArea( index )); QToolTip::showText(hevent->globalPos(), tr("Undo changes"), this, r); } else { QToolTip::hideText(); } } return QTreeView::viewportEvent(event); } QSize KPropertyEditorView::sizeHint() const { return viewportSizeHint(); } KPropertySet* KPropertyEditorView::propertySet() const { return d->set; } QColor KPropertyEditorView::gridLineColor() const { return d->gridLineColor; } void KPropertyEditorView::setGridLineColor(const QColor& color) { d->gridLineColor = color; viewport()->update(); } static QModelIndex findChildItem(const KProperty& property, const QModelIndex &parent) { if (parent.model() && KPropertyUtils::propertyForIndex(parent) == &property) { return parent; } int row = 0; while (true) { QModelIndex childItem = parent.child(row, 0); if (childItem.isValid()) { QModelIndex subchild = findChildItem(property, childItem); if (subchild.isValid()) { return subchild; } } else { return QModelIndex(); } row++; } } void KPropertyEditorView::slotPropertyChanged(KPropertySet& set, KProperty& property) { Q_UNUSED(set); if (!d->slotPropertyChangedEnabled || !d->model) return; d->slotPropertyChangedEnabled = false; KProperty *realProperty = &property; while (realProperty->parent()) { // find top-level property realProperty = realProperty->parent(); } const QModelIndex parentIndex( d->model->indexForPropertyName(realProperty->name()) ); if (parentIndex.isValid()) { QModelIndex index = findChildItem(property, parentIndex); updateSubtree(index); } d->slotPropertyChangedEnabled = true; } void KPropertyEditorView::updateSubtree(const QModelIndex &index) { if (!index.isValid() || !d->model) { return; } update(index); update(index.parent()); update(d->model->indexForColumn(index, 1)); update(d->model->indexForColumn(index.parent(), 1)); KProperty *property = static_cast(index.internalPointer()); if (property->children()) { int row = 0; foreach (KProperty* p, *property->children()) { updateSubtree(d->model->createIndex(row, 0, p)); ++row; } } } void KPropertyEditorView::slotPropertyReset(KPropertySet& set, KProperty& property) { //! @todo OK? slotPropertyChanged(set, property); } bool KPropertyEditorView::toolTipsVisible() const { return d->toolTipsVisible; } void KPropertyEditorView::setToolTipsVisible(bool set) { d->toolTipsVisible = set; } diff --git a/src/editors/booledit.cpp b/src/editors/booledit.cpp index 8f87a43..bae3624 100644 --- a/src/editors/booledit.cpp +++ b/src/editors/booledit.cpp @@ -1,326 +1,352 @@ /* This file is part of the KDE project Copyright (C) 2004 Alexander Dymo - Copyright (C) 2006-2016 Jarosław Staniek + Copyright (C) 2006-2018 Jarosław Staniek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "booledit.h" #include "KPropertyListData.h" #include "KPropertyUtils.h" #include "KPropertyUtils_p.h" #include "kproperty_debug.h" #include #include -/*! @return name for state with index @a index, - where 0 means true, 1 means false and 2 means none */ -static QString stateName(int index, const QLocale &locale, const KProperty* prop = nullptr) +namespace { + + +/** + * 3-state Index + */ +enum ThreeStateIndex { + TrueIndex, + FalseIndex, + NoneIndex +}; + +//! @return name for state with index @a index +QString stateName(ThreeStateIndex index, const QLocale &locale, const KProperty* prop = nullptr) { QString stateNameString; - if (index == 0) { + switch (index) { + case TrueIndex: stateNameString = prop ? prop->option("yesName", QString()).toString() : QString(); if (stateNameString.isEmpty()) { - return locale.language() == QLocale::C ? QString::fromLatin1("true") - : QObject::tr("Yes", "Property value: Boolean state Yes"); + stateNameString = locale.language() == QLocale::C + ? QString::fromLatin1("true") + : QObject::tr("Yes", "Property value: Boolean state Yes"); } - } - else if (index == 1) { + break; + case FalseIndex: stateNameString = prop ? prop->option("noName", QString()).toString() : QString(); if (stateNameString.isEmpty()) { - return locale.language() == QLocale::C ? QString::fromLatin1("false") - : QObject::tr("No", "Property value: Boolean state No"); + stateNameString = locale.language() == QLocale::C + ? QString::fromLatin1("false") + : QObject::tr("No", "Property value: Boolean state No"); } - } - else { + break; + case NoneIndex: stateNameString = prop ? prop->option("3rdStateName", QString()).toString() : QString(); if (stateNameString.isEmpty()) { - return locale.language() == QLocale::C ? QString::fromLatin1("null") - : QObject::tr("None", "Property value: Boolean (3rd) undefined state None"); + stateNameString = locale.language() == QLocale::C + ? QString::fromLatin1("null") + : QObject::tr("None", "Property value: Boolean (3rd) undefined state None"); } + break; } return stateNameString; } //! Sets up @a data list data with keys and names for true, false, none values, respectively -static void setupThreeStateListData(KPropertyListData *data, const KProperty* prop) +void setupThreeStateListData(KPropertyListData *data, const KProperty* prop) { data->setKeys({ true, false, QVariant() }); - data->setNamesAsStringList({ stateName(0, QLocale(), prop), stateName(1, QLocale(), prop), - stateName(2, QLocale(), prop) }); + data->setNamesAsStringList({ stateName(TrueIndex, QLocale(), prop), stateName(FalseIndex, QLocale(), prop), + stateName(NoneIndex, QLocale(), prop) }); } -static int valueToIndex(const QVariant& value) +/** + * Returns index for given value + * + * Assumes that support for 3-state is enabled. + */ +ThreeStateIndex valueToIndex(const QVariant& value) { if (value.isNull() || !value.isValid()) - return 2; + return NoneIndex; else - return value.toBool() ? 0 : 1; + return value.toBool() ? TrueIndex : FalseIndex; } +} // namespace //------------------------- class BoolEditGlobal { public: BoolEditGlobal() : yesIcon(QIcon::fromTheme(QLatin1String("dialog-ok"))) , noIcon(QIcon::fromTheme(QLatin1String("kproperty-value-false"))) { QPixmap none(16, 16); none.fill(Qt::transparent); noneIcon.addPixmap(none); none = QPixmap(22, 22); none.fill(Qt::transparent); noneIcon.addPixmap(none); } QIcon yesIcon; QIcon noIcon; QIcon noneIcon; }; Q_GLOBAL_STATIC(BoolEditGlobal, g_boolEdit) class Q_DECL_HIDDEN KPropertyBoolEditor::Private { public: Private(const KProperty *prop) - : yesText( stateName(0, QLocale(), prop) ) - , noText( stateName(1, QLocale(), prop) ) + : yesText( stateName(TrueIndex, QLocale(), prop) ) + , noText( stateName(FalseIndex, QLocale(), prop) ) { } QVariant value; QString yesText; QString noText; }; KPropertyBoolEditor::KPropertyBoolEditor(const KProperty *prop, QWidget *parent) : QToolButton(parent), d(new Private(prop)) { setFocusPolicy(Qt::WheelFocus); setCheckable(true); setAutoFillBackground(true); connect(this, SIGNAL(toggled(bool)), this, SLOT(slotValueChanged(bool))); } KPropertyBoolEditor::~KPropertyBoolEditor() { delete d; } QVariant KPropertyBoolEditor::value() const { return d->value; } void KPropertyBoolEditor::setValue(const QVariant &value) { d->value = value; if (value.type() == QVariant::Bool) { setChecked(value.toBool()); } } void KPropertyBoolEditor::slotValueChanged(bool state) { d->value = state; emit commitData(this); } void KPropertyBoolEditor::draw(QPainter *p, const QRect &r, const QVariant &value, const QString& text, bool threeState) { QIcon icon; QSize actualIconSize; QPoint textOffset; - if (valueToIndex(value) == 2) { + if (threeState && valueToIndex(value) == NoneIndex) { // draw icon for the 3rd state for Three-State editor icon = g_boolEdit->noneIcon; actualIconSize = g_boolEdit->yesIcon.actualSize(r.size()); textOffset = QPoint(actualIconSize.width() + 6, 0); } else { // draw true or false icon regardless of the 2 or 3 state version icon = value.toBool() ? g_boolEdit->yesIcon : g_boolEdit->noIcon; actualIconSize = icon.actualSize(r.size()); textOffset = QPoint(actualIconSize.width() + 6, 0); } QRect r2(r); r2.moveTop(r2.top() + 2); r2.setLeft(r2.left() + 3); //r2.setTop(r2.top() + (r.height() - actualIconSize.height()) / 2); - if (!threeState && value.isNull()) { - // 2 states but null value - p->drawText(r2.translated(textOffset), Qt::AlignVCenter | Qt::AlignLeft, text); - } else { - icon.paint(p, r2, Qt::AlignVCenter | Qt::AlignLeft); - p->drawText(r2.translated(textOffset), Qt::AlignVCenter | Qt::AlignLeft, text); - } + icon.paint(p, r2, Qt::AlignVCenter | Qt::AlignLeft); + p->drawText(r2.translated(textOffset), Qt::AlignVCenter | Qt::AlignLeft, text); } void KPropertyBoolEditor::paintEvent( QPaintEvent * event ) { QToolButton::paintEvent(event); QPainter p(this); const QVariant v( value() ); KPropertyBoolEditor::draw(&p, rect(), v, v.toBool() ? d->yesText : d->noText, false /*2state*/); } bool KPropertyBoolEditor::eventFilter(QObject* watched, QEvent* e) { if (e->type() == QEvent::KeyPress) { QKeyEvent* ev = static_cast(e); const int k = ev->key(); if (k == Qt::Key_Space || k == Qt::Key_Enter || k == Qt::Key_Return) { toggle(); return true; } } return QToolButton::eventFilter(watched, e); } //-------------------------------------------------- class ThreeStateBoolIconProvider : public KPropertyComboBoxEditorIconProviderInterface { public: ThreeStateBoolIconProvider() {} QIcon icon(int index) const override { - if (index == 0) - return g_boolEdit->yesIcon; - else if (index == 1) - return g_boolEdit->noIcon; - return g_boolEdit->noneIcon; + switch (index) { + case TrueIndex: + return g_boolEdit->yesIcon; + case FalseIndex: + return g_boolEdit->noIcon; + default: + return g_boolEdit->noneIcon; + } } KPropertyComboBoxEditorIconProviderInterface* clone() const override { return new ThreeStateBoolIconProvider(); } }; static KPropertyComboBoxEditorOptions initThreeStateBoolOptions() { KPropertyComboBoxEditorOptions options; options.iconProvider = new ThreeStateBoolIconProvider(); return options; } class Q_DECL_HIDDEN KPropertyThreeStateBoolEditor::Private { public: Private() { } }; KPropertyThreeStateBoolEditor::KPropertyThreeStateBoolEditor(const KPropertyListData &listData, QWidget *parent) : KPropertyComboBoxEditor(listData, initThreeStateBoolOptions(), parent), d(new Private) { // QPixmap nullIcon(m_yesIcon.size()); //transparent pixmap of appropriate size // nullIcon.fill(Qt::transparent); // m_edit->addItem(nullIcon, thirdState.toString().isEmpty() ? tr("None") : thirdState.toString()); - setCurrentIndex(2); + setCurrentIndex(NoneIndex); } KPropertyThreeStateBoolEditor::~KPropertyThreeStateBoolEditor() { delete d; } QVariant KPropertyThreeStateBoolEditor::value() const { // list items: true, false, NULL - const int idx = currentIndex(); - if (idx == 0) + const int i = currentIndex(); + const ThreeStateIndex index + = (i >= TrueIndex && i <= NoneIndex) ? static_cast(i) : NoneIndex; + switch (index) { + case TrueIndex: return true; - else - return idx == 1 ? false : QVariant(); + case FalseIndex: + return false; + default: + return QVariant(); + } } /*void ThreeStateBoolEdit::setProperty(Property *prop) { m_setValueEnabled = false; //setValue() couldn't be called before fillBox() Widget::setProperty(prop); m_setValueEnabled = true; if (prop) setValue(prop->value(), KProperty::ValueOption::IgnoreOld); //now the value can be set }*/ void KPropertyThreeStateBoolEditor::setValue(const QVariant &value) { setCurrentIndex( valueToIndex(value) ); } //--------------- KPropertyBoolDelegate::KPropertyBoolDelegate() { options()->setBordersVisible(true); } QWidget * KPropertyBoolDelegate::createEditor( int type, QWidget *parent, const QStyleOptionViewItem & option, const QModelIndex & index ) const { Q_UNUSED(type); Q_UNUSED(option); KProperty *prop = KPropertyUtils::propertyForIndex(index); // boolean editors can optionally accept 3rd state: if (prop && prop->option("3State", false).toBool()) { KPropertyListData threeStateListData; setupThreeStateListData(&threeStateListData, prop); return new KPropertyThreeStateBoolEditor(threeStateListData, parent); } else { return new KPropertyBoolEditor(prop, parent); } } void KPropertyBoolDelegate::paint( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const { const KPropertyUtilsPrivate::PainterSaver saver(painter); KProperty *prop = KPropertyUtils::propertyForIndex(index); if (!prop) { return; } const QVariant value( index.data(Qt::EditRole) ); QRect rect(option.rect); const bool threeState = prop->option("3State", false).toBool(); KPropertyBoolEditor::draw(painter, rect.translated(0, -2), value, propertyValueToString(prop, QLocale()), threeState); } QString KPropertyBoolDelegate::propertyValueToString(const KProperty* prop, const QLocale &locale) const { if (prop->option("3State", false).toBool()) { - int listIndex = valueToIndex(prop->value()); + const ThreeStateIndex listIndex = valueToIndex(prop->value()); return stateName(listIndex, locale, prop); } if (prop->value().isNull() && !prop->option("nullName", QString()).toString().isEmpty()) { return prop->option("nullName", QString()).toString(); } return valueToString(prop->value(), locale); } QString KPropertyBoolDelegate::valueToString(const QVariant& value, const QLocale &locale) const { // assume 2-state - return stateName(value.toBool() ? 0 : 1, locale); + return stateName(value.toBool() ? TrueIndex : FalseIndex, locale); }