diff --git a/examples/window.cpp b/examples/window.cpp index 02fb408..d1dea9f 100644 --- a/examples/window.cpp +++ b/examples/window.cpp @@ -1,414 +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.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/CMakeLists.txt b/src/CMakeLists.txt index d86560f..62beb03 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,330 +1,334 @@ # Options # ... set(kpropertycore_LIB_SRCS KProperty.cpp + KPropertyComposedUrl.cpp KPropertyListData.cpp KPropertySet.cpp KPropertySetBuffer.cpp KPropertyFactory.cpp KPropertyCoreUtils.cpp kproperty_debug.cpp ) set(kpropertycore_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ) ecm_create_qm_loader(kpropertycore_LIB_SRCS kpropertycore_qt) add_library(KPropertyCore SHARED ${kpropertycore_LIB_SRCS}) set_coinstallable_lib_version(KPropertyCore) target_link_libraries(KPropertyCore PUBLIC Qt5::Core ) generate_export_header(KPropertyCore) target_include_directories(KPropertyCore PUBLIC "$" INTERFACE "$" ) if(KPROPERTY_WIDGETS) add_library(KPropertyUtilsPrivate STATIC KPropertyUtils_p.cpp ) target_link_libraries(KPropertyUtilsPrivate PUBLIC Qt5::Widgets ) add_subdirectory(editors) set(kpropertywidgets_LIB_SRCS editors/utils.cpp editors/booledit.cpp editors/coloredit.cpp editors/combobox.cpp editors/cursoredit.cpp editors/dateedit.cpp editors/datetimeedit.cpp # editors/dummywidget.cpp editors/fontedit.cpp editors/pixmapedit.cpp editors/pointedit.cpp editors/pointfedit.cpp editors/rectedit.cpp editors/KPropertyRectFEditor.cpp editors/sizeedit.cpp editors/sizefedit.cpp editors/sizepolicyedit.cpp editors/spinbox.cpp + editors/KPropertyComposedUrlEditor.cpp editors/KPropertyGenericSelectionEditor.cpp editors/KPropertyMultiLineStringEditor.cpp editors/KPropertyStringEditor.cpp editors/KPropertyUrlEditor.cpp + editors/KPropertyUrlEditor_p.cpp editors/linestyleedit.cpp # editors/stringlistedit.cpp # editors/symbolcombo.cpp editors/timeedit.cpp kproperty_debug.cpp # needed here too KPropertyWidgetsFactory.cpp KPropertyWidgetsPluginManager.cpp KDefaultPropertyFactory.cpp KPropertyEditorItemEvent.cpp KPropertyEditorView.cpp KPropertyEditorDataModel_p.cpp KPropertyUtils.cpp KPropertyLineStyleSelector_p.cpp KPropertyLineStyleModel_p.cpp KPropertyLineStyleItemDelegate_p.cpp # non-source: Mainpage.dox Messages.sh ) ecm_create_qm_loader(kpropertywidgets_LIB_SRCS kpropertywidgets_qt) set(kpropertywidgets_INCLUDE_DIRS ${CMAKE_CURRENT_BINARY_DIR}/editors ) if(NOT KPROPERTY_KF) list(APPEND kpropertywidgets_LIB_SRCS editors/3rdparty/KColorCombo.cpp editors/3rdparty/KColorCollection.cpp ) list(APPEND kpropertywidgets_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/editors/3rdparty ) endif() add_library(KPropertyWidgets SHARED ${kpropertywidgets_LIB_SRCS}) set_coinstallable_lib_version(KPropertyWidgets) target_link_libraries(KPropertyWidgets PUBLIC KPropertyCore Qt5::Widgets PRIVATE KPropertyUtilsPrivate ) if(KPROPERTY_KF) target_link_libraries(KPropertyUtilsPrivate PUBLIC KF5::ConfigGui #KConfigGroup KF5::GuiAddons #KColorCollection KF5::WidgetsAddons #KMessageBox ) endif() generate_export_header(KPropertyWidgets) target_include_directories(KPropertyWidgets PUBLIC "$" INTERFACE "$" PRIVATE editors ) endif() # Create a Config.cmake and a ConfigVersion.cmake file and install them set(CMAKECONFIG_CORE_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/${KPROPERTYCORE_BASE_NAME}") # A place for KProperty plugins set(KPROPERTY_PLUGIN_INSTALL_DIR ${PLUGIN_INSTALL_DIR}/${PROJECT_NAME_LOWER}${PROJECT_STABLE_VERSION_MAJOR}) ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX KPROPERTYCORE VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kproperty_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPropertyCoreConfigVersion.cmake" ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KPropertyCoreConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KPropertyCoreConfig.cmake" INSTALL_DESTINATION "${CMAKECONFIG_CORE_INSTALL_DIR}" ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KPropertyCoreConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KPropertyCoreConfigVersion.cmake" DESTINATION "${CMAKECONFIG_CORE_INSTALL_DIR}" COMPONENT Devel) install(TARGETS KPropertyCore EXPORT KPropertyCoreTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(EXPORT KPropertyCoreTargets DESTINATION "${CMAKECONFIG_CORE_INSTALL_DIR}" FILE KPropertyCoreTargets.cmake) if(KPROPERTY_WIDGETS) set(CMAKECONFIG_WIDGETS_INSTALL_DIR "${CMAKECONFIG_INSTALL_PREFIX}/${KPROPERTYWIDGETS_BASE_NAME}") ecm_setup_version(${PROJECT_VERSION} VARIABLE_PREFIX KPROPERTYWIDGETS VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kproperty_version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KPropertyWidgetsConfigVersion.cmake" ) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/KPropertyWidgetsConfig.cmake.in" "${CMAKE_CURRENT_BINARY_DIR}/KPropertyWidgetsConfig.cmake" INSTALL_DESTINATION "${CMAKECONFIG_WIDGETS_INSTALL_DIR}" ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/KPropertyWidgetsConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KPropertyWidgetsConfigVersion.cmake" DESTINATION "${CMAKECONFIG_WIDGETS_INSTALL_DIR}" COMPONENT Devel) install(TARGETS KPropertyWidgets EXPORT KPropertyWidgetsTargets ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install(EXPORT KPropertyWidgetsTargets DESTINATION "${CMAKECONFIG_WIDGETS_INSTALL_DIR}" FILE KPropertyWidgetsTargets.cmake) endif() install(FILES ${PRI_FILENAME} DESTINATION ${ECM_MKSPECS_INSTALL_DIR}) if(KPROPERTY_GENERATE_PRI) ecm_generate_pri_file( BASE_NAME ${KPROPERTYCORE_BASE_NAME} LIB_NAME ${KPROPERTYCORE_BASE_NAME} DEPS "core" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KPROPERTYCORE_INCLUDE_INSTALL_DIR} ) if(KPROPERTY_WIDGETS) ecm_generate_pri_file( BASE_NAME ${KPROPERTYWIDGETS_BASE_NAME} LIB_NAME ${KPROPERTYWIDGETS_BASE_NAME} DEPS "widgets KPropertyCore" FILENAME_VAR PRI_FILENAME INCLUDE_INSTALL_DIR ${KPROPERTYWIDGETS_INCLUDE_INSTALL_DIR} ) endif() endif() #ecm_install_icons(${DATA_INSTALL_DIR}/kproperty/icons) ecm_generate_headers(kpropertycore_FORWARDING_HEADERS REQUIRED_HEADERS kpropertycore_HEADERS ORIGINAL CAMELCASE HEADER_NAMES KProperty + KPropertyComposedUrl KPropertyListData KPropertySet KPropertySetBuffer KPropertyFactory ) list(APPEND kpropertycore_HEADERS ${CMAKE_CURRENT_BINARY_DIR}/kproperty_version.h) install( FILES ${kpropertycore_HEADERS} ${kpropertycore_FORWARDING_HEADERS} ${PROJECT_BINARY_DIR}/src/kpropertycore_export.h ${PROJECT_BINARY_DIR}/src/config-kproperty.h DESTINATION ${KPROPERTYCORE_INCLUDE_INSTALL_DIR} COMPONENT Devel ) if(KPROPERTY_WIDGETS) add_subdirectory(pics) ecm_generate_headers(kpropertywidgets_FORWARDING_HEADERS REQUIRED_HEADERS kpropertywidgets_HEADERS ORIGINAL CAMELCASE HEADER_NAMES KPropertyWidgetsFactory KPropertyWidgetsPluginManager KPropertyUtils KPropertyEditorItemEvent KPropertyEditorView ) install( FILES ${kpropertywidgets_HEADERS} ${kpropertywidgets_FORWARDING_HEADERS} ${kproperty_editors_HEADERS} ${kproperty_editors_FORWARDING_HEADERS} ${PROJECT_BINARY_DIR}/src/kpropertywidgets_export.h DESTINATION ${KPROPERTYWIDGETS_INCLUDE_INSTALL_DIR} COMPONENT Devel ) endif() if(BUILD_QCH) kproperty_add_qch( KPropertyCore_QCH NAME KPropertyCore BASE_NAME ${KPROPERTYCORE_BASE_NAME} VERSION ${PROJECT_VERSION} NAMESPACE org.kde.${KPROPERTYCORE_BASE_NAME} SOURCES Mainpage.dox ${kpropertycore_HEADERS} LINK_QCHS Qt5Core_QCH BLANK_MACROS KPROPERTYCORE_EXPORT KPROPERTYCORE_DEPRECATED TAGFILE_INSTALL_DESTINATION ${KPROPERTY_QTQCH_FULL_INSTALL_DIR} QCH_INSTALL_DESTINATION ${KPROPERTY_QTQCH_FULL_INSTALL_DIR} ) set(kpropertycore_qch_targets KPropertyCore_QCH) endif() kproperty_install_qch_export( TARGETS ${kpropertycore_qch_targets} FILE KPropertyCoreQCHTargets.cmake DESTINATION "${CMAKECONFIG_CORE_INSTALL_DIR}" COMPONENT Devel ) if(KPROPERTY_WIDGETS) if(BUILD_QCH) if(KPROPERTY_KF) set(_KF5WidgetsAddons_QCH KF5WidgetsAddons_QCH) endif() kproperty_add_qch( KPropertyWidgets_QCH NAME KPropertyWidgets BASE_NAME ${KPROPERTYWIDGETS_BASE_NAME} VERSION ${PROJECT_VERSION} NAMESPACE org.kde.${KPROPERTYWIDGETS_BASE_NAME} SOURCES ${kpropertywidgets_HEADERS} ${kproperty_editors_HEADERS} LINK_QCHS Qt5Core_QCH Qt5Gui_QCH Qt5Widgets_QCH KPropertyCore_QCH ${_KF5WidgetsAddons_QCH} BLANK_MACROS KPROPERTYWIDGETS_EXPORT KPROPERTYWIDGETS_DEPRECATED TAGFILE_INSTALL_DESTINATION ${KPROPERTY_QTQCH_FULL_INSTALL_DIR} QCH_INSTALL_DESTINATION ${KPROPERTY_QTQCH_FULL_INSTALL_DIR} ) set(kpropertywidgets_qch_targets KPropertyWidgets_QCH) endif() kproperty_install_qch_export( TARGETS ${kpropertywidgets_qch_targets} FILE KPropertyWidgetsQCHTargets.cmake DESTINATION "${CMAKECONFIG_WIDGETS_INSTALL_DIR}" COMPONENT Devel ) endif() enable_testing() configure_file(config-kproperty.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kproperty.h) diff --git a/src/KDefaultPropertyFactory.cpp b/src/KDefaultPropertyFactory.cpp index 8a02ce5..2a22ed7 100644 --- a/src/KDefaultPropertyFactory.cpp +++ b/src/KDefaultPropertyFactory.cpp @@ -1,83 +1,84 @@ /* This file is part of the KDE project Copyright (C) 2008-2015 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 "KDefaultPropertyFactory.h" #include "KPropertyUtils_p.h" #include "config-kproperty.h" #include "editors/booledit.h" #include "editors/coloredit.h" #include "editors/combobox.h" #include "editors/cursoredit.h" #include "editors/dateedit.h" #include "editors/datetimeedit.h" // #include "editors/dummywidget.h" #include "editors/pixmapedit.h" #include "editors/pointedit.h" #include "editors/pointfedit.h" #include "editors/fontedit.h" #include "editors/rectedit.h" #include "editors/KPropertyRectFEditor.h" #include "editors/sizeedit.h" #include "editors/sizefedit.h" #include "editors/sizepolicyedit.h" #include "editors/spinbox.h" /*#include "stringlistedit.h"*/ #include "editors/linestyleedit.h" #include "editors/KPropertyStringEditor.h" // #include "symbolcombo.h" #include "editors/timeedit.h" #include "editors/KPropertyUrlEditor.h" KDefaultPropertyFactory::KDefaultPropertyFactory() : KPropertyWidgetsFactory() { KPropertyUtilsPrivate::setupPrivateIconsResourceWithMessage( QString::fromLatin1("kproperty%1").arg(KPROPERTY_STABLE_VERSION_MAJOR), QString::fromLatin1("icons/kproperty_%1.rcc").arg(KPropertyUtilsPrivate::supportedIconTheme()), QtFatalMsg); addEditor( KProperty::Bool, new KPropertyBoolDelegate ); //! @todo addEditor( KProperty::ByteArray, new KPropertyByteArrayDelegate ); addEditor( KProperty::Color, new KPropertyColorComboDelegate ); addEditor( KProperty::Cursor, new KPropertyCursorDelegate ); addEditor( KProperty::Date, new KPropertyDateDelegate ); addEditor( KProperty::DateTime, new KPropertyDateTimeDelegate ); addEditor( KProperty::Double, new KPropertyDoubleSpinBoxDelegate ); addEditor( KProperty::Font, new KPropertyFontDelegate ); addEditor( KProperty::Int, new KPropertyIntSpinBoxDelegate ); addEditor( KProperty::LineStyle, new KPropertyLineStyleComboDelegate ); addEditor( KProperty::LongLong, new KPropertyIntSpinBoxDelegate ); //!< @todo add more specialized delegate addEditor( KProperty::Pixmap, new KPropertyPixmapDelegate ); addEditor( KProperty::Point, new KPropertyPointDelegate ); addEditor( KProperty::PointF, new KPropertyPointFDelegate ); addEditor( KProperty::Rect, new KPropertyRectDelegate ); addEditor( KProperty::RectF, new KPropertyRectFDelegate ); addEditor( KProperty::Size, new KPropertySizeDelegate ); addEditor( KProperty::SizeF, new KPropertySizeFDelegate ); addEditor( KProperty::SizePolicy, new KPropertySizePolicyDelegate ); addEditor( KProperty::String, new KPropertyStringDelegate ); addEditor( KProperty::Time, new KPropertyTimeDelegate ); addEditor( KProperty::UInt, new KPropertyIntSpinBoxDelegate ); //!< @todo add more specialized delegate addEditor( KProperty::ULongLong, new KPropertyIntSpinBoxDelegate ); //!< @todo add more specialized delegate addEditor( KProperty::Url, new KPropertyUrlDelegate ); + addEditor( KProperty::ComposedUrl, new KPropertyUrlDelegate ); addEditor( KProperty::ValueFromList, new KPropertyComboBoxDelegate ); } KDefaultPropertyFactory::~KDefaultPropertyFactory() { } diff --git a/src/KProperty.h b/src/KProperty.h index de0db2b..220e7d6 100644 --- a/src/KProperty.h +++ b/src/KProperty.h @@ -1,528 +1,529 @@ /* 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.
  • 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/KPropertyComposedUrl.cpp b/src/KPropertyComposedUrl.cpp new file mode 100644 index 0000000..3f80a43 --- /dev/null +++ b/src/KPropertyComposedUrl.cpp @@ -0,0 +1,172 @@ +/* This file is part of the KDE project + Copyright (C) 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 "KPropertyComposedUrl.h" + +#include // std::tie + +class Q_DECL_HIDDEN KPropertyComposedUrl::Private +{ +public: + Private() {} + Private(const Private &other) { copy(other); } + Private(const QUrl &_baseUrl, const QUrl &_url) + : baseUrl(_baseUrl) + , url(_url) + { + } + +#define KPropertyComposedUrlPrivateArgs(o) std::tie(o.baseUrl, o.url) + void copy(const Private &other) + { + KPropertyComposedUrlPrivateArgs((*this)) = KPropertyComposedUrlPrivateArgs(other); + } + + bool operator==(const Private &other) const + { + return KPropertyComposedUrlPrivateArgs((*this)) == KPropertyComposedUrlPrivateArgs(other); + } +#undef KPropertyComposedUrlPrivateArgs + + //! Base URL for relative URLs, not used if 'url' is absolute but still stored + //! as it will be needed as soon as 'url' is changed to relative + QUrl baseUrl; + //! contains an absolute URL, or just a path for relative URLs + QUrl url; +}; + +KPropertyComposedUrl::KPropertyComposedUrl() + : d(new Private) +{ +} + +KPropertyComposedUrl::KPropertyComposedUrl(const QUrl &baseUrl, const QString &relativePath) + : KPropertyComposedUrl() +{ + setBaseUrl(baseUrl); + setRelativePath(relativePath); +} + +KPropertyComposedUrl::KPropertyComposedUrl(const QUrl &baseUrl, const QUrl &absoluteUrl) + : KPropertyComposedUrl() +{ + setBaseUrl(baseUrl); + setAbsoluteUrl(absoluteUrl); +} + +KPropertyComposedUrl::KPropertyComposedUrl(const KPropertyComposedUrl &other) + : d(new Private(*other.d)) +{ +} + +KPropertyComposedUrl::~KPropertyComposedUrl() +{ + delete d; +} + +KPropertyComposedUrl &KPropertyComposedUrl::operator=(const KPropertyComposedUrl &other) +{ + if (this != &other) { + d->copy(*other.d); + } + return *this; +} + +bool KPropertyComposedUrl::operator==(const KPropertyComposedUrl &other) const +{ + return *d == *other.d; +} + +QUrl KPropertyComposedUrl::value() const +{ + if (!isValid()) { + return QUrl(); + } + + if (d->url.isRelative()) { + QUrl baseUrl = d->baseUrl; + + //! append necessary '/' + if (!baseUrl.path().endsWith(QLatin1String("/"))) { + baseUrl.setPath(baseUrl.path() + QLatin1String("/")); + } + + return baseUrl.resolved(d->url); + } else { + return d->url; + } +} + +QUrl KPropertyComposedUrl::baseUrl() const +{ + return d->baseUrl; +} + +void KPropertyComposedUrl::setBaseUrl(const QUrl &url) +{ + if (!url.isValid() || url.isRelative()) { + d->baseUrl.clear(); + return; + } + + d->baseUrl = url; +} + +QUrl KPropertyComposedUrl::absoluteUrl() const +{ + return d->url.isRelative() ? QUrl() : d->url; +} + +void KPropertyComposedUrl::setAbsoluteUrl(const QUrl &absoluteUrl) +{ + d->url.clear(); + + if (absoluteUrl.isValid() && !absoluteUrl.isRelative()) { + d->url = absoluteUrl; + } +} + +QString KPropertyComposedUrl::relativePath() const +{ + return d->url.isRelative() ? d->url.path() : QString(); +} + +void KPropertyComposedUrl::setRelativePath(const QString &relativePath) +{ + d->url.clear(); + + if (!relativePath.isEmpty()) { + d->url.setPath(relativePath); + } +} + +bool KPropertyComposedUrl::isValid() const +{ + // we should have both URLs anyways + return d->baseUrl.isValid() && d->url.isValid(); +} + +QDebug operator<<(QDebug dbg, const KPropertyComposedUrl &p) +{ + dbg.nospace() << "KPropertyComposedUrl(" + << "baseUrl=" << p.baseUrl() << " relativePath=" << p.relativePath() + << " absoluteUrl=" << p.absoluteUrl() << ")"; + return dbg.space(); +} diff --git a/src/KPropertyComposedUrl.h b/src/KPropertyComposedUrl.h new file mode 100644 index 0000000..74eadbf --- /dev/null +++ b/src/KPropertyComposedUrl.h @@ -0,0 +1,164 @@ +/* This file is part of the KDE project + Copyright (C) 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. +*/ + +#ifndef KPROPERTYCOMPOSEDURL_H +#define KPROPERTYCOMPOSEDURL_H + +#include "kpropertycore_export.h" + +#include +#include +#include + +/** + * @brief A data structure that composes absolute and relative URLs. + * + * @since 3.2 + */ +class KPROPERTYCORE_EXPORT KPropertyComposedUrl +{ +public: + /** + * Constructs empty and invalid composed URL + */ + KPropertyComposedUrl(); + + /** + * Constructs composed URL with specified base URL and path relative to the base URL + * + * @a baseUrl must be a valid absolute URL, otherwise the entire composed URL is invalid. + * @a relativePath must be proper relative path. It is not important if it points to existing + * filesystem object. + */ + KPropertyComposedUrl(const QUrl &baseUrl, const QString &relativePath = QString()); + + /** + * Constructs composed URL with specified base URL and independent absolute URL + * + * @a baseUrl must be absolute or empty URL, otherwise the entire composed URL is invalid. + * @a absoluteUrl must be absolute URL. It is not important if it points to existing filesystem + * object. + * + * Because @a absoluteUrl carries all needed information, @a baseUrl is ignored in this variant + * of constructor. However @a baseUrl is still remembered and will be used if this object's + * value will be changed to relative path using setRelativePath(). + */ + KPropertyComposedUrl(const QUrl &baseUrl, const QUrl &absoluteUrl); + + /** + * Constructs a copy of @a other + */ + KPropertyComposedUrl(const KPropertyComposedUrl &other); + + ~KPropertyComposedUrl(); + + /** + * Assigns @a other to this KPropertyComposedUrl + */ + KPropertyComposedUrl &operator=(const KPropertyComposedUrl &other); + + /** + * Return @c true if this KPropertyComposedUrl equals to @a other + * + * Two KPropertyComposedUrl objects are equal if they have the same values. + * @see value() + */ + bool operator==(const KPropertyComposedUrl &other) const; + + /** + * Return @c true if this KPropertyComposedUrl does not equal to @a other + */ + bool operator!=(const KPropertyComposedUrl &other) const { return !operator==(other); } + + /** + * Returns URL value computed from base URL, absolute URL and relative path, whichever is + * defined. + * + * - If the KPropertyComposedUrl is invalid, empty QUrl is returned + * - If absolute path is present, it is returned + * - If base URL and relative path are present, URL with added relative path is returned + */ + QUrl value() const; + + /** + * Returns the base URL (absolute) or empty URL if there is no absolute base URL specified + */ + QUrl baseUrl() const; + + /** + * Sets base URL (absolute) + * + * If @a baseUrl is not valid and absolute, base URL is cleared. + * In any case relative path (relativePath()) and absolute URL (absoluteUrl()) are not modified. + */ + void setBaseUrl(const QUrl &url); + + /** + * Returns absolute URL that has been set for this object + * + * Empty URL is returned if absolute URL is not specified. In this case relative path can still + * be present. If absolute URL is present, relative path is empty. + */ + QUrl absoluteUrl() const; + + /** + * Sets a new absolute URL + * + * If @a absoluteUrl is not valid and absolute, it is cleared. + * If @a absoluteUrl is absolute, relative path (relativePath()) is cleared. + */ + void setAbsoluteUrl(const QUrl &absoluteUrl); + + /** + * Returns relative path that has been set for this object + * + * Empty string is returned if there is no path assigned. Non-empty path can be used to + * calculate value by concatenating it with the base URL. If relativePath() is present + * absoluteUrl() is empty . + */ + QString relativePath() const; + + /** + * Sets a new relative path for this object + * + * If @a relativePath is not a valid or empty path, it is cleared. + * If @a relativePath is empty or valid, absolute URL is cleared. + */ + void setRelativePath(const QString &relativePath); + + /** + * Return @c true if the URL value that can be computed (from base URL, absolute URL and + * relative path) is valid + * + * @see value() for description of how value is computed. + */ + bool isValid() const; + +private: + class Private; + Private *const d; +}; + +Q_DECLARE_METATYPE(KPropertyComposedUrl) + +//! qDebug() stream operator. Writes KPropertyComposedUrl to the debug output. +KPROPERTYCORE_EXPORT QDebug operator<<(QDebug dbg, const KPropertyComposedUrl &p); + +#endif // KPROPERTYCOMPOSEDURL_H diff --git a/src/editors/KPropertyComposedUrlEditor.cpp b/src/editors/KPropertyComposedUrlEditor.cpp new file mode 100644 index 0000000..d744a40 --- /dev/null +++ b/src/editors/KPropertyComposedUrlEditor.cpp @@ -0,0 +1,67 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur + Copyright (C) 2004 Alexander Dymo + Copyright (C) 2016-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 "KPropertyComposedUrlEditor.h" +#include "KPropertyUrlEditor.h" // KPropertyUrlDelegate +#include "KPropertyUrlEditor_p.h" + +#include + +KPropertyComposedUrlEditor::KPropertyComposedUrlEditor(const KProperty &property, QWidget *parent) + : KPropertyGenericSelectionEditor(parent) + , d(new KPropertyUrlEditorPrivate(this, property)) +{ + setMainWidget(d->lineEdit); + connect(d.data(), &KPropertyUrlEditorPrivate::commitData, this, + [this] { emit commitData(this); }); +} + +KPropertyComposedUrlEditor::~KPropertyComposedUrlEditor() {} + +KPropertyComposedUrl KPropertyComposedUrlEditor::value() const +{ + return d->value.value(); +} + +void KPropertyComposedUrlEditor::setValue(const KPropertyComposedUrl &value) +{ + d->setValue(QVariant::fromValue(value)); + const KPropertyUrlDelegate delegate; + d->updateLineEdit(delegate.valueToString(d->value, locale())); +} + +void KPropertyComposedUrlEditor::selectButtonClicked() +{ + QUrl url = d->getUrl(); + if (url.isValid() && d->checkAndUpdate(&url)) { + KPropertyComposedUrl composedUrl = value(); + composedUrl.setAbsoluteUrl(url); + setValue(composedUrl); + emit commitData(this); + } +} + +bool KPropertyComposedUrlEditor::eventFilter(QObject *o, QEvent *event) +{ + d->processEvent(o, event); + return KPropertyGenericSelectionEditor::eventFilter(o, event); +} diff --git a/src/editors/KPropertyUrlEditor.h b/src/editors/KPropertyComposedUrlEditor.h similarity index 51% copy from src/editors/KPropertyUrlEditor.h copy to src/editors/KPropertyComposedUrlEditor.h index 2feed82..0adf2ef 100644 --- a/src/editors/KPropertyUrlEditor.h +++ b/src/editors/KPropertyComposedUrlEditor.h @@ -1,74 +1,67 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur Copyright (C) 2004 Alexander Dymo - Copyright (C) 2016-2017 Jarosław Staniek + Copyright (C) 2016-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. */ -#ifndef KPROPERTYURLEDITOR_H -#define KPROPERTYURLEDITOR_H +#ifndef KPROPERTYCOMPOSEDURLEDITOR_H +#define KPROPERTYCOMPOSEDURLEDITOR_H -#include "KPropertyWidgetsFactory.h" +#include "KPropertyComposedUrl.h" #include "KPropertyGenericSelectionEditor.h" -#include +#include -//! Editor for Url type -class KPROPERTYWIDGETS_EXPORT KPropertyUrlEditor : public KPropertyGenericSelectionEditor +class KProperty; +class KPropertyUrlEditorPrivate; + +/** + * Editor for ComposedUrl type + * + * @since 3.2 + */ +class KPROPERTYWIDGETS_EXPORT KPropertyComposedUrlEditor : public KPropertyGenericSelectionEditor { - Q_PROPERTY(QUrl value READ value WRITE setValue USER true) + Q_PROPERTY(KPropertyComposedUrl value READ value WRITE setValue USER true) Q_OBJECT public: - explicit KPropertyUrlEditor(const KProperty &property, QWidget *parent = nullptr); - - ~KPropertyUrlEditor() override; + explicit KPropertyComposedUrlEditor(const KProperty &property, QWidget *parent = nullptr); + ~KPropertyComposedUrlEditor() override; - virtual QUrl value() const; + virtual KPropertyComposedUrl value() const; Q_SIGNALS: void commitData(QWidget * editor); public Q_SLOTS: - virtual void setValue(const QUrl &value); + virtual void setValue(const KPropertyComposedUrl &value); protected Q_SLOTS: void selectButtonClicked() override; protected: bool eventFilter(QObject *o, QEvent *event) override; private: - Q_DISABLE_COPY(KPropertyUrlEditor) - class Private; - QScopedPointer const d; -}; - -//! Delegate for Url type -class KPROPERTYWIDGETS_EXPORT KPropertyUrlDelegate : public KPropertyEditorCreatorInterface, - public KPropertyValueDisplayInterface -{ -public: - KPropertyUrlDelegate(); - - QWidget *createEditor(int type, QWidget *parent, const QStyleOptionViewItem &option, - const QModelIndex &index) const override; - - QString valueToString(const QVariant &value, const QLocale &locale) const override; + Q_DISABLE_COPY(KPropertyComposedUrlEditor) + QScopedPointer const d; }; -#endif +#endif // KPROPERTYCOMPOSEDURLEDITOR_H diff --git a/src/editors/KPropertyUrlEditor.cpp b/src/editors/KPropertyUrlEditor.cpp index 3b0ccf2..d4b05d4 100644 --- a/src/editors/KPropertyUrlEditor.cpp +++ b/src/editors/KPropertyUrlEditor.cpp @@ -1,266 +1,132 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur Copyright (C) 2004 Alexander Dymo Copyright (C) 2016-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 "KPropertyUrlEditor.h" -#include "KPropertyEditorItemEvent.h" +#include "KPropertyComposedUrl.h" +#include "KPropertyComposedUrlEditor.h" #include "KPropertyEditorView.h" -#include "KPropertySet.h" +#include "KPropertyUrlEditor_p.h" #include "KPropertyUtils.h" +#include "kproperty_debug.h" -#include -#include +#include #include -class Q_DECL_HIDDEN KPropertyUrlEditor::Private -{ -public: - Private(KPropertyUrlEditor *editor, const KProperty &property) - : fileMode(property.option("fileMode").toByteArray().toLower()) - , confirmOverwrites(property.option("confirmOverwrites", false).toBool()) - , propertyName(property.name()) - , m_editor(editor) - { - } - - QString fixUp(const QString &path) const - { - QString result = path; - if (path.endsWith(QLatin1Char('/')) -#ifdef Q_OS_WIN - && path.length() > 3 // "C:" would not be a valid path on Windows -#endif - ) - { - result.chop(1); - } - return result; - } - - //! @return @c true is @a url is valid for the property options - //! If path obtained from the URL needs fixing @a fixUpPath is set to fixed string, otherwise - //! it is set to empty string. - bool isValid(const QUrl &url, QString *fixUpPath) const - { - Q_ASSERT(fixUpPath); - fixUpPath->clear(); - if (url.isEmpty()) { - return true; - } - if (!url.isValid()) { - return false; - } - if (!url.isLocalFile()) { - // Non-file protocols are only allowed for "" fileMode - return fileMode.isEmpty(); - } - QString path = url.toLocalFile(); - QFileInfo info(path); - if (!info.isNativePath()) { - return false; - } - //! @todo check for illegal characters -- https://stackoverflow.com/a/45282192 - if (fileMode == "existingfile") { - //! @todo display error for false - return info.isFile() && info.exists(); - } else if (fileMode == "dirsonly") { - //! @todo display error for false - if (info.isDir() && info.exists()) { - *fixUpPath = fixUp(path); - } else { - return false; - } - } - // fileMode is "": - //! @todo support confirmOverwrites, display error - //if (info.exists()) { - //} - return true; - } - - //! @return URL based on text entered - QUrl urlFromText(const QString &text) const - { - //! @todo offer "workingDirectory" option so relative paths are supported - QString workingDirectory; - return QUrl::fromUserInput(text, workingDirectory, QUrl::AssumeLocalFile); - } - - //! @return @c true if @a event is a key press event for Enter or Return key - bool enterPressed(QEvent *event) const - { - if (event->type() == QEvent::KeyPress) { - const int key = static_cast(event)->key(); - switch (key) { - case Qt::Key_Enter: - case Qt::Key_Return: - case Qt::Key_Down: - case Qt::Key_Up: - return true; - default: - break; - } - } - return false; - } - - //! Get a new URL value either from a custom or the built-in dialog - QUrl getUrl() - { - QString caption; - if (fileMode == "existingfile") { - caption = QObject::tr("Select Existing File"); - } else if (fileMode == "dirsonly") { - caption = QObject::tr("Select Existing Directory"); - } else { - caption = QObject::tr("Select File"); - } - // Try to obtain URL from a custom dialog - if (m_editor->parentWidget()) { - KPropertyEditorView *view - = qobject_cast(m_editor->parentWidget()->parentWidget()); - KProperty *property = &view->propertySet()->property(propertyName); - if (property) { - QVariantMap parameters; - parameters[QStringLiteral("url")] = value; - parameters[QStringLiteral("caption")] = caption; - KPropertyEditorItemEvent event(*property, QStringLiteral("getOpenFileUrl"), parameters); - emit view->handlePropertyEditorItemEvent(&event); - if (event.hasResult()) { - return event.result().toUrl(); - } - } - } - // Use default dialogs - //! @todo filters, more options, supportedSchemes, localFilesOnly? - QFileDialog::Options options; - if (fileMode == "existingfile") { - return QFileDialog::getOpenFileUrl(m_editor, caption, value, QString(), nullptr, options); - } else if (fileMode == "dirsonly") { - options |= QFileDialog::ShowDirsOnly; - return QFileDialog::getExistingDirectoryUrl(m_editor, caption, value, options); - } else { - if (!confirmOverwrites) { - options |= QFileDialog::DontConfirmOverwrite; - } - return QFileDialog::getSaveFileUrl(m_editor, caption, value, QString(), nullptr, options); - } - return QUrl(); - } - - QUrl value; - QString savedText; - QLineEdit *lineEdit; - KPropertyUrlDelegate delegate; - QByteArray fileMode; - bool confirmOverwrites; - QByteArray propertyName; - -private: - KPropertyUrlEditor * const m_editor; -}; - KPropertyUrlEditor::KPropertyUrlEditor(const KProperty &property, QWidget *parent) - : KPropertyGenericSelectionEditor(parent), d(new Private(this, property)) + : KPropertyGenericSelectionEditor(parent) + , d(new KPropertyUrlEditorPrivate(this, property)) { - d->lineEdit = new QLineEdit; - d->lineEdit->setClearButtonEnabled(true); setMainWidget(d->lineEdit); - d->lineEdit->installEventFilter(this); + connect(d.data(), &KPropertyUrlEditorPrivate::commitData, this, + [this] { emit commitData(this); }); } -KPropertyUrlEditor::~KPropertyUrlEditor() -{ -} +KPropertyUrlEditor::~KPropertyUrlEditor() {} QUrl KPropertyUrlEditor::value() const { - return d->value; + return d->value.toUrl(); } void KPropertyUrlEditor::setValue(const QUrl &value) { - d->value = value; - d->lineEdit->setText(d->delegate.valueToString(d->value, locale())); - d->savedText = d->lineEdit->text(); + d->setValue(value); + const KPropertyUrlDelegate delegate; + d->updateLineEdit(delegate.valueToString(d->value, locale())); } void KPropertyUrlEditor::selectButtonClicked() { - const QUrl url = d->getUrl(); - QString fixUpPath; - if (!url.isEmpty() && d->isValid(url, &fixUpPath)) { + QUrl url = d->getUrl(); + if (url.isValid() && d->checkAndUpdate(&url)) { setValue(url); emit commitData(this); } } bool KPropertyUrlEditor::eventFilter(QObject *o, QEvent *event) { - if (o == d->lineEdit && (event->type() == QEvent::FocusOut || d->enterPressed(event))) { - if (d->savedText != d->lineEdit->text()) { // text changed since the recent setValue(): update - //! @todo offer "workingDirectory" option so relative paths are supported - QString workingDirectory; - QUrl newUrl = QUrl::fromUserInput(d->lineEdit->text(), workingDirectory, QUrl::AssumeLocalFile); - QString fixUpPath; - if (d->isValid(newUrl, &fixUpPath)) { - if (fixUpPath.isEmpty()) { // accept value as-is - d->value = newUrl; - d->savedText = d->lineEdit->text(); - } else { // fixed up value, e.g. directory path has removed trailing slash - d->value = QUrl::fromUserInput(fixUpPath, workingDirectory, QUrl::AssumeLocalFile); - d->savedText = fixUpPath; - } - emit commitData(this); - } else { // invalid URL: revert text to last saved value which is valid, emit no change - d->lineEdit->setText(d->savedText); - } - } - } + d->processEvent(o, event); return KPropertyGenericSelectionEditor::eventFilter(o, event); } // ---- -KPropertyUrlDelegate::KPropertyUrlDelegate() -{ -} +KPropertyUrlDelegate::KPropertyUrlDelegate() {} -QWidget* KPropertyUrlDelegate::createEditor(int type, QWidget *parent, - const QStyleOptionViewItem &option, const QModelIndex &index) const +QWidget *KPropertyUrlDelegate::createEditor(int type, QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const { - Q_UNUSED(type) Q_UNUSED(option) + const KProperty *prop = KPropertyUtils::propertyForIndex(index); - return new KPropertyUrlEditor(prop ? *prop : KProperty(), parent); + + if (type == KProperty::Url) { + return new KPropertyUrlEditor(prop ? *prop : KProperty(), parent); + } else if (type == KProperty::ComposedUrl) { + return new KPropertyComposedUrlEditor(prop ? *prop : KProperty(), parent); + } + + return nullptr; } -QString KPropertyUrlDelegate::valueToString(const QVariant &value, - const QLocale &locale) const +QString KPropertyUrlDelegate::valueToString(const QVariant &value, const QLocale &locale) const { - const QUrl url(value.toUrl()); - const QString s(url.isLocalFile() - ? QDir::toNativeSeparators(url.toLocalFile()) - : value.toString()); + QUrl url; + + if (value.canConvert()) { + url = value.toUrl(); + } else if (value.canConvert()) { + const KPropertyComposedUrl composedUrl = value.value(); + + if (composedUrl.isValid()) { + if (!composedUrl.relativePath().isEmpty()) { + QUrl urlWithPath; + urlWithPath.setPath(composedUrl.relativePath()); + url = urlWithPath; + } else { + url = composedUrl.absoluteUrl(); + } + } else { + return QString(); + } + } else { + return QString(); + } + + QString s; + + if (url.isLocalFile()) { + s = QDir::toNativeSeparators(url.toLocalFile()); + // TODO this assumes local files only + } else if (url.isRelative()) { + s = QDir::toNativeSeparators(url.toString()); + } else { + s = url.toString(); + } + if (locale.language() == QLocale::C) { return s; } return valueToLocalizedString(s); } diff --git a/src/editors/KPropertyUrlEditor.h b/src/editors/KPropertyUrlEditor.h index 2feed82..8992a46 100644 --- a/src/editors/KPropertyUrlEditor.h +++ b/src/editors/KPropertyUrlEditor.h @@ -1,74 +1,77 @@ /* This file is part of the KDE project Copyright (C) 2004 Cedric Pasteur Copyright (C) 2004 Alexander Dymo - Copyright (C) 2016-2017 Jarosław Staniek + Copyright (C) 2016-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. */ #ifndef KPROPERTYURLEDITOR_H #define KPROPERTYURLEDITOR_H +#include "KPropertyComposedUrl.h" #include "KPropertyWidgetsFactory.h" #include "KPropertyGenericSelectionEditor.h" #include +class KPropertyUrlEditorPrivate; + //! Editor for Url type class KPROPERTYWIDGETS_EXPORT KPropertyUrlEditor : public KPropertyGenericSelectionEditor { Q_PROPERTY(QUrl value READ value WRITE setValue USER true) Q_OBJECT public: explicit KPropertyUrlEditor(const KProperty &property, QWidget *parent = nullptr); ~KPropertyUrlEditor() override; virtual QUrl value() const; Q_SIGNALS: void commitData(QWidget * editor); public Q_SLOTS: virtual void setValue(const QUrl &value); protected Q_SLOTS: void selectButtonClicked() override; protected: bool eventFilter(QObject *o, QEvent *event) override; private: Q_DISABLE_COPY(KPropertyUrlEditor) - class Private; - QScopedPointer const d; + QScopedPointer const d; }; -//! Delegate for Url type +//! Delegate for Url and ComposedUrl types class KPROPERTYWIDGETS_EXPORT KPropertyUrlDelegate : public KPropertyEditorCreatorInterface, public KPropertyValueDisplayInterface { public: KPropertyUrlDelegate(); QWidget *createEditor(int type, QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; QString valueToString(const QVariant &value, const QLocale &locale) const override; }; #endif diff --git a/src/editors/KPropertyUrlEditor_p.cpp b/src/editors/KPropertyUrlEditor_p.cpp new file mode 100644 index 0000000..23e60d7 --- /dev/null +++ b/src/editors/KPropertyUrlEditor_p.cpp @@ -0,0 +1,267 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur + Copyright (C) 2004 Alexander Dymo + Copyright (C) 2016-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 "KPropertyUrlEditor_p.h" +#include "KProperty.h" +#include "KPropertyComposedUrl.h" +#include "KPropertyEditorItemEvent.h" +#include "KPropertyEditorView.h" +#include "KPropertyGenericSelectionEditor.h" +#include "KPropertySet.h" +#include "kproperty_debug.h" + +#include +#include +#include +#include +#include +#include + +KPropertyUrlEditorPrivate::KPropertyUrlEditorPrivate(KPropertyGenericSelectionEditor *editor, + const KProperty &property) + : QObject() + , fileMode(property.option("fileMode").toByteArray().toLower()) + , confirmOverwrites(property.option("confirmOverwrites", false).toBool()) + , propertyName(property.name()) + , m_editor(editor) +{ + lineEdit = new QLineEdit; + lineEdit->setClearButtonEnabled(true); + lineEdit->installEventFilter(m_editor); + + isComposedUrl = property.type() == KProperty::ComposedUrl; + setValue(property.value()); +} + +void KPropertyUrlEditorPrivate::setValue(const QVariant &newValue) +{ + if (isComposedUrl && newValue.type() == QVariant::Url) { + KPropertyComposedUrl composedUrl = value.value(); + const QUrl newUrlValue = newValue.toUrl(); + + if (newUrlValue.isRelative()) { + composedUrl.setRelativePath(newUrlValue.path()); + } else { + composedUrl.setAbsoluteUrl(newUrlValue); + } + + value = QVariant::fromValue(composedUrl); + } else { + value = newValue; + } +} + +void KPropertyUrlEditorPrivate::updateLineEdit(const QString &textToDisplay) +{ + lineEdit->setText(textToDisplay); + savedText = lineEdit->text(); +} + +bool KPropertyUrlEditorPrivate::checkAndUpdate(QUrl *url) const +{ + Q_ASSERT(url); + + if (url->isEmpty()) { + return true; + } + if (!url->isValid()) { + return false; + } + + QUrl finalUrlForChecking; + + // handle relative URLs. We pass absolute URLs or truly relative URLs + // into this function. We don't pass weird things like QUrl("C:/report.txt") + // (which is considered to be relative). That's why isRelative() is enough + if (url->isRelative()) { + const KPropertyComposedUrl composedUrl = value.value(); + + // regular QUrl types must be absolute + if (!isComposedUrl) { + kprWarning() << "Property" << propertyName << "doesn't support relative URLs:" << *url; + return false; + // complex url may be relative, so check its base URL + } else if (!composedUrl.baseUrl().isValid()) { + kprWarning() << "The base URL in property" << propertyName + << "is invalid:" << composedUrl; + return false; + } else { + finalUrlForChecking = composedUrl.baseUrl().resolved(*url); + } + } else { + finalUrlForChecking = *url; + } + + if (!finalUrlForChecking.isLocalFile()) { + // Non-file protocols are only allowed for "" fileMode + return fileMode.isEmpty(); + } + const QString path = finalUrlForChecking.toLocalFile(); + const QFileInfo info(path); + if (!info.isNativePath()) { + return false; + } + //! @todo check for illegal characters -- https://stackoverflow.com/a/45282192 + if (fileMode == "existingfile") { + //! @todo display error for false + return info.isFile() && info.exists(); + } else if (fileMode == "dirsonly") { + //! @todo display error for false + if (info.isDir() && info.exists()) { + if (url->isRelative()) { + url->setPath(fixUp(url->toString(), /* isRelative */ true)); + } else { + url->setPath(fixUp(path, /* isRelative */ false)); + } + return true; + } else { + return false; + } + } + // fileMode is "": + //! @todo support confirmOverwrites, display error + // if (info.exists()) { + //} + return true; +} + +bool KPropertyUrlEditorPrivate::enterPressed(QEvent *event) const +{ + if (event->type() == QEvent::KeyPress) { + const int key = static_cast(event)->key(); + switch (key) { + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Down: + case Qt::Key_Up: + return true; + default: + break; + } + } + return false; +} + +QString KPropertyUrlEditorPrivate::fixUp(const QString &path, bool isRelative) const +{ + Q_UNUSED(isRelative) + QString result = path; + if (path.endsWith(QLatin1Char('/')) +#ifdef Q_OS_WIN + && (isRelative || path.length() > 3) // "C:" would not be a valid path on Windows +#endif + ) { + result.chop(1); + } + return result; +} + +QUrl KPropertyUrlEditorPrivate::getUrl() +{ + QString caption; + if (fileMode == "existingfile") { + caption = QObject::tr("Select Existing File"); + } else if (fileMode == "dirsonly") { + caption = QObject::tr("Select Existing Directory"); + } else { + caption = QObject::tr("Select File"); + } + + QUrl dirUrlForFileDialog; + + if (isComposedUrl) { + const KPropertyComposedUrl composedUrl = value.value(); + dirUrlForFileDialog = composedUrl.value(); + } else { + dirUrlForFileDialog = value.toUrl(); + } + + // Try to obtain URL from a custom dialog + if (m_editor->parentWidget()) { + KPropertyEditorView *view + = qobject_cast(m_editor->parentWidget()->parentWidget()); + KProperty *property = &view->propertySet()->property(propertyName); + if (property) { + QVariantMap parameters; + parameters[QStringLiteral("url")] = dirUrlForFileDialog; + parameters[QStringLiteral("caption")] = caption; + KPropertyEditorItemEvent event(*property, QStringLiteral("getOpenFileUrl"), parameters); + emit view->handlePropertyEditorItemEvent(&event); + if (event.hasResult()) { + return event.result().toUrl(); + } + } + } + + // Use default dialogs + //! @todo filters, more options, supportedSchemes, localFilesOnly? + QFileDialog::Options options; + if (fileMode == "existingfile") { + return QFileDialog::getOpenFileUrl(m_editor, caption, dirUrlForFileDialog, QString(), + nullptr, options); + } else if (fileMode == "dirsonly") { + options |= QFileDialog::ShowDirsOnly; + return QFileDialog::getExistingDirectoryUrl(m_editor, caption, dirUrlForFileDialog, + options); + } else { + if (!confirmOverwrites) { + options |= QFileDialog::DontConfirmOverwrite; + } + return QFileDialog::getSaveFileUrl(m_editor, caption, dirUrlForFileDialog, QString(), + nullptr, options); + } + return QUrl(); +} + +void KPropertyUrlEditorPrivate::processEvent(QObject *o, QEvent *event) +{ + if (o == lineEdit && (event->type() == QEvent::FocusOut || enterPressed(event))) { + const QString enteredText = lineEdit->text(); + + if (savedText != enteredText) { // text changed since the recent setValue(): update + QUrl newUrl(enteredText); + + // "a/b" -> set this as a path + // "kde.org" -> "http://kde.org" (for regular non-composed URLs) + // "kde.org" -> "kde.org" (for composed URLs) + // "C:\report.txt" -> "file:///C:/report.txt" + // "http://google.com" -> convert with more strict rules + if (!newUrl.scheme().isEmpty() || (newUrl.scheme().isEmpty() && !isComposedUrl) + || QDir::isAbsolutePath(enteredText)) { + newUrl = QUrl::fromUserInput(enteredText); + } else { + newUrl.clear(); + newUrl.setPath(QDir::fromNativeSeparators(enteredText)); + } + + if (checkAndUpdate(&newUrl)) { + setValue(newUrl); + savedText = enteredText; + emit commitData(); + } else { // invalid URL: revert text to last saved value which is valid, + // emit no change + kprWarning() << "URL" << newUrl << "is not valid"; + lineEdit->setText(savedText); + } + } + } +} diff --git a/src/editors/KPropertyUrlEditor_p.h b/src/editors/KPropertyUrlEditor_p.h new file mode 100644 index 0000000..e2fca4a --- /dev/null +++ b/src/editors/KPropertyUrlEditor_p.h @@ -0,0 +1,89 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Cedric Pasteur + Copyright (C) 2004 Alexander Dymo + Copyright (C) 2016-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. +*/ + +#ifndef KPROPERTYURLEDITORPRIVATE_H +#define KPROPERTYURLEDITORPRIVATE_H + +#include +#include +#include +#include + +class KPropertyGenericSelectionEditor; +class KProperty; + +class QEvent; +class QLineEdit; +class QLocale; + +/*! + * A private implementation of KPropertyUrlEditor and KPropertyComposedUrlEditor + * + * @warning This file is not a part of public API and must not be used by clients + */ +class KPropertyUrlEditorPrivate : public QObject +{ + Q_OBJECT + +public: + KPropertyUrlEditorPrivate(KPropertyGenericSelectionEditor *editor, const KProperty &property); + + //! Save a new value as a variant. Additionally allow saving regular URLs + //! if the underlying type is KPropertyComposedUrl + void setValue(const QVariant &newValue); + + //! Update the line edit after setting a new value + void updateLineEdit(const QString &textToDisplay); + + //! @return @c true is @a url is valid for the property options + //! @param[in,out] url - the URL to check and update + bool checkAndUpdate(QUrl *url) const; + + //! Get a new URL value either from a custom or the built-in dialog + QUrl getUrl(); + + //! Process an input event from the editor + void processEvent(QObject *o, QEvent *event); + +private: + //! @return @c true if @a event is a key press event for Enter or Return key + bool enterPressed(QEvent *event) const; + + QString fixUp(const QString &path, bool isRelative) const; + +Q_SIGNALS: + void commitData(); + +public: + QVariant value; + bool isComposedUrl; + QString savedText; + QLineEdit *lineEdit; + QByteArray fileMode; + bool confirmOverwrites; + QByteArray propertyName; + +private: + KPropertyGenericSelectionEditor *const m_editor; +}; + +#endif