diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d1cef80215b..58b600f26d1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -97,6 +97,11 @@ add_subdirectory(plugins) add_subdirectory(android) +if(TARGET Qt::Widgets) + add_subdirectory(uiplugin) + add_subdirectory(uitools) +endif() + add_subdirectory(repparser) if(QT_FEATURE_localserver) add_subdirectory(remoteobjects) diff --git a/src/shared/deviceskin/deviceskin.cpp b/src/shared/deviceskin/deviceskin.cpp new file mode 100644 index 00000000000..cc7aac3a379 --- /dev/null +++ b/src/shared/deviceskin/deviceskin.cpp @@ -0,0 +1,806 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "deviceskin_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef TEST_SKIN +# include +# include +# include +# include +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + enum { joydistance = 10, key_repeat_period = 50, key_repeat_delay = 500 }; + enum { debugDeviceSkin = 0 }; +} + +static void parseRect(const QString &value, QRect *rect) { + const auto l = QStringView{value}.split(QLatin1Char(' ')); + rect->setRect(l[0].toInt(), l[1].toInt(), l[2].toInt(), l[3].toInt()); +} + +static QString msgImageNotLoaded(const QString &f) { + return DeviceSkin::tr("The image file '%1' could not be loaded.").arg(f); +} + +// ------------ DeviceSkinButtonArea +QDebug &operator<<(QDebug &str, const DeviceSkinButtonArea &a) +{ + + str << "Area: " << a.name << " keyCode=" << a.keyCode << " area=" << a.area + << " text=" << a.text << " activeWhenClosed=" << a.activeWhenClosed; + return str; +} + +// ------------ DeviceSkinParameters + +QDebug operator<<(QDebug str, const DeviceSkinParameters &p) +{ + str << "Images " << p.skinImageUpFileName << ',' + << p.skinImageDownFileName<< ',' << p.skinImageClosedFileName + << ',' << p.skinCursorFileName <<"\nScreen: " << p.screenRect + << " back: " << p.backScreenRect << " closed: " << p.closedScreenRect + << " cursor: " << p.cursorHot << " Prefix: " << p.prefix + << " Joystick: " << p.joystick << " MouseHover" << p.hasMouseHover; + const int numAreas = p.buttonAreas.size(); + for (int i = 0; i < numAreas; i++) + str << p.buttonAreas[i]; + return str; +} + +QSize DeviceSkinParameters::secondaryScreenSize() const +{ + return backScreenRect.isNull() ? closedScreenRect .size(): backScreenRect.size(); +} + +bool DeviceSkinParameters::hasSecondaryScreen() const +{ + return secondaryScreenSize() != QSize(0, 0); +} + +bool DeviceSkinParameters::read(const QString &skinDirectory, ReadMode rm, QString *errorMessage) +{ + // Figure out the name. remove ending '/' if present + QString skinFile = skinDirectory; + if (skinFile.endsWith(QLatin1Char('/'))) + skinFile.truncate(skinFile.size() - 1); + + QFileInfo fi(skinFile); + QString fn; + if ( fi.isDir() ) { + prefix = skinFile; + prefix += QLatin1Char('/'); + fn = prefix; + fn += fi.baseName(); + fn += ".skin"_L1; + } else if (fi.isFile()){ + fn = skinFile; + prefix = fi.path(); + prefix += QLatin1Char('/'); + } else { + *errorMessage = DeviceSkin::tr("The skin directory '%1' does not contain a configuration file.").arg(skinDirectory); + return false; + } + QFile f(fn); + if (!f.open(QIODevice::ReadOnly )) { + *errorMessage = DeviceSkin::tr("The skin configuration file '%1' could not be opened.").arg(fn); + return false; + } + QTextStream ts(&f); + const bool rc = read(ts, rm, errorMessage); + if (!rc) + *errorMessage = DeviceSkin::tr("The skin configuration file '%1' could not be read: %2") + .arg(fn, *errorMessage); + return rc; +} +bool DeviceSkinParameters::read(QTextStream &ts, ReadMode rm, QString *errorMessage) +{ + QStringList closedAreas; + QStringList toggleAreas; + QStringList toggleActiveAreas; + int nareas = 0; + screenDepth = 0; + QString mark; + ts >> mark; + hasMouseHover = true; // historical default + if (mark == "[SkinFile]"_L1) { + const QString UpKey = "Up"_L1; + const QString DownKey = "Down"_L1; + const QString ClosedKey = "Closed"_L1; + const QString ClosedAreasKey = "ClosedAreas"_L1; + const QString ScreenKey = "Screen"_L1; + const QString ScreenDepthKey = "ScreenDepth"_L1; + const QString BackScreenKey = "BackScreen"_L1; + const QString ClosedScreenKey = "ClosedScreen"_L1; + const QString CursorKey = "Cursor"_L1; + const QString AreasKey = "Areas"_L1; + const QString ToggleAreasKey = "ToggleAreas"_L1; + const QString ToggleActiveAreasKey = "ToggleActiveAreas"_L1; + const QString HasMouseHoverKey = "HasMouseHover"_L1; + // New + while (!nareas) { + QString line = ts.readLine(); + if ( line.isNull() ) + break; + if (!line.isEmpty() && line.at(0) != u'#') { + int eq = line.indexOf(QLatin1Char('=')); + if ( eq >= 0 ) { + const QString key = line.left(eq); + eq++; + while (eq> s >> x >> y >> w >> h >> na; + skinImageDownFileName = s; + screenRect.setRect(x, y, w, h); + nareas = na; + } + // Done for short mode + if (rm == ReadSizeOnly) + return true; + // verify skin files exist + skinImageUpFileName.insert(0, prefix); + if (!QFile(skinImageUpFileName).exists()) { + *errorMessage = DeviceSkin::tr("The skin \"up\" image file '%1' does not exist.").arg(skinImageUpFileName); + return false; + } + if (!skinImageUp.load(skinImageUpFileName)) { + *errorMessage = msgImageNotLoaded(skinImageUpFileName); + return false; + } + + skinImageDownFileName.insert(0, prefix); + if (!QFile(skinImageDownFileName).exists()) { + *errorMessage = DeviceSkin::tr("The skin \"down\" image file '%1' does not exist.").arg(skinImageDownFileName); + return false; + } + if (!skinImageDown.load(skinImageDownFileName)) { + *errorMessage = msgImageNotLoaded(skinImageDownFileName); + return false; + } + + if (!skinImageClosedFileName.isEmpty()) { + skinImageClosedFileName.insert(0, prefix); + if (!QFile(skinImageClosedFileName).exists()) { + *errorMessage = DeviceSkin::tr("The skin \"closed\" image file '%1' does not exist.").arg(skinImageClosedFileName); + return false; + } + if (!skinImageClosed.load(skinImageClosedFileName)) { + *errorMessage = msgImageNotLoaded(skinImageClosedFileName); + return false; + } + } + + if (!skinCursorFileName.isEmpty()) { + skinCursorFileName.insert(0, prefix); + if (!QFile(skinCursorFileName).exists()) { + *errorMessage = DeviceSkin::tr("The skin cursor image file '%1' does not exist.").arg(skinCursorFileName); + return false; + } + if (!skinCursor.load(skinCursorFileName)) { + *errorMessage = msgImageNotLoaded(skinCursorFileName); + return false; + } + } + + // read areas + if (!nareas) + return true; + buttonAreas.reserve(nareas); + + int i = 0; + ts.readLine(); // eol + joystick = -1; + const QString Joystick = "Joystick"_L1; + const QRegularExpression splitRe("[ \t][ \t]*"_L1); + Q_ASSERT(splitRe.isValid()); + while (i < nareas && !ts.atEnd() ) { + buttonAreas.push_back(DeviceSkinButtonArea()); + DeviceSkinButtonArea &area = buttonAreas.back(); + const QString line = ts.readLine(); + if ( !line.isEmpty() && line[0] != QLatin1Char('#') ) { + const QStringList tok = line.split(splitRe); + if ( tok.size()<6 ) { + *errorMessage = DeviceSkin::tr("Syntax error in area definition: %1").arg(line); + return false; + } else { + area.name = tok[0]; + QString k = tok[1]; + if ( k.left(2).toLower() == "0x"_L1) { + area.keyCode = k.mid(2).toInt(0,16); + } else { + area.keyCode = k.toInt(); + } + + int p=0; + for (int j=2; j < tok.size() - 1; ) { + const int x = tok[j++].toInt(); + const int y = tok[j++].toInt(); + area.area.putPoints(p++,1,x,y); + } + + const QChar doubleQuote = QLatin1Char('"'); + if ( area.name[0] == doubleQuote && area.name.endsWith(doubleQuote)) { + area.name.truncate(area.name.size() - 1); + area.name.remove(0, 1); + } + if ( area.name.size() == 1 ) + area.text = area.name; + if ( area.name == Joystick) + joystick = i; + area.activeWhenClosed = closedAreas.contains(area.name) + || area.keyCode == Qt::Key_Flip; // must be to work + area.toggleArea = toggleAreas.contains(area.name); + area.toggleActiveArea = toggleActiveAreas.contains(area.name); + if (area.toggleArea) + toggleAreaList += i; + i++; + } + } + } + if (i != nareas) { + qWarning() << DeviceSkin::tr("Mismatch in number of areas, expected %1, got %2.") + .arg(nareas).arg(i); + } + if (debugDeviceSkin) + qDebug() << *this; + return true; +} + +// --------- CursorWindow declaration + +namespace qvfb_internal { + +class CursorWindow : public QWidget +{ +public: + explicit CursorWindow(const QImage &cursor, QPoint hot, QWidget *sk); + + void setView(QWidget*); + void setPos(QPoint); + bool handleMouseEvent(QEvent *ev); + +protected: + bool event(QEvent *) override; + bool eventFilter(QObject*, QEvent *) override; + +private: + QWidget *mouseRecipient; + QWidget *m_view; + QWidget *skin; + QPoint hotspot; +}; +} + +// --------- Skin + +DeviceSkin::DeviceSkin(const DeviceSkinParameters ¶meters, QWidget *p ) : + QWidget(p), + m_parameters(parameters), + buttonRegions(parameters.buttonAreas.size(), QRegion()), + parent(p), + t_skinkey(new QTimer(this)), + t_parentmove(new QTimer(this)) +{ + Q_ASSERT(p); + setMouseTracking(true); + setAttribute(Qt::WA_NoSystemBackground); + + setZoom(1.0); + connect(t_skinkey, &QTimer::timeout, this, &DeviceSkin::skinKeyRepeat ); + t_parentmove->setSingleShot( true ); + connect(t_parentmove, &QTimer::timeout, this, &DeviceSkin::moveParent ); +} + +void DeviceSkin::skinKeyRepeat() +{ + if ( m_view ) { + const DeviceSkinButtonArea &area = m_parameters.buttonAreas[buttonIndex]; + emit skinKeyReleaseEvent(area.keyCode,area.text, true); + emit skinKeyPressEvent(area.keyCode, area.text, true); + t_skinkey->start(key_repeat_period); + } +} + +void DeviceSkin::calcRegions() +{ + const int numAreas = m_parameters.buttonAreas.size(); + for (int i=0; isetMask( skinImageUp.mask() ); + parent->setFixedSize( skinImageUp.size() ); + + delete cursorw; + cursorw = 0; + if (hasCursorImage) { + cursorw = new qvfb_internal::CursorWindow(m_parameters.skinCursor, m_parameters.cursorHot, this); + if (m_view) + cursorw->setView(m_view); + } +} + +DeviceSkin::~DeviceSkin( ) +{ + delete cursorw; +} + +void DeviceSkin::setTransform(const QTransform &wm) +{ + transform = QImage::trueMatrix(wm,m_parameters.skinImageUp.width(),m_parameters.skinImageUp.height()); + calcRegions(); + loadImages(); + if ( m_view ) { + QPoint p = transform.map(QPolygon(m_parameters.screenRect)).boundingRect().topLeft(); + m_view->move(p); + } + updateSecondaryScreen(); +} + +void DeviceSkin::setZoom( double z ) +{ + setTransform(QTransform().scale(z,z)); +} + +void DeviceSkin::updateSecondaryScreen() +{ + if (!m_secondaryView) + return; + if (flipped_open) { + if (m_parameters.backScreenRect.isNull()) { + m_secondaryView->hide(); + } else { + m_secondaryView->move(transform.map(QPolygon(m_parameters.backScreenRect)).boundingRect().topLeft()); + m_secondaryView->show(); + } + } else { + if (m_parameters.closedScreenRect.isNull()) { + m_secondaryView->hide(); + } else { + m_secondaryView->move(transform.map(QPolygon(m_parameters.closedScreenRect)).boundingRect().topLeft()); + m_secondaryView->show(); + } + } +} + +void DeviceSkin::setView( QWidget *v ) +{ + m_view = v; + m_view->setFocus(); + m_view->move(transform.map(QPolygon(m_parameters.screenRect)).boundingRect().topLeft()); + if ( cursorw ) + cursorw->setView(v); +} + +void DeviceSkin::setSecondaryView( QWidget *v ) +{ + m_secondaryView = v; + updateSecondaryScreen(); +} + +void DeviceSkin::paintEvent( QPaintEvent *) +{ + QPainter p( this ); + if ( flipped_open ) { + p.drawPixmap(0, 0, skinImageUp); + } else { + p.drawPixmap(0, 0, skinImageClosed); + } + QList toDraw; + if ( buttonPressed == true ) { + toDraw += buttonIndex; + } + for (int toggle : std::as_const(m_parameters.toggleAreaList)) { + const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[toggle]; + if (flipped_open || ba.activeWhenClosed) { + if (ba.toggleArea && ba.toggleActiveArea) + toDraw += toggle; + } + } + for (int button : std::as_const(toDraw)) { + const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[button]; + const QRect r = buttonRegions[button].boundingRect(); + if ( ba.area.size() > 2 ) + p.setClipRegion(buttonRegions[button]); + p.drawPixmap( r.topLeft(), skinImageDown, r); + } +} + +void DeviceSkin::mousePressEvent( QMouseEvent *e ) +{ + if (e->button() == Qt::RightButton) { + emit popupMenu(); + } else { + buttonPressed = false; + + onjoyrelease = -1; + const int numAreas = m_parameters.buttonAreas.size(); + for (int i = 0; i < numAreas ; i++) { + const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[i]; + if ( buttonRegions[i].contains( e->position().toPoint() ) ) { + if ( flipped_open || ba.activeWhenClosed ) { + if ( m_parameters.joystick == i ) { + joydown = true; + } else { + if ( joydown ) + onjoyrelease = i; + else + startPress(i); + break; + if (debugDeviceSkin)// Debug message to be sure we are clicking the right areas + qDebug()<< m_parameters.buttonAreas[i].name << " clicked"; + } + } + } + } + clickPos = e->position().toPoint(); +// This is handy for finding the areas to define rectangles for new skins + if (debugDeviceSkin) + qDebug()<< "Clicked in " << e->position().toPoint().x() << ',' << e->position().toPoint().y(); + clickPos = e->position().toPoint(); + } +} + +void DeviceSkin::flip(bool open) +{ + if ( flipped_open == open ) + return; + if ( open ) { + parent->setMask(skinImageUp.mask()); + emit skinKeyReleaseEvent(Qt::Key(Qt::Key_Flip), QString(), false); + } else { + parent->setMask(skinImageClosed.mask()); + emit skinKeyPressEvent(Qt::Key(Qt::Key_Flip), QString(), false); + } + flipped_open = open; + updateSecondaryScreen(); + repaint(); +} + +void DeviceSkin::startPress(int i) +{ + buttonPressed = true; + buttonIndex = i; + if (m_view) { + const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[buttonIndex]; + if (ba.keyCode == Qt::Key_Flip) { + flip(!flipped_open); + } else if (ba.toggleArea) { + bool active = !ba.toggleActiveArea; + const_cast(ba).toggleActiveArea = active; + if (active) + emit skinKeyPressEvent(ba.keyCode, ba.text, false); + else + emit skinKeyReleaseEvent(ba.keyCode, ba.text, false); + } else { + emit skinKeyPressEvent(ba.keyCode, ba.text, false); + t_skinkey->start(key_repeat_delay); + } + repaint(buttonRegions[buttonIndex].boundingRect()); + } +} + +void DeviceSkin::endPress() +{ + const DeviceSkinButtonArea &ba = m_parameters.buttonAreas[buttonIndex]; + if (m_view && ba.keyCode != Qt::Key_Flip && !ba.toggleArea ) + emit skinKeyReleaseEvent(ba.keyCode, ba.text, false); + t_skinkey->stop(); + buttonPressed = false; + repaint( buttonRegions[buttonIndex].boundingRect() ); +} + +void DeviceSkin::mouseMoveEvent( QMouseEvent *e ) +{ + if ( e->buttons() & Qt::LeftButton ) { + const int joystick = m_parameters.joystick; + QPoint newpos = e->globalPosition().toPoint() - clickPos; + if (joydown) { + int k1=0, k2=0; + if (newpos.x() < -joydistance) { + k1 = joystick+1; + } else if (newpos.x() > +joydistance) { + k1 = joystick+3; + } + if (newpos.y() < -joydistance) { + k2 = joystick+2; + } else if (newpos.y() > +joydistance) { + k2 = joystick+4; + } + if (k1 || k2) { + if (!buttonPressed) { + onjoyrelease = -1; + if (k1 && k2) { + startPress(k2); + endPress(); + } + startPress(k1 ? k1 : k2); + } + } else if (buttonPressed) { + endPress(); + } + } else if (buttonPressed == false) { + parentpos = newpos; + if (!t_parentmove->isActive()) + t_parentmove->start(50); + } + } + if ( cursorw ) + cursorw->setPos(e->globalPosition().toPoint()); +} + +void DeviceSkin::moveParent() +{ + parent->move( parentpos ); +} + +void DeviceSkin::mouseReleaseEvent( QMouseEvent * ) +{ + if ( buttonPressed ) + endPress(); + if ( joydown ) { + joydown = false; + if (onjoyrelease >= 0) { + startPress(onjoyrelease); + endPress(); + } + } +} + +bool DeviceSkin::hasCursor() const +{ + return !skinCursor.isNull(); +} + +// ------------------ CursorWindow implementation + +namespace qvfb_internal { + +bool CursorWindow::eventFilter( QObject *, QEvent *ev) +{ + handleMouseEvent(ev); + return false; +} + +bool CursorWindow::event( QEvent *ev ) +{ + if (handleMouseEvent(ev)) + return true; + return QWidget::event(ev); +} + +bool CursorWindow::handleMouseEvent(QEvent *ev) +{ + bool handledEvent = false; + static int inhere=0; + if ( !inhere ) { + inhere++; + if (m_view) { + if (ev->type() >= QEvent::MouseButtonPress && ev->type() <= QEvent::MouseMove) { + QMouseEvent *e = (QMouseEvent*)ev; + QPoint gp = e->globalPosition().toPoint(); + QPoint vp = m_view->mapFromGlobal(gp); + QPoint sp = skin->mapFromGlobal(gp); + if (e->type() == QEvent::MouseButtonPress || e->type() == QEvent::MouseButtonDblClick) { + if (m_view->rect().contains(vp)) + mouseRecipient = m_view; + else if (skin->parentWidget()->geometry().contains(gp)) + mouseRecipient = skin; + else + mouseRecipient = 0; + } + if (mouseRecipient) { + setPos(gp); + QMouseEvent me(e->type(),mouseRecipient==skin ? sp : vp,gp,e->button(),e->buttons(),e->modifiers()); + QApplication::sendEvent(mouseRecipient, &me); + } else if (!skin->parentWidget()->geometry().contains(gp)) { + hide(); + } else { + setPos(gp); + } + if (e->type() == QEvent::MouseButtonRelease) + mouseRecipient = 0; + handledEvent = true; + } + } + inhere--; + } + return handledEvent; +} + +void CursorWindow::setView(QWidget* v) +{ + if ( m_view ) { + m_view->removeEventFilter(this); + m_view->removeEventFilter(this); + } + m_view = v; + m_view->installEventFilter(this); + m_view->installEventFilter(this); + mouseRecipient = 0; +} + +CursorWindow::CursorWindow(const QImage &img, QPoint hot, QWidget* sk) + : QWidget(0), + m_view(0), + skin(sk), + hotspot(hot) +{ + setWindowFlags( Qt::FramelessWindowHint ); + mouseRecipient = 0; + setMouseTracking(true); +#ifndef QT_NO_CURSOR + setCursor(Qt::BlankCursor); +#endif + QPixmap p; + p = QPixmap::fromImage(img); + if (!p.mask()) { + QBitmap bm = img.hasAlphaChannel() ? QBitmap::fromImage(img.createAlphaMask()) + : QBitmap::fromImage(img.createHeuristicMask()); + p.setMask(bm); + } + QPalette palette; + palette.setBrush(backgroundRole(), QBrush(p)); + setPalette(palette); + setFixedSize( p.size() ); + if ( !p.mask().isNull() ) + setMask(p.mask()); +} + +void CursorWindow::setPos(QPoint p) +{ + move(p-hotspot); + show(); + raise(); +} +} + +#ifdef TEST_SKIN + +int main(int argc,char *argv[]) +{ + if (argc < 1) + return 1; + const QString skinFile = QString::fromUtf8(argv[1]); + QApplication app(argc,argv); + QMainWindow mw; + + DeviceSkinParameters params; + QString errorMessage; + if (!params.read(skinFile, DeviceSkinParameters::ReadAll, &errorMessage)) { + qWarning() << errorMessage; + return 1; + } + DeviceSkin ds(params, &mw); + // View Dialog + QDialog *dialog = new QDialog(); + QHBoxLayout *dialogLayout = new QHBoxLayout(); + dialog->setLayout(dialogLayout); + QDialogButtonBox *dialogButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); + QObject::connect(dialogButtonBox, &QDialogButtonBox::rejected, dialog, &QDialog::reject); + QObject::connect(dialogButtonBox, &QDialogButtonBox::accepted, dialog, &QDialog::accept); + dialogLayout->addWidget(dialogButtonBox); + dialog->setFixedSize(params.screenSize()); + dialog->setParent(&ds, Qt::SubWindow); + dialog->setAutoFillBackground(true); + ds.setView(dialog); + + QObject::connect(&ds, &DeviceSkin::popupMenu, &mw, &QWidget::close); + QObject::connect(&ds, &DeviceSkin::skinKeyPressEvent, &mw, &QWidget::close); + mw.show(); + return app.exec(); +} + +#endif + +QT_END_NAMESPACE + diff --git a/src/shared/deviceskin/deviceskin_p.h b/src/shared/deviceskin/deviceskin_p.h new file mode 100644 index 00000000000..618c92dd035 --- /dev/null +++ b/src/shared/deviceskin/deviceskin_p.h @@ -0,0 +1,148 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef SKIN_H +#define SKIN_H + +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qvfb_internal { + class CursorWindow; +} + +class QTextStream; + +// ------- Button Area +struct DeviceSkinButtonArea { + QString name; + int keyCode{0}; + QPolygon area; + QString text; + bool activeWhenClosed{false}; + bool toggleArea{false}; + bool toggleActiveArea{false}; +}; + +// -------- Parameters +struct DeviceSkinParameters { + enum ReadMode { ReadAll, ReadSizeOnly }; + bool read(const QString &skinDirectory, ReadMode rm, QString *errorMessage); + bool read(QTextStream &ts, ReadMode rm, QString *errorMessage); + + QSize screenSize() const { return screenRect.size(); } + QSize secondaryScreenSize() const; + bool hasSecondaryScreen() const; + + QString skinImageUpFileName; + QString skinImageDownFileName; + QString skinImageClosedFileName; + QString skinCursorFileName; + + QImage skinImageUp; + QImage skinImageDown; + QImage skinImageClosed; + QImage skinCursor; + + QRect screenRect; + QRect backScreenRect; + QRect closedScreenRect; + int screenDepth; + QPoint cursorHot; + QList buttonAreas; + QList toggleAreaList; + + int joystick; + QString prefix; + bool hasMouseHover; +}; + +// --------- Skin Widget +class DeviceSkin : public QWidget +{ + Q_OBJECT +public: + explicit DeviceSkin(const DeviceSkinParameters ¶meters, QWidget *p ); + ~DeviceSkin( ); + + QWidget *view() const { return m_view; } + void setView( QWidget *v ); + + QWidget *secondaryView() const { return m_secondaryView; } + void setSecondaryView( QWidget *v ); + + void setZoom( double ); + void setTransform(const QTransform &); + + bool hasCursor() const; + + QString prefix() const {return m_parameters.prefix;} + +signals: + void popupMenu(); + void skinKeyPressEvent(int code, const QString& text, bool autorep); + void skinKeyReleaseEvent(int code, const QString& text, bool autorep); + +protected slots: + void skinKeyRepeat(); + void moveParent(); + +protected: + void paintEvent(QPaintEvent *) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *) override; + +private: + void calcRegions(); + void flip(bool open); + void updateSecondaryScreen(); + void loadImages(); + void startPress(int); + void endPress(); + + const DeviceSkinParameters m_parameters; + QList buttonRegions; + QPixmap skinImageUp; + QPixmap skinImageDown; + QPixmap skinImageClosed; + QPixmap skinCursor; + QWidget *parent; + QWidget *m_view = nullptr; + QWidget *m_secondaryView = nullptr; + QPoint parentpos; + QPoint clickPos; + bool buttonPressed = false; + int buttonIndex = 0; + QTransform transform; + qvfb_internal::CursorWindow *cursorw = nullptr; + + bool joydown = false; + QTimer *t_skinkey; + QTimer *t_parentmove; + int onjoyrelease = 0; + + bool flipped_open = true; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone.skin b/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone.skin new file mode 100644 index 00000000000..976d9e90a33 --- /dev/null +++ b/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone.skin @@ -0,0 +1,30 @@ +[SkinFile] +Up=ClamshellPhone1-5.png +Down=ClamshellPhone1-5-pressed.png +Closed=ClamshellPhone1-5-closed.png +Screen=72 84 176 208 +Areas=22 +HasMouseHover=false + +"Power" 0x0100000a 205 563 249 586 +"1" 0x0031 62 414 119 438 +"2" 0x0032 130 414 189 438 +"3" 0x0033 198 413 257 438 +"4" 0x0034 54 444 117 470 +"5" 0x0035 128 444 189 471 +"6" 0x0036 202 444 264 471 +"7" 0x0037 47 477 113 507 +"8" 0x0038 126 477 190 507 +"9" 0x0039 205 478 270 509 +"*" 0x002a 39 515 110 552 +"0" 0x0030 122 515 195 553 +"#" 0x0023 207 516 280 553 +"Context1" 0x01100000 137 360 108 383 123 410 90 409 60 387 63 378 100 362 +"Back" 0x01000061 184 361 206 376 213 387 197 410 226 410 256 392 258 381 244 369 +"Backspace" 0x01000003 68 563 113 587 +"Select" 0x01010000 160 391 172 390 181 386 184 381 180 377 173 373 165 372 155 372 145 375 138 378 136 382 138 387 147 390 +"Left" 0x1000012 141 390 136 385 136 381 143 375 132 371 120 380 121 393 129 401 +"Down" 0x1000015 143 389 130 402 162 412 191 404 175 390 +"Right" 0x1000014 186 370 176 375 184 382 182 387 175 390 190 404 201 396 202 375 +"Up" 0x1000013 133 370 143 374 176 374 185 370 169 362 149 362 +"Flip" 0x01100006 98 325 225 353 diff --git a/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5-closed.png b/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5-closed.png new file mode 100644 index 00000000000..88ba3a1472c Binary files /dev/null and b/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5-closed.png differ diff --git a/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5-pressed.png b/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5-pressed.png new file mode 100644 index 00000000000..971cdef6497 Binary files /dev/null and b/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5-pressed.png differ diff --git a/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5.png b/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5.png new file mode 100644 index 00000000000..f3550ee7d78 Binary files /dev/null and b/src/shared/deviceskin/skins/ClamshellPhone.skin/ClamshellPhone1-5.png differ diff --git a/src/shared/deviceskin/skins/ClamshellPhone.skin/defaultbuttons.conf b/src/shared/deviceskin/skins/ClamshellPhone.skin/defaultbuttons.conf new file mode 100644 index 00000000000..e349dbc1f21 --- /dev/null +++ b/src/shared/deviceskin/skins/ClamshellPhone.skin/defaultbuttons.conf @@ -0,0 +1,78 @@ +[Translation] +File=QtopiaDefaults +Context=Buttons +[Menu] +Rows=4 +Columns=3 +Map=123456789*0# +Default=5 +1=Applications/camera.desktop +2=Applications/datebook.desktop +3=Applications +4=Applications/qtmail.desktop +5=Applications/addressbook.desktop +6=Games +7=Settings/Beaming.desktop +8=Applications/simapp.desktop,Applications/calculator.desktop +9=Settings +*=Applications/mediarecorder.desktop +0=Applications/todolist.desktop +#=Documents +Animator=Bounce +AnimatorBackground=Radial +[SoftKeys] +Count=3 +Key0=Context1 +Key1=Select +Key2=Back +[SystemButtons] +Count=5 +Key0=Context1 +Key1=Select +Key2=Back +Key3=Flip +Key4=Backspace +[TextButtons] +Buttons=0123456789*# +Hold0='0 +Hold1='1 +Hold2='2 +Hold3='3 +Hold4='4 +Hold5='5 +Hold6='6 +Hold7='7 +Hold8='8 +Hold9='9 +Hold*=symbol +Hold#=mode +Tap0=space +Tap1="\".,'?!-@:1" +Tap2="\"a\xe4\xe5\xe6\xe0\xe1\xe2\x62\x63\xe7\x32" +Tap3="\"de\xe8\xe9\xea\x66\x33" +Tap4="\"ghi\xec\xed\xee\x34" +Tap5="\"jkl5" +Tap6="\"mn\xf1o\xf6\xf8\xf2\xf3\x36" +Tap7="\"pqrs\xdf\x37" +Tap8="\"tu\xfc\xf9\xfav8" +Tap9="\"wxyz9" +Tap*=modify +Tap#=shift +[LocaleTextButtons] +Buttons=23456789 +Tap2[]='abc +Tap3[]='def +Tap4[]='ghi +Tap5[]='jkl +Tap6[]='mno +Tap7[]='pqrs +Tap8[]='tuv +Tap9[]='wxyz +[PhoneTextButtons] +Buttons=*# +Tap*='*+pw +Hold*=+ +Tap#=# +Hold#=mode +[Device] +PrimaryInput=Keypad diff --git a/src/shared/deviceskin/skins/PortableMedia.skin/PortableMedia.skin b/src/shared/deviceskin/skins/PortableMedia.skin/PortableMedia.skin new file mode 100644 index 00000000000..b76e5cfaddb --- /dev/null +++ b/src/shared/deviceskin/skins/PortableMedia.skin/PortableMedia.skin @@ -0,0 +1,14 @@ +[SkinFile] +Up=portablemedia.png +Down=portablemedia-pressed.png +Screen=18 20 480 272 +Areas=7 +HasMouseHover=false + +"Context1" 0x01100000 530 192 565 223 +"Back" 0x01000061 530 138 565 173 +"Select" 0x01010000 530 65 565 98 +"Left" 0x1000012 529 67 519 57 511 65 511 104 519 110 529 100 529 98 +"Down" 0x1000015 530 102 520 111 529 120 569 120 577 114 566 103 564 103 +"Right" 0x1000014 565 65 576 52 585 67 585 102 578 113 567 104 +"Up" 0x1000013 530 65 519 55 528 48 567 48 573 51 564 66 diff --git a/src/shared/deviceskin/skins/PortableMedia.skin/defaultbuttons.conf b/src/shared/deviceskin/skins/PortableMedia.skin/defaultbuttons.conf new file mode 100644 index 00000000000..514e8816ea0 --- /dev/null +++ b/src/shared/deviceskin/skins/PortableMedia.skin/defaultbuttons.conf @@ -0,0 +1,23 @@ +[Translation] +File=QtopiaDefaults +Context=Buttons +[Menu] +Map=123 +Default=1 +1=Applications/mediaplayer.desktop +2=Applications/photoedit.desktop +3=Settings +Animator=Bounce +AnimatorBackground=Radial +[SoftKeys] +Count=3 +Key0=Context1 +Key1=Select +Key2=Back +[SystemButtons] +Count=5 +Key0=Context1 +Key1=Select +Key2=Back +[Device] +PrimaryInput=Keypad diff --git a/src/shared/deviceskin/skins/PortableMedia.skin/portablemedia-pressed.png b/src/shared/deviceskin/skins/PortableMedia.skin/portablemedia-pressed.png new file mode 100644 index 00000000000..730e76287ae Binary files /dev/null and b/src/shared/deviceskin/skins/PortableMedia.skin/portablemedia-pressed.png differ diff --git a/src/shared/deviceskin/skins/PortableMedia.skin/portablemedia.png b/src/shared/deviceskin/skins/PortableMedia.skin/portablemedia.png new file mode 100644 index 00000000000..e44cbe16053 Binary files /dev/null and b/src/shared/deviceskin/skins/PortableMedia.skin/portablemedia.png differ diff --git a/src/shared/deviceskin/skins/PortableMedia.skin/portablemedia.xcf b/src/shared/deviceskin/skins/PortableMedia.skin/portablemedia.xcf new file mode 100644 index 00000000000..127e07c8eab Binary files /dev/null and b/src/shared/deviceskin/skins/PortableMedia.skin/portablemedia.xcf differ diff --git a/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar-down.png b/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar-down.png new file mode 100644 index 00000000000..89d40cbed3f Binary files /dev/null and b/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar-down.png differ diff --git a/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar.png b/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar.png new file mode 100644 index 00000000000..0d0e598b962 Binary files /dev/null and b/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar.png differ diff --git a/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar.skin b/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar.skin new file mode 100644 index 00000000000..db16782b4d8 --- /dev/null +++ b/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/S60-QVGA-Candybar.skin @@ -0,0 +1,15 @@ +[SkinFile] +Up=S60-QVGA-Candybar.png +Down=S60-QVGA-Candybar-down.png +Screen=61 93 240 320 +Areas=7 +HasMouseHover=false + + +"Context1" 0x01100000 54 469 151 469 140 483 88 485 81 496 54 498 +"Back" 0x01000061 211 468 307 467 307 498 278 497 219 486 +"Select" 0x01010000 165 491 196 522 +"Left" 0x1000012 149 474 166 492 163 519 143 538 142 481 +"Down" 0x1000015 164 521 195 522 212 539 204 545 154 544 145 536 +"Right" 0x1000014 214 475 219 487 219 528 212 539 196 522 197 492 +"Up" 0x1000013 150 474 156 467 209 467 213 476 197 489 165 489 diff --git a/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/defaultbuttons.conf b/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/defaultbuttons.conf new file mode 100644 index 00000000000..e349dbc1f21 --- /dev/null +++ b/src/shared/deviceskin/skins/S60-QVGA-Candybar.skin/defaultbuttons.conf @@ -0,0 +1,78 @@ +[Translation] +File=QtopiaDefaults +Context=Buttons +[Menu] +Rows=4 +Columns=3 +Map=123456789*0# +Default=5 +1=Applications/camera.desktop +2=Applications/datebook.desktop +3=Applications +4=Applications/qtmail.desktop +5=Applications/addressbook.desktop +6=Games +7=Settings/Beaming.desktop +8=Applications/simapp.desktop,Applications/calculator.desktop +9=Settings +*=Applications/mediarecorder.desktop +0=Applications/todolist.desktop +#=Documents +Animator=Bounce +AnimatorBackground=Radial +[SoftKeys] +Count=3 +Key0=Context1 +Key1=Select +Key2=Back +[SystemButtons] +Count=5 +Key0=Context1 +Key1=Select +Key2=Back +Key3=Flip +Key4=Backspace +[TextButtons] +Buttons=0123456789*# +Hold0='0 +Hold1='1 +Hold2='2 +Hold3='3 +Hold4='4 +Hold5='5 +Hold6='6 +Hold7='7 +Hold8='8 +Hold9='9 +Hold*=symbol +Hold#=mode +Tap0=space +Tap1="\".,'?!-@:1" +Tap2="\"a\xe4\xe5\xe6\xe0\xe1\xe2\x62\x63\xe7\x32" +Tap3="\"de\xe8\xe9\xea\x66\x33" +Tap4="\"ghi\xec\xed\xee\x34" +Tap5="\"jkl5" +Tap6="\"mn\xf1o\xf6\xf8\xf2\xf3\x36" +Tap7="\"pqrs\xdf\x37" +Tap8="\"tu\xfc\xf9\xfav8" +Tap9="\"wxyz9" +Tap*=modify +Tap#=shift +[LocaleTextButtons] +Buttons=23456789 +Tap2[]='abc +Tap3[]='def +Tap4[]='ghi +Tap5[]='jkl +Tap6[]='mno +Tap7[]='pqrs +Tap8[]='tuv +Tap9[]='wxyz +[PhoneTextButtons] +Buttons=*# +Tap*='*+pw +Hold*=+ +Tap#=# +Hold#=mode +[Device] +PrimaryInput=Keypad diff --git a/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen-down.png b/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen-down.png new file mode 100644 index 00000000000..7253e38012f Binary files /dev/null and b/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen-down.png differ diff --git a/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen.png b/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen.png new file mode 100644 index 00000000000..675563e5cf5 Binary files /dev/null and b/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen.png differ diff --git a/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen.skin b/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen.skin new file mode 100644 index 00000000000..bf5824e30c2 --- /dev/null +++ b/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/S60-nHD-Touchscreen.skin @@ -0,0 +1,10 @@ +[SkinFile] +Up=S60-nHD-Touchscreen.png +Down=S60-nHD-Touchscreen-down.png +Screen=53 183 360 640 +Areas=3 +HasMouseHover=false + +"Call" 0x01100004 76 874 171 899 +"Hangup" 0x01100005 300 876 393 899 +"Home" 0x1000010 174 878 298 899 diff --git a/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/defaultbuttons.conf b/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/defaultbuttons.conf new file mode 100644 index 00000000000..6665125c904 --- /dev/null +++ b/src/shared/deviceskin/skins/S60-nHD-Touchscreen.skin/defaultbuttons.conf @@ -0,0 +1,53 @@ +[Translation] +File=QtopiaDefaults +Context=Buttons +[Menu] +Rows=4 +Columns=3 +Map=123456789*0# +Default=5 +1=Applications/camera.desktop +2=Applications/mediaplayer.desktop +3=Applications/simapp.desktop,Applications/calculator.desktop +4=Applications/qtmail.desktop +5=Applications/addressbook.desktop +6=Applications/datebook.desktop +7=Games +8=Settings/beaming.desktop +9=Applications/todolist.desktop +*=Settings +0=Applications +#=Documents +Animator=Bounce +AnimatorBackground=Radial +[SoftKeys] +Count=3 +Key0=Context1 +Key1=Select +Key2=Back +[SystemButtons] +Count=5 +Key0=Context1 +Key1=Back +Key2=Select +Key3=Call +Key4=Hangup +[Device] +PrimaryInput=Touchscreen +[Button] +Count=2 +[Button0] +Name[]=Home Button +Key=Home +PressedActionMappable=0 +PressedActionService=TaskManager +PressedActionMessage=multitask() +HeldActionMappable=0 +HeldActionService=TaskManager +HeldActionMessage=showRunningTasks() +[Button1] +Name=Power Button +Key=Hangup +HeldActionService=Launcher +HeldActionMessage=execute(QString) +HeldActionArgs=@ByteArray(\0\0\0\x1\0\0\0\n\0\0\0\0\x10\0s\0h\0u\0t\0\x64\0o\0w\0n) diff --git a/src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone-pressed.png b/src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone-pressed.png new file mode 100644 index 00000000000..d0db2ed3520 Binary files /dev/null and b/src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone-pressed.png differ diff --git a/src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone.png b/src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone.png new file mode 100644 index 00000000000..e6ac5a0f386 Binary files /dev/null and b/src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone.png differ diff --git a/src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone.skin b/src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone.skin new file mode 100644 index 00000000000..2f44c5aa88f --- /dev/null +++ b/src/shared/deviceskin/skins/SmartPhone.skin/SmartPhone.skin @@ -0,0 +1,28 @@ +[SkinFile] +Up=SmartPhone.png +Down=SmartPhone-pressed.png +Screen=90 107 176 208 +Areas=21 +HasMouseHover=false + +"1" 0x0031 138 459 149 473 142 483 120 484 102 477 95 464 99 457 121 454 +"2" 0x0032 153 467 202 492 +"3" 0x0033 258 457 260 470 243 483 215 484 207 474 218 460 248 453 +"4" 0x0034 138 492 149 506 142 516 120 517 102 510 95 497 99 490 121 487 +"5" 0x0035 153 499 202 524 +"6" 0x0036 258 489 260 502 243 515 215 516 207 506 218 492 248 485 +"7" 0x0037 138 524 149 538 142 548 120 549 102 542 95 529 99 522 121 519 +"8" 0x0038 153 531 202 556 +"9" 0x0039 258 521 260 534 243 547 215 548 207 538 218 524 248 517 +"*" 0x002a 138 556 149 570 142 580 120 581 102 574 95 561 99 554 121 551 +"0" 0x0030 153 564 202 589 +"#" 0x0023 258 554 260 567 243 580 215 581 207 571 218 557 248 550 +"Call" 0x01100004 88 395 130 439 +"Hangup" 0x01100005 227 395 269 439 +"Context1" 0x01100000 145 321 134 333 132 361 134 374 110 343 109 318 +"Back" 0x01000061 249 322 240 354 219 373 223 344 216 325 208 318 +"Select" 0x01010000 160 338 195 371 +"Left" 0x1000012 159 338 149 328 141 336 141 373 149 381 159 371 159 369 +"Down" 0x1000015 160 373 150 382 159 391 199 391 207 385 196 374 194 374 +"Right" 0x1000014 195 336 206 323 215 338 215 373 208 384 197 375 +"Up" 0x1000013 160 336 149 326 158 319 197 319 203 322 194 337 diff --git a/src/shared/deviceskin/skins/SmartPhone.skin/defaultbuttons.conf b/src/shared/deviceskin/skins/SmartPhone.skin/defaultbuttons.conf new file mode 100644 index 00000000000..1103350557c --- /dev/null +++ b/src/shared/deviceskin/skins/SmartPhone.skin/defaultbuttons.conf @@ -0,0 +1,78 @@ +[Translation] +File=QtopiaDefaults +Context=Buttons +[Menu] +Rows=4 +Columns=3 +Map=123456789*0# +Default=5 +1=Applications/camera.desktop +2=Applications/datebook.desktop +3=Applications +4=Applications/qtmail.desktop +5=Applications/addressbook.desktop +6=Games +7=Settings/Beaming.desktop +8=Applications/simapp.desktop,Applications/calculator.desktop +9=Settings +*=Applications/mediarecorder.desktop +0=Applications/todolist.desktop +#=Documents +Animator=Bounce +AnimatorBackground=Radial +[SoftKeys] +Count=3 +Key0=Context1 +Key1=Select +Key2=Back +[SystemButtons] +Count=5 +Key0=Context1 +Key1=Select +Key2=Back +Key3=Call +Key4=Hangup +[TextButtons] +Buttons=0123456789*# +Hold0='0 +Hold1='1 +Hold2='2 +Hold3='3 +Hold4='4 +Hold5='5 +Hold6='6 +Hold7='7 +Hold8='8 +Hold9='9 +Hold*=symbol +Hold#=mode +Tap0=space +Tap1="\".,'?!-@:1" +Tap2="\"a\xe4\xe5\xe6\xe0\xe1\xe2\x62\x63\xe7\x32" +Tap3="\"de\xe8\xe9\xea\x66\x33" +Tap4="\"ghi\xec\xed\xee\x34" +Tap5="\"jkl5" +Tap6="\"mn\xf1o\xf6\xf8\xf2\xf3\x36" +Tap7="\"pqrs\xdf\x37" +Tap8="\"tu\xfc\xf9\xfav8" +Tap9="\"wxyz9" +Tap*=modify +Tap#=shift +[LocaleTextButtons] +Buttons=23456789 +Tap2[]='abc +Tap3[]='def +Tap4[]='ghi +Tap5[]='jkl +Tap6[]='mno +Tap7[]='pqrs +Tap8[]='tuv +Tap9[]='wxyz +[PhoneTextButtons] +Buttons=*# +Tap*='*+pw +Hold*=+ +Tap#='# +Hold#=mode +[Device] +PrimaryInput=Keypad diff --git a/src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2-pressed.png b/src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2-pressed.png new file mode 100644 index 00000000000..d4eb5b0c86d Binary files /dev/null and b/src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2-pressed.png differ diff --git a/src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2.png b/src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2.png new file mode 100644 index 00000000000..48ccc1c5a62 Binary files /dev/null and b/src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2.png differ diff --git a/src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2.skin b/src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2.skin new file mode 100644 index 00000000000..d644c6e9b16 --- /dev/null +++ b/src/shared/deviceskin/skins/SmartPhone2.skin/SmartPhone2.skin @@ -0,0 +1,25 @@ +SmartPhone2.png SmartPhone2-pressed.png +90 107 +176 220 +21 +"Menu" 0x01100000 70 400 115 427 +"Backspace" 0x01000003 238 400 285 427 +"1" 0x0031 138 437 149 451 142 461 120 462 102 455 95 442 99 435 121 432 +"2" 0x0032 153 445 202 470 +"3" 0x0033 258 435 260 448 243 461 215 462 207 452 218 438 248 431 +"4" 0x0034 138 470 149 484 142 494 120 495 102 488 95 475 99 468 121 465 +"5" 0x0035 153 477 202 502 +"6" 0x0036 258 467 260 480 243 493 215 494 207 484 218 470 248 463 +"7" 0x0037 138 502 149 516 142 526 120 527 102 520 95 507 99 500 121 497 +"8" 0x0038 153 509 202 534 +"9" 0x0039 258 499 260 512 243 525 215 526 207 516 218 502 248 495 +"*" 0x002a 138 534 149 548 142 558 120 559 102 552 95 539 99 532 121 529 +"0" 0x0030 153 542 202 567 +"#" 0x0023 258 532 260 545 243 558 215 559 207 549 218 535 248 528 +"Yes" 0x01010001 91 343 141 393 +"No" 0x01010002 219 343 269 393 +"Select" 0x01010000 160 356 195 389 +"Left" 0x1000012 159 356 149 346 141 354 141 391 149 399 159 389 159 387 +"Down" 0x1000015 160 391 150 400 159 409 199 409 207 403 196 392 194 392 +"Right" 0x1000014 195 354 206 341 215 356 215 391 208 402 197 393 +"Up" 0x1000013 160 354 149 344 158 337 197 337 203 340 194 355 diff --git a/src/shared/deviceskin/skins/SmartPhone2.skin/defaultbuttons.conf b/src/shared/deviceskin/skins/SmartPhone2.skin/defaultbuttons.conf new file mode 100644 index 00000000000..b08320347e3 --- /dev/null +++ b/src/shared/deviceskin/skins/SmartPhone2.skin/defaultbuttons.conf @@ -0,0 +1,52 @@ +[Button] +[IMethod] +key_count = 5 +key_hold_action_1 = insertText +key_hold_action_2 = insertText +key_hold_action_3 = insertSymbol +key_hold_action_4 = changeMode +key_hold_arg_1 = 1 +key_hold_arg_2 = 0 +key_id_1 = 1 +key_id_2 = 0 +key_id_3 = * +key_id_4 = # +key_id_5 = * +key_mode_1 = Abc +key_mode_2 = Abc +key_mode_3 = Abc +key_mode_4 = Abc +key_mode_5 = Phone +key_tap_action_1 = insertText +key_tap_action_2 = insertSpace +key_tap_action_3 = modifyText +key_tap_action_4 = changeShift +key_tap_action_5 = insertText +key_tap_arg_1 = .,'?!-@:〓 +key_tap_arg_5 = *+pw +[Menu] +1 = Applications/camera.desktop +2 = Applications/datebook.desktop +3 = Games +4 = Applications/qtmail.desktop +5 = Applications/addressbook.desktop +6 = Settings +7 = Settings/Beaming.desktop +8 = Applications +9 = Documents +Columns = 3 +Default = 5 +Map = 123456789 +Rows = 3 +Animator=Bounce +AnimatorBackground=Radial +[SoftKeys] +[SystemButtons] +Count=5 +Key0=Yes +Key1=Select +Key2=No +Key3=Menu +Key4=Backspace +[Device] +PrimaryInput=Keypad diff --git a/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons-pressed.png b/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons-pressed.png new file mode 100644 index 00000000000..456a068eee8 Binary files /dev/null and b/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons-pressed.png differ diff --git a/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons.png b/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons.png new file mode 100644 index 00000000000..5ffbd6e4e7f Binary files /dev/null and b/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons.png differ diff --git a/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons.skin b/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons.skin new file mode 100644 index 00000000000..9afa67f30d1 --- /dev/null +++ b/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/SmartPhoneWithButtons.skin @@ -0,0 +1,31 @@ +[SkinFile] +Up=SmartPhoneWithButtons.png +Down=SmartPhoneWithButtons-pressed.png +Screen=90 107 176 208 +Areas=24 +HasMouseHover=false + +"1" 0x0031 138 459 149 473 142 483 120 484 102 477 95 464 99 457 121 454 +"2" 0x0032 153 467 202 492 +"3" 0x0033 258 457 260 470 243 483 215 484 207 474 218 460 248 453 +"4" 0x0034 138 492 149 506 142 516 120 517 102 510 95 497 99 490 121 487 +"5" 0x0035 153 499 202 524 +"6" 0x0036 258 489 260 502 243 515 215 516 207 506 218 492 248 485 +"7" 0x0037 138 524 149 538 142 548 120 549 102 542 95 529 99 522 121 519 +"8" 0x0038 153 531 202 556 +"9" 0x0039 258 521 260 534 243 547 215 548 207 538 218 524 248 517 +"*" 0x002a 138 556 149 570 142 580 120 581 102 574 95 561 99 554 121 551 +"0" 0x0030 153 564 202 589 +"#" 0x0023 258 554 260 567 243 580 215 581 207 571 218 557 248 550 +"Call" 0x01100004 88 395 130 439 +"Hangup" 0x01100005 227 395 269 439 +"Context1" 0x01100000 145 321 134 333 132 361 134 374 110 343 109 318 +"Back" 0x01000061 249 322 240 354 219 373 223 344 216 325 208 318 +"Select" 0x01010000 160 338 195 371 +"Left" 0x1000012 159 338 149 328 141 336 141 373 149 381 159 371 159 369 +"Down" 0x1000015 160 373 150 382 159 391 199 391 207 385 196 374 194 374 +"Right" 0x1000014 195 336 206 323 215 338 215 373 208 384 197 375 +"Up" 0x1000013 160 336 149 326 158 319 197 319 203 322 194 337 +"Home" 0x1000010 164 402 195 434 +"F1" 0x1000030 138 422 163 448 +"F2" 0x1000031 196 422 220 448 diff --git a/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/defaultbuttons.conf b/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/defaultbuttons.conf new file mode 100644 index 00000000000..ebd6926ae38 --- /dev/null +++ b/src/shared/deviceskin/skins/SmartPhoneWithButtons.skin/defaultbuttons.conf @@ -0,0 +1,103 @@ +[Translation] +File=QtopiaDefaults +Context=Buttons +[Button] +Count=3 +[Button0] +Name[]=Calendar Button +Key=F1 +PressedActionService=Calendar +PressedActionMessage=raiseToday() +HeldActionService=Calendar +HeldActionMessage=newEvent() +[Button1] +Name[]=Tasks Button +Key=F2 +PressedActionService=Tasks +PressedActionMessage=raise() +HeldActionService=Tasks +HeldActionMessage=newTask() +[Button2] +Name[]=Home Button +Key=Home +PressedActionMappable=0 +PressedActionService=TaskManager +PressedActionMessage=multitask() +HeldActionMappable=0 +HeldActionService=TaskManager +HeldActionMessage=showRunningTasks() +[Menu] +Rows=4 +Columns=3 +Map=123456789*0# +Default=5 +1=Applications/camera.desktop +2=Applications/datebook.desktop +3=Applications +4=Applications/qtmail.desktop +5=Applications/addressbook.desktop +6=Games +7=Settings/Beaming.desktop +8=Applications/simapp.desktop,Applications/calculator.desktop +9=Settings +*=Applications/mediarecorder.desktop +0=Applications/todolist.desktop +#=Documents +Animator=Bounce +AnimatorBackground=Radial +[SoftKeys] +Count=3 +Key0=Context1 +Key1=Select +Key2=Back +[SystemButtons] +Count=5 +Key0=Context1 +Key1=Select +Key2=Back +Key3=Call +Key4=Hangup +[TextButtons] +Buttons=0123456789*# +Hold0='0 +Hold1='1 +Hold2='2 +Hold3='3 +Hold4='4 +Hold5='5 +Hold6='6 +Hold7='7 +Hold8='8 +Hold9='9 +Hold*=symbol +Hold#=mode +Tap0=space +Tap1="\".,'?!-@:1" +Tap2="\"a\xe4\xe5\xe6\xe0\xe1\xe2\x62\x63\xe7\x32" +Tap3="\"de\xe8\xe9\xea\x66\x33" +Tap4="\"ghi\xec\xed\xee\x34" +Tap5="\"jkl5" +Tap6="\"mn\xf1o\xf6\xf8\xf2\xf3\x36" +Tap7="\"pqrs\xdf\x37" +Tap8="\"tu\xfc\xf9\xfav8" +Tap9="\"wxyz9" +Tap*=modify +Tap#=shift +[LocaleTextButtons] +Buttons=23456789 +Tap2[]='abc +Tap3[]='def +Tap4[]='ghi +Tap5[]='jkl +Tap6[]='mno +Tap7[]='pqrs +Tap8[]='tuv +Tap9[]='wxyz +[PhoneTextButtons] +Buttons=*# +Tap*='*+pw +Hold*=+ +Tap#='# +Hold#=mode +[Device] +PrimaryInput=Keypad diff --git a/src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone-pressed.png b/src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone-pressed.png new file mode 100644 index 00000000000..01acb86547c Binary files /dev/null and b/src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone-pressed.png differ diff --git a/src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone.png b/src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone.png new file mode 100644 index 00000000000..e90de0de467 Binary files /dev/null and b/src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone.png differ diff --git a/src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone.skin b/src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone.skin new file mode 100644 index 00000000000..3525d0aa253 --- /dev/null +++ b/src/shared/deviceskin/skins/TouchscreenPhone.skin/TouchscreenPhone.skin @@ -0,0 +1,16 @@ +[SkinFile] +Up=TouchscreenPhone.png +Down=TouchscreenPhone-pressed.png +Screen=90 107 176 208 +Areas=9 +HasMouseHover=false + +"Context1" 0x01100000 145 321 134 333 132 361 134 374 110 343 109 318 +"Call" 0x01100004 88 395 130 439 +"Hangup" 0x01100005 227 395 269 439 +"Back" 0x01000061 249 322 240 354 219 373 223 344 216 325 208 318 +"Left" 0x1000012 159 338 149 328 141 336 141 373 149 381 159 371 159 369 +"Down" 0x1000015 160 373 150 382 159 391 199 391 207 385 196 374 194 374 +"Right" 0x1000014 195 336 206 323 215 338 215 373 208 384 197 375 +"Up" 0x1000013 160 336 149 326 158 319 197 319 203 322 194 337 +"Select" 0x01010000 160 338 195 371 diff --git a/src/shared/deviceskin/skins/TouchscreenPhone.skin/defaultbuttons.conf b/src/shared/deviceskin/skins/TouchscreenPhone.skin/defaultbuttons.conf new file mode 100644 index 00000000000..a13dfdc3b27 --- /dev/null +++ b/src/shared/deviceskin/skins/TouchscreenPhone.skin/defaultbuttons.conf @@ -0,0 +1,45 @@ +[Translation] +File=QtopiaDefaults +Context=Buttons +[Menu] +Rows=4 +Columns=3 +Map=123456789*0# +Default=5 +1=Applications/camera.desktop +2=Applications/datebook.desktop +3=Applications +4=Applications/qtmail.desktop +5=Applications/addressbook.desktop +6=Games +7=Settings/Beaming.desktop +8=Applications/simapp.desktop,Applications/calculator.desktop +9=Settings +*=Applications/mediarecorder.desktop +0=Applications/todolist.desktop +#=Documents +Animator=Bounce +AnimatorBackground=Radial +[SoftKeys] +Count=3 +Key0=Context1 +Key1=Select +Key2=Back +[SystemButtons] +Count=5 +Key0=Context1 +Key1=Back +Key2=Select +Key3=Call +Key4=Hangup +[Button] +Count=1 +[Button0] +Name=Power Button +Key=Hangup +HeldActionService=Launcher +HeldActionMessage=execute(QString) +HeldActionArgs=@ByteArray(\0\0\0\x1\0\0\0\n\0\0\0\0\x10\0s\0h\0u\0t\0\x64\0o\0w\0n) +[Device] +PrimaryInput=Touchscreen + diff --git a/src/shared/findwidget/abstractfindwidget.cpp b/src/shared/findwidget/abstractfindwidget.cpp new file mode 100644 index 00000000000..2f4e49604bc --- /dev/null +++ b/src/shared/findwidget/abstractfindwidget.cpp @@ -0,0 +1,276 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/*! \class AbstractFindWidget + + \brief A search bar that is commonly added below a searchable widget. + + \internal + + This widget implements a search bar which becomes visible when the user + wants to start searching. It is a modern replacement for the commonly used + search dialog. It is usually placed below the target widget using a QVBoxLayout. + + The search is incremental and can be set to case sensitive or whole words + using buttons available on the search bar. + */ + +#include "abstractfindwidget_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QIcon afwCreateIconSet(const QString &name) +{ + QStringList candidates = QStringList() + << (QString::fromUtf8(":/qt-project.org/shared/images/") + name) +#ifdef Q_OS_MAC + << (QString::fromUtf8(":/qt-project.org/shared/images/mac/") + name); +#else + << (QString::fromUtf8(":/qt-project.org/shared/images/win/") + name); +#endif + + for (const QString &f : std::as_const(candidates)) { + if (QFile::exists(f)) + return QIcon(f); + } + + return QIcon(); +} + +/*! + Constructs an AbstractFindWidget. + + \a flags can change the layout and turn off certain features. + \a parent is passed to the QWidget constructor. + */ +AbstractFindWidget::AbstractFindWidget(FindFlags flags, QWidget *parent) + : QWidget(parent) +{ + QBoxLayout *topLayOut; + QBoxLayout *layOut; + if (flags & NarrowLayout) { + topLayOut = new QVBoxLayout(this); + layOut = new QHBoxLayout; + topLayOut->addLayout(layOut); + } else { + topLayOut = layOut = new QHBoxLayout(this); + } +#ifndef Q_OS_MAC + topLayOut->setSpacing(6); + topLayOut->setContentsMargins(QMargins()); +#endif + + m_toolClose = new QToolButton(this); + m_toolClose->setIcon(afwCreateIconSet("closetab.png"_L1)); + m_toolClose->setAutoRaise(true); + layOut->addWidget(m_toolClose); + connect(m_toolClose, &QAbstractButton::clicked, this, &AbstractFindWidget::deactivate); + + m_editFind = new QLineEdit(this); + layOut->addWidget(m_editFind); + connect(m_editFind, &QLineEdit::returnPressed, this, &AbstractFindWidget::findNext); + connect(m_editFind, &QLineEdit::textChanged, this, &AbstractFindWidget::findCurrentText); + connect(m_editFind, &QLineEdit::textChanged, this, &AbstractFindWidget::updateButtons); + + m_toolPrevious = new QToolButton(this); + m_toolPrevious->setAutoRaise(true); + m_toolPrevious->setText(tr("&Previous")); + m_toolPrevious->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + m_toolPrevious->setIcon(afwCreateIconSet("previous.png"_L1)); + layOut->addWidget(m_toolPrevious); + connect(m_toolPrevious, &QAbstractButton::clicked, this, &AbstractFindWidget::findPrevious); + + m_toolNext = new QToolButton(this); + m_toolNext->setAutoRaise(true); + m_toolNext->setText(tr("&Next")); + m_toolNext->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + m_toolNext->setIcon(afwCreateIconSet("next.png"_L1)); + layOut->addWidget(m_toolNext); + connect(m_toolNext, &QAbstractButton::clicked, this, &AbstractFindWidget::findNext); + + if (flags & NarrowLayout) { + QSizePolicy sp(QSizePolicy::Preferred, QSizePolicy::Fixed); + m_toolPrevious->setSizePolicy(sp); + m_toolPrevious->setMinimumWidth(m_toolPrevious->minimumSizeHint().height()); + m_toolNext->setSizePolicy(sp); + m_toolNext->setMinimumWidth(m_toolNext->minimumSizeHint().height()); + + QSpacerItem *spacerItem = + new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); + layOut->addItem(spacerItem); + + layOut = new QHBoxLayout; + topLayOut->addLayout(layOut); + } else { + m_editFind->setMinimumWidth(150); + } + + if (!(flags & NoCaseSensitive)) { + m_checkCase = new QCheckBox(tr("&Case sensitive"), this); + layOut->addWidget(m_checkCase); + connect(m_checkCase, &QAbstractButton::toggled, + this, &AbstractFindWidget::findCurrentText); + } else { + m_checkCase = 0; + } + + if (!(flags & NoWholeWords)) { + m_checkWholeWords = new QCheckBox(tr("Whole &words"), this); + layOut->addWidget(m_checkWholeWords); + connect(m_checkWholeWords, &QAbstractButton::toggled, + this, &AbstractFindWidget::findCurrentText); + } else { + m_checkWholeWords = 0; + } + + m_labelWrapped = new QLabel(this); + m_labelWrapped->setTextFormat(Qt::RichText); + m_labelWrapped->setAlignment( + Qt::AlignLeading | Qt::AlignLeft | Qt::AlignVCenter); + m_labelWrapped->setText( + tr("" + " Search wrapped")); + m_labelWrapped->hide(); + layOut->addWidget(m_labelWrapped); + + QSpacerItem *spacerItem = + new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum); + layOut->addItem(spacerItem); + + setMinimumWidth(minimumSizeHint().width()); + + updateButtons(); + hide(); +} + +/*! + Destroys the AbstractFindWidget. + */ +AbstractFindWidget::~AbstractFindWidget() = default; + +/*! + Returns the icon set to be used for the action that initiates a search. + */ +QIcon AbstractFindWidget::findIconSet() +{ + return afwCreateIconSet("searchfind.png"_L1); +} + +/*! + Creates an actions with standard icon and shortcut to activate the widget. + */ +QAction *AbstractFindWidget::createFindAction(QObject *parent) +{ + + auto result = new QAction(AbstractFindWidget::findIconSet(), + tr("&Find in Text..."), parent); + connect(result, &QAction::triggered, this, &AbstractFindWidget::activate); + result->setShortcut(QKeySequence::Find); + return result; +} + +/*! + Activates the find widget, making it visible and having focus on its input + field. + */ +void AbstractFindWidget::activate() +{ + show(); + m_editFind->selectAll(); + m_editFind->setFocus(Qt::ShortcutFocusReason); +} + +/*! + Deactivates the find widget, making it invisible and handing focus to any + associated QTextEdit. + */ +void AbstractFindWidget::deactivate() +{ + hide(); +} + +void AbstractFindWidget::findNext() +{ + findInternal(m_editFind->text(), true, false); +} + +void AbstractFindWidget::findPrevious() +{ + findInternal(m_editFind->text(), true, true); +} + +void AbstractFindWidget::findCurrentText() +{ + findInternal(m_editFind->text(), false, false); +} + +void AbstractFindWidget::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Escape) { + deactivate(); + return; + } + + QWidget::keyPressEvent(event); +} + +void AbstractFindWidget::updateButtons() +{ + const bool en = !m_editFind->text().isEmpty(); + m_toolPrevious->setEnabled(en); + m_toolNext->setEnabled(en); +} + +void AbstractFindWidget::findInternal(const QString &ttf, bool skipCurrent, bool backward) +{ + bool found = false; + bool wrapped = false; + find(ttf, skipCurrent, backward, &found, &wrapped); + QPalette p; + p.setColor(QPalette::Active, QPalette::Base, found ? Qt::white : QColor(255, 102, 102)); + m_editFind->setPalette(p); + m_labelWrapped->setVisible(wrapped); +} + +bool AbstractFindWidget::caseSensitive() const +{ + return m_checkCase && m_checkCase->isChecked(); +} + +bool AbstractFindWidget::wholeWords() const +{ + return m_checkWholeWords && m_checkWholeWords->isChecked(); +} + +bool AbstractFindWidget::eventFilter(QObject *object, QEvent *e) +{ + if (isVisible() && e->type() == QEvent::KeyPress) { + QKeyEvent *ke = static_cast(e); + if (ke->key() == Qt::Key_Escape) { + hide(); + return true; + } + } + + return QWidget::eventFilter(object, e); +} + +QT_END_NAMESPACE diff --git a/src/shared/findwidget/abstractfindwidget_p.h b/src/shared/findwidget/abstractfindwidget_p.h new file mode 100644 index 00000000000..1fbdfac8841 --- /dev/null +++ b/src/shared/findwidget/abstractfindwidget_p.h @@ -0,0 +1,90 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef ABSTRACTFINDWIDGET_H +#define ABSTRACTFINDWIDGET_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; +class QCheckBox; +class QEvent; +class QKeyEvent; +class QLabel; +class QLineEdit; +class QObject; +class QToolButton; + +class AbstractFindWidget : public QWidget +{ + Q_OBJECT + +public: + enum FindFlag { + /// Use a layout that is roughly half as wide and twice as high as the regular one. + NarrowLayout = 1, + /// Do not show the "Whole words" checkbox. + NoWholeWords = 2, + /// Do not show the "Case sensitive" checkbox. + NoCaseSensitive = 4 + }; + Q_DECLARE_FLAGS(FindFlags, FindFlag) + + explicit AbstractFindWidget(FindFlags flags = FindFlags(), QWidget *parent = 0); + ~AbstractFindWidget() override; + + bool eventFilter(QObject *object, QEvent *e) override; + + static QIcon findIconSet(); + QAction *createFindAction(QObject *parent); + +public slots: + void activate(); + virtual void deactivate(); + void findNext(); + void findPrevious(); + void findCurrentText(); + +protected: + void keyPressEvent(QKeyEvent *event) override; + +private slots: + void updateButtons(); + +protected: + virtual void find(const QString &textToFind, bool skipCurrent, bool backward, bool *found, bool *wrapped) = 0; + + bool caseSensitive() const; + bool wholeWords() const; + +private: + void findInternal(const QString &textToFind, bool skipCurrent, bool backward); + + QLineEdit *m_editFind; + QLabel *m_labelWrapped; + QToolButton *m_toolNext; + QToolButton *m_toolClose; + QToolButton *m_toolPrevious; + QCheckBox *m_checkCase; + QCheckBox *m_checkWholeWords; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractFindWidget::FindFlags) + +QT_END_NAMESPACE + +#endif // ABSTRACTFINDWIDGET_H diff --git a/src/shared/findwidget/images/mac/closetab.png b/src/shared/findwidget/images/mac/closetab.png new file mode 100644 index 00000000000..ab9d669eeeb Binary files /dev/null and b/src/shared/findwidget/images/mac/closetab.png differ diff --git a/src/shared/findwidget/images/mac/next.png b/src/shared/findwidget/images/mac/next.png new file mode 100644 index 00000000000..a585cab80c1 Binary files /dev/null and b/src/shared/findwidget/images/mac/next.png differ diff --git a/src/shared/findwidget/images/mac/previous.png b/src/shared/findwidget/images/mac/previous.png new file mode 100644 index 00000000000..612fb34dced Binary files /dev/null and b/src/shared/findwidget/images/mac/previous.png differ diff --git a/src/shared/findwidget/images/mac/searchfind.png b/src/shared/findwidget/images/mac/searchfind.png new file mode 100644 index 00000000000..3561745f01e Binary files /dev/null and b/src/shared/findwidget/images/mac/searchfind.png differ diff --git a/src/shared/findwidget/images/win/closetab.png b/src/shared/findwidget/images/win/closetab.png new file mode 100644 index 00000000000..ef9e02086c6 Binary files /dev/null and b/src/shared/findwidget/images/win/closetab.png differ diff --git a/src/shared/findwidget/images/win/next.png b/src/shared/findwidget/images/win/next.png new file mode 100644 index 00000000000..8df4127a00b Binary files /dev/null and b/src/shared/findwidget/images/win/next.png differ diff --git a/src/shared/findwidget/images/win/previous.png b/src/shared/findwidget/images/win/previous.png new file mode 100644 index 00000000000..0780bc23dd9 Binary files /dev/null and b/src/shared/findwidget/images/win/previous.png differ diff --git a/src/shared/findwidget/images/win/searchfind.png b/src/shared/findwidget/images/win/searchfind.png new file mode 100644 index 00000000000..6ea35e930d2 Binary files /dev/null and b/src/shared/findwidget/images/win/searchfind.png differ diff --git a/src/shared/findwidget/images/wrap.png b/src/shared/findwidget/images/wrap.png new file mode 100644 index 00000000000..90f18d9f775 Binary files /dev/null and b/src/shared/findwidget/images/wrap.png differ diff --git a/src/shared/findwidget/itemviewfindwidget.cpp b/src/shared/findwidget/itemviewfindwidget.cpp new file mode 100644 index 00000000000..a2ad97e1ed5 --- /dev/null +++ b/src/shared/findwidget/itemviewfindwidget.cpp @@ -0,0 +1,288 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/*! \class ItemViewFindWidget + + \brief A search bar that is commonly added below the searchable item view. + + \internal + + This widget implements a search bar which becomes visible when the user + wants to start searching. It is a modern replacement for the commonly used + search dialog. It is usually placed below a QAbstractItemView using a QVBoxLayout. + + The QAbstractItemView instance will need to be associated with this class using + setItemView(). + + The search is incremental and can be set to case sensitive or whole words + using buttons available on the search bar. + + The item traversal order should fit QTreeView, QTableView and QListView alike. + More complex tree structures will work as well, assuming the branch structure + is painted left to the items, without crossing lines. + + \sa QAbstractItemView + */ + +#include "itemviewfindwidget_p.h" + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +/*! + Constructs a ItemViewFindWidget. + + \a flags is passed to the AbstractFindWidget constructor. + \a parent is passed to the QWidget constructor. + */ +ItemViewFindWidget::ItemViewFindWidget(FindFlags flags, QWidget *parent) + : AbstractFindWidget(flags, parent) + , m_itemView(0) +{ +} + +/*! + Associates a QAbstractItemView with this find widget. Searches done using this find + widget will then apply to the given QAbstractItemView. + + An event filter is set on the QAbstractItemView which intercepts the ESC key while + the find widget is active, and uses it to deactivate the find widget. + + If the find widget is already associated with a QAbstractItemView, the event filter + is removed from this QAbstractItemView first. + + \a itemView may be NULL. + */ +void ItemViewFindWidget::setItemView(QAbstractItemView *itemView) +{ + if (m_itemView) + m_itemView->removeEventFilter(this); + + m_itemView = itemView; + + if (m_itemView) + m_itemView->installEventFilter(this); +} + +/*! + \reimp + */ +void ItemViewFindWidget::deactivate() +{ + if (m_itemView) + m_itemView->setFocus(); + + AbstractFindWidget::deactivate(); +} + +// Sorting is needed to find the start/end of the selection. +// This is utter black magic. And it is damn slow. +static bool indexLessThan(const QModelIndex &a, const QModelIndex &b) +{ + // First determine the nesting of each index in the tree. + QModelIndex aa = a; + int aDepth = 0; + while (aa.parent() != QModelIndex()) { + // As a side effect, check if one of the items is the parent of the other. + // Children are always displayed below their parents, so sort them further down. + if (aa.parent() == b) + return true; + aa = aa.parent(); + aDepth++; + } + QModelIndex ba = b; + int bDepth = 0; + while (ba.parent() != QModelIndex()) { + if (ba.parent() == a) + return false; + ba = ba.parent(); + bDepth++; + } + // Now find indices at comparable depth. + for (aa = a; aDepth > bDepth; aDepth--) + aa = aa.parent(); + for (ba = b; aDepth < bDepth; bDepth--) + ba = ba.parent(); + // If they have the same parent, sort them within a top-to-bottom, left-to-right rectangle. + if (aa.parent() == ba.parent()) { + if (aa.row() < ba.row()) + return true; + if (aa.row() > ba.row()) + return false; + return aa.column() < ba.column(); + } + // Now try to find indices that have the same grandparent. This ends latest at the root node. + while (aa.parent().parent() != ba.parent().parent()) { + aa = aa.parent(); + ba = ba.parent(); + } + // A bigger row is always displayed further down. + if (aa.parent().row() < ba.parent().row()) + return true; + if (aa.parent().row() > ba.parent().row()) + return false; + // Here's the trick: a child spawned from a bigger column is displayed further *up*. + // That's because the tree lines are on the left and are supposed not to cross each other. + // This case is mostly academical, as "all" models spawn children from the first column. + return aa.parent().column() > ba.parent().column(); +} + +/*! + \reimp + */ +void ItemViewFindWidget::find(const QString &ttf, bool skipCurrent, bool backward, bool *found, bool *wrapped) +{ + if (!m_itemView || !m_itemView->model()->hasChildren()) + return; + + QModelIndex idx; + if (skipCurrent && m_itemView->selectionModel()->hasSelection()) { + QModelIndexList il = m_itemView->selectionModel()->selectedIndexes(); + std::sort(il.begin(), il.end(), indexLessThan); + idx = backward ? il.first() : il.last(); + } else { + idx = m_itemView->currentIndex(); + } + + *found = true; + QModelIndex newIdx = idx; + + if (!ttf.isEmpty()) { + if (newIdx.isValid()) { + int column = newIdx.column(); + if (skipCurrent) + if (QTreeView *tv = qobject_cast(m_itemView)) + if (tv->allColumnsShowFocus()) + column = backward ? 0 : m_itemView->model()->columnCount(newIdx.parent()) - 1; + newIdx = findHelper(ttf, skipCurrent, backward, + newIdx.parent(), newIdx.row(), column); + } + if (!newIdx.isValid()) { + int row = backward ? m_itemView->model()->rowCount() : 0; + int column = backward ? 0 : -1; + newIdx = findHelper(ttf, true, backward, m_itemView->rootIndex(), row, column); + if (!newIdx.isValid()) { + *found = false; + newIdx = idx; + } else { + *wrapped = true; + } + } + } + + if (!isVisible()) + show(); + + m_itemView->setCurrentIndex(newIdx); +} + +// You are not expected to understand the following two functions. +// The traversal order is described in the indexLessThan() comments above. + +static inline bool skipForward(const QAbstractItemModel *model, QModelIndex &parent, int &row, int &column) +{ + forever { + column++; + if (column < model->columnCount(parent)) + return true; + forever { + while (--column >= 0) { + QModelIndex nIdx = model->index(row, column, parent); + if (nIdx.isValid()) { + if (model->hasChildren(nIdx)) { + row = 0; + column = 0; + parent = nIdx; + return true; + } + } + } + if (++row < model->rowCount(parent)) + break; + if (!parent.isValid()) + return false; + row = parent.row(); + column = parent.column(); + parent = parent.parent(); + } + } +} + +static inline bool skipBackward(const QAbstractItemModel *model, QModelIndex &parent, int &row, int &column) +{ + column--; + if (column == -1) { + if (--row < 0) { + if (!parent.isValid()) + return false; + row = parent.row(); + column = parent.column(); + parent = parent.parent(); + } + while (++column < model->columnCount(parent)) { + QModelIndex nIdx = model->index(row, column, parent); + if (nIdx.isValid()) { + if (model->hasChildren(nIdx)) { + row = model->rowCount(nIdx) - 1; + column = -1; + parent = nIdx; + } + } + } + column--; + } + return true; +} + +// QAbstractItemModel::match() does not support backwards searching. Still using it would +// be just a bit inefficient (not much worse than when no match is found). +// The bigger problem is that QAbstractItemView does not provide a method to sort a +// set of indices in traversal order (to find the start and end of the selection). +// Consequently, we do everything by ourselves to be consistent. Of course, this puts +// constraints on the allowable visualizations. +QModelIndex ItemViewFindWidget::findHelper(const QString &textToFind, bool skipCurrent, bool backward, + QModelIndex parent, int row, int column) +{ + const QAbstractItemModel *model = m_itemView->model(); + forever { + if (skipCurrent) { + if (backward) { + if (!skipBackward(model, parent, row, column)) + return QModelIndex(); + } else { + if (!skipForward(model, parent, row, column)) + return QModelIndex(); + } + } + + QModelIndex idx = model->index(row, column, parent); + if (idx.isValid()) { + Qt::CaseSensitivity cs = caseSensitive() ? Qt::CaseSensitive : Qt::CaseInsensitive; + + if (wholeWords()) { + QString rx = "\\b"_L1 + QRegularExpression::escape(textToFind) + + "\\b"_L1; + QRegularExpression re(rx); + if (cs == Qt::CaseInsensitive) + re.setPatternOptions(QRegularExpression::CaseInsensitiveOption); + if (idx.data().toString().indexOf(re) >= 0) + return idx; + } else { + if (idx.data().toString().indexOf(textToFind, 0, cs) >= 0) + return idx; + } + } + + skipCurrent = true; + } +} + +QT_END_NAMESPACE diff --git a/src/shared/findwidget/itemviewfindwidget_p.h b/src/shared/findwidget/itemviewfindwidget_p.h new file mode 100644 index 00000000000..ef53a8c7c13 --- /dev/null +++ b/src/shared/findwidget/itemviewfindwidget_p.h @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef ITEMVIEWFINDWIDGET_H +#define ITEMVIEWFINDWIDGET_H + +#include "abstractfindwidget_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QAbstractItemView; + +class ItemViewFindWidget : public AbstractFindWidget +{ + Q_OBJECT + +public: + explicit ItemViewFindWidget(FindFlags flags = FindFlags(), QWidget *parent = 0); + + QAbstractItemView *itemView() const + { return m_itemView; } + + void setItemView(QAbstractItemView *itemView); + +protected: + void deactivate() override; + void find(const QString &textToFind, bool skipCurrent, + bool backward, bool *found, bool *wrapped) override; + +private: + QModelIndex findHelper(const QString &textToFind, bool skipCurrent, bool backward, + QModelIndex parent, int row, int column); + + QAbstractItemView *m_itemView; +}; + +QT_END_NAMESPACE + +#endif // ITEMVIEWFINDWIDGET_H diff --git a/src/shared/findwidget/texteditfindwidget.cpp b/src/shared/findwidget/texteditfindwidget.cpp new file mode 100644 index 00000000000..a7fe0df174f --- /dev/null +++ b/src/shared/findwidget/texteditfindwidget.cpp @@ -0,0 +1,131 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +/*! \class TextEditFindWidget + + \brief A search bar that is commonly added below the searchable text. + + \internal + + This widget implements a search bar which becomes visible when the user + wants to start searching. It is a modern replacement for the commonly used + search dialog. It is usually placed below a QTextEdit using a QVBoxLayout. + + The QTextEdit instance will need to be associated with this class using + setTextEdit(). + + The search is incremental and can be set to case sensitive or whole words + using buttons available on the search bar. + + \sa QTextEdit + */ + +#include "texteditfindwidget_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + Constructs a TextEditFindWidget. + + \a flags is passed to the AbstractFindWidget constructor. + \a parent is passed to the QWidget constructor. + */ +TextEditFindWidget::TextEditFindWidget(FindFlags flags, QWidget *parent) + : AbstractFindWidget(flags, parent) + , m_textEdit(0) +{ +} + +/*! + Associates a QTextEdit with this find widget. Searches done using this find + widget will then apply to the given QTextEdit. + + An event filter is set on the QTextEdit which intercepts the ESC key while + the find widget is active, and uses it to deactivate the find widget. + + If the find widget is already associated with a QTextEdit, the event filter + is removed from this QTextEdit first. + + \a textEdit may be NULL. + */ +void TextEditFindWidget::setTextEdit(QTextEdit *textEdit) +{ + if (m_textEdit) + m_textEdit->removeEventFilter(this); + + m_textEdit = textEdit; + + if (m_textEdit) + m_textEdit->installEventFilter(this); +} + +/*! + \reimp + */ +void TextEditFindWidget::deactivate() +{ + // Pass focus to the text edit + if (m_textEdit) + m_textEdit->setFocus(); + + AbstractFindWidget::deactivate(); +} + +/*! + \reimp + */ +void TextEditFindWidget::find(const QString &ttf, bool skipCurrent, bool backward, bool *found, bool *wrapped) +{ + if (!m_textEdit) + return; + + QTextCursor cursor = m_textEdit->textCursor(); + QTextDocument *doc = m_textEdit->document(); + + if (!doc || cursor.isNull()) + return; + + if (cursor.hasSelection()) + cursor.setPosition((skipCurrent && !backward) ? cursor.position() : cursor.anchor()); + + *found = true; + QTextCursor newCursor = cursor; + + if (!ttf.isEmpty()) { + QTextDocument::FindFlags options; + + if (backward) + options |= QTextDocument::FindBackward; + + if (caseSensitive()) + options |= QTextDocument::FindCaseSensitively; + + if (wholeWords()) + options |= QTextDocument::FindWholeWords; + + newCursor = doc->find(ttf, cursor, options); + if (newCursor.isNull()) { + QTextCursor ac(doc); + ac.movePosition(options & QTextDocument::FindBackward + ? QTextCursor::End : QTextCursor::Start); + newCursor = doc->find(ttf, ac, options); + if (newCursor.isNull()) { + *found = false; + newCursor = cursor; + } else { + *wrapped = true; + } + } + } + + if (!isVisible()) + show(); + + m_textEdit->setTextCursor(newCursor); +} + +QT_END_NAMESPACE diff --git a/src/shared/findwidget/texteditfindwidget_p.h b/src/shared/findwidget/texteditfindwidget_p.h new file mode 100644 index 00000000000..27438f97679 --- /dev/null +++ b/src/shared/findwidget/texteditfindwidget_p.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef TEXTEDITFINDWIDGET_H +#define TEXTEDITFINDWIDGET_H + +#include "abstractfindwidget_p.h" + +QT_BEGIN_NAMESPACE + +class QTextEdit; + +class TextEditFindWidget : public AbstractFindWidget +{ + Q_OBJECT + +public: + explicit TextEditFindWidget(FindFlags flags = FindFlags(), QWidget *parent = 0); + + QTextEdit *textEdit() const + { return m_textEdit; } + + void setTextEdit(QTextEdit *textEdit); + +protected: + void deactivate() override; + void find(const QString &textToFind, bool skipCurrent, + bool backward, bool *found, bool *wrapped) override; + +private: + QTextEdit *m_textEdit; +}; + +QT_END_NAMESPACE + +#endif // TEXTEDITFINDWIDGET_H diff --git a/src/shared/fontpanel/fontpanel.cpp b/src/shared/fontpanel/fontpanel.cpp new file mode 100644 index 00000000000..02135657458 --- /dev/null +++ b/src/shared/fontpanel/fontpanel.cpp @@ -0,0 +1,273 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "fontpanel_p.h" + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +FontPanel::FontPanel(QWidget *parentWidget) : + QGroupBox(parentWidget), + m_previewLineEdit(new QLineEdit), + m_writingSystemComboBox(new QComboBox), + m_familyComboBox(new QFontComboBox), + m_styleComboBox(new QComboBox), + m_pointSizeComboBox(new QComboBox), + m_previewFontUpdateTimer(0) +{ + setTitle(tr("Font")); + + QFormLayout *formLayout = new QFormLayout(this); + // writing systems + m_writingSystemComboBox->setEditable(false); + + auto writingSystems = QFontDatabase::writingSystems(); + writingSystems.push_front(QFontDatabase::Any); + for (QFontDatabase::WritingSystem ws : std::as_const(writingSystems)) + m_writingSystemComboBox->addItem(QFontDatabase::writingSystemName(ws), QVariant(ws)); + connect(m_writingSystemComboBox, &QComboBox::currentIndexChanged, + this, &FontPanel::slotWritingSystemChanged); + formLayout->addRow(tr("&Writing system"), m_writingSystemComboBox); + + connect(m_familyComboBox, &QFontComboBox::currentFontChanged, + this, &FontPanel::slotFamilyChanged); + formLayout->addRow(tr("&Family"), m_familyComboBox); + + m_styleComboBox->setEditable(false); + connect(m_styleComboBox, &QComboBox::currentIndexChanged, + this, &FontPanel::slotStyleChanged); + formLayout->addRow(tr("&Style"), m_styleComboBox); + + m_pointSizeComboBox->setEditable(false); + connect(m_pointSizeComboBox, &QComboBox::currentIndexChanged, + this, &FontPanel::slotPointSizeChanged); + formLayout->addRow(tr("&Point size"), m_pointSizeComboBox); + + m_previewLineEdit->setReadOnly(true); + formLayout->addRow(m_previewLineEdit); + + setWritingSystem(QFontDatabase::Any); +} + +QFont FontPanel::selectedFont() const +{ + QFont rc = m_familyComboBox->currentFont(); + const QString family = rc.family(); + rc.setPointSize(pointSize()); + const QString styleDescription = styleString(); + if (styleDescription.contains("Italic"_L1)) + rc.setStyle(QFont::StyleItalic); + else if (styleDescription.contains("Oblique"_L1)) + rc.setStyle(QFont::StyleOblique); + else + rc.setStyle(QFont::StyleNormal); + rc.setBold(QFontDatabase::bold(family, styleDescription)); + rc.setWeight(QFont::Weight(QFontDatabase::weight(family, styleDescription))); + return rc; +} + +void FontPanel::setSelectedFont(const QFont &f) +{ + m_familyComboBox->setCurrentFont(f); + if (m_familyComboBox->currentIndex() < 0) { + // family not in writing system - find the corresponding one? + QList familyWritingSystems = QFontDatabase::writingSystems(f.family()); + if (familyWritingSystems.isEmpty()) + return; + + setWritingSystem(familyWritingSystems.constFirst()); + m_familyComboBox->setCurrentFont(f); + } + + updateFamily(family()); + + const int pointSizeIndex = closestPointSizeIndex(f.pointSize()); + m_pointSizeComboBox->setCurrentIndex( pointSizeIndex); + + const QString styleString = QFontDatabase::styleString(f); + const int styleIndex = m_styleComboBox->findText(styleString); + m_styleComboBox->setCurrentIndex(styleIndex); + slotUpdatePreviewFont(); +} + + +QFontDatabase::WritingSystem FontPanel::writingSystem() const +{ + const int currentIndex = m_writingSystemComboBox->currentIndex(); + if ( currentIndex == -1) + return QFontDatabase::Latin; + return static_cast(m_writingSystemComboBox->itemData(currentIndex).toInt()); +} + +QString FontPanel::family() const +{ + const int currentIndex = m_familyComboBox->currentIndex(); + return currentIndex != -1 ? m_familyComboBox->currentFont().family() : QString(); +} + +int FontPanel::pointSize() const +{ + const int currentIndex = m_pointSizeComboBox->currentIndex(); + return currentIndex != -1 ? m_pointSizeComboBox->itemData(currentIndex).toInt() : 9; +} + +QString FontPanel::styleString() const +{ + const int currentIndex = m_styleComboBox->currentIndex(); + return currentIndex != -1 ? m_styleComboBox->itemText(currentIndex) : QString(); +} + +void FontPanel::setWritingSystem(QFontDatabase::WritingSystem ws) +{ + m_writingSystemComboBox->setCurrentIndex(m_writingSystemComboBox->findData(QVariant(ws))); + updateWritingSystem(ws); +} + + +void FontPanel::slotWritingSystemChanged(int) +{ + updateWritingSystem(writingSystem()); + delayedPreviewFontUpdate(); +} + +void FontPanel::slotFamilyChanged(const QFont &) +{ + updateFamily(family()); + delayedPreviewFontUpdate(); +} + +void FontPanel::slotStyleChanged(int) +{ + updatePointSizes(family(), styleString()); + delayedPreviewFontUpdate(); +} + +void FontPanel::slotPointSizeChanged(int) +{ + delayedPreviewFontUpdate(); +} + +void FontPanel::updateWritingSystem(QFontDatabase::WritingSystem ws) +{ + + m_previewLineEdit->setText(QFontDatabase::writingSystemSample(ws)); + m_familyComboBox->setWritingSystem (ws); + // Current font not in WS ... set index 0. + if (m_familyComboBox->currentIndex() < 0) { + m_familyComboBox->setCurrentIndex(0); + updateFamily(family()); + } +} + +void FontPanel::updateFamily(const QString &family) +{ + // Update styles and trigger update of point sizes. + // Try to maintain selection or select normal + const QString &oldStyleString = styleString(); + + const QStringList &styles = QFontDatabase::styles(family); + const bool hasStyles = !styles.isEmpty(); + + m_styleComboBox->setCurrentIndex(-1); + m_styleComboBox->clear(); + m_styleComboBox->setEnabled(hasStyles); + + int normalIndex = -1; + const QString normalStyle = "Normal"_L1; + + if (hasStyles) { + for (const QString &style : styles) { + // try to maintain selection or select 'normal' preferably + const int newIndex = m_styleComboBox->count(); + m_styleComboBox->addItem(style); + if (oldStyleString == style) { + m_styleComboBox->setCurrentIndex(newIndex); + } else { + if (oldStyleString == normalStyle) + normalIndex = newIndex; + } + } + if (m_styleComboBox->currentIndex() == -1 && normalIndex != -1) + m_styleComboBox->setCurrentIndex(normalIndex); + } + updatePointSizes(family, styleString()); +} + +int FontPanel::closestPointSizeIndex(int desiredPointSize) const +{ + // try to maintain selection or select closest. + int closestIndex = -1; + int closestAbsError = 0xFFFF; + + const int pointSizeCount = m_pointSizeComboBox->count(); + for (int i = 0; i < pointSizeCount; i++) { + const int itemPointSize = m_pointSizeComboBox->itemData(i).toInt(); + const int absError = qAbs(desiredPointSize - itemPointSize); + if (absError < closestAbsError) { + closestIndex = i; + closestAbsError = absError; + if (closestAbsError == 0) + break; + } else { // past optimum + if (absError > closestAbsError) { + break; + } + } + } + return closestIndex; +} + + +void FontPanel::updatePointSizes(const QString &family, const QString &styleString) +{ + const int oldPointSize = pointSize(); + + auto pointSizes = QFontDatabase::pointSizes(family, styleString); + if (pointSizes.isEmpty()) + pointSizes = QFontDatabase::standardSizes(); + + const bool hasSizes = !pointSizes.isEmpty(); + m_pointSizeComboBox->clear(); + m_pointSizeComboBox->setEnabled(hasSizes); + m_pointSizeComboBox->setCurrentIndex(-1); + + // try to maintain selection or select closest. + if (hasSizes) { + QString n; + for (int pointSize : std::as_const(pointSizes)) + m_pointSizeComboBox->addItem(n.setNum(pointSize), QVariant(pointSize)); + const int closestIndex = closestPointSizeIndex(oldPointSize); + if (closestIndex != -1) + m_pointSizeComboBox->setCurrentIndex(closestIndex); + } +} + +void FontPanel::slotUpdatePreviewFont() +{ + m_previewLineEdit->setFont(selectedFont()); +} + +void FontPanel::delayedPreviewFontUpdate() +{ + if (!m_previewFontUpdateTimer) { + m_previewFontUpdateTimer = new QTimer(this); + connect(m_previewFontUpdateTimer, &QTimer::timeout, + this, &FontPanel::slotUpdatePreviewFont); + m_previewFontUpdateTimer->setInterval(0); + m_previewFontUpdateTimer->setSingleShot(true); + } + if (m_previewFontUpdateTimer->isActive()) + return; + m_previewFontUpdateTimer->start(); +} + +QT_END_NAMESPACE diff --git a/src/shared/fontpanel/fontpanel_p.h b/src/shared/fontpanel/fontpanel_p.h new file mode 100644 index 00000000000..1750b4260d7 --- /dev/null +++ b/src/shared/fontpanel/fontpanel_p.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Qt tools. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef FONTPANEL_H +#define FONTPANEL_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QComboBox; +class QFontComboBox; +class QTimer; +class QLineEdit; + +class FontPanel: public QGroupBox +{ + Q_OBJECT +public: + FontPanel(QWidget *parentWidget = 0); + + QFont selectedFont() const; + void setSelectedFont(const QFont &); + + QFontDatabase::WritingSystem writingSystem() const; + void setWritingSystem(QFontDatabase::WritingSystem ws); + +private slots: + void slotWritingSystemChanged(int); + void slotFamilyChanged(const QFont &); + void slotStyleChanged(int); + void slotPointSizeChanged(int); + void slotUpdatePreviewFont(); + +private: + QString family() const; + QString styleString() const; + int pointSize() const; + int closestPointSizeIndex(int ps) const; + + void updateWritingSystem(QFontDatabase::WritingSystem ws); + void updateFamily(const QString &family); + void updatePointSizes(const QString &family, const QString &style); + void delayedPreviewFontUpdate(); + + QLineEdit *m_previewLineEdit; + QComboBox *m_writingSystemComboBox; + QFontComboBox* m_familyComboBox; + QComboBox *m_styleComboBox; + QComboBox *m_pointSizeComboBox; + QTimer *m_previewFontUpdateTimer; +}; + +QT_END_NAMESPACE + +#endif // FONTPANEL_H diff --git a/src/shared/qtgradienteditor/images/down.png b/src/shared/qtgradienteditor/images/down.png new file mode 100644 index 00000000000..29d1d4439a1 Binary files /dev/null and b/src/shared/qtgradienteditor/images/down.png differ diff --git a/src/shared/qtgradienteditor/images/edit.png b/src/shared/qtgradienteditor/images/edit.png new file mode 100644 index 00000000000..4231bd9a904 Binary files /dev/null and b/src/shared/qtgradienteditor/images/edit.png differ diff --git a/src/shared/qtgradienteditor/images/editdelete.png b/src/shared/qtgradienteditor/images/editdelete.png new file mode 100644 index 00000000000..df2a147d246 Binary files /dev/null and b/src/shared/qtgradienteditor/images/editdelete.png differ diff --git a/src/shared/qtgradienteditor/images/minus.png b/src/shared/qtgradienteditor/images/minus.png new file mode 100644 index 00000000000..d6f233d7399 Binary files /dev/null and b/src/shared/qtgradienteditor/images/minus.png differ diff --git a/src/shared/qtgradienteditor/images/plus.png b/src/shared/qtgradienteditor/images/plus.png new file mode 100644 index 00000000000..40df1134f84 Binary files /dev/null and b/src/shared/qtgradienteditor/images/plus.png differ diff --git a/src/shared/qtgradienteditor/images/spreadpad.png b/src/shared/qtgradienteditor/images/spreadpad.png new file mode 100644 index 00000000000..104c0a23d29 Binary files /dev/null and b/src/shared/qtgradienteditor/images/spreadpad.png differ diff --git a/src/shared/qtgradienteditor/images/spreadreflect.png b/src/shared/qtgradienteditor/images/spreadreflect.png new file mode 100644 index 00000000000..17b82b711a4 Binary files /dev/null and b/src/shared/qtgradienteditor/images/spreadreflect.png differ diff --git a/src/shared/qtgradienteditor/images/spreadrepeat.png b/src/shared/qtgradienteditor/images/spreadrepeat.png new file mode 100644 index 00000000000..7aea898b1de Binary files /dev/null and b/src/shared/qtgradienteditor/images/spreadrepeat.png differ diff --git a/src/shared/qtgradienteditor/images/typeconical.png b/src/shared/qtgradienteditor/images/typeconical.png new file mode 100644 index 00000000000..5479811df61 Binary files /dev/null and b/src/shared/qtgradienteditor/images/typeconical.png differ diff --git a/src/shared/qtgradienteditor/images/typelinear.png b/src/shared/qtgradienteditor/images/typelinear.png new file mode 100644 index 00000000000..dbd8a1f5a18 Binary files /dev/null and b/src/shared/qtgradienteditor/images/typelinear.png differ diff --git a/src/shared/qtgradienteditor/images/typeradial.png b/src/shared/qtgradienteditor/images/typeradial.png new file mode 100644 index 00000000000..dc5888dca7c Binary files /dev/null and b/src/shared/qtgradienteditor/images/typeradial.png differ diff --git a/src/shared/qtgradienteditor/images/up.png b/src/shared/qtgradienteditor/images/up.png new file mode 100644 index 00000000000..e4373122171 Binary files /dev/null and b/src/shared/qtgradienteditor/images/up.png differ diff --git a/src/shared/qtgradienteditor/images/zoomin.png b/src/shared/qtgradienteditor/images/zoomin.png new file mode 100644 index 00000000000..2e586fc7bfb Binary files /dev/null and b/src/shared/qtgradienteditor/images/zoomin.png differ diff --git a/src/shared/qtgradienteditor/images/zoomout.png b/src/shared/qtgradienteditor/images/zoomout.png new file mode 100644 index 00000000000..a736d393435 Binary files /dev/null and b/src/shared/qtgradienteditor/images/zoomout.png differ diff --git a/src/shared/qtgradienteditor/qtcolorbutton.cpp b/src/shared/qtgradienteditor/qtcolorbutton.cpp new file mode 100644 index 00000000000..b00ee7b37da --- /dev/null +++ b/src/shared/qtgradienteditor/qtcolorbutton.cpp @@ -0,0 +1,236 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtcolorbutton_p.h" +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtColorButtonPrivate : public QObject +{ + Q_OBJECT + QtColorButton *q_ptr; + Q_DECLARE_PUBLIC(QtColorButton) +public: + QColor m_color; +#ifndef QT_NO_DRAGANDDROP + QColor m_dragColor; + QPoint m_dragStart; + bool m_dragging; +#endif + bool m_backgroundCheckered; + + void slotEditColor(); + QColor shownColor() const; + QPixmap generatePixmap() const; +}; + +void QtColorButtonPrivate::slotEditColor() +{ + const QColor newColor = QColorDialog::getColor(m_color, q_ptr, QString(), QColorDialog::ShowAlphaChannel); + if (!newColor.isValid() || newColor == q_ptr->color()) + return; + q_ptr->setColor(newColor); + emit q_ptr->colorChanged(m_color); +} + +QColor QtColorButtonPrivate::shownColor() const +{ +#ifndef QT_NO_DRAGANDDROP + if (m_dragging) + return m_dragColor; +#endif + return m_color; +} + +QPixmap QtColorButtonPrivate::generatePixmap() const +{ + QPixmap pix(24, 24); + + int pixSize = 20; + QBrush br(shownColor()); + + QPixmap pm(2 * pixSize, 2 * pixSize); + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray); + pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, shownColor()); + br = QBrush(pm); + + QPainter p(&pix); + int corr = 1; + QRect r = pix.rect().adjusted(corr, corr, -corr, -corr); + p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); + p.fillRect(r, br); + + p.fillRect(r.width() / 4 + corr, r.height() / 4 + corr, + r.width() / 2, r.height() / 2, + QColor(shownColor().rgb())); + p.drawRect(pix.rect().adjusted(0, 0, -1, -1)); + + return pix; +} + +/////////////// + +QtColorButton::QtColorButton(QWidget *parent) + : QToolButton(parent), d_ptr(new QtColorButtonPrivate) +{ + d_ptr->q_ptr = this; + d_ptr->m_dragging = false; + d_ptr->m_backgroundCheckered = true; + + setAcceptDrops(true); + + connect(this, &QToolButton::clicked, d_ptr.data(), &QtColorButtonPrivate::slotEditColor); + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); +} + +QtColorButton::~QtColorButton() +{ +} + +void QtColorButton::setColor(QColor color) +{ + if (d_ptr->m_color == color) + return; + d_ptr->m_color = color; + update(); +} + +QColor QtColorButton::color() const +{ + return d_ptr->m_color; +} + +void QtColorButton::setBackgroundCheckered(bool checkered) +{ + if (d_ptr->m_backgroundCheckered == checkered) + return; + d_ptr->m_backgroundCheckered = checkered; + update(); +} + +bool QtColorButton::isBackgroundCheckered() const +{ + return d_ptr->m_backgroundCheckered; +} + +void QtColorButton::paintEvent(QPaintEvent *event) +{ + QToolButton::paintEvent(event); + if (!isEnabled()) + return; + + const int pixSize = 10; + QBrush br(d_ptr->shownColor()); + if (d_ptr->m_backgroundCheckered) { + QPixmap pm(2 * pixSize, 2 * pixSize); + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, Qt::white); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); + pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, d_ptr->shownColor()); + br = QBrush(pm); + } + + QPainter p(this); + const int corr = 4; + QRect r = rect().adjusted(corr, corr, -corr, -corr); + p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); + p.fillRect(r, br); + + //const int adjX = qRound(r.width() / 4.0); + //const int adjY = qRound(r.height() / 4.0); + //p.fillRect(r.adjusted(adjX, adjY, -adjX, -adjY), + // QColor(d_ptr->shownColor().rgb())); + /* + p.fillRect(r.adjusted(0, r.height() * 3 / 4, 0, 0), + QColor(d_ptr->shownColor().rgb())); + p.fillRect(r.adjusted(0, 0, 0, -r.height() * 3 / 4), + QColor(d_ptr->shownColor().rgb())); + */ + /* + const QColor frameColor0(0, 0, 0, qRound(0.2 * (0xFF - d_ptr->shownColor().alpha()))); + p.setPen(frameColor0); + p.drawRect(r.adjusted(adjX, adjY, -adjX - 1, -adjY - 1)); + */ + + const QColor frameColor1(0, 0, 0, 26); + p.setPen(frameColor1); + p.drawRect(r.adjusted(1, 1, -2, -2)); + const QColor frameColor2(0, 0, 0, 51); + p.setPen(frameColor2); + p.drawRect(r.adjusted(0, 0, -1, -1)); +} + +void QtColorButton::mousePressEvent(QMouseEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + if (event->button() == Qt::LeftButton) + d_ptr->m_dragStart = event->pos(); +#endif + QToolButton::mousePressEvent(event); +} + +void QtColorButton::mouseMoveEvent(QMouseEvent *event) +{ +#ifndef QT_NO_DRAGANDDROP + if (event->buttons() & Qt::LeftButton && + (d_ptr->m_dragStart - event->pos()).manhattanLength() > QApplication::startDragDistance()) { + QMimeData *mime = new QMimeData; + mime->setColorData(color()); + QDrag *drg = new QDrag(this); + drg->setMimeData(mime); + drg->setPixmap(d_ptr->generatePixmap()); + setDown(false); + event->accept(); + drg->exec(Qt::CopyAction); + return; + } +#endif + QToolButton::mouseMoveEvent(event); +} + +#ifndef QT_NO_DRAGANDDROP +void QtColorButton::dragEnterEvent(QDragEnterEvent *event) +{ + const QMimeData *mime = event->mimeData(); + if (!mime->hasColor()) + return; + + event->accept(); + d_ptr->m_dragColor = qvariant_cast(mime->colorData()); + d_ptr->m_dragging = true; + update(); +} + +void QtColorButton::dragLeaveEvent(QDragLeaveEvent *event) +{ + event->accept(); + d_ptr->m_dragging = false; + update(); +} + +void QtColorButton::dropEvent(QDropEvent *event) +{ + event->accept(); + d_ptr->m_dragging = false; + if (d_ptr->m_dragColor == color()) + return; + setColor(d_ptr->m_dragColor); + emit colorChanged(color()); +} +#endif + +QT_END_NAMESPACE + +#include "qtcolorbutton.moc" diff --git a/src/shared/qtgradienteditor/qtcolorbutton_p.h b/src/shared/qtgradienteditor/qtcolorbutton_p.h new file mode 100644 index 00000000000..56f7c9eb092 --- /dev/null +++ b/src/shared/qtgradienteditor/qtcolorbutton_p.h @@ -0,0 +1,59 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTCOLORBUTTON_H +#define QTCOLORBUTTON_H + +#include + +QT_BEGIN_NAMESPACE + +class QtColorButton : public QToolButton +{ + Q_OBJECT + Q_PROPERTY(bool backgroundCheckered READ isBackgroundCheckered WRITE setBackgroundCheckered) +public: + QtColorButton(QWidget *parent = 0); + ~QtColorButton(); + + bool isBackgroundCheckered() const; + void setBackgroundCheckered(bool checkered); + + QColor color() const; + +public slots: + void setColor(QColor color); + +signals: + void colorChanged(QColor color); + +protected: + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; +#endif + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtColorButton) + Q_DISABLE_COPY_MOVE(QtColorButton) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtcolorline.cpp b/src/shared/qtgradienteditor/qtcolorline.cpp new file mode 100644 index 00000000000..05363ad43d4 --- /dev/null +++ b/src/shared/qtgradienteditor/qtcolorline.cpp @@ -0,0 +1,1077 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtcolorline_p.h" +#include "qdrawutil.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtColorLinePrivate +{ + QtColorLine *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtColorLine) +public: + QtColorLinePrivate(); + + QColor color() const; + void setColor(QColor color); + + QtColorLine::ColorComponent colorComponent() const; + void setColorComponent(QtColorLine::ColorComponent component); + + void setIndicatorSize(int size); + int indicatorSize() const; + + void setIndicatorSpace(int space); + int indicatorSpace() const; + + void setFlip(bool flip); + bool flip() const; + + void setBackgroundCheckered(bool checkered); + bool isBackgroundCheckered() const; + + void setOrientation(Qt::Orientation orientation); + Qt::Orientation orientation() const; + + void resizeEvent(QResizeEvent *event); + void paintEvent(QPaintEvent *event); + void mousePressEvent(QMouseEvent *event); + void mouseMoveEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseDoubleClickEvent(QMouseEvent *event); +private: + void checkColor(); + bool isMainPixmapValid() const; + void validate(); + void recreateMainPixmap(); + QSize pixmapSizeFromGeometrySize(QSize geometrySize) const; + QPixmap gradientPixmap(int size, Qt::Orientation orientation, + QColor begin, QColor end, bool flipped = false) const; + QPixmap gradientPixmap(Qt::Orientation orientation, + QColor begin, QColor end, bool flipped = false) const; + QPixmap hueGradientPixmap(int size, Qt::Orientation orientation, bool flipped = false, + int saturation = 0xFF, int value = 0xFF, int alpha = 0xFF) const; + QPixmap hueGradientPixmap(Qt::Orientation orientation, bool flipped = false, + int saturation = 0xFF, int value = 0xFF, int alpha = 0xFF) const; + + QList rects(QPointF point) const; + + QColor colorFromPoint(QPointF point) const; + QPointF pointFromColor(QColor color) const; + + QColor m_color = Qt::black; + QtColorLine::ColorComponent m_component = QtColorLine::Value; + bool m_flipped = false; + bool m_backgroundCheckered = true; + Qt::Orientation m_orientation = Qt::Horizontal; + bool m_dragging = false; + bool m_combiningAlpha = false; + int m_indicatorSize = 22; + int m_indicatorSpace = 0; + QPointF m_point; + QPoint m_clickOffset; + + QPixmap m_mainPixmap; + QPixmap m_alphalessPixmap; + QPixmap m_semiAlphaPixmap; + QSize m_pixmapSize{0, 0}; + + struct PixData { + QSize size; + QColor color; + QtColorLine::ColorComponent component; + bool flipped; + Qt::Orientation orientation; + }; + + PixData m_lastValidMainPixmapData; +}; + +QtColorLinePrivate::QtColorLinePrivate() + : m_point(pointFromColor(m_color)) +{ +} + +void QtColorLinePrivate::setColor(QColor color) +{ + if (m_color == color) + return; + if (!color.isValid()) + return; + if (m_dragging) // Warning perhaps here, recursive call + return; + m_color = color; + checkColor(); + m_point = pointFromColor(m_color); + q_ptr->update(); +} + +QColor QtColorLinePrivate::color() const +{ + return m_color; +} + +void QtColorLinePrivate::setColorComponent(QtColorLine::ColorComponent component) +{ + if (m_component == component) + return; + if (m_dragging) // Warning perhaps here, recursive call + return; + m_component = component; + checkColor(); + m_point = pointFromColor(m_color); + q_ptr->update(); +} + +QtColorLine::ColorComponent QtColorLinePrivate::colorComponent() const +{ + return m_component; +} + +void QtColorLinePrivate::setIndicatorSize(int size) +{ + if (size <= 0) + return; + if (m_dragging) // Warning perhaps here, recursive call + return; + if (m_indicatorSize == size) + return; + m_indicatorSize = size; + m_pixmapSize = pixmapSizeFromGeometrySize(q_ptr->contentsRect().size()); + q_ptr->update(); + q_ptr->updateGeometry(); +} + +int QtColorLinePrivate::indicatorSize() const +{ + return m_indicatorSize; +} + +void QtColorLinePrivate::setIndicatorSpace(int space) +{ + if (space < 0) + return; + if (m_dragging) // Warning perhaps here, recursive call + return; + if (m_indicatorSpace == space) + return; + m_indicatorSpace = space; + m_pixmapSize = pixmapSizeFromGeometrySize(q_ptr->contentsRect().size()); + q_ptr->update(); +} + +int QtColorLinePrivate::indicatorSpace() const +{ + return m_indicatorSpace; +} + +void QtColorLinePrivate::setFlip(bool flip) +{ + if (m_dragging) // Warning perhaps here, recursive call + return; + if (m_flipped == flip) + return; + m_flipped = flip; + m_point = pointFromColor(m_color); + q_ptr->update(); +} + +bool QtColorLinePrivate::flip() const +{ + return m_flipped; +} + +void QtColorLinePrivate::setBackgroundCheckered(bool checkered) +{ + if (m_backgroundCheckered == checkered) + return; + m_backgroundCheckered = checkered; + q_ptr->update(); +} + +bool QtColorLinePrivate::isBackgroundCheckered() const +{ + return m_backgroundCheckered; +} + +void QtColorLinePrivate::setOrientation(Qt::Orientation orientation) +{ + if (m_dragging) // Warning perhaps here, recursive call + return; + if (m_orientation == orientation) + return; + + m_orientation = orientation; + if (!q_ptr->testAttribute(Qt::WA_WState_OwnSizePolicy)) { + QSizePolicy sp = q_ptr->sizePolicy(); + sp.transpose(); + q_ptr->setSizePolicy(sp); + q_ptr->setAttribute(Qt::WA_WState_OwnSizePolicy, false); + } + m_point = pointFromColor(m_color); + q_ptr->update(); + q_ptr->updateGeometry(); +} + +Qt::Orientation QtColorLinePrivate::orientation() const +{ + return m_orientation; +} + +void QtColorLinePrivate::checkColor() +{ + switch (m_component) { + case QtColorLine::Red: + case QtColorLine::Green: + case QtColorLine::Blue: + if (m_color.spec() != QColor::Rgb) + m_color = m_color.toRgb(); + break; + case QtColorLine::Hue: + case QtColorLine::Saturation: + case QtColorLine::Value: + if (m_color.spec() != QColor::Hsv) + m_color = m_color.toHsv(); + break; + default: + break; + } + if (m_color.spec() == QColor::Hsv) { + if (m_color.hue() == 360 || m_color.hue() == -1) { + m_color.setHsvF(0.0, m_color.saturationF(), m_color.valueF(), m_color.alphaF()); + } + } +} + +bool QtColorLinePrivate::isMainPixmapValid() const +{ + if (m_mainPixmap.isNull()) { + if (m_pixmapSize.isEmpty()) + return true; + else + return false; + } + if (m_lastValidMainPixmapData.component != m_component) + return false; + if (m_lastValidMainPixmapData.size != m_pixmapSize) + return false; + if (m_lastValidMainPixmapData.flipped != m_flipped) + return false; + if (m_lastValidMainPixmapData.orientation != m_orientation) + return false; + if (m_lastValidMainPixmapData.color == m_color) + return true; + switch (m_component) { + case QtColorLine::Red: + if (m_color.green() == m_lastValidMainPixmapData.color.green() && + m_color.blue() == m_lastValidMainPixmapData.color.blue() && + (!m_combiningAlpha || m_color.alpha() == m_lastValidMainPixmapData.color.alpha())) + return true; + break; + case QtColorLine::Green: + if (m_color.red() == m_lastValidMainPixmapData.color.red() && + m_color.blue() == m_lastValidMainPixmapData.color.blue() && + (!m_combiningAlpha || m_color.alpha() == m_lastValidMainPixmapData.color.alpha())) + return true; + break; + case QtColorLine::Blue: + if (m_color.red() == m_lastValidMainPixmapData.color.red() && + m_color.green() == m_lastValidMainPixmapData.color.green() && + (!m_combiningAlpha || m_color.alpha() == m_lastValidMainPixmapData.color.alpha())) + return true; + break; + case QtColorLine::Hue: + if (m_color.saturation() == m_lastValidMainPixmapData.color.saturation() && + m_color.value() == m_lastValidMainPixmapData.color.value() && + (!m_combiningAlpha || m_color.alpha() == m_lastValidMainPixmapData.color.alpha())) + return true; + break; + case QtColorLine::Saturation: + if (m_color.hue() == m_lastValidMainPixmapData.color.hue() && + m_color.value() == m_lastValidMainPixmapData.color.value() && + (!m_combiningAlpha || m_color.alpha() == m_lastValidMainPixmapData.color.alpha())) + return true; + break; + case QtColorLine::Value: + if (m_color.hue() == m_lastValidMainPixmapData.color.hue() && + m_color.saturation() == m_lastValidMainPixmapData.color.saturation() && + (!m_combiningAlpha || m_color.alpha() == m_lastValidMainPixmapData.color.alpha())) + return true; + break; + case QtColorLine::Alpha: + if (m_color.hue() == m_lastValidMainPixmapData.color.hue() && + m_color.saturation() == m_lastValidMainPixmapData.color.saturation() && + m_color.value() == m_lastValidMainPixmapData.color.value()) + return true; + } + return false; +} + +void QtColorLinePrivate::validate() +{ + if (isMainPixmapValid()) + return; + + recreateMainPixmap(); +} + +QPixmap QtColorLinePrivate::gradientPixmap(Qt::Orientation orientation, + QColor begin, QColor end, bool flipped) const +{ + int size = m_pixmapSize.width(); + if (orientation == Qt::Vertical) + size = m_pixmapSize.height(); + return gradientPixmap(size, orientation, begin, end, flipped); +} + +QPixmap QtColorLinePrivate::gradientPixmap(int size, Qt::Orientation orientation, + QColor begin, QColor end, bool flipped) const +{ + int gradW = size; + int gradH = size; + int w = size; + int h = size; + if (orientation == Qt::Horizontal) { + gradH = 0; + h = 1; + } else { + gradW = 0; + w = 1; + } + QColor c1 = begin; + QColor c2 = end; + if (flipped) { + c1 = end; + c2 = begin; + } + QLinearGradient lg(0, 0, gradW, gradH); + lg.setColorAt(0, c1); + lg.setColorAt(1, c2); + QImage img(w, h, QImage::Format_ARGB32); + QPainter p(&img); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(QRect(0, 0, w, h), lg); + return QPixmap::fromImage(img); +} + +QPixmap QtColorLinePrivate::hueGradientPixmap(Qt::Orientation orientation, bool flipped, + int saturation, int value, int alpha) const +{ + int size = m_pixmapSize.width(); + if (orientation == Qt::Vertical) + size = m_pixmapSize.height(); + return hueGradientPixmap(size, orientation, flipped, saturation, value, alpha); +} + +QPixmap QtColorLinePrivate::hueGradientPixmap(int size, Qt::Orientation orientation, bool flipped, + int saturation, int value, int alpha) const +{ + int gradW = size + 1; + int gradH = size + 1; + int w = size; + int h = size; + if (orientation == Qt::Horizontal) { + gradH = 0; + h = 1; + } else { + gradW = 0; + w = 1; + } + QList colorList; + colorList << QColor::fromHsv(0, saturation, value, alpha); + colorList << QColor::fromHsv(60, saturation, value, alpha); + colorList << QColor::fromHsv(120, saturation, value, alpha); + colorList << QColor::fromHsv(180, saturation, value, alpha); + colorList << QColor::fromHsv(240, saturation, value, alpha); + colorList << QColor::fromHsv(300, saturation, value, alpha); + colorList << QColor::fromHsv(0, saturation, value, alpha); + QLinearGradient lg(0, 0, gradW, gradH); + for (int i = 0; i <= 6; i++) + lg.setColorAt(double(i) / 6.0, flipped ? colorList.at(6 - i) : colorList.at(i)); + QImage img(w, h, QImage::Format_ARGB32); + QPainter p(&img); + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(QRect(0, 0, w, h), lg); + return QPixmap::fromImage(img); +} + +void QtColorLinePrivate::recreateMainPixmap() +{ + m_lastValidMainPixmapData.size = m_pixmapSize; + m_lastValidMainPixmapData.component = m_component; + m_lastValidMainPixmapData.color = m_color; + m_lastValidMainPixmapData.flipped = m_flipped; + m_lastValidMainPixmapData.orientation = m_orientation; + + if (m_pixmapSize.isEmpty()) { + m_mainPixmap = QPixmap(); + m_alphalessPixmap = QPixmap(); + m_semiAlphaPixmap = QPixmap(); + return; + } + + if (m_mainPixmap.size() != m_pixmapSize) { + m_mainPixmap = QPixmap(m_pixmapSize); + m_alphalessPixmap = QPixmap(m_pixmapSize); + m_semiAlphaPixmap = QPixmap(m_pixmapSize); + } + + Qt::Orientation orient = m_orientation; + const bool flip = m_flipped; + + const int r = m_color.red(); + const int g = m_color.green(); + const int b = m_color.blue(); + const int h = m_color.hue(); + const int s = m_color.saturation(); + const int v = m_color.value(); + const int a = m_color.alpha(); + const double coef = 0.5; + const int semi = qRound(a * coef + 0xFF * (1.0 - coef)); + + if (m_component == QtColorLine::Hue) { + m_alphalessPixmap = hueGradientPixmap(orient, flip, s, v, 0xFF); + if (m_combiningAlpha) { + m_mainPixmap = hueGradientPixmap(orient, flip, s, v, a); + m_semiAlphaPixmap = hueGradientPixmap(orient, flip, s, v, semi); + } + } else if (m_component == QtColorLine::Saturation) { + m_alphalessPixmap = gradientPixmap(orient, QColor::fromHsv(h, 0, v, 0xFF), QColor::fromHsv(h, 0xFF, v, 0xFF), flip); + if (m_combiningAlpha) { + m_mainPixmap = gradientPixmap(orient, QColor::fromHsv(h, 0, v, a), QColor::fromHsv(h, 0xFF, v, a), flip); + m_semiAlphaPixmap = gradientPixmap(orient, QColor::fromHsv(h, 0, v, semi), QColor::fromHsv(h, 0xFF, v, semi), flip); + } + } else if (m_component == QtColorLine::Value) { + m_alphalessPixmap = gradientPixmap(orient, QColor::fromRgb(0, 0, 0, 0xFF), QColor::fromHsv(h, s, 0xFF, 0xFF), flip); + if (m_combiningAlpha) { + m_mainPixmap = gradientPixmap(orient, QColor::fromRgb(0, 0, 0, a), QColor::fromHsv(h, s, 0xFF, a), flip); + m_semiAlphaPixmap = gradientPixmap(orient, QColor::fromRgb(0, 0, 0, semi), QColor::fromHsv(h, s, 0xFF, semi), flip); + } + } else if (m_component == QtColorLine::Red) { + m_alphalessPixmap = gradientPixmap(orient, QColor::fromRgb(0, g, b, 0xFF), QColor::fromRgb(0xFF, g, b, 0xFF), flip); + if (m_combiningAlpha) { + m_mainPixmap = gradientPixmap(orient, QColor::fromRgb(0, g, b, a), QColor::fromRgb(0xFF, g, b, a), flip); + m_semiAlphaPixmap = gradientPixmap(orient, QColor::fromRgb(0, g, b, semi), QColor::fromRgb(0xFF, g, b, semi), flip); + } + } else if (m_component == QtColorLine::Green) { + m_alphalessPixmap = gradientPixmap(orient, QColor::fromRgb(r, 0, b, 0xFF), QColor::fromRgb(r, 0xFF, b, 0xFF), flip); + if (m_combiningAlpha) { + m_mainPixmap = gradientPixmap(orient, QColor::fromRgb(r, 0, b, a), QColor::fromRgb(r, 0xFF, b, a), flip); + m_semiAlphaPixmap = gradientPixmap(orient, QColor::fromRgb(r, 0, b, semi), QColor::fromRgb(r, 0xFF, b, semi), flip); + } + } else if (m_component == QtColorLine::Blue) { + m_alphalessPixmap = gradientPixmap(orient, QColor::fromRgb(r, g, 0, 0xFF), QColor::fromRgb(r, g, 0xFF, 0xFF), flip); + if (m_combiningAlpha) { + m_mainPixmap = gradientPixmap(orient, QColor::fromRgb(r, g, 0, a), QColor::fromRgb(r, g, 0xFF, a), flip); + m_semiAlphaPixmap = gradientPixmap(orient, QColor::fromRgb(r, g, 0, semi), QColor::fromRgb(r, g, 0xFF, semi), flip); + } + } else if (m_component == QtColorLine::Alpha) { + m_mainPixmap = gradientPixmap(orient, QColor::fromRgb(r, g, b, 0), QColor::fromRgb(r, g, b, 0xFF), flip); + +// m_alphalessPixmap = gradientPixmap(orient, QColor::fromRgb(r, g, b, 0xFF), QColor::fromRgb(r, g, b, 0xFF), flip); +// m_semiAlphaPixmap = gradientPixmap(orient, QColor::fromRgb(r, g, b, semi), QColor::fromRgb(r, g, b, semi), flip); + } + if (!m_combiningAlpha && m_component != QtColorLine::Alpha) + m_mainPixmap = m_alphalessPixmap; +} + +QSize QtColorLinePrivate::pixmapSizeFromGeometrySize(QSize geometrySize) const +{ + QSize size(m_indicatorSize + 2 * m_indicatorSpace - 1, + m_indicatorSize + 2 * m_indicatorSpace - 1); + if (m_orientation == Qt::Horizontal) + size.setHeight(0); + else + size.setWidth(0); + return geometrySize - size; +} + +QColor QtColorLinePrivate::colorFromPoint(QPointF point) const +{ + QPointF p = point; + if (p.x() < 0) + p.setX(0.0); + else if (p.x() > 1) + p.setX(1.0); + if (p.y() < 0) + p.setY(0.0); + else if (p.y() > 1) + p.setY(1.0); + + double pos = p.x(); + if (m_orientation == Qt::Vertical) + pos = p.y(); + if (m_flipped) + pos = 1.0 - pos; + QColor c; + qreal hue; + switch (m_component) { + case QtColorLine::Red: + c.setRgbF(pos, m_color.greenF(), m_color.blueF(), m_color.alphaF()); + break; + case QtColorLine::Green: + c.setRgbF(m_color.redF(), pos, m_color.blueF(), m_color.alphaF()); + break; + case QtColorLine::Blue: + c.setRgbF(m_color.redF(), m_color.greenF(), pos, m_color.alphaF()); + break; + case QtColorLine::Hue: + hue = pos; + hue *= 35999.0 / 36000.0; + c.setHsvF(hue, m_color.saturationF(), m_color.valueF(), m_color.alphaF()); + break; + case QtColorLine::Saturation: + c.setHsvF(m_color.hueF(), pos, m_color.valueF(), m_color.alphaF()); + break; + case QtColorLine::Value: + c.setHsvF(m_color.hueF(), m_color.saturationF(), pos, m_color.alphaF()); + break; + case QtColorLine::Alpha: + c.setHsvF(m_color.hueF(), m_color.saturationF(), m_color.valueF(), pos); + break; + } + return c; +} + +QPointF QtColorLinePrivate::pointFromColor(QColor color) const +{ + qreal hue = color.hueF(); + if (color.hue() == 360) + hue = 0.0; + else + hue *= 36000.0 / 35999.0; + + double pos = 0.0; + switch (m_component) { + case QtColorLine::Red: + pos = color.redF(); + break; + case QtColorLine::Green: + pos = color.greenF(); + break; + case QtColorLine::Blue: + pos = color.blueF(); + break; + case QtColorLine::Hue: + pos = hue; + break; + case QtColorLine::Saturation: + pos = color.saturationF(); + break; + case QtColorLine::Value: + pos = color.valueF(); + break; + case QtColorLine::Alpha: + pos = color.alphaF(); + break; + } + if (m_flipped) + pos = 1.0 - pos; + QPointF p(pos, pos); + if (m_orientation == Qt::Horizontal) + p.setY(0); + else + p.setX(0); + return p; +} + +QList QtColorLinePrivate::rects(QPointF point) const +{ + QRect r = q_ptr->geometry(); + r.moveTo(0, 0); + + int x1 = (int)((r.width() - m_indicatorSize - 2 * m_indicatorSpace) * point.x() + 0.5); + int x2 = x1 + m_indicatorSize + 2 * m_indicatorSpace; + int y1 = (int)((r.height() - m_indicatorSize - 2 * m_indicatorSpace) * point.y() + 0.5); + int y2 = y1 + m_indicatorSize + 2 * m_indicatorSpace; + + QList rects; + if (m_orientation == Qt::Horizontal) { + // r0 r1 r2 + QRect r0(0, 0, x1, r.height()); + QRect r1(x1 + m_indicatorSpace, 0, m_indicatorSize, r.height()); + QRect r2(x2, 0, r.width() - x2, r.height()); + + rects << r0 << r1 << r2; + } else { + // r0 + // r1 + // r2 + QRect r0(0, 0, r.width(), y1); + QRect r1(0, y1 + m_indicatorSpace, r.width(), m_indicatorSize); + QRect r2(0, y2, r.width(), r.height() - y2); + + rects << r0 << r1 << r2; + } + return rects; +} + +void QtColorLinePrivate::resizeEvent(QResizeEvent *event) +{ + m_pixmapSize = pixmapSizeFromGeometrySize(event->size()); +} + +void QtColorLinePrivate::paintEvent(QPaintEvent *) +{ + QRect rect = q_ptr->rect(); + + QList r = rects(m_point); + + QColor c = colorFromPoint(m_point); + if (!m_combiningAlpha && m_component != QtColorLine::Alpha) + c.setAlpha(0xFF); + + QPainter p(q_ptr); + if (q_ptr->isEnabled()) { + if (m_backgroundCheckered) { + int pixSize = 20; + QPixmap pm(2 * pixSize, 2 * pixSize); + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, Qt::white); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); + pmp.end(); + + p.setBrushOrigin((rect.width() % pixSize + pixSize) / 2, (rect.height() % pixSize + pixSize) / 2); + + QRegion region(r[1].adjusted(4, 4, -4, -4)); + region += QRect(rect.topLeft(), QPoint(r[1].left() + 0, rect.bottom())); + region += QRect(QPoint(r[1].right() - 0, rect.top()), rect.bottomRight()); + region += QRect(rect.topLeft(), QPoint(rect.right(), r[1].top() + 0)); + region += QRect(QPoint(rect.left(), r[1].bottom() - 0), rect.bottomRight()); + p.setClipRegion(region); + p.fillRect(rect, pm); + p.setBrushOrigin(0, 0); + p.setClipping(false); + } + + validate(); + + QSize fieldSize = pixmapSizeFromGeometrySize(q_ptr->geometry().size()); + + QPoint posOnField = r[1].topLeft() - QPoint(m_indicatorSpace, m_indicatorSpace); + int x = posOnField.x(); + int y = posOnField.y(); + int w = fieldSize.width(); + int h = fieldSize.height(); + + QRect r0, r2; + if (m_orientation == Qt::Horizontal) { + r0 = QRect(0, 0, x, m_pixmapSize.height()); + r2 = QRect(x + 1, 0, w - x - 1, m_pixmapSize.height()); + } else { + r0 = QRect(0, 0, m_pixmapSize.width(), y); + r2 = QRect(0, y + 1, m_pixmapSize.width(), h - y - 1); + } + + p.setBrush(m_mainPixmap); + p.setPen(Qt::NoPen); + if (r[0].isValid()) { + p.drawRect(r[0]); + } + if (r[2].isValid()) { + p.setBrushOrigin(r[2].topLeft() - r2.topLeft()); + p.drawRect(r[2]); + } + if (m_indicatorSpace) { + p.setBrush(c); + if (m_orientation == Qt::Horizontal) { + p.drawRect(r[1].adjusted(-m_indicatorSpace, 0, -r[1].width(), 0)); + p.drawRect(r[1].adjusted(r[1].width(), 0, m_indicatorSpace, 0)); + } else { + p.drawRect(r[1].adjusted(0, -m_indicatorSpace, 0, -r[1].height())); + p.drawRect(r[1].adjusted(0, r[1].height(), 0, m_indicatorSpace)); + } + } + + QPen pen(c); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + if (r[1].isValid()) { + p.drawRect(r[1].adjusted(0, 0, -1, -1)); + // p.drawRect(r[1].adjusted(1, 1, -2, -2)); + } + double coef = 9.0 / 10; + p.setPen(Qt::NoPen); + if (m_component != QtColorLine::Alpha && m_combiningAlpha) { + p.setBrush(m_alphalessPixmap); + if (r[0].isValid()) { + p.setBrushOrigin(QPoint(0, 0)); + QRect thinRect1 = r[0]; + QRect thinRect2 = r[0]; + QRect thinRect = r[0]; + if (m_orientation == Qt::Horizontal) { + thinRect1.adjust(0, qRound(thinRect1.height() * coef), 0, 0); + thinRect2.adjust(0, 0, 0, -qRound(thinRect2.height() * coef)); + thinRect.adjust(0, qRound(thinRect.height() * coef), 0, -qRound(thinRect.height() * coef)); + } else { + thinRect1.adjust(qRound(thinRect1.width() * coef), 0, 0, 0); + thinRect2.adjust(0, 0, -qRound(thinRect2.width() * coef), 0); + thinRect.adjust(qRound(thinRect.width() * coef), 0, -qRound(thinRect.width() * coef), 0); + } + p.drawRect(thinRect1); + p.drawRect(thinRect2); + //p.drawRect(thinRect); + } + if (r[2].isValid()) { + p.setBrushOrigin(r[2].topLeft() - r2.topLeft()); + QRect thinRect1 = r[2]; + QRect thinRect2 = r[2]; + QRect thinRect = r[2]; + if (m_orientation == Qt::Horizontal) { + thinRect1.adjust(0, qRound(thinRect1.height() * coef), 0, 0); + thinRect2.adjust(0, 0, 0, -qRound(thinRect2.height() * coef)); + thinRect.adjust(0, qRound(thinRect.height() * coef), 0, -qRound(thinRect.height() * coef)); + } else { + thinRect1.adjust(qRound(thinRect1.width() * coef), 0, 0, 0); + thinRect2.adjust(0, 0, -qRound(thinRect2.width() * coef), 0); + thinRect.adjust(qRound(thinRect.width() * coef), 0, -qRound(thinRect.width() * coef), 0); + } + p.drawRect(thinRect1); + p.drawRect(thinRect2); + //p.drawRect(thinRect); + } + /* + +*/ + + + + + + p.setPen(Qt::NoPen); + + p.setBrush(m_semiAlphaPixmap); + if (r[0].isValid()) { + p.setBrushOrigin(QPoint(0, 0)); + QRect thinRect1 = r[0]; + QRect thinRect2 = r[0]; + QRect thinRect = r[0]; + if (m_orientation == Qt::Horizontal) { + thinRect1.adjust(0, qRound(thinRect1.height() * coef) - 1, 0, 0); + thinRect1.setBottom(thinRect1.top()); + thinRect2.adjust(0, 0, 0, -qRound(thinRect2.height() * coef) + 1); + thinRect2.setTop(thinRect2.bottom()); + thinRect.adjust(0, qRound(thinRect.height() * coef), 0, -qRound(thinRect.height() * coef)); + } else { + thinRect1.adjust(qRound(thinRect1.width() * coef) - 1, 0, 0, 0); + thinRect1.setRight(thinRect1.left()); + thinRect2.adjust(0, 0, -qRound(thinRect2.width() * coef) + 1, 0); + thinRect2.setLeft(thinRect2.right()); + thinRect.adjust(qRound(thinRect.width() * coef), 0, -qRound(thinRect.width() * coef), 0); + } + p.drawRect(thinRect1); + p.drawRect(thinRect2); + //p.drawRect(thinRect); + } + if (r[2].isValid()) { + p.setBrushOrigin(r[2].topLeft() - r2.topLeft()); + QRect thinRect1 = r[2]; + QRect thinRect2 = r[2]; + QRect thinRect = r[2]; + if (m_orientation == Qt::Horizontal) { + thinRect1.adjust(0, qRound(thinRect1.height() * coef) - 1, 0, 0); + thinRect1.setBottom(thinRect1.top()); + thinRect2.adjust(0, 0, 0, -qRound(thinRect2.height() * coef) + 1); + thinRect2.setTop(thinRect2.bottom()); + thinRect.adjust(0, qRound(thinRect.height() * coef), 0, -qRound(thinRect.height() * coef)); + } else { + thinRect1.adjust(qRound(thinRect1.width() * coef) - 1, 0, 0, 0); + thinRect1.setRight(thinRect1.left()); + thinRect2.adjust(0, 0, -qRound(thinRect2.width() * coef) + 1, 0); + thinRect2.setLeft(thinRect2.right()); + thinRect.adjust(qRound(thinRect.width() * coef), 0, -qRound(thinRect.width() * coef), 0); + } + p.drawRect(thinRect1); + p.drawRect(thinRect2); + //p.drawRect(thinRect); + } + p.setBrush(m_alphalessPixmap); + QRegion region; + if (m_orientation == Qt::Horizontal) { + region += r[1].adjusted(0, qRound(r[1].height() * coef), 0, 0); + region += r[1].adjusted(0, 0, 0, -qRound(r[1].height() * coef)); + p.setClipRegion(region); + } else { + region += r[1].adjusted(qRound(r[1].width() * coef), 0, 0, 0); + region += r[1].adjusted(0, 0, -qRound(r[1].width() * coef), 0); + p.setClipRegion(region); + } + p.setClipRegion(region); + p.setBrush(Qt::NoBrush); + p.setPen(QPen(QColor(c.rgb()))); + + p.drawRect(r[1].adjusted(0, 0, -1, -1)); + // p.drawRect(r[1].adjusted(1, 1, -2, -2)); +/* + p.setBrush(m_semiAlphaPixmap); + if (m_orientation == Qt::Horizontal) { + QRect top = r[1].adjusted(0, 0, 0, -qRound(r[1].height() * coef) + 1); + top.setTop(top.bottom()); + QRect bottom = r[1].adjusted(0, qRound(r[1].height() * coef) - 1, 0, 0); + top.setBottom(bottom.top()); + p.setClipRect(top); + p.setClipRect(bottom, Qt::UniteClip); + } else { + + } + QColor semiColor(c.rgb()); + semiColor.setAlpha((c.alpha() + 0xFF) / 2); + p.setPen(QPen(semiColor)); + p.drawRect(r[1].adjusted(0, 0, -1, -1)); + // p.drawRect(r[1].adjusted(1, 1, -2, -2)); +*/ + p.setClipping(false); + } + } + + p.setBrush(Qt::NoBrush); + int lw = 4; + //int br = 1; + int br = 0; + r[1].adjust(br, br, -br, -br); + if (r[1].adjusted(lw, lw, -lw, -lw).isValid()) { + QStyleOptionFrame opt; + opt.initFrom(q_ptr); + opt.rect = r[1]; + opt.lineWidth = 2; + opt.midLineWidth = 1; + if (m_dragging) + opt.state |= QStyle::State_Sunken; + else + opt.state |= QStyle::State_Raised; + q_ptr->style()->drawPrimitive(QStyle::PE_Frame, &opt, &p, q_ptr); + QRect colorRect = r[1].adjusted(lw, lw, -lw, -lw); + if (q_ptr->isEnabled()) { + p.fillRect(colorRect, c); + const QColor frameColor(0, 0, 0, 38); + p.setPen(frameColor); + p.drawRect(colorRect.adjusted(0, 0, -1, -1)); + /* + p.fillRect(colorRect.width() / 4 + colorRect.left(), + colorRect.height() / 4 + colorRect.top(), + colorRect.width() / 2, + colorRect.height() / 2, + QColor(c.rgb())); + */ + /* + if (m_component != QtColorLine::Alpha) { + p.fillRect(colorRect.adjusted(0, colorRect.height() * 4 / 5, 0, 0), QColor(c.rgb())); + p.fillRect(colorRect.adjusted(0, 0, 0, -colorRect.height() * 4 / 5), QColor(c.rgb())); + } + */ + } + } +} + +void QtColorLinePrivate::mousePressEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) + return; + + QList r = rects(m_point); + QPoint clickPos = event->position().toPoint(); + + QPoint posOnField = r[1].topLeft() - QPoint(m_indicatorSpace, m_indicatorSpace); + m_clickOffset = posOnField - clickPos; + + if (!r[1].contains(clickPos)) + return; + m_dragging = true; + q_ptr->update(); +} + +void QtColorLinePrivate::mouseMoveEvent(QMouseEvent *event) +{ + if (!m_dragging) + return; + QPoint newPos = event->position().toPoint(); + + QSize fieldSize = q_ptr->geometry().size() - + QSize(m_indicatorSize + 2 * m_indicatorSpace - 1, m_indicatorSize + 2 * m_indicatorSpace - 1); + QPoint newPosOnField = newPos + m_clickOffset; + if (newPosOnField.x() < 0) + newPosOnField.setX(0); + else if (newPosOnField.x() > fieldSize.width()) + newPosOnField.setX(fieldSize.width()); + if (newPosOnField.y() < 0) + newPosOnField.setY(0); + else if (newPosOnField.y() > fieldSize.height()) + newPosOnField.setY(fieldSize.height()); + + const double x = double(newPosOnField.x()) / fieldSize.width(); + const double y = double(newPosOnField.y()) / fieldSize.height(); + m_point = QPointF(x, y); + QColor color = colorFromPoint(m_point); + if (m_color == color) + return; + m_color = color; + emit q_ptr->colorChanged(color); // maybe before internal set, 1 line above + q_ptr->update(); +} + +void QtColorLinePrivate::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) + return; + m_dragging = false; + q_ptr->update(); +} + +void QtColorLinePrivate::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton) + return; + + QList r = rects(m_point); + QPoint clickPos = event->position().toPoint(); + if (!r[0].contains(clickPos) && !r[2].contains(clickPos)) + return; + QPoint newPosOnField = clickPos; + if (r[2].contains(clickPos)) + newPosOnField -= QPoint(m_indicatorSize + 2 * m_indicatorSpace - 2, m_indicatorSize + 2 * m_indicatorSpace - 2); + QSize fieldSize = q_ptr->geometry().size() - + QSize(m_indicatorSize + 2 * m_indicatorSpace - 1, m_indicatorSize + 2 * m_indicatorSpace - 1); + + const double x = double(newPosOnField.x()) / fieldSize.width(); + const double y = double(newPosOnField.y()) / fieldSize.height(); + m_point = QPointF(x, y); + QColor color = colorFromPoint(m_point); + if (m_color == color) + return; + m_color = color; + emit q_ptr->colorChanged(color); // maybe before internal set, 1 line above + q_ptr->update(); +} + +//////////////////////////////////////////////////// + +QtColorLine::QtColorLine(QWidget *parent) + : QWidget(parent), d_ptr(new QtColorLinePrivate) +{ + d_ptr->q_ptr = this; + + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); +} + +QtColorLine::~QtColorLine() +{ +} + +QSize QtColorLine::minimumSizeHint() const +{ + return QSize(d_ptr->m_indicatorSize, d_ptr->m_indicatorSize); +} + +QSize QtColorLine::sizeHint() const +{ + return QSize(d_ptr->m_indicatorSize, d_ptr->m_indicatorSize); +} + +void QtColorLine::setColor(QColor color) +{ + d_ptr->setColor(color); +} + +QColor QtColorLine::color() const +{ + return d_ptr->color(); +} + +void QtColorLine::setColorComponent(QtColorLine::ColorComponent component) +{ + d_ptr->setColorComponent(component); +} + +QtColorLine::ColorComponent QtColorLine::colorComponent() const +{ + return d_ptr->colorComponent(); +} + +void QtColorLine::setIndicatorSize(int size) +{ + d_ptr->setIndicatorSize(size); +} + +int QtColorLine::indicatorSize() const +{ + return d_ptr->indicatorSize(); +} + +void QtColorLine::setIndicatorSpace(int space) +{ + d_ptr->setIndicatorSpace(space); +} + +int QtColorLine::indicatorSpace() const +{ + return d_ptr->indicatorSpace(); +} + +void QtColorLine::setFlip(bool flip) +{ + d_ptr->setFlip(flip); +} + +bool QtColorLine::flip() const +{ + return d_ptr->flip(); +} + +void QtColorLine::setBackgroundCheckered(bool checkered) +{ + d_ptr->setBackgroundCheckered(checkered); +} + +bool QtColorLine::isBackgroundCheckered() const +{ + return d_ptr->isBackgroundCheckered(); +} + +void QtColorLine::setOrientation(Qt::Orientation orientation) +{ + d_ptr->setOrientation(orientation); +} + +Qt::Orientation QtColorLine::orientation() const +{ + return d_ptr->orientation(); +} +void QtColorLine::resizeEvent(QResizeEvent *event) +{ + d_ptr->resizeEvent(event); +} + +void QtColorLine::paintEvent(QPaintEvent *event) +{ + d_ptr->paintEvent(event); +} + +void QtColorLine::mousePressEvent(QMouseEvent *event) +{ + d_ptr->mousePressEvent(event); +} + +void QtColorLine::mouseMoveEvent(QMouseEvent *event) +{ + d_ptr->mouseMoveEvent(event); +} + +void QtColorLine::mouseReleaseEvent(QMouseEvent *event) +{ + d_ptr->mouseReleaseEvent(event); +} + +void QtColorLine::mouseDoubleClickEvent(QMouseEvent *event) +{ + d_ptr->mouseDoubleClickEvent(event); +} + +QT_END_NAMESPACE diff --git a/src/shared/qtgradienteditor/qtcolorline_p.h b/src/shared/qtgradienteditor/qtcolorline_p.h new file mode 100644 index 00000000000..c4e521a5235 --- /dev/null +++ b/src/shared/qtgradienteditor/qtcolorline_p.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTCOLORLINE_H +#define QTCOLORLINE_H + +#include + +QT_BEGIN_NAMESPACE + +class QtColorLine : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QColor color READ color WRITE setColor) + Q_PROPERTY(int indicatorSpace READ indicatorSpace WRITE setIndicatorSpace) + Q_PROPERTY(int indicatorSize READ indicatorSize WRITE setIndicatorSize) + Q_PROPERTY(bool flip READ flip WRITE setFlip) + Q_PROPERTY(bool backgroundCheckered READ isBackgroundCheckered WRITE setBackgroundCheckered) + Q_PROPERTY(ColorComponent colorComponent READ colorComponent WRITE setColorComponent) + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) +public: + + enum ColorComponent { + Red, + Green, + Blue, + Hue, + Saturation, + Value, + Alpha + }; + Q_ENUM(ColorComponent) + + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + + QtColorLine(QWidget *parent = 0); + ~QtColorLine(); + + QColor color() const; + + void setIndicatorSize(int size); + int indicatorSize() const; + + void setIndicatorSpace(int space); + int indicatorSpace() const; + + void setFlip(bool flip); + bool flip() const; + + bool isBackgroundCheckered() const; + void setBackgroundCheckered(bool checkered); + + void setOrientation(Qt::Orientation orientation); + Qt::Orientation orientation() const; + + void setColorComponent(ColorComponent component); + ColorComponent colorComponent() const; + +public slots: + void setColor(QColor color); + +signals: + void colorChanged(QColor color); + +protected: + void resizeEvent(QResizeEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtColorLine) + Q_DISABLE_COPY_MOVE(QtColorLine) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtgradientdialog.cpp b/src/shared/qtgradienteditor/qtgradientdialog.cpp new file mode 100644 index 00000000000..fefdb538b24 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientdialog.cpp @@ -0,0 +1,315 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradientdialog_p.h" +#include "ui_qtgradientdialog.h" +#include + +QT_BEGIN_NAMESPACE + +class QtGradientDialogPrivate : public QObject +{ + Q_OBJECT + QtGradientDialog *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtGradientDialog) +public: + void slotAboutToShowDetails(bool details, int extensionWidthHint); + + Ui::QtGradientDialog m_ui; +}; + +void QtGradientDialogPrivate::slotAboutToShowDetails(bool details, int extensionWidthHint) +{ + if (details) { + q_ptr->resize(q_ptr->size() + QSize(extensionWidthHint, 0)); + } else { + q_ptr->setMinimumSize(1, 1); + q_ptr->resize(q_ptr->size() - QSize(extensionWidthHint, 0)); + q_ptr->setMinimumSize(0, 0); + } +} + +/*! + \class QtGradientDialog + + \brief The QtGradientDialog class provides a dialog for specifying gradients. + + The gradient dialog's function is to allow users to edit gradients. + For example, you might use this in a drawing program to allow the user to set the brush gradient. + + \table + \row + \li \inlineimage qtgradientdialog.png + \li \inlineimage qtgradientdialogextension.png + \header + \li Details extension hidden + \li Details extension visible + \endtable + + Starting from the top of the dialog there are several buttons: + + \image qtgradientdialogtopbuttons.png + + The first three buttons allow for changing a type of the gradient (QGradient::Type), while the second three allow for + changing spread of the gradient (QGradient::Spread). The last button shows or hides the details extension of the dialog. + Conceptually the default view with hidden details provides the full functional control over gradient editing. + The additional extension with details allows to set gradient's parameters more precisely. The visibility + of extension can be controlled by detailsVisible property. Moreover, if you don't want the user to + switch on or off the visibility of extension you can set the detailsButtonVisible property to false. + + Below top buttons there is an area where edited gradient is interactively previewed. + In addition the user can edit gradient type's specific parameters directly in this area by dragging + appropriate handles. + + \table + \row + \li \inlineimage qtgradientdialoglineareditor.png + \li \inlineimage qtgradientdialogradialeditor.png + \li \inlineimage qtgradientdialogconicaleditor.png + \header + \li Editing linear type + \li Editing radial type + \li Editing conical type + \row + \li The user can change the start and final point positions by dragging the circular handles. + \li The user can change the center and focal point positions by dragging the circular handles + and can change the gradient's radius by dragging horizontal or vertical line. + \li The user can change the center point by dragging the circular handle + and can change the gradient's angle by dragging the big wheel. + \endtable + + In the middle of the dialog there is an area where the user can edit gradient stops. + + \table + \row + \li \inlineimage qtgradientdialogstops.png + \li \inlineimage qtgradientdialogstopszoomed.png + \endtable + + The top part of this area contains stop handles, and bottom part shows the preview of gradient stops path. + In order to create a new gradient stop double click inside the view over the desired position. + If you double click on existing stop handle in the top part of the view, clicked handle will be duplicated + (duplicate will contain the same color). + The stop can be activated by clicking on its handle. You can activate previous or next stop by pressing + left or right key respectively. To jump to the first or last stop press home or end key respectively. + The gradient stops editor supports multiselection. + Clicking a handle holding the shift modifier key down will select a range of stops between + the active stop and clicked one. Clicking a handle holding control modifier key down will remove from or + add to selection the clicked stop depending if it was or wasn't already selected respectively. + Multiselection can also be created using rubberband (by pressing the left mouse button outside + of any handle and dragging). + Sometimes it's hard to select a stop because its handle can be partially covered by other handle. + In that case the user can zoom in the view by spinning mouse wheel. + The selected stop handles can be moved by drag & drop. In order to remove selected stops press delete key. + For convenience context menu is provided with the following actions: + + \list + \li New Stop - creates a new gradient stop + \li Delete - removes the active and all selected stops + \li Flip All - mirrors all stops + \li Select All - selects all stops + \li Zoom In - zooms in + \li Zoom Out - zooms out + \li Zoom All - goes back to original 100% zoom + \endlist + + The bottom part of the QtGradientDialog contains a set of widgets allowing to control the color of + the active and selected stops. + + \table + \row + \li \inlineimage qtgradientdialogcolorhsv.png + \li \inlineimage qtgradientdialogcolorrgb.png + \endtable + + + The color button shows the color of the active gradient stop. It also allows for choosing + a color from standard color dialog and applying it to the + active stop and all selected stops. It's also possible to drag a color directly from the color button + and to drop it in gradient stops editor at desired position (it will create new stop with dragged color) + or at desired stop handle (it will change the color of that handle). + + To the right of color button there is a set of 2 radio buttons which allows to switch between + HVS and RGB color spec. + + Finally there are 4 color sliders working either in HSVA (hue saturation value alpha) or + RGBA (red green blue alpha) mode, depending on which radio button is chosen. The radio buttons + can be controlled programatically by spec() and setSpec() methods. The sliders show the + color of the active stop. By double clicking inside color slider you can set directly the desired color. + Changes of slider's are applied to stop selection in the way that the color + component being changed is applied to stops in selection only, while other components + remain unchanged in selected stops (e.g. when the user is changing the saturation, + new saturation is applied to selected stops preventing original hue, value and alpha in multiselection). + + The convenient static functions getGradient() provide modal gradient dialogs, e.g.: + + \snippet doc/src/snippets/code/tools_shared_qtgradienteditor_qtgradientdialog.cpp 0 + + In order to have more control over the properties of QtGradientDialog use + standard QDialog::exec() method: + + \snippet doc/src/snippets/code/tools_shared_qtgradienteditor_qtgradientdialog.cpp 1 + + \sa {Gradient View Example} +*/ + +/*! + Constructs a gradient dialog with \a parent as parent widget. +*/ + +QtGradientDialog::QtGradientDialog(QWidget *parent) + : QDialog(parent), d_ptr(new QtGradientDialogPrivate()) +{ +// setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); + d_ptr->q_ptr = this; + d_ptr->m_ui.setupUi(this); + QPushButton *button = d_ptr->m_ui.buttonBox->button(QDialogButtonBox::Ok); + if (button) + button->setAutoDefault(false); + button = d_ptr->m_ui.buttonBox->button(QDialogButtonBox::Cancel); + if (button) + button->setAutoDefault(false); + connect(d_ptr->m_ui.gradientEditor, &QtGradientEditor::aboutToShowDetails, + d_ptr.data(), &QtGradientDialogPrivate::slotAboutToShowDetails); +} + +/*! + Destroys the gradient dialog +*/ + +QtGradientDialog::~QtGradientDialog() +{ +} + +/*! + \property QtGradientDialog::gradient + \brief the gradient of the dialog +*/ +void QtGradientDialog::setGradient(const QGradient &gradient) +{ + d_ptr->m_ui.gradientEditor->setGradient(gradient); +} + +QGradient QtGradientDialog::gradient() const +{ + return d_ptr->m_ui.gradientEditor->gradient(); +} + +/*! + \property QtGradientDialog::backgroundCheckered + \brief whether the background of widgets able to show the colors with alpha channel is checkered. + + \table + \row + \li \inlineimage qtgradientdialogbackgroundcheckered.png + \li \inlineimage qtgradientdialogbackgroundtransparent.png + \row + \li \snippet doc/src/snippets/code/tools_shared_qtgradienteditor_qtgradientdialog.cpp 2 + \li \snippet doc/src/snippets/code/tools_shared_qtgradienteditor_qtgradientdialog.cpp 3 + \endtable + + When this property is set to true (the default) widgets inside gradient dialog like color button, + color sliders, gradient stops editor and gradient editor will show checkered background + in case of transparent colors. Otherwise the background of these widgets is transparent. +*/ + +bool QtGradientDialog::isBackgroundCheckered() const +{ + return d_ptr->m_ui.gradientEditor->isBackgroundCheckered(); +} + +void QtGradientDialog::setBackgroundCheckered(bool checkered) +{ + d_ptr->m_ui.gradientEditor->setBackgroundCheckered(checkered); +} + +/*! + \property QtGradientDialog::detailsVisible + \brief whether details extension is visible. + + When this property is set to true the details extension is visible. By default + this property is set to false and the details extension is hidden. + + \sa detailsButtonVisible +*/ +bool QtGradientDialog::detailsVisible() const +{ + return d_ptr->m_ui.gradientEditor->detailsVisible(); +} + +void QtGradientDialog::setDetailsVisible(bool visible) +{ + d_ptr->m_ui.gradientEditor->setDetailsVisible(visible); +} + +/*! + \property QtGradientDialog::detailsButtonVisible + \brief whether the details button allowing for showing and hiding details extension is visible. + + When this property is set to true (the default) the details button is visible and the user + can show and hide details extension interactively. Otherwise the button is hidden and the details + extension is always visible or hidded depending on the value of detailsVisible property. + + \sa detailsVisible +*/ +bool QtGradientDialog::isDetailsButtonVisible() const +{ + return d_ptr->m_ui.gradientEditor->isDetailsButtonVisible(); +} + +void QtGradientDialog::setDetailsButtonVisible(bool visible) +{ + d_ptr->m_ui.gradientEditor->setDetailsButtonVisible(visible); +} + +/*! + Returns the current QColor::Spec used for the color sliders in the dialog. +*/ +QColor::Spec QtGradientDialog::spec() const +{ + return d_ptr->m_ui.gradientEditor->spec(); +} + +/*! + Sets the current QColor::Spec to \a spec used for the color sliders in the dialog. +*/ +void QtGradientDialog::setSpec(QColor::Spec spec) +{ + d_ptr->m_ui.gradientEditor->setSpec(spec); +} + +/*! + Executes a modal gradient dialog, lets the user to specify a gradient, and returns that gradient. + + If the user clicks \gui OK, the gradient specified by the user is returned. If the user clicks \gui Cancel, the \a initial gradient is returned. + + The dialog is constructed with the given \a parent. \a caption is shown as the window title of the dialog and + \a initial is the initial gradient shown in the dialog. If the \a ok parameter is not-null, + the value it refers to is set to true if the user clicks \gui OK, and set to false if the user clicks \gui Cancel. +*/ +QGradient QtGradientDialog::getGradient(bool *ok, const QGradient &initial, QWidget *parent, const QString &caption) +{ + QtGradientDialog dlg(parent); + if (!caption.isEmpty()) + dlg.setWindowTitle(caption); + dlg.setGradient(initial); + const int res = dlg.exec(); + if (ok) { + *ok = (res == QDialog::Accepted) ? true : false; + } + if (res == QDialog::Accepted) + return dlg.gradient(); + return initial; +} + +/*! + This method calls getGradient(ok, QLinearGradient(), parent, caption). +*/ +QGradient QtGradientDialog::getGradient(bool *ok, QWidget *parent, const QString &caption) +{ + return getGradient(ok, QLinearGradient(), parent, caption); +} + +QT_END_NAMESPACE + +#include "qtgradientdialog.moc" diff --git a/src/shared/qtgradienteditor/qtgradientdialog.ui b/src/shared/qtgradienteditor/qtgradientdialog.ui new file mode 100644 index 00000000000..fdf3c4dc080 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientdialog.ui @@ -0,0 +1,85 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + QtGradientDialog + + + + 0 + 0 + 178 + 81 + + + + Edit Gradient + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + QtGradientEditor + QFrame +
qtgradienteditor_p.h
+ 1 +
+
+ + + + buttonBox + accepted() + QtGradientDialog + accept() + + + 72 + 224 + + + 21 + 243 + + + + + buttonBox + rejected() + QtGradientDialog + reject() + + + 168 + 233 + + + 152 + 251 + + + + +
diff --git a/src/shared/qtgradienteditor/qtgradientdialog_p.h b/src/shared/qtgradienteditor/qtgradientdialog_p.h new file mode 100644 index 00000000000..1cf18717d02 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientdialog_p.h @@ -0,0 +1,59 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTGRADIENTDIALOG_H +#define QTGRADIENTDIALOG_H + +#include + +QT_BEGIN_NAMESPACE + +class QtGradientDialog : public QDialog +{ + Q_OBJECT + Q_PROPERTY(QGradient gradient READ gradient WRITE setGradient) + Q_PROPERTY(bool backgroundCheckered READ isBackgroundCheckered WRITE setBackgroundCheckered) + Q_PROPERTY(bool detailsVisible READ detailsVisible WRITE setDetailsVisible) + Q_PROPERTY(bool detailsButtonVisible READ isDetailsButtonVisible WRITE setDetailsButtonVisible) +public: + QtGradientDialog(QWidget *parent = 0); + ~QtGradientDialog(); + + void setGradient(const QGradient &gradient); + QGradient gradient() const; + + bool isBackgroundCheckered() const; + void setBackgroundCheckered(bool checkered); + + bool detailsVisible() const; + void setDetailsVisible(bool visible); + + bool isDetailsButtonVisible() const; + void setDetailsButtonVisible(bool visible); + + QColor::Spec spec() const; + void setSpec(QColor::Spec spec); + + static QGradient getGradient(bool *ok, const QGradient &initial, QWidget *parent = 0, const QString &caption = QString()); + static QGradient getGradient(bool *ok, QWidget *parent = 0, const QString &caption = QString()); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtGradientDialog) + Q_DISABLE_COPY_MOVE(QtGradientDialog) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtgradienteditor.cpp b/src/shared/qtgradienteditor/qtgradienteditor.cpp new file mode 100644 index 00000000000..3adf910b4e2 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradienteditor.cpp @@ -0,0 +1,926 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradienteditor_p.h" +#include "qtgradientstopscontroller_p.h" +#include "ui_qtgradienteditor.h" + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QtGradientEditorPrivate : public QObject +{ + Q_OBJECT + QtGradientEditor *q_ptr; + Q_DECLARE_PUBLIC(QtGradientEditor) +public: + QtGradientEditorPrivate(QtGradientEditor *q); + + void setBackgroundCheckered(bool checkered); + + void slotGradientStopsChanged(const QGradientStops &stops); + void slotTypeChanged(int type); + void slotSpreadChanged(int spread); + void slotStartLinearXChanged(double value); + void slotStartLinearYChanged(double value); + void slotEndLinearXChanged(double value); + void slotEndLinearYChanged(double value); + void slotCentralRadialXChanged(double value); + void slotCentralRadialYChanged(double value); + void slotFocalRadialXChanged(double value); + void slotFocalRadialYChanged(double value); + void slotRadiusRadialChanged(double value); + void slotCentralConicalXChanged(double value); + void slotCentralConicalYChanged(double value); + void slotAngleConicalChanged(double value); + + void slotDetailsChanged(bool details); + + void startLinearChanged(QPointF point); + void endLinearChanged(QPointF point); + void centralRadialChanged(QPointF point); + void focalRadialChanged(QPointF point); + void radiusRadialChanged(qreal radius); + void centralConicalChanged(const QPointF &point); + void angleConicalChanged(qreal angle); + + void setStartLinear(QPointF point); + void setEndLinear(QPointF point); + void setCentralRadial(QPointF point); + void setFocalRadial(QPointF point); + void setRadiusRadial(qreal radius); + void setCentralConical(QPointF point); + void setAngleConical(qreal angle); + + void setType(QGradient::Type type); + void showDetails(bool details); + + using DoubleSlotPtr = void (QtGradientEditorPrivate::*)(double); + void setupSpinBox(QDoubleSpinBox *spinBox, DoubleSlotPtr slot, double max = 1.0, double step = 0.01, int decimals = 3); + void reset(); + void setLayout(bool details); + void layoutDetails(bool details); + bool row4Visible() const; + bool row5Visible() const; + int extensionWidthHint() const; + + void setCombos(bool combos); + + QGradient gradient() const; + void updateGradient(bool emitSignal); + + Ui::QtGradientEditor m_ui; + QtGradientStopsController *m_gradientStopsController; + + QDoubleSpinBox *startLinearXSpinBox = nullptr; + QDoubleSpinBox *startLinearYSpinBox = nullptr; + QDoubleSpinBox *endLinearXSpinBox = nullptr; + QDoubleSpinBox *endLinearYSpinBox = nullptr; + QDoubleSpinBox *centralRadialXSpinBox = nullptr; + QDoubleSpinBox *centralRadialYSpinBox = nullptr; + QDoubleSpinBox *focalRadialXSpinBox = nullptr; + QDoubleSpinBox *focalRadialYSpinBox = nullptr; + QDoubleSpinBox *radiusRadialSpinBox = nullptr; + QDoubleSpinBox *centralConicalXSpinBox = nullptr; + QDoubleSpinBox *centralConicalYSpinBox = nullptr; + QDoubleSpinBox *angleConicalSpinBox = nullptr; + + QButtonGroup *m_typeGroup = nullptr; + QButtonGroup *m_spreadGroup = nullptr; + + QGradient::Type m_type = QGradient::RadialGradient; + + QGridLayout *m_gridLayout = nullptr; + QWidget *m_hiddenWidget = nullptr; + QGridLayout *m_hiddenLayout = nullptr; + bool m_details = false; + bool m_detailsButtonVisible = true; + bool m_backgroundCheckered = true; + + QGradient m_gradient; + + bool m_combos = true; +}; + +QtGradientEditorPrivate::QtGradientEditorPrivate(QtGradientEditor *q) + : q_ptr(q) + , m_gradientStopsController(new QtGradientStopsController(this)) + , m_gradient(QLinearGradient()) +{ + m_ui.setupUi(q_ptr); + m_gradientStopsController->setUi(&m_ui); + reset(); + setType(QGradient::LinearGradient); + setCombos(!m_combos); + + showDetails(m_details); + setBackgroundCheckered(m_backgroundCheckered); + + setStartLinear(QPointF(0, 0)); + setEndLinear(QPointF(1, 1)); + setCentralRadial(QPointF(0.5, 0.5)); + setFocalRadial(QPointF(0.5, 0.5)); + setRadiusRadial(0.5); + setCentralConical(QPointF(0.5, 0.5)); + setAngleConical(0); + + QIcon icon; + icon.addPixmap(q_ptr->style()->standardPixmap(QStyle::SP_ArrowRight), QIcon::Normal, QIcon::Off); + icon.addPixmap(q_ptr->style()->standardPixmap(QStyle::SP_ArrowLeft), QIcon::Normal, QIcon::On); + m_ui.detailsButton->setIcon(icon); + + connect(m_ui.detailsButton, &QAbstractButton::clicked, + this, &QtGradientEditorPrivate::slotDetailsChanged); + connect(m_gradientStopsController, &QtGradientStopsController::gradientStopsChanged, + this, &QtGradientEditorPrivate::slotGradientStopsChanged); + + QIcon iconLinear(":/qt-project.org/qtgradienteditor/images/typelinear.png"_L1); + QIcon iconRadial(":/qt-project.org/qtgradienteditor/images/typeradial.png"_L1); + QIcon iconConical(":/qt-project.org/qtgradienteditor/images/typeconical.png"_L1); + + m_ui.typeComboBox->addItem(iconLinear, QtGradientEditor::tr("Linear")); + m_ui.typeComboBox->addItem(iconRadial, QtGradientEditor::tr("Radial")); + m_ui.typeComboBox->addItem(iconConical, QtGradientEditor::tr("Conical")); + + m_ui.linearButton->setIcon(iconLinear); + m_ui.radialButton->setIcon(iconRadial); + m_ui.conicalButton->setIcon(iconConical); + + m_typeGroup = new QButtonGroup(this); + m_typeGroup->addButton(m_ui.linearButton, 0); + m_typeGroup->addButton(m_ui.radialButton, 1); + m_typeGroup->addButton(m_ui.conicalButton, 2); + + connect(m_typeGroup, &QButtonGroup::idClicked, + this, &QtGradientEditorPrivate::slotTypeChanged); + connect(m_ui.typeComboBox, &QComboBox::activated, + this, &QtGradientEditorPrivate::slotTypeChanged); + + QIcon iconPad(":/qt-project.org/qtgradienteditor/images/spreadpad.png"_L1); + QIcon iconRepeat(":/qt-project.org/qtgradienteditor/images/spreadrepeat.png"_L1); + QIcon iconReflect(":/qt-project.org/qtgradienteditor/images/spreadreflect.png"_L1); + + m_ui.spreadComboBox->addItem(iconPad, QtGradientEditor::tr("Pad")); + m_ui.spreadComboBox->addItem(iconRepeat, QtGradientEditor::tr("Repeat")); + m_ui.spreadComboBox->addItem(iconReflect, QtGradientEditor::tr("Reflect")); + + m_ui.padButton->setIcon(iconPad); + m_ui.repeatButton->setIcon(iconRepeat); + m_ui.reflectButton->setIcon(iconReflect); + + m_spreadGroup = new QButtonGroup(this); + m_spreadGroup->addButton(m_ui.padButton, 0); + m_spreadGroup->addButton(m_ui.repeatButton, 1); + m_spreadGroup->addButton(m_ui.reflectButton, 2); + connect(m_spreadGroup, &QButtonGroup::idClicked, + this, &QtGradientEditorPrivate::slotSpreadChanged); + connect(m_ui.spreadComboBox, &QComboBox::activated, + this, &QtGradientEditorPrivate::slotSpreadChanged); + + connect(m_ui.gradientWidget, &QtGradientWidget::startLinearChanged, + this, &QtGradientEditorPrivate::startLinearChanged); + connect(m_ui.gradientWidget, &QtGradientWidget::endLinearChanged, + this, &QtGradientEditorPrivate::endLinearChanged); + connect(m_ui.gradientWidget, &QtGradientWidget::centralRadialChanged, + this, &QtGradientEditorPrivate::centralRadialChanged); + connect(m_ui.gradientWidget, &QtGradientWidget::focalRadialChanged, + this, &QtGradientEditorPrivate::focalRadialChanged); + connect(m_ui.gradientWidget, &QtGradientWidget::radiusRadialChanged, + this, &QtGradientEditorPrivate::radiusRadialChanged); + connect(m_ui.gradientWidget, &QtGradientWidget::centralConicalChanged, + this, &QtGradientEditorPrivate::centralConicalChanged); + connect(m_ui.gradientWidget, &QtGradientWidget::angleConicalChanged, + this, &QtGradientEditorPrivate::angleConicalChanged); + + QGradientStops stops = gradient().stops(); + m_gradientStopsController->setGradientStops(stops); + m_ui.gradientWidget->setGradientStops(stops); +} + +QGradient QtGradientEditorPrivate::gradient() const +{ + QGradient *gradient = nullptr; + switch (m_ui.gradientWidget->gradientType()) { + case QGradient::LinearGradient: + gradient = new QLinearGradient(m_ui.gradientWidget->startLinear(), + m_ui.gradientWidget->endLinear()); + break; + case QGradient::RadialGradient: + gradient = new QRadialGradient(m_ui.gradientWidget->centralRadial(), + m_ui.gradientWidget->radiusRadial(), + m_ui.gradientWidget->focalRadial()); + break; + case QGradient::ConicalGradient: + gradient = new QConicalGradient(m_ui.gradientWidget->centralConical(), + m_ui.gradientWidget->angleConical()); + break; + default: + break; + } + if (!gradient) + return QGradient(); + gradient->setStops(m_ui.gradientWidget->gradientStops()); + gradient->setSpread(m_ui.gradientWidget->gradientSpread()); + gradient->setCoordinateMode(QGradient::StretchToDeviceMode); + QGradient gr = *gradient; + delete gradient; + return gr; +} + +void QtGradientEditorPrivate::updateGradient(bool emitSignal) +{ + QGradient grad = gradient(); + if (m_gradient == grad) + return; + + m_gradient = grad; + if (emitSignal) + emit q_ptr->gradientChanged(m_gradient); +} + +void QtGradientEditorPrivate::setCombos(bool combos) +{ + if (m_combos == combos) + return; + + m_combos = combos; + m_ui.linearButton->setVisible(!m_combos); + m_ui.radialButton->setVisible(!m_combos); + m_ui.conicalButton->setVisible(!m_combos); + m_ui.padButton->setVisible(!m_combos); + m_ui.repeatButton->setVisible(!m_combos); + m_ui.reflectButton->setVisible(!m_combos); + m_ui.typeComboBox->setVisible(m_combos); + m_ui.spreadComboBox->setVisible(m_combos); +} + +void QtGradientEditorPrivate::setLayout(bool details) +{ + QHBoxLayout *hboxLayout = new QHBoxLayout(); + hboxLayout->setObjectName(QString::fromUtf8("hboxLayout")); + hboxLayout->addWidget(m_ui.typeComboBox); + hboxLayout->addWidget(m_ui.spreadComboBox); + QHBoxLayout *typeLayout = new QHBoxLayout(); + typeLayout->setSpacing(0); + typeLayout->addWidget(m_ui.linearButton); + typeLayout->addWidget(m_ui.radialButton); + typeLayout->addWidget(m_ui.conicalButton); + hboxLayout->addLayout(typeLayout); + QHBoxLayout *spreadLayout = new QHBoxLayout(); + spreadLayout->setSpacing(0); + spreadLayout->addWidget(m_ui.padButton); + spreadLayout->addWidget(m_ui.repeatButton); + spreadLayout->addWidget(m_ui.reflectButton); + hboxLayout->addLayout(spreadLayout); + hboxLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); + hboxLayout->addWidget(m_ui.detailsButton); + m_gridLayout->addLayout(hboxLayout, 0, 0, 1, 2); + int span = 1; + if (details) + span = 7; + m_gridLayout->addWidget(m_ui.frame, 1, 0, span, 2); + int row = 2; + if (details) { + row = 8; + span = 4; + } + m_gridLayout->addWidget(m_ui.gradientStopsWidget, row, 0, span, 2); + QHBoxLayout *hboxLayout1 = new QHBoxLayout(); + hboxLayout1->setObjectName(QString::fromUtf8("hboxLayout1")); + hboxLayout1->addWidget(m_ui.colorLabel); + hboxLayout1->addWidget(m_ui.colorButton); + hboxLayout1->addWidget(m_ui.hsvRadioButton); + hboxLayout1->addWidget(m_ui.rgbRadioButton); + hboxLayout1->addItem(new QSpacerItem(16, 23, QSizePolicy::Expanding, QSizePolicy::Minimum)); + int addRow = 0; + if (details) + addRow = 9; + m_gridLayout->addLayout(hboxLayout1, 3 + addRow, 0, 1, 2); + m_gridLayout->addWidget(m_ui.hLabel, 4 + addRow, 0, 1, 1); + m_gridLayout->addWidget(m_ui.frame_2, 4 + addRow, 1, 1, 1); + m_gridLayout->addWidget(m_ui.sLabel, 5 + addRow, 0, 1, 1); + m_gridLayout->addWidget(m_ui.frame_5, 5 + addRow, 1, 1, 1); + m_gridLayout->addWidget(m_ui.vLabel, 6 + addRow, 0, 1, 1); + m_gridLayout->addWidget(m_ui.frame_3, 6 + addRow, 1, 1, 1); + m_gridLayout->addWidget(m_ui.aLabel, 7 + addRow, 0, 1, 1); + m_gridLayout->addWidget(m_ui.frame_4, 7 + addRow, 1, 1, 1); + + if (details) { + layoutDetails(details); + } +} + +void QtGradientEditorPrivate::layoutDetails(bool details) +{ + QGridLayout *gridLayout = m_gridLayout; + int col = 2; + if (!details) { + col = 0; + if (!m_hiddenWidget) { + m_hiddenWidget = new QWidget(); + m_hiddenLayout = new QGridLayout(m_hiddenWidget); + m_hiddenLayout->setContentsMargins(0, 0, 0, 0); + m_hiddenLayout->setSizeConstraint(QLayout::SetFixedSize); + } + gridLayout = m_hiddenLayout; + } + gridLayout->addWidget(m_ui.label1, 1, col + 0, 1, 1); + gridLayout->addWidget(m_ui.spinBox1, 1, col + 1, 1, 1); + gridLayout->addWidget(m_ui.label2, 2, col + 0, 1, 1); + gridLayout->addWidget(m_ui.spinBox2, 2, col + 1, 1, 1); + gridLayout->addWidget(m_ui.label3, 3, col + 0, 1, 1); + gridLayout->addWidget(m_ui.spinBox3, 3, col + 1, 1, 1); + gridLayout->addWidget(m_ui.label4, 4, col + 0, 1, 1); + gridLayout->addWidget(m_ui.spinBox4, 4, col + 1, 1, 1); + gridLayout->addWidget(m_ui.label5, 5, col + 0, 1, 1); + gridLayout->addWidget(m_ui.spinBox5, 5, col + 1, 1, 1); + gridLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding), 6, col + 0, 1, 1); + gridLayout->addWidget(m_ui.line1Widget, 7, col + 0, 1, 2); + gridLayout->addWidget(m_ui.zoomLabel, 8, col + 0, 1, 1); + gridLayout->addWidget(m_ui.zoomWidget, 8, col + 1, 1, 1); + gridLayout->addWidget(m_ui.zoomButtonsWidget, 9, col + 0, 1, 1); + gridLayout->addWidget(m_ui.zoomAllButton, 9, col + 1, 1, 1); + gridLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Preferred), 10, col + 0, 1, 1); + gridLayout->addWidget(m_ui.line2Widget, 11, col + 0, 1, 2); + gridLayout->addWidget(m_ui.positionLabel, 12, col + 0, 1, 1); + gridLayout->addWidget(m_ui.positionWidget, 12, col + 1, 1, 1); + gridLayout->addWidget(m_ui.hueLabel, 13, col + 0, 1, 1); + gridLayout->addWidget(m_ui.hueWidget, 13, col + 1, 1, 1); + gridLayout->addWidget(m_ui.saturationLabel, 14, col + 0, 1, 1); + gridLayout->addWidget(m_ui.saturationWidget, 14, col + 1, 1, 1); + gridLayout->addWidget(m_ui.valueLabel, 15, col + 0, 1, 1); + gridLayout->addWidget(m_ui.valueWidget, 15, col + 1, 1, 1); + gridLayout->addWidget(m_ui.alphaLabel, 16, col + 0, 1, 1); + gridLayout->addWidget(m_ui.alphaWidget, 16, col + 1, 1, 1); + + if (details) { + if (m_hiddenLayout) { + delete m_hiddenLayout; + m_hiddenLayout = 0; + } + if (m_hiddenWidget) { + delete m_hiddenWidget; + m_hiddenWidget = 0; + } + } +} + +int QtGradientEditorPrivate::extensionWidthHint() const +{ + if (m_details) + return q_ptr->size().width() - m_ui.gradientStopsWidget->size().width(); + + const int space = m_ui.spinBox1->geometry().left() - m_ui.label1->geometry().right(); + + return m_hiddenLayout->minimumSize().width() + space; +} + +void QtGradientEditorPrivate::slotDetailsChanged(bool details) +{ + if (m_details == details) + return; + + showDetails(details); +} + +bool QtGradientEditorPrivate::row4Visible() const +{ + if (m_type == QGradient::ConicalGradient) + return false; + return true; +} + +bool QtGradientEditorPrivate::row5Visible() const +{ + if (m_type == QGradient::RadialGradient) + return true; + return false; +} + +void QtGradientEditorPrivate::showDetails(bool details) +{ + bool blocked = m_ui.detailsButton->signalsBlocked(); + m_ui.detailsButton->blockSignals(true); + m_ui.detailsButton->setChecked(details); + m_ui.detailsButton->blockSignals(blocked); + + bool updates = q_ptr->updatesEnabled(); + q_ptr->setUpdatesEnabled(false); + + if (m_gridLayout) { + m_gridLayout->setEnabled(false); + delete m_gridLayout; + m_gridLayout = 0; + } + + if (!details) { + layoutDetails(details); + } + + emit q_ptr->aboutToShowDetails(details, extensionWidthHint()); + m_details = details; + + m_gridLayout = new QGridLayout(q_ptr); + m_gridLayout->setEnabled(false); + m_gridLayout->setObjectName(QString::fromUtf8("gridLayout")); + m_gridLayout->setContentsMargins(0, 0, 0, 0); + + m_ui.label4->setVisible(row4Visible()); + m_ui.label5->setVisible(row5Visible()); + m_ui.spinBox4->setVisible(row4Visible()); + m_ui.spinBox5->setVisible(row5Visible()); + + setLayout(details); + m_gridLayout->setEnabled(true); + + q_ptr->setUpdatesEnabled(updates); + q_ptr->update(); +} + +void QtGradientEditorPrivate::setupSpinBox(QDoubleSpinBox *spinBox, DoubleSlotPtr slot, + double max, double step, int decimals) +{ + bool blocked = spinBox->signalsBlocked(); + spinBox->blockSignals(true); + spinBox->setDecimals(decimals); + spinBox->setMaximum(max); + spinBox->setSingleStep(step); + spinBox->blockSignals(blocked); + QObject::connect(spinBox, &QDoubleSpinBox::valueChanged, this, slot); +} + +void QtGradientEditorPrivate::reset() +{ + startLinearXSpinBox = 0; + startLinearYSpinBox = 0; + endLinearXSpinBox = 0; + endLinearYSpinBox = 0; + centralRadialXSpinBox = 0; + centralRadialYSpinBox = 0; + focalRadialXSpinBox = 0; + focalRadialYSpinBox = 0; + radiusRadialSpinBox = 0; + centralConicalXSpinBox = 0; + centralConicalYSpinBox = 0; + angleConicalSpinBox = 0; +} + +void QtGradientEditorPrivate::setType(QGradient::Type type) +{ + if (m_type == type) + return; + + m_type = type; + m_ui.spinBox1->disconnect(this); + m_ui.spinBox2->disconnect(this); + m_ui.spinBox3->disconnect(this); + m_ui.spinBox4->disconnect(this); + m_ui.spinBox5->disconnect(this); + + reset(); + + bool ena = true; + + if (m_gridLayout) { + ena = m_gridLayout->isEnabled(); + m_gridLayout->setEnabled(false); + } + + bool spreadEnabled = true; + + if (type == QGradient::LinearGradient) { + startLinearXSpinBox = m_ui.spinBox1; + setupSpinBox(startLinearXSpinBox, &QtGradientEditorPrivate::slotStartLinearXChanged); + m_ui.label1->setText(QCoreApplication::translate("QtGradientEditor", "Start X")); + + startLinearYSpinBox = m_ui.spinBox2; + setupSpinBox(startLinearYSpinBox, &QtGradientEditorPrivate::slotStartLinearYChanged); + m_ui.label2->setText(QCoreApplication::translate("QtGradientEditor", "Start Y")); + + endLinearXSpinBox = m_ui.spinBox3; + setupSpinBox(endLinearXSpinBox, &QtGradientEditorPrivate::slotEndLinearXChanged); + m_ui.label3->setText(QCoreApplication::translate("QtGradientEditor", "Final X")); + + endLinearYSpinBox = m_ui.spinBox4; + setupSpinBox(endLinearYSpinBox, &QtGradientEditorPrivate::slotEndLinearYChanged); + m_ui.label4->setText(QCoreApplication::translate("QtGradientEditor", "Final Y")); + + setStartLinear(m_ui.gradientWidget->startLinear()); + setEndLinear(m_ui.gradientWidget->endLinear()); + } else if (type == QGradient::RadialGradient) { + centralRadialXSpinBox = m_ui.spinBox1; + setupSpinBox(centralRadialXSpinBox, &QtGradientEditorPrivate::slotCentralRadialXChanged); + m_ui.label1->setText(QCoreApplication::translate("QtGradientEditor", "Central X")); + + centralRadialYSpinBox = m_ui.spinBox2; + setupSpinBox(centralRadialYSpinBox, &QtGradientEditorPrivate::slotCentralRadialYChanged); + m_ui.label2->setText(QCoreApplication::translate("QtGradientEditor", "Central Y")); + + focalRadialXSpinBox = m_ui.spinBox3; + setupSpinBox(focalRadialXSpinBox, &QtGradientEditorPrivate::slotFocalRadialXChanged); + m_ui.label3->setText(QCoreApplication::translate("QtGradientEditor", "Focal X")); + + focalRadialYSpinBox = m_ui.spinBox4; + setupSpinBox(focalRadialYSpinBox, &QtGradientEditorPrivate::slotFocalRadialYChanged); + m_ui.label4->setText(QCoreApplication::translate("QtGradientEditor", "Focal Y")); + + radiusRadialSpinBox = m_ui.spinBox5; + setupSpinBox(radiusRadialSpinBox, &QtGradientEditorPrivate::slotRadiusRadialChanged, 2.0); + m_ui.label5->setText(QCoreApplication::translate("QtGradientEditor", "Radius")); + + setCentralRadial(m_ui.gradientWidget->centralRadial()); + setFocalRadial(m_ui.gradientWidget->focalRadial()); + setRadiusRadial(m_ui.gradientWidget->radiusRadial()); + } else if (type == QGradient::ConicalGradient) { + centralConicalXSpinBox = m_ui.spinBox1; + setupSpinBox(centralConicalXSpinBox, &QtGradientEditorPrivate::slotCentralConicalXChanged); + m_ui.label1->setText(QCoreApplication::translate("QtGradientEditor", "Central X")); + + centralConicalYSpinBox = m_ui.spinBox2; + setupSpinBox(centralConicalYSpinBox, &QtGradientEditorPrivate::slotCentralConicalYChanged); + m_ui.label2->setText(QCoreApplication::translate("QtGradientEditor", "Central Y")); + + angleConicalSpinBox = m_ui.spinBox3; + setupSpinBox(angleConicalSpinBox, &QtGradientEditorPrivate::slotAngleConicalChanged, 360.0, 1.0, 1); + m_ui.label3->setText(QCoreApplication::translate("QtGradientEditor", "Angle")); + + setCentralConical(m_ui.gradientWidget->centralConical()); + setAngleConical(m_ui.gradientWidget->angleConical()); + + spreadEnabled = false; + } + m_ui.spreadComboBox->setEnabled(spreadEnabled); + m_ui.padButton->setEnabled(spreadEnabled); + m_ui.repeatButton->setEnabled(spreadEnabled); + m_ui.reflectButton->setEnabled(spreadEnabled); + + m_ui.label4->setVisible(row4Visible()); + m_ui.spinBox4->setVisible(row4Visible()); + m_ui.label5->setVisible(row5Visible()); + m_ui.spinBox5->setVisible(row5Visible()); + + if (m_gridLayout) + m_gridLayout->setEnabled(ena); +} + +void QtGradientEditorPrivate::setBackgroundCheckered(bool checkered) +{ + m_backgroundCheckered = checkered; + m_ui.hueColorLine->setBackgroundCheckered(checkered); + m_ui.saturationColorLine->setBackgroundCheckered(checkered); + m_ui.valueColorLine->setBackgroundCheckered(checkered); + m_ui.alphaColorLine->setBackgroundCheckered(checkered); + m_ui.gradientWidget->setBackgroundCheckered(checkered); + m_ui.gradientStopsWidget->setBackgroundCheckered(checkered); + m_ui.colorButton->setBackgroundCheckered(checkered); +} + +void QtGradientEditorPrivate::slotGradientStopsChanged(const QGradientStops &stops) +{ + m_ui.gradientWidget->setGradientStops(stops); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotTypeChanged(int idx) +{ + QGradient::Type type = QGradient::NoGradient; + if (idx == 0) + type = QGradient::LinearGradient; + else if (idx == 1) + type = QGradient::RadialGradient; + else if (idx == 2) + type = QGradient::ConicalGradient; + setType(type); + m_ui.typeComboBox->setCurrentIndex(idx); + m_typeGroup->button(idx)->setChecked(true); + m_ui.gradientWidget->setGradientType(type); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotSpreadChanged(int spread) +{ + if (spread == 0) { + m_ui.gradientWidget->setGradientSpread(QGradient::PadSpread); + } else if (spread == 1) { + m_ui.gradientWidget->setGradientSpread(QGradient::RepeatSpread); + } else if (spread == 2) { + m_ui.gradientWidget->setGradientSpread(QGradient::ReflectSpread); + } + m_ui.spreadComboBox->setCurrentIndex(spread); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotStartLinearXChanged(double value) +{ + QPointF point = m_ui.gradientWidget->startLinear(); + point.setX(value); + m_ui.gradientWidget->setStartLinear(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotStartLinearYChanged(double value) +{ + QPointF point = m_ui.gradientWidget->startLinear(); + point.setY(value); + m_ui.gradientWidget->setStartLinear(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotEndLinearXChanged(double value) +{ + QPointF point = m_ui.gradientWidget->endLinear(); + point.setX(value); + m_ui.gradientWidget->setEndLinear(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotEndLinearYChanged(double value) +{ + QPointF point = m_ui.gradientWidget->endLinear(); + point.setY(value); + m_ui.gradientWidget->setEndLinear(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotCentralRadialXChanged(double value) +{ + QPointF point = m_ui.gradientWidget->centralRadial(); + point.setX(value); + m_ui.gradientWidget->setCentralRadial(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotCentralRadialYChanged(double value) +{ + QPointF point = m_ui.gradientWidget->centralRadial(); + point.setY(value); + m_ui.gradientWidget->setCentralRadial(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotFocalRadialXChanged(double value) +{ + QPointF point = m_ui.gradientWidget->focalRadial(); + point.setX(value); + m_ui.gradientWidget->setFocalRadial(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotFocalRadialYChanged(double value) +{ + QPointF point = m_ui.gradientWidget->focalRadial(); + point.setY(value); + m_ui.gradientWidget->setFocalRadial(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotRadiusRadialChanged(double value) +{ + m_ui.gradientWidget->setRadiusRadial(value); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotCentralConicalXChanged(double value) +{ + QPointF point = m_ui.gradientWidget->centralConical(); + point.setX(value); + m_ui.gradientWidget->setCentralConical(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotCentralConicalYChanged(double value) +{ + QPointF point = m_ui.gradientWidget->centralConical(); + point.setY(value); + m_ui.gradientWidget->setCentralConical(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::slotAngleConicalChanged(double value) +{ + m_ui.gradientWidget->setAngleConical(value); + updateGradient(true); +} + +void QtGradientEditorPrivate::startLinearChanged(QPointF point) +{ + setStartLinear(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::endLinearChanged(QPointF point) +{ + setEndLinear(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::centralRadialChanged(QPointF point) +{ + setCentralRadial(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::focalRadialChanged(QPointF point) +{ + setFocalRadial(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::radiusRadialChanged(qreal radius) +{ + setRadiusRadial(radius); + updateGradient(true); +} + +void QtGradientEditorPrivate::centralConicalChanged(const QPointF &point) +{ + setCentralConical(point); + updateGradient(true); +} + +void QtGradientEditorPrivate::angleConicalChanged(qreal angle) +{ + setAngleConical(angle); + updateGradient(true); +} + +void QtGradientEditorPrivate::setStartLinear(QPointF point) +{ + if (startLinearXSpinBox) + startLinearXSpinBox->setValue(point.x()); + if (startLinearYSpinBox) + startLinearYSpinBox->setValue(point.y()); +} + +void QtGradientEditorPrivate::setEndLinear(QPointF point) +{ + if (endLinearXSpinBox) + endLinearXSpinBox->setValue(point.x()); + if (endLinearYSpinBox) + endLinearYSpinBox->setValue(point.y()); +} + +void QtGradientEditorPrivate::setCentralRadial(QPointF point) +{ + if (centralRadialXSpinBox) + centralRadialXSpinBox->setValue(point.x()); + if (centralRadialYSpinBox) + centralRadialYSpinBox->setValue(point.y()); +} + +void QtGradientEditorPrivate::setFocalRadial(QPointF point) +{ + if (focalRadialXSpinBox) + focalRadialXSpinBox->setValue(point.x()); + if (focalRadialYSpinBox) + focalRadialYSpinBox->setValue(point.y()); +} + +void QtGradientEditorPrivate::setRadiusRadial(qreal radius) +{ + if (radiusRadialSpinBox) + radiusRadialSpinBox->setValue(radius); +} + +void QtGradientEditorPrivate::setCentralConical(QPointF point) +{ + if (centralConicalXSpinBox) + centralConicalXSpinBox->setValue(point.x()); + if (centralConicalYSpinBox) + centralConicalYSpinBox->setValue(point.y()); +} + +void QtGradientEditorPrivate::setAngleConical(qreal angle) +{ + if (angleConicalSpinBox) + angleConicalSpinBox->setValue(angle); +} + +QtGradientEditor::QtGradientEditor(QWidget *parent) + : QWidget(parent), d_ptr(new QtGradientEditorPrivate(this)) +{ +} + +QtGradientEditor::~QtGradientEditor() +{ + if (d_ptr->m_hiddenWidget) + delete d_ptr->m_hiddenWidget; +} + +void QtGradientEditor::setGradient(const QGradient &grad) +{ + if (grad == gradient()) + return; + + QGradient::Type type = grad.type(); + int idx = 0; + switch (type) { + case QGradient::LinearGradient: idx = 0; break; + case QGradient::RadialGradient: idx = 1; break; + case QGradient::ConicalGradient: idx = 2; break; + default: return; + } + d_ptr->setType(type); + d_ptr->m_ui.typeComboBox->setCurrentIndex(idx); + d_ptr->m_ui.gradientWidget->setGradientType(type); + d_ptr->m_typeGroup->button(idx)->setChecked(true); + + QGradient::Spread spread = grad.spread(); + switch (spread) { + case QGradient::PadSpread: idx = 0; break; + case QGradient::RepeatSpread: idx = 1; break; + case QGradient::ReflectSpread: idx = 2; break; + default: idx = 0; break; + } + d_ptr->m_ui.spreadComboBox->setCurrentIndex(idx); + d_ptr->m_ui.gradientWidget->setGradientSpread(spread); + d_ptr->m_spreadGroup->button(idx)->setChecked(true); + + if (type == QGradient::LinearGradient) { + const QLinearGradient *gr = static_cast(&grad); + d_ptr->setStartLinear(gr->start()); + d_ptr->setEndLinear(gr->finalStop()); + d_ptr->m_ui.gradientWidget->setStartLinear(gr->start()); + d_ptr->m_ui.gradientWidget->setEndLinear(gr->finalStop()); + } else if (type == QGradient::RadialGradient) { + const QRadialGradient *gr = static_cast(&grad); + d_ptr->setCentralRadial(gr->center()); + d_ptr->setFocalRadial(gr->focalPoint()); + d_ptr->setRadiusRadial(gr->radius()); + d_ptr->m_ui.gradientWidget->setCentralRadial(gr->center()); + d_ptr->m_ui.gradientWidget->setFocalRadial(gr->focalPoint()); + d_ptr->m_ui.gradientWidget->setRadiusRadial(gr->radius()); + } else if (type == QGradient::ConicalGradient) { + const QConicalGradient *gr = static_cast(&grad); + d_ptr->setCentralConical(gr->center()); + d_ptr->setAngleConical(gr->angle()); + d_ptr->m_ui.gradientWidget->setCentralConical(gr->center()); + d_ptr->m_ui.gradientWidget->setAngleConical(gr->angle()); + } + + d_ptr->m_gradientStopsController->setGradientStops(grad.stops()); + d_ptr->m_ui.gradientWidget->setGradientStops(grad.stops()); + d_ptr->updateGradient(false); +} + +QGradient QtGradientEditor::gradient() const +{ + return d_ptr->m_gradient; +} + +bool QtGradientEditor::isBackgroundCheckered() const +{ + return d_ptr->m_backgroundCheckered; +} + +void QtGradientEditor::setBackgroundCheckered(bool checkered) +{ + if (d_ptr->m_backgroundCheckered == checkered) + return; + + d_ptr->setBackgroundCheckered(checkered); +} + +bool QtGradientEditor::detailsVisible() const +{ + return d_ptr->m_details; +} + +void QtGradientEditor::setDetailsVisible(bool visible) +{ + if (d_ptr->m_details == visible) + return; + + d_ptr->showDetails(visible); +} + +bool QtGradientEditor::isDetailsButtonVisible() const +{ + return d_ptr->m_detailsButtonVisible; +} + +void QtGradientEditor::setDetailsButtonVisible(bool visible) +{ + if (d_ptr->m_detailsButtonVisible == visible) + return; + + d_ptr->m_detailsButtonVisible = visible; + d_ptr->m_ui.detailsButton->setVisible(visible); +} + +QColor::Spec QtGradientEditor::spec() const +{ + return d_ptr->m_gradientStopsController->spec(); +} + +void QtGradientEditor::setSpec(QColor::Spec spec) +{ + d_ptr->m_gradientStopsController->setSpec(spec); +} + +QT_END_NAMESPACE + +#include "qtgradienteditor.moc" diff --git a/src/shared/qtgradienteditor/qtgradienteditor.ui b/src/shared/qtgradienteditor/qtgradienteditor.ui new file mode 100644 index 00000000000..ed3e7fd48df --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradienteditor.ui @@ -0,0 +1,1341 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + QtGradientEditor + + + + 0 + 0 + 364 + 518 + + + + Form + + + + + 10 + 69 + 193 + 150 + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Gradient Editor + + + This area shows a preview of the gradient being edited. It also allows you to edit parameters specific to the gradient's type such as start and final point, radius, etc. by drag & drop. + + + + + + + + + 209 + 69 + 64 + 23 + + + + 1 + + + + + + 279 + 69 + 73 + 23 + + + + false + + + 3 + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + 209 + 99 + 64 + 23 + + + + 2 + + + + + + 279 + 99 + 73 + 23 + + + + false + + + 3 + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + 209 + 129 + 64 + 23 + + + + 3 + + + + + + 279 + 129 + 73 + 23 + + + + false + + + 3 + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + 209 + 159 + 64 + 23 + + + + 4 + + + + + + 279 + 159 + 73 + 23 + + + + false + + + 3 + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + 209 + 189 + 64 + 23 + + + + 5 + + + + + + 279 + 189 + 73 + 23 + + + + false + + + 3 + + + 1.000000000000000 + + + 0.010000000000000 + + + + + + 10 + 225 + 193 + 67 + + + + Gradient Stops Editor + + + This area allows you to edit gradient stops. Double click on the existing stop handle to duplicate it. Double click outside of the existing stop handles to create a new stop. Drag & drop the handle to reposition it. Use right mouse button to popup context menu with extra actions. + + + + + + 209 + 231 + 64 + 23 + + + + Zoom + + + + + + 279 + 260 + 72 + 26 + + + + + 0 + 0 + + + + Reset Zoom + + + Reset Zoom + + + + + + 209 + 304 + 64 + 23 + + + + Position + + + + + + 10 + 335 + 32 + 18 + + + + + 0 + 0 + + + + Hue + + + H + + + + + + 48 + 333 + 155 + 23 + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Hue + + + + + + + + + 209 + 335 + 64 + 18 + + + + + 0 + 0 + + + + Hue + + + + + + 10 + 364 + 32 + 18 + + + + + 0 + 0 + + + + Saturation + + + S + + + + + + 48 + 362 + 155 + 23 + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Saturation + + + + + + + + + 209 + 364 + 64 + 18 + + + + + 0 + 0 + + + + Sat + + + + + + 10 + 393 + 32 + 18 + + + + + 0 + 0 + + + + Value + + + V + + + + + + 48 + 391 + 155 + 23 + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Value + + + + + + + + + 209 + 393 + 64 + 18 + + + + + 0 + 0 + + + + Val + + + + + + 10 + 422 + 32 + 18 + + + + + 0 + 0 + + + + Alpha + + + A + + + + + + 48 + 420 + 155 + 23 + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Alpha + + + + + + + + + 209 + 422 + 64 + 18 + + + + + 0 + 0 + + + + Alpha + + + + + + 10 + 40 + 79 + 22 + + + + Type + + + + + + 96 + 40 + 72 + 22 + + + + Spread + + + + + + 10 + 298 + 32 + 29 + + + + + 0 + 0 + + + + Color + + + + + + 48 + 300 + 26 + 25 + + + + Current stop's color + + + + + + + + + 80 + 301 + 49 + 23 + + + + + 0 + 0 + + + + Show HSV specification + + + HSV + + + true + + + + + + 135 + 301 + 49 + 23 + + + + + 0 + 0 + + + + Show RGB specification + + + RGB + + + + + + 279 + 304 + 73 + 23 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Current stop's position + + + false + + + 3 + + + 0.000000000000000 + + + 1.000000000000000 + + + 0.010000000000000 + + + 0.000000000000000 + + + + + + + + + 279 + 333 + 73 + 23 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + 359 + + + + + + + + + 279 + 362 + 73 + 23 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + 255 + + + + + + + + + 279 + 391 + 73 + 23 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + 255 + + + + + + + + + 279 + 420 + 73 + 23 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + 255 + + + + + + + + + 279 + 231 + 73 + 23 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + % + + + 100 + + + 10000 + + + 100 + + + 100 + + + + + + + + + 209 + 219 + 143 + 16 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + + 209 + 292 + 143 + 16 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + + + + + + + 209 + 260 + 64 + 26 + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Zoom In + + + + + + + Zoom Out + + + + + + + Qt::Horizontal + + + + 0 + 20 + + + + + + + + + + 176 + 40 + 25 + 22 + + + + + 0 + 0 + + + + Toggle details extension + + + > + + + true + + + true + + + + + + 10 + 10 + 30 + 26 + + + + Linear Type + + + ... + + + true + + + true + + + + + + 40 + 10 + 30 + 26 + + + + Radial Type + + + ... + + + true + + + true + + + + + + 70 + 10 + 30 + 26 + + + + Conical Type + + + ... + + + true + + + true + + + + + + 110 + 10 + 30 + 26 + + + + Pad Spread + + + ... + + + true + + + true + + + + + + 140 + 10 + 30 + 26 + + + + Repeat Spread + + + ... + + + true + + + true + + + + + + 170 + 10 + 30 + 26 + + + + Reflect Spread + + + ... + + + true + + + true + + + + + + QtColorButton + QToolButton +
qtcolorbutton_p.h
+
+ + QtColorLine + QWidget +
qtcolorline_p.h
+ 1 +
+ + QtGradientStopsWidget + QWidget +
qtgradientstopswidget_p.h
+ 1 +
+ + QtGradientWidget + QWidget +
qtgradientwidget_p.h
+ 1 +
+
+ + typeComboBox + spreadComboBox + detailsButton + spinBox1 + spinBox2 + spinBox3 + spinBox4 + spinBox5 + zoomSpinBox + zoomInButton + zoomOutButton + zoomAllButton + colorButton + hsvRadioButton + rgbRadioButton + positionSpinBox + hueSpinBox + saturationSpinBox + valueSpinBox + alphaSpinBox + + + +
diff --git a/src/shared/qtgradienteditor/qtgradienteditor_p.h b/src/shared/qtgradienteditor/qtgradienteditor_p.h new file mode 100644 index 00000000000..8faf10f867e --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradienteditor_p.h @@ -0,0 +1,60 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTGRADIENTEDITOR_H +#define QTGRADIENTEDITOR_H + +#include + +QT_BEGIN_NAMESPACE + +class QtGradientEditor : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QGradient gradient READ gradient WRITE setGradient) + Q_PROPERTY(bool backgroundCheckered READ isBackgroundCheckered WRITE setBackgroundCheckered) + Q_PROPERTY(bool detailsVisible READ detailsVisible WRITE setDetailsVisible) + Q_PROPERTY(bool detailsButtonVisible READ isDetailsButtonVisible WRITE setDetailsButtonVisible) +public: + QtGradientEditor(QWidget *parent = 0); + ~QtGradientEditor(); + + void setGradient(const QGradient &gradient); + QGradient gradient() const; + + bool isBackgroundCheckered() const; + void setBackgroundCheckered(bool checkered); + + bool detailsVisible() const; + void setDetailsVisible(bool visible); + + bool isDetailsButtonVisible() const; + void setDetailsButtonVisible(bool visible); + + QColor::Spec spec() const; + void setSpec(QColor::Spec spec); + +signals: + void gradientChanged(const QGradient &gradient); + void aboutToShowDetails(bool details, int extenstionWidthHint); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtGradientEditor) + Q_DISABLE_COPY_MOVE(QtGradientEditor) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtgradientmanager.cpp b/src/shared/qtgradienteditor/qtgradientmanager.cpp new file mode 100644 index 00000000000..c1d5de72810 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientmanager.cpp @@ -0,0 +1,95 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradientmanager_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +QtGradientManager::QtGradientManager(QObject *parent) + : QObject(parent) +{ +} + +QMap QtGradientManager::gradients() const +{ + return m_idToGradient; +} + +QString QtGradientManager::uniqueId(const QString &id) const +{ + if (!m_idToGradient.contains(id)) + return id; + + QString base = id; + while (base.size() > 0 && base.at(base.size() - 1).isDigit()) + base = base.left(base.size() - 1); + QString newId = base; + int counter = 0; + while (m_idToGradient.contains(newId)) { + ++counter; + newId = base + QString::number(counter); + } + return newId; +} + +QString QtGradientManager::addGradient(const QString &id, const QGradient &gradient) +{ + QString newId = uniqueId(id); + + m_idToGradient[newId] = gradient; + + emit gradientAdded(newId, gradient); + + return newId; +} + +void QtGradientManager::removeGradient(const QString &id) +{ + if (!m_idToGradient.contains(id)) + return; + + emit gradientRemoved(id); + + m_idToGradient.remove(id); +} + +void QtGradientManager::renameGradient(const QString &id, const QString &newId) +{ + if (!m_idToGradient.contains(id)) + return; + + if (newId == id) + return; + + QString changedId = uniqueId(newId); + QGradient gradient = m_idToGradient.value(id); + + emit gradientRenamed(id, changedId); + + m_idToGradient.remove(id); + m_idToGradient[changedId] = gradient; +} + +void QtGradientManager::changeGradient(const QString &id, const QGradient &newGradient) +{ + if (!m_idToGradient.contains(id)) + return; + + if (m_idToGradient.value(id) == newGradient) + return; + + emit gradientChanged(id, newGradient); + + m_idToGradient[id] = newGradient; +} + +void QtGradientManager::clear() +{ + const QMap grads = gradients(); + for (auto it = grads.cbegin(), end = grads.cend(); it != end; ++it) + removeGradient(it.key()); +} + +QT_END_NAMESPACE diff --git a/src/shared/qtgradienteditor/qtgradientmanager_p.h b/src/shared/qtgradienteditor/qtgradientmanager_p.h new file mode 100644 index 00000000000..911af189a55 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientmanager_p.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef GRADIENTMANAGER_H +#define GRADIENTMANAGER_H + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QGradient; +class QPixmap; +class QColor; + +class QtGradientManager : public QObject +{ + Q_OBJECT +public: + QtGradientManager(QObject *parent = 0); + + QMap gradients() const; + + QString uniqueId(const QString &id) const; + +public slots: + + QString addGradient(const QString &id, const QGradient &gradient); + void renameGradient(const QString &id, const QString &newId); + void changeGradient(const QString &id, const QGradient &newGradient); + void removeGradient(const QString &id); + + //utils + void clear(); + +signals: + + void gradientAdded(const QString &id, const QGradient &gradient); + void gradientRenamed(const QString &id, const QString &newId); + void gradientChanged(const QString &id, const QGradient &newGradient); + void gradientRemoved(const QString &id); + +private: + + QMap m_idToGradient; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtgradientstopscontroller.cpp b/src/shared/qtgradienteditor/qtgradientstopscontroller.cpp new file mode 100644 index 00000000000..d0d56e97f2e --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientstopscontroller.cpp @@ -0,0 +1,671 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradientstopscontroller_p.h" +#include "ui_qtgradienteditor.h" +#include "qtgradientstopsmodel_p.h" + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QtGradientStopsControllerPrivate : public QObject +{ + Q_OBJECT + QtGradientStopsController *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtGradientStopsController) +public: + using PositionColorMap = QMap; + using PositionStopMap = QMap; + + void setUi(Ui::QtGradientEditor *ui); + + void slotHsvClicked(); + void slotRgbClicked(); + + void slotCurrentStopChanged(QtGradientStop *stop); + void slotStopMoved(QtGradientStop *stop, qreal newPos); + void slotStopsSwapped(QtGradientStop *stop1, QtGradientStop *stop2); + void slotStopChanged(QtGradientStop *stop, QColor newColor); + void slotStopSelected(QtGradientStop *stop, bool selected); + void slotStopAdded(QtGradientStop *stop); + void slotStopRemoved(QtGradientStop *stop); + void slotUpdatePositionSpinBox(); + + void slotChangeColor(QColor color); + void slotChangeHueColor(QColor color); + void slotChangeSaturationColor(QColor color); + void slotChangeValueColor(QColor color); + void slotChangeAlphaColor(QColor color); + void slotChangeHue(int color); + void slotChangeSaturation(int color); + void slotChangeValue(int color); + void slotChangeAlpha(int color); + void slotChangePosition(double value); + + void slotChangeZoom(int value); + void slotZoomIn(); + void slotZoomOut(); + void slotZoomAll(); + void slotZoomChanged(double zoom); + + void enableCurrent(bool enable); + void setColorSpinBoxes(QColor color); + PositionColorMap stopsData(const PositionStopMap &stops) const; + QGradientStops makeGradientStops(const PositionColorMap &data) const; + void updateZoom(double zoom); + + QtGradientStopsModel *m_model = nullptr; + QColor::Spec m_spec = QColor::Hsv; + + Ui::QtGradientEditor *m_ui = nullptr; +}; + +void QtGradientStopsControllerPrivate::setUi(Ui::QtGradientEditor *ui) +{ + m_ui = ui; + + m_ui->hueColorLine->setColorComponent(QtColorLine::Hue); + m_ui->saturationColorLine->setColorComponent(QtColorLine::Saturation); + m_ui->valueColorLine->setColorComponent(QtColorLine::Value); + m_ui->alphaColorLine->setColorComponent(QtColorLine::Alpha); + + m_model = new QtGradientStopsModel(this); + m_ui->gradientStopsWidget->setGradientStopsModel(m_model); + connect(m_model, &QtGradientStopsModel::currentStopChanged, + this, &QtGradientStopsControllerPrivate::slotCurrentStopChanged); + connect(m_model, &QtGradientStopsModel::stopMoved, + this, &QtGradientStopsControllerPrivate::slotStopMoved); + connect(m_model, &QtGradientStopsModel::stopsSwapped, + this, &QtGradientStopsControllerPrivate::slotStopsSwapped); + connect(m_model, &QtGradientStopsModel::stopChanged, + this, &QtGradientStopsControllerPrivate::slotStopChanged); + connect(m_model, &QtGradientStopsModel::stopSelected, + this, &QtGradientStopsControllerPrivate::slotStopSelected); + connect(m_model, &QtGradientStopsModel::stopAdded, + this, &QtGradientStopsControllerPrivate::slotStopAdded); + connect(m_model, &QtGradientStopsModel::stopRemoved, + this, &QtGradientStopsControllerPrivate::slotStopRemoved); + + connect(m_ui->hueColorLine, &QtColorLine::colorChanged, + this, &QtGradientStopsControllerPrivate::slotChangeHueColor); + connect(m_ui->saturationColorLine, &QtColorLine::colorChanged, + this, &QtGradientStopsControllerPrivate::slotChangeSaturationColor); + connect(m_ui->valueColorLine, &QtColorLine::colorChanged, + this, &QtGradientStopsControllerPrivate::slotChangeValueColor); + connect(m_ui->alphaColorLine, &QtColorLine::colorChanged, + this, &QtGradientStopsControllerPrivate::slotChangeAlphaColor); + connect(m_ui->colorButton, &QtColorButton::colorChanged, + this, &QtGradientStopsControllerPrivate::slotChangeColor); + + connect(m_ui->hueSpinBox, &QSpinBox::valueChanged, + this, &QtGradientStopsControllerPrivate::slotChangeHue); + connect(m_ui->saturationSpinBox, &QSpinBox::valueChanged, + this, &QtGradientStopsControllerPrivate::slotChangeSaturation); + connect(m_ui->valueSpinBox, &QSpinBox::valueChanged, + this, &QtGradientStopsControllerPrivate::slotChangeValue); + connect(m_ui->alphaSpinBox, &QSpinBox::valueChanged, + this, &QtGradientStopsControllerPrivate::slotChangeAlpha); + + connect(m_ui->positionSpinBox, &QDoubleSpinBox::valueChanged, + this, &QtGradientStopsControllerPrivate::slotChangePosition); + + connect(m_ui->zoomSpinBox, &QSpinBox::valueChanged, + this, &QtGradientStopsControllerPrivate::slotChangeZoom); + connect(m_ui->zoomInButton, &QToolButton::clicked, + this, &QtGradientStopsControllerPrivate::slotZoomIn); + connect(m_ui->zoomOutButton, &QToolButton::clicked, + this, &QtGradientStopsControllerPrivate::slotZoomOut); + connect(m_ui->zoomAllButton, &QToolButton::clicked, + this, &QtGradientStopsControllerPrivate::slotZoomAll); + connect(m_ui->gradientStopsWidget, &QtGradientStopsWidget::zoomChanged, + this, &QtGradientStopsControllerPrivate::slotZoomChanged); + + connect(m_ui->hsvRadioButton, &QRadioButton::clicked, + this, &QtGradientStopsControllerPrivate::slotHsvClicked); + connect(m_ui->rgbRadioButton, &QRadioButton::clicked, + this, &QtGradientStopsControllerPrivate::slotRgbClicked); + + enableCurrent(false); + m_ui->zoomInButton->setIcon(QIcon(":/qt-project.org/qtgradienteditor/images/zoomin.png"_L1)); + m_ui->zoomOutButton->setIcon(QIcon(":/qt-project.org/qtgradienteditor/images/zoomout.png"_L1)); + updateZoom(1); +} + +void QtGradientStopsControllerPrivate::enableCurrent(bool enable) +{ + m_ui->positionLabel->setEnabled(enable); + m_ui->colorLabel->setEnabled(enable); + m_ui->hLabel->setEnabled(enable); + m_ui->sLabel->setEnabled(enable); + m_ui->vLabel->setEnabled(enable); + m_ui->aLabel->setEnabled(enable); + m_ui->hueLabel->setEnabled(enable); + m_ui->saturationLabel->setEnabled(enable); + m_ui->valueLabel->setEnabled(enable); + m_ui->alphaLabel->setEnabled(enable); + + m_ui->positionSpinBox->setEnabled(enable); + m_ui->colorButton->setEnabled(enable); + + m_ui->hueColorLine->setEnabled(enable); + m_ui->saturationColorLine->setEnabled(enable); + m_ui->valueColorLine->setEnabled(enable); + m_ui->alphaColorLine->setEnabled(enable); + + m_ui->hueSpinBox->setEnabled(enable); + m_ui->saturationSpinBox->setEnabled(enable); + m_ui->valueSpinBox->setEnabled(enable); + m_ui->alphaSpinBox->setEnabled(enable); +} + +QtGradientStopsControllerPrivate::PositionColorMap QtGradientStopsControllerPrivate::stopsData(const PositionStopMap &stops) const +{ + PositionColorMap data; + for (QtGradientStop *stop : stops) + data[stop->position()] = stop->color(); + return data; +} + +QGradientStops QtGradientStopsControllerPrivate::makeGradientStops(const PositionColorMap &data) const +{ + QGradientStops stops; + for (auto itData = data.cbegin(), cend = data.cend(); itData != cend; ++itData) + stops << std::pair(itData.key(), itData.value()); + return stops; +} + +void QtGradientStopsControllerPrivate::updateZoom(double zoom) +{ + m_ui->gradientStopsWidget->setZoom(zoom); + m_ui->zoomSpinBox->blockSignals(true); + m_ui->zoomSpinBox->setValue(qRound(zoom * 100)); + m_ui->zoomSpinBox->blockSignals(false); + bool zoomInEnabled = true; + bool zoomOutEnabled = true; + bool zoomAllEnabled = true; + if (zoom <= 1) { + zoomAllEnabled = false; + zoomOutEnabled = false; + } else if (zoom >= 100) { + zoomInEnabled = false; + } + m_ui->zoomInButton->setEnabled(zoomInEnabled); + m_ui->zoomOutButton->setEnabled(zoomOutEnabled); + m_ui->zoomAllButton->setEnabled(zoomAllEnabled); +} + +void QtGradientStopsControllerPrivate::slotHsvClicked() +{ + QString h = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "H"); + QString s = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "S"); + QString v = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "V"); + + m_ui->hLabel->setText(h); + m_ui->sLabel->setText(s); + m_ui->vLabel->setText(v); + + h = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "Hue"); + s = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "Sat"); + v = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "Val"); + + const QString hue = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "Hue"); + const QString saturation = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "Saturation"); + const QString value = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "Value"); + + m_ui->hLabel->setToolTip(hue); + m_ui->hueLabel->setText(h); + m_ui->hueColorLine->setToolTip(hue); + m_ui->hueColorLine->setColorComponent(QtColorLine::Hue); + + m_ui->sLabel->setToolTip(saturation); + m_ui->saturationLabel->setText(s); + m_ui->saturationColorLine->setToolTip(saturation); + m_ui->saturationColorLine->setColorComponent(QtColorLine::Saturation); + + m_ui->vLabel->setToolTip(value); + m_ui->valueLabel->setText(v); + m_ui->valueColorLine->setToolTip(value); + m_ui->valueColorLine->setColorComponent(QtColorLine::Value); + + setColorSpinBoxes(m_ui->colorButton->color()); +} + +void QtGradientStopsControllerPrivate::slotRgbClicked() +{ + QString r = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "R"); + QString g = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "G"); + QString b = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "B"); + + m_ui->hLabel->setText(r); + m_ui->sLabel->setText(g); + m_ui->vLabel->setText(b); + + QString red = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "Red"); + QString green = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "Green"); + QString blue = QCoreApplication::translate("qdesigner_internal::QtGradientStopsController", "Blue"); + + m_ui->hLabel->setToolTip(red); + m_ui->hueLabel->setText(red); + m_ui->hueColorLine->setToolTip(red); + m_ui->hueColorLine->setColorComponent(QtColorLine::Red); + + m_ui->sLabel->setToolTip(green); + m_ui->saturationLabel->setText(green); + m_ui->saturationColorLine->setToolTip(green); + m_ui->saturationColorLine->setColorComponent(QtColorLine::Green); + + m_ui->vLabel->setToolTip(blue); + m_ui->valueLabel->setText(blue); + m_ui->valueColorLine->setToolTip(blue); + m_ui->valueColorLine->setColorComponent(QtColorLine::Blue); + + setColorSpinBoxes(m_ui->colorButton->color()); +} + +void QtGradientStopsControllerPrivate::setColorSpinBoxes(QColor color) +{ + m_ui->hueSpinBox->blockSignals(true); + m_ui->saturationSpinBox->blockSignals(true); + m_ui->valueSpinBox->blockSignals(true); + m_ui->alphaSpinBox->blockSignals(true); + if (m_ui->hsvRadioButton->isChecked()) { + if (m_ui->hueSpinBox->maximum() != 359) + m_ui->hueSpinBox->setMaximum(359); + if (m_ui->hueSpinBox->value() != color.hue()) + m_ui->hueSpinBox->setValue(color.hue()); + if (m_ui->saturationSpinBox->value() != color.saturation()) + m_ui->saturationSpinBox->setValue(color.saturation()); + if (m_ui->valueSpinBox->value() != color.value()) + m_ui->valueSpinBox->setValue(color.value()); + } else { + if (m_ui->hueSpinBox->maximum() != 255) + m_ui->hueSpinBox->setMaximum(255); + if (m_ui->hueSpinBox->value() != color.red()) + m_ui->hueSpinBox->setValue(color.red()); + if (m_ui->saturationSpinBox->value() != color.green()) + m_ui->saturationSpinBox->setValue(color.green()); + if (m_ui->valueSpinBox->value() != color.blue()) + m_ui->valueSpinBox->setValue(color.blue()); + } + m_ui->alphaSpinBox->setValue(color.alpha()); + m_ui->hueSpinBox->blockSignals(false); + m_ui->saturationSpinBox->blockSignals(false); + m_ui->valueSpinBox->blockSignals(false); + m_ui->alphaSpinBox->blockSignals(false); +} + +void QtGradientStopsControllerPrivate::slotCurrentStopChanged(QtGradientStop *stop) +{ + if (!stop) { + enableCurrent(false); + return; + } + enableCurrent(true); + + QTimer::singleShot(0, this, &QtGradientStopsControllerPrivate::slotUpdatePositionSpinBox); + + m_ui->colorButton->setColor(stop->color()); + m_ui->hueColorLine->setColor(stop->color()); + m_ui->saturationColorLine->setColor(stop->color()); + m_ui->valueColorLine->setColor(stop->color()); + m_ui->alphaColorLine->setColor(stop->color()); + setColorSpinBoxes(stop->color()); +} + +void QtGradientStopsControllerPrivate::slotStopMoved(QtGradientStop *stop, qreal newPos) +{ + QTimer::singleShot(0, this, &QtGradientStopsControllerPrivate::slotUpdatePositionSpinBox); + + PositionColorMap stops = stopsData(m_model->stops()); + stops.remove(stop->position()); + stops[newPos] = stop->color(); + + QGradientStops gradStops = makeGradientStops(stops); + emit q_ptr->gradientStopsChanged(gradStops); +} + +void QtGradientStopsControllerPrivate::slotStopsSwapped(QtGradientStop *stop1, QtGradientStop *stop2) +{ + QTimer::singleShot(0, this, &QtGradientStopsControllerPrivate::slotUpdatePositionSpinBox); + + PositionColorMap stops = stopsData(m_model->stops()); + const qreal pos1 = stop1->position(); + const qreal pos2 = stop2->position(); + stops[pos1] = stop2->color(); + stops[pos2] = stop1->color(); + + QGradientStops gradStops = makeGradientStops(stops); + emit q_ptr->gradientStopsChanged(gradStops); +} + +void QtGradientStopsControllerPrivate::slotStopAdded(QtGradientStop *stop) +{ + PositionColorMap stops = stopsData(m_model->stops()); + stops[stop->position()] = stop->color(); + + QGradientStops gradStops = makeGradientStops(stops); + emit q_ptr->gradientStopsChanged(gradStops); +} + +void QtGradientStopsControllerPrivate::slotStopRemoved(QtGradientStop *stop) +{ + PositionColorMap stops = stopsData(m_model->stops()); + stops.remove(stop->position()); + + QGradientStops gradStops = makeGradientStops(stops); + emit q_ptr->gradientStopsChanged(gradStops); +} + +void QtGradientStopsControllerPrivate::slotStopChanged(QtGradientStop *stop, + QColor newColor) +{ + if (m_model->currentStop() == stop) { + m_ui->colorButton->setColor(newColor); + m_ui->hueColorLine->setColor(newColor); + m_ui->saturationColorLine->setColor(newColor); + m_ui->valueColorLine->setColor(newColor); + m_ui->alphaColorLine->setColor(newColor); + setColorSpinBoxes(newColor); + } + + PositionColorMap stops = stopsData(m_model->stops()); + stops[stop->position()] = newColor; + + QGradientStops gradStops = makeGradientStops(stops); + emit q_ptr->gradientStopsChanged(gradStops); +} + +void QtGradientStopsControllerPrivate::slotStopSelected(QtGradientStop *stop, bool selected) +{ + Q_UNUSED(stop); + Q_UNUSED(selected); + QTimer::singleShot(0, this, &QtGradientStopsControllerPrivate::slotUpdatePositionSpinBox); +} + +void QtGradientStopsControllerPrivate::slotUpdatePositionSpinBox() +{ + QtGradientStop *current = m_model->currentStop(); + if (!current) + return; + + qreal min = 0.0; + qreal max = 1.0; + const qreal pos = current->position(); + + QtGradientStop *first = m_model->firstSelected(); + QtGradientStop *last = m_model->lastSelected(); + + if (first && last) { + const qreal minPos = pos - first->position() - 0.0004999; + const qreal maxPos = pos + 1.0 - last->position() + 0.0004999; + + if (max > maxPos) + max = maxPos; + if (min < minPos) + min = minPos; + + if (first->position() == 0.0) + min = pos; + if (last->position() == 1.0) + max = pos; + } + + const int spinMin = qRound(m_ui->positionSpinBox->minimum() * 1000); + const int spinMax = qRound(m_ui->positionSpinBox->maximum() * 1000); + + const int newMin = qRound(min * 1000); + const int newMax = qRound(max * 1000); + + m_ui->positionSpinBox->blockSignals(true); + if (spinMin != newMin || spinMax != newMax) { + m_ui->positionSpinBox->setRange(double(newMin) / 1000, double(newMax) / 1000); + } + if (m_ui->positionSpinBox->value() != pos) + m_ui->positionSpinBox->setValue(pos); + m_ui->positionSpinBox->blockSignals(false); +} + +void QtGradientStopsControllerPrivate::slotChangeColor(QColor color) +{ + QtGradientStop *stop = m_model->currentStop(); + if (!stop) + return; + m_model->changeStop(stop, color); + const auto stops = m_model->selectedStops(); + for (QtGradientStop *s : stops) { + if (s != stop) + m_model->changeStop(s, color); + } +} + +void QtGradientStopsControllerPrivate::slotChangeHueColor(QColor color) +{ + QtGradientStop *stop = m_model->currentStop(); + if (!stop) + return; + m_model->changeStop(stop, color); + const auto stops = m_model->selectedStops(); + for (QtGradientStop *s : stops) { + if (s != stop) { + QColor c = s->color(); + if (m_ui->hsvRadioButton->isChecked()) + c.setHsvF(color.hueF(), c.saturationF(), c.valueF(), c.alphaF()); + else + c.setRgbF(color.redF(), c.greenF(), c.blueF(), c.alphaF()); + m_model->changeStop(s, c); + } + } +} + +void QtGradientStopsControllerPrivate::slotChangeHue(int color) +{ + QColor c = m_ui->hueColorLine->color(); + if (m_ui->hsvRadioButton->isChecked()) + c.setHsvF(qreal(color) / 360.0, c.saturationF(), c.valueF(), c.alphaF()); + else + c.setRed(color); + slotChangeHueColor(c); +} + +void QtGradientStopsControllerPrivate::slotChangeSaturationColor(QColor color) +{ + QtGradientStop *stop = m_model->currentStop(); + if (!stop) + return; + m_model->changeStop(stop, color); + const auto stops = m_model->selectedStops(); + for (QtGradientStop *s : stops) { + if (s != stop) { + QColor c = s->color(); + if (m_ui->hsvRadioButton->isChecked()) { + c.setHsvF(c.hueF(), color.saturationF(), c.valueF(), c.alphaF()); + int hue = c.hue(); + if (hue == 360 || hue == -1) + c.setHsvF(0.0, c.saturationF(), c.valueF(), c.alphaF()); + } else { + c.setRgbF(c.redF(), color.greenF(), c.blueF(), c.alphaF()); + } + m_model->changeStop(s, c); + } + } +} + +void QtGradientStopsControllerPrivate::slotChangeSaturation(int color) +{ + QColor c = m_ui->saturationColorLine->color(); + if (m_ui->hsvRadioButton->isChecked()) + c.setHsvF(c.hueF(), qreal(color) / 255, c.valueF(), c.alphaF()); + else + c.setGreen(color); + slotChangeSaturationColor(c); +} + +void QtGradientStopsControllerPrivate::slotChangeValueColor(QColor color) +{ + QtGradientStop *stop = m_model->currentStop(); + if (!stop) + return; + m_model->changeStop(stop, color); + const auto stops = m_model->selectedStops(); + for (QtGradientStop *s : stops) { + if (s != stop) { + QColor c = s->color(); + if (m_ui->hsvRadioButton->isChecked()) { + c.setHsvF(c.hueF(), c.saturationF(), color.valueF(), c.alphaF()); + int hue = c.hue(); + if (hue == 360 || hue == -1) + c.setHsvF(0.0, c.saturationF(), c.valueF(), c.alphaF()); + } else { + c.setRgbF(c.redF(), c.greenF(), color.blueF(), c.alphaF()); + } + m_model->changeStop(s, c); + } + } +} + +void QtGradientStopsControllerPrivate::slotChangeValue(int color) +{ + QColor c = m_ui->valueColorLine->color(); + if (m_ui->hsvRadioButton->isChecked()) + c.setHsvF(c.hueF(), c.saturationF(), qreal(color) / 255, c.alphaF()); + else + c.setBlue(color); + slotChangeValueColor(c); +} + +void QtGradientStopsControllerPrivate::slotChangeAlphaColor(QColor color) +{ + QtGradientStop *stop = m_model->currentStop(); + if (!stop) + return; + m_model->changeStop(stop, color); + const auto stops = m_model->selectedStops(); + for (QtGradientStop *s : stops) { + if (s != stop) { + QColor c = s->color(); + if (m_ui->hsvRadioButton->isChecked()) { + c.setHsvF(c.hueF(), c.saturationF(), c.valueF(), color.alphaF()); + int hue = c.hue(); + if (hue == 360 || hue == -1) + c.setHsvF(0.0, c.saturationF(), c.valueF(), c.alphaF()); + } else { + c.setRgbF(c.redF(), c.greenF(), c.blueF(), color.alphaF()); + } + m_model->changeStop(s, c); + } + } +} + +void QtGradientStopsControllerPrivate::slotChangeAlpha(int color) +{ + QColor c = m_ui->alphaColorLine->color(); + if (m_ui->hsvRadioButton->isChecked()) + c.setHsvF(c.hueF(), c.saturationF(), c.valueF(), qreal(color) / 255); + else + c.setAlpha(color); + slotChangeAlphaColor(c); +} + +void QtGradientStopsControllerPrivate::slotChangePosition(double value) +{ + QtGradientStop *stop = m_model->currentStop(); + if (!stop) + return; + + m_model->moveStops(value); +} + +void QtGradientStopsControllerPrivate::slotChangeZoom(int value) +{ + updateZoom(value / 100.0); +} + +void QtGradientStopsControllerPrivate::slotZoomIn() +{ + double newZoom = m_ui->gradientStopsWidget->zoom() * 2; + if (newZoom > 100) + newZoom = 100; + updateZoom(newZoom); +} + +void QtGradientStopsControllerPrivate::slotZoomOut() +{ + double newZoom = m_ui->gradientStopsWidget->zoom() / 2; + if (newZoom < 1) + newZoom = 1; + updateZoom(newZoom); +} + +void QtGradientStopsControllerPrivate::slotZoomAll() +{ + updateZoom(1); +} + +void QtGradientStopsControllerPrivate::slotZoomChanged(double zoom) +{ + updateZoom(zoom); +} + +QtGradientStopsController::QtGradientStopsController(QObject *parent) + : QObject(parent), d_ptr(new QtGradientStopsControllerPrivate()) +{ + d_ptr->q_ptr = this; +} + +void QtGradientStopsController::setUi(Ui::QtGradientEditor *ui) +{ + d_ptr->setUi(ui); +} + +QtGradientStopsController::~QtGradientStopsController() +{ +} + +void QtGradientStopsController::setGradientStops(const QGradientStops &stops) +{ + d_ptr->m_model->clear(); + QtGradientStop *first = nullptr; + for (const std::pair &pair : stops) { + QtGradientStop *stop = d_ptr->m_model->addStop(pair.first, pair.second); + if (!first) + first = stop; + } + if (first) + d_ptr->m_model->setCurrentStop(first); +} + +QGradientStops QtGradientStopsController::gradientStops() const +{ + QGradientStops stops; + const auto stopsList = d_ptr->m_model->stops().values(); + for (const QtGradientStop *stop : stopsList) + stops.append({stop->position(), stop->color()}); + return stops; +} + +QColor::Spec QtGradientStopsController::spec() const +{ + return d_ptr->m_spec; +} + +void QtGradientStopsController::setSpec(QColor::Spec spec) +{ + if (d_ptr->m_spec == spec) + return; + + d_ptr->m_spec = spec; + if (d_ptr->m_spec == QColor::Rgb) { + d_ptr->m_ui->rgbRadioButton->setChecked(true); + d_ptr->slotRgbClicked(); + } else { + d_ptr->m_ui->hsvRadioButton->setChecked(true); + d_ptr->slotHsvClicked(); + } +} + +QT_END_NAMESPACE + +#include "qtgradientstopscontroller.moc" diff --git a/src/shared/qtgradienteditor/qtgradientstopscontroller_p.h b/src/shared/qtgradienteditor/qtgradientstopscontroller_p.h new file mode 100644 index 00000000000..ec2147191bc --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientstopscontroller_p.h @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTGRADIENTSTOPSCONTROLLER_H +#define QTGRADIENTSTOPSCONTROLLER_H + +#include + +QT_BEGIN_NAMESPACE + +namespace Ui { + class QtGradientEditor; +} + +class QtGradientStopsController : public QObject +{ + Q_OBJECT +public: + QtGradientStopsController(QObject *parent = 0); + ~QtGradientStopsController(); + + void setUi(Ui::QtGradientEditor *editor); + + void setGradientStops(const QGradientStops &stops); + QGradientStops gradientStops() const; + + QColor::Spec spec() const; + void setSpec(QColor::Spec spec); + +signals: + void gradientStopsChanged(const QGradientStops &stops); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtGradientStopsController) + Q_DISABLE_COPY_MOVE(QtGradientStopsController) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtgradientstopsmodel.cpp b/src/shared/qtgradienteditor/qtgradientstopsmodel.cpp new file mode 100644 index 00000000000..73b3d98b9f3 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientstopsmodel.cpp @@ -0,0 +1,421 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradientstopsmodel_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QtGradientStopPrivate +{ +public: + qreal m_position = 0; + QColor m_color = Qt::white; + QtGradientStopsModel *m_model = nullptr; +}; + +qreal QtGradientStop::position() const +{ + return d_ptr->m_position; +} + +QColor QtGradientStop::color() const +{ + return d_ptr->m_color; +} + +QtGradientStopsModel *QtGradientStop::gradientModel() const +{ + return d_ptr->m_model; +} + +void QtGradientStop::setColor(QColor color) +{ + d_ptr->m_color = color; +} + +void QtGradientStop::setPosition(qreal position) +{ + d_ptr->m_position = position; +} + +QtGradientStop::QtGradientStop(QtGradientStopsModel *model) + : d_ptr(new QtGradientStopPrivate()) +{ + d_ptr->m_model = model; +} + +QtGradientStop::~QtGradientStop() +{ +} + +class QtGradientStopsModelPrivate +{ + QtGradientStopsModel *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtGradientStopsModel) +public: + QMap m_posToStop; + QHash m_stopToPos; + QHash m_selection; + QtGradientStop *m_current = nullptr; +}; + + + +QtGradientStopsModel::QtGradientStopsModel(QObject *parent) + : QObject(parent), d_ptr(new QtGradientStopsModelPrivate) +{ + d_ptr->q_ptr = this; +} + +QtGradientStopsModel::~QtGradientStopsModel() +{ + clear(); +} + +QtGradientStopsModel::PositionStopMap QtGradientStopsModel::stops() const +{ + return d_ptr->m_posToStop; +} + +QtGradientStop *QtGradientStopsModel::at(qreal pos) const +{ + if (d_ptr->m_posToStop.contains(pos)) + return d_ptr->m_posToStop[pos]; + return 0; +} + +QColor QtGradientStopsModel::color(qreal pos) const +{ + PositionStopMap gradStops = stops(); + if (gradStops.isEmpty()) + return QColor::fromRgbF(pos, pos, pos, 1.0); + if (gradStops.contains(pos)) + return gradStops[pos]->color(); + + gradStops[pos] = 0; + auto itStop = gradStops.constFind(pos); + if (itStop == gradStops.constBegin()) { + ++itStop; + return itStop.value()->color(); + } + if (itStop == --gradStops.constEnd()) { + --itStop; + return itStop.value()->color(); + } + auto itPrev = itStop; + auto itNext = itStop; + --itPrev; + ++itNext; + + double prevX = itPrev.key(); + double nextX = itNext.key(); + + double coefX = (pos - prevX) / (nextX - prevX); + QColor prevCol = itPrev.value()->color(); + QColor nextCol = itNext.value()->color(); + + QColor newColor; + newColor.setRgbF((nextCol.redF() - prevCol.redF() ) * coefX + prevCol.redF(), + (nextCol.greenF() - prevCol.greenF()) * coefX + prevCol.greenF(), + (nextCol.blueF() - prevCol.blueF() ) * coefX + prevCol.blueF(), + (nextCol.alphaF() - prevCol.alphaF()) * coefX + prevCol.alphaF()); + return newColor; +} + +QList QtGradientStopsModel::selectedStops() const +{ + return d_ptr->m_selection.keys(); +} + +QtGradientStop *QtGradientStopsModel::currentStop() const +{ + return d_ptr->m_current; +} + +bool QtGradientStopsModel::isSelected(QtGradientStop *stop) const +{ + if (d_ptr->m_selection.contains(stop)) + return true; + return false; +} + +QtGradientStop *QtGradientStopsModel::addStop(qreal pos, QColor color) +{ + qreal newPos = pos; + if (pos < 0.0) + newPos = 0.0; + if (pos > 1.0) + newPos = 1.0; + if (d_ptr->m_posToStop.contains(newPos)) + return 0; + QtGradientStop *stop = new QtGradientStop(); + stop->setPosition(newPos); + stop->setColor(color); + + d_ptr->m_posToStop[newPos] = stop; + d_ptr->m_stopToPos[stop] = newPos; + + emit stopAdded(stop); + + return stop; +} + +void QtGradientStopsModel::removeStop(QtGradientStop *stop) +{ + if (!d_ptr->m_stopToPos.contains(stop)) + return; + if (currentStop() == stop) + setCurrentStop(0); + selectStop(stop, false); + + emit stopRemoved(stop); + + qreal pos = d_ptr->m_stopToPos[stop]; + d_ptr->m_stopToPos.remove(stop); + d_ptr->m_posToStop.remove(pos); + delete stop; +} + +void QtGradientStopsModel::moveStop(QtGradientStop *stop, qreal newPos) +{ + if (!d_ptr->m_stopToPos.contains(stop)) + return; + if (d_ptr->m_posToStop.contains(newPos)) + return; + + if (newPos > 1.0) + newPos = 1.0; + else if (newPos < 0.0) + newPos = 0.0; + + emit stopMoved(stop, newPos); + + const qreal oldPos = stop->position(); + stop->setPosition(newPos); + d_ptr->m_stopToPos[stop] = newPos; + d_ptr->m_posToStop.remove(oldPos); + d_ptr->m_posToStop[newPos] = stop; +} + +void QtGradientStopsModel::swapStops(QtGradientStop *stop1, QtGradientStop *stop2) +{ + if (stop1 == stop2) + return; + if (!d_ptr->m_stopToPos.contains(stop1)) + return; + if (!d_ptr->m_stopToPos.contains(stop2)) + return; + + emit stopsSwapped(stop1, stop2); + + const qreal pos1 = stop1->position(); + const qreal pos2 = stop2->position(); + stop1->setPosition(pos2); + stop2->setPosition(pos1); + d_ptr->m_stopToPos[stop1] = pos2; + d_ptr->m_stopToPos[stop2] = pos1; + d_ptr->m_posToStop[pos1] = stop2; + d_ptr->m_posToStop[pos2] = stop1; +} + +void QtGradientStopsModel::changeStop(QtGradientStop *stop, QColor newColor) +{ + if (!d_ptr->m_stopToPos.contains(stop)) + return; + if (stop->color() == newColor) + return; + + emit stopChanged(stop, newColor); + + stop->setColor(newColor); +} + +void QtGradientStopsModel::selectStop(QtGradientStop *stop, bool select) +{ + if (!d_ptr->m_stopToPos.contains(stop)) + return; + bool selected = d_ptr->m_selection.contains(stop); + if (select == selected) + return; + + emit stopSelected(stop, select); + + if (select) + d_ptr->m_selection[stop] = true; + else + d_ptr->m_selection.remove(stop); +} + +void QtGradientStopsModel::setCurrentStop(QtGradientStop *stop) +{ + if (stop && !d_ptr->m_stopToPos.contains(stop)) + return; + if (stop == currentStop()) + return; + + emit currentStopChanged(stop); + + d_ptr->m_current = stop; +} + +QtGradientStop *QtGradientStopsModel::firstSelected() const +{ + PositionStopMap stopList = stops(); + auto itStop = stopList.cbegin(); + while (itStop != stopList.constEnd()) { + QtGradientStop *stop = itStop.value(); + if (isSelected(stop)) + return stop; + ++itStop; + }; + return 0; +} + +QtGradientStop *QtGradientStopsModel::lastSelected() const +{ + PositionStopMap stopList = stops(); + auto itStop = stopList.cend(); + while (itStop != stopList.constBegin()) { + --itStop; + + QtGradientStop *stop = itStop.value(); + if (isSelected(stop)) + return stop; + }; + return 0; +} + +QtGradientStopsModel *QtGradientStopsModel::clone() const +{ + QtGradientStopsModel *model = new QtGradientStopsModel(); + + QMap stopsToClone = stops(); + for (auto it = stopsToClone.cbegin(), end = stopsToClone.cend(); it != end; ++it) + model->addStop(it.key(), it.value()->color()); + // clone selection and current also + return model; +} + +void QtGradientStopsModel::moveStops(double newPosition) +{ + QtGradientStop *current = currentStop(); + if (!current) + return; + + double newPos = newPosition; + + if (newPos > 1) + newPos = 1; + else if (newPos < 0) + newPos = 0; + + if (newPos == current->position()) + return; + + double offset = newPos - current->position(); + + QtGradientStop *first = firstSelected(); + QtGradientStop *last = lastSelected(); + + if (first && last) { // multiselection + double maxOffset = 1.0 - last->position(); + double minOffset = -first->position(); + + if (offset > maxOffset) + offset = maxOffset; + else if (offset < minOffset) + offset = minOffset; + + } + + if (offset == 0) + return; + + bool forward = (offset > 0) ? false : true; + + PositionStopMap stopList; + + const auto selected = selectedStops(); + for (QtGradientStop *stop : selected) + stopList[stop->position()] = stop; + stopList[current->position()] = current; + + auto itStop = forward ? stopList.cbegin() : stopList.cend(); + while (itStop != (forward ? stopList.constEnd() : stopList.constBegin())) { + if (!forward) + --itStop; + QtGradientStop *stop = itStop.value(); + double pos = stop->position() + offset; + if (pos > 1) + pos = 1; + if (pos < 0) + pos = 0; + + if (current == stop) + pos = newPos; + + QtGradientStop *oldStop = at(pos); + if (oldStop && !stopList.values().contains(oldStop)) + removeStop(oldStop); + moveStop(stop, pos); + + if (forward) + ++itStop; + } +} + +void QtGradientStopsModel::clear() +{ + const auto stopsList = stops().values(); + for (QtGradientStop *stop : stopsList) + removeStop(stop); +} + +void QtGradientStopsModel::clearSelection() +{ + const auto stopsList = selectedStops(); + for (QtGradientStop *stop : stopsList) + selectStop(stop, false); +} + +void QtGradientStopsModel::flipAll() +{ + QMap stopsMap = stops(); + QHash swappedList; + for (auto itStop = stopsMap.keyValueEnd(), begin = stopsMap.keyValueBegin(); itStop != begin;) { + --itStop; + QtGradientStop *stop = (*itStop).second; + if (swappedList.contains(stop)) + continue; + const double newPos = 1.0 - (*itStop).first; + if (stopsMap.contains(newPos)) { + QtGradientStop *swapped = stopsMap.value(newPos); + swappedList[swapped] = true; + swapStops(stop, swapped); + } else { + moveStop(stop, newPos); + } + } +} + +void QtGradientStopsModel::selectAll() +{ + const auto stopsMap = stops(); + for (auto it = stopsMap.cbegin(), end = stopsMap.cend(); it != end; ++it) + selectStop(it.value(), true); +} + +void QtGradientStopsModel::deleteStops() +{ + const auto selected = selectedStops(); + for (QtGradientStop *stop : selected) + removeStop(stop); + QtGradientStop *current = currentStop(); + if (current) + removeStop(current); +} + +QT_END_NAMESPACE diff --git a/src/shared/qtgradienteditor/qtgradientstopsmodel_p.h b/src/shared/qtgradienteditor/qtgradientstopsmodel_p.h new file mode 100644 index 00000000000..5e52f5b3e63 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientstopsmodel_p.h @@ -0,0 +1,94 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTGRADIENTSTOPSMODEL_H +#define QTGRADIENTSTOPSMODEL_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QColor; + +class QtGradientStopsModel; + +class QtGradientStop +{ +public: + qreal position() const; + QColor color() const; + QtGradientStopsModel *gradientModel() const; + +private: + void setColor(QColor color); + void setPosition(qreal position); + friend class QtGradientStopsModel; + QtGradientStop(QtGradientStopsModel *model = 0); + ~QtGradientStop(); + QScopedPointer d_ptr; +}; + +class QtGradientStopsModel : public QObject +{ + Q_OBJECT +public: + using PositionStopMap = QMap; + + QtGradientStopsModel(QObject *parent = 0); + ~QtGradientStopsModel(); + + PositionStopMap stops() const; + QtGradientStop *at(qreal pos) const; + QColor color(qreal pos) const; // calculated between points + QList selectedStops() const; + QtGradientStop *currentStop() const; + bool isSelected(QtGradientStop *stop) const; + QtGradientStop *firstSelected() const; + QtGradientStop *lastSelected() const; + QtGradientStopsModel *clone() const; + + QtGradientStop *addStop(qreal pos, QColor color); + void removeStop(QtGradientStop *stop); + void moveStop(QtGradientStop *stop, qreal newPos); + void swapStops(QtGradientStop *stop1, QtGradientStop *stop2); + void changeStop(QtGradientStop *stop, QColor newColor); + void selectStop(QtGradientStop *stop, bool select); + void setCurrentStop(QtGradientStop *stop); + + void moveStops(double newPosition); // moves current stop to newPos and all selected stops are moved accordingly + void clear(); + void clearSelection(); + void flipAll(); + void selectAll(); + void deleteStops(); + +signals: + void stopAdded(QtGradientStop *stop); + void stopRemoved(QtGradientStop *stop); + void stopMoved(QtGradientStop *stop, qreal newPos); + void stopsSwapped(QtGradientStop *stop1, QtGradientStop *stop2); + void stopChanged(QtGradientStop *stop, const QColor &newColor); + void stopSelected(QtGradientStop *stop, bool selected); + void currentStopChanged(QtGradientStop *stop); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtGradientStopsModel) + Q_DISABLE_COPY_MOVE(QtGradientStopsModel) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtgradientstopswidget.cpp b/src/shared/qtgradienteditor/qtgradientstopswidget.cpp new file mode 100644 index 00000000000..cd1e56f2d4f --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientstopswidget.cpp @@ -0,0 +1,1100 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradientstopswidget_p.h" +#include "qtgradientstopsmodel_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtGradientStopsWidgetPrivate : public QObject +{ + Q_OBJECT + QtGradientStopsWidget *q_ptr; + Q_DECLARE_PUBLIC(QtGradientStopsWidget) +public: + void setGradientStopsModel(QtGradientStopsModel *model); + + void slotStopAdded(QtGradientStop *stop); + void slotStopRemoved(QtGradientStop *stop); + void slotStopMoved(QtGradientStop *stop, qreal newPos); + void slotStopsSwapped(QtGradientStop *stop1, QtGradientStop *stop2); + void slotStopChanged(QtGradientStop *stop, QColor newColor); + void slotStopSelected(QtGradientStop *stop, bool selected); + void slotCurrentStopChanged(QtGradientStop *stop); + void slotNewStop(); + void slotDelete(); + void slotFlipAll(); + void slotSelectAll(); + void slotZoomIn(); + void slotZoomOut(); + void slotResetZoom(); + + double fromViewport(int x) const; + double toViewport(double x) const; + QtGradientStop *stopAt(QPoint viewportPos) const; + QList stopsAt(QPoint viewportPos) const; + void setupMove(QtGradientStop *stop, int x); + void ensureVisible(double x); // x = stop position + void ensureVisible(QtGradientStop *stop); + QtGradientStop *newStop(QPoint viewportPos); + + bool m_backgroundCheckered; + QtGradientStopsModel *m_model; + double m_handleSize; + int m_scaleFactor; + double m_zoom; + +#ifndef QT_NO_DRAGANDDROP + QtGradientStop *m_dragStop; + QtGradientStop *m_changedStop; + QtGradientStop *m_clonedStop; + QtGradientStopsModel *m_dragModel; + QColor m_dragColor; + void clearDrag(); + void removeClonedStop(); + void restoreChangedStop(); + void changeStop(qreal pos); + void cloneStop(qreal pos); +#endif + + QRubberBand *m_rubber; + QPoint m_clickPos; + + QList m_stops; + + bool m_moving; + int m_moveOffset; + QHash m_moveStops; + + QMap m_moveOriginal; +}; + +void QtGradientStopsWidgetPrivate::setGradientStopsModel(QtGradientStopsModel *model) +{ + if (m_model == model) + return; + + if (m_model) { + disconnect(m_model, &QtGradientStopsModel::stopAdded, + this, &QtGradientStopsWidgetPrivate::slotStopAdded); + disconnect(m_model, &QtGradientStopsModel::stopRemoved, + this, &QtGradientStopsWidgetPrivate::slotStopRemoved); + disconnect(m_model, &QtGradientStopsModel::stopMoved, + this, &QtGradientStopsWidgetPrivate::slotStopMoved); + disconnect(m_model, &QtGradientStopsModel::stopsSwapped, + this, &QtGradientStopsWidgetPrivate::slotStopsSwapped); + disconnect(m_model, &QtGradientStopsModel::stopChanged, + this, &QtGradientStopsWidgetPrivate::slotStopChanged); + disconnect(m_model, &QtGradientStopsModel::stopSelected, + this, &QtGradientStopsWidgetPrivate::slotStopSelected); + disconnect(m_model, &QtGradientStopsModel::currentStopChanged, + this, &QtGradientStopsWidgetPrivate::slotCurrentStopChanged); + + m_stops.clear(); + } + + m_model = model; + + if (m_model) { + connect(m_model, &QtGradientStopsModel::stopAdded, + this, &QtGradientStopsWidgetPrivate::slotStopAdded); + connect(m_model, &QtGradientStopsModel::stopRemoved, + this, &QtGradientStopsWidgetPrivate::slotStopRemoved); + connect(m_model, &QtGradientStopsModel::stopMoved, + this, &QtGradientStopsWidgetPrivate::slotStopMoved); + connect(m_model, &QtGradientStopsModel::stopsSwapped, + this, &QtGradientStopsWidgetPrivate::slotStopsSwapped); + connect(m_model, &QtGradientStopsModel::stopChanged, + this, &QtGradientStopsWidgetPrivate::slotStopChanged); + connect(m_model, &QtGradientStopsModel::stopSelected, + this, &QtGradientStopsWidgetPrivate::slotStopSelected); + connect(m_model, &QtGradientStopsModel::currentStopChanged, + this, &QtGradientStopsWidgetPrivate::slotCurrentStopChanged); + + const auto stopsMap = m_model->stops(); + for (auto it = stopsMap.cbegin(), end = stopsMap.cend(); it != end; ++it) + slotStopAdded(it.value()); + + const auto selected = m_model->selectedStops(); + for (QtGradientStop *stop : selected) + slotStopSelected(stop, true); + + slotCurrentStopChanged(m_model->currentStop()); + } +} + +double QtGradientStopsWidgetPrivate::fromViewport(int x) const +{ + QSize size = q_ptr->viewport()->size(); + int w = size.width(); + int max = q_ptr->horizontalScrollBar()->maximum(); + int val = q_ptr->horizontalScrollBar()->value(); + return (double(x) * m_scaleFactor + w * val) / (w * (m_scaleFactor + max)); +} + +double QtGradientStopsWidgetPrivate::toViewport(double x) const +{ + QSize size = q_ptr->viewport()->size(); + int w = size.width(); + int max = q_ptr->horizontalScrollBar()->maximum(); + int val = q_ptr->horizontalScrollBar()->value(); + return w * (x * (m_scaleFactor + max) - val) / m_scaleFactor; +} + +QtGradientStop *QtGradientStopsWidgetPrivate::stopAt(QPoint viewportPos) const +{ + double posY = m_handleSize / 2; + for (QtGradientStop *stop : m_stops) { + double posX = toViewport(stop->position()); + + double x = viewportPos.x() - posX; + double y = viewportPos.y() - posY; + + if ((m_handleSize * m_handleSize / 4) > (x * x + y * y)) + return stop; + } + return 0; +} + +QList QtGradientStopsWidgetPrivate::stopsAt(QPoint viewportPos) const +{ + QList stops; + double posY = m_handleSize / 2; + for (QtGradientStop *stop : m_stops) { + double posX = toViewport(stop->position()); + + double x = viewportPos.x() - posX; + double y = viewportPos.y() - posY; + + if ((m_handleSize * m_handleSize / 4) > (x * x + y * y)) + stops.append(stop); + } + return stops; +} + +void QtGradientStopsWidgetPrivate::setupMove(QtGradientStop *stop, int x) +{ + m_model->setCurrentStop(stop); + + int viewportX = qRound(toViewport(stop->position())); + m_moveOffset = x - viewportX; + + const auto stops = m_stops; + m_stops.clear(); + for (QtGradientStop *s : stops) { + if (m_model->isSelected(s) || s == stop) { + m_moveStops[s] = s->position() - stop->position(); + m_stops.append(s); + } else { + m_moveOriginal[s->position()] = s->color(); + } + } + for (QtGradientStop *s : stops) { + if (!m_model->isSelected(s)) + m_stops.append(s); + } + m_stops.removeAll(stop); + m_stops.prepend(stop); +} + +void QtGradientStopsWidgetPrivate::ensureVisible(double x) +{ + double viewX = toViewport(x); + if (viewX < 0 || viewX > q_ptr->viewport()->size().width()) { + int max = q_ptr->horizontalScrollBar()->maximum(); + int newVal = qRound(x * (max + m_scaleFactor) - m_scaleFactor / 2); + q_ptr->horizontalScrollBar()->setValue(newVal); + } +} + +void QtGradientStopsWidgetPrivate::ensureVisible(QtGradientStop *stop) +{ + if (!stop) + return; + ensureVisible(stop->position()); +} + +QtGradientStop *QtGradientStopsWidgetPrivate::newStop(QPoint viewportPos) +{ + QtGradientStop *copyStop = stopAt(viewportPos); + double posX = fromViewport(viewportPos.x()); + QtGradientStop *stop = m_model->at(posX); + if (!stop) { + QColor newColor; + if (copyStop) + newColor = copyStop->color(); + else + newColor = m_model->color(posX); + if (!newColor.isValid()) + newColor = Qt::white; + stop = m_model->addStop(posX, newColor); + } + return stop; +} + +void QtGradientStopsWidgetPrivate::slotStopAdded(QtGradientStop *stop) +{ + m_stops.append(stop); + q_ptr->viewport()->update(); +} + +void QtGradientStopsWidgetPrivate::slotStopRemoved(QtGradientStop *stop) +{ + m_stops.removeAll(stop); + q_ptr->viewport()->update(); +} + +void QtGradientStopsWidgetPrivate::slotStopMoved(QtGradientStop *stop, qreal newPos) +{ + Q_UNUSED(stop); + Q_UNUSED(newPos); + q_ptr->viewport()->update(); +} + +void QtGradientStopsWidgetPrivate::slotStopsSwapped(QtGradientStop *stop1, QtGradientStop *stop2) +{ + Q_UNUSED(stop1); + Q_UNUSED(stop2); + q_ptr->viewport()->update(); +} + +void QtGradientStopsWidgetPrivate::slotStopChanged(QtGradientStop *stop, QColor newColor) +{ + Q_UNUSED(stop); + Q_UNUSED(newColor); + q_ptr->viewport()->update(); +} + +void QtGradientStopsWidgetPrivate::slotStopSelected(QtGradientStop *stop, bool selected) +{ + Q_UNUSED(stop); + Q_UNUSED(selected); + q_ptr->viewport()->update(); +} + +void QtGradientStopsWidgetPrivate::slotCurrentStopChanged(QtGradientStop *stop) +{ + Q_UNUSED(stop); + + if (!m_model) + return; + q_ptr->viewport()->update(); + if (stop) { + m_stops.removeAll(stop); + m_stops.prepend(stop); + } +} + +void QtGradientStopsWidgetPrivate::slotNewStop() +{ + if (!m_model) + return; + + QtGradientStop *stop = newStop(m_clickPos); + + if (!stop) + return; + + m_model->clearSelection(); + m_model->selectStop(stop, true); + m_model->setCurrentStop(stop); +} + +void QtGradientStopsWidgetPrivate::slotDelete() +{ + if (!m_model) + return; + + m_model->deleteStops(); +} + +void QtGradientStopsWidgetPrivate::slotFlipAll() +{ + if (!m_model) + return; + + m_model->flipAll(); +} + +void QtGradientStopsWidgetPrivate::slotSelectAll() +{ + if (!m_model) + return; + + m_model->selectAll(); +} + +void QtGradientStopsWidgetPrivate::slotZoomIn() +{ + double newZoom = q_ptr->zoom() * 2; + if (newZoom > 100) + newZoom = 100; + if (newZoom == q_ptr->zoom()) + return; + + q_ptr->setZoom(newZoom); + emit q_ptr->zoomChanged(q_ptr->zoom()); +} + +void QtGradientStopsWidgetPrivate::slotZoomOut() +{ + double newZoom = q_ptr->zoom() / 2; + if (newZoom < 1) + newZoom = 1; + if (newZoom == q_ptr->zoom()) + return; + + q_ptr->setZoom(newZoom); + emit q_ptr->zoomChanged(q_ptr->zoom()); +} + +void QtGradientStopsWidgetPrivate::slotResetZoom() +{ + if (1 == q_ptr->zoom()) + return; + + q_ptr->setZoom(1); + emit q_ptr->zoomChanged(1); +} + +QtGradientStopsWidget::QtGradientStopsWidget(QWidget *parent) + : QAbstractScrollArea(parent), d_ptr(new QtGradientStopsWidgetPrivate) +{ + d_ptr->q_ptr = this; + d_ptr->m_backgroundCheckered = true; + d_ptr->m_model = 0; + d_ptr->m_handleSize = 25.0; + d_ptr->m_scaleFactor = 1000; + d_ptr->m_moving = false; + d_ptr->m_zoom = 1; + d_ptr->m_rubber = new QRubberBand(QRubberBand::Rectangle, this); +#ifndef QT_NO_DRAGANDDROP + d_ptr->m_dragStop = 0; + d_ptr->m_changedStop = 0; + d_ptr->m_clonedStop = 0; + d_ptr->m_dragModel = 0; +#endif + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + horizontalScrollBar()->setRange(0, (int)(d_ptr->m_scaleFactor * (d_ptr->m_zoom - 1) + 0.5)); + horizontalScrollBar()->setPageStep(d_ptr->m_scaleFactor); + horizontalScrollBar()->setSingleStep(4); + viewport()->setAutoFillBackground(false); + + setAcceptDrops(true); + + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); +} + +QtGradientStopsWidget::~QtGradientStopsWidget() +{ +} + +QSize QtGradientStopsWidget::sizeHint() const +{ + return QSize(qRound(2 * d_ptr->m_handleSize), qRound(3 * d_ptr->m_handleSize) + horizontalScrollBar()->sizeHint().height()); +} + +QSize QtGradientStopsWidget::minimumSizeHint() const +{ + return QSize(qRound(2 * d_ptr->m_handleSize), qRound(3 * d_ptr->m_handleSize) + horizontalScrollBar()->minimumSizeHint().height()); +} + +void QtGradientStopsWidget::setBackgroundCheckered(bool checkered) +{ + if (d_ptr->m_backgroundCheckered == checkered) + return; + d_ptr->m_backgroundCheckered = checkered; + update(); +} + +bool QtGradientStopsWidget::isBackgroundCheckered() const +{ + return d_ptr->m_backgroundCheckered; +} + +void QtGradientStopsWidget::setGradientStopsModel(QtGradientStopsModel *model) +{ + d_ptr->setGradientStopsModel(model); +} + +void QtGradientStopsWidget::mousePressEvent(QMouseEvent *e) +{ + if (!d_ptr->m_model) + return; + + if (e->button() != Qt::LeftButton) + return; + + d_ptr->m_moving = true; + + d_ptr->m_moveStops.clear(); + d_ptr->m_moveOriginal.clear(); + d_ptr->m_clickPos = e->position().toPoint(); + QtGradientStop *stop = d_ptr->stopAt(e->position().toPoint()); + if (stop) { + if (e->modifiers() & Qt::ControlModifier) { + d_ptr->m_model->selectStop(stop, !d_ptr->m_model->isSelected(stop)); + } else if (e->modifiers() & Qt::ShiftModifier) { + QtGradientStop *oldCurrent = d_ptr->m_model->currentStop(); + if (oldCurrent) { + const auto stops = d_ptr->m_model->stops(); + auto itSt = stops.constFind(oldCurrent->position()); + if (itSt != stops.constEnd()) { + while (itSt != stops.constFind(stop->position())) { + d_ptr->m_model->selectStop(itSt.value(), true); + if (oldCurrent->position() < stop->position()) + ++itSt; + else + --itSt; + } + } + } + d_ptr->m_model->selectStop(stop, true); + } else { + if (!d_ptr->m_model->isSelected(stop)) { + d_ptr->m_model->clearSelection(); + d_ptr->m_model->selectStop(stop, true); + } + } + d_ptr->setupMove(stop, e->position().toPoint().x()); + } else { + d_ptr->m_model->clearSelection(); + d_ptr->m_rubber->setGeometry(QRect(d_ptr->m_clickPos, QSize())); + d_ptr->m_rubber->show(); + } + viewport()->update(); +} + +void QtGradientStopsWidget::mouseReleaseEvent(QMouseEvent *e) +{ + if (!d_ptr->m_model) + return; + + if (e->button() != Qt::LeftButton) + return; + + d_ptr->m_moving = false; + d_ptr->m_rubber->hide(); + d_ptr->m_moveStops.clear(); + d_ptr->m_moveOriginal.clear(); +} + +void QtGradientStopsWidget::mouseMoveEvent(QMouseEvent *e) +{ + if (!d_ptr->m_model) + return; + + if (!(e->buttons() & Qt::LeftButton)) + return; + + if (!d_ptr->m_moving) + return; + + if (!d_ptr->m_moveStops.isEmpty()) { + double maxOffset = 0.0; + double minOffset = 0.0; + bool first = true; + auto itStop = d_ptr->m_moveStops.cbegin(); + while (itStop != d_ptr->m_moveStops.constEnd()) { + double offset = itStop.value(); + + if (first) { + maxOffset = offset; + minOffset = offset; + first = false; + } else { + if (maxOffset < offset) + maxOffset = offset; + else if (minOffset > offset) + minOffset = offset; + } + ++itStop; + } + + double viewportMin = d_ptr->toViewport(-minOffset); + double viewportMax = d_ptr->toViewport(1.0 - maxOffset); + + QtGradientStopsModel::PositionStopMap newPositions; + + int viewportX = e->position().toPoint().x() - d_ptr->m_moveOffset; + + if (viewportX > viewport()->size().width()) + viewportX = viewport()->size().width(); + else if (viewportX < 0) + viewportX = 0; + + double posX = d_ptr->fromViewport(viewportX); + + if (viewportX > viewportMax) + posX = 1.0 - maxOffset; + else if (viewportX < viewportMin) + posX = -minOffset; + + itStop = d_ptr->m_moveStops.constBegin(); + while (itStop != d_ptr->m_moveStops.constEnd()) { + QtGradientStop *stop = itStop.key(); + + newPositions[posX + itStop.value()] = stop; + + ++itStop; + } + + bool forward = true; + auto itNewPos = newPositions.cbegin(); + if (itNewPos.value()->position() < itNewPos.key()) + forward = false; + + itNewPos = forward ? newPositions.constBegin() : newPositions.constEnd(); + while (itNewPos != (forward ? newPositions.constEnd() : newPositions.constBegin())) { + if (!forward) + --itNewPos; + QtGradientStop *stop = itNewPos.value(); + double newPos = itNewPos.key(); + if (newPos > 1) + newPos = 1; + else if (newPos < 0) + newPos = 0; + + QtGradientStop *existingStop = d_ptr->m_model->at(newPos); + if (existingStop && !d_ptr->m_moveStops.contains(existingStop)) + d_ptr->m_model->removeStop(existingStop); + d_ptr->m_model->moveStop(stop, newPos); + + if (forward) + ++itNewPos; + } + + auto itOld = d_ptr->m_moveOriginal.cbegin(); + while (itOld != d_ptr->m_moveOriginal.constEnd()) { + double position = itOld.key(); + if (!d_ptr->m_model->at(position)) + d_ptr->m_model->addStop(position, itOld.value()); + + ++itOld; + } + + } else { + QRect r(QRect(d_ptr->m_clickPos, e->position().toPoint()).normalized()); + r.translate(1, 0); + d_ptr->m_rubber->setGeometry(r); + //d_ptr->m_model->clearSelection(); + + int xv1 = d_ptr->m_clickPos.x(); + int xv2 = e->position().toPoint().x(); + if (xv1 > xv2) { + int temp = xv1; + xv1 = xv2; + xv2 = temp; + } + int yv1 = d_ptr->m_clickPos.y(); + int yv2 = e->position().toPoint().y(); + if (yv1 > yv2) { + int temp = yv1; + yv1 = yv2; + yv2 = temp; + } + + QPoint p1, p2; + + if (yv2 < d_ptr->m_handleSize / 2) { + p1 = QPoint(xv1, yv2); + p2 = QPoint(xv2, yv2); + } else if (yv1 > d_ptr->m_handleSize / 2) { + p1 = QPoint(xv1, yv1); + p2 = QPoint(xv2, yv1); + } else { + p1 = QPoint(xv1, qRound(d_ptr->m_handleSize / 2)); + p2 = QPoint(xv2, qRound(d_ptr->m_handleSize / 2)); + } + + const auto beginList = d_ptr->stopsAt(p1); + const auto endList = d_ptr->stopsAt(p2); + + double x1 = d_ptr->fromViewport(xv1); + double x2 = d_ptr->fromViewport(xv2); + + for (QtGradientStop *stop : std::as_const(d_ptr->m_stops)) { + if ((stop->position() >= x1 && stop->position() <= x2) || + beginList.contains(stop) || endList.contains(stop)) + d_ptr->m_model->selectStop(stop, true); + else + d_ptr->m_model->selectStop(stop, false); + } + } +} + +void QtGradientStopsWidget::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (!d_ptr->m_model) + return; + + if (e->button() != Qt::LeftButton) + return; + + if (d_ptr->m_clickPos != e->position().toPoint()) { + mousePressEvent(e); + return; + } + d_ptr->m_moving = true; + d_ptr->m_moveStops.clear(); + d_ptr->m_moveOriginal.clear(); + + QtGradientStop *stop = d_ptr->newStop(e->position().toPoint()); + + if (!stop) + return; + + d_ptr->m_model->clearSelection(); + d_ptr->m_model->selectStop(stop, true); + + d_ptr->setupMove(stop, e->position().toPoint().x()); + + viewport()->update(); +} + +void QtGradientStopsWidget::keyPressEvent(QKeyEvent *e) +{ + if (!d_ptr->m_model) + return; + + if (e->key() == Qt::Key_Delete || e->key() == Qt::Key_Backspace) { + d_ptr->m_model->deleteStops(); + } else if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Right || + e->key() == Qt::Key_Home || e->key() == Qt::Key_End) { + const auto stops = d_ptr->m_model->stops(); + if (stops.isEmpty()) + return; + QtGradientStop *newCurrent = nullptr; + QtGradientStop *current = d_ptr->m_model->currentStop(); + if (!current || e->key() == Qt::Key_Home || e->key() == Qt::Key_End) { + if (e->key() == Qt::Key_Left || e->key() == Qt::Key_Home) + newCurrent = stops.constBegin().value(); + else if (e->key() == Qt::Key_Right || e->key() == Qt::Key_End) + newCurrent = (--stops.constEnd()).value(); + } else { + auto itStop = stops.cbegin(); + while (itStop.value() != current) + ++itStop; + if (e->key() == Qt::Key_Left && itStop != stops.constBegin()) + --itStop; + else if (e->key() == Qt::Key_Right && itStop != --stops.constEnd()) + ++itStop; + newCurrent = itStop.value(); + } + d_ptr->m_model->clearSelection(); + d_ptr->m_model->selectStop(newCurrent, true); + d_ptr->m_model->setCurrentStop(newCurrent); + d_ptr->ensureVisible(newCurrent); + } else if (e->key() == Qt::Key_A) { + if (e->modifiers() & Qt::ControlModifier) + d_ptr->m_model->selectAll(); + } +} + +void QtGradientStopsWidget::paintEvent(QPaintEvent *e) +{ + Q_UNUSED(e); + if (!d_ptr->m_model) + return; + + QtGradientStopsModel *model = d_ptr->m_model; +#ifndef QT_NO_DRAGANDDROP + if (d_ptr->m_dragModel) + model = d_ptr->m_dragModel; +#endif + + QSize size = viewport()->size(); + int w = size.width(); + double h = size.height() - d_ptr->m_handleSize; + if (w <= 0) + return; + + QPixmap pix(size); + QPainter p; + + if (d_ptr->m_backgroundCheckered) { + int pixSize = 20; + QPixmap pm(2 * pixSize, 2 * pixSize); + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, Qt::white); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); + + p.begin(&pix); + p.setBrushOrigin((size.width() % pixSize + pixSize) / 2, (size.height() % pixSize + pixSize) / 2); + p.fillRect(viewport()->rect(), pm); + p.setBrushOrigin(0, 0); + } else { + p.begin(viewport()); + } + + const double viewBegin = double(w) * horizontalScrollBar()->value() / d_ptr->m_scaleFactor; + + int val = horizontalScrollBar()->value(); + int max = horizontalScrollBar()->maximum(); + + const double begin = double(val) / (d_ptr->m_scaleFactor + max); + const double end = double(val + d_ptr->m_scaleFactor) / (d_ptr->m_scaleFactor + max); + double width = end - begin; + + if (h > 0) { + QLinearGradient lg(0, 0, w, 0); + QMap stops = model->stops(); + for (auto itStop = stops.cbegin(), send = stops.cend(); itStop != send; ++itStop) { + QtGradientStop *stop = itStop.value(); + double pos = stop->position(); + if (pos >= begin && pos <= end) { + double gradPos = (pos - begin) / width; + QColor c = stop->color(); + lg.setColorAt(gradPos, c); + } + //lg.setColorAt(stop->position(), stop->color()); + } + lg.setColorAt(0, model->color(begin)); + lg.setColorAt(1, model->color(end)); + QImage img(w, 1, QImage::Format_ARGB32_Premultiplied); + QPainter p1(&img); + p1.setCompositionMode(QPainter::CompositionMode_Source); + + /* + if (viewBegin != 0) + p1.translate(-viewBegin, 0); + if (d_ptr->m_zoom != 1) + p1.scale(d_ptr->m_zoom, 1); + */ + p1.fillRect(0, 0, w, 1, lg); + + p.fillRect(QRectF(0, d_ptr->m_handleSize, w, h), QPixmap::fromImage(img)); + } + + + double handleWidth = d_ptr->m_handleSize * d_ptr->m_scaleFactor / (w * (d_ptr->m_scaleFactor + max)); + + QColor insideColor = QColor::fromRgb(0x20, 0x20, 0x20, 0xFF); + QColor drawColor; + QColor back1 = QColor(Qt::lightGray); + QColor back2 = QColor(Qt::darkGray); + QColor back = QColor::fromRgb((back1.red() + back2.red()) / 2, + (back1.green() + back2.green()) / 2, + (back1.blue() + back2.blue()) / 2); + + QPen pen; + p.setRenderHint(QPainter::Antialiasing); + for (auto rit = d_ptr->m_stops.crbegin(), rend = d_ptr->m_stops.crend(); rit != rend; ++rit) { + QtGradientStop *stop = *rit; + double x = stop->position(); + if (x >= begin - handleWidth / 2 && x <= end + handleWidth / 2) { + double viewX = x * w * (d_ptr->m_scaleFactor + max) / d_ptr->m_scaleFactor - viewBegin; + p.save(); + QColor c = stop->color(); +#ifndef QT_NO_DRAGANDDROP + if (stop == d_ptr->m_dragStop) + c = d_ptr->m_dragColor; +#endif + if ((0.3 * c.redF() + 0.59 * c.greenF() + 0.11 * c.blueF()) * c.alphaF() + + (0.3 * back.redF() + 0.59 * back.greenF() + 0.11 * back.blueF()) * (1.0 - c.alphaF()) < 0.5) { + drawColor = QColor::fromRgb(0xC0, 0xC0, 0xC0, 0xB0); + } else { + drawColor = QColor::fromRgb(0x40, 0x40, 0x40, 0x80); + } + QRectF rect(viewX - d_ptr->m_handleSize / 2, 0, d_ptr->m_handleSize, d_ptr->m_handleSize); + rect.adjust(0.5, 0.5, -0.5, -0.5); + if (h > 0) { + pen.setWidthF(1); + QLinearGradient lg(0, d_ptr->m_handleSize, 0, d_ptr->m_handleSize + h / 2); + lg.setColorAt(0, drawColor); + QColor alphaZero = drawColor; + alphaZero.setAlpha(0); + lg.setColorAt(1, alphaZero); + pen.setBrush(lg); + p.setPen(pen); + p.drawLine(QPointF(viewX, d_ptr->m_handleSize), QPointF(viewX, d_ptr->m_handleSize + h / 2)); + + pen.setWidthF(1); + pen.setBrush(drawColor); + p.setPen(pen); + QRectF r1 = rect.adjusted(0.5, 0.5, -0.5, -0.5); + QRectF r2 = rect.adjusted(1.5, 1.5, -1.5, -1.5); + QColor inColor = QColor::fromRgb(0x80, 0x80, 0x80, 0x80); + if (!d_ptr->m_model->isSelected(stop)) { + p.setBrush(c); + p.drawEllipse(rect); + } else { + pen.setBrush(insideColor); + pen.setWidthF(2); + p.setPen(pen); + p.setBrush(Qt::NoBrush); + p.drawEllipse(r1); + + pen.setBrush(inColor); + pen.setWidthF(1); + p.setPen(pen); + p.setBrush(c); + p.drawEllipse(r2); + } + + if (d_ptr->m_model->currentStop() == stop) { + p.setBrush(Qt::NoBrush); + pen.setWidthF(5); + pen.setBrush(drawColor); + int corr = 4; + if (!d_ptr->m_model->isSelected(stop)) { + corr = 3; + pen.setWidthF(7); + } + p.setPen(pen); + p.drawEllipse(rect.adjusted(corr, corr, -corr, -corr)); + } + + } + p.restore(); + } + } + if (d_ptr->m_backgroundCheckered) { + p.end(); + p.begin(viewport()); + p.drawPixmap(0, 0, pix); + } + p.end(); +} + +void QtGradientStopsWidget::focusInEvent(QFocusEvent *e) +{ + Q_UNUSED(e); + viewport()->update(); +} + +void QtGradientStopsWidget::focusOutEvent(QFocusEvent *e) +{ + Q_UNUSED(e); + viewport()->update(); +} + +void QtGradientStopsWidget::contextMenuEvent(QContextMenuEvent *e) +{ + if (!d_ptr->m_model) + return; + + d_ptr->m_clickPos = e->pos(); + + QMenu menu(this); + QAction *newStopAction = new QAction(tr("New Stop"), &menu); + QAction *deleteAction = new QAction(tr("Delete"), &menu); + QAction *flipAllAction = new QAction(tr("Flip All"), &menu); + QAction *selectAllAction = new QAction(tr("Select All"), &menu); + QAction *zoomInAction = new QAction(tr("Zoom In"), &menu); + QAction *zoomOutAction = new QAction(tr("Zoom Out"), &menu); + QAction *zoomAllAction = new QAction(tr("Reset Zoom"), &menu); + if (d_ptr->m_model->selectedStops().isEmpty() && !d_ptr->m_model->currentStop()) + deleteAction->setEnabled(false); + if (zoom() <= 1) { + zoomOutAction->setEnabled(false); + zoomAllAction->setEnabled(false); + } else if (zoom() >= 100) { + zoomInAction->setEnabled(false); + } + connect(newStopAction, &QAction::triggered, d_ptr.data(), &QtGradientStopsWidgetPrivate::slotNewStop); + connect(deleteAction, &QAction::triggered, d_ptr.data(), &QtGradientStopsWidgetPrivate::slotDelete); + connect(flipAllAction, &QAction::triggered, d_ptr.data(), &QtGradientStopsWidgetPrivate::slotFlipAll); + connect(selectAllAction, &QAction::triggered, d_ptr.data(), &QtGradientStopsWidgetPrivate::slotSelectAll); + connect(zoomInAction, &QAction::triggered, d_ptr.data(), &QtGradientStopsWidgetPrivate::slotZoomIn); + connect(zoomOutAction, &QAction::triggered, d_ptr.data(), &QtGradientStopsWidgetPrivate::slotZoomOut); + connect(zoomAllAction, &QAction::triggered, d_ptr.data(), &QtGradientStopsWidgetPrivate::slotResetZoom); + menu.addAction(newStopAction); + menu.addAction(deleteAction); + menu.addAction(flipAllAction); + menu.addAction(selectAllAction); + menu.addSeparator(); + menu.addAction(zoomInAction); + menu.addAction(zoomOutAction); + menu.addAction(zoomAllAction); + menu.exec(e->globalPos()); +} + +void QtGradientStopsWidget::wheelEvent(QWheelEvent *e) +{ + int numDegrees = e->angleDelta().y() / 8; + int numSteps = numDegrees / 15; + + int shift = numSteps; + if (shift < 0) + shift = -shift; + int pow = 1 << shift; + //const double c = 0.7071067; // 2 steps per doubled value + const double c = 0.5946036; // 4 steps pre doubled value + // in general c = pow(2, 1 / n) / 2; where n is the step + double factor = pow * c; + + double newZoom = zoom(); + if (numSteps < 0) + newZoom /= factor; + else + newZoom *= factor; + if (newZoom > 100) + newZoom = 100; + if (newZoom < 1) + newZoom = 1; + + if (newZoom == zoom()) + return; + + setZoom(newZoom); + emit zoomChanged(zoom()); +} + +#ifndef QT_NO_DRAGANDDROP +void QtGradientStopsWidget::dragEnterEvent(QDragEnterEvent *event) +{ + const QMimeData *mime = event->mimeData(); + if (!mime->hasColor()) + return; + event->accept(); + d_ptr->m_dragModel = d_ptr->m_model->clone(); + + d_ptr->m_dragColor = qvariant_cast(mime->colorData()); + update(); +} + +void QtGradientStopsWidget::dragMoveEvent(QDragMoveEvent *event) +{ + QRectF rect = viewport()->rect(); + rect.adjust(0, d_ptr->m_handleSize, 0, 0); + double x = d_ptr->fromViewport(event->position().toPoint().x()); + QtGradientStop *dragStop = d_ptr->stopAt(event->position().toPoint()); + if (dragStop) { + event->accept(); + d_ptr->removeClonedStop(); + d_ptr->changeStop(dragStop->position()); + } else if (rect.contains(event->position().toPoint())) { + event->accept(); + if (d_ptr->m_model->at(x)) { + d_ptr->removeClonedStop(); + d_ptr->changeStop(x); + } else { + d_ptr->restoreChangedStop(); + d_ptr->cloneStop(x); + } + } else { + event->ignore(); + d_ptr->removeClonedStop(); + d_ptr->restoreChangedStop(); + } + + update(); +} + +void QtGradientStopsWidget::dragLeaveEvent(QDragLeaveEvent *event) +{ + event->accept(); + d_ptr->clearDrag(); + update(); +} + +void QtGradientStopsWidget::dropEvent(QDropEvent *event) +{ + event->accept(); + if (!d_ptr->m_dragModel) + return; + + if (d_ptr->m_changedStop) + d_ptr->m_model->changeStop(d_ptr->m_model->at(d_ptr->m_changedStop->position()), d_ptr->m_dragColor); + else if (d_ptr->m_clonedStop) + d_ptr->m_model->addStop(d_ptr->m_clonedStop->position(), d_ptr->m_dragColor); + + d_ptr->clearDrag(); + update(); +} + +void QtGradientStopsWidgetPrivate::clearDrag() +{ + removeClonedStop(); + restoreChangedStop(); + delete m_dragModel; + m_dragModel = 0; +} + +void QtGradientStopsWidgetPrivate::removeClonedStop() +{ + if (!m_clonedStop) + return; + m_dragModel->removeStop(m_clonedStop); + m_clonedStop = 0; +} + +void QtGradientStopsWidgetPrivate::restoreChangedStop() +{ + if (!m_changedStop) + return; + m_dragModel->changeStop(m_changedStop, m_model->at(m_changedStop->position())->color()); + m_changedStop = 0; + m_dragStop = 0; +} + +void QtGradientStopsWidgetPrivate::changeStop(qreal pos) +{ + QtGradientStop *stop = m_dragModel->at(pos); + if (!stop) + return; + + m_dragModel->changeStop(stop, m_dragColor); + m_changedStop = stop; + m_dragStop = m_model->at(stop->position()); +} + +void QtGradientStopsWidgetPrivate::cloneStop(qreal pos) +{ + if (m_clonedStop) { + m_dragModel->moveStop(m_clonedStop, pos); + return; + } + QtGradientStop *stop = m_dragModel->at(pos); + if (stop) + return; + + m_clonedStop = m_dragModel->addStop(pos, m_dragColor); +} + +#endif + +void QtGradientStopsWidget::setZoom(double zoom) +{ + double z = zoom; + if (z < 1) + z = 1; + else if (z > 100) + z = 100; + + if (d_ptr->m_zoom == z) + return; + + d_ptr->m_zoom = z; + int oldMax = horizontalScrollBar()->maximum(); + int oldVal = horizontalScrollBar()->value(); + horizontalScrollBar()->setRange(0, qRound(d_ptr->m_scaleFactor * (d_ptr->m_zoom - 1))); + int newMax = horizontalScrollBar()->maximum(); + const double newVal = (oldVal + double(d_ptr->m_scaleFactor) / 2) * (newMax + d_ptr->m_scaleFactor) + / (oldMax + d_ptr->m_scaleFactor) - double(d_ptr->m_scaleFactor) / 2; + horizontalScrollBar()->setValue(qRound(newVal)); + viewport()->update(); +} + +double QtGradientStopsWidget::zoom() const +{ + return d_ptr->m_zoom; +} + +QT_END_NAMESPACE + +#include "qtgradientstopswidget.moc" diff --git a/src/shared/qtgradienteditor/qtgradientstopswidget_p.h b/src/shared/qtgradienteditor/qtgradientstopswidget_p.h new file mode 100644 index 00000000000..ae40f961207 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientstopswidget_p.h @@ -0,0 +1,73 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTGRADIENTSTOPSWIDGET_H +#define QTGRADIENTSTOPSWIDGET_H + +#include + +QT_BEGIN_NAMESPACE + +class QtGradientStopsModel; +class QtGradientStopsWidgetPrivate; + +class QtGradientStopsWidget : public QAbstractScrollArea +{ + Q_OBJECT + Q_PROPERTY(bool backgroundCheckered READ isBackgroundCheckered WRITE setBackgroundCheckered) +public: + QtGradientStopsWidget(QWidget *parent = 0); + ~QtGradientStopsWidget(); + + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + + bool isBackgroundCheckered() const; + void setBackgroundCheckered(bool checkered); + + void setGradientStopsModel(QtGradientStopsModel *model); + + void setZoom(double zoom); + double zoom() const; + +signals: + void zoomChanged(double zoom); + +protected: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void focusInEvent(QFocusEvent *e) override; + void focusOutEvent(QFocusEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + void wheelEvent(QWheelEvent *e) override; +#ifndef QT_NO_DRAGANDDROP + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; +#endif + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtGradientStopsWidget) + Q_DISABLE_COPY_MOVE(QtGradientStopsWidget) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtgradientutils.cpp b/src/shared/qtgradienteditor/qtgradientutils.cpp new file mode 100644 index 00000000000..250a760c0af --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientutils.cpp @@ -0,0 +1,383 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradientutils_p.h" +#include "qtgradientmanager_p.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QString gradientTypeToString(QGradient::Type type) +{ + if (type == QGradient::LinearGradient) + return "LinearGradient"_L1; + if (type == QGradient::RadialGradient) + return "RadialGradient"_L1; + if (type == QGradient::ConicalGradient) + return "ConicalGradient"_L1; + return "NoGradient"_L1; +} + +static QGradient::Type stringToGradientType(const QString &name) +{ + if (name == "LinearGradient"_L1) + return QGradient::LinearGradient; + if (name == "RadialGradient"_L1) + return QGradient::RadialGradient; + if (name == "ConicalGradient"_L1) + return QGradient::ConicalGradient; + return QGradient::NoGradient; +} + +static QString gradientSpreadToString(QGradient::Spread spread) +{ + if (spread == QGradient::PadSpread) + return "PadSpread"_L1; + if (spread == QGradient::RepeatSpread) + return "RepeatSpread"_L1; + if (spread == QGradient::ReflectSpread) + return "ReflectSpread"_L1; + return "PadSpread"_L1; +} + +static QGradient::Spread stringToGradientSpread(const QString &name) +{ + if (name == "PadSpread"_L1) + return QGradient::PadSpread; + if (name == "RepeatSpread"_L1) + return QGradient::RepeatSpread; + if (name == "ReflectSpread"_L1) + return QGradient::ReflectSpread; + return QGradient::PadSpread; +} + +static QString gradientCoordinateModeToString(QGradient::CoordinateMode mode) +{ + if (mode == QGradient::LogicalMode) + return "LogicalMode"_L1; + if (mode == QGradient::StretchToDeviceMode) + return "StretchToDeviceMode"_L1; + if (mode == QGradient::ObjectBoundingMode) + return "ObjectBoundingMode"_L1; + return "StretchToDeviceMode"_L1; +} + +static QGradient::CoordinateMode stringToGradientCoordinateMode(const QString &name) +{ + if (name == "LogicalMode"_L1) + return QGradient::LogicalMode; + if (name == "StretchToDeviceMode"_L1) + return QGradient::StretchToDeviceMode; + if (name == "ObjectBoundingMode"_L1) + return QGradient::ObjectBoundingMode; + return QGradient::StretchToDeviceMode; +} + +static QDomElement saveColor(QDomDocument &doc, QColor color) +{ + QDomElement colorElem = doc.createElement("colorData"_L1); + + colorElem.setAttribute("r"_L1, QString::number(color.red())); + colorElem.setAttribute("g"_L1, QString::number(color.green())); + colorElem.setAttribute("b"_L1, QString::number(color.blue())); + colorElem.setAttribute("a"_L1, QString::number(color.alpha())); + + return colorElem; +} + +static QDomElement saveGradientStop(QDomDocument &doc, const QGradientStop &stop) +{ + QDomElement stopElem = doc.createElement("stopData"_L1); + + stopElem.setAttribute("position"_L1, QString::number(stop.first)); + + const QDomElement colorElem = saveColor(doc, stop.second); + stopElem.appendChild(colorElem); + + return stopElem; +} + +static QDomElement saveGradient(QDomDocument &doc, const QGradient &gradient) +{ + QDomElement gradElem = doc.createElement("gradientData"_L1); + + const QGradient::Type type = gradient.type(); + gradElem.setAttribute("type"_L1, gradientTypeToString(type)); + gradElem.setAttribute("spread"_L1, gradientSpreadToString(gradient.spread())); + gradElem.setAttribute("coordinateMode"_L1, gradientCoordinateModeToString(gradient.coordinateMode())); + + const QGradientStops stops = gradient.stops(); + for (const QGradientStop &stop : stops) + gradElem.appendChild(saveGradientStop(doc, stop)); + + if (type == QGradient::LinearGradient) { + const QLinearGradient &g = *static_cast(&gradient); + gradElem.setAttribute("startX"_L1, QString::number(g.start().x())); + gradElem.setAttribute("startY"_L1, QString::number(g.start().y())); + gradElem.setAttribute("endX"_L1, QString::number(g.finalStop().x())); + gradElem.setAttribute("endY"_L1, QString::number(g.finalStop().y())); + } else if (type == QGradient::RadialGradient) { + const QRadialGradient &g = *static_cast(&gradient); + gradElem.setAttribute("centerX"_L1, QString::number(g.center().x())); + gradElem.setAttribute("centerY"_L1, QString::number(g.center().y())); + gradElem.setAttribute("focalX"_L1, QString::number(g.focalPoint().x())); + gradElem.setAttribute("focalY"_L1, QString::number(g.focalPoint().y())); + gradElem.setAttribute("radius"_L1, QString::number(g.radius())); + } else if (type == QGradient::ConicalGradient) { + const QConicalGradient &g = *static_cast(&gradient); + gradElem.setAttribute("centerX"_L1, QString::number(g.center().x())); + gradElem.setAttribute("centerY"_L1, QString::number(g.center().y())); + gradElem.setAttribute("angle"_L1, QString::number(g.angle())); + } + + return gradElem; +} + +static QColor loadColor(const QDomElement &elem) +{ + if (elem.tagName() != "colorData"_L1) + return QColor(); + + return QColor(elem.attribute("r"_L1).toInt(), + elem.attribute("g"_L1).toInt(), + elem.attribute("b"_L1).toInt(), + elem.attribute("a"_L1).toInt()); +} + +static QGradientStop loadGradientStop(const QDomElement &elem) +{ + if (elem.tagName() != "stopData"_L1) + return QGradientStop(); + + const qreal pos = static_cast(elem.attribute("position"_L1).toDouble()); + return std::make_pair(pos, loadColor(elem.firstChild().toElement())); +} + +static QGradient loadGradient(const QDomElement &elem) +{ + if (elem.tagName() != "gradientData"_L1) + return QLinearGradient(); + + const QGradient::Type type = stringToGradientType(elem.attribute("type"_L1)); + const QGradient::Spread spread = stringToGradientSpread(elem.attribute("spread"_L1)); + const QGradient::CoordinateMode mode = stringToGradientCoordinateMode(elem.attribute("coordinateMode"_L1)); + + QGradient gradient = QLinearGradient(); + + if (type == QGradient::LinearGradient) { + QLinearGradient g; + g.setStart(elem.attribute("startX"_L1).toDouble(), elem.attribute("startY"_L1).toDouble()); + g.setFinalStop(elem.attribute("endX"_L1).toDouble(), elem.attribute("endY"_L1).toDouble()); + gradient = g; + } else if (type == QGradient::RadialGradient) { + QRadialGradient g; + g.setCenter(elem.attribute("centerX"_L1).toDouble(), elem.attribute("centerY"_L1).toDouble()); + g.setFocalPoint(elem.attribute("focalX"_L1).toDouble(), elem.attribute("focalY"_L1).toDouble()); + g.setRadius(elem.attribute("radius"_L1).toDouble()); + gradient = g; + } else if (type == QGradient::ConicalGradient) { + QConicalGradient g; + g.setCenter(elem.attribute("centerX"_L1).toDouble(), elem.attribute("centerY"_L1).toDouble()); + g.setAngle(elem.attribute("angle"_L1).toDouble()); + gradient = g; + } + + QDomElement stopElem = elem.firstChildElement(); + while (!stopElem.isNull()) { + QGradientStop stop = loadGradientStop(stopElem); + + gradient.setColorAt(stop.first, stop.second); + + stopElem = stopElem.nextSiblingElement(); + } + + gradient.setSpread(spread); + gradient.setCoordinateMode(mode); + + return gradient; +} + +QString QtGradientUtils::saveState(const QtGradientManager *manager) +{ + QDomDocument doc; + + QDomElement rootElem = doc.createElement("gradients"_L1); + + QMap grads = manager->gradients(); + for (auto itGrad = grads.cbegin(), end = grads.cend(); itGrad != end; ++itGrad) { + QDomElement idElem = doc.createElement("gradient"_L1); + idElem.setAttribute("name"_L1, itGrad.key()); + QDomElement gradElem = saveGradient(doc, itGrad.value()); + idElem.appendChild(gradElem); + + rootElem.appendChild(idElem); + } + + doc.appendChild(rootElem); + + return doc.toString(); +} + +void QtGradientUtils::restoreState(QtGradientManager *manager, const QString &state) +{ + manager->clear(); + + QDomDocument doc; + doc.setContent(state); + + QDomElement rootElem = doc.documentElement(); + + QDomElement gradElem = rootElem.firstChildElement(); + while (!gradElem.isNull()) { + const QString name = gradElem.attribute("name"_L1); + const QGradient gradient = loadGradient(gradElem.firstChildElement()); + + manager->addGradient(name, gradient); + gradElem = gradElem.nextSiblingElement(); + } +} + +QPixmap QtGradientUtils::gradientPixmap(const QGradient &gradient, QSize size, + bool checkeredBackground) +{ + QImage image(size, QImage::Format_ARGB32); + QPainter p(&image); + p.setCompositionMode(QPainter::CompositionMode_Source); + + if (checkeredBackground) { + int pixSize = 20; + QPixmap pm(2 * pixSize, 2 * pixSize); + + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray); + + p.setBrushOrigin((size.width() % pixSize + pixSize) / 2, (size.height() % pixSize + pixSize) / 2); + p.fillRect(0, 0, size.width(), size.height(), pm); + p.setBrushOrigin(0, 0); + p.setCompositionMode(QPainter::CompositionMode_SourceOver); + } + + const qreal scaleFactor = 0.999999; + p.scale(scaleFactor, scaleFactor); + QGradient grad = gradient; + grad.setCoordinateMode(QGradient::StretchToDeviceMode); + p.fillRect(QRect(0, 0, size.width(), size.height()), grad); + p.drawRect(QRect(0, 0, size.width() - 1, size.height() - 1)); + + return QPixmap::fromImage(image); +} + +static QString styleSheetFillName(const QGradient &gradient) +{ + QString result; + + switch (gradient.type()) { + case QGradient::LinearGradient: + result += "qlineargradient"_L1; + break; + case QGradient::RadialGradient: + result += "qradialgradient"_L1; + break; + case QGradient::ConicalGradient: + result += "qconicalgradient"_L1; + break; + default: + qWarning() << "QtGradientUtils::styleSheetFillName(): gradient type" << gradient.type() << "not supported!"; + break; + } + + return result; +} + +static QStringList styleSheetParameters(const QGradient &gradient) +{ + QStringList result; + + if (gradient.type() != QGradient::ConicalGradient) { + QString spread; + switch (gradient.spread()) { + case QGradient::PadSpread: + spread = "pad"_L1; + break; + case QGradient::ReflectSpread: + spread = "reflect"_L1; + break; + case QGradient::RepeatSpread: + spread = "repeat"_L1; + break; + default: + qWarning() << "QtGradientUtils::styleSheetParameters(): gradient spread" << gradient.spread() << "not supported!"; + break; + } + result << "spread:"_L1 + spread; + } + + switch (gradient.type()) { + case QGradient::LinearGradient: { + const QLinearGradient *linearGradient = static_cast(&gradient); + result << "x1:"_L1 + QString::number(linearGradient->start().x()) + << "y1:"_L1 + QString::number(linearGradient->start().y()) + << "x2:"_L1 + QString::number(linearGradient->finalStop().x()) + << "y2:"_L1 + QString::number(linearGradient->finalStop().y()); + break; + } + case QGradient::RadialGradient: { + const QRadialGradient *radialGradient = static_cast(&gradient); + result << "cx:"_L1 + QString::number(radialGradient->center().x()) + << "cy:"_L1 + QString::number(radialGradient->center().y()) + << "radius:"_L1 + QString::number(radialGradient->radius()) + << "fx:"_L1 + QString::number(radialGradient->focalPoint().x()) + << "fy:"_L1 + QString::number(radialGradient->focalPoint().y()); + break; + } + case QGradient::ConicalGradient: { + const QConicalGradient *conicalGradient = static_cast(&gradient); + result << "cx:"_L1 + QString::number(conicalGradient->center().x()) + << "cy:"_L1 + QString::number(conicalGradient->center().y()) + << "angle:"_L1 + QString::number(conicalGradient->angle()); + break; + } + default: + qWarning() << "QtGradientUtils::styleSheetParameters(): gradient type" << gradient.type() << "not supported!"; + break; + } + + return result; +} + +static QStringList styleSheetStops(const QGradient &gradient) +{ + QStringList result; + const QGradientStops &stops = gradient.stops(); + for (const QGradientStop &stop : stops) { + const QColor color = stop.second; + + const QString stopDescription = "stop:"_L1 + QString::number(stop.first) + " rgba("_L1 + + QString::number(color.red()) + ", "_L1 + + QString::number(color.green()) + ", "_L1 + + QString::number(color.blue()) + ", "_L1 + + QString::number(color.alpha()) + QLatin1Char(')'); + result << stopDescription; + } + + return result; +} + +QString QtGradientUtils::styleSheetCode(const QGradient &gradient) +{ + QStringList gradientParameters; + gradientParameters << styleSheetParameters(gradient) << styleSheetStops(gradient); + + return styleSheetFillName(gradient) + QLatin1Char('(') + gradientParameters.join(", "_L1) + QLatin1Char(')'); +} + +QT_END_NAMESPACE diff --git a/src/shared/qtgradienteditor/qtgradientutils_p.h b/src/shared/qtgradienteditor/qtgradientutils_p.h new file mode 100644 index 00000000000..532d7b4afc0 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientutils_p.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef GRADIENTUTILS_H +#define GRADIENTUTILS_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QtGradientManager; + +class QtGradientUtils +{ +public: + static QString styleSheetCode(const QGradient &gradient); + // utils methods, they could be outside of this class + static QString saveState(const QtGradientManager *manager); + static void restoreState(QtGradientManager *manager, const QString &state); + + static QPixmap gradientPixmap(const QGradient &gradient, QSize size = QSize(64, 64), + bool checkeredBackground = false); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtgradientview.cpp b/src/shared/qtgradienteditor/qtgradientview.cpp new file mode 100644 index 00000000000..519992f15fb --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientview.cpp @@ -0,0 +1,256 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradientview_p.h" +#include "qtgradientmanager_p.h" +#include "qtgradientdialog_p.h" +#include "qtgradientutils_p.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +void QtGradientView::slotGradientAdded(const QString &id, const QGradient &gradient) +{ + QListWidgetItem *item = new QListWidgetItem(QtGradientUtils::gradientPixmap(gradient), id, m_ui.listWidget); + item->setToolTip(id); + item->setSizeHint(QSize(72, 84)); + item->setFlags(item->flags() | Qt::ItemIsEditable); + + m_idToItem[id] = item; + m_itemToId[item] = id; +} + +void QtGradientView::slotGradientRenamed(const QString &id, const QString &newId) +{ + if (!m_idToItem.contains(id)) + return; + + QListWidgetItem *item = m_idToItem.value(id); + item->setText(newId); + item->setToolTip(newId); + m_itemToId[item] = newId; + m_idToItem.remove(id); + m_idToItem[newId] = item; +} + +void QtGradientView::slotGradientChanged(const QString &id, const QGradient &newGradient) +{ + if (!m_idToItem.contains(id)) + return; + + QListWidgetItem *item = m_idToItem.value(id); + item->setIcon(QtGradientUtils::gradientPixmap(newGradient)); +} + +void QtGradientView::slotGradientRemoved(const QString &id) +{ + if (!m_idToItem.contains(id)) + return; + + QListWidgetItem *item = m_idToItem.value(id); + delete item; + m_itemToId.remove(item); + m_idToItem.remove(id); +} + +void QtGradientView::slotNewGradient() +{ + bool ok; + QListWidgetItem *item = m_ui.listWidget->currentItem(); + QGradient grad = QLinearGradient(); + if (item) + grad = m_manager->gradients().value(m_itemToId.value(item)); + QGradient gradient = QtGradientDialog::getGradient(&ok, grad, this); + if (!ok) + return; + + QString id = m_manager->addGradient(tr("Grad"), gradient); + m_ui.listWidget->setCurrentItem(m_idToItem.value(id)); +} + +void QtGradientView::slotEditGradient() +{ + bool ok; + QListWidgetItem *item = m_ui.listWidget->currentItem(); + if (!item) + return; + + const QString id = m_itemToId.value(item); + QGradient grad = m_manager->gradients().value(id); + QGradient gradient = QtGradientDialog::getGradient(&ok, grad, this); + if (!ok) + return; + + m_manager->changeGradient(id, gradient); +} + +void QtGradientView::slotRemoveGradient() +{ + QListWidgetItem *item = m_ui.listWidget->currentItem(); + if (!item) + return; + + if (QMessageBox::question(this, tr("Remove Gradient"), + tr("Are you sure you want to remove the selected gradient?"), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Yes) + return; + + const QString id = m_itemToId.value(item); + m_manager->removeGradient(id); +} + +void QtGradientView::slotRenameGradient() +{ + QListWidgetItem *item = m_ui.listWidget->currentItem(); + if (!item) + return; + + m_ui.listWidget->editItem(item); +} + +void QtGradientView::slotRenameGradientItem(QListWidgetItem *item) +{ + if (!item) + return; + + const QString id = m_itemToId.value(item); + m_manager->renameGradient(id, item->text()); +} + +void QtGradientView::slotCurrentItemChanged(QListWidgetItem *item) +{ + m_editAction->setEnabled(item); + m_renameAction->setEnabled(item); + m_removeAction->setEnabled(item); + emit currentGradientChanged(m_itemToId.value(item)); +} + +void QtGradientView::slotGradientActivated(QListWidgetItem *item) +{ + const QString id = m_itemToId.value(item); + if (!id.isEmpty()) + emit gradientActivated(id); +} + +QtGradientView::QtGradientView(QWidget *parent) + : QWidget(parent) +{ + m_manager = nullptr; + + m_ui.setupUi(this); + + m_ui.listWidget->setViewMode(QListView::IconMode); + m_ui.listWidget->setMovement(QListView::Static); + m_ui.listWidget->setTextElideMode(Qt::ElideRight); + m_ui.listWidget->setResizeMode(QListWidget::Adjust); + m_ui.listWidget->setIconSize(QSize(64, 64)); + m_ui.listWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); + + QPalette pal = m_ui.listWidget->viewport()->palette(); + int pixSize = 18; + QPixmap pm(2 * pixSize, 2 * pixSize); + + QColor c1 = palette().color(QPalette::Midlight); + QColor c2 = palette().color(QPalette::Dark); + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, c1); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, c1); + pmp.fillRect(0, pixSize, pixSize, pixSize, c2); + pmp.fillRect(pixSize, 0, pixSize, pixSize, c2); + + pal.setBrush(QPalette::Base, QBrush(pm)); + m_ui.listWidget->viewport()->setPalette(pal); + + connect(m_ui.listWidget, &QListWidget::itemDoubleClicked, this, &QtGradientView::slotGradientActivated); + connect(m_ui.listWidget, &QListWidget::itemChanged, this, &QtGradientView::slotRenameGradientItem); + connect(m_ui.listWidget, &QListWidget::currentItemChanged, this, &QtGradientView::slotCurrentItemChanged); + + m_newAction = new QAction(QIcon(":/qt-project.org/qtgradienteditor/images/plus.png"_L1), tr("New..."), this); + m_editAction = new QAction(QIcon(":/qt-project.org/qtgradienteditor/images/edit.png"_L1), tr("Edit..."), this); + m_renameAction = new QAction(tr("Rename"), this); + m_removeAction = new QAction(QIcon(":/qt-project.org/qtgradienteditor/images/minus.png"_L1), tr("Remove"), this); + + connect(m_newAction, &QAction::triggered, this, &QtGradientView::slotNewGradient); + connect(m_editAction, &QAction::triggered, this, &QtGradientView::slotEditGradient); + connect(m_removeAction, &QAction::triggered, this, &QtGradientView::slotRemoveGradient); + connect(m_renameAction, &QAction::triggered, this, &QtGradientView::slotRenameGradient); + + m_ui.listWidget->addAction(m_newAction); + m_ui.listWidget->addAction(m_editAction); + m_ui.listWidget->addAction(m_renameAction); + m_ui.listWidget->addAction(m_removeAction); + + m_ui.newButton->setDefaultAction(m_newAction); + m_ui.editButton->setDefaultAction(m_editAction); + m_ui.renameButton->setDefaultAction(m_renameAction); + m_ui.removeButton->setDefaultAction(m_removeAction); + + m_ui.listWidget->setContextMenuPolicy(Qt::ActionsContextMenu); +} + +void QtGradientView::setGradientManager(QtGradientManager *manager) +{ + if (m_manager == manager) + return; + + if (m_manager) { + disconnect(m_manager, &QtGradientManager::gradientAdded, + this, &QtGradientView::slotGradientAdded); + disconnect(m_manager, &QtGradientManager::gradientRenamed, + this, &QtGradientView::slotGradientRenamed); + disconnect(m_manager, &QtGradientManager::gradientChanged, + this, &QtGradientView::slotGradientChanged); + disconnect(m_manager, &QtGradientManager::gradientRemoved, + this, &QtGradientView::slotGradientRemoved); + + m_ui.listWidget->clear(); + m_idToItem.clear(); + m_itemToId.clear(); + } + + m_manager = manager; + + if (!m_manager) + return; + + QMap gradients = m_manager->gradients(); + for (auto itGrad = gradients.cbegin(), end = gradients.cend(); itGrad != end; ++itGrad) + slotGradientAdded(itGrad.key(), itGrad.value()); + + connect(m_manager, &QtGradientManager::gradientAdded, + this, &QtGradientView::slotGradientAdded); + connect(m_manager, &QtGradientManager::gradientRenamed, + this, &QtGradientView::slotGradientRenamed); + connect(m_manager, &QtGradientManager::gradientChanged, + this, &QtGradientView::slotGradientChanged); + connect(m_manager, &QtGradientManager::gradientRemoved, + this, &QtGradientView::slotGradientRemoved); +} + +QtGradientManager *QtGradientView::gradientManager() const +{ + return m_manager; +} + +void QtGradientView::setCurrentGradient(const QString &id) +{ + QListWidgetItem *item = m_idToItem.value(id); + if (!item) + return; + + m_ui.listWidget->setCurrentItem(item); +} + +QString QtGradientView::currentGradient() const +{ + return m_itemToId.value(m_ui.listWidget->currentItem()); +} + +QT_END_NAMESPACE diff --git a/src/shared/qtgradienteditor/qtgradientview.ui b/src/shared/qtgradienteditor/qtgradientview.ui new file mode 100644 index 00000000000..af7267ea2b0 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientview.ui @@ -0,0 +1,135 @@ + + QtGradientView + + + + 0 + 0 + 484 + 228 + + + + Gradient View + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + New... + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + + 0 + 0 + + + + Edit... + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + + 0 + 0 + + + + Rename + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + + 0 + 0 + + + + Remove + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + Qt::Horizontal + + + + 71 + 26 + + + + + + + + + + + + + listWidget + newButton + editButton + renameButton + removeButton + + + + diff --git a/src/shared/qtgradienteditor/qtgradientview_p.h b/src/shared/qtgradienteditor/qtgradientview_p.h new file mode 100644 index 00000000000..84b186c6f1c --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientview_p.h @@ -0,0 +1,72 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef GRADIENTVIEW_H +#define GRADIENTVIEW_H + +#include +#include +#include "ui_qtgradientview.h" + +QT_BEGIN_NAMESPACE + +class QtGradientManager; +class QListViewItem; +class QAction; + +class QtGradientView : public QWidget +{ + Q_OBJECT +public: + QtGradientView(QWidget *parent = 0); + + void setGradientManager(QtGradientManager *manager); + QtGradientManager *gradientManager() const; + + void setCurrentGradient(const QString &id); + QString currentGradient() const; + +signals: + void currentGradientChanged(const QString &id); + void gradientActivated(const QString &id); + +private slots: + void slotGradientAdded(const QString &id, const QGradient &gradient); + void slotGradientRenamed(const QString &id, const QString &newId); + void slotGradientChanged(const QString &id, const QGradient &newGradient); + void slotGradientRemoved(const QString &id); + void slotNewGradient(); + void slotEditGradient(); + void slotRemoveGradient(); + void slotRenameGradient(); + void slotRenameGradientItem(QListWidgetItem *item); + void slotCurrentItemChanged(QListWidgetItem *item); + void slotGradientActivated(QListWidgetItem *item); + +private: + QHash m_idToItem; + QHash m_itemToId; + + QAction *m_newAction; + QAction *m_editAction; + QAction *m_renameAction; + QAction *m_removeAction; + + QtGradientManager *m_manager; + Ui::QtGradientView m_ui; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtgradienteditor/qtgradientviewdialog.cpp b/src/shared/qtgradienteditor/qtgradientviewdialog.cpp new file mode 100644 index 00000000000..a80ce121f7e --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientviewdialog.cpp @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradientviewdialog_p.h" +#include "qtgradientmanager_p.h" +#include + +QT_BEGIN_NAMESPACE + +QtGradientViewDialog::QtGradientViewDialog(QWidget *parent) + : QDialog(parent) +{ + m_ui.setupUi(this); + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + connect(m_ui.gradientView, &QtGradientView::currentGradientChanged, + this, &QtGradientViewDialog::slotGradientSelected); + connect(m_ui.gradientView, &QtGradientView::gradientActivated, + this, &QtGradientViewDialog::slotGradientActivated); +} + +void QtGradientViewDialog::setGradientManager(QtGradientManager *manager) +{ + m_ui.gradientView->setGradientManager(manager); +} + +QGradient QtGradientViewDialog::getGradient(bool *ok, QtGradientManager *manager, QWidget *parent, const QString &caption) +{ + QtGradientViewDialog dlg(parent); + dlg.setGradientManager(manager); + dlg.setWindowTitle(caption); + QGradient grad = QLinearGradient(); + const int res = dlg.exec(); + if (res == QDialog::Accepted) + grad = dlg.m_ui.gradientView->gradientManager()->gradients().value(dlg.m_ui.gradientView->currentGradient()); + if (ok) + *ok = res == QDialog::Accepted; + return grad; +} + +void QtGradientViewDialog::slotGradientSelected(const QString &id) +{ + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!id.isEmpty()); +} + +void QtGradientViewDialog::slotGradientActivated(const QString &id) +{ + Q_UNUSED(id); + accept(); +} + +QT_END_NAMESPACE diff --git a/src/shared/qtgradienteditor/qtgradientviewdialog.ui b/src/shared/qtgradienteditor/qtgradientviewdialog.ui new file mode 100644 index 00000000000..442d1744099 --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientviewdialog.ui @@ -0,0 +1,85 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + + QtGradientViewDialog + + + + 0 + 0 + 178 + 72 + + + + Select Gradient + + + + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + QtGradientView + QFrame +
qtgradientview_p.h
+ 1 +
+
+ + + + buttonBox + accepted() + QtGradientViewDialog + accept() + + + 72 + 224 + + + 21 + 243 + + + + + buttonBox + rejected() + QtGradientViewDialog + reject() + + + 168 + 233 + + + 152 + 251 + + + + +
diff --git a/src/shared/qtgradienteditor/qtgradientviewdialog_p.h b/src/shared/qtgradienteditor/qtgradientviewdialog_p.h new file mode 100644 index 00000000000..2c14e5f6f8f --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientviewdialog_p.h @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef GRADIENTVIEWDIALOG_H +#define GRADIENTVIEWDIALOG_H + +#include +#include +#include "ui_qtgradientviewdialog.h" + +QT_BEGIN_NAMESPACE + +class QtGradientManager; + +class QtGradientViewDialog : public QDialog +{ + Q_OBJECT +public: + QtGradientViewDialog(QWidget *parent = 0); + + void setGradientManager(QtGradientManager *manager); + QtGradientManager *gradientManager() const; + + static QGradient getGradient(bool *ok, QtGradientManager *manager, QWidget *parent = 0, const QString &caption = tr("Select Gradient", 0)); + +private slots: + void slotGradientSelected(const QString &id); + void slotGradientActivated(const QString &id); + +private: + Ui::QtGradientViewDialog m_ui; +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/shared/qtgradienteditor/qtgradientwidget.cpp b/src/shared/qtgradienteditor/qtgradientwidget.cpp new file mode 100644 index 00000000000..0d38f89d6fe --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientwidget.cpp @@ -0,0 +1,757 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgradientwidget_p.h" +#include +#include +#include +#include +#include +#include + +#ifndef _USE_MATH_DEFINES +#define _USE_MATH_DEFINES +#endif + +#include "qmath.h" + +QT_BEGIN_NAMESPACE + +class QtGradientWidgetPrivate +{ + QtGradientWidget *q_ptr; + Q_DECLARE_PUBLIC(QtGradientWidget) +public: + QPointF fromViewport(QPointF point) const; + QPointF toViewport(QPointF point) const; +// void setupDrag(QtGradientStop *stop, int x); + + QPointF checkRange(QPointF point) const; + QRectF pointRect(QPointF point, double size) const; + + double correctAngle(double angle) const; + void setAngleConical(double angle); + + void paintPoint(QPainter *painter, QPointF point, double size) const; + + double m_handleSize; + bool m_backgroundCheckered; + + QGradientStops m_gradientStops; + QGradient::Type m_gradientType; + QGradient::Spread m_gradientSpread; + QPointF m_startLinear; + QPointF m_endLinear; + QPointF m_centralRadial; + QPointF m_focalRadial; + qreal m_radiusRadial; + QPointF m_centralConical; + qreal m_angleConical; + + enum Handle { + NoHandle, + StartLinearHandle, + EndLinearHandle, + CentralRadialHandle, + FocalRadialHandle, + RadiusRadialHandle, + CentralConicalHandle, + AngleConicalHandle + }; + + Handle m_dragHandle; + QPointF m_dragOffset; + //double m_radiusOffset; + double m_radiusFactor; + double m_dragRadius; + double m_angleOffset; + double m_dragAngle; +}; + +double QtGradientWidgetPrivate::correctAngle(double angle) const +{ + double a = angle; + while (a >= 360) + a -= 360; + while (a < 0) + a += 360; + return a; +} + +void QtGradientWidgetPrivate::setAngleConical(double angle) +{ + double a = correctAngle(angle); + if (m_angleConical == a) + return; + m_angleConical = a; + emit q_ptr->angleConicalChanged(m_angleConical); +} + +QRectF QtGradientWidgetPrivate::pointRect(QPointF point, double size) const +{ + return QRectF(point.x() - size / 2, point.y() - size / 2, size, size); +} + +QPointF QtGradientWidgetPrivate::checkRange(QPointF point) const +{ + QPointF p = point; + if (p.x() > 1) + p.setX(1); + else if (p.x() < 0) + p.setX(0); + if (p.y() > 1) + p.setY(1); + else if (p.y() < 0) + p.setY(0); + return p; +} + +QPointF QtGradientWidgetPrivate::fromViewport(QPointF point) const +{ + QSize size = q_ptr->size(); + return QPointF(point.x() / size.width(), point.y() / size.height()); +} + +QPointF QtGradientWidgetPrivate::toViewport(QPointF point) const +{ + QSize size = q_ptr->size(); + return QPointF(point.x() * size.width(), point.y() * size.height()); +} + +void QtGradientWidgetPrivate::paintPoint(QPainter *painter, QPointF point, + double size) const +{ + QPointF pf = toViewport(point); + QRectF rf = pointRect(pf, size); + + QPen pen; + pen.setWidthF(1); + QColor alphaZero = Qt::white; + alphaZero.setAlpha(0); + + painter->save(); + painter->drawEllipse(rf); + + /* + painter->save(); + + QLinearGradient lgV(0, rf.top(), 0, rf.bottom()); + lgV.setColorAt(0, alphaZero); + lgV.setColorAt(0.25, Qt::white); + lgV.setColorAt(0.25, Qt::white); + lgV.setColorAt(1, alphaZero); + pen.setBrush(lgV); + painter->setPen(pen); + + painter->drawLine(QPointF(pf.x(), rf.top()), QPointF(pf.x(), rf.bottom())); + + QLinearGradient lgH(rf.left(), 0, rf.right(), 0); + lgH.setColorAt(0, alphaZero); + lgH.setColorAt(0.5, Qt::white); + lgH.setColorAt(1, alphaZero); + pen.setBrush(lgH); + painter->setPen(pen); + + painter->drawLine(QPointF(rf.left(), pf.y()), QPointF(rf.right(), pf.y())); + + painter->restore(); + */ + + painter->restore(); +} + +/* +void QtGradientWidgetPrivate::setupDrag(QtGradientStop *stop, int x) +{ + m_model->setCurrentStop(stop); + + int viewportX = qRound(toViewport(stop->position())); + m_dragOffset = x - viewportX; + + const auto stops = m_stops; + m_stops.clear(); + for (QtGradientStop *s : stops) { + if (m_model->isSelected(s) || s == stop) { + m_dragStops[s] = s->position() - stop->position(); + m_stops.append(s); + } else { + m_dragOriginal[s->position()] = s->color(); + } + } + for (QtGradientStop *s : stops) { + if (!m_model->isSelected(s)) + m_stops.append(s); + } + m_stops.removeAll(stop); + m_stops.prepend(stop); +} +*/ +//////////////////////////// + +QtGradientWidget::QtGradientWidget(QWidget *parent) + : QWidget(parent), d_ptr(new QtGradientWidgetPrivate) +{ + d_ptr->q_ptr = this; + d_ptr->m_backgroundCheckered = true; + d_ptr->m_handleSize = 20.0; + d_ptr->m_gradientType = QGradient::LinearGradient; + d_ptr->m_startLinear = QPointF(0, 0); + d_ptr->m_endLinear = QPointF(1, 1); + d_ptr->m_centralRadial = QPointF(0.5, 0.5); + d_ptr->m_focalRadial = QPointF(0.5, 0.5); + d_ptr->m_radiusRadial = 0.5; + d_ptr->m_centralConical = QPointF(0.5, 0.5); + d_ptr->m_angleConical = 0; + d_ptr->m_dragHandle = QtGradientWidgetPrivate::NoHandle; + + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); +} + +QtGradientWidget::~QtGradientWidget() +{ +} + +QSize QtGradientWidget::sizeHint() const +{ + return QSize(176, 176); +} + +QSize QtGradientWidget::minimumSizeHint() const +{ + return QSize(128, 128); +} + +int QtGradientWidget::heightForWidth(int w) const +{ + return w; +} + +void QtGradientWidget::setBackgroundCheckered(bool checkered) +{ + if (d_ptr->m_backgroundCheckered == checkered) + return; + d_ptr->m_backgroundCheckered = checkered; + update(); +} + +bool QtGradientWidget::isBackgroundCheckered() const +{ + return d_ptr->m_backgroundCheckered; +} + +void QtGradientWidget::mousePressEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) + return; + + QPoint p = e->pos(); + if (d_ptr->m_gradientType == QGradient::LinearGradient) { + QPointF startPoint = d_ptr->toViewport(d_ptr->m_startLinear); + double x = p.x() - startPoint.x(); + double y = p.y() - startPoint.y(); + + if ((d_ptr->m_handleSize * d_ptr->m_handleSize / 4) > (x * x + y * y)) { + d_ptr->m_dragHandle = QtGradientWidgetPrivate::StartLinearHandle; + d_ptr->m_dragOffset = QPointF(x, y); + update(); + return; + } + + QPointF endPoint = d_ptr->toViewport(d_ptr->m_endLinear); + x = p.x() - endPoint.x(); + y = p.y() - endPoint.y(); + + if ((d_ptr->m_handleSize * d_ptr->m_handleSize / 4) > (x * x + y * y)) { + d_ptr->m_dragHandle = QtGradientWidgetPrivate::EndLinearHandle; + d_ptr->m_dragOffset = QPointF(x, y); + update(); + return; + } + } else if (d_ptr->m_gradientType == QGradient::RadialGradient) { + QPointF focalPoint = d_ptr->toViewport(d_ptr->m_focalRadial); + double x = p.x() - focalPoint.x(); + double y = p.y() - focalPoint.y(); + + if ((d_ptr->m_handleSize * d_ptr->m_handleSize / 9) > (x * x + y * y)) { + d_ptr->m_dragHandle = QtGradientWidgetPrivate::FocalRadialHandle; + d_ptr->m_dragOffset = QPointF(x, y); + update(); + return; + } + + QPointF centralPoint = d_ptr->toViewport(d_ptr->m_centralRadial); + x = p.x() - centralPoint.x(); + y = p.y() - centralPoint.y(); + + if ((d_ptr->m_handleSize * d_ptr->m_handleSize / 4) > (x * x + y * y)) { + d_ptr->m_dragHandle = QtGradientWidgetPrivate::CentralRadialHandle; + d_ptr->m_dragOffset = QPointF(x, y); + update(); + return; + } + + QPointF central = d_ptr->toViewport(d_ptr->m_centralRadial); + QRectF r = d_ptr->pointRect(central, 2 * d_ptr->m_handleSize / 3); + QRectF r1(0, r.y(), size().width(), r.height()); + QRectF r2(r.x(), 0, r.width(), r.y()); + QRectF r3(r.x(), r.y() + r.height(), r.width(), size().height() - r.y() - r.height()); + QPointF pF(p.x(), p.y()); + if (r1.contains(pF) || r2.contains(pF) || r3.contains(pF)) { + x = pF.x() / size().width() - d_ptr->m_centralRadial.x(); + y = pF.y() / size().height() - d_ptr->m_centralRadial.y(); + const double clickRadius = hypot(x, y); + //d_ptr->m_radiusOffset = d_ptr->m_radiusRadial - clickRadius; + d_ptr->m_radiusFactor = d_ptr->m_radiusRadial / clickRadius; + if (d_ptr->m_radiusFactor == 0) + d_ptr->m_radiusFactor = 1; + d_ptr->m_dragRadius = d_ptr->m_radiusRadial; + d_ptr->m_dragHandle = QtGradientWidgetPrivate::RadiusRadialHandle; + mouseMoveEvent(e); + update(); + return; + } + } else if (d_ptr->m_gradientType == QGradient::ConicalGradient) { + QPointF centralPoint = d_ptr->toViewport(d_ptr->m_centralConical); + double x = p.x() - centralPoint.x(); + double y = p.y() - centralPoint.y(); + + if ((d_ptr->m_handleSize * d_ptr->m_handleSize / 4) > (x * x + y * y)) { + d_ptr->m_dragHandle = QtGradientWidgetPrivate::CentralConicalHandle; + d_ptr->m_dragOffset = QPointF(x, y); + update(); + return; + } + double radius = size().width(); + if (size().height() < radius) + radius = size().height(); + radius /= 2; + double corr = d_ptr->m_handleSize / 3; + radius -= corr; + QPointF vp = d_ptr->toViewport(d_ptr->m_centralConical); + x = p.x() - vp.x(); + y = p.y() - vp.y(); + if (((radius - corr) * (radius - corr) < (x * x + y * y)) && + ((radius + corr) * (radius + corr) > (x * x + y * y))) { + QPointF central = d_ptr->toViewport(d_ptr->m_centralConical); + QPointF current(e->pos().x(), e->pos().y()); + x = current.x() - central.x(); + y = current.y() - central.y(); + x /= size().width() / 2; + y /= size().height() / 2; + const double angle = qRadiansToDegrees(atan2(-y, x)); + + d_ptr->m_angleOffset = d_ptr->m_angleConical - angle; + d_ptr->m_dragAngle = d_ptr->m_angleConical; + d_ptr->m_dragHandle = QtGradientWidgetPrivate::AngleConicalHandle; + update(); + return; + } + } +} + +void QtGradientWidget::mouseReleaseEvent(QMouseEvent *e) +{ + Q_UNUSED(e); + d_ptr->m_dragHandle = QtGradientWidgetPrivate::NoHandle; + update(); +} + +void QtGradientWidget::mouseMoveEvent(QMouseEvent *e) +{ + if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::NoHandle) + return; + + const QPointF newPos = e->position() - d_ptr->m_dragOffset; + QPointF newPoint = d_ptr->fromViewport(newPos); + if (newPoint.x() < 0) + newPoint.setX(0); + else if (newPoint.x() > 1) + newPoint.setX(1); + if (newPoint.y() < 0) + newPoint.setY(0); + else if (newPoint.y() > 1) + newPoint.setY(1); + + if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::StartLinearHandle) { + d_ptr->m_startLinear = newPoint; + emit startLinearChanged(newPoint); + } else if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::EndLinearHandle) { + d_ptr->m_endLinear = newPoint; + emit endLinearChanged(newPoint); + } else if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::CentralRadialHandle) { + d_ptr->m_centralRadial = newPoint; + emit centralRadialChanged(newPoint); + } else if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::FocalRadialHandle) { + d_ptr->m_focalRadial = newPoint; + emit focalRadialChanged(newPoint); + } else if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::RadiusRadialHandle) { + QPointF centralPoint = d_ptr->toViewport(d_ptr->m_centralRadial); + QPointF pF(e->pos().x(), e->pos().y()); + double x = pF.x() - centralPoint.x(); + double y = pF.y() - centralPoint.y(); + + if ((d_ptr->m_handleSize * d_ptr->m_handleSize / 4) > (x * x + y * y)) { + if (d_ptr->m_radiusRadial != d_ptr->m_dragRadius) { + d_ptr->m_radiusRadial = d_ptr->m_dragRadius; + emit radiusRadialChanged(d_ptr->m_radiusRadial); + } + } else { + x = pF.x() / size().width() - d_ptr->m_centralRadial.x(); + y = pF.y() / size().height() - d_ptr->m_centralRadial.y(); + const double moveRadius = hypot(x, y); + //double newRadius = moveRadius + d_ptr->m_radiusOffset; + double newRadius = moveRadius * d_ptr->m_radiusFactor; + if (newRadius > 2) + newRadius = 2; + d_ptr->m_radiusRadial = newRadius; + emit radiusRadialChanged(d_ptr->m_radiusRadial); + } + } else if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::CentralConicalHandle) { + d_ptr->m_centralConical = newPoint; + emit centralConicalChanged(newPoint); + } else if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::AngleConicalHandle) { + QPointF centralPoint = d_ptr->toViewport(d_ptr->m_centralConical); + QPointF pF(e->pos().x(), e->pos().y()); + double x = pF.x() - centralPoint.x(); + double y = pF.y() - centralPoint.y(); + + if ((d_ptr->m_handleSize * d_ptr->m_handleSize / 4) > (x * x + y * y)) { + if (d_ptr->m_angleConical != d_ptr->m_dragAngle) { + d_ptr->m_angleConical = d_ptr->m_dragAngle; + emit angleConicalChanged(d_ptr->m_angleConical); + } + } else { + QPointF central = d_ptr->toViewport(d_ptr->m_centralConical); + QPointF current = pF; + x = current.x() - central.x(); + y = current.y() - central.y(); + x /= size().width() / 2; + y /= size().height() / 2; + + const double angle = qRadiansToDegrees(atan2(-y, x)) + d_ptr->m_angleOffset; + d_ptr->setAngleConical(angle); + } + } + update(); +} + +void QtGradientWidget::mouseDoubleClickEvent(QMouseEvent *e) +{ + mousePressEvent(e); +} + +void QtGradientWidget::paintEvent(QPaintEvent *e) +{ + Q_UNUSED(e); + + QPainter p(this); + + if (d_ptr->m_backgroundCheckered) { + int pixSize = 40; + QPixmap pm(2 * pixSize, 2 * pixSize); + + QPainter pmp(&pm); + pmp.fillRect(0, 0, pixSize, pixSize, Qt::white); + pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white); + pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); + pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); + + p.setBrushOrigin((size().width() % pixSize + pixSize) / 2, (size().height() % pixSize + pixSize) / 2); + p.fillRect(rect(), pm); + p.setBrushOrigin(0, 0); + } + + QGradient *gradient = nullptr; + switch (d_ptr->m_gradientType) { + case QGradient::LinearGradient: + gradient = new QLinearGradient(d_ptr->m_startLinear, d_ptr->m_endLinear); + break; + case QGradient::RadialGradient: + gradient = new QRadialGradient(d_ptr->m_centralRadial, d_ptr->m_radiusRadial, d_ptr->m_focalRadial); + break; + case QGradient::ConicalGradient: + gradient = new QConicalGradient(d_ptr->m_centralConical, d_ptr->m_angleConical); + break; + default: + break; + } + if (!gradient) + return; + + gradient->setStops(d_ptr->m_gradientStops); + gradient->setSpread(d_ptr->m_gradientSpread); + + p.save(); + p.scale(size().width(), size().height()); + p.fillRect(QRect(0, 0, 1, 1), *gradient); + p.restore(); + + p.setRenderHint(QPainter::Antialiasing); + + QColor c = QColor::fromRgbF(0.5, 0.5, 0.5, 0.5); + QBrush br(c); + p.setBrush(br); + QPen pen(Qt::white); + pen.setWidthF(1); + p.setPen(pen); + QPen dragPen = pen; + dragPen.setWidthF(2); + if (d_ptr->m_gradientType == QGradient::LinearGradient) { + p.save(); + if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::StartLinearHandle) + p.setPen(dragPen); + d_ptr->paintPoint(&p, d_ptr->m_startLinear, d_ptr->m_handleSize); + p.restore(); + + p.save(); + if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::EndLinearHandle) + p.setPen(dragPen); + d_ptr->paintPoint(&p, d_ptr->m_endLinear, d_ptr->m_handleSize); + p.restore(); + } else if (d_ptr->m_gradientType == QGradient::RadialGradient) { + QPointF central = d_ptr->toViewport(d_ptr->m_centralRadial); + + p.save(); + QRectF r = d_ptr->pointRect(central, 2 * d_ptr->m_handleSize / 3); + QRectF r1(0, r.y(), size().width(), r.height()); + QRectF r2(r.x(), 0, r.width(), r.y()); + QRectF r3(r.x(), r.y() + r.height(), r.width(), size().height() - r.y() - r.height()); + p.fillRect(r1, c); + p.fillRect(r2, c); + p.fillRect(r3, c); + p.setBrush(Qt::NoBrush); + p.save(); + if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::CentralRadialHandle) + p.setPen(dragPen); + d_ptr->paintPoint(&p, d_ptr->m_centralRadial, d_ptr->m_handleSize); + p.restore(); + + const QRectF rect = QRectF(central.x() - d_ptr->m_radiusRadial * size().width(), + central.y() - d_ptr->m_radiusRadial * size().height(), + 2 * d_ptr->m_radiusRadial * size().width(), + 2 * d_ptr->m_radiusRadial * size().height()); + QRegion region(r1.toRect()); + region += r2.toRect(); + region += r3.toRect(); + p.setClipRegion(region); + + p.drawEllipse(rect); + if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::RadiusRadialHandle) { + p.save(); + p.setPen(dragPen); + QRectF rect = QRectF(central.x() - d_ptr->m_radiusRadial / d_ptr->m_radiusFactor * size().width(), + central.y() - d_ptr->m_radiusRadial / d_ptr->m_radiusFactor * size().height(), + 2 * d_ptr->m_radiusRadial / d_ptr->m_radiusFactor * size().width(), + 2 * d_ptr->m_radiusRadial / d_ptr->m_radiusFactor * size().height()); + p.drawEllipse(rect); + + p.restore(); + } + p.restore(); + + p.save(); + if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::FocalRadialHandle) + p.setPen(dragPen); + d_ptr->paintPoint(&p, d_ptr->m_focalRadial, 2 * d_ptr->m_handleSize / 3); + p.restore(); + } else if (d_ptr->m_gradientType == QGradient::ConicalGradient) { + double radius = size().width(); + if (size().height() < radius) + radius = size().height(); + radius /= 2; + double corr = d_ptr->m_handleSize / 3; + radius -= corr; + QPointF central = d_ptr->toViewport(d_ptr->m_centralConical); + + p.save(); + p.setBrush(Qt::NoBrush); + QPen pen2(c); + pen2.setWidthF(2 * d_ptr->m_handleSize / 3); + p.setPen(pen2); + p.drawEllipse(d_ptr->pointRect(central, 2 * radius)); + p.restore(); + + p.save(); + p.setBrush(Qt::NoBrush); + int pointCount = 2; + for (int i = 0; i < pointCount; i++) { + const qreal angle = qDegreesToRadians(i * 180.0 / pointCount + d_ptr->m_angleConical); + const QPointF ray(qCos(angle) * size().width() / 2, + -qSin(angle) * size().height() / 2); + const double mod = hypot(ray.x(), ray.y()); + p.drawLine(QPointF(central.x() + ray.x() * (radius - corr) / mod, + central.y() + ray.y() * (radius - corr) / mod), + QPointF(central.x() + ray.x() * (radius + corr) / mod, + central.y() + ray.y() * (radius + corr) / mod)); + p.drawLine(QPointF(central.x() - ray.x() * (radius - corr) / mod, + central.y() - ray.y() * (radius - corr) / mod), + QPointF(central.x() - ray.x() * (radius + corr) / mod, + central.y() - ray.y() * (radius + corr) / mod)); + } + if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::AngleConicalHandle) { + p.save(); + p.setPen(dragPen); + const qreal angle = qDegreesToRadians(d_ptr->m_angleConical - d_ptr->m_angleOffset); + const QPointF ray(qCos(angle) * size().width() / 2, + -qSin(angle) * size().height() / 2); + const double mod = hypot(ray.x(), ray.y()); + p.drawLine(QPointF(central.x() + ray.x() * (radius - corr) / mod, + central.y() + ray.y() * (radius - corr) / mod), + QPointF(central.x() + ray.x() * (radius + corr) / mod, + central.y() + ray.y() * (radius + corr) / mod)); + p.restore(); + } + + p.restore(); + + p.save(); + if (d_ptr->m_dragHandle == QtGradientWidgetPrivate::CentralConicalHandle) + p.setPen(dragPen); + d_ptr->paintPoint(&p, d_ptr->m_centralConical, d_ptr->m_handleSize); + p.restore(); + + } + + delete gradient; +} + +void QtGradientWidget::setGradientStops(const QGradientStops &stops) +{ + d_ptr->m_gradientStops = stops; + update(); +} + +QGradientStops QtGradientWidget::gradientStops() const +{ + return d_ptr->m_gradientStops; +} + +void QtGradientWidget::setGradientType(QGradient::Type type) +{ + if (type == QGradient::NoGradient) + return; + if (d_ptr->m_gradientType == type) + return; + + d_ptr->m_gradientType = type; + update(); +} + +QGradient::Type QtGradientWidget::gradientType() const +{ + return d_ptr->m_gradientType; +} + +void QtGradientWidget::setGradientSpread(QGradient::Spread spread) +{ + if (d_ptr->m_gradientSpread == spread) + return; + + d_ptr->m_gradientSpread = spread; + update(); +} + +QGradient::Spread QtGradientWidget::gradientSpread() const +{ + return d_ptr->m_gradientSpread; +} + +void QtGradientWidget::setStartLinear(QPointF point) +{ + if (d_ptr->m_startLinear == point) + return; + + d_ptr->m_startLinear = d_ptr->checkRange(point); + update(); +} + +QPointF QtGradientWidget::startLinear() const +{ + return d_ptr->m_startLinear; +} + +void QtGradientWidget::setEndLinear(QPointF point) +{ + if (d_ptr->m_endLinear == point) + return; + + d_ptr->m_endLinear = d_ptr->checkRange(point); + update(); +} + +QPointF QtGradientWidget::endLinear() const +{ + return d_ptr->m_endLinear; +} + +void QtGradientWidget::setCentralRadial(QPointF point) +{ + if (d_ptr->m_centralRadial == point) + return; + + d_ptr->m_centralRadial = point; + update(); +} + +QPointF QtGradientWidget::centralRadial() const +{ + return d_ptr->m_centralRadial; +} + +void QtGradientWidget::setFocalRadial(QPointF point) +{ + if (d_ptr->m_focalRadial == point) + return; + + d_ptr->m_focalRadial = point; + update(); +} + +QPointF QtGradientWidget::focalRadial() const +{ + return d_ptr->m_focalRadial; +} + +void QtGradientWidget::setRadiusRadial(qreal radius) +{ + if (d_ptr->m_radiusRadial == radius) + return; + + d_ptr->m_radiusRadial = radius; + update(); +} + +qreal QtGradientWidget::radiusRadial() const +{ + return d_ptr->m_radiusRadial; +} + +void QtGradientWidget::setCentralConical(QPointF point) +{ + if (d_ptr->m_centralConical == point) + return; + + d_ptr->m_centralConical = point; + update(); +} + +QPointF QtGradientWidget::centralConical() const +{ + return d_ptr->m_centralConical; +} + +void QtGradientWidget::setAngleConical(qreal angle) +{ + if (d_ptr->m_angleConical == angle) + return; + + d_ptr->m_angleConical = angle; + update(); +} + +qreal QtGradientWidget::angleConical() const +{ + return d_ptr->m_angleConical; +} + +QT_END_NAMESPACE diff --git a/src/shared/qtgradienteditor/qtgradientwidget_p.h b/src/shared/qtgradienteditor/qtgradientwidget_p.h new file mode 100644 index 00000000000..2f8308cebcc --- /dev/null +++ b/src/shared/qtgradienteditor/qtgradientwidget_p.h @@ -0,0 +1,94 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTGRADIENTWIDGET_H +#define QTGRADIENTWIDGET_H + +#include + +QT_BEGIN_NAMESPACE + +class QtGradientWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY(bool backgroundCheckered READ isBackgroundCheckered WRITE setBackgroundCheckered) +public: + QtGradientWidget(QWidget *parent = 0); + ~QtGradientWidget(); + + QSize minimumSizeHint() const override; + QSize sizeHint() const override; + int heightForWidth(int w) const override; + + bool isBackgroundCheckered() const; + void setBackgroundCheckered(bool checkered); + + QGradientStops gradientStops() const; + + void setGradientType(QGradient::Type type); + QGradient::Type gradientType() const; + + void setGradientSpread(QGradient::Spread spread); + QGradient::Spread gradientSpread() const; + + void setStartLinear(QPointF point); + QPointF startLinear() const; + + void setEndLinear(QPointF point); + QPointF endLinear() const; + + void setCentralRadial(QPointF point); + QPointF centralRadial() const; + + void setFocalRadial(QPointF point); + QPointF focalRadial() const; + + void setRadiusRadial(qreal radius); + qreal radiusRadial() const; + + void setCentralConical(QPointF point); + QPointF centralConical() const; + + void setAngleConical(qreal angle); + qreal angleConical() const; + +public slots: + void setGradientStops(const QGradientStops &stops); + +signals: + + void startLinearChanged(const QPointF &point); + void endLinearChanged(const QPointF &point); + void centralRadialChanged(const QPointF &point); + void focalRadialChanged(const QPointF &point); + void radiusRadialChanged(qreal radius); + void centralConicalChanged(const QPointF &point); + void angleConicalChanged(qreal angle); + +protected: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtGradientWidget) + Q_DISABLE_COPY_MOVE(QtGradientWidget) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtpropertybrowser/images/cursor-arrow.png b/src/shared/qtpropertybrowser/images/cursor-arrow.png new file mode 100644 index 00000000000..a69ef4eb615 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-arrow.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-busy.png b/src/shared/qtpropertybrowser/images/cursor-busy.png new file mode 100644 index 00000000000..53717e49928 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-busy.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-closedhand.png b/src/shared/qtpropertybrowser/images/cursor-closedhand.png new file mode 100644 index 00000000000..b78dd1dac5a Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-closedhand.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-cross.png b/src/shared/qtpropertybrowser/images/cursor-cross.png new file mode 100644 index 00000000000..fe38e744805 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-cross.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-forbidden.png b/src/shared/qtpropertybrowser/images/cursor-forbidden.png new file mode 100644 index 00000000000..2b08c4e2a3c Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-forbidden.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-hand.png b/src/shared/qtpropertybrowser/images/cursor-hand.png new file mode 100644 index 00000000000..d2004aefa73 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-hand.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-hsplit.png b/src/shared/qtpropertybrowser/images/cursor-hsplit.png new file mode 100644 index 00000000000..a5667e3ffba Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-hsplit.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-ibeam.png b/src/shared/qtpropertybrowser/images/cursor-ibeam.png new file mode 100644 index 00000000000..097fc5fa728 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-ibeam.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-openhand.png b/src/shared/qtpropertybrowser/images/cursor-openhand.png new file mode 100644 index 00000000000..9181c859eda Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-openhand.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-sizeall.png b/src/shared/qtpropertybrowser/images/cursor-sizeall.png new file mode 100644 index 00000000000..69f13eb347a Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-sizeall.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-sizeb.png b/src/shared/qtpropertybrowser/images/cursor-sizeb.png new file mode 100644 index 00000000000..f37d7b91e8c Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-sizeb.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-sizef.png b/src/shared/qtpropertybrowser/images/cursor-sizef.png new file mode 100644 index 00000000000..3b127a05d34 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-sizef.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-sizeh.png b/src/shared/qtpropertybrowser/images/cursor-sizeh.png new file mode 100644 index 00000000000..a9f40cbc3d7 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-sizeh.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-sizev.png b/src/shared/qtpropertybrowser/images/cursor-sizev.png new file mode 100644 index 00000000000..1edbab27a5b Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-sizev.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-uparrow.png b/src/shared/qtpropertybrowser/images/cursor-uparrow.png new file mode 100644 index 00000000000..d3e70ef4c24 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-uparrow.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-vsplit.png b/src/shared/qtpropertybrowser/images/cursor-vsplit.png new file mode 100644 index 00000000000..1beda2570e4 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-vsplit.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-wait.png b/src/shared/qtpropertybrowser/images/cursor-wait.png new file mode 100644 index 00000000000..69056c479e9 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-wait.png differ diff --git a/src/shared/qtpropertybrowser/images/cursor-whatsthis.png b/src/shared/qtpropertybrowser/images/cursor-whatsthis.png new file mode 100644 index 00000000000..b47601c3780 Binary files /dev/null and b/src/shared/qtpropertybrowser/images/cursor-whatsthis.png differ diff --git a/src/shared/qtpropertybrowser/qtbuttonpropertybrowser.cpp b/src/shared/qtpropertybrowser/qtbuttonpropertybrowser.cpp new file mode 100644 index 00000000000..edc2271cd46 --- /dev/null +++ b/src/shared/qtpropertybrowser/qtbuttonpropertybrowser.cpp @@ -0,0 +1,576 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtbuttonpropertybrowser_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtButtonPropertyBrowserPrivate +{ + QtButtonPropertyBrowser *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtButtonPropertyBrowser) +public: + + void init(QWidget *parent); + + void propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex); + void propertyRemoved(QtBrowserItem *index); + void propertyChanged(QtBrowserItem *index); + QWidget *createEditor(QtProperty *property, QWidget *parent) const + { return q_ptr->createEditor(property, parent); } + + void slotEditorDestroyed(); + void slotUpdate(); + void slotToggled(bool checked); + + struct WidgetItem + { + QWidget *widget{nullptr}; // can be null + QLabel *label{nullptr}; // main label with property name + QLabel *widgetLabel{nullptr}; // label substitute showing the current value if there is no widget + QToolButton *button{nullptr}; // expandable button for items with children + QWidget *container{nullptr}; // container which is expanded when the button is clicked + QGridLayout *layout{nullptr}; // layout in container + WidgetItem *parent{nullptr}; + QList children; + bool expanded{false}; + }; +private: + void updateLater(); + void updateItem(WidgetItem *item); + void insertRow(QGridLayout *layout, int row) const; + void removeRow(QGridLayout *layout, int row) const; + int gridRow(WidgetItem *item) const; + int gridSpan(WidgetItem *item) const; + void setExpanded(WidgetItem *item, bool expanded); + QToolButton *createButton(QWidget *panret = 0) const; + + QHash m_indexToItem; + QHash m_itemToIndex; + QHash m_widgetToItem; + QHash m_buttonToItem; + QGridLayout *m_mainLayout = nullptr; + QList m_children; + QList m_recreateQueue; +}; + +QToolButton *QtButtonPropertyBrowserPrivate::createButton(QWidget *parent) const +{ + auto *button = new QToolButton(parent); + button->setCheckable(true); + button->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); + button->setArrowType(Qt::DownArrow); + button->setIconSize(QSize(3, 16)); + /* + QIcon icon; + icon.addPixmap(q_ptr->style()->standardPixmap(QStyle::SP_ArrowDown), QIcon::Normal, QIcon::Off); + icon.addPixmap(q_ptr->style()->standardPixmap(QStyle::SP_ArrowUp), QIcon::Normal, QIcon::On); + button->setIcon(icon); + */ + return button; +} + +int QtButtonPropertyBrowserPrivate::gridRow(WidgetItem *item) const +{ + QList siblings; + if (item->parent) + siblings = item->parent->children; + else + siblings = m_children; + + int row = 0; + for (WidgetItem *sibling : std::as_const(siblings)) { + if (sibling == item) + return row; + row += gridSpan(sibling); + } + return -1; +} + +int QtButtonPropertyBrowserPrivate::gridSpan(WidgetItem *item) const +{ + if (item->container && item->expanded) + return 2; + return 1; +} + +void QtButtonPropertyBrowserPrivate::init(QWidget *parent) +{ + m_mainLayout = new QGridLayout(); + parent->setLayout(m_mainLayout); + auto *item = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); + m_mainLayout->addItem(item, 0, 0); +} + +void QtButtonPropertyBrowserPrivate::slotEditorDestroyed() +{ + auto *editor = qobject_cast(q_ptr->sender()); + if (!editor) + return; + if (!m_widgetToItem.contains(editor)) + return; + m_widgetToItem[editor]->widget = nullptr; + m_widgetToItem.remove(editor); +} + +void QtButtonPropertyBrowserPrivate::slotUpdate() +{ + for (WidgetItem *item : std::as_const(m_recreateQueue)) { + WidgetItem *parent = item->parent; + QWidget *w = nullptr; + QGridLayout *l = nullptr; + const int oldRow = gridRow(item); + if (parent) { + w = parent->container; + l = parent->layout; + } else { + w = q_ptr; + l = m_mainLayout; + } + + int span = 1; + if (!item->widget && !item->widgetLabel) + span = 2; + item->label = new QLabel(w); + item->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + l->addWidget(item->label, oldRow, 0, 1, span); + + updateItem(item); + } + m_recreateQueue.clear(); +} + +void QtButtonPropertyBrowserPrivate::setExpanded(WidgetItem *item, bool expanded) +{ + if (item->expanded == expanded) + return; + + if (!item->container) + return; + + item->expanded = expanded; + const int row = gridRow(item); + WidgetItem *parent = item->parent; + QGridLayout *l = nullptr; + if (parent) + l = parent->layout; + else + l = m_mainLayout; + + if (expanded) { + insertRow(l, row + 1); + l->addWidget(item->container, row + 1, 0, 1, 2); + item->container->show(); + } else { + l->removeWidget(item->container); + item->container->hide(); + removeRow(l, row + 1); + } + + item->button->setChecked(expanded); + item->button->setArrowType(expanded ? Qt::UpArrow : Qt::DownArrow); +} + +void QtButtonPropertyBrowserPrivate::slotToggled(bool checked) +{ + WidgetItem *item = m_buttonToItem.value(q_ptr->sender()); + if (!item) + return; + + setExpanded(item, checked); + + if (checked) + emit q_ptr->expanded(m_itemToIndex.value(item)); + else + emit q_ptr->collapsed(m_itemToIndex.value(item)); +} + +void QtButtonPropertyBrowserPrivate::updateLater() +{ + QMetaObject::invokeMethod(q_ptr, [this] { slotUpdate(); }, Qt::QueuedConnection); +} + +void QtButtonPropertyBrowserPrivate::propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex) +{ + WidgetItem *afterItem = m_indexToItem.value(afterIndex); + WidgetItem *parentItem = m_indexToItem.value(index->parent()); + + auto *newItem = new WidgetItem(); + newItem->parent = parentItem; + + QGridLayout *layout = nullptr; + QWidget *parentWidget = nullptr; + int row = -1; + if (!afterItem) { + row = 0; + if (parentItem) + parentItem->children.insert(0, newItem); + else + m_children.insert(0, newItem); + } else { + row = gridRow(afterItem) + gridSpan(afterItem); + if (parentItem) + parentItem->children.insert(parentItem->children.indexOf(afterItem) + 1, newItem); + else + m_children.insert(m_children.indexOf(afterItem) + 1, newItem); + } + + if (!parentItem) { + layout = m_mainLayout; + parentWidget = q_ptr; + } else { + if (!parentItem->container) { + m_recreateQueue.removeAll(parentItem); + WidgetItem *grandParent = parentItem->parent; + QGridLayout *l = nullptr; + const int oldRow = gridRow(parentItem); + if (grandParent) { + l = grandParent->layout; + } else { + l = m_mainLayout; + } + auto *container = new QFrame(); + container->setFrameShape(QFrame::Panel); + container->setFrameShadow(QFrame::Raised); + parentItem->container = container; + parentItem->button = createButton(); + m_buttonToItem[parentItem->button] = parentItem; + q_ptr->connect(parentItem->button, &QAbstractButton::toggled, + q_ptr, [this](bool checked) { slotToggled(checked); }); + parentItem->layout = new QGridLayout(); + container->setLayout(parentItem->layout); + if (parentItem->label) { + l->removeWidget(parentItem->label); + delete parentItem->label; + parentItem->label = nullptr; + } + int span = 1; + if (!parentItem->widget && !parentItem->widgetLabel) + span = 2; + l->addWidget(parentItem->button, oldRow, 0, 1, span); + updateItem(parentItem); + } + layout = parentItem->layout; + parentWidget = parentItem->container; + } + + newItem->label = new QLabel(parentWidget); + newItem->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + newItem->widget = createEditor(index->property(), parentWidget); + if (newItem->widget) { + QObject::connect(newItem->widget, &QWidget::destroyed, + q_ptr, [this] { slotEditorDestroyed(); }); + m_widgetToItem[newItem->widget] = newItem; + } else if (index->property()->hasValue()) { + newItem->widgetLabel = new QLabel(parentWidget); + newItem->widgetLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed)); + } + + insertRow(layout, row); + int span = 1; + if (newItem->widget) + layout->addWidget(newItem->widget, row, 1); + else if (newItem->widgetLabel) + layout->addWidget(newItem->widgetLabel, row, 1); + else + span = 2; + layout->addWidget(newItem->label, row, 0, span, 1); + + m_itemToIndex[newItem] = index; + m_indexToItem[index] = newItem; + + updateItem(newItem); +} + +void QtButtonPropertyBrowserPrivate::propertyRemoved(QtBrowserItem *index) +{ + WidgetItem *item = m_indexToItem.value(index); + + m_indexToItem.remove(index); + m_itemToIndex.remove(item); + + WidgetItem *parentItem = item->parent; + + const int row = gridRow(item); + + if (parentItem) + parentItem->children.removeAt(parentItem->children.indexOf(item)); + else + m_children.removeAt(m_children.indexOf(item)); + + const int colSpan = gridSpan(item); + + m_buttonToItem.remove(item->button); + + if (item->widget) + delete item->widget; + if (item->label) + delete item->label; + if (item->widgetLabel) + delete item->widgetLabel; + if (item->button) + delete item->button; + if (item->container) + delete item->container; + + if (!parentItem) { + removeRow(m_mainLayout, row); + if (colSpan > 1) + removeRow(m_mainLayout, row); + } else if (parentItem->children.size() != 0) { + removeRow(parentItem->layout, row); + if (colSpan > 1) + removeRow(parentItem->layout, row); + } else { + const WidgetItem *grandParent = parentItem->parent; + QGridLayout *l = nullptr; + if (grandParent) { + l = grandParent->layout; + } else { + l = m_mainLayout; + } + + const int parentRow = gridRow(parentItem); + const int parentSpan = gridSpan(parentItem); + + l->removeWidget(parentItem->button); + l->removeWidget(parentItem->container); + delete parentItem->button; + delete parentItem->container; + parentItem->button = nullptr; + parentItem->container = nullptr; + parentItem->layout = nullptr; + if (!m_recreateQueue.contains(parentItem)) + m_recreateQueue.append(parentItem); + if (parentSpan > 1) + removeRow(l, parentRow + 1); + + updateLater(); + } + m_recreateQueue.removeAll(item); + + delete item; +} + +void QtButtonPropertyBrowserPrivate::insertRow(QGridLayout *layout, int row) const +{ + QHash itemToPos; + int idx = 0; + while (idx < layout->count()) { + int r, c, rs, cs; + layout->getItemPosition(idx, &r, &c, &rs, &cs); + if (r >= row) { + itemToPos[layout->takeAt(idx)] = QRect(r + 1, c, rs, cs); + } else { + idx++; + } + } + + for (auto it = itemToPos.constBegin(), icend = itemToPos.constEnd(); it != icend; ++it) { + const QRect r = it.value(); + layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height()); + } +} + +void QtButtonPropertyBrowserPrivate::removeRow(QGridLayout *layout, int row) const +{ + QHash itemToPos; + int idx = 0; + while (idx < layout->count()) { + int r, c, rs, cs; + layout->getItemPosition(idx, &r, &c, &rs, &cs); + if (r > row) { + itemToPos[layout->takeAt(idx)] = QRect(r - 1, c, rs, cs); + } else { + idx++; + } + } + + for (auto it = itemToPos.constBegin(), icend = itemToPos.constEnd(); it != icend; ++it) { + const QRect r = it.value(); + layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height()); + } +} + +void QtButtonPropertyBrowserPrivate::propertyChanged(QtBrowserItem *index) +{ + WidgetItem *item = m_indexToItem.value(index); + + updateItem(item); +} + +void QtButtonPropertyBrowserPrivate::updateItem(WidgetItem *item) +{ + QtProperty *property = m_itemToIndex[item]->property(); + if (item->button) { + QFont font = item->button->font(); + font.setUnderline(property->isModified()); + item->button->setFont(font); + item->button->setText(property->propertyName()); + item->button->setToolTip(property->descriptionToolTip()); + item->button->setStatusTip(property->statusTip()); + item->button->setWhatsThis(property->whatsThis()); + item->button->setEnabled(property->isEnabled()); + } + if (item->label) { + QFont font = item->label->font(); + font.setUnderline(property->isModified()); + item->label->setFont(font); + item->label->setText(property->propertyName()); + item->label->setToolTip(property->descriptionToolTip()); + item->label->setStatusTip(property->statusTip()); + item->label->setWhatsThis(property->whatsThis()); + item->label->setEnabled(property->isEnabled()); + } + if (item->widgetLabel) { + QFont font = item->widgetLabel->font(); + font.setUnderline(false); + item->widgetLabel->setFont(font); + item->widgetLabel->setText(property->valueText()); + item->widgetLabel->setToolTip(property->valueText()); + item->widgetLabel->setEnabled(property->isEnabled()); + } + if (item->widget) { + QFont font = item->widget->font(); + font.setUnderline(false); + item->widget->setFont(font); + item->widget->setEnabled(property->isEnabled()); + const QString valueToolTip = property->valueToolTip(); + item->widget->setToolTip(valueToolTip.isEmpty() ? property->valueText() : valueToolTip); + } +} + + + +/*! + \class QtButtonPropertyBrowser + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtButtonPropertyBrowser class provides a drop down QToolButton + based property browser. + + A property browser is a widget that enables the user to edit a + given set of properties. Each property is represented by a label + specifying the property's name, and an editing widget (e.g. a line + edit or a combobox) holding its value. A property can have zero or + more subproperties. + + QtButtonPropertyBrowser provides drop down button for all nested + properties, i.e. subproperties are enclosed by a container associated with + the drop down button. The parent property's name is displayed as button text. For example: + + \image qtbuttonpropertybrowser.png + + Use the QtAbstractPropertyBrowser API to add, insert and remove + properties from an instance of the QtButtonPropertyBrowser + class. The properties themselves are created and managed by + implementations of the QtAbstractPropertyManager class. + + \sa QtTreePropertyBrowser, QtAbstractPropertyBrowser +*/ + +/*! + \fn void QtButtonPropertyBrowser::collapsed(QtBrowserItem *item) + + This signal is emitted when the \a item is collapsed. + + \sa expanded(), setExpanded() +*/ + +/*! + \fn void QtButtonPropertyBrowser::expanded(QtBrowserItem *item) + + This signal is emitted when the \a item is expanded. + + \sa collapsed(), setExpanded() +*/ + +/*! + Creates a property browser with the given \a parent. +*/ +QtButtonPropertyBrowser::QtButtonPropertyBrowser(QWidget *parent) + : QtAbstractPropertyBrowser(parent), d_ptr(new QtButtonPropertyBrowserPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->init(this); +} + +/*! + Destroys this property browser. + + Note that the properties that were inserted into this browser are + \e not destroyed since they may still be used in other + browsers. The properties are owned by the manager that created + them. + + \sa QtProperty, QtAbstractPropertyManager +*/ +QtButtonPropertyBrowser::~QtButtonPropertyBrowser() +{ + for (auto it = d_ptr->m_itemToIndex.cbegin(), icend = d_ptr->m_itemToIndex.cend(); it != icend; ++it) + delete it.key(); +} + +/*! + \reimp +*/ +void QtButtonPropertyBrowser::itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem) +{ + d_ptr->propertyInserted(item, afterItem); +} + +/*! + \reimp +*/ +void QtButtonPropertyBrowser::itemRemoved(QtBrowserItem *item) +{ + d_ptr->propertyRemoved(item); +} + +/*! + \reimp +*/ +void QtButtonPropertyBrowser::itemChanged(QtBrowserItem *item) +{ + d_ptr->propertyChanged(item); +} + +/*! + Sets the \a item to either collapse or expanded, depending on the value of \a expanded. + + \sa isExpanded(), expanded(), collapsed() +*/ + +void QtButtonPropertyBrowser::setExpanded(QtBrowserItem *item, bool expanded) +{ + QtButtonPropertyBrowserPrivate::WidgetItem *itm = d_ptr->m_indexToItem.value(item); + if (itm) + d_ptr->setExpanded(itm, expanded); +} + +/*! + Returns true if the \a item is expanded; otherwise returns false. + + \sa setExpanded() +*/ + +bool QtButtonPropertyBrowser::isExpanded(QtBrowserItem *item) const +{ + QtButtonPropertyBrowserPrivate::WidgetItem *itm = d_ptr->m_indexToItem.value(item); + if (itm) + return itm->expanded; + return false; +} + +QT_END_NAMESPACE + +#include "moc_qtbuttonpropertybrowser_p.cpp" diff --git a/src/shared/qtpropertybrowser/qtbuttonpropertybrowser_p.h b/src/shared/qtpropertybrowser/qtbuttonpropertybrowser_p.h new file mode 100644 index 00000000000..b8ed1ef6699 --- /dev/null +++ b/src/shared/qtpropertybrowser/qtbuttonpropertybrowser_p.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTBUTTONPROPERTYBROWSER_H +#define QTBUTTONPROPERTYBROWSER_H + +#include "qtpropertybrowser_p.h" + +QT_BEGIN_NAMESPACE + +class QtButtonPropertyBrowserPrivate; + +class QtButtonPropertyBrowser : public QtAbstractPropertyBrowser +{ + Q_OBJECT +public: + QtButtonPropertyBrowser(QWidget *parent = 0); + ~QtButtonPropertyBrowser(); + + void setExpanded(QtBrowserItem *item, bool expanded); + bool isExpanded(QtBrowserItem *item) const; + +Q_SIGNALS: + void collapsed(QtBrowserItem *item); + void expanded(QtBrowserItem *item); + +protected: + void itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem) override; + void itemRemoved(QtBrowserItem *item) override; + void itemChanged(QtBrowserItem *item) override; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtButtonPropertyBrowser) + Q_DISABLE_COPY_MOVE(QtButtonPropertyBrowser) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtpropertybrowser/qteditorfactory.cpp b/src/shared/qtpropertybrowser/qteditorfactory.cpp new file mode 100644 index 00000000000..b3428baccde --- /dev/null +++ b/src/shared/qtpropertybrowser/qteditorfactory.cpp @@ -0,0 +1,2492 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qteditorfactory_p.h" +#include "qtpropertybrowserutils_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(Q_CC_MSVC) +# pragma warning(disable: 4786) /* MS VS 6: truncating debug info after 255 characters */ +#endif + +QT_BEGIN_NAMESPACE + +// Set a hard coded left margin to account for the indentation +// of the tree view icon when switching to an editor + +static inline void setupTreeViewEditorMargin(QLayout *lt) +{ + enum { DecorationMargin = 4 }; + if (QApplication::layoutDirection() == Qt::LeftToRight) + lt->setContentsMargins(DecorationMargin, 0, 0, 0); + else + lt->setContentsMargins(0, 0, DecorationMargin, 0); +} + +// ---------- EditorFactoryPrivate : +// Base class for editor factory private classes. Manages mapping of properties to editors and vice versa. + +template +class EditorFactoryPrivate +{ +public: + + using EditorList = QList; + using PropertyToEditorListMap = QHash; + using EditorToPropertyMap = QHash; + + Editor *createEditor(QtProperty *property, QWidget *parent); + void initializeEditor(QtProperty *property, Editor *e); + void slotEditorDestroyed(QObject *object); + + PropertyToEditorListMap m_createdEditors; + EditorToPropertyMap m_editorToProperty; +}; + +template +Editor *EditorFactoryPrivate::createEditor(QtProperty *property, QWidget *parent) +{ + auto *editor = new Editor(parent); + initializeEditor(property, editor); + return editor; +} + +template +void EditorFactoryPrivate::initializeEditor(QtProperty *property, Editor *editor) +{ + auto it = m_createdEditors.find(property); + if (it == m_createdEditors.end()) + it = m_createdEditors.insert(property, EditorList()); + it.value().append(editor); + m_editorToProperty.insert(editor, property); +} + +template +void EditorFactoryPrivate::slotEditorDestroyed(QObject *object) +{ + const auto ecend = m_editorToProperty.end(); + for (auto itEditor = m_editorToProperty.begin(); itEditor != ecend; ++itEditor) { + if (itEditor.key() == object) { + Editor *editor = itEditor.key(); + QtProperty *property = itEditor.value(); + const auto pit = m_createdEditors.find(property); + if (pit != m_createdEditors.end()) { + pit.value().removeAll(editor); + if (pit.value().isEmpty()) + m_createdEditors.erase(pit); + } + m_editorToProperty.erase(itEditor); + return; + } + } +} + +// ------------ QtSpinBoxFactory + +class QtSpinBoxFactoryPrivate : public EditorFactoryPrivate +{ + QtSpinBoxFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtSpinBoxFactory) +public: + + void slotPropertyChanged(QtProperty *property, int value); + void slotRangeChanged(QtProperty *property, int min, int max); + void slotSingleStepChanged(QtProperty *property, int step); + void slotSetValue(int value); +}; + +void QtSpinBoxFactoryPrivate::slotPropertyChanged(QtProperty *property, int value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + for (QSpinBox *editor : it.value()) { + if (editor->value() != value) { + editor->blockSignals(true); + editor->setValue(value); + editor->blockSignals(false); + } + } +} + +void QtSpinBoxFactoryPrivate::slotRangeChanged(QtProperty *property, int min, int max) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + + QtIntPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + for (QSpinBox *editor : it.value()) { + editor->blockSignals(true); + editor->setRange(min, max); + editor->setValue(manager->value(property)); + editor->blockSignals(false); + } +} + +void QtSpinBoxFactoryPrivate::slotSingleStepChanged(QtProperty *property, int step) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + for (QSpinBox *editor : it.value()) { + editor->blockSignals(true); + editor->setSingleStep(step); + editor->blockSignals(false); + } +} + +void QtSpinBoxFactoryPrivate::slotSetValue(int value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) { + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtIntPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } + } +} + +/*! + \class QtSpinBoxFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtSpinBoxFactory class provides QSpinBox widgets for + properties created by QtIntPropertyManager objects. + + \sa QtAbstractEditorFactory, QtIntPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtSpinBoxFactory::QtSpinBoxFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtSpinBoxFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtSpinBoxFactory::~QtSpinBoxFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtSpinBoxFactory::connectPropertyManager(QtIntPropertyManager *manager) +{ + connect(manager, &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotPropertyChanged(property, value); }); + connect(manager, &QtIntPropertyManager::rangeChanged, + this, [this](QtProperty *property, int min, int max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(manager, &QtIntPropertyManager::singleStepChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotSingleStepChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtSpinBoxFactory::createEditor(QtIntPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + QSpinBox *editor = d_ptr->createEditor(property, parent); + editor->setSingleStep(manager->singleStep(property)); + editor->setRange(manager->minimum(property), manager->maximum(property)); + editor->setValue(manager->value(property)); + editor->setKeyboardTracking(false); + + connect(editor, &QSpinBox::valueChanged, + this, [this](int value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtSpinBoxFactory::disconnectPropertyManager(QtIntPropertyManager *manager) +{ + disconnect(manager, &QtIntPropertyManager::valueChanged, this, nullptr); + disconnect(manager, &QtIntPropertyManager::rangeChanged, this, nullptr); + disconnect(manager, &QtIntPropertyManager::singleStepChanged, this, nullptr); +} + +// QtSliderFactory + +class QtSliderFactoryPrivate : public EditorFactoryPrivate +{ + QtSliderFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtSliderFactory) +public: + void slotPropertyChanged(QtProperty *property, int value); + void slotRangeChanged(QtProperty *property, int min, int max); + void slotSingleStepChanged(QtProperty *property, int step); + void slotSetValue(int value); +}; + +void QtSliderFactoryPrivate::slotPropertyChanged(QtProperty *property, int value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + for (QSlider *editor : it.value()) { + editor->blockSignals(true); + editor->setValue(value); + editor->blockSignals(false); + } +} + +void QtSliderFactoryPrivate::slotRangeChanged(QtProperty *property, int min, int max) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + + QtIntPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + for (QSlider *editor : it.value()) { + editor->blockSignals(true); + editor->setRange(min, max); + editor->setValue(manager->value(property)); + editor->blockSignals(false); + } +} + +void QtSliderFactoryPrivate::slotSingleStepChanged(QtProperty *property, int step) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + for (QSlider *editor : it.value()) { + editor->blockSignals(true); + editor->setSingleStep(step); + editor->blockSignals(false); + } +} + +void QtSliderFactoryPrivate::slotSetValue(int value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor ) { + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtIntPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } + } +} + +/*! + \class QtSliderFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtSliderFactory class provides QSlider widgets for + properties created by QtIntPropertyManager objects. + + \sa QtAbstractEditorFactory, QtIntPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtSliderFactory::QtSliderFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtSliderFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtSliderFactory::~QtSliderFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtSliderFactory::connectPropertyManager(QtIntPropertyManager *manager) +{ + connect(manager, &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotPropertyChanged(property, value); }); + connect(manager, &QtIntPropertyManager::rangeChanged, + this, [this](QtProperty *property, int min, int max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(manager, &QtIntPropertyManager::singleStepChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotSingleStepChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtSliderFactory::createEditor(QtIntPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + auto *editor = new QSlider(Qt::Horizontal, parent); + d_ptr->initializeEditor(property, editor); + editor->setSingleStep(manager->singleStep(property)); + editor->setRange(manager->minimum(property), manager->maximum(property)); + editor->setValue(manager->value(property)); + + connect(editor, &QSlider::valueChanged, + this, [this](int value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtSliderFactory::disconnectPropertyManager(QtIntPropertyManager *manager) +{ + disconnect(manager, &QtIntPropertyManager::valueChanged, this, nullptr); + disconnect(manager, &QtIntPropertyManager::rangeChanged, this, nullptr); + disconnect(manager, &QtIntPropertyManager::singleStepChanged, this, nullptr); +} + +// QtSliderFactory + +class QtScrollBarFactoryPrivate : public EditorFactoryPrivate +{ + QtScrollBarFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtScrollBarFactory) +public: + void slotPropertyChanged(QtProperty *property, int value); + void slotRangeChanged(QtProperty *property, int min, int max); + void slotSingleStepChanged(QtProperty *property, int step); + void slotSetValue(int value); +}; + +void QtScrollBarFactoryPrivate::slotPropertyChanged(QtProperty *property, int value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + + for (QScrollBar *editor : it.value()) { + editor->blockSignals(true); + editor->setValue(value); + editor->blockSignals(false); + } +} + +void QtScrollBarFactoryPrivate::slotRangeChanged(QtProperty *property, int min, int max) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + + QtIntPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + for (QScrollBar *editor : it.value()) { + editor->blockSignals(true); + editor->setRange(min, max); + editor->setValue(manager->value(property)); + editor->blockSignals(false); + } +} + +void QtScrollBarFactoryPrivate::slotSingleStepChanged(QtProperty *property, int step) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + for (QScrollBar *editor : it.value()) { + editor->blockSignals(true); + editor->setSingleStep(step); + editor->blockSignals(false); + } +} + +void QtScrollBarFactoryPrivate::slotSetValue(int value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtIntPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtScrollBarFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtScrollBarFactory class provides QScrollBar widgets for + properties created by QtIntPropertyManager objects. + + \sa QtAbstractEditorFactory, QtIntPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtScrollBarFactory::QtScrollBarFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtScrollBarFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtScrollBarFactory::~QtScrollBarFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtScrollBarFactory::connectPropertyManager(QtIntPropertyManager *manager) +{ + connect(manager, &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotPropertyChanged(property, value); }); + connect(manager, &QtIntPropertyManager::rangeChanged, + this, [this](QtProperty *property, int min, int max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(manager, &QtIntPropertyManager::singleStepChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotSingleStepChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtScrollBarFactory::createEditor(QtIntPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + auto *editor = new QScrollBar(Qt::Horizontal, parent); + d_ptr->initializeEditor(property, editor); + editor->setSingleStep(manager->singleStep(property)); + editor->setRange(manager->minimum(property), manager->maximum(property)); + editor->setValue(manager->value(property)); + connect(editor, &QScrollBar::valueChanged, + this, [this](int value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtScrollBarFactory::disconnectPropertyManager(QtIntPropertyManager *manager) +{ + disconnect(manager, &QtIntPropertyManager::valueChanged, this, nullptr); + disconnect(manager, &QtIntPropertyManager::rangeChanged, this, nullptr); + disconnect(manager, &QtIntPropertyManager::singleStepChanged, this, nullptr); +} + +// QtCheckBoxFactory + +class QtCheckBoxFactoryPrivate : public EditorFactoryPrivate +{ + QtCheckBoxFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtCheckBoxFactory) +public: + void slotPropertyChanged(QtProperty *property, bool value); + void slotSetValue(bool value); +}; + +void QtCheckBoxFactoryPrivate::slotPropertyChanged(QtProperty *property, bool value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + + for (QtBoolEdit *editor : it.value()) { + editor->blockCheckBoxSignals(true); + editor->setChecked(value); + editor->blockCheckBoxSignals(false); + } +} + +void QtCheckBoxFactoryPrivate::slotSetValue(bool value) +{ + QObject *object = q_ptr->sender(); + + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtBoolPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtCheckBoxFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtCheckBoxFactory class provides QCheckBox widgets for + properties created by QtBoolPropertyManager objects. + + \sa QtAbstractEditorFactory, QtBoolPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtCheckBoxFactory::QtCheckBoxFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtCheckBoxFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtCheckBoxFactory::~QtCheckBoxFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtCheckBoxFactory::connectPropertyManager(QtBoolPropertyManager *manager) +{ + connect(manager, &QtBoolPropertyManager::valueChanged, + this, [this](QtProperty *property, bool value) + { d_ptr->slotPropertyChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtCheckBoxFactory::createEditor(QtBoolPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + QtBoolEdit *editor = d_ptr->createEditor(property, parent); + editor->setChecked(manager->value(property)); + + connect(editor, &QtBoolEdit::toggled, + this, [this](bool value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtCheckBoxFactory::disconnectPropertyManager(QtBoolPropertyManager *manager) +{ + disconnect(manager, &QtBoolPropertyManager::valueChanged, this, nullptr); +} + +// QtDoubleSpinBoxFactory + +class QtDoubleSpinBoxFactoryPrivate : public EditorFactoryPrivate +{ + QtDoubleSpinBoxFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtDoubleSpinBoxFactory) +public: + + void slotPropertyChanged(QtProperty *property, double value); + void slotRangeChanged(QtProperty *property, double min, double max); + void slotSingleStepChanged(QtProperty *property, double step); + void slotDecimalsChanged(QtProperty *property, int prec); + void slotSetValue(double value); +}; + +void QtDoubleSpinBoxFactoryPrivate::slotPropertyChanged(QtProperty *property, double value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + for (QDoubleSpinBox *editor : it.value()) { + if (editor->value() != value) { + editor->blockSignals(true); + editor->setValue(value); + editor->blockSignals(false); + } + } +} + +void QtDoubleSpinBoxFactoryPrivate::slotRangeChanged(QtProperty *property, + double min, double max) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + + QtDoublePropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + for (QDoubleSpinBox *editor : it.value()) { + editor->blockSignals(true); + editor->setRange(min, max); + editor->setValue(manager->value(property)); + editor->blockSignals(false); + } +} + +void QtDoubleSpinBoxFactoryPrivate::slotSingleStepChanged(QtProperty *property, double step) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.cend()) + return; + + QtDoublePropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + for (QDoubleSpinBox *editor : it.value()) { + editor->blockSignals(true); + editor->setSingleStep(step); + editor->blockSignals(false); + } +} + +void QtDoubleSpinBoxFactoryPrivate::slotDecimalsChanged(QtProperty *property, int prec) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + QtDoublePropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + for (QDoubleSpinBox *editor : it.value()) { + editor->blockSignals(true); + editor->setDecimals(prec); + editor->setValue(manager->value(property)); + editor->blockSignals(false); + } +} + +void QtDoubleSpinBoxFactoryPrivate::slotSetValue(double value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), itcend = m_editorToProperty.cend(); itEditor != itcend; ++itEditor) { + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtDoublePropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } + } +} + +/*! \class QtDoubleSpinBoxFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtDoubleSpinBoxFactory class provides QDoubleSpinBox + widgets for properties created by QtDoublePropertyManager objects. + + \sa QtAbstractEditorFactory, QtDoublePropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtDoubleSpinBoxFactory::QtDoubleSpinBoxFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtDoubleSpinBoxFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtDoubleSpinBoxFactory::~QtDoubleSpinBoxFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtDoubleSpinBoxFactory::connectPropertyManager(QtDoublePropertyManager *manager) +{ + connect(manager, &QtDoublePropertyManager::valueChanged, + this, [this](QtProperty *property, double value) + { d_ptr->slotPropertyChanged(property, value); }); + connect(manager, &QtDoublePropertyManager::rangeChanged, + this, [this](QtProperty *property, double min, double max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(manager, &QtDoublePropertyManager::singleStepChanged, + this, [this](QtProperty *property, double value) + { d_ptr->slotSingleStepChanged(property, value); }); + connect(manager, &QtDoublePropertyManager::decimalsChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotDecimalsChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtDoubleSpinBoxFactory::createEditor(QtDoublePropertyManager *manager, + QtProperty *property, QWidget *parent) +{ + QDoubleSpinBox *editor = d_ptr->createEditor(property, parent); + editor->setSingleStep(manager->singleStep(property)); + editor->setDecimals(manager->decimals(property)); + editor->setRange(manager->minimum(property), manager->maximum(property)); + editor->setValue(manager->value(property)); + editor->setKeyboardTracking(false); + + connect(editor, &QDoubleSpinBox::valueChanged, + this, [this](double value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtDoubleSpinBoxFactory::disconnectPropertyManager(QtDoublePropertyManager *manager) +{ + disconnect(manager, &QtDoublePropertyManager::valueChanged, this, nullptr); + disconnect(manager, &QtDoublePropertyManager::rangeChanged, this, nullptr); + disconnect(manager, &QtDoublePropertyManager::singleStepChanged, this, nullptr); + disconnect(manager, &QtDoublePropertyManager::decimalsChanged, this, nullptr); +} + +// QtLineEditFactory + +class QtLineEditFactoryPrivate : public EditorFactoryPrivate +{ + QtLineEditFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtLineEditFactory) +public: + + void slotPropertyChanged(QtProperty *property, const QString &value); + void slotRegExpChanged(QtProperty *property, const QRegularExpression ®Exp); + void slotSetValue(const QString &value); +}; + +void QtLineEditFactoryPrivate::slotPropertyChanged(QtProperty *property, + const QString &value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + for (QLineEdit *editor : it.value()) { + if (editor->text() != value) + editor->setText(value); + } +} + +void QtLineEditFactoryPrivate::slotRegExpChanged(QtProperty *property, + const QRegularExpression ®Exp) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + QtStringPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + for (QLineEdit *editor : it.value()) { + editor->blockSignals(true); + const QValidator *oldValidator = editor->validator(); + QValidator *newValidator = nullptr; + if (regExp.isValid()) { + newValidator = new QRegularExpressionValidator(regExp, editor); + } + editor->setValidator(newValidator); + if (oldValidator) + delete oldValidator; + editor->blockSignals(false); + } +} + +void QtLineEditFactoryPrivate::slotSetValue(const QString &value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtStringPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtLineEditFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtLineEditFactory class provides QLineEdit widgets for + properties created by QtStringPropertyManager objects. + + \sa QtAbstractEditorFactory, QtStringPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtLineEditFactory::QtLineEditFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtLineEditFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtLineEditFactory::~QtLineEditFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtLineEditFactory::connectPropertyManager(QtStringPropertyManager *manager) +{ + connect(manager, &QtStringPropertyManager::valueChanged, + this, [this](QtProperty *property, const QString &value) + { d_ptr->slotPropertyChanged(property, value); }); + connect(manager, &QtStringPropertyManager::regExpChanged, + this, [this](QtProperty *property, const QRegularExpression &value) + { d_ptr->slotRegExpChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtLineEditFactory::createEditor(QtStringPropertyManager *manager, + QtProperty *property, QWidget *parent) +{ + QLineEdit *editor = d_ptr->createEditor(property, parent); + QRegularExpression regExp = manager->regExp(property); + if (regExp.isValid() && !regExp.pattern().isEmpty()) { + auto *validator = new QRegularExpressionValidator(regExp, editor); + editor->setValidator(validator); + } + editor->setText(manager->value(property)); + + connect(editor, &QLineEdit::textEdited, + this, [this](const QString &value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtLineEditFactory::disconnectPropertyManager(QtStringPropertyManager *manager) +{ + disconnect(manager, &QtStringPropertyManager::valueChanged, this, nullptr); + disconnect(manager, &QtStringPropertyManager::regExpChanged, this, nullptr); +} + +// QtDateEditFactory + +class QtDateEditFactoryPrivate : public EditorFactoryPrivate +{ + QtDateEditFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtDateEditFactory) +public: + + void slotPropertyChanged(QtProperty *property, QDate value); + void slotRangeChanged(QtProperty *property, QDate min, QDate max); + void slotSetValue(QDate value); +}; + +void QtDateEditFactoryPrivate::slotPropertyChanged(QtProperty *property, QDate value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + for (QDateEdit *editor : it.value()) { + editor->blockSignals(true); + editor->setDate(value); + editor->blockSignals(false); + } +} + +void QtDateEditFactoryPrivate::slotRangeChanged(QtProperty *property, QDate min, QDate max) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + QtDatePropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + for (QDateEdit *editor : it.value()) { + editor->blockSignals(true); + editor->setDateRange(min, max); + editor->setDate(manager->value(property)); + editor->blockSignals(false); + } +} + +void QtDateEditFactoryPrivate::slotSetValue(QDate value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtDatePropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtDateEditFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtDateEditFactory class provides QDateEdit widgets for + properties created by QtDatePropertyManager objects. + + \sa QtAbstractEditorFactory, QtDatePropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtDateEditFactory::QtDateEditFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtDateEditFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtDateEditFactory::~QtDateEditFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtDateEditFactory::connectPropertyManager(QtDatePropertyManager *manager) +{ + connect(manager, &QtDatePropertyManager::valueChanged, + this, [this](QtProperty *property, QDate value) + { d_ptr->slotPropertyChanged(property, value); }); + connect(manager, &QtDatePropertyManager::rangeChanged, + this, [this](QtProperty *property, QDate min, QDate max) + { d_ptr->slotRangeChanged(property, min, max); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtDateEditFactory::createEditor(QtDatePropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + QDateEdit *editor = d_ptr->createEditor(property, parent); + editor->setDisplayFormat(QtPropertyBrowserUtils::dateFormat()); + editor->setCalendarPopup(true); + editor->setDateRange(manager->minimum(property), manager->maximum(property)); + editor->setDate(manager->value(property)); + + connect(editor, &QDateEdit::dateChanged, + this, [this](QDate value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtDateEditFactory::disconnectPropertyManager(QtDatePropertyManager *manager) +{ + disconnect(manager, &QtDatePropertyManager::valueChanged, this, nullptr); + disconnect(manager, &QtDatePropertyManager::rangeChanged, this, nullptr); +} + +// QtTimeEditFactory + +class QtTimeEditFactoryPrivate : public EditorFactoryPrivate +{ + QtTimeEditFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtTimeEditFactory) +public: + + void slotPropertyChanged(QtProperty *property, QTime value); + void slotSetValue(QTime value); +}; + +void QtTimeEditFactoryPrivate::slotPropertyChanged(QtProperty *property, QTime value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + for (QTimeEdit *editor : it.value()) { + editor->blockSignals(true); + editor->setTime(value); + editor->blockSignals(false); + } +} + +void QtTimeEditFactoryPrivate::slotSetValue(QTime value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtTimePropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtTimeEditFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtTimeEditFactory class provides QTimeEdit widgets for + properties created by QtTimePropertyManager objects. + + \sa QtAbstractEditorFactory, QtTimePropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtTimeEditFactory::QtTimeEditFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtTimeEditFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtTimeEditFactory::~QtTimeEditFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtTimeEditFactory::connectPropertyManager(QtTimePropertyManager *manager) +{ + connect(manager, &QtTimePropertyManager::valueChanged, + this, [this](QtProperty *property, QTime value) + { d_ptr->slotPropertyChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtTimeEditFactory::createEditor(QtTimePropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + QTimeEdit *editor = d_ptr->createEditor(property, parent); + editor->setDisplayFormat(QtPropertyBrowserUtils::timeFormat()); + editor->setTime(manager->value(property)); + + connect(editor, &QTimeEdit::timeChanged, + this, [this](QTime value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtTimeEditFactory::disconnectPropertyManager(QtTimePropertyManager *manager) +{ + disconnect(manager, &QtTimePropertyManager::valueChanged, this, nullptr); +} + +// QtDateTimeEditFactory + +class QtDateTimeEditFactoryPrivate : public EditorFactoryPrivate +{ + QtDateTimeEditFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtDateTimeEditFactory) +public: + + void slotPropertyChanged(QtProperty *property, const QDateTime &value); + void slotSetValue(const QDateTime &value); + +}; + +void QtDateTimeEditFactoryPrivate::slotPropertyChanged(QtProperty *property, + const QDateTime &value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + for (QDateTimeEdit *editor : it.value()) { + editor->blockSignals(true); + editor->setDateTime(value); + editor->blockSignals(false); + } +} + +void QtDateTimeEditFactoryPrivate::slotSetValue(const QDateTime &value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtDateTimePropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtDateTimeEditFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtDateTimeEditFactory class provides QDateTimeEdit + widgets for properties created by QtDateTimePropertyManager objects. + + \sa QtAbstractEditorFactory, QtDateTimePropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtDateTimeEditFactory::QtDateTimeEditFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtDateTimeEditFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtDateTimeEditFactory::~QtDateTimeEditFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtDateTimeEditFactory::connectPropertyManager(QtDateTimePropertyManager *manager) +{ + connect(manager, &QtDateTimePropertyManager::valueChanged, + this, [this](QtProperty *property, const QDateTime &value) + { d_ptr->slotPropertyChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtDateTimeEditFactory::createEditor(QtDateTimePropertyManager *manager, + QtProperty *property, QWidget *parent) +{ + QDateTimeEdit *editor = d_ptr->createEditor(property, parent); + editor->setDisplayFormat(QtPropertyBrowserUtils::dateTimeFormat()); + editor->setDateTime(manager->value(property)); + + connect(editor, &QDateTimeEdit::dateTimeChanged, + this, [this](const QDateTime &value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtDateTimeEditFactory::disconnectPropertyManager(QtDateTimePropertyManager *manager) +{ + disconnect(manager, &QtDateTimePropertyManager::valueChanged, this, nullptr); +} + +// QtKeySequenceEditorFactory + +class QtKeySequenceEditorFactoryPrivate : public EditorFactoryPrivate +{ + QtKeySequenceEditorFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtKeySequenceEditorFactory) +public: + + void slotPropertyChanged(QtProperty *property, const QKeySequence &value); + void slotSetValue(const QKeySequence &value); +}; + +void QtKeySequenceEditorFactoryPrivate::slotPropertyChanged(QtProperty *property, + const QKeySequence &value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + for (QKeySequenceEdit *editor : it.value()) { + editor->blockSignals(true); + editor->setKeySequence(value); + editor->blockSignals(false); + } +} + +void QtKeySequenceEditorFactoryPrivate::slotSetValue(const QKeySequence &value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtKeySequencePropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtKeySequenceEditorFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtKeySequenceEditorFactory class provides editor + widgets for properties created by QtKeySequencePropertyManager objects. + + \sa QtAbstractEditorFactory +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtKeySequenceEditorFactory::QtKeySequenceEditorFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtKeySequenceEditorFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtKeySequenceEditorFactory::~QtKeySequenceEditorFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtKeySequenceEditorFactory::connectPropertyManager(QtKeySequencePropertyManager *manager) +{ + connect(manager, &QtKeySequencePropertyManager::valueChanged, + this, [this](QtProperty *property, const QKeySequence &value) + { d_ptr->slotPropertyChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtKeySequenceEditorFactory::createEditor(QtKeySequencePropertyManager *manager, + QtProperty *property, QWidget *parent) +{ + QKeySequenceEdit *editor = d_ptr->createEditor(property, parent); + editor->setKeySequence(manager->value(property)); + + connect(editor, &QKeySequenceEdit::keySequenceChanged, + this, [this](const QKeySequence &value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtKeySequenceEditorFactory::disconnectPropertyManager(QtKeySequencePropertyManager *manager) +{ + disconnect(manager, &QtKeySequencePropertyManager::valueChanged, this, nullptr); +} + +// QtCharEdit + +class QtCharEdit : public QWidget +{ + Q_OBJECT +public: + QtCharEdit(QWidget *parent = 0); + + QChar value() const; + bool eventFilter(QObject *o, QEvent *e) override; +public Q_SLOTS: + void setValue(const QChar &value); +Q_SIGNALS: + void valueChanged(const QChar &value); +protected: + void focusInEvent(QFocusEvent *e) override; + void focusOutEvent(QFocusEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void keyReleaseEvent(QKeyEvent *e) override; + bool event(QEvent *e) override; +private slots: + void slotClearChar(); +private: + void handleKeyEvent(QKeyEvent *e); + + QChar m_value; + QLineEdit *m_lineEdit; +}; + +QtCharEdit::QtCharEdit(QWidget *parent) + : QWidget(parent), m_lineEdit(new QLineEdit(this)) +{ + auto *layout = new QHBoxLayout(this); + layout->addWidget(m_lineEdit); + layout->setContentsMargins(QMargins()); + m_lineEdit->installEventFilter(this); + m_lineEdit->setReadOnly(true); + m_lineEdit->setFocusProxy(this); + setFocusPolicy(m_lineEdit->focusPolicy()); + setAttribute(Qt::WA_InputMethodEnabled); +} + +bool QtCharEdit::eventFilter(QObject *o, QEvent *e) +{ + if (o == m_lineEdit && e->type() == QEvent::ContextMenu) { + QContextMenuEvent *c = static_cast(e); + QMenu *menu = m_lineEdit->createStandardContextMenu(); + const auto actions = menu->actions(); + for (QAction *action : actions) { + action->setShortcut(QKeySequence()); + QString actionString = action->text(); + const int pos = actionString.lastIndexOf(QLatin1Char('\t')); + if (pos > 0) + actionString = actionString.remove(pos, actionString.size() - pos); + action->setText(actionString); + } + QAction *actionBefore = nullptr; + if (actions.size() > 0) + actionBefore = actions[0]; + auto *clearAction = new QAction(tr("Clear Char"), menu); + menu->insertAction(actionBefore, clearAction); + menu->insertSeparator(actionBefore); + clearAction->setEnabled(!m_value.isNull()); + connect(clearAction, &QAction::triggered, this, &QtCharEdit::slotClearChar); + menu->exec(c->globalPos()); + delete menu; + e->accept(); + return true; + } + + return QWidget::eventFilter(o, e); +} + +void QtCharEdit::slotClearChar() +{ + if (m_value.isNull()) + return; + setValue(QChar()); + emit valueChanged(m_value); +} + +void QtCharEdit::handleKeyEvent(QKeyEvent *e) +{ + const int key = e->key(); + switch (key) { + case Qt::Key_Control: + case Qt::Key_Shift: + case Qt::Key_Meta: + case Qt::Key_Alt: + case Qt::Key_Super_L: + case Qt::Key_Return: + return; + default: + break; + } + + const QString text = e->text(); + if (text.size() != 1) + return; + + const QChar c = text.at(0); + if (!c.isPrint()) + return; + + if (m_value == c) + return; + + m_value = c; + const QString str = m_value.isNull() ? QString() : QString(m_value); + m_lineEdit->setText(str); + e->accept(); + emit valueChanged(m_value); +} + +void QtCharEdit::setValue(const QChar &value) +{ + if (value == m_value) + return; + + m_value = value; + QString str = value.isNull() ? QString() : QString(value); + m_lineEdit->setText(str); +} + +QChar QtCharEdit::value() const +{ + return m_value; +} + +void QtCharEdit::focusInEvent(QFocusEvent *e) +{ + m_lineEdit->event(e); + m_lineEdit->selectAll(); + QWidget::focusInEvent(e); +} + +void QtCharEdit::focusOutEvent(QFocusEvent *e) +{ + m_lineEdit->event(e); + QWidget::focusOutEvent(e); +} + +void QtCharEdit::keyPressEvent(QKeyEvent *e) +{ + handleKeyEvent(e); + e->accept(); +} + +void QtCharEdit::keyReleaseEvent(QKeyEvent *e) +{ + m_lineEdit->event(e); +} + +bool QtCharEdit::event(QEvent *e) +{ + switch(e->type()) { + case QEvent::Shortcut: + case QEvent::ShortcutOverride: + case QEvent::KeyRelease: + e->accept(); + return true; + default: + break; + } + return QWidget::event(e); +} + +// QtCharEditorFactory + +class QtCharEditorFactoryPrivate : public EditorFactoryPrivate +{ + QtCharEditorFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtCharEditorFactory) +public: + + void slotPropertyChanged(QtProperty *property, const QChar &value); + void slotSetValue(const QChar &value); + +}; + +void QtCharEditorFactoryPrivate::slotPropertyChanged(QtProperty *property, + const QChar &value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + for (QtCharEdit *editor : it.value()) { + editor->blockSignals(true); + editor->setValue(value); + editor->blockSignals(false); + } +} + +void QtCharEditorFactoryPrivate::slotSetValue(const QChar &value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtCharPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtCharEditorFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtCharEditorFactory class provides editor + widgets for properties created by QtCharPropertyManager objects. + + \sa QtAbstractEditorFactory +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtCharEditorFactory::QtCharEditorFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtCharEditorFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtCharEditorFactory::~QtCharEditorFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtCharEditorFactory::connectPropertyManager(QtCharPropertyManager *manager) +{ + connect(manager, &QtCharPropertyManager::valueChanged, + this, [this](QtProperty *property, const QChar &value) + { d_ptr->slotPropertyChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtCharEditorFactory::createEditor(QtCharPropertyManager *manager, + QtProperty *property, QWidget *parent) +{ + QtCharEdit *editor = d_ptr->createEditor(property, parent); + editor->setValue(manager->value(property)); + + connect(editor, &QtCharEdit::valueChanged, + this, [this](const QChar &value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtCharEditorFactory::disconnectPropertyManager(QtCharPropertyManager *manager) +{ + disconnect(manager, &QtCharPropertyManager::valueChanged, this, nullptr); +} + +// QtEnumEditorFactory + +class QtEnumEditorFactoryPrivate : public EditorFactoryPrivate +{ + QtEnumEditorFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtEnumEditorFactory) +public: + + void slotPropertyChanged(QtProperty *property, int value); + void slotEnumNamesChanged(QtProperty *property, const QStringList &); + void slotEnumIconsChanged(QtProperty *property, const QMap &); + void slotSetValue(int value); +}; + +void QtEnumEditorFactoryPrivate::slotPropertyChanged(QtProperty *property, int value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + for (QComboBox *editor : it.value()) { + editor->blockSignals(true); + editor->setCurrentIndex(value); + editor->blockSignals(false); + } +} + +void QtEnumEditorFactoryPrivate::slotEnumNamesChanged(QtProperty *property, + const QStringList &enumNames) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + QtEnumPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + QMap enumIcons = manager->enumIcons(property); + + for (QComboBox *editor : it.value()) { + editor->blockSignals(true); + editor->clear(); + editor->addItems(enumNames); + const int nameCount = enumNames.size(); + for (int i = 0; i < nameCount; i++) + editor->setItemIcon(i, enumIcons.value(i)); + editor->setCurrentIndex(manager->value(property)); + editor->blockSignals(false); + } +} + +void QtEnumEditorFactoryPrivate::slotEnumIconsChanged(QtProperty *property, + const QMap &enumIcons) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + QtEnumPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + + const QStringList enumNames = manager->enumNames(property); + for (QComboBox *editor : it.value()) { + editor->blockSignals(true); + const int nameCount = enumNames.size(); + for (int i = 0; i < nameCount; i++) + editor->setItemIcon(i, enumIcons.value(i)); + editor->setCurrentIndex(manager->value(property)); + editor->blockSignals(false); + } +} + +void QtEnumEditorFactoryPrivate::slotSetValue(int value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtEnumPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtEnumEditorFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtEnumEditorFactory class provides QComboBox widgets for + properties created by QtEnumPropertyManager objects. + + \sa QtAbstractEditorFactory, QtEnumPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtEnumEditorFactory::QtEnumEditorFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtEnumEditorFactoryPrivate()) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtEnumEditorFactory::~QtEnumEditorFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtEnumEditorFactory::connectPropertyManager(QtEnumPropertyManager *manager) +{ + connect(manager, &QtEnumPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotPropertyChanged(property, value); }); + connect(manager, &QtEnumPropertyManager::enumNamesChanged, + this, [this](QtProperty *property, const QStringList &value) + { d_ptr->slotEnumNamesChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtEnumEditorFactory::createEditor(QtEnumPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + QComboBox *editor = d_ptr->createEditor(property, parent); + editor->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed); + editor->view()->setTextElideMode(Qt::ElideRight); + QStringList enumNames = manager->enumNames(property); + editor->addItems(enumNames); + QMap enumIcons = manager->enumIcons(property); + const int enumNamesCount = enumNames.size(); + for (int i = 0; i < enumNamesCount; i++) + editor->setItemIcon(i, enumIcons.value(i)); + editor->setCurrentIndex(manager->value(property)); + + connect(editor, &QComboBox::currentIndexChanged, + this, [this](int value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtEnumEditorFactory::disconnectPropertyManager(QtEnumPropertyManager *manager) +{ + disconnect(manager, &QtEnumPropertyManager::valueChanged, this, nullptr); + disconnect(manager, &QtEnumPropertyManager::enumNamesChanged, this, nullptr); +} + +// QtCursorEditorFactory + +class QtCursorEditorFactoryPrivate +{ + QtCursorEditorFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtCursorEditorFactory) +public: + QtCursorEditorFactoryPrivate(); + + void slotPropertyChanged(QtProperty *property, const QCursor &cursor); + void slotEnumChanged(QtProperty *property, int value); + void slotEditorDestroyed(QObject *object); + + QtEnumEditorFactory *m_enumEditorFactory; + QtEnumPropertyManager *m_enumPropertyManager; + + QHash m_propertyToEnum; + QHash m_enumToProperty; + QHash m_enumToEditors; + QHash m_editorToEnum; + bool m_updatingEnum; +}; + +QtCursorEditorFactoryPrivate::QtCursorEditorFactoryPrivate() + : m_updatingEnum(false) +{ + +} + +void QtCursorEditorFactoryPrivate::slotPropertyChanged(QtProperty *property, const QCursor &cursor) +{ + // update enum property + QtProperty *enumProp = m_propertyToEnum.value(property); + if (!enumProp) + return; + + m_updatingEnum = true; + auto *cdb = QtCursorDatabase::instance(); + m_enumPropertyManager->setValue(enumProp, cdb->cursorToValue(cursor)); + m_updatingEnum = false; +} + +void QtCursorEditorFactoryPrivate::slotEnumChanged(QtProperty *property, int value) +{ + if (m_updatingEnum) + return; + // update cursor property + QtProperty *prop = m_enumToProperty.value(property); + if (!prop) + return; + QtCursorPropertyManager *cursorManager = q_ptr->propertyManager(prop); + if (!cursorManager) + return; +#ifndef QT_NO_CURSOR + auto *cdb = QtCursorDatabase::instance(); + cursorManager->setValue(prop, QCursor(cdb->valueToCursor(value))); +#endif +} + +void QtCursorEditorFactoryPrivate::slotEditorDestroyed(QObject *object) +{ + // remove from m_editorToEnum map; + // remove from m_enumToEditors map; + // if m_enumToEditors doesn't contains more editors delete enum property; + for (auto itEditor = m_editorToEnum.cbegin(), ecend = m_editorToEnum.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QWidget *editor = itEditor.key(); + QtProperty *enumProp = itEditor.value(); + m_editorToEnum.remove(editor); + m_enumToEditors[enumProp].removeAll(editor); + if (m_enumToEditors[enumProp].isEmpty()) { + m_enumToEditors.remove(enumProp); + QtProperty *property = m_enumToProperty.value(enumProp); + m_enumToProperty.remove(enumProp); + m_propertyToEnum.remove(property); + delete enumProp; + } + return; + } +} + +/*! + \class QtCursorEditorFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtCursorEditorFactory class provides QComboBox widgets for + properties created by QtCursorPropertyManager objects. + + \sa QtAbstractEditorFactory, QtCursorPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtCursorEditorFactory::QtCursorEditorFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtCursorEditorFactoryPrivate()) +{ + d_ptr->q_ptr = this; + + d_ptr->m_enumEditorFactory = new QtEnumEditorFactory(this); + d_ptr->m_enumPropertyManager = new QtEnumPropertyManager(this); + connect(d_ptr->m_enumPropertyManager, &QtEnumPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotEnumChanged(property, value); }); + d_ptr->m_enumEditorFactory->addPropertyManager(d_ptr->m_enumPropertyManager); +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtCursorEditorFactory::~QtCursorEditorFactory() +{ +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtCursorEditorFactory::connectPropertyManager(QtCursorPropertyManager *manager) +{ + connect(manager, &QtCursorPropertyManager::valueChanged, + this, [this](QtProperty *property, const QCursor &value) + { d_ptr->slotPropertyChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtCursorEditorFactory::createEditor(QtCursorPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + QtProperty *enumProp = nullptr; + if (d_ptr->m_propertyToEnum.contains(property)) { + enumProp = d_ptr->m_propertyToEnum[property]; + } else { + enumProp = d_ptr->m_enumPropertyManager->addProperty(property->propertyName()); + auto *cdb = QtCursorDatabase::instance(); + d_ptr->m_enumPropertyManager->setEnumNames(enumProp, cdb->cursorShapeNames()); + d_ptr->m_enumPropertyManager->setEnumIcons(enumProp, cdb->cursorShapeIcons()); +#ifndef QT_NO_CURSOR + d_ptr->m_enumPropertyManager->setValue(enumProp, cdb->cursorToValue(manager->value(property))); +#endif + d_ptr->m_propertyToEnum[property] = enumProp; + d_ptr->m_enumToProperty[enumProp] = property; + } + QtAbstractEditorFactoryBase *af = d_ptr->m_enumEditorFactory; + QWidget *editor = af->createEditor(enumProp, parent); + d_ptr->m_enumToEditors[enumProp].append(editor); + d_ptr->m_editorToEnum[editor] = enumProp; + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtCursorEditorFactory::disconnectPropertyManager(QtCursorPropertyManager *manager) +{ + disconnect(manager, &QtCursorPropertyManager::valueChanged, this, nullptr); +} + +// QtColorEditWidget + +class QtColorEditWidget : public QWidget { + Q_OBJECT + +public: + QtColorEditWidget(QWidget *parent); + + bool eventFilter(QObject *obj, QEvent *ev) override; + +public Q_SLOTS: + void setValue(QColor value); + +private Q_SLOTS: + void buttonClicked(); + +Q_SIGNALS: + void valueChanged(const QColor &value); + +private: + QColor m_color; + QLabel *m_pixmapLabel; + QLabel *m_label; + QToolButton *m_button; +}; + +QtColorEditWidget::QtColorEditWidget(QWidget *parent) : + QWidget(parent), + m_pixmapLabel(new QLabel), + m_label(new QLabel), + m_button(new QToolButton) +{ + auto *lt = new QHBoxLayout(this); + setupTreeViewEditorMargin(lt); + lt->setSpacing(0); + lt->addWidget(m_pixmapLabel); + lt->addWidget(m_label); + lt->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + + m_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); + m_button->setFixedWidth(20); + setFocusProxy(m_button); + setFocusPolicy(m_button->focusPolicy()); + m_button->setText(tr("...")); + m_button->installEventFilter(this); + connect(m_button, &QAbstractButton::clicked, this, &QtColorEditWidget::buttonClicked); + lt->addWidget(m_button); + m_pixmapLabel->setPixmap(QtPropertyBrowserUtils::brushValuePixmap(QBrush(m_color))); + m_label->setText(QtPropertyBrowserUtils::colorValueText(m_color)); +} + +void QtColorEditWidget::setValue(QColor c) +{ + if (m_color != c) { + m_color = c; + m_pixmapLabel->setPixmap(QtPropertyBrowserUtils::brushValuePixmap(QBrush(c))); + m_label->setText(QtPropertyBrowserUtils::colorValueText(c)); + } +} + +void QtColorEditWidget::buttonClicked() +{ + const QColor newColor = QColorDialog::getColor(m_color, this, QString(), QColorDialog::ShowAlphaChannel); + if (newColor.isValid() && newColor != m_color) { + setValue(newColor); + emit valueChanged(m_color); + } +} + +bool QtColorEditWidget::eventFilter(QObject *obj, QEvent *ev) +{ + if (obj == m_button) { + switch (ev->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: { // Prevent the QToolButton from handling Enter/Escape meant control the delegate + switch (static_cast(ev)->key()) { + case Qt::Key_Escape: + case Qt::Key_Enter: + case Qt::Key_Return: + ev->ignore(); + return true; + default: + break; + } + } + break; + default: + break; + } + } + return QWidget::eventFilter(obj, ev); +} + +// QtColorEditorFactoryPrivate + +class QtColorEditorFactoryPrivate : public EditorFactoryPrivate +{ + QtColorEditorFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtColorEditorFactory) +public: + + void slotPropertyChanged(QtProperty *property, QColor value); + void slotSetValue(QColor value); +}; + +void QtColorEditorFactoryPrivate::slotPropertyChanged(QtProperty *property, + QColor value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + for (QtColorEditWidget *e : it.value()) + e->setValue(value); +} + +void QtColorEditorFactoryPrivate::slotSetValue(QColor value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtColorPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtColorEditorFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtColorEditorFactory class provides color editing for + properties created by QtColorPropertyManager objects. + + \sa QtAbstractEditorFactory, QtColorPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtColorEditorFactory::QtColorEditorFactory(QObject *parent) : + QtAbstractEditorFactory(parent), + d_ptr(new QtColorEditorFactoryPrivate()) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtColorEditorFactory::~QtColorEditorFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtColorEditorFactory::connectPropertyManager(QtColorPropertyManager *manager) +{ + connect(manager, &QtColorPropertyManager::valueChanged, + this, [this](QtProperty *property, QColor value) + { d_ptr->slotPropertyChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtColorEditorFactory::createEditor(QtColorPropertyManager *manager, + QtProperty *property, QWidget *parent) +{ + QtColorEditWidget *editor = d_ptr->createEditor(property, parent); + editor->setValue(manager->value(property)); + connect(editor, &QtColorEditWidget::valueChanged, + this, [this](QColor value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtColorEditorFactory::disconnectPropertyManager(QtColorPropertyManager *manager) +{ + disconnect(manager, &QtColorPropertyManager::valueChanged, this, nullptr); +} + +// QtFontEditWidget + +class QtFontEditWidget : public QWidget { + Q_OBJECT + +public: + QtFontEditWidget(QWidget *parent); + + bool eventFilter(QObject *obj, QEvent *ev) override; + +public Q_SLOTS: + void setValue(const QFont &value); + +private Q_SLOTS: + void buttonClicked(); + +Q_SIGNALS: + void valueChanged(const QFont &value); + +private: + QFont m_font; + QLabel *m_pixmapLabel; + QLabel *m_label; + QToolButton *m_button; +}; + +QtFontEditWidget::QtFontEditWidget(QWidget *parent) : + QWidget(parent), + m_pixmapLabel(new QLabel), + m_label(new QLabel), + m_button(new QToolButton) +{ + auto *lt = new QHBoxLayout(this); + setupTreeViewEditorMargin(lt); + lt->setSpacing(0); + lt->addWidget(m_pixmapLabel); + lt->addWidget(m_label); + lt->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + + m_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); + m_button->setFixedWidth(20); + setFocusProxy(m_button); + setFocusPolicy(m_button->focusPolicy()); + m_button->setText(tr("...")); + m_button->installEventFilter(this); + connect(m_button, &QAbstractButton::clicked, this, &QtFontEditWidget::buttonClicked); + lt->addWidget(m_button); + m_pixmapLabel->setPixmap(QtPropertyBrowserUtils::fontValuePixmap(m_font)); + m_label->setText(QtPropertyBrowserUtils::fontValueText(m_font)); +} + +void QtFontEditWidget::setValue(const QFont &f) +{ + if (m_font != f) { + m_font = f; + m_pixmapLabel->setPixmap(QtPropertyBrowserUtils::fontValuePixmap(f)); + m_label->setText(QtPropertyBrowserUtils::fontValueText(f)); + } +} + +void QtFontEditWidget::buttonClicked() +{ + bool ok = false; + QFont newFont = QFontDialog::getFont(&ok, m_font, this, tr("Select Font")); + if (ok && newFont != m_font) { + QFont f = m_font; + // prevent mask for unchanged attributes, don't change other attributes (like kerning, etc...) + if (m_font.family() != newFont.family()) + f.setFamily(newFont.family()); + if (m_font.pointSize() != newFont.pointSize()) + f.setPointSize(newFont.pointSize()); + if (m_font.bold() != newFont.bold()) + f.setBold(newFont.bold()); + if (m_font.italic() != newFont.italic()) + f.setItalic(newFont.italic()); + if (m_font.underline() != newFont.underline()) + f.setUnderline(newFont.underline()); + if (m_font.strikeOut() != newFont.strikeOut()) + f.setStrikeOut(newFont.strikeOut()); + setValue(f); + emit valueChanged(m_font); + } +} + +bool QtFontEditWidget::eventFilter(QObject *obj, QEvent *ev) +{ + if (obj == m_button) { + switch (ev->type()) { + case QEvent::KeyPress: + case QEvent::KeyRelease: { // Prevent the QToolButton from handling Enter/Escape meant control the delegate + switch (static_cast(ev)->key()) { + case Qt::Key_Escape: + case Qt::Key_Enter: + case Qt::Key_Return: + ev->ignore(); + return true; + default: + break; + } + } + break; + default: + break; + } + } + return QWidget::eventFilter(obj, ev); +} + +// QtFontEditorFactoryPrivate + +class QtFontEditorFactoryPrivate : public EditorFactoryPrivate +{ + QtFontEditorFactory *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtFontEditorFactory) +public: + + void slotPropertyChanged(QtProperty *property, const QFont &value); + void slotSetValue(const QFont &value); +}; + +void QtFontEditorFactoryPrivate::slotPropertyChanged(QtProperty *property, + const QFont &value) +{ + const auto it = m_createdEditors.constFind(property); + if (it == m_createdEditors.constEnd()) + return; + + for (QtFontEditWidget *e : it.value()) + e->setValue(value); +} + +void QtFontEditorFactoryPrivate::slotSetValue(const QFont &value) +{ + QObject *object = q_ptr->sender(); + for (auto itEditor = m_editorToProperty.cbegin(), ecend = m_editorToProperty.cend(); itEditor != ecend; ++itEditor) + if (itEditor.key() == object) { + QtProperty *property = itEditor.value(); + QtFontPropertyManager *manager = q_ptr->propertyManager(property); + if (!manager) + return; + manager->setValue(property, value); + return; + } +} + +/*! + \class QtFontEditorFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtFontEditorFactory class provides font editing for + properties created by QtFontPropertyManager objects. + + \sa QtAbstractEditorFactory, QtFontPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtFontEditorFactory::QtFontEditorFactory(QObject *parent) : + QtAbstractEditorFactory(parent), + d_ptr(new QtFontEditorFactoryPrivate()) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtFontEditorFactory::~QtFontEditorFactory() +{ + qDeleteAll(d_ptr->m_editorToProperty.keys()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtFontEditorFactory::connectPropertyManager(QtFontPropertyManager *manager) +{ + connect(manager, &QtFontPropertyManager::valueChanged, + this, [this](QtProperty *property, const QFont &value) + { d_ptr->slotPropertyChanged(property, value); }); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtFontEditorFactory::createEditor(QtFontPropertyManager *manager, + QtProperty *property, QWidget *parent) +{ + QtFontEditWidget *editor = d_ptr->createEditor(property, parent); + editor->setValue(manager->value(property)); + connect(editor, &QtFontEditWidget::valueChanged, + this, [this](const QFont &value) { d_ptr->slotSetValue(value); }); + connect(editor, &QObject::destroyed, + this, [this](QObject *object) { d_ptr->slotEditorDestroyed(object); }); + return editor; +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtFontEditorFactory::disconnectPropertyManager(QtFontPropertyManager *manager) +{ + disconnect(manager, &QtFontPropertyManager::valueChanged, this, nullptr); +} + +QT_END_NAMESPACE + +#include "moc_qteditorfactory_p.cpp" +#include "qteditorfactory.moc" diff --git a/src/shared/qtpropertybrowser/qteditorfactory_p.h b/src/shared/qtpropertybrowser/qteditorfactory_p.h new file mode 100644 index 00000000000..37c07a4cd64 --- /dev/null +++ b/src/shared/qtpropertybrowser/qteditorfactory_p.h @@ -0,0 +1,311 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTEDITORFACTORY_H +#define QTEDITORFACTORY_H + +#include "qtpropertymanager_p.h" + +QT_BEGIN_NAMESPACE + +class QRegularExpression; + +class QtSpinBoxFactoryPrivate; + +class QtSpinBoxFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtSpinBoxFactory(QObject *parent = 0); + ~QtSpinBoxFactory(); +protected: + void connectPropertyManager(QtIntPropertyManager *manager) override; + QWidget *createEditor(QtIntPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtIntPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtSpinBoxFactory) + Q_DISABLE_COPY_MOVE(QtSpinBoxFactory) +}; + +class QtSliderFactoryPrivate; + +class QtSliderFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtSliderFactory(QObject *parent = 0); + ~QtSliderFactory(); +protected: + void connectPropertyManager(QtIntPropertyManager *manager) override; + QWidget *createEditor(QtIntPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtIntPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtSliderFactory) + Q_DISABLE_COPY_MOVE(QtSliderFactory) +}; + +class QtScrollBarFactoryPrivate; + +class QtScrollBarFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtScrollBarFactory(QObject *parent = 0); + ~QtScrollBarFactory(); +protected: + void connectPropertyManager(QtIntPropertyManager *manager) override; + QWidget *createEditor(QtIntPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtIntPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtScrollBarFactory) + Q_DISABLE_COPY_MOVE(QtScrollBarFactory) +}; + +class QtCheckBoxFactoryPrivate; + +class QtCheckBoxFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtCheckBoxFactory(QObject *parent = 0); + ~QtCheckBoxFactory(); +protected: + void connectPropertyManager(QtBoolPropertyManager *manager) override; + QWidget *createEditor(QtBoolPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtBoolPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtCheckBoxFactory) + Q_DISABLE_COPY_MOVE(QtCheckBoxFactory) +}; + +class QtDoubleSpinBoxFactoryPrivate; + +class QtDoubleSpinBoxFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtDoubleSpinBoxFactory(QObject *parent = 0); + ~QtDoubleSpinBoxFactory(); +protected: + void connectPropertyManager(QtDoublePropertyManager *manager) override; + QWidget *createEditor(QtDoublePropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtDoublePropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtDoubleSpinBoxFactory) + Q_DISABLE_COPY_MOVE(QtDoubleSpinBoxFactory) +}; + +class QtLineEditFactoryPrivate; + +class QtLineEditFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtLineEditFactory(QObject *parent = 0); + ~QtLineEditFactory(); +protected: + void connectPropertyManager(QtStringPropertyManager *manager) override; + QWidget *createEditor(QtStringPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtStringPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtLineEditFactory) + Q_DISABLE_COPY_MOVE(QtLineEditFactory) +}; + +class QtDateEditFactoryPrivate; + +class QtDateEditFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtDateEditFactory(QObject *parent = 0); + ~QtDateEditFactory(); +protected: + void connectPropertyManager(QtDatePropertyManager *manager) override; + QWidget *createEditor(QtDatePropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtDatePropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtDateEditFactory) + Q_DISABLE_COPY_MOVE(QtDateEditFactory) +}; + +class QtTimeEditFactoryPrivate; + +class QtTimeEditFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtTimeEditFactory(QObject *parent = 0); + ~QtTimeEditFactory(); +protected: + void connectPropertyManager(QtTimePropertyManager *manager) override; + QWidget *createEditor(QtTimePropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtTimePropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtTimeEditFactory) + Q_DISABLE_COPY_MOVE(QtTimeEditFactory) +}; + +class QtDateTimeEditFactoryPrivate; + +class QtDateTimeEditFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtDateTimeEditFactory(QObject *parent = 0); + ~QtDateTimeEditFactory(); +protected: + void connectPropertyManager(QtDateTimePropertyManager *manager) override; + QWidget *createEditor(QtDateTimePropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtDateTimePropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtDateTimeEditFactory) + Q_DISABLE_COPY_MOVE(QtDateTimeEditFactory) +}; + +class QtKeySequenceEditorFactoryPrivate; + +class QtKeySequenceEditorFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtKeySequenceEditorFactory(QObject *parent = 0); + ~QtKeySequenceEditorFactory(); +protected: + void connectPropertyManager(QtKeySequencePropertyManager *manager) override; + QWidget *createEditor(QtKeySequencePropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtKeySequencePropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtKeySequenceEditorFactory) + Q_DISABLE_COPY_MOVE(QtKeySequenceEditorFactory) +}; + +class QtCharEditorFactoryPrivate; + +class QtCharEditorFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtCharEditorFactory(QObject *parent = 0); + ~QtCharEditorFactory(); +protected: + void connectPropertyManager(QtCharPropertyManager *manager) override; + QWidget *createEditor(QtCharPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtCharPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtCharEditorFactory) + Q_DISABLE_COPY_MOVE(QtCharEditorFactory) +}; + +class QtEnumEditorFactoryPrivate; + +class QtEnumEditorFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtEnumEditorFactory(QObject *parent = 0); + ~QtEnumEditorFactory(); +protected: + void connectPropertyManager(QtEnumPropertyManager *manager) override; + QWidget *createEditor(QtEnumPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtEnumPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtEnumEditorFactory) + Q_DISABLE_COPY_MOVE(QtEnumEditorFactory) +}; + +class QtCursorEditorFactoryPrivate; + +class QtCursorEditorFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtCursorEditorFactory(QObject *parent = 0); + ~QtCursorEditorFactory(); +protected: + void connectPropertyManager(QtCursorPropertyManager *manager) override; + QWidget *createEditor(QtCursorPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtCursorPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtCursorEditorFactory) + Q_DISABLE_COPY_MOVE(QtCursorEditorFactory) +}; + +class QtColorEditorFactoryPrivate; + +class QtColorEditorFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtColorEditorFactory(QObject *parent = 0); + ~QtColorEditorFactory(); +protected: + void connectPropertyManager(QtColorPropertyManager *manager) override; + QWidget *createEditor(QtColorPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtColorPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtColorEditorFactory) + Q_DISABLE_COPY_MOVE(QtColorEditorFactory) +}; + +class QtFontEditorFactoryPrivate; + +class QtFontEditorFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtFontEditorFactory(QObject *parent = 0); + ~QtFontEditorFactory(); +protected: + void connectPropertyManager(QtFontPropertyManager *manager) override; + QWidget *createEditor(QtFontPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtFontPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtFontEditorFactory) + Q_DISABLE_COPY_MOVE(QtFontEditorFactory) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtpropertybrowser/qtgroupboxpropertybrowser.cpp b/src/shared/qtpropertybrowser/qtgroupboxpropertybrowser.cpp new file mode 100644 index 00000000000..1bfd6f69142 --- /dev/null +++ b/src/shared/qtpropertybrowser/qtgroupboxpropertybrowser.cpp @@ -0,0 +1,467 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtgroupboxpropertybrowser_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtGroupBoxPropertyBrowserPrivate +{ + QtGroupBoxPropertyBrowser *q_ptr; + Q_DECLARE_PUBLIC(QtGroupBoxPropertyBrowser) +public: + + void init(QWidget *parent); + + void propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex); + void propertyRemoved(QtBrowserItem *index); + void propertyChanged(QtBrowserItem *index); + QWidget *createEditor(QtProperty *property, QWidget *parent) const + { return q_ptr->createEditor(property, parent); } + + void slotEditorDestroyed(); + void slotUpdate(); + + struct WidgetItem + { + QWidget *widget{nullptr}; // can be null + QLabel *label{nullptr}; + QLabel *widgetLabel{nullptr}; + QGroupBox *groupBox{nullptr}; + QGridLayout *layout{nullptr}; + QFrame *line{nullptr}; + WidgetItem *parent{nullptr}; + QList children; + }; +private: + void updateLater(); + void updateItem(WidgetItem *item); + void insertRow(QGridLayout *layout, int row) const; + void removeRow(QGridLayout *layout, int row) const; + + bool hasHeader(WidgetItem *item) const; + + QHash m_indexToItem; + QHash m_itemToIndex; + QHash m_widgetToItem; + QGridLayout *m_mainLayout; + QList m_children; + QList m_recreateQueue; +}; + +void QtGroupBoxPropertyBrowserPrivate::init(QWidget *parent) +{ + m_mainLayout = new QGridLayout(); + parent->setLayout(m_mainLayout); + auto *item = new QSpacerItem(0, 0, QSizePolicy::Fixed, QSizePolicy::Expanding); + m_mainLayout->addItem(item, 0, 0); +} + +void QtGroupBoxPropertyBrowserPrivate::slotEditorDestroyed() +{ + auto *editor = qobject_cast(q_ptr->sender()); + if (!editor) + return; + if (!m_widgetToItem.contains(editor)) + return; + m_widgetToItem[editor]->widget = nullptr; + m_widgetToItem.remove(editor); +} + +void QtGroupBoxPropertyBrowserPrivate::slotUpdate() +{ + for (WidgetItem *item : std::as_const(m_recreateQueue)) { + WidgetItem *par = item->parent; + QWidget *w = nullptr; + QGridLayout *l = nullptr; + int oldRow = -1; + if (!par) { + w = q_ptr; + l = m_mainLayout; + oldRow = m_children.indexOf(item); + } else { + w = par->groupBox; + l = par->layout; + oldRow = par->children.indexOf(item); + if (hasHeader(par)) + oldRow += 2; + } + + if (item->widget) { + item->widget->setParent(w); + } else if (item->widgetLabel) { + item->widgetLabel->setParent(w); + } else { + item->widgetLabel = new QLabel(w); + } + int span = 1; + if (item->widget) + l->addWidget(item->widget, oldRow, 1, 1, 1); + else if (item->widgetLabel) + l->addWidget(item->widgetLabel, oldRow, 1, 1, 1); + else + span = 2; + item->label = new QLabel(w); + item->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + l->addWidget(item->label, oldRow, 0, 1, span); + + updateItem(item); + } + m_recreateQueue.clear(); +} + +void QtGroupBoxPropertyBrowserPrivate::updateLater() +{ + QMetaObject::invokeMethod(q_ptr, [this] { slotUpdate(); }, Qt::QueuedConnection); +} + +void QtGroupBoxPropertyBrowserPrivate::propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex) +{ + WidgetItem *afterItem = m_indexToItem.value(afterIndex); + WidgetItem *parentItem = m_indexToItem.value(index->parent()); + + auto *newItem = new WidgetItem(); + newItem->parent = parentItem; + + QGridLayout *layout = nullptr; + QWidget *parentWidget = nullptr; + int row = -1; + if (!afterItem) { + row = 0; + if (parentItem) + parentItem->children.insert(0, newItem); + else + m_children.insert(0, newItem); + } else { + if (parentItem) { + row = parentItem->children.indexOf(afterItem) + 1; + parentItem->children.insert(row, newItem); + } else { + row = m_children.indexOf(afterItem) + 1; + m_children.insert(row, newItem); + } + } + if (parentItem && hasHeader(parentItem)) + row += 2; + + if (!parentItem) { + layout = m_mainLayout; + parentWidget = q_ptr;; + } else { + if (!parentItem->groupBox) { + m_recreateQueue.removeAll(parentItem); + WidgetItem *par = parentItem->parent; + QWidget *w = nullptr; + QGridLayout *l = nullptr; + int oldRow = -1; + if (!par) { + w = q_ptr; + l = m_mainLayout; + oldRow = m_children.indexOf(parentItem); + } else { + w = par->groupBox; + l = par->layout; + oldRow = par->children.indexOf(parentItem); + if (hasHeader(par)) + oldRow += 2; + } + parentItem->groupBox = new QGroupBox(w); + parentItem->layout = new QGridLayout(); + parentItem->groupBox->setLayout(parentItem->layout); + if (parentItem->label) { + l->removeWidget(parentItem->label); + delete parentItem->label; + parentItem->label = nullptr; + } + if (parentItem->widget) { + l->removeWidget(parentItem->widget); + parentItem->widget->setParent(parentItem->groupBox); + parentItem->layout->addWidget(parentItem->widget, 0, 0, 1, 2); + parentItem->line = new QFrame(parentItem->groupBox); + } else if (parentItem->widgetLabel) { + l->removeWidget(parentItem->widgetLabel); + delete parentItem->widgetLabel; + parentItem->widgetLabel = nullptr; + } + if (parentItem->line) { + parentItem->line->setFrameShape(QFrame::HLine); + parentItem->line->setFrameShadow(QFrame::Sunken); + parentItem->layout->addWidget(parentItem->line, 1, 0, 1, 2); + } + l->addWidget(parentItem->groupBox, oldRow, 0, 1, 2); + updateItem(parentItem); + } + layout = parentItem->layout; + parentWidget = parentItem->groupBox; + } + + newItem->label = new QLabel(parentWidget); + newItem->label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + newItem->widget = createEditor(index->property(), parentWidget); + if (!newItem->widget) { + newItem->widgetLabel = new QLabel(parentWidget); + } else { + QObject::connect(newItem->widget, &QWidget::destroyed, + q_ptr, [this] { slotEditorDestroyed(); }); + m_widgetToItem[newItem->widget] = newItem; + } + + insertRow(layout, row); + int span = 1; + if (newItem->widget) + layout->addWidget(newItem->widget, row, 1); + else if (newItem->widgetLabel) + layout->addWidget(newItem->widgetLabel, row, 1); + else + span = 2; + layout->addWidget(newItem->label, row, 0, 1, span); + + m_itemToIndex[newItem] = index; + m_indexToItem[index] = newItem; + + updateItem(newItem); +} + +void QtGroupBoxPropertyBrowserPrivate::propertyRemoved(QtBrowserItem *index) +{ + WidgetItem *item = m_indexToItem.value(index); + + m_indexToItem.remove(index); + m_itemToIndex.remove(item); + + WidgetItem *parentItem = item->parent; + + int row = -1; + + if (parentItem) { + row = parentItem->children.indexOf(item); + parentItem->children.removeAt(row); + if (hasHeader(parentItem)) + row += 2; + } else { + row = m_children.indexOf(item); + m_children.removeAt(row); + } + + if (item->widget) + delete item->widget; + if (item->label) + delete item->label; + if (item->widgetLabel) + delete item->widgetLabel; + if (item->groupBox) + delete item->groupBox; + + if (!parentItem) { + removeRow(m_mainLayout, row); + } else if (parentItem->children.size() != 0) { + removeRow(parentItem->layout, row); + } else { + WidgetItem *par = parentItem->parent; + QGridLayout *l = (par ? par->layout : m_mainLayout); + if (parentItem->widget) { + parentItem->widget->hide(); + parentItem->widget->setParent(0); + } else if (parentItem->widgetLabel) { + parentItem->widgetLabel->hide(); + parentItem->widgetLabel->setParent(0); + } else { + //parentItem->widgetLabel = new QLabel(w); + } + l->removeWidget(parentItem->groupBox); + delete parentItem->groupBox; + parentItem->groupBox = nullptr; + parentItem->line = nullptr; + parentItem->layout = nullptr; + if (!m_recreateQueue.contains(parentItem)) + m_recreateQueue.append(parentItem); + updateLater(); + } + m_recreateQueue.removeAll(item); + + delete item; +} + +void QtGroupBoxPropertyBrowserPrivate::insertRow(QGridLayout *layout, int row) const +{ + QHash itemToPos; + int idx = 0; + while (idx < layout->count()) { + int r, c, rs, cs; + layout->getItemPosition(idx, &r, &c, &rs, &cs); + if (r >= row) { + itemToPos[layout->takeAt(idx)] = QRect(r + 1, c, rs, cs); + } else { + idx++; + } + } + + for (auto it = itemToPos.cbegin(), icend = itemToPos.cend(); it != icend; ++it) { + const QRect r = it.value(); + layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height()); + } +} + +void QtGroupBoxPropertyBrowserPrivate::removeRow(QGridLayout *layout, int row) const +{ + QHash itemToPos; + int idx = 0; + while (idx < layout->count()) { + int r, c, rs, cs; + layout->getItemPosition(idx, &r, &c, &rs, &cs); + if (r > row) { + itemToPos[layout->takeAt(idx)] = QRect(r - 1, c, rs, cs); + } else { + idx++; + } + } + + for (auto it = itemToPos.cbegin(), icend = itemToPos.cend(); it != icend; ++it) { + const QRect r = it.value(); + layout->addItem(it.key(), r.x(), r.y(), r.width(), r.height()); + } +} + +bool QtGroupBoxPropertyBrowserPrivate::hasHeader(WidgetItem *item) const +{ + return item->widget; +} + +void QtGroupBoxPropertyBrowserPrivate::propertyChanged(QtBrowserItem *index) +{ + WidgetItem *item = m_indexToItem.value(index); + + updateItem(item); +} + +void QtGroupBoxPropertyBrowserPrivate::updateItem(WidgetItem *item) +{ + QtProperty *property = m_itemToIndex[item]->property(); + if (item->groupBox) { + QFont font = item->groupBox->font(); + font.setUnderline(property->isModified()); + item->groupBox->setFont(font); + item->groupBox->setTitle(property->propertyName()); + item->groupBox->setToolTip(property->descriptionToolTip()); + item->groupBox->setStatusTip(property->statusTip()); + item->groupBox->setWhatsThis(property->whatsThis()); + item->groupBox->setEnabled(property->isEnabled()); + } + if (item->label) { + QFont font = item->label->font(); + font.setUnderline(property->isModified()); + item->label->setFont(font); + item->label->setText(property->propertyName()); + item->label->setToolTip(property->descriptionToolTip()); + item->label->setStatusTip(property->statusTip()); + item->label->setWhatsThis(property->whatsThis()); + item->label->setEnabled(property->isEnabled()); + } + if (item->widgetLabel) { + QFont font = item->widgetLabel->font(); + font.setUnderline(false); + item->widgetLabel->setFont(font); + item->widgetLabel->setText(property->valueText()); + item->widgetLabel->setEnabled(property->isEnabled()); + } + if (item->widget) { + QFont font = item->widget->font(); + font.setUnderline(false); + item->widget->setFont(font); + item->widget->setEnabled(property->isEnabled()); + const QString valueToolTip = property->valueToolTip(); + item->widget->setToolTip(valueToolTip.isEmpty() ? property->valueText() : valueToolTip); + } + //item->setIcon(1, property->valueIcon()); +} + + + +/*! + \class QtGroupBoxPropertyBrowser + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtGroupBoxPropertyBrowser class provides a QGroupBox + based property browser. + + A property browser is a widget that enables the user to edit a + given set of properties. Each property is represented by a label + specifying the property's name, and an editing widget (e.g. a line + edit or a combobox) holding its value. A property can have zero or + more subproperties. + + QtGroupBoxPropertyBrowser provides group boxes for all nested + properties, i.e. subproperties are enclosed by a group box with + the parent property's name as its title. For example: + + \image qtgroupboxpropertybrowser.png + + Use the QtAbstractPropertyBrowser API to add, insert and remove + properties from an instance of the QtGroupBoxPropertyBrowser + class. The properties themselves are created and managed by + implementations of the QtAbstractPropertyManager class. + + \sa QtTreePropertyBrowser, QtAbstractPropertyBrowser +*/ + +/*! + Creates a property browser with the given \a parent. +*/ +QtGroupBoxPropertyBrowser::QtGroupBoxPropertyBrowser(QWidget *parent) + : QtAbstractPropertyBrowser(parent), d_ptr(new QtGroupBoxPropertyBrowserPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->init(this); +} + +/*! + Destroys this property browser. + + Note that the properties that were inserted into this browser are + \e not destroyed since they may still be used in other + browsers. The properties are owned by the manager that created + them. + + \sa QtProperty, QtAbstractPropertyManager +*/ +QtGroupBoxPropertyBrowser::~QtGroupBoxPropertyBrowser() +{ + for (auto it = d_ptr->m_itemToIndex.cbegin(), icend = d_ptr->m_itemToIndex.cend(); it != icend; ++it) + delete it.key(); +} + +/*! + \reimp +*/ +void QtGroupBoxPropertyBrowser::itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem) +{ + d_ptr->propertyInserted(item, afterItem); +} + +/*! + \reimp +*/ +void QtGroupBoxPropertyBrowser::itemRemoved(QtBrowserItem *item) +{ + d_ptr->propertyRemoved(item); +} + +/*! + \reimp +*/ +void QtGroupBoxPropertyBrowser::itemChanged(QtBrowserItem *item) +{ + d_ptr->propertyChanged(item); +} + +QT_END_NAMESPACE + +#include "moc_qtgroupboxpropertybrowser_p.cpp" diff --git a/src/shared/qtpropertybrowser/qtgroupboxpropertybrowser_p.h b/src/shared/qtpropertybrowser/qtgroupboxpropertybrowser_p.h new file mode 100644 index 00000000000..50e0221bc7b --- /dev/null +++ b/src/shared/qtpropertybrowser/qtgroupboxpropertybrowser_p.h @@ -0,0 +1,44 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTGROUPBOXPROPERTYBROWSER_H +#define QTGROUPBOXPROPERTYBROWSER_H + +#include "qtpropertybrowser_p.h" + +QT_BEGIN_NAMESPACE + +class QtGroupBoxPropertyBrowserPrivate; + +class QtGroupBoxPropertyBrowser : public QtAbstractPropertyBrowser +{ + Q_OBJECT +public: + QtGroupBoxPropertyBrowser(QWidget *parent = 0); + ~QtGroupBoxPropertyBrowser(); + +protected: + void itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem) override; + void itemRemoved(QtBrowserItem *item) override; + void itemChanged(QtBrowserItem *item) override; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtGroupBoxPropertyBrowser) + Q_DISABLE_COPY_MOVE(QtGroupBoxPropertyBrowser) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtpropertybrowser/qtpropertybrowser.cpp b/src/shared/qtpropertybrowser/qtpropertybrowser.cpp new file mode 100644 index 00000000000..ef0562976f5 --- /dev/null +++ b/src/shared/qtpropertybrowser/qtpropertybrowser.cpp @@ -0,0 +1,1906 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtpropertybrowser_p.h" +#include +#include + +#if defined(Q_CC_MSVC) +# pragma warning(disable: 4786) /* MS VS 6: truncating debug info after 255 characters */ +#endif + +QT_BEGIN_NAMESPACE + +class QtPropertyPrivate +{ +public: + QtPropertyPrivate(QtAbstractPropertyManager *manager) : m_enabled(true), m_modified(false), m_manager(manager) {} + QtProperty *q_ptr; + + QSet m_parentItems; + QList m_subItems; + + QString m_valueToolTip; + QString m_descriptionToolTip; + QString m_statusTip; + QString m_whatsThis; + QString m_name; + bool m_enabled; + bool m_modified; + + QtAbstractPropertyManager * const m_manager; +}; + +class QtAbstractPropertyManagerPrivate +{ + QtAbstractPropertyManager *q_ptr; + Q_DECLARE_PUBLIC(QtAbstractPropertyManager) +public: + void propertyDestroyed(QtProperty *property); + void propertyChanged(QtProperty *property) const; + void propertyRemoved(QtProperty *property, + QtProperty *parentProperty) const; + void propertyInserted(QtProperty *property, QtProperty *parentProperty, + QtProperty *afterProperty) const; + + QSet m_properties; +}; + +/*! + \class QtProperty + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtProperty class encapsulates an instance of a property. + + Properties are created by objects of QtAbstractPropertyManager + subclasses; a manager can create properties of a given type, and + is used in conjunction with the QtAbstractPropertyBrowser class. A + property is always owned by the manager that created it, which can + be retrieved using the propertyManager() function. + + QtProperty contains the most common property attributes, and + provides functions for retrieving as well as setting their values: + + \table + \header \li Getter \li Setter + \row + \li propertyName() \li setPropertyName() + \row + \li statusTip() \li setStatusTip() + \row + \li descriptionToolTip() \li setDescriptionToolTip() + \row + \li valueToolTip() \li setValueToolTip() + \row + \li toolTip() \deprecated in 5.6 \li setToolTip() \deprecated in 5.6 + \row + \li whatsThis() \li setWhatsThis() + \row + \li isEnabled() \li setEnabled() + \row + \li isModified() \li setModified() + \row + \li valueText() \li Nop + \row + \li valueIcon() \li Nop + \endtable + + It is also possible to nest properties: QtProperty provides the + addSubProperty(), insertSubProperty() and removeSubProperty() functions to + manipulate the set of subproperties. Use the subProperties() + function to retrieve a property's current set of subproperties. + Note that nested properties are not owned by the parent property, + i.e. each subproperty is owned by the manager that created it. + + \sa QtAbstractPropertyManager, QtBrowserItem +*/ + +/*! + Creates a property with the given \a manager. + + This constructor is only useful when creating a custom QtProperty + subclass (e.g. QtVariantProperty). To create a regular QtProperty + object, use the QtAbstractPropertyManager::addProperty() + function instead. + + \sa QtAbstractPropertyManager::addProperty() +*/ +QtProperty::QtProperty(QtAbstractPropertyManager *manager) + : d_ptr(new QtPropertyPrivate(manager)) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this property. + + Note that subproperties are detached but not destroyed, i.e. they + can still be used in another context. + + \sa QtAbstractPropertyManager::clear() + +*/ +QtProperty::~QtProperty() +{ + for (QtProperty *property : std::as_const(d_ptr->m_parentItems)) + property->d_ptr->m_manager->d_ptr->propertyRemoved(this, property); + + d_ptr->m_manager->d_ptr->propertyDestroyed(this); + + for (QtProperty *property : std::as_const(d_ptr->m_subItems)) + property->d_ptr->m_parentItems.remove(this); + + for (QtProperty *property : std::as_const(d_ptr->m_parentItems)) + property->d_ptr->m_subItems.removeAll(this); +} + +/*! + Returns the set of subproperties. + + Note that subproperties are not owned by \e this property, but by + the manager that created them. + + \sa insertSubProperty(), removeSubProperty() +*/ +QList QtProperty::subProperties() const +{ + return d_ptr->m_subItems; +} + +/*! + Returns a pointer to the manager that owns this property. +*/ +QtAbstractPropertyManager *QtProperty::propertyManager() const +{ + return d_ptr->m_manager; +} + +/* Note: As of 17.7.2015 for Qt 5.6, the existing 'toolTip' of the Property + * Browser solution was split into valueToolTip() and descriptionToolTip() + * to be able to implement custom tool tip for QTBUG-45442. This could + * be back-ported to the solution. */ + +/*! + Returns the property value's tool tip. + + This is suitable for tool tips over the value (item delegate). + + \since 5.6 + \sa setValueToolTip() +*/ +QString QtProperty::valueToolTip() const +{ + return d_ptr->m_valueToolTip; +} + +/*! + Returns the property description's tool tip. + + This is suitable for tool tips over the description (label). + + \since 5.6 + \sa setDescriptionToolTip() +*/ +QString QtProperty::descriptionToolTip() const +{ + return d_ptr->m_descriptionToolTip; +} + +/*! + Returns the property's status tip. + + \sa setStatusTip() +*/ +QString QtProperty::statusTip() const +{ + return d_ptr->m_statusTip; +} + +/*! + Returns the property's "What's This" help text. + + \sa setWhatsThis() +*/ +QString QtProperty::whatsThis() const +{ + return d_ptr->m_whatsThis; +} + +/*! + Returns the property's name. + + \sa setPropertyName() +*/ +QString QtProperty::propertyName() const +{ + return d_ptr->m_name; +} + +/*! + Returns whether the property is enabled. + + \sa setEnabled() +*/ +bool QtProperty::isEnabled() const +{ + return d_ptr->m_enabled; +} + +/*! + Returns whether the property is modified. + + \sa setModified() +*/ +bool QtProperty::isModified() const +{ + return d_ptr->m_modified; +} + +/*! + Returns whether the property has a value. + + \sa QtAbstractPropertyManager::hasValue() +*/ +bool QtProperty::hasValue() const +{ + return d_ptr->m_manager->hasValue(this); +} + +/*! + Returns an icon representing the current state of this property. + + If the given property type can not generate such an icon, this + function returns an invalid icon. + + \sa QtAbstractPropertyManager::valueIcon() +*/ +QIcon QtProperty::valueIcon() const +{ + return d_ptr->m_manager->valueIcon(this); +} + +/*! + Returns a string representing the current state of this property. + + If the given property type can not generate such a string, this + function returns an empty string. + + \sa QtAbstractPropertyManager::valueText() +*/ +QString QtProperty::valueText() const +{ + return d_ptr->m_manager->valueText(this); +} + +/*! + Sets the property value's tool tip to the given \a text. + + \since 5.6 + \sa valueToolTip() +*/ +void QtProperty::setValueToolTip(const QString &text) +{ + if (d_ptr->m_valueToolTip == text) + return; + + d_ptr->m_valueToolTip = text; + propertyChanged(); +} + +/*! + Sets the property description's tool tip to the given \a text. + + \since 5.6 + \sa descriptionToolTip() +*/ +void QtProperty::setDescriptionToolTip(const QString &text) +{ + if (d_ptr->m_descriptionToolTip == text) + return; + + d_ptr->m_descriptionToolTip = text; + propertyChanged(); +} + +/*! + Sets the property's status tip to the given \a text. + + \sa statusTip() +*/ +void QtProperty::setStatusTip(const QString &text) +{ + if (d_ptr->m_statusTip == text) + return; + + d_ptr->m_statusTip = text; + propertyChanged(); +} + +/*! + Sets the property's "What's This" help text to the given \a text. + + \sa whatsThis() +*/ +void QtProperty::setWhatsThis(const QString &text) +{ + if (d_ptr->m_whatsThis == text) + return; + + d_ptr->m_whatsThis = text; + propertyChanged(); +} + +/*! + \fn void QtProperty::setPropertyName(const QString &name) + + Sets the property's name to the given \a name. + + \sa propertyName() +*/ +void QtProperty::setPropertyName(const QString &text) +{ + if (d_ptr->m_name == text) + return; + + d_ptr->m_name = text; + propertyChanged(); +} + +/*! + Enables or disables the property according to the passed \a enable value. + + \sa isEnabled() +*/ +void QtProperty::setEnabled(bool enable) +{ + if (d_ptr->m_enabled == enable) + return; + + d_ptr->m_enabled = enable; + propertyChanged(); +} + +/*! + Sets the property's modified state according to the passed \a modified value. + + \sa isModified() +*/ +void QtProperty::setModified(bool modified) +{ + if (d_ptr->m_modified == modified) + return; + + d_ptr->m_modified = modified; + propertyChanged(); +} + +/*! + Appends the given \a property to this property's subproperties. + + If the given \a property already is added, this function does + nothing. + + \sa insertSubProperty(), removeSubProperty() +*/ +void QtProperty::addSubProperty(QtProperty *property) +{ + QtProperty *after = nullptr; + if (d_ptr->m_subItems.size() > 0) + after = d_ptr->m_subItems.last(); + insertSubProperty(property, after); +} + +/*! + \fn void QtProperty::insertSubProperty(QtProperty *property, QtProperty *precedingProperty) + + Inserts the given \a property after the specified \a + precedingProperty into this property's list of subproperties. If + \a precedingProperty is 0, the specified \a property is inserted + at the beginning of the list. + + If the given \a property already is inserted, this function does + nothing. + + \sa addSubProperty(), removeSubProperty() +*/ +void QtProperty::insertSubProperty(QtProperty *property, + QtProperty *afterProperty) +{ + if (!property) + return; + + if (property == this) + return; + + // traverse all children of item. if this item is a child of item then cannot add. + auto pendingList = property->subProperties(); + QHash visited; + while (!pendingList.isEmpty()) { + QtProperty *i = pendingList.first(); + if (i == this) + return; + pendingList.removeFirst(); + if (visited.contains(i)) + continue; + visited[i] = true; + pendingList += i->subProperties(); + } + + pendingList = subProperties(); + int pos = 0; + int newPos = 0; + QtProperty *properAfterProperty = nullptr; + while (pos < pendingList.size()) { + QtProperty *i = pendingList.at(pos); + if (i == property) + return; // if item is already inserted in this item then cannot add. + if (i == afterProperty) { + newPos = pos + 1; + properAfterProperty = afterProperty; + } + pos++; + } + + d_ptr->m_subItems.insert(newPos, property); + property->d_ptr->m_parentItems.insert(this); + + d_ptr->m_manager->d_ptr->propertyInserted(property, this, properAfterProperty); +} + +/*! + Removes the given \a property from the list of subproperties + without deleting it. + + \sa addSubProperty(), insertSubProperty() +*/ +void QtProperty::removeSubProperty(QtProperty *property) +{ + if (!property) + return; + + d_ptr->m_manager->d_ptr->propertyRemoved(property, this); + + auto pendingList = subProperties(); + int pos = 0; + while (pos < pendingList.size()) { + if (pendingList.at(pos) == property) { + d_ptr->m_subItems.removeAt(pos); + property->d_ptr->m_parentItems.remove(this); + + return; + } + pos++; + } +} + +/*! + \internal +*/ +void QtProperty::propertyChanged() +{ + d_ptr->m_manager->d_ptr->propertyChanged(this); +} + +//////////////////////////////// + +void QtAbstractPropertyManagerPrivate::propertyDestroyed(QtProperty *property) +{ + if (m_properties.contains(property)) { + emit q_ptr->propertyDestroyed(property); + q_ptr->uninitializeProperty(property); + m_properties.remove(property); + } +} + +void QtAbstractPropertyManagerPrivate::propertyChanged(QtProperty *property) const +{ + emit q_ptr->propertyChanged(property); +} + +void QtAbstractPropertyManagerPrivate::propertyRemoved(QtProperty *property, + QtProperty *parentProperty) const +{ + emit q_ptr->propertyRemoved(property, parentProperty); +} + +void QtAbstractPropertyManagerPrivate::propertyInserted(QtProperty *property, + QtProperty *parentProperty, QtProperty *afterProperty) const +{ + emit q_ptr->propertyInserted(property, parentProperty, afterProperty); +} + +/*! + \class QtAbstractPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtAbstractPropertyManager provides an interface for + property managers. + + A manager can create and manage properties of a given type, and is + used in conjunction with the QtAbstractPropertyBrowser class. + + When using a property browser widget, the properties are created + and managed by implementations of the QtAbstractPropertyManager + class. To ensure that the properties' values will be displayed + using suitable editing widgets, the managers are associated with + objects of QtAbstractEditorFactory subclasses. The property browser + will use these associations to determine which factories it should + use to create the preferred editing widgets. + + The QtAbstractPropertyManager class provides common functionality + like creating a property using the addProperty() function, and + retrieving the properties created by the manager using the + properties() function. The class also provides signals that are + emitted when the manager's properties change: propertyInserted(), + propertyRemoved(), propertyChanged() and propertyDestroyed(). + + QtAbstractPropertyManager subclasses are supposed to provide their + own type specific API. Note that several ready-made + implementations are available: + + \list + \li QtBoolPropertyManager + \li QtColorPropertyManager + \li QtDatePropertyManager + \li QtDateTimePropertyManager + \li QtDoublePropertyManager + \li QtEnumPropertyManager + \li QtFlagPropertyManager + \li QtFontPropertyManager + \li QtGroupPropertyManager + \li QtIntPropertyManager + \li QtPointPropertyManager + \li QtRectPropertyManager + \li QtSizePropertyManager + \li QtSizePolicyPropertyManager + \li QtStringPropertyManager + \li QtTimePropertyManager + \li QtVariantPropertyManager + \endlist + + \sa QtAbstractEditorFactoryBase, QtAbstractPropertyBrowser, QtProperty +*/ + +/*! + \fn void QtAbstractPropertyManager::propertyInserted(QtProperty *newProperty, + QtProperty *parentProperty, QtProperty *precedingProperty) + + This signal is emitted when a new subproperty is inserted into an + existing property, passing pointers to the \a newProperty, \a + parentProperty and \a precedingProperty as parameters. + + If \a precedingProperty is 0, the \a newProperty was inserted at + the beginning of the \a parentProperty's subproperties list. + + Note that signal is emitted only if the \a parentProperty is created + by this manager. + + \sa QtAbstractPropertyBrowser::itemInserted() +*/ + +/*! + \fn void QtAbstractPropertyManager::propertyChanged(QtProperty *property) + + This signal is emitted whenever a property's data changes, passing + a pointer to the \a property as parameter. + + Note that signal is only emitted for properties that are created by + this manager. + + \sa QtAbstractPropertyBrowser::itemChanged() +*/ + +/*! + \fn void QtAbstractPropertyManager::propertyRemoved(QtProperty *property, QtProperty *parent) + + This signal is emitted when a subproperty is removed, passing + pointers to the removed \a property and the \a parent property as + parameters. + + Note that signal is emitted only when the \a parent property is + created by this manager. + + \sa QtAbstractPropertyBrowser::itemRemoved() +*/ + +/*! + \fn void QtAbstractPropertyManager::propertyDestroyed(QtProperty *property) + + This signal is emitted when the specified \a property is about to + be destroyed. + + Note that signal is only emitted for properties that are created + by this manager. + + \sa clear(), uninitializeProperty() +*/ + +/*! + \fn void QtAbstractPropertyBrowser::currentItemChanged(QtBrowserItem *current) + + This signal is emitted when the current item changes. The current item is specified by \a current. + + \sa QtAbstractPropertyBrowser::setCurrentItem() +*/ + +/*! + Creates an abstract property manager with the given \a parent. +*/ +QtAbstractPropertyManager::QtAbstractPropertyManager(QObject *parent) + : QObject(parent), d_ptr(new QtAbstractPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys the manager. All properties created by the manager are + destroyed. +*/ +QtAbstractPropertyManager::~QtAbstractPropertyManager() +{ + clear(); +} + +/*! + Destroys all the properties that this manager has created. + + \sa propertyDestroyed(), uninitializeProperty() +*/ +void QtAbstractPropertyManager::clear() const +{ + while (!d_ptr->m_properties.isEmpty()) + delete *d_ptr->m_properties.cbegin(); +} + +/*! + Returns the set of properties created by this manager. + + \sa addProperty() +*/ +QSet QtAbstractPropertyManager::properties() const +{ + return d_ptr->m_properties; +} + +/*! + Returns whether the given \a property has a value. + + The default implementation of this function returns true. + + \sa QtProperty::hasValue() +*/ +bool QtAbstractPropertyManager::hasValue(const QtProperty *property) const +{ + Q_UNUSED(property); + return true; +} + +/*! + Returns an icon representing the current state of the given \a + property. + + The default implementation of this function returns an invalid + icon. + + \sa QtProperty::valueIcon() +*/ +QIcon QtAbstractPropertyManager::valueIcon(const QtProperty *property) const +{ + Q_UNUSED(property); + return {}; +} + +/*! + Returns a string representing the current state of the given \a + property. + + The default implementation of this function returns an empty + string. + + \sa QtProperty::valueText() +*/ +QString QtAbstractPropertyManager::valueText(const QtProperty *property) const +{ + Q_UNUSED(property); + return {}; +} + +/*! + Creates a property with the given \a name which then is owned by this manager. + + Internally, this function calls the createProperty() and + initializeProperty() functions. + + \sa initializeProperty(), properties() +*/ +QtProperty *QtAbstractPropertyManager::addProperty(const QString &name) +{ + QtProperty *property = createProperty(); + if (property) { + property->setPropertyName(name); + d_ptr->m_properties.insert(property); + initializeProperty(property); + } + return property; +} + +/*! + Creates a property. + + The base implementation produce QtProperty instances; Reimplement + this function to make this manager produce objects of a QtProperty + subclass. + + \sa addProperty(), initializeProperty() +*/ +QtProperty *QtAbstractPropertyManager::createProperty() +{ + return new QtProperty(this); +} + +/*! + \fn void QtAbstractPropertyManager::initializeProperty(QtProperty *property) = 0 + + This function is called whenever a new valid property pointer has + been created, passing the pointer as parameter. + + The purpose is to let the manager know that the \a property has + been created so that it can provide additional attributes for the + new property, e.g. QtIntPropertyManager adds \l + {QtIntPropertyManager::value()}{value}, \l + {QtIntPropertyManager::minimum()}{minimum} and \l + {QtIntPropertyManager::maximum()}{maximum} attributes. Since each manager + subclass adds type specific attributes, this function is pure + virtual and must be reimplemented when deriving from the + QtAbstractPropertyManager class. + + \sa addProperty(), createProperty() +*/ + +/*! + This function is called just before the specified \a property is destroyed. + + The purpose is to let the property manager know that the \a + property is being destroyed so that it can remove the property's + additional attributes. + + \sa clear(), propertyDestroyed() +*/ +void QtAbstractPropertyManager::uninitializeProperty(QtProperty *property) +{ + Q_UNUSED(property); +} + +//////////////////////////////////// + +/*! + \class QtAbstractEditorFactoryBase + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtAbstractEditorFactoryBase provides an interface for + editor factories. + + An editor factory is a class that is able to create an editing + widget of a specified type (e.g. line edits or comboboxes) for a + given QtProperty object, and it is used in conjunction with the + QtAbstractPropertyManager and QtAbstractPropertyBrowser classes. + + When using a property browser widget, the properties are created + and managed by implementations of the QtAbstractPropertyManager + class. To ensure that the properties' values will be displayed + using suitable editing widgets, the managers are associated with + objects of QtAbstractEditorFactory subclasses. The property browser + will use these associations to determine which factories it should + use to create the preferred editing widgets. + + Typically, an editor factory is created by subclassing the + QtAbstractEditorFactory template class which inherits + QtAbstractEditorFactoryBase. But note that several ready-made + implementations are available: + + \list + \li QtCheckBoxFactory + \li QtDateEditFactory + \li QtDateTimeEditFactory + \li QtDoubleSpinBoxFactory + \li QtEnumEditorFactory + \li QtLineEditFactory + \li QtScrollBarFactory + \li QtSliderFactory + \li QtSpinBoxFactory + \li QtTimeEditFactory + \li QtVariantEditorFactory + \endlist + + \sa QtAbstractPropertyManager, QtAbstractPropertyBrowser +*/ + +/*! + \fn virtual QWidget *QtAbstractEditorFactoryBase::createEditor(QtProperty *property, + QWidget *parent) = 0 + + Creates an editing widget (with the given \a parent) for the given + \a property. + + This function is reimplemented in QtAbstractEditorFactory template class + which also provides a pure virtual convenience overload of this + function enabling access to the property's manager. + + \sa QtAbstractEditorFactory::createEditor() +*/ + +/*! + \fn QtAbstractEditorFactoryBase::QtAbstractEditorFactoryBase(QObject *parent = 0) + + Creates an abstract editor factory with the given \a parent. +*/ + +/*! + \fn virtual void QtAbstractEditorFactoryBase::breakConnection(QtAbstractPropertyManager *manager) = 0 + + \internal + + Detaches property manager from factory. + This method is reimplemented in QtAbstractEditorFactory template subclass. + You don't need to reimplement it in your subclasses. Instead implement more convenient + QtAbstractEditorFactory::disconnectPropertyManager() which gives you access to particular manager subclass. +*/ + +/*! + \fn virtual void QtAbstractEditorFactoryBase::managerDestroyed(QObject *manager) = 0 + + \internal + + This method is called when property manager is being destroyed. + Basically it notifies factory not to produce editors for properties owned by \a manager. + You don't need to reimplement it in your subclass. This method is implemented in + QtAbstractEditorFactory template subclass. +*/ + +/*! + \class QtAbstractEditorFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtAbstractEditorFactory is the base template class for editor + factories. + + An editor factory is a class that is able to create an editing + widget of a specified type (e.g. line edits or comboboxes) for a + given QtProperty object, and it is used in conjunction with the + QtAbstractPropertyManager and QtAbstractPropertyBrowser classes. + + Note that the QtAbstractEditorFactory functions are using the + PropertyManager template argument class which can be any + QtAbstractPropertyManager subclass. For example: + + \snippet doc/src/snippets/code/tools_shared_qtpropertybrowser_qtpropertybrowser.cpp 0 + + Note that QtSpinBoxFactory by definition creates editing widgets + \e only for properties created by QtIntPropertyManager. + + When using a property browser widget, the properties are created + and managed by implementations of the QtAbstractPropertyManager + class. To ensure that the properties' values will be displayed + using suitable editing widgets, the managers are associated with + objects of QtAbstractEditorFactory subclasses. The property browser will + use these associations to determine which factories it should use + to create the preferred editing widgets. + + A QtAbstractEditorFactory object is capable of producing editors for + several property managers at the same time. To create an + association between this factory and a given manager, use the + addPropertyManager() function. Use the removePropertyManager() function to make + this factory stop producing editors for a given property + manager. Use the propertyManagers() function to retrieve the set of + managers currently associated with this factory. + + Several ready-made implementations of the QtAbstractEditorFactory class + are available: + + \list + \li QtCheckBoxFactory + \li QtDateEditFactory + \li QtDateTimeEditFactory + \li QtDoubleSpinBoxFactory + \li QtEnumEditorFactory + \li QtLineEditFactory + \li QtScrollBarFactory + \li QtSliderFactory + \li QtSpinBoxFactory + \li QtTimeEditFactory + \li QtVariantEditorFactory + \endlist + + When deriving from the QtAbstractEditorFactory class, several pure virtual + functions must be implemented: the connectPropertyManager() function is + used by the factory to connect to the given manager's signals, the + createEditor() function is supposed to create an editor for the + given property controlled by the given manager, and finally the + disconnectPropertyManager() function is used by the factory to disconnect + from the specified manager's signals. + + \sa QtAbstractEditorFactoryBase, QtAbstractPropertyManager +*/ + +/*! + \fn QtAbstractEditorFactory::QtAbstractEditorFactory(QObject *parent = 0) + + Creates an editor factory with the given \a parent. + + \sa addPropertyManager() +*/ + +/*! + \fn QWidget *QtAbstractEditorFactory::createEditor(QtProperty *property, QWidget *parent) + + Creates an editing widget (with the given \a parent) for the given + \a property. +*/ + +/*! + \fn void QtAbstractEditorFactory::addPropertyManager(PropertyManager *manager) + + Adds the given \a manager to this factory's set of managers, + making this factory produce editing widgets for properties created + by the given manager. + + The PropertyManager type is a template argument class, and represents the chosen + QtAbstractPropertyManager subclass. + + \sa propertyManagers(), removePropertyManager() +*/ + +/*! + \fn void QtAbstractEditorFactory::removePropertyManager(PropertyManager *manager) + + Removes the given \a manager from this factory's set of + managers. The PropertyManager type is a template argument class, and may be + any QtAbstractPropertyManager subclass. + + \sa propertyManagers(), addPropertyManager() +*/ + +/*! + \fn virtual void QtAbstractEditorFactory::connectPropertyManager(PropertyManager *manager) = 0 + + Connects this factory to the given \a manager's signals. The + PropertyManager type is a template argument class, and represents + the chosen QtAbstractPropertyManager subclass. + + This function is used internally by the addPropertyManager() function, and + makes it possible to update an editing widget when the associated + property's data changes. This is typically done in custom slots + responding to the signals emitted by the property's manager, + e.g. QtIntPropertyManager::valueChanged() and + QtIntPropertyManager::rangeChanged(). + + \sa propertyManagers(), disconnectPropertyManager() +*/ + +/*! + \fn virtual QWidget *QtAbstractEditorFactory::createEditor(PropertyManager *manager, QtProperty *property, + QWidget *parent) = 0 + + Creates an editing widget with the given \a parent for the + specified \a property created by the given \a manager. The + PropertyManager type is a template argument class, and represents + the chosen QtAbstractPropertyManager subclass. + + This function must be implemented in derived classes: It is + recommended to store a pointer to the widget and map it to the + given \a property, since the widget must be updated whenever the + associated property's data changes. This is typically done in + custom slots responding to the signals emitted by the property's + manager, e.g. QtIntPropertyManager::valueChanged() and + QtIntPropertyManager::rangeChanged(). + + \sa connectPropertyManager() +*/ + +/*! + \fn virtual void QtAbstractEditorFactory::disconnectPropertyManager(PropertyManager *manager) = 0 + + Disconnects this factory from the given \a manager's signals. The + PropertyManager type is a template argument class, and represents + the chosen QtAbstractPropertyManager subclass. + + This function is used internally by the removePropertyManager() function. + + \sa propertyManagers(), connectPropertyManager() +*/ + +/*! + \fn QSet QtAbstractEditorFactory::propertyManagers() const + + Returns the factory's set of associated managers. The + PropertyManager type is a template argument class, and represents + the chosen QtAbstractPropertyManager subclass. + + \sa addPropertyManager(), removePropertyManager() +*/ + +/*! + \fn PropertyManager *QtAbstractEditorFactory::propertyManager(QtProperty *property) const + + Returns the property manager for the given \a property, or 0 if + the given \a property doesn't belong to any of this factory's + registered managers. + + The PropertyManager type is a template argument class, and represents the chosen + QtAbstractPropertyManager subclass. + + \sa propertyManagers() +*/ + +/*! + \fn virtual void QtAbstractEditorFactory::managerDestroyed(QObject *manager) + + \internal +*/ + +//////////////////////////////////// +class QtBrowserItemPrivate +{ +public: + QtBrowserItemPrivate(QtAbstractPropertyBrowser *browser, QtProperty *property, QtBrowserItem *parent) + : m_browser(browser), m_property(property), m_parent(parent), q_ptr(0) {} + + void addChild(QtBrowserItem *index, QtBrowserItem *after); + void removeChild(QtBrowserItem *index); + + QtAbstractPropertyBrowser * const m_browser; + QtProperty *m_property; + QtBrowserItem *m_parent; + + QtBrowserItem *q_ptr; + + QList m_children; + +}; + +void QtBrowserItemPrivate::addChild(QtBrowserItem *index, QtBrowserItem *after) +{ + if (m_children.contains(index)) + return; + int idx = m_children.indexOf(after) + 1; // we insert after returned idx, if it was -1 then we set idx to 0; + m_children.insert(idx, index); +} + +void QtBrowserItemPrivate::removeChild(QtBrowserItem *index) +{ + m_children.removeAll(index); +} + + +/*! + \class QtBrowserItem + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtBrowserItem class represents a property in + a property browser instance. + + Browser items are created whenever a QtProperty is inserted to the + property browser. A QtBrowserItem uniquely identifies a + browser's item. Thus, if the same QtProperty is inserted multiple + times, each occurrence gets its own unique QtBrowserItem. The + items are owned by QtAbstractPropertyBrowser and automatically + deleted when they are removed from the browser. + + You can traverse a browser's properties by calling parent() and + children(). The property and the browser associated with an item + are available as property() and browser(). + + \sa QtAbstractPropertyBrowser, QtProperty +*/ + +/*! + Returns the property which is accosiated with this item. Note that + several items can be associated with the same property instance in + the same property browser. + + \sa QtAbstractPropertyBrowser::items() +*/ + +QtProperty *QtBrowserItem::property() const +{ + return d_ptr->m_property; +} + +/*! + Returns the parent item of \e this item. Returns 0 if \e this item + is associated with top-level property in item's property browser. + + \sa children() +*/ + +QtBrowserItem *QtBrowserItem::parent() const +{ + return d_ptr->m_parent; +} + +/*! + Returns the children items of \e this item. The properties + reproduced from children items are always the same as + reproduced from associated property' children, for example: + + \snippet doc/src/snippets/code/tools_shared_qtpropertybrowser_qtpropertybrowser.cpp 1 + + The \e childrenItems list represents the same list as \e childrenProperties. +*/ + +QList QtBrowserItem::children() const +{ + return d_ptr->m_children; +} + +/*! + Returns the property browser which owns \e this item. +*/ + +QtAbstractPropertyBrowser *QtBrowserItem::browser() const +{ + return d_ptr->m_browser; +} + +QtBrowserItem::QtBrowserItem(QtAbstractPropertyBrowser *browser, QtProperty *property, QtBrowserItem *parent) + : d_ptr(new QtBrowserItemPrivate(browser, property, parent)) +{ + d_ptr->q_ptr = this; +} + +QtBrowserItem::~QtBrowserItem() +{ +} + + +//////////////////////////////////// + +using Map1 = QHash>; +using Map2 = QHash>>; + +Q_GLOBAL_STATIC(Map1, m_viewToManagerToFactory) +Q_GLOBAL_STATIC(Map2, m_managerToFactoryToViews) + +class QtAbstractPropertyBrowserPrivate +{ + QtAbstractPropertyBrowser *q_ptr; + Q_DECLARE_PUBLIC(QtAbstractPropertyBrowser) +public: + QtAbstractPropertyBrowserPrivate(); + + void insertSubTree(QtProperty *property, + QtProperty *parentProperty); + void removeSubTree(QtProperty *property, + QtProperty *parentProperty); + void createBrowserIndexes(QtProperty *property, QtProperty *parentProperty, QtProperty *afterProperty); + void removeBrowserIndexes(QtProperty *property, QtProperty *parentProperty); + QtBrowserItem *createBrowserIndex(QtProperty *property, QtBrowserItem *parentIndex, QtBrowserItem *afterIndex); + void removeBrowserIndex(QtBrowserItem *index); + void clearIndex(QtBrowserItem *index); + + void slotPropertyInserted(QtProperty *property, + QtProperty *parentProperty, QtProperty *afterProperty); + void slotPropertyRemoved(QtProperty *property, QtProperty *parentProperty); + void slotPropertyDestroyed(QtProperty *property); + void slotPropertyDataChanged(QtProperty *property); + + QList m_subItems; + QHash > m_managerToProperties; + QHash > m_propertyToParents; + + QHash m_topLevelPropertyToIndex; + QList m_topLevelIndexes; + QHash > m_propertyToIndexes; + + QtBrowserItem *m_currentItem; +}; + +QtAbstractPropertyBrowserPrivate::QtAbstractPropertyBrowserPrivate() : + m_currentItem(0) +{ +} + +void QtAbstractPropertyBrowserPrivate::insertSubTree(QtProperty *property, + QtProperty *parentProperty) +{ + if (m_propertyToParents.contains(property)) { + // property was already inserted, so its manager is connected + // and all its children are inserted and theirs managers are connected + // we just register new parent (parent has to be new). + m_propertyToParents[property].append(parentProperty); + // don't need to update m_managerToProperties map since + // m_managerToProperties[manager] already contains property. + return; + } + QtAbstractPropertyManager *manager = property->propertyManager(); + if (m_managerToProperties[manager].isEmpty()) { + // connect manager's signals + q_ptr->connect(manager, &QtAbstractPropertyManager::propertyInserted, + q_ptr, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { slotPropertyInserted(property, parent, after); }); + q_ptr->connect(manager, &QtAbstractPropertyManager::propertyRemoved, + q_ptr, [this](QtProperty *property, QtProperty *parent) + { slotPropertyRemoved(property, parent); }); + q_ptr->connect(manager, &QtAbstractPropertyManager::propertyDestroyed, + q_ptr, [this](QtProperty *property) { slotPropertyDestroyed(property); }); + q_ptr->connect(manager, &QtAbstractPropertyManager::propertyChanged, + q_ptr, [this](QtProperty *property) { slotPropertyDataChanged(property); }); + } + m_managerToProperties[manager].append(property); + m_propertyToParents[property].append(parentProperty); + + const auto subList = property->subProperties(); + for (QtProperty *subProperty : subList) + insertSubTree(subProperty, property); +} + +void QtAbstractPropertyBrowserPrivate::removeSubTree(QtProperty *property, + QtProperty *parentProperty) +{ + if (!m_propertyToParents.contains(property)) { + // ASSERT + return; + } + + m_propertyToParents[property].removeAll(parentProperty); + if (!m_propertyToParents[property].isEmpty()) + return; + + m_propertyToParents.remove(property); + QtAbstractPropertyManager *manager = property->propertyManager(); + m_managerToProperties[manager].removeAll(property); + if (m_managerToProperties[manager].isEmpty()) { + // disconnect manager's signals + q_ptr->disconnect(manager, &QtAbstractPropertyManager::propertyInserted, q_ptr, nullptr); + q_ptr->disconnect(manager, &QtAbstractPropertyManager::propertyRemoved, q_ptr, nullptr); + q_ptr->disconnect(manager, &QtAbstractPropertyManager::propertyDestroyed, q_ptr, nullptr); + q_ptr->disconnect(manager, &QtAbstractPropertyManager::propertyChanged, q_ptr, nullptr); + + m_managerToProperties.remove(manager); + } + + const auto subList = property->subProperties(); + for (QtProperty *subProperty : subList) + removeSubTree(subProperty, property); +} + +void QtAbstractPropertyBrowserPrivate::createBrowserIndexes(QtProperty *property, QtProperty *parentProperty, QtProperty *afterProperty) +{ + QHash parentToAfter; + if (afterProperty) { + const auto it = m_propertyToIndexes.constFind(afterProperty); + if (it == m_propertyToIndexes.constEnd()) + return; + + for (QtBrowserItem *idx : it.value()) { + QtBrowserItem *parentIdx = idx->parent(); + if ((parentProperty && parentIdx && parentIdx->property() == parentProperty) || (!parentProperty && !parentIdx)) + parentToAfter[idx->parent()] = idx; + } + } else if (parentProperty) { + const auto it = m_propertyToIndexes.find(parentProperty); + if (it == m_propertyToIndexes.constEnd()) + return; + + for (QtBrowserItem *idx : it.value()) + parentToAfter[idx] = nullptr; + } else { + parentToAfter[nullptr] = nullptr; + } + + for (auto it = parentToAfter.cbegin(), pcend = parentToAfter.cend(); it != pcend; ++it) + createBrowserIndex(property, it.key(), it.value()); +} + +QtBrowserItem *QtAbstractPropertyBrowserPrivate::createBrowserIndex(QtProperty *property, + QtBrowserItem *parentIndex, QtBrowserItem *afterIndex) +{ + auto *newIndex = new QtBrowserItem(q_ptr, property, parentIndex); + if (parentIndex) { + parentIndex->d_ptr->addChild(newIndex, afterIndex); + } else { + m_topLevelPropertyToIndex[property] = newIndex; + m_topLevelIndexes.insert(m_topLevelIndexes.indexOf(afterIndex) + 1, newIndex); + } + m_propertyToIndexes[property].append(newIndex); + + q_ptr->itemInserted(newIndex, afterIndex); + + const auto subItems = property->subProperties(); + QtBrowserItem *afterChild = nullptr; + for (QtProperty *child : subItems) + afterChild = createBrowserIndex(child, newIndex, afterChild); + return newIndex; +} + +void QtAbstractPropertyBrowserPrivate::removeBrowserIndexes(QtProperty *property, QtProperty *parentProperty) +{ + QList toRemove; + const auto it = m_propertyToIndexes.constFind(property); + if (it == m_propertyToIndexes.constEnd()) + return; + + for (QtBrowserItem *idx : it.value()) { + QtBrowserItem *parentIdx = idx->parent(); + if ((parentProperty && parentIdx && parentIdx->property() == parentProperty) || (!parentProperty && !parentIdx)) + toRemove.append(idx); + } + + for (QtBrowserItem *index : std::as_const(toRemove)) + removeBrowserIndex(index); +} + +void QtAbstractPropertyBrowserPrivate::removeBrowserIndex(QtBrowserItem *index) +{ + const auto children = index->children(); + for (int i = children.size(); i > 0; i--) { + removeBrowserIndex(children.at(i - 1)); + } + + q_ptr->itemRemoved(index); + + if (index->parent()) { + index->parent()->d_ptr->removeChild(index); + } else { + m_topLevelPropertyToIndex.remove(index->property()); + m_topLevelIndexes.removeAll(index); + } + + QtProperty *property = index->property(); + + m_propertyToIndexes[property].removeAll(index); + if (m_propertyToIndexes[property].isEmpty()) + m_propertyToIndexes.remove(property); + + delete index; +} + +void QtAbstractPropertyBrowserPrivate::clearIndex(QtBrowserItem *index) +{ + const auto children = index->children(); + for (QtBrowserItem *item : children) + clearIndex(item); + delete index; +} + +void QtAbstractPropertyBrowserPrivate::slotPropertyInserted(QtProperty *property, + QtProperty *parentProperty, QtProperty *afterProperty) +{ + if (!m_propertyToParents.contains(parentProperty)) + return; + createBrowserIndexes(property, parentProperty, afterProperty); + insertSubTree(property, parentProperty); + //q_ptr->propertyInserted(property, parentProperty, afterProperty); +} + +void QtAbstractPropertyBrowserPrivate::slotPropertyRemoved(QtProperty *property, + QtProperty *parentProperty) +{ + if (!m_propertyToParents.contains(parentProperty)) + return; + removeSubTree(property, parentProperty); // this line should be probably moved down after propertyRemoved call + //q_ptr->propertyRemoved(property, parentProperty); + removeBrowserIndexes(property, parentProperty); +} + +void QtAbstractPropertyBrowserPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (!m_subItems.contains(property)) + return; + q_ptr->removeProperty(property); +} + +void QtAbstractPropertyBrowserPrivate::slotPropertyDataChanged(QtProperty *property) +{ + if (!m_propertyToParents.contains(property)) + return; + + const auto it = m_propertyToIndexes.constFind(property); + if (it == m_propertyToIndexes.constEnd()) + return; + + const auto indexes = it.value(); + for (QtBrowserItem *idx : indexes) + q_ptr->itemChanged(idx); + //q_ptr->propertyChanged(property); +} + +/*! + \class QtAbstractPropertyBrowser + \internal + \inmodule QtDesigner + \since 4.4 + + \brief QtAbstractPropertyBrowser provides a base class for + implementing property browsers. + + A property browser is a widget that enables the user to edit a + given set of properties. Each property is represented by a label + specifying the property's name, and an editing widget (e.g. a line + edit or a combobox) holding its value. A property can have zero or + more subproperties. + + \image qtpropertybrowser.png + + The top level properties can be retrieved using the + properties() function. To traverse each property's + subproperties, use the QtProperty::subProperties() function. In + addition, the set of top level properties can be manipulated using + the addProperty(), insertProperty() and removeProperty() + functions. Note that the QtProperty class provides a corresponding + set of functions making it possible to manipulate the set of + subproperties as well. + + To remove all the properties from the property browser widget, use + the clear() function. This function will clear the editor, but it + will not delete the properties since they can still be used in + other editors. + + The properties themselves are created and managed by + implementations of the QtAbstractPropertyManager class. A manager + can handle (i.e. create and manage) properties of a given type. In + the property browser the managers are associated with + implementations of the QtAbstractEditorFactory: A factory is a + class able to create an editing widget of a specified type. + + When using a property browser widget, managers must be created for + each of the required property types before the properties + themselves can be created. To ensure that the properties' values + will be displayed using suitable editing widgets, the managers + must be associated with objects of the preferred factory + implementations using the setFactoryForManager() function. The + property browser will use these associations to determine which + factory it should use to create the preferred editing widget. + + Note that a factory can be associated with many managers, but a + manager can only be associated with one single factory within the + context of a single property browser. The associations between + managers and factories can at any time be removed using the + unsetFactoryForManager() function. + + Whenever the property data changes or a property is inserted or + removed, the itemChanged(), itemInserted() or + itemRemoved() functions are called, respectively. These + functions must be reimplemented in derived classes in order to + update the property browser widget. Be aware that some property + instances can appear several times in an abstract tree + structure. For example: + + \table 100% + \row + \li + \snippet doc/src/snippets/code/tools_shared_qtpropertybrowser_qtpropertybrowser.cpp 2 + \li \image qtpropertybrowser-duplicate.png + \endtable + + The addProperty() function returns a QtBrowserItem that uniquely + identifies the created item. + + To make a property editable in the property browser, the + createEditor() function must be called to provide the + property with a suitable editing widget. + + Note that there are two ready-made property browser + implementations: + + \list + \li QtGroupBoxPropertyBrowser + \li QtTreePropertyBrowser + \endlist + + \sa QtAbstractPropertyManager, QtAbstractEditorFactoryBase +*/ + +/*! + \fn void QtAbstractPropertyBrowser::setFactoryForManager(PropertyManager *manager, + QtAbstractEditorFactory *factory) + + Connects the given \a manager to the given \a factory, ensuring + that properties of the \a manager's type will be displayed with an + editing widget suitable for their value. + + For example: + + \snippet doc/src/snippets/code/tools_shared_qtpropertybrowser_qtpropertybrowser.cpp 3 + + In this example the \c myInteger property's value is displayed + with a QSpinBox widget, while the \c myDouble property's value is + displayed with a QDoubleSpinBox widget. + + Note that a factory can be associated with many managers, but a + manager can only be associated with one single factory. If the + given \a manager already is associated with another factory, the + old association is broken before the new one established. + + This function ensures that the given \a manager and the given \a + factory are compatible, and it automatically calls the + QtAbstractEditorFactory::addPropertyManager() function if necessary. + + \sa unsetFactoryForManager() +*/ + +/*! + \fn virtual void QtAbstractPropertyBrowser::itemInserted(QtBrowserItem *insertedItem, + QtBrowserItem *precedingItem) = 0 + + This function is called to update the widget whenever a property + is inserted or added to the property browser, passing pointers to + the \a insertedItem of property and the specified + \a precedingItem as parameters. + + If \a precedingItem is 0, the \a insertedItem was put at + the beginning of its parent item's list of subproperties. If + the parent of \a insertedItem is 0, the \a insertedItem was added as a top + level property of \e this property browser. + + This function must be reimplemented in derived classes. Note that + if the \a insertedItem's property has subproperties, this + method will be called for those properties as soon as the current call is finished. + + \sa insertProperty(), addProperty() +*/ + +/*! + \fn virtual void QtAbstractPropertyBrowser::itemRemoved(QtBrowserItem *item) = 0 + + This function is called to update the widget whenever a property + is removed from the property browser, passing the pointer to the + \a item of the property as parameters. The passed \a item is + deleted just after this call is finished. + + If the parent of \a item is 0, the removed \a item was a + top level property in this editor. + + This function must be reimplemented in derived classes. Note that + if the removed \a item's property has subproperties, this + method will be called for those properties just before the current call is started. + + \sa removeProperty() +*/ + +/*! + \fn virtual void QtAbstractPropertyBrowser::itemChanged(QtBrowserItem *item) = 0 + + This function is called whenever a property's data changes, + passing a pointer to the \a item of property as parameter. + + This function must be reimplemented in derived classes in order to + update the property browser widget whenever a property's name, + tool tip, status tip, "what's this" text, value text or value icon + changes. + + Note that if the property browser contains several occurrences of + the same property, this method will be called once for each + occurrence (with a different item each time). + + \sa QtProperty, items() +*/ + +/*! + Creates an abstract property browser with the given \a parent. +*/ +QtAbstractPropertyBrowser::QtAbstractPropertyBrowser(QWidget *parent) + : QWidget(parent), d_ptr(new QtAbstractPropertyBrowserPrivate) +{ + d_ptr->q_ptr = this; + +} + +/*! + Destroys the property browser, and destroys all the items that were + created by this property browser. + + Note that the properties that were displayed in the editor are not + deleted since they still can be used in other editors. Neither + does the destructor delete the property managers and editor + factories that were used by this property browser widget unless + this widget was their parent. + + \sa QtAbstractPropertyManager::~QtAbstractPropertyManager() +*/ +QtAbstractPropertyBrowser::~QtAbstractPropertyBrowser() +{ + const auto indexes = topLevelItems(); + for (QtBrowserItem *item : indexes) + d_ptr->clearIndex(item); +} + +/*! + Returns the property browser's list of top level properties. + + To traverse the subproperties, use the QtProperty::subProperties() + function. + + \sa addProperty(), insertProperty(), removeProperty() +*/ +QList QtAbstractPropertyBrowser::properties() const +{ + return d_ptr->m_subItems; +} + +/*! + Returns the property browser's list of all items associated + with the given \a property. + + There is one item per instance of the property in the browser. + + \sa topLevelItem() +*/ + +QList QtAbstractPropertyBrowser::items(QtProperty *property) const +{ + return d_ptr->m_propertyToIndexes.value(property); +} + +/*! + Returns the top-level items associated with the given \a property. + + Returns 0 if \a property wasn't inserted into this property + browser or isn't a top-level one. + + \sa topLevelItems(), items() +*/ + +QtBrowserItem *QtAbstractPropertyBrowser::topLevelItem(QtProperty *property) const +{ + return d_ptr->m_topLevelPropertyToIndex.value(property); +} + +/*! + Returns the list of top-level items. + + \sa topLevelItem() +*/ + +QList QtAbstractPropertyBrowser::topLevelItems() const +{ + return d_ptr->m_topLevelIndexes; +} + +/*! + Removes all the properties from the editor, but does not delete + them since they can still be used in other editors. + + \sa removeProperty(), QtAbstractPropertyManager::clear() +*/ +void QtAbstractPropertyBrowser::clear() +{ + const auto subList = properties(); + for (auto rit = subList.crbegin(), rend = subList.crend(); rit != rend; ++rit) + removeProperty(*rit); +} + +/*! + Appends the given \a property (and its subproperties) to the + property browser's list of top level properties. Returns the item + created by property browser which is associated with the \a property. + In order to get all children items created by the property + browser in this call, the returned item should be traversed. + + If the specified \a property is already added, this function does + nothing and returns 0. + + \sa insertProperty(), QtProperty::addSubProperty(), properties() +*/ +QtBrowserItem *QtAbstractPropertyBrowser::addProperty(QtProperty *property) +{ + QtProperty *afterProperty = nullptr; + if (d_ptr->m_subItems.size() > 0) + afterProperty = d_ptr->m_subItems.last(); + return insertProperty(property, afterProperty); +} + +/*! + \fn QtBrowserItem *QtAbstractPropertyBrowser::insertProperty(QtProperty *property, + QtProperty *afterProperty) + + Inserts the given \a property (and its subproperties) after + the specified \a afterProperty in the browser's list of top + level properties. Returns item created by property browser which + is associated with the \a property. In order to get all children items + created by the property browser in this call returned item should be traversed. + + If the specified \a afterProperty is 0, the given \a property is + inserted at the beginning of the list. If \a property is + already inserted, this function does nothing and returns 0. + + \sa addProperty(), QtProperty::insertSubProperty(), properties() +*/ +QtBrowserItem *QtAbstractPropertyBrowser::insertProperty(QtProperty *property, + QtProperty *afterProperty) +{ + if (!property) + return nullptr; + + // if item is already inserted in this item then cannot add. + auto pendingList = properties(); + int pos = 0; + int newPos = 0; + while (pos < pendingList.size()) { + QtProperty *prop = pendingList.at(pos); + if (prop == property) + return nullptr; + if (prop == afterProperty) { + newPos = pos + 1; + } + pos++; + } + d_ptr->createBrowserIndexes(property, 0, afterProperty); + + // traverse inserted subtree and connect to manager's signals + d_ptr->insertSubTree(property, 0); + + d_ptr->m_subItems.insert(newPos, property); + //propertyInserted(property, 0, properAfterProperty); + return topLevelItem(property); +} + +/*! + Removes the specified \a property (and its subproperties) from the + property browser's list of top level properties. All items + that were associated with the given \a property and its children + are deleted. + + Note that the properties are \e not deleted since they can still + be used in other editors. + + \sa clear(), QtProperty::removeSubProperty(), properties() +*/ +void QtAbstractPropertyBrowser::removeProperty(QtProperty *property) +{ + if (!property) + return; + + auto pendingList = properties(); + int pos = 0; + while (pos < pendingList.size()) { + if (pendingList.at(pos) == property) { + d_ptr->m_subItems.removeAt(pos); //perhaps this two lines + d_ptr->removeSubTree(property, 0); //should be moved down after propertyRemoved call. + //propertyRemoved(property, 0); + + d_ptr->removeBrowserIndexes(property, 0); + + // when item is deleted, item will call removeItem for top level items, + // and itemRemoved for nested items. + + return; + } + pos++; + } +} + +/*! + Creates an editing widget (with the given \a parent) for the given + \a property according to the previously established associations + between property managers and editor factories. + + If the property is created by a property manager which was not + associated with any of the existing factories in \e this property + editor, the function returns 0. + + To make a property editable in the property browser, the + createEditor() function must be called to provide the + property with a suitable editing widget. + + Reimplement this function to provide additional decoration for the + editing widgets created by the installed factories. + + \sa setFactoryForManager() +*/ +QWidget *QtAbstractPropertyBrowser::createEditor(QtProperty *property, + QWidget *parent) +{ + QtAbstractEditorFactoryBase *factory = nullptr; + QtAbstractPropertyManager *manager = property->propertyManager(); + + if (m_viewToManagerToFactory()->contains(this) && + (*m_viewToManagerToFactory())[this].contains(manager)) { + factory = (*m_viewToManagerToFactory())[this][manager]; + } + + if (!factory) + return nullptr; + QWidget *w = factory->createEditor(property, parent); + // Since some editors can be QComboBoxes, and we changed their focus policy in Qt 5 + // to make them feel more native on Mac, we need to relax the focus policy to something + // more permissive to keep the combo box from losing focus, allowing it to stay alive, + // when the user clicks on it to show the popup. + if (w) + w->setFocusPolicy(Qt::WheelFocus); + return w; +} + +bool QtAbstractPropertyBrowser::addFactory(QtAbstractPropertyManager *abstractManager, + QtAbstractEditorFactoryBase *abstractFactory) +{ + bool connectNeeded = false; + if (!m_managerToFactoryToViews()->contains(abstractManager) || + !(*m_managerToFactoryToViews())[abstractManager].contains(abstractFactory)) { + connectNeeded = true; + } else if ((*m_managerToFactoryToViews())[abstractManager][abstractFactory] + .contains(this)) { + return connectNeeded; + } + + if (m_viewToManagerToFactory()->contains(this) && + (*m_viewToManagerToFactory())[this].contains(abstractManager)) { + unsetFactoryForManager(abstractManager); + } + + (*m_managerToFactoryToViews())[abstractManager][abstractFactory].append(this); + (*m_viewToManagerToFactory())[this][abstractManager] = abstractFactory; + + return connectNeeded; +} + +/*! + Removes the association between the given \a manager and the + factory bound to it, automatically calling the + QtAbstractEditorFactory::removePropertyManager() function if necessary. + + \sa setFactoryForManager() +*/ +void QtAbstractPropertyBrowser::unsetFactoryForManager(QtAbstractPropertyManager *manager) +{ + if (!m_viewToManagerToFactory()->contains(this) || + !(*m_viewToManagerToFactory())[this].contains(manager)) { + return; + } + + QtAbstractEditorFactoryBase *abstractFactory = + (*m_viewToManagerToFactory())[this][manager]; + (*m_viewToManagerToFactory())[this].remove(manager); + if ((*m_viewToManagerToFactory())[this].isEmpty()) { + (*m_viewToManagerToFactory()).remove(this); + } + + (*m_managerToFactoryToViews())[manager][abstractFactory].removeAll(this); + if ((*m_managerToFactoryToViews())[manager][abstractFactory].isEmpty()) { + (*m_managerToFactoryToViews())[manager].remove(abstractFactory); + abstractFactory->breakConnection(manager); + if ((*m_managerToFactoryToViews())[manager].isEmpty()) { + (*m_managerToFactoryToViews()).remove(manager); + } + } +} + +/*! + Returns the current item in the property browser. + + \sa setCurrentItem() +*/ +QtBrowserItem *QtAbstractPropertyBrowser::currentItem() const +{ + return d_ptr->m_currentItem; +} + +/*! + Sets the current item in the property browser to \a item. + + \sa currentItem(), currentItemChanged() +*/ +void QtAbstractPropertyBrowser::setCurrentItem(QtBrowserItem *item) +{ + QtBrowserItem *oldItem = d_ptr->m_currentItem; + d_ptr->m_currentItem = item; + if (oldItem != item) + emit currentItemChanged(item); +} + +QT_END_NAMESPACE + +#include "moc_qtpropertybrowser_p.cpp" diff --git a/src/shared/qtpropertybrowser/qtpropertybrowser_p.h b/src/shared/qtpropertybrowser/qtpropertybrowser_p.h new file mode 100644 index 00000000000..fe232537b3b --- /dev/null +++ b/src/shared/qtpropertybrowser/qtpropertybrowser_p.h @@ -0,0 +1,269 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTPROPERTYBROWSER_H +#define QTPROPERTYBROWSER_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QtAbstractPropertyManager; +class QtPropertyPrivate; + +class QtProperty +{ +public: + virtual ~QtProperty(); + + QList subProperties() const; + + QtAbstractPropertyManager *propertyManager() const; + + QString toolTip() const { return valueToolTip(); } // Compatibility + QString valueToolTip() const; + QString descriptionToolTip() const; + QString statusTip() const; + QString whatsThis() const; + QString propertyName() const; + bool isEnabled() const; + bool isModified() const; + + bool hasValue() const; + QIcon valueIcon() const; + QString valueText() const; + + void setToolTip(const QString &text) { setValueToolTip(text); } // Compatibility + void setValueToolTip(const QString &text); + void setDescriptionToolTip(const QString &text); + void setStatusTip(const QString &text); + void setWhatsThis(const QString &text); + void setPropertyName(const QString &text); + void setEnabled(bool enable); + void setModified(bool modified); + + void addSubProperty(QtProperty *property); + void insertSubProperty(QtProperty *property, QtProperty *afterProperty); + void removeSubProperty(QtProperty *property); +protected: + explicit QtProperty(QtAbstractPropertyManager *manager); + void propertyChanged(); +private: + friend class QtAbstractPropertyManager; + QScopedPointer d_ptr; +}; + +class QtAbstractPropertyManagerPrivate; + +class QtAbstractPropertyManager : public QObject +{ + Q_OBJECT +public: + + explicit QtAbstractPropertyManager(QObject *parent = 0); + ~QtAbstractPropertyManager(); + + QSet properties() const; + void clear() const; + + QtProperty *addProperty(const QString &name = QString()); +Q_SIGNALS: + void propertyInserted(QtProperty *property, QtProperty *parent, QtProperty *after); + void propertyChanged(QtProperty *property); + void propertyRemoved(QtProperty *property, QtProperty *parent); + void propertyDestroyed(QtProperty *property); +protected: + virtual bool hasValue(const QtProperty *property) const; + virtual QIcon valueIcon(const QtProperty *property) const; + virtual QString valueText(const QtProperty *property) const; + virtual void initializeProperty(QtProperty *property) = 0; + virtual void uninitializeProperty(QtProperty *property); + virtual QtProperty *createProperty(); +private: + friend class QtProperty; + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtAbstractPropertyManager) + Q_DISABLE_COPY_MOVE(QtAbstractPropertyManager) +}; + +class QtAbstractEditorFactoryBase : public QObject +{ + Q_OBJECT +public: + virtual QWidget *createEditor(QtProperty *property, QWidget *parent) = 0; +protected: + explicit QtAbstractEditorFactoryBase(QObject *parent = 0) + : QObject(parent) {} + + virtual void breakConnection(QtAbstractPropertyManager *manager) = 0; +protected Q_SLOTS: + virtual void managerDestroyed(QObject *manager) = 0; + + friend class QtAbstractPropertyBrowser; +}; + +template +class QtAbstractEditorFactory : public QtAbstractEditorFactoryBase +{ +public: + explicit QtAbstractEditorFactory(QObject *parent) : QtAbstractEditorFactoryBase(parent) {} + QWidget *createEditor(QtProperty *property, QWidget *parent) override + { + for (PropertyManager *manager : std::as_const(m_managers)) { + if (manager == property->propertyManager()) { + return createEditor(manager, property, parent); + } + } + return 0; + } + void addPropertyManager(PropertyManager *manager) + { + if (m_managers.contains(manager)) + return; + m_managers.insert(manager); + connectPropertyManager(manager); + connect(manager, &QObject::destroyed, + this, &QtAbstractEditorFactory::managerDestroyed); + } + void removePropertyManager(PropertyManager *manager) + { + if (!m_managers.contains(manager)) + return; + disconnect(manager, &QObject::destroyed, + this, &QtAbstractEditorFactory::managerDestroyed); + disconnectPropertyManager(manager); + m_managers.remove(manager); + } + QSet propertyManagers() const + { + return m_managers; + } + PropertyManager *propertyManager(QtProperty *property) const + { + QtAbstractPropertyManager *manager = property->propertyManager(); + for (PropertyManager *m : std::as_const(m_managers)) { + if (m == manager) { + return m; + } + } + return 0; + } +protected: + virtual void connectPropertyManager(PropertyManager *manager) = 0; + virtual QWidget *createEditor(PropertyManager *manager, QtProperty *property, + QWidget *parent) = 0; + virtual void disconnectPropertyManager(PropertyManager *manager) = 0; + void managerDestroyed(QObject *manager) override + { + for (PropertyManager *m : std::as_const(m_managers)) { + if (m == manager) { + m_managers.remove(m); + return; + } + } + } +private: + void breakConnection(QtAbstractPropertyManager *manager) override + { + for (PropertyManager *m : std::as_const(m_managers)) { + if (m == manager) { + removePropertyManager(m); + return; + } + } + } +private: + QSet m_managers; + friend class QtAbstractPropertyEditor; +}; + +class QtAbstractPropertyBrowser; +class QtBrowserItemPrivate; + +class QtBrowserItem +{ +public: + QtProperty *property() const; + QtBrowserItem *parent() const; + QList children() const; + QtAbstractPropertyBrowser *browser() const; +private: + explicit QtBrowserItem(QtAbstractPropertyBrowser *browser, QtProperty *property, QtBrowserItem *parent); + ~QtBrowserItem(); + QScopedPointer d_ptr; + friend class QtAbstractPropertyBrowserPrivate; +}; + +class QtAbstractPropertyBrowserPrivate; + +class QtAbstractPropertyBrowser : public QWidget +{ + Q_OBJECT +public: + + explicit QtAbstractPropertyBrowser(QWidget *parent = 0); + ~QtAbstractPropertyBrowser(); + + QList properties() const; + QList items(QtProperty *property) const; + QtBrowserItem *topLevelItem(QtProperty *property) const; + QList topLevelItems() const; + void clear(); + + template + void setFactoryForManager(PropertyManager *manager, + QtAbstractEditorFactory *factory) { + QtAbstractPropertyManager *abstractManager = manager; + QtAbstractEditorFactoryBase *abstractFactory = factory; + + if (addFactory(abstractManager, abstractFactory)) + factory->addPropertyManager(manager); + } + + void unsetFactoryForManager(QtAbstractPropertyManager *manager); + + QtBrowserItem *currentItem() const; + void setCurrentItem(QtBrowserItem *); + +Q_SIGNALS: + void currentItemChanged(QtBrowserItem *); + +public Q_SLOTS: + + QtBrowserItem *addProperty(QtProperty *property); + QtBrowserItem *insertProperty(QtProperty *property, QtProperty *afterProperty); + void removeProperty(QtProperty *property); + +protected: + + virtual void itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem) = 0; + virtual void itemRemoved(QtBrowserItem *item) = 0; + // can be tooltip, statustip, whatsthis, name, icon, text. + virtual void itemChanged(QtBrowserItem *item) = 0; + + virtual QWidget *createEditor(QtProperty *property, QWidget *parent); +private: + + bool addFactory(QtAbstractPropertyManager *abstractManager, + QtAbstractEditorFactoryBase *abstractFactory); + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtAbstractPropertyBrowser) + Q_DISABLE_COPY_MOVE(QtAbstractPropertyBrowser) +}; + +QT_END_NAMESPACE + +#endif // QTPROPERTYBROWSER_H diff --git a/src/shared/qtpropertybrowser/qtpropertybrowserutils.cpp b/src/shared/qtpropertybrowser/qtpropertybrowserutils.cpp new file mode 100644 index 00000000000..fa038a15b8b --- /dev/null +++ b/src/shared/qtpropertybrowser/qtpropertybrowserutils.cpp @@ -0,0 +1,288 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtpropertybrowserutils_p.h" +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Make sure icons are removed as soon as QApplication is destroyed, otherwise, +// handles are leaked on X11. +static void clearCursorDatabase() +{ + QtCursorDatabase::instance()->clear(); +} + +QtCursorDatabase::QtCursorDatabase() +{ + qAddPostRoutine(clearCursorDatabase); + + appendCursor(Qt::ArrowCursor, QCoreApplication::translate("QtCursorDatabase", "Arrow"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-arrow.png"_L1)); + appendCursor(Qt::UpArrowCursor, QCoreApplication::translate("QtCursorDatabase", "Up Arrow"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-uparrow.png"_L1)); + appendCursor(Qt::CrossCursor, QCoreApplication::translate("QtCursorDatabase", "Cross"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-cross.png"_L1)); + appendCursor(Qt::WaitCursor, QCoreApplication::translate("QtCursorDatabase", "Wait"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-wait.png"_L1)); + appendCursor(Qt::IBeamCursor, QCoreApplication::translate("QtCursorDatabase", "IBeam"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-ibeam.png"_L1)); + appendCursor(Qt::SizeVerCursor, QCoreApplication::translate("QtCursorDatabase", "Size Vertical"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizev.png"_L1)); + appendCursor(Qt::SizeHorCursor, QCoreApplication::translate("QtCursorDatabase", "Size Horizontal"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizeh.png"_L1)); + appendCursor(Qt::SizeFDiagCursor, QCoreApplication::translate("QtCursorDatabase", "Size Backslash"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizef.png"_L1)); + appendCursor(Qt::SizeBDiagCursor, QCoreApplication::translate("QtCursorDatabase", "Size Slash"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizeb.png"_L1)); + appendCursor(Qt::SizeAllCursor, QCoreApplication::translate("QtCursorDatabase", "Size All"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-sizeall.png"_L1)); + appendCursor(Qt::BlankCursor, QCoreApplication::translate("QtCursorDatabase", "Blank"), + QIcon()); + appendCursor(Qt::SplitVCursor, QCoreApplication::translate("QtCursorDatabase", "Split Vertical"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-vsplit.png"_L1)); + appendCursor(Qt::SplitHCursor, QCoreApplication::translate("QtCursorDatabase", "Split Horizontal"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-hsplit.png"_L1)); + appendCursor(Qt::PointingHandCursor, QCoreApplication::translate("QtCursorDatabase", "Pointing Hand"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-hand.png"_L1)); + appendCursor(Qt::ForbiddenCursor, QCoreApplication::translate("QtCursorDatabase", "Forbidden"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-forbidden.png"_L1)); + appendCursor(Qt::OpenHandCursor, QCoreApplication::translate("QtCursorDatabase", "Open Hand"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-openhand.png"_L1)); + appendCursor(Qt::ClosedHandCursor, QCoreApplication::translate("QtCursorDatabase", "Closed Hand"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-closedhand.png"_L1)); + appendCursor(Qt::WhatsThisCursor, QCoreApplication::translate("QtCursorDatabase", "What's This"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-whatsthis.png"_L1)); + appendCursor(Qt::BusyCursor, QCoreApplication::translate("QtCursorDatabase", "Busy"), + QIcon(":/qt-project.org/qtpropertybrowser/images/cursor-busy.png"_L1)); +} + +void QtCursorDatabase::clear() +{ + m_cursorNames.clear(); + m_cursorIcons.clear(); + m_valueToCursorShape.clear(); + m_cursorShapeToValue.clear(); +} + +void QtCursorDatabase::appendCursor(Qt::CursorShape shape, const QString &name, const QIcon &icon) +{ + if (m_cursorShapeToValue.contains(shape)) + return; + const int value = m_cursorNames.size(); + m_cursorNames.append(name); + m_cursorIcons.insert(value, icon); + m_valueToCursorShape.insert(value, shape); + m_cursorShapeToValue.insert(shape, value); +} + +QStringList QtCursorDatabase::cursorShapeNames() const +{ + return m_cursorNames; +} + +QMap QtCursorDatabase::cursorShapeIcons() const +{ + return m_cursorIcons; +} + +QString QtCursorDatabase::cursorToShapeName(const QCursor &cursor) const +{ + int val = cursorToValue(cursor); + if (val >= 0) + return m_cursorNames.at(val); + return {}; +} + +QIcon QtCursorDatabase::cursorToShapeIcon(const QCursor &cursor) const +{ + int val = cursorToValue(cursor); + return m_cursorIcons.value(val); +} + +int QtCursorDatabase::cursorToValue(const QCursor &cursor) const +{ +#ifndef QT_NO_CURSOR + Qt::CursorShape shape = cursor.shape(); + if (m_cursorShapeToValue.contains(shape)) + return m_cursorShapeToValue[shape]; +#endif + return -1; +} + +#ifndef QT_NO_CURSOR +QCursor QtCursorDatabase::valueToCursor(int value) const +{ + if (m_valueToCursorShape.contains(value)) + return QCursor(m_valueToCursorShape[value]); + return {}; +} +#endif + +Q_GLOBAL_STATIC(QtCursorDatabase, cursorDatabase) + +QtCursorDatabase *QtCursorDatabase::instance() +{ + return cursorDatabase(); +} + +QPixmap QtPropertyBrowserUtils::brushValuePixmap(const QBrush &b) +{ + QImage img(16, 16, QImage::Format_ARGB32_Premultiplied); + img.fill(0); + + QPainter painter(&img); + painter.setCompositionMode(QPainter::CompositionMode_Source); + painter.fillRect(0, 0, img.width(), img.height(), b); + QColor color = b.color(); + if (color.alpha() != 255) { // indicate alpha by an inset + QBrush opaqueBrush = b; + color.setAlpha(255); + opaqueBrush.setColor(color); + painter.fillRect(img.width() / 4, img.height() / 4, + img.width() / 2, img.height() / 2, opaqueBrush); + } + painter.end(); + return QPixmap::fromImage(img); +} + +QIcon QtPropertyBrowserUtils::brushValueIcon(const QBrush &b) +{ + return QIcon(brushValuePixmap(b)); +} + +QString QtPropertyBrowserUtils::colorValueText(QColor c) +{ + return QCoreApplication::translate("QtPropertyBrowserUtils", "[%1, %2, %3] (%4)") + .arg(c.red()).arg(c.green()).arg(c.blue()).arg(c.alpha()); +} + +QPixmap QtPropertyBrowserUtils::fontValuePixmap(const QFont &font) +{ + QFont f = font; + QImage img(16, 16, QImage::Format_ARGB32_Premultiplied); + img.fill(0); + QPainter p(&img); + p.setRenderHint(QPainter::TextAntialiasing, true); + p.setRenderHint(QPainter::Antialiasing, true); + f.setPointSize(13); + p.setFont(f); + QTextOption t; + t.setAlignment(Qt::AlignCenter); + p.drawText(QRect(0, 0, 16, 16), QString(QLatin1Char('A')), t); + return QPixmap::fromImage(img); +} + +QIcon QtPropertyBrowserUtils::fontValueIcon(const QFont &f) +{ + return QIcon(fontValuePixmap(f)); +} + +QString QtPropertyBrowserUtils::fontValueText(const QFont &f) +{ + return QCoreApplication::translate("QtPropertyBrowserUtils", "[%1, %2]") + .arg(f.family()).arg(f.pointSize()); +} + +QString QtPropertyBrowserUtils::dateFormat() +{ + QLocale loc; + QString format = loc.dateFormat(QLocale::ShortFormat); + // Change dd.MM.yy, MM/dd/yy to 4 digit years + if (format.count(QLatin1Char('y')) == 2) + format.insert(format.indexOf(QLatin1Char('y')), "yy"_L1); + return format; +} + +QString QtPropertyBrowserUtils::timeFormat() +{ + QLocale loc; + // ShortFormat is missing seconds on UNIX. + return loc.timeFormat(QLocale::LongFormat); +} + +QString QtPropertyBrowserUtils::dateTimeFormat() +{ + QString format = dateFormat(); + format += QLatin1Char(' '); + format += timeFormat(); + return format; +} + +QtBoolEdit::QtBoolEdit(QWidget *parent) : + QWidget(parent), + m_checkBox(new QCheckBox(this)), + m_textVisible(true) +{ + auto *lt = new QHBoxLayout; + if (QApplication::layoutDirection() == Qt::LeftToRight) + lt->setContentsMargins(4, 0, 0, 0); + else + lt->setContentsMargins(0, 0, 4, 0); + lt->addWidget(m_checkBox); + setLayout(lt); + connect(m_checkBox, &QAbstractButton::toggled, this, &QtBoolEdit::toggled); + setFocusProxy(m_checkBox); + m_checkBox->setText(tr("True")); +} + +void QtBoolEdit::setTextVisible(bool textVisible) +{ + if (m_textVisible == textVisible) + return; + + m_textVisible = textVisible; + if (m_textVisible) + m_checkBox->setText(isChecked() ? tr("True") : tr("False")); + else + m_checkBox->setText(QString()); +} + +Qt::CheckState QtBoolEdit::checkState() const +{ + return m_checkBox->checkState(); +} + +void QtBoolEdit::setCheckState(Qt::CheckState state) +{ + m_checkBox->setCheckState(state); +} + +bool QtBoolEdit::isChecked() const +{ + return m_checkBox->isChecked(); +} + +void QtBoolEdit::setChecked(bool c) +{ + m_checkBox->setChecked(c); + if (!m_textVisible) + return; + m_checkBox->setText(isChecked() ? tr("True") : tr("False")); +} + +bool QtBoolEdit::blockCheckBoxSignals(bool block) +{ + return m_checkBox->blockSignals(block); +} + +void QtBoolEdit::mousePressEvent(QMouseEvent *event) +{ + if (event->buttons() == Qt::LeftButton) { + m_checkBox->click(); + event->accept(); + } else { + QWidget::mousePressEvent(event); + } +} + +QT_END_NAMESPACE diff --git a/src/shared/qtpropertybrowser/qtpropertybrowserutils_p.h b/src/shared/qtpropertybrowser/qtpropertybrowserutils_p.h new file mode 100644 index 00000000000..d48788e6908 --- /dev/null +++ b/src/shared/qtpropertybrowser/qtpropertybrowserutils_p.h @@ -0,0 +1,97 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTPROPERTYBROWSERUTILS_H +#define QTPROPERTYBROWSERUTILS_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QMouseEvent; +class QCheckBox; +class QLineEdit; + +class QtCursorDatabase +{ +public: + QtCursorDatabase(); + void clear(); + + QStringList cursorShapeNames() const; + QMap cursorShapeIcons() const; + QString cursorToShapeName(const QCursor &cursor) const; + QIcon cursorToShapeIcon(const QCursor &cursor) const; + int cursorToValue(const QCursor &cursor) const; +#ifndef QT_NO_CURSOR + QCursor valueToCursor(int value) const; +#endif + + static QtCursorDatabase *instance(); + +private: + void appendCursor(Qt::CursorShape shape, const QString &name, const QIcon &icon); + QStringList m_cursorNames; + QMap m_cursorIcons; + QMap m_valueToCursorShape; + QMap m_cursorShapeToValue; +}; + +class QtPropertyBrowserUtils +{ +public: + static QPixmap brushValuePixmap(const QBrush &b); + static QIcon brushValueIcon(const QBrush &b); + static QString colorValueText(QColor c); + static QPixmap fontValuePixmap(const QFont &f); + static QIcon fontValueIcon(const QFont &f); + static QString fontValueText(const QFont &f); + static QString dateFormat(); + static QString timeFormat(); + static QString dateTimeFormat(); +}; + +class QtBoolEdit : public QWidget { + Q_OBJECT +public: + QtBoolEdit(QWidget *parent = 0); + + bool textVisible() const { return m_textVisible; } + void setTextVisible(bool textVisible); + + Qt::CheckState checkState() const; + void setCheckState(Qt::CheckState state); + + bool isChecked() const; + void setChecked(bool c); + + bool blockCheckBoxSignals(bool block); + +Q_SIGNALS: + void toggled(bool); + +protected: + void mousePressEvent(QMouseEvent * event) override; + +private: + QCheckBox *m_checkBox; + bool m_textVisible; +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtpropertybrowser/qtpropertymanager.cpp b/src/shared/qtpropertybrowser/qtpropertymanager.cpp new file mode 100644 index 00000000000..fc39789631b --- /dev/null +++ b/src/shared/qtpropertybrowser/qtpropertymanager.cpp @@ -0,0 +1,6406 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtpropertymanager_p.h" +#include "qtpropertybrowserutils_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#if defined(Q_CC_MSVC) +# pragma warning(disable: 4786) /* MS VS 6: truncating debug info after 255 characters */ +#endif + +QT_BEGIN_NAMESPACE + +using DisambiguatedTranslation = std::pair; + +static const QFont::Weight weightValues[] = { + QFont::Thin, QFont::ExtraLight, QFont::Light, QFont::Normal, QFont::Medium, QFont::DemiBold, + QFont::Bold, QFont::ExtraBold, QFont::Black +}; + +static int indexOfFontWeight(QFont::Weight w) +{ + auto it = std::find(std::begin(weightValues), std::end(weightValues), w); + return int(it - std::begin(weightValues)); +} + +static inline QFont::Weight weightFromIndex(int i) +{ + return weightValues[i]; +} + +template +static void setSimpleMinimumData(PrivateData *data, const Value &minVal) +{ + data->minVal = minVal; + if (data->maxVal < data->minVal) + data->maxVal = data->minVal; + + if (data->val < data->minVal) + data->val = data->minVal; +} + +template +static void setSimpleMaximumData(PrivateData *data, const Value &maxVal) +{ + data->maxVal = maxVal; + if (data->minVal > data->maxVal) + data->minVal = data->maxVal; + + if (data->val > data->maxVal) + data->val = data->maxVal; +} + +template +static void setSizeMinimumData(PrivateData *data, const Value &newMinVal) +{ + data->minVal = newMinVal; + if (data->maxVal.width() < data->minVal.width()) + data->maxVal.setWidth(data->minVal.width()); + if (data->maxVal.height() < data->minVal.height()) + data->maxVal.setHeight(data->minVal.height()); + + if (data->val.width() < data->minVal.width()) + data->val.setWidth(data->minVal.width()); + if (data->val.height() < data->minVal.height()) + data->val.setHeight(data->minVal.height()); +} + +template +static void setSizeMaximumData(PrivateData *data, const Value &newMaxVal) +{ + data->maxVal = newMaxVal; + if (data->minVal.width() > data->maxVal.width()) + data->minVal.setWidth(data->maxVal.width()); + if (data->minVal.height() > data->maxVal.height()) + data->minVal.setHeight(data->maxVal.height()); + + if (data->val.width() > data->maxVal.width()) + data->val.setWidth(data->maxVal.width()); + if (data->val.height() > data->maxVal.height()) + data->val.setHeight(data->maxVal.height()); +} + +template +static SizeValue qBoundSize(const SizeValue &minVal, const SizeValue &val, const SizeValue &maxVal) +{ + SizeValue croppedVal = val; + if (minVal.width() > val.width()) + croppedVal.setWidth(minVal.width()); + else if (maxVal.width() < val.width()) + croppedVal.setWidth(maxVal.width()); + + if (minVal.height() > val.height()) + croppedVal.setHeight(minVal.height()); + else if (maxVal.height() < val.height()) + croppedVal.setHeight(maxVal.height()); + + return croppedVal; +} + +// Match the exact signature of qBound for VS 6. +QSize qBound(QSize minVal, QSize val, QSize maxVal) +{ + return qBoundSize(minVal, val, maxVal); +} + +QSizeF qBound(QSizeF minVal, QSizeF val, QSizeF maxVal) +{ + return qBoundSize(minVal, val, maxVal); +} + +namespace { + +namespace { +template +void orderBorders(Value &minVal, Value &maxVal) +{ + if (minVal > maxVal) + qSwap(minVal, maxVal); +} + +template +static void orderSizeBorders(Value &minVal, Value &maxVal) +{ + Value fromSize = minVal; + Value toSize = maxVal; + if (fromSize.width() > toSize.width()) { + fromSize.setWidth(maxVal.width()); + toSize.setWidth(minVal.width()); + } + if (fromSize.height() > toSize.height()) { + fromSize.setHeight(maxVal.height()); + toSize.setHeight(minVal.height()); + } + minVal = fromSize; + maxVal = toSize; +} + +void orderBorders(QSize &minVal, QSize &maxVal) +{ + orderSizeBorders(minVal, maxVal); +} + +void orderBorders(QSizeF &minVal, QSizeF &maxVal) +{ + orderSizeBorders(minVal, maxVal); +} + +} +} +//////// + +template +static Value getData(const QHash &propertyMap, + Value PrivateData::*data, + const QtProperty *property, const Value &defaultValue = Value()) +{ + const auto it = propertyMap.constFind(property); + if (it == propertyMap.constEnd()) + return defaultValue; + return it.value().*data; +} + +template +static Value getValue(const QHash &propertyMap, + const QtProperty *property, const Value &defaultValue = Value()) +{ + return getData(propertyMap, &PrivateData::val, property, defaultValue); +} + +template +static Value getMinimum(const QHash &propertyMap, + const QtProperty *property, const Value &defaultValue = Value()) +{ + return getData(propertyMap, &PrivateData::minVal, property, defaultValue); +} + +template +static Value getMaximum(const QHash &propertyMap, + const QtProperty *property, const Value &defaultValue = Value()) +{ + return getData(propertyMap, &PrivateData::maxVal, property, defaultValue); +} + +template +static void setSimpleValue(QHash &propertyMap, + PropertyManager *manager, + void (PropertyManager::*propertyChangedSignal)(QtProperty *), + void (PropertyManager::*valueChangedSignal)(QtProperty *, ValueChangeParameter), + QtProperty *property, const Value &val) +{ + const auto it = propertyMap.find(property); + if (it == propertyMap.end()) + return; + + if (it.value() == val) + return; + + it.value() = val; + + emit (manager->*propertyChangedSignal)(property); + emit (manager->*valueChangedSignal)(property, val); +} + +template +static void setValueInRange(PropertyManager *manager, PropertyManagerPrivate *managerPrivate, + void (PropertyManager::*propertyChangedSignal)(QtProperty *), + void (PropertyManager::*valueChangedSignal)(QtProperty *, ValueChangeParameter), + QtProperty *property, const Value &val, + void (PropertyManagerPrivate::*setSubPropertyValue)(QtProperty *, ValueChangeParameter)) +{ + const auto it = managerPrivate->m_values.find(property); + if (it == managerPrivate->m_values.end()) + return; + + auto &data = it.value(); + + if (data.val == val) + return; + + const Value oldVal = data.val; + + data.val = qBound(data.minVal, val, data.maxVal); + + if (data.val == oldVal) + return; + + if (setSubPropertyValue) + (managerPrivate->*setSubPropertyValue)(property, data.val); + + emit (manager->*propertyChangedSignal)(property); + emit (manager->*valueChangedSignal)(property, data.val); +} + +template +static void setBorderValues(PropertyManager *manager, PropertyManagerPrivate *managerPrivate, + void (PropertyManager::*propertyChangedSignal)(QtProperty *), + void (PropertyManager::*valueChangedSignal)(QtProperty *, ValueChangeParameter), + void (PropertyManager::*rangeChangedSignal)(QtProperty *, ValueChangeParameter, ValueChangeParameter), + QtProperty *property, ValueChangeParameter minVal, ValueChangeParameter maxVal, + void (PropertyManagerPrivate::*setSubPropertyRange)(QtProperty *, + ValueChangeParameter, ValueChangeParameter, ValueChangeParameter)) +{ + const auto it = managerPrivate->m_values.find(property); + if (it == managerPrivate->m_values.end()) + return; + + Value fromVal = minVal; + Value toVal = maxVal; + orderBorders(fromVal, toVal); + + auto &data = it.value(); + + if (data.minVal == fromVal && data.maxVal == toVal) + return; + + const Value oldVal = data.val; + + data.setMinimumValue(fromVal); + data.setMaximumValue(toVal); + + emit (manager->*rangeChangedSignal)(property, data.minVal, data.maxVal); + + if (setSubPropertyRange) + (managerPrivate->*setSubPropertyRange)(property, data.minVal, data.maxVal, data.val); + + if (data.val == oldVal) + return; + + emit (manager->*propertyChangedSignal)(property); + emit (manager->*valueChangedSignal)(property, data.val); +} + +template +static void setBorderValue(PropertyManager *manager, PropertyManagerPrivate *managerPrivate, + void (PropertyManager::*propertyChangedSignal)(QtProperty *), + void (PropertyManager::*valueChangedSignal)(QtProperty *, ValueChangeParameter), + void (PropertyManager::*rangeChangedSignal)(QtProperty *, ValueChangeParameter, ValueChangeParameter), + QtProperty *property, + Value (PrivateData::*getRangeVal)() const, + void (PrivateData::*setRangeVal)(ValueChangeParameter), const Value &borderVal, + void (PropertyManagerPrivate::*setSubPropertyRange)(QtProperty *, + ValueChangeParameter, ValueChangeParameter, ValueChangeParameter)) +{ + const auto it = managerPrivate->m_values.find(property); + if (it == managerPrivate->m_values.end()) + return; + + PrivateData &data = it.value(); + + if ((data.*getRangeVal)() == borderVal) + return; + + const Value oldVal = data.val; + + (data.*setRangeVal)(borderVal); + + emit (manager->*rangeChangedSignal)(property, data.minVal, data.maxVal); + + if (setSubPropertyRange) + (managerPrivate->*setSubPropertyRange)(property, data.minVal, data.maxVal, data.val); + + if (data.val == oldVal) + return; + + emit (manager->*propertyChangedSignal)(property); + emit (manager->*valueChangedSignal)(property, data.val); +} + +template +static void setMinimumValue(PropertyManager *manager, PropertyManagerPrivate *managerPrivate, + void (PropertyManager::*propertyChangedSignal)(QtProperty *), + void (PropertyManager::*valueChangedSignal)(QtProperty *, ValueChangeParameter), + void (PropertyManager::*rangeChangedSignal)(QtProperty *, ValueChangeParameter, ValueChangeParameter), + QtProperty *property, const Value &minVal) +{ + void (PropertyManagerPrivate::*setSubPropertyRange)(QtProperty *, + ValueChangeParameter, ValueChangeParameter, ValueChangeParameter) = 0; + setBorderValue(manager, managerPrivate, + propertyChangedSignal, valueChangedSignal, rangeChangedSignal, + property, &PropertyManagerPrivate::Data::minimumValue, &PropertyManagerPrivate::Data::setMinimumValue, minVal, setSubPropertyRange); +} + +template +static void setMaximumValue(PropertyManager *manager, PropertyManagerPrivate *managerPrivate, + void (PropertyManager::*propertyChangedSignal)(QtProperty *), + void (PropertyManager::*valueChangedSignal)(QtProperty *, ValueChangeParameter), + void (PropertyManager::*rangeChangedSignal)(QtProperty *, ValueChangeParameter, ValueChangeParameter), + QtProperty *property, const Value &maxVal) +{ + void (PropertyManagerPrivate::*setSubPropertyRange)(QtProperty *, + ValueChangeParameter, ValueChangeParameter, ValueChangeParameter) = 0; + setBorderValue(manager, managerPrivate, + propertyChangedSignal, valueChangedSignal, rangeChangedSignal, + property, &PropertyManagerPrivate::Data::maximumValue, &PropertyManagerPrivate::Data::setMaximumValue, maxVal, setSubPropertyRange); +} + +class QtMetaEnumWrapper : public QObject +{ + Q_OBJECT + Q_PROPERTY(QSizePolicy::Policy policy READ policy) +public: + QSizePolicy::Policy policy() const { return QSizePolicy::Ignored; } +private: + QtMetaEnumWrapper(QObject *parent) : QObject(parent) {} +}; + +class QtMetaEnumProvider +{ +public: + QtMetaEnumProvider(); + + QStringList policyEnumNames() const { return m_policyEnumNames; } + QStringList languageEnumNames() const { return m_languageEnumNames; } + QStringList territoryEnumNames(QLocale::Language language) const { return m_territoryEnumNames.value(language); } + + QSizePolicy::Policy indexToSizePolicy(int index) const; + int sizePolicyToIndex(QSizePolicy::Policy policy) const; + + void indexToLocale(int languageIndex, int territoryIndex, QLocale::Language *language, QLocale::Territory *territory) const; + void localeToIndex(QLocale::Language language, QLocale::Territory territory, int *languageIndex, int *territoryIndex) const; + +private: + void initLocale(); + + QStringList m_policyEnumNames; + QStringList m_languageEnumNames; + QMap m_territoryEnumNames; + QMap m_indexToLanguage; + QMap m_languageToIndex; + QMap > m_indexToTerritory; + QMap > m_territoryToIndex; + QMetaEnum m_policyEnum; +}; + +static QList sortedTerritories(const QList &locales) +{ + QMultiMap nameToTerritory; + for (const QLocale &locale : locales) { + const auto territory = locale.territory(); + nameToTerritory.insert(QLocale::territoryToString(territory), territory); + } + return nameToTerritory.values(); +} + +void QtMetaEnumProvider::initLocale() +{ + QMultiMap nameToLanguage; + for (int l = QLocale::C, last = QLocale::LastLanguage; l <= last; ++l) { + const QLocale::Language language = static_cast(l); + QLocale locale(language); + if (locale.language() == language) + nameToLanguage.insert(QLocale::languageToString(language), language); + } + + const QLocale system = QLocale::system(); + if (!nameToLanguage.contains(QLocale::languageToString(system.language()))) + nameToLanguage.insert(QLocale::languageToString(system.language()), system.language()); + + const auto languages = nameToLanguage.values(); + for (QLocale::Language language : languages) { + auto locales = QLocale::matchingLocales(language, QLocale::AnyScript, + QLocale::AnyTerritory); + + if (!locales.isEmpty() && !m_languageToIndex.contains(language)) { + const auto territories = sortedTerritories(locales); + int langIdx = m_languageEnumNames.size(); + m_indexToLanguage[langIdx] = language; + m_languageToIndex[language] = langIdx; + QStringList territoryNames; + int territoryIdx = 0; + for (QLocale::Territory territory : territories) { + territoryNames << QLocale::territoryToString(territory); + m_indexToTerritory[langIdx][territoryIdx] = territory; + m_territoryToIndex[language][territory] = territoryIdx; + ++territoryIdx; + } + m_languageEnumNames << QLocale::languageToString(language); + m_territoryEnumNames[language] = territoryNames; + } + } +} + +QtMetaEnumProvider::QtMetaEnumProvider() +{ + QMetaProperty p; + + p = QtMetaEnumWrapper::staticMetaObject.property( + QtMetaEnumWrapper::staticMetaObject.propertyOffset() + 0); + m_policyEnum = p.enumerator(); + const int keyCount = m_policyEnum.keyCount(); + for (int i = 0; i < keyCount; i++) + m_policyEnumNames << QLatin1StringView(m_policyEnum.key(i)); + + initLocale(); +} + +QSizePolicy::Policy QtMetaEnumProvider::indexToSizePolicy(int index) const +{ + return static_cast(m_policyEnum.value(index)); +} + +int QtMetaEnumProvider::sizePolicyToIndex(QSizePolicy::Policy policy) const +{ + const int keyCount = m_policyEnum.keyCount(); + for (int i = 0; i < keyCount; i++) + if (indexToSizePolicy(i) == policy) + return i; + return -1; +} + +void QtMetaEnumProvider::indexToLocale(int languageIndex, int territoryIndex, QLocale::Language *language, QLocale::Territory *territory) const +{ + QLocale::Language l = QLocale::C; + QLocale::Territory c = QLocale::AnyTerritory; + if (m_indexToLanguage.contains(languageIndex)) { + l = m_indexToLanguage[languageIndex]; + if (m_indexToTerritory.contains(languageIndex) && m_indexToTerritory[languageIndex].contains(territoryIndex)) + c = m_indexToTerritory[languageIndex][territoryIndex]; + } + if (language) + *language = l; + if (territory) + *territory = c; +} + +void QtMetaEnumProvider::localeToIndex(QLocale::Language language, QLocale::Territory territory, int *languageIndex, int *territoryIndex) const +{ + int l = -1; + int c = -1; + if (m_languageToIndex.contains(language)) { + l = m_languageToIndex[language]; + if (m_territoryToIndex.contains(language) && m_territoryToIndex[language].contains(territory)) + c = m_territoryToIndex[language][territory]; + } + + if (languageIndex) + *languageIndex = l; + if (territoryIndex) + *territoryIndex = c; +} + +Q_GLOBAL_STATIC(QtMetaEnumProvider, metaEnumProvider) + +// QtGroupPropertyManager + +/*! + \class QtGroupPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtGroupPropertyManager provides and manages group properties. + + This class is intended to provide a grouping element without any value. + + \sa QtAbstractPropertyManager +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtGroupPropertyManager::QtGroupPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent) +{ + +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtGroupPropertyManager::~QtGroupPropertyManager() +{ + +} + +/*! + \reimp +*/ +bool QtGroupPropertyManager::hasValue(const QtProperty *property) const +{ + Q_UNUSED(property); + return false; +} + +/*! + \reimp +*/ +void QtGroupPropertyManager::initializeProperty(QtProperty *property) +{ + Q_UNUSED(property); +} + +/*! + \reimp +*/ +void QtGroupPropertyManager::uninitializeProperty(QtProperty *property) +{ + Q_UNUSED(property); +} + +// QtIntPropertyManager + +class QtIntPropertyManagerPrivate +{ + QtIntPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtIntPropertyManager) +public: + + struct Data + { + int val{0}; + int minVal{-INT_MAX}; + int maxVal{INT_MAX}; + int singleStep{1}; + int minimumValue() const { return minVal; } + int maximumValue() const { return maxVal; } + void setMinimumValue(int newMinVal) { setSimpleMinimumData(this, newMinVal); } + void setMaximumValue(int newMaxVal) { setSimpleMaximumData(this, newMaxVal); } + }; + + QHash m_values; +}; + +/*! + \class QtIntPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtIntPropertyManager provides and manages int properties. + + An int property has a current value, and a range specifying the + valid values. The range is defined by a minimum and a maximum + value. + + The property's value and range can be retrieved using the value(), + minimum() and maximum() functions, and can be set using the + setValue(), setMinimum() and setMaximum() slots. Alternatively, + the range can be defined in one go using the setRange() slot. + + In addition, QtIntPropertyManager provides the valueChanged() signal which + is emitted whenever a property created by this manager changes, + and the rangeChanged() signal which is emitted whenever such a + property changes its range of valid values. + + \sa QtAbstractPropertyManager, QtSpinBoxFactory, QtSliderFactory, QtScrollBarFactory +*/ + +/*! + \fn void QtIntPropertyManager::valueChanged(QtProperty *property, int value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtIntPropertyManager::rangeChanged(QtProperty *property, int minimum, int maximum) + + This signal is emitted whenever a property created by this manager + changes its range of valid values, passing a pointer to the + \a property and the new \a minimum and \a maximum values. + + \sa setRange() +*/ + +/*! + \fn void QtIntPropertyManager::singleStepChanged(QtProperty *property, int step) + + This signal is emitted whenever a property created by this manager + changes its single step property, passing a pointer to the + \a property and the new \a step value + + \sa setSingleStep() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtIntPropertyManager::QtIntPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtIntPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtIntPropertyManager::~QtIntPropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given property is not managed by this manager, this + function returns 0. + + \sa setValue() +*/ +int QtIntPropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property, 0); +} + +/*! + Returns the given \a property's minimum value. + + \sa setMinimum(), maximum(), setRange() +*/ +int QtIntPropertyManager::minimum(const QtProperty *property) const +{ + return getMinimum(d_ptr->m_values, property, 0); +} + +/*! + Returns the given \a property's maximum value. + + \sa setMaximum(), minimum(), setRange() +*/ +int QtIntPropertyManager::maximum(const QtProperty *property) const +{ + return getMaximum(d_ptr->m_values, property, 0); +} + +/*! + Returns the given \a property's step value. + + The step is typically used to increment or decrement a property value while pressing an arrow key. + + \sa setSingleStep() +*/ +int QtIntPropertyManager::singleStep(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtIntPropertyManagerPrivate::Data::singleStep, property, 0); +} + +/*! + \reimp +*/ +QString QtIntPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + return QString::number(it.value().val); +} + +/*! + \fn void QtIntPropertyManager::setValue(QtProperty *property, int value) + + Sets the value of the given \a property to \a value. + + If the specified \a value is not valid according to the given \a + property's range, the \a value is adjusted to the nearest valid + value within the range. + + \sa value(), setRange(), valueChanged() +*/ +void QtIntPropertyManager::setValue(QtProperty *property, int val) +{ + void (QtIntPropertyManagerPrivate::*setSubPropertyValue)(QtProperty *, int) = nullptr; + setValueInRange(this, d_ptr.data(), + &QtIntPropertyManager::propertyChanged, + &QtIntPropertyManager::valueChanged, + property, val, setSubPropertyValue); +} + +/*! + Sets the minimum value for the given \a property to \a minVal. + + When setting the minimum value, the maximum and current values are + adjusted if necessary (ensuring that the range remains valid and + that the current value is within the range). + + \sa minimum(), setRange(), rangeChanged() +*/ +void QtIntPropertyManager::setMinimum(QtProperty *property, int minVal) +{ + setMinimumValue(this, d_ptr.data(), + &QtIntPropertyManager::propertyChanged, + &QtIntPropertyManager::valueChanged, + &QtIntPropertyManager::rangeChanged, + property, minVal); +} + +/*! + Sets the maximum value for the given \a property to \a maxVal. + + When setting maximum value, the minimum and current values are + adjusted if necessary (ensuring that the range remains valid and + that the current value is within the range). + + \sa maximum(), setRange(), rangeChanged() +*/ +void QtIntPropertyManager::setMaximum(QtProperty *property, int maxVal) +{ + setMaximumValue(this, d_ptr.data(), + &QtIntPropertyManager::propertyChanged, + &QtIntPropertyManager::valueChanged, + &QtIntPropertyManager::rangeChanged, + property, maxVal); +} + +/*! + \fn void QtIntPropertyManager::setRange(QtProperty *property, int minimum, int maximum) + + Sets the range of valid values. + + This is a convenience function defining the range of valid values + in one go; setting the \a minimum and \a maximum values for the + given \a property with a single function call. + + When setting a new range, the current value is adjusted if + necessary (ensuring that the value remains within range). + + \sa setMinimum(), setMaximum(), rangeChanged() +*/ +void QtIntPropertyManager::setRange(QtProperty *property, int minVal, int maxVal) +{ + void (QtIntPropertyManagerPrivate::*setSubPropertyRange)(QtProperty *, int, int, int) = nullptr; + setBorderValues(this, d_ptr.data(), + &QtIntPropertyManager::propertyChanged, + &QtIntPropertyManager::valueChanged, + &QtIntPropertyManager::rangeChanged, + property, minVal, maxVal, setSubPropertyRange); +} + +/*! + Sets the step value for the given \a property to \a step. + + The step is typically used to increment or decrement a property value while pressing an arrow key. + + \sa singleStep() +*/ +void QtIntPropertyManager::setSingleStep(QtProperty *property, int step) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtIntPropertyManagerPrivate::Data data = it.value(); + + if (step < 0) + step = 0; + + if (data.singleStep == step) + return; + + data.singleStep = step; + + it.value() = data; + + emit singleStepChanged(property, data.singleStep); +} + +/*! + \reimp +*/ +void QtIntPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtIntPropertyManagerPrivate::Data(); +} + +/*! + \reimp +*/ +void QtIntPropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtDoublePropertyManager + +class QtDoublePropertyManagerPrivate +{ + QtDoublePropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtDoublePropertyManager) +public: + + struct Data + { + double val{0}; + double minVal{-DBL_MAX}; + double maxVal{DBL_MAX}; + double singleStep{1}; + int decimals{2}; + double minimumValue() const { return minVal; } + double maximumValue() const { return maxVal; } + void setMinimumValue(double newMinVal) { setSimpleMinimumData(this, newMinVal); } + void setMaximumValue(double newMaxVal) { setSimpleMaximumData(this, newMaxVal); } + }; + + QHash m_values; +}; + +/*! + \class QtDoublePropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtDoublePropertyManager provides and manages double properties. + + A double property has a current value, and a range specifying the + valid values. The range is defined by a minimum and a maximum + value. + + The property's value and range can be retrieved using the value(), + minimum() and maximum() functions, and can be set using the + setValue(), setMinimum() and setMaximum() slots. + Alternatively, the range can be defined in one go using the + setRange() slot. + + In addition, QtDoublePropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes, and the rangeChanged() signal which is emitted whenever + such a property changes its range of valid values. + + \sa QtAbstractPropertyManager, QtDoubleSpinBoxFactory +*/ + +/*! + \fn void QtDoublePropertyManager::valueChanged(QtProperty *property, double value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtDoublePropertyManager::rangeChanged(QtProperty *property, double minimum, double maximum) + + This signal is emitted whenever a property created by this manager + changes its range of valid values, passing a pointer to the + \a property and the new \a minimum and \a maximum values + + \sa setRange() +*/ + +/*! + \fn void QtDoublePropertyManager::decimalsChanged(QtProperty *property, int prec) + + This signal is emitted whenever a property created by this manager + changes its precision of value, passing a pointer to the + \a property and the new \a prec value + + \sa setDecimals() +*/ + +/*! + \fn void QtDoublePropertyManager::singleStepChanged(QtProperty *property, double step) + + This signal is emitted whenever a property created by this manager + changes its single step property, passing a pointer to the + \a property and the new \a step value + + \sa setSingleStep() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtDoublePropertyManager::QtDoublePropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtDoublePropertyManagerPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtDoublePropertyManager::~QtDoublePropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given property is not managed by this manager, this + function returns 0. + + \sa setValue() +*/ +double QtDoublePropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property, 0.0); +} + +/*! + Returns the given \a property's minimum value. + + \sa maximum(), setRange() +*/ +double QtDoublePropertyManager::minimum(const QtProperty *property) const +{ + return getMinimum(d_ptr->m_values, property, 0.0); +} + +/*! + Returns the given \a property's maximum value. + + \sa minimum(), setRange() +*/ +double QtDoublePropertyManager::maximum(const QtProperty *property) const +{ + return getMaximum(d_ptr->m_values, property, 0.0); +} + +/*! + Returns the given \a property's step value. + + The step is typically used to increment or decrement a property value while pressing an arrow key. + + \sa setSingleStep() +*/ +double QtDoublePropertyManager::singleStep(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtDoublePropertyManagerPrivate::Data::singleStep, property, 0); +} + +/*! + Returns the given \a property's precision, in decimals. + + \sa setDecimals() +*/ +int QtDoublePropertyManager::decimals(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtDoublePropertyManagerPrivate::Data::decimals, property, 0); +} + +/*! + \reimp +*/ +QString QtDoublePropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + return QString::number(it.value().val, 'f', it.value().decimals); +} + +/*! + \fn void QtDoublePropertyManager::setValue(QtProperty *property, double value) + + Sets the value of the given \a property to \a value. + + If the specified \a value is not valid according to the given + \a property's range, the \a value is adjusted to the nearest valid value + within the range. + + \sa value(), setRange(), valueChanged() +*/ +void QtDoublePropertyManager::setValue(QtProperty *property, double val) +{ + void (QtDoublePropertyManagerPrivate::*setSubPropertyValue)(QtProperty *, double) = nullptr; + setValueInRange(this, d_ptr.data(), + &QtDoublePropertyManager::propertyChanged, + &QtDoublePropertyManager::valueChanged, + property, val, setSubPropertyValue); +} + +/*! + Sets the step value for the given \a property to \a step. + + The step is typically used to increment or decrement a property value while pressing an arrow key. + + \sa singleStep() +*/ +void QtDoublePropertyManager::setSingleStep(QtProperty *property, double step) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtDoublePropertyManagerPrivate::Data data = it.value(); + + if (step < 0) + step = 0; + + if (data.singleStep == step) + return; + + data.singleStep = step; + + it.value() = data; + + emit singleStepChanged(property, data.singleStep); +} + +/*! + \fn void QtDoublePropertyManager::setDecimals(QtProperty *property, int prec) + + Sets the precision of the given \a property to \a prec. + + The valid decimal range is 0-13. The default is 2. + + \sa decimals() +*/ +void QtDoublePropertyManager::setDecimals(QtProperty *property, int prec) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtDoublePropertyManagerPrivate::Data data = it.value(); + + if (prec > 13) + prec = 13; + else if (prec < 0) + prec = 0; + + if (data.decimals == prec) + return; + + data.decimals = prec; + + it.value() = data; + + emit decimalsChanged(property, data.decimals); +} + +/*! + Sets the minimum value for the given \a property to \a minVal. + + When setting the minimum value, the maximum and current values are + adjusted if necessary (ensuring that the range remains valid and + that the current value is within in the range). + + \sa minimum(), setRange(), rangeChanged() +*/ +void QtDoublePropertyManager::setMinimum(QtProperty *property, double minVal) +{ + setMinimumValue(this, d_ptr.data(), + &QtDoublePropertyManager::propertyChanged, + &QtDoublePropertyManager::valueChanged, + &QtDoublePropertyManager::rangeChanged, + property, minVal); +} + +/*! + Sets the maximum value for the given \a property to \a maxVal. + + When setting the maximum value, the minimum and current values are + adjusted if necessary (ensuring that the range remains valid and + that the current value is within in the range). + + \sa maximum(), setRange(), rangeChanged() +*/ +void QtDoublePropertyManager::setMaximum(QtProperty *property, double maxVal) +{ + setMaximumValue(this, d_ptr.data(), + &QtDoublePropertyManager::propertyChanged, + &QtDoublePropertyManager::valueChanged, + &QtDoublePropertyManager::rangeChanged, + property, maxVal); +} + +/*! + \fn void QtDoublePropertyManager::setRange(QtProperty *property, double minimum, double maximum) + + Sets the range of valid values. + + This is a convenience function defining the range of valid values + in one go; setting the \a minimum and \a maximum values for the + given \a property with a single function call. + + When setting a new range, the current value is adjusted if + necessary (ensuring that the value remains within range). + + \sa setMinimum(), setMaximum(), rangeChanged() +*/ +void QtDoublePropertyManager::setRange(QtProperty *property, double minVal, double maxVal) +{ + void (QtDoublePropertyManagerPrivate::*setSubPropertyRange)(QtProperty *, double, double, double) = nullptr; + setBorderValues(this, d_ptr.data(), + &QtDoublePropertyManager::propertyChanged, + &QtDoublePropertyManager::valueChanged, + &QtDoublePropertyManager::rangeChanged, + property, minVal, maxVal, setSubPropertyRange); +} + +/*! + \reimp +*/ +void QtDoublePropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtDoublePropertyManagerPrivate::Data(); +} + +/*! + \reimp +*/ +void QtDoublePropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtStringPropertyManager + +class QtStringPropertyManagerPrivate +{ + QtStringPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtStringPropertyManager) +public: + + struct Data + { + QString val; + QRegularExpression regExp; + }; + + QHash m_values; +}; + +/*! + \class QtStringPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtStringPropertyManager provides and manages QString properties. + + A string property's value can be retrieved using the value() + function, and set using the setValue() slot. + + The current value can be checked against a regular expression. To + set the regular expression use the setRegExp() slot, use the + regExp() function to retrieve the currently set expression. + + In addition, QtStringPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes, and the regExpChanged() signal which is emitted whenever + such a property changes its currently set regular expression. + + \sa QtAbstractPropertyManager, QtLineEditFactory +*/ + +/*! + \fn void QtStringPropertyManager::valueChanged(QtProperty *property, const QString &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the + new \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtStringPropertyManager::regExpChanged(QtProperty *property, const QRegularExpression ®Exp) + + This signal is emitted whenever a property created by this manager + changes its currenlty set regular expression, passing a pointer to + the \a property and the new \a regExp as parameters. + + \sa setRegExp() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtStringPropertyManager::QtStringPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtStringPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtStringPropertyManager::~QtStringPropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given property is not managed by this manager, this + function returns an empty string. + + \sa setValue() +*/ +QString QtStringPropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's currently set regular expression. + + If the given \a property is not managed by this manager, this + function returns an empty expression. + + \sa setRegExp() +*/ +QRegularExpression QtStringPropertyManager::regExp(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtStringPropertyManagerPrivate::Data::regExp, property, QRegularExpression()); +} + +/*! + \reimp +*/ +QString QtStringPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + return it.value().val; +} + +/*! + \fn void QtStringPropertyManager::setValue(QtProperty *property, const QString &value) + + Sets the value of the given \a property to \a value. + + If the specified \a value doesn't match the given \a property's + regular expression, this function does nothing. + + \sa value(), setRegExp(), valueChanged() +*/ +void QtStringPropertyManager::setValue(QtProperty *property, const QString &val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtStringPropertyManagerPrivate::Data data = it.value(); + + if (data.val == val) + return; + + if (data.regExp.isValid() && !data.regExp.pattern().isEmpty() + && !data.regExp.match(val).hasMatch()) { + return; + } + + data.val = val; + + it.value() = data; + + emit propertyChanged(property); + emit valueChanged(property, data.val); +} + +/*! + Sets the regular expression of the given \a property to \a regExp. + + \sa regExp(), setValue(), regExpChanged() +*/ +void QtStringPropertyManager::setRegExp(QtProperty *property, const QRegularExpression ®Exp) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtStringPropertyManagerPrivate::Data data = it.value() ; + + if (data.regExp == regExp) + return; + + data.regExp = regExp; + + it.value() = data; + + emit regExpChanged(property, data.regExp); +} + +/*! + \reimp +*/ +void QtStringPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtStringPropertyManagerPrivate::Data(); +} + +/*! + \reimp +*/ +void QtStringPropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtBoolPropertyManager +// Return an icon containing a check box indicator +static QIcon drawCheckBox(bool value) +{ + QStyleOptionButton opt; + opt.state |= value ? QStyle::State_On : QStyle::State_Off; + opt.state |= QStyle::State_Enabled; + const QStyle *style = QApplication::style(); + // Figure out size of an indicator and make sure it is not scaled down in a list view item + // by making the pixmap as big as a list view icon and centering the indicator in it. + // (if it is smaller, it can't be helped) + const int indicatorWidth = style->pixelMetric(QStyle::PM_IndicatorWidth, &opt); + const int indicatorHeight = style->pixelMetric(QStyle::PM_IndicatorHeight, &opt); + const int listViewIconSize = indicatorWidth; + const int pixmapWidth = indicatorWidth; + const int pixmapHeight = qMax(indicatorHeight, listViewIconSize); + + opt.rect = QRect(0, 0, indicatorWidth, indicatorHeight); + QPixmap pixmap = QPixmap(pixmapWidth, pixmapHeight); + pixmap.fill(Qt::transparent); + { + // Center? + const int xoff = (pixmapWidth > indicatorWidth) ? (pixmapWidth - indicatorWidth) / 2 : 0; + const int yoff = (pixmapHeight > indicatorHeight) ? (pixmapHeight - indicatorHeight) / 2 : 0; + QPainter painter(&pixmap); + painter.translate(xoff, yoff); + style->drawPrimitive(QStyle::PE_IndicatorCheckBox, &opt, &painter); + } + return QIcon(pixmap); +} + +class QtBoolPropertyManagerPrivate +{ + QtBoolPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtBoolPropertyManager) +public: + QtBoolPropertyManagerPrivate(); + + QHash m_values; + const QIcon m_checkedIcon; + const QIcon m_uncheckedIcon; +}; + +QtBoolPropertyManagerPrivate::QtBoolPropertyManagerPrivate() : + m_checkedIcon(drawCheckBox(true)), + m_uncheckedIcon(drawCheckBox(false)) +{ +} + +/*! + \class QtBoolPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtBoolPropertyManager class provides and manages boolean properties. + + The property's value can be retrieved using the value() function, + and set using the setValue() slot. + + In addition, QtBoolPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes. + + \sa QtAbstractPropertyManager, QtCheckBoxFactory +*/ + +/*! + \fn void QtBoolPropertyManager::valueChanged(QtProperty *property, bool value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the + new \a value as parameters. +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtBoolPropertyManager::QtBoolPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtBoolPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtBoolPropertyManager::~QtBoolPropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by \e this manager, this + function returns false. + + \sa setValue() +*/ +bool QtBoolPropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, false); +} + +/*! + \reimp +*/ +QString QtBoolPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + static const QString trueText = tr("True"); + static const QString falseText = tr("False"); + return it.value() ? trueText : falseText; +} + +/*! + \reimp +*/ +QIcon QtBoolPropertyManager::valueIcon(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + return it.value() ? d_ptr->m_checkedIcon : d_ptr->m_uncheckedIcon; +} + +/*! + \fn void QtBoolPropertyManager::setValue(QtProperty *property, bool value) + + Sets the value of the given \a property to \a value. + + \sa value() +*/ +void QtBoolPropertyManager::setValue(QtProperty *property, bool val) +{ + setSimpleValue(d_ptr->m_values, this, + &QtBoolPropertyManager::propertyChanged, + &QtBoolPropertyManager::valueChanged, + property, val); +} + +/*! + \reimp +*/ +void QtBoolPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = false; +} + +/*! + \reimp +*/ +void QtBoolPropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtDatePropertyManager + +class QtDatePropertyManagerPrivate +{ + QtDatePropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtDatePropertyManager) +public: + explicit QtDatePropertyManagerPrivate(QtDatePropertyManager *q); + + struct Data + { + QDate val{QDate::currentDate()}; + QDate minVal{QDate(1752, 9, 14)}; + QDate maxVal{QDate(9999, 12, 31)}; + QDate minimumValue() const { return minVal; } + QDate maximumValue() const { return maxVal; } + void setMinimumValue(QDate newMinVal) { setSimpleMinimumData(this, newMinVal); } + void setMaximumValue(QDate newMaxVal) { setSimpleMaximumData(this, newMaxVal); } + }; + + QString m_format; + + QHash m_values; +}; + +QtDatePropertyManagerPrivate::QtDatePropertyManagerPrivate(QtDatePropertyManager *q) : + q_ptr(q), + m_format(QtPropertyBrowserUtils::dateFormat()) +{ +} + +/*! + \class QtDatePropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtDatePropertyManager provides and manages QDate properties. + + A date property has a current value, and a range specifying the + valid dates. The range is defined by a minimum and a maximum + value. + + The property's values can be retrieved using the minimum(), + maximum() and value() functions, and can be set using the + setMinimum(), setMaximum() and setValue() slots. Alternatively, + the range can be defined in one go using the setRange() slot. + + In addition, QtDatePropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes, and the rangeChanged() signal which is emitted whenever + such a property changes its range of valid dates. + + \sa QtAbstractPropertyManager, QtDateEditFactory, QtDateTimePropertyManager +*/ + +/*! + \fn void QtDatePropertyManager::valueChanged(QtProperty *property, QDate value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtDatePropertyManager::rangeChanged(QtProperty *property, QDate minimum, QDate maximum) + + This signal is emitted whenever a property created by this manager + changes its range of valid dates, passing a pointer to the \a + property and the new \a minimum and \a maximum dates. + + \sa setRange() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtDatePropertyManager::QtDatePropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtDatePropertyManagerPrivate(this)) +{ +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtDatePropertyManager::~QtDatePropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by \e this manager, this + function returns an invalid date. + + \sa setValue() +*/ +QDate QtDatePropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's minimum date. + + \sa maximum(), setRange() +*/ +QDate QtDatePropertyManager::minimum(const QtProperty *property) const +{ + return getMinimum(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's maximum date. + + \sa minimum(), setRange() +*/ +QDate QtDatePropertyManager::maximum(const QtProperty *property) const +{ + return getMaximum(d_ptr->m_values, property); +} + +/*! + \reimp +*/ +QString QtDatePropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + return it.value().val.toString(d_ptr->m_format); +} + +/*! + \fn void QtDatePropertyManager::setValue(QtProperty *property, QDate value) + + Sets the value of the given \a property to \a value. + + If the specified \a value is not a valid date according to the + given \a property's range, the value is adjusted to the nearest + valid value within the range. + + \sa value(), setRange(), valueChanged() +*/ +void QtDatePropertyManager::setValue(QtProperty *property, QDate val) +{ + void (QtDatePropertyManagerPrivate::*setSubPropertyValue)(QtProperty *, QDate) = nullptr; + setValueInRange(this, d_ptr.data(), + &QtDatePropertyManager::propertyChanged, + &QtDatePropertyManager::valueChanged, + property, val, setSubPropertyValue); +} + +/*! + Sets the minimum value for the given \a property to \a minVal. + + When setting the minimum value, the maximum and current values are + adjusted if necessary (ensuring that the range remains valid and + that the current value is within in the range). + + \sa minimum(), setRange() +*/ +void QtDatePropertyManager::setMinimum(QtProperty *property, QDate minVal) +{ + setMinimumValue(this, d_ptr.data(), + &QtDatePropertyManager::propertyChanged, + &QtDatePropertyManager::valueChanged, + &QtDatePropertyManager::rangeChanged, + property, minVal); +} + +/*! + Sets the maximum value for the given \a property to \a maxVal. + + When setting the maximum value, the minimum and current + values are adjusted if necessary (ensuring that the range remains + valid and that the current value is within in the range). + + \sa maximum(), setRange() +*/ +void QtDatePropertyManager::setMaximum(QtProperty *property, QDate maxVal) +{ + setMaximumValue(this, d_ptr.data(), + &QtDatePropertyManager::propertyChanged, + &QtDatePropertyManager::valueChanged, + &QtDatePropertyManager::rangeChanged, + property, maxVal); +} + +/*! + \fn void QtDatePropertyManager::setRange(QtProperty *property, QDate minimum, QDate maximum) + + Sets the range of valid dates. + + This is a convenience function defining the range of valid dates + in one go; setting the \a minimum and \a maximum values for the + given \a property with a single function call. + + When setting a new date range, the current value is adjusted if + necessary (ensuring that the value remains in date range). + + \sa setMinimum(), setMaximum(), rangeChanged() +*/ +void QtDatePropertyManager::setRange(QtProperty *property, QDate minVal, QDate maxVal) +{ + void (QtDatePropertyManagerPrivate::*setSubPropertyRange)(QtProperty *, QDate, QDate, QDate) = nullptr; + setBorderValues(this, d_ptr.data(), + &QtDatePropertyManager::propertyChanged, + &QtDatePropertyManager::valueChanged, + &QtDatePropertyManager::rangeChanged, + property, minVal, maxVal, setSubPropertyRange); +} + +/*! + \reimp +*/ +void QtDatePropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtDatePropertyManagerPrivate::Data(); +} + +/*! + \reimp +*/ +void QtDatePropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtTimePropertyManager + +class QtTimePropertyManagerPrivate +{ + QtTimePropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtTimePropertyManager) +public: + explicit QtTimePropertyManagerPrivate(QtTimePropertyManager *q); + + const QString m_format; + + QHash m_values; +}; + +QtTimePropertyManagerPrivate::QtTimePropertyManagerPrivate(QtTimePropertyManager *q) : + q_ptr(q), + m_format(QtPropertyBrowserUtils::timeFormat()) +{ +} + +/*! + \class QtTimePropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtTimePropertyManager provides and manages QTime properties. + + A time property's value can be retrieved using the value() + function, and set using the setValue() slot. + + In addition, QtTimePropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes. + + \sa QtAbstractPropertyManager, QtTimeEditFactory +*/ + +/*! + \fn void QtTimePropertyManager::valueChanged(QtProperty *property, QTime value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the + new \a value as parameters. + + \sa setValue() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtTimePropertyManager::QtTimePropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtTimePropertyManagerPrivate(this)) +{ +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtTimePropertyManager::~QtTimePropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given property is not managed by this manager, this + function returns an invalid time object. + + \sa setValue() +*/ +QTime QtTimePropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QTime()); +} + +/*! + \reimp +*/ +QString QtTimePropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + return it.value().toString(d_ptr->m_format); +} + +/*! + \fn void QtTimePropertyManager::setValue(QtProperty *property, QTime value) + + Sets the value of the given \a property to \a value. + + \sa value(), valueChanged() +*/ +void QtTimePropertyManager::setValue(QtProperty *property, QTime val) +{ + setSimpleValue(d_ptr->m_values, this, + &QtTimePropertyManager::propertyChanged, + &QtTimePropertyManager::valueChanged, + property, val); +} + +/*! + \reimp +*/ +void QtTimePropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QTime::currentTime(); +} + +/*! + \reimp +*/ +void QtTimePropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtDateTimePropertyManager + +class QtDateTimePropertyManagerPrivate +{ + QtDateTimePropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtDateTimePropertyManager) +public: + explicit QtDateTimePropertyManagerPrivate(QtDateTimePropertyManager *q); + + const QString m_format; + + QHash m_values; +}; + +QtDateTimePropertyManagerPrivate::QtDateTimePropertyManagerPrivate(QtDateTimePropertyManager *q) : + q_ptr(q), + m_format(QtPropertyBrowserUtils::dateTimeFormat()) +{ +} + +/*! \class QtDateTimePropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtDateTimePropertyManager provides and manages QDateTime properties. + + A date and time property has a current value which can be + retrieved using the value() function, and set using the setValue() + slot. In addition, QtDateTimePropertyManager provides the + valueChanged() signal which is emitted whenever a property created + by this manager changes. + + \sa QtAbstractPropertyManager, QtDateTimeEditFactory, QtDatePropertyManager +*/ + +/*! + \fn void QtDateTimePropertyManager::valueChanged(QtProperty *property, const QDateTime &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtDateTimePropertyManager::QtDateTimePropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtDateTimePropertyManagerPrivate(this)) +{ +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtDateTimePropertyManager::~QtDateTimePropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns an invalid QDateTime object. + + \sa setValue() +*/ +QDateTime QtDateTimePropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QDateTime()); +} + +/*! + \reimp +*/ +QString QtDateTimePropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + return it.value().toString(d_ptr->m_format); +} + +/*! + \fn void QtDateTimePropertyManager::setValue(QtProperty *property, const QDateTime &value) + + Sets the value of the given \a property to \a value. + + \sa value(), valueChanged() +*/ +void QtDateTimePropertyManager::setValue(QtProperty *property, const QDateTime &val) +{ + setSimpleValue(d_ptr->m_values, this, + &QtDateTimePropertyManager::propertyChanged, + &QtDateTimePropertyManager::valueChanged, + property, val); +} + +/*! + \reimp +*/ +void QtDateTimePropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QDateTime::currentDateTime(); +} + +/*! + \reimp +*/ +void QtDateTimePropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtKeySequencePropertyManager + +class QtKeySequencePropertyManagerPrivate +{ + QtKeySequencePropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtKeySequencePropertyManager) +public: + + QString m_format; + + QHash m_values; +}; + +/*! \class QtKeySequencePropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtKeySequencePropertyManager provides and manages QKeySequence properties. + + A key sequence's value can be retrieved using the value() + function, and set using the setValue() slot. + + In addition, QtKeySequencePropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes. + + \sa QtAbstractPropertyManager +*/ + +/*! + \fn void QtKeySequencePropertyManager::valueChanged(QtProperty *property, const QKeySequence &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtKeySequencePropertyManager::QtKeySequencePropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtKeySequencePropertyManagerPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtKeySequencePropertyManager::~QtKeySequencePropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns an empty QKeySequence object. + + \sa setValue() +*/ +QKeySequence QtKeySequencePropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QKeySequence()); +} + +/*! + \reimp +*/ +QString QtKeySequencePropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + return it.value().toString(QKeySequence::NativeText); +} + +/*! + \fn void QtKeySequencePropertyManager::setValue(QtProperty *property, const QKeySequence &value) + + Sets the value of the given \a property to \a value. + + \sa value(), valueChanged() +*/ +void QtKeySequencePropertyManager::setValue(QtProperty *property, const QKeySequence &val) +{ + setSimpleValue(d_ptr->m_values, this, + &QtKeySequencePropertyManager::propertyChanged, + &QtKeySequencePropertyManager::valueChanged, + property, val); +} + +/*! + \reimp +*/ +void QtKeySequencePropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QKeySequence(); +} + +/*! + \reimp +*/ +void QtKeySequencePropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtCharPropertyManager + +class QtCharPropertyManagerPrivate +{ + QtCharPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtCharPropertyManager) +public: + + QHash m_values; +}; + +/*! \class QtCharPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtCharPropertyManager provides and manages QChar properties. + + A char's value can be retrieved using the value() + function, and set using the setValue() slot. + + In addition, QtCharPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes. + + \sa QtAbstractPropertyManager +*/ + +/*! + \fn void QtCharPropertyManager::valueChanged(QtProperty *property, const QChar &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtCharPropertyManager::QtCharPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtCharPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtCharPropertyManager::~QtCharPropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns an null QChar object. + + \sa setValue() +*/ +QChar QtCharPropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QChar()); +} + +/*! + \reimp +*/ +QString QtCharPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + const QChar c = it.value(); + return c.isNull() ? QString() : QString(c); +} + +/*! + \fn void QtCharPropertyManager::setValue(QtProperty *property, const QChar &value) + + Sets the value of the given \a property to \a value. + + \sa value(), valueChanged() +*/ +void QtCharPropertyManager::setValue(QtProperty *property, const QChar &val) +{ + setSimpleValue(d_ptr->m_values, this, + &QtCharPropertyManager::propertyChanged, + &QtCharPropertyManager::valueChanged, + property, val); +} + +/*! + \reimp +*/ +void QtCharPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QChar(); +} + +/*! + \reimp +*/ +void QtCharPropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtLocalePropertyManager + +class QtLocalePropertyManagerPrivate +{ + QtLocalePropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtLocalePropertyManager) +public: + + void slotEnumChanged(QtProperty *property, int value); + void slotPropertyDestroyed(QtProperty *property); + + QHash m_values; + + QtEnumPropertyManager *m_enumPropertyManager = nullptr; + + QHash m_propertyToLanguage; + QHash m_propertyToTerritory; + + QHash m_languageToProperty; + QHash m_territoryToProperty; +}; + +void QtLocalePropertyManagerPrivate::slotEnumChanged(QtProperty *property, int value) +{ + if (QtProperty *prop = m_languageToProperty.value(property, nullptr)) { + const QLocale loc = m_values[prop]; + QLocale::Language newLanguage = loc.language(); + QLocale::Territory newTerritory = loc.territory(); + metaEnumProvider()->indexToLocale(value, 0, &newLanguage, 0); + QLocale newLoc(newLanguage, newTerritory); + q_ptr->setValue(prop, newLoc); + } else if (QtProperty *prop = m_territoryToProperty.value(property, nullptr)) { + const QLocale loc = m_values[prop]; + QLocale::Language newLanguage = loc.language(); + QLocale::Territory newTerritory = loc.territory(); + metaEnumProvider()->indexToLocale(m_enumPropertyManager->value(m_propertyToLanguage.value(prop)), value, &newLanguage, &newTerritory); + QLocale newLoc(newLanguage, newTerritory); + q_ptr->setValue(prop, newLoc); + } +} + +void QtLocalePropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *subProp = m_languageToProperty.value(property, nullptr)) { + m_propertyToLanguage[subProp] = nullptr; + m_languageToProperty.remove(property); + } else if (QtProperty *subProp = m_territoryToProperty.value(property, nullptr)) { + m_propertyToTerritory[subProp] = nullptr; + m_territoryToProperty.remove(property); + } +} + +/*! + \class QtLocalePropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtLocalePropertyManager provides and manages QLocale properties. + + A locale property has nested \e language and \e territory + subproperties. The top-level property's value can be retrieved + using the value() function, and set using the setValue() slot. + + The subproperties are created by QtEnumPropertyManager object. + These submanager can be retrieved using the subEnumPropertyManager() + function. In order to provide editing widgets for the subproperties + in a property browser widget, this manager must be associated with editor factory. + + In addition, QtLocalePropertyManager provides the valueChanged() + signal which is emitted whenever a property created by this + manager changes. + + \sa QtAbstractPropertyManager, QtEnumPropertyManager +*/ + +/*! + \fn void QtLocalePropertyManager::valueChanged(QtProperty *property, const QLocale &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the + new \a value as parameters. + + \sa setValue() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtLocalePropertyManager::QtLocalePropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtLocalePropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_enumPropertyManager = new QtEnumPropertyManager(this); + connect(d_ptr->m_enumPropertyManager, &QtEnumPropertyManager::valueChanged, this, + [this](QtProperty *property, int value) { d_ptr->slotEnumChanged(property, value); }); + connect(d_ptr->m_enumPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtLocalePropertyManager::~QtLocalePropertyManager() +{ + clear(); +} + +/*! + Returns the manager that creates the nested \e language + and \e territory subproperties. + + In order to provide editing widgets for the mentioned subproperties + in a property browser widget, this manager must be associated with + an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtEnumPropertyManager *QtLocalePropertyManager::subEnumPropertyManager() const +{ + return d_ptr->m_enumPropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given property is not managed by this manager, this + function returns the default locale. + + \sa setValue() +*/ +QLocale QtLocalePropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QLocale()); +} + +/*! + \reimp +*/ +QString QtLocalePropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + const QLocale loc = it.value(); + + int langIdx = 0; + int territoryIdx = 0; + const QtMetaEnumProvider *me = metaEnumProvider(); + me->localeToIndex(loc.language(), loc.territory(), &langIdx, &territoryIdx); + if (langIdx < 0) { + qWarning("QtLocalePropertyManager::valueText: Unknown language %d", loc.language()); + return tr(""); + } + const QString languageName = me->languageEnumNames().at(langIdx); + if (territoryIdx < 0) { + qWarning("QtLocalePropertyManager::valueText: Unknown territory %d for %s", loc.territory(), qPrintable(languageName)); + return languageName; + } + const QString territoryName = me->territoryEnumNames(loc.language()).at(territoryIdx); + return tr("%1, %2").arg(languageName, territoryName); +} + +/*! + \fn void QtLocalePropertyManager::setValue(QtProperty *property, const QLocale &value) + + Sets the value of the given \a property to \a value. Nested + properties are updated automatically. + + \sa value(), valueChanged() +*/ +void QtLocalePropertyManager::setValue(QtProperty *property, const QLocale &val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + const QLocale loc = it.value(); + if (loc == val) + return; + + it.value() = val; + + int langIdx = 0; + int territoryIdx = 0; + metaEnumProvider()->localeToIndex(val.language(), val.territory(), &langIdx, &territoryIdx); + if (loc.language() != val.language()) { + d_ptr->m_enumPropertyManager->setValue(d_ptr->m_propertyToLanguage.value(property), langIdx); + d_ptr->m_enumPropertyManager->setEnumNames(d_ptr->m_propertyToTerritory.value(property), + metaEnumProvider()->territoryEnumNames(val.language())); + } + d_ptr->m_enumPropertyManager->setValue(d_ptr->m_propertyToTerritory.value(property), territoryIdx); + + emit propertyChanged(property); + emit valueChanged(property, val); +} + +/*! + \reimp +*/ +void QtLocalePropertyManager::initializeProperty(QtProperty *property) +{ + QLocale val; + d_ptr->m_values[property] = val; + + int langIdx = 0; + int territoryIdx = 0; + metaEnumProvider()->localeToIndex(val.language(), val.territory(), &langIdx, &territoryIdx); + + QtProperty *languageProp = d_ptr->m_enumPropertyManager->addProperty(); + languageProp->setPropertyName(tr("Language")); + d_ptr->m_enumPropertyManager->setEnumNames(languageProp, metaEnumProvider()->languageEnumNames()); + d_ptr->m_enumPropertyManager->setValue(languageProp, langIdx); + d_ptr->m_propertyToLanguage[property] = languageProp; + d_ptr->m_languageToProperty[languageProp] = property; + property->addSubProperty(languageProp); + + QtProperty *territoryProp = d_ptr->m_enumPropertyManager->addProperty(); + territoryProp->setPropertyName(tr("Territory")); + d_ptr->m_enumPropertyManager->setEnumNames(territoryProp, metaEnumProvider()->territoryEnumNames(val.language())); + d_ptr->m_enumPropertyManager->setValue(territoryProp, territoryIdx); + d_ptr->m_propertyToTerritory[property] = territoryProp; + d_ptr->m_territoryToProperty[territoryProp] = property; + property->addSubProperty(territoryProp); +} + +/*! + \reimp +*/ +void QtLocalePropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *languageProp = d_ptr->m_propertyToLanguage[property]; + if (languageProp) { + d_ptr->m_languageToProperty.remove(languageProp); + delete languageProp; + } + d_ptr->m_propertyToLanguage.remove(property); + + QtProperty *territoryProp = d_ptr->m_propertyToTerritory[property]; + if (territoryProp) { + d_ptr->m_territoryToProperty.remove(territoryProp); + delete territoryProp; + } + d_ptr->m_propertyToTerritory.remove(property); + + d_ptr->m_values.remove(property); +} + +// QtPointPropertyManager + +class QtPointPropertyManagerPrivate +{ + QtPointPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtPointPropertyManager) +public: + + void slotIntChanged(QtProperty *property, int value); + void slotPropertyDestroyed(QtProperty *property); + + QHash m_values; + + QtIntPropertyManager *m_intPropertyManager; + + QHash m_propertyToX; + QHash m_propertyToY; + + QHash m_xToProperty; + QHash m_yToProperty; +}; + +void QtPointPropertyManagerPrivate::slotIntChanged(QtProperty *property, int value) +{ + if (QtProperty *xprop = m_xToProperty.value(property, nullptr)) { + QPoint p = m_values[xprop]; + p.setX(value); + q_ptr->setValue(xprop, p); + } else if (QtProperty *yprop = m_yToProperty.value(property, nullptr)) { + QPoint p = m_values[yprop]; + p.setY(value); + q_ptr->setValue(yprop, p); + } +} + +void QtPointPropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *pointProp = m_xToProperty.value(property, nullptr)) { + m_propertyToX[pointProp] = nullptr; + m_xToProperty.remove(property); + } else if (QtProperty *pointProp = m_yToProperty.value(property, nullptr)) { + m_propertyToY[pointProp] = nullptr; + m_yToProperty.remove(property); + } +} + +/*! \class QtPointPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtPointPropertyManager provides and manages QPoint properties. + + A point property has nested \e x and \e y subproperties. The + top-level property's value can be retrieved using the value() + function, and set using the setValue() slot. + + The subproperties are created by a QtIntPropertyManager object. This + manager can be retrieved using the subIntPropertyManager() function. In + order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + In addition, QtPointPropertyManager provides the valueChanged() signal which + is emitted whenever a property created by this manager changes. + + \sa QtAbstractPropertyManager, QtIntPropertyManager, QtPointFPropertyManager +*/ + +/*! + \fn void QtPointPropertyManager::valueChanged(QtProperty *property, const QPoint &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the + new \a value as parameters. + + \sa setValue() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtPointPropertyManager::QtPointPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtPointPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_intPropertyManager = new QtIntPropertyManager(this); + connect(d_ptr->m_intPropertyManager, &QtIntPropertyManager::valueChanged, this, + [this](QtProperty *property, int value) { d_ptr->slotIntChanged(property, value); }); + connect(d_ptr->m_intPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtPointPropertyManager::~QtPointPropertyManager() +{ + clear(); +} + +/*! + Returns the manager that creates the nested \e x and \e y + subproperties. + + In order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtIntPropertyManager *QtPointPropertyManager::subIntPropertyManager() const +{ + return d_ptr->m_intPropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns a point with coordinates (0, 0). + + \sa setValue() +*/ +QPoint QtPointPropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QPoint()); +} + +/*! + \reimp +*/ +QString QtPointPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + const QPoint v = it.value(); + return tr("(%1, %2)").arg(v.x()).arg(v.y()); +} + +/*! + \fn void QtPointPropertyManager::setValue(QtProperty *property, const QPoint &value) + + Sets the value of the given \a property to \a value. Nested + properties are updated automatically. + + \sa value(), valueChanged() +*/ +void QtPointPropertyManager::setValue(QtProperty *property, QPoint val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + if (it.value() == val) + return; + + it.value() = val; + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToX[property], val.x()); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToY[property], val.y()); + + emit propertyChanged(property); + emit valueChanged(property, val); +} + +/*! + \reimp +*/ +void QtPointPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QPoint(0, 0); + + QtProperty *xProp = d_ptr->m_intPropertyManager->addProperty(); + xProp->setPropertyName(tr("X")); + d_ptr->m_intPropertyManager->setValue(xProp, 0); + d_ptr->m_propertyToX[property] = xProp; + d_ptr->m_xToProperty[xProp] = property; + property->addSubProperty(xProp); + + QtProperty *yProp = d_ptr->m_intPropertyManager->addProperty(); + yProp->setPropertyName(tr("Y")); + d_ptr->m_intPropertyManager->setValue(yProp, 0); + d_ptr->m_propertyToY[property] = yProp; + d_ptr->m_yToProperty[yProp] = property; + property->addSubProperty(yProp); +} + +/*! + \reimp +*/ +void QtPointPropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *xProp = d_ptr->m_propertyToX[property]; + if (xProp) { + d_ptr->m_xToProperty.remove(xProp); + delete xProp; + } + d_ptr->m_propertyToX.remove(property); + + QtProperty *yProp = d_ptr->m_propertyToY[property]; + if (yProp) { + d_ptr->m_yToProperty.remove(yProp); + delete yProp; + } + d_ptr->m_propertyToY.remove(property); + + d_ptr->m_values.remove(property); +} + +// QtPointFPropertyManager + +class QtPointFPropertyManagerPrivate +{ + QtPointFPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtPointFPropertyManager) +public: + + struct Data + { + QPointF val; + int decimals{2}; + }; + + void slotDoubleChanged(QtProperty *property, double value); + void slotPropertyDestroyed(QtProperty *property); + + QHash m_values; + + QtDoublePropertyManager *m_doublePropertyManager; + + QHash m_propertyToX; + QHash m_propertyToY; + + QHash m_xToProperty; + QHash m_yToProperty; +}; + +void QtPointFPropertyManagerPrivate::slotDoubleChanged(QtProperty *property, double value) +{ + if (QtProperty *prop = m_xToProperty.value(property, nullptr)) { + QPointF p = m_values[prop].val; + p.setX(value); + q_ptr->setValue(prop, p); + } else if (QtProperty *prop = m_yToProperty.value(property, nullptr)) { + QPointF p = m_values[prop].val; + p.setY(value); + q_ptr->setValue(prop, p); + } +} + +void QtPointFPropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *pointProp = m_xToProperty.value(property, nullptr)) { + m_propertyToX[pointProp] = nullptr; + m_xToProperty.remove(property); + } else if (QtProperty *pointProp = m_yToProperty.value(property, nullptr)) { + m_propertyToY[pointProp] = nullptr; + m_yToProperty.remove(property); + } +} + +/*! \class QtPointFPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtPointFPropertyManager provides and manages QPointF properties. + + A point property has nested \e x and \e y subproperties. The + top-level property's value can be retrieved using the value() + function, and set using the setValue() slot. + + The subproperties are created by a QtDoublePropertyManager object. This + manager can be retrieved using the subDoublePropertyManager() function. In + order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + In addition, QtPointFPropertyManager provides the valueChanged() signal which + is emitted whenever a property created by this manager changes. + + \sa QtAbstractPropertyManager, QtDoublePropertyManager, QtPointPropertyManager +*/ + +/*! + \fn void QtPointFPropertyManager::valueChanged(QtProperty *property, const QPointF &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the + new \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtPointFPropertyManager::decimalsChanged(QtProperty *property, int prec) + + This signal is emitted whenever a property created by this manager + changes its precision of value, passing a pointer to the + \a property and the new \a prec value + + \sa setDecimals() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtPointFPropertyManager::QtPointFPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtPointFPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_doublePropertyManager = new QtDoublePropertyManager(this); + connect(d_ptr->m_doublePropertyManager, &QtDoublePropertyManager::valueChanged, this, + [this](QtProperty *property, double value) { d_ptr->slotDoubleChanged(property, value); }); + connect(d_ptr->m_doublePropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtPointFPropertyManager::~QtPointFPropertyManager() +{ + clear(); +} + +/*! + Returns the manager that creates the nested \e x and \e y + subproperties. + + In order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtDoublePropertyManager *QtPointFPropertyManager::subDoublePropertyManager() const +{ + return d_ptr->m_doublePropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns a point with coordinates (0, 0). + + \sa setValue() +*/ +QPointF QtPointFPropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's precision, in decimals. + + \sa setDecimals() +*/ +int QtPointFPropertyManager::decimals(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtPointFPropertyManagerPrivate::Data::decimals, property, 0); +} + +/*! + \reimp +*/ +QString QtPointFPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + const QPointF v = it.value().val; + const int dec = it.value().decimals; + return tr("(%1, %2)").arg(QString::number(v.x(), 'f', dec), + QString::number(v.y(), 'f', dec)); +} + +/*! + \fn void QtPointFPropertyManager::setValue(QtProperty *property, const QPointF &value) + + Sets the value of the given \a property to \a value. Nested + properties are updated automatically. + + \sa value(), valueChanged() +*/ +void QtPointFPropertyManager::setValue(QtProperty *property, QPointF val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + if (it.value().val == val) + return; + + it.value().val = val; + d_ptr->m_doublePropertyManager->setValue(d_ptr->m_propertyToX[property], val.x()); + d_ptr->m_doublePropertyManager->setValue(d_ptr->m_propertyToY[property], val.y()); + + emit propertyChanged(property); + emit valueChanged(property, val); +} + +/*! + \fn void QtPointFPropertyManager::setDecimals(QtProperty *property, int prec) + + Sets the precision of the given \a property to \a prec. + + The valid decimal range is 0-13. The default is 2. + + \sa decimals() +*/ +void QtPointFPropertyManager::setDecimals(QtProperty *property, int prec) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtPointFPropertyManagerPrivate::Data data = it.value(); + + if (prec > 13) + prec = 13; + else if (prec < 0) + prec = 0; + + if (data.decimals == prec) + return; + + data.decimals = prec; + d_ptr->m_doublePropertyManager->setDecimals(d_ptr->m_propertyToX[property], prec); + d_ptr->m_doublePropertyManager->setDecimals(d_ptr->m_propertyToY[property], prec); + + it.value() = data; + + emit decimalsChanged(property, data.decimals); +} + +/*! + \reimp +*/ +void QtPointFPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtPointFPropertyManagerPrivate::Data(); + + QtProperty *xProp = d_ptr->m_doublePropertyManager->addProperty(); + xProp->setPropertyName(tr("X")); + d_ptr->m_doublePropertyManager->setDecimals(xProp, decimals(property)); + d_ptr->m_doublePropertyManager->setValue(xProp, 0); + d_ptr->m_propertyToX[property] = xProp; + d_ptr->m_xToProperty[xProp] = property; + property->addSubProperty(xProp); + + QtProperty *yProp = d_ptr->m_doublePropertyManager->addProperty(); + yProp->setPropertyName(tr("Y")); + d_ptr->m_doublePropertyManager->setDecimals(yProp, decimals(property)); + d_ptr->m_doublePropertyManager->setValue(yProp, 0); + d_ptr->m_propertyToY[property] = yProp; + d_ptr->m_yToProperty[yProp] = property; + property->addSubProperty(yProp); +} + +/*! + \reimp +*/ +void QtPointFPropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *xProp = d_ptr->m_propertyToX[property]; + if (xProp) { + d_ptr->m_xToProperty.remove(xProp); + delete xProp; + } + d_ptr->m_propertyToX.remove(property); + + QtProperty *yProp = d_ptr->m_propertyToY[property]; + if (yProp) { + d_ptr->m_yToProperty.remove(yProp); + delete yProp; + } + d_ptr->m_propertyToY.remove(property); + + d_ptr->m_values.remove(property); +} + +// QtSizePropertyManager + +class QtSizePropertyManagerPrivate +{ + QtSizePropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtSizePropertyManager) +public: + + void slotIntChanged(QtProperty *property, int value); + void slotPropertyDestroyed(QtProperty *property); + void setValue(QtProperty *property, QSize val); + void setRange(QtProperty *property, + QSize minVal, QSize maxVal, QSize val); + + struct Data + { + QSize val{0, 0}; + QSize minVal{0, 0}; + QSize maxVal{INT_MAX, INT_MAX}; + QSize minimumValue() const { return minVal; } + QSize maximumValue() const { return maxVal; } + void setMinimumValue(QSize newMinVal) { setSizeMinimumData(this, newMinVal); } + void setMaximumValue(QSize newMaxVal) { setSizeMaximumData(this, newMaxVal); } + }; + + QHash m_values; + + QtIntPropertyManager *m_intPropertyManager; + + QHash m_propertyToW; + QHash m_propertyToH; + + QHash m_wToProperty; + QHash m_hToProperty; +}; + +void QtSizePropertyManagerPrivate::slotIntChanged(QtProperty *property, int value) +{ + if (QtProperty *prop = m_wToProperty.value(property, nullptr)) { + QSize s = m_values[prop].val; + s.setWidth(value); + q_ptr->setValue(prop, s); + } else if (QtProperty *prop = m_hToProperty.value(property, nullptr)) { + QSize s = m_values[prop].val; + s.setHeight(value); + q_ptr->setValue(prop, s); + } +} + +void QtSizePropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *pointProp = m_wToProperty.value(property, nullptr)) { + m_propertyToW[pointProp] = nullptr; + m_wToProperty.remove(property); + } else if (QtProperty *pointProp = m_hToProperty.value(property, nullptr)) { + m_propertyToH[pointProp] = nullptr; + m_hToProperty.remove(property); + } +} + +void QtSizePropertyManagerPrivate::setValue(QtProperty *property, QSize val) +{ + m_intPropertyManager->setValue(m_propertyToW.value(property), val.width()); + m_intPropertyManager->setValue(m_propertyToH.value(property), val.height()); +} + +void QtSizePropertyManagerPrivate::setRange(QtProperty *property, + QSize minVal, QSize maxVal, QSize val) +{ + QtProperty *wProperty = m_propertyToW.value(property); + QtProperty *hProperty = m_propertyToH.value(property); + m_intPropertyManager->setRange(wProperty, minVal.width(), maxVal.width()); + m_intPropertyManager->setValue(wProperty, val.width()); + m_intPropertyManager->setRange(hProperty, minVal.height(), maxVal.height()); + m_intPropertyManager->setValue(hProperty, val.height()); +} + +/*! + \class QtSizePropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtSizePropertyManager provides and manages QSize properties. + + A size property has nested \e width and \e height + subproperties. The top-level property's value can be retrieved + using the value() function, and set using the setValue() slot. + + The subproperties are created by a QtIntPropertyManager object. This + manager can be retrieved using the subIntPropertyManager() function. In + order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + A size property also has a range of valid values defined by a + minimum size and a maximum size. These sizes can be retrieved + using the minimum() and the maximum() functions, and set using the + setMinimum() and setMaximum() slots. Alternatively, the range can + be defined in one go using the setRange() slot. + + In addition, QtSizePropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes, and the rangeChanged() signal which is emitted whenever + such a property changes its range of valid sizes. + + \sa QtAbstractPropertyManager, QtIntPropertyManager, QtSizeFPropertyManager +*/ + +/*! + \fn void QtSizePropertyManager::valueChanged(QtProperty *property, const QSize &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtSizePropertyManager::rangeChanged(QtProperty *property, const QSize &minimum, const QSize &maximum) + + This signal is emitted whenever a property created by this manager + changes its range of valid sizes, passing a pointer to the \a + property and the new \a minimum and \a maximum sizes. + + \sa setRange() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtSizePropertyManager::QtSizePropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtSizePropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_intPropertyManager = new QtIntPropertyManager(this); + connect(d_ptr->m_intPropertyManager, &QtIntPropertyManager::valueChanged, this, + [this](QtProperty *property, int value) { d_ptr->slotIntChanged(property, value); }); + connect(d_ptr->m_intPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtSizePropertyManager::~QtSizePropertyManager() +{ + clear(); +} + +/*! + Returns the manager that creates the nested \e width and \e height + subproperties. + + In order to provide editing widgets for the \e width and \e height + properties in a property browser widget, this manager must be + associated with an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtIntPropertyManager *QtSizePropertyManager::subIntPropertyManager() const +{ + return d_ptr->m_intPropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns an invalid size + + \sa setValue() +*/ +QSize QtSizePropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's minimum size value. + + \sa setMinimum(), maximum(), setRange() +*/ +QSize QtSizePropertyManager::minimum(const QtProperty *property) const +{ + return getMinimum(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's maximum size value. + + \sa setMaximum(), minimum(), setRange() +*/ +QSize QtSizePropertyManager::maximum(const QtProperty *property) const +{ + return getMaximum(d_ptr->m_values, property); +} + +/*! + \reimp +*/ +QString QtSizePropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + const QSize v = it.value().val; + return tr("%1 x %2").arg(v.width()).arg(v.height()); +} + +/*! + \fn void QtSizePropertyManager::setValue(QtProperty *property, const QSize &value) + + Sets the value of the given \a property to \a value. + + If the specified \a value is not valid according to the given \a + property's size range, the \a value is adjusted to the nearest + valid value within the size range. + + \sa value(), setRange(), valueChanged() +*/ +void QtSizePropertyManager::setValue(QtProperty *property, QSize val) +{ + setValueInRange(this, d_ptr.data(), + &QtSizePropertyManager::propertyChanged, + &QtSizePropertyManager::valueChanged, + property, val, &QtSizePropertyManagerPrivate::setValue); +} + +/*! + Sets the minimum size value for the given \a property to \a minVal. + + When setting the minimum size value, the maximum and current + values are adjusted if necessary (ensuring that the size range + remains valid and that the current value is within the range). + + \sa minimum(), setRange(), rangeChanged() +*/ +void QtSizePropertyManager::setMinimum(QtProperty *property, QSize minVal) +{ + setBorderValue(this, d_ptr.data(), + &QtSizePropertyManager::propertyChanged, + &QtSizePropertyManager::valueChanged, + &QtSizePropertyManager::rangeChanged, + property, + &QtSizePropertyManagerPrivate::Data::minimumValue, + &QtSizePropertyManagerPrivate::Data::setMinimumValue, + minVal, &QtSizePropertyManagerPrivate::setRange); +} + +/*! + Sets the maximum size value for the given \a property to \a maxVal. + + When setting the maximum size value, the minimum and current + values are adjusted if necessary (ensuring that the size range + remains valid and that the current value is within the range). + + \sa maximum(), setRange(), rangeChanged() +*/ +void QtSizePropertyManager::setMaximum(QtProperty *property, QSize maxVal) +{ + setBorderValue(this, d_ptr.data(), + &QtSizePropertyManager::propertyChanged, + &QtSizePropertyManager::valueChanged, + &QtSizePropertyManager::rangeChanged, + property, + &QtSizePropertyManagerPrivate::Data::maximumValue, + &QtSizePropertyManagerPrivate::Data::setMaximumValue, + maxVal, &QtSizePropertyManagerPrivate::setRange); +} + +/*! + \fn void QtSizePropertyManager::setRange(QtProperty *property, const QSize &minimum, const QSize &maximum) + + Sets the range of valid values. + + This is a convenience function defining the range of valid values + in one go; setting the \a minimum and \a maximum values for the + given \a property with a single function call. + + When setting a new range, the current value is adjusted if + necessary (ensuring that the value remains within the range). + + \sa setMinimum(), setMaximum(), rangeChanged() +*/ +void QtSizePropertyManager::setRange(QtProperty *property, QSize minVal, QSize maxVal) +{ + setBorderValues(this, d_ptr.data(), + &QtSizePropertyManager::propertyChanged, + &QtSizePropertyManager::valueChanged, + &QtSizePropertyManager::rangeChanged, + property, minVal, maxVal, &QtSizePropertyManagerPrivate::setRange); +} + +/*! + \reimp +*/ +void QtSizePropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtSizePropertyManagerPrivate::Data(); + + QtProperty *wProp = d_ptr->m_intPropertyManager->addProperty(); + wProp->setPropertyName(tr("Width")); + d_ptr->m_intPropertyManager->setValue(wProp, 0); + d_ptr->m_intPropertyManager->setMinimum(wProp, 0); + d_ptr->m_propertyToW[property] = wProp; + d_ptr->m_wToProperty[wProp] = property; + property->addSubProperty(wProp); + + QtProperty *hProp = d_ptr->m_intPropertyManager->addProperty(); + hProp->setPropertyName(tr("Height")); + d_ptr->m_intPropertyManager->setValue(hProp, 0); + d_ptr->m_intPropertyManager->setMinimum(hProp, 0); + d_ptr->m_propertyToH[property] = hProp; + d_ptr->m_hToProperty[hProp] = property; + property->addSubProperty(hProp); +} + +/*! + \reimp +*/ +void QtSizePropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *wProp = d_ptr->m_propertyToW[property]; + if (wProp) { + d_ptr->m_wToProperty.remove(wProp); + delete wProp; + } + d_ptr->m_propertyToW.remove(property); + + QtProperty *hProp = d_ptr->m_propertyToH[property]; + if (hProp) { + d_ptr->m_hToProperty.remove(hProp); + delete hProp; + } + d_ptr->m_propertyToH.remove(property); + + d_ptr->m_values.remove(property); +} + +// QtSizeFPropertyManager + +class QtSizeFPropertyManagerPrivate +{ + QtSizeFPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtSizeFPropertyManager) +public: + + void slotDoubleChanged(QtProperty *property, double value); + void slotPropertyDestroyed(QtProperty *property); + void setValue(QtProperty *property, QSizeF val); + void setRange(QtProperty *property, + QSizeF minVal, QSizeF maxVal, QSizeF val); + + struct Data + { + QSizeF val{0, 0}; + QSizeF minVal{0, 0}; + QSizeF maxVal{std::numeric_limits::max(), std::numeric_limits::max()}; + int decimals{2}; + QSizeF minimumValue() const { return minVal; } + QSizeF maximumValue() const { return maxVal; } + void setMinimumValue(QSizeF newMinVal) { setSizeMinimumData(this, newMinVal); } + void setMaximumValue(QSizeF newMaxVal) { setSizeMaximumData(this, newMaxVal); } + }; + + QHash m_values; + + QtDoublePropertyManager *m_doublePropertyManager; + + QHash m_propertyToW; + QHash m_propertyToH; + + QHash m_wToProperty; + QHash m_hToProperty; +}; + +void QtSizeFPropertyManagerPrivate::slotDoubleChanged(QtProperty *property, double value) +{ + if (QtProperty *prop = m_wToProperty.value(property, nullptr)) { + QSizeF s = m_values[prop].val; + s.setWidth(value); + q_ptr->setValue(prop, s); + } else if (QtProperty *prop = m_hToProperty.value(property, nullptr)) { + QSizeF s = m_values[prop].val; + s.setHeight(value); + q_ptr->setValue(prop, s); + } +} + +void QtSizeFPropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *pointProp = m_wToProperty.value(property, nullptr)) { + m_propertyToW[pointProp] = nullptr; + m_wToProperty.remove(property); + } else if (QtProperty *pointProp = m_hToProperty.value(property, nullptr)) { + m_propertyToH[pointProp] = nullptr; + m_hToProperty.remove(property); + } +} + +void QtSizeFPropertyManagerPrivate::setValue(QtProperty *property, QSizeF val) +{ + m_doublePropertyManager->setValue(m_propertyToW.value(property), val.width()); + m_doublePropertyManager->setValue(m_propertyToH.value(property), val.height()); +} + +void QtSizeFPropertyManagerPrivate::setRange(QtProperty *property, + QSizeF minVal, QSizeF maxVal, QSizeF val) +{ + m_doublePropertyManager->setRange(m_propertyToW[property], minVal.width(), maxVal.width()); + m_doublePropertyManager->setValue(m_propertyToW[property], val.width()); + m_doublePropertyManager->setRange(m_propertyToH[property], minVal.height(), maxVal.height()); + m_doublePropertyManager->setValue(m_propertyToH[property], val.height()); +} + +/*! + \class QtSizeFPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtSizeFPropertyManager provides and manages QSizeF properties. + + A size property has nested \e width and \e height + subproperties. The top-level property's value can be retrieved + using the value() function, and set using the setValue() slot. + + The subproperties are created by a QtDoublePropertyManager object. This + manager can be retrieved using the subDoublePropertyManager() function. In + order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + A size property also has a range of valid values defined by a + minimum size and a maximum size. These sizes can be retrieved + using the minimum() and the maximum() functions, and set using the + setMinimum() and setMaximum() slots. Alternatively, the range can + be defined in one go using the setRange() slot. + + In addition, QtSizeFPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes, and the rangeChanged() signal which is emitted whenever + such a property changes its range of valid sizes. + + \sa QtAbstractPropertyManager, QtDoublePropertyManager, QtSizePropertyManager +*/ + +/*! + \fn void QtSizeFPropertyManager::valueChanged(QtProperty *property, const QSizeF &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtSizeFPropertyManager::rangeChanged(QtProperty *property, const QSizeF &minimum, const QSizeF &maximum) + + This signal is emitted whenever a property created by this manager + changes its range of valid sizes, passing a pointer to the \a + property and the new \a minimum and \a maximum sizes. + + \sa setRange() +*/ + +/*! + \fn void QtSizeFPropertyManager::decimalsChanged(QtProperty *property, int prec) + + This signal is emitted whenever a property created by this manager + changes its precision of value, passing a pointer to the + \a property and the new \a prec value + + \sa setDecimals() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtSizeFPropertyManager::QtSizeFPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtSizeFPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_doublePropertyManager = new QtDoublePropertyManager(this); + connect(d_ptr->m_doublePropertyManager, &QtDoublePropertyManager::valueChanged, this, + [this](QtProperty *property, double value) { d_ptr->slotDoubleChanged(property, value); }); + connect(d_ptr->m_doublePropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtSizeFPropertyManager::~QtSizeFPropertyManager() +{ + clear(); +} + +/*! + Returns the manager that creates the nested \e width and \e height + subproperties. + + In order to provide editing widgets for the \e width and \e height + properties in a property browser widget, this manager must be + associated with an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtDoublePropertyManager *QtSizeFPropertyManager::subDoublePropertyManager() const +{ + return d_ptr->m_doublePropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns an invalid size + + \sa setValue() +*/ +QSizeF QtSizeFPropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's precision, in decimals. + + \sa setDecimals() +*/ +int QtSizeFPropertyManager::decimals(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtSizeFPropertyManagerPrivate::Data::decimals, property, 0); +} + +/*! + Returns the given \a property's minimum size value. + + \sa setMinimum(), maximum(), setRange() +*/ +QSizeF QtSizeFPropertyManager::minimum(const QtProperty *property) const +{ + return getMinimum(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's maximum size value. + + \sa setMaximum(), minimum(), setRange() +*/ +QSizeF QtSizeFPropertyManager::maximum(const QtProperty *property) const +{ + return getMaximum(d_ptr->m_values, property); +} + +/*! + \reimp +*/ +QString QtSizeFPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + const QSizeF v = it.value().val; + const int dec = it.value().decimals; + return tr("%1 x %2").arg(QString::number(v.width(), 'f', dec), + QString::number(v.height(), 'f', dec)); +} + +/*! + \fn void QtSizeFPropertyManager::setValue(QtProperty *property, const QSizeF &value) + + Sets the value of the given \a property to \a value. + + If the specified \a value is not valid according to the given \a + property's size range, the \a value is adjusted to the nearest + valid value within the size range. + + \sa value(), setRange(), valueChanged() +*/ +void QtSizeFPropertyManager::setValue(QtProperty *property, QSizeF val) +{ + setValueInRange(this, d_ptr.data(), + &QtSizeFPropertyManager::propertyChanged, + &QtSizeFPropertyManager::valueChanged, + property, val, &QtSizeFPropertyManagerPrivate::setValue); +} + +/*! + \fn void QtSizeFPropertyManager::setDecimals(QtProperty *property, int prec) + + Sets the precision of the given \a property to \a prec. + + The valid decimal range is 0-13. The default is 2. + + \sa decimals() +*/ +void QtSizeFPropertyManager::setDecimals(QtProperty *property, int prec) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtSizeFPropertyManagerPrivate::Data data = it.value(); + + if (prec > 13) + prec = 13; + else if (prec < 0) + prec = 0; + + if (data.decimals == prec) + return; + + data.decimals = prec; + d_ptr->m_doublePropertyManager->setDecimals(d_ptr->m_propertyToW[property], prec); + d_ptr->m_doublePropertyManager->setDecimals(d_ptr->m_propertyToH[property], prec); + + it.value() = data; + + emit decimalsChanged(property, data.decimals); +} + +/*! + Sets the minimum size value for the given \a property to \a minVal. + + When setting the minimum size value, the maximum and current + values are adjusted if necessary (ensuring that the size range + remains valid and that the current value is within the range). + + \sa minimum(), setRange(), rangeChanged() +*/ +void QtSizeFPropertyManager::setMinimum(QtProperty *property, QSizeF minVal) +{ + setBorderValue(this, d_ptr.data(), + &QtSizeFPropertyManager::propertyChanged, + &QtSizeFPropertyManager::valueChanged, + &QtSizeFPropertyManager::rangeChanged, + property, + &QtSizeFPropertyManagerPrivate::Data::minimumValue, + &QtSizeFPropertyManagerPrivate::Data::setMinimumValue, + minVal, &QtSizeFPropertyManagerPrivate::setRange); +} + +/*! + Sets the maximum size value for the given \a property to \a maxVal. + + When setting the maximum size value, the minimum and current + values are adjusted if necessary (ensuring that the size range + remains valid and that the current value is within the range). + + \sa maximum(), setRange(), rangeChanged() +*/ +void QtSizeFPropertyManager::setMaximum(QtProperty *property, QSizeF maxVal) +{ + setBorderValue(this, d_ptr.data(), + &QtSizeFPropertyManager::propertyChanged, + &QtSizeFPropertyManager::valueChanged, + &QtSizeFPropertyManager::rangeChanged, + property, + &QtSizeFPropertyManagerPrivate::Data::maximumValue, + &QtSizeFPropertyManagerPrivate::Data::setMaximumValue, + maxVal, &QtSizeFPropertyManagerPrivate::setRange); +} + +/*! + \fn void QtSizeFPropertyManager::setRange(QtProperty *property, const QSizeF &minimum, const QSizeF &maximum) + + Sets the range of valid values. + + This is a convenience function defining the range of valid values + in one go; setting the \a minimum and \a maximum values for the + given \a property with a single function call. + + When setting a new range, the current value is adjusted if + necessary (ensuring that the value remains within the range). + + \sa setMinimum(), setMaximum(), rangeChanged() +*/ +void QtSizeFPropertyManager::setRange(QtProperty *property, QSizeF minVal, QSizeF maxVal) +{ + setBorderValues(this, d_ptr.data(), + &QtSizeFPropertyManager::propertyChanged, + &QtSizeFPropertyManager::valueChanged, + &QtSizeFPropertyManager::rangeChanged, + property, minVal, maxVal, &QtSizeFPropertyManagerPrivate::setRange); +} + +/*! + \reimp +*/ +void QtSizeFPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtSizeFPropertyManagerPrivate::Data(); + + QtProperty *wProp = d_ptr->m_doublePropertyManager->addProperty(); + wProp->setPropertyName(tr("Width")); + d_ptr->m_doublePropertyManager->setDecimals(wProp, decimals(property)); + d_ptr->m_doublePropertyManager->setValue(wProp, 0); + d_ptr->m_doublePropertyManager->setMinimum(wProp, 0); + d_ptr->m_propertyToW[property] = wProp; + d_ptr->m_wToProperty[wProp] = property; + property->addSubProperty(wProp); + + QtProperty *hProp = d_ptr->m_doublePropertyManager->addProperty(); + hProp->setPropertyName(tr("Height")); + d_ptr->m_doublePropertyManager->setDecimals(hProp, decimals(property)); + d_ptr->m_doublePropertyManager->setValue(hProp, 0); + d_ptr->m_doublePropertyManager->setMinimum(hProp, 0); + d_ptr->m_propertyToH[property] = hProp; + d_ptr->m_hToProperty[hProp] = property; + property->addSubProperty(hProp); +} + +/*! + \reimp +*/ +void QtSizeFPropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *wProp = d_ptr->m_propertyToW[property]; + if (wProp) { + d_ptr->m_wToProperty.remove(wProp); + delete wProp; + } + d_ptr->m_propertyToW.remove(property); + + QtProperty *hProp = d_ptr->m_propertyToH[property]; + if (hProp) { + d_ptr->m_hToProperty.remove(hProp); + delete hProp; + } + d_ptr->m_propertyToH.remove(property); + + d_ptr->m_values.remove(property); +} + +// QtRectPropertyManager + +class QtRectPropertyManagerPrivate +{ + QtRectPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtRectPropertyManager) +public: + + void slotIntChanged(QtProperty *property, int value); + void slotPropertyDestroyed(QtProperty *property); + void setConstraint(QtProperty *property, QRect constraint, QRect val); + + struct Data + { + QRect val{0, 0, 0, 0}; + QRect constraint; + }; + + QHash m_values; + + QtIntPropertyManager *m_intPropertyManager; + + QHash m_propertyToX; + QHash m_propertyToY; + QHash m_propertyToW; + QHash m_propertyToH; + + QHash m_xToProperty; + QHash m_yToProperty; + QHash m_wToProperty; + QHash m_hToProperty; +}; + +void QtRectPropertyManagerPrivate::slotIntChanged(QtProperty *property, int value) +{ + if (QtProperty *prop = m_xToProperty.value(property, nullptr)) { + QRect r = m_values[prop].val; + r.moveLeft(value); + q_ptr->setValue(prop, r); + } else if (QtProperty *prop = m_yToProperty.value(property)) { + QRect r = m_values[prop].val; + r.moveTop(value); + q_ptr->setValue(prop, r); + } else if (QtProperty *prop = m_wToProperty.value(property, nullptr)) { + Data data = m_values[prop]; + QRect r = data.val; + r.setWidth(value); + if (!data.constraint.isNull() && data.constraint.x() + data.constraint.width() < r.x() + r.width()) { + r.moveLeft(data.constraint.left() + data.constraint.width() - r.width()); + } + q_ptr->setValue(prop, r); + } else if (QtProperty *prop = m_hToProperty.value(property, nullptr)) { + Data data = m_values[prop]; + QRect r = data.val; + r.setHeight(value); + if (!data.constraint.isNull() && data.constraint.y() + data.constraint.height() < r.y() + r.height()) { + r.moveTop(data.constraint.top() + data.constraint.height() - r.height()); + } + q_ptr->setValue(prop, r); + } +} + +void QtRectPropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *pointProp = m_xToProperty.value(property, nullptr)) { + m_propertyToX[pointProp] = nullptr; + m_xToProperty.remove(property); + } else if (QtProperty *pointProp = m_yToProperty.value(property, nullptr)) { + m_propertyToY[pointProp] = nullptr; + m_yToProperty.remove(property); + } else if (QtProperty *pointProp = m_wToProperty.value(property, nullptr)) { + m_propertyToW[pointProp] = nullptr; + m_wToProperty.remove(property); + } else if (QtProperty *pointProp = m_hToProperty.value(property, nullptr)) { + m_propertyToH[pointProp] = nullptr; + m_hToProperty.remove(property); + } +} + +void QtRectPropertyManagerPrivate::setConstraint(QtProperty *property, + QRect constraint, QRect val) +{ + const bool isNull = constraint.isNull(); + const int left = isNull ? INT_MIN : constraint.left(); + const int right = isNull ? INT_MAX : constraint.left() + constraint.width(); + const int top = isNull ? INT_MIN : constraint.top(); + const int bottom = isNull ? INT_MAX : constraint.top() + constraint.height(); + const int width = isNull ? INT_MAX : constraint.width(); + const int height = isNull ? INT_MAX : constraint.height(); + + m_intPropertyManager->setRange(m_propertyToX[property], left, right); + m_intPropertyManager->setRange(m_propertyToY[property], top, bottom); + m_intPropertyManager->setRange(m_propertyToW[property], 0, width); + m_intPropertyManager->setRange(m_propertyToH[property], 0, height); + + m_intPropertyManager->setValue(m_propertyToX[property], val.x()); + m_intPropertyManager->setValue(m_propertyToY[property], val.y()); + m_intPropertyManager->setValue(m_propertyToW[property], val.width()); + m_intPropertyManager->setValue(m_propertyToH[property], val.height()); +} + +/*! + \class QtRectPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtRectPropertyManager provides and manages QRect properties. + + A rectangle property has nested \e x, \e y, \e width and \e height + subproperties. The top-level property's value can be retrieved + using the value() function, and set using the setValue() slot. + + The subproperties are created by a QtIntPropertyManager object. This + manager can be retrieved using the subIntPropertyManager() function. In + order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + A rectangle property also has a constraint rectangle which can be + retrieved using the constraint() function, and set using the + setConstraint() slot. + + In addition, QtRectPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes, and the constraintChanged() signal which is emitted + whenever such a property changes its constraint rectangle. + + \sa QtAbstractPropertyManager, QtIntPropertyManager, QtRectFPropertyManager +*/ + +/*! + \fn void QtRectPropertyManager::valueChanged(QtProperty *property, const QRect &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtRectPropertyManager::constraintChanged(QtProperty *property, const QRect &constraint) + + This signal is emitted whenever property changes its constraint + rectangle, passing a pointer to the \a property and the new \a + constraint rectangle as parameters. + + \sa setConstraint() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtRectPropertyManager::QtRectPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtRectPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_intPropertyManager = new QtIntPropertyManager(this); + connect(d_ptr->m_intPropertyManager, &QtIntPropertyManager::valueChanged, this, + [this](QtProperty *property, int value) { d_ptr->slotIntChanged(property, value); }); + connect(d_ptr->m_intPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtRectPropertyManager::~QtRectPropertyManager() +{ + clear(); +} + +/*! + Returns the manager that creates the nested \e x, \e y, \e width + and \e height subproperties. + + In order to provide editing widgets for the mentioned + subproperties in a property browser widget, this manager must be + associated with an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtIntPropertyManager *QtRectPropertyManager::subIntPropertyManager() const +{ + return d_ptr->m_intPropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns an invalid rectangle. + + \sa setValue(), constraint() +*/ +QRect QtRectPropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's constraining rectangle. If returned value is null QRect it means there is no constraint applied. + + \sa value(), setConstraint() +*/ +QRect QtRectPropertyManager::constraint(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtRectPropertyManagerPrivate::Data::constraint, property, QRect()); +} + +/*! + \reimp +*/ +QString QtRectPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + const QRect v = it.value().val; + return tr("[(%1, %2), %3 x %4]").arg(v.x()) .arg(v.y()) + .arg(v.width()).arg(v.height()); +} + +/*! + \fn void QtRectPropertyManager::setValue(QtProperty *property, const QRect &value) + + Sets the value of the given \a property to \a value. Nested + properties are updated automatically. + + If the specified \a value is not inside the given \a property's + constraining rectangle, the value is adjusted accordingly to fit + within the constraint. + + \sa value(), setConstraint(), valueChanged() +*/ +void QtRectPropertyManager::setValue(QtProperty *property, QRect val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtRectPropertyManagerPrivate::Data data = it.value(); + + QRect newRect = val.normalized(); + if (!data.constraint.isNull() && !data.constraint.contains(newRect)) { + const QRect r1 = data.constraint; + const QRect r2 = newRect; + newRect.setLeft(qMax(r1.left(), r2.left())); + newRect.setRight(qMin(r1.right(), r2.right())); + newRect.setTop(qMax(r1.top(), r2.top())); + newRect.setBottom(qMin(r1.bottom(), r2.bottom())); + if (newRect.width() < 0 || newRect.height() < 0) + return; + } + + if (data.val == newRect) + return; + + data.val = newRect; + + it.value() = data; + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToX[property], newRect.x()); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToY[property], newRect.y()); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToW[property], newRect.width()); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToH[property], newRect.height()); + + emit propertyChanged(property); + emit valueChanged(property, data.val); +} + +/*! + Sets the given \a property's constraining rectangle to \a + constraint. + + When setting the constraint, the current value is adjusted if + necessary (ensuring that the current rectangle value is inside the + constraint). In order to reset the constraint pass a null QRect value. + + \sa setValue(), constraint(), constraintChanged() +*/ +void QtRectPropertyManager::setConstraint(QtProperty *property, QRect constraint) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtRectPropertyManagerPrivate::Data data = it.value(); + + QRect newConstraint = constraint.normalized(); + if (data.constraint == newConstraint) + return; + + const QRect oldVal = data.val; + + data.constraint = newConstraint; + + if (!data.constraint.isNull() && !data.constraint.contains(oldVal)) { + QRect r1 = data.constraint; + QRect r2 = data.val; + + if (r2.width() > r1.width()) + r2.setWidth(r1.width()); + if (r2.height() > r1.height()) + r2.setHeight(r1.height()); + if (r2.left() < r1.left()) + r2.moveLeft(r1.left()); + else if (r2.right() > r1.right()) + r2.moveRight(r1.right()); + if (r2.top() < r1.top()) + r2.moveTop(r1.top()); + else if (r2.bottom() > r1.bottom()) + r2.moveBottom(r1.bottom()); + + data.val = r2; + } + + it.value() = data; + + emit constraintChanged(property, data.constraint); + + d_ptr->setConstraint(property, data.constraint, data.val); + + if (data.val == oldVal) + return; + + emit propertyChanged(property); + emit valueChanged(property, data.val); +} + +/*! + \reimp +*/ +void QtRectPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtRectPropertyManagerPrivate::Data(); + + QtProperty *xProp = d_ptr->m_intPropertyManager->addProperty(); + xProp->setPropertyName(tr("X")); + d_ptr->m_intPropertyManager->setValue(xProp, 0); + d_ptr->m_propertyToX[property] = xProp; + d_ptr->m_xToProperty[xProp] = property; + property->addSubProperty(xProp); + + QtProperty *yProp = d_ptr->m_intPropertyManager->addProperty(); + yProp->setPropertyName(tr("Y")); + d_ptr->m_intPropertyManager->setValue(yProp, 0); + d_ptr->m_propertyToY[property] = yProp; + d_ptr->m_yToProperty[yProp] = property; + property->addSubProperty(yProp); + + QtProperty *wProp = d_ptr->m_intPropertyManager->addProperty(); + wProp->setPropertyName(tr("Width")); + d_ptr->m_intPropertyManager->setValue(wProp, 0); + d_ptr->m_intPropertyManager->setMinimum(wProp, 0); + d_ptr->m_propertyToW[property] = wProp; + d_ptr->m_wToProperty[wProp] = property; + property->addSubProperty(wProp); + + QtProperty *hProp = d_ptr->m_intPropertyManager->addProperty(); + hProp->setPropertyName(tr("Height")); + d_ptr->m_intPropertyManager->setValue(hProp, 0); + d_ptr->m_intPropertyManager->setMinimum(hProp, 0); + d_ptr->m_propertyToH[property] = hProp; + d_ptr->m_hToProperty[hProp] = property; + property->addSubProperty(hProp); +} + +/*! + \reimp +*/ +void QtRectPropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *xProp = d_ptr->m_propertyToX[property]; + if (xProp) { + d_ptr->m_xToProperty.remove(xProp); + delete xProp; + } + d_ptr->m_propertyToX.remove(property); + + QtProperty *yProp = d_ptr->m_propertyToY[property]; + if (yProp) { + d_ptr->m_yToProperty.remove(yProp); + delete yProp; + } + d_ptr->m_propertyToY.remove(property); + + QtProperty *wProp = d_ptr->m_propertyToW[property]; + if (wProp) { + d_ptr->m_wToProperty.remove(wProp); + delete wProp; + } + d_ptr->m_propertyToW.remove(property); + + QtProperty *hProp = d_ptr->m_propertyToH[property]; + if (hProp) { + d_ptr->m_hToProperty.remove(hProp); + delete hProp; + } + d_ptr->m_propertyToH.remove(property); + + d_ptr->m_values.remove(property); +} + +// QtRectFPropertyManager + +class QtRectFPropertyManagerPrivate +{ + QtRectFPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtRectFPropertyManager) +public: + + void slotDoubleChanged(QtProperty *property, double value); + void slotPropertyDestroyed(QtProperty *property); + void setConstraint(QtProperty *property, const QRectF &constraint, const QRectF &val); + + struct Data + { + QRectF val{0, 0, 0, 0}; + QRectF constraint; + int decimals{2}; + }; + + QHash m_values; + + QtDoublePropertyManager *m_doublePropertyManager; + + QHash m_propertyToX; + QHash m_propertyToY; + QHash m_propertyToW; + QHash m_propertyToH; + + QHash m_xToProperty; + QHash m_yToProperty; + QHash m_wToProperty; + QHash m_hToProperty; +}; + +void QtRectFPropertyManagerPrivate::slotDoubleChanged(QtProperty *property, double value) +{ + if (QtProperty *prop = m_xToProperty.value(property, nullptr)) { + QRectF r = m_values[prop].val; + r.moveLeft(value); + q_ptr->setValue(prop, r); + } else if (QtProperty *prop = m_yToProperty.value(property, nullptr)) { + QRectF r = m_values[prop].val; + r.moveTop(value); + q_ptr->setValue(prop, r); + } else if (QtProperty *prop = m_wToProperty.value(property, nullptr)) { + Data data = m_values[prop]; + QRectF r = data.val; + r.setWidth(value); + if (!data.constraint.isNull() && data.constraint.x() + data.constraint.width() < r.x() + r.width()) { + r.moveLeft(data.constraint.left() + data.constraint.width() - r.width()); + } + q_ptr->setValue(prop, r); + } else if (QtProperty *prop = m_hToProperty.value(property, nullptr)) { + Data data = m_values[prop]; + QRectF r = data.val; + r.setHeight(value); + if (!data.constraint.isNull() && data.constraint.y() + data.constraint.height() < r.y() + r.height()) { + r.moveTop(data.constraint.top() + data.constraint.height() - r.height()); + } + q_ptr->setValue(prop, r); + } +} + +void QtRectFPropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *pointProp = m_xToProperty.value(property, nullptr)) { + m_propertyToX[pointProp] = nullptr; + m_xToProperty.remove(property); + } else if (QtProperty *pointProp = m_yToProperty.value(property, nullptr)) { + m_propertyToY[pointProp] = nullptr; + m_yToProperty.remove(property); + } else if (QtProperty *pointProp = m_wToProperty.value(property, nullptr)) { + m_propertyToW[pointProp] = nullptr; + m_wToProperty.remove(property); + } else if (QtProperty *pointProp = m_hToProperty.value(property, nullptr)) { + m_propertyToH[pointProp] = nullptr; + m_hToProperty.remove(property); + } +} + +void QtRectFPropertyManagerPrivate::setConstraint(QtProperty *property, + const QRectF &constraint, const QRectF &val) +{ + const bool isNull = constraint.isNull(); + const float left = isNull ? FLT_MIN : constraint.left(); + const float right = isNull ? FLT_MAX : constraint.left() + constraint.width(); + const float top = isNull ? FLT_MIN : constraint.top(); + const float bottom = isNull ? FLT_MAX : constraint.top() + constraint.height(); + const float width = isNull ? FLT_MAX : constraint.width(); + const float height = isNull ? FLT_MAX : constraint.height(); + + m_doublePropertyManager->setRange(m_propertyToX[property], left, right); + m_doublePropertyManager->setRange(m_propertyToY[property], top, bottom); + m_doublePropertyManager->setRange(m_propertyToW[property], 0, width); + m_doublePropertyManager->setRange(m_propertyToH[property], 0, height); + + m_doublePropertyManager->setValue(m_propertyToX[property], val.x()); + m_doublePropertyManager->setValue(m_propertyToY[property], val.y()); + m_doublePropertyManager->setValue(m_propertyToW[property], val.width()); + m_doublePropertyManager->setValue(m_propertyToH[property], val.height()); +} + +/*! + \class QtRectFPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtRectFPropertyManager provides and manages QRectF properties. + + A rectangle property has nested \e x, \e y, \e width and \e height + subproperties. The top-level property's value can be retrieved + using the value() function, and set using the setValue() slot. + + The subproperties are created by a QtDoublePropertyManager object. This + manager can be retrieved using the subDoublePropertyManager() function. In + order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + A rectangle property also has a constraint rectangle which can be + retrieved using the constraint() function, and set using the + setConstraint() slot. + + In addition, QtRectFPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes, and the constraintChanged() signal which is emitted + whenever such a property changes its constraint rectangle. + + \sa QtAbstractPropertyManager, QtDoublePropertyManager, QtRectPropertyManager +*/ + +/*! + \fn void QtRectFPropertyManager::valueChanged(QtProperty *property, const QRectF &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtRectFPropertyManager::constraintChanged(QtProperty *property, const QRectF &constraint) + + This signal is emitted whenever property changes its constraint + rectangle, passing a pointer to the \a property and the new \a + constraint rectangle as parameters. + + \sa setConstraint() +*/ + +/*! + \fn void QtRectFPropertyManager::decimalsChanged(QtProperty *property, int prec) + + This signal is emitted whenever a property created by this manager + changes its precision of value, passing a pointer to the + \a property and the new \a prec value + + \sa setDecimals() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtRectFPropertyManager::QtRectFPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtRectFPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_doublePropertyManager = new QtDoublePropertyManager(this); + connect(d_ptr->m_doublePropertyManager, &QtDoublePropertyManager::valueChanged, this, + [this](QtProperty *property, double value) { d_ptr->slotDoubleChanged(property, value); }); + connect(d_ptr->m_doublePropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtRectFPropertyManager::~QtRectFPropertyManager() +{ + clear(); +} + +/*! + Returns the manager that creates the nested \e x, \e y, \e width + and \e height subproperties. + + In order to provide editing widgets for the mentioned + subproperties in a property browser widget, this manager must be + associated with an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtDoublePropertyManager *QtRectFPropertyManager::subDoublePropertyManager() const +{ + return d_ptr->m_doublePropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns an invalid rectangle. + + \sa setValue(), constraint() +*/ +QRectF QtRectFPropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property); +} + +/*! + Returns the given \a property's precision, in decimals. + + \sa setDecimals() +*/ +int QtRectFPropertyManager::decimals(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtRectFPropertyManagerPrivate::Data::decimals, property, 0); +} + +/*! + Returns the given \a property's constraining rectangle. If returned value is null QRectF it means there is no constraint applied. + + \sa value(), setConstraint() +*/ +QRectF QtRectFPropertyManager::constraint(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtRectFPropertyManagerPrivate::Data::constraint, property, QRect()); +} + +/*! + \reimp +*/ +QString QtRectFPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + const QRectF v = it.value().val; + const int dec = it.value().decimals; + return QString(tr("[(%1, %2), %3 x %4]").arg(QString::number(v.x(), 'f', dec), + QString::number(v.y(), 'f', dec), + QString::number(v.width(), 'f', dec), + QString::number(v.height(), 'f', dec))); +} + +/*! + \fn void QtRectFPropertyManager::setValue(QtProperty *property, const QRectF &value) + + Sets the value of the given \a property to \a value. Nested + properties are updated automatically. + + If the specified \a value is not inside the given \a property's + constraining rectangle, the value is adjusted accordingly to fit + within the constraint. + + \sa value(), setConstraint(), valueChanged() +*/ +void QtRectFPropertyManager::setValue(QtProperty *property, const QRectF &val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtRectFPropertyManagerPrivate::Data data = it.value(); + + QRectF newRect = val.normalized(); + if (!data.constraint.isNull() && !data.constraint.contains(newRect)) { + const QRectF r1 = data.constraint; + const QRectF r2 = newRect; + newRect.setLeft(qMax(r1.left(), r2.left())); + newRect.setRight(qMin(r1.right(), r2.right())); + newRect.setTop(qMax(r1.top(), r2.top())); + newRect.setBottom(qMin(r1.bottom(), r2.bottom())); + if (newRect.width() < 0 || newRect.height() < 0) + return; + } + + if (data.val == newRect) + return; + + data.val = newRect; + + it.value() = data; + d_ptr->m_doublePropertyManager->setValue(d_ptr->m_propertyToX[property], newRect.x()); + d_ptr->m_doublePropertyManager->setValue(d_ptr->m_propertyToY[property], newRect.y()); + d_ptr->m_doublePropertyManager->setValue(d_ptr->m_propertyToW[property], newRect.width()); + d_ptr->m_doublePropertyManager->setValue(d_ptr->m_propertyToH[property], newRect.height()); + + emit propertyChanged(property); + emit valueChanged(property, data.val); +} + +/*! + Sets the given \a property's constraining rectangle to \a + constraint. + + When setting the constraint, the current value is adjusted if + necessary (ensuring that the current rectangle value is inside the + constraint). In order to reset the constraint pass a null QRectF value. + + \sa setValue(), constraint(), constraintChanged() +*/ +void QtRectFPropertyManager::setConstraint(QtProperty *property, const QRectF &constraint) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtRectFPropertyManagerPrivate::Data data = it.value(); + + QRectF newConstraint = constraint.normalized(); + if (data.constraint == newConstraint) + return; + + const QRectF oldVal = data.val; + + data.constraint = newConstraint; + + if (!data.constraint.isNull() && !data.constraint.contains(oldVal)) { + QRectF r1 = data.constraint; + QRectF r2 = data.val; + + if (r2.width() > r1.width()) + r2.setWidth(r1.width()); + if (r2.height() > r1.height()) + r2.setHeight(r1.height()); + if (r2.left() < r1.left()) + r2.moveLeft(r1.left()); + else if (r2.right() > r1.right()) + r2.moveRight(r1.right()); + if (r2.top() < r1.top()) + r2.moveTop(r1.top()); + else if (r2.bottom() > r1.bottom()) + r2.moveBottom(r1.bottom()); + + data.val = r2; + } + + it.value() = data; + + emit constraintChanged(property, data.constraint); + + d_ptr->setConstraint(property, data.constraint, data.val); + + if (data.val == oldVal) + return; + + emit propertyChanged(property); + emit valueChanged(property, data.val); +} + +/*! + \fn void QtRectFPropertyManager::setDecimals(QtProperty *property, int prec) + + Sets the precision of the given \a property to \a prec. + + The valid decimal range is 0-13. The default is 2. + + \sa decimals() +*/ +void QtRectFPropertyManager::setDecimals(QtProperty *property, int prec) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtRectFPropertyManagerPrivate::Data data = it.value(); + + if (prec > 13) + prec = 13; + else if (prec < 0) + prec = 0; + + if (data.decimals == prec) + return; + + data.decimals = prec; + d_ptr->m_doublePropertyManager->setDecimals(d_ptr->m_propertyToX[property], prec); + d_ptr->m_doublePropertyManager->setDecimals(d_ptr->m_propertyToY[property], prec); + d_ptr->m_doublePropertyManager->setDecimals(d_ptr->m_propertyToW[property], prec); + d_ptr->m_doublePropertyManager->setDecimals(d_ptr->m_propertyToH[property], prec); + + it.value() = data; + + emit decimalsChanged(property, data.decimals); +} + +/*! + \reimp +*/ +void QtRectFPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtRectFPropertyManagerPrivate::Data(); + + QtProperty *xProp = d_ptr->m_doublePropertyManager->addProperty(); + xProp->setPropertyName(tr("X")); + d_ptr->m_doublePropertyManager->setDecimals(xProp, decimals(property)); + d_ptr->m_doublePropertyManager->setValue(xProp, 0); + d_ptr->m_propertyToX[property] = xProp; + d_ptr->m_xToProperty[xProp] = property; + property->addSubProperty(xProp); + + QtProperty *yProp = d_ptr->m_doublePropertyManager->addProperty(); + yProp->setPropertyName(tr("Y")); + d_ptr->m_doublePropertyManager->setDecimals(yProp, decimals(property)); + d_ptr->m_doublePropertyManager->setValue(yProp, 0); + d_ptr->m_propertyToY[property] = yProp; + d_ptr->m_yToProperty[yProp] = property; + property->addSubProperty(yProp); + + QtProperty *wProp = d_ptr->m_doublePropertyManager->addProperty(); + wProp->setPropertyName(tr("Width")); + d_ptr->m_doublePropertyManager->setDecimals(wProp, decimals(property)); + d_ptr->m_doublePropertyManager->setValue(wProp, 0); + d_ptr->m_doublePropertyManager->setMinimum(wProp, 0); + d_ptr->m_propertyToW[property] = wProp; + d_ptr->m_wToProperty[wProp] = property; + property->addSubProperty(wProp); + + QtProperty *hProp = d_ptr->m_doublePropertyManager->addProperty(); + hProp->setPropertyName(tr("Height")); + d_ptr->m_doublePropertyManager->setDecimals(hProp, decimals(property)); + d_ptr->m_doublePropertyManager->setValue(hProp, 0); + d_ptr->m_doublePropertyManager->setMinimum(hProp, 0); + d_ptr->m_propertyToH[property] = hProp; + d_ptr->m_hToProperty[hProp] = property; + property->addSubProperty(hProp); +} + +/*! + \reimp +*/ +void QtRectFPropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *xProp = d_ptr->m_propertyToX[property]; + if (xProp) { + d_ptr->m_xToProperty.remove(xProp); + delete xProp; + } + d_ptr->m_propertyToX.remove(property); + + QtProperty *yProp = d_ptr->m_propertyToY[property]; + if (yProp) { + d_ptr->m_yToProperty.remove(yProp); + delete yProp; + } + d_ptr->m_propertyToY.remove(property); + + QtProperty *wProp = d_ptr->m_propertyToW[property]; + if (wProp) { + d_ptr->m_wToProperty.remove(wProp); + delete wProp; + } + d_ptr->m_propertyToW.remove(property); + + QtProperty *hProp = d_ptr->m_propertyToH[property]; + if (hProp) { + d_ptr->m_hToProperty.remove(hProp); + delete hProp; + } + d_ptr->m_propertyToH.remove(property); + + d_ptr->m_values.remove(property); +} + +// QtEnumPropertyManager + +class QtEnumPropertyManagerPrivate +{ + QtEnumPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtEnumPropertyManager) +public: + + struct Data + { + int val{-1}; + QStringList enumNames; + QMap enumIcons; + }; + + QHash m_values; +}; + +/*! + \class QtEnumPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtEnumPropertyManager provides and manages enum properties. + + Each enum property has an associated list of enum names which can + be retrieved using the enumNames() function, and set using the + corresponding setEnumNames() function. An enum property's value is + represented by an index in this list, and can be retrieved and set + using the value() and setValue() slots respectively. + + Each enum value can also have an associated icon. The mapping from + values to icons can be set using the setEnumIcons() function and + queried with the enumIcons() function. + + In addition, QtEnumPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes. The enumNamesChanged() or enumIconsChanged() signal is emitted + whenever the list of enum names or icons is altered. + + \sa QtAbstractPropertyManager, QtEnumEditorFactory +*/ + +/*! + \fn void QtEnumPropertyManager::valueChanged(QtProperty *property, int value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtEnumPropertyManager::enumNamesChanged(QtProperty *property, const QStringList &names) + + This signal is emitted whenever a property created by this manager + changes its enum names, passing a pointer to the \a property and + the new \a names as parameters. + + \sa setEnumNames() +*/ + +/*! + \fn void QtEnumPropertyManager::enumIconsChanged(QtProperty *property, const QMap &icons) + + This signal is emitted whenever a property created by this manager + changes its enum icons, passing a pointer to the \a property and + the new mapping of values to \a icons as parameters. + + \sa setEnumIcons() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtEnumPropertyManager::QtEnumPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtEnumPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtEnumPropertyManager::~QtEnumPropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value which is an index in the + list returned by enumNames() + + If the given property is not managed by this manager, this + function returns -1. + + \sa enumNames(), setValue() +*/ +int QtEnumPropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property, -1); +} + +/*! + Returns the given \a property's list of enum names. + + \sa value(), setEnumNames() +*/ +QStringList QtEnumPropertyManager::enumNames(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtEnumPropertyManagerPrivate::Data::enumNames, property, QStringList()); +} + +/*! + Returns the given \a property's map of enum values to their icons. + + \sa value(), setEnumIcons() +*/ +QMap QtEnumPropertyManager::enumIcons(const QtProperty *property) const +{ + return getData >(d_ptr->m_values, &QtEnumPropertyManagerPrivate::Data::enumIcons, property, QMap()); +} + +/*! + \reimp +*/ +QString QtEnumPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + const QtEnumPropertyManagerPrivate::Data &data = it.value(); + + const int v = data.val; + if (v >= 0 && v < data.enumNames.size()) + return data.enumNames.at(v); + return {}; +} + +/*! + \reimp +*/ +QIcon QtEnumPropertyManager::valueIcon(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + const QtEnumPropertyManagerPrivate::Data &data = it.value(); + + const int v = data.val; + return data.enumIcons.value(v); +} + +/*! + \fn void QtEnumPropertyManager::setValue(QtProperty *property, int value) + + Sets the value of the given \a property to \a value. + + The specified \a value must be less than the size of the given \a + property's enumNames() list, and larger than (or equal to) 0. + + \sa value(), valueChanged() +*/ +void QtEnumPropertyManager::setValue(QtProperty *property, int val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtEnumPropertyManagerPrivate::Data data = it.value(); + + if (val >= data.enumNames.size()) + return; + + if (val < 0 && data.enumNames.size() > 0) + return; + + if (val < 0) + val = -1; + + if (data.val == val) + return; + + data.val = val; + + it.value() = data; + + emit propertyChanged(property); + emit valueChanged(property, data.val); +} + +/*! + Sets the given \a property's list of enum names to \a + enumNames. The \a property's current value is reset to 0 + indicating the first item of the list. + + If the specified \a enumNames list is empty, the \a property's + current value is set to -1. + + \sa enumNames(), enumNamesChanged() +*/ +void QtEnumPropertyManager::setEnumNames(QtProperty *property, const QStringList &enumNames) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtEnumPropertyManagerPrivate::Data data = it.value(); + + if (data.enumNames == enumNames) + return; + + data.enumNames = enumNames; + + data.val = -1; + + if (enumNames.size() > 0) + data.val = 0; + + it.value() = data; + + emit enumNamesChanged(property, data.enumNames); + + emit propertyChanged(property); + emit valueChanged(property, data.val); +} + +/*! + Sets the given \a property's map of enum values to their icons to \a + enumIcons. + + Each enum value can have associated icon. This association is represented with passed \a enumIcons map. + + \sa enumNames(), enumNamesChanged() +*/ +void QtEnumPropertyManager::setEnumIcons(QtProperty *property, const QMap &enumIcons) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + it.value().enumIcons = enumIcons; + + emit enumIconsChanged(property, it.value().enumIcons); + + emit propertyChanged(property); +} + +/*! + \reimp +*/ +void QtEnumPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtEnumPropertyManagerPrivate::Data(); +} + +/*! + \reimp +*/ +void QtEnumPropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +// QtFlagPropertyManager + +class QtFlagPropertyManagerPrivate +{ + QtFlagPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtFlagPropertyManager) +public: + + void slotBoolChanged(QtProperty *property, bool value); + void slotPropertyDestroyed(QtProperty *property); + + struct Data + { + int val{-1}; + QStringList flagNames; + }; + + QHash m_values; + + QtBoolPropertyManager *m_boolPropertyManager; + + QHash> m_propertyToFlags; + + QHash m_flagToProperty; +}; + +void QtFlagPropertyManagerPrivate::slotBoolChanged(QtProperty *property, bool value) +{ + QtProperty *prop = m_flagToProperty.value(property, nullptr); + if (prop == nullptr) + return; + + const auto pfit = m_propertyToFlags.constFind(prop); + if (pfit == m_propertyToFlags.constEnd()) + return; + int level = 0; + for (QtProperty *p : pfit.value()) { + if (p == property) { + int v = m_values[prop].val; + if (value) { + v |= (1 << level); + } else { + v &= ~(1 << level); + } + q_ptr->setValue(prop, v); + return; + } + level++; + } +} + +void QtFlagPropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + QtProperty *flagProperty = m_flagToProperty.value(property, nullptr); + if (flagProperty == nullptr) + return; + + m_propertyToFlags[flagProperty].replace(m_propertyToFlags[flagProperty].indexOf(property), 0); + m_flagToProperty.remove(property); +} + +/*! + \class QtFlagPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtFlagPropertyManager provides and manages flag properties. + + Each flag property has an associated list of flag names which can + be retrieved using the flagNames() function, and set using the + corresponding setFlagNames() function. + + The flag manager provides properties with nested boolean + subproperties representing each flag, i.e. a flag property's value + is the binary combination of the subproperties' values. A + property's value can be retrieved and set using the value() and + setValue() slots respectively. The combination of flags is represented + by single int value - that's why it's possible to store up to + 32 independent flags in one flag property. + + The subproperties are created by a QtBoolPropertyManager object. This + manager can be retrieved using the subBoolPropertyManager() function. In + order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + In addition, QtFlagPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes, and the flagNamesChanged() signal which is emitted + whenever the list of flag names is altered. + + \sa QtAbstractPropertyManager, QtBoolPropertyManager +*/ + +/*! + \fn void QtFlagPropertyManager::valueChanged(QtProperty *property, int value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtFlagPropertyManager::flagNamesChanged(QtProperty *property, const QStringList &names) + + This signal is emitted whenever a property created by this manager + changes its flag names, passing a pointer to the \a property and the + new \a names as parameters. + + \sa setFlagNames() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtFlagPropertyManager::QtFlagPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtFlagPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_boolPropertyManager = new QtBoolPropertyManager(this); + connect(d_ptr->m_boolPropertyManager, &QtBoolPropertyManager::valueChanged, this, + [this](QtProperty *property, bool value) { d_ptr->slotBoolChanged(property, value); }); + connect(d_ptr->m_boolPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtFlagPropertyManager::~QtFlagPropertyManager() +{ + clear(); +} + +/*! + Returns the manager that produces the nested boolean subproperties + representing each flag. + + In order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtBoolPropertyManager *QtFlagPropertyManager::subBoolPropertyManager() const +{ + return d_ptr->m_boolPropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given property is not managed by this manager, this + function returns 0. + + \sa flagNames(), setValue() +*/ +int QtFlagPropertyManager::value(const QtProperty *property) const +{ + return getValue(d_ptr->m_values, property, 0); +} + +/*! + Returns the given \a property's list of flag names. + + \sa value(), setFlagNames() +*/ +QStringList QtFlagPropertyManager::flagNames(const QtProperty *property) const +{ + return getData(d_ptr->m_values, &QtFlagPropertyManagerPrivate::Data::flagNames, property, QStringList()); +} + +/*! + \reimp +*/ +QString QtFlagPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + const QtFlagPropertyManagerPrivate::Data &data = it.value(); + + QString str; + int level = 0; + const QChar bar = QLatin1Char('|'); + for (const auto &name : data.flagNames) { + if (data.val & (1 << level)) { + if (!str.isEmpty()) + str += bar; + str += name; + } + + level++; + } + return str; +} + +/*! + \fn void QtFlagPropertyManager::setValue(QtProperty *property, int value) + + Sets the value of the given \a property to \a value. Nested + properties are updated automatically. + + The specified \a value must be less than the binary combination of + the property's flagNames() list size (i.e. less than 2\sup n, + where \c n is the size of the list) and larger than (or equal to) + 0. + + \sa value(), valueChanged() +*/ +void QtFlagPropertyManager::setValue(QtProperty *property, int val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtFlagPropertyManagerPrivate::Data data = it.value(); + + if (data.val == val) + return; + + if (val > (1 << data.flagNames.size()) - 1) + return; + + if (val < 0) + return; + + data.val = val; + + it.value() = data; + + const auto pfit = d_ptr->m_propertyToFlags.constFind(property); + int level = 0; + if (pfit != d_ptr->m_propertyToFlags.constEnd()) { + for (QtProperty *prop : pfit.value()) { + if (prop) + d_ptr->m_boolPropertyManager->setValue(prop, val & (1 << level)); + level++; + } + } + + emit propertyChanged(property); + emit valueChanged(property, data.val); +} + +/*! + Sets the given \a property's list of flag names to \a flagNames. The + property's current value is reset to 0 indicating the first item + of the list. + + \sa flagNames(), flagNamesChanged() +*/ +void QtFlagPropertyManager::setFlagNames(QtProperty *property, const QStringList &flagNames) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + QtFlagPropertyManagerPrivate::Data data = it.value(); + + if (data.flagNames == flagNames) + return; + + data.flagNames = flagNames; + data.val = 0; + + it.value() = data; + + const auto pfit = d_ptr->m_propertyToFlags.find(property); + if (pfit != d_ptr->m_propertyToFlags.end()) { + for (QtProperty *prop : std::as_const(pfit.value())) { + if (prop) { + delete prop; + d_ptr->m_flagToProperty.remove(prop); + } + } + pfit.value().clear(); + } + + for (const QString &flagName : flagNames) { + QtProperty *prop = d_ptr->m_boolPropertyManager->addProperty(); + prop->setPropertyName(flagName); + property->addSubProperty(prop); + d_ptr->m_propertyToFlags[property].append(prop); + d_ptr->m_flagToProperty[prop] = property; + } + + emit flagNamesChanged(property, data.flagNames); + + emit propertyChanged(property); + emit valueChanged(property, data.val); +} + +/*! + \reimp +*/ +void QtFlagPropertyManager::initializeProperty(QtProperty *property) +{ + d_ptr->m_values[property] = QtFlagPropertyManagerPrivate::Data(); + + d_ptr->m_propertyToFlags[property] = QList(); +} + +/*! + \reimp +*/ +void QtFlagPropertyManager::uninitializeProperty(QtProperty *property) +{ + const auto it = d_ptr->m_propertyToFlags.find(property); + if (it != d_ptr->m_propertyToFlags.end()) { + for (QtProperty *prop : std::as_const(it.value())) { + if (prop) { + d_ptr->m_flagToProperty.remove(prop); + delete prop; + } + } + } + d_ptr->m_propertyToFlags.erase(it); + + d_ptr->m_values.remove(property); +} + +// QtSizePolicyPropertyManager + +class QtSizePolicyPropertyManagerPrivate +{ + QtSizePolicyPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtSizePolicyPropertyManager) +public: + + QtSizePolicyPropertyManagerPrivate(); + + void slotIntChanged(QtProperty *property, int value); + void slotEnumChanged(QtProperty *property, int value); + void slotPropertyDestroyed(QtProperty *property); + + QHash m_values; + + QtIntPropertyManager *m_intPropertyManager; + QtEnumPropertyManager *m_enumPropertyManager; + + QHash m_propertyToHPolicy; + QHash m_propertyToVPolicy; + QHash m_propertyToHStretch; + QHash m_propertyToVStretch; + + QHash m_hPolicyToProperty; + QHash m_vPolicyToProperty; + QHash m_hStretchToProperty; + QHash m_vStretchToProperty; +}; + +QtSizePolicyPropertyManagerPrivate::QtSizePolicyPropertyManagerPrivate() +{ +} + +void QtSizePolicyPropertyManagerPrivate::slotIntChanged(QtProperty *property, int value) +{ + if (QtProperty *prop = m_hStretchToProperty.value(property, nullptr)) { + QSizePolicy sp = m_values[prop]; + sp.setHorizontalStretch(value); + q_ptr->setValue(prop, sp); + } else if (QtProperty *prop = m_vStretchToProperty.value(property, nullptr)) { + QSizePolicy sp = m_values[prop]; + sp.setVerticalStretch(value); + q_ptr->setValue(prop, sp); + } +} + +void QtSizePolicyPropertyManagerPrivate::slotEnumChanged(QtProperty *property, int value) +{ + if (QtProperty *prop = m_hPolicyToProperty.value(property, nullptr)) { + QSizePolicy sp = m_values[prop]; + sp.setHorizontalPolicy(metaEnumProvider()->indexToSizePolicy(value)); + q_ptr->setValue(prop, sp); + } else if (QtProperty *prop = m_vPolicyToProperty.value(property, nullptr)) { + QSizePolicy sp = m_values[prop]; + sp.setVerticalPolicy(metaEnumProvider()->indexToSizePolicy(value)); + q_ptr->setValue(prop, sp); + } +} + +void QtSizePolicyPropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *pointProp = m_hStretchToProperty.value(property, nullptr)) { + m_propertyToHStretch[pointProp] = nullptr; + m_hStretchToProperty.remove(property); + } else if (QtProperty *pointProp = m_vStretchToProperty.value(property, nullptr)) { + m_propertyToVStretch[pointProp] = nullptr; + m_vStretchToProperty.remove(property); + } else if (QtProperty *pointProp = m_hPolicyToProperty.value(property, nullptr)) { + m_propertyToHPolicy[pointProp] = nullptr; + m_hPolicyToProperty.remove(property); + } else if (QtProperty *pointProp = m_vPolicyToProperty.value(property, nullptr)) { + m_propertyToVPolicy[pointProp] = nullptr; + m_vPolicyToProperty.remove(property); + } +} + +/*! + \class QtSizePolicyPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtSizePolicyPropertyManager provides and manages QSizePolicy properties. + + A size policy property has nested \e horizontalPolicy, \e + verticalPolicy, \e horizontalStretch and \e verticalStretch + subproperties. The top-level property's value can be retrieved + using the value() function, and set using the setValue() slot. + + The subproperties are created by QtIntPropertyManager and QtEnumPropertyManager + objects. These managers can be retrieved using the subIntPropertyManager() + and subEnumPropertyManager() functions respectively. In order to provide + editing widgets for the subproperties in a property browser widget, + these managers must be associated with editor factories. + + In addition, QtSizePolicyPropertyManager provides the valueChanged() + signal which is emitted whenever a property created by this + manager changes. + + \sa QtAbstractPropertyManager, QtIntPropertyManager, QtEnumPropertyManager +*/ + +/*! + \fn void QtSizePolicyPropertyManager::valueChanged(QtProperty *property, const QSizePolicy &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the + new \a value as parameters. + + \sa setValue() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtSizePolicyPropertyManager::QtSizePolicyPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtSizePolicyPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_intPropertyManager = new QtIntPropertyManager(this); + connect(d_ptr->m_intPropertyManager, &QtIntPropertyManager::valueChanged, this, + [this](QtProperty *property, int value) { d_ptr->slotIntChanged(property, value); }); + connect(d_ptr->m_intPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); + + d_ptr->m_enumPropertyManager = new QtEnumPropertyManager(this); + connect(d_ptr->m_enumPropertyManager, &QtEnumPropertyManager::valueChanged, this, + [this](QtProperty *property, int value) { d_ptr->slotEnumChanged(property, value); }); + connect(d_ptr->m_enumPropertyManager, &QtEnumPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtSizePolicyPropertyManager::~QtSizePolicyPropertyManager() +{ + clear(); +} + +/*! + Returns the manager that creates the nested \e horizontalStretch + and \e verticalStretch subproperties. + + In order to provide editing widgets for the mentioned subproperties + in a property browser widget, this manager must be associated with + an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtIntPropertyManager *QtSizePolicyPropertyManager::subIntPropertyManager() const +{ + return d_ptr->m_intPropertyManager; +} + +/*! + Returns the manager that creates the nested \e horizontalPolicy + and \e verticalPolicy subproperties. + + In order to provide editing widgets for the mentioned subproperties + in a property browser widget, this manager must be associated with + an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtEnumPropertyManager *QtSizePolicyPropertyManager::subEnumPropertyManager() const +{ + return d_ptr->m_enumPropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given property is not managed by this manager, this + function returns the default size policy. + + \sa setValue() +*/ +QSizePolicy QtSizePolicyPropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QSizePolicy()); +} + +/*! + \reimp +*/ +QString QtSizePolicyPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + const QSizePolicy sp = it.value(); + const QtMetaEnumProvider *mep = metaEnumProvider(); + const int hIndex = mep->sizePolicyToIndex(sp.horizontalPolicy()); + const int vIndex = mep->sizePolicyToIndex(sp.verticalPolicy()); + //! Unknown size policy on reading invalid uic3 files + const QString hPolicy = hIndex != -1 ? mep->policyEnumNames().at(hIndex) : tr(""); + const QString vPolicy = vIndex != -1 ? mep->policyEnumNames().at(vIndex) : tr(""); + const QString str = tr("[%1, %2, %3, %4]").arg(hPolicy, vPolicy).arg(sp.horizontalStretch()).arg(sp.verticalStretch()); + return str; +} + +/*! + \fn void QtSizePolicyPropertyManager::setValue(QtProperty *property, const QSizePolicy &value) + + Sets the value of the given \a property to \a value. Nested + properties are updated automatically. + + \sa value(), valueChanged() +*/ +void QtSizePolicyPropertyManager::setValue(QtProperty *property, QSizePolicy val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + if (it.value() == val) + return; + + it.value() = val; + + d_ptr->m_enumPropertyManager->setValue(d_ptr->m_propertyToHPolicy[property], + metaEnumProvider()->sizePolicyToIndex(val.horizontalPolicy())); + d_ptr->m_enumPropertyManager->setValue(d_ptr->m_propertyToVPolicy[property], + metaEnumProvider()->sizePolicyToIndex(val.verticalPolicy())); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToHStretch[property], + val.horizontalStretch()); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToVStretch[property], + val.verticalStretch()); + + emit propertyChanged(property); + emit valueChanged(property, val); +} + +/*! + \reimp +*/ +void QtSizePolicyPropertyManager::initializeProperty(QtProperty *property) +{ + QSizePolicy val; + d_ptr->m_values[property] = val; + + QtProperty *hPolicyProp = d_ptr->m_enumPropertyManager->addProperty(); + hPolicyProp->setPropertyName(tr("Horizontal Policy")); + d_ptr->m_enumPropertyManager->setEnumNames(hPolicyProp, metaEnumProvider()->policyEnumNames()); + d_ptr->m_enumPropertyManager->setValue(hPolicyProp, + metaEnumProvider()->sizePolicyToIndex(val.horizontalPolicy())); + d_ptr->m_propertyToHPolicy[property] = hPolicyProp; + d_ptr->m_hPolicyToProperty[hPolicyProp] = property; + property->addSubProperty(hPolicyProp); + + QtProperty *vPolicyProp = d_ptr->m_enumPropertyManager->addProperty(); + vPolicyProp->setPropertyName(tr("Vertical Policy")); + d_ptr->m_enumPropertyManager->setEnumNames(vPolicyProp, metaEnumProvider()->policyEnumNames()); + d_ptr->m_enumPropertyManager->setValue(vPolicyProp, + metaEnumProvider()->sizePolicyToIndex(val.verticalPolicy())); + d_ptr->m_propertyToVPolicy[property] = vPolicyProp; + d_ptr->m_vPolicyToProperty[vPolicyProp] = property; + property->addSubProperty(vPolicyProp); + + QtProperty *hStretchProp = d_ptr->m_intPropertyManager->addProperty(); + hStretchProp->setPropertyName(tr("Horizontal Stretch")); + d_ptr->m_intPropertyManager->setValue(hStretchProp, val.horizontalStretch()); + d_ptr->m_intPropertyManager->setRange(hStretchProp, 0, 0xff); + d_ptr->m_propertyToHStretch[property] = hStretchProp; + d_ptr->m_hStretchToProperty[hStretchProp] = property; + property->addSubProperty(hStretchProp); + + QtProperty *vStretchProp = d_ptr->m_intPropertyManager->addProperty(); + vStretchProp->setPropertyName(tr("Vertical Stretch")); + d_ptr->m_intPropertyManager->setValue(vStretchProp, val.verticalStretch()); + d_ptr->m_intPropertyManager->setRange(vStretchProp, 0, 0xff); + d_ptr->m_propertyToVStretch[property] = vStretchProp; + d_ptr->m_vStretchToProperty[vStretchProp] = property; + property->addSubProperty(vStretchProp); + +} + +/*! + \reimp +*/ +void QtSizePolicyPropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *hPolicyProp = d_ptr->m_propertyToHPolicy[property]; + if (hPolicyProp) { + d_ptr->m_hPolicyToProperty.remove(hPolicyProp); + delete hPolicyProp; + } + d_ptr->m_propertyToHPolicy.remove(property); + + QtProperty *vPolicyProp = d_ptr->m_propertyToVPolicy[property]; + if (vPolicyProp) { + d_ptr->m_vPolicyToProperty.remove(vPolicyProp); + delete vPolicyProp; + } + d_ptr->m_propertyToVPolicy.remove(property); + + QtProperty *hStretchProp = d_ptr->m_propertyToHStretch[property]; + if (hStretchProp) { + d_ptr->m_hStretchToProperty.remove(hStretchProp); + delete hStretchProp; + } + d_ptr->m_propertyToHStretch.remove(property); + + QtProperty *vStretchProp = d_ptr->m_propertyToVStretch[property]; + if (vStretchProp) { + d_ptr->m_vStretchToProperty.remove(vStretchProp); + delete vStretchProp; + } + d_ptr->m_propertyToVStretch.remove(property); + + d_ptr->m_values.remove(property); +} + +// QtFontPropertyManager: +// QtFontPropertyManagerPrivate has a mechanism for reacting +// to QApplication::fontDatabaseChanged() [4.5], which is emitted +// when someone loads an application font. The signals are compressed +// using a timer with interval 0, which then causes the family +// enumeration manager to re-set its strings and index values +// for each property. + +class QtFontPropertyManagerPrivate +{ + QtFontPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtFontPropertyManager) +public: + + QtFontPropertyManagerPrivate(); + + void slotIntChanged(QtProperty *property, int value); + void slotEnumChanged(QtProperty *property, int value); + void slotBoolChanged(QtProperty *property, bool value); + void slotPropertyDestroyed(QtProperty *property); + void slotFontDatabaseChanged(); + void slotFontDatabaseDelayedChange(); + + QStringList m_familyNames; + + QHash m_values; + + QtIntPropertyManager *m_intPropertyManager; + QtEnumPropertyManager *m_enumPropertyManager; + QtBoolPropertyManager *m_boolPropertyManager; + + QHash m_propertyToFamily; + QHash m_propertyToPointSize; + QHash m_propertyToBold; + QHash m_propertyToItalic; + QHash m_propertyToUnderline; + QHash m_propertyToStrikeOut; + QHash m_propertyToKerning; + QHash m_propertyToWeight; + + QHash m_familyToProperty; + QHash m_pointSizeToProperty; + QHash m_boldToProperty; + QHash m_italicToProperty; + QHash m_underlineToProperty; + QHash m_strikeOutToProperty; + QHash m_kerningToProperty; + QHash m_weightToProperty; + + bool m_settingValue; + QTimer *m_fontDatabaseChangeTimer; +}; + +QtFontPropertyManagerPrivate::QtFontPropertyManagerPrivate() : + m_settingValue(false), + m_fontDatabaseChangeTimer(0) +{ +} + +void QtFontPropertyManagerPrivate::slotIntChanged(QtProperty *property, int value) +{ + if (m_settingValue) + return; + if (QtProperty *prop = m_pointSizeToProperty.value(property, nullptr)) { + QFont f = m_values[prop]; + f.setPointSize(value); + q_ptr->setValue(prop, f); + } +} + +void QtFontPropertyManagerPrivate::slotEnumChanged(QtProperty *property, int value) +{ + if (m_settingValue) + return; + if (QtProperty *prop = m_familyToProperty.value(property, nullptr)) { + QFont f = m_values[prop]; + f.setFamily(m_familyNames.at(value)); + q_ptr->setValue(prop, f); + } else if (auto *prop = m_weightToProperty.value(property, nullptr)) { + QFont f = m_values[prop]; + f.setWeight(weightFromIndex(value)); + q_ptr->setValue(prop, f); + } +} + +void QtFontPropertyManagerPrivate::slotBoolChanged(QtProperty *property, bool value) +{ + if (m_settingValue) + return; + if (QtProperty *prop = m_boldToProperty.value(property, nullptr)) { + QFont f = m_values[prop]; + f.setBold(value); + q_ptr->setValue(prop, f); + } else if (QtProperty *prop = m_italicToProperty.value(property, nullptr)) { + QFont f = m_values[prop]; + f.setItalic(value); + q_ptr->setValue(prop, f); + } else if (QtProperty *prop = m_underlineToProperty.value(property, nullptr)) { + QFont f = m_values[prop]; + f.setUnderline(value); + q_ptr->setValue(prop, f); + } else if (QtProperty *prop = m_strikeOutToProperty.value(property, nullptr)) { + QFont f = m_values[prop]; + f.setStrikeOut(value); + q_ptr->setValue(prop, f); + } else if (QtProperty *prop = m_kerningToProperty.value(property, nullptr)) { + QFont f = m_values[prop]; + f.setKerning(value); + q_ptr->setValue(prop, f); + } +} + +void QtFontPropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *pointProp = m_pointSizeToProperty.value(property, nullptr)) { + m_propertyToPointSize[pointProp] = nullptr; + m_pointSizeToProperty.remove(property); + } else if (QtProperty *pointProp = m_familyToProperty.value(property, nullptr)) { + m_propertyToFamily[pointProp] = nullptr; + m_familyToProperty.remove(property); + } else if (QtProperty *pointProp = m_boldToProperty.value(property, nullptr)) { + m_propertyToBold[pointProp] = nullptr; + m_boldToProperty.remove(property); + } else if (QtProperty *pointProp = m_italicToProperty.value(property, nullptr)) { + m_propertyToItalic[pointProp] = nullptr; + m_italicToProperty.remove(property); + } else if (QtProperty *pointProp = m_underlineToProperty.value(property, nullptr)) { + m_propertyToUnderline[pointProp] = nullptr; + m_underlineToProperty.remove(property); + } else if (QtProperty *pointProp = m_strikeOutToProperty.value(property, nullptr)) { + m_propertyToStrikeOut[pointProp] = nullptr; + m_strikeOutToProperty.remove(property); + } else if (QtProperty *pointProp = m_kerningToProperty.value(property, nullptr)) { + m_propertyToKerning[pointProp] = nullptr; + m_kerningToProperty.remove(property); + } else if (QtProperty *weightProp = m_weightToProperty.value(property, nullptr)) { + m_propertyToWeight[weightProp] = nullptr; + m_weightToProperty.remove(property); + } +} + +void QtFontPropertyManagerPrivate::slotFontDatabaseChanged() +{ + if (!m_fontDatabaseChangeTimer) { + m_fontDatabaseChangeTimer = new QTimer(q_ptr); + m_fontDatabaseChangeTimer->setInterval(0); + m_fontDatabaseChangeTimer->setSingleShot(true); + QObject::connect(m_fontDatabaseChangeTimer, &QTimer::timeout, q_ptr, + [this] { slotFontDatabaseDelayedChange(); }); + } + if (!m_fontDatabaseChangeTimer->isActive()) + m_fontDatabaseChangeTimer->start(); +} + +void QtFontPropertyManagerPrivate::slotFontDatabaseDelayedChange() +{ + // rescan available font names + const QStringList oldFamilies = m_familyNames; + m_familyNames = QFontDatabase::families(); + + // Adapt all existing properties + if (!m_propertyToFamily.isEmpty()) { + for (QtProperty *familyProp : std::as_const(m_propertyToFamily)) { + const int oldIdx = m_enumPropertyManager->value(familyProp); + int newIdx = m_familyNames.indexOf(oldFamilies.at(oldIdx)); + if (newIdx < 0) + newIdx = 0; + m_enumPropertyManager->setEnumNames(familyProp, m_familyNames); + m_enumPropertyManager->setValue(familyProp, newIdx); + } + } +} + +/*! + \class QtFontPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtFontPropertyManager provides and manages QFont properties. + + A font property has nested \e family, \e pointSize, \e bold, \e + italic, \e underline, \e strikeOut and \e kerning subproperties. The top-level + property's value can be retrieved using the value() function, and + set using the setValue() slot. + + The subproperties are created by QtIntPropertyManager, QtEnumPropertyManager and + QtBoolPropertyManager objects. These managers can be retrieved using the + corresponding subIntPropertyManager(), subEnumPropertyManager() and + subBoolPropertyManager() functions. In order to provide editing widgets + for the subproperties in a property browser widget, these managers + must be associated with editor factories. + + In addition, QtFontPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes. + + \sa QtAbstractPropertyManager, QtEnumPropertyManager, QtIntPropertyManager, QtBoolPropertyManager +*/ + +/*! + \fn void QtFontPropertyManager::valueChanged(QtProperty *property, const QFont &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the + new \a value as parameters. + + \sa setValue() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtFontPropertyManager::QtFontPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtFontPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + QObject::connect(qApp, &QGuiApplication::fontDatabaseChanged, this, + [this] { d_ptr->slotFontDatabaseChanged(); }); + + d_ptr->m_intPropertyManager = new QtIntPropertyManager(this); + connect(d_ptr->m_intPropertyManager, &QtIntPropertyManager::valueChanged, this, + [this](QtProperty *property, int value) { d_ptr->slotIntChanged(property, value); }); + connect(d_ptr->m_intPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); + + d_ptr->m_enumPropertyManager = new QtEnumPropertyManager(this); + connect(d_ptr->m_enumPropertyManager, &QtEnumPropertyManager::valueChanged, this, + [this](QtProperty *property, int value) { d_ptr->slotEnumChanged(property, value); }); + connect(d_ptr->m_enumPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); + + d_ptr->m_boolPropertyManager = new QtBoolPropertyManager(this); + connect(d_ptr->m_boolPropertyManager, &QtBoolPropertyManager::valueChanged, this, + [this](QtProperty *property, bool value) { d_ptr->slotBoolChanged(property, value); }); + connect(d_ptr->m_boolPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtFontPropertyManager::~QtFontPropertyManager() +{ + clear(); +} + +/*! + Returns the manager that creates the \e pointSize subproperty. + + In order to provide editing widgets for the \e pointSize property + in a property browser widget, this manager must be associated + with an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtIntPropertyManager *QtFontPropertyManager::subIntPropertyManager() const +{ + return d_ptr->m_intPropertyManager; +} + +/*! + Returns the manager that create the \e family subproperty. + + In order to provide editing widgets for the \e family property + in a property browser widget, this manager must be associated + with an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtEnumPropertyManager *QtFontPropertyManager::subEnumPropertyManager() const +{ + return d_ptr->m_enumPropertyManager; +} + +/*! + Returns the manager that creates the \e bold, \e italic, \e underline, + \e strikeOut and \e kerning subproperties. + + In order to provide editing widgets for the mentioned properties + in a property browser widget, this manager must be associated with + an editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtBoolPropertyManager *QtFontPropertyManager::subBoolPropertyManager() const +{ + return d_ptr->m_boolPropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given property is not managed by this manager, this + function returns a font object that uses the application's default + font. + + \sa setValue() +*/ +QFont QtFontPropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QFont()); +} + +/*! + \reimp +*/ +QString QtFontPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + return QtPropertyBrowserUtils::fontValueText(it.value()); +} + +/*! + \reimp +*/ +QIcon QtFontPropertyManager::valueIcon(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + return QtPropertyBrowserUtils::fontValueIcon(it.value()); +} + +/*! + \fn void QtFontPropertyManager::setValue(QtProperty *property, const QFont &value) + + Sets the value of the given \a property to \a value. Nested + properties are updated automatically. + + \sa value(), valueChanged() +*/ +void QtFontPropertyManager::setValue(QtProperty *property, const QFont &val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + const QFont oldVal = it.value(); + if (oldVal == val && oldVal.resolveMask() == val.resolveMask()) + return; + + it.value() = val; + + int idx = d_ptr->m_familyNames.indexOf(val.family()); + if (idx == -1) + idx = 0; + bool settingValue = d_ptr->m_settingValue; + d_ptr->m_settingValue = true; + d_ptr->m_enumPropertyManager->setValue(d_ptr->m_propertyToFamily[property], idx); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToPointSize[property], val.pointSize()); + d_ptr->m_boolPropertyManager->setValue(d_ptr->m_propertyToBold[property], val.bold()); + d_ptr->m_boolPropertyManager->setValue(d_ptr->m_propertyToItalic[property], val.italic()); + d_ptr->m_boolPropertyManager->setValue(d_ptr->m_propertyToUnderline[property], val.underline()); + d_ptr->m_boolPropertyManager->setValue(d_ptr->m_propertyToStrikeOut[property], val.strikeOut()); + d_ptr->m_boolPropertyManager->setValue(d_ptr->m_propertyToKerning[property], val.kerning()); + d_ptr->m_enumPropertyManager->setValue(d_ptr->m_propertyToWeight[property], + indexOfFontWeight(val.weight())); + d_ptr->m_settingValue = settingValue; + + emit propertyChanged(property); + emit valueChanged(property, val); +} + +static QStringList fontWeightNames() +{ + static const DisambiguatedTranslation weightsC[] = { + QT_TRANSLATE_NOOP3("FontPropertyManager", "Thin", "QFont::Weight combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "ExtraLight", "QFont::Weight combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "Light", "QFont::Weight combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "Normal", "QFont::Weight combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "Medium", "QFont::Weight combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "DemiBold", "QFont::Weight combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "Bold", "QFont::Weight combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "ExtraBold", "QFont::Weight combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "Black", "QFont::Weight combo") + }; + + QStringList result; + for (const auto &w : weightsC) + result.append(QCoreApplication::translate("FontPropertyManager", w.first, w.second)); + return result; +} + +/*! + \reimp +*/ +void QtFontPropertyManager::initializeProperty(QtProperty *property) +{ + QFont val; + d_ptr->m_values[property] = val; + + QtProperty *familyProp = d_ptr->m_enumPropertyManager->addProperty(); + familyProp->setPropertyName(tr("Family")); + if (d_ptr->m_familyNames.isEmpty()) + d_ptr->m_familyNames = QFontDatabase::families(); + d_ptr->m_enumPropertyManager->setEnumNames(familyProp, d_ptr->m_familyNames); + int idx = d_ptr->m_familyNames.indexOf(val.family()); + if (idx == -1) + idx = 0; + d_ptr->m_enumPropertyManager->setValue(familyProp, idx); + d_ptr->m_propertyToFamily[property] = familyProp; + d_ptr->m_familyToProperty[familyProp] = property; + property->addSubProperty(familyProp); + + QtProperty *pointSizeProp = d_ptr->m_intPropertyManager->addProperty(); + pointSizeProp->setPropertyName(tr("Point Size")); + d_ptr->m_intPropertyManager->setValue(pointSizeProp, val.pointSize()); + d_ptr->m_intPropertyManager->setMinimum(pointSizeProp, 1); + d_ptr->m_propertyToPointSize[property] = pointSizeProp; + d_ptr->m_pointSizeToProperty[pointSizeProp] = property; + property->addSubProperty(pointSizeProp); + + QtProperty *boldProp = d_ptr->m_boolPropertyManager->addProperty(); + boldProp->setPropertyName(tr("Bold", "Bold toggle")); + d_ptr->m_boolPropertyManager->setValue(boldProp, val.bold()); + d_ptr->m_propertyToBold[property] = boldProp; + d_ptr->m_boldToProperty[boldProp] = property; + property->addSubProperty(boldProp); + + QtProperty *italicProp = d_ptr->m_boolPropertyManager->addProperty(); + italicProp->setPropertyName(tr("Italic")); + d_ptr->m_boolPropertyManager->setValue(italicProp, val.italic()); + d_ptr->m_propertyToItalic[property] = italicProp; + d_ptr->m_italicToProperty[italicProp] = property; + property->addSubProperty(italicProp); + + QtProperty *underlineProp = d_ptr->m_boolPropertyManager->addProperty(); + underlineProp->setPropertyName(tr("Underline")); + d_ptr->m_boolPropertyManager->setValue(underlineProp, val.underline()); + d_ptr->m_propertyToUnderline[property] = underlineProp; + d_ptr->m_underlineToProperty[underlineProp] = property; + property->addSubProperty(underlineProp); + + QtProperty *strikeOutProp = d_ptr->m_boolPropertyManager->addProperty(); + strikeOutProp->setPropertyName(tr("Strikeout")); + d_ptr->m_boolPropertyManager->setValue(strikeOutProp, val.strikeOut()); + d_ptr->m_propertyToStrikeOut[property] = strikeOutProp; + d_ptr->m_strikeOutToProperty[strikeOutProp] = property; + property->addSubProperty(strikeOutProp); + + QtProperty *kerningProp = d_ptr->m_boolPropertyManager->addProperty(); + kerningProp->setPropertyName(tr("Kerning")); + d_ptr->m_boolPropertyManager->setValue(kerningProp, val.kerning()); + d_ptr->m_propertyToKerning[property] = kerningProp; + d_ptr->m_kerningToProperty[kerningProp] = property; + property->addSubProperty(kerningProp); + + auto *weightProp = d_ptr->m_enumPropertyManager->addProperty(); + weightProp->setPropertyName(tr("Weight")); + static const QStringList weightNames = fontWeightNames(); + d_ptr->m_enumPropertyManager->setEnumNames(weightProp, weightNames); + d_ptr->m_enumPropertyManager->setValue(weightProp, indexOfFontWeight(val.weight())); + d_ptr->m_propertyToWeight[property] = weightProp; + d_ptr->m_weightToProperty[weightProp] = property; + property->addSubProperty(weightProp); +} + +/*! + \reimp +*/ +void QtFontPropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *familyProp = d_ptr->m_propertyToFamily[property]; + if (familyProp) { + d_ptr->m_familyToProperty.remove(familyProp); + delete familyProp; + } + d_ptr->m_propertyToFamily.remove(property); + + QtProperty *pointSizeProp = d_ptr->m_propertyToPointSize[property]; + if (pointSizeProp) { + d_ptr->m_pointSizeToProperty.remove(pointSizeProp); + delete pointSizeProp; + } + d_ptr->m_propertyToPointSize.remove(property); + + QtProperty *boldProp = d_ptr->m_propertyToBold[property]; + if (boldProp) { + d_ptr->m_boldToProperty.remove(boldProp); + delete boldProp; + } + d_ptr->m_propertyToBold.remove(property); + + QtProperty *italicProp = d_ptr->m_propertyToItalic[property]; + if (italicProp) { + d_ptr->m_italicToProperty.remove(italicProp); + delete italicProp; + } + d_ptr->m_propertyToItalic.remove(property); + + QtProperty *underlineProp = d_ptr->m_propertyToUnderline[property]; + if (underlineProp) { + d_ptr->m_underlineToProperty.remove(underlineProp); + delete underlineProp; + } + d_ptr->m_propertyToUnderline.remove(property); + + QtProperty *strikeOutProp = d_ptr->m_propertyToStrikeOut[property]; + if (strikeOutProp) { + d_ptr->m_strikeOutToProperty.remove(strikeOutProp); + delete strikeOutProp; + } + d_ptr->m_propertyToStrikeOut.remove(property); + + QtProperty *kerningProp = d_ptr->m_propertyToKerning[property]; + if (kerningProp) { + d_ptr->m_kerningToProperty.remove(kerningProp); + delete kerningProp; + } + d_ptr->m_propertyToKerning.remove(property); + + if (auto weightProp = d_ptr->m_propertyToWeight[property]) { + d_ptr->m_weightToProperty.remove(weightProp); + delete weightProp; + } + + d_ptr->m_values.remove(property); +} + +// QtColorPropertyManager + +class QtColorPropertyManagerPrivate +{ + QtColorPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtColorPropertyManager) +public: + + void slotIntChanged(QtProperty *property, int value); + void slotPropertyDestroyed(QtProperty *property); + + QHash m_values; + + QtIntPropertyManager *m_intPropertyManager; + + QHash m_propertyToR; + QHash m_propertyToG; + QHash m_propertyToB; + QHash m_propertyToA; + + QHash m_rToProperty; + QHash m_gToProperty; + QHash m_bToProperty; + QHash m_aToProperty; +}; + +void QtColorPropertyManagerPrivate::slotIntChanged(QtProperty *property, int value) +{ + if (QtProperty *prop = m_rToProperty.value(property, nullptr)) { + QColor c = m_values[prop]; + c.setRed(value); + q_ptr->setValue(prop, c); + } else if (QtProperty *prop = m_gToProperty.value(property, nullptr)) { + QColor c = m_values[prop]; + c.setGreen(value); + q_ptr->setValue(prop, c); + } else if (QtProperty *prop = m_bToProperty.value(property, nullptr)) { + QColor c = m_values[prop]; + c.setBlue(value); + q_ptr->setValue(prop, c); + } else if (QtProperty *prop = m_aToProperty.value(property, nullptr)) { + QColor c = m_values[prop]; + c.setAlpha(value); + q_ptr->setValue(prop, c); + } +} + +void QtColorPropertyManagerPrivate::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *pointProp = m_rToProperty.value(property, nullptr)) { + m_propertyToR[pointProp] = nullptr; + m_rToProperty.remove(property); + } else if (QtProperty *pointProp = m_gToProperty.value(property, nullptr)) { + m_propertyToG[pointProp] = nullptr; + m_gToProperty.remove(property); + } else if (QtProperty *pointProp = m_bToProperty.value(property, nullptr)) { + m_propertyToB[pointProp] = nullptr; + m_bToProperty.remove(property); + } else if (QtProperty *pointProp = m_aToProperty.value(property, nullptr)) { + m_propertyToA[pointProp] = nullptr; + m_aToProperty.remove(property); + } +} + +/*! + \class QtColorPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtColorPropertyManager provides and manages QColor properties. + + A color property has nested \e red, \e green and \e blue + subproperties. The top-level property's value can be retrieved + using the value() function, and set using the setValue() slot. + + The subproperties are created by a QtIntPropertyManager object. This + manager can be retrieved using the subIntPropertyManager() function. In + order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + In addition, QtColorPropertyManager provides the valueChanged() signal + which is emitted whenever a property created by this manager + changes. + + \sa QtAbstractPropertyManager, QtAbstractPropertyBrowser, QtIntPropertyManager +*/ + +/*! + \fn void QtColorPropertyManager::valueChanged(QtProperty *property, const QColor &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtColorPropertyManager::QtColorPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtColorPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_intPropertyManager = new QtIntPropertyManager(this); + connect(d_ptr->m_intPropertyManager, &QtIntPropertyManager::valueChanged, this, + [this](QtProperty *property, int value) { d_ptr->slotIntChanged(property, value); }); + connect(d_ptr->m_intPropertyManager, &QtAbstractPropertyManager::propertyDestroyed, this, + [this](QtProperty *property) { d_ptr->slotPropertyDestroyed(property); }); +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtColorPropertyManager::~QtColorPropertyManager() +{ + clear(); +} + +/*! + Returns the manager that produces the nested \e red, \e green and + \e blue subproperties. + + In order to provide editing widgets for the subproperties in a + property browser widget, this manager must be associated with an + editor factory. + + \sa QtAbstractPropertyBrowser::setFactoryForManager() +*/ +QtIntPropertyManager *QtColorPropertyManager::subIntPropertyManager() const +{ + return d_ptr->m_intPropertyManager; +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by \e this manager, this + function returns an invalid color. + + \sa setValue() +*/ +QColor QtColorPropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QColor()); +} + +/*! + \reimp +*/ + +QString QtColorPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + return QtPropertyBrowserUtils::colorValueText(it.value()); +} + +/*! + \reimp +*/ + +QIcon QtColorPropertyManager::valueIcon(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + return QtPropertyBrowserUtils::brushValueIcon(QBrush(it.value())); +} + +/*! + \fn void QtColorPropertyManager::setValue(QtProperty *property, const QColor &value) + + Sets the value of the given \a property to \a value. Nested + properties are updated automatically. + + \sa value(), valueChanged() +*/ +void QtColorPropertyManager::setValue(QtProperty *property, QColor val) +{ + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + if (it.value() == val) + return; + + it.value() = val; + + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToR[property], val.red()); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToG[property], val.green()); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToB[property], val.blue()); + d_ptr->m_intPropertyManager->setValue(d_ptr->m_propertyToA[property], val.alpha()); + + emit propertyChanged(property); + emit valueChanged(property, val); +} + +/*! + \reimp +*/ +void QtColorPropertyManager::initializeProperty(QtProperty *property) +{ + QColor val; + d_ptr->m_values[property] = val; + + QtProperty *rProp = d_ptr->m_intPropertyManager->addProperty(); + rProp->setPropertyName(tr("Red")); + d_ptr->m_intPropertyManager->setValue(rProp, val.red()); + d_ptr->m_intPropertyManager->setRange(rProp, 0, 0xFF); + d_ptr->m_propertyToR[property] = rProp; + d_ptr->m_rToProperty[rProp] = property; + property->addSubProperty(rProp); + + QtProperty *gProp = d_ptr->m_intPropertyManager->addProperty(); + gProp->setPropertyName(tr("Green")); + d_ptr->m_intPropertyManager->setValue(gProp, val.green()); + d_ptr->m_intPropertyManager->setRange(gProp, 0, 0xFF); + d_ptr->m_propertyToG[property] = gProp; + d_ptr->m_gToProperty[gProp] = property; + property->addSubProperty(gProp); + + QtProperty *bProp = d_ptr->m_intPropertyManager->addProperty(); + bProp->setPropertyName(tr("Blue")); + d_ptr->m_intPropertyManager->setValue(bProp, val.blue()); + d_ptr->m_intPropertyManager->setRange(bProp, 0, 0xFF); + d_ptr->m_propertyToB[property] = bProp; + d_ptr->m_bToProperty[bProp] = property; + property->addSubProperty(bProp); + + QtProperty *aProp = d_ptr->m_intPropertyManager->addProperty(); + aProp->setPropertyName(tr("Alpha")); + d_ptr->m_intPropertyManager->setValue(aProp, val.alpha()); + d_ptr->m_intPropertyManager->setRange(aProp, 0, 0xFF); + d_ptr->m_propertyToA[property] = aProp; + d_ptr->m_aToProperty[aProp] = property; + property->addSubProperty(aProp); +} + +/*! + \reimp +*/ +void QtColorPropertyManager::uninitializeProperty(QtProperty *property) +{ + QtProperty *rProp = d_ptr->m_propertyToR[property]; + if (rProp) { + d_ptr->m_rToProperty.remove(rProp); + delete rProp; + } + d_ptr->m_propertyToR.remove(property); + + QtProperty *gProp = d_ptr->m_propertyToG[property]; + if (gProp) { + d_ptr->m_gToProperty.remove(gProp); + delete gProp; + } + d_ptr->m_propertyToG.remove(property); + + QtProperty *bProp = d_ptr->m_propertyToB[property]; + if (bProp) { + d_ptr->m_bToProperty.remove(bProp); + delete bProp; + } + d_ptr->m_propertyToB.remove(property); + + QtProperty *aProp = d_ptr->m_propertyToA[property]; + if (aProp) { + d_ptr->m_aToProperty.remove(aProp); + delete aProp; + } + d_ptr->m_propertyToA.remove(property); + + d_ptr->m_values.remove(property); +} + +// QtCursorPropertyManager + +class QtCursorPropertyManagerPrivate +{ + QtCursorPropertyManager *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtCursorPropertyManager) +public: + QHash m_values; +}; + +/*! + \class QtCursorPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtCursorPropertyManager provides and manages QCursor properties. + + A cursor property has a current value which can be + retrieved using the value() function, and set using the setValue() + slot. In addition, QtCursorPropertyManager provides the + valueChanged() signal which is emitted whenever a property created + by this manager changes. + + \sa QtAbstractPropertyManager +*/ + +/*! + \fn void QtCursorPropertyManager::valueChanged(QtProperty *property, const QCursor &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the new + \a value as parameters. + + \sa setValue() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtCursorPropertyManager::QtCursorPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtCursorPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtCursorPropertyManager::~QtCursorPropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns a default QCursor object. + + \sa setValue() +*/ +#ifndef QT_NO_CURSOR +QCursor QtCursorPropertyManager::value(const QtProperty *property) const +{ + return d_ptr->m_values.value(property, QCursor()); +} +#endif + +/*! + \reimp +*/ +QString QtCursorPropertyManager::valueText(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + return QtCursorDatabase::instance()->cursorToShapeName(it.value()); +} + +/*! + \reimp +*/ +QIcon QtCursorPropertyManager::valueIcon(const QtProperty *property) const +{ + const auto it = d_ptr->m_values.constFind(property); + if (it == d_ptr->m_values.constEnd()) + return {}; + + return QtCursorDatabase::instance()->cursorToShapeIcon(it.value()); +} + +/*! + \fn void QtCursorPropertyManager::setValue(QtProperty *property, const QCursor &value) + + Sets the value of the given \a property to \a value. + + \sa value(), valueChanged() +*/ +void QtCursorPropertyManager::setValue(QtProperty *property, const QCursor &value) +{ +#ifndef QT_NO_CURSOR + const auto it = d_ptr->m_values.find(property); + if (it == d_ptr->m_values.end()) + return; + + if (it.value().shape() == value.shape() && value.shape() != Qt::BitmapCursor) + return; + + it.value() = value; + + emit propertyChanged(property); + emit valueChanged(property, value); +#endif +} + +/*! + \reimp +*/ +void QtCursorPropertyManager::initializeProperty(QtProperty *property) +{ +#ifndef QT_NO_CURSOR + d_ptr->m_values[property] = QCursor(); +#endif +} + +/*! + \reimp +*/ +void QtCursorPropertyManager::uninitializeProperty(QtProperty *property) +{ + d_ptr->m_values.remove(property); +} + +QT_END_NAMESPACE + +#include "moc_qtpropertymanager_p.cpp" +#include "qtpropertymanager.moc" diff --git a/src/shared/qtpropertybrowser/qtpropertymanager_p.h b/src/shared/qtpropertybrowser/qtpropertymanager_p.h new file mode 100644 index 00000000000..33e855d945c --- /dev/null +++ b/src/shared/qtpropertybrowser/qtpropertymanager_p.h @@ -0,0 +1,693 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTPROPERTYMANAGER_H +#define QTPROPERTYMANAGER_H + +#include "qtpropertybrowser_p.h" + +QT_BEGIN_NAMESPACE + +class QDate; +class QTime; +class QDateTime; +class QLocale; +class QRegularExpression; + +class QtGroupPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtGroupPropertyManager(QObject *parent = 0); + ~QtGroupPropertyManager(); + +protected: + bool hasValue(const QtProperty *property) const override; + + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +}; + +class QtIntPropertyManagerPrivate; + +class QtIntPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtIntPropertyManager(QObject *parent = 0); + ~QtIntPropertyManager(); + + int value(const QtProperty *property) const; + int minimum(const QtProperty *property) const; + int maximum(const QtProperty *property) const; + int singleStep(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, int val); + void setMinimum(QtProperty *property, int minVal); + void setMaximum(QtProperty *property, int maxVal); + void setRange(QtProperty *property, int minVal, int maxVal); + void setSingleStep(QtProperty *property, int step); +Q_SIGNALS: + void valueChanged(QtProperty *property, int val); + void rangeChanged(QtProperty *property, int minVal, int maxVal); + void singleStepChanged(QtProperty *property, int step); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtIntPropertyManager) + Q_DISABLE_COPY_MOVE(QtIntPropertyManager) +}; + +class QtBoolPropertyManagerPrivate; + +class QtBoolPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtBoolPropertyManager(QObject *parent = 0); + ~QtBoolPropertyManager(); + + bool value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, bool val); +Q_SIGNALS: + void valueChanged(QtProperty *property, bool val); +protected: + QString valueText(const QtProperty *property) const override; + QIcon valueIcon(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtBoolPropertyManager) + Q_DISABLE_COPY_MOVE(QtBoolPropertyManager) +}; + +class QtDoublePropertyManagerPrivate; + +class QtDoublePropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtDoublePropertyManager(QObject *parent = 0); + ~QtDoublePropertyManager(); + + double value(const QtProperty *property) const; + double minimum(const QtProperty *property) const; + double maximum(const QtProperty *property) const; + double singleStep(const QtProperty *property) const; + int decimals(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, double val); + void setMinimum(QtProperty *property, double minVal); + void setMaximum(QtProperty *property, double maxVal); + void setRange(QtProperty *property, double minVal, double maxVal); + void setSingleStep(QtProperty *property, double step); + void setDecimals(QtProperty *property, int prec); +Q_SIGNALS: + void valueChanged(QtProperty *property, double val); + void rangeChanged(QtProperty *property, double minVal, double maxVal); + void singleStepChanged(QtProperty *property, double step); + void decimalsChanged(QtProperty *property, int prec); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtDoublePropertyManager) + Q_DISABLE_COPY_MOVE(QtDoublePropertyManager) +}; + +class QtStringPropertyManagerPrivate; + +class QtStringPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtStringPropertyManager(QObject *parent = 0); + ~QtStringPropertyManager(); + + QString value(const QtProperty *property) const; + QRegularExpression regExp(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, const QString &val); + void setRegExp(QtProperty *property, const QRegularExpression ®Exp); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QString &val); + void regExpChanged(QtProperty *property, const QRegularExpression ®Exp); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtStringPropertyManager) + Q_DISABLE_COPY_MOVE(QtStringPropertyManager) +}; + +class QtDatePropertyManagerPrivate; + +class QtDatePropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtDatePropertyManager(QObject *parent = 0); + ~QtDatePropertyManager(); + + QDate value(const QtProperty *property) const; + QDate minimum(const QtProperty *property) const; + QDate maximum(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, QDate val); + void setMinimum(QtProperty *property, QDate minVal); + void setMaximum(QtProperty *property, QDate maxVal); + void setRange(QtProperty *property, QDate minVal, QDate maxVal); +Q_SIGNALS: + void valueChanged(QtProperty *property, QDate val); + void rangeChanged(QtProperty *property, QDate minVal, QDate maxVal); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtDatePropertyManager) + Q_DISABLE_COPY_MOVE(QtDatePropertyManager) +}; + +class QtTimePropertyManagerPrivate; + +class QtTimePropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtTimePropertyManager(QObject *parent = 0); + ~QtTimePropertyManager(); + + QTime value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, QTime val); +Q_SIGNALS: + void valueChanged(QtProperty *property, QTime val); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtTimePropertyManager) + Q_DISABLE_COPY_MOVE(QtTimePropertyManager) +}; + +class QtDateTimePropertyManagerPrivate; + +class QtDateTimePropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtDateTimePropertyManager(QObject *parent = 0); + ~QtDateTimePropertyManager(); + + QDateTime value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, const QDateTime &val); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QDateTime &val); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtDateTimePropertyManager) + Q_DISABLE_COPY_MOVE(QtDateTimePropertyManager) +}; + +class QtKeySequencePropertyManagerPrivate; + +class QtKeySequencePropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtKeySequencePropertyManager(QObject *parent = 0); + ~QtKeySequencePropertyManager(); + + QKeySequence value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, const QKeySequence &val); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QKeySequence &val); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtKeySequencePropertyManager) + Q_DISABLE_COPY_MOVE(QtKeySequencePropertyManager) +}; + +class QtCharPropertyManagerPrivate; + +class QtCharPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtCharPropertyManager(QObject *parent = 0); + ~QtCharPropertyManager(); + + QChar value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, const QChar &val); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QChar &val); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtCharPropertyManager) + Q_DISABLE_COPY_MOVE(QtCharPropertyManager) +}; + +class QtEnumPropertyManager; +class QtLocalePropertyManagerPrivate; + +class QtLocalePropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtLocalePropertyManager(QObject *parent = 0); + ~QtLocalePropertyManager(); + + QtEnumPropertyManager *subEnumPropertyManager() const; + + QLocale value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, const QLocale &val); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QLocale &val); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtLocalePropertyManager) + Q_DISABLE_COPY_MOVE(QtLocalePropertyManager) +}; + +class QtPointPropertyManagerPrivate; + +class QtPointPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtPointPropertyManager(QObject *parent = 0); + ~QtPointPropertyManager(); + + QtIntPropertyManager *subIntPropertyManager() const; + + QPoint value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, QPoint val); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QPoint &val); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtPointPropertyManager) + Q_DISABLE_COPY_MOVE(QtPointPropertyManager) +}; + +class QtPointFPropertyManagerPrivate; + +class QtPointFPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtPointFPropertyManager(QObject *parent = 0); + ~QtPointFPropertyManager(); + + QtDoublePropertyManager *subDoublePropertyManager() const; + + QPointF value(const QtProperty *property) const; + int decimals(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, QPointF val); + void setDecimals(QtProperty *property, int prec); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QPointF &val); + void decimalsChanged(QtProperty *property, int prec); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtPointFPropertyManager) + Q_DISABLE_COPY_MOVE(QtPointFPropertyManager) +}; + +class QtSizePropertyManagerPrivate; + +class QtSizePropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtSizePropertyManager(QObject *parent = 0); + ~QtSizePropertyManager(); + + QtIntPropertyManager *subIntPropertyManager() const; + + QSize value(const QtProperty *property) const; + QSize minimum(const QtProperty *property) const; + QSize maximum(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, QSize val); + void setMinimum(QtProperty *property, QSize minVal); + void setMaximum(QtProperty *property, QSize maxVal); + void setRange(QtProperty *property, QSize minVal, QSize maxVal); +Q_SIGNALS: + void valueChanged(QtProperty *property, QSize val); + void rangeChanged(QtProperty *property, QSize minVal, QSize maxVal); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtSizePropertyManager) + Q_DISABLE_COPY_MOVE(QtSizePropertyManager) +}; + +class QtSizeFPropertyManagerPrivate; + +class QtSizeFPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtSizeFPropertyManager(QObject *parent = 0); + ~QtSizeFPropertyManager(); + + QtDoublePropertyManager *subDoublePropertyManager() const; + + QSizeF value(const QtProperty *property) const; + QSizeF minimum(const QtProperty *property) const; + QSizeF maximum(const QtProperty *property) const; + int decimals(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, QSizeF val); + void setMinimum(QtProperty *property, QSizeF minVal); + void setMaximum(QtProperty *property, QSizeF maxVal); + void setRange(QtProperty *property, QSizeF minVal, QSizeF maxVal); + void setDecimals(QtProperty *property, int prec); +Q_SIGNALS: + void valueChanged(QtProperty *property, QSizeF val); + void rangeChanged(QtProperty *property, QSizeF minVal, QSizeF maxVal); + void decimalsChanged(QtProperty *property, int prec); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtSizeFPropertyManager) + Q_DISABLE_COPY_MOVE(QtSizeFPropertyManager) +}; + +class QtRectPropertyManagerPrivate; + +class QtRectPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtRectPropertyManager(QObject *parent = 0); + ~QtRectPropertyManager(); + + QtIntPropertyManager *subIntPropertyManager() const; + + QRect value(const QtProperty *property) const; + QRect constraint(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, QRect val); + void setConstraint(QtProperty *property, QRect constraint); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QRect &val); + void constraintChanged(QtProperty *property, const QRect &constraint); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtRectPropertyManager) + Q_DISABLE_COPY_MOVE(QtRectPropertyManager) +}; + +class QtRectFPropertyManagerPrivate; + +class QtRectFPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtRectFPropertyManager(QObject *parent = 0); + ~QtRectFPropertyManager(); + + QtDoublePropertyManager *subDoublePropertyManager() const; + + QRectF value(const QtProperty *property) const; + QRectF constraint(const QtProperty *property) const; + int decimals(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, const QRectF &val); + void setConstraint(QtProperty *property, const QRectF &constraint); + void setDecimals(QtProperty *property, int prec); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QRectF &val); + void constraintChanged(QtProperty *property, const QRectF &constraint); + void decimalsChanged(QtProperty *property, int prec); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtRectFPropertyManager) + Q_DISABLE_COPY_MOVE(QtRectFPropertyManager) +}; + +class QtEnumPropertyManagerPrivate; + +class QtEnumPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtEnumPropertyManager(QObject *parent = 0); + ~QtEnumPropertyManager(); + + int value(const QtProperty *property) const; + QStringList enumNames(const QtProperty *property) const; + QMap enumIcons(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, int val); + void setEnumNames(QtProperty *property, const QStringList &names); + void setEnumIcons(QtProperty *property, const QMap &icons); +Q_SIGNALS: + void valueChanged(QtProperty *property, int val); + void enumNamesChanged(QtProperty *property, const QStringList &names); + void enumIconsChanged(QtProperty *property, const QMap &icons); +protected: + QString valueText(const QtProperty *property) const override; + QIcon valueIcon(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtEnumPropertyManager) + Q_DISABLE_COPY_MOVE(QtEnumPropertyManager) +}; + +class QtFlagPropertyManagerPrivate; + +class QtFlagPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtFlagPropertyManager(QObject *parent = 0); + ~QtFlagPropertyManager(); + + QtBoolPropertyManager *subBoolPropertyManager() const; + + int value(const QtProperty *property) const; + QStringList flagNames(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, int val); + void setFlagNames(QtProperty *property, const QStringList &names); +Q_SIGNALS: + void valueChanged(QtProperty *property, int val); + void flagNamesChanged(QtProperty *property, const QStringList &names); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtFlagPropertyManager) + Q_DISABLE_COPY_MOVE(QtFlagPropertyManager) +}; + +class QtSizePolicyPropertyManagerPrivate; + +class QtSizePolicyPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtSizePolicyPropertyManager(QObject *parent = 0); + ~QtSizePolicyPropertyManager(); + + QtIntPropertyManager *subIntPropertyManager() const; + QtEnumPropertyManager *subEnumPropertyManager() const; + + QSizePolicy value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, QSizePolicy val); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QSizePolicy &val); +protected: + QString valueText(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtSizePolicyPropertyManager) + Q_DISABLE_COPY_MOVE(QtSizePolicyPropertyManager) +}; + +class QtFontPropertyManagerPrivate; + +class QtFontPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtFontPropertyManager(QObject *parent = 0); + ~QtFontPropertyManager(); + + QtIntPropertyManager *subIntPropertyManager() const; + QtEnumPropertyManager *subEnumPropertyManager() const; + QtBoolPropertyManager *subBoolPropertyManager() const; + + QFont value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, const QFont &val); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QFont &val); +protected: + QString valueText(const QtProperty *property) const override; + QIcon valueIcon(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtFontPropertyManager) + Q_DISABLE_COPY_MOVE(QtFontPropertyManager) +}; + +class QtColorPropertyManagerPrivate; + +class QtColorPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtColorPropertyManager(QObject *parent = 0); + ~QtColorPropertyManager(); + + QtIntPropertyManager *subIntPropertyManager() const; + + QColor value(const QtProperty *property) const; + +public Q_SLOTS: + void setValue(QtProperty *property, QColor val); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QColor &val); +protected: + QString valueText(const QtProperty *property) const override; + QIcon valueIcon(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtColorPropertyManager) + Q_DISABLE_COPY_MOVE(QtColorPropertyManager) +}; + +class QtCursorPropertyManagerPrivate; + +class QtCursorPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtCursorPropertyManager(QObject *parent = 0); + ~QtCursorPropertyManager(); + +#ifndef QT_NO_CURSOR + QCursor value(const QtProperty *property) const; +#endif + +public Q_SLOTS: + void setValue(QtProperty *property, const QCursor &val); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QCursor &val); +protected: + QString valueText(const QtProperty *property) const override; + QIcon valueIcon(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtCursorPropertyManager) + Q_DISABLE_COPY_MOVE(QtCursorPropertyManager) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtpropertybrowser/qttreepropertybrowser.cpp b/src/shared/qtpropertybrowser/qttreepropertybrowser.cpp new file mode 100644 index 00000000000..1dd2ff1f048 --- /dev/null +++ b/src/shared/qtpropertybrowser/qttreepropertybrowser.cpp @@ -0,0 +1,1025 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qttreepropertybrowser_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr bool isWindows = QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows; + +static inline bool isLightTheme() +{ + return QGuiApplication::styleHints()->colorScheme() != Qt::ColorScheme::Dark; +} + +class QtPropertyEditorView; + +class QtTreePropertyBrowserPrivate +{ + QtTreePropertyBrowser *q_ptr; + Q_DECLARE_PUBLIC(QtTreePropertyBrowser) + +public: + QtTreePropertyBrowserPrivate(); + void init(QWidget *parent); + + void propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex); + void propertyRemoved(QtBrowserItem *index); + void propertyChanged(QtBrowserItem *index); + QWidget *createEditor(QtProperty *property, QWidget *parent) const + { return q_ptr->createEditor(property, parent); } + QtProperty *indexToProperty(const QModelIndex &index) const; + QTreeWidgetItem *indexToItem(const QModelIndex &index) const; + QtBrowserItem *indexToBrowserItem(const QModelIndex &index) const; + bool lastColumn(int column) const; + void disableItem(QTreeWidgetItem *item) const; + void enableItem(QTreeWidgetItem *item) const; + bool hasValue(QTreeWidgetItem *item) const; + + void slotCollapsed(const QModelIndex &index); + void slotExpanded(const QModelIndex &index); + + QColor calculatedBackgroundColor(QtBrowserItem *item) const; + + QtPropertyEditorView *treeWidget() const { return m_treeWidget; } + bool markPropertiesWithoutValue() const { return m_markPropertiesWithoutValue; } + + QtBrowserItem *currentItem() const; + void setCurrentItem(QtBrowserItem *browserItem, bool block); + void editItem(QtBrowserItem *browserItem); + + void slotCurrentBrowserItemChanged(QtBrowserItem *item); + void slotCurrentTreeItemChanged(QTreeWidgetItem *newItem, QTreeWidgetItem *); + + QTreeWidgetItem *editedItem() const; + +private: + void updateItem(QTreeWidgetItem *item); + + QHash m_indexToItem; + QHash m_itemToIndex; + + QHash m_indexToBackgroundColor; + + QtPropertyEditorView *m_treeWidget; + + bool m_headerVisible; + QtTreePropertyBrowser::ResizeMode m_resizeMode; + class QtPropertyEditorDelegate *m_delegate; + bool m_markPropertiesWithoutValue; + bool m_browserChangedBlocked; + QIcon m_expandIcon; +}; + +// ------------ QtPropertyEditorView +class QtPropertyEditorView : public QTreeWidget +{ + Q_OBJECT +public: + QtPropertyEditorView(QWidget *parent = 0); + + void setEditorPrivate(QtTreePropertyBrowserPrivate *editorPrivate) + { m_editorPrivate = editorPrivate; } + + QTreeWidgetItem *indexToItem(const QModelIndex &index) const + { return itemFromIndex(index); } + +protected: + void keyPressEvent(QKeyEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + +private: + QtTreePropertyBrowserPrivate *m_editorPrivate; +}; + +QtPropertyEditorView::QtPropertyEditorView(QWidget *parent) : + QTreeWidget(parent), + m_editorPrivate(0) +{ + connect(header(), &QHeaderView::sectionDoubleClicked, this, &QTreeView::resizeColumnToContents); +} + +void QtPropertyEditorView::drawRow(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyleOptionViewItem opt = option; + bool hasValue = true; + if (m_editorPrivate) { + QtProperty *property = m_editorPrivate->indexToProperty(index); + if (property) + hasValue = property->hasValue(); + } + if (!hasValue && m_editorPrivate->markPropertiesWithoutValue()) { + const QColor c = option.palette.color(QPalette::Dark); + painter->fillRect(option.rect, c); + opt.palette.setColor(QPalette::AlternateBase, c); + } else { + const QColor c = m_editorPrivate->calculatedBackgroundColor(m_editorPrivate->indexToBrowserItem(index)); + if (c.isValid()) { + painter->fillRect(option.rect, c); + opt.palette.setColor(QPalette::AlternateBase, c.lighter(112)); + } + } + QTreeWidget::drawRow(painter, opt, index); + QColor color = static_cast(QApplication::style()->styleHint(QStyle::SH_Table_GridLineColor, &opt)); + painter->save(); + painter->setPen(QPen(color)); + painter->drawLine(opt.rect.x(), opt.rect.bottom(), opt.rect.right(), opt.rect.bottom()); + painter->restore(); +} + +void QtPropertyEditorView::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Return: + case Qt::Key_Enter: + case Qt::Key_Space: // Trigger Edit + if (!m_editorPrivate->editedItem()) + if (const QTreeWidgetItem *item = currentItem()) + if (item->columnCount() >= 2 && ((item->flags() & (Qt::ItemIsEditable | Qt::ItemIsEnabled)) == (Qt::ItemIsEditable | Qt::ItemIsEnabled))) { + event->accept(); + // If the current position is at column 0, move to 1. + QModelIndex index = currentIndex(); + if (index.column() == 0) { + index = index.sibling(index.row(), 1); + setCurrentIndex(index); + } + edit(index); + return; + } + break; + default: + break; + } + QTreeWidget::keyPressEvent(event); +} + +void QtPropertyEditorView::mousePressEvent(QMouseEvent *event) +{ + QTreeWidget::mousePressEvent(event); + QTreeWidgetItem *item = itemAt(event->position().toPoint()); + + if (item) { + if ((item != m_editorPrivate->editedItem()) && (event->button() == Qt::LeftButton) + && (header()->logicalIndexAt(event->position().toPoint().x()) == 1) + && ((item->flags() & (Qt::ItemIsEditable | Qt::ItemIsEnabled)) == (Qt::ItemIsEditable | Qt::ItemIsEnabled))) { + editItem(item, 1); + } else if (!m_editorPrivate->hasValue(item) && m_editorPrivate->markPropertiesWithoutValue() && !rootIsDecorated()) { + if (event->position().toPoint().x() + header()->offset() < 20) + item->setExpanded(!item->isExpanded()); + } + } +} + +// ------------ QtPropertyEditorDelegate +class QtPropertyEditorDelegate : public QItemDelegate +{ + Q_OBJECT +public: + QtPropertyEditorDelegate(QObject *parent = 0) + : QItemDelegate(parent), m_editorPrivate(0), m_editedItem(0), m_editedWidget(0) + {} + + void setEditorPrivate(QtTreePropertyBrowserPrivate *editorPrivate) + { m_editorPrivate = editorPrivate; } + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + + void setModelData(QWidget *, QAbstractItemModel *, + const QModelIndex &) const override {} + + void setEditorData(QWidget *, const QModelIndex &) const override {} + + bool eventFilter(QObject *object, QEvent *event) override; + void closeEditor(QtProperty *property); + + QTreeWidgetItem *editedItem() const { return m_editedItem; } + +private slots: + void slotEditorDestroyed(QObject *object); + +private: + int indentation(const QModelIndex &index) const; + + using EditorToPropertyMap = QHash; + mutable EditorToPropertyMap m_editorToProperty; + + using PropertyToEditorMap = QHash; + mutable PropertyToEditorMap m_propertyToEditor; + QtTreePropertyBrowserPrivate *m_editorPrivate; + mutable QTreeWidgetItem *m_editedItem; + mutable QWidget *m_editedWidget; +}; + +int QtPropertyEditorDelegate::indentation(const QModelIndex &index) const +{ + if (!m_editorPrivate) + return 0; + + QTreeWidgetItem *item = m_editorPrivate->indexToItem(index); + int indent = 0; + while (item->parent()) { + item = item->parent(); + ++indent; + } + if (m_editorPrivate->treeWidget()->rootIsDecorated()) + ++indent; + return indent * m_editorPrivate->treeWidget()->indentation(); +} + +void QtPropertyEditorDelegate::slotEditorDestroyed(QObject *object) +{ + if (auto *w = qobject_cast(object)) { + const auto it = m_editorToProperty.find(w); + if (it != m_editorToProperty.end()) { + m_propertyToEditor.remove(it.value()); + m_editorToProperty.erase(it); + } + if (m_editedWidget == w) { + m_editedWidget = nullptr; + m_editedItem = nullptr; + } + } +} + +void QtPropertyEditorDelegate::closeEditor(QtProperty *property) +{ + if (QWidget *w = m_propertyToEditor.value(property, nullptr)) + w->deleteLater(); +} + +QWidget *QtPropertyEditorDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &, const QModelIndex &index) const +{ + if (index.column() == 1 && m_editorPrivate) { + QtProperty *property = m_editorPrivate->indexToProperty(index); + QTreeWidgetItem *item = m_editorPrivate->indexToItem(index); + if (property && item && (item->flags() & Qt::ItemIsEnabled)) { + QWidget *editor = m_editorPrivate->createEditor(property, parent); + if (editor) { + editor->setAutoFillBackground(true); + if (editor->palette().color(editor->backgroundRole()) == Qt::transparent) + editor->setBackgroundRole(QPalette::Window); + editor->installEventFilter(const_cast(this)); + connect(editor, &QObject::destroyed, + this, &QtPropertyEditorDelegate::slotEditorDestroyed); + m_propertyToEditor[property] = editor; + m_editorToProperty[editor] = property; + m_editedItem = item; + m_editedWidget = editor; + } + return editor; + } + } + return nullptr; +} + +void QtPropertyEditorDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + Q_UNUSED(index); + editor->setGeometry(option.rect.adjusted(0, 0, 0, -1)); +} + +void QtPropertyEditorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + bool hasValue = true; + if (m_editorPrivate) { + QtProperty *property = m_editorPrivate->indexToProperty(index); + if (property) + hasValue = property->hasValue(); + } + QStyleOptionViewItem opt = option; + if ((m_editorPrivate && index.column() == 0) || !hasValue) { + QtProperty *property = m_editorPrivate->indexToProperty(index); + if (property && property->isModified()) { + opt.font.setBold(true); + opt.fontMetrics = QFontMetrics(opt.font); + } + } + QColor c; + if (!hasValue && m_editorPrivate->markPropertiesWithoutValue()) { + c = opt.palette.color(QPalette::Dark); + // Hardcode "white" for Windows/light which is otherwise blue + const QColor textColor = isWindows && isLightTheme() + ? QColor(Qt::white) : opt.palette.color(QPalette::BrightText); + opt.palette.setColor(QPalette::Text, textColor); + } else { + c = m_editorPrivate->calculatedBackgroundColor(m_editorPrivate->indexToBrowserItem(index)); + if (c.isValid() && (opt.features & QStyleOptionViewItem::Alternate)) + c = c.lighter(112); + } + if (c.isValid()) + painter->fillRect(option.rect, c); + opt.state &= ~QStyle::State_HasFocus; + QItemDelegate::paint(painter, opt, index); + + opt.palette.setCurrentColorGroup(QPalette::Active); + QColor color = static_cast(QApplication::style()->styleHint(QStyle::SH_Table_GridLineColor, &opt)); + painter->save(); + painter->setPen(QPen(color)); + if (!m_editorPrivate || (!m_editorPrivate->lastColumn(index.column()) && hasValue)) { + int right = (option.direction == Qt::LeftToRight) ? option.rect.right() : option.rect.left(); + painter->drawLine(right, option.rect.y(), right, option.rect.bottom()); + } + painter->restore(); +} + +QSize QtPropertyEditorDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + return QItemDelegate::sizeHint(option, index) + QSize(3, 4); +} + +bool QtPropertyEditorDelegate::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::FocusOut) { + QFocusEvent *fe = static_cast(event); + if (fe->reason() == Qt::ActiveWindowFocusReason) + return false; + } + return QItemDelegate::eventFilter(object, event); +} + +// -------- QtTreePropertyBrowserPrivate implementation +QtTreePropertyBrowserPrivate::QtTreePropertyBrowserPrivate() : + m_treeWidget(0), + m_headerVisible(true), + m_resizeMode(QtTreePropertyBrowser::Stretch), + m_delegate(0), + m_markPropertiesWithoutValue(false), + m_browserChangedBlocked(false) +{ +} + +// Draw an icon indicating opened/closing branches +static QIcon drawIndicatorIcon(const QPalette &palette, QStyle *style) +{ + QPixmap pix(14, 14); + pix.fill(Qt::transparent); + QStyleOption branchOption; + branchOption.rect = QRect(2, 2, 9, 9); // ### hardcoded in qcommonstyle.cpp + branchOption.palette = palette; + branchOption.state = QStyle::State_Children; + + QPainter p; + // Draw closed state + p.begin(&pix); + style->drawPrimitive(QStyle::PE_IndicatorBranch, &branchOption, &p); + p.end(); + QIcon rc = pix; + rc.addPixmap(pix, QIcon::Selected, QIcon::Off); + // Draw opened state + branchOption.state |= QStyle::State_Open; + pix.fill(Qt::transparent); + p.begin(&pix); + style->drawPrimitive(QStyle::PE_IndicatorBranch, &branchOption, &p); + p.end(); + + rc.addPixmap(pix, QIcon::Normal, QIcon::On); + rc.addPixmap(pix, QIcon::Selected, QIcon::On); + return rc; +} + +void QtTreePropertyBrowserPrivate::init(QWidget *parent) +{ + auto *layout = new QHBoxLayout(parent); + layout->setContentsMargins(QMargins()); + m_treeWidget = new QtPropertyEditorView(parent); + m_treeWidget->setEditorPrivate(this); + m_treeWidget->setIconSize(QSize(18, 18)); + layout->addWidget(m_treeWidget); + + m_treeWidget->setColumnCount(2); + QStringList labels; + labels.append(QCoreApplication::translate("QtTreePropertyBrowser", "Property")); + labels.append(QCoreApplication::translate("QtTreePropertyBrowser", "Value")); + m_treeWidget->setHeaderLabels(labels); + m_treeWidget->setAlternatingRowColors(true); + m_treeWidget->setEditTriggers(QAbstractItemView::EditKeyPressed); + m_delegate = new QtPropertyEditorDelegate(parent); + m_delegate->setEditorPrivate(this); + m_treeWidget->setItemDelegate(m_delegate); + m_treeWidget->header()->setSectionsMovable(false); + m_treeWidget->header()->setSectionResizeMode(QHeaderView::Stretch); + + m_expandIcon = drawIndicatorIcon(q_ptr->palette(), q_ptr->style()); + + QObject::connect(m_treeWidget, &QTreeView::collapsed, + q_ptr, [this](const QModelIndex &index) { slotCollapsed(index); }); + QObject::connect(m_treeWidget, &QTreeView::expanded, + q_ptr, [this](const QModelIndex &index) { slotExpanded(index); }); + QObject::connect(m_treeWidget, &QTreeWidget::currentItemChanged, + q_ptr, [this](QTreeWidgetItem *current, QTreeWidgetItem *previous) + { slotCurrentTreeItemChanged(current, previous); }); +} + +QtBrowserItem *QtTreePropertyBrowserPrivate::currentItem() const +{ + if (QTreeWidgetItem *treeItem = m_treeWidget->currentItem()) + return m_itemToIndex.value(treeItem); + return nullptr; +} + +void QtTreePropertyBrowserPrivate::setCurrentItem(QtBrowserItem *browserItem, bool block) +{ + const bool blocked = block ? m_treeWidget->blockSignals(true) : false; + if (browserItem == nullptr) + m_treeWidget->setCurrentItem(nullptr); + else + m_treeWidget->setCurrentItem(m_indexToItem.value(browserItem)); + if (block) + m_treeWidget->blockSignals(blocked); +} + +QtProperty *QtTreePropertyBrowserPrivate::indexToProperty(const QModelIndex &index) const +{ + QTreeWidgetItem *item = m_treeWidget->indexToItem(index); + QtBrowserItem *idx = m_itemToIndex.value(item); + if (idx) + return idx->property(); + return nullptr; +} + +QtBrowserItem *QtTreePropertyBrowserPrivate::indexToBrowserItem(const QModelIndex &index) const +{ + QTreeWidgetItem *item = m_treeWidget->indexToItem(index); + return m_itemToIndex.value(item); +} + +QTreeWidgetItem *QtTreePropertyBrowserPrivate::indexToItem(const QModelIndex &index) const +{ + return m_treeWidget->indexToItem(index); +} + +bool QtTreePropertyBrowserPrivate::lastColumn(int column) const +{ + return m_treeWidget->header()->visualIndex(column) == m_treeWidget->columnCount() - 1; +} + +void QtTreePropertyBrowserPrivate::disableItem(QTreeWidgetItem *item) const +{ + Qt::ItemFlags flags = item->flags(); + if (flags & Qt::ItemIsEnabled) { + flags &= ~Qt::ItemIsEnabled; + item->setFlags(flags); + m_delegate->closeEditor(m_itemToIndex[item]->property()); + const int childCount = item->childCount(); + for (int i = 0; i < childCount; i++) { + QTreeWidgetItem *child = item->child(i); + disableItem(child); + } + } +} + +void QtTreePropertyBrowserPrivate::enableItem(QTreeWidgetItem *item) const +{ + Qt::ItemFlags flags = item->flags(); + flags |= Qt::ItemIsEnabled; + item->setFlags(flags); + const int childCount = item->childCount(); + for (int i = 0; i < childCount; i++) { + QTreeWidgetItem *child = item->child(i); + QtProperty *property = m_itemToIndex[child]->property(); + if (property->isEnabled()) { + enableItem(child); + } + } +} + +bool QtTreePropertyBrowserPrivate::hasValue(QTreeWidgetItem *item) const +{ + QtBrowserItem *browserItem = m_itemToIndex.value(item); + if (browserItem) + return browserItem->property()->hasValue(); + return false; +} + +void QtTreePropertyBrowserPrivate::propertyInserted(QtBrowserItem *index, QtBrowserItem *afterIndex) +{ + QTreeWidgetItem *afterItem = m_indexToItem.value(afterIndex); + QTreeWidgetItem *parentItem = m_indexToItem.value(index->parent()); + + QTreeWidgetItem *newItem = nullptr; + if (parentItem) { + newItem = new QTreeWidgetItem(parentItem, afterItem); + } else { + newItem = new QTreeWidgetItem(m_treeWidget, afterItem); + } + m_itemToIndex[newItem] = index; + m_indexToItem[index] = newItem; + + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + newItem->setExpanded(true); + + updateItem(newItem); +} + +void QtTreePropertyBrowserPrivate::propertyRemoved(QtBrowserItem *index) +{ + QTreeWidgetItem *item = m_indexToItem.value(index); + + if (m_treeWidget->currentItem() == item) { + m_treeWidget->setCurrentItem(0); + } + + delete item; + + m_indexToItem.remove(index); + m_itemToIndex.remove(item); + m_indexToBackgroundColor.remove(index); +} + +void QtTreePropertyBrowserPrivate::propertyChanged(QtBrowserItem *index) +{ + QTreeWidgetItem *item = m_indexToItem.value(index); + + updateItem(item); +} + +void QtTreePropertyBrowserPrivate::updateItem(QTreeWidgetItem *item) +{ + QtProperty *property = m_itemToIndex[item]->property(); + QIcon expandIcon; + if (property->hasValue()) { + const QString valueToolTip = property->valueToolTip(); + const QString valueText = property->valueText(); + item->setToolTip(1, valueToolTip.isEmpty() ? valueText : valueToolTip); + item->setIcon(1, property->valueIcon()); + item->setText(1, valueText); + } else if (markPropertiesWithoutValue() && !m_treeWidget->rootIsDecorated()) { + expandIcon = m_expandIcon; + } + item->setIcon(0, expandIcon); + item->setFirstColumnSpanned(!property->hasValue()); + const QString descriptionToolTip = property->descriptionToolTip(); + const QString propertyName = property->propertyName(); + item->setToolTip(0, descriptionToolTip.isEmpty() ? propertyName : descriptionToolTip); + item->setStatusTip(0, property->statusTip()); + item->setWhatsThis(0, property->whatsThis()); + item->setText(0, propertyName); + bool wasEnabled = item->flags() & Qt::ItemIsEnabled; + bool isEnabled = wasEnabled; + if (property->isEnabled()) { + QTreeWidgetItem *parent = item->parent(); + if (!parent || (parent->flags() & Qt::ItemIsEnabled)) + isEnabled = true; + else + isEnabled = false; + } else { + isEnabled = false; + } + if (wasEnabled != isEnabled) { + if (isEnabled) + enableItem(item); + else + disableItem(item); + } + m_treeWidget->viewport()->update(); +} + +QColor QtTreePropertyBrowserPrivate::calculatedBackgroundColor(QtBrowserItem *item) const +{ + QtBrowserItem *i = item; + const auto itEnd = m_indexToBackgroundColor.constEnd(); + while (i) { + auto it = m_indexToBackgroundColor.constFind(i); + if (it != itEnd) + return it.value(); + i = i->parent(); + } + return {}; +} + +void QtTreePropertyBrowserPrivate::slotCollapsed(const QModelIndex &index) +{ + QTreeWidgetItem *item = indexToItem(index); + QtBrowserItem *idx = m_itemToIndex.value(item); + if (item) + emit q_ptr->collapsed(idx); +} + +void QtTreePropertyBrowserPrivate::slotExpanded(const QModelIndex &index) +{ + QTreeWidgetItem *item = indexToItem(index); + QtBrowserItem *idx = m_itemToIndex.value(item); + if (item) + emit q_ptr->expanded(idx); +} + +void QtTreePropertyBrowserPrivate::slotCurrentBrowserItemChanged(QtBrowserItem *item) +{ + if (!m_browserChangedBlocked && item != currentItem()) + setCurrentItem(item, true); +} + +void QtTreePropertyBrowserPrivate::slotCurrentTreeItemChanged(QTreeWidgetItem *newItem, QTreeWidgetItem *) +{ + QtBrowserItem *browserItem = newItem ? m_itemToIndex.value(newItem) : 0; + m_browserChangedBlocked = true; + q_ptr->setCurrentItem(browserItem); + m_browserChangedBlocked = false; +} + +QTreeWidgetItem *QtTreePropertyBrowserPrivate::editedItem() const +{ + return m_delegate->editedItem(); +} + +void QtTreePropertyBrowserPrivate::editItem(QtBrowserItem *browserItem) +{ + if (QTreeWidgetItem *treeItem = m_indexToItem.value(browserItem, nullptr)) { + m_treeWidget->setCurrentItem (treeItem, 1); + m_treeWidget->editItem(treeItem, 1); + } +} + +/*! + \class QtTreePropertyBrowser + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtTreePropertyBrowser class provides QTreeWidget based + property browser. + + A property browser is a widget that enables the user to edit a + given set of properties. Each property is represented by a label + specifying the property's name, and an editing widget (e.g. a line + edit or a combobox) holding its value. A property can have zero or + more subproperties. + + QtTreePropertyBrowser provides a tree based view for all nested + properties, i.e. properties that have subproperties can be in an + expanded (subproperties are visible) or collapsed (subproperties + are hidden) state. For example: + + \image qttreepropertybrowser.png + + Use the QtAbstractPropertyBrowser API to add, insert and remove + properties from an instance of the QtTreePropertyBrowser class. + The properties themselves are created and managed by + implementations of the QtAbstractPropertyManager class. + + \sa QtGroupBoxPropertyBrowser, QtAbstractPropertyBrowser +*/ + +/*! + \fn void QtTreePropertyBrowser::collapsed(QtBrowserItem *item) + + This signal is emitted when the \a item is collapsed. + + \sa expanded(), setExpanded() +*/ + +/*! + \fn void QtTreePropertyBrowser::expanded(QtBrowserItem *item) + + This signal is emitted when the \a item is expanded. + + \sa collapsed(), setExpanded() +*/ + +/*! + Creates a property browser with the given \a parent. +*/ +QtTreePropertyBrowser::QtTreePropertyBrowser(QWidget *parent) + : QtAbstractPropertyBrowser(parent), d_ptr(new QtTreePropertyBrowserPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->init(this); + QObject::connect(this, &QtAbstractPropertyBrowser::currentItemChanged, + this, [this](QtBrowserItem *current) + { d_ptr->slotCurrentBrowserItemChanged(current); }); +} + +/*! + Destroys this property browser. + + Note that the properties that were inserted into this browser are + \e not destroyed since they may still be used in other + browsers. The properties are owned by the manager that created + them. + + \sa QtProperty, QtAbstractPropertyManager +*/ +QtTreePropertyBrowser::~QtTreePropertyBrowser() +{ +} + +/*! + \property QtTreePropertyBrowser::indentation + \brief indentation of the items in the tree view. +*/ +int QtTreePropertyBrowser::indentation() const +{ + return d_ptr->m_treeWidget->indentation(); +} + +void QtTreePropertyBrowser::setIndentation(int i) +{ + d_ptr->m_treeWidget->setIndentation(i); +} + +/*! + \property QtTreePropertyBrowser::rootIsDecorated + \brief whether to show controls for expanding and collapsing root items. +*/ +bool QtTreePropertyBrowser::rootIsDecorated() const +{ + return d_ptr->m_treeWidget->rootIsDecorated(); +} + +void QtTreePropertyBrowser::setRootIsDecorated(bool show) +{ + d_ptr->m_treeWidget->setRootIsDecorated(show); + for (auto it = d_ptr->m_itemToIndex.cbegin(), end = d_ptr->m_itemToIndex.cend(); it != end; ++it) { + QtProperty *property = it.value()->property(); + if (!property->hasValue()) + d_ptr->updateItem(it.key()); + } +} + +/*! + \property QtTreePropertyBrowser::alternatingRowColors + \brief whether to draw the background using alternating colors. + By default this property is set to true. +*/ +bool QtTreePropertyBrowser::alternatingRowColors() const +{ + return d_ptr->m_treeWidget->alternatingRowColors(); +} + +void QtTreePropertyBrowser::setAlternatingRowColors(bool enable) +{ + d_ptr->m_treeWidget->setAlternatingRowColors(enable); +} + +/*! + \property QtTreePropertyBrowser::headerVisible + \brief whether to show the header. +*/ +bool QtTreePropertyBrowser::isHeaderVisible() const +{ + return d_ptr->m_headerVisible; +} + +void QtTreePropertyBrowser::setHeaderVisible(bool visible) +{ + if (d_ptr->m_headerVisible == visible) + return; + + d_ptr->m_headerVisible = visible; + d_ptr->m_treeWidget->header()->setVisible(visible); +} + +/*! + \enum QtTreePropertyBrowser::ResizeMode + + The resize mode specifies the behavior of the header sections. + + \value Interactive The user can resize the sections. + The sections can also be resized programmatically using setSplitterPosition(). + + \value Fixed The user cannot resize the section. + The section can only be resized programmatically using setSplitterPosition(). + + \value Stretch QHeaderView will automatically resize the section to fill the available space. + The size cannot be changed by the user or programmatically. + + \value ResizeToContents QHeaderView will automatically resize the section to its optimal + size based on the contents of the entire column. + The size cannot be changed by the user or programmatically. + + \sa setResizeMode() +*/ + +/*! + \property QtTreePropertyBrowser::resizeMode + \brief the resize mode of setions in the header. +*/ + +QtTreePropertyBrowser::ResizeMode QtTreePropertyBrowser::resizeMode() const +{ + return d_ptr->m_resizeMode; +} + +void QtTreePropertyBrowser::setResizeMode(QtTreePropertyBrowser::ResizeMode mode) +{ + if (d_ptr->m_resizeMode == mode) + return; + + d_ptr->m_resizeMode = mode; + QHeaderView::ResizeMode m = QHeaderView::Stretch; + switch (mode) { + case QtTreePropertyBrowser::Interactive: m = QHeaderView::Interactive; break; + case QtTreePropertyBrowser::Fixed: m = QHeaderView::Fixed; break; + case QtTreePropertyBrowser::ResizeToContents: m = QHeaderView::ResizeToContents; break; + case QtTreePropertyBrowser::Stretch: + default: m = QHeaderView::Stretch; break; + } + d_ptr->m_treeWidget->header()->setSectionResizeMode(m); +} + +/*! + \property QtTreePropertyBrowser::splitterPosition + \brief the position of the splitter between the colunms. +*/ + +int QtTreePropertyBrowser::splitterPosition() const +{ + return d_ptr->m_treeWidget->header()->sectionSize(0); +} + +void QtTreePropertyBrowser::setSplitterPosition(int position) +{ + d_ptr->m_treeWidget->header()->resizeSection(0, position); +} + +/*! + Sets the \a item to either collapse or expanded, depending on the value of \a expanded. + + \sa isExpanded(), expanded(), collapsed() +*/ + +void QtTreePropertyBrowser::setExpanded(QtBrowserItem *item, bool expanded) +{ + QTreeWidgetItem *treeItem = d_ptr->m_indexToItem.value(item); + if (treeItem) + treeItem->setExpanded(expanded); +} + +/*! + Returns true if the \a item is expanded; otherwise returns false. + + \sa setExpanded() +*/ + +bool QtTreePropertyBrowser::isExpanded(QtBrowserItem *item) const +{ + QTreeWidgetItem *treeItem = d_ptr->m_indexToItem.value(item); + if (treeItem) + return treeItem->isExpanded(); + return false; +} + +/*! + Returns true if the \a item is visible; otherwise returns false. + + \sa setItemVisible() + \since 4.5 +*/ + +bool QtTreePropertyBrowser::isItemVisible(QtBrowserItem *item) const +{ + if (const QTreeWidgetItem *treeItem = d_ptr->m_indexToItem.value(item)) + return !treeItem->isHidden(); + return false; +} + +/*! + Sets the \a item to be visible, depending on the value of \a visible. + + \sa isItemVisible() + \since 4.5 +*/ + +void QtTreePropertyBrowser::setItemVisible(QtBrowserItem *item, bool visible) +{ + if (QTreeWidgetItem *treeItem = d_ptr->m_indexToItem.value(item)) + treeItem->setHidden(!visible); +} + +/*! + Sets the \a item's background color to \a color. Note that while item's background + is rendered every second row is being drawn with alternate color (which is a bit lighter than items \a color) + + \sa backgroundColor(), calculatedBackgroundColor() +*/ + +void QtTreePropertyBrowser::setBackgroundColor(QtBrowserItem *item, QColor color) +{ + if (!d_ptr->m_indexToItem.contains(item)) + return; + if (color.isValid()) + d_ptr->m_indexToBackgroundColor[item] = color; + else + d_ptr->m_indexToBackgroundColor.remove(item); + d_ptr->m_treeWidget->viewport()->update(); +} + +/*! + Returns the \a item's color. If there is no color set for item it returns invalid color. + + \sa calculatedBackgroundColor(), setBackgroundColor() +*/ + +QColor QtTreePropertyBrowser::backgroundColor(QtBrowserItem *item) const +{ + return d_ptr->m_indexToBackgroundColor.value(item); +} + +/*! + Returns the \a item's color. If there is no color set for item it returns parent \a item's + color (if there is no color set for parent it returns grandparent's color and so on). In case + the color is not set for \a item and it's top level item it returns invalid color. + + \sa backgroundColor(), setBackgroundColor() +*/ + +QColor QtTreePropertyBrowser::calculatedBackgroundColor(QtBrowserItem *item) const +{ + return d_ptr->calculatedBackgroundColor(item); +} + +/*! + \property QtTreePropertyBrowser::propertiesWithoutValueMarked + \brief whether to enable or disable marking properties without value. + + When marking is enabled the item's background is rendered in dark color and item's + foreground is rendered with light color. + + \sa propertiesWithoutValueMarked() +*/ +void QtTreePropertyBrowser::setPropertiesWithoutValueMarked(bool mark) +{ + if (d_ptr->m_markPropertiesWithoutValue == mark) + return; + + d_ptr->m_markPropertiesWithoutValue = mark; + for (auto it = d_ptr->m_itemToIndex.cbegin(), end = d_ptr->m_itemToIndex.cend(); it != end; ++it) { + QtProperty *property = it.value()->property(); + if (!property->hasValue()) + d_ptr->updateItem(it.key()); + } + d_ptr->m_treeWidget->viewport()->update(); +} + +bool QtTreePropertyBrowser::propertiesWithoutValueMarked() const +{ + return d_ptr->m_markPropertiesWithoutValue; +} + +/*! + \reimp +*/ +void QtTreePropertyBrowser::itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem) +{ + d_ptr->propertyInserted(item, afterItem); +} + +/*! + \reimp +*/ +void QtTreePropertyBrowser::itemRemoved(QtBrowserItem *item) +{ + d_ptr->propertyRemoved(item); +} + +/*! + \reimp +*/ +void QtTreePropertyBrowser::itemChanged(QtBrowserItem *item) +{ + d_ptr->propertyChanged(item); +} + +/*! + Sets the current item to \a item and opens the relevant editor for it. +*/ +void QtTreePropertyBrowser::editItem(QtBrowserItem *item) +{ + d_ptr->editItem(item); +} + +QT_END_NAMESPACE + +#include "moc_qttreepropertybrowser_p.cpp" +#include "qttreepropertybrowser.moc" diff --git a/src/shared/qtpropertybrowser/qttreepropertybrowser_p.h b/src/shared/qtpropertybrowser/qttreepropertybrowser_p.h new file mode 100644 index 00000000000..a27bacd742a --- /dev/null +++ b/src/shared/qtpropertybrowser/qttreepropertybrowser_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTTREEPROPERTYBROWSER_H +#define QTTREEPROPERTYBROWSER_H + +#include "qtpropertybrowser_p.h" + +QT_BEGIN_NAMESPACE + +class QTreeWidgetItem; +class QtTreePropertyBrowserPrivate; + +class QtTreePropertyBrowser : public QtAbstractPropertyBrowser +{ + Q_OBJECT + Q_PROPERTY(int indentation READ indentation WRITE setIndentation) + Q_PROPERTY(bool rootIsDecorated READ rootIsDecorated WRITE setRootIsDecorated) + Q_PROPERTY(bool alternatingRowColors READ alternatingRowColors WRITE setAlternatingRowColors) + Q_PROPERTY(bool headerVisible READ isHeaderVisible WRITE setHeaderVisible) + Q_PROPERTY(ResizeMode resizeMode READ resizeMode WRITE setResizeMode) + Q_PROPERTY(int splitterPosition READ splitterPosition WRITE setSplitterPosition) + Q_PROPERTY(bool propertiesWithoutValueMarked READ propertiesWithoutValueMarked WRITE setPropertiesWithoutValueMarked) +public: + enum ResizeMode + { + Interactive, + Stretch, + Fixed, + ResizeToContents + }; + Q_ENUM(ResizeMode) + + QtTreePropertyBrowser(QWidget *parent = 0); + ~QtTreePropertyBrowser(); + + int indentation() const; + void setIndentation(int i); + + bool rootIsDecorated() const; + void setRootIsDecorated(bool show); + + bool alternatingRowColors() const; + void setAlternatingRowColors(bool enable); + + bool isHeaderVisible() const; + void setHeaderVisible(bool visible); + + ResizeMode resizeMode() const; + void setResizeMode(ResizeMode mode); + + int splitterPosition() const; + void setSplitterPosition(int position); + + void setExpanded(QtBrowserItem *item, bool expanded); + bool isExpanded(QtBrowserItem *item) const; + + bool isItemVisible(QtBrowserItem *item) const; + void setItemVisible(QtBrowserItem *item, bool visible); + + void setBackgroundColor(QtBrowserItem *item, QColor color); + QColor backgroundColor(QtBrowserItem *item) const; + QColor calculatedBackgroundColor(QtBrowserItem *item) const; + + void setPropertiesWithoutValueMarked(bool mark); + bool propertiesWithoutValueMarked() const; + + void editItem(QtBrowserItem *item); + +Q_SIGNALS: + void collapsed(QtBrowserItem *item); + void expanded(QtBrowserItem *item); + +protected: + void itemInserted(QtBrowserItem *item, QtBrowserItem *afterItem) override; + void itemRemoved(QtBrowserItem *item) override; + void itemChanged(QtBrowserItem *item) override; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtTreePropertyBrowser) + Q_DISABLE_COPY_MOVE(QtTreePropertyBrowser) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qtpropertybrowser/qtvariantproperty.cpp b/src/shared/qtpropertybrowser/qtvariantproperty.cpp new file mode 100644 index 00000000000..0a5dffc2cc1 --- /dev/null +++ b/src/shared/qtpropertybrowser/qtvariantproperty.cpp @@ -0,0 +1,2256 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qtvariantproperty_p.h" +#include "qtpropertymanager_p.h" +#include "qteditorfactory_p.h" + +#include +#include +#include +#include +#include + +#if defined(Q_CC_MSVC) +# pragma warning(disable: 4786) /* MS VS 6: truncating debug info after 255 characters */ +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using QtIconMap = QMap; + +class QtEnumPropertyType +{ +}; + + +class QtFlagPropertyType +{ +}; + + +class QtGroupPropertyType +{ +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QtEnumPropertyType) +Q_DECLARE_METATYPE(QtFlagPropertyType) +Q_DECLARE_METATYPE(QtGroupPropertyType) + +QT_BEGIN_NAMESPACE + +/*! + Returns the type id for an enum property. + + Note that the property's value type can be retrieved using the + valueType() function (which is QMetaType::Int for the enum property + type). + + \sa propertyType(), valueType() +*/ +int QtVariantPropertyManager::enumTypeId() +{ + return qMetaTypeId(); +} + +/*! + Returns the type id for a flag property. + + Note that the property's value type can be retrieved using the + valueType() function (which is QMetaType::Int for the flag property + type). + + \sa propertyType(), valueType() +*/ +int QtVariantPropertyManager::flagTypeId() +{ + return qMetaTypeId(); +} + +/*! + Returns the type id for a group property. + + Note that the property's value type can be retrieved using the + valueType() function (which is QMetaType::UnknownType for the group + property type, since it doesn't provide any value). + + \sa propertyType(), valueType() +*/ +int QtVariantPropertyManager::groupTypeId() +{ + return qMetaTypeId(); +} + +/*! + Returns the type id for a icon map attribute. + + Note that the property's attribute type can be retrieved using the + attributeType() function. + + \sa attributeType(), QtEnumPropertyManager::enumIcons() +*/ +int QtVariantPropertyManager::iconMapTypeId() +{ + return qMetaTypeId(); +} + +using PropertyPropertyMap = QHash; + +Q_GLOBAL_STATIC(PropertyPropertyMap, propertyToWrappedProperty) + +static QtProperty *wrappedProperty(QtProperty *property) +{ + return propertyToWrappedProperty()->value(property, nullptr); +} + +class QtVariantPropertyPrivate +{ +public: + QtVariantPropertyPrivate(QtVariantPropertyManager *m) : manager(m) {} + + QtVariantPropertyManager *manager; +}; + +/*! + \class QtVariantProperty + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtVariantProperty class is a convenience class handling + QVariant based properties. + + QtVariantProperty provides additional API: A property's type, + value type, attribute values and current value can easily be + retrieved using the propertyType(), valueType(), attributeValue() + and value() functions respectively. In addition, the attribute + values and the current value can be set using the corresponding + setValue() and setAttribute() functions. + + For example, instead of writing: + + \snippet doc/src/snippets/code/tools_shared_qtpropertybrowser_qtvariantproperty.cpp 0 + + you can write: + + \snippet doc/src/snippets/code/tools_shared_qtpropertybrowser_qtvariantproperty.cpp 1 + + QtVariantProperty instances can only be created by the + QtVariantPropertyManager class. + + \sa QtProperty, QtVariantPropertyManager, QtVariantEditorFactory +*/ + +/*! + Creates a variant property using the given \a manager. + + Do not use this constructor to create variant property instances; + use the QtVariantPropertyManager::addProperty() function + instead. This constructor is used internally by the + QtVariantPropertyManager::createProperty() function. + + \sa QtVariantPropertyManager +*/ +QtVariantProperty::QtVariantProperty(QtVariantPropertyManager *manager) + : QtProperty(manager), d_ptr(new QtVariantPropertyPrivate(manager)) +{ +} + +/*! + Destroys this property. + + \sa QtProperty::~QtProperty() +*/ +QtVariantProperty::~QtVariantProperty() +{ +} + +/*! + Returns the property's current value. + + \sa valueType(), setValue() +*/ +QVariant QtVariantProperty::value() const +{ + return d_ptr->manager->value(this); +} + +/*! + Returns this property's value for the specified \a attribute. + + QtVariantPropertyManager provides a couple of related functions: + \l{QtVariantPropertyManager::attributes()}{attributes()} and + \l{QtVariantPropertyManager::attributeType()}{attributeType()}. + + \sa setAttribute() +*/ +QVariant QtVariantProperty::attributeValue(const QString &attribute) const +{ + return d_ptr->manager->attributeValue(this, attribute); +} + +/*! + Returns the type of this property's value. + + \sa propertyType() +*/ +int QtVariantProperty::valueType() const +{ + return d_ptr->manager->valueType(this); +} + +/*! + Returns this property's type. + + QtVariantPropertyManager provides several related functions: + \l{QtVariantPropertyManager::enumTypeId()}{enumTypeId()}, + \l{QtVariantPropertyManager::flagTypeId()}{flagTypeId()} and + \l{QtVariantPropertyManager::groupTypeId()}{groupTypeId()}. + + \sa valueType() +*/ +int QtVariantProperty::propertyType() const +{ + return d_ptr->manager->propertyType(this); +} + +/*! + Sets the value of this property to \a value. + + The specified \a value must be of the type returned by + valueType(), or of a type that can be converted to valueType() + using the QVariant::canConvert() function; otherwise this function + does nothing. + + \sa value() +*/ +void QtVariantProperty::setValue(const QVariant &value) +{ + d_ptr->manager->setValue(this, value); +} + +/*! + Sets the \a attribute of property to \a value. + + QtVariantPropertyManager provides the related + \l{QtVariantPropertyManager::setAttribute()}{setAttribute()} + function. + + \sa attributeValue() +*/ +void QtVariantProperty::setAttribute(const QString &attribute, const QVariant &value) +{ + d_ptr->manager->setAttribute(this, attribute, value); +} + +class QtVariantPropertyManagerPrivate +{ + QtVariantPropertyManager *q_ptr; + Q_DECLARE_PUBLIC(QtVariantPropertyManager) +public: + QtVariantPropertyManagerPrivate(); + + bool m_creatingProperty; + bool m_creatingSubProperties; + bool m_destroyingSubProperties; + int m_propertyType; + + void slotValueChanged(QtProperty *property, int val); + void slotRangeChanged(QtProperty *property, int min, int max); + void slotSingleStepChanged(QtProperty *property, int step); + void slotValueChanged(QtProperty *property, double val); + void slotRangeChanged(QtProperty *property, double min, double max); + void slotSingleStepChanged(QtProperty *property, double step); + void slotDecimalsChanged(QtProperty *property, int prec); + void slotValueChanged(QtProperty *property, bool val); + void slotValueChanged(QtProperty *property, const QString &val); + void slotRegExpChanged(QtProperty *property, const QRegularExpression ®Exp); + void slotValueChanged(QtProperty *property, QDate val); + void slotRangeChanged(QtProperty *property, QDate min, QDate max); + void slotValueChanged(QtProperty *property, QTime val); + void slotValueChanged(QtProperty *property, const QDateTime &val); + void slotValueChanged(QtProperty *property, const QKeySequence &val); + void slotValueChanged(QtProperty *property, const QChar &val); + void slotValueChanged(QtProperty *property, const QLocale &val); + void slotValueChanged(QtProperty *property, QPoint val); + void slotValueChanged(QtProperty *property, QPointF val); + void slotValueChanged(QtProperty *property, QSize val); + void slotRangeChanged(QtProperty *property, QSize min, QSize max); + void slotValueChanged(QtProperty *property, const QSizeF &val); + void slotRangeChanged(QtProperty *property, const QSizeF &min, const QSizeF &max); + void slotValueChanged(QtProperty *property, QRect val); + void slotConstraintChanged(QtProperty *property, QRect val); + void slotValueChanged(QtProperty *property, const QRectF &val); + void slotConstraintChanged(QtProperty *property, const QRectF &val); + void slotValueChanged(QtProperty *property, const QColor &val); + void slotEnumChanged(QtProperty *property, int val); + void slotEnumNamesChanged(QtProperty *property, const QStringList &enumNames); + void slotEnumIconsChanged(QtProperty *property, const QMap &enumIcons); + void slotValueChanged(QtProperty *property, QSizePolicy val); + void slotValueChanged(QtProperty *property, const QFont &val); + void slotValueChanged(QtProperty *property, const QCursor &val); + void slotFlagChanged(QtProperty *property, int val); + void slotFlagNamesChanged(QtProperty *property, const QStringList &flagNames); + void slotPropertyInserted(QtProperty *property, QtProperty *parent, QtProperty *after); + void slotPropertyRemoved(QtProperty *property, QtProperty *parent); + + void valueChanged(QtProperty *property, const QVariant &val); + + int internalPropertyToType(QtProperty *property) const; + QtVariantProperty *createSubProperty(QtVariantProperty *parent, QtVariantProperty *after, + QtProperty *internal); + void removeSubProperty(QtVariantProperty *property); + + QMap m_typeToPropertyManager; + QMap > m_typeToAttributeToAttributeType; + + QHash> m_propertyToType; + + QMap m_typeToValueType; + + + QHash m_internalToProperty; + + const QString m_constraintAttribute; + const QString m_singleStepAttribute; + const QString m_decimalsAttribute; + const QString m_enumIconsAttribute; + const QString m_enumNamesAttribute; + const QString m_flagNamesAttribute; + const QString m_maximumAttribute; + const QString m_minimumAttribute; + const QString m_regExpAttribute; +}; + +QtVariantPropertyManagerPrivate::QtVariantPropertyManagerPrivate() : + m_constraintAttribute("constraint"_L1), + m_singleStepAttribute("singleStep"_L1), + m_decimalsAttribute("decimals"_L1), + m_enumIconsAttribute("enumIcons"_L1), + m_enumNamesAttribute("enumNames"_L1), + m_flagNamesAttribute("flagNames"_L1), + m_maximumAttribute("maximum"_L1), + m_minimumAttribute("minimum"_L1), + m_regExpAttribute("regExp"_L1) +{ +} + +int QtVariantPropertyManagerPrivate::internalPropertyToType(QtProperty *property) const +{ + int type = 0; + QtAbstractPropertyManager *internPropertyManager = property->propertyManager(); + if (qobject_cast(internPropertyManager)) + type = QMetaType::Int; + else if (qobject_cast(internPropertyManager)) + type = QtVariantPropertyManager::enumTypeId(); + else if (qobject_cast(internPropertyManager)) + type = QMetaType::Bool; + else if (qobject_cast(internPropertyManager)) + type = QMetaType::Double; + return type; +} + +QtVariantProperty *QtVariantPropertyManagerPrivate::createSubProperty(QtVariantProperty *parent, + QtVariantProperty *after, QtProperty *internal) +{ + int type = internalPropertyToType(internal); + if (!type) + return 0; + + bool wasCreatingSubProperties = m_creatingSubProperties; + m_creatingSubProperties = true; + + QtVariantProperty *varChild = q_ptr->addProperty(type, internal->propertyName()); + + m_creatingSubProperties = wasCreatingSubProperties; + + varChild->setPropertyName(internal->propertyName()); + varChild->setToolTip(internal->toolTip()); + varChild->setStatusTip(internal->statusTip()); + varChild->setWhatsThis(internal->whatsThis()); + + parent->insertSubProperty(varChild, after); + + m_internalToProperty[internal] = varChild; + propertyToWrappedProperty()->insert(varChild, internal); + return varChild; +} + +void QtVariantPropertyManagerPrivate::removeSubProperty(QtVariantProperty *property) +{ + QtProperty *internChild = wrappedProperty(property); + bool wasDestroyingSubProperties = m_destroyingSubProperties; + m_destroyingSubProperties = true; + delete property; + m_destroyingSubProperties = wasDestroyingSubProperties; + m_internalToProperty.remove(internChild); + propertyToWrappedProperty()->remove(property); +} + +void QtVariantPropertyManagerPrivate::slotPropertyInserted(QtProperty *property, + QtProperty *parent, QtProperty *after) +{ + if (m_creatingProperty) + return; + + QtVariantProperty *varParent = m_internalToProperty.value(parent, nullptr); + if (!varParent) + return; + + QtVariantProperty *varAfter = nullptr; + if (after) { + varAfter = m_internalToProperty.value(after, nullptr); + if (!varAfter) + return; + } + + createSubProperty(varParent, varAfter, property); +} + +void QtVariantPropertyManagerPrivate::slotPropertyRemoved(QtProperty *property, QtProperty *parent) +{ + Q_UNUSED(parent); + + QtVariantProperty *varProperty = m_internalToProperty.value(property, nullptr); + if (!varProperty) + return; + + removeSubProperty(varProperty); +} + +void QtVariantPropertyManagerPrivate::valueChanged(QtProperty *property, const QVariant &val) +{ + QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr); + if (!varProp) + return; + emit q_ptr->valueChanged(varProp, val); + emit q_ptr->propertyChanged(varProp); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, int val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotRangeChanged(QtProperty *property, int min, int max) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) { + emit q_ptr->attributeChanged(varProp, m_minimumAttribute, QVariant(min)); + emit q_ptr->attributeChanged(varProp, m_maximumAttribute, QVariant(max)); + } +} + +void QtVariantPropertyManagerPrivate::slotSingleStepChanged(QtProperty *property, int step) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) + emit q_ptr->attributeChanged(varProp, m_singleStepAttribute, QVariant(step)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, double val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotRangeChanged(QtProperty *property, double min, double max) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) { + emit q_ptr->attributeChanged(varProp, m_minimumAttribute, QVariant(min)); + emit q_ptr->attributeChanged(varProp, m_maximumAttribute, QVariant(max)); + } +} + +void QtVariantPropertyManagerPrivate::slotSingleStepChanged(QtProperty *property, double step) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) + emit q_ptr->attributeChanged(varProp, m_singleStepAttribute, QVariant(step)); +} + +void QtVariantPropertyManagerPrivate::slotDecimalsChanged(QtProperty *property, int prec) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) + emit q_ptr->attributeChanged(varProp, m_decimalsAttribute, QVariant(prec)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, bool val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QString &val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotRegExpChanged(QtProperty *property, const QRegularExpression ®Exp) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) + emit q_ptr->attributeChanged(varProp, m_regExpAttribute, QVariant(regExp)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, QDate val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotRangeChanged(QtProperty *property, QDate min, QDate max) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) { + emit q_ptr->attributeChanged(varProp, m_minimumAttribute, QVariant(min)); + emit q_ptr->attributeChanged(varProp, m_maximumAttribute, QVariant(max)); + } +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, QTime val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QDateTime &val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QKeySequence &val) +{ + QVariant v; + v.setValue(val); + valueChanged(property, v); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QChar &val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QLocale &val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, QPoint val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, QPointF val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, QSize val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotRangeChanged(QtProperty *property, QSize min, QSize max) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) { + emit q_ptr->attributeChanged(varProp, m_minimumAttribute, QVariant(min)); + emit q_ptr->attributeChanged(varProp, m_maximumAttribute, QVariant(max)); + } +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QSizeF &val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotRangeChanged(QtProperty *property, const QSizeF &min, const QSizeF &max) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) { + emit q_ptr->attributeChanged(varProp, m_minimumAttribute, QVariant(min)); + emit q_ptr->attributeChanged(varProp, m_maximumAttribute, QVariant(max)); + } +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, QRect val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotConstraintChanged(QtProperty *property, QRect constraint) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) + emit q_ptr->attributeChanged(varProp, m_constraintAttribute, QVariant(constraint)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QRectF &val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotConstraintChanged(QtProperty *property, const QRectF &constraint) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) + emit q_ptr->attributeChanged(varProp, m_constraintAttribute, QVariant(constraint)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QColor &val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotEnumNamesChanged(QtProperty *property, const QStringList &enumNames) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) + emit q_ptr->attributeChanged(varProp, m_enumNamesAttribute, QVariant(enumNames)); +} + +void QtVariantPropertyManagerPrivate::slotEnumIconsChanged(QtProperty *property, const QMap &enumIcons) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) { + QVariant v; + v.setValue(enumIcons); + emit q_ptr->attributeChanged(varProp, m_enumIconsAttribute, v); + } +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, QSizePolicy val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QFont &val) +{ + valueChanged(property, QVariant(val)); +} + +void QtVariantPropertyManagerPrivate::slotValueChanged(QtProperty *property, const QCursor &val) +{ +#ifndef QT_NO_CURSOR + valueChanged(property, QVariant(val)); +#endif +} + +void QtVariantPropertyManagerPrivate::slotFlagNamesChanged(QtProperty *property, const QStringList &flagNames) +{ + if (QtVariantProperty *varProp = m_internalToProperty.value(property, nullptr)) + emit q_ptr->attributeChanged(varProp, m_flagNamesAttribute, QVariant(flagNames)); +} + +/*! + \class QtVariantPropertyManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtVariantPropertyManager class provides and manages QVariant based properties. + + QtVariantPropertyManager provides the addProperty() function which + creates QtVariantProperty objects. The QtVariantProperty class is + a convenience class handling QVariant based properties inheriting + QtProperty. A QtProperty object created by a + QtVariantPropertyManager instance can be converted into a + QtVariantProperty object using the variantProperty() function. + + The property's value can be retrieved using the value(), and set + using the setValue() slot. In addition the property's type, and + the type of its value, can be retrieved using the propertyType() + and valueType() functions respectively. + + A property's type is a QMetaType::QType enumerator value, and + usually a property's type is the same as its value type. But for + some properties the types differ, for example for enums, flags and + group types in which case QtVariantPropertyManager provides the + enumTypeId(), flagTypeId() and groupTypeId() functions, + respectively, to identify their property type (the value types are + QMetaType::Int for the enum and flag types, and QMetaType::UnknownType + for the group type). + + Use the isPropertyTypeSupported() function to check if a particular + property type is supported. The currently supported property types + are: + + \table + \header + \li Property Type + \li Property Type Id + \row + \li int + \li QMetaType::Int + \row + \li double + \li QMetaType::Double + \row + \li bool + \li QMetaType::Bool + \row + \li QString + \li QMetaType::QString + \row + \li QDate + \li QMetaType::QDate + \row + \li QTime + \li QMetaType::QTime + \row + \li QDateTime + \li QMetaType::QDateTime + \row + \li QKeySequence + \li QMetaType::QKeySequence + \row + \li QChar + \li QMetaType::QChar + \row + \li QLocale + \li QMetaType::QLocale + \row + \li QPoint + \li QMetaType::QPoint + \row + \li QPointF + \li QMetaType::QPointF + \row + \li QSize + \li QMetaType::QSize + \row + \li QSizeF + \li QMetaType::QSizeF + \row + \li QRect + \li QMetaType::QRect + \row + \li QRectF + \li QMetaType::QRectF + \row + \li QColor + \li QMetaType::QColor + \row + \li QSizePolicy + \li QMetaType::QSizePolicy + \row + \li QFont + \li QMetaType::QFont + \row + \li QCursor + \li QMetaType::QCursor + \row + \li enum + \li enumTypeId() + \row + \li flag + \li flagTypeId() + \row + \li group + \li groupTypeId() + \endtable + + Each property type can provide additional attributes, + e.g. QMetaType::Int and QMetaType::Double provides minimum and + maximum values. The currently supported attributes are: + + \table + \header + \li Property Type + \li Attribute Name + \li Attribute Type + \row + \li \c int + \li minimum + \li QMetaType::Int + \row + \li + \li maximum + \li QMetaType::Int + \row + \li + \li singleStep + \li QMetaType::Int + \row + \li \c double + \li minimum + \li QMetaType::Double + \row + \li + \li maximum + \li QMetaType::Double + \row + \li + \li singleStep + \li QMetaType::Double + \row + \li + \li decimals + \li QMetaType::Int + \row + \li QString + \li regExp + \li QMetaType::QRegExp + \row + \li QDate + \li minimum + \li QMetaType::QDate + \row + \li + \li maximum + \li QMetaType::QDate + \row + \li QPointF + \li decimals + \li QMetaType::Int + \row + \li QSize + \li minimum + \li QMetaType::QSize + \row + \li + \li maximum + \li QMetaType::QSize + \row + \li QSizeF + \li minimum + \li QMetaType::QSizeF + \row + \li + \li maximum + \li QMetaType::QSizeF + \row + \li + \li decimals + \li QMetaType::Int + \row + \li QRect + \li constraint + \li QMetaType::QRect + \row + \li QRectF + \li constraint + \li QMetaType::QRectF + \row + \li + \li decimals + \li QMetaType::Int + \row + \li \c enum + \li enumNames + \li QMetaType::QStringList + \row + \li + \li enumIcons + \li iconMapTypeId() + \row + \li \c flag + \li flagNames + \li QMetaType::QStringList + \endtable + + The attributes for a given property type can be retrieved using + the attributes() function. Each attribute has a value type which + can be retrieved using the attributeType() function, and a value + accessible through the attributeValue() function. In addition, the + value can be set using the setAttribute() slot. + + QtVariantManager also provides the valueChanged() signal which is + emitted whenever a property created by this manager change, and + the attributeChanged() signal which is emitted whenever an + attribute of such a property changes. + + \sa QtVariantProperty, QtVariantEditorFactory +*/ + +/*! + \fn void QtVariantPropertyManager::valueChanged(QtProperty *property, const QVariant &value) + + This signal is emitted whenever a property created by this manager + changes its value, passing a pointer to the \a property and the + new \a value as parameters. + + \sa setValue() +*/ + +/*! + \fn void QtVariantPropertyManager::attributeChanged(QtProperty *property, + const QString &attribute, const QVariant &value) + + This signal is emitted whenever an attribute of a property created + by this manager changes its value, passing a pointer to the \a + property, the \a attribute and the new \a value as parameters. + + \sa setAttribute() +*/ + +/*! + Creates a manager with the given \a parent. +*/ +QtVariantPropertyManager::QtVariantPropertyManager(QObject *parent) + : QtAbstractPropertyManager(parent), d_ptr(new QtVariantPropertyManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_creatingProperty = false; + d_ptr->m_creatingSubProperties = false; + d_ptr->m_destroyingSubProperties = false; + d_ptr->m_propertyType = 0; + + // IntPropertyManager + auto *intPropertyManager = new QtIntPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::Int] = intPropertyManager; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::Int][d_ptr->m_minimumAttribute] = QMetaType::Int; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::Int][d_ptr->m_maximumAttribute] = QMetaType::Int; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::Int][d_ptr->m_singleStepAttribute] = QMetaType::Int; + d_ptr->m_typeToValueType[QMetaType::Int] = QMetaType::Int; + connect(intPropertyManager, &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(intPropertyManager, &QtIntPropertyManager::rangeChanged, + this, [this](QtProperty *property, int min, int max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(intPropertyManager, &QtIntPropertyManager::singleStepChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotSingleStepChanged(property, value); }); + // DoublePropertyManager + auto *doublePropertyManager = new QtDoublePropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::Double] = doublePropertyManager; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::Double][d_ptr->m_minimumAttribute] = + QMetaType::Double; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::Double][d_ptr->m_maximumAttribute] = + QMetaType::Double; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::Double][d_ptr->m_singleStepAttribute] = + QMetaType::Double; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::Double][d_ptr->m_decimalsAttribute] = + QMetaType::Int; + d_ptr->m_typeToValueType[QMetaType::Double] = QMetaType::Double; + connect(doublePropertyManager, &QtDoublePropertyManager::valueChanged, + this, [this](QtProperty *property, double value) + { d_ptr->slotValueChanged(property, value); }); + connect(doublePropertyManager, &QtDoublePropertyManager::rangeChanged, + this, [this](QtProperty *property, double min, double max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(doublePropertyManager, &QtDoublePropertyManager::singleStepChanged, + this, [this](QtProperty *property, double value) + { d_ptr->slotSingleStepChanged(property, value); }); + connect(doublePropertyManager, &QtDoublePropertyManager::decimalsChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotDecimalsChanged(property, value); }); + // BoolPropertyManager + auto *boolPropertyManager = new QtBoolPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::Bool] = boolPropertyManager; + d_ptr->m_typeToValueType[QMetaType::Bool] = QMetaType::Bool; + connect(boolPropertyManager, &QtBoolPropertyManager::valueChanged, + this, [this](QtProperty *property, bool value) + { d_ptr->slotValueChanged(property, value); }); + // StringPropertyManager + auto *stringPropertyManager = new QtStringPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QString] = stringPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QString] = QMetaType::QString; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QString][d_ptr->m_regExpAttribute] = + QMetaType::QRegularExpression; + connect(stringPropertyManager, &QtStringPropertyManager::valueChanged, + this, [this](QtProperty *property, const QString &value) + { d_ptr->slotValueChanged(property, value); }); + connect(stringPropertyManager, &QtStringPropertyManager::regExpChanged, + this, [this](QtProperty *property, const QRegularExpression &value) + { d_ptr->slotRegExpChanged(property, value); }); + // DatePropertyManager + auto *datePropertyManager = new QtDatePropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QDate] = datePropertyManager; + d_ptr->m_typeToValueType[QMetaType::QDate] = QMetaType::QDate; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QDate][d_ptr->m_minimumAttribute] = + QMetaType::QDate; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QDate][d_ptr->m_maximumAttribute] = + QMetaType::QDate; + connect(datePropertyManager, &QtDatePropertyManager::valueChanged, + this, [this](QtProperty *property, const QDate &value) + { d_ptr->slotValueChanged(property, value); }); + connect(datePropertyManager, &QtDatePropertyManager::rangeChanged, + this, [this](QtProperty *property, const QDate &min, const QDate &max) + { d_ptr->slotRangeChanged(property, min, max); }); + // TimePropertyManager + auto *timePropertyManager = new QtTimePropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QTime] = timePropertyManager; + d_ptr->m_typeToValueType[QMetaType::QTime] = QMetaType::QTime; + connect(timePropertyManager, &QtTimePropertyManager::valueChanged, + this, [this](QtProperty *property, const QTime &value) + { d_ptr->slotValueChanged(property, value); }); + // DateTimePropertyManager + auto *dateTimePropertyManager = new QtDateTimePropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QDateTime] = dateTimePropertyManager; + d_ptr->m_typeToValueType[QMetaType::QDateTime] = QMetaType::QDateTime; + connect(dateTimePropertyManager, &QtDateTimePropertyManager::valueChanged, + this, [this](QtProperty *property, const QDateTime &value) + { d_ptr->slotValueChanged(property, value); }); + // KeySequencePropertyManager + auto *keySequencePropertyManager = new QtKeySequencePropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QKeySequence] = keySequencePropertyManager; + d_ptr->m_typeToValueType[QMetaType::QKeySequence] = QMetaType::QKeySequence; + connect(keySequencePropertyManager, &QtKeySequencePropertyManager::valueChanged, + this, [this](QtProperty *property, const QKeySequence &value) + { d_ptr->slotValueChanged(property, value); }); + // CharPropertyManager + auto *charPropertyManager = new QtCharPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QChar] = charPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QChar] = QMetaType::QChar; + connect(charPropertyManager, &QtCharPropertyManager::valueChanged, + this, [this](QtProperty *property, const QChar &value) + { d_ptr->slotValueChanged(property, value); }); + // LocalePropertyManager + auto *localePropertyManager = new QtLocalePropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QLocale] = localePropertyManager; + d_ptr->m_typeToValueType[QMetaType::QLocale] = QMetaType::QLocale; + connect(localePropertyManager, &QtLocalePropertyManager::valueChanged, + this, [this](QtProperty *property, const QLocale &value) + { d_ptr->slotValueChanged(property, value); }); + connect(localePropertyManager->subEnumPropertyManager(), &QtEnumPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(localePropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(localePropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // PointPropertyManager + auto *pointPropertyManager = new QtPointPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QPoint] = pointPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QPoint] = QMetaType::QPoint; + connect(pointPropertyManager, &QtPointPropertyManager::valueChanged, + this, [this](QtProperty *property, QPoint value) + { d_ptr->slotValueChanged(property, value); }); + connect(pointPropertyManager->subIntPropertyManager(), &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(pointPropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(pointPropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // PointFPropertyManager + auto *pointFPropertyManager = new QtPointFPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QPointF] = pointFPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QPointF] = QMetaType::QPointF; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QPointF][d_ptr->m_decimalsAttribute] = + QMetaType::Int; + connect(pointFPropertyManager, &QtPointFPropertyManager::valueChanged, + this, [this](QtProperty *property, QPointF value) + { d_ptr->slotValueChanged(property, value); }); + connect(pointFPropertyManager, &QtPointFPropertyManager::decimalsChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotDecimalsChanged(property, value); }); + connect(pointFPropertyManager->subDoublePropertyManager(), &QtDoublePropertyManager::valueChanged, + this, [this](QtProperty *property, double value) + { d_ptr->slotValueChanged(property, value); }); + connect(pointFPropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(pointFPropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // SizePropertyManager + auto *sizePropertyManager = new QtSizePropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QSize] = sizePropertyManager; + d_ptr->m_typeToValueType[QMetaType::QSize] = QMetaType::QSize; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QSize][d_ptr->m_minimumAttribute] = + QMetaType::QSize; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QSize][d_ptr->m_maximumAttribute] = + QMetaType::QSize; + connect(sizePropertyManager, &QtSizePropertyManager::valueChanged, + this, [this](QtProperty *property, QSize value) + { d_ptr->slotValueChanged(property, value); }); + connect(sizePropertyManager, &QtSizePropertyManager::rangeChanged, + this, [this](QtProperty *property, QSize min, QSize max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(sizePropertyManager->subIntPropertyManager(), &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(sizePropertyManager->subIntPropertyManager(), &QtIntPropertyManager::rangeChanged, + this, [this](QtProperty *property, int min, int max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(sizePropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(sizePropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // SizeFPropertyManager + auto *sizeFPropertyManager = new QtSizeFPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QSizeF] = sizeFPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QSizeF] = QMetaType::QSizeF; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QSizeF][d_ptr->m_minimumAttribute] = + QMetaType::QSizeF; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QSizeF][d_ptr->m_maximumAttribute] = + QMetaType::QSizeF; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QSizeF][d_ptr->m_decimalsAttribute] = + QMetaType::Int; + connect(sizeFPropertyManager, &QtSizeFPropertyManager::valueChanged, + this, [this](QtProperty *property, const QSizeF &value) + { d_ptr->slotValueChanged(property, value); }); + connect(sizeFPropertyManager, &QtSizeFPropertyManager::rangeChanged, + this, [this](QtProperty *property, const QSizeF &min, const QSizeF &max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(sizeFPropertyManager->subDoublePropertyManager(), &QtDoublePropertyManager::valueChanged, + this, [this](QtProperty *property, double value) + { d_ptr->slotValueChanged(property, value); }); + connect(sizeFPropertyManager->subDoublePropertyManager(), &QtDoublePropertyManager::rangeChanged, + this, [this](QtProperty *property, double min, double max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(sizeFPropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(sizeFPropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // RectPropertyManager + auto *rectPropertyManager = new QtRectPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QRect] = rectPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QRect] = QMetaType::QRect; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QRect][d_ptr->m_constraintAttribute] = + QMetaType::QRect; + connect(rectPropertyManager, &QtRectPropertyManager::valueChanged, + this, [this](QtProperty *property, QRect value) + { d_ptr->slotValueChanged(property, value); }); + connect(rectPropertyManager, &QtRectPropertyManager::constraintChanged, + this, [this](QtProperty *property, QRect value) + { d_ptr->slotConstraintChanged(property, value); }); + connect(rectPropertyManager->subIntPropertyManager(), &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(rectPropertyManager->subIntPropertyManager(), &QtIntPropertyManager::rangeChanged, + this, [this](QtProperty *property, int min, int max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(rectPropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(rectPropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // RectFPropertyManager + auto *rectFPropertyManager = new QtRectFPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QRectF] = rectFPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QRectF] = QMetaType::QRectF; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QRectF][d_ptr->m_constraintAttribute] = + QMetaType::QRectF; + d_ptr->m_typeToAttributeToAttributeType[QMetaType::QRectF][d_ptr->m_decimalsAttribute] = + QMetaType::Int; + connect(rectFPropertyManager, &QtRectFPropertyManager::valueChanged, + this, [this](QtProperty *property, const QRectF &value) + { d_ptr->slotValueChanged(property, value); }); + connect(rectFPropertyManager, &QtRectFPropertyManager::constraintChanged, + this, [this](QtProperty *property, const QRectF &value) + { d_ptr->slotConstraintChanged(property, value); }); + connect(rectFPropertyManager->subDoublePropertyManager(), &QtDoublePropertyManager::valueChanged, + this, [this](QtProperty *property, double value) + { d_ptr->slotValueChanged(property, value); }); + connect(rectFPropertyManager->subDoublePropertyManager(), &QtDoublePropertyManager::rangeChanged, + this, [this](QtProperty *property, double min, double max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(rectFPropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(rectFPropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // ColorPropertyManager + auto *colorPropertyManager = new QtColorPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QColor] = colorPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QColor] = QMetaType::QColor; + connect(colorPropertyManager, &QtColorPropertyManager::valueChanged, + this, [this](QtProperty *property, const QColor &value) + { d_ptr->slotValueChanged(property, value); }); + connect(colorPropertyManager->subIntPropertyManager(), &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(colorPropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(colorPropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // EnumPropertyManager + int enumId = enumTypeId(); + auto *enumPropertyManager = new QtEnumPropertyManager(this); + d_ptr->m_typeToPropertyManager[enumId] = enumPropertyManager; + d_ptr->m_typeToValueType[enumId] = QMetaType::Int; + d_ptr->m_typeToAttributeToAttributeType[enumId][d_ptr->m_enumNamesAttribute] = + QMetaType::QStringList; + d_ptr->m_typeToAttributeToAttributeType[enumId][d_ptr->m_enumIconsAttribute] = + iconMapTypeId(); + connect(enumPropertyManager, &QtEnumPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(enumPropertyManager, &QtEnumPropertyManager::enumNamesChanged, + this, [this](QtProperty *property, const QStringList &value) + { d_ptr->slotEnumNamesChanged(property, value); }); + connect(enumPropertyManager, &QtEnumPropertyManager::enumIconsChanged, + this, [this](QtProperty *property, const QMap &value) + { d_ptr->slotEnumIconsChanged(property, value); }); + // SizePolicyPropertyManager + auto *sizePolicyPropertyManager = new QtSizePolicyPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QSizePolicy] = sizePolicyPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QSizePolicy] = QMetaType::QSizePolicy; + connect(sizePolicyPropertyManager, &QtSizePolicyPropertyManager::valueChanged, + this, [this](QtProperty *property, QSizePolicy value) + { d_ptr->slotValueChanged(property, value); }); + connect(sizePolicyPropertyManager->subIntPropertyManager(), &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(sizePolicyPropertyManager->subIntPropertyManager(), &QtIntPropertyManager::rangeChanged, + this, [this](QtProperty *property, int min, int max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(sizePolicyPropertyManager->subEnumPropertyManager(), &QtEnumPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(sizePolicyPropertyManager->subEnumPropertyManager(), &QtEnumPropertyManager::enumNamesChanged, + this, [this](QtProperty *property, const QStringList &value) + { d_ptr->slotEnumNamesChanged(property, value); }); + connect(sizePolicyPropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(sizePolicyPropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // FontPropertyManager + auto *fontPropertyManager = new QtFontPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QFont] = fontPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QFont] = QMetaType::QFont; + connect(fontPropertyManager, &QtFontPropertyManager::valueChanged, + this, [this](QtProperty *property, const QFont &value) + { d_ptr->slotValueChanged(property, value); }); + connect(fontPropertyManager->subIntPropertyManager(), &QtIntPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(fontPropertyManager->subIntPropertyManager(), &QtIntPropertyManager::rangeChanged, + this, [this](QtProperty *property, int min, int max) + { d_ptr->slotRangeChanged(property, min, max); }); + connect(fontPropertyManager->subEnumPropertyManager(), &QtEnumPropertyManager::valueChanged, + this, [this](QtProperty *property, int value) + { d_ptr->slotValueChanged(property, value); }); + connect(fontPropertyManager->subEnumPropertyManager(), &QtEnumPropertyManager::enumNamesChanged, + this, [this](QtProperty *property, const QStringList &value) + { d_ptr->slotEnumNamesChanged(property, value); }); + connect(fontPropertyManager->subBoolPropertyManager(), &QtBoolPropertyManager::valueChanged, + this, [this](QtProperty *property, bool value) + { d_ptr->slotValueChanged(property, value); }); + connect(fontPropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(fontPropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // CursorPropertyManager + auto *cursorPropertyManager = new QtCursorPropertyManager(this); + d_ptr->m_typeToPropertyManager[QMetaType::QCursor] = cursorPropertyManager; + d_ptr->m_typeToValueType[QMetaType::QCursor] = QMetaType::QCursor; + connect(cursorPropertyManager, &QtCursorPropertyManager::valueChanged, + this, [this](QtProperty *property, const QCursor &value) + { d_ptr->slotValueChanged(property, value); }); + // FlagPropertyManager + int flagId = flagTypeId(); + auto *flagPropertyManager = new QtFlagPropertyManager(this); + d_ptr->m_typeToPropertyManager[flagId] = flagPropertyManager; + d_ptr->m_typeToValueType[flagId] = QMetaType::Int; + d_ptr->m_typeToAttributeToAttributeType[flagId][d_ptr->m_flagNamesAttribute] = + QMetaType::QStringList; + connect(flagPropertyManager, &QtFlagPropertyManager::valueChanged, + this, [this](QtProperty *property, const QColor &value) + { d_ptr->slotValueChanged(property, value); }); + connect(flagPropertyManager, &QtFlagPropertyManager::flagNamesChanged, + this, [this](QtProperty *property, const QStringList &value) + { d_ptr->slotFlagNamesChanged(property, value); }); + connect(flagPropertyManager->subBoolPropertyManager(), &QtBoolPropertyManager::valueChanged, + this, [this](QtProperty *property, bool value) + { d_ptr->slotValueChanged(property, value); }); + connect(flagPropertyManager, &QtAbstractPropertyManager::propertyInserted, + this, [this](QtProperty *property, QtProperty *parent, QtProperty *after) + { d_ptr->slotPropertyInserted(property, parent, after); }); + connect(flagPropertyManager, &QtAbstractPropertyManager::propertyRemoved, + this, [this](QtProperty *property, QtProperty *parent) + { d_ptr->slotPropertyRemoved(property, parent); }); + // FlagPropertyManager + int groupId = groupTypeId(); + auto *groupPropertyManager = new QtGroupPropertyManager(this); + d_ptr->m_typeToPropertyManager[groupId] = groupPropertyManager; + d_ptr->m_typeToValueType[groupId] = QMetaType::UnknownType; +} + +/*! + Destroys this manager, and all the properties it has created. +*/ +QtVariantPropertyManager::~QtVariantPropertyManager() +{ + clear(); +} + +/*! + Returns the given \a property converted into a QtVariantProperty. + + If the \a property was not created by this variant manager, the + function returns 0. + + \sa createProperty() +*/ +QtVariantProperty *QtVariantPropertyManager::variantProperty(const QtProperty *property) const +{ + const auto it = d_ptr->m_propertyToType.constFind(property); + if (it == d_ptr->m_propertyToType.constEnd()) + return 0; + return it.value().first; +} + +/*! + Returns true if the given \a propertyType is supported by this + variant manager; otherwise false. + + \sa propertyType() +*/ +bool QtVariantPropertyManager::isPropertyTypeSupported(int propertyType) const +{ + if (d_ptr->m_typeToValueType.contains(propertyType)) + return true; + return false; +} + +/*! + Creates and returns a variant property of the given \a propertyType + with the given \a name. + + If the specified \a propertyType is not supported by this variant + manager, this function returns 0. + + Do not use the inherited + QtAbstractPropertyManager::addProperty() function to create a + variant property (that function will always return 0 since it will + not be clear what type the property should have). + + \sa isPropertyTypeSupported() +*/ +QtVariantProperty *QtVariantPropertyManager::addProperty(int propertyType, const QString &name) +{ + if (!isPropertyTypeSupported(propertyType)) + return 0; + + bool wasCreating = d_ptr->m_creatingProperty; + d_ptr->m_creatingProperty = true; + d_ptr->m_propertyType = propertyType; + QtProperty *property = QtAbstractPropertyManager::addProperty(name); + d_ptr->m_creatingProperty = wasCreating; + d_ptr->m_propertyType = 0; + + if (!property) + return 0; + + return variantProperty(property); +} + +/*! + Returns the given \a property's value. + + If the given \a property is not managed by this manager, this + function returns an invalid variant. + + \sa setValue() +*/ +QVariant QtVariantPropertyManager::value(const QtProperty *property) const +{ + QtProperty *internProp = propertyToWrappedProperty()->value(property, nullptr); + if (internProp == nullptr) + return {}; + + QtAbstractPropertyManager *manager = internProp->propertyManager(); + if (auto *intManager = qobject_cast(manager)) { + return intManager->value(internProp); + } else if (auto *doubleManager = qobject_cast(manager)) { + return doubleManager->value(internProp); + } else if (auto *boolManager = qobject_cast(manager)) { + return boolManager->value(internProp); + } else if (auto *stringManager = qobject_cast(manager)) { + return stringManager->value(internProp); + } else if (auto *dateManager = qobject_cast(manager)) { + return dateManager->value(internProp); + } else if (auto *timeManager = qobject_cast(manager)) { + return timeManager->value(internProp); + } else if (auto *dateTimeManager = qobject_cast(manager)) { + return dateTimeManager->value(internProp); + } else if (auto *keySequenceManager = qobject_cast(manager)) { + return QVariant::fromValue(keySequenceManager->value(internProp)); + } else if (auto *charManager = qobject_cast(manager)) { + return charManager->value(internProp); + } else if (auto *localeManager = qobject_cast(manager)) { + return localeManager->value(internProp); + } else if (auto *pointManager = qobject_cast(manager)) { + return pointManager->value(internProp); + } else if (auto *pointFManager = qobject_cast(manager)) { + return pointFManager->value(internProp); + } else if (auto *sizeManager = qobject_cast(manager)) { + return sizeManager->value(internProp); + } else if (auto *sizeFManager = qobject_cast(manager)) { + return sizeFManager->value(internProp); + } else if (auto *rectManager = qobject_cast(manager)) { + return rectManager->value(internProp); + } else if (auto *rectFManager = qobject_cast(manager)) { + return rectFManager->value(internProp); + } else if (auto *colorManager = qobject_cast(manager)) { + return colorManager->value(internProp); + } else if (auto *enumManager = qobject_cast(manager)) { + return enumManager->value(internProp); + } else if (QtSizePolicyPropertyManager *sizePolicyManager = + qobject_cast(manager)) { + return sizePolicyManager->value(internProp); + } else if (auto *fontManager = qobject_cast(manager)) { + return fontManager->value(internProp); +#ifndef QT_NO_CURSOR + } else if (auto *cursorManager = qobject_cast(manager)) { + return cursorManager->value(internProp); +#endif + } else if (auto *flagManager = qobject_cast(manager)) { + return flagManager->value(internProp); + } + return {}; +} + +/*! + Returns the given \a property's value type. + + \sa propertyType() +*/ +int QtVariantPropertyManager::valueType(const QtProperty *property) const +{ + int propType = propertyType(property); + return valueType(propType); +} + +/*! + \overload + + Returns the value type associated with the given \a propertyType. +*/ +int QtVariantPropertyManager::valueType(int propertyType) const +{ + if (d_ptr->m_typeToValueType.contains(propertyType)) + return d_ptr->m_typeToValueType[propertyType]; + return 0; +} + +/*! + Returns the given \a property's type. + + \sa valueType() +*/ +int QtVariantPropertyManager::propertyType(const QtProperty *property) const +{ + const auto it = d_ptr->m_propertyToType.constFind(property); + if (it == d_ptr->m_propertyToType.constEnd()) + return 0; + return it.value().second; +} + +/*! + Returns the given \a property's value for the specified \a + attribute + + If the given \a property was not created by \e this manager, or if + the specified \a attribute does not exist, this function returns + an invalid variant. + + \sa attributes(), attributeType(), setAttribute() +*/ +QVariant QtVariantPropertyManager::attributeValue(const QtProperty *property, const QString &attribute) const +{ + int propType = propertyType(property); + if (!propType) + return {}; + + const auto it = d_ptr->m_typeToAttributeToAttributeType.constFind(propType); + if (it == d_ptr->m_typeToAttributeToAttributeType.constEnd()) + return {}; + + const QMap &attributes = it.value(); + const auto itAttr = attributes.constFind(attribute); + if (itAttr == attributes.constEnd()) + return {}; + + QtProperty *internProp = propertyToWrappedProperty()->value(property, nullptr); + if (internProp == nullptr) + return {}; + + QtAbstractPropertyManager *manager = internProp->propertyManager(); + if (auto *intManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + return intManager->maximum(internProp); + if (attribute == d_ptr->m_minimumAttribute) + return intManager->minimum(internProp); + if (attribute == d_ptr->m_singleStepAttribute) + return intManager->singleStep(internProp); + return {}; + } else if (auto *doubleManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + return doubleManager->maximum(internProp); + if (attribute == d_ptr->m_minimumAttribute) + return doubleManager->minimum(internProp); + if (attribute == d_ptr->m_singleStepAttribute) + return doubleManager->singleStep(internProp); + if (attribute == d_ptr->m_decimalsAttribute) + return doubleManager->decimals(internProp); + return {}; + } else if (auto *stringManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_regExpAttribute) + return stringManager->regExp(internProp); + return {}; + } else if (auto *dateManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + return dateManager->maximum(internProp); + if (attribute == d_ptr->m_minimumAttribute) + return dateManager->minimum(internProp); + return {}; + } else if (auto *pointFManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_decimalsAttribute) + return pointFManager->decimals(internProp); + return {}; + } else if (auto *sizeManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + return sizeManager->maximum(internProp); + if (attribute == d_ptr->m_minimumAttribute) + return sizeManager->minimum(internProp); + return {}; + } else if (auto *sizeFManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + return sizeFManager->maximum(internProp); + if (attribute == d_ptr->m_minimumAttribute) + return sizeFManager->minimum(internProp); + if (attribute == d_ptr->m_decimalsAttribute) + return sizeFManager->decimals(internProp); + return {}; + } else if (auto *rectManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_constraintAttribute) + return rectManager->constraint(internProp); + return {}; + } else if (auto *rectFManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_constraintAttribute) + return rectFManager->constraint(internProp); + if (attribute == d_ptr->m_decimalsAttribute) + return rectFManager->decimals(internProp); + return {}; + } else if (auto *enumManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_enumNamesAttribute) + return enumManager->enumNames(internProp); + if (attribute == d_ptr->m_enumIconsAttribute) { + QVariant v; + v.setValue(enumManager->enumIcons(internProp)); + return v; + } + return {}; + } else if (auto *flagManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_flagNamesAttribute) + return flagManager->flagNames(internProp); + return {}; + } + return {}; +} + +/*! + Returns a list of the given \a propertyType 's attributes. + + \sa attributeValue(), attributeType() +*/ +QStringList QtVariantPropertyManager::attributes(int propertyType) const +{ + const auto it = d_ptr->m_typeToAttributeToAttributeType.constFind(propertyType); + if (it == d_ptr->m_typeToAttributeToAttributeType.constEnd()) + return {}; + return it.value().keys(); +} + +/*! + Returns the type of the specified \a attribute of the given \a + propertyType. + + If the given \a propertyType is not supported by \e this manager, + or if the given \a propertyType does not possess the specified \a + attribute, this function returns QMetaType::UnknownType. + + \sa attributes(), valueType() +*/ +int QtVariantPropertyManager::attributeType(int propertyType, const QString &attribute) const +{ + const auto it = d_ptr->m_typeToAttributeToAttributeType.constFind(propertyType); + if (it == d_ptr->m_typeToAttributeToAttributeType.constEnd()) + return 0; + + const QMap &attributes = it.value(); + const auto itAttr = attributes.constFind(attribute); + if (itAttr == attributes.constEnd()) + return 0; + return itAttr.value(); +} + +/*! + \fn void QtVariantPropertyManager::setValue(QtProperty *property, const QVariant &value) + + Sets the value of the given \a property to \a value. + + The specified \a value must be of a type returned by valueType(), + or of type that can be converted to valueType() using the + QVariant::canConvert() function, otherwise this function does + nothing. + + \sa value(), QtVariantProperty::setValue(), valueChanged() +*/ +void QtVariantPropertyManager::setValue(QtProperty *property, const QVariant &val) +{ + int propType = val.userType(); + if (!propType) + return; + + int valType = valueType(property); + + if (propType != valType && !val.canConvert(QMetaType(valType))) + return; + + QtProperty *internProp = propertyToWrappedProperty()->value(property, nullptr); + if (internProp == nullptr) + return; + + + QtAbstractPropertyManager *manager = internProp->propertyManager(); + if (auto *intManager = qobject_cast(manager)) { + intManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *doubleManager = qobject_cast(manager)) { + doubleManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *boolManager = qobject_cast(manager)) { + boolManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *stringManager = qobject_cast(manager)) { + stringManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *dateManager = qobject_cast(manager)) { + dateManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *timeManager = qobject_cast(manager)) { + timeManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *dateTimeManager = qobject_cast(manager)) { + dateTimeManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *keySequenceManager = qobject_cast(manager)) { + keySequenceManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *charManager = qobject_cast(manager)) { + charManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *localeManager = qobject_cast(manager)) { + localeManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *pointManager = qobject_cast(manager)) { + pointManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *pointFManager = qobject_cast(manager)) { + pointFManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *sizeManager = qobject_cast(manager)) { + sizeManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *sizeFManager = qobject_cast(manager)) { + sizeFManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *rectManager = qobject_cast(manager)) { + rectManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *rectFManager = qobject_cast(manager)) { + rectFManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *colorManager = qobject_cast(manager)) { + colorManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *enumManager = qobject_cast(manager)) { + enumManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (QtSizePolicyPropertyManager *sizePolicyManager = + qobject_cast(manager)) { + sizePolicyManager->setValue(internProp, qvariant_cast(val)); + return; + } else if (auto *fontManager = qobject_cast(manager)) { + fontManager->setValue(internProp, qvariant_cast(val)); + return; +#ifndef QT_NO_CURSOR + } else if (auto *cursorManager = qobject_cast(manager)) { + cursorManager->setValue(internProp, qvariant_cast(val)); + return; +#endif + } else if (auto *flagManager = qobject_cast(manager)) { + flagManager->setValue(internProp, qvariant_cast(val)); + return; + } +} + +/*! + Sets the value of the specified \a attribute of the given \a + property, to \a value. + + The new \a value's type must be of the type returned by + attributeType(), or of a type that can be converted to + attributeType() using the QVariant::canConvert() function, + otherwise this function does nothing. + + \sa attributeValue(), QtVariantProperty::setAttribute(), attributeChanged() +*/ +void QtVariantPropertyManager::setAttribute(QtProperty *property, + const QString &attribute, const QVariant &value) +{ + QVariant oldAttr = attributeValue(property, attribute); + if (!oldAttr.isValid()) + return; + + int attrType = value.userType(); + if (!attrType) + return; + + if (attrType != attributeType(propertyType(property), attribute) && + !value.canConvert(QMetaType(attrType))) + return; + + QtProperty *internProp = propertyToWrappedProperty()->value(property, nullptr); + if (internProp == nullptr) + return; + + QtAbstractPropertyManager *manager = internProp->propertyManager(); + if (auto *intManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + intManager->setMaximum(internProp, qvariant_cast(value)); + else if (attribute == d_ptr->m_minimumAttribute) + intManager->setMinimum(internProp, qvariant_cast(value)); + else if (attribute == d_ptr->m_singleStepAttribute) + intManager->setSingleStep(internProp, qvariant_cast(value)); + return; + } else if (auto *doubleManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + doubleManager->setMaximum(internProp, qvariant_cast(value)); + if (attribute == d_ptr->m_minimumAttribute) + doubleManager->setMinimum(internProp, qvariant_cast(value)); + if (attribute == d_ptr->m_singleStepAttribute) + doubleManager->setSingleStep(internProp, qvariant_cast(value)); + if (attribute == d_ptr->m_decimalsAttribute) + doubleManager->setDecimals(internProp, qvariant_cast(value)); + return; + } else if (auto *stringManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_regExpAttribute) + stringManager->setRegExp(internProp, qvariant_cast(value)); + return; + } else if (auto *dateManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + dateManager->setMaximum(internProp, qvariant_cast(value)); + if (attribute == d_ptr->m_minimumAttribute) + dateManager->setMinimum(internProp, qvariant_cast(value)); + return; + } else if (auto *pointFManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_decimalsAttribute) + pointFManager->setDecimals(internProp, qvariant_cast(value)); + return; + } else if (auto *sizeManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + sizeManager->setMaximum(internProp, qvariant_cast(value)); + if (attribute == d_ptr->m_minimumAttribute) + sizeManager->setMinimum(internProp, qvariant_cast(value)); + return; + } else if (auto *sizeFManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_maximumAttribute) + sizeFManager->setMaximum(internProp, qvariant_cast(value)); + if (attribute == d_ptr->m_minimumAttribute) + sizeFManager->setMinimum(internProp, qvariant_cast(value)); + if (attribute == d_ptr->m_decimalsAttribute) + sizeFManager->setDecimals(internProp, qvariant_cast(value)); + return; + } else if (auto *rectManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_constraintAttribute) + rectManager->setConstraint(internProp, qvariant_cast(value)); + return; + } else if (auto *rectFManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_constraintAttribute) + rectFManager->setConstraint(internProp, qvariant_cast(value)); + if (attribute == d_ptr->m_decimalsAttribute) + rectFManager->setDecimals(internProp, qvariant_cast(value)); + return; + } else if (auto *enumManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_enumNamesAttribute) + enumManager->setEnumNames(internProp, qvariant_cast(value)); + if (attribute == d_ptr->m_enumIconsAttribute) + enumManager->setEnumIcons(internProp, qvariant_cast(value)); + return; + } else if (auto *flagManager = qobject_cast(manager)) { + if (attribute == d_ptr->m_flagNamesAttribute) + flagManager->setFlagNames(internProp, qvariant_cast(value)); + return; + } +} + +/*! + \internal +*/ +bool QtVariantPropertyManager::hasValue(const QtProperty *property) const +{ + if (propertyType(property) == groupTypeId()) + return false; + return true; +} + +/*! + \internal +*/ +QString QtVariantPropertyManager::valueText(const QtProperty *property) const +{ + const QtProperty *internProp = propertyToWrappedProperty()->value(property, nullptr); + return internProp ? internProp->valueText() : QString(); +} + +/*! + \internal +*/ +QIcon QtVariantPropertyManager::valueIcon(const QtProperty *property) const +{ + const QtProperty *internProp = propertyToWrappedProperty()->value(property, nullptr); + return internProp ? internProp->valueIcon() : QIcon(); +} + +/*! + \internal +*/ +void QtVariantPropertyManager::initializeProperty(QtProperty *property) +{ + QtVariantProperty *varProp = variantProperty(property); + if (!varProp) + return; + + const auto it = d_ptr->m_typeToPropertyManager.constFind(d_ptr->m_propertyType); + if (it != d_ptr->m_typeToPropertyManager.constEnd()) { + QtProperty *internProp = nullptr; + if (!d_ptr->m_creatingSubProperties) { + QtAbstractPropertyManager *manager = it.value(); + internProp = manager->addProperty(); + d_ptr->m_internalToProperty[internProp] = varProp; + } + propertyToWrappedProperty()->insert(varProp, internProp); + if (internProp) { + const auto children = internProp->subProperties(); + QtVariantProperty *lastProperty = nullptr; + for (QtProperty *child : children) { + QtVariantProperty *prop = d_ptr->createSubProperty(varProp, lastProperty, child); + lastProperty = prop ? prop : lastProperty; + } + } + } +} + +/*! + \internal +*/ +void QtVariantPropertyManager::uninitializeProperty(QtProperty *property) +{ + const auto type_it = d_ptr->m_propertyToType.find(property); + if (type_it == d_ptr->m_propertyToType.end()) + return; + + const auto it = propertyToWrappedProperty()->find(property); + if (it != propertyToWrappedProperty()->end()) { + QtProperty *internProp = it.value(); + if (internProp) { + d_ptr->m_internalToProperty.remove(internProp); + if (!d_ptr->m_destroyingSubProperties) { + delete internProp; + } + } + propertyToWrappedProperty()->erase(it); + } + d_ptr->m_propertyToType.erase(type_it); +} + +/*! + \internal +*/ +QtProperty *QtVariantPropertyManager::createProperty() +{ + if (!d_ptr->m_creatingProperty) + return 0; + + auto *property = new QtVariantProperty(this); + d_ptr->m_propertyToType.insert(property, {property, d_ptr->m_propertyType}); + + return property; +} + +///////////////////////////// + +class QtVariantEditorFactoryPrivate +{ + QtVariantEditorFactory *q_ptr; + Q_DECLARE_PUBLIC(QtVariantEditorFactory) +public: + + QtSpinBoxFactory *m_spinBoxFactory; + QtDoubleSpinBoxFactory *m_doubleSpinBoxFactory; + QtCheckBoxFactory *m_checkBoxFactory; + QtLineEditFactory *m_lineEditFactory; + QtDateEditFactory *m_dateEditFactory; + QtTimeEditFactory *m_timeEditFactory; + QtDateTimeEditFactory *m_dateTimeEditFactory; + QtKeySequenceEditorFactory *m_keySequenceEditorFactory; + QtCharEditorFactory *m_charEditorFactory; + QtEnumEditorFactory *m_comboBoxFactory; + QtCursorEditorFactory *m_cursorEditorFactory; + QtColorEditorFactory *m_colorEditorFactory; + QtFontEditorFactory *m_fontEditorFactory; + + QHash m_factoryToType; + QMap m_typeToFactory; +}; + +/*! + \class QtVariantEditorFactory + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtVariantEditorFactory class provides widgets for properties + created by QtVariantPropertyManager objects. + + The variant factory provides the following widgets for the + specified property types: + + \table + \header + \li Property Type + \li Widget + \row + \li \c int + \li QSpinBox + \row + \li \c double + \li QDoubleSpinBox + \row + \li \c bool + \li QCheckBox + \row + \li QString + \li QLineEdit + \row + \li QDate + \li QDateEdit + \row + \li QTime + \li QTimeEdit + \row + \li QDateTime + \li QDateTimeEdit + \row + \li QKeySequence + \li customized editor + \row + \li QChar + \li customized editor + \row + \li \c enum + \li QComboBox + \row + \li QCursor + \li QComboBox + \endtable + + Note that QtVariantPropertyManager supports several additional property + types for which the QtVariantEditorFactory class does not provide + editing widgets, e.g. QPoint and QSize. To provide widgets for other + types using the variant approach, derive from the QtVariantEditorFactory + class. + + \sa QtAbstractEditorFactory, QtVariantPropertyManager +*/ + +/*! + Creates a factory with the given \a parent. +*/ +QtVariantEditorFactory::QtVariantEditorFactory(QObject *parent) + : QtAbstractEditorFactory(parent), d_ptr(new QtVariantEditorFactoryPrivate()) +{ + d_ptr->q_ptr = this; + + d_ptr->m_spinBoxFactory = new QtSpinBoxFactory(this); + d_ptr->m_factoryToType[d_ptr->m_spinBoxFactory] = QMetaType::Int; + d_ptr->m_typeToFactory[QMetaType::Int] = d_ptr->m_spinBoxFactory; + + d_ptr->m_doubleSpinBoxFactory = new QtDoubleSpinBoxFactory(this); + d_ptr->m_factoryToType[d_ptr->m_doubleSpinBoxFactory] = QMetaType::Double; + d_ptr->m_typeToFactory[QMetaType::Double] = d_ptr->m_doubleSpinBoxFactory; + + d_ptr->m_checkBoxFactory = new QtCheckBoxFactory(this); + d_ptr->m_factoryToType[d_ptr->m_checkBoxFactory] = QMetaType::Bool; + d_ptr->m_typeToFactory[QMetaType::Bool] = d_ptr->m_checkBoxFactory; + + d_ptr->m_lineEditFactory = new QtLineEditFactory(this); + d_ptr->m_factoryToType[d_ptr->m_lineEditFactory] = QMetaType::QString; + d_ptr->m_typeToFactory[QMetaType::QString] = d_ptr->m_lineEditFactory; + + d_ptr->m_dateEditFactory = new QtDateEditFactory(this); + d_ptr->m_factoryToType[d_ptr->m_dateEditFactory] = QMetaType::QDate; + d_ptr->m_typeToFactory[QMetaType::QDate] = d_ptr->m_dateEditFactory; + + d_ptr->m_timeEditFactory = new QtTimeEditFactory(this); + d_ptr->m_factoryToType[d_ptr->m_timeEditFactory] = QMetaType::QTime; + d_ptr->m_typeToFactory[QMetaType::QTime] = d_ptr->m_timeEditFactory; + + d_ptr->m_dateTimeEditFactory = new QtDateTimeEditFactory(this); + d_ptr->m_factoryToType[d_ptr->m_dateTimeEditFactory] = QMetaType::QDateTime; + d_ptr->m_typeToFactory[QMetaType::QDateTime] = d_ptr->m_dateTimeEditFactory; + + d_ptr->m_keySequenceEditorFactory = new QtKeySequenceEditorFactory(this); + d_ptr->m_factoryToType[d_ptr->m_keySequenceEditorFactory] = QMetaType::QKeySequence; + d_ptr->m_typeToFactory[QMetaType::QKeySequence] = d_ptr->m_keySequenceEditorFactory; + + d_ptr->m_charEditorFactory = new QtCharEditorFactory(this); + d_ptr->m_factoryToType[d_ptr->m_charEditorFactory] = QMetaType::QChar; + d_ptr->m_typeToFactory[QMetaType::QChar] = d_ptr->m_charEditorFactory; + + d_ptr->m_cursorEditorFactory = new QtCursorEditorFactory(this); + d_ptr->m_factoryToType[d_ptr->m_cursorEditorFactory] = QMetaType::QCursor; + d_ptr->m_typeToFactory[QMetaType::QCursor] = d_ptr->m_cursorEditorFactory; + + d_ptr->m_colorEditorFactory = new QtColorEditorFactory(this); + d_ptr->m_factoryToType[d_ptr->m_colorEditorFactory] = QMetaType::QColor; + d_ptr->m_typeToFactory[QMetaType::QColor] = d_ptr->m_colorEditorFactory; + + d_ptr->m_fontEditorFactory = new QtFontEditorFactory(this); + d_ptr->m_factoryToType[d_ptr->m_fontEditorFactory] = QMetaType::QFont; + d_ptr->m_typeToFactory[QMetaType::QFont] = d_ptr->m_fontEditorFactory; + + d_ptr->m_comboBoxFactory = new QtEnumEditorFactory(this); + const int enumId = QtVariantPropertyManager::enumTypeId(); + d_ptr->m_factoryToType[d_ptr->m_comboBoxFactory] = enumId; + d_ptr->m_typeToFactory[enumId] = d_ptr->m_comboBoxFactory; +} + +/*! + Destroys this factory, and all the widgets it has created. +*/ +QtVariantEditorFactory::~QtVariantEditorFactory() +{ +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtVariantEditorFactory::connectPropertyManager(QtVariantPropertyManager *manager) +{ + const auto intPropertyManagers = manager->findChildren(); + for (QtIntPropertyManager *manager : intPropertyManagers) + d_ptr->m_spinBoxFactory->addPropertyManager(manager); + + const auto doublePropertyManagers = manager->findChildren(); + for (QtDoublePropertyManager *manager : doublePropertyManagers) + d_ptr->m_doubleSpinBoxFactory->addPropertyManager(manager); + + const auto boolPropertyManagers = manager->findChildren(); + for (QtBoolPropertyManager *manager : boolPropertyManagers) + d_ptr->m_checkBoxFactory->addPropertyManager(manager); + + const auto stringPropertyManagers = manager->findChildren(); + for (QtStringPropertyManager *manager : stringPropertyManagers) + d_ptr->m_lineEditFactory->addPropertyManager(manager); + + const auto datePropertyManagers = manager->findChildren(); + for (QtDatePropertyManager *manager : datePropertyManagers) + d_ptr->m_dateEditFactory->addPropertyManager(manager); + + const auto timePropertyManagers = manager->findChildren(); + for (QtTimePropertyManager *manager : timePropertyManagers) + d_ptr->m_timeEditFactory->addPropertyManager(manager); + + const auto dateTimePropertyManagers = manager->findChildren(); + for (QtDateTimePropertyManager *manager : dateTimePropertyManagers) + d_ptr->m_dateTimeEditFactory->addPropertyManager(manager); + + const auto keySequencePropertyManagers = manager->findChildren(); + for (QtKeySequencePropertyManager *manager : keySequencePropertyManagers) + d_ptr->m_keySequenceEditorFactory->addPropertyManager(manager); + + const auto charPropertyManagers = manager->findChildren(); + for (QtCharPropertyManager *manager : charPropertyManagers) + d_ptr->m_charEditorFactory->addPropertyManager(manager); + + const auto localePropertyManagers = manager->findChildren(); + for (QtLocalePropertyManager *manager : localePropertyManagers) + d_ptr->m_comboBoxFactory->addPropertyManager(manager->subEnumPropertyManager()); + + const auto pointPropertyManagers = manager->findChildren(); + for (QtPointPropertyManager *manager : pointPropertyManagers) + d_ptr->m_spinBoxFactory->addPropertyManager(manager->subIntPropertyManager()); + + const auto pointFPropertyManagers = manager->findChildren(); + for (QtPointFPropertyManager *manager : pointFPropertyManagers) + d_ptr->m_doubleSpinBoxFactory->addPropertyManager(manager->subDoublePropertyManager()); + + const auto sizePropertyManagers = manager->findChildren(); + for (QtSizePropertyManager *manager : sizePropertyManagers) + d_ptr->m_spinBoxFactory->addPropertyManager(manager->subIntPropertyManager()); + + const auto sizeFPropertyManagers = manager->findChildren(); + for (QtSizeFPropertyManager *manager : sizeFPropertyManagers) + d_ptr->m_doubleSpinBoxFactory->addPropertyManager(manager->subDoublePropertyManager()); + + const auto rectPropertyManagers = manager->findChildren(); + for (QtRectPropertyManager *manager : rectPropertyManagers) + d_ptr->m_spinBoxFactory->addPropertyManager(manager->subIntPropertyManager()); + + const auto rectFPropertyManagers = manager->findChildren(); + for (QtRectFPropertyManager *manager : rectFPropertyManagers) + d_ptr->m_doubleSpinBoxFactory->addPropertyManager(manager->subDoublePropertyManager()); + + const auto colorPropertyManagers = manager->findChildren(); + for (QtColorPropertyManager *manager : colorPropertyManagers) { + d_ptr->m_colorEditorFactory->addPropertyManager(manager); + d_ptr->m_spinBoxFactory->addPropertyManager(manager->subIntPropertyManager()); + } + + const auto enumPropertyManagers = manager->findChildren(); + for (QtEnumPropertyManager *manager : enumPropertyManagers) + d_ptr->m_comboBoxFactory->addPropertyManager(manager); + + const auto sizePolicyPropertyManagers = manager->findChildren(); + for (QtSizePolicyPropertyManager *manager : sizePolicyPropertyManagers) { + d_ptr->m_spinBoxFactory->addPropertyManager(manager->subIntPropertyManager()); + d_ptr->m_comboBoxFactory->addPropertyManager(manager->subEnumPropertyManager()); + } + + const auto fontPropertyManagers = manager->findChildren(); + for (QtFontPropertyManager *manager : fontPropertyManagers) { + d_ptr->m_fontEditorFactory->addPropertyManager(manager); + d_ptr->m_spinBoxFactory->addPropertyManager(manager->subIntPropertyManager()); + d_ptr->m_comboBoxFactory->addPropertyManager(manager->subEnumPropertyManager()); + d_ptr->m_checkBoxFactory->addPropertyManager(manager->subBoolPropertyManager()); + } + + const auto cursorPropertyManagers = manager->findChildren(); + for (QtCursorPropertyManager *manager : cursorPropertyManagers) + d_ptr->m_cursorEditorFactory->addPropertyManager(manager); + + const auto flagPropertyManagers = manager->findChildren(); + for (QtFlagPropertyManager *manager : flagPropertyManagers) + d_ptr->m_checkBoxFactory->addPropertyManager(manager->subBoolPropertyManager()); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +QWidget *QtVariantEditorFactory::createEditor(QtVariantPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + const int propType = manager->propertyType(property); + QtAbstractEditorFactoryBase *factory = d_ptr->m_typeToFactory.value(propType, nullptr); + if (!factory) + return 0; + return factory->createEditor(wrappedProperty(property), parent); +} + +/*! + \internal + + Reimplemented from the QtAbstractEditorFactory class. +*/ +void QtVariantEditorFactory::disconnectPropertyManager(QtVariantPropertyManager *manager) +{ + const auto intPropertyManagers = manager->findChildren(); + for (QtIntPropertyManager *manager : intPropertyManagers) + d_ptr->m_spinBoxFactory->removePropertyManager(manager); + + const auto doublePropertyManagers = manager->findChildren(); + for (QtDoublePropertyManager *manager : doublePropertyManagers) + d_ptr->m_doubleSpinBoxFactory->removePropertyManager(manager); + + const auto boolPropertyManagers = manager->findChildren(); + for (QtBoolPropertyManager *manager : boolPropertyManagers) + d_ptr->m_checkBoxFactory->removePropertyManager(manager); + + const auto stringPropertyManagers = manager->findChildren(); + for (QtStringPropertyManager *manager : stringPropertyManagers) + d_ptr->m_lineEditFactory->removePropertyManager(manager); + + const auto datePropertyManagers = manager->findChildren(); + for (QtDatePropertyManager *manager : datePropertyManagers) + d_ptr->m_dateEditFactory->removePropertyManager(manager); + + const auto timePropertyManagers = manager->findChildren(); + for (QtTimePropertyManager *manager : timePropertyManagers) + d_ptr->m_timeEditFactory->removePropertyManager(manager); + + const auto dateTimePropertyManagers = manager->findChildren(); + for (QtDateTimePropertyManager *manager : dateTimePropertyManagers) + d_ptr->m_dateTimeEditFactory->removePropertyManager(manager); + + const auto keySequencePropertyManagers = manager->findChildren(); + for (QtKeySequencePropertyManager *manager : keySequencePropertyManagers) + d_ptr->m_keySequenceEditorFactory->removePropertyManager(manager); + + const auto charPropertyManagers = manager->findChildren(); + for (QtCharPropertyManager *manager : charPropertyManagers) + d_ptr->m_charEditorFactory->removePropertyManager(manager); + + const auto localePropertyManagers = manager->findChildren(); + for (QtLocalePropertyManager *manager : localePropertyManagers) + d_ptr->m_comboBoxFactory->removePropertyManager(manager->subEnumPropertyManager()); + + const auto pointPropertyManagers = manager->findChildren(); + for (QtPointPropertyManager *manager : pointPropertyManagers) + d_ptr->m_spinBoxFactory->removePropertyManager(manager->subIntPropertyManager()); + + const auto pointFPropertyManagers = manager->findChildren(); + for (QtPointFPropertyManager *manager : pointFPropertyManagers) + d_ptr->m_doubleSpinBoxFactory->removePropertyManager(manager->subDoublePropertyManager()); + + const auto sizePropertyManagers = manager->findChildren(); + for (QtSizePropertyManager *manager : sizePropertyManagers) + d_ptr->m_spinBoxFactory->removePropertyManager(manager->subIntPropertyManager()); + + const auto sizeFPropertyManagers = manager->findChildren(); + for (QtSizeFPropertyManager *manager : sizeFPropertyManagers) + d_ptr->m_doubleSpinBoxFactory->removePropertyManager(manager->subDoublePropertyManager()); + + const auto rectPropertyManagers = manager->findChildren(); + for (QtRectPropertyManager *manager : rectPropertyManagers) + d_ptr->m_spinBoxFactory->removePropertyManager(manager->subIntPropertyManager()); + + const auto rectFPropertyManagers = manager->findChildren(); + for (QtRectFPropertyManager *manager : rectFPropertyManagers) + d_ptr->m_doubleSpinBoxFactory->removePropertyManager(manager->subDoublePropertyManager()); + + const auto colorPropertyManagers = manager->findChildren(); + for (QtColorPropertyManager *manager : colorPropertyManagers) { + d_ptr->m_colorEditorFactory->removePropertyManager(manager); + d_ptr->m_spinBoxFactory->removePropertyManager(manager->subIntPropertyManager()); + } + + const auto enumPropertyManagers = manager->findChildren(); + for (QtEnumPropertyManager *manager : enumPropertyManagers) + d_ptr->m_comboBoxFactory->removePropertyManager(manager); + + const auto sizePolicyPropertyManagers = manager->findChildren(); + for (QtSizePolicyPropertyManager *manager : sizePolicyPropertyManagers) { + d_ptr->m_spinBoxFactory->removePropertyManager(manager->subIntPropertyManager()); + d_ptr->m_comboBoxFactory->removePropertyManager(manager->subEnumPropertyManager()); + } + + const auto fontPropertyManagers = manager->findChildren(); + for (QtFontPropertyManager *manager : fontPropertyManagers) { + d_ptr->m_fontEditorFactory->removePropertyManager(manager); + d_ptr->m_spinBoxFactory->removePropertyManager(manager->subIntPropertyManager()); + d_ptr->m_comboBoxFactory->removePropertyManager(manager->subEnumPropertyManager()); + d_ptr->m_checkBoxFactory->removePropertyManager(manager->subBoolPropertyManager()); + } + + const auto cursorPropertyManagers = manager->findChildren(); + for (QtCursorPropertyManager *manager : cursorPropertyManagers) + d_ptr->m_cursorEditorFactory->removePropertyManager(manager); + + const auto flagPropertyManagers = manager->findChildren(); + for (QtFlagPropertyManager *manager : flagPropertyManagers) + d_ptr->m_checkBoxFactory->removePropertyManager(manager->subBoolPropertyManager()); +} + +QT_END_NAMESPACE + +#include "moc_qtvariantproperty_p.cpp" diff --git a/src/shared/qtpropertybrowser/qtvariantproperty_p.h b/src/shared/qtpropertybrowser/qtvariantproperty_p.h new file mode 100644 index 00000000000..825ee7c6862 --- /dev/null +++ b/src/shared/qtpropertybrowser/qtvariantproperty_p.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTVARIANTPROPERTY_H +#define QTVARIANTPROPERTY_H + +#include "qtpropertybrowser_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QRegularExpression; + +class QtVariantPropertyManager; + +class QtVariantProperty : public QtProperty +{ +public: + ~QtVariantProperty(); + QVariant value() const; + QVariant attributeValue(const QString &attribute) const; + int valueType() const; + int propertyType() const; + + void setValue(const QVariant &value); + void setAttribute(const QString &attribute, const QVariant &value); +protected: + QtVariantProperty(QtVariantPropertyManager *manager); +private: + friend class QtVariantPropertyManager; + QScopedPointer d_ptr; +}; + +class QtVariantPropertyManager : public QtAbstractPropertyManager +{ + Q_OBJECT +public: + QtVariantPropertyManager(QObject *parent = 0); + ~QtVariantPropertyManager(); + + virtual QtVariantProperty *addProperty(int propertyType, const QString &name = QString()); + + int propertyType(const QtProperty *property) const; + int valueType(const QtProperty *property) const; + QtVariantProperty *variantProperty(const QtProperty *property) const; + + virtual bool isPropertyTypeSupported(int propertyType) const; + virtual int valueType(int propertyType) const; + virtual QStringList attributes(int propertyType) const; + virtual int attributeType(int propertyType, const QString &attribute) const; + + virtual QVariant value(const QtProperty *property) const; + virtual QVariant attributeValue(const QtProperty *property, const QString &attribute) const; + + static int enumTypeId(); + static int flagTypeId(); + static int groupTypeId(); + static int iconMapTypeId(); +public Q_SLOTS: + virtual void setValue(QtProperty *property, const QVariant &val); + virtual void setAttribute(QtProperty *property, + const QString &attribute, const QVariant &value); +Q_SIGNALS: + void valueChanged(QtProperty *property, const QVariant &val); + void attributeChanged(QtProperty *property, + const QString &attribute, const QVariant &val); +protected: + bool hasValue(const QtProperty *property) const override; + QString valueText(const QtProperty *property) const override; + QIcon valueIcon(const QtProperty *property) const override; + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; + QtProperty *createProperty() override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtVariantPropertyManager) + Q_DISABLE_COPY_MOVE(QtVariantPropertyManager) +}; + +class QtVariantEditorFactory : public QtAbstractEditorFactory +{ + Q_OBJECT +public: + QtVariantEditorFactory(QObject *parent = 0); + ~QtVariantEditorFactory(); +protected: + void connectPropertyManager(QtVariantPropertyManager *manager) override; + QWidget *createEditor(QtVariantPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtVariantPropertyManager *manager) override; +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtVariantEditorFactory) + Q_DISABLE_COPY_MOVE(QtVariantEditorFactory) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/shared/qttoolbardialog/images/back.png b/src/shared/qttoolbardialog/images/back.png new file mode 100644 index 00000000000..e58177f43cb Binary files /dev/null and b/src/shared/qttoolbardialog/images/back.png differ diff --git a/src/shared/qttoolbardialog/images/down.png b/src/shared/qttoolbardialog/images/down.png new file mode 100644 index 00000000000..29d1d4439a1 Binary files /dev/null and b/src/shared/qttoolbardialog/images/down.png differ diff --git a/src/shared/qttoolbardialog/images/forward.png b/src/shared/qttoolbardialog/images/forward.png new file mode 100644 index 00000000000..34b91f09fa3 Binary files /dev/null and b/src/shared/qttoolbardialog/images/forward.png differ diff --git a/src/shared/qttoolbardialog/images/minus.png b/src/shared/qttoolbardialog/images/minus.png new file mode 100644 index 00000000000..d6f233d7399 Binary files /dev/null and b/src/shared/qttoolbardialog/images/minus.png differ diff --git a/src/shared/qttoolbardialog/images/plus.png b/src/shared/qttoolbardialog/images/plus.png new file mode 100644 index 00000000000..40df1134f84 Binary files /dev/null and b/src/shared/qttoolbardialog/images/plus.png differ diff --git a/src/shared/qttoolbardialog/images/up.png b/src/shared/qttoolbardialog/images/up.png new file mode 100644 index 00000000000..e4373122171 Binary files /dev/null and b/src/shared/qttoolbardialog/images/up.png differ diff --git a/src/shared/qttoolbardialog/qttoolbardialog.cpp b/src/shared/qttoolbardialog/qttoolbardialog.cpp new file mode 100644 index 00000000000..96b444f6fa2 --- /dev/null +++ b/src/shared/qttoolbardialog/qttoolbardialog.cpp @@ -0,0 +1,1766 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qttoolbardialog_p.h" +#include "ui_qttoolbardialog.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QtFullToolBarManagerPrivate; + +class QtFullToolBarManager : public QObject +{ + Q_OBJECT +public: + QtFullToolBarManager(QObject *parent); + ~QtFullToolBarManager(); + + void setMainWindow(QMainWindow *mainWindow); + QMainWindow *mainWindow() const; + + void addCategory(const QString &category); + bool hasCategory(const QString &category) const; + QStringList categories() const; + QList categoryActions(const QString &category) const; + QString actionCategory(QAction *action) const; + + // only non-separator + void addAction(QAction *action, const QString &category); + + void removeAction(QAction *action); + + QSet actions() const; + bool isWidgetAction(QAction *action) const; + + /* + Adds (registers) toolBar. Adds (registers) actions that already exists in toolBar. + Remembers toolbar and its actions as a default. + */ + void addDefaultToolBar(QToolBar *toolBar, const QString &category); + + void removeDefaultToolBar(QToolBar *toolBar); + // NULL on action list means separator. + QHash> defaultToolBars() const; + bool isDefaultToolBar(QToolBar *toolBar) const; + + QToolBar *createToolBar(const QString &toolBarName); + void deleteToolBar(QToolBar *toolBar); // only those which were created, not added + + QList actions(QToolBar *toolBar) const; + + void setToolBars(const QHash> &actions); + void setToolBar(QToolBar *toolBar, const QList &actions); + + QHash> toolBarsActions() const; + QByteArray saveState(int version = 0) const; + bool restoreState(const QByteArray &state, int version = 0); + +public slots: + + void resetToolBar(QToolBar *toolBar); + void resetAllToolBars(); + +signals: + void toolBarCreated(QToolBar *toolBar); + void toolBarRemoved(QToolBar *toolBar); + + /* + If QToolBarWidgetAction was in another tool bar and is inserted into + this toolBar, toolBarChanged is first emitted for other toolbar - without + that action. (Another approach may be that user first must call setToolBar + without that action for old tool bar) + */ + void toolBarChanged(QToolBar *toolBar, const QList &actions); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtFullToolBarManager) + Q_DISABLE_COPY_MOVE(QtFullToolBarManager) +}; + +class QtFullToolBarManagerPrivate +{ + class QtFullToolBarManager *q_ptr; + Q_DECLARE_PUBLIC(QtFullToolBarManager) + +public: + + QToolBar *toolBarWidgetAction(QAction *action) const; + void removeWidgetActions(const QHash> &actions); + + enum { + VersionMarker = 0xff, + ToolBarMarker = 0xfe, + CustomToolBarMarker = 0xfd, + }; + + void saveState(QDataStream &stream) const; + bool restoreState(QDataStream &stream) const; + QToolBar *findDefaultToolBar(const QString &objectName) const; + QAction *findAction(const QString &actionName) const; + + QToolBar *toolBarByName(const QString &toolBarName) const; + + QHash> categoryToActions; + QHash actionToCategory; + + QSet allActions; + QHash widgetActions; + QSet regularActions; + QHash> actionToToolBars; + + QHash> toolBars; + QHash> toolBarsWithSeparators; + QHash> defaultToolBars; + QList customToolBars; + + QMainWindow *theMainWindow{nullptr}; +}; + +QToolBar *QtFullToolBarManagerPrivate::toolBarWidgetAction(QAction *action) const +{ + if (widgetActions.contains(action)) + return widgetActions.value(action); + return 0; +} + +void QtFullToolBarManagerPrivate::removeWidgetActions(const QHash> + &actions) +{ + auto itToolBar = actions.constBegin(); + while (itToolBar != actions.constEnd()) { + QToolBar *toolBar = itToolBar.key(); + auto newActions = toolBars.value(toolBar); + auto newActionsWithSeparators = toolBarsWithSeparators.value(toolBar); + + QList removedActions; + const auto actionList = itToolBar.value(); + for (QAction *action : actionList) { + if (newActions.contains(action) && toolBarWidgetAction(action) == toolBar) { + newActions.removeAll(action); + newActionsWithSeparators.removeAll(action); + removedActions.append(action); + } + } + + //emit q_ptr->toolBarChanged(toolBar, newActions); + + toolBars.insert(toolBar, newActions); + toolBarsWithSeparators.insert(toolBar, newActionsWithSeparators); + for (QAction *oldAction : std::as_const(removedActions)) { + widgetActions.insert(oldAction, 0); + actionToToolBars[oldAction].removeAll(toolBar); + } + + ++itToolBar; + } +} + +void QtFullToolBarManagerPrivate::saveState(QDataStream &stream) const +{ + stream << (uchar) ToolBarMarker; + stream << defaultToolBars.size(); + auto itToolBar = defaultToolBars.constBegin(); + while (itToolBar != defaultToolBars.constEnd()) { + QToolBar *tb = itToolBar.key(); + if (tb->objectName().isEmpty()) { + qWarning("QtToolBarManager::saveState(): 'objectName' not set for QToolBar " + "%p '%s', using 'windowTitle' instead", + tb, tb->windowTitle().toLocal8Bit().constData()); + stream << tb->windowTitle(); + } else { + stream << tb->objectName(); + } + + const auto actions = toolBars.value(tb); + stream << actions.size(); + for (QAction *action : actions) { + if (action) { + if (action->objectName().isEmpty()) { + qWarning("QtToolBarManager::saveState(): 'objectName' not set for QAction " + "%p '%s', using 'text' instead", + action, action->text().toLocal8Bit().constData()); + stream << action->text(); + } else { + stream << action->objectName(); + } + } else { + stream << QString(); + } + } + ++itToolBar; + } + + + stream << (uchar) CustomToolBarMarker; + stream << toolBars.size() - defaultToolBars.size(); + itToolBar = toolBars.constBegin(); + while (itToolBar != toolBars.constEnd()) { + QToolBar *tb = itToolBar.key(); + if (!defaultToolBars.contains(tb)) { + stream << tb->objectName(); + stream << tb->windowTitle(); + + stream << toolBars[tb].size(); + + const auto actions = toolBars.value(tb); + for (QAction *action : actions) { + if (action) { + if (action->objectName().isEmpty()) { + qWarning("QtToolBarManager::saveState(): 'objectName' not set for QAction " + "%p '%s', using 'text' instead", + action, action->text().toLocal8Bit().constData()); + stream << action->text(); + } else { + stream << action->objectName(); + } + } else { + stream << QString(); + } + } + } + ++itToolBar; + } +} + +bool QtFullToolBarManagerPrivate::restoreState(QDataStream &stream) const +{ + uchar tmarker; + stream >> tmarker; + if (tmarker != ToolBarMarker) + return false; + + int toolBars; + stream >> toolBars; + for (int i = 0; i < toolBars; i++) { + QString objectName; + stream >> objectName; + int actionCount; + stream >> actionCount; + QList actions; + for (int j = 0; j < actionCount; j++) { + QString actionName; + stream >> actionName; + + if (actionName.isEmpty()) + actions.append(0); + else { + QAction *action = findAction(actionName); + if (action) + actions.append(action); + } + } + + QToolBar *toolBar = findDefaultToolBar(objectName); + if (toolBar) + q_ptr->setToolBar(toolBar, actions); + } + + + + uchar ctmarker; + stream >> ctmarker; + if (ctmarker != CustomToolBarMarker) + return false; + + auto oldCustomToolBars = customToolBars; + + stream >> toolBars; + for (int i = 0; i < toolBars; i++) { + QString objectName; + QString toolBarName; + int actionCount; + stream >> objectName; + stream >> toolBarName; + stream >> actionCount; + QList actions; + for (int j = 0; j < actionCount; j++) { + QString actionName; + stream >> actionName; + + if (actionName.isEmpty()) + actions.append(0); + else { + QAction *action = findAction(actionName); + if (action) + actions.append(action); + } + } + + QToolBar *toolBar = toolBarByName(objectName); + if (toolBar) { + toolBar->setWindowTitle(toolBarName); + oldCustomToolBars.removeAll(toolBar); + } + else + toolBar = q_ptr->createToolBar(toolBarName); + if (toolBar) { + toolBar->setObjectName(objectName); + q_ptr->setToolBar(toolBar, actions); + } + } + for (QToolBar *toolBar : std::as_const(oldCustomToolBars)) + q_ptr->deleteToolBar(toolBar); + return true; +} + +QToolBar *QtFullToolBarManagerPrivate::findDefaultToolBar(const QString &objectName) const +{ + auto itToolBar = defaultToolBars.constBegin(); + while (itToolBar != defaultToolBars.constEnd()) { + QToolBar *tb = itToolBar.key(); + if (tb->objectName() == objectName) + return tb; + + ++itToolBar; + } + + qWarning("QtToolBarManager::restoreState(): cannot find a QToolBar named " + "'%s', trying to match using 'windowTitle' instead.", + objectName.toLocal8Bit().constData()); + + itToolBar = defaultToolBars.constBegin(); + while (itToolBar != defaultToolBars.constEnd()) { + QToolBar *tb = itToolBar.key(); + if (tb->windowTitle() == objectName) + return tb; + + ++itToolBar; + } + qWarning("QtToolBarManager::restoreState(): cannot find a QToolBar with " + "matching 'windowTitle' (looking for '%s').", + objectName.toLocal8Bit().constData()); + + return 0; +} + +QAction *QtFullToolBarManagerPrivate::findAction(const QString &actionName) const +{ + auto it = + std::find_if(allActions.cbegin(), allActions.cend(), + [&actionName] (const QAction *a) { return a->objectName() == actionName; }); + if (it != allActions.cend()) + return *it; + qWarning("QtToolBarManager::restoreState(): cannot find a QAction named " + "'%s', trying to match using 'text' instead.", + actionName.toLocal8Bit().constData()); + + it = std::find_if(allActions.cbegin(), allActions.cend(), + [&actionName] (const QAction *a) { return a->text() == actionName; }); + if (it != allActions.cend()) + return *it; + qWarning("QtToolBarManager::restoreState(): cannot find a QAction with " + "matching 'text' (looking for '%s').", + actionName.toLocal8Bit().constData()); + + return 0; +} + +QToolBar *QtFullToolBarManagerPrivate::toolBarByName(const QString &toolBarName) const +{ + auto itToolBar = toolBars.constBegin(); + while (itToolBar != toolBars.constEnd()) { + QToolBar *toolBar = itToolBar.key(); + if (toolBar->objectName() == toolBarName) + return toolBar; + + ++itToolBar; + } + return 0; +} + +////////////////////////////// + +QtFullToolBarManager::QtFullToolBarManager(QObject *parent) + : QObject(parent), d_ptr(new QtFullToolBarManagerPrivate) +{ + d_ptr->q_ptr = this; +} + +QtFullToolBarManager::~QtFullToolBarManager() +{ +} + +void QtFullToolBarManager::setMainWindow(QMainWindow *mainWindow) +{ + d_ptr->theMainWindow = mainWindow; +} + +QMainWindow *QtFullToolBarManager::mainWindow() const +{ + return d_ptr->theMainWindow; +} + +void QtFullToolBarManager::addCategory(const QString &category) +{ + d_ptr->categoryToActions[category] = QList(); +} + +bool QtFullToolBarManager::hasCategory(const QString &category) const +{ + return d_ptr->categoryToActions.contains(category); +} + +QStringList QtFullToolBarManager::categories() const +{ + return d_ptr->categoryToActions.keys(); +} + +QList QtFullToolBarManager::categoryActions(const QString &category) const +{ + const auto it = d_ptr->categoryToActions.constFind(category); + if (it != d_ptr->categoryToActions.constEnd()) + return it.value(); + return {}; +} + +QString QtFullToolBarManager::actionCategory(QAction *action) const +{ + return d_ptr->actionToCategory.value(action, {}); +} + +void QtFullToolBarManager::addAction(QAction *action, const QString &category) +{ + if (!action) + return; + if (action->isSeparator()) + return; + if (d_ptr->allActions.contains(action)) + return; + if (qstrcmp(action->metaObject()->className(), "QToolBarWidgetAction") == 0) + d_ptr->widgetActions.insert(action, 0); + else + d_ptr->regularActions.insert(action); + d_ptr->allActions.insert(action); + d_ptr->categoryToActions[category].append(action); + d_ptr->actionToCategory[action] = category; +} + +void QtFullToolBarManager::removeAction(QAction *action) +{ + if (!d_ptr->allActions.contains(action)) + return; + + const auto toolBars = d_ptr->actionToToolBars[action]; + for (QToolBar *toolBar : toolBars) { + d_ptr->toolBars[toolBar].removeAll(action); + d_ptr->toolBarsWithSeparators[toolBar].removeAll(action); + + toolBar->removeAction(action); + } + + auto itDefault = d_ptr->defaultToolBars.constBegin(); + while (itDefault != d_ptr->defaultToolBars.constEnd()) { + if (itDefault.value().contains(action)) + d_ptr->defaultToolBars[itDefault.key()].removeAll(action); + + itDefault++; + } + + d_ptr->allActions.remove(action); + d_ptr->widgetActions.remove(action); + d_ptr->regularActions.remove(action); + d_ptr->actionToToolBars.remove(action); + + QString category = d_ptr->actionToCategory.value(action); + d_ptr->actionToCategory.remove(action); + d_ptr->categoryToActions[category].removeAll(action); + + if (d_ptr->categoryToActions[category].isEmpty()) + d_ptr->categoryToActions.remove(category); +} + +QSet QtFullToolBarManager::actions() const +{ + return d_ptr->allActions; +} + +bool QtFullToolBarManager::isWidgetAction(QAction *action) const +{ + if (d_ptr->widgetActions.contains(action)) + return true; + return false; +} + +void QtFullToolBarManager::addDefaultToolBar(QToolBar *toolBar, const QString &category) +{ + if (!toolBar) + return; + if (d_ptr->toolBars.contains(toolBar)) + return; + // could be also checked if toolBar belongs to mainwindow + + QList newActionsWithSeparators; + QList newActions; + const auto actions = toolBar->actions(); + for (QAction *action : actions) { + addAction(action, category); + if (d_ptr->widgetActions.contains(action)) + d_ptr->widgetActions.insert(action, toolBar); + newActionsWithSeparators.append(action); + if (action->isSeparator()) + action = 0; + else + d_ptr->actionToToolBars[action].append(toolBar); + newActions.append(action); + } + d_ptr->defaultToolBars.insert(toolBar, newActions); + //Below could be done by call setToolBar() if we want signal emission here. + d_ptr->toolBars.insert(toolBar, newActions); + d_ptr->toolBarsWithSeparators.insert(toolBar, newActionsWithSeparators); +} + +void QtFullToolBarManager::removeDefaultToolBar(QToolBar *toolBar) +{ + if (!d_ptr->defaultToolBars.contains(toolBar)) + return; + + const auto defaultActions = d_ptr->defaultToolBars[toolBar]; + setToolBar(toolBar, QList()); + for (QAction *action : defaultActions) + removeAction(action); + + d_ptr->toolBars.remove(toolBar); + d_ptr->toolBarsWithSeparators.remove(toolBar); + d_ptr->defaultToolBars.remove(toolBar); + + for (QAction *action : defaultActions) { + if (action) + toolBar->insertAction(0, action); + else + toolBar->insertSeparator(0); + } +} + +QHash> QtFullToolBarManager::defaultToolBars() const +{ + return d_ptr->defaultToolBars; +} + +bool QtFullToolBarManager::isDefaultToolBar(QToolBar *toolBar) const +{ + if (d_ptr->defaultToolBars.contains(toolBar)) + return true; + return false; +} + +QToolBar *QtFullToolBarManager::createToolBar(const QString &toolBarName) +{ + if (!mainWindow()) + return 0; + QToolBar *toolBar = new QToolBar(toolBarName, mainWindow()); + int i = 1; + const QString prefix = "_Custom_Toolbar_%1"_L1; + QString name = prefix.arg(i); + while (d_ptr->toolBarByName(name)) + name = prefix.arg(++i); + toolBar->setObjectName(name); + mainWindow()->addToolBar(toolBar); + d_ptr->customToolBars.append(toolBar); + d_ptr->toolBars.insert(toolBar, QList()); + d_ptr->toolBarsWithSeparators.insert(toolBar, QList()); + return toolBar; +} + +void QtFullToolBarManager::deleteToolBar(QToolBar *toolBar) +{ + if (!d_ptr->toolBars.contains(toolBar)) + return; + if (d_ptr->defaultToolBars.contains(toolBar)) + return; + setToolBar(toolBar, QList()); + d_ptr->customToolBars.removeAll(toolBar); + d_ptr->toolBars.remove(toolBar); + d_ptr->toolBarsWithSeparators.remove(toolBar); + delete toolBar; +} + +QList QtFullToolBarManager::actions(QToolBar *toolBar) const +{ + if (d_ptr->toolBars.contains(toolBar)) + return d_ptr->toolBars.value(toolBar); + return QList(); +} + +void QtFullToolBarManager::setToolBars(const QHash> &actions) +{ + auto it = actions.constBegin(); + while (it != actions.constEnd()) { + setToolBar(it.key(), it.value()); + ++it; + } +} + +void QtFullToolBarManager::setToolBar(QToolBar *toolBar, const QList &actions) +{ + if (!toolBar) + return; + if (!d_ptr->toolBars.contains(toolBar)) + return; + + if (actions == d_ptr->toolBars[toolBar]) + return; + + QHash> toRemove; + + QList newActions; + for (QAction *action : actions) { + if (!action || (!newActions.contains(action) && d_ptr->allActions.contains(action))) + newActions.append(action); + + QToolBar *oldToolBar = d_ptr->toolBarWidgetAction(action); + if (oldToolBar && oldToolBar != toolBar) + toRemove[oldToolBar].append(action); + } + + d_ptr->removeWidgetActions(toRemove); + + const auto oldActions = d_ptr->toolBarsWithSeparators.value(toolBar); + for (QAction *action : oldActions) { + /* + When addDefaultToolBar() separator actions could be checked if they are + inserted in other toolbars - if yes then create new one. + */ + if (d_ptr->toolBarWidgetAction(action) == toolBar) + d_ptr->widgetActions.insert(action, 0); + toolBar->removeAction(action); + if (action->isSeparator()) + delete action; + else + d_ptr->actionToToolBars[action].removeAll(toolBar); + } + + QList newActionsWithSeparators; + for (QAction *action : std::as_const(newActions)) { + QAction *newAction = nullptr; + if (!action) + newAction = toolBar->insertSeparator(0); + if (d_ptr->allActions.contains(action)) { + toolBar->insertAction(0, action); + newAction = action; + d_ptr->actionToToolBars[action].append(toolBar); + } + newActionsWithSeparators.append(newAction); + } + d_ptr->toolBars.insert(toolBar, newActions); + d_ptr->toolBarsWithSeparators.insert(toolBar, newActionsWithSeparators); +} + +QHash> QtFullToolBarManager::toolBarsActions() const +{ + return d_ptr->toolBars; +} + +void QtFullToolBarManager::resetToolBar(QToolBar *toolBar) +{ + if (!isDefaultToolBar(toolBar)) + return; + setToolBar(toolBar, defaultToolBars().value(toolBar)); +} + +void QtFullToolBarManager::resetAllToolBars() +{ + setToolBars(defaultToolBars()); + const auto oldCustomToolBars = d_ptr->customToolBars; + for (QToolBar *tb : oldCustomToolBars) + deleteToolBar(tb); +} + +QByteArray QtFullToolBarManager::saveState(int version) const +{ + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << int(QtFullToolBarManagerPrivate::VersionMarker); + stream << version; + d_ptr->saveState(stream); + return data; +} + +bool QtFullToolBarManager::restoreState(const QByteArray &state, int version) +{ + QByteArray sd = state; + QDataStream stream(&sd, QIODevice::ReadOnly); + int marker, v; + stream >> marker; + stream >> v; + if (marker != QtFullToolBarManagerPrivate::VersionMarker || v != version) + return false; + return d_ptr->restoreState(stream); +} + + +class QtToolBarManagerPrivate +{ + class QtToolBarManager *q_ptr; + Q_DECLARE_PUBLIC(QtToolBarManager) +public: + QtFullToolBarManager *manager; +}; + +////////////////////////////////////// + +/*! \class QtToolBarManager + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtToolBarManager class provides toolbar management for + main windows. + + The QtToolBarManager is typically used with a QtToolBarDialog + which allows the user to customize the toolbars for a given main + window. The QtToolBarDialog class's functionality is controlled by + an instance of the QtToolBarManager class, and the main window is + specified using the QtToolBarManager class's setMainWindow() + function. + + The currently specified main window can be retrieved using the + mainWindow() function. + + The toolbar manager holds lists of the given main window's actions + and toolbars, and can add actions and toolbars to these + lists using the addAction() and addToolBar() functions + respectively. The actions can in addition be categorized + acccording to the user's preferences. The toolbar manager can also + remove custom actions and toolbars using the removeAction() and + removeToolBar() functions. + + Finally, the QtToolBarManager is able to save the customized state + of its toolbars using the saveState() function as well as restore + the toolbars' saved state using restoreState() function. + + \sa QtToolBarDialog +*/ + +/*! + Creates a toolbar manager with the given \a parent. +*/ +QtToolBarManager::QtToolBarManager(QObject *parent) + : QObject(parent), d_ptr(new QtToolBarManagerPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->manager = new QtFullToolBarManager(this); +} + +/*! + Destroys the toolbar manager. +*/ +QtToolBarManager::~QtToolBarManager() +{ +} + +/*! + Sets the main window upon which the toolbar manager operates, to + be the given \a mainWindow. +*/ +void QtToolBarManager::setMainWindow(QMainWindow *mainWindow) +{ + d_ptr->manager->setMainWindow(mainWindow); +} + +/*! + Returns the main window associated this toolbar manager. +*/ +QMainWindow *QtToolBarManager::mainWindow() const +{ + return d_ptr->manager->mainWindow(); +} + +/*! + Adds the given \a action to the given \a category in the manager's + list of actions. If the \a category doesn't exist it is created. + Only non separator actions can be added. If the action is already + added to the list, the function doesn't do anything. + + \sa removeAction() +*/ +void QtToolBarManager::addAction(QAction *action, const QString &category) +{ + d_ptr->manager->addAction(action, category); +} + +/*! + Removes the specified \a action from the manager's list of + actions. The action is also removed from all the registered + toolbars. If the specified \a action is the only action in its + category, that category is removed as well. + + \sa addAction() +*/ +void QtToolBarManager::removeAction(QAction *action) +{ + d_ptr->manager->removeAction(action); +} + +/*! + Adds the given \a toolBar to the manager's toolbar list. + + All the \a toolBar's actions are automatically added to the given + \a category in the manager's list of actions if they're not + already there. The manager remembers which toolbar the actions + belonged to, so, when the \a toolBar is removed, its actions will + be removed as well. + + Custom toolbars are created with the main window returned by + the mainWindow() function, as its parent. + + \sa removeToolBar() +*/ +void QtToolBarManager::addToolBar(QToolBar *toolBar, const QString &category) +{ + d_ptr->manager->addDefaultToolBar(toolBar, category); +} + +/*! + Removes the specified \a toolBar from the manager's list. All the + actions that existed in the specified \a toolBar when it was + added are removed as well. + + \sa addToolBar() +*/ +void QtToolBarManager::removeToolBar(QToolBar *toolBar) +{ + d_ptr->manager->removeDefaultToolBar(toolBar); +} + +/*! + Returns the manager's toolbar list. +*/ +QList QtToolBarManager::toolBars() const +{ + return d_ptr->manager->toolBarsActions().keys(); +} + +/* +void QtToolBarManager::resetToolBar(QToolBar *toolBar) +{ + d_ptr->manager->resetToolBar(toolBar); +} + +void QtToolBarManager::resetAllToolBars() +{ + d_ptr->manager->resetAllToolBars(); +} +*/ + +/*! + Saves the state of the toolbar manager's toolbars. The \a version + number is stored as part of the data. + + Identifies all the QToolBar and QAction objects by their object + name property. Ensure that this property is unique for each + QToolBar and QAction that you add using the QtToolBarManager. + + Returns an identifier for the state which can be passed along with + the version number to the restoreState() function to restore the + saved state. + + \sa restoreState() +*/ +QByteArray QtToolBarManager::saveState(int version) const +{ + return d_ptr->manager->saveState(version); +} + +/*! + Restores the saved state of the toolbar manager's toolbars. The + \a version number is compared with the version number of the + stored \a state. + + Returns true if the version numbers are matching and the toolbar + manager's state is restored; otherwise the toolbar manager's state + is left unchanged and the function returns false. + + Note that the state of the toolbar manager's toolbars should be + restored before restoring the state of the main window's toolbars + and dockwidgets using the QMainWindow::restoreState() function. In + that way the restoreState() function can create the custom + toolbars before the QMainWindow::restoreState() function restores + the custom toolbars' positions. + + \sa saveState() +*/ +bool QtToolBarManager::restoreState(const QByteArray &state, int version) +{ + return d_ptr->manager->restoreState(state, version); +} + +////////////////////// + +class ToolBarItem { +public: + ToolBarItem() : tb(0) {} + ToolBarItem(QToolBar *toolBar) : tb(toolBar) {} + ToolBarItem(QToolBar *toolBar, const QString &toolBarName) + : tb(toolBar), tbName(toolBarName) {} + ToolBarItem(const QString &toolBarName) : tb(0), tbName(toolBarName) {} + QToolBar *toolBar() const + { return tb; } + void setToolBar(QToolBar *toolBar) + { tb = toolBar; } + QString toolBarName() const + { return tbName; } + void setToolBarName(const QString &toolBarName) + { tbName = toolBarName; } +private: + QToolBar *tb; + QString tbName; +}; + +class QtToolBarDialogPrivate { + QtToolBarDialog *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtToolBarDialog) +public: + ToolBarItem *createItem(QToolBar *toolBar); + ToolBarItem *createItem(const QString &toolBarName); + void deleteItem(ToolBarItem *item); + + void newClicked(); + void removeClicked(); + void defaultClicked(); + void okClicked(); + void applyClicked(); + void cancelClicked(); + void upClicked(); + void downClicked(); + void leftClicked(); + void rightClicked(); + void renameClicked(); + void toolBarRenamed(QListWidgetItem *item); + void currentActionChanged(QTreeWidgetItem *current); + void currentToolBarChanged(QListWidgetItem *current); + void currentToolBarActionChanged(QListWidgetItem *current); + + void removeToolBar(ToolBarItem *item); + bool isDefaultToolBar(ToolBarItem *item) const; + void setButtons(); + void clearOld(); + void fillNew(); + QtFullToolBarManager *toolBarManager = nullptr; + QHash> currentState; + QHash toolBarItems; + QSet createdItems; + QSet removedItems; + + QSet allToolBarItems; + + // static + QTreeWidgetItem *currentAction = nullptr; + QHash actionToItem; + QHash itemToAction; + + // dynamic + ToolBarItem *currentToolBar = nullptr; + QHash toolBarToItem; + QHash itemToToolBar; + + // dynamic + QHash actionToCurrentItem; + QHash currentItemToAction; + + QHash widgetActionToToolBar; + QHash> toolBarToWidgetActions; + + QString separatorText; + Ui::QtToolBarDialog ui; +}; + +ToolBarItem *QtToolBarDialogPrivate::createItem(QToolBar *toolBar) +{ + if (!toolBar) + return 0; + ToolBarItem *item = new ToolBarItem(toolBar, toolBar->windowTitle()); + allToolBarItems.insert(item); + return item; +} + +ToolBarItem *QtToolBarDialogPrivate::createItem(const QString &toolBarName) +{ + ToolBarItem *item = new ToolBarItem(toolBarName); + allToolBarItems.insert(item); + return item; +} + +void QtToolBarDialogPrivate::deleteItem(ToolBarItem *item) +{ + if (!allToolBarItems.contains(item)) + return; + allToolBarItems.remove(item); + delete item; +} + +void QtToolBarDialogPrivate::clearOld() +{ + ui.actionTree->clear(); + ui.toolBarList->clear(); + ui.currentToolBarList->clear(); + ui.removeButton->setEnabled(false); + ui.newButton->setEnabled(false); + ui.upButton->setEnabled(false); + ui.downButton->setEnabled(false); + ui.leftButton->setEnabled(false); + ui.rightButton->setEnabled(false); + + actionToItem.clear(); + itemToAction.clear(); + toolBarToItem.clear(); + itemToToolBar.clear(); + actionToCurrentItem.clear(); + currentItemToAction.clear(); + widgetActionToToolBar.clear(); + toolBarToWidgetActions.clear(); + + toolBarItems.clear(); + currentState.clear(); + createdItems.clear(); + removedItems.clear(); + qDeleteAll(allToolBarItems); + allToolBarItems.clear(); + + currentToolBar = nullptr; + currentAction = nullptr; +} + +void QtToolBarDialogPrivate::fillNew() +{ + if (!toolBarManager) + return; + + QTreeWidgetItem *item = new QTreeWidgetItem(ui.actionTree); + item->setText(0, separatorText); + ui.actionTree->setCurrentItem(item); + currentAction = item; + actionToItem.insert(0, item); + itemToAction.insert(item, 0); + const QStringList categories = toolBarManager->categories(); + for (const QString &category : categories) { + QTreeWidgetItem *categoryItem = new QTreeWidgetItem(ui.actionTree); + categoryItem->setText(0, category); + const auto actions = toolBarManager->categoryActions(category); + for (QAction *action : actions) { + item = new QTreeWidgetItem(categoryItem); + item->setText(0, action->text()); + item->setIcon(0, action->icon()); + item->setTextAlignment(0, Qt::Alignment(Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic)); + actionToItem.insert(action, item); + itemToAction.insert(item, action); + if (toolBarManager->isWidgetAction(action)) { + item->setData(0, Qt::ForegroundRole, QColor(Qt::blue)); + widgetActionToToolBar.insert(action, 0); + } + item->setFlags(item->flags() | Qt::ItemIsDragEnabled); + } + categoryItem->setExpanded(true); + } + //ui.actionTree->sortItems(0, Qt::AscendingOrder); + + const auto toolBars = toolBarManager->toolBarsActions(); + auto it = toolBars.constBegin(); + while (it != toolBars.constEnd()) { + QToolBar *toolBar = it.key(); + ToolBarItem *tbItem = createItem(toolBar); + toolBarItems.insert(toolBar, tbItem); + QListWidgetItem *item = new QListWidgetItem(toolBar->windowTitle(), + ui.toolBarList); + toolBarToItem.insert(tbItem, item); + itemToToolBar.insert(item, tbItem); + const auto actions = it.value(); + for (QAction *action : actions) { + if (toolBarManager->isWidgetAction(action)) { + widgetActionToToolBar.insert(action, tbItem); + toolBarToWidgetActions[tbItem].insert(action); + } + } + currentState.insert(tbItem, actions); + if (it == toolBars.constBegin()) + ui.toolBarList->setCurrentItem(item); + if (isDefaultToolBar(tbItem)) + item->setData(Qt::ForegroundRole, QColor(Qt::darkGreen)); + else + item->setFlags(item->flags() | Qt::ItemIsEditable); + + ++it; + } + ui.toolBarList->sortItems(); + setButtons(); +} + +bool QtToolBarDialogPrivate::isDefaultToolBar(ToolBarItem *item) const +{ + if (!item) + return false; + if (!item->toolBar()) + return false; + return toolBarManager->isDefaultToolBar(item->toolBar()); +} + +void QtToolBarDialogPrivate::setButtons() +{ + bool newEnabled = false; + bool removeEnabled = false; + bool renameEnabled = false; + bool upEnabled = false; + bool downEnabled = false; + bool leftEnabled = false; + bool rightEnabled = false; + + if (toolBarManager) { + newEnabled = true; + removeEnabled = !isDefaultToolBar(currentToolBar); + renameEnabled = removeEnabled; + QListWidgetItem *currentToolBarAction = ui.currentToolBarList->currentItem(); + if (currentToolBarAction) { + int row = ui.currentToolBarList->row(currentToolBarAction); + upEnabled = row > 0; + downEnabled = row < ui.currentToolBarList->count() - 1; + leftEnabled = true; + } + if (currentAction && currentToolBar) + rightEnabled = true; + } + ui.newButton->setEnabled(newEnabled); + ui.removeButton->setEnabled(removeEnabled); + ui.renameButton->setEnabled(renameEnabled); + ui.upButton->setEnabled(upEnabled); + ui.downButton->setEnabled(downEnabled); + ui.leftButton->setEnabled(leftEnabled); + ui.rightButton->setEnabled(rightEnabled); +} + +void QtToolBarDialogPrivate::newClicked() +{ + QString toolBarName = QtToolBarDialog::tr("Custom Toolbar"); // = QInputDialog::getString(); + // produce unique name + ToolBarItem *item = createItem(toolBarName); + currentState.insert(item, QList()); + createdItems.insert(item); + QListWidgetItem *i = new QListWidgetItem(toolBarName, ui.toolBarList); + i->setFlags(i->flags() | Qt::ItemIsEditable); + ui.toolBarList->setCurrentItem(i); + itemToToolBar.insert(i, item); + toolBarToItem.insert(item, i); + ui.toolBarList->sortItems(); + ui.toolBarList->setCurrentItem(i); + currentToolBarChanged(i); + renameClicked(); +} + +void QtToolBarDialogPrivate::removeToolBar(ToolBarItem *item) +{ + if (!item) + return; + if (item->toolBar() && toolBarManager->isDefaultToolBar(item->toolBar())) + return; + if (!toolBarToItem.contains(item)) + return; + QListWidgetItem *i = toolBarToItem.value(item); + bool wasCurrent = false; + if (i == ui.toolBarList->currentItem()) + wasCurrent = true; + int row = ui.toolBarList->row(i); + const auto itToolBar = toolBarToWidgetActions.find(item); + if (itToolBar != toolBarToWidgetActions.end()) { + for (QAction *action : std::as_const(itToolBar.value())) + widgetActionToToolBar.insert(action, 0); + toolBarToWidgetActions.erase(itToolBar); + } + + currentState.remove(item); + createdItems.remove(item); + toolBarToItem.remove(item); + itemToToolBar.remove(i); + delete i; + if (item->toolBar()) + removedItems.insert(item); + else + deleteItem(item); + if (wasCurrent) { + if (row == ui.toolBarList->count()) + row--; + if (row < 0) + ; + else + ui.toolBarList->setCurrentRow(row); + } + setButtons(); +} + +void QtToolBarDialogPrivate::removeClicked() +{ + QListWidgetItem *i = ui.toolBarList->currentItem(); + if (!i) + return; + ToolBarItem *item = itemToToolBar.value(i); + removeToolBar(item); +} + +void QtToolBarDialogPrivate::defaultClicked() +{ + const auto defaultToolBars = toolBarManager->defaultToolBars(); + auto itToolBar = defaultToolBars.constBegin(); + while (itToolBar != defaultToolBars.constEnd()) { + QToolBar *toolBar = itToolBar.key(); + ToolBarItem *toolBarItem = toolBarItems.value(toolBar); + + const auto tbwit = toolBarToWidgetActions.find(toolBarItem); + if (tbwit != toolBarToWidgetActions.end()) { + for (QAction *action : std::as_const(tbwit.value())) + widgetActionToToolBar.insert(action, 0); + toolBarToWidgetActions.erase(tbwit); + } + + currentState.remove(toolBarItem); + + for (QAction *action : itToolBar.value()) { + if (toolBarManager->isWidgetAction(action)) { + ToolBarItem *otherToolBar = widgetActionToToolBar.value(action); + if (otherToolBar) { + toolBarToWidgetActions[otherToolBar].remove(action); + currentState[otherToolBar].removeAll(action); + } + widgetActionToToolBar.insert(action, toolBarItem); + toolBarToWidgetActions[toolBarItem].insert(action); + } + } + currentState.insert(toolBarItem, itToolBar.value()); + + ++itToolBar; + } + currentToolBarChanged(toolBarToItem.value(currentToolBar)); + + const auto toolBars = currentState.keys(); + for (ToolBarItem *tb : toolBars) + removeToolBar(tb); +} + +void QtToolBarDialogPrivate::okClicked() +{ + applyClicked(); + q_ptr->accept(); +} + +void QtToolBarDialogPrivate::applyClicked() +{ + const auto toolBars = currentState; + auto itToolBar = toolBars.constBegin(); + while (itToolBar != toolBars.constEnd()) { + ToolBarItem *item = itToolBar.key(); + QToolBar *toolBar = item->toolBar(); + if (toolBar) { + toolBarManager->setToolBar(toolBar, itToolBar.value()); + toolBar->setWindowTitle(item->toolBarName()); + } + + ++itToolBar; + } + + const QSet toRemove = removedItems; + for (ToolBarItem *item : toRemove) { + QToolBar *toolBar = item->toolBar(); + removedItems.remove(item); + currentState.remove(item); + deleteItem(item); + if (toolBar) + toolBarManager->deleteToolBar(toolBar); + } + + const QSet toCreate = createdItems; + for (ToolBarItem *item : toCreate) { + QString toolBarName = item->toolBarName(); + createdItems.remove(item); + const auto actions = currentState.value(item); + QToolBar *toolBar = toolBarManager->createToolBar(toolBarName); + item->setToolBar(toolBar); + toolBarManager->setToolBar(toolBar, actions); + } +} + +void QtToolBarDialogPrivate::upClicked() +{ + QListWidgetItem *currentToolBarAction = ui.currentToolBarList->currentItem(); + if (!currentToolBarAction) + return; + int row = ui.currentToolBarList->row(currentToolBarAction); + if (row == 0) + return; + ui.currentToolBarList->takeItem(row); + int newRow = row - 1; + ui.currentToolBarList->insertItem(newRow, currentToolBarAction); + auto actions = currentState.value(currentToolBar); + QAction *action = actions.at(row); + actions.removeAt(row); + actions.insert(newRow, action); + currentState.insert(currentToolBar, actions); + ui.currentToolBarList->setCurrentItem(currentToolBarAction); + setButtons(); +} + +void QtToolBarDialogPrivate::downClicked() +{ + QListWidgetItem *currentToolBarAction = ui.currentToolBarList->currentItem(); + if (!currentToolBarAction) + return; + int row = ui.currentToolBarList->row(currentToolBarAction); + if (row == ui.currentToolBarList->count() - 1) + return; + ui.currentToolBarList->takeItem(row); + int newRow = row + 1; + ui.currentToolBarList->insertItem(newRow, currentToolBarAction); + auto actions = currentState.value(currentToolBar); + QAction *action = actions.at(row); + actions.removeAt(row); + actions.insert(newRow, action); + currentState.insert(currentToolBar, actions); + ui.currentToolBarList->setCurrentItem(currentToolBarAction); + setButtons(); +} + +void QtToolBarDialogPrivate::leftClicked() +{ + QListWidgetItem *currentToolBarAction = ui.currentToolBarList->currentItem(); + if (!currentToolBarAction) + return; + int row = ui.currentToolBarList->row(currentToolBarAction); + currentState[currentToolBar].removeAt(row); + QAction *action = currentItemToAction.value(currentToolBarAction); + if (widgetActionToToolBar.contains(action)) { + ToolBarItem *item = widgetActionToToolBar.value(action); + if (item == currentToolBar) { // have to be + toolBarToWidgetActions[item].remove(action); + if (toolBarToWidgetActions[item].isEmpty()) + toolBarToWidgetActions.remove(item); + } + widgetActionToToolBar.insert(action, 0); + } + if (action) + actionToCurrentItem.remove(action); + currentItemToAction.remove(currentToolBarAction); + delete currentToolBarAction; + if (row == ui.currentToolBarList->count()) + row--; + if (row >= 0) { + QListWidgetItem *item = ui.currentToolBarList->item(row); + ui.currentToolBarList->setCurrentItem(item); + } + setButtons(); +} + +void QtToolBarDialogPrivate::rightClicked() +{ + if (!currentAction) + return; + if (!currentToolBar) + return; + QListWidgetItem *currentToolBarAction = ui.currentToolBarList->currentItem(); + + QAction *action = itemToAction.value(currentAction); + QListWidgetItem *item = nullptr; + if (action) { + if (currentState[currentToolBar].contains(action)) { + item = actionToCurrentItem.value(action); + if (item == currentToolBarAction) + return; + int row = ui.currentToolBarList->row(item); + ui.currentToolBarList->takeItem(row); + currentState[currentToolBar].removeAt(row); + // only reorder here + } else { + item = new QListWidgetItem(action->text()); + item->setIcon(action->icon()); + item->setTextAlignment(Qt::Alignment(Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic)); + currentItemToAction.insert(item, action); + actionToCurrentItem.insert(action, item); + if (widgetActionToToolBar.contains(action)) { + item->setData(Qt::ForegroundRole, QColor(Qt::blue)); + ToolBarItem *toolBar = widgetActionToToolBar.value(action); + if (toolBar) { + currentState[toolBar].removeAll(action); + toolBarToWidgetActions[toolBar].remove(action); + if (toolBarToWidgetActions[toolBar].isEmpty()) + toolBarToWidgetActions.remove(toolBar); + } + widgetActionToToolBar.insert(action, currentToolBar); + toolBarToWidgetActions[currentToolBar].insert(action); + } + } + } else { + item = new QListWidgetItem(separatorText); + currentItemToAction.insert(item, 0); + } + + int row = ui.currentToolBarList->count(); + if (currentToolBarAction) { + row = ui.currentToolBarList->row(currentToolBarAction) + 1; + } + ui.currentToolBarList->insertItem(row, item); + currentState[currentToolBar].insert(row, action); + ui.currentToolBarList->setCurrentItem(item); + + setButtons(); +} + +void QtToolBarDialogPrivate::renameClicked() +{ + if (!currentToolBar) + return; + + QListWidgetItem *item = toolBarToItem.value(currentToolBar); + ui.toolBarList->editItem(item); +} + +void QtToolBarDialogPrivate::toolBarRenamed(QListWidgetItem *item) +{ + if (!currentToolBar) + return; + + ToolBarItem *tbItem = itemToToolBar.value(item); + if (!tbItem) + return; + tbItem->setToolBarName(item->text()); + //ui.toolBarList->sortItems(); +} + +void QtToolBarDialogPrivate::currentActionChanged(QTreeWidgetItem *current) +{ + if (itemToAction.contains(current)) + currentAction = current; + else + currentAction = NULL; + setButtons(); +} + +void QtToolBarDialogPrivate::currentToolBarChanged(QListWidgetItem *current) +{ + currentToolBar = itemToToolBar.value(current); + ui.currentToolBarList->clear(); + actionToCurrentItem.clear(); + currentItemToAction.clear(); + setButtons(); + if (!currentToolBar) { + return; + } + const auto actions = currentState.value(currentToolBar); + QListWidgetItem *first = nullptr; + for (QAction *action : actions) { + QString actionName = separatorText; + if (action) + actionName = action->text(); + QListWidgetItem *item = new QListWidgetItem(actionName, ui.currentToolBarList); + if (action) { + item->setIcon(action->icon()); + item->setTextAlignment(Qt::Alignment(Qt::AlignLeft | Qt::AlignVCenter | Qt::TextShowMnemonic)); + actionToCurrentItem.insert(action, item); + if (widgetActionToToolBar.contains(action)) + item->setData(Qt::ForegroundRole, QColor(Qt::blue)); + } + currentItemToAction.insert(item, action); + if (!first) + first = item; + } + if (first) + ui.currentToolBarList->setCurrentItem(first); +} + +void QtToolBarDialogPrivate::currentToolBarActionChanged(QListWidgetItem *) +{ + setButtons(); +} + +void QtToolBarDialogPrivate::cancelClicked() +{ + // just nothing + q_ptr->reject(); +} + +////////////////////// +/* +class FeedbackItemDelegate : public QItemDelegate +{ + Q_OBJECT +public: + FeedbackItemDelegate(QObject *parent = 0) : QItemDelegate(parent) { } + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex & index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; +}; + +void FeedbackItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if () + painter->save(); + QRect r = option.rect; + float yCentral = r.height() / 2.0; + float margin = 2.0; + float arrowWidth = 5.0; + float width = 20; + qDebug("rect: x %d, y %d, w %d, h %d", r.x(), r.y(), r.width(), r.height()); + QLineF lineBase(0.0 + margin, r.y() + yCentral, width - margin, r.y() + yCentral); + QLineF lineArrowLeft(width - margin - arrowWidth, r.y() + yCentral - arrowWidth, + width - margin, r.y() + yCentral); + QLineF lineArrowRight(width - margin - arrowWidth, r.y() + yCentral + arrowWidth, + width - margin, r.y() + yCentral); + painter->drawLine(lineBase); + painter->drawLine(lineArrowLeft); + painter->drawLine(lineArrowRight); + painter->translate(QPoint(width, 0)); + QItemDelegate::paint(painter, option, index); + painter->restore(); +} + +QSize FeedbackItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + //return QItemDelegate::sizeHint(option, index); + QSize s = QItemDelegate::sizeHint(option, index); + s.setWidth(s.width() - 20); + return s; +} + +class QtToolBarListWidget : public QListWidget +{ + Q_OBJECT +public: + QtToolBarListWidget(QWidget *parent) : QListWidget(parent), actionDrag(false) {} + +protected: + void startDrag(Qt::DropActions supportedActions); + + void dragEnterEvent(QDragEnterEvent *event); + void dragMoveEvent(QDragMoveEvent *event); + void dragLeaveEvent(QDragLeaveEvent *); + void dropEvent(QDropEvent *event); + + void setDragAction(const QString *action) { actionName = action; } +private: + QPersistentModelIndex lastDropIndicator; + QString actionName; + bool actionDrag; +}; + +void QtToolBarListWidget::startDrag(Qt::DropActions supportedActions) +{ + QListWidgetItem *item = currentItem(); + if (item) { + actionName = QString(); + emit aboutToDrag(item); + if (!actionName.isEmpty()) { + QDrag *drag = new QDrag(this); + QMimeData *data = new QMimeData; + data->setData("action", actionName.toLocal8Bit().constData()); + drag->setMimeData(data); + drag->exec(supportedActions); + } + } +} + +void QtToolBarListWidget::dragEnterEvent(QDragEnterEvent *event) +{ + const QMimeData *mime = event->mimeData(); + actionDrag = mime->hasFormat("action"); + if (actionDrag) + event->accept(); + else + event->ignore(); +} + +void QtToolBarListWidget::dragMoveEvent(QDragMoveEvent *event) +{ + event->ignore(); + if (actionDrag) { + QPoint p = event->pos(); + QListWidgetItem *item = itemAt(p); + Indicator indic = QtToolBarListWidget::None; + if (item) { + QRect rect = visualItemRect(item); + if (p.y() - rect.top() < rect.height() / 2) + indic = QtToolBarListWidget::Above; + else + indic = QtToolBarListWidget::Below; + } + setIndicator(item, indic); + event->accept(); + } +} + +void QtToolBarListWidget::dragLeaveEvent(QDragLeaveEvent *) +{ + if (actionDrag) { + actionDrag = false; + setIndicator(item, QtToolBarListWidget::None); + } +} + +void QtToolBarListWidget::dropEvent(QDropEvent *event) +{ + if (actionDrag) { + QListWidgetItem *item = indicatorItem(); + Indicator indic = indicator(); + QByteArray array = event->mimeData()->data("action"); + QDataStream stream(&array, QIODevice::ReadOnly); + QString action; + stream >> action; + emit actionDropped(action, item, ); + + actionDrag = false; + setIndicator(item, QtToolBarListWidget::None); + } +} +*/ + +/*! \class QtToolBarDialog + \internal + \inmodule QtDesigner + \since 4.4 + + \brief The QtToolBarDialog class provides a dialog for customizing + toolbars. + + QtToolBarDialog allows the user to customize the toolbars for a + given main window. + + \image qttoolbardialog.png + + The dialog lets the users add, rename and remove custom toolbars. + Note that built-in toolbars are marked with a green color, and + cannot be removed or renamed. + + The users can also add and remove actions from the toolbars. An + action can be added to many toolbars, but a toolbar can only + contain one instance of each action. Actions that contains a + widget are marked with a blue color in the list of actions, and + can only be added to one single toolbar. + + Finally, the users can add separators to the toolbars. + + The original toolbars can be restored by clicking the \gui + {Restore all} button. All custom toolbars will then be removed, + and all built-in toolbars will be restored to their original state. + + The QtToolBarDialog class's functionality is controlled by an + instance of the QtToolBarManager class, and the main window is + specified using the QtToolBarManager::setMainWindow() function. + + All you need to do to use QtToolBarDialog is to specify an + QtToolBarManager instance and call the QDialog::exec() slot: + + \snippet doc/src/snippets/code/tools_shared_qttoolbardialog_qttoolbardialog.cpp 0 + + \sa QtToolBarManager +*/ + +/*! + Creates a toolbar dialog with the given \a parent and the specified + window \a flags. +*/ +QtToolBarDialog::QtToolBarDialog(QWidget *parent, Qt::WindowFlags flags) + : QDialog(parent, flags), d_ptr(new QtToolBarDialogPrivate) +{ + d_ptr->q_ptr = this; + d_ptr->ui.setupUi(this); + d_ptr->separatorText = tr("< S E P A R A T O R >"); + + d_ptr->ui.actionTree->setColumnCount(1); + d_ptr->ui.actionTree->setRootIsDecorated(false); + d_ptr->ui.actionTree->header()->hide(); + + d_ptr->ui.upButton->setIcon(QIcon(":/qt-project.org/qttoolbardialog/images/up.png"_L1)); + d_ptr->ui.downButton->setIcon(QIcon(":/qt-project.org/qttoolbardialog/images/down.png"_L1)); + d_ptr->ui.leftButton->setIcon(QIcon(":/qt-project.org/qttoolbardialog/images/back.png"_L1)); + d_ptr->ui.rightButton->setIcon(QIcon(":/qt-project.org/qttoolbardialog/images/forward.png"_L1)); + d_ptr->ui.newButton->setIcon(QIcon(":/qt-project.org/qttoolbardialog/images/plus.png"_L1)); + d_ptr->ui.removeButton->setIcon(QIcon(":/qt-project.org/qttoolbardialog/images/minus.png"_L1)); + + connect(d_ptr->ui.newButton, &QAbstractButton::clicked, this, [this] { d_ptr->newClicked(); }); + connect(d_ptr->ui.removeButton, &QAbstractButton::clicked, this, [this] { d_ptr->removeClicked(); }); + connect(d_ptr->ui.renameButton, &QAbstractButton::clicked, this, [this] { d_ptr->renameClicked(); }); + connect(d_ptr->ui.upButton, &QAbstractButton::clicked, this, [this] { d_ptr->upClicked(); }); + connect(d_ptr->ui.downButton, &QAbstractButton::clicked, this, [this] { d_ptr->downClicked(); }); + connect(d_ptr->ui.leftButton, &QAbstractButton::clicked, this, [this] { d_ptr->leftClicked(); }); + connect(d_ptr->ui.rightButton, &QAbstractButton::clicked, this, [this] { d_ptr->rightClicked(); }); + + connect(d_ptr->ui.buttonBox->button(QDialogButtonBox::RestoreDefaults), + &QAbstractButton::clicked, this, [this] { d_ptr->defaultClicked(); }); + connect(d_ptr->ui.buttonBox->button(QDialogButtonBox::Ok), + &QAbstractButton::clicked, this, [this] { d_ptr->okClicked(); }); + connect(d_ptr->ui.buttonBox->button(QDialogButtonBox::Apply), + &QAbstractButton::clicked, this, [this] { d_ptr->applyClicked(); }); + connect(d_ptr->ui.buttonBox->button(QDialogButtonBox::Cancel), + &QAbstractButton::clicked, this, [this] { d_ptr->cancelClicked(); }); + + connect(d_ptr->ui.actionTree, &QTreeWidget::currentItemChanged, + this, [this](QTreeWidgetItem *current) { d_ptr->currentActionChanged(current); }); + connect(d_ptr->ui.currentToolBarList, &QListWidget::currentItemChanged, + this, [this](QListWidgetItem *current) { d_ptr->currentToolBarActionChanged(current); }); + connect(d_ptr->ui.toolBarList, &QListWidget::currentItemChanged, + this, [this](QListWidgetItem *current) { d_ptr->currentToolBarChanged(current); }); + + connect(d_ptr->ui.actionTree, &QTreeWidget::itemDoubleClicked, + this, [this] { d_ptr->rightClicked(); }); + connect(d_ptr->ui.currentToolBarList, &QListWidget::itemDoubleClicked, + this, [this] { d_ptr->leftClicked(); }); + connect(d_ptr->ui.toolBarList, &QListWidget::itemChanged, + this, [this](QListWidgetItem *current) { d_ptr->toolBarRenamed(current); }); +} + +/*! + Destroys the toolbar dialog. +*/ +QtToolBarDialog::~QtToolBarDialog() +{ + d_ptr->clearOld(); +} + +/*! + Connects the toolbar dialog to the given \a toolBarManager. Then, + when exec() is called, the toolbar dialog will operate using the + given \a toolBarManager. +*/ +void QtToolBarDialog::setToolBarManager(QtToolBarManager *toolBarManager) +{ + if (d_ptr->toolBarManager == toolBarManager->d_ptr->manager) + return; + if (isVisible()) + d_ptr->clearOld(); + d_ptr->toolBarManager = toolBarManager->d_ptr->manager; + if (isVisible()) + d_ptr->fillNew(); +} + +/*! + \reimp +*/ +void QtToolBarDialog::showEvent(QShowEvent *event) +{ + if (!event->spontaneous()) + d_ptr->fillNew(); +} + +/*! + \reimp +*/ +void QtToolBarDialog::hideEvent(QHideEvent *event) +{ + if (!event->spontaneous()) + d_ptr->clearOld(); +} + +QT_END_NAMESPACE + +#include "moc_qttoolbardialog_p.cpp" +#include "qttoolbardialog.moc" diff --git a/src/shared/qttoolbardialog/qttoolbardialog.ui b/src/shared/qttoolbardialog/qttoolbardialog.ui new file mode 100644 index 00000000000..c4ad934f80e --- /dev/null +++ b/src/shared/qttoolbardialog/qttoolbardialog.ui @@ -0,0 +1,207 @@ + + QtToolBarDialog + + + + 0 + 0 + 583 + 508 + + + + Customize Toolbars + + + + 8 + + + 6 + + + + + + 1 + + + + + + + + Actions + + + + + + + 6 + + + 0 + + + + + Toolbars + + + + + + + Add new toolbar + + + New + + + + + + + Remove selected toolbar + + + Remove + + + + + + + Rename toolbar + + + Rename + + + + + + + + + 6 + + + 0 + + + + + + 0 + 0 + + + + Move action up + + + Up + + + + + + + + 0 + 0 + + + + Remove action from toolbar + + + <- + + + + + + + + 0 + 0 + + + + Add action to toolbar + + + -> + + + + + + + + 0 + 0 + + + + Move action down + + + Down + + + + + + + Qt::Vertical + + + + 29 + 16 + + + + + + + + + + + + + Current Toolbar Actions + + + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults + + + + + + + newButton + removeButton + renameButton + toolBarList + upButton + leftButton + rightButton + downButton + currentToolBarList + + + + diff --git a/src/shared/qttoolbardialog/qttoolbardialog_p.h b/src/shared/qttoolbardialog/qttoolbardialog_p.h new file mode 100644 index 00000000000..3a283a0ec52 --- /dev/null +++ b/src/shared/qttoolbardialog/qttoolbardialog_p.h @@ -0,0 +1,84 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTTOOLBARDIALOG_H +#define QTTOOLBARDIALOG_H + +#include + +QT_BEGIN_NAMESPACE + +class QMainWindow; +class QAction; +class QToolBar; + +class QtToolBarManagerPrivate; + +class QtToolBarManager : public QObject +{ + Q_OBJECT +public: + + explicit QtToolBarManager(QObject *parent = 0); + ~QtToolBarManager(); + + void setMainWindow(QMainWindow *mainWindow); + QMainWindow *mainWindow() const; + + void addAction(QAction *action, const QString &category); + void removeAction(QAction *action); + + void addToolBar(QToolBar *toolBar, const QString &category); + void removeToolBar(QToolBar *toolBar); + + QList toolBars() const; + + QByteArray saveState(int version = 0) const; + bool restoreState(const QByteArray &state, int version = 0); + +private: + + friend class QtToolBarDialog; + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtToolBarManager) + Q_DISABLE_COPY_MOVE(QtToolBarManager) +}; + +class QtToolBarDialogPrivate; + +class QtToolBarDialog : public QDialog +{ + Q_OBJECT +public: + + explicit QtToolBarDialog(QWidget *parent = 0, Qt::WindowFlags flags = {}); + ~QtToolBarDialog(); + + void setToolBarManager(QtToolBarManager *toolBarManager); + +protected: + + void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; + +private: + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtToolBarDialog) + Q_DISABLE_COPY_MOVE(QtToolBarDialog) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/CMakeLists.txt b/src/tools/CMakeLists.txt index 4656f5bccba..2a656c6bc22 100644 --- a/src/tools/CMakeLists.txt +++ b/src/tools/CMakeLists.txt @@ -32,3 +32,7 @@ endif() if(QT_FEATURE_windeployqt) add_subdirectory(windeployqt) endif() + +if(QT_FEATURE_designer) + add_subdirectory(designer) +endif() diff --git a/src/tools/configure.cmake b/src/tools/configure.cmake index f813b727ba3..3af48e45e5a 100644 --- a/src/tools/configure.cmake +++ b/src/tools/configure.cmake @@ -27,9 +27,16 @@ qt_feature("qmake" PRIVATE (QT_FEATURE_alloca_malloc_h OR NOT WIN32) AND QT_FEATURE_cborstreamwriter AND QT_FEATURE_datestring AND QT_FEATURE_regularexpression AND QT_FEATURE_temporaryfile) +qt_feature("designer" PRIVATE + LABEL "Qt Widgets Designer" + PURPOSE "Qt Widgets Designer is the Qt tool for designing and building graphical user interfaces (GUIs) with Qt Widgets. You can compose and customize your windows or dialogs in a what-you-see-is-what-you-get (WYSIWYG) manner, and test them using different styles and resolutions." + CONDITION TARGET Qt::Widgets AND TARGET Qt::Network AND QT_FEATURE_png AND QT_FEATURE_pushbutton AND QT_FEATURE_toolbutton +) + qt_configure_add_summary_section(NAME "Core tools") qt_configure_add_summary_entry(ARGS "androiddeployqt") qt_configure_add_summary_entry(ARGS "macdeployqt") qt_configure_add_summary_entry(ARGS "windeployqt") qt_configure_add_summary_entry(ARGS "qmake") +qt_configure_add_summary_entry(ARGS "designer") qt_configure_end_summary_section() diff --git a/src/tools/designer/CMakeLists.txt b/src/tools/designer/CMakeLists.txt new file mode 100644 index 00000000000..b4deaf95670 --- /dev/null +++ b/src/tools/designer/CMakeLists.txt @@ -0,0 +1,7 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +if(NOT QT_FEATURE_designer) + return() +endif() +add_subdirectory(src) diff --git a/src/tools/designer/data/README b/src/tools/designer/data/README new file mode 100644 index 00000000000..5b05ffa2683 --- /dev/null +++ b/src/tools/designer/data/README @@ -0,0 +1,8 @@ +You may generate ui4.h and ui4.cpp parser files by using the generate_ui.py script +or manually by invoking the xsltproc command: + +xsltproc generate_header.xsl ui4.xsd > ui4.h +xsltproc generate_impl.xsl ui4.xsd > ui4.cpp + +Remember to update uic sources in qtbase module accordingly, +adapting the license. diff --git a/src/tools/designer/data/generate_header.xsl b/src/tools/designer/data/generate_header.xsl new file mode 100644 index 00000000000..c2b12dfa8e1 --- /dev/null +++ b/src/tools/designer/data/generate_header.xsl @@ -0,0 +1,482 @@ + +]> + + + + + + + + + + + + + class + + ;&endl; + + + + + + + + + + + + // child element accessors&endl; + + + + + + + + + + + + + + + + + + + enum Kind { Unknown = 0 + + + + + + + + + + + + + , + + + };&endl; + inline Kind kind() const { return m_kind; }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + inline + + element + + () const { return m_ + + ; }&endl; + + + + + takeElement + + ();&endl; + + + void setElement + + ( + + a);&endl; + + + inline bool hasElement + + () const { return m_children & + + ; }&endl; + void clearElement + + ();&endl; + + &endl; + + + + + + + + + + + + + + &endl; // child element data&endl; + Kind m_kind = Unknown;&endl; + + + &endl; // child element data&endl; + uint m_children = 0;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + m_ + + + + + + + = 0 + + + = 0.0 + + + = false + + + + + = nullptr + + + + + ;&endl; + + + + &endl; enum Child {&endl; + + + + + + + + + + + + = + + + + + , + + &endl; + + + };&endl; + + + + + + + + + + + + + // attribute accessors&endl; + + + + + + + + + + + + + + + + + + + + + + + + + inline bool hasAttribute + + () const { return m_has_attr_ + + ; }&endl; + + inline + + attribute + + () const { return m_attr_ + + ; }&endl; + + inline void setAttribute + + ( + + a) { m_attr_ + + = a; m_has_attr_ + + = true; }&endl; + + inline void clearAttribute + + () { m_has_attr_ + + = false; }&endl;&endl; + + + + + + + + + + + class QDESIGNER_UILIB_EXPORT + + {&endl; Q_DISABLE_COPY_MOVE( + + )&endl; + public:&endl; + + + () = default;&endl; + ~ + + ();&endl;&endl; + + void read(QXmlStreamReader &reader);&endl; + void write(QXmlStreamWriter &writer, const QString &tagName = QString()) const;&endl;&endl; + + + inline QString text() const { return m_text; }&endl; + inline void setText(const QString &s) { m_text = s; }&endl;&endl; + + + + + + + + + + + private:&endl; + + + QString m_text;&endl;&endl; + + + + void clear();&endl;&endl; + + + + + + + // attribute data&endl; + + + + + + + + + + + + + + + m_attr_ + + + + + = 0 + + + = 0.0 + + + = false + + + + ;&endl; + bool m_has_attr_ + + = false;&endl; + + &endl; + + + + + + + + };&endl;&endl; + + + + + + +@LICENSE@ +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Widgets Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT! + +#ifndef UI4_H +#define UI4_H + +#include <qlist.h> +#include <qstring.h> +#include <qstringlist.h> +#include <qxmlstream.h> +#include <qglobal.h> + +QT_BEGIN_NAMESPACE + +#define QDESIGNER_UILIB_EXTERN Q_DECL_EXPORT +#define QDESIGNER_UILIB_IMPORT Q_DECL_IMPORT + +#if defined(QT_DESIGNER_STATIC) || defined(QT_UIC) || defined(QT_UIC3) +# define QDESIGNER_UILIB_EXPORT +#elif defined(QDESIGNER_UILIB_LIBRARY) +# define QDESIGNER_UILIB_EXPORT QDESIGNER_UILIB_EXTERN +#else +# define QDESIGNER_UILIB_EXPORT QDESIGNER_UILIB_IMPORT +#endif + +#ifndef QDESIGNER_UILIB_EXPORT +# define QDESIGNER_UILIB_EXPORT +#endif + +#ifdef QFORMINTERNAL_NAMESPACE +namespace QFormInternal +{ +#endif + + + + &endl; + /*******************************************************************************&endl; + ** Forward declarations&endl; + */&endl;&endl; + + + + + + + + &endl; + /*******************************************************************************&endl; + ** Declarations&endl; + */&endl;&endl; + + + + + + + +#ifdef QFORMINTERNAL_NAMESPACE +} +#endif + +QT_END_NAMESPACE + +#endif // UI4_H + + + diff --git a/src/tools/designer/data/generate_impl.xsl b/src/tools/designer/data/generate_impl.xsl new file mode 100644 index 00000000000..7ce02f41bca --- /dev/null +++ b/src/tools/designer/data/generate_impl.xsl @@ -0,0 +1,845 @@ + +]> + + + + + + + + + + + + + + + + + + + + + m_ + + = 0;&endl; + + + m_ + + = 0.0;&endl; + + + m_ + + = false;&endl; + + + + + m_ + + = nullptr;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + qDeleteAll(m_ + + );&endl; + + m_ + + .clear();&endl; + + + + delete m_ + + ;&endl; + + + + + + + + + + + + ::~ + + () + + + + + + + + + + + + &endl;{&endl; + + }&endl;&endl; + + + = default;&endl;&endl; + + + + + + + + + + + + void + ::clear()&endl; + {&endl; + + + + + + + + + + + &endl; + + + + m_kind = Unknown;&endl;&endl; + + + + + m_children = 0;&endl; + + + + + + + + + + }&endl;&endl; + + + + + + + + u" + + "_s + + + + + + + + + const QXmlStreamAttributes &attributes = reader.attributes();&endl; + for (const QXmlStreamAttribute &attribute : attributes) {&endl; + const auto name = attribute.name();&endl; + + + + + + + + + + + + + + + + + attribute.value() + + + + + if (name == + + + + ) {&endl; + setAttribute + + ( + + );&endl; + continue;&endl; + }&endl; + + + reader.raiseError("Unexpected attribute "_L1 + name);&endl; + }&endl; + &endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if (!tag.compare( + + + + , Qt::CaseInsensitive)) {&endl; + + + + qWarning("Omitting deprecated element < + + >.");&endl; + reader.skipCurrentElement();&endl; + + + + + + + + + + setElement + + ( + + );&endl; + + + + + + + + + + m_ + + .append( + + );&endl; + + + auto + *v = new Dom + + ();&endl; + v->read(reader);&endl; + setElement + + (v);&endl; + + + auto + *v = new Dom + + ();&endl; + v->read(reader);&endl; + m_ + + .append(v);&endl; + + + continue;&endl; + }&endl; + + + + + + + + void + + ::read(QXmlStreamReader &reader)&endl; + + {&endl; + + + + + + while (!reader.hasError()) {&endl; + switch (reader.readNext()) {&endl; + case QXmlStreamReader::StartElement : {&endl; + const auto tag = reader.name();&endl; + + + + + + + + reader.raiseError("Unexpected element "_L1 + tag);&endl; + }&endl; + break;&endl; + case QXmlStreamReader::EndElement :&endl; + return;&endl; + + + + case QXmlStreamReader::Characters :&endl; + if (!reader.isWhitespace())&endl; + m_text.append(reader.text().toString());&endl; + break;&endl; + + + default :&endl; + break;&endl; + }&endl; + }&endl; + }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + if (hasAttribute + + ())&endl; + writer.writeAttribute( + + + + + , + + + + + + + );&endl;&endl; + + + + + + + + switch (kind()) {&endl; + + + + + + + + + + + + + + + + + + + + + + + + case + + :&endl; + + + + + + + + + + writer.writeTextElement( + + + + , + + );&endl; + + + + + + + + + if (m_ + + != nullptr)&endl; + m_ + + ->write(writer, + + + + );&endl; + + + break;&endl; + &endl; + + + default:&endl; + break;&endl; + }&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + for ( + + v : m_ + + )&endl; + + + v->write(writer, + + + + );&endl; + + + + + + + + + + writer.writeTextElement( + + + + , + + );&endl; + + + &endl; + + + if (m_children & + + )&endl; + + + m_ + + ->write(writer, + + + + );&endl; + + + + + + + + + writer.writeTextElement( + + + + , + + );&endl; + + + &endl; + + + + + + + + + + + + + + + void + + ::write(QXmlStreamWriter &writer, const QString &tagName) const&endl; + {&endl; + + writer.writeStartElement(tagName.isEmpty() ? QStringLiteral(" + + ") : tagName.toLower());&endl;&endl; + + + + + + + + + + + + + + + + + + + + if (!m_text.isEmpty())&endl; + writer.writeCharacters(m_text);&endl;&endl; + + + writer.writeEndElement();&endl; + }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ::takeElement + + ()&endl;{&endl; + + + a = m_ + + ;&endl; + m_ + + = nullptr;&endl; + + m_children ^= + + ;&endl; + + return a;&endl; + }&endl;&endl; + + + void + + ::setElement + + ( + + a)&endl; + {&endl; + + + clear();&endl; + m_kind = + + ;&endl; + + + delete + m_ + + ;&endl; + + + + m_children |= + + ;&endl; + + m_ + + = a;&endl; + }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + void + + ::clearElement + + ()&endl; + {&endl; + + delete m_ + + ;&endl; + m_ + + = nullptr;&endl; + + m_children &= ~ + + ;&endl; + }&endl;&endl; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +@LICENSE@ +// THIS FILE IS AUTOMATICALLY GENERATED. DO NOT EDIT! + + + #include "@HEADER@"&endl; + &endl; + &endl; + QT_BEGIN_NAMESPACE&endl; + &endl;using namespace Qt::StringLiterals;&endl;&endl; + + #ifdef QFORMINTERNAL_NAMESPACE&endl; + using namespace QFormInternal;&endl; + #endif&endl; + &endl; + + /*******************************************************************************&endl; + ** Implementations&endl; + */&endl;&endl; + + + + + + + QT_END_NAMESPACE&endl; + + &endl; + + + diff --git a/src/tools/designer/data/generate_shared.xsl b/src/tools/designer/data/generate_shared.xsl new file mode 100644 index 00000000000..d1ca2b94bb5 --- /dev/null +++ b/src/tools/designer/data/generate_shared.xsl @@ -0,0 +1,349 @@ + +]> + + + + + + + exportMacro + layoutDefault + layoutFunction + pixmapFunction + customWidgets + tabStops + tabStop + buttonGroups + exportMacro + actionGroup + buttonGroup + customWidget + sizeHint + addPageMethod + sizePolicy + horData + verData + rowSpan + colSpan + addAction + zOrder + startX + startY + endX + endY + centralX + centralY + focalX + focalY + widgetData + coordinateMode + brushStyle + colorRole + pointSize + strikeOut + styleStrategy + hintingPreference + fontWeight + hSizeType + vSizeType + horStretch + verStretch + normalOff + normalOn + disabledOff + disabledOn + activeOff + activeOn + selectedOff + selectedOn + cursorShape + iconSet + stringList + dateTime + pointF + rectF + sizeF + longLong + UInt + uLongLong + rowStretch + columnStretch + rowMinimumHeight + columnMinimumWidth + extraComment + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + + + + + + + + + + + + + + + + + .toInt() + + + + .toFloat() + + + + .toDouble() + + + + == u"true"_s + + + + .toLongLong() + + + + .toUInt() + + + + .toULongLong() + + ### BZZZZT! ### + + + + + + + + + + .toString() + + + + + + + + + + + + + + + + + + + QString::number( + + ) + + + QString::number( + + ) + + + QString::number( + + ) + + + QString::number( + + ) + + + QString::number( + + , 'f', 8) + + + QString::number( + + , 'f', 15) + + + ( + + ? u"true"_s : u"false"_s) + + ### BZZZZT! ### + + + + + + + + value + + + value + value + value + value + value + value + value + value + pointer + + + + + + + + + + + + QStringList + QList<int> + QList<float> + QList<double> + QList<bool> + QList<qlonglong> + QList<uint> + QList<qulonglong> + QList<Dom *> + + + + + QString + int + float + double + bool + qlonglong + uint + qulonglong + Dom + + + + + + + + + + + + QStringList + QList<int> + QList<float> + QList<double> + QList<bool> + QList<qlonglong> + QList<uint> + QList<qulonglong> + QList<Dom *> + + + + + QString + int + float + double + bool + qlonglong + uint + qulonglong + Dom * + + + + + + + + + + + + const QStringList & + const QList<int> & + const QList<float> & + const QList<double> & + const QList<bool> & + const QList<qlonglong> & + const QList<uint> & + const QList<qulonglong> & + const QList<Dom *> & + + + + + const QString & + int + float + double + bool + qlonglong + uint + qulonglong + Dom * + + + + + + + diff --git a/src/tools/designer/data/generate_ui.py b/src/tools/designer/data/generate_ui.py new file mode 100644 index 00000000000..05472c7f5ba --- /dev/null +++ b/src/tools/designer/data/generate_ui.py @@ -0,0 +1,96 @@ +#!/usr/bin/python3 + +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +import os +import subprocess + +from argparse import ArgumentParser, RawTextHelpFormatter +from pathlib import Path +from tempfile import NamedTemporaryFile + + +DESCRIPTION = """ +Usage: generate_ui.py + +Generates the source files ui4.cpp, ui4.h used in the uic tool, the QtUiTools library and +Qt Widgets Designer from the XML schema used for .ui files. + +Requires xalan. +""" + + +opt_delete_temp_files = True + + +def read_cpp_license(path): + """Read out the license from a C++ source""" + result = "" + for line in path.read_text().splitlines(): + result += line + "\n" + if 'SPDX-License-Identifier' in line: + break + return result + + +def replace_xsl_keys(xsl_source_file, license, ui_header_name=None): + """Replace special keys in XSL files and return a handle to temporary file""" + xsl = xsl_source_file.read_text() + xsl = xsl.replace("@LICENSE@", license) + if ui_header_name: + xsl = xsl.replace("@HEADER@", ui_header_name) + + result = NamedTemporaryFile(mode='w', suffix='.xsl', + dir=Path.cwd(), + delete=opt_delete_temp_files) + result.write(xsl) + return result + + +def run_xslt(source, sheet, target): + """Run xalan.""" + cmd = ['xalan', '-in', os.fspath(source), '-xsl', os.fspath(sheet), + '-out', os.fspath(target)] + subprocess.check_call(cmd) + + +if __name__ == '__main__': + argument_parser = ArgumentParser(description=DESCRIPTION, + formatter_class=RawTextHelpFormatter) + argument_parser.add_argument('--keep', '-k', action='store_true', + help='Keep temporary files') + options = argument_parser.parse_args() + opt_delete_temp_files = not options.keep + + # Generate uilib header and source. + xml_dir = Path(__file__).parent.resolve() + ui4_xsd = xml_dir / 'ui4.xsd' + + designer_dir = xml_dir.parent + uilib_dir = designer_dir / "src" / "lib" / "uilib" + uilib_impl = uilib_dir / 'ui4.cpp' + license = read_cpp_license(uilib_impl) + + print("Running XSLT processor for uilib header...\n") + header_xsl_source = xml_dir / 'generate_header.xsl' + header_xsl = replace_xsl_keys(header_xsl_source, license) + run_xslt(ui4_xsd, header_xsl.name, uilib_dir / 'ui4_p.h') + + print("Running XSLT processor for uilib source...\n") + impl_xsl_source = xml_dir / 'generate_impl.xsl' + impl_xsl = replace_xsl_keys(impl_xsl_source, license, 'ui4_p.h') + run_xslt(ui4_xsd, impl_xsl.name, uilib_impl) + + # uic: Header is called 'ui4.h' instead of 'ui4_p.h' + uic_dir = designer_dir.parents[2] / "qtbase" / "src" / "tools" / "uic" + uic_impl = uic_dir / 'ui4.cpp' + license = read_cpp_license(uic_impl) + print("Running XSLT processor for uic header...\n") + header_xsl = replace_xsl_keys(header_xsl_source, license) + run_xslt(ui4_xsd, header_xsl.name, uic_dir / 'ui4.h') + print("Running XSLT processor for uic source...\n") + impl_xsl = replace_xsl_keys(impl_xsl_source, license, 'ui4.h') + run_xslt(ui4_xsd, impl_xsl.name, uic_impl) + + subprocess.call(['git', 'diff']) diff --git a/src/tools/designer/data/ui3.xsd b/src/tools/designer/data/ui3.xsd new file mode 100644 index 00000000000..5bf417dca07 --- /dev/null +++ b/src/tools/designer/data/ui3.xsd @@ -0,0 +1,353 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tools/designer/data/ui4.xsd b/src/tools/designer/data/ui4.xsd new file mode 100644 index 00000000000..0063f473b8f --- /dev/null +++ b/src/tools/designer/data/ui4.xsd @@ -0,0 +1,557 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tools/designer/src/CMakeLists.txt b/src/tools/designer/src/CMakeLists.txt new file mode 100644 index 00000000000..00d2a48e213 --- /dev/null +++ b/src/tools/designer/src/CMakeLists.txt @@ -0,0 +1,18 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +qt_exclude_tool_directories_from_default_target( + lib + components + designer + plugins +) + +if(QT_FEATURE_process) + add_subdirectory(lib) + add_subdirectory(components) + add_subdirectory(designer) +endif() +if(QT_BUILD_SHARED_LIBS AND QT_FEATURE_process) + add_subdirectory(plugins) +endif() diff --git a/src/tools/designer/src/components/CMakeLists.txt b/src/tools/designer/src/components/CMakeLists.txt new file mode 100644 index 00000000000..ec6a9a8fdbe --- /dev/null +++ b/src/tools/designer/src/components/CMakeLists.txt @@ -0,0 +1,4 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +add_subdirectory(lib) diff --git a/src/tools/designer/src/components/buddyeditor/buddyeditor.cpp b/src/tools/designer/src/components/buddyeditor/buddyeditor.cpp new file mode 100644 index 00000000000..452d337719e --- /dev/null +++ b/src/tools/designer/src/components/buddyeditor/buddyeditor.cpp @@ -0,0 +1,398 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "buddyeditor.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto buddyPropertyC = "buddy"_L1; + +static bool canBeBuddy(QWidget *w, QDesignerFormWindowInterface *form) +{ + if (qobject_cast(w) || qobject_cast(w)) + return false; + if (w == form->mainContainer() || w->isHidden() ) + return false; + + QExtensionManager *ext = form->core()->extensionManager(); + if (QDesignerPropertySheetExtension *sheet = qt_extension(ext, w)) { + const int index = sheet->indexOf(u"focusPolicy"_s); + if (index != -1) { + bool ok = false; + const Qt::FocusPolicy q = static_cast(qdesigner_internal::Utils::valueOf(sheet->property(index), &ok)); + // Refuse No-focus unless the widget is promoted. + return (ok && q != Qt::NoFocus) || qdesigner_internal::isPromoted(form->core(), w); + } + } + return false; +} + +static QString buddy(QLabel *label, QDesignerFormEditorInterface *core) +{ + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), label); + if (sheet == nullptr) + return QString(); + const int prop_idx = sheet->indexOf(buddyPropertyC); + if (prop_idx == -1) + return QString(); + return sheet->property(prop_idx).toString(); +} + +namespace qdesigner_internal { + +/******************************************************************************* +** BuddyEditor +*/ + +BuddyEditor::BuddyEditor(QDesignerFormWindowInterface *form, QWidget *parent) : + ConnectionEdit(parent, form), + m_formWindow(form), + m_updating(false) +{ +} + + +QWidget *BuddyEditor::widgetAt(const QPoint &pos) const +{ + QWidget *w = ConnectionEdit::widgetAt(pos); + + while (w != nullptr && !m_formWindow->isManaged(w)) + w = w->parentWidget(); + if (!w) + return w; + + if (state() == Editing) { + QLabel *label = qobject_cast(w); + if (label == nullptr) + return nullptr; + const int cnt = connectionCount(); + for (int i = 0; i < cnt; ++i) { + Connection *con = connection(i); + if (con->widget(EndPoint::Source) == w) + return nullptr; + } + } else { + if (!canBeBuddy(w, m_formWindow)) + return nullptr; + } + + return w; +} + +Connection *BuddyEditor::createConnection(QWidget *source, QWidget *destination) +{ + return new Connection(this, source, destination); +} + +QDesignerFormWindowInterface *BuddyEditor::formWindow() const +{ + return m_formWindow; +} + +void BuddyEditor::updateBackground() +{ + if (m_updating || background() == nullptr) + return; + ConnectionEdit::updateBackground(); + + m_updating = true; + QList newList; + const auto label_list = background()->findChildren(); + for (QLabel *label : label_list) { + const QString buddy_name = buddy(label, m_formWindow->core()); + if (buddy_name.isEmpty()) + continue; + + const QWidgetList targets = background()->findChildren(buddy_name); + if (targets.isEmpty()) + continue; + + const auto wit = std::find_if(targets.cbegin(), targets.cend(), + [] (const QWidget *w) { return !w->isHidden(); }); + if (wit == targets.cend()) + continue; + + Connection *con = new Connection(this); + con->setEndPoint(EndPoint::Source, label, widgetRect(label).center()); + con->setEndPoint(EndPoint::Target, *wit, widgetRect(*wit).center()); + newList.append(con); + } + + QList toRemove; + + const int c = connectionCount(); + for (int i = 0; i < c; i++) { + Connection *con = connection(i); + QObject *source = con->object(EndPoint::Source); + QObject *target = con->object(EndPoint::Target); + const bool found = + std::any_of(newList.cbegin(), newList.cend(), + [source, target] (const Connection *nc) + { return nc->object(EndPoint::Source) == source && nc->object(EndPoint::Target) == target; }); + if (!found) + toRemove.append(con); + } + if (!toRemove.isEmpty()) { + DeleteConnectionsCommand command(this, toRemove); + command.redo(); + for (Connection *con : std::as_const(toRemove)) + delete takeConnection(con); + } + + for (Connection *newConn : std::as_const(newList)) { + bool found = false; + const int c = connectionCount(); + for (int i = 0; i < c; i++) { + Connection *con = connection(i); + if (con->object(EndPoint::Source) == newConn->object(EndPoint::Source) && + con->object(EndPoint::Target) == newConn->object(EndPoint::Target)) { + found = true; + break; + } + } + if (found) { + delete newConn; + } else { + AddConnectionCommand command(this, newConn); + command.redo(); + } + } + m_updating = false; +} + +void BuddyEditor::setBackground(QWidget *background) +{ + clear(); + ConnectionEdit::setBackground(background); + if (background == nullptr) + return; + + const auto label_list = background->findChildren(); + for (QLabel *label : label_list) { + const QString buddy_name = buddy(label, m_formWindow->core()); + if (buddy_name.isEmpty()) + continue; + QWidget *target = background->findChild(buddy_name); + if (target == nullptr) + continue; + + Connection *con = new Connection(this); + con->setEndPoint(EndPoint::Source, label, widgetRect(label).center()); + con->setEndPoint(EndPoint::Target, target, widgetRect(target).center()); + addConnection(con); + } +} + +static QUndoCommand *createBuddyCommand(QDesignerFormWindowInterface *fw, QLabel *label, QWidget *buddy) +{ + SetPropertyCommand *command = new SetPropertyCommand(fw); + command->init(label, buddyPropertyC, buddy->objectName()); + command->setText(BuddyEditor::tr("Add buddy")); + return command; +} + +void BuddyEditor::endConnection(QWidget *target, const QPoint &pos) +{ + Connection *tmp_con = newlyAddedConnection(); + Q_ASSERT(tmp_con != nullptr); + + tmp_con->setEndPoint(EndPoint::Target, target, pos); + + QWidget *source = tmp_con->widget(EndPoint::Source); + Q_ASSERT(source != nullptr); + Q_ASSERT(target != nullptr); + setEnabled(false); + Connection *new_con = createConnection(source, target); + setEnabled(true); + if (new_con != nullptr) { + new_con->setEndPoint(EndPoint::Source, source, tmp_con->endPointPos(EndPoint::Source)); + new_con->setEndPoint(EndPoint::Target, target, tmp_con->endPointPos(EndPoint::Target)); + + selectNone(); + addConnection(new_con); + QLabel *source = qobject_cast(new_con->widget(EndPoint::Source)); + QWidget *target = new_con->widget(EndPoint::Target); + if (source) { + undoStack()->push(createBuddyCommand(m_formWindow, source, target)); + } else { + qDebug("BuddyEditor::endConnection(): not a label"); + } + setSelected(new_con, true); + } + + clearNewlyAddedConnection(); + findObjectsUnderMouse(mapFromGlobal(QCursor::pos())); +} + +void BuddyEditor::widgetRemoved(QWidget *widget) +{ + QWidgetList child_list = widget->findChildren(); + child_list.prepend(widget); + + ConnectionSet remove_set; + for (QWidget *w : std::as_const(child_list)) { + const ConnectionList &cl = connectionList(); + for (Connection *con : cl) { + if (con->widget(EndPoint::Source) == w || con->widget(EndPoint::Target) == w) + remove_set.insert(con, con); + } + } + + if (!remove_set.isEmpty()) { + undoStack()->beginMacro(tr("Remove buddies")); + for (Connection *con : std::as_const(remove_set)) { + setSelected(con, false); + con->update(); + QWidget *source = con->widget(EndPoint::Source); + if (qobject_cast(source) == 0) { + qDebug("BuddyConnection::widgetRemoved(): not a label"); + } else { + ResetPropertyCommand *command = new ResetPropertyCommand(formWindow()); + command->init(source, buddyPropertyC); + undoStack()->push(command); + } + delete takeConnection(con); + } + undoStack()->endMacro(); + } +} + +void BuddyEditor::deleteSelected() +{ + const ConnectionSet selectedConnections = selection(); // want copy for unselect + if (selectedConnections.isEmpty()) + return; + + undoStack()->beginMacro(tr("Remove %n buddies", nullptr, selectedConnections.size())); + for (Connection *con : selectedConnections) { + setSelected(con, false); + con->update(); + QWidget *source = con->widget(EndPoint::Source); + if (qobject_cast(source) == 0) { + qDebug("BuddyConnection::deleteSelected(): not a label"); + } else { + ResetPropertyCommand *command = new ResetPropertyCommand(formWindow()); + command->init(source, buddyPropertyC); + undoStack()->push(command); + } + delete takeConnection(con); + } + undoStack()->endMacro(); +} + +void BuddyEditor::autoBuddy() +{ + // Any labels? + auto labelList = background()->findChildren(); + if (labelList.isEmpty()) + return; + // Find already used buddies + QWidgetList usedBuddies; + const ConnectionList &beforeConnections = connectionList(); + for (const Connection *c : beforeConnections) + usedBuddies.push_back(c->widget(EndPoint::Target)); + // Find potential new buddies, keep lists in sync + QWidgetList buddies; + for (auto it = labelList.begin(); it != labelList.end(); ) { + QLabel *label = *it; + QWidget *newBuddy = nullptr; + if (m_formWindow->isManaged(label)) { + const QString buddy_name = buddy(label, m_formWindow->core()); + if (buddy_name.isEmpty()) + newBuddy = findBuddy(label, usedBuddies); + } + if (newBuddy) { + buddies.push_back(newBuddy); + usedBuddies.push_back(newBuddy); + ++it; + } else { + it = labelList.erase(it); + } + } + // Add the list in one go. + if (labelList.isEmpty()) + return; + const auto count = labelList.size(); + Q_ASSERT(count == buddies.size()); + undoStack()->beginMacro(tr("Add %n buddies", nullptr, count)); + for (qsizetype i = 0; i < count; ++i) + undoStack()->push(createBuddyCommand(m_formWindow, labelList.at(i), buddies.at(i))); + undoStack()->endMacro(); + // Now select all new ones + const ConnectionList &connections = connectionList(); + for (Connection *con : connections) + setSelected(con, buddies.contains(con->widget(EndPoint::Target))); +} + +// Geometrically find a potential buddy for label by checking neighbouring children of parent +QWidget *BuddyEditor::findBuddy(QLabel *l, const QWidgetList &existingBuddies) const +{ + enum { DeltaX = 5 }; + const QWidget *parent = l->parentWidget(); + // Try to find next managed neighbour on horizontal line + const QRect geom = l->geometry(); + const int y = geom.center().y(); + QWidget *neighbour = nullptr; + switch (l->layoutDirection()) { + case Qt::LayoutDirectionAuto: + case Qt::LeftToRight: { // Walk right to find next managed neighbour + const int xEnd = parent->size().width(); + for (int x = geom.right() + 1; x < xEnd; x += DeltaX) + if (QWidget *c = parent->childAt (x, y)) + if (m_formWindow->isManaged(c)) { + neighbour = c; + break; + } + } + break; + case Qt::RightToLeft: // Walk left to find next managed neighbour + for (int x = geom.x() - 1; x >= 0; x -= DeltaX) + if (QWidget *c = parent->childAt (x, y)) + if (m_formWindow->isManaged(c)) { + neighbour = c; + break; + } + break; + } + if (neighbour && !existingBuddies.contains(neighbour) && canBeBuddy(neighbour, m_formWindow)) + return neighbour; + + return nullptr; +} + +void BuddyEditor::createContextMenu(QMenu &menu) +{ + QAction *autoAction = menu.addAction(tr("Set automatically")); + connect(autoAction, &QAction::triggered, this, &BuddyEditor::autoBuddy); + menu.addSeparator(); + ConnectionEdit::createContextMenu(menu); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/buddyeditor/buddyeditor.h b/src/tools/designer/src/components/buddyeditor/buddyeditor.h new file mode 100644 index 00000000000..d844b997cdd --- /dev/null +++ b/src/tools/designer/src/components/buddyeditor/buddyeditor.h @@ -0,0 +1,54 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUDDYEDITOR_H +#define BUDDYEDITOR_H + +#include "buddyeditor_global.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +class QLabel; + +namespace qdesigner_internal { + +class QT_BUDDYEDITOR_EXPORT BuddyEditor : public ConnectionEdit +{ + Q_OBJECT + +public: + BuddyEditor(QDesignerFormWindowInterface *form, QWidget *parent); + + QDesignerFormWindowInterface *formWindow() const; + void setBackground(QWidget *background) override; + void deleteSelected() override; + +public slots: + void updateBackground() override; + void widgetRemoved(QWidget *w) override; + void autoBuddy(); + +protected: + QWidget *widgetAt(const QPoint &pos) const override; + Connection *createConnection(QWidget *source, QWidget *destination) override; + void endConnection(QWidget *target, const QPoint &pos) override; + void createContextMenu(QMenu &menu) override; + +private: + QWidget *findBuddy(QLabel *l, const QWidgetList &existingBuddies) const; + + QPointer m_formWindow; + bool m_updating; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/designer/src/components/buddyeditor/buddyeditor.json b/src/tools/designer/src/components/buddyeditor/buddyeditor.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/src/tools/designer/src/components/buddyeditor/buddyeditor.json @@ -0,0 +1 @@ +{} diff --git a/src/tools/designer/src/components/buddyeditor/buddyeditor_global.h b/src/tools/designer/src/components/buddyeditor/buddyeditor_global.h new file mode 100644 index 00000000000..1085e409bf0 --- /dev/null +++ b/src/tools/designer/src/components/buddyeditor/buddyeditor_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUDDYEDITOR_GLOBAL_H +#define BUDDYEDITOR_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_BUDDYEDITOR_LIBRARY +# define QT_BUDDYEDITOR_EXPORT +#else +# define QT_BUDDYEDITOR_EXPORT +#endif +#else +#define QT_BUDDYEDITOR_EXPORT +#endif + +#endif // BUDDYEDITOR_GLOBAL_H diff --git a/src/tools/designer/src/components/buddyeditor/buddyeditor_plugin.cpp b/src/tools/designer/src/components/buddyeditor/buddyeditor_plugin.cpp new file mode 100644 index 00000000000..b4128bad457 --- /dev/null +++ b/src/tools/designer/src/components/buddyeditor/buddyeditor_plugin.cpp @@ -0,0 +1,94 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include "buddyeditor_plugin.h" +#include "buddyeditor_tool.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +BuddyEditorPlugin::BuddyEditorPlugin() = default; + +BuddyEditorPlugin::~BuddyEditorPlugin() = default; + +bool BuddyEditorPlugin::isInitialized() const +{ + return m_initialized; +} + +void BuddyEditorPlugin::initialize(QDesignerFormEditorInterface *core) +{ + Q_ASSERT(!isInitialized()); + + m_action = new QAction(tr("Edit Buddies"), this); + m_action->setObjectName(u"__qt_edit_buddies_action"_s); + QIcon buddyIcon = QIcon::fromTheme(u"designer-edit-buddy"_s, + QIcon(core->resourceLocation() + "/buddytool.png"_L1)); + m_action->setIcon(buddyIcon); + m_action->setEnabled(false); + + setParent(core); + m_core = core; + m_initialized = true; + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowAdded, + this, &BuddyEditorPlugin::addFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowRemoved, + this, &BuddyEditorPlugin::removeFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &BuddyEditorPlugin::activeFormWindowChanged); +} + +QDesignerFormEditorInterface *BuddyEditorPlugin::core() const +{ + return m_core; +} + +void BuddyEditorPlugin::addFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == false); + + BuddyEditorTool *tool = new BuddyEditorTool(formWindow, this); + m_tools[formWindow] = tool; + connect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + formWindow->registerTool(tool); +} + +void BuddyEditorPlugin::removeFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == true); + + BuddyEditorTool *tool = m_tools.value(formWindow); + m_tools.remove(formWindow); + disconnect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + // ### FIXME disable the tool + + delete tool; +} + +QAction *BuddyEditorPlugin::action() const +{ + return m_action; +} + +void BuddyEditorPlugin::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) +{ + m_action->setEnabled(formWindow != nullptr); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/buddyeditor/buddyeditor_plugin.h b/src/tools/designer/src/components/buddyeditor/buddyeditor_plugin.h new file mode 100644 index 00000000000..1e38f0ed937 --- /dev/null +++ b/src/tools/designer/src/components/buddyeditor/buddyeditor_plugin.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUDDYEDITOR_PLUGIN_H +#define BUDDYEDITOR_PLUGIN_H + +#include "buddyeditor_global.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class BuddyEditorTool; + +class QT_BUDDYEDITOR_EXPORT BuddyEditorPlugin: public QObject, public QDesignerFormEditorPluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerFormEditorPluginInterface" FILE "buddyeditor.json") + Q_INTERFACES(QDesignerFormEditorPluginInterface) +public: + BuddyEditorPlugin(); + ~BuddyEditorPlugin() override; + + bool isInitialized() const override; + void initialize(QDesignerFormEditorInterface *core) override; + QAction *action() const override; + + QDesignerFormEditorInterface *core() const override; + +public slots: + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + +private slots: + void addFormWindow(QDesignerFormWindowInterface *formWindow); + void removeFormWindow(QDesignerFormWindowInterface *formWindow); + +private: + QPointer m_core; + QHash m_tools; + bool m_initialized = false; + QAction *m_action = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // BUDDYEDITOR_PLUGIN_H diff --git a/src/tools/designer/src/components/buddyeditor/buddyeditor_tool.cpp b/src/tools/designer/src/components/buddyeditor/buddyeditor_tool.cpp new file mode 100644 index 00000000000..0ed4e7e64d5 --- /dev/null +++ b/src/tools/designer/src/components/buddyeditor/buddyeditor_tool.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "buddyeditor_tool.h" +#include "buddyeditor.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +BuddyEditorTool::BuddyEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent) + : QDesignerFormWindowToolInterface(parent), + m_formWindow(formWindow), + m_action(new QAction(tr("Edit Buddies"), this)) +{ +} + +BuddyEditorTool::~BuddyEditorTool() = default; + +QDesignerFormEditorInterface *BuddyEditorTool::core() const +{ + return m_formWindow->core(); +} + +QDesignerFormWindowInterface *BuddyEditorTool::formWindow() const +{ + return m_formWindow; +} + +bool BuddyEditorTool::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + Q_UNUSED(widget); + Q_UNUSED(managedWidget); + Q_UNUSED(event); + + return false; +} + +QWidget *BuddyEditorTool::editor() const +{ + if (!m_editor) { + Q_ASSERT(formWindow() != nullptr); + m_editor = new BuddyEditor(formWindow(), nullptr); + connect(formWindow(), &QDesignerFormWindowInterface::mainContainerChanged, + m_editor.data(), &BuddyEditor::setBackground); + connect(formWindow(), &QDesignerFormWindowInterface::changed, + m_editor.data(), &BuddyEditor::updateBackground); + } + + return m_editor; +} + +void BuddyEditorTool::activated() +{ + m_editor->enableUpdateBackground(true); +} + +void BuddyEditorTool::deactivated() +{ + m_editor->enableUpdateBackground(false); +} + +QAction *BuddyEditorTool::action() const +{ + return m_action; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/buddyeditor/buddyeditor_tool.h b/src/tools/designer/src/components/buddyeditor/buddyeditor_tool.h new file mode 100644 index 00000000000..136711ebb20 --- /dev/null +++ b/src/tools/designer/src/components/buddyeditor/buddyeditor_tool.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUDDYEDITOR_TOOL_H +#define BUDDYEDITOR_TOOL_H + +#include "buddyeditor_global.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class BuddyEditor; + +class QT_BUDDYEDITOR_EXPORT BuddyEditorTool: public QDesignerFormWindowToolInterface +{ + Q_OBJECT +public: + explicit BuddyEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent = nullptr); + ~BuddyEditorTool() override; + + QDesignerFormEditorInterface *core() const override; + QDesignerFormWindowInterface *formWindow() const override; + + QWidget *editor() const override; + QAction *action() const override; + + void activated() override; + void deactivated() override; + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) override; + +private: + QDesignerFormWindowInterface *m_formWindow; + mutable QPointer m_editor; + QAction *m_action; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // BUDDYEDITOR_TOOL_H diff --git a/src/tools/designer/src/components/formeditor/default_actionprovider.cpp b/src/tools/designer/src/components/formeditor/default_actionprovider.cpp new file mode 100644 index 00000000000..89039185775 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/default_actionprovider.cpp @@ -0,0 +1,171 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "default_actionprovider.h" +#include "invisible_widget_p.h" +#include "qdesigner_toolbar_p.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ------------ ActionProviderBase: +// Draws the drag indicator when dragging an action over a widget +// that receives action Dnd, such as ToolBar, Menu or MenuBar. +ActionProviderBase::ActionProviderBase(QWidget *widget) : + m_indicator(new InvisibleWidget(widget)) +{ + Q_ASSERT(widget != nullptr); + + m_indicator->setAutoFillBackground(true); + m_indicator->setBackgroundRole(QPalette::Window); + + QPalette p; + p.setColor(m_indicator->backgroundRole(), Qt::red); + m_indicator->setPalette(p); + m_indicator->hide(); +} + +enum { indicatorSize = 2 }; + +// Position an indicator horizontally over the rectangle, indicating +// 'Insert before' (left or right according to layout direction) +static inline QRect horizontalIndicatorRect(const QRect &rect, Qt::LayoutDirection layoutDirection) +{ + // Position right? + QRect rc = QRect(rect.x(), 0, indicatorSize, rect.height() - 1); + if (layoutDirection == Qt::RightToLeft) + rc.moveLeft(rc.x() + rect.width() - indicatorSize); + return rc; +} + +// Position an indicator vertically over the rectangle, indicating 'Insert before' (top) +static inline QRect verticalIndicatorRect(const QRect &rect) +{ + return QRect(0, rect.top(), rect.width() - 1, indicatorSize); +} + +// Determine the geometry of the indicator by retrieving +// the action under mouse and positioning the bar within its geometry. +QRect ActionProviderBase::indicatorGeometry(const QPoint &pos, Qt::LayoutDirection layoutDirection) const +{ + QAction *action = actionAt(pos); + if (!action) + return QRect(); + QRect rc = actionGeometry(action); + return orientation() == Qt::Horizontal ? horizontalIndicatorRect(rc, layoutDirection) : verticalIndicatorRect(rc); +} + +// Adjust the indicator while dragging. (-1,1) is called to finish a DND operation +void ActionProviderBase::adjustIndicator(const QPoint &pos) +{ + if (pos == QPoint(-1, -1)) { + m_indicator->hide(); + return; + } + const QRect ig = indicatorGeometry(pos, m_indicator->layoutDirection()); + if (ig.isValid()) { + m_indicator->setGeometry(ig); + QPalette p = m_indicator->palette(); + if (p.color(m_indicator->backgroundRole()) != Qt::red) { + p.setColor(m_indicator->backgroundRole(), Qt::red); + m_indicator->setPalette(p); + } + m_indicator->show(); + m_indicator->raise(); + } else { + m_indicator->hide(); + } +} + +// ------------- QToolBarActionProvider +QToolBarActionProvider::QToolBarActionProvider(QToolBar *widget, QObject *parent) : + QObject(parent), + ActionProviderBase(widget), + m_widget(widget) +{ +} + +QRect QToolBarActionProvider::actionGeometry(QAction *action) const +{ + return m_widget->actionGeometry(action); +} + +QAction *QToolBarActionProvider::actionAt(const QPoint &pos) const +{ + return ToolBarEventFilter::actionAt(m_widget, pos); +} + +Qt::Orientation QToolBarActionProvider::orientation() const +{ + return m_widget->orientation(); +} + +QRect QToolBarActionProvider::indicatorGeometry(const QPoint &pos, Qt::LayoutDirection layoutDirection) const +{ + const QRect actionRect = ActionProviderBase::indicatorGeometry(pos, layoutDirection); + if (actionRect.isValid()) + return actionRect; + // Toolbar differs in that is has no dummy placeholder to 'insert before' + // when intending to append. Check the free area. + const QRect freeArea = ToolBarEventFilter::freeArea(m_widget); + if (!freeArea.contains(pos)) + return QRect(); + return orientation() == Qt::Horizontal ? horizontalIndicatorRect(freeArea, layoutDirection) : verticalIndicatorRect(freeArea); +} + +// ------------- QMenuBarActionProvider +QMenuBarActionProvider::QMenuBarActionProvider(QMenuBar *widget, QObject *parent) : + QObject(parent), + ActionProviderBase(widget), + m_widget(widget) +{ +} + +QRect QMenuBarActionProvider::actionGeometry(QAction *action) const +{ + return m_widget->actionGeometry(action); +} + +QAction *QMenuBarActionProvider::actionAt(const QPoint &pos) const +{ + return m_widget->actionAt(pos); +} + +Qt::Orientation QMenuBarActionProvider::orientation() const +{ + return Qt::Horizontal; +} + +// ------------- QMenuActionProvider +QMenuActionProvider::QMenuActionProvider(QMenu *widget, QObject *parent) : + QObject(parent), + ActionProviderBase(widget), + m_widget(widget) +{ +} + +QRect QMenuActionProvider::actionGeometry(QAction *action) const +{ + return m_widget->actionGeometry(action); +} + +QAction *QMenuActionProvider::actionAt(const QPoint &pos) const +{ + return m_widget->actionAt(pos); +} + +Qt::Orientation QMenuActionProvider::orientation() const +{ + return Qt::Vertical; +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/default_actionprovider.h b/src/tools/designer/src/components/formeditor/default_actionprovider.h new file mode 100644 index 00000000000..88e7fabc250 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/default_actionprovider.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEFAULT_ACTIONPROVIDER_H +#define DEFAULT_ACTIONPROVIDER_H + +#include "formeditor_global.h" +#include "actionprovider_p.h" +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class FormWindow; + +class QT_FORMEDITOR_EXPORT ActionProviderBase: public QDesignerActionProviderExtension +{ +protected: + explicit ActionProviderBase(QWidget *widget); + +public: + void adjustIndicator(const QPoint &pos) override; + virtual Qt::Orientation orientation() const = 0; + +protected: + virtual QRect indicatorGeometry(const QPoint &pos, Qt::LayoutDirection layoutDirection) const; + +private: + QWidget *m_indicator; +}; + +class QT_FORMEDITOR_EXPORT QToolBarActionProvider: public QObject, public ActionProviderBase +{ + Q_OBJECT + Q_INTERFACES(QDesignerActionProviderExtension) +public: + explicit QToolBarActionProvider(QToolBar *widget, QObject *parent = nullptr); + + QRect actionGeometry(QAction *action) const override; + QAction *actionAt(const QPoint &pos) const override; + Qt::Orientation orientation() const override; + +protected: + QRect indicatorGeometry(const QPoint &pos, Qt::LayoutDirection layoutDirection) const override; + +private: + QToolBar *m_widget; +}; + +class QT_FORMEDITOR_EXPORT QMenuBarActionProvider: public QObject, public ActionProviderBase +{ + Q_OBJECT + Q_INTERFACES(QDesignerActionProviderExtension) +public: + explicit QMenuBarActionProvider(QMenuBar *widget, QObject *parent = nullptr); + + QRect actionGeometry(QAction *action) const override; + QAction *actionAt(const QPoint &pos) const override; + Qt::Orientation orientation() const override; + +private: + QMenuBar *m_widget; +}; + +class QT_FORMEDITOR_EXPORT QMenuActionProvider: public QObject, public ActionProviderBase +{ + Q_OBJECT + Q_INTERFACES(QDesignerActionProviderExtension) +public: + explicit QMenuActionProvider(QMenu *widget, QObject *parent = nullptr); + + QRect actionGeometry(QAction *action) const override; + QAction *actionAt(const QPoint &pos) const override; + Qt::Orientation orientation() const override; + +private: + QMenu *m_widget; +}; + +using QToolBarActionProviderFactory = ExtensionFactory; +using QMenuBarActionProviderFactory = ExtensionFactory; +using QMenuActionProviderFactory = ExtensionFactory; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DEFAULT_ACTIONPROVIDER_H diff --git a/src/tools/designer/src/components/formeditor/default_container.cpp b/src/tools/designer/src/components/formeditor/default_container.cpp new file mode 100644 index 00000000000..ea68b9b211d --- /dev/null +++ b/src/tools/designer/src/components/formeditor/default_container.cpp @@ -0,0 +1,137 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "default_container.h" +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +template +static inline void setCurrentContainerIndex(int index, Container *container) +{ + const bool blocked = container->signalsBlocked(); + container->blockSignals(true); + container->setCurrentIndex(index); + container->blockSignals(blocked); +} + +static inline void ensureNoParent(QWidget *widget) +{ + if (widget->parentWidget()) + widget->setParent(nullptr); +} + +static constexpr auto PageLabel = "Page"_L1; + +namespace qdesigner_internal { + +// --------- QStackedWidgetContainer +QStackedWidgetContainer::QStackedWidgetContainer(QStackedWidget *widget, QObject *parent) : + QObject(parent), + m_widget(widget) +{ +} + +void QStackedWidgetContainer::setCurrentIndex(int index) +{ + setCurrentContainerIndex(index, m_widget); +} + +void QStackedWidgetContainer::addWidget(QWidget *widget) +{ + ensureNoParent(widget); + m_widget->addWidget(widget); +} + +void QStackedWidgetContainer::insertWidget(int index, QWidget *widget) +{ + ensureNoParent(widget); + m_widget->insertWidget(index, widget); +} + +void QStackedWidgetContainer::remove(int index) +{ + m_widget->removeWidget(widget(index)); +} + +// --------- QTabWidgetContainer +QTabWidgetContainer::QTabWidgetContainer(QTabWidget *widget, QObject *parent) : + QObject(parent), + m_widget(widget) +{ +} + +void QTabWidgetContainer::setCurrentIndex(int index) +{ + setCurrentContainerIndex(index, m_widget); +} + +void QTabWidgetContainer::addWidget(QWidget *widget) +{ + ensureNoParent(widget); + m_widget->addTab(widget, QString::fromUtf8(PageLabel)); +} + +void QTabWidgetContainer::insertWidget(int index, QWidget *widget) +{ + ensureNoParent(widget); + m_widget->insertTab(index, widget, QString::fromUtf8(PageLabel)); +} + +void QTabWidgetContainer::remove(int index) +{ + m_widget->removeTab(index); +} + +// ------------------- QToolBoxContainer +QToolBoxContainer::QToolBoxContainer(QToolBox *widget, QObject *parent) : + QObject(parent), + m_widget(widget) +{ +} + +void QToolBoxContainer::setCurrentIndex(int index) +{ + setCurrentContainerIndex(index, m_widget); +} + +void QToolBoxContainer::addWidget(QWidget *widget) +{ + ensureNoParent(widget); + m_widget->addItem(widget, QString::fromUtf8(PageLabel)); +} + +void QToolBoxContainer::insertWidget(int index, QWidget *widget) +{ + ensureNoParent(widget); + m_widget->insertItem(index, widget, QString::fromUtf8(PageLabel)); +} + +void QToolBoxContainer::remove(int index) +{ + m_widget->removeItem(index); +} + +// ------------------- QScrollAreaContainer +// We pass on active=true only if there are no children yet. +// If there are children, it is a legacy custom widget QScrollArea that has an internal, +// unmanaged child, in which case we deactivate the extension (otherwise we crash). +// The child will then not show up in the task menu + +QScrollAreaContainer::QScrollAreaContainer(QScrollArea *widget, QObject *parent) : + QObject(parent), + SingleChildContainer(widget, widget->widget() == nullptr) +{ +} +// ------------------- QDockWidgetContainer +QDockWidgetContainer::QDockWidgetContainer(QDockWidget *widget, QObject *parent) : + QObject(parent), + SingleChildContainer(widget) +{ +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/default_container.h b/src/tools/designer/src/components/formeditor/default_container.h new file mode 100644 index 00000000000..a22abad42a3 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/default_container.h @@ -0,0 +1,184 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEFAULT_CONTAINER_H +#define DEFAULT_CONTAINER_H + +#include +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ------------ QStackedWidgetContainer +class QStackedWidgetContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QStackedWidgetContainer(QStackedWidget *widget, QObject *parent = nullptr); + + int count() const override { return m_widget->count(); } + QWidget *widget(int index) const override { return m_widget->widget(index); } + + int currentIndex() const override { return m_widget->currentIndex(); } + void setCurrentIndex(int index) override; + + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QStackedWidget *m_widget; +}; + +// ------------ QTabWidgetContainer +class QTabWidgetContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QTabWidgetContainer(QTabWidget *widget, QObject *parent = nullptr); + + int count() const override { return m_widget->count(); } + QWidget *widget(int index) const override { return m_widget->widget(index); } + + int currentIndex() const override { return m_widget->currentIndex(); } + void setCurrentIndex(int index) override; + + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QTabWidget *m_widget; +}; + +// ------------ QToolBoxContainer +class QToolBoxContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QToolBoxContainer(QToolBox *widget, QObject *parent = nullptr); + + int count() const override { return m_widget->count(); } + QWidget *widget(int index) const override { return m_widget->widget(index); } + + int currentIndex() const override { return m_widget->currentIndex(); } + void setCurrentIndex(int index) override; + + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QToolBox *m_widget; +}; + +// ------------ SingleChildContainer: +// Template for containers that have a single child widget using widget()/setWidget(). + +template +class SingleChildContainer: public QDesignerContainerExtension +{ +protected: + explicit SingleChildContainer(Container *widget, bool active = true); +public: + int count() const override; + QWidget *widget(int index) const override; + int currentIndex() const override; + void setCurrentIndex(int /*index*/) override {} + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + void remove(int /*index*/) override {} + + bool canAddWidget() const override { return false; } + bool canRemove(int) const override { return false; } + +private: + const bool m_active; + Container *m_container; +}; + +template +SingleChildContainer::SingleChildContainer(Container *widget, bool active) : + m_active(active), + m_container(widget) +{ +} + +template +int SingleChildContainer::count() const +{ + return m_active && m_container->widget() ? 1 : 0; +} + +template +QWidget *SingleChildContainer::widget(int /* index */) const +{ + return m_container->widget(); +} + +template +int SingleChildContainer::currentIndex() const +{ + return m_active && m_container->widget() ? 0 : -1; +} + +template +void SingleChildContainer::addWidget(QWidget *widget) +{ + Q_ASSERT(m_container->widget() == nullptr); + widget->setParent(m_container); + m_container->setWidget(widget); +} + +template +void SingleChildContainer::insertWidget(int /* index */, QWidget *widget) +{ + addWidget(widget); +} + +// ------------ QScrollAreaContainer +class QScrollAreaContainer: public QObject, public SingleChildContainer +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QScrollAreaContainer(QScrollArea *widget, QObject *parent = nullptr); +}; + +// --------------- QDockWidgetContainer +class QDockWidgetContainer: public QObject, public SingleChildContainer +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QDockWidgetContainer(QDockWidget *widget, QObject *parent = nullptr); +}; + +using QDesignerStackedWidgetContainerFactory = ExtensionFactory; +using QDesignerTabWidgetContainerFactory = ExtensionFactory; +using QDesignerToolBoxContainerFactory = ExtensionFactory; +using QScrollAreaContainerFactory = ExtensionFactory; +using QDockWidgetContainerFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DEFAULT_CONTAINER_H diff --git a/src/tools/designer/src/components/formeditor/default_layoutdecoration.cpp b/src/tools/designer/src/components/formeditor/default_layoutdecoration.cpp new file mode 100644 index 00000000000..171bd0f4ab1 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/default_layoutdecoration.cpp @@ -0,0 +1,41 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "default_layoutdecoration.h" +#include "qlayout_widget_p.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ---- QDesignerLayoutDecorationFactory ---- +QDesignerLayoutDecorationFactory::QDesignerLayoutDecorationFactory(QExtensionManager *parent) + : QExtensionFactory(parent) +{ +} + +QObject *QDesignerLayoutDecorationFactory::createExtension(QObject *object, const QString &iid, QObject *parent) const +{ + if (!object->isWidgetType() || iid != Q_TYPEID(QDesignerLayoutDecorationExtension)) + return nullptr; + + QWidget *widget = qobject_cast(object); + + if (const QLayoutWidget *layoutWidget = qobject_cast(widget)) + return QLayoutSupport::createLayoutSupport(layoutWidget->formWindow(), widget, parent); + + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(widget)) + if (LayoutInfo::managedLayout(fw->core(), widget)) + return QLayoutSupport::createLayoutSupport(fw, widget, parent); + + return nullptr; +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/default_layoutdecoration.h b/src/tools/designer/src/components/formeditor/default_layoutdecoration.h new file mode 100644 index 00000000000..b9cf5939fe3 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/default_layoutdecoration.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEFAULT_LAYOUTDECORATION_H +#define DEFAULT_LAYOUTDECORATION_H + +#include "formeditor_global.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { +class QDesignerLayoutDecorationFactory: public QExtensionFactory +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionFactory) +public: + explicit QDesignerLayoutDecorationFactory(QExtensionManager *parent = nullptr); + +protected: + QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DEFAULT_LAYOUTDECORATION_H diff --git a/src/tools/designer/src/components/formeditor/defaultbrushes.xml b/src/tools/designer/src/components/formeditor/defaultbrushes.xml new file mode 100644 index 00000000000..88035c3a685 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/defaultbrushes.xml @@ -0,0 +1,542 @@ + + + + + + + 0 + 0 + 255 + + + + + 0 + 0 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + + + 0 + 0 + 0 + + + + + 0 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 255 + 0 + + + + + 255 + 255 + 0 + + + + + + + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 0 + 176 + 221 + + + + + 0 + 176 + 221 + + + + + + + + + + + 60 + 160 + 0 + + + + + 60 + 160 + 0 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + + + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 5 + 0 + 70 + + + + + 5 + 0 + 70 + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + + + 255 + 255 + 255 + + + + + 255 + 255 + 255 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 255 + 0 + + + + + 255 + 255 + 0 + + + + + 255 + 0 + 0 + + + + + 255 + 0 + 0 + + + + + + + + + 0 + 0 + 0 + + + + + + + 0 + 0 + 255 + + + + + + + 0 + 255 + 255 + + + + + + + 0 + 255 + 0 + + + + + + + 255 + 0 + 255 + + + + + + + 255 + 0 + 0 + + + + + + + 255 + 255 + 255 + + + + + + + 255 + 255 + 0 + + + + diff --git a/src/tools/designer/src/components/formeditor/deviceprofiledialog.cpp b/src/tools/designer/src/components/formeditor/deviceprofiledialog.cpp new file mode 100644 index 00000000000..188e24cf0c0 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/deviceprofiledialog.cpp @@ -0,0 +1,172 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "deviceprofiledialog.h" +#include "ui_deviceprofiledialog.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto profileExtensionC = "qdp"_L1; + +static inline QString fileFilter() +{ + return qdesigner_internal::DeviceProfileDialog::tr("Device Profiles (*.%1)").arg(profileExtensionC); +} + +// Populate a combo with a sequence of integers, also set them as data. +template + static void populateNumericCombo(IntIterator i1, IntIterator i2, QComboBox *cb) +{ + QString s; + for ( ; i1 != i2 ; ++i1) { + const int n = *i1; + s.setNum(n); + cb->addItem(s, QVariant(n)); + } +} + +namespace qdesigner_internal { + +DeviceProfileDialog::DeviceProfileDialog(QDesignerDialogGuiInterface *dlgGui, QWidget *parent) : + QDialog(parent), + m_ui(new QT_PREPEND_NAMESPACE(Ui)::DeviceProfileDialog), + m_dlgGui(dlgGui) +{ + setModal(true); + m_ui->setupUi(this); + + const auto standardFontSizes = QFontDatabase::standardSizes(); + populateNumericCombo(standardFontSizes.constBegin(), standardFontSizes.constEnd(), m_ui->m_systemFontSizeCombo); + + // 288pt observed on macOS. + const int maxPointSize = qMax(288, standardFontSizes.constLast()); + m_ui->m_systemFontSizeCombo->setValidator(new QIntValidator(1, maxPointSize, + m_ui->m_systemFontSizeCombo)); + + // Styles + const QStringList styles = QStyleFactory::keys(); + m_ui->m_styleCombo->addItem(tr("Default"), QVariant(QString())); + for (const auto &s : styles) + m_ui->m_styleCombo->addItem(s, s); + + connect(m_ui->m_nameLineEdit, &QLineEdit::textChanged, this, &DeviceProfileDialog::nameChanged); + connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(m_ui->buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, + this, &QDialog::accept); + // Note that Load/Save emit accepted() of the button box.. + connect(m_ui->buttonBox->button(QDialogButtonBox::Save), &QAbstractButton::clicked, + this, &DeviceProfileDialog::save); + connect(m_ui->buttonBox->button(QDialogButtonBox::Open), &QAbstractButton::clicked, + this, &DeviceProfileDialog::open); +} + +DeviceProfileDialog::~DeviceProfileDialog() +{ + delete m_ui; +} + +DeviceProfile DeviceProfileDialog::deviceProfile() const +{ + DeviceProfile rc; + rc.setName(m_ui->m_nameLineEdit->text()); + rc.setFontFamily(m_ui->m_systemFontComboBox->currentFont().family()); + rc.setFontPointSize(m_ui->m_systemFontSizeCombo->itemData(m_ui->m_systemFontSizeCombo->currentIndex()).toInt()); + + int dpiX, dpiY; + m_ui->m_dpiChooser->getDPI(&dpiX, &dpiY); + rc.setDpiX(dpiX); + rc.setDpiY(dpiY); + + rc.setStyle(m_ui->m_styleCombo->itemData(m_ui->m_styleCombo->currentIndex()).toString()); + + return rc; +} + +void DeviceProfileDialog::setDeviceProfile(const DeviceProfile &s) +{ + m_ui->m_nameLineEdit->setText(s.name()); + m_ui->m_systemFontComboBox->setCurrentFont(QFont(s.fontFamily())); + const int fontSizeIndex = m_ui->m_systemFontSizeCombo->findData(QVariant(s.fontPointSize())); + m_ui->m_systemFontSizeCombo->setCurrentIndex(fontSizeIndex != -1 ? fontSizeIndex : 0); + m_ui->m_dpiChooser->setDPI(s.dpiX(), s.dpiY()); + const int styleIndex = m_ui->m_styleCombo->findData(s.style()); + m_ui->m_styleCombo->setCurrentIndex(styleIndex != -1 ? styleIndex : 0); +} + +void DeviceProfileDialog::setOkButtonEnabled(bool v) +{ + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(v); +} + +bool DeviceProfileDialog::showDialog(const QStringList &existingNames) +{ + m_existingNames = existingNames; + m_ui->m_nameLineEdit->setFocus(Qt::OtherFocusReason); + nameChanged(m_ui->m_nameLineEdit->text()); + return exec() == Accepted; +} + +void DeviceProfileDialog::nameChanged(const QString &name) +{ + const bool invalid = name.isEmpty() || m_existingNames.indexOf(name) != -1; + setOkButtonEnabled(!invalid); +} + +void DeviceProfileDialog::save() +{ + QString fn = m_dlgGui->getSaveFileName(this, tr("Save Profile"), QString(), fileFilter()); + if (fn.isEmpty()) + return; + if (QFileInfo(fn).completeSuffix().isEmpty()) + fn += u'.' + profileExtensionC; + + QFile file(fn); + if (!file.open(QIODevice::WriteOnly|QIODevice::Text)) { + critical(tr("Save Profile - Error"), tr("Unable to open the file '%1' for writing: %2").arg(fn, file.errorString())); + return; + } + file.write(deviceProfile().toXml().toUtf8()); +} + +void DeviceProfileDialog::open() +{ + const QString fn = m_dlgGui->getOpenFileName(this, tr("Open profile"), QString(), fileFilter()); + if (fn.isEmpty()) + return; + + QFile file(fn); + if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) { + critical(tr("Open Profile - Error"), tr("Unable to open the file '%1' for reading: %2").arg(fn, file.errorString())); + return; + } + QString errorMessage; + DeviceProfile newSettings; + if (!newSettings.fromXml(QString::fromUtf8(file.readAll()), &errorMessage)) { + critical(tr("Open Profile - Error"), tr("'%1' is not a valid profile: %2").arg(fn, errorMessage)); + return; + } + setDeviceProfile(newSettings); +} + +void DeviceProfileDialog::critical(const QString &title, const QString &msg) +{ + m_dlgGui->message(this, QDesignerDialogGuiInterface::OtherMessage, QMessageBox::Critical, title, msg); +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/deviceprofiledialog.h b/src/tools/designer/src/components/formeditor/deviceprofiledialog.h new file mode 100644 index 00000000000..34a9fe5b0ab --- /dev/null +++ b/src/tools/designer/src/components/formeditor/deviceprofiledialog.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef SYSTEMSETTINGSDIALOG_H +#define SYSTEMSETTINGSDIALOG_H + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace Ui { + class DeviceProfileDialog; +} + +class QDesignerDialogGuiInterface; + +class QDialogButtonBox; + +namespace qdesigner_internal { + +class DeviceProfile; + +/* DeviceProfileDialog: Widget to edit system settings for embedded design */ + +class DeviceProfileDialog : public QDialog +{ + Q_DISABLE_COPY_MOVE(DeviceProfileDialog) + Q_OBJECT +public: + explicit DeviceProfileDialog(QDesignerDialogGuiInterface *dlgGui, QWidget *parent = nullptr); + ~DeviceProfileDialog(); + + DeviceProfile deviceProfile() const; + void setDeviceProfile(const DeviceProfile &s); + + bool showDialog(const QStringList &existingNames); + +private slots: + void setOkButtonEnabled(bool); + void nameChanged(const QString &name); + void save(); + void open() override; + +private: + void critical(const QString &title, const QString &msg); + Ui::DeviceProfileDialog *m_ui; + QDesignerDialogGuiInterface *m_dlgGui; + QStringList m_existingNames; +}; +} + +QT_END_NAMESPACE + +#endif // SYSTEMSETTINGSDIALOG_H diff --git a/src/tools/designer/src/components/formeditor/deviceprofiledialog.ui b/src/tools/designer/src/components/formeditor/deviceprofiledialog.ui new file mode 100644 index 00000000000..1671d978050 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/deviceprofiledialog.ui @@ -0,0 +1,112 @@ + + + DeviceProfileDialog + + + + 0 + 0 + 348 + 209 + + + + + + + + + + &Family + + + m_systemFontComboBox + + + + + + + + + + &Point Size + + + m_systemFontSizeCombo + + + + + + + true + + + + + + + Style + + + m_styleCombo + + + + + + + + + + Device DPI + + + + + + + + + + Name + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Open|QDialogButtonBox::Save + + + + + + + + qdesigner_internal::DPI_Chooser + QWidget +
dpi_chooser.h
+ 1 +
+
+ + m_nameLineEdit + m_systemFontComboBox + m_systemFontSizeCombo + m_styleCombo + buttonBox + + + +
diff --git a/src/tools/designer/src/components/formeditor/dpi_chooser.cpp b/src/tools/designer/src/components/formeditor/dpi_chooser.cpp new file mode 100644 index 00000000000..5a34f124a81 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/dpi_chooser.cpp @@ -0,0 +1,168 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "dpi_chooser.h" + +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +enum { minDPI = 50, maxDPI = 400 }; + +namespace qdesigner_internal { + +// Entry struct for predefined values +struct DPI_Entry { + int dpiX; + int dpiY; + const char *description; +}; + +const struct DPI_Entry dpiEntries[] = { + //: Embedded device standard screen resolution + { 96, 96, QT_TRANSLATE_NOOP("DPI_Chooser", "Standard (96 x 96)") }, + //: Embedded device screen resolution + { 179, 185, QT_TRANSLATE_NOOP("DPI_Chooser", "Greenphone (179 x 185)") }, + //: Embedded device high definition screen resolution + { 192, 192, QT_TRANSLATE_NOOP("DPI_Chooser", "High (192 x 192)") } +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(const struct qdesigner_internal::DPI_Entry*); + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ------------- DPI_Chooser + +DPI_Chooser::DPI_Chooser(QWidget *parent) : + QWidget(parent), + m_systemEntry(new DPI_Entry), + m_predefinedCombo(new QComboBox), + m_dpiXSpinBox(new QSpinBox), + m_dpiYSpinBox(new QSpinBox) +{ + // Predefined settings: System + DeviceProfile::systemResolution(&(m_systemEntry->dpiX), &(m_systemEntry->dpiY)); + m_systemEntry->description = nullptr; + const struct DPI_Entry *systemEntry = m_systemEntry; + //: System resolution + m_predefinedCombo->addItem(tr("System (%1 x %2)").arg(m_systemEntry->dpiX).arg(m_systemEntry->dpiY), QVariant::fromValue(systemEntry)); + // Devices. Exclude the system values as not to duplicate the entries + for (const DPI_Entry &e : dpiEntries) { + if (e.dpiX != m_systemEntry->dpiX || e.dpiY != m_systemEntry->dpiY) + m_predefinedCombo->addItem(tr(e.description), QVariant::fromValue(&e)); + } + m_predefinedCombo->addItem(tr("User defined")); + + setFocusProxy(m_predefinedCombo); + m_predefinedCombo->setEditable(false); + m_predefinedCombo->setCurrentIndex(0); + connect(m_predefinedCombo, &QComboBox::currentIndexChanged, + this, &DPI_Chooser::syncSpinBoxes); + // top row with predefined settings + QVBoxLayout *vBoxLayout = new QVBoxLayout; + vBoxLayout->setContentsMargins(QMargins()); + vBoxLayout->addWidget(m_predefinedCombo); + // Spin box row + QHBoxLayout *hBoxLayout = new QHBoxLayout; + hBoxLayout->setContentsMargins(QMargins()); + + m_dpiXSpinBox->setMinimum(minDPI); + m_dpiXSpinBox->setMaximum(maxDPI); + hBoxLayout->addWidget(m_dpiXSpinBox); + //: DPI X/Y separator + hBoxLayout->addWidget(new QLabel(tr(" x "))); + + m_dpiYSpinBox->setMinimum(minDPI); + m_dpiYSpinBox->setMaximum(maxDPI); + hBoxLayout->addWidget(m_dpiYSpinBox); + + hBoxLayout->addStretch(); + vBoxLayout->addLayout(hBoxLayout); + setLayout(vBoxLayout); + + syncSpinBoxes(); +} + +DPI_Chooser::~DPI_Chooser() +{ + delete m_systemEntry; +} + +void DPI_Chooser::getDPI(int *dpiX, int *dpiY) const +{ + *dpiX = m_dpiXSpinBox->value(); + *dpiY = m_dpiYSpinBox->value(); +} + +void DPI_Chooser::setDPI(int dpiX, int dpiY) +{ + // Default to system if it is something weird + const bool valid = dpiX >= minDPI && dpiX <= maxDPI && dpiY >= minDPI && dpiY <= maxDPI; + if (!valid) { + m_predefinedCombo->setCurrentIndex(0); + return; + } + // Try to find the values among the predefined settings + const int count = m_predefinedCombo->count(); + int predefinedIndex = -1; + for (int i = 0; i < count; i++) { + const QVariant data = m_predefinedCombo->itemData(i); + if (data.metaType().id() != QMetaType::UnknownType) { + const struct DPI_Entry *entry = qvariant_cast(data); + if (entry->dpiX == dpiX && entry->dpiY == dpiY) { + predefinedIndex = i; + break; + } + } + } + if (predefinedIndex != -1) { + m_predefinedCombo->setCurrentIndex(predefinedIndex); // triggers syncSpinBoxes() + } else { + setUserDefinedValues(dpiX, dpiY); + } +} + +void DPI_Chooser::setUserDefinedValues(int dpiX, int dpiY) +{ + const bool blocked = m_predefinedCombo->blockSignals(true); + m_predefinedCombo->setCurrentIndex(m_predefinedCombo->count() - 1); + m_predefinedCombo->blockSignals(blocked); + + m_dpiXSpinBox->setEnabled(true); + m_dpiYSpinBox->setEnabled(true); + m_dpiXSpinBox->setValue(dpiX); + m_dpiYSpinBox->setValue(dpiY); +} + +void DPI_Chooser::syncSpinBoxes() +{ + const int predefIdx = m_predefinedCombo->currentIndex(); + const QVariant data = m_predefinedCombo->itemData(predefIdx); + + // Predefined mode in which spin boxes are disabled or user defined? + const bool userSetting = data.metaType().id() == QMetaType::UnknownType; + m_dpiXSpinBox->setEnabled(userSetting); + m_dpiYSpinBox->setEnabled(userSetting); + + if (!userSetting) { + const struct DPI_Entry *entry = qvariant_cast(data); + m_dpiXSpinBox->setValue(entry->dpiX); + m_dpiYSpinBox->setValue(entry->dpiY); + } +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/dpi_chooser.h b/src/tools/designer/src/components/formeditor/dpi_chooser.h new file mode 100644 index 00000000000..ba8af375fdd --- /dev/null +++ b/src/tools/designer/src/components/formeditor/dpi_chooser.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DPICHOOSER_H +#define DPICHOOSER_H + +#include + +QT_BEGIN_NAMESPACE + +class QSpinBox; +class QComboBox; + +namespace qdesigner_internal { + +struct DPI_Entry; + +/* Let the user choose a DPI settings */ +class DPI_Chooser : public QWidget { + Q_DISABLE_COPY_MOVE(DPI_Chooser) + Q_OBJECT + +public: + explicit DPI_Chooser(QWidget *parent = nullptr); + ~DPI_Chooser(); + + void getDPI(int *dpiX, int *dpiY) const; + void setDPI(int dpiX, int dpiY); + +private slots: + void syncSpinBoxes(); + +private: + void setUserDefinedValues(int dpiX, int dpiY); + + struct DPI_Entry *m_systemEntry; + QComboBox *m_predefinedCombo; + QSpinBox *m_dpiXSpinBox; + QSpinBox *m_dpiYSpinBox; +}; +} + +QT_END_NAMESPACE + +#endif // DPICHOOSER_H diff --git a/src/tools/designer/src/components/formeditor/embeddedoptionspage.cpp b/src/tools/designer/src/components/formeditor/embeddedoptionspage.cpp new file mode 100644 index 00000000000..3d9b7f9d5b6 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/embeddedoptionspage.cpp @@ -0,0 +1,420 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "embeddedoptionspage.h" +#include "deviceprofiledialog.h" +#include "widgetfactory_p.h" +#include "formwindowmanager.h" + +#include +#include +#include +#include +#include + + +// SDK +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +using DeviceProfileList = QList; + +enum { profileComboIndexOffset = 1 }; + +// Sort by name. Used by template, do not make it static! +bool deviceProfileLessThan(const DeviceProfile &d1, const DeviceProfile &d2) +{ + return d1.name().toLower() < d2.name().toLower(); +} + +static bool ask(QWidget *parent, + QDesignerDialogGuiInterface *dlgui, + const QString &title, + const QString &what) +{ + return dlgui->message(parent, QDesignerDialogGuiInterface::OtherMessage, + QMessageBox::Question, title, what, + QMessageBox::Yes|QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; +} + +// ------------ EmbeddedOptionsControlPrivate +class EmbeddedOptionsControlPrivate { + Q_DISABLE_COPY_MOVE(EmbeddedOptionsControlPrivate) +public: + EmbeddedOptionsControlPrivate(QDesignerFormEditorInterface *core); + void init(EmbeddedOptionsControl *q); + + bool isDirty() const { return m_dirty; } + + void loadSettings(); + void saveSettings(); + void slotAdd(); + void slotEdit(); + void slotDelete(); + void slotProfileIndexChanged(int); + +private: + QStringList existingProfileNames() const; + void sortAndPopulateProfileCombo(); + void updateState(); + void updateDescriptionLabel(); + + QDesignerFormEditorInterface *m_core; + QComboBox *m_profileCombo; + QToolButton *m_addButton; + QToolButton *m_editButton; + QToolButton *m_deleteButton; + QLabel *m_descriptionLabel; + + DeviceProfileList m_sortedProfiles; + EmbeddedOptionsControl *m_q = nullptr; + QSet m_usedProfiles; + bool m_dirty = false; +}; + +EmbeddedOptionsControlPrivate::EmbeddedOptionsControlPrivate(QDesignerFormEditorInterface *core) : + m_core(core), + m_profileCombo(new QComboBox), + m_addButton(new QToolButton), + m_editButton(new QToolButton), + m_deleteButton(new QToolButton), + m_descriptionLabel(new QLabel) +{ + m_descriptionLabel->setMinimumHeight(80); + // Determine used profiles to lock them + const QDesignerFormWindowManagerInterface *fwm = core->formWindowManager(); + if (const int fwCount = fwm->formWindowCount()) { + for (int i = 0; i < fwCount; i++) + if (const FormWindowBase *fwb = qobject_cast(fwm->formWindow(i))) { + const QString deviceProfileName = fwb->deviceProfileName(); + if (!deviceProfileName.isEmpty()) + m_usedProfiles.insert(deviceProfileName); + } + } +} + +void EmbeddedOptionsControlPrivate::init(EmbeddedOptionsControl *q) +{ + m_q = q; + QVBoxLayout *vLayout = new QVBoxLayout; + QHBoxLayout *hLayout = new QHBoxLayout; + m_profileCombo->setMinimumWidth(200); + m_profileCombo->setEditable(false); + hLayout->addWidget(m_profileCombo); + m_profileCombo->addItem(EmbeddedOptionsControl::tr("None")); + EmbeddedOptionsControl::connect(m_profileCombo, &QComboBox::currentIndexChanged, + m_q, &EmbeddedOptionsControl::slotProfileIndexChanged); + + m_addButton->setIcon(createIconSet("plus.png"_L1)); + m_addButton->setToolTip(EmbeddedOptionsControl::tr("Add a profile")); + EmbeddedOptionsControl::connect(m_addButton, &QAbstractButton::clicked, + m_q, &EmbeddedOptionsControl::slotAdd); + hLayout->addWidget(m_addButton); + + EmbeddedOptionsControl::connect(m_editButton, &QAbstractButton::clicked, + m_q, &EmbeddedOptionsControl::slotEdit); + m_editButton->setIcon(createIconSet("edit.png"_L1)); + m_editButton->setToolTip(EmbeddedOptionsControl::tr("Edit the selected profile")); + hLayout->addWidget(m_editButton); + + m_deleteButton->setIcon(createIconSet("minus.png"_L1)); + m_deleteButton->setToolTip(EmbeddedOptionsControl::tr("Delete the selected profile")); + EmbeddedOptionsControl::connect(m_deleteButton, &QAbstractButton::clicked, + m_q, &EmbeddedOptionsControl::slotDelete); + hLayout->addWidget(m_deleteButton); + + hLayout->addStretch(); + vLayout->addLayout(hLayout); + vLayout->addWidget(m_descriptionLabel); + m_q->setLayout(vLayout); +} + +QStringList EmbeddedOptionsControlPrivate::existingProfileNames() const +{ + QStringList rc; + for (const auto &dp : m_sortedProfiles) + rc.append(dp.name()); + return rc; +} + +void EmbeddedOptionsControlPrivate::slotAdd() +{ + DeviceProfileDialog dlg(m_core->dialogGui(), m_q); + dlg.setWindowTitle(EmbeddedOptionsControl::tr("Add Profile")); + // Create a new profile with a new, unique name + DeviceProfile settings; + settings.fromSystem(); + dlg.setDeviceProfile(settings); + + const QStringList names = existingProfileNames(); + const QString newNamePrefix = EmbeddedOptionsControl::tr("New profile"); + QString newName = newNamePrefix; + for (int i = 2; names.contains(newName); i++) { + newName = newNamePrefix; + newName += QString::number(i); + } + + settings.setName(newName); + dlg.setDeviceProfile(settings); + if (dlg.showDialog(names)) { + const DeviceProfile newProfile = dlg.deviceProfile(); + m_sortedProfiles.push_back(newProfile); + // Maintain sorted order + sortAndPopulateProfileCombo(); + const int index = m_profileCombo->findText(newProfile.name()); + m_profileCombo->setCurrentIndex(index); + m_dirty = true; + } +} + +void EmbeddedOptionsControlPrivate::slotEdit() +{ + const int index = m_profileCombo->currentIndex() - profileComboIndexOffset; + if (index < 0) + return; + + // Edit the profile, compile a list of existing names + // excluding current one. re-insert if changed, + // re-sort if name changed. + const DeviceProfile oldProfile = m_sortedProfiles.at(index); + const QString oldName = oldProfile.name(); + QStringList names = existingProfileNames(); + names.removeAll(oldName); + + DeviceProfileDialog dlg(m_core->dialogGui(), m_q); + dlg.setWindowTitle(EmbeddedOptionsControl::tr("Edit Profile")); + dlg.setDeviceProfile(oldProfile); + if (dlg.showDialog(names)) { + const DeviceProfile newProfile = dlg.deviceProfile(); + if (newProfile != oldProfile) { + m_dirty = true; + m_sortedProfiles[index] = newProfile; + if (newProfile.name() != oldName) { + sortAndPopulateProfileCombo(); + const int index = m_profileCombo->findText(newProfile.name()); + m_profileCombo->setCurrentIndex(index); + } else { + updateDescriptionLabel(); + } + + } + } +} + +void EmbeddedOptionsControlPrivate::slotDelete() +{ + const int index = m_profileCombo->currentIndex() - profileComboIndexOffset; + if (index < 0) + return; + const QString name = m_sortedProfiles.at(index).name(); + if (ask(m_q, m_core->dialogGui(), + EmbeddedOptionsControl::tr("Delete Profile"), + EmbeddedOptionsControl::tr("Would you like to delete the profile '%1'?").arg(name))) { + m_profileCombo->setCurrentIndex(0); + m_sortedProfiles.removeAt(index); + m_profileCombo->removeItem(index + profileComboIndexOffset); + m_dirty = true; + } +} + +void EmbeddedOptionsControlPrivate::sortAndPopulateProfileCombo() +{ + // Clear items until only "None" is left + for (int i = m_profileCombo->count() - 1; i > 0; i--) + m_profileCombo->removeItem(i); + if (!m_sortedProfiles.isEmpty()) { + std::sort(m_sortedProfiles.begin(), m_sortedProfiles.end(), deviceProfileLessThan); + m_profileCombo->addItems(existingProfileNames()); + } +} + +void EmbeddedOptionsControlPrivate::loadSettings() +{ + const QDesignerSharedSettings settings(m_core); + m_sortedProfiles = settings.deviceProfiles(); + sortAndPopulateProfileCombo(); + // Index: 0 is "None" + const int settingsIndex = settings.currentDeviceProfileIndex(); + const int profileIndex = settingsIndex >= 0 && settingsIndex < m_sortedProfiles.size() ? settingsIndex + profileComboIndexOffset : 0; + m_profileCombo->setCurrentIndex(profileIndex); + updateState(); + m_dirty = false; +} + +void EmbeddedOptionsControlPrivate::saveSettings() +{ + QDesignerSharedSettings settings(m_core); + settings.setDeviceProfiles(m_sortedProfiles); + // Index: 0 is "None" + settings.setCurrentDeviceProfileIndex(m_profileCombo->currentIndex() - profileComboIndexOffset); + m_dirty = false; +} + +//: Format embedded device profile description +static const char *descriptionFormat = QT_TRANSLATE_NOOP("EmbeddedOptionsControl", +"" +"" +"" +"" +"" +"
Font%1, %2
Style%3
Resolution%4 x %5
" +""); + +static inline QString description(const DeviceProfile& p) +{ + QString styleName = p.style(); + if (styleName.isEmpty()) + styleName = EmbeddedOptionsControl::tr("Default"); + return EmbeddedOptionsControl::tr(descriptionFormat). + arg(p.fontFamily()).arg(p.fontPointSize()).arg(styleName).arg(p.dpiX()).arg(p.dpiY()); +} + +void EmbeddedOptionsControlPrivate::updateDescriptionLabel() +{ + const int profileIndex = m_profileCombo->currentIndex() - profileComboIndexOffset; + if (profileIndex >= 0) { + m_descriptionLabel->setText(description(m_sortedProfiles.at(profileIndex))); + } else { + m_descriptionLabel->clear(); + } +} + +void EmbeddedOptionsControlPrivate::updateState() +{ + const int profileIndex = m_profileCombo->currentIndex() - profileComboIndexOffset; + // Allow for changing/deleting only if it is not in use + bool modifyEnabled = false; + if (profileIndex >= 0) + modifyEnabled = !m_usedProfiles.contains(m_sortedProfiles.at(profileIndex).name()); + m_editButton->setEnabled(modifyEnabled); + m_deleteButton->setEnabled(modifyEnabled); + updateDescriptionLabel(); +} + +void EmbeddedOptionsControlPrivate::slotProfileIndexChanged(int) +{ + updateState(); + m_dirty = true; +} + +// ------------- EmbeddedOptionsControl +EmbeddedOptionsControl::EmbeddedOptionsControl(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_d(new EmbeddedOptionsControlPrivate(core)) +{ + m_d->init(this); +} + +EmbeddedOptionsControl::~EmbeddedOptionsControl() +{ + delete m_d; +} + +void EmbeddedOptionsControl::slotAdd() +{ + m_d->slotAdd(); +} + +void EmbeddedOptionsControl::slotEdit() +{ + m_d->slotEdit(); +} + +void EmbeddedOptionsControl::slotDelete() +{ + m_d->slotDelete(); +} + +void EmbeddedOptionsControl::loadSettings() +{ + m_d->loadSettings(); +} + +void EmbeddedOptionsControl::saveSettings() +{ + m_d->saveSettings(); +} + +void EmbeddedOptionsControl::slotProfileIndexChanged(int i) +{ + m_d->slotProfileIndexChanged(i); +} + +bool EmbeddedOptionsControl::isDirty() const +{ + return m_d->isDirty(); +} + +// EmbeddedOptionsPage: +EmbeddedOptionsPage::EmbeddedOptionsPage(QDesignerFormEditorInterface *core) : + m_core(core) +{ +} + +QString EmbeddedOptionsPage::name() const +{ + //: Tab in preferences dialog + return QCoreApplication::translate("EmbeddedOptionsPage", "Embedded Design"); +} + +QWidget *EmbeddedOptionsPage::createPage(QWidget *parent) +{ + QWidget *optionsWidget = new QWidget(parent); + + QVBoxLayout *optionsVLayout = new QVBoxLayout(); + + //: EmbeddedOptionsControl group box" + QGroupBox *gb = new QGroupBox(QCoreApplication::translate("EmbeddedOptionsPage", "Device Profiles")); + QVBoxLayout *gbVLayout = new QVBoxLayout(); + m_embeddedOptionsControl = new EmbeddedOptionsControl(m_core); + m_embeddedOptionsControl->loadSettings(); + gbVLayout->addWidget(m_embeddedOptionsControl); + gb->setLayout(gbVLayout); + optionsVLayout->addWidget(gb); + + optionsVLayout->addStretch(1); + + // Outer layout to give it horizontal stretch + QHBoxLayout *optionsHLayout = new QHBoxLayout(); + optionsHLayout->addLayout(optionsVLayout); + optionsHLayout->addStretch(1); + optionsWidget->setLayout(optionsHLayout); + return optionsWidget; +} + +void EmbeddedOptionsPage::apply() +{ + if (!m_embeddedOptionsControl || !m_embeddedOptionsControl->isDirty()) + return; + + m_embeddedOptionsControl->saveSettings(); + if (FormWindowManager *fw = qobject_cast(m_core->formWindowManager())) + fw->deviceProfilesChanged(); +} + +void EmbeddedOptionsPage::finish() +{ +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/embeddedoptionspage.h b/src/tools/designer/src/components/formeditor/embeddedoptionspage.h new file mode 100644 index 00000000000..d1d2d0fb73a --- /dev/null +++ b/src/tools/designer/src/components/formeditor/embeddedoptionspage.h @@ -0,0 +1,67 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef EMBEDDEDOPTIONSPAGE_H +#define EMBEDDEDOPTIONSPAGE_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class EmbeddedOptionsControlPrivate; + +/* EmbeddedOptions Control. Presents the user with a list of embedded + * device profiles he can modify/add/delete. */ +class EmbeddedOptionsControl : public QWidget { + Q_DISABLE_COPY_MOVE(EmbeddedOptionsControl) + Q_OBJECT +public: + explicit EmbeddedOptionsControl(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~EmbeddedOptionsControl(); + + bool isDirty() const; + +public slots: + void loadSettings(); + void saveSettings(); + +private slots: + void slotAdd(); + void slotEdit(); + void slotDelete(); + void slotProfileIndexChanged(int); + +private: + friend class EmbeddedOptionsControlPrivate; + + EmbeddedOptionsControlPrivate *m_d; +}; + +// EmbeddedOptionsPage +class EmbeddedOptionsPage : public QDesignerOptionsPageInterface +{ + Q_DISABLE_COPY_MOVE(EmbeddedOptionsPage) +public: + explicit EmbeddedOptionsPage(QDesignerFormEditorInterface *core); + + QString name() const override; + QWidget *createPage(QWidget *parent) override; + void finish() override; + void apply() override; + +private: + QDesignerFormEditorInterface *m_core; + QPointer m_embeddedOptionsControl; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // EMBEDDEDOPTIONSPAGE_H diff --git a/src/tools/designer/src/components/formeditor/formeditor.cpp b/src/tools/designer/src/components/formeditor/formeditor.cpp new file mode 100644 index 00000000000..4d92608f73c --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formeditor.cpp @@ -0,0 +1,158 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formeditor.h" +#include "formeditor_optionspage.h" +#include "embeddedoptionspage.h" +#include "templateoptionspage.h" +#include "metadatabase_p.h" +#include "widgetdatabase_p.h" +#include "widgetfactory_p.h" +#include "formwindowmanager.h" +#include "qmainwindow_container.h" +#include "qmdiarea_container.h" +#include "qwizard_container.h" +#include "default_container.h" +#include "default_layoutdecoration.h" +#include "default_actionprovider.h" +#include "qlayoutwidget_propertysheet.h" +#include "spacer_propertysheet.h" +#include "line_propertysheet.h" +#include "layout_propertysheet.h" +#include "qdesigner_dockwidget_p.h" +#include "qdesigner_stackedbox_p.h" +#include "qdesigner_toolbox_p.h" +#include "qdesigner_tabwidget_p.h" +#include "qtresourcemodel_p.h" +#include "itemview_propertysheet.h" + +// sdk +#include +#include +// shared +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +FormEditor::FormEditor(QObject *parent) : FormEditor(QStringList{}, parent) +{ +} + +FormEditor::FormEditor(const QStringList &pluginPaths, + QObject *parent) + : QDesignerFormEditorInterface(parent) +{ + setIntrospection(new QDesignerIntrospection); + setDialogGui(new DialogGui); + auto *pluginManager = new QDesignerPluginManager(pluginPaths, this); + setPluginManager(pluginManager); + + WidgetDataBase *widgetDatabase = new WidgetDataBase(this, this); + setWidgetDataBase(widgetDatabase); + + MetaDataBase *metaDataBase = new MetaDataBase(this, this); + setMetaDataBase(metaDataBase); + + WidgetFactory *widgetFactory = new WidgetFactory(this, this); + setWidgetFactory(widgetFactory); + + FormWindowManager *formWindowManager = new FormWindowManager(this, this); + setFormManager(formWindowManager); + connect(formWindowManager, &QDesignerFormWindowManagerInterface::formWindowAdded, + widgetFactory, &WidgetFactory::formWindowAdded); + connect(formWindowManager, &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + widgetFactory, &WidgetFactory::activeFormWindowChanged); + + QExtensionManager *mgr = new QExtensionManager(this); + const QString containerExtensionId = Q_TYPEID(QDesignerContainerExtension); + + QDesignerStackedWidgetContainerFactory::registerExtension(mgr, containerExtensionId); + QDesignerTabWidgetContainerFactory::registerExtension(mgr, containerExtensionId); + QDesignerToolBoxContainerFactory::registerExtension(mgr, containerExtensionId); + QMainWindowContainerFactory::registerExtension(mgr, containerExtensionId); + QDockWidgetContainerFactory::registerExtension(mgr, containerExtensionId); + QScrollAreaContainerFactory::registerExtension(mgr, containerExtensionId); + QMdiAreaContainerFactory::registerExtension(mgr, containerExtensionId); + QWizardContainerFactory::registerExtension(mgr, containerExtensionId); + + mgr->registerExtensions(new QDesignerLayoutDecorationFactory(mgr), + Q_TYPEID(QDesignerLayoutDecorationExtension)); + + const QString actionProviderExtensionId = Q_TYPEID(QDesignerActionProviderExtension); + QToolBarActionProviderFactory::registerExtension(mgr, actionProviderExtensionId); + QMenuBarActionProviderFactory::registerExtension(mgr, actionProviderExtensionId); + QMenuActionProviderFactory::registerExtension(mgr, actionProviderExtensionId); + + QDesignerDefaultPropertySheetFactory::registerExtension(mgr); + QDockWidgetPropertySheetFactory::registerExtension(mgr); + QLayoutWidgetPropertySheetFactory::registerExtension(mgr); + SpacerPropertySheetFactory::registerExtension(mgr); + LinePropertySheetFactory::registerExtension(mgr); + LayoutPropertySheetFactory::registerExtension(mgr); + QStackedWidgetPropertySheetFactory::registerExtension(mgr); + QToolBoxWidgetPropertySheetFactory::registerExtension(mgr); + QTabWidgetPropertySheetFactory::registerExtension(mgr); + QMdiAreaPropertySheetFactory::registerExtension(mgr); + QWizardPagePropertySheetFactory::registerExtension(mgr); + QWizardPropertySheetFactory::registerExtension(mgr); + + QTreeViewPropertySheetFactory::registerExtension(mgr); + QTableViewPropertySheetFactory::registerExtension(mgr); + + QDesignerTaskMenuFactory::registerExtension(mgr, u"QDesignerInternalTaskMenuExtension"_s); + + mgr->registerExtensions(new QDesignerMemberSheetFactory(mgr), + Q_TYPEID(QDesignerMemberSheetExtension)); + + setExtensionManager(mgr); + + setPromotion(new QDesignerPromotion(this)); + + QtResourceModel *resourceModel = new QtResourceModel(this); + setResourceModel(resourceModel); + connect(resourceModel, &QtResourceModel::qrcFileModifiedExternally, + this, &FormEditor::slotQrcFileChangedExternally); + + QList optionsPages; + optionsPages << new TemplateOptionsPage(this) << new FormEditorOptionsPage(this) << new EmbeddedOptionsPage(this); + setOptionsPages(optionsPages); + + setSettingsManager(new QDesignerQSettings()); +} + +FormEditor::~FormEditor() = default; + +void FormEditor::slotQrcFileChangedExternally(const QString &path) +{ + if (!integration()) + return; + + QDesignerIntegration::ResourceFileWatcherBehaviour behaviour = integration()->resourceFileWatcherBehaviour(); + if (behaviour == QDesignerIntegration::NoResourceFileWatcher) + return; + if (behaviour == QDesignerIntegration::PromptToReloadResourceFile) { + QMessageBox::StandardButton button = dialogGui()->message(topLevel(), QDesignerDialogGuiInterface::FileChangedMessage, QMessageBox::Warning, + tr("Resource File Changed"), + tr("The file \"%1\" has changed outside Designer. Do you want to reload it?").arg(path), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (button != QMessageBox::Yes) + return; + } + + resourceModel()->reload(path); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/formeditor.h b/src/tools/designer/src/components/formeditor/formeditor.h new file mode 100644 index 00000000000..483bd6b69bc --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formeditor.h @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMEDITOR_H +#define FORMEDITOR_H + +#include "formeditor_global.h" + +#include + +QT_BEGIN_NAMESPACE + +class QObject; + +namespace qdesigner_internal { + +class QT_FORMEDITOR_EXPORT FormEditor: public QDesignerFormEditorInterface +{ + Q_OBJECT +public: + FormEditor(QObject *parent = nullptr); + FormEditor(const QStringList &pluginPaths, + QObject *parent = nullptr); + ~FormEditor() override; +public slots: + void slotQrcFileChangedExternally(const QString &path); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMEDITOR_H diff --git a/src/tools/designer/src/components/formeditor/formeditor_global.h b/src/tools/designer/src/components/formeditor/formeditor_global.h new file mode 100644 index 00000000000..37af044beca --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formeditor_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMEDITOR_GLOBAL_H +#define FORMEDITOR_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_FORMEDITOR_LIBRARY +# define QT_FORMEDITOR_EXPORT +#else +# define QT_FORMEDITOR_EXPORT +#endif +#else +#define QT_FORMEDITOR_EXPORT +#endif + +#endif // FORMEDITOR_GLOBAL_H diff --git a/src/tools/designer/src/components/formeditor/formeditor_optionspage.cpp b/src/tools/designer/src/components/formeditor/formeditor_optionspage.cpp new file mode 100644 index 00000000000..ffd93f2637b --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formeditor_optionspage.cpp @@ -0,0 +1,175 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formeditor_optionspage.h" + +// shared +#include "formwindowbase_p.h" +#include "gridpanel_p.h" +#include "grid_p.h" +#include "previewconfigurationwidget_p.h" +#include "shared_settings_p.h" +#include "zoomwidget_p.h" +#include + +// SDK +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Zoom, currently for preview only +class ZoomSettingsWidget : public QGroupBox { + Q_DISABLE_COPY_MOVE(ZoomSettingsWidget) +public: + explicit ZoomSettingsWidget(QWidget *parent = nullptr); + + void fromSettings(const QDesignerSharedSettings &s); + void toSettings(QDesignerSharedSettings &s) const; + +private: + QComboBox *m_zoomCombo; +}; + +ZoomSettingsWidget::ZoomSettingsWidget(QWidget *parent) : + QGroupBox(parent), + m_zoomCombo(new QComboBox) +{ + m_zoomCombo->setEditable(false); + const QList &zoomValues = ZoomMenu::zoomValues(); + for (int z : zoomValues) { + //: Zoom percentage + m_zoomCombo->addItem(QCoreApplication::translate("FormEditorOptionsPage", "%1 %").arg(z), QVariant(z)); + } + + // Layout + setCheckable(true); + setTitle(QCoreApplication::translate("FormEditorOptionsPage", "Preview Zoom")); + QFormLayout *lt = new QFormLayout; + lt->addRow(QCoreApplication::translate("FormEditorOptionsPage", "Default Zoom"), m_zoomCombo); + setLayout(lt); +} + +void ZoomSettingsWidget::fromSettings(const QDesignerSharedSettings &s) +{ + setChecked(s.zoomEnabled()); + const int idx = m_zoomCombo->findData(QVariant(s.zoom())); + m_zoomCombo->setCurrentIndex(qMax(0, idx)); +} + +void ZoomSettingsWidget::toSettings(QDesignerSharedSettings &s) const +{ + s.setZoomEnabled(isChecked()); + const int zoom = m_zoomCombo->itemData(m_zoomCombo->currentIndex()).toInt(); + s.setZoom(zoom); +} + + + +// FormEditorOptionsPage: +FormEditorOptionsPage::FormEditorOptionsPage(QDesignerFormEditorInterface *core) + : m_core(core) +{ +} + +QString FormEditorOptionsPage::name() const +{ + //: Tab in preferences dialog + return QCoreApplication::translate("FormEditorOptionsPage", "Forms"); +} + +QWidget *FormEditorOptionsPage::createPage(QWidget *parent) +{ + QWidget *optionsWidget = new QWidget(parent); + + const QDesignerSharedSettings settings(m_core); + m_previewConf = new PreviewConfigurationWidget(m_core); + m_zoomSettingsWidget = new ZoomSettingsWidget; + m_zoomSettingsWidget->fromSettings(settings); + + m_defaultGridConf = new GridPanel(); + m_defaultGridConf->setTitle(QCoreApplication::translate("FormEditorOptionsPage", "Default Grid")); + m_defaultGridConf->setGrid(settings.defaultGrid()); + + const QString namingTitle = + QCoreApplication::translate("FormEditorOptionsPage", "Object Naming Convention"); + QGroupBox *namingGroupBox = new QGroupBox(namingTitle); + const QString namingToolTip = + QCoreApplication::translate("FormEditorOptionsPage", + "Naming convention used for generating action object names from their text"); + namingGroupBox->setToolTip(namingToolTip); + QHBoxLayout *namingHLayout = new QHBoxLayout(namingGroupBox); + m_namingComboBox = new QComboBox; + m_namingComboBox->setToolTip(namingToolTip); + QStringList items; // matching ActionEditor::NamingMode + items << QCoreApplication::translate("FormEditorOptionsPage", "Camel Case") + << QCoreApplication::translate("FormEditorOptionsPage", "Underscore"); + m_namingComboBox->addItems(items); + m_namingComboBox->setCurrentIndex(settings.objectNamingMode()); + namingHLayout->addWidget(m_namingComboBox.data()); + + QVBoxLayout *optionsVLayout = new QVBoxLayout(); + optionsVLayout->addWidget(m_defaultGridConf); + optionsVLayout->addWidget(m_previewConf); + optionsVLayout->addWidget(m_zoomSettingsWidget); + optionsVLayout->addWidget(namingGroupBox); + optionsVLayout->addStretch(1); + + // Outer layout to give it horizontal stretch + QHBoxLayout *optionsHLayout = new QHBoxLayout(); + optionsHLayout->addLayout(optionsVLayout); + optionsHLayout->addStretch(1); + optionsWidget->setLayout(optionsHLayout); + + return optionsWidget; +} + +void FormEditorOptionsPage::apply() +{ + QDesignerSharedSettings settings(m_core); + if (m_defaultGridConf) { + const Grid defaultGrid = m_defaultGridConf->grid(); + settings.setDefaultGrid(defaultGrid); + + FormWindowBase::setDefaultDesignerGrid(defaultGrid); + // Update grid settings in all existing form windows + QDesignerFormWindowManagerInterface *fwm = m_core->formWindowManager(); + if (const int numWindows = fwm->formWindowCount()) { + for (int i = 0; i < numWindows; i++) + if (qdesigner_internal::FormWindowBase *fwb + = qobject_cast( fwm->formWindow(i))) + if (!fwb->hasFormGrid()) + fwb->setDesignerGrid(defaultGrid); + } + } + if (m_previewConf) { + m_previewConf->saveState(); + } + + if (m_zoomSettingsWidget) + m_zoomSettingsWidget->toSettings(settings); + + if (m_namingComboBox) { + const ObjectNamingMode namingMode + = static_cast(m_namingComboBox->currentIndex()); + settings.setObjectNamingMode(namingMode); + ActionEditor::setObjectNamingMode(namingMode); + } +} + +void FormEditorOptionsPage::finish() +{ +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/formeditor_optionspage.h b/src/tools/designer/src/components/formeditor/formeditor_optionspage.h new file mode 100644 index 00000000000..69758d28eb5 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formeditor_optionspage.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMEDITOR_OPTIONSPAGE_H +#define FORMEDITOR_OPTIONSPAGE_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QComboBox; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class PreviewConfigurationWidget; +class GridPanel; +class ZoomSettingsWidget; + +class FormEditorOptionsPage : public QDesignerOptionsPageInterface +{ +public: + explicit FormEditorOptionsPage(QDesignerFormEditorInterface *core); + + QString name() const override; + QWidget *createPage(QWidget *parent) override; + void apply() override; + void finish() override; + +private: + QDesignerFormEditorInterface *m_core; + QPointer m_previewConf; + QPointer m_defaultGridConf; + QPointer m_zoomSettingsWidget; + QPointer m_namingComboBox; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMEDITOR_OPTIONSPAGE_H diff --git a/src/tools/designer/src/components/formeditor/formwindow.cpp b/src/tools/designer/src/components/formeditor/formwindow.cpp new file mode 100644 index 00000000000..cf3e9dcc958 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindow.cpp @@ -0,0 +1,2943 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindow.h" +#include "formeditor.h" +#include "formwindow_dnditem.h" +#include "formwindow_widgetstack.h" +#include "formwindowcursor.h" +#include "formwindowmanager.h" +#include "tool_widgeteditor.h" +#include "widgetselection.h" +#include "qtresourcemodel_p.h" +#include "widgetfactory_p.h" + +// shared +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#if QT_CONFIG(clipboard) +# include +#endif +#include +#include + +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QWidget*) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { +class BlockSelection +{ + Q_DISABLE_COPY_MOVE(BlockSelection) +public: + BlockSelection(qdesigner_internal::FormWindow *fw) + : m_formWindow(fw), + m_blocked(m_formWindow->blockSelectionChanged(true)) + { + } + + ~BlockSelection() + { + if (m_formWindow) + m_formWindow->blockSelectionChanged(m_blocked); + } + +private: + QPointer m_formWindow; + const bool m_blocked; +}; + +enum { debugFormWindow = 0 }; +} + +namespace qdesigner_internal { + +// ------------------------ FormWindow::Selection +// Maintains a pool of WidgetSelections to be used for selected widgets. + +class FormWindow::Selection +{ + Q_DISABLE_COPY_MOVE(Selection) +public: + Selection(); + ~Selection(); + + // Clear + void clear(); + + // Also clear out the pool. Call if reparenting of the main container occurs. + void clearSelectionPool(); + + void repaintSelection(QWidget *w); + void repaintSelection(); + + bool isWidgetSelected(QWidget *w) const; + QWidgetList selectedWidgets() const; + + WidgetSelection *addWidget(FormWindow* fw, QWidget *w); + // remove widget, return new current widget or 0 + QWidget* removeWidget(QWidget *w); + + void raiseList(const QWidgetList& l); + void raiseWidget(QWidget *w); + + void updateGeometry(QWidget *w); + + void hide(QWidget *w); + void show(QWidget *w); + +private: + + using SelectionPool = QList; + SelectionPool m_selectionPool; + + QHash m_usedSelections; +}; + +FormWindow::Selection::Selection() = default; + +FormWindow::Selection::~Selection() +{ + clearSelectionPool(); +} + +void FormWindow::Selection::clear() +{ + if (!m_usedSelections.isEmpty()) { + for (auto it = m_usedSelections.begin(), mend = m_usedSelections.end(); it != mend; ++it) + it.value()->setWidget(nullptr); + m_usedSelections.clear(); + } +} + +void FormWindow::Selection::clearSelectionPool() +{ + clear(); + qDeleteAll(m_selectionPool); + m_selectionPool.clear(); +} + +WidgetSelection *FormWindow::Selection::addWidget(FormWindow* fw, QWidget *w) +{ + WidgetSelection *rc = m_usedSelections.value(w); + if (rc != nullptr) { + rc->show(); + rc->updateActive(); + return rc; + } + // find a free one in the pool + for (auto *s : std::as_const(m_selectionPool)) { + if (!s->isUsed()) { + rc = s; + break; + } + } + + if (rc == nullptr) { + rc = new WidgetSelection(fw); + m_selectionPool.push_back(rc); + } + + m_usedSelections.insert(w, rc); + rc->setWidget(w); + return rc; +} + +QWidget* FormWindow::Selection::removeWidget(QWidget *w) +{ + WidgetSelection *s = m_usedSelections.value(w); + if (!s) + return w; + + s->setWidget(nullptr); + m_usedSelections.remove(w); + + if (m_usedSelections.isEmpty()) + return nullptr; + + return (*m_usedSelections.begin())->widget(); +} + +void FormWindow::Selection::repaintSelection(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) + s->update(); +} + +void FormWindow::Selection::repaintSelection() +{ + for (auto it = m_usedSelections.begin(), mend = m_usedSelections.end(); it != mend; ++it) + it.value()->update(); +} + +bool FormWindow::Selection::isWidgetSelected(QWidget *w) const{ + return m_usedSelections.contains(w); +} + +QWidgetList FormWindow::Selection::selectedWidgets() const +{ + return m_usedSelections.keys(); +} + +void FormWindow::Selection::raiseList(const QWidgetList& l) +{ + for (auto it = m_usedSelections.constBegin(), mend = m_usedSelections.constEnd(); it != mend; ++it) { + WidgetSelection *w = it.value(); + if (l.contains(w->widget())) + w->show(); + } +} + +void FormWindow::Selection::raiseWidget(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) + s->show(); +} + +void FormWindow::Selection::updateGeometry(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) { + s->updateGeometry(); + } +} + +void FormWindow::Selection::hide(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) + s->hide(); +} + +void FormWindow::Selection::show(QWidget *w) +{ + if (WidgetSelection *s = m_usedSelections.value(w)) + s->show(); +} + +// ------------------------ FormWindow +FormWindow::FormWindow(FormEditor *core, QWidget *parent, Qt::WindowFlags flags) : + FormWindowBase(core, parent, flags), + m_mouseState(NoMouseState), + m_core(core), + m_selection(new Selection), + m_widgetStack(new FormWindowWidgetStack(this)), + m_contextMenuPosition(-1, -1) +{ + // Apply settings to formcontainer + deviceProfile().apply(core, m_widgetStack->formContainer(), qdesigner_internal::DeviceProfile::ApplyFormParent); + + setLayout(m_widgetStack->layout()); + init(); + + m_cursor = new FormWindowCursor(this, this); + + core->formWindowManager()->addFormWindow(this); + + setDirty(false); + setAcceptDrops(true); +} + +FormWindow::~FormWindow() +{ + Q_ASSERT(core() != nullptr); + Q_ASSERT(core()->metaDataBase() != nullptr); + Q_ASSERT(core()->formWindowManager() != nullptr); + + core()->formWindowManager()->removeFormWindow(this); + core()->metaDataBase()->remove(this); + + const QWidgetList &l = widgets(); + for (QWidget *w : l) + core()->metaDataBase()->remove(w); + + m_widgetStack = nullptr; + m_rubberBand = nullptr; + if (resourceSet()) + core()->resourceModel()->removeResourceSet(resourceSet()); + delete m_selection; + + if (FormWindowManager *manager = qobject_cast (core()->formWindowManager())) + manager->undoGroup()->removeStack(&m_undoStack); + m_undoStack.disconnect(); +} + +QDesignerFormEditorInterface *FormWindow::core() const +{ + return m_core; +} + +QDesignerFormWindowCursorInterface *FormWindow::cursor() const +{ + return m_cursor; +} + +void FormWindow::updateWidgets() +{ + if (!m_mainContainer) + return; +} + +int FormWindow::widgetDepth(const QWidget *w) +{ + int d = -1; + while (w && !w->isWindow()) { + d++; + w = w->parentWidget(); + } + + return d; +} + +bool FormWindow::isChildOf(const QWidget *c, const QWidget *p) +{ + while (c) { + if (c == p) + return true; + c = c->parentWidget(); + } + return false; +} + +void FormWindow::setCursorToAll(const QCursor &c, QWidget *start) +{ +#if QT_CONFIG(cursor) + start->setCursor(c); + const QWidgetList widgets = start->findChildren(); + for (QWidget *widget : widgets) { + if (!qobject_cast(widget)) { + widget->setCursor(c); + } + } +#endif +} + +void FormWindow::init() +{ + if (FormWindowManager *manager = qobject_cast (core()->formWindowManager())) { + manager->undoGroup()->addStack(&m_undoStack); + } + + m_blockSelectionChanged = false; + + m_defaultMargin = INT_MIN; + m_defaultSpacing = INT_MIN; + + connect(m_widgetStack, &FormWindowWidgetStack::currentToolChanged, + this, &QDesignerFormWindowInterface::toolChanged); + + m_selectionChangedTimer = new QTimer(this); + m_selectionChangedTimer->setSingleShot(true); + connect(m_selectionChangedTimer, &QTimer::timeout, this, + &FormWindow::selectionChangedTimerDone); + + m_checkSelectionTimer = new QTimer(this); + m_checkSelectionTimer->setSingleShot(true); + connect(m_checkSelectionTimer, &QTimer::timeout, + this, &FormWindow::checkSelectionNow); + + m_geometryChangedTimer = new QTimer(this); + m_geometryChangedTimer->setSingleShot(true); + connect(m_geometryChangedTimer, &QTimer::timeout, + this, &QDesignerFormWindowInterface::geometryChanged); + + m_rubberBand = nullptr; + + setFocusPolicy(Qt::StrongFocus); + + m_mainContainer = nullptr; + m_currentWidget = nullptr; + + connect(&m_undoStack, &QUndoStack::indexChanged, + this, &QDesignerFormWindowInterface::changed); + connect(&m_undoStack, &QUndoStack::cleanChanged, + this, &FormWindow::slotCleanChanged); + connect(this, &QDesignerFormWindowInterface::changed, + this, &FormWindow::checkSelection); + + core()->metaDataBase()->add(this); + + initializeCoreTools(); + + QAction *a = new QAction(this); + a->setText(tr("Edit contents")); + a->setShortcut(tr("F2")); + connect(a, &QAction::triggered, this, &FormWindow::editContents); + addAction(a); +} + +QWidget *FormWindow::mainContainer() const +{ + return m_mainContainer; +} + + +void FormWindow::clearMainContainer() +{ + if (m_mainContainer) { + setCurrentTool(0); + m_widgetStack->setMainContainer(nullptr); + core()->metaDataBase()->remove(m_mainContainer); + unmanageWidget(m_mainContainer); + delete m_mainContainer; + m_mainContainer = nullptr; + } +} + +void FormWindow::setMainContainer(QWidget *w) +{ + if (w == m_mainContainer) { + // nothing to do + return; + } + + clearMainContainer(); + + m_mainContainer = w; + const QSize sz = m_mainContainer->size(); + + m_widgetStack->setMainContainer(m_mainContainer); + m_widgetStack->setCurrentTool(m_widgetEditor); + + setCurrentWidget(m_mainContainer); + manageWidget(m_mainContainer); + + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), m_mainContainer)) { + sheet->setVisible(sheet->indexOf(u"windowTitle"_s), true); + sheet->setVisible(sheet->indexOf(u"windowIcon"_s), true); + sheet->setVisible(sheet->indexOf(u"windowModality"_s), true); + sheet->setVisible(sheet->indexOf(u"windowOpacity"_s), true); + sheet->setVisible(sheet->indexOf(u"windowFilePath"_s), true); + // ### generalize + } + + m_mainContainer->setFocusPolicy(Qt::StrongFocus); + m_mainContainer->resize(sz); + + emit mainContainerChanged(m_mainContainer); +} + +QWidget *FormWindow::findTargetContainer(QWidget *widget) const +{ + Q_ASSERT(widget); + + while (QWidget *parentWidget = widget->parentWidget()) { + if (LayoutInfo::layoutType(m_core, parentWidget) == LayoutInfo::NoLayout && isManaged(widget)) + return widget; + + widget = parentWidget; + } + + return mainContainer(); +} + +static inline void clearObjectInspectorSelection(const QDesignerFormEditorInterface *core) +{ + if (QDesignerObjectInspector *oi = qobject_cast(core->objectInspector())) + oi->clearSelection(); +} + +// Find a parent of a desired selection state +static QWidget *findSelectedParent(QDesignerFormWindowInterface *fw, const QWidget *w, bool selected) +{ + const QDesignerFormWindowCursorInterface *cursor = fw->cursor(); + QWidget *mainContainer = fw->mainContainer(); + for (QWidget *p = w->parentWidget(); p && p != mainContainer; p = p->parentWidget()) + if (fw->isManaged(p)) + if (cursor->isWidgetSelected(p) == selected) + return p; + return nullptr; +} + +// Mouse modifiers. + +enum MouseFlags { ToggleSelectionModifier = 0x1, CycleParentModifier=0x2, CopyDragModifier=0x4 }; + +static inline unsigned mouseFlags(Qt::KeyboardModifiers mod) +{ + switch (mod) { + case Qt::ShiftModifier: + return CycleParentModifier; + break; +#ifdef Q_OS_MACOS + case Qt::AltModifier: // "Alt" or "option" key on Mac means copy + return CopyDragModifier; +#endif + case Qt::ControlModifier: + return CopyDragModifier|ToggleSelectionModifier; + break; + default: + break; + } + return 0; +} + +// Handle the click selection: Do toggling/cycling +// of parents according to the modifiers. +void FormWindow::handleClickSelection(QWidget *managedWidget, unsigned mouseMode) +{ + const bool sameWidget = managedWidget == m_lastClickedWidget; + m_lastClickedWidget = managedWidget; + + const bool selected = isWidgetSelected(managedWidget); + if (debugFormWindow) + qDebug() << "handleClickSelection" << managedWidget << " same=" << sameWidget << " mouse= " << mouseMode << " selected=" << selected; + + // // toggle selection state of widget + if (mouseMode & ToggleSelectionModifier) { + selectWidget(managedWidget, !selected); + return; + } + + QWidget *selectionCandidate = nullptr; + // Hierarchy cycling: If the same widget clicked again: Attempt to cycle + // trough the hierarchy. Find the next currently selected parent + if (sameWidget && (mouseMode & CycleParentModifier)) + if (QWidget *currentlySelectedParent = selected ? managedWidget : findSelectedParent(this, managedWidget, true)) + selectionCandidate = findSelectedParent(this, currentlySelectedParent, false); + // Not the same widget, list wrapped over or there was no unselected parent + if (!selectionCandidate && !selected) + selectionCandidate = managedWidget; + + if (selectionCandidate) + selectSingleWidget(selectionCandidate); +} + +void FormWindow::selectSingleWidget(QWidget *w) +{ + clearSelection(false); + selectWidget(w, true); + raiseChildSelections(w); +} + +bool FormWindow::handleMousePressEvent(QWidget * widget, QWidget *managedWidget, QMouseEvent *e) +{ + m_mouseState = NoMouseState; + m_startPos = QPoint(); + e->accept(); + + BlockSelection blocker(this); + + if (core()->formWindowManager()->activeFormWindow() != this) + core()->formWindowManager()->setActiveFormWindow(this); + + const Qt::MouseButtons buttons = e->buttons(); + if (buttons != Qt::LeftButton && buttons != Qt::MiddleButton) + return true; + + m_startPos = mapFromGlobal(e->globalPosition().toPoint()); + + if (debugFormWindow) + qDebug() << "handleMousePressEvent:" << widget << ',' << managedWidget; + + if (buttons == Qt::MiddleButton || isMainContainer(managedWidget)) { // press was on the formwindow + clearObjectInspectorSelection(m_core); // We might have a toolbar or non-widget selected in the object inspector. + clearSelection(false); + + m_mouseState = MouseDrawRubber; + m_currRect = QRect(); + startRectDraw(mapFromGlobal(e->globalPosition().toPoint()), this, Rubber); + return true; + } + if (buttons != Qt::LeftButton) + return true; + + const unsigned mouseMode = mouseFlags(e->modifiers()); + + /* Normally, we want to be able to click /select-on-press to drag away + * the widget in the next step. However, in the case of a widget which + * itself or whose parent is selected, we defer the selection to the + * release event. + * This is to prevent children from being dragged away from layouts + * when their layouts are selected and one wants to move the layout. + * Note that toggle selection is only deferred if the widget is already + * selected, so, it is still possible to just Ctrl+Click and CopyDrag. */ + const bool deferSelection = isWidgetSelected(managedWidget) || findSelectedParent(this, managedWidget, true); + if (deferSelection) { + m_mouseState = MouseDeferredSelection; + } else { + // Cycle the parent unless we explicitly want toggle + const unsigned effectiveMouseMode = (mouseMode & ToggleSelectionModifier) ? mouseMode : static_cast(CycleParentModifier); + handleClickSelection(managedWidget, effectiveMouseMode); + } + return true; +} + +// We can drag widget in managed layouts except splitter. +static bool canDragWidgetInLayout(const QDesignerFormEditorInterface *core, QWidget *w) +{ + bool managed; + const LayoutInfo::Type type = LayoutInfo::laidoutWidgetType(core ,w, &managed); + if (!managed) + return false; + switch (type) { + case LayoutInfo::NoLayout: + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: + return false; + default: + break; + } + return true; +} + +bool FormWindow::handleMouseMoveEvent(QWidget *, QWidget *, QMouseEvent *e) +{ + e->accept(); + if (m_startPos.isNull()) + return true; + + const QPoint pos = mapFromGlobal(e->globalPosition().toPoint()); + + switch (m_mouseState) { + case MouseDrawRubber: // Rubber band with left/middle mouse + continueRectDraw(pos, this, Rubber); + return true; + case MouseMoveDrag: // Spurious move event after drag started? + return true; + default: + break; + } + + if (e->buttons() != Qt::LeftButton) + return true; + + const bool canStartDrag = (m_startPos - pos).manhattanLength() > QApplication::startDragDistance(); + + if (!canStartDrag) // nothing to do + return true; + + m_mouseState = MouseMoveDrag; + const bool blocked = blockSelectionChanged(true); + + QWidgetList sel = selectedWidgets(); + const QWidgetList originalSelection = sel; + simplifySelection(&sel); + + QSet widget_set; + + for (QWidget *child : std::as_const(sel)) { // Move parent layout or container? + QWidget *current = child; + + bool done = false; + while (!isMainContainer(current) && !done) { + if (!isManaged(current)) { + current = current->parentWidget(); + continue; + } + if (LayoutInfo::isWidgetLaidout(core(), current)) { + // Go up to parent of layout if shift pressed, else do that only for splitters + if (!canDragWidgetInLayout(core(), current)) { + current = current->parentWidget(); + continue; + } + } + done = true; + } + + if (current == mainContainer()) + continue; + + widget_set.insert(current); + } + + sel = widget_set.values(); + QDesignerFormWindowCursorInterface *c = cursor(); + QWidget *current = c->current(); + if (sel.contains(current)) { + sel.removeAll(current); + sel.prepend(current); + } + + QList item_list; + const QPoint globalPos = mapToGlobal(m_startPos); + const QDesignerDnDItemInterface::DropType dropType = (mouseFlags(e->modifiers()) & CopyDragModifier) ? + QDesignerDnDItemInterface::CopyDrop : QDesignerDnDItemInterface::MoveDrop; + for (QWidget *widget : std::as_const(sel)) { + item_list.append(new FormWindowDnDItem(dropType, this, widget, globalPos)); + if (dropType == QDesignerDnDItemInterface::MoveDrop) { + m_selection->hide(widget); + widget->hide(); + } + } + + // In case when we have reduced the selection (by calling simplifySelection() + // beforehand) we still need to hide selection handles for children widgets + for (auto *widget : originalSelection) + m_selection->hide(widget); + + blockSelectionChanged(blocked); + + if (!sel.isEmpty()) // reshow selection? + if (QDesignerMimeData::execDrag(item_list, core()->topLevel()) == Qt::IgnoreAction && dropType == QDesignerDnDItemInterface::MoveDrop) + for (QWidget *widget : std::as_const(sel)) + m_selection->show(widget); + + m_startPos = QPoint(); + + return true; +} + +bool FormWindow::handleMouseReleaseEvent(QWidget *w, QWidget *mw, QMouseEvent *e) +{ + const MouseState oldState = m_mouseState; + m_mouseState = NoMouseState; + + if (debugFormWindow) + qDebug() << "handleMouseeleaseEvent:" << w << ',' << mw << "state=" << oldState; + + if (oldState == MouseDoubleClicked) + return true; + + e->accept(); + + switch (oldState) { + case MouseDrawRubber: { // we were drawing a rubber selection + endRectDraw(); // get rid of the rectangle + const bool blocked = blockSelectionChanged(true); + selectWidgets(); // select widgets which intersect the rect + blockSelectionChanged(blocked); + } + break; + // Deferred select: Select the child here unless the parent was moved. + case MouseDeferredSelection: + handleClickSelection(mw, mouseFlags(e->modifiers())); + break; + default: + break; + } + + m_startPos = QPoint(); + + /* Inform about selection changes (left/mid or context menu). Also triggers + * in the case of an empty rubber drag that cleared the selection in + * MousePressEvent. */ + switch (e->button()) { + case Qt::LeftButton: + case Qt::MiddleButton: + case Qt::RightButton: + emitSelectionChanged(); + break; + default: + break; + } + + return true; +} + +void FormWindow::checkPreviewGeometry(QRect &r) +{ + if (!rect().contains(r)) { + if (r.left() < rect().left()) + r.moveTopLeft(QPoint(0, r.top())); + if (r.right() > rect().right()) + r.moveBottomRight(QPoint(rect().right(), r.bottom())); + if (r.top() < rect().top()) + r.moveTopLeft(QPoint(r.left(), rect().top())); + if (r.bottom() > rect().bottom()) + r.moveBottomRight(QPoint(r.right(), rect().bottom())); + } +} + +void FormWindow::startRectDraw(QPoint pos, QWidget *, RectType t) +{ + m_rectAnchor = (t == Insert) ? designerGrid().snapPoint(pos) : pos; + + m_currRect = QRect(m_rectAnchor, QSize(0, 0)); + if (!m_rubberBand) + m_rubberBand = new QRubberBand(QRubberBand::Rectangle, this); + m_rubberBand->setGeometry(m_currRect); + m_rubberBand->show(); +} + +void FormWindow::continueRectDraw(QPoint pos, QWidget *, RectType t) +{ + const QPoint p2 = (t == Insert) ? designerGrid().snapPoint(pos) : pos; + + QRect r(m_rectAnchor, p2); + r = r.normalized(); + + if (m_currRect == r) + return; + + if (r.width() > 1 || r.height() > 1) { + m_currRect = r; + if (m_rubberBand) + m_rubberBand->setGeometry(m_currRect); + } +} + +void FormWindow::endRectDraw() +{ + if (m_rubberBand) { + delete m_rubberBand; + m_rubberBand = nullptr; + } +} + +QWidget *FormWindow::currentWidget() const +{ + return m_currentWidget; +} + +bool FormWindow::setCurrentWidget(QWidget *currentWidget) +{ + if (debugFormWindow) + qDebug() << "setCurrentWidget:" << m_currentWidget << " --> " << currentWidget; + if (currentWidget == m_currentWidget) + return false; + // repaint the old widget unless it is the main window + if (m_currentWidget && m_currentWidget != mainContainer()) { + m_selection->repaintSelection(m_currentWidget); + } + // set new and repaint + m_currentWidget = currentWidget; + if (m_currentWidget && m_currentWidget != mainContainer()) { + m_selection->repaintSelection(m_currentWidget); + } + return true; +} + +void FormWindow::selectWidget(QWidget* w, bool select) +{ + if (trySelectWidget(w, select)) + emitSelectionChanged(); +} + +// Selects a widget and determines the new current one. Returns true if a change occurs. +bool FormWindow::trySelectWidget(QWidget *w, bool select) +{ + if (debugFormWindow) + qDebug() << "trySelectWidget:" << w << select; + if (!isManaged(w) && !isCentralWidget(w)) + return false; + + if (!select && !isWidgetSelected(w)) + return false; + + if (!mainContainer()) + return false; + + if (isMainContainer(w) || isCentralWidget(w)) { + setCurrentWidget(mainContainer()); + return true; + } + + if (select) { + setCurrentWidget(w); + m_selection->addWidget(this, w); + } else { + QWidget *newCurrent = m_selection->removeWidget(w); + if (!newCurrent) + newCurrent = mainContainer(); + setCurrentWidget(newCurrent); + } + return true; +} + +void FormWindow::clearSelection(bool changePropertyDisplay) +{ + if (debugFormWindow) + qDebug() << "clearSelection(" << changePropertyDisplay << ')'; + // At all events, we need a current widget. + m_selection->clear(); + setCurrentWidget(mainContainer()); + + if (changePropertyDisplay) + emitSelectionChanged(); +} + +void FormWindow::emitSelectionChanged() +{ + if (m_blockSelectionChanged) // nothing to do + return; + + m_selectionChangedTimer->start(0); +} + +void FormWindow::selectionChangedTimerDone() +{ + emit selectionChanged(); +} + +bool FormWindow::isWidgetSelected(QWidget *w) const +{ + return m_selection->isWidgetSelected(w); +} + +bool FormWindow::isMainContainer(const QWidget *w) const +{ + return w && (w == this || w == mainContainer()); +} + +void FormWindow::updateChildSelections(QWidget *w) +{ + const QWidgetList l = w->findChildren(); + for (auto *w : l) { + if (isManaged(w)) + updateSelection(w); + } +} + +void FormWindow::repaintSelection() +{ + m_selection->repaintSelection(); +} + +void FormWindow::raiseSelection(QWidget *w) +{ + m_selection->raiseWidget(w); +} + +void FormWindow::updateSelection(QWidget *w) +{ + if (!w->isVisibleTo(this)) { + selectWidget(w, false); + } else { + m_selection->updateGeometry(w); + } +} + +QWidget *FormWindow::designerWidget(QWidget *w) const +{ + while ((w && !isMainContainer(w) && !isManaged(w)) || isCentralWidget(w)) + w = w->parentWidget(); + + return w; +} + +bool FormWindow::isCentralWidget(QWidget *w) const +{ + if (QMainWindow *mainWindow = qobject_cast(mainContainer())) + return w == mainWindow->centralWidget(); + + return false; +} + +void FormWindow::ensureUniqueObjectName(QObject *object) +{ + QString name = object->objectName(); + if (name.isEmpty()) { + QDesignerWidgetDataBaseInterface *db = core()->widgetDataBase(); + if (QDesignerWidgetDataBaseItemInterface *item = db->item(db->indexOfObject(object))) + name = qdesigner_internal::qtify(item->name()); + } + unify(object, name, true); + object->setObjectName(name); +} + +template +static inline void insertNames(const QDesignerMetaDataBaseInterface *metaDataBase, + Iterator it, const Iterator &end, + QObject *excludedObject, QSet &nameSet) +{ + for ( ; it != end; ++it) + if (excludedObject != *it && metaDataBase->item(*it)) + nameSet.insert((*it)->objectName()); +} + +static QSet languageKeywords() +{ + static const QSet keywords = { + // C++ keywords + u"asm"_s, + u"assert"_s, + u"auto"_s, + u"bool"_s, + u"break"_s, + u"case"_s, + u"catch"_s, + u"char"_s, + u"class"_s, + u"const"_s, + u"const_cast"_s, + u"continue"_s, + u"default"_s, + u"delete"_s, + u"do"_s, + u"double"_s, + u"dynamic_cast"_s, + u"else"_s, + u"enum"_s, + u"explicit"_s, + u"export"_s, + u"extern"_s, + u"false"_s, + u"final"_s, + u"float"_s, + u"for"_s, + u"friend"_s, + u"goto"_s, + u"if"_s, + u"inline"_s, + u"int"_s, + u"long"_s, + u"mutable"_s, + u"namespace"_s, + u"new"_s, + u"noexcept"_s, + u"NULL"_s, + u"nullptr"_s, + u"operator"_s, + u"override"_s, + u"private"_s, + u"protected"_s, + u"public"_s, + u"register"_s, + u"reinterpret_cast"_s, + u"return"_s, + u"short"_s, + u"signed"_s, + u"sizeof"_s, + u"static"_s, + u"static_cast"_s, + u"struct"_s, + u"switch"_s, + u"template"_s, + u"this"_s, + u"throw"_s, + u"true"_s, + u"try"_s, + u"typedef"_s, + u"typeid"_s, + u"typename"_s, + u"union"_s, + u"unsigned"_s, + u"using"_s, + u"virtual"_s, + u"void"_s, + u"volatile"_s, + u"wchar_t"_s, + u"while"_s, + + // java keywords + u"abstract"_s, + u"boolean"_s, + u"byte"_s, + u"extends"_s, + u"finality"_s, + u"implements"_s, + u"import"_s, + u"instanceof"_s, + u"interface"_s, + u"native"_s, + u"null"_s, + u"package"_s, + u"strictfp"_s, + u"super"_s, + u"synchronized"_s, + u"throws"_s, + u"transient"_s, + }; + + return keywords; +} + +bool FormWindow::unify(QObject *w, QString &s, bool changeIt) +{ + using StringSet = QSet; + + QWidget *main = mainContainer(); + if (!main) + return true; + + StringSet existingNames = languageKeywords(); + // build a set of existing names of other widget excluding self + if (!(w->isWidgetType() && isMainContainer(qobject_cast(w)))) + existingNames.insert(main->objectName()); + + const QDesignerMetaDataBaseInterface *metaDataBase = core()->metaDataBase(); + const QWidgetList widgetChildren = main->findChildren(); + if (!widgetChildren.isEmpty()) + insertNames(metaDataBase, widgetChildren.constBegin(), widgetChildren.constEnd(), w, existingNames); + + const auto layoutChildren = main->findChildren(); + if (!layoutChildren.isEmpty()) + insertNames(metaDataBase, layoutChildren.constBegin(), layoutChildren.constEnd(), w, existingNames); + + const auto actionChildren = main->findChildren(); + if (!actionChildren.isEmpty()) + insertNames(metaDataBase, actionChildren.constBegin(), actionChildren.constEnd(), w, existingNames); + + const auto buttonGroupChildren = main->findChildren(); + if (!buttonGroupChildren.isEmpty()) + insertNames(metaDataBase, buttonGroupChildren.constBegin(), buttonGroupChildren.constEnd(), w, existingNames); + + if (!existingNames.contains(s)) + return true; + if (!changeIt) + return false; + + // split 'name_number' + qlonglong num = 0; + qlonglong factor = 1; + qsizetype idx = s.size() - 1; + const char16_t zeroUnicode = u'0'; + for ( ; idx > 0 && s.at(idx).isDigit(); --idx) { + num += (s.at(idx).unicode() - zeroUnicode) * factor; + factor *= 10; + } + // Position index past '_'. + const QChar underscore = u'_'; + if (idx >= 0 && s.at(idx) == underscore) { + idx++; + } else { + num = 1; + s += underscore; + idx = s.size(); + } + // try 'name_n', 'name_n+1' + for (num++ ; ;num++) { + s.truncate(idx); + s += QString::number(num); + if (!existingNames.contains(s)) + break; + } + return false; +} +/* already_in_form is true when we are moving a widget from one parent to another inside the same + * form. All this means is that InsertWidgetCommand::undo() must not unmanage it. */ + +void FormWindow::insertWidget(QWidget *w, QRect rect, QWidget *container, bool already_in_form) +{ + clearSelection(false); + + beginCommand(tr("Insert widget '%1'").arg(WidgetFactory::classNameOf(m_core, w))); // ### use the WidgetDatabaseItem + + /* Reparenting into a QSplitter automatically adjusts child's geometry. We create the geometry + * command before we push the reparent command, so that the geometry command has the original + * geometry of the widget. */ + QRect r = rect; + Q_ASSERT(r.isValid()); + SetPropertyCommand *geom_cmd = new SetPropertyCommand(this); + geom_cmd->init(w, u"geometry"_s, r); // ### use rc.size() + + if (w->parentWidget() != container) { + ReparentWidgetCommand *cmd = new ReparentWidgetCommand(this); + cmd->init(w, container); + m_undoStack.push(cmd); + } + + m_undoStack.push(geom_cmd); + + QUndoCommand *cmd = nullptr; + if (auto dockWidget = qobject_cast(w)) { + if (auto mainWindow = qobject_cast(container)) { + auto addDockCmd = new AddDockWidgetCommand(this); + addDockCmd->init(mainWindow, dockWidget); + cmd = addDockCmd; + } + } + if (cmd == nullptr) { + auto insertCmd = new InsertWidgetCommand(this); + insertCmd->init(w, already_in_form); + cmd = insertCmd; + } + m_undoStack.push(cmd); + + endCommand(); + + w->show(); +} + +QWidget *FormWindow::createWidget(DomUI *ui, QRect rc, QWidget *target) +{ + QWidget *container = findContainer(target, false); + if (!container) + return nullptr; + if (isMainContainer(container)) { + if (QMainWindow *mw = qobject_cast(container)) { + Q_ASSERT(mw->centralWidget() != nullptr); + container = mw->centralWidget(); + } + } + QDesignerResource resource(this); + const FormBuilderClipboard clipboard = resource.paste(ui, container); + if (clipboard.m_widgets.size() != 1) // multiple-paste from DomUI not supported yet + return nullptr; + QWidget *widget = clipboard.m_widgets.first(); + insertWidget(widget, rc, container); + return widget; +} + +static bool isDescendant(const QWidget *parent, const QWidget *child) +{ + for (; child != nullptr; child = child->parentWidget()) { + if (child == parent) + return true; + } + return false; +} + +void FormWindow::resizeWidget(QWidget *widget, QRect geometry) +{ + Q_ASSERT(isDescendant(this, widget)); + + QRect r = geometry; + SetPropertyCommand *cmd = new SetPropertyCommand(this); + cmd->init(widget, u"geometry"_s, r); + cmd->setText(tr("Resize")); + m_undoStack.push(cmd); +} + +void FormWindow::raiseChildSelections(QWidget *w) +{ + const QWidgetList l = w->findChildren(); + if (l.isEmpty()) + return; + m_selection->raiseList(l); +} + +QWidget *FormWindow::containerAt(QPoint pos, QWidget *notParentOf) +{ + QWidget *container = nullptr; + int depth = -1; + const QWidgetList selected = selectedWidgets(); + if (rect().contains(mapFromGlobal(pos))) { + container = mainContainer(); + depth = widgetDepth(container); + } + + for (QWidget *wit : std::as_const(m_widgets)) { + if (qobject_cast(wit) || qobject_cast(wit)) + continue; + if (!wit->isVisibleTo(this)) + continue; + if (selected.indexOf(wit) != -1) + continue; + if (!core()->widgetDataBase()->isContainer(wit) && + wit != mainContainer()) + continue; + + // the rectangles of all ancestors of the container must contain the insert position + QWidget *w = wit; + while (w && !w->isWindow()) { + if (!w->rect().contains((w->mapFromGlobal(pos)))) + break; + w = w->parentWidget(); + } + if (!(w == nullptr || w->isWindow())) + continue; // we did not get through the full while loop + + int wd = widgetDepth(wit); + if (wd == depth && container) { + if (wit->parentWidget()->children().indexOf(wit) > + container->parentWidget()->children().indexOf(container)) + wd++; + } + if (wd > depth && !isChildOf(wit, notParentOf)) { + depth = wd; + container = wit; + } + } + return container; +} + +QWidgetList FormWindow::selectedWidgets() const +{ + return m_selection->selectedWidgets(); +} + +void FormWindow::selectWidgets() +{ + bool selectionChanged = false; + const QWidgetList l = mainContainer()->findChildren(); + const QRect selRect(mapToGlobal(m_currRect.topLeft()), m_currRect.size()); + for (QWidget *w : l) { + if (w->isVisibleTo(this) && isManaged(w)) { + const QPoint p = w->mapToGlobal(QPoint(0,0)); + const QRect r(p, w->size()); + if (r.intersects(selRect) && !r.contains(selRect) && trySelectWidget(w, true)) + selectionChanged = true; + } + } + + if (selectionChanged) + emitSelectionChanged(); +} + +bool FormWindow::handleKeyPressEvent(QWidget *widget, QWidget *, QKeyEvent *e) +{ + if (qobject_cast(widget) || qobject_cast(widget)) + return false; + + e->accept(); // we always accept! + + switch (e->key()) { + default: break; // we don't care about the other keys + + case Qt::Key_Delete: + case Qt::Key_Backspace: + if (e->modifiers() == Qt::NoModifier) + deleteWidgets(); + break; + + case Qt::Key_Tab: + if (e->modifiers() == Qt::NoModifier) + cursor()->movePosition(QDesignerFormWindowCursorInterface::Next); + break; + + case Qt::Key_Backtab: + if (e->modifiers() == Qt::NoModifier) + cursor()->movePosition(QDesignerFormWindowCursorInterface::Prev); + break; + + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_Up: + case Qt::Key_Down: + handleArrowKeyEvent(e->key(), e->modifiers()); + break; + } + + return true; +} + +int FormWindow::getValue(QRect rect, int key, bool size) const +{ + if (size) { + if (key == Qt::Key_Left || key == Qt::Key_Right) + return rect.width(); + return rect.height(); + } + if (key == Qt::Key_Left || key == Qt::Key_Right) + return rect.x(); + return rect.y(); +} + +int FormWindow::calcValue(int val, bool forward, bool snap, int snapOffset) const +{ + if (snap) { + const int rest = val % snapOffset; + if (rest) { + const int offset = forward ? snapOffset : 0; + const int newOffset = rest < 0 ? offset - snapOffset : offset; + return val + newOffset - rest; + } + return (forward ? val + snapOffset : val - snapOffset); + } + return (forward ? val + 1 : val - 1); +} + +// ArrowKeyOperation: Stores a keyboard move or resize (Shift pressed) +// operation. +struct ArrowKeyOperation +{ + QRect apply(QRect rect) const; + + bool resize = false; // Resize: Shift-Key->drag bottom/right corner, else just move + int distance = 0; + int arrowKey = Qt::Key_Left; +}; + +} // namespace + +QT_END_NAMESPACE +Q_DECLARE_METATYPE(qdesigner_internal::ArrowKeyOperation) +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +QRect ArrowKeyOperation::apply(QRect rect) const +{ + QRect r = rect; + if (resize) { + if (arrowKey == Qt::Key_Left || arrowKey == Qt::Key_Right) + r.setWidth(r.width() + distance); + else + r.setHeight(r.height() + distance); + } else { + if (arrowKey == Qt::Key_Left || arrowKey == Qt::Key_Right) + r.moveLeft(r.x() + distance); + else + r.moveTop(r.y() + distance); + } + return r; +} + +QDebug operator<<(QDebug in, ArrowKeyOperation op) +{ + in.nospace() << "Resize=" << op.resize << " dist=" << op.distance << " Key=" << op.arrowKey << ' '; + return in; +} + +// ArrowKeyPropertyHelper: Applies a struct ArrowKeyOperation +// (stored as new value) to a list of widgets using to calculate the +// changed geometry of the widget in setValue(). Thus, the 'newValue' +// of the property command is the relative move distance, which is the same +// for all widgets (although resulting in different geometries for the widgets). +// The command merging can then work as it would when applying the same text +// to all QLabels. + +class ArrowKeyPropertyHelper : public PropertyHelper { +public: + ArrowKeyPropertyHelper(QObject* o, SpecialProperty sp, + QDesignerPropertySheetExtension *s, int i) : + PropertyHelper(o, sp, s, i) {} + + Value setValue(QDesignerFormWindowInterface *fw, const QVariant &value, bool changed, + quint64 subPropertyMask) override; +}; + +PropertyHelper::Value ArrowKeyPropertyHelper::setValue(QDesignerFormWindowInterface *fw, const QVariant &value, + bool changed, quint64 subPropertyMask) +{ + // Apply operation to obtain the new geometry value. + QWidget *w = qobject_cast(object()); + const ArrowKeyOperation operation = qvariant_cast(value); + const QRect newGeom = operation.apply(w->geometry()); + return PropertyHelper::setValue(fw, QVariant(newGeom), changed, subPropertyMask); +} + +// ArrowKeyPropertyCommand: Helper factory overwritten to create +// ArrowKeyPropertyHelper and a merge operation that merges values of +// the same direction. +class ArrowKeyPropertyCommand: public SetPropertyCommand { +public: + explicit ArrowKeyPropertyCommand(QDesignerFormWindowInterface *fw, + QUndoCommand *p = nullptr); + + void init(QWidgetList &l, ArrowKeyOperation op); + +protected: + std::unique_ptr + createPropertyHelper(QObject *o, SpecialProperty sp, + QDesignerPropertySheetExtension *s, int i) const override + { return std::make_unique(o, sp, s, i); } + QVariant mergeValue(const QVariant &newValue) override; +}; + +ArrowKeyPropertyCommand::ArrowKeyPropertyCommand(QDesignerFormWindowInterface *fw, + QUndoCommand *p) : + SetPropertyCommand(fw, p) +{ + static const int mid = qRegisterMetaType(); + Q_UNUSED(mid); +} + +void ArrowKeyPropertyCommand::init(QWidgetList &l, ArrowKeyOperation op) +{ + QObjectList ol; + for (QWidget *w : std::as_const(l)) + ol.push_back(w); + SetPropertyCommand::init(ol, u"geometry"_s, QVariant::fromValue(op)); + + setText(op.resize ? FormWindow::tr("Key Resize") : FormWindow::tr("Key Move")); +} + +QVariant ArrowKeyPropertyCommand::mergeValue(const QVariant &newMergeValue) +{ + // Merge move operations of the same arrow key + if (!newMergeValue.canConvert()) + return QVariant(); + ArrowKeyOperation mergedOperation = qvariant_cast(newValue()); + const ArrowKeyOperation newMergeOperation = qvariant_cast(newMergeValue); + if (mergedOperation.resize != newMergeOperation.resize || mergedOperation.arrowKey != newMergeOperation.arrowKey) + return QVariant(); + mergedOperation.distance += newMergeOperation.distance; + return QVariant::fromValue(mergedOperation); +} + +void FormWindow::handleArrowKeyEvent(int key, Qt::KeyboardModifiers modifiers) +{ + const QDesignerFormWindowCursorInterface *c = cursor(); + if (!c->hasSelection()) + return; + + QWidgetList selection; + + // check if a laid out widget is selected + const int count = c->selectedWidgetCount(); + for (int index = 0; index < count; ++index) { + QWidget *w = c->selectedWidget(index); + if (!LayoutInfo::isWidgetLaidout(m_core, w)) + selection.append(w); + } + + simplifySelection(&selection); + + if (selection.isEmpty()) + return; + + QWidget *current = c->current(); + if (!current || LayoutInfo::isWidgetLaidout(m_core, current)) { + current = selection.first(); + } + + const bool size = modifiers & Qt::ShiftModifier; + + const bool snap = !(modifiers & Qt::ControlModifier); + const bool forward = (key == Qt::Key_Right || key == Qt::Key_Down); + const int snapPoint = (key == Qt::Key_Left || key == Qt::Key_Right) ? grid().x() : grid().y(); + + const int oldValue = getValue(current->geometry(), key, size); + + const int newValue = calcValue(oldValue, forward, snap, snapPoint); + + ArrowKeyOperation operation; + operation.resize = modifiers & Qt::ShiftModifier; + operation.distance = newValue - oldValue; + operation.arrowKey = key; + + ArrowKeyPropertyCommand *cmd = new ArrowKeyPropertyCommand(this); + cmd->init(selection, operation); + m_undoStack.push(cmd); +} + +bool FormWindow::handleKeyReleaseEvent(QWidget *, QWidget *, QKeyEvent *e) +{ + e->accept(); + return true; +} + +void FormWindow::selectAll() +{ + bool selectionChanged = false; + for (QWidget *widget : std::as_const(m_widgets)) { + if (widget->isVisibleTo(this) && trySelectWidget(widget, true)) + selectionChanged = true; + } + if (selectionChanged) + emitSelectionChanged(); +} + +void FormWindow::createLayout(int type, QWidget *container) +{ + if (container) { + layoutContainer(container, type); + } else { + LayoutCommand *cmd = new LayoutCommand(this); + cmd->init(mainContainer(), selectedWidgets(), static_cast(type)); + commandHistory()->push(cmd); + } +} + +void FormWindow::morphLayout(QWidget *container, int newType) +{ + MorphLayoutCommand *cmd = new MorphLayoutCommand(this); + if (cmd->init(container, newType)) { + commandHistory()->push(cmd); + } else { + qDebug() << "** WARNING Unable to morph layout."; + delete cmd; + } +} + +void FormWindow::deleteWidgets() +{ + QWidgetList selection = selectedWidgets(); + simplifySelection(&selection); + + deleteWidgetList(selection); +} + +QString FormWindow::fileName() const +{ + return m_fileName; +} + +void FormWindow::setFileName(const QString &fileName) +{ + if (m_fileName == fileName) + return; + + m_fileName = fileName; + emit fileNameChanged(fileName); +} + +QString FormWindow::contents() const +{ + QBuffer b; + if (!mainContainer() || !b.open(QIODevice::WriteOnly)) + return QString(); + + QDesignerResource resource(const_cast(this)); + resource.save(&b, mainContainer()); + + return QString::fromUtf8(b.buffer()); +} + +#if QT_CONFIG(clipboard) +void FormWindow::copy() +{ + QBuffer b; + if (!b.open(QIODevice::WriteOnly)) + return; + + FormBuilderClipboard clipboard; + QDesignerResource resource(this); + resource.setSaveRelative(false); + clipboard.m_widgets = selectedWidgets(); + simplifySelection(&clipboard.m_widgets); + resource.copy(&b, clipboard); + + qApp->clipboard()->setText(QString::fromUtf8(b.buffer()), QClipboard::Clipboard); +} + +void FormWindow::cut() +{ + copy(); + deleteWidgets(); +} + +void FormWindow::paste() +{ + paste(PasteAll); +} +#endif + +// for cases like QMainWindow (central widget is an inner container) or QStackedWidget (page is an inner container) +QWidget *FormWindow::innerContainer(QWidget *outerContainer) const +{ + if (m_core->widgetDataBase()->isContainer(outerContainer)) + if (const QDesignerContainerExtension *container = qt_extension(m_core->extensionManager(), outerContainer)) { + const int currentIndex = container->currentIndex(); + return currentIndex >= 0 ? container->widget(currentIndex) : nullptr; + } + return outerContainer; +} + +QWidget *FormWindow::containerForPaste() const +{ + QWidget *w = mainContainer(); + if (!w) + return nullptr; + do { + // Try to find a close parent, for example a non-laid-out + // QFrame/QGroupBox when a widget within it is selected. + QWidgetList selection = selectedWidgets(); + if (selection.isEmpty()) + break; + simplifySelection(&selection); + + QWidget *containerOfW = findContainer(selection.first(), /* exclude layouts */ true); + if (!containerOfW || containerOfW == mainContainer()) + break; + // No layouts, must be container. No empty page-based containers. + containerOfW = innerContainer(containerOfW); + if (!containerOfW) + break; + if (LayoutInfo::layoutType(m_core, containerOfW) != LayoutInfo::NoLayout || !m_core->widgetDataBase()->isContainer(containerOfW)) + break; + w = containerOfW; + } while (false); + // First check for layout (note that it does not cover QMainWindow + // and the like as the central widget has the layout). + + w = innerContainer(w); + if (!w) + return nullptr; + if (LayoutInfo::layoutType(m_core, w) != LayoutInfo::NoLayout) + return nullptr; + // Go up via container extension (also includes step from QMainWindow to its central widget) + w = m_core->widgetFactory()->containerOfWidget(w); + if (w == nullptr || LayoutInfo::layoutType(m_core, w) != LayoutInfo::NoLayout) + return nullptr; + + if (debugFormWindow) + qDebug() <<"containerForPaste() " << w; + return w; +} + +#if QT_CONFIG(clipboard) +// Construct DomUI from clipboard (paste) and determine number of widgets/actions. +static inline DomUI *domUIFromClipboard(int *widgetCount, int *actionCount) +{ + *widgetCount = *actionCount = 0; + const QString clipboardText = qApp->clipboard()->text(); + if (clipboardText.isEmpty() || clipboardText.indexOf(u'<') == -1) + return nullptr; + + QXmlStreamReader reader(clipboardText); + DomUI *ui = nullptr; + while (!reader.atEnd()) { + if (reader.readNext() == QXmlStreamReader::StartElement) { + if (reader.name().compare("ui"_L1, Qt::CaseInsensitive) == 0 && !ui) { + ui = new DomUI(); + ui->read(reader); + break; + } + reader.raiseError(QCoreApplication::translate("FormWindow", "Unexpected element <%1>").arg(reader.name().toString())); + } + } + if (reader.hasError()) { + delete ui; + ui = nullptr; + designerWarning(QCoreApplication::translate("FormWindow", "Error while pasting clipboard contents at line %1, column %2: %3"). + arg(reader.lineNumber()).arg(reader.columnNumber()).arg(reader.errorString())); + return nullptr; + } + + if (const DomWidget *topLevel = ui->elementWidget()) { + *widgetCount = topLevel->elementWidget().size(); + *actionCount = topLevel->elementAction().size(); + } + if (*widgetCount == 0 && *actionCount == 0) { + delete ui; + return nullptr; + } + return ui; +} +#endif + +static inline QString pasteCommandDescription(int widgetCount, int actionCount) +{ + if (widgetCount == 0) + return FormWindow::tr("Paste %n action(s)", nullptr, actionCount); + if (actionCount == 0) + return FormWindow::tr("Paste %n widget(s)", nullptr, widgetCount); + return FormWindow::tr("Paste (%1 widgets, %2 actions)").arg(widgetCount).arg(actionCount); +} + +#if QT_CONFIG(clipboard) +static void positionPastedWidgetsAtMousePosition(FormWindow *fw, QPoint contextMenuPosition, QWidget *parent, const QWidgetList &l) +{ + // Try to position pasted widgets at mouse position (current mouse position for Ctrl-V or position of context menu) + // if it fits. If it is completely outside, force it to 0,0 + // If it fails, the old coordinates relative to the previous parent will be used. + QPoint currentPos = contextMenuPosition.x() >=0 ? parent->mapFrom(fw, contextMenuPosition) : parent->mapFromGlobal(QCursor::pos()); + const Grid &grid = fw->designerGrid(); + QPoint cursorPos = grid.snapPoint(currentPos); + const QRect parentGeometry = QRect(QPoint(0, 0), parent->size()); + const bool outside = !parentGeometry.contains(cursorPos); + if (outside) + cursorPos = grid.snapPoint(QPoint(0, 0)); + // Determine area of pasted widgets + QRect pasteArea; + for (auto *w : l) + pasteArea = pasteArea.isNull() ? w->geometry() : pasteArea.united(w->geometry()); + + // Mouse on some child? (try to position bottomRight on a free spot to + // get the stacked-offset effect of Designer 4.3, that is, offset by grid if Ctrl-V is pressed continuously + do { + const QPoint bottomRight = cursorPos + QPoint(pasteArea.width(), pasteArea.height()) - QPoint(1, 1); + if (bottomRight.y() > parentGeometry.bottom() || parent->childAt(bottomRight) == nullptr) + break; + cursorPos += QPoint(grid.deltaX(), grid.deltaY()); + } while (true); + // Move. + const QPoint offset = cursorPos - pasteArea.topLeft(); + for (auto *w : l) + w->move(w->pos() + offset); +} + +void FormWindow::paste(PasteMode pasteMode) +{ + // Avoid QDesignerResource constructing widgets that are not used as + // QDesignerResource manages the widgets it creates (creating havoc if one remains unused) + DomUI *ui = nullptr; + do { + int widgetCount; + int actionCount; + ui = domUIFromClipboard(&widgetCount, &actionCount); + if (!ui) + break; + + // Check for actions + if (pasteMode == PasteActionsOnly) + if (widgetCount != 0 || actionCount == 0) + break; + + // Check for widgets: need a container + QWidget *pasteContainer = widgetCount ? containerForPaste() : nullptr; + if (widgetCount && pasteContainer == nullptr) { + + const QString message = tr("Cannot paste widgets. Designer could not find a container " + "without a layout to paste into."); + const QString infoMessage = tr("Break the layout of the " + "container you want to paste into, select this container " + "and then paste again."); + core()->dialogGui()->message(this, QDesignerDialogGuiInterface::FormEditorMessage, QMessageBox::Information, + tr("Paste error"), message, infoMessage, QMessageBox::Ok); + break; + } + + QDesignerResource resource(this); + // Note that the widget factory must be able to locate the + // form window (us) via parent, otherwise, it will not able to construct QLayoutWidgets + // (It will then default to widgets) among other issues. + const FormBuilderClipboard clipboard = resource.paste(ui, pasteContainer, this); + + clearSelection(false); + // Create command sequence + beginCommand(pasteCommandDescription(widgetCount, actionCount)); + + if (widgetCount) { + positionPastedWidgetsAtMousePosition(this, m_contextMenuPosition, pasteContainer, clipboard.m_widgets); + for (QWidget *w : clipboard.m_widgets) { + InsertWidgetCommand *cmd = new InsertWidgetCommand(this); + cmd->init(w); + m_undoStack.push(cmd); + selectWidget(w); + } + } + + if (actionCount) + for (QAction *a : clipboard.m_actions) { + ensureUniqueObjectName(a); + AddActionCommand *cmd = new AddActionCommand(this); + cmd->init(a); + m_undoStack.push(cmd); + } + endCommand(); + } while (false); + delete ui; +} +#endif + +// Draw a dotted frame around containers +bool FormWindow::frameNeeded(QWidget *w) const +{ + if (!core()->widgetDataBase()->isContainer(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + if (qobject_cast(w)) + return false; + return true; +} + +bool FormWindow::eventFilter(QObject *watched, QEvent *event) +{ + const bool ret = FormWindowBase::eventFilter(watched, event); + if (event->type() != QEvent::Paint) + return ret; + + Q_ASSERT(watched->isWidgetType()); + QWidget *w = static_cast(watched); + QPaintEvent *pe = static_cast(event); + const QRect widgetRect = w->rect(); + const QRect paintRect = pe->rect(); + // Does the paint rectangle touch the borders of the widget rectangle + if (paintRect.x() > widgetRect.x() && paintRect.y() > widgetRect.y() && + paintRect.right() < widgetRect.right() && paintRect.bottom() < widgetRect.bottom()) + return ret; + QPainter p(w); + const QPen pen(QColor(0, 0, 0, 32), 0, Qt::DotLine); + p.setPen(pen); + p.setBrush(QBrush(Qt::NoBrush)); + p.drawRect(widgetRect.adjusted(0, 0, -1, -1)); + return ret; +} + +void FormWindow::manageWidget(QWidget *w) +{ + if (isManaged(w)) + return; + + Q_ASSERT(qobject_cast(w) == 0); + + if (w->hasFocus()) + setFocus(); + + core()->metaDataBase()->add(w); + + m_insertedWidgets.insert(w); + m_widgets.append(w); + +#if QT_CONFIG(cursor) + setCursorToAll(Qt::ArrowCursor, w); +#endif + + emit changed(); + emit widgetManaged(w); + + if (frameNeeded(w)) + w->installEventFilter(this); +} + +void FormWindow::unmanageWidget(QWidget *w) +{ + if (!isManaged(w)) + return; + + m_selection->removeWidget(w); + + emit aboutToUnmanageWidget(w); + + if (w == m_currentWidget) + setCurrentWidget(mainContainer()); + + core()->metaDataBase()->remove(w); + + m_insertedWidgets.remove(w); + m_widgets.removeAt(m_widgets.indexOf(w)); + + emit changed(); + emit widgetUnmanaged(w); + + if (frameNeeded(w)) + w->removeEventFilter(this); +} + +bool FormWindow::isManaged(QWidget *w) const +{ + return m_insertedWidgets.contains(w); +} + +void FormWindow::breakLayout(QWidget *w) +{ + if (w == this) + w = mainContainer(); + // Find the first-order managed child widgets + QWidgetList widgets; + + const QDesignerMetaDataBaseInterface *mdb = core()->metaDataBase(); + for (auto *o : w->children()) { + if (o->isWidgetType()) { + auto *w = static_cast(o); + if (mdb->item(w)) + widgets.push_back(w); + } + } + + BreakLayoutCommand *cmd = new BreakLayoutCommand(this); + cmd->init(widgets, w); + commandHistory()->push(cmd); + clearSelection(false); +} + +void FormWindow::beginCommand(const QString &description) +{ + m_undoStack.beginMacro(description); +} + +void FormWindow::endCommand() +{ + m_undoStack.endMacro(); +} + +void FormWindow::raiseWidgets() +{ + QWidgetList widgets = selectedWidgets(); + simplifySelection(&widgets); + + if (widgets.isEmpty()) + return; + + beginCommand(tr("Raise widgets")); + for (QWidget *widget : std::as_const(widgets)) { + RaiseWidgetCommand *cmd = new RaiseWidgetCommand(this); + cmd->init(widget); + m_undoStack.push(cmd); + } + endCommand(); +} + +void FormWindow::lowerWidgets() +{ + QWidgetList widgets = selectedWidgets(); + simplifySelection(&widgets); + + if (widgets.isEmpty()) + return; + + beginCommand(tr("Lower widgets")); + for (QWidget *widget : std::as_const(widgets)) { + LowerWidgetCommand *cmd = new LowerWidgetCommand(this); + cmd->init(widget); + m_undoStack.push(cmd); + } + endCommand(); +} + +bool FormWindow::handleMouseButtonDblClickEvent(QWidget *w, QWidget *managedWidget, QMouseEvent *e) +{ + if (debugFormWindow) + qDebug() << "handleMouseButtonDblClickEvent:" << w << ',' << managedWidget << "state=" << m_mouseState; + + e->accept(); + + // Might be out of sync due cycling of the parent selection + // In that case, do nothing + if (isWidgetSelected(managedWidget)) + emit activated(managedWidget); + + m_mouseState = MouseDoubleClicked; + return true; +} + + +QMenu *FormWindow::initializePopupMenu(QWidget *managedWidget) +{ + if (!isManaged(managedWidget) || currentTool()) + return nullptr; + + // Make sure the managedWidget is selected and current since + // the SetPropertyCommands must use the right reference + // object obtained from the property editor for the property group + // of a multiselection to be correct. + const bool selected = isWidgetSelected(managedWidget); + bool update = false; + if (selected) { + update = setCurrentWidget(managedWidget); + } else { + clearObjectInspectorSelection(m_core); // We might have a toolbar or non-widget selected in the object inspector. + clearSelection(false); + update = trySelectWidget(managedWidget, true); + raiseChildSelections(managedWidget); // raise selections and select widget + } + + if (update) { + emitSelectionChanged(); + QMetaObject::invokeMethod(core()->formWindowManager(), "slotUpdateActions"); + } + + QWidget *contextMenuWidget = nullptr; + + if (isMainContainer(managedWidget)) { // press on a child widget + contextMenuWidget = mainContainer(); + } else { // press on a child widget + // if widget is laid out, find the first non-laid out super-widget + QWidget *realWidget = managedWidget; // but store the original one + QMainWindow *mw = qobject_cast(mainContainer()); + + if (mw && mw->centralWidget() == realWidget) { + contextMenuWidget = managedWidget; + } else { + contextMenuWidget = realWidget; + } + } + + if (!contextMenuWidget) + return nullptr; + + QMenu *contextMenu = createPopupMenu(contextMenuWidget); + if (!contextMenu) + return nullptr; + + emit contextMenuRequested(contextMenu, contextMenuWidget); + return contextMenu; +} + +bool FormWindow::handleContextMenu(QWidget *, QWidget *managedWidget, QContextMenuEvent *e) +{ + QMenu *contextMenu = initializePopupMenu(managedWidget); + if (!contextMenu) + return false; + const QPoint globalPos = e->globalPos(); + m_contextMenuPosition = mapFromGlobal (globalPos); + contextMenu->exec(globalPos); + delete contextMenu; + e->accept(); + m_contextMenuPosition = QPoint(-1, -1); + return true; +} + +bool FormWindow::setContents(QIODevice *dev, QString *errorMessageIn /* = 0 */) +{ + QDesignerResource r(this); + QScopedPointer ui(r.readUi(dev)); + if (ui.isNull()) { + if (errorMessageIn) + *errorMessageIn = r.errorString(); + return false; + } + + UpdateBlocker ub(this); + clearSelection(); + m_selection->clearSelectionPool(); + m_insertedWidgets.clear(); + m_widgets.clear(); + // The main container is cleared as otherwise + // the names of the newly loaded objects will be unified. + clearMainContainer(); + m_undoStack.clear(); + emit changed(); + + QWidget *w = r.loadUi(ui.data(), formContainer()); + if (w) { + setMainContainer(w); + emit changed(); + } + if (errorMessageIn) + *errorMessageIn = r.errorString(); + return w != nullptr; +} + +bool FormWindow::setContents(const QString &contents) +{ + QString errorMessage; + QByteArray data = contents.toUtf8(); + QBuffer b(&data); + const bool success = b.open(QIODevice::ReadOnly) && setContents(&b, &errorMessage); + if (!success && !errorMessage.isEmpty()) + designerWarning(errorMessage); + return success; +} + +void FormWindow::layoutContainer(QWidget *w, int type) +{ + if (w == this) + w = mainContainer(); + + w = core()->widgetFactory()->containerOfWidget(w); + + // find managed widget children + QWidgetList widgets; + for (auto *o : w->children()) { + if (o->isWidgetType() ) { + auto *widget = static_cast(o); + if (widget->isVisibleTo(this) && isManaged(widget)) + widgets.append(widget); + } + } + + if (widgets.isEmpty()) // QTBUG-50563, observed when using hand-edited forms. + return; + + LayoutCommand *cmd = new LayoutCommand(this); + cmd->init(mainContainer(), widgets, static_cast(type), w); + clearSelection(false); + commandHistory()->push(cmd); +} + +bool FormWindow::hasInsertedChildren(QWidget *widget) const // ### move +{ + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) { + const int index = container->currentIndex(); + if (index < 0) + return false; + widget = container->widget(index); + } + + const QWidgetList l = widgets(widget); + + for (QWidget *child : l) { + if (isManaged(child) && !LayoutInfo::isWidgetLaidout(core(), child) && child->isVisibleTo(const_cast(this))) + return true; + } + + return false; +} + +// "Select Ancestor" sub menu code +void FormWindow::slotSelectWidget(QAction *a) +{ + if (QWidget *w = qvariant_cast(a->data())) + selectSingleWidget(w); +} + +void FormWindow::slotCleanChanged(bool clean) +{ + if (!clean) + emit changed(); +} + +static inline QString objectNameOf(const QWidget *w) +{ + if (const QLayoutWidget *lw = qobject_cast(w)) { + const QLayout *layout = lw->layout(); + const QString rc = layout->objectName(); + if (!rc.isEmpty()) + return rc; + // Fall thru for 4.3 forms which have a name on the widget: Display the class name + return QString::fromUtf8(layout->metaObject()->className()); + } + return w->objectName(); +} + +QAction *FormWindow::createSelectAncestorSubMenu(QWidget *w) +{ + // Find the managed, unselected parents + QWidgetList parents; + QWidget *mc = mainContainer(); + for (QWidget *p = w->parentWidget(); p && p != mc; p = p->parentWidget()) + if (isManaged(p) && !isWidgetSelected(p)) + parents.push_back(p); + if (parents.isEmpty()) + return nullptr; + // Create a submenu listing the managed, unselected parents + QMenu *menu = new QMenu; + QActionGroup *ag = new QActionGroup(menu); + QObject::connect(ag, &QActionGroup::triggered, this, &FormWindow::slotSelectWidget); + for (auto *w : std::as_const(parents)) { + QAction *a = ag->addAction(objectNameOf(w)); + a->setData(QVariant::fromValue(w)); + menu->addAction(a); + } + QAction *ma = new QAction(tr("Select Ancestor"), nullptr); + ma->setMenu(menu); + return ma; +} + +QMenu *FormWindow::createPopupMenu(QWidget *w) +{ + QMenu *popup = createExtensionTaskMenu(this, w, true); + if (!popup) + popup = new QMenu; + // if w doesn't have a QDesignerTaskMenu as a child create one and make it a child. + // insert actions from QDesignerTaskMenu + + QDesignerFormWindowManagerInterface *manager = core()->formWindowManager(); + const bool isFormWindow = qobject_cast(w); + + // Check for special containers and obtain the page menu from them to add layout actions. + if (!isFormWindow) { + if (QStackedWidget *stackedWidget = qobject_cast(w)) { + QStackedWidgetEventFilter::addStackedWidgetContextMenuActions(stackedWidget, popup); + } else if (QTabWidget *tabWidget = qobject_cast(w)) { + QTabWidgetEventFilter::addTabWidgetContextMenuActions(tabWidget, popup); + } else if (QToolBox *toolBox = qobject_cast(w)) { + QToolBoxHelper::addToolBoxContextMenuActions(toolBox, popup); + } + + if (manager->action(QDesignerFormWindowManagerInterface::LowerAction)->isEnabled()) { + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::LowerAction)); + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::RaiseAction)); + popup->addSeparator(); + } +#if QT_CONFIG(clipboard) + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::CutAction)); + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::CopyAction)); +#endif + } + +#if QT_CONFIG(clipboard) + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::PasteAction)); +#endif + + if (QAction *selectAncestorAction = createSelectAncestorSubMenu(w)) + popup->addAction(selectAncestorAction); + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::SelectAllAction)); + + if (!isFormWindow) { + popup->addAction(manager->action(QDesignerFormWindowManagerInterface::DeleteAction)); + } + + popup->addSeparator(); + QMenu *layoutMenu = popup->addMenu(tr("Lay out")); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::AdjustSizeAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::HorizontalLayoutAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::VerticalLayoutAction)); + if (!isFormWindow) { + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::SplitHorizontalAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::SplitVerticalAction)); + } + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::GridLayoutAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::FormLayoutAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::BreakLayoutAction)); + layoutMenu->addAction(manager->action(QDesignerFormWindowManagerInterface::SimplifyLayoutAction)); + + return popup; +} + +void FormWindow::resizeEvent(QResizeEvent *e) +{ + m_geometryChangedTimer->start(10); + + QWidget::resizeEvent(e); +} + +/*! + Maps \a pos in \a w's coordinates to the form's coordinate system. + + This is the equivalent to mapFromGlobal(w->mapToGlobal(pos)) but + avoids the two roundtrips to the X-Server on Unix/X11. + */ +QPoint FormWindow::mapToForm(const QWidget *w, QPoint pos) const +{ + QPoint p = pos; + const QWidget* i = w; + while (i && !i->isWindow() && !isMainContainer(i)) { + p = i->mapToParent(p); + i = i->parentWidget(); + } + + return mapFromGlobal(w->mapToGlobal(pos)); +} + +bool FormWindow::canBeBuddy(QWidget *w) const // ### rename me. +{ + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), w)) { + const int index = sheet->indexOf(u"focusPolicy"_s); + if (index != -1) { + bool ok = false; + const Qt::FocusPolicy q = static_cast(Utils::valueOf(sheet->property(index), &ok)); + return ok && q != Qt::NoFocus; + } + } + + return false; +} + +QWidget *FormWindow::findContainer(QWidget *w, bool excludeLayout) const +{ + if (!isChildOf(w, this) + || const_cast(w) == this) + return nullptr; + + QDesignerWidgetFactoryInterface *widgetFactory = core()->widgetFactory(); + QDesignerWidgetDataBaseInterface *widgetDataBase = core()->widgetDataBase(); + QDesignerMetaDataBaseInterface *metaDataBase = core()->metaDataBase(); + + QWidget *container = widgetFactory->containerOfWidget(mainContainer()); // default parent for new widget is the formwindow + if (!isMainContainer(w)) { // press was not on formwindow, check if we can find another parent + while (w) { + if (qobject_cast(w) || !metaDataBase->item(w)) { + w = w->parentWidget(); + continue; + } + + const bool isContainer = widgetDataBase->isContainer(w, true) || w == mainContainer(); + + if (!isContainer || (excludeLayout && qobject_cast(w))) { // ### skip QSplitter + w = w->parentWidget(); + } else { + container = w; + break; + } + } + } + + return container; +} + +void FormWindow::simplifySelection(QWidgetList *sel) const +{ + if (sel->size() < 2) + return; + // Figure out which widgets should be removed from selection. + // We want to remove those whose parent widget is also in the + // selection (because the child widgets are contained by + // their parent, they shouldn't be in the selection -- + // they are "implicitly" selected). + QWidget *mainC = mainContainer(); // Quick check for main container first + if (sel->contains(mainC)) { + sel->clear(); + sel->push_back(mainC); + return; + } + QWidgetList toBeRemoved; + toBeRemoved.reserve(sel->size()); + for (auto *child : std::as_const(*sel)) { + for (QWidget *w = child; true ; ) { // Is any of the parents also selected? + QWidget *parent = w->parentWidget(); + if (!parent || parent == mainC) + break; + if (sel->contains(parent)) { + toBeRemoved.append(child); + break; + } + w = parent; + } + } + // Now we can actually remove the widgets that were marked + // for removal in the previous pass. + for (auto *r : std::as_const(toBeRemoved)) + sel->removeAll(r); +} + +FormWindow *FormWindow::findFormWindow(QWidget *w) +{ + return qobject_cast(QDesignerFormWindowInterface::findFormWindow(w)); +} + +bool FormWindow::isDirty() const +{ + return !m_undoStack.isClean(); +} + +void FormWindow::setDirty(bool dirty) +{ + if (dirty) + m_undoStack.resetClean(); + else + m_undoStack.setClean(); +} + +QWidget *FormWindow::containerAt(const QPoint &pos) +{ + QWidget *widget = widgetAt(pos); + return findContainer(widget, true); +} + +static QWidget *childAt_SkipDropLine(QWidget *w, QPoint pos) +{ + const QObjectList &child_list = w->children(); + for (auto i = child_list.size() - 1; i >= 0; --i) { + QObject *child_obj = child_list.at(i); + if (qobject_cast(child_obj) != nullptr) + continue; + QWidget *child = qobject_cast(child_obj); + if (!child || child->isWindow() || !child->isVisible() || + !child->geometry().contains(pos) || child->testAttribute(Qt::WA_TransparentForMouseEvents)) + continue; + const QPoint childPos = child->mapFromParent(pos); + if (QWidget *res = childAt_SkipDropLine(child, childPos)) + return res; + if (child->testAttribute(Qt::WA_MouseNoMask) || child->mask().contains(pos) + || child->mask().isEmpty()) + return child; + } + + return nullptr; +} + +QWidget *FormWindow::widgetAt(const QPoint &pos) +{ + QWidget *w = childAt(pos); + if (qobject_cast(w) != 0) + w = childAt_SkipDropLine(this, pos); + return (w == nullptr || w == formContainer()) ? this : w; +} + +void FormWindow::highlightWidget(QWidget *widget, const QPoint &pos, HighlightMode mode) +{ + Q_ASSERT(widget); + + if (QMainWindow *mainWindow = qobject_cast (widget)) { + widget = mainWindow->centralWidget(); + } + + QWidget *container = findContainer(widget, false); + + if (container == nullptr || core()->metaDataBase()->item(container) == nullptr) + return; + + if (QDesignerActionProviderExtension *g = qt_extension(core()->extensionManager(), container)) { + if (mode == Restore) { + g->adjustIndicator(QPoint()); + } else { + const QPoint pt = widget->mapTo(container, pos); + g->adjustIndicator(pt); + } + } else if (QDesignerLayoutDecorationExtension *g = qt_extension(core()->extensionManager(), container)) { + if (mode == Restore) { + g->adjustIndicator(QPoint(), -1); + } else { + const QPoint pt = widget->mapTo(container, pos); + const int index = g->findItemAt(pt); + g->adjustIndicator(pt, index); + } + } + + QMainWindow *mw = qobject_cast (container); + if (container == mainContainer() || (mw && mw->centralWidget() && mw->centralWidget() == container)) + return; + + if (mode == Restore) { + const auto pit = m_palettesBeforeHighlight.find(container); + if (pit != m_palettesBeforeHighlight.end()) { + container->setPalette(pit.value().first); + container->setAutoFillBackground(pit.value().second); + m_palettesBeforeHighlight.erase(pit); + } + } else { + QPalette p = container->palette(); + if (!m_palettesBeforeHighlight.contains(container)) { + PaletteAndFill paletteAndFill; + if (container->testAttribute(Qt::WA_SetPalette)) + paletteAndFill.first = p; + paletteAndFill.second = container->autoFillBackground(); + m_palettesBeforeHighlight.insert(container, paletteAndFill); + } + + p.setColor(backgroundRole(), p.midlight().color()); + container->setPalette(p); + container->setAutoFillBackground(true); + } +} + +QWidgetList FormWindow::widgets(QWidget *widget) const +{ + if (widget->children().isEmpty()) + return QWidgetList(); + QWidgetList rc; + for (QObject *o : widget->children()) { + if (o->isWidgetType()) { + QWidget *w = qobject_cast(o); + if (isManaged(w)) + rc.push_back(w); + } + } + return rc; +} + +int FormWindow::toolCount() const +{ + return m_widgetStack->count(); +} + +QDesignerFormWindowToolInterface *FormWindow::tool(int index) const +{ + return m_widgetStack->tool(index); +} + +void FormWindow::registerTool(QDesignerFormWindowToolInterface *tool) +{ + Q_ASSERT(tool != nullptr); + + m_widgetStack->addTool(tool); + + if (m_mainContainer) + m_mainContainer->update(); +} + +void FormWindow::setCurrentTool(int index) +{ + m_widgetStack->setCurrentTool(index); +} + +int FormWindow::currentTool() const +{ + return m_widgetStack->currentIndex(); +} + +bool FormWindow::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + if (m_widgetStack == nullptr) + return false; + + QDesignerFormWindowToolInterface *tool = m_widgetStack->currentTool(); + if (tool == nullptr) + return false; + + return tool->handleEvent(widget, managedWidget, event); +} + +void FormWindow::initializeCoreTools() +{ + m_widgetEditor = new WidgetEditorTool(this); + registerTool(m_widgetEditor); +} + +void FormWindow::checkSelection() +{ + m_checkSelectionTimer->start(0); +} + +void FormWindow::checkSelectionNow() +{ + m_checkSelectionTimer->stop(); + + const QWidgetList &sel = selectedWidgets(); + for (QWidget *widget : sel) { + updateSelection(widget); + + if (LayoutInfo::layoutType(core(), widget) != LayoutInfo::NoLayout) + updateChildSelections(widget); + } +} + +QString FormWindow::author() const +{ + return m_author; +} + +QString FormWindow::comment() const +{ + return m_comment; +} + +void FormWindow::setAuthor(const QString &author) +{ + m_author = author; +} + +void FormWindow::setComment(const QString &comment) +{ + m_comment = comment; +} + +void FormWindow::editWidgets() +{ + m_widgetEditor->action()->trigger(); +} + +QStringList FormWindow::resourceFiles() const +{ + return m_resourceFiles; +} + +void FormWindow::addResourceFile(const QString &path) +{ + if (!m_resourceFiles.contains(path)) { + m_resourceFiles.append(path); + setDirty(true); + emit resourceFilesChanged(); + } +} + +void FormWindow::removeResourceFile(const QString &path) +{ + if (m_resourceFiles.removeAll(path) > 0) { + setDirty(true); + emit resourceFilesChanged(); + } +} + +bool FormWindow::blockSelectionChanged(bool b) +{ + const bool blocked = m_blockSelectionChanged; + m_blockSelectionChanged = b; + return blocked; +} + +void FormWindow::editContents() +{ + const QWidgetList sel = selectedWidgets(); + if (sel.size() == 1) { + QWidget *widget = sel.first(); + + if (QAction *a = preferredEditAction(core(), widget)) + a->trigger(); + } +} + +void FormWindow::dragWidgetWithinForm(QWidget *widget, QRect targetGeometry, QWidget *targetContainer) +{ + const bool fromLayout = canDragWidgetInLayout(core(), widget); + const QDesignerLayoutDecorationExtension *targetDeco = qt_extension(core()->extensionManager(), targetContainer); + const bool toLayout = targetDeco != nullptr; + + if (fromLayout) { + // Drag from Layout: We need to delete the widget properly to store the layout state + // Do not simplify the layout when dragging onto a layout + // as this might invalidate the insertion position if it is the same layout + DeleteWidgetCommand *cmd = new DeleteWidgetCommand(this); + unsigned deleteFlags = DeleteWidgetCommand::DoNotUnmanage; + if (toLayout) + deleteFlags |= DeleteWidgetCommand::DoNotSimplifyLayout; + cmd->init(widget, deleteFlags); + commandHistory()->push(cmd); + } + + if (toLayout) { + // Drag from form to layout: just insert. Do not manage + insertWidget(widget, targetGeometry, targetContainer, true); + } else { + // into container without layout + if (targetContainer != widget->parent()) { // different parent + ReparentWidgetCommand *cmd = new ReparentWidgetCommand(this); + cmd->init(widget, targetContainer ); + commandHistory()->push(cmd); + } + resizeWidget(widget, targetGeometry); + selectWidget(widget, true); + widget->show(); + } +} + +static Qt::DockWidgetArea detectDropArea(QMainWindow *mainWindow, QRect area, QPoint drop) +{ + QPoint offset = area.topLeft(); + QRect rect = area; + rect.moveTopLeft(QPoint(0, 0)); + QPoint point = drop - offset; + const int x = point.x(); + const int y = point.y(); + const int w = rect.width(); + const int h = rect.height(); + + if (rect.contains(point)) { + bool topRight = false; + bool topLeft = false; + if (w * y < h * x) // top and right, oterwise bottom and left + topRight = true; + if (w * y < h * (w - x)) // top and left, otherwise bottom and right + topLeft = true; + + if (topRight && topLeft) + return Qt::TopDockWidgetArea; + if (topRight && !topLeft) + return Qt::RightDockWidgetArea; + if (!topRight && topLeft) + return Qt::LeftDockWidgetArea; + return Qt::BottomDockWidgetArea; + } + + if (x < 0) { + if (y < 0) + return mainWindow->corner(Qt::TopLeftCorner); + return y > h ? mainWindow->corner(Qt::BottomLeftCorner) : Qt::LeftDockWidgetArea; + } + if (x > w) { + if (y < 0) + return mainWindow->corner(Qt::TopRightCorner); + return y > h ? mainWindow->corner(Qt::BottomRightCorner) : Qt::RightDockWidgetArea; + } + return y < 0 ? Qt::TopDockWidgetArea :Qt::LeftDockWidgetArea; +} + +bool FormWindow::dropDockWidget(QDesignerDnDItemInterface *item, QPoint global_mouse_pos) +{ + DomUI *dom_ui = item->domUi(); + + QMainWindow *mw = qobject_cast(mainContainer()); + if (!mw) + return false; + + QDesignerResource resource(this); + const FormBuilderClipboard clipboard = resource.paste(dom_ui, mw); + if (clipboard.m_widgets.size() != 1) // multiple-paste from DomUI not supported yet + return false; + + QWidget *centralWidget = mw->centralWidget(); + QPoint localPos = centralWidget->mapFromGlobal(global_mouse_pos); + const QRect centralWidgetAreaRect = centralWidget->rect(); + Qt::DockWidgetArea area = detectDropArea(mw, centralWidgetAreaRect, localPos); + + beginCommand(tr("Drop widget")); + + clearSelection(false); + highlightWidget(mw, QPoint(0, 0), FormWindow::Restore); + + QWidget *widget = clipboard.m_widgets.first(); + + insertWidget(widget, QRect(0, 0, 1, 1), mw); + + selectWidget(widget, true); + mw->setFocus(Qt::MouseFocusReason); // in case focus was in e.g. object inspector + + core()->formWindowManager()->setActiveFormWindow(this); + mainContainer()->activateWindow(); + + QDesignerPropertySheetExtension *propertySheet = qobject_cast(m_core->extensionManager()->extension(widget, Q_TYPEID(QDesignerPropertySheetExtension))); + if (propertySheet) { + const QString dockWidgetAreaName = u"dockWidgetArea"_s; + PropertySheetEnumValue e = qvariant_cast(propertySheet->property(propertySheet->indexOf(dockWidgetAreaName))); + e.value = area; + QVariant v; + v.setValue(e); + SetPropertyCommand *cmd = new SetPropertyCommand(this); + cmd->init(widget, dockWidgetAreaName, v); + m_undoStack.push(cmd); + } + + endCommand(); + return true; +} + +bool FormWindow::dropWidgets(const QList &item_list, QWidget *target, + const QPoint &global_mouse_pos) +{ + + QWidget *parent = target; + if (parent == nullptr) + parent = mainContainer(); + // You can only drop stuff onto the central widget of a QMainWindow + // ### generalize to use container extension + if (QMainWindow *main_win = qobject_cast(target)) { + if (!main_win->centralWidget()) { + designerWarning(tr("A QMainWindow-based form does not contain a central widget.")); + return false; + } + const QPoint main_win_pos = main_win->mapFromGlobal(global_mouse_pos); + const QRect central_wgt_geo = main_win->centralWidget()->geometry(); + if (!central_wgt_geo.contains(main_win_pos)) + return false; + } + + QWidget *container = findContainer(parent, false); + if (container == nullptr) + return false; + + beginCommand(tr("Drop widget")); + + clearSelection(false); + highlightWidget(target, target->mapFromGlobal(global_mouse_pos), FormWindow::Restore); + + QPoint offset; + QDesignerDnDItemInterface *current = nullptr; + QDesignerFormWindowCursorInterface *c = cursor(); + for (QDesignerDnDItemInterface *item : std::as_const(item_list)) { + QWidget *w = item->widget(); + if (!current) + current = item; + if (c->current() == w) { + current = item; + break; + } + } + if (current) { + QRect geom = current->decoration()->geometry(); + QPoint topLeft = container->mapFromGlobal(geom.topLeft()); + offset = designerGrid().snapPoint(topLeft) - topLeft; + } + + for (QDesignerDnDItemInterface *item : std::as_const(item_list)) { + DomUI *dom_ui = item->domUi(); + QRect geometry = item->decoration()->geometry(); + Q_ASSERT(dom_ui != nullptr); + + geometry.moveTopLeft(container->mapFromGlobal(geometry.topLeft()) + offset); + if (item->type() == QDesignerDnDItemInterface::CopyDrop) { // from widget box or CTRL + mouse move + QWidget *widget = createWidget(dom_ui, geometry, parent); + if (!widget) { + endCommand(); + return false; + } + selectWidget(widget, true); + mainContainer()->setFocus(Qt::MouseFocusReason); // in case focus was in e.g. object inspector + } else { // same form move + QWidget *widget = item->widget(); + Q_ASSERT(widget != nullptr); + QDesignerFormWindowInterface *dest = findFormWindow(widget); + if (dest == this) { + dragWidgetWithinForm(widget, geometry, container); + } else { // from other form + FormWindow *source = qobject_cast(item->source()); + Q_ASSERT(source != nullptr); + + source->deleteWidgetList(QWidgetList() << widget); + QWidget *new_widget = createWidget(dom_ui, geometry, parent); + + selectWidget(new_widget, true); + } + } + } + + core()->formWindowManager()->setActiveFormWindow(this); + mainContainer()->activateWindow(); + endCommand(); + return true; +} + +QDir FormWindow::absoluteDir() const +{ + if (fileName().isEmpty()) + return QDir::current(); + + return QFileInfo(fileName()).absoluteDir(); +} + +void FormWindow::layoutDefault(int *margin, int *spacing) +{ + *margin = m_defaultMargin; + *spacing = m_defaultSpacing; +} + +void FormWindow::setLayoutDefault(int margin, int spacing) +{ + m_defaultMargin = margin; + m_defaultSpacing = spacing; +} + +void FormWindow::layoutFunction(QString *margin, QString *spacing) +{ + *margin = m_marginFunction; + *spacing = m_spacingFunction; +} + +void FormWindow::setLayoutFunction(const QString &margin, const QString &spacing) +{ + m_marginFunction = margin; + m_spacingFunction = spacing; +} + +QString FormWindow::pixmapFunction() const +{ + return m_pixmapFunction; +} + +void FormWindow::setPixmapFunction(const QString &pixmapFunction) +{ + m_pixmapFunction = pixmapFunction; +} + +QStringList FormWindow::includeHints() const +{ + return m_includeHints; +} + +void FormWindow::setIncludeHints(const QStringList &includeHints) +{ + m_includeHints = includeHints; +} + +QString FormWindow::exportMacro() const +{ + return m_exportMacro; +} + +void FormWindow::setExportMacro(const QString &exportMacro) +{ + m_exportMacro = exportMacro; +} + +QEditorFormBuilder *FormWindow::createFormBuilder() +{ + return new QDesignerResource(this); +} + +QWidget *FormWindow::formContainer() const +{ + return m_widgetStack->formContainer(); +} + +QUndoStack *FormWindow::commandHistory() const +{ + return &const_cast(m_undoStack); +} + +} // namespace + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/components/formeditor/formwindow.h b/src/tools/designer/src/components/formeditor/formwindow.h new file mode 100644 index 00000000000..149f6a1fb45 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindow.h @@ -0,0 +1,352 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOW_H +#define FORMWINDOW_H + +#include "formeditor_global.h" +#include + +// Qt +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerDnDItemInterface; +class QDesignerTaskMenuExtension; +class DomConnections; +class DomUI; + +class QWidget; +class QAction; +class QLabel; +class QTimer; +class QAction; +class QMenu; +class QRubberBand; + +namespace qdesigner_internal { + +class FormEditor; +class FormWindowCursor; +class WidgetEditorTool; +class FormWindowWidgetStack; +class FormWindowManager; +class FormWindowDnDItem; +class SetPropertyCommand; + +class QT_FORMEDITOR_EXPORT FormWindow: public FormWindowBase +{ + Q_OBJECT + +public: + enum HandleOperation + { + NoHandleOperation, + ResizeHandleOperation, + ChangeLayoutSpanHandleOperation + }; + + explicit FormWindow(FormEditor *core, QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + ~FormWindow() override; + + QDesignerFormEditorInterface *core() const override; + + QDesignerFormWindowCursorInterface *cursor() const override; + + // Overwritten: FormWindowBase + QWidget *formContainer() const override; + + int toolCount() const override; + int currentTool() const override; + void setCurrentTool(int index) override; + QDesignerFormWindowToolInterface *tool(int index) const override; + void registerTool(QDesignerFormWindowToolInterface *tool) override; + + QString author() const override; + void setAuthor(const QString &author) override; + + QString comment() const override; + void setComment(const QString &comment) override; + + void layoutDefault(int *margin, int *spacing) override; + void setLayoutDefault(int margin, int spacing) override; + + void layoutFunction(QString *margin, QString *spacing) override; + void setLayoutFunction(const QString &margin, const QString &spacing) override; + + QString pixmapFunction() const override; + void setPixmapFunction(const QString &pixmapFunction) override; + + QString exportMacro() const override; + void setExportMacro(const QString &exportMacro) override; + + QStringList includeHints() const override; + void setIncludeHints(const QStringList &includeHints) override; + + QString fileName() const override; + void setFileName(const QString &fileName) override; + + QString contents() const override; + bool setContents(QIODevice *dev, QString *errorMessage = nullptr) override; + bool setContents(const QString &) override; + + QDir absoluteDir() const override; + + void simplifySelection(QWidgetList *sel) const override; + + void ensureUniqueObjectName(QObject *object) override; + + QWidget *mainContainer() const override; + void setMainContainer(QWidget *mainContainer) override; + bool isMainContainer(const QWidget *w) const; + + QWidget *currentWidget() const; + + bool hasInsertedChildren(QWidget *w) const; + + QWidgetList selectedWidgets() const; + void clearSelection(bool changePropertyDisplay = true) override; + bool isWidgetSelected(QWidget *w) const; + void selectWidget(QWidget *w, bool select = true) override; + + void selectWidgets(); + void repaintSelection(); + void updateSelection(QWidget *w); + void updateChildSelections(QWidget *w); + void raiseChildSelections(QWidget *w); + void raiseSelection(QWidget *w); + + inline const QWidgetList& widgets() const { return m_widgets; } + inline int widgetCount() const { return m_widgets.size(); } + inline QWidget *widgetAt(int index) const { return m_widgets.at(index); } + + QWidgetList widgets(QWidget *widget) const; + + QWidget *createWidget(DomUI *ui, QRect rect, QWidget *target); + + bool isManaged(QWidget *w) const override; + + void manageWidget(QWidget *w) override; + void unmanageWidget(QWidget *w) override; + + QUndoStack *commandHistory() const override; + void beginCommand(const QString &description) override; + void endCommand() override; + + bool blockSelectionChanged(bool blocked) override; + void emitSelectionChanged() override; + + bool unify(QObject *w, QString &s, bool changeIt); + + bool isDirty() const override; + void setDirty(bool dirty) override; + + static FormWindow *findFormWindow(QWidget *w); + + virtual QWidget *containerAt(const QPoint &pos); + QWidget *widgetAt(const QPoint &pos) override; + void highlightWidget(QWidget *w, const QPoint &pos, HighlightMode mode = Highlight) override; + + void updateOrderIndicators(); + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event); + + QStringList resourceFiles() const override; + void addResourceFile(const QString &path) override; + void removeResourceFile(const QString &path) override; + + void resizeWidget(QWidget *widget, QRect geometry); + + bool dropDockWidget(QDesignerDnDItemInterface *item, QPoint global_mouse_pos); + bool dropWidgets(const QList &item_list, QWidget *target, + const QPoint &global_mouse_pos) override; + + QWidget *findContainer(QWidget *w, bool excludeLayout) const override; + // for WidgetSelection only. + QWidget *designerWidget(QWidget *w) const; + + // Initialize and return a popup menu for a managed widget + QMenu *initializePopupMenu(QWidget *managedWidget) override; + +#if QT_CONFIG(clipboard) + void paste(PasteMode pasteMode) override; +#endif + QEditorFormBuilder *createFormBuilder() override; + + bool eventFilter(QObject *watched, QEvent *event) override; + + HandleOperation handleOperation() const { return m_handleOperation; } + void setHandleOperation(HandleOperation o) { m_handleOperation = o; } + +signals: + void contextMenuRequested(QMenu *menu, QWidget *widget); + +public slots: + void deleteWidgets(); + void raiseWidgets(); + void lowerWidgets(); +#if QT_CONFIG(clipboard) + void copy(); + void cut(); + void paste(); +#endif + void selectAll(); + + void createLayout(int type, QWidget *container = nullptr); + void morphLayout(QWidget *container, int newType); + void breakLayout(QWidget *w); + + void editContents(); + +protected: + virtual QMenu *createPopupMenu(QWidget *w); + void resizeEvent(QResizeEvent *e) override; + + void insertWidget(QWidget *w, QRect rect, QWidget *target, bool already_in_form = false); + +private slots: + void selectionChangedTimerDone(); + void checkSelection(); + void checkSelectionNow(); + void slotSelectWidget(QAction *); + void slotCleanChanged(bool); + +private: + enum MouseState { + NoMouseState, + // Double click received + MouseDoubleClicked, + // Drawing selection rubber band rectangle + MouseDrawRubber, + // Started a move operation + MouseMoveDrag, + // Click on a widget whose parent is selected. Defer selection to release + MouseDeferredSelection + }; + MouseState m_mouseState; + QPointer m_lastClickedWidget; + + void init(); + void initializeCoreTools(); + + int getValue(QRect rect, int key, bool size) const; + int calcValue(int val, bool forward, bool snap, int snapOffset) const; + void handleClickSelection(QWidget *managedWidget, unsigned mouseFlags); + + bool frameNeeded(QWidget *w) const; + + enum RectType { Insert, Rubber }; + + void startRectDraw(QPoint global, QWidget *, RectType t); + void continueRectDraw(QPoint global, QWidget *, RectType t); + void endRectDraw(); + + QWidget *containerAt(QPoint pos, QWidget *notParentOf); + + void checkPreviewGeometry(QRect &r); + + bool handleContextMenu(QWidget *widget, QWidget *managedWidget, QContextMenuEvent *e); + bool handleMouseButtonDblClickEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMousePressEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMouseMoveEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMouseReleaseEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleKeyPressEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e); + bool handleKeyReleaseEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e); + + bool isCentralWidget(QWidget *w) const; + + bool setCurrentWidget(QWidget *currentWidget); + bool trySelectWidget(QWidget *w, bool select); + + void dragWidgetWithinForm(QWidget *widget, QRect targetGeometry, QWidget *targetContainer); + + void setCursorToAll(const QCursor &c, QWidget *start); + + QPoint mapToForm(const QWidget *w, QPoint pos) const; + bool canBeBuddy(QWidget *w) const; + + QWidget *findTargetContainer(QWidget *widget) const; + + void clearMainContainer(); + + static int widgetDepth(const QWidget *w); + static bool isChildOf(const QWidget *c, const QWidget *p); + + void editWidgets() override; + + void updateWidgets(); + + void handleArrowKeyEvent(int key, Qt::KeyboardModifiers modifiers); + + void layoutSelection(int type); + void layoutContainer(QWidget *w, int type); + +private: + QWidget *innerContainer(QWidget *outerContainer) const; + QWidget *containerForPaste() const; + QAction *createSelectAncestorSubMenu(QWidget *w); + void selectSingleWidget(QWidget *w); + + FormEditor *m_core; + FormWindowCursor *m_cursor; + QWidget *m_mainContainer = nullptr; + QWidget *m_currentWidget = nullptr; + + bool m_blockSelectionChanged = false; + + QPoint m_rectAnchor; + QRect m_currRect; + + QWidgetList m_widgets; + QSet m_insertedWidgets; + + class Selection; + Selection *m_selection; + + QPoint m_startPos; + + QUndoStack m_undoStack; + + QString m_fileName; + + using PaletteAndFill = std::pair; + QHash m_palettesBeforeHighlight; + + QRubberBand *m_rubberBand = nullptr; + + QTimer *m_selectionChangedTimer = nullptr; + QTimer *m_checkSelectionTimer = nullptr; + QTimer *m_geometryChangedTimer = nullptr; + + FormWindowWidgetStack *m_widgetStack; + WidgetEditorTool *m_widgetEditor = nullptr; + + QStringList m_resourceFiles; + + QString m_comment; + QString m_author; + QString m_pixmapFunction; + int m_defaultMargin, m_defaultSpacing; + QString m_marginFunction, m_spacingFunction; + QString m_exportMacro; + QStringList m_includeHints; + + QPoint m_contextMenuPosition; + HandleOperation m_handleOperation = NoHandleOperation; + +private: + friend class WidgetEditorTool; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOW_H diff --git a/src/tools/designer/src/components/formeditor/formwindow_dnditem.cpp b/src/tools/designer/src/components/formeditor/formwindow_dnditem.cpp new file mode 100644 index 00000000000..2b7a7a052dc --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindow_dnditem.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindow_dnditem.h" +#include "formwindow.h" + +#include +#include +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +static QWidget *decorationFromWidget(QWidget *w) +{ + QLabel *label = new QLabel(nullptr, Qt::ToolTip); + QPixmap pm = w->grab(QRect(0, 0, -1, -1)); + label->setPixmap(pm); + label->resize((QSizeF(pm.size()) / pm.devicePixelRatio()).toSize()); + + return label; +} + +static DomUI *widgetToDom(QWidget *widget, FormWindow *form) +{ + QDesignerResource builder(form); + builder.setSaveRelative(false); + return builder.copy(FormBuilderClipboard(widget)); +} + +FormWindowDnDItem::FormWindowDnDItem(QDesignerDnDItemInterface::DropType type, FormWindow *form, + QWidget *widget, QPoint global_mouse_pos) + : QDesignerDnDItem(type, form) +{ + QWidget *decoration = decorationFromWidget(widget); + QPoint pos = widget->mapToGlobal(QPoint(0, 0)); + decoration->move(pos); + + init(nullptr, widget, decoration, global_mouse_pos); +} + +DomUI *FormWindowDnDItem::domUi() const +{ + DomUI *result = QDesignerDnDItem::domUi(); + if (result != nullptr) + return result; + FormWindow *form = qobject_cast(source()); + if (widget() == nullptr || form == nullptr) + return nullptr; + + QtResourceModel *resourceModel = form->core()->resourceModel(); + QtResourceSet *currentResourceSet = resourceModel->currentResourceSet(); + /* Short: + * We need to activate the original resourceSet associated with a form + * to properly generate the dom resource includes. + * Long: + * widgetToDom() calls copy() on QDesignerResource. It generates the + * Dom structure. In order to create DomResources properly we need to + * have the associated ResourceSet active (QDesignerResource::saveResources() + * queries the resource model for a qrc path for the given resource file: + * qrcFile = m_core->resourceModel()->qrcPath(ri->text()); + * This works only when the resource file comes from the active + * resourceSet */ + resourceModel->setCurrentResourceSet(form->resourceSet()); + + result = widgetToDom(widget(), form); + const_cast(this)->setDomUi(result); + resourceModel->setCurrentResourceSet(currentResourceSet); + return result; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/formwindow_dnditem.h b/src/tools/designer/src/components/formeditor/formwindow_dnditem.h new file mode 100644 index 00000000000..dab5d5f3254 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindow_dnditem.h @@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOW_DNDITEM_H +#define FORMWINDOW_DNDITEM_H + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class FormWindow; + +class FormWindowDnDItem : public QDesignerDnDItem +{ +public: + FormWindowDnDItem(QDesignerDnDItemInterface::DropType type, FormWindow *form, + QWidget *widget, QPoint global_mouse_pos); + DomUI *domUi() const override; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOW_DNDITEM_H diff --git a/src/tools/designer/src/components/formeditor/formwindow_widgetstack.cpp b/src/tools/designer/src/components/formeditor/formwindow_widgetstack.cpp new file mode 100644 index 00000000000..e42345b4111 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindow_widgetstack.cpp @@ -0,0 +1,184 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindow_widgetstack.h" +#include + +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +FormWindowWidgetStack::FormWindowWidgetStack(QObject *parent) : + QObject(parent), + m_formContainer(new QWidget), + m_formContainerLayout(new QStackedLayout), + m_layout(new QStackedLayout) +{ + m_layout->setContentsMargins(QMargins()); + m_layout->setSpacing(0); + m_layout->setStackingMode(QStackedLayout::StackAll); + + // We choose a QStackedLayout as immediate layout for + // the form windows as it ignores the sizePolicy of + // its child (for example, Fixed would cause undesired side effects). + m_formContainerLayout->setContentsMargins(QMargins()); + m_formContainer->setObjectName(u"formContainer"_s); + m_formContainer->setLayout(m_formContainerLayout); + m_formContainerLayout->setStackingMode(QStackedLayout::StackAll); + // System settings might have different background colors, autofill them + // (affects for example mainwindow status bars) + m_formContainer->setAutoFillBackground(true); +} + +FormWindowWidgetStack::~FormWindowWidgetStack() = default; + +int FormWindowWidgetStack::count() const +{ + return m_tools.size(); +} + +QDesignerFormWindowToolInterface *FormWindowWidgetStack::currentTool() const +{ + return tool(currentIndex()); +} + +void FormWindowWidgetStack::setCurrentTool(int index) +{ + const int cnt = count(); + if (index < 0 || index >= cnt) { + qDebug("FormWindowWidgetStack::setCurrentTool(): invalid index: %d", index); + return; + } + + const int cur = currentIndex(); + if (index == cur) + return; + + if (cur != -1) + m_tools.at(cur)->deactivated(); + + + m_layout->setCurrentIndex(index); + // Show the widget editor and the current tool + for (int i = 0; i < cnt; i++) + m_tools.at(i)->editor()->setVisible(i == 0 || i == index); + + QDesignerFormWindowToolInterface *tool = m_tools.at(index); + tool->activated(); + + emit currentToolChanged(index); +} + +void FormWindowWidgetStack::setSenderAsCurrentTool() +{ + QDesignerFormWindowToolInterface *tool = nullptr; + QAction *action = qobject_cast(sender()); + if (action == nullptr) { + qDebug("FormWindowWidgetStack::setSenderAsCurrentTool(): sender is not a QAction"); + return; + } + + for (QDesignerFormWindowToolInterface *t : std::as_const(m_tools)) { + if (action == t->action()) { + tool = t; + break; + } + } + + if (tool == nullptr) { + qDebug("FormWindowWidgetStack::setSenderAsCurrentTool(): unknown tool"); + return; + } + + setCurrentTool(tool); +} + +int FormWindowWidgetStack::indexOf(QDesignerFormWindowToolInterface *tool) const +{ + return m_tools.indexOf(tool); +} + +void FormWindowWidgetStack::setCurrentTool(QDesignerFormWindowToolInterface *tool) +{ + int index = indexOf(tool); + if (index == -1) { + qDebug("FormWindowWidgetStack::setCurrentTool(): unknown tool"); + return; + } + + setCurrentTool(index); +} + +void FormWindowWidgetStack::setMainContainer(QWidget *w) +{ + // This code is triggered once by the formwindow and + // by integrations doing "revert to saved". Anything changing? + const int previousCount = m_formContainerLayout->count(); + QWidget *previousMainContainer = previousCount + ? m_formContainerLayout->itemAt(0)->widget() : nullptr; + if (previousMainContainer == w) + return; + // Swap + if (previousCount) + delete m_formContainerLayout->takeAt(0); + if (w) + m_formContainerLayout->addWidget(w); +} + +void FormWindowWidgetStack::addTool(QDesignerFormWindowToolInterface *tool) +{ + if (QWidget *w = tool->editor()) { + w->setVisible(m_layout->count() == 0); // Initially only form editor is visible + m_layout->addWidget(w); + } else { + // The form editor might not have a tool initially, use dummy. Assert on anything else + Q_ASSERT(m_tools.isEmpty()); + m_layout->addWidget(m_formContainer); + } + + m_tools.append(tool); + + connect(tool->action(), &QAction::triggered, + this, &FormWindowWidgetStack::setSenderAsCurrentTool); +} + +QDesignerFormWindowToolInterface *FormWindowWidgetStack::tool(int index) const +{ + if (index < 0 || index >= count()) + return nullptr; + + return m_tools.at(index); +} + +int FormWindowWidgetStack::currentIndex() const +{ + return m_layout->currentIndex(); +} + +QWidget *FormWindowWidgetStack::defaultEditor() const +{ + if (m_tools.isEmpty()) + return nullptr; + + return m_tools.at(0)->editor(); +} + +QLayout *FormWindowWidgetStack::layout() const +{ + return m_layout; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/formwindow_widgetstack.h b/src/tools/designer/src/components/formeditor/formwindow_widgetstack.h new file mode 100644 index 00000000000..9961754c840 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindow_widgetstack.h @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOW_WIDGETSTACK_H +#define FORMWINDOW_WIDGETSTACK_H + +#include "formeditor_global.h" + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowToolInterface; + +class QStackedLayout; +class QWidget; + +namespace qdesigner_internal { + +class QT_FORMEDITOR_EXPORT FormWindowWidgetStack: public QObject +{ + Q_OBJECT +public: + FormWindowWidgetStack(QObject *parent = nullptr); + ~FormWindowWidgetStack() override; + + QLayout *layout() const; + + int count() const; + QDesignerFormWindowToolInterface *tool(int index) const; + QDesignerFormWindowToolInterface *currentTool() const; + int currentIndex() const; + int indexOf(QDesignerFormWindowToolInterface *tool) const; + + void setMainContainer(QWidget *w = nullptr); + + // Return the widget containing the form which can be used to apply embedded design settings to. + // These settings should not affect the other editing tools. + QWidget *formContainer() const { return m_formContainer; } + +signals: + void currentToolChanged(int index); + +public slots: + void addTool(QDesignerFormWindowToolInterface *tool); + void setCurrentTool(QDesignerFormWindowToolInterface *tool); + void setCurrentTool(int index); + void setSenderAsCurrentTool(); + +protected: + QWidget *defaultEditor() const; + +private: + QList m_tools; + QWidget *m_formContainer; + QStackedLayout *m_formContainerLayout; + QStackedLayout *m_layout; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOW_WIDGETSTACK_H diff --git a/src/tools/designer/src/components/formeditor/formwindowcursor.cpp b/src/tools/designer/src/components/formeditor/formwindowcursor.cpp new file mode 100644 index 00000000000..4bf230e1682 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindowcursor.cpp @@ -0,0 +1,171 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindowcursor.h" +#include "formwindow.h" + +// sdk +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +FormWindowCursor::FormWindowCursor(FormWindow *fw, QObject *parent) + : QObject(parent), + m_formWindow(fw) +{ + update(); + connect(fw, &QDesignerFormWindowInterface::changed, this, &FormWindowCursor::update); +} + +FormWindowCursor::~FormWindowCursor() = default; + +QDesignerFormWindowInterface *FormWindowCursor::formWindow() const +{ + return m_formWindow; +} + +bool FormWindowCursor::movePosition(MoveOperation op, MoveMode mode) +{ + if (widgetCount() == 0) + return false; + + int iterator = position(); + + if (mode == MoveAnchor) + m_formWindow->clearSelection(false); + + switch (op) { + case Next: + ++iterator; + if (iterator >= widgetCount()) + iterator = 0; + + m_formWindow->selectWidget(m_formWindow->widgetAt(iterator), true); + return true; + + case Prev: + --iterator; + if (iterator < 0) + iterator = widgetCount() - 1; + + if (iterator < 0) + return false; + + m_formWindow->selectWidget(m_formWindow->widgetAt(iterator), true); + return true; + + default: + return false; + } +} + +int FormWindowCursor::position() const +{ + const int index = m_formWindow->widgets().indexOf(current()); + return index == -1 ? 0 : index; +} + +void FormWindowCursor::setPosition(int pos, MoveMode mode) +{ + if (!widgetCount()) + return; + + if (mode == MoveAnchor) + m_formWindow->clearSelection(false); + + if (pos >= widgetCount()) + pos = 0; + + m_formWindow->selectWidget(m_formWindow->widgetAt(pos), true); +} + +QWidget *FormWindowCursor::current() const +{ + return m_formWindow->currentWidget(); +} + +bool FormWindowCursor::hasSelection() const +{ + return !m_formWindow->selectedWidgets().isEmpty(); +} + +int FormWindowCursor::selectedWidgetCount() const +{ + int N = m_formWindow->selectedWidgets().size(); + return N ? N : 1; +} + +QWidget *FormWindowCursor::selectedWidget(int index) const +{ + return hasSelection() + ? m_formWindow->selectedWidgets().at(index) + : m_formWindow->mainContainer(); +} + +void FormWindowCursor::update() +{ + // ### todo +} + +int FormWindowCursor::widgetCount() const +{ + return m_formWindow->widgetCount(); +} + +QWidget *FormWindowCursor::widget(int index) const +{ + return m_formWindow->widgetAt(index); +} + +void FormWindowCursor::setProperty(const QString &name, const QVariant &value) +{ + + // build selection + const int N = selectedWidgetCount(); + Q_ASSERT(N); + + QObjectList selection; + for (int i=0; iinit(selection, name, value, current())) { + m_formWindow->commandHistory()->push(setPropertyCommand); + } else { + delete setPropertyCommand; + qDebug() << "Unable to set property " << name << '.'; + } +} + +void FormWindowCursor::setWidgetProperty(QWidget *widget, const QString &name, const QVariant &value) +{ + SetPropertyCommand *cmd = new SetPropertyCommand(m_formWindow); + if (cmd->init(widget, name, value)) { + m_formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "Unable to set property " << name << '.'; + } +} + +void FormWindowCursor::resetWidgetProperty(QWidget *widget, const QString &name) +{ + ResetPropertyCommand *cmd = new ResetPropertyCommand(m_formWindow); + if (cmd->init(widget, name)) { + m_formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "Unable to reset property " << name << '.'; + } +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/formwindowcursor.h b/src/tools/designer/src/components/formeditor/formwindowcursor.h new file mode 100644 index 00000000000..c3559a5e032 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindowcursor.h @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOWCURSOR_H +#define FORMWINDOWCURSOR_H + +#include "formeditor_global.h" +#include "formwindow.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QT_FORMEDITOR_EXPORT FormWindowCursor: public QObject, public QDesignerFormWindowCursorInterface +{ + Q_OBJECT +public: + explicit FormWindowCursor(FormWindow *fw, QObject *parent = nullptr); + ~FormWindowCursor() override; + + QDesignerFormWindowInterface *formWindow() const override; + + bool movePosition(MoveOperation op, MoveMode mode) override; + + int position() const override; + void setPosition(int pos, MoveMode mode) override; + + QWidget *current() const override; + + int widgetCount() const override; + QWidget *widget(int index) const override; + + bool hasSelection() const override; + int selectedWidgetCount() const override; + QWidget *selectedWidget(int index) const override; + + void setProperty(const QString &name, const QVariant &value) override; + void setWidgetProperty(QWidget *widget, const QString &name, const QVariant &value) override; + void resetWidgetProperty(QWidget *widget, const QString &name) override; + +public slots: + void update(); + +private: + FormWindow *m_formWindow; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOWCURSOR_H diff --git a/src/tools/designer/src/components/formeditor/formwindowmanager.cpp b/src/tools/designer/src/components/formeditor/formwindowmanager.cpp new file mode 100644 index 00000000000..2f94397cde2 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindowmanager.cpp @@ -0,0 +1,1050 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// components/formeditor +#include "formwindowmanager.h" +#include "formwindow_dnditem.h" +#include "formwindow.h" +#include "formeditor.h" +#include "widgetselection.h" +#include "previewactiongroup.h" +#include "formwindowsettings.h" + +// shared +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// SDK +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#if QT_CONFIG(clipboard) +# include +#endif +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + enum { debugFWM = 0 }; +} + +static inline QString whatsThisFrom(const QString &str) { /// ### implement me! + return str; +} + +// find the first child of w in a sequence +template +static inline Iterator findFirstChildOf(Iterator it,Iterator end, const QWidget *w) +{ + for (;it != end; ++it) { + if (w->isAncestorOf(*it)) + return it; + } + return it; +} + +namespace qdesigner_internal { + +FormWindowManager::FormWindowManager(QDesignerFormEditorInterface *core, QObject *parent) : + QDesignerFormWindowManager(parent), + m_core(core), + m_activeFormWindow(nullptr), + m_previewManager(new PreviewManager(PreviewManager::SingleFormNonModalPreview, this)), + m_createLayoutContext(LayoutContainer), + m_morphLayoutContainer(nullptr), + m_actionGroupPreviewInStyle(nullptr), + m_actionShowFormWindowSettingsDialog(nullptr) +{ + setupActions(); + qApp->installEventFilter(this); +} + +FormWindowManager::~FormWindowManager() +{ + qDeleteAll(m_formWindows); +} + +QDesignerFormEditorInterface *FormWindowManager::core() const +{ + return m_core; +} + +QDesignerFormWindowInterface *FormWindowManager::activeFormWindow() const +{ + return m_activeFormWindow; +} + +int FormWindowManager::formWindowCount() const +{ + return m_formWindows.size(); +} + +QDesignerFormWindowInterface *FormWindowManager::formWindow(int index) const +{ + return m_formWindows.at(index); +} + +bool FormWindowManager::eventFilter(QObject *o, QEvent *e) +{ + if (!o->isWidgetType()) + return false; + + // If we don't have an active form, we only listen for WindowActivate to speed up integrations + const QEvent::Type eventType = e->type(); + if (m_activeFormWindow == nullptr && eventType != QEvent::WindowActivate) + return false; + + switch (eventType) { // Uninteresting events + case QEvent::Create: + case QEvent::Destroy: + case QEvent::ActionAdded: + case QEvent::ActionChanged: + case QEvent::ActionRemoved: + case QEvent::ChildAdded: + case QEvent::ChildPolished: + case QEvent::ChildRemoved: +#if QT_CONFIG(clipboard) + case QEvent::Clipboard: +#endif + case QEvent::ContentsRectChange: + case QEvent::DeferredDelete: + case QEvent::FileOpen: + case QEvent::LanguageChange: + case QEvent::MetaCall: + case QEvent::ModifiedChange: + case QEvent::Paint: + case QEvent::PaletteChange: + case QEvent::ParentAboutToChange: + case QEvent::ParentChange: + case QEvent::Polish: + case QEvent::PolishRequest: + case QEvent::QueryWhatsThis: + case QEvent::StatusTip: + case QEvent::StyleChange: + case QEvent::Timer: + case QEvent::ToolBarChange: + case QEvent::ToolTip: + case QEvent::WhatsThis: + case QEvent::WhatsThisClicked: + case QEvent::WinIdChange: + case QEvent::DynamicPropertyChange: + case QEvent::HoverEnter: + case QEvent::HoverLeave: + case QEvent::HoverMove: + case QEvent::AcceptDropsChange: + return false; + default: + break; + } + + QWidget *widget = static_cast(o); + + if (qobject_cast(widget)) { // ### remove me + return false; + } + + FormWindow *fw = FormWindow::findFormWindow(widget); + if (fw == nullptr) { + return false; + } + + if (QWidget *managedWidget = findManagedWidget(fw, widget)) { + // Prevent MDI subwindows from being closed by clicking at the title bar + if (managedWidget != widget && eventType == QEvent::Close) { + e->ignore(); + return true; + } + switch (eventType) { + case QEvent::LayoutRequest: + // QTBUG-61439: Suppress layout request while changing the QGridLayout + // span of a QTabWidget, which sends LayoutRequest in resizeEvent(). + if (fw->handleOperation() == FormWindow::ChangeLayoutSpanHandleOperation) { + e->ignore(); + return true; + } + break; + + case QEvent::WindowActivate: { + if (fw->parentWidget()->isWindow() && fw->isMainContainer(managedWidget) && activeFormWindow() != fw) { + setActiveFormWindow(fw); + } + } break; + + case QEvent::WindowDeactivate: { + if (o == fw && o == activeFormWindow()) + fw->repaintSelection(); + } break; + + case QEvent::KeyPress: { + QKeyEvent *ke = static_cast(e); + if (ke->key() == Qt::Key_Escape) { + ke->accept(); + return true; + } + } + Q_FALLTHROUGH(); // don't break... + + // Embedded Design: Drop on different form: Make sure the right form + // window/device is active before having the widget created by the factory + case QEvent::Drop: + if (activeFormWindow() != fw) + setActiveFormWindow(fw); + Q_FALLTHROUGH(); // don't break... + default: { + if (fw->handleEvent(widget, managedWidget, e)) { + return true; + } + } break; + + } // end switch + } + + return false; +} + +void FormWindowManager::addFormWindow(QDesignerFormWindowInterface *w) +{ + FormWindow *formWindow = qobject_cast(w); + if (!formWindow || m_formWindows.contains(formWindow)) + return; + + connect(formWindow, &QDesignerFormWindowInterface::selectionChanged, + this, &FormWindowManager::slotUpdateActions); + connect(formWindow->commandHistory(), &QUndoStack::indexChanged, + this, &FormWindowManager::slotUpdateActions); + connect(formWindow, &QDesignerFormWindowInterface::toolChanged, + this, &FormWindowManager::slotUpdateActions); + + if (ActionEditor *ae = qobject_cast(m_core->actionEditor())) { + connect(w, &QDesignerFormWindowInterface::mainContainerChanged, + ae, &ActionEditor::mainContainerChanged); + } + if (QDesignerObjectInspector *oi = qobject_cast(m_core->objectInspector())) + connect(w, &QDesignerFormWindowInterface::mainContainerChanged, + oi, &QDesignerObjectInspector::mainContainerChanged); + + m_formWindows.append(formWindow); + emit formWindowAdded(formWindow); +} + +void FormWindowManager::removeFormWindow(QDesignerFormWindowInterface *w) +{ + FormWindow *formWindow = qobject_cast(w); + + int idx = m_formWindows.indexOf(formWindow); + if (!formWindow || idx == -1) + return; + + formWindow->disconnect(this); + m_formWindows.removeAt(idx); + emit formWindowRemoved(formWindow); + + if (formWindow == m_activeFormWindow) + setActiveFormWindow(nullptr); + + // Make sure that widget box is enabled by default + if (m_formWindows.isEmpty() && m_core->widgetBox()) + m_core->widgetBox()->setEnabled(true); + +} + +void FormWindowManager::setActiveFormWindow(QDesignerFormWindowInterface *w) +{ + FormWindow *formWindow = qobject_cast(w); + + if (formWindow == m_activeFormWindow) + return; + + FormWindow *old = m_activeFormWindow; + + m_activeFormWindow = formWindow; + + QtResourceSet *resourceSet = nullptr; + if (formWindow) + resourceSet = formWindow->resourceSet(); + m_core->resourceModel()->setCurrentResourceSet(resourceSet); + + slotUpdateActions(); + + if (m_activeFormWindow) { + m_activeFormWindow->repaintSelection(); + if (old) + old->repaintSelection(); + } + + emit activeFormWindowChanged(m_activeFormWindow); + + if (m_activeFormWindow) { + m_activeFormWindow->emitSelectionChanged(); + m_activeFormWindow->commandHistory()->setActive(); + // Trigger setActiveSubWindow on mdi area unless we are in toplevel mode + QMdiSubWindow *mdiSubWindow = nullptr; + if (QWidget *formwindow = m_activeFormWindow->parentWidget()) { + mdiSubWindow = qobject_cast(formwindow->parentWidget()); + } + if (mdiSubWindow) { + for (QWidget *parent = mdiSubWindow->parentWidget(); parent; parent = parent->parentWidget()) { + if (QMdiArea *mdiArea = qobject_cast(parent)) { + mdiArea->setActiveSubWindow(mdiSubWindow); + break; + } + } + } + } +} + +void FormWindowManager::closeAllPreviews() +{ + m_previewManager->closeAllPreviews(); +} + +QWidget *FormWindowManager::findManagedWidget(FormWindow *fw, QWidget *w) +{ + while (w && w != fw) { + if (fw->isManaged(w)) + break; + w = w->parentWidget(); + } + return w; +} + +void FormWindowManager::setupActions() +{ +#if QT_CONFIG(clipboard) + const QIcon cutIcon = createIconSet(QIcon::ThemeIcon::EditCut, + "editcut.png"_L1); + m_actionCut = new QAction(cutIcon, tr("Cu&t"), this); + m_actionCut->setObjectName(u"__qt_cut_action"_s); + m_actionCut->setShortcut(QKeySequence::Cut); + m_actionCut->setStatusTip(tr("Cuts the selected widgets and puts them on the clipboard")); + m_actionCut->setWhatsThis(whatsThisFrom(u"Edit|Cut"_s)); + connect(m_actionCut, &QAction::triggered, this, &FormWindowManager::slotActionCutActivated); + m_actionCut->setEnabled(false); + + const QIcon copyIcon = createIconSet(QIcon::ThemeIcon::EditCopy, "editcopy.png"_L1); + m_actionCopy = new QAction(copyIcon, tr("&Copy"), this); + m_actionCopy->setObjectName(u"__qt_copy_action"_s); + m_actionCopy->setShortcut(QKeySequence::Copy); + m_actionCopy->setStatusTip(tr("Copies the selected widgets to the clipboard")); + m_actionCopy->setWhatsThis(whatsThisFrom(u"Edit|Copy"_s)); + connect(m_actionCopy, &QAction::triggered, this, &FormWindowManager::slotActionCopyActivated); + m_actionCopy->setEnabled(false); + + const QIcon pasteIcon = createIconSet(QIcon::ThemeIcon::EditPaste, "editpaste.png"_L1); + m_actionPaste = new QAction(pasteIcon, tr("&Paste"), this); + m_actionPaste->setObjectName(u"__qt_paste_action"_s); + m_actionPaste->setShortcut(QKeySequence::Paste); + m_actionPaste->setStatusTip(tr("Pastes the clipboard's contents")); + m_actionPaste->setWhatsThis(whatsThisFrom(u"Edit|Paste"_s)); + connect(m_actionPaste, &QAction::triggered, this, &FormWindowManager::slotActionPasteActivated); + m_actionPaste->setEnabled(false); +#endif + + m_actionDelete = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::EditDelete), + tr("&Delete"), this); + m_actionDelete->setObjectName(u"__qt_delete_action"_s); + m_actionDelete->setStatusTip(tr("Deletes the selected widgets")); + m_actionDelete->setWhatsThis(whatsThisFrom(u"Edit|Delete"_s)); + connect(m_actionDelete, &QAction::triggered, this, &FormWindowManager::slotActionDeleteActivated); + m_actionDelete->setEnabled(false); + + m_actionSelectAll = new QAction(tr("Select &All"), this); + m_actionSelectAll->setObjectName(u"__qt_select_all_action"_s); + m_actionSelectAll->setShortcut(QKeySequence::SelectAll); + m_actionSelectAll->setStatusTip(tr("Selects all widgets")); + m_actionSelectAll->setWhatsThis(whatsThisFrom(u"Edit|Select All"_s)); + connect(m_actionSelectAll, &QAction::triggered, this, &FormWindowManager::slotActionSelectAllActivated); + m_actionSelectAll->setEnabled(false); + + m_actionRaise = new QAction(createIconSet("editraise.png"_L1), + tr("Bring to &Front"), this); + m_actionRaise->setObjectName(u"__qt_raise_action"_s); + m_actionRaise->setShortcut(Qt::CTRL | Qt::Key_L); + m_actionRaise->setStatusTip(tr("Raises the selected widgets")); + m_actionRaise->setWhatsThis(tr("Raises the selected widgets")); + connect(m_actionRaise, &QAction::triggered, this, &FormWindowManager::slotActionRaiseActivated); + m_actionRaise->setEnabled(false); + + m_actionLower = new QAction(createIconSet("editlower.png"_L1), + tr("Send to &Back"), this); + m_actionLower->setObjectName(u"__qt_lower_action"_s); + m_actionLower->setShortcut(Qt::CTRL | Qt::Key_K); + m_actionLower->setStatusTip(tr("Lowers the selected widgets")); + m_actionLower->setWhatsThis(tr("Lowers the selected widgets")); + connect(m_actionLower, &QAction::triggered, this, &FormWindowManager::slotActionLowerActivated); + m_actionLower->setEnabled(false); + + m_actionAdjustSize = new QAction(createIconSet("adjustsize.png"_L1), + tr("Adjust &Size"), this); + m_actionAdjustSize->setObjectName(u"__qt_adjust_size_action"_s); + m_actionAdjustSize->setShortcut(Qt::CTRL | Qt::Key_J); + m_actionAdjustSize->setStatusTip(tr("Adjusts the size of the selected widget")); + m_actionAdjustSize->setWhatsThis(whatsThisFrom(u"Layout|Adjust Size"_s)); + connect(m_actionAdjustSize, &QAction::triggered, this, &FormWindowManager::slotActionAdjustSizeActivated); + m_actionAdjustSize->setEnabled(false); + + + m_actionHorizontalLayout = new QAction(createIconSet("edithlayout.png"_L1), + tr("Lay Out &Horizontally"), this); + m_actionHorizontalLayout->setObjectName(u"__qt_horizontal_layout_action"_s); + m_actionHorizontalLayout->setShortcut(Qt::CTRL | Qt::Key_1); + m_actionHorizontalLayout->setStatusTip(tr("Lays out the selected widgets horizontally")); + m_actionHorizontalLayout->setWhatsThis(whatsThisFrom(u"Layout|Lay Out Horizontally"_s)); + m_actionHorizontalLayout->setData(LayoutInfo::HBox); + m_actionHorizontalLayout->setEnabled(false); + connect(m_actionHorizontalLayout, &QAction::triggered, this, &FormWindowManager::createLayout); + + m_actionVerticalLayout = new QAction(createIconSet("editvlayout.png"_L1), + tr("Lay Out &Vertically"), this); + m_actionVerticalLayout->setObjectName(u"__qt_vertical_layout_action"_s); + m_actionVerticalLayout->setShortcut(Qt::CTRL | Qt::Key_2); + m_actionVerticalLayout->setStatusTip(tr("Lays out the selected widgets vertically")); + m_actionVerticalLayout->setWhatsThis(whatsThisFrom(u"Layout|Lay Out Vertically"_s)); + m_actionVerticalLayout->setData(LayoutInfo::VBox); + m_actionVerticalLayout->setEnabled(false); + connect(m_actionVerticalLayout, &QAction::triggered, this, &FormWindowManager::createLayout); + + QIcon formIcon = QIcon::fromTheme(u"designer-form-layout"_s, + createIconSet("editform.png"_L1)); + m_actionFormLayout = new QAction(formIcon, tr("Lay Out in a &Form Layout"), this); + m_actionFormLayout->setObjectName(u"__qt_form_layout_action"_s); + m_actionFormLayout->setShortcut(Qt::CTRL | Qt::Key_6); + m_actionFormLayout->setStatusTip(tr("Lays out the selected widgets in a form layout")); + m_actionFormLayout->setWhatsThis(whatsThisFrom(u"Layout|Lay Out in a Form"_s)); + m_actionFormLayout->setData(LayoutInfo::Form); + m_actionFormLayout->setEnabled(false); + connect(m_actionFormLayout, &QAction::triggered, this, &FormWindowManager::createLayout); + + m_actionGridLayout = new QAction(createIconSet("editgrid.png"_L1), + tr("Lay Out in a &Grid"), this); + m_actionGridLayout->setObjectName(u"__qt_grid_layout_action"_s); + m_actionGridLayout->setShortcut(Qt::CTRL | Qt::Key_5); + m_actionGridLayout->setStatusTip(tr("Lays out the selected widgets in a grid")); + m_actionGridLayout->setWhatsThis(whatsThisFrom(u"Layout|Lay Out in a Grid"_s)); + m_actionGridLayout->setData(LayoutInfo::Grid); + m_actionGridLayout->setEnabled(false); + connect(m_actionGridLayout, &QAction::triggered, this, &FormWindowManager::createLayout); + + m_actionSplitHorizontal = new QAction(createIconSet("edithlayoutsplit.png"_L1), + tr("Lay Out Horizontally in S&plitter"), this); + m_actionSplitHorizontal->setObjectName(u"__qt_split_horizontal_action"_s); + m_actionSplitHorizontal->setShortcut(Qt::CTRL | Qt::Key_3); + m_actionSplitHorizontal->setStatusTip(tr("Lays out the selected widgets horizontally in a splitter")); + m_actionSplitHorizontal->setWhatsThis(whatsThisFrom(u"Layout|Lay Out Horizontally in Splitter"_s)); + m_actionSplitHorizontal->setData(LayoutInfo::HSplitter); + m_actionSplitHorizontal->setEnabled(false); + connect(m_actionSplitHorizontal, &QAction::triggered, this, &FormWindowManager::createLayout); + + m_actionSplitVertical = new QAction(createIconSet("editvlayoutsplit.png"_L1), + tr("Lay Out Vertically in Sp&litter"), this); + m_actionSplitVertical->setObjectName(u"__qt_split_vertical_action"_s); + m_actionSplitVertical->setShortcut(Qt::CTRL | Qt::Key_4); + m_actionSplitVertical->setStatusTip(tr("Lays out the selected widgets vertically in a splitter")); + m_actionSplitVertical->setWhatsThis(whatsThisFrom(u"Layout|Lay Out Vertically in Splitter"_s)); + connect(m_actionSplitVertical, &QAction::triggered, this, &FormWindowManager::createLayout); + m_actionSplitVertical->setData(LayoutInfo::VSplitter); + + m_actionSplitVertical->setEnabled(false); + + m_actionBreakLayout = new QAction(createIconSet("editbreaklayout.png"_L1), + tr("&Break Layout"), this); + m_actionBreakLayout->setObjectName(u"__qt_break_layout_action"_s); + m_actionBreakLayout->setShortcut(Qt::CTRL | Qt::Key_0); + m_actionBreakLayout->setStatusTip(tr("Breaks the selected layout")); + m_actionBreakLayout->setWhatsThis(whatsThisFrom(u"Layout|Break Layout"_s)); + connect(m_actionBreakLayout, &QAction::triggered, this, &FormWindowManager::slotActionBreakLayoutActivated); + m_actionBreakLayout->setEnabled(false); + + m_actionSimplifyLayout = new QAction(tr("Si&mplify Grid Layout"), this); + m_actionSimplifyLayout->setObjectName(u"__qt_simplify_layout_action"_s); + m_actionSimplifyLayout->setStatusTip(tr("Removes empty columns and rows")); + m_actionSimplifyLayout->setWhatsThis(whatsThisFrom(u"Layout|Simplify Layout"_s)); + connect(m_actionSimplifyLayout, &QAction::triggered, this, &FormWindowManager::slotActionSimplifyLayoutActivated); + m_actionSimplifyLayout->setEnabled(false); + + m_actionDefaultPreview = new QAction(tr("&Preview..."), this); + m_actionDefaultPreview->setObjectName(u"__qt_default_preview_action"_s); + m_actionDefaultPreview->setStatusTip(tr("Preview current form")); + m_actionDefaultPreview->setWhatsThis(whatsThisFrom(u"Form|Preview"_s)); + connect(m_actionDefaultPreview, &QAction::triggered, + this, &FormWindowManager::showPreview); + + m_undoGroup = new QUndoGroup(this); + + m_actionUndo = m_undoGroup->createUndoAction(this); + m_actionUndo->setEnabled(false); + + m_actionUndo->setIcon(createIconSet(QIcon::ThemeIcon::EditUndo, "undo.png"_L1)); + m_actionRedo = m_undoGroup->createRedoAction(this); + m_actionRedo->setEnabled(false); + m_actionRedo->setIcon(createIconSet(QIcon::ThemeIcon::EditRedo, "redo.png"_L1)); + + m_actionShowFormWindowSettingsDialog = new QAction(tr("Form &Settings..."), this); + m_actionShowFormWindowSettingsDialog->setObjectName(u"__qt_form_settings_action"_s); + connect(m_actionShowFormWindowSettingsDialog, &QAction::triggered, + this, &FormWindowManager::slotActionShowFormWindowSettingsDialog); + m_actionShowFormWindowSettingsDialog->setEnabled(false); +} + +#if QT_CONFIG(clipboard) +void FormWindowManager::slotActionCutActivated() +{ + m_activeFormWindow->cut(); +} + +void FormWindowManager::slotActionCopyActivated() +{ + m_activeFormWindow->copy(); + slotUpdateActions(); +} + +void FormWindowManager::slotActionPasteActivated() +{ + m_activeFormWindow->paste(); +} +#endif + +void FormWindowManager::slotActionDeleteActivated() +{ + m_activeFormWindow->deleteWidgets(); +} + +void FormWindowManager::slotActionLowerActivated() +{ + m_activeFormWindow->lowerWidgets(); +} + +void FormWindowManager::slotActionRaiseActivated() +{ + m_activeFormWindow->raiseWidgets(); +} + +static inline QWidget *findLayoutContainer(const FormWindow *fw) +{ + QWidgetList l(fw->selectedWidgets()); + fw->simplifySelection(&l); + return l.isEmpty() ? fw->mainContainer() : l.constFirst(); +} + +void FormWindowManager::createLayout() +{ + QAction *a = qobject_cast(sender()); + if (!a) + return; + const int type = a->data().toInt(); + switch (m_createLayoutContext) { + case LayoutContainer: + // Cannot create a splitter on a container + if (type != LayoutInfo::HSplitter && type != LayoutInfo::VSplitter) + m_activeFormWindow->createLayout(type, findLayoutContainer(m_activeFormWindow)); + break; + case LayoutSelection: + m_activeFormWindow->createLayout(type); + break; + case MorphLayout: + m_activeFormWindow->morphLayout(m_morphLayoutContainer, type); + break; + } +} + +void FormWindowManager::slotActionBreakLayoutActivated() +{ + const QWidgetList layouts = layoutsToBeBroken(); + if (layouts.isEmpty()) + return; + + if (debugFWM) { + qDebug() << "slotActionBreakLayoutActivated: " << layouts.size(); + for (const QWidget *w : layouts) + qDebug() << w; + } + + m_activeFormWindow->beginCommand(tr("Break Layout")); + for (QWidget *layout : layouts) + m_activeFormWindow->breakLayout(layout); + m_activeFormWindow->endCommand(); +} + +void FormWindowManager::slotActionSimplifyLayoutActivated() +{ + Q_ASSERT(m_activeFormWindow != nullptr); + QWidgetList selectedWidgets = m_activeFormWindow->selectedWidgets(); + m_activeFormWindow->simplifySelection(&selectedWidgets); + if (selectedWidgets.size() != 1) + return; + SimplifyLayoutCommand *cmd = new SimplifyLayoutCommand(m_activeFormWindow); + if (cmd->init(selectedWidgets.constFirst())) { + m_activeFormWindow->commandHistory()->push(cmd); + } else { + delete cmd; + } +} + +void FormWindowManager::slotActionAdjustSizeActivated() +{ + Q_ASSERT(m_activeFormWindow != nullptr); + + m_activeFormWindow->beginCommand(tr("Adjust Size")); + + QWidgetList selectedWidgets = m_activeFormWindow->selectedWidgets(); + m_activeFormWindow->simplifySelection(&selectedWidgets); + + if (selectedWidgets.isEmpty()) { + Q_ASSERT(m_activeFormWindow->mainContainer() != nullptr); + selectedWidgets.append(m_activeFormWindow->mainContainer()); + } + + // Always count the main container as unlaid-out + for (QWidget *widget : std::as_const(selectedWidgets)) { + bool unlaidout = LayoutInfo::layoutType(core(), widget->parentWidget()) == LayoutInfo::NoLayout; + bool isMainContainer = m_activeFormWindow->isMainContainer(widget); + + if (unlaidout || isMainContainer) { + AdjustWidgetSizeCommand *cmd = new AdjustWidgetSizeCommand(m_activeFormWindow); + cmd->init(widget); + m_activeFormWindow->commandHistory()->push(cmd); + } + } + + m_activeFormWindow->endCommand(); +} + +void FormWindowManager::slotActionSelectAllActivated() +{ + m_activeFormWindow->selectAll(); +} + +void FormWindowManager::showPreview() +{ + slotActionGroupPreviewInStyle(QString(), -1); +} + +void FormWindowManager::slotActionGroupPreviewInStyle(const QString &style, int deviceProfileIndex) +{ + QDesignerFormWindowInterface *fw = activeFormWindow(); + if (!fw) + return; + + QString errorMessage; + if (!m_previewManager->showPreview(fw, style, deviceProfileIndex, &errorMessage)) { + const QString title = tr("Could not create form preview", "Title of warning message box"); + core()->dialogGui()->message(fw, QDesignerDialogGuiInterface::FormEditorMessage, QMessageBox::Warning, + title, errorMessage); + } +} + +// The user might click on a layout child or the actual layout container. +QWidgetList FormWindowManager::layoutsToBeBroken(QWidget *w) const +{ + if (!w) + return QWidgetList(); + + if (debugFWM) + qDebug() << "layoutsToBeBroken: " << w; + + QWidget *parent = w->parentWidget(); + if (m_activeFormWindow->isMainContainer(w)) + parent = nullptr; + + QWidget *widget = core()->widgetFactory()->containerOfWidget(w); + + // maybe we want to remove following block + const QDesignerWidgetDataBaseInterface *db = m_core->widgetDataBase(); + const QDesignerWidgetDataBaseItemInterface *item = db->item(db->indexOfObject(widget)); + if (!item) { + if (debugFWM) + qDebug() << "layoutsToBeBroken: Don't have an item, recursing for parent"; + return layoutsToBeBroken(parent); + } + + const bool layoutContainer = (item->isContainer() || m_activeFormWindow->isMainContainer(widget)); + + if (!layoutContainer) { + if (debugFWM) + qDebug() << "layoutsToBeBroken: Not a container, recursing for parent"; + return layoutsToBeBroken(parent); + } + + QLayout *widgetLayout = widget->layout(); + QLayout *managedLayout = LayoutInfo::managedLayout(m_core, widgetLayout); + if (!managedLayout) { + if (qobject_cast(widget)) { + if (debugFWM) + qDebug() << "layoutsToBeBroken: Splitter special"; + QWidgetList list = layoutsToBeBroken(parent); + list.append(widget); + return list; + } + if (debugFWM) + qDebug() << "layoutsToBeBroken: Is a container but doesn't have a managed layout (has an internal layout), returning 0"; + return QWidgetList(); + } + + if (managedLayout) { + QWidgetList list; + if (debugFWM) + qDebug() << "layoutsToBeBroken: Is a container and has a layout"; + if (qobject_cast(widget)) { + if (debugFWM) + qDebug() << "layoutsToBeBroken: red layout special case"; + list = layoutsToBeBroken(parent); + } + list.append(widget); + return list; + } + if (debugFWM) + qDebug() << "layoutsToBeBroken: Is a container but doesn't have a layout at all, returning 0"; + return QWidgetList(); + +} + +QSet FormWindowManager::getUnsortedLayoutsToBeBroken(bool firstOnly) const +{ + // Return a set of layouts to be broken. + QSet layouts; + + QWidgetList selection = m_activeFormWindow->selectedWidgets(); + if (selection.isEmpty() && m_activeFormWindow->mainContainer()) + selection.append(m_activeFormWindow->mainContainer()); + + for (QWidget *selectedWidget : std::as_const(selection)) { + // find all layouts + const QWidgetList &list = layoutsToBeBroken(selectedWidget); + if (!list.isEmpty()) { + for (QWidget *widget : list) + layouts.insert(widget); + if (firstOnly) + return layouts; + } + } + return layouts; +} + +bool FormWindowManager::hasLayoutsToBeBroken() const +{ + // Quick check for layouts to be broken + return !getUnsortedLayoutsToBeBroken(true).isEmpty(); +} + +QWidgetList FormWindowManager::layoutsToBeBroken() const +{ + // Get all layouts. This is a list of all 'red' layouts (QLayoutWidgets) + // up to the first 'real' widget with a layout in hierarchy order. + const QSet unsortedLayouts = getUnsortedLayoutsToBeBroken(false); + // Sort in order of hierarchy + QWidgetList orderedLayoutList; + for (QWidget *wToBeInserted : unsortedLayouts) { + if (!orderedLayoutList.contains(wToBeInserted)) { + // try to find first child, use as insertion position, else append + const auto firstChildPos = findFirstChildOf(orderedLayoutList.begin(), orderedLayoutList.end(), wToBeInserted); + if (firstChildPos == orderedLayoutList.end()) { + orderedLayoutList.push_back(wToBeInserted); + } else { + orderedLayoutList.insert(firstChildPos, wToBeInserted); + } + } + } + return orderedLayoutList; +} + +static inline bool hasManagedLayoutItems(const QDesignerFormEditorInterface *core, QWidget *w) +{ + if (const QLayout *ml = LayoutInfo::managedLayout(core, w)) { + // Try to find managed items, ignore dummy grid spacers + const int count = ml->count(); + for (int i = 0; i < count; i++) + if (!LayoutInfo::isEmptyItem(ml->itemAt(i))) + return true; + } + return false; +} + +void FormWindowManager::slotUpdateActions() +{ + m_createLayoutContext = LayoutSelection; + m_morphLayoutContainer = nullptr; + bool canMorphIntoVBoxLayout = false; + bool canMorphIntoHBoxLayout = false; + bool canMorphIntoGridLayout = false; + bool canMorphIntoFormLayout = false; + bool hasSelectedWidgets = false; + int unlaidoutWidgetCount = 0; +#if QT_CONFIG(clipboard) + bool pasteAvailable = false; +#endif + bool layoutAvailable = false; + bool breakAvailable = false; + bool simplifyAvailable = false; + bool layoutContainer = false; + bool canChangeZOrder = true; + + do { + if (m_activeFormWindow == nullptr || m_activeFormWindow->currentTool() != 0) + break; + + breakAvailable = hasLayoutsToBeBroken(); + + QWidgetList simplifiedSelection = m_activeFormWindow->selectedWidgets(); + + hasSelectedWidgets = !simplifiedSelection.isEmpty(); +#if QT_CONFIG(clipboard) + pasteAvailable = qApp->clipboard()->mimeData() && qApp->clipboard()->mimeData()->hasText(); +#endif + + m_activeFormWindow->simplifySelection(&simplifiedSelection); + QWidget *mainContainer = m_activeFormWindow->mainContainer(); + if (simplifiedSelection.isEmpty() && mainContainer) + simplifiedSelection.append(mainContainer); + + // Always count the main container as unlaid-out + for (auto *w : std::as_const(simplifiedSelection)) { + if (w == mainContainer || !LayoutInfo::isWidgetLaidout(m_core, w)) + ++unlaidoutWidgetCount; + + if (qobject_cast(w) || qobject_cast(w)) + canChangeZOrder = false; + } + + // Figure out layouts: Looking at a group of dangling widgets + if (simplifiedSelection.size() != 1) { + layoutAvailable = unlaidoutWidgetCount > 1; + //breakAvailable = false; + break; + } + // Manipulate layout of a single widget + m_createLayoutContext = LayoutSelection; + QWidget *widget = core()->widgetFactory()->containerOfWidget(simplifiedSelection.first()); + if (widget == nullptr) // We are looking at a page-based container with 0 pages + break; + + const QDesignerWidgetDataBaseInterface *db = m_core->widgetDataBase(); + const QDesignerWidgetDataBaseItemInterface *item = db->item(db->indexOfObject(widget)); + if (!item) + break; + + QLayout *widgetLayout = LayoutInfo::internalLayout(widget); + QLayout *managedLayout = LayoutInfo::managedLayout(m_core, widgetLayout); + // We don't touch a layout created by a custom widget + if (widgetLayout && !managedLayout) + break; + + layoutContainer = (item->isContainer() || m_activeFormWindow->isMainContainer(widget)); + + layoutAvailable = layoutContainer && m_activeFormWindow->hasInsertedChildren(widget) && managedLayout == nullptr; + simplifyAvailable = SimplifyLayoutCommand::canSimplify(m_core, widget); + if (layoutAvailable) { + m_createLayoutContext = LayoutContainer; + } else { + /* Cannot create a layout, have some layouts to be broken and + * exactly one, non-empty layout with selected: check the morph layout options + * (Note that there might be > 1 layouts to broken if the selection + * is a red layout, however, we want the inner-most layout here). */ + if (breakAvailable && simplifiedSelection.size() == 1 + && hasManagedLayoutItems(m_core, widget)) { + int type; + m_morphLayoutContainer = widget; // Was: page of first selected + m_createLayoutContext = MorphLayout; + if (MorphLayoutCommand::canMorph(m_activeFormWindow, m_morphLayoutContainer, &type)) { + canMorphIntoVBoxLayout = type != LayoutInfo::VBox; + canMorphIntoHBoxLayout = type != LayoutInfo::HBox; + canMorphIntoGridLayout = type != LayoutInfo::Grid; + canMorphIntoFormLayout = type != LayoutInfo::Form; + } + } + } + } while(false); + +#if QT_CONFIG(clipboard) + m_actionCut->setEnabled(hasSelectedWidgets); + m_actionCopy->setEnabled(hasSelectedWidgets); + m_actionPaste->setEnabled(pasteAvailable); +#endif + m_actionDelete->setEnabled(hasSelectedWidgets); + m_actionLower->setEnabled(canChangeZOrder && hasSelectedWidgets); + m_actionRaise->setEnabled(canChangeZOrder && hasSelectedWidgets); + + + m_actionSelectAll->setEnabled(m_activeFormWindow != nullptr); + + m_actionAdjustSize->setEnabled(unlaidoutWidgetCount > 0); + + m_actionHorizontalLayout->setEnabled(layoutAvailable || canMorphIntoHBoxLayout); + m_actionVerticalLayout->setEnabled(layoutAvailable || canMorphIntoVBoxLayout); + m_actionSplitHorizontal->setEnabled(layoutAvailable && !layoutContainer); + m_actionSplitVertical->setEnabled(layoutAvailable && !layoutContainer); + m_actionFormLayout->setEnabled(layoutAvailable || canMorphIntoFormLayout); + m_actionGridLayout->setEnabled(layoutAvailable || canMorphIntoGridLayout); + + m_actionBreakLayout->setEnabled(breakAvailable); + m_actionSimplifyLayout->setEnabled(simplifyAvailable); + m_actionShowFormWindowSettingsDialog->setEnabled(m_activeFormWindow != nullptr); +} + +QDesignerFormWindowInterface *FormWindowManager::createFormWindow(QWidget *parentWidget, Qt::WindowFlags flags) +{ + FormWindow *formWindow = new FormWindow(qobject_cast(core()), parentWidget, flags); + formWindow->setProperty(WidgetFactory::disableStyleCustomPaintingPropertyC, QVariant(true)); + addFormWindow(formWindow); + return formWindow; +} + +QPixmap FormWindowManager::createPreviewPixmap() const +{ + const QDesignerFormWindowInterface *fw = activeFormWindow(); + if (!fw) + return QPixmap(); + QString errorMessage; + const QPixmap pix = m_previewManager->createPreviewPixmap(fw, QString(), &errorMessage); + if (pix.isNull() && !errorMessage.isEmpty()) + qWarning("Preview pixmap creation failed: %s", qPrintable(errorMessage)); + return pix; +} + +void FormWindowManager::deviceProfilesChanged() +{ + if (m_actionGroupPreviewInStyle) + m_actionGroupPreviewInStyle->updateDeviceProfiles(); +} + +// DnD stuff + +void FormWindowManager::dragItems(const QList &item_list) +{ + QDesignerMimeData::execDrag(item_list, m_core->topLevel()); +} + +QUndoGroup *FormWindowManager::undoGroup() const +{ + return m_undoGroup; +} + +void FormWindowManager::slotActionShowFormWindowSettingsDialog() +{ + QDesignerFormWindowInterface *fw = activeFormWindow(); + if (!fw) + return; + + QDialog *settingsDialog = nullptr; + const bool wasDirty = fw->isDirty(); + + // Ask the language extension for a dialog. If not, create our own + if (QDesignerLanguageExtension *lang = qt_extension(m_core->extensionManager(), m_core)) + settingsDialog = lang->createFormWindowSettingsDialog(fw, /*parent=*/ nullptr); + + if (!settingsDialog) + settingsDialog = new FormWindowSettings(fw); + + QString title = QFileInfo(fw->fileName()).fileName(); + if (title.isEmpty()) // Grab the title from the outer window if no filename + if (const QWidget *window = m_core->integration()->containerWindow(fw)) + title = window->windowTitle(); + + settingsDialog->setWindowTitle(tr("Form Settings - %1").arg(title)); + if (settingsDialog->exec()) + if (fw->isDirty() != wasDirty) + emit formWindowSettingsChanged(fw); + + delete settingsDialog; +} + +QAction *FormWindowManager::action(Action action) const +{ + switch (action) { +#if QT_CONFIG(clipboard) + case QDesignerFormWindowManagerInterface::CutAction: + return m_actionCut; + case QDesignerFormWindowManagerInterface::CopyAction: + return m_actionCopy; + case QDesignerFormWindowManagerInterface::PasteAction: + return m_actionPaste; +#endif + case QDesignerFormWindowManagerInterface::DeleteAction: + return m_actionDelete; + case QDesignerFormWindowManagerInterface::SelectAllAction: + return m_actionSelectAll; + case QDesignerFormWindowManagerInterface::LowerAction: + return m_actionLower; + case QDesignerFormWindowManagerInterface::RaiseAction: + return m_actionRaise; + case QDesignerFormWindowManagerInterface::UndoAction: + return m_actionUndo; + case QDesignerFormWindowManagerInterface::RedoAction: + return m_actionRedo; + case QDesignerFormWindowManagerInterface::HorizontalLayoutAction: + return m_actionHorizontalLayout; + case QDesignerFormWindowManagerInterface::VerticalLayoutAction: + return m_actionVerticalLayout; + case QDesignerFormWindowManagerInterface::SplitHorizontalAction: + return m_actionSplitHorizontal; + case QDesignerFormWindowManagerInterface::SplitVerticalAction: + return m_actionSplitVertical; + case QDesignerFormWindowManagerInterface::GridLayoutAction: + return m_actionGridLayout; + case QDesignerFormWindowManagerInterface::FormLayoutAction: + return m_actionFormLayout; + case QDesignerFormWindowManagerInterface::BreakLayoutAction: + return m_actionBreakLayout; + case QDesignerFormWindowManagerInterface::AdjustSizeAction: + return m_actionAdjustSize; + case QDesignerFormWindowManagerInterface::SimplifyLayoutAction: + return m_actionSimplifyLayout; + case QDesignerFormWindowManagerInterface::DefaultPreviewAction: + return m_actionDefaultPreview; + case QDesignerFormWindowManagerInterface::FormWindowSettingsDialogAction: + return m_actionShowFormWindowSettingsDialog; + } + qWarning("FormWindowManager::action: Unhanded enumeration value %d", action); + return nullptr; +} + +QActionGroup *FormWindowManager::actionGroup(ActionGroup actionGroup) const +{ + switch (actionGroup) { + case QDesignerFormWindowManagerInterface::StyledPreviewActionGroup: + if (m_actionGroupPreviewInStyle == nullptr) { + // Wish we could make the 'this' pointer mutable ;-) + QObject *parent = const_cast(this); + m_actionGroupPreviewInStyle = new PreviewActionGroup(m_core, parent); + connect(m_actionGroupPreviewInStyle, &PreviewActionGroup::preview, + this, &FormWindowManager::slotActionGroupPreviewInStyle); + } + return m_actionGroupPreviewInStyle; + } + qWarning("FormWindowManager::actionGroup: Unhanded enumeration value %d", actionGroup); + return nullptr; +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/formwindowmanager.h b/src/tools/designer/src/components/formeditor/formwindowmanager.h new file mode 100644 index 00000000000..001390bb421 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindowmanager.h @@ -0,0 +1,150 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOWMANAGER_H +#define FORMWINDOWMANAGER_H + +#include "formeditor_global.h" + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; +class QActionGroup; +class QUndoGroup; +class QDesignerFormEditorInterface; +class QDesignerWidgetBoxInterface; + +namespace qdesigner_internal { + +class FormWindow; +class PreviewManager; +class PreviewActionGroup; + +class QT_FORMEDITOR_EXPORT FormWindowManager + : public QDesignerFormWindowManager +{ + Q_OBJECT +public: + explicit FormWindowManager(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~FormWindowManager() override; + + QDesignerFormEditorInterface *core() const override; + + QAction *action(Action action) const override; + QActionGroup *actionGroup(ActionGroup actionGroup) const override; + + QDesignerFormWindowInterface *activeFormWindow() const override; + + int formWindowCount() const override; + QDesignerFormWindowInterface *formWindow(int index) const override; + + QDesignerFormWindowInterface *createFormWindow(QWidget *parentWidget = nullptr, Qt::WindowFlags flags = {}) override; + + QPixmap createPreviewPixmap() const override; + + bool eventFilter(QObject *o, QEvent *e) override; + + void dragItems(const QList &item_list) override; + + QUndoGroup *undoGroup() const; + + PreviewManager *previewManager() const override { return m_previewManager; } + +public slots: + void addFormWindow(QDesignerFormWindowInterface *formWindow) override; + void removeFormWindow(QDesignerFormWindowInterface *formWindow) override; + void setActiveFormWindow(QDesignerFormWindowInterface *formWindow) override; + void closeAllPreviews() override; + void deviceProfilesChanged(); + +private slots: +#if QT_CONFIG(clipboard) + void slotActionCutActivated(); + void slotActionCopyActivated(); + void slotActionPasteActivated(); +#endif + void slotActionDeleteActivated(); + void slotActionSelectAllActivated(); + void slotActionLowerActivated(); + void slotActionRaiseActivated(); + void createLayout(); + void slotActionBreakLayoutActivated(); + void slotActionAdjustSizeActivated(); + void slotActionSimplifyLayoutActivated(); + void showPreview() override; + void slotActionGroupPreviewInStyle(const QString &style, int deviceProfileIndex); + void slotActionShowFormWindowSettingsDialog(); + + void slotUpdateActions(); + +private: + void setupActions(); + FormWindow *findFormWindow(QWidget *w); + QWidget *findManagedWidget(FormWindow *fw, QWidget *w); + + void setCurrentUndoStack(QUndoStack *stack); + +private: + enum CreateLayoutContext { LayoutContainer, LayoutSelection, MorphLayout }; + + QDesignerFormEditorInterface *m_core; + FormWindow *m_activeFormWindow; + QList m_formWindows; + + PreviewManager *m_previewManager; + + /* Context of the layout actions and base for morphing layouts. Determined + * in slotUpdateActions() and used later on in the action slots. */ + CreateLayoutContext m_createLayoutContext; + QWidget *m_morphLayoutContainer; + + // edit actions +#if QT_CONFIG(clipboard) + QAction *m_actionCut = nullptr; + QAction *m_actionCopy = nullptr; + QAction *m_actionPaste = nullptr; +#endif + QAction *m_actionSelectAll = nullptr; + QAction *m_actionDelete = nullptr; + QAction *m_actionLower = nullptr; + QAction *m_actionRaise = nullptr; + // layout actions + QAction *m_actionHorizontalLayout = nullptr; + QAction *m_actionVerticalLayout = nullptr; + QAction *m_actionFormLayout = nullptr; + QAction *m_actionSplitHorizontal = nullptr; + QAction *m_actionSplitVertical = nullptr; + QAction *m_actionGridLayout = nullptr; + QAction *m_actionBreakLayout = nullptr; + QAction *m_actionSimplifyLayout = nullptr; + QAction *m_actionAdjustSize = nullptr; + // preview actions + QAction *m_actionDefaultPreview = nullptr; + mutable PreviewActionGroup *m_actionGroupPreviewInStyle = nullptr; + QAction *m_actionShowFormWindowSettingsDialog = nullptr; + + QAction *m_actionUndo = nullptr; + QAction *m_actionRedo = nullptr; + + QSet getUnsortedLayoutsToBeBroken(bool firstOnly) const; + bool hasLayoutsToBeBroken() const; + QWidgetList layoutsToBeBroken(QWidget *w) const; + QWidgetList layoutsToBeBroken() const; + + QUndoGroup *m_undoGroup = nullptr; + +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOWMANAGER_H diff --git a/src/tools/designer/src/components/formeditor/formwindowsettings.cpp b/src/tools/designer/src/components/formeditor/formwindowsettings.cpp new file mode 100644 index 00000000000..ad0e32f48f9 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindowsettings.cpp @@ -0,0 +1,247 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindowsettings.h" +#include "ui_formwindowsettings.h" + +#include +#include + +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// Data structure containing form dialog data providing comparison +struct FormWindowData { + void fromFormWindow(FormWindowBase* fw); + void applyToFormWindow(FormWindowBase* fw) const; + + bool layoutDefaultEnabled{false}; + int defaultMargin{0}; + int defaultSpacing{0}; + + bool layoutFunctionsEnabled{false}; + QString marginFunction; + QString spacingFunction; + + QString pixFunction; + + QString author; + + QStringList includeHints; + + bool hasFormGrid{false}; + Grid grid; + bool idBasedTranslations{false}; + bool connectSlotsByName{true}; + + friend bool comparesEqual(const FormWindowData &lhs, + const FormWindowData &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(FormWindowData) +}; + +QDebug operator<<(QDebug str, const FormWindowData &d) +{ + str.nospace() << "LayoutDefault=" << d.layoutDefaultEnabled << ',' << d.defaultMargin + << ',' << d.defaultSpacing << " LayoutFunctions=" << d.layoutFunctionsEnabled << ',' + << d.marginFunction << ',' << d.spacingFunction << " PixFunction=" + << d.pixFunction << " Author=" << d.author << " Hints=" << d.includeHints + << " Grid=" << d.hasFormGrid << d.grid.deltaX() << d.grid.deltaY() + << " ID-based translations" << d.idBasedTranslations + << " Connect slots by name" << d.connectSlotsByName + << '\n'; + return str; +} + +bool comparesEqual(const FormWindowData &lhs, const FormWindowData &rhs) noexcept +{ + return lhs.layoutDefaultEnabled == rhs.layoutDefaultEnabled && + lhs.defaultMargin == rhs.defaultMargin && + lhs.defaultSpacing == rhs.defaultSpacing && + lhs.layoutFunctionsEnabled == rhs.layoutFunctionsEnabled && + lhs.marginFunction == rhs.marginFunction && + lhs.spacingFunction == rhs.spacingFunction && + lhs.pixFunction == rhs.pixFunction && + lhs.author == rhs.author && + lhs.includeHints == rhs.includeHints && + lhs.hasFormGrid == rhs.hasFormGrid && + lhs.grid == rhs.grid && + lhs.idBasedTranslations == rhs.idBasedTranslations && + lhs.connectSlotsByName == rhs.connectSlotsByName; +} + +void FormWindowData::fromFormWindow(FormWindowBase* fw) +{ + defaultMargin = defaultSpacing = INT_MIN; + fw->layoutDefault(&defaultMargin, &defaultSpacing); + + auto container = fw->formContainer(); + QStyle *style = container->style(); + layoutDefaultEnabled = defaultMargin != INT_MIN || defaultSpacing != INT_MIN; + if (defaultMargin == INT_MIN) + defaultMargin = style->pixelMetric(QStyle::PM_LayoutLeftMargin, nullptr, container); + if (defaultSpacing == INT_MIN) + defaultSpacing = style->pixelMetric(QStyle::PM_LayoutHorizontalSpacing, nullptr); + + + marginFunction.clear(); + spacingFunction.clear(); + fw->layoutFunction(&marginFunction, &spacingFunction); + layoutFunctionsEnabled = !marginFunction.isEmpty() || !spacingFunction.isEmpty(); + + pixFunction = fw->pixmapFunction(); + + author = fw->author(); + + includeHints = fw->includeHints(); + includeHints.removeAll(QString()); + + hasFormGrid = fw->hasFormGrid(); + grid = hasFormGrid ? fw->designerGrid() : FormWindowBase::defaultDesignerGrid(); + idBasedTranslations = fw->useIdBasedTranslations(); + connectSlotsByName = fw->connectSlotsByName(); +} + +void FormWindowData::applyToFormWindow(FormWindowBase* fw) const +{ + fw->setAuthor(author); + fw->setPixmapFunction(pixFunction); + + if (layoutDefaultEnabled) { + fw->setLayoutDefault(defaultMargin, defaultSpacing); + } else { + fw->setLayoutDefault(INT_MIN, INT_MIN); + } + + if (layoutFunctionsEnabled) { + fw->setLayoutFunction(marginFunction, spacingFunction); + } else { + fw->setLayoutFunction(QString(), QString()); + } + + fw->setIncludeHints(includeHints); + + const bool hadFormGrid = fw->hasFormGrid(); + fw->setHasFormGrid(hasFormGrid); + if (hasFormGrid || hadFormGrid != hasFormGrid) + fw->setDesignerGrid(hasFormGrid ? grid : FormWindowBase::defaultDesignerGrid()); + fw->setUseIdBasedTranslations(idBasedTranslations); + fw->setConnectSlotsByName(connectSlotsByName); +} + +// -------------------------- FormWindowSettings + +FormWindowSettings::FormWindowSettings(QDesignerFormWindowInterface *parent) : + QDialog(parent), + m_ui(new QT_PREPEND_NAMESPACE(Ui)::FormWindowSettings), + m_formWindow(qobject_cast(parent)), + m_oldData(new FormWindowData) +{ + Q_ASSERT(m_formWindow); + + m_ui->setupUi(this); + m_ui->gridPanel->setCheckable(true); + m_ui->gridPanel->setResetButtonVisible(false); + + QString deviceProfileName = m_formWindow->deviceProfileName(); + if (deviceProfileName.isEmpty()) + deviceProfileName = tr("None"); + m_ui->deviceProfileLabel->setText(tr("Device Profile: %1").arg(deviceProfileName)); + + m_oldData->fromFormWindow(m_formWindow); + setData(*m_oldData); +} + +FormWindowSettings::~FormWindowSettings() +{ + delete m_oldData; + delete m_ui; +} + +FormWindowData FormWindowSettings::data() const +{ + FormWindowData rc; + rc.author = m_ui->authorLineEdit->text(); + + if (m_ui->pixmapFunctionGroupBox->isChecked()) { + rc.pixFunction = m_ui->pixmapFunctionLineEdit->text(); + } else { + rc.pixFunction.clear(); + } + + rc.layoutDefaultEnabled = m_ui->layoutDefaultGroupBox->isChecked(); + rc.defaultMargin = m_ui->defaultMarginSpinBox->value(); + rc.defaultSpacing = m_ui->defaultSpacingSpinBox->value(); + + rc.layoutFunctionsEnabled = m_ui->layoutFunctionGroupBox->isChecked(); + rc.marginFunction = m_ui->marginFunctionLineEdit->text(); + rc.spacingFunction = m_ui->spacingFunctionLineEdit->text(); + + const QString hints = m_ui->includeHintsTextEdit->toPlainText(); + if (!hints.isEmpty()) { + rc.includeHints = hints.split(u'\n'); + // Purge out any lines consisting of blanks only + const QRegularExpression blankLine(u"^\\s*$"_s); + Q_ASSERT(blankLine.isValid()); + rc.includeHints.erase(std::remove_if(rc.includeHints.begin(), rc.includeHints.end(), + [blankLine](const QString &hint){ return blankLine.match(hint).hasMatch(); }), + rc.includeHints.end()); + } + + rc.hasFormGrid = m_ui->gridPanel->isChecked(); + rc.grid = m_ui->gridPanel->grid(); + rc.idBasedTranslations = m_ui->idBasedTranslationsCheckBox->isChecked(); + rc.connectSlotsByName = m_ui->connectSlotsByNameCheckBox->isChecked(); + return rc; +} + +void FormWindowSettings::setData(const FormWindowData &data) +{ + m_ui->layoutDefaultGroupBox->setChecked(data.layoutDefaultEnabled); + m_ui->defaultMarginSpinBox->setValue(data.defaultMargin); + m_ui->defaultSpacingSpinBox->setValue(data.defaultSpacing); + + m_ui->layoutFunctionGroupBox->setChecked(data.layoutFunctionsEnabled); + m_ui->marginFunctionLineEdit->setText(data.marginFunction); + m_ui->spacingFunctionLineEdit->setText(data.spacingFunction); + + m_ui->pixmapFunctionLineEdit->setText(data.pixFunction); + m_ui->pixmapFunctionGroupBox->setChecked(!data.pixFunction.isEmpty()); + + m_ui->authorLineEdit->setText(data.author); + + if (data.includeHints.isEmpty()) { + m_ui->includeHintsTextEdit->clear(); + } else { + m_ui->includeHintsTextEdit->setText(data.includeHints.join(u'\n')); + } + + m_ui->gridPanel->setChecked(data.hasFormGrid); + m_ui->gridPanel->setGrid(data.grid); + m_ui->idBasedTranslationsCheckBox->setChecked(data.idBasedTranslations); + m_ui->connectSlotsByNameCheckBox->setChecked(data.connectSlotsByName); +} + +void FormWindowSettings::accept() +{ + // Anything changed? -> Apply and set dirty + const FormWindowData newData = data(); + if (newData != *m_oldData) { + newData.applyToFormWindow(m_formWindow); + m_formWindow->setDirty(true); + } + + QDialog::accept(); +} +} +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/formwindowsettings.h b/src/tools/designer/src/components/formeditor/formwindowsettings.h new file mode 100644 index 00000000000..37e5101c723 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindowsettings.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMWINDOWSETTINGS_H +#define FORMWINDOWSETTINGS_H + +#include + +QT_BEGIN_NAMESPACE + +namespace Ui { + class FormWindowSettings; +} + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +struct FormWindowData; +class FormWindowBase; + +/* Dialog to edit the settings of a QDesignerFormWindowInterface. + * It sets the dirty flag on the form window if something was changed. */ + +class FormWindowSettings: public QDialog +{ + Q_DISABLE_COPY_MOVE(FormWindowSettings) + Q_OBJECT +public: + explicit FormWindowSettings(QDesignerFormWindowInterface *formWindow); + ~FormWindowSettings() override; + + void accept() override; + +private: + FormWindowData data() const; + void setData(const FormWindowData&); + + QT_PREPEND_NAMESPACE(Ui)::FormWindowSettings *m_ui; + FormWindowBase *m_formWindow; + FormWindowData *m_oldData; +}; +} + +QT_END_NAMESPACE + +#endif // FORMWINDOWSETTINGS_H diff --git a/src/tools/designer/src/components/formeditor/formwindowsettings.ui b/src/tools/designer/src/components/formeditor/formwindowsettings.ui new file mode 100644 index 00000000000..1fd1e35b7d8 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/formwindowsettings.ui @@ -0,0 +1,386 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + FormWindowSettings + + + + 0 + 0 + 463 + 654 + + + + Form Settings + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + Grid + + + + + + + &Include Hints + + + + 6 + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + + + + + + Translations + + + + + + ID-based + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + &Pixmap Function + + + true + + + + 6 + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + + + + + + + + Qt::Horizontal + + + + + + + &Author + + + + 6 + + + 8 + + + 8 + + + 8 + + + 8 + + + + + + + + + + + Qt::Vertical + + + + 111 + 115 + + + + + + + + Embedded Design + + + + + + TextLabel + + + + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Layout &Default + + + true + + + + 8 + + + 8 + + + 8 + + + 8 + + + 6 + + + + + &Spacing: + + + defaultSpacingSpinBox + + + + + + + &Margin: + + + defaultMarginSpinBox + + + + + + + + + + + + + + + + &Layout Function + + + true + + + + 8 + + + 8 + + + 8 + + + 8 + + + 6 + + + + + + + + + + + Ma&rgin: + + + marginFunctionLineEdit + + + + + + + Spa&cing: + + + spacingFunctionLineEdit + + + + + + + + + + + + Connections + + + + + + Connect slots by name + + + + + + + + + + + qdesigner_internal::GridPanel + QGroupBox +
gridpanel_p.h
+ 1 +
+
+ + authorLineEdit + defaultMarginSpinBox + defaultSpacingSpinBox + marginFunctionLineEdit + spacingFunctionLineEdit + pixmapFunctionLineEdit + + + + + buttonBox + accepted() + FormWindowSettings + accept() + + + 294 + 442 + + + 150 + 459 + + + + + buttonBox + rejected() + FormWindowSettings + reject() + + + 373 + 444 + + + 357 + 461 + + + + +
diff --git a/src/tools/designer/src/components/formeditor/images/color.png b/src/tools/designer/src/components/formeditor/images/color.png new file mode 100644 index 00000000000..54b7ebcdee0 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/color.png differ diff --git a/src/tools/designer/src/components/formeditor/images/configure.png b/src/tools/designer/src/components/formeditor/images/configure.png new file mode 100644 index 00000000000..d9f2fd8c072 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/configure.png differ diff --git a/src/tools/designer/src/components/formeditor/images/downplus.png b/src/tools/designer/src/components/formeditor/images/downplus.png new file mode 100644 index 00000000000..1e384a72d60 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/downplus.png differ diff --git a/src/tools/designer/src/components/formeditor/images/dropdownbutton.png b/src/tools/designer/src/components/formeditor/images/dropdownbutton.png new file mode 100644 index 00000000000..5dd964946ce Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/dropdownbutton.png differ diff --git a/src/tools/designer/src/components/formeditor/images/edit.png b/src/tools/designer/src/components/formeditor/images/edit.png new file mode 100644 index 00000000000..a5e49adf99d Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/edit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/editdelete-16.png b/src/tools/designer/src/components/formeditor/images/editdelete-16.png new file mode 100644 index 00000000000..ef5c799c15f Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/editdelete-16.png differ diff --git a/src/tools/designer/src/components/formeditor/images/emptyicon.png b/src/tools/designer/src/components/formeditor/images/emptyicon.png new file mode 100644 index 00000000000..897220e2130 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/emptyicon.png differ diff --git a/src/tools/designer/src/components/formeditor/images/filenew-16.png b/src/tools/designer/src/components/formeditor/images/filenew-16.png new file mode 100644 index 00000000000..eefb3c52070 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/filenew-16.png differ diff --git a/src/tools/designer/src/components/formeditor/images/fileopen-16.png b/src/tools/designer/src/components/formeditor/images/fileopen-16.png new file mode 100644 index 00000000000..d832c621cc5 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/fileopen-16.png differ diff --git a/src/tools/designer/src/components/formeditor/images/leveldown.png b/src/tools/designer/src/components/formeditor/images/leveldown.png new file mode 100644 index 00000000000..742b7fb84b0 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/leveldown.png differ diff --git a/src/tools/designer/src/components/formeditor/images/levelup.png b/src/tools/designer/src/components/formeditor/images/levelup.png new file mode 100644 index 00000000000..48b3e892236 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/levelup.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/adjustsize.png b/src/tools/designer/src/components/formeditor/images/mac/adjustsize.png new file mode 100644 index 00000000000..c4d884c8b2a Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/adjustsize.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/back.png b/src/tools/designer/src/components/formeditor/images/mac/back.png new file mode 100644 index 00000000000..e58177f43cb Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/back.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/buddytool.png b/src/tools/designer/src/components/formeditor/images/mac/buddytool.png new file mode 100644 index 00000000000..2a4287089b0 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/buddytool.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/down.png b/src/tools/designer/src/components/formeditor/images/mac/down.png new file mode 100644 index 00000000000..29d1d4439a1 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/down.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editbreaklayout.png b/src/tools/designer/src/components/formeditor/images/mac/editbreaklayout.png new file mode 100644 index 00000000000..dc005590bfc Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editbreaklayout.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editcopy.png b/src/tools/designer/src/components/formeditor/images/mac/editcopy.png new file mode 100644 index 00000000000..f551364464a Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editcopy.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editcut.png b/src/tools/designer/src/components/formeditor/images/mac/editcut.png new file mode 100644 index 00000000000..a784fd5709d Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editcut.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editdelete.png b/src/tools/designer/src/components/formeditor/images/mac/editdelete.png new file mode 100644 index 00000000000..201b31cdb16 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editdelete.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editform.png b/src/tools/designer/src/components/formeditor/images/mac/editform.png new file mode 100644 index 00000000000..4fc2e40dc50 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editform.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editgrid.png b/src/tools/designer/src/components/formeditor/images/mac/editgrid.png new file mode 100644 index 00000000000..bba4a695bc5 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editgrid.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/edithlayout.png b/src/tools/designer/src/components/formeditor/images/mac/edithlayout.png new file mode 100644 index 00000000000..ec880bb5cba Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/edithlayout.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/edithlayoutsplit.png b/src/tools/designer/src/components/formeditor/images/mac/edithlayoutsplit.png new file mode 100644 index 00000000000..227d0115fe3 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/edithlayoutsplit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editlower.png b/src/tools/designer/src/components/formeditor/images/mac/editlower.png new file mode 100644 index 00000000000..347806fc0f6 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editlower.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editpaste.png b/src/tools/designer/src/components/formeditor/images/mac/editpaste.png new file mode 100644 index 00000000000..64c0b2d6ab8 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editpaste.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editraise.png b/src/tools/designer/src/components/formeditor/images/mac/editraise.png new file mode 100644 index 00000000000..09cbbd7d5ac Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editraise.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editvlayout.png b/src/tools/designer/src/components/formeditor/images/mac/editvlayout.png new file mode 100644 index 00000000000..63b26cdb2dd Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editvlayout.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/editvlayoutsplit.png b/src/tools/designer/src/components/formeditor/images/mac/editvlayoutsplit.png new file mode 100644 index 00000000000..5a02c944e03 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/editvlayoutsplit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/filenew.png b/src/tools/designer/src/components/formeditor/images/mac/filenew.png new file mode 100644 index 00000000000..9dcba428446 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/filenew.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/fileopen.png b/src/tools/designer/src/components/formeditor/images/mac/fileopen.png new file mode 100644 index 00000000000..c12bcd5079f Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/fileopen.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/filesave.png b/src/tools/designer/src/components/formeditor/images/mac/filesave.png new file mode 100644 index 00000000000..b41ecf53198 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/filesave.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/forward.png b/src/tools/designer/src/components/formeditor/images/mac/forward.png new file mode 100644 index 00000000000..34b91f09fa3 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/forward.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/insertimage.png b/src/tools/designer/src/components/formeditor/images/mac/insertimage.png new file mode 100644 index 00000000000..b8673e13bc6 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/insertimage.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/minus.png b/src/tools/designer/src/components/formeditor/images/mac/minus.png new file mode 100644 index 00000000000..8d2eaed523f Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/minus.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/plus.png b/src/tools/designer/src/components/formeditor/images/mac/plus.png new file mode 100644 index 00000000000..1ee45423e39 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/plus.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/redo.png b/src/tools/designer/src/components/formeditor/images/mac/redo.png new file mode 100644 index 00000000000..8875bf246c5 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/redo.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/resetproperty.png b/src/tools/designer/src/components/formeditor/images/mac/resetproperty.png new file mode 100644 index 00000000000..9048252ec2a Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/resetproperty.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/signalslottool.png b/src/tools/designer/src/components/formeditor/images/mac/signalslottool.png new file mode 100644 index 00000000000..71c9b07a8d8 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/signalslottool.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/simplifyrichtext.png b/src/tools/designer/src/components/formeditor/images/mac/simplifyrichtext.png new file mode 100644 index 00000000000..cdfc086bbf8 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/simplifyrichtext.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/tabordertool.png b/src/tools/designer/src/components/formeditor/images/mac/tabordertool.png new file mode 100644 index 00000000000..f54faf9abe2 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/tabordertool.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textanchor.png b/src/tools/designer/src/components/formeditor/images/mac/textanchor.png new file mode 100644 index 00000000000..baa9dda52df Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textanchor.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textbold.png b/src/tools/designer/src/components/formeditor/images/mac/textbold.png new file mode 100644 index 00000000000..38400bd1f69 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textbold.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textcenter.png b/src/tools/designer/src/components/formeditor/images/mac/textcenter.png new file mode 100644 index 00000000000..2ef5b2ee6f3 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textcenter.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textitalic.png b/src/tools/designer/src/components/formeditor/images/mac/textitalic.png new file mode 100644 index 00000000000..0170ee26a6f Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textitalic.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textjustify.png b/src/tools/designer/src/components/formeditor/images/mac/textjustify.png new file mode 100644 index 00000000000..39cd6c1a9d7 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textjustify.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textleft.png b/src/tools/designer/src/components/formeditor/images/mac/textleft.png new file mode 100644 index 00000000000..83a66d55355 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textleft.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textright.png b/src/tools/designer/src/components/formeditor/images/mac/textright.png new file mode 100644 index 00000000000..e7c04645cf4 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textright.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textsubscript.png b/src/tools/designer/src/components/formeditor/images/mac/textsubscript.png new file mode 100644 index 00000000000..ff431f396da Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textsubscript.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textsuperscript.png b/src/tools/designer/src/components/formeditor/images/mac/textsuperscript.png new file mode 100644 index 00000000000..cb67a33d011 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textsuperscript.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/textunder.png b/src/tools/designer/src/components/formeditor/images/mac/textunder.png new file mode 100644 index 00000000000..968bac5e90e Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/textunder.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/undo.png b/src/tools/designer/src/components/formeditor/images/mac/undo.png new file mode 100644 index 00000000000..a3bd5e0bf21 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/undo.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/up.png b/src/tools/designer/src/components/formeditor/images/mac/up.png new file mode 100644 index 00000000000..e4373122171 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/up.png differ diff --git a/src/tools/designer/src/components/formeditor/images/mac/widgettool.png b/src/tools/designer/src/components/formeditor/images/mac/widgettool.png new file mode 100644 index 00000000000..e1aa353dbf3 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/mac/widgettool.png differ diff --git a/src/tools/designer/src/components/formeditor/images/minus-16.png b/src/tools/designer/src/components/formeditor/images/minus-16.png new file mode 100644 index 00000000000..745b445722b Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/minus-16.png differ diff --git a/src/tools/designer/src/components/formeditor/images/prefix-add.png b/src/tools/designer/src/components/formeditor/images/prefix-add.png new file mode 100644 index 00000000000..cfbb053f423 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/prefix-add.png differ diff --git a/src/tools/designer/src/components/formeditor/images/qtlogo128x128.png b/src/tools/designer/src/components/formeditor/images/qtlogo128x128.png new file mode 100644 index 00000000000..8dfeb1a02f4 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/qtlogo128x128.png differ diff --git a/src/tools/designer/src/components/formeditor/images/qtlogo16x16.png b/src/tools/designer/src/components/formeditor/images/qtlogo16x16.png new file mode 100644 index 00000000000..8496849840c Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/qtlogo16x16.png differ diff --git a/src/tools/designer/src/components/formeditor/images/qtlogo24x24.png b/src/tools/designer/src/components/formeditor/images/qtlogo24x24.png new file mode 100644 index 00000000000..4effcc4abeb Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/qtlogo24x24.png differ diff --git a/src/tools/designer/src/components/formeditor/images/qtlogo32x32.png b/src/tools/designer/src/components/formeditor/images/qtlogo32x32.png new file mode 100644 index 00000000000..d34a6a17f59 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/qtlogo32x32.png differ diff --git a/src/tools/designer/src/components/formeditor/images/qtlogo64x64.png b/src/tools/designer/src/components/formeditor/images/qtlogo64x64.png new file mode 100644 index 00000000000..e40b5c6fc80 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/qtlogo64x64.png differ diff --git a/src/tools/designer/src/components/formeditor/images/reload.png b/src/tools/designer/src/components/formeditor/images/reload.png new file mode 100644 index 00000000000..18c752e1461 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/reload.png differ diff --git a/src/tools/designer/src/components/formeditor/images/resetproperty.png b/src/tools/designer/src/components/formeditor/images/resetproperty.png new file mode 100644 index 00000000000..9048252ec2a Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/resetproperty.png differ diff --git a/src/tools/designer/src/components/formeditor/images/righttoleft.png b/src/tools/designer/src/components/formeditor/images/righttoleft.png new file mode 100644 index 00000000000..75906647945 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/righttoleft.png differ diff --git a/src/tools/designer/src/components/formeditor/images/sort.png b/src/tools/designer/src/components/formeditor/images/sort.png new file mode 100644 index 00000000000..883bfa9de53 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/sort.png differ diff --git a/src/tools/designer/src/components/formeditor/images/submenu.png b/src/tools/designer/src/components/formeditor/images/submenu.png new file mode 100644 index 00000000000..3deb28e3a88 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/submenu.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/calendarwidget.png b/src/tools/designer/src/components/formeditor/images/widgets/calendarwidget.png new file mode 100644 index 00000000000..26737b88389 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/calendarwidget.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/checkbox.png b/src/tools/designer/src/components/formeditor/images/widgets/checkbox.png new file mode 100644 index 00000000000..ab6f53e02c6 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/checkbox.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/columnview.png b/src/tools/designer/src/components/formeditor/images/widgets/columnview.png new file mode 100644 index 00000000000..4132ee6b1da Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/columnview.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/combobox.png b/src/tools/designer/src/components/formeditor/images/widgets/combobox.png new file mode 100644 index 00000000000..bf3ed79f7ec Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/combobox.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/commandlinkbutton.png b/src/tools/designer/src/components/formeditor/images/widgets/commandlinkbutton.png new file mode 100644 index 00000000000..6bbd84a9721 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/commandlinkbutton.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/dateedit.png b/src/tools/designer/src/components/formeditor/images/widgets/dateedit.png new file mode 100644 index 00000000000..6827fa742ef Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/dateedit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/datetimeedit.png b/src/tools/designer/src/components/formeditor/images/widgets/datetimeedit.png new file mode 100644 index 00000000000..7d8e6fe6d5c Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/datetimeedit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/dial.png b/src/tools/designer/src/components/formeditor/images/widgets/dial.png new file mode 100644 index 00000000000..050d1dbd01a Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/dial.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/dialogbuttonbox.png b/src/tools/designer/src/components/formeditor/images/widgets/dialogbuttonbox.png new file mode 100644 index 00000000000..b1f89fbb36f Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/dialogbuttonbox.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/dockwidget.png b/src/tools/designer/src/components/formeditor/images/widgets/dockwidget.png new file mode 100644 index 00000000000..9eee04f7019 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/dockwidget.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/doublespinbox.png b/src/tools/designer/src/components/formeditor/images/widgets/doublespinbox.png new file mode 100644 index 00000000000..5686ac89b3d Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/doublespinbox.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/fontcombobox.png b/src/tools/designer/src/components/formeditor/images/widgets/fontcombobox.png new file mode 100644 index 00000000000..6848f15c2e6 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/fontcombobox.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/frame.png b/src/tools/designer/src/components/formeditor/images/widgets/frame.png new file mode 100644 index 00000000000..68f5da0a363 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/frame.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/graphicsview.png b/src/tools/designer/src/components/formeditor/images/widgets/graphicsview.png new file mode 100644 index 00000000000..93fe7603ae8 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/graphicsview.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/groupbox.png b/src/tools/designer/src/components/formeditor/images/widgets/groupbox.png new file mode 100644 index 00000000000..4025b4dc512 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/groupbox.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/hscrollbar.png b/src/tools/designer/src/components/formeditor/images/widgets/hscrollbar.png new file mode 100644 index 00000000000..466c58de5bf Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/hscrollbar.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/hslider.png b/src/tools/designer/src/components/formeditor/images/widgets/hslider.png new file mode 100644 index 00000000000..525bd1cabdd Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/hslider.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/label.png b/src/tools/designer/src/components/formeditor/images/widgets/label.png new file mode 100644 index 00000000000..5d7d7b4cc9c Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/label.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/lcdnumber.png b/src/tools/designer/src/components/formeditor/images/widgets/lcdnumber.png new file mode 100644 index 00000000000..c3cac182659 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/lcdnumber.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/line.png b/src/tools/designer/src/components/formeditor/images/widgets/line.png new file mode 100644 index 00000000000..5c64dfb591c Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/line.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/lineedit.png b/src/tools/designer/src/components/formeditor/images/widgets/lineedit.png new file mode 100644 index 00000000000..75fc890f407 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/lineedit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/listbox.png b/src/tools/designer/src/components/formeditor/images/widgets/listbox.png new file mode 100644 index 00000000000..367e67ff577 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/listbox.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/listview.png b/src/tools/designer/src/components/formeditor/images/widgets/listview.png new file mode 100644 index 00000000000..d1308d57588 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/listview.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/mdiarea.png b/src/tools/designer/src/components/formeditor/images/widgets/mdiarea.png new file mode 100644 index 00000000000..7783dd527a5 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/mdiarea.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/plaintextedit.png b/src/tools/designer/src/components/formeditor/images/widgets/plaintextedit.png new file mode 100644 index 00000000000..077bf16cb9a Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/plaintextedit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/progress.png b/src/tools/designer/src/components/formeditor/images/widgets/progress.png new file mode 100644 index 00000000000..44ae094e7c0 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/progress.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/pushbutton.png b/src/tools/designer/src/components/formeditor/images/widgets/pushbutton.png new file mode 100644 index 00000000000..61f779ce2bf Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/pushbutton.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/radiobutton.png b/src/tools/designer/src/components/formeditor/images/widgets/radiobutton.png new file mode 100644 index 00000000000..10c1d8c3efd Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/radiobutton.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/scrollarea.png b/src/tools/designer/src/components/formeditor/images/widgets/scrollarea.png new file mode 100644 index 00000000000..651ea24c0e7 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/scrollarea.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/spacer.png b/src/tools/designer/src/components/formeditor/images/widgets/spacer.png new file mode 100644 index 00000000000..8a0931bf8d7 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/spacer.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/spinbox.png b/src/tools/designer/src/components/formeditor/images/widgets/spinbox.png new file mode 100644 index 00000000000..cdd9fe14132 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/spinbox.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/table.png b/src/tools/designer/src/components/formeditor/images/widgets/table.png new file mode 100644 index 00000000000..4bbd9c2d081 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/table.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/tabwidget.png b/src/tools/designer/src/components/formeditor/images/widgets/tabwidget.png new file mode 100644 index 00000000000..1254bb63a65 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/tabwidget.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/textedit.png b/src/tools/designer/src/components/formeditor/images/widgets/textedit.png new file mode 100644 index 00000000000..32e897d9727 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/textedit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/timeedit.png b/src/tools/designer/src/components/formeditor/images/widgets/timeedit.png new file mode 100644 index 00000000000..c66d91b2f78 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/timeedit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/toolbox.png b/src/tools/designer/src/components/formeditor/images/widgets/toolbox.png new file mode 100644 index 00000000000..2ab71dc743d Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/toolbox.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/toolbutton.png b/src/tools/designer/src/components/formeditor/images/widgets/toolbutton.png new file mode 100644 index 00000000000..0bff069a5df Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/toolbutton.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/vline.png b/src/tools/designer/src/components/formeditor/images/widgets/vline.png new file mode 100644 index 00000000000..35a7300a580 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/vline.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/vscrollbar.png b/src/tools/designer/src/components/formeditor/images/widgets/vscrollbar.png new file mode 100644 index 00000000000..28b7c40c6e2 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/vscrollbar.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/vslider.png b/src/tools/designer/src/components/formeditor/images/widgets/vslider.png new file mode 100644 index 00000000000..59f06bae4a5 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/vslider.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/vspacer.png b/src/tools/designer/src/components/formeditor/images/widgets/vspacer.png new file mode 100644 index 00000000000..ce5e8bd7dcc Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/vspacer.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/widget.png b/src/tools/designer/src/components/formeditor/images/widgets/widget.png new file mode 100644 index 00000000000..1cf960e61be Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/widget.png differ diff --git a/src/tools/designer/src/components/formeditor/images/widgets/widgetstack.png b/src/tools/designer/src/components/formeditor/images/widgets/widgetstack.png new file mode 100644 index 00000000000..2c6964e3edc Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/widgets/widgetstack.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/adjustsize.png b/src/tools/designer/src/components/formeditor/images/win/adjustsize.png new file mode 100644 index 00000000000..3cda3337136 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/adjustsize.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/back.png b/src/tools/designer/src/components/formeditor/images/win/back.png new file mode 100644 index 00000000000..e58177f43cb Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/back.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/buddytool.png b/src/tools/designer/src/components/formeditor/images/win/buddytool.png new file mode 100644 index 00000000000..4cd968bbf5f Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/buddytool.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/down.png b/src/tools/designer/src/components/formeditor/images/win/down.png new file mode 100644 index 00000000000..29d1d4439a1 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/down.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editbreaklayout.png b/src/tools/designer/src/components/formeditor/images/win/editbreaklayout.png new file mode 100644 index 00000000000..07c5fae696c Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editbreaklayout.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editcopy.png b/src/tools/designer/src/components/formeditor/images/win/editcopy.png new file mode 100644 index 00000000000..1121b47d8b6 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editcopy.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editcut.png b/src/tools/designer/src/components/formeditor/images/win/editcut.png new file mode 100644 index 00000000000..4b6c82c7a77 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editcut.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editdelete.png b/src/tools/designer/src/components/formeditor/images/win/editdelete.png new file mode 100644 index 00000000000..5a4251402d8 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editdelete.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editform.png b/src/tools/designer/src/components/formeditor/images/win/editform.png new file mode 100644 index 00000000000..452fcd8878b Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editform.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editgrid.png b/src/tools/designer/src/components/formeditor/images/win/editgrid.png new file mode 100644 index 00000000000..789bf7d9608 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editgrid.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/edithlayout.png b/src/tools/designer/src/components/formeditor/images/win/edithlayout.png new file mode 100644 index 00000000000..4dd3f0ce467 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/edithlayout.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/edithlayoutsplit.png b/src/tools/designer/src/components/formeditor/images/win/edithlayoutsplit.png new file mode 100644 index 00000000000..2dcc6902913 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/edithlayoutsplit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editlower.png b/src/tools/designer/src/components/formeditor/images/win/editlower.png new file mode 100644 index 00000000000..ba630944caf Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editlower.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editpaste.png b/src/tools/designer/src/components/formeditor/images/win/editpaste.png new file mode 100644 index 00000000000..ffab15aaf8d Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editpaste.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editraise.png b/src/tools/designer/src/components/formeditor/images/win/editraise.png new file mode 100644 index 00000000000..bb8362c1f1e Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editraise.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editvlayout.png b/src/tools/designer/src/components/formeditor/images/win/editvlayout.png new file mode 100644 index 00000000000..7ad28fdeabd Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editvlayout.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/editvlayoutsplit.png b/src/tools/designer/src/components/formeditor/images/win/editvlayoutsplit.png new file mode 100644 index 00000000000..720e18bb32f Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/editvlayoutsplit.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/filenew.png b/src/tools/designer/src/components/formeditor/images/win/filenew.png new file mode 100644 index 00000000000..af5d1221412 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/filenew.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/fileopen.png b/src/tools/designer/src/components/formeditor/images/win/fileopen.png new file mode 100644 index 00000000000..fc6f17e9774 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/fileopen.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/filesave.png b/src/tools/designer/src/components/formeditor/images/win/filesave.png new file mode 100644 index 00000000000..e65a29d5f17 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/filesave.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/forward.png b/src/tools/designer/src/components/formeditor/images/win/forward.png new file mode 100644 index 00000000000..34b91f09fa3 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/forward.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/insertimage.png b/src/tools/designer/src/components/formeditor/images/win/insertimage.png new file mode 100644 index 00000000000..cfab6375f75 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/insertimage.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/minus.png b/src/tools/designer/src/components/formeditor/images/win/minus.png new file mode 100644 index 00000000000..c0dc274bb4f Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/minus.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/plus.png b/src/tools/designer/src/components/formeditor/images/win/plus.png new file mode 100644 index 00000000000..ecf05894154 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/plus.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/redo.png b/src/tools/designer/src/components/formeditor/images/win/redo.png new file mode 100644 index 00000000000..686ad141c6e Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/redo.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/signalslottool.png b/src/tools/designer/src/components/formeditor/images/win/signalslottool.png new file mode 100644 index 00000000000..e80fd1caa62 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/signalslottool.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/simplifyrichtext.png b/src/tools/designer/src/components/formeditor/images/win/simplifyrichtext.png new file mode 100644 index 00000000000..e251cf7b9c2 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/simplifyrichtext.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/tabordertool.png b/src/tools/designer/src/components/formeditor/images/win/tabordertool.png new file mode 100644 index 00000000000..7e6e2de716e Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/tabordertool.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textanchor.png b/src/tools/designer/src/components/formeditor/images/win/textanchor.png new file mode 100644 index 00000000000..1911ab0d5ab Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textanchor.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textbold.png b/src/tools/designer/src/components/formeditor/images/win/textbold.png new file mode 100644 index 00000000000..9cbc7138b90 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textbold.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textcenter.png b/src/tools/designer/src/components/formeditor/images/win/textcenter.png new file mode 100644 index 00000000000..11efb4b8524 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textcenter.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textitalic.png b/src/tools/designer/src/components/formeditor/images/win/textitalic.png new file mode 100644 index 00000000000..b30ce14c14d Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textitalic.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textjustify.png b/src/tools/designer/src/components/formeditor/images/win/textjustify.png new file mode 100644 index 00000000000..9de0c880850 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textjustify.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textleft.png b/src/tools/designer/src/components/formeditor/images/win/textleft.png new file mode 100644 index 00000000000..16f80bc3258 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textleft.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textright.png b/src/tools/designer/src/components/formeditor/images/win/textright.png new file mode 100644 index 00000000000..16872df62a9 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textright.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textsubscript.png b/src/tools/designer/src/components/formeditor/images/win/textsubscript.png new file mode 100644 index 00000000000..d86347d839c Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textsubscript.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textsuperscript.png b/src/tools/designer/src/components/formeditor/images/win/textsuperscript.png new file mode 100644 index 00000000000..910996560ca Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textsuperscript.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/textunder.png b/src/tools/designer/src/components/formeditor/images/win/textunder.png new file mode 100644 index 00000000000..c72eff53fba Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/textunder.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/undo.png b/src/tools/designer/src/components/formeditor/images/win/undo.png new file mode 100644 index 00000000000..c3b8c51368e Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/undo.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/up.png b/src/tools/designer/src/components/formeditor/images/win/up.png new file mode 100644 index 00000000000..e4373122171 Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/up.png differ diff --git a/src/tools/designer/src/components/formeditor/images/win/widgettool.png b/src/tools/designer/src/components/formeditor/images/win/widgettool.png new file mode 100644 index 00000000000..a52224e068c Binary files /dev/null and b/src/tools/designer/src/components/formeditor/images/win/widgettool.png differ diff --git a/src/tools/designer/src/components/formeditor/itemview_propertysheet.cpp b/src/tools/designer/src/components/formeditor/itemview_propertysheet.cpp new file mode 100644 index 00000000000..3124273417e --- /dev/null +++ b/src/tools/designer/src/components/formeditor/itemview_propertysheet.cpp @@ -0,0 +1,228 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "itemview_propertysheet.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +struct Property { + Property() = default; + Property(QDesignerPropertySheetExtension *sheet, int id) + : m_sheet(sheet), m_id(id) {} + + QDesignerPropertySheetExtension *m_sheet{nullptr}; + int m_id{-1}; +}; + +struct ItemViewPropertySheetPrivate { + ItemViewPropertySheetPrivate(QDesignerFormEditorInterface *core, + QHeaderView *horizontalHeader, + QHeaderView *verticalHeader); + + inline QStringList realPropertyNames(); + inline QString fakePropertyName(const QString &prefix, const QString &realName); + + // Maps index of fake property to index of real property in respective sheet + QMap m_propertyIdMap; + + // Maps name of fake property to name of real property + QHash m_propertyNameMap; + + QHash m_propertySheet; + QStringList m_realPropertyNames; +}; + +// Name of the fake group +static constexpr auto headerGroup = "Header"_L1; + +// Name of the real properties +static constexpr auto visibleProperty = "visible"_L1; +static constexpr auto cascadingSectionResizesProperty = "cascadingSectionResizes"_L1; +static constexpr auto defaultSectionSizeProperty = "defaultSectionSize"_L1; +static constexpr auto highlightSectionsProperty = "highlightSections"_L1; +static constexpr auto minimumSectionSizeProperty = "minimumSectionSize"_L1; +static constexpr auto showSortIndicatorProperty = "showSortIndicator"_L1; +static constexpr auto stretchLastSectionProperty = "stretchLastSection"_L1; + +/***************** ItemViewPropertySheetPrivate *********************/ + +ItemViewPropertySheetPrivate::ItemViewPropertySheetPrivate(QDesignerFormEditorInterface *core, + QHeaderView *horizontalHeader, + QHeaderView *verticalHeader) +{ + if (horizontalHeader) + m_propertySheet.insert(horizontalHeader, + qt_extension + (core->extensionManager(), horizontalHeader)); + if (verticalHeader) + m_propertySheet.insert(verticalHeader, + qt_extension + (core->extensionManager(), verticalHeader)); +} + +QStringList ItemViewPropertySheetPrivate::realPropertyNames() +{ + if (m_realPropertyNames.isEmpty()) + m_realPropertyNames = { + visibleProperty, cascadingSectionResizesProperty, + defaultSectionSizeProperty, highlightSectionsProperty, + minimumSectionSizeProperty, showSortIndicatorProperty, + stretchLastSectionProperty + }; + return m_realPropertyNames; +} + +QString ItemViewPropertySheetPrivate::fakePropertyName(const QString &prefix, + const QString &realName) +{ + // prefix = "header", realPropertyName = "isVisible" returns "headerIsVisible" + QString fakeName = prefix + realName.at(0).toUpper() + realName.mid(1); + m_propertyNameMap.insert(fakeName, realName); + return fakeName; +} + +/***************** ItemViewPropertySheet *********************/ + +/*! + \class qdesigner_internal::ItemViewPropertySheet + + \brief + Adds header fake properties to QTreeView and QTableView objects + + QHeaderView objects are currently not shown in the object inspector. + This class adds some fake properties to the property sheet + of QTreeView and QTableView objects that nevertheless allow the manipulation + of the headers attached to the item view object. + + Currently the defaultAlignment property is not shown because the property sheet + would only show integers, instead of the Qt::Alignment enumeration. + + The fake properties here need special handling in QDesignerResource, uiloader and uic. + */ + +ItemViewPropertySheet::ItemViewPropertySheet(QTreeView *treeViewObject, QObject *parent) + : QDesignerPropertySheet(treeViewObject, parent), + d(new ItemViewPropertySheetPrivate(core(), treeViewObject->header(), nullptr)) +{ + initHeaderProperties(treeViewObject->header(), u"header"_s); +} + +ItemViewPropertySheet::ItemViewPropertySheet(QTableView *tableViewObject, QObject *parent) + : QDesignerPropertySheet(tableViewObject, parent), + d(new ItemViewPropertySheetPrivate(core(), + tableViewObject->horizontalHeader(), + tableViewObject->verticalHeader())) +{ + initHeaderProperties(tableViewObject->horizontalHeader(), u"horizontalHeader"_s); + initHeaderProperties(tableViewObject->verticalHeader(), u"verticalHeader"_s); +} + +ItemViewPropertySheet::~ItemViewPropertySheet() +{ + delete d; +} + +void ItemViewPropertySheet::initHeaderProperties(QHeaderView *hv, const QString &prefix) +{ + QDesignerPropertySheetExtension *headerSheet = d->m_propertySheet.value(hv); + Q_ASSERT(headerSheet); + const QString headerGroupS = headerGroup; + const QStringList &realPropertyNames = d->realPropertyNames(); + for (const QString &realPropertyName : realPropertyNames) { + const int headerIndex = headerSheet->indexOf(realPropertyName); + Q_ASSERT(headerIndex != -1); + const QVariant defaultValue = realPropertyName == visibleProperty ? + QVariant(true) : headerSheet->property(headerIndex); + const QString fakePropertyName = d->fakePropertyName(prefix, realPropertyName); + const int fakeIndex = createFakeProperty(fakePropertyName, defaultValue); + d->m_propertyIdMap.insert(fakeIndex, Property(headerSheet, headerIndex)); + setAttribute(fakeIndex, true); + setPropertyGroup(fakeIndex, headerGroupS); + } +} + +/*! + Returns the mapping of fake property names to real property names + */ +QHash ItemViewPropertySheet::propertyNameMap() const +{ + return d->m_propertyNameMap; +} + +QVariant ItemViewPropertySheet::property(int index) const +{ + const auto it = d->m_propertyIdMap.constFind(index); + if (it != d->m_propertyIdMap.constEnd()) + return it.value().m_sheet->property(it.value().m_id); + return QDesignerPropertySheet::property(index); +} + +void ItemViewPropertySheet::setProperty(int index, const QVariant &value) +{ + const auto it = d->m_propertyIdMap.find(index); + if (it != d->m_propertyIdMap.end()) { + it.value().m_sheet->setProperty(it.value().m_id, value); + } else { + QDesignerPropertySheet::setProperty(index, value); + } +} + +void ItemViewPropertySheet::setChanged(int index, bool changed) +{ + const auto it = d->m_propertyIdMap.find(index); + if (it != d->m_propertyIdMap.end()) { + it.value().m_sheet->setChanged(it.value().m_id, changed); + } else { + QDesignerPropertySheet::setChanged(index, changed); + } +} + +bool ItemViewPropertySheet::isChanged(int index) const +{ + const auto it = d->m_propertyIdMap.constFind(index); + if (it != d->m_propertyIdMap.constEnd()) + return it.value().m_sheet->isChanged(it.value().m_id); + return QDesignerPropertySheet::isChanged(index); +} + +bool ItemViewPropertySheet::hasReset(int index) const +{ + const auto it = d->m_propertyIdMap.constFind(index); + if (it != d->m_propertyIdMap.constEnd()) + return it.value().m_sheet->hasReset(it.value().m_id); + return QDesignerPropertySheet::hasReset(index); +} + +bool ItemViewPropertySheet::reset(int index) +{ + const auto it = d->m_propertyIdMap.find(index); + if (it != d->m_propertyIdMap.end()) { + QDesignerPropertySheetExtension *headerSheet = it.value().m_sheet; + const int headerIndex = it.value().m_id; + const bool resetRC = headerSheet->reset(headerIndex); + // Resetting for "visible" might fail and the stored default + // of the Widget database is "false" due to the widget not being + // visible at the time it was determined. Reset to "true" manually. + if (!resetRC && headerSheet->propertyName(headerIndex) == visibleProperty) { + headerSheet->setProperty(headerIndex, QVariant(true)); + headerSheet->setChanged(headerIndex, false); + return true; + } + return resetRC; + } + return QDesignerPropertySheet::reset(index); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/itemview_propertysheet.h b/src/tools/designer/src/components/formeditor/itemview_propertysheet.h new file mode 100644 index 00000000000..5ef06197887 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/itemview_propertysheet.h @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ITEMVIEW_PROPERTYSHEET_H +#define ITEMVIEW_PROPERTYSHEET_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +struct ItemViewPropertySheetPrivate; + +class ItemViewPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit ItemViewPropertySheet(QTreeView *treeViewObject, QObject *parent = nullptr); + explicit ItemViewPropertySheet(QTableView *tableViewObject, QObject *parent = nullptr); + ~ItemViewPropertySheet(); + + QHash propertyNameMap() const; + + // QDesignerPropertySheet + QVariant property(int index) const override; + void setProperty(int index, const QVariant &value) override; + + void setChanged(int index, bool changed) override; + bool isChanged(int index) const override; + + bool hasReset(int index) const override; + bool reset(int index) override; + +private: + void initHeaderProperties(QHeaderView *hv, const QString &prefix); + + ItemViewPropertySheetPrivate *d; +}; + +using QTreeViewPropertySheetFactory = QDesignerPropertySheetFactory; +using QTableViewPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ITEMVIEW_PROPERTYSHEET_H diff --git a/src/tools/designer/src/components/formeditor/layout_propertysheet.cpp b/src/tools/designer/src/components/formeditor/layout_propertysheet.cpp new file mode 100644 index 00000000000..8447f3fd507 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/layout_propertysheet.cpp @@ -0,0 +1,491 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "layout_propertysheet.h" + +// sdk +#include +#include +// shared + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include // Remove once there is an editor for lists + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +#define USE_LAYOUT_SIZE_CONSTRAINT + +static constexpr auto leftMargin = "leftMargin"_L1; +static constexpr auto topMargin = "topMargin"_L1; +static constexpr auto rightMargin = "rightMargin"_L1; +static constexpr auto bottomMargin = "bottomMargin"_L1; +static constexpr auto horizontalSpacing = "horizontalSpacing"_L1; +static constexpr auto verticalSpacing = "verticalSpacing"_L1; +static constexpr auto spacing = "spacing"_L1; +static constexpr auto sizeConstraint = "sizeConstraint"_L1; +static constexpr auto boxStretchPropertyC = "stretch"_L1; +static constexpr auto gridRowStretchPropertyC = "rowStretch"_L1; +static constexpr auto gridColumnStretchPropertyC = "columnStretch"_L1; +static constexpr auto gridRowMinimumHeightPropertyC = "rowMinimumHeight"_L1; +static constexpr auto gridColumnMinimumWidthPropertyC = "columnMinimumWidth"_L1; + +namespace { + enum LayoutPropertyType { + LayoutPropertyNone, + LayoutPropertyLeftMargin, + LayoutPropertyTopMargin, + LayoutPropertyRightMargin, + LayoutPropertyBottomMargin, + LayoutPropertySpacing, + LayoutPropertyHorizontalSpacing, + LayoutPropertyVerticalSpacing, + LayoutPropertySizeConstraint, + LayoutPropertyBoxStretch, + LayoutPropertyGridRowStretch, + LayoutPropertyGridColumnStretch, + LayoutPropertyGridRowMinimumHeight, + LayoutPropertyGridColumnMinimumWidth + }; +} + +// Check for a comma-separated list of integers. Used for +// per-cell stretch properties and grid per row/column properties. +// As it works now, they are passed as QByteArray strings. The +// property sheet refuses all invalid values. This could be +// replaced by lists once the property editor can handle them. + +static bool isIntegerList(const QString &s) +{ + // Check for empty string or comma-separated list of integers + static const QRegularExpression re(u"^[0-9]+(,[0-9]+)+$"_s); + Q_ASSERT(re.isValid()); + return s.isEmpty() || re.match(s).hasMatch(); +} + +// Quick lookup by name +static LayoutPropertyType layoutPropertyType(const QString &name) +{ + static const QHash namePropertyMap = { + {leftMargin, LayoutPropertyLeftMargin}, + {topMargin, LayoutPropertyTopMargin}, + {rightMargin, LayoutPropertyRightMargin}, + {bottomMargin, LayoutPropertyBottomMargin}, + {horizontalSpacing, LayoutPropertyHorizontalSpacing}, + {verticalSpacing, LayoutPropertyVerticalSpacing}, + {spacing, LayoutPropertySpacing}, + {sizeConstraint, LayoutPropertySizeConstraint}, + {boxStretchPropertyC, LayoutPropertyBoxStretch}, + {gridRowStretchPropertyC, LayoutPropertyGridRowStretch}, + {gridColumnStretchPropertyC, LayoutPropertyGridColumnStretch}, + {gridRowMinimumHeightPropertyC, LayoutPropertyGridRowMinimumHeight}, + {gridColumnMinimumWidthPropertyC, LayoutPropertyGridColumnMinimumWidth} + }; + return namePropertyMap.value(name, LayoutPropertyNone); +} + +// return the layout margin if it is margin +static int getLayoutMargin(const QLayout *l, LayoutPropertyType type) +{ + int left, top, right, bottom; + l->getContentsMargins(&left, &top, &right, &bottom); + switch (type) { + case LayoutPropertyLeftMargin: + return left; + case LayoutPropertyTopMargin: + return top; + case LayoutPropertyRightMargin: + return right; + case LayoutPropertyBottomMargin: + return bottom; + default: + Q_ASSERT(0); + break; + } + return 0; +} + +// return the layout margin if it is margin +static void setLayoutMargin(QLayout *l, LayoutPropertyType type, int margin) +{ + int left, top, right, bottom; + l->getContentsMargins(&left, &top, &right, &bottom); + switch (type) { + case LayoutPropertyLeftMargin: + left = margin; + break; + case LayoutPropertyTopMargin: + top = margin; + break; + case LayoutPropertyRightMargin: + right = margin; + break; + case LayoutPropertyBottomMargin: + bottom = margin; + break; + default: + Q_ASSERT(0); + break; + } + l->setContentsMargins(left, top, right, bottom); +} + +namespace qdesigner_internal { + +// ---------- LayoutPropertySheet: This sheet is never visible in +// the property editor. Rather, the sheet pulled for QLayoutWidget +// forwards all properties to it. Some properties (grid spacings) must be handled +// manually, as they are QDOC_PROPERTY only and not visible to introspection. Ditto +// for the 4 margins. + +LayoutPropertySheet::LayoutPropertySheet(QLayout *l, QObject *parent) + : QDesignerPropertySheet(l, parent), m_layout(l) +{ + const QString layoutGroup = u"Layout"_s; + int pindex = createFakeProperty(leftMargin, 0); + setPropertyGroup(pindex, layoutGroup); + + pindex = createFakeProperty(topMargin, 0); + setPropertyGroup(pindex, layoutGroup); + + pindex = createFakeProperty(rightMargin, 0); + setPropertyGroup(pindex, layoutGroup); + + pindex = createFakeProperty(bottomMargin, 0); + setPropertyGroup(pindex, layoutGroup); + + const int visibleMask = LayoutProperties::visibleProperties(m_layout); + if (visibleMask & LayoutProperties::HorizSpacingProperty) { + pindex = createFakeProperty(horizontalSpacing, 0); + setPropertyGroup(pindex, layoutGroup); + + pindex = createFakeProperty(verticalSpacing, 0); + setPropertyGroup(pindex, layoutGroup); + + setAttribute(indexOf(spacing), true); + } + + // Stretch + if (visibleMask & LayoutProperties::BoxStretchProperty) { + pindex = createFakeProperty(boxStretchPropertyC, QByteArray()); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + } else { + // Add the grid per-row/column stretch and size limits + if (visibleMask & LayoutProperties::GridColumnStretchProperty) { + const QByteArray empty; + pindex = createFakeProperty(gridRowStretchPropertyC, empty); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + pindex = createFakeProperty(gridColumnStretchPropertyC, empty); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + pindex = createFakeProperty(gridRowMinimumHeightPropertyC, empty); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + pindex = createFakeProperty(gridColumnMinimumWidthPropertyC, empty); + setPropertyGroup(pindex, layoutGroup); + setAttribute(pindex, true); + } + } +#ifdef USE_LAYOUT_SIZE_CONSTRAINT + // SizeConstraint cannot possibly be handled as a real property + // as it affects the layout parent widget and thus + // conflicts with Designer's special layout widget. + // It will take effect on the preview only. + pindex = createFakeProperty(sizeConstraint); + setPropertyGroup(pindex, layoutGroup); +#endif +} + +LayoutPropertySheet::~LayoutPropertySheet() = default; + +void LayoutPropertySheet::setProperty(int index, const QVariant &value) +{ + const LayoutPropertyType type = layoutPropertyType(propertyName(index)); + if (QLayoutWidget *lw = qobject_cast(m_layout->parent())) { + switch (type) { + case LayoutPropertyLeftMargin: + lw->setLayoutLeftMargin(value.toInt()); + return; + case LayoutPropertyTopMargin: + lw->setLayoutTopMargin(value.toInt()); + return; + case LayoutPropertyRightMargin: + lw->setLayoutRightMargin(value.toInt()); + return; + case LayoutPropertyBottomMargin: + lw->setLayoutBottomMargin(value.toInt()); + return; + default: + break; + } + } + switch (type) { + case LayoutPropertyLeftMargin: + case LayoutPropertyTopMargin: + case LayoutPropertyRightMargin: + case LayoutPropertyBottomMargin: + setLayoutMargin(m_layout, type, value.toInt()); + return; + case LayoutPropertyHorizontalSpacing: + if (QGridLayout *grid = qobject_cast(m_layout)) { + grid->setHorizontalSpacing(value.toInt()); + return; + } + if (QFormLayout *form = qobject_cast(m_layout)) { + form->setHorizontalSpacing(value.toInt()); + return; + } + break; + case LayoutPropertyVerticalSpacing: + if (QGridLayout *grid = qobject_cast(m_layout)) { + grid->setVerticalSpacing(value.toInt()); + return; + } + if (QFormLayout *form = qobject_cast(m_layout)) { + form->setVerticalSpacing(value.toInt()); + return; + } + break; + case LayoutPropertyBoxStretch: + // TODO: Remove the regexp check once a proper editor for integer + // lists is in place? + if (QBoxLayout *box = qobject_cast(m_layout)) { + const QString stretch = value.toString(); + if (isIntegerList(stretch)) + QFormBuilderExtra::setBoxLayoutStretch(value.toString(), box); + } + break; + case LayoutPropertyGridRowStretch: + if (QGridLayout *grid = qobject_cast(m_layout)) { + const QString stretch = value.toString(); + if (isIntegerList(stretch)) + QFormBuilderExtra::setGridLayoutRowStretch(stretch, grid); + } + break; + case LayoutPropertyGridColumnStretch: + if (QGridLayout *grid = qobject_cast(m_layout)) { + const QString stretch = value.toString(); + if (isIntegerList(stretch)) + QFormBuilderExtra::setGridLayoutColumnStretch(value.toString(), grid); + } + break; + case LayoutPropertyGridRowMinimumHeight: + if (QGridLayout *grid = qobject_cast(m_layout)) { + const QString minSize = value.toString(); + if (isIntegerList(minSize)) + QFormBuilderExtra::setGridLayoutRowMinimumHeight(minSize, grid); + } + break; + case LayoutPropertyGridColumnMinimumWidth: + if (QGridLayout *grid = qobject_cast(m_layout)) { + const QString minSize = value.toString(); + if (isIntegerList(minSize)) + QFormBuilderExtra::setGridLayoutColumnMinimumWidth(minSize, grid); + } + break; + default: + break; + } + QDesignerPropertySheet::setProperty(index, value); +} + +QVariant LayoutPropertySheet::property(int index) const +{ + const LayoutPropertyType type = layoutPropertyType(propertyName(index)); + if (const QLayoutWidget *lw = qobject_cast(m_layout->parent())) { + switch (type) { + case LayoutPropertyLeftMargin: + return lw->layoutLeftMargin(); + case LayoutPropertyTopMargin: + return lw->layoutTopMargin(); + case LayoutPropertyRightMargin: + return lw->layoutRightMargin(); + case LayoutPropertyBottomMargin: + return lw->layoutBottomMargin(); + default: + break; + } + } + switch (type) { + case LayoutPropertyLeftMargin: + case LayoutPropertyTopMargin: + case LayoutPropertyRightMargin: + case LayoutPropertyBottomMargin: + return getLayoutMargin(m_layout, type); + case LayoutPropertyHorizontalSpacing: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return grid->horizontalSpacing(); + if (const QFormLayout *form = qobject_cast(m_layout)) + return form->horizontalSpacing(); + break; + case LayoutPropertyVerticalSpacing: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return grid->verticalSpacing(); + if (const QFormLayout *form = qobject_cast(m_layout)) + return form->verticalSpacing(); + break; + case LayoutPropertyBoxStretch: + if (const QBoxLayout *box = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::boxLayoutStretch(box).toUtf8())); + break; + case LayoutPropertyGridRowStretch: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::gridLayoutRowStretch(grid).toUtf8())); + break; + case LayoutPropertyGridColumnStretch: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::gridLayoutColumnStretch(grid).toUtf8())); + break; + case LayoutPropertyGridRowMinimumHeight: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::gridLayoutRowMinimumHeight(grid).toUtf8())); + break; + case LayoutPropertyGridColumnMinimumWidth: + if (const QGridLayout *grid = qobject_cast(m_layout)) + return QVariant(QByteArray(QFormBuilderExtra::gridLayoutColumnMinimumWidth(grid).toUtf8())); + break; + default: + break; + } + return QDesignerPropertySheet::property(index); +} + +bool LayoutPropertySheet::reset(int index) +{ + int left, top, right, bottom; + m_layout->getContentsMargins(&left, &top, &right, &bottom); + const LayoutPropertyType type = layoutPropertyType(propertyName(index)); + bool rc = true; + switch (type) { + case LayoutPropertyLeftMargin: + m_layout->setContentsMargins(-1, top, right, bottom); + break; + case LayoutPropertyTopMargin: + m_layout->setContentsMargins(left, -1, right, bottom); + break; + case LayoutPropertyRightMargin: + m_layout->setContentsMargins(left, top, -1, bottom); + break; + case LayoutPropertyBottomMargin: + m_layout->setContentsMargins(left, top, right, -1); + break; + case LayoutPropertyBoxStretch: + if (QBoxLayout *box = qobject_cast(m_layout)) + QFormBuilderExtra::clearBoxLayoutStretch(box); + break; + case LayoutPropertyGridRowStretch: + if (QGridLayout *grid = qobject_cast(m_layout)) + QFormBuilderExtra::clearGridLayoutRowStretch(grid); + break; + case LayoutPropertyGridColumnStretch: + if (QGridLayout *grid = qobject_cast(m_layout)) + QFormBuilderExtra::clearGridLayoutColumnStretch(grid); + break; + case LayoutPropertyGridRowMinimumHeight: + if (QGridLayout *grid = qobject_cast(m_layout)) + QFormBuilderExtra::clearGridLayoutRowMinimumHeight(grid); + break; + case LayoutPropertyGridColumnMinimumWidth: + if (QGridLayout *grid = qobject_cast(m_layout)) + QFormBuilderExtra::clearGridLayoutColumnMinimumWidth(grid); + break; + default: + rc = QDesignerPropertySheet::reset(index); + break; + } + return rc; +} + +void LayoutPropertySheet::setChanged(int index, bool changed) +{ + const LayoutPropertyType type = layoutPropertyType(propertyName(index)); + switch (type) { + case LayoutPropertySpacing: + if (LayoutProperties::visibleProperties(m_layout) & LayoutProperties::HorizSpacingProperty) { + setChanged(indexOf(horizontalSpacing), changed); + setChanged(indexOf(verticalSpacing), changed); + } + break; + default: + break; + } + QDesignerPropertySheet::setChanged(index, changed); +} + +void LayoutPropertySheet::stretchAttributesToDom(QDesignerFormEditorInterface *core, QLayout *lt, DomLayout *domLayout) +{ + // Check if the respective stretch properties of the layout are changed. + // If so, set them to the DOM + const int visibleMask = LayoutProperties::visibleProperties(lt); + if (!(visibleMask & (LayoutProperties::BoxStretchProperty|LayoutProperties::GridColumnStretchProperty|LayoutProperties::GridRowStretchProperty))) + return; + const QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), lt); + Q_ASSERT(sheet); + + // Stretch + if (visibleMask & LayoutProperties::BoxStretchProperty) { + const int index = sheet->indexOf(boxStretchPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeStretch(sheet->property(index).toString()); + } + if (visibleMask & LayoutProperties::GridColumnStretchProperty) { + const int index = sheet->indexOf(gridColumnStretchPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeColumnStretch(sheet->property(index).toString()); + } + if (visibleMask & LayoutProperties::GridRowStretchProperty) { + const int index = sheet->indexOf(gridRowStretchPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeRowStretch(sheet->property(index).toString()); + } + if (visibleMask & LayoutProperties::GridRowMinimumHeightProperty) { + const int index = sheet->indexOf(gridRowMinimumHeightPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeRowMinimumHeight(sheet->property(index).toString()); + } + if (visibleMask & LayoutProperties::GridColumnMinimumWidthProperty) { + const int index = sheet->indexOf(gridColumnMinimumWidthPropertyC); + Q_ASSERT(index != -1); + if (sheet->isChanged(index)) + domLayout->setAttributeColumnMinimumWidth(sheet->property(index).toString()); + } +} + +void LayoutPropertySheet::markChangedStretchProperties(QDesignerFormEditorInterface *core, QLayout *lt, const DomLayout *domLayout) +{ + // While the actual values are applied by the form builder, we still need + // to mark them as 'changed'. + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), lt); + Q_ASSERT(sheet); + if (!domLayout->attributeStretch().isEmpty()) + sheet->setChanged(sheet->indexOf(boxStretchPropertyC), true); + if (!domLayout->attributeRowStretch().isEmpty()) + sheet->setChanged(sheet->indexOf(gridRowStretchPropertyC), true); + if (!domLayout->attributeColumnStretch().isEmpty()) + sheet->setChanged(sheet->indexOf(gridColumnStretchPropertyC), true); + if (!domLayout->attributeColumnMinimumWidth().isEmpty()) + sheet->setChanged(sheet->indexOf(gridColumnMinimumWidthPropertyC), true); + if (!domLayout->attributeRowMinimumHeight().isEmpty()) + sheet->setChanged(sheet->indexOf(gridRowMinimumHeightPropertyC), true); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/layout_propertysheet.h b/src/tools/designer/src/components/formeditor/layout_propertysheet.h new file mode 100644 index 00000000000..5d59071de7d --- /dev/null +++ b/src/tools/designer/src/components/formeditor/layout_propertysheet.h @@ -0,0 +1,44 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LAYOUT_PROPERTYSHEET_H +#define LAYOUT_PROPERTYSHEET_H + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class DomLayout; + +namespace qdesigner_internal { + +class LayoutPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit LayoutPropertySheet(QLayout *object, QObject *parent = nullptr); + ~LayoutPropertySheet() override; + + void setProperty(int index, const QVariant &value) override; + QVariant property(int index) const override; + bool reset(int index) override; + void setChanged(int index, bool changed) override; + + static void stretchAttributesToDom(QDesignerFormEditorInterface *core, QLayout *lt, DomLayout *domLayout); + static void markChangedStretchProperties(QDesignerFormEditorInterface *core, QLayout *lt, const DomLayout *domLayout); + +private: + QLayout *m_layout; +}; + +using LayoutPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LAYOUT_PROPERTYSHEET_H diff --git a/src/tools/designer/src/components/formeditor/line_propertysheet.cpp b/src/tools/designer/src/components/formeditor/line_propertysheet.cpp new file mode 100644 index 00000000000..b155e78dbbf --- /dev/null +++ b/src/tools/designer/src/components/formeditor/line_propertysheet.cpp @@ -0,0 +1,49 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "line_propertysheet.h" +#include "formwindow.h" + +// sdk +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +LinePropertySheet::LinePropertySheet(Line *object, QObject *parent) + : QDesignerPropertySheet(object, parent) +{ + clearFakeProperties(); +} + +LinePropertySheet::~LinePropertySheet() = default; + +bool LinePropertySheet::isVisible(int index) const +{ + const QString name = propertyName(index); + + if (name == "frameShape"_L1) + return false; + return QDesignerPropertySheet::isVisible(index); +} + +void LinePropertySheet::setProperty(int index, const QVariant &value) +{ + QDesignerPropertySheet::setProperty(index, value); +} + +QString LinePropertySheet::propertyGroup(int index) const +{ + return QDesignerPropertySheet::propertyGroup(index); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/line_propertysheet.h b/src/tools/designer/src/components/formeditor/line_propertysheet.h new file mode 100644 index 00000000000..ba1dbe5a7e2 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/line_propertysheet.h @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LINE_PROPERTYSHEET_H +#define LINE_PROPERTYSHEET_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class LinePropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit LinePropertySheet(Line *object, QObject *parent = nullptr); + ~LinePropertySheet() override; + + void setProperty(int index, const QVariant &value) override; + bool isVisible(int index) const override; + QString propertyGroup(int index) const override; +}; + +using LinePropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LINE_PROPERTYSHEET_H diff --git a/src/tools/designer/src/components/formeditor/previewactiongroup.cpp b/src/tools/designer/src/components/formeditor/previewactiongroup.cpp new file mode 100644 index 00000000000..abcf0b8bd5c --- /dev/null +++ b/src/tools/designer/src/components/formeditor/previewactiongroup.cpp @@ -0,0 +1,100 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "previewactiongroup.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { MaxDeviceActions = 20 }; + +namespace qdesigner_internal { + +PreviewActionGroup::PreviewActionGroup(QDesignerFormEditorInterface *core, QObject *parent) : + QActionGroup(parent), + m_core(core) +{ + /* Create a list of up to MaxDeviceActions invisible actions to be + * populated with device profiles (actiondata: index) followed by the + * standard style actions (actiondata: style name). */ + connect(this, &PreviewActionGroup::triggered, this, &PreviewActionGroup::slotTriggered); + setExclusive(true); + + // Create invisible actions for devices. Set index as action data. + for (int i = 0; i < MaxDeviceActions; i++) { + QAction *a = new QAction(this); + a->setObjectName(QString::asprintf("__qt_designer_device_%d_action", i)); + a->setVisible(false); + a->setData(i); + addAction(a); + } + // Create separator at index MaxDeviceActions + QAction *sep = new QAction(this); + sep->setObjectName(u"__qt_designer_deviceseparator"_s); + sep->setSeparator(true); + sep->setVisible(false); + addAction(sep); + // Populate devices + updateDeviceProfiles(); + + // Add style actions + const QStringList styles = QStyleFactory::keys(); + // Make sure ObjectName is unique in case toolbar solution is used. + // Create styles. Set style name string as action data. + for (const auto &s : styles) { + QAction *a = new QAction(tr("%1 Style").arg(s), this); + a->setObjectName("__qt_designer_style_"_L1 + s + "_action"_L1); + a->setData(s); + addAction(a); + } +} + +void PreviewActionGroup::updateDeviceProfiles() +{ + const QDesignerSharedSettings settings(m_core); + const auto profiles = settings.deviceProfiles(); + const auto al = actions(); + // Separator? + const bool hasProfiles = !profiles.isEmpty(); + al.at(MaxDeviceActions)->setVisible(hasProfiles); + int index = 0; + if (hasProfiles) { + // Make actions visible + const int maxIndex = qMin(static_cast(MaxDeviceActions), profiles.size()); + for (; index < maxIndex; index++) { + const QString name = profiles.at(index).name(); + al.at(index)->setText(name); + al.at(index)->setVisible(true); + } + } + // Hide rest + for ( ; index < MaxDeviceActions; index++) + al.at(index)->setVisible(false); +} + +void PreviewActionGroup::slotTriggered(QAction *a) +{ + // Device or style according to data. + const QVariant data = a->data(); + switch (data.metaType().id()) { + case QMetaType::QString: + emit preview(data.toString(), -1); + break; + case QMetaType::Int: + emit preview(QString(), data.toInt()); + break; + default: + break; + } +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/previewactiongroup.h b/src/tools/designer/src/components/formeditor/previewactiongroup.h new file mode 100644 index 00000000000..cb0ee0a1970 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/previewactiongroup.h @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PREVIEWACTIONGROUP_H +#define PREVIEWACTIONGROUP_H + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +/* PreviewActionGroup: To be used as a submenu for 'Preview in...' + * Offers a menu of styles and device profiles. */ + +class PreviewActionGroup : public QActionGroup +{ + Q_DISABLE_COPY_MOVE(PreviewActionGroup) + Q_OBJECT +public: + explicit PreviewActionGroup(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + +signals: + void preview(const QString &style, int deviceProfileIndex); + +public slots: + void updateDeviceProfiles(); + +private slots: + void slotTriggered(QAction *); + +private: + QDesignerFormEditorInterface *m_core; +}; +} + +QT_END_NAMESPACE + +#endif // PREVIEWACTIONGROUP_H diff --git a/src/tools/designer/src/components/formeditor/qdesigner_resource.cpp b/src/tools/designer/src/components/formeditor/qdesigner_resource.cpp new file mode 100644 index 00000000000..1dc7aa735a6 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qdesigner_resource.cpp @@ -0,0 +1,2236 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_resource.h" +#include "formwindow.h" +#include "dynamicpropertysheet.h" +#include "qdesigner_tabwidget_p.h" +#include "iconloader_p.h" +#include "qdesigner_toolbox_p.h" +#include "qdesigner_stackedbox_p.h" +#include "qdesigner_toolbar_p.h" +#include "qdesigner_dockwidget_p.h" +#include "qdesigner_menu_p.h" +#include "qdesigner_menubar_p.h" +#include "qdesigner_membersheet_p.h" +#include "qtresourcemodel_p.h" +#include "qmdiarea_container.h" +#include "qwizard_container.h" +#include "layout_propertysheet.h" + +#include +#include +#include +#include +#include + +// shared +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// sdk +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +Q_DECLARE_METATYPE(QWidgetList) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using QFBE = QFormBuilderExtra; + +namespace { + using DomPropertyList = QList; +} + +static constexpr auto currentUiVersion = "4.0"_L1; +static constexpr auto clipboardObjectName = "__qt_fake_top_level"_L1; + +#define OLD_RESOURCE_FORMAT // Support pre 4.4 format. + +namespace qdesigner_internal { + +// -------------------- QDesignerResourceBuilder: A resource builder that works on the property sheet icon types. +class QDesignerResourceBuilder : public QResourceBuilder +{ +public: + QDesignerResourceBuilder(QDesignerFormEditorInterface *core, DesignerPixmapCache *pixmapCache, DesignerIconCache *iconCache); + + void setPixmapCache(DesignerPixmapCache *pixmapCache) { m_pixmapCache = pixmapCache; } + void setIconCache(DesignerIconCache *iconCache) { m_iconCache = iconCache; } + bool isSaveRelative() const { return m_saveRelative; } + void setSaveRelative(bool relative) { m_saveRelative = relative; } + QStringList usedQrcFiles() const { return m_usedQrcFiles.keys(); } +#ifdef OLD_RESOURCE_FORMAT + QStringList loadedQrcFiles() const { return m_loadedQrcFiles.keys(); } // needed only for loading old resource attribute of tag. +#endif + + QVariant loadResource(const QDir &workingDirectory, const DomProperty *icon) const override; + + QVariant toNativeValue(const QVariant &value) const override; + + DomProperty *saveResource(const QDir &workingDirectory, const QVariant &value) const override; + + bool isResourceType(const QVariant &value) const override; +private: + + QDesignerFormEditorInterface *m_core; + DesignerPixmapCache *m_pixmapCache; + DesignerIconCache *m_iconCache; + const QDesignerLanguageExtension *m_lang; + bool m_saveRelative; + mutable QMap m_usedQrcFiles; + mutable QMap m_loadedQrcFiles; +}; + +QDesignerResourceBuilder::QDesignerResourceBuilder(QDesignerFormEditorInterface *core, DesignerPixmapCache *pixmapCache, DesignerIconCache *iconCache) : + m_core(core), + m_pixmapCache(pixmapCache), + m_iconCache(iconCache), + m_lang(qt_extension(core->extensionManager(), core)), + m_saveRelative(true) +{ +} + +static inline void setIconPixmap(QIcon::Mode m, QIcon::State s, const QDir &workingDirectory, + QString path, PropertySheetIconValue &icon, + const QDesignerLanguageExtension *lang = nullptr) +{ + if (lang == nullptr || !lang->isLanguageResource(path)) + path = QFileInfo(workingDirectory, path).absoluteFilePath(); + icon.setPixmap(m, s, PropertySheetPixmapValue(path)); +} + +QVariant QDesignerResourceBuilder::loadResource(const QDir &workingDirectory, const DomProperty *property) const +{ + switch (property->kind()) { + case DomProperty::Pixmap: { + PropertySheetPixmapValue pixmap; + DomResourcePixmap *dp = property->elementPixmap(); + if (!dp->text().isEmpty()) { + if (m_lang != nullptr && m_lang->isLanguageResource(dp->text())) { + pixmap.setPath(dp->text()); + } else { + pixmap.setPath(QFileInfo(workingDirectory, dp->text()).absoluteFilePath()); + } +#ifdef OLD_RESOURCE_FORMAT + if (dp->hasAttributeResource()) + m_loadedQrcFiles.insert(QFileInfo(workingDirectory, dp->attributeResource()).absoluteFilePath(), false); +#endif + } + return QVariant::fromValue(pixmap); + } + + case DomProperty::IconSet: { + PropertySheetIconValue icon; + DomResourceIcon *di = property->elementIconSet(); + const bool hasTheme = di->hasAttributeTheme(); + if (hasTheme) { + const QString &theme = di->attributeTheme(); + const qsizetype themeEnum = theme.startsWith("QIcon::"_L1) + ? QDesignerResourceBuilder::themeIconIndex(theme) : -1; + if (themeEnum != -1) + icon.setThemeEnum(themeEnum); + else + icon.setTheme(theme); + } + if (const int flags = iconStateFlags(di)) { // new, post 4.4 format + if (flags & NormalOff) + setIconPixmap(QIcon::Normal, QIcon::Off, workingDirectory, di->elementNormalOff()->text(), icon, m_lang); + if (flags & NormalOn) + setIconPixmap(QIcon::Normal, QIcon::On, workingDirectory, di->elementNormalOn()->text(), icon, m_lang); + if (flags & DisabledOff) + setIconPixmap(QIcon::Disabled, QIcon::Off, workingDirectory, di->elementDisabledOff()->text(), icon, m_lang); + if (flags & DisabledOn) + setIconPixmap(QIcon::Disabled, QIcon::On, workingDirectory, di->elementDisabledOn()->text(), icon, m_lang); + if (flags & ActiveOff) + setIconPixmap(QIcon::Active, QIcon::Off, workingDirectory, di->elementActiveOff()->text(), icon, m_lang); + if (flags & ActiveOn) + setIconPixmap(QIcon::Active, QIcon::On, workingDirectory, di->elementActiveOn()->text(), icon, m_lang); + if (flags & SelectedOff) + setIconPixmap(QIcon::Selected, QIcon::Off, workingDirectory, di->elementSelectedOff()->text(), icon, m_lang); + if (flags & SelectedOn) + setIconPixmap(QIcon::Selected, QIcon::On, workingDirectory, di->elementSelectedOn()->text(), icon, m_lang); + } else if (!hasTheme) { +#ifdef OLD_RESOURCE_FORMAT + setIconPixmap(QIcon::Normal, QIcon::Off, workingDirectory, di->text(), icon, m_lang); + if (di->hasAttributeResource()) + m_loadedQrcFiles.insert(QFileInfo(workingDirectory, di->attributeResource()).absoluteFilePath(), false); +#endif + } + return QVariant::fromValue(icon); + } + default: + break; + } + return QVariant(); +} + +QVariant QDesignerResourceBuilder::toNativeValue(const QVariant &value) const +{ + if (value.canConvert()) { + if (m_pixmapCache) + return m_pixmapCache->pixmap(qvariant_cast(value)); + } else if (value.canConvert()) { + if (m_iconCache) + return m_iconCache->icon(qvariant_cast(value)); + } + return value; +} + +DomProperty *QDesignerResourceBuilder::saveResource(const QDir &workingDirectory, const QVariant &value) const +{ + DomProperty *p = new DomProperty; + if (value.canConvert()) { + const PropertySheetPixmapValue pix = qvariant_cast(value); + DomResourcePixmap *rp = new DomResourcePixmap; + const QString pixPath = pix.path(); + switch (pix.pixmapSource(m_core)) { + case PropertySheetPixmapValue::LanguageResourcePixmap: + rp->setText(pixPath); + break; + case PropertySheetPixmapValue::ResourcePixmap: { + rp->setText(pixPath); + const QString qrcFile = m_core->resourceModel()->qrcPath(pixPath); + if (!qrcFile.isEmpty()) { + m_usedQrcFiles.insert(qrcFile, false); +#ifdef OLD_RESOURCE_FORMAT // Legacy: Add qrc path + rp->setAttributeResource(workingDirectory.relativeFilePath(qrcFile)); +#endif + } + } + break; + case PropertySheetPixmapValue::FilePixmap: + rp->setText(m_saveRelative ? workingDirectory.relativeFilePath(pixPath) : pixPath); + break; + } + p->setElementPixmap(rp); + return p; + } + if (value.canConvert()) { + const PropertySheetIconValue icon = qvariant_cast(value); + const auto &pixmaps = icon.paths(); + const int themeEnum = icon.themeEnum(); + const QString theme = themeEnum != -1 + ? QDesignerResourceBuilder::fullyQualifiedThemeIconName(themeEnum) : icon.theme(); + if (!pixmaps.isEmpty() || !theme.isEmpty()) { + DomResourceIcon *ri = new DomResourceIcon; + if (!theme.isEmpty()) + ri->setAttributeTheme(theme); + for (auto itPix = pixmaps.cbegin(), end = pixmaps.cend(); itPix != end; ++itPix) { + const QIcon::Mode mode = itPix.key().first; + const QIcon::State state = itPix.key().second; + DomResourcePixmap *rp = new DomResourcePixmap; + const PropertySheetPixmapValue &pix = itPix.value(); + const PropertySheetPixmapValue::PixmapSource ps = pix.pixmapSource(m_core); + const QString pixPath = pix.path(); + rp->setText(ps == PropertySheetPixmapValue::FilePixmap && m_saveRelative ? workingDirectory.relativeFilePath(pixPath) : pixPath); + if (state == QIcon::Off) { + switch (mode) { + case QIcon::Normal: + ri->setElementNormalOff(rp); +#ifdef OLD_RESOURCE_FORMAT // Legacy: Set Normal off as text/path in old format. + ri->setText(rp->text()); +#endif + if (ps == PropertySheetPixmapValue::ResourcePixmap) { + // Be sure that ri->text() file comes from active resourceSet (i.e. make appropriate + // resourceSet active before calling this method). + const QString qrcFile = m_core->resourceModel()->qrcPath(ri->text()); + if (!qrcFile.isEmpty()) { + m_usedQrcFiles.insert(qrcFile, false); +#ifdef OLD_RESOURCE_FORMAT // Legacy: Set Normal off as text/path in old format. + ri->setAttributeResource(workingDirectory.relativeFilePath(qrcFile)); +#endif + } + } + break; + case QIcon::Disabled: ri->setElementDisabledOff(rp); break; + case QIcon::Active: ri->setElementActiveOff(rp); break; + case QIcon::Selected: ri->setElementSelectedOff(rp); break; + } + } else { + switch (mode) { + case QIcon::Normal: ri->setElementNormalOn(rp); break; + case QIcon::Disabled: ri->setElementDisabledOn(rp); break; + case QIcon::Active: ri->setElementActiveOn(rp); break; + case QIcon::Selected: ri->setElementSelectedOn(rp); break; + } + } + } + p->setElementIconSet(ri); + return p; + } + } + delete p; + return nullptr; +} + +bool QDesignerResourceBuilder::isResourceType(const QVariant &value) const +{ + return value.canConvert() + || value.canConvert(); +} +// ------------------------- QDesignerTextBuilder + +template // for DomString, potentially DomStringList +inline void translationParametersToDom(const PropertySheetTranslatableData &data, DomElement *e) +{ + const QString propertyComment = data.disambiguation(); + if (!propertyComment.isEmpty()) + e->setAttributeComment(propertyComment); + const QString propertyExtracomment = data.comment(); + if (!propertyExtracomment.isEmpty()) + e->setAttributeExtraComment(propertyExtracomment); + const QString &id = data.id(); + if (!id.isEmpty()) + e->setAttributeId(id); + if (!data.translatable()) + e->setAttributeNotr(u"true"_s); +} + +template // for DomString, potentially DomStringList +inline void translationParametersFromDom(const DomElement *e, PropertySheetTranslatableData *data) +{ + if (e->hasAttributeComment()) + data->setDisambiguation(e->attributeComment()); + if (e->hasAttributeExtraComment()) + data->setComment(e->attributeExtraComment()); + if (e->hasAttributeId()) + data->setId(e->attributeId()); + if (e->hasAttributeNotr()) { + const QString notr = e->attributeNotr(); + const bool translatable = !(notr == "true"_L1 || notr == "yes"_L1); + data->setTranslatable(translatable); + } +} + +class QDesignerTextBuilder : public QTextBuilder +{ +public: + QDesignerTextBuilder() = default; + + QVariant loadText(const DomProperty *icon) const override; + + QVariant toNativeValue(const QVariant &value) const override; + + DomProperty *saveText(const QVariant &value) const override; +}; + +QVariant QDesignerTextBuilder::loadText(const DomProperty *text) const +{ + if (const DomString *domString = text->elementString()) { + PropertySheetStringValue stringValue(domString->text()); + translationParametersFromDom(domString, &stringValue); + return QVariant::fromValue(stringValue); + } + return QVariant(QString()); +} + +QVariant QDesignerTextBuilder::toNativeValue(const QVariant &value) const +{ + if (value.canConvert()) + return QVariant::fromValue(qvariant_cast(value).value()); + return value; +} + +static inline DomProperty *stringToDomProperty(const QString &value) +{ + DomString *domString = new DomString(); + domString->setText(value); + DomProperty *property = new DomProperty(); + property->setElementString(domString); + return property; +} + +static inline DomProperty *stringToDomProperty(const QString &value, + const PropertySheetTranslatableData &translatableData) +{ + DomString *domString = new DomString(); + domString->setText(value); + translationParametersToDom(translatableData, domString); + DomProperty *property = new DomProperty(); + property->setElementString(domString); + return property; +} + +DomProperty *QDesignerTextBuilder::saveText(const QVariant &value) const +{ + if (value.canConvert()) { + const PropertySheetStringValue str = qvariant_cast(value); + return stringToDomProperty(str.value(), str); + } + if (value.canConvert()) + return stringToDomProperty(value.toString()); + return nullptr; +} + +QDesignerResource::QDesignerResource(FormWindow *formWindow) : + QEditorFormBuilder(formWindow->core()), + m_formWindow(formWindow), + m_copyWidget(false), + m_selected(nullptr), + m_resourceBuilder(new QDesignerResourceBuilder(m_formWindow->core(), m_formWindow->pixmapCache(), m_formWindow->iconCache())) +{ + // Check language unless extension present (Jambi) + QDesignerFormEditorInterface *core = m_formWindow->core(); + if (const QDesignerLanguageExtension *le = qt_extension(core->extensionManager(), core)) + d->m_language = le->name(); + + setWorkingDirectory(formWindow->absoluteDir()); + setResourceBuilder(m_resourceBuilder); + setTextBuilder(new QDesignerTextBuilder()); + + // ### generalise + const QString designerWidget = u"QDesignerWidget"_s; + const QString layoutWidget = u"QLayoutWidget"_s; + const QString widget = u"QWidget"_s; + m_internal_to_qt.insert(layoutWidget, widget); + m_internal_to_qt.insert(designerWidget, widget); + m_internal_to_qt.insert(u"QDesignerDialog"_s, u"QDialog"_s); + m_internal_to_qt.insert(u"QDesignerMenuBar"_s, u"QMenuBar"_s); + m_internal_to_qt.insert(u"QDesignerMenu"_s, u"QMenu"_s); + m_internal_to_qt.insert(u"QDesignerDockWidget"_s, u"QDockWidget"_s); + + // invert + for (auto it = m_internal_to_qt.cbegin(), cend = m_internal_to_qt.cend(); it != cend; ++it ) { + if (it.value() != designerWidget && it.value() != layoutWidget) + m_qt_to_internal.insert(it.value(), it.key()); + + } +} + +QDesignerResource::~QDesignerResource() = default; + +DomUI *QDesignerResource::readUi(QIODevice *dev) +{ + return d->readUi(dev); +} + +static inline QString messageBoxTitle() +{ + return QApplication::translate("Designer", "Qt Widgets Designer"); +} + +void QDesignerResource::save(QIODevice *dev, QWidget *widget) +{ + QAbstractFormBuilder::save(dev, widget); +} + +void QDesignerResource::saveDom(DomUI *ui, QWidget *widget) +{ + QAbstractFormBuilder::saveDom(ui, widget); + + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), widget); + Q_ASSERT(sheet != nullptr); + + const QVariant classVar = sheet->property(sheet->indexOf(u"objectName"_s)); + QString classStr; + if (classVar.canConvert()) + classStr = classVar.toString(); + else + classStr = qvariant_cast(classVar).value(); + ui->setElementClass(classStr); + + for (int index = 0; index < m_formWindow->toolCount(); ++index) { + QDesignerFormWindowToolInterface *tool = m_formWindow->tool(index); + Q_ASSERT(tool != nullptr); + tool->saveToDom(ui, widget); + } + + const QString author = m_formWindow->author(); + if (!author.isEmpty()) { + ui->setElementAuthor(author); + } + + const QString comment = m_formWindow->comment(); + if (!comment.isEmpty()) { + ui->setElementComment(comment); + } + + const QString exportMacro = m_formWindow->exportMacro(); + if (!exportMacro.isEmpty()) { + ui->setElementExportMacro(exportMacro); + } + + if (m_formWindow->useIdBasedTranslations()) + ui->setAttributeIdbasedtr(true); + if (!m_formWindow->connectSlotsByName()) // Don't write out if true (default) + ui->setAttributeConnectslotsbyname(false); + + const QVariantMap designerFormData = m_formWindow->formData(); + if (!designerFormData.isEmpty()) { + DomPropertyList domPropertyList; + for (auto it = designerFormData.cbegin(), cend = designerFormData.cend(); it != cend; ++it) { + if (DomProperty *prop = variantToDomProperty(this, widget->metaObject(), it.key(), it.value())) + domPropertyList += prop; + } + if (!domPropertyList.isEmpty()) { + DomDesignerData* domDesignerFormData = new DomDesignerData; + domDesignerFormData->setElementProperty(domPropertyList); + ui->setElementDesignerdata(domDesignerFormData); + } + } + + if (!m_formWindow->includeHints().isEmpty()) { + const QString local = u"local"_s; + const QString global = u"global"_s; + QList ui_includes; + const QStringList &includeHints = m_formWindow->includeHints(); + ui_includes.reserve(includeHints.size()); + for (QString includeHint : includeHints) { + if (includeHint.isEmpty()) + continue; + DomInclude *incl = new DomInclude; + const QString location = includeHint.at(0) == u'<' ? global : local; + includeHint.remove(u'"'); + includeHint.remove(u'<'); + includeHint.remove(u'>'); + incl->setAttributeLocation(location); + incl->setText(includeHint); + ui_includes.append(incl); + } + + DomIncludes *includes = new DomIncludes; + includes->setElementInclude(ui_includes); + ui->setElementIncludes(includes); + } + + int defaultMargin = INT_MIN, defaultSpacing = INT_MIN; + m_formWindow->layoutDefault(&defaultMargin, &defaultSpacing); + + if (defaultMargin != INT_MIN || defaultSpacing != INT_MIN) { + DomLayoutDefault *def = new DomLayoutDefault; + if (defaultMargin != INT_MIN) + def->setAttributeMargin(defaultMargin); + if (defaultSpacing != INT_MIN) + def->setAttributeSpacing(defaultSpacing); + ui->setElementLayoutDefault(def); + } + + QString marginFunction, spacingFunction; + m_formWindow->layoutFunction(&marginFunction, &spacingFunction); + if (!marginFunction.isEmpty() || !spacingFunction.isEmpty()) { + DomLayoutFunction *def = new DomLayoutFunction; + + if (!marginFunction.isEmpty()) + def->setAttributeMargin(marginFunction); + if (!spacingFunction.isEmpty()) + def->setAttributeSpacing(spacingFunction); + ui->setElementLayoutFunction(def); + } + + QString pixFunction = m_formWindow->pixmapFunction(); + if (!pixFunction.isEmpty()) { + ui->setElementPixmapFunction(pixFunction); + } + + if (QDesignerExtraInfoExtension *extra = qt_extension(core()->extensionManager(), core())) + extra->saveUiExtraInfo(ui); + + if (MetaDataBase *metaDataBase = qobject_cast(core()->metaDataBase())) { + const MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(m_formWindow->mainContainer()); + const QStringList fakeSlots = item->fakeSlots(); + const QStringList fakeSignals =item->fakeSignals(); + if (!fakeSlots.isEmpty() || !fakeSignals.isEmpty()) { + DomSlots *domSlots = new DomSlots(); + domSlots->setElementSlot(fakeSlots); + domSlots->setElementSignal(fakeSignals); + ui->setElementSlots(domSlots); + } + } +} + +QWidget *QDesignerResource::load(QIODevice *dev, QWidget *parentWidget) +{ + QScopedPointer ui(readUi(dev)); + return ui.isNull() ? nullptr : loadUi(ui.data(), parentWidget); +} + +QWidget *QDesignerResource::loadUi(DomUI *ui, QWidget *parentWidget) +{ + QWidget *widget = create(ui, parentWidget); + // Store the class name as 'reset' value for the main container's object name. + if (widget) + widget->setProperty("_q_classname", widget->objectName()); + else if (d->m_errorString.isEmpty()) + d->m_errorString = QFormBuilderExtra::msgInvalidUiFile(); + return widget; +} + +bool QDesignerResource::saveRelative() const +{ + return m_resourceBuilder->isSaveRelative(); +} + +void QDesignerResource::setSaveRelative(bool relative) +{ + m_resourceBuilder->setSaveRelative(relative); +} + +QWidget *QDesignerResource::create(DomUI *ui, QWidget *parentWidget) +{ + // Load extra info extension. This is used by Jambi for preventing + // C++ UI files from being loaded + if (QDesignerExtraInfoExtension *extra = qt_extension(core()->extensionManager(), core())) { + if (!extra->loadUiExtraInfo(ui)) { + const QString errorMessage = QApplication::translate("Designer", "This file cannot be read because the extra info extension failed to load."); + core()->dialogGui()->message(parentWidget->window(), QDesignerDialogGuiInterface::FormLoadFailureMessage, + QMessageBox::Warning, messageBoxTitle(), errorMessage, QMessageBox::Ok); + return nullptr; + } + } + + qdesigner_internal::WidgetFactory *factory = qobject_cast(core()->widgetFactory()); + Q_ASSERT(factory != nullptr); + + QDesignerFormWindowInterface *previousFormWindow = factory->currentFormWindow(m_formWindow); + + m_isMainWidget = true; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + QWidget *mainWidget = QAbstractFormBuilder::create(ui, parentWidget); + + if (m_formWindow) { + m_formWindow->setUseIdBasedTranslations(ui->attributeIdbasedtr()); + // Default to true unless set. + const bool connectSlotsByName = !ui->hasAttributeConnectslotsbyname() || ui->attributeConnectslotsbyname(); + m_formWindow->setConnectSlotsByName(connectSlotsByName); + } + + if (mainWidget && m_formWindow) { + m_formWindow->setAuthor(ui->elementAuthor()); + m_formWindow->setComment(ui->elementComment()); + m_formWindow->setExportMacro(ui->elementExportMacro()); + + // Designer data + QVariantMap designerFormData; + if (ui->hasElementDesignerdata()) { + const DomPropertyList domPropertyList = ui->elementDesignerdata()->elementProperty(); + for (auto *prop : domPropertyList) { + const QVariant vprop = domPropertyToVariant(this, mainWidget->metaObject(), prop); + if (vprop.metaType().id() != QMetaType::UnknownType) + designerFormData.insert(prop->attributeName(), vprop); + } + } + m_formWindow->setFormData(designerFormData); + + m_formWindow->setPixmapFunction(ui->elementPixmapFunction()); + + if (DomLayoutDefault *def = ui->elementLayoutDefault()) { + m_formWindow->setLayoutDefault(def->attributeMargin(), def->attributeSpacing()); + } + + if (DomLayoutFunction *fun = ui->elementLayoutFunction()) { + m_formWindow->setLayoutFunction(fun->attributeMargin(), fun->attributeSpacing()); + } + + if (DomIncludes *includes = ui->elementIncludes()) { + const auto global = "global"_L1; + QStringList includeHints; + const auto &elementInclude = includes->elementInclude(); + for (DomInclude *incl : elementInclude) { + QString text = incl->text(); + + if (text.isEmpty()) + continue; + + if (incl->hasAttributeLocation() && incl->attributeLocation() == global ) { + text.prepend(u'<'); + text.append(u'>'); + } else { + text.prepend(u'"'); + text.append(u'"'); + } + + includeHints.append(text); + } + + m_formWindow->setIncludeHints(includeHints); + } + + // Register all button groups the form builder adds as children of the main container for them to be found + // in the signal slot editor + auto *mdb = core()->metaDataBase(); + for (auto *child : mainWidget->children()) { + if (QButtonGroup *bg = qobject_cast(child)) + mdb->add(bg); + } + // Load tools + for (int index = 0; index < m_formWindow->toolCount(); ++index) { + QDesignerFormWindowToolInterface *tool = m_formWindow->tool(index); + Q_ASSERT(tool != nullptr); + tool->loadFromDom(ui, mainWidget); + } + } + + factory->currentFormWindow(previousFormWindow); + + if (const DomSlots *domSlots = ui->elementSlots()) { + if (MetaDataBase *metaDataBase = qobject_cast(core()->metaDataBase())) { + QStringList fakeSlots; + QStringList fakeSignals; + if (addFakeMethods(domSlots, fakeSlots, fakeSignals)) { + MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(mainWidget); + item->setFakeSlots(fakeSlots); + item->setFakeSignals(fakeSignals); + } + } + } + if (mainWidget) { + // Initialize the mainwindow geometry. Has it been explicitly specified? + bool hasExplicitGeometry = false; + const auto &properties = ui->elementWidget()->elementProperty(); + if (!properties.isEmpty()) { + for (const DomProperty *p : properties) { + if (p->attributeName() == "geometry"_L1) { + hasExplicitGeometry = true; + break; + } + } + } + if (hasExplicitGeometry) { + // Geometry was specified explicitly: Verify that smartMinSize is respected + // (changed fonts, label wrapping policies, etc). This does not happen automatically in docked mode. + const QSize size = mainWidget->size(); + const QSize minSize = size.expandedTo(qSmartMinSize(mainWidget)); + if (minSize != size) + mainWidget->resize(minSize); + } else { + // No explicit Geometry: perform an adjustSize() to resize the form correctly before embedding it into a container + // (which might otherwise squeeze the form) + mainWidget->adjustSize(); + } + // Some integration wizards create forms with main containers + // based on derived classes of QWidget and load them into Designer + // without the plugin existing. This will trigger the auto-promotion + // mechanism of Designer, which will set container=false for + // QWidgets. For the main container, force container=true and warn. + const QDesignerWidgetDataBaseInterface *wdb = core()->widgetDataBase(); + const int wdbIndex = wdb->indexOfObject(mainWidget); + if (wdbIndex != -1) { + QDesignerWidgetDataBaseItemInterface *item = wdb->item(wdbIndex); + // Promoted main container that is not of container type + if (item->isPromoted() && !item->isContainer()) { + item->setContainer(true); + qWarning("** WARNING The form's main container is an unknown custom widget '%s'." + " Defaulting to a promoted instance of '%s', assuming container.", + item->name().toUtf8().constData(), item->extends().toUtf8().constData()); + } + } + } + return mainWidget; +} + +QWidget *QDesignerResource::create(DomWidget *ui_widget, QWidget *parentWidget) +{ + const QString className = ui_widget->attributeClass(); + if (!m_isMainWidget && className == "QWidget"_L1 + && !ui_widget->elementLayout().isEmpty() + && !ui_widget->hasAttributeNative()) { + // ### check if elementLayout.size() == 1 + + QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), parentWidget); + + if (container == nullptr) { + // generate a QLayoutWidget iff the parent is not an QDesignerContainerExtension. + ui_widget->setAttributeClass(u"QLayoutWidget"_s); + } + } + + // save the actions + const auto &actionRefs = ui_widget->elementAddAction(); + ui_widget->setElementAddAction(QList()); + + QWidget *w = QAbstractFormBuilder::create(ui_widget, parentWidget); + + // restore the actions + ui_widget->setElementAddAction(actionRefs); + + if (w == nullptr) + return nullptr; + + // ### generalize using the extension manager + QDesignerMenu *menu = qobject_cast(w); + QDesignerMenuBar *menuBar = qobject_cast(w); + + if (menu) + menu->hide(); + + for (DomActionRef *ui_action_ref : actionRefs) { + const QString name = ui_action_ref->attributeName(); + if (name == "separator"_L1) { + QAction *sep = new QAction(w); + sep->setSeparator(true); + w->addAction(sep); + addMenuAction(sep); + } else if (QAction *a = d->m_actions.value(name)) { + w->addAction(a); + } else if (QActionGroup *g = d->m_actionGroups.value(name)) { + w->addActions(g->actions()); + } else if (QMenu *menu = w->findChild(name)) { + w->addAction(menu->menuAction()); + addMenuAction(menu->menuAction()); + } + } + + if (menu) + menu->adjustSpecialActions(); + else if (menuBar) + menuBar->adjustSpecialActions(); + + ui_widget->setAttributeClass(className); // fix the class name + applyExtensionDataFromDOM(this, core(), ui_widget, w); + + return w; +} + +QLayout *QDesignerResource::create(DomLayout *ui_layout, QLayout *layout, QWidget *parentWidget) +{ + QLayout *l = QAbstractFormBuilder::create(ui_layout, layout, parentWidget); + + if (QGridLayout *gridLayout = qobject_cast(l)) { + QLayoutSupport::createEmptyCells(gridLayout); + } else { + if (QFormLayout *formLayout = qobject_cast(l)) + QLayoutSupport::createEmptyCells(formLayout); + } + // While the actual values are applied by the form builder, we still need + // to mark them as 'changed'. + LayoutPropertySheet::markChangedStretchProperties(core(), l, ui_layout); + return l; +} + +QLayoutItem *QDesignerResource::create(DomLayoutItem *ui_layoutItem, QLayout *layout, QWidget *parentWidget) +{ + if (ui_layoutItem->kind() == DomLayoutItem::Spacer) { + const DomSpacer *domSpacer = ui_layoutItem->elementSpacer(); + Spacer *spacer = static_cast(core()->widgetFactory()->createWidget(u"Spacer"_s, parentWidget)); + if (domSpacer->hasAttributeName()) + changeObjectName(spacer, domSpacer->attributeName()); + core()->metaDataBase()->add(spacer); + + spacer->setInteractiveMode(false); + applyProperties(spacer, ui_layoutItem->elementSpacer()->elementProperty()); + spacer->setInteractiveMode(true); + + if (m_formWindow) { + m_formWindow->manageWidget(spacer); + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), spacer)) + sheet->setChanged(sheet->indexOf(u"orientation"_s), true); + } + + return new QWidgetItem(spacer); + } + if (ui_layoutItem->kind() == DomLayoutItem::Layout && parentWidget) { + DomLayout *ui_layout = ui_layoutItem->elementLayout(); + QLayoutWidget *layoutWidget = new QLayoutWidget(m_formWindow, parentWidget); + core()->metaDataBase()->add(layoutWidget); + if (m_formWindow) + m_formWindow->manageWidget(layoutWidget); + (void) create(ui_layout, nullptr, layoutWidget); + return new QWidgetItem(layoutWidget); + } + return QAbstractFormBuilder::create(ui_layoutItem, layout, parentWidget); +} + +void QDesignerResource::changeObjectName(QObject *o, QString objName) +{ + m_formWindow->unify(o, objName, true); + o->setObjectName(objName); + +} + +/* If the property is a enum or flag value, retrieve + * the existing enum/flag via property sheet and use it to convert */ + +static bool readDomEnumerationValue(const DomProperty *p, + const QDesignerPropertySheetExtension* sheet, int index, + QVariant &v) +{ + switch (p->kind()) { + case DomProperty::Set: { + const QVariant sheetValue = sheet->property(index); + if (sheetValue.canConvert()) { + const PropertySheetFlagValue f = qvariant_cast(sheetValue); + bool ok = false; + v = f.metaFlags.parseFlags(p->elementSet(), &ok); + if (!ok) + designerWarning(f.metaFlags.messageParseFailed(p->elementSet())); + return true; + } + } + break; + case DomProperty::Enum: { + const QVariant sheetValue = sheet->property(index); + if (sheetValue.canConvert()) { + const PropertySheetEnumValue e = qvariant_cast(sheetValue); + bool ok = false; + v = e.metaEnum.parseEnum(p->elementEnum(), &ok); + if (!ok) + designerWarning(e.metaEnum.messageParseFailed(p->elementEnum())); + return true; + } + } + break; + default: + break; + } + return false; +} + +// ### fixme Qt 7 remove this: Exclude deprecated properties of Qt 5. +static bool isDeprecatedQt5Property(const QObject *o, const DomProperty *p) +{ + const QString &propertyName = p->attributeName(); + switch (p->kind()) { + case DomProperty::Set: + if (propertyName == u"features" && o->inherits("QDockWidget") + && p->elementSet() == u"QDockWidget::AllDockWidgetFeatures") { + return true; + } + break; + case DomProperty::Enum: + if (propertyName == u"sizeAdjustPolicy" && o->inherits("QComboBox") + && p->elementEnum() == u"QComboBox::AdjustToMinimumContentsLength") { + return true; + } + break; + default: + break; + } + return false; +} + +void QDesignerResource::applyProperties(QObject *o, const QList &properties) +{ + if (properties.isEmpty()) + return; + + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), o); + if (!sheet) + return; + + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core()->extensionManager(), o); + const bool dynamicPropertiesAllowed = dynamicSheet && dynamicSheet->dynamicPropertiesAllowed(); + + for (DomProperty *p : properties) { + if (isDeprecatedQt5Property(o, p)) // ### fixme Qt 7 remove this + continue; // ### fixme Qt 7 remove this: Exclude deprecated value of Qt 5. + QString propertyName = p->attributeName(); + if (propertyName == "numDigits"_L1 && o->inherits("QLCDNumber")) // Deprecated in Qt 4, removed in Qt 5. + propertyName = u"digitCount"_s; + const int index = sheet->indexOf(propertyName); + QVariant v; + if (!readDomEnumerationValue(p, sheet, index, v)) + v = toVariant(o->metaObject(), p); + + switch (p->kind()) { + case DomProperty::String: + if (index != -1 && sheet->property(index).userType() == qMetaTypeId()) { + const DomString *key = p->elementString(); + PropertySheetKeySequenceValue keyVal(QKeySequence(key->text())); + translationParametersFromDom(key, &keyVal); + v = QVariant::fromValue(keyVal); + } else { + const DomString *str = p->elementString(); + PropertySheetStringValue strVal(v.toString()); + translationParametersFromDom(str, &strVal); + v = QVariant::fromValue(strVal); + } + break; + case DomProperty::StringList: { + const DomStringList *list = p->elementStringList(); + PropertySheetStringListValue listValue(list->elementString()); + translationParametersFromDom(list, &listValue); + v = QVariant::fromValue(listValue); + } + break; + default: + break; + } + + d->applyPropertyInternally(o, propertyName, v); + if (index != -1) { + sheet->setProperty(index, v); + sheet->setChanged(index, true); + } else if (dynamicPropertiesAllowed) { + QVariant defaultValue = QVariant(v.metaType()); + bool isDefault = (v == defaultValue); + if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QIcon)); + isDefault = (qvariant_cast(v) == PropertySheetIconValue()); + } else if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QPixmap)); + isDefault = (qvariant_cast(v) == PropertySheetPixmapValue()); + } else if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QString)); + isDefault = (qvariant_cast(v) == PropertySheetStringValue()); + } else if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QStringList)); + isDefault = (qvariant_cast(v) == PropertySheetStringListValue()); + } else if (v.canConvert()) { + defaultValue = QVariant(QMetaType(QMetaType::QKeySequence)); + isDefault = (qvariant_cast(v) == PropertySheetKeySequenceValue()); + } + if (defaultValue.metaType().id() != QMetaType::User) { + const int idx = dynamicSheet->addDynamicProperty(p->attributeName(), defaultValue); + if (idx != -1) { + sheet->setProperty(idx, v); + sheet->setChanged(idx, !isDefault); + } + } + } + + if (propertyName == "objectName"_L1) + changeObjectName(o, o->objectName()); + } +} + +QWidget *QDesignerResource::createWidget(const QString &widgetName, QWidget *parentWidget, const QString &_name) +{ + QString name = _name; + if (m_isMainWidget) + m_isMainWidget = false; + + QWidget *w = core()->widgetFactory()->createWidget(widgetName, parentWidget); + if (!w) + return nullptr; + + if (name.isEmpty()) { + QDesignerWidgetDataBaseInterface *db = core()->widgetDataBase(); + if (QDesignerWidgetDataBaseItemInterface *item = db->item(db->indexOfObject(w))) + name = qtify(item->name()); + } + + changeObjectName(w, name); + + QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), parentWidget); + if (!qobject_cast(w) && (!parentWidget || !container)) { + m_formWindow->manageWidget(w); + if (parentWidget) { + QWidgetList list = qvariant_cast(parentWidget->property("_q_widgetOrder")); + list.append(w); + parentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(list)); + QWidgetList zOrder = qvariant_cast(parentWidget->property("_q_zOrder")); + zOrder.append(w); + parentWidget->setProperty("_q_zOrder", QVariant::fromValue(zOrder)); + } + } else { + core()->metaDataBase()->add(w); + } + + w->setWindowFlags(w->windowFlags() & ~Qt::Window); + // Make sure it is non-modal (for example, KDialog calls setModal(true) in the constructor). + w->setWindowModality(Qt::NonModal); + + return w; +} + +QLayout *QDesignerResource::createLayout(const QString &layoutName, QObject *parent, const QString &name) +{ + QWidget *layoutBase = nullptr; + QLayout *layout = qobject_cast(parent); + + if (parent->isWidgetType()) + layoutBase = static_cast(parent); + else { + Q_ASSERT( layout != nullptr ); + layoutBase = layout->parentWidget(); + } + + LayoutInfo::Type layoutType = LayoutInfo::layoutType(layoutName); + if (layoutType == LayoutInfo::NoLayout) { + designerWarning(QCoreApplication::translate("QDesignerResource", "The layout type '%1' is not supported, defaulting to grid.").arg(layoutName)); + layoutType = LayoutInfo::Grid; + } + QLayout *lay = core()->widgetFactory()->createLayout(layoutBase, layout, layoutType); + if (lay != nullptr) + changeObjectName(lay, name); + + return lay; +} + +// save +DomWidget *QDesignerResource::createDom(QWidget *widget, DomWidget *ui_parentWidget, bool recursive) +{ + QDesignerMetaDataBaseItemInterface *item = core()->metaDataBase()->item(widget); + if (!item) + return nullptr; + + if (qobject_cast(widget) && !m_copyWidget) + return nullptr; + + const QDesignerWidgetDataBaseInterface *wdb = core()->widgetDataBase(); + QDesignerWidgetDataBaseItemInterface *widgetInfo = nullptr; + const int widgetInfoIndex = wdb->indexOfObject(widget, false); + if (widgetInfoIndex != -1) { + widgetInfo = wdb->item(widgetInfoIndex); + // Recursively add all dependent custom widgets + QDesignerWidgetDataBaseItemInterface *customInfo = widgetInfo; + while (customInfo && customInfo->isCustom()) { + m_usedCustomWidgets.insert(customInfo, true); + const QString extends = customInfo->extends(); + if (extends == customInfo->name()) + break; // There are faulty files around that have name==extends + const int extendsIndex = wdb->indexOfClassName(customInfo->extends()); + customInfo = extendsIndex != -1 ? wdb->item(extendsIndex) : nullptr; + } + } + + DomWidget *w = nullptr; + + if (QTabWidget *tabWidget = qobject_cast(widget)) + w = saveWidget(tabWidget, ui_parentWidget); + else if (QStackedWidget *stackedWidget = qobject_cast(widget)) + w = saveWidget(stackedWidget, ui_parentWidget); + else if (QToolBox *toolBox = qobject_cast(widget)) + w = saveWidget(toolBox, ui_parentWidget); + else if (QToolBar *toolBar = qobject_cast(widget)) + w = saveWidget(toolBar, ui_parentWidget); + else if (QDesignerDockWidget *dockWidget = qobject_cast(widget)) + w = saveWidget(dockWidget, ui_parentWidget); + else if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) + w = saveWidget(widget, container, ui_parentWidget); + else if (QWizardPage *wizardPage = qobject_cast(widget)) + w = saveWidget(wizardPage, ui_parentWidget); + else + w = QAbstractFormBuilder::createDom(widget, ui_parentWidget, recursive); + + Q_ASSERT( w != nullptr ); + + if (!qobject_cast(widget) && w->attributeClass() == "QWidget"_L1) + w->setAttributeNative(true); + + const QString className = w->attributeClass(); + if (m_internal_to_qt.contains(className)) + w->setAttributeClass(m_internal_to_qt.value(className)); + + if (isPromoted( core(), widget)) { // is promoted? + Q_ASSERT(widgetInfo != nullptr); + + w->setAttributeClass(widgetInfo->name()); + + const auto &prop_list = w->elementProperty(); + for (DomProperty *prop : prop_list) { + if (prop->attributeName() == "geometry"_L1) { + if (DomRect *rect = prop->elementRect()) { + rect->setElementX(widget->x()); + rect->setElementY(widget->y()); + } + break; + } + } + } else if (widgetInfo != nullptr && m_usedCustomWidgets.contains(widgetInfo)) { + if (widgetInfo->name() != w->attributeClass()) + w->setAttributeClass(widgetInfo->name()); + } + addExtensionDataToDOM(this, core(), w, widget); + return w; +} + +DomLayout *QDesignerResource::createDom(QLayout *layout, DomLayout *ui_parentLayout, DomWidget *ui_parentWidget) +{ + QDesignerMetaDataBaseItemInterface *item = core()->metaDataBase()->item(layout); + + if (item == nullptr) { + layout = layout->findChild(); + // refresh the meta database item + item = core()->metaDataBase()->item(layout); + } + + if (item == nullptr) { + // nothing to do. + return nullptr; + } + + if (qobject_cast(layout->parentWidget()) != 0) { + // nothing to do. + return nullptr; + } + + m_chain.push(layout); + + DomLayout *l = QAbstractFormBuilder::createDom(layout, ui_parentLayout, ui_parentWidget); + Q_ASSERT(l != nullptr); + LayoutPropertySheet::stretchAttributesToDom(core(), layout, l); + + m_chain.pop(); + + return l; +} + +DomLayoutItem *QDesignerResource::createDom(QLayoutItem *item, DomLayout *ui_layout, DomWidget *ui_parentWidget) +{ + DomLayoutItem *ui_item = nullptr; + + if (Spacer *s = qobject_cast(item->widget())) { + if (!core()->metaDataBase()->item(s)) + return nullptr; + + DomSpacer *spacer = new DomSpacer(); + const QString objectName = s->objectName(); + if (!objectName.isEmpty()) + spacer->setAttributeName(objectName); + // ### filter the properties + spacer->setElementProperty(computeProperties(item->widget())); + + ui_item = new DomLayoutItem(); + ui_item->setElementSpacer(spacer); + d->m_laidout.insert(item->widget(), true); + } else if (QLayoutWidget *layoutWidget = qobject_cast(item->widget())) { + // Do not save a QLayoutWidget if it is within a layout (else it is saved as "QWidget" + Q_ASSERT(layoutWidget->layout()); + DomLayout *l = createDom(layoutWidget->layout(), ui_layout, ui_parentWidget); + ui_item = new DomLayoutItem(); + ui_item->setElementLayout(l); + d->m_laidout.insert(item->widget(), true); + } else if (!item->spacerItem()) { // we use spacer as fake item in the Designer + ui_item = QAbstractFormBuilder::createDom(item, ui_layout, ui_parentWidget); + } else { + return nullptr; + } + return ui_item; +} + +void QDesignerResource::createCustomWidgets(DomCustomWidgets *dom_custom_widgets) +{ + QSimpleResource::handleDomCustomWidgets(core(), dom_custom_widgets); +} + +DomTabStops *QDesignerResource::saveTabStops() +{ + QDesignerMetaDataBaseItemInterface *item = core()->metaDataBase()->item(m_formWindow); + Q_ASSERT(item); + + QStringList tabStops; + const QWidgetList &tabOrder = item->tabOrder(); + for (QWidget *widget : tabOrder) { + if (m_formWindow->mainContainer()->isAncestorOf(widget)) + tabStops.append(widget->objectName()); + } + + if (!tabStops.isEmpty()) { + DomTabStops *dom = new DomTabStops; + dom->setElementTabStop(tabStops); + return dom; + } + + return nullptr; +} + +void QDesignerResource::applyTabStops(QWidget *widget, DomTabStops *tabStops) +{ + if (tabStops == nullptr || widget == nullptr) + return; + + QWidgetList tabOrder; + const QStringList &elementTabStop = tabStops->elementTabStop(); + for (const QString &widgetName : elementTabStop) { + if (QWidget *w = widget->findChild(widgetName)) { + tabOrder.append(w); + } + } + + QDesignerMetaDataBaseItemInterface *item = core()->metaDataBase()->item(m_formWindow); + Q_ASSERT(item); + item->setTabOrder(tabOrder); +} + +/* Unmanaged container pages occur when someone adds a page in a custom widget + * constructor. They don't have a meta DB entry which causes createDom + * to return 0. */ +inline QString msgUnmanagedPage(QDesignerFormEditorInterface *core, + QWidget *container, int index, QWidget *page) +{ + return QCoreApplication::translate("QDesignerResource", +"The container extension of the widget '%1' (%2) returned a widget not managed by Designer '%3' (%4) when queried for page #%5.\n" +"Container pages should only be added by specifying them in XML returned by the domXml() method of the custom widget."). + arg(container->objectName(), WidgetFactory::classNameOf(core, container), + page->objectName(), WidgetFactory::classNameOf(core, page)). + arg(index); +} + +DomWidget *QDesignerResource::saveWidget(QWidget *widget, QDesignerContainerExtension *container, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(widget, ui_parentWidget, false); + QList ui_widget_list; + + for (int i=0; icount(); ++i) { + QWidget *page = container->widget(i); + Q_ASSERT(page); + + if (DomWidget *ui_page = createDom(page, ui_widget)) { + ui_widget_list.append(ui_page); + } else { + designerWarning(msgUnmanagedPage(core(), widget, i, page)); + } + } + + ui_widget->setElementWidget(ui_widget_list); + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QStackedWidget *widget, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(widget, ui_parentWidget, false); + QList ui_widget_list; + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) { + for (int i=0; icount(); ++i) { + QWidget *page = container->widget(i); + Q_ASSERT(page); + if (DomWidget *ui_page = createDom(page, ui_widget)) { + ui_widget_list.append(ui_page); + } else { + designerWarning(msgUnmanagedPage(core(), widget, i, page)); + } + } + } + + ui_widget->setElementWidget(ui_widget_list); + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QToolBar *toolBar, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(toolBar, ui_parentWidget, false); + if (const QMainWindow *mainWindow = qobject_cast(toolBar->parentWidget())) { + const bool toolBarBreak = mainWindow->toolBarBreak(toolBar); + const Qt::ToolBarArea area = mainWindow->toolBarArea(toolBar); + + auto attributes = ui_widget->elementAttribute(); + + DomProperty *attr = new DomProperty(); + attr->setAttributeName(u"toolBarArea"_s); + attr->setElementEnum(QLatin1StringView(toolBarAreaMetaEnum().valueToKey(area))); + attributes << attr; + + attr = new DomProperty(); + attr->setAttributeName(u"toolBarBreak"_s); + attr->setElementBool(toolBarBreak ? u"true"_s : u"false"_s); + attributes << attr; + ui_widget->setElementAttribute(attributes); + } + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QDesignerDockWidget *dockWidget, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(dockWidget, ui_parentWidget, true); + if (QMainWindow *mainWindow = qobject_cast(dockWidget->parentWidget())) { + const Qt::DockWidgetArea area = mainWindow->dockWidgetArea(dockWidget); + DomProperty *attr = new DomProperty(); + attr->setAttributeName(u"dockWidgetArea"_s); + attr->setElementNumber(int(area)); + ui_widget->setElementAttribute(ui_widget->elementAttribute() << attr); + } + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QTabWidget *widget, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(widget, ui_parentWidget, false); + QList ui_widget_list; + + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) { + const int current = widget->currentIndex(); + for (int i=0; icount(); ++i) { + QWidget *page = container->widget(i); + Q_ASSERT(page); + + DomWidget *ui_page = createDom(page, ui_widget); + if (!ui_page) { + designerWarning(msgUnmanagedPage(core(), widget, i, page)); + continue; + } + QList ui_attribute_list; + + // attribute `icon' + widget->setCurrentIndex(i); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), widget); + PropertySheetIconValue icon = qvariant_cast(sheet->property(sheet->indexOf(u"currentTabIcon"_s))); + DomProperty *p = resourceBuilder()->saveResource(workingDirectory(), QVariant::fromValue(icon)); + if (p) { + p->setAttributeName(QFormBuilderStrings::iconAttribute); + ui_attribute_list.append(p); + } + // attribute `title' + p = textBuilder()->saveText(sheet->property(sheet->indexOf(u"currentTabText"_s))); + if (p) { + p->setAttributeName(QFormBuilderStrings::titleAttribute); + ui_attribute_list.append(p); + } + + // attribute `toolTip' + QVariant v = sheet->property(sheet->indexOf(u"currentTabToolTip"_s)); + if (!qvariant_cast(v).value().isEmpty()) { + p = textBuilder()->saveText(v); + if (p) { + p->setAttributeName(QFormBuilderStrings::toolTipAttribute); + ui_attribute_list.append(p); + } + } + + // attribute `whatsThis' + v = sheet->property(sheet->indexOf(u"currentTabWhatsThis"_s)); + if (!qvariant_cast(v).value().isEmpty()) { + p = textBuilder()->saveText(v); + if (p) { + p->setAttributeName(QFormBuilderStrings::whatsThisAttribute); + ui_attribute_list.append(p); + } + } + + ui_page->setElementAttribute(ui_attribute_list); + + ui_widget_list.append(ui_page); + } + widget->setCurrentIndex(current); + } + + ui_widget->setElementWidget(ui_widget_list); + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QToolBox *widget, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(widget, ui_parentWidget, false); + QList ui_widget_list; + + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), widget)) { + const int current = widget->currentIndex(); + for (int i=0; icount(); ++i) { + QWidget *page = container->widget(i); + Q_ASSERT(page); + + DomWidget *ui_page = createDom(page, ui_widget); + if (!ui_page) { + designerWarning(msgUnmanagedPage(core(), widget, i, page)); + continue; + } + + // attribute `label' + QList ui_attribute_list; + + // attribute `icon' + widget->setCurrentIndex(i); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), widget); + PropertySheetIconValue icon = qvariant_cast(sheet->property(sheet->indexOf(u"currentItemIcon"_s))); + DomProperty *p = resourceBuilder()->saveResource(workingDirectory(), QVariant::fromValue(icon)); + if (p) { + p->setAttributeName(QFormBuilderStrings::iconAttribute); + ui_attribute_list.append(p); + } + p = textBuilder()->saveText(sheet->property(sheet->indexOf(u"currentItemText"_s))); + if (p) { + p->setAttributeName(QFormBuilderStrings::labelAttribute); + ui_attribute_list.append(p); + } + + // attribute `toolTip' + QVariant v = sheet->property(sheet->indexOf(u"currentItemToolTip"_s)); + if (!qvariant_cast(v).value().isEmpty()) { + p = textBuilder()->saveText(v); + if (p) { + p->setAttributeName(QFormBuilderStrings::toolTipAttribute); + ui_attribute_list.append(p); + } + } + + ui_page->setElementAttribute(ui_attribute_list); + + ui_widget_list.append(ui_page); + } + widget->setCurrentIndex(current); + } + + ui_widget->setElementWidget(ui_widget_list); + + return ui_widget; +} + +DomWidget *QDesignerResource::saveWidget(QWizardPage *wizardPage, DomWidget *ui_parentWidget) +{ + DomWidget *ui_widget = QAbstractFormBuilder::createDom(wizardPage, ui_parentWidget, true); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), wizardPage); + // Save the page id (string) attribute, append to existing attributes + const QString pageIdPropertyName = QLatin1StringView(QWizardPagePropertySheet::pageIdProperty); + const int pageIdIndex = sheet->indexOf(pageIdPropertyName); + if (pageIdIndex != -1 && sheet->isChanged(pageIdIndex)) { + DomProperty *property = variantToDomProperty(this, wizardPage->metaObject(), pageIdPropertyName, sheet->property(pageIdIndex)); + Q_ASSERT(property); + property->elementString()->setAttributeNotr(u"true"_s); + DomPropertyList attributes = ui_widget->elementAttribute(); + attributes.push_back(property); + ui_widget->setElementAttribute(attributes); + } + return ui_widget; +} + +// Do not save the 'currentTabName' properties of containers +static inline bool checkContainerProperty(const QWidget *w, const QString &propertyName) +{ + if (qobject_cast(w)) + return QToolBoxWidgetPropertySheet::checkProperty(propertyName); + if (qobject_cast(w)) + return QTabWidgetPropertySheet::checkProperty(propertyName); + if (qobject_cast(w)) + return QStackedWidgetPropertySheet::checkProperty(propertyName); + if (qobject_cast(w)) + return QMdiAreaPropertySheet::checkProperty(propertyName); + return true; +} + +bool QDesignerResource::checkProperty(QObject *obj, const QString &prop) const +{ + const QDesignerMetaObjectInterface *meta = core()->introspection()->metaObject(obj); + + const int pindex = meta->indexOfProperty(prop); + if (pindex != -1 && !meta->property(pindex)->attributes().testFlag(QDesignerMetaPropertyInterface::StoredAttribute)) + return false; + + if (prop == "objectName"_L1 || prop == "spacerName"_L1) // ### don't store the property objectName + return false; + + QWidget *check_widget = nullptr; + if (obj->isWidgetType()) + check_widget = static_cast(obj); + + if (check_widget && prop == "geometry"_L1) { + if (check_widget == m_formWindow->mainContainer()) + return true; // Save although maincontainer is technically laid-out by embedding container + if (m_selected && m_selected == check_widget) + return true; + + return !LayoutInfo::isWidgetLaidout(core(), check_widget); + } + + if (check_widget && !checkContainerProperty(check_widget, prop)) + return false; + + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), obj)) { + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core()->extensionManager(), obj); + const int pindex = sheet->indexOf(prop); + if (sheet->isAttribute(pindex)) + return false; + + if (!dynamicSheet || !dynamicSheet->isDynamicProperty(pindex)) + return sheet->isChanged(pindex); + if (!sheet->isVisible(pindex)) + return false; + return true; + } + + return false; +} + +bool QDesignerResource::addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) +{ + if (item->widget() == nullptr) { + return false; + } + + QGridLayout *grid = qobject_cast(layout); + QBoxLayout *box = qobject_cast(layout); + + if (grid != nullptr) { + const int rowSpan = ui_item->hasAttributeRowSpan() ? ui_item->attributeRowSpan() : 1; + const int colSpan = ui_item->hasAttributeColSpan() ? ui_item->attributeColSpan() : 1; + grid->addWidget(item->widget(), ui_item->attributeRow(), ui_item->attributeColumn(), rowSpan, colSpan, item->alignment()); + return true; + } + if (box != nullptr) { + box->addItem(item); + return true; + } + + return QAbstractFormBuilder::addItem(ui_item, item, layout); +} + +bool QDesignerResource::addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) +{ + core()->metaDataBase()->add(widget); // ensure the widget is in the meta database + + if (! QAbstractFormBuilder::addItem(ui_widget, widget, parentWidget) || qobject_cast (parentWidget)) { + if (QDesignerContainerExtension *container = qt_extension(core()->extensionManager(), parentWidget)) + container->addWidget(widget); + } + + if (QTabWidget *tabWidget = qobject_cast(parentWidget)) { + const int tabIndex = tabWidget->count() - 1; + const int current = tabWidget->currentIndex(); + + tabWidget->setCurrentIndex(tabIndex); + + const auto &attributes = ui_widget->elementAttribute(); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), parentWidget); + if (auto *picon = QFBE::propertyByName(attributes, QFormBuilderStrings::iconAttribute)) { + QVariant v = resourceBuilder()->loadResource(workingDirectory(), picon); + sheet->setProperty(sheet->indexOf(u"currentTabIcon"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::titleAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentTabText"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::toolTipAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentTabToolTip"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::whatsThisAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentTabWhatsThis"_s), v); + } + tabWidget->setCurrentIndex(current); + } else if (QToolBox *toolBox = qobject_cast(parentWidget)) { + const int itemIndex = toolBox->count() - 1; + const int current = toolBox->currentIndex(); + + toolBox->setCurrentIndex(itemIndex); + + const auto &attributes = ui_widget->elementAttribute(); + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), parentWidget); + if (auto *picon = QFBE::propertyByName(attributes, QFormBuilderStrings::iconAttribute)) { + QVariant v = resourceBuilder()->loadResource(workingDirectory(), picon); + sheet->setProperty(sheet->indexOf(u"currentItemIcon"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::labelAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentItemText"_s), v); + } + if (auto *ptext = QFBE::propertyByName(attributes, QFormBuilderStrings::toolTipAttribute)) { + QVariant v = textBuilder()->loadText(ptext); + sheet->setProperty(sheet->indexOf(u"currentItemToolTip"_s), v); + } + toolBox->setCurrentIndex(current); + } + + return true; +} + +bool QDesignerResource::copy(QIODevice *dev, const FormBuilderClipboard &selection) +{ + m_copyWidget = true; + + DomUI *ui = copy(selection); + + d->m_laidout.clear(); + m_copyWidget = false; + + if (!ui) + return false; + + QXmlStreamWriter writer(dev); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(1); + writer.writeStartDocument(); + ui->write(writer); + writer.writeEndDocument(); + delete ui; + return true; +} + +DomUI *QDesignerResource::copy(const FormBuilderClipboard &selection) +{ + if (selection.empty()) + return nullptr; + + m_copyWidget = true; + + DomWidget *ui_widget = new DomWidget(); + ui_widget->setAttributeName(clipboardObjectName); + bool hasItems = false; + // Widgets + if (!selection.m_widgets.isEmpty()) { + QList ui_widget_list; + for (auto *w : selection.m_widgets) { + m_selected = w; + DomWidget *ui_child = createDom(w, ui_widget); + m_selected = nullptr; + if (ui_child) + ui_widget_list.append(ui_child); + } + if (!ui_widget_list.isEmpty()) { + ui_widget->setElementWidget(ui_widget_list); + hasItems = true; + } + } + // actions + if (!selection.m_actions.isEmpty()) { + QList domActions; + for (QAction* action : std::as_const(selection.m_actions)) { + if (DomAction *domAction = createDom(action)) + domActions += domAction; + } + if (!domActions.isEmpty()) { + ui_widget-> setElementAction(domActions); + hasItems = true; + } + } + + d->m_laidout.clear(); + m_copyWidget = false; + + if (!hasItems) { + delete ui_widget; + return nullptr; + } + // UI + DomUI *ui = new DomUI(); + ui->setAttributeVersion(currentUiVersion); + ui->setElementWidget(ui_widget); + ui->setElementResources(saveResources(m_resourceBuilder->usedQrcFiles())); + if (DomCustomWidgets *cws = saveCustomWidgets()) + ui->setElementCustomWidgets(cws); + return ui; +} + +FormBuilderClipboard QDesignerResource::paste(DomUI *ui, QWidget *widgetParent, QObject *actionParent) +{ + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + const int saved = m_isMainWidget; + m_isMainWidget = false; + + FormBuilderClipboard rc; + + // Widgets + const DomWidget *topLevel = ui->elementWidget(); + initialize(ui); + const auto &domWidgets = topLevel->elementWidget(); + if (!domWidgets.isEmpty()) { + const QPoint offset = m_formWindow->grid(); + for (DomWidget* domWidget : domWidgets) { + if (QWidget *w = create(domWidget, widgetParent)) { + w->move(w->pos() + offset); + // ### change the init properties of w + rc.m_widgets.append(w); + } + } + } + const auto domActions = topLevel->elementAction(); + for (DomAction *domAction : domActions) { + if (QAction *a = create(domAction, actionParent)) + rc.m_actions .append(a); + } + + m_isMainWidget = saved; + + if (QDesignerExtraInfoExtension *extra = qt_extension(core()->extensionManager(), core())) + extra->loadUiExtraInfo(ui); + + createResources(ui->elementResources()); + + return rc; +} + +FormBuilderClipboard QDesignerResource::paste(QIODevice *dev, QWidget *widgetParent, QObject *actionParent) +{ + DomUI ui; + QXmlStreamReader reader(dev); + bool uiInitialized = false; + + while (!reader.atEnd()) { + if (reader.readNext() == QXmlStreamReader::StartElement) { + if (reader.name().compare("ui"_L1, Qt::CaseInsensitive)) { + ui.read(reader); + uiInitialized = true; + } else { + //: Parsing clipboard contents + reader.raiseError(QCoreApplication::translate("QDesignerResource", "Unexpected element <%1>").arg(reader.name().toString())); + } + } + } + if (reader.hasError()) { + //: Parsing clipboard contents + designerWarning(QCoreApplication::translate("QDesignerResource", "Error while pasting clipboard contents at line %1, column %2: %3") + .arg(reader.lineNumber()).arg(reader.columnNumber()) + .arg(reader.errorString())); + uiInitialized = false; + } else if (!uiInitialized) { + //: Parsing clipboard contents + designerWarning(QCoreApplication::translate("QDesignerResource", "Error while pasting clipboard contents: The root element is missing.")); + } + + if (!uiInitialized) + return FormBuilderClipboard(); + + FormBuilderClipboard clipBoard = paste(&ui, widgetParent, actionParent); + + return clipBoard; +} + +void QDesignerResource::layoutInfo(DomLayout *layout, QObject *parent, int *margin, int *spacing) +{ + QAbstractFormBuilder::layoutInfo(layout, parent, margin, spacing); +} + +DomCustomWidgets *QDesignerResource::saveCustomWidgets() +{ + if (m_usedCustomWidgets.isEmpty()) + return nullptr; + + // We would like the list to be in order of the widget database indexes + // to ensure that base classes come first (nice optics) + QDesignerFormEditorInterface *core = m_formWindow->core(); + QDesignerWidgetDataBaseInterface *db = core->widgetDataBase(); + const bool isInternalWidgetDataBase = qobject_cast(db); + QMap orderedMap; + + for (auto it = m_usedCustomWidgets.cbegin(), end = m_usedCustomWidgets.cend(); it != end; ++it) { + QDesignerWidgetDataBaseItemInterface *item = it.key(); + const QString name = item->name(); + DomCustomWidget *custom_widget = new DomCustomWidget; + + custom_widget->setElementClass(name); + if (item->isContainer()) + custom_widget->setElementContainer(item->isContainer()); + + if (!item->includeFile().isEmpty()) { + DomHeader *header = new DomHeader; + const IncludeSpecification spec = includeSpecification(item->includeFile()); + header->setText(spec.first); + if (spec.second == IncludeGlobal) { + header->setAttributeLocation(u"global"_s); + } + custom_widget->setElementHeader(header); + custom_widget->setElementExtends(item->extends()); + } + + if (isInternalWidgetDataBase) { + WidgetDataBaseItem *internalItem = static_cast(item); + const QStringList fakeSlots = internalItem->fakeSlots(); + const QStringList fakeSignals = internalItem->fakeSignals(); + if (!fakeSlots.isEmpty() || !fakeSignals.isEmpty()) { + DomSlots *domSlots = new DomSlots(); + domSlots->setElementSlot(fakeSlots); + domSlots->setElementSignal(fakeSignals); + custom_widget->setElementSlots(domSlots); + } + const QString addPageMethod = internalItem->addPageMethod(); + if (!addPageMethod.isEmpty()) + custom_widget->setElementAddPageMethod(addPageMethod); + } + + orderedMap.insert(db->indexOfClassName(name), custom_widget); + } + + DomCustomWidgets *customWidgets = new DomCustomWidgets; + customWidgets->setElementCustomWidget(orderedMap.values().toVector()); + return customWidgets; +} + +bool QDesignerResource::canCompressSpacings(QObject *object) const +{ + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), object)) { + if (qobject_cast(object)) { + const int h = sheet->property(sheet->indexOf(u"horizontalSpacing"_s)).toInt(); + const int v = sheet->property(sheet->indexOf(u"verticalSpacing"_s)).toInt(); + if (h == v) + return true; + } + } + return false; +} + +QList QDesignerResource::computeProperties(QObject *object) +{ + QList properties; + if (QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), object)) { + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core()->extensionManager(), object); + const int count = sheet->count(); + QList spacingProperties; + const bool compressSpacings = canCompressSpacings(object); + for (int index = 0; index < count; ++index) { + if (!sheet->isChanged(index) && (!dynamicSheet || !dynamicSheet->isDynamicProperty(index))) + continue; + + const QString propertyName = sheet->propertyName(index); + // Suppress windowModality in legacy forms that have it set on child widgets + if (propertyName == "windowModality"_L1 && !sheet->isVisible(index)) + continue; + + const QVariant value = sheet->property(index); + if (DomProperty *p = createProperty(object, propertyName, value)) { + if (compressSpacings && (propertyName == "horizontalSpacing"_L1 + || propertyName == "verticalSpacing"_L1)) { + spacingProperties.append(p); + } else { + properties.append(p); + } + } + } + if (compressSpacings) { + if (spacingProperties.size() == 2) { + DomProperty *spacingProperty = spacingProperties.at(0); + spacingProperty->setAttributeName(u"spacing"_s); + properties.append(spacingProperty); + delete spacingProperties.at(1); + } else { + properties += spacingProperties; + } + } + } + return properties; +} + +DomProperty *QDesignerResource::applyProperStdSetAttribute(QObject *object, const QString &propertyName, DomProperty *property) +{ + if (!property) + return nullptr; + + QExtensionManager *mgr = core()->extensionManager(); + if (const QDesignerPropertySheetExtension *sheet = qt_extension(mgr, object)) { + const QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(mgr, object); + const QDesignerPropertySheet *designerSheet = qobject_cast(core()->extensionManager()->extension(object, Q_TYPEID(QDesignerPropertySheetExtension))); + const int index = sheet->indexOf(propertyName); + if ((dynamicSheet && dynamicSheet->isDynamicProperty(index)) || (designerSheet && designerSheet->isDefaultDynamicProperty(index))) + property->setAttributeStdset(0); + } + return property; +} + +// Optimistic check for a standard setter function +static inline bool hasSetter(QDesignerFormEditorInterface *core, QObject *object, const QString &propertyName) +{ + const QDesignerMetaObjectInterface *meta = core->introspection()->metaObject(object); + const int pindex = meta->indexOfProperty(propertyName); + if (pindex == -1) + return true; + return meta->property(pindex)->hasSetter(); +} + +DomProperty *QDesignerResource::createProperty(QObject *object, const QString &propertyName, const QVariant &value) +{ + if (!checkProperty(object, propertyName)) { + return nullptr; + } + + if (value.canConvert()) { + const PropertySheetFlagValue f = qvariant_cast(value); + const QString flagString = f.metaFlags.toString(f.value, DesignerMetaFlags::FullyQualified); + if (flagString.isEmpty()) + return nullptr; + + DomProperty *p = new DomProperty; + // check if we have a standard cpp set function + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + p->setAttributeName(propertyName); + p->setElementSet(flagString); + return applyProperStdSetAttribute(object, propertyName, p); + } + if (value.canConvert()) { + const PropertySheetEnumValue e = qvariant_cast(value); + bool ok; + const QString id = e.metaEnum.toString(e.value, DesignerMetaEnum::FullyQualified, &ok); + if (!ok) + designerWarning(e.metaEnum.messageToStringFailed(e.value)); + if (id.isEmpty()) + return nullptr; + + DomProperty *p = new DomProperty; + // check if we have a standard cpp set function + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + p->setAttributeName(propertyName); + p->setElementEnum(id); + return applyProperStdSetAttribute(object, propertyName, p); + } + if (value.canConvert()) { + const PropertySheetStringValue strVal = qvariant_cast(value); + DomProperty *p = stringToDomProperty(strVal.value(), strVal); + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + + p->setAttributeName(propertyName); + + return applyProperStdSetAttribute(object, propertyName, p); + } + if (value.canConvert()) { + const PropertySheetStringListValue listValue = qvariant_cast(value); + DomProperty *p = new DomProperty; + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + + p->setAttributeName(propertyName); + + DomStringList *domStringList = new DomStringList(); + domStringList->setElementString(listValue.value()); + translationParametersToDom(listValue, domStringList); + p->setElementStringList(domStringList); + return applyProperStdSetAttribute(object, propertyName, p); + } + if (value.canConvert()) { + const PropertySheetKeySequenceValue keyVal = qvariant_cast(value); + DomProperty *p = stringToDomProperty(keyVal.value().toString(), keyVal); + if (!hasSetter(core(), object, propertyName)) + p->setAttributeStdset(0); + + p->setAttributeName(propertyName); + + return applyProperStdSetAttribute(object, propertyName, p); + } + + return applyProperStdSetAttribute(object, propertyName, QAbstractFormBuilder::createProperty(object, propertyName, value)); +} + +QStringList QDesignerResource::mergeWithLoadedPaths(const QStringList &paths) const +{ + QStringList newPaths = paths; +#ifdef OLD_RESOURCE_FORMAT + const QStringList loadedPaths = m_resourceBuilder->loadedQrcFiles(); + std::remove_copy_if(loadedPaths.cbegin(), loadedPaths.cend(), + std::back_inserter(newPaths), + [&newPaths] (const QString &path) { return newPaths.contains(path); }); +#endif + return newPaths; +} + + +void QDesignerResource::createResources(DomResources *resources) +{ + QStringList paths; + if (resources != nullptr) { + const auto &dom_include = resources->elementInclude(); + for (DomResource *res : dom_include) { + QString path = QDir::cleanPath(m_formWindow->absoluteDir().absoluteFilePath(res->attributeLocation())); + while (!QFile::exists(path)) { + QWidget *dialogParent = m_formWindow->core()->topLevel(); + const QString promptTitle = QCoreApplication::translate("qdesigner_internal::QDesignerResource", "Loading qrc file"); + const QString prompt = QCoreApplication::translate("qdesigner_internal::QDesignerResource", "The specified qrc file

%1

could not be found. Do you want to update the file location?

").arg(path); + + const QMessageBox::StandardButton answer = core()->dialogGui()->message(dialogParent, QDesignerDialogGuiInterface::ResourceLoadFailureMessage, + QMessageBox::Warning, promptTitle, prompt, QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes); + if (answer == QMessageBox::Yes) { + const QFileInfo fi(path); + const QString fileDialogTitle = QCoreApplication::translate("qdesigner_internal::QDesignerResource", "New location for %1").arg(fi.fileName()); + const QString fileDialogPattern = QCoreApplication::translate("qdesigner_internal::QDesignerResource", "Resource files (*.qrc)"); + path = core()->dialogGui()->getOpenFileName(dialogParent, fileDialogTitle, fi.absolutePath(), fileDialogPattern); + if (path.isEmpty()) + break; + m_formWindow->setProperty("_q_resourcepathchanged", QVariant(true)); + } else { + break; + } + } + if (!path.isEmpty()) { + paths << path; + m_formWindow->addResourceFile(path); + } + } + } + +#ifdef OLD_RESOURCE_FORMAT + paths = mergeWithLoadedPaths(paths); +#endif + + QtResourceSet *resourceSet = m_formWindow->resourceSet(); + if (resourceSet) { + QStringList newPaths = resourceSet->activeResourceFilePaths(); + std::remove_copy_if(paths.cbegin(), paths.cend(), + std::back_inserter(newPaths), + [&newPaths] (const QString &path) { return newPaths.contains(path); }); + resourceSet->activateResourceFilePaths(newPaths); + } else { + resourceSet = m_formWindow->core()->resourceModel()->addResourceSet(paths); + m_formWindow->setResourceSet(resourceSet); + QObject::connect(m_formWindow->core()->resourceModel(), &QtResourceModel::resourceSetActivated, + m_formWindow, &FormWindowBase::resourceSetActivated); + } +} + +DomResources *QDesignerResource::saveResources() +{ + QStringList paths; + switch (m_formWindow->resourceFileSaveMode()) { + case QDesignerFormWindowInterface::SaveAllResourceFiles: + paths = m_formWindow->activeResourceFilePaths(); + break; + case QDesignerFormWindowInterface::SaveOnlyUsedResourceFiles: + paths = m_resourceBuilder->usedQrcFiles(); + break; + case QDesignerFormWindowInterface::DontSaveResourceFiles: + break; + } + return saveResources(paths); +} + +DomResources *QDesignerResource::saveResources(const QStringList &qrcPaths) +{ + QtResourceSet *resourceSet = m_formWindow->resourceSet(); + QList dom_include; + if (resourceSet) { + const QStringList activePaths = resourceSet->activeResourceFilePaths(); + for (const QString &path : activePaths) { + if (qrcPaths.contains(path)) { + DomResource *dom_res = new DomResource; + QString conv_path = path; + if (m_resourceBuilder->isSaveRelative()) + conv_path = m_formWindow->absoluteDir().relativeFilePath(path); + conv_path.replace(QDir::separator(), u'/'); + dom_res->setAttributeLocation(conv_path); + dom_include.append(dom_res); + } + } + } + + DomResources *dom_resources = new DomResources; + dom_resources->setElementInclude(dom_include); + + return dom_resources; +} + +DomAction *QDesignerResource::createDom(QAction *action) +{ + if (!core()->metaDataBase()->item(action) || action->menu()) + return nullptr; + + return QAbstractFormBuilder::createDom(action); +} + +DomActionGroup *QDesignerResource::createDom(QActionGroup *actionGroup) +{ + if (core()->metaDataBase()->item(actionGroup) != nullptr) { + return QAbstractFormBuilder::createDom(actionGroup); + } + + return nullptr; +} + +QAction *QDesignerResource::create(DomAction *ui_action, QObject *parent) +{ + if (QAction *action = QAbstractFormBuilder::create(ui_action, parent)) { + core()->metaDataBase()->add(action); + return action; + } + + return nullptr; +} + +QActionGroup *QDesignerResource::create(DomActionGroup *ui_action_group, QObject *parent) +{ + if (QActionGroup *actionGroup = QAbstractFormBuilder::create(ui_action_group, parent)) { + core()->metaDataBase()->add(actionGroup); + return actionGroup; + } + + return nullptr; +} + +DomActionRef *QDesignerResource::createActionRefDom(QAction *action) +{ + if (!core()->metaDataBase()->item(action) + || (!action->isSeparator() && !action->menu() && action->objectName().isEmpty())) + return nullptr; + + return QAbstractFormBuilder::createActionRefDom(action); +} + +void QDesignerResource::addMenuAction(QAction *action) +{ + core()->metaDataBase()->add(action); +} + +QAction *QDesignerResource::createAction(QObject *parent, const QString &name) +{ + if (QAction *action = QAbstractFormBuilder::createAction(parent, name)) { + core()->metaDataBase()->add(action); + return action; + } + + return nullptr; +} + +QActionGroup *QDesignerResource::createActionGroup(QObject *parent, const QString &name) +{ + if (QActionGroup *actionGroup = QAbstractFormBuilder::createActionGroup(parent, name)) { + core()->metaDataBase()->add(actionGroup); + return actionGroup; + } + + return nullptr; +} + +/* Apply the attributes to a widget via property sheet where appropriate, + * that is, the sheet handles attributive fake properties */ +void QDesignerResource::applyAttributesToPropertySheet(const DomWidget *ui_widget, QWidget *widget) +{ + const DomPropertyList attributes = ui_widget->elementAttribute(); + if (attributes.isEmpty()) + return; + QDesignerPropertySheetExtension *sheet = qt_extension(m_formWindow->core()->extensionManager(), widget); + for (auto *prop : attributes) { + const QString name = prop->attributeName(); + const int index = sheet->indexOf(name); + if (index == -1) { + const QString msg = "Unable to apply attributive property '%1' to '%2'. It does not exist."_L1.arg(name, widget->objectName()); + designerWarning(msg); + } else { + sheet->setProperty(index, domPropertyToVariant(this, widget->metaObject(), prop)); + sheet->setChanged(index, true); + } + } +} + +void QDesignerResource::loadExtraInfo(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) +{ + QAbstractFormBuilder::loadExtraInfo(ui_widget, widget, parentWidget); + // Apply the page id attribute of a QWizardPage (which is an attributive fake property) + if (qobject_cast(widget)) + applyAttributesToPropertySheet(ui_widget, widget); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/qdesigner_resource.h b/src/tools/designer/src/components/formeditor/qdesigner_resource.h new file mode 100644 index 00000000000..7466744cadc --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qdesigner_resource.h @@ -0,0 +1,140 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_RESOURCE_H +#define QDESIGNER_RESOURCE_H + +#include "formeditor_global.h" +#include "qsimpleresource_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class DomCustomWidget; +class DomCustomWidgets; +class DomResource; + +class QDesignerContainerExtension; +class QDesignerFormEditorInterface; +class QDesignerCustomWidgetInterface; +class QDesignerWidgetDataBaseItemInterface; + +class QTabWidget; +class QStackedWidget; +class QToolBox; +class QToolBar; +class QDesignerDockWidget; +class QLayoutWidget; +class QWizardPage; + +namespace qdesigner_internal { + +class FormWindow; + +class QT_FORMEDITOR_EXPORT QDesignerResource : public QEditorFormBuilder +{ +public: + explicit QDesignerResource(FormWindow *fw); + ~QDesignerResource() override; + + void save(QIODevice *dev, QWidget *widget) override; + + bool copy(QIODevice *dev, const FormBuilderClipboard &selection) override; + DomUI *copy(const FormBuilderClipboard &selection) override; + + FormBuilderClipboard paste(DomUI *ui, QWidget *widgetParent, QObject *actionParent = nullptr) override; + FormBuilderClipboard paste(QIODevice *dev, QWidget *widgetParent, QObject *actionParent = nullptr) override; + + bool saveRelative() const; + void setSaveRelative(bool relative); + + QWidget *load(QIODevice *dev, QWidget *parentWidget) override; + + DomUI *readUi(QIODevice *dev); + QWidget *loadUi(DomUI *ui, QWidget *parentWidget); + +protected: + using QEditorFormBuilder::create; + using QEditorFormBuilder::createDom; + + void saveDom(DomUI *ui, QWidget *widget) override; + QWidget *create(DomUI *ui, QWidget *parentWidget) override; + QWidget *create(DomWidget *ui_widget, QWidget *parentWidget) override; + QLayout *create(DomLayout *ui_layout, QLayout *layout, QWidget *parentWidget) override; + QLayoutItem *create(DomLayoutItem *ui_layoutItem, QLayout *layout, QWidget *parentWidget) override; + void applyProperties(QObject *o, const QList &properties) override; + QList computeProperties(QObject *obj) override; + DomProperty *createProperty(QObject *object, const QString &propertyName, const QVariant &value) override; + + QWidget *createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) override; + QLayout *createLayout(const QString &layoutName, QObject *parent, const QString &name) override; + void createCustomWidgets(DomCustomWidgets *) override; + void createResources(DomResources*) override; + void applyTabStops(QWidget *widget, DomTabStops *tabStops) override; + + bool addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) override; + bool addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) override; + + DomWidget *createDom(QWidget *widget, DomWidget *ui_parentWidget, bool recursive = true) override; + DomLayout *createDom(QLayout *layout, DomLayout *ui_layout, DomWidget *ui_parentWidget) override; + DomLayoutItem *createDom(QLayoutItem *item, DomLayout *ui_layout, DomWidget *ui_parentWidget) override; + + QAction *create(DomAction *ui_action, QObject *parent) override; + QActionGroup *create(DomActionGroup *ui_action_group, QObject *parent) override; + void addMenuAction(QAction *action) override; + + DomAction *createDom(QAction *action) override; + DomActionGroup *createDom(QActionGroup *actionGroup) override; + DomActionRef *createActionRefDom(QAction *action) override; + + QAction *createAction(QObject *parent, const QString &name) override; + QActionGroup *createActionGroup(QObject *parent, const QString &name) override; + + bool checkProperty(QObject *obj, const QString &prop) const override; + + DomWidget *saveWidget(QTabWidget *widget, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QStackedWidget *widget, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QToolBox *widget, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QWidget *widget, QDesignerContainerExtension *container, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QToolBar *toolBar, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QDesignerDockWidget *dockWidget, DomWidget *ui_parentWidget); + DomWidget *saveWidget(QWizardPage *wizardPage, DomWidget *ui_parentWidget); + + DomCustomWidgets *saveCustomWidgets() override; + DomTabStops *saveTabStops() override; + DomResources *saveResources() override; + + void layoutInfo(DomLayout *layout, QObject *parent, int *margin, int *spacing) override; + + void loadExtraInfo(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) override; + + void changeObjectName(QObject *o, QString name); + DomProperty *applyProperStdSetAttribute(QObject *object, const QString &propertyName, DomProperty *property); + +private: + DomResources *saveResources(const QStringList &qrcPaths); + bool canCompressSpacings(QObject *object) const; + QStringList mergeWithLoadedPaths(const QStringList &paths) const; + void applyAttributesToPropertySheet(const DomWidget *ui_widget, QWidget *widget); + + using DomCustomWidgetList = QList; + void addCustomWidgetsToWidgetDatabase(DomCustomWidgetList& list); + FormWindow *m_formWindow; + bool m_isMainWidget; + QHash m_internal_to_qt; + QHash m_qt_to_internal; + QStack m_chain; + QHash m_usedCustomWidgets; + bool m_copyWidget; + QWidget *m_selected; + class QDesignerResourceBuilder *m_resourceBuilder; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_RESOURCE_H diff --git a/src/tools/designer/src/components/formeditor/qlayoutwidget_propertysheet.cpp b/src/tools/designer/src/components/formeditor/qlayoutwidget_propertysheet.cpp new file mode 100644 index 00000000000..78fc443a23c --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qlayoutwidget_propertysheet.cpp @@ -0,0 +1,46 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qlayoutwidget_propertysheet.h" +#include "qlayout_widget_p.h" +#include "formwindow.h" +#include "formeditor.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +QLayoutWidgetPropertySheet::QLayoutWidgetPropertySheet(QLayoutWidget *object, QObject *parent) + : QDesignerPropertySheet(object, parent) +{ + clearFakeProperties(); +} + +QLayoutWidgetPropertySheet::~QLayoutWidgetPropertySheet() = default; + +bool QLayoutWidgetPropertySheet::isVisible(int index) const +{ + if (propertyGroup(index) == "Layout"_L1) + return QDesignerPropertySheet::isVisible(index); + return false; +} + +void QLayoutWidgetPropertySheet::setProperty(int index, const QVariant &value) +{ + QDesignerPropertySheet::setProperty(index, value); +} + +bool QLayoutWidgetPropertySheet::dynamicPropertiesAllowed() const +{ + return false; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/qlayoutwidget_propertysheet.h b/src/tools/designer/src/components/formeditor/qlayoutwidget_propertysheet.h new file mode 100644 index 00000000000..b01a9b148df --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qlayoutwidget_propertysheet.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QLAYOUTWIDGET_PROPERTYSHEET_H +#define QLAYOUTWIDGET_PROPERTYSHEET_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QLayoutWidgetPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit QLayoutWidgetPropertySheet(QLayoutWidget *object, QObject *parent = nullptr); + ~QLayoutWidgetPropertySheet() override; + + void setProperty(int index, const QVariant &value) override; + bool isVisible(int index) const override; + + bool dynamicPropertiesAllowed() const override; +}; + +using QLayoutWidgetPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QLAYOUTWIDGET_PROPERTYSHEET_H diff --git a/src/tools/designer/src/components/formeditor/qmainwindow_container.cpp b/src/tools/designer/src/components/formeditor/qmainwindow_container.cpp new file mode 100644 index 00000000000..d4c85bc7511 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qmainwindow_container.cpp @@ -0,0 +1,181 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmainwindow_container.h" +#include "qdesigner_toolbar_p.h" +#include "formwindow.h" + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +QMainWindowContainer::QMainWindowContainer(QMainWindow *widget, QObject *parent) + : QObject(parent), + m_mainWindow(widget) +{ +} + +int QMainWindowContainer::count() const +{ + return m_widgets.size(); +} + +QWidget *QMainWindowContainer::widget(int index) const +{ + return m_widgets.value(index, nullptr); +} + +int QMainWindowContainer::currentIndex() const +{ + // QTBUG-111603, handle plugins with unmanaged central widgets + auto *cw = m_mainWindow->centralWidget(); + return cw != nullptr && m_widgets.contains(cw) ? 0 : -1; +} + +void QMainWindowContainer::setCurrentIndex(int index) +{ + Q_UNUSED(index); +} + + +namespace { + // Pair of + using ToolBarData = std::pair; + + ToolBarData toolBarData(QToolBar *me) { + const QMainWindow *mw = qobject_cast(me->parentWidget()); + if (!mw || !mw->layout() || mw->layout()->indexOf(me) == -1) { + const QVariant desiredAreaV = me->property("_q_desiredArea"); + const Qt::ToolBarArea desiredArea = desiredAreaV.canConvert() + ? desiredAreaV.value() : Qt::TopToolBarArea; + return {desiredArea, false}; + } + return ToolBarData(mw->toolBarArea(me), mw->toolBarBreak(me)); + } + +Qt::DockWidgetArea dockWidgetArea(QDockWidget *me) +{ + if (const QMainWindow *mw = qobject_cast(me->parentWidget())) { + // Make sure that me is actually managed by mw, otherwise + // QMainWindow::dockWidgetArea() will be VERY upset + QList candidates; + if (mw->layout()) { + candidates.append(mw->layout()); + candidates += mw->layout()->findChildren(); + } + for (QLayout *l : std::as_const(candidates)) { + if (l->indexOf(me) != -1) + return mw->dockWidgetArea(me); + } + } + return Qt::LeftDockWidgetArea; +} +} + +// In QMainWindowContainer::remove(), remember the dock area in a dynamic +// property so that it can used in addWidget() if that is called by undo(). +static const char dockAreaPropertyName[] = "_q_dockArea"; + +void QMainWindowContainer::addWidget(QWidget *widget) +{ + // remove all the occurrences of widget + m_widgets.removeAll(widget); + + // the + if (QToolBar *toolBar = qobject_cast(widget)) { + m_widgets.append(widget); + const ToolBarData data = toolBarData(toolBar); + m_mainWindow->addToolBar(data.first, toolBar); + if (data.second) m_mainWindow->insertToolBarBreak(toolBar); + toolBar->show(); + } + + else if (QMenuBar *menuBar = qobject_cast(widget)) { + if (menuBar != m_mainWindow->menuBar()) + m_mainWindow->setMenuBar(menuBar); + + m_widgets.append(widget); + menuBar->show(); + } + + else if (QStatusBar *statusBar = qobject_cast(widget)) { + if (statusBar != m_mainWindow->statusBar()) + m_mainWindow->setStatusBar(statusBar); + + m_widgets.append(widget); + statusBar->show(); + } + + else if (QDockWidget *dockWidget = qobject_cast(widget)) { + m_widgets.append(widget); + + Qt::DockWidgetArea area = Qt::LeftDockWidgetArea; + const auto areaProperty = widget->property(dockAreaPropertyName); + if (areaProperty.canConvert()) { + area = areaProperty.value(); + widget->setProperty(dockAreaPropertyName, {}); + } else { + area = dockWidgetArea(dockWidget); + } + + m_mainWindow->addDockWidget(area, dockWidget); + dockWidget->show(); + + if (FormWindow *fw = FormWindow::findFormWindow(m_mainWindow)) { + fw->manageWidget(widget); + } + } + + else if (widget) { + m_widgets.prepend(widget); + + if (widget != m_mainWindow->centralWidget()) { + // note that qmainwindow will delete the current central widget if you + // call setCentralWidget(), we end up with dangeling pointers in m_widgets list + m_widgets.removeAll(m_mainWindow->centralWidget()); + + widget->setParent(m_mainWindow); + m_mainWindow->setCentralWidget(widget); + } + } +} + +void QMainWindowContainer::insertWidget(int index, QWidget *widget) +{ + Q_UNUSED(index); + + addWidget(widget); +} + +void QMainWindowContainer::remove(int index) +{ + QWidget *widget = m_widgets.at(index); + if (QToolBar *toolBar = qobject_cast(widget)) { + m_mainWindow->removeToolBar(toolBar); + } else if (QMenuBar *menuBar = qobject_cast(widget)) { + menuBar->hide(); + menuBar->setParent(nullptr); + m_mainWindow->setMenuBar(nullptr); + } else if (QStatusBar *statusBar = qobject_cast(widget)) { + statusBar->hide(); + statusBar->setParent(nullptr); + m_mainWindow->setStatusBar(nullptr); + } else if (QDockWidget *dockWidget = qobject_cast(widget)) { + const auto area = m_mainWindow->dockWidgetArea(dockWidget); + dockWidget->setProperty(dockAreaPropertyName, QVariant::fromValue(area)); + m_mainWindow->removeDockWidget(dockWidget); + } + m_widgets.removeAt(index); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/qmainwindow_container.h b/src/tools/designer/src/components/formeditor/qmainwindow_container.h new file mode 100644 index 00000000000..4473cab8f7f --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qmainwindow_container.h @@ -0,0 +1,45 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMAINWINDOW_CONTAINER_H +#define QMAINWINDOW_CONTAINER_H + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QMainWindowContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QMainWindowContainer(QMainWindow *widget, QObject *parent = nullptr); + + int count() const override; + QWidget *widget(int index) const override; + int currentIndex() const override; + void setCurrentIndex(int index) override; + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QMainWindow *m_mainWindow; + QWidgetList m_widgets; +}; + +using QMainWindowContainerFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QMAINWINDOW_CONTAINER_H diff --git a/src/tools/designer/src/components/formeditor/qmdiarea_container.cpp b/src/tools/designer/src/components/formeditor/qmdiarea_container.cpp new file mode 100644 index 00000000000..137bd96187e --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qmdiarea_container.cpp @@ -0,0 +1,243 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qmdiarea_container.h" + +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +QMdiAreaContainer::QMdiAreaContainer(QMdiArea *widget, QObject *parent) + : QObject(parent), + m_mdiArea(widget) +{ +} + +int QMdiAreaContainer::count() const +{ + return m_mdiArea->subWindowList(QMdiArea::CreationOrder).size(); +} + +QWidget *QMdiAreaContainer::widget(int index) const +{ + if (index < 0) + return nullptr; + return m_mdiArea->subWindowList(QMdiArea::CreationOrder).at(index)->widget(); +} + +int QMdiAreaContainer::currentIndex() const +{ + if (QMdiSubWindow *sub = m_mdiArea->activeSubWindow()) + return m_mdiArea->subWindowList(QMdiArea::CreationOrder).indexOf(sub); + return -1; +} + +void QMdiAreaContainer::setCurrentIndex(int index) +{ + if (index < 0) { + qDebug() << "** WARNING Attempt to QMdiAreaContainer::setCurrentIndex(-1)"; + return; + } + QMdiSubWindow *frame = m_mdiArea->subWindowList(QMdiArea::CreationOrder).at(index); + m_mdiArea->setActiveSubWindow(frame); +} + +void QMdiAreaContainer::addWidget(QWidget *widget) +{ + QMdiSubWindow *frame = m_mdiArea->addSubWindow(widget, Qt::Window); + frame->show(); + m_mdiArea->cascadeSubWindows(); + positionNewMdiChild(m_mdiArea, frame); +} + +// Semi-smart positioning of new windows: Make child fill the whole MDI window below +// cascaded other windows +void QMdiAreaContainer::positionNewMdiChild(const QWidget *area, QWidget *mdiChild) +{ + enum { MinSize = 20 }; + const QPoint pos = mdiChild->pos(); + const QSize areaSize = area->size(); + switch (QApplication::layoutDirection()) { + case Qt::LayoutDirectionAuto: + case Qt::LeftToRight: { + const QSize fullSize = QSize(areaSize.width() - pos.x(), areaSize.height() - pos.y()); + if (fullSize.width() > MinSize && fullSize.height() > MinSize) + mdiChild->resize(fullSize); + } + break; + case Qt::RightToLeft: { + const QSize fullSize = QSize(pos.x() + mdiChild->width(), areaSize.height() - pos.y()); + if (fullSize.width() > MinSize && fullSize.height() > MinSize) { + mdiChild->move(0, pos.y()); + mdiChild->resize(fullSize); + } + } + break; + } +} + +void QMdiAreaContainer::insertWidget(int, QWidget *widget) +{ + addWidget(widget); +} + +void QMdiAreaContainer::remove(int index) +{ + auto subWins = m_mdiArea->subWindowList(QMdiArea::CreationOrder); + if (index >= 0 && index < subWins.size()) { + QMdiSubWindow *f = subWins.at(index); + m_mdiArea->removeSubWindow(f->widget()); + delete f; + } +} + +// ---------- MdiAreaPropertySheet, creates fake properties: +// 1) window name (object name of child) +// 2) title (windowTitle of child). + +static constexpr auto subWindowTitleC = "activeSubWindowTitle"_L1; +static constexpr auto subWindowNameC = "activeSubWindowName"_L1; + +QMdiAreaPropertySheet::QMdiAreaPropertySheet(QWidget *mdiArea, QObject *parent) : + QDesignerPropertySheet(mdiArea, parent), + m_windowTitleProperty(u"windowTitle"_s) +{ + createFakeProperty(subWindowNameC, QString()); + createFakeProperty(subWindowTitleC, QString()); +} + +QMdiAreaPropertySheet::MdiAreaProperty QMdiAreaPropertySheet::mdiAreaProperty(const QString &name) +{ + static const QHash mdiAreaPropertyHash = { + {subWindowNameC, MdiAreaSubWindowName}, + {subWindowTitleC, MdiAreaSubWindowTitle} + }; + return mdiAreaPropertyHash.value(name, MdiAreaNone); +} + +void QMdiAreaPropertySheet::setProperty(int index, const QVariant &value) +{ + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + if (QWidget *w = currentWindow()) + w->setObjectName(value.toString()); + break; + case MdiAreaSubWindowTitle: // Forward to window title of subwindow + if (QDesignerPropertySheetExtension *cws = currentWindowSheet()) { + const int index = cws->indexOf(m_windowTitleProperty); + cws->setProperty(index, value); + cws->setChanged(index, true); + } + break; + default: + QDesignerPropertySheet::setProperty(index, value); + break; + } +} + +bool QMdiAreaPropertySheet::reset(int index) +{ + bool rc = true; + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + setProperty(index, QVariant(QString())); + setChanged(index, false); + break; + case MdiAreaSubWindowTitle: // Forward to window title of subwindow + if (QDesignerPropertySheetExtension *cws = currentWindowSheet()) { + const int index = cws->indexOf(m_windowTitleProperty); + rc = cws->reset(index); + } + break; + default: + rc = QDesignerPropertySheet::reset(index); + break; + } + return rc; +} + +QVariant QMdiAreaPropertySheet::property(int index) const +{ + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + if (QWidget *w = currentWindow()) + return w->objectName(); + return QVariant(QString()); + case MdiAreaSubWindowTitle: + if (QWidget *w = currentWindow()) + return w->windowTitle(); + return QVariant(QString()); + case MdiAreaNone: + break; + } + return QDesignerPropertySheet::property(index); +} + +bool QMdiAreaPropertySheet::isEnabled(int index) const +{ + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + case MdiAreaSubWindowTitle: + return currentWindow() != nullptr; + case MdiAreaNone: + break; + } + return QDesignerPropertySheet::isEnabled(index); +} + +bool QMdiAreaPropertySheet::isChanged(int index) const +{ + bool rc = false; + switch (mdiAreaProperty(propertyName(index))) { + case MdiAreaSubWindowName: + rc = currentWindow() != nullptr; + break; + case MdiAreaSubWindowTitle: + if (QDesignerPropertySheetExtension *cws = currentWindowSheet()) { + const int index = cws->indexOf(m_windowTitleProperty); + rc = cws->isChanged(index); + } + break; + default: + rc = QDesignerPropertySheet::isChanged(index); + break; + } + return rc; +} + +QWidget *QMdiAreaPropertySheet::currentWindow() const +{ + if (const QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), object())) { + const int ci = c->currentIndex(); + if (ci < 0) + return nullptr; + return c->widget(ci); + } + return nullptr; +} + +QDesignerPropertySheetExtension *QMdiAreaPropertySheet::currentWindowSheet() const +{ + QWidget *cw = currentWindow(); + if (cw == nullptr) + return nullptr; + return qt_extension(core()->extensionManager(), cw); +} + +bool QMdiAreaPropertySheet::checkProperty(const QString &propertyName) +{ + return mdiAreaProperty(propertyName) == MdiAreaNone; +} +} +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/qmdiarea_container.h b/src/tools/designer/src/components/formeditor/qmdiarea_container.h new file mode 100644 index 00000000000..aebd3f2c348 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qmdiarea_container.h @@ -0,0 +1,80 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QMDIAREA_CONTAINER_H +#define QMDIAREA_CONTAINER_H + +#include + + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Container for QMdiArea +class QMdiAreaContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QMdiAreaContainer(QMdiArea *widget, QObject *parent = nullptr); + + int count() const override; + QWidget *widget(int index) const override; + int currentIndex() const override; + void setCurrentIndex(int index) override; + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + + // Semismart positioning of a new MDI child after cascading + static void positionNewMdiChild(const QWidget *area, QWidget *mdiChild); + +private: + QMdiArea *m_mdiArea; +}; + +// PropertySheet for QMdiArea: Fakes window title and name. + +class QMdiAreaPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit QMdiAreaPropertySheet(QWidget *mdiArea, QObject *parent = nullptr); + + void setProperty(int index, const QVariant &value) override; + bool reset(int index) override; + bool isEnabled(int index) const override; + bool isChanged(int index) const override; + QVariant property(int index) const override; + + // Check whether the property is to be saved. Returns false for the page + // properties (as the property sheet has no concept of 'stored') + static bool checkProperty(const QString &propertyName); + +private: + const QString m_windowTitleProperty; + QWidget *currentWindow() const; + QDesignerPropertySheetExtension *currentWindowSheet() const; + + enum MdiAreaProperty { MdiAreaSubWindowName, MdiAreaSubWindowTitle, MdiAreaNone }; + static MdiAreaProperty mdiAreaProperty(const QString &name); +}; + +// Factories + +using QMdiAreaContainerFactory = ExtensionFactory; +using QMdiAreaPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QMDIAREA_CONTAINER_H diff --git a/src/tools/designer/src/components/formeditor/qwizard_container.cpp b/src/tools/designer/src/components/formeditor/qwizard_container.cpp new file mode 100644 index 00000000000..99254a4fbc7 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qwizard_container.cpp @@ -0,0 +1,185 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qwizard_container.h" + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using WizardPageList = QList; + +namespace qdesigner_internal { + +QWizardContainer::QWizardContainer(QWizard *widget, QObject *parent) : + QObject(parent), + m_wizard(widget) +{ +} + +int QWizardContainer::count() const +{ + return m_wizard->pageIds().size(); +} + +QWidget *QWizardContainer::widget(int index) const +{ + QWidget *rc = nullptr; + if (index >= 0) { + const auto idList = m_wizard->pageIds(); + if (index < idList.size()) + rc = m_wizard->page(idList.at(index)); + } + return rc; +} + +int QWizardContainer::currentIndex() const +{ + return m_wizard->pageIds().indexOf(m_wizard->currentId()); +} + +void QWizardContainer::setCurrentIndex(int index) +{ + if (index < 0 || m_wizard->pageIds().isEmpty()) + return; + + int currentIdx = currentIndex(); + + if (currentIdx == -1) { + m_wizard->restart(); + currentIdx = currentIndex(); + } + + if (currentIdx == index) + return; + + const int d = qAbs(index - currentIdx); + if (index > currentIdx) { + for (int i = 0; i < d; i++) + m_wizard->next(); + } else { + for (int i = 0; i < d; i++) + m_wizard->back(); + } +} + +static const char msgWrongType[] = "** WARNING Attempt to add oject that is not of class WizardPage to a QWizard"; + +void QWizardContainer::addWidget(QWidget *widget) +{ + QWizardPage *page = qobject_cast(widget); + if (!page) { + qWarning("%s", msgWrongType); + return; + } + m_wizard->addPage(page); + // Might be -1 after adding the first page + setCurrentIndex(m_wizard->pageIds().size() - 1); +} + +void QWizardContainer::insertWidget(int index, QWidget *widget) +{ + enum { delta = 5 }; + + QWizardPage *newPage = qobject_cast(widget); + if (!newPage) { + qWarning("%s", msgWrongType); + return; + } + + const auto idList = m_wizard->pageIds(); + const auto pageCount = idList.size(); + if (index >= pageCount) { + addWidget(widget); + return; + } + + // Insert before, reshuffle ids if required + const int idBefore = idList.at(index); + const int newId = idBefore - 1; + const bool needsShuffle = + (index == 0 && newId < 0) // At start: QWizard refuses to insert id -1 + || (index > 0 && idList.at(index - 1) == newId); // In-between + if (needsShuffle) { + // Create a gap by shuffling pages + WizardPageList pageList; + pageList.push_back(newPage); + for (qsizetype i = index; i < pageCount; ++i) { + pageList.push_back(m_wizard->page(idList.at(i))); + m_wizard->removePage(idList.at(i)); + } + int newId = idBefore + delta; + for (QWizardPage *page : std::as_const(pageList)) { + m_wizard->setPage(newId, page); + newId += delta; + } + } else { + // Gap found, just insert + m_wizard->setPage(newId, newPage); + } + // Might be at -1 after adding the first page + setCurrentIndex(index); +} + +void QWizardContainer::remove(int index) +{ + if (index < 0) + return; + + const auto idList = m_wizard->pageIds(); + if (index >= idList.size()) + return; + + m_wizard->removePage(idList.at(index)); + // goto next page, preferably + const int newSize = idList.size() - 1; + if (index < newSize) { + setCurrentIndex(index); + } else { + if (newSize > 0) + setCurrentIndex(newSize - 1); + } +} + +// ---------------- QWizardPagePropertySheet +const char *QWizardPagePropertySheet::pageIdProperty = "pageId"; + +QWizardPagePropertySheet::QWizardPagePropertySheet(QWizardPage *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_pageIdIndex(createFakeProperty(QLatin1StringView(pageIdProperty), QString())) +{ + setAttribute(m_pageIdIndex, true); +} + +bool QWizardPagePropertySheet::reset(int index) +{ + if (index == m_pageIdIndex) { + setProperty(index, QString()); + return true; + } + return QDesignerPropertySheet::reset(index); +} + +// ---------------- QWizardPropertySheet +QWizardPropertySheet::QWizardPropertySheet(QWizard *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_startId(u"startId"_s) +{ +} + +bool QWizardPropertySheet::isVisible(int index) const +{ + if (propertyName(index) == m_startId) + return false; + return QDesignerPropertySheet::isVisible(index); +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/qwizard_container.h b/src/tools/designer/src/components/formeditor/qwizard_container.h new file mode 100644 index 00000000000..5c6533f66ed --- /dev/null +++ b/src/tools/designer/src/components/formeditor/qwizard_container.h @@ -0,0 +1,86 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QWIZARD_CONTAINER_H +#define QWIZARD_CONTAINER_H + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QWizardPage; + +namespace qdesigner_internal { + +// Container for QWizard. Care must be taken to position +// the QWizard at some valid page after removal/insertion +// as it is not used to having its pages ripped out. +class QWizardContainer: public QObject, public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) +public: + explicit QWizardContainer(QWizard *widget, QObject *parent = nullptr); + + int count() const override; + QWidget *widget(int index) const override; + int currentIndex() const override; + void setCurrentIndex(int index) override; + bool canAddWidget() const override { return true; } + void addWidget(QWidget *widget) override; + void insertWidget(int index, QWidget *widget) override; + bool canRemove(int) const override { return true; } + void remove(int index) override; + +private: + QWizard *m_wizard; +}; + +// QWizardPagePropertySheet: Introduces a attribute string fake property +// "pageId" that allows for specifying enumeration values (uic only). +// This breaks the pattern of having a "currentSth" property for the +// container, but was deemed to make sense here since the Page has +// its own "title" properties. +class QWizardPagePropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT +public: + explicit QWizardPagePropertySheet(QWizardPage *object, QObject *parent = nullptr); + + bool reset(int index) override; + + static const char *pageIdProperty; + +private: + const int m_pageIdIndex; +}; + +// QWizardPropertySheet: Hides the "startId" property. It cannot be used +// as QWizard cannot handle setting it as a property before the actual +// page is added. + +class QWizardPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT +public: + explicit QWizardPropertySheet(QWizard *object, QObject *parent = nullptr); + bool isVisible(int index) const override; + +private: + const QString m_startId; +}; + +// Factories +using QWizardPropertySheetFactory = QDesignerPropertySheetFactory; +using QWizardPagePropertySheetFactory = QDesignerPropertySheetFactory; +using QWizardContainerFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QWIZARD_CONTAINER_H diff --git a/src/tools/designer/src/components/formeditor/spacer_propertysheet.cpp b/src/tools/designer/src/components/formeditor/spacer_propertysheet.cpp new file mode 100644 index 00000000000..9c3da13039c --- /dev/null +++ b/src/tools/designer/src/components/formeditor/spacer_propertysheet.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "spacer_propertysheet.h" +#include "qdesigner_widget_p.h" +#include "formwindow.h" +#include "spacer_widget_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal +{ +SpacerPropertySheet::SpacerPropertySheet(Spacer *object, QObject *parent) + : QDesignerPropertySheet(object, parent) +{ + clearFakeProperties(); +} + +SpacerPropertySheet::~SpacerPropertySheet() = default; + +bool SpacerPropertySheet::isVisible(int index) const +{ + return propertyGroup(index) == "Spacer"_L1; +} + +void SpacerPropertySheet::setProperty(int index, const QVariant &value) +{ + QDesignerPropertySheet::setProperty(index, value); +} + +bool SpacerPropertySheet::dynamicPropertiesAllowed() const +{ + return false; +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/spacer_propertysheet.h b/src/tools/designer/src/components/formeditor/spacer_propertysheet.h new file mode 100644 index 00000000000..c2525d84d34 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/spacer_propertysheet.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SPACER_PROPERTYSHEET_H +#define SPACER_PROPERTYSHEET_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class SpacerPropertySheet: public QDesignerPropertySheet +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) +public: + explicit SpacerPropertySheet(Spacer *object, QObject *parent = nullptr); + ~SpacerPropertySheet() override; + + void setProperty(int index, const QVariant &value) override; + bool isVisible(int index) const override; + + bool dynamicPropertiesAllowed() const override; +}; + +using SpacerPropertySheetFactory = QDesignerPropertySheetFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SPACER_PROPERTYSHEET_H diff --git a/src/tools/designer/src/components/formeditor/templateoptionspage.cpp b/src/tools/designer/src/components/formeditor/templateoptionspage.cpp new file mode 100644 index 00000000000..2063972d9fb --- /dev/null +++ b/src/tools/designer/src/components/formeditor/templateoptionspage.cpp @@ -0,0 +1,147 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "templateoptionspage.h" +#include "ui_templateoptionspage.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// ----------------- TemplateOptionsWidget +TemplateOptionsWidget::TemplateOptionsWidget(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_core(core), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::TemplateOptionsWidget) +{ + m_ui->setupUi(this); + + m_ui->m_addTemplatePathButton->setIcon( + qdesigner_internal::createIconSet("plus.png"_L1)); + m_ui->m_removeTemplatePathButton->setIcon( + qdesigner_internal::createIconSet("minus.png"_L1)); + + connect(m_ui->m_templatePathListWidget, &QListWidget::itemSelectionChanged, + this, &TemplateOptionsWidget::templatePathSelectionChanged); + connect(m_ui->m_addTemplatePathButton, &QAbstractButton::clicked, + this, &TemplateOptionsWidget::addTemplatePath); + connect(m_ui->m_removeTemplatePathButton, &QAbstractButton::clicked, + this, &TemplateOptionsWidget::removeTemplatePath); +} + +TemplateOptionsWidget::~TemplateOptionsWidget() +{ + delete m_ui; +} + +QStringList TemplateOptionsWidget::templatePaths() const +{ + QStringList rc; + const int count = m_ui->m_templatePathListWidget->count(); + for (int i = 0; i < count; i++) { + rc += m_ui->m_templatePathListWidget->item(i)->text(); + } + return rc; +} + +void TemplateOptionsWidget::setTemplatePaths(const QStringList &l) +{ + // add paths and select 0 + m_ui->m_templatePathListWidget->clear(); + if (l.isEmpty()) { + // disable button + templatePathSelectionChanged(); + } else { + for (const auto &s : l) + m_ui->m_templatePathListWidget->addItem(s); + m_ui->m_templatePathListWidget->setCurrentItem(m_ui->m_templatePathListWidget->item(0)); + } +} + +void TemplateOptionsWidget::addTemplatePath() +{ + const QString templatePath = chooseTemplatePath(m_core, this); + if (templatePath.isEmpty()) + return; + + const auto existing + = m_ui->m_templatePathListWidget->findItems(templatePath, Qt::MatchExactly); + if (!existing.isEmpty()) + return; + + QListWidgetItem *newItem = new QListWidgetItem(templatePath); + m_ui->m_templatePathListWidget->addItem(newItem); + m_ui->m_templatePathListWidget->setCurrentItem(newItem); +} + +void TemplateOptionsWidget::removeTemplatePath() +{ + const auto selectedPaths = m_ui->m_templatePathListWidget->selectedItems(); + if (selectedPaths.isEmpty()) + return; + delete selectedPaths.constFirst(); +} + +void TemplateOptionsWidget::templatePathSelectionChanged() +{ + const auto selectedPaths = m_ui->m_templatePathListWidget->selectedItems(); + m_ui->m_removeTemplatePathButton->setEnabled(!selectedPaths.isEmpty()); +} + +QString TemplateOptionsWidget::chooseTemplatePath(QDesignerFormEditorInterface *core, QWidget *parent) +{ + QString rc = core->dialogGui()->getExistingDirectory(parent, + tr("Pick a directory to save templates in")); + if (rc.isEmpty()) + return rc; + + if (rc.endsWith(QDir::separator())) + rc.remove(rc.size() - 1, 1); + return rc; +} + +// ----------------- TemplateOptionsPage +TemplateOptionsPage::TemplateOptionsPage(QDesignerFormEditorInterface *core) : + m_core(core) +{ +} + +QString TemplateOptionsPage::name() const +{ + //: Tab in preferences dialog + return QCoreApplication::translate("TemplateOptionsPage", "Template Paths"); +} + +QWidget *TemplateOptionsPage::createPage(QWidget *parent) +{ + m_widget = new TemplateOptionsWidget(m_core, parent); + m_initialTemplatePaths = QDesignerSharedSettings(m_core).additionalFormTemplatePaths(); + m_widget->setTemplatePaths(m_initialTemplatePaths); + return m_widget; +} + +void TemplateOptionsPage::apply() +{ + if (m_widget) { + const QStringList newTemplatePaths = m_widget->templatePaths(); + if (newTemplatePaths != m_initialTemplatePaths) { + QDesignerSharedSettings settings(m_core); + settings.setAdditionalFormTemplatePaths(newTemplatePaths); + m_initialTemplatePaths = newTemplatePaths; + } + } +} + +void TemplateOptionsPage::finish() +{ +} +} +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/templateoptionspage.h b/src/tools/designer/src/components/formeditor/templateoptionspage.h new file mode 100644 index 00000000000..9ce8c45cf70 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/templateoptionspage.h @@ -0,0 +1,72 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_TEMPLATEOPTIONS_H +#define QDESIGNER_TEMPLATEOPTIONS_H + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +namespace Ui { + class TemplateOptionsWidget; +} + +/* Present the user with a list of form template paths to save + * form templates. */ +class TemplateOptionsWidget : public QWidget +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(TemplateOptionsWidget) +public: + explicit TemplateOptionsWidget(QDesignerFormEditorInterface *core, + QWidget *parent = nullptr); + ~TemplateOptionsWidget(); + + + QStringList templatePaths() const; + void setTemplatePaths(const QStringList &l); + + static QString chooseTemplatePath(QDesignerFormEditorInterface *core, QWidget *parent); + +private slots: + void addTemplatePath(); + void removeTemplatePath(); + void templatePathSelectionChanged(); + +private: + QDesignerFormEditorInterface *m_core; + Ui::TemplateOptionsWidget *m_ui; +}; + +class TemplateOptionsPage : public QDesignerOptionsPageInterface +{ + Q_DISABLE_COPY_MOVE(TemplateOptionsPage) +public: + explicit TemplateOptionsPage(QDesignerFormEditorInterface *core); + + QString name() const override; + QWidget *createPage(QWidget *parent) override; + void apply() override; + void finish() override; + +private: + QDesignerFormEditorInterface *m_core; + QStringList m_initialTemplatePaths; + QPointer m_widget; +}; + +} + +QT_END_NAMESPACE + +#endif // QDESIGNER_TEMPLATEOPTIONS_H diff --git a/src/tools/designer/src/components/formeditor/templateoptionspage.ui b/src/tools/designer/src/components/formeditor/templateoptionspage.ui new file mode 100644 index 00000000000..3427ffeb801 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/templateoptionspage.ui @@ -0,0 +1,59 @@ + + qdesigner_internal::TemplateOptionsWidget + + + + 0 + 0 + 376 + 387 + + + + Form + + + + + + Additional Template Paths + + + + + + + + + ... + + + + + + + ... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + diff --git a/src/tools/designer/src/components/formeditor/tool_widgeteditor.cpp b/src/tools/designer/src/components/formeditor/tool_widgeteditor.cpp new file mode 100644 index 00000000000..fd019b75cb3 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/tool_widgeteditor.cpp @@ -0,0 +1,343 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tool_widgeteditor.h" +#include "formwindow.h" + +// sdk +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +WidgetEditorTool::WidgetEditorTool(FormWindow *formWindow) + : QDesignerFormWindowToolInterface(formWindow), + m_formWindow(formWindow), + m_action(new QAction(tr("Edit Widgets"), this)), + m_specialDockDrag(false) +{ +} + +QAction *WidgetEditorTool::action() const +{ + return m_action; +} + +WidgetEditorTool::~WidgetEditorTool() = default; + +QDesignerFormEditorInterface *WidgetEditorTool::core() const +{ + return m_formWindow->core(); +} + +QDesignerFormWindowInterface *WidgetEditorTool::formWindow() const +{ + return m_formWindow; +} + +// separators in QMainWindow are no longer widgets +bool WidgetEditorTool::mainWindowSeparatorEvent(QWidget *widget, QEvent *event) +{ + QMainWindow *mw = qobject_cast(widget); + if (mw == nullptr) + return false; + + if (event->type() != QEvent::MouseButtonPress + && event->type() != QEvent::MouseMove + && event->type() != QEvent::MouseButtonRelease) + return false; + + QMouseEvent *e = static_cast(event); + + if (event->type() == QEvent::MouseButtonPress) { + if (mw->isSeparator(e->position().toPoint())) { + m_separator_drag_mw = mw; + return true; + } + return false; + } + + if (event->type() == QEvent::MouseMove) + return m_separator_drag_mw == mw; + + if (event->type() == QEvent::MouseButtonRelease) { + if (m_separator_drag_mw != mw) + return false; + m_separator_drag_mw = nullptr; + return true; + } + + return false; +} + +bool WidgetEditorTool::isPassiveInteractor(QWidget *widget, QEvent *event) +{ + auto *widgetFactory = core()->widgetFactory(); + return widgetFactory->isPassiveInteractor(widget) || mainWindowSeparatorEvent(widget, event); +} + +bool WidgetEditorTool::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + switch (event->type()) { + case QEvent::Resize: + case QEvent::Move: + m_formWindow->updateSelection(widget); + break; + + case QEvent::FocusOut: + case QEvent::FocusIn: // Popup cancelled over a form widget: Reset its focus frame + return widget != m_formWindow && widget != m_formWindow->mainContainer() + && !isPassiveInteractor(widget, event); + + case QEvent::Wheel: // Prevent spinboxes and combos from reacting + if (widget == m_formWindow->formContainer() || widget == m_formWindow + || widget == m_formWindow->mainContainer()) { // Allow scrolling the form with wheel. + return false; + } + return !isPassiveInteractor(widget, event); + + case QEvent::KeyPress: + return !isPassiveInteractor(widget, event) + && handleKeyPressEvent(widget, managedWidget, static_cast(event)); + + case QEvent::KeyRelease: + return !isPassiveInteractor(widget, event) + && handleKeyReleaseEvent(widget, managedWidget, static_cast(event)); + + case QEvent::MouseMove: + return !isPassiveInteractor(widget, event) + && handleMouseMoveEvent(widget, managedWidget, static_cast(event)); + + case QEvent::MouseButtonPress: + return !isPassiveInteractor(widget, event) + && handleMousePressEvent(widget, managedWidget, static_cast(event)); + + case QEvent::MouseButtonRelease: + return !isPassiveInteractor(widget, event) + && handleMouseReleaseEvent(widget, managedWidget, static_cast(event)); + + case QEvent::MouseButtonDblClick: + return !isPassiveInteractor(widget, event) + && handleMouseButtonDblClickEvent(widget, managedWidget, static_cast(event)); + + case QEvent::ContextMenu: + return !isPassiveInteractor(widget, event) + && handleContextMenu(widget, managedWidget, static_cast(event)); + + case QEvent::DragEnter: + return handleDragEnterMoveEvent(widget, managedWidget, static_cast(event), true); + case QEvent::DragMove: + return handleDragEnterMoveEvent(widget, managedWidget, static_cast(event), false); + case QEvent::DragLeave: + return handleDragLeaveEvent(widget, managedWidget, static_cast(event)); + case QEvent::Drop: + return handleDropEvent(widget, managedWidget, static_cast(event)); + default: + break; + + } // end switch + + return false; +} + +// ### remove me + +bool WidgetEditorTool::handleContextMenu(QWidget *widget, QWidget *managedWidget, QContextMenuEvent *e) +{ + return m_formWindow->handleContextMenu(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleMouseButtonDblClickEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e) +{ + return m_formWindow->handleMouseButtonDblClickEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleMousePressEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e) +{ + return m_formWindow->handleMousePressEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleMouseMoveEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e) +{ + return m_formWindow->handleMouseMoveEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleMouseReleaseEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e) +{ + return m_formWindow->handleMouseReleaseEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleKeyPressEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e) +{ + return m_formWindow->handleKeyPressEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handleKeyReleaseEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e) +{ + return m_formWindow->handleKeyReleaseEvent(widget, managedWidget, e); +} + +bool WidgetEditorTool::handlePaintEvent(QWidget *widget, QWidget *managedWidget, QPaintEvent *e) +{ + Q_UNUSED(widget); + Q_UNUSED(managedWidget); + Q_UNUSED(e); + + return false; +} + +void WidgetEditorTool::detectDockDrag(const QDesignerMimeData *mimeData) +{ + m_specialDockDrag = false; + if (!mimeData) + return; + + QMainWindow *mw = qobject_cast(m_formWindow->mainContainer()); + if (!mw) + return; + + const auto item_list = mimeData->items(); + + for (QDesignerDnDItemInterface *item : item_list) { + if (item->decoration() && item->decoration()->property("_q_dockDrag").toBool()) + m_specialDockDrag = true; + + } +} + +bool WidgetEditorTool::handleDragEnterMoveEvent(QWidget *widget, QWidget * /*managedWidget*/, QDragMoveEvent *e, bool isEnter) +{ + const QDesignerMimeData *mimeData = qobject_cast(e->mimeData()); + if (!mimeData) + return false; + + if (!m_formWindow->hasFeature(QDesignerFormWindowInterface::EditFeature)) { + e->ignore(); + return true; + } + + if (isEnter) + detectDockDrag(mimeData); + + + QPoint globalPos = QPoint(0, 0); + if (m_specialDockDrag) { + m_lastDropTarget = nullptr; + QMainWindow *mw = qobject_cast(m_formWindow->mainContainer()); + if (mw) + m_lastDropTarget = mw->centralWidget(); + } else { + // If custom widgets have acceptDrops=true, the event occurs for them + const QPoint formPos = widget != m_formWindow ? widget->mapTo(m_formWindow, e->position().toPoint()) : e->position().toPoint(); + globalPos = m_formWindow->mapToGlobal(formPos); + const FormWindowBase::WidgetUnderMouseMode wum = mimeData->items().size() == 1 ? FormWindowBase::FindSingleSelectionDropTarget : FormWindowBase::FindMultiSelectionDropTarget; + QWidget *dropTarget = m_formWindow->widgetUnderMouse(formPos, wum); + if (m_lastDropTarget && dropTarget != m_lastDropTarget) + m_formWindow->highlightWidget(m_lastDropTarget, m_lastDropTarget->mapFromGlobal(globalPos), FormWindow::Restore); + m_lastDropTarget = dropTarget; + } + + if (m_lastDropTarget) + m_formWindow->highlightWidget(m_lastDropTarget, m_lastDropTarget->mapFromGlobal(globalPos), FormWindow::Highlight); + + if (isEnter || m_lastDropTarget) + mimeData->acceptEvent(e); + else + e->ignore(); + return true; +} + +bool WidgetEditorTool::handleDropEvent(QWidget *widget, QWidget *, QDropEvent *e) +{ + const QDesignerMimeData *mimeData = qobject_cast(e->mimeData()); + if (!mimeData) + return false; + + if (!m_lastDropTarget || + !m_formWindow->hasFeature(QDesignerFormWindowInterface::EditFeature)) { + e->ignore(); + return true; + } + // FormWindow determines the position from the decoration. + const QPoint globalPos = widget->mapToGlobal(e->position().toPoint()); + mimeData->moveDecoration(globalPos); + if (m_specialDockDrag) { + if (!m_formWindow->dropDockWidget(mimeData->items().at(0), globalPos)) { + e->ignore(); + return true; + } + } else if (!m_formWindow->dropWidgets(mimeData->items(), m_lastDropTarget, globalPos)) { + e->ignore(); + return true; + } + mimeData->acceptEvent(e); + return true; +} + +bool WidgetEditorTool::restoreDropHighlighting() +{ + if (!m_lastDropTarget) + return false; + + m_formWindow->highlightWidget(m_lastDropTarget, m_lastDropTarget->mapFromGlobal(QCursor::pos()), FormWindow::Restore); + m_lastDropTarget = nullptr; + return true; +} + +bool WidgetEditorTool::handleDragLeaveEvent(QWidget *, QWidget *, QDragLeaveEvent *event) +{ + if (restoreDropHighlighting()) { + event->accept(); + return true; + } + return false; +} + +QWidget *WidgetEditorTool::editor() const +{ + Q_ASSERT(formWindow() != nullptr); + return formWindow()->mainContainer(); +} + +void WidgetEditorTool::activated() +{ + if (core()->widgetBox()) + core()->widgetBox()->setEnabled(true); + + if (m_formWindow == nullptr) + return; + + const QWidgetList &sel = m_formWindow->selectedWidgets(); + for (QWidget *w : sel) + m_formWindow->raiseSelection(w); +} + +void WidgetEditorTool::deactivated() +{ + if (core()->widgetBox()) + core()->widgetBox()->setEnabled(false); + + if (m_formWindow == nullptr) + return; + + m_formWindow->clearSelection(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/tool_widgeteditor.h b/src/tools/designer/src/components/formeditor/tool_widgeteditor.h new file mode 100644 index 00000000000..5e22224be19 --- /dev/null +++ b/src/tools/designer/src/components/formeditor/tool_widgeteditor.h @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TOOL_WIDGETEDITOR_H +#define TOOL_WIDGETEDITOR_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; +class QMainWindow; + +namespace qdesigner_internal { + +class FormWindow; +class QDesignerMimeData; + +class WidgetEditorTool: public QDesignerFormWindowToolInterface +{ + Q_OBJECT +public: + explicit WidgetEditorTool(FormWindow *formWindow); + ~WidgetEditorTool() override; + + QDesignerFormEditorInterface *core() const override; + QDesignerFormWindowInterface *formWindow() const override; + QWidget *editor() const override; + QAction *action() const override; + + void activated() override; + void deactivated() override; + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) override; + + bool handleContextMenu(QWidget *widget, QWidget *managedWidget, QContextMenuEvent *e); + bool handleMouseButtonDblClickEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMousePressEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMouseMoveEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleMouseReleaseEvent(QWidget *widget, QWidget *managedWidget, QMouseEvent *e); + bool handleKeyPressEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e); + bool handleKeyReleaseEvent(QWidget *widget, QWidget *managedWidget, QKeyEvent *e); + bool handlePaintEvent(QWidget *widget, QWidget *managedWidget, QPaintEvent *e); + + bool handleDragEnterMoveEvent(QWidget *widget, QWidget *managedWidget, QDragMoveEvent *e, bool isEnter); + bool handleDragLeaveEvent(QWidget *widget, QWidget *managedWidget, QDragLeaveEvent *e); + bool handleDropEvent(QWidget *widget, QWidget *managedWidget, QDropEvent *e); + +private: + bool restoreDropHighlighting(); + void detectDockDrag(const QDesignerMimeData *mimeData); + + FormWindow *m_formWindow; + QAction *m_action; + + bool mainWindowSeparatorEvent(QWidget *widget, QEvent *event); + bool isPassiveInteractor(QWidget *widget, QEvent *event); + QPointer m_separator_drag_mw; + QPointer m_lastDropTarget; + bool m_specialDockDrag; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TOOL_WIDGETEDITOR_H diff --git a/src/tools/designer/src/components/formeditor/widgetselection.cpp b/src/tools/designer/src/components/formeditor/widgetselection.cpp new file mode 100644 index 00000000000..75d8a5fb72e --- /dev/null +++ b/src/tools/designer/src/components/formeditor/widgetselection.cpp @@ -0,0 +1,720 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetselection.h" +#include "formwindow.h" +#include "formwindowmanager.h" + +// sdk +#include +#include + +// shared +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +enum { debugWidgetSelection = 0 }; + +// Return the layout the widget is in +template +static inline Layout *managedLayoutOf(const QDesignerFormEditorInterface *core, + QWidget *w, + const Layout * /* vs6dummy */ = nullptr) +{ + if (QWidget *p = w->parentWidget()) + if (QLayout *l = LayoutInfo::managedLayout(core, p)) + return qobject_cast(l); + return nullptr; +} + +// ----------- WidgetHandle +WidgetHandle::WidgetHandle(FormWindow *parent, WidgetHandle::Type t, WidgetSelection *s) : + InvisibleWidget(parent->formContainer()), + m_widget(nullptr), + m_type(t), + m_formWindow( parent), + m_sel(s), + m_active(true) +{ + setMouseTracking(false); + setAutoFillBackground(true); + + setBackgroundRole(m_active ? QPalette::Text : QPalette::Dark); + setFixedSize(6, 6); + + updateCursor(); +} + +void WidgetHandle::updateCursor() +{ +#if QT_CONFIG(cursor) + if (!m_active) { + setCursor(Qt::ArrowCursor); + return; + } + + switch (m_type) { + case LeftTop: + setCursor(Qt::SizeFDiagCursor); + break; + case Top: + setCursor(Qt::SizeVerCursor); + break; + case RightTop: + setCursor(Qt::SizeBDiagCursor); + break; + case Right: + setCursor(Qt::SizeHorCursor); + break; + case RightBottom: + setCursor(Qt::SizeFDiagCursor); + break; + case Bottom: + setCursor(Qt::SizeVerCursor); + break; + case LeftBottom: + setCursor(Qt::SizeBDiagCursor); + break; + case Left: + setCursor(Qt::SizeHorCursor); + break; + default: + Q_ASSERT(0); + } +#endif +} + +QDesignerFormEditorInterface *WidgetHandle::core() const +{ + if (m_formWindow) + return m_formWindow->core(); + + return nullptr; +} + +void WidgetHandle::setActive(bool a) +{ + m_active = a; + setBackgroundRole(m_active ? QPalette::Text : QPalette::Dark); + updateCursor(); +} + +void WidgetHandle::setWidget(QWidget *w) +{ + m_widget = w; +} + +void WidgetHandle::paintEvent(QPaintEvent *) +{ + QDesignerFormWindowManagerInterface *m = m_formWindow->core()->formWindowManager(); + + QStylePainter p(this); + if (m_formWindow->currentWidget() == m_widget) { + p.setPen(m->activeFormWindow() == m_formWindow ? Qt::blue : Qt::red); + p.drawRect(0, 0, width() - 1, height() - 1); + } +} + +void WidgetHandle::mousePressEvent(QMouseEvent *e) +{ + e->accept(); + + if (!m_formWindow->hasFeature(FormWindow::EditFeature)) + return; + + if (!(m_widget && e->button() == Qt::LeftButton)) + return; + + if (!(m_active)) + return; + + QWidget *container = m_widget->parentWidget(); + + m_origPressPos = container->mapFromGlobal(e->globalPosition().toPoint()); + m_geom = m_origGeom = m_widget->geometry(); + + switch (WidgetSelection::widgetState(m_formWindow->core(), m_widget)) { + case WidgetSelection::UnlaidOut: + case WidgetSelection::LaidOut: + m_formWindow->setHandleOperation(FormWindow::ResizeHandleOperation); + break; + case WidgetSelection::ManagedGridLayout: + case WidgetSelection::ManagedFormLayout: + m_formWindow->setHandleOperation(FormWindow::ChangeLayoutSpanHandleOperation); + break; + } +} + +void WidgetHandle::mouseMoveEvent(QMouseEvent *e) +{ + if (!(m_widget && m_active && e->buttons() & Qt::LeftButton)) + return; + + e->accept(); + + QWidget *container = m_widget->parentWidget(); + + const QPoint rp = container->mapFromGlobal(e->globalPosition().toPoint()); + const QPoint d = rp - m_origPressPos; + + const QRect pr = container->rect(); + + qdesigner_internal::Grid grid; + if (const qdesigner_internal::FormWindowBase *fwb = qobject_cast(m_formWindow)) + grid = fwb->designerGrid(); + + switch (m_type) { + + case LeftTop: { + if (rp.x() > pr.width() - 2 * width() || rp.y() > pr.height() - 2 * height()) + return; + + int w = m_origGeom.width() - d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + int h = m_origGeom.height() - d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + const int dx = m_widget->width() - w; + const int dy = m_widget->height() - h; + + trySetGeometry(m_widget, m_widget->x() + dx, m_widget->y() + dy, w, h); + } break; + + case Top: { + if (rp.y() > pr.height() - 2 * height()) + return; + + int h = m_origGeom.height() - d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + const int dy = m_widget->height() - h; + trySetGeometry(m_widget, m_widget->x(), m_widget->y() + dy, m_widget->width(), h); + } break; + + case RightTop: { + if (rp.x() < 2 * width() || rp.y() > pr.height() - 2 * height()) + return; + + int h = m_origGeom.height() - d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + const int dy = m_widget->height() - h; + + int w = m_origGeom.width() + d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + trySetGeometry(m_widget, m_widget->x(), m_widget->y() + dy, w, h); + } break; + + case Right: { + if (rp.x() < 2 * width()) + return; + + int w = m_origGeom.width() + d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + tryResize(m_widget, w, m_widget->height()); + } break; + + case RightBottom: { + if (rp.x() < 2 * width() || rp.y() < 2 * height()) + return; + + int w = m_origGeom.width() + d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + int h = m_origGeom.height() + d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + tryResize(m_widget, w, h); + } break; + + case Bottom: { + if (rp.y() < 2 * height()) + return; + + int h = m_origGeom.height() + d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + tryResize(m_widget, m_widget->width(), h); + } break; + + case LeftBottom: { + if (rp.x() > pr.width() - 2 * width() || rp.y() < 2 * height()) + return; + + int w = m_origGeom.width() - d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + int h = m_origGeom.height() + d.y(); + m_geom.setHeight(h); + h = grid.widgetHandleAdjustY(h); + + int dx = m_widget->width() - w; + + trySetGeometry(m_widget, m_widget->x() + dx, m_widget->y(), w, h); + } break; + + case Left: { + if (rp.x() > pr.width() - 2 * width()) + return; + + int w = m_origGeom.width() - d.x(); + m_geom.setWidth(w); + w = grid.widgetHandleAdjustX(w); + + const int dx = m_widget->width() - w; + + trySetGeometry(m_widget, m_widget->x() + dx, m_widget->y(), w, m_widget->height()); + } break; + + default: break; + + } // end switch + + m_sel->updateGeometry(); + + if (LayoutInfo::layoutType(m_formWindow->core(), m_widget) != LayoutInfo::NoLayout) + m_formWindow->updateChildSelections(m_widget); +} + +void WidgetHandle::mouseReleaseEvent(QMouseEvent *e) +{ + m_formWindow->setHandleOperation(FormWindow::NoHandleOperation); + + if (e->button() != Qt::LeftButton || !m_active) + return; + + e->accept(); + + if (!m_formWindow->hasFeature(FormWindow::EditFeature)) + return; + + switch (WidgetSelection::widgetState(m_formWindow->core(), m_widget)) { + case WidgetSelection::UnlaidOut: + if (m_geom != m_widget->geometry()) { + SetPropertyCommand *cmd = new SetPropertyCommand(m_formWindow); + cmd->init(m_widget, u"geometry"_s, m_widget->geometry()); + cmd->setOldValue(m_origGeom); + m_formWindow->commandHistory()->push(cmd); + m_formWindow->emitSelectionChanged(); + } + break; + case WidgetSelection::LaidOut: + break; + case WidgetSelection::ManagedGridLayout: + changeGridLayoutItemSpan(); + break; + case WidgetSelection::ManagedFormLayout: + changeFormLayoutItemSpan(); + break; + } +} + +// Match the left/right widget handle mouse movements to form layout span-changing operations +static inline int formLayoutLeftHandleOperation(int dx, unsigned possibleOperations) +{ + if (dx < 0) { + if (possibleOperations & ChangeFormLayoutItemRoleCommand::FieldToSpanning) + return ChangeFormLayoutItemRoleCommand::FieldToSpanning; + return 0; + } + if (possibleOperations & ChangeFormLayoutItemRoleCommand::SpanningToField) + return ChangeFormLayoutItemRoleCommand::SpanningToField; + return 0; +} + +static inline int formLayoutRightHandleOperation(int dx, unsigned possibleOperations) +{ + if (dx < 0) { + if (possibleOperations & ChangeFormLayoutItemRoleCommand::SpanningToLabel) + return ChangeFormLayoutItemRoleCommand::SpanningToLabel; + return 0; + } + if (possibleOperations & ChangeFormLayoutItemRoleCommand::LabelToSpanning) + return ChangeFormLayoutItemRoleCommand::LabelToSpanning; + return 0; +} + +// Change form layout item horizontal span +void WidgetHandle::changeFormLayoutItemSpan() +{ + QUndoCommand *cmd = nullptr; + // Figure out command according to the movement + const int dx = m_widget->geometry().center().x() - m_origGeom.center().x(); + if (qAbs(dx) >= QApplication::startDragDistance()) { + int operation = 0; + if (const unsigned possibleOperations = ChangeFormLayoutItemRoleCommand::possibleOperations(m_formWindow->core(), m_widget)) { + switch (m_type) { + case WidgetHandle::Left: + operation = formLayoutLeftHandleOperation(dx, possibleOperations); + break; + case WidgetHandle::Right: + operation = formLayoutRightHandleOperation(dx, possibleOperations); + break; + default: + break; + } + if (operation) { + ChangeFormLayoutItemRoleCommand *fcmd = new ChangeFormLayoutItemRoleCommand(m_formWindow); + fcmd->init(m_widget, static_cast(operation)); + cmd = fcmd; + } + } + } + if (cmd) { + m_formWindow->commandHistory()->push(cmd); + } else { + // Cancelled/Invalid. Restore the size of the widget. + if (QFormLayout *form = managedLayoutOf(m_formWindow->core(), m_widget)) { + form->invalidate(); + form->activate(); + m_formWindow->clearSelection(false); + m_formWindow->selectWidget(m_widget); + } + } +} + +void WidgetHandle::changeGridLayoutItemSpan() +{ + QDesignerLayoutDecorationExtension *deco = qt_extension(core()->extensionManager(), m_widget->parentWidget()); + if (!deco) + return; + QGridLayout *grid = managedLayoutOf(m_formWindow->core(), m_widget); + if (!grid) + return; + + const int index = deco->indexOf(m_widget); + const QRect info = deco->itemInfo(index); + const int top = deco->findItemAt(info.top() - 1, info.left()); + const int left = deco->findItemAt(info.top(), info.left() - 1); + const int bottom = deco->findItemAt(info.bottom() + 1, info.left()); + const int right = deco->findItemAt(info.top(), info.right() + 1); + + const QPoint pt = m_origGeom.center() - m_widget->geometry().center(); + + ChangeLayoutItemGeometry *cmd = nullptr; + + switch (m_type) { + default: + break; + + case WidgetHandle::Top: { + if (pt.y() < 0 && info.height() > 1) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y() + 1, info.x(), info.height() - 1, info.width()); + } else if (pt.y() > 0 && top != -1 && grid->itemAt(top)->spacerItem()) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y() - 1, info.x(), info.height() + 1, info.width()); + } + } + break; + + case WidgetHandle::Left: { + if (pt.x() < 0 && info.width() > 1) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x() + 1, info.height(), info.width() - 1); + } else if (pt.x() > 0 && left != -1 && grid->itemAt(left)->spacerItem()) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x() - 1, info.height(), info.width() + 1); + } + } + break; + + case WidgetHandle::Right: { + if (pt.x() > 0 && info.width() > 1) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x(), info.height(), info.width() - 1); + } else if (pt.x() < 0 && right != -1 && grid->itemAt(right)->spacerItem()) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x(), info.height(), info.width() + 1); + } + } + break; + + case WidgetHandle::Bottom: { + if (pt.y() > 0 && info.height() > 1) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x(), info.height() - 1, info.width()); + } else if (pt.y() < 0 && bottom != -1 && grid->itemAt(bottom)->spacerItem()) { + cmd = new ChangeLayoutItemGeometry(m_formWindow); + cmd->init(m_widget, info.y(), info.x(), info.height() + 1, info.width()); + } + } + break; + } + + if (cmd != nullptr) { + m_formWindow->commandHistory()->push(cmd); + } else { + grid->invalidate(); + grid->activate(); + m_formWindow->clearSelection(false); + m_formWindow->selectWidget(m_widget); + } +} + +void WidgetHandle::trySetGeometry(QWidget *w, int x, int y, int width, int height) +{ + if (!m_formWindow->hasFeature(FormWindow::EditFeature)) + return; + + int minw = w->minimumSize().width(); + minw = qMax(minw, 2 * m_formWindow->grid().x()); + + int minh = w->minimumSize().height(); + minh = qMax(minh, 2 * m_formWindow->grid().y()); + + if (qMax(minw, width) > w->maximumWidth() || + qMax(minh, height) > w->maximumHeight()) + return; + + if (width < minw && x != w->x()) + x -= minw - width; + + if (height < minh && y != w->y()) + y -= minh - height; + + w->setGeometry(x, y, qMax(minw, width), qMax(minh, height)); +} + +void WidgetHandle::tryResize(QWidget *w, int width, int height) +{ + int minw = w->minimumSize().width(); + minw = qMax(minw, 16); + + int minh = w->minimumSize().height(); + minh = qMax(minh, 16); + + w->resize(qMax(minw, width), qMax(minh, height)); +} + +// ------------------ WidgetSelection + +WidgetSelection::WidgetState WidgetSelection::widgetState(const QDesignerFormEditorInterface *core, QWidget *w) +{ + bool isManaged; + const LayoutInfo::Type lt = LayoutInfo::laidoutWidgetType(core, w, &isManaged); + if (lt == LayoutInfo::NoLayout) + return UnlaidOut; + if (!isManaged) + return LaidOut; + switch (lt) { + case LayoutInfo::Grid: + return ManagedGridLayout; + case LayoutInfo::Form: + return ManagedFormLayout; + default: + break; + } + return LaidOut; +} + +WidgetSelection::WidgetSelection(FormWindow *parent) : + m_widget(nullptr), + m_formWindow(parent) +{ + for (int i = WidgetHandle::LeftTop; i < WidgetHandle::TypeCount; ++i) + m_handles[i] = new WidgetHandle(m_formWindow, static_cast(i), this); + hide(); +} + +void WidgetSelection::setWidget(QWidget *w) +{ + if (m_widget != nullptr) + m_widget->removeEventFilter(this); + + if (w == nullptr) { + hide(); + m_widget = nullptr; + return; + } + + m_widget = w; + + m_widget->installEventFilter(this); + + updateActive(); + + updateGeometry(); + show(); +} + +void WidgetSelection::updateActive() +{ + const WidgetState ws = widgetState(m_formWindow->core(), m_widget); + bool active[WidgetHandle::TypeCount]; + std::fill(active, active + WidgetHandle::TypeCount, false); + // Determine active handles + switch (ws) { + case UnlaidOut: + std::fill(active, active + WidgetHandle::TypeCount, true); + break; + case ManagedGridLayout: // Grid: Allow changing span + active[WidgetHandle::Left] = active[WidgetHandle::Top] = active[WidgetHandle::Right] = active[WidgetHandle::Bottom] = true; + break; + case ManagedFormLayout: // Form: Allow changing column span + if (const unsigned operation = ChangeFormLayoutItemRoleCommand::possibleOperations(m_formWindow->core(), m_widget)) { + active[WidgetHandle::Left] = operation & (ChangeFormLayoutItemRoleCommand::SpanningToField|ChangeFormLayoutItemRoleCommand::FieldToSpanning); + active[WidgetHandle::Right] = operation & (ChangeFormLayoutItemRoleCommand::SpanningToLabel|ChangeFormLayoutItemRoleCommand::LabelToSpanning); + } + break; + default: + break; + } + + for (int i = WidgetHandle::LeftTop; i < WidgetHandle::TypeCount; ++i) + if (WidgetHandle *h = m_handles[i]) { + h->setWidget(m_widget); + h->setActive(active[i]); + } +} + +bool WidgetSelection::isUsed() const +{ + return m_widget != nullptr; +} + +void WidgetSelection::updateGeometry() +{ + if (!m_widget || !m_widget->parentWidget()) + return; + + QPoint p = m_widget->parentWidget()->mapToGlobal(m_widget->pos()); + p = m_formWindow->formContainer()->mapFromGlobal(p); + const QRect r(p, m_widget->size()); + + const int w = 6; + const int h = 6; + + for (int i = WidgetHandle::LeftTop; i < WidgetHandle::TypeCount; ++i) { + WidgetHandle *hndl = m_handles[ i ]; + if (!hndl) + continue; + switch (i) { + case WidgetHandle::LeftTop: + hndl->move(r.x() - w / 2, r.y() - h / 2); + break; + case WidgetHandle::Top: + hndl->move(r.x() + r.width() / 2 - w / 2, r.y() - h / 2); + break; + case WidgetHandle::RightTop: + hndl->move(r.x() + r.width() - w / 2, r.y() - h / 2); + break; + case WidgetHandle::Right: + hndl->move(r.x() + r.width() - w / 2, r.y() + r.height() / 2 - h / 2); + break; + case WidgetHandle::RightBottom: + hndl->move(r.x() + r.width() - w / 2, r.y() + r.height() - h / 2); + break; + case WidgetHandle::Bottom: + hndl->move(r.x() + r.width() / 2 - w / 2, r.y() + r.height() - h / 2); + break; + case WidgetHandle::LeftBottom: + hndl->move(r.x() - w / 2, r.y() + r.height() - h / 2); + break; + case WidgetHandle::Left: + hndl->move(r.x() - w / 2, r.y() + r.height() / 2 - h / 2); + break; + default: + break; + } + } +} + +void WidgetSelection::hide() +{ + for (WidgetHandle *h : m_handles) { + if (h) + h->hide(); + } +} + +void WidgetSelection::show() +{ + for (WidgetHandle *h : m_handles) { + if (h) { + h->show(); + h->raise(); + } + } +} + +void WidgetSelection::update() +{ + for (WidgetHandle *h : m_handles) { + if (h) + h->update(); + } +} + +QWidget *WidgetSelection::widget() const +{ + return m_widget; +} + +QDesignerFormEditorInterface *WidgetSelection::core() const +{ + if (m_formWindow) + return m_formWindow->core(); + + return nullptr; +} + +bool WidgetSelection::eventFilter(QObject *object, QEvent *event) +{ + if (object != widget()) + return false; + + switch (event->type()) { + default: break; + + case QEvent::Move: + case QEvent::Resize: + updateGeometry(); + break; + case QEvent::ZOrderChange: + show(); + break; + } // end switch + + return false; +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/formeditor/widgetselection.h b/src/tools/designer/src/components/formeditor/widgetselection.h new file mode 100644 index 00000000000..b5a6ae20ccd --- /dev/null +++ b/src/tools/designer/src/components/formeditor/widgetselection.h @@ -0,0 +1,107 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETSELECTION_H +#define WIDGETSELECTION_H + +#include "formeditor_global.h" +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QMouseEvent; +class QPaintEvent; + +namespace qdesigner_internal { + +class FormWindow; +class WidgetSelection; + +class QT_FORMEDITOR_EXPORT WidgetHandle: public InvisibleWidget +{ + Q_OBJECT +public: + enum Type + { + LeftTop, + Top, + RightTop, + Right, + RightBottom, + Bottom, + LeftBottom, + Left, + + TypeCount + }; + + WidgetHandle(FormWindow *parent, Type t, WidgetSelection *s); + void setWidget(QWidget *w); + void setActive(bool a); + void updateCursor(); + + void setEnabled(bool) {} + + QDesignerFormEditorInterface *core() const; + +protected: + void paintEvent(QPaintEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + +private: + void changeGridLayoutItemSpan(); + void changeFormLayoutItemSpan(); + void trySetGeometry(QWidget *w, int x, int y, int width, int height); + void tryResize(QWidget *w, int width, int height); + +private: + QWidget *m_widget; + const Type m_type; + QPoint m_origPressPos; + FormWindow *m_formWindow; + WidgetSelection *m_sel; + QRect m_geom, m_origGeom; + bool m_active; +}; + +class QT_FORMEDITOR_EXPORT WidgetSelection: public QObject +{ + Q_OBJECT +public: + WidgetSelection(FormWindow *parent); + + void setWidget(QWidget *w); + bool isUsed() const; + + void updateActive(); + void updateGeometry(); + void hide(); + void show(); + void update(); + + QWidget *widget() const; + + QDesignerFormEditorInterface *core() const; + + bool eventFilter(QObject *object, QEvent *event) override; + + enum WidgetState { UnlaidOut, LaidOut, ManagedGridLayout, ManagedFormLayout }; + static WidgetState widgetState(const QDesignerFormEditorInterface *core, QWidget *w); + +private: + WidgetHandle *m_handles[WidgetHandle::TypeCount]; + QPointer m_widget; + FormWindow *m_formWindow; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETSELECTION_H diff --git a/src/tools/designer/src/components/lib/CMakeLists.txt b/src/tools/designer/src/components/lib/CMakeLists.txt new file mode 100644 index 00000000000..71096fcef79 --- /dev/null +++ b/src/tools/designer/src/components/lib/CMakeLists.txt @@ -0,0 +1,437 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + +##################################################################### +## DesignerComponentsPrivate Module: +##################################################################### + +qt_internal_add_module(DesignerComponentsPrivate + INTERNAL_MODULE + SOURCES + lib_pch.h + ../buddyeditor/buddyeditor.cpp ../buddyeditor/buddyeditor.h + ../buddyeditor/buddyeditor_global.h + ../buddyeditor/buddyeditor_plugin.cpp ../buddyeditor/buddyeditor_plugin.h + ../buddyeditor/buddyeditor_tool.cpp ../buddyeditor/buddyeditor_tool.h + ../formeditor/default_actionprovider.cpp ../formeditor/default_actionprovider.h + ../formeditor/default_container.cpp ../formeditor/default_container.h + ../formeditor/default_layoutdecoration.cpp ../formeditor/default_layoutdecoration.h + ../formeditor/deviceprofiledialog.cpp ../formeditor/deviceprofiledialog.h + ../formeditor/dpi_chooser.cpp ../formeditor/dpi_chooser.h + ../formeditor/embeddedoptionspage.cpp ../formeditor/embeddedoptionspage.h + ../formeditor/formeditor.cpp ../formeditor/formeditor.h + ../formeditor/formeditor_global.h + ../formeditor/formeditor_optionspage.cpp ../formeditor/formeditor_optionspage.h + ../formeditor/formwindow.cpp ../formeditor/formwindow.h + ../formeditor/formwindow_dnditem.cpp ../formeditor/formwindow_dnditem.h + ../formeditor/formwindow_widgetstack.cpp ../formeditor/formwindow_widgetstack.h + ../formeditor/formwindowcursor.cpp ../formeditor/formwindowcursor.h + ../formeditor/formwindowmanager.cpp ../formeditor/formwindowmanager.h + ../formeditor/formwindowsettings.cpp ../formeditor/formwindowsettings.h + ../formeditor/itemview_propertysheet.cpp ../formeditor/itemview_propertysheet.h + ../formeditor/layout_propertysheet.cpp ../formeditor/layout_propertysheet.h + ../formeditor/line_propertysheet.cpp ../formeditor/line_propertysheet.h + ../formeditor/previewactiongroup.cpp ../formeditor/previewactiongroup.h + ../formeditor/qdesigner_resource.cpp ../formeditor/qdesigner_resource.h + ../formeditor/qlayoutwidget_propertysheet.cpp ../formeditor/qlayoutwidget_propertysheet.h + ../formeditor/qmainwindow_container.cpp ../formeditor/qmainwindow_container.h + ../formeditor/qmdiarea_container.cpp ../formeditor/qmdiarea_container.h + ../formeditor/qwizard_container.cpp ../formeditor/qwizard_container.h + ../formeditor/spacer_propertysheet.cpp ../formeditor/spacer_propertysheet.h + ../formeditor/templateoptionspage.cpp ../formeditor/templateoptionspage.h + ../formeditor/tool_widgeteditor.cpp ../formeditor/tool_widgeteditor.h + ../formeditor/widgetselection.cpp ../formeditor/widgetselection.h + ../objectinspector/objectinspector.cpp ../objectinspector/objectinspector.h + ../objectinspector/objectinspector_global.h + ../objectinspector/objectinspectormodel.cpp ../objectinspector/objectinspectormodel_p.h + ../propertyeditor/brushpropertymanager.cpp ../propertyeditor/brushpropertymanager.h + ../propertyeditor/designerpropertymanager.cpp ../propertyeditor/designerpropertymanager.h + ../propertyeditor/fontpropertymanager.cpp ../propertyeditor/fontpropertymanager.h + ../propertyeditor/newdynamicpropertydialog.cpp ../propertyeditor/newdynamicpropertydialog.h + ../propertyeditor/paletteeditor.cpp ../propertyeditor/paletteeditor.h + ../propertyeditor/paletteeditorbutton.cpp ../propertyeditor/paletteeditorbutton.h + ../propertyeditor/pixmapeditor.cpp ../propertyeditor/pixmapeditor.h + ../propertyeditor/previewframe.cpp ../propertyeditor/previewframe.h + ../propertyeditor/previewwidget.cpp ../propertyeditor/previewwidget.h + ../propertyeditor/propertyeditor.cpp ../propertyeditor/propertyeditor.h + ../propertyeditor/propertyeditor_global.h + ../propertyeditor/qlonglongvalidator.cpp ../propertyeditor/qlonglongvalidator.h + ../propertyeditor/stringlisteditor.cpp ../propertyeditor/stringlisteditor.h + ../propertyeditor/stringlisteditorbutton.cpp ../propertyeditor/stringlisteditorbutton.h + ../signalsloteditor/connectdialog.cpp ../signalsloteditor/connectdialog_p.h + ../signalsloteditor/signalslot_utils.cpp ../signalsloteditor/signalslot_utils_p.h + ../signalsloteditor/signalsloteditor.cpp ../signalsloteditor/signalsloteditor.h ../signalsloteditor/signalsloteditor_p.h + ../signalsloteditor/signalsloteditor_global.h + ../signalsloteditor/signalsloteditor_plugin.cpp ../signalsloteditor/signalsloteditor_plugin.h + ../signalsloteditor/signalsloteditor_tool.cpp ../signalsloteditor/signalsloteditor_tool.h + ../signalsloteditor/signalsloteditorwindow.cpp ../signalsloteditor/signalsloteditorwindow.h + ../tabordereditor/tabordereditor.cpp ../tabordereditor/tabordereditor.h + ../tabordereditor/tabordereditor_global.h + ../tabordereditor/tabordereditor_plugin.cpp ../tabordereditor/tabordereditor_plugin.h + ../tabordereditor/tabordereditor_tool.cpp ../tabordereditor/tabordereditor_tool.h + ../taskmenu/button_taskmenu.cpp ../taskmenu/button_taskmenu.h + ../taskmenu/combobox_taskmenu.cpp ../taskmenu/combobox_taskmenu.h + ../taskmenu/containerwidget_taskmenu.cpp ../taskmenu/containerwidget_taskmenu.h + ../taskmenu/groupbox_taskmenu.cpp ../taskmenu/groupbox_taskmenu.h + ../taskmenu/inplace_editor.cpp ../taskmenu/inplace_editor.h + ../taskmenu/inplace_widget_helper.cpp ../taskmenu/inplace_widget_helper.h + ../taskmenu/itemlisteditor.cpp ../taskmenu/itemlisteditor.h + ../taskmenu/label_taskmenu.cpp ../taskmenu/label_taskmenu.h + ../taskmenu/layouttaskmenu.cpp ../taskmenu/layouttaskmenu.h + ../taskmenu/lineedit_taskmenu.cpp ../taskmenu/lineedit_taskmenu.h + ../taskmenu/listwidget_taskmenu.cpp ../taskmenu/listwidget_taskmenu.h + ../taskmenu/listwidgeteditor.cpp ../taskmenu/listwidgeteditor.h + ../taskmenu/menutaskmenu.cpp ../taskmenu/menutaskmenu.h + ../taskmenu/tablewidget_taskmenu.cpp ../taskmenu/tablewidget_taskmenu.h + ../taskmenu/tablewidgeteditor.cpp ../taskmenu/tablewidgeteditor.h + ../taskmenu/taskmenu_component.cpp ../taskmenu/taskmenu_component.h + ../taskmenu/textedit_taskmenu.cpp ../taskmenu/textedit_taskmenu.h + ../taskmenu/toolbar_taskmenu.cpp ../taskmenu/toolbar_taskmenu.h + ../taskmenu/treewidget_taskmenu.cpp ../taskmenu/treewidget_taskmenu.h + ../taskmenu/treewidgeteditor.cpp ../taskmenu/treewidgeteditor.h + ../widgetbox/widgetbox.cpp ../widgetbox/widgetbox.h + ../widgetbox/widgetbox_dnditem.cpp ../widgetbox/widgetbox_dnditem.h + ../widgetbox/widgetbox_global.h + ../widgetbox/widgetboxcategorylistview.cpp ../widgetbox/widgetboxcategorylistview.h + ../widgetbox/widgetboxtreewidget.cpp ../widgetbox/widgetboxtreewidget.h + qdesigner_components.cpp + NO_UNITY_BUILD_SOURCES + ../tabordereditor/tabordereditor.cpp # redefinition of 'QMetaTypeId>' (from qdesigner_resource.cpp) + ../formeditor/formwindow.cpp # explicit specialization of 'QMetaTypeId' after instantiation + DEFINES + QDESIGNER_COMPONENTS_LIBRARY + QT_STATICPLUGIN + INCLUDE_DIRECTORIES + . + .. + ../../../../../shared/tools/shared/qtpropertybrowser + ../../lib/components + ../../lib/extension + ../../lib/sdk + ../../lib/shared + ../buddyeditor + ../formeditor + ../objectinspector + ../propertyeditor ../propertyeditor + ../signalsloteditor + ../tabordereditor + ../taskmenu + ../widgetbox + LIBRARIES + Qt::Xml + PUBLIC_LIBRARIES + Qt::Core + Qt::DesignerPrivate + Qt::GuiPrivate + Qt::WidgetsPrivate + Qt::Xml + ENABLE_AUTOGEN_TOOLS + uic + PRECOMPILED_HEADER + "lib_pch.h" + NO_GENERATE_CPP_EXPORTS +) + +set(ui_sources + ../formeditor/deviceprofiledialog.ui + ../formeditor/formwindowsettings.ui + ../formeditor/templateoptionspage.ui + ../propertyeditor/newdynamicpropertydialog.ui + ../propertyeditor/paletteeditor.ui + ../propertyeditor/previewwidget.ui + ../propertyeditor/stringlisteditor.ui + ../signalsloteditor/connectdialog.ui + ../taskmenu/itemlisteditor.ui + ../taskmenu/tablewidgeteditor.ui + ../taskmenu/treewidgeteditor.ui +) + +# Work around QTBUG-95305 +if(CMAKE_GENERATOR STREQUAL "Ninja Multi-Config" AND CMAKE_CROSS_CONFIGS) + qt6_wrap_ui(ui_sources_processed ${ui_sources}) +else() + set(ui_sources_processed ${ui_sources}) +endif() +target_sources(DesignerComponentsPrivate PRIVATE ${ui_sources_processed}) + +# Resources: +set(propertyeditor_resource_files + "../propertyeditor/fontmapping.xml" +) + +qt_internal_add_resource(DesignerComponentsPrivate "propertyeditor" + PREFIX + "/qt-project.org/propertyeditor" + BASE + "../propertyeditor" + FILES + ${propertyeditor_resource_files} +) +set(formeditor_resource_files + "../formeditor/images/color.png" + "../formeditor/images/configure.png" + "../formeditor/images/downplus.png" + "../formeditor/images/dropdownbutton.png" + "../formeditor/images/edit.png" + "../formeditor/images/editdelete-16.png" + "../formeditor/images/emptyicon.png" + "../formeditor/images/filenew-16.png" + "../formeditor/images/fileopen-16.png" + "../formeditor/images/leveldown.png" + "../formeditor/images/levelup.png" + "../formeditor/images/mac/adjustsize.png" + "../formeditor/images/mac/back.png" + "../formeditor/images/mac/buddytool.png" + "../formeditor/images/mac/down.png" + "../formeditor/images/mac/editbreaklayout.png" + "../formeditor/images/mac/editcopy.png" + "../formeditor/images/mac/editcut.png" + "../formeditor/images/mac/editdelete.png" + "../formeditor/images/mac/editform.png" + "../formeditor/images/mac/editgrid.png" + "../formeditor/images/mac/edithlayout.png" + "../formeditor/images/mac/edithlayoutsplit.png" + "../formeditor/images/mac/editlower.png" + "../formeditor/images/mac/editpaste.png" + "../formeditor/images/mac/editraise.png" + "../formeditor/images/mac/editvlayout.png" + "../formeditor/images/mac/editvlayoutsplit.png" + "../formeditor/images/mac/filenew.png" + "../formeditor/images/mac/fileopen.png" + "../formeditor/images/mac/filesave.png" + "../formeditor/images/mac/forward.png" + "../formeditor/images/mac/insertimage.png" + "../formeditor/images/mac/minus.png" + "../formeditor/images/mac/plus.png" + "../formeditor/images/mac/redo.png" + "../formeditor/images/mac/signalslottool.png" + "../formeditor/images/mac/simplifyrichtext.png" + "../formeditor/images/mac/tabordertool.png" + "../formeditor/images/mac/textanchor.png" + "../formeditor/images/mac/textbold.png" + "../formeditor/images/mac/textcenter.png" + "../formeditor/images/mac/textitalic.png" + "../formeditor/images/mac/textjustify.png" + "../formeditor/images/mac/textleft.png" + "../formeditor/images/mac/textright.png" + "../formeditor/images/mac/textsubscript.png" + "../formeditor/images/mac/textsuperscript.png" + "../formeditor/images/mac/textunder.png" + "../formeditor/images/mac/undo.png" + "../formeditor/images/mac/up.png" + "../formeditor/images/mac/widgettool.png" + "../formeditor/images/minus-16.png" + "../formeditor/images/prefix-add.png" + "../formeditor/images/qtlogo128x128.png" + "../formeditor/images/qtlogo16x16.png" + "../formeditor/images/qtlogo24x24.png" + "../formeditor/images/qtlogo32x32.png" + "../formeditor/images/qtlogo64x64.png" + "../formeditor/images/reload.png" + "../formeditor/images/resetproperty.png" + "../formeditor/images/righttoleft.png" + "../formeditor/images/sort.png" + "../formeditor/images/submenu.png" + "../formeditor/images/widgets/calendarwidget.png" + "../formeditor/images/widgets/checkbox.png" + "../formeditor/images/widgets/columnview.png" + "../formeditor/images/widgets/combobox.png" + "../formeditor/images/widgets/commandlinkbutton.png" + "../formeditor/images/widgets/dateedit.png" + "../formeditor/images/widgets/datetimeedit.png" + "../formeditor/images/widgets/dial.png" + "../formeditor/images/widgets/dialogbuttonbox.png" + "../formeditor/images/widgets/dockwidget.png" + "../formeditor/images/widgets/doublespinbox.png" + "../formeditor/images/widgets/fontcombobox.png" + "../formeditor/images/widgets/frame.png" + "../formeditor/images/widgets/graphicsview.png" + "../formeditor/images/widgets/groupbox.png" + "../formeditor/images/widgets/hscrollbar.png" + "../formeditor/images/widgets/hslider.png" + "../formeditor/images/widgets/label.png" + "../formeditor/images/widgets/lcdnumber.png" + "../formeditor/images/widgets/line.png" + "../formeditor/images/widgets/lineedit.png" + "../formeditor/images/widgets/listbox.png" + "../formeditor/images/widgets/listview.png" + "../formeditor/images/widgets/mdiarea.png" + "../formeditor/images/widgets/plaintextedit.png" + "../formeditor/images/widgets/progress.png" + "../formeditor/images/widgets/pushbutton.png" + "../formeditor/images/widgets/radiobutton.png" + "../formeditor/images/widgets/scrollarea.png" + "../formeditor/images/widgets/spacer.png" + "../formeditor/images/widgets/spinbox.png" + "../formeditor/images/widgets/table.png" + "../formeditor/images/widgets/tabwidget.png" + "../formeditor/images/widgets/textedit.png" + "../formeditor/images/widgets/timeedit.png" + "../formeditor/images/widgets/toolbox.png" + "../formeditor/images/widgets/toolbutton.png" + "../formeditor/images/widgets/vline.png" + "../formeditor/images/widgets/vscrollbar.png" + "../formeditor/images/widgets/vslider.png" + "../formeditor/images/widgets/vspacer.png" + "../formeditor/images/widgets/widget.png" + "../formeditor/images/widgets/widgetstack.png" + "../formeditor/images/win/adjustsize.png" + "../formeditor/images/win/back.png" + "../formeditor/images/win/buddytool.png" + "../formeditor/images/win/down.png" + "../formeditor/images/win/editbreaklayout.png" + "../formeditor/images/win/editcopy.png" + "../formeditor/images/win/editcut.png" + "../formeditor/images/win/editdelete.png" + "../formeditor/images/win/editform.png" + "../formeditor/images/win/editgrid.png" + "../formeditor/images/win/edithlayout.png" + "../formeditor/images/win/edithlayoutsplit.png" + "../formeditor/images/win/editlower.png" + "../formeditor/images/win/editpaste.png" + "../formeditor/images/win/editraise.png" + "../formeditor/images/win/editvlayout.png" + "../formeditor/images/win/editvlayoutsplit.png" + "../formeditor/images/win/filenew.png" + "../formeditor/images/win/fileopen.png" + "../formeditor/images/win/filesave.png" + "../formeditor/images/win/forward.png" + "../formeditor/images/win/insertimage.png" + "../formeditor/images/win/minus.png" + "../formeditor/images/win/plus.png" + "../formeditor/images/win/redo.png" + "../formeditor/images/win/signalslottool.png" + "../formeditor/images/win/simplifyrichtext.png" + "../formeditor/images/win/tabordertool.png" + "../formeditor/images/win/textanchor.png" + "../formeditor/images/win/textbold.png" + "../formeditor/images/win/textcenter.png" + "../formeditor/images/win/textitalic.png" + "../formeditor/images/win/textjustify.png" + "../formeditor/images/win/textleft.png" + "../formeditor/images/win/textright.png" + "../formeditor/images/win/textsubscript.png" + "../formeditor/images/win/textsuperscript.png" + "../formeditor/images/win/textunder.png" + "../formeditor/images/win/undo.png" + "../formeditor/images/win/up.png" + "../formeditor/images/win/widgettool.png" +) + +qt_internal_add_resource(DesignerComponentsPrivate "formeditor" + PREFIX + "/qt-project.org/formeditor" + BASE + "../formeditor" + FILES + ${formeditor_resource_files} +) +set(formeditor1_resource_files + "../formeditor/defaultbrushes.xml" +) + +qt_internal_add_resource(DesignerComponentsPrivate "formeditor1" + PREFIX + "/qt-project.org/brushes" + BASE + "../formeditor" + FILES + ${formeditor1_resource_files} +) +set(widgetbox_resource_files + "../widgetbox/widgetbox.xml" +) + +qt_internal_add_resource(DesignerComponentsPrivate "widgetbox" + PREFIX + "/qt-project.org/widgetbox" + BASE + "../widgetbox" + FILES + ${widgetbox_resource_files} +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(DesignerComponentsPrivate CONDITION NOT QT_BUILD_SHARED_LIBS + DEFINES + QT_DESIGNER_STATIC + INCLUDE_DIRECTORIES + ../../../../../shared/findwidget + ../../../../../shared/qtgradienteditor + ../../../../../shared/qtpropertybrowser +) + +qt_internal_extend_target(DesignerComponentsPrivate CONDITION QT_BUILD_SHARED_LIBS + SOURCES + ../../../../../shared/findwidget/abstractfindwidget.cpp ../../../../../shared/findwidget/abstractfindwidget_p.h + ../../../../../shared/findwidget/itemviewfindwidget.cpp ../../../../../shared/findwidget/itemviewfindwidget_p.h + ../../../../../shared/findwidget/texteditfindwidget.cpp ../../../../../shared/findwidget/texteditfindwidget_p.h + ../../../../../shared/qtgradienteditor/qtcolorbutton.cpp ../../../../../shared/qtgradienteditor/qtcolorbutton_p.h + ../../../../../shared/qtpropertybrowser/qtbuttonpropertybrowser.cpp ../../../../../shared/qtpropertybrowser/qtbuttonpropertybrowser_p.h + ../../../../../shared/qtpropertybrowser/qteditorfactory.cpp ../../../../../shared/qtpropertybrowser/qteditorfactory_p.h + ../../../../../shared/qtpropertybrowser/qtgroupboxpropertybrowser.cpp ../../../../../shared/qtpropertybrowser/qtgroupboxpropertybrowser_p.h + ../../../../../shared/qtpropertybrowser/qtpropertybrowser.cpp ../../../../../shared/qtpropertybrowser/qtpropertybrowser_p.h + ../../../../../shared/qtpropertybrowser/qtpropertybrowserutils.cpp ../../../../../shared/qtpropertybrowser/qtpropertybrowserutils_p.h + ../../../../../shared/qtpropertybrowser/qtpropertymanager.cpp ../../../../../shared/qtpropertybrowser/qtpropertymanager_p.h + ../../../../../shared/qtpropertybrowser/qttreepropertybrowser.cpp ../../../../../shared/qtpropertybrowser/qttreepropertybrowser_p.h + ../../../../../shared/qtpropertybrowser/qtvariantproperty.cpp ../../../../../shared/qtpropertybrowser/qtvariantproperty_p.h + INCLUDE_DIRECTORIES + ../../../../../shared/findwidget + ../../../../../shared/qtgradienteditor + ../../../../../shared/qtpropertybrowser +) + +if(QT_BUILD_SHARED_LIBS) + # Resources: + set(findwidget_resource_files + "../../../../../shared/findwidget/images/mac/closetab.png" + "../../../../../shared/findwidget/images/mac/next.png" + "../../../../../shared/findwidget/images/mac/previous.png" + "../../../../../shared/findwidget/images/mac/searchfind.png" + "../../../../../shared/findwidget/images/win/closetab.png" + "../../../../../shared/findwidget/images/win/next.png" + "../../../../../shared/findwidget/images/win/previous.png" + "../../../../../shared/findwidget/images/win/searchfind.png" + "../../../../../shared/findwidget/images/wrap.png" + ) + + qt_internal_add_resource(DesignerComponentsPrivate "findwidget" + PREFIX + "/qt-project.org/shared" + BASE + "../../../../../shared/findwidget" + FILES + ${findwidget_resource_files} + ) + set(qtpropertybrowser_resource_files + "../../../../../shared/qtpropertybrowser/images/cursor-arrow.png" + "../../../../../shared/qtpropertybrowser/images/cursor-busy.png" + "../../../../../shared/qtpropertybrowser/images/cursor-closedhand.png" + "../../../../../shared/qtpropertybrowser/images/cursor-cross.png" + "../../../../../shared/qtpropertybrowser/images/cursor-forbidden.png" + "../../../../../shared/qtpropertybrowser/images/cursor-hand.png" + "../../../../../shared/qtpropertybrowser/images/cursor-hsplit.png" + "../../../../../shared/qtpropertybrowser/images/cursor-ibeam.png" + "../../../../../shared/qtpropertybrowser/images/cursor-openhand.png" + "../../../../../shared/qtpropertybrowser/images/cursor-sizeall.png" + "../../../../../shared/qtpropertybrowser/images/cursor-sizeb.png" + "../../../../../shared/qtpropertybrowser/images/cursor-sizef.png" + "../../../../../shared/qtpropertybrowser/images/cursor-sizeh.png" + "../../../../../shared/qtpropertybrowser/images/cursor-sizev.png" + "../../../../../shared/qtpropertybrowser/images/cursor-uparrow.png" + "../../../../../shared/qtpropertybrowser/images/cursor-vsplit.png" + "../../../../../shared/qtpropertybrowser/images/cursor-wait.png" + "../../../../../shared/qtpropertybrowser/images/cursor-whatsthis.png" + ) + + qt_internal_add_resource(DesignerComponentsPrivate "qtpropertybrowser" + PREFIX + "/qt-project.org/qtpropertybrowser" + BASE + "../../../../../shared/qtpropertybrowser" + FILES + ${qtpropertybrowser_resource_files} + ) +endif() diff --git a/src/tools/designer/src/components/lib/lib_pch.h b/src/tools/designer/src/components/lib/lib_pch.h new file mode 100644 index 00000000000..6befeb75122 --- /dev/null +++ b/src/tools/designer/src/components/lib/lib_pch.h @@ -0,0 +1,7 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#if defined __cplusplus +#include +#include +#endif diff --git a/src/tools/designer/src/components/lib/qdesigner_components.cpp b/src/tools/designer/src/components/lib/qdesigner_components.cpp new file mode 100644 index 00000000000..02e38a24b47 --- /dev/null +++ b/src/tools/designer/src/components/lib/qdesigner_components.cpp @@ -0,0 +1,255 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "qtresourceview_p.h" +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define INIT_PLUGIN_INSTANCE(PLUGIN) \ + do { \ + Static##PLUGIN##PluginInstance instance; \ + Q_UNUSED(instance); \ + } while (0) + +Q_IMPORT_PLUGIN(SignalSlotEditorPlugin) +Q_IMPORT_PLUGIN(BuddyEditorPlugin) +Q_IMPORT_PLUGIN(TabOrderEditorPlugin) + +static void initResources() +{ + // Q_INIT_RESOURCE only usable in functions in global namespace + Q_INIT_RESOURCE(formeditor); + Q_INIT_RESOURCE(widgetbox); + Q_INIT_RESOURCE(propertyeditor); +} + + +static void initInstances() +{ + static bool plugins_initialized = false; + + if (!plugins_initialized) { + INIT_PLUGIN_INSTANCE(SignalSlotEditorPlugin); + INIT_PLUGIN_INSTANCE(BuddyEditorPlugin); + INIT_PLUGIN_INSTANCE(TabOrderEditorPlugin); + plugins_initialized = true; + } +} + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +/*! + \class QDesignerComponents + \brief The QDesignerComponents class provides a central resource for the various components + used in the \QD user interface. + \inmodule QtDesigner + \internal + + The QDesignerComponents class is a factory for each of the standard components present + in the \QD user interface. It is mostly useful for developers who want to implement + a standalone form editing environment using \QD's components, or who need to integrate + \QD's components into an existing integrated development environment (IDE). + + \sa QDesignerFormEditorInterface, QDesignerObjectInspectorInterface, + QDesignerPropertyEditorInterface, QDesignerWidgetBoxInterface +*/ + +/*! + Initializes the resources used by the components.*/ +void QDesignerComponents::initializeResources() +{ + initResources(); +} + +/*! + Initializes the plugins used by the components.*/ +void QDesignerComponents::initializePlugins(QDesignerFormEditorInterface *core) +{ + QDesignerIntegration::initializePlugins(core); +} + +// ### fixme Qt 7 createFormEditorWithPluginPaths->createFormEditor + +/*! + Constructs a form editor interface with the given \a parent.*/ +QDesignerFormEditorInterface *QDesignerComponents::createFormEditor(QObject *parent) +{ + return createFormEditorWithPluginPaths({}, parent); +} + +/*! + Constructs a form editor interface with the given \a pluginPaths and the \a parent. + \since 6.7 +*/ +QDesignerFormEditorInterface * + QDesignerComponents::createFormEditorWithPluginPaths(const QStringList &pluginPaths, + QObject *parent) +{ + initInstances(); + return new qdesigner_internal::FormEditor(pluginPaths, parent); +} + +/*! + Returns a new task menu with the given \a parent for the \a core interface.*/ +QObject *QDesignerComponents::createTaskMenu(QDesignerFormEditorInterface *core, QObject *parent) +{ + return new qdesigner_internal::TaskMenuComponent(core, parent); +} + +static inline int qtMajorVersion(int qtVersion) { return qtVersion >> 16; } +static inline int qtMinorVersion(int qtVersion) { return (qtVersion >> 8) & 0xFF; } +static inline void setMinorVersion(int minorVersion, int *qtVersion) +{ + *qtVersion &= ~0xFF00; + *qtVersion |= minorVersion << 8; +} + +// Build the version-dependent name of the user widget box file, '$HOME.designer/widgetbox4.4.xml' +static inline QString widgetBoxFileName(int qtVersion, const QDesignerLanguageExtension *lang = nullptr) +{ + QString rc; { + QTextStream str(&rc); + str << QDir::homePath() << QDir::separator() << ".designer" << QDir::separator() + << "widgetbox"; + // The naming convention using the version was introduced with 4.4 + const int major = qtMajorVersion(qtVersion); + const int minor = qtMinorVersion(qtVersion); + if (major >= 4 && minor >= 4) + str << major << '.' << minor; + if (lang) + str << '.' << lang->uiExtension(); + str << ".xml"; + } + return rc; +} + +/*! + Returns a new widget box interface with the given \a parent for the \a core interface.*/ +QDesignerWidgetBoxInterface *QDesignerComponents::createWidgetBox(QDesignerFormEditorInterface *core, QWidget *parent) +{ + qdesigner_internal::WidgetBox *widgetBox = new qdesigner_internal::WidgetBox(core, parent); + + const QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core); + + do { + if (lang) { + const QString languageWidgetBox = lang->widgetBoxContents(); + if (!languageWidgetBox.isEmpty()) { + widgetBox->loadContents(lang->widgetBoxContents()); + break; + } + } + + widgetBox->setFileName(u":/qt-project.org/widgetbox/widgetbox.xml"_s); + widgetBox->load(); + } while (false); + + const QString userWidgetBoxFile = widgetBoxFileName(QT_VERSION, lang); + + widgetBox->setFileName(userWidgetBoxFile); + if (!QFileInfo::exists(userWidgetBoxFile)) { + // check previous version, that is, are we running the new version for the first time + // If so, try to copy the old widget box file + if (const int minv = qtMinorVersion(QT_VERSION)) { + int oldVersion = QT_VERSION; + setMinorVersion(minv - 1, &oldVersion); + const QString oldWidgetBoxFile = widgetBoxFileName(oldVersion, lang); + if (QFileInfo::exists(oldWidgetBoxFile)) + QFile::copy(oldWidgetBoxFile, userWidgetBoxFile); + } + } + widgetBox->load(); + + return widgetBox; +} + +/*! + Returns a new property editor interface with the given \a parent for the \a core interface.*/ +QDesignerPropertyEditorInterface *QDesignerComponents::createPropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::PropertyEditor(core, parent); +} + +/*! + Returns a new object inspector interface with the given \a parent for the \a core interface.*/ +QDesignerObjectInspectorInterface *QDesignerComponents::createObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::ObjectInspector(core, parent); +} + +/*! + Returns a new action editor interface with the given \a parent for the \a core interface.*/ +QDesignerActionEditorInterface *QDesignerComponents::createActionEditor(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::ActionEditor(core, parent); +} + +/*! + Returns a new resource editor with the given \a parent for the \a core interface.*/ +QWidget *QDesignerComponents::createResourceEditor(QDesignerFormEditorInterface *core, QWidget *parent) +{ + if (QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core)) { + QWidget *w = lang->createResourceBrowser(parent); + if (w) + return w; + } + QtResourceView *resourceView = new QtResourceView(core, parent); + resourceView->setResourceModel(core->resourceModel()); + resourceView->setSettingsKey(u"ResourceBrowser"_s); + // Note for integrators: make sure you call createResourceEditor() after you instantiated your subclass of designer integration + // (designer doesn't do that since by default editing resources is enabled) + const QDesignerIntegrationInterface *integration = core->integration(); + if (integration && !integration->hasFeature(QDesignerIntegrationInterface::ResourceEditorFeature)) + resourceView->setResourceEditingEnabled(false); + return resourceView; +} + +/*! + Returns a new signal-slot editor with the given \a parent for the \a core interface.*/ +QWidget *QDesignerComponents::createSignalSlotEditor(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::SignalSlotEditorWindow(core, parent); +} + +/*! + Returns the default plugin paths of Qt Widgets Designer's plugin manager. + + \return Plugin paths + \since 6.7 +*/ +QStringList QDesignerComponents::defaultPluginPaths() +{ + return QDesignerPluginManager::defaultPluginPaths(); +} + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/components/objectinspector/objectinspector.cpp b/src/tools/designer/src/components/objectinspector/objectinspector.cpp new file mode 100644 index 00000000000..18096065b09 --- /dev/null +++ b/src/tools/designer/src/components/objectinspector/objectinspector.cpp @@ -0,0 +1,824 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "objectinspector.h" +#include "objectinspectormodel_p.h" +#include "formwindow.h" + +// sdk +#include +#include +#include +#include +#include +#include +#include +#include + +// shared +#include +#include +#include +#include +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { + // Selections: Basically, ObjectInspector has to ensure a consistent + // selection, that is, either form-managed widgets (represented + // by the cursor interface selection), or unmanaged widgets/objects, + // for example actions, container pages, menu bars, tool bars + // and the like. The selection state of the latter is managed only in the object inspector. + // As soon as a managed widget is selected, unmanaged objects + // have to be unselected + // Normally, an empty selection is not allowed, the main container + // should be selected in this case (applyCursorSelection()). + // An exception is when clearSelection is called directly for example + // by the action editor that puts an unassociated action into the property + // editor. A hack exists to avoid the update in this case. + + enum SelectionType { + NoSelection, + // A QObject that has a meta database entry + QObjectSelection, + // Unmanaged widget, menu bar or the like + UnmanagedWidgetSelection, + // A widget managed by the form window cursor + ManagedWidgetSelection }; +} + +static inline SelectionType selectionType(const QDesignerFormWindowInterface *fw, QObject *o) +{ + if (!o->isWidgetType()) + return fw->core()->metaDataBase()->item(o) ? QObjectSelection : NoSelection; + return fw->isManaged(qobject_cast(o)) ? ManagedWidgetSelection : UnmanagedWidgetSelection; +} + +// Return an offset for dropping (when dropping widgets on the object +// inspector, we fake a position on the form based on the widget dropped on). +// Position the dropped widget with form grid offset to avoid overlapping unless we +// drop on a layout. Position doesn't matter in the layout case +// and this enables us to drop on a squeezed layout widget of size zero + +static inline QPoint dropPointOffset(const qdesigner_internal::FormWindowBase *fw, const QWidget *dropTarget) +{ + if (!dropTarget || dropTarget->layout()) + return QPoint(0, 0); + return QPoint(fw->designerGrid().deltaX(), fw->designerGrid().deltaY()); +} + +namespace qdesigner_internal { +// Delegate with object name validator for the object name column +class ObjectInspectorDelegate : public QStyledItemDelegate { +public: + using QStyledItemDelegate::QStyledItemDelegate; + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; +}; + +QWidget *ObjectInspectorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & option, const QModelIndex &index) const +{ + if (index.column() != ObjectInspectorModel::ObjectNameColumn) + return QStyledItemDelegate::createEditor(parent, option, index); + // Object name editor + const bool isMainContainer = !index.parent().isValid(); + return new TextPropertyEditor(parent, TextPropertyEditor::EmbeddingTreeView, + isMainContainer ? ValidationObjectNameScope : ValidationObjectName); +} + +// ------------ ObjectInspectorTreeView: +// - Makes the Space key start editing +// - Suppresses a range selection by dragging or Shift-up/down, which does not really work due +// to the need to maintain a consistent selection. + +class ObjectInspectorTreeView : public QTreeView { +public: + using QTreeView::QTreeView; + +protected: + void mouseMoveEvent (QMouseEvent * event) override; + void keyPressEvent(QKeyEvent *event) override; + +}; + +void ObjectInspectorTreeView::mouseMoveEvent(QMouseEvent *event) +{ + event->ignore(); // suppress a range selection by dragging +} + +void ObjectInspectorTreeView::keyPressEvent(QKeyEvent *event) +{ + bool handled = false; + switch (event->key()) { + case Qt::Key_Up: + case Qt::Key_Down: // suppress shift-up/down range selection + if (event->modifiers() & Qt::ShiftModifier) { + event->ignore(); + handled = true; + } + break; + case Qt::Key_Space: { // Space pressed: Start editing + const QModelIndex index = currentIndex(); + if (index.isValid() && index.column() == 0 && !model()->hasChildren(index) && model()->flags(index) & Qt::ItemIsEditable) { + event->accept(); + handled = true; + edit(index); + } + } + break; + default: + break; + } + if (!handled) + QTreeView::keyPressEvent(event); +} + +// ------------ ObjectInspectorPrivate + +class ObjectInspector::ObjectInspectorPrivate { + Q_DISABLE_COPY_MOVE(ObjectInspectorPrivate) +public: + ObjectInspectorPrivate(QDesignerFormEditorInterface *core); + ~ObjectInspectorPrivate(); + + QLineEdit *filterLineEdit() const { return m_filterLineEdit; } + QTreeView *treeView() const { return m_treeView; } + QDesignerFormEditorInterface *core() const { return m_core; } + const QPointer &formWindow() const { return m_formWindow; } + + void clear(); + void setFormWindow(QDesignerFormWindowInterface *fwi); + + QWidget *managedWidgetAt(const QPoint &global_mouse_pos); + + void restoreDropHighlighting(); + void handleDragEnterMoveEvent(const QWidget *objectInspectorWidget, QDragMoveEvent * event, bool isDragEnter); + void dropEvent (QDropEvent * event); + + void clearSelection(); + bool selectObject(QObject *o); + void slotSelectionChanged(const QItemSelection & selected, const QItemSelection &deselected); + void getSelection(Selection &s) const; + + QModelIndexList indexesOf(QObject *o) const; + QObject *objectAt(const QModelIndex &index) const; + QObjectList indexesToObjects(const QModelIndexList &indexes) const; + + void slotHeaderDoubleClicked(int column) { m_treeView->resizeColumnToContents(column); } + void slotPopupContextMenu(QWidget *parent, const QPoint &pos); + +private: + void setFormWindowBlocked(QDesignerFormWindowInterface *fwi); + void applyCursorSelection(); + void synchronizeSelection(const QItemSelection & selected, const QItemSelection &deselected); + bool checkManagedWidgetSelection(const QModelIndexList &selection); + void showContainersCurrentPage(QWidget *widget); + + enum SelectionFlags { AddToSelection = 1, MakeCurrent = 2}; + void selectIndexRange(const QModelIndexList &indexes, unsigned flags); + + QDesignerFormEditorInterface *m_core; + QLineEdit *m_filterLineEdit; + QTreeView *m_treeView; + ObjectInspectorModel *m_model; + QSortFilterProxyModel *m_filterModel; + QPointer m_formWindow; + QPointer m_formFakeDropTarget; + bool m_withinClearSelection; +}; + +ObjectInspector::ObjectInspectorPrivate::ObjectInspectorPrivate(QDesignerFormEditorInterface *core) : + m_core(core), + m_filterLineEdit(new QLineEdit), + m_treeView(new ObjectInspectorTreeView), + m_model(new ObjectInspectorModel(m_treeView)), + m_filterModel(new QSortFilterProxyModel(m_treeView)), + m_withinClearSelection(false) +{ + m_filterModel->setRecursiveFilteringEnabled(true); + m_filterLineEdit->setPlaceholderText(ObjectInspector::tr("Filter")); + m_filterLineEdit->setClearButtonEnabled(true); + connect(m_filterLineEdit, &QLineEdit::textChanged, + m_filterModel, &QSortFilterProxyModel::setFilterFixedString); + // Filtering text collapses nodes, expand on clear. + connect(m_filterLineEdit, &QLineEdit::textChanged, + m_core, [this] (const QString &text) { + if (text.isEmpty()) + this->m_treeView->expandAll(); + }); + m_filterModel->setSourceModel(m_model); + m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + m_treeView->setModel(m_filterModel); + m_treeView->setSortingEnabled(true); + m_treeView->sortByColumn(0, Qt::AscendingOrder); + m_treeView->setItemDelegate(new ObjectInspectorDelegate); + m_treeView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + m_treeView->header()->setSectionResizeMode(1, QHeaderView::Stretch); + m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_treeView->setSelectionBehavior(QAbstractItemView::SelectRows); + m_treeView->setAlternatingRowColors(true); + m_treeView->setTextElideMode (Qt::ElideMiddle); + + m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); +} + +ObjectInspector::ObjectInspectorPrivate::~ObjectInspectorPrivate() +{ + delete m_treeView->itemDelegate(); +} + +void ObjectInspector::ObjectInspectorPrivate::clearSelection() +{ + m_withinClearSelection = true; + m_treeView->clearSelection(); + m_withinClearSelection = false; +} + +QWidget *ObjectInspector::ObjectInspectorPrivate::managedWidgetAt(const QPoint &global_mouse_pos) +{ + if (!m_formWindow) + return nullptr; + + const QPoint pos = m_treeView->viewport()->mapFromGlobal(global_mouse_pos); + QObject *o = objectAt(m_treeView->indexAt(pos)); + + if (!o || !o->isWidgetType()) + return nullptr; + + QWidget *rc = qobject_cast(o); + if (!m_formWindow->isManaged(rc)) + return nullptr; + return rc; +} + +void ObjectInspector::ObjectInspectorPrivate::showContainersCurrentPage(QWidget *widget) +{ + if (!widget) + return; + + FormWindow *fw = FormWindow::findFormWindow(widget); + if (!fw) + return; + + QWidget *w = widget->parentWidget(); + bool macroStarted = false; + // Find a multipage container (tab widgets, etc.) in the hierarchy and set the right page. + while (w != nullptr) { + if (fw->isManaged(w) && !qobject_cast(w)) { // Rule out unmanaged internal scroll areas, for example, on QToolBoxes. + if (QDesignerContainerExtension *c = qt_extension(m_core->extensionManager(), w)) { + const int count = c->count(); + if (count > 1 && !c->widget(c->currentIndex())->isAncestorOf(widget)) { + for (int i = 0; i < count; i++) + if (c->widget(i)->isAncestorOf(widget)) { + if (!macroStarted) { + macroStarted = true; + fw->beginCommand(tr("Change Current Page")); + } + ChangeCurrentPageCommand *cmd = new ChangeCurrentPageCommand(fw); + cmd->init(w, i); + fw->commandHistory()->push(cmd); + break; + } + } + } + } + w = w->parentWidget(); + } + if (macroStarted) + fw->endCommand(); +} + +void ObjectInspector::ObjectInspectorPrivate::restoreDropHighlighting() +{ + if (m_formFakeDropTarget) { + if (m_formWindow) { + m_formWindow->highlightWidget(m_formFakeDropTarget, QPoint(5, 5), FormWindow::Restore); + } + m_formFakeDropTarget = nullptr; + } +} + +void ObjectInspector::ObjectInspectorPrivate::handleDragEnterMoveEvent(const QWidget *objectInspectorWidget, QDragMoveEvent * event, bool isDragEnter) +{ + if (!m_formWindow) { + event->ignore(); + return; + } + + const QDesignerMimeData *mimeData = qobject_cast(event->mimeData()); + if (!mimeData) { + event->ignore(); + return; + } + + QWidget *dropTarget = nullptr; + QPoint fakeDropTargetOffset = QPoint(0, 0); + if (QWidget *managedWidget = managedWidgetAt(objectInspectorWidget->mapToGlobal(event->position().toPoint()))) { + fakeDropTargetOffset = dropPointOffset(m_formWindow, managedWidget); + // pretend we drag over the managed widget on the form + const QPoint fakeFormPos = m_formWindow->mapFromGlobal(managedWidget->mapToGlobal(fakeDropTargetOffset)); + const FormWindowBase::WidgetUnderMouseMode wum = mimeData->items().size() == 1 ? FormWindowBase::FindSingleSelectionDropTarget : FormWindowBase::FindMultiSelectionDropTarget; + dropTarget = m_formWindow->widgetUnderMouse(fakeFormPos, wum); + } + + if (m_formFakeDropTarget && dropTarget != m_formFakeDropTarget) + m_formWindow->highlightWidget(m_formFakeDropTarget, fakeDropTargetOffset, FormWindow::Restore); + + m_formFakeDropTarget = dropTarget; + if (m_formFakeDropTarget) + m_formWindow->highlightWidget(m_formFakeDropTarget, fakeDropTargetOffset, FormWindow::Highlight); + + // Do not refuse drag enter even if the area is not droppable + if (isDragEnter || m_formFakeDropTarget) + mimeData->acceptEvent(event); + else + event->ignore(); +} +void ObjectInspector::ObjectInspectorPrivate::dropEvent (QDropEvent * event) +{ + if (!m_formWindow || !m_formFakeDropTarget) { + event->ignore(); + return; + } + + const QDesignerMimeData *mimeData = qobject_cast(event->mimeData()); + if (!mimeData) { + event->ignore(); + return; + } + const QPoint fakeGlobalDropFormPos = m_formFakeDropTarget->mapToGlobal(dropPointOffset(m_formWindow , m_formFakeDropTarget)); + mimeData->moveDecoration(fakeGlobalDropFormPos + mimeData->hotSpot()); + if (!m_formWindow->dropWidgets(mimeData->items(), m_formFakeDropTarget, fakeGlobalDropFormPos)) { + event->ignore(); + return; + } + mimeData->acceptEvent(event); +} + +QModelIndexList ObjectInspector::ObjectInspectorPrivate::indexesOf(QObject *o) const +{ + QModelIndexList result; + const auto srcIndexes = m_model->indexesOf(o); + if (!srcIndexes.isEmpty()) { + result.reserve(srcIndexes.size()); + for (const auto &srcIndex : srcIndexes) + result.append(m_filterModel->mapFromSource(srcIndex)); + } + return result; +} + +QObject *ObjectInspector::ObjectInspectorPrivate::objectAt(const QModelIndex &index) const +{ + return m_model->objectAt(m_filterModel->mapToSource(index)); +} + +bool ObjectInspector::ObjectInspectorPrivate::selectObject(QObject *o) +{ + if (!m_core->metaDataBase()->item(o)) + return false; + + using ModelIndexSet = QSet; + + const QModelIndexList objectIndexes = indexesOf(o); + if (objectIndexes.isEmpty()) + return false; + + QItemSelectionModel *selectionModel = m_treeView->selectionModel(); + const auto currentSelectedItemList = selectionModel->selectedRows(0); + const ModelIndexSet currentSelectedItems(currentSelectedItemList.cbegin(), currentSelectedItemList.cend()); + + // Change in selection? + if (!currentSelectedItems.isEmpty() + && currentSelectedItems == ModelIndexSet(objectIndexes.cbegin(), objectIndexes.cend())) { + return true; + } + + // do select and update + selectIndexRange(objectIndexes, MakeCurrent); + return true; +} + +void ObjectInspector::ObjectInspectorPrivate::selectIndexRange(const QModelIndexList &indexes, unsigned flags) +{ + if (indexes.isEmpty()) + return; + + QItemSelectionModel::SelectionFlags selectFlags = QItemSelectionModel::Select|QItemSelectionModel::Rows; + if (!(flags & AddToSelection)) + selectFlags |= QItemSelectionModel::Clear; + if (flags & MakeCurrent) + selectFlags |= QItemSelectionModel::Current; + + QItemSelectionModel *selectionModel = m_treeView->selectionModel(); + for (const auto &mi : indexes) { + if (mi.column() == 0) { + selectionModel->select(mi, selectFlags); + selectFlags &= ~(QItemSelectionModel::Clear|QItemSelectionModel::Current); + } + } + if (flags & MakeCurrent) + m_treeView->scrollTo(indexes.constFirst(), QAbstractItemView::EnsureVisible); +} + +void ObjectInspector::ObjectInspectorPrivate::clear() +{ + m_formFakeDropTarget = nullptr; + m_formWindow = nullptr; +} + +// Form window cursor is in state 'main container only' +static inline bool mainContainerIsCurrent(const QDesignerFormWindowInterface *fw) +{ + const QDesignerFormWindowCursorInterface *cursor = fw->cursor(); + if (cursor->selectedWidgetCount() > 1) + return false; + const QWidget *current = cursor->current(); + return current == fw || current == fw->mainContainer(); +} + +void ObjectInspector::ObjectInspectorPrivate::setFormWindow(QDesignerFormWindowInterface *fwi) +{ + const bool blocked = m_treeView->selectionModel()->blockSignals(true); + { + UpdateBlocker ub(m_treeView); + setFormWindowBlocked(fwi); + } + + m_treeView->update(); + m_treeView->selectionModel()->blockSignals(blocked); +} + +void ObjectInspector::ObjectInspectorPrivate::setFormWindowBlocked(QDesignerFormWindowInterface *fwi) +{ + FormWindowBase *fw = qobject_cast(fwi); + const bool formWindowChanged = m_formWindow != fw; + + m_formWindow = fw; + + const int oldWidth = m_treeView->columnWidth(0); + const int xoffset = m_treeView->horizontalScrollBar()->value(); + const int yoffset = m_treeView->verticalScrollBar()->value(); + + if (formWindowChanged) + m_formFakeDropTarget = nullptr; + + switch (m_model->update(m_formWindow)) { + case ObjectInspectorModel::NoForm: + clear(); + return; + case ObjectInspectorModel::Rebuilt: // Complete rebuild: Just apply cursor selection + applyCursorSelection(); + m_treeView->expandAll(); + if (formWindowChanged) { + m_treeView->resizeColumnToContents(0); + } else { + m_treeView->setColumnWidth(0, oldWidth); + m_treeView->horizontalScrollBar()->setValue(xoffset); + m_treeView->verticalScrollBar()->setValue(yoffset); + } + break; + case ObjectInspectorModel::Updated: { + // Same structure (property changed or click on the form) + // We maintain a selection of unmanaged objects + // only if the cursor is in state "mainContainer() == current". + // and we have a non-managed selection. + // Else we take over the cursor selection. + bool applySelection = !mainContainerIsCurrent(m_formWindow); + if (!applySelection) { + const QModelIndexList currentIndexes = m_treeView->selectionModel()->selectedRows(0); + if (currentIndexes.isEmpty()) { + applySelection = true; + } else { + applySelection = selectionType(m_formWindow, objectAt(currentIndexes.constFirst())) == ManagedWidgetSelection; + } + } + if (applySelection) + applyCursorSelection(); + } + break; + } +} + +// Apply selection of form window cursor to object inspector, set current +void ObjectInspector::ObjectInspectorPrivate::applyCursorSelection() +{ + const QDesignerFormWindowCursorInterface *cursor = m_formWindow->cursor(); + const int count = cursor->selectedWidgetCount(); + if (!count) + return; + + // Set the current widget first which also clears the selection + QWidget *currentWidget = cursor->current(); + if (currentWidget) + selectIndexRange(indexesOf(currentWidget), MakeCurrent); + else + m_treeView->selectionModel()->clearSelection(); + + for (int i = 0;i < count; i++) { + QWidget *widget = cursor->selectedWidget(i); + if (widget != currentWidget) + selectIndexRange(indexesOf(widget), AddToSelection); + } +} + +// Synchronize managed widget in the form (select in cursor). Block updates +static int selectInCursor(FormWindowBase *fw, const QObjectList &objects, bool value) +{ + int rc = 0; + const bool blocked = fw->blockSelectionChanged(true); + for (auto *o : objects) { + if (selectionType(fw, o) == ManagedWidgetSelection) { + fw->selectWidget(static_cast(o), value); + rc++; + } + } + fw->blockSelectionChanged(blocked); + return rc; +} + +void ObjectInspector::ObjectInspectorPrivate::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + if (m_formWindow) { + synchronizeSelection(selected, deselected); + QMetaObject::invokeMethod(m_core->formWindowManager(), "slotUpdateActions"); + } +} + +// Convert indexes to object vectors taking into account that +// some index lists are multicolumn ranges +QObjectList ObjectInspector::ObjectInspectorPrivate::indexesToObjects(const QModelIndexList &indexes) const +{ + QObjectList rc; + if (indexes.isEmpty()) + return rc; + rc.reserve(indexes.size()); + for (const auto &mi : indexes) { + if (mi.column() == 0) + rc.append(objectAt(mi)); + } + return rc; +} + +// Check if any managed widgets are selected. If so, iterate over +// selection and deselect all unmanaged objects +bool ObjectInspector::ObjectInspectorPrivate::checkManagedWidgetSelection(const QModelIndexList &rowSelection) +{ + bool isManagedWidgetSelection = false; + QItemSelectionModel *selectionModel = m_treeView->selectionModel(); + for (const auto &mi : rowSelection) { + QObject *object = objectAt(mi); + if (selectionType(m_formWindow, object) == ManagedWidgetSelection) { + isManagedWidgetSelection = true; + break; + } + } + + if (!isManagedWidgetSelection) + return false; + // Need to unselect unmanaged ones + const bool blocked = selectionModel->blockSignals(true); + for (const auto &mi : rowSelection) { + QObject *object = objectAt(mi); + if (selectionType(m_formWindow, object) != ManagedWidgetSelection) + selectionModel->select(mi, QItemSelectionModel::Deselect|QItemSelectionModel::Rows); + } + selectionModel->blockSignals(blocked); + return true; +} + +void ObjectInspector::ObjectInspectorPrivate::synchronizeSelection(const QItemSelection & selectedSelection, const QItemSelection &deselectedSelection) +{ + // Synchronize form window cursor. + const QObjectList deselected = indexesToObjects(deselectedSelection.indexes()); + const QObjectList newlySelected = indexesToObjects(selectedSelection.indexes()); + + const QModelIndexList currentSelectedIndexes = m_treeView->selectionModel()->selectedRows(0); + + int deselectedManagedWidgetCount = 0; + if (!deselected.isEmpty()) + deselectedManagedWidgetCount = selectInCursor(m_formWindow, deselected, false); + + if (newlySelected.isEmpty()) { // Nothing selected + if (currentSelectedIndexes.isEmpty()) // Do not allow a null-selection, reset to main container + m_formWindow->clearSelection(!m_withinClearSelection); + return; + } + + const int selectManagedWidgetCount = selectInCursor(m_formWindow, newlySelected, true); + // Check consistency: Make sure either managed widgets or unmanaged objects are selected. + // No newly-selected managed widgets: Unless there are ones in the (old) current selection, + // select the unmanaged object + if (selectManagedWidgetCount == 0) { + if (checkManagedWidgetSelection(currentSelectedIndexes)) { + // Managed selection exists, refuse and update if necessary + if (deselectedManagedWidgetCount != 0 || selectManagedWidgetCount != 0) + m_formWindow->emitSelectionChanged(); + return; + } + // And now for the unmanaged selection + m_formWindow->clearSelection(false); + QObject *unmanagedObject = newlySelected.constFirst(); + m_core->propertyEditor()->setObject(unmanagedObject); + m_core->propertyEditor()->setEnabled(true); + // open container page if it is a single widget + if (newlySelected.size() == 1 && unmanagedObject->isWidgetType()) + showContainersCurrentPage(static_cast(unmanagedObject)); + return; + } + // Open container page if it is a single widget + if (newlySelected.size() == 1) { + QObject *object = newlySelected.constFirst(); + if (object->isWidgetType()) + showContainersCurrentPage(static_cast(object)); + } + + // A managed widget was newly selected. Make sure there are no unmanaged objects + // in the whole unless just single selection + if (currentSelectedIndexes.size() > selectManagedWidgetCount) + checkManagedWidgetSelection(currentSelectedIndexes); + // Update form + if (deselectedManagedWidgetCount != 0 || selectManagedWidgetCount != 0) + m_formWindow->emitSelectionChanged(); +} + + +void ObjectInspector::ObjectInspectorPrivate::getSelection(Selection &s) const +{ + s.clear(); + + if (!m_formWindow) + return; + + const QModelIndexList currentSelectedIndexes = m_treeView->selectionModel()->selectedRows(0); + if (currentSelectedIndexes.isEmpty()) + return; + + // sort objects + for (const QModelIndex &index : currentSelectedIndexes) { + if (QObject *object = objectAt(index)) { + switch (selectionType(m_formWindow, object)) { + case NoSelection: + break; + case QObjectSelection: + // It is actually possible to select an action twice if it is in a menu bar + // and in a tool bar. + if (!s.objects.contains(object)) + s.objects.push_back(object); + break; + case UnmanagedWidgetSelection: + s.unmanaged.push_back(qobject_cast(object)); + break; + case ManagedWidgetSelection: + s.managed.push_back(qobject_cast(object)); + break; + } + } + } +} + +// Utility to create a task menu +static inline QMenu *createTaskMenu(QObject *object, QDesignerFormWindowInterface *fw) +{ + // 1) Objects + if (!object->isWidgetType()) + return FormWindowBase::createExtensionTaskMenu(fw, object, false); + // 2) Unmanaged widgets + QWidget *w = static_cast(object); + if (!fw->isManaged(w)) + return FormWindowBase::createExtensionTaskMenu(fw, w, false); + // 3) Mananaged widgets + if (qdesigner_internal::FormWindowBase *fwb = qobject_cast(fw)) + return fwb->initializePopupMenu(w); + return nullptr; +} + +void ObjectInspector::ObjectInspectorPrivate::slotPopupContextMenu(QWidget * /*parent*/, const QPoint &pos) +{ + if (m_formWindow == nullptr || m_formWindow->currentTool() != 0) + return; + + if (QObject *object = objectAt(m_treeView->indexAt(pos))) { + if (QMenu *menu = createTaskMenu(object, m_formWindow)) { + menu->exec(m_treeView->viewport()->mapToGlobal(pos)); + delete menu; + } + } +} + +// ------------ ObjectInspector +ObjectInspector::ObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent) : + QDesignerObjectInspector(parent), + m_impl(new ObjectInspectorPrivate(core)) +{ + QVBoxLayout *vbox = new QVBoxLayout(this); + vbox->setContentsMargins(QMargins()); + + vbox->addWidget(m_impl->filterLineEdit()); + QTreeView *treeView = m_impl->treeView(); + vbox->addWidget(treeView); + + connect(treeView, &QWidget::customContextMenuRequested, + this, &ObjectInspector::slotPopupContextMenu); + + connect(treeView->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &ObjectInspector::slotSelectionChanged); + + connect(treeView->header(), &QHeaderView::sectionDoubleClicked, + this, &ObjectInspector::slotHeaderDoubleClicked); + setAcceptDrops(true); +} + +ObjectInspector::~ObjectInspector() +{ + delete m_impl; +} + +QDesignerFormEditorInterface *ObjectInspector::core() const +{ + return m_impl->core(); +} + +void ObjectInspector::slotPopupContextMenu(const QPoint &pos) +{ + m_impl->slotPopupContextMenu(this, pos); +} + +void ObjectInspector::setFormWindow(QDesignerFormWindowInterface *fwi) +{ + m_impl->setFormWindow(fwi); +} + +void ObjectInspector::slotSelectionChanged(const QItemSelection & selected, const QItemSelection &deselected) +{ + m_impl->slotSelectionChanged(selected, deselected); +} + +void ObjectInspector::getSelection(Selection &s) const +{ + m_impl->getSelection(s); +} + +bool ObjectInspector::selectObject(QObject *o) +{ + return m_impl->selectObject(o); +} + +void ObjectInspector::clearSelection() +{ + m_impl->clearSelection(); +} + +void ObjectInspector::slotHeaderDoubleClicked(int column) +{ + m_impl->slotHeaderDoubleClicked(column); +} + +void ObjectInspector::mainContainerChanged() +{ + // Invalidate references to objects kept in items + if (sender() == m_impl->formWindow()) + setFormWindow(nullptr); +} + +void ObjectInspector::dragEnterEvent (QDragEnterEvent * event) +{ + m_impl->handleDragEnterMoveEvent(this, event, true); +} + +void ObjectInspector::dragMoveEvent(QDragMoveEvent * event) +{ + m_impl->handleDragEnterMoveEvent(this, event, false); +} + +void ObjectInspector::dragLeaveEvent(QDragLeaveEvent * /* event*/) +{ + m_impl->restoreDropHighlighting(); +} + +void ObjectInspector::dropEvent (QDropEvent * event) +{ + m_impl->dropEvent(event); + +QT_END_NAMESPACE +} +} diff --git a/src/tools/designer/src/components/objectinspector/objectinspector.h b/src/tools/designer/src/components/objectinspector/objectinspector.h new file mode 100644 index 00000000000..c856d7d91a0 --- /dev/null +++ b/src/tools/designer/src/components/objectinspector/objectinspector.h @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OBJECTINSPECTOR_H +#define OBJECTINSPECTOR_H + +#include "objectinspector_global.h" +#include "qdesigner_objectinspector_p.h" + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class QItemSelection; + +namespace qdesigner_internal { + +class QT_OBJECTINSPECTOR_EXPORT ObjectInspector: public QDesignerObjectInspector +{ + Q_OBJECT +public: + explicit ObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~ObjectInspector() override; + + QDesignerFormEditorInterface *core() const override; + + void getSelection(Selection &s) const override; + bool selectObject(QObject *o) override; + void clearSelection() override; + + void setFormWindow(QDesignerFormWindowInterface *formWindow) override; + +public slots: + void mainContainerChanged() override; + +private slots: + void slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + void slotPopupContextMenu(const QPoint &pos); + void slotHeaderDoubleClicked(int column); + +protected: + void dragEnterEvent (QDragEnterEvent * event) override; + void dragMoveEvent(QDragMoveEvent * event) override; + void dragLeaveEvent(QDragLeaveEvent * event) override; + void dropEvent (QDropEvent * event) override; + +private: + class ObjectInspectorPrivate; + ObjectInspectorPrivate *m_impl; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // OBJECTINSPECTOR_H diff --git a/src/tools/designer/src/components/objectinspector/objectinspector_global.h b/src/tools/designer/src/components/objectinspector/objectinspector_global.h new file mode 100644 index 00000000000..6a1185564a5 --- /dev/null +++ b/src/tools/designer/src/components/objectinspector/objectinspector_global.h @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef OBJECTINSPECTOR_GLOBAL_H +#define OBJECTINSPECTOR_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_WIN +#ifdef QT_OBJECTINSPECTOR_LIBRARY +# define QT_OBJECTINSPECTOR_EXPORT +#else +# define QT_OBJECTINSPECTOR_EXPORT +#endif +#else +#define QT_OBJECTINSPECTOR_EXPORT +#endif + +QT_END_NAMESPACE + +#endif // OBJECTINSPECTOR_GLOBAL_H diff --git a/src/tools/designer/src/components/objectinspector/objectinspectormodel.cpp b/src/tools/designer/src/components/objectinspector/objectinspectormodel.cpp new file mode 100644 index 00000000000..5168d4ff86b --- /dev/null +++ b/src/tools/designer/src/components/objectinspector/objectinspectormodel.cpp @@ -0,0 +1,463 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "objectinspectormodel_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + enum { DataRole = 1000 }; +} + +static inline QObject *objectOfItem(const QStandardItem *item) { + return qvariant_cast(item->data(DataRole)); +} + +static bool sameIcon(const QIcon &i1, const QIcon &i2) +{ + if (i1.isNull() && i2.isNull()) + return true; + if (i1.isNull() != i2.isNull()) + return false; + return i1.cacheKey() == i2.cacheKey(); +} + +static inline bool isNameColumnEditable(const QObject *o) +{ + if (auto *action = qobject_cast(o)) + return !action->isSeparator(); + return true; +} + +static qdesigner_internal::ObjectData::StandardItemList createModelRow(const QObject *o) +{ + qdesigner_internal::ObjectData::StandardItemList rc; + const Qt::ItemFlags baseFlags = Qt::ItemIsSelectable|Qt::ItemIsDropEnabled|Qt::ItemIsEnabled; + for (int i = 0; i < qdesigner_internal::ObjectInspectorModel::NumColumns; i++) { + QStandardItem *item = new QStandardItem; + Qt::ItemFlags flags = baseFlags; + if (i == qdesigner_internal::ObjectInspectorModel::ObjectNameColumn && isNameColumnEditable(o)) + flags |= Qt::ItemIsEditable; + item->setFlags(flags); + rc += item; + } + return rc; +} + +static inline bool isQLayoutWidget(const QObject *o) +{ + return o->metaObject() == &QLayoutWidget::staticMetaObject; +} + +namespace qdesigner_internal { + + // context kept while building a model, just there to reduce string allocations + struct ModelRecursionContext { + explicit ModelRecursionContext(QDesignerFormEditorInterface *core, const QString &sepName); + + const QString designerPrefix; + const QString separator; + + QDesignerFormEditorInterface *core; + const QDesignerWidgetDataBaseInterface *db; + const QDesignerMetaDataBaseInterface *mdb; + }; + + ModelRecursionContext::ModelRecursionContext(QDesignerFormEditorInterface *c, const QString &sepName) : + designerPrefix(u"QDesigner"_s), + separator(sepName), + core(c), + db(c->widgetDataBase()), + mdb(c->metaDataBase()) + { + } + + // ------------ ObjectData/ ObjectModel: + // Whenever the selection changes, ObjectInspector::setFormWindow is + // called. To avoid rebuilding the tree every time (loosing expanded state) + // a model is first built from the object tree by recursion. + // As a tree is difficult to represent, a flat list of entries (ObjectData) + // containing object and parent object is used. + // ObjectData has an overloaded operator== that compares the object pointers. + // Structural changes which cause a rebuild can be detected by + // comparing the lists of ObjectData. If it is the same, only the item data (class name [changed by promotion], + // object name and icon) are checked and the existing items are updated. + + ObjectData::ObjectData() = default; + + ObjectData::ObjectData(QObject *parent, QObject *object, const ModelRecursionContext &ctx) : + m_parent(parent), + m_object(object), + m_className(QLatin1StringView(object->metaObject()->className())), + m_objectName(object->objectName()) + { + + // 1) set entry + if (object->isWidgetType()) { + initWidget(static_cast(object), ctx); + } else { + initObject(ctx); + } + if (m_className.startsWith(ctx.designerPrefix)) + m_className.remove(1, ctx.designerPrefix.size() - 1); + } + + void ObjectData::initObject(const ModelRecursionContext &ctx) + { + // Check objects: Action? + if (const QAction *act = qobject_cast(m_object)) { + if (act->isSeparator()) { // separator is reserved + m_objectName = ctx.separator; + m_type = SeparatorAction; + } else { + m_type = Action; + } + m_classIcon = act->icon(); + } else { + m_type = Object; + } + } + + void ObjectData::initWidget(QWidget *w, const ModelRecursionContext &ctx) + { + // Check for extension container, QLayoutwidget, or normal container + bool isContainer = false; + if (const QDesignerWidgetDataBaseItemInterface *widgetItem = ctx.db->item(ctx.db->indexOfObject(w, true))) { + m_classIcon = widgetItem->icon(); + m_className = widgetItem->name(); + isContainer = widgetItem->isContainer(); + } + + // We might encounter temporary states with no layouts when re-layouting. + // Just default to Widget handling for the moment. + if (isQLayoutWidget(w)) { + if (const QLayout *layout = w->layout()) { + m_type = LayoutWidget; + m_managedLayoutType = LayoutInfo::layoutType(ctx.core, layout); + m_className = QLatin1StringView(layout->metaObject()->className()); + m_objectName = layout->objectName(); + } + return; + } + + if (qt_extension(ctx.core->extensionManager(), w)) { + m_type = ExtensionContainer; + return; + } + if (isContainer) { + m_type = LayoutableContainer; + m_managedLayoutType = LayoutInfo::managedLayoutType(ctx.core, w); + return; + } + m_type = ChildWidget; + } + + bool ObjectData::equals(const ObjectData & me) const + { + return m_parent == me.m_parent && m_object == me.m_object; + } + + unsigned ObjectData::compare(const ObjectData & rhs) const + { + unsigned rc = 0; + if (m_className != rhs.m_className) + rc |= ClassNameChanged; + if (m_objectName != rhs.m_objectName) + rc |= ObjectNameChanged; + if (!sameIcon(m_classIcon, rhs.m_classIcon)) + rc |= ClassIconChanged; + if (m_type != rhs.m_type) + rc |= TypeChanged; + if (m_managedLayoutType != rhs.m_managedLayoutType) + rc |= LayoutTypeChanged; + return rc; + } + + void ObjectData::setItemsDisplayData(const StandardItemList &row, const ObjectInspectorIcons &icons, unsigned mask) const + { + if (mask & ObjectNameChanged) + row[ObjectInspectorModel::ObjectNameColumn]->setText(m_objectName); + if (mask & ClassNameChanged) { + row[ObjectInspectorModel::ClassNameColumn]->setText(m_className); + row[ObjectInspectorModel::ClassNameColumn]->setToolTip(m_className); + } + // Set a layout icon only for containers. Note that QLayoutWidget don't have + // real class icons + if (mask & (ClassIconChanged|TypeChanged|LayoutTypeChanged)) { + switch (m_type) { + case LayoutWidget: + row[ObjectInspectorModel::ObjectNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]); + row[ObjectInspectorModel::ClassNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]); + break; + case LayoutableContainer: + row[ObjectInspectorModel::ObjectNameColumn]->setIcon(icons.layoutIcons[m_managedLayoutType]); + row[ObjectInspectorModel::ClassNameColumn]->setIcon(m_classIcon); + break; + default: + row[ObjectInspectorModel::ObjectNameColumn]->setIcon(QIcon()); + row[ObjectInspectorModel::ClassNameColumn]->setIcon(m_classIcon); + break; + } + } + } + + void ObjectData::setItems(const StandardItemList &row, const ObjectInspectorIcons &icons) const + { + const QVariant object = QVariant::fromValue(m_object); + row[ObjectInspectorModel::ObjectNameColumn]->setData(object, DataRole); + row[ObjectInspectorModel::ClassNameColumn]->setData(object, DataRole); + setItemsDisplayData(row, icons, ClassNameChanged|ObjectNameChanged|ClassIconChanged|TypeChanged|LayoutTypeChanged); + } + + // Recursive routine that creates the model by traversing the form window object tree. + void createModelRecursion(const QDesignerFormWindowInterface *fwi, + QObject *parent, + QObject *object, + ObjectModel &model, + const ModelRecursionContext &ctx) + { + using ButtonGroupList = QList; + // 1) Create entry + const ObjectData entry(parent, object, ctx); + model.push_back(entry); + + // 2) recurse over widget children via container extension or children list + const QDesignerContainerExtension *containerExtension = nullptr; + if (entry.type() == ObjectData::ExtensionContainer) { + containerExtension = qt_extension(fwi->core()->extensionManager(), object); + Q_ASSERT(containerExtension); + const int count = containerExtension->count(); + for (int i=0; i < count; ++i) { + QObject *page = containerExtension->widget(i); + Q_ASSERT(page != nullptr); + createModelRecursion(fwi, object, page, model, ctx); + } + } + + if (!object->children().isEmpty()) { + ButtonGroupList buttonGroups; + for (QObject *childObject : object->children()) { + // Managed child widgets unless we had a container extension + if (childObject->isWidgetType()) { + if (!containerExtension) { + QWidget *widget = qobject_cast(childObject); + if (fwi->isManaged(widget)) + createModelRecursion(fwi, object, widget, model, ctx); + } + } else { + if (ctx.mdb->item(childObject)) { + if (auto bg = qobject_cast(childObject)) + buttonGroups.push_back(bg); + } // Has MetaDataBase entry + } + } + // Add button groups + if (!buttonGroups.isEmpty()) { + for (QButtonGroup *group : std::as_const(buttonGroups)) + createModelRecursion(fwi, object, group, model, ctx); + } + } // has children + if (object->isWidgetType()) { + // Add actions + const auto actions = static_cast(object)->actions(); + for (QAction *action : actions) { + if (ctx.mdb->item(action)) { + QObject *childObject = action; + if (auto menu = action->menu()) + childObject = menu; + createModelRecursion(fwi, object, childObject, model, ctx); + } + } + } + } + + // ------------ ObjectInspectorModel + ObjectInspectorModel::ObjectInspectorModel(QObject *parent) : + QStandardItemModel(0, NumColumns, parent) + { + QStringList headers; + headers += QCoreApplication::translate("ObjectInspectorModel", "Object"); + headers += QCoreApplication::translate("ObjectInspectorModel", "Class"); + Q_ASSERT(headers.size() == NumColumns); + setColumnCount(NumColumns); + setHorizontalHeaderLabels(headers); + // Icons + m_icons.layoutIcons[LayoutInfo::NoLayout] = createIconSet("editbreaklayout.png"_L1); + m_icons.layoutIcons[LayoutInfo::HSplitter] = createIconSet("edithlayoutsplit.png"_L1); + m_icons.layoutIcons[LayoutInfo::VSplitter] = createIconSet("editvlayoutsplit.png"_L1); + m_icons.layoutIcons[LayoutInfo::HBox] = createIconSet("edithlayout.png"_L1); + m_icons.layoutIcons[LayoutInfo::VBox] = createIconSet("editvlayout.png"_L1); + m_icons.layoutIcons[LayoutInfo::Grid] = createIconSet("editgrid.png"_L1); + m_icons.layoutIcons[LayoutInfo::Form] = createIconSet("editform.png"_L1); + } + + void ObjectInspectorModel::clearItems() + { + beginResetModel(); + m_objectIndexMultiMap.clear(); + m_model.clear(); + endResetModel(); // force editors to be closed in views + removeRow(0); + } + + ObjectInspectorModel::UpdateResult ObjectInspectorModel::update(QDesignerFormWindowInterface *fw) + { + QWidget *mainContainer = fw ? fw->mainContainer() : nullptr; + if (!mainContainer) { + clearItems(); + m_formWindow = nullptr; + return NoForm; + } + m_formWindow = fw; + // Build new model and compare to previous one. If the structure is + // identical, just update, else rebuild + ObjectModel newModel; + + static const QString separator = QCoreApplication::translate("ObjectInspectorModel", "separator"); + const ModelRecursionContext ctx(fw->core(), separator); + createModelRecursion(fw, nullptr, mainContainer, newModel, ctx); + + if (newModel == m_model) { + updateItemContents(m_model, newModel); + return Updated; + } + + rebuild(newModel); + m_model = newModel; + return Rebuilt; + } + + QObject *ObjectInspectorModel::objectAt(const QModelIndex &index) const + { + if (index.isValid()) + if (const QStandardItem *item = itemFromIndex(index)) + return objectOfItem(item); + return nullptr; + } + + // Missing Qt API: get a row + ObjectInspectorModel::StandardItemList ObjectInspectorModel::rowAt(QModelIndex index) const + { + StandardItemList rc; + while (true) { + rc += itemFromIndex(index); + const int nextColumn = index.column() + 1; + if (nextColumn >= NumColumns) + break; + index = index.sibling(index.row(), nextColumn); + } + return rc; + } + + // Rebuild the tree in case the model has completely changed. + void ObjectInspectorModel::rebuild(const ObjectModel &newModel) + { + clearItems(); + if (newModel.isEmpty()) + return; + + const auto mcend = newModel.cend(); + auto it = newModel.cbegin(); + // Set up root element + StandardItemList rootRow = createModelRow(it->object()); + it->setItems(rootRow, m_icons); + appendRow(rootRow); + m_objectIndexMultiMap.insert(it->object(), indexFromItem(rootRow.constFirst())); + for (++it; it != mcend; ++it) { + // Add to parent item, found via map + const QModelIndex parentIndex = m_objectIndexMultiMap.value(it->parent(), QModelIndex()); + Q_ASSERT(parentIndex.isValid()); + QStandardItem *parentItem = itemFromIndex(parentIndex); + StandardItemList row = createModelRow(it->object()); + it->setItems(row, m_icons); + parentItem->appendRow(row); + m_objectIndexMultiMap.insert(it->object(), indexFromItem(row.constFirst())); + } + } + + // Update item data in case the model has the same structure + void ObjectInspectorModel::updateItemContents(ObjectModel &oldModel, const ObjectModel &newModel) + { + // Change text and icon. Keep a set of changed object + // as for example actions might occur several times in the tree. + using QObjectSet = QSet; + + QObjectSet changedObjects; + + const auto size = newModel.size(); + Q_ASSERT(oldModel.size() == size); + for (qsizetype i = 0; i < size; ++i) { + const ObjectData &newEntry = newModel.at(i); + ObjectData &entry = oldModel[i]; + // Has some data changed? + if (const unsigned changedMask = entry.compare(newEntry)) { + entry = newEntry; + QObject * o = entry.object(); + if (!changedObjects.contains(o)) { + changedObjects.insert(o); + const QModelIndexList indexes = m_objectIndexMultiMap.values(o); + for (const QModelIndex &index : indexes) + entry.setItemsDisplayData(rowAt(index), m_icons, changedMask); + } + } + } + } + + QVariant ObjectInspectorModel::data(const QModelIndex &index, int role) const + { + const QVariant rc = QStandardItemModel::data(index, role); + // Return if the string is empty for the display role + // only (else, editing starts with ). + if (role == Qt::DisplayRole && rc.metaType().id() == QMetaType::QString) { + const QString s = rc.toString(); + if (s.isEmpty()) { + static const QString noName = QCoreApplication::translate("ObjectInspectorModel", ""); + return QVariant(noName); + } + } + return rc; + } + + bool ObjectInspectorModel::setData(const QModelIndex &index, const QVariant &value, int role) + { + if (role != Qt::EditRole || !m_formWindow) + return false; + + QObject *object = objectAt(index); + if (!object) + return false; + // Is this a layout widget? + const QString nameProperty = isQLayoutWidget(object) ? u"layoutName"_s : u"objectName"_s; + m_formWindow->commandHistory()->push(createTextPropertyCommand(nameProperty, value.toString(), object, m_formWindow)); + return true; + } +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/objectinspector/objectinspectormodel_p.h b/src/tools/designer/src/components/objectinspector/objectinspectormodel_p.h new file mode 100644 index 00000000000..5ba4b83d371 --- /dev/null +++ b/src/tools/designer/src/components/objectinspector/objectinspectormodel_p.h @@ -0,0 +1,131 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef OBJECTINSPECTORMODEL_H +#define OBJECTINSPECTORMODEL_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + + // Data structure containing the fixed item type icons + struct ObjectInspectorIcons { + QIcon layoutIcons[LayoutInfo::UnknownLayout + 1]; + }; + + struct ModelRecursionContext; + + // Data structure representing one item of the object inspector. + class ObjectData { + public: + enum Type { + Object, + Action, + SeparatorAction, + ChildWidget, // A child widget + LayoutableContainer, // A container that can be laid out + LayoutWidget, // A QLayoutWidget + ExtensionContainer // QTabWidget and the like, container extension + }; + + using StandardItemList = QList; + + explicit ObjectData(QObject *parent, QObject *object, const ModelRecursionContext &ctx); + ObjectData(); + + inline Type type() const { return m_type; } + inline QObject *object() const { return m_object; } + inline QObject *parent() const { return m_parent; } + inline QString objectName() const { return m_objectName; } + + bool equals(const ObjectData & me) const; + + enum ChangedMask { ClassNameChanged = 1, ObjectNameChanged = 2, + ClassIconChanged = 4, TypeChanged = 8, + LayoutTypeChanged = 16}; + + unsigned compare(const ObjectData & me) const; + + // Initially set up a row + void setItems(const StandardItemList &row, const ObjectInspectorIcons &icons) const; + // Update row data according to change mask + void setItemsDisplayData(const StandardItemList &row, const ObjectInspectorIcons &icons, unsigned mask) const; + + private: + friend bool comparesEqual(const ObjectData &lhs, const ObjectData &rhs) noexcept + { + return lhs.m_parent == rhs.m_parent && lhs.m_object == rhs.m_object; + } + Q_DECLARE_EQUALITY_COMPARABLE(ObjectData) + + void initObject(const ModelRecursionContext &ctx); + void initWidget(QWidget *w, const ModelRecursionContext &ctx); + + QObject *m_parent = nullptr; + QObject *m_object = nullptr; + Type m_type = Object; + QString m_className; + QString m_objectName; + QIcon m_classIcon; + LayoutInfo::Type m_managedLayoutType = LayoutInfo::NoLayout; + }; + + using ObjectModel = QList; + + // QStandardItemModel for ObjectInspector. Uses ObjectData/ObjectModel + // internally for its updates. + class ObjectInspectorModel : public QStandardItemModel { + public: + using StandardItemList = QList; + enum { ObjectNameColumn, ClassNameColumn, NumColumns }; + + explicit ObjectInspectorModel(QObject *parent); + + enum UpdateResult { NoForm, Rebuilt, Updated }; + UpdateResult update(QDesignerFormWindowInterface *fw); + + const QModelIndexList indexesOf(QObject *o) const { return m_objectIndexMultiMap.values(o); } + QObject *objectAt(const QModelIndex &index) const; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + private: + void rebuild(const ObjectModel &newModel); + void updateItemContents(ObjectModel &oldModel, const ObjectModel &newModel); + void clearItems(); + StandardItemList rowAt(QModelIndex index) const; + + ObjectInspectorIcons m_icons; + QMultiMap m_objectIndexMultiMap; + ObjectModel m_model; + QPointer m_formWindow; + }; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // OBJECTINSPECTORMODEL_H diff --git a/src/tools/designer/src/components/propertyeditor/brushpropertymanager.cpp b/src/tools/designer/src/components/propertyeditor/brushpropertymanager.cpp new file mode 100644 index 00000000000..503e07ecfee --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/brushpropertymanager.cpp @@ -0,0 +1,271 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "brushpropertymanager.h" +#include "qtpropertymanager_p.h" +#include "designerpropertymanager.h" +#include "qtpropertybrowserutils_p.h" + +#include +#include +#include + +static const char *brushStyles[] = { +QT_TRANSLATE_NOOP("BrushPropertyManager", "No brush"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Solid"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 1"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 2"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 3"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 4"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 5"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 6"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Dense 7"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Horizontal"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Vertical"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Cross"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Backward diagonal"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Forward diagonal"), +QT_TRANSLATE_NOOP("BrushPropertyManager", "Crossing diagonal"), +}; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +BrushPropertyManager::BrushPropertyManager() = default; + +int BrushPropertyManager::brushStyleToIndex(Qt::BrushStyle st) +{ + switch (st) { + case Qt::NoBrush: return 0; + case Qt::SolidPattern: return 1; + case Qt::Dense1Pattern: return 2; + case Qt::Dense2Pattern: return 3; + case Qt::Dense3Pattern: return 4; + case Qt::Dense4Pattern: return 5; + case Qt::Dense5Pattern: return 6; + case Qt::Dense6Pattern: return 7; + case Qt::Dense7Pattern: return 8; + case Qt::HorPattern: return 9; + case Qt::VerPattern: return 10; + case Qt::CrossPattern: return 11; + case Qt::BDiagPattern: return 12; + case Qt::FDiagPattern: return 13; + case Qt::DiagCrossPattern: return 14; + default: break; + } + return 0; +} + +Qt::BrushStyle BrushPropertyManager::brushStyleIndexToStyle(int brushStyleIndex) +{ + switch (brushStyleIndex) { + case 0: return Qt::NoBrush; + case 1: return Qt::SolidPattern; + case 2: return Qt::Dense1Pattern; + case 3: return Qt::Dense2Pattern; + case 4: return Qt::Dense3Pattern; + case 5: return Qt::Dense4Pattern; + case 6: return Qt::Dense5Pattern; + case 7: return Qt::Dense6Pattern; + case 8: return Qt::Dense7Pattern; + case 9: return Qt::HorPattern; + case 10: return Qt::VerPattern; + case 11: return Qt::CrossPattern; + case 12: return Qt::BDiagPattern; + case 13: return Qt::FDiagPattern; + case 14: return Qt::DiagCrossPattern; + } + return Qt::NoBrush; +} + +static void clearBrushIcons(); + +namespace { +class EnumIndexIconMap : public QMap +{ +public: + EnumIndexIconMap() + { + qAddPostRoutine(clearBrushIcons); + } +}; +} + +Q_GLOBAL_STATIC(EnumIndexIconMap, brushIcons) + +static void clearBrushIcons() +{ + brushIcons()->clear(); +} + +const QMap &BrushPropertyManager::brushStyleIcons() +{ + // Create a map of icons for the brush style editor + if (brushIcons()->empty()) { + const int brushStyleCount = sizeof(brushStyles)/sizeof(const char *); + QBrush brush(Qt::black); + for (int i = 0; i < brushStyleCount; i++) { + const Qt::BrushStyle style = brushStyleIndexToStyle(i); + brush.setStyle(style); + brushIcons()->insert(i, QtPropertyBrowserUtils::brushValueIcon(brush)); + } + } + return *(brushIcons()); +} + +QString BrushPropertyManager::brushStyleIndexToString(int brushStyleIndex) +{ + const int brushStyleCount = sizeof(brushStyles)/sizeof(const char *); + return brushStyleIndex < brushStyleCount ? QCoreApplication::translate("BrushPropertyManager", brushStyles[brushStyleIndex]) : QString(); +} + +BrushPropertyManager::~BrushPropertyManager() = default; + +void BrushPropertyManager::initializeProperty(QtVariantPropertyManager *vm, QtProperty *property, int enumTypeId) +{ + m_brushValues.insert(property, QBrush()); + // style + QtVariantProperty *styleSubProperty = vm->addProperty(enumTypeId, QCoreApplication::translate("BrushPropertyManager", "Style")); + property->addSubProperty(styleSubProperty); + QStringList styles; + for (const char *brushStyle : brushStyles) + styles.push_back(QCoreApplication::translate("BrushPropertyManager", brushStyle)); + styleSubProperty->setAttribute(u"enumNames"_s, styles); + styleSubProperty->setAttribute(u"enumIcons"_s, QVariant::fromValue(brushStyleIcons())); + m_brushPropertyToStyleSubProperty.insert(property, styleSubProperty); + m_brushStyleSubPropertyToProperty.insert(styleSubProperty, property); + // color + QtVariantProperty *colorSubProperty = + vm->addProperty(QMetaType::QColor, QCoreApplication::translate("BrushPropertyManager", "Color")); + property->addSubProperty(colorSubProperty); + m_brushPropertyToColorSubProperty.insert(property, colorSubProperty); + m_brushColorSubPropertyToProperty.insert(colorSubProperty, property); +} + +bool BrushPropertyManager::uninitializeProperty(QtProperty *property) +{ + const auto brit = m_brushValues.find(property); // Brushes + if (brit == m_brushValues.end()) + return false; + m_brushValues.erase(brit); + // style + const auto styleIt = m_brushPropertyToStyleSubProperty.find(property); + if (styleIt != m_brushPropertyToStyleSubProperty.end()) { + QtProperty *styleProp = styleIt .value(); + m_brushStyleSubPropertyToProperty.remove(styleProp); + m_brushPropertyToStyleSubProperty.erase(styleIt ); + delete styleProp; + } + // color + const auto colorIt = m_brushPropertyToColorSubProperty.find(property); + if (colorIt != m_brushPropertyToColorSubProperty.end()) { + QtProperty *colorProp = colorIt .value(); + m_brushColorSubPropertyToProperty.remove(colorProp); + m_brushPropertyToColorSubProperty.erase(colorIt ); + delete colorProp; + } + return true; +} + +void BrushPropertyManager::slotPropertyDestroyed(QtProperty *property) +{ + auto subit = m_brushStyleSubPropertyToProperty.find(property); + if (subit != m_brushStyleSubPropertyToProperty.end()) { + m_brushPropertyToStyleSubProperty[subit.value()] = 0; + m_brushStyleSubPropertyToProperty.erase(subit); + } + subit = m_brushColorSubPropertyToProperty.find(property); + if (subit != m_brushColorSubPropertyToProperty.end()) { + m_brushPropertyToColorSubProperty[subit.value()] = 0; + m_brushColorSubPropertyToProperty.erase(subit); + } +} + + +int BrushPropertyManager::valueChanged(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value) +{ + switch (value.metaType().id()) { + case QMetaType::Int: // Style subproperty? + if (QtProperty *brushProperty = m_brushStyleSubPropertyToProperty.value(property, 0)) { + const QBrush oldValue = m_brushValues.value(brushProperty); + QBrush newBrush = oldValue; + const int index = value.toInt(); + newBrush.setStyle(brushStyleIndexToStyle(index)); + if (newBrush == oldValue) + return DesignerPropertyManager::Unchanged; + vm->variantProperty(brushProperty)->setValue(newBrush); + return DesignerPropertyManager::Changed; + } + break; + case QMetaType::QColor: // Color subproperty? + if (QtProperty *brushProperty = m_brushColorSubPropertyToProperty.value(property, 0)) { + const QBrush oldValue = m_brushValues.value(brushProperty); + QBrush newBrush = oldValue; + newBrush.setColor(qvariant_cast(value)); + if (newBrush == oldValue) + return DesignerPropertyManager::Unchanged; + vm->variantProperty(brushProperty)->setValue(newBrush); + return DesignerPropertyManager::Changed; + } + break; + default: + break; + } + return DesignerPropertyManager::NoMatch; +} + +int BrushPropertyManager::setValue(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value) +{ + if (value.metaType().id() != QMetaType::QBrush) + return DesignerPropertyManager::NoMatch; + const auto brit = m_brushValues.find(property); + if (brit == m_brushValues.end()) + return DesignerPropertyManager::NoMatch; + + const QBrush newBrush = qvariant_cast(value); + if (newBrush == brit.value()) + return DesignerPropertyManager::Unchanged; + brit.value() = newBrush; + if (QtProperty *styleProperty = m_brushPropertyToStyleSubProperty.value(property)) + vm->variantProperty(styleProperty)->setValue(brushStyleToIndex(newBrush.style())); + if (QtProperty *colorProperty = m_brushPropertyToColorSubProperty.value(property)) + vm->variantProperty(colorProperty)->setValue(newBrush.color()); + + return DesignerPropertyManager::Changed; +} + +bool BrushPropertyManager::valueText(const QtProperty *property, QString *text) const +{ + const auto brit = m_brushValues.constFind(property); + if (brit == m_brushValues.constEnd()) + return false; + const QBrush &brush = brit.value(); + const QString styleName = brushStyleIndexToString(brushStyleToIndex(brush.style())); + *text = QCoreApplication::translate("BrushPropertyManager", "[%1, %2]") + .arg(styleName, QtPropertyBrowserUtils::colorValueText(brush.color())); + return true; +} + +bool BrushPropertyManager::valueIcon(const QtProperty *property, QIcon *icon) const +{ + const auto brit = m_brushValues.constFind(property); + if (brit == m_brushValues.constEnd()) + return false; + *icon = QtPropertyBrowserUtils::brushValueIcon(brit.value()); + return true; +} + +bool BrushPropertyManager::value(const QtProperty *property, QVariant *v) const +{ + const auto brit = m_brushValues.constFind(property); + if (brit == m_brushValues.constEnd()) + return false; + v->setValue(brit.value()); + return true; +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/brushpropertymanager.h b/src/tools/designer/src/components/propertyeditor/brushpropertymanager.h new file mode 100644 index 00000000000..8bfe7f5c6bc --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/brushpropertymanager.h @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BRUSHPROPERTYMANAGER_H +#define BRUSHPROPERTYMANAGER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtProperty; +class QtVariantPropertyManager; + +class QString; +class QVariant; + +namespace qdesigner_internal { + +// BrushPropertyManager: A mixin for DesignerPropertyManager that manages brush properties. + +class BrushPropertyManager { +public: + Q_DISABLE_COPY_MOVE(BrushPropertyManager); + + BrushPropertyManager(); + ~BrushPropertyManager(); + + void initializeProperty(QtVariantPropertyManager *vm, QtProperty *property, int enumTypeId); + bool uninitializeProperty(QtProperty *property); + + // Call from slotValueChanged(). + int valueChanged(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value); + int setValue(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value); + + bool valueText(const QtProperty *property, QString *text) const; + bool valueIcon(const QtProperty *property, QIcon *icon) const; + bool value(const QtProperty *property, QVariant *v) const; + + // Call from QtPropertyManager's propertyDestroyed signal + void slotPropertyDestroyed(QtProperty *property); + +private: + static int brushStyleToIndex(Qt::BrushStyle st); + static Qt::BrushStyle brushStyleIndexToStyle(int brushStyleIndex); + static QString brushStyleIndexToString(int brushStyleIndex); + + static const QMap &brushStyleIcons(); + + using PropertyToPropertyMap = QHash; + PropertyToPropertyMap m_brushPropertyToStyleSubProperty; + PropertyToPropertyMap m_brushPropertyToColorSubProperty; + PropertyToPropertyMap m_brushStyleSubPropertyToProperty; + PropertyToPropertyMap m_brushColorSubPropertyToProperty; + + QHash m_brushValues; +}; + +} + +QT_END_NAMESPACE + +#endif // BRUSHPROPERTYMANAGER_H diff --git a/src/tools/designer/src/components/propertyeditor/designerpropertymanager.cpp b/src/tools/designer/src/components/propertyeditor/designerpropertymanager.cpp new file mode 100644 index 00000000000..30b6e351883 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/designerpropertymanager.cpp @@ -0,0 +1,2664 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "designerpropertymanager.h" +#include "qtpropertymanager_p.h" +#include "paletteeditorbutton.h" +#include "pixmapeditor.h" +#include "qlonglongvalidator.h" +#include "stringlisteditorbutton.h" +#include "qtresourceview_p.h" +#include "qtpropertybrowserutils_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto resettableAttributeC = "resettable"_L1; +static constexpr auto flagsAttributeC = "flags"_L1; +static constexpr auto validationModesAttributeC = "validationMode"_L1; +static constexpr auto superPaletteAttributeC = "superPalette"_L1; +static constexpr auto defaultResourceAttributeC = "defaultResource"_L1; +static constexpr auto fontAttributeC = "font"_L1; +static constexpr auto themeAttributeC = "theme"_L1; +static constexpr auto themeEnumAttributeC = "themeEnum"_L1; + +class DesignerFlagPropertyType +{ +}; + + +class DesignerAlignmentPropertyType +{ +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(DesignerFlagPropertyType) +Q_DECLARE_METATYPE(DesignerAlignmentPropertyType) + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +template +void TranslatablePropertyManager::initialize(QtVariantPropertyManager *m, + QtProperty *property, + const PropertySheetValue &value) +{ + m_values.insert(property, value); + + QtVariantProperty *translatable = m->addProperty(QMetaType::Bool, DesignerPropertyManager::tr("translatable")); + translatable->setValue(value.translatable()); + m_valueToTranslatable.insert(property, translatable); + m_translatableToValue.insert(translatable, property); + property->addSubProperty(translatable); + + if (!DesignerPropertyManager::useIdBasedTranslations()) { + QtVariantProperty *disambiguation = + m->addProperty(QMetaType::QString, DesignerPropertyManager::tr("disambiguation")); + disambiguation->setValue(value.disambiguation()); + m_valueToDisambiguation.insert(property, disambiguation); + m_disambiguationToValue.insert(disambiguation, property); + property->addSubProperty(disambiguation); + } + + QtVariantProperty *comment = m->addProperty(QMetaType::QString, DesignerPropertyManager::tr("comment")); + comment->setValue(value.comment()); + m_valueToComment.insert(property, comment); + m_commentToValue.insert(comment, property); + property->addSubProperty(comment); + + if (DesignerPropertyManager::useIdBasedTranslations()) { + QtVariantProperty *id = m->addProperty(QMetaType::QString, DesignerPropertyManager::tr("id")); + id->setValue(value.id()); + m_valueToId.insert(property, id); + m_idToValue.insert(id, property); + property->addSubProperty(id); + } +} + +template +bool TranslatablePropertyManager::uninitialize(QtProperty *property) +{ + if (QtProperty *comment = m_valueToComment.value(property)) { + delete comment; + m_commentToValue.remove(comment); + } else { + return false; + } + if (QtProperty *translatable = m_valueToTranslatable.value(property)) { + delete translatable; + m_translatableToValue.remove(translatable); + } + if (QtProperty *disambiguation = m_valueToDisambiguation.value(property)) { + delete disambiguation; + m_disambiguationToValue.remove(disambiguation); + } + if (QtProperty *id = m_valueToId.value(property)) { + delete id; + m_idToValue.remove(id); + } + + m_values.remove(property); + m_valueToComment.remove(property); + m_valueToTranslatable.remove(property); + m_valueToDisambiguation.remove(property); + m_valueToId.remove(property); + return true; +} + +template +bool TranslatablePropertyManager::destroy(QtProperty *subProperty) +{ + const auto commentToValueIt = m_commentToValue.find(subProperty); + if (commentToValueIt != m_commentToValue.end()) { + m_valueToComment.remove(commentToValueIt.value()); + m_commentToValue.erase(commentToValueIt); + return true; + } + const auto translatableToValueIt = m_translatableToValue.find(subProperty); + if (translatableToValueIt != m_translatableToValue.end()) { + m_valueToTranslatable.remove(translatableToValueIt.value()); + m_translatableToValue.erase(translatableToValueIt); + return true; + } + const auto disambiguationToValueIt = m_disambiguationToValue.find(subProperty); + if (disambiguationToValueIt != m_disambiguationToValue.end()) { + m_valueToDisambiguation.remove(disambiguationToValueIt.value()); + m_disambiguationToValue.erase(disambiguationToValueIt); + return true; + } + const auto idToValueIt = m_idToValue.find(subProperty); + if (idToValueIt != m_idToValue.end()) { + m_valueToId.remove(idToValueIt.value()); + m_idToValue.erase(idToValueIt); + return true; + } + return false; +} + +template +int TranslatablePropertyManager::valueChanged(QtVariantPropertyManager *m, + QtProperty *propertyIn, + const QVariant &value) +{ + if (QtProperty *property = m_translatableToValue.value(propertyIn, 0)) { + const PropertySheetValue oldValue = m_values.value(property); + PropertySheetValue newValue = oldValue; + newValue.setTranslatable(value.toBool()); + if (newValue != oldValue) { + m->variantProperty(property)->setValue(QVariant::fromValue(newValue)); + return DesignerPropertyManager::Changed; + } + return DesignerPropertyManager::Unchanged; + } + if (QtProperty *property = m_commentToValue.value(propertyIn)) { + const PropertySheetValue oldValue = m_values.value(property); + PropertySheetValue newValue = oldValue; + newValue.setComment(value.toString()); + if (newValue != oldValue) { + m->variantProperty(property)->setValue(QVariant::fromValue(newValue)); + return DesignerPropertyManager::Changed; + } + return DesignerPropertyManager::Unchanged; + } + if (QtProperty *property = m_disambiguationToValue.value(propertyIn, 0)) { + const PropertySheetValue oldValue = m_values.value(property); + PropertySheetValue newValue = oldValue; + newValue.setDisambiguation(value.toString()); + if (newValue != oldValue) { + m->variantProperty(property)->setValue(QVariant::fromValue(newValue)); + return DesignerPropertyManager::Changed; + } + return DesignerPropertyManager::Unchanged; + } + if (QtProperty *property = m_idToValue.value(propertyIn)) { + const PropertySheetValue oldValue = m_values.value(property); + PropertySheetValue newValue = oldValue; + newValue.setId(value.toString()); + if (newValue != oldValue) { + m->variantProperty(property)->setValue(QVariant::fromValue(newValue)); + return DesignerPropertyManager::Changed; + } + return DesignerPropertyManager::Unchanged; + } + return DesignerPropertyManager::NoMatch; +} + +template +int TranslatablePropertyManager::setValue(QtVariantPropertyManager *m, + QtProperty *property, + int expectedTypeId, + const QVariant &variantValue) +{ + const auto it = m_values.find(property); + if (it == m_values.end()) + return DesignerPropertyManager::NoMatch; + if (variantValue.userType() != expectedTypeId) + return DesignerPropertyManager::NoMatch; + const PropertySheetValue value = qvariant_cast(variantValue); + if (value == it.value()) + return DesignerPropertyManager::Unchanged; + if (QtVariantProperty *comment = m->variantProperty(m_valueToComment.value(property))) + comment->setValue(value.comment()); + if (QtVariantProperty *translatable = m->variantProperty(m_valueToTranslatable.value(property))) + translatable->setValue(value.translatable()); + if (QtVariantProperty *disambiguation = m->variantProperty(m_valueToDisambiguation.value(property))) + disambiguation->setValue(value.disambiguation()); + if (QtVariantProperty *id = m->variantProperty(m_valueToId.value(property))) + id->setValue(value.id()); + it.value() = value; + return DesignerPropertyManager::Changed; +} + +template +bool TranslatablePropertyManager::value(const QtProperty *property, QVariant *rc) const +{ + const auto it = m_values.constFind(property); + if (it == m_values.constEnd()) + return false; + *rc = QVariant::fromValue(it.value()); + return true; +} + +// ------------ TextEditor +class TextEditor : public QWidget +{ + Q_OBJECT +public: + TextEditor(QDesignerFormEditorInterface *core, QWidget *parent); + + TextPropertyValidationMode textPropertyValidationMode() const; + void setTextPropertyValidationMode(TextPropertyValidationMode vm); + + void setRichTextDefaultFont(const QFont &font) { m_richTextDefaultFont = font; } + QFont richTextDefaultFont() const { return m_richTextDefaultFont; } + + void setSpacing(int spacing); + + TextPropertyEditor::UpdateMode updateMode() const { return m_editor->updateMode(); } + void setUpdateMode(TextPropertyEditor::UpdateMode um) { m_editor->setUpdateMode(um); } + + void setIconThemeModeEnabled(bool enable); + +public slots: + void setText(const QString &text); + +signals: + void textChanged(const QString &text); + +private slots: + void buttonClicked(); + void resourceActionActivated(); + void fileActionActivated(); +private: + TextPropertyEditor *m_editor; + IconThemeEditor *m_themeEditor; + bool m_iconThemeModeEnabled; + QFont m_richTextDefaultFont; + QToolButton *m_button; + QMenu *m_menu; + QAction *m_resourceAction; + QAction *m_fileAction; + QHBoxLayout *m_layout; + QDesignerFormEditorInterface *m_core; +}; + +TextEditor::TextEditor(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_editor(new TextPropertyEditor(this)), + m_themeEditor(new IconThemeEditor(this, false)), + m_iconThemeModeEnabled(false), + m_richTextDefaultFont(QApplication::font()), + m_button(new QToolButton(this)), + m_menu(new QMenu(this)), + m_resourceAction(new QAction(tr("Choose Resource..."), this)), + m_fileAction(new QAction(tr("Choose File..."), this)), + m_layout(new QHBoxLayout(this)), + m_core(core) +{ + m_themeEditor->setVisible(false); + m_button->setVisible(false); + + m_layout->addWidget(m_editor); + m_layout->addWidget(m_themeEditor); + m_button->setText(tr("...")); + m_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); + m_button->setFixedWidth(20); + m_layout->addWidget(m_button); + m_layout->setContentsMargins(QMargins()); + m_layout->setSpacing(0); + + connect(m_resourceAction, &QAction::triggered, this, &TextEditor::resourceActionActivated); + connect(m_fileAction, &QAction::triggered, this, &TextEditor::fileActionActivated); + connect(m_editor, &TextPropertyEditor::textChanged, this, &TextEditor::textChanged); + connect(m_themeEditor, &IconThemeEditor::edited, this, &TextEditor::textChanged); + connect(m_button, &QAbstractButton::clicked, this, &TextEditor::buttonClicked); + + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + setFocusProxy(m_editor); + + m_menu->addAction(m_resourceAction); + m_menu->addAction(m_fileAction); +} + +void TextEditor::setSpacing(int spacing) +{ + m_layout->setSpacing(spacing); +} + +void TextEditor::setIconThemeModeEnabled(bool enable) +{ + if (m_iconThemeModeEnabled == enable) + return; // nothing changes + m_iconThemeModeEnabled = enable; + m_editor->setVisible(!enable); + m_themeEditor->setVisible(enable); + if (enable) { + m_themeEditor->setTheme(m_editor->text()); + setFocusProxy(m_themeEditor); + } else { + m_editor->setText(m_themeEditor->theme()); + setFocusProxy(m_editor); + } +} + +TextPropertyValidationMode TextEditor::textPropertyValidationMode() const +{ + return m_editor->textPropertyValidationMode(); +} + +void TextEditor::setTextPropertyValidationMode(TextPropertyValidationMode vm) +{ + m_editor->setTextPropertyValidationMode(vm); + if (vm == ValidationURL) { + m_button->setMenu(m_menu); + m_button->setFixedWidth(30); + m_button->setPopupMode(QToolButton::MenuButtonPopup); + } else { + m_button->setMenu(nullptr); + m_button->setFixedWidth(20); + m_button->setPopupMode(QToolButton::DelayedPopup); + } + m_button->setVisible(vm == ValidationStyleSheet || vm == ValidationRichText || vm == ValidationMultiLine || vm == ValidationURL); +} + +void TextEditor::setText(const QString &text) +{ + if (m_iconThemeModeEnabled) + m_themeEditor->setTheme(text); + else + m_editor->setText(text); +} + +void TextEditor::buttonClicked() +{ + const QString oldText = m_editor->text(); + QString newText; + switch (textPropertyValidationMode()) { + case ValidationStyleSheet: { + StyleSheetEditorDialog dlg(m_core, this); + dlg.setText(oldText); + if (dlg.exec() != QDialog::Accepted) + return; + newText = dlg.text(); + } + break; + case ValidationRichText: { + RichTextEditorDialog dlg(m_core, this); + dlg.setDefaultFont(m_richTextDefaultFont); + dlg.setText(oldText); + if (dlg.showDialog() != QDialog::Accepted) + return; + newText = dlg.text(Qt::AutoText); + } + break; + case ValidationMultiLine: { + PlainTextEditorDialog dlg(m_core, this); + dlg.setDefaultFont(m_richTextDefaultFont); + dlg.setText(oldText); + if (dlg.showDialog() != QDialog::Accepted) + return; + newText = dlg.text(); + } + break; + case ValidationURL: + if (oldText.isEmpty() || oldText.startsWith("qrc:"_L1)) + resourceActionActivated(); + else + fileActionActivated(); + return; + default: + return; + } + if (newText != oldText) { + m_editor->setText(newText); + emit textChanged(newText); + } +} + +void TextEditor::resourceActionActivated() +{ + QString oldPath = m_editor->text(); + if (oldPath.startsWith("qrc:"_L1)) + oldPath.remove(0, 4); + // returns ':/file' + QString newPath = IconSelector::choosePixmapResource(m_core, m_core->resourceModel(), oldPath, this); + if (newPath.startsWith(u':')) + newPath.remove(0, 1); + if (newPath.isEmpty() || newPath == oldPath) + return; + const QString newText = "qrc:"_L1 + newPath; + m_editor->setText(newText); + emit textChanged(newText); +} + +void TextEditor::fileActionActivated() +{ + QString oldPath = m_editor->text(); + if (oldPath.startsWith("file:"_L1)) + oldPath = oldPath.mid(5); + const QString newPath = m_core->dialogGui()->getOpenFileName(this, tr("Choose a File"), oldPath); + if (newPath.isEmpty() || newPath == oldPath) + return; + const QString newText = QUrl::fromLocalFile(newPath).toString(); + m_editor->setText(newText); + emit textChanged(newText); +} + +// --------------- ResetWidget +class ResetWidget : public QWidget +{ + Q_OBJECT +public: + ResetWidget(QtProperty *property, QWidget *parent = nullptr); + + void setWidget(QWidget *widget); + void setResetEnabled(bool enabled); + void setValueText(const QString &text); + void setValueIcon(const QIcon &icon); + void setSpacing(int spacing); +signals: + void resetProperty(QtProperty *property); +private slots: + void slotClicked(); +private: + QtProperty *m_property; + QLabel *m_textLabel; + QLabel *m_iconLabel; + QToolButton *m_button; + int m_spacing; +}; + +ResetWidget::ResetWidget(QtProperty *property, QWidget *parent) : + QWidget(parent), + m_property(property), + m_textLabel(new QLabel(this)), + m_iconLabel(new QLabel(this)), + m_button(new QToolButton(this)), + m_spacing(-1) +{ + m_textLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed)); + m_iconLabel->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + m_button->setToolButtonStyle(Qt::ToolButtonIconOnly); + m_button->setIcon(createIconSet("resetproperty.png"_L1)); + m_button->setIconSize(QSize(8,8)); + m_button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding)); + connect(m_button, &QAbstractButton::clicked, this, &ResetWidget::slotClicked); + QLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(m_spacing); + layout->addWidget(m_iconLabel); + layout->addWidget(m_textLabel); + layout->addWidget(m_button); + setFocusProxy(m_textLabel); + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); +} + +void ResetWidget::setSpacing(int spacing) +{ + m_spacing = spacing; + layout()->setSpacing(m_spacing); +} + +void ResetWidget::setWidget(QWidget *widget) +{ + if (m_textLabel) { + delete m_textLabel; + m_textLabel = nullptr; + } + if (m_iconLabel) { + delete m_iconLabel; + m_iconLabel = nullptr; + } + delete layout(); + QLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(m_spacing); + layout->addWidget(widget); + layout->addWidget(m_button); + setFocusProxy(widget); +} + +void ResetWidget::setResetEnabled(bool enabled) +{ + m_button->setEnabled(enabled); +} + +void ResetWidget::setValueText(const QString &text) +{ + if (m_textLabel) + m_textLabel->setText(text); +} + +void ResetWidget::setValueIcon(const QIcon &icon) +{ + QPixmap pix = icon.pixmap(QSize(16, 16)); + if (m_iconLabel) { + m_iconLabel->setVisible(!pix.isNull()); + m_iconLabel->setPixmap(pix); + } +} + +void ResetWidget::slotClicked() +{ + emit resetProperty(m_property); +} + + +// ------------ DesignerPropertyManager: + +DesignerPropertyManager::DesignerPropertyManager(QDesignerFormEditorInterface *core, QObject *parent) : + QtVariantPropertyManager(parent), + m_changingSubValue(false), + m_core(core), + m_object(nullptr), + m_sourceOfChange(nullptr) +{ + connect(this, &QtVariantPropertyManager::valueChanged, + this, &DesignerPropertyManager::slotValueChanged); + connect(this, & QtAbstractPropertyManager::propertyDestroyed, + this, &DesignerPropertyManager::slotPropertyDestroyed); +} + +DesignerPropertyManager::~DesignerPropertyManager() +{ + clear(); +} + +bool DesignerPropertyManager::m_IdBasedTranslations = false; + +template +static int bitCount(IntT mask) +{ + int count = 0; + for (; mask; count++) + mask &= mask - 1; // clear the least significant bit set + return count; +} + +int DesignerPropertyManager::alignToIndexH(uint align) const +{ + if (align & Qt::AlignLeft) + return 0; + if (align & Qt::AlignHCenter) + return 1; + if (align & Qt::AlignRight) + return 2; + if (align & Qt::AlignJustify) + return 3; + return 0; +} + +int DesignerPropertyManager::alignToIndexV(uint align) const +{ + if (align & Qt::AlignTop) + return 0; + if (align & Qt::AlignVCenter) + return 1; + if (align & Qt::AlignBottom) + return 2; + return 1; +} + +uint DesignerPropertyManager::indexHToAlign(int idx) const +{ + switch (idx) { + case 0: return Qt::AlignLeft; + case 1: return Qt::AlignHCenter; + case 2: return Qt::AlignRight; + case 3: return Qt::AlignJustify; + default: break; + } + return Qt::AlignLeft; +} + +uint DesignerPropertyManager::indexVToAlign(int idx) const +{ + switch (idx) { + case 0: return Qt::AlignTop; + case 1: return Qt::AlignVCenter; + case 2: return Qt::AlignBottom; + default: break; + } + return Qt::AlignVCenter; +} + +QString DesignerPropertyManager::indexHToString(int idx) const +{ + switch (idx) { + case 0: return tr("AlignLeft"); + case 1: return tr("AlignHCenter"); + case 2: return tr("AlignRight"); + case 3: return tr("AlignJustify"); + default: break; + } + return tr("AlignLeft"); +} + +QString DesignerPropertyManager::indexVToString(int idx) const +{ + switch (idx) { + case 0: return tr("AlignTop"); + case 1: return tr("AlignVCenter"); + case 2: return tr("AlignBottom"); + default: break; + } + return tr("AlignVCenter"); +} + +void DesignerPropertyManager::slotValueChanged(QtProperty *property, const QVariant &value) +{ + if (m_changingSubValue) + return; + bool enableSubPropertyHandling = true; + + // Find a matching manager + int subResult = m_stringManager.valueChanged(this, property, value); + if (subResult == NoMatch) + subResult = m_keySequenceManager.valueChanged(this, property, value); + if (subResult == NoMatch) + subResult = m_stringListManager.valueChanged(this, property, value); + if (subResult == NoMatch) + subResult = m_brushManager.valueChanged(this, property, value); + if (subResult == NoMatch) + subResult = m_fontManager.valueChanged(this, property, value); + if (subResult != NoMatch) { + if (subResult == Changed) + emit valueChanged(property, value, enableSubPropertyHandling); + return; + } + + if (QtProperty *flagProperty = m_flagToProperty.value(property, 0)) { + const auto subFlags = m_propertyToFlags.value(flagProperty); + const qsizetype subFlagCount = subFlags.size(); + // flag changed + const bool subValue = variantProperty(property)->value().toBool(); + const qsizetype subIndex = subFlags.indexOf(property); + if (subIndex < 0) + return; + + uint newValue = 0; + + m_changingSubValue = true; + + FlagData data = m_flagValues.value(flagProperty); + const auto values = data.values; + // Compute new value, without including (additional) supermasks + if (values.at(subIndex) == 0) { + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + subFlag->setValue(i == subIndex); + } + } else { + if (subValue) + newValue = values.at(subIndex); // value mask of subValue + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + if (subFlag->value().toBool() && bitCount(values.at(i)) == 1) + newValue |= values.at(i); + } + if (newValue == 0) { + // Uncheck all items except 0-mask + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + subFlag->setValue(values.at(i) == 0); + } + } else if (newValue == data.val) { + if (!subValue && bitCount(values.at(subIndex)) > 1) { + // We unchecked something, but the original value still holds + variantProperty(property)->setValue(true); + } + } else { + // Make sure 0-mask is not selected + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + if (values.at(i) == 0) + subFlag->setValue(false); + } + // Check/uncheck proper masks + if (subValue) { + // Make sure submasks and supermasks are selected + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + const uint vi = values.at(i); + if ((vi != 0) && ((vi & newValue) == vi) && !subFlag->value().toBool()) + subFlag->setValue(true); + } + } else { + // Make sure supermasks are not selected if they're no longer valid + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + const uint vi = values.at(i); + if (subFlag->value().toBool() && ((vi & newValue) != vi)) + subFlag->setValue(false); + } + } + } + } + m_changingSubValue = false; + data.val = newValue; + QVariant v; + v.setValue(data.val); + variantProperty(flagProperty)->setValue(v); + } else if (QtProperty *alignProperty = m_alignHToProperty.value(property, 0)) { + const uint v = m_alignValues.value(alignProperty); + const uint newValue = indexHToAlign(value.toInt()) | indexVToAlign(alignToIndexV(v)); + if (v == newValue) + return; + + variantProperty(alignProperty)->setValue(newValue); + } else if (QtProperty *alignProperty = m_alignVToProperty.value(property, 0)) { + const uint v = m_alignValues.value(alignProperty); + const uint newValue = indexVToAlign(value.toInt()) | indexHToAlign(alignToIndexH(v)); + if (v == newValue) + return; + + variantProperty(alignProperty)->setValue(newValue); + } else if (QtProperty *iProperty = m_iconSubPropertyToProperty.value(property, 0)) { + QtVariantProperty *iconProperty = variantProperty(iProperty); + PropertySheetIconValue icon = qvariant_cast(iconProperty->value()); + const auto itState = m_iconSubPropertyToState.constFind(property); + if (itState != m_iconSubPropertyToState.constEnd()) { + const auto pair = m_iconSubPropertyToState.value(property); + icon.setPixmap(pair.first, pair.second, qvariant_cast(value)); + } else if (attributeValue(property, themeEnumAttributeC).toBool()) { + icon.setThemeEnum(value.toInt()); + } else { // must be theme property + icon.setTheme(value.toString()); + } + QtProperty *origSourceOfChange = m_sourceOfChange; + if (!origSourceOfChange) + m_sourceOfChange = property; + iconProperty->setValue(QVariant::fromValue(icon)); + if (!origSourceOfChange) + m_sourceOfChange = origSourceOfChange; + } else if (m_iconValues.contains(property)) { + enableSubPropertyHandling = m_sourceOfChange; + } + emit valueChanged(property, value, enableSubPropertyHandling); +} + +void DesignerPropertyManager::slotPropertyDestroyed(QtProperty *property) +{ + if (QtProperty *flagProperty = m_flagToProperty.value(property, 0)) { + const auto it = m_propertyToFlags.find(flagProperty); + auto &propertyList = it.value(); + propertyList.replace(propertyList.indexOf(property), 0); + m_flagToProperty.remove(property); + } else if (QtProperty *alignProperty = m_alignHToProperty.value(property, 0)) { + m_propertyToAlignH.remove(alignProperty); + m_alignHToProperty.remove(property); + } else if (QtProperty *alignProperty = m_alignVToProperty.value(property, 0)) { + m_propertyToAlignV.remove(alignProperty); + m_alignVToProperty.remove(property); + } else if (m_stringManager.destroy(property) + || m_stringListManager.destroy(property) + || m_keySequenceManager.destroy(property)) { + } else if (QtProperty *iconProperty = m_iconSubPropertyToProperty.value(property, 0)) { + if (m_propertyToTheme.value(iconProperty) == property) { + m_propertyToTheme.remove(iconProperty); + } else if (m_propertyToThemeEnum.value(iconProperty) == property) { + m_propertyToThemeEnum.remove(iconProperty); + } else { + const auto it = m_propertyToIconSubProperties.find(iconProperty); + const auto state = m_iconSubPropertyToState.value(property); + auto &propertyList = it.value(); + propertyList.remove(state); + m_iconSubPropertyToState.remove(property); + } + m_iconSubPropertyToProperty.remove(property); + } else { + m_fontManager.slotPropertyDestroyed(property); + m_brushManager.slotPropertyDestroyed(property); + } + m_alignDefault.remove(property); +} + +QStringList DesignerPropertyManager::attributes(int propertyType) const +{ + if (!isPropertyTypeSupported(propertyType)) + return QStringList(); + + QStringList list = QtVariantPropertyManager::attributes(propertyType); + if (propertyType == designerFlagTypeId()) { + list.append(flagsAttributeC); + } else if (propertyType == designerPixmapTypeId()) { + list.append(defaultResourceAttributeC); + } else if (propertyType == designerIconTypeId()) { + list.append(defaultResourceAttributeC); + } else if (propertyType == designerStringTypeId() || propertyType == QMetaType::QString) { + list.append(validationModesAttributeC); + list.append(fontAttributeC); + list.append(themeAttributeC); + } else if (propertyType == QMetaType::QPalette) { + list.append(superPaletteAttributeC); + } else if (propertyType == QMetaType::Int) { + list.append(themeEnumAttributeC); + } + list.append(resettableAttributeC); + return list; +} + +int DesignerPropertyManager::attributeType(int propertyType, const QString &attribute) const +{ + if (!isPropertyTypeSupported(propertyType)) + return 0; + + if (propertyType == designerFlagTypeId() && attribute == flagsAttributeC) + return designerFlagListTypeId(); + if (propertyType == designerPixmapTypeId() && attribute == defaultResourceAttributeC) + return QMetaType::QPixmap; + if (propertyType == designerIconTypeId() && attribute == defaultResourceAttributeC) + return QMetaType::QIcon; + if (attribute == resettableAttributeC) + return QMetaType::Bool; + if (propertyType == designerStringTypeId() || propertyType == QMetaType::QString) { + if (attribute == validationModesAttributeC) + return QMetaType::Int; + if (attribute == fontAttributeC) + return QMetaType::QFont; + if (attribute == themeAttributeC) + return QMetaType::Bool; + } + if (propertyType == QMetaType::QPalette && attribute == superPaletteAttributeC) + return QMetaType::QPalette; + + return QtVariantPropertyManager::attributeType(propertyType, attribute); +} + +QVariant DesignerPropertyManager::attributeValue(const QtProperty *property, const QString &attribute) const +{ + if (attribute == resettableAttributeC) { + const auto it = m_resetMap.constFind(property); + if (it != m_resetMap.constEnd()) + return it.value(); + } + + if (attribute == flagsAttributeC) { + const auto it = m_flagValues.constFind(property); + if (it != m_flagValues.constEnd()) { + QVariant v; + v.setValue(it.value().flags); + return v; + } + } + if (attribute == validationModesAttributeC) { + const auto it = m_stringAttributes.constFind(property); + if (it != m_stringAttributes.constEnd()) + return it.value(); + } + + if (attribute == fontAttributeC) { + const auto it = m_stringFontAttributes.constFind(property); + if (it != m_stringFontAttributes.constEnd()) + return it.value(); + } + + if (attribute == themeAttributeC) { + const auto it = m_stringThemeAttributes.constFind(property); + if (it != m_stringThemeAttributes.constEnd()) + return it.value(); + } + + if (attribute == themeEnumAttributeC) { + const auto it = m_intThemeEnumAttributes.constFind(property); + if (it != m_intThemeEnumAttributes.constEnd()) + return it.value(); + } + + if (attribute == superPaletteAttributeC) { + const auto it = m_paletteValues.constFind(property); + if (it != m_paletteValues.cend()) + return it.value().superPalette; + } + + if (attribute == defaultResourceAttributeC) { + const auto itPix = m_defaultPixmaps.constFind(property); + if (itPix != m_defaultPixmaps.constEnd()) + return itPix.value(); + + const auto itIcon = m_defaultIcons.constFind(property); + if (itIcon != m_defaultIcons.constEnd()) + return itIcon.value(); + } + + if (attribute == alignDefaultAttribute()) { + Qt::Alignment v = m_alignDefault.value(property, + Qt::Alignment(Qt::AlignLeading | Qt::AlignHCenter)); + return QVariant(uint(v)); + } + + return QtVariantPropertyManager::attributeValue(property, attribute); +} + +void DesignerPropertyManager::setAttribute(QtProperty *property, + const QString &attribute, const QVariant &value) +{ + if (attribute == resettableAttributeC && m_resetMap.contains(property)) { + if (value.userType() != QMetaType::Bool) + return; + const bool val = value.toBool(); + const auto it = m_resetMap.find(property); + if (it.value() == val) + return; + it.value() = val; + emit attributeChanged(variantProperty(property), attribute, value); + return; + } + if (attribute == flagsAttributeC && m_flagValues.contains(property)) { + if (value.userType() != designerFlagListTypeId()) + return; + + const DesignerFlagList flags = qvariant_cast(value); + const auto fit = m_flagValues.find(property); + FlagData data = fit.value(); + if (data.flags == flags) + return; + + const auto pfit = m_propertyToFlags.find(property); + for (QtProperty *prop : std::as_const(pfit.value())) { + if (prop) { + delete prop; + m_flagToProperty.remove(prop); + } + } + pfit.value().clear(); + + QList values; + + for (const auto &pair : flags) { + const QString flagName = pair.first; + QtProperty *prop = addProperty(QMetaType::Bool); + prop->setPropertyName(flagName); + property->addSubProperty(prop); + m_propertyToFlags[property].append(prop); + m_flagToProperty[prop] = property; + values.append(pair.second); + } + + data.val = 0; + data.flags = flags; + data.values = values; + + fit.value() = data; + + QVariant v; + v.setValue(flags); + emit attributeChanged(property, attribute, v); + + emit propertyChanged(property); + emit QtVariantPropertyManager::valueChanged(property, data.val); + } else if (attribute == validationModesAttributeC && m_stringAttributes.contains(property)) { + if (value.userType() != QMetaType::Int) + return; + + const auto it = m_stringAttributes.find(property); + const int oldValue = it.value(); + + const int newValue = value.toInt(); + + if (oldValue == newValue) + return; + + it.value() = newValue; + + emit attributeChanged(property, attribute, newValue); + } else if (attribute == fontAttributeC && m_stringFontAttributes.contains(property)) { + if (value.userType() != QMetaType::QFont) + return; + + const auto it = m_stringFontAttributes.find(property); + const QFont oldValue = it.value(); + + const QFont newValue = qvariant_cast(value); + + if (oldValue == newValue) + return; + + it.value() = newValue; + + emit attributeChanged(property, attribute, newValue); + } else if (attribute == themeAttributeC && m_stringThemeAttributes.contains(property)) { + if (value.userType() != QMetaType::Bool) + return; + + const auto it = m_stringThemeAttributes.find(property); + const bool oldValue = it.value(); + + const bool newValue = value.toBool(); + + if (oldValue == newValue) + return; + + it.value() = newValue; + + emit attributeChanged(property, attribute, newValue); + } else if (attribute == themeEnumAttributeC && m_intThemeEnumAttributes.contains(property)) { + if (value.userType() != QMetaType::Bool) + return; + + const auto it = m_intThemeEnumAttributes.find(property); + const bool oldValue = it.value(); + + const bool newValue = value.toBool(); + + if (oldValue == newValue) + return; + + it.value() = newValue; + + emit attributeChanged(property, attribute, newValue); + } else if (attribute == superPaletteAttributeC && m_paletteValues.contains(property)) { + if (value.userType() != QMetaType::QPalette) + return; + + QPalette superPalette = qvariant_cast(value); + + const auto it = m_paletteValues.find(property); + PaletteData data = it.value(); + if (data.superPalette == superPalette) + return; + + data.superPalette = superPalette; + // resolve here + const auto mask = data.val.resolveMask(); + data.val = data.val.resolve(superPalette); + data.val.setResolveMask(mask); + + it.value() = data; + + QVariant v; + v.setValue(superPalette); + emit attributeChanged(property, attribute, v); + + emit propertyChanged(property); + emit QtVariantPropertyManager::valueChanged(property, data.val); // if resolve was done, this is also for consistency + } else if (attribute == defaultResourceAttributeC && m_defaultPixmaps.contains(property)) { + if (value.userType() != QMetaType::QPixmap) + return; + + QPixmap defaultPixmap = qvariant_cast(value); + + const auto it = m_defaultPixmaps.find(property); + QPixmap oldDefaultPixmap = it.value(); + if (defaultPixmap.cacheKey() == oldDefaultPixmap.cacheKey()) + return; + + it.value() = defaultPixmap; + + QVariant v = QVariant::fromValue(defaultPixmap); + emit attributeChanged(property, attribute, v); + + emit propertyChanged(property); + } else if (attribute == defaultResourceAttributeC && m_defaultIcons.contains(property)) { + if (value.userType() != QMetaType::QIcon) + return; + + QIcon defaultIcon = qvariant_cast(value); + + const auto it = m_defaultIcons.find(property); + QIcon oldDefaultIcon = it.value(); + if (defaultIcon.cacheKey() == oldDefaultIcon.cacheKey()) + return; + + it.value() = defaultIcon; + + qdesigner_internal::PropertySheetIconValue icon = m_iconValues.value(property); + if (icon.paths().isEmpty()) { + const auto &subIconProperties = m_propertyToIconSubProperties.value(property); + for (auto itSub = subIconProperties.cbegin(), end = subIconProperties.cend(); itSub != end; ++itSub) { + const auto pair = itSub.key(); + QtProperty *subProp = itSub.value(); + setAttribute(subProp, defaultResourceAttributeC, + defaultIcon.pixmap(16, 16, pair.first, pair.second)); + } + } + + QVariant v = QVariant::fromValue(defaultIcon); + emit attributeChanged(property, attribute, v); + + emit propertyChanged(property); + } else if (attribute == alignDefaultAttribute()) { + m_alignDefault[property] = Qt::Alignment(value.toUInt()); + } + QtVariantPropertyManager::setAttribute(property, attribute, value); +} + +int DesignerPropertyManager::designerFlagTypeId() +{ + static const int rc = qMetaTypeId(); + return rc; +} + +int DesignerPropertyManager::designerFlagListTypeId() +{ + static const int rc = qMetaTypeId(); + return rc; +} + +int DesignerPropertyManager::designerAlignmentTypeId() +{ + static const int rc = qMetaTypeId(); + return rc; +} + +int DesignerPropertyManager::designerPixmapTypeId() +{ + return qMetaTypeId(); +} + +int DesignerPropertyManager::designerIconTypeId() +{ + return qMetaTypeId(); +} + +int DesignerPropertyManager::designerStringTypeId() +{ + return qMetaTypeId(); +} + +int DesignerPropertyManager::designerStringListTypeId() +{ + return qMetaTypeId(); +} + +int DesignerPropertyManager::designerKeySequenceTypeId() +{ + return qMetaTypeId(); +} + +QString DesignerPropertyManager::alignDefaultAttribute() +{ + return u"alignDefault"_s; +} + +uint DesignerPropertyManager::alignDefault(const QtVariantProperty *prop) +{ + return prop->attributeValue(DesignerPropertyManager::alignDefaultAttribute()).toUInt(); +} + +bool DesignerPropertyManager::isPropertyTypeSupported(int propertyType) const +{ + switch (propertyType) { + case QMetaType::QPalette: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::QUrl: + case QMetaType::QByteArray: + case QMetaType::QStringList: + case QMetaType::QBrush: + return true; + default: + break; + } + + if (propertyType == designerFlagTypeId()) + return true; + if (propertyType == designerAlignmentTypeId()) + return true; + if (propertyType == designerPixmapTypeId()) + return true; + if (propertyType == designerIconTypeId()) + return true; + if (propertyType == designerStringTypeId() || propertyType == designerStringListTypeId()) + return true; + if (propertyType == designerKeySequenceTypeId()) + return true; + + return QtVariantPropertyManager::isPropertyTypeSupported(propertyType); +} + +QString DesignerPropertyManager::valueText(const QtProperty *property) const +{ + if (m_flagValues.contains(property)) { + const FlagData data = m_flagValues.value(property); + const uint v = data.val; + QString valueStr; + for (const DesignerIntPair &p : data.flags) { + const uint val = p.second; + const bool checked = (val == 0) ? (v == 0) : ((val & v) == val); + if (checked) { + if (!valueStr.isEmpty()) + valueStr += u'|'; + valueStr += p.first; + } + } + return valueStr; + } + if (m_alignValues.contains(property)) { + const uint v = m_alignValues.value(property); + return tr("%1, %2").arg(indexHToString(alignToIndexH(v)), + indexVToString(alignToIndexV(v))); + } + if (m_paletteValues.contains(property)) { + const PaletteData data = m_paletteValues.value(property); + const auto mask = data.val.resolveMask(); + if (mask) + return tr("Customized (%n roles)", nullptr, bitCount(mask)); + static const QString inherited = tr("Inherited"); + return inherited; + } + if (m_iconValues.contains(property)) + return PixmapEditor::displayText(m_iconValues.value(property)); + if (m_pixmapValues.contains(property)) { + const QString path = m_pixmapValues.value(property).path(); + if (path.isEmpty()) + return QString(); + return QFileInfo(path).fileName(); + } + if (m_intValues.contains(property)) { + const auto value = m_intValues.value(property); + if (m_intThemeEnumAttributes.value(property)) + return IconThemeEnumEditor::iconName(value); + return QString::number(value); + } + if (m_uintValues.contains(property)) + return QString::number(m_uintValues.value(property)); + if (m_longLongValues.contains(property)) + return QString::number(m_longLongValues.value(property)); + if (m_uLongLongValues.contains(property)) + return QString::number(m_uLongLongValues.value(property)); + if (m_urlValues.contains(property)) + return m_urlValues.value(property).toString(); + if (m_byteArrayValues.contains(property)) + return QString::fromUtf8(m_byteArrayValues.value(property)); + const int vType = QtVariantPropertyManager::valueType(property); + if (vType == QMetaType::QString || vType == designerStringTypeId()) { + const QString str = (QtVariantPropertyManager::valueType(property) == QMetaType::QString) + ? value(property).toString() : qvariant_cast(value(property)).value(); + const int validationMode = attributeValue(property, validationModesAttributeC).toInt(); + return TextPropertyEditor::stringToEditorString(str, static_cast(validationMode)); + } + if (vType == QMetaType::QStringList || vType == designerStringListTypeId()) { + QVariant v = value(property); + const QStringList list = v.metaType().id() == QMetaType::QStringList + ? v.toStringList() : qvariant_cast(v).value(); + return list.join("; "_L1); + } + if (vType == designerKeySequenceTypeId()) { + return qvariant_cast(value(property)).value().toString(QKeySequence::NativeText); + } + if (vType == QMetaType::Bool) { + return QString(); + } + + QString rc; + if (m_brushManager.valueText(property, &rc)) + return rc; + return QtVariantPropertyManager::valueText(property); +} + +void DesignerPropertyManager::reloadResourceProperties() +{ + DesignerIconCache *iconCache = nullptr; + for (auto itIcon = m_iconValues.cbegin(), end = m_iconValues.cend(); itIcon!= end; ++itIcon) { + auto *property = itIcon.key(); + const PropertySheetIconValue &icon = itIcon.value(); + + QIcon defaultIcon = m_defaultIcons.value(property); + if (!icon.paths().isEmpty()) { + if (!iconCache) { + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + qdesigner_internal::FormWindowBase *fwb = qobject_cast(formWindow); + iconCache = fwb->iconCache(); + } + if (iconCache) + defaultIcon = iconCache->icon(icon); + } + + const auto &subProperties = m_propertyToIconSubProperties.value(property); + for (auto itSub = subProperties.cbegin(), end = subProperties.cend(); itSub != end; ++itSub) { + const auto pair = itSub.key(); + QtVariantProperty *subProperty = variantProperty(itSub.value()); + subProperty->setAttribute(defaultResourceAttributeC, + defaultIcon.pixmap(16, 16, pair.first, pair.second)); + } + + auto *ncProperty = const_cast(property); + emit propertyChanged(ncProperty); + emit QtVariantPropertyManager::valueChanged(ncProperty, QVariant::fromValue(itIcon.value())); + } + for (auto itPix = m_pixmapValues.cbegin(), end = m_pixmapValues.cend(); itPix != end; ++itPix) { + auto *property = const_cast(itPix.key()); + emit propertyChanged(property); + emit QtVariantPropertyManager::valueChanged(property, QVariant::fromValue(itPix.value())); + } +} + +QIcon DesignerPropertyManager::valueIcon(const QtProperty *property) const +{ + if (m_iconValues.contains(property)) { + if (!property->isModified()) + return m_defaultIcons.value(property).pixmap(16, 16); + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + qdesigner_internal::FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + return fwb->iconCache()->icon(m_iconValues.value(property)).pixmap(16, 16); + } else if (m_pixmapValues.contains(property)) { + if (!property->isModified()) + return m_defaultPixmaps.value(property); + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + qdesigner_internal::FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + return fwb->pixmapCache()->pixmap(m_pixmapValues.value(property)); + } else if (m_stringThemeAttributes.value(property, false)) { + return QIcon::fromTheme(value(property).toString()); + } else { + QIcon rc; + if (m_brushManager.valueIcon(property, &rc)) + return rc; + } + + return QtVariantPropertyManager::valueIcon(property); +} + +QVariant DesignerPropertyManager::value(const QtProperty *property) const +{ + if (m_flagValues.contains(property)) + return m_flagValues.value(property).val; + if (m_alignValues.contains(property)) + return m_alignValues.value(property); + if (m_paletteValues.contains(property)) + return m_paletteValues.value(property).val; + if (m_iconValues.contains(property)) + return QVariant::fromValue(m_iconValues.value(property)); + if (m_pixmapValues.contains(property)) + return QVariant::fromValue(m_pixmapValues.value(property)); + QVariant rc; + if (m_stringManager.value(property, &rc) + || m_keySequenceManager.value(property, &rc) + || m_stringListManager.value(property, &rc) + || m_brushManager.value(property, &rc)) + return rc; + if (m_intValues.contains(property)) + return m_intValues.value(property); + if (m_uintValues.contains(property)) + return m_uintValues.value(property); + if (m_longLongValues.contains(property)) + return m_longLongValues.value(property); + if (m_uLongLongValues.contains(property)) + return m_uLongLongValues.value(property); + if (m_urlValues.contains(property)) + return m_urlValues.value(property); + if (m_byteArrayValues.contains(property)) + return m_byteArrayValues.value(property); + + return QtVariantPropertyManager::value(property); +} + +int DesignerPropertyManager::valueType(int propertyType) const +{ + switch (propertyType) { + case QMetaType::QPalette: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: + case QMetaType::QUrl: + case QMetaType::QByteArray: + case QMetaType::QStringList: + case QMetaType::QBrush: + return propertyType; + default: + break; + } + if (propertyType == designerFlagTypeId()) + return QMetaType::UInt; + if (propertyType == designerAlignmentTypeId()) + return QMetaType::UInt; + if (propertyType == designerPixmapTypeId()) + return propertyType; + if (propertyType == designerIconTypeId()) + return propertyType; + if (propertyType == designerStringTypeId() || propertyType == designerStringListTypeId()) + return propertyType; + if (propertyType == designerKeySequenceTypeId()) + return propertyType; + return QtVariantPropertyManager::valueType(propertyType); +} + +void DesignerPropertyManager::setValue(QtProperty *property, const QVariant &value) +{ + int subResult = m_stringManager.setValue(this, property, designerStringTypeId(), value); + if (subResult == NoMatch) + subResult = m_stringListManager.setValue(this, property, designerStringListTypeId(), value); + if (subResult == NoMatch) + subResult = m_keySequenceManager.setValue(this, property, designerKeySequenceTypeId(), value); + if (subResult == NoMatch) + subResult = m_brushManager.setValue(this, property, value); + if (subResult != NoMatch) { + if (subResult == Changed) { + emit QtVariantPropertyManager::valueChanged(property, value); + emit propertyChanged(property); + } + return; + } + + const auto fit = m_flagValues.find(property); + + if (fit != m_flagValues.end()) { + if (value.metaType().id() != QMetaType::UInt && !value.canConvert()) + return; + + const uint v = value.toUInt(); + + FlagData data = fit.value(); + if (data.val == v) + return; + + // set Value + + const auto values = data.values; + const auto subFlags = m_propertyToFlags.value(property); + const qsizetype subFlagCount = subFlags.size(); + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + const uint val = values.at(i); + const bool checked = (val == 0) ? (v == 0) : ((val & v) == val); + subFlag->setValue(checked); + } + + for (qsizetype i = 0; i < subFlagCount; ++i) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(i)); + const uint val = values.at(i); + const bool checked = (val == 0) ? (v == 0) : ((val & v) == val); + bool enabled = true; + if (val == 0) { + if (checked) + enabled = false; + } else if (bitCount(val) > 1) { + // Disabled if all flags contained in the mask are checked + uint currentMask = 0; + for (qsizetype j = 0; j < subFlagCount; ++j) { + QtVariantProperty *subFlag = variantProperty(subFlags.at(j)); + if (bitCount(values.at(j)) == 1) + currentMask |= subFlag->value().toBool() ? values.at(j) : 0; + } + if ((currentMask & values.at(i)) == values.at(i)) + enabled = false; + } + subFlag->setEnabled(enabled); + } + + data.val = v; + fit.value() = data; + + emit QtVariantPropertyManager::valueChanged(property, data.val); + emit propertyChanged(property); + + return; + } + if (m_alignValues.contains(property)) { + if (value.metaType().id() != QMetaType::UInt && !value.canConvert()) + return; + + const uint v = value.toUInt(); + + uint val = m_alignValues.value(property); + + if (val == v) + return; + + QtVariantProperty *alignH = variantProperty(m_propertyToAlignH.value(property)); + QtVariantProperty *alignV = variantProperty(m_propertyToAlignV.value(property)); + + if (alignH) + alignH->setValue(alignToIndexH(v)); + if (alignV) + alignV->setValue(alignToIndexV(v)); + + m_alignValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_paletteValues.contains(property)) { + if (value.metaType().id() != QMetaType::QPalette && !value.canConvert()) + return; + + QPalette p = qvariant_cast(value); + + PaletteData data = m_paletteValues.value(property); + + const auto mask = p.resolveMask(); + p = p.resolve(data.superPalette); + p.setResolveMask(mask); + + if (data.val == p && data.val.resolveMask() == p.resolveMask()) + return; + + data.val = p; + m_paletteValues[property] = data; + + emit QtVariantPropertyManager::valueChanged(property, data.val); + emit propertyChanged(property); + + return; + } + if (m_iconValues.contains(property)) { + if (value.userType() != designerIconTypeId()) + return; + + const PropertySheetIconValue icon = qvariant_cast(value); + + const PropertySheetIconValue oldIcon = m_iconValues.value(property); + if (icon == oldIcon) + return; + + m_iconValues[property] = icon; + + QIcon defaultIcon = m_defaultIcons.value(property); + if (!icon.paths().isEmpty()) { + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + qdesigner_internal::FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + defaultIcon = fwb->iconCache()->icon(icon); + } + + const auto &iconPaths = icon.paths(); + + const auto &subProperties = m_propertyToIconSubProperties.value(property); + for (auto itSub = subProperties.cbegin(), end = subProperties.cend(); itSub != end; ++itSub) { + const auto pair = itSub.key(); + QtVariantProperty *subProperty = variantProperty(itSub.value()); + bool hasPath = iconPaths.contains(pair); + subProperty->setModified(hasPath); + subProperty->setValue(QVariant::fromValue(iconPaths.value(pair))); + subProperty->setAttribute(defaultResourceAttributeC, + defaultIcon.pixmap(16, 16, pair.first, pair.second)); + } + QtVariantProperty *themeSubProperty = variantProperty(m_propertyToTheme.value(property)); + if (themeSubProperty) { + const QString theme = icon.theme(); + themeSubProperty->setModified(!theme.isEmpty()); + themeSubProperty->setValue(theme); + } + QtVariantProperty *themeEnumSubProperty = variantProperty(m_propertyToThemeEnum.value(property)); + if (themeEnumSubProperty) { + const int themeEnum = icon.themeEnum(); + themeEnumSubProperty->setModified(themeEnum != -1); + themeEnumSubProperty->setValue(QVariant(themeEnum)); + } + + emit QtVariantPropertyManager::valueChanged(property, QVariant::fromValue(icon)); + emit propertyChanged(property); + + QString toolTip; + const auto itNormalOff = iconPaths.constFind({QIcon::Normal, QIcon::Off}); + if (itNormalOff != iconPaths.constEnd()) + toolTip = itNormalOff.value().path(); + // valueText() only show the file name; show full path as ToolTip. + property->setToolTip(QDir::toNativeSeparators(toolTip)); + + return; + } + if (m_pixmapValues.contains(property)) { + if (value.userType() != designerPixmapTypeId()) + return; + + const PropertySheetPixmapValue pixmap = qvariant_cast(value); + + const PropertySheetPixmapValue oldPixmap = m_pixmapValues.value(property); + if (pixmap == oldPixmap) + return; + + m_pixmapValues[property] = pixmap; + + emit QtVariantPropertyManager::valueChanged(property, QVariant::fromValue(pixmap)); + emit propertyChanged(property); + + // valueText() only show the file name; show full path as ToolTip. + property->setToolTip(QDir::toNativeSeparators(pixmap.path())); + + return; + } + if (m_intValues.contains(property)) { + if (value.metaType().id() != QMetaType::Int && !value.canConvert()) + return; + + const int v = value.toInt(nullptr); + + const int oldValue = m_intValues.value(property); + if (v == oldValue) + return; + + m_intValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_uintValues.contains(property)) { + if (value.metaType().id() != QMetaType::UInt && !value.canConvert()) + return; + + const uint v = value.toUInt(nullptr); + + const uint oldValue = m_uintValues.value(property); + if (v == oldValue) + return; + + m_uintValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_longLongValues.contains(property)) { + if (value.metaType().id() != QMetaType::LongLong && !value.canConvert()) + return; + + const qlonglong v = value.toLongLong(nullptr); + + const qlonglong oldValue = m_longLongValues.value(property); + if (v == oldValue) + return; + + m_longLongValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_uLongLongValues.contains(property)) { + if (value.metaType().id() != QMetaType::ULongLong && !value.canConvert()) + return; + + qulonglong v = value.toULongLong(nullptr); + + qulonglong oldValue = m_uLongLongValues.value(property); + if (v == oldValue) + return; + + m_uLongLongValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_urlValues.contains(property)) { + if (value.metaType().id() != QMetaType::QUrl && !value.canConvert()) + return; + + const QUrl v = value.toUrl(); + + const QUrl oldValue = m_urlValues.value(property); + if (v == oldValue) + return; + + m_urlValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + if (m_byteArrayValues.contains(property)) { + if (value.metaType().id() != QMetaType::QByteArray && !value.canConvert()) + return; + + const QByteArray v = value.toByteArray(); + + const QByteArray oldValue = m_byteArrayValues.value(property); + if (v == oldValue) + return; + + m_byteArrayValues[property] = v; + + emit QtVariantPropertyManager::valueChanged(property, v); + emit propertyChanged(property); + + return; + } + m_fontManager.setValue(this, property, value); + QtVariantPropertyManager::setValue(property, value); + if (QtVariantPropertyManager::valueType(property) == QMetaType::Bool) + property->setToolTip(QtVariantPropertyManager::valueText(property)); +} + +void DesignerPropertyManager::initializeProperty(QtProperty *property) +{ + static bool creatingIconProperties = false; + + m_resetMap[property] = false; + + const int type = propertyType(property); + m_fontManager.preInitializeProperty(property, type, m_resetMap); + switch (type) { + case QMetaType::QPalette: + m_paletteValues[property] = PaletteData(); + break; + case QMetaType::QString: + m_stringAttributes[property] = ValidationSingleLine; + m_stringFontAttributes[property] = QApplication::font(); + m_stringThemeAttributes[property] = false; + break; + case QMetaType::Int: + if (creatingIconProperties) { + m_intValues[property] = 0; + m_intThemeEnumAttributes[property] = false; + } + break; + case QMetaType::UInt: + m_uintValues[property] = 0; + break; + case QMetaType::LongLong: + m_longLongValues[property] = 0; + break; + case QMetaType::ULongLong: + m_uLongLongValues[property] = 0; + break; + case QMetaType::QUrl: + m_urlValues[property] = QUrl(); + break; + case QMetaType::QByteArray: + m_byteArrayValues[property] = QByteArray(); + break; + case QMetaType::QBrush: + m_brushManager.initializeProperty(this, property, enumTypeId()); + break; + default: + if (type == designerFlagTypeId()) { + m_flagValues[property] = FlagData(); + m_propertyToFlags[property] = QList(); + } else if (type == designerAlignmentTypeId()) { + const uint align = Qt::AlignLeft | Qt::AlignVCenter; + m_alignValues[property] = align; + + QtVariantProperty *alignH = addProperty(enumTypeId(), tr("Horizontal")); + QStringList namesH; + namesH << indexHToString(0) << indexHToString(1) << indexHToString(2) << indexHToString(3); + alignH->setAttribute(u"enumNames"_s, namesH); + alignH->setValue(alignToIndexH(align)); + m_propertyToAlignH[property] = alignH; + m_alignHToProperty[alignH] = property; + property->addSubProperty(alignH); + + QtVariantProperty *alignV = addProperty(enumTypeId(), tr("Vertical")); + QStringList namesV; + namesV << indexVToString(0) << indexVToString(1) << indexVToString(2); + alignV->setAttribute(u"enumNames"_s, namesV); + alignV->setValue(alignToIndexV(align)); + m_propertyToAlignV[property] = alignV; + m_alignVToProperty[alignV] = property; + property->addSubProperty(alignV); + } else if (type == designerPixmapTypeId()) { + m_pixmapValues[property] = PropertySheetPixmapValue(); + m_defaultPixmaps[property] = QPixmap(); + } else if (type == designerIconTypeId()) { + creatingIconProperties = true; + m_iconValues[property] = PropertySheetIconValue(); + m_defaultIcons[property] = QIcon(); + + QtVariantProperty *themeEnumProp = addProperty(QMetaType::Int, tr("Theme")); + m_intValues[themeEnumProp] = -1; + themeEnumProp->setAttribute(themeEnumAttributeC, true); + m_iconSubPropertyToProperty[themeEnumProp] = property; + m_propertyToThemeEnum[property] = themeEnumProp; + m_resetMap[themeEnumProp] = true; + property->addSubProperty(themeEnumProp); + + QtVariantProperty *themeProp = addProperty(QMetaType::QString, tr("XDG Theme")); + themeProp->setAttribute(themeAttributeC, true); + m_iconSubPropertyToProperty[themeProp] = property; + m_propertyToTheme[property] = themeProp; + m_resetMap[themeProp] = true; + property->addSubProperty(themeProp); + + createIconSubProperty(property, QIcon::Normal, QIcon::Off, tr("Normal Off")); + createIconSubProperty(property, QIcon::Normal, QIcon::On, tr("Normal On")); + createIconSubProperty(property, QIcon::Disabled, QIcon::Off, tr("Disabled Off")); + createIconSubProperty(property, QIcon::Disabled, QIcon::On, tr("Disabled On")); + createIconSubProperty(property, QIcon::Active, QIcon::Off, tr("Active Off")); + createIconSubProperty(property, QIcon::Active, QIcon::On, tr("Active On")); + createIconSubProperty(property, QIcon::Selected, QIcon::Off, tr("Selected Off")); + createIconSubProperty(property, QIcon::Selected, QIcon::On, tr("Selected On")); + creatingIconProperties = false; + } else if (type == designerStringTypeId()) { + m_stringManager.initialize(this, property, PropertySheetStringValue()); + m_stringAttributes.insert(property, ValidationMultiLine); + m_stringFontAttributes.insert(property, QApplication::font()); + m_stringThemeAttributes.insert(property, false); + } else if (type == designerStringListTypeId()) { + m_stringListManager.initialize(this, property, PropertySheetStringListValue()); + } else if (type == designerKeySequenceTypeId()) { + m_keySequenceManager.initialize(this, property, PropertySheetKeySequenceValue()); + } + } + + QtVariantPropertyManager::initializeProperty(property); + m_fontManager.postInitializeProperty(this, property, type, DesignerPropertyManager::enumTypeId()); + if (type == QMetaType::Double) + setAttribute(property, u"decimals"_s, 6); +} + +void DesignerPropertyManager::createIconSubProperty(QtProperty *iconProperty, QIcon::Mode mode, QIcon::State state, const QString &subName) +{ + const auto pair = std::make_pair(mode, state); + QtVariantProperty *subProp = addProperty(DesignerPropertyManager::designerPixmapTypeId(), subName); + m_propertyToIconSubProperties[iconProperty][pair] = subProp; + m_iconSubPropertyToState[subProp] = pair; + m_iconSubPropertyToProperty[subProp] = iconProperty; + m_resetMap[subProp] = true; + iconProperty->addSubProperty(subProp); +} + +void DesignerPropertyManager::uninitializeProperty(QtProperty *property) +{ + m_resetMap.remove(property); + + const auto propList = m_propertyToFlags.value(property); + for (QtProperty *prop : propList) { + if (prop) { + delete prop; + m_flagToProperty.remove(prop); + } + } + m_propertyToFlags.remove(property); + m_flagValues.remove(property); + + QtProperty *alignH = m_propertyToAlignH.value(property); + if (alignH) { + delete alignH; + m_alignHToProperty.remove(alignH); + } + QtProperty *alignV = m_propertyToAlignV.value(property); + if (alignV) { + delete alignV; + m_alignVToProperty.remove(alignV); + } + + m_stringManager.uninitialize(property); + m_stringListManager.uninitialize(property); + m_keySequenceManager.uninitialize(property); + + if (QtProperty *iconTheme = m_propertyToTheme.value(property)) { + delete iconTheme; // Delete first (QTBUG-126182) + m_iconSubPropertyToProperty.remove(iconTheme); + } + + if (QtProperty *iconThemeEnum = m_propertyToThemeEnum.value(property)) { + delete iconThemeEnum; // Delete first (QTBUG-126182) + m_iconSubPropertyToProperty.remove(iconThemeEnum); + } + + m_propertyToAlignH.remove(property); + m_propertyToAlignV.remove(property); + + m_stringAttributes.remove(property); + m_stringFontAttributes.remove(property); + + m_paletteValues.remove(property); + + m_iconValues.remove(property); + m_defaultIcons.remove(property); + + m_pixmapValues.remove(property); + m_defaultPixmaps.remove(property); + + const auto &iconSubProperties = m_propertyToIconSubProperties.value(property); + for (auto itIcon = iconSubProperties.cbegin(), end = iconSubProperties.cend(); itIcon != end; ++itIcon) { + QtProperty *subIcon = itIcon.value(); + delete subIcon; + m_iconSubPropertyToState.remove(subIcon); + m_iconSubPropertyToProperty.remove(subIcon); + } + m_propertyToIconSubProperties.remove(property); + m_iconSubPropertyToState.remove(property); + m_iconSubPropertyToProperty.remove(property); + + m_intValues.remove(property); + m_uintValues.remove(property); + m_longLongValues.remove(property); + m_uLongLongValues.remove(property); + m_urlValues.remove(property); + m_byteArrayValues.remove(property); + + m_fontManager.uninitializeProperty(property); + m_brushManager.uninitializeProperty(property); + + QtVariantPropertyManager::uninitializeProperty(property); +} + +bool DesignerPropertyManager::resetTextAlignmentProperty(QtProperty *property) +{ + const auto it = m_alignDefault.constFind(property); + if (it == m_alignDefault.cend()) + return false; + QtVariantProperty *alignProperty = variantProperty(property); + alignProperty->setValue(DesignerPropertyManager::alignDefault(alignProperty)); + alignProperty->setModified(false); + return true; +} + +bool DesignerPropertyManager::resetFontSubProperty(QtProperty *property) +{ + return m_fontManager.resetFontSubProperty(this, property); +} + +bool DesignerPropertyManager::resetIconSubProperty(QtProperty *property) +{ + QtProperty *iconProperty = m_iconSubPropertyToProperty.value(property); + if (!iconProperty) + return false; + + if (m_pixmapValues.contains(property)) { + QtVariantProperty *pixmapProperty = variantProperty(property); + pixmapProperty->setValue(QVariant::fromValue(PropertySheetPixmapValue())); + return true; + } + if (attributeValue(property, themeAttributeC).toBool()) { + QtVariantProperty *themeProperty = variantProperty(property); + themeProperty->setValue(QString()); + return true; + } + if (attributeValue(property, themeEnumAttributeC).toBool()) { + QtVariantProperty *themeEnumProperty = variantProperty(property); + themeEnumProperty->setValue(-1); + return true; + } + + return false; +} + +// -------- DesignerEditorFactory +DesignerEditorFactory::DesignerEditorFactory(QDesignerFormEditorInterface *core, QObject *parent) : + QtVariantEditorFactory(parent), + m_resetDecorator(new ResetDecorator(core, this)), + m_core(core) +{ + connect(m_resetDecorator, &ResetDecorator::resetProperty, + this, &DesignerEditorFactory::resetProperty); +} + +DesignerEditorFactory::~DesignerEditorFactory() = default; + +void DesignerEditorFactory::setSpacing(int spacing) +{ + m_spacing = spacing; + m_resetDecorator->setSpacing(spacing); +} + +void DesignerEditorFactory::setFormWindowBase(qdesigner_internal::FormWindowBase *fwb) +{ + m_fwb = fwb; + DesignerPixmapCache *cache = nullptr; + if (fwb) + cache = fwb->pixmapCache(); + for (auto it = m_editorToPixmapProperty.cbegin(), end = m_editorToPixmapProperty.cend(); it != end; ++it) + it.key()->setPixmapCache(cache); + for (auto it = m_editorToIconProperty.cbegin(), end = m_editorToIconProperty.cend(); it != end; ++it) + it.key()->setPixmapCache(cache); +} + +void DesignerEditorFactory::connectPropertyManager(QtVariantPropertyManager *manager) +{ + m_resetDecorator->connectPropertyManager(manager); + connect(manager, &QtVariantPropertyManager::attributeChanged, + this, &DesignerEditorFactory::slotAttributeChanged); + connect(manager, &QtVariantPropertyManager::valueChanged, + this, &DesignerEditorFactory::slotValueChanged); + connect(manager, &QtVariantPropertyManager::propertyChanged, + this, &DesignerEditorFactory::slotPropertyChanged); + QtVariantEditorFactory::connectPropertyManager(manager); +} + +void DesignerEditorFactory::disconnectPropertyManager(QtVariantPropertyManager *manager) +{ + m_resetDecorator->disconnectPropertyManager(manager); + disconnect(manager, &QtVariantPropertyManager::attributeChanged, + this, &DesignerEditorFactory::slotAttributeChanged); + disconnect(manager, &QtVariantPropertyManager::valueChanged, + this, &DesignerEditorFactory::slotValueChanged); + disconnect(manager, &QtVariantPropertyManager::propertyChanged, + this, &DesignerEditorFactory::slotPropertyChanged); + QtVariantEditorFactory::disconnectPropertyManager(manager); +} + +// A helper that calls a setter with a value on a pointer list of editor objects. +// Could use QList instead of EditorContainer/Editor, but that crashes VS 6. +template +static inline void applyToEditors(const EditorContainer &list, void (Editor::*setter)(SetterParameter), const Value &value) +{ + if (list.isEmpty()) { + return; + } + for (auto it = list.constBegin(), end = list.constEnd(); it != end; ++it) { + Editor &editor = *(*it); + (editor.*setter)(value); + } +} + +void DesignerEditorFactory::slotAttributeChanged(QtProperty *property, const QString &attribute, const QVariant &value) +{ + QtVariantPropertyManager *manager = propertyManager(property); + const int type = manager->propertyType(property); + if (type == DesignerPropertyManager::designerPixmapTypeId() && attribute == defaultResourceAttributeC) { + const QPixmap pixmap = qvariant_cast(value); + applyToEditors(m_pixmapPropertyToEditors.value(property), &PixmapEditor::setDefaultPixmap, pixmap); + } else if (type == DesignerPropertyManager::designerStringTypeId() || type == QMetaType::QString) { + if (attribute == validationModesAttributeC) { + const TextPropertyValidationMode validationMode = static_cast(value.toInt()); + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setTextPropertyValidationMode, validationMode); + } + if (attribute == fontAttributeC) { + const QFont font = qvariant_cast(value); + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setRichTextDefaultFont, font); + } + if (attribute == themeAttributeC) { + const bool themeEnabled = value.toBool(); + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setIconThemeModeEnabled, themeEnabled); + } + } else if (type == QMetaType::QPalette && attribute == superPaletteAttributeC) { + const QPalette palette = qvariant_cast(value); + applyToEditors(m_palettePropertyToEditors.value(property), &PaletteEditorButton::setSuperPalette, palette); + } +} + +void DesignerEditorFactory::slotPropertyChanged(QtProperty *property) +{ + QtVariantPropertyManager *manager = propertyManager(property); + const int type = manager->propertyType(property); + if (type == DesignerPropertyManager::designerIconTypeId()) { + QIcon defaultPixmap; + if (!property->isModified()) { + const auto attributeValue = manager->attributeValue(property, defaultResourceAttributeC); + defaultPixmap = attributeValue.value(); + } else if (m_fwb) { + const auto value = manager->value(property); + defaultPixmap = m_fwb->iconCache()->icon(value.value()); + } + const auto editors = m_iconPropertyToEditors.value(property); + for (PixmapEditor *editor : editors) + editor->setDefaultPixmapIcon(defaultPixmap); + } +} + +void DesignerEditorFactory::slotValueChanged(QtProperty *property, const QVariant &value) +{ + if (m_changingPropertyValue) + return; + + QtVariantPropertyManager *manager = propertyManager(property); + const int type = manager->propertyType(property); + switch (type) { + case QMetaType::QString: + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setText, value.toString()); + break; + case QMetaType::QPalette: + applyToEditors(m_palettePropertyToEditors.value(property), &PaletteEditorButton::setPalette, qvariant_cast(value)); + break; + case QMetaType::Int: { + auto it = m_intPropertyToComboEditors.constFind(property); + if (it != m_intPropertyToComboEditors.cend()) + applyToEditors(it.value(), &QComboBox::setCurrentIndex, value.toInt()); + } + break; + case QMetaType::UInt: + applyToEditors(m_uintPropertyToEditors.value(property), &QLineEdit::setText, QString::number(value.toUInt())); + break; + case QMetaType::LongLong: + applyToEditors(m_longLongPropertyToEditors.value(property), &QLineEdit::setText, QString::number(value.toLongLong())); + break; + case QMetaType::ULongLong: + applyToEditors(m_uLongLongPropertyToEditors.value(property), &QLineEdit::setText, QString::number(value.toULongLong())); + break; + case QMetaType::QUrl: + applyToEditors(m_urlPropertyToEditors.value(property), &TextEditor::setText, value.toUrl().toString()); + break; + case QMetaType::QByteArray: + applyToEditors(m_byteArrayPropertyToEditors.value(property), &TextEditor::setText, QString::fromUtf8(value.toByteArray())); + break; + case QMetaType::QStringList: + applyToEditors(m_stringListPropertyToEditors.value(property), &StringListEditorButton::setStringList, value.toStringList()); + break; + default: + if (type == DesignerPropertyManager::designerIconTypeId()) { + PropertySheetIconValue iconValue = qvariant_cast(value); + applyToEditors(m_iconPropertyToEditors.value(property), &PixmapEditor::setTheme, iconValue.theme()); + applyToEditors(m_iconPropertyToEditors.value(property), &PixmapEditor::setThemeEnum, iconValue.themeEnum()); + applyToEditors(m_iconPropertyToEditors.value(property), &PixmapEditor::setPath, iconValue.pixmap(QIcon::Normal, QIcon::Off).path()); + } else if (type == DesignerPropertyManager::designerPixmapTypeId()) { + applyToEditors(m_pixmapPropertyToEditors.value(property), &PixmapEditor::setPath, qvariant_cast(value).path()); + } else if (type == DesignerPropertyManager::designerStringTypeId()) { + applyToEditors(m_stringPropertyToEditors.value(property), &TextEditor::setText, qvariant_cast(value).value()); + } else if (type == DesignerPropertyManager::designerStringListTypeId()) { + applyToEditors(m_stringListPropertyToEditors.value(property), &StringListEditorButton::setStringList, qvariant_cast(value).value()); + } else if (type == DesignerPropertyManager::designerKeySequenceTypeId()) { + applyToEditors(m_keySequencePropertyToEditors.value(property), &QKeySequenceEdit::setKeySequence, qvariant_cast(value).value()); + } + break; + } +} + +TextEditor *DesignerEditorFactory::createTextEditor(QWidget *parent, TextPropertyValidationMode vm, const QString &value) +{ + TextEditor *rc = new TextEditor(m_core, parent); + rc->setText(value); + rc->setSpacing(m_spacing); + rc->setTextPropertyValidationMode(vm); + connect(rc, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + return rc; +} + +QWidget *DesignerEditorFactory::createEditor(QtVariantPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + QWidget *editor = nullptr; + const int type = manager->propertyType(property); + switch (type) { + case QMetaType::Bool: { + editor = QtVariantEditorFactory::createEditor(manager, property, parent); + QtBoolEdit *boolEdit = qobject_cast(editor); + if (boolEdit) + boolEdit->setTextVisible(false); + } + break; + case QMetaType::QString: { + const int itvm = manager->attributeValue(property, validationModesAttributeC).toInt(); + const auto tvm = static_cast(itvm); + TextEditor *ed = createTextEditor(parent, tvm, manager->value(property).toString()); + const QVariant richTextDefaultFont = manager->attributeValue(property, fontAttributeC); + if (richTextDefaultFont.metaType().id() == QMetaType::QFont) + ed->setRichTextDefaultFont(qvariant_cast(richTextDefaultFont)); + const bool themeEnabled = manager->attributeValue(property, themeAttributeC).toBool(); + ed->setIconThemeModeEnabled(themeEnabled); + m_stringPropertyToEditors[property].append(ed); + m_editorToStringProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &TextEditor::textChanged, this, &DesignerEditorFactory::slotStringTextChanged); + editor = ed; + } + break; + case QMetaType::QPalette: { + PaletteEditorButton *ed = new PaletteEditorButton(m_core, qvariant_cast(manager->value(property)), parent); + ed->setSuperPalette(qvariant_cast(manager->attributeValue(property, superPaletteAttributeC))); + m_palettePropertyToEditors[property].append(ed); + m_editorToPaletteProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &PaletteEditorButton::paletteChanged, this, &DesignerEditorFactory::slotPaletteChanged); + editor = ed; + } + break; + case QMetaType::Int: + if (manager->attributeValue(property, themeEnumAttributeC).toBool()) { + auto *ed = IconThemeEnumEditor::createComboBox(parent); + ed->setCurrentIndex(manager->value(property).toInt()); + connect(ed, &QComboBox::currentIndexChanged, this, + &DesignerEditorFactory::slotIntChanged); + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + m_intPropertyToComboEditors[property].append(ed); + m_comboEditorToIntProperty.insert(ed, property); + editor = ed; + } else { + editor = QtVariantEditorFactory::createEditor(manager, property, parent); + } + break; + case QMetaType::UInt: { + QLineEdit *ed = new QLineEdit(parent); + ed->setValidator(new QULongLongValidator(0, UINT_MAX, ed)); + ed->setText(QString::number(manager->value(property).toUInt())); + m_uintPropertyToEditors[property].append(ed); + m_editorToUintProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &QLineEdit::textChanged, this, &DesignerEditorFactory::slotUintChanged); + editor = ed; + } + break; + case QMetaType::LongLong: { + QLineEdit *ed = new QLineEdit(parent); + ed->setValidator(new QLongLongValidator(ed)); + ed->setText(QString::number(manager->value(property).toLongLong())); + m_longLongPropertyToEditors[property].append(ed); + m_editorToLongLongProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &QLineEdit::textChanged, this, &DesignerEditorFactory::slotLongLongChanged); + editor = ed; + } + break; + case QMetaType::ULongLong: { + QLineEdit *ed = new QLineEdit(parent); + ed->setValidator(new QULongLongValidator(ed)); + ed->setText(QString::number(manager->value(property).toULongLong())); + m_uLongLongPropertyToEditors[property].append(ed); + m_editorToULongLongProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &QLineEdit::textChanged, this, &DesignerEditorFactory::slotULongLongChanged); + editor = ed; + } + break; + case QMetaType::QUrl: { + TextEditor *ed = createTextEditor(parent, ValidationURL, manager->value(property).toUrl().toString()); + ed->setUpdateMode(TextPropertyEditor::UpdateOnFinished); + m_urlPropertyToEditors[property].append(ed); + m_editorToUrlProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &TextEditor::textChanged, this, &DesignerEditorFactory::slotUrlChanged); + editor = ed; + } + break; + case QMetaType::QByteArray: { + TextEditor *ed = createTextEditor(parent, ValidationMultiLine, QString::fromUtf8(manager->value(property).toByteArray())); + m_byteArrayPropertyToEditors[property].append(ed); + m_editorToByteArrayProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &TextEditor::textChanged, this, &DesignerEditorFactory::slotByteArrayChanged); + editor = ed; + } + break; + default: + if (type == DesignerPropertyManager::designerPixmapTypeId()) { + PixmapEditor *ed = new PixmapEditor(m_core, parent); + ed->setPixmapCache(m_fwb->pixmapCache()); + ed->setPath(qvariant_cast(manager->value(property)).path()); + ed->setDefaultPixmap(qvariant_cast(manager->attributeValue(property, defaultResourceAttributeC))); + ed->setSpacing(m_spacing); + m_pixmapPropertyToEditors[property].append(ed); + m_editorToPixmapProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &PixmapEditor::pathChanged, this, &DesignerEditorFactory::slotPixmapChanged); + editor = ed; + } else if (type == DesignerPropertyManager::designerIconTypeId()) { + PixmapEditor *ed = new PixmapEditor(m_core, parent); + ed->setPixmapCache(m_fwb->pixmapCache()); + ed->setIconThemeModeEnabled(true); + PropertySheetIconValue value = qvariant_cast(manager->value(property)); + ed->setTheme(value.theme()); + ed->setThemeEnum(value.themeEnum()); + ed->setPath(value.pixmap(QIcon::Normal, QIcon::Off).path()); + QIcon defaultPixmap; + if (!property->isModified()) + defaultPixmap = qvariant_cast(manager->attributeValue(property, defaultResourceAttributeC)); + else if (m_fwb) + defaultPixmap = m_fwb->iconCache()->icon(value); + ed->setDefaultPixmapIcon(defaultPixmap); + ed->setSpacing(m_spacing); + m_iconPropertyToEditors[property].append(ed); + m_editorToIconProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &PixmapEditor::pathChanged, this, &DesignerEditorFactory::slotIconChanged); + connect(ed, &PixmapEditor::themeChanged, this, &DesignerEditorFactory::slotIconThemeChanged); + connect(ed, &PixmapEditor::themeEnumChanged, this, &DesignerEditorFactory::slotIconThemeEnumChanged); + editor = ed; + } else if (type == DesignerPropertyManager::designerStringTypeId()) { + const TextPropertyValidationMode tvm = static_cast(manager->attributeValue(property, validationModesAttributeC).toInt()); + TextEditor *ed = createTextEditor(parent, tvm, qvariant_cast(manager->value(property)).value()); + const QVariant richTextDefaultFont = manager->attributeValue(property, fontAttributeC); + if (richTextDefaultFont.metaType().id() == QMetaType::QFont) + ed->setRichTextDefaultFont(qvariant_cast(richTextDefaultFont)); + m_stringPropertyToEditors[property].append(ed); + m_editorToStringProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &TextEditor::textChanged, this, &DesignerEditorFactory::slotStringTextChanged); + editor = ed; + } else if (type == DesignerPropertyManager::designerStringListTypeId() || type == QMetaType::QStringList) { + const QVariant variantValue = manager->value(property); + const QStringList value = type == QMetaType::QStringList + ? variantValue.toStringList() : qvariant_cast(variantValue).value(); + StringListEditorButton *ed = new StringListEditorButton(value, parent); + m_stringListPropertyToEditors[property].append(ed); + m_editorToStringListProperty.insert(ed, property); + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &StringListEditorButton::stringListChanged, this, &DesignerEditorFactory::slotStringListChanged); + editor = ed; + } else if (type == DesignerPropertyManager::designerKeySequenceTypeId()) { + QKeySequenceEdit *ed = new QKeySequenceEdit(parent); + ed->setKeySequence(qvariant_cast(manager->value(property)).value()); + m_keySequencePropertyToEditors[property].append(ed); + m_editorToKeySequenceProperty[ed] = property; + connect(ed, &QObject::destroyed, this, &DesignerEditorFactory::slotEditorDestroyed); + connect(ed, &QKeySequenceEdit::keySequenceChanged, this, &DesignerEditorFactory::slotKeySequenceChanged); + editor = ed; + } else { + editor = QtVariantEditorFactory::createEditor(manager, property, parent); + } + break; + } + return m_resetDecorator->editor(editor, + manager->variantProperty(property)->attributeValue(resettableAttributeC).toBool(), + manager, property, parent); +} + +template +bool removeEditor(QObject *object, + QHash> *propertyToEditors, + QHash *editorToProperty) +{ + if (!propertyToEditors) + return false; + if (!editorToProperty) + return false; + for (auto e2pIt = editorToProperty->begin(), end = editorToProperty->end(); e2pIt != end; ++e2pIt) { + Editor editor = e2pIt.key(); + if (editor == object) { + const auto p2eIt = propertyToEditors->find(e2pIt.value()); + if (p2eIt != propertyToEditors->end()) { + p2eIt.value().removeAll(editor); + if (p2eIt.value().isEmpty()) + propertyToEditors->erase(p2eIt); + } + editorToProperty->erase(e2pIt); + return true; + } + } + return false; +} + +void DesignerEditorFactory::slotEditorDestroyed(QObject *object) +{ + if (removeEditor(object, &m_stringPropertyToEditors, &m_editorToStringProperty)) + return; + if (removeEditor(object, &m_keySequencePropertyToEditors, &m_editorToKeySequenceProperty)) + return; + if (removeEditor(object, &m_palettePropertyToEditors, &m_editorToPaletteProperty)) + return; + if (removeEditor(object, &m_pixmapPropertyToEditors, &m_editorToPixmapProperty)) + return; + if (removeEditor(object, &m_iconPropertyToEditors, &m_editorToIconProperty)) + return; + if (removeEditor(object, &m_uintPropertyToEditors, &m_editorToUintProperty)) + return; + if (removeEditor(object, &m_longLongPropertyToEditors, &m_editorToLongLongProperty)) + return; + if (removeEditor(object, &m_intPropertyToComboEditors, &m_comboEditorToIntProperty)) + return; + if (removeEditor(object, &m_uLongLongPropertyToEditors, &m_editorToULongLongProperty)) + return; + if (removeEditor(object, &m_urlPropertyToEditors, &m_editorToUrlProperty)) + return; + if (removeEditor(object, &m_byteArrayPropertyToEditors, &m_editorToByteArrayProperty)) + return; + if (removeEditor(object, &m_stringListPropertyToEditors, &m_editorToStringListProperty)) + return; +} + +template +bool updateManager(QtVariantEditorFactory *factory, bool *changingPropertyValue, + const QHash &editorToProperty, QWidget *editor, const QVariant &value) +{ + if (!editor) + return false; + for (auto it = editorToProperty.cbegin(), end = editorToProperty.cend(); it != end; ++it) { + if (it.key() == editor) { + QtProperty *prop = it.value(); + QtVariantPropertyManager *manager = factory->propertyManager(prop); + *changingPropertyValue = true; + manager->variantProperty(prop)->setValue(value); + *changingPropertyValue = false; + return true; + } + } + return false; +} + +void DesignerEditorFactory::slotUintChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToUintProperty, qobject_cast(sender()), value.toUInt()); +} + +void DesignerEditorFactory::slotLongLongChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToLongLongProperty, qobject_cast(sender()), value.toLongLong()); +} + +void DesignerEditorFactory::slotIntChanged(int v) +{ + updateManager(this, &m_changingPropertyValue, m_comboEditorToIntProperty, + qobject_cast(sender()), v); +} + +void DesignerEditorFactory::slotULongLongChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToULongLongProperty, qobject_cast(sender()), value.toULongLong()); +} + +void DesignerEditorFactory::slotUrlChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToUrlProperty, qobject_cast(sender()), QUrl(value)); +} + +void DesignerEditorFactory::slotByteArrayChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToByteArrayProperty, qobject_cast(sender()), value.toUtf8()); +} + +template +QtProperty *findPropertyForEditor(const QHash &editorMap, + const QObject *sender) +{ + for (auto it = editorMap.constBegin(), cend = editorMap.constEnd(); it != cend; ++it) + if (it.key() == sender) + return it.value(); + return nullptr; +} + +void DesignerEditorFactory::slotStringTextChanged(const QString &value) +{ + if (QtProperty *prop = findPropertyForEditor(m_editorToStringProperty, sender())) { + QtVariantPropertyManager *manager = propertyManager(prop); + QtVariantProperty *varProp = manager->variantProperty(prop); + QVariant val = varProp->value(); + if (val.userType() == DesignerPropertyManager::designerStringTypeId()) { + PropertySheetStringValue strVal = qvariant_cast(val); + strVal.setValue(value); + // Disable translation if no translation subproperties exist. + if (varProp->subProperties().isEmpty()) + strVal.setTranslatable(false); + val = QVariant::fromValue(strVal); + } else { + val = QVariant(value); + } + m_changingPropertyValue = true; + manager->variantProperty(prop)->setValue(val); + m_changingPropertyValue = false; + } +} + +void DesignerEditorFactory::slotKeySequenceChanged(const QKeySequence &value) +{ + if (QtProperty *prop = findPropertyForEditor(m_editorToKeySequenceProperty, sender())) { + QtVariantPropertyManager *manager = propertyManager(prop); + QtVariantProperty *varProp = manager->variantProperty(prop); + QVariant val = varProp->value(); + if (val.userType() == DesignerPropertyManager::designerKeySequenceTypeId()) { + PropertySheetKeySequenceValue keyVal = qvariant_cast(val); + keyVal.setValue(value); + val = QVariant::fromValue(keyVal); + } else { + val = QVariant::fromValue(value); + } + m_changingPropertyValue = true; + manager->variantProperty(prop)->setValue(val); + m_changingPropertyValue = false; + } +} + +void DesignerEditorFactory::slotPaletteChanged(const QPalette &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToPaletteProperty, qobject_cast(sender()), QVariant::fromValue(value)); +} + +void DesignerEditorFactory::slotPixmapChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToPixmapProperty, qobject_cast(sender()), + QVariant::fromValue(PropertySheetPixmapValue(value))); +} + +void DesignerEditorFactory::slotIconChanged(const QString &value) +{ + updateManager(this, &m_changingPropertyValue, m_editorToIconProperty, qobject_cast(sender()), + QVariant::fromValue(PropertySheetIconValue(PropertySheetPixmapValue(value)))); +} + +void DesignerEditorFactory::slotIconThemeChanged(const QString &value) +{ + PropertySheetIconValue icon; + icon.setTheme(value); + updateManager(this, &m_changingPropertyValue, m_editorToIconProperty, qobject_cast(sender()), + QVariant::fromValue(icon)); +} + +void DesignerEditorFactory::slotIconThemeEnumChanged(int value) +{ + PropertySheetIconValue icon; + icon.setThemeEnum(value); + updateManager(this, &m_changingPropertyValue, m_editorToIconProperty, + qobject_cast(sender()), QVariant::fromValue(icon)); +} + +void DesignerEditorFactory::slotStringListChanged(const QStringList &value) +{ + if (QtProperty *prop = findPropertyForEditor(m_editorToStringListProperty, sender())) { + QtVariantPropertyManager *manager = propertyManager(prop); + QtVariantProperty *varProp = manager->variantProperty(prop); + QVariant val = varProp->value(); + if (val.userType() == DesignerPropertyManager::designerStringListTypeId()) { + PropertySheetStringListValue listValue = qvariant_cast(val); + listValue.setValue(value); + // Disable translation if no translation subproperties exist. + if (varProp->subProperties().isEmpty()) + listValue.setTranslatable(false); + val = QVariant::fromValue(listValue); + } else { + val = QVariant(value); + } + m_changingPropertyValue = true; + manager->variantProperty(prop)->setValue(val); + m_changingPropertyValue = false; + } +} + +ResetDecorator::ResetDecorator(const QDesignerFormEditorInterface *core, QObject *parent) + : QObject(parent) + , m_spacing(-1) + , m_core(core) +{ +} + +ResetDecorator::~ResetDecorator() +{ + const auto editors = m_resetWidgetToProperty.keys(); + qDeleteAll(editors); +} + +void ResetDecorator::connectPropertyManager(QtAbstractPropertyManager *manager) +{ + connect(manager, &QtAbstractPropertyManager::propertyChanged, + this, &ResetDecorator::slotPropertyChanged); +} + +void ResetDecorator::disconnectPropertyManager(QtAbstractPropertyManager *manager) +{ + disconnect(manager, &QtAbstractPropertyManager::propertyChanged, + this, &ResetDecorator::slotPropertyChanged); +} + +void ResetDecorator::setSpacing(int spacing) +{ + m_spacing = spacing; +} + +static inline bool isModifiedInMultiSelection(const QDesignerFormEditorInterface *core, + const QString &propertyName) +{ + const QDesignerFormWindowInterface *form = core->formWindowManager()->activeFormWindow(); + if (!form) + return false; + const QDesignerFormWindowCursorInterface *cursor = form->cursor(); + const int selectionSize = cursor->selectedWidgetCount(); + if (selectionSize < 2) + return false; + for (int i = 0; i < selectionSize; ++i) { + const QDesignerPropertySheetExtension *sheet = + qt_extension(core->extensionManager(), + cursor->selectedWidget(i)); + const int index = sheet->indexOf(propertyName); + if (index >= 0 && sheet->isChanged(index)) + return true; + } + return false; +} + +QWidget *ResetDecorator::editor(QWidget *subEditor, bool resettable, QtAbstractPropertyManager *manager, QtProperty *property, + QWidget *parent) +{ + Q_UNUSED(manager); + + ResetWidget *resetWidget = nullptr; + if (resettable) { + resetWidget = new ResetWidget(property, parent); + resetWidget->setSpacing(m_spacing); + resetWidget->setResetEnabled(property->isModified() || isModifiedInMultiSelection(m_core, property->propertyName())); + resetWidget->setValueText(property->valueText()); + resetWidget->setValueIcon(property->valueIcon()); + resetWidget->setAutoFillBackground(true); + connect(resetWidget, &QObject::destroyed, this, &ResetDecorator::slotEditorDestroyed); + connect(resetWidget, &ResetWidget::resetProperty, this, &ResetDecorator::resetProperty); + m_createdResetWidgets[property].append(resetWidget); + m_resetWidgetToProperty[resetWidget] = property; + } + if (subEditor) { + if (resetWidget) { + subEditor->setParent(resetWidget); + resetWidget->setWidget(subEditor); + } + } + if (resetWidget) + return resetWidget; + return subEditor; +} + +void ResetDecorator::slotPropertyChanged(QtProperty *property) +{ + const auto prIt = m_createdResetWidgets.constFind(property); + if (prIt == m_createdResetWidgets.constEnd()) + return; + + for (ResetWidget *widget : prIt.value()) { + widget->setResetEnabled(property->isModified() || isModifiedInMultiSelection(m_core, property->propertyName())); + widget->setValueText(property->valueText()); + widget->setValueIcon(property->valueIcon()); + } +} + +void ResetDecorator::slotEditorDestroyed(QObject *object) +{ + for (auto itEditor = m_resetWidgetToProperty.cbegin(), cend = m_resetWidgetToProperty.cend(); itEditor != cend; ++itEditor) { + if (itEditor.key() == object) { + ResetWidget *editor = itEditor.key(); + QtProperty *property = itEditor.value(); + m_resetWidgetToProperty.remove(editor); + m_createdResetWidgets[property].removeAll(editor); + if (m_createdResetWidgets[property].isEmpty()) + m_createdResetWidgets.remove(property); + return; + } + } +} + +} + +QT_END_NAMESPACE + +#include "designerpropertymanager.moc" diff --git a/src/tools/designer/src/components/propertyeditor/designerpropertymanager.h b/src/tools/designer/src/components/propertyeditor/designerpropertymanager.h new file mode 100644 index 00000000000..e13a9ef4a3a --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/designerpropertymanager.h @@ -0,0 +1,308 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DESIGNERPROPERTYMANAGER_H +#define DESIGNERPROPERTYMANAGER_H + +#include "qtvariantproperty_p.h" +#include "brushpropertymanager.h" +#include "fontpropertymanager.h" + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using DesignerIntPair = std::pair; +using DesignerFlagList = QList; + +class QComboBox; +class QDesignerFormEditorInterface; +class QLineEdit; +class QUrl; +class QKeySequenceEdit; + +namespace qdesigner_internal +{ + +class ResetWidget; + +class TextEditor; +class PaletteEditorButton; +class PixmapEditor; +class StringListEditorButton; +class FormWindowBase; + +class ResetDecorator : public QObject +{ + Q_OBJECT +public: + explicit ResetDecorator(const QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~ResetDecorator(); + + void connectPropertyManager(QtAbstractPropertyManager *manager); + QWidget *editor(QWidget *subEditor, bool resettable, QtAbstractPropertyManager *manager, QtProperty *property, + QWidget *parent); + void disconnectPropertyManager(QtAbstractPropertyManager *manager); + void setSpacing(int spacing); +signals: + void resetProperty(QtProperty *property); +private slots: + void slotPropertyChanged(QtProperty *property); + void slotEditorDestroyed(QObject *object); +private: + QHash> m_createdResetWidgets; + QHash m_resetWidgetToProperty; + int m_spacing; + const QDesignerFormEditorInterface *m_core; +}; + +// Helper for handling sub-properties of properties inheriting PropertySheetTranslatableData +// (translatable, disambiguation, comment). +template +class TranslatablePropertyManager +{ +public: + void initialize(QtVariantPropertyManager *m, QtProperty *property, const PropertySheetValue &value); + bool uninitialize(QtProperty *property); + bool destroy(QtProperty *subProperty); + + bool value(const QtProperty *property, QVariant *rc) const; + int valueChanged(QtVariantPropertyManager *m, QtProperty *property, + const QVariant &value); + + int setValue(QtVariantPropertyManager *m, QtProperty *property, + int expectedTypeId, const QVariant &value); + +private: + QHash m_values; + QHash m_valueToComment; + QHash m_valueToTranslatable; + QHash m_valueToDisambiguation; + QHash m_valueToId; + + QHash m_commentToValue; + QHash m_translatableToValue; + QHash m_disambiguationToValue; + QHash m_idToValue; +}; + +class DesignerPropertyManager : public QtVariantPropertyManager +{ + Q_OBJECT +public: + enum ValueChangedResult { NoMatch, Unchanged, Changed }; + + explicit DesignerPropertyManager(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~DesignerPropertyManager(); + + QStringList attributes(int propertyType) const override; + int attributeType(int propertyType, const QString &attribute) const override; + + QVariant attributeValue(const QtProperty *property, const QString &attribute) const override; + bool isPropertyTypeSupported(int propertyType) const override; + QVariant value(const QtProperty *property) const override; + int valueType(int propertyType) const override; + QString valueText(const QtProperty *property) const override; + QIcon valueIcon(const QtProperty *property) const override; + + bool resetTextAlignmentProperty(QtProperty *property); + bool resetFontSubProperty(QtProperty *property); + bool resetIconSubProperty(QtProperty *subProperty); + + void reloadResourceProperties(); + + static int designerFlagTypeId(); + static int designerFlagListTypeId(); + static int designerAlignmentTypeId(); + static int designerPixmapTypeId(); + static int designerIconTypeId(); + static int designerStringTypeId(); + static int designerStringListTypeId(); + static int designerKeySequenceTypeId(); + + void setObject(QObject *object) { m_object = object; } + + static void setUseIdBasedTranslations(bool v) + { m_IdBasedTranslations = v; } + static bool useIdBasedTranslations() + { return m_IdBasedTranslations; } + + static QString alignDefaultAttribute(); + + static uint alignDefault(const QtVariantProperty *prop); + +public Q_SLOTS: + void setAttribute(QtProperty *property, const QString &attribute, const QVariant &value) override; + void setValue(QtProperty *property, const QVariant &value) override; +Q_SIGNALS: + // sourceOfChange - a subproperty (or just property) which caused a change + //void valueChanged(QtProperty *property, const QVariant &value, QtProperty *sourceOfChange); + void valueChanged(QtProperty *property, const QVariant &value, bool enableSubPropertyHandling); +protected: + void initializeProperty(QtProperty *property) override; + void uninitializeProperty(QtProperty *property) override; +private Q_SLOTS: + void slotValueChanged(QtProperty *property, const QVariant &value); + void slotPropertyDestroyed(QtProperty *property); +private: + void createIconSubProperty(QtProperty *iconProperty, QIcon::Mode mode, QIcon::State state, const QString &subName); + + QHash m_resetMap; + + struct FlagData + { + uint val{0}; + DesignerFlagList flags; + QList values; + }; + + QHash m_flagValues; + QHash> m_propertyToFlags; + QHash m_flagToProperty; + + int alignToIndexH(uint align) const; + int alignToIndexV(uint align) const; + uint indexHToAlign(int idx) const; + uint indexVToAlign(int idx) const; + QString indexHToString(int idx) const; + QString indexVToString(int idx) const; + QHash m_alignValues; + using PropertyToPropertyMap = QHash; + PropertyToPropertyMap m_propertyToAlignH; + PropertyToPropertyMap m_propertyToAlignV; + PropertyToPropertyMap m_alignHToProperty; + PropertyToPropertyMap m_alignVToProperty; + QHash m_alignDefault; + + QHash, QtProperty *>> m_propertyToIconSubProperties; + QHash> m_iconSubPropertyToState; + PropertyToPropertyMap m_iconSubPropertyToProperty; + PropertyToPropertyMap m_propertyToTheme; + PropertyToPropertyMap m_propertyToThemeEnum; + + TranslatablePropertyManager m_stringManager; + TranslatablePropertyManager m_keySequenceManager; + TranslatablePropertyManager m_stringListManager; + + struct PaletteData + { + QPalette val; + QPalette superPalette; + }; + QHash m_paletteValues; + + QHash m_pixmapValues; + QHash m_iconValues; + + QHash m_intValues; + QHash m_uintValues; + QHash m_longLongValues; + QHash m_uLongLongValues; + QHash m_urlValues; + QHash m_byteArrayValues; + + QHash m_stringAttributes; + QHash m_stringFontAttributes; + QHash m_stringThemeAttributes; + QHash m_intThemeEnumAttributes; + + BrushPropertyManager m_brushManager; + FontPropertyManager m_fontManager; + + QHash m_defaultPixmaps; + QHash m_defaultIcons; + + bool m_changingSubValue; + QDesignerFormEditorInterface *m_core; + + QObject *m_object; + + QtProperty *m_sourceOfChange; + static bool m_IdBasedTranslations; +}; + +class DesignerEditorFactory : public QtVariantEditorFactory +{ + Q_OBJECT +public: + explicit DesignerEditorFactory(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~DesignerEditorFactory(); + void setSpacing(int spacing); + void setFormWindowBase(FormWindowBase *fwb); +signals: + void resetProperty(QtProperty *property); +protected: + void connectPropertyManager(QtVariantPropertyManager *manager) override; + QWidget *createEditor(QtVariantPropertyManager *manager, QtProperty *property, + QWidget *parent) override; + void disconnectPropertyManager(QtVariantPropertyManager *manager) override; +private slots: + void slotEditorDestroyed(QObject *object); + void slotAttributeChanged(QtProperty *property, const QString &attribute, const QVariant &value); + void slotPropertyChanged(QtProperty *property); + void slotValueChanged(QtProperty *property, const QVariant &value); + void slotStringTextChanged(const QString &value); + void slotKeySequenceChanged(const QKeySequence &value); + void slotPaletteChanged(const QPalette &value); + void slotPixmapChanged(const QString &value); + void slotIconChanged(const QString &value); + void slotIconThemeChanged(const QString &value); + void slotIconThemeEnumChanged(int value); + void slotUintChanged(const QString &value); + void slotIntChanged(int); + void slotLongLongChanged(const QString &value); + void slotULongLongChanged(const QString &value); + void slotUrlChanged(const QString &value); + void slotByteArrayChanged(const QString &value); + void slotStringListChanged(const QStringList &value); +private: + TextEditor *createTextEditor(QWidget *parent, TextPropertyValidationMode vm, const QString &value); + + ResetDecorator *m_resetDecorator; + bool m_changingPropertyValue = false; + QDesignerFormEditorInterface *m_core; + FormWindowBase *m_fwb = nullptr; + + int m_spacing = -1; + + QHash> m_stringPropertyToEditors; + QHash m_editorToStringProperty; + QHash> m_keySequencePropertyToEditors; + QHash m_editorToKeySequenceProperty; + QHash> m_palettePropertyToEditors; + QHash m_editorToPaletteProperty; + QHash> m_pixmapPropertyToEditors; + QHash m_editorToPixmapProperty; + QHash> m_iconPropertyToEditors; + QHash m_editorToIconProperty; + QHash> m_intPropertyToComboEditors; + QHash m_comboEditorToIntProperty; + QHash> m_uintPropertyToEditors; + QHash m_editorToUintProperty; + QHash> m_longLongPropertyToEditors; + QHash m_editorToLongLongProperty; + QHash> m_uLongLongPropertyToEditors; + QHash m_editorToULongLongProperty; + QHash> m_urlPropertyToEditors; + QHash m_editorToUrlProperty; + QHash> m_byteArrayPropertyToEditors; + QHash m_editorToByteArrayProperty; + QHash> m_stringListPropertyToEditors; + QHash m_editorToStringListProperty; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(DesignerIntPair) +Q_DECLARE_METATYPE(DesignerFlagList) + +#endif + diff --git a/src/tools/designer/src/components/propertyeditor/fontmapping.xml b/src/tools/designer/src/components/propertyeditor/fontmapping.xml new file mode 100644 index 00000000000..d7a716e392e --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/fontmapping.xml @@ -0,0 +1,35 @@ + + + + +]> + + +DejaVu SansDejaVu Sans [&qe;] +DejaVu SansDejaVu Sans [&qe;] +DejaVu SansDejaVu Sans [&qe;] +DejaVu SansDejaVu Sans [&qe;] +DejaVu Sans MonoDejaVu Sans Mono [&qe;] +DejaVu Sans MonoDejaVu Sans Mono [&qe;] +DejaVu Sans MonoDejaVu Sans Mono [&qe;] +DejaVu Sans MonoDejaVu Sans Mono [&qe;] +DejaVu SerifDejaVu Serif [&qe;] +DejaVu SerifDejaVu Serif [&qe;] +DejaVu SerifDejaVu Serif [&qe;] +DejaVu SerifDejaVu Serif [&qe;] +Bitstream Vera SansBitstream Vera Sans [&qe;] +Bitstream Vera SansBitstream Vera Sans [&qe;] +Bitstream Vera SansBitstream Vera Sans [&qe;] +Bitstream Vera SansBitstream Vera Sans [&qe;] +Bitstream Vera Sans MonoBitstream Vera Sans Mono [&qe;] +Bitstream Vera Sans MonoBitstream Vera Sans Mono [&qe;] +Bitstream Vera Sans MonoBitstream Vera Sans Mono [&qe;] +Bitstream Vera Sans MonoBitstream Vera Sans Mono [&qe;] +Bitstream Vera SerifBitstream Vera Serif [&qe;] +Bitstream Vera SerifBitstream Vera Serif [&qe;] + diff --git a/src/tools/designer/src/components/propertyeditor/fontpropertymanager.cpp b/src/tools/designer/src/components/propertyeditor/fontpropertymanager.cpp new file mode 100644 index 00000000000..33d30fc3cef --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/fontpropertymanager.cpp @@ -0,0 +1,454 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "fontpropertymanager.h" +#include "qtpropertymanager_p.h" +#include "designerpropertymanager.h" +#include "qtpropertybrowserutils_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + + using DisambiguatedTranslation = std::pair; + + static const char *aliasingC[] = { + QT_TRANSLATE_NOOP("FontPropertyManager", "PreferDefault"), + QT_TRANSLATE_NOOP("FontPropertyManager", "NoAntialias"), + QT_TRANSLATE_NOOP("FontPropertyManager", "PreferAntialias") + }; + + static const DisambiguatedTranslation hintingPreferenceC[] = { + QT_TRANSLATE_NOOP3("FontPropertyManager", "PreferDefaultHinting", "QFont::StyleStrategy combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "PreferNoHinting", "QFont::StyleStrategy combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "PreferVerticalHinting", "QFont::StyleStrategy combo"), + QT_TRANSLATE_NOOP3("FontPropertyManager", "PreferFullHinting", "QFont::StyleStrategy combo") + }; + + FontPropertyManager::FontPropertyManager() + { + for (const auto *a : aliasingC) + m_aliasingEnumNames.append(QCoreApplication::translate("FontPropertyManager", a)); + + for (const auto &h : hintingPreferenceC) + m_hintingPreferenceEnumNames.append(QCoreApplication::translate("FontPropertyManager", h.first, h.second)); + + QString errorMessage; + if (!readFamilyMapping(&m_familyMappings, &errorMessage)) { + designerWarning(errorMessage); + } + + } + + void FontPropertyManager::preInitializeProperty(QtProperty *property, + int type, + ResetMap &resetMap) + { + if (m_createdFontProperty) { + auto it = m_propertyToFontSubProperties.find(m_createdFontProperty); + if (it == m_propertyToFontSubProperties.end()) + it = m_propertyToFontSubProperties.insert(m_createdFontProperty, PropertyList()); + const int index = it.value().size(); + m_fontSubPropertyToFlag.insert(property, index); + it.value().push_back(property); + m_fontSubPropertyToProperty[property] = m_createdFontProperty; + resetMap[property] = true; + } + + if (type == QMetaType::QFont) + m_createdFontProperty = property; + } + + // Map the font family names to display names retrieved from the XML configuration + static QStringList designerFamilyNames(QStringList families, const FontPropertyManager::NameMap &nm) + { + if (nm.isEmpty()) + return families; + + const auto ncend = nm.constEnd(); + for (auto it = families.begin(), end = families.end(); it != end; ++it) { + const auto nit = nm.constFind(*it); + if (nit != ncend) + *it = nit.value(); + } + return families; + } + + void FontPropertyManager::postInitializeProperty(QtVariantPropertyManager *vm, + QtProperty *property, + int type, + int enumTypeId) + { + if (type != QMetaType::QFont) + return; + + // This will cause a recursion + QtVariantProperty *antialiasing = vm->addProperty(enumTypeId, QCoreApplication::translate("FontPropertyManager", "Antialiasing")); + const QFont font = qvariant_cast(vm->variantProperty(property)->value()); + + antialiasing->setAttribute(u"enumNames"_s, m_aliasingEnumNames); + antialiasing->setValue(antialiasingToIndex(font.styleStrategy())); + property->addSubProperty(antialiasing); + + m_propertyToAntialiasing[property] = antialiasing; + m_antialiasingToProperty[antialiasing] = property; + + QtVariantProperty *hintingPreference = vm->addProperty(enumTypeId, QCoreApplication::translate("FontPropertyManager", "HintingPreference")); + hintingPreference->setAttribute(u"enumNames"_s, m_hintingPreferenceEnumNames); + hintingPreference->setValue(hintingPreferenceToIndex(font.hintingPreference())); + property->addSubProperty(hintingPreference); + + m_propertyToHintingPreference[property] = hintingPreference; + m_hintingPreferenceToProperty[hintingPreference] = property; + + // Fiddle family names + if (!m_familyMappings.isEmpty()) { + const auto it = m_propertyToFontSubProperties.find(m_createdFontProperty); + QtVariantProperty *familyProperty = vm->variantProperty(it.value().constFirst()); + const QString enumNamesAttribute = u"enumNames"_s; + QStringList plainFamilyNames = familyProperty->attributeValue(enumNamesAttribute).toStringList(); + // Did someone load fonts or something? + if (m_designerFamilyNames.size() != plainFamilyNames.size()) + m_designerFamilyNames = designerFamilyNames(plainFamilyNames, m_familyMappings); + familyProperty->setAttribute(enumNamesAttribute, m_designerFamilyNames); + } + // Next + m_createdFontProperty = nullptr; + } + + bool FontPropertyManager::uninitializeProperty(QtProperty *property) + { + const auto ait = m_propertyToAntialiasing.find(property); + if (ait != m_propertyToAntialiasing.end()) { + QtProperty *antialiasing = ait.value(); + m_antialiasingToProperty.remove(antialiasing); + m_propertyToAntialiasing.erase(ait); + delete antialiasing; + } + + const auto hit = m_propertyToHintingPreference.find(property); + if (hit != m_propertyToHintingPreference.end()) { + QtProperty *hintingPreference = hit.value(); + m_hintingPreferenceToProperty.remove(hintingPreference); + m_propertyToHintingPreference.erase(hit); + delete hintingPreference; + } + + const auto sit = m_propertyToFontSubProperties.find(property); + if (sit == m_propertyToFontSubProperties.end()) + return false; + + m_propertyToFontSubProperties.erase(sit); + m_fontSubPropertyToFlag.remove(property); + m_fontSubPropertyToProperty.remove(property); + + return true; + } + + void FontPropertyManager::slotPropertyDestroyed(QtProperty *property) + { + removeAntialiasingProperty(property); + removeHintingPreferenceProperty(property); + } + + void FontPropertyManager::removeAntialiasingProperty(QtProperty *property) + { + const auto ait = m_antialiasingToProperty.find(property); + if (ait == m_antialiasingToProperty.end()) + return; + m_propertyToAntialiasing[ait.value()] = 0; + m_antialiasingToProperty.erase(ait); + } + + void FontPropertyManager::removeHintingPreferenceProperty(QtProperty *property) + { + const auto hit = m_hintingPreferenceToProperty.find(property); + if (hit == m_hintingPreferenceToProperty.end()) + return; + m_propertyToHintingPreference[hit.value()] = nullptr; + m_hintingPreferenceToProperty.erase(hit); + } + + bool FontPropertyManager::resetFontSubProperty(QtVariantPropertyManager *vm, QtProperty *property) + { + const auto it = m_fontSubPropertyToProperty.find(property); + if (it == m_fontSubPropertyToProperty.end()) + return false; + + QtVariantProperty *fontProperty = vm->variantProperty(it.value()); + + QVariant v = fontProperty->value(); + QFont font = qvariant_cast(v); + unsigned mask = font.resolveMask(); + const unsigned flag = fontFlag(m_fontSubPropertyToFlag.value(property)); + + mask &= ~flag; + font.setResolveMask(mask); + v.setValue(font); + fontProperty->setValue(v); + return true; + } + + int FontPropertyManager::antialiasingToIndex(QFont::StyleStrategy antialias) + { + switch (antialias) { + case QFont::PreferDefault: return 0; + case QFont::NoAntialias: return 1; + case QFont::PreferAntialias: return 2; + default: break; + } + return 0; + } + + QFont::StyleStrategy FontPropertyManager::indexToAntialiasing(int idx) + { + switch (idx) { + case 0: return QFont::PreferDefault; + case 1: return QFont::NoAntialias; + case 2: return QFont::PreferAntialias; + } + return QFont::PreferDefault; + } + + int FontPropertyManager::hintingPreferenceToIndex(QFont::HintingPreference h) + { + switch (h) { + case QFont::PreferDefaultHinting: + return 0; + case QFont::PreferNoHinting: + return 1; + case QFont::PreferVerticalHinting: + return 2; + case QFont::PreferFullHinting: + return 3; + } + return 0; + } + + QFont::HintingPreference FontPropertyManager::indexToHintingPreference(int idx) + { + switch (idx) { + case 0: + return QFont::PreferDefaultHinting; + case 1: + return QFont::PreferNoHinting; + case 2: + return QFont::PreferVerticalHinting; + case 3: + return QFont::PreferFullHinting; + } + return QFont::PreferDefaultHinting; + } + + unsigned FontPropertyManager::fontFlag(int idx) + { + switch (idx) { + case 0: + return QFont::FamilyResolved | QFont::FamiliesResolved; + case 1: + return QFont::SizeResolved; + case 2: + case 7: + return QFont::WeightResolved; + case 3: + return QFont::StyleResolved; + case 4: + return QFont::UnderlineResolved; + case 5: + return QFont::StrikeOutResolved; + case 6: + return QFont::KerningResolved; + case 8: + return QFont::StyleStrategyResolved; + case 9: + return QFont::HintingPreferenceResolved; + } + return 0; + } + + int FontPropertyManager::valueChanged(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value) + { + if (auto *antialiasingProperty = m_antialiasingToProperty.value(property, nullptr)) + return antialiasingValueChanged(vm, antialiasingProperty, value); + + if (auto *hintingPreferenceProperty = m_hintingPreferenceToProperty.value(property, nullptr)) + return hintingPreferenceValueChanged(vm, hintingPreferenceProperty, value); + + if (m_propertyToFontSubProperties.contains(property)) + updateModifiedState(property, value); + + return DesignerPropertyManager::NoMatch; + } + + int FontPropertyManager::antialiasingValueChanged(QtVariantPropertyManager *vm, + QtProperty *antialiasingProperty, + const QVariant &value) + { + QtVariantProperty *fontProperty = vm->variantProperty(antialiasingProperty); + const QFont::StyleStrategy newValue = indexToAntialiasing(value.toInt()); + + QFont font = qvariant_cast(fontProperty->value()); + const QFont::StyleStrategy oldValue = font.styleStrategy(); + if (newValue == oldValue) + return DesignerPropertyManager::Unchanged; + + font.setStyleStrategy(newValue); + fontProperty->setValue(QVariant::fromValue(font)); + return DesignerPropertyManager::Changed; + } + + int FontPropertyManager::hintingPreferenceValueChanged(QtVariantPropertyManager *vm, + QtProperty *hintingPreferenceProperty, + const QVariant &value) + { + QtVariantProperty *fontProperty = vm->variantProperty(hintingPreferenceProperty); + const QFont::HintingPreference newValue = indexToHintingPreference(value.toInt()); + + QFont font = qvariant_cast(fontProperty->value()); + const QFont::HintingPreference oldValue = font.hintingPreference(); + if (newValue == oldValue) + return DesignerPropertyManager::Unchanged; + + font.setHintingPreference(newValue); + fontProperty->setValue(QVariant::fromValue(font)); + return DesignerPropertyManager::Changed; + } + + void FontPropertyManager::updateModifiedState(QtProperty *property, const QVariant &value) + { + const auto it = m_propertyToFontSubProperties.find(property); + if (it == m_propertyToFontSubProperties.end()) + return; + + const PropertyList &subProperties = it.value(); + + QFont font = qvariant_cast(value); + const unsigned mask = font.resolveMask(); + + const int count = subProperties.size(); + for (int index = 0; index < count; index++) { + const unsigned flag = fontFlag(index); + subProperties.at(index)->setModified(mask & flag); + } + } + + void FontPropertyManager::setValue(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value) + { + updateModifiedState(property, value); + + if (QtProperty *antialiasingProperty = m_propertyToAntialiasing.value(property, 0)) { + QtVariantProperty *antialiasing = vm->variantProperty(antialiasingProperty); + if (antialiasing) { + QFont font = qvariant_cast(value); + antialiasing->setValue(antialiasingToIndex(font.styleStrategy())); + } + } + + if (QtProperty *hintingPreferenceProperty = m_propertyToHintingPreference.value(property, nullptr)) { + if (auto *hintingPreference = vm->variantProperty(hintingPreferenceProperty)) { + QFont font = qvariant_cast(value); + hintingPreference->setValue(hintingPreferenceToIndex(font.hintingPreference())); + } + } + + } + + /* Parse a mappings file of the form: + * + * DejaVu SansDejaVu Sans [CE] + * ... which is used to display on which platforms fonts are available.*/ + +static constexpr auto rootTagC = "fontmappings"_L1; +static constexpr auto mappingTagC = "mapping"_L1; +static constexpr auto familyTagC = "family"_L1; +static constexpr auto displayTagC = "display"_L1; + + static QString msgXmlError(const QXmlStreamReader &r, const QString& fileName) + { + return u"An error has been encountered at line %1 of %2: %3:"_s.arg(r.lineNumber()).arg(fileName, r.errorString()); + } + + /* Switch stages when encountering a start element (state table) */ + enum ParseStage { ParseBeginning, ParseWithinRoot, ParseWithinMapping, ParseWithinFamily, + ParseWithinDisplay, ParseError }; + + static ParseStage nextStage(ParseStage currentStage, QStringView startElement) + { + switch (currentStage) { + case ParseBeginning: + return startElement == rootTagC ? ParseWithinRoot : ParseError; + case ParseWithinRoot: + case ParseWithinDisplay: // Next mapping, was in + return startElement == mappingTagC ? ParseWithinMapping : ParseError; + case ParseWithinMapping: + return startElement == familyTagC ? ParseWithinFamily : ParseError; + case ParseWithinFamily: + return startElement == displayTagC ? ParseWithinDisplay : ParseError; + case ParseError: + break; + } + return ParseError; + } + + bool FontPropertyManager::readFamilyMapping(NameMap *rc, QString *errorMessage) + { + rc->clear(); + const QString fileName = u":/qt-project.org/propertyeditor/fontmapping.xml"_s; + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + *errorMessage = "Unable to open %1: %2"_L1.arg(fileName, file.errorString()); + return false; + } + + QXmlStreamReader reader(&file); + QXmlStreamReader::TokenType token; + + QString family; + ParseStage stage = ParseBeginning; + do { + token = reader.readNext(); + switch (token) { + case QXmlStreamReader::Invalid: + *errorMessage = msgXmlError(reader, fileName); + return false; + case QXmlStreamReader::StartElement: + stage = nextStage(stage, reader.name()); + switch (stage) { + case ParseError: + reader.raiseError("Unexpected element <%1>."_L1.arg(reader.name())); + *errorMessage = msgXmlError(reader, fileName); + return false; + case ParseWithinFamily: + family = reader.readElementText(); + break; + case ParseWithinDisplay: + rc->insert(family, reader.readElementText()); + break; + default: + break; + } + break; + default: + break; + } + } while (token != QXmlStreamReader::EndDocument); + return true; + } + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/fontpropertymanager.h b/src/tools/designer/src/components/propertyeditor/fontpropertymanager.h new file mode 100644 index 00000000000..3e628dbf2d1 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/fontpropertymanager.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FONTPROPERTYMANAGER_H +#define FONTPROPERTYMANAGER_H + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtProperty; +class QtVariantPropertyManager; + +class QString; +class QVariant; + +namespace qdesigner_internal { + +/* FontPropertyManager: A mixin for DesignerPropertyManager that manages font + * properties. Adds an antialiasing subproperty and reset flags/mask handling + * for the other subproperties. It also modifies the font family + * enumeration names, which it reads from an XML mapping file that + * contains annotations indicating the platform the font is available on. */ + +class FontPropertyManager { + Q_DISABLE_COPY_MOVE(FontPropertyManager) + +public: + FontPropertyManager(); + + using ResetMap = QHash; + using NameMap = QMap; + + // Call before QtVariantPropertyManager::initializeProperty. + void preInitializeProperty(QtProperty *property, int type, ResetMap &resetMap); + // Call after QtVariantPropertyManager::initializeProperty. This will trigger + // a recursion for the sub properties + void postInitializeProperty(QtVariantPropertyManager *vm, QtProperty *property, int type, int enumTypeId); + + bool uninitializeProperty(QtProperty *property); + + // Call from QtPropertyManager's propertyDestroyed signal + void slotPropertyDestroyed(QtProperty *property); + + bool resetFontSubProperty(QtVariantPropertyManager *vm, QtProperty *subProperty); + + // Call from slotValueChanged(), returns DesignerPropertyManager::ValueChangedResult + int valueChanged(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value); + + // Call from setValue() before calling setValue() on QtVariantPropertyManager. + void setValue(QtVariantPropertyManager *vm, QtProperty *property, const QVariant &value); + + static bool readFamilyMapping(NameMap *rc, QString *errorMessage); + +private: + using PropertyToPropertyMap = QHash; + using PropertyList = QList; + + void removeAntialiasingProperty(QtProperty *); + void removeHintingPreferenceProperty(QtProperty *); + int antialiasingValueChanged(QtVariantPropertyManager *vm, + QtProperty *antialiasingProperty, const QVariant &value); + int hintingPreferenceValueChanged(QtVariantPropertyManager *vm, + QtProperty *hintingPreferenceProperty, + const QVariant &value); + void updateModifiedState(QtProperty *property, const QVariant &value); + static int antialiasingToIndex(QFont::StyleStrategy antialias); + static QFont::StyleStrategy indexToAntialiasing(int idx); + static int hintingPreferenceToIndex(QFont::HintingPreference h); + static QFont::HintingPreference indexToHintingPreference(int idx); + + static unsigned fontFlag(int idx); + + PropertyToPropertyMap m_propertyToAntialiasing; + PropertyToPropertyMap m_antialiasingToProperty; + PropertyToPropertyMap m_propertyToHintingPreference; + PropertyToPropertyMap m_hintingPreferenceToProperty; + + + QHash m_propertyToFontSubProperties; + QHash m_fontSubPropertyToFlag; + PropertyToPropertyMap m_fontSubPropertyToProperty; + QtProperty *m_createdFontProperty = nullptr; + QStringList m_aliasingEnumNames; + QStringList m_hintingPreferenceEnumNames; + // Font families with Designer annotations + QStringList m_designerFamilyNames; + NameMap m_familyMappings; +}; + +} + +QT_END_NAMESPACE + +#endif // FONTPROPERTYMANAGER_H diff --git a/src/tools/designer/src/components/propertyeditor/newdynamicpropertydialog.cpp b/src/tools/designer/src/components/propertyeditor/newdynamicpropertydialog.cpp new file mode 100644 index 00000000000..a8972c332a4 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/newdynamicpropertydialog.cpp @@ -0,0 +1,162 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "newdynamicpropertydialog.h" +#include "ui_newdynamicpropertydialog.h" +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +NewDynamicPropertyDialog::NewDynamicPropertyDialog(QDesignerDialogGuiInterface *dialogGui, + QWidget *parent) : + QDialog(parent), + m_dialogGui(dialogGui), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::NewDynamicPropertyDialog) +{ + m_ui->setupUi(this); + connect(m_ui->m_lineEdit, &QLineEdit::textChanged, this, &NewDynamicPropertyDialog::nameChanged); + connect(m_ui->m_buttonBox, &QDialogButtonBox::clicked, + this, &NewDynamicPropertyDialog::buttonBoxClicked); + + m_ui->m_comboBox->addItem(u"String"_s, + QVariant(QMetaType(QMetaType::QString))); + m_ui->m_comboBox->addItem(u"StringList"_s, + QVariant(QMetaType(QMetaType::QStringList))); + m_ui->m_comboBox->addItem(u"Char"_s, + QVariant(QMetaType(QMetaType::QChar))); + m_ui->m_comboBox->addItem(u"ByteArray"_s, + QVariant(QMetaType(QMetaType::QByteArray))); + m_ui->m_comboBox->addItem(u"Url"_s, + QVariant(QMetaType(QMetaType::QUrl))); + m_ui->m_comboBox->addItem(u"Bool"_s, + QVariant(QMetaType(QMetaType::Bool))); + m_ui->m_comboBox->addItem(u"Int"_s, + QVariant(QMetaType(QMetaType::Int))); + m_ui->m_comboBox->addItem(u"UInt"_s, + QVariant(QMetaType(QMetaType::UInt))); + m_ui->m_comboBox->addItem(u"LongLong"_s, + QVariant(QMetaType(QMetaType::LongLong))); + m_ui->m_comboBox->addItem(u"ULongLong"_s, + QVariant(QMetaType(QMetaType::ULongLong))); + m_ui->m_comboBox->addItem(u"Double"_s, + QVariant(QMetaType(QMetaType::Double))); + m_ui->m_comboBox->addItem(u"Size"_s, + QVariant(QMetaType(QMetaType::QSize))); + m_ui->m_comboBox->addItem(u"SizeF"_s, + QVariant(QMetaType(QMetaType::QSizeF))); + m_ui->m_comboBox->addItem(u"Point"_s, + QVariant(QMetaType(QMetaType::QPoint))); + m_ui->m_comboBox->addItem(u"PointF"_s, + QVariant(QMetaType(QMetaType::QPointF))); + m_ui->m_comboBox->addItem(u"Rect"_s, + QVariant(QMetaType(QMetaType::QRect))); + m_ui->m_comboBox->addItem(u"RectF"_s, + QVariant(QMetaType(QMetaType::QRectF))); + m_ui->m_comboBox->addItem(u"Date"_s, + QVariant(QMetaType(QMetaType::QDate))); + m_ui->m_comboBox->addItem(u"Time"_s, + QVariant(QMetaType(QMetaType::QTime))); + m_ui->m_comboBox->addItem(u"DateTime"_s, + QVariant(QMetaType(QMetaType::QDateTime))); + m_ui->m_comboBox->addItem(u"Font"_s, + QVariant(QMetaType(QMetaType::QFont))); + m_ui->m_comboBox->addItem(u"Palette"_s, + QVariant(QMetaType(QMetaType::QPalette))); + m_ui->m_comboBox->addItem(u"Color"_s, + QVariant(QMetaType(QMetaType::QColor))); + m_ui->m_comboBox->addItem(u"Pixmap"_s, + QVariant(QMetaType(QMetaType::QPixmap))); + m_ui->m_comboBox->addItem(u"Icon"_s, + QVariant(QMetaType(QMetaType::QIcon))); + m_ui->m_comboBox->addItem(u"Cursor"_s, + QVariant(QMetaType(QMetaType::QCursor))); + m_ui->m_comboBox->addItem(u"SizePolicy"_s, + QVariant(QMetaType(QMetaType::QSizePolicy))); + m_ui->m_comboBox->addItem(u"KeySequence"_s, + QVariant(QMetaType(QMetaType::QKeySequence))); + + m_ui->m_comboBox->setCurrentIndex(0); // String + setOkButtonEnabled(false); +} + +void NewDynamicPropertyDialog::setOkButtonEnabled(bool e) +{ + m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(e); +} + +NewDynamicPropertyDialog::~NewDynamicPropertyDialog() +{ + delete m_ui; +} + +void NewDynamicPropertyDialog::setReservedNames(const QStringList &names) +{ + m_reservedNames = names; +} + +void NewDynamicPropertyDialog::setPropertyType(int t) +{ + const int index = m_ui->m_comboBox->findData(QVariant(QMetaType(t))); + if (index != -1) + m_ui->m_comboBox->setCurrentIndex(index); +} + +QString NewDynamicPropertyDialog::propertyName() const +{ + return m_ui->m_lineEdit->text(); +} + +QVariant NewDynamicPropertyDialog::propertyValue() const +{ + const int index = m_ui->m_comboBox->currentIndex(); + if (index == -1) + return QVariant(); + return m_ui->m_comboBox->itemData(index); +} + +void NewDynamicPropertyDialog::information(const QString &message) +{ + m_dialogGui->message(this, QDesignerDialogGuiInterface::PropertyEditorMessage, QMessageBox::Information, tr("Set Property Name"), message); +} + +void NewDynamicPropertyDialog::nameChanged(const QString &s) +{ + setOkButtonEnabled(!s.isEmpty()); +} + +bool NewDynamicPropertyDialog::validatePropertyName(const QString& name) +{ + if (m_reservedNames.contains(name)) { + information(tr("The current object already has a property named '%1'.\nPlease select another, unique one.").arg(name)); + return false; + } + if (!QDesignerPropertySheet::internalDynamicPropertiesEnabled() && name.startsWith("_q_"_L1)) { + information(tr("The '_q_' prefix is reserved for the Qt library.\nPlease select another name.")); + return false; + } + return true; +} + +void NewDynamicPropertyDialog::buttonBoxClicked(QAbstractButton *btn) +{ + const int role = m_ui->m_buttonBox->buttonRole(btn); + switch (role) { + case QDialogButtonBox::RejectRole: + reject(); + break; + case QDialogButtonBox::AcceptRole: + if (validatePropertyName(propertyName())) + accept(); + break; + } +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/newdynamicpropertydialog.h b/src/tools/designer/src/components/propertyeditor/newdynamicpropertydialog.h new file mode 100644 index 00000000000..0142e44823d --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/newdynamicpropertydialog.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef NEWDYNAMICPROPERTYDIALOG_P_H +#define NEWDYNAMICPROPERTYDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "propertyeditor_global.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QAbstractButton; +class QDesignerDialogGuiInterface; + +namespace qdesigner_internal { + +namespace Ui +{ + class NewDynamicPropertyDialog; +} + +class QT_PROPERTYEDITOR_EXPORT NewDynamicPropertyDialog: public QDialog +{ + Q_OBJECT +public: + explicit NewDynamicPropertyDialog(QDesignerDialogGuiInterface *dialogGui, QWidget *parent = nullptr); + ~NewDynamicPropertyDialog(); + + void setReservedNames(const QStringList &names); + void setPropertyType(int t); + + QString propertyName() const; + QVariant propertyValue() const; + +private slots: + + void buttonBoxClicked(QAbstractButton *btn); + void nameChanged(const QString &); + +private: + bool validatePropertyName(const QString& name); + void setOkButtonEnabled(bool e); + void information(const QString &message); + + QDesignerDialogGuiInterface *m_dialogGui; + Ui::NewDynamicPropertyDialog *m_ui; + QStringList m_reservedNames; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // NEWDYNAMICPROPERTYDIALOG_P_H diff --git a/src/tools/designer/src/components/propertyeditor/newdynamicpropertydialog.ui b/src/tools/designer/src/components/propertyeditor/newdynamicpropertydialog.ui new file mode 100644 index 00000000000..dec3bb3c0ee --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/newdynamicpropertydialog.ui @@ -0,0 +1,109 @@ + + qdesigner_internal::NewDynamicPropertyDialog + + + + 0 + 0 + 340 + 118 + + + + Create Dynamic Property + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + + 220 + 0 + + + + + + + + + 0 + 0 + + + + Property Name + + + + + + + + + + + + horizontalSpacer + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 0 + + + + Property Type + + + + + + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + false + + + + + + + + diff --git a/src/tools/designer/src/components/propertyeditor/paletteeditor.cpp b/src/tools/designer/src/components/propertyeditor/paletteeditor.cpp new file mode 100644 index 00000000000..baadb3ae148 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/paletteeditor.cpp @@ -0,0 +1,775 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "paletteeditor.h" + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if QT_CONFIG(clipboard) +# include +#endif +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +enum { BrushRole = 33 }; + +PaletteEditor::PaletteEditor(QDesignerFormEditorInterface *core, QWidget *parent) : + QDialog(parent), + m_paletteModel(new PaletteModel(this)), + m_core(core) +{ + ui.setupUi(this); + auto saveButton = ui.buttonBox->addButton(tr("Save..."), QDialogButtonBox::ActionRole); + connect(saveButton, &QPushButton::clicked, this, &PaletteEditor::save); + auto loadButton = ui.buttonBox->addButton(tr("Load..."), QDialogButtonBox::ActionRole); + connect(loadButton, &QPushButton::clicked, this, &PaletteEditor::load); + + connect(ui.buildButton, &QtColorButton::colorChanged, + this, &PaletteEditor::buildButtonColorChanged); + connect(ui.activeRadio, &QAbstractButton::clicked, + this, &PaletteEditor::activeRadioClicked); + connect(ui.inactiveRadio, &QAbstractButton::clicked, + this, &PaletteEditor::inactiveRadioClicked); + connect(ui.disabledRadio, &QAbstractButton::clicked, + this, &PaletteEditor::disabledRadioClicked); + connect(ui.computeRadio, &QAbstractButton::clicked, + this, &PaletteEditor::computeRadioClicked); + connect(ui.detailsRadio, &QAbstractButton::clicked, + this, &PaletteEditor::detailsRadioClicked); + + ui.paletteView->setModel(m_paletteModel); + ui.previewGroupBox->setTitle(tr("Preview (%1)").arg(style()->objectName())); + updatePreviewPalette(); + updateStyledButton(); + ui.paletteView->setModel(m_paletteModel); + ColorDelegate *delegate = new ColorDelegate(core, this); + ui.paletteView->setItemDelegate(delegate); + ui.paletteView->setEditTriggers(QAbstractItemView::AllEditTriggers); + connect(m_paletteModel, &PaletteModel::paletteChanged, + this, &PaletteEditor::paletteChanged); + ui.paletteView->setSelectionBehavior(QAbstractItemView::SelectRows); + ui.paletteView->setDragEnabled(true); + ui.paletteView->setDropIndicatorShown(true); + ui.paletteView->setRootIsDecorated(false); + ui.paletteView->setColumnHidden(2, true); + ui.paletteView->setColumnHidden(3, true); + ui.paletteView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui.paletteView, &QWidget::customContextMenuRequested, + this, &PaletteEditor::viewContextMenuRequested); + + const auto itemRect = ui.paletteView->visualRect(m_paletteModel->index(0, 0)); + const int minHeight = qMin(itemRect.height() * QPalette::NColorRoles, + (screen()->geometry().height() * 2) / 3); + ui.paletteView->setMinimumSize({itemRect.width() * 4, minHeight}); +} + +PaletteEditor::~PaletteEditor() = default; + +QPalette PaletteEditor::palette() const +{ + return m_editPalette; +} + +void PaletteEditor::setPalette(const QPalette &palette) +{ + m_editPalette = palette; + for (int r = 0; r < static_cast(QPalette::NColorRoles); ++r) { + for (int g = 0; g < static_cast(QPalette::NColorGroups); ++g) { + const auto role = static_cast(r); + const auto group = static_cast(g); + if (!palette.isBrushSet(group, role)) + m_editPalette.setBrush(group, role, m_parentPalette.brush(group, role)); + } + } + m_editPalette.setResolveMask(palette.resolveMask()); + updatePreviewPalette(); + updateStyledButton(); + m_paletteUpdated = true; + if (!m_modelUpdated) + m_paletteModel->setPalette(m_editPalette, m_parentPalette); + m_paletteUpdated = false; +} + +void PaletteEditor::setPalette(const QPalette &palette, const QPalette &parentPalette) +{ + m_parentPalette = parentPalette; + setPalette(palette); +} + +void PaletteEditor::buildButtonColorChanged() +{ + buildPalette(); +} + +void PaletteEditor::activeRadioClicked() +{ + m_currentColorGroup = QPalette::Active; + updatePreviewPalette(); +} + +void PaletteEditor::inactiveRadioClicked() +{ + m_currentColorGroup = QPalette::Inactive; + updatePreviewPalette(); +} + +void PaletteEditor::disabledRadioClicked() +{ + m_currentColorGroup = QPalette::Disabled; + updatePreviewPalette(); +} + +void PaletteEditor::computeRadioClicked() +{ + if (m_compute) + return; + ui.paletteView->setColumnHidden(2, true); + ui.paletteView->setColumnHidden(3, true); + m_compute = true; + m_paletteModel->setCompute(true); +} + +void PaletteEditor::detailsRadioClicked() +{ + if (!m_compute) + return; + const int w = ui.paletteView->columnWidth(1); + ui.paletteView->setColumnHidden(2, false); + ui.paletteView->setColumnHidden(3, false); + QHeaderView *header = ui.paletteView->header(); + header->resizeSection(1, w / 3); + header->resizeSection(2, w / 3); + header->resizeSection(3, w / 3); + m_compute = false; + m_paletteModel->setCompute(false); +} + +void PaletteEditor::paletteChanged(const QPalette &palette) +{ + m_modelUpdated = true; + if (!m_paletteUpdated) + setPalette(palette); + m_modelUpdated = false; +} + +void PaletteEditor::buildPalette() +{ + const QColor btn = ui.buildButton->color(); + const QPalette temp = QPalette(btn); + setPalette(temp); +} + +void PaletteEditor::updatePreviewPalette() +{ + const QPalette::ColorGroup g = currentColorGroup(); + // build the preview palette + const QPalette currentPalette = palette(); + QPalette previewPalette; + for (int i = QPalette::WindowText; i < QPalette::NColorRoles; i++) { + const QPalette::ColorRole r = static_cast(i); + const QBrush &br = currentPalette.brush(g, r); + previewPalette.setBrush(QPalette::Active, r, br); + previewPalette.setBrush(QPalette::Inactive, r, br); + previewPalette.setBrush(QPalette::Disabled, r, br); + } + ui.previewFrame->setPreviewPalette(previewPalette); + + const bool enabled = g != QPalette::Disabled; + ui.previewFrame->setEnabled(enabled); + ui.previewFrame->setSubWindowActive(g != QPalette::Inactive); +} + +void PaletteEditor::updateStyledButton() +{ + ui.buildButton->setColor(palette().color(QPalette::Active, QPalette::Button)); +} + +QPalette PaletteEditor::getPalette(QDesignerFormEditorInterface *core, QWidget* parent, const QPalette &init, + const QPalette &parentPal, int *ok) +{ + PaletteEditor dlg(core, parent); + QPalette parentPalette(parentPal); + for (int r = 0; r < static_cast(QPalette::NColorRoles); ++r) { + for (int g = 0; g < static_cast(QPalette::NColorGroups); ++g) { + const auto role = static_cast(r); + const auto group = static_cast(g); + if (!init.isBrushSet(group, role)) + parentPalette.setBrush(group, role, init.brush(group, role)); + } + } + dlg.setPalette(init, parentPalette); + + const int result = dlg.exec(); + if (ok) *ok = result; + + return result == QDialog::Accepted ? dlg.palette() : init; +} + +void PaletteEditor::viewContextMenuRequested(QPoint pos) +{ + const auto index = ui.paletteView->indexAt(pos); + if (!index.isValid()) + return; + auto brush = m_paletteModel->brushAt(index); + const auto color = brush.color(); + if (!m_contextMenu) { + m_contextMenu = new QMenu(this); + m_lighterAction = m_contextMenu->addAction(tr("Lighter")); + m_darkerAction = m_contextMenu->addAction(tr("Darker")); + m_copyColorAction = m_contextMenu->addAction(QString()); + } + const auto rgb = color.rgb() & 0xffffffu; + const bool isBlack = rgb == 0u; + m_lighterAction->setEnabled(rgb != 0xffffffu); + m_darkerAction->setDisabled(isBlack); + m_copyColorAction->setText(tr("Copy color %1").arg(color.name())); + auto action = m_contextMenu->exec(ui.paletteView->viewport()->mapToGlobal(pos)); + if (!action) + return; + if (action == m_copyColorAction) { +#if QT_CONFIG(clipboard) + QGuiApplication::clipboard()->setText(color.name()); +#endif + return; + } + // Fall through to darker/lighter. Note: black cannot be made lighter due + // to QTBUG-9343. + enum : int { factor = 120 }; + const QColor newColor = action == m_darkerAction + ? color.darker(factor) + : (isBlack ? QColor(0x404040u) : color.lighter(factor)); + brush.setColor(newColor); + m_paletteModel->setData(index, QVariant(brush), BrushRole); +} + +static inline QString paletteFilter() +{ + return PaletteEditor::tr("QPalette UI file (*.xml)"); +} + +static bool savePalette(const QString &fileName, const QPalette &pal, QString *errorMessage) +{ + QSaveFile file; + file.setFileName(fileName); + if (!file.open(QIODevice::WriteOnly)) { + *errorMessage = PaletteEditor::tr("Cannot open %1 for writing: %2") + .arg(QDir::toNativeSeparators(fileName), file.errorString()); + return false; + } + { + QScopedPointer domPalette(QFormBuilderExtra::savePalette(pal)); + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(1); + writer.writeStartDocument(); + domPalette->write(writer); + writer.writeEndDocument(); + } + const bool result = file.commit(); + if (!result) { + *errorMessage = PaletteEditor::tr("Cannot write %1: %2") + .arg(QDir::toNativeSeparators(fileName), file.errorString()); + } + return result; +} + +static QString msgCannotReadPalette(const QString &fileName, const QXmlStreamReader &reader, + const QString &why) +{ + return PaletteEditor::tr("Cannot read palette from %1:%2:%3") + .arg(QDir::toNativeSeparators(fileName)).arg(reader.lineNumber()).arg(why); +} + +static inline QString msgCannotReadPalette(const QString &fileName, const QXmlStreamReader &reader) +{ + return msgCannotReadPalette(fileName, reader, reader.errorString()); +} + +static bool loadPalette(const QString &fileName, QPalette *pal, QString *errorMessage) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + *errorMessage = PaletteEditor::tr("Cannot open %1 for reading: %2") + .arg(QDir::toNativeSeparators(fileName), file.errorString()); + return false; + } + QXmlStreamReader reader(&file); + if (!reader.readNextStartElement()) { + *errorMessage = msgCannotReadPalette(fileName, reader); + return false; + } + if (reader.name() != "palette"_L1) { + const auto why = PaletteEditor::tr("Invalid element \"%1\", expected \"palette\".") + .arg(reader.name().toString()); + *errorMessage = msgCannotReadPalette(fileName, reader, why); + return false; + } + QScopedPointer domPalette(new DomPalette); + domPalette->read(reader); + if (reader.hasError()) { + *errorMessage = msgCannotReadPalette(fileName, reader); + return false; + } + *pal = QFormBuilderExtra::loadPalette(domPalette.data()); + return true; +} + +void PaletteEditor::save() +{ + QFileDialog dialog(this, tr("Save Palette"), QString(), paletteFilter()); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDefaultSuffix(u"xml"_s); + while (dialog.exec() == QDialog::Accepted) { + QString errorMessage; + if (savePalette(dialog.selectedFiles().constFirst(), palette(), &errorMessage)) + break; + QMessageBox::warning(this, tr("Error Writing Palette"), errorMessage); + } +} + +void PaletteEditor::load() +{ + QFileDialog dialog(this, tr("Load Palette"), QString(), paletteFilter()); + dialog.setAcceptMode(QFileDialog::AcceptOpen); + while (dialog.exec() == QDialog::Accepted) { + QPalette pal; + QString errorMessage; + if (loadPalette(dialog.selectedFiles().constFirst(), &pal, &errorMessage)) { + setPalette(pal); + break; + } + QMessageBox::warning(this, tr("Error Reading Palette"), errorMessage); + } +} + +////////////////////// +// Column 0: Role name and reset button. Uses a boolean value indicating +// whether the role is modified for the edit role. +// Column 1: Color group Active +// Column 2: Color group Inactive (visibility depending on m_compute/detail radio group) +// Column 3: Color group Disabled + +PaletteModel::PaletteModel(QObject *parent) : + QAbstractTableModel(parent) +{ + const QMetaObject *meta = metaObject(); + const int index = meta->indexOfProperty("colorRole"); + const QMetaProperty p = meta->property(index); + const QMetaEnum e = p.enumerator(); + m_roleEntries.reserve(QPalette::NColorRoles); + for (int r = QPalette::WindowText; r < QPalette::NColorRoles; r++) { + const auto role = static_cast(r); + if (role != QPalette::NoRole) + m_roleEntries.append({QLatin1StringView(e.key(r)), role}); + } +} + +int PaletteModel::rowCount(const QModelIndex &) const +{ + return m_roleEntries.size(); +} + +int PaletteModel::columnCount(const QModelIndex &) const +{ + return 4; +} + +QBrush PaletteModel::brushAt(const QModelIndex &index) const +{ + return m_palette.brush(columnToGroup(index.column()), roleAt(index.row())); +} + +// Palette resolve mask with all group bits for a row/role +quint64 PaletteModel::rowMask(const QModelIndex &index) const +{ + return paletteResolveMask(roleAt(index.row())); +} + +QVariant PaletteModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (index.row() < 0 || index.row() >= m_roleEntries.size()) + return QVariant(); + if (index.column() < 0 || index.column() >= 4) + return QVariant(); + + if (index.column() == 0) { // Role name/bold print if changed + if (role == Qt::DisplayRole) + return m_roleEntries.at(index.row()).name; + if (role == Qt::EditRole) + return (rowMask(index) & m_palette.resolveMask()) != 0; + return QVariant(); + } + if (role == Qt::ToolTipRole) + return brushAt(index).color().name(); + if (role == BrushRole) + return brushAt(index); + return QVariant(); +} + +bool PaletteModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + const int row = index.row(); + const auto colorRole = roleAt(row); + + if (index.column() != 0 && role == BrushRole) { + const QBrush br = qvariant_cast(value); + const QPalette::ColorGroup g = columnToGroup(index.column()); + m_palette.setBrush(g, colorRole, br); + + QModelIndex idxBegin = PaletteModel::index(row, 0); + QModelIndex idxEnd = PaletteModel::index(row, 3); + if (m_compute) { + m_palette.setBrush(QPalette::Inactive, colorRole, br); + switch (colorRole) { + case QPalette::WindowText: + case QPalette::Text: + case QPalette::ButtonText: + case QPalette::Base: + break; + case QPalette::Dark: + m_palette.setBrush(QPalette::Disabled, QPalette::WindowText, br); + m_palette.setBrush(QPalette::Disabled, QPalette::Dark, br); + m_palette.setBrush(QPalette::Disabled, QPalette::Text, br); + m_palette.setBrush(QPalette::Disabled, QPalette::ButtonText, br); + idxBegin = PaletteModel::index(0, 0); + idxEnd = PaletteModel::index(m_roleEntries.size() - 1, 3); + break; + case QPalette::Window: + m_palette.setBrush(QPalette::Disabled, QPalette::Base, br); + m_palette.setBrush(QPalette::Disabled, QPalette::Window, br); + idxBegin = PaletteModel::index(rowOf(QPalette::Base), 0); + break; + case QPalette::Highlight: + //m_palette.setBrush(QPalette::Disabled, QPalette::Highlight, c.dark(120)); + break; + default: + m_palette.setBrush(QPalette::Disabled, colorRole, br); + break; + } + } + emit paletteChanged(m_palette); + emit dataChanged(idxBegin, idxEnd); + return true; + } + if (index.column() == 0 && role == Qt::EditRole) { + auto mask = m_palette.resolveMask(); + const bool isMask = qvariant_cast(value); + const auto bitMask = rowMask(index); + if (isMask) { + mask |= bitMask; + } else { + m_palette.setBrush(QPalette::Active, colorRole, + m_parentPalette.brush(QPalette::Active, colorRole)); + m_palette.setBrush(QPalette::Inactive, colorRole, + m_parentPalette.brush(QPalette::Inactive, colorRole)); + m_palette.setBrush(QPalette::Disabled, colorRole, + m_parentPalette.brush(QPalette::Disabled, colorRole)); + + mask &= ~bitMask; + } + m_palette.setResolveMask(mask); + emit paletteChanged(m_palette); + const QModelIndex idxEnd = PaletteModel::index(row, 3); + emit dataChanged(index, idxEnd); + return true; + } + return false; +} + +Qt::ItemFlags PaletteModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemIsEnabled; + return Qt::ItemIsEditable | Qt::ItemIsEnabled; +} + +QVariant PaletteModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { + if (section == 0) + return tr("Color Role"); + if (section == groupToColumn(QPalette::Active)) + return tr("Active"); + if (section == groupToColumn(QPalette::Inactive)) + return tr("Inactive"); + if (section == groupToColumn(QPalette::Disabled)) + return tr("Disabled"); + } + return QVariant(); +} + +QPalette PaletteModel::getPalette() const +{ + return m_palette; +} + +void PaletteModel::setPalette(const QPalette &palette, const QPalette &parentPalette) +{ + m_parentPalette = parentPalette; + m_palette = palette; + const QModelIndex idxBegin = index(0, 0); + const QModelIndex idxEnd = index(m_roleEntries.size() - 1, 3); + emit dataChanged(idxBegin, idxEnd); +} + +QPalette::ColorGroup PaletteModel::columnToGroup(int index) const +{ + if (index == 1) + return QPalette::Active; + if (index == 2) + return QPalette::Inactive; + return QPalette::Disabled; +} + +int PaletteModel::groupToColumn(QPalette::ColorGroup group) const +{ + if (group == QPalette::Active) + return 1; + if (group == QPalette::Inactive) + return 2; + return 3; +} + +int PaletteModel::rowOf(QPalette::ColorRole role) const +{ + for (qsizetype row = 0, size = m_roleEntries.size(); row < size; ++row) { + if (m_roleEntries.at(row).role == role) + return row; + } + return -1; +} + +////////////////////////// + +BrushEditor::BrushEditor(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_button(new QtColorButton(this)), + m_core(core) +{ + QLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->addWidget(m_button); + connect(m_button, &QtColorButton::colorChanged, this, &BrushEditor::brushChanged); + setFocusProxy(m_button); +} + +void BrushEditor::setBrush(const QBrush &brush) +{ + m_button->setColor(brush.color()); + m_changed = false; +} + +QBrush BrushEditor::brush() const +{ + return QBrush(m_button->color()); +} + +void BrushEditor::brushChanged() +{ + m_changed = true; + emit changed(this); +} + +bool BrushEditor::changed() const +{ + return m_changed; +} + +////////////////////////// + +RoleEditor::RoleEditor(QWidget *parent) : + QWidget(parent), + m_label(new QLabel(this)) +{ + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + + layout->addWidget(m_label); + m_label->setAutoFillBackground(true); + m_label->setIndent(3); // ### hardcode it should have the same value of textMargin in QItemDelegate + setFocusProxy(m_label); + + QToolButton *button = new QToolButton(this); + button->setToolButtonStyle(Qt::ToolButtonIconOnly); + button->setIcon(createIconSet("resetproperty.png"_L1)); + button->setIconSize(QSize(8,8)); + button->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::MinimumExpanding)); + layout->addWidget(button); + connect(button, &QAbstractButton::clicked, this, &RoleEditor::emitResetProperty); +} + +void RoleEditor::setLabel(const QString &label) +{ + m_label->setText(label); +} + +void RoleEditor::setEdited(bool on) +{ + QFont font; + if (on) + font.setBold(on); + m_label->setFont(font); + m_edited = on; +} + +bool RoleEditor::edited() const +{ + return m_edited; +} + +void RoleEditor::emitResetProperty() +{ + setEdited(false); + emit changed(this); +} + +////////////////////////// +ColorDelegate::ColorDelegate(QDesignerFormEditorInterface *core, QObject *parent) : + QItemDelegate(parent), + m_core(core) +{ +} + +QWidget *ColorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, + const QModelIndex &index) const +{ + QWidget *ed = nullptr; + if (index.column() == 0) { + RoleEditor *editor = new RoleEditor(parent); + connect(editor, &RoleEditor::changed, this, &ColorDelegate::commitData); + //editor->setFocusPolicy(Qt::NoFocus); + //editor->installEventFilter(const_cast(this)); + ed = editor; + } else { + BrushEditor *editor = new BrushEditor(m_core, parent); + connect(editor, QOverload::of(&BrushEditor::changed), + this, &ColorDelegate::commitData); + editor->setFocusPolicy(Qt::NoFocus); + editor->installEventFilter(const_cast(this)); + ed = editor; + } + return ed; +} + +void ColorDelegate::setEditorData(QWidget *ed, const QModelIndex &index) const +{ + if (index.column() == 0) { + const bool mask = qvariant_cast(index.model()->data(index, Qt::EditRole)); + RoleEditor *editor = static_cast(ed); + editor->setEdited(mask); + const QString colorName = qvariant_cast(index.model()->data(index, Qt::DisplayRole)); + editor->setLabel(colorName); + } else { + const QBrush br = qvariant_cast(index.model()->data(index, BrushRole)); + BrushEditor *editor = static_cast(ed); + editor->setBrush(br); + } +} + +void ColorDelegate::setModelData(QWidget *ed, QAbstractItemModel *model, + const QModelIndex &index) const +{ + if (index.column() == 0) { + RoleEditor *editor = static_cast(ed); + const bool mask = editor->edited(); + model->setData(index, mask, Qt::EditRole); + } else { + BrushEditor *editor = static_cast(ed); + if (editor->changed()) { + QBrush br = editor->brush(); + model->setData(index, br, BrushRole); + } + } +} + +void ColorDelegate::updateEditorGeometry(QWidget *ed, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QItemDelegate::updateEditorGeometry(ed, option, index); + ed->setGeometry(ed->geometry().adjusted(0, 0, -1, -1)); +} + +void ColorDelegate::paint(QPainter *painter, const QStyleOptionViewItem &opt, + const QModelIndex &index) const +{ + QStyleOptionViewItem option = opt; + const bool mask = qvariant_cast(index.model()->data(index, Qt::EditRole)); + if (index.column() == 0 && mask) { + option.font.setBold(true); + } + QBrush br = qvariant_cast(index.model()->data(index, BrushRole)); + if (br.style() == Qt::LinearGradientPattern || + br.style() == Qt::RadialGradientPattern || + br.style() == Qt::ConicalGradientPattern) { + painter->save(); + painter->translate(option.rect.x(), option.rect.y()); + painter->scale(option.rect.width(), option.rect.height()); + QGradient gr = *(br.gradient()); + gr.setCoordinateMode(QGradient::LogicalMode); + br = QBrush(gr); + painter->fillRect(0, 0, 1, 1, br); + painter->restore(); + } else { + painter->save(); + painter->setBrushOrigin(option.rect.x(), option.rect.y()); + painter->fillRect(option.rect, br); + painter->restore(); + } + QItemDelegate::paint(painter, option, index); + + + const QColor color = static_cast(QApplication::style()->styleHint(QStyle::SH_Table_GridLineColor, &option)); + const QPen oldPen = painter->pen(); + painter->setPen(QPen(color)); + + painter->drawLine(option.rect.right(), option.rect.y(), + option.rect.right(), option.rect.bottom()); + painter->drawLine(option.rect.x(), option.rect.bottom(), + option.rect.right(), option.rect.bottom()); + painter->setPen(oldPen); +} + +QSize ColorDelegate::sizeHint(const QStyleOptionViewItem &opt, const QModelIndex &index) const +{ + return QItemDelegate::sizeHint(opt, index) + QSize(4, 4); +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/paletteeditor.h b/src/tools/designer/src/components/propertyeditor/paletteeditor.h new file mode 100644 index 00000000000..782729d944c --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/paletteeditor.h @@ -0,0 +1,187 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PALETTEEDITOR_H +#define PALETTEEDITOR_H + +#include "ui_paletteeditor.h" +#include + +QT_BEGIN_NAMESPACE + +class QAction; +class QListView; +class QMenu; +class QLabel; +class QtColorButton; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class PaletteEditor: public QDialog +{ + Q_OBJECT +public: + ~PaletteEditor() override; + + static QPalette getPalette(QDesignerFormEditorInterface *core, + QWidget* parent, const QPalette &init = QPalette(), + const QPalette &parentPal = QPalette(), int *result = nullptr); + + QPalette palette() const; + void setPalette(const QPalette &palette); + void setPalette(const QPalette &palette, const QPalette &parentPalette); + +private slots: + + void buildButtonColorChanged(); + void activeRadioClicked(); + void inactiveRadioClicked(); + void disabledRadioClicked(); + void computeRadioClicked(); + void detailsRadioClicked(); + + void paletteChanged(const QPalette &palette); + void viewContextMenuRequested(QPoint pos); + void save(); + void load(); + +protected: + +private: + PaletteEditor(QDesignerFormEditorInterface *core, QWidget *parent); + void buildPalette(); + + void updatePreviewPalette(); + void updateStyledButton(); + + QPalette::ColorGroup currentColorGroup() const + { return m_currentColorGroup; } + + Ui::PaletteEditor ui; + QPalette m_editPalette; + QPalette m_parentPalette; + class PaletteModel *m_paletteModel; + QDesignerFormEditorInterface *m_core; + QAction *m_lighterAction = nullptr; + QAction *m_darkerAction = nullptr; + QAction *m_copyColorAction = nullptr; + QMenu *m_contextMenu = nullptr; + QPalette::ColorGroup m_currentColorGroup = QPalette::Active; + bool m_modelUpdated = false; + bool m_paletteUpdated = false; + bool m_compute = true; +}; + + +class PaletteModel : public QAbstractTableModel +{ + Q_OBJECT + Q_PROPERTY(QPalette::ColorRole colorRole READ colorRole) +public: + explicit PaletteModel(QObject *parent = nullptr); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + + QPalette getPalette() const; + void setPalette(const QPalette &palette, const QPalette &parentPalette); + + QBrush brushAt(const QModelIndex &index) const; + + QPalette::ColorRole colorRole() const { return QPalette::NoRole; } + void setCompute(bool on) { m_compute = on; } + + quint64 rowMask(const QModelIndex &index) const; + +signals: + void paletteChanged(const QPalette &palette); +private: + struct RoleEntry + { + QString name; + QPalette::ColorRole role; + }; + + QPalette::ColorGroup columnToGroup(int index) const; + int groupToColumn(QPalette::ColorGroup group) const; + QPalette::ColorRole roleAt(int row) const { return m_roleEntries.at(row).role; } + int rowOf(QPalette::ColorRole role) const; + + QPalette m_palette; + QPalette m_parentPalette; + QList m_roleEntries; + bool m_compute = true; +}; + +class BrushEditor : public QWidget +{ + Q_OBJECT +public: + explicit BrushEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + + void setBrush(const QBrush &brush); + QBrush brush() const; + bool changed() const; +signals: + void changed(QWidget *widget); +private slots: + void brushChanged(); +private: + QtColorButton *m_button; + bool m_changed = false; + QDesignerFormEditorInterface *m_core; +}; + +class RoleEditor : public QWidget +{ + Q_OBJECT +public: + explicit RoleEditor(QWidget *parent = nullptr); + + void setLabel(const QString &label); + void setEdited(bool on); + bool edited() const; +signals: + void changed(QWidget *widget); +private slots: + void emitResetProperty(); +private: + QLabel *m_label; + bool m_edited = false; +}; + +class ColorDelegate : public QItemDelegate +{ + Q_OBJECT + +public: + explicit ColorDelegate(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void setEditorData(QWidget *ed, const QModelIndex &index) const override; + void setModelData(QWidget *ed, QAbstractItemModel *model, + const QModelIndex &index) const override; + + void updateEditorGeometry(QWidget *ed, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + + void paint(QPainter *painter, const QStyleOptionViewItem &opt, + const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &opt, const QModelIndex &index) const override; +private: + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PALETTEEDITOR_H diff --git a/src/tools/designer/src/components/propertyeditor/paletteeditor.ui b/src/tools/designer/src/components/propertyeditor/paletteeditor.ui new file mode 100644 index 00000000000..0a6aeaac7ea --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/paletteeditor.ui @@ -0,0 +1,237 @@ + + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::PaletteEditor + + + + 0 + 0 + 918 + 599 + + + + + 0 + 0 + + + + Edit Palette + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Tune Palette + + + + 9 + + + 9 + + + 9 + + + 9 + + + 6 + + + + + + 0 + 0 + + + + + + + + + + + + 0 + 200 + + + + + + + + Show Details + + + + + + + Compute Details + + + true + + + + + + + Quick + + + + + + + + + + + 0 + 0 + + + + Preview + + + + 8 + + + 8 + + + 8 + + + 8 + + + 6 + + + + + Disabled + + + + + + + Inactive + + + + + + + Active + + + true + + + + + + + + 0 + 0 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + QtColorButton + QToolButton +
qtcolorbutton_p.h
+
+ + qdesigner_internal::PreviewFrame + QWidget +
previewframe.h
+
+
+ + + + buttonBox + accepted() + qdesigner_internal::PaletteEditor + accept() + + + 180 + 331 + + + 134 + 341 + + + + + buttonBox + rejected() + qdesigner_internal::PaletteEditor + reject() + + + 287 + 329 + + + 302 + 342 + + + + +
diff --git a/src/tools/designer/src/components/propertyeditor/paletteeditorbutton.cpp b/src/tools/designer/src/components/propertyeditor/paletteeditorbutton.cpp new file mode 100644 index 00000000000..4f6661bdca8 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/paletteeditorbutton.cpp @@ -0,0 +1,49 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "paletteeditorbutton.h" +#include "paletteeditor.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +PaletteEditorButton::PaletteEditorButton(QDesignerFormEditorInterface *core, const QPalette &palette, QWidget *parent) + : QToolButton(parent), + m_palette(palette) +{ + m_core = core; + setFocusPolicy(Qt::NoFocus); + setText(tr("Change Palette")); + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + + connect(this, &QAbstractButton::clicked, this, &PaletteEditorButton::showPaletteEditor); +} + +PaletteEditorButton::~PaletteEditorButton() = default; + +void PaletteEditorButton::setPalette(const QPalette &palette) +{ + m_palette = palette; +} + +void PaletteEditorButton::setSuperPalette(const QPalette &palette) +{ + m_superPalette = palette; +} + +void PaletteEditorButton::showPaletteEditor() +{ + int result; + QPalette pal = PaletteEditor::getPalette(m_core, nullptr, m_palette, m_superPalette, &result); + if (result == QDialog::Accepted) { + m_palette = pal; + emit paletteChanged(m_palette); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/paletteeditorbutton.h b/src/tools/designer/src/components/propertyeditor/paletteeditorbutton.h new file mode 100644 index 00000000000..01b9c211a2d --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/paletteeditorbutton.h @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PALETTEEDITORBUTTON_H +#define PALETTEEDITORBUTTON_H + +#include "propertyeditor_global.h" + +#include +#include + +#include "abstractformeditor.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QT_PROPERTYEDITOR_EXPORT PaletteEditorButton: public QToolButton +{ + Q_OBJECT +public: + PaletteEditorButton(QDesignerFormEditorInterface *core, const QPalette &palette, QWidget *parent = nullptr); + ~PaletteEditorButton() override; + + void setSuperPalette(const QPalette &palette); + inline QPalette palette() const + { return m_palette; } + +signals: + void paletteChanged(const QPalette &palette); + +public slots: + void setPalette(const QPalette &palette); + +private slots: + void showPaletteEditor(); + +private: + QPalette m_palette; + QPalette m_superPalette; + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PALETTEEDITORBUTTON_H diff --git a/src/tools/designer/src/components/propertyeditor/pixmapeditor.cpp b/src/tools/designer/src/components/propertyeditor/pixmapeditor.cpp new file mode 100644 index 00000000000..312fd135cc0 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/pixmapeditor.cpp @@ -0,0 +1,420 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "pixmapeditor.h" +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr QSize ICON_SIZE{16, 16}; + +namespace qdesigner_internal { + +static void createIconThemeDialog(QDialog *topLevel, const QString &labelText, + QWidget *themeEditor) +{ + QVBoxLayout *layout = new QVBoxLayout(topLevel); + QLabel *label = new QLabel(labelText, topLevel); + QDialogButtonBox *buttons = new QDialogButtonBox(topLevel); + buttons->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + QObject::connect(buttons, &QDialogButtonBox::accepted, topLevel, &QDialog::accept); + QObject::connect(buttons, &QDialogButtonBox::rejected, topLevel, &QDialog::reject); + + layout->addWidget(label); + layout->addWidget(themeEditor); + layout->addWidget(buttons); +} + +IconThemeDialog::IconThemeDialog(QWidget *parent) + : QDialog(parent) +{ + setWindowTitle(tr("Set Icon From XDG Theme")); + m_editor = new IconThemeEditor(this); + createIconThemeDialog(this, tr("Select icon name from XDG theme:"), m_editor); +} + +std::optional IconThemeDialog::getTheme(QWidget *parent, const QString &theme) +{ + IconThemeDialog dlg(parent); + dlg.m_editor->setTheme(theme); + if (dlg.exec() == QDialog::Accepted) + return dlg.m_editor->theme(); + return std::nullopt; +} + +IconThemeEnumDialog::IconThemeEnumDialog(QWidget *parent) + : QDialog(parent) +{ + setWindowTitle(tr("Set Icon From Theme")); + m_editor = new IconThemeEnumEditor(this); + createIconThemeDialog(this, tr("Select icon name from theme:"), m_editor); +} + +std::optional IconThemeEnumDialog::getTheme(QWidget *parent, int theme) +{ + IconThemeEnumDialog dlg(parent); + dlg.m_editor->setThemeEnum(theme); + if (dlg.exec() == QDialog::Accepted) + return dlg.m_editor->themeEnum(); + return std::nullopt; +} + +PixmapEditor::PixmapEditor(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + m_iconThemeModeEnabled(false), + m_core(core), + m_pixmapLabel(new QLabel(this)), + m_pathLabel(new QLabel(this)), + m_button(new QToolButton(this)), + m_resourceAction(new QAction(tr("Choose Resource..."), this)), + m_fileAction(new QAction(tr("Choose File..."), this)), + m_themeEnumAction(new QAction(tr("Set Icon From Theme..."), this)), + m_themeAction(new QAction(tr("Set Icon From XDG Theme..."), this)), + m_copyAction(new QAction(createIconSet(QIcon::ThemeIcon::EditCopy, "editcopy.png"_L1), + tr("Copy Path"), this)), + m_pasteAction(new QAction(createIconSet(QIcon::ThemeIcon::EditPaste, "editpaste.png"_L1), + tr("Paste Path"), this)), + m_layout(new QHBoxLayout(this)), + m_pixmapCache(nullptr) +{ + m_layout->addWidget(m_pixmapLabel); + m_layout->addWidget(m_pathLabel); + m_button->setText(tr("...")); + m_button->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Ignored); + m_button->setFixedWidth(30); + m_button->setPopupMode(QToolButton::MenuButtonPopup); + m_layout->addWidget(m_button); + m_layout->setContentsMargins(QMargins()); + m_layout->setSpacing(0); + m_pixmapLabel->setFixedWidth(ICON_SIZE.width()); + m_pixmapLabel->setAlignment(Qt::AlignCenter); + m_pathLabel->setSizePolicy(QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed)); + m_themeAction->setVisible(false); + m_themeEnumAction->setVisible(false); + + QMenu *menu = new QMenu(this); + menu->addAction(m_resourceAction); + menu->addAction(m_fileAction); + menu->addAction(m_themeEnumAction); + menu->addAction(m_themeAction); + + m_button->setMenu(menu); + m_button->setText(tr("...")); + + connect(m_button, &QAbstractButton::clicked, this, &PixmapEditor::defaultActionActivated); + connect(m_resourceAction, &QAction::triggered, this, &PixmapEditor::resourceActionActivated); + connect(m_fileAction, &QAction::triggered, this, &PixmapEditor::fileActionActivated); + connect(m_themeEnumAction, &QAction::triggered, this, &PixmapEditor::themeEnumActionActivated); + connect(m_themeAction, &QAction::triggered, this, &PixmapEditor::themeActionActivated); +#if QT_CONFIG(clipboard) + connect(m_copyAction, &QAction::triggered, this, &PixmapEditor::copyActionActivated); + connect(m_pasteAction, &QAction::triggered, this, &PixmapEditor::pasteActionActivated); +#endif + setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored)); + setFocusProxy(m_button); + +#if QT_CONFIG(clipboard) + connect(QApplication::clipboard(), &QClipboard::dataChanged, + this, &PixmapEditor::clipboardDataChanged); + clipboardDataChanged(); +#endif +} + +void PixmapEditor::setPixmapCache(DesignerPixmapCache *cache) +{ + m_pixmapCache = cache; +} + +void PixmapEditor::setIconThemeModeEnabled(bool enabled) +{ + if (m_iconThemeModeEnabled == enabled) + return; + m_iconThemeModeEnabled = enabled; + m_themeAction->setVisible(enabled); + m_themeEnumAction->setVisible(enabled); +} + +void PixmapEditor::setSpacing(int spacing) +{ + m_layout->setSpacing(spacing); +} + +void PixmapEditor::setPath(const QString &path) +{ + m_path = path; + updateLabels(); +} + +void PixmapEditor::setTheme(const QString &theme) +{ + m_theme = theme; + updateLabels(); +} + +QString PixmapEditor::msgThemeIcon(const QString &t) +{ + return tr("[Theme] %1").arg(t); +} + +QString PixmapEditor::msgMissingThemeIcon(const QString &t) +{ + return tr("[Theme] %1 (missing)").arg(t); +} + +void PixmapEditor::setThemeEnum(int e) +{ + m_themeEnum = e; + updateLabels(); +} + +void PixmapEditor::updateLabels() +{ + m_pathLabel->setText(displayText(m_themeEnum, m_theme, m_path)); + switch (state()) { + case State::Empty: + case State::MissingXdgTheme: + case State::MissingThemeEnum: + m_pixmapLabel->setPixmap(m_defaultPixmap); + m_copyAction->setEnabled(false); + break; + case State::ThemeEnum: + m_pixmapLabel->setPixmap(QIcon::fromTheme(static_cast(m_themeEnum)).pixmap(ICON_SIZE)); + m_copyAction->setEnabled(true); + break; + case State::XdgTheme: + m_pixmapLabel->setPixmap(QIcon::fromTheme(m_theme).pixmap(ICON_SIZE)); + m_copyAction->setEnabled(true); + break; + case State::Path: + case State::PathFallback: + if (m_pixmapCache) { + auto pixmap = m_pixmapCache->pixmap(PropertySheetPixmapValue(m_path)); + m_pixmapLabel->setPixmap(QIcon(pixmap).pixmap(ICON_SIZE)); + } + m_copyAction->setEnabled(true); + break; + } +} + +void PixmapEditor::setDefaultPixmapIcon(const QIcon &icon) +{ + m_defaultPixmap = icon.pixmap(ICON_SIZE); + if (state() == State::Empty) + m_pixmapLabel->setPixmap(m_defaultPixmap); +} + +void PixmapEditor::setDefaultPixmap(const QPixmap &pixmap) +{ + setDefaultPixmapIcon(QIcon(pixmap)); +} + +void PixmapEditor::contextMenuEvent(QContextMenuEvent *event) +{ + QMenu menu(this); + menu.addAction(m_copyAction); + menu.addAction(m_pasteAction); + menu.exec(event->globalPos()); + event->accept(); +} + +void PixmapEditor::defaultActionActivated() +{ + if (m_iconThemeModeEnabled) { + themeEnumActionActivated(); + return; + } + // Default to resource + const PropertySheetPixmapValue::PixmapSource ps = m_path.isEmpty() + ? PropertySheetPixmapValue::ResourcePixmap + : PropertySheetPixmapValue::getPixmapSource(m_core, m_path); + switch (ps) { + case PropertySheetPixmapValue::LanguageResourcePixmap: + case PropertySheetPixmapValue::ResourcePixmap: + resourceActionActivated(); + break; + case PropertySheetPixmapValue::FilePixmap: + fileActionActivated(); + break; + } +} + +void PixmapEditor::resourceActionActivated() +{ + const QString oldPath = m_path; + const QString newPath = IconSelector::choosePixmapResource(m_core, m_core->resourceModel(), + oldPath, this); + if (!newPath.isEmpty() && newPath != oldPath) { + setTheme({}); + setThemeEnum(-1); + setPath(newPath); + emit pathChanged(newPath); + } +} + +void PixmapEditor::fileActionActivated() +{ + const QString newPath = IconSelector::choosePixmapFile(m_path, m_core->dialogGui(), this); + if (!newPath.isEmpty() && newPath != m_path) { + setTheme({}); + setThemeEnum(-1); + setPath(newPath); + emit pathChanged(newPath); + } +} + +void PixmapEditor::themeEnumActionActivated() +{ + const auto newThemeO = IconThemeEnumDialog::getTheme(this, {}); + if (newThemeO.has_value()) { + const int newTheme = newThemeO.value(); + if (newTheme != m_themeEnum) { + setThemeEnum(newTheme); + setTheme({}); + setPath({}); + emit themeEnumChanged(newTheme); + } + } +} + +void PixmapEditor::themeActionActivated() +{ + const auto newThemeO = IconThemeDialog::getTheme(this, m_theme); + if (newThemeO.has_value()) { + const QString newTheme = newThemeO.value(); + if (newTheme != m_theme) { + setTheme(newTheme); + setThemeEnum(-1); + setPath({}); + emit themeChanged(newTheme); + } + } +} + +PixmapEditor::State PixmapEditor::stateFromData(int themeEnum, const QString &xdgTheme, + const QString &path) +{ + if (themeEnum != -1) { + if (QIcon::hasThemeIcon(static_cast(themeEnum))) + return State::ThemeEnum; + return path.isEmpty() ? State::MissingThemeEnum : State::PathFallback; + } + if (!xdgTheme.isEmpty()) { + if (QIcon::hasThemeIcon(xdgTheme)) + return State::XdgTheme; + return path.isEmpty() ? State::MissingXdgTheme : State::PathFallback; + } + return path.isEmpty() ? State::Empty : State::Path; +} + +PixmapEditor::State PixmapEditor::state() const +{ + return stateFromData(m_themeEnum, m_theme, m_path); +} + +QString PixmapEditor::displayText(int themeEnum, const QString &xdgTheme, const QString &path) +{ + switch (stateFromData(themeEnum, xdgTheme, path)) { + case State::ThemeEnum: + return msgThemeIcon(IconThemeEnumEditor::iconName(themeEnum)); + case State::MissingThemeEnum: + return msgMissingThemeIcon(IconThemeEnumEditor::iconName(themeEnum)); + case State::XdgTheme: + return msgThemeIcon(xdgTheme); + case State::MissingXdgTheme: + return msgMissingThemeIcon(xdgTheme); + case State::Path: + return QFileInfo(path).fileName(); + case State::PathFallback: + return tr("%1 (fallback)").arg(QFileInfo(path).fileName()); + case State::Empty: + break; + } + return {}; +} + +QString PixmapEditor::displayText(const PropertySheetIconValue &icon) +{ + const auto &paths = icon.paths(); + const auto &it = paths.constFind({QIcon::Normal, QIcon::Off}); + const QString path = it != paths.constEnd() ? it.value().path() : QString{}; + return displayText(icon.themeEnum(), icon.theme(), path); +} + +#if QT_CONFIG(clipboard) +void PixmapEditor::copyActionActivated() +{ + QClipboard *clipboard = QApplication::clipboard(); + switch (state()) { + case State::ThemeEnum: + case State::MissingThemeEnum: + clipboard->setText(IconThemeEnumEditor::iconName(m_themeEnum)); + break; + case State::XdgTheme: + case State::MissingXdgTheme: + clipboard->setText(m_theme); + break; + case State::Path: + case State::PathFallback: + clipboard->setText(m_path); + break; + case State::Empty: + break; + } +} + +void PixmapEditor::pasteActionActivated() +{ + QClipboard *clipboard = QApplication::clipboard(); + QString subtype = u"plain"_s; + QString text = clipboard->text(subtype); + if (!text.isNull()) { + QStringList list = text.split(u'\n'); + if (!list.isEmpty()) { + text = list.at(0); + if (m_iconThemeModeEnabled && QIcon::hasThemeIcon(text)) { + setTheme(text); + setPath(QString()); + emit themeChanged(text); + } else { + setPath(text); + setTheme(QString()); + emit pathChanged(text); + } + } + } +} + +void PixmapEditor::clipboardDataChanged() +{ + QClipboard *clipboard = QApplication::clipboard(); + QString subtype = u"plain"_s; + const QString text = clipboard->text(subtype); + m_pasteAction->setEnabled(!text.isNull()); +} +#endif // QT_CONFIG(clipboard) + +} // qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/pixmapeditor.h b/src/tools/designer/src/components/propertyeditor/pixmapeditor.h new file mode 100644 index 00000000000..9ca73059513 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/pixmapeditor.h @@ -0,0 +1,128 @@ +// Copyright (C) 2024 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PIXMAPEDITOR_H +#define PIXMAPEDITOR_H + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QLabel; +class QHBoxLayout; +class QToolButton; + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class DesignerPixmapCache; +class IconThemeEditor; +class IconThemeEnumEditor; +class PropertySheetIconValue; + +class IconThemeDialog : public QDialog +{ + Q_OBJECT +public: + static std::optional getTheme(QWidget *parent, const QString &theme); +private: + explicit IconThemeDialog(QWidget *parent); + IconThemeEditor *m_editor; +}; + +class IconThemeEnumDialog : public QDialog +{ + Q_OBJECT +public: + static std::optional getTheme(QWidget *parent, int theme); + +private: + IconThemeEnumDialog(QWidget *parent); + IconThemeEnumEditor *m_editor; +}; + +class PixmapEditor : public QWidget +{ + Q_OBJECT +public: + explicit PixmapEditor(QDesignerFormEditorInterface *core, QWidget *parent); + + void setSpacing(int spacing); + void setPixmapCache(DesignerPixmapCache *cache); + void setIconThemeModeEnabled(bool enabled); + + static QString msgThemeIcon(const QString &t); + static QString msgMissingThemeIcon(const QString &t); + static QString displayText(const PropertySheetIconValue &icon); + +public slots: + void setPath(const QString &path); + void setTheme(const QString &theme); + void setThemeEnum(int e); + void setDefaultPixmap(const QPixmap &pixmap); + void setDefaultPixmapIcon(const QIcon &icon); + +signals: + void pathChanged(const QString &path); + void themeEnumChanged(int themeEnum); + void themeChanged(const QString &theme); + +protected: + void contextMenuEvent(QContextMenuEvent *event) override; + +private slots: + void defaultActionActivated(); + void resourceActionActivated(); + void fileActionActivated(); + void themeEnumActionActivated(); + void themeActionActivated(); +#if QT_CONFIG(clipboard) + void copyActionActivated(); + void pasteActionActivated(); + void clipboardDataChanged(); +#endif +private: + enum class State { + Empty, + ThemeEnum, + MissingThemeEnum, + XdgTheme, + MissingXdgTheme, + Path, + PathFallback // Non-existent theme icon, falling back to path + }; + + static State stateFromData(int themeEnum, const QString &xdgTheme, const QString &path); + State state() const; + static QString displayText(int themeEnum, const QString &xdgTheme, const QString &path); + + void updateLabels(); + bool m_iconThemeModeEnabled; + QDesignerFormEditorInterface *m_core; + QLabel *m_pixmapLabel; + QLabel *m_pathLabel; + QToolButton *m_button; + QAction *m_resourceAction; + QAction *m_fileAction; + QAction *m_themeEnumAction; + QAction *m_themeAction; + QAction *m_copyAction; + QAction *m_pasteAction; + QHBoxLayout *m_layout; + QPixmap m_defaultPixmap; + QString m_path; + QString m_theme; + int m_themeEnum = -1; + DesignerPixmapCache *m_pixmapCache; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PIXMAPEDITOR_H diff --git a/src/tools/designer/src/components/propertyeditor/previewframe.cpp b/src/tools/designer/src/components/propertyeditor/previewframe.cpp new file mode 100644 index 00000000000..04a3141c231 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/previewframe.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "previewframe.h" +#include "previewwidget.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + + class PreviewMdiArea: public QMdiArea { + public: + PreviewMdiArea(QWidget *parent = nullptr) : QMdiArea(parent) {} + protected: + bool viewportEvent(QEvent *event) override; + }; + + bool PreviewMdiArea::viewportEvent (QEvent * event) { + if (event->type() != QEvent::Paint) + return QMdiArea::viewportEvent (event); + QWidget *paintWidget = viewport(); + QPainter p(paintWidget); + p.fillRect(rect(), paintWidget->palette().color(backgroundRole()).darker()); + p.setPen(QPen(Qt::white)); + //: Palette editor background + p.drawText(0, height() / 2, width(), height(), Qt::AlignHCenter, + QCoreApplication::translate("qdesigner_internal::PreviewMdiArea", "The moose in the noose\nate the goose who was loose.")); + return true; + } + +PreviewFrame::PreviewFrame(QWidget *parent) : + QFrame(parent), + m_mdiArea(new PreviewMdiArea(this)) +{ + m_mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + setLineWidth(1); + + QVBoxLayout *vbox = new QVBoxLayout(this); + vbox->setContentsMargins(QMargins()); + vbox->addWidget(m_mdiArea); + + setMinimumSize(ensureMdiSubWindow()->minimumSizeHint()); +} + +void PreviewFrame::setPreviewPalette(const QPalette &pal) +{ + ensureMdiSubWindow()->setPalette(pal); +} + +void PreviewFrame::setSubWindowActive(bool active) +{ + m_mdiArea->setActiveSubWindow (active ? ensureMdiSubWindow() : nullptr); +} + +QMdiSubWindow *PreviewFrame::ensureMdiSubWindow() +{ + if (!m_mdiSubWindow) { + PreviewWidget *previewWidget = new PreviewWidget(m_mdiArea); + m_mdiSubWindow = m_mdiArea->addSubWindow(previewWidget, Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint); + m_mdiSubWindow->move(10,10); + m_mdiSubWindow->showMaximized(); + } + + const Qt::WindowStates state = m_mdiSubWindow->windowState(); + if (state & Qt::WindowMinimized) + m_mdiSubWindow->setWindowState(state & ~Qt::WindowMinimized); + + return m_mdiSubWindow; +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/previewframe.h b/src/tools/designer/src/components/propertyeditor/previewframe.h new file mode 100644 index 00000000000..f368bb8d272 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/previewframe.h @@ -0,0 +1,38 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PREVIEWFRAME_H +#define PREVIEWFRAME_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QMdiArea; +class QMdiSubWindow; + +namespace qdesigner_internal { + +class PreviewFrame: public QFrame +{ + Q_OBJECT +public: + explicit PreviewFrame(QWidget *parent); + + void setPreviewPalette(const QPalette &palette); + void setSubWindowActive(bool active); + +private: + // The user can on some platforms close the mdi child by invoking the system menu. + // Ensure a child is present. + QMdiSubWindow *ensureMdiSubWindow(); + QMdiArea *m_mdiArea; + QPointer m_mdiSubWindow; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/designer/src/components/propertyeditor/previewwidget.cpp b/src/tools/designer/src/components/propertyeditor/previewwidget.cpp new file mode 100644 index 00000000000..722338dd959 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/previewwidget.cpp @@ -0,0 +1,36 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "previewwidget.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +PreviewWidget::PreviewWidget(QWidget *parent) + : QWidget(parent) +{ + ui.setupUi(this); + ui.treeWidget->expandAll(); + auto model = ui.treeWidget->model(); + ui.treeWidget->setCurrentIndex(model->index(0, 0, model->index(0, 0))); + auto toolButtonMenu = new QMenu(ui.menuToolButton); + toolButtonMenu->addAction(tr("Option 1")); + toolButtonMenu->addSeparator(); + auto checkable = toolButtonMenu->addAction(tr("Checkable")); + checkable->setCheckable(true); + ui.menuToolButton->setMenu(toolButtonMenu); + ui.menuToolButton->setPopupMode(QToolButton::InstantPopup); +} + +PreviewWidget::~PreviewWidget() = default; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/previewwidget.h b/src/tools/designer/src/components/propertyeditor/previewwidget.h new file mode 100644 index 00000000000..5b129ef82ab --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/previewwidget.h @@ -0,0 +1,28 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PREVIEWWIDGET_H +#define PREVIEWWIDGET_H + +#include "ui_previewwidget.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class PreviewWidget: public QWidget +{ + Q_OBJECT +public: + explicit PreviewWidget(QWidget *parent); + ~PreviewWidget() override; + +private: + Ui::PreviewWidget ui; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PREVIEWWIDGET_H diff --git a/src/tools/designer/src/components/propertyeditor/previewwidget.ui b/src/tools/designer/src/components/propertyeditor/previewwidget.ui new file mode 100644 index 00000000000..dcbf6272316 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/previewwidget.ui @@ -0,0 +1,307 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::PreviewWidget + + + + 0 + 0 + 608 + 367 + + + + + 0 + 0 + + + + Preview Window + + + + + + Buttons + + + true + + + + + + + + RadioButton1 + + + true + + + + + + + RadioButton2 + + + + + + + RadioButton3 + + + + + + + CheckBox1 + + + true + + + + + + + Tristate CheckBox + + + true + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + PushButton + + + + + + + ToggleButton + + + true + + + true + + + false + + + + + + + + + ToolButton + + + + + + + Menu + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + Item Views + + + true + + + + + + true + + + + Column 1 + + + + + Top Level 1 + + + + Nested Item 1 + + + + + Nested Item 2 + + + + + Nested Item 3 + + + + + + + + + + + + Simple Input Widgets + + + true + + + + + + + + LineEdit + + + true + + + + + + + + ComboBox + + + + + Item1 + + + + + Item2 + + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + + + + + + Display Widgets + + + + + + 50 + + + Qt::Horizontal + + + + + + + QLabel + + + + + + + QFrame::StyledPanel + + + QLabel with frame + + + + + + + + + + + diff --git a/src/tools/designer/src/components/propertyeditor/propertyeditor.cpp b/src/tools/designer/src/components/propertyeditor/propertyeditor.cpp new file mode 100644 index 00000000000..43192138cc5 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/propertyeditor.cpp @@ -0,0 +1,1247 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "propertyeditor.h" + +#include "qttreepropertybrowser_p.h" +#include "qtbuttonpropertybrowser_p.h" +#include "qtvariantproperty_p.h" +#include "designerpropertymanager.h" +#include "qdesigner_propertysheet_p.h" +#include "formwindowbase_p.h" + +#include "newdynamicpropertydialog.h" +#include "dynamicpropertysheet.h" +#include "shared_enums_p.h" + +// sdk +#include +#include +#include +#include +#include +#include +// shared +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +enum SettingsView { TreeView, ButtonView }; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto SettingsGroupC = "PropertyEditor"_L1; +static constexpr auto ViewKeyC = "View"_L1; +static constexpr auto ColorKeyC = "Colored"_L1; +static constexpr auto SortedKeyC = "Sorted"_L1; +static constexpr auto ExpansionKeyC = "ExpandedItems"_L1; +static constexpr auto SplitterPositionKeyC = "SplitterPosition"_L1; + +// --------------------------------------------------------------------------------- + +namespace qdesigner_internal { + +// ----------- ElidingLabel +// QLabel does not support text eliding so we need a helper class + +class ElidingLabel : public QWidget +{ +public: + explicit ElidingLabel(const QString &text = QString(), + QWidget *parent = nullptr) : QWidget(parent), m_text(text) + { setContentsMargins(3, 2, 3, 2); } + + void setText(const QString &text) { + m_text = text; + updateGeometry(); + } + void setElidemode(Qt::TextElideMode mode) { + m_mode = mode; + updateGeometry(); + } + +protected: + QSize sizeHint() const override; + void paintEvent(QPaintEvent *e) override; + +private: + QString m_text; + Qt::TextElideMode m_mode = Qt::ElideRight; +}; + +QSize ElidingLabel::sizeHint() const +{ + QSize size = fontMetrics().boundingRect(m_text).size(); + size += QSize(contentsMargins().left() + contentsMargins().right(), + contentsMargins().top() + contentsMargins().bottom()); + return size; +} + +void ElidingLabel::paintEvent(QPaintEvent *) { + QPainter painter(this); + painter.setPen(QColor(0, 0, 0, 60)); + painter.setBrush(QColor(255, 255, 255, 40)); + painter.drawRect(rect().adjusted(0, 0, -1, -1)); + painter.setPen(palette().windowText().color()); + painter.drawText(contentsRect(), Qt::AlignLeft, + fontMetrics().elidedText(m_text, Qt::ElideRight, width(), 0)); +} + + +// ----------- PropertyEditor::Strings + +PropertyEditor::Strings::Strings() : + m_alignmentProperties{u"alignment"_s, + u"layoutLabelAlignment"_s, // QFormLayout + u"layoutFormAlignment"_s}, + m_fontProperty(u"font"_s), + m_qLayoutWidget(u"QLayoutWidget"_s), + m_designerPrefix(u"QDesigner"_s), + m_layout(u"Layout"_s), + m_validationModeAttribute(u"validationMode"_s), + m_fontAttribute(u"font"_s), + m_superPaletteAttribute(u"superPalette"_s), + m_enumNamesAttribute(u"enumNames"_s), + m_resettableAttribute(u"resettable"_s), + m_flagsAttribute(u"flags"_s) +{ +} + +// ----------- PropertyEditor + +QDesignerMetaDataBaseItemInterface* PropertyEditor::metaDataBaseItem() const +{ + QObject *o = object(); + if (!o) + return nullptr; + QDesignerMetaDataBaseInterface *db = core()->metaDataBase(); + if (!db) + return nullptr; + return db->item(o); +} + +void PropertyEditor::setupStringProperty(QtVariantProperty *property, bool isMainContainer) +{ + const StringPropertyParameters params = textPropertyValidationMode(core(), m_object, property->propertyName(), isMainContainer); + // Does a meta DB entry exist - add comment + const bool hasComment = params.second; + property->setAttribute(m_strings.m_validationModeAttribute, params.first); + // assuming comment cannot appear or disappear for the same property in different object instance + if (!hasComment) + qDeleteAll(property->subProperties()); +} + +void PropertyEditor::setupPaletteProperty(QtVariantProperty *property) +{ + QPalette superPalette = QPalette(); + QWidget *currentWidget = qobject_cast(m_object); + if (currentWidget) { + if (currentWidget->isWindow()) + superPalette = QApplication::palette(currentWidget); + else { + if (currentWidget->parentWidget()) + superPalette = currentWidget->parentWidget()->palette(); + } + } + m_updatingBrowser = true; + property->setAttribute(m_strings.m_superPaletteAttribute, superPalette); + m_updatingBrowser = false; +} + +static inline QToolButton *createDropDownButton(QAction *defaultAction, QWidget *parent = nullptr) +{ + QToolButton *rc = new QToolButton(parent); + rc->setDefaultAction(defaultAction); + rc->setPopupMode(QToolButton::InstantPopup); + return rc; +} + +PropertyEditor::PropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) : + QDesignerPropertyEditor(parent, flags), + m_core(core), + m_propertyManager(new DesignerPropertyManager(m_core, this)), + m_stackedWidget(new QStackedWidget), + m_filterWidget(new QLineEdit), + m_addDynamicAction(new QAction(createIconSet("plus.png"_L1), tr("Add Dynamic Property..."), this)), + m_removeDynamicAction(new QAction(createIconSet("minus.png"_L1), tr("Remove Dynamic Property"), this)), + m_sortingAction(new QAction(createIconSet("sort.png"_L1), tr("Sorting"), this)), + m_coloringAction(new QAction(createIconSet("color.png"_L1), tr("Color Groups"), this)), + m_treeAction(new QAction(tr("Tree View"), this)), + m_buttonAction(new QAction(tr("Drop Down Button View"), this)), + m_classLabel(new ElidingLabel) +{ + const QColor colors[] = {{255, 230, 191}, {255, 255, 191}, {191, 255, 191}, + {199, 255, 255}, {234, 191, 255}, {255, 191, 239}}; + const int darknessFactor = 250; + m_colors.reserve(std::size(colors)); + for (const QColor &c : colors) + m_colors.append({c, c.darker(darknessFactor)}); + QColor dynamicColor(191, 207, 255); + QColor layoutColor(255, 191, 191); + m_dynamicColor = {dynamicColor, dynamicColor.darker(darknessFactor)}; + m_layoutColor = {layoutColor, layoutColor.darker(darknessFactor)}; + + updateForegroundBrightness(); + + QActionGroup *actionGroup = new QActionGroup(this); + + m_treeAction->setCheckable(true); + m_treeAction->setIcon(createIconSet("widgets/listview.png"_L1)); + m_buttonAction->setCheckable(true); + m_buttonAction->setIcon(createIconSet("dropdownbutton.png"_L1)); + + actionGroup->addAction(m_treeAction); + actionGroup->addAction(m_buttonAction); + connect(actionGroup, &QActionGroup::triggered, + this, &PropertyEditor::slotViewTriggered); + + // Add actions + QActionGroup *addDynamicActionGroup = new QActionGroup(this); + connect(addDynamicActionGroup, &QActionGroup::triggered, + this, &PropertyEditor::slotAddDynamicProperty); + + QMenu *addDynamicActionMenu = new QMenu(this); + m_addDynamicAction->setMenu(addDynamicActionMenu); + m_addDynamicAction->setEnabled(false); + QAction *addDynamicAction = addDynamicActionGroup->addAction(tr("String...")); + addDynamicAction->setData(static_cast(QMetaType::QString)); + addDynamicActionMenu->addAction(addDynamicAction); + addDynamicAction = addDynamicActionGroup->addAction(tr("Bool...")); + addDynamicAction->setData(static_cast(QMetaType::Bool)); + addDynamicActionMenu->addAction(addDynamicAction); + addDynamicActionMenu->addSeparator(); + addDynamicAction = addDynamicActionGroup->addAction(tr("Other...")); + addDynamicAction->setData(static_cast(QMetaType::UnknownType)); + addDynamicActionMenu->addAction(addDynamicAction); + // remove + m_removeDynamicAction->setEnabled(false); + connect(m_removeDynamicAction, &QAction::triggered, this, &PropertyEditor::slotRemoveDynamicProperty); + // Configure + QAction *configureAction = new QAction(tr("Configure Property Editor"), this); + configureAction->setIcon(createIconSet("configure.png"_L1)); + QMenu *configureMenu = new QMenu(this); + configureAction->setMenu(configureMenu); + + m_sortingAction->setCheckable(true); + connect(m_sortingAction, &QAction::toggled, this, &PropertyEditor::slotSorting); + + m_coloringAction->setCheckable(true); + connect(m_coloringAction, &QAction::toggled, this, &PropertyEditor::slotColoring); + + configureMenu->addAction(m_sortingAction); + configureMenu->addAction(m_coloringAction); + configureMenu->addSeparator(); + configureMenu->addAction(m_treeAction); + configureMenu->addAction(m_buttonAction); + // Assemble toolbar + QToolBar *toolBar = new QToolBar; + toolBar->addWidget(m_filterWidget); + toolBar->addWidget(createDropDownButton(m_addDynamicAction)); + toolBar->addAction(m_removeDynamicAction); + toolBar->addWidget(createDropDownButton(configureAction)); + // Views + QScrollArea *buttonScroll = new QScrollArea(m_stackedWidget); + m_buttonBrowser = new QtButtonPropertyBrowser(buttonScroll); + buttonScroll->setWidgetResizable(true); + buttonScroll->setWidget(m_buttonBrowser); + m_buttonIndex = m_stackedWidget->addWidget(buttonScroll); + connect(m_buttonBrowser, &QtAbstractPropertyBrowser::currentItemChanged, + this, &PropertyEditor::slotCurrentItemChanged); + + m_treeBrowser = new QtTreePropertyBrowser(m_stackedWidget); + m_treeBrowser->setRootIsDecorated(false); + m_treeBrowser->setPropertiesWithoutValueMarked(true); + m_treeBrowser->setResizeMode(QtTreePropertyBrowser::Interactive); + m_treeIndex = m_stackedWidget->addWidget(m_treeBrowser); + connect(m_treeBrowser, &QtAbstractPropertyBrowser::currentItemChanged, + this, &PropertyEditor::slotCurrentItemChanged); + m_filterWidget->setPlaceholderText(tr("Filter")); + m_filterWidget->setClearButtonEnabled(true); + connect(m_filterWidget, &QLineEdit::textChanged, this, &PropertyEditor::setFilter); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(toolBar); + layout->addWidget(m_classLabel); + layout->addSpacerItem(new QSpacerItem(0,1)); + layout->addWidget(m_stackedWidget); + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + + m_treeFactory = new DesignerEditorFactory(m_core, this); + m_treeFactory->setSpacing(0); + m_groupFactory = new DesignerEditorFactory(m_core, this); + QtVariantPropertyManager *variantManager = m_propertyManager; + m_buttonBrowser->setFactoryForManager(variantManager, m_groupFactory); + m_treeBrowser->setFactoryForManager(variantManager, m_treeFactory); + + m_stackedWidget->setCurrentIndex(m_treeIndex); + m_currentBrowser = m_treeBrowser; + m_treeAction->setChecked(true); + + connect(m_groupFactory, &DesignerEditorFactory::resetProperty, + this, &PropertyEditor::slotResetProperty); + connect(m_treeFactory, &DesignerEditorFactory::resetProperty, + this, &PropertyEditor::slotResetProperty); + connect(m_propertyManager, &DesignerPropertyManager::valueChanged, + this, &PropertyEditor::slotValueChanged); + + // retrieve initial settings + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(SettingsGroupC); + const SettingsView view = settings->value(ViewKeyC, TreeView).toInt() == TreeView ? TreeView : ButtonView; + // Coloring not available unless treeview and not sorted + m_sorting = settings->value(SortedKeyC, false).toBool(); + m_coloring = settings->value(ColorKeyC, true).toBool(); + const QVariantMap expansionState = settings->value(ExpansionKeyC, QVariantMap()).toMap(); + const int splitterPosition = settings->value(SplitterPositionKeyC, 150).toInt(); + settings->endGroup(); + // Apply settings + m_sortingAction->setChecked(m_sorting); + m_coloringAction->setChecked(m_coloring); + m_treeBrowser->setSplitterPosition(splitterPosition); + switch (view) { + case TreeView: + m_currentBrowser = m_treeBrowser; + m_stackedWidget->setCurrentIndex(m_treeIndex); + m_treeAction->setChecked(true); + break; + case ButtonView: + m_currentBrowser = m_buttonBrowser; + m_stackedWidget->setCurrentIndex(m_buttonIndex); + m_buttonAction->setChecked(true); + break; + } + // Restore expansionState from QVariant map + for (auto it = expansionState.cbegin(), cend = expansionState.cend(); it != cend; ++it) + m_expansionState.insert(it.key(), it.value().toBool()); + + updateActionsState(); +} + +PropertyEditor::~PropertyEditor() +{ + // Prevent emission of QtTreePropertyBrowser::itemChanged() when deleting + // the current item, causing asserts. + m_treeBrowser->setCurrentItem(nullptr); + storeExpansionState(); + saveSettings(); +} + +void PropertyEditor::saveSettings() const +{ + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(SettingsGroupC); + settings->setValue(ViewKeyC, QVariant(m_treeAction->isChecked() ? TreeView : ButtonView)); + settings->setValue(ColorKeyC, QVariant(m_coloring)); + settings->setValue(SortedKeyC, QVariant(m_sorting)); + // Save last expansionState as QVariant map + QVariantMap expansionState; + for (auto it = m_expansionState.cbegin(), cend = m_expansionState.cend(); it != cend; ++it) + expansionState.insert(it.key(), QVariant(it.value())); + settings->setValue(ExpansionKeyC, expansionState); + settings->setValue(SplitterPositionKeyC, m_treeBrowser->splitterPosition()); + settings->endGroup(); +} + +void PropertyEditor::setExpanded(QtBrowserItem *item, bool expanded) +{ + if (m_buttonBrowser == m_currentBrowser) + m_buttonBrowser->setExpanded(item, expanded); + else if (m_treeBrowser == m_currentBrowser) + m_treeBrowser->setExpanded(item, expanded); +} + +bool PropertyEditor::isExpanded(QtBrowserItem *item) const +{ + if (m_buttonBrowser == m_currentBrowser) + return m_buttonBrowser->isExpanded(item); + if (m_treeBrowser == m_currentBrowser) + return m_treeBrowser->isExpanded(item); + return false; +} + +void PropertyEditor::setItemVisible(QtBrowserItem *item, bool visible) +{ + if (m_currentBrowser == m_treeBrowser) { + m_treeBrowser->setItemVisible(item, visible); + } else { + qWarning("** WARNING %s is not implemented for this browser.", Q_FUNC_INFO); + } +} + +bool PropertyEditor::isItemVisible(QtBrowserItem *item) const +{ + return m_currentBrowser == m_treeBrowser ? m_treeBrowser->isItemVisible(item) : true; +} + +/* Default handling of items not found in the map: + * - Top-level items (classes) are assumed to be expanded + * - Anything below (properties) is assumed to be collapsed + * That is, the map is required, the state cannot be stored in a set */ + +void PropertyEditor::storePropertiesExpansionState(const QList &items) +{ + for (QtBrowserItem *propertyItem : items) { + if (!propertyItem->children().isEmpty()) { + QtProperty *property = propertyItem->property(); + const QString propertyName = property->propertyName(); + const auto itGroup = m_propertyToGroup.constFind(property); + if (itGroup != m_propertyToGroup.constEnd()) { + const QString key = itGroup.value() + u'|' + propertyName; + m_expansionState[key] = isExpanded(propertyItem); + } + } + } +} + +void PropertyEditor::storeExpansionState() +{ + const auto items = m_currentBrowser->topLevelItems(); + if (m_sorting) { + storePropertiesExpansionState(items); + } else { + for (QtBrowserItem *item : items) { + const QString groupName = item->property()->propertyName(); + auto propertyItems = item->children(); + if (!propertyItems.isEmpty()) + m_expansionState[groupName] = isExpanded(item); + + // properties stuff here + storePropertiesExpansionState(propertyItems); + } + } +} + +void PropertyEditor::collapseAll() +{ + const auto items = m_currentBrowser->topLevelItems(); + for (QtBrowserItem *group : items) + setExpanded(group, false); +} + +void PropertyEditor::applyPropertiesExpansionState(const QList &items) +{ + for (QtBrowserItem *propertyItem : items) { + const auto excend = m_expansionState.cend(); + QtProperty *property = propertyItem->property(); + const QString propertyName = property->propertyName(); + const auto itGroup = m_propertyToGroup.constFind(property); + if (itGroup != m_propertyToGroup.constEnd()) { + const QString key = itGroup.value() + u'|' + propertyName; + const auto pit = m_expansionState.constFind(key); + if (pit != excend) + setExpanded(propertyItem, pit.value()); + else + setExpanded(propertyItem, false); + } + } +} + +void PropertyEditor::applyExpansionState() +{ + const auto items = m_currentBrowser->topLevelItems(); + if (m_sorting) { + applyPropertiesExpansionState(items); + } else { + const auto excend = m_expansionState.cend(); + for (QtBrowserItem *item : items) { + const QString groupName = item->property()->propertyName(); + const auto git = m_expansionState.constFind(groupName); + if (git != excend) + setExpanded(item, git.value()); + else + setExpanded(item, true); + // properties stuff here + applyPropertiesExpansionState(item->children()); + } + } +} + +int PropertyEditor::applyPropertiesFilter(const QList &items) +{ + int showCount = 0; + const bool matchAll = m_filterPattern.isEmpty(); + for (QtBrowserItem *propertyItem : items) { + QtProperty *property = propertyItem->property(); + const QString propertyName = property->propertyName(); + const bool showProperty = matchAll || propertyName.contains(m_filterPattern, Qt::CaseInsensitive); + setItemVisible(propertyItem, showProperty); + if (showProperty) + showCount++; + } + return showCount; +} + +void PropertyEditor::applyFilter() +{ + const auto items = m_currentBrowser->topLevelItems(); + if (m_sorting) { + applyPropertiesFilter(items); + } else { + for (QtBrowserItem *item : items) + setItemVisible(item, applyPropertiesFilter(item->children())); + } +} + +void PropertyEditor::clearView() +{ + m_currentBrowser->clear(); +} + +bool PropertyEditor::event(QEvent *event) +{ + if (event->type() == QEvent::PaletteChange) + updateForegroundBrightness(); + + return QDesignerPropertyEditor::event(event); +} + +void PropertyEditor::updateForegroundBrightness() +{ + QColor c = palette().color(QPalette::Text); + bool newBrightness = qRound(0.3 * c.redF() + 0.59 * c.greenF() + 0.11 * c.blueF()); + + if (m_brightness == newBrightness) + return; + + m_brightness = newBrightness; + + updateColors(); +} + +QColor PropertyEditor::propertyColor(QtProperty *property) const +{ + if (!m_coloring) + return QColor(); + + QtProperty *groupProperty = property; + + const auto itProp = m_propertyToGroup.constFind(property); + if (itProp != m_propertyToGroup.constEnd()) + groupProperty = m_nameToGroup.value(itProp.value()); + + const int groupIdx = m_groups.indexOf(groupProperty); + std::pair pair; + if (groupIdx != -1) { + if (groupProperty == m_dynamicGroup) + pair = m_dynamicColor; + else if (isLayoutGroup(groupProperty)) + pair = m_layoutColor; + else + pair = m_colors[groupIdx % m_colors.size()]; + } + if (!m_brightness) + return pair.first; + return pair.second; +} + +void PropertyEditor::fillView() +{ + if (m_sorting) { + for (auto itProperty = m_nameToProperty.cbegin(), end = m_nameToProperty.cend(); itProperty != end; ++itProperty) + m_currentBrowser->addProperty(itProperty.value()); + } else { + for (QtProperty *group : std::as_const(m_groups)) { + QtBrowserItem *item = m_currentBrowser->addProperty(group); + if (m_currentBrowser == m_treeBrowser) + m_treeBrowser->setBackgroundColor(item, propertyColor(group)); + group->setModified(m_currentBrowser == m_treeBrowser); + } + } +} + +bool PropertyEditor::isLayoutGroup(QtProperty *group) const +{ + return group->propertyName() == m_strings.m_layout; +} + +void PropertyEditor::updateActionsState() +{ + m_coloringAction->setEnabled(m_treeAction->isChecked() && !m_sortingAction->isChecked()); +} + +void PropertyEditor::slotViewTriggered(QAction *action) +{ + storeExpansionState(); + collapseAll(); + { + UpdateBlocker ub(this); + clearView(); + int idx = 0; + if (action == m_treeAction) { + m_currentBrowser = m_treeBrowser; + idx = m_treeIndex; + } else if (action == m_buttonAction) { + m_currentBrowser = m_buttonBrowser; + idx = m_buttonIndex; + } + fillView(); + m_stackedWidget->setCurrentIndex(idx); + applyExpansionState(); + applyFilter(); + } + updateActionsState(); +} + +void PropertyEditor::slotSorting(bool sort) +{ + if (sort == m_sorting) + return; + + storeExpansionState(); + m_sorting = sort; + collapseAll(); + { + UpdateBlocker ub(this); + clearView(); + m_treeBrowser->setRootIsDecorated(sort); + fillView(); + applyExpansionState(); + applyFilter(); + } + updateActionsState(); +} + +void PropertyEditor::updateColors() +{ + if (m_treeBrowser && m_currentBrowser == m_treeBrowser) { + const auto items = m_treeBrowser->topLevelItems(); + for (QtBrowserItem *item : items) + m_treeBrowser->setBackgroundColor(item, propertyColor(item->property())); + } +} + +void PropertyEditor::slotColoring(bool coloring) +{ + if (coloring == m_coloring) + return; + + m_coloring = coloring; + + updateColors(); +} + +void PropertyEditor::slotAddDynamicProperty(QAction *action) +{ + if (!m_propertySheet) + return; + + const QDesignerDynamicPropertySheetExtension *dynamicSheet = + qt_extension(m_core->extensionManager(), m_object); + + if (!dynamicSheet) + return; + + QString newName; + QVariant newValue; + { // Make sure the dialog is closed before the signal is emitted. + const int type = action->data().toInt(); + NewDynamicPropertyDialog dlg(core()->dialogGui(), m_currentBrowser); + if (type != QMetaType::UnknownType) + dlg.setPropertyType(type); + + QStringList reservedNames; + const int propertyCount = m_propertySheet->count(); + for (int i = 0; i < propertyCount; i++) { + if (!dynamicSheet->isDynamicProperty(i) || m_propertySheet->isVisible(i)) + reservedNames.append(m_propertySheet->propertyName(i)); + } + dlg.setReservedNames(reservedNames); + if (dlg.exec() == QDialog::Rejected) + return; + newName = dlg.propertyName(); + newValue = dlg.propertyValue(); + } + m_recentlyAddedDynamicProperty = newName; + emit addDynamicProperty(newName, newValue); +} + +QDesignerFormEditorInterface *PropertyEditor::core() const +{ + return m_core; +} + +bool PropertyEditor::isReadOnly() const +{ + return false; +} + +void PropertyEditor::setReadOnly(bool /*readOnly*/) +{ + qDebug() << "PropertyEditor::setReadOnly() request"; +} + +void PropertyEditor::setPropertyValue(const QString &name, const QVariant &value, bool changed) +{ + const auto it = m_nameToProperty.constFind(name); + if (it == m_nameToProperty.constEnd()) + return; + QtVariantProperty *property = it.value(); + updateBrowserValue(property, value); + property->setModified(changed); +} + +/* Quick update that assumes the actual count of properties has not changed + * N/A when for example executing a layout command and margin properties appear. */ +void PropertyEditor::updatePropertySheet() +{ + if (!m_propertySheet) + return; + + updateToolBarLabel(); + + const int propertyCount = m_propertySheet->count(); + const auto npcend = m_nameToProperty.cend(); + for (int i = 0; i < propertyCount; ++i) { + const QString propertyName = m_propertySheet->propertyName(i); + const auto it = m_nameToProperty.constFind(propertyName); + if (it != npcend) + updateBrowserValue(it.value(), m_propertySheet->property(i)); + } +} + +static inline QLayout *layoutOfQLayoutWidget(QObject *o) +{ + if (o->isWidgetType() && !qstrcmp(o->metaObject()->className(), "QLayoutWidget")) + return static_cast(o)->layout(); + return nullptr; +} + +void PropertyEditor::updateToolBarLabel() +{ + QString objectName; + QString className; + if (m_object) { + if (QLayout *l = layoutOfQLayoutWidget(m_object)) + objectName = l->objectName(); + else + objectName = m_object->objectName(); + className = realClassName(m_object); + } + + m_classLabel->setVisible(!objectName.isEmpty() || !className.isEmpty()); + m_classLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + + QString classLabelText; + if (!objectName.isEmpty()) + classLabelText += objectName + " : "_L1; + classLabelText += className; + + m_classLabel->setText(classLabelText); + m_classLabel->setToolTip(tr("Object: %1\nClass: %2") + .arg(objectName, className)); +} + +void PropertyEditor::updateBrowserValue(QtVariantProperty *property, const QVariant &value) +{ + QVariant v = value; + const int type = property->propertyType(); + if (type == QtVariantPropertyManager::enumTypeId()) { + const PropertySheetEnumValue e = qvariant_cast(v); + v = e.metaEnum.keys().indexOf(e.metaEnum.valueToKey(e.value)); + } else if (type == DesignerPropertyManager::designerFlagTypeId()) { + const PropertySheetFlagValue f = qvariant_cast(v); + v = QVariant(f.value); + } else if (type == DesignerPropertyManager::designerAlignmentTypeId()) { + const PropertySheetFlagValue f = qvariant_cast(v); + v = QVariant(f.value); + } + QDesignerPropertySheet *sheet = qobject_cast(m_core->extensionManager()->extension(m_object, Q_TYPEID(QDesignerPropertySheetExtension))); + int index = -1; + if (sheet) + index = sheet->indexOf(property->propertyName()); + if (sheet && m_propertyToGroup.contains(property)) { // don't do it for comments since property sheet doesn't keep them + property->setEnabled(sheet->isEnabled(index)); + } + + // Rich text string property with comment: Store/Update the font the rich text editor dialog starts out with + if (type == QMetaType::QString && !property->subProperties().isEmpty()) { + const int fontIndex = m_propertySheet->indexOf(m_strings.m_fontProperty); + if (fontIndex != -1) + property->setAttribute(m_strings.m_fontAttribute, m_propertySheet->property(fontIndex)); + } + + m_updatingBrowser = true; + property->setValue(v); + if (sheet && sheet->isResourceProperty(index)) + property->setAttribute(u"defaultResource"_s, sheet->defaultResourceProperty(index)); + m_updatingBrowser = false; +} + +int PropertyEditor::toBrowserType(const QVariant &value, const QString &propertyName) const +{ + if (value.canConvert()) { + if (m_strings.m_alignmentProperties.contains(propertyName)) + return DesignerPropertyManager::designerAlignmentTypeId(); + return DesignerPropertyManager::designerFlagTypeId(); + } + if (value.canConvert()) + return DesignerPropertyManager::enumTypeId(); + + return value.userType(); +} + +QString PropertyEditor::realClassName(QObject *object) const +{ + if (!object) + return QString(); + + QString className = QLatin1StringView(object->metaObject()->className()); + const QDesignerWidgetDataBaseInterface *db = core()->widgetDataBase(); + if (QDesignerWidgetDataBaseItemInterface *widgetItem = db->item(db->indexOfObject(object, true))) { + className = widgetItem->name(); + + if (object->isWidgetType() && className == m_strings.m_qLayoutWidget + && static_cast(object)->layout()) { + className = QLatin1StringView(static_cast(object)->layout()->metaObject()->className()); + } + } + + if (className.startsWith(m_strings.m_designerPrefix)) + className.remove(1, m_strings.m_designerPrefix.size() - 1); + + return className; +} + +static const char *typeName(int type) +{ + if (type == qMetaTypeId()) + type = QMetaType::QString; + if (type < int(QMetaType::User)) + return QMetaType(type).name(); + if (type == qMetaTypeId()) + return "QIcon"; + if (type == qMetaTypeId()) + return "QPixmap"; + if (type == qMetaTypeId()) + return "QKeySequence"; + if (type == qMetaTypeId()) + return "QFlags"; + if (type == qMetaTypeId()) + return "enum"; + if (type == QMetaType::UnknownType) + return "invalid"; + if (type == QMetaType::User) + return "user type"; + return nullptr; +} + +static QString msgUnsupportedType(const QString &propertyName, int type) +{ + QString rc; + QTextStream str(&rc); + const char *typeS = typeName(type); + str << "The property \"" << propertyName << "\" of type (" + << (typeS ? typeS : "unknown") << ") is not supported yet!"; + return rc; +} + +void PropertyEditor::setObject(QObject *object) +{ + QDesignerFormWindowInterface *oldFormWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + // In the first setObject() call following the addition of a dynamic property, focus and edit it. + const bool editNewDynamicProperty = object != nullptr && m_object == object && !m_recentlyAddedDynamicProperty.isEmpty(); + m_object = object; + m_propertyManager->setObject(object); + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(m_object); + // QTBUG-68507: Form window can be null for objects in Morph Undo macros with buddies + if (object != nullptr && formWindow == nullptr) { + formWindow = m_core->formWindowManager()->activeFormWindow(); + if (formWindow == nullptr) { + qWarning("PropertyEditor::setObject(): Unable to find form window for \"%s\".", + qPrintable(object->objectName())); + return; + } + } + FormWindowBase *fwb = qobject_cast(formWindow); + const bool idIdBasedTranslation = fwb && fwb->useIdBasedTranslations(); + const bool idIdBasedTranslationUnchanged = (idIdBasedTranslation == DesignerPropertyManager::useIdBasedTranslations()); + DesignerPropertyManager::setUseIdBasedTranslations(idIdBasedTranslation); + m_treeFactory->setFormWindowBase(fwb); + m_groupFactory->setFormWindowBase(fwb); + + storeExpansionState(); + + UpdateBlocker ub(this); + + updateToolBarLabel(); + + QMap toRemove = m_nameToProperty; + + const QDesignerDynamicPropertySheetExtension *dynamicSheet = + qt_extension(m_core->extensionManager(), m_object); + const QDesignerPropertySheet *sheet = qobject_cast(m_core->extensionManager()->extension(m_object, Q_TYPEID(QDesignerPropertySheetExtension))); + + // Optimizization: Instead of rebuilding the complete list every time, compile a list of properties to remove, + // remove them, traverse the sheet, in case property exists just set a value, otherwise - create it. + QExtensionManager *m = m_core->extensionManager(); + + m_propertySheet = qobject_cast(m->extension(object, Q_TYPEID(QDesignerPropertySheetExtension))); + if (m_propertySheet) { + const int stringTypeId = qMetaTypeId(); + const int propertyCount = m_propertySheet->count(); + for (int i = 0; i < propertyCount; ++i) { + if (!m_propertySheet->isVisible(i)) + continue; + + const QString propertyName = m_propertySheet->propertyName(i); + if (m_propertySheet->indexOf(propertyName) != i) + continue; + const QString groupName = m_propertySheet->propertyGroup(i); + const auto rit = toRemove.constFind(propertyName); + if (rit != toRemove.constEnd()) { + QtVariantProperty *property = rit.value(); + const int propertyType = property->propertyType(); + // Also remove string properties in case a change in translation mode + // occurred since different sub-properties are used (disambiguation/id). + if (m_propertyToGroup.value(property) == groupName + && (idIdBasedTranslationUnchanged || propertyType != stringTypeId) + && toBrowserType(m_propertySheet->property(i), propertyName) == propertyType) { + toRemove.remove(propertyName); + } + } + } + } + + for (auto itRemove = toRemove.cbegin(), end = toRemove.cend(); itRemove != end; ++itRemove) { + QtVariantProperty *property = itRemove.value(); + m_nameToProperty.remove(itRemove.key()); + m_propertyToGroup.remove(property); + delete property; + } + + if (oldFormWindow != formWindow) + reloadResourceProperties(); + + bool isMainContainer = false; + if (QWidget *widget = qobject_cast(object)) { + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(widget)) { + isMainContainer = (fw->mainContainer() == widget); + } + } + m_groups.clear(); + + if (m_propertySheet) { + const QString className = WidgetFactory::classNameOf(formWindow->core(), m_object); + const QDesignerCustomWidgetData customData = formWindow->core()->pluginManager()->customWidgetData(className); + + QtProperty *lastProperty = nullptr; + QtProperty *lastGroup = nullptr; + const int propertyCount = m_propertySheet->count(); + for (int i = 0; i < propertyCount; ++i) { + if (!m_propertySheet->isVisible(i)) + continue; + + const QString propertyName = m_propertySheet->propertyName(i); + if (m_propertySheet->indexOf(propertyName) != i) + continue; + const QVariant value = m_propertySheet->property(i); + + const int type = toBrowserType(value, propertyName); + + QtVariantProperty *property = m_nameToProperty.value(propertyName, 0); + bool newProperty = property == nullptr; + if (newProperty) { + property = m_propertyManager->addProperty(type, propertyName); + if (property) { + newProperty = true; + if (type == DesignerPropertyManager::enumTypeId()) { + const PropertySheetEnumValue e = qvariant_cast(value); + m_updatingBrowser = true; + property->setAttribute(m_strings.m_enumNamesAttribute, e.metaEnum.keys()); + m_updatingBrowser = false; + } else if (type == DesignerPropertyManager::designerFlagTypeId()) { + const PropertySheetFlagValue f = qvariant_cast(value); + QList> flags; + for (const QString &name : f.metaFlags.keys()) { + const uint val = f.metaFlags.keyToValue(name); + flags.append({name, val}); + } + m_updatingBrowser = true; + QVariant v; + v.setValue(flags); + property->setAttribute(m_strings.m_flagsAttribute, v); + m_updatingBrowser = false; + } + } + } + + if (property != nullptr) { + const bool dynamicProperty = (dynamicSheet && dynamicSheet->isDynamicProperty(i)) + || (sheet && sheet->isDefaultDynamicProperty(i)); + QString descriptionToolTip; + if (!dynamicProperty && !customData.isNull()) + descriptionToolTip = customData.propertyToolTip(propertyName); + if (descriptionToolTip.isEmpty()) { + if (const char *typeS = typeName(type)) { + descriptionToolTip = propertyName + " ("_L1 + + QLatin1StringView(typeS) + ')'_L1; + } + } + if (!descriptionToolTip.isEmpty()) + property->setDescriptionToolTip(descriptionToolTip); + switch (type) { + case QMetaType::QPalette: + setupPaletteProperty(property); + break; + case QMetaType::QKeySequence: + //addCommentProperty(property, propertyName); + break; + default: + break; + } + if (type == QMetaType::QString || type == qMetaTypeId()) + setupStringProperty(property, isMainContainer); + property->setAttribute(m_strings.m_resettableAttribute, m_propertySheet->hasReset(i)); + + const QString groupName = m_propertySheet->propertyGroup(i); + QtVariantProperty *groupProperty = nullptr; + + if (newProperty) { + auto itPrev = m_nameToProperty.insert(propertyName, property); + m_propertyToGroup[property] = groupName; + if (m_sorting) { + QtProperty *previous = nullptr; + if (itPrev != m_nameToProperty.begin()) + previous = (--itPrev).value(); + m_currentBrowser->insertProperty(property, previous); + } + } + const auto gnit = m_nameToGroup.constFind(groupName); + if (gnit != m_nameToGroup.constEnd()) { + groupProperty = gnit.value(); + } else { + groupProperty = m_propertyManager->addProperty(QtVariantPropertyManager::groupTypeId(), groupName); + QtBrowserItem *item = nullptr; + if (!m_sorting) + item = m_currentBrowser->insertProperty(groupProperty, lastGroup); + m_nameToGroup[groupName] = groupProperty; + m_groups.append(groupProperty); + if (dynamicProperty) + m_dynamicGroup = groupProperty; + if (m_currentBrowser == m_treeBrowser && item) { + m_treeBrowser->setBackgroundColor(item, propertyColor(groupProperty)); + groupProperty->setModified(true); + } + } + /* Group changed or new group. Append to last subproperty of + * that group. Note that there are cases in which a derived + * property sheet appends fake properties for the class + * which will appear after the layout group properties + * (QWizardPage). To make them appear at the end of the + * actual class group, goto last element. */ + if (lastGroup != groupProperty) { + lastGroup = groupProperty; + lastProperty = nullptr; // Append at end + const auto subProperties = lastGroup->subProperties(); + if (!subProperties.isEmpty()) + lastProperty = subProperties.constLast(); + lastGroup = groupProperty; + } + if (!m_groups.contains(groupProperty)) + m_groups.append(groupProperty); + if (newProperty) + groupProperty->insertSubProperty(property, lastProperty); + + lastProperty = property; + + updateBrowserValue(property, value); + + property->setModified(m_propertySheet->isChanged(i)); + if (propertyName == "geometry"_L1 && type == QMetaType::QRect) { + const auto &subProperties = property->subProperties(); + for (QtProperty *subProperty : subProperties) { + const QString subPropertyName = subProperty->propertyName(); + if (subPropertyName == "X"_L1 || subPropertyName == "Y"_L1) + subProperty->setEnabled(!isMainContainer); + } + } + } else { + qWarning("%s", qPrintable(msgUnsupportedType(propertyName, type))); + } + } + } + QMap groups = m_nameToGroup; + for (auto itGroup = groups.cbegin(), end = groups.cend(); itGroup != end; ++itGroup) { + QtVariantProperty *groupProperty = itGroup.value(); + if (groupProperty->subProperties().isEmpty()) { + if (groupProperty == m_dynamicGroup) + m_dynamicGroup = nullptr; + delete groupProperty; + m_nameToGroup.remove(itGroup.key()); + } + } + const bool addEnabled = dynamicSheet ? dynamicSheet->dynamicPropertiesAllowed() : false; + m_addDynamicAction->setEnabled(addEnabled); + m_removeDynamicAction->setEnabled(false); + applyExpansionState(); + applyFilter(); + // In the first setObject() call following the addition of a dynamic property, focus and edit it. + if (editNewDynamicProperty) { + // Have QApplication process the events related to completely closing the modal 'add' dialog, + // otherwise, we cannot focus the property editor in docked mode. + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + editProperty(m_recentlyAddedDynamicProperty); + } + m_recentlyAddedDynamicProperty.clear(); + m_filterWidget->setEnabled(object); +} + +void PropertyEditor::reloadResourceProperties() +{ + m_updatingBrowser = true; + m_propertyManager->reloadResourceProperties(); + m_updatingBrowser = false; +} + +QtBrowserItem *PropertyEditor::nonFakePropertyBrowserItem(QtBrowserItem *item) const +{ + // Top-level properties are QObject/QWidget groups, etc. Find first item property below + // which should be nonfake + const auto topLevelItems = m_currentBrowser->topLevelItems(); + do { + if (topLevelItems.contains(item->parent())) + return item; + item = item->parent(); + } while (item); + return nullptr; +} + +QString PropertyEditor::currentPropertyName() const +{ + if (QtBrowserItem *browserItem = m_currentBrowser->currentItem()) + if (QtBrowserItem *topLevelItem = nonFakePropertyBrowserItem(browserItem)) { + return topLevelItem->property()->propertyName(); + } + return QString(); +} + +void PropertyEditor::slotResetProperty(QtProperty *property) +{ + QDesignerFormWindowInterface *form = m_core->formWindowManager()->activeFormWindow(); + if (!form) + return; + + if (m_propertyManager->resetFontSubProperty(property)) + return; + + if (m_propertyManager->resetIconSubProperty(property)) + return; + + if (m_propertyManager->resetTextAlignmentProperty(property)) + return; + + if (!m_propertyToGroup.contains(property)) + return; + + emit resetProperty(property->propertyName()); +} + +void PropertyEditor::slotValueChanged(QtProperty *property, const QVariant &value, bool enableSubPropertyHandling) +{ + if (m_updatingBrowser) + return; + + if (!m_propertySheet) + return; + + QtVariantProperty *varProp = m_propertyManager->variantProperty(property); + + if (!varProp) + return; + + if (!m_propertyToGroup.contains(property)) + return; + + if (varProp->propertyType() == QtVariantPropertyManager::enumTypeId()) { + PropertySheetEnumValue e = qvariant_cast(m_propertySheet->property(m_propertySheet->indexOf(property->propertyName()))); + const int val = value.toInt(); + const QString valName = varProp->attributeValue(m_strings.m_enumNamesAttribute).toStringList().at(val); + bool ok = false; + e.value = e.metaEnum.parseEnum(valName, &ok); + Q_ASSERT(ok); + QVariant v; + v.setValue(e); + emitPropertyValueChanged(property->propertyName(), v, true); + return; + } + + emitPropertyValueChanged(property->propertyName(), value, enableSubPropertyHandling); +} + +bool PropertyEditor::isDynamicProperty(const QtBrowserItem* item) const +{ + if (!item) + return false; + + const QDesignerDynamicPropertySheetExtension *dynamicSheet = + qt_extension(m_core->extensionManager(), m_object); + + if (!dynamicSheet) + return false; + + return m_propertyToGroup.contains(item->property()) + && dynamicSheet->isDynamicProperty(m_propertySheet->indexOf(item->property()->propertyName())); +} + +void PropertyEditor::editProperty(const QString &name) +{ + // find the browser item belonging to the property, make it current and edit it + QtBrowserItem *browserItem = nullptr; + if (QtVariantProperty *property = m_nameToProperty.value(name, 0)) { + const auto items = m_currentBrowser->items(property); + if (items.size() == 1) + browserItem = items.constFirst(); + } + if (browserItem == nullptr) + return; + m_currentBrowser->setFocus(Qt::OtherFocusReason); + if (m_currentBrowser == m_treeBrowser) { // edit is currently only supported in tree view + m_treeBrowser->editItem(browserItem); + } else { + m_currentBrowser->setCurrentItem(browserItem); + } +} + +void PropertyEditor::slotCurrentItemChanged(QtBrowserItem *item) +{ + m_removeDynamicAction->setEnabled(isDynamicProperty(item)); + +} + +void PropertyEditor::slotRemoveDynamicProperty() +{ + if (QtBrowserItem* item = m_currentBrowser->currentItem()) + if (isDynamicProperty(item)) + emit removeDynamicProperty(item->property()->propertyName()); +} + +void PropertyEditor::setFilter(const QString &pattern) +{ + m_filterPattern = pattern; + applyFilter(); +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/propertyeditor.h b/src/tools/designer/src/components/propertyeditor/propertyeditor.h new file mode 100644 index 00000000000..48cd03b2544 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/propertyeditor.h @@ -0,0 +1,169 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PROPERTYEDITOR_H +#define PROPERTYEDITOR_H + +#include "propertyeditor_global.h" +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class DomProperty; +class QDesignerMetaDataBaseItemInterface; +class QDesignerPropertySheetExtension; +class QLineEdit; + +class QtAbstractPropertyBrowser; +class QtButtonPropertyBrowser; +class QtTreePropertyBrowser; +class QtProperty; +class QtVariantProperty; +class QtBrowserItem; +class QStackedWidget; + +namespace qdesigner_internal { + +class StringProperty; +class DesignerPropertyManager; +class DesignerEditorFactory; +class ElidingLabel; + +class QT_PROPERTYEDITOR_EXPORT PropertyEditor: public QDesignerPropertyEditor +{ + Q_OBJECT +public: + explicit PropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + ~PropertyEditor() override; + + QDesignerFormEditorInterface *core() const override; + + bool isReadOnly() const override; + void setReadOnly(bool readOnly) override; + void setPropertyValue(const QString &name, const QVariant &value, bool changed = true) override; + void updatePropertySheet() override; + + void setObject(QObject *object) override; + + void reloadResourceProperties() override; + + QObject *object() const override + { return m_object; } + + QString currentPropertyName() const override; + +protected: + + bool event(QEvent *event) override; + +private slots: + void slotResetProperty(QtProperty *property); + void slotValueChanged(QtProperty *property, const QVariant &value, bool enableSubPropertyHandling); + void slotViewTriggered(QAction *action); + void slotAddDynamicProperty(QAction *action); + void slotRemoveDynamicProperty(); + void slotSorting(bool sort); + void slotColoring(bool color); + void slotCurrentItemChanged(QtBrowserItem*); + void setFilter(const QString &pattern); + +private: + void updateBrowserValue(QtVariantProperty *property, const QVariant &value); + void updateToolBarLabel(); + int toBrowserType(const QVariant &value, const QString &propertyName) const; + QString removeScope(const QString &value) const; + QDesignerMetaDataBaseItemInterface *metaDataBaseItem() const; + void setupStringProperty(QtVariantProperty *property, bool isMainContainer); + void setupPaletteProperty(QtVariantProperty *property); + QString realClassName(QObject *object) const; + void storeExpansionState(); + void applyExpansionState(); + void storePropertiesExpansionState(const QList &items); + void applyPropertiesExpansionState(const QList &items); + void applyFilter(); + int applyPropertiesFilter(const QList &items); + void setExpanded(QtBrowserItem *item, bool expanded); + bool isExpanded(QtBrowserItem *item) const; + void setItemVisible(QtBrowserItem *item, bool visible); + bool isItemVisible(QtBrowserItem *item) const; + void collapseAll(); + void clearView(); + void fillView(); + bool isLayoutGroup(QtProperty *group) const; + void updateColors(); + void updateForegroundBrightness(); + QColor propertyColor(QtProperty *property) const; + void updateActionsState(); + QtBrowserItem *nonFakePropertyBrowserItem(QtBrowserItem *item) const; + void saveSettings() const; + void editProperty(const QString &name); + bool isDynamicProperty(const QtBrowserItem* item) const; + + struct Strings { + Strings(); + QSet m_alignmentProperties; + const QString m_fontProperty; + const QString m_qLayoutWidget; + const QString m_designerPrefix; + const QString m_layout; + const QString m_validationModeAttribute; + const QString m_fontAttribute; + const QString m_superPaletteAttribute; + const QString m_enumNamesAttribute; + const QString m_resettableAttribute; + const QString m_flagsAttribute; + }; + + const Strings m_strings; + QDesignerFormEditorInterface *m_core; + QDesignerPropertySheetExtension *m_propertySheet = nullptr; + QtAbstractPropertyBrowser *m_currentBrowser = nullptr; + QtButtonPropertyBrowser *m_buttonBrowser; + QtTreePropertyBrowser *m_treeBrowser = nullptr; + DesignerPropertyManager *m_propertyManager; + DesignerEditorFactory *m_treeFactory; + DesignerEditorFactory *m_groupFactory; + QPointer m_object; + QMap m_nameToProperty; + QHash m_propertyToGroup; + QMap m_nameToGroup; + QList m_groups; + QtProperty *m_dynamicGroup = nullptr; + QString m_recentlyAddedDynamicProperty; + bool m_updatingBrowser = false; + + QStackedWidget *m_stackedWidget; + QLineEdit *m_filterWidget; + int m_buttonIndex = -1; + int m_treeIndex = -1; + QAction *m_addDynamicAction; + QAction *m_removeDynamicAction; + QAction *m_sortingAction; + QAction *m_coloringAction; + QAction *m_treeAction; + QAction *m_buttonAction; + ElidingLabel *m_classLabel; + + bool m_sorting = false; + bool m_coloring = false; + + QMap m_expansionState; + + QString m_filterPattern; + QList > m_colors; + std::pair m_dynamicColor; + std::pair m_layoutColor; + + bool m_brightness = false; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PROPERTYEDITOR_H diff --git a/src/tools/designer/src/components/propertyeditor/propertyeditor_global.h b/src/tools/designer/src/components/propertyeditor/propertyeditor_global.h new file mode 100644 index 00000000000..7172218f8d9 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/propertyeditor_global.h @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PROPERTYEDITOR_GLOBAL_H +#define PROPERTYEDITOR_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_WIN +#ifdef QT_PROPERTYEDITOR_LIBRARY +# define QT_PROPERTYEDITOR_EXPORT +#else +# define QT_PROPERTYEDITOR_EXPORT +#endif +#else +#define QT_PROPERTYEDITOR_EXPORT +#endif + +QT_END_NAMESPACE + +#endif // PROPERTYEDITOR_GLOBAL_H diff --git a/src/tools/designer/src/components/propertyeditor/qlonglongvalidator.cpp b/src/tools/designer/src/components/propertyeditor/qlonglongvalidator.cpp new file mode 100644 index 00000000000..906ffdf80c5 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/qlonglongvalidator.cpp @@ -0,0 +1,110 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qlonglongvalidator.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// ---------------------------------------------------------------------------- +QLongLongValidator::QLongLongValidator(QObject * parent) + : QValidator(parent), + b(Q_UINT64_C(0x8000000000000000)), t(Q_UINT64_C(0x7FFFFFFFFFFFFFFF)) +{ +} + +QLongLongValidator::QLongLongValidator(qlonglong minimum, qlonglong maximum, + QObject * parent) + : QValidator(parent), b(minimum), t(maximum) +{ +} + +QLongLongValidator::~QLongLongValidator() = default; + +QValidator::State QLongLongValidator::validate(QString & input, int &) const +{ + if (input.contains(u' ')) + return Invalid; + if (input.isEmpty() || (b < 0 && input == "-"_L1)) + return Intermediate; + bool ok; + qlonglong entered = input.toLongLong(&ok); + if (!ok || (entered < 0 && b >= 0)) + return Invalid; + if (entered >= b && entered <= t) + return Acceptable; + if (entered >= 0) + return entered > t ? Invalid : Intermediate; + return entered < b ? Invalid : Intermediate; +} + +void QLongLongValidator::setRange(qlonglong bottom, qlonglong top) +{ + b = bottom; + t = top; +} + +void QLongLongValidator::setBottom(qlonglong bottom) +{ + setRange(bottom, top()); +} + +void QLongLongValidator::setTop(qlonglong top) +{ + setRange(bottom(), top); +} + + +// ---------------------------------------------------------------------------- +QULongLongValidator::QULongLongValidator(QObject * parent) + : QValidator(parent), + b(0), t(Q_UINT64_C(0xFFFFFFFFFFFFFFFF)) +{ +} + +QULongLongValidator::QULongLongValidator(qulonglong minimum, qulonglong maximum, + QObject * parent) + : QValidator(parent), b(minimum), t(maximum) +{ +} + +QULongLongValidator::~QULongLongValidator() = default; + +QValidator::State QULongLongValidator::validate(QString & input, int &) const +{ + if (input.isEmpty()) + return Intermediate; + + bool ok; + qulonglong entered = input.toULongLong(&ok); + if (input.contains(u' ') || input.contains(u'-') || !ok) + return Invalid; + + if (entered >= b && entered <= t) + return Acceptable; + + return Invalid; +} + +void QULongLongValidator::setRange(qulonglong bottom, qulonglong top) +{ + b = bottom; + t = top; +} + +void QULongLongValidator::setBottom(qulonglong bottom) +{ + setRange(bottom, top()); +} + +void QULongLongValidator::setTop(qulonglong top) +{ + setRange(bottom(), top); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/qlonglongvalidator.h b/src/tools/designer/src/components/propertyeditor/qlonglongvalidator.h new file mode 100644 index 00000000000..4a70908e20c --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/qlonglongvalidator.h @@ -0,0 +1,72 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QLONGLONGVALIDATOR_H +#define QLONGLONGVALIDATOR_H + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QLongLongValidator : public QValidator +{ + Q_OBJECT + Q_PROPERTY(qlonglong bottom READ bottom WRITE setBottom) + Q_PROPERTY(qlonglong top READ top WRITE setTop) + +public: + explicit QLongLongValidator(QObject * parent); + QLongLongValidator(qlonglong bottom, qlonglong top, QObject * parent); + ~QLongLongValidator(); + + QValidator::State validate(QString &, int &) const override; + + void setBottom(qlonglong); + void setTop(qlonglong); + void setRange(qlonglong bottom, qlonglong top); + + qlonglong bottom() const { return b; } + qlonglong top() const { return t; } + +private: + Q_DISABLE_COPY_MOVE(QLongLongValidator) + + qlonglong b; + qlonglong t; +}; + +// ---------------------------------------------------------------------------- +class QULongLongValidator : public QValidator +{ + Q_OBJECT + Q_PROPERTY(qulonglong bottom READ bottom WRITE setBottom) + Q_PROPERTY(qulonglong top READ top WRITE setTop) + +public: + explicit QULongLongValidator(QObject * parent); + QULongLongValidator(qulonglong bottom, qulonglong top, QObject * parent); + ~QULongLongValidator(); + + QValidator::State validate(QString &, int &) const override; + + void setBottom(qulonglong); + void setTop(qulonglong); + void setRange(qulonglong bottom, qulonglong top); + + qulonglong bottom() const { return b; } + qulonglong top() const { return t; } + +private: + Q_DISABLE_COPY_MOVE(QULongLongValidator) + + qulonglong b; + qulonglong t; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QLONGLONGVALIDATOR_H diff --git a/src/tools/designer/src/components/propertyeditor/stringlisteditor.cpp b/src/tools/designer/src/components/propertyeditor/stringlisteditor.cpp new file mode 100644 index 00000000000..3e170ee6960 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/stringlisteditor.cpp @@ -0,0 +1,181 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "stringlisteditor.h" +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +StringListEditor::StringListEditor(QWidget *parent) + : QDialog(parent), m_model(new QStringListModel(this)) +{ + setupUi(this); + listView->setModel(m_model); + + connect(listView->selectionModel(), + &QItemSelectionModel::currentChanged, + this, &StringListEditor::currentIndexChanged); + connect(listView->itemDelegate(), + &QAbstractItemDelegate::closeEditor, + this, &StringListEditor::currentValueChanged); + + connect(upButton, &QAbstractButton::clicked, this, &StringListEditor::upButtonClicked); + connect(downButton, &QAbstractButton::clicked, this, &StringListEditor::downButtonClicked); + connect(newButton, &QAbstractButton::clicked, this, &StringListEditor::newButtonClicked); + connect(deleteButton, &QAbstractButton::clicked, this, &StringListEditor::deleteButtonClicked); + connect(valueEdit, &QLineEdit::textEdited, this, &StringListEditor::valueEdited); + + QIcon upIcon = createIconSet("up.png"_L1); + QIcon downIcon = createIconSet("down.png"_L1); + QIcon minusIcon = createIconSet("minus.png"_L1); + QIcon plusIcon = createIconSet("plus.png"_L1); + upButton->setIcon(upIcon); + downButton->setIcon(downIcon); + newButton->setIcon(plusIcon); + deleteButton->setIcon(minusIcon); + + updateUi(); +} + +StringListEditor::~StringListEditor() = default; + +QStringList StringListEditor::getStringList(QWidget *parent, const QStringList &init, int *result) +{ + StringListEditor dlg(parent); + dlg.setStringList(init); + int res = dlg.exec(); + if (result) + *result = res; + return (res == QDialog::Accepted) ? dlg.stringList() : init; +} + +void StringListEditor::setStringList(const QStringList &stringList) +{ + m_model->setStringList(stringList); + updateUi(); +} + +QStringList StringListEditor::stringList() const +{ + return m_model->stringList(); +} + +void StringListEditor::currentIndexChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + Q_UNUSED(previous); + setCurrentIndex(current.row()); + updateUi(); +} + +void StringListEditor::currentValueChanged() +{ + setCurrentIndex(currentIndex()); + updateUi(); +} + +void StringListEditor::upButtonClicked() +{ + int from = currentIndex(); + int to = currentIndex() - 1; + QString value = stringAt(from); + removeString(from); + insertString(to, value); + setCurrentIndex(to); + updateUi(); +} + +void StringListEditor::downButtonClicked() +{ + int from = currentIndex(); + int to = currentIndex() + 1; + QString value = stringAt(from); + removeString(from); + insertString(to, value); + setCurrentIndex(to); + updateUi(); +} + +void StringListEditor::newButtonClicked() +{ + int to = currentIndex(); + if (to == -1) + to = count() - 1; + ++to; + insertString(to, QString()); + setCurrentIndex(to); + updateUi(); + editString(to); +} + +void StringListEditor::deleteButtonClicked() +{ + removeString(currentIndex()); + setCurrentIndex(currentIndex()); + updateUi(); +} + +void StringListEditor::valueEdited(const QString &text) +{ + setStringAt(currentIndex(), text); +} + +void StringListEditor::updateUi() +{ + upButton->setEnabled((count() > 1) && (currentIndex() > 0)); + downButton->setEnabled((count() > 1) && (currentIndex() >= 0) && (currentIndex() < (count() - 1))); + deleteButton->setEnabled(currentIndex() != -1); + valueEdit->setEnabled(currentIndex() != -1); +} + +int StringListEditor::currentIndex() const +{ + return listView->currentIndex().row(); +} + +void StringListEditor::setCurrentIndex(int index) +{ + QModelIndex modelIndex = m_model->index(index, 0); + if (listView->currentIndex() != modelIndex) + listView->setCurrentIndex(modelIndex); + valueEdit->setText(stringAt(index)); +} + +int StringListEditor::count() const +{ + return m_model->rowCount(); +} + +QString StringListEditor::stringAt(int index) const +{ + return qvariant_cast(m_model->data(m_model->index(index, 0), Qt::DisplayRole)); +} + +void StringListEditor::setStringAt(int index, const QString &value) +{ + m_model->setData(m_model->index(index, 0), value); +} + +void StringListEditor::removeString(int index) +{ + m_model->removeRows(index, 1); +} + +void StringListEditor::insertString(int index, const QString &value) +{ + m_model->insertRows(index, 1); + m_model->setData(m_model->index(index, 0), value); +} + +void StringListEditor::editString(int index) +{ + listView->edit(m_model->index(index, 0)); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/stringlisteditor.h b/src/tools/designer/src/components/propertyeditor/stringlisteditor.h new file mode 100644 index 00000000000..a64250fccc1 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/stringlisteditor.h @@ -0,0 +1,54 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef STRINGLISTEDITOR_H +#define STRINGLISTEDITOR_H + +#include "ui_stringlisteditor.h" +#include + +QT_BEGIN_NAMESPACE +class QStringListModel; + +namespace qdesigner_internal { + +class StringListEditor : public QDialog, private Ui::Dialog +{ + Q_OBJECT +public: + ~StringListEditor(); + void setStringList(const QStringList &stringList); + QStringList stringList() const; + + static QStringList getStringList( + QWidget *parent, const QStringList &init = QStringList(), int *result = nullptr); + +private slots: + void upButtonClicked(); + void downButtonClicked(); + void newButtonClicked(); + void deleteButtonClicked(); + void valueEdited(const QString &text); + void currentIndexChanged(const QModelIndex ¤t, const QModelIndex &previous); + void currentValueChanged(); + +private: + StringListEditor(QWidget *parent = nullptr); + void updateUi(); + int currentIndex() const; + void setCurrentIndex(int index); + int count() const; + QString stringAt(int index) const; + void setStringAt(int index, const QString &value); + void removeString(int index); + void insertString(int index, const QString &value); + void editString(int index); + + QStringListModel *m_model; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // STRINGLISTEDITOR_H diff --git a/src/tools/designer/src/components/propertyeditor/stringlisteditor.ui b/src/tools/designer/src/components/propertyeditor/stringlisteditor.ui new file mode 100644 index 00000000000..c7a718c9fca --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/stringlisteditor.ui @@ -0,0 +1,229 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::Dialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + 9 + + + 6 + + + + + StringList + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + 0 + + + 6 + + + + + New String + + + &New + + + Qt::ToolButtonTextBesideIcon + + + + + + + Delete String + + + &Delete + + + Qt::ToolButtonTextBesideIcon + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + 6 + + + + + &Value: + + + valueEdit + + + + + + + + + + + + + + 0 + + + 6 + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Move String Up + + + Up + + + + + + + Move String Down + + + Down + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + qdesigner_internal::Dialog + accept() + + + 258 + 283 + + + 138 + 294 + + + + + buttonBox + rejected() + qdesigner_internal::Dialog + reject() + + + 350 + 284 + + + 369 + 295 + + + + + diff --git a/src/tools/designer/src/components/propertyeditor/stringlisteditorbutton.cpp b/src/tools/designer/src/components/propertyeditor/stringlisteditorbutton.cpp new file mode 100644 index 00000000000..59323686cd4 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/stringlisteditorbutton.cpp @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "stringlisteditorbutton.h" +#include "stringlisteditor.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +StringListEditorButton::StringListEditorButton( + const QStringList &stringList, QWidget *parent) + : QToolButton(parent), m_stringList(stringList) +{ + setFocusPolicy(Qt::NoFocus); + setText(tr("Change String List")); + setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed)); + + connect(this, &QAbstractButton::clicked, this, &StringListEditorButton::showStringListEditor); +} + +StringListEditorButton::~StringListEditorButton() = default; + +void StringListEditorButton::setStringList(const QStringList &stringList) +{ + m_stringList = stringList; +} + +void StringListEditorButton::showStringListEditor() +{ + int result; + QStringList lst = StringListEditor::getStringList(nullptr, m_stringList, &result); + if (result == QDialog::Accepted) { + m_stringList = lst; + emit stringListChanged(m_stringList); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/propertyeditor/stringlisteditorbutton.h b/src/tools/designer/src/components/propertyeditor/stringlisteditorbutton.h new file mode 100644 index 00000000000..8a41c065d67 --- /dev/null +++ b/src/tools/designer/src/components/propertyeditor/stringlisteditorbutton.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef STRINGLISTEDITORBUTTON_H +#define STRINGLISTEDITORBUTTON_H + +#include "propertyeditor_global.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QT_PROPERTYEDITOR_EXPORT StringListEditorButton: public QToolButton +{ + Q_OBJECT +public: + explicit StringListEditorButton(const QStringList &stringList, QWidget *parent = nullptr); + ~StringListEditorButton() override; + + inline QStringList stringList() const + { return m_stringList; } + +signals: + void stringListChanged(const QStringList &stringList); + +public slots: + void setStringList(const QStringList &stringList); + +private slots: + void showStringListEditor(); + +private: + QStringList m_stringList; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // STRINGLISTEDITORBUTTON_H diff --git a/src/tools/designer/src/components/signalsloteditor/connectdialog.cpp b/src/tools/designer/src/components/signalsloteditor/connectdialog.cpp new file mode 100644 index 00000000000..6f122b16ce3 --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/connectdialog.cpp @@ -0,0 +1,296 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "connectdialog_p.h" +#include "signalslot_utils_p.h" + +#include +#include + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QString realClassName(QDesignerFormEditorInterface *core, QWidget *widget) +{ + QString class_name = QLatin1StringView(widget->metaObject()->className()); + const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); + const int idx = wdb->indexOfObject(widget); + if (idx != -1) + class_name = wdb->item(idx)->name(); + return class_name; +} + +static QString widgetLabel(QDesignerFormEditorInterface *core, QWidget *widget) +{ + return "%1 (%2)"_L1 + .arg(qdesigner_internal::realObjectName(core, widget), + realClassName(core, widget)); +} + +namespace qdesigner_internal { + +ConnectDialog::ConnectDialog(QDesignerFormWindowInterface *formWindow, + QWidget *source, QWidget *destination, + QWidget *parent) : + QDialog(parent), + m_source(source), + m_destination(destination), + m_sourceMode(widgetMode(m_source, formWindow)), + m_destinationMode(widgetMode(m_destination, formWindow)), + m_formWindow(formWindow) +{ + m_ui.setupUi(this); + + connect(m_ui.signalList, &QListWidget::itemClicked, + this, &ConnectDialog::selectSignal); + connect(m_ui.slotList, &QListWidget::itemClicked, + this, &ConnectDialog::selectSlot); + m_ui.slotList->setEnabled(false); + + QPushButton *ok_button = okButton(); + ok_button->setDefault(true); + ok_button->setEnabled(false); + + connect(m_ui.showAllCheckBox, &QCheckBox::toggled, this, &ConnectDialog::populateLists); + + QDesignerFormEditorInterface *core = m_formWindow->core(); + m_ui.signalGroupBox->setTitle(widgetLabel(core, source)); + m_ui.slotGroupBox->setTitle(widgetLabel(core, destination)); + + m_ui.editSignalsButton->setEnabled(m_sourceMode != NormalWidget); + connect(m_ui.editSignalsButton, &QAbstractButton::clicked, + this, &ConnectDialog::editSignals); + + m_ui.editSlotsButton->setEnabled(m_destinationMode != NormalWidget); + connect(m_ui.editSlotsButton, &QAbstractButton::clicked, + this, &ConnectDialog::editSlots); + + populateLists(); +} + +ConnectDialog::WidgetMode ConnectDialog::widgetMode(QWidget *w, QDesignerFormWindowInterface *formWindow) +{ + QDesignerFormEditorInterface *core = formWindow->core(); + if (qt_extension(core->extensionManager(), core)) + return NormalWidget; + + if (w == formWindow || formWindow->mainContainer() == w) + return MainContainer; + + if (isPromoted(formWindow->core(), w)) + return PromotedWidget; + + return NormalWidget; +} + +QPushButton *ConnectDialog::okButton() +{ + return m_ui.buttonBox->button(QDialogButtonBox::Ok); +} + +void ConnectDialog::setOkButtonEnabled(bool e) +{ + okButton()->setEnabled(e); +} + +void ConnectDialog::populateLists() +{ + populateSignalList(); +} + +void ConnectDialog::setSignalSlot(const QString &signal, const QString &slot) +{ + auto sigItems = m_ui.signalList->findItems(signal, Qt::MatchExactly); + + if (sigItems.isEmpty()) { + m_ui.showAllCheckBox->setChecked(true); + sigItems = m_ui.signalList->findItems(signal, Qt::MatchExactly); + } + + if (!sigItems.isEmpty()) { + selectSignal(sigItems.constFirst()); + auto slotItems = m_ui.slotList->findItems(slot, Qt::MatchExactly); + if (slotItems.isEmpty()) { + m_ui.showAllCheckBox->setChecked(true); + slotItems = m_ui.slotList->findItems(slot, Qt::MatchExactly); + } + if (!slotItems.isEmpty()) + selectSlot(slotItems.constFirst()); + } +} + +bool ConnectDialog::showAllSignalsSlots() const +{ + return m_ui.showAllCheckBox->isChecked(); +} + +void ConnectDialog::setShowAllSignalsSlots(bool showIt) +{ + m_ui.showAllCheckBox->setChecked(showIt); +} + +void ConnectDialog::selectSignal(QListWidgetItem *item) +{ + if (item) { + m_ui.signalList->setCurrentItem(item); + populateSlotList(item->text()); + m_ui.slotList->setEnabled(true); + setOkButtonEnabled(!m_ui.slotList->selectedItems().isEmpty()); + } else { + m_ui.signalList->clearSelection(); + populateSlotList(); + m_ui.slotList->setEnabled(false); + setOkButtonEnabled(false); + } +} + +void ConnectDialog::selectSlot(QListWidgetItem *item) +{ + if (item) { + m_ui.slotList->setCurrentItem(item); + } else { + m_ui.slotList->clearSelection(); + } + setOkButtonEnabled(true); +} + +QString ConnectDialog::signal() const +{ + const auto item_list = m_ui.signalList->selectedItems(); + if (item_list.size() != 1) + return QString(); + return item_list.at(0)->text(); +} + +QString ConnectDialog::slot() const +{ + const auto item_list = m_ui.slotList->selectedItems(); + if (item_list.size() != 1) + return QString(); + return item_list.at(0)->text(); +} + +void ConnectDialog::populateSlotList(const QString &signal) +{ + enum { deprecatedSlot = 0 }; + QString selectedName; + if (const QListWidgetItem * item = m_ui.slotList->currentItem()) + selectedName = item->text(); + + m_ui.slotList->clear(); + + QMap memberToClassName = getMatchingSlots(m_formWindow->core(), m_destination, signal, showAllSignalsSlots()); + + QFont font = QApplication::font(); + font.setItalic(true); + QVariant variantFont = QVariant::fromValue(font); + + QListWidgetItem *curr = nullptr; + for (auto itMember = memberToClassName.cbegin(), itMemberEnd = memberToClassName.cend(); itMember != itMemberEnd; ++itMember) { + const QString member = itMember.key(); + QListWidgetItem *item = new QListWidgetItem(m_ui.slotList); + item->setText(member); + if (member == selectedName) + curr = item; + + // Mark deprecated slots red. Not currently in use (historically for Qt 3 slots in Qt 4), + // but may be used again in the future. + if (deprecatedSlot) { + item->setData(Qt::FontRole, variantFont); + item->setData(Qt::ForegroundRole, QColor(Qt::red)); + } + } + + if (curr) + m_ui.slotList->setCurrentItem(curr); + + if (m_ui.slotList->selectedItems().isEmpty()) + setOkButtonEnabled(false); +} + +void ConnectDialog::populateSignalList() +{ + enum { deprecatedSignal = 0 }; + + QString selectedName; + if (const QListWidgetItem *item = m_ui.signalList->currentItem()) + selectedName = item->text(); + + m_ui.signalList->clear(); + + QMap memberToClassName = getSignals(m_formWindow->core(), m_source, showAllSignalsSlots()); + + QFont font = QApplication::font(); + font.setItalic(true); + QVariant variantFont = QVariant::fromValue(font); + + QListWidgetItem *curr = nullptr; + for (auto itMember = memberToClassName.cbegin(), itMemberEnd = memberToClassName.cend(); itMember != itMemberEnd; ++itMember) { + const QString member = itMember.key(); + + QListWidgetItem *item = new QListWidgetItem(m_ui.signalList); + item->setText(member); + if (!selectedName.isEmpty() && member == selectedName) + curr = item; + + // Mark deprecated signals red. Not currently in use (historically for Qt 3 slots in Qt 4), + // but may be used again in the future. + if (deprecatedSignal) { + item->setData(Qt::FontRole, variantFont); + item->setData(Qt::ForegroundRole, QColor(Qt::red)); + } + } + + if (curr) { + m_ui.signalList->setCurrentItem(curr); + } else { + selectedName.clear(); + } + + populateSlotList(selectedName); + if (!curr) + m_ui.slotList->setEnabled(false); +} + +void ConnectDialog::editSignals() +{ + editSignalsSlots(m_source, m_sourceMode, SignalSlotDialog::FocusSignals); +} + +void ConnectDialog::editSlots() +{ + editSignalsSlots(m_destination, m_destinationMode, SignalSlotDialog::FocusSlots); +} + +void ConnectDialog::editSignalsSlots(QWidget *w, WidgetMode mode, int signalSlotDialogModeInt) +{ + const SignalSlotDialog::FocusMode signalSlotDialogMode = static_cast(signalSlotDialogModeInt); + switch (mode) { + case NormalWidget: + break; + case MainContainer: + if (SignalSlotDialog::editMetaDataBase(m_formWindow, w, this, signalSlotDialogMode)) + populateLists(); + break; + case PromotedWidget: + if (SignalSlotDialog::editPromotedClass(m_formWindow->core(), w, this, signalSlotDialogMode)) + populateLists(); + break; + } +} + +} + +QT_END_NAMESPACE + +#include "moc_connectdialog_p.cpp" + diff --git a/src/tools/designer/src/components/signalsloteditor/connectdialog.ui b/src/tools/designer/src/components/signalsloteditor/connectdialog.ui new file mode 100644 index 00000000000..568516a422b --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/connectdialog.ui @@ -0,0 +1,150 @@ + + ConnectDialog + + + + 0 + 0 + 585 + 361 + + + + Configure Connection + + + + + + GroupBox + + + + + + Qt::ElideMiddle + + + + + + + + + Edit... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + GroupBox + + + + + + Qt::ElideMiddle + + + + + + + + + Edit... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Show signals and slots inherited from QWidget + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + ConnectDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + ConnectDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/tools/designer/src/components/signalsloteditor/connectdialog_p.h b/src/tools/designer/src/components/signalsloteditor/connectdialog_p.h new file mode 100644 index 00000000000..fe9fae32d7d --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/connectdialog_p.h @@ -0,0 +1,71 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONNECTDIALOG_H +#define CONNECTDIALOG_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "ui_connectdialog.h" +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QPushButton; + +namespace qdesigner_internal { + +class ConnectDialog : public QDialog +{ + Q_OBJECT +public: + ConnectDialog(QDesignerFormWindowInterface *formWindow, QWidget *sender, QWidget *receiver, QWidget *parent = nullptr); + + QString signal() const; + QString slot() const; + + void setSignalSlot(const QString &signal, const QString &slot); + + bool showAllSignalsSlots() const; + void setShowAllSignalsSlots(bool showIt); + +private slots: + void populateLists(); + void selectSignal(QListWidgetItem *item); + void selectSlot(QListWidgetItem *item); + void populateSignalList(); + void populateSlotList(const QString &signal = QString()); + void editSignals(); + void editSlots(); + +private: + enum WidgetMode { NormalWidget, MainContainer, PromotedWidget }; + + static WidgetMode widgetMode(QWidget *w, QDesignerFormWindowInterface *formWindow); + QPushButton *okButton(); + void setOkButtonEnabled(bool); + void editSignalsSlots(QWidget *w, WidgetMode mode, int signalSlotDialogMode); + + QWidget *m_source; + QWidget *m_destination; + const WidgetMode m_sourceMode; + const WidgetMode m_destinationMode; + QDesignerFormWindowInterface *m_formWindow; + QT_PREPEND_NAMESPACE(Ui)::ConnectDialog m_ui; +}; + +} + +QT_END_NAMESPACE + +#endif // CONNECTDIALOG_H diff --git a/src/tools/designer/src/components/signalsloteditor/signalslot_utils.cpp b/src/tools/designer/src/components/signalsloteditor/signalslot_utils.cpp new file mode 100644 index 00000000000..f8db32ee3d2 --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalslot_utils.cpp @@ -0,0 +1,261 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalslot_utils_p.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using ClassNameSignaturePair = std::pair; + +// Find all member functions that match a predicate on the signature string +// using the member sheet and the fake methods stored in the widget +// database and the meta data base. +// Assign a pair of to OutputIterator. + +template +static void memberList(QDesignerFormEditorInterface *core, + QObject *object, + qdesigner_internal::MemberType member_type, + bool showAll, + SignaturePredicate predicate, + OutputIterator it) +{ + if (!object) + return; + // 1) member sheet + const QDesignerMemberSheetExtension *members = qt_extension(core->extensionManager(), object); + Q_ASSERT(members != nullptr); + const int count = members->count(); + for (int i = 0; i < count; ++i) { + if (!members->isVisible(i)) + continue; + + if (member_type == qdesigner_internal::SignalMember && !members->isSignal(i)) + continue; + + if (member_type == qdesigner_internal::SlotMember && !members->isSlot(i)) + continue; + + if (!showAll && members->inheritedFromWidget(i)) + continue; + + const QString signature = members->signature(i); + if (predicate(signature)) { + *it = ClassNameSignaturePair(members->declaredInClass(i), signature); + ++it; + } + } + // 2) fake slots from widget DB + const qdesigner_internal::WidgetDataBase *wdb = qobject_cast(core->widgetDataBase()); + if (!wdb) + return; + const int idx = wdb->indexOfObject(object); + Q_ASSERT(idx != -1); + // get the promoted class name + const qdesigner_internal::WidgetDataBaseItem *wdbItem = static_cast(wdb->item(idx)); + const QString className = wdbItem->name(); + + const QStringList wdbFakeMethods = member_type == qdesigner_internal::SlotMember ? wdbItem->fakeSlots() : wdbItem->fakeSignals(); + if (!wdbFakeMethods.isEmpty()) + for (const QString &fakeMethod : wdbFakeMethods) + if (predicate(fakeMethod)) { + *it = ClassNameSignaturePair(className, fakeMethod); + ++it; + } + // 3) fake slots from meta DB + qdesigner_internal::MetaDataBase *metaDataBase = qobject_cast(core->metaDataBase()); + if (!metaDataBase) + return; + + if (const qdesigner_internal::MetaDataBaseItem *mdbItem = metaDataBase->metaDataBaseItem(object)) { + const QStringList mdbFakeMethods = member_type == qdesigner_internal::SlotMember ? mdbItem->fakeSlots() : mdbItem->fakeSignals(); + if (!mdbFakeMethods.isEmpty()) + for (const QString &fakeMethod : mdbFakeMethods) + if (predicate(fakeMethod)) { + *it = ClassNameSignaturePair(className, fakeMethod); + ++it; + } + } +} + +namespace { + // Predicate that matches the exact signature string + class EqualsPredicate { + public: + EqualsPredicate(const QString &pattern) : m_pattern(pattern) {} + bool operator()(const QString &s) const { return s == m_pattern; } + private: + const QString m_pattern; + }; + // Predicate for a QString member signature that matches signals up with slots and vice versa + class SignalMatchesSlotPredicate { + public: + SignalMatchesSlotPredicate(QDesignerFormEditorInterface *core, const QString &peer, qdesigner_internal::MemberType memberType); + bool operator()(const QString &s) const; + + private: + bool signalMatchesSlot(const QString &signal, const QString &slot) const; + + const QString m_peer; + qdesigner_internal::MemberType m_memberType; + const QDesignerLanguageExtension *m_lang; + }; + + SignalMatchesSlotPredicate::SignalMatchesSlotPredicate(QDesignerFormEditorInterface *core, const QString &peer, qdesigner_internal::MemberType memberType) : + m_peer(peer), + m_memberType(memberType), + m_lang(qt_extension(core->extensionManager(), core)) + { + } + + bool SignalMatchesSlotPredicate::operator()(const QString &s) const + { + return m_memberType == qdesigner_internal::SlotMember ? signalMatchesSlot(m_peer, s) : signalMatchesSlot(s, m_peer); + } + + bool SignalMatchesSlotPredicate::signalMatchesSlot(const QString &signal, const QString &slot) const + { + if (m_lang) + return m_lang->signalMatchesSlot(signal, slot); + + return QDesignerMemberSheet::signalMatchesSlot(signal, slot); + } + + // Output iterator for a pair of pair of + // that builds the reverse class list for reverseClassesMemberFunctions() + // (for the combos of the ToolWindow) + class ReverseClassesMemberIterator { + public: + ReverseClassesMemberIterator(qdesigner_internal::ClassesMemberFunctions *result); + + ReverseClassesMemberIterator &operator*() { return *this; } + ReverseClassesMemberIterator &operator++() { return *this; } + ReverseClassesMemberIterator &operator=(const ClassNameSignaturePair &classNameSignature); + + private: + qdesigner_internal::ClassesMemberFunctions *m_result; + QString m_lastClassName; + QStringList *m_memberList; + }; + + ReverseClassesMemberIterator::ReverseClassesMemberIterator(qdesigner_internal::ClassesMemberFunctions *result) : + m_result(result), + m_memberList(nullptr) + { + } + + ReverseClassesMemberIterator &ReverseClassesMemberIterator::operator=(const ClassNameSignaturePair &classNameSignature) + { + // prepend a new entry if class changes + if (!m_memberList || classNameSignature.first != m_lastClassName) { + m_lastClassName = classNameSignature.first; + m_result->push_front(qdesigner_internal::ClassMemberFunctions(m_lastClassName)); + m_memberList = &(m_result->front().m_memberList); + } + m_memberList->push_back(classNameSignature.second); + return *this; + } + + // Output iterator for a pair of pair of + // that adds the signatures to a string list + class SignatureIterator { + public: + SignatureIterator(QMap *result) : m_result(result) {} + + SignatureIterator &operator*() { return *this; } + SignatureIterator &operator++() { return *this; } + SignatureIterator &operator=(const ClassNameSignaturePair &classNameSignature) + { + m_result->insert(classNameSignature.second, classNameSignature.first); + return *this; + } + + private: + QMap *m_result; + }; +} + +static inline bool truePredicate(const QString &) { return true; } + +namespace qdesigner_internal { + + ClassMemberFunctions::ClassMemberFunctions(const QString &class_name) : + m_className(class_name) + { + } + + bool signalMatchesSlot(QDesignerFormEditorInterface *core, const QString &signal, const QString &slot) + { + const SignalMatchesSlotPredicate predicate(core, signal, qdesigner_internal::SlotMember); + return predicate(slot); + } + + // return classes and members in reverse class order to + // populate of the combo of the ToolWindow + ClassesMemberFunctions reverseClassesMemberFunctions(const QString &obj_name, MemberType member_type, + const QString &peer, QDesignerFormWindowInterface *form) + { + QObject *object = nullptr; + if (obj_name == form->mainContainer()->objectName()) { + object = form->mainContainer(); + } else { + object = form->mainContainer()->findChild(obj_name); + } + if (!object) + return ClassesMemberFunctions(); + QDesignerFormEditorInterface *core = form->core(); + + ClassesMemberFunctions rc; + memberList(form->core(), object, member_type, true, SignalMatchesSlotPredicate(core, peer, member_type), + ReverseClassesMemberIterator(&rc)); + return rc; + } + + QMap getSignals(QDesignerFormEditorInterface *core, QObject *object, bool showAll) + { + QMap rc; + memberList(core, object, SignalMember, showAll, truePredicate, SignatureIterator(&rc)); + return rc; + } + + QMap getMatchingSlots(QDesignerFormEditorInterface *core, QObject *object, const QString &signalSignature, bool showAll) + { + QMap rc; + memberList(core, object, SlotMember, showAll, SignalMatchesSlotPredicate(core, signalSignature, qdesigner_internal::SlotMember), SignatureIterator(&rc)); + return rc; + } + + bool memberFunctionListContains(QDesignerFormEditorInterface *core, QObject *object, MemberType type, const QString &signature) + { + QMap rc; + memberList(core, object, type, true, EqualsPredicate(signature), SignatureIterator(&rc)); + return !rc.isEmpty(); + } + + // ### deprecated + QString realObjectName(QDesignerFormEditorInterface *core, QObject *object) + { + if (!object) + return QString(); + + const QDesignerMetaDataBaseInterface *mdb = core->metaDataBase(); + if (const QDesignerMetaDataBaseItemInterface *item = mdb->item(object)) + return item->name(); + + return object->objectName(); + } +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/signalsloteditor/signalslot_utils_p.h b/src/tools/designer/src/components/signalsloteditor/signalslot_utils_p.h new file mode 100644 index 00000000000..e7441f8556a --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalslot_utils_p.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTUTILS_P_H +#define SIGNALSLOTUTILS_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +enum MemberType { SignalMember, SlotMember }; + +// member to class name +QMap getSignals(QDesignerFormEditorInterface *core, QObject *object, bool showAll); +QMap getMatchingSlots(QDesignerFormEditorInterface *core, QObject *object, + const QString &signalSignature, bool showAll); + +bool memberFunctionListContains(QDesignerFormEditorInterface *core, QObject *object, MemberType type, const QString &signature); + +// Members functions listed by class they were inherited from +struct ClassMemberFunctions +{ + ClassMemberFunctions() = default; + ClassMemberFunctions(const QString &_class_name); + + QString m_className; + QStringList m_memberList; +}; + +using ClassesMemberFunctions = QList; + +// Return classes and members in reverse class order to +// populate of the combo of the ToolWindow. + +ClassesMemberFunctions reverseClassesMemberFunctions(const QString &obj_name, MemberType member_type, + const QString &peer, QDesignerFormWindowInterface *form); + +bool signalMatchesSlot(QDesignerFormEditorInterface *core, const QString &signal, const QString &slot); + +QString realObjectName(QDesignerFormEditorInterface *core, QObject *object); + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTUTILS_P_H diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditor.cpp b/src/tools/designer/src/components/signalsloteditor/signalsloteditor.cpp new file mode 100644 index 00000000000..76239f07e7b --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditor.cpp @@ -0,0 +1,525 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalsloteditor.h" +#include "signalsloteditor_p.h" +#include "connectdialog_p.h" +#include "signalslot_utils_p.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +/******************************************************************************* +** SignalSlotConnection +*/ + +SignalSlotConnection::SignalSlotConnection(ConnectionEdit *edit, QWidget *source, QWidget *target) + : Connection(edit, source, target) +{ +} + +DomConnection *SignalSlotConnection::toUi() const +{ + DomConnection *result = new DomConnection; + + result->setElementSender(sender()); + result->setElementSignal(signal()); + result->setElementReceiver(receiver()); + result->setElementSlot(slot()); + + DomConnectionHints *hints = new DomConnectionHints; + QList list; + + QPoint sp = endPointPos(EndPoint::Source); + QPoint tp = endPointPos(EndPoint::Target); + + DomConnectionHint *hint = new DomConnectionHint; + hint->setAttributeType(u"sourcelabel"_s); + hint->setElementX(sp.x()); + hint->setElementY(sp.y()); + list.append(hint); + + hint = new DomConnectionHint; + hint->setAttributeType(u"destinationlabel"_s); + hint->setElementX(tp.x()); + hint->setElementY(tp.y()); + list.append(hint); + + hints->setElementHint(list); + result->setElementHints(hints); + + return result; +} + +void SignalSlotConnection::setSignal(const QString &signal) +{ + m_signal = signal; + setLabel(EndPoint::Source, m_signal); +} + +void SignalSlotConnection::setSlot(const QString &slot) +{ + m_slot = slot; + setLabel(EndPoint::Target, m_slot); +} + +QString SignalSlotConnection::sender() const +{ + QObject *source = object(EndPoint::Source); + if (!source) + return QString(); + + SignalSlotEditor *edit = qobject_cast(this->edit()); + Q_ASSERT(edit != nullptr); + + return realObjectName(edit->formWindow()->core(), source); +} + +QString SignalSlotConnection::receiver() const +{ + QObject *sink = object(EndPoint::Target); + if (!sink) + return QString(); + + SignalSlotEditor *edit = qobject_cast(this->edit()); + Q_ASSERT(edit != nullptr); + return realObjectName(edit->formWindow()->core(), sink); +} + +void SignalSlotConnection::updateVisibility() +{ + Connection::updateVisibility(); + if (isVisible() && (signal().isEmpty() || slot().isEmpty())) + setVisible(false); +} + +QString SignalSlotConnection::toString() const +{ + return QCoreApplication::translate("SignalSlotConnection", "SENDER(%1), SIGNAL(%2), RECEIVER(%3), SLOT(%4)") + .arg(sender(), signal(), receiver(), slot()); +} + +SignalSlotConnection::State SignalSlotConnection::isValid(const QWidget *background) const +{ + const QObject *source = object(EndPoint::Source); + if (!source) + return ObjectDeleted; + + const QObject *target = object(EndPoint::Target); + if (!target) + return ObjectDeleted; + + if (m_slot.isEmpty() || m_signal.isEmpty()) + return InvalidMethod; + + if (const QWidget *sourceWidget = qobject_cast(source)) + if (!background->isAncestorOf(sourceWidget)) + return NotAncestor; + + if (const QWidget *targetWidget = qobject_cast(target)) + if (!background->isAncestorOf(targetWidget)) + return NotAncestor; + + return Valid; +} + +/******************************************************************************* +** Commands +*/ + +class SetMemberCommand : public QUndoCommand, public CETypes +{ +public: + SetMemberCommand(SignalSlotConnection *con, EndPoint::Type type, + const QString &member, SignalSlotEditor *editor); + void redo() override; + void undo() override; +private: + const QString m_old_member; + const QString m_new_member; + const EndPoint::Type m_type; + SignalSlotConnection *m_con; + SignalSlotEditor *m_editor; +}; + +SetMemberCommand::SetMemberCommand(SignalSlotConnection *con, EndPoint::Type type, + const QString &member, SignalSlotEditor *editor) : + m_old_member(type == EndPoint::Source ? con->signal() : con->slot()), + m_new_member(member), + m_type(type), + m_con(con), + m_editor(editor) +{ + if (type == EndPoint::Source) + setText(QApplication::translate("Command", "Change signal")); + else + setText(QApplication::translate("Command", "Change slot")); +} + +void SetMemberCommand::redo() +{ + m_con->update(); + if (m_type == EndPoint::Source) + m_con->setSignal(m_new_member); + else + m_con->setSlot(m_new_member); + m_con->update(); + emit m_editor->connectionChanged(m_con); +} + +void SetMemberCommand::undo() +{ + m_con->update(); + if (m_type == EndPoint::Source) + m_con->setSignal(m_old_member); + else + m_con->setSlot(m_old_member); + m_con->update(); + emit m_editor->connectionChanged(m_con); +} + +// Command to modify a connection +class ModifyConnectionCommand : public QDesignerFormWindowCommand +{ +public: + explicit ModifyConnectionCommand(QDesignerFormWindowInterface *form, + SignalSlotConnection *conn, + const QString &newSignal, + const QString &newSlot); + void redo() override; + void undo() override; + +private: + SignalSlotConnection *m_conn; + const QString m_oldSignal; + const QString m_oldSlot; + const QString m_newSignal; + const QString m_newSlot; +}; + +ModifyConnectionCommand::ModifyConnectionCommand(QDesignerFormWindowInterface *form, + SignalSlotConnection *conn, + const QString &newSignal, + const QString &newSlot) : + QDesignerFormWindowCommand(QCoreApplication::translate("Command", "Change signal-slot connection"), form), + m_conn(conn), + m_oldSignal(conn->signal()), + m_oldSlot(conn->slot()), + m_newSignal(newSignal), + m_newSlot(newSlot) +{ +} + +void ModifyConnectionCommand::redo() +{ + m_conn->setSignal(m_newSignal); + m_conn->setSlot(m_newSlot); +} + +void ModifyConnectionCommand::undo() +{ + m_conn->setSignal(m_oldSignal); + m_conn->setSlot(m_oldSlot); +} + +/******************************************************************************* +** SignalSlotEditor +*/ + +SignalSlotEditor::SignalSlotEditor(QDesignerFormWindowInterface *form_window, QWidget *parent) : + ConnectionEdit(parent, form_window), + m_form_window(form_window), + m_showAllSignalsSlots(false) +{ +} + +void SignalSlotEditor::modifyConnection(Connection *con) +{ + SignalSlotConnection *sigslot_con = static_cast(con); + ConnectDialog dialog(m_form_window, + sigslot_con->widget(EndPoint::Source), + sigslot_con->widget(EndPoint::Target), + m_form_window->core()->topLevel()); + + dialog.setSignalSlot(sigslot_con->signal(), sigslot_con->slot()); + dialog.setShowAllSignalsSlots(m_showAllSignalsSlots); + + if (dialog.exec() == QDialog::Accepted) { + const QString newSignal = dialog.signal(); + const QString newSlot = dialog.slot(); + if (sigslot_con->signal() != newSignal || sigslot_con->slot() != newSlot) { + ModifyConnectionCommand *cmd = new ModifyConnectionCommand(m_form_window, sigslot_con, newSignal, newSlot); + m_form_window->commandHistory()->push(cmd); + } + } + + m_showAllSignalsSlots = dialog.showAllSignalsSlots(); +} + +Connection *SignalSlotEditor::createConnection(QWidget *source, QWidget *destination) +{ + SignalSlotConnection *con = nullptr; + + Q_ASSERT(source != nullptr); + Q_ASSERT(destination != nullptr); + + ConnectDialog dialog(m_form_window, source, destination, m_form_window->core()->topLevel()); + dialog.setShowAllSignalsSlots(m_showAllSignalsSlots); + + if (dialog.exec() == QDialog::Accepted) { + con = new SignalSlotConnection(this, source, destination); + con->setSignal(dialog.signal()); + con->setSlot(dialog.slot()); + } + + m_showAllSignalsSlots = dialog.showAllSignalsSlots(); + + return con; +} + +DomConnections *SignalSlotEditor::toUi() const +{ + DomConnections *result = new DomConnections; + QList list; + + const int count = connectionCount(); + list.reserve(count); + for (int i = 0; i < count; ++i) { + const SignalSlotConnection *con = static_cast(connection(i)); + Q_ASSERT(con != nullptr); + + // If a widget's parent has been removed or moved to a different form, + // and the parent was not a managed widget + // (a page in a tab widget), we never get a widgetRemoved(). So we filter out + // these child widgets here (check QPointer and verify ancestor). + // Also, the user might demote a promoted widget or remove a fake + // slot in the editor, which causes the connection to become invalid + // once he doubleclicks on the method combo. + switch (con->isValid(background())) { + case SignalSlotConnection::Valid: + list.append(con->toUi()); + break; + case SignalSlotConnection::ObjectDeleted: + case SignalSlotConnection::InvalidMethod: + case SignalSlotConnection::NotAncestor: + break; + } + } + result->setElementConnection(list); + return result; +} + +QObject *SignalSlotEditor::objectByName(QWidget *topLevel, const QString &name) const +{ + if (name.isEmpty()) + return nullptr; + + Q_ASSERT(topLevel); + QObject *object = nullptr; + if (topLevel->objectName() == name) + object = topLevel; + else + object = topLevel->findChild(name); + const QDesignerMetaDataBaseInterface *mdb = formWindow()->core()->metaDataBase(); + if (mdb->item(object)) + return object; + return nullptr; +} + +void SignalSlotEditor::fromUi(const DomConnections *connections, QWidget *parent) +{ + if (connections == nullptr) + return; + + // For old forms, that were saved before Qt 4 times, there was no + // section inside ui file. Currently, when we specify custom signals or slots + // for the form, we add them into the section. For all signals / slots + // inside section uic creates string-based connections. + // In order to fix old forms, we detect if a signal or slot used inside connection + // is a custom (fake) one, like it's being done inside SignalSlotDialog. + // In case of a fake signal / slot we register it inside meta data base, so that + // the next save will add a missing section. + QStringList existingSlots, existingSignals; + SignalSlotDialog::existingMethodsFromMemberSheet(m_form_window->core(), parent, + existingSlots, existingSignals); + QStringList fakeSlots, fakeSignals; + SignalSlotDialog::fakeMethodsFromMetaDataBase(m_form_window->core(), parent, + fakeSlots, fakeSignals); + + setBackground(parent); + clear(); + const auto &list = connections->elementConnection(); + for (const DomConnection *dom_con : list) { + QObject *source = objectByName(parent, dom_con->elementSender()); + if (source == nullptr) { + qDebug("SignalSlotEditor::fromUi(): no source widget called \"%s\"", + dom_con->elementSender().toUtf8().constData()); + continue; + } + QObject *destination = objectByName(parent, dom_con->elementReceiver()); + if (destination == nullptr) { + qDebug("SignalSlotEditor::fromUi(): no destination widget called \"%s\"", + dom_con->elementReceiver().toUtf8().constData()); + continue; + } + + QPoint sp = QPoint(20, 20), tp = QPoint(20, 20); + const DomConnectionHints *dom_hints = dom_con->elementHints(); + if (dom_hints != nullptr) { + const auto &hints = dom_hints->elementHint(); + for (DomConnectionHint *hint : hints) { + QString attr_type = hint->attributeType(); + QPoint p = QPoint(hint->elementX(), hint->elementY()); + if (attr_type == "sourcelabel"_L1) + sp = p; + else if (attr_type == "destinationlabel"_L1) + tp = p; + } + } + + const QString sourceSignal = dom_con->elementSignal(); + if (source == parent && !existingSignals.contains(sourceSignal) + && !fakeSignals.contains(sourceSignal)) { + fakeSignals.append(sourceSignal); + } + + const QString destSlot = dom_con->elementSlot(); + if (destination == parent && !existingSlots.contains(destSlot) + && !fakeSlots.contains(destSlot)) { + fakeSlots.append(destSlot); + } + + + SignalSlotConnection *con = new SignalSlotConnection(this); + + con->setEndPoint(EndPoint::Source, source, sp); + con->setEndPoint(EndPoint::Target, destination, tp); + con->setSignal(sourceSignal); + con->setSlot(destSlot); + addConnection(con); + } + SignalSlotDialog::fakeMethodsToMetaDataBase(m_form_window->core(), parent, + fakeSlots, fakeSignals); +} + +static bool skipWidget(const QWidget *w) +{ + const QString name = QLatin1StringView(w->metaObject()->className()); + if (name == "QDesignerWidget"_L1) + return true; + if (name == "QLayoutWidget"_L1) + return true; + if (name == "qdesigner_internal::FormWindow"_L1) + return true; + if (name == "Spacer"_L1) + return true; + return false; +} + +QWidget *SignalSlotEditor::widgetAt(const QPoint &pos) const +{ + QWidget *widget = ConnectionEdit::widgetAt(pos); + + if (widget == m_form_window->mainContainer()) + return widget; + + for (; widget != nullptr; widget = widget->parentWidget()) { + QDesignerMetaDataBaseItemInterface *item = m_form_window->core()->metaDataBase()->item(widget); + if (item == nullptr) + continue; + if (skipWidget(widget)) + continue; + break; + } + + return widget; +} + +void SignalSlotEditor::setSignal(SignalSlotConnection *con, const QString &member) +{ + if (member == con->signal()) + return; + + m_form_window->beginCommand(QApplication::translate("Command", "Change signal")); + undoStack()->push(new SetMemberCommand(con, EndPoint::Source, member, this)); + if (!signalMatchesSlot(m_form_window->core(), member, con->slot())) + undoStack()->push(new SetMemberCommand(con, EndPoint::Target, QString(), this)); + m_form_window->endCommand(); +} + +void SignalSlotEditor::setSlot(SignalSlotConnection *con, const QString &member) +{ + if (member == con->slot()) + return; + + m_form_window->beginCommand(QApplication::translate("Command", "Change slot")); + undoStack()->push(new SetMemberCommand(con, EndPoint::Target, member, this)); + if (!signalMatchesSlot(m_form_window->core(), con->signal(), member)) + undoStack()->push(new SetMemberCommand(con, EndPoint::Source, QString(), this)); + m_form_window->endCommand(); +} + +void SignalSlotEditor::setSource(Connection *_con, const QString &obj_name) +{ + SignalSlotConnection *con = static_cast(_con); + + if (con->sender() == obj_name) + return; + + m_form_window->beginCommand(QApplication::translate("Command", "Change sender")); + ConnectionEdit::setSource(con, obj_name); + + QObject *sourceObject = con->object(EndPoint::Source); + + if (!memberFunctionListContains(m_form_window->core(), sourceObject, SignalMember, con->signal())) + undoStack()->push(new SetMemberCommand(con, EndPoint::Source, QString(), this)); + + m_form_window->endCommand(); +} + +void SignalSlotEditor::setTarget(Connection *_con, const QString &obj_name) +{ + SignalSlotConnection *con = static_cast(_con); + + if (con->receiver() == obj_name) + return; + + m_form_window->beginCommand(QApplication::translate("Command", "Change receiver")); + ConnectionEdit::setTarget(con, obj_name); + + QObject *targetObject = con->object(EndPoint::Target); + if (!memberFunctionListContains(m_form_window->core(), targetObject, SlotMember, con->slot())) + undoStack()->push(new SetMemberCommand(con, EndPoint::Target, QString(), this)); + + m_form_window->endCommand(); +} + +void SignalSlotEditor::addEmptyConnection() +{ + SignalSlotConnection *con = new SignalSlotConnection(this); + undoStack()->push(new AddConnectionCommand(this, con)); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditor.h b/src/tools/designer/src/components/signalsloteditor/signalsloteditor.h new file mode 100644 index 00000000000..6ce49890803 --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditor.h @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_H +#define SIGNALSLOTEDITOR_H + +#include "signalsloteditor_global.h" + +#include + +QT_BEGIN_NAMESPACE + +class DomConnections; + +namespace qdesigner_internal { + +class SignalSlotConnection; + +class QT_SIGNALSLOTEDITOR_EXPORT SignalSlotEditor : public ConnectionEdit +{ + Q_OBJECT + +public: + SignalSlotEditor(QDesignerFormWindowInterface *form_window, QWidget *parent); + + virtual void setSignal(SignalSlotConnection *con, const QString &member); + virtual void setSlot(SignalSlotConnection *con, const QString &member); + void setSource(Connection *con, const QString &obj_name) override; + void setTarget(Connection *con, const QString &obj_name) override; + + DomConnections *toUi() const; + void fromUi(const DomConnections *connections, QWidget *parent); + + QDesignerFormWindowInterface *formWindow() const { return m_form_window; } + + QObject *objectByName(QWidget *topLevel, const QString &name) const; + + void addEmptyConnection(); + +protected: + QWidget *widgetAt(const QPoint &pos) const override; + +private: + Connection *createConnection(QWidget *source, QWidget *destination) override; + void modifyConnection(Connection *con) override; + + QDesignerFormWindowInterface *m_form_window; + bool m_showAllSignalsSlots; + + friend class SetMemberCommand; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITOR_H diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditor.json b/src/tools/designer/src/components/signalsloteditor/signalsloteditor.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditor.json @@ -0,0 +1 @@ +{} diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditor_global.h b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_global.h new file mode 100644 index 00000000000..fbe025316ae --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_GLOBAL_H +#define SIGNALSLOTEDITOR_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_SIGNALSLOTEDITOR_LIBRARY +# define QT_SIGNALSLOTEDITOR_EXPORT +#else +# define QT_SIGNALSLOTEDITOR_EXPORT +#endif +#else +#define QT_SIGNALSLOTEDITOR_EXPORT +#endif + +#endif // SIGNALSLOTEDITOR_GLOBAL_H diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditor_p.h b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_p.h new file mode 100644 index 00000000000..70157a3b442 --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_p.h @@ -0,0 +1,101 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_P_H +#define SIGNALSLOTEDITOR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class DomConnection; + +namespace qdesigner_internal { + +class SignalSlotEditor; + +class SignalSlotConnection : public Connection +{ +public: + explicit SignalSlotConnection(ConnectionEdit *edit, QWidget *source = nullptr, QWidget *target = nullptr); + + void setSignal(const QString &signal); + void setSlot(const QString &slot); + + QString sender() const; + QString receiver() const; + inline QString signal() const { return m_signal; } + inline QString slot() const { return m_slot; } + + DomConnection *toUi() const; + + void updateVisibility() override; + + enum State { Valid, ObjectDeleted, InvalidMethod, NotAncestor }; + State isValid(const QWidget *background) const; + + // format for messages, etc. + QString toString() const; + +private: + QString m_signal, m_slot; +}; + +class ConnectionModel : public QAbstractItemModel +{ + Q_OBJECT +public: + explicit ConnectionModel(QObject *parent = nullptr); + void setEditor(SignalSlotEditor *editor = nullptr); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &data, int role = Qt::DisplayRole) override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + QModelIndex connectionToIndex(Connection *con) const; + Connection *indexToConnection(const QModelIndex &index) const; + void updateAll(); + + const SignalSlotConnection *connectionAt(const QModelIndex &index) const; + static QString columnText(const SignalSlotConnection *con, int column); + +private slots: + void connectionAdded(Connection *con); + void connectionRemoved(int idx); + void aboutToRemoveConnection(Connection *con); + void aboutToAddConnection(int idx); + void connectionChanged(Connection *con); + +private: + QPointer m_editor; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITOR_P_H diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditor_plugin.cpp b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_plugin.cpp new file mode 100644 index 00000000000..8b0b1f75a83 --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_plugin.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalsloteditor_plugin.h" +#include "signalsloteditor_tool.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +SignalSlotEditorPlugin::SignalSlotEditorPlugin() = default; + +SignalSlotEditorPlugin::~SignalSlotEditorPlugin() = default; + +bool SignalSlotEditorPlugin::isInitialized() const +{ + return m_initialized; +} + +void SignalSlotEditorPlugin::initialize(QDesignerFormEditorInterface *core) +{ + Q_ASSERT(!isInitialized()); + + m_action = new QAction(tr("Edit Signals/Slots"), this); + m_action->setObjectName(u"__qt_edit_signals_slots_action"_s); + m_action->setShortcut(tr("F4")); + QIcon icon = QIcon::fromTheme(u"designer-edit-signals"_s, + QIcon(core->resourceLocation() + "/signalslottool.png"_L1)); + m_action->setIcon(icon); + m_action->setEnabled(false); + + setParent(core); + m_core = core; + m_initialized = true; + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowAdded, + this, &SignalSlotEditorPlugin::addFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowRemoved, + this, &SignalSlotEditorPlugin::removeFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &SignalSlotEditorPlugin::activeFormWindowChanged); +} + +QDesignerFormEditorInterface *SignalSlotEditorPlugin::core() const +{ + return m_core; +} + +void SignalSlotEditorPlugin::addFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == false); + + SignalSlotEditorTool *tool = new SignalSlotEditorTool(formWindow, this); + connect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + m_tools[formWindow] = tool; + formWindow->registerTool(tool); +} + +void SignalSlotEditorPlugin::removeFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == true); + + SignalSlotEditorTool *tool = m_tools.value(formWindow); + m_tools.remove(formWindow); + disconnect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + // ### FIXME disable the tool + + delete tool; +} + +QAction *SignalSlotEditorPlugin::action() const +{ + return m_action; +} + +void SignalSlotEditorPlugin::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) +{ + m_action->setEnabled(formWindow != nullptr); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "moc_signalsloteditor_plugin.cpp" diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditor_plugin.h b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_plugin.h new file mode 100644 index 00000000000..dc70c676d67 --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_plugin.h @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_PLUGIN_H +#define SIGNALSLOTEDITOR_PLUGIN_H + +#include "signalsloteditor_global.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class SignalSlotEditorTool; + +class QT_SIGNALSLOTEDITOR_EXPORT SignalSlotEditorPlugin: public QObject, public QDesignerFormEditorPluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerFormEditorPluginInterface" FILE "signalsloteditor.json") + Q_INTERFACES(QDesignerFormEditorPluginInterface) +public: + SignalSlotEditorPlugin(); + ~SignalSlotEditorPlugin() override; + + bool isInitialized() const override; + void initialize(QDesignerFormEditorInterface *core) override; + QAction *action() const override; + + QDesignerFormEditorInterface *core() const override; + +public slots: + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + +private slots: + void addFormWindow(QDesignerFormWindowInterface *formWindow); + void removeFormWindow(QDesignerFormWindowInterface *formWindow); + +private: + QPointer m_core; + QHash m_tools; + bool m_initialized = false; + QAction *m_action = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITOR_PLUGIN_H diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditor_tool.cpp b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_tool.cpp new file mode 100644 index 00000000000..9b5e72fbd99 --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_tool.cpp @@ -0,0 +1,87 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalsloteditor_tool.h" +#include "signalsloteditor.h" + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +SignalSlotEditorTool::SignalSlotEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent) + : QDesignerFormWindowToolInterface(parent), + m_formWindow(formWindow), + m_action(new QAction(tr("Edit Signals/Slots"), this)) +{ +} + +SignalSlotEditorTool::~SignalSlotEditorTool() = default; + +QDesignerFormEditorInterface *SignalSlotEditorTool::core() const +{ + return m_formWindow->core(); +} + +QDesignerFormWindowInterface *SignalSlotEditorTool::formWindow() const +{ + return m_formWindow; +} + +bool SignalSlotEditorTool::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + Q_UNUSED(widget); + Q_UNUSED(managedWidget); + Q_UNUSED(event); + + return false; +} + +QWidget *SignalSlotEditorTool::editor() const +{ + if (!m_editor) { + Q_ASSERT(formWindow() != nullptr); + m_editor = new qdesigner_internal::SignalSlotEditor(formWindow(), nullptr); + connect(formWindow(), &QDesignerFormWindowInterface::mainContainerChanged, + m_editor.data(), &SignalSlotEditor::setBackground); + connect(formWindow(), &QDesignerFormWindowInterface::changed, + m_editor.data(), &SignalSlotEditor::updateBackground); + } + + return m_editor; +} + +QAction *SignalSlotEditorTool::action() const +{ + return m_action; +} + +void SignalSlotEditorTool::activated() +{ + m_editor->enableUpdateBackground(true); +} + +void SignalSlotEditorTool::deactivated() +{ + m_editor->enableUpdateBackground(false); +} + +void SignalSlotEditorTool::saveToDom(DomUI *ui, QWidget*) +{ + ui->setElementConnections(m_editor->toUi()); +} + +void SignalSlotEditorTool::loadFromDom(DomUI *ui, QWidget *mainContainer) +{ + m_editor->fromUi(ui->elementConnections(), mainContainer); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditor_tool.h b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_tool.h new file mode 100644 index 00000000000..7627389307e --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditor_tool.h @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITOR_TOOL_H +#define SIGNALSLOTEDITOR_TOOL_H + +#include "signalsloteditor_global.h" +#include "signalsloteditor.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class SignalSlotEditor; + +class QT_SIGNALSLOTEDITOR_EXPORT SignalSlotEditorTool: public QDesignerFormWindowToolInterface +{ + Q_OBJECT +public: + explicit SignalSlotEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent = nullptr); + ~SignalSlotEditorTool() override; + + QDesignerFormEditorInterface *core() const override; + QDesignerFormWindowInterface *formWindow() const override; + + QWidget *editor() const override; + + QAction *action() const override; + + void activated() override; + void deactivated() override; + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) override; + + void saveToDom(DomUI *ui, QWidget *mainContainer) override; + void loadFromDom(DomUI *ui, QWidget *mainContainer) override; + +private: + QDesignerFormWindowInterface *m_formWindow; + mutable QPointer m_editor; + QAction *m_action; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITOR_TOOL_H diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditorwindow.cpp b/src/tools/designer/src/components/signalsloteditor/signalsloteditorwindow.cpp new file mode 100644 index 00000000000..1efc66064d5 --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditorwindow.cpp @@ -0,0 +1,820 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "signalsloteditorwindow.h" +#include "signalsloteditor_p.h" +#include "signalsloteditor.h" +#include "signalslot_utils_p.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Add suitable form widgets to a list of objects for the signal slot +// editor. Prevent special widgets from showing up there. +static void addWidgetToObjectList(const QWidget *w, QStringList &r) +{ + const QMetaObject *mo = w->metaObject(); + if (mo != &QLayoutWidget::staticMetaObject && mo != &Spacer::staticMetaObject) { + const QString name = w->objectName().trimmed(); + if (!name.isEmpty()) + r.push_back(name); + } +} + +static QStringList objectNameList(QDesignerFormWindowInterface *form) +{ + QStringList result; + + QWidget *mainContainer = form->mainContainer(); + if (!mainContainer) + return result; + + // Add main container container pages (QStatusBar, QWizardPages) etc. + // to the list. Pages of containers on the form are not added, however. + if (const QDesignerContainerExtension *c = qt_extension(form->core()->extensionManager(), mainContainer)) { + const int count = c->count(); + for (int i = 0 ; i < count; i++) + addWidgetToObjectList(c->widget(i), result); + } + + const QDesignerFormWindowCursorInterface *cursor = form->cursor(); + const int widgetCount = cursor->widgetCount(); + for (int i = 0; i < widgetCount; ++i) + addWidgetToObjectList(cursor->widget(i), result); + + const QDesignerMetaDataBaseInterface *mdb = form->core()->metaDataBase(); + + // Add managed actions and actions with managed menus + const auto actions = mainContainer->findChildren(); + for (QAction *a : actions) { + if (!a->isSeparator()) { + if (QMenu *menu = a->menu()) { + if (mdb->item(menu)) + result.push_back(menu->objectName()); + } else { + if (mdb->item(a)) + result.push_back(a->objectName()); + } + } + } + + // Add managed buttons groups + const auto buttonGroups = mainContainer->findChildren(); + for (QButtonGroup * b : buttonGroups) { + if (mdb->item(b)) + result.append(b->objectName()); + } + + result.sort(); + return result; +} + +namespace qdesigner_internal { + +// ------------ ConnectionModel + +ConnectionModel::ConnectionModel(QObject *parent) : + QAbstractItemModel(parent) +{ +} + +void ConnectionModel::setEditor(SignalSlotEditor *editor) +{ + if (m_editor == editor) + return; + beginResetModel(); + + if (m_editor) { + disconnect(m_editor.data(), &SignalSlotEditor::connectionAdded, + this, &ConnectionModel::connectionAdded); + disconnect(m_editor.data(), &SignalSlotEditor::connectionRemoved, + this, &ConnectionModel::connectionRemoved); + disconnect(m_editor.data(), &SignalSlotEditor::aboutToRemoveConnection, + this, &ConnectionModel::aboutToRemoveConnection); + disconnect(m_editor.data(), &SignalSlotEditor::aboutToAddConnection, + this, &ConnectionModel::aboutToAddConnection); + disconnect(m_editor.data(), &SignalSlotEditor::connectionChanged, + this, &ConnectionModel::connectionChanged); + } + m_editor = editor; + if (m_editor) { + connect(m_editor.data(), &SignalSlotEditor::connectionAdded, + this, &ConnectionModel::connectionAdded); + connect(m_editor.data(), &SignalSlotEditor::connectionRemoved, + this, &ConnectionModel::connectionRemoved); + connect(m_editor.data(), &SignalSlotEditor::aboutToRemoveConnection, + this, &ConnectionModel::aboutToRemoveConnection); + connect(m_editor.data(), &SignalSlotEditor::aboutToAddConnection, + this, &ConnectionModel::aboutToAddConnection); + connect(m_editor.data(), &SignalSlotEditor::connectionChanged, + this, &ConnectionModel::connectionChanged); + } + endResetModel(); +} + +QVariant ConnectionModel::headerData(int section, Qt::Orientation orientation, + int role) const +{ + if (orientation == Qt::Vertical || role != Qt::DisplayRole) + return QVariant(); + + static const QVariant senderTitle = tr("Sender"); + static const QVariant signalTitle = tr("Signal"); + static const QVariant receiverTitle = tr("Receiver"); + static const QVariant slotTitle = tr("Slot"); + + switch (section) { + case 0: + return senderTitle; + case 1: + return signalTitle; + case 2: + return receiverTitle; + case 3: + return slotTitle; + } + return QVariant(); +} + +QModelIndex ConnectionModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (parent.isValid() || !m_editor) + return QModelIndex(); + if (row < 0 || row >= m_editor->connectionCount()) + return QModelIndex(); + return createIndex(row, column); +} + +Connection *ConnectionModel::indexToConnection(const QModelIndex &index) const +{ + if (!index.isValid() || !m_editor) + return nullptr; + if (index.row() < 0 || index.row() >= m_editor->connectionCount()) + return nullptr; + return m_editor->connection(index.row()); +} + +QModelIndex ConnectionModel::connectionToIndex(Connection *con) const +{ + Q_ASSERT(m_editor); + return createIndex(m_editor->indexOfConnection(con), 0); +} + +QModelIndex ConnectionModel::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + +int ConnectionModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid() || !m_editor) + return 0; + return m_editor->connectionCount(); +} + +int ConnectionModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + return 4; +} + +const SignalSlotConnection *ConnectionModel::connectionAt(const QModelIndex &index) const +{ + const int row = index.row(); + return m_editor != nullptr && row >= 0 && row < m_editor->connectionCount() + ? static_cast(m_editor->connection(row)) + : nullptr; +} + +QVariant ConnectionModel::data(const QModelIndex &index, int role) const +{ + enum { deprecatedMember = 0 }; + + const SignalSlotConnection *con = connectionAt(index); + if (con == nullptr) + return QVariant(); + + // Mark deprecated slots red/italic. Not currently in use (historically for Qt 3 slots in Qt 4), + // but may be used again in the future. + switch (role) { + case Qt::ForegroundRole: + return deprecatedMember ? QColor(Qt::red) : QVariant(); + case Qt::FontRole: + if (deprecatedMember) { + QFont font = QApplication::font(); + font.setItalic(true); + return font; + } + return QVariant(); + case Qt::DisplayRole: + case Qt::EditRole: + return ConnectionModel::columnText(con, index.column()); + default: + break; + } + + return QVariant(); +} + +QString ConnectionModel::columnText(const SignalSlotConnection *con, int column) +{ + static const QString senderDefault = tr(""); + static const QString signalDefault = tr(""); + static const QString receiverDefault = tr(""); + static const QString slotDefault = tr(""); + + switch (column) { + case 0: { + const QString sender = con->sender(); + return sender.isEmpty() ? senderDefault : sender; + } + case 1: { + const QString signalName = con->signal(); + return signalName.isEmpty() ? signalDefault : signalName; + } + case 2: { + const QString receiver = con->receiver(); + return receiver.isEmpty() ? receiverDefault : receiver; + } + case 3: { + const QString slotName = con->slot(); + return slotName.isEmpty() ? slotDefault : slotName; + } + } + return QString(); +} + +bool ConnectionModel::setData(const QModelIndex &index, const QVariant &data, int) +{ + if (!index.isValid() || !m_editor) + return false; + if (data.metaType().id() != QMetaType::QString) + return false; + + SignalSlotConnection *con = static_cast(m_editor->connection(index.row())); + QDesignerFormWindowInterface *form = m_editor->formWindow(); + + QString s = data.toString(); + switch (index.column()) { + case 0: + if (!s.isEmpty() && !objectNameList(form).contains(s)) + s.clear(); + m_editor->setSource(con, s); + break; + case 1: + if (!memberFunctionListContains(form->core(), con->object(CETypes::EndPoint::Source), SignalMember, s)) + s.clear(); + m_editor->setSignal(con, s); + break; + case 2: + if (!s.isEmpty() && !objectNameList(form).contains(s)) + s.clear(); + m_editor->setTarget(con, s); + break; + case 3: + if (!memberFunctionListContains(form->core(), con->object(CETypes::EndPoint::Target), SlotMember, s)) + s.clear(); + m_editor->setSlot(con, s); + break; + } + + return true; +} + +void ConnectionModel::connectionAdded(Connection*) +{ + endInsertRows(); +} + +void ConnectionModel::connectionRemoved(int) +{ + endRemoveRows(); +} + +void ConnectionModel::aboutToRemoveConnection(Connection *con) +{ + Q_ASSERT(m_editor); + int idx = m_editor->indexOfConnection(con); + beginRemoveRows(QModelIndex(), idx, idx); +} + +void ConnectionModel::aboutToAddConnection(int idx) +{ + Q_ASSERT(m_editor); + beginInsertRows(QModelIndex(), idx, idx); +} + +Qt::ItemFlags ConnectionModel::flags(const QModelIndex&) const +{ + return Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; +} + +void ConnectionModel::connectionChanged(Connection *con) +{ + Q_ASSERT(m_editor); + const int idx = m_editor->indexOfConnection(con); + SignalSlotConnection *changedCon = static_cast(m_editor->connection(idx)); + SignalSlotConnection *c = nullptr; + for (int i=0; iconnectionCount(); ++i) { + if (i == idx) + continue; + c = static_cast(m_editor->connection(i)); + if (c->sender() == changedCon->sender() && c->signal() == changedCon->signal() + && c->receiver() == changedCon->receiver() && c->slot() == changedCon->slot()) { + const QString message = tr("The connection already exists!
%1").arg(changedCon->toString()); + m_editor->formWindow()->core()->dialogGui()->message(m_editor->parentWidget(), QDesignerDialogGuiInterface::SignalSlotEditorMessage, + QMessageBox::Warning, tr("Signal and Slot Editor"), message, QMessageBox::Ok); + break; + } + } + emit dataChanged(createIndex(idx, 0), createIndex(idx, 3)); +} + +void ConnectionModel::updateAll() +{ + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} +} + +namespace { +// ---------------------- InlineEditorModel + +class InlineEditorModel : public QStandardItemModel +{ + Q_OBJECT +public: + enum { TitleItem = 1 }; + + InlineEditorModel(int rows, int cols, QObject *parent = nullptr); + + void addTitle(const QString &title); + void addTextList(const QMap &text_list); + void addText(const QString &text); + bool isTitle(int idx) const; + + int findText(const QString &text) const; + + Qt::ItemFlags flags(const QModelIndex &index) const override; +}; + +InlineEditorModel::InlineEditorModel(int rows, int cols, QObject *parent) + : QStandardItemModel(rows, cols, parent) +{ +} + +void InlineEditorModel::addTitle(const QString &title) +{ + const int cnt = rowCount(); + insertRows(cnt, 1); + QModelIndex cat_idx = index(cnt, 0); + setData(cat_idx, QString(title + u':'), Qt::DisplayRole); + setData(cat_idx, TitleItem, Qt::UserRole); + QFont font = QApplication::font(); + font.setBold(true); + setData(cat_idx, font, Qt::FontRole); +} + +bool InlineEditorModel::isTitle(int idx) const +{ + if (idx == -1) + return false; + + return data(index(idx, 0), Qt::UserRole).toInt() == TitleItem; +} + +void InlineEditorModel::addText(const QString &text) +{ + const int cnt = rowCount(); + insertRows(cnt, 1); + setData(index(cnt, 0), text, Qt::DisplayRole); +} + +void InlineEditorModel::addTextList(const QMap &text_list) +{ + int cnt = rowCount(); + insertRows(cnt, text_list.size()); + QFont font = QApplication::font(); + font.setItalic(true); + QVariant fontVariant = QVariant::fromValue(font); + for (auto it = text_list.cbegin(), itEnd = text_list.cend(); it != itEnd; ++it) { + const QModelIndex text_idx = index(cnt++, 0); + setData(text_idx, it.key(), Qt::DisplayRole); + if (it.value()) { + setData(text_idx, fontVariant, Qt::FontRole); + setData(text_idx, QColor(Qt::red), Qt::ForegroundRole); + } + } +} + +Qt::ItemFlags InlineEditorModel::flags(const QModelIndex &index) const +{ + return isTitle(index.row()) + ? Qt::ItemFlags(Qt::ItemIsEnabled) + : Qt::ItemFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); +} + +int InlineEditorModel::findText(const QString &text) const +{ + const int cnt = rowCount(); + for (int i = 0; i < cnt; ++i) { + const QModelIndex idx = index(i, 0); + if (data(idx, Qt::UserRole).toInt() == TitleItem) + continue; + if (data(idx, Qt::DisplayRole).toString() == text) + return i; + } + return -1; +} + +// ------------ InlineEditor +class InlineEditor : public QComboBox +{ + Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText USER true) +public: + InlineEditor(QWidget *parent = nullptr); + + QString text() const; + void setText(const QString &text); + + void addTitle(const QString &title); + void addText(const QString &text); + void addTextList(const QMap &text_list); + +private slots: + void checkSelection(int idx); + +private: + InlineEditorModel *m_model; + int m_idx = -1; +}; + +InlineEditor::InlineEditor(QWidget *parent) : + QComboBox(parent) +{ + setModel(m_model = new InlineEditorModel(0, 4, this)); + setFrame(false); + m_idx = -1; + connect(this, &QComboBox::activated, + this, &InlineEditor::checkSelection); +} + +void InlineEditor::checkSelection(int idx) +{ + if (idx == m_idx) + return; + + if (m_model->isTitle(idx)) + setCurrentIndex(m_idx); + else + m_idx = idx; +} + +void InlineEditor::addTitle(const QString &title) +{ + m_model->addTitle(title); +} + +void InlineEditor::addTextList(const QMap &text_list) +{ + m_model->addTextList(text_list); +} + +void InlineEditor::addText(const QString &text) +{ + m_model->addText(text); +} + +QString InlineEditor::text() const +{ + return currentText(); +} + +void InlineEditor::setText(const QString &text) +{ + m_idx = m_model->findText(text); + if (m_idx == -1) + m_idx = 0; + setCurrentIndex(m_idx); +} + +// ------------------ ConnectionDelegate + +class ConnectionDelegate : public QItemDelegate +{ + Q_OBJECT +public: + ConnectionDelegate(QWidget *parent = nullptr); + + void setForm(QDesignerFormWindowInterface *form); + + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + +private slots: + void emitCommitData(); + +private: + QDesignerFormWindowInterface *m_form; +}; + +ConnectionDelegate::ConnectionDelegate(QWidget *parent) + : QItemDelegate(parent) +{ + m_form = nullptr; + + static QItemEditorFactory *factory = nullptr; + if (factory == nullptr) { + factory = new QItemEditorFactory; + QItemEditorCreatorBase *creator + = new QItemEditorCreator("text"); + factory->registerEditor(QMetaType::QString, creator); + } + + setItemEditorFactory(factory); +} + +void ConnectionDelegate::setForm(QDesignerFormWindowInterface *form) +{ + m_form = form; +} + +QWidget *ConnectionDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (m_form == nullptr) + return nullptr; + + QWidget *w = QItemDelegate::createEditor(parent, option, index); + InlineEditor *inline_editor = qobject_cast(w); + Q_ASSERT(inline_editor != nullptr); + const QAbstractItemModel *model = index.model(); + + const QModelIndex obj_name_idx = model->index(index.row(), index.column() <= 1 ? 0 : 2); + const QString obj_name = model->data(obj_name_idx, Qt::DisplayRole).toString(); + + switch (index.column()) { + case 0: + case 2: { // object names + const QStringList &obj_name_list = objectNameList(m_form); + QMap markedNameList; + markedNameList.insert(tr(""), false); + inline_editor->addTextList(markedNameList); + markedNameList.clear(); + for (const QString &name : obj_name_list) + markedNameList.insert(name, false); + inline_editor->addTextList(markedNameList); + } + break; + case 1: + case 3: { // signals, slots + const qdesigner_internal::MemberType type = index.column() == 1 ? qdesigner_internal::SignalMember : qdesigner_internal::SlotMember; + const QModelIndex peer_index = model->index(index.row(), type == qdesigner_internal::SignalMember ? 3 : 1); + const QString peer = model->data(peer_index, Qt::DisplayRole).toString(); + + const qdesigner_internal::ClassesMemberFunctions class_list = qdesigner_internal::reverseClassesMemberFunctions(obj_name, type, peer, m_form); + + inline_editor->addText(type == qdesigner_internal::SignalMember ? tr("") : tr("")); + for (const qdesigner_internal::ClassMemberFunctions &classInfo : class_list) { + if (classInfo.m_className.isEmpty() || classInfo.m_memberList.isEmpty()) + continue; + // Mark deprecated members by passing bool=true. + QMap markedMemberList; + for (const QString &member : std::as_const(classInfo.m_memberList)) + markedMemberList.insert(member, false); + inline_editor->addTitle(classInfo.m_className); + inline_editor->addTextList(markedMemberList); + } + } + break; + default: + break; + } + + connect(inline_editor, &QComboBox::activated, + this, &ConnectionDelegate::emitCommitData); + + return inline_editor; +} + +void ConnectionDelegate::emitCommitData() +{ + InlineEditor *editor = qobject_cast(sender()); + emit commitData(editor); +} + +} + +namespace qdesigner_internal { + +/******************************************************************************* +** SignalSlotEditorWindow +*/ + +SignalSlotEditorWindow::SignalSlotEditorWindow(QDesignerFormEditorInterface *core, + QWidget *parent) : + QWidget(parent), + m_view(new QTreeView), + m_editor(nullptr), + m_add_button(new QToolButton), + m_remove_button(new QToolButton), + m_core(core), + m_model(new ConnectionModel(this)), + m_proxy_model(new QSortFilterProxyModel(this)), + m_handling_selection_change(false) +{ + m_proxy_model->setSourceModel(m_model); + m_view->setModel(m_proxy_model); + m_view->setSortingEnabled(true); + m_view->setItemDelegate(new ConnectionDelegate(this)); + m_view->setEditTriggers(QAbstractItemView::DoubleClicked + | QAbstractItemView::EditKeyPressed); + m_view->setRootIsDecorated(false); + m_view->setTextElideMode (Qt::ElideMiddle); + connect(m_view->selectionModel(), &QItemSelectionModel::currentChanged, + this, &SignalSlotEditorWindow::updateUi); + connect(m_view->header(), &QHeaderView::sectionDoubleClicked, + m_view, &QTreeView::resizeColumnToContents); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + + QToolBar *toolBar = new QToolBar; + toolBar->setIconSize(QSize(22, 22)); + m_add_button->setIcon(createIconSet("plus.png"_L1)); + connect(m_add_button, &QAbstractButton::clicked, this, &SignalSlotEditorWindow::addConnection); + toolBar->addWidget(m_add_button); + + m_remove_button->setIcon(createIconSet("minus.png"_L1)); + connect(m_remove_button, &QAbstractButton::clicked, this, &SignalSlotEditorWindow::removeConnection); + toolBar->addWidget(m_remove_button); + + layout->addWidget(toolBar); + layout->addWidget(m_view); + + connect(core->formWindowManager(), + &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &SignalSlotEditorWindow::setActiveFormWindow); + + updateUi(); +} + +void SignalSlotEditorWindow::setActiveFormWindow(QDesignerFormWindowInterface *form) +{ + QDesignerIntegrationInterface *integration = m_core->integration(); + + if (!m_editor.isNull()) { + disconnect(m_view->selectionModel(), + &QItemSelectionModel::currentChanged, + this, &SignalSlotEditorWindow::updateEditorSelection); + disconnect(m_editor.data(), &SignalSlotEditor::connectionSelected, + this, &SignalSlotEditorWindow::updateDialogSelection); + disconnect(m_editor.data(), &SignalSlotEditor::connectionAdded, + this, &SignalSlotEditorWindow::resizeColumns); + if (integration) { + disconnect(integration, &QDesignerIntegrationInterface::objectNameChanged, + this, &SignalSlotEditorWindow::objectNameChanged); + } + } + + m_editor = form ? form->findChild() : nullptr; + m_model->setEditor(m_editor); + if (!m_editor.isNull()) { + ConnectionDelegate *delegate + = qobject_cast(m_view->itemDelegate()); + if (delegate != nullptr) + delegate->setForm(form); + + connect(m_view->selectionModel(), + &QItemSelectionModel::currentChanged, + this, &SignalSlotEditorWindow::updateEditorSelection); + connect(m_editor.data(), &SignalSlotEditor::connectionSelected, + this, &SignalSlotEditorWindow::updateDialogSelection); + connect(m_editor.data(), &SignalSlotEditor::connectionAdded, + this, &SignalSlotEditorWindow::resizeColumns); + if (integration) { + connect(integration, &QDesignerIntegrationInterface::objectNameChanged, + this, &SignalSlotEditorWindow::objectNameChanged); + } + } + + resizeColumns(); + updateUi(); +} + +void SignalSlotEditorWindow::updateDialogSelection(Connection *con) +{ + if (m_handling_selection_change || m_editor == nullptr) + return; + + QModelIndex index = m_proxy_model->mapFromSource(m_model->connectionToIndex(con)); + if (!index.isValid() || index == m_view->currentIndex()) + return; + m_handling_selection_change = true; + m_view->scrollTo(index, QTreeView::EnsureVisible); + m_view->setCurrentIndex(index); + m_handling_selection_change = false; + + updateUi(); +} + +void SignalSlotEditorWindow::updateEditorSelection(const QModelIndex &index) +{ + if (m_handling_selection_change || m_editor == nullptr) + return; + + if (m_editor == nullptr) + return; + + Connection *con = m_model->indexToConnection(m_proxy_model->mapToSource(index)); + if (m_editor->selected(con)) + return; + m_handling_selection_change = true; + m_editor->selectNone(); + m_editor->setSelected(con, true); + m_handling_selection_change = false; + + updateUi(); +} + +void SignalSlotEditorWindow::objectNameChanged(QDesignerFormWindowInterface *, QObject *, const QString &, const QString &) +{ + if (m_editor) + m_model->updateAll(); +} + +void SignalSlotEditorWindow::addConnection() +{ + if (m_editor.isNull()) + return; + + m_editor->addEmptyConnection(); + updateUi(); +} + +void SignalSlotEditorWindow::removeConnection() +{ + if (m_editor.isNull()) + return; + + m_editor->deleteSelected(); + updateUi(); +} + +void SignalSlotEditorWindow::updateUi() +{ + m_add_button->setEnabled(!m_editor.isNull()); + m_remove_button->setEnabled(!m_editor.isNull() && m_view->currentIndex().isValid()); +} + +void SignalSlotEditorWindow::resizeColumns() +{ + for (int c = 0, count = m_model->columnCount(); c < count; ++c) + m_view->resizeColumnToContents(c); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "signalsloteditorwindow.moc" diff --git a/src/tools/designer/src/components/signalsloteditor/signalsloteditorwindow.h b/src/tools/designer/src/components/signalsloteditor/signalsloteditorwindow.h new file mode 100644 index 00000000000..07891d0cdeb --- /dev/null +++ b/src/tools/designer/src/components/signalsloteditor/signalsloteditorwindow.h @@ -0,0 +1,59 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SIGNALSLOTEDITORWINDOW_H +#define SIGNALSLOTEDITORWINDOW_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QModelIndex; +class QSortFilterProxyModel; +class QTreeView; +class QToolButton; + +namespace qdesigner_internal { + +class SignalSlotEditor; +class ConnectionModel; +class Connection; + +class SignalSlotEditorWindow : public QWidget +{ + Q_OBJECT +public: + explicit SignalSlotEditorWindow(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + +public slots: + void setActiveFormWindow(QDesignerFormWindowInterface *form); + +private slots: + void updateDialogSelection(Connection *con); + void updateEditorSelection(const QModelIndex &index); + + void objectNameChanged(QDesignerFormWindowInterface *formWindow, QObject *object, const QString &newName, const QString &oldName); + + void addConnection(); + void removeConnection(); + void updateUi(); + void resizeColumns(); + +private: + QTreeView *m_view; + QPointer m_editor; + QToolButton *m_add_button, *m_remove_button; + QDesignerFormEditorInterface *m_core; + ConnectionModel *m_model; + QSortFilterProxyModel *m_proxy_model; + bool m_handling_selection_change; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SIGNALSLOTEDITORWINDOW_H diff --git a/src/tools/designer/src/components/tabordereditor/tabordereditor.cpp b/src/tools/designer/src/components/tabordereditor/tabordereditor.cpp new file mode 100644 index 00000000000..76fe3b134aa --- /dev/null +++ b/src/tools/designer/src/components/tabordereditor/tabordereditor.cpp @@ -0,0 +1,398 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tabordereditor.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QWidgetList) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + enum { VBOX_MARGIN = 1, HBOX_MARGIN = 4, BG_ALPHA = 32 }; +} + +static QRect fixRect(QRect r) +{ + return QRect(r.x(), r.y(), r.width() - 1, r.height() - 1); +} + +namespace qdesigner_internal { + +TabOrderEditor::TabOrderEditor(QDesignerFormWindowInterface *form, QWidget *parent) : + QWidget(parent), + m_form_window(form), + m_bg_widget(nullptr), + m_undo_stack(form->commandHistory()), + m_font_metrics(font()), + m_current_index(0), + m_beginning(true) +{ + connect(form, &QDesignerFormWindowInterface::widgetRemoved, this, &TabOrderEditor::widgetRemoved); + + QFont tabFont = font(); + tabFont.setPointSize(tabFont.pointSize()*2); + tabFont.setBold(true); + setFont(tabFont); + m_font_metrics = QFontMetrics(tabFont); + setAttribute(Qt::WA_MouseTracking, true); +} + +QDesignerFormWindowInterface *TabOrderEditor::formWindow() const +{ + return m_form_window; +} + +void TabOrderEditor::setBackground(QWidget *background) +{ + if (background == m_bg_widget) { + return; + } + + m_bg_widget = background; + updateBackground(); +} + +void TabOrderEditor::updateBackground() +{ + if (m_bg_widget == nullptr) { + // nothing to do + return; + } + + initTabOrder(); + update(); +} + +void TabOrderEditor::widgetRemoved(QWidget*) +{ + initTabOrder(); +} + +void TabOrderEditor::showEvent(QShowEvent *e) +{ + QWidget::showEvent(e); + updateBackground(); +} + +QRect TabOrderEditor::indicatorRect(int index) const +{ + if (index < 0 || index >= m_tab_order_list.size()) + return QRect(); + + const QWidget *w = m_tab_order_list.at(index); + const QString text = QString::number(index + 1); + + const QPoint tl = mapFromGlobal(w->mapToGlobal(w->rect().topLeft())); + const QSize size = m_font_metrics.size(Qt::TextSingleLine, text); + QRect r(tl - QPoint(size.width(), size.height())/2, size); + r = QRect(r.left() - HBOX_MARGIN, r.top() - VBOX_MARGIN, + r.width() + HBOX_MARGIN*2, r.height() + VBOX_MARGIN*2); + + return r; +} + +static bool isWidgetVisible(QWidget *widget) +{ + while (widget && widget->parentWidget()) { + if (!widget->isVisibleTo(widget->parentWidget())) + return false; + + widget = widget->parentWidget(); + } + + return true; +} + +void TabOrderEditor::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + p.setClipRegion(e->region()); + + int cur = m_current_index - 1; + if (!m_beginning && cur < 0) + cur = m_tab_order_list.size() - 1; + + for (qsizetype i = 0; i < m_tab_order_list.size(); ++i) { + QWidget *widget = m_tab_order_list.at(i); + if (!isWidgetVisible(widget)) + continue; + + const QRect r = indicatorRect(i); + + QColor c = Qt::darkGreen; + if (i == cur) + c = Qt::red; + else if (i > cur) + c = Qt::blue; + p.setPen(c); + c.setAlpha(BG_ALPHA); + p.setBrush(c); + p.drawRect(fixRect(r)); + + p.setPen(Qt::white); + p.drawText(r, QString::number(i + 1), QTextOption(Qt::AlignCenter)); + } +} + +bool TabOrderEditor::skipWidget(QWidget *w) const +{ + if (qobject_cast(w) + || w == formWindow()->mainContainer() + || w->isHidden()) + return true; + + if (!formWindow()->isManaged(w)) { + return true; + } + + QExtensionManager *ext = formWindow()->core()->extensionManager(); + if (const QDesignerPropertySheetExtension *sheet = qt_extension(ext, w)) { + const int index = sheet->indexOf(u"focusPolicy"_s); + if (index != -1) { + bool ok = false; + Qt::FocusPolicy q = (Qt::FocusPolicy) Utils::valueOf(sheet->property(index), &ok); + return !ok || !(q & Qt::TabFocus); + } + } + + return true; +} + +void TabOrderEditor::initTabOrder() +{ + m_tab_order_list.clear(); + + QDesignerFormEditorInterface *core = formWindow()->core(); + + if (const QDesignerMetaDataBaseItemInterface *item = core->metaDataBase()->item(formWindow())) { + m_tab_order_list = item->tabOrder(); + } + + // Remove any widgets that have been removed form the form + for (qsizetype i = 0; i < m_tab_order_list.size(); ) { + QWidget *w = m_tab_order_list.at(i); + if (!formWindow()->mainContainer()->isAncestorOf(w) || skipWidget(w)) + m_tab_order_list.removeAt(i); + else + ++i; + } + + // Append any widgets that are in the form but are not in the tab order + QWidgetList childQueue; + childQueue.append(formWindow()->mainContainer()); + while (!childQueue.isEmpty()) { + QWidget *child = childQueue.takeFirst(); + childQueue += qvariant_cast(child->property("_q_widgetOrder")); + + if (skipWidget(child)) + continue; + + if (!m_tab_order_list.contains(child)) + m_tab_order_list.append(child); + } + + // Just in case we missed some widgets + QDesignerFormWindowCursorInterface *cursor = formWindow()->cursor(); + for (int i = 0; i < cursor->widgetCount(); ++i) { + + QWidget *widget = cursor->widget(i); + if (skipWidget(widget)) + continue; + + if (!m_tab_order_list.contains(widget)) + m_tab_order_list.append(widget); + } + + m_indicator_region = QRegion(); + for (qsizetype i = 0; i < m_tab_order_list.size(); ++i) { + if (m_tab_order_list.at(i)->isVisible()) + m_indicator_region |= indicatorRect(i); + } + + if (m_current_index >= m_tab_order_list.size()) + m_current_index = m_tab_order_list.size() - 1; + if (m_current_index < 0) + m_current_index = 0; +} + +void TabOrderEditor::mouseMoveEvent(QMouseEvent *e) +{ + e->accept(); +#if QT_CONFIG(cursor) + if (m_indicator_region.contains(e->position().toPoint())) + setCursor(Qt::PointingHandCursor); + else + setCursor(QCursor()); +#endif +} + +int TabOrderEditor::widgetIndexAt(QPoint pos) const +{ + int target_index = -1; + for (qsizetype i = 0; i < m_tab_order_list.size(); ++i) { + if (!m_tab_order_list.at(i)->isVisible()) + continue; + if (indicatorRect(i).contains(pos)) { + target_index = i; + break; + } + } + + return target_index; +} + +void TabOrderEditor::mousePressEvent(QMouseEvent *e) +{ + e->accept(); + + if (!m_indicator_region.contains(e->position().toPoint())) { + if (QWidget *child = m_bg_widget->childAt(e->position().toPoint())) { + QDesignerFormEditorInterface *core = m_form_window->core(); + if (core->widgetFactory()->isPassiveInteractor(child)) { + + QMouseEvent event(QEvent::MouseButtonPress, + child->mapFromGlobal(e->globalPosition().toPoint()), + e->globalPosition().toPoint(), e->button(), e->buttons(), + e->modifiers()); + + qApp->sendEvent(child, &event); + + QMouseEvent event2(QEvent::MouseButtonRelease, + child->mapFromGlobal(e->globalPosition().toPoint()), + e->globalPosition().toPoint(), e->button(), e->buttons(), + e->modifiers()); + + qApp->sendEvent(child, &event2); + + updateBackground(); + } + } + return; + } + + if (e->button() != Qt::LeftButton) + return; + + const int target_index = widgetIndexAt(e->position().toPoint()); + if (target_index == -1) + return; + + m_beginning = false; + + if (e->modifiers() & Qt::ControlModifier) { + m_current_index = target_index + 1; + if (m_current_index >= m_tab_order_list.size()) + m_current_index = 0; + update(); + return; + } + + if (m_current_index == -1) + return; + + m_tab_order_list.swapItemsAt(target_index, m_current_index); + + ++m_current_index; + if (m_current_index == m_tab_order_list.size()) + m_current_index = 0; + + TabOrderCommand *cmd = new TabOrderCommand(formWindow()); + cmd->init(m_tab_order_list); + formWindow()->commandHistory()->push(cmd); +} + +void TabOrderEditor::contextMenuEvent(QContextMenuEvent *e) +{ + QMenu menu(this); + const int target_index = widgetIndexAt(e->pos()); + QAction *setIndex = menu.addAction(tr("Start from Here")); + setIndex->setEnabled(target_index >= 0); + + QAction *resetIndex = menu.addAction(tr("Restart")); + menu.addSeparator(); + QAction *showDialog = menu.addAction(tr("Tab Order List...")); + showDialog->setEnabled(m_tab_order_list.size() > 1); + + QAction *result = menu.exec(e->globalPos()); + if (result == resetIndex) { + m_current_index = 0; + m_beginning = true; + update(); + } else if (result == setIndex) { + m_beginning = false; + m_current_index = target_index + 1; + if (m_current_index >= m_tab_order_list.size()) + m_current_index = 0; + update(); + } else if (result == showDialog) { + showTabOrderDialog(); + } +} + +void TabOrderEditor::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) + return; + + const int target_index = widgetIndexAt(e->position().toPoint()); + if (target_index >= 0) + return; + + m_beginning = true; + m_current_index = 0; + update(); +} + +void TabOrderEditor::resizeEvent(QResizeEvent *e) +{ + updateBackground(); + QWidget::resizeEvent(e); +} + +void TabOrderEditor::showTabOrderDialog() +{ + if (m_tab_order_list.size() < 2) + return; + OrderDialog dlg(this); + dlg.setWindowTitle(tr("Tab Order List")); + dlg.setDescription(tr("Tab Order")); + dlg.setFormat(OrderDialog::TabOrderFormat); + dlg.setPageList(m_tab_order_list); + + if (dlg.exec() == QDialog::Rejected) + return; + + const QWidgetList newOrder = dlg.pageList(); + if (newOrder == m_tab_order_list) + return; + + m_tab_order_list = newOrder; + TabOrderCommand *cmd = new TabOrderCommand(formWindow()); + cmd->init(m_tab_order_list); + formWindow()->commandHistory()->push(cmd); + update(); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/tabordereditor/tabordereditor.h b/src/tools/designer/src/components/tabordereditor/tabordereditor.h new file mode 100644 index 00000000000..d3406e7afbc --- /dev/null +++ b/src/tools/designer/src/components/tabordereditor/tabordereditor.h @@ -0,0 +1,71 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABORDEREDITOR_H +#define TABORDEREDITOR_H + +#include "tabordereditor_global.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QUndoStack; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class QT_TABORDEREDITOR_EXPORT TabOrderEditor : public QWidget +{ + Q_OBJECT + +public: + TabOrderEditor(QDesignerFormWindowInterface *form, QWidget *parent); + + QDesignerFormWindowInterface *formWindow() const; + +public slots: + void setBackground(QWidget *background); + void updateBackground(); + void widgetRemoved(QWidget*); + void initTabOrder(); + +private slots: + void showTabOrderDialog(); + +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void contextMenuEvent(QContextMenuEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void showEvent(QShowEvent *e) override; + +private: + QRect indicatorRect(int index) const; + int widgetIndexAt(QPoint pos) const; + bool skipWidget(QWidget *w) const; + + QPointer m_form_window; + + QWidgetList m_tab_order_list; + + QWidget *m_bg_widget; + QUndoStack *m_undo_stack; + QRegion m_indicator_region; + + QFontMetrics m_font_metrics; + int m_current_index; + bool m_beginning; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/designer/src/components/tabordereditor/tabordereditor.json b/src/tools/designer/src/components/tabordereditor/tabordereditor.json new file mode 100644 index 00000000000..0967ef424bc --- /dev/null +++ b/src/tools/designer/src/components/tabordereditor/tabordereditor.json @@ -0,0 +1 @@ +{} diff --git a/src/tools/designer/src/components/tabordereditor/tabordereditor_global.h b/src/tools/designer/src/components/tabordereditor/tabordereditor_global.h new file mode 100644 index 00000000000..68d5c86eff4 --- /dev/null +++ b/src/tools/designer/src/components/tabordereditor/tabordereditor_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABORDEREDITOR_GLOBAL_H +#define TABORDEREDITOR_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_TABORDEREDITOR_LIBRARY +# define QT_TABORDEREDITOR_EXPORT +#else +# define QT_TABORDEREDITOR_EXPORT +#endif +#else +#define QT_TABORDEREDITOR_EXPORT +#endif + +#endif // TABORDEREDITOR_GLOBAL_H diff --git a/src/tools/designer/src/components/tabordereditor/tabordereditor_plugin.cpp b/src/tools/designer/src/components/tabordereditor/tabordereditor_plugin.cpp new file mode 100644 index 00000000000..2fe3a421294 --- /dev/null +++ b/src/tools/designer/src/components/tabordereditor/tabordereditor_plugin.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include "tabordereditor_plugin.h" +#include "tabordereditor_tool.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TabOrderEditorPlugin::TabOrderEditorPlugin() = default; + +TabOrderEditorPlugin::~TabOrderEditorPlugin() = default; + +bool TabOrderEditorPlugin::isInitialized() const +{ + return m_initialized; +} + +void TabOrderEditorPlugin::initialize(QDesignerFormEditorInterface *core) +{ + Q_ASSERT(!isInitialized()); + + m_action = new QAction(tr("Edit Tab Order"), this); + m_action->setObjectName(u"_qt_edit_tab_order_action"_s); + QIcon icon = QIcon::fromTheme(u"designer-edit-tabs"_s, + QIcon(core->resourceLocation() + "/tabordertool.png"_L1)); + m_action->setIcon(icon); + m_action->setEnabled(false); + + setParent(core); + m_core = core; + m_initialized = true; + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowAdded, + this, &TabOrderEditorPlugin::addFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowRemoved, + this, &TabOrderEditorPlugin::removeFormWindow); + + connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &TabOrderEditorPlugin::activeFormWindowChanged); +} + +void TabOrderEditorPlugin::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) +{ + m_action->setEnabled(formWindow != nullptr); +} + +QDesignerFormEditorInterface *TabOrderEditorPlugin::core() const +{ + return m_core; +} + +void TabOrderEditorPlugin::addFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == false); + + TabOrderEditorTool *tool = new TabOrderEditorTool(formWindow, this); + m_tools[formWindow] = tool; + connect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + formWindow->registerTool(tool); +} + +void TabOrderEditorPlugin::removeFormWindow(QDesignerFormWindowInterface *formWindow) +{ + Q_ASSERT(formWindow != nullptr); + Q_ASSERT(m_tools.contains(formWindow) == true); + + TabOrderEditorTool *tool = m_tools.value(formWindow); + m_tools.remove(formWindow); + disconnect(m_action, &QAction::triggered, tool->action(), &QAction::trigger); + // ### FIXME disable the tool + + delete tool; +} + +QAction *TabOrderEditorPlugin::action() const +{ + return m_action; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "moc_tabordereditor_plugin.cpp" diff --git a/src/tools/designer/src/components/tabordereditor/tabordereditor_plugin.h b/src/tools/designer/src/components/tabordereditor/tabordereditor_plugin.h new file mode 100644 index 00000000000..99168099c75 --- /dev/null +++ b/src/tools/designer/src/components/tabordereditor/tabordereditor_plugin.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABORDEREDITOR_PLUGIN_H +#define TABORDEREDITOR_PLUGIN_H + +#include "tabordereditor_global.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class TabOrderEditorTool; + +class QT_TABORDEREDITOR_EXPORT TabOrderEditorPlugin: public QObject, public QDesignerFormEditorPluginInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerFormEditorPluginInterface" FILE "tabordereditor.json") + Q_INTERFACES(QDesignerFormEditorPluginInterface) +public: + TabOrderEditorPlugin(); + ~TabOrderEditorPlugin() override; + + bool isInitialized() const override; + void initialize(QDesignerFormEditorInterface *core) override; + QAction *action() const override; + + QDesignerFormEditorInterface *core() const override; + +public slots: + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + +private slots: + void addFormWindow(QDesignerFormWindowInterface *formWindow); + void removeFormWindow(QDesignerFormWindowInterface *formWindow); + +private: + QPointer m_core; + QHash m_tools; + bool m_initialized = false; + QAction *m_action = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TABORDEREDITOR_PLUGIN_H diff --git a/src/tools/designer/src/components/tabordereditor/tabordereditor_tool.cpp b/src/tools/designer/src/components/tabordereditor/tabordereditor_tool.cpp new file mode 100644 index 00000000000..46270b00742 --- /dev/null +++ b/src/tools/designer/src/components/tabordereditor/tabordereditor_tool.cpp @@ -0,0 +1,75 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tabordereditor_tool.h" +#include "tabordereditor.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +TabOrderEditorTool::TabOrderEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent) + : QDesignerFormWindowToolInterface(parent), + m_formWindow(formWindow), + m_action(new QAction(tr("Edit Tab Order"), this)) +{ +} + +TabOrderEditorTool::~TabOrderEditorTool() = default; + +QDesignerFormEditorInterface *TabOrderEditorTool::core() const +{ + return m_formWindow->core(); +} + +QDesignerFormWindowInterface *TabOrderEditorTool::formWindow() const +{ + return m_formWindow; +} + +bool TabOrderEditorTool::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) +{ + Q_UNUSED(widget); + Q_UNUSED(managedWidget); + + return event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease; +} + +QWidget *TabOrderEditorTool::editor() const +{ + if (!m_editor) { + Q_ASSERT(formWindow() != nullptr); + m_editor = new TabOrderEditor(formWindow(), nullptr); + connect(formWindow(), &QDesignerFormWindowInterface::mainContainerChanged, + m_editor.data(), &TabOrderEditor::setBackground); + } + + return m_editor; +} + +void TabOrderEditorTool::activated() +{ + connect(formWindow(), &QDesignerFormWindowInterface::changed, + m_editor.data(), &TabOrderEditor::updateBackground); +} + +void TabOrderEditorTool::deactivated() +{ + disconnect(formWindow(), &QDesignerFormWindowInterface::changed, + m_editor.data(), &TabOrderEditor::updateBackground); +} + +QAction *TabOrderEditorTool::action() const +{ + return m_action; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/tabordereditor/tabordereditor_tool.h b/src/tools/designer/src/components/tabordereditor/tabordereditor_tool.h new file mode 100644 index 00000000000..bbd4f5c64f0 --- /dev/null +++ b/src/tools/designer/src/components/tabordereditor/tabordereditor_tool.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABORDEREDITOR_TOOL_H +#define TABORDEREDITOR_TOOL_H + +#include "tabordereditor_global.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QAction; + +namespace qdesigner_internal { + +class TabOrderEditor; + +class QT_TABORDEREDITOR_EXPORT TabOrderEditorTool: public QDesignerFormWindowToolInterface +{ + Q_OBJECT +public: + explicit TabOrderEditorTool(QDesignerFormWindowInterface *formWindow, QObject *parent = nullptr); + ~TabOrderEditorTool() override; + + QDesignerFormEditorInterface *core() const override; + QDesignerFormWindowInterface *formWindow() const override; + + QWidget *editor() const override; + QAction *action() const override; + + void activated() override; + void deactivated() override; + + bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) override; + +private: + QDesignerFormWindowInterface *m_formWindow; + mutable QPointer m_editor; + QAction *m_action; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TABORDEREDITOR_TOOL_H diff --git a/src/tools/designer/src/components/taskmenu/button_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/button_taskmenu.cpp new file mode 100644 index 00000000000..7edf6c04ad9 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/button_taskmenu.cpp @@ -0,0 +1,665 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "button_taskmenu.h" +#include "inplace_editor.h" +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +Q_DECLARE_METATYPE(QButtonGroup*) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +enum { debugButtonMenu = 0 }; + +using ButtonList = QList; +using ButtonGroupList = QList; + +// ButtonGroupCommand: Base for commands handling button groups and button lists +// addButtonsToGroup() and removeButtonsFromGroup() are low-level helpers for +// adding/removing members to/from existing groups. +// +// createButtonGroup()/breakButtonGroup() create and remove the groups from scratch. +// When using them in a command, the command must be executed within +// a macro since it makes the form emit objectRemoved() which might cause other components +// to add commands (for example, removal of signals and slots) +class ButtonGroupCommand : public QDesignerFormWindowCommand { + +protected: + ButtonGroupCommand(const QString &description, QDesignerFormWindowInterface *formWindow); + + void initialize(const ButtonList &bl, QButtonGroup *buttonGroup); + + // Helper: Add the buttons to the group + void addButtonsToGroup(); + // Helper; Remove the buttons + void removeButtonsFromGroup(); + + // Create the button group in Designer + void createButtonGroup(); + // Remove the button group from Designer + void breakButtonGroup(); + +public: + static QString nameList(const ButtonList& bl); + static ButtonGroupList managedButtonGroups(const QDesignerFormWindowInterface *formWindow); + +private: + ButtonList m_buttonList; + QButtonGroup *m_buttonGroup; +}; + +ButtonGroupCommand::ButtonGroupCommand(const QString &description, QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(description, formWindow), + m_buttonGroup(nullptr) +{ +} + +void ButtonGroupCommand::initialize(const ButtonList &bl, QButtonGroup *buttonGroup) +{ + m_buttonList = bl; + m_buttonGroup = buttonGroup; +} + +void ButtonGroupCommand::addButtonsToGroup() +{ + if (debugButtonMenu) + qDebug() << "Adding " << m_buttonList << " to " << m_buttonGroup; + for (auto *b : std::as_const(m_buttonList)) + m_buttonGroup->addButton(b); +} + +void ButtonGroupCommand::removeButtonsFromGroup() +{ + if (debugButtonMenu) + qDebug() << "Removing " << m_buttonList << " from " << m_buttonGroup; + for (auto *b : std::as_const(m_buttonList)) + m_buttonGroup->removeButton(b); +} + +void ButtonGroupCommand::createButtonGroup() +{ + if (debugButtonMenu) + qDebug() << "Creating " << m_buttonGroup << " from " << m_buttonList; + + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + core->metaDataBase()->add(m_buttonGroup); + addButtonsToGroup(); + // Make button group visible + core->objectInspector()->setFormWindow(fw); +} + +void ButtonGroupCommand::breakButtonGroup() +{ + if (debugButtonMenu) + qDebug() << "Removing " << m_buttonGroup << " consisting of " << m_buttonList; + + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + // Button group was selected, that is, break was invoked via its context menu. Remove it from property editor, select the buttons + if (core->propertyEditor()->object() == m_buttonGroup) { + fw->clearSelection(false); + for (auto *b : std::as_const(m_buttonList)) + fw->selectWidget(b, true); + } + // Now remove and refresh object inspector + removeButtonsFromGroup(); + // Notify components (for example, signal slot editor) + if (qdesigner_internal::FormWindowBase *fwb = qobject_cast(fw)) + fwb->emitObjectRemoved(m_buttonGroup); + core->metaDataBase()->remove(m_buttonGroup); + core->objectInspector()->setFormWindow(fw); +} + +QString ButtonGroupCommand::nameList(const ButtonList& bl) +{ + QString rc; + const QChar quote = QLatin1Char('\''); + const auto separator = ", "_L1; + for (qsizetype i = 0, size = bl.size(); i < size; ++i) { + if (i) + rc += separator; + rc += quote; + rc += bl.at(i)->objectName(); + rc += quote; + } + return rc; + +} + +ButtonGroupList ButtonGroupCommand::managedButtonGroups(const QDesignerFormWindowInterface *formWindow) +{ + const QDesignerMetaDataBaseInterface *mdb = formWindow->core()->metaDataBase(); + ButtonGroupList bl; + // Check 1st order children for managed button groups + for (auto *o : formWindow->mainContainer()->children()) { + if (!o->isWidgetType()) { + if (QButtonGroup *bg = qobject_cast(o)) { + if (mdb->item(bg)) + bl.push_back(bg); + } + } + } + return bl; +} + +// --------------- CreateButtonGroupCommand +// This command might be executed in a macro with a remove +// command to move buttons from one group to a new one. +class CreateButtonGroupCommand : public ButtonGroupCommand { +public: + CreateButtonGroupCommand(QDesignerFormWindowInterface *formWindow); + bool init(const ButtonList &bl); + + void undo() override { breakButtonGroup(); } + void redo() override { createButtonGroup(); } +}; + +CreateButtonGroupCommand::CreateButtonGroupCommand(QDesignerFormWindowInterface *formWindow) : + ButtonGroupCommand(QApplication::translate("Command", "Create button group"), formWindow) +{ +} + +bool CreateButtonGroupCommand::init(const ButtonList &bl) +{ + if (bl.isEmpty()) + return false; + QDesignerFormWindowInterface *fw = formWindow(); + QButtonGroup *buttonGroup = new QButtonGroup(fw->mainContainer()); + buttonGroup->setObjectName(u"buttonGroup"_s); + fw->ensureUniqueObjectName(buttonGroup); + initialize(bl, buttonGroup); + return true; +} + +// --------------- BreakButtonGroupCommand +class BreakButtonGroupCommand : public ButtonGroupCommand { +public: + BreakButtonGroupCommand(QDesignerFormWindowInterface *formWindow); + bool init(QButtonGroup *group); + + void undo() override { createButtonGroup(); } + void redo() override { breakButtonGroup(); } +}; + +BreakButtonGroupCommand::BreakButtonGroupCommand(QDesignerFormWindowInterface *formWindow) : + ButtonGroupCommand(QApplication::translate("Command", "Break button group"), formWindow) +{ +} + +bool BreakButtonGroupCommand::init(QButtonGroup *group) +{ + if (!group) + return false; + initialize(group->buttons(), group); + setText(QApplication::translate("Command", "Break button group '%1'").arg(group->objectName())); + return true; +} + +// --------------- AddButtonsToGroupCommand +// This command might be executed in a macro with a remove +// command to move buttons from one group to a new one. +class AddButtonsToGroupCommand : public ButtonGroupCommand { +public: + AddButtonsToGroupCommand(QDesignerFormWindowInterface *formWindow); + void init(const ButtonList &bl, QButtonGroup *group); + + void undo() override { removeButtonsFromGroup(); } + void redo() override { addButtonsToGroup(); } +}; + +AddButtonsToGroupCommand::AddButtonsToGroupCommand(QDesignerFormWindowInterface *formWindow) : + ButtonGroupCommand(QApplication::translate("Command", "Add buttons to group"), formWindow) +{ +} + +void AddButtonsToGroupCommand::init(const ButtonList &bl, QButtonGroup *group) +{ + initialize(bl, group); + //: Command description for adding buttons to a QButtonGroup + setText(QApplication::translate("Command", "Add '%1' to '%2'").arg(nameList(bl), group->objectName())); +} + +//-------------------- RemoveButtonsFromGroupCommand +class RemoveButtonsFromGroupCommand : public ButtonGroupCommand { +public: + RemoveButtonsFromGroupCommand(QDesignerFormWindowInterface *formWindow); + bool init(const ButtonList &bl); + + void undo() override { addButtonsToGroup(); } + void redo() override { removeButtonsFromGroup(); } +}; + +RemoveButtonsFromGroupCommand::RemoveButtonsFromGroupCommand(QDesignerFormWindowInterface *formWindow) : + ButtonGroupCommand(QApplication::translate("Command", "Remove buttons from group"), formWindow) +{ +} + +bool RemoveButtonsFromGroupCommand::init(const ButtonList &bl) +{ + if (bl.isEmpty()) + return false; + QButtonGroup *group = bl.constFirst()->group(); + if (!group) + return false; + if (bl.size() >= group->buttons().size()) + return false; + initialize(bl, group); + //: Command description for removing buttons from a QButtonGroup + setText(QApplication::translate("Command", "Remove '%1' from '%2'").arg(nameList(bl), group->objectName())); + return true; +} + +// -------- ButtonGroupMenu +ButtonGroupMenu::ButtonGroupMenu(QObject *parent) : + QObject(parent), + m_selectGroupAction(new QAction(tr("Select members"), this)), + m_breakGroupAction(new QAction(tr("Break"), this)) +{ + connect(m_breakGroupAction, &QAction::triggered, this, &ButtonGroupMenu::breakGroup); + connect(m_selectGroupAction, &QAction::triggered, this, &ButtonGroupMenu::selectGroup); +} + +void ButtonGroupMenu::initialize(QDesignerFormWindowInterface *formWindow, QButtonGroup *buttonGroup, QAbstractButton *currentButton) +{ + m_buttonGroup = buttonGroup; + m_currentButton = currentButton; + m_formWindow = formWindow; + Q_ASSERT(m_formWindow); + + const bool canBreak = buttonGroup != nullptr; + m_breakGroupAction->setEnabled(canBreak); + m_selectGroupAction->setEnabled(canBreak); +} + +void ButtonGroupMenu::selectGroup() +{ + // Select and make current button "current" again by selecting it last (if there is any) + const ButtonList buttons = m_buttonGroup->buttons(); + m_formWindow->clearSelection(false); + for (auto *b : buttons) { + if (b != m_currentButton) + m_formWindow->selectWidget(b, true); + } + if (m_currentButton) + m_formWindow->selectWidget(m_currentButton, true); +} + +void ButtonGroupMenu::breakGroup() +{ + BreakButtonGroupCommand *cmd = new BreakButtonGroupCommand(m_formWindow); + if (cmd->init(m_buttonGroup)) { + // Need a macro since the command might trigger additional commands + QUndoStack *history = m_formWindow->commandHistory(); + history->beginMacro(cmd->text()); + history->push(cmd); + history->endMacro(); + } else { + qWarning("** WARNING Failed to initialize BreakButtonGroupCommand!"); + delete cmd; + } +} + +// ButtonGroupTaskMenu +ButtonGroupTaskMenu::ButtonGroupTaskMenu(QButtonGroup *buttonGroup, QObject *parent) : + QObject(parent), + m_buttonGroup(buttonGroup) +{ + m_taskActions.push_back(m_menu.breakGroupAction()); + m_taskActions.push_back(m_menu.selectGroupAction()); +} + +QAction *ButtonGroupTaskMenu::preferredEditAction() const +{ + return m_menu.selectGroupAction(); +} + +QList ButtonGroupTaskMenu::taskActions() const +{ + m_menu.initialize(QDesignerFormWindowInterface::findFormWindow(m_buttonGroup), m_buttonGroup); + return m_taskActions; +} + +// -------- Text area editor +class ButtonTextTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + ButtonTextTaskMenuInlineEditor(QAbstractButton *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +ButtonTextTaskMenuInlineEditor::ButtonTextTaskMenuInlineEditor(QAbstractButton *button, QObject *parent) : + TaskMenuInlineEditor(button, ValidationMultiLine, u"text"_s, parent) +{ +} + +QRect ButtonTextTaskMenuInlineEditor::editRectangle() const +{ + QWidget *w = widget(); + QStyleOptionButton opt; + opt.initFrom(w); + return w->style()->subElementRect(QStyle::SE_PushButtonContents, &opt, w); +} + +// -------- Command link button description editor +class LinkDescriptionTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + LinkDescriptionTaskMenuInlineEditor(QAbstractButton *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +LinkDescriptionTaskMenuInlineEditor::LinkDescriptionTaskMenuInlineEditor(QAbstractButton *button, QObject *parent) : + TaskMenuInlineEditor(button, ValidationMultiLine, u"description"_s, parent) +{ +} + +QRect LinkDescriptionTaskMenuInlineEditor::editRectangle() const +{ + QWidget *w = widget(); // TODO: What is the exact description area? + QStyleOptionButton opt; + opt.initFrom(w); + return w->style()->subElementRect(QStyle::SE_PushButtonContents, &opt, w); +} + +// ----------- ButtonTaskMenu: + +ButtonTaskMenu::ButtonTaskMenu(QAbstractButton *button, QObject *parent) : + QDesignerTaskMenu(button, parent), + m_assignGroupSubMenu(new QMenu), + m_assignActionGroup(nullptr), + m_assignToGroupSubMenuAction(new QAction(tr("Assign to button group"), this)), + m_currentGroupSubMenu(new QMenu), + m_currentGroupSubMenuAction(new QAction(tr("Button group"), this)), + m_createGroupAction(new QAction(tr("New button group"), this)), + m_preferredEditAction(new QAction(tr("Change text..."), this)), + m_removeFromGroupAction(new QAction(tr("None"), this)) +{ + connect(m_createGroupAction, &QAction::triggered, this, &ButtonTaskMenu::createGroup); + TaskMenuInlineEditor *textEditor = new ButtonTextTaskMenuInlineEditor(button, this); + connect(m_preferredEditAction, &QAction::triggered, textEditor, &TaskMenuInlineEditor::editText); + connect(m_removeFromGroupAction, &QAction::triggered, this, &ButtonTaskMenu::removeFromGroup); + + m_assignToGroupSubMenuAction->setMenu(m_assignGroupSubMenu); + + m_currentGroupSubMenu->addAction(m_groupMenu.breakGroupAction()); + m_currentGroupSubMenu->addAction(m_groupMenu.selectGroupAction()); + m_currentGroupSubMenuAction->setMenu(m_currentGroupSubMenu); + + + m_taskActions.append(m_preferredEditAction); + m_taskActions.append(m_assignToGroupSubMenuAction); + m_taskActions.append(m_currentGroupSubMenuAction); + m_taskActions.append(createSeparator()); +} + +ButtonTaskMenu::~ButtonTaskMenu() +{ + delete m_assignGroupSubMenu; + delete m_currentGroupSubMenu; +} + +QAction *ButtonTaskMenu::preferredEditAction() const +{ + return m_preferredEditAction; +} + +bool ButtonTaskMenu::refreshAssignMenu(const QDesignerFormWindowInterface *fw, int buttonCount, SelectionType st, QButtonGroup *currentGroup) +{ + // clear + if (m_assignActionGroup) { + delete m_assignActionGroup; + m_assignActionGroup = nullptr; + } + m_assignGroupSubMenu->clear(); + if (st == OtherSelection) + return false; + + + // Assign to new: Need several + const bool canAssignToNewGroup = buttonCount > 1; + m_createGroupAction->setEnabled(canAssignToNewGroup); + if (canAssignToNewGroup) + m_assignGroupSubMenu->addAction(m_createGroupAction); + + // Assign to other + const ButtonGroupList bl = ButtonGroupCommand::managedButtonGroups(fw); + // Groups: Any groups to add to except the current? + const auto groupCount = bl.size(); + const bool hasAddGroups = groupCount > 1 || (groupCount == 1 && !bl.contains(currentGroup)); + if (hasAddGroups) { + if (!m_assignGroupSubMenu->isEmpty()) + m_assignGroupSubMenu->addSeparator(); + // Create a new action group + m_assignActionGroup = new QActionGroup(this); + connect(m_assignActionGroup, &QActionGroup::triggered, this, &ButtonTaskMenu::addToGroup); + for (auto *bg : bl) { + if (bg != currentGroup) { + QAction *a = new QAction(bg->objectName(), m_assignGroupSubMenu); + a->setData(QVariant::fromValue(bg)); + m_assignActionGroup->addAction(a); + m_assignGroupSubMenu->addAction(a); + } + } + } + // Can remove: A homogenous selection of another group that does not completely break it. + const bool canRemoveFromGroup = st == GroupedButtonSelection; + m_removeFromGroupAction->setEnabled(canRemoveFromGroup); + if (canRemoveFromGroup) { + if (!m_assignGroupSubMenu->isEmpty()) + m_assignGroupSubMenu->addSeparator(); + m_assignGroupSubMenu->addAction(m_removeFromGroupAction); + } + return !m_assignGroupSubMenu->isEmpty(); +} + +QList ButtonTaskMenu::taskActions() const +{ + ButtonTaskMenu *ncThis = const_cast(this); + QButtonGroup *buttonGroup = nullptr; + + QDesignerFormWindowInterface *fw = formWindow(); + const SelectionType st = selectionType(fw->cursor(), &buttonGroup); + + m_groupMenu.initialize(fw, buttonGroup, button()); + const bool hasAssignOptions = ncThis->refreshAssignMenu(fw, fw->cursor()->selectedWidgetCount(), st, buttonGroup); + m_assignToGroupSubMenuAction->setVisible(hasAssignOptions); + // add/remove + switch (st) { + case UngroupedButtonSelection: + case OtherSelection: + m_currentGroupSubMenuAction->setVisible(false); + break; + case GroupedButtonSelection: + m_currentGroupSubMenuAction->setText(tr("Button group '%1'").arg(buttonGroup->objectName())); + m_currentGroupSubMenuAction->setVisible(true); + break; + } + + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + + +void ButtonTaskMenu::insertAction(int index, QAction *a) +{ + m_taskActions.insert(index, a); +} + +/* Create a button list from the cursor selection */ +static ButtonList buttonList(const QDesignerFormWindowCursorInterface *cursor) +{ + ButtonList rc; + const int selectionCount = cursor->selectedWidgetCount(); + for (int i = 0; i < selectionCount; i++) { + QAbstractButton *ab = qobject_cast(cursor->selectedWidget(i)); + Q_ASSERT(ab); + rc += ab; + } + return rc; +} + +// Create a command to remove the buttons from their group +// If it would leave an empty or 1-member group behind, create a break command instead + +static QUndoCommand *createRemoveButtonsCommand(QDesignerFormWindowInterface *fw, const ButtonList &bl) +{ + + QButtonGroup *bg = bl.constFirst()->group(); + // Complete group or 1-member group? + if (bl.size() >= bg->buttons().size() - 1) { + BreakButtonGroupCommand *breakCmd = new BreakButtonGroupCommand(fw); + if (!breakCmd->init(bg)) { + qWarning("** WARNING Failed to initialize BreakButtonGroupCommand!"); + delete breakCmd; + return nullptr; + } + return breakCmd; + } + // Just remove the buttons + + RemoveButtonsFromGroupCommand *removeCmd = new RemoveButtonsFromGroupCommand(fw); + if (!removeCmd->init(bl)) { + qWarning("** WARNING Failed to initialize RemoveButtonsFromGroupCommand!"); + delete removeCmd; + return nullptr; + } + return removeCmd; +} + +void ButtonTaskMenu::createGroup() +{ + QDesignerFormWindowInterface *fw = formWindow(); + const ButtonList bl = buttonList(fw->cursor()); + // Do we need to remove the buttons from an existing group? + QUndoCommand *removeCmd = nullptr; + if (bl.constFirst()->group()) { + removeCmd = createRemoveButtonsCommand(fw, bl); + if (!removeCmd) + return; + } + // Add cmd + CreateButtonGroupCommand *addCmd = new CreateButtonGroupCommand(fw); + if (!addCmd->init(bl)) { + qWarning("** WARNING Failed to initialize CreateButtonGroupCommand!"); + delete addCmd; + return; + } + // Need a macro [even if we only have the add command] since the command might trigger additional commands + QUndoStack *history = fw->commandHistory(); + history->beginMacro(addCmd->text()); + if (removeCmd) + history->push(removeCmd); + history->push(addCmd); + history->endMacro(); +} + +QAbstractButton *ButtonTaskMenu::button() const +{ + return qobject_cast(widget()); +} + +// Figure out if we have a homogenous selections (buttons of the same group or no group) +ButtonTaskMenu::SelectionType ButtonTaskMenu::selectionType(const QDesignerFormWindowCursorInterface *cursor, QButtonGroup **ptrToGroup) const +{ + const int selectionCount = cursor->selectedWidgetCount(); + if (!selectionCount) + return OtherSelection; + + QButtonGroup *commonGroup = nullptr; + for (int i = 0; i < selectionCount; i++) { + if (const QAbstractButton *ab = qobject_cast(cursor->selectedWidget(i))) { + QButtonGroup *buttonGroup = ab->group(); + if (i) { + if (buttonGroup != commonGroup) + return OtherSelection; + } else { + commonGroup = buttonGroup; + } + } else { + return OtherSelection; + } + } + + if (ptrToGroup) + *ptrToGroup = commonGroup; + + return commonGroup ? GroupedButtonSelection : UngroupedButtonSelection; +} + +void ButtonTaskMenu::addToGroup(QAction *a) +{ + QButtonGroup *bg = qvariant_cast(a->data()); + Q_ASSERT(bg); + + QDesignerFormWindowInterface *fw = formWindow(); + const ButtonList bl = buttonList(fw->cursor()); + // Do we need to remove the buttons from an existing group? + QUndoCommand *removeCmd = nullptr; + if (bl.constFirst()->group()) { + removeCmd = createRemoveButtonsCommand(fw, bl); + if (!removeCmd) + return; + } + AddButtonsToGroupCommand *addCmd = new AddButtonsToGroupCommand(fw); + addCmd->init(bl, bg); + + QUndoStack *history = fw->commandHistory(); + if (removeCmd) { + history->beginMacro(addCmd->text()); + history->push(removeCmd); + history->push(addCmd); + history->endMacro(); + } else { + history->push(addCmd); + } +} + +void ButtonTaskMenu::removeFromGroup() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (QUndoCommand *cmd = createRemoveButtonsCommand(fw, buttonList(fw->cursor()))) + fw->commandHistory()->push(cmd); +} + +// -------------- CommandLinkButtonTaskMenu + +CommandLinkButtonTaskMenu::CommandLinkButtonTaskMenu(QCommandLinkButton *button, QObject *parent) : + ButtonTaskMenu(button, parent) +{ + TaskMenuInlineEditor *descriptonEditor = new LinkDescriptionTaskMenuInlineEditor(button, this); + QAction *descriptionAction = new QAction(tr("Change description..."), this); + connect(descriptionAction, &QAction::triggered, descriptonEditor, &TaskMenuInlineEditor::editText); + insertAction(1, descriptionAction); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/button_taskmenu.h b/src/tools/designer/src/components/taskmenu/button_taskmenu.h new file mode 100644 index 00000000000..9623bea14e5 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/button_taskmenu.h @@ -0,0 +1,132 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef BUTTON_TASKMENU_H +#define BUTTON_TASKMENU_H + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QMenu; +class QActionGroup; +class QDesignerFormWindowCursorInterface; + +namespace qdesigner_internal { + +// ButtonGroupMenu: Mixin menu for the 'select members'/'break group' options of +// the task menu of buttons and button group +class ButtonGroupMenu : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ButtonGroupMenu) +public: + ButtonGroupMenu(QObject *parent = nullptr); + + void initialize(QDesignerFormWindowInterface *formWindow, + QButtonGroup *buttonGroup = nullptr, + /* Current button for selection in ButtonMode */ + QAbstractButton *currentButton = nullptr); + + QAction *selectGroupAction() const { return m_selectGroupAction; } + QAction *breakGroupAction() const { return m_breakGroupAction; } + +private slots: + void selectGroup(); + void breakGroup(); + +private: + QAction *m_selectGroupAction; + QAction *m_breakGroupAction; + + QDesignerFormWindowInterface *m_formWindow = nullptr; + QButtonGroup *m_buttonGroup = nullptr; + QAbstractButton *m_currentButton = nullptr; +}; + +// Task menu extension of a QButtonGroup +class ButtonGroupTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ButtonGroupTaskMenu) + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit ButtonGroupTaskMenu(QButtonGroup *buttonGroup, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QButtonGroup *m_buttonGroup; + QList m_taskActions; + mutable ButtonGroupMenu m_menu; +}; + +// Task menu extension of a QAbstractButton +class ButtonTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ButtonTaskMenu) +public: + explicit ButtonTaskMenu(QAbstractButton *button, QObject *parent = nullptr); + ~ButtonTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + + QAbstractButton *button() const; + +protected: + void insertAction(int index, QAction *a); + +private slots: + void createGroup(); + void addToGroup(QAction *a); + void removeFromGroup(); + +private: + enum SelectionType { + OtherSelection, + UngroupedButtonSelection, + GroupedButtonSelection + }; + + SelectionType selectionType(const QDesignerFormWindowCursorInterface *cursor, QButtonGroup ** ptrToGroup = nullptr) const; + bool refreshAssignMenu(const QDesignerFormWindowInterface *fw, int buttonCount, SelectionType st, QButtonGroup *currentGroup); + QMenu *createGroupSelectionMenu(const QDesignerFormWindowInterface *fw); + + QList m_taskActions; + mutable ButtonGroupMenu m_groupMenu; + QMenu *m_assignGroupSubMenu; + QActionGroup *m_assignActionGroup; + QAction *m_assignToGroupSubMenuAction; + QMenu *m_currentGroupSubMenu; + QAction *m_currentGroupSubMenuAction; + + QAction *m_createGroupAction; + QAction *m_preferredEditAction; + QAction *m_removeFromGroupAction; +}; + +// Task menu extension of a QCommandLinkButton +class CommandLinkButtonTaskMenu: public ButtonTaskMenu +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(CommandLinkButtonTaskMenu) +public: + explicit CommandLinkButtonTaskMenu(QCommandLinkButton *button, QObject *parent = nullptr); +}; + +using ButtonGroupTaskMenuFactory = ExtensionFactory; +using CommandLinkButtonTaskMenuFactory = ExtensionFactory; +using ButtonTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // BUTTON_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/combobox_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/combobox_taskmenu.cpp new file mode 100644 index 00000000000..75c1959b522 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/combobox_taskmenu.cpp @@ -0,0 +1,96 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "combobox_taskmenu.h" +#include "listwidgeteditor.h" +#include "qdesigner_utils_p.h" +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +ComboBoxTaskMenu::ComboBoxTaskMenu(QComboBox *button, QObject *parent) + : QDesignerTaskMenu(button, parent), + m_comboBox(button) +{ + m_editItemsAction = new QAction(this); + m_editItemsAction->setText(tr("Edit Items...")); + connect(m_editItemsAction, &QAction::triggered, this, &ComboBoxTaskMenu::editItems); + m_taskActions.append(m_editItemsAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +ComboBoxTaskMenu::~ComboBoxTaskMenu() = default; + +QAction *ComboBoxTaskMenu::preferredEditAction() const +{ + return m_editItemsAction; +} + +QList ComboBoxTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void ComboBoxTaskMenu::editItems() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_comboBox); + if (m_formWindow.isNull()) + return; + + Q_ASSERT(m_comboBox != nullptr); + + ListWidgetEditor dlg(m_formWindow, m_comboBox->window()); + ListContents oldItems = dlg.fillContentsFromComboBox(m_comboBox); + if (dlg.exec() == QDialog::Accepted) { + ListContents items = dlg.contents(); + if (items != oldItems) { + ChangeListContentsCommand *cmd = new ChangeListContentsCommand(m_formWindow); + cmd->init(m_comboBox, oldItems, items); + cmd->setText(tr("Change Combobox Contents")); + m_formWindow->commandHistory()->push(cmd); + } + } +} + +ComboBoxTaskMenuFactory::ComboBoxTaskMenuFactory(const QString &iid, QExtensionManager *extensionManager) : + ExtensionFactory(iid, extensionManager) +{ +} + +QComboBox *ComboBoxTaskMenuFactory::checkObject(QObject *qObject) const +{ + QComboBox *combo = qobject_cast(qObject); + if (!combo) + return nullptr; + if (qobject_cast(combo)) + return nullptr; + return combo; +} + +void ComboBoxTaskMenu::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/combobox_taskmenu.h b/src/tools/designer/src/components/taskmenu/combobox_taskmenu.h new file mode 100644 index 00000000000..47dad220d65 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/combobox_taskmenu.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef COMBOBOX_TASKMENU_H +#define COMBOBOX_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QLineEdit; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class ComboBoxTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit ComboBoxTaskMenu(QComboBox *button, + QObject *parent = nullptr); + ~ComboBoxTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editItems(); + void updateSelection(); + +private: + QComboBox *m_comboBox; + QPointer m_formWindow; + QPointer m_editor; + mutable QList m_taskActions; + QAction *m_editItemsAction; +}; + +class ComboBoxTaskMenuFactory : public ExtensionFactory +{ +public: + explicit ComboBoxTaskMenuFactory(const QString &iid, QExtensionManager *extensionManager); + +private: + QComboBox *checkObject(QObject *qObject) const override; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // COMBOBOX_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/containerwidget_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/containerwidget_taskmenu.cpp new file mode 100644 index 00000000000..a9cec6eb2e5 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/containerwidget_taskmenu.cpp @@ -0,0 +1,302 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "containerwidget_taskmenu.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +ContainerWidgetTaskMenu::ContainerWidgetTaskMenu(QWidget *widget, ContainerType type, QObject *parent) : + QDesignerTaskMenu(widget, parent), + m_type(type), + m_containerWidget(widget), + m_core(formWindow()->core()), + m_pagePromotionTaskMenu(new PromotionTaskMenu(nullptr, PromotionTaskMenu::ModeSingleWidget, this)), + m_pageMenuAction(new QAction(this)), + m_pageMenu(new QMenu), + m_actionInsertPageAfter(new QAction(this)), + m_actionInsertPage(nullptr), + m_actionDeletePage(new QAction(tr("Delete"), this)) +{ + Q_ASSERT(m_core); + m_taskActions.append(createSeparator()); + + connect(m_actionDeletePage, &QAction::triggered, this, &ContainerWidgetTaskMenu::removeCurrentPage); + + connect(m_actionInsertPageAfter, &QAction::triggered, this, &ContainerWidgetTaskMenu::addPageAfter); + // Empty Per-Page submenu, deletion and promotion. Updated on demand due to promotion state + switch (m_type) { + case WizardContainer: + case PageContainer: + m_taskActions.append(createSeparator()); // for the browse actions + break; + case MdiContainer: + break; + } + // submenu + m_pageMenuAction->setMenu(m_pageMenu); + m_taskActions.append(m_pageMenuAction); + // Insertion + switch (m_type) { + case WizardContainer: + case PageContainer: { // Before and after in a submenu + QAction *insertMenuAction = new QAction(tr("Insert"), this); + QMenu *insertMenu = new QMenu; + // before + m_actionInsertPage = new QAction(tr("Insert Page Before Current Page"), this); + connect(m_actionInsertPage, &QAction::triggered, this, &ContainerWidgetTaskMenu::addPage); + insertMenu->addAction(m_actionInsertPage); + // after + m_actionInsertPageAfter->setText(tr("Insert Page After Current Page")); + insertMenu->addAction(m_actionInsertPageAfter); + + insertMenuAction->setMenu(insertMenu); + m_taskActions.append(insertMenuAction); + } + break; + case MdiContainer: // No concept of order + m_actionInsertPageAfter->setText(tr("Add Subwindow")); + m_taskActions.append(m_actionInsertPageAfter); + break; + } +} + +ContainerWidgetTaskMenu::~ContainerWidgetTaskMenu() = default; + +QAction *ContainerWidgetTaskMenu::preferredEditAction() const +{ + return nullptr; +} + +bool ContainerWidgetTaskMenu::canDeletePage() const +{ + switch (pageCount()) { + case 0: + return false; + case 1: + return m_type != PageContainer; // Do not delete last page of page-type container + default: + break; + } + return true; +} + +int ContainerWidgetTaskMenu::pageCount() const +{ + if (const QDesignerContainerExtension *ce = containerExtension()) + return ce->count(); + return 0; +} + +QString ContainerWidgetTaskMenu::pageMenuText(ContainerType ct, int index, int count) +{ + if (ct == MdiContainer) + return tr("Subwindow"); // No concept of order, same text everywhere + if (index < 0) + return tr("Page"); + return tr("Page %1 of %2").arg(index + 1).arg(count); +} + +QList ContainerWidgetTaskMenu::taskActions() const +{ + const QDesignerContainerExtension *ce = containerExtension(); + const int index = ce->currentIndex(); + + auto actions = QDesignerTaskMenu::taskActions(); + actions += m_taskActions; + // Update the page submenu, deletion and promotion. Updated on demand due to promotion state. + m_pageMenu->clear(); + const bool canAddWidget = ce->canAddWidget(); + if (m_actionInsertPage) + m_actionInsertPage->setEnabled(canAddWidget); + m_actionInsertPageAfter->setEnabled(canAddWidget); + m_pageMenu->addAction(m_actionDeletePage); + m_actionDeletePage->setEnabled(index >= 0 && ce->canRemove(index) && canDeletePage()); + m_pageMenuAction->setText(pageMenuText(m_type, index, ce->count())); + if (index != -1) { // Has a page + m_pageMenuAction->setEnabled(true); + m_pagePromotionTaskMenu->setWidget(ce->widget(index)); + m_pagePromotionTaskMenu->addActions(PromotionTaskMenu::LeadingSeparator|PromotionTaskMenu::SuppressGlobalEdit, m_pageMenu); + } else { // No page + m_pageMenuAction->setEnabled(false); + } + + return actions; +} + +QDesignerFormWindowInterface *ContainerWidgetTaskMenu::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(m_containerWidget); +} + +QDesignerContainerExtension *ContainerWidgetTaskMenu::containerExtension() const +{ + QExtensionManager *mgr = m_core->extensionManager(); + return qt_extension(mgr, m_containerWidget); +} + +void ContainerWidgetTaskMenu::removeCurrentPage() +{ + if (QDesignerContainerExtension *c = containerExtension()) { + if (c->currentIndex() == -1) + return; + + QDesignerFormWindowInterface *fw = formWindow(); + DeleteContainerWidgetPageCommand *cmd = new DeleteContainerWidgetPageCommand(fw); + cmd->init(m_containerWidget, m_type); + fw->commandHistory()->push(cmd); + } +} + +void ContainerWidgetTaskMenu::addPage() +{ + if (containerExtension()) { + QDesignerFormWindowInterface *fw = formWindow(); + AddContainerWidgetPageCommand *cmd = new AddContainerWidgetPageCommand(fw); + cmd->init(m_containerWidget, m_type, AddContainerWidgetPageCommand::InsertBefore); + fw->commandHistory()->push(cmd); + } +} + +void ContainerWidgetTaskMenu::addPageAfter() +{ + if (containerExtension()) { + QDesignerFormWindowInterface *fw = formWindow(); + AddContainerWidgetPageCommand *cmd = new AddContainerWidgetPageCommand(fw); + cmd->init(m_containerWidget, m_type, AddContainerWidgetPageCommand::InsertAfter); + fw->commandHistory()->push(cmd); + } +} + +// -------------- WizardContainerWidgetTaskMenu +WizardContainerWidgetTaskMenu::WizardContainerWidgetTaskMenu(QWizard *w, QObject *parent) : + ContainerWidgetTaskMenu(w, WizardContainer, parent), + m_nextAction(new QAction(tr("Next"), this)), + m_previousAction(new QAction(tr("Back"), this)) +{ + connect(m_nextAction, &QAction::triggered, w, &QWizard::next); + connect(m_previousAction, &QAction::triggered, w, &QWizard::back); + auto &l = containerActions(); + l.push_front(createSeparator()); + l.push_front(m_nextAction); + l.push_front(m_previousAction); + l.push_front(createSeparator()); +} + +QList WizardContainerWidgetTaskMenu::taskActions() const +{ + // Enable + const QDesignerContainerExtension *ce = containerExtension(); + const int index = ce->currentIndex(); + m_previousAction->setEnabled(index > 0); + m_nextAction->setEnabled(index >= 0 && index < (ce->count() - 1)); + return ContainerWidgetTaskMenu::taskActions(); +} + +// -------------- MdiContainerWidgetTaskMenu + +MdiContainerWidgetTaskMenu::MdiContainerWidgetTaskMenu(QMdiArea *m, QObject *parent) : + ContainerWidgetTaskMenu(m, MdiContainer, parent) +{ + initializeActions(); + connect(m_nextAction, &QAction::triggered, m, &QMdiArea::activateNextSubWindow); + connect(m_previousAction, &QAction::triggered, m , &QMdiArea::activatePreviousSubWindow); + connect(m_tileAction, &QAction::triggered, m, &QMdiArea::tileSubWindows); + connect(m_cascadeAction, &QAction::triggered, m, &QMdiArea::cascadeSubWindows); +} + +void MdiContainerWidgetTaskMenu::initializeActions() +{ + m_nextAction =new QAction(tr("Next Subwindow"), this); + m_previousAction = new QAction(tr("Previous Subwindow"), this); + m_tileAction = new QAction(tr("Tile"), this); + m_cascadeAction = new QAction(tr("Cascade"), this); + + auto &l = containerActions(); + l.push_front(createSeparator()); + l.push_front(m_tileAction); + l.push_front(m_cascadeAction); + l.push_front(m_previousAction); + l.push_front(m_nextAction); + l.push_front(createSeparator()); +} + +QList MdiContainerWidgetTaskMenu::taskActions() const +{ + const auto rc = ContainerWidgetTaskMenu::taskActions(); + // Enable + const int count = pageCount(); + m_nextAction->setEnabled(count > 1); + m_previousAction->setEnabled(count > 1); + m_tileAction->setEnabled(count); + m_cascadeAction->setEnabled(count); + return rc; +} + +// -------------- ContainerWidgetTaskMenuFactory + +ContainerWidgetTaskMenuFactory::ContainerWidgetTaskMenuFactory(QDesignerFormEditorInterface *core, QExtensionManager *extensionManager) : + QExtensionFactory(extensionManager), + m_core(core) +{ +} + +QObject *ContainerWidgetTaskMenuFactory::createExtension(QObject *object, const QString &iid, QObject *parent) const +{ + if (iid != "QDesignerInternalTaskMenuExtension"_L1 || !object->isWidgetType()) + return nullptr; + + QWidget *widget = qobject_cast(object); + + if (qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget) + || qobject_cast(widget)) { + // Are we using Designer's own container extensions and task menus or did + // someone provide an extra one with an addpage method, for example for a QScrollArea? + if (const WidgetDataBase *wb = qobject_cast(m_core->widgetDataBase())) { + const int idx = wb->indexOfObject(widget); + const WidgetDataBaseItem *item = static_cast(wb->item(idx)); + if (item->addPageMethod().isEmpty()) + return nullptr; + } + } + + if (qt_extension(extensionManager(), object) == nullptr) + return nullptr; + + if (QMdiArea* ma = qobject_cast(widget)) + return new MdiContainerWidgetTaskMenu(ma, parent); + if (QWizard *wz = qobject_cast(widget)) + return new WizardContainerWidgetTaskMenu(wz, parent); + return new ContainerWidgetTaskMenu(widget, PageContainer, parent); +} + +} +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/containerwidget_taskmenu.h b/src/tools/designer/src/components/taskmenu/containerwidget_taskmenu.h new file mode 100644 index 00000000000..e27cb29ca77 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/containerwidget_taskmenu.h @@ -0,0 +1,119 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONTAINERWIDGER_TASKMENU_H +#define CONTAINERWIDGER_TASKMENU_H + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QDesignerContainerExtension; +class QAction; +class QMdiArea; +class QMenu; +class QWizard; + +namespace qdesigner_internal { + +class PromotionTaskMenu; + +// ContainerWidgetTaskMenu: Task menu for containers with extension + +class ContainerWidgetTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit ContainerWidgetTaskMenu(QWidget *widget, ContainerType type, QObject *parent = nullptr); + ~ContainerWidgetTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void removeCurrentPage(); + void addPage(); + void addPageAfter(); + +protected: + QDesignerContainerExtension *containerExtension() const; + QList &containerActions() { return m_taskActions; } + int pageCount() const; + +private: + QDesignerFormWindowInterface *formWindow() const; + +private: + static QString pageMenuText(ContainerType ct, int index, int count); + bool canDeletePage() const; + + const ContainerType m_type; + QWidget *m_containerWidget; + QDesignerFormEditorInterface *m_core; + PromotionTaskMenu *m_pagePromotionTaskMenu; + QAction *m_pageMenuAction; + QMenu *m_pageMenu; + QList m_taskActions; + QAction *m_actionInsertPageAfter; + QAction *m_actionInsertPage; + QAction *m_actionDeletePage; +}; + +// WizardContainerWidgetTaskMenu: Provide next/back since QWizard +// has modes in which the "Back" button is not visible. + +class WizardContainerWidgetTaskMenu : public ContainerWidgetTaskMenu { + Q_OBJECT +public: + explicit WizardContainerWidgetTaskMenu(QWizard *w, QObject *parent = nullptr); + + QList taskActions() const override; + +private: + QAction *m_nextAction; + QAction *m_previousAction; +}; + + +// MdiContainerWidgetTaskMenu: Provide tile/cascade for MDI containers in addition + +class MdiContainerWidgetTaskMenu : public ContainerWidgetTaskMenu { + Q_OBJECT +public: + explicit MdiContainerWidgetTaskMenu(QMdiArea *m, QObject *parent = nullptr); + + QList taskActions() const override; +private: + void initializeActions(); + + QAction *m_nextAction = nullptr; + QAction *m_previousAction = nullptr; + QAction *m_tileAction = nullptr; + QAction *m_cascadeAction = nullptr; +}; + +class ContainerWidgetTaskMenuFactory: public QExtensionFactory +{ + Q_OBJECT +public: + explicit ContainerWidgetTaskMenuFactory(QDesignerFormEditorInterface *core, QExtensionManager *extensionManager = nullptr); + +protected: + QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override; + +private: + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // CONTAINERWIDGER_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/groupbox_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/groupbox_taskmenu.cpp new file mode 100644 index 00000000000..f8818bdf0cf --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/groupbox_taskmenu.cpp @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "groupbox_taskmenu.h" +#include "inplace_editor.h" + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// -------- GroupBoxTaskMenuInlineEditor +class GroupBoxTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + GroupBoxTaskMenuInlineEditor(QGroupBox *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +GroupBoxTaskMenuInlineEditor::GroupBoxTaskMenuInlineEditor(QGroupBox *w, QObject *parent) : + TaskMenuInlineEditor(w, ValidationSingleLine, u"title"_s, parent) +{ +} + +QRect GroupBoxTaskMenuInlineEditor::editRectangle() const +{ + QWidget *w = widget(); + QStyleOption opt; // ## QStyleOptionGroupBox + opt.initFrom(w); + return QRect(QPoint(), QSize(w->width(),20)); +} + +// --------------- GroupBoxTaskMenu + +GroupBoxTaskMenu::GroupBoxTaskMenu(QGroupBox *groupbox, QObject *parent) + : QDesignerTaskMenu(groupbox, parent), + m_editTitleAction(new QAction(tr("Change title..."), this)) + +{ + TaskMenuInlineEditor *editor = new GroupBoxTaskMenuInlineEditor(groupbox, this); + connect(m_editTitleAction, &QAction::triggered, editor, &TaskMenuInlineEditor::editText); + m_taskActions.append(m_editTitleAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +QList GroupBoxTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +QAction *GroupBoxTaskMenu::preferredEditAction() const +{ + return m_editTitleAction; +} + +} +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/groupbox_taskmenu.h b/src/tools/designer/src/components/taskmenu/groupbox_taskmenu.h new file mode 100644 index 00000000000..6217273c776 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/groupbox_taskmenu.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef GROUPBOX_TASKMENU_H +#define GROUPBOX_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { +class InPlaceEditor; + +class GroupBoxTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit GroupBoxTaskMenu(QGroupBox *groupbox, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QAction *m_editTitleAction; + QList m_taskActions; +}; + +using GroupBoxTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // GROUPBOX_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/inplace_editor.cpp b/src/tools/designer/src/components/taskmenu/inplace_editor.cpp new file mode 100644 index 00000000000..5afbea3a4d7 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/inplace_editor.cpp @@ -0,0 +1,101 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindow.h" +#include "inplace_editor.h" + +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// ----------------- InPlaceEditor + +InPlaceEditor::InPlaceEditor(QWidget *widget, + TextPropertyValidationMode validationMode, + QDesignerFormWindowInterface *fw, + const QString& text, + const QRect& r) : + TextPropertyEditor(widget, EmbeddingInPlace, validationMode), + m_InPlaceWidgetHelper(this, widget, fw) +{ + setAlignment(m_InPlaceWidgetHelper.alignment()); + setObjectName(u"__qt__passive_m_editor"_s); + + setText(text); + selectAll(); + + setGeometry(QRect(widget->mapTo(widget->window(), r.topLeft()), r.size())); + setFocus(); + show(); + + connect(this, &TextPropertyEditor::editingFinished,this, &QWidget::close); +} + + +// -------------- TaskMenuInlineEditor + +TaskMenuInlineEditor::TaskMenuInlineEditor(QWidget *w, TextPropertyValidationMode vm, + const QString &property, QObject *parent) : + QObject(parent), + m_vm(vm), + m_property(property), + m_widget(w), + m_managed(true) +{ +} + +void TaskMenuInlineEditor::editText() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_widget); + if (m_formWindow.isNull()) + return; + m_managed = m_formWindow->isManaged(m_widget); + // Close as soon as a different widget is selected + connect(m_formWindow.data(), &QDesignerFormWindowInterface::selectionChanged, + this, &TaskMenuInlineEditor::updateSelection); + + // get old value + QDesignerFormEditorInterface *core = m_formWindow->core(); + const QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), m_widget); + const int index = sheet->indexOf(m_property); + if (index == -1) + return; + m_value = qvariant_cast(sheet->property(index)); + const QString oldValue = m_value.value(); + + m_editor = new InPlaceEditor(m_widget, m_vm, m_formWindow, oldValue, editRectangle()); + connect(m_editor.data(), &InPlaceEditor::textChanged, this, &TaskMenuInlineEditor::updateText); +} + +void TaskMenuInlineEditor::updateText(const QString &text) +{ + // In the [rare] event we are invoked on an unmanaged widget, + // do not use the cursor selection + m_value.setValue(text); + if (m_managed) { + m_formWindow->cursor()->setProperty(m_property, QVariant::fromValue(m_value)); + } else { + m_formWindow->cursor()->setWidgetProperty(m_widget, m_property, QVariant::fromValue(m_value)); + } +} + +void TaskMenuInlineEditor::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/inplace_editor.h b/src/tools/designer/src/components/taskmenu/inplace_editor.h new file mode 100644 index 00000000000..c8b28f0bab4 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/inplace_editor.h @@ -0,0 +1,71 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef INPLACE_EDITOR_H +#define INPLACE_EDITOR_H + +#include +#include + +#include "inplace_widget_helper.h" +#include + +#include + +QT_BEGIN_NAMESPACE + + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class InPlaceEditor: public TextPropertyEditor +{ + Q_OBJECT +public: + InPlaceEditor(QWidget *widget, + TextPropertyValidationMode validationMode, + QDesignerFormWindowInterface *fw, + const QString& text, + const QRect& r); +private: + InPlaceWidgetHelper m_InPlaceWidgetHelper; +}; + +// Base class for inline editor helpers to be embedded into a task menu. +// Inline-edits a property on a multi-selection. +// To use it for a particular widget/property, overwrite the method +// returning the edit area. + +class TaskMenuInlineEditor : public QObject +{ + Q_OBJECT + +public slots: + void editText(); + +private slots: + void updateText(const QString &text); + void updateSelection(); + +protected: + TaskMenuInlineEditor(QWidget *w, TextPropertyValidationMode vm, const QString &property, QObject *parent); + // Overwrite to return the area for the inline editor. + virtual QRect editRectangle() const = 0; + QWidget *widget() const { return m_widget; } + +private: + const TextPropertyValidationMode m_vm; + const QString m_property; + QWidget *m_widget; + QPointer m_formWindow; + QPointer m_editor; + bool m_managed; + qdesigner_internal::PropertySheetStringValue m_value; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // INPLACE_EDITOR_H diff --git a/src/tools/designer/src/components/taskmenu/inplace_widget_helper.cpp b/src/tools/designer/src/components/taskmenu/inplace_widget_helper.cpp new file mode 100644 index 00000000000..0183076bd7e --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/inplace_widget_helper.cpp @@ -0,0 +1,86 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindow.h" +#include "inplace_widget_helper.h" + + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + InPlaceWidgetHelper::InPlaceWidgetHelper(QWidget *editorWidget, QWidget *parentWidget, QDesignerFormWindowInterface *fw) + : QObject(nullptr), + m_editorWidget(editorWidget), + m_parentWidget(parentWidget), + m_noChildEvent(m_parentWidget->testAttribute(Qt::WA_NoChildEventsForParent)) + { + m_editorWidget->setAttribute(Qt::WA_DeleteOnClose); + m_editorWidget->setParent(m_parentWidget->window()); + m_parentWidget->installEventFilter(this); + m_editorWidget->installEventFilter(this); + connect(m_editorWidget, &QObject::destroyed, + fw->mainContainer(), QOverload<>::of(&QWidget::setFocus)); + } + + InPlaceWidgetHelper::~InPlaceWidgetHelper() + { + if (m_parentWidget) + m_parentWidget->setAttribute(Qt::WA_NoChildEventsForParent, m_noChildEvent); + } + + Qt::Alignment InPlaceWidgetHelper::alignment() const { + if (m_parentWidget->metaObject()->indexOfProperty("alignment") != -1) + return Qt::Alignment(m_parentWidget->property("alignment").toInt()); + + if (qobject_cast(m_parentWidget) + || qobject_cast(m_parentWidget) /* tool needs to be more complex */) + return Qt::AlignHCenter; + + return Qt::AlignJustify; + } + + + bool InPlaceWidgetHelper::eventFilter(QObject *object, QEvent *e) + { + if (object == m_parentWidget) { + if (e->type() == QEvent::Resize) { + const QResizeEvent *event = static_cast(e); + const QPoint localPos = m_parentWidget->geometry().topLeft(); + const QPoint globalPos = m_parentWidget->parentWidget() ? m_parentWidget->parentWidget()->mapToGlobal(localPos) : localPos; + const QPoint newPos = (m_editorWidget->parentWidget() ? m_editorWidget->parentWidget()->mapFromGlobal(globalPos) : globalPos) + + m_posOffset; + const QSize newSize = event->size() + m_sizeOffset; + m_editorWidget->setGeometry(QRect(newPos, newSize)); + } + } else if (object == m_editorWidget) { + if (e->type() == QEvent::ShortcutOverride) { + if (static_cast(e)->key() == Qt::Key_Escape) { + e->accept(); + return false; + } + } else if (e->type() == QEvent::KeyPress) { + if (static_cast(e)->key() == Qt::Key_Escape) { + e->accept(); + m_editorWidget->close(); + return true; + } + } else if (e->type() == QEvent::Show) { + const QPoint localPos = m_parentWidget->geometry().topLeft(); + const QPoint globalPos = m_parentWidget->parentWidget() ? m_parentWidget->parentWidget()->mapToGlobal(localPos) : localPos; + const QPoint newPos = m_editorWidget->parentWidget() ? m_editorWidget->parentWidget()->mapFromGlobal(globalPos) : globalPos; + m_posOffset = m_editorWidget->geometry().topLeft() - newPos; + m_sizeOffset = m_editorWidget->size() - m_parentWidget->size(); + } + } + + return QObject::eventFilter(object, e); + } +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/inplace_widget_helper.h b/src/tools/designer/src/components/taskmenu/inplace_widget_helper.h new file mode 100644 index 00000000000..587f3a415a5 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/inplace_widget_helper.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef INPLACE_WIDGETHELPER_H +#define INPLACE_WIDGETHELPER_H + + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + + // A helper class to make an editor widget suitable for form inline + // editing. Derive from the editor widget class and make InPlaceWidgetHelper a member. + // + // Sets "destructive close" on the editor widget and + // wires "ESC" to it. + // Installs an event filter on the parent to listen for + // resize events and passes them on to the child. + // You might want to connect editingFinished() to close() of the editor widget. + class InPlaceWidgetHelper: public QObject + { + Q_OBJECT + public: + InPlaceWidgetHelper(QWidget *editorWidget, QWidget *parentWidget, QDesignerFormWindowInterface *fw); + ~InPlaceWidgetHelper() override; + + bool eventFilter(QObject *object, QEvent *event) override; + + // returns a recommended alignment for the editor widget determined from the parent. + Qt::Alignment alignment() const; + private: + QWidget *m_editorWidget; + QPointer m_parentWidget; + const bool m_noChildEvent; + QPoint m_posOffset; + QSize m_sizeOffset; + }; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // INPLACE_WIDGETHELPER_H diff --git a/src/tools/designer/src/components/taskmenu/itemlisteditor.cpp b/src/tools/designer/src/components/taskmenu/itemlisteditor.cpp new file mode 100644 index 00000000000..696927664f4 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/itemlisteditor.cpp @@ -0,0 +1,490 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "itemlisteditor.h" +#include +#include +#include +#include + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +class ItemPropertyBrowser : public QtTreePropertyBrowser +{ +public: + ItemPropertyBrowser() + { + setResizeMode(Interactive); + //: Sample string to determinate the width for the first column of the list item property browser + const QString widthSampleString = QCoreApplication::translate("ItemPropertyBrowser", "XX Icon Selected off"); + m_width = fontMetrics().horizontalAdvance(widthSampleString); + setSplitterPosition(m_width); + m_width += fontMetrics().horizontalAdvance(u"/this/is/some/random/path"_s); + } + + QSize sizeHint() const override + { + return QSize(m_width, 1); + } + +private: + int m_width; +}; + +////////////////// Item editor /////////////// +AbstractItemEditor::AbstractItemEditor(QDesignerFormWindowInterface *form, QWidget *parent) + : QWidget(parent), + m_iconCache(qobject_cast(form)->iconCache()) +{ + m_propertyManager = new DesignerPropertyManager(form->core(), this); + m_editorFactory = new DesignerEditorFactory(form->core(), this); + m_editorFactory->setSpacing(0); + m_propertyBrowser = new ItemPropertyBrowser; + m_propertyBrowser->setFactoryForManager(static_cast(m_propertyManager), + m_editorFactory); + + connect(m_editorFactory, &DesignerEditorFactory::resetProperty, + this, &AbstractItemEditor::resetProperty); + connect(m_propertyManager, &DesignerPropertyManager::valueChanged, + this, &AbstractItemEditor::propertyChanged); + connect(iconCache(), &DesignerIconCache::reloaded, this, &AbstractItemEditor::cacheReloaded); +} + +AbstractItemEditor::~AbstractItemEditor() +{ + m_propertyBrowser->unsetFactoryForManager(m_propertyManager); +} + +static const char * const itemFlagNames[] = { + QT_TRANSLATE_NOOP("AbstractItemEditor", "Selectable"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "Editable"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "DragEnabled"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "DropEnabled"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "UserCheckable"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "Enabled"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "Tristate"), + nullptr +}; + +static const char * const checkStateNames[] = { + QT_TRANSLATE_NOOP("AbstractItemEditor", "Unchecked"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "PartiallyChecked"), + QT_TRANSLATE_NOOP("AbstractItemEditor", "Checked"), + nullptr +}; + +static QStringList c2qStringList(const char * const in[]) +{ + QStringList out; + for (int i = 0; in[i]; i++) + out << AbstractItemEditor::tr(in[i]); + return out; +} + +void AbstractItemEditor::setupProperties(const PropertyDefinition *propList, + Qt::Alignment alignDefault) +{ + for (int i = 0; propList[i].name; i++) { + int type = propList[i].typeFunc ? propList[i].typeFunc() : propList[i].type; + int role = propList[i].role; + QtVariantProperty *prop = m_propertyManager->addProperty(type, QLatin1StringView(propList[i].name)); + if (role == Qt::TextAlignmentRole) { + prop->setAttribute(DesignerPropertyManager::alignDefaultAttribute(), + QVariant(uint(alignDefault))); + } + Q_ASSERT(prop); + if (role == Qt::ToolTipPropertyRole || role == Qt::WhatsThisPropertyRole) + prop->setAttribute(u"validationMode"_s, ValidationRichText); + else if (role == Qt::DisplayPropertyRole) + prop->setAttribute(u"validationMode"_s, ValidationMultiLine); + else if (role == Qt::StatusTipPropertyRole) + prop->setAttribute(u"validationMode"_s, ValidationSingleLine); + else if (role == ItemFlagsShadowRole) + prop->setAttribute(u"flagNames"_s, c2qStringList(itemFlagNames)); + else if (role == Qt::CheckStateRole) + prop->setAttribute(u"enumNames"_s, c2qStringList(checkStateNames)); + prop->setAttribute(u"resettable"_s, true); + m_properties.append(prop); + m_rootProperties.append(prop); + m_propertyToRole.insert(prop, role); + } +} + +void AbstractItemEditor::setupObject(QWidget *object) +{ + m_propertyManager->setObject(object); + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(object); + FormWindowBase *fwb = qobject_cast(formWindow); + m_editorFactory->setFormWindowBase(fwb); +} + +void AbstractItemEditor::setupEditor(QWidget *object, + const PropertyDefinition *propList, + Qt::Alignment alignDefault) +{ + setupProperties(propList, alignDefault); + setupObject(object); +} + +void AbstractItemEditor::propertyChanged(QtProperty *property) +{ + if (m_updatingBrowser) + return; + + + BoolBlocker block(m_updatingBrowser); + QtVariantProperty *prop = m_propertyManager->variantProperty(property); + int role; + if ((role = m_propertyToRole.value(prop, -1)) == -1) + // Subproperty + return; + + if ((role == ItemFlagsShadowRole && prop->value().toInt() == defaultItemFlags()) + || (role == Qt::DecorationPropertyRole && !qvariant_cast(prop->value()).mask()) + || (role == Qt::FontRole && !qvariant_cast(prop->value()).resolveMask())) { + prop->setModified(false); + setItemData(role, QVariant()); + } else { + prop->setModified(true); + setItemData(role, prop->value()); + } + + switch (role) { + case Qt::DecorationPropertyRole: + setItemData(Qt::DecorationRole, QVariant::fromValue(iconCache()->icon(qvariant_cast(prop->value())))); + break; + case Qt::DisplayPropertyRole: + setItemData(Qt::EditRole, QVariant::fromValue(qvariant_cast(prop->value()).value())); + break; + case Qt::ToolTipPropertyRole: + setItemData(Qt::ToolTipRole, QVariant::fromValue(qvariant_cast(prop->value()).value())); + break; + case Qt::StatusTipPropertyRole: + setItemData(Qt::StatusTipRole, QVariant::fromValue(qvariant_cast(prop->value()).value())); + break; + case Qt::WhatsThisPropertyRole: + setItemData(Qt::WhatsThisRole, QVariant::fromValue(qvariant_cast(prop->value()).value())); + break; + default: + break; + } + + prop->setValue(getItemData(role)); +} + +void AbstractItemEditor::resetProperty(QtProperty *property) +{ + if (m_propertyManager->resetFontSubProperty(property)) + return; + + if (m_propertyManager->resetIconSubProperty(property)) + return; + + if (m_propertyManager->resetTextAlignmentProperty(property)) + return; + + BoolBlocker block(m_updatingBrowser); + + QtVariantProperty *prop = m_propertyManager->variantProperty(property); + int role = m_propertyToRole.value(prop); + if (role == ItemFlagsShadowRole) + prop->setValue(QVariant::fromValue(defaultItemFlags())); + else + prop->setValue(QVariant(QMetaType(prop->valueType()), nullptr)); + prop->setModified(false); + + setItemData(role, QVariant()); + if (role == Qt::DecorationPropertyRole) + setItemData(Qt::DecorationRole, QVariant::fromValue(QIcon())); + if (role == Qt::DisplayPropertyRole) + setItemData(Qt::EditRole, QVariant::fromValue(QString())); + if (role == Qt::ToolTipPropertyRole) + setItemData(Qt::ToolTipRole, QVariant::fromValue(QString())); + if (role == Qt::StatusTipPropertyRole) + setItemData(Qt::StatusTipRole, QVariant::fromValue(QString())); + if (role == Qt::WhatsThisPropertyRole) + setItemData(Qt::WhatsThisRole, QVariant::fromValue(QString())); +} + +void AbstractItemEditor::cacheReloaded() +{ + BoolBlocker block(m_updatingBrowser); + m_propertyManager->reloadResourceProperties(); +} + +void AbstractItemEditor::updateBrowser() +{ + BoolBlocker block(m_updatingBrowser); + for (QtVariantProperty *prop : std::as_const(m_properties)) { + int role = m_propertyToRole.value(prop); + QVariant val = getItemData(role); + + bool modified = false; + if (!val.isValid()) { + if (role == ItemFlagsShadowRole) + val = QVariant::fromValue(defaultItemFlags()); + else + val = QVariant(QMetaType(prop->value().userType()), nullptr); + } else { + modified = role != Qt::TextAlignmentRole + || val.toUInt() != DesignerPropertyManager::alignDefault(prop); + } + prop->setModified(modified); + prop->setValue(val); + } + + if (m_propertyBrowser->topLevelItems().isEmpty()) { + for (QtVariantProperty *prop : std::as_const(m_rootProperties)) + m_propertyBrowser->addProperty(prop); + } +} + +void AbstractItemEditor::injectPropertyBrowser(QWidget *parent, QWidget *widget) +{ + // It is impossible to design a splitter with just one widget, so we do it by hand. + m_propertySplitter = new QSplitter; + m_propertySplitter->addWidget(widget); + m_propertySplitter->addWidget(m_propertyBrowser); + m_propertySplitter->setStretchFactor(0, 1); + m_propertySplitter->setStretchFactor(1, 0); + parent->layout()->addWidget(m_propertySplitter); +} + +////////////////// List editor /////////////// +ItemListEditor::ItemListEditor(QDesignerFormWindowInterface *form, QWidget *parent) + : AbstractItemEditor(form, parent), + m_updating(false) +{ + ui.setupUi(this); + + injectPropertyBrowser(this, ui.widget); + connect(ui.showPropertiesButton, &QAbstractButton::clicked, + this, &ItemListEditor::togglePropertyBrowser); + + connect(ui.newListItemButton, &QAbstractButton::clicked, + this, &ItemListEditor::newListItemButtonClicked); + connect(ui.deleteListItemButton, &QAbstractButton::clicked, + this, &ItemListEditor::deleteListItemButtonClicked); + connect(ui.moveListItemUpButton, &QAbstractButton::clicked, + this, &ItemListEditor::moveListItemUpButtonClicked); + connect(ui.moveListItemDownButton, &QAbstractButton::clicked, + this, &ItemListEditor::moveListItemDownButtonClicked); + connect(ui.listWidget, &QListWidget::currentRowChanged, + this, &ItemListEditor::listWidgetCurrentRowChanged); + connect(ui.listWidget, &QListWidget::itemChanged, + this, &ItemListEditor::listWidgetItemChanged); + + setPropertyBrowserVisible(false); + + QIcon upIcon = createIconSet("up.png"_L1); + QIcon downIcon = createIconSet("down.png"_L1); + QIcon minusIcon = createIconSet("minus.png"_L1); + QIcon plusIcon = createIconSet("plus.png"_L1); + ui.moveListItemUpButton->setIcon(upIcon); + ui.moveListItemDownButton->setIcon(downIcon); + ui.newListItemButton->setIcon(plusIcon); + ui.deleteListItemButton->setIcon(minusIcon); + + connect(iconCache(), &DesignerIconCache::reloaded, this, &AbstractItemEditor::cacheReloaded); +} + +void ItemListEditor::setupEditor(QWidget *object, + const PropertyDefinition *propList, + Qt::Alignment alignDefault) +{ + AbstractItemEditor::setupEditor(object, propList, alignDefault); + + if (ui.listWidget->count() > 0) + ui.listWidget->setCurrentRow(0); + else + updateEditor(); +} + +void ItemListEditor::setCurrentIndex(int idx) +{ + m_updating = true; + ui.listWidget->setCurrentRow(idx); + m_updating = false; +} + +void ItemListEditor::newListItemButtonClicked() +{ + int row = ui.listWidget->currentRow() + 1; + + QListWidgetItem *item = new QListWidgetItem(m_newItemText); + item->setData(Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(m_newItemText))); + if (m_alignDefault != 0) + item->setTextAlignment(Qt::Alignment(m_alignDefault)); + item->setFlags(item->flags() | Qt::ItemIsEditable); + if (row < ui.listWidget->count()) + ui.listWidget->insertItem(row, item); + else + ui.listWidget->addItem(item); + emit itemInserted(row); + + ui.listWidget->setCurrentItem(item); + ui.listWidget->editItem(item); +} + +void ItemListEditor::deleteListItemButtonClicked() +{ + int row = ui.listWidget->currentRow(); + + if (row != -1) { + delete ui.listWidget->takeItem(row); + emit itemDeleted(row); + } + + if (row == ui.listWidget->count()) + row--; + if (row < 0) + updateEditor(); + else + ui.listWidget->setCurrentRow(row); +} + +void ItemListEditor::moveListItemUpButtonClicked() +{ + int row = ui.listWidget->currentRow(); + if (row <= 0) + return; // nothing to do + + ui.listWidget->insertItem(row - 1, ui.listWidget->takeItem(row)); + ui.listWidget->setCurrentRow(row - 1); + emit itemMovedUp(row); +} + +void ItemListEditor::moveListItemDownButtonClicked() +{ + int row = ui.listWidget->currentRow(); + if (row == -1 || row == ui.listWidget->count() - 1) + return; // nothing to do + + ui.listWidget->insertItem(row + 1, ui.listWidget->takeItem(row)); + ui.listWidget->setCurrentRow(row + 1); + emit itemMovedDown(row); +} + +void ItemListEditor::listWidgetCurrentRowChanged() +{ + updateEditor(); + if (!m_updating) + emit indexChanged(ui.listWidget->currentRow()); +} + +void ItemListEditor::listWidgetItemChanged(QListWidgetItem *item) +{ + if (m_updatingBrowser) + return; + + PropertySheetStringValue val = qvariant_cast(item->data(Qt::DisplayPropertyRole)); + val.setValue(item->text()); + BoolBlocker block(m_updatingBrowser); + item->setData(Qt::DisplayPropertyRole, QVariant::fromValue(val)); + + // The checkState could change, too, but if this signal is connected, + // checkState is not in the list anyway, as we are editing a header item. + emit itemChanged(ui.listWidget->currentRow(), Qt::DisplayPropertyRole, + QVariant::fromValue(val)); + updateBrowser(); +} + +void ItemListEditor::togglePropertyBrowser() +{ + setPropertyBrowserVisible(!m_propertyBrowser->isVisible()); +} + +void ItemListEditor::setPropertyBrowserVisible(bool v) +{ + ui.showPropertiesButton->setText(v ? tr("Properties &>>") : tr("Properties &<<")); + m_propertyBrowser->setVisible(v); +} + +void ItemListEditor::setItemData(int role, const QVariant &v) +{ + QListWidgetItem *item = ui.listWidget->currentItem(); + bool reLayout = false; + if ((role == Qt::EditRole + && (v.toString().count(u'\n') != item->data(role).toString().count(u'\n'))) + || role == Qt::FontRole) { + reLayout = true; + } + QVariant newValue = v; + if (role == Qt::FontRole && newValue.metaType().id() == QMetaType::QFont) { + QFont oldFont = ui.listWidget->font(); + QFont newFont = qvariant_cast(newValue).resolve(oldFont); + newValue = QVariant::fromValue(newFont); + item->setData(role, QVariant()); // force the right font with the current resolve mask is set (item view bug) + } + item->setData(role, newValue); + if (reLayout) + ui.listWidget->doItemsLayout(); + emit itemChanged(ui.listWidget->currentRow(), role, newValue); +} + +QVariant ItemListEditor::getItemData(int role) const +{ + return ui.listWidget->currentItem()->data(role); +} + +int ItemListEditor::defaultItemFlags() const +{ + static const int flags = QListWidgetItem().flags(); + return flags; +} + +void ItemListEditor::cacheReloaded() +{ + reloadIconResources(iconCache(), ui.listWidget); +} + +void ItemListEditor::updateEditor() +{ + bool currentItemEnabled = false; + + bool moveRowUpEnabled = false; + bool moveRowDownEnabled = false; + + QListWidgetItem *item = ui.listWidget->currentItem(); + if (item) { + currentItemEnabled = true; + int currentRow = ui.listWidget->currentRow(); + if (currentRow > 0) + moveRowUpEnabled = true; + if (currentRow < ui.listWidget->count() - 1) + moveRowDownEnabled = true; + } + + ui.moveListItemUpButton->setEnabled(moveRowUpEnabled); + ui.moveListItemDownButton->setEnabled(moveRowDownEnabled); + ui.deleteListItemButton->setEnabled(currentItemEnabled); + + if (item) + updateBrowser(); + else + m_propertyBrowser->clear(); +} + +uint ItemListEditor::alignDefault() const +{ + return m_alignDefault; +} + +void ItemListEditor::setAlignDefault(uint newAlignDefault) +{ + m_alignDefault = newAlignDefault; +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/itemlisteditor.h b/src/tools/designer/src/components/taskmenu/itemlisteditor.h new file mode 100644 index 00000000000..084a3294746 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/itemlisteditor.h @@ -0,0 +1,140 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ITEMLISTEDITOR_H +#define ITEMLISTEDITOR_H + +#include "ui_itemlisteditor.h" + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QtProperty; +class QtVariantProperty; +class QtTreePropertyBrowser; +class QSplitter; +class QVBoxLayout; + +namespace qdesigner_internal { + +class DesignerIconCache; +class DesignerPropertyManager; +class DesignerEditorFactory; + +// Utility class that ensures a bool is true while in scope. +// Courtesy of QBoolBlocker in qobject_p.h +class BoolBlocker +{ +public: + Q_DISABLE_COPY_MOVE(BoolBlocker); + + inline explicit BoolBlocker(bool &b) noexcept : block(b), reset(b) { block = true; } + inline ~BoolBlocker() noexcept { block = reset; } +private: + bool █ + bool reset; +}; + +class AbstractItemEditor: public QWidget +{ + Q_OBJECT + +public: + explicit AbstractItemEditor(QDesignerFormWindowInterface *form, QWidget *parent); + ~AbstractItemEditor(); + + DesignerIconCache *iconCache() const { return m_iconCache; } + + struct PropertyDefinition { + int role; + int type; + int (*typeFunc)(); + const char *name; + }; + +public slots: + void cacheReloaded(); + +private slots: + void propertyChanged(QtProperty *property); + void resetProperty(QtProperty *property); + +protected: + virtual int defaultItemFlags() const = 0; + void setupProperties(const PropertyDefinition *propList, + Qt::Alignment alignDefault = Qt::AlignLeading | Qt::AlignVCenter); + void setupObject(QWidget *object); + void setupEditor(QWidget *object, const PropertyDefinition *propDefs, + Qt::Alignment alignDefault = Qt::AlignLeading | Qt::AlignVCenter); + void injectPropertyBrowser(QWidget *parent, QWidget *widget); + void updateBrowser(); + virtual void setItemData(int role, const QVariant &v) = 0; + virtual QVariant getItemData(int role) const = 0; + + DesignerIconCache *m_iconCache; + DesignerPropertyManager *m_propertyManager; + DesignerEditorFactory *m_editorFactory; + QSplitter *m_propertySplitter = nullptr; + QtTreePropertyBrowser *m_propertyBrowser; + QList m_properties; + QList m_rootProperties; + QHash m_propertyToRole; + bool m_updatingBrowser = false; +}; + +class ItemListEditor: public AbstractItemEditor +{ + Q_OBJECT + +public: + explicit ItemListEditor(QDesignerFormWindowInterface *form, QWidget *parent); + + void setupEditor(QWidget *object, const PropertyDefinition *propDefs, + Qt::Alignment alignDefault = Qt::AlignLeading | Qt::AlignVCenter); + QListWidget *listWidget() const { return ui.listWidget; } + void setNewItemText(const QString &tpl) { m_newItemText = tpl; } + QString newItemText() const { return m_newItemText; } + void setCurrentIndex(int idx); + + uint alignDefault() const; + void setAlignDefault(uint newAlignDefault); + +signals: + void indexChanged(int idx); + void itemChanged(int idx, int role, const QVariant &v); + void itemInserted(int idx); + void itemDeleted(int idx); + void itemMovedUp(int idx); + void itemMovedDown(int idx); + +private slots: + void newListItemButtonClicked(); + void deleteListItemButtonClicked(); + void moveListItemUpButtonClicked(); + void moveListItemDownButtonClicked(); + void listWidgetCurrentRowChanged(); + void listWidgetItemChanged(QListWidgetItem * item); + void togglePropertyBrowser(); + void cacheReloaded(); + +protected: + void setItemData(int role, const QVariant &v) override; + QVariant getItemData(int role) const override; + int defaultItemFlags() const override; + +private: + void setPropertyBrowserVisible(bool v); + void updateEditor(); + Ui::ItemListEditor ui; + uint m_alignDefault = 0; + bool m_updating; + QString m_newItemText; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ITEMLISTEDITOR_H diff --git a/src/tools/designer/src/components/taskmenu/itemlisteditor.ui b/src/tools/designer/src/components/taskmenu/itemlisteditor.ui new file mode 100644 index 00000000000..75394acf691 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/itemlisteditor.ui @@ -0,0 +1,120 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::ItemListEditor + + + + 0 + 0 + 550 + 360 + + + + + + + + + + + 0 + + + + + true + + + Items List + + + + + + + + + New Item + + + &New + + + + + + + Delete Item + + + &Delete + + + + + + + Qt::Horizontal + + + + 16 + 10 + + + + + + + + Move Item Up + + + U + + + + + + + Move Item Down + + + D + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Properties &>> + + + + + + + + + + + + + diff --git a/src/tools/designer/src/components/taskmenu/label_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/label_taskmenu.cpp new file mode 100644 index 00000000000..14a58d8cef5 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/label_taskmenu.cpp @@ -0,0 +1,82 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "label_taskmenu.h" +#include "inplace_editor.h" + +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto textPropertyC = "text"_L1; + +namespace qdesigner_internal { + +// -------- LabelTaskMenuInlineEditor +class LabelTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + LabelTaskMenuInlineEditor(QLabel *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +LabelTaskMenuInlineEditor::LabelTaskMenuInlineEditor(QLabel *w, QObject *parent) : + TaskMenuInlineEditor(w, ValidationRichText, textPropertyC, parent) +{ +} + +QRect LabelTaskMenuInlineEditor::editRectangle() const +{ + QStyleOptionButton opt; + opt.initFrom(widget()); + return opt.rect; +} + +// --------------- LabelTaskMenu + +LabelTaskMenu::LabelTaskMenu(QLabel *label, QObject *parent) + : QDesignerTaskMenu(label, parent), + m_label(label), + m_editRichTextAction(new QAction(tr("Change rich text..."), this)), + m_editPlainTextAction(new QAction(tr("Change plain text..."), this)) +{ + LabelTaskMenuInlineEditor *editor = new LabelTaskMenuInlineEditor(label, this); + connect(m_editPlainTextAction, &QAction::triggered, editor, &LabelTaskMenuInlineEditor::editText); + m_taskActions.append(m_editPlainTextAction); + + connect(m_editRichTextAction, &QAction::triggered, this, &LabelTaskMenu::editRichText); + m_taskActions.append(m_editRichTextAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +QAction *LabelTaskMenu::preferredEditAction() const +{ + if (m_label->textFormat () == Qt::PlainText) return m_editPlainTextAction; + return Qt::mightBeRichText(m_label->text()) ? m_editRichTextAction : m_editPlainTextAction; +} + +QList LabelTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void LabelTaskMenu::editRichText() +{ + changeTextProperty(textPropertyC, QString(), MultiSelectionMode, m_label->textFormat()); +} + +} +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/label_taskmenu.h b/src/tools/designer/src/components/taskmenu/label_taskmenu.h new file mode 100644 index 00000000000..b7aed921f7e --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/label_taskmenu.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LABEL_TASKMENU_H +#define LABEL_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class LabelTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit LabelTaskMenu(QLabel *button, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editRichText(); + +private: + QLabel *m_label; + QList m_taskActions; + QAction *m_editRichTextAction; + QAction *m_editPlainTextAction; +}; + +using LabelTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LABEL_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/layouttaskmenu.cpp b/src/tools/designer/src/components/taskmenu/layouttaskmenu.cpp new file mode 100644 index 00000000000..256fd291212 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/layouttaskmenu.cpp @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "layouttaskmenu.h" +#include +#include + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +// ------------ LayoutWidgetTaskMenu +LayoutWidgetTaskMenu::LayoutWidgetTaskMenu(QLayoutWidget *lw, QObject *parent) : + QObject(parent), + m_widget(lw), + m_morphMenu(new qdesigner_internal::MorphMenu(this)), + m_formLayoutMenu(new qdesigner_internal::FormLayoutMenu(this)) +{ +} + +QAction *LayoutWidgetTaskMenu::preferredEditAction() const +{ + return m_formLayoutMenu->preferredEditAction(m_widget, m_widget->formWindow()); +} + +QList LayoutWidgetTaskMenu::taskActions() const +{ + QList rc; + QDesignerFormWindowInterface *fw = m_widget->formWindow(); + m_morphMenu->populate(m_widget, fw, rc); + m_formLayoutMenu->populate(m_widget, fw, rc); + return rc; +} + +// ------------- SpacerTaskMenu +SpacerTaskMenu::SpacerTaskMenu(Spacer *, QObject *parent) : + QObject(parent) +{ +} + +QAction *SpacerTaskMenu::preferredEditAction() const +{ + return nullptr; +} + +QList SpacerTaskMenu::taskActions() const +{ + return {}; +} + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/components/taskmenu/layouttaskmenu.h b/src/tools/designer/src/components/taskmenu/layouttaskmenu.h new file mode 100644 index 00000000000..d0b37407e88 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/layouttaskmenu.h @@ -0,0 +1,55 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LAYOUTTASKMENU_H +#define LAYOUTTASKMENU_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + class FormLayoutMenu; + class MorphMenu; +} + +// Morph menu for QLayoutWidget. +class LayoutWidgetTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit LayoutWidgetTaskMenu(QLayoutWidget *w, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QLayoutWidget *m_widget; + qdesigner_internal::MorphMenu *m_morphMenu; + qdesigner_internal::FormLayoutMenu *m_formLayoutMenu; +}; + +// Empty task menu for spacers. +class SpacerTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit SpacerTaskMenu(Spacer *bar, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +}; + +using LayoutWidgetTaskMenuFactory = qdesigner_internal::ExtensionFactory; +using SpacerTaskMenuFactory = qdesigner_internal::ExtensionFactory; + +QT_END_NAMESPACE + +#endif // LAYOUTTASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/lineedit_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/lineedit_taskmenu.cpp new file mode 100644 index 00000000000..88dcf456a99 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/lineedit_taskmenu.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "lineedit_taskmenu.h" +#include "inplace_editor.h" + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// -------- LineEditTaskMenuInlineEditor +class LineEditTaskMenuInlineEditor : public TaskMenuInlineEditor +{ +public: + LineEditTaskMenuInlineEditor(QLineEdit *button, QObject *parent); + +protected: + QRect editRectangle() const override; +}; + +LineEditTaskMenuInlineEditor::LineEditTaskMenuInlineEditor(QLineEdit *w, QObject *parent) : + TaskMenuInlineEditor(w, ValidationSingleLine, u"text"_s, parent) +{ +} + +QRect LineEditTaskMenuInlineEditor::editRectangle() const +{ + QStyleOption opt; + opt.initFrom(widget()); + return opt.rect; +} + +// --------------- LineEditTaskMenu +LineEditTaskMenu::LineEditTaskMenu(QLineEdit *lineEdit, QObject *parent) : + QDesignerTaskMenu(lineEdit, parent), + m_editTextAction(new QAction(tr("Change text..."), this)) +{ + TaskMenuInlineEditor *editor = new LineEditTaskMenuInlineEditor(lineEdit, this); + connect(m_editTextAction, &QAction::triggered, editor, &LineEditTaskMenuInlineEditor::editText); + m_taskActions.append(m_editTextAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +QAction *LineEditTaskMenu::preferredEditAction() const +{ + return m_editTextAction; +} + +QList LineEditTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/lineedit_taskmenu.h b/src/tools/designer/src/components/taskmenu/lineedit_taskmenu.h new file mode 100644 index 00000000000..efc0002754a --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/lineedit_taskmenu.h @@ -0,0 +1,36 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LINEEDIT_TASKMENU_H +#define LINEEDIT_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class LineEditTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit LineEditTaskMenu(QLineEdit *button, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QList m_taskActions; + QAction *m_editTextAction; +}; + +using LineEditTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LINEEDIT_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/listwidget_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/listwidget_taskmenu.cpp new file mode 100644 index 00000000000..4bb08c4c2a6 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/listwidget_taskmenu.cpp @@ -0,0 +1,80 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "listwidget_taskmenu.h" +#include "listwidgeteditor.h" +#include "qdesigner_utils_p.h" +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +ListWidgetTaskMenu::ListWidgetTaskMenu(QListWidget *button, QObject *parent) + : QDesignerTaskMenu(button, parent), + m_listWidget(button) +{ + m_editItemsAction = new QAction(this); + m_editItemsAction->setText(tr("Edit Items...")); + connect(m_editItemsAction, &QAction::triggered, this, &ListWidgetTaskMenu::editItems); + m_taskActions.append(m_editItemsAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +ListWidgetTaskMenu::~ListWidgetTaskMenu() = default; + +QAction *ListWidgetTaskMenu::preferredEditAction() const +{ + return m_editItemsAction; +} + +QList ListWidgetTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void ListWidgetTaskMenu::editItems() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_listWidget); + if (m_formWindow.isNull()) + return; + + Q_ASSERT(m_listWidget != nullptr); + + ListWidgetEditor dlg(m_formWindow, m_listWidget->window()); + ListContents oldItems = dlg.fillContentsFromListWidget(m_listWidget); + if (dlg.exec() == QDialog::Accepted) { + ListContents items = dlg.contents(); + if (items != oldItems) { + ChangeListContentsCommand *cmd = new ChangeListContentsCommand(m_formWindow); + cmd->init(m_listWidget, oldItems, items); + cmd->setText(tr("Change List Contents")); + m_formWindow->commandHistory()->push(cmd); + } + } +} + +void ListWidgetTaskMenu::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/listwidget_taskmenu.h b/src/tools/designer/src/components/taskmenu/listwidget_taskmenu.h new file mode 100644 index 00000000000..a08d4b6c692 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/listwidget_taskmenu.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LISTWIDGET_TASKMENU_H +#define LISTWIDGET_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QLineEdit; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class ListWidgetTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit ListWidgetTaskMenu(QListWidget *button, QObject *parent = nullptr); + ~ListWidgetTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editItems(); + void updateSelection(); + +private: + QListWidget *m_listWidget; + QPointer m_formWindow; + QPointer m_editor; + mutable QList m_taskActions; + QAction *m_editItemsAction; +}; + +using ListWidgetTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LISTWIDGET_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/listwidgeteditor.cpp b/src/tools/designer/src/components/taskmenu/listwidgeteditor.cpp new file mode 100644 index 00000000000..84e9b0a5d12 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/listwidgeteditor.cpp @@ -0,0 +1,101 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "listwidgeteditor.h" +#include +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +ListWidgetEditor::ListWidgetEditor(QDesignerFormWindowInterface *form, + QWidget *parent) + : QDialog(parent) +{ + QDialogButtonBox *buttonBox = new QDialogButtonBox; + buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + m_itemsEditor = new ItemListEditor(form, nullptr); + m_itemsEditor->layout()->setContentsMargins(QMargins()); + m_itemsEditor->setNewItemText(tr("New Item")); + + QFrame *sep = new QFrame; + sep->setFrameStyle(QFrame::HLine | QFrame::Sunken); + + QBoxLayout *box = new QVBoxLayout(this); + box->addWidget(m_itemsEditor); + box->addWidget(sep); + box->addWidget(buttonBox); + + // Numbers copied from itemlisteditor.ui + // (Automatic resizing doesn't work because ui has parent). + resize(550, 360); +} + +static AbstractItemEditor::PropertyDefinition listBoxPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, + { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QBrush, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { ItemFlagsShadowRole, 0, QtVariantPropertyManager::flagTypeId, "flags" }, + { Qt::CheckStateRole, 0, QtVariantPropertyManager::enumTypeId, "checkState" }, + { 0, 0, nullptr, nullptr } +}; + +ListContents ListWidgetEditor::fillContentsFromListWidget(QListWidget *listWidget) +{ + setWindowTitle(tr("Edit List Widget")); + + ListContents retVal; + retVal.createFromListWidget(listWidget, false); + retVal.applyToListWidget(m_itemsEditor->listWidget(), m_itemsEditor->iconCache(), true); + + m_itemsEditor->setupEditor(listWidget, listBoxPropList); + + return retVal; +} + +static AbstractItemEditor::PropertyDefinition comboBoxPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { 0, 0, nullptr, nullptr } +}; + +ListContents ListWidgetEditor::fillContentsFromComboBox(QComboBox *comboBox) +{ + setWindowTitle(tr("Edit Combobox")); + + ListContents retVal; + retVal.createFromComboBox(comboBox); + retVal.applyToListWidget(m_itemsEditor->listWidget(), m_itemsEditor->iconCache(), true); + + m_itemsEditor->setupEditor(comboBox, comboBoxPropList); + + return retVal; +} + +ListContents ListWidgetEditor::contents() const +{ + ListContents retVal; + retVal.createFromListWidget(m_itemsEditor->listWidget(), true); + return retVal; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/listwidgeteditor.h b/src/tools/designer/src/components/taskmenu/listwidgeteditor.h new file mode 100644 index 00000000000..b16f63b1fad --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/listwidgeteditor.h @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LISTWIDGETEDITOR_H +#define LISTWIDGETEDITOR_H + +#include "itemlisteditor.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +class QListWidget; +class QComboBox; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class ListWidgetEditor: public QDialog +{ + Q_OBJECT + +public: + ListWidgetEditor(QDesignerFormWindowInterface *form, + QWidget *parent); + + ListContents fillContentsFromListWidget(QListWidget *listWidget); + ListContents fillContentsFromComboBox(QComboBox *comboBox); + ListContents contents() const; + +private: + ItemListEditor *m_itemsEditor; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LISTWIDGETEDITOR_H diff --git a/src/tools/designer/src/components/taskmenu/menutaskmenu.cpp b/src/tools/designer/src/components/taskmenu/menutaskmenu.cpp new file mode 100644 index 00000000000..6b95a1bf4e6 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/menutaskmenu.cpp @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "menutaskmenu.h" + +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + // ------------ MenuTaskMenu + MenuTaskMenu::MenuTaskMenu(QDesignerMenu *menu, QObject *parent) : + QObject(parent), + m_menu(menu), + m_removeAction(new QAction(tr("Remove"), this)), + m_promotionTaskMenu(new PromotionTaskMenu(menu, PromotionTaskMenu::ModeSingleWidget, this)) + { + connect(m_removeAction, &QAction::triggered, this, &MenuTaskMenu::removeMenu); + } + + QAction *MenuTaskMenu::preferredEditAction() const + { + return nullptr; + } + + QList MenuTaskMenu::taskActions() const + { + QList rc; + rc.push_back(m_removeAction); + m_promotionTaskMenu->addActions(PromotionTaskMenu::LeadingSeparator, rc); + return rc; + } + + void MenuTaskMenu::removeMenu() + { + // Are we on a menu bar or on a menu? + QWidget *pw = m_menu->parentWidget(); + if (QDesignerMenuBar *mb = qobject_cast(pw)) { + mb->deleteMenuAction(m_menu->menuAction()); + return; + } + if (QDesignerMenu *m = qobject_cast(pw)) { + m->deleteAction(m_menu->menuAction()); + } + } + + // ------------- MenuBarTaskMenu + MenuBarTaskMenu::MenuBarTaskMenu(QDesignerMenuBar *bar, QObject *parent) : + QObject(parent), + m_bar(bar) + { + } + + QAction *MenuBarTaskMenu::preferredEditAction() const + { + return nullptr; + } + + QList MenuBarTaskMenu::taskActions() const + { + return m_bar->contextMenuActions(); + } +} + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/components/taskmenu/menutaskmenu.h b/src/tools/designer/src/components/taskmenu/menutaskmenu.h new file mode 100644 index 00000000000..1d290d43b40 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/menutaskmenu.h @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MENUTASKMENU_H +#define MENUTASKMENU_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + + class PromotionTaskMenu; + +// The QMenu task menu provides promotion and a remove option. The actual +// menu context options are not forwarded since they make only sense +// when a menu is being edited/visible. + +class MenuTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit MenuTaskMenu(QDesignerMenu *menu, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void removeMenu(); + +private: + QDesignerMenu *m_menu; + QAction *m_removeAction; + PromotionTaskMenu *m_promotionTaskMenu; +}; + +// The QMenuBar task menu forwards the actions of QDesignerMenuBar, +// making them available in the object inspector. + +class MenuBarTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit MenuBarTaskMenu(QDesignerMenuBar *bar, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QDesignerMenuBar *m_bar; +}; + +using MenuTaskMenuFactory = ExtensionFactory; +using MenuBarTaskMenuFactory = ExtensionFactory; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // MENUTASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/tablewidget_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/tablewidget_taskmenu.cpp new file mode 100644 index 00000000000..9ace1eacfb3 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/tablewidget_taskmenu.cpp @@ -0,0 +1,78 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tablewidget_taskmenu.h" +#include "tablewidgeteditor.h" + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +TableWidgetTaskMenu::TableWidgetTaskMenu(QTableWidget *button, QObject *parent) + : QDesignerTaskMenu(button, parent), + m_tableWidget(button), + m_editItemsAction(new QAction(tr("Edit Items..."), this)) +{ + connect(m_editItemsAction, &QAction::triggered, this, &TableWidgetTaskMenu::editItems); + m_taskActions.append(m_editItemsAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + + +TableWidgetTaskMenu::~TableWidgetTaskMenu() = default; + +QAction *TableWidgetTaskMenu::preferredEditAction() const +{ + return m_editItemsAction; +} + +QList TableWidgetTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void TableWidgetTaskMenu::editItems() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_tableWidget); + if (m_formWindow.isNull()) + return; + + Q_ASSERT(m_tableWidget != nullptr); + + TableWidgetEditorDialog dlg(m_formWindow, m_tableWidget->window()); + TableWidgetContents oldCont = dlg.fillContentsFromTableWidget(m_tableWidget); + if (dlg.exec() == QDialog::Accepted) { + TableWidgetContents newCont = dlg.contents(); + if (newCont != oldCont) { + ChangeTableContentsCommand *cmd = new ChangeTableContentsCommand(m_formWindow); + cmd->init(m_tableWidget, oldCont, newCont); + m_formWindow->commandHistory()->push(cmd); + } + } +} + +void TableWidgetTaskMenu::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/tablewidget_taskmenu.h b/src/tools/designer/src/components/taskmenu/tablewidget_taskmenu.h new file mode 100644 index 00000000000..6c3c79c3cbc --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/tablewidget_taskmenu.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABLEWIDGET_TASKMENU_H +#define TABLEWIDGET_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QLineEdit; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class TableWidgetTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit TableWidgetTaskMenu(QTableWidget *button, QObject *parent = nullptr); + ~TableWidgetTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editItems(); + void updateSelection(); + +private: + QTableWidget *m_tableWidget; + QPointer m_formWindow; + QPointer m_editor; + mutable QList m_taskActions; + QAction *m_editItemsAction; +}; + +using TableWidgetTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TABLEWIDGET_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/tablewidgeteditor.cpp b/src/tools/designer/src/components/taskmenu/tablewidgeteditor.cpp new file mode 100644 index 00000000000..8664fd559fa --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/tablewidgeteditor.cpp @@ -0,0 +1,427 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "tablewidgeteditor.h" +#include +#include +#include +#include "formwindowbase_p.h" +#include "qdesigner_utils_p.h" +#include +#include + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TableWidgetEditor::TableWidgetEditor(QDesignerFormWindowInterface *form, QDialog *dialog) + : AbstractItemEditor(form, nullptr), m_updatingBrowser(false) +{ + m_columnEditor = new ItemListEditor(form, this); + m_columnEditor->setObjectName(u"columnEditor"_s); + m_columnEditor->setAlignDefault(Qt::AlignCenter); + m_columnEditor->setNewItemText(tr("New Column")); + m_rowEditor = new ItemListEditor(form, this); + m_rowEditor->setObjectName(u"rowEditor"_s); + m_rowEditor->setNewItemText(tr("New Row")); + ui.setupUi(dialog); + + injectPropertyBrowser(ui.itemsTab, ui.widget); + connect(ui.showPropertiesButton, &QAbstractButton::clicked, + this, &TableWidgetEditor::togglePropertyBrowser); + setPropertyBrowserVisible(false); + + ui.tabWidget->insertTab(0, m_columnEditor, tr("&Columns")); + ui.tabWidget->insertTab(1, m_rowEditor, tr("&Rows")); + ui.tabWidget->setCurrentIndex(0); + + ui.tableWidget->setSelectionMode(QAbstractItemView::SingleSelection); + + connect(iconCache(), &DesignerIconCache::reloaded, this, &TableWidgetEditor::cacheReloaded); + + connect(ui.tableWidget, &QTableWidget::currentCellChanged, + this, &TableWidgetEditor::tableWidgetCurrentCellChanged); + connect(ui.tableWidget, &QTableWidget::itemChanged, + this, &TableWidgetEditor::tableWidgetItemChanged); + connect(m_columnEditor, &ItemListEditor::indexChanged, + this, &TableWidgetEditor::columnEditorIndexChanged); + connect(m_columnEditor, &ItemListEditor::itemChanged, + this, &TableWidgetEditor::columnEditorItemChanged); + connect(m_columnEditor, &ItemListEditor::itemInserted, + this, &TableWidgetEditor::columnEditorItemInserted); + connect(m_columnEditor, &ItemListEditor::itemDeleted, + this, &TableWidgetEditor::columnEditorItemDeleted); + connect(m_columnEditor, &ItemListEditor::itemMovedUp, + this, &TableWidgetEditor::columnEditorItemMovedUp); + connect(m_columnEditor, &ItemListEditor::itemMovedDown, + this, &TableWidgetEditor::columnEditorItemMovedDown); + + connect(m_rowEditor, &ItemListEditor::indexChanged, + this, &TableWidgetEditor::rowEditorIndexChanged); + connect(m_rowEditor, &ItemListEditor::itemChanged, + this, &TableWidgetEditor::rowEditorItemChanged); + connect(m_rowEditor, &ItemListEditor::itemInserted, + this, &TableWidgetEditor::rowEditorItemInserted); + connect(m_rowEditor, &ItemListEditor::itemDeleted, + this, &TableWidgetEditor::rowEditorItemDeleted); + connect(m_rowEditor, &ItemListEditor::itemMovedUp, + this, &TableWidgetEditor::rowEditorItemMovedUp); + connect(m_rowEditor, &ItemListEditor::itemMovedDown, + this, &TableWidgetEditor::rowEditorItemMovedDown); +} + +static AbstractItemEditor::PropertyDefinition tableHeaderPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, +// { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QColor, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { 0, 0, nullptr, nullptr } +}; + +static AbstractItemEditor::PropertyDefinition tableItemPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, +// { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QBrush, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { ItemFlagsShadowRole, 0, QtVariantPropertyManager::flagTypeId, "flags" }, + { Qt::CheckStateRole, 0, QtVariantPropertyManager::enumTypeId, "checkState" }, + { 0, 0, nullptr, nullptr } +}; + +TableWidgetContents TableWidgetEditor::fillContentsFromTableWidget(QTableWidget *tableWidget) +{ + TableWidgetContents tblCont; + tblCont.fromTableWidget(tableWidget, false); + tblCont.applyToTableWidget(ui.tableWidget, iconCache(), true); + + auto *header = tableWidget->verticalHeader(); + auto headerAlignment = header != nullptr + ? header->defaultAlignment() : Qt::Alignment(Qt::AlignLeading | Qt::AlignVCenter); + tblCont.m_verticalHeader.applyToListWidget(m_rowEditor->listWidget(), iconCache(), + true, headerAlignment); + m_rowEditor->setupEditor(tableWidget, tableHeaderPropList, headerAlignment); + + header = tableWidget->horizontalHeader(); + headerAlignment = header != nullptr + ? header->defaultAlignment() : Qt::Alignment(Qt::AlignCenter); + tblCont.m_horizontalHeader.applyToListWidget(m_columnEditor->listWidget(), iconCache(), + true, headerAlignment); + m_columnEditor->setupEditor(tableWidget, tableHeaderPropList, headerAlignment); + + setupEditor(tableWidget, tableItemPropList); + if (ui.tableWidget->columnCount() > 0 && ui.tableWidget->rowCount() > 0) + ui.tableWidget->setCurrentCell(0, 0); + + updateEditor(); + + return tblCont; +} + +TableWidgetContents TableWidgetEditor::contents() const +{ + TableWidgetContents retVal; + retVal.fromTableWidget(ui.tableWidget, true); + return retVal; +} + +void TableWidgetEditor::setItemData(int role, const QVariant &v) +{ + QTableWidgetItem *item = ui.tableWidget->currentItem(); + BoolBlocker block(m_updatingBrowser); + if (!item) { + item = new QTableWidgetItem; + ui.tableWidget->setItem(ui.tableWidget->currentRow(), ui.tableWidget->currentColumn(), item); + } + QVariant newValue = v; + if (role == Qt::FontRole && newValue.metaType().id() == QMetaType::QFont) { + QFont oldFont = ui.tableWidget->font(); + QFont newFont = qvariant_cast(newValue).resolve(oldFont); + newValue = QVariant::fromValue(newFont); + item->setData(role, QVariant()); // force the right font with the current resolve mask is set (item view bug) + } + item->setData(role, newValue); +} + +QVariant TableWidgetEditor::getItemData(int role) const +{ + QTableWidgetItem *item = ui.tableWidget->currentItem(); + if (!item) + return QVariant(); + return item->data(role); +} + +int TableWidgetEditor::defaultItemFlags() const +{ + static const int flags = QTableWidgetItem().flags(); + return flags; +} + +void TableWidgetEditor::tableWidgetCurrentCellChanged(int currentRow, int currentCol) +{ + m_rowEditor->setCurrentIndex(currentRow); + m_columnEditor->setCurrentIndex(currentCol); + updateBrowser(); +} + +void TableWidgetEditor::tableWidgetItemChanged(QTableWidgetItem *item) +{ + if (m_updatingBrowser) + return; + + PropertySheetStringValue val = qvariant_cast(item->data(Qt::DisplayPropertyRole)); + val.setValue(item->text()); + BoolBlocker block(m_updatingBrowser); + item->setData(Qt::DisplayPropertyRole, QVariant::fromValue(val)); + + updateBrowser(); +} + +void TableWidgetEditor::columnEditorIndexChanged(int col) +{ + ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow(), col); +} + +void TableWidgetEditor::columnEditorItemChanged(int idx, int role, const QVariant &v) +{ + ui.tableWidget->horizontalHeaderItem(idx)->setData(role, v); +} + +void TableWidgetEditor::rowEditorIndexChanged(int col) +{ + ui.tableWidget->setCurrentCell(col, ui.tableWidget->currentColumn()); +} + +void TableWidgetEditor::rowEditorItemChanged(int idx, int role, const QVariant &v) +{ + ui.tableWidget->verticalHeaderItem(idx)->setData(role, v); +} + +void TableWidgetEditor::setPropertyBrowserVisible(bool v) +{ + ui.showPropertiesButton->setText(v ? tr("Properties &>>") : tr("Properties &<<")); + m_propertyBrowser->setVisible(v); +} + +void TableWidgetEditor::togglePropertyBrowser() +{ + setPropertyBrowserVisible(!m_propertyBrowser->isVisible()); +} + +void TableWidgetEditor::updateEditor() +{ + const bool wasEnabled = ui.tabWidget->isTabEnabled(2); + const bool isEnabled = ui.tableWidget->columnCount() && ui.tableWidget->rowCount(); + ui.tabWidget->setTabEnabled(2, isEnabled); + if (!wasEnabled && isEnabled) + ui.tableWidget->setCurrentCell(0, 0); + + QMetaObject::invokeMethod(ui.tableWidget, "updateGeometries"); + ui.tableWidget->viewport()->update(); +} + +void TableWidgetEditor::moveColumnsLeft(int fromColumn, int toColumn) +{ + if (fromColumn >= toColumn) + return; + + QTableWidgetItem *lastItem = ui.tableWidget->takeHorizontalHeaderItem(toColumn); + for (int i = toColumn; i > fromColumn; i--) { + ui.tableWidget->setHorizontalHeaderItem(i, + ui.tableWidget->takeHorizontalHeaderItem(i - 1)); + } + ui.tableWidget->setHorizontalHeaderItem(fromColumn, lastItem); + + for (int i = 0; i < ui.tableWidget->rowCount(); i++) { + QTableWidgetItem *lastItem = ui.tableWidget->takeItem(i, toColumn); + for (int j = toColumn; j > fromColumn; j--) + ui.tableWidget->setItem(i, j, ui.tableWidget->takeItem(i, j - 1)); + ui.tableWidget->setItem(i, fromColumn, lastItem); + } +} + +void TableWidgetEditor::moveColumnsRight(int fromColumn, int toColumn) +{ + if (fromColumn >= toColumn) + return; + + QTableWidgetItem *lastItem = ui.tableWidget->takeHorizontalHeaderItem(fromColumn); + for (int i = fromColumn; i < toColumn; i++) { + ui.tableWidget->setHorizontalHeaderItem(i, + ui.tableWidget->takeHorizontalHeaderItem(i + 1)); + } + ui.tableWidget->setHorizontalHeaderItem(toColumn, lastItem); + + for (int i = 0; i < ui.tableWidget->rowCount(); i++) { + QTableWidgetItem *lastItem = ui.tableWidget->takeItem(i, fromColumn); + for (int j = fromColumn; j < toColumn; j++) + ui.tableWidget->setItem(i, j, ui.tableWidget->takeItem(i, j + 1)); + ui.tableWidget->setItem(i, toColumn, lastItem); + } +} + +void TableWidgetEditor::moveRowsDown(int fromRow, int toRow) +{ + if (fromRow >= toRow) + return; + + QTableWidgetItem *lastItem = ui.tableWidget->takeVerticalHeaderItem(toRow); + for (int i = toRow; i > fromRow; i--) { + ui.tableWidget->setVerticalHeaderItem(i, + ui.tableWidget->takeVerticalHeaderItem(i - 1)); + } + ui.tableWidget->setVerticalHeaderItem(fromRow, lastItem); + + for (int i = 0; i < ui.tableWidget->columnCount(); i++) { + QTableWidgetItem *lastItem = ui.tableWidget->takeItem(toRow, i); + for (int j = toRow; j > fromRow; j--) + ui.tableWidget->setItem(j, i, ui.tableWidget->takeItem(j - 1, i)); + ui.tableWidget->setItem(fromRow, i, lastItem); + } +} + +void TableWidgetEditor::moveRowsUp(int fromRow, int toRow) +{ + if (fromRow >= toRow) + return; + + QTableWidgetItem *lastItem = ui.tableWidget->takeVerticalHeaderItem(fromRow); + for (int i = fromRow; i < toRow; i++) { + ui.tableWidget->setVerticalHeaderItem(i, + ui.tableWidget->takeVerticalHeaderItem(i + 1)); + } + ui.tableWidget->setVerticalHeaderItem(toRow, lastItem); + + for (int i = 0; i < ui.tableWidget->columnCount(); i++) { + QTableWidgetItem *lastItem = ui.tableWidget->takeItem(fromRow, i); + for (int j = fromRow; j < toRow; j++) + ui.tableWidget->setItem(j, i, ui.tableWidget->takeItem(j + 1, i)); + ui.tableWidget->setItem(toRow, i, lastItem); + } +} + +void TableWidgetEditor::columnEditorItemInserted(int idx) +{ + const int columnCount = ui.tableWidget->columnCount(); + ui.tableWidget->setColumnCount(columnCount + 1); + + QTableWidgetItem *newItem = new QTableWidgetItem(m_columnEditor->newItemText()); + newItem->setData(Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(m_columnEditor->newItemText()))); + ui.tableWidget->setHorizontalHeaderItem(columnCount, newItem); + + moveColumnsLeft(idx, columnCount); + + int row = ui.tableWidget->currentRow(); + if (row >= 0) + ui.tableWidget->setCurrentCell(row, idx); + + updateEditor(); +} + +void TableWidgetEditor::columnEditorItemDeleted(int idx) +{ + const int columnCount = ui.tableWidget->columnCount(); + + moveColumnsRight(idx, columnCount - 1); + ui.tableWidget->setColumnCount(columnCount - 1); + + updateEditor(); +} + +void TableWidgetEditor::columnEditorItemMovedUp(int idx) +{ + moveColumnsRight(idx - 1, idx); + + ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow(), idx - 1); +} + +void TableWidgetEditor::columnEditorItemMovedDown(int idx) +{ + moveColumnsLeft(idx, idx + 1); + + ui.tableWidget->setCurrentCell(ui.tableWidget->currentRow(), idx + 1); +} + +void TableWidgetEditor::rowEditorItemInserted(int idx) +{ + const int rowCount = ui.tableWidget->rowCount(); + ui.tableWidget->setRowCount(rowCount + 1); + + QTableWidgetItem *newItem = new QTableWidgetItem(m_rowEditor->newItemText()); + newItem->setData(Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(m_rowEditor->newItemText()))); + ui.tableWidget->setVerticalHeaderItem(rowCount, newItem); + + moveRowsDown(idx, rowCount); + + int col = ui.tableWidget->currentColumn(); + if (col >= 0) + ui.tableWidget->setCurrentCell(idx, col); + + updateEditor(); +} + +void TableWidgetEditor::rowEditorItemDeleted(int idx) +{ + const int rowCount = ui.tableWidget->rowCount(); + + moveRowsUp(idx, rowCount - 1); + ui.tableWidget->setRowCount(rowCount - 1); + + updateEditor(); +} + +void TableWidgetEditor::rowEditorItemMovedUp(int idx) +{ + moveRowsUp(idx - 1, idx); + + ui.tableWidget->setCurrentCell(idx - 1, ui.tableWidget->currentColumn()); +} + +void TableWidgetEditor::rowEditorItemMovedDown(int idx) +{ + moveRowsDown(idx, idx + 1); + + ui.tableWidget->setCurrentCell(idx + 1, ui.tableWidget->currentColumn()); +} + +void TableWidgetEditor::cacheReloaded() +{ + reloadIconResources(iconCache(), ui.tableWidget); +} + +TableWidgetEditorDialog::TableWidgetEditorDialog(QDesignerFormWindowInterface *form, QWidget *parent) : + QDialog(parent), m_editor(form, this) +{ +} + +TableWidgetContents TableWidgetEditorDialog::fillContentsFromTableWidget(QTableWidget *tableWidget) +{ + return m_editor.fillContentsFromTableWidget(tableWidget); +} + +TableWidgetContents TableWidgetEditorDialog::contents() const +{ + return m_editor.contents(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/tablewidgeteditor.h b/src/tools/designer/src/components/taskmenu/tablewidgeteditor.h new file mode 100644 index 00000000000..defc05f3c9f --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/tablewidgeteditor.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TABLEWIDGETEDITOR_H +#define TABLEWIDGETEDITOR_H + +#include "ui_tablewidgeteditor.h" + +#include "listwidgeteditor.h" + +#include + +QT_BEGIN_NAMESPACE + +class QTableWidget; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class FormWindowBase; +class PropertySheetIconValue; + +class TableWidgetEditor: public AbstractItemEditor +{ + Q_OBJECT +public: + explicit TableWidgetEditor(QDesignerFormWindowInterface *form, QDialog *dialog); + + TableWidgetContents fillContentsFromTableWidget(QTableWidget *tableWidget); + TableWidgetContents contents() const; + +private slots: + + void tableWidgetCurrentCellChanged(int currentRow, int currentCol); + void tableWidgetItemChanged(QTableWidgetItem *item); + + void columnEditorIndexChanged(int idx); + void columnEditorItemChanged(int idx, int role, const QVariant &v); + + void columnEditorItemInserted(int idx); + void columnEditorItemDeleted(int idx); + void columnEditorItemMovedUp(int idx); + void columnEditorItemMovedDown(int idx); + + void rowEditorIndexChanged(int idx); + void rowEditorItemChanged(int idx, int role, const QVariant &v); + + void rowEditorItemInserted(int idx); + void rowEditorItemDeleted(int idx); + void rowEditorItemMovedUp(int idx); + void rowEditorItemMovedDown(int idx); + + void togglePropertyBrowser(); + + void cacheReloaded(); + +protected: + void setItemData(int role, const QVariant &v) override; + QVariant getItemData(int role) const override; + int defaultItemFlags() const override; + +private: + void setPropertyBrowserVisible(bool v); + void updateEditor(); + void moveColumnsLeft(int fromColumn, int toColumn); + void moveColumnsRight(int fromColumn, int toColumn); + void moveRowsUp(int fromRow, int toRow); + void moveRowsDown(int fromRow, int toRow); + + Ui::TableWidgetEditor ui; + ItemListEditor *m_rowEditor; + ItemListEditor *m_columnEditor; + bool m_updatingBrowser; +}; + +class TableWidgetEditorDialog : public QDialog +{ + Q_OBJECT +public: + explicit TableWidgetEditorDialog(QDesignerFormWindowInterface *form, QWidget *parent); + + TableWidgetContents fillContentsFromTableWidget(QTableWidget *tableWidget); + TableWidgetContents contents() const; + +private: + TableWidgetEditor m_editor; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TABLEWIDGETEDITOR_H diff --git a/src/tools/designer/src/components/taskmenu/tablewidgeteditor.ui b/src/tools/designer/src/components/taskmenu/tablewidgeteditor.ui new file mode 100644 index 00000000000..ad067bae9c7 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/tablewidgeteditor.ui @@ -0,0 +1,121 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::TableWidgetEditor + + + + 0 + 0 + 550 + 360 + + + + Edit Table Widget + + + + + + 0 + + + + &Items + + + + + + + 0 + + + + + Table Items + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Properties &>> + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + qdesigner_internal::TableWidgetEditor + accept() + + + 431 + 351 + + + 373 + 362 + + + + + buttonBox + rejected() + qdesigner_internal::TableWidgetEditor + reject() + + + 547 + 354 + + + 562 + 362 + + + + + diff --git a/src/tools/designer/src/components/taskmenu/taskmenu_component.cpp b/src/tools/designer/src/components/taskmenu/taskmenu_component.cpp new file mode 100644 index 00000000000..8da00ee8716 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/taskmenu_component.cpp @@ -0,0 +1,71 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "taskmenu_component.h" +#include "button_taskmenu.h" +#include "groupbox_taskmenu.h" +#include "label_taskmenu.h" +#include "lineedit_taskmenu.h" +#include "listwidget_taskmenu.h" +#include "treewidget_taskmenu.h" +#include "tablewidget_taskmenu.h" +#include "containerwidget_taskmenu.h" +#include "combobox_taskmenu.h" +#include "textedit_taskmenu.h" +#include "menutaskmenu.h" +#include "toolbar_taskmenu.h" +#include "layouttaskmenu.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TaskMenuComponent::TaskMenuComponent(QDesignerFormEditorInterface *core, QObject *parent) + : QObject(parent), + m_core(core) +{ + Q_ASSERT(m_core != nullptr); + + QExtensionManager *mgr = core->extensionManager(); + const QString taskMenuId = u"QDesignerInternalTaskMenuExtension"_s; + + ButtonTaskMenuFactory::registerExtension(mgr, taskMenuId); + CommandLinkButtonTaskMenuFactory::registerExtension(mgr, taskMenuId); // Order! + ButtonGroupTaskMenuFactory::registerExtension(mgr, taskMenuId); + + GroupBoxTaskMenuFactory::registerExtension(mgr, taskMenuId); + LabelTaskMenuFactory::registerExtension(mgr, taskMenuId); + LineEditTaskMenuFactory::registerExtension(mgr, taskMenuId); + ListWidgetTaskMenuFactory::registerExtension(mgr, taskMenuId); + TreeWidgetTaskMenuFactory::registerExtension(mgr, taskMenuId); + TableWidgetTaskMenuFactory::registerExtension(mgr, taskMenuId); + TextEditTaskMenuFactory::registerExtension(mgr, taskMenuId); + PlainTextEditTaskMenuFactory::registerExtension(mgr, taskMenuId); + MenuTaskMenuFactory::registerExtension(mgr, taskMenuId); + MenuBarTaskMenuFactory::registerExtension(mgr, taskMenuId); + ToolBarTaskMenuFactory::registerExtension(mgr, taskMenuId); + StatusBarTaskMenuFactory::registerExtension(mgr, taskMenuId); + LayoutWidgetTaskMenuFactory::registerExtension(mgr, taskMenuId); + SpacerTaskMenuFactory::registerExtension(mgr, taskMenuId); + + mgr->registerExtensions(new ContainerWidgetTaskMenuFactory(core, mgr), taskMenuId); + mgr->registerExtensions(new ComboBoxTaskMenuFactory(taskMenuId, mgr), taskMenuId); +} + +TaskMenuComponent::~TaskMenuComponent() = default; + +QDesignerFormEditorInterface *TaskMenuComponent::core() const +{ + return m_core; + +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/components/taskmenu/taskmenu_component.h b/src/tools/designer/src/components/taskmenu/taskmenu_component.h new file mode 100644 index 00000000000..c97eb3e9412 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/taskmenu_component.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TASKMENU_COMPONENT_H +#define TASKMENU_COMPONENT_H + +#include "taskmenu_global.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class QT_TASKMENU_EXPORT TaskMenuComponent: public QObject +{ + Q_OBJECT +public: + explicit TaskMenuComponent(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~TaskMenuComponent() override; + + QDesignerFormEditorInterface *core() const; + +private: + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TASKMENU_COMPONENT_H diff --git a/src/tools/designer/src/components/taskmenu/taskmenu_global.h b/src/tools/designer/src/components/taskmenu/taskmenu_global.h new file mode 100644 index 00000000000..a8e6a3dda25 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/taskmenu_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TASKMENU_GLOBAL_H +#define TASKMENU_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_TASKMENU_LIBRARY +# define QT_TASKMENU_EXPORT +#else +# define QT_TASKMENU_EXPORT +#endif +#else +#define QT_TASKMENU_EXPORT +#endif + +#endif // TASKMENU_GLOBAL_H diff --git a/src/tools/designer/src/components/taskmenu/textedit_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/textedit_taskmenu.cpp new file mode 100644 index 00000000000..ed85cf792ae --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/textedit_taskmenu.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "textedit_taskmenu.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TextEditTaskMenu::TextEditTaskMenu(QTextEdit *textEdit, QObject *parent) : + QDesignerTaskMenu(textEdit, parent), + m_format(Qt::RichText), + m_property(u"html"_s), + m_windowTitle(tr("Edit HTML")), + m_editTextAction(new QAction(tr("Change HTML..."), this)) +{ + initialize(); +} + +TextEditTaskMenu::TextEditTaskMenu(QPlainTextEdit *textEdit, QObject *parent) : + QDesignerTaskMenu(textEdit, parent), + m_format(Qt::PlainText), + m_property(u"plainText"_s), + m_windowTitle(tr("Edit Text")), + m_editTextAction(new QAction(tr("Change Plain Text..."), this)) +{ + initialize(); +} + + +void TextEditTaskMenu::initialize() +{ + connect(m_editTextAction, &QAction::triggered, this, &TextEditTaskMenu::editText); + m_taskActions.append(m_editTextAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + +TextEditTaskMenu::~TextEditTaskMenu() = default; + +QAction *TextEditTaskMenu::preferredEditAction() const +{ + return m_editTextAction; +} + +QList TextEditTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void TextEditTaskMenu::editText() +{ + changeTextProperty(m_property, m_windowTitle, MultiSelectionMode, m_format); +} + +} +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/textedit_taskmenu.h b/src/tools/designer/src/components/taskmenu/textedit_taskmenu.h new file mode 100644 index 00000000000..38814f46873 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/textedit_taskmenu.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TEXTEDIT_TASKMENU_H +#define TEXTEDIT_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class TextEditTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit TextEditTaskMenu(QTextEdit *button, QObject *parent = nullptr); + explicit TextEditTaskMenu(QPlainTextEdit *button, QObject *parent = nullptr); + + ~TextEditTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editText(); + +private: + void initialize(); + + const Qt::TextFormat m_format; + const QString m_property; + const QString m_windowTitle; + + mutable QList m_taskActions; + QAction *m_editTextAction; +}; + +using TextEditTaskMenuFactory = ExtensionFactory; +using PlainTextEditTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TEXTEDIT_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/toolbar_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/toolbar_taskmenu.cpp new file mode 100644 index 00000000000..3b230704a47 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/toolbar_taskmenu.cpp @@ -0,0 +1,73 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "toolbar_taskmenu.h" +#include "qdesigner_toolbar_p.h" + +#include + +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + // ------------ ToolBarTaskMenu + ToolBarTaskMenu::ToolBarTaskMenu(QToolBar *tb, QObject *parent) : + QObject(parent), + m_toolBar(tb) + { + } + + QAction *ToolBarTaskMenu::preferredEditAction() const + { + return nullptr; + } + + QList ToolBarTaskMenu::taskActions() const + { + if (ToolBarEventFilter *ef = ToolBarEventFilter::eventFilterOf(m_toolBar)) + return ef->contextMenuActions(); + return {}; + } + + // ------------ StatusBarTaskMenu + StatusBarTaskMenu::StatusBarTaskMenu(QStatusBar *sb, QObject *parent) : + QObject(parent), + m_statusBar(sb), + m_removeAction(new QAction(tr("Remove"), this)), + m_promotionTaskMenu(new PromotionTaskMenu(sb, PromotionTaskMenu::ModeSingleWidget, this)) + { + connect(m_removeAction, &QAction::triggered, this, &StatusBarTaskMenu::removeStatusBar); + } + + QAction *StatusBarTaskMenu::preferredEditAction() const + { + return nullptr; + } + + QList StatusBarTaskMenu::taskActions() const + { + QList rc; + rc.push_back(m_removeAction); + m_promotionTaskMenu->addActions(PromotionTaskMenu::LeadingSeparator, rc); + return rc; + } + + void StatusBarTaskMenu::removeStatusBar() + { + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_statusBar)) { + DeleteStatusBarCommand *cmd = new DeleteStatusBarCommand(fw); + cmd->init(m_statusBar); + fw->commandHistory()->push(cmd); + } + } +} + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/components/taskmenu/toolbar_taskmenu.h b/src/tools/designer/src/components/taskmenu/toolbar_taskmenu.h new file mode 100644 index 00000000000..ea6e1cf5132 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/toolbar_taskmenu.h @@ -0,0 +1,61 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TOOLBAR_TASKMENU_H +#define TOOLBAR_TASKMENU_H + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + class PromotionTaskMenu; + +// ToolBarTaskMenu forwards the actions of ToolBarEventFilter +class ToolBarTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit ToolBarTaskMenu(QToolBar *tb, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private: + QToolBar *m_toolBar; +}; + +// StatusBarTaskMenu provides promotion and deletion +class StatusBarTaskMenu : public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + explicit StatusBarTaskMenu(QStatusBar *tb, QObject *parent = nullptr); + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void removeStatusBar(); + +private: + QStatusBar *m_statusBar; + QAction *m_removeAction; + PromotionTaskMenu *m_promotionTaskMenu; +}; + +using ToolBarTaskMenuFactory = ExtensionFactory; +using StatusBarTaskMenuFactory = ExtensionFactory; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TOOLBAR_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/treewidget_taskmenu.cpp b/src/tools/designer/src/components/taskmenu/treewidget_taskmenu.cpp new file mode 100644 index 00000000000..6ce0b3969bf --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/treewidget_taskmenu.cpp @@ -0,0 +1,77 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "treewidget_taskmenu.h" +#include "treewidgeteditor.h" + +#include + +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +TreeWidgetTaskMenu::TreeWidgetTaskMenu(QTreeWidget *button, QObject *parent) + : QDesignerTaskMenu(button, parent), + m_treeWidget(button), + m_editItemsAction(new QAction(tr("Edit Items..."), this)) +{ + connect(m_editItemsAction, &QAction::triggered, this, &TreeWidgetTaskMenu::editItems); + m_taskActions.append(m_editItemsAction); + + QAction *sep = new QAction(this); + sep->setSeparator(true); + m_taskActions.append(sep); +} + + +TreeWidgetTaskMenu::~TreeWidgetTaskMenu() = default; + +QAction *TreeWidgetTaskMenu::preferredEditAction() const +{ + return m_editItemsAction; +} + +QList TreeWidgetTaskMenu::taskActions() const +{ + return m_taskActions + QDesignerTaskMenu::taskActions(); +} + +void TreeWidgetTaskMenu::editItems() +{ + m_formWindow = QDesignerFormWindowInterface::findFormWindow(m_treeWidget); + if (m_formWindow.isNull()) + return; + + Q_ASSERT(m_treeWidget != nullptr); + + TreeWidgetEditorDialog dlg(m_formWindow, m_treeWidget->window()); + TreeWidgetContents oldCont = dlg.fillContentsFromTreeWidget(m_treeWidget); + if (dlg.exec() == QDialog::Accepted) { + TreeWidgetContents newCont = dlg.contents(); + if (newCont != oldCont) { + ChangeTreeContentsCommand *cmd = new ChangeTreeContentsCommand(m_formWindow); + cmd->init(m_treeWidget, oldCont, newCont); + m_formWindow->commandHistory()->push(cmd); + } + } +} + +void TreeWidgetTaskMenu::updateSelection() +{ + if (m_editor) + m_editor->deleteLater(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/treewidget_taskmenu.h b/src/tools/designer/src/components/taskmenu/treewidget_taskmenu.h new file mode 100644 index 00000000000..5ad6c34c81f --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/treewidget_taskmenu.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TREEWIDGET_TASKMENU_H +#define TREEWIDGET_TASKMENU_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QLineEdit; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class TreeWidgetTaskMenu: public QDesignerTaskMenu +{ + Q_OBJECT +public: + explicit TreeWidgetTaskMenu(QTreeWidget *button, QObject *parent = nullptr); + ~TreeWidgetTaskMenu() override; + + QAction *preferredEditAction() const override; + QList taskActions() const override; + +private slots: + void editItems(); + void updateSelection(); + +private: + QTreeWidget *m_treeWidget; + QPointer m_formWindow; + QPointer m_editor; + mutable QList m_taskActions; + QAction *m_editItemsAction; +}; + +using TreeWidgetTaskMenuFactory = ExtensionFactory; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TREEWIDGET_TASKMENU_H diff --git a/src/tools/designer/src/components/taskmenu/treewidgeteditor.cpp b/src/tools/designer/src/components/taskmenu/treewidgeteditor.cpp new file mode 100644 index 00000000000..db887d1455a --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/treewidgeteditor.cpp @@ -0,0 +1,618 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "treewidgeteditor.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +TreeWidgetEditor::TreeWidgetEditor(QDesignerFormWindowInterface *form, QDialog *dialog) + : AbstractItemEditor(form, nullptr), m_updatingBrowser(false) +{ + m_columnEditor = new ItemListEditor(form, this); + m_columnEditor->setObjectName(u"columnEditor"_s); + m_columnEditor->setNewItemText(tr("New Column")); + ui.setupUi(dialog); + + injectPropertyBrowser(ui.itemsTab, ui.widget); + connect(ui.showPropertiesButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::togglePropertyBrowser); + setPropertyBrowserVisible(false); + + ui.tabWidget->insertTab(0, m_columnEditor, tr("&Columns")); + ui.tabWidget->setCurrentIndex(0); + + ui.newItemButton->setIcon(createIconSet("plus.png"_L1)); + ui.newSubItemButton->setIcon(createIconSet("downplus.png"_L1)); + ui.deleteItemButton->setIcon(createIconSet("minus.png"_L1)); + ui.moveItemUpButton->setIcon(createIconSet("up.png"_L1)); + ui.moveItemDownButton->setIcon(createIconSet("down.png"_L1)); + ui.moveItemRightButton->setIcon(createIconSet("leveldown.png"_L1)); + ui.moveItemLeftButton->setIcon(createIconSet("levelup.png"_L1)); + + ui.treeWidget->header()->setSectionsMovable(false); + + connect(ui.newItemButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::newItemButtonClicked); + connect(ui.newSubItemButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::newSubItemButtonClicked); + connect(ui.moveItemUpButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::moveItemUpButtonClicked); + connect(ui.moveItemDownButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::moveItemDownButtonClicked); + connect(ui.moveItemRightButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::moveItemRightButtonClicked); + connect(ui.moveItemLeftButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::moveItemLeftButtonClicked); + connect(ui.deleteItemButton, &QAbstractButton::clicked, + this, &TreeWidgetEditor::deleteItemButtonClicked); + connect(ui.treeWidget, &QTreeWidget::currentItemChanged, + this, &TreeWidgetEditor::treeWidgetCurrentItemChanged); + connect(ui.treeWidget, &QTreeWidget::itemChanged, + this, &TreeWidgetEditor::treeWidgetItemChanged); + + connect(m_columnEditor, &ItemListEditor::indexChanged, + this, &TreeWidgetEditor::columnEditorIndexChanged); + connect(m_columnEditor, &ItemListEditor::itemChanged, + this, &TreeWidgetEditor::columnEditorItemChanged); + connect(m_columnEditor, &ItemListEditor::itemInserted, + this, &TreeWidgetEditor::columnEditorItemInserted); + connect(m_columnEditor, &ItemListEditor::itemDeleted, + this, &TreeWidgetEditor::columnEditorItemDeleted); + connect(m_columnEditor, &ItemListEditor::itemMovedUp, + this, &TreeWidgetEditor::columnEditorItemMovedUp); + connect(m_columnEditor, &ItemListEditor::itemMovedDown, + this, &TreeWidgetEditor::columnEditorItemMovedDown); + + connect(iconCache(), &DesignerIconCache::reloaded, this, &TreeWidgetEditor::cacheReloaded); +} + +static AbstractItemEditor::PropertyDefinition treeHeaderPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, + { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QColor, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { 0, 0, nullptr, nullptr } +}; + +static AbstractItemEditor::PropertyDefinition treeItemColumnPropList[] = { + { Qt::DisplayPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "text" }, + { Qt::DecorationPropertyRole, 0, DesignerPropertyManager::designerIconTypeId, "icon" }, + { Qt::ToolTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "toolTip" }, + { Qt::StatusTipPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "statusTip" }, + { Qt::WhatsThisPropertyRole, 0, DesignerPropertyManager::designerStringTypeId, "whatsThis" }, + { Qt::FontRole, QMetaType::QFont, nullptr, "font" }, + { Qt::TextAlignmentRole, 0, DesignerPropertyManager::designerAlignmentTypeId, "textAlignment" }, + { Qt::BackgroundRole, QMetaType::QBrush, nullptr, "background" }, + { Qt::ForegroundRole, QMetaType::QBrush, nullptr, "foreground" }, + { Qt::CheckStateRole, 0, QtVariantPropertyManager::enumTypeId, "checkState" }, + { 0, 0, nullptr, nullptr } +}; + +static AbstractItemEditor::PropertyDefinition treeItemCommonPropList[] = { + { ItemFlagsShadowRole, 0, QtVariantPropertyManager::flagTypeId, "flags" }, + { 0, 0, nullptr, nullptr } +}; + +QtVariantProperty *TreeWidgetEditor::setupPropertyGroup(const QString &title, PropertyDefinition *propDefs) +{ + setupProperties(propDefs); + QtVariantProperty *groupProp = m_propertyManager->addProperty(QtVariantPropertyManager::groupTypeId(), title); + for (QtVariantProperty *prop : std::as_const(m_rootProperties)) + groupProp->addSubProperty(prop); + m_rootProperties.clear(); + return groupProp; +} + +TreeWidgetContents TreeWidgetEditor::fillContentsFromTreeWidget(QTreeWidget *treeWidget) +{ + TreeWidgetContents treeCont; + treeCont.fromTreeWidget(treeWidget, false); + treeCont.applyToTreeWidget(ui.treeWidget, iconCache(), true); + + treeCont.m_headerItem.applyToListWidget(m_columnEditor->listWidget(), iconCache(), true); + m_columnEditor->setupEditor(treeWidget, treeHeaderPropList); + + QList rootProperties; + rootProperties.append(setupPropertyGroup(tr("Per column properties"), treeItemColumnPropList)); + rootProperties.append(setupPropertyGroup(tr("Common properties"), treeItemCommonPropList)); + m_rootProperties = rootProperties; + m_propertyBrowser->setPropertiesWithoutValueMarked(true); + m_propertyBrowser->setRootIsDecorated(false); + setupObject(treeWidget); + + if (ui.treeWidget->topLevelItemCount() > 0) + ui.treeWidget->setCurrentItem(ui.treeWidget->topLevelItem(0)); + + updateEditor(); + + return treeCont; +} + +TreeWidgetContents TreeWidgetEditor::contents() const +{ + TreeWidgetContents retVal; + retVal.fromTreeWidget(ui.treeWidget, true); + return retVal; +} + +void TreeWidgetEditor::setItemData(int role, const QVariant &v) +{ + const int col = (role == ItemFlagsShadowRole) ? 0 : ui.treeWidget->currentColumn(); + QVariant newValue = v; + BoolBlocker block(m_updatingBrowser); + if (role == Qt::FontRole && newValue.metaType().id() == QMetaType::QFont) { + QFont oldFont = ui.treeWidget->font(); + QFont newFont = qvariant_cast(newValue).resolve(oldFont); + newValue = QVariant::fromValue(newFont); + ui.treeWidget->currentItem()->setData(col, role, QVariant()); // force the right font with the current resolve mask is set (item view bug) + } + ui.treeWidget->currentItem()->setData(col, role, newValue); +} + +QVariant TreeWidgetEditor::getItemData(int role) const +{ + const int col = (role == ItemFlagsShadowRole) ? 0 : ui.treeWidget->currentColumn(); + return ui.treeWidget->currentItem()->data(col, role); +} + +int TreeWidgetEditor::defaultItemFlags() const +{ + static const int flags = QTreeWidgetItem().flags(); + return flags; +} + +void TreeWidgetEditor::newItemButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + QTreeWidgetItem *newItem = nullptr; + ui.treeWidget->blockSignals(true); + if (curItem) { + if (curItem->parent()) + newItem = new QTreeWidgetItem(curItem->parent(), curItem); + else + newItem = new QTreeWidgetItem(ui.treeWidget, curItem); + } else + newItem = new QTreeWidgetItem(ui.treeWidget); + const QString newItemText = tr("New Item"); + newItem->setText(0, newItemText); + newItem->setData(0, Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(newItemText))); + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(newItem, qMax(ui.treeWidget->currentColumn(), 0)); + updateEditor(); + ui.treeWidget->editItem(newItem, ui.treeWidget->currentColumn()); +} + +void TreeWidgetEditor::newSubItemButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + ui.treeWidget->blockSignals(true); + QTreeWidgetItem *newItem = new QTreeWidgetItem(curItem); + const QString newItemText = tr("New Subitem"); + newItem->setText(0, newItemText); + newItem->setData(0, Qt::DisplayPropertyRole, QVariant::fromValue(PropertySheetStringValue(newItemText))); + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(newItem, ui.treeWidget->currentColumn()); + updateEditor(); + ui.treeWidget->editItem(newItem, ui.treeWidget->currentColumn()); +} + +void TreeWidgetEditor::deleteItemButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + QTreeWidgetItem *nextCurrent = nullptr; + if (curItem->parent()) { + int idx = curItem->parent()->indexOfChild(curItem); + if (idx == curItem->parent()->childCount() - 1) + idx--; + else + idx++; + if (idx < 0) + nextCurrent = curItem->parent(); + else + nextCurrent = curItem->parent()->child(idx); + } else { + int idx = ui.treeWidget->indexOfTopLevelItem(curItem); + if (idx == ui.treeWidget->topLevelItemCount() - 1) + idx--; + else + idx++; + if (idx >= 0) + nextCurrent = ui.treeWidget->topLevelItem(idx); + } + closeEditors(); + ui.treeWidget->blockSignals(true); + delete curItem; + ui.treeWidget->blockSignals(false); + + if (nextCurrent) + ui.treeWidget->setCurrentItem(nextCurrent, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::moveItemUpButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + int idx; + if (curItem->parent()) + idx = curItem->parent()->indexOfChild(curItem); + else + idx = ui.treeWidget->indexOfTopLevelItem(curItem); + if (idx == 0) + return; + + QTreeWidgetItem *takenItem; + ui.treeWidget->blockSignals(true); + if (curItem->parent()) { + QTreeWidgetItem *parentItem = curItem->parent(); + takenItem = parentItem->takeChild(idx); + parentItem->insertChild(idx - 1, takenItem); + } else { + takenItem = ui.treeWidget->takeTopLevelItem(idx); + ui.treeWidget->insertTopLevelItem(idx - 1, takenItem); + } + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(takenItem, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::moveItemDownButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + int idx, idxCount; + if (curItem->parent()) { + idx = curItem->parent()->indexOfChild(curItem); + idxCount = curItem->parent()->childCount(); + } else { + idx = ui.treeWidget->indexOfTopLevelItem(curItem); + idxCount = ui.treeWidget->topLevelItemCount(); + } + if (idx == idxCount - 1) + return; + + QTreeWidgetItem *takenItem; + ui.treeWidget->blockSignals(true); + if (curItem->parent()) { + QTreeWidgetItem *parentItem = curItem->parent(); + takenItem = parentItem->takeChild(idx); + parentItem->insertChild(idx + 1, takenItem); + } else { + takenItem = ui.treeWidget->takeTopLevelItem(idx); + ui.treeWidget->insertTopLevelItem(idx + 1, takenItem); + } + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(takenItem, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::moveItemLeftButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + QTreeWidgetItem *parentItem = curItem->parent(); + if (!parentItem) + return; + + ui.treeWidget->blockSignals(true); + QTreeWidgetItem *takenItem = parentItem->takeChild(parentItem->indexOfChild(curItem)); + if (parentItem->parent()) { + int idx = parentItem->parent()->indexOfChild(parentItem); + parentItem->parent()->insertChild(idx, takenItem); + } else { + int idx = ui.treeWidget->indexOfTopLevelItem(parentItem); + ui.treeWidget->insertTopLevelItem(idx, takenItem); + } + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(takenItem, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::moveItemRightButtonClicked() +{ + QTreeWidgetItem *curItem = ui.treeWidget->currentItem(); + if (!curItem) + return; + + int idx, idxCount; + if (curItem->parent()) { + idx = curItem->parent()->indexOfChild(curItem); + idxCount = curItem->parent()->childCount(); + } else { + idx = ui.treeWidget->indexOfTopLevelItem(curItem); + idxCount = ui.treeWidget->topLevelItemCount(); + } + if (idx == idxCount - 1) + return; + + QTreeWidgetItem *takenItem; + ui.treeWidget->blockSignals(true); + if (curItem->parent()) { + QTreeWidgetItem *parentItem = curItem->parent()->child(idx + 1); + takenItem = curItem->parent()->takeChild(idx); + parentItem->insertChild(0, takenItem); + } else { + QTreeWidgetItem *parentItem = ui.treeWidget->topLevelItem(idx + 1); + takenItem = ui.treeWidget->takeTopLevelItem(idx); + parentItem->insertChild(0, takenItem); + } + ui.treeWidget->blockSignals(false); + + ui.treeWidget->setCurrentItem(takenItem, ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::togglePropertyBrowser() +{ + setPropertyBrowserVisible(!m_propertyBrowser->isVisible()); +} + +void TreeWidgetEditor::setPropertyBrowserVisible(bool v) +{ + ui.showPropertiesButton->setText(v ? tr("Properties &>>") : tr("Properties &<<")); + m_propertyBrowser->setVisible(v); +} + +void TreeWidgetEditor::treeWidgetCurrentItemChanged() +{ + m_columnEditor->setCurrentIndex(ui.treeWidget->currentColumn()); + updateEditor(); +} + +void TreeWidgetEditor::treeWidgetItemChanged(QTreeWidgetItem *item, int column) +{ + if (m_updatingBrowser) + return; + + PropertySheetStringValue val = qvariant_cast(item->data(column, Qt::DisplayPropertyRole)); + val.setValue(item->text(column)); + BoolBlocker block(m_updatingBrowser); + item->setData(column, Qt::DisplayPropertyRole, QVariant::fromValue(val)); + + updateBrowser(); +} + +void TreeWidgetEditor::columnEditorIndexChanged(int idx) +{ + if (QTreeWidgetItem *item = ui.treeWidget->currentItem()) + ui.treeWidget->setCurrentItem(item, idx); +} + +void TreeWidgetEditor::columnEditorItemChanged(int idx, int role, const QVariant &v) +{ + if (role == Qt::DisplayPropertyRole) + ui.treeWidget->headerItem()->setData(idx, Qt::EditRole, qvariant_cast(v).value()); + ui.treeWidget->headerItem()->setData(idx, role, v); +} + +void TreeWidgetEditor::updateEditor() +{ + QTreeWidgetItem *current = ui.treeWidget->currentItem(); + + bool itemsEnabled = false; + bool currentItemEnabled = false; + bool moveItemUpEnabled = false; + bool moveItemDownEnabled = false; + bool moveItemRightEnabled = false; + bool moveItemLeftEnabled = false; + + if (ui.treeWidget->columnCount() > 0) { + itemsEnabled = true; + if (current) { + int idx; + int idxCount; + currentItemEnabled = true; + if (current->parent()) { + moveItemLeftEnabled = true; + idx = current->parent()->indexOfChild(current); + idxCount = current->parent()->childCount(); + } else { + idx = ui.treeWidget->indexOfTopLevelItem(current); + idxCount = ui.treeWidget->topLevelItemCount(); + } + if (idx > 0) + moveItemUpEnabled = true; + if (idx < idxCount - 1) { + moveItemDownEnabled = true; + moveItemRightEnabled = true; + } + } + } + ui.tabWidget->setTabEnabled(1, itemsEnabled); + ui.newSubItemButton->setEnabled(currentItemEnabled); + ui.deleteItemButton->setEnabled(currentItemEnabled); + + ui.moveItemUpButton->setEnabled(moveItemUpEnabled); + ui.moveItemDownButton->setEnabled(moveItemDownEnabled); + ui.moveItemRightButton->setEnabled(moveItemRightEnabled); + ui.moveItemLeftButton->setEnabled(moveItemLeftEnabled); + + if (current) + updateBrowser(); + else + m_propertyBrowser->clear(); +} + +void TreeWidgetEditor::moveColumnItems(const PropertyDefinition *propList, + QTreeWidgetItem *item, int fromColumn, int toColumn, int step) +{ + BoolBlocker block(m_updatingBrowser); + + QList saveCol; + for (int j = 0; propList[j].name; j++) + saveCol.append(item->data(toColumn, propList[j].role)); + QVariant editVariant = item->data(toColumn, Qt::EditRole); + QVariant toolTipVariant = item->data(toColumn, Qt::ToolTipRole); + QVariant statusTipVariant = item->data(toColumn, Qt::StatusTipRole); + QVariant whatsThisVariant = item->data(toColumn, Qt::WhatsThisRole); + QVariant decorationVariant = item->data(toColumn, Qt::DecorationRole); + for (int i = toColumn; i != fromColumn; i += step) { + for (int j = 0; propList[j].name; j++) + item->setData(i, propList[j].role, item->data(i + step, propList[j].role)); + item->setData(i, Qt::EditRole, item->data(i + step, Qt::EditRole)); + item->setData(i, Qt::ToolTipRole, item->data(i + step, Qt::ToolTipRole)); + item->setData(i, Qt::StatusTipRole, item->data(i + step, Qt::StatusTipRole)); + item->setData(i, Qt::WhatsThisRole, item->data(i + step, Qt::WhatsThisRole)); + item->setData(i, Qt::DecorationRole, item->data(i + step, Qt::DecorationRole)); + } + for (int j = 0; propList[j].name; j++) + item->setData(fromColumn, propList[j].role, saveCol[j]); + item->setData(fromColumn, Qt::EditRole, editVariant); + item->setData(fromColumn, Qt::ToolTipRole, toolTipVariant); + item->setData(fromColumn, Qt::StatusTipRole, statusTipVariant); + item->setData(fromColumn, Qt::WhatsThisRole, whatsThisVariant); + item->setData(fromColumn, Qt::DecorationRole, decorationVariant); +} + +void TreeWidgetEditor::moveColumns(int fromColumn, int toColumn, int step) +{ + ui.treeWidget->blockSignals(true); + + moveColumnItems(treeHeaderPropList, ui.treeWidget->headerItem(), fromColumn, toColumn, step); + + QQueue pendingQueue; + for (int i = 0; i < ui.treeWidget->topLevelItemCount(); i++) + pendingQueue.enqueue(ui.treeWidget->topLevelItem(i)); + + while (!pendingQueue.isEmpty()) { + QTreeWidgetItem *item = pendingQueue.dequeue(); + for (int i = 0; i < item->childCount(); i++) + pendingQueue.enqueue(item->child(i)); + + moveColumnItems(treeItemColumnPropList, item, fromColumn, toColumn, step); + } + + ui.treeWidget->blockSignals(false); +} + +void TreeWidgetEditor::moveColumnsLeft(int fromColumn, int toColumn) +{ + if (fromColumn >= toColumn) + return; + + moveColumns(fromColumn, toColumn, -1); +} + +void TreeWidgetEditor::moveColumnsRight(int fromColumn, int toColumn) +{ + if (fromColumn >= toColumn) + return; + + moveColumns(toColumn, fromColumn, 1); +} + +void TreeWidgetEditor::columnEditorItemInserted(int idx) +{ + int columnCount = ui.treeWidget->columnCount(); + ui.treeWidget->setColumnCount(columnCount + 1); + ui.treeWidget->headerItem()->setText(columnCount, m_columnEditor->newItemText()); + moveColumnsLeft(idx, columnCount); + + updateEditor(); +} + +void TreeWidgetEditor::columnEditorItemDeleted(int idx) +{ + closeEditors(); + + int columnCount = ui.treeWidget->columnCount() - 1; + if (!columnCount) + ui.treeWidget->clear(); + else + moveColumnsRight(idx, columnCount); + ui.treeWidget->setColumnCount(columnCount); + + updateEditor(); +} + +void TreeWidgetEditor::columnEditorItemMovedUp(int idx) +{ + moveColumnsRight(idx - 1, idx); + + ui.treeWidget->setCurrentItem(ui.treeWidget->currentItem(), idx - 1); + updateEditor(); +} + +void TreeWidgetEditor::columnEditorItemMovedDown(int idx) +{ + moveColumnsLeft(idx, idx + 1); + + ui.treeWidget->setCurrentItem(ui.treeWidget->currentItem(), idx + 1); + updateEditor(); +} + +void TreeWidgetEditor::closeEditors() +{ + if (QTreeWidgetItem *cur = ui.treeWidget->currentItem() ) { + const int numCols = cur->columnCount (); + for (int i = 0; i < numCols; i++) + ui.treeWidget->closePersistentEditor (cur, i); + } +} + +void TreeWidgetEditor::cacheReloaded() +{ + reloadIconResources(iconCache(), ui.treeWidget); +} + +TreeWidgetEditorDialog::TreeWidgetEditorDialog(QDesignerFormWindowInterface *form, QWidget *parent) : + QDialog(parent), m_editor(form, this) +{ +} + +TreeWidgetContents TreeWidgetEditorDialog::fillContentsFromTreeWidget(QTreeWidget *treeWidget) +{ + return m_editor.fillContentsFromTreeWidget(treeWidget); +} + +TreeWidgetContents TreeWidgetEditorDialog::contents() const +{ + return m_editor.contents(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/taskmenu/treewidgeteditor.h b/src/tools/designer/src/components/taskmenu/treewidgeteditor.h new file mode 100644 index 00000000000..b2d306f0a11 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/treewidgeteditor.h @@ -0,0 +1,92 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TREEWIDGETEDITOR_H +#define TREEWIDGETEDITOR_H + +#include "ui_treewidgeteditor.h" + +#include "listwidgeteditor.h" + +#include + +QT_BEGIN_NAMESPACE + +class QTreeWidget; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class FormWindowBase; +class PropertySheetIconValue; + +class TreeWidgetEditor: public AbstractItemEditor +{ + Q_OBJECT +public: + explicit TreeWidgetEditor(QDesignerFormWindowInterface *form, QDialog *dialog); + + TreeWidgetContents fillContentsFromTreeWidget(QTreeWidget *treeWidget); + TreeWidgetContents contents() const; + +private slots: + void newItemButtonClicked(); + void newSubItemButtonClicked(); + void deleteItemButtonClicked(); + void moveItemUpButtonClicked(); + void moveItemDownButtonClicked(); + void moveItemRightButtonClicked(); + void moveItemLeftButtonClicked(); + + void treeWidgetCurrentItemChanged(); + void treeWidgetItemChanged(QTreeWidgetItem *item, int column); + + void columnEditorIndexChanged(int idx); + void columnEditorItemChanged(int idx, int role, const QVariant &v); + + void columnEditorItemInserted(int idx); + void columnEditorItemDeleted(int idx); + void columnEditorItemMovedUp(int idx); + void columnEditorItemMovedDown(int idx); + + void togglePropertyBrowser(); + void cacheReloaded(); + +protected: + void setItemData(int role, const QVariant &v) override; + QVariant getItemData(int role) const override; + int defaultItemFlags() const override; + +private: + void setPropertyBrowserVisible(bool v); + QtVariantProperty *setupPropertyGroup(const QString &title, PropertyDefinition *propDefs); + void updateEditor(); + void moveColumnItems(const PropertyDefinition *propList, QTreeWidgetItem *item, int fromColumn, int toColumn, int step); + void moveColumns(int fromColumn, int toColumn, int step); + void moveColumnsLeft(int fromColumn, int toColumn); + void moveColumnsRight(int fromColumn, int toColumn); + void closeEditors(); + + Ui::TreeWidgetEditor ui; + ItemListEditor *m_columnEditor; + bool m_updatingBrowser; +}; + +class TreeWidgetEditorDialog : public QDialog +{ + Q_OBJECT +public: + explicit TreeWidgetEditorDialog(QDesignerFormWindowInterface *form, QWidget *parent); + + TreeWidgetContents fillContentsFromTreeWidget(QTreeWidget *treeWidget); + TreeWidgetContents contents() const; + +private: + TreeWidgetEditor m_editor; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // TREEWIDGETEDITOR_H diff --git a/src/tools/designer/src/components/taskmenu/treewidgeteditor.ui b/src/tools/designer/src/components/taskmenu/treewidgeteditor.ui new file mode 100644 index 00000000000..688b2f45a53 --- /dev/null +++ b/src/tools/designer/src/components/taskmenu/treewidgeteditor.ui @@ -0,0 +1,221 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::TreeWidgetEditor + + + + 0 + 0 + 700 + 360 + + + + Edit Tree Widget + + + + + + 0 + + + + &Items + + + + 9 + + + 9 + + + 9 + + + + + + 0 + + + + + Qt::WheelFocus + + + Tree Items + + + + 1 + + + + + + + + + + New Item + + + &New + + + + + + + New Subitem + + + New &Subitem + + + + + + + Delete Item + + + &Delete + + + + + + + Qt::Horizontal + + + + 28 + 23 + + + + + + + + Move Item Left (before Parent Item) + + + L + + + + + + + Move Item Right (as a First Subitem of the Next Sibling Item) + + + R + + + + + + + Move Item Up + + + U + + + + + + + Move Item Down + + + D + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Properties &>> + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + qdesigner_internal::TreeWidgetEditor + accept() + + + 440 + 335 + + + 373 + 362 + + + + + buttonBox + rejected() + qdesigner_internal::TreeWidgetEditor + reject() + + + 556 + 335 + + + 562 + 362 + + + + + diff --git a/src/tools/designer/src/components/widgetbox/widgetbox.cpp b/src/tools/designer/src/components/widgetbox/widgetbox.cpp new file mode 100644 index 00000000000..7294799f91e --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetbox.cpp @@ -0,0 +1,238 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetbox.h" +#include "widgetboxtreewidget.h" +#include "widgetbox_dnditem.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +/* WidgetBoxFilterLineEdit: This widget should never have initial focus + * (ie, be the first widget of a dialog, else, the hint cannot be displayed. + * As it is the only focusable control in the widget box, it clears the focus + * policy and focusses explicitly on click only (note that setting Qt::ClickFocus + * is not sufficient for that as an ActivationFocus will occur). */ + +class WidgetBoxFilterLineEdit : public QLineEdit { +public: + explicit WidgetBoxFilterLineEdit(QWidget *parent = nullptr) : QLineEdit(parent), m_defaultFocusPolicy(focusPolicy()) + { setFocusPolicy(Qt::NoFocus); } + +protected: + void mousePressEvent(QMouseEvent *event) override; + void focusInEvent(QFocusEvent *e) override; + +private: + const Qt::FocusPolicy m_defaultFocusPolicy; +}; + +void WidgetBoxFilterLineEdit::mousePressEvent(QMouseEvent *e) +{ + if (!hasFocus()) // Explicitly focus on click. + setFocus(Qt::OtherFocusReason); + QLineEdit::mousePressEvent(e); +} + +void WidgetBoxFilterLineEdit::focusInEvent(QFocusEvent *e) +{ + // Refuse the focus if the mouse it outside. In addition to the mouse + // press logic, this prevents a re-focussing which occurs once + // we actually had focus + const Qt::FocusReason reason = e->reason(); + if (reason == Qt::ActiveWindowFocusReason || reason == Qt::PopupFocusReason) { + const QPoint mousePos = mapFromGlobal(QCursor::pos()); + const bool refuse = !geometry().contains(mousePos); + if (refuse) { + e->ignore(); + return; + } + } + QLineEdit::focusInEvent(e); +} + +WidgetBox::WidgetBox(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) + : QDesignerWidgetBox(parent, flags), + m_core(core), + m_view(new WidgetBoxTreeWidget(m_core)) +{ + + QVBoxLayout *l = new QVBoxLayout(this); + l->setContentsMargins(QMargins()); + l->setSpacing(0); + + // Prevent the filter from grabbing focus since Our view has Qt::NoFocus + QToolBar *toolBar = new QToolBar(this); + QLineEdit *filterWidget = new WidgetBoxFilterLineEdit(toolBar); + filterWidget->setPlaceholderText(tr("Filter")); + filterWidget->setClearButtonEnabled(true); + connect(filterWidget, &QLineEdit::textChanged, m_view, &WidgetBoxTreeWidget::filter); + toolBar->addWidget(filterWidget); + l->addWidget(toolBar); + + // View + connect(m_view, &WidgetBoxTreeWidget::widgetBoxPressed, + this, &WidgetBox::handleMousePress); + l->addWidget(m_view); + + setAcceptDrops (true); +} + +WidgetBox::~WidgetBox() = default; + +QDesignerFormEditorInterface *WidgetBox::core() const +{ + return m_core; +} + +void WidgetBox::handleMousePress(const QString &name, const QString &xml, const QPoint &global_mouse_pos) +{ + if (QApplication::mouseButtons() != Qt::LeftButton) + return; + + DomUI *ui = xmlToUi(name, xml, true); + if (ui == nullptr) + return; + QList item_list; + item_list.append(new WidgetBoxDnDItem(core(), ui, global_mouse_pos)); + m_core->formWindowManager()->dragItems(item_list); +} + +int WidgetBox::categoryCount() const +{ + return m_view->categoryCount(); +} + +QDesignerWidgetBoxInterface::Category WidgetBox::category(int cat_idx) const +{ + return m_view->category(cat_idx); +} + +void WidgetBox::addCategory(const Category &cat) +{ + m_view->addCategory(cat); +} + +void WidgetBox::removeCategory(int cat_idx) +{ + m_view->removeCategory(cat_idx); +} + +int WidgetBox::widgetCount(int cat_idx) const +{ + return m_view->widgetCount(cat_idx); +} + +QDesignerWidgetBoxInterface::Widget WidgetBox::widget(int cat_idx, int wgt_idx) const +{ + return m_view->widget(cat_idx, wgt_idx); +} + +void WidgetBox::addWidget(int cat_idx, const Widget &wgt) +{ + m_view->addWidget(cat_idx, wgt); +} + +void WidgetBox::removeWidget(int cat_idx, int wgt_idx) +{ + m_view->removeWidget(cat_idx, wgt_idx); +} + +void WidgetBox::dropWidgets(const QList &item_list, const QPoint&) +{ + m_view->dropWidgets(item_list); +} + +void WidgetBox::setFileName(const QString &file_name) +{ + m_view->setFileName(file_name); +} + +QString WidgetBox::fileName() const +{ + return m_view->fileName(); +} + +bool WidgetBox::load() +{ + return m_view->load(loadMode()); +} + +bool WidgetBox::loadContents(const QString &contents) +{ + return m_view->loadContents(contents); +} + +bool WidgetBox::save() +{ + return m_view->save(); +} + +static const QDesignerMimeData *checkDragEvent(QDropEvent * event, + bool acceptEventsFromWidgetBox) +{ + const QDesignerMimeData *mimeData = qobject_cast(event->mimeData()); + if (!mimeData) { + event->ignore(); + return nullptr; + } + // If desired, ignore a widget box drag and drop, where widget==0. + if (!acceptEventsFromWidgetBox) { + const bool fromWidgetBox = !mimeData->items().first()->widget(); + if (fromWidgetBox) { + event->ignore(); + return nullptr; + } + } + + mimeData->acceptEvent(event); + return mimeData; +} + +void WidgetBox::dragEnterEvent (QDragEnterEvent * event) +{ + // We accept event originating from the widget box also here, + // because otherwise Windows will not show the DnD pixmap. + checkDragEvent(event, true); +} + +void WidgetBox::dragMoveEvent(QDragMoveEvent * event) +{ + checkDragEvent(event, true); +} + +void WidgetBox::dropEvent(QDropEvent * event) +{ + const QDesignerMimeData *mimeData = checkDragEvent(event, false); + if (!mimeData) + return; + + dropWidgets(mimeData->items(), event->position().toPoint()); + QDesignerMimeData::removeMovedWidgetsFromSourceForm(mimeData->items()); +} + +QIcon WidgetBox::iconForWidget(const QString &className, const QString &category) const +{ + Widget widgetData; + if (!findWidget(this, className, category, &widgetData)) + return QIcon(); + return m_view->iconForWidget(widgetData.iconName()); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/widgetbox/widgetbox.h b/src/tools/designer/src/components/widgetbox/widgetbox.h new file mode 100644 index 00000000000..cf43b63b4c4 --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetbox.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOX_H +#define WIDGETBOX_H + +#include "widgetbox_global.h" +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +class WidgetBoxTreeWidget; + +class QT_WIDGETBOX_EXPORT WidgetBox : public QDesignerWidgetBox +{ + Q_OBJECT +public: + explicit WidgetBox(QDesignerFormEditorInterface *core, QWidget *parent = nullptr, + Qt::WindowFlags flags = {}); + ~WidgetBox() override; + + QDesignerFormEditorInterface *core() const; + + int categoryCount() const override; + Category category(int cat_idx) const override; + void addCategory(const Category &cat) override; + void removeCategory(int cat_idx) override; + + int widgetCount(int cat_idx) const override; + Widget widget(int cat_idx, int wgt_idx) const override; + void addWidget(int cat_idx, const Widget &wgt) override; + void removeWidget(int cat_idx, int wgt_idx) override; + + void dropWidgets(const QList &item_list, const QPoint &global_mouse_pos) override; + + void setFileName(const QString &file_name) override; + QString fileName() const override; + bool load() override; + bool save() override; + + bool loadContents(const QString &contents) override; + QIcon iconForWidget(const QString &className, const QString &category = QString()) const override; + +protected: + void dragEnterEvent (QDragEnterEvent * event) override; + void dragMoveEvent(QDragMoveEvent * event) override; + void dropEvent (QDropEvent * event) override; + +private slots: + void handleMousePress(const QString &name, const QString &xml, const QPoint &global_mouse_pos); + +private: + QDesignerFormEditorInterface *m_core; + WidgetBoxTreeWidget *m_view; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETBOX_H diff --git a/src/tools/designer/src/components/widgetbox/widgetbox.xml b/src/tools/designer/src/components/widgetbox/widgetbox.xml new file mode 100644 index 00000000000..4cb29cd2091 --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetbox.xml @@ -0,0 +1,939 @@ + + + + + + + + + verticalLayoutWidget + + + + 0 + 0 + 160 + 80 + + + + + + + + + + + + + horizontalLayoutWidget + + + + 0 + 0 + 160 + 80 + + + + + + + + + + + + + gridLayoutWidget + + + + 0 + 0 + 160 + 80 + + + + + + + + + + + + + formLayoutWidget + + + + 0 + 0 + 160 + 80 + + + + + + + + + + + + + + + + + Qt::Horizontal + + + horizontalSpacer + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + verticalSpacer + + + + 20 + 40 + + + + + + + + + + + + + + + PushButton + + + pushButton + + + + + + + + + + toolButton + + + ... + + + + + + + + + + RadioButton + + + radioButton + + + + + + + + + + CheckBox + + + checkBox + + + + + + + + + + CommandLinkButton + + + commandLinkButton + + + + + + + + + + QDialogButtonBox::Ok|QDialogButtonBox::Cancel + + + buttonBox + + + + + + + + + + + + + + listView + + + + + + + + + + treeView + + + + + + + + + + tableView + + + + + + + + + + columnView + + + + + + + + + + undoView + + + + + + + + + + + + + listWidget + + + + + + + + + + treeWidget + + + + + + + + + + tableWidget + + + + + + + + + + + + + + GroupBox + + + + 0 + 0 + 120 + 80 + + + + groupBox + + + + + + + + + + scrollArea + + + true + + + + 0 + 0 + 120 + 80 + + + + + + + + + + + + + 0 + + + toolBox + + + + Page 1 + + + + + Page 2 + + + + + + + + + + + + 0 + 0 + 120 + 80 + + + + tabWidget + + + + Tab 1 + + + + + Tab 2 + + + + + + + + + + + + 0 + 0 + 120 + 80 + + + + stackedWidget + + + + + + + + + + + + QFrame::Raised + + + + 0 + 0 + 120 + 80 + + + + QFrame::StyledPanel + + + frame + + + + + + + + + + + 0 + 0 + 120 + 80 + + + + widget + + + + + + + + + + + 0 + 0 + 200 + 160 + + + + mdiArea + + + + + + + + + + + 0 + 0 + 120 + 80 + + + + dockWidget + + + + + + + + + + + + + + + + 119 + 28 + 41 + 22 + + + + comboBox + + + + + + + + + + + 119 + 28 + 41 + 22 + + + + fontComboBox + + + + + + + + + + + 0 + 1 + 113 + 20 + + + + lineEdit + + + + + + + + + + textEdit + + + + 0 + 0 + 104 + 64 + + + + + + + + + + + plainTextEdit + + + + 0 + 0 + 104 + 64 + + + + + + + + + + + + 119 + 0 + 42 + 22 + + + + spinBox + + + + + + + + + + + 119 + 0 + 62 + 22 + + + + doubleSpinBox + + + + + + + + + + + 0 + 28 + 118 + 22 + + + + timeEdit + + + + + + + + + + + 0 + 28 + 110 + 22 + + + + dateEdit + + + + + + + + + + + 0 + 28 + 194 + 22 + + + + dateTimeEdit + + + + + + + + + + + 110 + 0 + 50 + 64 + + + + dial + + + + + + + + + + Qt::Horizontal + + + + 0 + 126 + 160 + 16 + + + + horizontalScrollBar + + + + + + + + + + Qt::Vertical + + + + 0 + 126 + 16 + 160 + + + + verticalScrollBar + + + + + + + + + + Qt::Horizontal + + + + 0 + 126 + 160 + 16 + + + + horizontalSlider + + + + + + + + + + Qt::Vertical + + + + 0 + 126 + 16 + 160 + + + + verticalSlider + + + + + + + + + + 0 + 1 + 113 + 20 + + + + keySequenceEdit + + + + + + + + + + + + + + TextLabel + + + label + + + + + + + + + + textBrowser + + + + + + + + + + graphicsView + + + + + + + + + + calendarWidget + + + + + + + + + + lcdNumber + + + + + + + + + + 24 + + + + 9 + 38 + 118 + 23 + + + + progressBar + + + + + + + + + + Qt::Horizontal + + + line + + + + 9 + 67 + 118 + 3 + + + + + + + + + + + Qt::Vertical + + + line + + + + 133 + 9 + 3 + 61 + + + + + + + + + + + + 0 + 0 + 300 + 200 + + + + openGLWidget + + + + + + + diff --git a/src/tools/designer/src/components/widgetbox/widgetbox_dnditem.cpp b/src/tools/designer/src/components/widgetbox/widgetbox_dnditem.cpp new file mode 100644 index 00000000000..afbfc05671e --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetbox_dnditem.cpp @@ -0,0 +1,191 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetbox_dnditem.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +/******************************************************************************* +** WidgetBoxResource +*/ + +static inline DeviceProfile currentDeviceProfile(const QDesignerFormEditorInterface *core) +{ + if (QDesignerFormWindowInterface *cfw = core->formWindowManager()->activeFormWindow()) + if (const FormWindowBase *fwb = qobject_cast(cfw)) + return fwb->deviceProfile(); + return DeviceProfile(); +} + +class WidgetBoxResource : public QDesignerFormBuilder +{ +public: + WidgetBoxResource(QDesignerFormEditorInterface *core); + + // protected->public + QWidget *createUI(DomUI *ui, QWidget *parents) { return QDesignerFormBuilder::create(ui, parents); } + +protected: + + QWidget *create(DomWidget *ui_widget, QWidget *parents) override; + QWidget *createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) override; + void createCustomWidgets(DomCustomWidgets *) override; +}; + +WidgetBoxResource::WidgetBoxResource(QDesignerFormEditorInterface *core) : + QDesignerFormBuilder(core, currentDeviceProfile(core)) +{ +} + + +QWidget *WidgetBoxResource::createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) +{ + if (widgetName == "Spacer"_L1) { + Spacer *spacer = new Spacer(parentWidget); + spacer->setObjectName(name); + return spacer; + } + + return QDesignerFormBuilder::createWidget(widgetName, parentWidget, name); +} + +QWidget *WidgetBoxResource::create(DomWidget *ui_widget, QWidget *parent) +{ + QWidget *result = QDesignerFormBuilder::create(ui_widget, parent); + // It is possible to have a syntax error or something in custom + // widget XML, so, try to recover here by creating an artificial + // top level + widget. + if (!result) { + const QString msg = QApplication::translate("qdesigner_internal::WidgetBox", "Warning: Widget creation failed in the widget box. This could be caused by invalid custom widget XML."); + qdesigner_internal::designerWarning(msg); + result = new QWidget(parent); + new QWidget(result); + } + result->setFocusPolicy(Qt::NoFocus); + result->setObjectName(ui_widget->attributeName()); + return result; +} + +void WidgetBoxResource::createCustomWidgets(DomCustomWidgets *dc) +{ + // Make a promotion entry in case someone has a promoted widget + // in the scratchpad. + QSimpleResource::handleDomCustomWidgets(core(), dc); + +} + +/******************************************************************************* +** WidgetBoxResource +*/ + +static QSize geometryProp(const DomWidget *dw) +{ + const auto &prop_list = dw->elementProperty(); + for (DomProperty *prop : prop_list) { + if (prop->attributeName() != "geometry"_L1) + continue; + DomRect *dr = prop->elementRect(); + if (dr == nullptr) + continue; + return QSize(dr->elementWidth(), dr->elementHeight()); + } + return QSize(); +} + +static QSize domWidgetSize(const DomWidget *dw) +{ + QSize size = geometryProp(dw); + if (size.isValid()) + return size; + + const auto &elementWidgets = dw->elementWidget(); + for (const DomWidget *child : elementWidgets) { + size = geometryProp(child); + if (size.isValid()) + return size; + } + + const auto &elementLayouts = dw->elementLayout(); + for (const DomLayout *dl : elementLayouts) { + const auto &elementItems = dl->elementItem(); + for (DomLayoutItem *item : elementItems) { + const DomWidget *child = item->elementWidget(); + if (child == nullptr) + continue; + size = geometryProp(child); + if (size.isValid()) + return size; + } + } + + return QSize(); +} + +static QWidget *decorationFromDomWidget(DomUI *dom_ui, QDesignerFormEditorInterface *core) +{ + WidgetBoxResource builder(core); + // We have the builder create the articial QWidget fake top level as a tooltip + // because the size algorithm works better at weird DPI settings + // if the actual widget is created as a child of a container + QWidget *fakeTopLevel = builder.createUI(dom_ui, nullptr); + fakeTopLevel->setParent(nullptr, Qt::ToolTip); // Container + // Actual widget + const DomWidget *domW = dom_ui->elementWidget()->elementWidget().constFirst(); + QWidget *w = fakeTopLevel->findChildren().constFirst(); + Q_ASSERT(w); + // hack begin; + // We set _q_dockDrag dynamic property which will be detected in drag enter event of form window. + // Dock drop is handled in special way (highlight goes to central widget of main window) + if (qobject_cast(w)) + fakeTopLevel->setProperty("_q_dockDrag", QVariant(true)); + // hack end; + w->setAutoFillBackground(true); // Different style for embedded + QSize size = domWidgetSize(domW); + const QSize minimumSize = w->minimumSizeHint(); + if (!size.isValid()) + size = w->sizeHint(); + if (size.width() < minimumSize.width()) + size.setWidth(minimumSize.width()); + if (size.height() < minimumSize.height()) + size.setHeight(minimumSize.height()); + // A QWidget might have size -1,-1 if no geometry property is specified in the widget box. + if (size.isEmpty()) + size = size.expandedTo(QSize(16, 16)); + w->setGeometry(QRect(QPoint(0, 0), size)); + fakeTopLevel->resize(size); + return fakeTopLevel; +} + +WidgetBoxDnDItem::WidgetBoxDnDItem(QDesignerFormEditorInterface *core, + DomUI *dom_ui, + const QPoint &global_mouse_pos) : + QDesignerDnDItem(CopyDrop) +{ + QWidget *decoration = decorationFromDomWidget(dom_ui, core); + decoration->move(global_mouse_pos - QPoint(5, 5)); + + init(dom_ui, nullptr, decoration, global_mouse_pos); +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/widgetbox/widgetbox_dnditem.h b/src/tools/designer/src/components/widgetbox/widgetbox_dnditem.h new file mode 100644 index 00000000000..f8f25d58eb7 --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetbox_dnditem.h @@ -0,0 +1,29 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOX_DNDITEM_H +#define WIDGETBOX_DNDITEM_H + +#include +#include "widgetbox_global.h" + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class DomUI; + +namespace qdesigner_internal { + +class QT_WIDGETBOX_EXPORT WidgetBoxDnDItem : public QDesignerDnDItem +{ +public: + WidgetBoxDnDItem(QDesignerFormEditorInterface *core, + DomUI *dom_ui, + const QPoint &global_mouse_pos); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETBOX_DNDITEM_H diff --git a/src/tools/designer/src/components/widgetbox/widgetbox_global.h b/src/tools/designer/src/components/widgetbox/widgetbox_global.h new file mode 100644 index 00000000000..68ebc35542d --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetbox_global.h @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOX_GLOBAL_H +#define WIDGETBOX_GLOBAL_H + +#include + +#ifdef Q_OS_WIN +#ifdef QT_WIDGETBOX_LIBRARY +# define QT_WIDGETBOX_EXPORT +#else +# define QT_WIDGETBOX_EXPORT +#endif +#else +#define QT_WIDGETBOX_EXPORT +#endif + +#endif // WIDGETBOX_GLOBAL_H diff --git a/src/tools/designer/src/components/widgetbox/widgetboxcategorylistview.cpp b/src/tools/designer/src/components/widgetbox/widgetboxcategorylistview.cpp new file mode 100644 index 00000000000..1e90ef08e98 --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetboxcategorylistview.cpp @@ -0,0 +1,471 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetboxcategorylistview.h" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto widgetElementC = "widget"_L1; +static constexpr auto nameAttributeC = "name"_L1; +static constexpr auto uiOpeningTagC = ""_L1; +static constexpr auto uiClosingTagC = ""_L1; + +enum { FilterRole = Qt::UserRole + 11 }; + +static QString domToString(const QDomElement &elt) +{ + QString result; + QTextStream stream(&result, QIODevice::WriteOnly); + elt.save(stream, 2); + stream.flush(); + return result; +} + +static QDomDocument stringToDom(const QString &xml) +{ + QDomDocument result; + result.setContent(xml); + return result; +} + +namespace qdesigner_internal { + +// Entry of the model list + +struct WidgetBoxCategoryEntry { + WidgetBoxCategoryEntry() = default; + explicit WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &widget, + const QString &filter, + const QIcon &icon, + bool editable); + + QDesignerWidgetBoxInterface::Widget widget; + QString toolTip; + QString whatsThis; + QString filter; + QIcon icon; + bool editable{false}; +}; + +WidgetBoxCategoryEntry::WidgetBoxCategoryEntry(const QDesignerWidgetBoxInterface::Widget &w, + const QString &filterIn, + const QIcon &i, bool e) : + widget(w), + filter(filterIn), + icon(i), + editable(e) +{ +} + +/* WidgetBoxCategoryModel, representing a list of category entries. Uses a + * QAbstractListModel since the behaviour depends on the view mode of the list + * view, it does not return text in the case of IconMode. */ + +class WidgetBoxCategoryModel : public QAbstractListModel { +public: + explicit WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + + // QAbstractListModel + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override; + Qt::ItemFlags flags (const QModelIndex & index ) const override; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; + + // The model returns no text in icon mode, so, it also needs to know it + QListView::ViewMode viewMode() const; + void setViewMode(QListView::ViewMode vm); + + void addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable); + + QDesignerWidgetBoxInterface::Widget widgetAt(const QModelIndex & index) const; + QDesignerWidgetBoxInterface::Widget widgetAt(int row) const; + + int indexOfWidget(const QString &name); + + QDesignerWidgetBoxInterface::Category category() const; + bool removeCustomWidgets(); + +private: + QDesignerFormEditorInterface *m_core; + QList m_items; + QListView::ViewMode m_viewMode; +}; + +WidgetBoxCategoryModel::WidgetBoxCategoryModel(QDesignerFormEditorInterface *core, QObject *parent) : + QAbstractListModel(parent), + m_core(core), + m_viewMode(QListView::ListMode) +{ +} + +QListView::ViewMode WidgetBoxCategoryModel::viewMode() const +{ + return m_viewMode; +} + +void WidgetBoxCategoryModel::setViewMode(QListView::ViewMode vm) +{ + if (m_viewMode == vm) + return; + const bool empty = m_items.isEmpty(); + if (!empty) + beginResetModel(); + m_viewMode = vm; + if (!empty) + endResetModel(); +} + +int WidgetBoxCategoryModel::indexOfWidget(const QString &name) +{ + for (qsizetype i = 0, count = m_items.size(); i < count; ++i) + if (m_items.at(i).widget.name() == name) + return i; + return -1; +} + +QDesignerWidgetBoxInterface::Category WidgetBoxCategoryModel::category() const +{ + QDesignerWidgetBoxInterface::Category rc; + for (const auto &c : m_items) + rc.addWidget(c.widget); + return rc; +} + +bool WidgetBoxCategoryModel::removeCustomWidgets() +{ + // Typically, we are a whole category of custom widgets, so, remove all + // and do reset. + bool changed = false; + for (auto it = m_items.begin(); it != m_items.end(); ) + if (it->widget.type() == QDesignerWidgetBoxInterface::Widget::Custom) { + if (!changed) + beginResetModel(); + it = m_items.erase(it); + changed = true; + } else { + ++it; + } + if (changed) + endResetModel(); + return changed; +} + +void WidgetBoxCategoryModel::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon,bool editable) +{ + static const QRegularExpression classNameRegExp(QStringLiteral("widgetDataBase(); + int dbIndex = className.isEmpty() ? -1 : db->indexOfClassName(className); + if (dbIndex == -1) + dbIndex = db->indexOfClassName(widget.name()); + if (dbIndex != -1) { + const QDesignerWidgetDataBaseItemInterface *dbItem = db->item(dbIndex); + const QString toolTip = dbItem->toolTip(); + if (!toolTip.isEmpty()) + item.toolTip = toolTip; + const QString whatsThis = dbItem->whatsThis(); + if (!whatsThis.isEmpty()) + item.whatsThis = whatsThis; + } + // insert + const int row = m_items.size(); + beginInsertRows(QModelIndex(), row, row); + m_items.push_back(item); + endInsertRows(); +} + +QVariant WidgetBoxCategoryModel::data(const QModelIndex &index, int role) const +{ + const int row = index.row(); + if (row < 0 || row >= m_items.size()) + return QVariant(); + + const WidgetBoxCategoryEntry &item = m_items.at(row); + switch (role) { + case Qt::DisplayRole: + // No text in icon mode + return QVariant(m_viewMode == QListView::ListMode ? item.widget.name() : QString()); + case Qt::DecorationRole: + return QVariant(item.icon); + case Qt::EditRole: + return QVariant(item.widget.name()); + case Qt::ToolTipRole: { + if (m_viewMode == QListView::ListMode) + return QVariant(item.toolTip); + // Icon mode tooltip should contain the class name + QString tt = item.widget.name(); + if (!item.toolTip.isEmpty()) + tt += u'\n' + item.toolTip; + return QVariant(tt); + + } + case Qt::WhatsThisRole: + return QVariant(item.whatsThis); + case FilterRole: + return item.filter; + } + return QVariant(); +} + +bool WidgetBoxCategoryModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + const int row = index.row(); + if (role != Qt::EditRole || row < 0 || row >= m_items.size() + || value.metaType().id() != QMetaType::QString) { + return false; + } + // Set name and adapt Xml + WidgetBoxCategoryEntry &item = m_items[row]; + const QString newName = value.toString(); + item.widget.setName(newName); + + const QDomDocument doc = stringToDom(WidgetBoxCategoryListView::widgetDomXml(item.widget)); + QDomElement widget_elt = doc.firstChildElement(widgetElementC); + if (!widget_elt.isNull()) { + widget_elt.setAttribute(nameAttributeC, newName); + item.widget.setDomXml(domToString(widget_elt)); + } + emit dataChanged(index, index); + return true; +} + +Qt::ItemFlags WidgetBoxCategoryModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags rc = Qt::ItemIsEnabled; + const int row = index.row(); + if (row >= 0 && row < m_items.size()) + if (m_items.at(row).editable) { + rc |= Qt::ItemIsSelectable; + // Can change name in list mode only + if (m_viewMode == QListView::ListMode) + rc |= Qt::ItemIsEditable; + } + return rc; +} + +int WidgetBoxCategoryModel::rowCount(const QModelIndex & /*parent*/) const +{ + return m_items.size(); +} + +bool WidgetBoxCategoryModel::removeRows(int row, int count, const QModelIndex & parent) +{ + if (row < 0 || count < 1) + return false; + const int size = m_items.size(); + const int last = row + count - 1; + if (row >= size || last >= size) + return false; + beginRemoveRows(parent, row, last); + for (int r = last; r >= row; r--) + m_items.removeAt(r); + endRemoveRows(); + return true; +} + +QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryModel::widgetAt(const QModelIndex & index) const +{ + return widgetAt(index.row()); +} + +QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryModel::widgetAt(int row) const +{ + if (row < 0 || row >= m_items.size()) + return QDesignerWidgetBoxInterface::Widget(); + return m_items.at(row).widget; +} + +/* WidgetSubBoxItemDelegate, ensures a valid name using a regexp validator */ + +class WidgetBoxCategoryEntryDelegate : public QItemDelegate +{ +public: + explicit WidgetBoxCategoryEntryDelegate(QWidget *parent = nullptr) : QItemDelegate(parent) {} + QWidget *createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; +}; + +QWidget *WidgetBoxCategoryEntryDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QWidget *result = QItemDelegate::createEditor(parent, option, index); + if (QLineEdit *line_edit = qobject_cast(result)) { + static const QRegularExpression re(u"^[_a-zA-Z][_a-zA-Z0-9]*$"_s); + Q_ASSERT(re.isValid()); + line_edit->setValidator(new QRegularExpressionValidator(re, line_edit)); + } + return result; +} + +// ---------------------- WidgetBoxCategoryListView + +WidgetBoxCategoryListView::WidgetBoxCategoryListView(QDesignerFormEditorInterface *core, QWidget *parent) : + QListView(parent), + m_proxyModel(new QSortFilterProxyModel(this)), + m_model(new WidgetBoxCategoryModel(core, this)) +{ + setFocusPolicy(Qt::NoFocus); + setFrameShape(QFrame::NoFrame); + setIconSize(QSize(22, 22)); + setSpacing(1); + setTextElideMode(Qt::ElideMiddle); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setResizeMode(QListView::Adjust); + setUniformItemSizes(true); + + setItemDelegate(new WidgetBoxCategoryEntryDelegate(this)); + + connect(this, &QListView::pressed, this, + &WidgetBoxCategoryListView::slotPressed); + setEditTriggers(QAbstractItemView::AnyKeyPressed); + + m_proxyModel->setSourceModel(m_model); + m_proxyModel->setFilterRole(FilterRole); + setModel(m_proxyModel); + connect(m_model, &QAbstractItemModel::dataChanged, + this, &WidgetBoxCategoryListView::scratchPadChanged); +} + +void WidgetBoxCategoryListView::setViewMode(ViewMode vm) +{ + QListView::setViewMode(vm); + m_model->setViewMode(vm); +} + +void WidgetBoxCategoryListView::setCurrentItem(AccessMode am, int row) +{ + const QModelIndex index = am == FilteredAccess ? + m_proxyModel->index(row, 0) : + m_proxyModel->mapFromSource(m_model->index(row, 0)); + + if (index.isValid()) + setCurrentIndex(index); +} + +void WidgetBoxCategoryListView::slotPressed(const QModelIndex &index) +{ + const QDesignerWidgetBoxInterface::Widget wgt = m_model->widgetAt(m_proxyModel->mapToSource(index)); + if (wgt.isNull()) + return; + emit widgetBoxPressed(wgt.name(), widgetDomXml(wgt), QCursor::pos()); +} + +void WidgetBoxCategoryListView::removeCurrentItem() +{ + const QModelIndex index = currentIndex(); + if (!index.isValid() || !m_proxyModel->removeRow(index.row())) + return; + + // We check the unfiltered item count here, we don't want to get removed if the + // filtered view is empty + if (m_model->rowCount()) { + emit itemRemoved(); + } else { + emit lastItemRemoved(); + } +} + +void WidgetBoxCategoryListView::editCurrentItem() +{ + const QModelIndex index = currentIndex(); + if (index.isValid()) + edit(index); +} + +int WidgetBoxCategoryListView::count(AccessMode am) const +{ + return am == FilteredAccess ? m_proxyModel->rowCount() : m_model->rowCount(); +} + +int WidgetBoxCategoryListView::mapRowToSource(int filterRow) const +{ + const QModelIndex filterIndex = m_proxyModel->index(filterRow, 0); + return m_proxyModel->mapToSource(filterIndex).row(); +} + +QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryListView::widgetAt(AccessMode am, const QModelIndex & index) const +{ + const QModelIndex unfilteredIndex = am == FilteredAccess ? m_proxyModel->mapToSource(index) : index; + return m_model->widgetAt(unfilteredIndex); +} + +QDesignerWidgetBoxInterface::Widget WidgetBoxCategoryListView::widgetAt(AccessMode am, int row) const +{ + return m_model->widgetAt(am == UnfilteredAccess ? row : mapRowToSource(row)); +} + +void WidgetBoxCategoryListView::removeRow(AccessMode am, int row) +{ + m_model->removeRow(am == UnfilteredAccess ? row : mapRowToSource(row)); +} + +bool WidgetBoxCategoryListView::containsWidget(const QString &name) +{ + return m_model->indexOfWidget(name) != -1; +} + +void WidgetBoxCategoryListView::addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable) +{ + m_model->addWidget(widget, icon, editable); +} + +QString WidgetBoxCategoryListView::widgetDomXml(const QDesignerWidgetBoxInterface::Widget &widget) +{ + QString domXml = widget.domXml(); + + if (domXml.isEmpty()) + domXml = uiOpeningTagC + ""_L1 + uiClosingTagC; + return domXml; +} + +void WidgetBoxCategoryListView::filter(const QString &needle, Qt::CaseSensitivity caseSensitivity) +{ + m_proxyModel->setFilterFixedString(needle); + m_proxyModel->setFilterCaseSensitivity(caseSensitivity); +} + +QDesignerWidgetBoxInterface::Category WidgetBoxCategoryListView::category() const +{ + return m_model->category(); +} + +bool WidgetBoxCategoryListView::removeCustomWidgets() +{ + return m_model->removeCustomWidgets(); +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/widgetbox/widgetboxcategorylistview.h b/src/tools/designer/src/components/widgetbox/widgetboxcategorylistview.h new file mode 100644 index 00000000000..9223a2f49eb --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetboxcategorylistview.h @@ -0,0 +1,79 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOXCATEGORYLISTVIEW_H +#define WIDGETBOXCATEGORYLISTVIEW_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerDnDItemInterface; + +class QSortFilterProxyModel; + +namespace qdesigner_internal { + +class WidgetBoxCategoryModel; + +// List view of a category, switchable between icon and list mode. +// Provides a filtered view. +class WidgetBoxCategoryListView : public QListView +{ + Q_OBJECT +public: + // Whether to access the filtered or unfiltered view + enum AccessMode { FilteredAccess, UnfilteredAccess }; + + explicit WidgetBoxCategoryListView(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + void setViewMode(ViewMode vm); + + void dropWidgets(const QList &item_list); + + using QListView::contentsSize; + + // These methods operate on the filtered/unfiltered model according to accessmode + int count(AccessMode am) const; + QDesignerWidgetBoxInterface::Widget widgetAt(AccessMode am, const QModelIndex &index) const; + QDesignerWidgetBoxInterface::Widget widgetAt(AccessMode am, int row) const; + void removeRow(AccessMode am, int row); + void setCurrentItem(AccessMode am, int row); + + // These methods operate on the unfiltered model and are used for serialization + void addWidget(const QDesignerWidgetBoxInterface::Widget &widget, const QIcon &icon, bool editable); + bool containsWidget(const QString &name); + QDesignerWidgetBoxInterface::Category category() const; + bool removeCustomWidgets(); + + // Helper: Ensure a tag in the case of empty XML + static QString widgetDomXml(const QDesignerWidgetBoxInterface::Widget &widget); + +signals: + void scratchPadChanged(); + void widgetBoxPressed(const QString &name, const QString &xml, const QPoint &globalPos); + void itemRemoved(); + void lastItemRemoved(); + +public slots: + void filter(const QString &needle, Qt::CaseSensitivity caseSensitivity); + void removeCurrentItem(); + void editCurrentItem(); + +private slots: + void slotPressed(const QModelIndex &index); + +private: + int mapRowToSource(int filterRow) const; + QSortFilterProxyModel *m_proxyModel; + WidgetBoxCategoryModel *m_model; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETBOXCATEGORYLISTVIEW_H diff --git a/src/tools/designer/src/components/widgetbox/widgetboxtreewidget.cpp b/src/tools/designer/src/components/widgetbox/widgetboxtreewidget.cpp new file mode 100644 index 00000000000..4dca4a1d188 --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetboxtreewidget.cpp @@ -0,0 +1,981 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "widgetboxtreewidget.h" +#include "widgetboxcategorylistview.h" + +// shared +#include +#include +#include +#include +#include + +// sdk +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto widgetBoxRootElementC = "widgetbox"_L1; +static constexpr auto wbWidgetElementC = "widget"_L1; +static constexpr auto uiElementC = "ui"_L1; +static constexpr auto categoryElementC = "category"_L1; +static constexpr auto categoryEntryElementC = "categoryentry"_L1; +static constexpr auto wbNameAttributeC = "name"_L1; +static constexpr auto typeAttributeC = "type"_L1; +static constexpr auto iconAttributeC = "icon"_L1; +static constexpr auto defaultTypeValueC = "default"_L1; +static constexpr auto customValueC = "custom"_L1; +static constexpr auto iconPrefixC = "__qt_icon__"_L1; +static constexpr auto scratchPadValueC = "scratchpad"_L1; +static constexpr auto invisibleNameC = "[invisible]"_L1; + +enum TopLevelRole { NORMAL_ITEM, SCRATCHPAD_ITEM, CUSTOM_ITEM }; + +static void setTopLevelRole(TopLevelRole tlr, QTreeWidgetItem *item) +{ + item->setData(0, Qt::UserRole, QVariant(tlr)); +} + +static TopLevelRole topLevelRole(const QTreeWidgetItem *item) +{ + return static_cast(item->data(0, Qt::UserRole).toInt()); +} + +namespace qdesigner_internal { + +WidgetBoxTreeWidget::WidgetBoxTreeWidget(QDesignerFormEditorInterface *core, QWidget *parent) : + QTreeWidget(parent), + m_core(core), + m_iconMode(false), + m_scratchPadDeleteTimer(nullptr) +{ + setFocusPolicy(Qt::NoFocus); + setIndentation(0); + setRootIsDecorated(false); + setColumnCount(1); + header()->hide(); + header()->setSectionResizeMode(QHeaderView::Stretch); + setTextElideMode(Qt::ElideMiddle); + setVerticalScrollMode(ScrollPerPixel); + + setItemDelegate(new SheetDelegate(this, this)); + + connect(this, &QTreeWidget::itemPressed, + this, &WidgetBoxTreeWidget::handleMousePress); +} + +QIcon WidgetBoxTreeWidget::iconForWidget(const QString &iconName) const +{ + if (iconName.isEmpty()) + return qdesigner_internal::qtLogoIcon(); + + if (iconName.startsWith(iconPrefixC)) { + const auto it = m_pluginIcons.constFind(iconName); + if (it != m_pluginIcons.constEnd()) + return it.value(); + } + return createIconSet(iconName); +} + +WidgetBoxCategoryListView *WidgetBoxTreeWidget::categoryViewAt(int idx) const +{ + WidgetBoxCategoryListView *rc = nullptr; + if (QTreeWidgetItem *cat_item = topLevelItem(idx)) + if (QTreeWidgetItem *embedItem = cat_item->child(0)) + rc = qobject_cast(itemWidget(embedItem, 0)); + Q_ASSERT(rc); + return rc; +} + +static constexpr auto widgetBoxSettingsGroupC = "WidgetBox"_L1; +static constexpr auto widgetBoxExpandedKeyC = "Closed categories"_L1; +static constexpr auto widgetBoxViewModeKeyC = "View mode"_L1; + +void WidgetBoxTreeWidget::saveExpandedState() const +{ + QStringList closedCategories; + if (const int numCategories = categoryCount()) { + for (int i = 0; i < numCategories; ++i) { + const QTreeWidgetItem *cat_item = topLevelItem(i); + if (!cat_item->isExpanded()) + closedCategories.append(cat_item->text(0)); + } + } + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(widgetBoxSettingsGroupC); + settings->setValue(widgetBoxExpandedKeyC, closedCategories); + settings->setValue(widgetBoxViewModeKeyC, m_iconMode); + settings->endGroup(); +} + +void WidgetBoxTreeWidget::restoreExpandedState() +{ + using StringSet = QSet; + QDesignerSettingsInterface *settings = m_core->settingsManager(); + const QString groupKey = widgetBoxSettingsGroupC + u'/'; + m_iconMode = settings->value(groupKey + widgetBoxViewModeKeyC).toBool(); + updateViewMode(); + const auto &closedCategoryList = settings->value(groupKey + widgetBoxExpandedKeyC, QStringList()).toStringList(); + const StringSet closedCategories(closedCategoryList.cbegin(), closedCategoryList.cend()); + expandAll(); + if (closedCategories.isEmpty()) + return; + + if (const int numCategories = categoryCount()) { + for (int i = 0; i < numCategories; ++i) { + QTreeWidgetItem *item = topLevelItem(i); + if (closedCategories.contains(item->text(0))) + item->setExpanded(false); + } + } +} + +WidgetBoxTreeWidget::~WidgetBoxTreeWidget() +{ + saveExpandedState(); +} + +void WidgetBoxTreeWidget::setFileName(const QString &file_name) +{ + m_file_name = file_name; +} + +QString WidgetBoxTreeWidget::fileName() const +{ + return m_file_name; +} + +bool WidgetBoxTreeWidget::save() +{ + if (fileName().isEmpty()) + return false; + + QFile file(fileName()); + if (!file.open(QIODevice::WriteOnly)) + return false; + + CategoryList cat_list; + const int count = categoryCount(); + for (int i = 0; i < count; ++i) + cat_list.append(category(i)); + + QXmlStreamWriter writer(&file); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(1); + writer.writeStartDocument(); + writeCategories(writer, cat_list); + writer.writeEndDocument(); + + return true; +} + +void WidgetBoxTreeWidget::slotSave() +{ + save(); +} + +void WidgetBoxTreeWidget::handleMousePress(QTreeWidgetItem *item) +{ + if (item == nullptr) + return; + + if (QApplication::mouseButtons() != Qt::LeftButton) + return; + + if (item->parent() == nullptr) { + item->setExpanded(!item->isExpanded()); + return; + } +} + +int WidgetBoxTreeWidget::ensureScratchpad() +{ + const int existingIndex = indexOfScratchpad(); + if (existingIndex != -1) + return existingIndex; + + QTreeWidgetItem *scratch_item = new QTreeWidgetItem(this); + scratch_item->setText(0, tr("Scratchpad")); + setTopLevelRole(SCRATCHPAD_ITEM, scratch_item); + addCategoryView(scratch_item, false); // Scratchpad in list mode. + return categoryCount() - 1; +} + +WidgetBoxCategoryListView *WidgetBoxTreeWidget::addCategoryView(QTreeWidgetItem *parent, bool iconMode) +{ + QTreeWidgetItem *embed_item = new QTreeWidgetItem(parent); + embed_item->setFlags(Qt::ItemIsEnabled); + WidgetBoxCategoryListView *categoryView = new WidgetBoxCategoryListView(m_core, this); + categoryView->setViewMode(iconMode ? QListView::IconMode : QListView::ListMode); + connect(categoryView, &WidgetBoxCategoryListView::scratchPadChanged, + this, &WidgetBoxTreeWidget::slotSave); + connect(categoryView, &WidgetBoxCategoryListView::widgetBoxPressed, + this, &WidgetBoxTreeWidget::widgetBoxPressed); + connect(categoryView, &WidgetBoxCategoryListView::itemRemoved, + this, &WidgetBoxTreeWidget::slotScratchPadItemDeleted); + connect(categoryView, &WidgetBoxCategoryListView::lastItemRemoved, + this, &WidgetBoxTreeWidget::slotLastScratchPadItemDeleted); + setItemWidget(embed_item, 0, categoryView); + return categoryView; +} + +int WidgetBoxTreeWidget::indexOfScratchpad() const +{ + if (const int numTopLevels = topLevelItemCount()) { + for (int i = numTopLevels - 1; i >= 0; --i) { + if (topLevelRole(topLevelItem(i)) == SCRATCHPAD_ITEM) + return i; + } + } + return -1; +} + +int WidgetBoxTreeWidget::indexOfCategory(const QString &name) const +{ + const int topLevelCount = topLevelItemCount(); + for (int i = 0; i < topLevelCount; ++i) { + if (topLevelItem(i)->text(0) == name) + return i; + } + return -1; +} + +bool WidgetBoxTreeWidget::load(QDesignerWidgetBox::LoadMode loadMode) +{ + switch (loadMode) { + case QDesignerWidgetBox::LoadReplace: + clear(); + break; + case QDesignerWidgetBox::LoadCustomWidgetsOnly: + addCustomCategories(true); + updateGeometries(); + return true; + default: + break; + } + + const QString name = fileName(); + + QFile f(name); + if (!f.open(QIODevice::ReadOnly)) // Might not exist at first startup + return false; + + const QString contents = QString::fromUtf8(f.readAll()); + if (!loadContents(contents)) + return false; + if (topLevelItemCount() > 0) { + // QTBUG-93099: Set the single step to the item height to have some + // size-related value. + const auto itemHeight = visualItemRect(topLevelItem(0)).height(); + verticalScrollBar()->setSingleStep(itemHeight); + } + return true; +} + +bool WidgetBoxTreeWidget::loadContents(const QString &contents) +{ + QString errorMessage; + CategoryList cat_list; + if (!readCategories(m_file_name, contents, &cat_list, &errorMessage)) { + qdesigner_internal::designerWarning(errorMessage); + return false; + } + + for (const Category &cat : std::as_const(cat_list)) + addCategory(cat); + + addCustomCategories(false); + // Restore which items are expanded + restoreExpandedState(); + return true; +} + +void WidgetBoxTreeWidget::addCustomCategories(bool replace) +{ + if (replace) { + // clear out all existing custom widgets + if (const int numTopLevels = topLevelItemCount()) { + for (int t = 0; t < numTopLevels ; ++t) + categoryViewAt(t)->removeCustomWidgets(); + } + } + // re-add + const CategoryList customList = loadCustomCategoryList(); + for (const auto &c : customList) + addCategory(c); +} + +static inline QString msgXmlError(const QString &fileName, const QXmlStreamReader &r) +{ + return QDesignerWidgetBox::tr("An error has been encountered at line %1 of %2: %3") + .arg(r.lineNumber()).arg(fileName, r.errorString()); +} + +bool WidgetBoxTreeWidget::readCategories(const QString &fileName, const QString &contents, + CategoryList *cats, QString *errorMessage) +{ + // Read widget box XML: + // + // + // + // + // + // ... + + QXmlStreamReader reader(contents); + + + // Entries of category with name="invisible" should be ignored + bool ignoreEntries = false; + + while (!reader.atEnd()) { + switch (reader.readNext()) { + case QXmlStreamReader::StartElement: { + const auto tag = reader.name(); + if (tag == widgetBoxRootElementC) { + // + continue; + } + if (tag == categoryElementC) { + // + const QXmlStreamAttributes attributes = reader.attributes(); + const QString categoryName = attributes.value(wbNameAttributeC).toString(); + if (categoryName == invisibleNameC) { + ignoreEntries = true; + } else { + Category category(categoryName); + if (attributes.value(typeAttributeC) == scratchPadValueC) + category.setType(Category::Scratchpad); + cats->push_back(category); + } + continue; + } + if (tag == categoryEntryElementC) { + // + if (!ignoreEntries) { + QXmlStreamAttributes attr = reader.attributes(); + const QString widgetName = attr.value(wbNameAttributeC).toString(); + const QString widgetIcon = attr.value(iconAttributeC).toString(); + const WidgetBoxTreeWidget::Widget::Type widgetType = + attr.value(typeAttributeC).toString() + == customValueC ? + WidgetBoxTreeWidget::Widget::Custom : + WidgetBoxTreeWidget::Widget::Default; + + Widget w; + w.setName(widgetName); + w.setIconName(widgetIcon); + w.setType(widgetType); + if (!readWidget(&w, contents, reader)) + continue; + + cats->back().addWidget(w); + } // ignoreEntries + continue; + } + break; + } + case QXmlStreamReader::EndElement: { + const auto tag = reader.name(); + if (tag == widgetBoxRootElementC) { + continue; + } + if (tag == categoryElementC) { + ignoreEntries = false; + continue; + } + if (tag == categoryEntryElementC) { + continue; + } + break; + } + default: break; + } + } + + if (reader.hasError()) { + *errorMessage = msgXmlError(fileName, reader); + return false; + } + + return true; +} + +/*! + * Read out a widget within a category. This can either be + * enclosed in a element or a (legacy) element which may + * contain nested elements. + * + * Examples: + * + * + * ... + * ... + * + * + * or + * + * + * ... + * ... + * + * + * Returns true on success, false if end was reached or an error has been encountered + * in which case the reader has its error flag set. If successful, the current item + * of the reader will be the closing element ( or ) + */ +bool WidgetBoxTreeWidget::readWidget(Widget *w, const QString &xml, QXmlStreamReader &r) +{ + qint64 startTagPosition =0, endTagPosition = 0; + + int nesting = 0; + bool endEncountered = false; + bool parsedWidgetTag = false; + while (!endEncountered) { + const qint64 currentPosition = r.characterOffset(); + switch(r.readNext()) { + case QXmlStreamReader::StartElement: + if (nesting++ == 0) { + // First element must be or (legacy) + const auto name = r.name(); + if (name == uiElementC) { + startTagPosition = currentPosition; + } else { + if (name == wbWidgetElementC) { + startTagPosition = currentPosition; + parsedWidgetTag = true; + } else { + r.raiseError(QDesignerWidgetBox::tr("Unexpected element <%1> encountered when parsing for or ").arg(name.toString())); + return false; + } + } + } else { + // We are within looking for the first tag + if (!parsedWidgetTag && r.name() == wbWidgetElementC) { + parsedWidgetTag = true; + } + } + break; + case QXmlStreamReader::EndElement: + // Reached end of widget? + if (--nesting == 0) { + endTagPosition = r.characterOffset(); + endEncountered = true; + } + break; + case QXmlStreamReader::EndDocument: + r.raiseError(QDesignerWidgetBox::tr("Unexpected end of file encountered when parsing widgets.")); + return false; + case QXmlStreamReader::Invalid: + return false; + default: + break; + } + } + if (!parsedWidgetTag) { + r.raiseError(QDesignerWidgetBox::tr("A widget element could not be found.")); + return false; + } + // Oddity: Startposition is 1 off + QString widgetXml = xml.mid(startTagPosition, endTagPosition - startTagPosition); + if (!widgetXml.startsWith(u'<')) + widgetXml.prepend(u'<'); + w->setDomXml(widgetXml); + return true; +} + +void WidgetBoxTreeWidget::writeCategories(QXmlStreamWriter &writer, const CategoryList &cat_list) const +{ + const QString widgetbox = widgetBoxRootElementC; + const QString name = wbNameAttributeC; + const QString type = typeAttributeC; + const QString icon = iconAttributeC; + const QString defaultType = defaultTypeValueC; + const QString category = categoryElementC; + const QString categoryEntry = categoryEntryElementC; + const QString iconPrefix = iconPrefixC; + + // + // + // + // + // + // ... + // + // + // ... + // + // ... + // + // + + writer.writeStartElement(widgetbox); + + for (const Category &cat : cat_list) { + writer.writeStartElement(category); + writer.writeAttribute(name, cat.name()); + if (cat.type() == Category::Scratchpad) + writer.writeAttribute(type, scratchPadValueC); + + const int widgetCount = cat.widgetCount(); + for (int i = 0; i < widgetCount; ++i) { + const Widget wgt = cat.widget(i); + if (wgt.type() == Widget::Custom) + continue; + + writer.writeStartElement(categoryEntry); + writer.writeAttribute(name, wgt.name()); + if (!wgt.iconName().startsWith(iconPrefix)) + writer.writeAttribute(icon, wgt.iconName()); + writer.writeAttribute(type, defaultType); + + const DomUI *domUI = QDesignerWidgetBox::xmlToUi(wgt.name(), WidgetBoxCategoryListView::widgetDomXml(wgt), false); + if (domUI) { + domUI->write(writer); + delete domUI; + } + + writer.writeEndElement(); // categoryEntry + } + writer.writeEndElement(); // categoryEntry + } + + writer.writeEndElement(); // widgetBox +} + +static int findCategory(const QString &name, const WidgetBoxTreeWidget::CategoryList &list) +{ + int idx = 0; + for (const WidgetBoxTreeWidget::Category &cat : list) { + if (cat.name() == name) + return idx; + ++idx; + } + return -1; +} + +static inline bool isValidIcon(const QIcon &icon) +{ + if (!icon.isNull()) { + const auto availableSizes = icon.availableSizes(); + return !availableSizes.isEmpty() && !availableSizes.constFirst().isEmpty(); + } + return false; +} + +WidgetBoxTreeWidget::CategoryList WidgetBoxTreeWidget::loadCustomCategoryList() const +{ + CategoryList result; + + const QDesignerPluginManager *pm = m_core->pluginManager(); + const QDesignerPluginManager::CustomWidgetList customWidgets = pm->registeredCustomWidgets(); + if (customWidgets.isEmpty()) + return result; + + static const QString customCatName = tr("Custom Widgets"); + + const QString invisible = invisibleNameC; + const QString iconPrefix = iconPrefixC; + + for (QDesignerCustomWidgetInterface *c : customWidgets) { + const QString dom_xml = c->domXml(); + if (dom_xml.isEmpty()) + continue; + + const QString pluginName = c->name(); + const QDesignerCustomWidgetData data = pm->customWidgetData(c); + QString displayName = data.xmlDisplayName(); + if (displayName.isEmpty()) + displayName = pluginName; + + QString cat_name = c->group(); + if (cat_name.isEmpty()) + cat_name = customCatName; + else if (cat_name == invisible) + continue; + + int idx = findCategory(cat_name, result); + if (idx == -1) { + result.append(Category(cat_name)); + idx = result.size() - 1; + } + Category &cat = result[idx]; + + const QIcon icon = c->icon(); + + QString icon_name; + if (isValidIcon(icon)) { + icon_name = iconPrefix; + icon_name += pluginName; + m_pluginIcons.insert(icon_name, icon); + } + + cat.addWidget(Widget(displayName, dom_xml, icon_name, Widget::Custom)); + } + + return result; +} + +void WidgetBoxTreeWidget::adjustSubListSize(QTreeWidgetItem *cat_item) +{ + QTreeWidgetItem *embedItem = cat_item->child(0); + if (embedItem == nullptr) + return; + + WidgetBoxCategoryListView *list_widget = static_cast(itemWidget(embedItem, 0)); + list_widget->setFixedWidth(header()->width()); + list_widget->doItemsLayout(); + const int height = qMax(list_widget->contentsSize().height() ,1); + list_widget->setFixedHeight(height); + embedItem->setSizeHint(0, QSize(-1, height - 1)); +} + +int WidgetBoxTreeWidget::categoryCount() const +{ + return topLevelItemCount(); +} + +WidgetBoxTreeWidget::Category WidgetBoxTreeWidget::category(int cat_idx) const +{ + if (cat_idx >= topLevelItemCount()) + return Category(); + + QTreeWidgetItem *cat_item = topLevelItem(cat_idx); + + QTreeWidgetItem *embedItem = cat_item->child(0); + WidgetBoxCategoryListView *categoryView = static_cast(itemWidget(embedItem, 0)); + + Category result = categoryView->category(); + result.setName(cat_item->text(0)); + + switch (topLevelRole(cat_item)) { + case SCRATCHPAD_ITEM: + result.setType(Category::Scratchpad); + break; + default: + result.setType(Category::Default); + break; + } + return result; +} + +void WidgetBoxTreeWidget::addCategory(const Category &cat) +{ + if (cat.widgetCount() == 0) + return; + + const bool isScratchPad = cat.type() == Category::Scratchpad; + WidgetBoxCategoryListView *categoryView; + QTreeWidgetItem *cat_item; + + if (isScratchPad) { + const int idx = ensureScratchpad(); + categoryView = categoryViewAt(idx); + cat_item = topLevelItem(idx); + } else { + const int existingIndex = indexOfCategory(cat.name()); + if (existingIndex == -1) { + cat_item = new QTreeWidgetItem(); + cat_item->setText(0, cat.name()); + setTopLevelRole(NORMAL_ITEM, cat_item); + // insert before scratchpad + const int scratchPadIndex = indexOfScratchpad(); + if (scratchPadIndex == -1) { + addTopLevelItem(cat_item); + } else { + insertTopLevelItem(scratchPadIndex, cat_item); + } + cat_item->setExpanded(true); + categoryView = addCategoryView(cat_item, m_iconMode); + } else { + categoryView = categoryViewAt(existingIndex); + cat_item = topLevelItem(existingIndex); + } + } + // The same categories are read from the file $HOME, avoid duplicates + const int widgetCount = cat.widgetCount(); + for (int i = 0; i < widgetCount; ++i) { + const Widget w = cat.widget(i); + if (!categoryView->containsWidget(w.name())) + categoryView->addWidget(w, iconForWidget(w.iconName()), isScratchPad); + } + adjustSubListSize(cat_item); +} + +void WidgetBoxTreeWidget::removeCategory(int cat_idx) +{ + if (cat_idx >= topLevelItemCount()) + return; + delete takeTopLevelItem(cat_idx); +} + +int WidgetBoxTreeWidget::widgetCount(int cat_idx) const +{ + if (cat_idx >= topLevelItemCount()) + return 0; + // SDK functions want unfiltered access + return categoryViewAt(cat_idx)->count(WidgetBoxCategoryListView::UnfilteredAccess); +} + +WidgetBoxTreeWidget::Widget WidgetBoxTreeWidget::widget(int cat_idx, int wgt_idx) const +{ + if (cat_idx >= topLevelItemCount()) + return Widget(); + // SDK functions want unfiltered access + WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx); + return categoryView->widgetAt(WidgetBoxCategoryListView::UnfilteredAccess, wgt_idx); +} + +void WidgetBoxTreeWidget::addWidget(int cat_idx, const Widget &wgt) +{ + if (cat_idx >= topLevelItemCount()) + return; + + QTreeWidgetItem *cat_item = topLevelItem(cat_idx); + WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx); + + const bool scratch = topLevelRole(cat_item) == SCRATCHPAD_ITEM; + categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), scratch); + adjustSubListSize(cat_item); +} + +void WidgetBoxTreeWidget::removeWidget(int cat_idx, int wgt_idx) +{ + if (cat_idx >= topLevelItemCount()) + return; + + WidgetBoxCategoryListView *categoryView = categoryViewAt(cat_idx); + + // SDK functions want unfiltered access + const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::UnfilteredAccess; + if (wgt_idx >= categoryView->count(am)) + return; + + categoryView->removeRow(am, wgt_idx); +} + +void WidgetBoxTreeWidget::slotScratchPadItemDeleted() +{ + const int scratch_idx = indexOfScratchpad(); + QTreeWidgetItem *scratch_item = topLevelItem(scratch_idx); + adjustSubListSize(scratch_item); + save(); +} + +void WidgetBoxTreeWidget::slotLastScratchPadItemDeleted() +{ + // Remove the scratchpad in the next idle loop + if (!m_scratchPadDeleteTimer) { + m_scratchPadDeleteTimer = new QTimer(this); + m_scratchPadDeleteTimer->setSingleShot(true); + m_scratchPadDeleteTimer->setInterval(0); + connect(m_scratchPadDeleteTimer, &QTimer::timeout, + this, &WidgetBoxTreeWidget::deleteScratchpad); + } + if (!m_scratchPadDeleteTimer->isActive()) + m_scratchPadDeleteTimer->start(); +} + +void WidgetBoxTreeWidget::deleteScratchpad() +{ + const int idx = indexOfScratchpad(); + if (idx == -1) + return; + delete takeTopLevelItem(idx); + save(); +} + + +void WidgetBoxTreeWidget::slotListMode() +{ + m_iconMode = false; + updateViewMode(); +} + +void WidgetBoxTreeWidget::slotIconMode() +{ + m_iconMode = true; + updateViewMode(); +} + +void WidgetBoxTreeWidget::updateViewMode() +{ + if (const int numTopLevels = topLevelItemCount()) { + for (int i = numTopLevels - 1; i >= 0; --i) { + QTreeWidgetItem *topLevel = topLevelItem(i); + // Scratch pad stays in list mode. + const QListView::ViewMode viewMode = m_iconMode && (topLevelRole(topLevel) != SCRATCHPAD_ITEM) ? QListView::IconMode : QListView::ListMode; + WidgetBoxCategoryListView *categoryView = categoryViewAt(i); + if (viewMode != categoryView->viewMode()) { + categoryView->setViewMode(viewMode); + adjustSubListSize(topLevelItem(i)); + } + } + } + + updateGeometries(); +} + +void WidgetBoxTreeWidget::resizeEvent(QResizeEvent *e) +{ + QTreeWidget::resizeEvent(e); + if (const int numTopLevels = topLevelItemCount()) { + for (int i = numTopLevels - 1; i >= 0; --i) + adjustSubListSize(topLevelItem(i)); + } +} + +void WidgetBoxTreeWidget::contextMenuEvent(QContextMenuEvent *e) +{ + QTreeWidgetItem *item = itemAt(e->pos()); + + const bool scratchpad_menu = item != nullptr + && item->parent() != nullptr + && topLevelRole(item->parent()) == SCRATCHPAD_ITEM; + + QMenu menu; + menu.addAction(tr("Expand all"), this, &WidgetBoxTreeWidget::expandAll); + menu.addAction(tr("Collapse all"), this, &WidgetBoxTreeWidget::collapseAll); + menu.addSeparator(); + + QAction *listModeAction = menu.addAction(tr("List View")); + QAction *iconModeAction = menu.addAction(tr("Icon View")); + listModeAction->setCheckable(true); + iconModeAction->setCheckable(true); + QActionGroup *viewModeGroup = new QActionGroup(&menu); + viewModeGroup->addAction(listModeAction); + viewModeGroup->addAction(iconModeAction); + if (m_iconMode) + iconModeAction->setChecked(true); + else + listModeAction->setChecked(true); + connect(listModeAction, &QAction::triggered, this, &WidgetBoxTreeWidget::slotListMode); + connect(iconModeAction, &QAction::triggered, this, &WidgetBoxTreeWidget::slotIconMode); + + if (scratchpad_menu) { + menu.addSeparator(); + WidgetBoxCategoryListView *listView = qobject_cast(itemWidget(item, 0)); + Q_ASSERT(listView); + menu.addAction(tr("Remove"), listView, &WidgetBoxCategoryListView::removeCurrentItem); + if (!m_iconMode) + menu.addAction(tr("Edit name"), listView, &WidgetBoxCategoryListView::editCurrentItem); + } + e->accept(); + menu.exec(mapToGlobal(e->pos())); +} + +void WidgetBoxTreeWidget::dropWidgets(const QList &item_list) +{ + QTreeWidgetItem *scratch_item = nullptr; + WidgetBoxCategoryListView *categoryView = nullptr; + bool added = false; + + for (QDesignerDnDItemInterface *item : item_list) { + QWidget *w = item->widget(); + if (w == nullptr) + continue; + + DomUI *dom_ui = item->domUi(); + if (dom_ui == nullptr) + continue; + + const int scratch_idx = ensureScratchpad(); + scratch_item = topLevelItem(scratch_idx); + categoryView = categoryViewAt(scratch_idx); + + // Temporarily remove the fake toplevel in-between + DomWidget *fakeTopLevel = dom_ui->takeElementWidget(); + DomWidget *firstWidget = nullptr; + if (fakeTopLevel && !fakeTopLevel->elementWidget().isEmpty()) { + firstWidget = fakeTopLevel->elementWidget().constFirst(); + dom_ui->setElementWidget(firstWidget); + } else { + dom_ui->setElementWidget(fakeTopLevel); + continue; + } + + // Serialize to XML + QString xml; + { + QXmlStreamWriter writer(&xml); + writer.setAutoFormatting(true); + writer.setAutoFormattingIndent(1); + writer.writeStartDocument(); + dom_ui->write(writer); + writer.writeEndDocument(); + } + + // Insert fake toplevel again + dom_ui->takeElementWidget(); + dom_ui->setElementWidget(fakeTopLevel); + + const Widget wgt = Widget(w->objectName(), xml); + categoryView->addWidget(wgt, iconForWidget(wgt.iconName()), true); + scratch_item->setExpanded(true); + added = true; + } + + if (added) { + save(); + activateWindow(); + // Is the new item visible in filtered mode? + const WidgetBoxCategoryListView::AccessMode am = WidgetBoxCategoryListView::FilteredAccess; + if (const int count = categoryView->count(am)) + categoryView->setCurrentItem(am, count - 1); + categoryView->adjustSize(); // XXX + adjustSubListSize(scratch_item); + doItemsLayout(); + scrollToItem(scratch_item, PositionAtTop); + } +} + +void WidgetBoxTreeWidget::filter(const QString &f) +{ + const bool empty = f.isEmpty(); + const int numTopLevels = topLevelItemCount(); + bool changed = false; + for (int i = 0; i < numTopLevels; i++) { + QTreeWidgetItem *tl = topLevelItem(i); + WidgetBoxCategoryListView *categoryView = categoryViewAt(i); + // Anything changed? -> Enable the category + const int oldCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess); + categoryView->filter(f, Qt::CaseInsensitive); + const int newCount = categoryView->count(WidgetBoxCategoryListView::FilteredAccess); + if (oldCount != newCount) { + changed = true; + const bool categoryEnabled = newCount > 0 || empty; + if (categoryEnabled) { + categoryView->adjustSize(); + adjustSubListSize(tl); + } + setRowHidden (i, QModelIndex(), !categoryEnabled); + } + } + if (changed) + updateGeometries(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/components/widgetbox/widgetboxtreewidget.h b/src/tools/designer/src/components/widgetbox/widgetboxtreewidget.h new file mode 100644 index 00000000000..f557081050f --- /dev/null +++ b/src/tools/designer/src/components/widgetbox/widgetboxtreewidget.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef WIDGETBOXTREEWIDGET_H +#define WIDGETBOXTREEWIDGET_H + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerDnDItemInterface; + +class QTimer; + +namespace qdesigner_internal { + +class WidgetBoxCategoryListView; + +// WidgetBoxTreeWidget: A tree of categories + +class WidgetBoxTreeWidget : public QTreeWidget +{ + Q_OBJECT + +public: + using Widget = QDesignerWidgetBoxInterface::Widget; + using Category = QDesignerWidgetBoxInterface::Category; + using CategoryList = QDesignerWidgetBoxInterface::CategoryList; + + explicit WidgetBoxTreeWidget(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~WidgetBoxTreeWidget(); + + int categoryCount() const; + Category category(int cat_idx) const; + void addCategory(const Category &cat); + void removeCategory(int cat_idx); + + int widgetCount(int cat_idx) const; + Widget widget(int cat_idx, int wgt_idx) const; + void addWidget(int cat_idx, const Widget &wgt); + void removeWidget(int cat_idx, int wgt_idx); + + void dropWidgets(const QList &item_list); + + void setFileName(const QString &file_name); + QString fileName() const; + bool load(QDesignerWidgetBox::LoadMode loadMode); + bool loadContents(const QString &contents); + bool save(); + QIcon iconForWidget(const QString &iconName) const; + +signals: + void widgetBoxPressed(const QString &name, const QString &dom_xml, + const QPoint &global_mouse_pos); + +public slots: + void filter(const QString &); + +protected: + void contextMenuEvent(QContextMenuEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + +private slots: + void slotSave(); + void slotScratchPadItemDeleted(); + void slotLastScratchPadItemDeleted(); + + void handleMousePress(QTreeWidgetItem *item); + void deleteScratchpad(); + void slotListMode(); + void slotIconMode(); + +private: + WidgetBoxCategoryListView *addCategoryView(QTreeWidgetItem *parent, bool iconMode); + WidgetBoxCategoryListView *categoryViewAt(int idx) const; + void adjustSubListSize(QTreeWidgetItem *cat_item); + + static bool readCategories(const QString &fileName, const QString &xml, CategoryList *cats, QString *errorMessage); + static bool readWidget(Widget *w, const QString &xml, QXmlStreamReader &r); + + CategoryList loadCustomCategoryList() const; + void writeCategories(QXmlStreamWriter &writer, const CategoryList &cat_list) const; + + int indexOfCategory(const QString &name) const; + int indexOfScratchpad() const; + int ensureScratchpad(); + void addCustomCategories(bool replace); + + void saveExpandedState() const; + void restoreExpandedState(); + void updateViewMode(); + + QDesignerFormEditorInterface *m_core; + QString m_file_name; + mutable QHash m_pluginIcons; + bool m_iconMode; + QTimer *m_scratchPadDeleteTimer; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // WIDGETBOXTREEWIDGET_H diff --git a/src/tools/designer/src/designer/CMakeLists.txt b/src/tools/designer/src/designer/CMakeLists.txt new file mode 100644 index 00000000000..08eba889af8 --- /dev/null +++ b/src/tools/designer/src/designer/CMakeLists.txt @@ -0,0 +1,151 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +##################################################################### +## designer App: +##################################################################### + +qt_internal_add_app(designer + SOURCES + ../../../../shared/fontpanel/fontpanel.cpp ../../../../shared/fontpanel/fontpanel_p.h + ../../../../shared/qttoolbardialog/qttoolbardialog.cpp ../../../../shared/qttoolbardialog/qttoolbardialog_p.h ../../../../shared/qttoolbardialog/qttoolbardialog.ui + appfontdialog.cpp appfontdialog.h + assistantclient.cpp assistantclient.h + designer_enums.h + main.cpp + mainwindow.cpp mainwindow.h + newform.cpp newform.h + preferencesdialog.cpp preferencesdialog.h preferencesdialog.ui + qdesigner.cpp qdesigner.h + qdesigner_actions.cpp qdesigner_actions.h + qdesigner_appearanceoptions.cpp qdesigner_appearanceoptions.h qdesigner_appearanceoptions.ui + qdesigner_formwindow.cpp qdesigner_formwindow.h + qdesigner_server.cpp qdesigner_server.h + qdesigner_settings.cpp qdesigner_settings.h + qdesigner_toolwindow.cpp qdesigner_toolwindow.h + qdesigner_workbench.cpp qdesigner_workbench.h + saveformastemplate.cpp saveformastemplate.h saveformastemplate.ui + versiondialog.cpp versiondialog.h + INCLUDE_DIRECTORIES + ../../../../shared/fontpanel + ../../../../shared/qttoolbardialog + ../lib/extension + ../lib/sdk + ../lib/shared + extra + LIBRARIES + Qt::CorePrivate + Qt::DesignerComponentsPrivate + Qt::DesignerPrivate + Qt::Gui + Qt::Network + Qt::Widgets + Qt::Xml + ENABLE_AUTOGEN_TOOLS + uic + PRECOMPILED_HEADER + "qdesigner_pch.h" +) + +# Due to QTBUG-110369, don't add designer as dependency to External Project examples. +qt_internal_skip_dependency_for_examples(designer) + +# Resources: +set(designer_resource_files + "images/designer.png" +) + +qt_internal_add_resource(designer "designer" + PREFIX + "/qt-project.org/designer" + FILES + ${designer_resource_files} +) +set(qttoolbardialog_resource_files + "../../../../shared/qttoolbardialog/images/back.png" + "../../../../shared/qttoolbardialog/images/down.png" + "../../../../shared/qttoolbardialog/images/forward.png" + "../../../../shared/qttoolbardialog/images/minus.png" + "../../../../shared/qttoolbardialog/images/plus.png" + "../../../../shared/qttoolbardialog/images/up.png" +) + +qt_internal_add_resource(designer "qttoolbardialog" + PREFIX + "/qt-project.org/qttoolbardialog" + BASE + "../../../../shared/qttoolbardialog" + FILES + ${qttoolbardialog_resource_files} +) + +set_target_properties(designer PROPERTIES + QT_TARGET_DESCRIPTION "Qt Widgets Designer" +) + +## Scopes: +##################################################################### + +qt_internal_extend_target(designer CONDITION TARGET Qt::PrintSupport + PUBLIC_LIBRARIES + Qt::PrintSupport +) + +qt_internal_extend_target(designer CONDITION NOT QT_BUILD_SHARED_LIBS + DEFINES + QT_DESIGNER_STATIC +) + +if(WIN32) + set_target_properties(designer PROPERTIES + QT_TARGET_RC_ICONS "${CMAKE_CURRENT_SOURCE_DIR}/designer.ico" + ) +endif() + +if(WIN32) + set_target_properties(designer PROPERTIES + QT_TARGET_VERSION "${PROJECT_VERSION}.0" + ) +endif() + +if(UNIX) + set_target_properties(designer PROPERTIES + QT_TARGET_VERSION "${PROJECT_VERSION}" + ) +endif() + +if(APPLE) + set_target_properties(designer PROPERTIES + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info_mac.plist" + MACOSX_BUNDLE TRUE + MACOSX_BUNDLE_ICON_FILE "designer.icns" + OUTPUT_NAME "Designer" + ) + set_source_files_properties(designer.icns PROPERTIES + MACOSX_PACKAGE_LOCATION Resources + ) + target_sources(designer PRIVATE + designer.icns + ) + # special case end + # Set values to be replaced in the custom Info_mac.plist. + # Also package the uifile.icns. + set(ICON "designer.icns") + set(EXECUTABLE "Designer") + set_source_files_properties(uifile.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources) + target_sources(designer PRIVATE uifile.icns) + # special case end +endif() + +# FILETYPES.files = "uifile.icns" +# FILETYPES.path = "Contents/Resources" +# QMAKE_BUNDLE_DATA = "FILETYPES" + +qt_internal_extend_target(designer CONDITION UNIX AND NOT HAIKU AND NOT MACOS + PUBLIC_LIBRARIES + m +) +qt_internal_add_docs(designer + doc/qtdesigner.qdocconf +) diff --git a/src/tools/designer/src/designer/Info_mac.plist b/src/tools/designer/src/designer/Info_mac.plist new file mode 100644 index 00000000000..fd6141528d6 --- /dev/null +++ b/src/tools/designer/src/designer/Info_mac.plist @@ -0,0 +1,35 @@ + + + + + NSPrincipalClass + NSApplication + CFBundleIconFile + @ICON@ + CFBundlePackageType + APPL + CFBundleIdentifier + org.qt-project.Designer + CFBundleSignature + ttxt + CFBundleExecutable + @EXECUTABLE@ + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + ui + + CFBundleTypeIconFile + uifile.icns + CFBundleTypeRole + Editor + LSIsAppleDefaultForType + + + + NSHumanReadableCopyright + (C) 2017 The Qt Company Ltd + + diff --git a/src/tools/designer/src/designer/appfontdialog.cpp b/src/tools/designer/src/designer/appfontdialog.cpp new file mode 100644 index 00000000000..725e6d870e0 --- /dev/null +++ b/src/tools/designer/src/designer/appfontdialog.cpp @@ -0,0 +1,384 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "appfontdialog.h" + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum {FileNameRole = Qt::UserRole + 1, IdRole = Qt::UserRole + 2 }; +enum { debugAppFontWidget = 0 }; + +static constexpr auto fontFileKeyC = "fontFiles"_L1; + +// AppFontManager: Singleton that maintains the mapping of loaded application font +// ids to the file names (which are not stored in QFontDatabase) +// and provides API for loading/unloading fonts as well for saving/restoring settings. + +class AppFontManager +{ + Q_DISABLE_COPY_MOVE(AppFontManager) + AppFontManager(); +public: + static AppFontManager &instance(); + + void save(QDesignerSettingsInterface *s, const QString &prefix) const; + void restore(const QDesignerSettingsInterface *s, const QString &prefix); + + // Return id or -1 + int add(const QString &fontFile, QString *errorMessage); + + bool remove(int id, QString *errorMessage); + bool remove(const QString &fontFile, QString *errorMessage); + bool removeAt(int index, QString *errorMessage); + + // Store loaded fonts as pair of file name and Id + using FileNameFontIdPair = std::pair; + using FileNameFontIdPairs = QList; + const FileNameFontIdPairs &fonts() const; + +private: + FileNameFontIdPairs m_fonts; +}; + +AppFontManager::AppFontManager() = default; + +AppFontManager &AppFontManager::instance() +{ + static AppFontManager rc; + return rc; +} + +void AppFontManager::save(QDesignerSettingsInterface *s, const QString &prefix) const +{ + // Store as list of file names + QStringList fontFiles; + for (const auto &fnp : m_fonts) + fontFiles.push_back(fnp.first); + + s->beginGroup(prefix); + s->setValue(fontFileKeyC, fontFiles); + s->endGroup(); + + if (debugAppFontWidget) + qDebug() << "AppFontManager::saved" << fontFiles.size() << "fonts under " << prefix; +} + +void AppFontManager::restore(const QDesignerSettingsInterface *s, const QString &prefix) +{ + const QString key = prefix + u'/' + fontFileKeyC; + const QStringList fontFiles = s->value(key, QStringList()).toStringList(); + + if (debugAppFontWidget) + qDebug() << "AppFontManager::restoring" << fontFiles.size() << "fonts from " << prefix; + if (!fontFiles.isEmpty()) { + QString errorMessage; + for (const auto &ff : fontFiles) { + if (add(ff, &errorMessage) == -1) + qWarning("%s", qPrintable(errorMessage)); + } + } +} + +int AppFontManager::add(const QString &fontFile, QString *errorMessage) +{ + const QFileInfo inf(fontFile); + if (!inf.isFile()) { + *errorMessage = QCoreApplication::translate("AppFontManager", "'%1' is not a file.").arg(fontFile); + return -1; + } + if (!inf.isReadable()) { + *errorMessage = QCoreApplication::translate("AppFontManager", "The font file '%1' does not have read permissions.").arg(fontFile); + return -1; + } + const QString fullPath = inf.absoluteFilePath(); + // Check if already loaded + for (const auto &fnp : std::as_const(m_fonts)) { + if (fnp.first == fullPath) { + *errorMessage = QCoreApplication::translate("AppFontManager", "The font file '%1' is already loaded.").arg(fontFile); + return -1; + } + } + + const int id = QFontDatabase::addApplicationFont(fullPath); + if (id == -1) { + *errorMessage = QCoreApplication::translate("AppFontManager", "The font file '%1' could not be loaded.").arg(fontFile); + return -1; + } + + if (debugAppFontWidget) + qDebug() << "AppFontManager::add" << fontFile << id; + m_fonts.push_back(FileNameFontIdPair(fullPath, id)); + return id; +} + +bool AppFontManager::remove(int id, QString *errorMessage) +{ + for (qsizetype i = 0, count = m_fonts.size(); i < count; ++i) + if (m_fonts.at(i).second == id) + return removeAt(i, errorMessage); + + *errorMessage = QCoreApplication::translate("AppFontManager", "'%1' is not a valid font id.").arg(id); + return false; +} + +bool AppFontManager::remove(const QString &fontFile, QString *errorMessage) +{ + for (qsizetype i = 0, count = m_fonts.size(); i < count; ++i) + if (m_fonts.at(i).first == fontFile) + return removeAt(i, errorMessage); + + *errorMessage = QCoreApplication::translate("AppFontManager", "There is no loaded font matching the id '%1'.").arg(fontFile); + return false; +} + +bool AppFontManager::removeAt(int index, QString *errorMessage) +{ + Q_ASSERT(index >= 0 && index < m_fonts.size()); + + const QString fontFile = m_fonts[index].first; + const int id = m_fonts[index].second; + + if (debugAppFontWidget) + qDebug() << "AppFontManager::removeAt" << index << '(' << fontFile << id << ')'; + + if (!QFontDatabase::removeApplicationFont(id)) { + *errorMessage = QCoreApplication::translate("AppFontManager", "The font '%1' (%2) could not be unloaded.").arg(fontFile).arg(id); + return false; + } + m_fonts.removeAt(index); + return true; +} + +const AppFontManager::FileNameFontIdPairs &AppFontManager::fonts() const +{ + return m_fonts; +} + +// ------------- AppFontModel +class AppFontModel : public QStandardItemModel { + Q_DISABLE_COPY_MOVE(AppFontModel) +public: + AppFontModel(QObject *parent = nullptr); + + void init(const AppFontManager &mgr); + void add(const QString &fontFile, int id); + int idAt(const QModelIndex &idx) const; +}; + +AppFontModel::AppFontModel(QObject * parent) : + QStandardItemModel(parent) +{ + setHorizontalHeaderLabels(QStringList(AppFontWidget::tr("Fonts"))); +} + +void AppFontModel::init(const AppFontManager &mgr) +{ + using FileNameFontIdPairs = AppFontManager::FileNameFontIdPairs; + + const FileNameFontIdPairs &fonts = mgr.fonts(); + for (const auto &fnp : fonts) + add(fnp.first, fnp.second); +} + +void AppFontModel::add(const QString &fontFile, int id) +{ + const QFileInfo inf(fontFile); + // Root item with base name + QStandardItem *fileItem = new QStandardItem(inf.completeBaseName()); + const QString fullPath = inf.absoluteFilePath(); + fileItem->setData(fullPath, FileNameRole); + fileItem->setToolTip(fullPath); + fileItem->setData(id, IdRole); + fileItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); + + appendRow(fileItem); + const QStringList families = QFontDatabase::applicationFontFamilies(id); + for (const auto &fam : families) { + QStandardItem *familyItem = new QStandardItem(fam); + familyItem->setToolTip(fullPath); + familyItem->setFont(QFont(fam)); + familyItem->setFlags(Qt::ItemIsEnabled); + fileItem->appendRow(familyItem); + } +} + +int AppFontModel::idAt(const QModelIndex &idx) const +{ + if (const QStandardItem *item = itemFromIndex(idx)) + return item->data(IdRole).toInt(); + return -1; +} + +// ------------- AppFontWidget +AppFontWidget::AppFontWidget(QWidget *parent) : + QGroupBox(parent), + m_view(new QTreeView), + m_addButton(new QToolButton), + m_removeButton(new QToolButton), + m_removeAllButton(new QToolButton), + m_model(new AppFontModel(this)) +{ + m_model->init(AppFontManager::instance()); + m_view->setModel(m_model); + m_view->setSelectionMode(QAbstractItemView::ExtendedSelection); + m_view->expandAll(); + connect(m_view->selectionModel(), &QItemSelectionModel::selectionChanged, this, &AppFontWidget::selectionChanged); + + m_addButton->setToolTip(tr("Add font files")); + m_addButton->setIcon(qdesigner_internal::createIconSet("plus.png"_L1)); + connect(m_addButton, &QAbstractButton::clicked, this, &AppFontWidget::addFiles); + + m_removeButton->setEnabled(false); + m_removeButton->setToolTip(tr("Remove current font file")); + m_removeButton->setIcon(qdesigner_internal::createIconSet("minus.png"_L1)); + connect(m_removeButton, &QAbstractButton::clicked, this, &AppFontWidget::slotRemoveFiles); + + m_removeAllButton->setToolTip(tr("Remove all font files")); + m_removeAllButton->setIcon(qdesigner_internal::createIconSet(QIcon::ThemeIcon::EditDelete, + "editdelete.png"_L1)); + connect(m_removeAllButton, &QAbstractButton::clicked, this, &AppFontWidget::slotRemoveAll); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->addWidget(m_addButton); + hLayout->addWidget(m_removeButton); + hLayout->addWidget(m_removeAllButton); + hLayout->addItem(new QSpacerItem(0, 0,QSizePolicy::MinimumExpanding)); + + QVBoxLayout *vLayout = new QVBoxLayout; + vLayout->addWidget(m_view); + vLayout->addLayout(hLayout); + setLayout(vLayout); +} + +void AppFontWidget::addFiles() +{ + const QStringList files = + QFileDialog::getOpenFileNames(this, tr("Add Font Files"), QString(), + tr("Font files (*.ttf)")); + if (files.isEmpty()) + return; + + QString errorMessage; + + AppFontManager &fmgr = AppFontManager::instance(); + for (const auto &f : files) { + const int id = fmgr.add(f, &errorMessage); + if (id != -1) { + m_model->add(f, id); + } else { + QMessageBox::critical(this, tr("Error Adding Fonts"), errorMessage); + } + } + m_view->expandAll(); +} + +static void removeFonts(const QModelIndexList &selectedIndexes, AppFontModel *model, QWidget *dialogParent) +{ + if (selectedIndexes.isEmpty()) + return; + + // Reverse sort top level rows and remove + AppFontManager &fmgr = AppFontManager::instance(); + QList rows; + rows.reserve(selectedIndexes.size()); + + QString errorMessage; + for (const auto &mi : selectedIndexes) { + const int id = model->idAt(mi); + if (id != -1) { + if (fmgr.remove(id, &errorMessage)) { + rows.append(mi.row()); + } else { + QMessageBox::critical(dialogParent, AppFontWidget::tr("Error Removing Fonts"), errorMessage); + } + } + } + + std::stable_sort(rows.begin(), rows.end()); + for (qsizetype i = rows.size() - 1; i >= 0; --i) + model->removeRow(rows.at(i)); +} + +void AppFontWidget::slotRemoveFiles() +{ + removeFonts(m_view->selectionModel()->selectedIndexes(), m_model, this); +} + +void AppFontWidget::slotRemoveAll() +{ + const int count = m_model->rowCount(); + if (!count) + return; + + const QMessageBox::StandardButton answer = + QMessageBox::question(this, tr("Remove Fonts"), tr("Would you like to remove all fonts?"), + QMessageBox::Yes|QMessageBox::No, QMessageBox::No); + if (answer == QMessageBox::No) + return; + + QModelIndexList topLevels; + for (int i = 0; i < count; i++) + topLevels.push_back(m_model->index(i, 0)); + removeFonts(topLevels, m_model, this); +} + +void AppFontWidget::selectionChanged(const QItemSelection &selected, const QItemSelection & /*deselected*/) +{ + m_removeButton->setEnabled(!selected.indexes().isEmpty()); +} + +void AppFontWidget::save(QDesignerSettingsInterface *s, const QString &prefix) +{ + AppFontManager::instance().save(s, prefix); +} + +void AppFontWidget::restore(const QDesignerSettingsInterface *s, const QString &prefix) +{ + AppFontManager::instance().restore(s, prefix); +} + +// ------------ AppFontDialog +AppFontDialog::AppFontDialog(QWidget *parent) : + QDialog(parent), + m_appFontWidget(new AppFontWidget) +{ + setAttribute(Qt::WA_DeleteOnClose, true); + setWindowTitle(tr("Additional Fonts")); + setModal(false); + QVBoxLayout *vl = new QVBoxLayout; + vl->addWidget(m_appFontWidget); + + QDialogButtonBox *bb = new QDialogButtonBox(QDialogButtonBox::Close); + QDialog::connect(bb, &QDialogButtonBox::rejected, this, &AppFontDialog::reject); + vl->addWidget(bb); + setLayout(vl); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/appfontdialog.h b/src/tools/designer/src/designer/appfontdialog.h new file mode 100644 index 00000000000..5b33c4a5b93 --- /dev/null +++ b/src/tools/designer/src/designer/appfontdialog.h @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_APPFONTWIDGET_H +#define QDESIGNER_APPFONTWIDGET_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class AppFontModel; + +class QTreeView; +class QToolButton; +class QItemSelection; +class QDesignerSettingsInterface; + +// AppFontWidget: Manages application fonts which the user can load and +// provides API for saving/restoring them. + +class AppFontWidget : public QGroupBox +{ + Q_DISABLE_COPY_MOVE(AppFontWidget) + Q_OBJECT +public: + explicit AppFontWidget(QWidget *parent = nullptr); + + QStringList fontFiles() const; + + static void save(QDesignerSettingsInterface *s, const QString &prefix); + static void restore(const QDesignerSettingsInterface *s, const QString &prefix); + +private slots: + void addFiles(); + void slotRemoveFiles(); + void slotRemoveAll(); + void selectionChanged(const QItemSelection & selected, const QItemSelection & deselected); + +private: + QTreeView *m_view; + QToolButton *m_addButton; + QToolButton *m_removeButton; + QToolButton *m_removeAllButton; + AppFontModel *m_model; +}; + +// AppFontDialog: Non modal dialog for AppFontWidget which has Qt::WA_DeleteOnClose set. + +class AppFontDialog : public QDialog +{ + Q_DISABLE_COPY_MOVE(AppFontDialog) + Q_OBJECT +public: + explicit AppFontDialog(QWidget *parent = nullptr); + +private: + AppFontWidget *m_appFontWidget; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_APPFONTWIDGET_H diff --git a/src/tools/designer/src/designer/assistantclient.cpp b/src/tools/designer/src/designer/assistantclient.cpp new file mode 100644 index 00000000000..43934c13e54 --- /dev/null +++ b/src/tools/designer/src/designer/assistantclient.cpp @@ -0,0 +1,154 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "assistantclient.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { debugAssistantClient = 0 }; + +AssistantClient::AssistantClient() = default; + +AssistantClient::~AssistantClient() +{ + if (isRunning()) { + m_process->terminate(); + m_process->waitForFinished(); + } + delete m_process; +} + +bool AssistantClient::showPage(const QString &path, QString *errorMessage) +{ + const QString cmd = "SetSource "_L1 + path; + return sendCommand(cmd, errorMessage); +} + +bool AssistantClient::activateIdentifier(const QString &identifier, QString *errorMessage) +{ + const QString cmd = "ActivateIdentifier "_L1 + identifier; + return sendCommand(cmd, errorMessage); +} + +bool AssistantClient::activateKeyword(const QString &keyword, QString *errorMessage) +{ + const QString cmd = "ActivateKeyword "_L1 + keyword; + return sendCommand(cmd, errorMessage); +} + +bool AssistantClient::sendCommand(const QString &cmd, QString *errorMessage) +{ + if (debugAssistantClient) + qDebug() << "sendCommand " << cmd; + if (!ensureRunning(errorMessage)) + return false; + if (!m_process->isWritable() || m_process->bytesToWrite() > 0) { + *errorMessage = QCoreApplication::translate("AssistantClient", "Unable to send request: Assistant is not responding."); + return false; + } + QTextStream str(m_process); + str << cmd << "\n\n"; + return true; +} + +bool AssistantClient::isRunning() const +{ + return m_process && m_process->state() != QProcess::NotRunning; +} + +QString AssistantClient::binary() +{ + QString app = QLibraryInfo::path(QLibraryInfo::BinariesPath) + QDir::separator(); +#if !defined(Q_OS_MACOS) + app += "assistant"_L1; +#else + app += "Assistant.app/Contents/MacOS/Assistant"_L1; +#endif + +#if defined(Q_OS_WIN) + app += ".exe"_L1; +#endif + + return app; +} + +void AssistantClient::readyReadStandardError() +{ + qWarning("%s: %s", + qPrintable(QDir::toNativeSeparators(m_process->program())), + m_process->readAllStandardError().constData()); +} + +void AssistantClient::processTerminated(int exitCode, QProcess::ExitStatus exitStatus) +{ + const QString binary = QDir::toNativeSeparators(m_process->program()); + if (exitStatus != QProcess::NormalExit) + qWarning("%s: crashed.", qPrintable(binary)); + else if (exitCode != 0) + qWarning("%s: terminated with exit code %d.", qPrintable(binary), exitCode); +} + +bool AssistantClient::ensureRunning(QString *errorMessage) +{ + if (isRunning()) + return true; + + if (!m_process) { + m_process = new QProcess; + QObject::connect(m_process, QOverload::of(&QProcess::finished), + this, &AssistantClient::processTerminated); + QObject::connect(m_process, &QProcess::readyReadStandardError, + this, &AssistantClient::readyReadStandardError); + } + + const QString app = binary(); + if (!QFileInfo(app).isFile()) { + *errorMessage = QCoreApplication::translate("AssistantClient", "The binary '%1' does not exist.").arg(app); + return false; + } + if (debugAssistantClient) + qDebug() << "Running " << app; + // run + QStringList args{u"-enableRemoteControl"_s}; + m_process->start(app, args); + if (!m_process->waitForStarted()) { + *errorMessage = QCoreApplication::translate("AssistantClient", "Unable to launch assistant (%1).").arg(app); + return false; + } + return true; +} + +QString AssistantClient::documentUrl(const QString &module, int qtVersion) +{ + if (qtVersion == 0) + qtVersion = QT_VERSION; + QString rc; + QTextStream(&rc) << "qthelp://org.qt-project." << module << '.' + << (qtVersion >> 16) << ((qtVersion >> 8) & 0xFF) << (qtVersion & 0xFF) + << '/' << module << '/'; + return rc; +} + +QString AssistantClient::designerManualUrl(int qtVersion) +{ + return documentUrl(u"qtdesigner"_s, qtVersion); +} + +QString AssistantClient::qtReferenceManualUrl(int qtVersion) +{ + return documentUrl(u"qtdoc"_s, qtVersion); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/assistantclient.h b/src/tools/designer/src/designer/assistantclient.h new file mode 100644 index 00000000000..b1ee033d584 --- /dev/null +++ b/src/tools/designer/src/designer/assistantclient.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ASSISTANTCLIENT_H +#define ASSISTANTCLIENT_H + +#include + +QT_BEGIN_NAMESPACE + +class QString; + +class AssistantClient : public QObject +{ + Q_OBJECT + +public: + AssistantClient(); + ~AssistantClient(); + + bool showPage(const QString &path, QString *errorMessage); + bool activateIdentifier(const QString &identifier, QString *errorMessage); + bool activateKeyword(const QString &keyword, QString *errorMessage); + + bool isRunning() const; + + static QString documentUrl(const QString &prefix, int qtVersion = 0); + // Root of the Qt Widgets Designer documentation + static QString designerManualUrl(int qtVersion = 0); + // Root of the Qt Reference documentation + static QString qtReferenceManualUrl(int qtVersion = 0); + +private slots: + void readyReadStandardError(); + void processTerminated(int exitCode, QProcess::ExitStatus exitStatus); + +private: + static QString binary(); + bool sendCommand(const QString &cmd, QString *errorMessage); + bool ensureRunning(QString *errorMessage); + + QProcess *m_process = nullptr; +}; + +QT_END_NAMESPACE + +#endif // ASSISTANTCLIENT_H diff --git a/src/tools/designer/src/designer/designer.icns b/src/tools/designer/src/designer/designer.icns new file mode 100644 index 00000000000..b0800b5fcb7 Binary files /dev/null and b/src/tools/designer/src/designer/designer.icns differ diff --git a/src/tools/designer/src/designer/designer.ico b/src/tools/designer/src/designer/designer.ico new file mode 100644 index 00000000000..d44ef332d5f Binary files /dev/null and b/src/tools/designer/src/designer/designer.ico differ diff --git a/src/tools/designer/src/designer/designer_enums.h b/src/tools/designer/src/designer/designer_enums.h new file mode 100644 index 00000000000..87f67d10392 --- /dev/null +++ b/src/tools/designer/src/designer/designer_enums.h @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DESIGNERENUMS_H +#define DESIGNERENUMS_H + +enum UIMode +{ + NeutralMode, + TopLevelMode, + DockedMode +}; + +#endif // DESIGNERENUMS_H diff --git a/src/tools/designer/src/designer/doc/images/addressbook-tutorial-part3-labeled-layout.png b/src/tools/designer/src/designer/doc/images/addressbook-tutorial-part3-labeled-layout.png new file mode 100644 index 00000000000..cfcec31e698 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/addressbook-tutorial-part3-labeled-layout.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-action-editor.png b/src/tools/designer/src/designer/doc/images/designer-action-editor.png new file mode 100644 index 00000000000..cfc3e3a6165 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-action-editor.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-add-custom-toolbar.png b/src/tools/designer/src/designer/doc/images/designer-add-custom-toolbar.png new file mode 100644 index 00000000000..fe165869dac Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-add-custom-toolbar.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-add-files-button.png b/src/tools/designer/src/designer/doc/images/designer-add-files-button.png new file mode 100644 index 00000000000..45ff4a04000 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-add-files-button.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-add-resource-entry-button.png b/src/tools/designer/src/designer/doc/images/designer-add-resource-entry-button.png new file mode 100644 index 00000000000..e29fcf8038b Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-add-resource-entry-button.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-adding-dockwidget.png b/src/tools/designer/src/designer/doc/images/designer-adding-dockwidget.png new file mode 100644 index 00000000000..f48c6d90f15 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-adding-dockwidget.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-adding-dynamic-property.png b/src/tools/designer/src/designer/doc/images/designer-adding-dynamic-property.png new file mode 100644 index 00000000000..32352f6433b Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-adding-dynamic-property.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-adding-menu-action.png b/src/tools/designer/src/designer/doc/images/designer-adding-menu-action.png new file mode 100644 index 00000000000..5b0ea9964d5 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-adding-menu-action.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-adding-toolbar-action.png b/src/tools/designer/src/designer/doc/images/designer-adding-toolbar-action.png new file mode 100644 index 00000000000..9ca083ba0da Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-adding-toolbar-action.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-buddy-making.png b/src/tools/designer/src/designer/doc/images/designer-buddy-making.png new file mode 100644 index 00000000000..3d8e8a113d6 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-buddy-making.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-buddy-mode.png b/src/tools/designer/src/designer/doc/images/designer-buddy-mode.png new file mode 100644 index 00000000000..48197f698eb Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-buddy-mode.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-buddy-tool.png b/src/tools/designer/src/designer/doc/images/designer-buddy-tool.png new file mode 100644 index 00000000000..2a4287089b0 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-buddy-tool.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-choosing-form.png b/src/tools/designer/src/designer/doc/images/designer-choosing-form.png new file mode 100644 index 00000000000..c6a6dcd1cfc Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-choosing-form.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-code-viewer.png b/src/tools/designer/src/designer/doc/images/designer-code-viewer.png new file mode 100644 index 00000000000..6af9477a028 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-code-viewer.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-connection-dialog.png b/src/tools/designer/src/designer/doc/images/designer-connection-dialog.png new file mode 100644 index 00000000000..3577c08e483 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-connection-dialog.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-connection-editing.png b/src/tools/designer/src/designer/doc/images/designer-connection-editing.png new file mode 100644 index 00000000000..5f791cecf80 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-connection-editing.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-connection-editor.png b/src/tools/designer/src/designer/doc/images/designer-connection-editor.png new file mode 100644 index 00000000000..09d9a7e9e30 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-connection-editor.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-connection-highlight.png b/src/tools/designer/src/designer/doc/images/designer-connection-highlight.png new file mode 100644 index 00000000000..089d1e422b1 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-connection-highlight.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-connection-making.png b/src/tools/designer/src/designer/doc/images/designer-connection-making.png new file mode 100644 index 00000000000..a7ce33fe2a9 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-connection-making.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-connection-mode.png b/src/tools/designer/src/designer/doc/images/designer-connection-mode.png new file mode 100644 index 00000000000..1bf7593ae70 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-connection-mode.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-connection-to-form.png b/src/tools/designer/src/designer/doc/images/designer-connection-to-form.png new file mode 100644 index 00000000000..320f70ff479 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-connection-to-form.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-connection-tool.png b/src/tools/designer/src/designer/doc/images/designer-connection-tool.png new file mode 100644 index 00000000000..71c9b07a8d8 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-connection-tool.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-containers-dockwidget.png b/src/tools/designer/src/designer/doc/images/designer-containers-dockwidget.png new file mode 100644 index 00000000000..f4dcc0b7f05 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-containers-dockwidget.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-containers-frame.png b/src/tools/designer/src/designer/doc/images/designer-containers-frame.png new file mode 100644 index 00000000000..d16823a4b52 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-containers-frame.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-containers-groupbox.png b/src/tools/designer/src/designer/doc/images/designer-containers-groupbox.png new file mode 100644 index 00000000000..d347e2f47ef Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-containers-groupbox.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-containers-stackedwidget.png b/src/tools/designer/src/designer/doc/images/designer-containers-stackedwidget.png new file mode 100644 index 00000000000..3239e529169 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-containers-stackedwidget.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-containers-tabwidget.png b/src/tools/designer/src/designer/doc/images/designer-containers-tabwidget.png new file mode 100644 index 00000000000..dab3dfdb634 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-containers-tabwidget.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-containers-toolbox.png b/src/tools/designer/src/designer/doc/images/designer-containers-toolbox.png new file mode 100644 index 00000000000..63fb3e018be Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-containers-toolbox.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-dynamic-property.png b/src/tools/designer/src/designer/doc/images/designer-creating-dynamic-property.png new file mode 100644 index 00000000000..4ef6225d9bf Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-dynamic-property.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry1.png b/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry1.png new file mode 100644 index 00000000000..19491f7741e Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry1.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry2.png b/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry2.png new file mode 100644 index 00000000000..3757b8d3713 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry2.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry3.png b/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry3.png new file mode 100644 index 00000000000..2334e96a51c Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry3.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry4.png b/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry4.png new file mode 100644 index 00000000000..bdf3bbb86a1 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menu-entry4.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menu.png b/src/tools/designer/src/designer/doc/images/designer-creating-menu.png new file mode 100644 index 00000000000..9e4cd7d5c74 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menu.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menu1.png b/src/tools/designer/src/designer/doc/images/designer-creating-menu1.png new file mode 100644 index 00000000000..cb805422c26 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menu1.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menu2.png b/src/tools/designer/src/designer/doc/images/designer-creating-menu2.png new file mode 100644 index 00000000000..9e4cd7d5c74 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menu2.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menu3.png b/src/tools/designer/src/designer/doc/images/designer-creating-menu3.png new file mode 100644 index 00000000000..98354730107 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menu3.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menu4.png b/src/tools/designer/src/designer/doc/images/designer-creating-menu4.png new file mode 100644 index 00000000000..4b83e1ec661 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menu4.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-menubar.png b/src/tools/designer/src/designer/doc/images/designer-creating-menubar.png new file mode 100644 index 00000000000..ae9c5d3b6e5 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-menubar.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-creating-toolbar.png b/src/tools/designer/src/designer/doc/images/designer-creating-toolbar.png new file mode 100644 index 00000000000..bd2b5ca7300 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-creating-toolbar.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-custom-widget-box.png b/src/tools/designer/src/designer/doc/images/designer-custom-widget-box.png new file mode 100644 index 00000000000..fe5314578c3 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-custom-widget-box.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-customize-toolbar.png b/src/tools/designer/src/designer/doc/images/designer-customize-toolbar.png new file mode 100644 index 00000000000..3afecfed206 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-customize-toolbar.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-dialog-final.png b/src/tools/designer/src/designer/doc/images/designer-dialog-final.png new file mode 100644 index 00000000000..c1363b8bd96 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-dialog-final.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-dialog-initial.png b/src/tools/designer/src/designer/doc/images/designer-dialog-initial.png new file mode 100644 index 00000000000..6644e758e8b Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-dialog-initial.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-dialog-layout.png b/src/tools/designer/src/designer/doc/images/designer-dialog-layout.png new file mode 100644 index 00000000000..bae945d4516 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-dialog-layout.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-dialog-preview.png b/src/tools/designer/src/designer/doc/images/designer-dialog-preview.png new file mode 100644 index 00000000000..c905822ee33 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-dialog-preview.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-disambiguation.png b/src/tools/designer/src/designer/doc/images/designer-disambiguation.png new file mode 100644 index 00000000000..1c83a50cc4b Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-disambiguation.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-dragging-onto-form.png b/src/tools/designer/src/designer/doc/images/designer-dragging-onto-form.png new file mode 100644 index 00000000000..07b4393a4fb Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-dragging-onto-form.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-edit-resource.png b/src/tools/designer/src/designer/doc/images/designer-edit-resource.png new file mode 100644 index 00000000000..a26214390e6 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-edit-resource.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-edit-resources-button.png b/src/tools/designer/src/designer/doc/images/designer-edit-resources-button.png new file mode 100644 index 00000000000..14ffc68e43f Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-edit-resources-button.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-editing-mode.png b/src/tools/designer/src/designer/doc/images/designer-editing-mode.png new file mode 100644 index 00000000000..9241fc3facb Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-editing-mode.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-embedded-preview.png b/src/tools/designer/src/designer/doc/images/designer-embedded-preview.png new file mode 100644 index 00000000000..163172822ec Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-embedded-preview.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-english-dialog.png b/src/tools/designer/src/designer/doc/images/designer-english-dialog.png new file mode 100644 index 00000000000..769e2863c91 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-english-dialog.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-examples.png b/src/tools/designer/src/designer/doc/images/designer-examples.png new file mode 100644 index 00000000000..784f2196b9c Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-examples.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-file-menu.png b/src/tools/designer/src/designer/doc/images/designer-file-menu.png new file mode 100644 index 00000000000..187cffaa177 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-file-menu.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-find-icon.png b/src/tools/designer/src/designer/doc/images/designer-find-icon.png new file mode 100644 index 00000000000..aa84bada048 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-find-icon.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-form-layout-cleanlooks.png b/src/tools/designer/src/designer/doc/images/designer-form-layout-cleanlooks.png new file mode 100644 index 00000000000..3a3b88863f1 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-form-layout-cleanlooks.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-form-layout-macintosh.png b/src/tools/designer/src/designer/doc/images/designer-form-layout-macintosh.png new file mode 100644 index 00000000000..44f8b0dadbf Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-form-layout-macintosh.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-form-layout-windowsXP.png b/src/tools/designer/src/designer/doc/images/designer-form-layout-windowsXP.png new file mode 100644 index 00000000000..2c188106e0a Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-form-layout-windowsXP.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-form-layout.png b/src/tools/designer/src/designer/doc/images/designer-form-layout.png new file mode 100644 index 00000000000..d77b927294c Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-form-layout.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-form-layoutfunction.png b/src/tools/designer/src/designer/doc/images/designer-form-layoutfunction.png new file mode 100644 index 00000000000..c94b8f62c68 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-form-layoutfunction.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-form-settings.png b/src/tools/designer/src/designer/doc/images/designer-form-settings.png new file mode 100644 index 00000000000..a2f68dfe2a2 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-form-settings.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-form-viewcode.png b/src/tools/designer/src/designer/doc/images/designer-form-viewcode.png new file mode 100644 index 00000000000..48c889ccc4e Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-form-viewcode.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-french-dialog.png b/src/tools/designer/src/designer/doc/images/designer-french-dialog.png new file mode 100644 index 00000000000..ac4ac59edf2 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-french-dialog.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-getting-started.png b/src/tools/designer/src/designer/doc/images/designer-getting-started.png new file mode 100644 index 00000000000..14d949933ad Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-getting-started.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-layout-inserting.png b/src/tools/designer/src/designer/doc/images/designer-layout-inserting.png new file mode 100644 index 00000000000..1b6a52c0e84 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-layout-inserting.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-main-window.png b/src/tools/designer/src/designer/doc/images/designer-main-window.png new file mode 100644 index 00000000000..e3c5fed49ac Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-main-window.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-making-connection.png b/src/tools/designer/src/designer/doc/images/designer-making-connection.png new file mode 100644 index 00000000000..ec8f5899705 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-making-connection.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-manual-containerextension.png b/src/tools/designer/src/designer/doc/images/designer-manual-containerextension.png new file mode 100644 index 00000000000..7f6552bd901 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-manual-containerextension.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-manual-membersheetextension.png b/src/tools/designer/src/designer/doc/images/designer-manual-membersheetextension.png new file mode 100644 index 00000000000..1e634630d6f Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-manual-membersheetextension.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-manual-propertysheetextension.png b/src/tools/designer/src/designer/doc/images/designer-manual-propertysheetextension.png new file mode 100644 index 00000000000..a5b5bf57a85 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-manual-propertysheetextension.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-manual-taskmenuextension.png b/src/tools/designer/src/designer/doc/images/designer-manual-taskmenuextension.png new file mode 100644 index 00000000000..ea76727e96e Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-manual-taskmenuextension.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-multiple-screenshot.png b/src/tools/designer/src/designer/doc/images/designer-multiple-screenshot.png new file mode 100644 index 00000000000..1b84f54fdcf Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-multiple-screenshot.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-object-inspector.png b/src/tools/designer/src/designer/doc/images/designer-object-inspector.png new file mode 100644 index 00000000000..0a43aed716b Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-object-inspector.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-palette-brush-editor.png b/src/tools/designer/src/designer/doc/images/designer-palette-brush-editor.png new file mode 100644 index 00000000000..4a895bc19d9 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-palette-brush-editor.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-palette-editor.png b/src/tools/designer/src/designer/doc/images/designer-palette-editor.png new file mode 100644 index 00000000000..b6ef0e89508 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-palette-editor.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-palette-gradient-editor.png b/src/tools/designer/src/designer/doc/images/designer-palette-gradient-editor.png new file mode 100644 index 00000000000..d4b4d66019f Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-palette-gradient-editor.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-palette-pattern-editor.png b/src/tools/designer/src/designer/doc/images/designer-palette-pattern-editor.png new file mode 100644 index 00000000000..a112257d28b Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-palette-pattern-editor.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-preview-device-skin.png b/src/tools/designer/src/designer/doc/images/designer-preview-device-skin.png new file mode 100644 index 00000000000..10271abbaaf Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-preview-device-skin.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-preview-deviceskin-selection.png b/src/tools/designer/src/designer/doc/images/designer-preview-deviceskin-selection.png new file mode 100644 index 00000000000..af7c5b0b2ac Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-preview-deviceskin-selection.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-preview-style-selection.png b/src/tools/designer/src/designer/doc/images/designer-preview-style-selection.png new file mode 100644 index 00000000000..3f2b3c16e9e Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-preview-style-selection.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-preview-style.png b/src/tools/designer/src/designer/doc/images/designer-preview-style.png new file mode 100644 index 00000000000..ab65c109221 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-preview-style.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-preview-stylesheet.png b/src/tools/designer/src/designer/doc/images/designer-preview-stylesheet.png new file mode 100644 index 00000000000..d54df72855f Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-preview-stylesheet.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-promoting-widgets.png b/src/tools/designer/src/designer/doc/images/designer-promoting-widgets.png new file mode 100644 index 00000000000..789c4f4ad70 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-promoting-widgets.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-property-editor-add-dynamic.png b/src/tools/designer/src/designer/doc/images/designer-property-editor-add-dynamic.png new file mode 100644 index 00000000000..87868a903bd Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-property-editor-add-dynamic.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-property-editor-configure.png b/src/tools/designer/src/designer/doc/images/designer-property-editor-configure.png new file mode 100644 index 00000000000..797e40d11ea Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-property-editor-configure.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-property-editor-link.png b/src/tools/designer/src/designer/doc/images/designer-property-editor-link.png new file mode 100644 index 00000000000..f4562a41688 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-property-editor-link.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-property-editor-remove-dynamic.png b/src/tools/designer/src/designer/doc/images/designer-property-editor-remove-dynamic.png new file mode 100644 index 00000000000..c0bdfe137e6 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-property-editor-remove-dynamic.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-property-editor-toolbar.png b/src/tools/designer/src/designer/doc/images/designer-property-editor-toolbar.png new file mode 100644 index 00000000000..ae6345e93c7 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-property-editor-toolbar.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-property-editor.png b/src/tools/designer/src/designer/doc/images/designer-property-editor.png new file mode 100644 index 00000000000..522dd1f7c2f Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-property-editor.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-reload-resources-button.png b/src/tools/designer/src/designer/doc/images/designer-reload-resources-button.png new file mode 100644 index 00000000000..c101e76ffe6 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-reload-resources-button.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-remove-custom-toolbar.png b/src/tools/designer/src/designer/doc/images/designer-remove-custom-toolbar.png new file mode 100644 index 00000000000..d1aa0ae9ff9 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-remove-custom-toolbar.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-remove-resource-entry-button.png b/src/tools/designer/src/designer/doc/images/designer-remove-resource-entry-button.png new file mode 100644 index 00000000000..212fcefaa29 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-remove-resource-entry-button.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-removing-toolbar-action.png b/src/tools/designer/src/designer/doc/images/designer-removing-toolbar-action.png new file mode 100644 index 00000000000..71d77366683 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-removing-toolbar-action.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-removing-toolbar.png b/src/tools/designer/src/designer/doc/images/designer-removing-toolbar.png new file mode 100644 index 00000000000..be0871de1cb Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-removing-toolbar.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-resource-browser.png b/src/tools/designer/src/designer/doc/images/designer-resource-browser.png new file mode 100644 index 00000000000..223bc104ad5 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-resource-browser.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-resource-selector.png b/src/tools/designer/src/designer/doc/images/designer-resource-selector.png new file mode 100644 index 00000000000..c814bf4f935 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-resource-selector.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-resource-tool.png b/src/tools/designer/src/designer/doc/images/designer-resource-tool.png new file mode 100644 index 00000000000..1fc0bdd61e3 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-resource-tool.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-resources-adding.png b/src/tools/designer/src/designer/doc/images/designer-resources-adding.png new file mode 100644 index 00000000000..30f51d738ef Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-resources-adding.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-resources-editing.png b/src/tools/designer/src/designer/doc/images/designer-resources-editing.png new file mode 100644 index 00000000000..1e935020d3a Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-resources-editing.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-resources-empty.png b/src/tools/designer/src/designer/doc/images/designer-resources-empty.png new file mode 100644 index 00000000000..012232127e5 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-resources-empty.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-resources-using.png b/src/tools/designer/src/designer/doc/images/designer-resources-using.png new file mode 100644 index 00000000000..1cb99fec497 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-resources-using.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-screenshot.png b/src/tools/designer/src/designer/doc/images/designer-screenshot.png new file mode 100644 index 00000000000..c2b91450c34 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-screenshot.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-selecting-widget.png b/src/tools/designer/src/designer/doc/images/designer-selecting-widget.png new file mode 100644 index 00000000000..a358d30142c Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-selecting-widget.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-selecting-widgets.png b/src/tools/designer/src/designer/doc/images/designer-selecting-widgets.png new file mode 100644 index 00000000000..151fda0dd08 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-selecting-widgets.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-set-layout.png b/src/tools/designer/src/designer/doc/images/designer-set-layout.png new file mode 100644 index 00000000000..6a9c3ab697c Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-set-layout.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-set-layout2.png b/src/tools/designer/src/designer/doc/images/designer-set-layout2.png new file mode 100644 index 00000000000..df23859d8da Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-set-layout2.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-splitter-layout.png b/src/tools/designer/src/designer/doc/images/designer-splitter-layout.png new file mode 100644 index 00000000000..c7d8d7fce21 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-splitter-layout.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-stylesheet-options.png b/src/tools/designer/src/designer/doc/images/designer-stylesheet-options.png new file mode 100644 index 00000000000..98a4e5d6797 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-stylesheet-options.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-stylesheet-usage.png b/src/tools/designer/src/designer/doc/images/designer-stylesheet-usage.png new file mode 100644 index 00000000000..eb488239b09 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-stylesheet-usage.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-tab-order-mode.png b/src/tools/designer/src/designer/doc/images/designer-tab-order-mode.png new file mode 100644 index 00000000000..7a1393c7cd9 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-tab-order-mode.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-tab-order-tool.png b/src/tools/designer/src/designer/doc/images/designer-tab-order-tool.png new file mode 100644 index 00000000000..91992653034 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-tab-order-tool.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-widget-box.png b/src/tools/designer/src/designer/doc/images/designer-widget-box.png new file mode 100644 index 00000000000..57cb698f372 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-widget-box.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-widget-filter.png b/src/tools/designer/src/designer/doc/images/designer-widget-filter.png new file mode 100644 index 00000000000..bdee88133a8 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-widget-filter.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-widget-final.png b/src/tools/designer/src/designer/doc/images/designer-widget-final.png new file mode 100644 index 00000000000..a1fd3d3f0f4 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-widget-final.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-widget-initial.png b/src/tools/designer/src/designer/doc/images/designer-widget-initial.png new file mode 100644 index 00000000000..e3e8f6fe135 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-widget-initial.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-widget-layout.png b/src/tools/designer/src/designer/doc/images/designer-widget-layout.png new file mode 100644 index 00000000000..7f9c5d32fa9 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-widget-layout.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-widget-morph.png b/src/tools/designer/src/designer/doc/images/designer-widget-morph.png new file mode 100644 index 00000000000..0435bf0c36f Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-widget-morph.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-widget-preview.png b/src/tools/designer/src/designer/doc/images/designer-widget-preview.png new file mode 100644 index 00000000000..266d1d2a728 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-widget-preview.png differ diff --git a/src/tools/designer/src/designer/doc/images/designer-widget-tool.png b/src/tools/designer/src/designer/doc/images/designer-widget-tool.png new file mode 100644 index 00000000000..3018cd868d4 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/designer-widget-tool.png differ diff --git a/src/tools/designer/src/designer/doc/images/directapproach-calculatorform.png b/src/tools/designer/src/designer/doc/images/directapproach-calculatorform.png new file mode 100644 index 00000000000..37afa0c3085 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/directapproach-calculatorform.png differ diff --git a/src/tools/designer/src/designer/doc/images/qtdesignerextensions.png b/src/tools/designer/src/designer/doc/images/qtdesignerextensions.png new file mode 100644 index 00000000000..814908db9b8 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/qtdesignerextensions.png differ diff --git a/src/tools/designer/src/designer/doc/images/qtdesignerscreenshot.png b/src/tools/designer/src/designer/doc/images/qtdesignerscreenshot.png new file mode 100644 index 00000000000..d71c986df95 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/qtdesignerscreenshot.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-arrangement.png b/src/tools/designer/src/designer/doc/images/rgbController-arrangement.png new file mode 100644 index 00000000000..fcebb2bf956 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-arrangement.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-configure-connection1.png b/src/tools/designer/src/designer/doc/images/rgbController-configure-connection1.png new file mode 100644 index 00000000000..d737bc2580e Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-configure-connection1.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-configure-connection2.png b/src/tools/designer/src/designer/doc/images/rgbController-configure-connection2.png new file mode 100644 index 00000000000..f705cd6f486 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-configure-connection2.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-final-layout.png b/src/tools/designer/src/designer/doc/images/rgbController-final-layout.png new file mode 100644 index 00000000000..f4fab0b18c8 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-final-layout.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-form-gridLayout.png b/src/tools/designer/src/designer/doc/images/rgbController-form-gridLayout.png new file mode 100644 index 00000000000..5647c22a21d Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-form-gridLayout.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-no-toplevel-layout.png b/src/tools/designer/src/designer/doc/images/rgbController-no-toplevel-layout.png new file mode 100644 index 00000000000..93e375c29a7 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-no-toplevel-layout.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-property-editing.png b/src/tools/designer/src/designer/doc/images/rgbController-property-editing.png new file mode 100644 index 00000000000..a5c0aefa302 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-property-editing.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-screenshot.png b/src/tools/designer/src/designer/doc/images/rgbController-screenshot.png new file mode 100644 index 00000000000..e62bb109806 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-screenshot.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-selectForLayout.png b/src/tools/designer/src/designer/doc/images/rgbController-selectForLayout.png new file mode 100644 index 00000000000..36853585768 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-selectForLayout.png differ diff --git a/src/tools/designer/src/designer/doc/images/rgbController-signalsAndSlots.png b/src/tools/designer/src/designer/doc/images/rgbController-signalsAndSlots.png new file mode 100644 index 00000000000..79bc3c9cdba Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/rgbController-signalsAndSlots.png differ diff --git a/src/tools/designer/src/designer/doc/images/worldtimeclockplugin-example.png b/src/tools/designer/src/designer/doc/images/worldtimeclockplugin-example.png new file mode 100644 index 00000000000..33e18fffc34 Binary files /dev/null and b/src/tools/designer/src/designer/doc/images/worldtimeclockplugin-example.png differ diff --git a/src/tools/designer/src/designer/doc/qtdesigner.qdocconf b/src/tools/designer/src/designer/doc/qtdesigner.qdocconf new file mode 100644 index 00000000000..fb0a67e8daf --- /dev/null +++ b/src/tools/designer/src/designer/doc/qtdesigner.qdocconf @@ -0,0 +1,43 @@ +include($QT_INSTALL_DOCS/global/qt-module-defaults.qdocconf) +include($QT_INSTALL_DOCS/config/exampleurl-qttools.qdocconf) + +project = QtDesigner +description = Qt Widgets Designer Manual +examplesinstallpath = designer + +qhp.projects = QtDesigner + +qhp.QtDesigner.file = qtdesigner.qhp +qhp.QtDesigner.namespace = org.qt-project.qtdesigner.$QT_VERSION_TAG +qhp.QtDesigner.virtualFolder = qtdesigner +qhp.QtDesigner.indexTitle = Qt Widgets Designer Manual + +qhp.QtDesigner.subprojects = manual examples classes +qhp.QtDesigner.subprojects.manual.title = Manual +qhp.QtDesigner.subprojects.manual.indexTitle = Qt Widgets Designer Manual +qhp.QtDesigner.subprojects.manual.selectors = fake:page +qhp.QtDesigner.subprojects.examples.title = Examples +qhp.QtDesigner.subprojects.examples.indexTitle = Qt Widgets Designer Examples +qhp.QtDesigner.subprojects.examples.selectors = fake:example +qhp.QtDesigner.subprojects.examples.sortPages = true +qhp.QtDesigner.subprojects.classes.title = C++ Classes +qhp.QtDesigner.subprojects.classes.indexTitle = Qt Widgets Designer C++ Classes +qhp.QtDesigner.subprojects.classes.selectors = class fake:headerfile +qhp.QtDesigner.subprojects.classes.sortPages = true + +language = Cpp + +{headerdirs,sourcedirs} += .. \ + ../../../../uiplugin \ + ../../lib + +exampledirs = ../../../../../examples/designer \ + snippets + +imagedirs = images + +depends += qtdoc qtgui qtwidgets qtcore qtuitools qtquick qtcmake qmake + +navigation.landingpage = "Qt Widgets Designer Manual" +navigation.cppclassespage = "Qt Widgets Designer C++ Classes" + diff --git a/src/tools/designer/src/designer/doc/snippets/CMakeLists.txt b/src/tools/designer/src/designer/doc/snippets/CMakeLists.txt new file mode 100644 index 00000000000..974b43ffdc2 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/CMakeLists.txt @@ -0,0 +1,10 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +add_subdirectory(autoconnection) +add_subdirectory(imagedialog) +add_subdirectory(multipleinheritance) +add_subdirectory(noautoconnection) + +add_subdirectory(singleinheritance) +add_subdirectory(uitools/calculatorform) diff --git a/src/tools/designer/src/designer/doc/snippets/autoconnection/CMakeLists.txt b/src/tools/designer/src/designer/doc/snippets/autoconnection/CMakeLists.txt new file mode 100644 index 00000000000..ba19d4501ee --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/autoconnection/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(autoconnection LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(autoconnection + imagedialog.cpp imagedialog.h imagedialog.ui main.cpp) + +set_target_properties(autoconnection PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(autoconnection PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/tools/designer/src/designer/doc/snippets/autoconnection/autoconnection.pro b/src/tools/designer/src/designer/doc/snippets/autoconnection/autoconnection.pro new file mode 100644 index 00000000000..6937e8e5486 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/autoconnection/autoconnection.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +HEADERS = imagedialog.h +SOURCES = imagedialog.cpp \ + main.cpp diff --git a/src/tools/designer/src/designer/doc/snippets/autoconnection/imagedialog.cpp b/src/tools/designer/src/designer/doc/snippets/autoconnection/imagedialog.cpp new file mode 100644 index 00000000000..8776e38cd4a --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/autoconnection/imagedialog.cpp @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "imagedialog.h" + +ImageDialog::ImageDialog(QWidget *parent) + : QDialog(parent) +{ + setupUi(this); + okButton->setAutoDefault(false); + cancelButton->setAutoDefault(false); + + colorDepthCombo->addItem(tr("2 colors (1 bit per pixel)")); + colorDepthCombo->addItem(tr("4 colors (2 bits per pixel)")); + colorDepthCombo->addItem(tr("16 colors (4 bits per pixel)")); + colorDepthCombo->addItem(tr("256 colors (8 bits per pixel)")); + colorDepthCombo->addItem(tr("65536 colors (16 bits per pixel)")); + colorDepthCombo->addItem(tr("16 million colors (24 bits per pixel)")); + + connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); +} + +void ImageDialog::on_okButton_clicked() +{ + if (nameLineEdit->text().isEmpty()) { + QMessageBox::information(this, tr("No Image Name"), + tr("Please supply a name for the image."), QMessageBox::Cancel); + } else { + accept(); + } +} diff --git a/src/tools/designer/src/designer/doc/snippets/autoconnection/imagedialog.h b/src/tools/designer/src/designer/doc/snippets/autoconnection/imagedialog.h new file mode 100644 index 00000000000..64d6b4e883b --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/autoconnection/imagedialog.h @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef IMAGEDIALOG_H +#define IMAGEDIALOG_H + +#include "ui_imagedialog.h" + +//! [0] +class ImageDialog : public QDialog, private Ui::ImageDialog +{ + Q_OBJECT + +public: + explicit ImageDialog(QWidget *parent = nullptr); + +private slots: + void on_okButton_clicked(); +}; +//! [0] + +#endif diff --git a/src/tools/designer/src/designer/doc/snippets/autoconnection/imagedialog.ui b/src/tools/designer/src/designer/doc/snippets/autoconnection/imagedialog.ui new file mode 100644 index 00000000000..1c5e546f2c0 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/autoconnection/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/tools/designer/src/designer/doc/snippets/autoconnection/main.cpp b/src/tools/designer/src/designer/doc/snippets/autoconnection/main.cpp new file mode 100644 index 00000000000..5c05efddb61 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/autoconnection/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + ImageDialog *dialog = new ImageDialog; + dialog->show(); + return app.exec(); +} diff --git a/src/tools/designer/src/designer/doc/snippets/designer.pro b/src/tools/designer/src/designer/doc/snippets/designer.pro new file mode 100644 index 00000000000..1445ca58b24 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/designer.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs +SUBDIRS = autoconnection \ + imagedialog \ + multipleinheritance \ + noautoconnection \ + singleinheritance diff --git a/src/tools/designer/src/designer/doc/snippets/imagedialog/CMakeLists.txt b/src/tools/designer/src/designer/doc/snippets/imagedialog/CMakeLists.txt new file mode 100644 index 00000000000..3ba729065de --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/imagedialog/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(imagedialog LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(imagedialog + imagedialog.ui main.cpp) + +set_target_properties(imagedialog PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(imagedialog PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/tools/designer/src/designer/doc/snippets/imagedialog/imagedialog.pro b/src/tools/designer/src/designer/doc/snippets/imagedialog/imagedialog.pro new file mode 100644 index 00000000000..c0afe036d56 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/imagedialog/imagedialog.pro @@ -0,0 +1,4 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +SOURCES = main.cpp diff --git a/src/tools/designer/src/designer/doc/snippets/imagedialog/imagedialog.ui b/src/tools/designer/src/designer/doc/snippets/imagedialog/imagedialog.ui new file mode 100644 index 00000000000..1c5e546f2c0 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/imagedialog/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/tools/designer/src/designer/doc/snippets/imagedialog/main.cpp b/src/tools/designer/src/designer/doc/snippets/imagedialog/main.cpp new file mode 100644 index 00000000000..83272df090e --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/imagedialog/main.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "ui_imagedialog.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QDialog *window = new QDialog; + Ui::ImageDialog ui; + ui.setupUi(window); + + window->show(); + return app.exec(); +} diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_default_extensionfactory.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_default_extensionfactory.cpp new file mode 100644 index 00000000000..ab2b5d2d55d --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_default_extensionfactory.cpp @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const + { + if (iid != Q_TYPEID(QDesignerContainerExtension)) + return nullptr; + + if (auto *widget = qobject_cast(object)) + return new MyContainerExtension(widget, parent); + + return nullptr; + } +//! [0] + + +//! [1] + QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const + { + auto *widget = qobject_cast(object); + if (!widget) + return nullptr; + + if (iid == Q_TYPEID(QDesignerTaskMenuExtension)) + return new MyTaskMenuExtension(widget, parent); + + if (iid == Q_TYPEID(QDesignerContainerExtension)) + return new MyContainerExtension(widget, parent); + + return nullptr; + } +//! [1] + + diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_extension.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_extension.cpp new file mode 100644 index 00000000000..b5ed7f4c40b --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_extension.cpp @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *manager = formEditor->extensionManager(); + + auto *propertySheet = qt_extension(manager, widget); + + if(propertySheet) {...} +//! [0] + + +//! [1] + Q_DECLARE_EXTENSION_INTERFACE(MyExtension, "com.mycompany.myproduct.myextension") +//! [1] + + diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_qextensionmanager.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_qextensionmanager.cpp new file mode 100644 index 00000000000..0b79dd60da1 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_extension_qextensionmanager.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + void MyPlugin::initialize(QDesignerFormEditorInterface *formEditor) + { + if (initialized) + return; + + auto *manager = formEditor->extensionManager(); + Q_ASSERT(manager != nullptr); + + manager->registerExtensions(new MyExtensionFactory(manager), + Q_TYPEID(QDesignerTaskMenuExtension)); + + initialized = true; + } +//! [0] + + diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindow.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindow.cpp new file mode 100644 index 00000000000..4a8f5df6bdd --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindow.cpp @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *formWindow = QDesignerFormWindowInterface::findFormWindow(myWidget); +//! [0] + + +//! [1] + QList forms; + + auto *manager = formEditor->formWindowManager(); + + for (int i = 0; i < manager->formWindowCount(); ++i) + forms.append(manager->formWindow(i)); +//! [1] + + +//! [2] + if (formWindow->isManaged(myWidget)) + formWindow->manageWidget(myWidget->childWidget); +//! [2] + + diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowcursor.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowcursor.cpp new file mode 100644 index 00000000000..c6552713b11 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowcursor.cpp @@ -0,0 +1,10 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *formWindow = QDesignerFormWindowInterface::findFormWindow(myWidget); + + formWindow->cursor()->setProperty(myWidget, myProperty, newValue); +//! [0] + + diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowmanager.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowmanager.cpp new file mode 100644 index 00000000000..9427dbdc356 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractformwindowmanager.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *manager = formEditor->formWindowManager(); + auto *formWindow = manager->formWindow(0); + + manager->setActiveFormWindow(formWindow); +//! [0] + + diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp new file mode 100644 index 00000000000..9d806a846b3 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp @@ -0,0 +1,11 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *objectInspector = formEditor->objectInspector(); + auto *manager = formEditor->formWindowManager(); + + objectInspector->setFormWindow(manager->formWindow(0)); +//! [0] + + diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp new file mode 100644 index 00000000000..347988365a4 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp @@ -0,0 +1,25 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *propertyEditor = formEditor->propertyEditor(); + + connect(propertyEditor, &QDesignerPropertyEditorInterface::propertyChanged, + this, &MyClass::checkProperty); +//! [0] + + +//! [1] + void checkProperty(const QString &property, const QVariant &value) + { + auto *propertyEditor = formEditor->propertyEditor(); + + auto *object = propertyeditor->object(); + auto *widget = qobject_cast(object); + + if (widget && property == aProperty && value != expectedValue) + {...} + } +//! [1] + + diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp new file mode 100644 index 00000000000..a893306ea09 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp @@ -0,0 +1,32 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + auto *widgetBox = formEditor->widgetBox(); + + widgetBox->load(); +//! [0] + + +//! [1] + QString originalFile = widgetBox->fileName(); + + widgetBox->setFileName("myWidgetBox.xml"); + widgetBox->save(); +//! [1] + + +//! [2] + widgetBox->setFileName(originalFile); + widgetBox->load(); +//! [2] + + +//! [3] + if (widgetBox->filename() != "myWidgetBox.xml") { + widgetBox->setFileName("myWidgetBox.xml"); + widgetBox->load(); + } +//! [3] + + diff --git a/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_uilib_formbuilder.cpp b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_uilib_formbuilder.cpp new file mode 100644 index 00000000000..2120dcacbf0 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/lib/tools_designer_src_lib_uilib_formbuilder.cpp @@ -0,0 +1,28 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] + MyForm::MyForm(QWidget *parent) + : QWidget(parent) + { + QFormBuilder builder; + QFile file(":/forms/myWidget.ui"); + file.open(QFile::ReadOnly); + QWidget *myWidget = builder.load(&file, this); + file.close(); + + auto *layout = new QVBoxLayout(this); + layout->addWidget(myWidget); + } +//! [0] + + +//! [1] + + + mywidget.ui + + +//! [1] + + diff --git a/src/tools/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.cpp b/src/tools/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.cpp new file mode 100644 index 00000000000..f0483c05b6d --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.cpp @@ -0,0 +1,75 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [1] +#include +//! [1] + + +//! [2] +void on__(); +//! [2] + + +//! [7] +class MyExtension: public QObject, + public QdesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACE(QDesignerContainerExtension) + + ... +} +//! [7] + + +//! [8] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerContainerExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast + (object)) + return new MyContainerExtension(widget, parent); + + return 0; +} +//! [8] + + +//! [9] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerContainerExtension))) { + return new MyContainerExtension(widget, parent); + + } else { + return 0; + } +} +//! [9] + + +//! [10] +void MyPlugin::initialize(QDesignerFormEditorInterface *formEditor) +{ + if (initialized) + return; + + QExtensionManager *manager = formEditor->extensionManager(); + Q_ASSERT(manager != 0); + + manager->registerExtensions(new MyExtensionFactory(manager), + Q_TYPEID(QDesignerTaskMenuExtension)); + + initialized = true; +} +//! [10] diff --git a/src/tools/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.js b/src/tools/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.js new file mode 100644 index 00000000000..da1ad350dff --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.js @@ -0,0 +1,6 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [6] +widget.text = 'Hi - I was built ' + new Date().toString(); +//! [6] diff --git a/src/tools/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.pro b/src/tools/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.pro new file mode 100644 index 00000000000..f09f257e22d --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/manual/doc_src_designer-manual.pro @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#! [0] +QT += uitools +#! [0] + + +#! [3] +CONFIG += release +#! [3] + + +#! [4] +target.path = $$[QT_INSTALL_PLUGINS]/designer +INSTALLS += target +#! [4] + + +#! [5] +QT += script +#! [5] diff --git a/src/tools/designer/src/designer/doc/snippets/multipleinheritance/CMakeLists.txt b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/CMakeLists.txt new file mode 100644 index 00000000000..c7e3d8aaed0 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(multipleinheritance LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(multipleinheritance + imagedialog.cpp imagedialog.h imagedialog.ui main.cpp) + +set_target_properties(multipleinheritance PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(multipleinheritance PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/tools/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.cpp b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.cpp new file mode 100644 index 00000000000..1c34df2bb72 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +ImageDialog::ImageDialog(QWidget *parent) + : QDialog(parent) +{ + setupUi(this); + + colorDepthCombo->addItem(tr("2 colors (1 bit per pixel)")); + colorDepthCombo->addItem(tr("4 colors (2 bits per pixel)")); + colorDepthCombo->addItem(tr("16 colors (4 bits per pixel)")); + colorDepthCombo->addItem(tr("256 colors (8 bits per pixel)")); + colorDepthCombo->addItem(tr("65536 colors (16 bits per pixel)")); + colorDepthCombo->addItem(tr("16 million colors (24 bits per pixel)")); + + connect(okButton, &QAbstractButton::clicked, this, &QDialog::accept); + connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); +} diff --git a/src/tools/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.h b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.h new file mode 100644 index 00000000000..f6bc3b62cb5 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.h @@ -0,0 +1,17 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef IMAGEDIALOG_H +#define IMAGEDIALOG_H + +#include "ui_imagedialog.h" + +class ImageDialog : public QDialog, private Ui::ImageDialog +{ + Q_OBJECT + +public: + explicit ImageDialog(QWidget *parent = nullptr); +}; + +#endif diff --git a/src/tools/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.ui b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.ui new file mode 100644 index 00000000000..1c5e546f2c0 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/tools/designer/src/designer/doc/snippets/multipleinheritance/main.cpp b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/main.cpp new file mode 100644 index 00000000000..5c05efddb61 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + ImageDialog *dialog = new ImageDialog; + dialog->show(); + return app.exec(); +} diff --git a/src/tools/designer/src/designer/doc/snippets/multipleinheritance/multipleinheritance.pro b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/multipleinheritance.pro new file mode 100644 index 00000000000..6937e8e5486 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/multipleinheritance/multipleinheritance.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +HEADERS = imagedialog.h +SOURCES = imagedialog.cpp \ + main.cpp diff --git a/src/tools/designer/src/designer/doc/snippets/noautoconnection/CMakeLists.txt b/src/tools/designer/src/designer/doc/snippets/noautoconnection/CMakeLists.txt new file mode 100644 index 00000000000..967bc640c29 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/noautoconnection/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(noautoconnection LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(noautoconnection + imagedialog.cpp imagedialog.h imagedialog.ui main.cpp) + +set_target_properties(noautoconnection PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(noautoconnection PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/tools/designer/src/designer/doc/snippets/noautoconnection/imagedialog.cpp b/src/tools/designer/src/designer/doc/snippets/noautoconnection/imagedialog.cpp new file mode 100644 index 00000000000..e29c410957d --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/noautoconnection/imagedialog.cpp @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include + +#include "imagedialog.h" + +//! [0] +ImageDialog::ImageDialog(QWidget *parent) + : QDialog(parent) +{ + setupUi(this); + okButton->setAutoDefault(false); + cancelButton->setAutoDefault(false); +//! [0] + + colorDepthCombo->addItem(tr("2 colors (1 bit per pixel)")); + colorDepthCombo->addItem(tr("4 colors (2 bits per pixel)")); + colorDepthCombo->addItem(tr("16 colors (4 bits per pixel)")); + colorDepthCombo->addItem(tr("256 colors (8 bits per pixel)")); + colorDepthCombo->addItem(tr("65536 colors (16 bits per pixel)")); + colorDepthCombo->addItem(tr("16 million colors (24 bits per pixel)")); + + connect(cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); +//! [1] + connect(okButton, &QAbstractButton::clicked, this, &ImageDialog::checkValues); +} +//! [1] + +//! [2] +void ImageDialog::checkValues() +{ + if (nameLineEdit->text().isEmpty()) { + QMessageBox::information(this, tr("No Image Name"), + tr("Please supply a name for the image."), QMessageBox::Cancel); + } else { + accept(); + } +} +//! [2] diff --git a/src/tools/designer/src/designer/doc/snippets/noautoconnection/imagedialog.h b/src/tools/designer/src/designer/doc/snippets/noautoconnection/imagedialog.h new file mode 100644 index 00000000000..611b90119df --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/noautoconnection/imagedialog.h @@ -0,0 +1,22 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef IMAGEDIALOG_H +#define IMAGEDIALOG_H + +#include "ui_imagedialog.h" + +//! [0] +class ImageDialog : public QDialog, private Ui::ImageDialog +{ + Q_OBJECT + +public: + explicit ImageDialog(QWidget *parent = nullptr); + +private slots: + void checkValues(); +}; +//! [0] + +#endif diff --git a/src/tools/designer/src/designer/doc/snippets/noautoconnection/imagedialog.ui b/src/tools/designer/src/designer/doc/snippets/noautoconnection/imagedialog.ui new file mode 100644 index 00000000000..1c5e546f2c0 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/noautoconnection/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/tools/designer/src/designer/doc/snippets/noautoconnection/main.cpp b/src/tools/designer/src/designer/doc/snippets/noautoconnection/main.cpp new file mode 100644 index 00000000000..5c05efddb61 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/noautoconnection/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + ImageDialog *dialog = new ImageDialog; + dialog->show(); + return app.exec(); +} diff --git a/src/tools/designer/src/designer/doc/snippets/noautoconnection/noautoconnection.pro b/src/tools/designer/src/designer/doc/snippets/noautoconnection/noautoconnection.pro new file mode 100644 index 00000000000..6937e8e5486 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/noautoconnection/noautoconnection.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +HEADERS = imagedialog.h +SOURCES = imagedialog.cpp \ + main.cpp diff --git a/src/tools/designer/src/designer/doc/snippets/plugins/doc_src_qtdesigner.cpp b/src/tools/designer/src/designer/doc/snippets/plugins/doc_src_qtdesigner.cpp new file mode 100644 index 00000000000..6ac5db04f80 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/plugins/doc_src_qtdesigner.cpp @@ -0,0 +1,285 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [2] +QDesignerMemberSheetExtension *memberSheet = nullptr; +QExtensionManager manager = formEditor->extensionManager(); + +memberSheet = qt_extension(manager, widget); +int index = memberSheet->indexOf(setEchoMode); +memberSheet->setVisible(index, false); + +delete memberSheet; +//! [2] + + +//! [3] +class MyMemberSheetExtension : public QObject, + public QDesignerMemberSheetExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerMemberSheetExtension) + +public: + ... +} +//! [3] + + +//! [4] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerMemberSheetExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast + (object)) + return new MyMemberSheetExtension(widget, parent); + + return 0; +} +//! [4] + + +//! [5] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerMemberSheetExtension))) { + return new MyMemberSheetExtension(widget, parent); + + } else { + return 0; + } +} +//! [5] + + +//! [6] +class MyContainerExtension : public QObject, + public QDesignerContainerExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerContainerExtension) + +public: + MyContainerExtension(MyCustomWidget *widget, + QObject *parent = 0); + int count() const; + QWidget *widget(int index) const; + int currentIndex() const; + void setCurrentIndex(int index); + void addWidget(QWidget *widget); + void insertWidget(int index, QWidget *widget); + void remove(int index); + +private: + MyCustomWidget *myWidget; +}; +//! [6] + + +//! [7] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerContainerExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast + (object)) + return new MyContainerExtension(widget, parent); + + return 0; +} +//! [7] + + +//! [8] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerContainerExtension))) { + return new MyContainerExtension(widget, parent); + + } else { + return 0; + } +} +//! [8] + + +//! [9] +class MyTaskMenuExtension : public QObject, + public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) + +public: + MyTaskMenuExtension(MyCustomWidget *widget, QObject *parent); + + QAction *preferredEditAction() const; + QList taskActions() const; + +private slots: + void mySlot(); + +private: + MyCustomWidget *widget; + QAction *myAction; +}; +//! [9] + + +//! [10] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerTaskMenuExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast(object)) + return new MyTaskMenuExtension(widget, parent); + + return 0; +} +//! [10] + + +//! [11] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerContainerExtension))) { + return new MyContainerExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else { + return 0; + } +} +//! [11] + + +//! [12] +#include customwidgetoneinterface.h +#include customwidgettwointerface.h +#include customwidgetthreeinterface.h + +#include +#include + +class MyCustomWidgets: public QObject, public QDesignerCustomWidgetCollectionInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetCollectionInterface") + Q_INTERFACES(QDesignerCustomWidgetCollectionInterface) + +public: + MyCustomWidgets(QObject *parent = 0); + + QList customWidgets() const override; + +private: + QList widgets; +}; +//! [12] + + +//! [13] +MyCustomWidgets::MyCustomWidgets(QObject *parent) + : QObject(parent) +{ + widgets.append(new CustomWidgetOneInterface(this)); + widgets.append(new CustomWidgetTwoInterface(this)); + widgets.append(new CustomWidgetThreeInterface(this)); +} + +QList MyCustomWidgets::customWidgets() const +{ + return widgets; +} +//! [13] + + +//! [14] +Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface") +//! [14] + + +//! [15] +QDesignerPropertySheetExtension *propertySheet = nullptr; +QExtensionManager manager = formEditor->extensionManager(); + +propertySheet = qt_extension(manager, widget); +int index = propertySheet->indexOf(u"margin"_s); + +propertySheet->setProperty(index, 10); +propertySheet->setChanged(index, true); + +delete propertySheet; +//! [15] + + +//! [16] +class MyPropertySheetExtension : public QObject, + public QDesignerPropertySheetExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension) + +public: + ... +} +//! [16] + + +//! [17] +QObject *ANewExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + if (iid != Q_TYPEID(QDesignerPropertySheetExtension)) + return 0; + + if (MyCustomWidget *widget = qobject_cast + (object)) + return new MyPropertySheetExtension(widget, parent); + + return 0; +} +//! [17] + + +//! [18] +QObject *AGeneralExtensionFactory::createExtension(QObject *object, + const QString &iid, QObject *parent) const +{ + MyCustomWidget *widget = qobject_cast(object); + + if (widget && (iid == Q_TYPEID(QDesignerTaskMenuExtension))) { + return new MyTaskMenuExtension(widget, parent); + + } else if (widget && (iid == Q_TYPEID(QDesignerPropertySheetExtension))) { + return new MyPropertySheetExtension(widget, parent); + + } else { + return 0; + } +} +//! [18] diff --git a/src/tools/designer/src/designer/doc/snippets/singleinheritance/CMakeLists.txt b/src/tools/designer/src/designer/doc/snippets/singleinheritance/CMakeLists.txt new file mode 100644 index 00000000000..ac5c2be1aa2 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/singleinheritance/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +cmake_minimum_required(VERSION 3.16) +project(singleinheritanceinheritance LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(singleinheritanceinheritance + imagedialog.cpp imagedialog.h imagedialog.ui main.cpp) + +set_target_properties(singleinheritanceinheritance PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(singleinheritanceinheritance PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) diff --git a/src/tools/designer/src/designer/doc/snippets/singleinheritance/imagedialog.cpp b/src/tools/designer/src/designer/doc/snippets/singleinheritance/imagedialog.cpp new file mode 100644 index 00000000000..4cf642d1756 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/singleinheritance/imagedialog.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +ImageDialog::ImageDialog(QWidget *parent) + : QDialog(parent) +{ + ui.setupUi(this); + + ui.colorDepthCombo->addItem(tr("2 colors (1 bit per pixel)")); + ui.colorDepthCombo->addItem(tr("4 colors (2 bits per pixel)")); + ui.colorDepthCombo->addItem(tr("16 colors (4 bits per pixel)")); + ui.colorDepthCombo->addItem(tr("256 colors (8 bits per pixel)")); + ui.colorDepthCombo->addItem(tr("65536 colors (16 bits per pixel)")); + ui.colorDepthCombo->addItem(tr("16 million colors (24 bits per pixel)")); + + connect(ui.okButton, &QAbstractButton::clicked, this, &QDialog::accept); + connect(ui.cancelButton, &QAbstractButton::clicked, this, &QDialog::reject); +} diff --git a/src/tools/designer/src/designer/doc/snippets/singleinheritance/imagedialog.h b/src/tools/designer/src/designer/doc/snippets/singleinheritance/imagedialog.h new file mode 100644 index 00000000000..6fd2df0e03a --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/singleinheritance/imagedialog.h @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#ifndef IMAGEDIALOG_H +#define IMAGEDIALOG_H + +#include "ui_imagedialog.h" + +class ImageDialog : public QDialog +{ + Q_OBJECT + +public: + explicit ImageDialog(QWidget *parent = nullptr); + +private: + Ui::ImageDialog ui; +}; + +#endif diff --git a/src/tools/designer/src/designer/doc/snippets/singleinheritance/imagedialog.ui b/src/tools/designer/src/designer/doc/snippets/singleinheritance/imagedialog.ui new file mode 100644 index 00000000000..1c5e546f2c0 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/singleinheritance/imagedialog.ui @@ -0,0 +1,389 @@ + + + ImageDialog + + + ImageDialog + + + + 0 + 0 + 320 + 180 + + + + Create Image + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + widthLabel + + + + 1 + 27 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Width: + + + Qt::AutoText + + + + + + + heightLabel + + + + 1 + 55 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Height: + + + Qt::AutoText + + + + + + + colorDepthCombo + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QComboBox::InsertAtBottom + + + + + + + nameLineEdit + + + + 74 + 83 + 227 + 22 + + + + + 5 + 0 + 1 + 0 + + + + Untitled image + + + QLineEdit::Normal + + + + + + + spinBox + + + + 74 + 1 + 227 + 20 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + spinBox_2 + + + + 74 + 27 + 227 + 22 + + + + + 5 + 0 + 0 + 0 + + + + QAbstractSpinBox::UpDownArrows + + + 32 + + + 1024 + + + 1 + + + + + + + nameLabel + + + + 1 + 1 + 67 + 20 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Name: + + + Qt::AutoText + + + + + + + colorDepthLabel + + + + 1 + 83 + 67 + 22 + + + + QFrame::NoFrame + + + QFrame::Plain + + + Color depth: + + + Qt::AutoText + + + + + + + + + + + + + 9 + 121 + 302 + 18 + + + + Qt::Vertical + + + + + + + + + + 1 + + + 6 + + + + + + + + + 1 + 1 + 128 + 24 + + + + Qt::Horizontal + + + + + + + okButton + + + + 135 + 1 + 80 + 24 + + + + OK + + + + + + + cancelButton + + + + 221 + 1 + 80 + 24 + + + + Cancel + + + + + + + + + + nameLineEdit + spinBox + spinBox_2 + colorDepthCombo + okButton + cancelButton + + + + nameLineEdit + returnPressed() + okButton + animateClick() + + + -1 + 7 + + + -1 + 7 + + + + + diff --git a/src/tools/designer/src/designer/doc/snippets/singleinheritance/main.cpp b/src/tools/designer/src/designer/doc/snippets/singleinheritance/main.cpp new file mode 100644 index 00000000000..5c05efddb61 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/singleinheritance/main.cpp @@ -0,0 +1,14 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#include "imagedialog.h" + +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + ImageDialog *dialog = new ImageDialog; + dialog->show(); + return app.exec(); +} diff --git a/src/tools/designer/src/designer/doc/snippets/singleinheritance/singleinheritance.pro b/src/tools/designer/src/designer/doc/snippets/singleinheritance/singleinheritance.pro new file mode 100644 index 00000000000..6937e8e5486 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/singleinheritance/singleinheritance.pro @@ -0,0 +1,6 @@ +TEMPLATE = app +QT += widgets +FORMS = imagedialog.ui +HEADERS = imagedialog.h +SOURCES = imagedialog.cpp \ + main.cpp diff --git a/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/CMakeLists.txt b/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/CMakeLists.txt new file mode 100644 index 00000000000..ace6f3d1dd4 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/CMakeLists.txt @@ -0,0 +1,26 @@ +# Copyright (C) 2023 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +#! [0] +cmake_minimum_required(VERSION 3.16) +project(calculatorform LANGUAGES CXX) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTOUIC ON) + +find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) + +qt_add_executable(calculatorform + calculatorform.ui main.cpp) + +set_target_properties(calculatorform PROPERTIES + WIN32_EXECUTABLE TRUE + MACOSX_BUNDLE TRUE +) + +target_link_libraries(calculatorform PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets +) +#! [0] diff --git a/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.pro b/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.pro new file mode 100644 index 00000000000..1a18378f813 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.pro @@ -0,0 +1,5 @@ +#! [0] +TEMPLATE = app +FORMS = calculatorform.ui +SOURCES = main.cpp +#! [0] diff --git a/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.ui b/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.ui new file mode 100644 index 00000000000..dda0e62ddd0 --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/calculatorform.ui @@ -0,0 +1,303 @@ + + + + + CalculatorForm + + + CalculatorForm + + + + 0 + 0 + 276 + 98 + + + + + 5 + 5 + 0 + 0 + + + + Calculator Builder + + + + + + + 9 + + + 6 + + + + + + + + 1 + + + 6 + + + + + + + + 1 + + + 6 + + + + + label + + + + 1 + 1 + 45 + 19 + + + + Input 1 + + + + + + + inputSpinBox1 + + + + 1 + 26 + 45 + 25 + + + + true + + + + + + + + + label_3 + + + + 54 + 1 + 7 + 52 + + + + + + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label_2 + + + + 1 + 1 + 45 + 19 + + + + Input 2 + + + + + + + inputSpinBox2 + + + + 1 + 26 + 45 + 25 + + + + true + + + + + + + + + label_3_2 + + + + 120 + 1 + 7 + 52 + + + + = + + + Qt::AlignCenter + + + + + + + + + + 1 + + + 6 + + + + + label_2_2_2 + + + + 1 + 1 + 37 + 17 + + + + Output + + + + + + + outputWidget + + + + 1 + 24 + 37 + 27 + + + + QFrame::Box + + + QFrame::Sunken + + + 0 + + + Qt::AlignAbsolute|Qt::AlignBottom|Qt::AlignCenter|Qt::AlignHCenter|Qt::AlignHorizontal_Mask|Qt::AlignJustify|Qt::AlignLeading|Qt::AlignLeft|Qt::AlignRight|Qt::AlignTop|Qt::AlignTrailing|Qt::AlignVCenter|Qt::AlignVertical_Mask + + + + + + + + + + + verticalSpacer + + + + 85 + 69 + 20 + 20 + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + horizontalSpacer + + + + 188 + 26 + 79 + 20 + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + diff --git a/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/main.cpp b/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/main.cpp new file mode 100644 index 00000000000..8e9c32bb08c --- /dev/null +++ b/src/tools/designer/src/designer/doc/snippets/uitools/calculatorform/main.cpp @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause + +//! [0] +#include "ui_calculatorform.h" +//! [0] +#include + +//! [1] +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QWidget widget; + Ui::CalculatorForm ui; + ui.setupUi(&widget); + + widget.show(); + return app.exec(); +} +//! [1] diff --git a/src/tools/designer/src/designer/doc/src/designer-custom-widgets.qdoc b/src/tools/designer/src/designer/doc/src/designer-custom-widgets.qdoc new file mode 100644 index 00000000000..d7cc6a0fa53 --- /dev/null +++ b/src/tools/designer/src/designer/doc/src/designer-custom-widgets.qdoc @@ -0,0 +1,107 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtdesigner-components.html + \title Creating and Using Components for Qt Widgets Designer + \brief How to create and use custom widget plugins. + \ingroup best-practices + + \tableofcontents + + \section1 Creating Custom Widget Plugins + + When implementing a custom widget plugin for \QD, you must + subclass QDesignerCustomWidgetInterface to expose your custom + widget to \QD. A single custom widget plugin is built as a + separate library. If you want to include several custom widget + plugins in the same library, you must in addition subclass + QDesignerCustomWidgetCollectionInterface. + + To provide your custom widget plugin with the expected behavior + and functionality within \QD's workspace you can subclass the + associated extension classes: + + The QDesignerContainerExtension class allows you to add pages to a + custom multi-page container. The QDesignerTaskMenuExtension class + allows you to add custom menu entries to \QD's task menu. The + QDesignerMemberSheetExtension class allows you to manipulate a + widget's member functions which is displayed when configuring + connections using \QD's mode for editing signals and slots. And + finally, the QDesignerPropertySheetExtension class allows you to + manipulate a widget's properties which is displayed in \QD's + property editor. + + \image qtdesignerextensions.png + + In \QD the extensions are not created until they are required. For + that reason, when implementing extensions, you must also subclass + QExtensionFactory, i.e create a class that is able to make + instances of your extensions. In addition, you must make \QD's + extension manager register your factory; the extension manager + controls the construction of extensions as they are required, and + you can access it through QDesignerFormEditorInterface and + QExtensionManager. + + For a complete example creating a custom widget plugin with an + extension, see the \l {taskmenuextension}{Task Menu + Extension} or \l {containerextension}{Container + Extension} examples. + + \section1 Retrieving Access to \QD Components + + The purpose of the classes mentioned in this section is to provide + access to \QD's components, managers and workspace, and they are + not intended to be instantiated directly. + + \QD is composed by several components. It has an action editor, a + property editor, widget box and object inspector which you can + view in its workspace. + + \image qtdesignerscreenshot.png + + \QD also has an object that works behind the scene; it contains + the logic that integrates all of \QD's components into a coherent + application. You can access this object, using the + QDesignerFormEditorInterface, to retrieve interfaces to \QD's + components: + + \list + \li QDesignerActionEditorInterface + \li QDesignerObjectInspectorInterface + \li QDesignerPropertyEditorInterface + \li QDesignerWidgetBoxInterface + \endlist + + In addition, you can use QDesignerFormEditorInterface to retrieve + interfaces to \QD's extension manager (QExtensionManager) and form + window manager (QDesignerFormWindowManagerInterface). The + extension manager controls the construction of extensions as they + are required, while the form window manager controls the form + windows appearing in \QD's workspace. + + Once you have an interface to \QD's form window manager + (QDesignerFormWindowManagerInterface), you also have access to all + the form windows currently appearing in \QD's workspace: The + QDesignerFormWindowInterface class allows you to query and + manipulate the form windows, and it provides an interface to the + form windows' cursors. QDesignerFormWindowCursorInterface is a + convenience class allowing you to query and modify a given form + window's widget selection, and in addition modify the properties + of all the form's widgets. + + \section1 Creating User Interfaces at Run-Time + + The \c QtDesigner module contains the QFormBuilder class that + provides a mechanism for dynamically creating user interfaces at + run-time, based on UI files created with \QD. This class is + typically used by custom components and applications that embed + \QD. Standalone applications that need to dynamically generate + user interfaces at run-time use the QUiLoader class, found in + the QtUiTools module. + + For a complete example using QUiLoader, see + the \l {calculatorbuilder}{Calculator Builder example}. + + \sa {Qt Widgets Designer Manual}, {Qt UI Tools} +*/ diff --git a/src/tools/designer/src/designer/doc/src/designer-examples.qdoc b/src/tools/designer/src/designer/doc/src/designer-examples.qdoc new file mode 100644 index 00000000000..6eb9dc8a031 --- /dev/null +++ b/src/tools/designer/src/designer/doc/src/designer-examples.qdoc @@ -0,0 +1,30 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \group examples-designer + \ingroup all-examples + \title Qt Widgets Designer Examples + \brief Using \QD to build your UI. + + \QD is a capable graphical user interface designer that lets you + create and configure forms without writing code. GUIs created with + \QD can be compiled into an application or created at run-time. + + The following examples illustrate how to create and use \QD forms + and how to create \QD custom widget plugins. +*/ + +/* + \list + \li \l{arthurplugin}{Arthur Plugin} + \li \l{calculatorbuilder}{Calculator Builder}\raisedaster + \li \l{calculatorform}{Calculator Form}\raisedaster + \li \l{calculatorform_mi}{Calculator Form/Multiple Inheritance}\raisedaster + \li \l{customwidgetplugin}{Custom Widget Plugin}\raisedaster + \li \l{taskmenuextension}{Task Menu Extension}\raisedaster + \li \l{containerextension}{Container Extension}\raisedaster + \endlist + + Examples marked with an asterisk (*) are fully documented. +*/ diff --git a/src/tools/designer/src/designer/doc/src/designer-manual.qdoc b/src/tools/designer/src/designer/doc/src/designer-manual.qdoc new file mode 100644 index 00000000000..50a7835f14d --- /dev/null +++ b/src/tools/designer/src/designer/doc/src/designer-manual.qdoc @@ -0,0 +1,2967 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtdesigner-manual.html + + \title Qt Widgets Designer Manual + \ingroup qttools + \keyword Qt Widgets Designer + + \QD is the Qt tool for designing and building graphical user + interfaces (GUIs) with \l {Qt Widgets}. For user interface design with + \l {Qt Quick}, see \l {Qt Design Studio Manual} {Qt Design Studio}. + + You can compose and customize your windows or dialogs in a + what-you-see-is-what-you-get (WYSIWYG) manner, and test them using different + styles and resolutions. Widgets and forms created with \QD integrate + seamlessly with programmed code, using Qt's signals and slots mechanism, so + that you can easily assign behavior to graphical elements. All properties + set in \QD can be changed dynamically within the code. Furthermore, features + like widget promotion and custom plugins allow you to use your own + components with \QD. + + \note You have the option of using \l {Qt Quick} and + \l {Qt Design Studio Manual}{Qt Design Studio} for user interface + design rather than widgets. It is a much easier way to write many kinds of + applications. It enables a completely customizable appearance, + touch-reactive elements, and smooth animated transitions, taking advantage + of hardware acceleration. + + If you are new to \QD, you can take a look at the + \l{Getting To Know Qt Widgets Designer} document. For a quick tutorial on how to + use \QD, refer to \l{A Quick Start to Qt Widgets Designer}. + + \image designer-multiple-screenshot.png + + \section1 Table of Contents + + \list + \li \l{A Quick Start to Qt Widgets Designer} + \li \l{Qt Widgets Designer's Editing Modes} + \list + \li \l{Qt Widgets Designer's Widget Editing Mode}{Widget Editing Mode} + \li \l{Qt Widgets Designer's Signals and Slots Editing Mode} + {Signals and Slots Editing Mode} + \li \l{Qt Widgets Designer's Buddy Editing Mode} + {Buddy Editing Mode} + \li \l{Qt Widgets Designer's Tab Order Editing Mode} + {Tab Order Editing Mode} + \endlist + \li \l{Using Layouts in Qt Widgets Designer} + \li \l{Saving, Previewing and Printing Forms in Qt Widgets Designer} + \li \l{Using Containers in Qt Widgets Designer} + \li \l{Creating Main Windows in Qt Widgets Designer} + \li \l{Editing Resources with Qt Widgets Designer} + \li \l{Using Stylesheets with Qt Widgets Designer} + \li \l{Using a Designer UI File in Your C++ Application} + \li \l{Using a Designer UI File in Your Qt for Python Application} + \li Advanced Use + \list + \li \l{Customizing Qt Widgets Designer Forms} + \li \l{Using Custom Widgets with Qt Widgets Designer} + \li \l{Creating Custom Widgets for Qt Widgets Designer} + \li \l{Creating Custom Widget Extensions} + \li \l{Qt Widgets Designer's UI File Format} + \endlist + \endlist +*/ + + +/*! + \page designer-to-know.html + + + \title Getting to Know Qt Widgets Designer + + \image designer-screenshot.png + + \section1 Launching Designer + + Once you have installed Qt, you can start \QD in the same way as any other + application on the development host. You can also launch \QD directly from + Qt Creator. Qt Creator automatically opens all .ui files in the integrated + \QD, in \gui Design mode. + + Generally, the integrated \QD contains the same functions as the standalone + \QD. For more information about the differences, see the + \l{http://doc.qt.io/qtcreator/index.html}{Qt Creator Manual}. + + If you have large forms that do not fit in the Qt Creator \gui Design mode, + you can open them in the stand-alone \QD. + + \section1 The User Interface + + When used as a standalone application, \QD's user interface can be + configured to provide either a multi-window user interface (the default + mode), or it can be used in docked window mode. When used from within an + integrated development environment (IDE) only the multi-window user + interface is available. You can switch modes in the \gui Preferences dialog + from the \gui Edit menu. + + In multi-window mode, you can arrange each of the tool windows to suit your + working style. The main window consists of a menu bar, a tool bar, and a + widget box that contains the widgets you can use to create your user + interface. + + \target MainWindow + \table + \row + \li \inlineimage designer-main-window.png + \li \b{Qt Widgets Designer's Main Window} + + The menu bar provides all the standard actions for managing forms, + using the clipboard, and accessing application-specific help. + The current editing mode, the tool windows, and the forms in use can + also be accessed via the menu bar. + + The tool bar displays common actions that are used when editing a form. + These are also available via the main menu. + + The widget box provides common widgets and layouts that are used to + design components. These are grouped into categories that reflect their + uses or features. + \endtable + + Most features of \QD are accessible via the menu bar, the tool bar, or the + widget box. Some features are also available through context menus that can + be opened over the form windows. On most platforms, the right mouse is used + to open context menus. + + \target WidgetBox + \table + \row + \li \inlineimage designer-widget-box.png + \li \b{Qt Widgets Designer's Widget Box} + + The widget box provides a selection of standard Qt widgets, layouts, + and other objects that can be used to create user interfaces on forms. + Each of the categories in the widget box contain widgets with similar + uses or related features. + + You can display all of the available objects in a category by clicking + on the handle next to the category label. When in + \l{Qt Widgets Designer's Widget Editing Mode}{Widget Editing + Mode}, you can add objects to a form by dragging the appropriate items + from the widget box onto the form, and dropping them in the required + locations. + + \QD provides a scratch pad feature that allows you to collect + frequently used objects in a separate category. The scratch pad + category can be filled with any widget currently displayed in a form + by dragging them from the form and dropping them onto the widget box. + These widgets can be used in the same way as any other widgets, but + they can also contain child widgets. Open a context menu over a widget + to change its name or remove it from the scratch pad. + \endtable + + + \section1 The Concept of Layouts in Qt + + A layout is used to arrange and manage the elements that make up a user + interface. Qt provides a number of classes to automatically handle layouts + -- QHBoxLayout, QVBoxLayout, QGridLayout, and QFormLayout. These classes + solve the challenge of laying out widgets automatically, providing a user + interface that behaves predictably. Fortunately knowledge of the layout + classes is not required to arrange widgets with \QD. Instead, select one of + the \gui{Lay Out Horizontally}, \gui{Lay Out in a Grid}, etc., options from + the context menu. + + Each Qt widget has a recommended size, known as \l{QWidget::}{sizeHint()}. + The layout manager will attempt to resize a widget to meet its size hint. + In some cases, there is no need to have a different size. For example, the + height of a QLineEdit is always a fixed value, depending on font size and + style. In other cases, you may require the size to change, e.g., the width + of a QLineEdit or the width and height of item view widgets. This is where + the widget size constraints -- \l{QWidget::minimumSize()}{minimumSize} and + \l{QWidget::maximumSize()}{maximumSize} constraints come into play. These + are properties you can set in the property editor. For example, to override + the default \l{QWidget::}{sizeHint()}, simply set + \l{QWidget::minimumSize()}{minimumSize} and \l{QWidget::maximumSize()} + {maximumSize} to the same value. Alternatively, to use the current size as + a size constraint value, choose one of the \gui{Size Constraint} options + from the widget's context menu. The layout will then ensure that those + constraints are met. To control the size of your widgets via code, you can + reimplement \l{QWidget::}{sizeHint()} in your code. + + The screenshot below shows the breakdown of a basic user interface designed + using a grid. The coordinates on the screenshot show the position of each + widget within the grid. + + \image addressbook-tutorial-part3-labeled-layout.png + + \note Inside the grid, the QPushButton objects are actually nested. The + buttons on the right are first placed in a QVBoxLayout; the buttons at the + bottom are first placed in a QHBoxLayout. Finally, they are put into + coordinates (1,2) and (2,1) of the QGridLayout. + + To visualize, imagine the layout as a box that shrinks as much as possible, + attempting to \e squeeze your widgets in a neat arrangement, and, at the + same time, maximize the use of available space. + + Qt's layouts help when you: + + \list 1 + \li Resize the user face to fit different window sizes. + \li Resize elements within the user interface to suit different + localizations. + \li Arrange elements to adhere to layout guidelines for different + platforms. + \endlist + + So, you no longer have to worry about rearranging widgets for different + platforms, settings, and languages. + + The example below shows how different localizations can affect the user + interface. When a localization requires more space for longer text strings + the Qt layout automatically scales to accommodate this, while ensuring that + the user interface looks presentable and still matches the platform + guidelines. + + \table + \header + \li A Dialog in English + \li A Dialog in French + \row + \li \image designer-english-dialog.png + \li \image designer-french-dialog.png + \endtable + + The process of laying out widgets consists of creating the layout hierarchy + while setting as few widget size constraints as possible. + + For a more technical perspective on Qt's layout classes, refer to the + \l{Layout Management} documentation. +*/ + + +/*! + \page designer-quick-start.html + + + \title A Quick Start to Qt Widgets Designer + + Using \QD involves \b four basic steps: + + \list 1 + \li Choose your form and objects + \li Lay the objects out on the form + \li Connect the signals to the slots + \li Preview the form + \endlist + + \image rgbController-screenshot.png + + Suppose you would like to design a small widget (see screenshot above) that + contains the controls needed to manipulate Red, Green and Blue (RGB) values + -- a type of widget that can be seen everywhere in image manipulation + programs. + + \table + \row + \li \inlineimage designer-choosing-form.png + \li \b{Choosing a Form} + + You start by choosing \gui Widget from the \gui{New Form} dialog. + \endtable + + + \table + \row + \li \inlineimage rgbController-arrangement.png + \li \b{Placing Widgets on a Form} + + Drag three labels, three spin boxes and three vertical sliders on to your + form. To change the label's default text, simply double-click on it. You + can arrange them according to how you would like them to be laid out. + \endtable + + To ensure that they are laid out exactly like this in your program, you + need to place these widgets into a layout. We will do this in groups of + three. Select the "RED" label. Then, hold down \key Ctrl while you select + its corresponding spin box and slider. In the \gui{Form} menu, select + \gui{Lay Out in a Grid}. + + \table + \row + \li \inlineimage rgbController-form-gridLayout.png + \li \inlineimage rgbController-selectForLayout.png + \endtable + + + Repeat the step for the other two labels along with their corresponding + spin boxes and sliders as well. + + The next step is to combine all three layouts into one \b{main layout}. + The main layout is the top level widget's (in this case, the QWidget) + layout. It is important that your top level widget has a layout; otherwise, + the widgets on your window will not resize when your window is resized. To + set the layout, \gui{Right click} anywhere on your form, outside of the + three separate layouts, and select \gui{Lay Out Horizontally}. + Alternatively, you could also select \gui{Lay Out in a Grid} -- you will + still see the same arrangement (shown below). + + \image rgbController-final-layout.png + + \note Main layouts cannot be seen on the form. To check if you have a main + layout installed, try resizing your form; your widgets should resize + accordingly. Alternatively, you can take a look at \QD's + \gui{Object Inspector}. If your top level widget does not have a layout, + you will see the broken layout icon next to it, + \inlineimage rgbController-no-toplevel-layout.png + . + + When you click on the slider and drag it to a certain value, you want the + spin box to display the slider's position. To accomplish this behavior, you + need to connect the slider's \l{QAbstractSlider::}{valueChanged()} signal + to the spin box's \l{QSpinBox::}{setValue()} slot. You also need to make + the reverse connections, e.g., connect the spin box's \l{QSpinBox::} + {valueChanged()} signal to the slider's \l{QAbstractSlider::value()} + {setValue()} slot. + + To do this, you have to switch to \gui{Edit Signals/Slots} mode, either by + pressing \key{F4} or selecting \gui{Edit Signals/Slots} from the \gui{Edit} + menu. + + \table + \row + \li \inlineimage rgbController-signalsAndSlots.png + \li \b{Connecting Signals to Slots} + + Click on the slider and drag the cursor towards the spin box. The + \gui{Configure Connection} dialog, shown below, will pop up. Select the + correct signal and slot and click \gui OK. + \endtable + + \image rgbController-configure-connection1.png + + Repeat the step (in reverse order), clicking on the spin box and dragging + the cursor towards the slider, to connect the spin box's + \l{QSpinBox::}{valueChanged()} signal to the slider's + \l{QAbstractSlider::value()}{setValue()} slot. + + You can use the screenshot below as a guide to selecting the correct signal + and slot. + + \image rgbController-configure-connection2.png + + Now that you have successfully connected the objects for the "RED" + component of the RGB Controller, do the same for the "GREEN" and "BLUE" + components as well. + + Since RGB values range between 0 and 255, we need to limit the spin box + and slider to that particular range. + + \table + \row + \li \inlineimage rgbController-property-editing.png + \li \b{Setting Widget Properties} + + Click on the first spin box. Within the \gui{Property Editor}, you will + see \l{QSpinBox}'s properties. Enter "255" for the + \l{QSpinBox::}{maximum} property. Then, click on the first vertical + slider, you will see \l{QAbstractSlider}'s properties. Enter "255" for + the \l{QAbstractSlider::}{maximum} property as well. Repeat this + process for the remaining spin boxes and sliders. + \endtable + + Now, we preview your form to see how it would look in your application - + press \key{Ctrl + R} or select \gui Preview from the \gui Form menu. Try + dragging the slider - the spin box will mirror its value too (and vice + versa). Also, you can resize it to see how the layouts that are used to + manage the child widgets, respond to different window sizes. +*/ + + +/*! + \page designer-editing-mode.html + \previouspage Getting to Know Qt Widgets Designer + \nextpage Using Layouts in Qt Widgets Designer + + \title Qt Widgets Designer's Editing Modes + + \QD provides four editing modes: \l{Qt Widgets Designer's Widget Editing Mode} + {Widget Editing Mode}, \l{Qt Widgets Designer's Signals and Slots Editing Mode} + {Signals and Slots Editing Mode}, \l{Qt Widgets Designer's Buddy Editing Mode} + {Buddy Editing Mode} and \l{Qt Widgets Designer's Tab Order Editing Mode} + {Tab Order Editing Mode}. When working with \QD, you will always be in one + of these four modes. To switch between modes, simply select it from the + \gui{Edit} menu or the toolbar. The table below describes these modes in + further detail. + + \table + \header \li \li \b{Editing Modes} + \row + \li \inlineimage designer-widget-tool.png + \li In \l{Qt Widgets Designer's Widget Editing Mode}{Edit} mode, we can + change the appearance of the form, add layouts, and edit the + properties of each widget. To switch to this mode, press + \key{F3}. This is \QD's default mode. + + \row + \li \inlineimage designer-connection-tool.png + \li In \l{Qt Widgets Designer's Signals and Slots Editing Mode} + {Signals and Slots} mode, we can connect widgets together using + Qt's signals and slots mechanism. To switch to this mode, press + \key{F4}. + + \row + \li \inlineimage designer-buddy-tool.png + \li In \l{Qt Widgets Designer's Buddy Editing Mode}{Buddy Editing Mode}, + buddy widgets can be assigned to label widgets to help them + handle keyboard focus correctly. + + \row + \li \inlineimage designer-tab-order-tool.png + \li In \l{Qt Widgets Designer's Tab Order Editing Mode} + {Tab Order Editing Mode}, we can set the order in which widgets + receive the keyboard focus. + \endtable + +*/ + + +/*! + \page designer-widget-mode.html + \previouspage Qt Widgets Designer's Editing Modes + \nextpage Qt Widgets Designer's Signals and Slots Editing Mode + + \title Qt Widgets Designer's Widget Editing Mode + + \image designer-editing-mode.png + + In the Widget Editing Mode, objects can be dragged from the main window's + widget box to a form, edited, resized, dragged around on the form, and even + dragged between forms. Object properties can be modified interactively, so + that changes can be seen immediately. The editing interface is intuitive + for simple operations, yet it still supports Qt's powerful layout + facilities. + + + \tableofcontents + + To create and edit new forms, open the \gui File menu and select + \gui{New Form...} or press \key{Ctrl+N}. Existing forms can also be edited + by selecting \gui{Open Form...} from the \gui File menu or pressing + \key{Ctrl+O}. + + At any point, you can save your form by selecting the \gui{Save From As...} + option from the \gui File menu. The UI files saved by \QD contain + information about the objects used, and any details of signal and slot + connections between them. + + + \section1 Editing A Form + + By default, new forms are opened in widget editing mode. To switch to Edit + mode from another mode, select \gui{Edit Widgets} from the \gui Edit menu + or press the \key F3 key. + + Objects are added to the form by dragging them from the main widget box + and dropping them in the desired location on the form. Once there, they + can be moved around simply by dragging them, or using the cursor keys. + Pressing the \key Ctrl key at the same time moves the selected widget + pixel by pixel, while using the cursor keys alone make the selected widget + snap to the grid when it is moved. Objects can be selected by clicking on + them with the left mouse button. You can also use the \key Tab key to + change the selection. + + The widget box contains objects in a number of different categories, all of + which can be placed on the form as required. The only objects that require + a little more preparation are the \gui Container widgets. These are + described in further detail in the \l{Using Containers in Qt Widgets Designer} + chapter. + + + \target SelectingObjects + \table + \row + \li \inlineimage designer-selecting-widget.png + \li \b{Selecting Objects} + + Objects on the form are selected by clicking on them with the left + mouse button. When an object is selected, resize handles are shown at + each corner and the midpoint of each side, indicating that it can be + resized. + + To select additional objects, hold down the \key Control key and click on + them. If more than one object is selected, the current object will be + displayed with resize handles of a different color. + + To move a widget within a layout, hold down \key Shift and \key Control + while dragging the widget. This extends the selection to the widget's + parent layout. + + Alternatively, objects can be selected in the + \l{The Object Inspector}{Object Inspector}. + \endtable + + When a widget is selected, normal clipboard operations such as cut, copy, + and paste can be performed on it. All of these operations can be done and + undone, as necessary. + + The following shortcuts can be used: + + \target ShortcutsForEditing + \table + \header \li Action \li Shortcut \li Description + \row + \li Cut + \li \key{Ctrl+X} + \li Cuts the selected objects to the clipboard. + \row + \li Copy + \li \key{Ctrl+C} + \li Copies the selected objects to the clipboard. + \row + \li Paste + \li \key{Ctrl+V} + \li Pastes the objects in the clipboard onto the form. + \row + \li Delete + \li \key Delete + \li Deletes the selected objects. + \row + \li Clone object + \li \key{Ctrl+drag} (leftmouse button) + \li Makes a copy of the selected object or group of objects. + \row + \li Preview + \li \key{Ctrl+R} + \li Shows a preview of the form. + \endtable + + All of the above actions (apart from cloning) can be accessed via both the + \gui Edit menu and the form's context menu. These menus also provide + funcitons for laying out objects as well as a \gui{Select All} function to + select all the objects on the form. + + Widgets are not unique objects; you can make as many copies of them as you + need. To quickly duplicate a widget, you can clone it by holding down the + \key Ctrl key and dragging it. This allows widgets to be copied and placed + on the form more quickly than with clipboard operations. + + + \target DragAndDrop + \table + \row + \li \inlineimage designer-dragging-onto-form.png + \li \b{Drag and Drop} + + \QD makes extensive use of the drag and drop facilities provided by Qt. + Widgets can be dragged from the widget box and dropped onto the form. + + Widgets can also be "cloned" on the form: Holding down \key Ctrl and + dragging the widget creates a copy of the widget that can be dragged to + a new position. + + It is also possible to drop Widgets onto the \l {The Object Inspector} + {Object Inspector} to handle nested layouts easily. + \endtable + + \QD allows selections of objects to be copied, pasted, and dragged between + forms. You can use this feature to create more than one copy of the same + form, and experiment with different layouts in each of them. + + + \section2 The Property Editor + + The Property Editor always displays properties of the currently selected + object on the form. The available properties depend on the object being + edited, but all of the widgets provided have common properties such as + \l{QObject::}{objectName}, the object's internal name, and + \l{QWidget::}{enabled}, the property that determines whether an + object can be interacted with or not. + + + \target EditingProperties + \table + \row + \li \inlineimage designer-property-editor.png + \li \b{Editing Properties} + + The property editor uses standard Qt input widgets to manage the + properties of objects on the form. Textual properties are shown in line + edits, integer properties are displayed in spinboxes, boolean + properties are displayed in check boxes, and compound properties such + as colors and sizes are presented in drop-down lists of input widgets. + + Modified properties are indicated with bold labels. To reset them, click + the arrow button on the right. + + Changes in properties are applied to all selected objects that have the + same property. + \endtable + + Certain properties are treated specially by the property editor: + + \list + \li Compound properties -- properties that are made up of more than one + value -- are represented as nodes that can be expanded, allowing + their values to be edited. + \li Properties that contain a choice or selection of flags are edited + via combo boxes with checkable items. + \li Properties that allow access to rich data types, such as QPalette, + are modified using dialogs that open when the properties are edited. + QLabel and the widgets in the \gui Buttons section of the widget box + have a \c text property that can also be edited by double-clicking + on the widget or by pressing \gui F2. \QD interprets the backslash + (\\) character specially, enabling newline (\\n) characters to be + inserted into the text; the \\\\ character sequence is used to + insert a single backslash into the text. A context menu can also be + opened while editing, providing another way to insert special + characters and newlines into the text. + \endlist + + + \section2 Dynamic Properties + + The property editor can also be used to add new + \l{QObject#Dynamic Properties}{dynamic properties} to both standard Qt + widgets and to forms themselves. Since Qt 4.4, dynamic properties are added + and removed via the property editor's toolbar, shown below. + + \image designer-property-editor-toolbar.png + + To add a dynamic property, click on the \gui Add button + \inlineimage designer-property-editor-add-dynamic.png + . To remove it, click on the \gui Remove button + \inlineimage designer-property-editor-remove-dynamic.png + instead. You can also sort the properties alphabetically and change the + color groups by clickinig on the \gui Configure button + \inlineimage designer-property-editor-configure.png + . + + \section2 The Object Inspector + \table + \row + \li \inlineimage designer-object-inspector.png + \li \b{The Object Inspector} + + The \gui{Object Inspector} displays a hierarchical list of all the + objects on the form that is currently being edited. To show the child + objects of a container widget or a layout, click the handle next to the + object label. + + Each object on a form can be selected by clicking on the corresponding + item in the \gui{Object Inspector}. Right-clicking opens the form's + context menu. These features can be useful if you have many overlapping + objects. To locate an object in the \gui{Object Inspector}, use + \key{Ctrl+F}. + + Since Qt 4.4, double-clicking on the object's name allows you to change + the object's name with the in-place editor. + + Since Qt 4.5, the \gui{Object Inspector} displays the layout state of + the containers. The broken layout icon ###ICON is displayed if there is + something wrong with the layouts. + + \endtable +*/ + + +/*! + \page designer-layouts.html + \previouspage Qt Widgets Designer's Widget Editing Mode + \nextpage Qt Widgets Designer's Signals and Slots Editing Mode + + \title Using Layouts in Qt Widgets Designer + + Before a form can be used, the objects on the form need to be placed into + layouts. This ensures that the objects will be displayed properly when the + form is previewed or used in an application. Placing objects in a layout + also ensures that they will be resized correctly when the form is resized. + + Once widgets have been inserted into a layout, it is not possible to move + and resize them individually because the layout itself controls the + geometry of each widget within it, taking account of the hints provided by + spacers. Spacers can be added to the layout to influence the geometries of + the widgets. + + Layouts can be nested to form a hierarchy. For example, to achieve a + typical dialog layout with a horizontal row of buttons, the dialog + elements can be laid out using a vertical box layout with a horizontal + box layout containing the buttons at the bottom. For an introduction to + the Qt layout system, refer to \l{Layout Management}. + + To break a layout, press \key{Ctrl+0} or choose \gui{Break Layout} from + the form's context menu, the \gui Form menu or the main toolbar. + + \tableofcontents + + \section1 Setting A Top Level Layout + + The form's top level layout can be set by clearing the selection (click the + left mouse button on the form itself) and applying a layout. A top level + layout is necessary to ensure that your widgets will resize correctly when + its window is resized. To check if you have set a top level layout, preview + your widget and attempt to resize the window by dragging the size grip. + + \table + \row + \li \inlineimage designer-set-layout.png + \li \b{Applying a Layout} + + To apply a layout, you can select your choice of layout from the + toolbar shown on the left, or from the context menu shown below. + \endtable + + Similary, top level layouts are set on container widgets (QGroupBox) + or on pages of page-based container widgets (QTabWidget, QToolBox + and QStackedWidget), respectively. The container widget needs to be + selected for this to succeed. + + Top level layouts are not visible as separate objects in the Object + Inspector. Their properties appear below the widget properties of the + main form, container widget, or page of a container widget in the + Property Editor. + + \image designer-set-layout2.png + + + \section1 Layout Objects + + Layout objects are created by applying a layout to a group of + existing objects. This is achieved by selecting the objects that you need + to manage and applying one of the standard layouts using the main toolbar, + the \gui Form menu, or the form's context menu. + + The layout object is indicated by a red frame on the form and appears as + an object in the Object Inspector. Its properties (margins and constraints) + are shown in the Property Editor. + + The layout object can be selected and placed within another layout along + with other widgets and layout objects to build a layout hierarchy. + + When a child layout object is selected, its parent layout object can be + selected by pressing down the \key Shift key while clicking on it. This + makes it possible to select a specific layout in a hierarchy, which is + otherwise difficult due to the small frame. + + + \section1 Inserting Objects Into a Layout + \target InsertingObjectsIntoALayout + + Objects can be inserted into an existing layout by dragging them from + their current positions and dropping them at the required location. A + blue cursor is displayed in the layout as an object is dragged over + it to indicate where the object will be added. + + \image designer-layout-inserting.png + \caption Inserting Objects into a Layout + + \section1 Layout Types + \section2 Horizontal and Vertical (Box) Layouts + + The simplest way to arrange objects on a form is to place them in a + horizontal or vertical layout. Horizontal layouts ensure that the widgets + within are aligned horizontally; vertical layouts ensure that they are + aligned vertically. + + Horizontal and vertical layouts can be combined and nested to any depth. + However, if you need more control over the placement of objects, consider + using the grid layout. + + + \section2 The Grid Layout + + Complex form layouts can be created by placing objects in a grid layout. + This kind of layout gives the form designer much more freedom to arrange + widgets on the form, but can result in a much less flexible layout. + However, for some kinds of form layout, a grid arrangement is much more + suitable than a nested arrangement of horizontal and vertical layouts. + + + \section2 The Form Layout + + The QFormLayout + class manages widgets in a two-column form; the left column holds labels + and the right column holds field widgets such as line edits, spin boxes, + etc. The QFormLayout class adheres to various platform look and feel + guidelines and supports wrapping for long rows. + + \image designer-form-layout.png + + The UI file above results in the previews shown below. + + \table + \header + \li Windows XP + \li \macos + \li Cleanlooks + \row + \li \inlineimage designer-form-layout-windowsXP.png + \li \inlineimage designer-form-layout-macintosh.png + \li \inlineimage designer-form-layout-cleanlooks.png + \endtable + + + \section2 Splitter Layouts + + Another common way to manage the layout of objects on a form is to place + them in a splitter. These splitters arrange the objects horizontally or + vertically in the same way as normal layouts, but also allow the user to + adjust the amount of space allocated to each object. + + \image designer-splitter-layout.png + + Although QSplitter is a container widget, \QD treats splitter objects as + layouts that are applied to existing widgets. To place a group of widgets + into a splitter, select them + \l{Qt Widgets Designer's Widget Editing Mode#SelectingObjects}{as described here} + then apply the splitter layout by using the appropriate toolbar button, + keyboard shortcut, or \gui{Lay out} context menu entry. + + + \section1 Shortcut Keys + + In addition to the standard toolbar and context menu entries, there is also + a set of keyboard shortcuts to apply layouts on widgets. + + \target LayoutShortcuts + \table + \header + \li Layout + \li Shortcut + \li Description + \row + \li Horizontal + \li \key{Ctrl+1} + \li Places the selected objects in a horizontal layout. + \row + \li Vertical + \li \key{Ctrl+2} + \li Places the selected objects in a vertical layout. + \row + \li Grid + \li \key{Ctrl+5} + \li Places the selected objects in a grid layout. + \row + \li Form + \li \key{Ctrl+6} + \li Places the selected objects in a form layout. + \row + \li Horizontal splitter + \li \key{Ctrl+3} + \li Creates a horizontal splitter and places the selected objects + inside it. + \row + \li Vertical splitter + \li \key{Ctrl+4} + \li Creates a vertical splitter and places the selected objects + inside it. + \row + \li Adjust size + \li \key{Ctrl+J} + \li Adjusts the size of the layout to ensure that each child object + has sufficient space to display its contents. See + QWidget::adjustSize() for more information. + \endtable + + \note \key{Ctrl+0} is used to break a layout. + +*/ + + +/*! + \page designer-preview.html + \previouspage Using Layouts in Qt Widgets Designer + \nextpage Qt Widgets Designer's Buddy Editing Mode + \title Saving, Previewing and Printing Forms in Qt Widgets Designer + + Although \QD's forms are accurate representations of the components being + edited, it is useful to preview the final appearance while editing. This + feature can be activated by opening the \gui Form menu and selecting + \gui Preview, or by pressing \key{Ctrl+R} when in the form. + + \image designer-dialog-preview.png + + The preview shows exactly what the final component will look like when used + in an application. + + Since Qt 4.4, it is possible to preview forms with various skins - default + skins, skins created with Qt Style Sheets or device skins. This feature + simulates the effect of calling \c{QApplication::setStyleSheet()} in the + application. + + To preview your form with skins, open the \gui Edit menu and select + \gui{Preferences...} + + You will see the dialog shown below: + + \image designer-preview-style.png + + The \gui{Print/Preview Configuration} checkbox must be checked to activate + previews of skins. You can select the styles provided from the \gui{Style} + drop-down box. + + \image designer-preview-style-selection.png + + Alternatively, you can preview custom style sheet created with Qt Style + Sheets. The figure below shows an example of Qt Style Sheet syntax and the + corresponding output. + + \image designer-preview-stylesheet.png + + Another option would be to preview your form with device skins. A list of + generic device skins are available in \QD, however, you may also use + other QVFB skins with the \gui{Browse...} option. + + \image designer-preview-deviceskin-selection.png + + + \section1 Viewing the Form's Code + + Since Qt 4.4, it is possible to view code generated by the User Interface + Compiler (uic) for the \QD form. + + \image designer-form-viewcode.png + + Select \gui{View Code...} from the \gui{Form} menu and a dialog with the + generated code will be displayed. The screenshot below is an example of + code generated by the \c{uic}. + + \image designer-code-viewer.png + + \section1 Saving and Printing the Form + + Forms created in \QD can be saved to an image or printed. + + \table + \row + \li \inlineimage designer-file-menu.png + \li \b{Saving Forms} + + To save a form as an image, choose the \gui{Save Image...} option. The file + will be saved in \c{.png} format. + + \b{Printing Forms} + + To print a form, select the \gui{Print...} option. + + \endtable +*/ + + +/*! + \page designer-connection-mode.html + \previouspage Using Layouts in Qt Widgets Designer + \nextpage Qt Widgets Designer's Buddy Editing Mode + + + \title Qt Widgets Designer's Signals and Slots Editing Mode + + \image designer-connection-mode.png + + In \QD's signals and slots editing mode, you can connect objects in a form + together using Qt's signals and slots mechanism. Both widgets and layouts + can be connected via an intuitive connection interface, using the menu of + compatible signals and slots provided by \QD. When a form is saved, all + connections are preserved so that they will be ready for use when your + project is built. + + + \tableofcontents + + For more information on Qt's signals and sltos mechanism, refer to the + \l{Signals and Slots} document. + + + \section1 Connecting Objects + + To begin connecting objects, enter the signals and slots editing mode by + opening the \gui Edit menu and selecting \gui{Edit Signals/Slots}, or by + pressing the \key F4 key. + + All widgets and layouts on the form can be connected together. However, + spacers just provide spacing hints to layouts, so they cannot be connected + to other objects. + + + \target HighlightedObjects + \table + \row + \li \inlineimage designer-connection-highlight.png + \li \b{Highlighted Objects} + + When the cursor is over an object that can be used in a connection, the + object will be highlighted. + \endtable + + To make a connectionn, press the left mouse button and drag the cursor + towards the object you want to connect it to. As you do this, a line will + extend from the source object to the cursor. If the cursor is over another + object on the form, the line will end with an arrow head that points to the + destination object. This indicates that a connection will be made between + the two objects when you release the mouse button. + + You can abandon the connection at any point while you are dragging the + connection path by pressing \key{Esc}. + + \target MakingAConnection + \table + \row + \li \inlineimage designer-connection-making.png + \li \b{Making a Connection} + + The connection path will change its shape as the cursor moves around + the form. As it passes over objects, they are highlighted, indicating + that they can be used in a signal and slot connection. Release the + mouse button to make the connection. + \endtable + + The \gui{Configure Connection} dialog (below) is displayed, showing signals + from the source object and slots from the destination object that you can + use. + + \image designer-connection-dialog.png + + To complete the connection, select a signal from the source object and a + slot from the destination object, then click \key OK. Click \key Cancel if + you wish to abandon the connection. + + \note If the \gui{Show all signals and slots} checkbox is selected, all + available signals from the source object will be shown. Otherwise, the + signals and slots inherited from QWidget will be hidden. + + You can make as many connections as you like between objects on the form; + it is possible to connect signals from objects to slots in the form itself. + As a result, the signal and slot connections in many dialogs can be + completely configured from within \QD. + + \target ConnectingToTheForm + \table + \row + \li \inlineimage designer-connection-to-form.png + \li \b{Connecting to a Form} + + To connect an object to the form itself, simply position the cursor + over the form and release the mouse button. The end point of the + connection changes to the electrical "ground" symbol. + \endtable + + + \section1 Editing and Deleting Connections + + By default, connection paths are created with two labels that show the + signal and slot involved in the connection. These labels are usually + oriented along the line of the connection. You can move them around inside + their host widgets by dragging the red square at each end of the connection + path. + + \target ConnectionEditor + \table + \row + \li \inlineimage designer-connection-editor.png + \li \b{The Signal/Slot Editor} + + The signal and slot used in a connection can be changed after it has + been set up. When a connection is configured, it becomes visible in + \QD's signal and slot editor where it can be further edited. You can + also edit signal/slot connections by double-clicking on the connection + path or one of its labels to display the Connection Dialog. + \endtable + + \target DeletingConnections + \table + \row + \li \inlineimage designer-connection-editing.png + \li \b{Deleting Connections} + + The whole connection can be selected by clicking on any of its path + segments. Once selected, a connection can be deleted with the + \key Delete key, ensuring that it will not be set up in the UI + file. + \endtable +*/ + + +/*! + \page designer-buddy-mode.html + \previouspage Qt Widgets Designer's Signals and Slots Editing Mode + \nextpage Qt Widgets Designer's Tab Order Editing Mode + + \title Qt Widgets Designer's Buddy Editing Mode + + \image designer-buddy-mode.png + + One of the most useful basic features of Qt is the support for buddy + widgets. A buddy widget accepts the input focus on behalf of a QLabel when + the user types the label's shortcut key combination. The buddy concept is + also used in Qt's \l{Model/View Programming}{model/view} framework. + + + \section1 Linking Labels to Buddy Widgets + + To enter buddy editing mode, open the \gui Edit menu and select + \gui{Edit Buddies}. This mode presents the widgets on the form in a similar + way to \l{Qt Widgets Designer's Signals and Slots Editing Mode}{signals and slots + editing mode} but in this mode, connections must start at label widgets. + Ideally, you should connect each label widget that provides a shortcut with + a suitable input widget, such as a QLineEdit. + + + \target MakingBuddies + \table + \row + \li \inlineimage designer-buddy-making.png + \li \b{Making Buddies} + + To define a buddy widget for a label, click on the label, drag the + connection to another widget on the form, and release the mouse button. + The connection shown indicates how input focus is passed to the buddy + widget. You can use the form preview to test the connections between + each label and its buddy. + \endtable + + + \section1 Removing Buddy Connections + + Only one buddy widget can be defined for each label. To change the buddy + used, it is necessary to delete any existing buddy connection before you + create a new one. + + Connections between labels and their buddy widgets can be deleted in the + same way as signal-slot connections in signals and slots editing mode: + Select the buddy connection by clicking on it and press the \key Delete + key. This operation does not modify either the label or its buddy in any + way. +*/ + + +/*! + \page designer-tab-order.html + \previouspage Qt Widgets Designer's Buddy Editing Mode + \nextpage Using Containers in Qt Widgets Designer + + \title Qt Widgets Designer's Tab Order Editing Mode + + \image designer-tab-order-mode.png + + Many users expect to be able to navigate between widgets and controls + using only the keyboard. Qt lets the user navigate between input widgets + with the \key Tab and \key{Shift+Tab} keyboard shortcuts. The default + \e{tab order} is based on the order in which widgets are constructed. + Although this order may be sufficient for many users, it is often better + to explicitly specify the tab order to make your application easier to + use. + + + \section1 Setting the Tab Order + + To enter tab order editing mode, open the \gui Edit menu and select + \gui{Edit Tab Order}. In this mode, each input widget in the form is shown + with a number indicating its position in the tab order. So, if the user + gives the first input widget the input focus and then presses the tab key, + the focus will move to the second input widget, and so on. + + The tab order is defined by clicking on each of the numbers in the correct + order. The first number you click will change to red, indicating the + currently edited position in the tab order chain. The widget associated + with the number will become the first one in the tab order chain. Clicking + on another widget will make it the second in the tab order, and so on. + + Repeat this process until you are satisfied with the tab order in the form + -- you do not need to click every input widget if you see that the + remaining widgets are already in the correct order. Numbers, for which you + already set the order, change to green, while those which are not clicked + yet, remain blue. + + If you make a mistake, simply double click outside of any number or choose + \gui{Restart} from the form's context menu to start again. If you have many + widgets on your form and would like to change the tab order in the middle or + at the end of the tab order chain, you can edit it at any position. Press + \key{Ctrl} and click the number from which you want to start. + Alternatively, choose \gui{Start from Here} in the context menu. + +*/ + + +/*! + \page designer-using-containers.html + \previouspage Qt Widgets Designer's Tab Order Editing Mode + \nextpage Creating Main Windows in Qt Widgets Designer + + + \title Using Containers in Qt Widgets Designer + + Container widgets provide high level control over groups of objects on a + form. They can be used to perform a variety of functions, such as managing + input widgets, providing paged and tabbed layouts, or just acting as + decorative containers for other objects. + + \image designer-widget-morph.png + + \QD provides visual feedback to help you place objects inside your + containers. When you drag an object from the widget box (or elsewhere) on + the form, each container will be highlighted when the cursor is positioned + over it. This indicates that you can drop the object inside, making it a + child object of the container. This feedback is important because it is + easy to place objects close to containers without actually placing them + inside. Both widgets and spacers can be used inside containers. + + Stacked widgets, tab widgets, and toolboxes are handled specially in \QD. + Normally, when adding pages (tabs, pages, compartments) to these containers + in your own code, you need to supply existing widgets, either as + placeholders or containing child widgets. In \QD, these are automatically + created for you, so you can add child objects to each page straight away. + + Each container typically allows its child objects to be arranged in one or + more layouts. The type of layout management provided depends on each + container, although setting the layout is usually just a matter of + selecting the container by clicking it, and applying a layout. The table + below shows a list of available containers. + + \table + \row + \li \inlineimage designer-containers-frame.png + \li \b Frames + + Frames are used to enclose and group widgets, as well as to provide + decoration. They are used as the foundation for more complex + containers, but they can also be used as placeholders in forms. + + The most important properties of frames are \c frameShape, + \c frameShadow, \c lineWidth, and \c midLineWidth. These are described + in more detail in the QFrame class description. + + \row + \li \inlineimage designer-containers-groupbox.png + \li \b{Group Boxes} + + Group boxes are usually used to group together collections of + checkboxes and radio buttons with similar purposes. + + Among the significant properties of group boxes are \c title, \c flat, + \c checkable, and \c checked, as described in the \l QGroupBox + class documentation. Each group box can contain its own layout, and + this is necessary if it contains other widgets. To add a layout to the + group box, click inside it and apply the layout as usual. + + \row + \li \inlineimage designer-containers-stackedwidget.png + \li \b{Stacked Widgets} + + Stacked widgets are collections of widgets in which only the topmost + layer is visible. Control over the visible layer is usually managed by + another widget, such as combobox, using signals and slots. + + \QD shows arrows in the top-right corner of the stack to allow you to + see all the widgets in the stack when designing it. These arrows do not + appear in the preview or in the final component. To navigate between + pages in the stack, select the stacked widget and use the + \gui{Next Page} and \gui{Previous Page} entries from the context menu. + The \gui{Insert Page} and \gui{Delete Page} context menu options allow + you to add and remove pages. + + \row + \li \inlineimage designer-containers-tabwidget.png + \li \b{Tab Widgets} + + Tab widgets allow the developer to split up the contents of a widget + into different labelled sections, only one of which is displayed at any + given time. By default, the tab widget contains two tabs, and these can + be deleted or renamed as required. You can also add additional tabs. + + To delete a tab: + \list + \li Click on its label to make it the current tab. + \li Select the tab widget and open its context menu. + \li Select \gui{Delete Page}. + \endlist + + To add a new tab: + \list + \li Select the tab widget and open its context menu. + \li Select \gui{Insert Page}. + \li You can add a page before or after the \e current page. \QD + will create a new widget for that particular tab and insert it + into the tab widget. + \li You can set the title of the current tab by changing the + \c currentTabText property in the \gui{Property Editor}. + \endlist + + \row + \li \inlineimage designer-containers-toolbox.png + \li \b{ToolBox Widgets} + + Toolbox widgets provide a series of pages or compartments in a toolbox. + They are handled in a way similar to stacked widgets. + + To rename a page in a toolbox, make the toolbox your current pange and + change its \c currentItemText property from the \gui{Property Editor}. + + To add a new page, select \gui{Insert Page} from the toolbox widget's + context menu. You can add the page before or after the current page. + + To delete a page, select \gui{Delete Page} from the toolbox widget's + context menu. + + \row + \li \inlineimage designer-containers-dockwidget.png + \li \b{Dock Widgets} + + Dock widgets are floating panels, often containing input widgets and + more complex controls, that are either attached to the edges of the + main window in "dock areas", or floated as independent tool windows. + + Although dock widgets can be added to any type of form, they are + typically used with forms created from the + \l{Creating Main Windows in Qt Widgets Designer}{main window template}. + + \endtable +*/ + + +/*! + \page designer-creating-mainwindows.html + \previouspage Using Containers in Qt Widgets Designer + \nextpage Editing Resources with Qt Widgets Designer + + \title Creating Main Windows in Qt Widgets Designer + + \QD can be used to create user interfaces for different purposes, and + it provides different kinds of form templates for each user interface. The + main window template is used to create application windows with menu bars, + toolbars, and dock widgets. + + \omit + \image designer-mainwindow-example.png + \endomit + + Create a new main window by opening the \gui File menu and selecting the + \gui{New Form...} option, or by pressing \key{Ctrl+N}. Then, select the + \gui{Main Window} template. This template provides a main application + window containing a menu bar and a toolbar by default -- these can be + removed if they are not required. + + If you remove the menu bar, a new one can be created by selecting the + \gui{Create Menu Bar} option from the context menu, obtained by + right-clicking within the main window form. + + An application can have only \b one menu bar, but \b several + toolbars. + + + \section1 Menus + + Menus are added to the menu bar by modifying the \gui{Type Here} + placeholders. One of these is always present for editing purposes, and + will not be displayed in the preview or in the finished window. + + Once created, the properties of a menu can be accessed using the + \l{Qt Widgets Designer's Widget Editing Mode#The Property Editor}{Property Editor}, + and each menu can be accessed for this purpose via the + \l{Qt Widgets Designer's Widget Editing Mode#The Object Inspector}{The Object Inspector}. + + Existing menus can be removed by opening a context menu over the label in + the menu bar, and selecting \gui{Remove Menu 'menu_name'}. + + + \target CreatingAMenu + \div {class="float-left"} + \inlineimage designer-creating-menu1.png + \inlineimage designer-creating-menu2.png + \br + \inlineimage designer-creating-menu3.png + \inlineimage designer-creating-menu4.png + \enddiv + + \section2 Creating a Menu + + Double-click the placeholder item to begin editing. The menu text, + displayed using a line edit, can be modified. + + Insert the required text for the new menu. Inserting an + ampersand character (&) causes the letter following it to be + used as a mnemonic for the menu. + + Press \key Return or \key Enter to accept the new text, or press + \key Escape to reject it. You can undo the editing operation later if + required. + + \div {class="clear-both"} + \enddiv + + Menus can also be rearranged in the menu bar simply by dragging and + dropping them in the preferred location. A vertical red line indicates the + position where the menu will be inserted. + + Menus can contain any number of entries and separators, and can be nested + to the required depth. Adding new entries to menus can be achieved by + navigating the menu structure in the usual way. + + \target CreatingAMenuEntry + \div {class="float-right"} + \inlineimage designer-creating-menu-entry1.png + \inlineimage designer-creating-menu-entry2.png + \br + \inlineimage designer-creating-menu-entry3.png + \inlineimage designer-creating-menu-entry4.png + \enddiv + + \section2 Creating a Menu Entry + + Double-click the \gui{Type Here} placeholder to begin editing, or + double-click \gui{Add Separator} to insert a new separator line after + the last entry in the menu. + + The menu entry's text is displayed using a line edit, and can be + modified. + + Insert the required text for the new entry, optionally using + the ampersand character (&) to mark the letter to use as a + mnemonic for the entry. + + Press \key Return or \key Enter to accept the new text, or press + \key Escape to reject it. The action created for this menu entry will + be accessible via the \l{#TheActionEditor}{Action Editor}, and any + associated keyboard shortcut can be set there. + + \div {class="clear-both"} + \enddiv + + Just like with menus, entries can be moved around simply by dragging and + dropping them in the preferred location. When an entry is dragged over a + closed menu, the menu will open to allow it to be inserted there. Since + menu entries are based on actions, they can also be dropped onto toolbars, + where they will be displayed as toolbar buttons. + + \section1 Toolbars + + \div {class="float-left"} + \inlineimage designer-creating-toolbar.png + \enddiv + + \section2 Creating and Removing a Toolbar + + Toolbars are added to a main window in a similar way to the menu bar: + Select the \gui{Add Tool Bar} option from the form's context menu. + Alternatively, if there is an existing toolbar in the main window, you can + click the arrow on its right end to create a new toolbar. + + Toolbars are removed from the form via an entry in the toolbar's context + menu. + + \div {class="clear-both"} + \enddiv + + \section2 Adding and Removing Toolbar Buttons + + Toolbar buttons are created as actions in the + \l{#TheActionEditor}{Action Editor} and dragged onto the toolbar. + Since actions can be represented by menu entries and toolbar buttons, + they can be moved between menus and toolbars. + + \div {class="float-right"} + \inlineimage designer-adding-toolbar-action.png + \inlineimage designer-removing-toolbar-action.png + \enddiv + + To share an action between a menu and a toolbar, drag its icon from the + action editor to the toolbar rather than from the menu where its entry is + located. See \l{#Adding an Action}{Adding an Action} for more information + about this process. + + Toolbar buttons are removed via the toolbar's context menu. + + \div {class="clear-both"} + \enddiv + + \section1 Actions + + With the menu bar and the toolbars in place, it's time to populate them + with actions. New actions for both menus and toolbars are created in the + action editor window, simplifying the creation and management of actions. + + \target TheActionEditor + \div {class="float-left"} + \inlineimage designer-action-editor.png + \enddiv + + \section2 The Action Editor + + Enable the action editor by opening the \gui Tools menu, and switching + on the \gui{Action Editor} option. + + The action editor allows you to create \gui New actions and \gui Delete + actions. It also provides a search function, \gui Filter, using the + action's text. + + \QD's action editor can be viewed in the classic \gui{Icon View} and + \gui{Detailed View}. The screenshot below shows the action editor in + \gui{Detailed View}. You can also copy and paste actions between menus, + toolbars and forms. + + \div {class="clear-both"} + \enddiv + + \section2 Creating an Action + + To create an action, use the action editor's \gui New button, which will + then pop up an input dialog. Provide the new action with a \gui Text -- + this is the text that will appear in a menu entry and as the action's + tooltip. The text is also automatically added to an "action" prefix, + creating the action's \gui{Object Name}. + + In addition, the dialog provides the option of selecting an \gui Icon for + the action, as well as removing the current icon. + + Once the action is created, it can be used wherever actions are applicable. + + \div {class="clear-left"} + \enddiv + + \target AddingAnAction + \div {class="float-right"} + \inlineimage designer-adding-menu-action.png + \inlineimage designer-adding-toolbar-action.png + \enddiv + + \section2 Adding an Action + + To add an action to a menu or a toolbar, simply press the left mouse + button over the action in the action editor, and drag it to the + preferred location. + + \QD provides highlighted guide lines that tell you where the action + will be added. Release the mouse button to add the action when you have + found the right spot. + + \div {class="clear-right"} + \enddiv + + \section1 Dock Widgets + + Dock widgets are \l{Using Containers in Qt Widgets Designer}{container widgets} + as well. They can be added to a form by dropping them onto the desired + dock area. + + \target AddingADockWidget + + \div {class="float-left"} + \inlineimage designer-adding-dockwidget.png + \enddiv + + \section2 Adding a Dock Widget + + To add a dock widget to a form, drag one from the \gui Containers section + of the widget box, and drop it onto the main form area. Do not add the + dock widget to an existing layout. Instead, open the \gui{Property Editor} + and enable the \gui{docked} property to place it in a dock area. + + Note that it is sometimes easier to configure a dock widget if it is added + to a form before a layout is applied to the central widget. For example, + it is possible to undock it and resize it, making it more convenient to + add child widgets. + + Dock widgets can be optionally floated as independent tool windows. + Hence, it is useful to give them window titles by setting their + \l{QDockWidget::}{windowTitle} property. This also helps to identify them on the + form. + + \div {class="clear-both"} + \enddiv +*/ + + +/*! + \page designer-resources.html + \previouspage Creating Main Windows in Qt Widgets Designer + \nextpage Using Stylesheets with Qt Widgets Designer + + \title Editing Resources with Qt Widgets Designer + + \image designer-resources-editing.png + + \QD fully supports the \l{The Qt Resource System}{Qt Resource System}, + enabling resources to be specified together with forms as they are + designed. To aid designers and developers manage resources for their + applications, \QD's resource editor allows resources to be defined on a + per-form basis. In other words, each form can have a separate resource + file. + + \section1 Defining a Resource File + + To specify a resource file you must enable the resource editor by opening + the \gui Tools menu, and switching on the \gui{Resource Browser} option. + + \target ResourceFiles + \table + \row + \li \inlineimage designer-resource-browser.png + \li \b{Resource Files} + + Within the resource browser, you can open existing resource files or + create new ones. Click the \gui{Edit Resources} button + \inlineimage designer-edit-resources-button.png + to edit your resources. To reload resources, click on the \gui Reload + button + \inlineimage designer-reload-resources-button.png + . + \endtable + + + Once a resource file is loaded, you can create or remove entries in it + using the given \gui{Add Files} + \inlineimage designer-add-resource-entry-button.png + and \gui{Remove Files} + \inlineimage designer-remove-resource-entry-button.png + buttons, and specify resources (e.g., images) using the \gui{Add Files} + button + \inlineimage designer-add-files-button.png + . Note that these resources must reside within the current resource file's + directory or one of its subdirectories. + + + \target EditResource + \table + \row + \li \inlineimage designer-edit-resource.png + \li \b{Editing Resource Files} + + Press the + \inlineimage designer-add-resource-entry-button.png + button to add a new resource entry to the file. Then use the + \gui{Add Files} button + \inlineimage designer-add-files-button.png + to specify the resource. + + You can remove resources by selecting the corresponding entry in the + resource editor, and pressing the + \inlineimage designer-remove-resource-entry-button.png + button. + \endtable + + + \section1 Using the Resources + + Once the resources are defined you can use them actively when composing + your form. For example, you might want to create a tool button using an + icon specified in the resource file. + + \target UsingResources + \table + \row + \li \inlineimage designer-resources-using.png + \li \b{Using Resources} + + When changing properties with values that may be defined within a + resource file, \QD's property editor allows you to specify a resource + in addition to the option of selecting a source file in the ordinary + way. + + \row + \li \inlineimage designer-resource-selector.png + \li \b{Selecting a Resource} + + You can open the resource selector by clicking \gui{Choose Resource...} + to add resources any time during the design process. + +\omit +... check with Friedemann +To quickly assign icon pixmaps to actions or pixmap properties, you may +drag the pixmap from the resource editor to the action editor, or to the +pixmap property in the property editor. +\endomit + + \endtable +*/ + + +/*! + \page designer-stylesheet.html + \previouspage Editing Resources with Qt Widgets Designer + \nextpage Using a Designer UI File in Your C++ Application + + \title Using Stylesheets with Qt Widgets Designer + + Since Qt 4.2, it is possible to edit stylesheets in \QD with the stylesheet + editor. + + \target UsingStylesheets + \table + \row + \li \inlineimage designer-stylesheet-options.png + \b{Setting a Stylesheet} + + The stylesheet editor can be accessed by right-clicking a widget + and selecting \gui{Change styleSheet...} + + \row + \li \inlineimage designer-stylesheet-usage.png + \endtable + +*/ + + +/*! + \page designer-using-a-ui-file.html + \previouspage Using Stylesheets with Qt Widgets Designer + \nextpage Using a Designer UI File in Your Qt for Python Application + + \keyword Using a Designer UI File in Your Application + \title Using a Designer UI File in Your C++ Application + + Qt Widgets Designer UI files represent the widget tree of the form in XML format. The + forms can be processed: + + \list + \li \l{Compile Time Form Processing}{At compile time}, which means that forms + are converted to C++ code that can be compiled. + \li \l{Run Time Form Processing}{At runtime}, which means that forms are processed + by the QUiLoader class that dynamically constructs the widget tree while + parsing the XML file. + \endlist + + \tableofcontents + \section1 Compile Time Form Processing + + You create user interface components with \QD and use Qt's integrated build tools, + \l{qmake Manual}{qmake} and \l{User Interface Compiler (uic)}{uic}, to generate code + for them when the application is built. The generated code contains the form's user + interface object. It is a C++ struct that contains: + + \list + \li Pointers to the form's widgets, layouts, layout items, + button groups, and actions. + \li A member function called \c setupUi() to build the widget tree + on the parent widget. + \li A member function called \c retranslateUi() that handles the + translation of the string properties of the form. For more information, + see \l{Reacting to Language Changes}. + \endlist + + The generated code can be included in your application and used directly from + it. Alternatively, you can use it to extend subclasses of standard widgets. + + A compile time processed form can be used in your application with one of + the following approaches: + + \list + \li \l{The Direct Approach}: you construct a widget to use as a placeholder + for the component, and set up the user interface inside it. + \li \l{The Single Inheritance Approach}: you subclass the form's base class + (QWidget or QDialog, for example), and include a private instance + of the form's user interface object. + \li \l{The Multiple Inheritance Approach}: you subclass both the form's base + class and the form's user interface object. This allows the widgets + defined in the form to be used directly from within the scope of + the subclass. + \endlist + + To demonstrate, we create a simple Calculator Form application. It is based on the + original \l{Calculator Form} example. + + The application consists of one source file, \c main.cpp and a UI + file. + + The \c{calculatorform.ui} file designed with \QD is shown below: + + \image directapproach-calculatorform.png + + When using \c CMake to build the executable, a \c{CMakeLists.txt} + file is required: + + \snippet uitools/calculatorform/CMakeLists.txt 0 + + The form is listed among the C++ source files in \c qt_add_executable(). + The option \c CMAKE_AUTOUIC tells \c CMake to run the \c uic tool + to create a \c ui_calculatorform.h file that can be used + by the source files. + + When using \c qmake to build the executable, a \c{.pro} file is required: + + \snippet uitools/calculatorform/calculatorform.pro 0 + + The special feature of this file is the \c FORMS declaration that tells + \c qmake which files to process with \c uic. In this case, the + \c calculatorform.ui file is used to create a \c ui_calculatorform.h file + that can be used by any file listed in the \c SOURCES declaration. + + \note You can use Qt Creator to create the Calculator Form project. It + automatically generates the main.cpp, UI, and a project file for the + desired build tool, which you can modify. + + \section2 The Direct Approach + + To use the direct approach, we include the \c ui_calculatorform.h file + directly in \c main.cpp: + + \snippet uitools/calculatorform/main.cpp 0 + + The \c main function creates the calculator widget by constructing a + standard QWidget that we use to host the user interface described by the + \c calculatorform.ui file. + + \snippet uitools/calculatorform/main.cpp 1 + + In this case, the \c{Ui::CalculatorForm} is an interface description object + from the \c ui_calculatorform.h file that sets up all the dialog's widgets + and the connections between its signals and slots. + + The direct approach provides a quick and easy way to use simple, self-contained + components in your applications. However, componens created with \QD often + require close integration with the rest of the application code. For + instance, the \c CalculatorForm code provided above will compile and run, + but the QSpinBox objects will not interact with the QLabel as we need a + custom slot to carry out the add operation and display the result in the + QLabel. To achieve this, we need to use the single inheritance approach. + + \section2 The Single Inheritance Approach + + To use the single inheritance approach, we subclass a standard Qt widget and + include a private instance of the form's user interface object. This can take + the form of: + + \list + \li A member variable + \li A pointer member variable + \endlist + + \section3 Using a Member Variable + + In this approach, we subclass a Qt widget and set up the user interface + from within the constructor. Components used in this way expose the widgets + and layouts used in the form to the Qt widget subclass, and provide a + standard system for making signal and slot connections between the user + interface and other objects in your application. + The generated \c{Ui::CalculatorForm} structure is a member of the class. + + This approach is used in the \l{Calculator Form} example. + + To ensure that we can use the user interface, we need to include the header + file that \c uic generates before referring to \c{Ui::CalculatorForm}: + + \snippet calculatorform/calculatorform.h 0 + + The project file must be updated to include \c{calculatorform.h}. + For \c CMake: + + \snippet calculatorform/CMakeLists.txt 1 + + For \c qmake: + + \snippet calculatorform/calculatorform.pro 0 + + The subclass is defined in the following way: + + \snippet calculatorform/calculatorform.h 1 + + The important feature of the class is the private \c ui object which + provides the code for setting up and managing the user interface. + + The constructor for the subclass constructs and configures all the widgets + and layouts for the dialog just by calling the \c ui object's \c setupUi() + function. Once this has been done, it is possible to modify the user + interface as needed. + + \snippet calculatorform/calculatorform.cpp 0 + + We can connect signals and slots in user interface widgets in the usual + way by adding the on_ - prefix. For more information, + see \l{widgets-and-dialogs-with-auto-connect}. + + The advantages of this approach are its simple use of inheritance to + provide a QWidget-based interface, and its encapsulation of the user + interface widget variables within the \c ui data member. We can use this + method to define a number of user interfaces within the same widget, each + of which is contained within its own namespace, and overlay (or compose) + them. This approach can be used to create individual tabs from existing + forms, for example. + + \section3 Using a Pointer Member Variable + + Alternatively, the \c{Ui::CalculatorForm} structure can be made a pointer + member of the class. The header then looks as follows: + + \code + + namespace Ui { + class CalculatorForm; + } + + class CalculatorForm : public QWidget + ... + virtual ~CalculatorForm(); + ... + private: + Ui::CalculatorForm *ui; + ... + + \endcode + + The corresponding source file looks as follows: + + \code + #include "ui_calculatorform.h" + + CalculatorForm::CalculatorForm(QWidget *parent) : + QWidget(parent), ui(new Ui::CalculatorForm) + { + ui->setupUi(this); + } + + CalculatorForm::~CalculatorForm() + { + delete ui; + } + \endcode + + The advantage of this approach is that the user interface object can be + forward-declared, which means that we do not have to include the generated + \c ui_calculatorform.h file in the header. The form can then be changed without + recompiling the dependent source files. This is particularly important if the + class is subject to binary compatibility restrictions. + + We generally recommend this approach for libraries and large applications. + For more information, see \l{Creating Shared Libraries}. + + \section2 The Multiple Inheritance Approach + + Forms created with \QD can be subclassed together with a standard + QWidget-based class. This approach makes all the user interface components + defined in the form directly accessible within the scope of the subclass, + and enables signal and slot connections to be made in the usual way with + the \l{QObject::connect()}{connect()} function. + + We need to include the header file that \c uic generates from the + \c calculatorform.ui file, as follows: + + \snippet ../designer/calculatorform_mi/calculatorform.h 0 + + The class is defined in a similar way to the one used in the + \l{The Single Inheritance Approach}{single inheritance approach}, except that + this time we inherit from \e{both} QWidget and \c{Ui::CalculatorForm}, + as follows: + + \snippet ../designer/calculatorform_mi/calculatorform.h 1 + + We inherit \c{Ui::CalculatorForm} privately to ensure that the user + interface objects are private in our subclass. We can also inherit it with + the \c public or \c protected keywords in the same way that we could have + made \c ui public or protected in the previous case. + + The constructor for the subclass performs many of the same tasks as the + constructor used in the \l{The Single Inheritance Approach} + {single inheritance} example: + + \snippet ../designer/calculatorform_mi/calculatorform.cpp 0 + + In this case, the widgets used in the user interface can be accessed in the + same say as a widget created in code by hand. We no longer require the + \c{ui} prefix to access them. + + \section2 Reacting to Language Changes + + Qt notifies applications if the user interface language changes by sending an + event of the type QEvent::LanguageChange. To call the member function + \c retranslateUi() of the user interface object, we reimplement + \c QWidget::changeEvent() in the form class, as follows: + + \code + void CalculatorForm::changeEvent(QEvent *e) + { + QWidget::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } + } + \endcode + + \section1 Run Time Form Processing + + Alternatively, forms can be processed at run time, producing dynamically- + generated user interfaces. This can be done using the QtUiTools module + that provides the QUiLoader class to handle forms created with \QD. + + + \section2 The UiTools Approach + + A resource file containing a UI file is required to process forms at + run time. Also, the application needs to be configured to use the QtUiTools + module. This is done by including the following declarations in a \c CMake + project file, ensuring that the application is compiled and linked + appropriately. + + \snippet ../uitools/textfinder/CMakeLists.txt 0 + \snippet ../uitools/textfinder/CMakeLists.txt 1 + + For \c qmake: + + \snippet manual/doc_src_designer-manual.pro 0 + + The QUiLoader class provides a form loader object to construct the user + interface. This user interface can be retrieved from any QIODevice, e.g., + a QFile object, to obtain a form stored in a project's resource file. The + QUiLoader::load() function constructs the form widget using the user + interface description contained in the file. + + The QtUiTools module classes can be included using the following directive: + + \snippet manual/doc_src_designer-manual.cpp 1 + + The QUiLoader::load() function is invoked as shown in this code from the + \l{Text Finder} example: + + \snippet ../uitools/textfinder/textfinder.cpp 4 + + In a class that uses QtUiTools to build its user interface at run time, we + can locate objects in the form using QObject::findChild(). For example, in the + following code, we locate some components based on their object names and + widget types: + + \snippet ../uitools/textfinder/textfinder.cpp 1 + + Processing forms at run-time gives the developer the freedom to change a + program's user interface, just by changing the UI file. This is useful + when customizing programs to suit various user needs, such as extra large + icons or a different colour scheme for accessibility support. + + + \section1 Automatic Connections + + The signals and slots connections defined for compile time or run time + forms can either be set up manually or automatically, using QMetaObject's + ability to make connections between signals and suitably-named slots. + + Generally, in a QDialog, if we want to process the information entered by + the user before accepting it, we need to connect the clicked() signal from + the \gui OK button to a custom slot in our dialog. We will first show an + example of the dialog in which the slot is connected by hand then compare + it with a dialog that uses automatic connection. + + + \section2 A Dialog Without Auto-Connect + + We define the dialog in the same way as before, but now include a slot in + addition to the constructor: + + \snippet noautoconnection/imagedialog.h 0 + + The \c checkValues() slot will be used to validate the values provided by + the user. + + In the dialog's constructor we set up the widgets as before, and connect + the \gui Cancel button's \l{QPushButton::clicked()}{clicked()} signal to + the dialog's reject() slot. We also disable the + \l{QPushButton::autoDefault}{autoDefault} property in both buttons to + ensure that the dialog does not interfere with the way that the line edit + handles return key events: + + \snippet noautoconnection/imagedialog.cpp 0 + \dots + \snippet noautoconnection/imagedialog.cpp 1 + + We connect the \gui OK button's \l{QPushButton::clicked()}{clicked()} + signal to the dialog's checkValues() slot which we implement as follows: + + \snippet noautoconnection/imagedialog.cpp 2 + + This custom slot does the minimum necessary to ensure that the data + entered by the user is valid - it only accepts the input if a name was + given for the image. + + \section2 Widgets and Dialogs with Auto-Connect + + Although it is easy to implement a custom slot in the dialog and connect + it in the constructor, we could instead use QMetaObject's auto-connection + facilities to connect the \gui OK button's clicked() signal to a slot in + our subclass. \c{uic} automatically generates code in the dialog's + \c setupUi() function to do this, so we only need to declare and + implement a slot with a name that follows a standard convention: + + \snippet manual/doc_src_designer-manual.cpp 2 + + \note When renaming widgets in the form, the slot names need to be + adapted accordingly, which can become a maintenance problem. + For this reason, we recommend against using this in new code. + + Using this convention, we can define and implement a slot that responds to + mouse clicks on the \gui OK button: + + \snippet autoconnection/imagedialog.h 0 + + Another example of automatic signal and slot connection would be the + \l{Text Finder} with its \c{on_findButton_clicked()} + slot. + + We use QMetaObject's system to enable signal and slot connections: + + \snippet ../uitools/textfinder/textfinder.cpp 2 + + This enables us to implement the slot, as shown below: + + \snippet ../uitools/textfinder/textfinder.cpp 6 + \dots + \snippet ../uitools/textfinder/textfinder.cpp 8 + + Automatic connection of signals and slots provides both a standard naming + convention and an explicit interface for widget designers to work to. By + providing source code that implements a given interface, user interface + designers can check that their designs actually work without having to + write code themselves. +*/ + +/*! + \page designer-using-a-ui-file-python.html + \previouspage Using a Designer UI File in Your C++ Application + \nextpage Using Custom Widgets with Qt Widgets Designer + + \title Using a Designer UI File in Your Qt for Python Application + + \section1 Converting the Form to Python Code + + To demonstrate, we use the Qt Widgets animation easing example. + + The application consists of one source file, \c easing.py, a UI + file \c form.ui, a resource file \c easing.qrc and the project + file, \c{easing.pyproject} file in the YAML format: + + \code + { + "files": ["easing.qrc", "ui_form.py", "easing.py", "easing_rc.py", + "form.ui"] + } + \endcode + + The UI file is converted to Python code building the form using the + \l{User Interface Compiler (uic)}: + + \code + uic -g python form.ui > ui_form.py + \endcode + + Since the top level widget is named \c Form, this results in a Python + class named \c Ui_Form being generated. It provides a function + \c setupUi(), taking the widget as parameter, which is called to + create the UI elements: + + \code + from ui_form import Ui_Form + ... + class Window(QtWidgets.QWidget): + def __init__(self, parent=None): + super(Window, self).__init__(parent) + + self.m_ui = Ui_Form() + self.m_ui.setupUi(self) + \endcode + + Later on, the widgets can be accessed via the \c Ui_Form class: + + \code + self.m_ui.graphicsView.setScene(self.m_scene) + \endcode + + Besides \c setupUi(), \c Ui_Form provides another method + \c retranslateUi(), which can be called in reaction to + a QEvent of type QEvent.LanguageChange, which indicates + a change in the application language. + + \section2 The UiTools Approach + + The QUiLoader class provides a form loader object to construct the user + interface at runtime. This user interface can be retrieved from any + QIODevice, e.g., a QFile object. The QUiLoader::load() function + constructs the form widget using the user interface description + contained in the file. + + It is demonstrated by the uiloader example: + + \code + from PySide2.QtUiTools import QUiLoader + + if __name__ == '__main__': + # Some code to obtain the form file name, ui_file_name + app = QApplication(sys.argv) + ui_file = QFile(ui_file_name) + if not ui_file.open(QIODevice.ReadOnly): + print("Cannot open {}: {}".format(ui_file_name, ui_file.errorString())) + sys.exit(-1) + loader = QUiLoader() + widget = loader.load(ui_file, None) + ui_file.close() + if not widget: + print(loader.errorString()) + sys.exit(-1) + widget.show() + sys.exit(app.exec_()) + \endcode + + \section1 Resource imports + + \section2 Single directory usage + + When using icons from \l{The Qt Resource System}{resource files}, say + \c resources.qrc, \c uic will generate an import of the form: + + \code + import resources_rc + \endcode + + This assumes that a file \c resources_rc.py generated by calling the + \l {Resource Compiler (rcc)} tool (passing the \c {-g python} + command line option) exists in the same directory as the form source. + + \c uic has a command line option \c --rc-prefix causing the \c rc indicator + to be prepended: + + \code + import rc_resources + \endcode + + The command line option \c --from-imports causes the imports to be generated + relative to '.': + + \code + from . import resources_rc + \endcode + + \section2 Directory trees + + Some projects have more complicated directory trees, for example: + + \badcode + project + resources (resources.qrc) + ui (.ui files) + \endcode + + The resource file is then not in the same directory as the form source + and the \c .ui files typically have relative paths to the resource files: + + \badcode + + \endcode + + In this case, the command line option \c --absolute-imports can be used + to generate an absolute import in Python, resulting in: + + \code + import resources.resources_rc + \endcode + + based on the assumption that \c .. is the root directory of the project + contained in the Python import path list. + + For more deeply nested trees, it is possible to use the + command line option \c {--python-paths } to pass a Python + import path list. \c uic will then try to determine the project root + by matching the form file path against the path components. + + If \c {--python-paths} is not given, the environment variable + \c PYTHONPATH is by default checked. +*/ + +/*! + \page designer-customizing-forms.html + \previouspage Using a Designer UI File in Your Qt for Python Application + \nextpage Using Custom Widgets with Qt Widgets Designer + + \title Customizing Qt Widgets Designer Forms + + \image designer-form-settings.png + + When saving a form in \QD, it is stored as a UI file. Several form + settings, for example the grid settings or the margin and spacing for the + default layout, are stored along with the form's components. These settings + are used when the \l uic generates the form's C++ code. For more + information on how to use forms in your application, see the + \l{Using a Designer UI File in Your C++ Application} section. + + + \section1 Modifying the Form Settings + + To modify the form settings, open the \gui Form menu and select \gui{Form + Settings...} + + In the forms settings dialog you can specify the \gui Author of the form. + + You can also alter the margin and spacing properties for the form's default + layout (\gui {Layout Default}). These default layout properties will be + replaced by the corresponding \gui {Layout Function}, if the function is + specified, when \c uic generates code for the form. The form settings + dialog lets you specify functions for both the margin and the spacing. + + \target LayoutFunction + \table + \row + \li \inlineimage designer-form-layoutfunction.png + \li \b{Layout Function} + + The default layout properties will be replaced by the corresponding + \gui{Layout Function}, when \c uic generates code for the form. This is + useful when different environments requires different layouts for the same + form. + + To specify layout functions for the form's margin and spacing, check the + \gui{Layout Function} group box to enable the line edits. + \endtable + + You can also specify the form's \gui{Include Hints}; i.e., provide a list + of the header files which will then be included in the form window's + associated UI file. Header files may be local, i.e., relative to the + project's directory, \c "mywidget.h", or global, i.e. part of Qt or the + compilers standard libraries: \c . + + Finally, you can specify the function used to load pixmaps into the form + window (the \gui {Pixmap Function}). +*/ + + +/*! + \page designer-using-custom-widgets.html + \previouspage Customizing Qt Widgets Designer Forms + \nextpage Creating Custom Widgets for Qt Widgets Designer + + \title Using Custom Widgets with Qt Widgets Designer + + \QD can display custom widgets through its extensible plugin mechanism, + allowing the range of designable widgets to be extended by the user and + third parties. Alternatively, it is possible + to use existing widgets as placeholders for widget classes that provide + similar APIs. + + + \section1 Handling Custom Widgets + + Although \QD supports all of the standard Qt widgets, some specialized + widgets may not be available as standard for a number of reasons: + + \list + \li Custom widgets may not be available at the time the user interface + is being designed. + \li Custom widgets may be platform-specific, and designers may be + developing the user interface on a different platform to end users. + \li The source code for a custom widget is not available, or the user + interface designers are unable to use the widget for non-technical + reasons. + \endlist + + In the above situations, it is still possible to design forms with the aim + of using custom widgets in the application. To achieve this, we can use + the widget promotion feature of \QD. + + In all other cases, where the source code to the custom widgets is + available, we can adapt the custom widget for use with \QD. + + + \section2 Promoting Widgets + + \image designer-promoting-widgets.png + + If some forms must be designed, but certain custom widgets are unavailble + to the designer, we can substitute similar widgets to represent the missing + widgets. For example, we might represent instances of a custom push button + class, \c MyPushButton, with instances of QPushButton and promote these to + \c MyPushButton so that \l{uic.html}{uic} generates suitable code for this + missing class. + + When choosing a widget to use as a placeholder, it is useful to compare the + API of the missing widget with those of standard Qt widgets. For + specialized widgets that subclass standard classes, the obvious choice of + placeholder is the base class of the custom widget; for example, QSlider + might be used for specialized QSlider subclasses. + + For specialized widgets that do not share a common API with standard Qt + widgets, it is worth considering adapting a custom widget for use in \QD. + If this is not possible then QWidget is the obvious choice for a + placeholder widget since it is the lowest common denominator for all + widgets. + + To add a placeholder, select an object of a suitable base class and choose + \gui{Promote to ...} from the form's context menu. After entering the class + name and header file in the lower part of the dialog, choose \gui{Add}. The + placeholder class will now appear along with the base class in the upper + list. Click the \gui{Promote} button to accept this choice. + + Now, when the form's context menu is opened over objects of the base class, + the placeholder class will appear in the \gui{Promote to} submenu, allowing + for convenient promotion of objects to that class. + + A promoted widget can be reverted to its base class by choosing + \gui{Demote to} from the form's context menu. + + + \section2 User Defined Custom Widgets + + Custom widgets can be adapted for use with \QD, giving designers the + opportunity to configure the user interface using the actual widgets that + will be used in an application rather than placeholder widgets. The process + of creating a custom widget plugin is described in the + \l{Creating Custom Widgets for Qt Widgets Designer} chapter of this manual. + + To use a plugin created in this way, it is necessary to ensure that the + plugin is located on a path that \QD searches for plugins. Generally, + plugins stored in \c{$QTDIR/plugins/designer} will be loaded when \QD + starts. Further information on building and installing plugins can be found + \l{Creating Custom Widgets for Qt Widgets Designer#BuildingandInstallingthePlugin} + {here}. You can also refer to the \l{How to Create Qt Plugins} + {Plugins HOWTO} document for information about creating plugins. +*/ + + +/*! + \page designer-creating-custom-widgets.html + \previouspage Using Custom Widgets with Qt Widgets Designer + \nextpage Creating Custom Widget Extensions + + \title Creating Custom Widgets for Qt Widgets Designer + + \QD's plugin-based architecture allows user-defined and third party custom + widgets to be edited just like you do with standard Qt widgets. All of the + custom widget's features are made available to \QD, including widget + properties, signals, and slots. Since \QD uses real widgets during the form + design process, custom widgets will appear the same as they do when + previewed. + + The \l QtDesigner module provides you with the ability to create custom + widgets in \QD. + + + \section1 Getting Started + + To integrate a custom widget with \QD, you require a suitable description + for the widget and an appropriate project file. + + + \section2 Providing an Interface Description + + To inform \QD about the type of widget you want to provide, create a + subclass of QDesignerCustomWidgetInterface that describes the various + properties your widget exposes. Most of these are supplied by functions + that are pure virtual in the base class, because only the author of the + plugin can provide this information. + + \table + \header + \li Function + \li Description of the return value + \row + \li \c name() + \li The name of the class that provides the widget. + \row + \li \c group() + \li The group in \QD's widget box that the widget belongs to. + \row + \li \c toolTip() + \li A short description to help users identify the widget in \QD. + \row + \li \c whatsThis() + \li A longer description of the widget for users of \QD. + \row + \li \c includeFile() + \li The header file that must be included in applications that use + this widget. This information is stored in UI files and will + be used by \c uic to create a suitable \c{#includes} statement + in the code it generates for the form containing the custom + widget. + \row + \li \c icon() + \li An icon that can be used to represent the widget in \QD's + widget box. + \row + \li \c isContainer() + \li True if the widget will be used to hold child widgets; + false otherwise. + \row + \li \c createWidget() + \li A QWidget pointer to an instance of the custom widget, + constructed with the parent supplied. + \note createWidget() is a factory function responsible for + creating the widget only. The custom widget's properties will + not be available until load() returns. + \row + \li \c domXml() + \li A description of the widget's properties, such as its object + name, size hint, and other standard QWidget properties. + \row + \li \c codeTemplate() + \li This function is reserved for future use by \QD. + \endtable + + Two other virtual functions can also be reimplemented: + + \table + \row + \li \c initialize() + \li Sets up extensions and other features for custom widgets. Custom + container extensions (see QDesignerContainerExtension) and task + menu extensions (see QDesignerTaskMenuExtension) should be set + up in this function. + \row + \li \c isInitialized() + \li Returns true if the widget has been initialized; returns false + otherwise. Reimplementations usually check whether the + \c initialize() function has been called and return the result + of this test. + \endtable + + + \section2 Notes on the \c{domXml()} Function + + The \c{domXml()} function returns a UI file snippet that is used by + \QD's widget factory to create a custom widget and its applicable + properties. + + Since Qt 4.4, \QD's widget box allows for a complete UI file to + describe \b one custom widget. The UI file can be loaded using the + \c{} tag. Specifying the tag allows for adding the + element that contains additional information for custom widgets. The + \c{} tag is sufficient if no additional information is required + + If the custom widget does not provide a reasonable size hint, it is + necessary to specify a default geometry in the string returned by the + \c domXml() function in your subclass. For example, the + \c AnalogClockPlugin provided by the \l{customwidgetplugin} + {Custom Widget Plugin} example, defines a default widgetgeometry in the + following way: + + \dots + \snippet customwidgetplugin/customwidgetplugin.cpp 11 + \dots + + An additional feature of the \c domXml() function is that, if it returns + an empty string, the widget will not be installed in \QD's widget box. + However, it can still be used by other widgets in the form. This feature + is used to hide widgets that should not be explicitly created by the user, + but are required by other widgets. + + A complete custom widget specification looks like: + + \code + displayname="MyWidget"> + + + + widgets::MyWidget + addPage + + + + Explanatory text to be shown in Property Editor + + + + + \endcode + + Attributes of the \c{} tag: + \table + \header + \li Attribute + \li Presence + \li Values + \li Comment + \row + \li \c{language} + \li optional + \li "c++", "jambi" + \li This attribute specifies the language the custom widget is intended for. + It is mainly there to prevent C++-plugins from appearing in Qt Jambi. + \row + \li \c{displayname} + \li optional + \li Class name + \li The value of the attribute appears in the Widget box and can be used to + strip away namespaces. + \endtable + + The \c{} tag tells \QD and \l uic which method should be used to + add pages to a container widget. This applies to container widgets that require + calling a particular method to add a child rather than adding the child by passing + the parent. In particular, this is relevant for containers that are not a + a subclass of the containers provided in \QD, but are based on the notion + of \e{Current Page}. In addition, you need to provide a container extension + for them. + + The \c{} element can contain a list of property meta information. + + The tag \c{} may be used to specify a tool tip to be shown in Property Editor + when hovering over the property. The property name is given in the attribute \c name and + the element text is the tooltip. This functionality was added in Qt 5.6. + + For properties of type string, the \c{} tag can be used. + This tag has the following attributes: + + \table + \header + \li Attribute + \li Presence + \li Values + \li Comment + \row + \li \c{name} + \li required + \li Name of the property + \row + \li \c{type} + \li required + \li See below table + \li The value of the attribute determines how the property editor will handle them. + \row + \li \c{notr} + \li optional + \li "true", "false" + \li If the attribute is "true", the value is not meant to be translated. + \endtable + + Values of the \c{type} attribute of the string property: + + \table + \header + \li Value + \li Type + \row + \li \c{"richtext"} + \li Rich text. + \row + \li \c{"multiline"} + \li Multi-line plain text. + \row + \li \c{"singleline"} + \li Single-line plain text. + \row + \li \c{"stylesheet"} + \li A CSS-style sheet. + \row + \li \c{"objectname"} + \li An object name (restricted set of valid characters). + \row + \li \c{"url"} + \li URL, file name. + \endtable + + \section1 Plugin Requirements + + In order for plugins to work correctly on all platforms, you need to ensure + that they export the symbols needed by \QD. + + First of all, the plugin class must be exported in order for the plugin to + be loaded by \QD. Use the Q_PLUGIN_METADATA() macro to do this. Also, the + QDESIGNER_WIDGET_EXPORT macro must be used to define each custom widget class + within a plugin, that \QD will instantiate. + + + \section1 Creating Well Behaved Widgets + + Some custom widgets have special user interface features that may make them + behave differently to many of the standard widgets found in \QD. + Specifically, if a custom widget grabs the keyboard as a result of a call + to QWidget::grabKeyboard(), the operation of \QD will be affected. + + To give custom widgets special behavior in \QD, provide an implementation + of the initialize() function to configure the widget construction process + for \QD specific behavior. This function will be called for the first time + before any calls to createWidget() and could perhaps set an internal flag + that can be tested later when \QD calls the plugin's createWidget() + function. + + + \target BuildingandInstallingthePlugin + \section1 Building and Installing the Plugin + + \section2 A Simple Plugin + + The \l{Custom Widget Plugin} demonstrates a simple \QD plugin. + + The project file for a plugin must specify the headers and sources for + both the custom widget and the plugin interface. Typically, this file only + has to specify that the plugin's project will be built as a library, but + with specific plugin support for \QD. For \c CMake, this is done with + the following declarations: + + \snippet customwidgetplugin/CMakeLists.txt 0 + \snippet customwidgetplugin/CMakeLists.txt 1 + \snippet customwidgetplugin/CMakeLists.txt 2 + + The link libraries list specifies \c Qt::UiPlugin. This indicates that + the plugin uses the abstract interfaces QDesignerCustomWidgetInterface + and QDesignerCustomWidgetCollectionInterface only and has no linkage + to the \QD libraries. When accessing other interfaces of \QD that have + linkage, \c Designer should be used instead; this ensures that the plugin + dynamically links to the \QD libraries and has a run-time dependency on + them. + + It is also necessary to ensure that the plugin is installed together with + other \QD widget plugins: + + \snippet customwidgetplugin/CMakeLists.txt 3 + \snippet customwidgetplugin/CMakeLists.txt 4 + + For \c qmake: + + \snippet customwidgetplugin/customwidgetplugin.pro 0 + \snippet customwidgetplugin/customwidgetplugin.pro 2 + + The \c QT variable contains the keyword \c uiplugin, which is + the equivalent of the \c Qt::UiPlugin library. + + It is also necessary to ensure that the plugin is installed together with + other \QD widget plugins: + + \snippet manual/doc_src_designer-manual.pro 4 + + The \c $[QT_INSTALL_PLUGINS] variable is a placeholder to the location of + the installed Qt plugins. You can configure \QD to look for plugins in + other locations by setting the \c QT_PLUGIN_PATH environment variable + before running the application. + + \note \QD will look for a \c designer subdirectory in each path supplied. + + See QCoreApplication::libraryPaths() for more information about customizing + paths for libraries and plugins with Qt applications. + + If plugins are built in a mode that is incompatible with \QD, they will + not be loaded and installed. For more information about plugins, see the + \l{plugins-howto.html}{Plugins HOWTO} document. + + \section2 Splitting up the Plugin + + The simple approach explained above introduces a problem particularly + when using the other interfaces of \QD that have linkage: + The application using the custom widget will then depend on + \QD headers and libraries. In a real world scenario, this is not desired. + + The following sections describe how to resolve this. + + \section3 Linking the Widget into the Application + + When using \c qmake, the source and header file of the custom widget + can be shared between the application and \QD by creating a \c{.pri} + file for inclusion: + + \code + INCLUDEPATH += $$PWD + HEADERS += $$PWD/analogclock.h + SOURCES += $$PWD/analogclock.cpp + \endcode + + This file would then be included by the \c{.pro} file of the plugin and + the application: + + \code + include(customwidget.pri) + \endcode + + When using \c CMake, the source files of the widget can similarly be + added to the application project. + + \section3 Sharing the Widget Using a Library + + Another approach is to put the widget into a library that is linked to + the \QD plugin as well as to the application. It is recommended to + use static libraries to avoid problems locating the library at run-time. + + For shared libraries, see \l{sharedlibrary.html}{Creating Shared Libraries}. + + \section3 Using the Plugin with QUiLoader + + The preferred way of adding custom widgets to QUiLoader is to subclass it + reimplementing QUiLoader::createWidget(). + + However, it is also possible to use \QD custom widget plugins + (see QUiLoader::pluginPaths() and related functions). To avoid having + to deploy the \QD libraries onto the target device, those plugins should + have no linkage to the \QD libraries (\c {QT = uiplugin}, see + \l{Creating Custom Widgets for Qt Widgets Designer#BuildingandInstallingthePlugin}). + + \section1 Related Examples + + For more information on using custom widgets in \QD, refer to the + \l{customwidgetplugin}{Custom Widget Plugin} and + \l{taskmenuextension}{Task Menu Extension} examples for more + information about using custom widgets in \QD. Also, you can use the + QDesignerCustomWidgetCollectionInterface class to combine several custom + widgets into a single library. +*/ + + +/*! + \page designer-creating-custom-widgets-extensions.html + \previouspage Creating Custom Widgets for Qt Widgets Designer + \nextpage Qt Widgets Designer's UI File Format + + \title Creating Custom Widget Extensions + + Once you have a custom widget plugin for \QD, you can provide it with the + expected behavior and functionality within \QD's workspace, using custom + widget extensions. + + + \section1 Extension Types + + There are several available types of extensions in \QD. You can use all of + these extensions in the same pattern, only replacing the respective + extension base class. + + QDesignerContainerExtension is necessary when implementing a custom + multi-page container. + + \table + \row + \li \inlineimage designer-manual-taskmenuextension.png + \li \b{QDesignerTaskMenuExtension} + + QDesignerTaskMenuExtension is useful for custom widgets. It provides an + extension that allows you to add custom menu entries to \QD's task + menu. + + The \l{taskmenuextension}{Task Menu Extension} example + illustrates how to use this class. + + \row + \li \inlineimage designer-manual-containerextension.png + \li \b{QDesignerContainerExtension} + + QDesignerContainerExtension is necessary when implementing a custom + multi-page container. It provides an extension that allows you to add + and delete pages for a multi-page container plugin in \QD. + + The \l{containerextension}{Container Extension} example + further explains how to use this class. + + \note It is not possible to add custom per-page properties for some + widgets (e.g., QTabWidget) due to the way they are implemented. + \endtable + + \table + \row + \li \inlineimage designer-manual-membersheetextension.png + \li \b{QDesignerMemberSheetExtension} + + The QDesignerMemberSheetExtension class allows you to manipulate a + widget's member functions displayed when connecting signals and slots. + + \row + \li \inlineimage designer-manual-propertysheetextension.png + \li \b{QDesignerPropertySheetExtension, + QDesignerDynamicPropertySheetExtension} + + These extension classes allow you to control how a widget's properties + are displayed in \QD's property editor. + \endtable + +\omit + \row + \li + \li \b {QDesignerScriptExtension} + + The QDesignerScriptExtension class allows you to define script + snippets that are executed when a form is loaded. The extension + is primarily intended to be used to set up the internal states + of custom widgets. + \endtable +\endomit + + + \QD uses the QDesignerPropertySheetExtension and the + QDesignerMemberSheetExtension classes to feed its property and signal and + slot editors. Whenever a widget is selected in its workspace, \QD will + query for the widget's property sheet extension; likewise, whenever a + connection between two widgets is requested, \QD will query for the + widgets' member sheet extensions. + + \warning All widgets have default property and member sheets. If you + implement custom property sheet or member sheet extensions, your custom + extensions will override the default sheets. + + + \section1 Creating an Extension + + To create an extension you must inherit both QObject and the appropriate + base class, and reimplement its functions. Since we are implementing an + interface, we must ensure that it is made known to the meta object system + using the Q_INTERFACES() macro in the extension class's definition. For + example: + + \snippet manual/doc_src_designer-manual.cpp 7 + + This enables \QD to use the qobject_cast() function to query for supported + interfaces using a QObject pointer only. + + + \section1 Exposing an Extension to Qt Widgets Designer + + In \QD the extensions are not created until they are required. For this + reason, when implementing extensions, you must subclass QExtensionFactory + to create a class that is able to make instances of your extensions. Also, + you must register your factory with \QD's extension manager; the extension + manager handles the construction of extensions. + + When an extension is requested, \QD's extension manager will run through + its registered factories calling QExtensionFactory::createExtension() for + each of them until it finds one that is able to create the requested + extension for the selected widget. This factory will then make an instance + of the extension. + + \image qtdesignerextensions.png + + + \section2 Creating an Extension Factory + + The QExtensionFactory class provides a standard extension factory, but it + can also be used as an interface for custom extension factories. + + The purpose is to reimplement the QExtensionFactory::createExtension() + function, making it able to create your extension, such as a + \l{containerextension}{MultiPageWidget} container extension. + + You can either create a new QExtensionFactory and reimplement the + QExtensionFactory::createExtension() function: + + \snippet manual/doc_src_designer-manual.cpp 8 + + or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to enable the factory to + create your custom extension as well: + + \snippet manual/doc_src_designer-manual.cpp 9 + + + \section2 Accessing Qt Widgets Designer's Extension Manager + + When implementing a custom widget plugin, you must subclass the + QDesignerCustomWidgetInterface to expose your plugin to \QD. This is + covered in more detail in the + \l{Creating Custom Widgets for Qt Widgets Designer} section. The registration of + an extension factory is typically made in the + QDesignerCustomWidgetInterface::initialize() function: + + \snippet manual/doc_src_designer-manual.cpp 10 + + The \c formEditor parameter in the + QDesignerCustomWidgetInterface::initialize() function is a pointer to \QD's + current QDesignerFormEditorInterface object. You must use the + QDesignerFormEditorInterface::extensionManager() function to retrieve an + interface to \QD's extension manager. Then you use the + QExtensionManager::registerExtensions() function to register your custom + extension factory. + + + \section1 Related Examples + + For more information on creating custom widget extensions in \QD, refer to + the \l{taskmenuextension}{Task Menu Extension} and + \l{containerextension}{Container Extension} examples. +*/ + + +/*! + \page designer-ui-file-format.html + \previouspage Creating Custom Widget Extensions + + \title Qt Widgets Designer's UI File Format + + The \c UI file format used by \QD is described by the + \l{http://www.w3.org/XML/Schema}{XML schema} presented below, + which we include for your convenience. Be aware that the format + may change in future Qt releases. + + \quotefile ../../../../data/ui4.xsd +*/ diff --git a/src/tools/designer/src/designer/doc/src/qtdesigner-index.qdoc b/src/tools/designer/src/designer/doc/src/qtdesigner-index.qdoc new file mode 100644 index 00000000000..469cb587f9e --- /dev/null +++ b/src/tools/designer/src/designer/doc/src/qtdesigner-index.qdoc @@ -0,0 +1,53 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \page qtdesigner-index.html + \title Qt Widgets Designer + + \brief Provides classes to create your own custom widget plugins for + \QD and classes to access \QD components. + + In addition, the QFormBuilder class provides the possibility of + constructing user interfaces from UI files at run-time. + + \note This documentation covers the tool \QD and classes + related to building graphical user interfaces for Qt Widgets. + \l {Qt Design Studio Manual}{Qt Design Studio} is a UI composition tool + that covers all steps of UI design and implementation for Qt Quick user + interfaces. + + \if !defined(qtforpython) + + \section1 Using the Module + + \include {module-use.qdocinc} {using the c++ api} + + \section2 Building with CMake + + \include {module-use.qdocinc} {building with cmake} {Designer} + + \section2 Building with qmake + + \include {module-use.qdocinc} {building_with_qmake} {designer} + \endif + + \section1 Articles and Guides + + These articles contain information about \QD. + + \list + \li \l{Creating and Using Components for Qt Widgets Designer} - Creating and using + custom widget plugins + \li \l{Qt Widgets Designer Manual} - Using Qt Widgets Designer + \endlist + + \section1 API Reference + + These are links to the API reference material. + + \list + \li \l{Qt Widgets Designer C++ Classes}{C++ Classes} + \endlist + +*/ diff --git a/src/tools/designer/src/designer/doc/src/qtdesigner-module.qdoc b/src/tools/designer/src/designer/doc/src/qtdesigner-module.qdoc new file mode 100644 index 00000000000..1e545de8bc9 --- /dev/null +++ b/src/tools/designer/src/designer/doc/src/qtdesigner-module.qdoc @@ -0,0 +1,21 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \module QtDesigner + \title Qt Widgets Designer C++ Classes + \ingroup modules + \qtcmakepackage Designer + \qtvariable designer + + \brief Provides classes to create your own custom widget plugins for + \QD and classes to access \QD components. + + In addition, the QFormBuilder class provides the possibility of + constructing user interfaces from UI files at run-time. +*/ + +/*! + \namespace qdesigner_internal + \internal +*/ diff --git a/src/tools/designer/src/designer/images/designer.png b/src/tools/designer/src/designer/images/designer.png new file mode 100644 index 00000000000..9e6097c7d33 Binary files /dev/null and b/src/tools/designer/src/designer/images/designer.png differ diff --git a/src/tools/designer/src/designer/main.cpp b/src/tools/designer/src/designer/main.cpp new file mode 100644 index 00000000000..445996202d8 --- /dev/null +++ b/src/tools/designer/src/designer/main.cpp @@ -0,0 +1,38 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner.h" +#include +#include +#include + +#include + +QT_USE_NAMESPACE + +static const char rhiBackEndVar[] = "QSG_RHI_BACKEND"; + +int main(int argc, char *argv[]) +{ + // Enable the QWebEngineView, QQuickWidget plugins on Windows. + if (QOperatingSystemVersion::currentType() == QOperatingSystemVersion::Windows + && !qEnvironmentVariableIsSet(rhiBackEndVar)) { + qputenv(rhiBackEndVar, "gl"); + } + + // required for QWebEngineView + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); + + QDesigner app(argc, argv); + switch (app.parseCommandLineArguments()) { + case QDesigner::ParseArgumentsSuccess: + break; + case QDesigner::ParseArgumentsError: + return 1; + case QDesigner::ParseArgumentsHelpRequested: + return 0; + } + QGuiApplication::setQuitOnLastWindowClosed(false); + + return QApplication::exec(); +} diff --git a/src/tools/designer/src/designer/mainwindow.cpp b/src/tools/designer/src/designer/mainwindow.cpp new file mode 100644 index 00000000000..c0f314a0b80 --- /dev/null +++ b/src/tools/designer/src/designer/mainwindow.cpp @@ -0,0 +1,372 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "mainwindow.h" +#include "qdesigner.h" +#include "qdesigner_actions.h" +#include "qdesigner_workbench.h" +#include "qdesigner_formwindow.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_settings.h" +#include "qttoolbardialog_p.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using ActionList = QList; + +// Helpers for creating toolbars and menu + +static void addActionsToToolBar(const ActionList &actions, QToolBar *t) +{ + for (QAction *action : actions) { + if (action->property(QDesignerActions::defaultToolbarPropertyName).toBool()) + t->addAction(action); + } +} + +static QToolBar *createToolBar(const QString &title, const QString &objectName, + const ActionList &actions) +{ + QToolBar *rc = new QToolBar; + rc->setObjectName(objectName); + rc->setWindowTitle(title); + addActionsToToolBar(actions, rc); + return rc; +} + +// ---------------- MainWindowBase + +MainWindowBase::MainWindowBase(QWidget *parent, Qt::WindowFlags flags) : + QMainWindow(parent, flags) +{ +#ifndef Q_OS_MACOS + setWindowIcon(qDesigner->windowIcon()); +#endif +} + +void MainWindowBase::closeEvent(QCloseEvent *e) +{ + switch (m_policy) { + case AcceptCloseEvents: + QMainWindow::closeEvent(e); + break; + case EmitCloseEventSignal: + emit closeEventReceived(e); + break; + } +} + +QList MainWindowBase::createToolBars(const QDesignerActions *actions, bool singleToolBar) +{ + // Note that whenever you want to add a new tool bar here, you also have to update the default + // action groups added to the toolbar manager in the mainwindow constructor + QList rc; + if (singleToolBar) { + //: Not currently used (main tool bar) + QToolBar *main = createToolBar(tr("Main"), u"mainToolBar"_s, + actions->fileActions()->actions()); + addActionsToToolBar(actions->editActions()->actions(), main); + addActionsToToolBar(actions->toolActions()->actions(), main); + addActionsToToolBar(actions->formActions()->actions(), main); + rc.push_back(main); + } else { + rc.append(createToolBar(tr("File"), u"fileToolBar"_s, + actions->fileActions()->actions())); + rc.append(createToolBar(tr("Edit"), u"editToolBar"_s, + actions->editActions()->actions())); + rc.append(createToolBar(tr("Tools"), u"toolsToolBar"_s, + actions->toolActions()->actions())); + rc.append(createToolBar(tr("Form"), u"formToolBar"_s, + actions->formActions()->actions())); + } + return rc; +} + +QString MainWindowBase::mainWindowTitle() +{ + return tr("Qt Widgets Designer"); +} + +// Use the minor Qt version as settings versions to avoid conflicts +int MainWindowBase::settingsVersion() +{ + const int version = QT_VERSION; + return (version & 0x00FF00) >> 8; +} + +// ----------------- DockedMdiArea + +DockedMdiArea::DockedMdiArea(const QString &extension, QWidget *parent) : + QMdiArea(parent), + m_extension(extension) +{ + setFrameStyle(QFrame::StyledPanel | QFrame::Sunken); + setLineWidth(1); + setAcceptDrops(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); +} + +QStringList DockedMdiArea::uiFiles(const QMimeData *d) const +{ + // Extract dropped UI files from Mime data. + QStringList rc; + if (!d->hasFormat("text/uri-list"_L1)) + return rc; + const auto urls = d->urls(); + if (urls.isEmpty()) + return rc; + for (const auto &url : urls) { + const QString fileName = url.toLocalFile(); + if (!fileName.isEmpty() && fileName.endsWith(m_extension)) + rc.push_back(fileName); + } + return rc; +} + +bool DockedMdiArea::event(QEvent *event) +{ + // Listen for desktop file manager drop and emit a signal once a file is + // dropped. + switch (event->type()) { + case QEvent::DragEnter: { + QDragEnterEvent *e = static_cast(event); + if (!uiFiles(e->mimeData()).isEmpty()) { + e->acceptProposedAction(); + return true; + } + } + break; + case QEvent::Drop: { + QDropEvent *e = static_cast(event); + const QStringList files = uiFiles(e->mimeData()); + for (const auto &f : files) + emit fileDropped(f); + e->acceptProposedAction(); + return true; + } + break; + default: + break; + } + return QMdiArea::event(event); +} + +// ------------- ToolBarManager: + +static void addActionsToToolBarManager(const ActionList &al, const QString &title, QtToolBarManager *tbm) +{ + for (QAction *action : al) + tbm->addAction(action, title); +} + +ToolBarManager::ToolBarManager(QMainWindow *configureableMainWindow, + QWidget *parent, + QMenu *toolBarMenu, + const QDesignerActions *actions, + const QList &toolbars, + const QList &toolWindows) : + QObject(parent), + m_configureableMainWindow(configureableMainWindow), + m_parent(parent), + m_toolBarMenu(toolBarMenu), + m_manager(new QtToolBarManager(this)), + m_configureAction(new QAction(tr("Configure Toolbars..."), this)), + m_toolbars(toolbars) +{ + m_configureAction->setMenuRole(QAction::NoRole); + m_configureAction->setObjectName(u"__qt_configure_tool_bars_action"_s); + connect(m_configureAction, &QAction::triggered, this, &ToolBarManager::configureToolBars); + + m_manager->setMainWindow(configureableMainWindow); + + for (QToolBar *tb : std::as_const(m_toolbars)) { + const QString title = tb->windowTitle(); + m_manager->addToolBar(tb, title); + addActionsToToolBarManager(tb->actions(), title, m_manager); + } + + addActionsToToolBarManager(actions->windowActions()->actions(), tr("Window"), m_manager); + addActionsToToolBarManager(actions->helpActions()->actions(), tr("Help"), m_manager); + + // Filter out the device profile preview actions which have int data(). + ActionList previewActions = actions->styleActions()->actions(); + auto it = previewActions.begin(); + for ( ; (*it)->isSeparator() || (*it)->data().metaType().id() == QMetaType::Int; ++it) ; + previewActions.erase(previewActions.begin(), it); + addActionsToToolBarManager(previewActions, tr("Style"), m_manager); + + const QString dockTitle = tr("Dock views"); + for (QDesignerToolWindow *tw : toolWindows) { + if (QAction *action = tw->action()) + m_manager->addAction(action, dockTitle); + } + + addActionsToToolBarManager(actions->fileActions()->actions(), tr("File"), m_manager); + addActionsToToolBarManager(actions->editActions()->actions(), tr("Edit"), m_manager); + addActionsToToolBarManager(actions->toolActions()->actions(), tr("Tools"), m_manager); + addActionsToToolBarManager(actions->formActions()->actions(), tr("Form"), m_manager); + + m_manager->addAction(m_configureAction, tr("Toolbars")); + updateToolBarMenu(); +} + +// sort function for sorting tool bars alphabetically by title [non-static since called from template] + +bool toolBarTitleLessThan(const QToolBar *t1, const QToolBar *t2) +{ + return t1->windowTitle() < t2->windowTitle(); +} + +void ToolBarManager::updateToolBarMenu() +{ + // Sort tool bars alphabetically by title + std::stable_sort(m_toolbars.begin(), m_toolbars.end(), toolBarTitleLessThan); + // add to menu + m_toolBarMenu->clear(); + for (QToolBar *tb : std::as_const(m_toolbars)) + m_toolBarMenu->addAction(tb->toggleViewAction()); + m_toolBarMenu->addAction(m_configureAction); +} + +void ToolBarManager::configureToolBars() +{ + QtToolBarDialog dlg(m_parent); + dlg.setToolBarManager(m_manager); + dlg.exec(); + updateToolBarMenu(); +} + +QByteArray ToolBarManager::saveState(int version) const +{ + return m_manager->saveState(version); +} + +bool ToolBarManager::restoreState(const QByteArray &state, int version) +{ + return m_manager->restoreState(state, version); +} + +// ---------- DockedMainWindow + +DockedMainWindow::DockedMainWindow(QDesignerWorkbench *wb, + QMenu *toolBarMenu, + const QList &toolWindows) +{ + setObjectName(u"MDIWindow"_s); + setWindowTitle(mainWindowTitle()); + + const QList toolbars = createToolBars(wb->actionManager(), false); + for (QToolBar *tb : toolbars) + addToolBar(tb); + DockedMdiArea *dma = new DockedMdiArea(wb->actionManager()->uiExtension()); + connect(dma, &DockedMdiArea::fileDropped, + this, &DockedMainWindow::fileDropped); + connect(dma, &QMdiArea::subWindowActivated, + this, &DockedMainWindow::slotSubWindowActivated); + setCentralWidget(dma); + + QStatusBar *sb = statusBar(); + Q_UNUSED(sb); + + m_toolBarManager = new ToolBarManager(this, this, toolBarMenu, wb->actionManager(), toolbars, toolWindows); +} + +QMdiArea *DockedMainWindow::mdiArea() const +{ + return static_cast(centralWidget()); +} + +void DockedMainWindow::slotSubWindowActivated(QMdiSubWindow* subWindow) +{ + if (subWindow) { + QWidget *widget = subWindow->widget(); + if (QDesignerFormWindow *fw = qobject_cast(widget)) { + emit formWindowActivated(fw); + mdiArea()->setActiveSubWindow(subWindow); + } + } +} + +// Create a MDI subwindow for the form. +QMdiSubWindow *DockedMainWindow::createMdiSubWindow(QWidget *fw, Qt::WindowFlags f, const QKeySequence &designerCloseActionShortCut) +{ + QMdiSubWindow *rc = mdiArea()->addSubWindow(fw, f); + // Make action shortcuts respond only if focused to avoid conflicts with + // designer menu actions + if (designerCloseActionShortCut == QKeySequence(QKeySequence::Close)) { + const ActionList systemMenuActions = rc->systemMenu()->actions(); + for (auto *a : systemMenuActions) { + if (a->shortcut() == designerCloseActionShortCut) { + a->setShortcutContext(Qt::WidgetShortcut); + break; + } + } + } + return rc; +} + +DockedMainWindow::DockWidgetList DockedMainWindow::addToolWindows(const DesignerToolWindowList &tls) +{ + DockWidgetList rc; + for (QDesignerToolWindow *tw : tls) { + QDockWidget *dockWidget = new QDockWidget; + dockWidget->setObjectName(tw->objectName() + "_dock"_L1); + dockWidget->setWindowTitle(tw->windowTitle()); + addDockWidget(tw->dockWidgetAreaHint(), dockWidget); + dockWidget->setWidget(tw); + rc.push_back(dockWidget); + } + return rc; +} + +// Settings consist of MainWindow state and tool bar manager state +void DockedMainWindow::restoreSettings(const QDesignerSettings &s, const DockWidgetList &dws, const QRect &desktopArea) +{ + const int version = settingsVersion(); + m_toolBarManager->restoreState(s.toolBarsState(DockedMode), version); + + // If there are no old geometry settings, show the window maximized + s.restoreGeometry(this, QRect(desktopArea.topLeft(), QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX))); + + const QByteArray mainWindowState = s.mainWindowState(DockedMode); + const bool restored = !mainWindowState.isEmpty() && restoreState(mainWindowState, version); + if (!restored) { + // Default: Tabify less relevant windows bottom/right. + tabifyDockWidget(dws.at(QDesignerToolWindow::SignalSlotEditor), + dws.at(QDesignerToolWindow::ActionEditor)); + tabifyDockWidget(dws.at(QDesignerToolWindow::ActionEditor), + dws.at(QDesignerToolWindow::ResourceEditor)); + } +} + +void DockedMainWindow::saveSettings(QDesignerSettings &s) const +{ + const int version = settingsVersion(); + s.setToolBarsState(DockedMode, m_toolBarManager->saveState(version)); + s.saveGeometryFor(this); + s.setMainWindowState(DockedMode, saveState(version)); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/mainwindow.h b/src/tools/designer/src/designer/mainwindow.h new file mode 100644 index 00000000000..df41d6f3439 --- /dev/null +++ b/src/tools/designer/src/designer/mainwindow.h @@ -0,0 +1,150 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerActions; +class QDesignerWorkbench; +class QDesignerToolWindow; +class QDesignerFormWindow; +class QDesignerSettings; + +class QtToolBarManager; +class QToolBar; +class QMdiArea; +class QMenu; +class QByteArray; +class QMimeData; + +/* A main window that has a configureable policy on handling close events. If + * enabled, it can forward the close event to external handlers. + * Base class for windows that can switch roles between tool windows + * and main windows. */ + +class MainWindowBase: public QMainWindow +{ + Q_DISABLE_COPY_MOVE(MainWindowBase) + Q_OBJECT +protected: + explicit MainWindowBase(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::Window); + +public: + enum CloseEventPolicy { + /* Always accept close events */ + AcceptCloseEvents, + /* Emit a signal with the event, have it handled elsewhere */ + EmitCloseEventSignal }; + + CloseEventPolicy closeEventPolicy() const { return m_policy; } + void setCloseEventPolicy(CloseEventPolicy pol) { m_policy = pol; } + + static QList createToolBars(const QDesignerActions *actions, bool singleToolBar); + static QString mainWindowTitle(); + + // Use the minor Qt version as settings versions to avoid conflicts + static int settingsVersion(); + +signals: + void closeEventReceived(QCloseEvent *e); + +protected: + void closeEvent(QCloseEvent *e) override; +private: + CloseEventPolicy m_policy = AcceptCloseEvents; +}; + +/* An MdiArea that listens for desktop file manager file drop events and emits + * a signal to open a dropped file. */ +class DockedMdiArea : public QMdiArea +{ + Q_DISABLE_COPY_MOVE(DockedMdiArea) + Q_OBJECT +public: + explicit DockedMdiArea(const QString &extension, QWidget *parent = nullptr); + +signals: + void fileDropped(const QString &); + +protected: + bool event (QEvent *event) override; + +private: + QStringList uiFiles(const QMimeData *d) const; + + const QString m_extension; +}; + +// Convenience class that manages a QtToolBarManager and an action to trigger +// it on a mainwindow. +class ToolBarManager : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(ToolBarManager) +public: + explicit ToolBarManager(QMainWindow *configureableMainWindow, + QWidget *parent, + QMenu *toolBarMenu, + const QDesignerActions *actions, + const QList &toolbars, + const QList &toolWindows); + + QByteArray saveState(int version = 0) const; + bool restoreState(const QByteArray &state, int version = 0); + +private slots: + void configureToolBars(); + void updateToolBarMenu(); + +private: + QMainWindow *m_configureableMainWindow; + QWidget *m_parent; + QMenu *m_toolBarMenu; + QtToolBarManager *m_manager; + QAction *m_configureAction; + QList m_toolbars; +}; + +/* Main window to be used for docked mode */ +class DockedMainWindow : public MainWindowBase { + Q_OBJECT + Q_DISABLE_COPY_MOVE(DockedMainWindow) +public: + using DesignerToolWindowList = QList; + using DockWidgetList = QList; + + explicit DockedMainWindow(QDesignerWorkbench *wb, + QMenu *toolBarMenu, + const DesignerToolWindowList &toolWindows); + + // Create a MDI subwindow for the form. + QMdiSubWindow *createMdiSubWindow(QWidget *fw, Qt::WindowFlags f, const QKeySequence &designerCloseActionShortCut); + + QMdiArea *mdiArea() const; + + DockWidgetList addToolWindows(const DesignerToolWindowList &toolWindows); + + void restoreSettings(const QDesignerSettings &s, const DockWidgetList &dws, const QRect &desktopArea); + void saveSettings(QDesignerSettings &) const; + +signals: + void fileDropped(const QString &); + void formWindowActivated(QDesignerFormWindow *); + +private slots: + void slotSubWindowActivated(QMdiSubWindow*); + +private: + ToolBarManager *m_toolBarManager = nullptr; +}; + +QT_END_NAMESPACE + +#endif // MAINWINDOW_H diff --git a/src/tools/designer/src/designer/newform.cpp b/src/tools/designer/src/designer/newform.cpp new file mode 100644 index 00000000000..3c77bc6a65b --- /dev/null +++ b/src/tools/designer/src/designer/newform.cpp @@ -0,0 +1,187 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "newform.h" +#include "qdesigner_workbench.h" +#include "qdesigner_actions.h" +#include "qdesigner_formwindow.h" +#include "qdesigner_settings.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +NewForm::NewForm(QDesignerWorkbench *workbench, QWidget *parentWidget, const QString &fileName) + : QDialog(parentWidget, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint), + m_fileName(fileName), + m_newFormWidget(QDesignerNewFormWidgetInterface::createNewFormWidget(workbench->core())), + m_workbench(workbench), + m_chkShowOnStartup(new QCheckBox(tr("Show this Dialog on Startup"))), + m_createButton(new QPushButton(QApplication::translate("NewForm", "C&reate", nullptr))), + m_recentButton(new QPushButton(QApplication::translate("NewForm", "Recent", nullptr))) +{ + setWindowTitle(tr("New Form")); + QDesignerSettings settings(m_workbench->core()); + + QVBoxLayout *vBoxLayout = new QVBoxLayout; + + connect(m_newFormWidget, &QDesignerNewFormWidgetInterface::templateActivated, + this, &NewForm::slotTemplateActivated); + connect(m_newFormWidget, &QDesignerNewFormWidgetInterface::currentTemplateChanged, + this, &NewForm::slotCurrentTemplateChanged); + vBoxLayout->addWidget(m_newFormWidget); + + QFrame *horizontalLine = new QFrame; + horizontalLine->setFrameShape(QFrame::HLine); + horizontalLine->setFrameShadow(QFrame::Sunken); + vBoxLayout->addWidget(horizontalLine); + + m_chkShowOnStartup->setChecked(settings.showNewFormOnStartup()); + vBoxLayout->addWidget(m_chkShowOnStartup); + + m_buttonBox = createButtonBox(); + vBoxLayout->addWidget(m_buttonBox); + setLayout(vBoxLayout); + + resize(500, 400); + slotCurrentTemplateChanged(m_newFormWidget->hasCurrentTemplate()); +} + +QDialogButtonBox *NewForm::createButtonBox() +{ + // Dialog buttons with 'recent files' + QDialogButtonBox *buttonBox = new QDialogButtonBox; + buttonBox->addButton(QApplication::translate("NewForm", "&Close", nullptr), + QDialogButtonBox::RejectRole); + buttonBox->addButton(m_createButton, QDialogButtonBox::AcceptRole); + buttonBox->addButton(QApplication::translate("NewForm", "&Open...", nullptr), + QDialogButtonBox::ActionRole); + buttonBox->addButton(m_recentButton, QDialogButtonBox::ActionRole); + QDesignerActions *da = m_workbench->actionManager(); + QMenu *recentFilesMenu = new QMenu(tr("&Recent Forms"), m_recentButton); + // Pop the "Recent Files" stuff in here. + const auto recentActions = da->recentFilesActions()->actions(); + for (auto action : recentActions) { + recentFilesMenu->addAction(action); + connect(action, &QAction::triggered, this, &NewForm::recentFileChosen); + } + m_recentButton->setMenu(recentFilesMenu); + connect(buttonBox, &QDialogButtonBox::clicked, this, &NewForm::slotButtonBoxClicked); + return buttonBox; +} + +NewForm::~NewForm() +{ + QDesignerSettings settings (m_workbench->core()); + settings.setShowNewFormOnStartup(m_chkShowOnStartup->isChecked()); +} + +void NewForm::recentFileChosen() +{ + QAction *action = qobject_cast(sender()); + if (action && action->objectName() != "__qt_action_clear_menu_"_L1) + close(); +} + +void NewForm::slotCurrentTemplateChanged(bool templateSelected) +{ + if (templateSelected) { + m_createButton->setEnabled(true); + m_createButton->setDefault(true); + } else { + m_createButton->setEnabled(false); + } +} + +void NewForm::slotTemplateActivated() +{ + m_createButton->animateClick(); +} + +void NewForm::slotButtonBoxClicked(QAbstractButton *btn) +{ + switch (m_buttonBox->buttonRole(btn)) { + case QDialogButtonBox::RejectRole: + reject(); + break; + case QDialogButtonBox::ActionRole: + if (btn != m_recentButton) { + m_fileName.clear(); + if (m_workbench->actionManager()->openForm(this)) + accept(); + } + break; + case QDialogButtonBox::AcceptRole: { + QString errorMessage; + if (openTemplate(&errorMessage)) { + accept(); + } else { + QMessageBox::warning(this, tr("Read error"), errorMessage); + } + } + break; + default: + break; + } +} + +bool NewForm::openTemplate(QString *ptrToErrorMessage) +{ + const QString contents = m_newFormWidget->currentTemplate(ptrToErrorMessage); + if (contents.isEmpty()) + return false; + // Write to temporary file and open + QString tempPattern = QDir::tempPath(); + if (!tempPattern.endsWith(QDir::separator())) // platform-dependant + tempPattern += QDir::separator(); + tempPattern += "XXXXXX.ui"_L1; + QTemporaryFile tempFormFile(tempPattern); + + tempFormFile.setAutoRemove(true); + if (!tempFormFile.open()) { + *ptrToErrorMessage = tr("A temporary form file could not be created in %1: %2") + .arg(QDir::toNativeSeparators(QDir::tempPath()), tempFormFile.errorString()); + return false; + } + const QString tempFormFileName = tempFormFile.fileName(); + tempFormFile.write(contents.toUtf8()); + if (!tempFormFile.flush()) { + *ptrToErrorMessage = tr("The temporary form file %1 could not be written: %2") + .arg(QDir::toNativeSeparators(tempFormFileName), tempFormFile.errorString()); + return false; + } + tempFormFile.close(); + return m_workbench->openTemplate(tempFormFileName, m_fileName, ptrToErrorMessage); +} + +QImage NewForm::grabForm(QDesignerFormEditorInterface *core, + QIODevice &file, + const QString &workingDir, + const qdesigner_internal::DeviceProfile &dp) +{ + return qdesigner_internal::NewFormWidget::grabForm(core, file, workingDir, dp); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/newform.h b/src/tools/designer/src/designer/newform.h new file mode 100644 index 00000000000..e13f685718f --- /dev/null +++ b/src/tools/designer/src/designer/newform.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef NEWFORM_H +#define NEWFORM_H + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + class DeviceProfile; +} + +class QDesignerFormEditorInterface; +class QDesignerNewFormWidgetInterface; +class QDesignerWorkbench; + +class QCheckBox; +class QAbstractButton; +class QPushButton; +class QDialogButtonBox; +class QImage; +class QIODevice; + +class NewForm: public QDialog +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(NewForm) + +public: + NewForm(QDesignerWorkbench *workbench, + QWidget *parentWidget, + // Use that file name instead of a temporary one + const QString &fileName = QString()); + + ~NewForm() override; + + // Convenience for implementing file dialogs with preview + static QImage grabForm(QDesignerFormEditorInterface *core, + QIODevice &file, + const QString &workingDir, + const qdesigner_internal::DeviceProfile &dp); + +private slots: + void slotButtonBoxClicked(QAbstractButton *btn); + void recentFileChosen(); + void slotCurrentTemplateChanged(bool templateSelected); + void slotTemplateActivated(); + +private: + QDialogButtonBox *createButtonBox(); + bool openTemplate(QString *ptrToErrorMessage); + + QString m_fileName; + QDesignerNewFormWidgetInterface *m_newFormWidget; + QDesignerWorkbench *m_workbench; + QCheckBox *m_chkShowOnStartup; + QPushButton *m_createButton; + QPushButton *m_recentButton; + QDialogButtonBox *m_buttonBox = nullptr; +}; + +QT_END_NAMESPACE + +#endif // NEWFORM_H diff --git a/src/tools/designer/src/designer/preferencesdialog.cpp b/src/tools/designer/src/designer/preferencesdialog.cpp new file mode 100644 index 00000000000..b3760928ca0 --- /dev/null +++ b/src/tools/designer/src/designer/preferencesdialog.cpp @@ -0,0 +1,79 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "preferencesdialog.h" +#include "ui_preferencesdialog.h" +#include "qdesigner_appearanceoptions.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +PreferencesDialog::PreferencesDialog(QDesignerFormEditorInterface *core, QWidget *parentWidget) : + QDialog(parentWidget), + m_ui(new QT_PREPEND_NAMESPACE(Ui)::PreferencesDialog()) +{ + m_ui->setupUi(this); + + m_optionsPages = core->optionsPages(); + + m_ui->m_optionTabWidget->clear(); + for (QDesignerOptionsPageInterface *optionsPage : std::as_const(m_optionsPages)) { + QWidget *page = optionsPage->createPage(this); + m_ui->m_optionTabWidget->addTab(page, optionsPage->name()); + if (QDesignerAppearanceOptionsWidget *appearanceWidget = qobject_cast(page)) + connect(appearanceWidget, &QDesignerAppearanceOptionsWidget::uiModeChanged, + this, &PreferencesDialog::slotUiModeChanged); + } + + connect(m_ui->m_dialogButtonBox, &QDialogButtonBox::rejected, this, &PreferencesDialog::slotRejected); + connect(m_ui->m_dialogButtonBox, &QDialogButtonBox::accepted, this, &PreferencesDialog::slotAccepted); + connect(applyButton(), &QAbstractButton::clicked, this, &PreferencesDialog::slotApply); +} + +PreferencesDialog::~PreferencesDialog() +{ + delete m_ui; +} + +QPushButton *PreferencesDialog::applyButton() const +{ + return m_ui->m_dialogButtonBox->button(QDialogButtonBox::Apply); +} + +void PreferencesDialog::slotApply() +{ + for (QDesignerOptionsPageInterface *optionsPage : std::as_const(m_optionsPages)) + optionsPage->apply(); +} + +void PreferencesDialog::slotAccepted() +{ + slotApply(); + closeOptionPages(); + accept(); +} + +void PreferencesDialog::slotRejected() +{ + closeOptionPages(); + reject(); +} + +void PreferencesDialog::slotUiModeChanged(bool modified) +{ + // Cannot "apply" a ui mode change (destroy the dialogs parent) + applyButton()->setEnabled(!modified); +} + +void PreferencesDialog::closeOptionPages() +{ + for (QDesignerOptionsPageInterface *optionsPage : std::as_const(m_optionsPages)) + optionsPage->finish(); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/preferencesdialog.h b/src/tools/designer/src/designer/preferencesdialog.h new file mode 100644 index 00000000000..c07b3e067a7 --- /dev/null +++ b/src/tools/designer/src/designer/preferencesdialog.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PREFERENCESDIALOG_H +#define PREFERENCESDIALOG_H + +#include + +QT_BEGIN_NAMESPACE + +class QPushButton; +class QDesignerFormEditorInterface; +class QDesignerOptionsPageInterface; + +namespace Ui { + class PreferencesDialog; +} + +class PreferencesDialog: public QDialog +{ + Q_OBJECT +public: + explicit PreferencesDialog(QDesignerFormEditorInterface *core, QWidget *parentWidget = nullptr); + ~PreferencesDialog(); + + +private slots: + void slotAccepted(); + void slotRejected(); + void slotApply(); + void slotUiModeChanged(bool modified); + +private: + QPushButton *applyButton() const; + void closeOptionPages(); + + Ui::PreferencesDialog *m_ui; + QList m_optionsPages; +}; + +QT_END_NAMESPACE + +#endif // PREFERENCESDIALOG_H diff --git a/src/tools/designer/src/designer/preferencesdialog.ui b/src/tools/designer/src/designer/preferencesdialog.ui new file mode 100644 index 00000000000..28d14bb8383 --- /dev/null +++ b/src/tools/designer/src/designer/preferencesdialog.ui @@ -0,0 +1,91 @@ + + + PreferencesDialog + + + + 0 + 0 + 474 + 304 + + + + + 0 + 0 + + + + Preferences + + + true + + + + + + + 0 + 0 + + + + 0 + + + + Tab 1 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Apply|QDialogButtonBox::Ok + + + + + + + + + m_dialogButtonBox + accepted() + PreferencesDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + m_dialogButtonBox + rejected() + PreferencesDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/tools/designer/src/designer/qdesigner.cpp b/src/tools/designer/src/designer/qdesigner.cpp new file mode 100644 index 00000000000..0ec87e2b08a --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner.cpp @@ -0,0 +1,301 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// designer +#include "qdesigner.h" +#include "qdesigner_actions.h" +#include "qdesigner_server.h" +#include "qdesigner_settings.h" +#include "qdesigner_workbench.h" +#include "mainwindow.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto designerApplicationName = "Designer"_L1; +static constexpr auto designerDisplayName = "Qt Widgets Designer"_L1; +static constexpr auto designerWarningPrefix = "Designer: "_L1; +static QtMessageHandler previousMessageHandler = nullptr; + +static void designerMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) +{ + // Only Designer warnings are displayed as box + QDesigner *designerApp = qDesigner; + if (type != QtWarningMsg || !designerApp || !msg.startsWith(designerWarningPrefix)) { + previousMessageHandler(type, context, msg); + return; + } + designerApp->showErrorMessage(msg); +} + +QDesigner::QDesigner(int &argc, char **argv) + : QApplication(argc, argv) +{ + setOrganizationName(u"QtProject"_s); + QGuiApplication::setApplicationDisplayName(designerDisplayName); + setApplicationName(designerApplicationName); + QDesignerComponents::initializeResources(); + +#if !defined(Q_OS_MACOS) && !defined(Q_OS_WIN) + setWindowIcon(QIcon(u":/qt-project.org/designer/images/designer.png"_s)); +#endif +} + +QDesigner::~QDesigner() +{ + delete m_workbench; + delete m_server; + delete m_client; +} + +void QDesigner::showErrorMessage(const QString &message) +{ + // strip the prefix + const QString qMessage = + message.right(message.size() - int(designerWarningPrefix.size())); + // If there is no main window yet, just store the message. + // The QErrorMessage would otherwise be hidden by the main window. + if (m_mainWindow) { + showErrorMessageBox(qMessage); + } else { + const QMessageLogContext emptyContext; + previousMessageHandler(QtWarningMsg, emptyContext, message); // just in case we crash + m_initializationErrors += qMessage; + m_initializationErrors += u'\n'; + } +} + +void QDesigner::showErrorMessageBox(const QString &msg) +{ + // Manually suppress consecutive messages. + // This happens if for example sth is wrong with custom widget creation. + // The same warning will be displayed by Widget box D&D and form Drop + // while trying to create instance. + if (m_errorMessageDialog && m_lastErrorMessage == msg) + return; + + if (!m_errorMessageDialog) { + m_lastErrorMessage.clear(); + m_errorMessageDialog = new QErrorMessage(m_mainWindow); + const QString title = QCoreApplication::translate("QDesigner", "%1 - warning").arg(designerApplicationName); + m_errorMessageDialog->setWindowTitle(title); + m_errorMessageDialog->setMinimumSize(QSize(600, 250)); + } + m_errorMessageDialog->showMessage(msg); + m_lastErrorMessage = msg; +} + +QDesignerWorkbench *QDesigner::workbench() const +{ + return m_workbench; +} + +QDesignerServer *QDesigner::server() const +{ + return m_server; +} + +static void showHelp(QCommandLineParser &parser, const QString &errorMessage = QString()) +{ + QString text; + QTextStream str(&text); + str << ""; + if (!errorMessage.isEmpty()) + str << "

" << errorMessage << "

"; + str << "
" << parser.helpText().toHtmlEscaped() << "
"; + QMessageBox box(errorMessage.isEmpty() ? QMessageBox::Information : QMessageBox::Warning, + QGuiApplication::applicationDisplayName(), text, + QMessageBox::Ok); + box.setTextInteractionFlags(Qt::TextBrowserInteraction); + box.exec(); +} + +struct Options +{ + QStringList files; + QString resourceDir{QLibraryInfo::path(QLibraryInfo::TranslationsPath)}; + QStringList pluginPaths; + bool server{false}; + quint16 clientPort{0}; + bool enableInternalDynamicProperties{false}; +}; + +static inline QDesigner::ParseArgumentsResult + parseDesignerCommandLineArguments(QCommandLineParser &parser, Options *options, + QString *errorMessage) +{ + parser.setApplicationDescription(u"Qt Widgets Designer " QT_VERSION_STR "\n\nUI designer for QWidget-based applications."_s); + const QCommandLineOption helpOption = parser.addHelpOption(); + parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); + const QCommandLineOption serverOption(u"server"_s, + u"Server mode"_s); + parser.addOption(serverOption); + const QCommandLineOption clientOption(u"client"_s, + u"Client mode"_s, + u"port"_s); + parser.addOption(clientOption); + const QCommandLineOption resourceDirOption(u"resourcedir"_s, + u"Resource directory"_s, + u"directory"_s); + parser.addOption(resourceDirOption); + const QCommandLineOption internalDynamicPropertyOption(u"enableinternaldynamicproperties"_s, + u"Enable internal dynamic properties"_s); + parser.addOption(internalDynamicPropertyOption); + const QCommandLineOption pluginPathsOption(u"plugin-path"_s, + u"Default plugin path list"_s, + u"path"_s); + parser.addOption(pluginPathsOption); + + parser.addPositionalArgument(u"files"_s, + u"The UI files to open."_s); + + if (!parser.parse(QCoreApplication::arguments())) { + *errorMessage = parser.errorText(); + return QDesigner::ParseArgumentsError; + } + + if (parser.isSet(helpOption)) + return QDesigner::ParseArgumentsHelpRequested; + // There is no way to retrieve the complete help text from QCommandLineParser, + // so, call process() to display it. + if (parser.isSet(u"help-all"_s)) + parser.process(QCoreApplication::arguments()); // exits + options->server = parser.isSet(serverOption); + if (parser.isSet(clientOption)) { + bool ok; + options->clientPort = parser.value(clientOption).toUShort(&ok); + if (!ok) { + *errorMessage = u"Non-numeric argument specified for -client"_s; + return QDesigner::ParseArgumentsError; + } + } + if (parser.isSet(resourceDirOption)) + options->resourceDir = parser.value(resourceDirOption); + const auto pluginPathValues = parser.values(pluginPathsOption); + for (const auto &pluginPath : pluginPathValues) + options->pluginPaths.append(pluginPath.split(QDir::listSeparator(), Qt::SkipEmptyParts)); + + options->enableInternalDynamicProperties = parser.isSet(internalDynamicPropertyOption); + options->files = parser.positionalArguments(); + return QDesigner::ParseArgumentsSuccess; +} + +QDesigner::ParseArgumentsResult QDesigner::parseCommandLineArguments() +{ + QString errorMessage; + Options options; + QCommandLineParser parser; + const ParseArgumentsResult result = parseDesignerCommandLineArguments(parser, &options, &errorMessage); + if (result != ParseArgumentsSuccess) { + showHelp(parser, errorMessage); + return result; + } + // initialize the sub components + if (options.clientPort) + m_client = new QDesignerClient(options.clientPort, this); + if (options.server) { + m_server = new QDesignerServer(); + printf("%d\n", m_server->serverPort()); + fflush(stdout); + } + if (options.enableInternalDynamicProperties) + QDesignerPropertySheet::setInternalDynamicPropertiesEnabled(true); + + std::unique_ptr designerTranslator(new QTranslator(this)); + if (designerTranslator->load(QLocale(), u"designer"_s, u"_"_s, options.resourceDir)) { + installTranslator(designerTranslator.release()); + std::unique_ptr qtTranslator(new QTranslator(this)); + if (qtTranslator->load(QLocale(), u"qt"_s, u"_"_s, options.resourceDir)) + installTranslator(qtTranslator.release()); + } + + m_workbench = new QDesignerWorkbench(options.pluginPaths); + + emit initialized(); + previousMessageHandler = qInstallMessageHandler(designerMessageHandler); // Warn when loading faulty forms + Q_ASSERT(previousMessageHandler); + + bool suppressNewFormShow = m_workbench->readInBackup(); + + for (auto fileName : std::as_const(options.files)) { + // Ensure absolute paths for recent file list to be unique + const QFileInfo fi(fileName); + if (fi.exists() && fi.isRelative()) + fileName = fi.absoluteFilePath(); + m_workbench->readInForm(fileName); + } + + if (m_workbench->formWindowCount() > 0) + suppressNewFormShow = true; + + // Show up error box with parent now if something went wrong + if (m_initializationErrors.isEmpty()) { + if (!suppressNewFormShow) + m_workbench->showNewForm(); + } else { + showErrorMessageBox(m_initializationErrors); + m_initializationErrors.clear(); + } + return result; +} + +bool QDesigner::event(QEvent *ev) +{ + bool eaten; + switch (ev->type()) { + case QEvent::FileOpen: + m_workbench->readInForm(static_cast(ev)->file()); + eaten = true; + break; + case QEvent::Close: { + QCloseEvent *closeEvent = static_cast(ev); + closeEvent->setAccepted(m_workbench->handleClose()); + if (closeEvent->isAccepted()) { + // We're going down, make sure that we don't get our settings saved twice. + if (m_mainWindow) + m_mainWindow->setCloseEventPolicy(MainWindowBase::AcceptCloseEvents); + eaten = QApplication::event(ev); + } + eaten = true; + break; + } + default: + eaten = QApplication::event(ev); + break; + } + return eaten; +} + +void QDesigner::setMainWindow(MainWindowBase *tw) +{ + m_mainWindow = tw; +} + +MainWindowBase *QDesigner::mainWindow() const +{ + return m_mainWindow; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/qdesigner.h b/src/tools/designer/src/designer/qdesigner.h new file mode 100644 index 00000000000..5b763d7a1c8 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner.h @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_H +#define QDESIGNER_H + +#include +#include + +QT_BEGIN_NAMESPACE + +#define qDesigner \ + (static_cast(QCoreApplication::instance())) + +class QDesignerWorkbench; +class QDesignerToolWindow; +class MainWindowBase; +class QDesignerServer; +class QDesignerClient; +class QErrorMessage; +class QCommandLineParser; +struct Options; + +class QDesigner: public QApplication +{ + Q_OBJECT +public: + enum ParseArgumentsResult { + ParseArgumentsSuccess, + ParseArgumentsError, + ParseArgumentsHelpRequested + }; + + QDesigner(int &argc, char **argv); + ~QDesigner() override; + + ParseArgumentsResult parseCommandLineArguments(); + + QDesignerWorkbench *workbench() const; + QDesignerServer *server() const; + MainWindowBase *mainWindow() const; + void setMainWindow(MainWindowBase *tw); + +protected: + bool event(QEvent *ev) override; + +signals: + void initialized(); + +public slots: + void showErrorMessage(const QString &message); + +private: + void showErrorMessageBox(const QString &); + + QDesignerServer *m_server = nullptr; + QDesignerClient *m_client = nullptr; + QDesignerWorkbench *m_workbench = nullptr; + QPointer m_mainWindow; + QPointer m_errorMessageDialog; + + QString m_initializationErrors; + QString m_lastErrorMessage; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_H diff --git a/src/tools/designer/src/designer/qdesigner_actions.cpp b/src/tools/designer/src/designer/qdesigner_actions.cpp new file mode 100644 index 00000000000..07f34709350 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_actions.cpp @@ -0,0 +1,1317 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_actions.h" +#include "designer_enums.h" +#include +#include "qdesigner.h" +#include "qdesigner_workbench.h" +#include "qdesigner_formwindow.h" +#include "mainwindow.h" +#include "newform.h" +#include "versiondialog.h" +#include "saveformastemplate.h" +#include "preferencesdialog.h" +#include "appfontdialog.h" + +#include +#include +#include +#include +#include +#include +#include + +// sdk +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#if defined(QT_PRINTSUPPORT_LIB) // Some platforms may not build QtPrintSupport +# include +# if QT_CONFIG(printer) && QT_CONFIG(printdialog) +# include +# include +# define HAS_PRINTER +# endif +#endif +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +const char *QDesignerActions::defaultToolbarPropertyName = "__qt_defaultToolBarAction"; + +//#ifdef Q_OS_MACOS +# define NONMODAL_PREVIEW +//#endif + +static QAction *createSeparator(QObject *parent) { + QAction * rc = new QAction(parent); + rc->setSeparator(true); + return rc; +} + +static QActionGroup *createActionGroup(QObject *parent, bool exclusive = false) { + QActionGroup * rc = new QActionGroup(parent); + rc->setExclusive(exclusive); + return rc; +} + +static void fixActionContext(const QList &actions) +{ + for (QAction *a : actions) + a->setShortcutContext(Qt::ApplicationShortcut); +} + +static inline QString savedMessage(const QString &fileName) +{ + return QDesignerActions::tr("Saved %1.").arg(fileName); +} + +static QString fileDialogFilters(const QString &extension) +{ + return QDesignerActions::tr("Designer UI files (*.%1);;All Files (*)").arg(extension); +} + +static QString fixResourceFileBackupPath(const QDesignerFormWindowInterface *fwi, + const QDir& backupDir); + +static QByteArray formWindowContents(const QDesignerFormWindowInterface *fw, + std::optional alternativeDir = {}) +{ + QString contents = alternativeDir.has_value() + ? fixResourceFileBackupPath(fw, alternativeDir.value()) : fw->contents(); + if (auto *fwb = qobject_cast(fw)) { + if (fwb->lineTerminatorMode() == qdesigner_internal::FormWindowBase::CRLFLineTerminator) + contents.replace(u'\n', "\r\n"_L1); + } + return contents.toUtf8(); +} + +QFileDialog *createSaveAsDialog(QWidget *parent, const QString &dir, const QString &extension) +{ + auto result = new QFileDialog(parent, QDesignerActions::tr("Save Form As"), + dir, fileDialogFilters(extension)); + result->setAcceptMode(QFileDialog::AcceptSave); + result->setDefaultSuffix(extension); + return result; +} + +QDesignerActions::QDesignerActions(QDesignerWorkbench *workbench) + : QObject(workbench), + m_workbench(workbench), + m_core(workbench->core()), + m_settings(workbench->core()), + m_backupTimer(new QTimer(this)), + m_fileActions(createActionGroup(this)), + m_recentFilesActions(createActionGroup(this)), + m_editActions(createActionGroup(this)), + m_formActions(createActionGroup(this)), + m_settingsActions(createActionGroup(this)), + m_windowActions(createActionGroup(this)), + m_toolActions(createActionGroup(this, true)), + m_editWidgetsAction(new QAction(tr("Edit Widgets"), this)), + m_newFormAction(new QAction(qdesigner_internal::createIconSet(QIcon::ThemeIcon::DocumentNew, + "filenew.png"_L1), + tr("&New..."), this)), + m_openFormAction(new QAction(qdesigner_internal::createIconSet(QIcon::ThemeIcon::DocumentOpen, + "fileopen.png"_L1), + tr("&Open..."), this)), + m_saveFormAction(new QAction(qdesigner_internal::createIconSet(QIcon::ThemeIcon::DocumentSave, + "filesave.png"_L1), + tr("&Save"), this)), + m_saveFormAsAction(new QAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentSaveAs), + tr("Save &As..."), this)), + m_saveAllFormsAction(new QAction(tr("Save A&ll"), this)), + m_saveFormAsTemplateAction(new QAction(tr("Save As &Template..."), this)), + m_closeFormAction(new QAction(QIcon::fromTheme(QIcon::ThemeIcon::WindowClose), + tr("&Close"), this)), + m_savePreviewImageAction(new QAction(tr("Save &Image..."), this)), + m_printPreviewAction(new QAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentPrint), + tr("&Print..."), this)), + m_quitAction(new QAction(QIcon::fromTheme(QIcon::ThemeIcon::ApplicationExit), + tr("&Quit"), this)), + m_viewCppCodeAction(new QAction(tr("View &C++ Code..."), this)), + m_viewPythonCodeAction(new QAction(tr("View &Python Code..."), this)), + m_minimizeAction(new QAction(tr("&Minimize"), this)), + m_bringAllToFrontSeparator(createSeparator(this)), + m_bringAllToFrontAction(new QAction(tr("Bring All to Front"), this)), + m_windowListSeparatorAction(createSeparator(this)), + m_preferencesAction(new QAction(tr("Preferences..."), this)), + m_appFontAction(new QAction(tr("Additional Fonts..."), this)) +{ + Q_ASSERT(m_core != nullptr); + qdesigner_internal::QDesignerFormWindowManager *ifwm = qobject_cast(m_core->formWindowManager()); + Q_ASSERT(ifwm); + m_previewManager = ifwm->previewManager(); + m_previewFormAction = ifwm->action(QDesignerFormWindowManagerInterface::DefaultPreviewAction); + m_styleActions = ifwm->actionGroup(QDesignerFormWindowManagerInterface::StyledPreviewActionGroup); + connect(ifwm, &QDesignerFormWindowManagerInterface::formWindowSettingsChanged, + this, &QDesignerActions::formWindowSettingsChanged); + + m_editWidgetsAction->setObjectName(u"__qt_edit_widgets_action"_s); + m_newFormAction->setObjectName(u"__qt_new_form_action"_s); + m_openFormAction->setObjectName(u"__qt_open_form_action"_s); + m_saveFormAction->setObjectName(u"__qt_save_form_action"_s); + m_saveFormAsAction->setObjectName(u"__qt_save_form_as_action"_s); + m_saveAllFormsAction->setObjectName(u"__qt_save_all_forms_action"_s); + m_saveFormAsTemplateAction->setObjectName(u"__qt_save_form_as_template_action"_s); + m_closeFormAction->setObjectName(u"__qt_close_form_action"_s); + m_quitAction->setObjectName(u"__qt_quit_action"_s); + m_previewFormAction->setObjectName(u"__qt_preview_form_action"_s); + m_viewCppCodeAction->setObjectName(u"__qt_preview_cpp_code_action"_s); + m_viewPythonCodeAction->setObjectName(u"__qt_preview_python_code_action"_s); + m_minimizeAction->setObjectName(u"__qt_minimize_action"_s); + m_bringAllToFrontAction->setObjectName(u"__qt_bring_all_to_front_action"_s); + m_preferencesAction->setObjectName(u"__qt_preferences_action"_s); + + m_helpActions = createHelpActions(); + + m_newFormAction->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + m_openFormAction->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + m_saveFormAction->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + + QDesignerFormWindowManagerInterface *formWindowManager = m_core->formWindowManager(); + Q_ASSERT(formWindowManager != nullptr); + +// +// file actions +// + m_newFormAction->setShortcut(QKeySequence::New); + connect(m_newFormAction, &QAction::triggered, this, &QDesignerActions::createForm); + m_fileActions->addAction(m_newFormAction); + + m_openFormAction->setShortcut(QKeySequence::Open); + connect(m_openFormAction, &QAction::triggered, this, &QDesignerActions::slotOpenForm); + m_fileActions->addAction(m_openFormAction); + + m_fileActions->addAction(createRecentFilesMenu()); + m_fileActions->addAction(createSeparator(this)); + + m_saveFormAction->setShortcut(QKeySequence::Save); + connect(m_saveFormAction, &QAction::triggered, this, + QOverload<>::of(&QDesignerActions::saveForm)); + m_fileActions->addAction(m_saveFormAction); + + connect(m_saveFormAsAction, &QAction::triggered, this, + QOverload<>::of(&QDesignerActions::saveFormAs)); + m_fileActions->addAction(m_saveFormAsAction); + +#ifdef Q_OS_MACOS + m_saveAllFormsAction->setShortcut(tr("ALT+CTRL+S")); +#else + m_saveAllFormsAction->setShortcut(tr("CTRL+SHIFT+S")); // Commonly "Save As" on Mac +#endif + connect(m_saveAllFormsAction, &QAction::triggered, this, &QDesignerActions::saveAllForms); + m_fileActions->addAction(m_saveAllFormsAction); + + connect(m_saveFormAsTemplateAction, &QAction::triggered, this, &QDesignerActions::saveFormAsTemplate); + m_fileActions->addAction(m_saveFormAsTemplateAction); + + m_fileActions->addAction(createSeparator(this)); + + m_printPreviewAction->setShortcut(QKeySequence::Print); + connect(m_printPreviewAction, &QAction::triggered, this, &QDesignerActions::printPreviewImage); + m_fileActions->addAction(m_printPreviewAction); + m_printPreviewAction->setObjectName(u"__qt_print_action"_s); + + connect(m_savePreviewImageAction, &QAction::triggered, this, &QDesignerActions::savePreviewImage); + m_savePreviewImageAction->setObjectName(u"__qt_saveimage_action"_s); + m_fileActions->addAction(m_savePreviewImageAction); + m_fileActions->addAction(createSeparator(this)); + + m_closeFormAction->setShortcut(QKeySequence::Close); + connect(m_closeFormAction, &QAction::triggered, this, &QDesignerActions::closeForm); + m_fileActions->addAction(m_closeFormAction); + updateCloseAction(); + + m_fileActions->addAction(createSeparator(this)); + + m_quitAction->setShortcuts(QKeySequence::Quit); + m_quitAction->setMenuRole(QAction::QuitRole); + connect(m_quitAction, &QAction::triggered, this, &QDesignerActions::shutdown); + m_fileActions->addAction(m_quitAction); + +// +// edit actions +// + QAction *undoAction = formWindowManager->action(QDesignerFormWindowManagerInterface::UndoAction); + undoAction->setObjectName(u"__qt_undo_action"_s); + undoAction->setShortcut(QKeySequence::Undo); + m_editActions->addAction(undoAction); + + QAction *redoAction = formWindowManager->action(QDesignerFormWindowManagerInterface::RedoAction); + redoAction->setObjectName(u"__qt_redo_action"_s); + redoAction->setShortcut(QKeySequence::Redo); + m_editActions->addAction(redoAction); + + m_editActions->addAction(createSeparator(this)); + +#if QT_CONFIG(clipboard) + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::CutAction)); + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::CopyAction)); + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::PasteAction)); +#endif + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::DeleteAction)); + + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::SelectAllAction)); + + m_editActions->addAction(createSeparator(this)); + + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::LowerAction)); + m_editActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::RaiseAction)); + + formWindowManager->action(QDesignerFormWindowManagerInterface::LowerAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::RaiseAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + +// +// edit mode actions +// + + m_editWidgetsAction->setCheckable(true); + QList shortcuts; + shortcuts.append(QKeySequence(Qt::Key_F3)); + shortcuts.append(QKeySequence(Qt::Key_Escape)); + m_editWidgetsAction->setShortcuts(shortcuts); + QIcon fallback(m_core->resourceLocation() + "/widgettool.png"_L1); + m_editWidgetsAction->setIcon(QIcon::fromTheme(u"designer-edit-widget"_s, + fallback)); + connect(m_editWidgetsAction, &QAction::triggered, this, &QDesignerActions::editWidgetsSlot); + m_editWidgetsAction->setChecked(true); + m_editWidgetsAction->setEnabled(false); + m_editWidgetsAction->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + m_toolActions->addAction(m_editWidgetsAction); + + connect(formWindowManager, &qdesigner_internal::QDesignerFormWindowManager::activeFormWindowChanged, + this, &QDesignerActions::activeFormWindowChanged); + + const QObjectList builtinPlugins = QPluginLoader::staticInstances() + + m_core->pluginManager()->instances(); + for (QObject *plugin : builtinPlugins) { + if (QDesignerFormEditorPluginInterface *formEditorPlugin = qobject_cast(plugin)) { + if (QAction *action = formEditorPlugin->action()) { + m_toolActions->addAction(action); + action->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + action->setCheckable(true); + } + } + } + + connect(m_preferencesAction, &QAction::triggered, this, &QDesignerActions::showPreferencesDialog); + m_preferencesAction->setMenuRole(QAction::PreferencesRole); + m_settingsActions->addAction(m_preferencesAction); + + connect(m_appFontAction, &QAction::triggered, this, &QDesignerActions::showAppFontDialog); + m_settingsActions->addAction(m_appFontAction); +// +// form actions +// + + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::HorizontalLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::VerticalLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::SplitHorizontalAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::SplitVerticalAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::GridLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::FormLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::BreakLayoutAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::AdjustSizeAction)); + m_formActions->addAction(formWindowManager->action(QDesignerFormWindowManagerInterface::SimplifyLayoutAction)); + m_formActions->addAction(createSeparator(this)); + + formWindowManager->action(QDesignerFormWindowManagerInterface::HorizontalLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::VerticalLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::SplitHorizontalAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::SplitVerticalAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::GridLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::FormLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::BreakLayoutAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + formWindowManager->action(QDesignerFormWindowManagerInterface::AdjustSizeAction)->setProperty(QDesignerActions::defaultToolbarPropertyName, true); + + m_previewFormAction->setShortcut(tr("CTRL+R")); + m_formActions->addAction(m_previewFormAction); + connect(m_previewManager, &qdesigner_internal::PreviewManager::firstPreviewOpened, + this, &QDesignerActions::updateCloseAction); + connect(m_previewManager, &qdesigner_internal::PreviewManager::lastPreviewClosed, + this, &QDesignerActions::updateCloseAction); + + connect(m_viewCppCodeAction, &QAction::triggered, this, + [this] () { this->viewCode(qdesigner_internal::UicLanguage::Cpp); }); + connect(m_viewPythonCodeAction, &QAction::triggered, this, + [this] () { this->viewCode(qdesigner_internal::UicLanguage::Python); }); + + // Preview code only in Cpp/Python (uic) + if (qt_extension(m_core->extensionManager(), m_core) == nullptr) { + m_formActions->addAction(m_viewCppCodeAction); + m_formActions->addAction(m_viewPythonCodeAction); + } + + m_formActions->addAction(createSeparator(this)); + + m_formActions->addAction(ifwm->action(QDesignerFormWindowManagerInterface::FormWindowSettingsDialogAction)); +// +// window actions +// + m_minimizeAction->setEnabled(false); + m_minimizeAction->setCheckable(true); + m_minimizeAction->setShortcut(tr("CTRL+M")); + connect(m_minimizeAction, &QAction::triggered, m_workbench, &QDesignerWorkbench::toggleFormMinimizationState); + m_windowActions->addAction(m_minimizeAction); + + m_windowActions->addAction(m_bringAllToFrontSeparator); + connect(m_bringAllToFrontAction, &QAction::triggered, m_workbench, &QDesignerWorkbench::bringAllToFront); + m_windowActions->addAction(m_bringAllToFrontAction); + m_windowActions->addAction(m_windowListSeparatorAction); + + setWindowListSeparatorVisible(false); + +// +// connections +// + fixActionContext(m_fileActions->actions()); + fixActionContext(m_editActions->actions()); + fixActionContext(m_toolActions->actions()); + fixActionContext(m_formActions->actions()); + fixActionContext(m_windowActions->actions()); + fixActionContext(m_helpActions->actions()); + + activeFormWindowChanged(core()->formWindowManager()->activeFormWindow()); + + m_backupTimer->start(180000); // 3min + connect(m_backupTimer, &QTimer::timeout, this, &QDesignerActions::backupForms); + + // Enable application font action + connect(formWindowManager, &QDesignerFormWindowManagerInterface::formWindowAdded, + this, &QDesignerActions::formWindowCountChanged); + connect(formWindowManager, &QDesignerFormWindowManagerInterface::formWindowRemoved, + this, &QDesignerActions::formWindowCountChanged); + formWindowCountChanged(); +} + +QActionGroup *QDesignerActions::createHelpActions() +{ + QActionGroup *helpActions = createActionGroup(this); + +#ifndef QT_JAMBI_BUILD + QAction *mainHelpAction = new QAction(tr("Qt Widgets Designer &Help"), this); + mainHelpAction->setObjectName(u"__qt_designer_help_action"_s); + connect(mainHelpAction, &QAction::triggered, this, &QDesignerActions::showDesignerHelp); + mainHelpAction->setShortcut(Qt::CTRL | Qt::Key_Question); + helpActions->addAction(mainHelpAction); + + helpActions->addAction(createSeparator(this)); + QAction *widgetHelp = new QAction(tr("Current Widget Help"), this); + widgetHelp->setObjectName(u"__qt_current_widget_help_action"_s); + widgetHelp->setShortcut(Qt::Key_F1); + connect(widgetHelp, &QAction::triggered, this, &QDesignerActions::showWidgetSpecificHelp); + helpActions->addAction(widgetHelp); + +#endif + + helpActions->addAction(createSeparator(this)); + QAction *aboutPluginsAction = new QAction(tr("About Plugins"), this); + aboutPluginsAction->setObjectName(u"__qt_about_plugins_action"_s); + aboutPluginsAction->setMenuRole(QAction::ApplicationSpecificRole); + connect(aboutPluginsAction, &QAction::triggered, + m_core->formWindowManager(), &QDesignerFormWindowManagerInterface::showPluginDialog); + helpActions->addAction(aboutPluginsAction); + + QAction *aboutDesignerAction = new QAction(tr("About Qt Widgets Designer"), this); + aboutDesignerAction->setMenuRole(QAction::AboutRole); + aboutDesignerAction->setObjectName(u"__qt_about_designer_action"_s); + connect(aboutDesignerAction, &QAction::triggered, this, &QDesignerActions::aboutDesigner); + helpActions->addAction(aboutDesignerAction); + + QAction *aboutQtAction = new QAction(tr("About Qt"), this); + aboutQtAction->setMenuRole(QAction::AboutQtRole); + aboutQtAction->setObjectName(u"__qt_about_qt_action"_s); + connect(aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt); + helpActions->addAction(aboutQtAction); + return helpActions; +} + +QDesignerActions::~QDesignerActions() +{ +#ifdef HAS_PRINTER + delete m_printer; +#endif +} + +QString QDesignerActions::uiExtension() const +{ + QDesignerLanguageExtension *lang + = qt_extension(m_core->extensionManager(), m_core); + if (lang) + return lang->uiExtension(); + return u"ui"_s; +} + +QAction *QDesignerActions::createRecentFilesMenu() +{ + m_recentMenu.reset(new QMenu); + QAction *act; + // Need to insert this into the QAction. + for (int i = 0; i < MaxRecentFiles; ++i) { + act = new QAction(this); + act->setVisible(false); + connect(act, &QAction::triggered, this, &QDesignerActions::openRecentForm); + m_recentFilesActions->addAction(act); + m_recentMenu->addAction(act); + } + updateRecentFileActions(); + m_recentMenu->addSeparator(); + act = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::EditClear), + tr("Clear &Menu"), this); + act->setObjectName(u"__qt_action_clear_menu_"_s); + connect(act, &QAction::triggered, this, &QDesignerActions::clearRecentFiles); + m_recentFilesActions->addAction(act); + m_recentMenu->addAction(act); + + act = new QAction(QIcon::fromTheme(QIcon::ThemeIcon::DocumentOpenRecent), + tr("&Recent Forms"), this); + act->setMenu(m_recentMenu.get()); + return act; +} + +QActionGroup *QDesignerActions::toolActions() const +{ return m_toolActions; } + +QDesignerWorkbench *QDesignerActions::workbench() const +{ return m_workbench; } + +QDesignerFormEditorInterface *QDesignerActions::core() const +{ return m_core; } + +QActionGroup *QDesignerActions::fileActions() const +{ return m_fileActions; } + +QActionGroup *QDesignerActions::editActions() const +{ return m_editActions; } + +QActionGroup *QDesignerActions::formActions() const +{ return m_formActions; } + +QActionGroup *QDesignerActions::settingsActions() const +{ return m_settingsActions; } + +QActionGroup *QDesignerActions::windowActions() const +{ return m_windowActions; } + +QActionGroup *QDesignerActions::helpActions() const +{ return m_helpActions; } + +QActionGroup *QDesignerActions::styleActions() const +{ return m_styleActions; } + +QAction *QDesignerActions::previewFormAction() const +{ return m_previewFormAction; } + +QAction *QDesignerActions::viewCodeAction() const +{ return m_viewCppCodeAction; } + + +void QDesignerActions::editWidgetsSlot() +{ + QDesignerFormWindowManagerInterface *formWindowManager = core()->formWindowManager(); + for (int i=0; iformWindowCount(); ++i) { + QDesignerFormWindowInterface *formWindow = formWindowManager->formWindow(i); + formWindow->editWidgets(); + } +} + +void QDesignerActions::createForm() +{ + showNewFormDialog(QString()); +} + +void QDesignerActions::showNewFormDialog(const QString &fileName) +{ + closePreview(); + NewForm *dlg = new NewForm(workbench(), workbench()->core()->topLevel(), fileName); + + dlg->setAttribute(Qt::WA_DeleteOnClose); + dlg->setAttribute(Qt::WA_ShowModal); + + dlg->setGeometry(fixDialogRect(dlg->rect())); + dlg->exec(); +} + +void QDesignerActions::slotOpenForm() +{ + openForm(core()->topLevel()); +} + +bool QDesignerActions::openForm(QWidget *parent) +{ + closePreview(); + const QString extension = uiExtension(); + const QStringList fileNames = QFileDialog::getOpenFileNames(parent, tr("Open Form"), + m_openDirectory, fileDialogFilters(extension), nullptr); + + if (fileNames.isEmpty()) + return false; + + bool atLeastOne = false; + for (const QString &fileName : fileNames) { + if (readInForm(fileName) && !atLeastOne) + atLeastOne = true; + } + + return atLeastOne; +} + +bool QDesignerActions::saveFormAs(QDesignerFormWindowInterface *fw) +{ + const QString extension = uiExtension(); + + QString dir = fw->fileName(); + if (dir.isEmpty()) { + do { + // Build untitled name + if (!m_saveDirectory.isEmpty()) { + dir = m_saveDirectory; + break; + } + if (!m_openDirectory.isEmpty()) { + dir = m_openDirectory; + break; + } + dir = QDir::current().absolutePath(); + } while (false); + dir += QDir::separator(); + dir += "untitled."_L1; + dir += extension; + } + + QScopedPointer saveAsDialog(createSaveAsDialog(fw, dir, extension)); + if (saveAsDialog->exec() != QDialog::Accepted) + return false; + + const QString saveFile = saveAsDialog->selectedFiles().constFirst(); + saveAsDialog.reset(); // writeOutForm potentially shows other dialogs + + fw->setFileName(saveFile); + return writeOutForm(fw, saveFile); +} + +void QDesignerActions::saveForm() +{ + if (QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow()) { + if (saveForm(fw)) + showStatusBarMessage(savedMessage(QFileInfo(fw->fileName()).fileName())); + } +} + +void QDesignerActions::saveAllForms() +{ + QString fileNames; + QDesignerFormWindowManagerInterface *formWindowManager = core()->formWindowManager(); + if (const int totalWindows = formWindowManager->formWindowCount()) { + const auto separator = ", "_L1; + for (int i = 0; i < totalWindows; ++i) { + QDesignerFormWindowInterface *fw = formWindowManager->formWindow(i); + if (fw && fw->isDirty()) { + formWindowManager->setActiveFormWindow(fw); + if (saveForm(fw)) { + if (!fileNames.isEmpty()) + fileNames += separator; + fileNames += QFileInfo(fw->fileName()).fileName(); + } else { + break; + } + } + } + } + + if (!fileNames.isEmpty()) { + showStatusBarMessage(savedMessage(fileNames)); + } +} + +bool QDesignerActions::saveForm(QDesignerFormWindowInterface *fw) +{ + bool ret; + if (fw->fileName().isEmpty()) + ret = saveFormAs(fw); + else + ret = writeOutForm(fw, fw->fileName()); + return ret; +} + +void QDesignerActions::closeForm() +{ + if (m_previewManager->previewCount()) { + closePreview(); + return; + } + + if (QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow()) + if (QWidget *parent = fw->parentWidget()) { + if (QMdiSubWindow *mdiSubWindow = qobject_cast(parent->parentWidget())) { + mdiSubWindow->close(); + } else { + parent->close(); + } + } +} + +void QDesignerActions::saveFormAs() +{ + if (QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow()) { + if (saveFormAs(fw)) + showStatusBarMessage(savedMessage(fw->fileName())); + } +} + +void QDesignerActions::saveFormAsTemplate() +{ + if (QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow()) { + SaveFormAsTemplate dlg(core(), fw, fw->window()); + dlg.exec(); + } +} + +void QDesignerActions::notImplementedYet() +{ + QMessageBox::information(core()->topLevel(), tr("Designer"), tr("Feature not implemented yet!")); +} + +void QDesignerActions::closePreview() +{ + m_previewManager->closeAllPreviews(); +} + +void QDesignerActions::viewCode(qdesigner_internal::UicLanguage language) +{ + QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow(); + if (!fw) + return; + QString errorMessage; + if (!qdesigner_internal::CodeDialog::showCodeDialog(fw, language, fw, &errorMessage)) + QMessageBox::warning(fw, tr("Code generation failed"), errorMessage); +} + +bool QDesignerActions::readInForm(const QString &fileName) +{ + QString fn = fileName; + + // First make sure that we don't have this one open already. + QDesignerFormWindowManagerInterface *formWindowManager = core()->formWindowManager(); + const int totalWindows = formWindowManager->formWindowCount(); + for (int i = 0; i < totalWindows; ++i) { + QDesignerFormWindowInterface *w = formWindowManager->formWindow(i); + if (w->fileName() == fn) { + w->raise(); + formWindowManager->setActiveFormWindow(w); + addRecentFile(fn); + return true; + } + } + + // Otherwise load it. + do { + QString errorMessage; + if (workbench()->openForm(fn, &errorMessage)) { + addRecentFile(fn); + m_openDirectory = QFileInfo(fn).absolutePath(); + return true; + } else { + // prompt to reload + QMessageBox box(QMessageBox::Warning, tr("Read error"), + tr("%1\nDo you want to update the file location or generate a new form?").arg(errorMessage), + QMessageBox::Cancel, core()->topLevel()); + + QPushButton *updateButton = box.addButton(tr("&Update"), QMessageBox::ActionRole); + QPushButton *newButton = box.addButton(tr("&New Form"), QMessageBox::ActionRole); + box.exec(); + if (box.clickedButton() == box.button(QMessageBox::Cancel)) + return false; + + if (box.clickedButton() == updateButton) { + const QString extension = uiExtension(); + fn = QFileDialog::getOpenFileName(core()->topLevel(), + tr("Open Form"), m_openDirectory, + fileDialogFilters(extension), nullptr); + + if (fn.isEmpty()) + return false; + } else if (box.clickedButton() == newButton) { + // If the file does not exist, but its directory, is valid, open the template with the editor file name set to it. + // (called from command line). + QString newFormFileName; + const QFileInfo fInfo(fn); + if (!fInfo.exists()) { + // Normalize file name + const QString directory = fInfo.absolutePath(); + if (QDir(directory).exists()) + newFormFileName = directory + u'/' + fInfo.fileName(); + } + showNewFormDialog(newFormFileName); + return false; + } + } + } while (true); + return true; +} + +bool QDesignerActions::writeOutForm(QDesignerFormWindowInterface *fw, const QString &saveFile, bool check) +{ + Q_ASSERT(fw && !saveFile.isEmpty()); + + if (check) { + const QStringList problems = fw->checkContents(); + if (!problems.isEmpty()) + QMessageBox::information(fw->window(), tr("Qt Widgets Designer"), problems.join("
"_L1)); + } + + m_workbench->updateBackup(fw); + + QSaveFile f(saveFile); + while (!f.open(QFile::WriteOnly)) { + QMessageBox box(QMessageBox::Warning, + tr("Save Form?"), + tr("Could not open file"), + QMessageBox::NoButton, fw); + + box.setWindowModality(Qt::WindowModal); + box.setInformativeText(tr("The file %1 could not be opened." + "\nReason: %2" + "\nWould you like to retry or select a different file?") + .arg(f.fileName(), f.errorString())); + QPushButton *retryButton = box.addButton(QMessageBox::Retry); + retryButton->setDefault(true); + QPushButton *switchButton = box.addButton(tr("Select New File"), QMessageBox::AcceptRole); + QPushButton *cancelButton = box.addButton(QMessageBox::Cancel); + box.exec(); + + if (box.clickedButton() == cancelButton) + return false; + if (box.clickedButton() == switchButton) { + QScopedPointer saveAsDialog(createSaveAsDialog(fw, QDir::currentPath(), uiExtension())); + if (saveAsDialog->exec() != QDialog::Accepted) + return false; + + const QString fileName = saveAsDialog->selectedFiles().constFirst(); + f.setFileName(fileName); + fw->setFileName(fileName); + } + // loop back around... + } + f.write(formWindowContents(fw)); + if (!f.commit()) { + QMessageBox box(QMessageBox::Warning, tr("Save Form"), + tr("Could not write file"), + QMessageBox::Cancel, fw); + box.setWindowModality(Qt::WindowModal); + box.setInformativeText(tr("It was not possible to write the file %1 to disk." + "\nReason: %2") + .arg(f.fileName(), f.errorString())); + box.exec(); + return false; + } + addRecentFile(saveFile); + m_saveDirectory = QFileInfo(f.fileName()).absolutePath(); + + fw->setDirty(false); + fw->parentWidget()->setWindowModified(false); + return true; +} + +void QDesignerActions::shutdown() +{ + // Follow the idea from the Mac, i.e. send the Application a close event + // and if it's accepted, quit. + QCloseEvent ev; + QApplication::sendEvent(qDesigner, &ev); + if (ev.isAccepted()) + qDesigner->quit(); +} + +void QDesignerActions::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) +{ + const bool enable = formWindow != nullptr; + m_saveFormAction->setEnabled(enable); + m_saveFormAsAction->setEnabled(enable); + m_saveAllFormsAction->setEnabled(enable); + m_saveFormAsTemplateAction->setEnabled(enable); + m_closeFormAction->setEnabled(enable); + m_savePreviewImageAction->setEnabled(enable); + m_printPreviewAction->setEnabled(enable); + + m_editWidgetsAction->setEnabled(enable); + + m_previewFormAction->setEnabled(enable); + m_viewCppCodeAction->setEnabled(enable); + m_viewPythonCodeAction->setEnabled(enable); + m_styleActions->setEnabled(enable); +} + +void QDesignerActions::formWindowSettingsChanged(QDesignerFormWindowInterface *fw) +{ + if (QDesignerFormWindow *window = m_workbench->findFormWindow(fw)) + window->updateChanged(); +} + +void QDesignerActions::updateRecentFileActions() +{ + QStringList files = m_settings.recentFilesList(); + auto existingEnd = std::remove_if(files.begin(), files.end(), + [] (const QString &f) { return !QFileInfo::exists(f); }); + if (existingEnd != files.end()) { + files.erase(existingEnd, files.end()); + m_settings.setRecentFilesList(files); + } + + const auto recentFilesActs = m_recentFilesActions->actions(); + qsizetype i = 0; + for (QAction *action : recentFilesActs) { + if (i < files.size()) { + const QString &file = files.at(i); + action->setText(QFileInfo(file).fileName()); + action->setIconText(file); + action->setVisible(true); + } else { + action->setVisible(false); + } + ++i; + } +} + +void QDesignerActions::openRecentForm() +{ + if (const QAction *action = qobject_cast(sender())) { + if (!readInForm(action->iconText())) + updateRecentFileActions(); // File doesn't exist, remove it from settings + } +} + +void QDesignerActions::clearRecentFiles() +{ + m_settings.setRecentFilesList(QStringList()); + updateRecentFileActions(); +} + +QActionGroup *QDesignerActions::recentFilesActions() const +{ + return m_recentFilesActions; +} + +void QDesignerActions::addRecentFile(const QString &fileName) +{ + QStringList files = m_settings.recentFilesList(); + files.removeAll(fileName); + files.prepend(fileName); + while (files.size() > MaxRecentFiles) + files.removeLast(); + + m_settings.setRecentFilesList(files); + updateRecentFileActions(); +} + +QAction *QDesignerActions::openFormAction() const +{ + return m_openFormAction; +} + +QAction *QDesignerActions::closeFormAction() const +{ + return m_closeFormAction; +} + +QAction *QDesignerActions::minimizeAction() const +{ + return m_minimizeAction; +} + +void QDesignerActions::showDesignerHelp() +{ + QString url = AssistantClient::designerManualUrl(); + url += "qtdesigner-manual.html"_L1; + showHelp(url); +} + +void QDesignerActions::helpRequested(const QString &manual, const QString &document) +{ + QString url = AssistantClient::documentUrl(manual); + url += document; + showHelp(url); +} + +void QDesignerActions::showHelp(const QString &url) +{ + QString errorMessage; + if (!m_assistantClient.showPage(url, &errorMessage)) + QMessageBox::warning(core()->topLevel(), tr("Assistant"), errorMessage); +} + +void QDesignerActions::aboutDesigner() +{ + VersionDialog mb(core()->topLevel()); + mb.setWindowTitle(tr("About Qt Widgets Designer")); + if (mb.exec()) { + QMessageBox messageBox(QMessageBox::Information, u"Easter Egg"_s, + u"Easter Egg"_s, QMessageBox::Ok, core()->topLevel()); + messageBox.setInformativeText(u"The Easter Egg has been removed."_s); + messageBox.exec(); + } +} + +QAction *QDesignerActions::editWidgets() const +{ + return m_editWidgetsAction; +} + +void QDesignerActions::showWidgetSpecificHelp() +{ + const QString helpId = core()->integration()->contextHelpId(); + + if (helpId.isEmpty()) { + showDesignerHelp(); + return; + } + + QString errorMessage; + const bool rc = m_assistantClient.activateIdentifier(helpId, &errorMessage); + if (!rc) + QMessageBox::warning(core()->topLevel(), tr("Assistant"), errorMessage); +} + +void QDesignerActions::updateCloseAction() +{ + if (m_previewManager->previewCount()) { + m_closeFormAction->setText(tr("&Close Preview")); + } else { + m_closeFormAction->setText(tr("&Close")); + } +} + +void QDesignerActions::backupForms() +{ + const int count = m_workbench->formWindowCount(); + if (!count || !ensureBackupDirectories()) + return; + + + QMap backupMap; + QDir backupDir(m_backupPath); + for (int i = 0; i < count; ++i) { + QDesignerFormWindow *fw = m_workbench->formWindow(i); + QDesignerFormWindowInterface *fwi = fw->editor(); + + QString formBackupName = m_backupPath + "/backup"_L1 + QString::number(i) + ".bak"_L1; + + QString fwn = QDir::toNativeSeparators(fwi->fileName()); + if (fwn.isEmpty()) + fwn = fw->windowTitle(); + + backupMap.insert(fwn, formBackupName); + + bool ok = false; + QSaveFile file(formBackupName); + if (file.open(QFile::WriteOnly)) { + file.write(formWindowContents(fw->editor(), backupDir)); + ok = file.commit(); + } + if (!ok) { + backupMap.remove(fwn); + qdesigner_internal::designerWarning(tr("The backup file %1 could not be written: %2"). + arg(QDir::toNativeSeparators(file.fileName()), + file.errorString())); + } + } + + if (!backupMap.isEmpty()) + m_settings.setBackup(backupMap); +} + +static QString fixResourceFileBackupPath(const QDesignerFormWindowInterface *fwi, + const QDir& backupDir) +{ + const QString content = fwi->contents(); + QDomDocument domDoc(u"backup"_s); + if(!domDoc.setContent(content)) + return content; + + const QDomNodeList list = domDoc.elementsByTagName(u"resources"_s); + if (list.isEmpty()) + return content; + + for (int i = 0; i < list.count(); i++) { + const QDomNode node = list.at(i); + if (!node.isNull()) { + const QDomElement element = node.toElement(); + if (!element.isNull() && element.tagName() == "resources"_L1) { + QDomNode childNode = element.firstChild(); + while (!childNode.isNull()) { + QDomElement childElement = childNode.toElement(); + if (!childElement.isNull() && childElement.tagName() == "include"_L1) { + const QString attr = childElement.attribute(u"location"_s); + const QString path = fwi->absoluteDir().absoluteFilePath(attr); + childElement.setAttribute(u"location"_s, backupDir.relativeFilePath(path)); + } + childNode = childNode.nextSibling(); + } + } + } + } + + + return domDoc.toString(); +} + +QRect QDesignerActions::fixDialogRect(const QRect &rect) const +{ + QRect frameGeometry; + const QRect availableGeometry = core()->topLevel()->screen()->geometry(); + + if (workbench()->mode() == DockedMode) { + frameGeometry = core()->topLevel()->frameGeometry(); + } else + frameGeometry = availableGeometry; + + QRect dlgRect = rect; + dlgRect.moveCenter(frameGeometry.center()); + + // make sure that parts of the dialog are not outside of screen + dlgRect.moveBottom(qMin(dlgRect.bottom(), availableGeometry.bottom())); + dlgRect.moveRight(qMin(dlgRect.right(), availableGeometry.right())); + dlgRect.moveLeft(qMax(dlgRect.left(), availableGeometry.left())); + dlgRect.moveTop(qMax(dlgRect.top(), availableGeometry.top())); + + return dlgRect; +} + +void QDesignerActions::showStatusBarMessage(const QString &message) const +{ + if (workbench()->mode() == DockedMode) { + QStatusBar *bar = qDesigner->mainWindow()->statusBar(); + if (bar && !bar->isHidden()) + bar->showMessage(message, 3000); + } +} + +void QDesignerActions::setBringAllToFrontVisible(bool visible) +{ + m_bringAllToFrontSeparator->setVisible(visible); + m_bringAllToFrontAction->setVisible(visible); +} + +void QDesignerActions::setWindowListSeparatorVisible(bool visible) +{ + m_windowListSeparatorAction->setVisible(visible); +} + +bool QDesignerActions::ensureBackupDirectories() { + + if (m_backupPath.isEmpty()) // create names + m_backupPath = qdesigner_internal::dataDirectory() + u"/backup"_s; + + // ensure directories + const QDir backupDir(m_backupPath); + + if (!backupDir.exists()) { + if (!backupDir.mkpath(m_backupPath)) { + qdesigner_internal::designerWarning(tr("The backup directory %1 could not be created.") + .arg(QDir::toNativeSeparators(m_backupPath))); + return false; + } + } + return true; +} + +void QDesignerActions::showPreferencesDialog() +{ + { + PreferencesDialog preferencesDialog(workbench()->core(), m_core->topLevel()); + preferencesDialog.exec(); + } // Make sure the preference dialog is destroyed before switching UI modes. + m_workbench->applyUiSettings(); +} + +void QDesignerActions::showAppFontDialog() +{ + if (!m_appFontDialog) // Might get deleted when switching ui modes + m_appFontDialog = new AppFontDialog(core()->topLevel()); + m_appFontDialog->show(); + m_appFontDialog->raise(); +} + +QPixmap QDesignerActions::createPreviewPixmap(QDesignerFormWindowInterface *fw) +{ + const QCursor oldCursor = core()->topLevel()->cursor(); + core()->topLevel()->setCursor(Qt::WaitCursor); + + QString errorMessage; + const QPixmap pixmap = m_previewManager->createPreviewPixmap(fw, QString(), &errorMessage); + core()->topLevel()->setCursor(oldCursor); + if (pixmap.isNull()) { + QMessageBox::warning(fw, tr("Preview failed"), errorMessage); + } + return pixmap; +} + +qdesigner_internal::PreviewConfiguration QDesignerActions::previewConfiguration() +{ + qdesigner_internal::PreviewConfiguration pc; + qdesigner_internal::QDesignerSharedSettings settings(core()); + if (settings.isCustomPreviewConfigurationEnabled()) + pc = settings.customPreviewConfiguration(); + return pc; +} + +void QDesignerActions::savePreviewImage() +{ + const char *format = "png"; + + QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow(); + if (!fw) + return; + + QImage image; + const QString extension = QString::fromLatin1(format); + const QString filter = tr("Image files (*.%1)").arg(extension); + + QString suggestion = fw->fileName(); + if (!suggestion.isEmpty()) + suggestion = QFileInfo(suggestion).baseName() + u'.' + extension; + + QFileDialog dialog(fw, tr("Save Image"), suggestion, filter); + dialog.setAcceptMode(QFileDialog::AcceptSave); + dialog.setDefaultSuffix(extension); + + do { + if (dialog.exec() != QDialog::Accepted) + break; + const QString fileName = dialog.selectedFiles().constFirst(); + + if (image.isNull()) { + const QPixmap pixmap = createPreviewPixmap(fw); + if (pixmap.isNull()) + break; + + image = pixmap.toImage(); + } + + if (image.save(fileName, format)) { + showStatusBarMessage(tr("Saved image %1.").arg(QFileInfo(fileName).fileName())); + break; + } + + QMessageBox box(QMessageBox::Warning, tr("Save Image"), + tr("The file %1 could not be written.").arg( fileName), + QMessageBox::Retry|QMessageBox::Cancel, fw); + if (box.exec() == QMessageBox::Cancel) + break; + } while (true); +} + +void QDesignerActions::formWindowCountChanged() +{ + const bool enabled = m_core->formWindowManager()->formWindowCount() == 0; + /* Disable the application font action if there are form windows open + * as the reordering of the fonts sets font properties to 'changed' + * and overloaded fonts are not updated. */ + static const QString disabledTip = tr("Please close all forms to enable the loading of additional fonts."); + m_appFontAction->setEnabled(enabled); + m_appFontAction->setStatusTip(enabled ? QString() : disabledTip); +} + +void QDesignerActions::printPreviewImage() +{ +#ifdef HAS_PRINTER + QDesignerFormWindowInterface *fw = core()->formWindowManager()->activeFormWindow(); + if (!fw) + return; + + if (!m_printer) + m_printer = new QPrinter(QPrinter::HighResolution); + + m_printer->setFullPage(false); + + // Grab the image to be able to a suggest suitable orientation + const QPixmap pixmap = createPreviewPixmap(fw); + if (pixmap.isNull()) + return; + + const QSizeF pixmapSize = pixmap.size(); + + m_printer->setPageOrientation(pixmapSize.width() > pixmapSize.height() ? + QPageLayout::Landscape : QPageLayout::Portrait); + + // Printer parameters + QPrintDialog dialog(m_printer, fw); + if (!dialog.exec()) + return; + + const QCursor oldCursor = core()->topLevel()->cursor(); + core()->topLevel()->setCursor(Qt::WaitCursor); + // Estimate of required scaling to make form look the same on screen and printer. + const double suggestedScaling = static_cast(m_printer->physicalDpiX()) / static_cast(fw->physicalDpiX()); + + QPainter painter(m_printer); + painter.setRenderHint(QPainter::SmoothPixmapTransform); + + // Clamp to page + const QRectF page = painter.viewport(); + const double maxScaling = qMin(page.size().width() / pixmapSize.width(), page.size().height() / pixmapSize.height()); + const double scaling = qMin(suggestedScaling, maxScaling); + + const double xOffset = page.left() + qMax(0.0, (page.size().width() - scaling * pixmapSize.width()) / 2.0); + const double yOffset = page.top() + qMax(0.0, (page.size().height() - scaling * pixmapSize.height()) / 2.0); + + // Draw. + painter.translate(xOffset, yOffset); + painter.scale(scaling, scaling); + painter.drawPixmap(0, 0, pixmap); + core()->topLevel()->setCursor(oldCursor); + + showStatusBarMessage(tr("Printed %1.").arg(QFileInfo(fw->fileName()).fileName())); +#endif // HAS_PRINTER +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/qdesigner_actions.h b/src/tools/designer/src/designer/qdesigner_actions.h new file mode 100644 index 00000000000..e2efdfb5280 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_actions.h @@ -0,0 +1,191 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_ACTIONS_H +#define QDESIGNER_ACTIONS_H + +#include "assistantclient.h" +#include "qdesigner_settings.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerWorkbench; + +class QTimer; +class QAction; +class QActionGroup; +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class AppFontDialog; + +class QRect; +class QWidget; +class QPixmap; +class QPrinter; +class QMenu; + +namespace qdesigner_internal { + class PreviewConfiguration; + class PreviewManager; + enum class UicLanguage; +} + +class QDesignerActions: public QObject +{ + Q_OBJECT +public: + explicit QDesignerActions(QDesignerWorkbench *mainWindow); + ~QDesignerActions() override; + + QDesignerWorkbench *workbench() const; + QDesignerFormEditorInterface *core() const; + + bool saveForm(QDesignerFormWindowInterface *fw); + bool readInForm(const QString &fileName); + bool writeOutForm(QDesignerFormWindowInterface *formWindow, const QString &fileName, bool check = true); + + QActionGroup *fileActions() const; + QActionGroup *recentFilesActions() const; + QActionGroup *editActions() const; + QActionGroup *formActions() const; + QActionGroup *settingsActions() const; + QActionGroup *windowActions() const; + QActionGroup *toolActions() const; + QActionGroup *helpActions() const; + QActionGroup *uiMode() const; + QActionGroup *styleActions() const; + // file actions + QAction *openFormAction() const; + QAction *closeFormAction() const; + // window actions + QAction *minimizeAction() const; + // edit mode actions + QAction *editWidgets() const; + // form actions + QAction *previewFormAction() const; + QAction *viewCodeAction() const; + + void setBringAllToFrontVisible(bool visible); + void setWindowListSeparatorVisible(bool visible); + + bool openForm(QWidget *parent); + + QString uiExtension() const; + + // Boolean dynamic property set on actions to + // show them in the default toolbar layout + static const char *defaultToolbarPropertyName; + +public slots: + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + void createForm(); + void slotOpenForm(); + void helpRequested(const QString &manual, const QString &document); + +signals: + void useBigIcons(bool); + +private slots: + void saveForm(); + void saveFormAs(); + void saveAllForms(); + void saveFormAsTemplate(); + void notImplementedYet(); + void shutdown(); + void editWidgetsSlot(); + void openRecentForm(); + void clearRecentFiles(); + void closeForm(); + void showDesignerHelp(); + void aboutDesigner(); + void showWidgetSpecificHelp(); + void backupForms(); + void showNewFormDialog(const QString &fileName); + void showPreferencesDialog(); + void showAppFontDialog(); + void savePreviewImage(); + void printPreviewImage(); + void updateCloseAction(); + void formWindowCountChanged(); + void formWindowSettingsChanged(QDesignerFormWindowInterface *fw); + +private: + QAction *createRecentFilesMenu(); + bool saveFormAs(QDesignerFormWindowInterface *fw); + void updateRecentFileActions(); + void addRecentFile(const QString &fileName); + void showHelp(const QString &help); + void closePreview(); + QRect fixDialogRect(const QRect &rect) const; + void showStatusBarMessage(const QString &message) const; + QActionGroup *createHelpActions(); + bool ensureBackupDirectories(); + QPixmap createPreviewPixmap(QDesignerFormWindowInterface *fw); + qdesigner_internal::PreviewConfiguration previewConfiguration(); + void viewCode(qdesigner_internal::UicLanguage language); + + enum { MaxRecentFiles = 10 }; + QDesignerWorkbench *m_workbench; + QDesignerFormEditorInterface *m_core; + QDesignerSettings m_settings; + AssistantClient m_assistantClient; + QString m_openDirectory; + QString m_saveDirectory; + + + QString m_backupPath; + QString m_backupTmpPath; + + QTimer* m_backupTimer; + + QActionGroup *m_fileActions; + QActionGroup *m_recentFilesActions; + QActionGroup *m_editActions; + QActionGroup *m_formActions; + QActionGroup *m_settingsActions; + QActionGroup *m_windowActions; + QActionGroup *m_toolActions; + QActionGroup *m_helpActions = nullptr; + QActionGroup *m_styleActions = nullptr; + + QAction *m_editWidgetsAction; + + QAction *m_newFormAction; + QAction *m_openFormAction; + QAction *m_saveFormAction; + QAction *m_saveFormAsAction; + QAction *m_saveAllFormsAction; + QAction *m_saveFormAsTemplateAction; + QAction *m_closeFormAction; + QAction *m_savePreviewImageAction; + QAction *m_printPreviewAction; + + QAction *m_quitAction; + + QAction *m_previewFormAction = nullptr; + QAction *m_viewCppCodeAction; + QAction *m_viewPythonCodeAction; + + QAction *m_minimizeAction; + QAction *m_bringAllToFrontSeparator; + QAction *m_bringAllToFrontAction; + QAction *m_windowListSeparatorAction; + + QAction *m_preferencesAction; + QAction *m_appFontAction; + + QPointer m_appFontDialog; + + QPrinter *m_printer = nullptr; + + qdesigner_internal::PreviewManager *m_previewManager = nullptr; + + std::unique_ptr m_recentMenu; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_ACTIONS_H diff --git a/src/tools/designer/src/designer/qdesigner_appearanceoptions.cpp b/src/tools/designer/src/designer/qdesigner_appearanceoptions.cpp new file mode 100644 index 00000000000..a4e06214503 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_appearanceoptions.cpp @@ -0,0 +1,118 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_appearanceoptions.h" +#include "ui_qdesigner_appearanceoptions.h" + +#include "qdesigner_settings.h" +#include "qdesigner_toolwindow.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// ---------------- AppearanceOptions +void AppearanceOptions::toSettings(QDesignerSettings &settings) const +{ + settings.setUiMode(uiMode); + settings.setToolWindowFont(toolWindowFontSettings); +} + +void AppearanceOptions::fromSettings(const QDesignerSettings &settings) +{ + uiMode = settings.uiMode(); + toolWindowFontSettings = settings.toolWindowFont(); +} + +// ---------------- QDesignerAppearanceOptionsWidget +QDesignerAppearanceOptionsWidget::QDesignerAppearanceOptionsWidget(QWidget *parent) : + QWidget(parent), + m_ui(new QT_PREPEND_NAMESPACE(Ui)::AppearanceOptionsWidget) +{ + m_ui->setupUi(this); + + m_ui->m_uiModeCombo->addItem(tr("Docked Window"), QVariant(DockedMode)); + m_ui->m_uiModeCombo->addItem(tr("Multiple Top-Level Windows"), QVariant(TopLevelMode)); + connect(m_ui->m_uiModeCombo, &QComboBox::currentIndexChanged, + this, &QDesignerAppearanceOptionsWidget::slotUiModeComboChanged); + + m_ui->m_fontPanel->setCheckable(true); + m_ui->m_fontPanel->setTitle(tr("Toolwindow Font")); + +} + +QDesignerAppearanceOptionsWidget::~QDesignerAppearanceOptionsWidget() +{ + delete m_ui; +} + +UIMode QDesignerAppearanceOptionsWidget::uiMode() const +{ + return static_cast(m_ui->m_uiModeCombo->itemData(m_ui->m_uiModeCombo->currentIndex()).toInt()); +} + +AppearanceOptions QDesignerAppearanceOptionsWidget::appearanceOptions() const +{ + AppearanceOptions rc; + rc.uiMode = uiMode(); + rc.toolWindowFontSettings.m_font = m_ui->m_fontPanel->selectedFont(); + rc.toolWindowFontSettings.m_useFont = m_ui->m_fontPanel->isChecked(); + rc.toolWindowFontSettings.m_writingSystem = m_ui->m_fontPanel->writingSystem(); + return rc; +} + +void QDesignerAppearanceOptionsWidget::setAppearanceOptions(const AppearanceOptions &ao) +{ + m_initialUIMode = ao.uiMode; + m_ui->m_uiModeCombo->setCurrentIndex(m_ui->m_uiModeCombo->findData(QVariant(ao.uiMode))); + m_ui->m_fontPanel->setWritingSystem(ao.toolWindowFontSettings.m_writingSystem); + m_ui->m_fontPanel->setSelectedFont(ao.toolWindowFontSettings.m_font); + m_ui->m_fontPanel->setChecked(ao.toolWindowFontSettings.m_useFont); +} + +void QDesignerAppearanceOptionsWidget::slotUiModeComboChanged() +{ + emit uiModeChanged(m_initialUIMode != uiMode()); +} + +// ----------- QDesignerAppearanceOptionsPage +QDesignerAppearanceOptionsPage::QDesignerAppearanceOptionsPage(QDesignerFormEditorInterface *core) : + m_core(core) +{ +} + +QString QDesignerAppearanceOptionsPage::name() const +{ + //: Tab in preferences dialog + return QCoreApplication::translate("QDesignerAppearanceOptionsPage", "Appearance"); +} + +QWidget *QDesignerAppearanceOptionsPage::createPage(QWidget *parent) +{ + m_widget = new QDesignerAppearanceOptionsWidget(parent); + m_initialOptions.fromSettings(QDesignerSettings(m_core)); + m_widget->setAppearanceOptions(m_initialOptions); + return m_widget; +} + +void QDesignerAppearanceOptionsPage::apply() +{ + if (m_widget) { + const AppearanceOptions newOptions = m_widget->appearanceOptions(); + if (newOptions != m_initialOptions) { + QDesignerSettings settings(m_core); + newOptions.toSettings(settings); + m_initialOptions = newOptions; + emit settingsChanged(); + } + } +} + +void QDesignerAppearanceOptionsPage::finish() +{ +} + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/designer/qdesigner_appearanceoptions.h b/src/tools/designer/src/designer/qdesigner_appearanceoptions.h new file mode 100644 index 00000000000..1b37750c6d8 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_appearanceoptions.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_APPEARANCEOPTIONS_H +#define QDESIGNER_APPEARANCEOPTIONS_H + +#include "designer_enums.h" +#include "qdesigner_toolwindow.h" + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerSettings; + +namespace Ui { + class AppearanceOptionsWidget; +} + +/* AppearanceOptions data */ +struct AppearanceOptions +{ + void toSettings(QDesignerSettings &) const; + void fromSettings(const QDesignerSettings &); + + UIMode uiMode{DockedMode}; + ToolWindowFontSettings toolWindowFontSettings; + + friend bool comparesEqual(const AppearanceOptions &lhs, + const AppearanceOptions &rhs) noexcept + { + return lhs.uiMode == rhs.uiMode + && lhs.toolWindowFontSettings == rhs.toolWindowFontSettings; + } + Q_DECLARE_EQUALITY_COMPARABLE(AppearanceOptions) +}; + +/* QDesignerAppearanceOptionsWidget: Let the user edit AppearanceOptions */ +class QDesignerAppearanceOptionsWidget : public QWidget +{ + Q_OBJECT +public: + explicit QDesignerAppearanceOptionsWidget(QWidget *parent = nullptr); + ~QDesignerAppearanceOptionsWidget(); + + AppearanceOptions appearanceOptions() const; + void setAppearanceOptions(const AppearanceOptions &ao); + +signals: + void uiModeChanged(bool modified); + +private slots: + void slotUiModeComboChanged(); + +private: + UIMode uiMode() const; + + Ui::AppearanceOptionsWidget *m_ui; + UIMode m_initialUIMode = NeutralMode; +}; + +/* The options page for appearance options. */ + +class QDesignerAppearanceOptionsPage : public QObject, public QDesignerOptionsPageInterface +{ + Q_OBJECT + +public: + QDesignerAppearanceOptionsPage(QDesignerFormEditorInterface *core); + + QString name() const override; + QWidget *createPage(QWidget *parent) override; + void apply() override; + void finish() override; + +signals: + void settingsChanged(); + +private: + QDesignerFormEditorInterface *m_core; + QPointer m_widget; + AppearanceOptions m_initialOptions; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_APPEARANCEOPTIONS_H diff --git a/src/tools/designer/src/designer/qdesigner_appearanceoptions.ui b/src/tools/designer/src/designer/qdesigner_appearanceoptions.ui new file mode 100644 index 00000000000..6db8b8811ad --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_appearanceoptions.ui @@ -0,0 +1,57 @@ + + + AppearanceOptionsWidget + + + + 0 + 0 + 325 + 360 + + + + Form + + + + + + User Interface Mode + + + + + + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + FontPanel + QGroupBox +
fontpanel_p.h
+ 1 +
+
+ + +
diff --git a/src/tools/designer/src/designer/qdesigner_formwindow.cpp b/src/tools/designer/src/designer/qdesigner_formwindow.cpp new file mode 100644 index 00000000000..44db4df0384 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_formwindow.cpp @@ -0,0 +1,255 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_formwindow.h" +#include "qdesigner_workbench.h" +#include "formwindowbase_p.h" + +// sdk +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QDesignerFormWindow::QDesignerFormWindow(QDesignerFormWindowInterface *editor, QDesignerWorkbench *workbench, QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags), + m_editor(editor), + m_workbench(workbench), + m_action(new QAction(this)) +{ + Q_ASSERT(workbench); + + setMaximumSize(0xFFF, 0xFFF); + QDesignerFormEditorInterface *core = workbench->core(); + + if (m_editor) { + m_editor->setParent(this); + } else { + m_editor = core->formWindowManager()->createFormWindow(this); + } + + QVBoxLayout *l = new QVBoxLayout(this); + l->setContentsMargins(QMargins()); + l->addWidget(m_editor); + + m_action->setCheckable(true); + + connect(m_editor->commandHistory(), &QUndoStack::indexChanged, this, &QDesignerFormWindow::updateChanged); + connect(m_editor.data(), &QDesignerFormWindowInterface::geometryChanged, + this, &QDesignerFormWindow::slotGeometryChanged); +} + +QDesignerFormWindow::~QDesignerFormWindow() +{ + if (workbench()) + workbench()->removeFormWindow(this); +} + +QAction *QDesignerFormWindow::action() const +{ + return m_action; +} + +void QDesignerFormWindow::changeEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::WindowTitleChange: + m_action->setText(windowTitle().remove("[*]"_L1)); + break; + case QEvent::WindowIconChange: + m_action->setIcon(windowIcon()); + break; + case QEvent::WindowStateChange: { + const QWindowStateChangeEvent *wsce = static_cast(e); + const bool wasMinimized = Qt::WindowMinimized & wsce->oldState(); + const bool isMinimizedNow = isMinimized(); + if (wasMinimized != isMinimizedNow ) + emit minimizationStateChanged(m_editor, isMinimizedNow); + } + break; + default: + break; + } + QWidget::changeEvent(e); +} + +QRect QDesignerFormWindow::geometryHint() const +{ + const QPoint point(0, 0); + // If we have a container, we want to be just as big. + // QMdiSubWindow attempts to resize its children to sizeHint() when switching user interface modes. + if (QWidget *mainContainer = m_editor->mainContainer()) + return QRect(point, mainContainer->size()); + + return QRect(point, sizeHint()); +} + +QDesignerFormWindowInterface *QDesignerFormWindow::editor() const +{ + return m_editor; +} + +QDesignerWorkbench *QDesignerFormWindow::workbench() const +{ + return m_workbench; +} + +void QDesignerFormWindow::firstShow() +{ + // Set up handling of file name changes and set initial title. + if (!m_windowTitleInitialized) { + m_windowTitleInitialized = true; + if (m_editor) { + connect(m_editor.data(), &QDesignerFormWindowInterface::fileNameChanged, + this, &QDesignerFormWindow::updateWindowTitle); + updateWindowTitle(m_editor->fileName()); + updateChanged(); + } + } + show(); +} + +int QDesignerFormWindow::getNumberOfUntitledWindows() const +{ + const int totalWindows = m_workbench->formWindowCount(); + if (!totalWindows) + return 0; + + int maxUntitled = 0; + // Find the number of untitled windows excluding ourselves. + // Do not fall for 'untitled.ui', match with modified place holder. + // This will cause some problems with i18n, but for now I need the string to be "static" + static const QRegularExpression rx(u"untitled( (\\d+))?\\[\\*\\]$"_s); + Q_ASSERT(rx.isValid()); + for (int i = 0; i < totalWindows; ++i) { + QDesignerFormWindow *fw = m_workbench->formWindow(i); + if (fw != this) { + const QString title = m_workbench->formWindow(i)->windowTitle(); + const QRegularExpressionMatch match = rx.match(title); + if (match.hasMatch()) { + if (maxUntitled == 0) + ++maxUntitled; + if (match.lastCapturedIndex() >= 2) { + const auto numberCapture = match.capturedView(2); + if (!numberCapture.isEmpty()) + maxUntitled = qMax(numberCapture.toInt(), maxUntitled); + } + } + } + } + return maxUntitled; +} + +void QDesignerFormWindow::updateWindowTitle(const QString &fileName) +{ + if (!m_windowTitleInitialized) { + m_windowTitleInitialized = true; + if (m_editor) + connect(m_editor.data(), &QDesignerFormWindowInterface::fileNameChanged, + this, &QDesignerFormWindow::updateWindowTitle); + } + + QString fileNameTitle; + if (fileName.isEmpty()) { + fileNameTitle += "untitled"_L1; + if (const int maxUntitled = getNumberOfUntitledWindows()) { + fileNameTitle += u' ' + QString::number(maxUntitled + 1); + } + } else { + fileNameTitle = QFileInfo(fileName).fileName(); + } + + if (const QWidget *mc = m_editor->mainContainer()) { + setWindowIcon(mc->windowIcon()); + setWindowTitle(tr("%1 - %2[*]").arg(mc->windowTitle(), fileNameTitle)); + } else { + setWindowTitle(fileNameTitle); + } +} + +void QDesignerFormWindow::closeEvent(QCloseEvent *ev) +{ + if (m_editor->isDirty()) { + raise(); + QMessageBox box(QMessageBox::Information, tr("Save Form?"), + tr("Do you want to save the changes to this document before closing?"), + QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save, m_editor); + box.setInformativeText(tr("If you don't save, your changes will be lost.")); + box.setWindowModality(Qt::WindowModal); + static_cast(box.button(QMessageBox::Save))->setDefault(true); + + switch (box.exec()) { + case QMessageBox::Save: { + bool ok = workbench()->saveForm(m_editor); + ev->setAccepted(ok); + m_editor->setDirty(!ok); + break; + } + case QMessageBox::Discard: + m_editor->setDirty(false); // Not really necessary, but stops problems if we get close again. + ev->accept(); + break; + case QMessageBox::Cancel: + ev->ignore(); + break; + } + } +} + +void QDesignerFormWindow::updateChanged() +{ + // Sometimes called after form window destruction. + if (m_editor) { + setWindowModified(m_editor->isDirty()); + updateWindowTitle(m_editor->fileName()); + } +} + +void QDesignerFormWindow::resizeEvent(QResizeEvent *rev) +{ + if(m_initialized) { + m_editor->setDirty(true); + setWindowModified(true); + } + + m_initialized = true; + QWidget::resizeEvent(rev); +} + +void QDesignerFormWindow::slotGeometryChanged() +{ + // If the form window changes, re-update the geometry of the current widget in the property editor. + // Note that in the case of layouts, non-maincontainer widgets must also be updated, + // so, do not do it for the main container only + const QDesignerFormEditorInterface *core = m_editor->core(); + QObject *object = core->propertyEditor()->object(); + if (object == nullptr || !object->isWidgetType()) + return; + static const QString geometryProperty = u"geometry"_s; + const QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), object); + const int geometryIndex = sheet->indexOf(geometryProperty); + if (geometryIndex == -1) + return; + core->propertyEditor()->setPropertyValue(geometryProperty, sheet->property(geometryIndex)); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/qdesigner_formwindow.h b/src/tools/designer/src/designer/qdesigner_formwindow.h new file mode 100644 index 00000000000..6e509fcddde --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_formwindow.h @@ -0,0 +1,59 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_FORMWINDOW_H +#define QDESIGNER_FORMWINDOW_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerWorkbench; +class QDesignerFormWindowInterface; + +class QDesignerFormWindow: public QWidget +{ + Q_OBJECT +public: + QDesignerFormWindow(QDesignerFormWindowInterface *formWindow, QDesignerWorkbench *workbench, + QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + + void firstShow(); + + ~QDesignerFormWindow() override; + + QAction *action() const; + QDesignerWorkbench *workbench() const; + QDesignerFormWindowInterface *editor() const; + + QRect geometryHint() const; + +public slots: + void updateChanged(); + +private slots: + void updateWindowTitle(const QString &fileName); + void slotGeometryChanged(); + +signals: + void minimizationStateChanged(QDesignerFormWindowInterface *formWindow, bool minimized); + void triggerAction(); + +protected: + void changeEvent(QEvent *e) override; + void closeEvent(QCloseEvent *ev) override; + void resizeEvent(QResizeEvent* rev) override; + +private: + int getNumberOfUntitledWindows() const; + QPointer m_editor; + QPointer m_workbench; + QAction *m_action; + bool m_initialized = false; + bool m_windowTitleInitialized = false; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_FORMWINDOW_H diff --git a/src/tools/designer/src/designer/qdesigner_pch.h b/src/tools/designer/src/designer/qdesigner_pch.h new file mode 100644 index 00000000000..da4e03ddbdf --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_pch.h @@ -0,0 +1,21 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#if defined __cplusplus +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qdesigner.h" +#include "qdesigner_formwindow.h" +#include "qdesigner_settings.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_workbench.h" +#endif diff --git a/src/tools/designer/src/designer/qdesigner_server.cpp b/src/tools/designer/src/designer/qdesigner_server.cpp new file mode 100644 index 00000000000..5fd5c63ba9e --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_server.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include + +#include +#include +#include + +#include "qdesigner.h" +#include "qdesigner_server.h" + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// ### review + +QDesignerServer::QDesignerServer(QObject *parent) + : QObject(parent) +{ + m_server = new QTcpServer(this); + if (m_server->listen(QHostAddress::LocalHost, 0)) { + connect(m_server, &QTcpServer::newConnection, + this, &QDesignerServer::handleNewConnection); + } +} + +QDesignerServer::~QDesignerServer() = default; + +quint16 QDesignerServer::serverPort() const +{ + return m_server ? m_server->serverPort() : 0; +} + +void QDesignerServer::sendOpenRequest(int port, const QStringList &files) +{ + QTcpSocket *sSocket = new QTcpSocket(); + sSocket->connectToHost(QHostAddress::LocalHost, port); + if(sSocket->waitForConnected(3000)) + { + for (const QString &file : files) { + QFileInfo fi(file); + sSocket->write(fi.absoluteFilePath().toUtf8() + '\n'); + } + sSocket->waitForBytesWritten(3000); + sSocket->close(); + } + delete sSocket; +} + +void QDesignerServer::readFromClient() +{ + while (m_socket->canReadLine()) { + QString file = QString::fromUtf8(m_socket->readLine()); + if (!file.isNull()) { + file.remove(u'\n'); + file.remove(u'\r'); + qDesigner->postEvent(qDesigner, new QFileOpenEvent(file)); + } + } +} + +void QDesignerServer::socketClosed() +{ + m_socket = nullptr; +} + +void QDesignerServer::handleNewConnection() +{ + // no need for more than one connection + if (m_socket == nullptr) { + m_socket = m_server->nextPendingConnection(); + connect(m_socket, &QTcpSocket::readyRead, + this, &QDesignerServer::readFromClient); + connect(m_socket, &QTcpSocket::disconnected, + this, &QDesignerServer::socketClosed); + } +} + + +QDesignerClient::QDesignerClient(quint16 port, QObject *parent) +: QObject(parent) +{ + m_socket = new QTcpSocket(this); + m_socket->connectToHost(QHostAddress::LocalHost, port); + connect(m_socket, &QTcpSocket::readyRead, + this, &QDesignerClient::readFromSocket); + +} + +QDesignerClient::~QDesignerClient() +{ + m_socket->close(); + m_socket->flush(); +} + +void QDesignerClient::readFromSocket() +{ + while (m_socket->canReadLine()) { + QString file = QString::fromUtf8(m_socket->readLine()); + if (!file.isNull()) { + file.remove(u'\n'); + file.remove(u'\r'); + if (QFile::exists(file)) + qDesigner->postEvent(qDesigner, new QFileOpenEvent(file)); + } + } +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/qdesigner_server.h b/src/tools/designer/src/designer/qdesigner_server.h new file mode 100644 index 00000000000..d45345599df --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_server.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_SERVER_H +#define QDESIGNER_SERVER_H + +#include + +QT_BEGIN_NAMESPACE + +class QTcpServer; +class QTcpSocket; + +class QDesignerServer: public QObject +{ + Q_OBJECT +public: + explicit QDesignerServer(QObject *parent = nullptr); + ~QDesignerServer() override; + + quint16 serverPort() const; + + static void sendOpenRequest(int port, const QStringList &files); + +private slots: + void handleNewConnection(); + void readFromClient(); + void socketClosed(); + +private: + QTcpServer *m_server; + QTcpSocket *m_socket = nullptr; +}; + +class QDesignerClient: public QObject +{ + Q_OBJECT +public: + explicit QDesignerClient(quint16 port, QObject *parent = nullptr); + ~QDesignerClient() override; + +private slots: + void readFromSocket(); + +private: + QTcpSocket *m_socket; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_SERVER_H diff --git a/src/tools/designer/src/designer/qdesigner_settings.cpp b/src/tools/designer/src/designer/qdesigner_settings.cpp new file mode 100644 index 00000000000..9a3010323f3 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_settings.cpp @@ -0,0 +1,214 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner.h" +#include "qdesigner_settings.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_workbench.h" + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include + +enum { debugSettings = 0 }; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto newFormShowKey = "newFormDialog/ShowOnStartup"_L1; + +// Change the version whenever the arrangement changes significantly. +static constexpr auto mainWindowStateKey = "MainWindowState45"_L1; +static constexpr auto toolBarsStateKey = "ToolBarsState45"_L1; + +static constexpr auto backupOrgListKey = "backup/fileListOrg"_L1; +static constexpr auto backupBakListKey = "backup/fileListBak"_L1; +static constexpr auto recentFilesListKey = "recentFilesList"_L1; + +QDesignerSettings::QDesignerSettings(QDesignerFormEditorInterface *core) : + qdesigner_internal::QDesignerSharedSettings(core) +{ +} + +void QDesignerSettings::setValue(const QString &key, const QVariant &value) +{ + settings()->setValue(key, value); +} + +QVariant QDesignerSettings::value(const QString &key, const QVariant &defaultValue) const +{ + return settings()->value(key, defaultValue); +} + +static inline QChar modeChar(UIMode mode) +{ + return QLatin1Char(static_cast(mode) + '0'); +} + +void QDesignerSettings::saveGeometryFor(const QWidget *w) +{ + Q_ASSERT(w && !w->objectName().isEmpty()); + QDesignerSettingsInterface *s = settings(); + const bool visible = w->isVisible(); + if (debugSettings) + qDebug() << Q_FUNC_INFO << w << "visible=" << visible; + s->beginGroup(w->objectName()); + s->setValue(u"visible"_s, visible); + s->setValue(u"geometry"_s, w->saveGeometry()); + s->endGroup(); +} + +void QDesignerSettings::restoreGeometry(QWidget *w, QRect fallBack) const +{ + Q_ASSERT(w && !w->objectName().isEmpty()); + const QString key = w->objectName(); + const QByteArray ba(settings()->value(key + "/geometry"_L1).toByteArray()); + const bool visible = settings()->value(key + "/visible"_L1, true).toBool(); + + if (debugSettings) + qDebug() << Q_FUNC_INFO << w << fallBack << "visible=" << visible; + if (ba.isEmpty()) { + /// Apply default geometry, check for null and maximal size + if (fallBack.isNull()) + fallBack = QRect(QPoint(0, 0), w->sizeHint()); + if (fallBack.size() == QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) { + w->setWindowState(w->windowState() | Qt::WindowMaximized); + } else { + w->move(fallBack.topLeft()); + w->resize(fallBack.size()); + } + } else { + w->restoreGeometry(ba); + } + + if (visible) + w->show(); +} + +QStringList QDesignerSettings::recentFilesList() const +{ + return settings()->value(recentFilesListKey).toStringList(); +} + +void QDesignerSettings::setRecentFilesList(const QStringList &sl) +{ + settings()->setValue(recentFilesListKey, sl); +} + +void QDesignerSettings::setShowNewFormOnStartup(bool showIt) +{ + settings()->setValue(newFormShowKey, showIt); +} + +bool QDesignerSettings::showNewFormOnStartup() const +{ + return settings()->value(newFormShowKey, true).toBool(); +} + +QByteArray QDesignerSettings::mainWindowState(UIMode mode) const +{ + return settings()->value(mainWindowStateKey + modeChar(mode)).toByteArray(); +} + +void QDesignerSettings::setMainWindowState(UIMode mode, const QByteArray &mainWindowState) +{ + settings()->setValue(mainWindowStateKey + modeChar(mode), mainWindowState); +} + +QByteArray QDesignerSettings::toolBarsState(UIMode mode) const +{ + QString key = toolBarsStateKey; + key += modeChar(mode); + return settings()->value(key).toByteArray(); +} + +void QDesignerSettings::setToolBarsState(UIMode mode, const QByteArray &toolBarsState) +{ + QString key = toolBarsStateKey; + key += modeChar(mode); + settings()->setValue(key, toolBarsState); +} + +void QDesignerSettings::clearBackup() +{ + QDesignerSettingsInterface *s = settings(); + s->remove(backupOrgListKey); + s->remove(backupBakListKey); +} + +void QDesignerSettings::setBackup(const QMap &map) +{ + const QStringList org = map.keys(); + const QStringList bak = map.values(); + + QDesignerSettingsInterface *s = settings(); + s->setValue(backupOrgListKey, org); + s->setValue(backupBakListKey, bak); +} + +QMap QDesignerSettings::backup() const +{ + const QStringList org = settings()->value(backupOrgListKey, QStringList()).toStringList(); + const QStringList bak = settings()->value(backupBakListKey, QStringList()).toStringList(); + + QMap map; + const qsizetype orgCount = org.size(); + for (qsizetype i = 0; i < orgCount; ++i) + map.insert(org.at(i), bak.at(i)); + + return map; +} + +void QDesignerSettings::setUiMode(UIMode mode) +{ + QDesignerSettingsInterface *s = settings(); + s->beginGroup(u"UI"_s); + s->setValue(u"currentMode"_s, mode); + s->endGroup(); +} + +UIMode QDesignerSettings::uiMode() const +{ +#ifdef Q_OS_MACOS + const UIMode defaultMode = TopLevelMode; +#else + const UIMode defaultMode = DockedMode; +#endif + UIMode uiMode = static_cast(value(u"UI/currentMode"_s, defaultMode).toInt()); + return uiMode; +} + +void QDesignerSettings::setToolWindowFont(const ToolWindowFontSettings &fontSettings) +{ + QDesignerSettingsInterface *s = settings(); + s->beginGroup(u"UI"_s); + s->setValue(u"font"_s, fontSettings.m_font); + s->setValue(u"useFont"_s, fontSettings.m_useFont); + s->setValue(u"writingSystem"_s, fontSettings.m_writingSystem); + s->endGroup(); +} + +ToolWindowFontSettings QDesignerSettings::toolWindowFont() const +{ + ToolWindowFontSettings fontSettings; + fontSettings.m_writingSystem = + static_cast(value(u"UI/writingSystem"_s, + QFontDatabase::Any).toInt()); + fontSettings.m_font = qvariant_cast(value(u"UI/font"_s)); + fontSettings.m_useFont = + settings()->value(u"UI/useFont"_s, QVariant(false)).toBool(); + return fontSettings; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/qdesigner_settings.h b/src/tools/designer/src/designer/qdesigner_settings.h new file mode 100644 index 00000000000..72895a52d0e --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_settings.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_SETTINGS_H +#define QDESIGNER_SETTINGS_H + +#include "designer_enums.h" +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerSettingsInterface; +struct ToolWindowFontSettings; + +class QDesignerSettings : public qdesigner_internal::QDesignerSharedSettings +{ +public: + QDesignerSettings(QDesignerFormEditorInterface *core); + + void setValue(const QString &key, const QVariant &value); + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const; + + void restoreGeometry(QWidget *w, QRect fallBack = QRect()) const; + void saveGeometryFor(const QWidget *w); + + QStringList recentFilesList() const; + void setRecentFilesList(const QStringList &list); + + void setShowNewFormOnStartup(bool showIt); + bool showNewFormOnStartup() const; + + void setUiMode(UIMode mode); + UIMode uiMode() const; + + void setToolWindowFont(const ToolWindowFontSettings &fontSettings); + ToolWindowFontSettings toolWindowFont() const; + + QByteArray mainWindowState(UIMode mode) const; + void setMainWindowState(UIMode mode, const QByteArray &mainWindowState); + + QByteArray toolBarsState(UIMode mode) const; + void setToolBarsState(UIMode mode, const QByteArray &mainWindowState); + + void clearBackup(); + void setBackup(const QMap &map); + QMap backup() const; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_SETTINGS_H diff --git a/src/tools/designer/src/designer/qdesigner_toolwindow.cpp b/src/tools/designer/src/designer/qdesigner_toolwindow.cpp new file mode 100644 index 00000000000..3583a55e9b9 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_toolwindow.cpp @@ -0,0 +1,364 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_settings.h" +#include "qdesigner_workbench.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +static constexpr bool debugToolWindow = false; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr int margin = 20; + +// ---------------- QDesignerToolWindow +QDesignerToolWindow::QDesignerToolWindow(QDesignerWorkbench *workbench, + QWidget *w, + const QString &objectName, + const QString &title, + const QString &actionObjectName, + Qt::DockWidgetArea dockAreaHint, + QWidget *parent, + Qt::WindowFlags flags) : + MainWindowBase(parent, flags), + m_dockAreaHint(dockAreaHint), + m_workbench(workbench), + m_action(new QAction(this)) +{ + setObjectName(objectName); + setCentralWidget(w); + + setWindowTitle(title); + + m_action->setObjectName(actionObjectName); + m_action->setShortcutContext(Qt::ApplicationShortcut); + m_action->setText(title); + m_action->setCheckable(true); + connect(m_action, &QAction::triggered, this, &QDesignerToolWindow::showMe); +} + +void QDesignerToolWindow::showMe(bool v) +{ + // Access the QMdiSubWindow in MDI mode. + if (QWidget *target = m_workbench->mode() == DockedMode ? parentWidget() : this) { + if (v) + target->setWindowState(target->windowState() & ~Qt::WindowMinimized); + target->setVisible(v); + } +} + +void QDesignerToolWindow::showEvent(QShowEvent *e) +{ + Q_UNUSED(e); + + bool blocked = m_action->blockSignals(true); + m_action->setChecked(true); + m_action->blockSignals(blocked); +} + +void QDesignerToolWindow::hideEvent(QHideEvent *e) +{ + Q_UNUSED(e); + + bool blocked = m_action->blockSignals(true); + m_action->setChecked(false); + m_action->blockSignals(blocked); +} + +QAction *QDesignerToolWindow::action() const +{ + return m_action; +} + +void QDesignerToolWindow::changeEvent(QEvent *e) +{ + switch (e->type()) { + case QEvent::WindowTitleChange: + m_action->setText(windowTitle()); + break; + case QEvent::WindowIconChange: + m_action->setIcon(windowIcon()); + break; + default: + break; + } + QMainWindow::changeEvent(e); +} + +QDesignerWorkbench *QDesignerToolWindow::workbench() const +{ + return m_workbench; +} + +// ---------------------- PropertyEditorToolWindow + +static inline QWidget *createPropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr) +{ + QDesignerPropertyEditorInterface *widget = QDesignerComponents::createPropertyEditor(core, parent); + core->setPropertyEditor(widget); + return widget; +} + +class PropertyEditorToolWindow : public QDesignerToolWindow +{ +public: + explicit PropertyEditorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &) const override; + +protected: + void showEvent(QShowEvent *event) override; +}; + +PropertyEditorToolWindow::PropertyEditorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + createPropertyEditor(workbench->core()), + u"qt_designer_propertyeditor"_s, + QDesignerToolWindow::tr("Property Editor"), + u"__qt_property_editor_action"_s, + Qt::RightDockWidgetArea) +{ + action()->setShortcut(Qt::CTRL | Qt::Key_I); + +} + +QRect PropertyEditorToolWindow::geometryHint(const QRect &g) const +{ + const int spacing = 40; + const QSize sz(g.width() * 1/4, g.height() * 4/6); + + const QRect rc = QRect((g.right() + 1 - sz.width() - margin), + (g.top() + margin + g.height() * 1/6) + spacing, + sz.width(), sz.height()); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << rc; + return rc; +} + +void PropertyEditorToolWindow::showEvent(QShowEvent *event) +{ + if (QDesignerPropertyEditorInterface *e = workbench()->core()->propertyEditor()) { + // workaround to update the propertyeditor when it is not visible! + e->setObject(e->object()); // ### remove me + } + + QDesignerToolWindow::showEvent(event); +} + +// ---------------------- ActionEditorToolWindow + +static inline QWidget *createActionEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr) +{ + QDesignerActionEditorInterface *widget = QDesignerComponents::createActionEditor(core, parent); + core->setActionEditor(widget); + return widget; +} + +class ActionEditorToolWindow: public QDesignerToolWindow +{ +public: + explicit ActionEditorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +ActionEditorToolWindow::ActionEditorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + createActionEditor(workbench->core()), + u"qt_designer_actioneditor"_s, + QDesignerToolWindow::tr("Action Editor"), + u"__qt_action_editor_tool_action"_s, + Qt::RightDockWidgetArea) +{ +} + +QRect ActionEditorToolWindow::geometryHint(const QRect &g) const +{ + const QSize sz(g.width() * 1/4, g.height() * 1/6); + + const QRect rc = QRect((g.right() + 1 - sz.width() - margin), + g.top() + margin, + sz.width(), sz.height()); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << rc; + return rc; +} + +// ---------------------- ObjectInspectorToolWindow + +static inline QWidget *createObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent = nullptr) +{ + QDesignerObjectInspectorInterface *widget = QDesignerComponents::createObjectInspector(core, parent); + core->setObjectInspector(widget); + return widget; +} + +class ObjectInspectorToolWindow: public QDesignerToolWindow +{ +public: + explicit ObjectInspectorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +ObjectInspectorToolWindow::ObjectInspectorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + createObjectInspector(workbench->core()), + u"qt_designer_objectinspector"_s, + QDesignerToolWindow::tr("Object Inspector"), + u"__qt_object_inspector_tool_action"_s, + Qt::RightDockWidgetArea) +{ +} + +QRect ObjectInspectorToolWindow::geometryHint(const QRect &g) const +{ + const QSize sz(g.width() * 1/4, g.height() * 1/6); + + const QRect rc = QRect((g.right() + 1 - sz.width() - margin), + g.top() + margin, + sz.width(), sz.height()); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << rc; + return rc; +} + +// ---------------------- ResourceEditorToolWindow + +class ResourceEditorToolWindow: public QDesignerToolWindow +{ +public: + explicit ResourceEditorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +ResourceEditorToolWindow::ResourceEditorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + QDesignerComponents::createResourceEditor(workbench->core(), nullptr), + u"qt_designer_resourceeditor"_s, + QDesignerToolWindow::tr("Resource Browser"), + u"__qt_resource_editor_tool_action"_s, + Qt::RightDockWidgetArea) +{ +} + +QRect ResourceEditorToolWindow::geometryHint(const QRect &g) const +{ + const QSize sz(g.width() * 1/3, g.height() * 1/6); + QRect r(QPoint(0, 0), sz); + r.moveCenter(g.center()); + r.moveBottom(g.bottom() - margin); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << r; + return r; +} + +// ---------------------- SignalSlotEditorToolWindow + +class SignalSlotEditorToolWindow: public QDesignerToolWindow +{ +public: + explicit SignalSlotEditorToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +SignalSlotEditorToolWindow::SignalSlotEditorToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + QDesignerComponents::createSignalSlotEditor(workbench->core(), nullptr), + u"qt_designer_signalsloteditor"_s, + QDesignerToolWindow::tr("Signal/Slot Editor"), + u"__qt_signal_slot_editor_tool_action"_s, + Qt::RightDockWidgetArea) +{ +} + +QRect SignalSlotEditorToolWindow::geometryHint(const QRect &g) const +{ + const QSize sz(g.width() * 1/3, g.height() * 1/6); + QRect r(QPoint(0, 0), sz); + r.moveCenter(g.center()); + r.moveTop(margin + g.top()); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << r; + return r; +} + +// ---------------------- WidgetBoxToolWindow + +static inline QWidget *createWidgetBox(QDesignerFormEditorInterface *core, QWidget *parent = nullptr) +{ + QDesignerWidgetBoxInterface *widget = QDesignerComponents::createWidgetBox(core, parent); + core->setWidgetBox(widget); + return widget; +} + +class WidgetBoxToolWindow: public QDesignerToolWindow +{ +public: + explicit WidgetBoxToolWindow(QDesignerWorkbench *workbench); + + QRect geometryHint(const QRect &g) const override; +}; + +WidgetBoxToolWindow::WidgetBoxToolWindow(QDesignerWorkbench *workbench) : + QDesignerToolWindow(workbench, + createWidgetBox(workbench->core()), + u"qt_designer_widgetbox"_s, + QDesignerToolWindow::tr("Widget Box"), + u"__qt_widget_box_tool_action"_s, + Qt::LeftDockWidgetArea) +{ +} + +QRect WidgetBoxToolWindow::geometryHint(const QRect &g) const +{ + const QRect rc = QRect(g.left() + margin, + g.top() + margin, + g.width() * 1/4, g.height() * 5/6); + if (debugToolWindow) + qDebug() << Q_FUNC_INFO << rc; + return rc; +} + +// -- Factory +QDesignerToolWindow *QDesignerToolWindow::createStandardToolWindow(StandardToolWindow which, + QDesignerWorkbench *workbench) +{ + switch (which) { + case ActionEditor: + return new ActionEditorToolWindow(workbench); + case ResourceEditor: + return new ResourceEditorToolWindow(workbench); + case SignalSlotEditor: + return new SignalSlotEditorToolWindow(workbench); + case PropertyEditor: + return new PropertyEditorToolWindow(workbench); + case ObjectInspector: + return new ObjectInspectorToolWindow(workbench); + case WidgetBox: + return new WidgetBoxToolWindow(workbench); + default: + break; + } + return nullptr; +} + + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/qdesigner_toolwindow.h b/src/tools/designer/src/designer/qdesigner_toolwindow.h new file mode 100644 index 00000000000..7fec13981ad --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_toolwindow.h @@ -0,0 +1,81 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_TOOLWINDOW_H +#define QDESIGNER_TOOLWINDOW_H + +#include "mainwindow.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +struct ToolWindowFontSettings +{ + QFont m_font; + QFontDatabase::WritingSystem m_writingSystem{QFontDatabase::Any}; + bool m_useFont{false}; + + friend bool comparesEqual(const ToolWindowFontSettings &lhs, + const ToolWindowFontSettings &rhs) noexcept + { + return lhs.m_useFont == rhs.m_useFont + && lhs.m_writingSystem == rhs.m_writingSystem + && lhs.m_font == rhs.m_font; + } + Q_DECLARE_EQUALITY_COMPARABLE(ToolWindowFontSettings) +}; + +class QDesignerWorkbench; + +/* A tool window with an action that activates it. Note that in toplevel mode, + * the Widget box is a tool window as well as the applications' main window, + * So, we need to inherit from MainWindowBase. */ + +class QDesignerToolWindow : public MainWindowBase +{ + Q_OBJECT +protected: + explicit QDesignerToolWindow(QDesignerWorkbench *workbench, + QWidget *w, + const QString &objectName, + const QString &title, + const QString &actionObjectName, + Qt::DockWidgetArea dockAreaHint, + QWidget *parent = nullptr, + Qt::WindowFlags flags = Qt::Window); + +public: + // Note: The order influences the dock widget position. + enum StandardToolWindow { WidgetBox, ObjectInspector, PropertyEditor, + ResourceEditor, ActionEditor, SignalSlotEditor, + StandardToolWindowCount }; + + static QDesignerToolWindow *createStandardToolWindow(StandardToolWindow which, QDesignerWorkbench *workbench); + + QDesignerWorkbench *workbench() const; + QAction *action() const; + + Qt::DockWidgetArea dockWidgetAreaHint() const { return m_dockAreaHint; } + virtual QRect geometryHint(const QRect &availableGeometry) const = 0; + +private slots: + void showMe(bool); + +protected: + void showEvent(QShowEvent *e) override; + void hideEvent(QHideEvent *e) override; + void changeEvent(QEvent *e) override; + +private: + const Qt::DockWidgetArea m_dockAreaHint; + QDesignerWorkbench *m_workbench; + QAction *m_action; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_TOOLWINDOW_H diff --git a/src/tools/designer/src/designer/qdesigner_workbench.cpp b/src/tools/designer/src/designer/qdesigner_workbench.cpp new file mode 100644 index 00000000000..ff1b3703006 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_workbench.cpp @@ -0,0 +1,1092 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_workbench.h" +#include "qdesigner.h" +#include "qdesigner_actions.h" +#include "qdesigner_appearanceoptions.h" +#include "qdesigner_settings.h" +#include "qdesigner_toolwindow.h" +#include "qdesigner_formwindow.h" +#include "appfontdialog.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto appFontPrefixC = "AppFonts"_L1; + +using ActionList = QList; + +static QMdiSubWindow *mdiSubWindowOf(const QWidget *w) +{ + auto *rc = qobject_cast(w->parentWidget()); + Q_ASSERT(rc); + return rc; +} + +static QDockWidget *dockWidgetOf(const QWidget *w) +{ + for (QWidget *parentWidget = w->parentWidget(); parentWidget ; parentWidget = parentWidget->parentWidget()) { + if (auto *dw = qobject_cast(parentWidget)) { + return dw; + } + } + Q_ASSERT("Dock widget not found"); + return nullptr; +} + +// ------------ QDesignerWorkbench::Position +QDesignerWorkbench::Position::Position(const QMdiSubWindow *mdiSubWindow) : + m_minimized(mdiSubWindow->isShaded()), + m_position(mdiSubWindow->pos() + mdiSubWindow->mdiArea()->pos()) +{ +} + +QDesignerWorkbench::Position::Position(const QDockWidget *dockWidget) : + m_minimized(dockWidget->isMinimized()), + m_position(dockWidget->pos()) +{ +} + +QDesignerWorkbench::Position::Position(const QWidget *topLevelWindow) +{ + const QWidget *window = topLevelWindow->window(); + Q_ASSERT(window); + m_minimized = window->isMinimized(); + m_position = window->pos() - window->screen()->availableGeometry().topLeft(); +} + +void QDesignerWorkbench::Position::applyTo(QMdiSubWindow *mdiSubWindow, + const QPoint &mdiAreaOffset) const +{ + // QMdiSubWindow attempts to resize its children to sizeHint() when switching user interface modes. + // Restore old size + const QPoint mdiAreaPos = QPoint(qMax(0, m_position.x() - mdiAreaOffset.x()), + qMax(0, m_position.y() - mdiAreaOffset.y())); + mdiSubWindow->move(mdiAreaPos); + const QSize decorationSize = mdiSubWindow->size() - mdiSubWindow->contentsRect().size(); + mdiSubWindow->resize(mdiSubWindow->widget()->size() + decorationSize); + mdiSubWindow->show(); + if (m_minimized) { + mdiSubWindow->showShaded(); + } +} + +void QDesignerWorkbench::Position::applyTo(QWidget *topLevelWindow, const QPoint &desktopTopLeft) const +{ + QWidget *window = topLevelWindow->window (); + const QPoint newPos = m_position + desktopTopLeft; + window->move(newPos); + if ( m_minimized) { + topLevelWindow->showMinimized(); + } else { + topLevelWindow->show(); + } +} + +void QDesignerWorkbench::Position::applyTo(QDockWidget *dockWidget) const +{ + dockWidget->widget()->setVisible(true); + dockWidget->setVisible(!m_minimized); +} + +static inline void addActionsToMenu(QMenu *m, const ActionList &al) +{ + for (auto *a : al) + m->addAction(a); +} + +static inline QMenu *addMenu(QMenuBar *mb, const QString &title, const ActionList &al) +{ + QMenu *rc = mb->addMenu(title); + addActionsToMenu(rc, al); + return rc; +} + +// -------- QDesignerWorkbench + +QDesignerWorkbench::QDesignerWorkbench(const QStringList &pluginPaths) : + m_core(QDesignerComponents::createFormEditorWithPluginPaths(pluginPaths, this)), + m_windowActions(new QActionGroup(this)), + m_globalMenuBar(new QMenuBar) +{ + QDesignerSettings settings(m_core); + + (void) QDesignerComponents::createTaskMenu(core(), this); + + initializeCorePlugins(); + QDesignerComponents::initializePlugins(core()); + m_actionManager = new QDesignerActions(this); // accesses plugin components + + m_windowActions->setExclusive(true); + connect(m_windowActions, &QActionGroup::triggered, + this, &QDesignerWorkbench::formWindowActionTriggered); + + // Build main menu bar + addMenu(m_globalMenuBar, tr("&File"), m_actionManager->fileActions()->actions()); + + QMenu *editMenu = addMenu(m_globalMenuBar, tr("&Edit"), m_actionManager->editActions()->actions()); + editMenu->addSeparator(); + addActionsToMenu(editMenu, m_actionManager->toolActions()->actions()); + + QMenu *formMenu = addMenu(m_globalMenuBar, tr("F&orm"), m_actionManager->formActions()->actions()); + auto *previewSubMenu = new QMenu(tr("Preview in"), formMenu); + formMenu->insertMenu(m_actionManager->previewFormAction(), previewSubMenu); + addActionsToMenu(previewSubMenu, m_actionManager->styleActions()->actions()); + + QMenu *viewMenu = m_globalMenuBar->addMenu(tr("&View")); + + addMenu(m_globalMenuBar, tr("&Settings"), m_actionManager->settingsActions()->actions()); + + m_windowMenu = addMenu(m_globalMenuBar, tr("&Window"), m_actionManager->windowActions()->actions()); + + addMenu(m_globalMenuBar, tr("&Help"), m_actionManager->helpActions()->actions()); + + // Add the tools in view menu order + auto *viewActions = new QActionGroup(this); + viewActions->setExclusive(false); + + for (int i = 0; i < QDesignerToolWindow::StandardToolWindowCount; i++) { + QDesignerToolWindow *toolWindow = QDesignerToolWindow::createStandardToolWindow(static_cast< QDesignerToolWindow::StandardToolWindow>(i), this); + m_toolWindows.push_back(toolWindow); + if (QAction *action = toolWindow->action()) { + viewMenu->addAction(action); + viewActions->addAction(action); + } + // The widget box becomes the main window in top level mode + if (i == QDesignerToolWindow::WidgetBox) { + connect(toolWindow, &QDesignerToolWindow::closeEventReceived, + this, &QDesignerWorkbench::handleCloseEvent); + } + } + // Integration + m_integration = new QDesignerIntegration(m_core, this); + connect(m_integration, &QDesignerIntegration::helpRequested, + m_actionManager, &QDesignerActions::helpRequested); + + // remaining view options (config toolbars) + viewMenu->addSeparator(); + m_toolbarMenu = viewMenu->addMenu(tr("Toolbars")); + + emit initialized(); + + connect(m_core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + this, &QDesignerWorkbench::updateWindowMenu); + + + { // Add application specific options pages + QDesignerAppearanceOptionsPage *appearanceOptions = new QDesignerAppearanceOptionsPage(m_core); + connect(appearanceOptions, &QDesignerAppearanceOptionsPage::settingsChanged, this, &QDesignerWorkbench::notifyUISettingsChanged); + auto optionsPages = m_core->optionsPages(); + optionsPages.push_front(appearanceOptions); + m_core->setOptionsPages(optionsPages); + } + + restoreUISettings(); + AppFontWidget::restore(m_core->settingsManager(), appFontPrefixC); + m_state = StateUp; +} + +QDesignerWorkbench::~QDesignerWorkbench() +{ + switch (m_mode) { + case NeutralMode: + case DockedMode: + qDeleteAll(m_toolWindows); + break; + case TopLevelMode: // Everything parented here + delete widgetBoxToolWindow(); + break; + } + delete m_globalMenuBar; + m_windowMenu = nullptr; + delete m_dockedMainWindow; +} + +void QDesignerWorkbench::saveGeometriesForModeChange() +{ + m_Positions.clear(); + switch (m_mode) { + case NeutralMode: + break; + case TopLevelMode: { + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + m_Positions.insert(tw, Position(tw)); + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) + m_Positions.insert(fw, Position(fw)); + } + break; + case DockedMode: { + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + m_Positions.insert(tw, Position(dockWidgetOf(tw))); + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) + m_Positions.insert(fw, Position(mdiSubWindowOf(fw))); + } + break; + } +} + +UIMode QDesignerWorkbench::mode() const +{ + return m_mode; +} + +void QDesignerWorkbench::addFormWindow(QDesignerFormWindow *formWindow) +{ + // ### Q_ASSERT(formWindow->windowTitle().isEmpty() == false); + + m_formWindows.append(formWindow); + + + m_actionManager->setWindowListSeparatorVisible(true); + + if (QAction *action = formWindow->action()) { + m_windowActions->addAction(action); + m_windowMenu->addAction(action); + action->setChecked(true); + } + + m_actionManager->minimizeAction()->setEnabled(true); + m_actionManager->minimizeAction()->setChecked(false); + connect(formWindow, &QDesignerFormWindow::minimizationStateChanged, + this, &QDesignerWorkbench::minimizationStateChanged); + + m_actionManager->editWidgets()->trigger(); +} + +Qt::WindowFlags QDesignerWorkbench::magicalWindowFlags(const QWidget *widgetForFlags) const +{ + switch (m_mode) { + case TopLevelMode: { +#ifdef Q_OS_MACOS + if (qobject_cast(widgetForFlags)) + return Qt::Tool; +#else + Q_UNUSED(widgetForFlags); +#endif + return Qt::Window; + } + case DockedMode: + return Qt::Window | Qt::WindowShadeButtonHint | Qt::WindowSystemMenuHint | Qt::WindowTitleHint; + case NeutralMode: + return Qt::Window; + default: + Q_ASSERT(0); + return {}; + } +} + +QWidget *QDesignerWorkbench::magicalParent(const QWidget *w) const +{ + switch (m_mode) { + case TopLevelMode: { + // Use widget box as parent for all windows except self. This will + // result in having just one entry in the MS Windows task bar. + QWidget *widgetBoxWrapper = widgetBoxToolWindow(); + return w == widgetBoxWrapper ? nullptr : widgetBoxWrapper; + } + case DockedMode: + return m_dockedMainWindow->mdiArea(); + case NeutralMode: + break; + default: + Q_ASSERT(false); + break; + } + return nullptr; +} + +void QDesignerWorkbench::switchToNeutralMode() +{ + QDesignerSettings settings(m_core); + saveGeometries(settings); + saveGeometriesForModeChange(); + + if (m_mode == TopLevelMode) { + delete m_topLevelData.toolbarManager; + m_topLevelData.toolbarManager = nullptr; + qDeleteAll(m_topLevelData.toolbars); + m_topLevelData.toolbars.clear(); + } + + m_mode = NeutralMode; + + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) { + tw->setCloseEventPolicy(MainWindowBase::AcceptCloseEvents); + tw->setParent(nullptr); + // Prevent unneeded native children when switching to docked + if (auto *handle = tw->windowHandle()) + handle->destroy(); + } + + if (m_dockedMainWindow != nullptr) // Prevent assert + m_dockedMainWindow->mdiArea()->setActiveSubWindow(nullptr); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) { + fw->setParent(nullptr); + fw->setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); + // Prevent unneeded native children when switching to docked + if (auto *handle = fw->windowHandle()) + handle->destroy(); + } + +#ifndef Q_OS_MACOS + m_globalMenuBar->setParent(nullptr); +#endif + + m_core->setTopLevel(nullptr); + qDesigner->setMainWindow(nullptr); + + delete m_dockedMainWindow; +} + +void QDesignerWorkbench::switchToDockedMode() +{ + if (m_mode == DockedMode) + return; + + switchToNeutralMode(); + +#if !defined(Q_OS_MACOS) +# if defined(Q_OS_UNIX) + QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, false); +# endif // Q_OS_UNIX + QDesignerToolWindow *widgetBoxWrapper = widgetBoxToolWindow(); + widgetBoxWrapper->action()->setVisible(true); + widgetBoxWrapper->setWindowTitle(tr("Widget Box")); +#endif // !Q_OS_MACOS + + m_mode = DockedMode; + const QDesignerSettings settings(m_core); + m_dockedMainWindow = new DockedMainWindow(this, m_toolbarMenu, m_toolWindows); + m_dockedMainWindow->setUnifiedTitleAndToolBarOnMac(true); + m_dockedMainWindow->setCloseEventPolicy(MainWindowBase::EmitCloseEventSignal); + connect(m_dockedMainWindow, &DockedMainWindow::closeEventReceived, + this, &QDesignerWorkbench::handleCloseEvent); + connect(m_dockedMainWindow, &DockedMainWindow::fileDropped, + this, &QDesignerWorkbench::slotFileDropped); + connect(m_dockedMainWindow, &DockedMainWindow::formWindowActivated, + this, &QDesignerWorkbench::slotFormWindowActivated); + m_dockedMainWindow->restoreSettings(settings, + m_dockedMainWindow->addToolWindows(m_toolWindows), + screen()->availableGeometry()); + + m_core->setTopLevel(m_dockedMainWindow); + +#ifndef Q_OS_MACOS + m_dockedMainWindow->setMenuBar(m_globalMenuBar); + m_globalMenuBar->show(); +#endif + qDesigner->setMainWindow(m_dockedMainWindow); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) { + QMdiSubWindow *subwin = m_dockedMainWindow->createMdiSubWindow(fw, magicalWindowFlags(fw), + m_actionManager->closeFormAction()->shortcut()); + subwin->hide(); + if (QWidget *mainContainer = fw->editor()->mainContainer()) + resizeForm(fw, mainContainer); + } + + m_actionManager->setBringAllToFrontVisible(false); + m_dockedMainWindow->show(); + // Trigger adjustMDIFormPositions() delayed as viewport size is not yet known. + + if (m_state != StateInitializing) + QMetaObject::invokeMethod(this, "adjustMDIFormPositions", Qt::QueuedConnection); +} + +void QDesignerWorkbench::adjustMDIFormPositions() +{ + const QPoint mdiAreaOffset = m_dockedMainWindow->mdiArea()->pos(); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) { + const auto pit = m_Positions.constFind(fw); + if (pit != m_Positions.constEnd()) + pit->applyTo(mdiSubWindowOf(fw), mdiAreaOffset); + } +} + +static QScreen *screenUnderMouse() +{ + const auto &screens = QGuiApplication::screens(); + const auto pos = QCursor::pos(); + auto pred = [pos](const QScreen *s) { return s->geometry().contains(pos); }; + auto it = std::find_if(screens.cbegin(), screens.cend(), pred); + return it != screens.cend() ? *it : QGuiApplication::primaryScreen(); +} + +void QDesignerWorkbench::switchToTopLevelMode() +{ + if (m_mode == TopLevelMode) + return; + + // make sure that the widgetbox is visible if it is different from neutral. + QDesignerToolWindow *widgetBoxWrapper = widgetBoxToolWindow(); + Q_ASSERT(widgetBoxWrapper); + + switchToNeutralMode(); + m_mode = TopLevelMode; // Set new mode before calling screen() + const QDesignerSettings settings(m_core); + const QByteArray mainWindowState = settings.mainWindowState(m_mode); + // Open on screen where the mouse is when no settings exist + const auto *currentScreen = mainWindowState.isEmpty() ? screenUnderMouse() : screen(); + const QRect availableGeometry = currentScreen->availableGeometry(); + const QPoint desktopOffset = availableGeometry.topLeft(); + + // The widget box is special, it gets the menubar and gets to be the main widget. + + m_core->setTopLevel(widgetBoxWrapper); +#if !defined(Q_OS_MACOS) +# if defined(Q_OS_UNIX) + // For now the appmenu protocol does not make it possible to associate a + // menubar with all application windows. This means in top level mode you + // can only reach the menubar when the widgetbox window is active. Since + // this is quite inconvenient, better not use the native menubar in this + // configuration and keep the menubar in the widgetbox window. + QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar, true); +# endif // Q_OS_UNIX + widgetBoxWrapper->setMenuBar(m_globalMenuBar); + widgetBoxWrapper->action()->setVisible(false); + widgetBoxWrapper->setCloseEventPolicy(MainWindowBase::EmitCloseEventSignal); + qDesigner->setMainWindow(widgetBoxWrapper); + widgetBoxWrapper->setWindowTitle(MainWindowBase::mainWindowTitle()); +#endif // !Q_OS_MACOS + + m_topLevelData.toolbars = MainWindowBase::createToolBars(m_actionManager, false); + m_topLevelData.toolbarManager = new ToolBarManager(widgetBoxWrapper, widgetBoxWrapper, + m_toolbarMenu, m_actionManager, + m_topLevelData.toolbars, m_toolWindows); + const qsizetype toolBarCount = m_topLevelData.toolbars.size(); + for (qsizetype i = 0; i < toolBarCount; ++i) { + widgetBoxWrapper->addToolBar(m_topLevelData.toolbars.at(i)); + if (i == 3) + widgetBoxWrapper->insertToolBarBreak(m_topLevelData.toolbars.at(i)); + } + m_topLevelData.toolbarManager->restoreState(settings.toolBarsState(m_mode), MainWindowBase::settingsVersion()); + widgetBoxWrapper->restoreState(mainWindowState, MainWindowBase::settingsVersion()); + + bool found_visible_window = false; + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) { + tw->setParent(magicalParent(tw), magicalWindowFlags(tw)); + settings.restoreGeometry(tw, tw->geometryHint(availableGeometry)); + tw->action()->setChecked(tw->isVisible()); + found_visible_window |= tw->isVisible(); + } + + if (!m_toolWindows.isEmpty() && !found_visible_window) + m_toolWindows.constFirst()->show(); + + m_actionManager->setBringAllToFrontVisible(true); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) { + fw->setParent(magicalParent(fw), magicalWindowFlags(fw)); + fw->setAttribute(Qt::WA_DeleteOnClose, true); + const auto pit = m_Positions.constFind(fw); + if (pit != m_Positions.constEnd()) pit->applyTo(fw, desktopOffset); + // Force an activate in order to refresh minimumSize, otherwise it will not be respected + if (QLayout *layout = fw->layout()) + layout->invalidate(); + if (QWidget *mainContainer = fw->editor()->mainContainer()) + resizeForm(fw, mainContainer); + } +} + +QDesignerFormWindowManagerInterface *QDesignerWorkbench::formWindowManager() const +{ + return m_core->formWindowManager(); +} + +QDesignerFormEditorInterface *QDesignerWorkbench::core() const +{ + return m_core; +} + +int QDesignerWorkbench::toolWindowCount() const +{ + return m_toolWindows.size(); +} + +QDesignerToolWindow *QDesignerWorkbench::toolWindow(int index) const +{ + return m_toolWindows.at(index); +} + +int QDesignerWorkbench::formWindowCount() const +{ + return m_formWindows.size(); +} + +QDesignerFormWindow *QDesignerWorkbench::formWindow(int index) const +{ + return m_formWindows.at(index); +} + +QScreen *QDesignerWorkbench::screen() const +{ + auto *widget = m_mode == DockedMode + ? static_cast(m_dockedMainWindow.data()) + : static_cast(widgetBoxToolWindow()); + return widget != nullptr + ? widget->screen() : QGuiApplication::primaryScreen(); +} + +QRect QDesignerWorkbench::availableFormGeometry() const +{ + // Return available geometry for forms + return m_mode == DockedMode + ? m_dockedMainWindow->mdiArea()->geometry() : screen()->availableGeometry(); +} + +void QDesignerWorkbench::slotFormWindowActivated(QDesignerFormWindow* fw) +{ + core()->formWindowManager()->setActiveFormWindow(fw->editor()); +} + +void QDesignerWorkbench::removeFormWindow(QDesignerFormWindow *formWindow) +{ + QDesignerFormWindowInterface *editor = formWindow->editor(); + const bool loadOk = editor->mainContainer(); + updateBackup(editor); + const int index = m_formWindows.indexOf(formWindow); + if (index != -1) { + m_formWindows.removeAt(index); + } + + if (QAction *action = formWindow->action()) { + m_windowActions->removeAction(action); + if (m_windowMenu) + m_windowMenu->removeAction(action); + } + + if (m_formWindows.isEmpty()) { + m_actionManager->setWindowListSeparatorVisible(false); + // Show up new form dialog unless closing + if (loadOk && m_state == StateUp) + showNewForm(); + } +} + +void QDesignerWorkbench::showNewForm() +{ + if (!m_suppressNewFormShow && QDesignerSettings(m_core).showNewFormOnStartup()) + QTimer::singleShot(100, m_actionManager, &QDesignerActions::createForm); +} + +void QDesignerWorkbench::initializeCorePlugins() +{ + QObjectList plugins = QPluginLoader::staticInstances(); + plugins += core()->pluginManager()->instances(); + + for (QObject *plugin : std::as_const(plugins)) { + if (QDesignerFormEditorPluginInterface *formEditorPlugin = qobject_cast(plugin)) { + if (!formEditorPlugin->isInitialized()) + formEditorPlugin->initialize(core()); + } + } +} + +void QDesignerWorkbench::saveSettings() const +{ + QDesignerSettings settings(m_core); + settings.clearBackup(); + saveGeometries(settings); + AppFontWidget::save(m_core->settingsManager(), appFontPrefixC); +} + +void QDesignerWorkbench::saveGeometries(QDesignerSettings &settings) const +{ + switch (m_mode) { + case DockedMode: + m_dockedMainWindow->saveSettings(settings); + break; + case TopLevelMode: + settings.setToolBarsState(m_mode, m_topLevelData.toolbarManager->saveState(MainWindowBase::settingsVersion())); + settings.setMainWindowState(m_mode, widgetBoxToolWindow()->saveState(MainWindowBase::settingsVersion())); + for (const QDesignerToolWindow *tw : m_toolWindows) + settings.saveGeometryFor(tw); + break; + case NeutralMode: + break; + } +} + +void QDesignerWorkbench::slotFileDropped(const QString &f) +{ + readInForm(f); +} + +bool QDesignerWorkbench::readInForm(const QString &fileName) const +{ + return m_actionManager->readInForm(fileName); +} + +bool QDesignerWorkbench::writeOutForm(QDesignerFormWindowInterface *formWindow, const QString &fileName) const +{ + return m_actionManager->writeOutForm(formWindow, fileName); +} + +bool QDesignerWorkbench::saveForm(QDesignerFormWindowInterface *frm) +{ + return m_actionManager->saveForm(frm); +} + +QDesignerFormWindow *QDesignerWorkbench::findFormWindow(QWidget *widget) const +{ + for (QDesignerFormWindow *formWindow : m_formWindows) { + if (formWindow->editor() == widget) + return formWindow; + } + + return nullptr; +} + +bool QDesignerWorkbench::handleClose() +{ + m_state = StateClosing; + QList dirtyForms; + for (QDesignerFormWindow *w : std::as_const(m_formWindows)) { + if (w->editor()->isDirty()) + dirtyForms << w; + } + + const auto count = dirtyForms.size(); + if (count == 1) { + if (!dirtyForms.at(0)->close()) { + m_state = StateUp; + return false; + } + } else if (count > 1) { + QMessageBox box(QMessageBox::Warning, tr("Save Forms?"), + tr("There are %n forms with unsaved changes." + " Do you want to review these changes before quitting?", "", count), + QMessageBox::Cancel | QMessageBox::Discard | QMessageBox::Save); + box.setInformativeText(tr("If you do not review your documents, all your changes will be lost.")); + box.button(QMessageBox::Discard)->setText(tr("Discard Changes")); + auto *save = static_cast(box.button(QMessageBox::Save)); + save->setText(tr("Review Changes")); + box.setDefaultButton(save); + switch (box.exec()) { + case QMessageBox::Cancel: + m_state = StateUp; + return false; + case QMessageBox::Save: + for (QDesignerFormWindow *fw : std::as_const(dirtyForms)) { + fw->show(); + fw->raise(); + if (!fw->close()) { + m_state = StateUp; + return false; + } + } + break; + case QMessageBox::Discard: + for (QDesignerFormWindow *fw : std::as_const(dirtyForms)) { + fw->editor()->setDirty(false); + fw->setWindowModified(false); + } + break; + } + } + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) + fw->close(); + + saveSettings(); + return true; +} + +QDesignerActions *QDesignerWorkbench::actionManager() const +{ + return m_actionManager; +} + +void QDesignerWorkbench::updateWindowMenu(QDesignerFormWindowInterface *fwi) +{ + bool minimizeChecked = false; + bool minimizeEnabled = false; + QDesignerFormWindow *activeFormWindow = nullptr; + do { + if (!fwi) + break; + activeFormWindow = qobject_cast(fwi->parentWidget()); + if (!activeFormWindow) + break; + + minimizeEnabled = true; + minimizeChecked = isFormWindowMinimized(activeFormWindow); + } while (false) ; + + m_actionManager->minimizeAction()->setEnabled(minimizeEnabled); + m_actionManager->minimizeAction()->setChecked(minimizeChecked); + + for (QDesignerFormWindow *fw : std::as_const(m_formWindows)) + fw->action()->setChecked(fw == activeFormWindow); +} + +void QDesignerWorkbench::formWindowActionTriggered(QAction *a) +{ + auto *fw = qobject_cast(a->parent()); + Q_ASSERT(fw); + + if (isFormWindowMinimized(fw)) + setFormWindowMinimized(fw, false); + + if (m_mode == DockedMode) { + if (auto *subWindow = qobject_cast(fw->parent())) { + m_dockedMainWindow->mdiArea()->setActiveSubWindow(subWindow); + } + } else { + fw->activateWindow(); + fw->raise(); + } +} + +void QDesignerWorkbench::closeAllToolWindows() +{ + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + tw->hide(); +} + +bool QDesignerWorkbench::readInBackup() +{ + const QMap backupFileMap = QDesignerSettings(m_core).backup(); + if (backupFileMap.isEmpty()) + return false; + + const QMessageBox::StandardButton answer = + QMessageBox::question(nullptr, tr("Backup Information"), + tr("The last session of Designer was not terminated correctly. " + "Backup files were left behind. Do you want to load them?"), + QMessageBox::Yes|QMessageBox::No, QMessageBox::Yes); + if (answer == QMessageBox::No) + return false; + + const auto modifiedPlaceHolder = "[*]"_L1; + for (auto it = backupFileMap.cbegin(), end = backupFileMap.cend(); it != end; ++it) { + QString fileName = it.key(); + fileName.remove(modifiedPlaceHolder); + + if(m_actionManager->readInForm(it.value())) + formWindowManager()->activeFormWindow()->setFileName(fileName); + + } + return true; +} + +void QDesignerWorkbench::updateBackup(QDesignerFormWindowInterface* fwi) +{ + QString fwn = QDir::toNativeSeparators(fwi->fileName()); + if (fwn.isEmpty()) + fwn = fwi->parentWidget()->windowTitle(); + + QDesignerSettings settings(m_core); + QMap map = settings.backup(); + map.remove(fwn); + settings.setBackup(map); +} + +namespace { + void raiseWindow(QWidget *w) { + if (w->isMinimized()) + w->setWindowState(w->windowState() & ~Qt::WindowMinimized); + w->raise(); + } +} + +void QDesignerWorkbench::bringAllToFront() +{ + if (m_mode != TopLevelMode) + return; + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + raiseWindow(tw); + for (QDesignerFormWindow *dfw : std::as_const(m_formWindows)) + raiseWindow(dfw); +} + +// Resize a form window taking MDI decorations into account +// Apply maximum size as there is no layout connection between +// the form's main container and the integration's outer +// container due to the tool widget stack. + +void QDesignerWorkbench::resizeForm(QDesignerFormWindow *fw, const QWidget *mainContainer) const +{ + const QSize containerSize = mainContainer->size(); + const QSize containerMaximumSize = mainContainer->maximumSize(); + if (m_mode != DockedMode) { + fw->resize(containerSize); + fw->setMaximumSize(containerMaximumSize); + return; + } + // get decorations and resize MDI + auto *mdiSubWindow = qobject_cast(fw->parent()); + Q_ASSERT(mdiSubWindow); + const QSize decorationSize = mdiSubWindow->geometry().size() - mdiSubWindow->contentsRect().size(); + mdiSubWindow->resize(containerSize + decorationSize); + // In Qt::RightToLeft mode, the window can grow to be partially hidden by the right border. + const int mdiAreaWidth = m_dockedMainWindow->mdiArea()->width(); + if (qApp->layoutDirection() == Qt::RightToLeft && mdiSubWindow->geometry().right() >= mdiAreaWidth) + mdiSubWindow->move(mdiAreaWidth - mdiSubWindow->width(), mdiSubWindow->pos().y()); + + if (containerMaximumSize == QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX)) { + mdiSubWindow->setMaximumSize(containerMaximumSize); + } else { + mdiSubWindow->setMaximumSize(containerMaximumSize + decorationSize); + } +} + + +// Load a form or return 0 and do cleanup. file name and editor file +// name in case of loading a template file. + +QDesignerFormWindow * QDesignerWorkbench::loadForm(const QString &fileName, + bool detectLineTermiantorMode, + QString *errorMessage) +{ + QFile file(fileName); + + qdesigner_internal::FormWindowBase::LineTerminatorMode mode = qdesigner_internal::FormWindowBase::NativeLineTerminator; + + if (detectLineTermiantorMode) { + if (file.open(QFile::ReadOnly)) { + const QString text = QString::fromUtf8(file.readLine()); + file.close(); + + const auto lf = text.indexOf(u'\n'); + if (lf > 0 && text.at(lf - 1) == u'\r') { + mode = qdesigner_internal::FormWindowBase::CRLFLineTerminator; + } else if (lf >= 0) { + mode = qdesigner_internal::FormWindowBase::LFLineTerminator; + } + } + } + + if (!file.open(QFile::ReadOnly|QFile::Text)) { + *errorMessage = tr("The file %1 could not be opened: %2").arg(file.fileName(), file.errorString()); + return nullptr; + } + + // Create a form + QDesignerFormWindowManagerInterface *formWindowManager = m_core->formWindowManager(); + + auto *formWindow = new QDesignerFormWindow(/*formWindow=*/ nullptr, this); + addFormWindow(formWindow); + QDesignerFormWindowInterface *editor = formWindow->editor(); + Q_ASSERT(editor); + + // Temporarily set the file name. It is needed when converting a UIC 3 file. + // In this case, the file name will we be cleared on return to force a save box. + editor->setFileName(fileName); + + if (!editor->setContents(&file, errorMessage)) { + removeFormWindow(formWindow); + formWindowManager->removeFormWindow(editor); + m_core->metaDataBase()->remove(editor); + return nullptr; + } + + if (qdesigner_internal::FormWindowBase *fwb = qobject_cast(editor)) + fwb->setLineTerminatorMode(mode); + + switch (m_mode) { + case DockedMode: { + // below code must be after above call to setContents(), because setContents() may popup warning dialogs which would cause + // mdi sub window activation (because of dialogs internal call to processEvent or such) + // That activation could have worse consequences, e.g. NULL resource set for active form) before the form is loaded + QMdiSubWindow *subWin = m_dockedMainWindow->createMdiSubWindow(formWindow, magicalWindowFlags(formWindow), m_actionManager->closeFormAction()->shortcut()); + m_dockedMainWindow->mdiArea()->setActiveSubWindow(subWin); + } + break; + case TopLevelMode: { + const QRect formWindowGeometryHint = formWindow->geometryHint(); + formWindow->setAttribute(Qt::WA_DeleteOnClose, true); + formWindow->setParent(magicalParent(formWindow), magicalWindowFlags(formWindow)); + formWindow->resize(formWindowGeometryHint.size()); + formWindow->move(availableFormGeometry().center() - formWindowGeometryHint.center()); + } + break; + case NeutralMode: + break; + } + + // Did user specify another (missing) resource path -> set dirty. + const bool dirty = editor->property("_q_resourcepathchanged").toBool(); + editor->setDirty(dirty); + resizeForm(formWindow, editor->mainContainer()); + formWindowManager->setActiveFormWindow(editor); + return formWindow; +} + + +QDesignerFormWindow * QDesignerWorkbench::openForm(const QString &fileName, QString *errorMessage) +{ + QDesignerFormWindow *rc = loadForm(fileName, true, errorMessage); + if (!rc) + return nullptr; + rc->editor()->setFileName(fileName); + rc->firstShow(); + return rc; +} + +QDesignerFormWindow * QDesignerWorkbench::openTemplate(const QString &templateFileName, + const QString &editorFileName, + QString *errorMessage) +{ + QDesignerFormWindow *rc = loadForm(templateFileName, false, errorMessage); + if (!rc) + return nullptr; + + rc->editor()->setFileName(editorFileName); + rc->firstShow(); + return rc; +} + +void QDesignerWorkbench::minimizationStateChanged(QDesignerFormWindowInterface *formWindow, bool minimized) +{ + // refresh the minimize action state + if (core()->formWindowManager()->activeFormWindow() == formWindow) { + m_actionManager->minimizeAction()->setChecked(minimized); + } +} + +void QDesignerWorkbench::toggleFormMinimizationState() +{ + QDesignerFormWindowInterface *fwi = core()->formWindowManager()->activeFormWindow(); + if (!fwi || m_mode == NeutralMode) + return; + QDesignerFormWindow *fw = qobject_cast(fwi->parentWidget()); + Q_ASSERT(fw); + setFormWindowMinimized(fw, !isFormWindowMinimized(fw)); +} + +bool QDesignerWorkbench::isFormWindowMinimized(const QDesignerFormWindow *fw) +{ + switch (m_mode) { + case DockedMode: + return mdiSubWindowOf(fw)->isShaded(); + case TopLevelMode: + return fw->window()->isMinimized(); + default: + break; + } + return fw->isMinimized(); +} + +void QDesignerWorkbench::setFormWindowMinimized(QDesignerFormWindow *fw, bool minimized) +{ + switch (m_mode) { + case DockedMode: { + QMdiSubWindow *mdiSubWindow = mdiSubWindowOf(fw); + if (minimized) { + mdiSubWindow->showShaded(); + } else { + mdiSubWindow->setWindowState(mdiSubWindow->windowState() & ~Qt::WindowMinimized); + } + } + break; + case TopLevelMode: { + QWidget *window = fw->window(); + if (window->isMinimized()) { + window->setWindowState(window->windowState() & ~Qt::WindowMinimized); + } else { + window->showMinimized(); + } + } + break; + default: + break; + } +} + +/* Applies UI mode changes using Timer-0 delayed signal + * signal to make sure the preferences dialog is closed and destroyed + * before a possible switch from docked mode to top-level mode happens. + * (The switch deletes the main window, which the preference dialog is + * a child of -> BOOM) */ + +void QDesignerWorkbench::applyUiSettings() +{ + if (m_uiSettingsChanged) { + m_uiSettingsChanged = false; + QTimer::singleShot(0, this, &QDesignerWorkbench::restoreUISettings); + } +} + +void QDesignerWorkbench::notifyUISettingsChanged() +{ + m_uiSettingsChanged = true; +} + +void QDesignerWorkbench::restoreUISettings() +{ + UIMode mode = QDesignerSettings(m_core).uiMode(); + switch (mode) { + case TopLevelMode: + switchToTopLevelMode(); + break; + case DockedMode: + switchToDockedMode(); + break; + + default: Q_ASSERT(0); + } + + ToolWindowFontSettings fontSettings = QDesignerSettings(m_core).toolWindowFont(); + const QFont &font = fontSettings.m_useFont ? fontSettings.m_font : qApp->font(); + + if (font == m_toolWindows.constFirst()->font()) + return; + + for (QDesignerToolWindow *tw : std::as_const(m_toolWindows)) + tw->setFont(font); +} + +void QDesignerWorkbench::handleCloseEvent(QCloseEvent *ev) +{ + ev->setAccepted(handleClose()); + if (ev->isAccepted()) + QMetaObject::invokeMethod(qDesigner, "quit", Qt::QueuedConnection); // We're going down! +} + +QDesignerToolWindow *QDesignerWorkbench::widgetBoxToolWindow() const +{ + return m_toolWindows.at(QDesignerToolWindow::WidgetBox); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/qdesigner_workbench.h b/src/tools/designer/src/designer/qdesigner_workbench.h new file mode 100644 index 00000000000..171c5b6feb4 --- /dev/null +++ b/src/tools/designer/src/designer/qdesigner_workbench.h @@ -0,0 +1,177 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_WORKBENCH_H +#define QDESIGNER_WORKBENCH_H + +#include "designer_enums.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerActions; +class QDesignerToolWindow; +class QDesignerFormWindow; +class DockedMainWindow; +class QDesignerSettings; + +class QAction; +class QActionGroup; +class QDockWidget; +class QMenu; +class QMenuBar; +class QToolBar; +class QMdiSubWindow; +class QCloseEvent; +class QScreen; +class ToolBarManager; + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QDesignerFormWindowManagerInterface; +class QDesignerIntegration; + +class QDesignerWorkbench: public QObject +{ + Q_OBJECT + +public: + explicit QDesignerWorkbench(const QStringList &pluginPaths); + ~QDesignerWorkbench() override; + + UIMode mode() const; + + QDesignerFormEditorInterface *core() const; + QDesignerFormWindow *findFormWindow(QWidget *widget) const; + + QDesignerFormWindow *openForm(const QString &fileName, QString *errorMessage); + QDesignerFormWindow *openTemplate(const QString &templateFileName, + const QString &editorFileName, + QString *errorMessage); + + int toolWindowCount() const; + QDesignerToolWindow *toolWindow(int index) const; + + int formWindowCount() const; + QDesignerFormWindow *formWindow(int index) const; + + QDesignerActions *actionManager() const; + + QActionGroup *modeActionGroup() const; + + bool readInForm(const QString &fileName) const; + bool writeOutForm(QDesignerFormWindowInterface *formWindow, const QString &fileName) const; + bool saveForm(QDesignerFormWindowInterface *fw); + bool handleClose(); + bool readInBackup(); + void updateBackup(QDesignerFormWindowInterface* fwi); + void applyUiSettings(); + + bool suppressNewFormShow() const { return m_suppressNewFormShow; } + void setSuppressNewFormShow(bool v) { m_suppressNewFormShow = v; } + +signals: + void modeChanged(UIMode mode); + void initialized(); + +public slots: + void addFormWindow(QDesignerFormWindow *formWindow); + void removeFormWindow(QDesignerFormWindow *formWindow); + void bringAllToFront(); + void toggleFormMinimizationState(); + void showNewForm(); + +private slots: + void switchToNeutralMode(); + void switchToDockedMode(); + void switchToTopLevelMode(); + void initializeCorePlugins(); + void handleCloseEvent(QCloseEvent *); + void slotFormWindowActivated(QDesignerFormWindow* fw); + void updateWindowMenu(QDesignerFormWindowInterface *fw); + void formWindowActionTriggered(QAction *a); + void adjustMDIFormPositions(); + void minimizationStateChanged(QDesignerFormWindowInterface *formWindow, bool minimized); + + void restoreUISettings(); + void notifyUISettingsChanged(); + void slotFileDropped(const QString &f); + +private: + QScreen *screen() const; + QRect availableFormGeometry() const; + QWidget *magicalParent(const QWidget *w) const; + Qt::WindowFlags magicalWindowFlags(const QWidget *widgetForFlags) const; + QDesignerFormWindowManagerInterface *formWindowManager() const; + void closeAllToolWindows(); + QDesignerToolWindow *widgetBoxToolWindow() const; + QDesignerFormWindow *loadForm(const QString &fileName, bool detectLineTermiantorMode, QString *errorMessage); + void resizeForm(QDesignerFormWindow *fw, const QWidget *mainContainer) const; + void saveGeometriesForModeChange(); + void saveGeometries(QDesignerSettings &settings) const; + + bool isFormWindowMinimized(const QDesignerFormWindow *fw); + void setFormWindowMinimized(QDesignerFormWindow *fw, bool minimized); + void saveSettings() const; + + QDesignerFormEditorInterface *m_core; + QDesignerIntegration *m_integration; + + QDesignerActions *m_actionManager; + QActionGroup *m_windowActions; + + QMenu *m_windowMenu; + + QPointer m_globalMenuBar; + + struct TopLevelData { + ToolBarManager *toolbarManager; + QList toolbars; + }; + TopLevelData m_topLevelData; + + UIMode m_mode = NeutralMode; + QPointer m_dockedMainWindow; + + QList m_toolWindows; + QList m_formWindows; + + QMenu *m_toolbarMenu; + + // Helper class to remember the position of a window while switching user + // interface modes. + class Position { + public: + explicit Position(const QDockWidget *dockWidget); + explicit Position(const QMdiSubWindow *mdiSubWindow); + explicit Position(const QWidget *topLevelWindow); + + void applyTo(QMdiSubWindow *mdiSubWindow, const QPoint &mdiAreaOffset) const; + void applyTo(QWidget *topLevelWindow, const QPoint &desktopTopLeft) const; + void applyTo(QDockWidget *dockWidget) const; + + QPoint position() const { return m_position; } + private: + bool m_minimized; + // Position referring to top-left corner (desktop in top-level mode or + // main window in MDI mode) + QPoint m_position; + }; + using PositionMap = QHash; + PositionMap m_Positions; + + enum State { StateInitializing, StateUp, StateClosing }; + State m_state = StateInitializing; + bool m_uiSettingsChanged = false; // UI mode changed in preference dialog, trigger delayed slot. + bool m_suppressNewFormShow = false; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_WORKBENCH_H diff --git a/src/tools/designer/src/designer/saveformastemplate.cpp b/src/tools/designer/src/designer/saveformastemplate.cpp new file mode 100644 index 00000000000..e8bc64477d1 --- /dev/null +++ b/src/tools/designer/src/designer/saveformastemplate.cpp @@ -0,0 +1,135 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "saveformastemplate.h" +#include "qdesigner_settings.h" + +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +SaveFormAsTemplate::SaveFormAsTemplate(QDesignerFormEditorInterface *core, + QDesignerFormWindowInterface *formWindow, + QWidget *parent) + : QDialog(parent, Qt::Sheet), + m_core(core), + m_formWindow(formWindow) +{ + ui.setupUi(this); + + ui.templateNameEdit->setText(formWindow->mainContainer()->objectName()); + ui.templateNameEdit->selectAll(); + + ui.templateNameEdit->setFocus(); + + QStringList paths = QDesignerSettings(m_core).formTemplatePaths(); + ui.categoryCombo->addItems(paths); + ui.categoryCombo->addItem(tr("Add path...")); + m_addPathIndex = ui.categoryCombo->count() - 1; + connect(ui.templateNameEdit, &QLineEdit::textChanged, + this, &SaveFormAsTemplate::updateOKButton); + connect(ui.categoryCombo, &QComboBox::activated, + this, &SaveFormAsTemplate::checkToAddPath); +} + +SaveFormAsTemplate::~SaveFormAsTemplate() = default; + +void SaveFormAsTemplate::accept() +{ + const QString name = ui.templateNameEdit->text(); + QString templateFileName = ui.categoryCombo->currentText() + u'/' + name; + const auto extension = ".ui"_L1; + if (!templateFileName.endsWith(extension)) + templateFileName.append(extension); + QFile file(templateFileName); + + if (file.exists()) { + QMessageBox msgBox(QMessageBox::Information, tr("Template Exists"), + tr("A template with the name %1 already exists.\n" + "Do you want overwrite the template?").arg(name), QMessageBox::Cancel, m_formWindow); + msgBox.setDefaultButton(QMessageBox::Cancel); + QPushButton *overwriteButton = msgBox.addButton(tr("Overwrite Template"), QMessageBox::AcceptRole); + msgBox.exec(); + if (msgBox.clickedButton() != overwriteButton) + return; + } + + while (!file.open(QFile::WriteOnly)) { + if (QMessageBox::information(m_formWindow, tr("Open Error"), + tr("There was an error opening template %1 for writing. Reason: %2") + .arg(name, file.errorString()), + QMessageBox::Retry|QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Cancel) { + return; + } + } + + const QString origName = m_formWindow->fileName(); + // ensure m_formWindow->contents() will convert properly resource paths to relative paths + // (relative to template location, not to the current form location) + m_formWindow->setFileName(templateFileName); + QByteArray ba = m_formWindow->contents().toUtf8(); + m_formWindow->setFileName(origName); + while (file.write(ba) != ba.size()) { + if (QMessageBox::information(m_formWindow, tr("Write Error"), + tr("There was an error writing the template %1 to disk. Reason: %2") + .arg(name, file.errorString()), + QMessageBox::Retry|QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Cancel) { + file.close(); + file.remove(); + return; + } + file.reset(); + } + // update the list of places too... + QStringList sl; + for (int i = 0; i < m_addPathIndex; ++i) + sl << ui.categoryCombo->itemText(i); + + QDesignerSettings(m_core).setFormTemplatePaths(sl); + + QDialog::accept(); +} + +void SaveFormAsTemplate::updateOKButton(const QString &str) +{ + QPushButton *okButton = ui.buttonBox->button(QDialogButtonBox::Ok); + okButton->setEnabled(!str.isEmpty()); +} + +QString SaveFormAsTemplate::chooseTemplatePath(QWidget *parent) +{ + QString rc = QFileDialog::getExistingDirectory(parent, + tr("Pick a directory to save templates in")); + if (rc.isEmpty()) + return rc; + + if (rc.endsWith(QDir::separator())) + rc.remove(rc.size() - 1, 1); + return rc; +} + +void SaveFormAsTemplate::checkToAddPath(int itemIndex) +{ + if (itemIndex != m_addPathIndex) + return; + + const QString dir = chooseTemplatePath(this); + if (dir.isEmpty()) { + ui.categoryCombo->setCurrentIndex(0); + return; + } + + ui.categoryCombo->insertItem(m_addPathIndex, dir); + ui.categoryCombo->setCurrentIndex(m_addPathIndex); + ++m_addPathIndex; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/designer/saveformastemplate.h b/src/tools/designer/src/designer/saveformastemplate.h new file mode 100644 index 00000000000..2a76306d8be --- /dev/null +++ b/src/tools/designer/src/designer/saveformastemplate.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SAVEFORMASTEMPLATE_H +#define SAVEFORMASTEMPLATE_H + +#include "ui_saveformastemplate.h" + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class SaveFormAsTemplate: public QDialog +{ + Q_OBJECT +public: + explicit SaveFormAsTemplate(QDesignerFormEditorInterface *m_core, + QDesignerFormWindowInterface *formWindow, + QWidget *parent = nullptr); + ~SaveFormAsTemplate() override; + +private slots: + void accept() override; + void updateOKButton(const QString &str); + void checkToAddPath(int itemIndex); + +private: + static QString chooseTemplatePath(QWidget *parent); + + Ui::SaveFormAsTemplate ui; + QDesignerFormEditorInterface *m_core; + QDesignerFormWindowInterface *m_formWindow; + int m_addPathIndex; +}; + +QT_END_NAMESPACE + +#endif // SAVEFORMASTEMPLATE_H diff --git a/src/tools/designer/src/designer/saveformastemplate.ui b/src/tools/designer/src/designer/saveformastemplate.ui new file mode 100644 index 00000000000..e77e4c2461f --- /dev/null +++ b/src/tools/designer/src/designer/saveformastemplate.ui @@ -0,0 +1,130 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + SaveFormAsTemplate + + + Save Form As Template + + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + &Name: + + + Qt::AutoText + + + templateNameEdit + + + + + + + + 222 + 0 + + + + + + + QLineEdit::Normal + + + + + + + QFrame::NoFrame + + + QFrame::Plain + + + &Category: + + + Qt::AutoText + + + categoryCombo + + + + + + + + + + + + QFrame::HLine + + + QFrame::Sunken + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SaveFormAsTemplate + accept() + + + 256 + 124 + + + 113 + 143 + + + + + buttonBox + rejected() + SaveFormAsTemplate + reject() + + + 332 + 127 + + + 372 + 147 + + + + + diff --git a/src/tools/designer/src/designer/uifile.icns b/src/tools/designer/src/designer/uifile.icns new file mode 100644 index 00000000000..f9d84d506ad Binary files /dev/null and b/src/tools/designer/src/designer/uifile.icns differ diff --git a/src/tools/designer/src/designer/versiondialog.cpp b/src/tools/designer/src/designer/versiondialog.cpp new file mode 100644 index 00000000000..c9a77e145ee --- /dev/null +++ b/src/tools/designer/src/designer/versiondialog.cpp @@ -0,0 +1,159 @@ +// Copyright (C) 2020 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "versiondialog.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class VersionLabel : public QLabel +{ + Q_OBJECT +public: + VersionLabel(QWidget *parent = nullptr); + +signals: + void triggered(); + +protected: + void mousePressEvent(QMouseEvent *me) override; + void mouseMoveEvent(QMouseEvent *me) override; + void mouseReleaseEvent(QMouseEvent *me) override; + void paintEvent(QPaintEvent *pe) override; +private: + QList hitPoints; + QList missPoints; + QPainterPath m_path; + bool secondStage = false; + bool m_pushed = false; +}; + +VersionLabel::VersionLabel(QWidget *parent) + : QLabel(parent) +{ + QPixmap pixmap(u":/qt-project.org/designer/images/designer.png"_s); + pixmap.setDevicePixelRatio(devicePixelRatioF()); + setPixmap(pixmap); + hitPoints.append(QPoint(56, 25)); + hitPoints.append(QPoint(29, 55)); + hitPoints.append(QPoint(56, 87)); + hitPoints.append(QPoint(82, 55)); + hitPoints.append(QPoint(58, 56)); + + secondStage = false; + m_pushed = false; +} + +void VersionLabel::mousePressEvent(QMouseEvent *me) +{ + if (me->button() == Qt::LeftButton) { + if (!secondStage) { + m_path = QPainterPath(me->pos()); + } else { + m_pushed = true; + update(); + } + } +} + +void VersionLabel::mouseMoveEvent(QMouseEvent *me) +{ + if (me->buttons() & Qt::LeftButton) + if (!secondStage) + m_path.lineTo(me->pos()); +} + +void VersionLabel::mouseReleaseEvent(QMouseEvent *me) +{ + if (me->button() == Qt::LeftButton) { + if (!secondStage) { + m_path.lineTo(me->pos()); + bool gotIt = true; + for (const QPoint &pt : std::as_const(hitPoints)) { + if (!m_path.contains(pt)) { + gotIt = false; + break; + } + } + if (gotIt) { + for (const QPoint &pt : std::as_const(missPoints)) { + if (m_path.contains(pt)) { + gotIt = false; + break; + } + } + } + if (gotIt && !secondStage) { + secondStage = true; + m_path = QPainterPath(); + update(); + } + } else { + m_pushed = false; + update(); + emit triggered(); + } + } +} + +void VersionLabel::paintEvent(QPaintEvent *pe) +{ + if (secondStage) { + QPainter p(this); + QStyleOptionButton opt; + opt.initFrom(this); + if (!m_pushed) + opt.state |= QStyle::State_Raised; + else + opt.state |= QStyle::State_Sunken; + opt.state &= ~QStyle::State_HasFocus; + style()->drawControl(QStyle::CE_PushButtonBevel, &opt, &p, this); + } + QLabel::paintEvent(pe); +} + +VersionDialog::VersionDialog(QWidget *parent) + : QDialog(parent +#ifdef Q_OS_MACOS + , Qt::Tool +#endif + ) +{ + setWindowFlag(Qt::MSWindowsFixedSizeDialogHint, true); + QGridLayout *layout = new QGridLayout(this); + VersionLabel *label = new VersionLabel(this); + QLabel *lbl = new QLabel(this); + QString version = tr("

%1



Version %2"); + version = version.arg(tr("Qt Widgets Designer")).arg(QLatin1StringView(QT_VERSION_STR)); + version.append(tr("
Qt Widgets Designer is a graphical user interface designer for Qt applications.
")); + + lbl->setText( + tr("%1
Copyright (C) The Qt Company Ltd. and other contributors.").arg(version)); + + lbl->setWordWrap(true); + lbl->setOpenExternalLinks(true); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close, this); + connect(buttonBox , &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(label, &VersionLabel::triggered, this, &QDialog::accept); + layout->addWidget(label, 0, 0, 1, 1); + layout->addWidget(lbl, 0, 1, 4, 4); + layout->addWidget(buttonBox, 4, 2, 1, 1); +} + +QT_END_NAMESPACE + +#include "versiondialog.moc" diff --git a/src/tools/designer/src/designer/versiondialog.h b/src/tools/designer/src/designer/versiondialog.h new file mode 100644 index 00000000000..4f7f8e4c361 --- /dev/null +++ b/src/tools/designer/src/designer/versiondialog.h @@ -0,0 +1,20 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef VERSIONDIALOG_H +#define VERSIONDIALOG_H + +#include + +QT_BEGIN_NAMESPACE + +class VersionDialog : public QDialog +{ + Q_OBJECT +public: + explicit VersionDialog(QWidget *parent); +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/designer/src/lib/CMakeLists.txt b/src/tools/designer/src/lib/CMakeLists.txt new file mode 100644 index 00000000000..2401109f7b8 --- /dev/null +++ b/src/tools/designer/src/lib/CMakeLists.txt @@ -0,0 +1,485 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: BSD-3-Clause + + +##################################################################### +## Designer Module: +##################################################################### + +qt_internal_add_module(Designer + PLUGIN_TYPES designer + SOURCES + ../../../../shared/deviceskin/deviceskin.cpp ../../../../shared/deviceskin/deviceskin_p.h + ../../../../shared/findwidget/abstractfindwidget.cpp ../../../../shared/findwidget/abstractfindwidget_p.h + ../../../../shared/findwidget/itemviewfindwidget.cpp ../../../../shared/findwidget/itemviewfindwidget_p.h + ../../../../shared/findwidget/texteditfindwidget.cpp ../../../../shared/findwidget/texteditfindwidget_p.h + ../../../../shared/qtgradienteditor/qtcolorbutton.cpp ../../../../shared/qtgradienteditor/qtcolorbutton_p.h + ../../../../shared/qtgradienteditor/qtcolorline.cpp ../../../../shared/qtgradienteditor/qtcolorline_p.h + ../../../../shared/qtgradienteditor/qtgradientdialog.cpp ../../../../shared/qtgradienteditor/qtgradientdialog_p.h + ../../../../shared/qtgradienteditor/qtgradienteditor.cpp ../../../../shared/qtgradienteditor/qtgradienteditor_p.h + ../../../../shared/qtgradienteditor/qtgradientmanager.cpp ../../../../shared/qtgradienteditor/qtgradientmanager_p.h + ../../../../shared/qtgradienteditor/qtgradientstopscontroller.cpp ../../../../shared/qtgradienteditor/qtgradientstopscontroller_p.h + ../../../../shared/qtgradienteditor/qtgradientstopsmodel.cpp ../../../../shared/qtgradienteditor/qtgradientstopsmodel_p.h + ../../../../shared/qtgradienteditor/qtgradientstopswidget.cpp ../../../../shared/qtgradienteditor/qtgradientstopswidget_p.h + ../../../../shared/qtgradienteditor/qtgradientutils.cpp ../../../../shared/qtgradienteditor/qtgradientutils_p.h + ../../../../shared/qtgradienteditor/qtgradientview.cpp ../../../../shared/qtgradienteditor/qtgradientview_p.h + ../../../../shared/qtgradienteditor/qtgradientviewdialog.cpp ../../../../shared/qtgradienteditor/qtgradientviewdialog_p.h + ../../../../shared/qtgradienteditor/qtgradientwidget.cpp ../../../../shared/qtgradienteditor/qtgradientwidget_p.h + extension/default_extensionfactory.cpp extension/default_extensionfactory.h + extension/extension_global.h + extension/extension.cpp extension/extension.h + extension/qextensionmanager.cpp extension/qextensionmanager.h + sdk/abstractactioneditor.cpp sdk/abstractactioneditor.h + sdk/abstractdialoggui.cpp sdk/abstractdialoggui_p.h + sdk/abstractdnditem.h + sdk/abstractformeditor.cpp sdk/abstractformeditor.h + sdk/abstractformeditorplugin.cpp sdk/abstractformeditorplugin.h + sdk/abstractformwindow.cpp sdk/abstractformwindow.h + sdk/abstractformwindowcursor.cpp sdk/abstractformwindowcursor.h + sdk/abstractformwindowmanager.cpp sdk/abstractformwindowmanager.h + sdk/abstractformwindowtool.cpp sdk/abstractformwindowtool.h + sdk/abstractintegration.cpp sdk/abstractintegration.h + sdk/abstractintrospection.cpp sdk/abstractintrospection_p.h + sdk/abstractlanguage.h + sdk/abstractmetadatabase.cpp sdk/abstractmetadatabase.h + sdk/abstractnewformwidget.cpp sdk/abstractnewformwidget.h + sdk/abstractobjectinspector.cpp sdk/abstractobjectinspector.h + sdk/abstractoptionspage.h + sdk/abstractpromotioninterface.cpp sdk/abstractpromotioninterface.h + sdk/abstractpropertyeditor.cpp sdk/abstractpropertyeditor.h + sdk/abstractresourcebrowser.cpp sdk/abstractresourcebrowser.h + sdk/abstractsettings.h + sdk/abstractwidgetbox.cpp sdk/abstractwidgetbox.h + sdk/abstractwidgetdatabase.cpp sdk/abstractwidgetdatabase.h + sdk/abstractwidgetfactory.cpp sdk/abstractwidgetfactory.h + sdk/container.h + sdk/dynamicpropertysheet.h + sdk/extrainfo.cpp sdk/extrainfo.h + sdk/layoutdecoration.h + sdk/membersheet.h + sdk/propertysheet.h + sdk/sdk_global.h + sdk/taskmenu.cpp sdk/taskmenu.h + shared/actioneditor.cpp shared/actioneditor_p.h + shared/actionprovider_p.h + shared/actionrepository.cpp shared/actionrepository_p.h + shared/codedialog.cpp shared/codedialog_p.h + shared/connectionedit.cpp shared/connectionedit_p.h + shared/csshighlighter.cpp shared/csshighlighter_p.h + shared/deviceprofile.cpp shared/deviceprofile_p.h + shared/dialoggui.cpp shared/dialoggui_p.h + shared/extensionfactory_p.h + shared/formlayoutmenu.cpp shared/formlayoutmenu_p.h + shared/formwindowbase.cpp shared/formwindowbase_p.h + shared/grid.cpp shared/grid_p.h + shared/gridpanel.cpp shared/gridpanel_p.h + shared/htmlhighlighter.cpp shared/htmlhighlighter_p.h + shared/iconloader.cpp shared/iconloader_p.h + shared/iconselector.cpp shared/iconselector_p.h + shared/invisible_widget.cpp shared/invisible_widget_p.h + shared/layout.cpp shared/layout_p.h + shared/layoutinfo.cpp shared/layoutinfo_p.h + shared/metadatabase.cpp shared/metadatabase_p.h + shared/morphmenu.cpp shared/morphmenu_p.h + shared/newactiondialog.cpp shared/newactiondialog_p.h + shared/newformwidget.cpp shared/newformwidget_p.h + shared/orderdialog.cpp shared/orderdialog_p.h + shared/plaintexteditor.cpp shared/plaintexteditor_p.h + shared/plugindialog.cpp shared/plugindialog_p.h + shared/pluginmanager.cpp shared/pluginmanager_p.h + shared/previewconfigurationwidget.cpp shared/previewconfigurationwidget_p.h + shared/previewmanager.cpp shared/previewmanager_p.h + shared/promotionmodel.cpp shared/promotionmodel_p.h + shared/promotiontaskmenu.cpp shared/promotiontaskmenu_p.h + shared/propertylineedit.cpp shared/propertylineedit_p.h + shared/qdesigner_command.cpp shared/qdesigner_command_p.h + shared/qdesigner_command2.cpp shared/qdesigner_command2_p.h + shared/qdesigner_dnditem.cpp shared/qdesigner_dnditem_p.h + shared/qdesigner_dockwidget.cpp shared/qdesigner_dockwidget_p.h + shared/qdesigner_formbuilder.cpp shared/qdesigner_formbuilder_p.h + shared/qdesigner_formeditorcommand.cpp shared/qdesigner_formeditorcommand_p.h + shared/qdesigner_formwindowcommand.cpp shared/qdesigner_formwindowcommand_p.h + shared/qdesigner_formwindowmanager.cpp shared/qdesigner_formwindowmanager_p.h + shared/qdesigner_introspection.cpp shared/qdesigner_introspection_p.h + shared/qdesigner_membersheet.cpp shared/qdesigner_membersheet_p.h + shared/qdesigner_menu.cpp shared/qdesigner_menu_p.h + shared/qdesigner_menubar.cpp shared/qdesigner_menubar_p.h + shared/qdesigner_objectinspector.cpp shared/qdesigner_objectinspector_p.h + shared/qdesigner_promotion.cpp shared/qdesigner_promotion_p.h + shared/qdesigner_promotiondialog.cpp shared/qdesigner_promotiondialog_p.h + shared/qdesigner_propertycommand.cpp + shared/qdesigner_propertyeditor.cpp shared/qdesigner_propertyeditor_p.h + shared/qdesigner_propertysheet.cpp shared/qdesigner_propertysheet_p.h + shared/qdesigner_qsettings.cpp shared/qdesigner_qsettings_p.h + shared/qdesigner_stackedbox.cpp shared/qdesigner_stackedbox_p.h + shared/qdesigner_tabwidget.cpp shared/qdesigner_tabwidget_p.h + shared/qdesigner_taskmenu.cpp shared/qdesigner_taskmenu_p.h + shared/qdesigner_toolbar.cpp shared/qdesigner_toolbar_p.h + shared/qdesigner_toolbox.cpp shared/qdesigner_toolbox_p.h + shared/qdesigner_utils.cpp shared/qdesigner_utils_p.h + shared/qdesigner_widget.cpp shared/qdesigner_widget_p.h + shared/qdesigner_widgetbox.cpp shared/qdesigner_widgetbox_p.h + shared/qdesigner_widgetitem.cpp shared/qdesigner_widgetitem_p.h + shared/qlayout_widget.cpp shared/qlayout_widget_p.h + shared/qsimpleresource.cpp shared/qsimpleresource_p.h + shared/qtresourceeditordialog.cpp shared/qtresourceeditordialog_p.h + shared/qtresourcemodel.cpp shared/qtresourcemodel_p.h + shared/qtresourceview.cpp shared/qtresourceview_p.h + shared/rcc.cpp shared/rcc_p.h + shared/richtexteditor.cpp shared/richtexteditor_p.h + shared/selectsignaldialog.cpp shared/selectsignaldialog_p.h + shared/shared_enums_p.h + shared/shared_global_p.h + shared/shared_settings.cpp shared/shared_settings_p.h + shared/sheet_delegate.cpp shared/sheet_delegate_p.h + shared/signalslotdialog.cpp shared/signalslotdialog_p.h + shared/spacer_widget.cpp shared/spacer_widget_p.h + shared/stylesheeteditor.cpp shared/stylesheeteditor_p.h + shared/textpropertyeditor.cpp shared/textpropertyeditor_p.h + shared/widgetdatabase.cpp shared/widgetdatabase_p.h + shared/widgetfactory.cpp shared/widgetfactory_p.h + shared/zoomwidget.cpp shared/zoomwidget_p.h + uilib/uilib_global.h + uilib/abstractformbuilder.cpp uilib/abstractformbuilder.h + uilib/formbuilder.cpp uilib/formbuilder.h + uilib/formbuilderextra.cpp uilib/formbuilderextra_p.h + uilib/properties.cpp uilib/properties_p.h + uilib/resourcebuilder.cpp uilib/resourcebuilder_p.h + uilib/textbuilder.cpp uilib/textbuilder_p.h + uilib/ui4.cpp uilib/ui4_p.h + components/qdesigner_components.h + components/qdesigner_components_global.h + NO_UNITY_BUILD_SOURCES + shared/qdesigner_command.cpp # redefinition of 'QMetaTypeId>' (from morphmenu.cpp) + # and recursiveUpdate (from formwindowbase.cpp) + uilib/abstractformbuilder.cpp # using namespace QFormInternal/redefinition of 'QMetaTypeId>' (from morphmenu.cpp) + uilib/ui4.cpp # using namespace QFormInternal + DEFINES + QDESIGNER_EXTENSION_LIBRARY + QDESIGNER_SDK_LIBRARY + QDESIGNER_SHARED_LIBRARY + QDESIGNER_UILIB_LIBRARY + QT_DESIGNER + QT_USE_QSTRINGBUILDER + INCLUDE_DIRECTORIES + ../../../../shared/deviceskin + ../../../../shared/findwidget + ../../../../shared/qtgradienteditor + extension + sdk + shared + uilib + LIBRARIES + Qt::CorePrivate + Qt::GuiPrivate + Qt::UiPlugin + Qt::WidgetsPrivate + PUBLIC_LIBRARIES + Qt::Core + Qt::Gui + Qt::UiPlugin + Qt::Widgets + Qt::Xml + PRIVATE_MODULE_INTERFACE + Qt::CorePrivate + Qt::GuiPrivate + Qt::WidgetsPrivate + ENABLE_AUTOGEN_TOOLS + uic + PRECOMPILED_HEADER + "lib_pch.h" + NO_GENERATE_CPP_EXPORTS +) + +set(ui_sources + ../../../../shared/qtgradienteditor/qtgradientdialog.ui + ../../../../shared/qtgradienteditor/qtgradienteditor.ui + ../../../../shared/qtgradienteditor/qtgradientview.ui + ../../../../shared/qtgradienteditor/qtgradientviewdialog.ui + shared/addlinkdialog.ui + shared/formlayoutrowdialog.ui + shared/gridpanel.ui + shared/previewconfigurationwidget.ui + shared/newactiondialog.ui + shared/newformwidget.ui + shared/orderdialog.ui + shared/plugindialog.ui + shared/qtresourceeditordialog.ui + shared/selectsignaldialog.ui + shared/signalslotdialog.ui +) + +# Work around QTBUG-95305 +if(CMAKE_GENERATOR STREQUAL "Ninja Multi-Config" AND CMAKE_CROSS_CONFIGS) + qt6_wrap_ui(ui_sources_processed ${ui_sources}) +else() + set(ui_sources_processed ${ui_sources}) +endif() +target_sources(Designer PRIVATE ${ui_sources_processed}) + +# Resources: +set(ClamshellPhone_resource_files + "../../../../shared/deviceskin/skins/ClamshellPhone.skin" +) + +qt_internal_add_resource(Designer "ClamshellPhone" + PREFIX + "/skins" + BASE + "../../../../shared/deviceskin/skins" + FILES + ${ClamshellPhone_resource_files} +) +set(SmartPhone2_resource_files + "../../../../shared/deviceskin/skins/SmartPhone2.skin" +) + +qt_internal_add_resource(Designer "SmartPhone2" + PREFIX + "/skins" + BASE + "../../../../shared/deviceskin/skins" + FILES + ${SmartPhone2_resource_files} +) +set(SmartPhone_resource_files + "../../../../shared/deviceskin/skins/SmartPhone.skin" +) + +qt_internal_add_resource(Designer "SmartPhone" + PREFIX + "/skins" + BASE + "../../../../shared/deviceskin/skins" + FILES + ${SmartPhone_resource_files} +) +set(SmartPhoneWithButtons_resource_files + "../../../../shared/deviceskin/skins/SmartPhoneWithButtons.skin" +) + +qt_internal_add_resource(Designer "SmartPhoneWithButtons" + PREFIX + "/skins" + BASE + "../../../../shared/deviceskin/skins" + FILES + ${SmartPhoneWithButtons_resource_files} +) +set(TouchscreenPhone_resource_files + "../../../../shared/deviceskin/skins/TouchscreenPhone.skin" +) + +qt_internal_add_resource(Designer "TouchscreenPhone" + PREFIX + "/skins" + BASE + "../../../../shared/deviceskin/skins" + FILES + ${TouchscreenPhone_resource_files} +) +set(PortableMedia_resource_files + "../../../../shared/deviceskin/skins/PortableMedia.skin" +) + +qt_internal_add_resource(Designer "PortableMedia" + PREFIX + "/skins" + BASE + "../../../../shared/deviceskin/skins" + FILES + ${PortableMedia_resource_files} +) +set(S60-QVGA-Candybar_resource_files + "../../../../shared/deviceskin/skins/S60-QVGA-Candybar.skin" +) + +qt_internal_add_resource(Designer "S60-QVGA-Candybar" + PREFIX + "/skins" + BASE + "../../../../shared/deviceskin/skins" + FILES + ${S60-QVGA-Candybar_resource_files} +) +set(S60-nHD-Touchscreen_resource_files + "../../../../shared/deviceskin/skins/S60-nHD-Touchscreen.skin" +) + +qt_internal_add_resource(Designer "S60-nHD-Touchscreen" + PREFIX + "/skins" + BASE + "../../../../shared/deviceskin/skins" + FILES + ${S60-nHD-Touchscreen_resource_files} +) +set(findwidget_resource_files + "../../../../shared/findwidget/images/mac/closetab.png" + "../../../../shared/findwidget/images/mac/next.png" + "../../../../shared/findwidget/images/mac/previous.png" + "../../../../shared/findwidget/images/mac/searchfind.png" + "../../../../shared/findwidget/images/win/closetab.png" + "../../../../shared/findwidget/images/win/next.png" + "../../../../shared/findwidget/images/win/previous.png" + "../../../../shared/findwidget/images/win/searchfind.png" + "../../../../shared/findwidget/images/wrap.png" +) + +qt_internal_add_resource(Designer "findwidget" + PREFIX + "/qt-project.org/shared" + BASE + "../../../../shared/findwidget" + FILES + ${findwidget_resource_files} +) +set(qtgradienteditor_resource_files + "../../../../shared/qtgradienteditor/images/down.png" + "../../../../shared/qtgradienteditor/images/edit.png" + "../../../../shared/qtgradienteditor/images/editdelete.png" + "../../../../shared/qtgradienteditor/images/minus.png" + "../../../../shared/qtgradienteditor/images/plus.png" + "../../../../shared/qtgradienteditor/images/spreadpad.png" + "../../../../shared/qtgradienteditor/images/spreadreflect.png" + "../../../../shared/qtgradienteditor/images/spreadrepeat.png" + "../../../../shared/qtgradienteditor/images/typeconical.png" + "../../../../shared/qtgradienteditor/images/typelinear.png" + "../../../../shared/qtgradienteditor/images/typeradial.png" + "../../../../shared/qtgradienteditor/images/up.png" + "../../../../shared/qtgradienteditor/images/zoomin.png" + "../../../../shared/qtgradienteditor/images/zoomout.png" +) + +qt_internal_add_resource(Designer "qtgradienteditor" + PREFIX + "/qt-project.org/qtgradienteditor" + BASE + "../../../../shared/qtgradienteditor" + FILES + ${qtgradienteditor_resource_files} +) +set(shared_resource_files + "shared/defaultgradients.xml" + "shared/icon-naming-spec.txt" + "shared/templates/forms/240x320/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/240x320/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/320x240/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/320x240/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/480x640/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/480x640/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/640x480/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/640x480/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/Dialog_with_Buttons_Bottom.ui" + "shared/templates/forms/Dialog_with_Buttons_Right.ui" + "shared/templates/forms/Dialog_without_Buttons.ui" + "shared/templates/forms/Main_Window.ui" + "shared/templates/forms/Widget.ui" +) + +qt_internal_add_resource(Designer "shared" + PREFIX + "/qt-project.org/designer" + BASE + "shared" + FILES + ${shared_resource_files} +) + +# MODULE = "designer" + +## Scopes: +##################################################################### + +qt_internal_extend_target(Designer CONDITION TARGET Qt::OpenGLWidgets + PUBLIC_LIBRARIES + Qt::OpenGLWidgets +) + +qt_internal_extend_target(Designer CONDITION NOT QT_BUILD_SHARED_LIBS + DEFINES + QT_DESIGNER_STATIC + SOURCES + ../../../../shared/qtpropertybrowser/qtbuttonpropertybrowser.cpp ../../../../shared/qtpropertybrowser/qtbuttonpropertybrowser_p.h + ../../../../shared/qtpropertybrowser/qteditorfactory.cpp ../../../../shared/qtpropertybrowser/qteditorfactory_p.h + ../../../../shared/qtpropertybrowser/qtgroupboxpropertybrowser.cpp ../../../../shared/qtpropertybrowser/qtgroupboxpropertybrowser_p.h + ../../../../shared/qtpropertybrowser/qtpropertybrowser.cpp ../../../../shared/qtpropertybrowser/qtpropertybrowser_p.h + ../../../../shared/qtpropertybrowser/qtpropertybrowserutils.cpp ../../../../shared/qtpropertybrowser/qtpropertybrowserutils_p.h + ../../../../shared/qtpropertybrowser/qtpropertymanager.cpp ../../../../shared/qtpropertybrowser/qtpropertymanager_p.h + ../../../../shared/qtpropertybrowser/qttreepropertybrowser.cpp ../../../../shared/qtpropertybrowser/qttreepropertybrowser_p.h + ../../../../shared/qtpropertybrowser/qtvariantproperty.cpp ../../../../shared/qtpropertybrowser/qtvariantproperty_p.h + INCLUDE_DIRECTORIES + ../../../../shared/qtpropertybrowser +) + +if(TARGET zstd::libzstd) + qt_internal_disable_find_package_global_promotion(zstd::libzstd) +endif() +if(TARGET zstd::libzstd_shared) + qt_internal_disable_find_package_global_promotion(zstd::libzstd_shared) +endif() +if(TARGET zstd::libzstd_static) + qt_internal_disable_find_package_global_promotion(zstd::libzstd_static) +endif() +if(NOT TARGET WrapZSTD::WrapZSTD) + qt_find_package(WrapZSTD 1.3 + PROVIDED_TARGETS + WrapZSTD::WrapZSTD + zstd::libzstd + zstd::libzstd_static + zstd::libzstd_shared + ) +endif() + +qt_internal_extend_target(Designer CONDITION QT_FEATURE_zstd + LIBRARIES + WrapZSTD::WrapZSTD +) + +if(NOT QT_BUILD_SHARED_LIBS) + # Resources: + set(qtpropertybrowser_resource_files + "../../../../shared/qtpropertybrowser/images/cursor-arrow.png" + "../../../../shared/qtpropertybrowser/images/cursor-busy.png" + "../../../../shared/qtpropertybrowser/images/cursor-closedhand.png" + "../../../../shared/qtpropertybrowser/images/cursor-cross.png" + "../../../../shared/qtpropertybrowser/images/cursor-forbidden.png" + "../../../../shared/qtpropertybrowser/images/cursor-hand.png" + "../../../../shared/qtpropertybrowser/images/cursor-hsplit.png" + "../../../../shared/qtpropertybrowser/images/cursor-ibeam.png" + "../../../../shared/qtpropertybrowser/images/cursor-openhand.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizeall.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizeb.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizef.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizeh.png" + "../../../../shared/qtpropertybrowser/images/cursor-sizev.png" + "../../../../shared/qtpropertybrowser/images/cursor-uparrow.png" + "../../../../shared/qtpropertybrowser/images/cursor-vsplit.png" + "../../../../shared/qtpropertybrowser/images/cursor-wait.png" + "../../../../shared/qtpropertybrowser/images/cursor-whatsthis.png" + ) + + qt_internal_add_resource(Designer "qtpropertybrowser" + PREFIX + "/qt-project.org/qtpropertybrowser" + BASE + "../../../../shared/qtpropertybrowser" + FILES + ${qtpropertybrowser_resource_files} + ) +endif() + +qt_internal_extend_target(Designer CONDITION QT_BUILD_SHARED_LIBS + SOURCES + ../../../../shared/qtpropertybrowser/qtpropertybrowserutils.cpp ../../../../shared/qtpropertybrowser/qtpropertybrowserutils_p.h + INCLUDE_DIRECTORIES + ../../../../shared/qtpropertybrowser +) + +qt_internal_extend_target(Designer CONDITION QT_FEATURE_opengl + LIBRARIES + Qt::OpenGL +) + +# UiPlugin module generates deprecated header files for Designer. +qt_internal_add_sync_header_dependencies(Designer UiPlugin) diff --git a/src/tools/designer/src/lib/components/qdesigner_components.h b/src/tools/designer/src/lib/components/qdesigner_components.h new file mode 100644 index 00000000000..33ee90d20fd --- /dev/null +++ b/src/tools/designer/src/lib/components/qdesigner_components.h @@ -0,0 +1,45 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_COMPONENTS_H +#define QDESIGNER_COMPONENTS_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QObject; +class QWidget; + +class QDesignerFormEditorInterface; +class QDesignerWidgetBoxInterface; +class QDesignerPropertyEditorInterface; +class QDesignerObjectInspectorInterface; +class QDesignerActionEditorInterface; + +class QDESIGNER_COMPONENTS_EXPORT QDesignerComponents +{ +public: + static void initializeResources(); + static void initializePlugins(QDesignerFormEditorInterface *core); + + static QDesignerFormEditorInterface *createFormEditor(QObject *parent); + static QDesignerFormEditorInterface * + createFormEditorWithPluginPaths(const QStringList &pluginPaths, + QObject *parent); + static QDesignerWidgetBoxInterface *createWidgetBox(QDesignerFormEditorInterface *core, QWidget *parent); + static QDesignerPropertyEditorInterface *createPropertyEditor(QDesignerFormEditorInterface *core, QWidget *parent); + static QDesignerObjectInspectorInterface *createObjectInspector(QDesignerFormEditorInterface *core, QWidget *parent); + static QDesignerActionEditorInterface *createActionEditor(QDesignerFormEditorInterface *core, QWidget *parent); + + static QObject *createTaskMenu(QDesignerFormEditorInterface *core, QObject *parent); + static QWidget *createResourceEditor(QDesignerFormEditorInterface *core, QWidget *parent); + static QWidget *createSignalSlotEditor(QDesignerFormEditorInterface *core, QWidget *parent); + + static QStringList defaultPluginPaths(); +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_COMPONENTS_H diff --git a/src/tools/designer/src/lib/components/qdesigner_components_global.h b/src/tools/designer/src/lib/components/qdesigner_components_global.h new file mode 100644 index 00000000000..ced3bd14ae2 --- /dev/null +++ b/src/tools/designer/src/lib/components/qdesigner_components_global.h @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QDESIGNER_COMPONENTS_GLOBAL_H +#define QDESIGNER_COMPONENTS_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#define QDESIGNER_COMPONENTS_EXTERN Q_DECL_EXPORT +#define QDESIGNER_COMPONENTS_IMPORT Q_DECL_IMPORT + +#ifdef QT_DESIGNER_STATIC +# define QDESIGNER_COMPONENTS_EXPORT +#elif defined(QDESIGNER_COMPONENTS_LIBRARY) +# define QDESIGNER_COMPONENTS_EXPORT QDESIGNER_COMPONENTS_EXTERN +#else +# define QDESIGNER_COMPONENTS_EXPORT QDESIGNER_COMPONENTS_IMPORT +#endif + + +QT_END_NAMESPACE +#endif // QDESIGNER_COMPONENTS_GLOBAL_H diff --git a/src/tools/designer/src/lib/extension/default_extensionfactory.cpp b/src/tools/designer/src/lib/extension/default_extensionfactory.cpp new file mode 100644 index 00000000000..87a8d15206f --- /dev/null +++ b/src/tools/designer/src/lib/extension/default_extensionfactory.cpp @@ -0,0 +1,137 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "default_extensionfactory.h" +#include "qextensionmanager.h" +#include +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QExtensionFactory + + \brief The QExtensionFactory class allows you to create a factory + that is able to make instances of custom extensions in Qt + Designer. + + \inmodule QtDesigner + + In \QD the extensions are not created until they are required. For + that reason, when implementing a custom extension, you must also + create a QExtensionFactory, i.e. a class that is able to make an + instance of your extension, and register it using \QD's \l + {QExtensionManager}{extension manager}. + + The QExtensionManager class provides extension management + facilities for \QD. When an extension is required, Qt + Designer's \l {QExtensionManager}{extension manager} will run + through all its registered factories calling + QExtensionFactory::createExtension() for each until the first one + that is able to create a requested extension for the selected + object, is found. This factory will then make an instance of the + extension. + + There are four available types of extensions in \QD: + QDesignerContainerExtension , QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and QDesignerTaskMenuExtension. Qt + Designer's behavior is the same whether the requested extension is + associated with a multi page container, a member sheet, a property + sheet or a task menu. + + You can either create a new QExtensionFactory and reimplement the + QExtensionFactory::createExtension() function. For example: + + \snippet lib/tools_designer_src_lib_extension_default_extensionfactory.cpp 0 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create your extension as well. For example: + + \snippet lib/tools_designer_src_lib_extension_default_extensionfactory.cpp 1 + + For a complete example using the QExtensionFactory class, see the + \l {taskmenuextension}{Task Menu Extension example}. The + example shows how to create a custom widget plugin for Qt + Designer, and how to use the QDesignerTaskMenuExtension class + to add custom items to \QD's task menu. + + \sa QExtensionManager, QAbstractExtensionFactory +*/ + +/*! + Constructs an extension factory with the given \a parent. +*/ +QExtensionFactory::QExtensionFactory(QExtensionManager *parent) + : QObject(parent) +{ +} + +/*! + Returns the extension specified by \a iid for the given \a object. + + \sa createExtension() +*/ + +QObject *QExtensionFactory::extension(QObject *object, const QString &iid) const +{ + if (!object) + return nullptr; + const auto key = std::make_pair(iid, object); + + auto it = m_extensions.find(key); + if (it == m_extensions.end()) { + if (QObject *ext = createExtension(object, iid, const_cast(this))) { + connect(ext, &QObject::destroyed, this, &QExtensionFactory::objectDestroyed); + it = m_extensions.insert(key, ext); + } + } + + if (!m_extended.contains(object)) { + connect(object, &QObject::destroyed, this, &QExtensionFactory::objectDestroyed); + m_extended.insert(object, true); + } + + if (it == m_extensions.end()) + return nullptr; + + return it.value(); +} + +void QExtensionFactory::objectDestroyed(QObject *object) +{ + for (auto it = m_extensions.begin(); it != m_extensions.end(); ) { + if (it.key().second == object || object == it.value()) + it = m_extensions.erase(it); + else + ++it; + } + + m_extended.remove(object); +} + +/*! + Creates an extension specified by \a iid for the given \a object. + The extension object is created as a child of the specified \a + parent. + + \sa extension() +*/ +QObject *QExtensionFactory::createExtension(QObject *object, const QString &iid, QObject *parent) const +{ + Q_UNUSED(object); + Q_UNUSED(iid); + Q_UNUSED(parent); + + return nullptr; +} + +/*! + Returns the extension manager for the extension factory. +*/ +QExtensionManager *QExtensionFactory::extensionManager() const +{ + return static_cast(parent()); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/extension/default_extensionfactory.h b/src/tools/designer/src/lib/extension/default_extensionfactory.h new file mode 100644 index 00000000000..825b9d5a119 --- /dev/null +++ b/src/tools/designer/src/lib/extension/default_extensionfactory.h @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DEFAULT_EXTENSIONFACTORY_H +#define DEFAULT_EXTENSIONFACTORY_H + +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QExtensionManager; + +class QDESIGNER_EXTENSION_EXPORT QExtensionFactory : public QObject, public QAbstractExtensionFactory +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionFactory) +public: + explicit QExtensionFactory(QExtensionManager *parent = nullptr); + + QObject *extension(QObject *object, const QString &iid) const override; + QExtensionManager *extensionManager() const; + +private Q_SLOTS: + void objectDestroyed(QObject *object); + +protected: + virtual QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const; + +private: + mutable QMap, QObject *> m_extensions; + // ### FIXME Qt 7: Use QSet, add out of line destructor. + mutable QHash m_extended; +}; + +QT_END_NAMESPACE + +#endif // DEFAULT_EXTENSIONFACTORY_H diff --git a/src/tools/designer/src/lib/extension/extension.cpp b/src/tools/designer/src/lib/extension/extension.cpp new file mode 100644 index 00000000000..d51b3917c11 --- /dev/null +++ b/src/tools/designer/src/lib/extension/extension.cpp @@ -0,0 +1,148 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QAbstractExtensionFactory + + \brief The QAbstractExtensionFactory class provides an interface + for extension factories in \QD. + + \inmodule QtDesigner + + QAbstractExtensionFactory is not intended to be instantiated + directly; use the QExtensionFactory instead. + + In \QD, extension factories are used to look up and create named + extensions as they are required. For that reason, when + implementing a custom extension, you must also create a + QExtensionFactory, i.e a class that is able to make an instance of + your extension, and register it using \QD's \l + {QExtensionManager}{extension manager}. + + When an extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create the requested + extension for the selected object, is found. This factory will + then make an instance of the extension. + + \sa QExtensionFactory, QExtensionManager +*/ + +/*! + Destroys the extension factory. +*/ +QAbstractExtensionFactory::~QAbstractExtensionFactory() + = default; + +/*! + \fn QObject *QAbstractExtensionFactory::extension(QObject *object, const QString &iid) const + + Returns the extension specified by \a iid for the given \a object. +*/ + + +/*! + \class QAbstractExtensionManager + + \brief The QAbstractExtensionManager class provides an interface + for extension managers in \QD. + + \inmodule QtDesigner + + QAbstractExtensionManager is not intended to be instantiated + directly; use the QExtensionManager instead. + + In \QD, extension are not created until they are required. For + that reason, when implementing a custom extension, you must also + create a QExtensionFactory, i.e a class that is able to make an + instance of your extension, and register it using \QD's \l + {QExtensionManager}{extension manager}. + + When an extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create the requested + extension for the selected object, is found. This factory will + then make an instance of the extension. + + \sa QExtensionManager, QExtensionFactory +*/ + +/*! + Destroys the extension manager. +*/ +QAbstractExtensionManager::~QAbstractExtensionManager() + = default; + +/*! + \fn void QAbstractExtensionManager::registerExtensions(QAbstractExtensionFactory *factory, const QString &iid) + + Register the given extension \a factory with the extension + specified by \a iid. +*/ + +/*! + \fn void QAbstractExtensionManager::unregisterExtensions(QAbstractExtensionFactory *factory, const QString &iid) + + Unregister the given \a factory with the extension specified by \a + iid. +*/ + +/*! + \fn QObject *QAbstractExtensionManager::extension(QObject *object, const QString &iid) const + + Returns the extension, specified by \a iid, for the given \a + object. +*/ + +/*! + \fn template T qt_extension(QAbstractExtensionManager* manager, QObject *object) + + \relates QExtensionManager + + Returns the extension of the given \a object cast to type T if the + object is of type T (or of a subclass); otherwise returns 0. The + extension is retrieved using the given extension \a manager. + + \snippet lib/tools_designer_src_lib_extension_extension.cpp 0 + + When implementing a custom widget plugin, a pointer to \QD's + current QDesignerFormEditorInterface object (\c formEditor) is + provided by the QDesignerCustomWidgetInterface::initialize() + function's parameter. + + If the widget in the example above doesn't have a defined + QDesignerPropertySheetExtension, \c propertySheet will be a null + pointer. + +*/ + +/*! + \macro Q_DECLARE_EXTENSION_INTERFACE(ExtensionName, Identifier) + + \relates QExtensionManager + + Associates the given \a Identifier (a string literal) to the + extension class called \a ExtensionName. The \a Identifier must be + unique. For example: + + \snippet lib/tools_designer_src_lib_extension_extension.cpp 1 + + Using the company and product names is a good way to ensure + uniqueness of the identifier. + + When implementing a custom extension class, you must use + Q_DECLARE_EXTENSION_INTERFACE() to enable usage of the + qt_extension() function. The macro is normally located right after the + class definition for \a ExtensionName, in the associated header + file. + + \sa {Q_DECLARE_INTERFACE}{Q_DECLARE_INTERFACE()} +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/extension/extension.h b/src/tools/designer/src/lib/extension/extension.h new file mode 100644 index 00000000000..e9c9e24bd3b --- /dev/null +++ b/src/tools/designer/src/lib/extension/extension.h @@ -0,0 +1,49 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef EXTENSION_H +#define EXTENSION_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +#define Q_TYPEID(IFace) QLatin1StringView(IFace##_iid) + +class QDESIGNER_EXTENSION_EXPORT QAbstractExtensionFactory +{ +public: + virtual ~QAbstractExtensionFactory(); + + virtual QObject *extension(QObject *object, const QString &iid) const = 0; +}; +Q_DECLARE_INTERFACE(QAbstractExtensionFactory, "org.qt-project.Qt.QAbstractExtensionFactory") + +class QDESIGNER_EXTENSION_EXPORT QAbstractExtensionManager +{ +public: + virtual ~QAbstractExtensionManager(); + + virtual void registerExtensions(QAbstractExtensionFactory *factory, const QString &iid) = 0; + virtual void unregisterExtensions(QAbstractExtensionFactory *factory, const QString &iid) = 0; + + virtual QObject *extension(QObject *object, const QString &iid) const = 0; +}; +Q_DECLARE_INTERFACE(QAbstractExtensionManager, "org.qt-project.Qt.QAbstractExtensionManager") + +template +inline T qt_extension(QAbstractExtensionManager *, QObject *) +{ return nullptr; } + +#define Q_DECLARE_EXTENSION_INTERFACE(IFace, IId) \ +const char * const IFace##_iid = IId; \ +Q_DECLARE_INTERFACE(IFace, IId) \ +template <> inline IFace *qt_extension(QAbstractExtensionManager *manager, QObject *object) \ +{ QObject *extension = manager->extension(object, Q_TYPEID(IFace)); return extension ? static_cast(extension->qt_metacast(IFace##_iid)) : static_cast(nullptr); } + +QT_END_NAMESPACE + +#endif // EXTENSION_H diff --git a/src/tools/designer/src/lib/extension/extension_global.h b/src/tools/designer/src/lib/extension/extension_global.h new file mode 100644 index 00000000000..781974f6a9a --- /dev/null +++ b/src/tools/designer/src/lib/extension/extension_global.h @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef EXTENSION_GLOBAL_H +#define EXTENSION_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#define QDESIGNER_EXTENSION_EXTERN Q_DECL_EXPORT +#define QDESIGNER_EXTENSION_IMPORT Q_DECL_IMPORT + +#ifdef QT_DESIGNER_STATIC +# define QDESIGNER_EXTENSION_EXPORT +#elif defined(QDESIGNER_EXTENSION_LIBRARY) +# define QDESIGNER_EXTENSION_EXPORT QDESIGNER_EXTENSION_EXTERN +#else +# define QDESIGNER_EXTENSION_EXPORT QDESIGNER_EXTENSION_IMPORT +#endif + +QT_END_NAMESPACE + +#endif // EXTENSION_GLOBAL_H diff --git a/src/tools/designer/src/lib/extension/qextensionmanager.cpp b/src/tools/designer/src/lib/extension/qextensionmanager.cpp new file mode 100644 index 00000000000..2d974c63a9f --- /dev/null +++ b/src/tools/designer/src/lib/extension/qextensionmanager.cpp @@ -0,0 +1,135 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qextensionmanager.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QExtensionManager + + \brief The QExtensionManager class provides extension management + facilities for \QD. + + \inmodule QtDesigner + + In \QD the extensions are not created until they are required. For + that reason, when implementing an extension, you must also create + a QExtensionFactory, i.e a class that is able to make an instance + of your extension, and register it using \QD's extension manager. + + The registration of an extension factory is typically made in the + QDesignerCustomWidgetInterface::initialize() function: + + \snippet lib/tools_designer_src_lib_extension_qextensionmanager.cpp 0 + + The QExtensionManager is not intended to be instantiated + directly. You can retrieve an interface to \QD's extension manager + using the QDesignerFormEditorInterface::extensionManager() + function. A pointer to \QD's current QDesignerFormEditorInterface + object (\c formEditor in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + Then, when an extension is required, \QD's extension manager will + run through all its registered factories calling + QExtensionFactory::createExtension() for each until the first one + that is able to create the requested extension for the selected + object, is found. This factory will then make an instance of the + extension. + + There are four available types of extensions in \QD: + QDesignerContainerExtension , QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and + QDesignerTaskMenuExtension. \QD's behavior is the same whether the + requested extension is associated with a container, a member + sheet, a property sheet or a task menu. + + For a complete example using the QExtensionManager class, see the + \l {taskmenuextension}{Task Menu Extension example}. The + example shows how to create a custom widget plugin for Qt + Designer, and how to use the QDesignerTaskMenuExtension class + to add custom items to \QD's task menu. + + \sa QExtensionFactory, QAbstractExtensionManager +*/ + +/*! + Constructs an extension manager with the given \a parent. +*/ +QExtensionManager::QExtensionManager(QObject *parent) + : QObject(parent) +{ +} + + +/*! + Destroys the extension manager +*/ +QExtensionManager::~QExtensionManager() = default; + +/*! + Register the extension specified by the given \a factory and + extension identifier \a iid. +*/ +void QExtensionManager::registerExtensions(QAbstractExtensionFactory *factory, const QString &iid) +{ + if (iid.isEmpty()) { + m_globalExtension.prepend(factory); + return; + } + + auto it = m_extensions.find(iid); + if (it == m_extensions.end()) + it = m_extensions.insert(iid, FactoryList()); + + it.value().prepend(factory); +} + +/*! + Unregister the extension specified by the given \a factory and + extension identifier \a iid. +*/ +void QExtensionManager::unregisterExtensions(QAbstractExtensionFactory *factory, const QString &iid) +{ + if (iid.isEmpty()) { + m_globalExtension.removeAll(factory); + return; + } + + const auto it = m_extensions.find(iid); + if (it == m_extensions.end()) + return; + + FactoryList &factories = it.value(); + factories.removeAll(factory); + + if (factories.isEmpty()) + m_extensions.erase(it); +} + +/*! + Returns the extension specified by \a iid, for the given \a + object. +*/ +QObject *QExtensionManager::extension(QObject *object, const QString &iid) const +{ + const auto it = m_extensions.constFind(iid); + if (it != m_extensions.constEnd()) { + for (const auto &f : it.value()) { + if (QObject *ext = f->extension(object, iid)) + return ext; + } + } + + for (const auto &gf : m_globalExtension) { + if (QObject *ext = gf->extension(object, iid)) + return ext; + } + + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/extension/qextensionmanager.h b/src/tools/designer/src/lib/extension/qextensionmanager.h new file mode 100644 index 00000000000..0fb2bcd9e21 --- /dev/null +++ b/src/tools/designer/src/lib/extension/qextensionmanager.h @@ -0,0 +1,36 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef QEXTENSIONMANAGER_H +#define QEXTENSIONMANAGER_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QObject; // Fool syncqt + +class QDESIGNER_EXTENSION_EXPORT QExtensionManager: public QObject, public QAbstractExtensionManager +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionManager) +public: + explicit QExtensionManager(QObject *parent = nullptr); + ~QExtensionManager(); + + void registerExtensions(QAbstractExtensionFactory *factory, const QString &iid = QString()) override; + void unregisterExtensions(QAbstractExtensionFactory *factory, const QString &iid = QString()) override; + + QObject *extension(QObject *object, const QString &iid) const override; + +private: + using FactoryList = QList; + QHash m_extensions; + FactoryList m_globalExtension; +}; + +QT_END_NAMESPACE + +#endif // QEXTENSIONMANAGER_H diff --git a/src/tools/designer/src/lib/lib_pch.h b/src/tools/designer/src/lib/lib_pch.h new file mode 100644 index 00000000000..f8dfc9947ba --- /dev/null +++ b/src/tools/designer/src/lib/lib_pch.h @@ -0,0 +1,27 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifdef __cplusplus +#include "shared_global_p.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "qdesigner_widget_p.h" +#include +#include +#include +#include +#include "layout_p.h" +#endif diff --git a/src/tools/designer/src/lib/sdk/abstractactioneditor.cpp b/src/tools/designer/src/lib/sdk/abstractactioneditor.cpp new file mode 100644 index 00000000000..5b50a941bc3 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractactioneditor.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractactioneditor.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerActionEditorInterface + + \brief The QDesignerActionEditorInterface class allows you to + change the focus of \QD's action editor. + + \inmodule QtDesigner + + The QDesignerActionEditorInterface class is not intended to be + instantiated directly. You can retrieve an interface to \QD's + action editor using the + QDesignerFormEditorInterface::actionEditor() function. + + You can control which actions that are available in the action + editor's window using the manageAction() and unmanageAction() + functions. An action that is managed by \QD is available in the + action editor while an unmanaged action is ignored. + + QDesignerActionEditorInterface also provides the core() function + that you can use to retrieve a pointer to \QD's current + QDesignerFormEditorInterface object, and the setFormWindow() + function that enables you to change the currently selected form + window. + + \sa QDesignerFormEditorInterface, QDesignerFormWindowInterface +*/ + +/*! + Constructs an action editor interface with the given \a parent and + the specified window \a flags. +*/ +QDesignerActionEditorInterface::QDesignerActionEditorInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the action editor interface. +*/ +QDesignerActionEditorInterface::~QDesignerActionEditorInterface() = default; + +/*! + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ +QDesignerFormEditorInterface *QDesignerActionEditorInterface::core() const +{ + return nullptr; +} + +/*! + \fn void QDesignerActionEditorInterface::setFormWindow(QDesignerFormWindowInterface *formWindow) + + Sets the currently selected form window to \a formWindow. + +*/ + +/*! + \fn void QDesignerActionEditorInterface::manageAction(QAction *action) + + Instructs \QD to manage the specified \a action. An action that is + managed by \QD is available in the action editor. + + \sa unmanageAction() +*/ + +/*! + \fn void QDesignerActionEditorInterface::unmanageAction(QAction *action) + + Instructs \QD to ignore the specified \a action. An unmanaged + action is not available in the action editor. + + \sa manageAction() +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractactioneditor.h b/src/tools/designer/src/lib/sdk/abstractactioneditor.h new file mode 100644 index 00000000000..5ca6b2c3818 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractactioneditor.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTACTIONEDITOR_H +#define ABSTRACTACTIONEDITOR_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class QDESIGNER_SDK_EXPORT QDesignerActionEditorInterface: public QWidget +{ + Q_OBJECT +public: + explicit QDesignerActionEditorInterface(QWidget *parent, Qt::WindowFlags flags = {}); + virtual ~QDesignerActionEditorInterface(); + + virtual QDesignerFormEditorInterface *core() const; + + virtual void manageAction(QAction *action) = 0; + virtual void unmanageAction(QAction *action) = 0; + +public Q_SLOTS: + virtual void setFormWindow(QDesignerFormWindowInterface *formWindow) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTACTIONEDITOR_H diff --git a/src/tools/designer/src/lib/sdk/abstractdialoggui.cpp b/src/tools/designer/src/lib/sdk/abstractdialoggui.cpp new file mode 100644 index 00000000000..6c1eb6d9903 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractdialoggui.cpp @@ -0,0 +1,119 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractdialoggui_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerDialogGuiInterface + \since 4.4 + \internal + + \brief The QDesignerDialogGuiInterface allows integrations of \QD to replace the + message boxes displayed by \QD by custom dialogs. + + \inmodule QtDesigner + + QDesignerDialogGuiInterface provides virtual functions that can be overwritten + to display message boxes and file dialogs. + \sa QMessageBox, QFileDialog +*/ + +/*! + \enum QDesignerDialogGuiInterface::Message + + This enum specifies the context from within the message box is called. + + \value FormLoadFailureMessage Loading of a form failed + \value UiVersionMismatchMessage Attempt to load a file created with an old version of Designer + \value ResourceLoadFailureMessage Resources specified in a file could not be found + \value TopLevelSpacerMessage Spacer items detected on a container without layout + \value PropertyEditorMessage Messages of the propert yeditor + \value SignalSlotEditorMessage Messages of the signal / slot editor + \value FormEditorMessage Messages of the form editor + \value PreviewFailureMessage A preview could not be created + \value PromotionErrorMessage Messages related to promotion of a widget + \value ResourceEditorMessage Messages of the resource editor + \value ScriptDialogMessage Messages of the script dialog + \value SignalSlotDialogMessage Messages of the signal slot dialog + \value OtherMessage Unspecified context +*/ + +/*! + Constructs a QDesignerDialogGuiInterface object. +*/ + +QDesignerDialogGuiInterface::QDesignerDialogGuiInterface() = default; + +/*! + Destroys the QDesignerDialogGuiInterface object. +*/ +QDesignerDialogGuiInterface::~QDesignerDialogGuiInterface() = default; + +/*! + \fn QMessageBox::StandardButton QDesignerDialogGuiInterface::message(QWidget *parent, Message context, QMessageBox::Icon icon, const QString &title, const QString &text, QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) + + Opens a message box as child of \a parent within the context \a context, using \a icon, \a title, \a text, \a buttons and \a defaultButton + and returns the button chosen by the user. +*/ + +/*! + \fn QString QDesignerDialogGuiInterface::getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options) + + Opens a file dialog as child of \a parent using the parameters \a caption, \a dir and \a options that prompts the + user for an existing directory. Returns a directory selected by the user. +*/ + +/*! + \fn QString QDesignerDialogGuiInterface::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options) + + Opens a file dialog as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for an existing file. Returns a file selected by the user. +*/ + +/*! + \fn QStringList QDesignerDialogGuiInterface::getOpenFileNames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options) + + Opens a file dialog as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for a set of existing files. Returns one or more existing files selected by the user. +*/ + +/*! + Opens a file dialog with image browsing capabilities as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for an existing file. Returns a file selected by the user. + + The default implementation simply calls getOpenFileName(). On platforms that do not support an image preview in the QFileDialog, + the function can be reimplemented to provide an image browser. + + \since 4.5 +*/ + +QString QDesignerDialogGuiInterface::getOpenImageFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return getOpenFileName(parent, caption, dir, filter, selectedFilter, options); +} + +/*! + Opens a file dialog with image browsing capabilities as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for a set of existing files. Returns one or more existing files selected by the user. + + The default implementation simply calls getOpenFileNames(). On platforms that do not support an image preview in the QFileDialog, + the function can be reimplemented to provide an image browser. + + \since 4.5 +*/ + +QStringList QDesignerDialogGuiInterface::getOpenImageFileNames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return getOpenFileNames(parent, caption, dir, filter, selectedFilter, options); +} + +/*! + \fn QString QDesignerDialogGuiInterface::getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options) + + Opens a file dialog as child of \a parent using the parameters \a caption, \a dir, \a filter, \a selectedFilter and \a options + that prompts the user for a file. Returns a file selected by the user. The file does not have to exist. +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractdialoggui_p.h b/src/tools/designer/src/lib/sdk/abstractdialoggui_p.h new file mode 100644 index 00000000000..b230a5be017 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractdialoggui_p.h @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ABSTRACTDIALOGGUI_H +#define ABSTRACTDIALOGGUI_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QWidget; + +class QDESIGNER_SDK_EXPORT QDesignerDialogGuiInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerDialogGuiInterface) + + QDesignerDialogGuiInterface(); + virtual ~QDesignerDialogGuiInterface(); + + enum Message { FormLoadFailureMessage, UiVersionMismatchMessage, ResourceLoadFailureMessage, + TopLevelSpacerMessage, PropertyEditorMessage, SignalSlotEditorMessage, FormEditorMessage, + PreviewFailureMessage, PromotionErrorMessage, ResourceEditorMessage, + ScriptDialogMessage, SignalSlotDialogMessage, OtherMessage, FileChangedMessage }; + + virtual QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) = 0; + + virtual QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) = 0; + + virtual QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, const QString &detailedText, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) = 0; + + virtual QString getExistingDirectory(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), QFileDialog::Options options = QFileDialog::ShowDirsOnly)= 0; + virtual QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {})= 0; + virtual QString getOpenImageFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}); + virtual QStringList getOpenFileNames(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {})= 0; + virtual QStringList getOpenImageFileNames(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}); + virtual QString getSaveFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {})= 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTDIALOGGUI_H diff --git a/src/tools/designer/src/lib/sdk/abstractdnditem.h b/src/tools/designer/src/lib/sdk/abstractdnditem.h new file mode 100644 index 00000000000..44d1da16b0a --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractdnditem.h @@ -0,0 +1,35 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTDNDITEM_H +#define ABSTRACTDNDITEM_H + +#include + +QT_BEGIN_NAMESPACE + +class DomUI; +class QWidget; +class QPoint; + +class QDESIGNER_SDK_EXPORT QDesignerDnDItemInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerDnDItemInterface) + + enum DropType { MoveDrop, CopyDrop }; + + QDesignerDnDItemInterface() = default; + virtual ~QDesignerDnDItemInterface() = default; + + virtual DomUI *domUi() const = 0; + virtual QWidget *widget() const = 0; + virtual QWidget *decoration() const = 0; + virtual QPoint hotSpot() const = 0; + virtual DropType type() const = 0; + virtual QWidget *source() const = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTDNDITEM_H diff --git a/src/tools/designer/src/lib/sdk/abstractdnditem.qdoc b/src/tools/designer/src/lib/sdk/abstractdnditem.qdoc new file mode 100644 index 00000000000..b3dd3d3ed73 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractdnditem.qdoc @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerDnDItemInterface + \brief The QDesignerDnDItemInterface class provides an interface that is used to manage items + during a drag and drop operation. + \inmodule QtDesigner + \internal +*/ + +/*! + \enum QDesignerDnDItemInterface::DropType + + This enum describes the result of a drag and drop operation. + + \value MoveDrop The item was moved. + \value CopyDrop The item was copied. +*/ + +/*! + \fn QDesignerDnDItemInterface::QDesignerDnDItemInterface() + + Constructs a new interface to a drag and drop item. +*/ + +/*! + \fn QDesignerDnDItemInterface::~QDesignerDnDItemInterface() + + Destroys the interface to the item. +*/ + +/*! + \fn DomUI *QDesignerDnDItemInterface::domUi() const + + Returns a user interface object for the item. +*/ + +/*! + \fn QWidget *QDesignerDnDItemInterface::widget() const + + Returns the widget being copied or moved in the drag and drop operation. + + \sa source() +*/ + +/*! + \fn QWidget *QDesignerDnDItemInterface::decoration() const + + Returns the widget used to represent the item. +*/ + +/*! + \fn QPoint QDesignerDnDItemInterface::hotSpot() const + + Returns the cursor's hotspot. + + \sa QDrag::hotSpot() +*/ + +/*! + \fn DropType QDesignerDnDItemInterface::type() const + + Returns the type of drag and drop operation in progress. +*/ + +/*! + \fn QWidget *QDesignerDnDItemInterface::source() const + + Returns the widget that is the source of the drag and drop operation; i.e. the original + container of the widget being dragged. + + \sa widget() +*/ diff --git a/src/tools/designer/src/lib/sdk/abstractformeditor.cpp b/src/tools/designer/src/lib/sdk/abstractformeditor.cpp new file mode 100644 index 00000000000..9a75805a32a --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformeditor.cpp @@ -0,0 +1,571 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformeditor.h" +#include "abstractdialoggui_p.h" +#include "abstractintrospection_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Must be done outside of the Qt namespace +static void initResources() +{ + Q_INIT_RESOURCE(shared); + Q_INIT_RESOURCE(ClamshellPhone); + Q_INIT_RESOURCE(PortableMedia); + Q_INIT_RESOURCE(S60_nHD_Touchscreen); + Q_INIT_RESOURCE(S60_QVGA_Candybar); + Q_INIT_RESOURCE(SmartPhone2); + Q_INIT_RESOURCE(SmartPhone); + Q_INIT_RESOURCE(SmartPhoneWithButtons); + Q_INIT_RESOURCE(TouchscreenPhone); +} + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QDesignerFormEditorInterfacePrivate { +public: + QDesignerFormEditorInterfacePrivate(); + ~QDesignerFormEditorInterfacePrivate(); + + + QPointer m_topLevel; + QPointer m_widgetBox; + QPointer m_propertyEditor; + QPointer m_formWindowManager; + QPointer m_extensionManager; + QPointer m_metaDataBase; + QPointer m_widgetDataBase; + QPointer m_widgetFactory; + QPointer m_objectInspector; + QPointer m_integration; + QPointer m_actionEditor; + QDesignerSettingsInterface *m_settingsManager = nullptr; + QDesignerPluginManager *m_pluginManager = nullptr; + QDesignerPromotionInterface *m_promotion = nullptr; + QDesignerIntrospectionInterface *m_introspection = nullptr; + QDesignerDialogGuiInterface *m_dialogGui = nullptr; + QPointer m_resourceModel; + QPointer m_gradientManager; // instantiated and deleted by designer_integration + QList m_optionsPages; +}; + +QDesignerFormEditorInterfacePrivate::QDesignerFormEditorInterfacePrivate() = default; + +QDesignerFormEditorInterfacePrivate::~QDesignerFormEditorInterfacePrivate() +{ + delete m_settingsManager; + delete m_formWindowManager; + delete m_promotion; + delete m_introspection; + delete m_dialogGui; + delete m_resourceModel; + qDeleteAll(m_optionsPages); +} + +/*! + \class QDesignerFormEditorInterface + + \brief The QDesignerFormEditorInterface class allows you to access + Qt Widgets Designer's various components. + + \inmodule QtDesigner + + \QD's current QDesignerFormEditorInterface object holds + information about all \QD's components: The action editor, the + object inspector, the property editor, the widget box, and the + extension and form window managers. QDesignerFormEditorInterface + contains a collection of functions that provides interfaces to all + these components. They are typically used to query (and + manipulate) the respective component. For example: + + \snippet lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp 0 + + QDesignerFormEditorInterface is not intended to be instantiated + directly. A pointer to \QD's current QDesignerFormEditorInterface + object (\c formEditor in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + QDesignerFormEditorInterface also provides functions that can set + the action editor, property editor, object inspector and widget + box. These are only useful if you want to provide your own custom + components. + + If designer is embedded in another program, one could to provide its + own settings manager. The manager is used by the components of \QD + to store/retrieve persistent configuration settings. The default + manager uses QSettings as the backend. + + Finally, QDesignerFormEditorInterface provides the topLevel() + function that returns \QD's top-level widget. + + \sa QDesignerCustomWidgetInterface +*/ + +/*! + Constructs a QDesignerFormEditorInterface object with the given \a + parent. +*/ + +QDesignerFormEditorInterface::QDesignerFormEditorInterface(QObject *parent) + : QObject(parent), + d(new QDesignerFormEditorInterfacePrivate()) +{ + initResources(); +} + +/*! + Destroys the QDesignerFormEditorInterface object. +*/ +QDesignerFormEditorInterface::~QDesignerFormEditorInterface() = default; + +/*! + Returns an interface to \QD's widget box. + + \sa setWidgetBox() +*/ +QDesignerWidgetBoxInterface *QDesignerFormEditorInterface::widgetBox() const +{ + return d->m_widgetBox; +} + +/*! + Sets \QD's widget box to be the specified \a widgetBox. + + \sa widgetBox() +*/ +void QDesignerFormEditorInterface::setWidgetBox(QDesignerWidgetBoxInterface *widgetBox) +{ + d->m_widgetBox = widgetBox; +} + +/*! + Returns an interface to \QD's property editor. + + \sa setPropertyEditor() +*/ +QDesignerPropertyEditorInterface *QDesignerFormEditorInterface::propertyEditor() const +{ + return d->m_propertyEditor; +} + +/*! + Sets \QD's property editor to be the specified \a propertyEditor. + + \sa propertyEditor() +*/ +void QDesignerFormEditorInterface::setPropertyEditor(QDesignerPropertyEditorInterface *propertyEditor) +{ + d->m_propertyEditor = propertyEditor; +} + +/*! + Returns an interface to \QD's action editor. + + \sa setActionEditor() +*/ +QDesignerActionEditorInterface *QDesignerFormEditorInterface::actionEditor() const +{ + return d->m_actionEditor; +} + +/*! + Sets \QD's action editor to be the specified \a actionEditor. + + \sa actionEditor() +*/ +void QDesignerFormEditorInterface::setActionEditor(QDesignerActionEditorInterface *actionEditor) +{ + d->m_actionEditor = actionEditor; +} + +/*! + Returns \QD's top-level widget. +*/ +QWidget *QDesignerFormEditorInterface::topLevel() const +{ + return d->m_topLevel; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setTopLevel(QWidget *topLevel) +{ + d->m_topLevel = topLevel; +} + +/*! + Returns an interface to \QD's form window manager. +*/ +QDesignerFormWindowManagerInterface *QDesignerFormEditorInterface::formWindowManager() const +{ + return d->m_formWindowManager; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setFormManager(QDesignerFormWindowManagerInterface *formWindowManager) +{ + d->m_formWindowManager = formWindowManager; +} + +/*! + Returns an interface to \QD's extension manager. +*/ +QExtensionManager *QDesignerFormEditorInterface::extensionManager() const +{ + return d->m_extensionManager; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setExtensionManager(QExtensionManager *extensionManager) +{ + d->m_extensionManager = extensionManager; +} + +/*! + \internal + + Returns an interface to the meta database used by the form editor. +*/ +QDesignerMetaDataBaseInterface *QDesignerFormEditorInterface::metaDataBase() const +{ + return d->m_metaDataBase; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setMetaDataBase(QDesignerMetaDataBaseInterface *metaDataBase) +{ + d->m_metaDataBase = metaDataBase; +} + +/*! + \internal + + Returns an interface to the widget database used by the form editor. +*/ +QDesignerWidgetDataBaseInterface *QDesignerFormEditorInterface::widgetDataBase() const +{ + return d->m_widgetDataBase; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setWidgetDataBase(QDesignerWidgetDataBaseInterface *widgetDataBase) +{ + d->m_widgetDataBase = widgetDataBase; +} + +/*! + \internal + + Returns an interface to the designer promotion handler. +*/ + +QDesignerPromotionInterface *QDesignerFormEditorInterface::promotion() const +{ + return d->m_promotion; +} + +/*! + \internal + + Sets the designer promotion handler. +*/ + +void QDesignerFormEditorInterface::setPromotion(QDesignerPromotionInterface *promotion) +{ + delete d->m_promotion; + d->m_promotion = promotion; +} + +/*! + \internal + + Returns an interface to the widget factory used by the form editor + to create widgets for the form. +*/ +QDesignerWidgetFactoryInterface *QDesignerFormEditorInterface::widgetFactory() const +{ + return d->m_widgetFactory; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setWidgetFactory(QDesignerWidgetFactoryInterface *widgetFactory) +{ + d->m_widgetFactory = widgetFactory; +} + +/*! + Returns an interface to \QD's object inspector. +*/ +QDesignerObjectInspectorInterface *QDesignerFormEditorInterface::objectInspector() const +{ + return d->m_objectInspector; +} + +/*! + Sets \QD's object inspector to be the specified \a + objectInspector. + + \sa objectInspector() +*/ +void QDesignerFormEditorInterface::setObjectInspector(QDesignerObjectInspectorInterface *objectInspector) +{ + d->m_objectInspector = objectInspector; +} + +/*! + \internal + + Returns an interface to the integration. +*/ +QDesignerIntegrationInterface *QDesignerFormEditorInterface::integration() const +{ + return d->m_integration; +} + +/*! + \internal +*/ +void QDesignerFormEditorInterface::setIntegration(QDesignerIntegrationInterface *integration) +{ + d->m_integration = integration; +} + +/*! + \internal + \since 4.5 + Returns the list of options pages that allow the user to configure \QD components. +*/ +QList QDesignerFormEditorInterface::optionsPages() const +{ + return d->m_optionsPages; +} + +/*! + \internal + \since 4.5 + Sets the list of options pages that allow the user to configure \QD components. +*/ +void QDesignerFormEditorInterface::setOptionsPages(const QList &optionsPages) +{ + d->m_optionsPages = optionsPages; +} + + +/*! + \internal + + Returns the plugin manager used by the form editor. +*/ +QDesignerPluginManager *QDesignerFormEditorInterface::pluginManager() const +{ + return d->m_pluginManager; +} + +/*! + \internal + + Sets the plugin manager used by the form editor to the specified + \a pluginManager. +*/ +void QDesignerFormEditorInterface::setPluginManager(QDesignerPluginManager *pluginManager) +{ + d->m_pluginManager = pluginManager; +} + +/*! + \internal + \since 4.4 + Returns the resource model used by the form editor. +*/ +QtResourceModel *QDesignerFormEditorInterface::resourceModel() const +{ + return d->m_resourceModel; +} + +/*! + \internal + + Sets the resource model used by the form editor to the specified + \a resourceModel. +*/ +void QDesignerFormEditorInterface::setResourceModel(QtResourceModel *resourceModel) +{ + d->m_resourceModel = resourceModel; +} + +/*! + \internal + \since 4.4 + Returns the gradient manager used by the style sheet editor. +*/ +QtGradientManager *QDesignerFormEditorInterface::gradientManager() const +{ + return d->m_gradientManager; +} + +/*! + \internal + + Sets the gradient manager used by the style sheet editor to the specified + \a gradientManager. +*/ +void QDesignerFormEditorInterface::setGradientManager(QtGradientManager *gradientManager) +{ + d->m_gradientManager = gradientManager; +} + +/*! + \internal + \since 4.5 + Returns the settings manager used by the components to store persistent settings. +*/ +QDesignerSettingsInterface *QDesignerFormEditorInterface::settingsManager() const +{ + return d->m_settingsManager; +} + +/*! + \internal + \since 4.5 + Sets the settings manager used to store/retrieve the persistent settings of the components. +*/ +void QDesignerFormEditorInterface::setSettingsManager(QDesignerSettingsInterface *settingsManager) +{ + if (d->m_settingsManager) + delete d->m_settingsManager; + d->m_settingsManager = settingsManager; + + // This is a (hopefully) safe place to perform settings-dependent + // initializations. + const qdesigner_internal::QDesignerSharedSettings settings(this); + qdesigner_internal::FormWindowBase::setDefaultDesignerGrid(settings.defaultGrid()); + qdesigner_internal::ActionEditor::setObjectNamingMode(settings.objectNamingMode()); +} + +/*! + \internal + \since 4.4 + Returns the introspection used by the form editor. +*/ +QDesignerIntrospectionInterface *QDesignerFormEditorInterface::introspection() const +{ + return d->m_introspection; +} + +/*! + \internal + \since 4.4 + + Sets the introspection used by the form editor to the specified \a introspection. +*/ +void QDesignerFormEditorInterface::setIntrospection(QDesignerIntrospectionInterface *introspection) +{ + delete d->m_introspection; + d->m_introspection = introspection; +} + +/*! + \internal + + Returns the path to the resources used by the form editor. +*/ +QString QDesignerFormEditorInterface::resourceLocation() const +{ +#ifdef Q_OS_MACOS + return u":/qt-project.org/formeditor/images/mac"_s; +#else + return u":/qt-project.org/formeditor/images/win"_s; +#endif +} + +/*! + \internal + + Returns the dialog GUI used by the form editor. +*/ + +QDesignerDialogGuiInterface *QDesignerFormEditorInterface::dialogGui() const +{ + return d->m_dialogGui; +} + +/*! + \internal + + Sets the dialog GUI used by the form editor to the specified \a dialogGui. +*/ + +void QDesignerFormEditorInterface::setDialogGui(QDesignerDialogGuiInterface *dialogGui) +{ + delete d->m_dialogGui; + d->m_dialogGui = dialogGui; +} + +/*! + \internal + + \since 5.0 + + Returns the plugin instances of QDesignerPluginManager. +*/ + +QObjectList QDesignerFormEditorInterface::pluginInstances() const +{ + return d->m_pluginManager->instances(); +} + +/*! + \internal + + \since 5.0 + + Return icons for actions of \QD. +*/ + +QIcon QDesignerFormEditorInterface::createIcon(const QString &name) +{ + return qdesigner_internal::createIconSet(name); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractformeditor.h b/src/tools/designer/src/lib/sdk/abstractformeditor.h new file mode 100644 index 00000000000..6512f7c046e --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformeditor.h @@ -0,0 +1,100 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMEDITOR_H +#define ABSTRACTFORMEDITOR_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerWidgetBoxInterface; +class QDesignerPropertyEditorInterface; +class QDesignerFormWindowManagerInterface; +class QDesignerWidgetDataBaseInterface; +class QDesignerMetaDataBaseInterface; +class QDesignerWidgetFactoryInterface; +class QDesignerObjectInspectorInterface; +class QDesignerPromotionInterface; +class QDesignerActionEditorInterface; +class QDesignerIntegrationInterface; +class QDesignerPluginManager; +class QDesignerIntrospectionInterface; +class QDesignerDialogGuiInterface; +class QDesignerSettingsInterface; +class QDesignerOptionsPageInterface; +class QtResourceModel; +class QtGradientManager; + +class QWidget; +class QIcon; + +class QExtensionManager; + +class QDesignerFormEditorInterfacePrivate; + +class QDESIGNER_SDK_EXPORT QDesignerFormEditorInterface : public QObject +{ + Q_OBJECT +public: + explicit QDesignerFormEditorInterface(QObject *parent = nullptr); + virtual ~QDesignerFormEditorInterface(); + + QExtensionManager *extensionManager() const; + + QWidget *topLevel() const; + QDesignerWidgetBoxInterface *widgetBox() const; + QDesignerPropertyEditorInterface *propertyEditor() const; + QDesignerObjectInspectorInterface *objectInspector() const; + QDesignerFormWindowManagerInterface *formWindowManager() const; + QDesignerWidgetDataBaseInterface *widgetDataBase() const; + QDesignerMetaDataBaseInterface *metaDataBase() const; + QDesignerPromotionInterface *promotion() const; + QDesignerWidgetFactoryInterface *widgetFactory() const; + QDesignerActionEditorInterface *actionEditor() const; + QDesignerIntegrationInterface *integration() const; + QDesignerPluginManager *pluginManager() const; + QDesignerIntrospectionInterface *introspection() const; + QDesignerDialogGuiInterface *dialogGui() const; + QDesignerSettingsInterface *settingsManager() const; + QString resourceLocation() const; + QtResourceModel *resourceModel() const; + QtGradientManager *gradientManager() const; + QList optionsPages() const; + + void setTopLevel(QWidget *topLevel); + void setWidgetBox(QDesignerWidgetBoxInterface *widgetBox); + void setPropertyEditor(QDesignerPropertyEditorInterface *propertyEditor); + void setObjectInspector(QDesignerObjectInspectorInterface *objectInspector); + void setPluginManager(QDesignerPluginManager *pluginManager); + void setActionEditor(QDesignerActionEditorInterface *actionEditor); + void setIntegration(QDesignerIntegrationInterface *integration); + void setIntrospection(QDesignerIntrospectionInterface *introspection); + void setDialogGui(QDesignerDialogGuiInterface *dialogGui); + void setSettingsManager(QDesignerSettingsInterface *settingsManager); + void setResourceModel(QtResourceModel *model); + void setGradientManager(QtGradientManager *manager); + void setOptionsPages(const QList &optionsPages); + + QObjectList pluginInstances() const; + + static QIcon createIcon(const QString &name); + +protected: + void setFormManager(QDesignerFormWindowManagerInterface *formWindowManager); + void setMetaDataBase(QDesignerMetaDataBaseInterface *metaDataBase); + void setWidgetDataBase(QDesignerWidgetDataBaseInterface *widgetDataBase); + void setPromotion(QDesignerPromotionInterface *promotion); + void setWidgetFactory(QDesignerWidgetFactoryInterface *widgetFactory); + void setExtensionManager(QExtensionManager *extensionManager); + +private: + QScopedPointer d; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMEDITOR_H diff --git a/src/tools/designer/src/lib/sdk/abstractformeditorplugin.cpp b/src/tools/designer/src/lib/sdk/abstractformeditorplugin.cpp new file mode 100644 index 00000000000..af4f49ecf82 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformeditorplugin.cpp @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include + +QT_BEGIN_NAMESPACE + +/*! + \internal + \class QDesignerFormEditorPluginInterface + \brief The QDesignerFormEditorPluginInterface class provides an interface that is used to + manage plugins for \QD's form editor component. + \inmodule QtDesigner + + \sa QDesignerFormEditorInterface +*/ + +/*! + \fn virtual QDesignerFormEditorPluginInterface::~QDesignerFormEditorPluginInterface() + + Destroys the plugin interface. +*/ + +/*! + \fn virtual bool QDesignerFormEditorPluginInterface::isInitialized() const = 0 + + Returns true if the plugin interface is initialized; otherwise returns false. +*/ + +/*! + \fn virtual void QDesignerFormEditorPluginInterface::initialize(QDesignerFormEditorInterface *core) = 0 + + Initializes the plugin interface for the specified \a core interface. +*/ + +/*! + \fn virtual QAction *QDesignerFormEditorPluginInterface::action() const = 0 + + Returns the action associated with this interface. +*/ + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerFormEditorPluginInterface::core() const = 0 + + Returns the core form editor interface associated with this component. +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractformeditorplugin.h b/src/tools/designer/src/lib/sdk/abstractformeditorplugin.h new file mode 100644 index 00000000000..eab46768ab6 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformeditorplugin.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMEDITORPLUGIN_H +#define ABSTRACTFORMEDITORPLUGIN_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QAction; + +class QDESIGNER_SDK_EXPORT QDesignerFormEditorPluginInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerFormEditorPluginInterface) + + QDesignerFormEditorPluginInterface() = default; + virtual ~QDesignerFormEditorPluginInterface() = default; + + virtual bool isInitialized() const = 0; + virtual void initialize(QDesignerFormEditorInterface *core) = 0; + virtual QAction *action() const = 0; + + virtual QDesignerFormEditorInterface *core() const = 0; +}; +Q_DECLARE_INTERFACE(QDesignerFormEditorPluginInterface, "org.qt-project.Qt.Designer.QDesignerFormEditorPluginInterface") + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMEDITORPLUGIN_H diff --git a/src/tools/designer/src/lib/sdk/abstractformwindow.cpp b/src/tools/designer/src/lib/sdk/abstractformwindow.cpp new file mode 100644 index 00000000000..1835f81ea61 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformwindow.cpp @@ -0,0 +1,867 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindow.h" +#include "qtresourcemodel_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerFormWindowInterface + + \brief The QDesignerFormWindowInterface class allows you to query + and manipulate form windows appearing in \QD's workspace. + + \inmodule QtDesigner + + QDesignerFormWindowInterface provides information about + the associated form window as well as allowing its properties to be + altered. The interface is not intended to be instantiated + directly, but to provide access to \QD's current form windows + controlled by \QD's \l {QDesignerFormWindowManagerInterface}{form + window manager}. + + If you are looking for the form window containing a specific + widget, you can use the static + QDesignerFormWindowInterface::findFormWindow() function: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindow.cpp 0 + + But in addition, you can access any of the current form windows + through \QD's form window manager: Use the + QDesignerFormEditorInterface::formWindowManager() function to + retrieve an interface to the manager. Once you have this + interface, you have access to all of \QD's current form windows + through the QDesignerFormWindowManagerInterface::formWindow() + function. For example: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindow.cpp 1 + + The pointer to \QD's current QDesignerFormEditorInterface object + (\c formEditor in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface class to expose your + plugin to \QD. + + Once you have the form window, you can query its properties. For + example, a plain custom widget plugin is managed by \QD only at + its top level, i.e. none of its child widgets can be resized in + \QD's workspace. But QDesignerFormWindowInterface provides you + with functions that enables you to control whether a widget should + be managed by \QD, or not: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindow.cpp 2 + + The complete list of functions concerning widget management is: + isManaged(), manageWidget() and unmanageWidget(). There is also + several associated signals: widgetManaged(), widgetRemoved(), + aboutToUnmanageWidget() and widgetUnmanaged(). + + In addition to controlling the management of widgets, you can + control the current selection in the form window using the + selectWidget(), clearSelection() and emitSelectionChanged() + functions, and the selectionChanged() signal. + + You can also retrieve information about where the form is stored + using absoluteDir(), its include files using includeHints(), and + its layout and pixmap functions using layoutDefault(), + layoutFunction() and pixmapFunction(). You can find out whether + the form window has been modified (but not saved) or not, using + the isDirty() function. You can retrieve its author(), its + contents(), its fileName(), associated comment() and + exportMacro(), its mainContainer(), its features(), its grid() and + its resourceFiles(). + + The interface provides you with functions and slots allowing you + to alter most of this information as well. The exception is the + directory storing the form window. Finally, there is several + signals associated with changes to the information mentioned above + and to the form window in general. + + \sa QDesignerFormWindowCursorInterface, + QDesignerFormEditorInterface, QDesignerFormWindowManagerInterface +*/ + +/*! + \enum QDesignerFormWindowInterface::FeatureFlag + + This enum describes the features that are available and can be + controlled by the form window interface. These values are used + when querying the form window to determine which features it + supports: + + \value EditFeature Form editing + \value GridFeature Grid display and snap-to-grid facilities for editing + \value TabOrderFeature Tab order management + \value DefaultFeature Support for default features (form editing and grid) + + \sa hasFeature(), features() +*/ + +/*! + \enum QDesignerFormWindowInterface::ResourceFileSaveMode + \since 5.0 + + This enum describes how resource files are saved. + + + \value SaveAllResourceFiles Save all resource files. + \value SaveOnlyUsedResourceFiles Save resource files used by form. + \value DontSaveResourceFiles Do not save resource files. +*/ + +/*! + Constructs a form window interface with the given \a parent and + the specified window \a flags. +*/ +QDesignerFormWindowInterface::QDesignerFormWindowInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the form window interface. +*/ +QDesignerFormWindowInterface::~QDesignerFormWindowInterface() = default; + +/*! + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ +QDesignerFormEditorInterface *QDesignerFormWindowInterface::core() const +{ + return nullptr; +} + +/*! + \fn QDesignerFormWindowInterface *QDesignerFormWindowInterface::findFormWindow(QWidget *widget) + + Returns the form window interface for the given \a widget. +*/ + +static inline bool stopFindAtTopLevel(const QObject *w, bool stopAtMenu) +{ + // Do we need to go beyond top levels when looking for the form window? + // 1) A dialog has a window attribute at the moment it is created + // before it is properly embedded into a form window. The property + // sheet queries the layout attributes precisely at this moment. + // 2) In the case of floating docks and toolbars, we also need to go beyond the top level window. + // 3) In the case of menu editing, we don't want to block events from the + // Designer menu, so, we say stop. + // Note that there must be no false positives for dialogs parented on + // the form (for example, the "change object name" dialog), else, its + // events will be blocked. + + if (stopAtMenu && w->inherits("QDesignerMenu")) + return true; + return !qdesigner_internal::WidgetFactory::isFormEditorObject(w); +} + +QDesignerFormWindowInterface *QDesignerFormWindowInterface::findFormWindow(QWidget *w) +{ + while (w != nullptr) { + if (QDesignerFormWindowInterface *fw = qobject_cast(w)) + return fw; + if (w->isWindow() && stopFindAtTopLevel(w, true)) + break; + + w = w->parentWidget(); + } + + return nullptr; +} + +/*! + \fn QDesignerFormWindowInterface *QDesignerFormWindowInterface::findFormWindow(QObject *object) + + Returns the form window interface for the given \a object. + + \since 4.4 +*/ + +QDesignerFormWindowInterface *QDesignerFormWindowInterface::findFormWindow(QObject *object) +{ + while (object != nullptr) { + if (QDesignerFormWindowInterface *fw = qobject_cast(object)) + return fw; + + QWidget *w = qobject_cast(object); + // QDesignerMenu is a window, so stopFindAtTopLevel(w) returns 0. + // However, we want to find the form window for QActions of a menu. + // If this check is inside stopFindAtTopLevel(w), it will break designer + // menu editing (e.g. when clicking on items inside an opened menu) + if (w && w->isWindow() && stopFindAtTopLevel(w, false)) + break; + + object = object->parent(); + } + + return nullptr; +} + +/*! + \fn virtual QtResourceSet *QDesignerFormWindowInterface::resourceSet() const + + Returns the resource set used by the form window interface. + + \since 5.0 + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setResourceSet(QtResourceSet *resourceSet) + + Sets the resource set used by the form window interface to \a resourceSet. + + \since 5.0 + \internal +*/ + +/*! + Returns the active resource (.qrc) file paths currently loaded in \QD. + \since 5.0 + \sa activateResourceFilePaths() +*/ + +QStringList QDesignerFormWindowInterface::activeResourceFilePaths() const +{ + return resourceSet()->activeResourceFilePaths(); +} + +/*! + Activates the resource (.qrc) file paths \a paths, returning the count of errors in \a errorCount and + error message in \a errorMessages. \QD loads the resources using the QResource class, + making them available for form editing. + + In IDE integrations, a list of the project's resource (.qrc) file can be activated, making + them available to \QD. + + \since 5.0 + \sa activeResourceFilePaths() + \sa QResource +*/ + +void QDesignerFormWindowInterface::activateResourceFilePaths(const QStringList &paths, int *errorCount, QString *errorMessages) +{ + resourceSet()->activateResourceFilePaths(paths, errorCount, errorMessages); +} + +/*! + \fn virtual QString QDesignerFormWindowInterface::fileName() const + + Returns the file name of the UI file that describes the form + currently being shown. + + \sa setFileName() +*/ + +/*! + \fn virtual QDir QDesignerFormWindowInterface::absoluteDir() const + + Returns the absolute location of the directory containing the form + shown in the form window. +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::contents() const + + Returns details of the contents of the form currently being + displayed in the window. +*/ + +/*! + \fn virtual QStringList QDesignerFormWindowInterface::checkContents() const + + Performs checks on the current form and returns a list of richtext warnings + about potential issues (for example, top level spacers on unlaid-out forms). + + IDE integrations can call this before handling starting a save operation. + + \since 5.0 +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::setContents(QIODevice *device, QString *errorMessage = 0) + + Sets the form's contents using data obtained from the given \a device and returns whether + loading succeeded. If it fails, the error message is returned in \a errorMessage. + + Data can be read from QFile objects or any other subclass of QIODevice. +*/ + +/*! + \fn virtual Feature QDesignerFormWindowInterface::features() const + + Returns a combination of the features provided by the form window + associated with the interface. The value returned can be tested + against the \l Feature enum values to determine which features are + supported by the window. + + \sa setFeatures(), hasFeature() +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::hasFeature(Feature feature) const + + Returns true if the form window offers the specified \a feature; + otherwise returns false. + + \sa features() +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::author() const + + Returns details of the author or creator of the form currently + being displayed in the window. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setAuthor(const QString &author) + + Sets the details for the author or creator of the form to the \a + author specified. +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::comment() const + + Returns comments about the form currently being displayed in the window. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setComment(const QString &comment) + + Sets the information about the form to the \a comment + specified. This information should be a human-readable comment + about the form. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::layoutDefault(int *margin, int *spacing) + + Fills in the default margin and spacing for the form's default + layout in the \a margin and \a spacing variables specified. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setLayoutDefault(int margin, int spacing) + + Sets the default \a margin and \a spacing for the form's layout. + + \sa layoutDefault() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::layoutFunction(QString *margin, QString *spacing) + + Fills in the current margin and spacing for the form's layout in + the \a margin and \a spacing variables specified. +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setLayoutFunction(const QString &margin, const QString &spacing) + + Sets the \a margin and \a spacing for the form's layout. + + The default layout properties will be replaced by the + corresponding layout functions when \c uic generates code for the + form, that is, if the functions are specified. This is useful when + different environments requires different layouts for the same + form. + + \sa layoutFunction() +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::pixmapFunction() const + + Returns the name of the function used to load pixmaps into the + form window. + + \sa setPixmapFunction() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setPixmapFunction(const QString &pixmapFunction) + + Sets the function used to load pixmaps into the form window + to the given \a pixmapFunction. + + \sa pixmapFunction() +*/ + +/*! + \fn virtual QString QDesignerFormWindowInterface::exportMacro() const + + Returns the export macro associated with the form currently being + displayed in the window. The export macro is used when the form + is compiled to create a widget plugin. + + \sa {Creating Custom Widgets for Qt Widgets Designer} +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setExportMacro(const QString &exportMacro) + + Sets the form window's export macro to \a exportMacro. The export + macro is used when building a widget plugin to export the form's + interface to other components. +*/ + +/*! + \fn virtual QStringList QDesignerFormWindowInterface::includeHints() const + + Returns a list of the header files that will be included in the + form window's associated UI file. + + Header files may be local, i.e. relative to the project's + directory, \c "mywidget.h", or global, i.e. part of Qt or the + compilers standard libraries: \c . + + \sa setIncludeHints() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setIncludeHints(const QStringList &includeHints) + + Sets the header files that will be included in the form window's + associated UI file to the specified \a includeHints. + + Header files may be local, i.e. relative to the project's + directory, \c "mywidget.h", or global, i.e. part of Qt or the + compilers standard libraries: \c . + + \sa includeHints() +*/ + +/*! + \fn virtual QDesignerFormWindowCursorInterface *QDesignerFormWindowInterface::cursor() const + + Returns the cursor interface used by the form window. +*/ + +/*! + \fn virtual int QDesignerFormWindowInterface::toolCount() const + + Returns the number of tools available. + + \internal +*/ + +/*! + \fn virtual int QDesignerFormWindowInterface::currentTool() const + + Returns the index of the current tool in use. + + \sa setCurrentTool() + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setCurrentTool(int index) + + Sets the current tool to be the one with the given \a index. + + \sa currentTool() + + \internal +*/ + +/*! + \fn virtual QDesignerFormWindowToolInterface *QDesignerFormWindowInterface::tool(int index) const + + Returns an interface to the tool with the given \a index. + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::registerTool(QDesignerFormWindowToolInterface *tool) + + Registers the given \a tool with the form window. + + \internal +*/ + +/*! + \fn virtual QPoint QDesignerFormWindowInterface::grid() const = 0 + + Returns the grid spacing used by the form window. + + \sa setGrid() +*/ + +/*! + \fn virtual QWidget *QDesignerFormWindowInterface::mainContainer() const + + Returns the main container widget for the form window. + + \sa setMainContainer() + \internal +*/ + +/*! + \fn virtual QWidget *QDesignerFormWindowInterface::formContainer() const + + Returns the form the widget containing the main container widget. + + \since 5.0 +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setMainContainer(QWidget *mainContainer) + + Sets the main container widget on the form to the specified \a + mainContainer. + + \sa mainContainerChanged() +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::isManaged(QWidget *widget) const + + Returns true if the specified \a widget is managed by the form + window; otherwise returns false. + + \sa manageWidget() +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::isDirty() const + + Returns true if the form window is "dirty" (modified but not + saved); otherwise returns false. + + \sa setDirty() +*/ + +/*! + \fn virtual QUndoStack *QDesignerFormWindowInterface::commandHistory() const + + Returns an object that can be used to obtain the commands used so + far in the construction of the form. + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::beginCommand(const QString &description) + + Begins execution of a command with the given \a + description. Commands are executed between beginCommand() and + endCommand() function calls to ensure that they are recorded on + the undo stack. + + \sa endCommand() + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::endCommand() + + Ends execution of the current command. + + \sa beginCommand() + + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::simplifySelection(QList *widgets) const + + Simplifies the selection of widgets specified by \a widgets. + + \sa selectionChanged() + \internal +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::emitSelectionChanged() + + Emits the selectionChanged() signal. + + \sa selectWidget(), clearSelection() +*/ + +/*! + \fn virtual QStringList QDesignerFormWindowInterface::resourceFiles() const + + Returns a list of paths to resource files that are currently being + used by the form window. + + \sa addResourceFile(), removeResourceFile() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::addResourceFile(const QString &path) + + Adds the resource file at the given \a path to those used by the form. + + \sa resourceFiles(), resourceFilesChanged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::removeResourceFile(const QString &path) + + Removes the resource file at the specified \a path from the list + of those used by the form. + + \sa resourceFiles(), resourceFilesChanged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::ensureUniqueObjectName(QObject *object) + + Ensures that the specified \a object has a unique name amongst the + other objects on the form. + + \internal +*/ + +// Slots + +/*! + \fn virtual void QDesignerFormWindowInterface::manageWidget(QWidget *widget) + + Instructs the form window to manage the specified \a widget. + + \sa isManaged(), unmanageWidget(), widgetManaged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::unmanageWidget(QWidget *widget) + + Instructs the form window not to manage the specified \a widget. + + \sa aboutToUnmanageWidget(), widgetUnmanaged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setFeatures(Feature features) + + Enables the specified \a features for the form window. + + \sa features(), featureChanged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setDirty(bool dirty) + + If \a dirty is true, the form window is marked as dirty, meaning + that it is modified but not saved. If \a dirty is false, the form + window is considered to be unmodified. + + \sa isDirty() +*/ + +/*! +\fn virtual void QDesignerFormWindowInterface::clearSelection(bool update) + + Clears the current selection in the form window. If \a update is + true, the emitSelectionChanged() function is called, emitting the + selectionChanged() signal. + + \sa selectWidget() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::selectWidget(QWidget *widget, bool select) + + If \a select is true, the given \a widget is selected; otherwise + the \a widget is deselected. + + \sa clearSelection(), selectionChanged() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setGrid(const QPoint &grid) + + Sets the grid size for the form window to the point specified by + \a grid. In this function, the coordinates in the QPoint are used + to specify the dimensions of a rectangle in the grid. + + \sa grid() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::setFileName(const QString &fileName) + + Sets the file name for the form to the given \a fileName. + + \sa fileName(), fileNameChanged() +*/ + +/*! + \fn virtual bool QDesignerFormWindowInterface::setContents(const QString &contents) + + Sets the contents of the form using data read from the specified + \a contents string and returns whether the operation succeeded. + + \sa contents() +*/ + +/*! + \fn virtual void QDesignerFormWindowInterface::editWidgets() + + Switches the form window into editing mode. + + \sa {Qt Widgets Designer's Form Editing Mode} + + \internal +*/ + +// Signals + +/*! + \fn void QDesignerFormWindowInterface::mainContainerChanged(QWidget *mainContainer) + + This signal is emitted whenever the main container changes. + The new container is specified by \a mainContainer. + + \sa setMainContainer() +*/ + +/*! + \fn void QDesignerFormWindowInterface::toolChanged(int toolIndex) + + This signal is emitted whenever the current tool changes. + The specified \a toolIndex is the index of the new tool in the list of + tools in the widget box. + + \internal +*/ + +/*! + \fn void QDesignerFormWindowInterface::fileNameChanged(const QString &fileName) + + This signal is emitted whenever the file name of the form changes. + The new file name is specified by \a fileName. + + \sa setFileName() +*/ + +/*! + \fn void QDesignerFormWindowInterface::featureChanged(Feature feature) + + This signal is emitted whenever a feature changes in the form. + The new feature is specified by \a feature. + + \sa setFeatures() +*/ + +/*! + \fn void QDesignerFormWindowInterface::selectionChanged() + + This signal is emitted whenever the selection in the form changes. + + \sa selectWidget(), clearSelection() +*/ + +/*! + \fn void QDesignerFormWindowInterface::geometryChanged() + + This signal is emitted whenever the form's geometry changes. +*/ + +/*! + \fn void QDesignerFormWindowInterface::resourceFilesChanged() + + This signal is emitted whenever the list of resource files used by the + form changes. + + \sa resourceFiles() +*/ + +/*! + \fn ResourceFileSaveMode QDesignerFormWindowInterface::resourceFileSaveMode() const + + Returns the resource file save mode behavior. + + \sa setResourceFileSaveMode() +*/ + +/*! + \fn void QDesignerFormWindowInterface::setResourceFileSaveMode(ResourceFileSaveMode behavior) + + Sets the resource file save mode \a behavior. + + \sa resourceFileSaveMode() +*/ + +/*! + \fn void QDesignerFormWindowInterface::widgetManaged(QWidget *widget) + + This signal is emitted whenever a widget on the form becomes managed. + The newly managed widget is specified by \a widget. + + \sa manageWidget() +*/ + +/*! + \fn void QDesignerFormWindowInterface::widgetUnmanaged(QWidget *widget) + + This signal is emitted whenever a widget on the form becomes unmanaged. + The newly released widget is specified by \a widget. + + \sa unmanageWidget(), aboutToUnmanageWidget() +*/ + +/*! + \fn void QDesignerFormWindowInterface::aboutToUnmanageWidget(QWidget *widget) + + This signal is emitted whenever a widget on the form is about to + become unmanaged. When this signal is emitted, the specified \a + widget is still managed, and a widgetUnmanaged() signal will + follow, indicating when it is no longer managed. + + \sa unmanageWidget(), isManaged() +*/ + +/*! + \fn void QDesignerFormWindowInterface::activated(QWidget *widget) + + This signal is emitted whenever a widget is activated on the form. + The activated widget is specified by \a widget. +*/ + +/*! + \fn void QDesignerFormWindowInterface::changed() + + This signal is emitted whenever a form is changed. +*/ + +/*! + \fn void QDesignerFormWindowInterface::widgetRemoved(QWidget *widget) + + This signal is emitted whenever a widget is removed from the form. + The widget that was removed is specified by \a widget. +*/ + +/*! + \fn void QDesignerFormWindowInterface::objectRemoved(QObject *object) + + This signal is emitted whenever an object (such as + an action or a QButtonGroup) is removed from the form. + The object that was removed is specified by \a object. + + \since 4.5 +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractformwindow.h b/src/tools/designer/src/lib/sdk/abstractformwindow.h new file mode 100644 index 00000000000..441ae3ab66a --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformwindow.h @@ -0,0 +1,159 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMWINDOW_H +#define ABSTRACTFORMWINDOW_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowCursorInterface; +class QDesignerFormWindowToolInterface; +class QUndoStack; +class QDir; +class QtResourceSet; + +class QDESIGNER_SDK_EXPORT QDesignerFormWindowInterface: public QWidget +{ + Q_OBJECT +public: + enum FeatureFlag + { + EditFeature = 0x01, + GridFeature = 0x02, + TabOrderFeature = 0x04, + DefaultFeature = EditFeature | GridFeature + }; + Q_DECLARE_FLAGS(Feature, FeatureFlag) + + enum ResourceFileSaveMode + { + SaveAllResourceFiles, + SaveOnlyUsedResourceFiles, + DontSaveResourceFiles + }; + Q_ENUM(ResourceFileSaveMode) + + explicit QDesignerFormWindowInterface(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + virtual ~QDesignerFormWindowInterface(); + + virtual QString fileName() const = 0; + virtual QDir absoluteDir() const = 0; + + virtual QString contents() const = 0; + virtual QStringList checkContents() const = 0; + virtual bool setContents(QIODevice *dev, QString *errorMessage = nullptr) = 0; + + virtual Feature features() const = 0; + virtual bool hasFeature(Feature f) const = 0; + + virtual QString author() const = 0; + virtual void setAuthor(const QString &author) = 0; + + virtual QString comment() const = 0; + virtual void setComment(const QString &comment) = 0; + + virtual void layoutDefault(int *margin, int *spacing) = 0; + virtual void setLayoutDefault(int margin, int spacing) = 0; + + virtual void layoutFunction(QString *margin, QString *spacing) = 0; + virtual void setLayoutFunction(const QString &margin, const QString &spacing) = 0; + + virtual QString pixmapFunction() const = 0; + virtual void setPixmapFunction(const QString &pixmapFunction) = 0; + + virtual QString exportMacro() const = 0; + virtual void setExportMacro(const QString &exportMacro) = 0; + + virtual QStringList includeHints() const = 0; + virtual void setIncludeHints(const QStringList &includeHints) = 0; + + virtual ResourceFileSaveMode resourceFileSaveMode() const = 0; + virtual void setResourceFileSaveMode(ResourceFileSaveMode behaviour) = 0; + + virtual QtResourceSet *resourceSet() const = 0; + virtual void setResourceSet(QtResourceSet *resourceSet) = 0; + + QStringList activeResourceFilePaths() const; + + virtual QDesignerFormEditorInterface *core() const; + virtual QDesignerFormWindowCursorInterface *cursor() const = 0; + + virtual int toolCount() const = 0; + + virtual int currentTool() const = 0; + virtual void setCurrentTool(int index) = 0; + + virtual QDesignerFormWindowToolInterface *tool(int index) const = 0; + virtual void registerTool(QDesignerFormWindowToolInterface *tool) = 0; + + virtual QPoint grid() const = 0; + + virtual QWidget *mainContainer() const = 0; + virtual void setMainContainer(QWidget *mainContainer) = 0; + virtual QWidget *formContainer() const = 0; + + virtual bool isManaged(QWidget *widget) const = 0; + + virtual bool isDirty() const = 0; + + static QDesignerFormWindowInterface *findFormWindow(QWidget *w); + static QDesignerFormWindowInterface *findFormWindow(QObject *obj); + + virtual QUndoStack *commandHistory() const = 0; + virtual void beginCommand(const QString &description) = 0; + virtual void endCommand() = 0; + + virtual void simplifySelection(QList *widgets) const = 0; + + // notifications + virtual void emitSelectionChanged() = 0; + + virtual QStringList resourceFiles() const = 0; + virtual void addResourceFile(const QString &path) = 0; + virtual void removeResourceFile(const QString &path) = 0; + + virtual void ensureUniqueObjectName(QObject *object) = 0; + +public Q_SLOTS: + virtual void manageWidget(QWidget *widget) = 0; + virtual void unmanageWidget(QWidget *widget) = 0; + + virtual void setFeatures(Feature f) = 0; + virtual void setDirty(bool dirty) = 0; + virtual void clearSelection(bool changePropertyDisplay = true) = 0; + virtual void selectWidget(QWidget *w, bool select = true) = 0; + virtual void setGrid(const QPoint &grid) = 0; + virtual void setFileName(const QString &fileName) = 0; + virtual bool setContents(const QString &contents) = 0; + + virtual void editWidgets() = 0; + void activateResourceFilePaths(const QStringList &paths, int *errorCount = nullptr, QString *errorMessages = nullptr); + +Q_SIGNALS: + void mainContainerChanged(QWidget *mainContainer); + void toolChanged(int toolIndex); + void fileNameChanged(const QString &fileName); + void featureChanged(Feature f); + void selectionChanged(); + void geometryChanged(); + + void resourceFilesChanged(); + + void widgetManaged(QWidget *widget); + void widgetUnmanaged(QWidget *widget); + void aboutToUnmanageWidget(QWidget *widget); + void activated(QWidget *widget); + + void changed(); + void widgetRemoved(QWidget *w); + void objectRemoved(QObject *o); +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMWINDOW_H diff --git a/src/tools/designer/src/lib/sdk/abstractformwindowcursor.cpp b/src/tools/designer/src/lib/sdk/abstractformwindowcursor.cpp new file mode 100644 index 00000000000..9c4c135d04b --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformwindowcursor.cpp @@ -0,0 +1,214 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindowcursor.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerFormWindowCursorInterface + + \brief The QDesignerFormWindowCursorInterface class allows you to + query and modify a form window's widget selection, and in addition + modify the properties of all the form's widgets. + + \inmodule QtDesigner + + QDesignerFormWindowCursorInterface is a convenience class that + provides an interface to the associated form window's text cursor; + it provides a collection of functions that enables you to query a + given form window's selection and change the selection's focus + according to defined modes (MoveMode) and movements + (MoveOperation). You can also use the interface to query the + form's widgets and change their properties. + + The interface is not intended to be instantiated directly, but to + provide access to the selections and widgets of \QD's current form + windows. QDesignerFormWindowInterface always provides an + associated cursor interface. The form window for a given widget + can be retrieved using the static + QDesignerFormWindowInterface::findFormWindow() functions. For + example: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindowcursor.cpp 0 + + You can retrieve any of \QD's current form windows through + \QD's \l {QDesignerFormWindowManagerInterface}{form window + manager}. + + Once you have a form window's cursor interface, you can check if + the form window has a selection at all using the hasSelection() + function. You can query the form window for its total + widgetCount() and selectedWidgetCount(). You can retrieve the + currently selected widget (or widgets) using the current() or + selectedWidget() functions. + + You can retrieve any of the form window's widgets using the + widget() function, and check if a widget is selected using the + isWidgetSelected() function. You can use the setProperty() + function to set the selected widget's properties, and the + setWidgetProperty() or resetWidgetProperty() functions to modify + the properties of any given widget. + + Finally, you can change the selection by changing the text + cursor's position() using the setPosition() and movePosition() + functions. + + \sa QDesignerFormWindowInterface, QDesignerFormWindowManagerInterface +*/ + +/*! + \enum QDesignerFormWindowCursorInterface::MoveOperation + + This enum describes the types of text cursor operation that can occur in a form window. + + \value NoMove The cursor does not move. + \value Start Moves the cursor to the start of the focus chain. + \value End Moves the cursor to the end of the focus chain. + \value Next Moves the cursor to the next widget in the focus chain. + \value Prev Moves the cursor to the previous widget in the focus chain. + \value Left The cursor moves to the left. + \value Right The cursor moves to the right. + \value Up The cursor moves upwards. + \value Down The cursor moves downwards. +*/ + +/*! + \enum QDesignerFormWindowCursorInterface::MoveMode + + This enum describes the different modes that are used when the text cursor moves. + + \value MoveAnchor The anchor moves with the cursor to its new location. + \value KeepAnchor The anchor remains at the cursor's old location. +*/ + +/*! + Returns true if the specified \a widget is selected; otherwise + returns false. +*/ +bool QDesignerFormWindowCursorInterface::isWidgetSelected(QWidget *widget) const +{ + for (int index=0; index + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QWidget; +class QVariant; +class QString; + +class QDESIGNER_SDK_EXPORT QDesignerFormWindowCursorInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerFormWindowCursorInterface) + + enum MoveOperation + { + NoMove, + + Start, + End, + Next, + Prev, + Left, + Right, + Up, + Down + }; + + enum MoveMode + { + MoveAnchor, + KeepAnchor + }; + + QDesignerFormWindowCursorInterface() = default; + virtual ~QDesignerFormWindowCursorInterface() = default; + + virtual QDesignerFormWindowInterface *formWindow() const = 0; + + virtual bool movePosition(MoveOperation op, MoveMode mode = MoveAnchor) = 0; + + virtual int position() const = 0; + virtual void setPosition(int pos, MoveMode mode = MoveAnchor) = 0; + + virtual QWidget *current() const = 0; + + virtual int widgetCount() const = 0; + virtual QWidget *widget(int index) const = 0; + + virtual bool hasSelection() const = 0; + virtual int selectedWidgetCount() const = 0; + virtual QWidget *selectedWidget(int index) const = 0; + + virtual void setProperty(const QString &name, const QVariant &value) = 0; + virtual void setWidgetProperty(QWidget *widget, const QString &name, const QVariant &value) = 0; + virtual void resetWidgetProperty(QWidget *widget, const QString &name) = 0; + + bool isWidgetSelected(QWidget *widget) const; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMWINDOWCURSOR_H diff --git a/src/tools/designer/src/lib/sdk/abstractformwindowmanager.cpp b/src/tools/designer/src/lib/sdk/abstractformwindowmanager.cpp new file mode 100644 index 00000000000..9e8494ca475 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformwindowmanager.cpp @@ -0,0 +1,538 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindowmanager.h" + +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerFormWindowManagerInterface + + \brief The QDesignerFormWindowManagerInterface class allows you to + manipulate the collection of form windows in \QD, and + control \QD's form editing actions. + + \inmodule QtDesigner + + QDesignerFormWindowManagerInterface is not intended to be + instantiated directly. \QD uses the form window manager to + control the various form windows in its workspace. You can + retrieve an interface to \QD's form window manager using + the QDesignerFormEditorInterface::formWindowManager() + function. For example: + + \snippet lib/tools_designer_src_lib_sdk_abstractformwindowmanager.cpp 0 + + When implementing a custom widget plugin, a pointer to \QD's + current QDesignerFormEditorInterface object (\c formEditor in the + example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's parameter. + You must subclass the QDesignerCustomWidgetInterface to expose + your plugin to \QD. + + The form window manager interface provides the createFormWindow() + function that enables you to create a new form window which you + can add to the collection of form windows that the manager + maintains, using the addFormWindow() slot. It also provides the + formWindowCount() function returning the number of form windows + currently under the manager's control, the formWindow() function + returning the form window associated with a given index, and the + activeFormWindow() function returning the currently selected form + window. The removeFormWindow() slot allows you to reduce the + number of form windows the manager must maintain, and the + setActiveFormWindow() slot allows you to change the form window + focus in \QD's workspace. + + In addition, QDesignerFormWindowManagerInterface contains a + collection of functions that enables you to intervene and control + \QD's form editing actions. All these functions return the + original action, making it possible to propagate the function + further after intervention. + + Finally, the interface provides three signals which are emitted + when a form window is added, when the currently selected form + window changes, or when a form window is removed, respectively. All + the signals carry the form window in question as their parameter. + + \sa QDesignerFormEditorInterface, QDesignerFormWindowInterface +*/ + +/*! + \enum QDesignerFormWindowManagerInterface::Action + + Specifies an action of \QD. + + \sa action() + + \since 5.0 + \value CutAction Clipboard Cut + \value CopyAction Clipboard Copy + \value PasteAction Clipboard Paste + \value DeleteAction Clipboard Delete + \value SelectAllAction Select All + \value LowerAction Lower current widget + \value RaiseAction Raise current widget + \value UndoAction Undo + \value RedoAction Redo + \value HorizontalLayoutAction Lay out using QHBoxLayout + \value VerticalLayoutAction Lay out using QVBoxLayout + \value SplitHorizontalAction Lay out in horizontal QSplitter + \value SplitVerticalAction Lay out in vertical QSplitter + \value GridLayoutAction Lay out using QGridLayout + \value FormLayoutAction Lay out using QFormLayout + \value BreakLayoutAction Break existing layout + \value AdjustSizeAction Adjust size + \value SimplifyLayoutAction Simplify QGridLayout or QFormLayout + \value DefaultPreviewAction Create a preview in default style + \value FormWindowSettingsDialogAction Show dialog with form settings +*/ + +/*! + \enum QDesignerFormWindowManagerInterface::ActionGroup + + Specifies an action group of \QD. + + \sa actionGroup() + \since 5.0 + \value StyledPreviewActionGroup Action group containing styled preview actions +*/ + +/*! + Constructs an interface with the given \a parent for the form window + manager. +*/ +QDesignerFormWindowManagerInterface::QDesignerFormWindowManagerInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the interface for the form window manager. +*/ +QDesignerFormWindowManagerInterface::~QDesignerFormWindowManagerInterface() = default; + +/*! + Allows you to intervene and control \QD's "cut" action. The function + returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +#if QT_CONFIG(clipboard) +QAction *QDesignerFormWindowManagerInterface::actionCut() const +{ + return action(CutAction); +} +#endif + +/*! + Allows you to intervene and control \QD's "copy" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +#if QT_CONFIG(clipboard) +QAction *QDesignerFormWindowManagerInterface::actionCopy() const +{ + return action(CopyAction); +} +#endif + +/*! + Allows you to intervene and control \QD's "paste" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +#if QT_CONFIG(clipboard) +QAction *QDesignerFormWindowManagerInterface::actionPaste() const +{ + return action(PasteAction); +} +#endif + +/*! + Allows you to intervene and control \QD's "delete" action. The function + returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionDelete() const +{ + return action(DeleteAction); +} + +/*! + Allows you to intervene and control \QD's "select all" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionSelectAll() const +{ + return action(SelectAllAction); +} + +/*! + Allows you to intervene and control the action of lowering a form + window in \QD's workspace. The function returns the original + action. + + \sa QAction + \deprecated + + Use action() instead. +*/ + +QAction *QDesignerFormWindowManagerInterface::actionLower() const +{ + return action(LowerAction); +} + +/*! + Allows you to intervene and control the action of raising of a + form window in \QD's workspace. The function returns the original + action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionRaise() const +{ + return action(RaiseAction); +} + +/*! + Allows you to intervene and control a request for horizontal + layout for a form window in \QD's workspace. The function returns + the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionHorizontalLayout() const +{ + return action(HorizontalLayoutAction); +} + +/*! + Allows you to intervene and control a request for vertical layout + for a form window in \QD's workspace. The function returns the + original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionVerticalLayout() const +{ + return action(VerticalLayoutAction); +} + +/*! + Allows you to intervene and control \QD's "split horizontal" + action. The function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionSplitHorizontal() const +{ + return action(SplitHorizontalAction); +} + +/*! + Allows you to intervene and control \QD's "split vertical" + action. The function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionSplitVertical() const +{ + return action(SplitVerticalAction); +} + +/*! + Allows you to intervene and control a request for grid layout for + a form window in \QD's workspace. The function returns the + original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionGridLayout() const +{ + return action(GridLayoutAction); +} + +/*! + Allows you to intervene and control \QD's "form layout" action. The + function returns the original action. + + \sa QAction + \since 4.4 + \deprecated + + Use action() instead. +*/ + +QAction *QDesignerFormWindowManagerInterface::actionFormLayout() const +{ + return action(FormLayoutAction); +} + +/*! + Allows you to intervene and control \QD's "break layout" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionBreakLayout() const +{ + return action(BreakLayoutAction); +} + +/*! + Allows you to intervene and control \QD's "adjust size" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionAdjustSize() const +{ + return action(AdjustSizeAction); +} + +/*! + Allows you to intervene and control \QD's "simplify layout" action. The + function returns the original action. + + \sa QAction + \since 4.4 + \deprecated + + Use action() instead. +*/ + +QAction *QDesignerFormWindowManagerInterface::actionSimplifyLayout() const +{ + return action(SimplifyLayoutAction); +} + +/*! + \fn virtual QDesignerFormWindowInterface *QDesignerFormWindowManagerInterface::activeFormWindow() const + Returns the currently active form window in \QD's workspace. + + \sa setActiveFormWindow(), removeFormWindow() +*/ + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerFormWindowManagerInterface::core() const + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::addFormWindow(QDesignerFormWindowInterface *formWindow) + Adds the given \a formWindow to the collection of windows that + \QD's form window manager maintains. + + \sa formWindowAdded() +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::removeFormWindow(QDesignerFormWindowInterface *formWindow) + Removes the given \a formWindow from the collection of windows that + \QD's form window manager maintains. + + \sa formWindow(), formWindowRemoved() +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::setActiveFormWindow(QDesignerFormWindowInterface *formWindow) + Sets the given \a formWindow to be the currently active form window in + \QD's workspace. + + \sa activeFormWindow(), activeFormWindowChanged() +*/ + +/*! + \fn int QDesignerFormWindowManagerInterface::formWindowCount() const + Returns the number of form windows maintained by \QD's form window + manager. +*/ + +/*! + \fn QDesignerFormWindowInterface *QDesignerFormWindowManagerInterface::formWindow(int index) const + Returns the form window at the given \a index. + + \sa setActiveFormWindow(), removeFormWindow() +*/ + +/*! + \fn QDesignerFormWindowInterface *QDesignerFormWindowManagerInterface::createFormWindow(QWidget *parent, Qt::WindowFlags flags) + + Creates a form window with the given \a parent and the given window + \a flags. + + \sa addFormWindow() +*/ + +/*! + \fn QPixmap QDesignerFormWindowManagerInterface::createPreviewPixmap() const + + Creates a pixmap representing the preview of the currently active form. +*/ + +/*! + Allows you to intervene and control \QD's "undo" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionUndo() const +{ + return action(UndoAction); +} + +/*! + Allows you to intervene and control \QD's "redo" action. The + function returns the original action. + + \sa QAction + \deprecated + + Use action() instead. +*/ +QAction *QDesignerFormWindowManagerInterface::actionRedo() const +{ + return action(RedoAction); +} + +/*! + \fn void QDesignerFormWindowManagerInterface::formWindowAdded(QDesignerFormWindowInterface *formWindow) + + This signal is emitted when a new form window is added to the + collection of windows that \QD's form window manager maintains. A + pointer to the new \a formWindow is passed as an argument. + + \sa addFormWindow(), setActiveFormWindow() +*/ + +/*! + \fn void QDesignerFormWindowManagerInterface::formWindowSettingsChanged(QDesignerFormWindowInterface *formWindow) + + This signal is emitted when the settings of the form window change. It can be used to update + window titles, etc. accordingly. A pointer to the \a formWindow is passed as an argument. + + \sa FormWindowSettingsDialogAction +*/ + +/*! + \fn void QDesignerFormWindowManagerInterface::formWindowRemoved(QDesignerFormWindowInterface *formWindow) + + This signal is emitted when a form window is removed from the + collection of windows that \QD's form window manager maintains. A + pointer to the removed \a formWindow is passed as an argument. + + \sa removeFormWindow() +*/ + +/*! + \fn void QDesignerFormWindowManagerInterface::activeFormWindowChanged(QDesignerFormWindowInterface *formWindow) + + This signal is emitted when the contents of the currently active + form window in \QD's workspace changed. A pointer to the currently + active \a formWindow is passed as an argument. + + \sa activeFormWindow() +*/ + +/*! + \fn void QDesignerFormWindowManagerInterface::dragItems(const QList &item_list) + + \internal +*/ + +/*! + \fn virtual QAction QDesignerFormWindowManagerInterface::action(Action action) const + + Returns the action specified by the enumeration value \a action. + + Obsoletes the action accessors of Qt 4.X. + + \since 5.0 +*/ + +/*! + \fn virtual QActionGroup *QDesignerFormWindowManagerInterface::actionGroup(ActionGroup actionGroup) const + + Returns the action group specified by the enumeration value \a actionGroup. + + \since 5.0 +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::showPreview() + + Show a preview of the current form using the default parameters. + + \since 5.0 + \sa closeAllPreviews() +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::closeAllPreviews() + + Close all currently open previews. + + \since 5.0 + \sa showPreview() +*/ + +/*! + \fn virtual void QDesignerFormWindowManagerInterface::showPluginDialog() + + Opens a dialog showing the plugins loaded by \QD's and its plugin load failures. + + \since 5.0 +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractformwindowmanager.h b/src/tools/designer/src/lib/sdk/abstractformwindowmanager.h new file mode 100644 index 00000000000..08e07f0cf6b --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformwindowmanager.h @@ -0,0 +1,123 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMWINDOWMANAGER_H +#define ABSTRACTFORMWINDOWMANAGER_H + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerDnDItemInterface; + +class QWidget; +class QPixmap; +class QAction; +class QActionGroup; + +class QDESIGNER_SDK_EXPORT QDesignerFormWindowManagerInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerFormWindowManagerInterface(QObject *parent = nullptr); + virtual ~QDesignerFormWindowManagerInterface(); + + enum Action + { +#if QT_CONFIG(clipboard) + CutAction = 100, + CopyAction, + PasteAction, +#endif + DeleteAction = 103, + SelectAllAction, + + LowerAction = 200, + RaiseAction, + + UndoAction = 300, + RedoAction, + + HorizontalLayoutAction = 400, + VerticalLayoutAction, + SplitHorizontalAction, + SplitVerticalAction, + GridLayoutAction, + FormLayoutAction, + BreakLayoutAction, + AdjustSizeAction, + SimplifyLayoutAction, + + DefaultPreviewAction = 500, + + FormWindowSettingsDialogAction = 600 + }; + Q_ENUM(Action) + + enum ActionGroup + { + StyledPreviewActionGroup = 100 + }; + Q_ENUM(ActionGroup) + + virtual QAction *action(Action action) const = 0; + virtual QActionGroup *actionGroup(ActionGroup actionGroup) const = 0; + +#if QT_CONFIG(clipboard) + QAction *actionCut() const; + QAction *actionCopy() const; + QAction *actionPaste() const; +#endif + QAction *actionDelete() const; + QAction *actionSelectAll() const; + QAction *actionLower() const; + QAction *actionRaise() const; + QAction *actionUndo() const; + QAction *actionRedo() const; + + QAction *actionHorizontalLayout() const; + QAction *actionVerticalLayout() const; + QAction *actionSplitHorizontal() const; + QAction *actionSplitVertical() const; + QAction *actionGridLayout() const; + QAction *actionFormLayout() const; + QAction *actionBreakLayout() const; + QAction *actionAdjustSize() const; + QAction *actionSimplifyLayout() const; + + virtual QDesignerFormWindowInterface *activeFormWindow() const = 0; + + virtual int formWindowCount() const = 0; + virtual QDesignerFormWindowInterface *formWindow(int index) const = 0; + + virtual QDesignerFormWindowInterface *createFormWindow(QWidget *parentWidget = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()) = 0; + + virtual QDesignerFormEditorInterface *core() const = 0; + + virtual void dragItems(const QList &item_list) = 0; + + virtual QPixmap createPreviewPixmap() const = 0; + +Q_SIGNALS: + void formWindowAdded(QDesignerFormWindowInterface *formWindow); + void formWindowRemoved(QDesignerFormWindowInterface *formWindow); + void activeFormWindowChanged(QDesignerFormWindowInterface *formWindow); + void formWindowSettingsChanged(QDesignerFormWindowInterface *fw); + +public Q_SLOTS: + virtual void addFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void removeFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void setActiveFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void showPreview() = 0; + virtual void closeAllPreviews() = 0; + virtual void showPluginDialog() = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMWINDOWMANAGER_H diff --git a/src/tools/designer/src/lib/sdk/abstractformwindowtool.cpp b/src/tools/designer/src/lib/sdk/abstractformwindowtool.cpp new file mode 100644 index 00000000000..4bb7db8ee65 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformwindowtool.cpp @@ -0,0 +1,66 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractformwindowtool.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerFormWindowToolInterface + + \brief The QDesignerFormWindowToolInterface class provides an + interface that enables tools to be used on items in a form window. + + \inmodule QtDesigner + + \internal +*/ + +/*! +*/ +QDesignerFormWindowToolInterface::QDesignerFormWindowToolInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! +*/ +QDesignerFormWindowToolInterface::~QDesignerFormWindowToolInterface() = default; + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerFormWindowToolInterface::core() const = 0 +*/ + +/*! + \fn virtual QDesignerFormWindowInterface *QDesignerFormWindowToolInterface::formWindow() const = 0 +*/ + +/*! + \fn virtual QWidget *QDesignerFormWindowToolInterface::editor() const = 0 +*/ + +/*! + \fn virtual QAction *QDesignerFormWindowToolInterface::action() const = 0 +*/ + +/*! + \fn virtual void QDesignerFormWindowToolInterface::activated() = 0 +*/ + +/*! + \fn virtual void QDesignerFormWindowToolInterface::deactivated() = 0 +*/ + +/*! + \fn virtual void QDesignerFormWindowToolInterface::saveToDom(DomUI*, QWidget*) { +*/ + +/*! + \fn virtual void QDesignerFormWindowToolInterface::loadFromDom(DomUI*, QWidget*) { +*/ + +/*! + \fn virtual bool QDesignerFormWindowToolInterface::handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) = 0 +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractformwindowtool.h b/src/tools/designer/src/lib/sdk/abstractformwindowtool.h new file mode 100644 index 00000000000..93dee157d5b --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractformwindowtool.h @@ -0,0 +1,43 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTFORMWINDOWTOOL_H +#define ABSTRACTFORMWINDOWTOOL_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QWidget; +class QAction; +class DomUI; + +class QDESIGNER_SDK_EXPORT QDesignerFormWindowToolInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerFormWindowToolInterface(QObject *parent = nullptr); + virtual ~QDesignerFormWindowToolInterface(); + + virtual QDesignerFormEditorInterface *core() const = 0; + virtual QDesignerFormWindowInterface *formWindow() const = 0; + virtual QWidget *editor() const = 0; + + virtual QAction *action() const = 0; + + virtual void activated() = 0; + virtual void deactivated() = 0; + + virtual void saveToDom(DomUI*, QWidget*) {} + virtual void loadFromDom(DomUI*, QWidget*) {} + + virtual bool handleEvent(QWidget *widget, QWidget *managedWidget, QEvent *event) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMWINDOWTOOL_H diff --git a/src/tools/designer/src/lib/sdk/abstractintegration.cpp b/src/tools/designer/src/lib/sdk/abstractintegration.cpp new file mode 100644 index 00000000000..c1cd3f32e80 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractintegration.cpp @@ -0,0 +1,746 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractintegration.h" +#include "abstractformwindow.h" +#include "abstractformwindowmanager.h" +#include "abstractformwindowcursor.h" +#include "abstractformeditor.h" +#include "abstractactioneditor.h" +#include "abstractwidgetbox.h" +#include "abstractresourcebrowser.h" +#include "propertysheet.h" +#include "abstractpropertyeditor.h" +#include "qextensionmanager.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +/*! + \class QDesignerIntegrationInterface + + \brief The QDesignerIntegrationInterface glues together parts of \QD + and allows for overwriting functionality for IDE integration. + + \internal + + \inmodule QtDesigner + + \sa QDesignerFormEditorInterface +*/ + +/*! + \enum QDesignerIntegrationInterface::ResourceFileWatcherBehaviour + + \internal + + This enum describes if and how resource files are watched. + + \value NoResourceFileWatcher Do not watch for changes + \value ReloadResourceFileSilently Reload files silently. + \value PromptToReloadResourceFile Prompt the user to reload. +*/ + +/*! + \enum QDesignerIntegrationInterface::FeatureFlag + + \internal + + This enum describes the features that are available and can be + controlled by the integration. + + \value ResourceEditorFeature The resource editor is enabled. + \value SlotNavigationFeature Provide context menu entry offering 'Go to slot'. + \value DefaultWidgetActionFeature Trigger the preferred action of + QDesignerTaskMenuExtension when widget is activated. + \value DefaultFeature Default for \QD + + \sa hasFeature(), features() +*/ + +/*! + \property QDesignerIntegrationInterface::headerSuffix + + Returns the suffix of the header of promoted widgets. +*/ + +/*! + \property QDesignerIntegrationInterface::headerLowercase + + Returns whether headers of promoted widgets should be lower-cased (as the user types the class name). +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateSelection() + + \brief Sets the selected widget of the form window in various components. + + \internal + + In IDE integrations, the method can be overwritten to move the selection handles + of the form's main window, should it be selected. +*/ + +/*! + \fn virtual QWidget *QDesignerIntegrationInterface::containerWindow(QWidget *widget) const + + \brief Returns the outer window containing a form for applying main container geometries. + + \internal + + Should be implemented by IDE integrations. +*/ + +/*! + \fn virtual QDesignerResourceBrowserInterface *QDesignerIntegrationInterface::createResourceBrowser(QWidget *parent = 0) + + \brief Creates a resource browser depending on IDE integration. + + \internal + + \note Language integration takes precedence. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) + + \brief Triggered by the property editor to update a property value. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateProperty(const QString &name, const QVariant &value) + + \brief Triggered by the property editor to update a property value without subproperty handling. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::resetProperty(const QString &name) + + \brief Triggered by the property editor to reset a property value. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::addDynamicProperty(const QString &name, const QVariant &value) + + \brief Triggered by the property editor to add a dynamic property value. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::removeDynamicProperty(const QString &name) + + \brief Triggered by the property editor to remove a dynamic property. + + \internal + + If a different property editor is used, it should invoke this function. +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateActiveFormWindow(QDesignerFormWindowInterface *formWindow) + + \brief Sets up the active form window. + + \internal +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::setupFormWindow(QDesignerFormWindowInterface *formWindow) + + \brief Sets up the new form window. + + \internal +*/ + +/*! + \fn virtual void QDesignerIntegrationInterface::updateCustomWidgetPlugins() + + \brief Triggers a reload of the custom widget plugins. + + \internal +*/ + +class QDesignerIntegrationInterfacePrivate +{ +public: + QDesignerIntegrationInterfacePrivate(QDesignerFormEditorInterface *core) : + m_core(core) {} + + QDesignerFormEditorInterface *m_core; +}; + +QDesignerIntegrationInterface::QDesignerIntegrationInterface(QDesignerFormEditorInterface *core, QObject *parent) + : QObject(parent), d(new QDesignerIntegrationInterfacePrivate(core)) +{ + core->setIntegration(this); +} + +QDesignerIntegrationInterface::~QDesignerIntegrationInterface() = default; + +QDesignerFormEditorInterface *QDesignerIntegrationInterface::core() const +{ + return d->m_core; +} + +bool QDesignerIntegrationInterface::hasFeature(Feature f) const +{ + return (features() & f) != 0; +} + +void QDesignerIntegrationInterface::emitObjectNameChanged(QDesignerFormWindowInterface *formWindow, QObject *object, const QString &newName, const QString &oldName) +{ + emit objectNameChanged(formWindow, object, newName, oldName); +} + +void QDesignerIntegrationInterface::emitNavigateToSlot(const QString &objectName, + const QString &signalSignature, + const QStringList ¶meterNames) +{ + emit navigateToSlot(objectName, signalSignature, parameterNames); +} + +void QDesignerIntegrationInterface::emitNavigateToSlot(const QString &slotSignature) +{ + emit navigateToSlot(slotSignature); +} + +void QDesignerIntegrationInterface::emitHelpRequested(const QString &manual, const QString &document) +{ + emit helpRequested(manual, document); +} + +/*! + \class QDesignerIntegration + + \brief The QDesignerIntegration class is \QD's implementation of + QDesignerIntegrationInterface. + + \internal + + \inmodule QtDesigner + + IDE integrations should register classes derived from QDesignerIntegration + with QDesignerFormEditorInterface. +*/ + +namespace qdesigner_internal { + +class QDesignerIntegrationPrivate { +public: + explicit QDesignerIntegrationPrivate(QDesignerIntegration *qq); + + QWidget *containerWindow(QWidget *widget) const; + + // Load plugins into widget database and factory. + static void initializePlugins(QDesignerFormEditorInterface *formEditor); + + QString contextHelpId() const; + + void updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling); + void resetProperty(const QString &name); + void addDynamicProperty(const QString &name, const QVariant &value); + void removeDynamicProperty(const QString &name); + + void setupFormWindow(QDesignerFormWindowInterface *formWindow); + void updateSelection(); + void updateCustomWidgetPlugins(); + + void initialize(); + void getSelection(qdesigner_internal::Selection &s); + QObject *propertyEditorObject(); + + QDesignerIntegration *q; + QString headerSuffix; + bool headerLowercase; + QDesignerIntegrationInterface::Feature m_features; + QDesignerIntegrationInterface::ResourceFileWatcherBehaviour m_resourceFileWatcherBehaviour; + QString m_gradientsPath; + QtGradientManager *m_gradientManager; +}; + +QDesignerIntegrationPrivate::QDesignerIntegrationPrivate(QDesignerIntegration *qq) : + q(qq), + headerSuffix(u".h"_s), + headerLowercase(true), + m_features(QDesignerIntegrationInterface::DefaultFeature), + m_resourceFileWatcherBehaviour(QDesignerIntegrationInterface::PromptToReloadResourceFile), + m_gradientManager(nullptr) +{ +} + +void QDesignerIntegrationPrivate::initialize() +{ + // + // integrate the `Form Editor component' + // + + // Extensions + QDesignerFormEditorInterface *core = q->core(); + if (QDesignerPropertyEditor *designerPropertyEditor= qobject_cast(core->propertyEditor())) { + QObject::connect(designerPropertyEditor, &QDesignerPropertyEditor::propertyValueChanged, + q, QOverload::of(&QDesignerIntegration::updateProperty)); + QObject::connect(designerPropertyEditor, &QDesignerPropertyEditor::resetProperty, + q, &QDesignerIntegration::resetProperty); + QObject::connect(designerPropertyEditor, &QDesignerPropertyEditor::addDynamicProperty, + q, &QDesignerIntegration::addDynamicProperty); + QObject::connect(designerPropertyEditor, &QDesignerPropertyEditor::removeDynamicProperty, + q, &QDesignerIntegration::removeDynamicProperty); + } + + QObject::connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::formWindowAdded, + q, &QDesignerIntegrationInterface::setupFormWindow); + + QObject::connect(core->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + q, &QDesignerIntegrationInterface::updateActiveFormWindow); + + m_gradientManager = new QtGradientManager(q); + core->setGradientManager(m_gradientManager); + + const QString gradientsFile = u"/gradients.xml"_s; + m_gradientsPath = dataDirectory() + gradientsFile; + + // Migrate from legacy to standard data directory in Qt 7 + // ### FIXME Qt 8: Remove (QTBUG-96005) +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + const QString source = QFileInfo::exists(m_gradientsPath) + ? m_gradientsPath : legacyDataDirectory() + gradientsFile; +#else + const QString source = m_gradientsPath; +#endif + + QFile f(source); + if (f.open(QIODevice::ReadOnly)) { + QtGradientUtils::restoreState(m_gradientManager, QString::fromLatin1(f.readAll())); + f.close(); + } else { + QFile defaultGradients(u":/qt-project.org/designer/defaultgradients.xml"_s); + if (defaultGradients.open(QIODevice::ReadOnly)) { + QtGradientUtils::restoreState(m_gradientManager, QString::fromLatin1(defaultGradients.readAll())); + defaultGradients.close(); + } + } + + if (WidgetDataBase *widgetDataBase = qobject_cast(core->widgetDataBase())) + widgetDataBase->grabStandardWidgetBoxIcons(); +} + +void QDesignerIntegrationPrivate::updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) +{ + QDesignerFormWindowInterface *formWindow = q->core()->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + Selection selection; + getSelection(selection); + if (selection.empty()) + return; + + SetPropertyCommand *cmd = new SetPropertyCommand(formWindow); + // find a reference object to compare to and to find the right group + if (cmd->init(selection.selection(), name, value, propertyEditorObject(), enableSubPropertyHandling)) { + formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "Unable to set property " << name << '.'; + } +} + +void QDesignerIntegrationPrivate::resetProperty(const QString &name) +{ + QDesignerFormWindowInterface *formWindow = q->core()->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + Selection selection; + getSelection(selection); + if (selection.empty()) + return; + + ResetPropertyCommand *cmd = new ResetPropertyCommand(formWindow); + // find a reference object to find the right group + if (cmd->init(selection.selection(), name, propertyEditorObject())) { + formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "** WARNING Unable to reset property " << name << '.'; + } +} + +void QDesignerIntegrationPrivate::addDynamicProperty(const QString &name, const QVariant &value) +{ + QDesignerFormWindowInterface *formWindow = q->core()->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + Selection selection; + getSelection(selection); + if (selection.empty()) + return; + + AddDynamicPropertyCommand *cmd = new AddDynamicPropertyCommand(formWindow); + if (cmd->init(selection.selection(), propertyEditorObject(), name, value)) { + formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "** WARNING Unable to add dynamic property " << name << '.'; + } +} + +void QDesignerIntegrationPrivate::removeDynamicProperty(const QString &name) +{ + QDesignerFormWindowInterface *formWindow = q->core()->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + Selection selection; + getSelection(selection); + if (selection.empty()) + return; + + RemoveDynamicPropertyCommand *cmd = new RemoveDynamicPropertyCommand(formWindow); + if (cmd->init(selection.selection(), propertyEditorObject(), name)) { + formWindow->commandHistory()->push(cmd); + } else { + delete cmd; + qDebug() << "** WARNING Unable to remove dynamic property " << name << '.'; + } + +} + +void QDesignerIntegrationPrivate::setupFormWindow(QDesignerFormWindowInterface *formWindow) +{ + QObject::connect(formWindow, &QDesignerFormWindowInterface::selectionChanged, + q, &QDesignerIntegrationInterface::updateSelection); +} + +void QDesignerIntegrationPrivate::updateSelection() +{ + QDesignerFormEditorInterface *core = q->core(); + QDesignerFormWindowInterface *formWindow = core->formWindowManager()->activeFormWindow(); + QWidget *selection = nullptr; + + if (formWindow) { + selection = formWindow->cursor()->current(); + } + + if (QDesignerActionEditorInterface *actionEditor = core->actionEditor()) + actionEditor->setFormWindow(formWindow); + + if (QDesignerPropertyEditorInterface *propertyEditor = core->propertyEditor()) + propertyEditor->setObject(selection); + + if (QDesignerObjectInspectorInterface *objectInspector = core->objectInspector()) + objectInspector->setFormWindow(formWindow); + +} + +QWidget *QDesignerIntegrationPrivate::containerWindow(QWidget *widget) const +{ + // Find the parent window to apply a geometry to. + while (widget) { + if (widget->isWindow()) + break; + if (!qstrcmp(widget->metaObject()->className(), "QMdiSubWindow")) + break; + + widget = widget->parentWidget(); + } + + return widget; +} + +void QDesignerIntegrationPrivate::getSelection(Selection &s) +{ + QDesignerFormEditorInterface *core = q->core(); + // Get multiselection from object inspector + if (QDesignerObjectInspector *designerObjectInspector = qobject_cast(core->objectInspector())) { + designerObjectInspector->getSelection(s); + // Action editor puts actions that are not on the form yet + // into the property editor only. + if (s.empty()) + if (QObject *object = core->propertyEditor()->object()) + s.objects.push_back(object); + + } else { + // Just in case someone plugs in an old-style object inspector: Emulate selection + s.clear(); + QDesignerFormWindowInterface *formWindow = core->formWindowManager()->activeFormWindow(); + if (!formWindow) + return; + + QObject *object = core->propertyEditor()->object(); + if (object->isWidgetType()) { + QWidget *widget = static_cast(object); + QDesignerFormWindowCursorInterface *cursor = formWindow->cursor(); + if (cursor->isWidgetSelected(widget)) { + s.managed.push_back(widget); + } else { + s.unmanaged.push_back(widget); + } + } else { + s.objects.push_back(object); + } + } +} + +QObject *QDesignerIntegrationPrivate::propertyEditorObject() +{ + if (QDesignerPropertyEditorInterface *propertyEditor = q->core()->propertyEditor()) + return propertyEditor->object(); + return nullptr; +} + +// Load plugins into widget database and factory. +void QDesignerIntegrationPrivate::initializePlugins(QDesignerFormEditorInterface *formEditor) +{ + // load the plugins + qdesigner_internal::WidgetDataBase *widgetDataBase = qobject_cast(formEditor->widgetDataBase()); + if (widgetDataBase) { + widgetDataBase->loadPlugins(); + } + + if (qdesigner_internal::WidgetFactory *widgetFactory = qobject_cast(formEditor->widgetFactory())) { + widgetFactory->loadPlugins(); + } + + if (widgetDataBase) { + widgetDataBase->grabDefaultPropertyValues(); + } +} + +void QDesignerIntegrationPrivate::updateCustomWidgetPlugins() +{ + QDesignerFormEditorInterface *formEditor = q->core(); + if (QDesignerPluginManager *pm = formEditor->pluginManager()) + pm->registerNewPlugins(); + + initializePlugins(formEditor); + + // Do not just reload the last file as the WidgetBox merges the compiled-in resources + // and $HOME/.designer/widgetbox.xml. This would also double the scratchpad. + if (QDesignerWidgetBox *wb = qobject_cast(formEditor->widgetBox())) { + const QDesignerWidgetBox::LoadMode oldLoadMode = wb->loadMode(); + wb->setLoadMode(QDesignerWidgetBox::LoadCustomWidgetsOnly); + wb->load(); + wb->setLoadMode(oldLoadMode); + } +} + +static QString fixHelpClassName(const QString &className) +{ + // ### generalize using the Widget Data Base + if (className == "Line"_L1) + return u"QFrame"_s; + if (className == "Spacer"_L1) + return u"QSpacerItem"_s; + if (className == "QLayoutWidget"_L1) + return u"QLayout"_s; + return className; +} + +// Return class in which the property is defined +static QString classForProperty(QDesignerFormEditorInterface *core, + QObject *object, + const QString &property) +{ + if (const QDesignerPropertySheetExtension *ps = qt_extension(core->extensionManager(), object)) { + const int index = ps->indexOf(property); + if (index >= 0) + return ps->propertyGroup(index); + } + return QString(); +} + +QString QDesignerIntegrationPrivate::contextHelpId() const +{ + QDesignerFormEditorInterface *core = q->core(); + QObject *currentObject = core->propertyEditor()->object(); + if (!currentObject) + return QString(); + // Return a help index id consisting of "class::property" + QString className; + QString currentPropertyName = core->propertyEditor()->currentPropertyName(); + if (!currentPropertyName.isEmpty()) + className = classForProperty(core, currentObject, currentPropertyName); + if (className.isEmpty()) { + currentPropertyName.clear(); // We hit on some fake property. + className = qdesigner_internal::WidgetFactory::classNameOf(core, currentObject); + } + QString helpId = fixHelpClassName(className); + if (!currentPropertyName.isEmpty()) { + helpId += "::"_L1; + helpId += currentPropertyName; + } + return helpId; +} + +} // namespace qdesigner_internal + +// -------------- QDesignerIntegration +// As of 4.4, the header will be distributed with the Eclipse plugin. + +QDesignerIntegration::QDesignerIntegration(QDesignerFormEditorInterface *core, QObject *parent) : + QDesignerIntegrationInterface(core, parent), + d(new qdesigner_internal::QDesignerIntegrationPrivate(this)) +{ + d->initialize(); +} + +QDesignerIntegration::~QDesignerIntegration() +{ + QFile f(d->m_gradientsPath); + if (f.open(QIODevice::WriteOnly)) { + f.write(QtGradientUtils::saveState(d->m_gradientManager).toUtf8()); + f.close(); + } +} + +QString QDesignerIntegration::headerSuffix() const +{ + return d->headerSuffix; +} + +void QDesignerIntegration::setHeaderSuffix(const QString &headerSuffix) +{ + d->headerSuffix = headerSuffix; +} + +bool QDesignerIntegration::isHeaderLowercase() const +{ + return d->headerLowercase; +} + +void QDesignerIntegration::setHeaderLowercase(bool headerLowercase) +{ + d->headerLowercase = headerLowercase; +} + +QDesignerIntegrationInterface::Feature QDesignerIntegration::features() const +{ + return d->m_features; +} + +void QDesignerIntegration::setFeatures(Feature f) +{ + d->m_features = f; +} + +QDesignerIntegrationInterface::ResourceFileWatcherBehaviour QDesignerIntegration::resourceFileWatcherBehaviour() const +{ + return d->m_resourceFileWatcherBehaviour; +} + +void QDesignerIntegration::setResourceFileWatcherBehaviour(ResourceFileWatcherBehaviour behaviour) +{ + if (d->m_resourceFileWatcherBehaviour != behaviour) { + d->m_resourceFileWatcherBehaviour = behaviour; + core()->resourceModel()->setWatcherEnabled(behaviour != QDesignerIntegrationInterface::NoResourceFileWatcher); + } +} + +void QDesignerIntegration::updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) +{ + d->updateProperty(name, value, enableSubPropertyHandling); + emit propertyChanged(core()->formWindowManager()->activeFormWindow(), name, value); +} + +void QDesignerIntegration::updateProperty(const QString &name, const QVariant &value) +{ + updateProperty(name, value, true); +} + +void QDesignerIntegration::resetProperty(const QString &name) +{ + d->resetProperty(name); +} + +void QDesignerIntegration::addDynamicProperty(const QString &name, const QVariant &value) +{ + d->addDynamicProperty(name, value); +} + +void QDesignerIntegration::removeDynamicProperty(const QString &name) +{ + d->removeDynamicProperty(name); +} + +void QDesignerIntegration::updateActiveFormWindow(QDesignerFormWindowInterface *) +{ + d->updateSelection(); +} + +void QDesignerIntegration::setupFormWindow(QDesignerFormWindowInterface *formWindow) +{ + d->setupFormWindow(formWindow); + connect(formWindow, &QDesignerFormWindowInterface::selectionChanged, + this, &QDesignerIntegrationInterface::updateSelection); +} + +void QDesignerIntegration::updateSelection() +{ + d->updateSelection(); +} + +QWidget *QDesignerIntegration::containerWindow(QWidget *widget) const +{ + return d->containerWindow(widget); +} + +// Load plugins into widget database and factory. +void QDesignerIntegration::initializePlugins(QDesignerFormEditorInterface *formEditor) +{ + qdesigner_internal::QDesignerIntegrationPrivate::initializePlugins(formEditor); +} + +void QDesignerIntegration::updateCustomWidgetPlugins() +{ + d->updateCustomWidgetPlugins(); +} + +QDesignerResourceBrowserInterface *QDesignerIntegration::createResourceBrowser(QWidget *) +{ + return nullptr; +} + +QString QDesignerIntegration::contextHelpId() const +{ + return d->contextHelpId(); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractintegration.h b/src/tools/designer/src/lib/sdk/abstractintegration.h new file mode 100644 index 00000000000..5e3e297c192 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractintegration.h @@ -0,0 +1,153 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTINTEGRATION_H +#define ABSTRACTINTEGRATION_H + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QDesignerIntegrationInterfacePrivate; +class QDesignerResourceBrowserInterface; +class QVariant; +class QWidget; + +namespace qdesigner_internal { +class QDesignerIntegrationPrivate; +} + +class QDESIGNER_SDK_EXPORT QDesignerIntegrationInterface: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString headerSuffix READ headerSuffix WRITE setHeaderSuffix) + Q_PROPERTY(bool headerLowercase READ isHeaderLowercase WRITE setHeaderLowercase) + +public: + enum ResourceFileWatcherBehaviour + { + NoResourceFileWatcher, + ReloadResourceFileSilently, + PromptToReloadResourceFile // Default + }; + Q_ENUM(ResourceFileWatcherBehaviour) + + enum FeatureFlag + { + ResourceEditorFeature = 0x1, + SlotNavigationFeature = 0x2, + DefaultWidgetActionFeature = 0x4, + DefaultFeature = ResourceEditorFeature | DefaultWidgetActionFeature + }; + Q_DECLARE_FLAGS(Feature, FeatureFlag) + + explicit QDesignerIntegrationInterface(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + virtual ~QDesignerIntegrationInterface(); + + QDesignerFormEditorInterface *core() const; + + virtual QWidget *containerWindow(QWidget *widget) const = 0; + + // Create a resource browser specific to integration. Language integration takes precedence + virtual QDesignerResourceBrowserInterface *createResourceBrowser(QWidget *parent = nullptr) = 0; + virtual QString headerSuffix() const = 0; + virtual void setHeaderSuffix(const QString &headerSuffix) = 0; + + virtual bool isHeaderLowercase() const = 0; + virtual void setHeaderLowercase(bool headerLowerCase) = 0; + + virtual Feature features() const = 0; + bool hasFeature(Feature f) const; + + virtual ResourceFileWatcherBehaviour resourceFileWatcherBehaviour() const = 0; + virtual void setResourceFileWatcherBehaviour(ResourceFileWatcherBehaviour behaviour) = 0; + + virtual QString contextHelpId() const = 0; + + void emitObjectNameChanged(QDesignerFormWindowInterface *formWindow, QObject *object, + const QString &newName, const QString &oldName); + void emitNavigateToSlot(const QString &objectName, const QString &signalSignature, const QStringList ¶meterNames); + void emitNavigateToSlot(const QString &slotSignature); + void emitHelpRequested(const QString &manual, const QString &document); + +Q_SIGNALS: + void propertyChanged(QDesignerFormWindowInterface *formWindow, const QString &name, const QVariant &value); + void objectNameChanged(QDesignerFormWindowInterface *formWindow, QObject *object, const QString &newName, const QString &oldName); + void helpRequested(const QString &manual, const QString &document); + + void navigateToSlot(const QString &objectName, const QString &signalSignature, const QStringList ¶meterNames); + void navigateToSlot(const QString &slotSignature); + +public Q_SLOTS: + virtual void setFeatures(Feature f) = 0; + virtual void updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) = 0; + virtual void updateProperty(const QString &name, const QVariant &value) = 0; + // Additional signals of designer property editor + virtual void resetProperty(const QString &name) = 0; + virtual void addDynamicProperty(const QString &name, const QVariant &value) = 0; + virtual void removeDynamicProperty(const QString &name) = 0; + + virtual void updateActiveFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void setupFormWindow(QDesignerFormWindowInterface *formWindow) = 0; + virtual void updateSelection() = 0; + virtual void updateCustomWidgetPlugins() = 0; + +private: + QScopedPointer d; +}; + +class QDESIGNER_SDK_EXPORT QDesignerIntegration: public QDesignerIntegrationInterface +{ + Q_OBJECT +public: + explicit QDesignerIntegration(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + virtual ~QDesignerIntegration(); + + QString headerSuffix() const override; + void setHeaderSuffix(const QString &headerSuffix) override; + + bool isHeaderLowercase() const override; + void setHeaderLowercase(bool headerLowerCase) override; + + Feature features() const override; + virtual void setFeatures(Feature f) override; + + ResourceFileWatcherBehaviour resourceFileWatcherBehaviour() const override; + void setResourceFileWatcherBehaviour(ResourceFileWatcherBehaviour behaviour) override; + + virtual QWidget *containerWindow(QWidget *widget) const override; + + // Load plugins into widget database and factory. + static void initializePlugins(QDesignerFormEditorInterface *formEditor); + + // Create a resource browser specific to integration. Language integration takes precedence + QDesignerResourceBrowserInterface *createResourceBrowser(QWidget *parent = nullptr) override; + + QString contextHelpId() const override; + + void updateProperty(const QString &name, const QVariant &value, bool enableSubPropertyHandling) override; + void updateProperty(const QString &name, const QVariant &value) override; + // Additional signals of designer property editor + void resetProperty(const QString &name) override; + void addDynamicProperty(const QString &name, const QVariant &value) override; + void removeDynamicProperty(const QString &name) override; + + void updateActiveFormWindow(QDesignerFormWindowInterface *formWindow) override; + void setupFormWindow(QDesignerFormWindowInterface *formWindow) override; + void updateSelection() override; + void updateCustomWidgetPlugins() override; + +private: + QScopedPointer d; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTINTEGRATION_H diff --git a/src/tools/designer/src/lib/sdk/abstractintrospection.cpp b/src/tools/designer/src/lib/sdk/abstractintrospection.cpp new file mode 100644 index 00000000000..6fc112f36d6 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractintrospection.cpp @@ -0,0 +1,496 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractintrospection_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerMetaEnumInterface + \internal + \since 4.4 + + \brief QDesignerMetaEnumInterface is part of \QD's introspection interface and represents an enumeration. + + \inmodule QtDesigner + + The QDesignerMetaEnumInterface class provides meta-data about an enumerator. + + \sa QDesignerMetaObjectInterface +*/ + +/*! + Constructs a QDesignerMetaEnumInterface object. +*/ + +QDesignerMetaEnumInterface::QDesignerMetaEnumInterface() = default; + +/*! + Destroys the QDesignerMetaEnumInterface object. +*/ +QDesignerMetaEnumInterface::~QDesignerMetaEnumInterface() = default; + +/*! + \fn bool QDesignerMetaEnumInterface::isFlag() const + + Returns true if this enumerator is used as a flag. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::key(int index) const + + Returns the key with the given \a index. +*/ + +/*! + \fn int QDesignerMetaEnumInterface::keyCount() const + + Returns the number of keys. +*/ + +/*! + \fn int QDesignerMetaEnumInterface::keyToValue(const QString &key) const + + Returns the integer value of the given enumeration \a key, or -1 if \a key is not defined. +*/ + +/*! + \fn int QDesignerMetaEnumInterface::keysToValue(const QString &keys) const + + Returns the value derived from combining together the values of the \a keys using the OR operator, or -1 if keys is not defined. Note that the strings in \a keys must be '|'-separated. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::name() const + + Returns the name of the enumerator (without the scope). +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::scope() const + + Returns the scope this enumerator was declared in. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::separator() const + + Returns the separator to be used when building enumeration names. +*/ + +/*! + \fn int QDesignerMetaEnumInterface::value(int index) const + + Returns the value with the given \a index; or returns -1 if there is no such value. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::valueToKey(int value) const + + Returns the string that is used as the name of the given enumeration \a value, or QString::null if value is not defined. +*/ + +/*! + \fn QString QDesignerMetaEnumInterface::valueToKeys(int value) const + + Returns a byte array of '|'-separated keys that represents the given \a value. +*/ + +/*! + \class QDesignerMetaPropertyInterface + \internal + \since 4.4 + + \brief QDesignerMetaPropertyInterface is part of \QD's introspection interface and represents a property. + + \inmodule QtDesigner + + The QDesignerMetaPropertyInterface class provides meta-data about a property. + + \sa QDesignerMetaObjectInterface +*/ + +/*! + Constructs a QDesignerMetaPropertyInterface object. +*/ + +QDesignerMetaPropertyInterface::QDesignerMetaPropertyInterface() = default; + +/*! + Destroys the QDesignerMetaPropertyInterface object. +*/ + +QDesignerMetaPropertyInterface::~QDesignerMetaPropertyInterface() = default; + +/*! + \enum QDesignerMetaPropertyInterface::Kind + + This enum indicates whether the property is of a special type. + + \value EnumKind The property is of an enumeration type + \value FlagKind The property is of an flag type + \value OtherKind The property is of another type + */ + +/*! + \enum QDesignerMetaPropertyInterface::AccessFlag + + These flags specify the access the property provides. + + \value ReadAccess Property can be read + \value WriteAccess Property can be written + \value ResetAccess Property can be reset to a default value + */ + +/*! + \enum QDesignerMetaPropertyInterface::Attribute + + Various attributes of the property. + + \value DesignableAttribute Property is designable (visible in \QD) + \value ScriptableAttribute Property is scriptable + \value StoredAttribute Property is stored, that is, not calculated + \value UserAttribute Property is the property that the user can edit for the QObject + */ + +/*! + \fn const QDesignerMetaEnumInterface *QDesignerMetaPropertyInterface::enumerator() const + + Returns the enumerator if this property's type is an enumerator type; +*/ + +/*! + \fn Kind QDesignerMetaPropertyInterface::kind() const + + Returns the type of the property. +*/ + +/*! + \fn AccessFlags QDesignerMetaPropertyInterface::accessFlags() const + + Returns a combination of access flags. +*/ + +/*! + \fn Attributes QDesignerMetaPropertyInterface::attributes() const + + Returns the attributes of the property. +*/ + +/*! + \fn int QDesignerMetaPropertyInterface::type() const + + Returns the type of the property. + + \sa QMetaType::Type +*/ + +/*! + \fn QString QDesignerMetaPropertyInterface::name() const + + Returns the name of the property. +*/ + +/*! + \fn QString QDesignerMetaPropertyInterface::typeName() const + + Returns the name of this property's type. +*/ + + +/*! + \fn int QDesignerMetaPropertyInterface::userType() const + + Returns this property's user type. +*/ + +/*! + \fn bool QDesignerMetaPropertyInterface::hasSetter() const + + Returns whether getter and setter methods exist for this property. +*/ + +/*! + \fn QVariant QDesignerMetaPropertyInterface::read(const QObject *object) const + + Reads the property's value from the given \a object. Returns the value if it was able to read it; otherwise returns an invalid variant. +*/ + +/*! + \fn bool QDesignerMetaPropertyInterface::reset(QObject *object) const + + Resets the property for the given \a object with a reset method. Returns true if the reset worked; otherwise returns false. +*/ + +/*! + \fn bool QDesignerMetaPropertyInterface::write(QObject *object, const QVariant &value) const + + Writes \a value as the property's value to the given \a object. Returns true if the write succeeded; otherwise returns false. +*/ + +/*! + \class QDesignerMetaMethodInterface + \internal + \since 4.4 + + \brief QDesignerMetaMethodInterface is part of \QD's introspection interface and represents a member function. + + \inmodule QtDesigner + + The QDesignerMetaMethodInterface class provides meta-data about a member function. + + \sa QDesignerMetaObjectInterface +*/ + +/*! + Constructs a QDesignerMetaMethodInterface object. +*/ + +QDesignerMetaMethodInterface::QDesignerMetaMethodInterface() = default; + +/*! + Destroys the QDesignerMetaMethodInterface object. +*/ + +QDesignerMetaMethodInterface::~QDesignerMetaMethodInterface() = default; + +/*! + \enum QDesignerMetaMethodInterface::MethodType + + This enum specifies the type of the method + + \value Method The function is a plain member function. + \value Signal The function is a signal. + \value Slot The function is a slot. + \value Constructor The function is a constructor. + +*/ + +/*! + \enum QDesignerMetaMethodInterface::Access + + This enum represents the access specification of the method + + \value Private A private member function + \value Protected A protected member function + \value Public A public member function +*/ + +/*! + \fn QDesignerMetaMethodInterface::Access QDesignerMetaMethodInterface::access() const + + Returns the access specification of this method. +*/ + + +/*! + \fn QDesignerMetaMethodInterface::MethodType QDesignerMetaMethodInterface::methodType() const + + Returns the type of this method. +*/ + +/*! + \fn QStringList QDesignerMetaMethodInterface::parameterNames() const + + Returns a list of parameter names. +*/ + +/*! + \fn QStringList QDesignerMetaMethodInterface::parameterTypes() const + + Returns a list of parameter types. +*/ + +/*! + \fn QString QDesignerMetaMethodInterface::signature() const + + Returns the signature of this method. +*/ + +/*! + \fn QString QDesignerMetaMethodInterface::normalizedSignature() const + + Returns the normalized signature of this method (suitable as signal/slot specification). +*/ + + +/*! + \fn QString QDesignerMetaMethodInterface::tag() const + + Returns the tag associated with this method. +*/ + +/*! + \fn QString QDesignerMetaMethodInterface::typeName() const + + Returns the return type of this method, or an empty string if the return type is void. +*/ + +/*! + \class QDesignerMetaObjectInterface + \internal + \since 4.4 + + \brief QDesignerMetaObjectInterface is part of \QD's introspection interface and provides meta-information about Qt objects + + \inmodule QtDesigner + + The QDesignerMetaObjectInterface class provides meta-data about Qt objects. For a given object, it can be obtained + by querying QDesignerIntrospectionInterface. + + \sa QDesignerIntrospectionInterface +*/ + +/*! + Constructs a QDesignerMetaObjectInterface object. +*/ + +QDesignerMetaObjectInterface::QDesignerMetaObjectInterface() = default; + +/*! + Destroys the QDesignerMetaObjectInterface object. +*/ + +QDesignerMetaObjectInterface::~QDesignerMetaObjectInterface() = default; + +/*! + \fn QString QDesignerMetaObjectInterface::className() const + + Returns the class name. +*/ + +/*! + \fn const QDesignerMetaEnumInterface *QDesignerMetaObjectInterface::enumerator(int index) const + + Returns the meta-data for the enumerator with the given \a index. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::enumeratorCount() const + + Returns the number of enumerators in this class. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::enumeratorOffset() const + + Returns the enumerator offset for this class; i.e. the index position of this class's first enumerator. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfEnumerator(const QString &name) const + + Finds enumerator \a name and returns its index; otherwise returns -1. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfMethod(const QString &method) const + + Finds \a method and returns its index; otherwise returns -1. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfProperty(const QString &name) const + + Finds property \a name and returns its index; otherwise returns -1. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfSignal(const QString &signal) const + + Finds \a signal and returns its index; otherwise returns -1. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::indexOfSlot(const QString &slot) const + + Finds \a slot and returns its index; otherwise returns -1. +*/ + +/*! + \fn const QDesignerMetaMethodInterface *QDesignerMetaObjectInterface::method(int index) const + + Returns the meta-data for the method with the given \a index. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::methodCount() const + + Returns the number of methods in this class. These include ordinary methods, signals, and slots. +*/ + +/*! + \fn int QDesignerMetaObjectInterface::methodOffset() const + + Returns the method offset for this class; i.e. the index position of this class's first member function. +*/ + +/*! + \fn const QDesignerMetaPropertyInterface *QDesignerMetaObjectInterface::property(int index) const + + Returns the meta-data for the property with the given \a index. +*/ +/*! + \fn int QDesignerMetaObjectInterface::propertyCount() const + + Returns the number of properties in this class. +*/ +/*! + \fn int QDesignerMetaObjectInterface::propertyOffset() const + + Returns the property offset for this class; i.e. the index position of this class's first property. +*/ + +/*! + \fn const QDesignerMetaObjectInterface *QDesignerMetaObjectInterface::superClass() const + + Returns the meta-object of the superclass, or 0 if there is no such object. +*/ + +/*! + \fn const QDesignerMetaPropertyInterface *QDesignerMetaObjectInterface::userProperty() const + + Returns the property that has the USER flag set to true. +*/ + +/*! + \class QDesignerIntrospectionInterface + \internal + \since 4.4 + + \brief QDesignerIntrospectionInterface provides access to a QDesignerMetaObjectInterface for a given Qt object. + + \inmodule QtDesigner + + QDesignerIntrospectionInterface is the main class of \QD's introspection interface. These + interfaces provide a layer of abstraction around QMetaObject and related classes to allow for the integration + of other programming languages. + + An instance of QDesignerIntrospectionInterface can be obtained from the core. + + \sa QDesignerMetaObjectInterface +*/ + +/*! + Constructs a QDesignerIntrospectionInterface object. +*/ + +QDesignerIntrospectionInterface::QDesignerIntrospectionInterface() +{ +} + +/*! + Destroys the QDesignerIntrospectionInterface object. +*/ + +QDesignerIntrospectionInterface::~QDesignerIntrospectionInterface() +{ +} + +/*! + \fn const QDesignerMetaObjectInterface* QDesignerIntrospectionInterface::metaObject(const QObject *object) const + + Returns the meta object of this \a object. +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractintrospection_p.h b/src/tools/designer/src/lib/sdk/abstractintrospection_p.h new file mode 100644 index 00000000000..8add3a4ecc8 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractintrospection_p.h @@ -0,0 +1,145 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ABSTRACTMETAOBJECT_H +#define ABSTRACTMETAOBJECT_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDESIGNER_SDK_EXPORT QDesignerMetaEnumInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaEnumInterface) + + QDesignerMetaEnumInterface(); + virtual ~QDesignerMetaEnumInterface(); + virtual bool isFlag() const = 0; + virtual QString key(int index) const = 0; + virtual int keyCount() const = 0; + virtual int keyToValue(const QString &key) const = 0; + virtual int keysToValue(const QString &keys) const = 0; + virtual QString name() const = 0; + virtual QString enumName() const = 0; + virtual QString scope() const = 0; + virtual QString separator() const = 0; + virtual int value(int index) const = 0; + virtual QString valueToKey(int value) const = 0; + virtual QString valueToKeys(int value) const = 0; +}; + +class QDESIGNER_SDK_EXPORT QDesignerMetaPropertyInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaPropertyInterface) + + enum Kind { EnumKind, FlagKind, OtherKind }; + enum AccessFlag { ReadAccess = 0x0001, WriteAccess = 0x0002, ResetAccess = 0x0004 }; + enum Attribute { DesignableAttribute = 0x0001, ScriptableAttribute = 0x0002, StoredAttribute = 0x0004, UserAttribute = 0x0008}; + Q_DECLARE_FLAGS(Attributes, Attribute) + Q_DECLARE_FLAGS(AccessFlags, AccessFlag) + + QDesignerMetaPropertyInterface(); + virtual ~QDesignerMetaPropertyInterface(); + + virtual const QDesignerMetaEnumInterface *enumerator() const = 0; + + virtual Kind kind() const = 0; + virtual AccessFlags accessFlags() const = 0; + virtual Attributes attributes() const = 0; + + virtual int type() const = 0; + virtual QString name() const = 0; + virtual QString typeName() const = 0; + virtual int userType() const = 0; + virtual bool hasSetter() const = 0; + + virtual QVariant read(const QObject *object) const = 0; + virtual bool reset(QObject *object) const = 0; + virtual bool write(QObject *object, const QVariant &value) const = 0; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDesignerMetaPropertyInterface::AccessFlags) +Q_DECLARE_OPERATORS_FOR_FLAGS(QDesignerMetaPropertyInterface::Attributes) + +class QDESIGNER_SDK_EXPORT QDesignerMetaMethodInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaMethodInterface) + + QDesignerMetaMethodInterface(); + virtual ~QDesignerMetaMethodInterface(); + + enum MethodType { Method, Signal, Slot, Constructor }; + enum Access { Private, Protected, Public }; + + virtual Access access() const = 0; + virtual MethodType methodType() const = 0; + virtual QStringList parameterNames() const = 0; + virtual QStringList parameterTypes() const = 0; + virtual QString signature() const = 0; + virtual QString normalizedSignature() const = 0; + virtual QString tag() const = 0; + virtual QString typeName() const = 0; +}; + +class QDESIGNER_SDK_EXPORT QDesignerMetaObjectInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaObjectInterface) + + QDesignerMetaObjectInterface(); + virtual ~QDesignerMetaObjectInterface(); + + virtual QString className() const = 0; + virtual const QDesignerMetaEnumInterface *enumerator(int index) const = 0; + virtual int enumeratorCount() const = 0; + virtual int enumeratorOffset() const = 0; + + virtual int indexOfEnumerator(const QString &name) const = 0; + virtual int indexOfMethod(const QString &method) const = 0; + virtual int indexOfProperty(const QString &name) const = 0; + virtual int indexOfSignal(const QString &signal) const = 0; + virtual int indexOfSlot(const QString &slot) const = 0; + + virtual const QDesignerMetaMethodInterface *method(int index) const = 0; + virtual int methodCount() const = 0; + virtual int methodOffset() const = 0; + + virtual const QDesignerMetaPropertyInterface *property(int index) const = 0; + virtual int propertyCount() const = 0; + virtual int propertyOffset() const = 0; + + virtual const QDesignerMetaObjectInterface *superClass() const = 0; + virtual const QDesignerMetaPropertyInterface *userProperty() const = 0; +}; + +// To be obtained from core +class QDESIGNER_SDK_EXPORT QDesignerIntrospectionInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerIntrospectionInterface) + + QDesignerIntrospectionInterface(); + virtual ~QDesignerIntrospectionInterface(); + + virtual const QDesignerMetaObjectInterface* metaObject(const QObject *object) const = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTMETAOBJECT_H diff --git a/src/tools/designer/src/lib/sdk/abstractlanguage.h b/src/tools/designer/src/lib/sdk/abstractlanguage.h new file mode 100644 index 00000000000..0e0c4f60e4f --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractlanguage.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_ABTRACT_LANGUAGE_H +#define QDESIGNER_ABTRACT_LANGUAGE_H + +#include + +QT_BEGIN_NAMESPACE + +class QDialog; +class QWidget; +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QDesignerResourceBrowserInterface; + +class QDesignerLanguageExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerLanguageExtension) + + QDesignerLanguageExtension() = default; + virtual ~QDesignerLanguageExtension() = default; + + /*! + Returns the name to be matched against the "language" attribute of the element. + + \since 5.0 + */ + + virtual QString name() const = 0; + + virtual QDialog *createFormWindowSettingsDialog(QDesignerFormWindowInterface *formWindow, QWidget *parentWidget) = 0; + virtual QDesignerResourceBrowserInterface *createResourceBrowser(QWidget *parentWidget) = 0; + + virtual QDialog *createPromotionDialog(QDesignerFormEditorInterface *formEditor, QWidget *parentWidget = nullptr) = 0; + + virtual QDialog *createPromotionDialog(QDesignerFormEditorInterface *formEditor, + const QString &promotableWidgetClassName, + QString *promoteToClassName, + QWidget *parentWidget = nullptr) = 0; + + virtual bool isLanguageResource(const QString &path) const = 0; + + virtual QString classNameOf(QObject *object) const = 0; + + virtual bool signalMatchesSlot(const QString &signal, const QString &slot) const = 0; + + virtual QString widgetBoxContents() const = 0; + + virtual QString uiExtension() const = 0; +}; + +Q_DECLARE_EXTENSION_INTERFACE(QDesignerLanguageExtension, "org.qt-project.Qt.Designer.Language.3") + +QT_END_NAMESPACE + +#endif // QDESIGNER_ABTRACT_LANGUAGE_H diff --git a/src/tools/designer/src/lib/sdk/abstractmetadatabase.cpp b/src/tools/designer/src/lib/sdk/abstractmetadatabase.cpp new file mode 100644 index 00000000000..46b0d967c0f --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractmetadatabase.cpp @@ -0,0 +1,130 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// sdk +#include "abstractmetadatabase.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerMetaDataBaseInterface + \brief The QDesignerMetaDataBaseInterface class provides an interface to Qt Widgets Designer's + object meta database. + \inmodule QtDesigner + \internal +*/ + +/*! + Constructs an interface to the meta database with the given \a parent. +*/ +QDesignerMetaDataBaseInterface::QDesignerMetaDataBaseInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the interface to the meta database. +*/ +QDesignerMetaDataBaseInterface::~QDesignerMetaDataBaseInterface() = default; + +/*! + \fn QDesignerMetaDataBaseItemInterface *QDesignerMetaDataBaseInterface::item(QObject *object) const + + Returns the item in the meta database associated with the given \a object. +*/ + +/*! + \fn void QDesignerMetaDataBaseInterface::add(QObject *object) + + Adds the specified \a object to the meta database. +*/ + +/*! + \fn void QDesignerMetaDataBaseInterface::remove(QObject *object) + + Removes the specified \a object from the meta database. +*/ + +/*! + \fn QList QDesignerMetaDataBaseInterface::objects() const + + Returns the list of objects that have corresponding items in the meta database. +*/ + +/*! + \fn QDesignerFormEditorInterface *QDesignerMetaDataBaseInterface::core() const + + Returns the core interface that is associated with the meta database. +*/ + + +// Doc: Interface only + +/*! + \class QDesignerMetaDataBaseItemInterface + \brief The QDesignerMetaDataBaseItemInterface class provides an interface to individual + items in \QD's meta database. + \inmodule QtDesigner + \internal + + This class allows individual items in \QD's meta-data database to be accessed and modified. + Use the QDesignerMetaDataBaseInterface class to change the properties of the database itself. +*/ + +/*! + \fn QDesignerMetaDataBaseItemInterface::~QDesignerMetaDataBaseItemInterface() + + Destroys the item interface to the meta-data database. +*/ + +/*! + \fn QString QDesignerMetaDataBaseItemInterface::name() const + + Returns the name of the item in the database. + + \sa setName() +*/ + +/*! + \fn void QDesignerMetaDataBaseItemInterface::setName(const QString &name) + + Sets the name of the item to the given \a name. + + \sa name() +*/ + +/*! + \fn QList QDesignerMetaDataBaseItemInterface::tabOrder() const + + Returns a list of widgets in the order defined by the form's tab order. + + \sa setTabOrder() +*/ + + +/*! + \fn void QDesignerMetaDataBaseItemInterface::setTabOrder(const QList &tabOrder) + + Sets the tab order in the form using the list of widgets defined by \a tabOrder. + + \sa tabOrder() +*/ + + +/*! + \fn bool QDesignerMetaDataBaseItemInterface::enabled() const + + Returns whether the item is enabled. + + \sa setEnabled() +*/ + +/*! + \fn void QDesignerMetaDataBaseItemInterface::setEnabled(bool enabled) + + If \a enabled is true, the item is enabled; otherwise it is disabled. + + \sa enabled() +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractmetadatabase.h b/src/tools/designer/src/lib/sdk/abstractmetadatabase.h new file mode 100644 index 00000000000..46728ea66ca --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractmetadatabase.h @@ -0,0 +1,60 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTMETADATABASE_H +#define ABSTRACTMETADATABASE_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QCursor; +class QWidget; + +class QDesignerFormEditorInterface; + +class QDesignerMetaDataBaseItemInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMetaDataBaseItemInterface) + + QDesignerMetaDataBaseItemInterface() = default; + virtual ~QDesignerMetaDataBaseItemInterface() = default; + + virtual QString name() const = 0; + virtual void setName(const QString &name) = 0; + + virtual QList tabOrder() const = 0; + virtual void setTabOrder(const QList &tabOrder) = 0; + + virtual bool enabled() const = 0; + virtual void setEnabled(bool b) = 0; +}; + + +class QDESIGNER_SDK_EXPORT QDesignerMetaDataBaseInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerMetaDataBaseInterface(QObject *parent = nullptr); + virtual ~QDesignerMetaDataBaseInterface(); + + virtual QDesignerMetaDataBaseItemInterface *item(QObject *object) const = 0; + virtual void add(QObject *object) = 0; + virtual void remove(QObject *object) = 0; + + virtual QList objects() const = 0; + + virtual QDesignerFormEditorInterface *core() const = 0; + +Q_SIGNALS: + void changed(); +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTMETADATABASE_H diff --git a/src/tools/designer/src/lib/sdk/abstractnewformwidget.cpp b/src/tools/designer/src/lib/sdk/abstractnewformwidget.cpp new file mode 100644 index 00000000000..e4b0e7dc0dd --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractnewformwidget.cpp @@ -0,0 +1,77 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractnewformwidget.h" +#include + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerNewFormWidgetInterface + \since 4.5 + \internal + + \brief QDesignerNewFormWidgetInterface provides an interface for chooser + widgets that can be used within "New Form" dialogs and wizards. + It presents the user with a list of choices taken from built-in + templates, pre-defined template paths and suitable custom widgets. + It provides a static creation function that returns \QD's + implementation. + + \inmodule QtDesigner +*/ + +/*! + Constructs a QDesignerNewFormWidgetInterface object. +*/ + +QDesignerNewFormWidgetInterface::QDesignerNewFormWidgetInterface(QWidget *parent) : + QWidget(parent) +{ +} + +/*! + Destroys the QDesignerNewFormWidgetInterface object. +*/ + +QDesignerNewFormWidgetInterface::~QDesignerNewFormWidgetInterface() = default; + +/*! + Creates an instance of the QDesignerNewFormWidgetInterface as a child + of \a parent using \a core. +*/ + +QDesignerNewFormWidgetInterface *QDesignerNewFormWidgetInterface::createNewFormWidget(QDesignerFormEditorInterface *core, QWidget *parent) +{ + return new qdesigner_internal::NewFormWidget(core, parent); +} + +/*! + \fn bool QDesignerNewFormWidgetInterface::hasCurrentTemplate() const + + Returns whether a form template is currently selected. +*/ + +/*! + \fn QString QDesignerNewFormWidgetInterface::currentTemplate(QString *errorMessage = 0) + + Returns the contents of the currently selected template. If the method fails, + an empty string is returned and \a errorMessage receives an error message. +*/ + +// Signals + +/*! + \fn void QDesignerNewFormWidgetInterface::templateActivated() + + This signal is emitted whenever the user activates a template by double-clicking. +*/ + +/*! + \fn void QDesignerNewFormWidgetInterface::currentTemplateChanged(bool templateSelected) + + This signal is emitted whenever the user changes the current template. + \a templateSelected indicates whether a template is currently selected. +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractnewformwidget.h b/src/tools/designer/src/lib/sdk/abstractnewformwidget.h new file mode 100644 index 00000000000..c63bcf38cd4 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractnewformwidget.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTNEWFORMWIDGET_H +#define ABSTRACTNEWFORMWIDGET_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +class QDESIGNER_SDK_EXPORT QDesignerNewFormWidgetInterface : public QWidget +{ + Q_OBJECT +public: + explicit QDesignerNewFormWidgetInterface(QWidget *parent = nullptr); + virtual ~QDesignerNewFormWidgetInterface(); + + virtual bool hasCurrentTemplate() const = 0; + virtual QString currentTemplate(QString *errorMessage = nullptr) = 0; + + static QDesignerNewFormWidgetInterface *createNewFormWidget(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + +Q_SIGNALS: + void templateActivated(); + void currentTemplateChanged(bool templateSelected); +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTNEWFORMWIDGET_H diff --git a/src/tools/designer/src/lib/sdk/abstractobjectinspector.cpp b/src/tools/designer/src/lib/sdk/abstractobjectinspector.cpp new file mode 100644 index 00000000000..fed91fe5d02 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractobjectinspector.cpp @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractobjectinspector.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerObjectInspectorInterface + + \brief The QDesignerObjectInspectorInterface class allows you to + change the focus of \QD's object inspector. + + \inmodule QtDesigner + + You can use the QDesignerObjectInspectorInterface to change the + current form window selection. For example, when implementing a + custom widget plugin: + + \snippet lib/tools_designer_src_lib_sdk_abstractobjectinspector.cpp 0 + + The QDesignerObjectInspectorInterface class is not intended to be + instantiated directly. You can retrieve an interface to \QD's + object inspector using the + QDesignerFormEditorInterface::objectInspector() function. A + pointer to \QD's current QDesignerFormEditorInterface object (\c + formEditor in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + The interface provides the core() function that you can use to + retrieve a pointer to \QD's current QDesignerFormEditorInterface + object, and the setFormWindow() function that enables you to + change the current form window selection. + + \sa QDesignerFormEditorInterface, QDesignerFormWindowInterface +*/ + +/*! + Constructs an object inspector interface with the given \a parent + and the specified window \a flags. +*/ +QDesignerObjectInspectorInterface::QDesignerObjectInspectorInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the object inspector interface. +*/ +QDesignerObjectInspectorInterface::~QDesignerObjectInspectorInterface() = default; + +/*! + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ +QDesignerFormEditorInterface *QDesignerObjectInspectorInterface::core() const +{ + return nullptr; +} + +/*! + \fn void QDesignerObjectInspectorInterface::setFormWindow(QDesignerFormWindowInterface *formWindow) + + Sets the currently selected form window to \a formWindow. +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractobjectinspector.h b/src/tools/designer/src/lib/sdk/abstractobjectinspector.h new file mode 100644 index 00000000000..1d7748c9a03 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractobjectinspector.h @@ -0,0 +1,31 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTOBJECTINSPECTOR_H +#define ABSTRACTOBJECTINSPECTOR_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class QDESIGNER_SDK_EXPORT QDesignerObjectInspectorInterface: public QWidget +{ + Q_OBJECT +public: + explicit QDesignerObjectInspectorInterface(QWidget *parent, Qt::WindowFlags flags = {}); + virtual ~QDesignerObjectInspectorInterface(); + + virtual QDesignerFormEditorInterface *core() const; + +public Q_SLOTS: + virtual void setFormWindow(QDesignerFormWindowInterface *formWindow) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTOBJECTINSPECTOR_H diff --git a/src/tools/designer/src/lib/sdk/abstractoptionspage.h b/src/tools/designer/src/lib/sdk/abstractoptionspage.h new file mode 100644 index 00000000000..f0ff5327bde --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractoptionspage.h @@ -0,0 +1,30 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTOPTIONSPAGE_P_H +#define ABSTRACTOPTIONSPAGE_P_H + +#include + +QT_BEGIN_NAMESPACE + +class QString; +class QWidget; + +class QDESIGNER_SDK_EXPORT QDesignerOptionsPageInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerOptionsPageInterface) + + QDesignerOptionsPageInterface() = default; + virtual ~QDesignerOptionsPageInterface() = default; + + virtual QString name() const = 0; + virtual QWidget *createPage(QWidget *parent) = 0; + virtual void apply() = 0; + virtual void finish() = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTOPTIONSPAGE_P_H diff --git a/src/tools/designer/src/lib/sdk/abstractoptionspage.qdoc b/src/tools/designer/src/lib/sdk/abstractoptionspage.qdoc new file mode 100644 index 00000000000..75214edaeba --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractoptionspage.qdoc @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerOptionsPageInterface + + \brief The QDesignerOptionsPageInterface provides an interface for integrating \QD's + options pages into IDE option dialogs. + + Plugin-based IDE's typically have options dialogs for which the plugins can provide + widgets to be shown for example in a tab-widget. The widgets are created on + demand when the user activates a page. + + In order to do this for \QD, a list of QDesignerOptionsPageInterface objects + can be obtained from QDesignerFormEditorInterface and registered with the option + dialog. When the respective tab is activated, createPage() is invoked to + create the widget. To apply the modified settings, apply() is called. + finish() is called when the dialog closes. + + \sa QDesignerFormEditorInterface::optionsPages(), QDesignerFormEditorInterface::setOptionsPages() + + \internal + \inmodule QtDesigner + \since 5.0 +*/ + +/*! + \fn QDesignerOptionsPageInterface::~QDesignerOptionsPageInterface() + + Destroys the QDesignerOptionsPageInterface object. +*/ + +/*! + \fn QString QDesignerOptionsPageInterface::name() const + + Returns the name of the page, which can for example be used as a tab title. +*/ + +/*! + \fn QWidget *QDesignerOptionsPageInterface::createPage(QWidget *parent) + + Creates the widget of the page parented on \a parent. +*/ + +/*! + \fn QDesignerOptionsPageInterface::apply() + + This function should be called when the user clicks \gui{OK} or \gui{Apply} to + apply the modified settings. +*/ + +/*! + \fn QDesignerOptionsPageInterface::finish() + + This function should be called when the option dialog is closed. +*/ diff --git a/src/tools/designer/src/lib/sdk/abstractpromotioninterface.cpp b/src/tools/designer/src/lib/sdk/abstractpromotioninterface.cpp new file mode 100644 index 00000000000..d279145f9a2 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractpromotioninterface.cpp @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractpromotioninterface.h" + +QT_BEGIN_NAMESPACE +/*! + \class QDesignerPromotionInterface + + \brief The QDesignerPromotionInterface provides functions for modifying + the promoted classes in Designer. + \inmodule QtDesigner + \internal + \since 4.3 +*/ + +/*! + \class QDesignerPromotionInterface::PromotedClass + \inmodule QtDesigner + A pair of database items containing the base class and the promoted class. +*/ + +/*! + \typedef QDesignerPromotionInterface::PromotedClasses + A list of PromotedClass items. +*/ + +/*! + \fn QDesignerPromotionInterface::PromotedClasses QDesignerPromotionInterface::promotedClasses() const + + Returns a list of promoted classes along with their base classes in alphabetical order. + It can be used to populate tree models for editing promoted widgets. +*/ + +/*! + \fn virtual QSet QDesignerPromotionInterface::referencedPromotedClassNames() const + + Returns a set of promoted classed that are referenced by the currently opened forms. +*/ + +/*! + \fn virtual bool QDesignerPromotionInterface::addPromotedClass(const QString &baseClass, const QString &className, const QString &includeFile, QString *errorMessage) + + Add a promoted class named \a with the base class \a and include file \a includeFile. Returns \c true on success or \c false along + with an error message in \a errorMessage on failure. +*/ + +/*! + \fn virtual bool QDesignerPromotionInterface::removePromotedClass(const QString &className, QString *errorMessage) + + Remove the promoted class named \a className unless it is referenced by a form. Returns \c true on success or \c false along + with an error message in \a errorMessage on failure. +*/ + +/*! + \fn virtual bool QDesignerPromotionInterface::changePromotedClassName(const QString &oldClassName, const QString &newClassName, QString *errorMessage) + + Change the class name of a promoted class from \a oldClassName to \a newClassName. Returns \c true on success or \c false along + with an error message in \a errorMessage on failure. +*/ + +/*! + \fn virtual bool QDesignerPromotionInterface::setPromotedClassIncludeFile(const QString &className, const QString &includeFile, QString *errorMessage) + + Change the include file of a promoted class named \a className to be \a includeFile. Returns \c true on success or \c false along + with an error message in \a errorMessage on failure. +*/ + +/*! \fn virtual QList QDesignerPromotionInterface::promotionBaseClasses() const + + Return a list of base classes that are suitable for promotion. +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractpromotioninterface.h b/src/tools/designer/src/lib/sdk/abstractpromotioninterface.h new file mode 100644 index 00000000000..752d36bc7b0 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractpromotioninterface.h @@ -0,0 +1,53 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTPROMOTIONINTERFACE_H +#define ABSTRACTPROMOTIONINTERFACE_H + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerWidgetDataBaseItemInterface; + +class QDESIGNER_SDK_EXPORT QDesignerPromotionInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerPromotionInterface) + + QDesignerPromotionInterface() = default; + virtual ~QDesignerPromotionInterface() = default; + + struct PromotedClass + { + QDesignerWidgetDataBaseItemInterface *baseItem; + QDesignerWidgetDataBaseItemInterface *promotedItem; + }; + + using PromotedClasses = QList; + + virtual PromotedClasses promotedClasses() const = 0; + + virtual QSet referencedPromotedClassNames() const = 0; + + virtual bool addPromotedClass(const QString &baseClass, + const QString &className, + const QString &includeFile, + QString *errorMessage) = 0; + + virtual bool removePromotedClass(const QString &className, QString *errorMessage) = 0; + + virtual bool changePromotedClassName(const QString &oldClassName, const QString &newClassName, QString *errorMessage) = 0; + + virtual bool setPromotedClassIncludeFile(const QString &className, const QString &includeFile, QString *errorMessage) = 0; + + virtual QList promotionBaseClasses() const = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTPROMOTIONINTERFACE_H diff --git a/src/tools/designer/src/lib/sdk/abstractpropertyeditor.cpp b/src/tools/designer/src/lib/sdk/abstractpropertyeditor.cpp new file mode 100644 index 00000000000..c9e011e15d4 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractpropertyeditor.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractpropertyeditor.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerPropertyEditorInterface + + \brief The QDesignerPropertyEditorInterface class allows you to + query and manipulate the current state of Qt Widgets Designer's property + editor. + + \inmodule QtDesigner + + QDesignerPropertyEditorInterface contains a collection of + functions that is typically used to query the property editor for + its current state, and several slots manipulating it's state. The + interface also provide a signal, propertyChanged(), which is + emitted whenever a property changes in the property editor. The + signal's arguments are the property that changed and its new + value. + + For example, when implementing a custom widget plugin, you can + connect the signal to a custom slot: + + \snippet lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp 0 + + Then the custom slot can check if the new value is within the + range we want when a specified property, belonging to a particular + widget, changes: + + \snippet lib/tools_designer_src_lib_sdk_abstractpropertyeditor.cpp 1 + + The QDesignerPropertyEditorInterface class is not intended to be + instantiated directly. You can retrieve an interface to \QD's + property editor using the + QDesignerFormEditorInterface::propertyEditor() function. A pointer + to \QD's current QDesignerFormEditorInterface object (\c + formEditor in the examples above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + The functions accessing the property editor are the core() + function that you can use to retrieve an interface to the form + editor, the currentPropertyName() function that returns the name + of the currently selected property in the property editor, the + object() function that returns the currently selected object in + \QD's workspace, and the isReadOnly() function that returns true + if the property editor is write proteced (otherwise false). + + The slots manipulating the property editor's state are the + setObject() slot that you can use to change the currently selected + object in \QD's workspace, the setPropertyValue() slot that + changes the value of a given property and the setReadOnly() slot + that control the write protection of the property editor. + + \sa QDesignerFormEditorInterface +*/ + +/*! + Constructs a property editor interface with the given \a parent and + the specified window \a flags. +*/ +QDesignerPropertyEditorInterface::QDesignerPropertyEditorInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the property editor interface. +*/ +QDesignerPropertyEditorInterface::~QDesignerPropertyEditorInterface() = default; + +/*! + Returns a pointer to \QD's current QDesignerFormEditorInterface + object. +*/ +QDesignerFormEditorInterface *QDesignerPropertyEditorInterface::core() const +{ + return nullptr; +} + +/*! + \fn bool QDesignerPropertyEditorInterface::isReadOnly() const + + Returns true if the property editor is write protected; otherwise + false. + + \sa setReadOnly() +*/ + +/*! + \fn QObject *QDesignerPropertyEditorInterface::object() const + + Returns the currently selected object in \QD's workspace. + + \sa setObject() +*/ + +/*! + \fn QString QDesignerPropertyEditorInterface::currentPropertyName() const + + Returns the name of the currently selected property in the + property editor. + + \sa setPropertyValue() +*/ + +/*! + \fn void QDesignerPropertyEditorInterface::propertyChanged(const QString &name, const QVariant &value) + + This signal is emitted whenever a property changes in the property + editor. The property that changed and its new value are specified + by \a name and \a value respectively. + + \sa setPropertyValue() +*/ + +/*! + \fn void QDesignerPropertyEditorInterface::setObject(QObject *object) + + Changes the currently selected object in \QD's workspace, to \a + object. + + \sa object() +*/ + +/*! + \fn void QDesignerPropertyEditorInterface::setPropertyValue(const QString &name, const QVariant &value, bool changed = true) + + Sets the value of the property specified by \a name to \a + value. + + In addition, the property is marked as \a changed in the property + editor, i.e. its value is different from the default value. + + \sa currentPropertyName(), propertyChanged() +*/ + +/*! + \fn void QDesignerPropertyEditorInterface::setReadOnly(bool readOnly) + + If \a readOnly is true, the property editor is made write + protected; otherwise the write protection is removed. + + \sa isReadOnly() +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractpropertyeditor.h b/src/tools/designer/src/lib/sdk/abstractpropertyeditor.h new file mode 100644 index 00000000000..8fe5d1cd951 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractpropertyeditor.h @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTPROPERTYEDITOR_H +#define ABSTRACTPROPERTYEDITOR_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QString; +class QVariant; + +class QDESIGNER_SDK_EXPORT QDesignerPropertyEditorInterface: public QWidget +{ + Q_OBJECT +public: + explicit QDesignerPropertyEditorInterface(QWidget *parent, Qt::WindowFlags flags = {}); + virtual ~QDesignerPropertyEditorInterface(); + + virtual QDesignerFormEditorInterface *core() const; + + virtual bool isReadOnly() const = 0; + virtual QObject *object() const = 0; + + virtual QString currentPropertyName() const = 0; + +Q_SIGNALS: + void propertyChanged(const QString &name, const QVariant &value); + +public Q_SLOTS: + virtual void setObject(QObject *object) = 0; + virtual void setPropertyValue(const QString &name, const QVariant &value, bool changed = true) = 0; + virtual void setReadOnly(bool readOnly) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTPROPERTYEDITOR_H diff --git a/src/tools/designer/src/lib/sdk/abstractresourcebrowser.cpp b/src/tools/designer/src/lib/sdk/abstractresourcebrowser.cpp new file mode 100644 index 00000000000..516cc903f06 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractresourcebrowser.cpp @@ -0,0 +1,16 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractresourcebrowser.h" + +QT_BEGIN_NAMESPACE + +QDesignerResourceBrowserInterface::QDesignerResourceBrowserInterface(QWidget *parent) + : QWidget(parent) +{ + +} + +QDesignerResourceBrowserInterface::~QDesignerResourceBrowserInterface() = default; + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractresourcebrowser.h b/src/tools/designer/src/lib/sdk/abstractresourcebrowser.h new file mode 100644 index 00000000000..a0c9f62dd71 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractresourcebrowser.h @@ -0,0 +1,33 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTRESOURCEBROWSER_H +#define ABSTRACTRESOURCEBROWSER_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QWidget; // FIXME: fool syncqt + +class QDESIGNER_SDK_EXPORT QDesignerResourceBrowserInterface : public QWidget +{ + Q_OBJECT +public: + explicit QDesignerResourceBrowserInterface(QWidget *parent = nullptr); + virtual ~QDesignerResourceBrowserInterface(); + + virtual void setCurrentPath(const QString &filePath) = 0; + virtual QString currentPath() const = 0; + +Q_SIGNALS: + void currentPathChanged(const QString &filePath); + void pathActivated(const QString &filePath); +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTFORMEDITOR_H + diff --git a/src/tools/designer/src/lib/sdk/abstractsettings.h b/src/tools/designer/src/lib/sdk/abstractsettings.h new file mode 100644 index 00000000000..3f5b92a0acd --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractsettings.h @@ -0,0 +1,34 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTSETTINGS_P_H +#define ABSTRACTSETTINGS_P_H + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QString; + +class QDESIGNER_SDK_EXPORT QDesignerSettingsInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerSettingsInterface) + + QDesignerSettingsInterface() = default; + virtual ~QDesignerSettingsInterface() = default; + + virtual void beginGroup(const QString &prefix) = 0; + virtual void endGroup() = 0; + + virtual bool contains(const QString &key) const = 0; + virtual void setValue(const QString &key, const QVariant &value) = 0; + virtual QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const = 0; + virtual void remove(const QString &key) = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTSETTINGS_P_H diff --git a/src/tools/designer/src/lib/sdk/abstractsettings.qdoc b/src/tools/designer/src/lib/sdk/abstractsettings.qdoc new file mode 100644 index 00000000000..6a0ac2b04ef --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractsettings.qdoc @@ -0,0 +1,23 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerSettingsInterface + + \brief The QDesignerSettingsInterface provides an abstraction of QSettings to be used in \QD. + + \QD uses the QDesignerSettingsInterface to save and retrieve its settings. + The default implementation in \QD is based on QSettings, using a file in .ini-format. + IDE integrations can provide their own implementations that for + example store the settings in a database instead of a file. + + The virtual functions are equivalent to the functions of the same name in the + QSettings class. + + \sa QDesignerFormEditorInterface::setSettingsManager(), QDesignerFormEditorInterface::settingsManager() + \sa QSettings + + \internal + \inmodule QtDesigner + \since 5.0 +*/ diff --git a/src/tools/designer/src/lib/sdk/abstractwidgetbox.cpp b/src/tools/designer/src/lib/sdk/abstractwidgetbox.cpp new file mode 100644 index 00000000000..db559bdccbe --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractwidgetbox.cpp @@ -0,0 +1,300 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractwidgetbox.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerWidgetBoxInterface + + \brief The QDesignerWidgetBoxInterface class allows you to control + the contents of \QD's widget box. + + \inmodule QtDesigner + + QDesignerWidgetBoxInterface contains a collection of functions + that is typically used to manipulate the contents of \QD's widget + box. + + \QD uses an XML file to populate its widget box. The name of that + file is one of the widget box's properties, and you can retrieve + it using the fileName() function. + + QDesignerWidgetBoxInterface also provides the save() function that + saves the contents of the widget box in the file specified by the + widget box's file name property. If you have made changes to the + widget box, for example by dropping a widget into the widget box, + without calling the save() function, the original content can be + restored by a simple invocation of the load() function: + + \snippet lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp 0 + + The QDesignerWidgetBoxInterface class is not intended to be + instantiated directly. You can retrieve an interface to Qt + Designer's widget box using the + QDesignerFormEditorInterface::widgetBox() function. A pointer to + \QD's current QDesignerFormEditorInterface object (\c formEditor + in the example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's + parameter. When implementing a custom widget plugin, you must + subclass the QDesignerCustomWidgetInterface to expose your plugin + to \QD. + + If you want to save your changes, and at the same time preserve + the original contents, you can use the save() function combined + with the setFileName() function to save your changes into another + file. Remember to store the name of the original file first: + + \snippet lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp 1 + + Then you can restore the original contents of the widget box by + resetting the file name to the original file and calling load(): + + \snippet lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp 2 + + In a similar way, you can later use your customized XML file: + + \snippet lib/tools_designer_src_lib_sdk_abstractwidgetbox.cpp 3 + + + \sa QDesignerFormEditorInterface +*/ + +/*! + Constructs a widget box interface with the given \a parent and + the specified window \a flags. +*/ +QDesignerWidgetBoxInterface::QDesignerWidgetBoxInterface(QWidget *parent, Qt::WindowFlags flags) + : QWidget(parent, flags) +{ +} + +/*! + Destroys the widget box interface. +*/ +QDesignerWidgetBoxInterface::~QDesignerWidgetBoxInterface() = default; + +/*! + \internal +*/ +int QDesignerWidgetBoxInterface::findOrInsertCategory(const QString &categoryName) +{ + int count = categoryCount(); + for (int index=0; index &item_list, const QPoint &global_mouse_pos) + +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::setFileName(const QString &fileName) + + Sets the XML file that \QD will use to populate its widget box, to + \a fileName. You must call load() to update the widget box with + the new XML file. + + \sa fileName(), load() +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::fileName() const + + Returns the name of the XML file \QD is currently using to + populate its widget box. + + \sa setFileName() +*/ + +/*! + \fn bool QDesignerWidgetBoxInterface::load() + + Populates \QD's widget box by loading (or reloading) the currently + specified XML file. Returns true if the file is successfully + loaded; otherwise false. + + \sa setFileName() +*/ + +/*! + \fn bool QDesignerWidgetBoxInterface::save() + + Saves the contents of \QD's widget box in the file specified by + the fileName() function. Returns true if the content is + successfully saved; otherwise false. + + \sa fileName(), setFileName() +*/ + + +/*! + \internal + + \class QDesignerWidgetBoxInterface::Widget + + \brief The Widget class specified a widget in \QD's widget + box component. +*/ + +/*! + \enum QDesignerWidgetBoxInterface::Widget::Type + + \value Default + \value Custom +*/ + +/*! + \fn QDesignerWidgetBoxInterface::Widget::Widget(const QString &aname, const QString &xml, const QString &icon_name, Type atype) +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::Widget::name() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Widget::setName(const QString &aname) +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::Widget::domXml() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Widget::setDomXml(const QString &xml) +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::Widget::iconName() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Widget::setIconName(const QString &icon_name) +*/ + +/*! + \fn Type QDesignerWidgetBoxInterface::Widget::type() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Widget::setType(Type atype) +*/ + +/*! + \fn bool QDesignerWidgetBoxInterface::Widget::isNull() const +*/ + + +/*! + \class QDesignerWidgetBoxInterface::Category + \brief The Category class specifies a category in \QD's widget box component. + \internal +*/ + +/*! + \enum QDesignerWidgetBoxInterface::Category::Type + + \value Default + \value Scratchpad +*/ + +/*! + \fn QDesignerWidgetBoxInterface::Category::Category(const QString &aname, Type atype) +*/ + +/*! + \fn QString QDesignerWidgetBoxInterface::Category::name() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Category::setName(const QString &aname) +*/ + +/*! + \fn int QDesignerWidgetBoxInterface::Category::widgetCount() const +*/ + +/*! + \fn Widget QDesignerWidgetBoxInterface::Category::widget(int idx) const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Category::removeWidget(int idx) +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Category::addWidget(const Widget &awidget) +*/ + +/*! + \fn Type QDesignerWidgetBoxInterface::Category::type() const +*/ + +/*! + \fn void QDesignerWidgetBoxInterface::Category::setType(Type atype) +*/ + +/*! + \fn bool QDesignerWidgetBoxInterface::Category::isNull() const +*/ + +/*! + \typedef QDesignerWidgetBoxInterface::CategoryList + \internal +*/ + +/*! + \typedef QDesignerWidgetBoxInterface::WidgetList + \internal +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractwidgetbox.h b/src/tools/designer/src/lib/sdk/abstractwidgetbox.h new file mode 100644 index 00000000000..6f6d9f4958b --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractwidgetbox.h @@ -0,0 +1,107 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTWIDGETBOX_H +#define ABSTRACTWIDGETBOX_H + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class DomUI; +class QDesignerDnDItemInterface; + +class QDesignerWidgetBoxWidgetData; + +class QDESIGNER_SDK_EXPORT QDesignerWidgetBoxInterface : public QWidget +{ + Q_OBJECT +public: + class QDESIGNER_SDK_EXPORT Widget + { + public: + enum Type { Default, Custom }; + Widget(const QString &aname = QString(), const QString &xml = QString(), + const QString &icon_name = QString(), Type atype = Default); + ~Widget(); + Widget(const Widget &w); + Widget &operator=(const Widget &w); + + QString name() const; + void setName(const QString &aname); + QString domXml() const; + void setDomXml(const QString &xml); + QString iconName() const; + void setIconName(const QString &icon_name); + Type type() const; + void setType(Type atype); + + bool isNull() const; + + private: + QSharedDataPointer m_data; + }; + + using WidgetList = QList; + + class Category + { + public: + enum Type { Default, Scratchpad }; + + Category(const QString &aname = QString(), Type atype = Default) + : m_name(aname), m_type(atype) {} + + QString name() const { return m_name; } + void setName(const QString &aname) { m_name = aname; } + int widgetCount() const { return int(m_widget_list.size()); } + Widget widget(int idx) const { return m_widget_list.at(idx); } + void removeWidget(int idx) { m_widget_list.removeAt(idx); } + void addWidget(const Widget &awidget) { m_widget_list.append(awidget); } + Type type() const { return m_type; } + void setType(Type atype) { m_type = atype; } + + bool isNull() const { return m_name.isEmpty(); } + + private: + QString m_name; + Type m_type; + WidgetList m_widget_list; + }; + + using CategoryList = QList; + + explicit QDesignerWidgetBoxInterface(QWidget *parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + virtual ~QDesignerWidgetBoxInterface(); + + virtual int categoryCount() const = 0; + virtual Category category(int cat_idx) const = 0; + virtual void addCategory(const Category &cat) = 0; + virtual void removeCategory(int cat_idx) = 0; + + virtual int widgetCount(int cat_idx) const = 0; + virtual Widget widget(int cat_idx, int wgt_idx) const = 0; + virtual void addWidget(int cat_idx, const Widget &wgt) = 0; + virtual void removeWidget(int cat_idx, int wgt_idx) = 0; + + int findOrInsertCategory(const QString &categoryName); + + virtual void dropWidgets(const QList &item_list, + const QPoint &global_mouse_pos) = 0; + + virtual void setFileName(const QString &file_name) = 0; + virtual QString fileName() const = 0; + virtual bool load() = 0; + virtual bool save() = 0; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QT_PREPEND_NAMESPACE(QDesignerWidgetBoxInterface::Widget)) + +#endif // ABSTRACTWIDGETBOX_H diff --git a/src/tools/designer/src/lib/sdk/abstractwidgetdatabase.cpp b/src/tools/designer/src/lib/sdk/abstractwidgetdatabase.cpp new file mode 100644 index 00000000000..2147afecae8 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractwidgetdatabase.cpp @@ -0,0 +1,320 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "abstractwidgetdatabase.h" +#include +#include + +QT_BEGIN_NAMESPACE + +enum { debugAbstractWidgetDataBase = 0 }; + +/*! + \class QDesignerWidgetDataBaseInterface + \brief The QDesignerWidgetDataBaseInterface class provides an interface that is used to + access and modify \QD's widget database. + \inmodule QtDesigner + \internal +*/ + +/*! + Constructs an interface to the widget database with the given \a parent. +*/ +QDesignerWidgetDataBaseInterface::QDesignerWidgetDataBaseInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! + Destroys the interface to the widget database. +*/ +QDesignerWidgetDataBaseInterface::~QDesignerWidgetDataBaseInterface() +{ + qDeleteAll(m_items); +} + +/*! + +*/ +int QDesignerWidgetDataBaseInterface::count() const +{ + return m_items.size(); +} + +/*! +*/ +QDesignerWidgetDataBaseItemInterface *QDesignerWidgetDataBaseInterface::item(int index) const +{ + return index != -1 ? m_items.at(index) : 0; +} + +/*! +*/ +int QDesignerWidgetDataBaseInterface::indexOf(QDesignerWidgetDataBaseItemInterface *item) const +{ + return m_items.indexOf(item); +} + +/*! +*/ +void QDesignerWidgetDataBaseInterface::insert(int index, QDesignerWidgetDataBaseItemInterface *item) +{ + if (debugAbstractWidgetDataBase) + qDebug() << "insert at " << index << ' ' << item->name() << " derived from " << item->extends(); + + m_items.insert(index, item); +} + +/*! +*/ +void QDesignerWidgetDataBaseInterface::append(QDesignerWidgetDataBaseItemInterface *item) +{ + if (debugAbstractWidgetDataBase) + qDebug() << "append " << item->name() << " derived from " << item->extends(); + m_items.append(item); +} + +/*! +*/ +QDesignerFormEditorInterface *QDesignerWidgetDataBaseInterface::core() const +{ + return nullptr; +} + +/*! +*/ +int QDesignerWidgetDataBaseInterface::indexOfClassName(const QString &name, bool) const +{ + const int itemCount = count(); + for (int i=0; iname() == name) + return i; + } + + return -1; +} + +/*! +*/ +int QDesignerWidgetDataBaseInterface::indexOfObject(QObject *object, bool) const +{ + if (!object) + return -1; + + const QString className = QString::fromUtf8(object->metaObject()->className()); + return indexOfClassName(className); +} + +/*! +*/ +bool QDesignerWidgetDataBaseInterface::isContainer(QObject *object, bool resolveName) const +{ + if (const QDesignerWidgetDataBaseItemInterface *i = item(indexOfObject(object, resolveName))) + return i->isContainer(); + return false; +} + +/*! +*/ +bool QDesignerWidgetDataBaseInterface::isCustom(QObject *object, bool resolveName) const +{ + if (const QDesignerWidgetDataBaseItemInterface *i = item(indexOfObject(object, resolveName))) + return i->isCustom(); + return false; +} + +/*! + \fn void QDesignerWidgetDataBaseInterface::changed() + + This signal is emitted ... +*/ + + +// Doc: No implementation - an abstract class + +/*! + \class QDesignerWidgetDataBaseItemInterface + \brief The QDesignerWidgetDataBaseItemInterface class provides an interface that is used to + access individual items in \QD's widget database. + \inmodule QtDesigner + \internal + + This class enables individual items in the widget database to be accessed and modified. + Changes to the widget database itself are made through the QDesignerWidgetDataBaseInterface + class. +*/ + +/*! + \fn virtual QDesignerWidgetDataBaseItemInterface::~QDesignerWidgetDataBaseItemInterface() + + Destroys the interface. +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::name() const = 0 + + Returns the name of the widget. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setName(const QString &name) = 0 +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::group() const = 0 + + Returns the name of the group that the widget belongs to. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setGroup(const QString &group) = 0 +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::toolTip() const = 0 + + Returns the tool tip to be used by the widget. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setToolTip(const QString &toolTip) = 0 +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::whatsThis() const = 0 + + Returns the "What's This?" help for the widget. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setWhatsThis(const QString &whatsThis) = 0 +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::includeFile() const = 0 + + Returns the name of the include file that the widget needs when being built from source. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setIncludeFile(const QString &includeFile) = 0 +*/ + +/*! + \fn virtual QIcon QDesignerWidgetDataBaseItemInterface::icon() const = 0 + + Returns the icon used to represent the item. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setIcon(const QIcon &icon) = 0 +*/ + +/*! + \fn virtual bool QDesignerWidgetDataBaseItemInterface::isCompat() const = 0 + + Returns true if this type of widget is provided for compatibility purposes (e.g. Qt3Support + widgets); otherwise returns false. + + \sa setCompat() +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setCompat(bool compat) = 0 + + If \a compat is true, the widget is handled as a compatibility widget; otherwise it is + handled normally by \QD. + + \sa isCompat() +*/ + +/*! + \fn virtual bool QDesignerWidgetDataBaseItemInterface::isContainer() const = 0 + + Returns true if this widget is intended to be used to hold other widgets; otherwise returns + false. + + \sa setContainer() +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setContainer(bool container) = 0 + + If \a container is true, the widget can be used to hold other widgets in \QD; otherwise + \QD will refuse to let the user place other widgets inside it. + + \sa isContainer() +*/ + +/*! + \fn virtual bool QDesignerWidgetDataBaseItemInterface::isCustom() const = 0 + + Returns true if the widget is a custom widget; otherwise return false if it is a standard + Qt widget. + + \sa setCustom() +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setCustom(bool custom) = 0 + + If \a custom is true, the widget is handled specially by \QD; otherwise it is handled as + a standard Qt widget. + + \sa isCustom() +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::pluginPath() const = 0 + + Returns the path to use for the widget plugin. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setPluginPath(const QString &path) = 0 +*/ + +/*! + \fn virtual bool QDesignerWidgetDataBaseItemInterface::isPromoted() const = 0 + + Returns true if the widget is promoted; otherwise returns false. + + Promoted widgets are those that represent custom widgets, but which are represented in + \QD by either standard Qt widgets or readily-available custom widgets. + + \sa setPromoted() +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setPromoted(bool promoted) = 0 + + If \a promoted is true, the widget is handled as a promoted widget by \QD and will use + a placeholder widget to represent it; otherwise it is handled as a standard widget. + + \sa isPromoted() +*/ + +/*! + \fn virtual QString QDesignerWidgetDataBaseItemInterface::extends() const = 0 + + Returns the name of the widget that the item extends. +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setExtends(const QString &s) = 0 +*/ + +/*! + \fn virtual void QDesignerWidgetDataBaseItemInterface::setDefaultPropertyValues(const QList &list) = 0 + + Sets the default property values for the widget to the given \a list. +*/ + +/*! + \fn virtual QList QDesignerWidgetDataBaseItemInterface::defaultPropertyValues() const = 0 + + Returns a list of default values to be used as properties for the item. +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractwidgetdatabase.h b/src/tools/designer/src/lib/sdk/abstractwidgetdatabase.h new file mode 100644 index 00000000000..fbf88e7e26e --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractwidgetdatabase.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTWIDGETDATABASE_H +#define ABSTRACTWIDGETDATABASE_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QIcon; +class QString; +class QDesignerFormEditorInterface; +class QDebug; + +class QDesignerWidgetDataBaseItemInterface +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerWidgetDataBaseItemInterface) + + QDesignerWidgetDataBaseItemInterface() = default; + virtual ~QDesignerWidgetDataBaseItemInterface() = default; + + virtual QString name() const = 0; + virtual void setName(const QString &name) = 0; + + virtual QString group() const = 0; + virtual void setGroup(const QString &group) = 0; + + virtual QString toolTip() const = 0; + virtual void setToolTip(const QString &toolTip) = 0; + + virtual QString whatsThis() const = 0; + virtual void setWhatsThis(const QString &whatsThis) = 0; + + virtual QString includeFile() const = 0; + virtual void setIncludeFile(const QString &includeFile) = 0; + + virtual QIcon icon() const = 0; + virtual void setIcon(const QIcon &icon) = 0; + + virtual bool isCompat() const = 0; + virtual void setCompat(bool compat) = 0; + + virtual bool isContainer() const = 0; + virtual void setContainer(bool container) = 0; + + virtual bool isCustom() const = 0; + virtual void setCustom(bool custom) = 0; + + virtual QString pluginPath() const = 0; + virtual void setPluginPath(const QString &path) = 0; + + virtual bool isPromoted() const = 0; + virtual void setPromoted(bool b) = 0; + + virtual QString extends() const = 0; + virtual void setExtends(const QString &s) = 0; + + virtual void setDefaultPropertyValues(const QList &list) = 0; + virtual QList defaultPropertyValues() const = 0; +}; + +class QDESIGNER_SDK_EXPORT QDesignerWidgetDataBaseInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerWidgetDataBaseInterface(QObject *parent = nullptr); + virtual ~QDesignerWidgetDataBaseInterface(); + + virtual int count() const; + virtual QDesignerWidgetDataBaseItemInterface *item(int index) const; + + virtual int indexOf(QDesignerWidgetDataBaseItemInterface *item) const; + virtual void insert(int index, QDesignerWidgetDataBaseItemInterface *item); + virtual void append(QDesignerWidgetDataBaseItemInterface *item); + + virtual int indexOfObject(QObject *object, bool resolveName = true) const; + virtual int indexOfClassName(const QString &className, bool resolveName = true) const; + + virtual QDesignerFormEditorInterface *core() const; + + bool isContainer(QObject *object, bool resolveName = true) const; + bool isCustom(QObject *object, bool resolveName = true) const; + +Q_SIGNALS: + void changed(); + +protected: + QList m_items; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTWIDGETDATABASE_H diff --git a/src/tools/designer/src/lib/sdk/abstractwidgetfactory.cpp b/src/tools/designer/src/lib/sdk/abstractwidgetfactory.cpp new file mode 100644 index 00000000000..52c41b6a75d --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractwidgetfactory.cpp @@ -0,0 +1,72 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include "abstractformeditor.h" +#include "abstractwidgetdatabase.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerWidgetFactoryInterface + \brief The QDesignerWidgetFactoryInterface class provides an interface that is used to control + the widget factory used by \QD. + \inmodule QtDesigner + \internal +*/ + +/*! + \fn QDesignerWidgetFactoryInterface::QDesignerWidgetFactoryInterface(QObject *parent) + + Constructs an interface to a widget factory with the given \a parent. +*/ +QDesignerWidgetFactoryInterface::QDesignerWidgetFactoryInterface(QObject *parent) + : QObject(parent) +{ +} + +/*! + \fn virtual QDesignerWidgetFactoryInterface::~QDesignerWidgetFactoryInterface() +*/ +QDesignerWidgetFactoryInterface::~QDesignerWidgetFactoryInterface() = default; + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerWidgetFactoryInterface::core() const = 0 + + Returns the core form editor interface associated with this interface. +*/ + +/*! + \fn virtual QWidget* QDesignerWidgetFactoryInterface::containerOfWidget(QWidget *child) const = 0 + + Returns the widget that contains the specified \a child widget. +*/ + +/*! + \fn virtual QWidget* QDesignerWidgetFactoryInterface::widgetOfContainer(QWidget *container) const = 0 + + +*/ + +/*! + \fn virtual QWidget *QDesignerWidgetFactoryInterface::createWidget(const QString &name, QWidget *parent) const = 0 + + Returns a new widget with the given \a name and \a parent widget. If no parent is specified, + the widget created will be a top-level widget. +*/ + +/*! + \fn virtual QLayout *QDesignerWidgetFactoryInterface::createLayout(QWidget *widget, QLayout *layout, int type) const = 0 + + Returns a new layout of the specified \a type for the given \a widget or \a layout. +*/ + +/*! + \fn virtual bool QDesignerWidgetFactoryInterface::isPassiveInteractor(QWidget *widget) = 0 +*/ + +/*! + \fn virtual void QDesignerWidgetFactoryInterface::initialize(QObject *object) const = 0 +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/abstractwidgetfactory.h b/src/tools/designer/src/lib/sdk/abstractwidgetfactory.h new file mode 100644 index 00000000000..a8c5cd23cb7 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/abstractwidgetfactory.h @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ABSTRACTWIDGETFACTORY_H +#define ABSTRACTWIDGETFACTORY_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QWidget; +class QLayout; + +class QDESIGNER_SDK_EXPORT QDesignerWidgetFactoryInterface: public QObject +{ + Q_OBJECT +public: + explicit QDesignerWidgetFactoryInterface(QObject *parent = nullptr); + virtual ~QDesignerWidgetFactoryInterface(); + + virtual QDesignerFormEditorInterface *core() const = 0; + + virtual QWidget* containerOfWidget(QWidget *w) const = 0; + virtual QWidget* widgetOfContainer(QWidget *w) const = 0; + + virtual QWidget *createWidget(const QString &name, QWidget *parentWidget = nullptr) const = 0; + virtual QLayout *createLayout(QWidget *widget, QLayout *layout, int type) const = 0; + + virtual bool isPassiveInteractor(QWidget *widget) = 0; + virtual void initialize(QObject *object) const = 0; +}; + +QT_END_NAMESPACE + +#endif // ABSTRACTWIDGETFACTORY_H diff --git a/src/tools/designer/src/lib/sdk/container.h b/src/tools/designer/src/lib/sdk/container.h new file mode 100644 index 00000000000..079e4d5c2d5 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/container.h @@ -0,0 +1,39 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef CONTAINER_H +#define CONTAINER_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWidget; + +class QDesignerContainerExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerContainerExtension) + + QDesignerContainerExtension() = default; + virtual ~QDesignerContainerExtension() = default; + + virtual int count() const = 0; + virtual QWidget *widget(int index) const = 0; + + virtual int currentIndex() const = 0; + virtual void setCurrentIndex(int index) = 0; + + virtual bool canAddWidget() const = 0; + virtual void addWidget(QWidget *widget) = 0; + virtual void insertWidget(int index, QWidget *widget) = 0; + virtual bool canRemove(int index) const = 0; + virtual void remove(int index) = 0; +}; + +Q_DECLARE_EXTENSION_INTERFACE(QDesignerContainerExtension, "org.qt-project.Qt.Designer.Container") + +QT_END_NAMESPACE + +#endif // CONTAINER_H diff --git a/src/tools/designer/src/lib/sdk/container.qdoc b/src/tools/designer/src/lib/sdk/container.qdoc new file mode 100644 index 00000000000..be3723cb357 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/container.qdoc @@ -0,0 +1,175 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerContainerExtension + \brief The QDesignerContainerExtension class allows you to add pages to + a custom multi-page container in \QD's workspace. + \inmodule QtDesigner + + \image containerextension-example.webp + + QDesignerContainerExtension provide an interface for creating + custom container extensions. A container extension consists of a + collection of functions that \QD needs to manage a multi-page + container plugin, and a list of the container's pages. + + \warning This is \e not an extension for container plugins in + general, only custom \e multi-page containers. + + To create a container extension, your extension class must inherit + from both QObject and QDesignerContainerExtension. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 6 + + Since we are implementing an interface, we must ensure that it's + made known to the meta object system using the Q_INTERFACES() + macro. This enables \QD to use the qobject_cast() function to + query for supported interfaces using nothing but a QObject + pointer. + + You must reimplement several functions to enable \QD to manage a + custom multi-page container widget: \QD uses count() to keep track + of the number pages in your container, widget() to return the page + at a given index in the list of the container's pages, and + currentIndex() to return the list index of the selected page. \QD + uses the addWidget() function to add a given page to the + container, expecting it to be appended to the list of pages, while + it expects the insertWidget() function to add a given page to the + container by inserting it at a given index. + + In \QD the extensions are not created until they are + required. For that reason you must also create a + QExtensionFactory, i.e a class that is able to make an instance of + your extension, and register it using \QD's \l + {QExtensionManager}{extension manager}. + + When a container extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create a container + extension, is found. This factory will then create the extension + for the plugin. + + There are four available types of extensions in \QD: + QDesignerContainerExtension , QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and QDesignerTaskMenuExtension. + \QD's behavior is the same whether the requested extension is + associated with a multi page container, a member sheet, a property + sheet or a task menu. + + The QExtensionFactory class provides a standard extension factory, + and can also be used as an interface for custom extension + factories. You can either create a new QExtensionFactory and + reimplement the QExtensionFactory::createExtension() function. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 7 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create a container extension as well. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 8 + + For a complete example using the QDesignerContainerExtension + class, see the \l {containerextension}{Container + Extension example}. The example shows how to create a custom + multi-page plugin for \QD. + + \sa QExtensionFactory, QExtensionManager, {Creating Custom Widget + Extensions} +*/ + +/*! + \fn QDesignerContainerExtension::~QDesignerContainerExtension() + + Destroys the extension. +*/ + +/*! + \fn int QDesignerContainerExtension::count() const + + Returns the number of pages in the container. +*/ + +/*! + \fn QWidget *QDesignerContainerExtension::widget(int index) const + + Returns the page at the given \a index in the extension's list of + pages. + + \sa addWidget(), insertWidget() +*/ + +/*! + \fn int QDesignerContainerExtension::currentIndex() const + + Returns the index of the currently selected page in the + container. + + \sa setCurrentIndex() +*/ + +/*! + \fn void QDesignerContainerExtension::setCurrentIndex(int index) + + Sets the currently selected page in the container to be the + page at the given \a index in the extension's list of pages. + + \sa currentIndex() +*/ + +/*! + \fn void QDesignerContainerExtension::addWidget(QWidget *page) + + Adds the given \a page to the container by appending it to the + extension's list of pages. + + \sa insertWidget(), remove(), widget() +*/ + +/*! + \fn void QDesignerContainerExtension::insertWidget(int index, QWidget *page) + + Adds the given \a page to the container by inserting it at the + given \a index in the extension's list of pages. + + \sa addWidget(), remove(), widget() +*/ + +/*! + \fn void QDesignerContainerExtension::remove(int index) + + Removes the page at the given \a index from the extension's list + of pages. + + \sa addWidget(), insertWidget() +*/ + +/*! + \fn bool QDesignerContainerExtension::canAddWidget() const + + Returns whether a widget can be added. This determines whether + the context menu options to add or insert pages are enabled. + + This should return false for containers that have a single, fixed + page, for example QScrollArea or QDockWidget. + + \since 5.0 + \sa addWidget(), canRemove() +*/ + +/*! + \fn bool QDesignerContainerExtension::canRemove(int index) const + + Returns whether the widget at the given \a index can be removed. + This determines whether the context menu option to remove the current + page is enabled. + + This should return false for containers that have a single, fixed + page, for example QScrollArea or QDockWidget. + + \since 5.0 + \sa remove(), canAddWidget() +*/ diff --git a/src/tools/designer/src/lib/sdk/dynamicpropertysheet.h b/src/tools/designer/src/lib/sdk/dynamicpropertysheet.h new file mode 100644 index 00000000000..9046d2e22be --- /dev/null +++ b/src/tools/designer/src/lib/sdk/dynamicpropertysheet.h @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DYNAMICPROPERTYSHEET_H +#define DYNAMICPROPERTYSHEET_H + +#include + +QT_BEGIN_NAMESPACE + +class QString; // FIXME: fool syncqt + +class QDesignerDynamicPropertySheetExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerDynamicPropertySheetExtension) + + QDesignerDynamicPropertySheetExtension() = default; + virtual ~QDesignerDynamicPropertySheetExtension() = default; + + virtual bool dynamicPropertiesAllowed() const = 0; + virtual int addDynamicProperty(const QString &propertyName, const QVariant &value) = 0; + virtual bool removeDynamicProperty(int index) = 0; + virtual bool isDynamicProperty(int index) const = 0; + virtual bool canAddDynamicProperty(const QString &propertyName) const = 0; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerDynamicPropertySheetExtension, "org.qt-project.Qt.Designer.DynamicPropertySheet") + +QT_END_NAMESPACE + +#endif // DYNAMICPROPERTYSHEET_H diff --git a/src/tools/designer/src/lib/sdk/dynamicpropertysheet.qdoc b/src/tools/designer/src/lib/sdk/dynamicpropertysheet.qdoc new file mode 100644 index 00000000000..df457a4500f --- /dev/null +++ b/src/tools/designer/src/lib/sdk/dynamicpropertysheet.qdoc @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerDynamicPropertySheetExtension + + \brief The QDesignerDynamicPropertySheetExtension class allows you to + manipulate a widget's dynamic properties in \QD's property editor. + + \sa QDesignerPropertySheetExtension, {QObject#Dynamic Properties}{Dynamic Properties} + + \inmodule QtDesigner + \since 4.3 +*/ + +/*! + \fn QDesignerDynamicPropertySheetExtension::~QDesignerDynamicPropertySheetExtension() + + Destroys the dynamic property sheet extension. +*/ + +/*! + \fn bool QDesignerDynamicPropertySheetExtension::dynamicPropertiesAllowed() const + + Returns true if the widget supports dynamic properties; otherwise returns false. +*/ + +/*! + \fn int QDesignerDynamicPropertySheetExtension::addDynamicProperty(const QString &propertyName, const QVariant &value) + + Adds a dynamic property named \a propertyName and sets its value to \a value. + Returns the index of the property if it was added successfully; otherwise returns -1 to + indicate failure. +*/ + +/*! + \fn bool QDesignerDynamicPropertySheetExtension::removeDynamicProperty(int index) + + Removes the dynamic property at the given \a index. + Returns true if the operation succeeds; otherwise returns false. +*/ + +/*! + \fn bool QDesignerDynamicPropertySheetExtension::isDynamicProperty(int index) const + + Returns true if the property at the given \a index is a dynamic property; otherwise + returns false. +*/ + +/*! + \fn bool QDesignerDynamicPropertySheetExtension::canAddDynamicProperty(const QString &propertyName) const + + Returns true if \a propertyName is a valid, unique name for a dynamic + property; otherwise returns false. + +*/ diff --git a/src/tools/designer/src/lib/sdk/extrainfo.cpp b/src/tools/designer/src/lib/sdk/extrainfo.cpp new file mode 100644 index 00000000000..1b161c3b9ec --- /dev/null +++ b/src/tools/designer/src/lib/sdk/extrainfo.cpp @@ -0,0 +1,78 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "extrainfo.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QDesignerExtraInfoExtension + \brief The QDesignerExtraInfoExtension class provides extra information about a widget in + Qt Widgets Designer. + \inmodule QtDesigner + \internal +*/ + +/*! + Returns the path to the working directory used by this extension.*/ +QString QDesignerExtraInfoExtension::workingDirectory() const +{ + return m_workingDirectory; +} + +/*! + Sets the path to the working directory used by the extension to \a workingDirectory.*/ +void QDesignerExtraInfoExtension::setWorkingDirectory(const QString &workingDirectory) +{ + m_workingDirectory = workingDirectory; +} + +/*! + \fn virtual QDesignerExtraInfoExtension::~QDesignerExtraInfoExtension() + + Destroys the extension. +*/ + +/*! + \fn virtual QDesignerFormEditorInterface *QDesignerExtraInfoExtension::core() const = 0 + + \omit + ### Description required + \endomit +*/ + +/*! + \fn virtual QWidget *QDesignerExtraInfoExtension::widget() const = 0 + + Returns the widget described by this extension. +*/ + +/*! + \fn virtual bool QDesignerExtraInfoExtension::saveUiExtraInfo(DomUI *ui) = 0 + + Saves the information about the user interface specified by \a ui, and returns true if + successful; otherwise returns false. +*/ + +/*! + \fn virtual bool QDesignerExtraInfoExtension::loadUiExtraInfo(DomUI *ui) = 0 + + Loads extra information about the user interface specified by \a ui, and returns true if + successful; otherwise returns false. +*/ + +/*! + \fn virtual bool QDesignerExtraInfoExtension::saveWidgetExtraInfo(DomWidget *widget) = 0 + + Saves the information about the specified \a widget, and returns true if successful; + otherwise returns false. +*/ + +/*! + \fn virtual bool QDesignerExtraInfoExtension::loadWidgetExtraInfo(DomWidget *widget) = 0 + + Loads extra information about the specified \a widget, and returns true if successful; + otherwise returns false. +*/ + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/extrainfo.h b/src/tools/designer/src/lib/sdk/extrainfo.h new file mode 100644 index 00000000000..0ce315e1764 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/extrainfo.h @@ -0,0 +1,45 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef EXTRAINFO_H +#define EXTRAINFO_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class DomWidget; +class DomUI; +class QWidget; + +class QDesignerFormEditorInterface; + +class QDESIGNER_SDK_EXPORT QDesignerExtraInfoExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerExtraInfoExtension) + + QDesignerExtraInfoExtension() = default; + virtual ~QDesignerExtraInfoExtension() = default; + + virtual QDesignerFormEditorInterface *core() const = 0; + virtual QWidget *widget() const = 0; + + virtual bool saveUiExtraInfo(DomUI *ui) = 0; + virtual bool loadUiExtraInfo(DomUI *ui) = 0; + + virtual bool saveWidgetExtraInfo(DomWidget *ui_widget) = 0; + virtual bool loadWidgetExtraInfo(DomWidget *ui_widget) = 0; + + QString workingDirectory() const; + void setWorkingDirectory(const QString &workingDirectory); + +private: + QString m_workingDirectory; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerExtraInfoExtension, "org.qt-project.Qt.Designer.ExtraInfo.2") + +QT_END_NAMESPACE + +#endif // EXTRAINFO_H diff --git a/src/tools/designer/src/lib/sdk/layoutdecoration.h b/src/tools/designer/src/lib/sdk/layoutdecoration.h new file mode 100644 index 00000000000..3808c5d618e --- /dev/null +++ b/src/tools/designer/src/lib/sdk/layoutdecoration.h @@ -0,0 +1,60 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef LAYOUTDECORATION_H +#define LAYOUTDECORATION_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QPoint; +class QLayoutItem; +class QWidget; +class QRect; +class QLayout; + +class QDesignerLayoutDecorationExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerLayoutDecorationExtension) + + enum InsertMode + { + InsertWidgetMode, + InsertRowMode, + InsertColumnMode + }; + + QDesignerLayoutDecorationExtension() = default; + virtual ~QDesignerLayoutDecorationExtension() = default; + + virtual QList widgets(QLayout *layout) const = 0; + + virtual QRect itemInfo(int index) const = 0; + virtual int indexOf(QWidget *widget) const = 0; + virtual int indexOf(QLayoutItem *item) const = 0; + + virtual InsertMode currentInsertMode() const = 0; + virtual int currentIndex() const = 0; + virtual QPair currentCell() const = 0; + virtual void insertWidget(QWidget *widget, const QPair &cell) = 0; + virtual void removeWidget(QWidget *widget) = 0; + + virtual void insertRow(int row) = 0; + virtual void insertColumn(int column) = 0; + virtual void simplify() = 0; + + virtual int findItemAt(const QPoint &pos) const = 0; + virtual int findItemAt(int row, int column) const = 0; // atm only for grid. + + virtual void adjustIndicator(const QPoint &pos, int index) = 0; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerLayoutDecorationExtension, "org.qt-project.Qt.Designer.LayoutDecoration") + +QT_END_NAMESPACE + +#endif // LAYOUTDECORATION_H diff --git a/src/tools/designer/src/lib/sdk/layoutdecoration.qdoc b/src/tools/designer/src/lib/sdk/layoutdecoration.qdoc new file mode 100644 index 00000000000..8f98af16857 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/layoutdecoration.qdoc @@ -0,0 +1,127 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +// ### FIXME Qt 7: std::pair in QDesignerLayoutDecorationExtension (QTBUG-115841) + +/*! + \class QDesignerLayoutDecorationExtension + \brief The QDesignerLayoutDecorationExtension class provides an extension to a layout in \QD. + \inmodule QtDesigner + \internal +*/ + +/*! + \enum QDesignerLayoutDecorationExtension::InsertMode + + This enum describes the modes that are used to insert items into a layout. + + \value InsertWidgetMode Widgets are inserted into empty cells in a layout. + \value InsertRowMode Whole rows are inserted into a vertical or grid layout. + \value InsertColumnMode Whole columns are inserted into a horizontal or grid layout. +*/ + +/*! + \fn virtual QDesignerLayoutDecorationExtension::~QDesignerLayoutDecorationExtension() + + Destroys the extension. +*/ + +/*! + \fn virtual QList QDesignerLayoutDecorationExtension::widgets(QLayout *layout) const + + Returns the widgets that are managed by the given \a layout. + + \sa insertWidget(), removeWidget() +*/ + +/*! + \fn QRect QDesignerLayoutDecorationExtension::itemInfo(int index) const + + Returns the rectangle covered by the item at the given \a index in the layout. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::indexOf(QWidget *widget) const + + Returns the index of the specified \a widget in the layout. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::indexOf(QLayoutItem *item) const + + Returns the index of the specified layout \a item. +*/ + +/*! + \fn QDesignerLayoutDecorationExtension::InsertMode QDesignerLayoutDecorationExtension::currentInsertMode() const + + Returns the current insertion mode. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::currentIndex() const + + Returns the current index in the layout. +*/ + +/*! + \fn QPair QDesignerLayoutDecorationExtension::currentCell() const + + Returns a pair containing the row and column of the current cell in the layout. +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::insertWidget(QWidget *widget, const QPair &cell) + + Inserts the given \a widget into the specified \a cell in the layout. + + \sa removeWidget() +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::removeWidget(QWidget *widget) + + Removes the specified \a widget from the layout. + + \sa insertWidget() +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::insertRow(int row) + + Inserts a new row into the form at the position specified by \a row. +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::insertColumn(int column) + + Inserts a new column into the form at the position specified by \a column. +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::simplify() + + Simplifies the layout by removing unnecessary empty rows and columns, and by changing the + number of rows or columns spanned by widgets. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::findItemAt(const QPoint &position) const + + Returns the index of the item in the layout that covers the given \a position. +*/ + +/*! + \fn int QDesignerLayoutDecorationExtension::findItemAt(int row, int column) const + + Returns the item in the layout that occupies the specified \a row and \a column in the layout. + + Currently, this only applies to grid layouts. +*/ + +/*! + \fn void QDesignerLayoutDecorationExtension::adjustIndicator(const QPoint &position, int index) + + Adjusts the indicator for the item specified by \a index so that + it lies at the given \a position on the form. +*/ diff --git a/src/tools/designer/src/lib/sdk/membersheet.h b/src/tools/designer/src/lib/sdk/membersheet.h new file mode 100644 index 00000000000..06b773a93e5 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/membersheet.h @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef MEMBERSHEET_H +#define MEMBERSHEET_H + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QString; // FIXME: fool syncqt + +class QDesignerMemberSheetExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerMemberSheetExtension) + + QDesignerMemberSheetExtension() = default; + virtual ~QDesignerMemberSheetExtension() = default; + + virtual int count() const = 0; + + virtual int indexOf(const QString &name) const = 0; + + virtual QString memberName(int index) const = 0; + virtual QString memberGroup(int index) const = 0; + virtual void setMemberGroup(int index, const QString &group) = 0; + + virtual bool isVisible(int index) const = 0; + virtual void setVisible(int index, bool b) = 0; + + virtual bool isSignal(int index) const = 0; + virtual bool isSlot(int index) const = 0; + + virtual bool inheritedFromWidget(int index) const = 0; + + virtual QString declaredInClass(int index) const = 0; + + virtual QString signature(int index) const = 0; + virtual QList parameterTypes(int index) const = 0; + virtual QList parameterNames(int index) const = 0; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerMemberSheetExtension, "org.qt-project.Qt.Designer.MemberSheet") + +QT_END_NAMESPACE + +#endif // MEMBERSHEET_H diff --git a/src/tools/designer/src/lib/sdk/membersheet.qdoc b/src/tools/designer/src/lib/sdk/membersheet.qdoc new file mode 100644 index 00000000000..65e56cda1eb --- /dev/null +++ b/src/tools/designer/src/lib/sdk/membersheet.qdoc @@ -0,0 +1,225 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerMemberSheetExtension + + \brief The QDesignerMemberSheetExtension class allows you to + manipulate a widget's member functions which is displayed when + configuring connections using \QD's mode for editing + signals and slots. + + \inmodule QtDesigner + + QDesignerMemberSheetExtension is a collection of functions that is + typically used to query a widget's member functions, and to + manipulate the member functions' appearance in \QD's signals and + slots editing mode. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 2 + + When implementing a custom widget plugin, a pointer to \QD's + current QDesignerFormEditorInterface object (\c formEditor in the + example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's parameter. + + The member sheet (and any other extension), can be retrieved by + querying \QD's extension manager using the qt_extension() + function. When you want to release the extension, you only need to + delete the pointer. + + All widgets have a default member sheet used in \QD's signals and + slots editing mode with the widget's member functions. But + QDesignerMemberSheetExtension also provides an interface for + creating custom member sheet extensions. + + \warning \QD uses the QDesignerMemberSheetExtension to facilitate + the signal and slot editing mode. Whenever a connection between + two widgets is requested, \QD will query for the widgets' member + sheet extensions. If a widget has an implemented member sheet + extension, this extension will override the default member sheet. + + To create a member sheet extension, your extension class must + inherit from both QObject and QDesignerMemberSheetExtension. Then, + since we are implementing an interface, we must ensure that it's + made known to the meta object system using the Q_INTERFACES() + macro: + + \snippet plugins/doc_src_qtdesigner.cpp 3 + + This enables \QD to use qobject_cast() to query for + supported interfaces using nothing but a QObject pointer. + + In \QD the extensions are not created until they are + required. For that reason, when implementing a member sheet + extension, you must also create a QExtensionFactory, i.e a class + that is able to make an instance of your extension, and register + it using \QD's \l {QExtensionManager}{extension manager}. + + When a widget's member sheet extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create a member sheet + extension for that widget, is found. This factory will then make + an instance of the extension. If no such factory is found, \QD + will use the default member sheet. + + There are four available types of extensions in \QD: + QDesignerContainerExtension, QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and + QDesignerTaskMenuExtension. \QD's behavior is the same whether the + requested extension is associated with a multi page container, a + member sheet, a property sheet or a task menu. + + The QExtensionFactory class provides a standard extension + factory, and can also be used as an interface for custom + extension factories. You can either create a new + QExtensionFactory and reimplement the + QExtensionFactory::createExtension() function. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 4 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create a member sheet extension as well. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 5 + + For a complete example using an extension class, see \l + {taskmenuextension}{Task Menu Extension example}. The + example shows how to create a custom widget plugin for Qt + Designer, and how to use the QDesignerTaskMenuExtension class + to add custom items to \QD's task menu. + + \sa QExtensionFactory, QExtensionManager, {Creating Custom Widget + Extensions} +*/ + +/*! + \fn QDesignerMemberSheetExtension::~QDesignerMemberSheetExtension() + + Destroys the member sheet extension. +*/ + +/*! + \fn int QDesignerMemberSheetExtension::count() const + + Returns the extension's number of member functions. +*/ + +/*! + \fn int QDesignerMemberSheetExtension::indexOf(const QString &name) const + + Returns the index of the member function specified by the given \a + name. + + \sa memberName() +*/ + +/*! + \fn QString QDesignerMemberSheetExtension::memberName(int index) const + + Returns the name of the member function with the given \a index. + + \sa indexOf() +*/ + +/*! + \fn QString QDesignerMemberSheetExtension::memberGroup(int index) const + + Returns the name of the member group specified for the function + with the given \a index. + + \sa indexOf(), setMemberGroup() +*/ + +/*! + \fn void QDesignerMemberSheetExtension::setMemberGroup(int index, const QString &group) + + Sets the member group of the member function with the given \a + index, to \a group. + + \sa indexOf(), memberGroup() +*/ + +/*! + \fn bool QDesignerMemberSheetExtension::isVisible(int index) const + + Returns true if the member function with the given \a index is + visible in \QD's signal and slot editor, otherwise false. + + \sa indexOf(), setVisible() +*/ + +/*! + \fn void QDesignerMemberSheetExtension::setVisible(int index, bool visible) + + If \a visible is true, the member function with the given \a index + is visible in \QD's signals and slots editing mode; otherwise the + member function is hidden. + + \sa indexOf(), isVisible() +*/ + +/*! + \fn virtual bool QDesignerMemberSheetExtension::isSignal(int index) const + + Returns true if the member function with the given \a index is a + signal, otherwise false. + + \sa indexOf() +*/ + +/*! + \fn bool QDesignerMemberSheetExtension::isSlot(int index) const + + Returns true if the member function with the given \a index is a + slot, otherwise false. + + \sa indexOf() +*/ + +/*! + \fn bool QDesignerMemberSheetExtension::inheritedFromWidget(int index) const + + Returns true if the member function with the given \a index is + inherited from QWidget, otherwise false. + + \sa indexOf() +*/ + +/*! + \fn QString QDesignerMemberSheetExtension::declaredInClass(int index) const + + Returns the name of the class in which the member function with + the given \a index is declared. + + \sa indexOf() +*/ + +/*! + \fn QString QDesignerMemberSheetExtension::signature(int index) const + + Returns the signature of the member function with the given \a + index. + + \sa indexOf() +*/ + +/*! + \fn QList QDesignerMemberSheetExtension::parameterTypes(int index) const + + Returns the parameter types of the member function with the given + \a index, as a QByteArray list. + + \sa indexOf(), parameterNames() +*/ + +/*! + \fn QList QDesignerMemberSheetExtension::parameterNames(int index) const + + Returns the parameter names of the member function with the given + \a index, as a QByteArray list. + + \sa indexOf(), parameterTypes() +*/ diff --git a/src/tools/designer/src/lib/sdk/propertysheet.h b/src/tools/designer/src/lib/sdk/propertysheet.h new file mode 100644 index 00000000000..9aa49b8eb27 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/propertysheet.h @@ -0,0 +1,52 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PROPERTYSHEET_H +#define PROPERTYSHEET_H + +#include + +QT_BEGIN_NAMESPACE + +class QVariant; + +class QDesignerPropertySheetExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerPropertySheetExtension) + + QDesignerPropertySheetExtension() = default; + virtual ~QDesignerPropertySheetExtension() = default; + + virtual int count() const = 0; + + virtual int indexOf(const QString &name) const = 0; + + virtual QString propertyName(int index) const = 0; + virtual QString propertyGroup(int index) const = 0; + virtual void setPropertyGroup(int index, const QString &group) = 0; + + virtual bool hasReset(int index) const = 0; + virtual bool reset(int index) = 0; + + virtual bool isVisible(int index) const = 0; + virtual void setVisible(int index, bool b) = 0; + + virtual bool isAttribute(int index) const = 0; + virtual void setAttribute(int index, bool b) = 0; + + virtual QVariant property(int index) const = 0; + virtual void setProperty(int index, const QVariant &value) = 0; + + virtual bool isChanged(int index) const = 0; + virtual void setChanged(int index, bool changed) = 0; + + virtual bool isEnabled(int index) const = 0; +}; + +Q_DECLARE_EXTENSION_INTERFACE(QDesignerPropertySheetExtension, + "org.qt-project.Qt.Designer.PropertySheet") + +QT_END_NAMESPACE + +#endif // PROPERTYSHEET_H diff --git a/src/tools/designer/src/lib/sdk/propertysheet.qdoc b/src/tools/designer/src/lib/sdk/propertysheet.qdoc new file mode 100644 index 00000000000..5804ecb1bcc --- /dev/null +++ b/src/tools/designer/src/lib/sdk/propertysheet.qdoc @@ -0,0 +1,288 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerPropertySheetExtension + + \brief The QDesignerPropertySheetExtension class allows you to + manipulate a widget's properties which is displayed in Qt + Designer's property editor. + + \sa QDesignerDynamicPropertySheetExtension + + \inmodule QtDesigner + + QDesignerPropertySheetExtension provides a collection of functions that + are typically used to query a widget's properties, and to + manipulate the properties' appearance in the property editor. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 15 + + Note that if you change the value of a property using the + QDesignerPropertySheetExtension::setProperty() function, the undo + stack is not updated. To ensure that a property's value can be + reverted using the undo stack, you must use the + QDesignerFormWindowCursorInterface::setProperty() function, or its + buddy \l + {QDesignerFormWindowCursorInterface::setWidgetProperty()}{setWidgetProperty()}, + instead. + + When implementing a custom widget plugin, a pointer to \QD's + current QDesignerFormEditorInterface object (\c formEditor in the + example above) is provided by the + QDesignerCustomWidgetInterface::initialize() function's parameter. + + The property sheet, or any other extension, can be retrieved by + querying \QD's extension manager using the qt_extension() + function. When you want to release the extension, you only need to + delete the pointer. + + All widgets have a default property sheet which populates \QD's + property editor with the widget's properties (i.e the ones defined + with the Q_PROPERTY() macro). But QDesignerPropertySheetExtension + also provides an interface for creating custom property sheet + extensions. + + Keep the following limitations in mind: + + \list + \li \QD uses the QDesignerPropertySheetExtension to feed its + property editor. Whenever a widget is selected in its workspace, + \QD will query for the widget's property sheet extension. If the + selected widget has an implemented property sheet extension, this + extension will override the default property sheet. + + \li The data types used by the property sheet for some properties + are opaque custom QVariant types containing additional information + instead of plain Qt data types. For example, this is the case for + enumerations, flags, icons, pixmaps and strings. + + \li \QD's property editor has no implementation for handling + Q_PROPERTY types for custom types that have been declared + with Q_DECLARE_METATYPE(). + \endlist + + To create a property sheet extension, your extension class must + inherit from both QObject and + QDesignerPropertySheetExtension. Then, since we are implementing + an interface, we must ensure that it's made known to the meta + object system using the Q_INTERFACES() macro: + + \snippet plugins/doc_src_qtdesigner.cpp 16 + + This enables \QD to use qobject_cast() to query for supported + interfaces using nothing but a QObject pointer. + + In \QD the extensions are not created until they are + required. For that reason, when implementing a property sheet + extension, you must also create a QExtensionFactory, i.e a class + that is able to make an instance of your extension, and register + it using \QD's \l {QExtensionManager}{extension manager}. + + When a property sheet extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until the first one that is able to create a property + sheet extension for the selected widget, is found. This factory + will then make an instance of the extension. If no such factory + can be found, \QD will use the default property sheet. + + There are four available types of extensions in \QD: + QDesignerContainerExtension, QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension and QDesignerTaskMenuExtension. Qt + Designer's behavior is the same whether the requested extension is + associated with a multi page container, a member sheet, a property + sheet or a task menu. + + The QExtensionFactory class provides a standard extension factory, + and can also be used as an interface for custom extension + factories. You can either create a new QExtensionFactory and + reimplement the QExtensionFactory::createExtension() function. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 17 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create a property sheet extension as well. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 18 + + For a complete example using an extension class, see the \l + {taskmenuextension}{Task Menu Extension example}. The + example shows how to create a custom widget plugin for Qt + Designer, and how to use the QDesignerTaskMenuExtension class + to add custom items to \QD's task menu. + + \sa QExtensionFactory, QExtensionManager, {Creating Custom Widget + Extensions} +*/ + +/*! + \fn QDesignerPropertySheetExtension::~QDesignerPropertySheetExtension() + + Destroys the property sheet extension. +*/ + +/*! + \fn int QDesignerPropertySheetExtension::count() const + + Returns the selected widget's number of properties. +*/ + +/*! + \fn int QDesignerPropertySheetExtension::indexOf(const QString &name) const + + Returns the index for a given property \a name. + + \sa propertyName() +*/ + +/*! + \fn QString QDesignerPropertySheetExtension::propertyName(int index) const + + Returns the name of the property at the given \a index. + + \sa indexOf() +*/ + +/*! + \fn QString QDesignerPropertySheetExtension::propertyGroup(int index) const + + Returns the property group for the property at the given \a index. + + \QD's property editor supports property groups, i.e. sections of + related properties. A property can be related to a group using the + setPropertyGroup() function. The default group of any property is + the name of the class that defines it. For example, the + QObject::objectName property appears within the QObject property + group. + + \sa indexOf(), setPropertyGroup() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setPropertyGroup(int index, const QString &group) + + Sets the property group for the property at the given \a index to + \a group. + + Relating a property to a group makes it appear within that group's + section in the property editor. The default property group of any + property is the name of the class that defines it. For example, + the QObject::objectName property appears within the QObject + property group. + + \sa indexOf(), property(), propertyGroup() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::hasReset(int index) const + + Returns true if the property at the given \a index has a reset + button in \QD's property editor, otherwise false. + + \sa indexOf(), reset() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::reset(int index) + + Resets the value of the property at the given \a index, to the + default value. Returns true if a default value could be found, otherwise false. + + \sa indexOf(), hasReset(), isChanged() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::isVisible(int index) const + + Returns true if the property at the given \a index is visible in + \QD's property editor, otherwise false. + + \sa indexOf(), setVisible() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setVisible(int index, bool visible) + + If \a visible is true, the property at the given \a index is + visible in \QD's property editor; otherwise the property is + hidden. + + \sa indexOf(), isVisible() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::isAttribute(int index) const + + Returns true if the property at the given \a index is an attribute, + which will be \e excluded from the UI file, otherwise false. + + \sa indexOf(), setAttribute() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setAttribute(int index, bool attribute) + + If \a attribute is true, the property at the given \a index is + made an attribute which will be \e excluded from the UI file; + otherwise it will be included. + + \sa indexOf(), isAttribute() +*/ + +/*! + \fn QVariant QDesignerPropertySheetExtension::property(int index) const + + Returns the value of the property at the given \a index. + + \sa indexOf(), setProperty(), propertyGroup() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setProperty(int index, const QVariant &value) + + Sets the \a value of the property at the given \a index. + + \warning If you change the value of a property using this + function, the undo stack is not updated. To ensure that a + property's value can be reverted using the undo stack, you must + use the QDesignerFormWindowCursorInterface::setProperty() + function, or its buddy \l + {QDesignerFormWindowCursorInterface::setWidgetProperty()}{setWidgetProperty()}, + instead. + + \sa indexOf(), property(), propertyGroup() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::isChanged(int index) const + + Returns true if the value of the property at the given \a index + differs from the property's default value, otherwise false. + + \sa indexOf(), setChanged(), reset() +*/ + +/*! + \fn void QDesignerPropertySheetExtension::setChanged(int index, bool changed) + + Sets whether the property at the given \a index is different from + its default value, or not, depending on the \a changed parameter. + + \sa indexOf(), isChanged() +*/ + +/*! + \fn bool QDesignerPropertySheetExtension::isEnabled(int index) const + + Returns true if the property at the given \a index is enabled in + \QD's property editor, otherwise false. + + \since 5.0 + + \sa indexOf() +*/ diff --git a/src/tools/designer/src/lib/sdk/sdk_global.h b/src/tools/designer/src/lib/sdk/sdk_global.h new file mode 100644 index 00000000000..b3ab7e1bcfe --- /dev/null +++ b/src/tools/designer/src/lib/sdk/sdk_global.h @@ -0,0 +1,24 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef SDK_GLOBAL_H +#define SDK_GLOBAL_H + +#include + +QT_BEGIN_NAMESPACE + +#define QDESIGNER_SDK_EXTERN Q_DECL_EXPORT +#define QDESIGNER_SDK_IMPORT Q_DECL_IMPORT + +#ifdef QT_DESIGNER_STATIC +# define QDESIGNER_SDK_EXPORT +#elif defined(QDESIGNER_SDK_LIBRARY) +# define QDESIGNER_SDK_EXPORT QDESIGNER_SDK_EXTERN +#else +# define QDESIGNER_SDK_EXPORT QDESIGNER_SDK_IMPORT +#endif + +QT_END_NAMESPACE + +#endif // SDK_GLOBAL_H diff --git a/src/tools/designer/src/lib/sdk/taskmenu.cpp b/src/tools/designer/src/lib/sdk/taskmenu.cpp new file mode 100644 index 00000000000..3e41f43b724 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/taskmenu.cpp @@ -0,0 +1,13 @@ +// Copyright (C) 2022 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "taskmenu.h" + +QT_BEGIN_NAMESPACE + +QDesignerTaskMenuExtension::~QDesignerTaskMenuExtension() = default; + +QAction *QDesignerTaskMenuExtension::preferredEditAction() const +{ return nullptr; } + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/sdk/taskmenu.h b/src/tools/designer/src/lib/sdk/taskmenu.h new file mode 100644 index 00000000000..355e11deb94 --- /dev/null +++ b/src/tools/designer/src/lib/sdk/taskmenu.h @@ -0,0 +1,30 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef TASKMENU_H +#define TASKMENU_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; + +class QDESIGNER_SDK_EXPORT QDesignerTaskMenuExtension +{ +public: + Q_DISABLE_COPY_MOVE(QDesignerTaskMenuExtension) + + QDesignerTaskMenuExtension() = default; + virtual ~QDesignerTaskMenuExtension(); + + virtual QAction *preferredEditAction() const; + + virtual QList taskActions() const = 0; +}; +Q_DECLARE_EXTENSION_INTERFACE(QDesignerTaskMenuExtension, "org.qt-project.Qt.Designer.TaskMenu") + +QT_END_NAMESPACE + +#endif // TASKMENU_H diff --git a/src/tools/designer/src/lib/sdk/taskmenu.qdoc b/src/tools/designer/src/lib/sdk/taskmenu.qdoc new file mode 100644 index 00000000000..78e76d9f1ec --- /dev/null +++ b/src/tools/designer/src/lib/sdk/taskmenu.qdoc @@ -0,0 +1,114 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only + +/*! + \class QDesignerTaskMenuExtension + \brief The QDesignerTaskMenuExtension class allows you to add custom + menu entries to \QD's task menu. + \inmodule QtDesigner + + QDesignerTaskMenuExtension provides an interface for creating + custom task menu extensions. It is typically used to create task + menu entries that are specific to a plugin in \QD. + + \QD uses the QDesignerTaskMenuExtension to feed its task + menu. Whenever a task menu is requested, \QD will query + for the selected widget's task menu extension. + + \image taskmenuextension-example.webp + + A task menu extension is a collection of QActions. The actions + appear as entries in the task menu when the plugin with the + specified extension is selected. The image above shows the custom + \gui {Edit State...} action which appears in addition to \QD's + default task menu entries: \gui Cut, \gui Copy, \gui Paste etc. + + To create a custom task menu extension, your extension class must + inherit from both QObject and QDesignerTaskMenuExtension. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 9 + + Since we are implementing an interface, we must ensure that it + is made known to the meta-object system using the Q_INTERFACES() + macro. This enables \QD to use the qobject_cast() function to + query for supported interfaces using nothing but a QObject + pointer. + + You must reimplement the taskActions() function to return a list + of actions that will be included in \QD task menu. Optionally, you + can reimplement the preferredEditAction() function to set the + action that is invoked when selecting your plugin and pressing + \key F2. The preferred edit action must be one of the actions + returned by taskActions() and, if it's not defined, pressing the + \key F2 key will simply be ignored. + + In \QD, extensions are not created until they are required. A + task menu extension, for example, is created when you click the + right mouse button over a widget in \QD's workspace. For that + reason you must also construct an extension factory, using either + QExtensionFactory or a subclass, and register it using \QD's + \l {QExtensionManager}{extension manager}. + + When a task menu extension is required, \QD's \l + {QExtensionManager}{extension manager} will run through all its + registered factories calling QExtensionFactory::createExtension() + for each until it finds one that is able to create a task menu + extension for the selected widget. This factory will then make an + instance of the extension. + + There are four available types of extensions in \QD: + QDesignerContainerExtension, QDesignerMemberSheetExtension, + QDesignerPropertySheetExtension, and QDesignerTaskMenuExtension. + \QD's behavior is the same whether the requested extension is + associated with a container, a member sheet, a property sheet or a + task menu. + + The QExtensionFactory class provides a standard extension factory, + and can also be used as an interface for custom extension + factories. You can either create a new QExtensionFactory and + reimplement the QExtensionFactory::createExtension() function. For + example: + + \snippet plugins/doc_src_qtdesigner.cpp 10 + + Or you can use an existing factory, expanding the + QExtensionFactory::createExtension() function to make the factory + able to create a task menu extension as well. For example: + + \snippet plugins/doc_src_qtdesigner.cpp 11 + + For a complete example using the QDesignerTaskMenuExtension class, + see the \l {taskmenuextension}{Task Menu Extension + example}. The example shows how to create a custom widget plugin + for \QD, and how to use the QDesignerTaskMenuExtension + class to add custom items to \QD's task menu. + + \sa QExtensionFactory, QExtensionManager, {Creating Custom Widget + Extensions} +*/ + +/*! + \fn QDesignerTaskMenuExtension::~QDesignerTaskMenuExtension() + + Destroys the task menu extension. +*/ + +/*! + \fn QAction *QDesignerTaskMenuExtension::preferredEditAction() const + + Returns the action that is invoked when selecting a plugin with + the specified extension and pressing \key F2. + + The action must be one of the actions returned by taskActions(). +*/ + +/*! + \fn QList QDesignerTaskMenuExtension::taskActions() const + + Returns the task menu extension as a list of actions which will be + included in \QD's task menu when a plugin with the specified + extension is selected. + + The function must be reimplemented to add actions to the list. +*/ diff --git a/src/tools/designer/src/lib/shared/actioneditor.cpp b/src/tools/designer/src/lib/shared/actioneditor.cpp new file mode 100644 index 00000000000..b6f3c778c19 --- /dev/null +++ b/src/tools/designer/src/lib/shared/actioneditor.cpp @@ -0,0 +1,895 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "actioneditor_p.h" +#include "actionrepository_p.h" +#include "iconloader_p.h" +#include "newactiondialog_p.h" +#include "qdesigner_menu_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "qdesigner_objectinspector_p.h" +#include "qdesigner_utils_p.h" +#include "qsimpleresource_p.h" +#include "formwindowbase_p.h" +#include "qdesigner_taskmenu_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto actionEditorViewModeKey = "ActionEditorViewMode"_L1; + +static constexpr auto iconPropertyC = "icon"_L1; +static constexpr auto shortcutPropertyC = "shortcut"_L1; +static constexpr auto menuRolePropertyC = "menuRole"_L1; +static constexpr auto toolTipPropertyC = "toolTip"_L1; +static constexpr auto checkablePropertyC = "checkable"_L1; +static constexpr auto objectNamePropertyC = "objectName"_L1; +static constexpr auto textPropertyC = "text"_L1; + +namespace qdesigner_internal { +//-------- ActionGroupDelegate +class ActionGroupDelegate: public QItemDelegate +{ +public: + ActionGroupDelegate(QObject *parent) + : QItemDelegate(parent) {} + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override + { + if (option.state & QStyle::State_Selected) + painter->fillRect(option.rect, option.palette.highlight()); + + QItemDelegate::paint(painter, option, index); + } + + void drawFocus(QPainter *, const QStyleOptionViewItem &, const QRect &) const override {} +}; + +//-------- ActionEditor +ObjectNamingMode ActionEditor::m_objectNamingMode = CamelCase; + +ActionEditor::ActionEditor(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) : + QDesignerActionEditorInterface(parent, flags), + m_core(core), + m_actionGroups(nullptr), + m_actionView(new ActionView), + m_actionNew(new QAction(tr("New..."), this)), + m_actionEdit(new QAction(tr("Edit..."), this)), + m_actionNavigateToSlot(new QAction(tr("Go to slot..."), this)), +#if QT_CONFIG(clipboard) + m_actionCopy(new QAction(tr("Copy"), this)), + m_actionCut(new QAction(tr("Cut"), this)), + m_actionPaste(new QAction(tr("Paste"), this)), +#endif + m_actionSelectAll(new QAction(tr("Select all"), this)), + m_actionDelete(new QAction(tr("Delete"), this)), + m_viewModeGroup(new QActionGroup(this)), + m_iconViewAction(nullptr), + m_listViewAction(nullptr), + m_filterWidget(nullptr) +{ + m_actionView->initialize(m_core); + m_actionView->setSelectionMode(QAbstractItemView::ExtendedSelection); + setWindowTitle(tr("Actions")); + + QVBoxLayout *l = new QVBoxLayout(this); + l->setContentsMargins(QMargins()); + l->setSpacing(0); + + QToolBar *toolbar = new QToolBar; + toolbar->setIconSize(QSize(22, 22)); + toolbar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); + l->addWidget(toolbar); + // edit actions + QIcon documentNewIcon = createIconSet(QIcon::ThemeIcon::DocumentNew, + "filenew.png"_L1); + m_actionNew->setIcon(documentNewIcon); + m_actionNew->setEnabled(false); + connect(m_actionNew, &QAction::triggered, this, &ActionEditor::slotNewAction); + toolbar->addAction(m_actionNew); + + connect(m_actionSelectAll, &QAction::triggered, m_actionView, &ActionView::selectAll); + +#if QT_CONFIG(clipboard) + m_actionCut->setEnabled(false); + connect(m_actionCut, &QAction::triggered, this, &ActionEditor::slotCut); + QIcon editCutIcon = createIconSet(QIcon::ThemeIcon::EditCut, + "editcut.png"_L1); + m_actionCut->setIcon(editCutIcon); + + m_actionCopy->setEnabled(false); + connect(m_actionCopy, &QAction::triggered, this, &ActionEditor::slotCopy); + QIcon editCopyIcon = createIconSet(QIcon::ThemeIcon::EditCopy, + "editcopy.png"_L1); + m_actionCopy->setIcon(editCopyIcon); + toolbar->addAction(m_actionCopy); + + connect(m_actionPaste, &QAction::triggered, this, &ActionEditor::slotPaste); + QIcon editPasteIcon = createIconSet(QIcon::ThemeIcon::EditPaste, + "editpaste.png"_L1); + m_actionPaste->setIcon(editPasteIcon); + toolbar->addAction(m_actionPaste); +#endif + + m_actionEdit->setEnabled(false); + connect(m_actionEdit, &QAction::triggered, this, &ActionEditor::editCurrentAction); + + connect(m_actionNavigateToSlot, &QAction::triggered, this, &ActionEditor::navigateToSlotCurrentAction); + + QIcon editDeleteIcon = createIconSet(QIcon::ThemeIcon::EditDelete, + "editdelete.png"_L1); + m_actionDelete->setIcon(editDeleteIcon); + m_actionDelete->setEnabled(false); + connect(m_actionDelete, &QAction::triggered, this, &ActionEditor::slotDelete); + toolbar->addAction(m_actionDelete); + + // Toolbutton with menu containing action group for detailed/icon view. Steal the icons from the file dialog. + // + QMenu *configureMenu; + toolbar->addWidget(createConfigureMenuButton(tr("Configure Action Editor"), &configureMenu)); + + connect(m_viewModeGroup, &QActionGroup::triggered, this, &ActionEditor::slotViewMode); + m_iconViewAction = m_viewModeGroup->addAction(tr("Icon View")); + m_iconViewAction->setData(QVariant(ActionView::IconView)); + m_iconViewAction->setCheckable(true); + m_iconViewAction->setIcon(style()->standardIcon (QStyle::SP_FileDialogListView)); + configureMenu->addAction(m_iconViewAction); + + m_listViewAction = m_viewModeGroup->addAction(tr("Detailed View")); + m_listViewAction->setData(QVariant(ActionView::DetailedView)); + m_listViewAction->setCheckable(true); + m_listViewAction->setIcon(style()->standardIcon (QStyle::SP_FileDialogDetailedView)); + configureMenu->addAction(m_listViewAction); + // filter + m_filterWidget = new QWidget(toolbar); + QHBoxLayout *filterLayout = new QHBoxLayout(m_filterWidget); + filterLayout->setContentsMargins(0, 0, 0, 0); + QLineEdit *filterLineEdit = new QLineEdit(m_filterWidget); + connect(filterLineEdit, &QLineEdit::textChanged, this, &ActionEditor::setFilter); + filterLineEdit->setPlaceholderText(tr("Filter")); + filterLineEdit->setClearButtonEnabled(true); + filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored)); + filterLayout->addWidget(filterLineEdit); + m_filterWidget->setEnabled(false); + toolbar->addWidget(m_filterWidget); + + // main layout + QSplitter *splitter = new QSplitter(Qt::Horizontal); + splitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + splitter->addWidget(m_actionView); + l->addWidget(splitter); + +#if 0 // ### implement me + m_actionGroups = new QListWidget(splitter); + splitter->addWidget(m_actionGroups); + m_actionGroups->setItemDelegate(new ActionGroupDelegate(m_actionGroups)); + m_actionGroups->setMovement(QListWidget::Static); + m_actionGroups->setResizeMode(QListWidget::Fixed); + m_actionGroups->setIconSize(QSize(48, 48)); + m_actionGroups->setFlow(QListWidget::TopToBottom); + m_actionGroups->setViewMode(QListWidget::IconMode); + m_actionGroups->setWrapping(false); +#endif + + connect(m_actionView, &ActionView::resourceImageDropped, + this, &ActionEditor::resourceImageDropped); + + connect(m_actionView, &ActionView::currentChanged,this, &ActionEditor::slotCurrentItemChanged); + // make it possible for vs integration to reimplement edit action dialog + connect(m_actionView, &ActionView::activated, this, &ActionEditor::itemActivated); + + connect(m_actionView, &ActionView::selectionChanged, + this, &ActionEditor::slotSelectionChanged); + + connect(m_actionView, &ActionView::contextMenuRequested, + this, &ActionEditor::slotContextMenuRequested); + + connect(this, &ActionEditor::itemActivated, this, &ActionEditor::editAction); + + restoreSettings(); + updateViewModeActions(); +} + +// Utility to create a configure button with menu for usage on toolbars +QToolButton *ActionEditor::createConfigureMenuButton(const QString &t, QMenu **ptrToMenu) +{ + QToolButton *configureButton = new QToolButton; + QAction *configureAction = new QAction(t, configureButton); + QIcon configureIcon = QIcon::fromTheme(QIcon::ThemeIcon::DocumentProperties, + createIconSet("configure.png"_L1)); + configureAction->setIcon(configureIcon); + QMenu *configureMenu = new QMenu(configureButton); + configureAction->setMenu(configureMenu); + configureButton->setDefaultAction(configureAction); + configureButton->setPopupMode(QToolButton::InstantPopup); + *ptrToMenu = configureMenu; + return configureButton; +} + +ActionEditor::~ActionEditor() +{ + saveSettings(); +} + +QAction *ActionEditor::actionNew() const +{ + return m_actionNew; +} + +QAction *ActionEditor::actionDelete() const +{ + return m_actionDelete; +} + +QDesignerFormWindowInterface *ActionEditor::formWindow() const +{ + return m_formWindow; +} + +void ActionEditor::setFormWindow(QDesignerFormWindowInterface *formWindow) +{ + if (formWindow != nullptr && formWindow->mainContainer() == nullptr) + formWindow = nullptr; + + // we do NOT rely on this function to update the action editor + if (m_formWindow == formWindow) + return; + + if (m_formWindow != nullptr) { + const ActionList actionList = m_formWindow->mainContainer()->findChildren(); + for (QAction *action : actionList) + disconnect(action, &QAction::changed, this, &ActionEditor::slotActionChanged); + } + + m_formWindow = formWindow; + + m_actionView->model()->clearActions(); + + m_actionEdit->setEnabled(false); +#if QT_CONFIG(clipboard) + m_actionCopy->setEnabled(false); + m_actionCut->setEnabled(false); +#endif + m_actionDelete->setEnabled(false); + + if (!formWindow || !formWindow->mainContainer()) { + m_actionNew->setEnabled(false); + m_filterWidget->setEnabled(false); + return; + } + + m_actionNew->setEnabled(true); + m_filterWidget->setEnabled(true); + + const ActionList actionList = formWindow->mainContainer()->findChildren(); + for (QAction *action : actionList) + if (!action->isSeparator() && core()->metaDataBase()->item(action) != nullptr) { + // Show unless it has a menu. However, listen for change on menu actions also as it might be removed + if (!action->menu()) + m_actionView->model()->addAction(action); + connect(action, &QAction::changed, this, &ActionEditor::slotActionChanged); + } + + setFilter(m_filter); +} + +void ActionEditor::slotSelectionChanged(const QItemSelection& selected, const QItemSelection& /*deselected*/) +{ + const bool hasSelection = !selected.indexes().isEmpty(); +#if QT_CONFIG(clipboard) + m_actionCopy->setEnabled(hasSelection); + m_actionCut->setEnabled(hasSelection); +#endif + m_actionDelete->setEnabled(hasSelection); +} + +void ActionEditor::slotCurrentItemChanged(QAction *action) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (m_withinSelectAction || fw == nullptr) + return; + + const bool hasCurrentAction = action != nullptr; + m_actionEdit->setEnabled(hasCurrentAction); + + if (!action) { + fw->clearSelection(); + return; + } + + QDesignerObjectInspector *oi = qobject_cast(core()->objectInspector()); + + // Check if we have at least one associated QWidget: + const auto associatedObjects = action->associatedObjects(); + auto it = std::find_if(associatedObjects.cbegin(), associatedObjects.cend(), + [](QObject *obj) { + return qobject_cast(obj) != nullptr; + }); + if (it == associatedObjects.cend()) { + // Special case: action not in object tree. Deselect all and set in property editor + fw->clearSelection(false); + if (oi) + oi->clearSelection(); + core()->propertyEditor()->setObject(action); + } else { + if (oi) + oi->selectObject(action); + } +} + +void ActionEditor::slotActionChanged() +{ + QAction *action = qobject_cast(sender()); + Q_ASSERT(action != nullptr); + + ActionModel *model = m_actionView->model(); + const int row = model->findAction(action); + if (row == -1) { + if (action->menu() == nullptr) // action got its menu deleted, create item + model->addAction(action); + } else if (action->menu() != nullptr) { // action got its menu created, remove item + model->removeRow(row); + } else { + // action text or icon changed, update item + model->update(row); + } +} + +QDesignerFormEditorInterface *ActionEditor::core() const +{ + return m_core; +} + +QString ActionEditor::filter() const +{ + return m_filter; +} + +void ActionEditor::setFilter(const QString &f) +{ + m_filter = f; + m_actionView->filter(m_filter); +} + +// Set changed state of icon property, reset when icon is cleared +static void refreshIconPropertyChanged(const QAction *action, QDesignerPropertySheetExtension *sheet) +{ + sheet->setChanged(sheet->indexOf(iconPropertyC), !action->icon().isNull()); +} + +void ActionEditor::manageAction(QAction *action) +{ + action->setParent(formWindow()->mainContainer()); + core()->metaDataBase()->add(action); + + if (action->isSeparator() || action->menu() != nullptr) + return; + + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), action); + sheet->setChanged(sheet->indexOf(objectNamePropertyC), true); + sheet->setChanged(sheet->indexOf(textPropertyC), true); + refreshIconPropertyChanged(action, sheet); + + m_actionView->setCurrentIndex(m_actionView->model()->addAction(action)); + connect(action, &QAction::changed, this, &ActionEditor::slotActionChanged); +} + +void ActionEditor::unmanageAction(QAction *action) +{ + core()->metaDataBase()->remove(action); + action->setParent(nullptr); + + disconnect(action, &QAction::changed, this, &ActionEditor::slotActionChanged); + + const int row = m_actionView->model()->findAction(action); + if (row != -1) + m_actionView->model()->remove(row); +} + +// Set an initial property and mark it as changed in the sheet +static void setInitialProperty(QDesignerPropertySheetExtension *sheet, const QString &name, const QVariant &value) +{ + const int index = sheet->indexOf(name); + Q_ASSERT(index != -1); + sheet->setProperty(index, value); + sheet->setChanged(index, true); +} + +void ActionEditor::slotNewAction() +{ + NewActionDialog dlg(this); + dlg.setWindowTitle(tr("New action")); + + if (dlg.exec() == QDialog::Accepted) { + const ActionData actionData = dlg.actionData(); + m_actionView->clearSelection(); + QAction *action = new QAction(formWindow()); + action->setObjectName(actionData.name); + formWindow()->ensureUniqueObjectName(action); + action->setText(actionData.text); + + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), action); + if (!actionData.toolTip.isEmpty()) + setInitialProperty(sheet, toolTipPropertyC, actionData.toolTip); + + if (actionData.checkable) + setInitialProperty(sheet, checkablePropertyC, QVariant(true)); + + if (!actionData.keysequence.value().isEmpty()) + setInitialProperty(sheet, shortcutPropertyC, QVariant::fromValue(actionData.keysequence)); + + sheet->setProperty(sheet->indexOf(iconPropertyC), QVariant::fromValue(actionData.icon)); + + setInitialProperty(sheet, menuRolePropertyC, QVariant::fromValue(actionData.menuRole)); + + AddActionCommand *cmd = new AddActionCommand(formWindow()); + cmd->init(action); + formWindow()->commandHistory()->push(cmd); + } +} + +// return a FormWindow command to apply an icon or a reset command in case it +// is empty. + +static QDesignerFormWindowCommand *setIconPropertyCommand(const PropertySheetIconValue &newIcon, QAction *action, QDesignerFormWindowInterface *fw) +{ + const QString iconProperty = iconPropertyC; + if (newIcon.isEmpty()) { + ResetPropertyCommand *cmd = new ResetPropertyCommand(fw); + cmd->init(action, iconProperty); + return cmd; + } + SetPropertyCommand *cmd = new SetPropertyCommand(fw); + cmd->init(action, iconProperty, QVariant::fromValue(newIcon)); + return cmd; +} + +// return a FormWindow command to apply a QKeySequence or a reset command +// in case it is empty. + +static QDesignerFormWindowCommand *setKeySequencePropertyCommand(const PropertySheetKeySequenceValue &ks, QAction *action, QDesignerFormWindowInterface *fw) +{ + const QString shortcutProperty = shortcutPropertyC; + if (ks.value().isEmpty()) { + ResetPropertyCommand *cmd = new ResetPropertyCommand(fw); + cmd->init(action, shortcutProperty); + return cmd; + } + SetPropertyCommand *cmd = new SetPropertyCommand(fw); + cmd->init(action, shortcutProperty, QVariant::fromValue(ks)); + return cmd; +} + +// return a FormWindow command to apply a POD value or reset command in case +// it is equal to the default value. + +template +QDesignerFormWindowCommand *setPropertyCommand(const QString &name, T value, T defaultValue, + QObject *o, QDesignerFormWindowInterface *fw) +{ + if (value == defaultValue) { + ResetPropertyCommand *cmd = new ResetPropertyCommand(fw); + cmd->init(o, name); + return cmd; + } + SetPropertyCommand *cmd = new SetPropertyCommand(fw); + cmd->init(o, name, QVariant(value)); + return cmd; +} + +// Return the text value of a string property via PropertySheetStringValue +static inline QString textPropertyValue(const QDesignerPropertySheetExtension *sheet, const QString &name) +{ + const int index = sheet->indexOf(name); + Q_ASSERT(index != -1); + const PropertySheetStringValue ps = qvariant_cast(sheet->property(index)); + return ps.value(); +} + +void ActionEditor::editAction(QAction *action, int column) +{ + if (!action) + return; + + NewActionDialog dlg(this); + dlg.setWindowTitle(tr("Edit action")); + + ActionData oldActionData; + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), action); + oldActionData.name = action->objectName(); + oldActionData.text = action->text(); + oldActionData.toolTip = textPropertyValue(sheet, toolTipPropertyC); + oldActionData.icon = qvariant_cast(sheet->property(sheet->indexOf(iconPropertyC))); + oldActionData.keysequence = ActionModel::actionShortCut(sheet); + oldActionData.checkable = action->isCheckable(); + oldActionData.menuRole.value = action->menuRole(); + dlg.setActionData(oldActionData); + + switch (column) { + case qdesigner_internal::ActionModel::NameColumn: + dlg.focusName(); + break; + case qdesigner_internal::ActionModel::TextColumn: + dlg.focusText(); + break; + case qdesigner_internal::ActionModel::ShortCutColumn: + dlg.focusShortcut(); + break; + case qdesigner_internal::ActionModel::CheckedColumn: + dlg.focusCheckable(); + break; + case qdesigner_internal::ActionModel::ToolTipColumn: + dlg.focusTooltip(); + break; + case qdesigner_internal::ActionModel::MenuRoleColumn: + dlg.focusMenuRole(); + break; + } + + if (!dlg.exec()) + return; + + // figure out changes and whether to start a macro + const ActionData newActionData = dlg.actionData(); + const unsigned changeMask = newActionData.compare(oldActionData); + if (changeMask == 0u) + return; + + const bool severalChanges = (changeMask != ActionData::TextChanged) && (changeMask != ActionData::NameChanged) + && (changeMask != ActionData::ToolTipChanged) && (changeMask != ActionData::IconChanged) + && (changeMask != ActionData::CheckableChanged) && (changeMask != ActionData::KeysequenceChanged) + && (changeMask != ActionData::MenuRoleChanged); + + QDesignerFormWindowInterface *fw = formWindow(); + QUndoStack *undoStack = fw->commandHistory(); + if (severalChanges) + fw->beginCommand(u"Edit action"_s); + + if (changeMask & ActionData::NameChanged) + undoStack->push(createTextPropertyCommand(objectNamePropertyC, newActionData.name, action, fw)); + + if (changeMask & ActionData::TextChanged) + undoStack->push(createTextPropertyCommand(textPropertyC, newActionData.text, action, fw)); + + if (changeMask & ActionData::ToolTipChanged) + undoStack->push(createTextPropertyCommand(toolTipPropertyC, newActionData.toolTip, action, fw)); + + if (changeMask & ActionData::IconChanged) + undoStack->push(setIconPropertyCommand(newActionData.icon, action, fw)); + + if (changeMask & ActionData::CheckableChanged) + undoStack->push(setPropertyCommand(checkablePropertyC, newActionData.checkable, false, action, fw)); + + if (changeMask & ActionData::KeysequenceChanged) + undoStack->push(setKeySequencePropertyCommand(newActionData.keysequence, action, fw)); + + if (changeMask & ActionData::MenuRoleChanged) + undoStack->push(setPropertyCommand(menuRolePropertyC, static_cast(newActionData.menuRole.value), QAction::NoRole, action, fw)); + + if (severalChanges) + fw->endCommand(); +} + +void ActionEditor::editCurrentAction() +{ + if (QAction *a = m_actionView->currentAction()) + editAction(a); +} + +void ActionEditor::navigateToSlotCurrentAction() +{ + if (QAction *a = m_actionView->currentAction()) + QDesignerTaskMenu::navigateToSlot(m_core, a, u"triggered()"_s); +} + +void ActionEditor::deleteActions(QDesignerFormWindowInterface *fw, const ActionList &actions) +{ + // We need a macro even in the case of single action because the commands might cause the + // scheduling of other commands (signal slots connections) + const QString description = actions.size() == 1 + ? tr("Remove action '%1'").arg(actions.constFirst()->objectName()) + : tr("Remove actions"); + fw->beginCommand(description); + for (QAction *action : actions) { + RemoveActionCommand *cmd = new RemoveActionCommand(fw); + cmd->init(action); + fw->commandHistory()->push(cmd); + } + fw->endCommand(); +} + +#if QT_CONFIG(clipboard) +void ActionEditor::copyActions(QDesignerFormWindowInterface *fwi, const ActionList &actions) +{ + FormWindowBase *fw = qobject_cast(fwi); + if (!fw ) + return; + + FormBuilderClipboard clipboard; + clipboard.m_actions = actions; + + if (clipboard.empty()) + return; + + QEditorFormBuilder *formBuilder = fw->createFormBuilder(); + Q_ASSERT(formBuilder); + + QBuffer buffer; + if (buffer.open(QIODevice::WriteOnly)) + if (formBuilder->copy(&buffer, clipboard)) + qApp->clipboard()->setText(QString::fromUtf8(buffer.buffer()), QClipboard::Clipboard); + delete formBuilder; +} +#endif + +void ActionEditor::slotDelete() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + + const ActionView::ActionList selection = m_actionView->selectedActions(); + if (selection.isEmpty()) + return; + + deleteActions(fw, selection); +} + +// UnderScore: "Open file" -> actionOpen_file +static QString underscore(QString text) +{ + static const QRegularExpression nonAsciiPattern(u"[^a-zA-Z_0-9]"_s); + Q_ASSERT(nonAsciiPattern.isValid()); + text.replace(nonAsciiPattern, "_"_L1); + static const QRegularExpression multipleSpacePattern(u"__*"_s); + Q_ASSERT(multipleSpacePattern.isValid()); + text.replace(multipleSpacePattern, "_"_L1); + if (text.endsWith(u'_')) + text.chop(1); + return text; +} + +// CamelCase: "Open file" -> actionOpenFile, ignoring non-ASCII letters. + +enum CharacterCategory { OtherCharacter, DigitOrAsciiLetter, NonAsciiLetter }; + +static inline CharacterCategory category(QChar c) +{ + if (c.isDigit()) + return DigitOrAsciiLetter; + if (c.isLetter()) { + const ushort uc = c.unicode(); + return (uc >= 'a' && uc <= 'z') || (uc >= 'A' && uc <= 'Z') + ? DigitOrAsciiLetter : NonAsciiLetter; + } + return OtherCharacter; +} + +static QString camelCase(const QString &text) +{ + QString result; + result.reserve(text.size()); + bool lastCharAccepted = false; + for (QChar c : text) { + const CharacterCategory cat = category(c); + if (cat != NonAsciiLetter) { + const bool acceptable = cat == DigitOrAsciiLetter; + if (acceptable) + result.append(lastCharAccepted ? c : c.toUpper()); // New word starts + lastCharAccepted = acceptable; + } + } + return result; +} + +QString ActionEditor::actionTextToName(const QString &text, const QString &prefix) +{ + QString name = text; + if (name.isEmpty()) + return QString(); + return prefix + (m_objectNamingMode == CamelCase ? camelCase(text) : underscore(text)); + +} + +void ActionEditor::resourceImageDropped(const QString &path, QAction *action) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + + QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), action); + const PropertySheetIconValue oldIcon = + qvariant_cast(sheet->property(sheet->indexOf(iconPropertyC))); + PropertySheetIconValue newIcon; + newIcon.setPixmap(QIcon::Normal, QIcon::Off, PropertySheetPixmapValue(path)); + if (newIcon.paths().isEmpty() || newIcon.paths() == oldIcon.paths()) + return; + + fw->commandHistory()->push(setIconPropertyCommand(newIcon , action, fw)); +} + +void ActionEditor::mainContainerChanged() +{ + // Invalidate references to objects kept in model + if (sender() == formWindow()) + setFormWindow(nullptr); +} + +void ActionEditor::clearSelection() +{ + // For use by the menu editor; block the syncing of the object inspector + // in slotCurrentItemChanged() since the menu editor updates it itself. + m_withinSelectAction = true; + m_actionView->clearSelection(); + m_withinSelectAction = false; +} + +void ActionEditor::selectAction(QAction *a) +{ + // For use by the menu editor; block the syncing of the object inspector + // in slotCurrentItemChanged() since the menu editor updates it itself. + m_withinSelectAction = true; + m_actionView->selectAction(a); + m_withinSelectAction = false; +} + +void ActionEditor::slotViewMode(QAction *a) +{ + m_actionView->setViewMode(a->data().toInt()); + updateViewModeActions(); +} + +void ActionEditor::slotSelectAssociatedWidget(QWidget *w) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw ) + return; + + QDesignerObjectInspector *oi = qobject_cast(core()->objectInspector()); + if (!oi) + return; + + fw->clearSelection(); // Actually, there are no widgets selected due to focus in event handling. Just to be sure. + oi->selectObject(w); +} + +void ActionEditor::restoreSettings() +{ + QDesignerSettingsInterface *settings = m_core->settingsManager(); + m_actionView->setViewMode(settings->value(actionEditorViewModeKey, 0).toInt()); + updateViewModeActions(); +} + +void ActionEditor::saveSettings() +{ + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->setValue(actionEditorViewModeKey, m_actionView->viewMode()); +} + +void ActionEditor::updateViewModeActions() +{ + switch (m_actionView->viewMode()) { + case ActionView::IconView: + m_iconViewAction->setChecked(true); + break; + case ActionView::DetailedView: + m_listViewAction->setChecked(true); + break; + } +} + +#if QT_CONFIG(clipboard) +void ActionEditor::slotCopy() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw ) + return; + + const ActionView::ActionList selection = m_actionView->selectedActions(); + if (selection.isEmpty()) + return; + + copyActions(fw, selection); +} + +void ActionEditor::slotCut() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw ) + return; + + const ActionView::ActionList selection = m_actionView->selectedActions(); + if (selection.isEmpty()) + return; + + copyActions(fw, selection); + deleteActions(fw, selection); +} + +void ActionEditor::slotPaste() +{ + FormWindowBase *fw = qobject_cast(formWindow()); + if (!fw) + return; + m_actionView->clearSelection(); + fw->paste(FormWindowBase::PasteActionsOnly); +} +#endif + +void ActionEditor::slotContextMenuRequested(QContextMenuEvent *e, QAction *item) +{ + QMenu menu(this); + menu.addAction(m_actionNew); + menu.addSeparator(); + menu.addAction(m_actionEdit); + if (QDesignerTaskMenu::isSlotNavigationEnabled(m_core)) + menu.addAction(m_actionNavigateToSlot); + + // Associated Widgets + if (QAction *action = m_actionView->currentAction()) { + const QWidgetList associatedWidgets = ActionModel::associatedWidgets(action); + if (!associatedWidgets.isEmpty()) { + QMenu *associatedWidgetsSubMenu = menu.addMenu(tr("Used In")); + for (QWidget *w : associatedWidgets) { + associatedWidgetsSubMenu->addAction(w->objectName(), + this, [this, w] { this->slotSelectAssociatedWidget(w); }); + } + } + } + + menu.addSeparator(); +#if QT_CONFIG(clipboard) + menu.addAction(m_actionCut); + menu.addAction(m_actionCopy); + menu.addAction(m_actionPaste); +#endif + menu.addAction(m_actionSelectAll); + menu.addAction(m_actionDelete); + menu.addSeparator(); + menu.addAction(m_iconViewAction); + menu.addAction(m_listViewAction); + + emit contextMenuRequested(&menu, item); + + menu.exec(e->globalPos()); + e->accept(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/lib/shared/actioneditor_p.h b/src/tools/designer/src/lib/shared/actioneditor_p.h new file mode 100644 index 00000000000..471d12d043d --- /dev/null +++ b/src/tools/designer/src/lib/shared/actioneditor_p.h @@ -0,0 +1,145 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ACTIONEDITOR_H +#define ACTIONEDITOR_H + +#include "shared_global_p.h" +#include "shared_enums_p.h" +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerPropertyEditorInterface; +class QDesignerSettingsInterface; +class QMenu; +class QActionGroup; +class QItemSelection; +class QListWidget; +class QPushButton; +class QLineEdit; +class QToolButton; + +namespace qdesigner_internal { + +class ActionView; +class ResourceMimeData; + +class QDESIGNER_SHARED_EXPORT ActionEditor: public QDesignerActionEditorInterface +{ + Q_OBJECT +public: + explicit ActionEditor(QDesignerFormEditorInterface *core, QWidget *parent = nullptr, + Qt::WindowFlags flags = {}); + ~ActionEditor() override; + + QDesignerFormWindowInterface *formWindow() const; + void setFormWindow(QDesignerFormWindowInterface *formWindow) override; + + QDesignerFormEditorInterface *core() const override; + + QAction *actionNew() const; + QAction *actionDelete() const; + + QString filter() const; + + void manageAction(QAction *action) override; + void unmanageAction(QAction *action) override; + + static ObjectNamingMode objectNamingMode() { return m_objectNamingMode; } + static void setObjectNamingMode(ObjectNamingMode n) { m_objectNamingMode = n; } + + static QString actionTextToName(const QString &text, + const QString &prefix = QLatin1StringView("action")); + + // Utility to create a configure button with menu for usage on toolbars + static QToolButton *createConfigureMenuButton(const QString &t, QMenu **ptrToMenu); + +public slots: + void setFilter(const QString &filter); + void mainContainerChanged(); + void clearSelection(); + void selectAction(QAction *a); // For use by the menu editor + +private slots: + void slotCurrentItemChanged(QAction *item); + void slotSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void editAction(QAction *item, int column = -1); + void editCurrentAction(); + void navigateToSlotCurrentAction(); + void slotActionChanged(); + void slotNewAction(); + void slotDelete(); + void resourceImageDropped(const QString &path, QAction *action); + void slotContextMenuRequested(QContextMenuEvent *, QAction *); + void slotViewMode(QAction *a); + void slotSelectAssociatedWidget(QWidget *w); +#if QT_CONFIG(clipboard) + void slotCopy(); + void slotCut(); + void slotPaste(); +#endif + +signals: + void itemActivated(QAction *item, int column); + // Context menu for item or global menu if item == 0. + void contextMenuRequested(QMenu *menu, QAction *item); + +private: + using ActionList = QList; + void deleteActions(QDesignerFormWindowInterface *formWindow, const ActionList &); +#if QT_CONFIG(clipboard) + void copyActions(QDesignerFormWindowInterface *formWindow, const ActionList &); +#endif + + void restoreSettings(); + void saveSettings(); + + void updateViewModeActions(); + + static ObjectNamingMode m_objectNamingMode; + + QDesignerFormEditorInterface *m_core; + QPointer m_formWindow; + QListWidget *m_actionGroups; + + ActionView *m_actionView; + + QAction *m_actionNew; + QAction *m_actionEdit; + QAction *m_actionNavigateToSlot; +#if QT_CONFIG(clipboard) + QAction *m_actionCopy; + QAction *m_actionCut; + QAction *m_actionPaste; +#endif + QAction *m_actionSelectAll; + QAction *m_actionDelete; + + QActionGroup *m_viewModeGroup; + QAction *m_iconViewAction; + QAction *m_listViewAction; + + QString m_filter; + QWidget *m_filterWidget; + bool m_withinSelectAction = false; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ACTIONEDITOR_H diff --git a/src/tools/designer/src/lib/shared/actionprovider_p.h b/src/tools/designer/src/lib/shared/actionprovider_p.h new file mode 100644 index 00000000000..16f987da90c --- /dev/null +++ b/src/tools/designer/src/lib/shared/actionprovider_p.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef ACTIONPROVIDER_H +#define ACTIONPROVIDER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QAction; + +class QDesignerActionProviderExtension +{ +public: + virtual ~QDesignerActionProviderExtension() = default; + + virtual QRect actionGeometry(QAction *action) const = 0; + virtual QAction *actionAt(const QPoint &pos) const = 0; + + virtual void adjustIndicator(const QPoint &pos) = 0; +}; + +// Find action at the given position for a widget that has actionGeometry() (QToolBar, +// QMenuBar, QMenu). They usually have actionAt(), but that fails since Designer usually sets +// WA_TransparentForMouseEvents on the widgets. +template + int actionIndexAt(const Widget *w, const QPoint &pos, Qt::Orientation orientation) +{ + const auto actions = w->actions(); + if (actions.isEmpty()) + return -1; + // actionGeometry() can be wrong sometimes; it returns a geometry that + // stretches to the end of the toolbar/menu bar. So, check from the beginning + // in the case of a horizontal right-to-left orientation. + const bool checkTopRight = orientation == Qt::Horizontal && w->layoutDirection() == Qt::RightToLeft; + const QPoint topRight = QPoint(w->rect().width(), 0); + for (qsizetype index = 0, actionCount = actions.size(); index < actionCount; ++index) { + QRect g = w->actionGeometry(actions.at(index)); + if (checkTopRight) + g.setTopRight(topRight); + else + g.setTopLeft(QPoint(0, 0)); + + if (g.contains(pos)) + return int(index); + } + return -1; +} + +Q_DECLARE_EXTENSION_INTERFACE(QDesignerActionProviderExtension, "org.qt-project.Qt.Designer.ActionProvider") + +QT_END_NAMESPACE + +#endif // ACTIONPROVIDER_H diff --git a/src/tools/designer/src/lib/shared/actionrepository.cpp b/src/tools/designer/src/lib/shared/actionrepository.cpp new file mode 100644 index 00000000000..59d8fc6b754 --- /dev/null +++ b/src/tools/designer/src/lib/shared/actionrepository.cpp @@ -0,0 +1,655 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "actionrepository_p.h" +#include "qtresourceview_p.h" +#include "iconloader_p.h" +#include "qdesigner_utils_p.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + enum { listModeIconSize = 16, iconModeIconSize = 24 }; +} + +static constexpr auto actionMimeType = "action-repository/actions"_L1; +static constexpr auto plainTextMimeType = "text/plain"_L1; + +static inline QAction *actionOfItem(const QStandardItem* item) +{ + return qvariant_cast(item->data(qdesigner_internal::ActionModel::ActionRole)); +} + +namespace qdesigner_internal { + +// ----------- ActionModel +ActionModel::ActionModel(QWidget *parent ) : + QStandardItemModel(parent), + m_emptyIcon(emptyIcon()) +{ + QStringList headers; + headers += tr("Name"); + headers += tr("Used"); + headers += tr("Text"); + headers += tr("Shortcut"); + headers += tr("Checkable"); + headers += tr("ToolTip"); + headers += tr("MenuRole"); + Q_ASSERT(NumColumns == headers.size()); + setHorizontalHeaderLabels(headers); +} + +void ActionModel::clearActions() +{ + removeRows(0, rowCount()); +} + +int ActionModel::findAction(QAction *action) const +{ + const int rows = rowCount(); + for (int i = 0; i < rows; i++) + if (action == actionOfItem(item(i))) + return i; + return -1; +} + +void ActionModel::update(int row) +{ + Q_ASSERT(m_core); + // need to create the row list ... grrr.. + if (row >= rowCount()) + return; + + QStandardItemList list; + for (int i = 0; i < NumColumns; i++) + list += item(row, i); + + setItems(m_core, actionOfItem(list.constFirst()), m_emptyIcon, list); +} + +void ActionModel::remove(int row) +{ + qDeleteAll(takeRow(row)); +} + +QModelIndex ActionModel::addAction(QAction *action) +{ + Q_ASSERT(m_core); + QStandardItemList items; + const Qt::ItemFlags flags = Qt::ItemIsSelectable|Qt::ItemIsDropEnabled|Qt::ItemIsDragEnabled|Qt::ItemIsEnabled; + + QVariant itemData; + itemData.setValue(action); + + for (int i = 0; i < NumColumns; i++) { + QStandardItem *item = new QStandardItem; + item->setData(itemData, ActionRole); + item->setFlags(flags); + items.push_back(item); + } + setItems(m_core, action, m_emptyIcon, items); + appendRow(items); + return indexFromItem(items.constFirst()); +} + +// Find the associated menus and toolbars, ignore toolbuttons +QWidgetList ActionModel::associatedWidgets(const QAction *action) +{ + const QObjectList rc = action->associatedObjects(); + QWidgetList result; + result.reserve(rc.size()); + for (QObject *obj : rc) { + if (QWidget *w = qobject_cast(obj)) { + if (qobject_cast(w) || qobject_cast(w)) + result.push_back(w); + } + } + return result; +} + +// shortcut is a fake property, need to retrieve it via property sheet. +PropertySheetKeySequenceValue ActionModel::actionShortCut(QDesignerFormEditorInterface *core, QAction *action) +{ + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), action); + if (!sheet) + return PropertySheetKeySequenceValue(); + return actionShortCut(sheet); +} + +PropertySheetKeySequenceValue ActionModel::actionShortCut(const QDesignerPropertySheetExtension *sheet) +{ + const int index = sheet->indexOf(u"shortcut"_s); + if (index == -1) + return PropertySheetKeySequenceValue(); + return qvariant_cast(sheet->property(index)); +} + +void ActionModel::setItems(QDesignerFormEditorInterface *core, QAction *action, + const QIcon &defaultIcon, + QStandardItemList &sl) +{ + + // Tooltip, mostly for icon view mode + QString firstTooltip = action->objectName(); + const QString text = action->text(); + if (!text.isEmpty()) + firstTooltip += u'\n' + text; + + Q_ASSERT(sl.size() == NumColumns); + + QStandardItem *item = sl[NameColumn]; + item->setText(action->objectName()); + QIcon icon = action->icon(); + if (icon.isNull()) + icon = defaultIcon; + item->setIcon(icon); + item->setToolTip(firstTooltip); + item->setWhatsThis(firstTooltip); + // Used + const QWidgetList associatedDesignerWidgets = associatedWidgets(action); + const bool used = !associatedDesignerWidgets.isEmpty(); + item = sl[UsedColumn]; + item->setCheckState(used ? Qt::Checked : Qt::Unchecked); + if (used) { + QString usedToolTip; + const auto separator = ", "_L1; + const int count = associatedDesignerWidgets.size(); + for (int i = 0; i < count; i++) { + if (i) + usedToolTip += separator; + usedToolTip += associatedDesignerWidgets.at(i)->objectName(); + } + item->setToolTip(usedToolTip); + } else { + item->setToolTip(QString()); + } + // text + item = sl[TextColumn]; + item->setText(action->text()); + item->setToolTip(action->text()); + // shortcut + const QString shortcut = actionShortCut(core, action).value().toString(QKeySequence::NativeText); + item = sl[ShortCutColumn]; + item->setText(shortcut); + item->setToolTip(shortcut); + // checkable + sl[CheckedColumn]->setCheckState(action->isCheckable() ? Qt::Checked : Qt::Unchecked); + // ToolTip. This might be multi-line, rich text + QString toolTip = action->toolTip(); + item = sl[ToolTipColumn]; + item->setToolTip(toolTip); + item->setText(toolTip.replace(u'\n', u' ')); + // menuRole + const auto menuRole = action->menuRole(); + item = sl[MenuRoleColumn]; + item->setText(QLatin1StringView(QMetaEnum::fromType().valueToKey(menuRole))); +} + +QMimeData *ActionModel::mimeData(const QModelIndexList &indexes ) const +{ + ActionRepositoryMimeData::ActionList actionList; + + QSet actions; + for (const QModelIndex &index : indexes) + if (QStandardItem *item = itemFromIndex(index)) + if (QAction *action = actionOfItem(item)) + actions.insert(action); + return new ActionRepositoryMimeData(actions.values(), Qt::CopyAction); +} + +// Resource images are plain text. The drag needs to be restricted, however. +QStringList ActionModel::mimeTypes() const +{ + return QStringList(plainTextMimeType); +} + +QString ActionModel::actionName(int row) const +{ + return item(row, NameColumn)->text(); +} + +bool ActionModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &) +{ + if (action != Qt::CopyAction) + return false; + + QStandardItem *droppedItem = item(row, column); + if (!droppedItem) + return false; + + + QtResourceView::ResourceType type; + QString path; + if (!QtResourceView::decodeMimeData(data, &type, &path) || type != QtResourceView::ResourceImage) + return false; + + emit resourceImageDropped(path, actionOfItem(droppedItem)); + return true; +} + +QAction *ActionModel::actionAt(const QModelIndex &index) const +{ + if (!index.isValid()) + return nullptr; + QStandardItem *i = itemFromIndex(index); + if (!i) + return nullptr; + return actionOfItem(i); +} + +QModelIndex ActionModel::indexOf(QAction *a) const +{ + for (int r = rowCount() - 1; r >= 0; --r) { + QStandardItem *stdItem = item(r, 0); + if (actionOfItem(stdItem) == a) + return indexFromItem(stdItem); + } + return {}; +} + +// helpers + +static bool handleImageDragEnterMoveEvent(QDropEvent *event) +{ + QtResourceView::ResourceType type; + const bool rc = QtResourceView::decodeMimeData(event->mimeData(), &type) && type == QtResourceView::ResourceImage; + if (rc) + event->acceptProposedAction(); + else + event->ignore(); + return rc; +} + +static void handleImageDropEvent(const QAbstractItemView *iv, QDropEvent *event, ActionModel *am) +{ + const QModelIndex index = iv->indexAt(event->position().toPoint()); + if (!index.isValid()) { + event->ignore(); + return; + } + + if (!handleImageDragEnterMoveEvent(event)) + return; + + am->dropMimeData(event->mimeData(), event->proposedAction(), index.row(), 0, iv->rootIndex()); +} + +// Basically mimic QAbstractItemView's startDrag routine, except that +// another pixmap is used, we don't want the whole row. + +void startActionDrag(QWidget *dragParent, ActionModel *model, const QModelIndexList &indexes, Qt::DropActions supportedActions) +{ + if (indexes.isEmpty()) + return; + + QDrag *drag = new QDrag(dragParent); + QMimeData *data = model->mimeData(indexes); + drag->setMimeData(data); + if (ActionRepositoryMimeData *actionMimeData = qobject_cast(data)) + drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(actionMimeData->actionList().constFirst())); + + drag->exec(supportedActions); +} + +// ---------------- ActionTreeView: +ActionTreeView::ActionTreeView(ActionModel *model, QWidget *parent) : + QTreeView(parent), + m_model(model) +{ + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + setDragDropMode(DragDrop); + setModel(model); + setRootIsDecorated(false); + setTextElideMode(Qt::ElideMiddle); + + setModel(model); + connect(this, &QTreeView::activated, this, &ActionTreeView::slotActivated); + connect(header(), &QHeaderView::sectionDoubleClicked, + this, &QTreeView::resizeColumnToContents); + + setIconSize(QSize(listModeIconSize, listModeIconSize)); + +} + +QAction *ActionTreeView::currentAction() const +{ + return m_model->actionAt(currentIndex()); +} + +void ActionTreeView::filter(const QString &text) +{ + const int rowCount = m_model->rowCount(); + const bool empty = text.isEmpty(); + const QModelIndex parent = rootIndex(); + for (int i = 0; i < rowCount; i++) + setRowHidden(i, parent, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive)); +} + +void ActionTreeView::dragEnterEvent(QDragEnterEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionTreeView::dragMoveEvent(QDragMoveEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionTreeView::dropEvent(QDropEvent *event) +{ + handleImageDropEvent(this, event, m_model); +} + +void ActionTreeView::focusInEvent(QFocusEvent *event) +{ + QTreeView::focusInEvent(event); + // Make property editor display current action + if (QAction *a = currentAction()) + emit currentActionChanged(a); +} + +void ActionTreeView::contextMenuEvent(QContextMenuEvent *event) +{ + emit actionContextMenuRequested(event, m_model->actionAt(indexAt(event->pos()))); +} + +void ActionTreeView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + emit currentActionChanged(m_model->actionAt(current)); + QTreeView::currentChanged(current, previous); +} + +void ActionTreeView::slotActivated(const QModelIndex &index) +{ + emit actionActivated(m_model->actionAt(index), index.column()); +} + +void ActionTreeView::startDrag(Qt::DropActions supportedActions) +{ + startActionDrag(this, m_model, selectedIndexes(), supportedActions); +} + +// ---------------- ActionListView: +ActionListView::ActionListView(ActionModel *model, QWidget *parent) : + QListView(parent), + m_model(model) +{ + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(true); + setDragDropMode(DragDrop); + setModel(model); + setTextElideMode(Qt::ElideMiddle); + connect(this, &QListView::activated, this, &ActionListView::slotActivated); + + // We actually want 'Static' as the user should be able to + // drag away actions only (not to rearrange icons). + // We emulate that by not accepting our own + // drag data. 'Static' causes the list view to disable drag and drop + // on the viewport. + setMovement(Snap); + setViewMode(IconMode); + setIconSize(QSize(iconModeIconSize, iconModeIconSize)); + setGridSize(QSize(4 * iconModeIconSize, 2 * iconModeIconSize)); + setSpacing(iconModeIconSize / 3); +} + +QAction *ActionListView::currentAction() const +{ + return m_model->actionAt(currentIndex()); +} + +void ActionListView::filter(const QString &text) +{ + const int rowCount = m_model->rowCount(); + const bool empty = text.isEmpty(); + for (int i = 0; i < rowCount; i++) + setRowHidden(i, !empty && !m_model->actionName(i).contains(text, Qt::CaseInsensitive)); +} + +void ActionListView::dragEnterEvent(QDragEnterEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionListView::dragMoveEvent(QDragMoveEvent *event) +{ + handleImageDragEnterMoveEvent(event); +} + +void ActionListView::dropEvent(QDropEvent *event) +{ + handleImageDropEvent(this, event, m_model); +} + +void ActionListView::focusInEvent(QFocusEvent *event) +{ + QListView::focusInEvent(event); + // Make property editor display current action + if (QAction *a = currentAction()) + emit currentActionChanged(a); +} + +void ActionListView::contextMenuEvent(QContextMenuEvent *event) +{ + emit actionContextMenuRequested(event, m_model->actionAt(indexAt(event->pos()))); +} + +void ActionListView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) +{ + emit currentActionChanged(m_model->actionAt(current)); + QListView::currentChanged(current, previous); +} + +void ActionListView::slotActivated(const QModelIndex &index) +{ + emit actionActivated(m_model->actionAt(index)); +} + +void ActionListView::startDrag(Qt::DropActions supportedActions) +{ + startActionDrag(this, m_model, selectedIndexes(), supportedActions); +} + +// ActionView +ActionView::ActionView(QWidget *parent) : + QStackedWidget(parent), + m_model(new ActionModel(this)), + m_actionTreeView(new ActionTreeView(m_model)), + m_actionListView(new ActionListView(m_model)) +{ + addWidget(m_actionListView); + addWidget(m_actionTreeView); + // Wire signals + connect(m_actionTreeView, &ActionTreeView::actionContextMenuRequested, + this, &ActionView::contextMenuRequested); + connect(m_actionListView, &ActionListView::actionContextMenuRequested, + this, &ActionView::contextMenuRequested); + + // make it possible for vs integration to reimplement edit action dialog + // [which it shouldn't do actually] + connect(m_actionListView, &ActionListView::actionActivated, + this, [this](QAction *a) { this->activated(a, -1); }); + connect(m_actionTreeView, &ActionTreeView::actionActivated, this, &ActionView::activated); + + connect(m_actionListView, &ActionListView::currentActionChanged, + this, &ActionView::slotCurrentChanged); + connect(m_actionTreeView, &ActionTreeView::currentActionChanged, + this, &ActionView::slotCurrentChanged); + + connect(m_model, &ActionModel::resourceImageDropped, + this, &ActionView::resourceImageDropped); + + // sync selection models + QItemSelectionModel *selectionModel = m_actionTreeView->selectionModel(); + m_actionListView->setSelectionModel(selectionModel); + connect(selectionModel, &QItemSelectionModel::selectionChanged, + this, &ActionView::selectionChanged); +} + +int ActionView::viewMode() const +{ + return currentWidget() == m_actionListView ? IconView : DetailedView; +} + +void ActionView::setViewMode(int lm) +{ + if (viewMode() == lm) + return; + + switch (lm) { + case IconView: + setCurrentWidget(m_actionListView); + break; + case DetailedView: + setCurrentWidget(m_actionTreeView); + break; + default: + break; + } +} + +void ActionView::slotCurrentChanged(QAction *action) +{ + // emit only for currently visible + if (sender() == currentWidget()) + emit currentChanged(action); +} + +void ActionView::filter(const QString &text) +{ + m_actionTreeView->filter(text); + m_actionListView->filter(text); +} + +void ActionView::selectAll() +{ + m_actionTreeView->selectAll(); +} + +void ActionView::clearSelection() +{ + m_actionTreeView->selectionModel()->clearSelection(); +} + +void ActionView::selectAction(QAction *a) +{ + const QModelIndex index = m_model->indexOf(a); + if (index.isValid()) + setCurrentIndex(index); +} + +void ActionView::setCurrentIndex(const QModelIndex &index) +{ + m_actionTreeView->setCurrentIndex(index); +} + +QAction *ActionView::currentAction() const +{ + return m_actionListView->currentAction(); +} + +void ActionView::setSelectionMode(QAbstractItemView::SelectionMode sm) +{ + m_actionTreeView->setSelectionMode(sm); + m_actionListView->setSelectionMode(sm); +} + +QAbstractItemView::SelectionMode ActionView::selectionMode() const +{ + return m_actionListView->selectionMode(); +} + +QItemSelection ActionView::selection() const +{ + return m_actionListView->selectionModel()->selection(); +} + +ActionView::ActionList ActionView::selectedActions() const +{ + ActionList rc; + const QModelIndexList &indexes = selection().indexes(); + for (const QModelIndex &index : indexes) { + if (index.column() == 0) + rc += actionOfItem(m_model->itemFromIndex(index)); + } + return rc; +} +// ---------- ActionRepositoryMimeData +ActionRepositoryMimeData::ActionRepositoryMimeData(QAction *a, Qt::DropAction dropAction) : + m_dropAction(dropAction) +{ + m_actionList += a; +} + +ActionRepositoryMimeData::ActionRepositoryMimeData(const ActionList &al, Qt::DropAction dropAction) : + m_dropAction(dropAction), + m_actionList(al) +{ +} + +QStringList ActionRepositoryMimeData::formats() const +{ + return QStringList(actionMimeType); +} + +QPixmap ActionRepositoryMimeData::actionDragPixmap(const QAction *action) +{ + + // Try to find a suitable pixmap. Grab either widget or icon. + const QIcon icon = action->icon(); + if (!icon.isNull()) + return icon.pixmap(QSize(22, 22)); + + const QObjectList associatedObjects = action->associatedObjects(); + for (QObject *o : associatedObjects) { + if (QToolButton *tb = qobject_cast(o)) + return tb->grab(QRect(0, 0, -1, -1)); + } + + // Create a QToolButton + QToolButton *tb = new QToolButton; + tb->setText(action->text()); + tb->setToolButtonStyle(Qt::ToolButtonTextOnly); + tb->adjustSize(); + const QPixmap rc = tb->grab(QRect(0, 0, -1, -1)); + tb->deleteLater(); + return rc; +} + +void ActionRepositoryMimeData::accept(QDragMoveEvent *event) const +{ + if (event->proposedAction() == m_dropAction) { + event->acceptProposedAction(); + } else { + event->setDropAction(m_dropAction); + event->accept(); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/actionrepository_p.h b/src/tools/designer/src/lib/shared/actionrepository_p.h new file mode 100644 index 00000000000..bc31ccd555a --- /dev/null +++ b/src/tools/designer/src/lib/shared/actionrepository_p.h @@ -0,0 +1,233 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ACTIONREPOSITORY_H +#define ACTIONREPOSITORY_H + +#include "shared_global_p.h" +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QPixmap; + +class QDesignerFormEditorInterface; +class QDesignerPropertySheetExtension; + +namespace qdesigner_internal { + +class PropertySheetKeySequenceValue; + +// Shared model of actions, to be used for several views (detailed/icon view). +class QDESIGNER_SHARED_EXPORT ActionModel: public QStandardItemModel +{ + Q_OBJECT +public: + enum Columns { NameColumn, UsedColumn, TextColumn, ShortCutColumn, CheckedColumn, ToolTipColumn, MenuRoleColumn, NumColumns }; + enum { ActionRole = Qt::UserRole + 1000 }; + + explicit ActionModel(QWidget *parent = nullptr); + void initialize(QDesignerFormEditorInterface *core) { m_core = core; } + + void clearActions(); + QModelIndex addAction(QAction *a); + // remove row + void remove(int row); + // update the row from the underlying action + void update(int row); + + // return row of action or -1. + int findAction(QAction *) const; + + QString actionName(int row) const; + QAction *actionAt(const QModelIndex &index) const; + QModelIndex indexOf(QAction *a) const; + + QMimeData *mimeData(const QModelIndexList &indexes) const override; + QStringList mimeTypes() const override; + bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override; + + // Find the associated menus and toolbars, ignore toolbuttons + static QWidgetList associatedWidgets(const QAction *action); + + // Retrieve shortcut via property sheet as it is a fake property + static PropertySheetKeySequenceValue actionShortCut(QDesignerFormEditorInterface *core, QAction *action); + static PropertySheetKeySequenceValue actionShortCut(const QDesignerPropertySheetExtension *ps); + +signals: + void resourceImageDropped(const QString &path, QAction *action); + +private: + using QStandardItemList = QList; + + void initializeHeaders(); + static void setItems(QDesignerFormEditorInterface *core, QAction *a, + const QIcon &defaultIcon, + QStandardItemList &sl); + + const QIcon m_emptyIcon; + + QDesignerFormEditorInterface *m_core = nullptr; +}; + +// Internal class that provides the detailed view of actions. +class ActionTreeView: public QTreeView +{ + Q_OBJECT +public: + explicit ActionTreeView(ActionModel *model, QWidget *parent = nullptr); + QAction *currentAction() const; + +public slots: + void filter(const QString &text); + +signals: + void actionContextMenuRequested(QContextMenuEvent *event, QAction *); + void currentActionChanged(QAction *action); + void actionActivated(QAction *action, int column); + +protected slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; + +protected: + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void focusInEvent(QFocusEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + void startDrag(Qt::DropActions supportedActions) override; + +private slots: + void slotActivated(const QModelIndex &); + +private: + ActionModel *m_model; +}; + +// Internal class that provides the icon view of actions. +class ActionListView: public QListView +{ + Q_OBJECT +public: + explicit ActionListView(ActionModel *model, QWidget *parent = nullptr); + QAction *currentAction() const; + +public slots: + void filter(const QString &text); + +signals: + void actionContextMenuRequested(QContextMenuEvent *event, QAction *); + void currentActionChanged(QAction *action); + void actionActivated(QAction *action); + +protected slots: + void currentChanged(const QModelIndex ¤t, const QModelIndex &previous) override; + +protected: + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void focusInEvent(QFocusEvent *event) override; + void contextMenuEvent(QContextMenuEvent *event) override; + void startDrag(Qt::DropActions supportedActions) override; + +private slots: + void slotActivated(const QModelIndex &); + +private: + ActionModel *m_model; +}; + +// Action View that can be switched between detailed and icon view +// using a QStackedWidget of ActionListView / ActionTreeView +// that share the item model and the selection model. + +class ActionView : public QStackedWidget { + Q_OBJECT +public: + // Separate initialize() function takes core argument to make this + // thing usable as promoted widget. + explicit ActionView(QWidget *parent = nullptr); + void initialize(QDesignerFormEditorInterface *core) { m_model->initialize(core); } + + // View mode + enum { DetailedView, IconView }; + int viewMode() const; + void setViewMode(int lm); + + void setSelectionMode(QAbstractItemView::SelectionMode sm); + QAbstractItemView::SelectionMode selectionMode() const; + + ActionModel *model() const { return m_model; } + + QAction *currentAction() const; + void setCurrentIndex(const QModelIndex &index); + + using ActionList = QList; + ActionList selectedActions() const; + QItemSelection selection() const; + +public slots: + void filter(const QString &text); + void selectAll(); + void clearSelection(); + void selectAction(QAction *a); + +signals: + void contextMenuRequested(QContextMenuEvent *event, QAction *); + void currentChanged(QAction *action); + void activated(QAction *action, int column); + void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + void resourceImageDropped(const QString &data, QAction *action); + +private slots: + void slotCurrentChanged(QAction *action); + +private: + ActionModel *m_model; + ActionTreeView *m_actionTreeView; + ActionListView *m_actionListView; +}; + +class QDESIGNER_SHARED_EXPORT ActionRepositoryMimeData: public QMimeData +{ + Q_OBJECT +public: + using ActionList = QList; + + ActionRepositoryMimeData(const ActionList &, Qt::DropAction dropAction); + ActionRepositoryMimeData(QAction *, Qt::DropAction dropAction); + + const ActionList &actionList() const { return m_actionList; } + QStringList formats() const override; + + static QPixmap actionDragPixmap(const QAction *action); + + // Utility to accept with right action + void accept(QDragMoveEvent *event) const; +private: + const Qt::DropAction m_dropAction; + ActionList m_actionList; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ACTIONREPOSITORY_H diff --git a/src/tools/designer/src/lib/shared/addlinkdialog.ui b/src/tools/designer/src/lib/shared/addlinkdialog.ui new file mode 100644 index 00000000000..3171159f97d --- /dev/null +++ b/src/tools/designer/src/lib/shared/addlinkdialog.ui @@ -0,0 +1,112 @@ + + AddLinkDialog + + + Insert Link + + + false + + + true + + + + + + + + Title: + + + + + + + + 337 + 0 + + + + + + + + URL: + + + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + AddLinkDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + AddLinkDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/tools/designer/src/lib/shared/codedialog.cpp b/src/tools/designer/src/lib/shared/codedialog.cpp new file mode 100644 index 00000000000..3f257524b1d --- /dev/null +++ b/src/tools/designer/src/lib/shared/codedialog.cpp @@ -0,0 +1,264 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "codedialog_p.h" +#include "qdesigner_utils_p.h" +#include "iconloader_p.h" + +#include + +#include +#if QT_CONFIG(clipboard) +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +// ----------------- CodeDialogPrivate +struct CodeDialog::CodeDialogPrivate { + CodeDialogPrivate(); + + QTextEdit *m_textEdit; + TextEditFindWidget *m_findWidget; + QString m_formFileName; + QString m_mimeType; +}; + +CodeDialog::CodeDialogPrivate::CodeDialogPrivate() + : m_textEdit(new QTextEdit) + , m_findWidget(new TextEditFindWidget) +{ +} + +// ----------------- CodeDialog +CodeDialog::CodeDialog(QWidget *parent) : + QDialog(parent), + m_impl(new CodeDialogPrivate) +{ + QVBoxLayout *vBoxLayout = new QVBoxLayout; + + // Edit tool bar + QToolBar *toolBar = new QToolBar; + + const QIcon saveIcon = createIconSet(QIcon::ThemeIcon::DocumentSave, + "filesave.png"_L1); + QAction *saveAction = toolBar->addAction(saveIcon, tr("Save...")); + connect(saveAction, &QAction::triggered, this, &CodeDialog::slotSaveAs); + +#if QT_CONFIG(clipboard) + const QIcon copyIcon = createIconSet(QIcon::ThemeIcon::EditCopy, + "editcopy.png"_L1); + QAction *copyAction = toolBar->addAction(copyIcon, tr("Copy All")); + connect(copyAction, &QAction::triggered, this, &CodeDialog::copyAll); +#endif + + toolBar->addAction(m_impl->m_findWidget->createFindAction(toolBar)); + + vBoxLayout->addWidget(toolBar); + + // Edit + m_impl->m_textEdit->setReadOnly(true); + const auto font = QFontDatabase::systemFont(QFontDatabase::SystemFont::FixedFont); + const int editorWidth = QFontMetrics(font, this).averageCharWidth() * 100; + m_impl->m_textEdit->setFont(font); + m_impl->m_textEdit->setMinimumSize(QSize( + qMax(editorWidth, m_impl->m_findWidget->minimumSize().width()), + 500)); + vBoxLayout->addWidget(m_impl->m_textEdit); + + // Find + m_impl->m_findWidget->setTextEdit(m_impl->m_textEdit); + vBoxLayout->addWidget(m_impl->m_findWidget); + + // Button box + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + // Disable auto default + QPushButton *closeButton = buttonBox->button(QDialogButtonBox::Close); + closeButton->setAutoDefault(false); + vBoxLayout->addWidget(buttonBox); + + setLayout(vBoxLayout); +} + +CodeDialog::~CodeDialog() +{ + delete m_impl; +} + +void CodeDialog::setCode(const QString &code) +{ + m_impl->m_textEdit->setPlainText(code); +} + +QString CodeDialog::code() const +{ + return m_impl->m_textEdit->toPlainText(); +} + +void CodeDialog::setFormFileName(const QString &f) +{ + m_impl->m_formFileName = f; +} + +QString CodeDialog::formFileName() const +{ + return m_impl->m_formFileName; +} + +void CodeDialog::setMimeType(const QString &m) +{ + m_impl->m_mimeType = m; +} + +bool CodeDialog::generateCode(const QDesignerFormWindowInterface *fw, + UicLanguage language, + QString *code, + QString *errorMessage) +{ + // Generate temporary file name similar to form file name + // (for header guards) + QString tempPattern = QDir::tempPath(); + if (!tempPattern.endsWith(QDir::separator())) // platform-dependant + tempPattern += QDir::separator(); + const QString fileName = fw->fileName(); + if (fileName.isEmpty()) { + tempPattern += "designer"_L1; + } else { + tempPattern += QFileInfo(fileName).baseName(); + } + tempPattern += "XXXXXX.ui"_L1; + // Write to temp file + QTemporaryFile tempFormFile(tempPattern); + + tempFormFile.setAutoRemove(true); + if (!tempFormFile.open()) { + *errorMessage = tr("A temporary form file could not be created in %1.").arg(QDir::tempPath()); + return false; + } + const QString tempFormFileName = tempFormFile.fileName(); + tempFormFile.write(fw->contents().toUtf8()); + if (!tempFormFile.flush()) { + *errorMessage = tr("The temporary form file %1 could not be written.").arg(tempFormFileName); + return false; + } + tempFormFile.close(); + // Run uic + QByteArray rc; + if (!runUIC(tempFormFileName, language, rc, *errorMessage)) + return false; + *code = QString::fromUtf8(rc); + return true; +} + +bool CodeDialog::showCodeDialog(const QDesignerFormWindowInterface *fw, + UicLanguage language, + QWidget *parent, + QString *errorMessage) +{ + QString code; + if (!generateCode(fw, language, &code, errorMessage)) + return false; + + auto dialog = new CodeDialog(parent); + dialog->setModal(false); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->setCode(code); + dialog->setFormFileName(fw->fileName()); + QLatin1StringView languageName; + switch (language) { + case UicLanguage::Cpp: + languageName = "C++"_L1; + dialog->setMimeType(u"text/x-chdr"_s); + break; + case UicLanguage::Python: + languageName = "Python"_L1; + dialog->setMimeType(u"text/x-python"_s); + break; + } + dialog->setWindowTitle(tr("%1 - [%2 Code]"). + arg(fw->mainContainer()->windowTitle(), languageName)); + dialog->show(); + return true; +} + +void CodeDialog::slotSaveAs() +{ + // build the default relative name 'ui_sth.h' + QMimeDatabase mimeDb; + const QString suffix = mimeDb.mimeTypeForName(m_impl->m_mimeType).preferredSuffix(); + + // file dialog + QFileDialog fileDialog(this, tr("Save Code")); + fileDialog.setMimeTypeFilters(QStringList(m_impl->m_mimeType)); + fileDialog.setAcceptMode(QFileDialog::AcceptSave); + fileDialog.setDefaultSuffix(suffix); + const QString uiFile = formFileName(); + if (!uiFile.isEmpty()) { + QFileInfo uiFi(uiFile); + fileDialog.setDirectory(uiFi.absolutePath()); + fileDialog.selectFile("ui_"_L1 + uiFi.baseName() + + '.'_L1 + suffix); + } + + while (true) { + if (fileDialog.exec() != QDialog::Accepted) + break; + const QString fileName = fileDialog.selectedFiles().constFirst(); + + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly|QIODevice::Text)) { + warning(tr("The file %1 could not be opened: %2") + .arg(fileName, file.errorString())); + continue; + } + file.write(code().toUtf8()); + if (!file.flush()) { + warning(tr("The file %1 could not be written: %2") + .arg(fileName, file.errorString())); + continue; + } + file.close(); + break; + } +} + +void CodeDialog::warning(const QString &msg) +{ + QMessageBox::warning( + this, tr("%1 - Error").arg(windowTitle()), + msg, QMessageBox::Close); +} + +#if QT_CONFIG(clipboard) +void CodeDialog::copyAll() +{ + QApplication::clipboard()->setText(code()); +} +#endif + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/codedialog_p.h b/src/tools/designer/src/lib/shared/codedialog_p.h new file mode 100644 index 00000000000..e68f48a2465 --- /dev/null +++ b/src/tools/designer/src/lib/shared/codedialog_p.h @@ -0,0 +1,70 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef CODEPREVIEWDIALOG_H +#define CODEPREVIEWDIALOG_H + +#include "shared_global_p.h" +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + +enum class UicLanguage; + +// Dialog for viewing code. +class QDESIGNER_SHARED_EXPORT CodeDialog : public QDialog +{ + Q_OBJECT + explicit CodeDialog(QWidget *parent = nullptr); +public: + ~CodeDialog() override; + + static bool generateCode(const QDesignerFormWindowInterface *fw, + UicLanguage language, + QString *code, + QString *errorMessage); + + static bool showCodeDialog(const QDesignerFormWindowInterface *fw, + UicLanguage language, + QWidget *parent, + QString *errorMessage); + +private slots: + void slotSaveAs(); +#if QT_CONFIG(clipboard) + void copyAll(); +#endif + +private: + void setCode(const QString &code); + QString code() const; + void setFormFileName(const QString &f); + QString formFileName() const; + void setMimeType(const QString &m); + + void warning(const QString &msg); + + struct CodeDialogPrivate; + CodeDialogPrivate *m_impl; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // CODEPREVIEWDIALOG_H diff --git a/src/tools/designer/src/lib/shared/connectionedit.cpp b/src/tools/designer/src/lib/shared/connectionedit.cpp new file mode 100644 index 00000000000..d92ca00e234 --- /dev/null +++ b/src/tools/designer/src/lib/shared/connectionedit.cpp @@ -0,0 +1,1572 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "connectionedit_p.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +static const int BG_ALPHA = 32; +static const int LINE_PROXIMITY_RADIUS = 3; +static const int LOOP_MARGIN = 20; +static const int VLABEL_MARGIN = 1; +static const int HLABEL_MARGIN = 3; +static const int GROUND_W = 20; +static const int GROUND_H = 25; + +/******************************************************************************* +** Tools +*/ + +static QRect fixRect(const QRect &r) +{ + return QRect(r.x(), r.y(), r.width() - 1, r.height() - 1); +} + +static QRect expand(const QRect &r, int i) +{ + return QRect(r.x() - i, r.y() - i, r.width() + 2*i, r.height() + 2*i); +} + +static QRect endPointRectHelper(const QPoint &pos) +{ + const QRect r(pos + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS), + QSize(2*LINE_PROXIMITY_RADIUS, 2*LINE_PROXIMITY_RADIUS)); + return r; +} + +static void paintGround(QPainter *p, QRect r) +{ + const QPoint mid = r.center(); + p->drawLine(mid.x(), r.top(), mid.x(), mid.y()); + p->drawLine(r.left(), mid.y(), r.right(), mid.y()); + int y = r.top() + 4*r.height()/6; + int x = GROUND_W/6; + p->drawLine(r.left() + x, y, r.right() - x, y); + y = r.top() + 5*r.height()/6; + x = 2*GROUND_W/6; + p->drawLine(r.left() + x, y, r.right() - x, y); + p->drawLine(mid.x(), r.bottom(), mid.x() + 1, r.bottom()); +} + +static void paintEndPoint(QPainter *p, const QPoint &pos) +{ + const QRect r(pos + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS), + QSize(2*LINE_PROXIMITY_RADIUS, 2*LINE_PROXIMITY_RADIUS)); + p->fillRect(fixRect(r), p->pen().color()); +} + +static qdesigner_internal::CETypes::LineDir classifyLine(const QPoint &p1, const QPoint &p2) +{ + if (p1.x() == p2.x()) + return p1.y() < p2.y() ? qdesigner_internal::CETypes::DownDir : qdesigner_internal::CETypes::UpDir; + Q_ASSERT(p1.y() == p2.y()); + return p1.x() < p2.x() ? qdesigner_internal::CETypes::RightDir : qdesigner_internal::CETypes::LeftDir; +} + +static QPoint pointInsideRect(const QRect &r, QPoint p) +{ + if (p.x() < r.left()) + p.setX(r.left()); + else if (p.x() > r.right()) + p.setX(r.right()); + + if (p.y() < r.top()) + p.setY(r.top()); + else if (p.y() > r.bottom()) + p.setY(r.bottom()); + + return p; +} + +namespace qdesigner_internal { + +/******************************************************************************* +** Commands +*/ + +AddConnectionCommand::AddConnectionCommand(ConnectionEdit *edit, Connection *con) + : CECommand(edit), m_con(con) +{ + setText(QApplication::translate("Command", "Add connection")); +} + +void AddConnectionCommand::redo() +{ + edit()->selectNone(); + emit edit()->aboutToAddConnection(edit()->m_con_list.size()); + edit()->m_con_list.append(m_con); + m_con->inserted(); + emit edit()->connectionAdded(m_con); + edit()->setSelected(m_con, true); +} + +void AddConnectionCommand::undo() +{ + const int idx = edit()->indexOfConnection(m_con); + emit edit()->aboutToRemoveConnection(m_con); + edit()->setSelected(m_con, false); + m_con->update(); + m_con->removed(); + edit()->m_con_list.removeAll(m_con); + emit edit()->connectionRemoved(idx); +} + +class AdjustConnectionCommand : public CECommand +{ +public: + AdjustConnectionCommand(ConnectionEdit *edit, Connection *con, + const QPoint &old_source_pos, + const QPoint &old_target_pos, + const QPoint &new_source_pos, + const QPoint &new_target_pos); + void redo() override; + void undo() override; +private: + Connection *m_con; + const QPoint m_old_source_pos; + const QPoint m_old_target_pos; + const QPoint m_new_source_pos; + const QPoint m_new_target_pos; +}; + +AdjustConnectionCommand::AdjustConnectionCommand(ConnectionEdit *edit, Connection *con, + const QPoint &old_source_pos, + const QPoint &old_target_pos, + const QPoint &new_source_pos, + const QPoint &new_target_pos) : + CECommand(edit), + m_con(con), + m_old_source_pos(old_source_pos), + m_old_target_pos(old_target_pos), + m_new_source_pos(new_source_pos), + m_new_target_pos(new_target_pos) +{ + setText(QApplication::translate("Command", "Adjust connection")); +} + +void AdjustConnectionCommand::undo() +{ + m_con->setEndPoint(EndPoint::Source, m_con->widget(EndPoint::Source), m_old_source_pos); + m_con->setEndPoint(EndPoint::Target, m_con->widget(EndPoint::Target), m_old_target_pos); +} + +void AdjustConnectionCommand::redo() +{ + m_con->setEndPoint(EndPoint::Source, m_con->widget(EndPoint::Source), m_new_source_pos); + m_con->setEndPoint(EndPoint::Target, m_con->widget(EndPoint::Target), m_new_target_pos); +} + +DeleteConnectionsCommand::DeleteConnectionsCommand(ConnectionEdit *edit, + const ConnectionList &con_list) + : CECommand(edit), m_con_list(con_list) +{ + setText(QApplication::translate("Command", "Delete connections")); +} + +void DeleteConnectionsCommand::redo() +{ + for (Connection *con : std::as_const(m_con_list)) { + const int idx = edit()->indexOfConnection(con); + emit edit()->aboutToRemoveConnection(con); + Q_ASSERT(edit()->m_con_list.contains(con)); + edit()->setSelected(con, false); + con->update(); + con->removed(); + edit()->m_con_list.removeAll(con); + emit edit()->connectionRemoved(idx); + } +} + +void DeleteConnectionsCommand::undo() +{ + for (Connection *con : std::as_const(m_con_list)) { + Q_ASSERT(!edit()->m_con_list.contains(con)); + emit edit()->aboutToAddConnection(edit()->m_con_list.size()); + edit()->m_con_list.append(con); + edit()->selectNone(); + con->update(); + con->inserted(); + emit edit()->connectionAdded(con); + edit()->setSelected(con, true); + } +} + +class SetEndPointCommand : public CECommand +{ +public: + SetEndPointCommand(ConnectionEdit *edit, Connection *con, EndPoint::Type type, QObject *object); + void redo() override; + void undo() override; +private: + Connection *m_con; + const EndPoint::Type m_type; + QObject *m_old_widget, *m_new_widget; + const QPoint m_old_pos; + QPoint m_new_pos; +}; + +SetEndPointCommand::SetEndPointCommand(ConnectionEdit *edit, Connection *con, + EndPoint::Type type, QObject *object) : + CECommand(edit), + m_con(con), + m_type(type), + m_old_widget(con->object(type)), + m_new_widget(object), + m_old_pos(con->endPointPos(type)) +{ + if (QWidget *widget = qobject_cast(object)) { + m_new_pos = edit->widgetRect(widget).center(); + } + + if (m_type == EndPoint::Source) + setText(QApplication::translate("Command", "Change source")); + else + setText(QApplication::translate("Command", "Change target")); +} + +void SetEndPointCommand::redo() +{ + m_con->setEndPoint(m_type, m_new_widget, m_new_pos); + emit edit()->connectionChanged(m_con); +} + +void SetEndPointCommand::undo() +{ + m_con->setEndPoint(m_type, m_old_widget, m_old_pos); + emit edit()->connectionChanged(m_con); +} + +/******************************************************************************* +** Connection +*/ + +Connection::Connection(ConnectionEdit *edit) : + m_source_pos(QPoint(-1, -1)), + m_target_pos(QPoint(-1, -1)), + m_source(nullptr), + m_target(nullptr), + m_edit(edit), + m_visible(true) +{ + +} + +Connection::Connection(ConnectionEdit *edit, QObject *source, QObject *target) : + m_source_pos(QPoint(-1, -1)), + m_target_pos(QPoint(-1, -1)), + m_source(source), + m_target(target), + m_edit(edit), + m_visible(true) +{ +} + +void Connection::setVisible(bool b) +{ + m_visible = b; +} + +void Connection::updateVisibility() +{ + QWidget *source = widget(EndPoint::Source); + QWidget *target = widget(EndPoint::Target); + + if (source == nullptr || target == nullptr) { + setVisible(false); + return; + } + + QWidget *w = source; + while (w && w->parentWidget()) { + if (!w->isVisibleTo(w->parentWidget())) { + setVisible(false); + return; + } + w = w->parentWidget(); + } + + w = target; + while (w && w->parentWidget()) { + if (!w->isVisibleTo(w->parentWidget())) { + setVisible(false); + return; + } + w = w->parentWidget(); + } + + setVisible(true); +} + +bool Connection::isVisible() const +{ + return m_visible; +} + +bool Connection::ground() const +{ + return m_target != nullptr && m_target == m_edit->m_bg_widget; +} + +QPoint Connection::endPointPos(EndPoint::Type type) const +{ + return type == EndPoint::Source ? m_source_pos : m_target_pos; +} + +static QPoint lineEntryPos(const QPoint &p1, const QPoint &p2, const QRect &rect) +{ + QPoint result; + + switch (classifyLine(p1, p2)) { + case CETypes::UpDir: + result = QPoint(p1.x(), rect.bottom()); + break; + case CETypes::DownDir: + result = QPoint(p1.x(), rect.top()); + break; + case CETypes::LeftDir: + result = QPoint(rect.right(), p1.y()); + break; + case CETypes::RightDir: + result = QPoint(rect.left(), p1.y()); + break; + } + + return result; +} + +static QPolygonF arrowHead(const QPoint &p1, const QPoint &p2) +{ + QPolygonF result; + + switch (classifyLine(p1, p2)) { + case CETypes::UpDir: + result.append(p2 + QPoint(0, 1)); + result.append(p2 + QPoint(LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1)); + result.append(p2 + QPoint(-LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS*2 + 1)); + break; + case CETypes::DownDir: + result.append(p2); + result.append(p2 + QPoint(LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2)); + result.append(p2 + QPoint(-LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS*2)); + break; + case CETypes::LeftDir: + result.append(p2 + QPoint(1, 0)); + result.append(p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, -LINE_PROXIMITY_RADIUS)); + result.append(p2 + QPoint(2*LINE_PROXIMITY_RADIUS + 1, LINE_PROXIMITY_RADIUS)); + break; + case CETypes::RightDir: + result.append(p2); + result.append(p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, -LINE_PROXIMITY_RADIUS)); + result.append(p2 + QPoint(-2*LINE_PROXIMITY_RADIUS, LINE_PROXIMITY_RADIUS)); + break; + } + + return result; +} + +static CETypes::LineDir closestEdge(const QPoint &p, const QRect &r) +{ + CETypes::LineDir result = CETypes::UpDir; + int min = p.y() - r.top(); + + int d = p.x() - r.left(); + if (d < min) { + min = d; + result = CETypes::LeftDir; + } + + d = r.bottom() - p.y(); + if (d < min) { + min = d; + result = CETypes::DownDir; + } + + d = r.right() - p.x(); + if (d < min) { + min = d; + result = CETypes::RightDir; + } + + return result; +} + +static bool pointAboveLine(const QPoint &l1, const QPoint &l2, const QPoint &p) +{ + if (l1.x() == l2.x()) + return p.x() >= l1.x(); + return p.y() <= l1.y() + (p.x() - l1.x())*(l2.y() - l1.y())/(l2.x() - l1.x()); +} + +void Connection::updateKneeList() +{ + const LineDir old_source_label_dir = labelDir(EndPoint::Source); + const LineDir old_target_label_dir = labelDir(EndPoint::Target); + + QPoint s = endPointPos(EndPoint::Source); + QPoint t = endPointPos(EndPoint::Target); + const QRect sr = m_source_rect; + const QRect tr = m_target_rect; + + m_knee_list.clear(); + m_arrow_head.clear(); + + if (m_source == nullptr || s == QPoint(-1, -1) || t == QPoint(-1, -1)) + return; + + const QRect r = sr | tr; + + m_knee_list.append(s); + if (m_target == nullptr) { + m_knee_list.append(QPoint(t.x(), s.y())); + } else if (m_target == m_edit->m_bg_widget) { + m_knee_list.append(QPoint(s.x(), t.y())); + } else if (tr.contains(sr) || sr.contains(tr)) { +/* + +------------------+ + | +----------+ | + | | | | + | | o | | + | +---|------+ | + | | x | + +-----|-----|------+ + +-----+ + + We find out which edge of the outer rectangle is closest to the target + point, and make a loop which exits and re-enters through that edge. +*/ + const LineDir dir = closestEdge(t, tr); + switch (dir) { + case UpDir: + m_knee_list.append(QPoint(s.x(), r.top() - LOOP_MARGIN)); + m_knee_list.append(QPoint(t.x(), r.top() - LOOP_MARGIN)); + break; + case DownDir: + m_knee_list.append(QPoint(s.x(), r.bottom() + LOOP_MARGIN)); + m_knee_list.append(QPoint(t.x(), r.bottom() + LOOP_MARGIN)); + break; + case LeftDir: + m_knee_list.append(QPoint(r.left() - LOOP_MARGIN, s.y())); + m_knee_list.append(QPoint(r.left() - LOOP_MARGIN, t.y())); + break; + case RightDir: + m_knee_list.append(QPoint(r.right() + LOOP_MARGIN, s.y())); + m_knee_list.append(QPoint(r.right() + LOOP_MARGIN, t.y())); + break; + } + } else { + if (r.height() < sr.height() + tr.height()) { + if ((s.y() >= tr.top() && s.y() <= tr.bottom()) || (t.y() >= sr.bottom() || t.y() <= sr.top())) { +/* + +--------+ + | | +--------+ + | o--+---+--x | + | o | | | + +-----|--+ | | + +------+--x | + +--------+ + + When dragging one end point, move the other end point to the same y position, + if that does not cause it to exit it's rectangle. +*/ + if (m_edit->state() == ConnectionEdit::Dragging) { + if (m_edit->m_drag_end_point.type == EndPoint::Source) { + const QPoint p(t.x(), s.y()); + m_knee_list.append(p); + if (tr.contains(p)) + t = m_target_pos = p; + } else { + const QPoint p(s.x(), t.y()); + m_knee_list.append(p); + if (sr.contains(p)) + s = m_source_pos = p; + } + } else { + m_knee_list.append(QPoint(s.x(), t.y())); + } + } else { +/* + +--------+ + | o----+-------+ + | | +---|----+ + +--------+ | | | + | x | + +--------+ +*/ + m_knee_list.append(QPoint(t.x(), s.y())); + } + } else if (r.width() < sr.width() + tr.width()) { + if ((s.x() >= tr.left() && s.x() <= tr.right()) || t.x() >= sr.right() || t.x() <= sr.left()) { +/* + +--------+ + | | + | o o+--+ + +----|---+ | + +-|------|-+ + | x x | + | | + +----------+ + + When dragging one end point, move the other end point to the same x position, + if that does not cause it to exit it's rectangle. +*/ + if (m_edit->state() == ConnectionEdit::Dragging) { + if (m_edit->m_drag_end_point.type == EndPoint::Source) { + const QPoint p(s.x(), t.y()); + m_knee_list.append(p); + if (tr.contains(p)) + t = m_target_pos = p; + } else { + const QPoint p(t.x(), s.y()); + m_knee_list.append(p); + if (sr.contains(p)) + s = m_source_pos = p; + } + } else { + m_knee_list.append(QPoint(t.x(), s.y())); + } + } else { +/* + +--------+ + | | + | o | + +--|-----+ + | +--------+ + +---+-x | + | | + +--------+ + +*/ + m_knee_list.append(QPoint(s.x(), t.y())); + } + } else { +/* + +--------+ + | | + | o o-+--------+ + +--|-----+ | + | +-----|--+ + | | x | + +--------+-x | + +--------+ + + The line enters the target rectangle through the closest edge. +*/ + if (sr.topLeft() == r.topLeft()) { + if (pointAboveLine(tr.topLeft(), tr.bottomRight(), t)) + m_knee_list.append(QPoint(t.x(), s.y())); + else + m_knee_list.append(QPoint(s.x(), t.y())); + } else if (sr.topRight() == r.topRight()) { + if (pointAboveLine(tr.bottomLeft(), tr.topRight(), t)) + m_knee_list.append(QPoint(t.x(), s.y())); + else + m_knee_list.append(QPoint(s.x(), t.y())); + } else if (sr.bottomRight() == r.bottomRight()) { + if (pointAboveLine(tr.topLeft(), tr.bottomRight(), t)) + m_knee_list.append(QPoint(s.x(), t.y())); + else + m_knee_list.append(QPoint(t.x(), s.y())); + } else { + if (pointAboveLine(tr.bottomLeft(), tr.topRight(), t)) + m_knee_list.append(QPoint(s.x(), t.y())); + else + m_knee_list.append(QPoint(t.x(), s.y())); + } + } + } + m_knee_list.append(t); + + if (m_knee_list.size() == 2) + m_knee_list.clear(); + + trimLine(); + + const LineDir new_source_label_dir = labelDir(EndPoint::Source); + const LineDir new_target_label_dir = labelDir(EndPoint::Target); + if (new_source_label_dir != old_source_label_dir) + updatePixmap(EndPoint::Source); + if (new_target_label_dir != old_target_label_dir) + updatePixmap(EndPoint::Target); +} + +void Connection::trimLine() +{ + if (m_source == nullptr || m_source_pos == QPoint(-1, -1) || m_target_pos == QPoint(-1, -1)) + return; + auto cnt = m_knee_list.size(); + if (cnt < 2) + return; + + const QRect sr = m_source_rect; + const QRect tr = m_target_rect; + + if (sr.contains(m_knee_list.at(1))) + m_knee_list.removeFirst(); + + cnt = m_knee_list.size(); + if (cnt < 2) + return; + + if (!tr.contains(sr) && tr.contains(m_knee_list.at(cnt - 2))) + m_knee_list.removeLast(); + + cnt = m_knee_list.size(); + if (cnt < 2) + return; + + if (sr.contains(m_knee_list.at(0)) && !sr.contains(m_knee_list.at(1))) + m_knee_list[0] = lineEntryPos(m_knee_list.at(1), m_knee_list.at(0), sr); + + if (tr.contains(m_knee_list.at(cnt - 1)) && !tr.contains(m_knee_list.at(cnt - 2))) { + m_knee_list[cnt - 1] + = lineEntryPos(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1), tr); + m_arrow_head = arrowHead(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1)); + } +} + +void Connection::setSource(QObject *source, const QPoint &pos) +{ + if (source == m_source && m_source_pos == pos) + return; + + update(false); + + m_source = source; + if (QWidget *widget = qobject_cast(source)) { + m_source_pos = pos; + m_source_rect = m_edit->widgetRect(widget); + updateKneeList(); + } + + update(false); +} + +void Connection::setTarget(QObject *target, const QPoint &pos) +{ + if (target == m_target && m_target_pos == pos) + return; + + update(false); + + m_target = target; + if (QWidget *widget = qobject_cast(target)) { + m_target_pos = pos; + m_target_rect = m_edit->widgetRect(widget); + updateKneeList(); + } + + update(false); +} + +static QRect lineRect(const QPoint &a, const QPoint &b) +{ + const QPoint c(qMin(a.x(), b.x()), qMin(a.y(), b.y())); + const QPoint d(qMax(a.x(), b.x()), qMax(a.y(), b.y())); + + QRect result(c, d); + return expand(result, LINE_PROXIMITY_RADIUS); +} + +QRect Connection::groundRect() const +{ + if (!ground()) + return QRect(); + if (m_knee_list.isEmpty()) + return QRect(); + + const QPoint p = m_knee_list.last(); + return QRect(p.x() - GROUND_W/2, p.y(), GROUND_W, GROUND_H); +} + +QRegion Connection::region() const +{ + QRegion result; + + for (qsizetype i = 0; i < m_knee_list.size() - 1; ++i) + result = result.united(lineRect(m_knee_list.at(i), m_knee_list.at(i + 1))); + + if (!m_arrow_head.isEmpty()) { + QRect r = m_arrow_head.boundingRect().toRect(); + r = expand(r, 1); + result = result.united(r); + } else if (ground()) { + result = result.united(groundRect()); + } + + result = result.united(labelRect(EndPoint::Source)); + result = result.united(labelRect(EndPoint::Target)); + + return result; +} + +void Connection::update(bool update_widgets) const +{ + m_edit->update(region()); + if (update_widgets) { + if (m_source != nullptr) + m_edit->update(m_source_rect); + if (m_target != nullptr) + m_edit->update(m_target_rect); + } + + m_edit->update(endPointRect(EndPoint::Source)); + m_edit->update(endPointRect(EndPoint::Target)); +} + +void Connection::paint(QPainter *p) const +{ + for (qsizetype i = 0; i < m_knee_list.size() - 1; ++i) + p->drawLine(m_knee_list.at(i), m_knee_list.at(i + 1)); + + if (!m_arrow_head.isEmpty()) { + p->save(); + p->setBrush(p->pen().color()); + p->drawPolygon(m_arrow_head); + p->restore(); + } else if (ground()) { + paintGround(p, groundRect()); + } +} + +bool Connection::contains(const QPoint &pos) const +{ + return region().contains(pos); +} + +QRect Connection::endPointRect(EndPoint::Type type) const +{ + if (type == EndPoint::Source) { + if (m_source_pos != QPoint(-1, -1)) + return endPointRectHelper(m_source_pos); + } else { + if (m_target_pos != QPoint(-1, -1)) + return endPointRectHelper(m_target_pos); + } + return QRect(); +} + +CETypes::LineDir Connection::labelDir(EndPoint::Type type) const +{ + const auto cnt = m_knee_list.size(); + if (cnt < 2) + return RightDir; + + LineDir dir; + if (type == EndPoint::Source) + dir = classifyLine(m_knee_list.at(0), m_knee_list.at(1)); + else + dir = classifyLine(m_knee_list.at(cnt - 2), m_knee_list.at(cnt - 1)); + + if (dir == LeftDir) + dir = RightDir; + if (dir == UpDir) + dir = DownDir; + + return dir; +} + +QRect Connection::labelRect(EndPoint::Type type) const +{ + const auto cnt = m_knee_list.size(); + if (cnt < 2) + return QRect(); + const QString text = label(type); + if (text.isEmpty()) + return QRect(); + + const QSize size = labelPixmap(type).size(); + QPoint p1, p2; + if (type == EndPoint::Source) { + p1 = m_knee_list.at(0); + p2 = m_knee_list.at(1); + } else { + p1 = m_knee_list.at(cnt - 1); + p2 = m_knee_list.at(cnt - 2); + } + const LineDir dir = classifyLine(p1, p2); + + QRect result; + switch (dir) { + case UpDir: + result = QRect(p1 + QPoint(-size.width()/2, 0), size); + break; + case DownDir: + result = QRect(p1 + QPoint(-size.width()/2, -size.height()), size); + break; + case LeftDir: + result = QRect(p1 + QPoint(0, -size.height()/2), size); + break; + case RightDir: + result = QRect(p1 + QPoint(-size.width(), -size.height()/2), size); + break; + } + + return result; +} + +void Connection::setLabel(EndPoint::Type type, const QString &text) +{ + if (text == label(type)) + return; + + if (type == EndPoint::Source) + m_source_label = text; + else + m_target_label = text; + + updatePixmap(type); +} + +void Connection::updatePixmap(EndPoint::Type type) +{ + QPixmap *pm = type == EndPoint::Source ? &m_source_label_pm : &m_target_label_pm; + + const QString text = label(type); + if (text.isEmpty()) { + *pm = QPixmap(); + return; + } + + const QFontMetrics fm = m_edit->fontMetrics(); + const QSize size = fm.size(Qt::TextSingleLine, text) + QSize(HLABEL_MARGIN*2, VLABEL_MARGIN*2); + *pm = QPixmap(size); + QColor color = m_edit->palette().color(QPalette::Normal, QPalette::Base); + color.setAlpha(190); + pm->fill(color); + + QPainter p(pm); + p.setPen(m_edit->palette().color(QPalette::Normal, QPalette::Text)); + p.drawText(-fm.leftBearing(text.at(0)) + HLABEL_MARGIN, fm.ascent() + VLABEL_MARGIN, text); + p.end(); + + const LineDir dir = labelDir(type); + + if (dir == DownDir) + *pm = pm->transformed(QTransform(0.0, -1.0, 1.0, 0.0, 0.0, 0.0)); +} + +void Connection::checkWidgets() +{ + bool changed = false; + + if (QWidget *sourceWidget = qobject_cast(m_source)) { + const QRect r = m_edit->widgetRect(sourceWidget); + if (r != m_source_rect) { + if (m_source_pos != QPoint(-1, -1) && !r.contains(m_source_pos)) { + QPoint offset = m_source_pos - m_source_rect.topLeft(); + m_source_pos = pointInsideRect(r, r.topLeft() + offset); + } + m_edit->update(m_source_rect); + m_source_rect = r; + changed = true; + } + } + + if (QWidget *targetWidget = qobject_cast(m_target)) { + const QRect r = m_edit->widgetRect(targetWidget); + if (r != m_target_rect) { + if (m_target_pos != QPoint(-1, -1) && !r.contains(m_target_pos)) { + const QPoint offset = m_target_pos - m_target_rect.topLeft(); + m_target_pos = pointInsideRect(r, r.topLeft() + offset); + } + m_edit->update(m_target_rect); + m_target_rect = r; + changed = true; + } + } + + if (changed) { + update(); + updateKneeList(); + update(); + } +} + +/******************************************************************************* +** ConnectionEdit +*/ + +ConnectionEdit::ConnectionEdit(QWidget *parent, QDesignerFormWindowInterface *form) : + QWidget(parent), + m_bg_widget(nullptr), + m_undo_stack(form->commandHistory()), + m_enable_update_background(false), + m_tmp_con(nullptr), + m_start_connection_on_drag(true), + m_widget_under_mouse(nullptr), + m_inactive_color(Qt::blue), + m_active_color(Qt::red) +{ + setAttribute(Qt::WA_MouseTracking, true); + setFocusPolicy(Qt::ClickFocus); + + connect(form, &QDesignerFormWindowInterface::widgetRemoved, this, &ConnectionEdit::widgetRemoved); + connect(form, &QDesignerFormWindowInterface::objectRemoved, this, &ConnectionEdit::objectRemoved); +} + +ConnectionEdit::~ConnectionEdit() +{ + qDeleteAll(m_con_list); +} + +void ConnectionEdit::clear() +{ + m_con_list.clear(); + m_sel_con_set.clear(); + m_bg_widget = nullptr; + m_widget_under_mouse = nullptr; + m_tmp_con = nullptr; +} + +void ConnectionEdit::setBackground(QWidget *background) +{ + if (background == m_bg_widget) { + // nothing to do + return; + } + + m_bg_widget = background; + updateBackground(); +} + +void ConnectionEdit::enableUpdateBackground(bool enable) +{ + m_enable_update_background = enable; + if (enable) + updateBackground(); +} + +void ConnectionEdit::updateBackground() +{ + // Might happen while reloading a form. + if (m_bg_widget == nullptr) + return; + + if (!m_enable_update_background) + return; + + for (Connection *c : std::as_const(m_con_list)) + c->updateVisibility(); + + updateLines(); + update(); +} + +QWidget *ConnectionEdit::widgetAt(const QPoint &pos) const +{ + if (m_bg_widget == nullptr) + return nullptr; + QWidget *widget = m_bg_widget->childAt(pos); + if (widget == nullptr) + widget = m_bg_widget; + + return widget; +} + + +QRect ConnectionEdit::widgetRect(QWidget *w) const +{ + if (w == nullptr) + return QRect(); + QRect r = w->geometry(); + QPoint pos = w->mapToGlobal(QPoint(0, 0)); + pos = mapFromGlobal(pos); + r.moveTopLeft(pos); + return r; +} + +ConnectionEdit::State ConnectionEdit::state() const +{ + if (m_tmp_con != nullptr) + return Connecting; + if (!m_drag_end_point.isNull()) + return Dragging; + return Editing; +} + +void ConnectionEdit::paintLabel(QPainter *p, EndPoint::Type type, Connection *con) +{ + if (con->label(type).isEmpty()) + return; + + const bool heavy = selected(con) || con == m_tmp_con; + p->setPen(heavy ? m_active_color : m_inactive_color); + p->setBrush(Qt::NoBrush); + const QRect r = con->labelRect(type); + p->drawPixmap(r.topLeft(), con->labelPixmap(type)); + p->drawRect(fixRect(r)); +} + +void ConnectionEdit::paintConnection(QPainter *p, Connection *con, + WidgetSet *heavy_highlight_set, + WidgetSet *light_highlight_set) const +{ + QWidget *source = con->widget(EndPoint::Source); + QWidget *target = con->widget(EndPoint::Target); + + const bool heavy = selected(con) || con == m_tmp_con; + WidgetSet *set = heavy ? heavy_highlight_set : light_highlight_set; + p->setPen(heavy ? m_active_color : m_inactive_color); + con->paint(p); + + if (source != nullptr && source != m_bg_widget) + set->insert(source, source); + + if (target != nullptr && target != m_bg_widget) + set->insert(target, target); +} + +void ConnectionEdit::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + p.setClipRegion(e->region()); + + WidgetSet heavy_highlight_set, light_highlight_set; + + for (Connection *con : std::as_const(m_con_list)) { + if (!con->isVisible()) + continue; + + paintConnection(&p, con, &heavy_highlight_set, &light_highlight_set); + } + + if (m_tmp_con != nullptr) + paintConnection(&p, m_tmp_con, &heavy_highlight_set, &light_highlight_set); + + if (!m_widget_under_mouse.isNull() && m_widget_under_mouse != m_bg_widget) + heavy_highlight_set.insert(m_widget_under_mouse, m_widget_under_mouse); + + QColor c = m_active_color; + p.setPen(c); + c.setAlpha(BG_ALPHA); + p.setBrush(c); + + for (QWidget *w : std::as_const(heavy_highlight_set)) { + p.drawRect(fixRect(widgetRect(w))); + light_highlight_set.remove(w); + } + + c = m_inactive_color; + p.setPen(c); + c.setAlpha(BG_ALPHA); + p.setBrush(c); + + for (QWidget *w : std::as_const(light_highlight_set)) + p.drawRect(fixRect(widgetRect(w))); + + p.setBrush(palette().color(QPalette::Base)); + p.setPen(palette().color(QPalette::Text)); + for (Connection *con : std::as_const(m_con_list)) { + if (con->isVisible()) { + paintLabel(&p, EndPoint::Source, con); + paintLabel(&p, EndPoint::Target, con); + } + } + + p.setPen(m_active_color); + p.setBrush(m_active_color); + + for (Connection *con : std::as_const(m_con_list)) { + if (!selected(con) || !con->isVisible()) + continue; + + paintEndPoint(&p, con->endPointPos(EndPoint::Source)); + + if (con->widget(EndPoint::Target) != nullptr) + paintEndPoint(&p, con->endPointPos(EndPoint::Target)); + } +} + +void ConnectionEdit::abortConnection() +{ + m_tmp_con->update(); + delete m_tmp_con; + m_tmp_con = nullptr; +#if QT_CONFIG(cursor) + setCursor(QCursor()); +#endif + if (m_widget_under_mouse == m_bg_widget) + m_widget_under_mouse = nullptr; +} + +void ConnectionEdit::mousePressEvent(QMouseEvent *e) +{ + // Right click only to cancel + const Qt::MouseButton button = e->button(); + const State cstate = state(); + if (button != Qt::LeftButton && !(button == Qt::RightButton && cstate == Connecting)) { + QWidget::mousePressEvent(e); + return; + } + + e->accept(); + // Prefer a non-background widget over the connection, + // otherwise, widgets covered by the connection labels cannot be accessed + Connection *con_under_mouse = nullptr; + if (!m_widget_under_mouse || m_widget_under_mouse == m_bg_widget) + con_under_mouse = connectionAt(e->position().toPoint()); + + m_start_connection_on_drag = false; + const bool toggleSelection = e->modifiers().testFlag(Qt::ControlModifier); + switch (cstate) { + case Connecting: + if (button == Qt::RightButton) + abortConnection(); + break; + case Dragging: + break; + case Editing: + if (!m_end_point_under_mouse.isNull()) { + if (!toggleSelection) + startDrag(m_end_point_under_mouse, e->position().toPoint()); + } else if (con_under_mouse != nullptr) { + if (toggleSelection) { + setSelected(con_under_mouse, !selected(con_under_mouse)); + } else { + selectNone(); + setSelected(con_under_mouse, true); + } + } else { + if (!toggleSelection) { + selectNone(); + if (!m_widget_under_mouse.isNull()) + m_start_connection_on_drag = true; + } + } + break; + } +} + +void ConnectionEdit::mouseDoubleClickEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) { + QWidget::mouseDoubleClickEvent(e); + return; + } + + e->accept(); + switch (state()) { + case Connecting: + abortConnection(); + break; + case Dragging: + break; + case Editing: + if (!m_widget_under_mouse.isNull()) + emit widgetActivated(m_widget_under_mouse); + else if (m_sel_con_set.size() == 1) + modifyConnection(m_sel_con_set.constBegin().key()); + break; + } + +} + +void ConnectionEdit::mouseReleaseEvent(QMouseEvent *e) +{ + if (e->button() != Qt::LeftButton) { + QWidget::mouseReleaseEvent(e); + return; + } + e->accept(); + + switch (state()) { + case Connecting: + if (m_widget_under_mouse.isNull()) + abortConnection(); + else + endConnection(m_widget_under_mouse, e->position().toPoint()); +#if QT_CONFIG(cursor) + setCursor(QCursor()); +#endif + break; + case Editing: + break; + case Dragging: + endDrag(e->position().toPoint()); + break; + } +} + + +void ConnectionEdit::findObjectsUnderMouse(const QPoint &pos) +{ + Connection *con_under_mouse = connectionAt(pos); + + QWidget *w = widgetAt(pos); + // Prefer a non-background widget over the connection, + // otherwise, widgets covered by the connection labels cannot be accessed + if (w == m_bg_widget && con_under_mouse) + w = nullptr; + else + con_under_mouse = nullptr; + + if (w != m_widget_under_mouse) { + if (!m_widget_under_mouse.isNull()) + update(widgetRect(m_widget_under_mouse)); + m_widget_under_mouse = w; + if (!m_widget_under_mouse.isNull()) + update(widgetRect(m_widget_under_mouse)); + } + + const EndPoint hs = endPointAt(pos); + if (hs != m_end_point_under_mouse) { +#if QT_CONFIG(cursor) + if (m_end_point_under_mouse.isNull()) + setCursor(Qt::PointingHandCursor); + else + setCursor(QCursor()); +#endif + m_end_point_under_mouse = hs; + } +} + +void ConnectionEdit::mouseMoveEvent(QMouseEvent *e) +{ + findObjectsUnderMouse(e->position().toPoint()); + switch (state()) { + case Connecting: + continueConnection(m_widget_under_mouse, e->position().toPoint()); + break; + case Editing: + if ((e->buttons() & Qt::LeftButton) + && m_start_connection_on_drag + && !m_widget_under_mouse.isNull()) { + m_start_connection_on_drag = false; + startConnection(m_widget_under_mouse, e->position().toPoint()); +#if QT_CONFIG(cursor) + setCursor(Qt::CrossCursor); +#endif + } + break; + case Dragging: + continueDrag(e->position().toPoint()); + break; + } + + e->accept(); +} + +void ConnectionEdit::keyPressEvent(QKeyEvent *e) +{ + switch (e->key()) { + case Qt::Key_Delete: + if (state() == Editing) + deleteSelected(); + break; + case Qt::Key_Escape: + if (state() == Connecting) + abortConnection(); + break; + } + + e->accept(); +} + +void ConnectionEdit::startConnection(QWidget *source, const QPoint &pos) +{ + Q_ASSERT(m_tmp_con == nullptr); + + m_tmp_con = new Connection(this); + m_tmp_con->setEndPoint(EndPoint::Source, source, pos); +} + +void ConnectionEdit::endConnection(QWidget *target, const QPoint &pos) +{ + Q_ASSERT(m_tmp_con != nullptr); + + m_tmp_con->setEndPoint(EndPoint::Target, target, pos); + + QWidget *source = m_tmp_con->widget(EndPoint::Source); + Q_ASSERT(source != nullptr); + Q_ASSERT(target != nullptr); + setEnabled(false); + Connection *new_con = createConnection(source, target); + setEnabled(true); + if (new_con != nullptr) { + new_con->setEndPoint(EndPoint::Source, source, m_tmp_con->endPointPos(EndPoint::Source)); + new_con->setEndPoint(EndPoint::Target, target, m_tmp_con->endPointPos(EndPoint::Target)); + m_undo_stack->push(new AddConnectionCommand(this, new_con)); + emit connectionChanged(new_con); + } + + delete m_tmp_con; + m_tmp_con = nullptr; + + findObjectsUnderMouse(mapFromGlobal(QCursor::pos())); +} + +void ConnectionEdit::continueConnection(QWidget *target, const QPoint &pos) +{ + Q_ASSERT(m_tmp_con != nullptr); + + m_tmp_con->setEndPoint(EndPoint::Target, target, pos); +} + +void ConnectionEdit::modifyConnection(Connection *) +{ +} + +Connection *ConnectionEdit::createConnection(QWidget *source, QWidget *target) +{ + Connection *con = new Connection(this, source, target); + return con; +} + +// Find all connections which in which a sequence of objects is involved +template +static ConnectionEdit::ConnectionSet findConnectionsOf(const ConnectionEdit::ConnectionList &cl, ObjectIterator oi1, const ObjectIterator &oi2) +{ + ConnectionEdit::ConnectionSet rc; + + const auto ccend = cl.cend(); + for ( ; oi1 != oi2; ++oi1) { + for (auto cit = cl.constBegin(); cit != ccend; ++cit) { + Connection *con = *cit; + if (con->object(ConnectionEdit::EndPoint::Source) == *oi1 || con->object(ConnectionEdit::EndPoint::Target) == *oi1) + rc.insert(con, con); + } + } + return rc; +} + +void ConnectionEdit::widgetRemoved(QWidget *widget) +{ + // Remove all connections of that widget and its children. + if (m_con_list.isEmpty()) + return; + + QWidgetList child_list = widget->findChildren(); + child_list.prepend(widget); + + const ConnectionSet remove_set = findConnectionsOf(m_con_list, child_list.constBegin(), child_list.constEnd()); + + if (!remove_set.isEmpty()) { + auto cmd = new DeleteConnectionsCommand(this, ConnectionList(remove_set.cbegin(), remove_set.cend())); + m_undo_stack->push(cmd); + } + + updateBackground(); +} + +void ConnectionEdit::objectRemoved(QObject *o) +{ + // Remove all connections of that object and its children (in case of action groups). + if (m_con_list.isEmpty()) + return; + + QObjectList child_list = o->children(); + child_list.prepend(o); + const ConnectionSet remove_set = findConnectionsOf(m_con_list, child_list.constBegin(), child_list.constEnd()); + if (!remove_set.isEmpty()) { + auto cmd = new DeleteConnectionsCommand(this, ConnectionList(remove_set.cbegin(), remove_set.cend())); + m_undo_stack->push(cmd); + } + + updateBackground(); +} + +void ConnectionEdit::setSelected(Connection *con, bool sel) +{ + if (!con || sel == m_sel_con_set.contains(con)) + return; + + if (sel) { + m_sel_con_set.insert(con, con); + emit connectionSelected(con); + } else { + m_sel_con_set.remove(con); + } + + con->update(); +} + +bool ConnectionEdit::selected(const Connection *con) const +{ + return m_sel_con_set.contains(const_cast(con)); +} + +void ConnectionEdit::selectNone() +{ + for (Connection *con : std::as_const(m_sel_con_set)) + con->update(); + + m_sel_con_set.clear(); +} + +void ConnectionEdit::selectAll() +{ + if (m_sel_con_set.size() == m_con_list.size()) + return; + for (Connection *con : std::as_const(m_con_list)) + setSelected(con, true); +} + +Connection *ConnectionEdit::connectionAt(const QPoint &pos) const +{ + for (Connection *con : m_con_list) { + if (con->contains(pos)) + return con; + } + return nullptr; +} + +CETypes::EndPoint ConnectionEdit::endPointAt(const QPoint &pos) const +{ + for (Connection *con : m_con_list) { + if (!selected(con)) + continue; + const QRect sr = con->endPointRect(EndPoint::Source); + const QRect tr = con->endPointRect(EndPoint::Target); + + if (sr.contains(pos)) + return EndPoint(con, EndPoint::Source); + if (tr.contains(pos)) + return EndPoint(con, EndPoint::Target); + } + return EndPoint(); +} + +void ConnectionEdit::startDrag(const EndPoint &end_point, const QPoint &pos) +{ + Q_ASSERT(m_drag_end_point.isNull()); + m_drag_end_point = end_point; + m_old_source_pos = m_drag_end_point.con->endPointPos(EndPoint::Source); + m_old_target_pos = m_drag_end_point.con->endPointPos(EndPoint::Target); + adjustHotSopt(m_drag_end_point, pos); +} + +void ConnectionEdit::continueDrag(const QPoint &pos) +{ + Q_ASSERT(!m_drag_end_point.isNull()); + adjustHotSopt(m_drag_end_point, pos); +} + +void ConnectionEdit::endDrag(const QPoint &pos) +{ + Q_ASSERT(!m_drag_end_point.isNull()); + adjustHotSopt(m_drag_end_point, pos); + + Connection *con = m_drag_end_point.con; + const QPoint new_source_pos = con->endPointPos(EndPoint::Source); + const QPoint new_target_pos = con->endPointPos(EndPoint::Target); + m_undo_stack->push(new AdjustConnectionCommand(this, con, m_old_source_pos, m_old_target_pos, + new_source_pos, new_target_pos)); + + m_drag_end_point = EndPoint(); +} + +void ConnectionEdit::adjustHotSopt(const EndPoint &end_point, const QPoint &pos) +{ + QWidget *w = end_point.con->widget(end_point.type); + end_point.con->setEndPoint(end_point.type, w, pointInsideRect(widgetRect(w), pos)); +} + +void ConnectionEdit::deleteSelected() +{ + if (m_sel_con_set.isEmpty()) + return; + auto cmd = new DeleteConnectionsCommand(this, ConnectionList(m_sel_con_set.cbegin(), m_sel_con_set.cend())); + m_undo_stack->push(cmd); +} + +void ConnectionEdit::addConnection(Connection *con) +{ + m_con_list.append(con); +} + +void ConnectionEdit::updateLines() +{ + for (Connection *con : std::as_const(m_con_list)) + con->checkWidgets(); +} + +void ConnectionEdit::resizeEvent(QResizeEvent *e) +{ + updateBackground(); + QWidget::resizeEvent(e); +} + +void ConnectionEdit::setSource(Connection *con, const QString &obj_name) +{ + QObject *object = nullptr; + if (!obj_name.isEmpty()) { + object = m_bg_widget->findChild(obj_name); + if (object == nullptr && m_bg_widget->objectName() == obj_name) + object = m_bg_widget; + + if (object == con->object(EndPoint::Source)) + return; + } + m_undo_stack->push(new SetEndPointCommand(this, con, EndPoint::Source, object)); +} + +void ConnectionEdit::setTarget(Connection *con, const QString &obj_name) +{ + QObject *object = nullptr; + if (!obj_name.isEmpty()) { + object = m_bg_widget->findChild(obj_name); + if (object == nullptr && m_bg_widget->objectName() == obj_name) + object = m_bg_widget; + + if (object == con->object(EndPoint::Target)) + return; + } + m_undo_stack->push(new SetEndPointCommand(this, con, EndPoint::Target, object)); +} + +Connection *ConnectionEdit::takeConnection(Connection *con) +{ + if (!m_con_list.contains(con)) + return nullptr; + m_con_list.removeAll(con); + return con; +} + +void ConnectionEdit::clearNewlyAddedConnection() +{ + delete m_tmp_con; + m_tmp_con = nullptr; +} + +void ConnectionEdit::createContextMenu(QMenu &menu) +{ + // Select + QAction *selectAllAction = menu.addAction(tr("Select All")); + selectAllAction->setEnabled(!connectionList().isEmpty()); + connect(selectAllAction, &QAction::triggered, this, &ConnectionEdit::selectAll); + QAction *deselectAllAction = menu.addAction(tr("Deselect All")); + deselectAllAction->setEnabled(!selection().isEmpty()); + connect(deselectAllAction, &QAction::triggered, this, &ConnectionEdit::selectNone); + menu.addSeparator(); + // Delete + QAction *deleteAction = menu.addAction(tr("Delete")); + deleteAction->setShortcut(QKeySequence::Delete); + deleteAction->setEnabled(!selection().isEmpty()); + connect(deleteAction, &QAction::triggered, this, &ConnectionEdit::deleteSelected); +} + +void ConnectionEdit::contextMenuEvent(QContextMenuEvent * event) +{ + QMenu menu; + createContextMenu(menu); + menu.exec(event->globalPos()); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/connectionedit_p.h b/src/tools/designer/src/lib/shared/connectionedit_p.h new file mode 100644 index 00000000000..3cfce754bad --- /dev/null +++ b/src/tools/designer/src/lib/shared/connectionedit_p.h @@ -0,0 +1,285 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + + +#ifndef CONNECTIONEDIT_H +#define CONNECTIONEDIT_H + +#include "shared_global_p.h" + +#include +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QUndoStack; +class QMenu; + +namespace qdesigner_internal { + +class Connection; +class ConnectionEdit; + +class QDESIGNER_SHARED_EXPORT CETypes +{ +public: + using ConnectionList = QList; + using ConnectionSet = QHash ; + using WidgetSet = QHash; + + class EndPoint { + public: + enum Type { Source, Target }; + explicit EndPoint(Connection *_con = nullptr, Type _type = Source) : con(_con), type(_type) {} + bool isNull() const { return con == nullptr; } + bool operator == (const EndPoint &other) const { return con == other.con && type == other.type; } + bool operator != (const EndPoint &other) const { return !operator == (other); } + Connection *con; + Type type; + }; + enum LineDir { UpDir = 0, DownDir, RightDir, LeftDir }; +}; + +class QDESIGNER_SHARED_EXPORT Connection : public CETypes +{ +public: + explicit Connection(ConnectionEdit *edit); + explicit Connection(ConnectionEdit *edit, QObject *source, QObject *target); + virtual ~Connection() = default; + + QObject *object(EndPoint::Type type) const + { + return (type == EndPoint::Source ? m_source : m_target); + } + + QWidget *widget(EndPoint::Type type) const + { + return qobject_cast(object(type)); + } + + QPoint endPointPos(EndPoint::Type type) const; + QRect endPointRect(EndPoint::Type) const; + void setEndPoint(EndPoint::Type type, QObject *w, const QPoint &pos) + { type == EndPoint::Source ? setSource(w, pos) : setTarget(w, pos); } + + bool isVisible() const; + virtual void updateVisibility(); + void setVisible(bool b); + + virtual QRegion region() const; + bool contains(const QPoint &pos) const; + virtual void paint(QPainter *p) const; + + void update(bool update_widgets = true) const; + void checkWidgets(); + + QString label(EndPoint::Type type) const + { return type == EndPoint::Source ? m_source_label : m_target_label; } + void setLabel(EndPoint::Type type, const QString &text); + QRect labelRect(EndPoint::Type type) const; + QPixmap labelPixmap(EndPoint::Type type) const + { return type == EndPoint::Source ? m_source_label_pm : m_target_label_pm; } + + ConnectionEdit *edit() const { return m_edit; } + + virtual void inserted() {} + virtual void removed() {} + +private: + QPoint m_source_pos, m_target_pos; + QObject *m_source, *m_target; + QList m_knee_list; + QPolygonF m_arrow_head; + ConnectionEdit *m_edit; + QString m_source_label, m_target_label; + QPixmap m_source_label_pm, m_target_label_pm; + QRect m_source_rect, m_target_rect; + bool m_visible; + + void setSource(QObject *source, const QPoint &pos); + void setTarget(QObject *target, const QPoint &pos); + void updateKneeList(); + void trimLine(); + void updatePixmap(EndPoint::Type type); + LineDir labelDir(EndPoint::Type type) const; + bool ground() const; + QRect groundRect() const; +}; + +class QDESIGNER_SHARED_EXPORT ConnectionEdit : public QWidget, public CETypes +{ + Q_OBJECT +public: + ConnectionEdit(QWidget *parent, QDesignerFormWindowInterface *form); + ~ConnectionEdit() override; + + inline const QPointer &background() const { return m_bg_widget; } + + void setSelected(Connection *con, bool sel); + bool selected(const Connection *con) const; + + int connectionCount() const { return m_con_list.size(); } + Connection *connection(int i) const { return m_con_list.at(i); } + int indexOfConnection(Connection *con) const { return m_con_list.indexOf(con); } + + virtual void setSource(Connection *con, const QString &obj_name); + virtual void setTarget(Connection *con, const QString &obj_name); + + QUndoStack *undoStack() const { return m_undo_stack; } + + void clear(); + + void showEvent(QShowEvent * /*e*/) override + { + updateBackground(); + } + +signals: + void aboutToAddConnection(int idx); + void connectionAdded(Connection *con); + void aboutToRemoveConnection(Connection *con); + void connectionRemoved(int idx); + void connectionSelected(Connection *con); + void widgetActivated(QWidget *wgt); + void connectionChanged(Connection *con); + +public slots: + void selectNone(); + void selectAll(); + virtual void deleteSelected(); + virtual void setBackground(QWidget *background); + virtual void updateBackground(); + virtual void widgetRemoved(QWidget *w); + virtual void objectRemoved(QObject *o); + + void updateLines(); + void enableUpdateBackground(bool enable); + +protected: + void paintEvent(QPaintEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void mousePressEvent(QMouseEvent *e) override; + void mouseReleaseEvent(QMouseEvent *e) override; + void keyPressEvent(QKeyEvent *e) override; + void mouseDoubleClickEvent(QMouseEvent *e) override; + void resizeEvent(QResizeEvent *e) override; + void contextMenuEvent(QContextMenuEvent * event) override; + + virtual Connection *createConnection(QWidget *source, QWidget *target); + virtual void modifyConnection(Connection *con); + + virtual QWidget *widgetAt(const QPoint &pos) const; + virtual void createContextMenu(QMenu &menu); + void addConnection(Connection *con); + QRect widgetRect(QWidget *w) const; + + enum State { Editing, Connecting, Dragging }; + State state() const; + + virtual void endConnection(QWidget *target, const QPoint &pos); + + const ConnectionList &connectionList() const { return m_con_list; } + const ConnectionSet &selection() const { return m_sel_con_set; } + Connection *takeConnection(Connection *con); + Connection *newlyAddedConnection() { return m_tmp_con; } + void clearNewlyAddedConnection(); + + void findObjectsUnderMouse(const QPoint &pos); + +private: + void startConnection(QWidget *source, const QPoint &pos); + void continueConnection(QWidget *target, const QPoint &pos); + void abortConnection(); + + void startDrag(const EndPoint &end_point, const QPoint &pos); + void continueDrag(const QPoint &pos); + void endDrag(const QPoint &pos); + void adjustHotSopt(const EndPoint &end_point, const QPoint &pos); + Connection *connectionAt(const QPoint &pos) const; + EndPoint endPointAt(const QPoint &pos) const; + void paintConnection(QPainter *p, Connection *con, + WidgetSet *heavy_highlight_set, + WidgetSet *light_highlight_set) const; + void paintLabel(QPainter *p, EndPoint::Type type, Connection *con); + + + QPointer m_bg_widget; + QUndoStack *m_undo_stack; + bool m_enable_update_background; + + Connection *m_tmp_con; // the connection we are currently editing + ConnectionList m_con_list; + bool m_start_connection_on_drag; + EndPoint m_end_point_under_mouse; + QPointer m_widget_under_mouse; + + EndPoint m_drag_end_point; + QPoint m_old_source_pos, m_old_target_pos; + ConnectionSet m_sel_con_set; + const QColor m_inactive_color; + const QColor m_active_color; + +private: + friend class Connection; + friend class AddConnectionCommand; + friend class DeleteConnectionsCommand; + friend class SetEndPointCommand; +}; + +class QDESIGNER_SHARED_EXPORT CECommand : public QUndoCommand, public CETypes +{ +public: + explicit CECommand(ConnectionEdit *edit) + : m_edit(edit) {} + + bool mergeWith(const QUndoCommand *) override { return false; } + + ConnectionEdit *edit() const { return m_edit; } + +private: + ConnectionEdit *m_edit; +}; + +class QDESIGNER_SHARED_EXPORT AddConnectionCommand : public CECommand +{ +public: + AddConnectionCommand(ConnectionEdit *edit, Connection *con); + void redo() override; + void undo() override; +private: + Connection *m_con; +}; + +class QDESIGNER_SHARED_EXPORT DeleteConnectionsCommand : public CECommand +{ +public: + DeleteConnectionsCommand(ConnectionEdit *edit, const ConnectionList &con_list); + void redo() override; + void undo() override; +private: + ConnectionList m_con_list; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // CONNECTIONEDIT_H diff --git a/src/tools/designer/src/lib/shared/csshighlighter.cpp b/src/tools/designer/src/lib/shared/csshighlighter.cpp new file mode 100644 index 00000000000..d34e7e4de2d --- /dev/null +++ b/src/tools/designer/src/lib/shared/csshighlighter.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "csshighlighter_p.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +CssHighlighter::CssHighlighter(const CssHighlightColors &colors, + QTextDocument *document) + : QSyntaxHighlighter(document), m_colors(colors) +{ +} + +void CssHighlighter::highlightBlock(const QString& text) +{ + enum Token { ALNUM, LBRACE, RBRACE, COLON, SEMICOLON, COMMA, QUOTE, SLASH, STAR }; + static const int transitions[10][9] = { + { Selector, Property, Selector, Pseudo, Property, Selector, Quote, MaybeComment, Selector }, // Selector + { Property, Property, Selector, Value, Property, Property, Quote, MaybeComment, Property }, // Property + { Value, Property, Selector, Value, Property, Value, Quote, MaybeComment, Value }, // Value + { Pseudo1, Property, Selector, Pseudo2, Selector, Selector, Quote, MaybeComment, Pseudo }, // Pseudo + { Pseudo1, Property, Selector, Pseudo, Selector, Selector, Quote, MaybeComment, Pseudo1 }, // Pseudo1 + { Pseudo2, Property, Selector, Pseudo, Selector, Selector, Quote, MaybeComment, Pseudo2 }, // Pseudo2 + { Quote, Quote, Quote, Quote, Quote, Quote, -1, Quote, Quote }, // Quote + { -1, -1, -1, -1, -1, -1, -1, -1, Comment }, // MaybeComment + { Comment, Comment, Comment, Comment, Comment, Comment, Comment, Comment, MaybeCommentEnd }, // Comment + { Comment, Comment, Comment, Comment, Comment, Comment, Comment, -1, MaybeCommentEnd } // MaybeCommentEnd + }; + + qsizetype lastIndex = 0; + bool lastWasSlash = false; + int state = previousBlockState(), save_state; + if (state == -1) { + // As long as the text is empty, leave the state undetermined + if (text.isEmpty()) { + setCurrentBlockState(-1); + return; + } + // The initial state is based on the precense of a : and the absense of a {. + // This is because Qt style sheets support both a full stylesheet as well as + // an inline form with just properties. + state = save_state = (text.indexOf(u':') > -1 && + text.indexOf(u'{') == -1) ? Property : Selector; + } else { + save_state = state>>16; + state &= 0x00ff; + } + + if (state == MaybeCommentEnd) { + state = Comment; + } else if (state == MaybeComment) { + state = save_state; + } + + for (qsizetype i = 0; i < text.size(); ++i) { + int token = ALNUM; + const QChar c = text.at(i); + const char a = c.toLatin1(); + + if (state == Quote) { + if (a == '\\') { + lastWasSlash = true; + } else { + if (a == '\"' && !lastWasSlash) { + token = QUOTE; + } + lastWasSlash = false; + } + } else { + switch (a) { + case '{': token = LBRACE; break; + case '}': token = RBRACE; break; + case ':': token = COLON; break; + case ';': token = SEMICOLON; break; + case ',': token = COMMA; break; + case '\"': token = QUOTE; break; + case '/': token = SLASH; break; + case '*': token = STAR; break; + default: break; + } + } + + int new_state = transitions[state][token]; + + if (new_state != state) { + bool include_token = new_state == MaybeCommentEnd || (state == MaybeCommentEnd && new_state!= Comment) + || state == Quote; + highlight(text, lastIndex, i-lastIndex+include_token, state); + + if (new_state == Comment) { + lastIndex = i-1; // include the slash and star + } else { + lastIndex = i + ((token == ALNUM || new_state == Quote) ? 0 : 1); + } + } + + if (new_state == -1) { + state = save_state; + } else if (state <= Pseudo2) { + save_state = state; + state = new_state; + } else { + state = new_state; + } + } + + highlight(text, lastIndex, text.size() - lastIndex, state); + setCurrentBlockState(state + (save_state<<16)); +} + +void CssHighlighter::highlight(const QString &text, int start, int length, int state) +{ + if (start >= text.size() || length <= 0) + return; + + QTextCharFormat format; + + switch (state) { + case Selector: + setFormat(start, length, m_colors.selector); + break; + case Property: + setFormat(start, length, m_colors.property); + break; + case Value: + setFormat(start, length, m_colors.value); + break; + case Pseudo1: + setFormat(start, length, m_colors.pseudo1); + break; + case Pseudo2: + setFormat(start, length, m_colors.pseudo2); + break; + case Quote: + setFormat(start, length, m_colors.quote); + break; + case Comment: + case MaybeCommentEnd: + format.setForeground(m_colors.comment); + setFormat(start, length, format); + break; + default: + break; + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/csshighlighter_p.h b/src/tools/designer/src/lib/shared/csshighlighter_p.h new file mode 100644 index 00000000000..bad7e9f7dfe --- /dev/null +++ b/src/tools/designer/src/lib/shared/csshighlighter_p.h @@ -0,0 +1,59 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef CSSHIGHLIGHTER_H +#define CSSHIGHLIGHTER_H + +#include +#include +#include "shared_global_p.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +struct CssHighlightColors +{ + QColor selector; + QColor property; + QColor value; + QColor pseudo1; + QColor pseudo2; + QColor quote; + QColor comment; +}; + +class QDESIGNER_SHARED_EXPORT CssHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT +public: + explicit CssHighlighter(const CssHighlightColors &colors, + QTextDocument *document); + +protected: + void highlightBlock(const QString&) override; + void highlight(const QString&, int, int, int/*State*/); + +private: + enum State { Selector, Property, Value, Pseudo, Pseudo1, Pseudo2, Quote, + MaybeComment, Comment, MaybeCommentEnd }; + + const CssHighlightColors m_colors; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // CSSHIGHLIGHTER_H diff --git a/src/tools/designer/src/lib/shared/defaultgradients.xml b/src/tools/designer/src/lib/shared/defaultgradients.xml new file mode 100644 index 00000000000..70559ad12dd --- /dev/null +++ b/src/tools/designer/src/lib/shared/defaultgradients.xml @@ -0,0 +1,498 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/tools/designer/src/lib/shared/deviceprofile.cpp b/src/tools/designer/src/lib/shared/deviceprofile.cpp new file mode 100644 index 00000000000..25148b52609 --- /dev/null +++ b/src/tools/designer/src/lib/shared/deviceprofile.cpp @@ -0,0 +1,422 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "deviceprofile_p.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + + +static const char dpiXPropertyC[] = "_q_customDpiX"; +static const char dpiYPropertyC[] = "_q_customDpiY"; + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// XML serialization +static const char *xmlVersionC="1.0"; +static const char *rootElementC="deviceprofile"; +static constexpr auto nameElementC = "name"_L1; +static constexpr auto fontFamilyElementC = "fontfamily"_L1; +static constexpr auto fontPointSizeElementC = "fontpointsize"_L1; +static constexpr auto dPIXElementC = "dpix"_L1; +static constexpr auto dPIYElementC = "dpiy"_L1; +static constexpr auto styleElementC = "style"_L1; + +/* DeviceProfile: + * For preview purposes (preview, widget box, new form dialog), the + * QDesignerFormBuilder applies the settings to the form main container + * (Point being here that the DPI must be set directly for size calculations + * to be correct). + * For editing purposes, FormWindow applies the settings to the form container + * as not to interfere with the font settings of the form main container. + * In addition, the widgetfactory maintains the system settings style + * and applies it when creating widgets. */ + +// ---------------- DeviceProfileData +class DeviceProfileData : public QSharedData { +public: + DeviceProfileData() = default; + void fromSystem(); + void clear(); + + QString m_fontFamily; + QString m_style; + QString m_name; + int m_fontPointSize = -1; + int m_dpiX = -1; + int m_dpiY = -1; +}; + +void DeviceProfileData::clear() +{ + m_fontPointSize = -1; + m_dpiX = 0; + m_dpiY = 0; + m_name.clear(); + m_style.clear(); +} + +void DeviceProfileData::fromSystem() +{ + const QFont appFont = QApplication::font(); + m_fontFamily = appFont.family(); + m_fontPointSize = appFont.pointSize(); + DeviceProfile::systemResolution(&m_dpiX, &m_dpiY); + m_style.clear(); +} + +// ---------------- DeviceProfile +DeviceProfile::DeviceProfile() : + m_d(new DeviceProfileData) +{ +} + +DeviceProfile::DeviceProfile(const DeviceProfile &o) : + m_d(o.m_d) + +{ +} + +DeviceProfile& DeviceProfile::operator=(const DeviceProfile &o) +{ + m_d.operator=(o.m_d); + return *this; +} + +DeviceProfile::~DeviceProfile() = default; + +void DeviceProfile::clear() +{ + m_d->clear(); +} + +bool DeviceProfile::isEmpty() const +{ + return m_d->m_name.isEmpty(); +} + +QString DeviceProfile::fontFamily() const +{ + return m_d->m_fontFamily; +} + +void DeviceProfile::setFontFamily(const QString &f) +{ + m_d->m_fontFamily = f; +} + +int DeviceProfile::fontPointSize() const +{ + return m_d->m_fontPointSize; +} + +void DeviceProfile::setFontPointSize(int p) +{ + m_d->m_fontPointSize = p; +} + +QString DeviceProfile::style() const +{ + return m_d->m_style; +} + +void DeviceProfile::setStyle(const QString &s) +{ + m_d->m_style = s; +} + +int DeviceProfile::dpiX() const +{ + return m_d->m_dpiX; +} + +void DeviceProfile::setDpiX(int d) +{ + m_d->m_dpiX = d; +} + +int DeviceProfile::dpiY() const +{ + return m_d->m_dpiY; +} + +void DeviceProfile::setDpiY(int d) +{ + m_d->m_dpiY = d; +} + +void DeviceProfile::fromSystem() +{ + m_d->fromSystem(); +} + +QString DeviceProfile::name() const +{ + return m_d->m_name; +} + +void DeviceProfile::setName(const QString &n) +{ + m_d->m_name = n; +} + +void DeviceProfile::systemResolution(int *dpiX, int *dpiY) +{ + auto s = qApp->primaryScreen(); + *dpiX = s->logicalDotsPerInchX(); + *dpiY = s->logicalDotsPerInchY(); +} + +class FriendlyWidget : public QWidget { + friend class DeviceProfile; +}; + +void DeviceProfile::widgetResolution(const QWidget *w, int *dpiX, int *dpiY) +{ + const FriendlyWidget *fw = static_cast(w); + *dpiX = fw->metric(QPaintDevice::PdmDpiX); + *dpiY = fw->metric(QPaintDevice::PdmDpiY); +} + +QString DeviceProfile::toString() const +{ + const DeviceProfileData &d = *m_d; + QString rc; + QTextStream(&rc) << "DeviceProfile:name=" << d.m_name << " Font=" << d.m_fontFamily << ' ' + << d.m_fontPointSize << " Style=" << d.m_style << " DPI=" << d.m_dpiX << ',' << d.m_dpiY; + return rc; +} + +// Apply font to widget +static void applyFont(const QString &family, int size, DeviceProfile::ApplyMode am, QWidget *widget) +{ + QFont currentFont = widget->font(); + if (currentFont.pointSize() == size && currentFont.family() == family) + return; + switch (am) { + case DeviceProfile::ApplyFormParent: + // Invisible form parent: Apply all + widget->setFont(QFont(family, size)); + break; + case DeviceProfile::ApplyPreview: { + // Preview: Apply only subproperties that have not been changed by designer properties + bool apply = false; + const uint resolve = currentFont.resolveMask(); + if (!(resolve & QFont::FamilyResolved)) { + currentFont.setFamily(family); + apply = true; + } + if (!(resolve & QFont::SizeResolved)) { + currentFont.setPointSize(size); + apply = true; + } + if (apply) + widget->setFont(currentFont); + } + break; + } +} + +void DeviceProfile::applyDPI(int dpiX, int dpiY, QWidget *widget) +{ + int sysDPIX, sysDPIY; // Set dynamic variables in case values are different from system DPI + systemResolution(&sysDPIX, &sysDPIY); + if (dpiX != sysDPIX && dpiY != sysDPIY) { + widget->setProperty(dpiXPropertyC, QVariant(dpiX)); + widget->setProperty(dpiYPropertyC, QVariant(dpiY)); + } +} + +void DeviceProfile::apply(const QDesignerFormEditorInterface *core, QWidget *widget, ApplyMode am) const +{ + if (isEmpty()) + return; + + const DeviceProfileData &d = *m_d; + + if (!d.m_fontFamily.isEmpty()) + applyFont(d.m_fontFamily, d.m_fontPointSize, am, widget); + + applyDPI(d.m_dpiX, d.m_dpiY, widget); + + if (!d.m_style.isEmpty()) { + if (WidgetFactory *wf = qobject_cast(core->widgetFactory())) + wf->applyStyleTopLevel(d.m_style, widget); + } +} + +bool comparesEqual(const DeviceProfile &lhs, const DeviceProfile &rhs) noexcept +{ + const DeviceProfileData &d = *lhs.m_d; + const DeviceProfileData &rhs_d = *rhs.m_d; + return d.m_fontPointSize == rhs_d.m_fontPointSize && + d.m_dpiX == rhs_d.m_dpiX && d.m_dpiY == rhs_d.m_dpiY && d.m_fontFamily == rhs_d.m_fontFamily && + d.m_style == rhs_d.m_style && d.m_name == rhs_d.m_name; +} + +static inline void writeElement(QXmlStreamWriter &writer, const QString &element, const QString &cdata) +{ + writer.writeStartElement(element); + writer.writeCharacters(cdata); + writer.writeEndElement(); +} + +QString DeviceProfile::toXml() const +{ + const DeviceProfileData &d = *m_d; + QString rc; + QXmlStreamWriter writer(&rc); + writer.writeStartDocument(QLatin1StringView(xmlVersionC)); + writer.writeStartElement(QLatin1StringView(rootElementC)); + writeElement(writer, nameElementC, d.m_name); + + if (!d.m_fontFamily.isEmpty()) + writeElement(writer, fontFamilyElementC, d.m_fontFamily); + if (d.m_fontPointSize >= 0) + writeElement(writer, fontPointSizeElementC, QString::number(d.m_fontPointSize)); + if (d.m_dpiX > 0) + writeElement(writer, dPIXElementC, QString::number(d.m_dpiX)); + if (d.m_dpiY > 0) + writeElement(writer, dPIYElementC, QString::number(d.m_dpiY)); + if (!d.m_style.isEmpty()) + writeElement(writer, styleElementC, d.m_style); + + writer.writeEndElement(); + writer.writeEndDocument(); + return rc; +} + +/* Switch stages when encountering a start element (state table) */ +enum ParseStage { ParseBeginning, ParseWithinRoot, + ParseName, ParseFontFamily, ParseFontPointSize, ParseDPIX, ParseDPIY, ParseStyle, + ParseError }; + +static ParseStage nextStage(ParseStage currentStage, QStringView startElement) +{ + switch (currentStage) { + case ParseBeginning: + if (startElement == QLatin1StringView(rootElementC)) + return ParseWithinRoot; + break; + case ParseWithinRoot: + case ParseName: + case ParseFontFamily: + case ParseFontPointSize: + case ParseDPIX: + case ParseDPIY: + case ParseStyle: + if (startElement == nameElementC) + return ParseName; + if (startElement == fontFamilyElementC) + return ParseFontFamily; + if (startElement == fontPointSizeElementC) + return ParseFontPointSize; + if (startElement == dPIXElementC) + return ParseDPIX; + if (startElement == dPIYElementC) + return ParseDPIY; + if (startElement == styleElementC) + return ParseStyle; + break; + case ParseError: + break; + } + return ParseError; +} + +static bool readIntegerElement(QXmlStreamReader &reader, int *v) +{ + const QString e = reader.readElementText(); + bool ok; + *v = e.toInt(&ok); + //: Reading a number for an embedded device profile + if (!ok) + reader.raiseError(QApplication::translate("DeviceProfile", "'%1' is not a number.").arg(e)); + return ok; +} + +bool DeviceProfile::fromXml(const QString &xml, QString *errorMessage) +{ + DeviceProfileData &d = *m_d; + d.fromSystem(); + + QXmlStreamReader reader(xml); + + ParseStage ps = ParseBeginning; + QXmlStreamReader::TokenType tt = QXmlStreamReader::NoToken; + int iv = 0; + do { + tt = reader.readNext(); + if (tt == QXmlStreamReader::StartElement) { + ps = nextStage(ps, reader.name()); + switch (ps) { + case ParseBeginning: + case ParseWithinRoot: + break; + case ParseError: + reader.raiseError(QApplication::translate("DeviceProfile", "An invalid tag <%1> was encountered.").arg(reader.name().toString())); + tt = QXmlStreamReader::Invalid; + break; + case ParseName: + d.m_name = reader.readElementText(); + break; + case ParseFontFamily: + d.m_fontFamily = reader.readElementText(); + break; + case ParseFontPointSize: + if (readIntegerElement(reader, &iv)) { + d.m_fontPointSize = iv; + } else { + tt = QXmlStreamReader::Invalid; + } + break; + case ParseDPIX: + if (readIntegerElement(reader, &iv)) { + d.m_dpiX = iv; + } else { + tt = QXmlStreamReader::Invalid; + } + break; + case ParseDPIY: + if (readIntegerElement(reader, &iv)) { + d.m_dpiY = iv; + } else { + tt = QXmlStreamReader::Invalid; + } + break; + case ParseStyle: + d.m_style = reader.readElementText(); + break; + } + } + } while (tt != QXmlStreamReader::Invalid && tt != QXmlStreamReader::EndDocument); + + if (reader.hasError()) { + *errorMessage = reader.errorString(); + return false; + } + + return true; +} +} + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/lib/shared/deviceprofile_p.h b/src/tools/designer/src/lib/shared/deviceprofile_p.h new file mode 100644 index 00000000000..273a1a1a25e --- /dev/null +++ b/src/tools/designer/src/lib/shared/deviceprofile_p.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DEVICEPROFILE_H +#define DEVICEPROFILE_H + +#include "shared_global_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QWidget; +class QStyle; + +namespace qdesigner_internal { + +class DeviceProfileData; + +/* DeviceProfile for embedded design. They influence + * default properties (for example, fonts), dpi and + * style of the form. This class represents a device + * profile. */ + +class QDESIGNER_SHARED_EXPORT DeviceProfile { +public: + DeviceProfile(); + + DeviceProfile(const DeviceProfile&); + DeviceProfile& operator=(const DeviceProfile&); + ~DeviceProfile(); + + void clear(); + + // Device name + QString name() const; + void setName(const QString &); + + // System settings active + bool isEmpty() const; + + // Default font family of the embedded system + QString fontFamily() const; + void setFontFamily(const QString &); + + // Default font size of the embedded system + int fontPointSize() const; + void setFontPointSize(int p); + + // Display resolution of the embedded system + int dpiX() const; + void setDpiX(int d); + int dpiY() const; + void setDpiY(int d); + + // Style + QString style() const; + void setStyle(const QString &); + + // Initialize from desktop system + void fromSystem(); + + static void systemResolution(int *dpiX, int *dpiY); + static void widgetResolution(const QWidget *w, int *dpiX, int *dpiY); + + // Apply to form/preview (using font inheritance) + enum ApplyMode { + /* Pre-Apply to parent widget of form being edited: Apply font + * and make use of property inheritance to be able to modify the + * font property freely. */ + ApplyFormParent, + /* Post-Apply to preview widget: Change only inherited font + * sub properties. */ + ApplyPreview + }; + void apply(const QDesignerFormEditorInterface *core, QWidget *widget, ApplyMode am) const; + + static void applyDPI(int dpiX, int dpiY, QWidget *widget); + + QString toString() const; + + QString toXml() const; + bool fromXml(const QString &xml, QString *errorMessage); + +private: + friend QDESIGNER_SHARED_EXPORT bool comparesEqual(const DeviceProfile &lhs, + const DeviceProfile &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(DeviceProfile) + + QSharedDataPointer m_d; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DEVICEPROFILE_H diff --git a/src/tools/designer/src/lib/shared/dialoggui.cpp b/src/tools/designer/src/lib/shared/dialoggui.cpp new file mode 100644 index 00000000000..168462ad332 --- /dev/null +++ b/src/tools/designer/src/lib/shared/dialoggui.cpp @@ -0,0 +1,221 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "dialoggui_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include + +// QFileDialog on X11 does not provide an image preview. Display icons. +#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS) +# define IMAGE_PREVIEW +#endif + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Icon provider that reads out the known image formats +class IconProvider : public QFileIconProvider { + Q_DISABLE_COPY_MOVE(IconProvider) + +public: + IconProvider(); + QIcon icon (const QFileInfo &info) const override; + + inline bool loadCheck(const QFileInfo &info) const; + QImage loadImage(const QString &fileName) const; + +private: + QSet m_imageFormats; +}; + +IconProvider::IconProvider() +{ + // Determine a list of readable extensions (upper and lower case) + const auto &fmts = QImageReader::supportedImageFormats(); + for (const QByteArray &fmt : fmts) { + const QString suffix = QString::fromUtf8(fmt); + m_imageFormats.insert(suffix.toLower()); + m_imageFormats.insert(suffix.toUpper()); + } +} + +// Check by extension and type if this appears to be a loadable image +bool IconProvider::loadCheck(const QFileInfo &info) const +{ + if (info.isFile() && info.isReadable()) { + const QString suffix = info.suffix(); + if (!suffix.isEmpty()) + return m_imageFormats.contains(suffix); + } + return false; +} + +QImage IconProvider::loadImage(const QString &fileName) const +{ + QFile file(fileName); + if (file.open(QIODevice::ReadOnly)) { + QImageReader imgReader(&file); + if (imgReader.canRead()) { + QImage image; + if (imgReader.read(&image)) + return image; + } + } + return QImage(); +} + +QIcon IconProvider::icon (const QFileInfo &info) const +{ + // Don't get stuck on large images. + const qint64 maxSize = 131072; + if (loadCheck(info) && info.size() < maxSize) { + const QImage image = loadImage(info.absoluteFilePath()); + if (!image.isNull()) + return QIcon(QPixmap::fromImage(image, Qt::ThresholdDither|Qt::AutoColor)); + } + return QFileIconProvider::icon(info); +} + +// ---------------- DialogGui +DialogGui::DialogGui() = default; + +DialogGui::~DialogGui() +{ + delete m_iconProvider; +} + +QFileIconProvider *DialogGui::ensureIconProvider() +{ + if (!m_iconProvider) + m_iconProvider = new IconProvider; + return m_iconProvider; +} + +QMessageBox::StandardButton + DialogGui::message(QWidget *parent, Message /*context*/, QMessageBox::Icon icon, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) +{ + QMessageBox::StandardButton rc = QMessageBox::NoButton; + switch (icon) { + case QMessageBox::Information: + rc = QMessageBox::information(parent, title, text, buttons, defaultButton); + break; + case QMessageBox::Warning: + rc = QMessageBox::warning(parent, title, text, buttons, defaultButton); + break; + case QMessageBox::Critical: + rc = QMessageBox::critical(parent, title, text, buttons, defaultButton); + break; + case QMessageBox::Question: + rc = QMessageBox::question(parent, title, text, buttons, defaultButton); + break; + case QMessageBox::NoIcon: + break; + } + return rc; +} + +QMessageBox::StandardButton + DialogGui::message(QWidget *parent, Message /*context*/, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) +{ + QMessageBox msgBox(icon, title, text, buttons, parent); + msgBox.setDefaultButton(defaultButton); + msgBox.setInformativeText(informativeText); + return static_cast(msgBox.exec()); +} + +QMessageBox::StandardButton + DialogGui::message(QWidget *parent, Message /*context*/, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, const QString &detailedText, + QMessageBox::StandardButtons buttons, QMessageBox::StandardButton defaultButton) +{ + QMessageBox msgBox(icon, title, text, buttons, parent); + msgBox.setDefaultButton(defaultButton); + msgBox.setInformativeText(informativeText); + msgBox.setDetailedText(detailedText); + return static_cast(msgBox.exec()); +} + +QString DialogGui::getExistingDirectory(QWidget *parent, const QString &caption, const QString &dir, QFileDialog::Options options) +{ + return QFileDialog::getExistingDirectory(parent, caption, dir, options); +} + +QString DialogGui::getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return QFileDialog::getOpenFileName(parent, caption, dir, filter, selectedFilter, options); +} + +QStringList DialogGui::getOpenFileNames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return QFileDialog::getOpenFileNames(parent, caption, dir, filter, selectedFilter, options); +} + +QString DialogGui::getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) +{ + return QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options); +} + +void DialogGui::initializeImageFileDialog(QFileDialog &fileDialog, QFileDialog::Options options, QFileDialog::FileMode fm) +{ + fileDialog.setOption(QFileDialog::DontConfirmOverwrite, options.testFlag(QFileDialog::DontConfirmOverwrite)); + fileDialog.setOption(QFileDialog::DontResolveSymlinks, options.testFlag(QFileDialog::DontResolveSymlinks)); + fileDialog.setIconProvider(ensureIconProvider()); + fileDialog.setFileMode(fm); +} + +QString DialogGui::getOpenImageFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options ) +{ + +#ifdef IMAGE_PREVIEW + QFileDialog fileDialog(parent, caption, dir, filter); + initializeImageFileDialog(fileDialog, options, QFileDialog::ExistingFile); + if (fileDialog.exec() != QDialog::Accepted) + return QString(); + + const QStringList selectedFiles = fileDialog.selectedFiles(); + if (selectedFiles.isEmpty()) + return QString(); + + if (selectedFilter) + *selectedFilter = fileDialog.selectedNameFilter(); + + return selectedFiles.constFirst(); +#else + return getOpenFileName(parent, caption, dir, filter, selectedFilter, options); +#endif +} + +QStringList DialogGui::getOpenImageFileNames(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options ) +{ +#ifdef IMAGE_PREVIEW + QFileDialog fileDialog(parent, caption, dir, filter); + initializeImageFileDialog(fileDialog, options, QFileDialog::ExistingFiles); + if (fileDialog.exec() != QDialog::Accepted) + return QStringList(); + + const QStringList selectedFiles = fileDialog.selectedFiles(); + if (!selectedFiles.isEmpty() && selectedFilter) + *selectedFilter = fileDialog.selectedNameFilter(); + + return selectedFiles; +#else + return getOpenFileNames(parent, caption, dir, filter, selectedFilter, options); +#endif +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/dialoggui_p.h b/src/tools/designer/src/lib/shared/dialoggui_p.h new file mode 100644 index 00000000000..a60c90494ef --- /dev/null +++ b/src/tools/designer/src/lib/shared/dialoggui_p.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef DIALOGGUI +#define DIALOGGUI + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include "shared_global_p.h" +#include + +QT_BEGIN_NAMESPACE + +class QFileIconProvider; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT DialogGui : public QDesignerDialogGuiInterface +{ +public: + DialogGui(); + ~DialogGui() override; + + QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) override; + + QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) override; + + QMessageBox::StandardButton + message(QWidget *parent, Message context, QMessageBox::Icon icon, + const QString &title, const QString &text, const QString &informativeText, const QString &detailedText, + QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) override; + + QString getExistingDirectory(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), QFileDialog::Options options = QFileDialog::ShowDirsOnly) override; + QString getOpenFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + QStringList getOpenFileNames(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + QString getSaveFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + + QString getOpenImageFileName(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + QStringList getOpenImageFileNames(QWidget *parent = nullptr, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = nullptr, QFileDialog::Options options = {}) override; + +private: + QFileIconProvider *ensureIconProvider(); + void initializeImageFileDialog(QFileDialog &fd, QFileDialog::Options options, QFileDialog::FileMode); + + QFileIconProvider *m_iconProvider = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DIALOGGUI diff --git a/src/tools/designer/src/lib/shared/extensionfactory_p.h b/src/tools/designer/src/lib/shared/extensionfactory_p.h new file mode 100644 index 00000000000..7c34728b6bb --- /dev/null +++ b/src/tools/designer/src/lib/shared/extensionfactory_p.h @@ -0,0 +1,82 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef SHARED_EXTENSIONFACTORY_H +#define SHARED_EXTENSIONFACTORY_H + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Extension factory for registering an extension for an object type. +template +class ExtensionFactory: public QExtensionFactory +{ +public: + explicit ExtensionFactory(const QString &iid, QExtensionManager *parent = nullptr); + + // Convenience for registering the extension. Do not use for derived classes. + static void registerExtension(QExtensionManager *mgr, const QString &iid); + +protected: + QObject *createExtension(QObject *qObject, const QString &iid, QObject *parent) const override; + +private: + // Can be overwritten to perform checks on the object. + // Default does a qobject_cast to the desired class. + virtual Object *checkObject(QObject *qObject) const; + + const QString m_iid; +}; + +template +ExtensionFactory::ExtensionFactory(const QString &iid, QExtensionManager *parent) : + QExtensionFactory(parent), + m_iid(iid) +{ +} + +template +Object *ExtensionFactory::checkObject(QObject *qObject) const +{ + return qobject_cast(qObject); +} + +template +QObject *ExtensionFactory::createExtension(QObject *qObject, const QString &iid, QObject *parent) const +{ + if (iid != m_iid) + return nullptr; + + Object *object = checkObject(qObject); + if (!object) + return nullptr; + + return new Extension(object, parent); +} + +template +void ExtensionFactory::registerExtension(QExtensionManager *mgr, const QString &iid) +{ + ExtensionFactory *factory = new ExtensionFactory(iid, mgr); + mgr->registerExtensions(factory, iid); +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // SHARED_EXTENSIONFACTORY_H diff --git a/src/tools/designer/src/lib/shared/formlayoutmenu.cpp b/src/tools/designer/src/lib/shared/formlayoutmenu.cpp new file mode 100644 index 00000000000..0c73137706c --- /dev/null +++ b/src/tools/designer/src/lib/shared/formlayoutmenu.cpp @@ -0,0 +1,491 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formlayoutmenu_p.h" +#include "layoutinfo_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_propertycommand_p.h" +#include "ui_formlayoutrowdialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto buddyPropertyC = "buddy"_L1; +static const char *fieldWidgetBaseClasses[] = { + "QLineEdit", "QComboBox", "QSpinBox", "QDoubleSpinBox", "QCheckBox", + "QDateEdit", "QTimeEdit", "QDateTimeEdit", "QDial", "QWidget" +}; + +namespace qdesigner_internal { + +// Struct that describes a row of controls (descriptive label and control) to +// be added to a form layout. +struct FormLayoutRow { + QString labelName; + QString labelText; + QString fieldClassName; + QString fieldName; + bool buddy{false}; +}; + +// A Dialog to edit a FormLayoutRow. Lets the user input a label text, label +// name, field widget type, field object name and buddy setting. As the +// user types the label text; the object names to be used for label and field +// are updated. It also checks the buddy setting depending on whether the +// label text contains a buddy marker. +class FormLayoutRowDialog : public QDialog { + Q_DISABLE_COPY_MOVE(FormLayoutRowDialog) + Q_OBJECT +public: + explicit FormLayoutRowDialog(QDesignerFormEditorInterface *core, + QWidget *parent); + + FormLayoutRow formLayoutRow() const; + + bool buddy() const; + void setBuddy(bool); + + // Accessors for form layout row numbers using 0..[n-1] convention + int row() const; + void setRow(int); + void setRowRange(int, int); + + QString fieldClass() const; + QString labelText() const; + + static QStringList fieldWidgetClasses(QDesignerFormEditorInterface *core); + +private slots: + void labelTextEdited(const QString &text); + void labelNameEdited(const QString &text); + void fieldNameEdited(const QString &text); + void buddyClicked(); + void fieldClassChanged(int); + +private: + bool isValid() const; + void updateObjectNames(bool updateLabel, bool updateField); + void updateOkButton(); + + // Check for buddy marker in string + const QRegularExpression m_buddyMarkerRegexp; + + QT_PREPEND_NAMESPACE(Ui)::FormLayoutRowDialog m_ui; + bool m_labelNameEdited; + bool m_fieldNameEdited; + bool m_buddyClicked; +}; + +FormLayoutRowDialog::FormLayoutRowDialog(QDesignerFormEditorInterface *core, + QWidget *parent) : + QDialog(parent), + m_buddyMarkerRegexp(u"\\&[^&]"_s), + m_labelNameEdited(false), + m_fieldNameEdited(false), + m_buddyClicked(false) +{ + Q_ASSERT(m_buddyMarkerRegexp.isValid()); + + setModal(true); + m_ui.setupUi(this); + connect(m_ui.labelTextLineEdit, &QLineEdit::textEdited, this, &FormLayoutRowDialog::labelTextEdited); + + auto *nameValidator = new QRegularExpressionValidator(QRegularExpression(u"^[a-zA-Z0-9_]+$"_s), this); + Q_ASSERT(nameValidator->regularExpression().isValid()); + + m_ui.labelNameLineEdit->setValidator(nameValidator); + connect(m_ui.labelNameLineEdit, &QLineEdit::textEdited, + this, &FormLayoutRowDialog::labelNameEdited); + + m_ui.fieldNameLineEdit->setValidator(nameValidator); + connect(m_ui.fieldNameLineEdit, &QLineEdit::textEdited, + this, &FormLayoutRowDialog::fieldNameEdited); + + connect(m_ui.buddyCheckBox, &QAbstractButton::clicked, this, &FormLayoutRowDialog::buddyClicked); + + m_ui.fieldClassComboBox->addItems(fieldWidgetClasses(core)); + m_ui.fieldClassComboBox->setCurrentIndex(0); + connect(m_ui.fieldClassComboBox, + &QComboBox::currentIndexChanged, + this, &FormLayoutRowDialog::fieldClassChanged); + + updateOkButton(); +} + +FormLayoutRow FormLayoutRowDialog::formLayoutRow() const +{ + FormLayoutRow rc; + rc.labelText = labelText(); + rc.labelName = m_ui.labelNameLineEdit->text(); + rc.fieldClassName = fieldClass(); + rc.fieldName = m_ui.fieldNameLineEdit->text(); + rc.buddy = buddy(); + return rc; +} + +bool FormLayoutRowDialog::buddy() const +{ + return m_ui.buddyCheckBox->checkState() == Qt::Checked; +} + +void FormLayoutRowDialog::setBuddy(bool b) +{ + m_ui.buddyCheckBox->setCheckState(b ? Qt::Checked : Qt::Unchecked); +} + +// Convert rows to 1..n convention for users +int FormLayoutRowDialog::row() const +{ + return m_ui.rowSpinBox->value() - 1; +} + +void FormLayoutRowDialog::setRow(int row) +{ + m_ui.rowSpinBox->setValue(row + 1); +} + +void FormLayoutRowDialog::setRowRange(int from, int to) +{ + m_ui.rowSpinBox->setMinimum(from + 1); + m_ui.rowSpinBox->setMaximum(to + 1); + m_ui.rowSpinBox->setEnabled(to - from > 0); +} + +QString FormLayoutRowDialog::fieldClass() const +{ + return m_ui.fieldClassComboBox->itemText(m_ui.fieldClassComboBox->currentIndex()); +} + +QString FormLayoutRowDialog::labelText() const +{ + return m_ui.labelTextLineEdit->text(); +} + +bool FormLayoutRowDialog::isValid() const +{ + // Check for non-empty names and presence of buddy marker if checked + const QString name = labelText(); + if (name.isEmpty() || m_ui.labelNameLineEdit->text().isEmpty() || m_ui.fieldNameLineEdit->text().isEmpty()) + return false; + if (buddy() && !name.contains(m_buddyMarkerRegexp)) + return false; + return true; +} + +void FormLayoutRowDialog::updateOkButton() +{ + m_ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isValid()); +} + +void FormLayoutRowDialog::labelTextEdited(const QString &text) +{ + updateObjectNames(true, true); + // Set buddy if '&' is present unless the user changed it + if (!m_buddyClicked) + setBuddy(text.contains(m_buddyMarkerRegexp)); + + updateOkButton(); +} + +// Get a suitable object name postfix from a class name: +// "namespace::QLineEdit"->"LineEdit" +static inline QString postFixFromClassName(QString className) +{ + const int index = className.lastIndexOf("::"_L1); + if (index != -1) + className.remove(0, index + 2); + if (className.size() > 2) + if (className.at(0) == u'Q' || className.at(0) == u'K') + if (className.at(1).isUpper()) + className.remove(0, 1); + return className; +} + +// Helper routines to filter out characters for converting texts into +// class name prefixes. Only accepts ASCII characters/digits and underscores. + +enum PrefixCharacterKind { PC_Digit, PC_UpperCaseLetter, PC_LowerCaseLetter, + PC_Other, PC_Invalid }; + +static inline PrefixCharacterKind prefixCharacterKind(const QChar &c) +{ + switch (c.category()) { + case QChar::Number_DecimalDigit: + return PC_Digit; + case QChar::Letter_Lowercase: { + const char a = c.toLatin1(); + if (a >= 'a' && a <= 'z') + return PC_LowerCaseLetter; + } + break; + case QChar::Letter_Uppercase: { + const char a = c.toLatin1(); + if (a >= 'A' && a <= 'Z') + return PC_UpperCaseLetter; + } + break; + case QChar::Punctuation_Connector: + if (c.toLatin1() == '_') + return PC_Other; + break; + default: + break; + } + return PC_Invalid; +} + +// Convert the text the user types into a usable class name prefix by filtering +// characters, lower-casing the first character and camel-casing subsequent +// words. ("zip code:") --> ("zipCode"). + +static QString prefixFromLabel(const QString &prefix) +{ + QString rc; + bool lastWasAcceptable = false; + for (const QChar &c : prefix) { + const PrefixCharacterKind kind = prefixCharacterKind(c); + const bool acceptable = kind != PC_Invalid; + if (acceptable) { + if (rc.isEmpty()) { + // Lower-case first character + rc += kind == PC_UpperCaseLetter ? c.toLower() : c; + } else { + // Camel-case words + rc += !lastWasAcceptable && kind == PC_LowerCaseLetter ? c.toUpper() : c; + } + } + lastWasAcceptable = acceptable; + } + return rc; +} + +void FormLayoutRowDialog::updateObjectNames(bool updateLabel, bool updateField) +{ + // Generate label + field object names from the label text, that is, + // "&Zip code:" -> "zipcodeLabel", "zipcodeLineEdit" unless the user + // edited it. + const bool doUpdateLabel = !m_labelNameEdited && updateLabel; + const bool doUpdateField = !m_fieldNameEdited && updateField; + if (!doUpdateLabel && !doUpdateField) + return; + + const QString prefix = prefixFromLabel(labelText()); + // Set names + if (doUpdateLabel) + m_ui.labelNameLineEdit->setText(prefix + "Label"_L1); + if (doUpdateField) + m_ui.fieldNameLineEdit->setText(prefix + postFixFromClassName(fieldClass())); +} + +void FormLayoutRowDialog::fieldClassChanged(int) +{ + updateObjectNames(false, true); +} + +void FormLayoutRowDialog::labelNameEdited(const QString & /*text*/) +{ + m_labelNameEdited = true; // stop auto-updating after user change + updateOkButton(); +} + +void FormLayoutRowDialog::fieldNameEdited(const QString & /*text*/) +{ + m_fieldNameEdited = true; // stop auto-updating after user change + updateOkButton(); +} + +void FormLayoutRowDialog::buddyClicked() +{ + m_buddyClicked = true; // stop auto-updating after user change + updateOkButton(); +} + +/* Create a list of classes suitable for field widgets. Take the fixed base + * classes provided and look in the widget database for custom widgets derived + * from them ("QLineEdit", "CustomLineEdit", "QComboBox"...). */ +QStringList FormLayoutRowDialog::fieldWidgetClasses(QDesignerFormEditorInterface *core) +{ + static QStringList rc; + if (rc.isEmpty()) { + // Turn known base classes into list + QStringList baseClasses; + for (auto fw : fieldWidgetBaseClasses) + baseClasses.append(QLatin1StringView(fw)); + // Scan for custom widgets that inherit them and store them in a + // multimap of base class->custom widgets unless we have a language + // extension installed which might do funny things with custom widgets. + QMultiHash customClassMap; // Base class -> custom widgets map + if (qt_extension(core->extensionManager(), core) == nullptr) { + const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); + const int wdbCount = wdb->count(); + for (int w = 0; w < wdbCount; ++w) { + // Check for non-container custom types that extend the + // respective base class. + const QDesignerWidgetDataBaseItemInterface *dbItem = wdb->item(w); + if (!dbItem->isPromoted() && !dbItem->isContainer() && dbItem->isCustom()) { + const int index = baseClasses.indexOf(dbItem->extends()); + if (index != -1) + customClassMap.insert(baseClasses.at(index), dbItem->name()); + } + } + } + // Compile final list, taking each base class and append custom widgets + // based on it. + for (const auto &baseClass : baseClasses) { + rc.append(baseClass); + rc += customClassMap.values(baseClass); + } + } + return rc; +} + +// ------------------ Utilities + +static QFormLayout *managedFormLayout(const QDesignerFormEditorInterface *core, const QWidget *w) +{ + QLayout *l = nullptr; + if (LayoutInfo::managedLayoutType(core, w, &l) == LayoutInfo::Form) + return qobject_cast(l); + return nullptr; +} + +// Create the widgets of a control row and apply text properties contained +// in the struct, called by addFormLayoutRow() +static std::pair + createWidgets(const FormLayoutRow &row, QWidget *parent, + QDesignerFormWindowInterface *formWindow) +{ + QDesignerFormEditorInterface *core = formWindow->core(); + QDesignerWidgetFactoryInterface *wf = core->widgetFactory(); + + std::pair rc{wf->createWidget(u"QLabel"_s, parent), + wf->createWidget(row.fieldClassName, parent)}; + // Set up properties of the label + const QString objectNameProperty = u"objectName"_s; + QDesignerPropertySheetExtension *labelSheet = qt_extension(core->extensionManager(), rc.first); + int nameIndex = labelSheet->indexOf(objectNameProperty); + labelSheet->setProperty(nameIndex, QVariant::fromValue(PropertySheetStringValue(row.labelName))); + labelSheet->setChanged(nameIndex, true); + formWindow->ensureUniqueObjectName(rc.first); + const int textIndex = labelSheet->indexOf(u"text"_s); + labelSheet->setProperty(textIndex, QVariant::fromValue(PropertySheetStringValue(row.labelText))); + labelSheet->setChanged(textIndex, true); + // Set up properties of the control + QDesignerPropertySheetExtension *controlSheet = qt_extension(core->extensionManager(), rc.second); + nameIndex = controlSheet->indexOf(objectNameProperty); + controlSheet->setProperty(nameIndex, QVariant::fromValue(PropertySheetStringValue(row.fieldName))); + controlSheet->setChanged(nameIndex, true); + formWindow->ensureUniqueObjectName(rc.second); + return rc; +} + +// Create a command sequence on the undo stack of the form window that creates +// the widgets of the row and inserts them into the form layout. +static void addFormLayoutRow(const FormLayoutRow &formLayoutRow, int row, QWidget *w, + QDesignerFormWindowInterface *formWindow) +{ + QFormLayout *formLayout = managedFormLayout(formWindow->core(), w); + Q_ASSERT(formLayout); + QUndoStack *undoStack = formWindow->commandHistory(); + const QString macroName = QCoreApplication::translate("Command", "Add '%1' to '%2'").arg(formLayoutRow.labelText, formLayout->objectName()); + undoStack->beginMacro(macroName); + + // Create a list of widget insertion commands and pass them a cell position + const auto widgetPair = createWidgets(formLayoutRow, w, formWindow); + + InsertWidgetCommand *labelCmd = new InsertWidgetCommand(formWindow); + labelCmd->init(widgetPair.first, false, row, 0); + undoStack->push(labelCmd); + InsertWidgetCommand *controlCmd = new InsertWidgetCommand(formWindow); + controlCmd->init(widgetPair.second, false, row, 1); + undoStack->push(controlCmd); + if (formLayoutRow.buddy) { + SetPropertyCommand *buddyCommand = new SetPropertyCommand(formWindow); + buddyCommand->init(widgetPair.first, buddyPropertyC, widgetPair.second->objectName()); + undoStack->push(buddyCommand); + } + undoStack->endMacro(); +} + +// ---------------- FormLayoutMenu +FormLayoutMenu::FormLayoutMenu(QObject *parent) : + QObject(parent), + m_separator1(new QAction(this)), + m_populateFormAction(new QAction(tr("Add form layout row..."), this)), + m_separator2(new QAction(this)) +{ + m_separator1->setSeparator(true); + connect(m_populateFormAction, &QAction::triggered, this, &FormLayoutMenu::slotAddRow); + m_separator2->setSeparator(true); +} + +void FormLayoutMenu::populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList &actions) +{ + switch (LayoutInfo::managedLayoutType(fw->core(), w)) { + case LayoutInfo::Form: + if (!actions.isEmpty() && !actions.constLast()->isSeparator()) + actions.push_back(m_separator1); + actions.push_back(m_populateFormAction); + actions.push_back(m_separator2); + m_widget = w; + break; + default: + m_widget = nullptr; + break; + } +} + +void FormLayoutMenu::slotAddRow() +{ + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_widget); + Q_ASSERT(m_widget && fw); + const int rowCount = managedFormLayout(fw->core(), m_widget)->rowCount(); + + FormLayoutRowDialog dialog(fw->core(), fw); + dialog.setRowRange(0, rowCount); + dialog.setRow(rowCount); + + if (dialog.exec() != QDialog::Accepted) + return; + addFormLayoutRow(dialog.formLayoutRow(), dialog.row(), m_widget, fw); +} + +QAction *FormLayoutMenu::preferredEditAction(QWidget *w, QDesignerFormWindowInterface *fw) +{ + if (LayoutInfo::managedLayoutType(fw->core(), w) == LayoutInfo::Form) { + m_widget = w; + return m_populateFormAction; + } + return nullptr; +} +} + +QT_END_NAMESPACE + +#include "formlayoutmenu.moc" + diff --git a/src/tools/designer/src/lib/shared/formlayoutmenu_p.h b/src/tools/designer/src/lib/shared/formlayoutmenu_p.h new file mode 100644 index 00000000000..3f4bac07036 --- /dev/null +++ b/src/tools/designer/src/lib/shared/formlayoutmenu_p.h @@ -0,0 +1,62 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef FORMLAYOUTMENU +#define FORMLAYOUTMENU + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include "shared_global_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +class QAction; +class QWidget; + +namespace qdesigner_internal { + +// Task menu to be used for form layouts. Offers an options "Add row" which +// pops up a dialog in which the user can specify label name, text and buddy. +class QDESIGNER_SHARED_EXPORT FormLayoutMenu : public QObject +{ + Q_DISABLE_COPY_MOVE(FormLayoutMenu) + Q_OBJECT +public: + using ActionList = QList; + + explicit FormLayoutMenu(QObject *parent); + + // Populate a list of actions with the form layout actions. + void populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList &actions); + // For implementing QDesignerTaskMenuExtension::preferredEditAction(): + // Return appropriate action for double clicking. + QAction *preferredEditAction(QWidget *w, QDesignerFormWindowInterface *fw); + +private slots: + void slotAddRow(); + +private: + QAction *m_separator1; + QAction *m_populateFormAction; + QAction *m_separator2; + QPointer m_widget; +}; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMLAYOUTMENU diff --git a/src/tools/designer/src/lib/shared/formlayoutrowdialog.ui b/src/tools/designer/src/lib/shared/formlayoutrowdialog.ui new file mode 100644 index 00000000000..c0e0cfe2baf --- /dev/null +++ b/src/tools/designer/src/lib/shared/formlayoutrowdialog.ui @@ -0,0 +1,166 @@ + + + FormLayoutRowDialog + + + Add Form Layout Row + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + &Label text: + + + labelTextLineEdit + + + + + + + + 180 + 0 + + + + + + + + + + + Field &type: + + + fieldClassComboBox + + + + + + + + 0 + 0 + + + + + + + + &Field name: + + + fieldNameLineEdit + + + + + + + &Buddy: + + + buddyCheckBox + + + + + + + + + + + + + + &Row: + + + rowSpinBox + + + + + + + + + + + + + Label &name: + + + labelNameLineEdit + + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + FormLayoutRowDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + FormLayoutRowDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/tools/designer/src/lib/shared/formwindowbase.cpp b/src/tools/designer/src/lib/shared/formwindowbase.cpp new file mode 100644 index 00000000000..17dde04090b --- /dev/null +++ b/src/tools/designer/src/lib/shared/formwindowbase.cpp @@ -0,0 +1,549 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindowbase_p.h" +#include "connectionedit_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertysheet_p.h" +#include "qdesigner_propertyeditor_p.h" +#include "qdesigner_menu_p.h" +#include "qdesigner_menubar_p.h" +#include "shared_settings_p.h" +#include "grid_p.h" +#include "deviceprofile_p.h" +#include "qdesigner_utils_p.h" +#include "spacer_widget_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +class FormWindowBasePrivate { +public: + explicit FormWindowBasePrivate(QDesignerFormEditorInterface *core); + + static Grid m_defaultGrid; + + QDesignerFormWindowInterface::Feature m_feature; + Grid m_grid; + bool m_hasFormGrid; + DesignerPixmapCache *m_pixmapCache; + DesignerIconCache *m_iconCache; + QtResourceSet *m_resourceSet; + QHash> m_reloadableResources; + QHash m_reloadablePropertySheets; + const DeviceProfile m_deviceProfile; + FormWindowBase::LineTerminatorMode m_lineTerminatorMode; + FormWindowBase::ResourceFileSaveMode m_saveResourcesBehaviour; + bool m_useIdBasedTranslations; + bool m_connectSlotsByName; +}; + +FormWindowBasePrivate::FormWindowBasePrivate(QDesignerFormEditorInterface *core) : + m_feature(QDesignerFormWindowInterface::DefaultFeature), + m_grid(m_defaultGrid), + m_hasFormGrid(false), + m_pixmapCache(nullptr), + m_iconCache(nullptr), + m_resourceSet(nullptr), + m_deviceProfile(QDesignerSharedSettings(core).currentDeviceProfile()), + m_lineTerminatorMode(FormWindowBase::NativeLineTerminator), + m_saveResourcesBehaviour(FormWindowBase::SaveAllResourceFiles), + m_useIdBasedTranslations(false), + m_connectSlotsByName(true) +{ +} + +Grid FormWindowBasePrivate::m_defaultGrid; + +FormWindowBase::FormWindowBase(QDesignerFormEditorInterface *core, QWidget *parent, Qt::WindowFlags flags) : + QDesignerFormWindowInterface(parent, flags), + m_d(new FormWindowBasePrivate(core)) +{ + syncGridFeature(); + m_d->m_pixmapCache = new DesignerPixmapCache(this); + m_d->m_iconCache = new DesignerIconCache(m_d->m_pixmapCache, this); + if (core->integration()->hasFeature(QDesignerIntegrationInterface::DefaultWidgetActionFeature)) + connect(this, &QDesignerFormWindowInterface::activated, this, &FormWindowBase::triggerDefaultAction); +} + +FormWindowBase::~FormWindowBase() +{ + QSet sheets; + for (auto it = m_d->m_reloadableResources.cbegin(), end = m_d->m_reloadableResources.cend(); it != end; ++it) + sheets.insert(it.key()); + for (auto it = m_d->m_reloadablePropertySheets.cbegin(), end = m_d->m_reloadablePropertySheets.cend(); it != end; ++it) + sheets.insert(it.key()); + + m_d->m_reloadableResources.clear(); + m_d->m_reloadablePropertySheets.clear(); + + for (QDesignerPropertySheet *sheet : sheets) + disconnectSheet(sheet); + + delete m_d; +} + +DesignerPixmapCache *FormWindowBase::pixmapCache() const +{ + return m_d->m_pixmapCache; +} + +DesignerIconCache *FormWindowBase::iconCache() const +{ + return m_d->m_iconCache; +} + +QtResourceSet *FormWindowBase::resourceSet() const +{ + return m_d->m_resourceSet; +} + +void FormWindowBase::setResourceSet(QtResourceSet *resourceSet) +{ + m_d->m_resourceSet = resourceSet; +} + +void FormWindowBase::addReloadableProperty(QDesignerPropertySheet *sheet, int index) +{ + connectSheet(sheet); + m_d->m_reloadableResources[sheet].insert(index); +} + +void FormWindowBase::removeReloadableProperty(QDesignerPropertySheet *sheet, int index) +{ + m_d->m_reloadableResources[sheet].remove(index); + if (m_d->m_reloadableResources[sheet].isEmpty()) { + m_d->m_reloadableResources.remove(sheet); + disconnectSheet(sheet); + } +} + +void FormWindowBase::addReloadablePropertySheet(QDesignerPropertySheet *sheet, QObject *object) +{ + if (qobject_cast(object) || + qobject_cast(object) || + qobject_cast(object) || + qobject_cast(object)) { + connectSheet(sheet); + m_d->m_reloadablePropertySheets[sheet] = object; + } +} + +void FormWindowBase::connectSheet(QDesignerPropertySheet *sheet) +{ + if (m_d->m_reloadableResources.contains(sheet) + || m_d->m_reloadablePropertySheets.contains(sheet)) { + // already connected + return; + } + connect(sheet, &QObject::destroyed, this, &FormWindowBase::sheetDestroyed); +} + +void FormWindowBase::disconnectSheet(QDesignerPropertySheet *sheet) +{ + if (m_d->m_reloadableResources.contains(sheet) + || m_d->m_reloadablePropertySheets.contains(sheet)) { + // still need to be connected + return; + } + disconnect(sheet, &QObject::destroyed, this, &FormWindowBase::sheetDestroyed); +} + +void FormWindowBase::sheetDestroyed(QObject *object) +{ + // qobject_cast(object) + // will fail since the destructor of QDesignerPropertySheet + // has already finished + + for (auto it = m_d->m_reloadableResources.begin(); + it != m_d->m_reloadableResources.end(); ++it) { + if (it.key() == object) { + m_d->m_reloadableResources.erase(it); + break; + } + } + + for (auto it = m_d->m_reloadablePropertySheets.begin(); + it != m_d->m_reloadablePropertySheets.end(); ++it) { + if (it.key() == object) { + m_d->m_reloadablePropertySheets.erase(it); + break; + } + } +} + +void FormWindowBase::reloadProperties() +{ + pixmapCache()->clear(); + iconCache()->clear(); + for (auto it = m_d->m_reloadableResources.cbegin(), end = m_d->m_reloadableResources.cend(); it != end; ++it) { + QDesignerPropertySheet *sheet = it.key(); + for (int index : it.value()) { + const QVariant newValue = sheet->property(index); + if (qobject_cast(sheet->object()) && sheet->propertyName(index) == "text"_L1) { + const PropertySheetStringValue newString = qvariant_cast(newValue); + // optimize a bit, reset only if the text value might contain a reference to qt resources + // (however reloading of icons other than taken from resources might not work here) + if (newString.value().contains(":/"_L1)) { + const QVariant resetValue = QVariant::fromValue(PropertySheetStringValue()); + sheet->setProperty(index, resetValue); + } + } + sheet->setProperty(index, newValue); + } + if (QTabWidget *tabWidget = qobject_cast(sheet->object())) { + const int count = tabWidget->count(); + const int current = tabWidget->currentIndex(); + const QString currentTabIcon = u"currentTabIcon"_s; + for (int i = 0; i < count; i++) { + tabWidget->setCurrentIndex(i); + const int index = sheet->indexOf(currentTabIcon); + sheet->setProperty(index, sheet->property(index)); + } + tabWidget->setCurrentIndex(current); + } else if (QToolBox *toolBox = qobject_cast(sheet->object())) { + const int count = toolBox->count(); + const int current = toolBox->currentIndex(); + const QString currentItemIcon = u"currentItemIcon"_s; + for (int i = 0; i < count; i++) { + toolBox->setCurrentIndex(i); + const int index = sheet->indexOf(currentItemIcon); + sheet->setProperty(index, sheet->property(index)); + } + toolBox->setCurrentIndex(current); + } + } + for (QObject *object : std::as_const(m_d->m_reloadablePropertySheets)) { + reloadIconResources(iconCache(), object); + } +} + +void FormWindowBase::resourceSetActivated(QtResourceSet *resource, bool resourceSetChanged) +{ + if (resource == resourceSet() && resourceSetChanged) { + reloadProperties(); + emit pixmapCache()->reloaded(); + emit iconCache()->reloaded(); + if (QDesignerPropertyEditor *propertyEditor = qobject_cast(core()->propertyEditor())) + propertyEditor->reloadResourceProperties(); + } +} + +QVariantMap FormWindowBase::formData() +{ + QVariantMap rc; + if (m_d->m_hasFormGrid) + m_d->m_grid.addToVariantMap(rc, true); + return rc; +} + +void FormWindowBase::setFormData(const QVariantMap &vm) +{ + Grid formGrid; + m_d->m_hasFormGrid = formGrid.fromVariantMap(vm); + if (m_d->m_hasFormGrid) + m_d->m_grid = formGrid; +} + +QPoint FormWindowBase::grid() const +{ + return QPoint(m_d->m_grid.deltaX(), m_d->m_grid.deltaY()); +} + +void FormWindowBase::setGrid(const QPoint &grid) +{ + m_d->m_grid.setDeltaX(grid.x()); + m_d->m_grid.setDeltaY(grid.y()); +} + +bool FormWindowBase::hasFeature(Feature f) const +{ + return f & m_d->m_feature; +} + +static void recursiveUpdate(QWidget *w) +{ + w->update(); + + for (auto *child : w->children()) { + if (QWidget *w = qobject_cast(child)) + recursiveUpdate(w); + } +} + +void FormWindowBase::setFeatures(Feature f) +{ + m_d->m_feature = f; + const bool enableGrid = f & GridFeature; + m_d->m_grid.setVisible(enableGrid); + m_d->m_grid.setSnapX(enableGrid); + m_d->m_grid.setSnapY(enableGrid); + emit featureChanged(f); + recursiveUpdate(this); +} + +FormWindowBase::Feature FormWindowBase::features() const +{ + return m_d->m_feature; +} + +bool FormWindowBase::gridVisible() const +{ + return m_d->m_grid.visible() && currentTool() == 0; +} + +FormWindowBase::ResourceFileSaveMode FormWindowBase::resourceFileSaveMode() const +{ + return m_d->m_saveResourcesBehaviour; +} + +void FormWindowBase::setResourceFileSaveMode(ResourceFileSaveMode behaviour) +{ + m_d->m_saveResourcesBehaviour = behaviour; +} + +void FormWindowBase::syncGridFeature() +{ + if (m_d->m_grid.snapX() || m_d->m_grid.snapY()) + m_d->m_feature |= GridFeature; + else + m_d->m_feature &= ~GridFeature; +} + +void FormWindowBase::setDesignerGrid(const Grid& grid) +{ + m_d->m_grid = grid; + syncGridFeature(); + recursiveUpdate(this); +} + +const Grid &FormWindowBase::designerGrid() const +{ + return m_d->m_grid; +} + +bool FormWindowBase::hasFormGrid() const +{ + return m_d->m_hasFormGrid; +} + +void FormWindowBase::setHasFormGrid(bool b) +{ + m_d->m_hasFormGrid = b; +} + +void FormWindowBase::setDefaultDesignerGrid(const Grid& grid) +{ + FormWindowBasePrivate::m_defaultGrid = grid; +} + +const Grid &FormWindowBase::defaultDesignerGrid() +{ + return FormWindowBasePrivate::m_defaultGrid; +} + +QMenu *FormWindowBase::initializePopupMenu(QWidget * /*managedWidget*/) +{ + return nullptr; +} + +// Widget under mouse for finding the Widget to highlight +// when doing DnD. Restricts to pages by geometry if a container with +// a container extension (or one of its helper widgets) is hit; otherwise +// returns the widget as such (be it managed/unmanaged) + +QWidget *FormWindowBase::widgetUnderMouse(const QPoint &formPos, WidgetUnderMouseMode /* wum */) +{ + // widget_under_mouse might be some temporary thing like the dropLine. We need + // the actual widget that's part of the edited GUI. + QWidget *rc = widgetAt(formPos); + if (!rc || qobject_cast(rc)) + return nullptr; + + if (rc == mainContainer()) { + // Refuse main container areas if the main container has a container extension, + // for example when hitting QToolBox/QTabWidget empty areas. + if (qt_extension(core()->extensionManager(), rc)) + return nullptr; + return rc; + } + + // If we hit on container extension type container, make sure + // we use the top-most current page + if (QWidget *container = findContainer(rc, false)) + if (QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), container)) { + // For container that do not have a "stacked" nature (QToolBox, QMdiArea), + // make sure the position is within the current page + const int ci = c->currentIndex(); + if (ci < 0) + return nullptr; + QWidget *page = c->widget(ci); + QRect pageGeometry = page->geometry(); + pageGeometry.moveTo(page->mapTo(this, pageGeometry.topLeft())); + if (!pageGeometry.contains(formPos)) + return nullptr; + return page; + } + + return rc; +} + +void FormWindowBase::deleteWidgetList(const QWidgetList &widget_list) +{ + // We need a macro here even for single widgets because the some components (for example, + // the signal slot editor are connected to widgetRemoved() and add their + // own commands (for example, to delete w's connections) + const QString description = widget_list.size() == 1 ? + tr("Delete '%1'").arg(widget_list.constFirst()->objectName()) : tr("Delete"); + + commandHistory()->beginMacro(description); + for (QWidget *w : std::as_const(widget_list)) { + emit widgetRemoved(w); + DeleteWidgetCommand *cmd = new DeleteWidgetCommand(this); + cmd->init(w); + commandHistory()->push(cmd); + } + commandHistory()->endMacro(); +} + +QMenu *FormWindowBase::createExtensionTaskMenu(QDesignerFormWindowInterface *fw, QObject *o, bool trailingSeparator) +{ + using ActionList = QList; + ActionList actions; + // 1) Standard public extension + QExtensionManager *em = fw->core()->extensionManager(); + if (const QDesignerTaskMenuExtension *extTaskMenu = qt_extension(em, o)) + actions += extTaskMenu->taskActions(); + if (const auto *intTaskMenu = qobject_cast(em->extension(o, u"QDesignerInternalTaskMenuExtension"_s))) { + if (!actions.isEmpty()) { + QAction *a = new QAction(fw); + a->setSeparator(true); + actions.push_back(a); + } + actions += intTaskMenu->taskActions(); + } + if (actions.isEmpty()) + return nullptr; + if (trailingSeparator && !actions.constLast()->isSeparator()) { + QAction *a = new QAction(fw); + a->setSeparator(true); + actions.push_back(a); + } + QMenu *rc = new QMenu; + for (auto *a : std::as_const(actions)) + rc->addAction(a); + return rc; +} + +void FormWindowBase::emitObjectRemoved(QObject *o) +{ + emit objectRemoved(o); +} + +DeviceProfile FormWindowBase::deviceProfile() const +{ + return m_d->m_deviceProfile; +} + +QString FormWindowBase::styleName() const +{ + return m_d->m_deviceProfile.isEmpty() ? QString() : m_d->m_deviceProfile.style(); +} + +void FormWindowBase::emitWidgetRemoved(QWidget *w) +{ + emit widgetRemoved(w); +} + +QString FormWindowBase::deviceProfileName() const +{ + return m_d->m_deviceProfile.isEmpty() ? QString() : m_d->m_deviceProfile.name(); +} + +void FormWindowBase::setLineTerminatorMode(FormWindowBase::LineTerminatorMode mode) +{ + m_d->m_lineTerminatorMode = mode; +} + +FormWindowBase::LineTerminatorMode FormWindowBase::lineTerminatorMode() const +{ + return m_d->m_lineTerminatorMode; +} + +void FormWindowBase::triggerDefaultAction(QWidget *widget) +{ + if (QAction *action = qdesigner_internal::preferredEditAction(core(), widget)) + QTimer::singleShot(0, action, &QAction::trigger); +} + +bool FormWindowBase::useIdBasedTranslations() const +{ + return m_d->m_useIdBasedTranslations; +} + +void FormWindowBase::setUseIdBasedTranslations(bool v) +{ + m_d->m_useIdBasedTranslations = v; +} + +bool FormWindowBase::connectSlotsByName() const +{ + return m_d->m_connectSlotsByName; +} + +void FormWindowBase::setConnectSlotsByName(bool v) +{ + m_d->m_connectSlotsByName = v; +} + +QStringList FormWindowBase::checkContents() const +{ + if (!mainContainer()) + return QStringList(tr("Invalid form")); + // Test for non-laid toplevel spacers, which will not be saved + // as not to throw off uic. + QStringList problems; + const auto &spacers = mainContainer()->findChildren(); + for (const Spacer *spacer : spacers) { + if (spacer->parentWidget() && !spacer->parentWidget()->layout()) { + problems.push_back(tr("

This file contains top level spacers.
" + "They will not be saved.

" + "Perhaps you forgot to create a layout?

")); + break; + } + } + return problems; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/formwindowbase_p.h b/src/tools/designer/src/lib/shared/formwindowbase_p.h new file mode 100644 index 00000000000..77fd7b8c8e4 --- /dev/null +++ b/src/tools/designer/src/lib/shared/formwindowbase_p.h @@ -0,0 +1,165 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef FORMWINDOWBASE_H +#define FORMWINDOWBASE_H + +#include "shared_global_p.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerDnDItemInterface; +class QMenu; +class QtResourceSet; +class QDesignerPropertySheet; + +namespace qdesigner_internal { + +class QEditorFormBuilder; +class DeviceProfile; +class Grid; + +class DesignerPixmapCache; +class DesignerIconCache; +class FormWindowBasePrivate; + +class QDESIGNER_SHARED_EXPORT FormWindowBase: public QDesignerFormWindowInterface +{ + Q_OBJECT +public: + enum HighlightMode { Restore, Highlight }; + + explicit FormWindowBase(QDesignerFormEditorInterface *core, QWidget *parent = nullptr, + Qt::WindowFlags flags = {}); + ~FormWindowBase() override; + + QVariantMap formData(); + void setFormData(const QVariantMap &vm); + + QStringList checkContents() const override; + + // Deprecated + QPoint grid() const override; + + // Deprecated + void setGrid(const QPoint &grid) override; + + bool hasFeature(Feature f) const override; + Feature features() const override; + void setFeatures(Feature f) override; + + const Grid &designerGrid() const; + void setDesignerGrid(const Grid& grid); + + bool hasFormGrid() const; + void setHasFormGrid(bool b); + + bool gridVisible() const; + + ResourceFileSaveMode resourceFileSaveMode() const override; + void setResourceFileSaveMode(ResourceFileSaveMode behavior) override; + + static const Grid &defaultDesignerGrid(); + static void setDefaultDesignerGrid(const Grid& grid); + + // Overwrite to initialize and return a full popup menu for a managed widget + virtual QMenu *initializePopupMenu(QWidget *managedWidget); + // Helper to create a basic popup menu from task menu extensions (internal/public) + static QMenu *createExtensionTaskMenu(QDesignerFormWindowInterface *fw, QObject *o, bool trailingSeparator = true); + + virtual bool dropWidgets(const QList &item_list, QWidget *target, + const QPoint &global_mouse_pos) = 0; + + // Helper to find the widget at the mouse position with some flags. + enum WidgetUnderMouseMode { FindSingleSelectionDropTarget, FindMultiSelectionDropTarget }; + QWidget *widgetUnderMouse(const QPoint &formPos, WidgetUnderMouseMode m); + + virtual QWidget *widgetAt(const QPoint &pos) = 0; + virtual QWidget *findContainer(QWidget *w, bool excludeLayout) const = 0; + + void deleteWidgetList(const QWidgetList &widget_list); + + virtual void highlightWidget(QWidget *w, const QPoint &pos, HighlightMode mode = Highlight) = 0; + + enum PasteMode { PasteAll, PasteActionsOnly }; +#if QT_CONFIG(clipboard) + virtual void paste(PasteMode pasteMode) = 0; +#endif + + // Factory method to create a form builder + virtual QEditorFormBuilder *createFormBuilder() = 0; + + virtual bool blockSelectionChanged(bool blocked) = 0; + + DesignerPixmapCache *pixmapCache() const; + DesignerIconCache *iconCache() const; + QtResourceSet *resourceSet() const override; + void setResourceSet(QtResourceSet *resourceSet) override; + void addReloadableProperty(QDesignerPropertySheet *sheet, int index); + void removeReloadableProperty(QDesignerPropertySheet *sheet, int index); + void addReloadablePropertySheet(QDesignerPropertySheet *sheet, QObject *object); + void reloadProperties(); + + void emitWidgetRemoved(QWidget *w); + void emitObjectRemoved(QObject *o); + + DeviceProfile deviceProfile() const; + QString styleName() const; + QString deviceProfileName() const; + + enum LineTerminatorMode { + LFLineTerminator, + CRLFLineTerminator, + NativeLineTerminator = +#if defined (Q_OS_WIN) + CRLFLineTerminator +#else + LFLineTerminator +#endif + }; + + void setLineTerminatorMode(LineTerminatorMode mode); + LineTerminatorMode lineTerminatorMode() const; + + bool useIdBasedTranslations() const; + void setUseIdBasedTranslations(bool v); + + bool connectSlotsByName() const; + void setConnectSlotsByName(bool v); + +public slots: + void resourceSetActivated(QtResourceSet *resourceSet, bool resourceSetChanged); + +private slots: + void triggerDefaultAction(QWidget *w); + void sheetDestroyed(QObject *object); + +private: + void syncGridFeature(); + void connectSheet(QDesignerPropertySheet *sheet); + void disconnectSheet(QDesignerPropertySheet *sheet); + + FormWindowBasePrivate *m_d; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // FORMWINDOWBASE_H diff --git a/src/tools/designer/src/lib/shared/grid.cpp b/src/tools/designer/src/lib/shared/grid.cpp new file mode 100644 index 00000000000..f4906a113f1 --- /dev/null +++ b/src/tools/designer/src/lib/shared/grid.cpp @@ -0,0 +1,152 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "grid_p.h" + +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +static const bool defaultSnap = true; +static const bool defaultVisible = true; +static const int DEFAULT_GRID = 10; +static const char* KEY_VISIBLE = "gridVisible"; +static const char* KEY_SNAPX = "gridSnapX"; +static const char* KEY_SNAPY = "gridSnapY"; +static const char* KEY_DELTAX = "gridDeltaX"; +static const char* KEY_DELTAY = "gridDeltaY"; + +// Insert a value into the serialization map unless default +template + static inline void valueToVariantMap(T value, T defaultValue, const QString &key, QVariantMap &v, bool forceKey) { + if (forceKey || value != defaultValue) + v.insert(key, QVariant(value)); + } + +// Obtain a value form QVariantMap +template + static inline bool valueFromVariantMap(const QVariantMap &v, const QString &key, T &value) { + const auto it = v.constFind(key); + const bool found = it != v.constEnd(); + if (found) + value = qvariant_cast(it.value()); + return found; + } + +namespace qdesigner_internal +{ + +Grid::Grid() : + m_visible(defaultVisible), + m_snapX(defaultSnap), + m_snapY(defaultSnap), + m_deltaX(DEFAULT_GRID), + m_deltaY(DEFAULT_GRID) +{ +} + +bool Grid::fromVariantMap(const QVariantMap& vm) +{ + Grid grid; + bool anyData = valueFromVariantMap(vm, QLatin1StringView(KEY_VISIBLE), grid.m_visible); + anyData |= valueFromVariantMap(vm, QLatin1StringView(KEY_SNAPX), grid.m_snapX); + anyData |= valueFromVariantMap(vm, QLatin1StringView(KEY_SNAPY), grid.m_snapY); + anyData |= valueFromVariantMap(vm, QLatin1StringView(KEY_DELTAX), grid.m_deltaX); + anyData |= valueFromVariantMap(vm, QLatin1StringView(KEY_DELTAY), grid.m_deltaY); + if (!anyData) + return false; + if (grid.m_deltaX == 0 || grid.m_deltaY == 0) { + qWarning("Attempt to set invalid grid with a spacing of 0."); + return false; + } + *this = grid; + return true; +} + +QVariantMap Grid::toVariantMap(bool forceKeys) const +{ + QVariantMap rc; + addToVariantMap(rc, forceKeys); + return rc; +} + +void Grid::addToVariantMap(QVariantMap& vm, bool forceKeys) const +{ + valueToVariantMap(m_visible, defaultVisible, QLatin1StringView(KEY_VISIBLE), vm, forceKeys); + valueToVariantMap(m_snapX, defaultSnap, QLatin1StringView(KEY_SNAPX), vm, forceKeys); + valueToVariantMap(m_snapY, defaultSnap, QLatin1StringView(KEY_SNAPY), vm, forceKeys); + valueToVariantMap(m_deltaX, DEFAULT_GRID, QLatin1StringView(KEY_DELTAX), vm, forceKeys); + valueToVariantMap(m_deltaY, DEFAULT_GRID, QLatin1StringView(KEY_DELTAY), vm, forceKeys); +} + +void Grid::paint(QWidget *widget, QPaintEvent *e) const +{ + QPainter p(widget); + paint(p, widget, e); +} + +void Grid::paint(QPainter &p, const QWidget *widget, QPaintEvent *e) const +{ + p.setPen(widget->palette().dark().color()); + + if (m_visible) { + const int xstart = (e->rect().x() / m_deltaX) * m_deltaX; + const int ystart = (e->rect().y() / m_deltaY) * m_deltaY; + + const int xend = e->rect().right(); + const int yend = e->rect().bottom(); + + using Points = QList; + static Points points; + points.clear(); + + for (int x = xstart; x <= xend; x += m_deltaX) { + points.reserve((yend - ystart) / m_deltaY + 1); + for (int y = ystart; y <= yend; y += m_deltaY) + points.push_back(QPointF(x, y)); + p.drawPoints( &(*points.begin()), points.size()); + points.clear(); + } + } +} + +int Grid::snapValue(int value, int grid) const +{ + const int rest = value % grid; + const int absRest = (rest < 0) ? -rest : rest; + int offset = 0; + if (2 * absRest > grid) + offset = 1; + if (rest < 0) + offset *= -1; + return (value / grid + offset) * grid; +} + +QPoint Grid::snapPoint(const QPoint &p) const +{ + const int sx = m_snapX ? snapValue(p.x(), m_deltaX) : p.x(); + const int sy = m_snapY ? snapValue(p.y(), m_deltaY) : p.y(); + return QPoint(sx, sy); +} + +int Grid::widgetHandleAdjustX(int x) const +{ + return m_snapX ? (x / m_deltaX) * m_deltaX + 1 : x; +} + +int Grid::widgetHandleAdjustY(int y) const +{ + return m_snapY ? (y / m_deltaY) * m_deltaY + 1 : y; +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/grid_p.h b/src/tools/designer/src/lib/shared/grid_p.h new file mode 100644 index 00000000000..606732884da --- /dev/null +++ b/src/tools/designer/src/lib/shared/grid_p.h @@ -0,0 +1,85 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_GRID_H +#define QDESIGNER_GRID_H + +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QWidget; +class QPaintEvent; +class QPainter; + +namespace qdesigner_internal { + +// Designer grid which is able to serialize to QVariantMap +class QDESIGNER_SHARED_EXPORT Grid +{ +public: + Grid(); + + bool fromVariantMap(const QVariantMap& vm); + + void addToVariantMap(QVariantMap& vm, bool forceKeys = false) const; + QVariantMap toVariantMap(bool forceKeys = false) const; + + inline bool visible() const { return m_visible; } + void setVisible(bool visible) { m_visible = visible; } + + inline bool snapX() const { return m_snapX; } + void setSnapX(bool snap) { m_snapX = snap; } + + inline bool snapY() const { return m_snapY; } + void setSnapY(bool snap) { m_snapY = snap; } + + inline int deltaX() const { return m_deltaX; } + void setDeltaX(int dx) { m_deltaX = dx; } + + inline int deltaY() const { return m_deltaY; } + void setDeltaY(int dy) { m_deltaY = dy; } + + void paint(QWidget *widget, QPaintEvent *e) const; + void paint(QPainter &p, const QWidget *widget, QPaintEvent *e) const; + + QPoint snapPoint(const QPoint &p) const; + + int widgetHandleAdjustX(int x) const; + int widgetHandleAdjustY(int y) const; + +private: + friend bool comparesEqual(const Grid &lhs, const Grid &rhs) noexcept + { + return lhs.m_visible == rhs.m_visible + && lhs.m_snapX == rhs.m_snapX && lhs.m_snapY == rhs.m_snapY + && lhs.m_deltaX == rhs.m_deltaX && lhs.m_deltaY == rhs.m_deltaY; + } + Q_DECLARE_EQUALITY_COMPARABLE(Grid) + + int snapValue(int value, int grid) const; + bool m_visible; + bool m_snapX; + bool m_snapY; + int m_deltaX; + int m_deltaY; +}; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_GRID_H diff --git a/src/tools/designer/src/lib/shared/gridpanel.cpp b/src/tools/designer/src/lib/shared/gridpanel.cpp new file mode 100644 index 00000000000..a5f6ae65487 --- /dev/null +++ b/src/tools/designer/src/lib/shared/gridpanel.cpp @@ -0,0 +1,83 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "gridpanel_p.h" +#include "ui_gridpanel.h" +#include "grid_p.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +GridPanel::GridPanel(QWidget *parentWidget) : + QWidget(parentWidget) +{ + m_ui = new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::GridPanel; + m_ui->setupUi(this); + + connect(m_ui->m_resetButton, &QAbstractButton::clicked, this, &GridPanel::reset); +} + +GridPanel::~GridPanel() +{ + delete m_ui; +} + +void GridPanel::setGrid(const Grid &g) +{ + m_ui->m_deltaXSpinBox->setValue(g.deltaX()); + m_ui->m_deltaYSpinBox->setValue(g.deltaY()); + m_ui->m_visibleCheckBox->setCheckState(g.visible() ? Qt::Checked : Qt::Unchecked); + m_ui->m_snapXCheckBox->setCheckState(g.snapX() ? Qt::Checked : Qt::Unchecked); + m_ui->m_snapYCheckBox->setCheckState(g.snapY() ? Qt::Checked : Qt::Unchecked); +} + +void GridPanel::setTitle(const QString &title) +{ + m_ui->m_gridGroupBox->setTitle(title); +} + +Grid GridPanel::grid() const +{ + Grid rc; + rc.setDeltaX(m_ui->m_deltaXSpinBox->value()); + rc.setDeltaY(m_ui->m_deltaYSpinBox->value()); + rc.setSnapX(m_ui->m_snapXCheckBox->checkState() == Qt::Checked); + rc.setSnapY(m_ui->m_snapYCheckBox->checkState() == Qt::Checked); + rc.setVisible(m_ui->m_visibleCheckBox->checkState() == Qt::Checked); + return rc; +} + +void GridPanel::reset() +{ + setGrid(Grid()); +} + +void GridPanel::setCheckable (bool c) +{ + m_ui->m_gridGroupBox->setCheckable(c); +} + +bool GridPanel::isCheckable () const +{ + return m_ui->m_gridGroupBox->isCheckable (); +} + +bool GridPanel::isChecked () const +{ + return m_ui->m_gridGroupBox->isChecked (); +} + +void GridPanel::setChecked(bool c) +{ + m_ui->m_gridGroupBox->setChecked(c); +} + +void GridPanel::setResetButtonVisible(bool v) +{ + m_ui->m_resetButton->setVisible(v); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/gridpanel.ui b/src/tools/designer/src/lib/shared/gridpanel.ui new file mode 100644 index 00000000000..adfdd3684a5 --- /dev/null +++ b/src/tools/designer/src/lib/shared/gridpanel.ui @@ -0,0 +1,144 @@ + + qdesigner_internal::GridPanel + + + + 0 + 0 + 393 + 110 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Grid + + + + + + + 0 + 0 + + + + Visible + + + + + + + Grid &X + + + m_deltaXSpinBox + + + + + + + 2 + + + 100 + + + + + + + + 0 + 0 + + + + Snap + + + + + + + + + Reset + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + + Grid &Y + + + m_deltaYSpinBox + + + + + + + 2 + + + 100 + + + + + + + + 0 + 0 + + + + Snap + + + + + + + + + + + diff --git a/src/tools/designer/src/lib/shared/gridpanel_p.h b/src/tools/designer/src/lib/shared/gridpanel_p.h new file mode 100644 index 00000000000..000a250ad42 --- /dev/null +++ b/src/tools/designer/src/lib/shared/gridpanel_p.h @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of the Qt tools. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef GRIDPANEL_H +#define GRIDPANEL_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class Grid; + +namespace Ui { + class GridPanel; +} + +class QDESIGNER_SHARED_EXPORT GridPanel : public QWidget +{ + Q_OBJECT +public: + GridPanel(QWidget *parent = nullptr); + ~GridPanel(); + + void setTitle(const QString &title); + + void setGrid(const Grid &g); + Grid grid() const; + + void setCheckable (bool c); + bool isCheckable () const; + + bool isChecked () const; + void setChecked(bool c); + + void setResetButtonVisible(bool v); + +private slots: + void reset(); + +private: + Ui::GridPanel *m_ui; +}; + +} // qdesigner_internal + +QT_END_NAMESPACE + +#endif // GRIDPANEL_H diff --git a/src/tools/designer/src/lib/shared/htmlhighlighter.cpp b/src/tools/designer/src/lib/shared/htmlhighlighter.cpp new file mode 100644 index 00000000000..59e2a5efa00 --- /dev/null +++ b/src/tools/designer/src/lib/shared/htmlhighlighter.cpp @@ -0,0 +1,153 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include +#include + +#include "htmlhighlighter_p.h" + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +HtmlHighlighter::HtmlHighlighter(QTextEdit *textEdit) + : QSyntaxHighlighter(textEdit->document()) +{ + QTextCharFormat entityFormat; + entityFormat.setForeground(Qt::red); + setFormatFor(Entity, entityFormat); + + QTextCharFormat tagFormat; + tagFormat.setForeground(Qt::darkMagenta); + tagFormat.setFontWeight(QFont::Bold); + setFormatFor(Tag, tagFormat); + + QTextCharFormat commentFormat; + commentFormat.setForeground(Qt::gray); + commentFormat.setFontItalic(true); + setFormatFor(Comment, commentFormat); + + QTextCharFormat attributeFormat; + attributeFormat.setForeground(Qt::black); + attributeFormat.setFontWeight(QFont::Bold); + setFormatFor(Attribute, attributeFormat); + + QTextCharFormat valueFormat; + valueFormat.setForeground(Qt::blue); + setFormatFor(Value, valueFormat); +} + +void HtmlHighlighter::setFormatFor(Construct construct, + const QTextCharFormat &format) +{ + m_formats[construct] = format; + rehighlight(); +} + +void HtmlHighlighter::highlightBlock(const QString &text) +{ + static const QChar tab = u'\t'; + static const QChar space = u' '; + + int state = previousBlockState(); + qsizetype len = text.size(); + qsizetype start = 0; + qsizetype pos = 0; + + while (pos < len) { + switch (state) { + case NormalState: + default: + while (pos < len) { + QChar ch = text.at(pos); + if (ch == u'<') { + if (QStringView{text}.sliced(pos).startsWith(""_L1)) { + pos += 3; + state = NormalState; + break; + } + } + setFormat(start, pos - start, m_formats[Comment]); + break; + case InTag: + QChar quote = QChar::Null; + while (pos < len) { + QChar ch = text.at(pos); + if (quote.isNull()) { + start = pos; + if (ch == '\''_L1 || ch == u'"') { + quote = ch; + } else if (ch == u'>') { + ++pos; + setFormat(start, pos - start, m_formats[Tag]); + state = NormalState; + break; + } else if (QStringView{text}.sliced(pos).startsWith("/>"_L1)) { + pos += 2; + setFormat(start, pos - start, m_formats[Tag]); + state = NormalState; + break; + } else if (ch != space && text.at(pos) != tab) { + // Tag not ending, not a quote and no whitespace, so + // we must be dealing with an attribute. + ++pos; + while (pos < len && text.at(pos) != space + && text.at(pos) != tab + && text.at(pos) != u'=') + ++pos; + setFormat(start, pos - start, m_formats[Attribute]); + start = pos; + } + } else if (ch == quote) { + quote = QChar::Null; + + // Anything quoted is a value + setFormat(start, pos - start, m_formats[Value]); + } + ++pos; + } + break; + } + } + setCurrentBlockState(state); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/htmlhighlighter_p.h b/src/tools/designer/src/lib/shared/htmlhighlighter_p.h new file mode 100644 index 00000000000..e86bfce97b3 --- /dev/null +++ b/src/tools/designer/src/lib/shared/htmlhighlighter_p.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef HTMLHIGHLIGHTER_H +#define HTMLHIGHLIGHTER_H + +#include + +QT_BEGIN_NAMESPACE + +class QTextEdit; + +namespace qdesigner_internal { + +/* HTML syntax highlighter based on Qt Quarterly example */ +class HtmlHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + enum Construct { + Entity, + Tag, + Comment, + Attribute, + Value, + LastConstruct = Value + }; + + HtmlHighlighter(QTextEdit *textEdit); + + void setFormatFor(Construct construct, const QTextCharFormat &format); + + QTextCharFormat formatFor(Construct construct) const + { return m_formats[construct]; } + +protected: + enum State { + NormalState = -1, + InComment, + InTag + }; + + void highlightBlock(const QString &text) override; + +private: + QTextCharFormat m_formats[LastConstruct + 1]; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // HTMLHIGHLIGHTER_H diff --git a/src/tools/designer/src/lib/shared/icon-naming-spec.txt b/src/tools/designer/src/lib/shared/icon-naming-spec.txt new file mode 100644 index 00000000000..e9b85476949 --- /dev/null +++ b/src/tools/designer/src/lib/shared/icon-naming-spec.txt @@ -0,0 +1,309 @@ +# https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html + +# Table 2. Standard Action Icons +address-book-new +application-exit +appointment-new +call-start +call-stop +contact-new +document-new +document-open +document-open-recent +document-page-setup +document-print +document-print-preview +document-properties +document-revert +document-save +document-save-as +document-send +edit-clear +edit-copy +edit-cut +edit-delete +edit-find +edit-find-replace +edit-paste +edit-redo +edit-select-all +edit-undo +folder-new +format-indent-less +format-indent-more +format-justify-center +format-justify-fill +format-justify-left +format-justify-right +format-text-direction-ltr +format-text-direction-rtl +format-text-bold +format-text-italic +format-text-underline +format-text-strikethrough +go-bottom +go-down +go-first +go-home +go-jump +go-last +go-next +go-previous +go-top +go-up +help-about +help-contents +help-faq +insert-image +insert-link +insert-object +insert-text +list-add +list-remove +mail-forward +mail-mark-important +mail-mark-junk +mail-mark-notjunk +mail-mark-read +mail-mark-unread +mail-message-new +mail-reply-all +mail-reply-sender +mail-send +mail-send-receive +media-eject +media-playback-pause +media-playback-start +media-playback-stop +media-record +media-seek-backward +media-seek-forward +media-skip-backward +media-skip-forward +object-flip-horizontal +object-flip-vertical +object-rotate-left +object-rotate-right +process-stop +system-lock-screen +system-log-out +system-run +system-search +system-reboot +system-shutdown +tools-check-spelling +view-fullscreen +view-refresh +view-restore +view-sort-ascending +view-sort-descending +window-close +window-new +zoom-fit-best +zoom-in +zoom-original +zoom-out + +# Table 3. Standard Animation Icons +process-working + +# Table 4. Standard Application Icons +accessories-calculator +accessories-character-map +accessories-dictionary +accessories-text-editor +help-browser +multimedia-volume-control +preferences-desktop-accessibility +preferences-desktop-font +preferences-desktop-keyboard +preferences-desktop-locale +preferences-desktop-multimedia +preferences-desktop-screensaver +preferences-desktop-theme +preferences-desktop-wallpaper +system-file-manager +system-software-install +system-software-update +utilities-system-monitor +utilities-terminal + +# Table 5. Standard Category Icons +applications-accessories +applications-development +applications-engineering +applications-games +applications-graphics +applications-internet +applications-multimedia +applications-office +applications-other +applications-science +applications-system +applications-utilities +preferences-desktop +preferences-desktop-peripherals +preferences-desktop-personal +preferences-other +preferences-system +preferences-system-network +system-help + +# Table 6. Standard Device Icons +audio-card +audio-input-microphone +battery +camera-photo +camera-video +camera-web +computer +drive-harddisk +drive-optical +drive-removable-media +input-gaming +input-keyboard +input-mouse +input-tablet +media-flash +media-floppy +media-optical +media-tape +modem +multimedia-player +network-wired +network-wireless +pda +phone +printer +scanner +video-display + +# Table 7. Standard Emblem Icons +emblem-default +emblem-documents +emblem-downloads +emblem-favorite +emblem-important +emblem-mail +emblem-photos +emblem-readonly +emblem-shared +emblem-symbolic-link +emblem-synchronized +emblem-system +emblem-unreadable + +# Table 8. Standard Emotion Icons +face-angel +face-angry +face-cool +face-crying +face-devilish +face-embarrassed +face-kiss +face-laugh +face-monkey +face-plain +face-raspberry +face-sad +face-sick +face-smile +face-smile-big +face-smirk +face-surprise +face-tired +face-uncertain +face-wink +face-worried + +# Table 9. Standard International Icons +flag-aa + +# Table 10. Standard MIME Type Icons +application-x-executable +audio-x-generic +font-x-generic +image-x-generic +package-x-generic +text-html +text-x-generic +text-x-generic-template +text-x-script +video-x-generic +x-office-address-book +x-office-calendar +x-office-document +x-office-presentation +x-office-spreadsheet + +# Table 11. Standard Place Icons +folder +folder-remote +network-server +network-workgroup +start-here +user-bookmarks +user-desktop +user-home +user-trash + +# Table 12. Standard Status Icons +appointment-missed +appointment-soon +audio-volume-high +audio-volume-low +audio-volume-medium +audio-volume-muted +battery-caution +battery-low +dialog-error +dialog-information +dialog-password +dialog-question +dialog-warning +folder-drag-accept +folder-open +folder-visiting +image-loading +image-missing +mail-attachment +mail-unread +mail-read +mail-replied +mail-signed +mail-signed-verified +media-playlist-repeat +media-playlist-shuffle +network-error +network-idle +network-offline +network-receive +network-transmit +network-transmit-receive +printer-error +printer-printing +security-high +security-medium +security-low +software-update-available +software-update-urgent +sync-error +sync-synchronizing +task-due +task-past-due +user-available +user-away +user-idle +user-offline +user-trash-full +weather-clear +weather-clear-night +weather-few-clouds +weather-few-clouds-night +weather-fog +weather-overcast +weather-severe-alert +weather-showers +weather-showers-scattered +weather-snow +weather-storm diff --git a/src/tools/designer/src/lib/shared/iconloader.cpp b/src/tools/designer/src/lib/shared/iconloader.cpp new file mode 100644 index 00000000000..ecb97ba2b66 --- /dev/null +++ b/src/tools/designer/src/lib/shared/iconloader.cpp @@ -0,0 +1,86 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "iconloader_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QIcon::ThemeIcon themeIcon, + QLatin1StringView name) +{ + return QOperatingSystemVersion::currentType() != QOperatingSystemVersion::MacOS + && QIcon::hasThemeIcon(themeIcon) + ? QIcon::fromTheme(themeIcon) : createIconSet(name); +} + +template +static inline QIcon createIconSetHelper(StringView name) +{ + constexpr QLatin1StringView prefixes[] = { + ":/qt-project.org/formeditor/images/"_L1, +#ifdef Q_OS_MACOS + ":/qt-project.org/formeditor/images/mac/"_L1, +#else + ":/qt-project.org/formeditor/images/win/"_L1, +#endif + ":/qt-project.org/formeditor/images/designer_"_L1 + }; + + for (QLatin1StringView prefix : prefixes) { + const QString f = prefix + name; + if (QFile::exists(f)) + return QIcon(f); + } + + return {}; +} + +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QStringView name) +{ + return createIconSetHelper(name); +} + +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QLatin1StringView name) +{ + return createIconSetHelper(name); +} + +QDESIGNER_SHARED_EXPORT QIcon emptyIcon() +{ + return QIcon(u":/qt-project.org/formeditor/images/emptyicon.png"_s); +} + +static QIcon buildIcon(const QString &prefix, const int *sizes, size_t sizeCount) +{ + QIcon result; + for (size_t i = 0; i < sizeCount; ++i) { + const QString size = QString::number(sizes[i]); + const QPixmap pixmap(prefix + size + 'x'_L1 + size + ".png"_L1); + Q_ASSERT(!pixmap.size().isEmpty()); + result.addPixmap(pixmap); + } + return result; +} + +QDESIGNER_SHARED_EXPORT QIcon qtLogoIcon() +{ + static const int sizes[] = {16, 24, 32, 64, 128}; + static const QIcon result = + buildIcon(u":/qt-project.org/formeditor/images/qtlogo"_s, + sizes, sizeof(sizes) / sizeof(sizes[0])); + return result; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + diff --git a/src/tools/designer/src/lib/shared/iconloader_p.h b/src/tools/designer/src/lib/shared/iconloader_p.h new file mode 100644 index 00000000000..c054d677c86 --- /dev/null +++ b/src/tools/designer/src/lib/shared/iconloader_p.h @@ -0,0 +1,40 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ICONLOADER_H +#define ICONLOADER_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QString; +class QIcon; + +namespace qdesigner_internal { + +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QStringView name); +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QLatin1StringView name); +QDESIGNER_SHARED_EXPORT QIcon createIconSet(QIcon::ThemeIcon themeIcon, + QLatin1StringView name); +QDESIGNER_SHARED_EXPORT QIcon emptyIcon(); +QDESIGNER_SHARED_EXPORT QIcon qtLogoIcon(); + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ICONLOADER_H diff --git a/src/tools/designer/src/lib/shared/iconselector.cpp b/src/tools/designer/src/lib/shared/iconselector.cpp new file mode 100644 index 00000000000..60c404423ce --- /dev/null +++ b/src/tools/designer/src/lib/shared/iconselector.cpp @@ -0,0 +1,651 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "iconselector_p.h" +#include "qdesigner_utils_p.h" +#include "qtresourcemodel_p.h" +#include "qtresourceview_p.h" +#include "iconloader_p.h" +#include "formwindowbase_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +using ThemeIconEnumEntry = std::pair; + +static const QList &themeEnumIcons() +{ + static QList result; + if (result.isEmpty()) { + const QStringList &names = QResourceBuilder::themeIconNames(); + result.reserve(names.size()); + for (qsizetype i = 0, size = names.size(); i < size; ++i) + result.append({names.at(i), QIcon::fromTheme(QIcon::ThemeIcon(i))}); + } + return result; +} + +static void initThemeCombo(QComboBox *cb) +{ + cb->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + + for (const auto &te : themeEnumIcons()) + cb->addItem(te.second, te.first); + + cb->setCurrentIndex(-1); +} + +// Validator for theme line edit, accepts empty or non-blank strings. +class BlankSuppressingValidator : public QValidator { +public: + explicit BlankSuppressingValidator(QObject * parent = nullptr) : QValidator(parent) {} + State validate(QString &input, int &pos) const override + { + const auto blankPos = input.indexOf(u' '); + if (blankPos != -1) { + pos = blankPos; + return Invalid; + } + return Acceptable; + } +}; + +// -------------------- LanguageResourceDialogPrivate +class LanguageResourceDialogPrivate { + LanguageResourceDialog *q_ptr; + Q_DECLARE_PUBLIC(LanguageResourceDialog) + +public: + LanguageResourceDialogPrivate(QDesignerResourceBrowserInterface *rb); + void init(LanguageResourceDialog *p); + + void setCurrentPath(const QString &filePath); + QString currentPath() const; + + void slotAccepted(); + void slotPathChanged(const QString &); + +private: + void setOkButtonEnabled(bool v) { m_dialogButtonBox->button(QDialogButtonBox::Ok)->setEnabled(v); } + static bool checkPath(const QString &p); + + QDesignerResourceBrowserInterface *m_browser; + QDialogButtonBox *m_dialogButtonBox; +}; + +LanguageResourceDialogPrivate::LanguageResourceDialogPrivate(QDesignerResourceBrowserInterface *rb) : + q_ptr(nullptr), + m_browser(rb), + m_dialogButtonBox(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)) +{ + setOkButtonEnabled(false); +} + +void LanguageResourceDialogPrivate::init(LanguageResourceDialog *p) +{ + q_ptr = p; + QLayout *layout = new QVBoxLayout(p); + layout->addWidget(m_browser); + layout->addWidget(m_dialogButtonBox); + QObject::connect(m_dialogButtonBox, &QDialogButtonBox::accepted, p, [this] { slotAccepted(); }); + QObject::connect(m_dialogButtonBox, &QDialogButtonBox::rejected, p, &QDialog::reject); + QObject::connect(m_browser, &QDesignerResourceBrowserInterface::currentPathChanged, + p, [this](const QString &fileName) { slotPathChanged(fileName); }); + QObject::connect(m_browser, &QDesignerResourceBrowserInterface::pathActivated, + p, [this] { slotAccepted(); }); + p->setModal(true); + p->setWindowTitle(LanguageResourceDialog::tr("Choose Resource")); + setOkButtonEnabled(false); +} + +void LanguageResourceDialogPrivate::setCurrentPath(const QString &filePath) +{ + m_browser->setCurrentPath(filePath); + setOkButtonEnabled(checkPath(filePath)); +} + +QString LanguageResourceDialogPrivate::currentPath() const +{ + return m_browser->currentPath(); +} + +bool LanguageResourceDialogPrivate::checkPath(const QString &p) +{ + return p.isEmpty() ? false : IconSelector::checkPixmap(p, IconSelector::CheckFast); +} + +void LanguageResourceDialogPrivate::slotAccepted() +{ + if (checkPath(currentPath())) + q_ptr->accept(); +} + +void LanguageResourceDialogPrivate::slotPathChanged(const QString &p) +{ + setOkButtonEnabled(checkPath(p)); +} + +// ------------ LanguageResourceDialog +LanguageResourceDialog::LanguageResourceDialog(QDesignerResourceBrowserInterface *rb, QWidget *parent) : + QDialog(parent), + d_ptr(new LanguageResourceDialogPrivate(rb)) +{ + d_ptr->init( this); +} + +LanguageResourceDialog::~LanguageResourceDialog() = default; + +void LanguageResourceDialog::setCurrentPath(const QString &filePath) +{ + d_ptr->setCurrentPath(filePath); +} + +QString LanguageResourceDialog::currentPath() const +{ + return d_ptr->currentPath(); +} + +LanguageResourceDialog* LanguageResourceDialog::create(QDesignerFormEditorInterface *core, QWidget *parent) +{ + if (QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core)) + if (QDesignerResourceBrowserInterface *rb = lang->createResourceBrowser(nullptr)) + return new LanguageResourceDialog(rb, parent); + if (QDesignerResourceBrowserInterface *rb = core->integration()->createResourceBrowser(nullptr)) + return new LanguageResourceDialog(rb, parent); + return nullptr; +} + +// ------------ IconSelectorPrivate + +struct QIconStateName +{ + std::pair state; + const char *name; +}; + +constexpr QIconStateName stateToName[] = { + {{QIcon::Normal, QIcon::Off}, QT_TRANSLATE_NOOP("IconSelector", "Normal Off")}, + {{QIcon::Normal, QIcon::On}, QT_TRANSLATE_NOOP("IconSelector", "Normal On")}, + {{QIcon::Disabled, QIcon::Off}, QT_TRANSLATE_NOOP("IconSelector", "Disabled Off")}, + {{QIcon::Disabled, QIcon::On}, QT_TRANSLATE_NOOP("IconSelector", "Disabled On")}, + {{QIcon::Active, QIcon::Off}, QT_TRANSLATE_NOOP("IconSelector", "Active Off")}, + {{QIcon::Active, QIcon::On}, QT_TRANSLATE_NOOP("IconSelector", "Active On")}, + {{QIcon::Selected, QIcon::Off}, QT_TRANSLATE_NOOP("IconSelector", "Selected Off")}, + {{QIcon::Selected, QIcon::On}, QT_TRANSLATE_NOOP("IconSelector", "Selected On")} +}; + +constexpr int stateToNameSize = int(sizeof(stateToName) / sizeof(stateToName[0])); + +class IconSelectorPrivate +{ + IconSelector *q_ptr = nullptr; + Q_DECLARE_PUBLIC(IconSelector) +public: + IconSelectorPrivate() = default; + + void slotStateActivated(); + void slotSetActivated(); + void slotSetResourceActivated(); + void slotSetFileActivated(); + void slotResetActivated(); + void slotResetAllActivated(); + void slotUpdate(); + + std::pair currentState() const + { + const int i = m_stateComboBox->currentIndex(); + return i >= 0 && i < stateToNameSize + ? stateToName[i].state : std::pair{}; + } + + const QIcon m_emptyIcon; + QComboBox *m_stateComboBox = nullptr; + QToolButton *m_iconButton = nullptr; + QAction *m_resetAction = nullptr; + QAction *m_resetAllAction = nullptr; + PropertySheetIconValue m_icon; + DesignerIconCache *m_iconCache = nullptr; + DesignerPixmapCache *m_pixmapCache = nullptr; + QtResourceModel *m_resourceModel = nullptr; + QDesignerFormEditorInterface *m_core = nullptr; +}; + +void IconSelectorPrivate::slotUpdate() +{ + QIcon icon; + if (m_iconCache) + icon = m_iconCache->icon(m_icon); + + const auto &paths = m_icon.paths(); + for (int index = 0; index < stateToNameSize; ++index) { + const auto &state = stateToName[index].state; + const PropertySheetPixmapValue pixmap = paths.value(state); + QIcon pixmapIcon = QIcon(icon.pixmap(16, 16, state.first, state.second)); + if (pixmapIcon.isNull()) + pixmapIcon = m_emptyIcon; + m_stateComboBox->setItemIcon(index, pixmapIcon); + QFont font = q_ptr->font(); + if (!pixmap.path().isEmpty()) + font.setBold(true); + m_stateComboBox->setItemData(index, font, Qt::FontRole); + } + + PropertySheetPixmapValue currentPixmap = paths.value(currentState()); + m_resetAction->setEnabled(!currentPixmap.path().isEmpty()); + m_resetAllAction->setEnabled(!paths.isEmpty()); + m_stateComboBox->update(); +} + +void IconSelectorPrivate::slotStateActivated() +{ + slotUpdate(); +} + +void IconSelectorPrivate::slotSetActivated() +{ + const auto state = currentState(); + const PropertySheetPixmapValue pixmap = m_icon.pixmap(state.first, state.second); + // Default to resource + const PropertySheetPixmapValue::PixmapSource ps = pixmap.path().isEmpty() ? PropertySheetPixmapValue::ResourcePixmap : pixmap.pixmapSource(m_core); + switch (ps) { + case PropertySheetPixmapValue::LanguageResourcePixmap: + case PropertySheetPixmapValue::ResourcePixmap: + slotSetResourceActivated(); + break; + case PropertySheetPixmapValue::FilePixmap: + slotSetFileActivated(); + break; + } +} + +// Choose a pixmap from resource; use language-dependent resource browser if present +QString IconSelector::choosePixmapResource(QDesignerFormEditorInterface *core, QtResourceModel *resourceModel, const QString &oldPath, QWidget *parent) +{ + Q_UNUSED(resourceModel); + QString rc; + + if (LanguageResourceDialog* ldlg = LanguageResourceDialog::create(core, parent)) { + ldlg->setCurrentPath(oldPath); + if (ldlg->exec() == QDialog::Accepted) + rc = ldlg->currentPath(); + delete ldlg; + } else { + QtResourceViewDialog dlg(core, parent); + dlg.setResourceEditingEnabled(core->integration()->hasFeature(QDesignerIntegration::ResourceEditorFeature)); + + dlg.selectResource(oldPath); + if (dlg.exec() == QDialog::Accepted) + rc = dlg.selectedResource(); + } + return rc; +} + +void IconSelectorPrivate::slotSetResourceActivated() +{ + const auto state = currentState(); + + PropertySheetPixmapValue pixmap = m_icon.pixmap(state.first, state.second); + const QString oldPath = pixmap.path(); + const QString newPath = IconSelector::choosePixmapResource(m_core, m_resourceModel, oldPath, q_ptr); + if (newPath.isEmpty() || newPath == oldPath) + return; + const PropertySheetPixmapValue newPixmap = PropertySheetPixmapValue(newPath); + if (newPixmap != pixmap) { + m_icon.setPixmap(state.first, state.second, newPixmap); + slotUpdate(); + emit q_ptr->iconChanged(m_icon); + } +} + +// Helpers for choosing image files: Check for valid image. +bool IconSelector::checkPixmap(const QString &fileName, CheckMode cm, QString *errorMessage) +{ + const QFileInfo fi(fileName); + if (!fi.exists() || !fi.isFile() || !fi.isReadable()) { + if (errorMessage) + *errorMessage = tr("The pixmap file '%1' cannot be read.").arg(fileName); + return false; + } + QImageReader reader(fileName); + if (!reader.canRead()) { + if (errorMessage) + *errorMessage = tr("The file '%1' does not appear to be a valid pixmap file: %2") + .arg(fileName, reader.errorString()); + return false; + } + if (cm == CheckFast) + return true; + + const QImage image = reader.read(); + if (image.isNull()) { + if (errorMessage) + *errorMessage = tr("The file '%1' could not be read: %2") + .arg(fileName, reader.errorString()); + return false; + } + return true; +} + +// Helpers for choosing image files: Return an image filter for QFileDialog, courtesy of StyledButton +static QString imageFilter() +{ + QString filter = QApplication::translate("IconSelector", "All Pixmaps ("); + const auto supportedImageFormats = QImageReader::supportedImageFormats(); + const qsizetype count = supportedImageFormats.size(); + for (qsizetype i = 0; i < count; ++i) { + if (i) + filter += u' '; + filter += "*."_L1; + const QString outputFormat = QString::fromUtf8(supportedImageFormats.at(i)); + if (outputFormat != "JPEG"_L1) + filter += outputFormat.toLower(); + else + filter += "jpg *.jpeg"_L1; + } + filter += u')'; + return filter; +} + +// Helpers for choosing image files: Choose a file +QString IconSelector::choosePixmapFile(const QString &directory, QDesignerDialogGuiInterface *dlgGui,QWidget *parent) +{ + QString errorMessage; + QString newPath; + do { + const QString title = tr("Choose a Pixmap"); + static const QString filter = imageFilter(); + newPath = dlgGui->getOpenImageFileName(parent, title, directory, filter); + if (newPath.isEmpty()) + break; + if (checkPixmap(newPath, CheckFully, &errorMessage)) + break; + dlgGui->message(parent, QDesignerDialogGuiInterface::ResourceEditorMessage, QMessageBox::Warning, tr("Pixmap Read Error"), errorMessage); + } while(true); + return newPath; +} + +void IconSelectorPrivate::slotSetFileActivated() +{ + const auto state = currentState(); + + PropertySheetPixmapValue pixmap = m_icon.pixmap(state.first, state.second); + const QString newPath = IconSelector::choosePixmapFile(pixmap.path(), m_core->dialogGui(), q_ptr); + if (!newPath.isEmpty()) { + const PropertySheetPixmapValue newPixmap = PropertySheetPixmapValue(newPath); + if (!(newPixmap == pixmap)) { + m_icon.setPixmap(state.first, state.second, newPixmap); + slotUpdate(); + emit q_ptr->iconChanged(m_icon); + } + } +} + +void IconSelectorPrivate::slotResetActivated() +{ + const auto state = currentState(); + + PropertySheetPixmapValue pixmap = m_icon.pixmap(state.first, state.second); + const PropertySheetPixmapValue newPixmap; + if (!(newPixmap == pixmap)) { + m_icon.setPixmap(state.first, state.second, newPixmap); + slotUpdate(); + emit q_ptr->iconChanged(m_icon); + } +} + +void IconSelectorPrivate::slotResetAllActivated() +{ + const PropertySheetIconValue newIcon; + if (!(m_icon == newIcon)) { + m_icon = newIcon; + slotUpdate(); + emit q_ptr->iconChanged(m_icon); + } +} + +// ------------- IconSelector +IconSelector::IconSelector(QWidget *parent) : + QWidget(parent), d_ptr(new IconSelectorPrivate()) +{ + d_ptr->q_ptr = this; + + d_ptr->m_stateComboBox = new QComboBox(this); + + QHBoxLayout *l = new QHBoxLayout(this); + d_ptr->m_iconButton = new QToolButton(this); + d_ptr->m_iconButton->setText(tr("...")); + d_ptr->m_iconButton->setPopupMode(QToolButton::MenuButtonPopup); + l->addWidget(d_ptr->m_stateComboBox); + l->addWidget(d_ptr->m_iconButton); + l->setContentsMargins(QMargins()); + + QMenu *setMenu = new QMenu(this); + + QAction *setResourceAction = new QAction(tr("Choose Resource..."), this); + QAction *setFileAction = new QAction(tr("Choose File..."), this); + d_ptr->m_resetAction = new QAction(tr("Reset"), this); + d_ptr->m_resetAllAction = new QAction(tr("Reset All"), this); + d_ptr->m_resetAction->setEnabled(false); + d_ptr->m_resetAllAction->setEnabled(false); + //d_ptr->m_resetAction->setIcon(createIconSet("resetproperty.png"_L1)); + + setMenu->addAction(setResourceAction); + setMenu->addAction(setFileAction); + setMenu->addSeparator(); + setMenu->addAction(d_ptr->m_resetAction); + setMenu->addAction(d_ptr->m_resetAllAction); + + for (const auto &item : stateToName) + d_ptr->m_stateComboBox->addItem(tr(item.name)); + + d_ptr->m_iconButton->setMenu(setMenu); + + connect(d_ptr->m_stateComboBox, &QComboBox::activated, + this, [this] { d_ptr->slotStateActivated(); }); + connect(d_ptr->m_iconButton, &QAbstractButton::clicked, + this, [this] { d_ptr->slotSetActivated(); }); + connect(setResourceAction, &QAction::triggered, + this, [this] { d_ptr->slotSetResourceActivated(); }); + connect(setFileAction, &QAction::triggered, + this, [this] { d_ptr->slotSetFileActivated(); }); + connect(d_ptr->m_resetAction, &QAction::triggered, + this, [this] { d_ptr->slotResetActivated(); }); + connect(d_ptr->m_resetAllAction, &QAction::triggered, + this, [this] { d_ptr->slotResetAllActivated(); }); + d_ptr->slotUpdate(); +} + +IconSelector::~IconSelector() = default; + +void IconSelector::setIcon(const PropertySheetIconValue &icon) +{ + if (d_ptr->m_icon == icon) + return; + + d_ptr->m_icon = icon; + d_ptr->slotUpdate(); +} + +PropertySheetIconValue IconSelector::icon() const +{ + return d_ptr->m_icon; +} + +void IconSelector::setFormEditor(QDesignerFormEditorInterface *core) +{ + d_ptr->m_core = core; + d_ptr->m_resourceModel = core->resourceModel(); + d_ptr->slotUpdate(); +} + +void IconSelector::setIconCache(DesignerIconCache *iconCache) +{ + d_ptr->m_iconCache = iconCache; + connect(iconCache, &DesignerIconCache::reloaded, this, [this] { d_ptr->slotUpdate(); }); + d_ptr->slotUpdate(); +} + +void IconSelector::setPixmapCache(DesignerPixmapCache *pixmapCache) +{ + d_ptr->m_pixmapCache = pixmapCache; + connect(pixmapCache, &DesignerPixmapCache::reloaded, this, [this] { d_ptr->slotUpdate(); }); + d_ptr->slotUpdate(); +} + +// --- IconThemeEditor + +static const QMap &themeIcons() +{ + static QMap result; + if (result.isEmpty()) { + QFile file(u":/qt-project.org/designer/icon-naming-spec.txt"_s); + if (file.open(QIODevice::ReadOnly)) { + while (!file.atEnd()) { + const auto line = file.readLine().trimmed(); + if (line.isEmpty() || line.startsWith('#')) + continue; + const auto iconName = QString::fromUtf8(line); + result.insert(iconName, QIcon::fromTheme(iconName)); + } + file.close(); + } + } + return result; +} + +struct IconThemeEditorPrivate { + void create(QWidget *topLevel, bool wantResetButton); + + QComboBox *m_themeComboBox{}; + QToolButton *m_themeResetButton{}; +}; + +void IconThemeEditorPrivate::create(QWidget *topLevel, bool wantResetButton) +{ + m_themeComboBox = new QComboBox(); + QHBoxLayout *mainHLayout = new QHBoxLayout(topLevel); + mainHLayout->setContentsMargins({}); + mainHLayout->addWidget(m_themeComboBox); + if (wantResetButton) { + m_themeResetButton = new QToolButton; + m_themeResetButton->setIcon(createIconSet("resetproperty.png"_L1)); + mainHLayout->addWidget(m_themeResetButton); + } + topLevel->setFocusProxy(m_themeComboBox); +} + +IconThemeEditor::IconThemeEditor(QWidget *parent, bool wantResetButton) : + QWidget (parent), d(new IconThemeEditorPrivate) +{ + d->create(this, wantResetButton); + d->m_themeComboBox->setEditable(true); + + const auto icons = themeIcons(); + for (auto i = icons.constBegin(); i != icons.constEnd(); ++i) + d->m_themeComboBox->addItem(i.value(), i.key()); + d->m_themeComboBox->setCurrentIndex(-1); + d->m_themeComboBox->lineEdit()->setValidator(new BlankSuppressingValidator(this)); + connect(d->m_themeComboBox, &QComboBox::currentTextChanged, this, &IconThemeEditor::edited); + if (wantResetButton) + connect(d->m_themeResetButton, &QAbstractButton::clicked, this, &IconThemeEditor::reset); +} + +IconThemeEditor::~IconThemeEditor() = default; + +void IconThemeEditor::reset() +{ + d->m_themeComboBox->setCurrentIndex(-1); + emit edited(QString()); +} + +QString IconThemeEditor::theme() const +{ + return d->m_themeComboBox->currentText(); +} + +void IconThemeEditor::setTheme(const QString &t) +{ + d->m_themeComboBox->setCurrentText(t); +} + +IconThemeEnumEditor::IconThemeEnumEditor(QWidget *parent, bool wantResetButton) : + QWidget (parent), d(new IconThemeEditorPrivate) +{ + d->create(this, wantResetButton); + initThemeCombo(d->m_themeComboBox); + + connect(d->m_themeComboBox, &QComboBox::currentIndexChanged, + this, &IconThemeEnumEditor::edited); + if (wantResetButton) + connect(d->m_themeResetButton, &QAbstractButton::clicked, this, &IconThemeEnumEditor::reset); +} + +IconThemeEnumEditor::~IconThemeEnumEditor() = default; + +void IconThemeEnumEditor::reset() +{ + d->m_themeComboBox->setCurrentIndex(-1); + emit edited(-1); +} + +int IconThemeEnumEditor::themeEnum() const +{ + return d->m_themeComboBox->currentIndex(); +} + +void IconThemeEnumEditor::setThemeEnum(int t) +{ + Q_ASSERT(t >= -1 && t < int(QIcon::ThemeIcon::NThemeIcons)); + d->m_themeComboBox->setCurrentIndex(t); +} + +QString IconThemeEnumEditor::iconName(int e) +{ + return QResourceBuilder::themeIconNames().value(e); +} + +QComboBox *IconThemeEnumEditor::createComboBox(QWidget *parent) +{ + auto *result = new QComboBox(parent); + initThemeCombo(result); + return result; +} + +} // qdesigner_internal + +QT_END_NAMESPACE + +#include "moc_iconselector_p.cpp" diff --git a/src/tools/designer/src/lib/shared/iconselector_p.h b/src/tools/designer/src/lib/shared/iconselector_p.h new file mode 100644 index 00000000000..4a4238e668c --- /dev/null +++ b/src/tools/designer/src/lib/shared/iconselector_p.h @@ -0,0 +1,146 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + + +#ifndef ICONSELECTOR_H +#define ICONSELECTOR_H + +#include "shared_global_p.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QComboBox; + +class QtResourceModel; +class QDesignerFormEditorInterface; +class QDesignerDialogGuiInterface; +class QDesignerResourceBrowserInterface; + +namespace qdesigner_internal { + +class DesignerIconCache; +class DesignerPixmapCache; +class PropertySheetIconValue; +struct IconThemeEditorPrivate; + +// Resource Dialog that embeds the language-dependent resource widget as returned by the language extension +class QDESIGNER_SHARED_EXPORT LanguageResourceDialog : public QDialog +{ + Q_OBJECT + + explicit LanguageResourceDialog(QDesignerResourceBrowserInterface *rb, QWidget *parent = nullptr); + +public: + ~LanguageResourceDialog() override; + // Factory: Returns 0 if the language extension does not provide a resource browser. + static LanguageResourceDialog* create(QDesignerFormEditorInterface *core, QWidget *parent); + + void setCurrentPath(const QString &filePath); + QString currentPath() const; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(LanguageResourceDialog) + Q_DISABLE_COPY_MOVE(LanguageResourceDialog) + +}; + +class QDESIGNER_SHARED_EXPORT IconSelector: public QWidget +{ + Q_OBJECT +public: + IconSelector(QWidget *parent = nullptr); + ~IconSelector() override; + + void setFormEditor(QDesignerFormEditorInterface *core); // required for dialog gui. + void setIconCache(DesignerIconCache *iconCache); + void setPixmapCache(DesignerPixmapCache *pixmapCache); + + void setIcon(const PropertySheetIconValue &icon); + PropertySheetIconValue icon() const; + + // Check whether a pixmap may be read + enum CheckMode { CheckFast, CheckFully }; + static bool checkPixmap(const QString &fileName, CheckMode cm = CheckFully, QString *errorMessage = nullptr); + // Choose a pixmap from file + static QString choosePixmapFile(const QString &directory, QDesignerDialogGuiInterface *dlgGui, QWidget *parent); + // Choose a pixmap from resource; use language-dependent resource browser if present + static QString choosePixmapResource(QDesignerFormEditorInterface *core, QtResourceModel *resourceModel, const QString &oldPath, QWidget *parent); + +signals: + void iconChanged(const PropertySheetIconValue &icon); +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(IconSelector) + Q_DISABLE_COPY_MOVE(IconSelector) +}; + +// IconThemeEditor: Let's the user input theme icon names and shows a preview label. +class QDESIGNER_SHARED_EXPORT IconThemeEditor : public QWidget +{ + Q_OBJECT + Q_PROPERTY(QString theme READ theme WRITE setTheme DESIGNABLE true) +public: + explicit IconThemeEditor(QWidget *parent = nullptr, bool wantResetButton = true); + ~IconThemeEditor() override; + + QString theme() const; + void setTheme(const QString &theme); + +signals: + void edited(const QString &); + +public slots: + void reset(); + +private: + QScopedPointer d; +}; + +// IconThemeEnumEditor: Let's the user input theme icon enum values +// (QIcon::ThemeIcon) and shows a preview label. -1 means nothing selected. +class QDESIGNER_SHARED_EXPORT IconThemeEnumEditor : public QWidget +{ + Q_OBJECT +public: + explicit IconThemeEnumEditor(QWidget *parent = nullptr, bool wantResetButton = true); + ~IconThemeEnumEditor() override; + + int themeEnum() const; + void setThemeEnum(int); + + static QString iconName(int e); + static QComboBox *createComboBox(QWidget *parent = nullptr); + +signals: + void edited(int); + +public slots: + void reset(); + +private: + QScopedPointer d; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ICONSELECTOR_H + diff --git a/src/tools/designer/src/lib/shared/invisible_widget.cpp b/src/tools/designer/src/lib/shared/invisible_widget.cpp new file mode 100644 index 00000000000..7fec394ba0b --- /dev/null +++ b/src/tools/designer/src/lib/shared/invisible_widget.cpp @@ -0,0 +1,19 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "invisible_widget_p.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +InvisibleWidget::InvisibleWidget(QWidget *parent) + : QWidget() +{ + setAttribute(Qt::WA_NoChildEventsForParent); + setParent(parent); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/invisible_widget_p.h b/src/tools/designer/src/lib/shared/invisible_widget_p.h new file mode 100644 index 00000000000..30a7970d4f1 --- /dev/null +++ b/src/tools/designer/src/lib/shared/invisible_widget_p.h @@ -0,0 +1,37 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef INVISIBLE_WIDGET_H +#define INVISIBLE_WIDGET_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT InvisibleWidget: public QWidget +{ + Q_OBJECT +public: + InvisibleWidget(QWidget *parent = nullptr); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // INVISIBLE_WIDGET_H diff --git a/src/tools/designer/src/lib/shared/layout.cpp b/src/tools/designer/src/lib/shared/layout.cpp new file mode 100644 index 00000000000..b35361a381e --- /dev/null +++ b/src/tools/designer/src/lib/shared/layout.cpp @@ -0,0 +1,1219 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "layout_p.h" +#include "layoutdecoration.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_widgetitem_p.h" +#include "qlayout_widget_p.h" +#include "spacer_widget_p.h" +#include "widgetfactory_p.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +/* The wizard has a policy of setting a size policy of its external children + * according to the page being expanding or not (in the latter case, the + * page will be pushed to the top). When setting/breaking layouts, this needs + * to be updated, which happens via a fake style change event. */ + +void updateWizardLayout(QWidget *layoutBase); + +class FriendlyWizardPage : public QWizardPage { + friend void updateWizardLayout(QWidget *); +}; + +void updateWizardLayout(QWidget *layoutBase) +{ + if (QWizardPage *wizardPage = qobject_cast(layoutBase)) + if (QWizard *wizard = static_cast(wizardPage)->wizard()) { + QEvent event(QEvent::StyleChange); + QApplication::sendEvent(wizard, &event); + } +} + +/*! + \class qdesigner_internal::Layout + \brief Baseclass for layouting widgets in the Designer (Helper for Layout commands) + \internal + + Classes derived from this abstract base class are used for layouting + operations in the Designer (creating/breaking layouts). + + Instances live in the Layout/BreakLayout commands. +*/ + +/*! \a p specifies the parent of the layoutBase \a lb. The parent + might be changed in setup(). If the layoutBase is a + container, the parent and the layoutBase are the same. Also they + always have to be a widget known to the designer (e.g. in the case + of the tabwidget parent and layoutBase are the tabwidget and not the + page which actually gets laid out. For actual usage the correct + widget is found later by Layout.) + */ + +Layout::Layout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, LayoutInfo::Type layoutType) : + m_widgets(wl), + m_parentWidget(p), + m_layoutBase(lb), + m_formWindow(fw), + m_layoutType(layoutType), + m_reparentLayoutWidget(true), + m_isBreak(false) +{ + if (m_layoutBase) + m_oldGeometry = m_layoutBase->geometry(); +} + +Layout::~Layout() = default; + +/*! The widget list we got in the constructor might contain too much + widgets (like widgets with different parents, already laid out + widgets, etc.). Here we set up the list and so the only the "best" + widgets get laid out. +*/ + +void Layout::setup() +{ + m_startPoint = QPoint(32767, 32767); + + // Go through all widgets of the list we got. As we can only + // layout widgets which have the same parent, we first do some + // sorting which means create a list for each parent containing + // its child here. After that we keep working on the list of + // children which has the most entries. + // Widgets which are already laid out are thrown away here too + + QMultiMap lists; + for (QWidget *w : std::as_const(m_widgets)) { + QWidget *p = w->parentWidget(); + + if (p && LayoutInfo::layoutType(m_formWindow->core(), p) != LayoutInfo::NoLayout + && m_formWindow->core()->metaDataBase()->item(p->layout()) != nullptr) + continue; + + lists.insert(p, w); + } + + QWidgetList lastList; + const QWidgetList &parents = lists.keys(); + for (QWidget *p : parents) { + if (lists.count(p) > lastList.size()) + lastList = lists.values(p); + } + + + // If we found no list (because no widget did fit at all) or the + // best list has only one entry and we do not layout a container, + // we leave here. + QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase(); + if (lastList.size() < 2 && + (!m_layoutBase || + (!widgetDataBase->isContainer(m_layoutBase, false) && + m_layoutBase != m_formWindow->mainContainer())) + ) { + m_widgets.clear(); + m_startPoint = QPoint(0, 0); + return; + } + + // Now we have a new and clean widget list, which makes sense + // to layout + m_widgets = lastList; + // Also use the only correct parent later, so store it + + Q_ASSERT(m_widgets.isEmpty() == false); + + m_parentWidget = m_formWindow->core()->widgetFactory()->widgetOfContainer(m_widgets.first()->parentWidget()); + // Now calculate the position where the layout-meta-widget should + // be placed and connect to widgetDestroyed() signals of the + // widgets to get informed if one gets deleted to be able to + // handle that and do not crash in this case + for (QWidget *w : std::as_const(m_widgets)) { + connect(w, &QObject::destroyed, this, &Layout::widgetDestroyed); + m_startPoint = QPoint(qMin(m_startPoint.x(), w->x()), qMin(m_startPoint.y(), w->y())); + const QRect rc(w->geometry()); + + m_geometries.insert(w, rc); + // Change the Z-order, as saving/loading uses the Z-order for + // writing/creating widgets and this has to be the same as in + // the layout. Else saving + loading will give different results + w->raise(); + } + + sort(); +} + +void Layout::widgetDestroyed() +{ + if (QWidget *w = qobject_cast(sender())) { + m_widgets.removeAt(m_widgets.indexOf(w)); + m_geometries.remove(w); + } +} + +bool Layout::prepareLayout(bool &needMove, bool &needReparent) +{ + for (QWidget *widget : std::as_const(m_widgets)) + widget->raise(); + + needMove = !m_layoutBase; + needReparent = needMove || (m_reparentLayoutWidget && qobject_cast(m_layoutBase)) || qobject_cast(m_layoutBase); + + QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory(); + QDesignerMetaDataBaseInterface *metaDataBase = m_formWindow->core()->metaDataBase(); + + if (m_layoutBase == nullptr) { + const bool useSplitter = m_layoutType == LayoutInfo::HSplitter || m_layoutType == LayoutInfo::VSplitter; + const QString baseWidgetClassName = useSplitter ? u"QSplitter"_s : u"QLayoutWidget"_s; + m_layoutBase = widgetFactory->createWidget(baseWidgetClassName, widgetFactory->containerOfWidget(m_parentWidget)); + if (useSplitter) { + m_layoutBase->setObjectName(u"splitter"_s); + m_formWindow->ensureUniqueObjectName(m_layoutBase); + } + } else { + LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase); + } + + metaDataBase->add(m_layoutBase); + + Q_ASSERT(m_layoutBase->layout() == nullptr || metaDataBase->item(m_layoutBase->layout()) == nullptr); + + return true; +} + +static bool isMainContainer(QDesignerFormWindowInterface *fw, const QWidget *w) +{ + return w && (w == fw || w == fw->mainContainer()); +} + +static bool isPageOfContainerWidget(QDesignerFormWindowInterface *fw, QWidget *widget) +{ + QDesignerContainerExtension *c = qt_extension( + fw->core()->extensionManager(), widget->parentWidget()); + + if (c != nullptr) { + for (int i = 0; icount(); ++i) { + if (widget == c->widget(i)) + return true; + } + } + + return false; +} +void Layout::finishLayout(bool needMove, QLayout *layout) +{ + if (m_parentWidget == m_layoutBase) { + QWidget *widget = m_layoutBase; + m_oldGeometry = widget->geometry(); + + bool done = false; + while (!isMainContainer(m_formWindow, widget) && !done) { + if (!m_formWindow->isManaged(widget)) { + widget = widget->parentWidget(); + continue; + } + if (LayoutInfo::isWidgetLaidout(m_formWindow->core(), widget)) { + widget = widget->parentWidget(); + continue; + } + if (isPageOfContainerWidget(m_formWindow, widget)) { + widget = widget->parentWidget(); + continue; + } + if (widget->parentWidget()) { + QScrollArea *area = qobject_cast(widget->parentWidget()->parentWidget()); + if (area && area->widget() == widget) { + widget = area; + continue; + } + } + + done = true; + } + updateWizardLayout(m_layoutBase); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + // We don't want to resize the form window + if (!Utils::isCentralWidget(m_formWindow, widget)) + widget->adjustSize(); + + return; + } + + if (needMove) + m_layoutBase->move(m_startPoint); + + const QRect g(m_layoutBase->pos(), m_layoutBase->size()); + + if (LayoutInfo::layoutType(m_formWindow->core(), m_layoutBase->parentWidget()) == LayoutInfo::NoLayout && !m_isBreak) + m_layoutBase->adjustSize(); + else if (m_isBreak) + m_layoutBase->setGeometry(m_oldGeometry); + + m_oldGeometry = g; + if (layout) + layout->invalidate(); + m_layoutBase->show(); + + if (qobject_cast(m_layoutBase) || qobject_cast(m_layoutBase)) { + m_formWindow->clearSelection(false); + m_formWindow->manageWidget(m_layoutBase); + m_formWindow->selectWidget(m_layoutBase); + } +} + +void Layout::undoLayout() +{ + if (m_widgets.isEmpty()) + return; + + m_formWindow->selectWidget(m_layoutBase, false); + + QDesignerWidgetFactoryInterface *widgetFactory = m_formWindow->core()->widgetFactory(); + for (auto it = m_geometries.cbegin(), end = m_geometries.cend(); it != end; ++it) { + if (!it.key()) + continue; + + QWidget* w = it.key(); + const QRect rc = it.value(); + + const bool showIt = w->isVisibleTo(m_formWindow); + QWidget *container = widgetFactory->containerOfWidget(m_parentWidget); + + // ### remove widget here + QWidget *parentWidget = w->parentWidget(); + QDesignerFormEditorInterface *core = m_formWindow->core(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), parentWidget); + + if (deco) + deco->removeWidget(w); + + w->setParent(container); + w->setGeometry(rc); + + if (showIt) + w->show(); + } + + LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase); + + if (m_parentWidget != m_layoutBase && !qobject_cast(m_layoutBase)) { + m_formWindow->unmanageWidget(m_layoutBase); + m_layoutBase->hide(); + } else { + QMainWindow *mw = qobject_cast(m_formWindow->mainContainer()); + if (m_layoutBase != m_formWindow->mainContainer() && + (!mw || mw->centralWidget() != m_layoutBase)) + m_layoutBase->setGeometry(m_oldGeometry); + } +} + +void Layout::breakLayout() +{ + QHash rects; + /* Store the geometry of the widgets. The idea is to give the user space + * to rearrange them, so, we do a adjustSize() on them, unless they want + * to grow (expanding widgets like QTextEdit), in which the geometry is + * preserved. Note that historically, geometries were re-applied + * only after breaking splitters. */ + for (QWidget *w : std::as_const(m_widgets)) { + const QRect geom = w->geometry(); + const QSize sizeHint = w->sizeHint(); + const bool restoreGeometry = sizeHint.isEmpty() || sizeHint.width() > geom.width() || sizeHint.height() > geom.height(); + rects.insert(w, restoreGeometry ? w->geometry() : QRect(geom.topLeft(), QSize())); + } + const QPoint m_layoutBasePos = m_layoutBase->pos(); + QDesignerWidgetDataBaseInterface *widgetDataBase = m_formWindow->core()->widgetDataBase(); + + LayoutInfo::deleteLayout(m_formWindow->core(), m_layoutBase); + + const bool needReparent = (m_reparentLayoutWidget && qobject_cast(m_layoutBase)) || + qobject_cast(m_layoutBase) || + (!widgetDataBase->isContainer(m_layoutBase, false) && + m_layoutBase != m_formWindow->mainContainer()); + const bool add = m_geometries.isEmpty(); + + for (auto it = rects.cbegin(), end = rects.cend(); it != end; ++it) { + QWidget *w = it.key(); + if (needReparent) { + w->setParent(m_layoutBase->parentWidget(), {}); + w->move(m_layoutBasePos + it.value().topLeft()); + w->show(); + } + + const QRect oldGeometry = it.value(); + if (oldGeometry.isEmpty()) { + w->adjustSize(); + } else { + w->resize(oldGeometry.size()); + } + + if (add) + m_geometries.insert(w, QRect(w->pos(), w->size())); + } + + if (needReparent) { + m_layoutBase->hide(); + m_parentWidget = m_layoutBase->parentWidget(); + m_formWindow->unmanageWidget(m_layoutBase); + } else { + m_parentWidget = m_layoutBase; + } + updateWizardLayout(m_layoutBase); + + if (!m_widgets.isEmpty() && m_widgets.first() && m_widgets.first()->isVisibleTo(m_formWindow)) + m_formWindow->selectWidget(m_widgets.first()); + else + m_formWindow->selectWidget(m_formWindow); +} + +static QString suggestLayoutName(const char *className) +{ + // Legacy + if (!qstrcmp(className, "QHBoxLayout")) + return u"horizontalLayout"_s; + if (!qstrcmp(className, "QVBoxLayout")) + return u"verticalLayout"_s; + if (!qstrcmp(className, "QGridLayout")) + return u"gridLayout"_s; + + return qtify(QString::fromUtf8(className)); +} +QLayout *Layout::createLayout(int type) +{ + Q_ASSERT(m_layoutType != LayoutInfo::HSplitter && m_layoutType != LayoutInfo::VSplitter); + QLayout *layout = m_formWindow->core()->widgetFactory()->createLayout(m_layoutBase, nullptr, type); + // set a name + layout->setObjectName(suggestLayoutName(layout->metaObject()->className())); + m_formWindow->ensureUniqueObjectName(layout); + // QLayoutWidget + QDesignerPropertySheetExtension *sheet = qt_extension(m_formWindow->core()->extensionManager(), layout); + if (sheet && qobject_cast(m_layoutBase)) { + sheet->setProperty(sheet->indexOf(u"leftMargin"_s), 0); + sheet->setProperty(sheet->indexOf(u"topMargin"_s), 0); + sheet->setProperty(sheet->indexOf(u"rightMargin"_s), 0); + sheet->setProperty(sheet->indexOf(u"bottomMargin"_s), 0); + } + return layout; +} + +void Layout::reparentToLayoutBase(QWidget *w) +{ + if (w->parent() != m_layoutBase) { + w->setParent(m_layoutBase, {}); + w->move(QPoint(0,0)); + } +} + +namespace { // within qdesigner_internal + +// ----- PositionSortPredicate: Predicate to be usable as LessThan function to sort widgets by position +class PositionSortPredicate { +public: + PositionSortPredicate(Qt::Orientation orientation) : m_orientation(orientation) {} + bool operator()(const QWidget* w1, const QWidget* w2) { + return m_orientation == Qt::Horizontal ? w1->x() < w2->x() : w1->y() < w2->y(); + } + private: + const Qt::Orientation m_orientation; +}; + +// -------- BoxLayout +class BoxLayout : public Layout +{ +public: + BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, + Qt::Orientation orientation); + + void doLayout() override; + void sort() override; + +private: + const Qt::Orientation m_orientation; +}; + +BoxLayout::BoxLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, + Qt::Orientation orientation) : + Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox), + m_orientation(orientation) +{ +} + +void BoxLayout::sort() +{ + QWidgetList wl = widgets(); + std::stable_sort(wl.begin(), wl.end(), PositionSortPredicate(m_orientation)); + setWidgets(wl); +} + +void BoxLayout::doLayout() +{ + bool needMove, needReparent; + if (!prepareLayout(needMove, needReparent)) + return; + + QBoxLayout *layout = static_cast(createLayout(m_orientation == Qt::Horizontal ? LayoutInfo::HBox : LayoutInfo::VBox)); + + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + + for (auto *w : widgets()) { + if (needReparent) + reparentToLayoutBase(w); + + if (const Spacer *spacer = qobject_cast(w)) + layout->addWidget(w, 0, spacer->alignment()); + else + layout->addWidget(w); + w->show(); + } + finishLayout(needMove, layout); +} + +// -------- SplitterLayout +class SplitterLayout : public Layout +{ +public: + SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, + Qt::Orientation orientation); + + void doLayout() override; + void sort() override; + +private: + const Qt::Orientation m_orientation; +}; + +SplitterLayout::SplitterLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, + Qt::Orientation orientation) : + Layout(wl, p, fw, lb, orientation == Qt::Horizontal ? LayoutInfo::HSplitter : LayoutInfo::VSplitter), + m_orientation(orientation) +{ +} + +void SplitterLayout::sort() +{ + QWidgetList wl = widgets(); + std::stable_sort(wl.begin(), wl.end(), PositionSortPredicate(m_orientation)); + setWidgets(wl); +} + +void SplitterLayout::doLayout() +{ + bool needMove, needReparent; + if (!prepareLayout(needMove, needReparent)) + return; + + QSplitter *splitter = qobject_cast(layoutBaseWidget()); + Q_ASSERT(splitter != nullptr); + + for (auto *w : widgets()) { + if (needReparent) + reparentToLayoutBase(w); + splitter->addWidget(w); + w->show(); + } + + splitter->setOrientation(m_orientation); + finishLayout(needMove); +} + +// ---------- Grid: Helper for laying out grids + +class GridHelper +{ + Q_DISABLE_COPY_MOVE(GridHelper); +public: + enum { FormLayoutColumns = 2 }; + + enum Mode { + GridLayout, // Arbitrary size/supports span + FormLayout // 2-column/no span + }; + + GridHelper(Mode mode); + void resize(int nrows, int ncols); + + ~GridHelper(); + + QWidget* cell(int row, int col) const { return m_cells[ row * m_ncols + col]; } + + void setCells(const QRect &c, QWidget* w); + + bool empty() const { return !m_nrows || !m_ncols; } + int numRows() const { return m_nrows; } + int numCols() const { return m_ncols; } + + void simplify(); + bool locateWidget(QWidget* w, int& row, int& col, int& rowspan, int& colspan) const; + +private: + void setCell(int row, int col, QWidget* w) { m_cells[ row * m_ncols + col] = w; } + void shrink(); + void reallocFormLayout(); + int countRow(int r, int c) const; + int countCol(int r, int c) const; + void setRow(int r, int c, QWidget* w, int count); + void setCol(int r, int c, QWidget* w, int count); + bool isWidgetStartCol(int c) const; + bool isWidgetEndCol(int c) const; + bool isWidgetStartRow(int r) const; + bool isWidgetEndRow(int r) const; + bool isWidgetTopLeft(int r, int c) const; + void extendLeft(); + void extendRight(); + void extendUp(); + void extendDown(); + bool shrinkFormLayoutSpans(); + + const Mode m_mode; + int m_nrows; + int m_ncols; + + QWidget** m_cells; // widget matrix w11, w12, w21... +}; + +GridHelper::GridHelper(Mode mode) : + m_mode(mode), + m_nrows(0), + m_ncols(0), + m_cells(nullptr) +{ +} + +GridHelper::~GridHelper() +{ + delete [] m_cells; +} + +void GridHelper::resize(int nrows, int ncols) +{ + delete [] m_cells; + m_cells = nullptr; + m_nrows = nrows; + m_ncols = ncols; + if (const int allocSize = m_nrows * m_ncols) { + m_cells = new QWidget*[allocSize]; + std::fill(m_cells, m_cells + allocSize, nullptr); + } +} + +void GridHelper::setCells(const QRect &c, QWidget* w) +{ + const int bottom = c.top() + c.height(); + const int width = c.width(); + + for (int r = c.top(); r < bottom; r++) { + QWidget **pos = m_cells + r * m_ncols + c.left(); + std::fill(pos, pos + width, w); + } +} + +int GridHelper::countRow(int r, int c) const +{ + QWidget* w = cell(r, c); + int i = c + 1; + while (i < m_ncols && cell(r, i) == w) + i++; + return i - c; +} + +int GridHelper::countCol(int r, int c) const +{ + QWidget* w = cell(r, c); + int i = r + 1; + while (i < m_nrows && cell(i, c) == w) + i++; + return i - r; +} + +void GridHelper::setCol(int r, int c, QWidget* w, int count) +{ + for (int i = 0; i < count; i++) + setCell(r + i, c, w); +} + +void GridHelper::setRow(int r, int c, QWidget* w, int count) +{ + for (int i = 0; i < count; i++) + setCell(r, c + i, w); +} + +bool GridHelper::isWidgetStartCol(int c) const +{ + for (int r = 0; r < m_nrows; r++) { + if (cell(r, c) && ((c==0) || (cell(r, c) != cell(r, c-1)))) { + return true; + } + } + return false; +} + +bool GridHelper::isWidgetEndCol(int c) const +{ + for (int r = 0; r < m_nrows; r++) { + if (cell(r, c) && ((c == m_ncols-1) || (cell(r, c) != cell(r, c+1)))) + return true; + } + return false; +} + +bool GridHelper::isWidgetStartRow(int r) const +{ + for ( int c = 0; c < m_ncols; c++) { + if (cell(r, c) && ((r==0) || (cell(r, c) != cell(r-1, c)))) + return true; + } + return false; +} + +bool GridHelper::isWidgetEndRow(int r) const +{ + for (int c = 0; c < m_ncols; c++) { + if (cell(r, c) && ((r == m_nrows-1) || (cell(r, c) != cell(r+1, c)))) + return true; + } + return false; +} + + +bool GridHelper::isWidgetTopLeft(int r, int c) const +{ + QWidget* w = cell(r, c); + if (!w) + return false; + return (!r || cell(r-1, c) != w) && (!c || cell(r, c-1) != w); +} + +void GridHelper::extendLeft() +{ + for (int c = 1; c < m_ncols; c++) { + for (int r = 0; r < m_nrows; r++) { + QWidget* w = cell(r, c); + if (!w) + continue; + + const int cc = countCol(r, c); + int stretch = 0; + for (int i = c-1; i >= 0; i--) { + if (cell(r, i)) + break; + if (countCol(r, i) < cc) + break; + if (isWidgetEndCol(i)) + break; + if (isWidgetStartCol(i)) { + stretch = c - i; + break; + } + } + if (stretch) { + for (int i = 0; i < stretch; i++) + setCol(r, c-i-1, w, cc); + } + } + } +} + + +void GridHelper::extendRight() +{ + for (int c = m_ncols - 2; c >= 0; c--) { + for (int r = 0; r < m_nrows; r++) { + QWidget* w = cell(r, c); + if (!w) + continue; + const int cc = countCol(r, c); + int stretch = 0; + for (int i = c+1; i < m_ncols; i++) { + if (cell(r, i)) + break; + if (countCol(r, i) < cc) + break; + if (isWidgetStartCol(i)) + break; + if (isWidgetEndCol(i)) { + stretch = i - c; + break; + } + } + if (stretch) { + for (int i = 0; i < stretch; i++) + setCol(r, c+i+1, w, cc); + } + } + } + +} + +void GridHelper::extendUp() +{ + for (int r = 1; r < m_nrows; r++) { + for (int c = 0; c < m_ncols; c++) { + QWidget* w = cell(r, c); + if (!w) + continue; + const int cr = countRow(r, c); + int stretch = 0; + for (int i = r-1; i >= 0; i--) { + if (cell(i, c)) + break; + if (countRow(i, c) < cr) + break; + if (isWidgetEndRow(i)) + break; + if (isWidgetStartRow(i)) { + stretch = r - i; + break; + } + } + if (stretch) { + for (int i = 0; i < stretch; i++) + setRow(r-i-1, c, w, cr); + } + } + } +} + +void GridHelper::extendDown() +{ + for (int r = m_nrows - 2; r >= 0; r--) { + for (int c = 0; c < m_ncols; c++) { + QWidget* w = cell(r, c); + if (!w) + continue; + const int cr = countRow(r, c); + int stretch = 0; + for (int i = r+1; i < m_nrows; i++) { + if (cell(i, c)) + break; + if (countRow(i, c) < cr) + break; + if (isWidgetStartRow(i)) + break; + if (isWidgetEndRow(i)) { + stretch = i - r; + break; + } + } + if (stretch) { + for (int i = 0; i < stretch; i++) + setRow(r+i+1, c, w, cr); + } + } + } +} + +void GridHelper::simplify() +{ + switch (m_mode) { + case GridLayout: + // Grid: Extend all widgets to occupy most space and delete + // rows/columns that are not bordering on a widget + extendLeft(); + extendRight(); + extendUp(); + extendDown(); + shrink(); + break; + case FormLayout: + // Form: First treat it as a grid to get the same behaviour + // regarding spanning and shrinking. Then restrict the span to + // the horizontal span possible in the form, simplify again + // and spread the widgets over a 2-column layout + extendLeft(); + extendRight(); + extendUp(); + extendDown(); + shrink(); + if (shrinkFormLayoutSpans()) + shrink(); + reallocFormLayout(); + break; + } + +} + +void GridHelper::shrink() +{ + // tick off the occupied cols/rows (bordering on widget edges) + QList columns(m_ncols, false); + QList rows(m_nrows, false); + + for (int c = 0; c < m_ncols; c++) + for (int r = 0; r < m_nrows; r++) + if (isWidgetTopLeft(r, c)) + rows[r] = columns[c] = true; + + // remove empty cols/rows + const int simplifiedNCols = columns.count(true); + const int simplifiedNRows = rows.count(true); + if (simplifiedNCols == m_ncols && simplifiedNRows == m_nrows) + return; + // reallocate and copy omitting the empty cells + QWidget **simplifiedCells = new QWidget*[simplifiedNCols * simplifiedNRows]; + std::fill(simplifiedCells, simplifiedCells + simplifiedNCols * simplifiedNRows, nullptr); + QWidget **simplifiedPtr = simplifiedCells; + + for (int r = 0; r < m_nrows; r++) + if (rows[r]) + for (int c = 0; c < m_ncols; c++) + if (columns[c]) { + if (QWidget *w = cell(r, c)) + *simplifiedPtr = w; + simplifiedPtr++; + } + Q_ASSERT(simplifiedPtr == simplifiedCells + simplifiedNCols * simplifiedNRows); + delete [] m_cells; + m_cells = simplifiedCells; + m_nrows = simplifiedNRows; + m_ncols = simplifiedNCols; +} + +bool GridHelper::shrinkFormLayoutSpans() +{ + bool shrunk = false; + using WidgetSet = QSet; + // Determine unique set of widgets + WidgetSet widgets; + QWidget **end = m_cells + m_ncols * m_nrows; + for (QWidget **wptr = m_cells; wptr < end; wptr++) + if (QWidget *w = *wptr) + widgets.insert(w); + // Restrict the widget span: max horizontal span at column 0: 2, anything else: 1 + const int maxRowSpan = 1; + for (auto *w : std::as_const(widgets)) { + int row, col, rowspan, colspan; + if (!locateWidget(w, row, col, rowspan, colspan)) { + qDebug("ooops, widget '%s' does not fit in layout", w->objectName().toUtf8().constData()); + row = col = rowspan = colspan = 0; + } + const int maxColSpan = col == 0 ? 2 : 1; + const int newColSpan = qMin(colspan, maxColSpan); + const int newRowSpan = qMin(rowspan, maxRowSpan); + if (newColSpan != colspan || newRowSpan != rowspan) { + // in case like this: + // W1 W1 + // W1 W2 + // do: + // W1 0 + // 0 W2 + for (int i = row; i < row + rowspan - 1; i++) + for (int j = col; j < col + colspan - 1; j++) + if (i > row + newColSpan - 1 || j > col + newRowSpan - 1) + if (cell(i, j) == w) + setCell(i, j, nullptr); + shrunk = true; + } + } + return shrunk; +} + +void GridHelper::reallocFormLayout() +{ + // Columns matching? -> happy! + if (m_ncols == FormLayoutColumns) + return; + + // If there are offset columns (starting past the field column), + // move them to the left and squeeze them. This also prevents the + // following reallocation from creating empty form rows. + int pastRightWidgetCount = 0; + if (m_ncols > FormLayoutColumns) { + for (int r = 0; r < m_nrows; r++) { + // Try to find a column where the form columns are empty and + // there are widgets further to the right. + if (cell(r, 0) == nullptr && cell(r, 1) == nullptr) { + int sourceCol = FormLayoutColumns; + QWidget *firstWidget = nullptr; + for ( ; sourceCol < m_ncols; sourceCol++) + if (QWidget *w = cell(r, sourceCol)) { + firstWidget = w; + break; + } + if (firstWidget) { + // Move/squeeze. Copy to beginning of column if it is a label, else field + int targetCol = qobject_cast(firstWidget) ? 0 : 1; + for ( ; sourceCol < m_ncols; sourceCol++) + if (QWidget *w = cell(r, sourceCol)) + setCell(r, targetCol++, w); + // Pad with zero + for ( ; targetCol < m_ncols; targetCol++) + setCell(r, targetCol, nullptr); + } + } + // Any protruding widgets left on that row? + for (int c = FormLayoutColumns; c < m_ncols; c++) + if (cell(r, c)) + pastRightWidgetCount++; + } + } + // Reallocate with 2 columns. Just insert the protruding ones as fields. + const int formNRows = m_nrows + pastRightWidgetCount; + QWidget **formCells = new QWidget*[FormLayoutColumns * formNRows]; + std::fill(formCells, formCells + FormLayoutColumns * formNRows, nullptr); + QWidget **formPtr = formCells; + const int matchingColumns = qMin(m_ncols, static_cast(FormLayoutColumns)); + for (int r = 0; r < m_nrows; r++) { + int c = 0; + for ( ; c < matchingColumns; c++) // Just copy over matching columns + *formPtr++ = cell(r, c); + formPtr += FormLayoutColumns - matchingColumns; // In case old format was 1 column + // protruding widgets: Insert as single-field rows + for ( ; c < m_ncols; c++) + if (QWidget *w = cell(r, c)) { + formPtr++; + *formPtr++ = w; + } + } + Q_ASSERT(formPtr == formCells + FormLayoutColumns * formNRows); + delete [] m_cells; + m_cells = formCells; + m_nrows = formNRows; + m_ncols = FormLayoutColumns; +} + +bool GridHelper::locateWidget(QWidget *w, int &row, int &col, int &rowspan, int &colspan) const +{ + const int end = m_nrows * m_ncols; + const int startIndex = std::find(m_cells, m_cells + end, w) - m_cells; + if (startIndex == end) + return false; + + row = startIndex / m_ncols; + col = startIndex % m_ncols; + for (rowspan = 1; row + rowspan < m_nrows && cell(row + rowspan, col) == w; rowspan++) {} + for (colspan = 1; col + colspan < m_ncols && cell(row, col + colspan) == w; colspan++) {} + return true; +} + +// QGridLayout/QFormLayout Helpers: get item position/add item (overloads to make templates work) + +void addWidgetToGrid(QGridLayout *lt, QWidget * widget, int row, int column, int rowSpan, int columnSpan, Qt::Alignment alignment) +{ + lt->addWidget(widget, row, column, rowSpan, columnSpan, alignment); +} + +inline void addWidgetToGrid(QFormLayout *lt, QWidget * widget, int row, int column, int, int columnSpan, Qt::Alignment) +{ + formLayoutAddWidget(lt, widget, QRect(column, row, columnSpan, 1), false); +} + +// ----------- Base template for grid like layouts +template +class GridLayout : public Layout +{ +public: + GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb); + + void doLayout() override; + void sort() override { setWidgets(buildGrid(widgets())); } + +protected: + QWidgetList buildGrid(const QWidgetList &); + GridHelper m_grid; +}; + +template +GridLayout::GridLayout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb) : + Layout(wl, p, fw, lb, LayoutInfo::Grid), + m_grid(static_cast(GridMode)) +{ +} + +template +void GridLayout::doLayout() +{ + bool needMove, needReparent; + if (!prepareLayout(needMove, needReparent)) + return; + + GridLikeLayout *layout = static_cast(createLayout(LayoutType)); + + if (!m_grid.empty()) + sort(); + + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + + for (auto *w : widgets()) { + int r = 0, c = 0, rs = 0, cs = 0; + + if (m_grid.locateWidget(w, r, c, rs, cs)) { + if (needReparent) + reparentToLayoutBase(w); + + Qt::Alignment alignment; + if (const Spacer *spacer = qobject_cast(w)) + alignment = spacer->alignment(); + + addWidgetToGrid(layout, w, r, c, rs, cs, alignment); + + w->show(); + } else { + qDebug("ooops, widget '%s' does not fit in layout", w->objectName().toUtf8().constData()); + } + } + + QLayoutSupport::createEmptyCells(layout); + + finishLayout(needMove, layout); +} + +// Remove duplicate entries (Remove next, if equal to current) +void removeIntVecDuplicates(QList &v) +{ + if (v.size() < 2) + return; + + for (auto current = v.begin() ; (current != v.end()) && ((current + 1) != v.end()) ; ) + if ( *current == *(current+1) ) + v.erase(current+1); + else + ++current; +} + +// Ensure a non-zero size for a widget geometry (squeezed spacers) +inline QRect expandGeometry(const QRect &rect) +{ + return rect.isEmpty() ? QRect(rect.topLeft(), rect.size().expandedTo(QSize(1, 1))) : rect; +} + +template +QWidgetList GridLayout::buildGrid(const QWidgetList &widgetList) +{ + if (widgetList.isEmpty()) + return QWidgetList(); + + // Pixel to cell conversion: + // By keeping a list of start'n'stop values (x & y) for each widget, + // it is possible to create a very small grid of cells to represent + // the widget layout. + // ----------------------------------------------------------------- + + // We need a list of both start and stop values for x- & y-axis + const auto widgetCount = widgetList.size(); + QList x( widgetCount * 2 ); + QList y( widgetCount * 2 ); + + // Using push_back would look nicer, but operator[] is much faster + qsizetype index = 0; + for (const auto *w : widgetList) { + const QRect widgetPos = expandGeometry(w->geometry()); + x[index] = widgetPos.left(); + x[index+1] = widgetPos.right(); + y[index] = widgetPos.top(); + y[index+1] = widgetPos.bottom(); + index += 2; + } + + std::sort(x.begin(), x.end()); + std::sort(y.begin(), y.end()); + + // Remove duplicate x entries (Remove next, if equal to current) + removeIntVecDuplicates(x); + removeIntVecDuplicates(y); + + // Note that left == right and top == bottom for size 1 items; reserve + // enough space + m_grid.resize(y.size(), x.size()); + + for (auto *w : widgetList) { + // Mark the cells in the grid that contains a widget + const QRect widgetPos = expandGeometry(w->geometry()); + QRect c(0, 0, 0, 0); // rect of columns/rows + + // From left til right (not including) + const int leftIdx = x.indexOf(widgetPos.left()); + Q_ASSERT(leftIdx != -1); + c.setLeft(leftIdx); + c.setRight(leftIdx); + for (qsizetype cw = leftIdx; cw < x.size(); ++cw) + if (x.at(cw) < widgetPos.right()) + c.setRight(cw); + else + break; + // From top til bottom (not including) + const int topIdx = y.indexOf(widgetPos.top()); + Q_ASSERT(topIdx != -1); + c.setTop(topIdx); + c.setBottom(topIdx); + for (qsizetype ch = topIdx; ch < y.size(); ++ch) + if (y.at(ch) < widgetPos.bottom()) + c.setBottom(ch); + else + break; + m_grid.setCells(c, w); // Mark cellblock + } + + m_grid.simplify(); + + QWidgetList ordered; + for (int i = 0; i < m_grid.numRows(); i++) + for (int j = 0; j < m_grid.numCols(); j++) { + QWidget *w = m_grid.cell(i, j); + if (w && !ordered.contains(w)) + ordered.append(w); + } + return ordered; +} +} // anonymous + +Layout* Layout::createLayout(const QWidgetList &widgets, QWidget *parentWidget, + QDesignerFormWindowInterface *fw, + QWidget *layoutBase, LayoutInfo::Type layoutType) +{ + switch (layoutType) { + case LayoutInfo::Grid: + return new GridLayout(widgets, parentWidget, fw, layoutBase); + case LayoutInfo::HBox: + case LayoutInfo::VBox: { + const Qt::Orientation orientation = layoutType == LayoutInfo::HBox ? Qt::Horizontal : Qt::Vertical; + return new BoxLayout(widgets, parentWidget, fw, layoutBase, orientation); + } + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: { + const Qt::Orientation orientation = layoutType == LayoutInfo::HSplitter ? Qt::Horizontal : Qt::Vertical; + return new SplitterLayout(widgets, parentWidget, fw, layoutBase, orientation); + } + case LayoutInfo::Form: + return new GridLayout(widgets, parentWidget, fw, layoutBase); + default: + break; + } + Q_ASSERT(0); + return nullptr; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/layout_p.h b/src/tools/designer/src/lib/shared/layout_p.h new file mode 100644 index 00000000000..2dd8ee7e602 --- /dev/null +++ b/src/tools/designer/src/lib/shared/layout_p.h @@ -0,0 +1,113 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef LAYOUT_H +#define LAYOUT_H + +#include "shared_global_p.h" +#include "layoutinfo_p.h" + +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { +class QDESIGNER_SHARED_EXPORT Layout : public QObject +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(Layout) +protected: + Layout(const QWidgetList &wl, QWidget *p, QDesignerFormWindowInterface *fw, QWidget *lb, LayoutInfo::Type layoutType); + +public: + static Layout* createLayout(const QWidgetList &widgets, QWidget *parentWidget, + QDesignerFormWindowInterface *fw, + QWidget *layoutBase, LayoutInfo::Type layoutType); + + ~Layout() override; + + virtual void sort() = 0; + virtual void doLayout() = 0; + + virtual void setup(); + virtual void undoLayout(); + virtual void breakLayout(); + + const QWidgetList &widgets() const { return m_widgets; } + QWidget *parentWidget() const { return m_parentWidget; } + QWidget *layoutBaseWidget() const { return m_layoutBase; } + + /* Determines whether instances of QLayoutWidget are unmanaged/hidden + * after breaking a layout. Default is true. Can be turned off when + * morphing */ + bool reparentLayoutWidget() const { return m_reparentLayoutWidget; } + void setReparentLayoutWidget(bool v) { m_reparentLayoutWidget = v; } + +protected: + virtual void finishLayout(bool needMove, QLayout *layout = nullptr); + virtual bool prepareLayout(bool &needMove, bool &needReparent); + + void setWidgets(const QWidgetList &widgets) { m_widgets = widgets; } + QLayout *createLayout(int type); + void reparentToLayoutBase(QWidget *w); + +private slots: + void widgetDestroyed(); + +private: + QWidgetList m_widgets; + QWidget *m_parentWidget; + QHash m_geometries; + QWidget *m_layoutBase; + QDesignerFormWindowInterface *m_formWindow; + const LayoutInfo::Type m_layoutType; + QPoint m_startPoint; + QRect m_oldGeometry; + + bool m_reparentLayoutWidget; + const bool m_isBreak; +}; + +namespace Utils +{ + +inline int indexOfWidget(QLayout *layout, QWidget *widget) +{ + int index = 0; + while (QLayoutItem *item = layout->itemAt(index)) { + if (item->widget() == widget) + return index; + + ++index; + } + + return -1; +} + +} // namespace Utils + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LAYOUT_H diff --git a/src/tools/designer/src/lib/shared/layoutinfo.cpp b/src/tools/designer/src/lib/shared/layoutinfo.cpp new file mode 100644 index 00000000000..182e42f37ff --- /dev/null +++ b/src/tools/designer/src/lib/shared/layoutinfo.cpp @@ -0,0 +1,269 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "layoutinfo_p.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +/*! + \overload +*/ +LayoutInfo::Type LayoutInfo::layoutType(const QDesignerFormEditorInterface *core, const QLayout *layout) +{ + Q_UNUSED(core); + if (!layout) + return NoLayout; + if (qobject_cast(layout)) + return HBox; + if (qobject_cast(layout)) + return VBox; + if (qobject_cast(layout)) + return Grid; + if (qobject_cast(layout)) + return Form; + return UnknownLayout; +} + +static const QHash &layoutNameTypeMap() +{ + static const QHash nameTypeMap = { + {u"QVBoxLayout"_s, LayoutInfo::VBox}, + {u"QHBoxLayout"_s, LayoutInfo::HBox}, + {u"QGridLayout"_s, LayoutInfo::Grid}, + {u"QFormLayout"_s, LayoutInfo::Form} + }; + return nameTypeMap; +} + +LayoutInfo::Type LayoutInfo::layoutType(const QString &typeName) +{ + return layoutNameTypeMap().value(typeName, NoLayout); +} + +QString LayoutInfo::layoutName(Type t) +{ + return layoutNameTypeMap().key(t); +} + +/*! + \overload +*/ +LayoutInfo::Type LayoutInfo::layoutType(const QDesignerFormEditorInterface *core, const QWidget *w) +{ + if (const QSplitter *splitter = qobject_cast(w)) + return splitter->orientation() == Qt::Horizontal ? HSplitter : VSplitter; + return layoutType(core, w->layout()); +} + +LayoutInfo::Type LayoutInfo::managedLayoutType(const QDesignerFormEditorInterface *core, + const QWidget *w, + QLayout **ptrToLayout) +{ + if (ptrToLayout) + *ptrToLayout = nullptr; + if (const QSplitter *splitter = qobject_cast(w)) + return splitter->orientation() == Qt::Horizontal ? HSplitter : VSplitter; + QLayout *layout = managedLayout(core, w); + if (!layout) + return NoLayout; + if (ptrToLayout) + *ptrToLayout = layout; + return layoutType(core, layout); +} + +QWidget *LayoutInfo::layoutParent(const QDesignerFormEditorInterface *core, QLayout *layout) +{ + Q_UNUSED(core); + + QObject *o = layout; + while (o) { + if (QWidget *widget = qobject_cast(o)) + return widget; + + o = o->parent(); + } + return nullptr; +} + +void LayoutInfo::deleteLayout(const QDesignerFormEditorInterface *core, QWidget *widget) +{ + if (QDesignerContainerExtension *container = qt_extension(core->extensionManager(), widget)) + widget = container->widget(container->currentIndex()); + + Q_ASSERT(widget != nullptr); + + QLayout *layout = managedLayout(core, widget); + + if (layout == nullptr || core->metaDataBase()->item(layout) != nullptr) { + delete layout; + widget->updateGeometry(); + return; + } + + qDebug() << "trying to delete an unmanaged layout:" << "widget:" << widget << "layout:" << layout; +} + +LayoutInfo::Type LayoutInfo::laidoutWidgetType(const QDesignerFormEditorInterface *core, + QWidget *widget, + bool *isManaged, + QLayout **ptrToLayout) +{ + if (isManaged) + *isManaged = false; + if (ptrToLayout) + *ptrToLayout = nullptr; + + QWidget *parent = widget->parentWidget(); + if (!parent) + return NoLayout; + + // 1) Splitter + if (QSplitter *splitter = qobject_cast(parent)) { + if (isManaged) + *isManaged = core->metaDataBase()->item(splitter); + return splitter->orientation() == Qt::Horizontal ? HSplitter : VSplitter; + } + + // 2) Layout of parent + QLayout *parentLayout = parent->layout(); + if (!parentLayout) + return NoLayout; + + if (parentLayout->indexOf(widget) != -1) { + if (isManaged) + *isManaged = core->metaDataBase()->item(parentLayout); + if (ptrToLayout) + *ptrToLayout = parentLayout; + return layoutType(core, parentLayout); + } + + // 3) Some child layout (see below comment about Q3GroupBox) + const auto childLayouts = parentLayout->findChildren(); + if (childLayouts.isEmpty()) + return NoLayout; + for (QLayout *layout : childLayouts) { + if (layout->indexOf(widget) != -1) { + if (isManaged) + *isManaged = core->metaDataBase()->item(layout); + if (ptrToLayout) + *ptrToLayout = layout; + return layoutType(core, layout); + } + } + + return NoLayout; +} + +QLayout *LayoutInfo::internalLayout(const QWidget *widget) +{ + return widget->layout(); +} + + +QLayout *LayoutInfo::managedLayout(const QDesignerFormEditorInterface *core, const QWidget *widget) +{ + if (widget == nullptr) + return nullptr; + + QLayout *layout = widget->layout(); + if (!layout) + return nullptr; + + return managedLayout(core, layout); +} + +QLayout *LayoutInfo::managedLayout(const QDesignerFormEditorInterface *core, QLayout *layout) +{ + if (!layout) + return nullptr; + + QDesignerMetaDataBaseInterface *metaDataBase = core->metaDataBase(); + + if (!metaDataBase) + return layout; + /* This code exists mainly for the Q3GroupBox class, for which + * widget->layout() returns an internal VBoxLayout. */ + const QDesignerMetaDataBaseItemInterface *item = metaDataBase->item(layout); + if (item == nullptr) { + layout = layout->findChild(); + item = metaDataBase->item(layout); + } + if (!item) + return nullptr; + return layout; +} + +// Is it a a dummy grid placeholder created by Designer? +bool LayoutInfo::isEmptyItem(QLayoutItem *item) +{ + if (item == nullptr) { + qDebug() << "** WARNING Zero-item passed on to isEmptyItem(). This indicates a layout inconsistency."; + return true; + } + return item->spacerItem() != nullptr; +} + +QDESIGNER_SHARED_EXPORT void getFormLayoutItemPosition(const QFormLayout *formLayout, int index, int *rowPtr, int *columnPtr, int *rowspanPtr, int *colspanPtr) +{ + int row; + QFormLayout::ItemRole role; + formLayout->getItemPosition(index, &row, &role); + const int columnspan = role == QFormLayout::SpanningRole ? 2 : 1; + const int column = (columnspan > 1 || role == QFormLayout::LabelRole) ? 0 : 1; + if (rowPtr) + *rowPtr = row; + if (columnPtr) + *columnPtr = column; + if (rowspanPtr) + *rowspanPtr = 1; + if (colspanPtr) + *colspanPtr = columnspan; +} + +static inline QFormLayout::ItemRole formLayoutRole(int column, int colspan) +{ + if (colspan > 1) + return QFormLayout::SpanningRole; + return column == 0 ? QFormLayout::LabelRole : QFormLayout::FieldRole; +} + +QDESIGNER_SHARED_EXPORT void formLayoutAddWidget(QFormLayout *formLayout, QWidget *w, const QRect &r, bool insert) +{ + // Consistent API galore... + if (insert) { + const bool spanning = r.width() > 1; + if (spanning) { + formLayout->insertRow(r.y(), w); + } else { + QWidget *label = nullptr; + QWidget *field = nullptr; + if (r.x() == 0) { + label = w; + } else { + field = w; + } + formLayout->insertRow(r.y(), label, field); + } + } else { + formLayout->setWidget(r.y(), formLayoutRole(r.x(), r.width()), w); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/layoutinfo_p.h b/src/tools/designer/src/lib/shared/layoutinfo_p.h new file mode 100644 index 00000000000..7ddb2129f11 --- /dev/null +++ b/src/tools/designer/src/lib/shared/layoutinfo_p.h @@ -0,0 +1,76 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef LAYOUTINFO_H +#define LAYOUTINFO_H + +#include "shared_global_p.h" + +QT_BEGIN_NAMESPACE + +class QWidget; +class QLayout; +class QLayoutItem; +class QDesignerFormEditorInterface; +class QFormLayout; +class QRect; +class QString; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT LayoutInfo +{ +public: + enum Type + { + NoLayout, + HSplitter, + VSplitter, + HBox, + VBox, + Grid, + Form, + UnknownLayout // QDockWindow inside QMainWindow is inside QMainWindowLayout - it doesn't mean there is no layout + }; + + static void deleteLayout(const QDesignerFormEditorInterface *core, QWidget *widget); + + // Examines the immediate layout of the widget. + static Type layoutType(const QDesignerFormEditorInterface *core, const QWidget *w); + // Examines the managed layout of the widget + static Type managedLayoutType(const QDesignerFormEditorInterface *core, const QWidget *w, QLayout **layout = nullptr); + static Type layoutType(const QDesignerFormEditorInterface *core, const QLayout *layout); + static Type layoutType(const QString &typeName); + static QString layoutName(Type t); + + static QWidget *layoutParent(const QDesignerFormEditorInterface *core, QLayout *layout); + + static Type laidoutWidgetType(const QDesignerFormEditorInterface *core, QWidget *widget, bool *isManaged = nullptr, QLayout **layout = nullptr); + static bool inline isWidgetLaidout(const QDesignerFormEditorInterface *core, QWidget *widget) { return laidoutWidgetType(core, widget) != NoLayout; } + + static QLayout *managedLayout(const QDesignerFormEditorInterface *core, const QWidget *widget); + static QLayout *managedLayout(const QDesignerFormEditorInterface *core, QLayout *layout); + static QLayout *internalLayout(const QWidget *widget); + + // Is it a a dummy grid placeholder created by Designer? + static bool isEmptyItem(QLayoutItem *item); +}; + +QDESIGNER_SHARED_EXPORT void getFormLayoutItemPosition(const QFormLayout *formLayout, int index, int *rowPtr, int *columnPtr = nullptr, int *rowspanPtr = nullptr, int *colspanPtr = nullptr); +QDESIGNER_SHARED_EXPORT void formLayoutAddWidget(QFormLayout *formLayout, QWidget *w, const QRect &r, bool insert); +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // LAYOUTINFO_H diff --git a/src/tools/designer/src/lib/shared/metadatabase.cpp b/src/tools/designer/src/lib/shared/metadatabase.cpp new file mode 100644 index 00000000000..77d6aa23c9d --- /dev/null +++ b/src/tools/designer/src/lib/shared/metadatabase.cpp @@ -0,0 +1,243 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "metadatabase_p.h" +#include "widgetdatabase_p.h" + +// sdk +#include + +// Qt +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { + const bool debugMetaDatabase = false; +} + +namespace qdesigner_internal { + +MetaDataBaseItem::MetaDataBaseItem(QObject *object) + : m_object(object), + m_enabled(true) +{ +} + +MetaDataBaseItem::~MetaDataBaseItem() = default; + +QString MetaDataBaseItem::name() const +{ + Q_ASSERT(m_object); + return m_object->objectName(); +} + +void MetaDataBaseItem::setName(const QString &name) +{ + Q_ASSERT(m_object); + m_object->setObjectName(name); +} + +QString MetaDataBaseItem::customClassName() const +{ + return m_customClassName; +} +void MetaDataBaseItem::setCustomClassName(const QString &customClassName) +{ + m_customClassName = customClassName; +} + + +QWidgetList MetaDataBaseItem::tabOrder() const +{ + return m_tabOrder; +} + +void MetaDataBaseItem::setTabOrder(const QWidgetList &tabOrder) +{ + m_tabOrder = tabOrder; +} + +bool MetaDataBaseItem::enabled() const +{ + return m_enabled; +} + +void MetaDataBaseItem::setEnabled(bool b) +{ + m_enabled = b; +} + +QStringList MetaDataBaseItem::fakeSlots() const +{ + return m_fakeSlots; +} + +void MetaDataBaseItem::setFakeSlots(const QStringList &fs) +{ + m_fakeSlots = fs; +} + +QStringList MetaDataBaseItem::fakeSignals() const +{ + return m_fakeSignals; +} + +void MetaDataBaseItem::setFakeSignals(const QStringList &fs) +{ + m_fakeSignals = fs; +} + +// ----------------------------------------------------- +MetaDataBase::MetaDataBase(QDesignerFormEditorInterface *core, QObject *parent) + : QDesignerMetaDataBaseInterface(parent), + m_core(core) +{ +} + +MetaDataBase::~MetaDataBase() +{ + qDeleteAll(m_items); +} + +MetaDataBaseItem *MetaDataBase::metaDataBaseItem(QObject *object) const +{ + MetaDataBaseItem *i = m_items.value(object); + if (i == nullptr || !i->enabled()) + return nullptr; + return i; +} + +void MetaDataBase::add(QObject *object) +{ + MetaDataBaseItem *item = m_items.value(object); + if (item != nullptr) { + item->setEnabled(true); + if (debugMetaDatabase) { + qDebug() << "MetaDataBase::add: Existing item for " << object->metaObject()->className() << item->name(); + } + return; + } + + item = new MetaDataBaseItem(object); + m_items.insert(object, item); + if (debugMetaDatabase) { + qDebug() << "MetaDataBase::add: New item " << object->metaObject()->className() << item->name(); + } + connect(object, &QObject::destroyed, this, &MetaDataBase::slotDestroyed); + + emit changed(); +} + +void MetaDataBase::remove(QObject *object) +{ + Q_ASSERT(object); + + if (MetaDataBaseItem *item = m_items.value(object)) { + item->setEnabled(false); + emit changed(); + } +} + +QObjectList MetaDataBase::objects() const +{ + QObjectList result; + + for (auto it = m_items.cbegin(), cend = m_items.cend(); it != cend; ++it) { + if (it.value()->enabled()) + result.append(it.key()); + } + + return result; +} + +QDesignerFormEditorInterface *MetaDataBase::core() const +{ + return m_core; +} + +void MetaDataBase::slotDestroyed(QObject *object) +{ + if (m_items.contains(object)) { + MetaDataBaseItem *item = m_items.value(object); + delete item; + m_items.remove(object); + } +} + +// promotion convenience +QDESIGNER_SHARED_EXPORT bool promoteWidget(QDesignerFormEditorInterface *core,QWidget *widget,const QString &customClassName) +{ + + MetaDataBase *db = qobject_cast(core->metaDataBase()); + if (!db) + return false; + MetaDataBaseItem *item = db->metaDataBaseItem(widget); + if (!item) { + db ->add(widget); + item = db->metaDataBaseItem(widget); + } + // Recursive promotion occurs if there is a plugin missing. + const QString oldCustomClassName = item->customClassName(); + if (!oldCustomClassName.isEmpty()) { + qDebug() << "WARNING: Recursive promotion of " << oldCustomClassName << " to " << customClassName + << ". A plugin is missing."; + } + item->setCustomClassName(customClassName); + if (debugMetaDatabase) { + qDebug() << "Promoting " << widget->metaObject()->className() << " to " << customClassName; + } + return true; +} + +QDESIGNER_SHARED_EXPORT void demoteWidget(QDesignerFormEditorInterface *core,QWidget *widget) +{ + MetaDataBase *db = qobject_cast(core->metaDataBase()); + if (!db) + return; + MetaDataBaseItem *item = db->metaDataBaseItem(widget); + item->setCustomClassName(QString()); + if (debugMetaDatabase) { + qDebug() << "Demoting " << widget; + } +} + +QDESIGNER_SHARED_EXPORT bool isPromoted(QDesignerFormEditorInterface *core, QWidget* widget) +{ + const MetaDataBase *db = qobject_cast(core->metaDataBase()); + if (!db) + return false; + const MetaDataBaseItem *item = db->metaDataBaseItem(widget); + if (!item) + return false; + return !item->customClassName().isEmpty(); +} + +QDESIGNER_SHARED_EXPORT QString promotedCustomClassName(QDesignerFormEditorInterface *core, QWidget* widget) +{ + const MetaDataBase *db = qobject_cast(core->metaDataBase()); + if (!db) + return QString(); + const MetaDataBaseItem *item = db->metaDataBaseItem(widget); + if (!item) + return QString(); + return item->customClassName(); +} + +QDESIGNER_SHARED_EXPORT QString promotedExtends(QDesignerFormEditorInterface *core, QWidget* widget) +{ + const QString customClassName = promotedCustomClassName(core,widget); + if (customClassName.isEmpty()) + return QString(); + const int i = core->widgetDataBase()->indexOfClassName(customClassName); + if (i == -1) + return QString(); + return core->widgetDataBase()->item(i)->extends(); +} + + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/metadatabase_p.h b/src/tools/designer/src/lib/shared/metadatabase_p.h new file mode 100644 index 00000000000..2117f59750b --- /dev/null +++ b/src/tools/designer/src/lib/shared/metadatabase_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef METADATABASE_H +#define METADATABASE_H + +#include "shared_global_p.h" + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT MetaDataBaseItem: public QDesignerMetaDataBaseItemInterface +{ +public: + explicit MetaDataBaseItem(QObject *object); + ~MetaDataBaseItem() override; + + QString name() const override; + void setName(const QString &name) override; + + QWidgetList tabOrder() const override; + void setTabOrder(const QWidgetList &tabOrder) override; + + bool enabled() const override; + void setEnabled(bool b) override; + + QString customClassName() const; + void setCustomClassName(const QString &customClassName); + + QStringList fakeSlots() const; + void setFakeSlots(const QStringList &); + + QStringList fakeSignals() const; + void setFakeSignals(const QStringList &); + +private: + QObject *m_object; + QWidgetList m_tabOrder; + bool m_enabled; + QString m_customClassName; + QStringList m_fakeSlots; + QStringList m_fakeSignals; +}; + +class QDESIGNER_SHARED_EXPORT MetaDataBase: public QDesignerMetaDataBaseInterface +{ + Q_OBJECT +public: + explicit MetaDataBase(QDesignerFormEditorInterface *core, QObject *parent = nullptr); + ~MetaDataBase() override; + + QDesignerFormEditorInterface *core() const override; + + QDesignerMetaDataBaseItemInterface *item(QObject *object) const override { return metaDataBaseItem(object); } + virtual MetaDataBaseItem *metaDataBaseItem(QObject *object) const; + void add(QObject *object) override; + void remove(QObject *object) override; + + QObjectList objects() const override; + +private slots: + void slotDestroyed(QObject *object); + +private: + QDesignerFormEditorInterface *m_core; + QHash m_items; +}; + + // promotion convenience + QDESIGNER_SHARED_EXPORT bool promoteWidget(QDesignerFormEditorInterface *core,QWidget *widget,const QString &customClassName); + QDESIGNER_SHARED_EXPORT void demoteWidget(QDesignerFormEditorInterface *core,QWidget *widget); + QDESIGNER_SHARED_EXPORT bool isPromoted(QDesignerFormEditorInterface *core, QWidget* w); + QDESIGNER_SHARED_EXPORT QString promotedCustomClassName(QDesignerFormEditorInterface *core, QWidget* w); + QDESIGNER_SHARED_EXPORT QString promotedExtends(QDesignerFormEditorInterface *core, QWidget* w); + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // METADATABASE_H diff --git a/src/tools/designer/src/lib/shared/morphmenu.cpp b/src/tools/designer/src/lib/shared/morphmenu.cpp new file mode 100644 index 00000000000..3e7c053caf4 --- /dev/null +++ b/src/tools/designer/src/lib/shared/morphmenu.cpp @@ -0,0 +1,587 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "morphmenu_p.h" +#include "formwindowbase_p.h" +#include "widgetfactory_p.h" +#include "qdesigner_formwindowcommand_p.h" +#include "qlayout_widget_p.h" +#include "layoutinfo_p.h" +#include "qdesigner_propertycommand_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QWidgetList) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Helpers for the dynamic properties that store Z/Widget order +static const char widgetOrderPropertyC[] = "_q_widgetOrder"; +static const char zOrderPropertyC[] = "_q_zOrder"; + +/* Morphing in Designer: + * It is possible to morph: + * - Non-Containers into similar widgets by category + * - Simple page containers into similar widgets or page-based containers with + * a single page (in theory also into a QLayoutWidget, but this might + * not always be appropriate). + * - Page-based containers into page-based containers or simple containers if + * they have just one page + * [Page based containers meaning here having a container extension] + * Morphing types are restricted to the basic Qt types. Morphing custom + * widgets is considered risky since they might have unmanaged layouts + * or the like. + * + * Requirements: + * - The widget must be on a non-laid out parent or in a layout managed + * by Designer + * - Its child widgets must be non-laid out or in a layout managed + * by Designer + * Note that child widgets can be + * - On the widget itself in the case of simple containers + * - On several pages in the case of page-based containers + * This is what is called 'childContainers' in the code (the widget itself + * or the list of container extension pages). + * + * The Morphing process encompasses: + * - Create a target widget and apply properties as far as applicable + * If the target widget has a container extension, add a sufficient + * number of pages. + * - Transferring the child widgets over to the new childContainers. + * In the case of a managed layout on a childContainer, this is simply + * set on the target childContainer, which is a new Qt 4.5 + * functionality. + * - Replace the widget itself in the parent layout + */ + +namespace qdesigner_internal { + +enum MorphCategory { + MorphCategoryNone, MorphSimpleContainer, MorphPageContainer, MorphItemView, + MorphButton, MorphSpinBox, MorphTextEdit +}; + +// Determine category of a widget +static MorphCategory category(const QWidget *w) +{ + // Simple containers: Exact match + const QMetaObject *mo = w->metaObject(); + if (mo == &QWidget::staticMetaObject || mo == &QFrame::staticMetaObject || mo == &QGroupBox::staticMetaObject || mo == &QLayoutWidget::staticMetaObject) + return MorphSimpleContainer; + if (mo == &QTabWidget::staticMetaObject || mo == &QStackedWidget::staticMetaObject || mo == &QToolBox::staticMetaObject) + return MorphPageContainer; + if (qobject_cast(w)) + return MorphItemView; + if (qobject_cast(w)) + return MorphButton; + if (qobject_cast(w)) + return MorphSpinBox; + if (qobject_cast(w) || qobject_cast(w)) + return MorphTextEdit; + + return MorphCategoryNone; +} + +/* Return the similar classes of a category. This is currently restricted + * to the known Qt classes with no precautions to parse the Widget Database + * (which is too risky, custom classes might have container extensions + * or non-managed layouts, etc.). */ + +static QStringList classesOfCategory(MorphCategory cat) +{ + static QMap candidateCache; + auto it = candidateCache.find(cat); + if (it == candidateCache.end()) { + it = candidateCache.insert(cat, QStringList()); + QStringList &l = it.value(); + switch (cat) { + case MorphCategoryNone: + break; + case MorphSimpleContainer: + // Do not generally allow to morph into a layout. + // This can be risky in case of container pages,etc. + l << u"QWidget"_s << u"QFrame"_s << u"QGroupBox"_s; + break; + case MorphPageContainer: + l << u"QTabWidget"_s << u"QStackedWidget"_s << u"QToolBox"_s; + break; + case MorphItemView: + l << u"QListView"_s << u"QListWidget"_s + << u"QTreeView"_s << u"QTreeWidget"_s + << u"QTableView"_s << u"QTableWidget"_s + << u"QColumnView"_s; + break; + case MorphButton: + l << u"QCheckBox"_s << u"QRadioButton"_s + << u"QPushButton"_s << u"QToolButton"_s + << u"QCommandLinkButton"_s; + break; + case MorphSpinBox: + l << u"QDateTimeEdit"_s << u"QDateEdit"_s + << u"QTimeEdit"_s + << u"QSpinBox"_s << u"QDoubleSpinBox"_s; + break; + case MorphTextEdit: + l << u"QTextEdit"_s << u"QPlainTextEdit"_s << u"QTextBrowser"_s; + break; + } + } + return it.value(); +} + +// Return the widgets containing the children to be transferred to. This is the +// widget itself in most cases, except for QDesignerContainerExtension cases +static QWidgetList childContainers(const QDesignerFormEditorInterface *core, QWidget *w) +{ + if (const QDesignerContainerExtension *ce = qt_extension(core->extensionManager(), w)) { + QWidgetList children; + if (const int count = ce->count()) { + for (int i = 0; i < count; i++) + children.push_back(ce->widget(i)); + } + return children; + } + QWidgetList self; + self.push_back(w); + return self; +} + +// Suggest a suitable objectname for the widget to be morphed into +// Replace the class name parts: 'xxFrame' -> 'xxGroupBox', 'frame' -> 'groupBox' +static QString suggestObjectName(const QString &oldClassName, const QString &newClassName, const QString &oldName) +{ + QString oldClassPart = oldClassName; + QString newClassPart = newClassName; + if (oldClassPart.startsWith(u'Q')) + oldClassPart.remove(0, 1); + if (newClassPart.startsWith(u'Q')) + newClassPart.remove(0, 1); + + QString newName = oldName; + newName.replace(oldClassPart, newClassPart); + oldClassPart[0] = oldClassPart.at(0).toLower(); + newClassPart[0] = newClassPart.at(0).toLower(); + newName.replace(oldClassPart, newClassPart); + return newName; +} + +// Find the label whose buddy the widget is. +QLabel *buddyLabelOf(QDesignerFormWindowInterface *fw, QWidget *w) +{ + const auto labelList = fw->findChildren(); + for (QLabel *label : labelList) + if (label->buddy() == w) + return label; + return nullptr; +} + +// Replace widgets in a widget-list type dynamic property of the parent +// used for Z-order, etc. +static void replaceWidgetListDynamicProperty(QWidget *parentWidget, + QWidget *oldWidget, QWidget *newWidget, + const char *name) +{ + QWidgetList list = qvariant_cast(parentWidget->property(name)); + const int index = list.indexOf(oldWidget); + if (index != -1) { + list.replace(index, newWidget); + parentWidget->setProperty(name, QVariant::fromValue(list)); + } +} + +/* Morph a widget into another class. Use the static addMorphMacro() to + * add a respective command sequence to the undo stack as it emits signals + * which cause other commands to be added. */ +class MorphWidgetCommand : public QDesignerFormWindowCommand +{ + Q_DISABLE_COPY_MOVE(MorphWidgetCommand) +public: + + explicit MorphWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~MorphWidgetCommand() override; + + // Convenience to add a morph command sequence macro + static bool addMorphMacro(QDesignerFormWindowInterface *formWindow, QWidget *w, const QString &newClass); + + bool init(QWidget *widget, const QString &newClassName); + + QString newWidgetName() const { return m_afterWidget->objectName(); } + + void redo() override; + void undo() override; + + static QStringList candidateClasses(QDesignerFormWindowInterface *fw, QWidget *w); + +private: + static bool canMorph(QDesignerFormWindowInterface *fw, QWidget *w, int *childContainerCount = nullptr, MorphCategory *cat = nullptr); + void morph(QWidget *before, QWidget *after); + + QWidget *m_beforeWidget; + QWidget *m_afterWidget; +}; + +bool MorphWidgetCommand::addMorphMacro(QDesignerFormWindowInterface *fw, QWidget *w, const QString &newClass) +{ + MorphWidgetCommand *morphCmd = new MorphWidgetCommand(fw); + if (!morphCmd->init(w, newClass)) { + qWarning("*** Unable to create a MorphWidgetCommand"); + delete morphCmd; + return false; + } + QLabel *buddyLabel = buddyLabelOf(fw, w); + // Need a macro since it adds further commands + QUndoStack *us = fw->commandHistory(); + us->beginMacro(morphCmd->text()); + // Have the signal slot/buddy editors add their commands to delete widget + if (FormWindowBase *fwb = qobject_cast(fw)) + fwb->emitWidgetRemoved(w); + + const QString newWidgetName = morphCmd->newWidgetName(); + us->push(morphCmd); + + // restore buddy using the QByteArray name. + if (buddyLabel) { + SetPropertyCommand *buddyCmd = new SetPropertyCommand(fw); + buddyCmd->init(buddyLabel, u"buddy"_s, QVariant(newWidgetName.toUtf8())); + us->push(buddyCmd); + } + us->endMacro(); + return true; +} + +MorphWidgetCommand::MorphWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_beforeWidget(nullptr), + m_afterWidget(nullptr) +{ +} + +MorphWidgetCommand::~MorphWidgetCommand() = default; + +bool MorphWidgetCommand::init(QWidget *widget, const QString &newClassName) +{ + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + + if (!canMorph(fw, widget)) + return false; + + const QString oldClassName = WidgetFactory::classNameOf(core, widget); + const QString oldName = widget->objectName(); + //: MorphWidgetCommand description + setText(QApplication::translate("Command", "Morph %1/'%2' into %3").arg(oldClassName, oldName, newClassName)); + + m_beforeWidget = widget; + m_afterWidget = core->widgetFactory()->createWidget(newClassName, fw); + if (!m_afterWidget) + return false; + + // Set object name. Do not unique it (as to maintain it). + m_afterWidget->setObjectName(suggestObjectName(oldClassName, newClassName, oldName)); + + // If the target has a container extension, we add enough new pages to take + // up the children of the before widget + if (QDesignerContainerExtension* c = qt_extension(core->extensionManager(), m_afterWidget)) { + if (const auto pageCount = childContainers(core, m_beforeWidget).size()) { + const QString containerName = m_afterWidget->objectName(); + for (qsizetype i = 0; i < pageCount; ++i) { + QString name = containerName; + name += "Page"_L1; + name += QString::number(i + 1); + QWidget *page = core->widgetFactory()->createWidget(u"QWidget"_s); + page->setObjectName(name); + fw->ensureUniqueObjectName(page); + c->addWidget(page); + core->metaDataBase()->add(page); + } + } + } + + // Copy over applicable properties + const QDesignerPropertySheetExtension *beforeSheet = qt_extension(core->extensionManager(), widget); + QDesignerPropertySheetExtension *afterSheet = qt_extension(core->extensionManager(), m_afterWidget); + const int count = beforeSheet->count(); + for (int i = 0; i < count; i++) + if (beforeSheet->isVisible(i) && beforeSheet->isChanged(i)) { + const QString name = beforeSheet->propertyName(i); + if (name != "objectName"_L1) { + const int afterIndex = afterSheet->indexOf(name); + if (afterIndex != -1 && afterSheet->isVisible(afterIndex) && afterSheet->propertyGroup(afterIndex) == beforeSheet->propertyGroup(i)) { + afterSheet->setProperty(i, beforeSheet->property(i)); + afterSheet->setChanged(i, true); + } else { + // Some mismatch. The rest won't match, either + break; + } + } + } + return true; +} + +void MorphWidgetCommand::redo() +{ + morph(m_beforeWidget, m_afterWidget); +} + +void MorphWidgetCommand::undo() +{ + morph(m_afterWidget, m_beforeWidget); +} + +void MorphWidgetCommand::morph(QWidget *before, QWidget *after) +{ + QDesignerFormWindowInterface *fw = formWindow(); + + fw->unmanageWidget(before); + + const QRect oldGeom = before->geometry(); + QWidget *parent = before->parentWidget(); + Q_ASSERT(parent); + /* Morphing consists of main 2 steps + * 1) Move over children (laid out, non-laid out) + * 2) Register self with new parent (laid out, non-laid out) */ + + // 1) Move children. Loop over child containers + QWidgetList beforeChildContainers = childContainers(fw->core(), before); + QWidgetList afterChildContainers = childContainers(fw->core(), after); + Q_ASSERT(beforeChildContainers.size() == afterChildContainers.size()); + const auto childContainerCount = beforeChildContainers.size(); + for (qsizetype i = 0; i < childContainerCount; ++i) { + QWidget *beforeChildContainer = beforeChildContainers.at(i); + QWidget *afterChildContainer = afterChildContainers.at(i); + if (QLayout *childLayout = beforeChildContainer->layout()) { + // Laid-out: Move the layout (since 4.5) + afterChildContainer->setLayout(childLayout); + } else { + // Non-Laid-out: Reparent, move over + for (QObject *o : beforeChildContainer->children()) { + if (o->isWidgetType()) { + QWidget *w = static_cast(o); + if (fw->isManaged(w)) { + const QRect geom = w->geometry(); + w->setParent(afterChildContainer); + w->setGeometry(geom); + } + } + } + } + afterChildContainer->setProperty(widgetOrderPropertyC, beforeChildContainer->property(widgetOrderPropertyC)); + afterChildContainer->setProperty(zOrderPropertyC, beforeChildContainer->property(zOrderPropertyC)); + } + + // 2) Replace the actual widget in the parent layout + after->setGeometry(oldGeom); + if (QLayout *containingLayout = LayoutInfo::managedLayout(fw->core(), parent)) { + LayoutHelper *lh = LayoutHelper::createLayoutHelper(LayoutInfo::layoutType(fw->core(), containingLayout)); + Q_ASSERT(lh); + lh->replaceWidget(containingLayout, before, after); + delete lh; + } else if (QSplitter *splitter = qobject_cast(parent)) { + const int index = splitter->indexOf(before); + before->hide(); + before->setParent(nullptr); + splitter->insertWidget(index, after); + after->setParent(parent); + after->setGeometry(oldGeom); + } else { + before->hide(); + before->setParent(nullptr); + after->setParent(parent); + after->setGeometry(oldGeom); + } + + // Check various properties: Z order, form tab order + replaceWidgetListDynamicProperty(parent, before, after, widgetOrderPropertyC); + replaceWidgetListDynamicProperty(parent, before, after, zOrderPropertyC); + + QDesignerMetaDataBaseItemInterface *formItem = fw->core()->metaDataBase()->item(fw); + QWidgetList tabOrder = formItem->tabOrder(); + const int tabIndex = tabOrder.indexOf(before); + if (tabIndex != -1) { + tabOrder.replace(tabIndex, after); + formItem->setTabOrder(tabOrder); + } + + after->show(); + fw->manageWidget(after); + + fw->clearSelection(false); + fw->selectWidget(after); +} + +/* Check if morphing is possible. It must be a valid category and the parent/ + * child relationships must be either non-laidout or directly on + * Designer-managed layouts. */ +bool MorphWidgetCommand::canMorph(QDesignerFormWindowInterface *fw, QWidget *w, int *ptrToChildContainerCount, MorphCategory *ptrToCat) +{ + if (ptrToChildContainerCount) + *ptrToChildContainerCount = 0; + const MorphCategory cat = category(w); + if (ptrToCat) + *ptrToCat = cat; + if (cat == MorphCategoryNone) + return false; + + QDesignerFormEditorInterface *core = fw->core(); + // Don't know how to fiddle class names in Jambi.. + if (qt_extension(core->extensionManager(), core)) + return false; + if (!fw->isManaged(w) || w == fw->mainContainer()) + return false; + // Check the parent relationship. We accept only managed parent widgets + // with a single, managed layout in which widget is a member. + QWidget *parent = w->parentWidget(); + if (parent == nullptr) + return false; + if (QLayout *pl = LayoutInfo::managedLayout(core, parent)) + if (pl->indexOf(w) < 0 || !core->metaDataBase()->item(pl)) + return false; + // Check Widget database + const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); + const int wdbindex = wdb->indexOfObject(w); + if (wdbindex == -1) + return false; + const bool isContainer = wdb->item(wdbindex)->isContainer(); + if (!isContainer) + return true; + // Check children. All child containers must be non-laid-out or have managed layouts + const QWidgetList pages = childContainers(core, w); + const auto pageCount = pages.size(); + if (ptrToChildContainerCount) + *ptrToChildContainerCount = pageCount; + if (pageCount) { + for (qsizetype i = 0; i < pageCount; ++i) + if (QLayout *cl = pages.at(i)->layout()) + if (!core->metaDataBase()->item(cl)) + return false; + } + return true; +} + +QStringList MorphWidgetCommand::candidateClasses(QDesignerFormWindowInterface *fw, QWidget *w) +{ + int childContainerCount; + MorphCategory cat; + if (!canMorph(fw, w, &childContainerCount, &cat)) + return QStringList(); + + QStringList rc = classesOfCategory(cat); + switch (cat) { + // Frames, etc can always be morphed into one-page page containers + case MorphSimpleContainer: + rc += classesOfCategory(MorphPageContainer); + break; + // Multipage-Containers can be morphed into simple containers if they + // have 1 page. + case MorphPageContainer: + if (childContainerCount == 1) + rc += classesOfCategory(MorphSimpleContainer); + break; + default: + break; + } + return rc; +} + +// MorphMenu +MorphMenu::MorphMenu(QObject *parent) : + QObject(parent) +{ +} + +void MorphMenu::populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList& al) +{ + if (populateMenu(w, fw)) + al.push_back(m_subMenuAction); +} + +void MorphMenu::populate(QWidget *w, QDesignerFormWindowInterface *fw, QMenu& m) +{ + if (populateMenu(w, fw)) + m.addAction(m_subMenuAction); +} + +void MorphMenu::slotMorph(const QString &newClassName) +{ + MorphWidgetCommand::addMorphMacro(m_formWindow, m_widget, newClassName); +} + +bool MorphMenu::populateMenu(QWidget *w, QDesignerFormWindowInterface *fw) +{ + m_widget = nullptr; + m_formWindow = nullptr; + + // Clear menu + if (m_subMenuAction) { + m_subMenuAction->setVisible(false); + m_menu->clear(); + } + + // Checks: Must not be main container + if (w == fw->mainContainer()) + return false; + + const QStringList c = MorphWidgetCommand::candidateClasses(fw, w); + if (c.isEmpty()) + return false; + + // Pull up + m_widget = w; + m_formWindow = fw; + const QString oldClassName = WidgetFactory::classNameOf(fw->core(), w); + + if (!m_subMenuAction) { + m_subMenuAction = new QAction(tr("Morph into"), this); + m_menu = new QMenu; + m_subMenuAction->setMenu(m_menu); + } + + // Add actions + for (const auto &className : c) { + if (className != oldClassName) { + m_menu->addAction(className, + this, [this, className] { this->slotMorph(className); }); + } + } + m_subMenuAction->setVisible(true); + return true; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/morphmenu_p.h b/src/tools/designer/src/lib/shared/morphmenu_p.h new file mode 100644 index 00000000000..dbd94f6ea25 --- /dev/null +++ b/src/tools/designer/src/lib/shared/morphmenu_p.h @@ -0,0 +1,56 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef MORPH_COMMAND_H +#define MORPH_COMMAND_H + +#include "shared_global_p.h" +#include "qdesigner_formwindowcommand_p.h" + +QT_BEGIN_NAMESPACE + +class QAction; +class QMenu; + +namespace qdesigner_internal { + +/* Conveniene morph menu that acts on a single widget. */ +class QDESIGNER_SHARED_EXPORT MorphMenu : public QObject { + Q_DISABLE_COPY_MOVE(MorphMenu) + Q_OBJECT +public: + using ActionList = QList; + + explicit MorphMenu(QObject *parent = nullptr); + + void populate(QWidget *w, QDesignerFormWindowInterface *fw, ActionList& al); + void populate(QWidget *w, QDesignerFormWindowInterface *fw, QMenu& m); + +private slots: + void slotMorph(const QString &newClassName); + +private: + bool populateMenu(QWidget *w, QDesignerFormWindowInterface *fw); + + QAction *m_subMenuAction = nullptr; + QMenu *m_menu = nullptr; + QWidget *m_widget = nullptr; + QDesignerFormWindowInterface *m_formWindow = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // MORPH_COMMAND_H diff --git a/src/tools/designer/src/lib/shared/newactiondialog.cpp b/src/tools/designer/src/lib/shared/newactiondialog.cpp new file mode 100644 index 00000000000..5bbb5d72551 --- /dev/null +++ b/src/tools/designer/src/lib/shared/newactiondialog.cpp @@ -0,0 +1,216 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "newactiondialog_p.h" +#include "ui_newactiondialog.h" +#include "richtexteditor_p.h" +#include "actioneditor_p.h" +#include "formwindowbase_p.h" +#include "qdesigner_utils_p.h" +#include "iconloader_p.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +// Returns a combination of ChangeMask flags +unsigned ActionData::compare(const ActionData &rhs) const +{ + unsigned rc = 0; + if (text != rhs.text) + rc |= TextChanged; + if (name != rhs.name) + rc |= NameChanged; + if (toolTip != rhs.toolTip) + rc |= ToolTipChanged ; + if (icon != rhs.icon) + rc |= IconChanged ; + if (checkable != rhs.checkable) + rc |= CheckableChanged; + if (keysequence != rhs.keysequence) + rc |= KeysequenceChanged ; + if (menuRole.value != rhs.menuRole.value) + rc |= MenuRoleChanged ; + return rc; +} + +// -------------------- NewActionDialog +NewActionDialog::NewActionDialog(ActionEditor *parent) : + QDialog(parent, Qt::Sheet), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::NewActionDialog), + m_actionEditor(parent), + m_autoUpdateObjectName(true) +{ + m_ui->setupUi(this); + + m_ui->tooltipEditor->setTextPropertyValidationMode(ValidationRichText); + connect(m_ui->toolTipToolButton, &QAbstractButton::clicked, this, &NewActionDialog::slotEditToolTip); + connect(m_ui->editActionText, &QLineEdit::textEdited, + this, &NewActionDialog::onEditActionTextTextEdited); + connect(m_ui->editObjectName, &QLineEdit::textEdited, + this, &NewActionDialog::onEditObjectNameTextEdited); + + m_ui->keysequenceResetToolButton->setIcon(createIconSet("resetproperty.png"_L1)); + connect(m_ui->keysequenceResetToolButton, &QAbstractButton::clicked, + this, &NewActionDialog::slotResetKeySequence); + + // Clear XDG icon once a theme enum is chosen and vv. + auto *iconThemeEnumEditor = m_ui->iconThemeEnumEditor; + auto *iconThemeEditor = m_ui->iconThemeEditor; + connect(iconThemeEnumEditor, &IconThemeEnumEditor::edited, + this, [iconThemeEditor](int i) { + if (i >= 0) + iconThemeEditor->reset(); + }); + connect(iconThemeEditor, &IconThemeEditor::edited, + this, [iconThemeEnumEditor](const QString &t) { + if (!t.isEmpty()) + iconThemeEnumEditor->reset(); + }); + + const auto menuRoles = QMetaEnum::fromType(); + for (int i = 0; i < menuRoles.keyCount(); i++) { + const auto key = menuRoles.key(i); + const auto value = menuRoles.value(i); + m_ui->menuRole->addItem(QLatin1StringView(key), value); + } + + focusText(); + updateButtons(); + + QDesignerFormWindowInterface *form = parent->formWindow(); + m_ui->iconSelector->setFormEditor(form->core()); + FormWindowBase *formBase = qobject_cast(form); + + if (formBase) { + m_ui->iconSelector->setPixmapCache(formBase->pixmapCache()); + m_ui->iconSelector->setIconCache(formBase->iconCache()); + } +} + +NewActionDialog::~NewActionDialog() +{ + delete m_ui; +} + +void NewActionDialog::focusName() +{ + m_ui->editObjectName->setFocus(); +} + +void NewActionDialog::focusText() +{ + m_ui->editActionText->setFocus(); +} + +void NewActionDialog::focusTooltip() +{ + m_ui->tooltipEditor->setFocus(); +} + +void NewActionDialog::focusShortcut() +{ + m_ui->keySequenceEdit->setFocus(); +} + +void NewActionDialog::focusCheckable() +{ + m_ui->checkableCheckBox->setFocus(); +} + +void NewActionDialog::focusMenuRole() +{ + m_ui->menuRole->setFocus(); +} + +QString NewActionDialog::actionText() const +{ + return m_ui->editActionText->text(); +} + +QString NewActionDialog::actionName() const +{ + return m_ui->editObjectName->text(); +} + +ActionData NewActionDialog::actionData() const +{ + ActionData rc; + rc.text = actionText(); + rc.name = actionName(); + rc.toolTip = m_ui->tooltipEditor->text(); + rc.icon = m_ui->iconSelector->icon(); + const int themeEnum = m_ui->iconThemeEnumEditor->themeEnum(); + rc.icon.setThemeEnum(themeEnum); + rc.icon.setTheme(themeEnum == -1 ? m_ui->iconThemeEditor->theme() : QString{}); + rc.checkable = m_ui->checkableCheckBox->checkState() == Qt::Checked; + rc.keysequence = PropertySheetKeySequenceValue(m_ui->keySequenceEdit->keySequence()); + rc.menuRole.value = m_ui->menuRole->currentData().toInt(); + return rc; +} + +void NewActionDialog::setActionData(const ActionData &d) +{ + m_ui->editActionText->setText(d.text); + m_ui->editObjectName->setText(d.name); + m_ui->iconSelector->setIcon(d.icon.unthemed()); + m_ui->iconThemeEnumEditor->setThemeEnum(d.icon.themeEnum()); + m_ui->iconThemeEditor->setTheme(d.icon.theme()); + m_ui->tooltipEditor->setText(d.toolTip); + m_ui->keySequenceEdit->setKeySequence(d.keysequence.value()); + m_ui->checkableCheckBox->setCheckState(d.checkable ? Qt::Checked : Qt::Unchecked); + m_ui->menuRole->setCurrentIndex(m_ui->menuRole->findData(d.menuRole.value)); + + // Suppress updating of the object name from the text for existing actions. + m_autoUpdateObjectName = d.name.isEmpty(); + updateButtons(); +} + +void NewActionDialog::onEditActionTextTextEdited(const QString &text) +{ + if (m_autoUpdateObjectName) + m_ui->editObjectName->setText(ActionEditor::actionTextToName(text)); + + updateButtons(); +} + +void NewActionDialog::onEditObjectNameTextEdited(const QString&) +{ + updateButtons(); + m_autoUpdateObjectName = false; +} + +void NewActionDialog::slotEditToolTip() +{ + const QString oldToolTip = m_ui->tooltipEditor->text(); + RichTextEditorDialog richTextDialog(m_actionEditor->core(), this); + richTextDialog.setText(oldToolTip); + if (richTextDialog.showDialog() == QDialog::Rejected) + return; + const QString newToolTip = richTextDialog.text(); + if (newToolTip != oldToolTip) + m_ui->tooltipEditor->setText(newToolTip); +} + +void NewActionDialog::slotResetKeySequence() +{ + m_ui->keySequenceEdit->setKeySequence(QKeySequence()); + m_ui->keySequenceEdit->setFocus(Qt::MouseFocusReason); +} + +void NewActionDialog::updateButtons() +{ + QPushButton *okButton = m_ui->buttonBox->button(QDialogButtonBox::Ok); + okButton->setEnabled(!actionText().isEmpty() && !actionName().isEmpty()); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/newactiondialog.ui b/src/tools/designer/src/lib/shared/newactiondialog.ui new file mode 100644 index 00000000000..7fd99842ba3 --- /dev/null +++ b/src/tools/designer/src/lib/shared/newactiondialog.ui @@ -0,0 +1,306 @@ + + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::NewActionDialog + + + + 0 + 0 + 381 + 291 + + + + New Action... + + + + + + QFormLayout::ExpandingFieldsGrow + + + + + &Text: + + + editActionText + + + + + + + + 255 + 0 + + + + + + + + Object &name: + + + editObjectName + + + + + + + + + + T&oolTip: + + + tooltipEditor + + + + + + + + + + 0 + 0 + + + + + + + + ... + + + + + + + + + Icon &XDG theme: + + + iconThemeEditor + + + + + + + + + + &Icon: + + + iconSelector + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + &Checkable: + + + checkableCheckBox + + + + + + + &Shortcut: + + + keySequenceEdit + + + + + + + + + + 0 + 0 + + + + + + + + ... + + + + + + + + + &Menu role: + + + menuRole + + + + + + + + + + Icon &theme: + + + iconThemeEnumEditor + + + + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + qdesigner_internal::IconSelector + QWidget +
iconselector_p.h
+ 1 +
+ + TextPropertyEditor + QWidget +
textpropertyeditor_p.h
+ 1 +
+ + qdesigner_internal::IconThemeEditor + QWidget +
iconselector_p.h
+ 1 +
+ + qdesigner_internal::IconThemeEnumEditor + QWidget +
iconselector_p.h
+ 1 +
+
+ + editActionText + editObjectName + + + + + buttonBox + accepted() + qdesigner_internal::NewActionDialog + accept() + + + 165 + 162 + + + 291 + 94 + + + + + buttonBox + rejected() + qdesigner_internal::NewActionDialog + reject() + + + 259 + 162 + + + 293 + 128 + + + + +
diff --git a/src/tools/designer/src/lib/shared/newactiondialog_p.h b/src/tools/designer/src/lib/shared/newactiondialog_p.h new file mode 100644 index 00000000000..de42d16f00b --- /dev/null +++ b/src/tools/designer/src/lib/shared/newactiondialog_p.h @@ -0,0 +1,100 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef NEWACTIONDIALOG_P_H +#define NEWACTIONDIALOG_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qdesigner_utils_p.h" // PropertySheetIconValue + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +namespace Ui { + class NewActionDialog; +} + +class ActionEditor; + +struct ActionData { + + enum ChangeMask { + TextChanged = 0x1, NameChanged = 0x2, ToolTipChanged = 0x4, + IconChanged = 0x8, CheckableChanged = 0x10, KeysequenceChanged = 0x20, + MenuRoleChanged = 0x40 + }; + + // Returns a combination of ChangeMask flags + unsigned compare(const ActionData &rhs) const; + + QString text; + QString name; + QString toolTip; + PropertySheetIconValue icon; + bool checkable{false}; + PropertySheetKeySequenceValue keysequence; + PropertySheetFlagValue menuRole; + + friend bool comparesEqual(const ActionData &lhs, const ActionData &rhs) noexcept + { + return lhs.compare(rhs) == 0; + } + Q_DECLARE_EQUALITY_COMPARABLE(ActionData) +}; + +class NewActionDialog: public QDialog +{ + Q_OBJECT +public: + explicit NewActionDialog(ActionEditor *parent); + ~NewActionDialog() override; + + ActionData actionData() const; + void setActionData(const ActionData &d); + + QString actionText() const; + QString actionName() const; + +public slots: + void focusName(); + void focusText(); + void focusTooltip(); + void focusShortcut(); + void focusCheckable(); + void focusMenuRole(); + +private slots: + void onEditActionTextTextEdited(const QString &text); + void onEditObjectNameTextEdited(const QString &text); + + void slotEditToolTip(); + void slotResetKeySequence(); + +private: + Ui::NewActionDialog *m_ui; + ActionEditor *m_actionEditor; + bool m_autoUpdateObjectName; + + void updateButtons(); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // NEWACTIONDIALOG_P_H diff --git a/src/tools/designer/src/lib/shared/newformwidget.cpp b/src/tools/designer/src/lib/shared/newformwidget.cpp new file mode 100644 index 00000000000..383b3fc5a9a --- /dev/null +++ b/src/tools/designer/src/lib/shared/newformwidget.cpp @@ -0,0 +1,560 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "newformwidget_p.h" +#include "ui_newformwidget.h" +#include "qdesigner_formbuilder_p.h" +#include "sheet_delegate_p.h" +#include "widgetdatabase_p.h" +#include "shared_settings_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { profileComboIndexOffset = 1 }; +enum { debugNewFormWidget = 0 }; + +enum NewForm_CustomRole { + // File name (templates from resources, paths) + TemplateNameRole = Qt::UserRole + 100, + // Class name (widgets from Widget data base) + ClassNameRole = Qt::UserRole + 101 +}; + +static constexpr auto newFormObjectNameC = "Form"_L1; + +// Create a form name for an arbitrary class. If it is Qt, qtify it, +// else return "Form". +static QString formName(const QString &className) +{ + if (!className.startsWith(u'Q')) + return newFormObjectNameC; + QString rc = className; + rc.remove(0, 1); + return rc; +} + +namespace qdesigner_internal { + +struct TemplateSize { + const char *name; + int width; + int height; +}; + +static const struct TemplateSize templateSizes[] = +{ + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "Default size"), 0, 0 }, + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "QVGA portrait (240x320)"), 240, 320 }, + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "QVGA landscape (320x240)"), 320, 240 }, + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "VGA portrait (480x640)"), 480, 640 }, + { QT_TRANSLATE_NOOP("qdesigner_internal::NewFormWidget", "VGA landscape (640x480)"), 640, 480 } +}; + +/* -------------- NewForm dialog. + * Designer takes new form templates from: + * 1) Files located in directories specified in resources + * 2) Files located in directories specified as user templates + * 3) XML from container widgets deemed usable for form templates by the widget + * database + * 4) XML from custom container widgets deemed usable for form templates by the + * widget database + * + * The widget database provides helper functions to obtain lists of names + * and xml for 3,4. + * + * Fixed-size forms for embedded platforms are obtained as follows: + * 1) If the origin is a file: + * - Check if the file exists in the subdirectory "/x/" of + * the path (currently the case for the dialog box because the button box + * needs to be positioned) + * - Scale the form using the QWidgetDatabase::scaleFormTemplate routine. + * 2) If the origin is XML: + * - Scale the form using the QWidgetDatabase::scaleFormTemplate routine. + * + * The tree widget item roles indicate which type of entry it is + * (TemplateNameRole = file name 1,2, ClassNameRole = class name 3,4) + */ + +NewFormWidget::NewFormWidget(QDesignerFormEditorInterface *core, QWidget *parentWidget) : + QDesignerNewFormWidgetInterface(parentWidget), + m_core(core), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::NewFormWidget), + m_currentItem(nullptr), + m_acceptedItem(nullptr) +{ + // ### FIXME Qt 8: Remove (QTBUG-96005) +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + QDesignerSharedSettings::migrateTemplates(); +#endif + + m_ui->setupUi(this); + m_ui->treeWidget->setItemDelegate(new qdesigner_internal::SheetDelegate(m_ui->treeWidget, this)); + m_ui->treeWidget->header()->hide(); + m_ui->treeWidget->header()->setStretchLastSection(true); + m_ui->lblPreview->setBackgroundRole(QPalette::Base); + + connect(m_ui->treeWidget, &QTreeWidget::itemActivated, + this, &NewFormWidget::treeWidgetItemActivated); + connect(m_ui->treeWidget, &QTreeWidget::currentItemChanged, + this, &NewFormWidget::treeWidgetCurrentItemChanged); + connect(m_ui->treeWidget, &QTreeWidget::itemPressed, + this, &NewFormWidget::treeWidgetItemPressed); + + QDesignerSharedSettings settings(m_core); + + QString uiExtension = u"ui"_s; + QString templatePath = u":/qt-project.org/designer/templates/forms"_s; + + QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core); + if (lang) { + templatePath = u":/templates/forms"_s; + uiExtension = lang->uiExtension(); + } + + // Resource templates + const QString formTemplate = settings.formTemplate(); + QTreeWidgetItem *selectedItem = nullptr; + loadFrom(templatePath, true, uiExtension, formTemplate, selectedItem); + // Additional template paths + const QStringList formTemplatePaths = settings.formTemplatePaths(); + for (const auto &ftp : formTemplatePaths) + loadFrom(ftp, false, uiExtension, formTemplate, selectedItem); + + // Widgets/custom widgets + if (!lang) { + //: New Form Dialog Categories + loadFrom(tr("Widgets"), qdesigner_internal::WidgetDataBase::formWidgetClasses(core), formTemplate, selectedItem); + loadFrom(tr("Custom Widgets"), qdesigner_internal::WidgetDataBase::customFormWidgetClasses(core), formTemplate, selectedItem); + } + + // Still no selection - default to first item + if (selectedItem == nullptr && m_ui->treeWidget->topLevelItemCount() != 0) { + QTreeWidgetItem *firstTopLevel = m_ui->treeWidget->topLevelItem(0); + if (firstTopLevel->childCount() > 0) + selectedItem = firstTopLevel->child(0); + } + + // Open parent, select and make visible + if (selectedItem) { + m_ui->treeWidget->setCurrentItem(selectedItem); + selectedItem->setSelected(true); + m_ui->treeWidget->scrollToItem(selectedItem->parent()); + } + // Fill profile combo + m_deviceProfiles = settings.deviceProfiles(); + m_ui->profileComboBox->addItem(tr("None")); + connect(m_ui->profileComboBox, + &QComboBox::currentIndexChanged, + this, &NewFormWidget::slotDeviceProfileIndexChanged); + if (m_deviceProfiles.isEmpty()) { + m_ui->profileComboBox->setEnabled(false); + } else { + for (const auto &deviceProfile : std::as_const(m_deviceProfiles)) + m_ui->profileComboBox->addItem(deviceProfile.name()); + const int ci = settings.currentDeviceProfileIndex(); + if (ci >= 0) + m_ui->profileComboBox->setCurrentIndex(ci + profileComboIndexOffset); + } + // Fill size combo + for (const TemplateSize &t : templateSizes) + m_ui->sizeComboBox->addItem(tr(t.name), QSize(t.width, t.height)); + + setTemplateSize(settings.newFormSize()); + + if (debugNewFormWidget) + qDebug() << Q_FUNC_INFO << "Leaving"; +} + +NewFormWidget::~NewFormWidget() +{ + QDesignerSharedSettings settings (m_core); + settings.setNewFormSize(templateSize()); + // Do not change previously stored item if dialog was rejected + if (m_acceptedItem) + settings.setFormTemplate(m_acceptedItem->text(0)); + delete m_ui; +} + +void NewFormWidget::treeWidgetCurrentItemChanged(QTreeWidgetItem *current) +{ + if (debugNewFormWidget) + qDebug() << Q_FUNC_INFO << current; + if (!current) + return; + + if (!current->parent()) { // Top level item: Ensure expanded when browsing down + return; + } + + m_currentItem = current; + + emit currentTemplateChanged(showCurrentItemPixmap()); +} + +bool NewFormWidget::showCurrentItemPixmap() +{ + bool rc = false; + if (m_currentItem) { + const QPixmap pixmap = formPreviewPixmap(m_currentItem); + if (pixmap.isNull()) { + m_ui->lblPreview->setText(tr("Error loading form")); + } else { + m_ui->lblPreview->setPixmap(pixmap); + rc = true; + } + } + return rc; +} + +void NewFormWidget::treeWidgetItemActivated(QTreeWidgetItem *item) +{ + if (debugNewFormWidget) + qDebug() << Q_FUNC_INFO << item; + + if (item->data(0, TemplateNameRole).isValid() || item->data(0, ClassNameRole).isValid()) + emit templateActivated(); +} + +QPixmap NewFormWidget::formPreviewPixmap(const QTreeWidgetItem *item) +{ + // Cache pixmaps per item/device profile + const ItemPixmapCacheKey cacheKey(item, profileComboIndex()); + auto it = m_itemPixmapCache.find(cacheKey); + if (it == m_itemPixmapCache.end()) { + // file or string? + const QVariant fileName = item->data(0, TemplateNameRole); + QPixmap rc; + if (fileName.metaType().id() == QMetaType::QString) { + rc = formPreviewPixmap(fileName.toString()); + } else { + const QVariant classNameV = item->data(0, ClassNameRole); + Q_ASSERT(classNameV.metaType().id() == QMetaType::QString); + const QString className = classNameV.toString(); + QByteArray data = qdesigner_internal::WidgetDataBase::formTemplate(m_core, className, formName(className)).toUtf8(); + QBuffer buffer(&data); + buffer.open(QIODevice::ReadOnly); + rc = formPreviewPixmap(buffer); + } + if (rc.isNull()) // Retry invalid ones + return rc; + it = m_itemPixmapCache.insert(cacheKey, rc); + } + return it.value(); +} + +QPixmap NewFormWidget::formPreviewPixmap(const QString &fileName) const +{ + QFile f(fileName); + if (f.open(QFile::ReadOnly)) { + QFileInfo fi(fileName); + const QPixmap rc = formPreviewPixmap(f, fi.absolutePath()); + f.close(); + return rc; + } + qWarning() << "The file " << fileName << " could not be opened: " << f.errorString(); + return QPixmap(); +} + +QImage NewFormWidget::grabForm(QDesignerFormEditorInterface *core, + QIODevice &file, + const QString &workingDir, + const qdesigner_internal::DeviceProfile &dp) +{ + qdesigner_internal::NewFormWidgetFormBuilder + formBuilder(core, dp); + if (!workingDir.isEmpty()) + formBuilder.setWorkingDirectory(workingDir); + + QWidget *widget = formBuilder.load(&file, nullptr); + if (!widget) + return QImage(); + + const QPixmap pixmap = widget->grab(QRect(0, 0, -1, -1)); + widget->deleteLater(); + return pixmap.toImage(); +} + +QPixmap NewFormWidget::formPreviewPixmap(QIODevice &file, const QString &workingDir) const +{ + const QSizeF screenSize(screen()->geometry().size()); + const int previewSize = qRound(screenSize.width() / 7.5); // 256 on 1920px screens. + const int margin = previewSize / 32 - 1; // 7 on 1920px screens. + const int shadow = margin; + + const QImage wimage = grabForm(m_core, file, workingDir, currentDeviceProfile()); + if (wimage.isNull()) + return QPixmap(); + const qreal devicePixelRatio = wimage.devicePixelRatioF(); + const QSize imageSize(previewSize - margin * 2, previewSize - margin * 2); + QImage image = wimage.scaled((QSizeF(imageSize) * devicePixelRatio).toSize(), + Qt::KeepAspectRatio, Qt::SmoothTransformation); + image.setDevicePixelRatio(devicePixelRatio); + + QImage dest((QSizeF(previewSize, previewSize) * devicePixelRatio).toSize(), + QImage::Format_ARGB32_Premultiplied); + dest.setDevicePixelRatio(devicePixelRatio); + dest.fill(0); + + QPainter p(&dest); + p.drawImage(margin, margin, image); + + p.setPen(QPen(palette().brush(QPalette::WindowText), 0)); + + p.drawRect(QRectF(margin - 1, margin - 1, imageSize.width() + 1.5, imageSize.height() + 1.5)); + + const QColor dark(Qt::darkGray); + const QColor light(Qt::transparent); + + // right shadow + { + const QRect rect(margin + imageSize.width() + 1, margin + shadow, shadow, imageSize.height() - shadow + 1); + QLinearGradient lg(rect.topLeft(), rect.topRight()); + lg.setColorAt(0, dark); + lg.setColorAt(1, light); + p.fillRect(rect, lg); + } + + // bottom shadow + { + const QRect rect(margin + shadow, margin + imageSize.height() + 1, imageSize.width() - shadow + 1, shadow); + QLinearGradient lg(rect.topLeft(), rect.bottomLeft()); + lg.setColorAt(0, dark); + lg.setColorAt(1, light); + p.fillRect(rect, lg); + } + + // bottom/right corner shadow + { + const QRect rect(margin + imageSize.width() + 1, margin + imageSize.height() + 1, shadow, shadow); + QRadialGradient g(rect.topLeft(), shadow - 1); + g.setColorAt(0, dark); + g.setColorAt(1, light); + p.fillRect(rect, g); + } + + // top/right corner + { + const QRect rect(margin + imageSize.width() + 1, margin, shadow, shadow); + QRadialGradient g(rect.bottomLeft(), shadow - 1); + g.setColorAt(0, dark); + g.setColorAt(1, light); + p.fillRect(rect, g); + } + + // bottom/left corner + { + const QRect rect(margin, margin + imageSize.height() + 1, shadow, shadow); + QRadialGradient g(rect.topRight(), shadow - 1); + g.setColorAt(0, dark); + g.setColorAt(1, light); + p.fillRect(rect, g); + } + + p.end(); + + return QPixmap::fromImage(dest); +} + +void NewFormWidget::loadFrom(const QString &path, bool resourceFile, const QString &uiExtension, + const QString &selectedItem, QTreeWidgetItem *&selectedItemFound) +{ + const QDir dir(path); + + if (!dir.exists()) + return; + + // Iterate through the directory and add the templates + const QFileInfoList list = dir.entryInfoList(QStringList{"*."_L1 + uiExtension}, + QDir::Files); + + if (list.isEmpty()) + return; + + const QChar separator = resourceFile ? QChar(u'/') + : QDir::separator(); + QTreeWidgetItem *root = new QTreeWidgetItem(m_ui->treeWidget); + root->setFlags(root->flags() & ~Qt::ItemIsSelectable); + // Try to get something that is easy to read. + QString visiblePath = path; + int index = visiblePath.lastIndexOf(separator); + if (index != -1) { + // try to find a second slash, just to be a bit better. + const int index2 = visiblePath.lastIndexOf(separator, index - 1); + if (index2 != -1) + index = index2; + visiblePath = visiblePath.mid(index + 1); + visiblePath = QDir::toNativeSeparators(visiblePath); + } + + root->setText(0, visiblePath.replace(u'_', u' ')); + root->setToolTip(0, path); + + for (const auto &fi : list) { + if (!fi.isFile()) + continue; + + QTreeWidgetItem *item = new QTreeWidgetItem(root); + const QString text = fi.baseName().replace(u'_', u' '); + if (selectedItemFound == nullptr && text == selectedItem) + selectedItemFound = item; + item->setText(0, text); + item->setData(0, TemplateNameRole, fi.absoluteFilePath()); + } +} + +void NewFormWidget::loadFrom(const QString &title, const QStringList &nameList, + const QString &selectedItem, QTreeWidgetItem *&selectedItemFound) +{ + if (nameList.isEmpty()) + return; + QTreeWidgetItem *root = new QTreeWidgetItem(m_ui->treeWidget); + root->setFlags(root->flags() & ~Qt::ItemIsSelectable); + root->setText(0, title); + for (const auto &text : nameList) { + QTreeWidgetItem *item = new QTreeWidgetItem(root); + item->setText(0, text); + if (selectedItemFound == nullptr && text == selectedItem) + selectedItemFound = item; + item->setData(0, ClassNameRole, text); + } +} + +void NewFormWidget::treeWidgetItemPressed(QTreeWidgetItem *item) +{ + if (item && !item->parent()) + item->setExpanded(!item->isExpanded()); +} + +QSize NewFormWidget::templateSize() const +{ + return m_ui->sizeComboBox->itemData(m_ui->sizeComboBox->currentIndex()).toSize(); +} + +void NewFormWidget::setTemplateSize(const QSize &s) +{ + const int index = s.isNull() ? 0 : m_ui->sizeComboBox->findData(s); + if (index != -1) + m_ui->sizeComboBox->setCurrentIndex(index); +} + +static QString readAll(const QString &fileName, QString *errorMessage) +{ + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly|QIODevice::Text)) { + *errorMessage = NewFormWidget::tr("Unable to open the form template file '%1': %2").arg(fileName, file.errorString()); + return QString(); + } + return QString::fromUtf8(file.readAll()); +} + +QString NewFormWidget::itemToTemplate(const QTreeWidgetItem *item, QString *errorMessage) const +{ + const QSize size = templateSize(); + // file name or string contents? + const QVariant templateFileName = item->data(0, TemplateNameRole); + if (templateFileName.metaType().id() == QMetaType::QString) { + const QString fileName = templateFileName.toString(); + // No fixed size: just open. + if (size.isNull()) + return readAll(fileName, errorMessage); + // try to find a file matching the size, like "../640x480/xx.ui" + const QFileInfo fiBase(fileName); + QString sizeFileName; + QTextStream(&sizeFileName) << fiBase.path() << QDir::separator() + << size.width() << 'x' << size.height() << QDir::separator() + << fiBase.fileName(); + if (QFileInfo(sizeFileName).isFile()) + return readAll(sizeFileName, errorMessage); + // Nothing found, scale via DOM/temporary file + QString contents = readAll(fileName, errorMessage); + if (!contents.isEmpty()) + contents = qdesigner_internal::WidgetDataBase::scaleFormTemplate(contents, size, false); + return contents; + } + // Content. + const QString className = item->data(0, ClassNameRole).toString(); + QString contents = qdesigner_internal::WidgetDataBase::formTemplate(m_core, className, formName(className)); + if (!size.isNull()) + contents = qdesigner_internal::WidgetDataBase::scaleFormTemplate(contents, size, false); + return contents; +} + +void NewFormWidget::slotDeviceProfileIndexChanged(int idx) +{ + // Store index for form windows to take effect and refresh pixmap + QDesignerSharedSettings settings(m_core); + settings.setCurrentDeviceProfileIndex(idx - profileComboIndexOffset); + showCurrentItemPixmap(); +} + +int NewFormWidget::profileComboIndex() const +{ + return m_ui->profileComboBox->currentIndex(); +} + +qdesigner_internal::DeviceProfile NewFormWidget::currentDeviceProfile() const +{ + const int ci = profileComboIndex(); + if (ci > 0) + return m_deviceProfiles.at(ci - profileComboIndexOffset); + return qdesigner_internal::DeviceProfile(); +} + +bool NewFormWidget::hasCurrentTemplate() const +{ + return m_currentItem != nullptr; +} + +QString NewFormWidget::currentTemplateI(QString *ptrToErrorMessage) +{ + if (m_currentItem == nullptr) { + *ptrToErrorMessage = tr("Internal error: No template selected."); + return QString(); + } + const QString contents = itemToTemplate(m_currentItem, ptrToErrorMessage); + if (contents.isEmpty()) + return contents; + + m_acceptedItem = m_currentItem; + return contents; +} + +QString NewFormWidget::currentTemplate(QString *ptrToErrorMessage) +{ + if (ptrToErrorMessage) + return currentTemplateI(ptrToErrorMessage); + // Do not loose the error + QString errorMessage; + const QString contents = currentTemplateI(&errorMessage); + if (!errorMessage.isEmpty()) + qWarning("%s", errorMessage.toUtf8().constData()); + return contents; +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/newformwidget.ui b/src/tools/designer/src/lib/shared/newformwidget.ui new file mode 100644 index 00000000000..8cf6f638bc8 --- /dev/null +++ b/src/tools/designer/src/lib/shared/newformwidget.ui @@ -0,0 +1,155 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::NewFormWidget + + + + 0 + 0 + 480 + 194 + + + + + 6 + + + 1 + + + + + + 200 + 0 + + + + + 128 + 128 + + + + false + + + 1 + + + + 0 + + + + + + + + + + + 0 + 0 + + + + 1 + + + Choose a template for a preview + + + Qt::AlignCenter + + + 5 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 7 + 0 + + + + + + + + Embedded Design + + + + + + + + + + + + Device: + + + + + + + Screen Size: + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + diff --git a/src/tools/designer/src/lib/shared/newformwidget_p.h b/src/tools/designer/src/lib/shared/newformwidget_p.h new file mode 100644 index 00000000000..d9acbd2a361 --- /dev/null +++ b/src/tools/designer/src/lib/shared/newformwidget_p.h @@ -0,0 +1,105 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef NEWFORMWIDGET_H +#define NEWFORMWIDGET_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "shared_global_p.h" +#include "deviceprofile_p.h" + +#include + +#include + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QIODevice; +class QTreeWidgetItem; + +namespace qdesigner_internal { + +namespace Ui { + class NewFormWidget; +} + +class QDESIGNER_SHARED_EXPORT NewFormWidget : public QDesignerNewFormWidgetInterface +{ + Q_OBJECT + Q_DISABLE_COPY_MOVE(NewFormWidget) + +public: + using DeviceProfileList = QList; + + explicit NewFormWidget(QDesignerFormEditorInterface *core, QWidget *parentWidget); + ~NewFormWidget() override; + + bool hasCurrentTemplate() const override; + QString currentTemplate(QString *errorMessage = nullptr) override; + + // Convenience for implementing file dialogs with preview + static QImage grabForm(QDesignerFormEditorInterface *core, + QIODevice &file, + const QString &workingDir, + const qdesigner_internal::DeviceProfile &dp); + +private slots: + void treeWidgetItemActivated(QTreeWidgetItem *item); + void treeWidgetCurrentItemChanged(QTreeWidgetItem *current); + void treeWidgetItemPressed(QTreeWidgetItem *item); + void slotDeviceProfileIndexChanged(int idx); + +private: + QPixmap formPreviewPixmap(const QString &fileName) const; + QPixmap formPreviewPixmap(QIODevice &file, const QString &workingDir = QString()) const; + QPixmap formPreviewPixmap(const QTreeWidgetItem *item); + + void loadFrom(const QString &path, bool resourceFile, const QString &uiExtension, + const QString &selectedItem, QTreeWidgetItem *&selectedItemFound); + void loadFrom(const QString &title, const QStringList &nameList, + const QString &selectedItem, QTreeWidgetItem *&selectedItemFound); + +private: + QString itemToTemplate(const QTreeWidgetItem *item, QString *errorMessage) const; + QString currentTemplateI(QString *ptrToErrorMessage); + + QSize templateSize() const; + void setTemplateSize(const QSize &s); + int profileComboIndex() const; + qdesigner_internal::DeviceProfile currentDeviceProfile() const; + bool showCurrentItemPixmap(); + + // Pixmap cache (item, profile combo index) + using ItemPixmapCacheKey = std::pair; + using ItemPixmapCache = QMap; + ItemPixmapCache m_itemPixmapCache; + + QDesignerFormEditorInterface *m_core; + Ui::NewFormWidget *m_ui; + QTreeWidgetItem *m_currentItem; + QTreeWidgetItem *m_acceptedItem; + DeviceProfileList m_deviceProfiles; +}; + +} + +QT_END_NAMESPACE + +#endif // NEWFORMWIDGET_H diff --git a/src/tools/designer/src/lib/shared/orderdialog.cpp b/src/tools/designer/src/lib/shared/orderdialog.cpp new file mode 100644 index 00000000000..faa13501aa6 --- /dev/null +++ b/src/tools/designer/src/lib/shared/orderdialog.cpp @@ -0,0 +1,156 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "orderdialog_p.h" +#include "iconloader_p.h" +#include "ui_orderdialog.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// OrderDialog: Used to reorder the pages of QStackedWidget and QToolBox. +// Provides up and down buttons as well as DnD via QAbstractItemView::InternalMove mode +namespace qdesigner_internal { + +OrderDialog::OrderDialog(QWidget *parent) : + QDialog(parent), + m_ui(new QT_PREPEND_NAMESPACE(qdesigner_internal)::Ui::OrderDialog), + m_format(PageOrderFormat) +{ + m_ui->setupUi(this); + m_ui->upButton->setIcon(createIconSet("up.png"_L1)); + m_ui->downButton->setIcon(createIconSet("down.png"_L1)); + m_ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + connect(m_ui->buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, + this, &OrderDialog::slotReset); + // Catch the remove operation of a DnD operation in QAbstractItemView::InternalMove mode to enable buttons + // Selection mode is 'contiguous' to enable DnD of groups + connect(m_ui->pageList->model(), &QAbstractItemModel::rowsRemoved, + this, &OrderDialog::slotEnableButtonsAfterDnD); + + connect(m_ui->upButton, &QAbstractButton::clicked, this, &OrderDialog::upButtonClicked); + connect(m_ui->downButton, &QAbstractButton::clicked, this, &OrderDialog::downButtonClicked); + connect(m_ui->pageList, &QListWidget::currentRowChanged, + this, &OrderDialog::pageListCurrentRowChanged); + + m_ui->upButton->setEnabled(false); + m_ui->downButton->setEnabled(false); +} + +OrderDialog::~OrderDialog() +{ + delete m_ui; +} + +void OrderDialog::setDescription(const QString &d) +{ + m_ui->groupBox->setTitle(d); +} + +void OrderDialog::setPageList(const QWidgetList &pages) +{ + // The QWidget* are stored in a map indexed by the old index. + // The old index is set as user data on the item instead of the QWidget* + // because DnD is enabled which requires the user data to serializable + m_orderMap.clear(); + const qsizetype count = pages.size(); + for (qsizetype i = 0; i < count; ++i) + m_orderMap.insert(int(i), pages.at(i)); + buildList(); +} + +void OrderDialog::buildList() +{ + m_ui->pageList->clear(); + for (auto it = m_orderMap.cbegin(), cend = m_orderMap.cend(); it != cend; ++it) { + QListWidgetItem *item = new QListWidgetItem(); + const int index = it.key(); + switch (m_format) { + case PageOrderFormat: + item->setText(tr("Index %1 (%2)").arg(index).arg(it.value()->objectName())); + break; + case TabOrderFormat: + item->setText(tr("%1 %2").arg(index+1).arg(it.value()->objectName())); + break; + } + item->setData(Qt::UserRole, QVariant(index)); + m_ui->pageList->addItem(item); + } + + if (m_ui->pageList->count() > 0) + m_ui->pageList->setCurrentRow(0); +} + +void OrderDialog::slotReset() +{ + buildList(); +} + +QWidgetList OrderDialog::pageList() const +{ + QWidgetList rc; + const int count = m_ui->pageList->count(); + for (int i=0; i < count; ++i) { + const int oldIndex = m_ui->pageList->item(i)->data(Qt::UserRole).toInt(); + rc.append(m_orderMap.value(oldIndex)); + } + return rc; +} + +void OrderDialog::upButtonClicked() +{ + const int row = m_ui->pageList->currentRow(); + if (row <= 0) + return; + + m_ui->pageList->insertItem(row - 1, m_ui->pageList->takeItem(row)); + m_ui->pageList->setCurrentRow(row - 1); +} + +void OrderDialog::downButtonClicked() +{ + const int row = m_ui->pageList->currentRow(); + if (row == -1 || row == m_ui->pageList->count() - 1) + return; + + m_ui->pageList->insertItem(row + 1, m_ui->pageList->takeItem(row)); + m_ui->pageList->setCurrentRow(row + 1); +} + +void OrderDialog::slotEnableButtonsAfterDnD() +{ + enableButtons(m_ui->pageList->currentRow()); +} + +void OrderDialog::pageListCurrentRowChanged(int r) +{ + enableButtons(r); +} + +void OrderDialog::enableButtons(int r) +{ + m_ui->upButton->setEnabled(r > 0); + m_ui->downButton->setEnabled(r >= 0 && r < m_ui->pageList->count() - 1); +} + +QWidgetList OrderDialog::pagesOfContainer(const QDesignerFormEditorInterface *core, QWidget *container) +{ + QWidgetList rc; + if (QDesignerContainerExtension* ce = qt_extension(core->extensionManager(), container)) { + const int count = ce->count(); + for (int i = 0; i < count ;i ++) + rc.push_back(ce->widget(i)); + } + return rc; +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/orderdialog.ui b/src/tools/designer/src/lib/shared/orderdialog.ui new file mode 100644 index 00000000000..0af976dfe89 --- /dev/null +++ b/src/tools/designer/src/lib/shared/orderdialog.ui @@ -0,0 +1,162 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + qdesigner_internal::OrderDialog + + + + 0 + 0 + 467 + 310 + + + + Change Page Order + + + + + + Page Order + + + + 6 + + + 9 + + + 9 + + + 9 + + + 9 + + + + + + 344 + 0 + + + + QAbstractItemView::InternalMove + + + QAbstractItemView::ContiguousSelection + + + QListView::Snap + + + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Move page up + + + + + + + Move page down + + + + + + + + 0 + 0 + + + + Qt::Vertical + + + + 20 + 99 + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + + + + + + + + + buttonBox + accepted() + qdesigner_internal::OrderDialog + accept() + + + 50 + 163 + + + 6 + 151 + + + + + buttonBox + rejected() + qdesigner_internal::OrderDialog + reject() + + + 300 + 160 + + + 348 + 148 + + + + + diff --git a/src/tools/designer/src/lib/shared/orderdialog_p.h b/src/tools/designer/src/lib/shared/orderdialog_p.h new file mode 100644 index 00000000000..2fb506f071c --- /dev/null +++ b/src/tools/designer/src/lib/shared/orderdialog_p.h @@ -0,0 +1,75 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#ifndef ORDERDIALOG_P_H +#define ORDERDIALOG_P_H + +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +namespace Ui { + class OrderDialog; +} + +class QDESIGNER_SHARED_EXPORT OrderDialog: public QDialog +{ + Q_OBJECT +public: + OrderDialog(QWidget *parent); + ~OrderDialog() override; + + static QWidgetList pagesOfContainer(const QDesignerFormEditorInterface *core, QWidget *container); + + void setPageList(const QWidgetList &pages); + QWidgetList pageList() const; + + void setDescription(const QString &d); + + enum Format { // Display format + PageOrderFormat, // Container pages, ranging 0..[n-1] + TabOrderFormat // List of widgets, ranging 1..1 + }; + + void setFormat(Format f) { m_format = f; } + Format format() const { return m_format; } + +private slots: + void upButtonClicked(); + void downButtonClicked(); + void pageListCurrentRowChanged(int row); + void slotEnableButtonsAfterDnD(); + void slotReset(); + +private: + void buildList(); + void enableButtons(int r); + + QMap m_orderMap; + Ui::OrderDialog* m_ui; + Format m_format; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // ORDERDIALOG_P_H diff --git a/src/tools/designer/src/lib/shared/plaintexteditor.cpp b/src/tools/designer/src/lib/shared/plaintexteditor.cpp new file mode 100644 index 00000000000..ad6708c9fe9 --- /dev/null +++ b/src/tools/designer/src/lib/shared/plaintexteditor.cpp @@ -0,0 +1,81 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "plaintexteditor_p.h" + +#include +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto PlainTextDialogC = "PlainTextDialog"_L1; +static constexpr auto PlainTextEditorGeometryC = "Geometry"_L1; + +namespace qdesigner_internal { + +PlainTextEditorDialog::PlainTextEditorDialog(QDesignerFormEditorInterface *core, QWidget *parent) : + QDialog(parent), + m_editor(new QPlainTextEdit), + m_core(core) +{ + setWindowTitle(tr("Edit text")); + + QVBoxLayout *vlayout = new QVBoxLayout(this); + vlayout->addWidget(m_editor); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal); + QPushButton *ok_button = buttonBox->button(QDialogButtonBox::Ok); + ok_button->setDefault(true); + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + vlayout->addWidget(buttonBox); + + QDesignerSettingsInterface *settings = core->settingsManager(); + settings->beginGroup(PlainTextDialogC); + + if (settings->contains(PlainTextEditorGeometryC)) + restoreGeometry(settings->value(PlainTextEditorGeometryC).toByteArray()); + + settings->endGroup(); +} + +PlainTextEditorDialog::~PlainTextEditorDialog() +{ + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(PlainTextDialogC); + + settings->setValue(PlainTextEditorGeometryC, saveGeometry()); + settings->endGroup(); +} + +int PlainTextEditorDialog::showDialog() +{ + m_editor->setFocus(); + return exec(); +} + +void PlainTextEditorDialog::setDefaultFont(const QFont &font) +{ + m_editor->setFont(font); +} + +void PlainTextEditorDialog::setText(const QString &text) +{ + m_editor->setPlainText(text); +} + +QString PlainTextEditorDialog::text() const +{ + return m_editor->toPlainText(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/plaintexteditor_p.h b/src/tools/designer/src/lib/shared/plaintexteditor_p.h new file mode 100644 index 00000000000..988a5f5cb87 --- /dev/null +++ b/src/tools/designer/src/lib/shared/plaintexteditor_p.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PLAINTEXTEDITOR_H +#define PLAINTEXTEDITOR_H + +#include +#include "shared_global_p.h" + +QT_BEGIN_NAMESPACE + +class QPlainTextEdit; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT PlainTextEditorDialog : public QDialog +{ + Q_OBJECT +public: + explicit PlainTextEditorDialog(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~PlainTextEditorDialog(); + + int showDialog(); + + void setDefaultFont(const QFont &font); + + void setText(const QString &text); + QString text() const; + +private: + QPlainTextEdit *m_editor; + QDesignerFormEditorInterface *m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // RITCHTEXTEDITOR_H diff --git a/src/tools/designer/src/lib/shared/plugindialog.cpp b/src/tools/designer/src/lib/shared/plugindialog.cpp new file mode 100644 index 00000000000..fb4ec4e6ab6 --- /dev/null +++ b/src/tools/designer/src/lib/shared/plugindialog.cpp @@ -0,0 +1,208 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "plugindialog_p.h" +#include "pluginmanager_p.h" +#include "iconloader_p.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#if QT_CONFIG(clipboard) +# include +#endif + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { ErrorItemRole = Qt::UserRole + 1 }; + +namespace qdesigner_internal { + +PluginDialog::PluginDialog(QDesignerFormEditorInterface *core, QWidget *parent) + : QDialog(parent +#ifdef Q_OS_MACOS + , Qt::Tool +#endif + ), m_core(core) +{ + ui.setupUi(this); + + ui.message->hide(); + + const QStringList headerLabels(tr("Components")); + + ui.treeWidget->setAlternatingRowColors(false); + ui.treeWidget->setSelectionMode(QAbstractItemView::NoSelection); + ui.treeWidget->setHeaderLabels(headerLabels); + ui.treeWidget->header()->hide(); + ui.treeWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(ui.treeWidget, &QWidget::customContextMenuRequested, + this, &PluginDialog::treeWidgetContextMenu); + + interfaceIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirOpenIcon), + QIcon::Normal, QIcon::On); + interfaceIcon.addPixmap(style()->standardPixmap(QStyle::SP_DirClosedIcon), + QIcon::Normal, QIcon::Off); + featureIcon.addPixmap(style()->standardPixmap(QStyle::SP_FileIcon)); + + setWindowTitle(tr("Plugin Information")); + populateTreeWidget(); + + QPushButton *updateButton = new QPushButton(tr("Refresh")); + const QString tooltip = tr("Scan for newly installed custom widget plugins."); + updateButton->setToolTip(tooltip); + updateButton->setWhatsThis(tooltip); + connect(updateButton, &QAbstractButton::clicked, this, &PluginDialog::updateCustomWidgetPlugins); + ui.buttonBox->addButton(updateButton, QDialogButtonBox::ActionRole); + +} + +void PluginDialog::populateTreeWidget() +{ + ui.treeWidget->clear(); + QDesignerPluginManager *pluginManager = m_core->pluginManager(); + const QStringList fileNames = pluginManager->registeredPlugins(); + + if (!fileNames.isEmpty()) { + QTreeWidgetItem *topLevelItem = setTopLevelItem(tr("Loaded Plugins")); + QFont boldFont = topLevelItem->font(0); + + for (const QString &fileName : fileNames) { + QPluginLoader loader(fileName); + const QFileInfo fileInfo(fileName); + + QTreeWidgetItem *pluginItem = setPluginItem(topLevelItem, fileInfo, boldFont); + + if (QObject *plugin = loader.instance()) { + if (const QDesignerCustomWidgetCollectionInterface *c = qobject_cast(plugin)) { + const auto &collCustomWidgets = c->customWidgets(); + for (const QDesignerCustomWidgetInterface *p : collCustomWidgets) + setItem(pluginItem, p->name(), p->toolTip(), p->whatsThis(), p->icon()); + } else { + if (const QDesignerCustomWidgetInterface *p = qobject_cast(plugin)) + setItem(pluginItem, p->name(), p->toolTip(), p->whatsThis(), p->icon()); + } + } + } + } + + const QStringList notLoadedPlugins = pluginManager->failedPlugins(); + if (!notLoadedPlugins.isEmpty()) { + QTreeWidgetItem *topLevelItem = setTopLevelItem(tr("Failed Plugins")); + const QFont boldFont = topLevelItem->font(0); + for (const QString &plugin : notLoadedPlugins) { + const QString failureReason = pluginManager->failureReason(plugin); + const QString htmlFailureReason = "

"_L1 + + failureReason.toHtmlEscaped() + + "

"_L1; + QTreeWidgetItem *pluginItem = setPluginItem(topLevelItem, QFileInfo(plugin), boldFont); + auto errorItem = setItem(pluginItem, failureReason, + htmlFailureReason, QString(), QIcon()); + errorItem->setData(0, ErrorItemRole, QVariant(true)); + } + } + + if (ui.treeWidget->topLevelItemCount() == 0) { + ui.label->setText(tr("Qt Widgets Designer couldn't find any plugins")); + ui.treeWidget->hide(); + } else { + ui.label->setText(tr("Qt Widgets Designer found the following plugins")); + } +} + +QTreeWidgetItem* PluginDialog::setTopLevelItem(const QString &itemName) +{ + QTreeWidgetItem *topLevelItem = new QTreeWidgetItem(ui.treeWidget); + topLevelItem->setText(0, itemName); + topLevelItem->setExpanded(true); + topLevelItem->setIcon(0, style()->standardPixmap(QStyle::SP_DirOpenIcon)); + + QFont boldFont = topLevelItem->font(0); + boldFont.setBold(true); + topLevelItem->setFont(0, boldFont); + + return topLevelItem; +} + +QTreeWidgetItem* PluginDialog::setPluginItem(QTreeWidgetItem *topLevelItem, + const QFileInfo &file, const QFont &font) +{ + QTreeWidgetItem *pluginItem = new QTreeWidgetItem(topLevelItem); + QString toolTip = QDir::toNativeSeparators(file.absoluteFilePath()); + if (file.exists()) + toolTip += u'\n' + file.lastModified().toString(); + pluginItem->setFont(0, font); + pluginItem->setText(0, file.fileName()); + pluginItem->setToolTip(0, toolTip); + pluginItem->setExpanded(true); + pluginItem->setIcon(0, style()->standardPixmap(QStyle::SP_DirOpenIcon)); + + return pluginItem; +} + +QTreeWidgetItem *PluginDialog::setItem(QTreeWidgetItem *pluginItem, const QString &name, + const QString &toolTip, const QString &whatsThis, + const QIcon &icon) +{ + QTreeWidgetItem *item = new QTreeWidgetItem(pluginItem); + item->setText(0, name); + item->setToolTip(0, toolTip); + item->setWhatsThis(0, whatsThis); + item->setIcon(0, icon.isNull() ? qtLogoIcon() : icon); + return item; +} + +void PluginDialog::updateCustomWidgetPlugins() +{ + const int before = m_core->widgetDataBase()->count(); + m_core->integration()->updateCustomWidgetPlugins(); + const int after = m_core->widgetDataBase()->count(); + if (after > before) { + ui.message->setText(tr("New custom widget plugins have been found.")); + ui.message->show(); + } else { + ui.message->setText(QString()); + } + populateTreeWidget(); +} + +void PluginDialog::treeWidgetContextMenu(const QPoint &pos) +{ +#if QT_CONFIG(clipboard) + const QTreeWidgetItem *item = ui.treeWidget->itemAt(pos); + if (item == nullptr || !item->data(0, ErrorItemRole).toBool()) + return; + QMenu menu; + //: Copy error text + auto copyAction = menu.addAction(tr("Copy")); + auto chosenAction = menu.exec(ui.treeWidget->mapToGlobal(pos)); + if (chosenAction == nullptr) + return; + if (chosenAction == copyAction) + QGuiApplication::clipboard()->setText(item->text(0)); +#else + Q_UNUSED(pos); +#endif +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "moc_plugindialog_p.cpp" diff --git a/src/tools/designer/src/lib/shared/plugindialog.ui b/src/tools/designer/src/lib/shared/plugindialog.ui new file mode 100644 index 00000000000..39cf22c8b7a --- /dev/null +++ b/src/tools/designer/src/lib/shared/plugindialog.ui @@ -0,0 +1,99 @@ + + +* Copyright (C) 2016 The Qt Company Ltd. +* SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + PluginDialog + + + + 0 + 0 + 401 + 331 + + + + Plugin Information + + + + 6 + + + 8 + + + + + TextLabel + + + true + + + + + + + Qt::ElideNone + + + + 1 + + + + + + + + TextLabel + + + true + + + + + + + 6 + + + 0 + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttonBox + rejected() + PluginDialog + reject() + + + 154 + 307 + + + 401 + 280 + + + + + diff --git a/src/tools/designer/src/lib/shared/plugindialog_p.h b/src/tools/designer/src/lib/shared/plugindialog_p.h new file mode 100644 index 00000000000..cbdad6019b4 --- /dev/null +++ b/src/tools/designer/src/lib/shared/plugindialog_p.h @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#ifndef PLUGINDIALOG_H +#define PLUGINDIALOG_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "ui_plugindialog.h" + +QT_BEGIN_NAMESPACE + +class QFileInfo; + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class PluginDialog : public QDialog +{ + Q_OBJECT +public: + explicit PluginDialog(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + +private slots: + void updateCustomWidgetPlugins(); + void treeWidgetContextMenu(const QPoint &pos); + +private: + void populateTreeWidget(); + QTreeWidgetItem* setTopLevelItem(const QString &itemName); + QTreeWidgetItem* setPluginItem(QTreeWidgetItem *topLevelItem, + const QFileInfo &file, const QFont &font); + QTreeWidgetItem *setItem(QTreeWidgetItem *pluginItem, const QString &name, + const QString &toolTip, const QString &whatsThis, + const QIcon &icon); + + QDesignerFormEditorInterface *m_core; + QT_PREPEND_NAMESPACE(Ui)::PluginDialog ui; + QIcon interfaceIcon; + QIcon featureIcon; +}; + +} + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/designer/src/lib/shared/pluginmanager.cpp b/src/tools/designer/src/lib/shared/pluginmanager.cpp new file mode 100644 index 00000000000..cc520997c1a --- /dev/null +++ b/src/tools/designer/src/lib/shared/pluginmanager.cpp @@ -0,0 +1,755 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "pluginmanager_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_qsettings_p.h" + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace Qt::StringLiterals; + +static constexpr auto uiElementC = "ui"_L1; +static constexpr auto languageAttributeC = "language"_L1; +static constexpr auto widgetElementC = "widget"_L1; +static constexpr auto displayNameAttributeC = "displayname"_L1; +static constexpr auto classAttributeC = "class"_L1; +static constexpr auto customwidgetElementC = "customwidget"_L1; +static constexpr auto extendsElementC = "extends"_L1; +static constexpr auto addPageMethodC = "addpagemethod"_L1; +static constexpr auto propertySpecsC = "propertyspecifications"_L1; +static constexpr auto stringPropertySpecC = "stringpropertyspecification"_L1; +static constexpr auto propertyToolTipC = "tooltip"_L1; +static constexpr auto stringPropertyNameAttrC = "name"_L1; +static constexpr auto stringPropertyTypeAttrC = "type"_L1; +static constexpr auto stringPropertyNoTrAttrC = "notr"_L1; +static constexpr auto jambiLanguageC = "jambi"_L1; + +enum { debugPluginManager = 0 }; + +/* Custom widgets: Loading custom widgets is a 2-step process: PluginManager + * scans for its plugins in the constructor. At this point, it might not be safe + * to immediately initialize the custom widgets it finds, because the rest of + * Designer is not initialized yet. + * Later on, in ensureInitialized(), the plugin instances (including static ones) + * are iterated and the custom widget plugins are initialized and added to internal + * list of custom widgets and parsed data. Should there be a parse error or a language + * mismatch, it kicks out the respective custom widget. The m_initialized flag + * is used to indicate the state. + * Later, someone might call registerNewPlugins(), which agains clears the flag via + * registerPlugin() and triggers the process again. + * Also note that Jambi fakes a custom widget collection that changes its contents + * every time the project is switched. So, custom widget plugins can actually + * disappear, and the custom widget list must be cleared and refilled in + * ensureInitialized() after registerNewPlugins. */ + +QT_BEGIN_NAMESPACE + +static QStringList unique(const QStringList &lst) +{ + const QSet s(lst.cbegin(), lst.cend()); + return s.values(); +} + +QStringList QDesignerPluginManager::defaultPluginPaths() +{ + QStringList result; + + const QStringList path_list = QCoreApplication::libraryPaths(); + + for (const QString &path : path_list) + result.append(path + "/designer"_L1); + + result.append(qdesigner_internal::dataDirectory() + "/plugins"_L1); + return result; +} + +// Figure out the language designer is running. ToDo: Introduce some +// Language name API to QDesignerLanguageExtension? + +static inline QString getDesignerLanguage(QDesignerFormEditorInterface *core) +{ + if (QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core)) { + if (lang->uiExtension() == "jui"_L1) + return jambiLanguageC; + return u"unknown"_s; + } + return u"c++"_s; +} + +// ---------------- QDesignerCustomWidgetSharedData + +class QDesignerCustomWidgetSharedData : public QSharedData { +public: + // Type of a string property + using StringPropertyType = std::pair; + + explicit QDesignerCustomWidgetSharedData(const QString &thePluginPath) : pluginPath(thePluginPath) {} + void clearXML(); + + QString pluginPath; + + QString xmlClassName; + QString xmlDisplayName; + QString xmlLanguage; + QString xmlAddPageMethod; + QString xmlExtends; + + QHash xmlStringPropertyTypeMap; + QHash propertyToolTipMap; +}; + +void QDesignerCustomWidgetSharedData::clearXML() +{ + xmlClassName.clear(); + xmlDisplayName.clear(); + xmlLanguage.clear(); + xmlAddPageMethod.clear(); + xmlExtends.clear(); + xmlStringPropertyTypeMap.clear(); +} + +// ---------------- QDesignerCustomWidgetData + +QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QString &pluginPath) : + m_d(new QDesignerCustomWidgetSharedData(pluginPath)) +{ +} + +QDesignerCustomWidgetData::QDesignerCustomWidgetData(const QDesignerCustomWidgetData &o) : + m_d(o.m_d) +{ +} + +QDesignerCustomWidgetData& QDesignerCustomWidgetData::operator=(const QDesignerCustomWidgetData &o) +{ + m_d.operator=(o.m_d); + return *this; +} + +QDesignerCustomWidgetData::~QDesignerCustomWidgetData() +{ +} + +bool QDesignerCustomWidgetData::isNull() const +{ + return m_d->xmlClassName.isEmpty() || m_d->pluginPath.isEmpty(); +} + +QString QDesignerCustomWidgetData::xmlClassName() const +{ + return m_d->xmlClassName; +} + +QString QDesignerCustomWidgetData::xmlLanguage() const +{ + return m_d->xmlLanguage; +} + +QString QDesignerCustomWidgetData::xmlAddPageMethod() const +{ + return m_d->xmlAddPageMethod; +} + +QString QDesignerCustomWidgetData::xmlExtends() const +{ + return m_d->xmlExtends; +} + +QString QDesignerCustomWidgetData::xmlDisplayName() const +{ + return m_d->xmlDisplayName; +} + +QString QDesignerCustomWidgetData::pluginPath() const +{ + return m_d->pluginPath; +} + +bool QDesignerCustomWidgetData::xmlStringPropertyType(const QString &name, StringPropertyType *type) const +{ + const auto it = m_d->xmlStringPropertyTypeMap.constFind(name); + if (it == m_d->xmlStringPropertyTypeMap.constEnd()) { + *type = StringPropertyType(qdesigner_internal::ValidationRichText, true); + return false; + } + *type = it.value(); + return true; +} + +QString QDesignerCustomWidgetData::propertyToolTip(const QString &name) const +{ + return m_d->propertyToolTipMap.value(name); +} + +// Wind a QXmlStreamReader until it finds an element. Returns index or one of FindResult +enum FindResult { FindError = -2, ElementNotFound = -1 }; + +static int findElement(const QStringList &desiredElts, QXmlStreamReader &sr) +{ + while (true) { + switch(sr.readNext()) { + case QXmlStreamReader::EndDocument: + return ElementNotFound; + case QXmlStreamReader::Invalid: + return FindError; + case QXmlStreamReader::StartElement: { + const int index = desiredElts.indexOf(sr.name().toString().toLower()); + if (index >= 0) + return index; + } + break; + default: + break; + } + } + return FindError; +} + +static inline QString msgXmlError(const QString &name, const QString &errorMessage) +{ + return QDesignerPluginManager::tr("An XML error was encountered when parsing the XML of the custom widget %1: %2").arg(name, errorMessage); +} + +static inline QString msgAttributeMissing(const QString &name) +{ + return QDesignerPluginManager::tr("A required attribute ('%1') is missing.").arg(name); +} + +static qdesigner_internal::TextPropertyValidationMode typeStringToType(const QString &v, bool *ok) +{ + *ok = true; + if (v == "multiline"_L1) + return qdesigner_internal::ValidationMultiLine; + if (v == "richtext"_L1) + return qdesigner_internal::ValidationRichText; + if (v == "stylesheet"_L1) + return qdesigner_internal::ValidationStyleSheet; + if (v == "singleline"_L1) + return qdesigner_internal::ValidationSingleLine; + if (v == "objectname"_L1) + return qdesigner_internal::ValidationObjectName; + if (v == "objectnamescope"_L1) + return qdesigner_internal::ValidationObjectNameScope; + if (v == "url"_L1) + return qdesigner_internal::ValidationURL; + *ok = false; + return qdesigner_internal::ValidationRichText; +} + +static bool parsePropertySpecs(QXmlStreamReader &sr, + QDesignerCustomWidgetSharedData *data, + QString *errorMessage) +{ + const QString propertySpecs = propertySpecsC; + const QString stringPropertySpec = stringPropertySpecC; + const QString propertyToolTip = propertyToolTipC; + const QString stringPropertyTypeAttr = stringPropertyTypeAttrC; + const QString stringPropertyNoTrAttr = stringPropertyNoTrAttrC; + const QString stringPropertyNameAttr = stringPropertyNameAttrC; + + while (!sr.atEnd()) { + switch(sr.readNext()) { + case QXmlStreamReader::StartElement: { + if (sr.name() == stringPropertySpec) { + const QXmlStreamAttributes atts = sr.attributes(); + const QString name = atts.value(stringPropertyNameAttr).toString(); + const QString type = atts.value(stringPropertyTypeAttr).toString(); + const QString notrS = atts.value(stringPropertyNoTrAttr).toString(); //Optional + + if (type.isEmpty()) { + *errorMessage = msgAttributeMissing(stringPropertyTypeAttr); + return false; + } + if (name.isEmpty()) { + *errorMessage = msgAttributeMissing(stringPropertyNameAttr); + return false; + } + bool typeOk; + const bool noTr = notrS == "true"_L1 || notrS == "1"_L1; + QDesignerCustomWidgetSharedData::StringPropertyType v(typeStringToType(type, &typeOk), !noTr); + if (!typeOk) { + *errorMessage = QDesignerPluginManager::tr("'%1' is not a valid string property specification.").arg(type); + return false; + } + data->xmlStringPropertyTypeMap.insert(name, v); + } else if (sr.name() == propertyToolTip) { + const QString name = sr.attributes().value(stringPropertyNameAttr).toString(); + if (name.isEmpty()) { + *errorMessage = msgAttributeMissing(stringPropertyNameAttr); + return false; + } + data->propertyToolTipMap.insert(name, sr.readElementText().trimmed()); + } else { + *errorMessage = QDesignerPluginManager::tr("An invalid property specification ('%1') was encountered. Supported types: %2").arg(sr.name().toString(), stringPropertySpec); + return false; + } + } + break; + case QXmlStreamReader::EndElement: // Outer + if (sr.name() == propertySpecs) + return true; + break; + default: + break; + } + } + return true; +} + +QDesignerCustomWidgetData::ParseResult + QDesignerCustomWidgetData::parseXml(const QString &xml, const QString &name, QString *errorMessage) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << name; + + QDesignerCustomWidgetSharedData &data = *m_d; + data.clearXML(); + + QXmlStreamReader sr(xml); + + bool foundUI = false; + bool foundWidget = false; + ParseResult rc = ParseOk; + // Parse for the (optional) or the first element + QStringList elements; + elements.push_back(uiElementC); + elements.push_back(widgetElementC); + for (int i = 0; i < 2 && !foundWidget; i++) { + switch (findElement(elements, sr)) { + case FindError: + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + case ElementNotFound: + *errorMessage = QDesignerPluginManager::tr("The XML of the custom widget %1 does not contain any of the elements or .").arg(name); + return ParseError; + case 0: { // + const QXmlStreamAttributes attributes = sr.attributes(); + data.xmlLanguage = attributes.value(languageAttributeC).toString(); + data.xmlDisplayName = attributes.value(displayNameAttributeC).toString(); + foundUI = true; + } + break; + case 1: // : Do some sanity checks + data.xmlClassName = sr.attributes().value(classAttributeC).toString(); + if (data.xmlClassName.isEmpty()) { + *errorMessage = QDesignerPluginManager::tr("The class attribute for the class %1 is missing.").arg(name); + rc = ParseWarning; + } else { + if (data.xmlClassName != name) { + *errorMessage = QDesignerPluginManager::tr("The class attribute for the class %1 does not match the class name %2.").arg(data.xmlClassName, name); + rc = ParseWarning; + } + } + foundWidget = true; + break; + } + } + // Parse out the element which might be present if was there + if (!foundUI) + return rc; + elements.clear(); + elements.push_back(customwidgetElementC); + switch (findElement(elements, sr)) { + case FindError: + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + case ElementNotFound: + return rc; + default: + break; + } + // Find , , + elements = {extendsElementC, addPageMethodC, propertySpecsC}; + while (true) { + switch (findElement(elements, sr)) { + case FindError: + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + case ElementNotFound: + return rc; + case 0: // + data.xmlExtends = sr.readElementText(); + if (sr.tokenType() != QXmlStreamReader::EndElement) { + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + } + break; + case 1: // + data.xmlAddPageMethod = sr.readElementText(); + if (sr.tokenType() != QXmlStreamReader::EndElement) { + *errorMessage = msgXmlError(name, sr.errorString()); + return ParseError; + } + break; + case 2: // + if (!parsePropertySpecs(sr, m_d.data(), errorMessage)) { + *errorMessage = msgXmlError(name, *errorMessage); + return ParseError; + } + break; + } + } + return rc; +} + +// ---------------- QDesignerPluginManagerPrivate + +class QDesignerPluginManagerPrivate { + public: + using ClassNamePropertyNameKey = std::pair; + + QDesignerPluginManagerPrivate(QDesignerFormEditorInterface *core); + + void clearCustomWidgets(); + bool addCustomWidget(QDesignerCustomWidgetInterface *c, + const QString &pluginPath, + const QString &designerLanguage); + void addCustomWidgets(QObject *o, + const QString &pluginPath, + const QString &designerLanguage); + + QDesignerFormEditorInterface *m_core; + QStringList m_pluginPaths; + QStringList m_registeredPlugins; + // TODO: QPluginLoader also caches invalid plugins -> This seems to be dead code + QStringList m_disabledPlugins; + + QMap m_failedPlugins; + + // Synced lists of custom widgets and their data. Note that the list + // must be ordered for collections to appear in order. + QList m_customWidgets; + QList m_customWidgetData; + + QStringList defaultPluginPaths() const; + + bool m_initialized; +}; + +QDesignerPluginManagerPrivate::QDesignerPluginManagerPrivate(QDesignerFormEditorInterface *core) : + m_core(core), + m_initialized(false) +{ +} + +void QDesignerPluginManagerPrivate::clearCustomWidgets() +{ + m_customWidgets.clear(); + m_customWidgetData.clear(); +} + +// Add a custom widget to the list if it parses correctly +// and is of the right language +bool QDesignerPluginManagerPrivate::addCustomWidget(QDesignerCustomWidgetInterface *c, + const QString &pluginPath, + const QString &designerLanguage) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << c->name(); + + if (!c->isInitialized()) + c->initialize(m_core); + // Parse the XML even if the plugin is initialized as Jambi might play tricks here + QDesignerCustomWidgetData data(pluginPath); + const QString domXml = c->domXml(); + if (!domXml.isEmpty()) { // Legacy: Empty XML means: Do not show up in widget box. + QString errorMessage; + const QDesignerCustomWidgetData::ParseResult pr = data.parseXml(domXml, c->name(), &errorMessage); + switch (pr) { + case QDesignerCustomWidgetData::ParseOk: + break; + case QDesignerCustomWidgetData::ParseWarning: + qdesigner_internal::designerWarning(errorMessage); + break; + case QDesignerCustomWidgetData::ParseError: + qdesigner_internal::designerWarning(errorMessage); + return false; + } + // Does the language match? + const QString pluginLanguage = data.xmlLanguage(); + if (!pluginLanguage.isEmpty() && pluginLanguage.compare(designerLanguage, Qt::CaseInsensitive)) + return false; + } + m_customWidgets.push_back(c); + m_customWidgetData.push_back(data); + return true; +} + +// Check the plugin interface for either a custom widget or a collection and +// add all contained custom widgets. +void QDesignerPluginManagerPrivate::addCustomWidgets(QObject *o, + const QString &pluginPath, + const QString &designerLanguage) +{ + if (QDesignerCustomWidgetInterface *c = qobject_cast(o)) { + addCustomWidget(c, pluginPath, designerLanguage); + return; + } + if (QDesignerCustomWidgetCollectionInterface *coll = qobject_cast(o)) { + const auto &collCustomWidgets = coll->customWidgets(); + for (QDesignerCustomWidgetInterface *c : collCustomWidgets) + addCustomWidget(c, pluginPath, designerLanguage); + } +} + + +// ---------------- QDesignerPluginManager +// As of 4.4, the header will be distributed with the Eclipse plugin. + +QDesignerPluginManager::QDesignerPluginManager(QDesignerFormEditorInterface *core) : + QDesignerPluginManager(QStringList{}, core) +{ +} + +QDesignerPluginManager::QDesignerPluginManager(const QStringList &pluginPaths, + QDesignerFormEditorInterface *core) : + QObject(core), + m_d(new QDesignerPluginManagerPrivate(core)) +{ + m_d->m_pluginPaths = pluginPaths.isEmpty() ? defaultPluginPaths() : pluginPaths; + const QSettings settings(qApp->organizationName(), QDesignerQSettings::settingsApplicationName()); + m_d->m_disabledPlugins = unique(settings.value("PluginManager/DisabledPlugins").toStringList()); + + // Register plugins + updateRegisteredPlugins(); + + if (debugPluginManager) + qDebug() << "QDesignerPluginManager::disabled: " << m_d->m_disabledPlugins << " static " << m_d->m_customWidgets.size(); +} + +QDesignerPluginManager::~QDesignerPluginManager() +{ + syncSettings(); + delete m_d; +} + +QDesignerFormEditorInterface *QDesignerPluginManager::core() const +{ + return m_d->m_core; +} + +QStringList QDesignerPluginManager::findPlugins(const QString &path) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << path; + const QDir dir(path); + if (!dir.exists()) + return QStringList(); + + const QFileInfoList infoList = dir.entryInfoList(QDir::Files); + if (infoList.isEmpty()) + return QStringList(); + + // Load symbolic links but make sure all file names are unique as not + // to fall for something like 'libplugin.so.1 -> libplugin.so' + QStringList result; + for (const auto &fi : infoList) { + QString fileName; + if (fi.isSymLink()) { + const QFileInfo linkTarget = QFileInfo(fi.symLinkTarget()); + if (linkTarget.exists() && linkTarget.isFile()) + fileName = linkTarget.absoluteFilePath(); + } else { + fileName = fi.absoluteFilePath(); + } + if (!fileName.isEmpty() && QLibrary::isLibrary(fileName) && !result.contains(fileName)) + result += fileName; + } + return result; +} + +void QDesignerPluginManager::setDisabledPlugins(const QStringList &disabled_plugins) +{ + m_d->m_disabledPlugins = disabled_plugins; + updateRegisteredPlugins(); +} + +void QDesignerPluginManager::setPluginPaths(const QStringList &plugin_paths) +{ + m_d->m_pluginPaths = plugin_paths; + updateRegisteredPlugins(); +} + +QStringList QDesignerPluginManager::disabledPlugins() const +{ + return m_d->m_disabledPlugins; +} + +QStringList QDesignerPluginManager::failedPlugins() const +{ + return m_d->m_failedPlugins.keys(); +} + +QString QDesignerPluginManager::failureReason(const QString &pluginName) const +{ + return m_d->m_failedPlugins.value(pluginName); +} + +QStringList QDesignerPluginManager::registeredPlugins() const +{ + return m_d->m_registeredPlugins; +} + +QStringList QDesignerPluginManager::pluginPaths() const +{ + return m_d->m_pluginPaths; +} + +QObject *QDesignerPluginManager::instance(const QString &plugin) const +{ + if (m_d->m_disabledPlugins.contains(plugin)) + return nullptr; + + QPluginLoader loader(plugin); + return loader.instance(); +} + +void QDesignerPluginManager::updateRegisteredPlugins() +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO; + m_d->m_registeredPlugins.clear(); + for (const QString &path : std::as_const(m_d->m_pluginPaths)) + registerPath(path); +} + +bool QDesignerPluginManager::registerNewPlugins() +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO; + + const int before = m_d->m_registeredPlugins.size(); + for (const QString &path : std::as_const(m_d->m_pluginPaths)) + registerPath(path); + const bool newPluginsFound = m_d->m_registeredPlugins.size() > before; + // We force a re-initialize as Jambi collection might return + // different widget lists when switching projects. + m_d->m_initialized = false; + ensureInitialized(); + + return newPluginsFound; +} + +void QDesignerPluginManager::registerPath(const QString &path) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << path; + const QStringList &candidates = findPlugins(path); + for (const QString &plugin : candidates) + registerPlugin(plugin); +} + +void QDesignerPluginManager::registerPlugin(const QString &plugin) +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << plugin; + if (m_d->m_disabledPlugins.contains(plugin)) + return; + if (m_d->m_registeredPlugins.contains(plugin)) + return; + + QPluginLoader loader(plugin); + if (loader.isLoaded() || loader.load()) { + m_d->m_registeredPlugins += plugin; + const auto fit = m_d->m_failedPlugins.find(plugin); + if (fit != m_d->m_failedPlugins.end()) + m_d->m_failedPlugins.erase(fit); + return; + } + + const QString errorMessage = loader.errorString(); + m_d->m_failedPlugins.insert(plugin, errorMessage); +} + + + +bool QDesignerPluginManager::syncSettings() +{ + QSettings settings(qApp->organizationName(), QDesignerQSettings::settingsApplicationName()); + settings.beginGroup("PluginManager"); + settings.setValue("DisabledPlugins", m_d->m_disabledPlugins); + settings.endGroup(); + return settings.status() == QSettings::NoError; +} + +void QDesignerPluginManager::ensureInitialized() +{ + if (debugPluginManager) + qDebug() << Q_FUNC_INFO << m_d->m_initialized << m_d->m_customWidgets.size(); + + if (m_d->m_initialized) + return; + + const QString designerLanguage = getDesignerLanguage(m_d->m_core); + + m_d->clearCustomWidgets(); + // Add the static custom widgets + const QObjectList staticPluginObjects = QPluginLoader::staticInstances(); + if (!staticPluginObjects.isEmpty()) { + const QString staticPluginPath = QCoreApplication::applicationFilePath(); + for (QObject *o : staticPluginObjects) + m_d->addCustomWidgets(o, staticPluginPath, designerLanguage); + } + for (const QString &plugin : std::as_const(m_d->m_registeredPlugins)) { + if (QObject *o = instance(plugin)) + m_d->addCustomWidgets(o, plugin, designerLanguage); + } + + m_d->m_initialized = true; +} + +QDesignerPluginManager::CustomWidgetList QDesignerPluginManager::registeredCustomWidgets() const +{ + const_cast(this)->ensureInitialized(); + return m_d->m_customWidgets; +} + +QDesignerCustomWidgetData QDesignerPluginManager::customWidgetData(QDesignerCustomWidgetInterface *w) const +{ + const int index = m_d->m_customWidgets.indexOf(w); + if (index == -1) + return QDesignerCustomWidgetData(); + return m_d->m_customWidgetData.at(index); +} + +QDesignerCustomWidgetData QDesignerPluginManager::customWidgetData(const QString &name) const +{ + for (qsizetype i = 0, count = m_d->m_customWidgets.size(); i < count; ++i) + if (m_d->m_customWidgets.at(i)->name() == name) + return m_d->m_customWidgetData.at(i); + return QDesignerCustomWidgetData(); +} + +QObjectList QDesignerPluginManager::instances() const +{ + const QStringList &plugins = registeredPlugins(); + + QObjectList lst; + for (const QString &plugin : plugins) { + if (QObject *o = instance(plugin)) + lst.append(o); + } + + return lst; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/pluginmanager_p.h b/src/tools/designer/src/lib/shared/pluginmanager_p.h new file mode 100644 index 00000000000..be0f4bfd79b --- /dev/null +++ b/src/tools/designer/src/lib/shared/pluginmanager_p.h @@ -0,0 +1,126 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include "shared_global_p.h" +#include "shared_enums_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerCustomWidgetInterface; +class QDesignerPluginManagerPrivate; + +class QDesignerCustomWidgetSharedData; + +/* Information contained in the Dom XML of a custom widget. */ +class QDESIGNER_SHARED_EXPORT QDesignerCustomWidgetData { +public: + // StringPropertyType: validation mode and translatable flag. + using StringPropertyType = std::pair; + + explicit QDesignerCustomWidgetData(const QString &pluginPath = QString()); + + enum ParseResult { ParseOk, ParseWarning, ParseError }; + ParseResult parseXml(const QString &xml, const QString &name, QString *errorMessage); + + QDesignerCustomWidgetData(const QDesignerCustomWidgetData&); + QDesignerCustomWidgetData& operator=(const QDesignerCustomWidgetData&); + ~QDesignerCustomWidgetData(); + + bool isNull() const; + + QString pluginPath() const; + + // Data as parsed from the widget's domXML(). + QString xmlClassName() const; + // Optional. The language the plugin is supposed to be used with. + QString xmlLanguage() const; + // Optional. method used to add pages to a container with a container extension + QString xmlAddPageMethod() const; + // Optional. Base class + QString xmlExtends() const; + // Optional. The name to be used in the widget box. + QString xmlDisplayName() const; + // Type of a string property + bool xmlStringPropertyType(const QString &name, StringPropertyType *type) const; + // Custom tool tip of property + QString propertyToolTip(const QString &name) const; + +private: + QSharedDataPointer m_d; +}; + +class QDESIGNER_SHARED_EXPORT QDesignerPluginManager: public QObject +{ + Q_OBJECT +public: + using CustomWidgetList = QList; + + explicit QDesignerPluginManager(QDesignerFormEditorInterface *core); + explicit QDesignerPluginManager(const QStringList &pluginPaths, + QDesignerFormEditorInterface *core); + ~QDesignerPluginManager() override; + + QDesignerFormEditorInterface *core() const; + + QObject *instance(const QString &plugin) const; + + QStringList registeredPlugins() const; + + QStringList findPlugins(const QString &path); + + QStringList pluginPaths() const; + void setPluginPaths(const QStringList &plugin_paths); + + QStringList disabledPlugins() const; + void setDisabledPlugins(const QStringList &disabled_plugins); + + QStringList failedPlugins() const; + QString failureReason(const QString &pluginName) const; + + QObjectList instances() const; + + CustomWidgetList registeredCustomWidgets() const; + QDesignerCustomWidgetData customWidgetData(QDesignerCustomWidgetInterface *w) const; + QDesignerCustomWidgetData customWidgetData(const QString &className) const; + + bool registerNewPlugins(); + + static QStringList defaultPluginPaths(); + +public slots: + bool syncSettings(); + void ensureInitialized(); + +private: + void updateRegisteredPlugins(); + void registerPath(const QString &path); + void registerPlugin(const QString &plugin); + +private: + QDesignerPluginManagerPrivate *m_d; +}; + +QT_END_NAMESPACE + +#endif // PLUGINMANAGER_H diff --git a/src/tools/designer/src/lib/shared/previewconfigurationwidget.cpp b/src/tools/designer/src/lib/shared/previewconfigurationwidget.cpp new file mode 100644 index 00000000000..5d24ddfee54 --- /dev/null +++ b/src/tools/designer/src/lib/shared/previewconfigurationwidget.cpp @@ -0,0 +1,323 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "previewconfigurationwidget_p.h" +#include "ui_previewconfigurationwidget.h" +#include "previewmanager_p.h" +#include "shared_settings_p.h" + +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto skinResourcePathC = ":/skins/"_L1; +static constexpr auto skinExtensionC = "skin"_L1; + +// Pair of skin name, path +using SkinNamePath = std::pair; +using Skins = QList; +enum { SkinComboNoneIndex = 0 }; + +// find default skins (resources) +static const Skins &defaultSkins() { + static Skins rc; + if (rc.isEmpty()) { + const QDir dir(skinResourcePathC, "*."_L1 + skinExtensionC); + const QFileInfoList list = dir.entryInfoList(QDir::Dirs|QDir::NoDotAndDotDot, QDir::Name); + if (list.isEmpty()) + return rc; + for (const auto &fi : list) + rc.append(SkinNamePath(fi.baseName(), fi.filePath())); + } + return rc; +} + +namespace qdesigner_internal { + +// ------------- PreviewConfigurationWidgetPrivate +class PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate { +public: + PreviewConfigurationWidgetPrivate(QDesignerFormEditorInterface *core, QGroupBox *g); + + void slotEditAppStyleSheet(); + void slotDeleteSkinEntry(); + void slotSkinChanged(int index); + + void retrieveSettings(); + void storeSettings() const; + + QAbstractButton *appStyleSheetChangeButton() const { return m_ui.m_appStyleSheetChangeButton; } + QAbstractButton *skinRemoveButton() const { return m_ui.m_skinRemoveButton; } + QComboBox *skinCombo() const { return m_ui.m_skinCombo; } + + QDesignerFormEditorInterface *m_core; + +private: + PreviewConfiguration previewConfiguration() const; + void setPreviewConfiguration(const PreviewConfiguration &pc); + + QStringList userSkins() const; + void addUserSkins(const QStringList &files); + bool canRemoveSkin(int index) const; + int browseSkin(); + + const QString m_defaultStyle; + QGroupBox *m_parent; + QT_PREPEND_NAMESPACE(Ui)::PreviewConfigurationWidget m_ui; + + int m_firstUserSkinIndex; + int m_browseSkinIndex; + int m_lastSkinIndex; // required in case browse fails +}; + +PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::PreviewConfigurationWidgetPrivate( + QDesignerFormEditorInterface *core, QGroupBox *g) : + m_core(core), + m_defaultStyle(PreviewConfigurationWidget::tr("Default")), + m_parent(g), + m_firstUserSkinIndex(0), + m_browseSkinIndex(0), + m_lastSkinIndex(0) +{ + m_ui.setupUi(g); + // styles + m_ui.m_styleCombo->setEditable(false); + QStringList styleItems(m_defaultStyle); + styleItems += QStyleFactory::keys(); + m_ui.m_styleCombo->addItems(styleItems); + + // sheet + m_ui.m_appStyleSheetLineEdit->setTextPropertyValidationMode(qdesigner_internal::ValidationStyleSheet); + m_ui.m_appStyleSheetClearButton->setIcon(qdesigner_internal::createIconSet("resetproperty.png"_L1)); + QObject::connect(m_ui.m_appStyleSheetClearButton, &QAbstractButton::clicked, + m_ui.m_appStyleSheetLineEdit, &qdesigner_internal::TextPropertyEditor::clear); + + m_ui.m_skinRemoveButton->setIcon(qdesigner_internal::createIconSet(QIcon::ThemeIcon::EditDelete, + "editdelete.png"_L1)); + // skins: find default skins (resources) + m_ui.m_skinRemoveButton->setEnabled(false); + Skins skins = defaultSkins(); + skins.push_front(SkinNamePath(PreviewConfigurationWidget::tr("None"), QString())); + + for (const auto &skin : std::as_const(skins)) + m_ui.m_skinCombo->addItem(skin.first, QVariant(skin.second)); + m_browseSkinIndex = m_firstUserSkinIndex = skins.size(); + m_ui.m_skinCombo->addItem(PreviewConfigurationWidget::tr("Browse..."), QString()); + + m_ui.m_skinCombo->setMaxVisibleItems (qMax(15, 2 * m_browseSkinIndex)); + m_ui.m_skinCombo->setEditable(false); + + retrieveSettings(); +} + +bool PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::canRemoveSkin(int index) const +{ + return index >= m_firstUserSkinIndex && index != m_browseSkinIndex; +} + +QStringList PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::userSkins() const +{ + QStringList rc; + for (int i = m_firstUserSkinIndex; i < m_browseSkinIndex; i++) + rc.push_back(m_ui.m_skinCombo->itemData(i).toString()); + return rc; +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::addUserSkins(const QStringList &files) +{ + if (files.isEmpty()) + return; + for (const auto &f : files) { + const QFileInfo fi(f); + if (fi.isDir() && fi.isReadable()) + m_ui.m_skinCombo->insertItem(m_browseSkinIndex++, fi.baseName(), QVariant(f)); + else + qWarning() << "Unable to access the skin directory '" << f << "'."; + } +} + +PreviewConfiguration PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::previewConfiguration() const +{ + PreviewConfiguration rc; + QString style = m_ui.m_styleCombo->currentText(); + if (style == m_defaultStyle) + style.clear(); + const QString applicationStyleSheet = m_ui.m_appStyleSheetLineEdit->text(); + // Figure out skin. 0 is None by definition.. + const int skinIndex = m_ui.m_skinCombo->currentIndex(); + QString deviceSkin; + if (skinIndex != SkinComboNoneIndex && skinIndex != m_browseSkinIndex) + deviceSkin = m_ui.m_skinCombo->itemData(skinIndex).toString(); + + return PreviewConfiguration(style, applicationStyleSheet, deviceSkin); +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::setPreviewConfiguration(const PreviewConfiguration &pc) +{ + int styleIndex = m_ui.m_styleCombo->findText(pc.style()); + if (styleIndex == -1) + styleIndex = m_ui.m_styleCombo->findText(m_defaultStyle); + m_ui.m_styleCombo->setCurrentIndex(styleIndex); + m_ui.m_appStyleSheetLineEdit->setText(pc.applicationStyleSheet()); + // find skin by file name. 0 is "none" + const QString deviceSkin = pc.deviceSkin(); + int skinIndex = deviceSkin.isEmpty() ? 0 : m_ui.m_skinCombo->findData(QVariant(deviceSkin)); + if (skinIndex == -1) { + qWarning() << "Unable to find skin '" << deviceSkin << "'."; + skinIndex = 0; + } + m_ui.m_skinCombo->setCurrentIndex(skinIndex); +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotEditAppStyleSheet() +{ + StyleSheetEditorDialog dlg(m_core, m_parent, StyleSheetEditorDialog::ModeGlobal); + dlg.setText(m_ui.m_appStyleSheetLineEdit->text()); + if (dlg.exec() == QDialog::Accepted) + m_ui.m_appStyleSheetLineEdit->setText(dlg.text()); +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotDeleteSkinEntry() +{ + const int index = m_ui.m_skinCombo->currentIndex(); + if (canRemoveSkin(index)) { + m_ui.m_skinCombo->setCurrentIndex(SkinComboNoneIndex); + m_ui.m_skinCombo->removeItem(index); + m_browseSkinIndex--; + } +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::slotSkinChanged(int index) +{ + if (index == m_browseSkinIndex) { + m_ui.m_skinCombo->setCurrentIndex(browseSkin()); + } else { + m_lastSkinIndex = index; + m_ui.m_skinRemoveButton->setEnabled(canRemoveSkin(index)); + m_ui.m_skinCombo->setToolTip(index != SkinComboNoneIndex ? m_ui.m_skinCombo->itemData(index).toString() : QString()); + } +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::retrieveSettings() +{ + QDesignerSharedSettings settings(m_core); + m_parent->setChecked(settings.isCustomPreviewConfigurationEnabled()); + setPreviewConfiguration(settings.customPreviewConfiguration()); + addUserSkins(settings.userDeviceSkins()); +} + +void PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::storeSettings() const +{ + QDesignerSharedSettings settings(m_core); + settings.setCustomPreviewConfigurationEnabled(m_parent->isChecked()); + settings.setCustomPreviewConfiguration(previewConfiguration()); + settings.setUserDeviceSkins(userSkins()); +} + +int PreviewConfigurationWidget::PreviewConfigurationWidgetPrivate::browseSkin() +{ + QFileDialog dlg(m_parent); + dlg.setFileMode(QFileDialog::Directory); + dlg.setOption(QFileDialog::ShowDirsOnly); + const QString title = tr("Load Custom Device Skin"); + dlg.setWindowTitle(title); + dlg.setNameFilter(tr("All QVFB Skins (*.%1)").arg(skinExtensionC)); + + int rc = m_lastSkinIndex; + do { + if (!dlg.exec()) + break; + + const QStringList directories = dlg.selectedFiles(); + if (directories.size() != 1) + break; + + // check: 1) name already there + const QString directory = directories.constFirst(); + const QString name = QFileInfo(directory).baseName(); + const int existingIndex = m_ui.m_skinCombo->findText(name); + if (existingIndex != -1 && existingIndex != SkinComboNoneIndex && existingIndex != m_browseSkinIndex) { + const QString msgTitle = tr("%1 - Duplicate Skin").arg(title); + const QString msg = tr("The skin '%1' already exists.").arg(name); + QMessageBox::information(m_parent, msgTitle, msg); + break; + } + // check: 2) can read + DeviceSkinParameters parameters; + QString readError; + if (parameters.read(directory, DeviceSkinParameters::ReadSizeOnly, &readError)) { + const QString name = QFileInfo(directory).baseName(); + m_ui.m_skinCombo->insertItem(m_browseSkinIndex, name, QVariant(directory)); + rc = m_browseSkinIndex++; + + break; + } + const QString msgTitle = tr("%1 - Error").arg(title); + const QString msg = tr("%1 is not a valid skin directory:\n%2") + .arg(directory, readError); + QMessageBox::warning (m_parent, msgTitle, msg); + } while (true); + return rc; +} + +// ------------- PreviewConfigurationWidget +PreviewConfigurationWidget::PreviewConfigurationWidget(QDesignerFormEditorInterface *core, + QWidget *parent) : + QGroupBox(parent), + m_impl(new PreviewConfigurationWidgetPrivate(core, this)) +{ + connect(m_impl->appStyleSheetChangeButton(), &QAbstractButton::clicked, + this, &PreviewConfigurationWidget::slotEditAppStyleSheet); + connect(m_impl->skinRemoveButton(), &QAbstractButton::clicked, + this, &PreviewConfigurationWidget::slotDeleteSkinEntry); + connect(m_impl->skinCombo(), &QComboBox::currentIndexChanged, + this, &PreviewConfigurationWidget::slotSkinChanged); + + m_impl->retrieveSettings(); +} + +PreviewConfigurationWidget::~PreviewConfigurationWidget() +{ + delete m_impl; +} + +void PreviewConfigurationWidget::saveState() +{ + m_impl->storeSettings(); +} + +void PreviewConfigurationWidget::slotEditAppStyleSheet() +{ + m_impl->slotEditAppStyleSheet(); +} + +void PreviewConfigurationWidget::slotDeleteSkinEntry() +{ + m_impl->slotDeleteSkinEntry(); +} + +void PreviewConfigurationWidget::slotSkinChanged(int index) +{ + m_impl->slotSkinChanged(index); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/previewconfigurationwidget.ui b/src/tools/designer/src/lib/shared/previewconfigurationwidget.ui new file mode 100644 index 00000000000..2f18766ff7d --- /dev/null +++ b/src/tools/designer/src/lib/shared/previewconfigurationwidget.ui @@ -0,0 +1,91 @@ + + PreviewConfigurationWidget + + + Form + + + Print/Preview Configuration + + + true + + + + + + Style + + + + + + + + + + Style sheet + + + + + + + + + + 149 + 0 + + + + + + + + ... + + + + + + + ... + + + + + + + + + Device skin + + + + + + + + + + + + ... + + + + + + + + + + qdesigner_internal::TextPropertyEditor + QLineEdit +
textpropertyeditor_p.h
+
+
+ + +
diff --git a/src/tools/designer/src/lib/shared/previewconfigurationwidget_p.h b/src/tools/designer/src/lib/shared/previewconfigurationwidget_p.h new file mode 100644 index 00000000000..ec5e2bed3a2 --- /dev/null +++ b/src/tools/designer/src/lib/shared/previewconfigurationwidget_p.h @@ -0,0 +1,58 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PREVIEWCONFIGURATIONWIDGET_H +#define PREVIEWCONFIGURATIONWIDGET_H + +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerSettingsInterface; + +namespace qdesigner_internal { + +// ----------- PreviewConfigurationWidget: Widget to edit the preview configuration. + +class QDESIGNER_SHARED_EXPORT PreviewConfigurationWidget : public QGroupBox +{ + Q_OBJECT +public: + explicit PreviewConfigurationWidget(QDesignerFormEditorInterface *core, + QWidget *parent = nullptr); + ~PreviewConfigurationWidget() override; + void saveState(); + +private slots: + void slotEditAppStyleSheet(); + void slotDeleteSkinEntry(); + void slotSkinChanged(int); + +private: + class PreviewConfigurationWidgetPrivate; + PreviewConfigurationWidgetPrivate *m_impl; + + PreviewConfigurationWidget(const PreviewConfigurationWidget &other); + PreviewConfigurationWidget &operator =(const PreviewConfigurationWidget &other); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PREVIEWCONFIGURATIONWIDGET_H diff --git a/src/tools/designer/src/lib/shared/previewmanager.cpp b/src/tools/designer/src/lib/shared/previewmanager.cpp new file mode 100644 index 00000000000..96c9287f2a7 --- /dev/null +++ b/src/tools/designer/src/lib/shared/previewmanager.cpp @@ -0,0 +1,900 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "formwindowbase_p.h" +#include "previewmanager_p.h" +#include "qdesigner_formbuilder_p.h" +#include "shared_settings_p.h" +#include "widgetfactory_p.h" +#include "zoomwidget_p.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static inline int compare(const qdesigner_internal::PreviewConfiguration &pc1, const qdesigner_internal::PreviewConfiguration &pc2) +{ + int rc = pc1.style().compare(pc2.style()); + if (rc) + return rc; + rc = pc1.applicationStyleSheet().compare(pc2.applicationStyleSheet()); + if (rc) + return rc; + return pc1.deviceSkin().compare(pc2.deviceSkin()); +} + +namespace qdesigner_internal { + // ------ PreviewData (data associated with a preview window) + struct PreviewData { + PreviewData(const QPointer &widget, const QDesignerFormWindowInterface *formWindow, const qdesigner_internal::PreviewConfiguration &pc); + QPointer m_widget; + const QDesignerFormWindowInterface *m_formWindow; + qdesigner_internal::PreviewConfiguration m_configuration; + }; + + PreviewData::PreviewData(const QPointer& widget, + const QDesignerFormWindowInterface *formWindow, + const qdesigner_internal::PreviewConfiguration &pc) : + m_widget(widget), + m_formWindow(formWindow), + m_configuration(pc) + { + } + +/* In designer, we have the situation that laid-out maincontainers have + * a geometry set (which might differ from their sizeHint()). The QGraphicsItem + * should return that in its size hint, else such cases won't work */ + +class DesignerZoomProxyWidget : public ZoomProxyWidget { + Q_DISABLE_COPY_MOVE(DesignerZoomProxyWidget) +public: + DesignerZoomProxyWidget(QGraphicsItem *parent = nullptr, Qt::WindowFlags wFlags = {}); +protected: + QSizeF sizeHint(Qt::SizeHint which, const QSizeF & constraint = QSizeF() ) const override; +}; + +DesignerZoomProxyWidget::DesignerZoomProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) : + ZoomProxyWidget(parent, wFlags) +{ +} + +QSizeF DesignerZoomProxyWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const +{ + if (const QWidget *w = widget()) + return QSizeF(w->size()); + return ZoomProxyWidget::sizeHint(which, constraint); +} + +// DesignerZoomWidget which returns DesignerZoomProxyWidget in its factory function +class DesignerZoomWidget : public ZoomWidget { + Q_DISABLE_COPY_MOVE(DesignerZoomWidget) +public: + DesignerZoomWidget(QWidget *parent = nullptr); +private: + QGraphicsProxyWidget *createProxyWidget(QGraphicsItem *parent = nullptr, + Qt::WindowFlags wFlags = {}) const override; +}; + +DesignerZoomWidget::DesignerZoomWidget(QWidget *parent) : + ZoomWidget(parent) +{ +} + +QGraphicsProxyWidget *DesignerZoomWidget::createProxyWidget(QGraphicsItem *parent, Qt::WindowFlags wFlags) const +{ + return new DesignerZoomProxyWidget(parent, wFlags); +} + +// PreviewDeviceSkin: Forwards the key events to the window and +// provides context menu with rotation options. Derived class +// can apply additional transformations to the skin. + +class PreviewDeviceSkin : public DeviceSkin +{ + Q_OBJECT +public: + enum Direction { DirectionUp, DirectionLeft, DirectionRight }; + + explicit PreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent); + virtual void setPreview(QWidget *w); + QSize screenSize() const { return m_screenSize; } + +private slots: + void slotSkinKeyPressEvent(int code, const QString& text, bool autorep); + void slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep); + void slotPopupMenu(); + +protected: + virtual void populateContextMenu(QMenu *) {} + +private slots: + void slotDirection(QAction *); + +protected: + // Fit the widget in case the orientation changes (transposing screensize) + virtual void fitWidget(const QSize &size); + // Calculate the complete transformation for the skin + // (base class implementation provides rotation). + virtual QTransform skinTransform() const; + +private: + const QSize m_screenSize; + Direction m_direction; + + QAction *m_directionUpAction; + QAction *m_directionLeftAction; + QAction *m_directionRightAction; + QAction *m_closeAction; +}; + +PreviewDeviceSkin::PreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent) : + DeviceSkin(parameters, parent), + m_screenSize(parameters.screenSize()), + m_direction(DirectionUp), + m_directionUpAction(nullptr), + m_directionLeftAction(nullptr), + m_directionRightAction(nullptr), + m_closeAction(nullptr) +{ + connect(this, &PreviewDeviceSkin::skinKeyPressEvent, + this, &PreviewDeviceSkin::slotSkinKeyPressEvent); + connect(this, &PreviewDeviceSkin::skinKeyReleaseEvent, + this, &PreviewDeviceSkin::slotSkinKeyReleaseEvent); + connect(this, &PreviewDeviceSkin::popupMenu, this, &PreviewDeviceSkin::slotPopupMenu); +} + +void PreviewDeviceSkin::setPreview(QWidget *formWidget) +{ + formWidget->setFixedSize(m_screenSize); + formWidget->setParent(this, Qt::SubWindow); + formWidget->setAutoFillBackground(true); + setView(formWidget); +} + +void PreviewDeviceSkin::slotSkinKeyPressEvent(int code, const QString& text, bool autorep) +{ + if (QWidget *focusWidget = QApplication::focusWidget()) { + QKeyEvent e(QEvent::KeyPress, code, {}, text, autorep); + QApplication::sendEvent(focusWidget, &e); + } +} + +void PreviewDeviceSkin::slotSkinKeyReleaseEvent(int code, const QString& text, bool autorep) +{ + if (QWidget *focusWidget = QApplication::focusWidget()) { + QKeyEvent e(QEvent::KeyRelease, code, {}, text, autorep); + QApplication::sendEvent(focusWidget, &e); + } +} + +// Create a checkable action with integer data and +// set it checked if it matches the currentState. +static inline QAction + *createCheckableActionIntData(const QString &label, + int actionValue, int currentState, + QActionGroup *ag, QObject *parent) +{ + QAction *a = new QAction(label, parent); + a->setData(actionValue); + a->setCheckable(true); + if (actionValue == currentState) + a->setChecked(true); + ag->addAction(a); + return a; +} + +void PreviewDeviceSkin::slotPopupMenu() +{ + QMenu menu(this); + // Create actions + if (!m_directionUpAction) { + QActionGroup *directionGroup = new QActionGroup(this); + connect(directionGroup, &QActionGroup::triggered, this, &PreviewDeviceSkin::slotDirection); + directionGroup->setExclusive(true); + m_directionUpAction = createCheckableActionIntData(tr("&Portrait"), DirectionUp, m_direction, directionGroup, this); + //: Rotate form preview counter-clockwise + m_directionLeftAction = createCheckableActionIntData(tr("Landscape (&CCW)"), DirectionLeft, m_direction, directionGroup, this); + //: Rotate form preview clockwise + m_directionRightAction = createCheckableActionIntData(tr("&Landscape (CW)"), DirectionRight, m_direction, directionGroup, this); + m_closeAction = new QAction(tr("&Close"), this); + connect(m_closeAction, &QAction::triggered, parentWidget(), &QWidget::close); + } + menu.addAction(m_directionUpAction); + menu.addAction(m_directionLeftAction); + menu.addAction(m_directionRightAction); + menu.addSeparator(); + populateContextMenu(&menu); + menu.addAction(m_closeAction); + menu.exec(QCursor::pos()); +} + +void PreviewDeviceSkin::slotDirection(QAction *a) +{ + const Direction newDirection = static_cast(a->data().toInt()); + if (m_direction == newDirection) + return; + const Qt::Orientation newOrientation = newDirection == DirectionUp ? Qt::Vertical : Qt::Horizontal; + const Qt::Orientation oldOrientation = m_direction == DirectionUp ? Qt::Vertical : Qt::Horizontal; + m_direction = newDirection; + QApplication::setOverrideCursor(Qt::WaitCursor); + if (oldOrientation != newOrientation) { + QSize size = screenSize(); + if (newOrientation == Qt::Horizontal) + size.transpose(); + fitWidget(size); + } + setTransform(skinTransform()); + QApplication::restoreOverrideCursor(); +} + +void PreviewDeviceSkin::fitWidget(const QSize &size) +{ + view()->setFixedSize(size); +} + +QTransform PreviewDeviceSkin::skinTransform() const +{ + QTransform newTransform; + switch (m_direction) { + case DirectionUp: + break; + case DirectionLeft: + newTransform.rotate(270.0); + break; + case DirectionRight: + newTransform.rotate(90.0); + break; + } + return newTransform; +} + +// ------------ PreviewConfigurationPrivate +class PreviewConfigurationData : public QSharedData { +public: + PreviewConfigurationData() = default; + explicit PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin); + + QString m_style; + // Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()). + QString m_applicationStyleSheet; + QString m_deviceSkin; +}; + +PreviewConfigurationData::PreviewConfigurationData(const QString &style, const QString &applicationStyleSheet, const QString &deviceSkin) : + m_style(style), + m_applicationStyleSheet(applicationStyleSheet), + m_deviceSkin(deviceSkin) +{ +} + +/* ZoomablePreviewDeviceSkin: A Zoomable Widget Preview skin. Embeds preview + * into a ZoomWidget and this in turn into the DeviceSkin view and keeps + * Device skin zoom + ZoomWidget zoom in sync. */ + +class ZoomablePreviewDeviceSkin : public PreviewDeviceSkin +{ + Q_OBJECT +public: + explicit ZoomablePreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent); + void setPreview(QWidget *w) override; + + int zoomPercent() const; // Device Skins have a double 'zoom' property + +public slots: + void setZoomPercent(int); + +signals: + void zoomPercentChanged(int); + +protected: + void populateContextMenu(QMenu *m) override; + QTransform skinTransform() const override; + void fitWidget(const QSize &size) override; + +private: + ZoomMenu *m_zoomMenu; + QAction *m_zoomSubMenuAction; + ZoomWidget *m_zoomWidget; +}; + +ZoomablePreviewDeviceSkin::ZoomablePreviewDeviceSkin(const DeviceSkinParameters ¶meters, QWidget *parent) : + PreviewDeviceSkin(parameters, parent), + m_zoomMenu(new ZoomMenu(this)), + m_zoomSubMenuAction(nullptr), + m_zoomWidget(new DesignerZoomWidget) +{ + connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::setZoomPercent); + connect(m_zoomMenu, &ZoomMenu::zoomChanged, this, &ZoomablePreviewDeviceSkin::zoomPercentChanged); + m_zoomWidget->setZoomContextMenuEnabled(false); + m_zoomWidget->setWidgetZoomContextMenuEnabled(false); + m_zoomWidget->resize(screenSize()); + m_zoomWidget->setParent(this, Qt::SubWindow); + m_zoomWidget->setAutoFillBackground(true); + setView(m_zoomWidget); +} + +static inline qreal zoomFactor(int percent) +{ + return qreal(percent) / 100.0; +} + +static inline QSize scaleSize(int zoomPercent, const QSize &size) +{ + return zoomPercent == 100 ? size : (QSizeF(size) * zoomFactor(zoomPercent)).toSize(); +} + +void ZoomablePreviewDeviceSkin::setPreview(QWidget *formWidget) +{ + m_zoomWidget->setWidget(formWidget); + m_zoomWidget->resize(scaleSize(zoomPercent(), screenSize())); +} + +int ZoomablePreviewDeviceSkin::zoomPercent() const +{ + return m_zoomWidget->zoom(); +} + +void ZoomablePreviewDeviceSkin::setZoomPercent(int zp) +{ + if (zp == zoomPercent()) + return; + + // If not triggered by the menu itself: Update it + if (m_zoomMenu->zoom() != zp) + m_zoomMenu->setZoom(zp); + + QApplication::setOverrideCursor(Qt::WaitCursor); + m_zoomWidget->setZoom(zp); + setTransform(skinTransform()); + QApplication::restoreOverrideCursor(); +} + +void ZoomablePreviewDeviceSkin::populateContextMenu(QMenu *menu) +{ + if (!m_zoomSubMenuAction) { + m_zoomSubMenuAction = new QAction(tr("&Zoom"), this); + QMenu *zoomSubMenu = new QMenu; + m_zoomSubMenuAction->setMenu(zoomSubMenu); + m_zoomMenu->addActions(zoomSubMenu); + } + menu->addAction(m_zoomSubMenuAction); + menu->addSeparator(); +} + +QTransform ZoomablePreviewDeviceSkin::skinTransform() const +{ + // Complete transformation consisting of base class rotation and zoom. + QTransform rc = PreviewDeviceSkin::skinTransform(); + const int zp = zoomPercent(); + if (zp != 100) { + const qreal factor = zoomFactor(zp); + rc.scale(factor, factor); + } + return rc; +} + +void ZoomablePreviewDeviceSkin::fitWidget(const QSize &size) +{ + m_zoomWidget->resize(scaleSize(zoomPercent(), size)); +} + +// ------------- PreviewConfiguration + +static constexpr auto styleKey = "Style"_L1; +static constexpr auto appStyleSheetKey = "AppStyleSheet"_L1; +static constexpr auto skinKey = "Skin"_L1; + +PreviewConfiguration::PreviewConfiguration() : + m_d(new PreviewConfigurationData) +{ +} + +PreviewConfiguration::PreviewConfiguration(const QString &sty, const QString &applicationSheet, const QString &skin) : + m_d(new PreviewConfigurationData(sty, applicationSheet, skin)) +{ +} + +PreviewConfiguration::PreviewConfiguration(const PreviewConfiguration &o) : + m_d(o.m_d) +{ +} + +PreviewConfiguration &PreviewConfiguration::operator=(const PreviewConfiguration &o) +{ + m_d.operator=(o.m_d); + return *this; +} + +PreviewConfiguration::~PreviewConfiguration() = default; + +void PreviewConfiguration::clear() +{ + PreviewConfigurationData &d = *m_d; + d.m_style.clear(); + d.m_applicationStyleSheet.clear(); + d.m_deviceSkin.clear(); +} + +QString PreviewConfiguration::style() const +{ + return m_d->m_style; +} + +void PreviewConfiguration::setStyle(const QString &s) +{ + m_d->m_style = s; +} + +// Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()). +QString PreviewConfiguration::applicationStyleSheet() const +{ + return m_d->m_applicationStyleSheet; +} + +void PreviewConfiguration::setApplicationStyleSheet(const QString &as) +{ + m_d->m_applicationStyleSheet = as; +} + +QString PreviewConfiguration::deviceSkin() const +{ + return m_d->m_deviceSkin; +} + +void PreviewConfiguration::setDeviceSkin(const QString &s) +{ + m_d->m_deviceSkin = s; +} + +void PreviewConfiguration::toSettings(const QString &prefix, QDesignerSettingsInterface *settings) const +{ + const PreviewConfigurationData &d = *m_d; + settings->beginGroup(prefix); + settings->setValue(styleKey, d.m_style); + settings->setValue(appStyleSheetKey, d.m_applicationStyleSheet); + settings->setValue(skinKey, d.m_deviceSkin); + settings->endGroup(); +} + +void PreviewConfiguration::fromSettings(const QString &prefix, const QDesignerSettingsInterface *settings) +{ + clear(); + QString key = prefix + u'/'; + const auto prefixSize = key.size(); + + PreviewConfigurationData &d = *m_d; + + const QVariant emptyString = QVariant(QString()); + + key += styleKey; + d.m_style = settings->value(key, emptyString).toString(); + + key.replace(prefixSize, key.size() - prefixSize, appStyleSheetKey); + d.m_applicationStyleSheet = settings->value(key, emptyString).toString(); + + key.replace(prefixSize, key.size() - prefixSize, skinKey); + d.m_deviceSkin = settings->value(key, emptyString).toString(); +} + + +QDESIGNER_SHARED_EXPORT bool operator<(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2) +{ + return compare(pc1, pc2) < 0; +} + +QDESIGNER_SHARED_EXPORT bool operator==(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2) +{ + return compare(pc1, pc2) == 0; +} + +QDESIGNER_SHARED_EXPORT bool operator!=(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2) +{ + return compare(pc1, pc2) != 0; +} + +// ------------- PreviewManagerPrivate +class PreviewManagerPrivate { +public: + PreviewManagerPrivate(PreviewManager::PreviewMode mode); + + const PreviewManager::PreviewMode m_mode; + + QPointer m_activePreview; + + using PreviewDataList = QList; + + PreviewDataList m_previews; + + QMap m_deviceSkinConfigCache; + + QDesignerFormEditorInterface *m_core; + bool m_updateBlocked; +}; + +PreviewManagerPrivate::PreviewManagerPrivate(PreviewManager::PreviewMode mode) : + m_mode(mode), + m_core(nullptr), + m_updateBlocked(false) +{ +} + +// ------------- PreviewManager + +PreviewManager::PreviewManager(PreviewMode mode, QObject *parent) : + QObject(parent), + d(new PreviewManagerPrivate(mode)) +{ +} + +PreviewManager:: ~PreviewManager() +{ + delete d; +} + + +Qt::WindowFlags PreviewManager::previewWindowFlags(const QWidget *widget) const +{ +#ifdef Q_OS_WIN + Qt::WindowFlags windowFlags = (widget->windowType() == Qt::Window) ? + Qt::Window | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint : + Qt::WindowFlags(Qt::Dialog); +#else + Q_UNUSED(widget); + // Only Dialogs have close buttons on Mac. + // On Linux, we don't want an additional task bar item and we don't want a minimize button; + // we want the preview to be on top. + Qt::WindowFlags windowFlags = Qt::Dialog; +#endif + return windowFlags; +} + +QWidget *PreviewManager::createDeviceSkinContainer(const QDesignerFormWindowInterface *fw) const +{ + return new QDialog(fw->window()); +} + +// Some widgets might require fake containers + +static QWidget *fakeContainer(QWidget *w) +{ + // Prevent a dock widget from trying to dock to Designer's main window + // (which can be found in the parent hierarchy in MDI mode) by + // providing a fake mainwindow + if (QDockWidget *dock = qobject_cast(w)) { + // Reparent: Clear modality, propagate title and resize outer container + const QSize size = w->size(); + w->setWindowModality(Qt::NonModal); + dock->setFeatures(dock->features() & ~(QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable|QDockWidget::DockWidgetClosable)); + dock->setAllowedAreas(Qt::LeftDockWidgetArea); + QMainWindow *mw = new QMainWindow; + const QMargins cm = mw->contentsMargins(); + mw->addDockWidget(Qt::LeftDockWidgetArea, dock); + mw->resize(size + QSize(cm.left() + cm.right(), cm.top() + cm.bottom())); + return mw; + } + return w; +} + +static PreviewConfiguration configurationFromSettings(QDesignerFormEditorInterface *core, const QString &style) +{ + qdesigner_internal::PreviewConfiguration pc; + const QDesignerSharedSettings settings(core); + if (settings.isCustomPreviewConfigurationEnabled()) + pc = settings.customPreviewConfiguration(); + if (!style.isEmpty()) + pc.setStyle(style); + return pc; +} + +QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, const QString &style, int deviceProfileIndex, QString *errorMessage) +{ + return showPreview(fw, configurationFromSettings(fw->core(), style), deviceProfileIndex, errorMessage); +} + +QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage) +{ + return showPreview(fw, style, -1, errorMessage); +} + +QWidget *PreviewManager::createPreview(const QDesignerFormWindowInterface *fw, + const PreviewConfiguration &pc, + int deviceProfileIndex, + QString *errorMessage, + int initialZoom) +{ + if (!d->m_core) + d->m_core = fw->core(); + + const bool zoomable = initialZoom > 0; + // Figure out which profile to apply + DeviceProfile deviceProfile; + if (deviceProfileIndex >= 0) { + deviceProfile = QDesignerSharedSettings(fw->core()).deviceProfileAt(deviceProfileIndex); + } else { + if (const FormWindowBase *fwb = qobject_cast(fw)) + deviceProfile = fwb->deviceProfile(); + } + // Create + QWidget *formWidget = QDesignerFormBuilder::createPreview(fw, pc.style(), pc.applicationStyleSheet(), deviceProfile, errorMessage); + if (!formWidget) + return nullptr; + + const QString title = tr("%1 - [Preview]").arg(formWidget->windowTitle()); + formWidget = fakeContainer(formWidget); + formWidget->setWindowTitle(title); + + // Clear any modality settings, child widget modalities must not be higher than parent's + formWidget->setWindowModality(Qt::NonModal); + // No skin + const QString deviceSkin = pc.deviceSkin(); + if (deviceSkin.isEmpty()) { + if (zoomable) { // Embed into ZoomWidget + ZoomWidget *zw = new DesignerZoomWidget; + connect(zw->zoomMenu(), &ZoomMenu::zoomChanged, this, &PreviewManager::slotZoomChanged); + zw->setWindowTitle(title); + zw->setWidget(formWidget); + // Keep any widgets' context menus working, do not use global menu + zw->setWidgetZoomContextMenuEnabled(true); + zw->setParent(fw->window(), previewWindowFlags(formWidget)); + // Make preview close when Widget closes (Dialog/accept, etc) + formWidget->setAttribute(Qt::WA_DeleteOnClose, true); + connect(formWidget, &QObject::destroyed, zw, &QWidget::close); + zw->setZoom(initialZoom); + zw->setProperty(WidgetFactory::disableStyleCustomPaintingPropertyC, QVariant(true)); + return zw; + } + formWidget->setParent(fw->window(), previewWindowFlags(formWidget)); + formWidget->setProperty(WidgetFactory::disableStyleCustomPaintingPropertyC, QVariant(true)); + return formWidget; + } + // Embed into skin. find config in cache + auto it = d->m_deviceSkinConfigCache.find(deviceSkin); + if (it == d->m_deviceSkinConfigCache.end()) { + DeviceSkinParameters parameters; + if (!parameters.read(deviceSkin, DeviceSkinParameters::ReadAll, errorMessage)) { + formWidget->deleteLater(); + return nullptr; + } + it = d->m_deviceSkinConfigCache.insert(deviceSkin, parameters); + } + + QWidget *skinContainer = createDeviceSkinContainer(fw); + PreviewDeviceSkin *skin = nullptr; + if (zoomable) { + ZoomablePreviewDeviceSkin *zds = new ZoomablePreviewDeviceSkin(it.value(), skinContainer); + zds->setZoomPercent(initialZoom); + connect(zds, &ZoomablePreviewDeviceSkin::zoomPercentChanged, + this, &PreviewManager::slotZoomChanged); + skin = zds; + } else { + skin = new PreviewDeviceSkin(it.value(), skinContainer); + } + skin->setPreview(formWidget); + // Make preview close when Widget closes (Dialog/accept, etc) + formWidget->setAttribute(Qt::WA_DeleteOnClose, true); + connect(formWidget, &QObject::destroyed, skinContainer, &QWidget::close); + skinContainer->setWindowTitle(title); + skinContainer->setProperty(WidgetFactory::disableStyleCustomPaintingPropertyC, QVariant(true)); + return skinContainer; +} + +QWidget *PreviewManager::showPreview(const QDesignerFormWindowInterface *fw, + const PreviewConfiguration &pc, + int deviceProfileIndex, + QString *errorMessage) +{ + enum { Spacing = 10 }; + if (QWidget *existingPreviewWidget = raise(fw, pc)) + return existingPreviewWidget; + + const QDesignerSharedSettings settings(fw->core()); + const int initialZoom = settings.zoomEnabled() ? settings.zoom() : -1; + + QWidget *widget = createPreview(fw, pc, deviceProfileIndex, errorMessage, initialZoom); + if (!widget) + return nullptr; + // Install filter for Escape key + widget->setAttribute(Qt::WA_DeleteOnClose, true); + widget->installEventFilter(this); + + switch (d->m_mode) { + case ApplicationModalPreview: + // Cannot do this on the Mac as the dialog would have no close button + widget->setWindowModality(Qt::ApplicationModal); + break; + case SingleFormNonModalPreview: + case MultipleFormNonModalPreview: + widget->setWindowModality(Qt::NonModal); + connect(fw, &QDesignerFormWindowInterface::changed, widget, &QWidget::close); + connect(fw, &QObject::destroyed, widget, &QWidget::close); + if (d->m_mode == SingleFormNonModalPreview) { + connect(fw->core()->formWindowManager(), &QDesignerFormWindowManagerInterface::activeFormWindowChanged, + widget, &QWidget::close); + } + break; + } + // Semi-smart algorithm to position previews: + // If its the first one, position relative to form. + // 2nd, attempt to tile right (for comparing styles) or cascade + const QSize size = widget->size(); + const bool firstPreview = d->m_previews.isEmpty(); + if (firstPreview) { + widget->move(fw->mapToGlobal(QPoint(Spacing, Spacing))); + } else { + if (QWidget *lastPreview = d->m_previews.constLast().m_widget) { + const QRect lastPreviewGeometry = lastPreview->frameGeometry(); + const QRect availGeometry = lastPreview->screen()->availableGeometry(); + const QPoint newPos = lastPreviewGeometry.topRight() + QPoint(Spacing, 0); + if (newPos.x() + size.width() < availGeometry.right()) + widget->move(newPos); + else + widget->move(lastPreviewGeometry.topLeft() + QPoint(Spacing, Spacing)); + } + + } + d->m_previews.push_back(PreviewData(widget, fw, pc)); + widget->show(); + if (firstPreview) + emit firstPreviewOpened(); + return widget; +} + +QWidget *PreviewManager::raise(const QDesignerFormWindowInterface *fw, const PreviewConfiguration &pc) +{ + if (d->m_previews.isEmpty()) + return nullptr; + + // find matching window + for (const auto &pd : std::as_const(d->m_previews)) { + QWidget *w = pd.m_widget; + if (w && pd.m_formWindow == fw && pd.m_configuration == pc) { + w->raise(); + w->activateWindow(); + return w; + } + } + return nullptr; +} + +void PreviewManager::closeAllPreviews() +{ + if (!d->m_previews.isEmpty()) { + d->m_updateBlocked = true; + d->m_activePreview = nullptr; + for (const auto &pd : std::as_const(d->m_previews)) { + if (pd.m_widget) + pd.m_widget->close(); + } + d->m_previews.clear(); + d->m_updateBlocked = false; + emit lastPreviewClosed(); + } +} + +void PreviewManager::updatePreviewClosed(QWidget *w) +{ + if (d->m_updateBlocked) + return; + // Purge out all 0 or widgets to be deleted + for (auto it = d->m_previews.begin(); it != d->m_previews.end() ; ) { + QWidget *iw = it->m_widget; // Might be 0 when catching QEvent::Destroyed + if (iw == nullptr || iw == w) { + it = d->m_previews.erase(it); + } else { + ++it; + } + } + if (d->m_previews.isEmpty()) + emit lastPreviewClosed(); +} + +bool PreviewManager::eventFilter(QObject *watched, QEvent *event) +{ + // Courtesy of designer + do { + if (!watched->isWidgetType()) + break; + QWidget *previewWindow = qobject_cast(watched); + if (!previewWindow || !previewWindow->isWindow()) + break; + + switch (event->type()) { + case QEvent::KeyPress: + case QEvent::ShortcutOverride: { + const QKeyEvent *keyEvent = static_cast(event); + const int key = keyEvent->key(); + if ((key == Qt::Key_Escape +#ifdef Q_OS_MACOS + || (keyEvent->modifiers() == Qt::ControlModifier && key == Qt::Key_Period) +#endif + )) { + previewWindow->close(); + return true; + } + } + break; + case QEvent::WindowActivate: + d->m_activePreview = previewWindow; + break; + case QEvent::Destroy: // We don't get QEvent::Close if someone accepts a QDialog. + updatePreviewClosed(previewWindow); + break; + case QEvent::Close: + updatePreviewClosed(previewWindow); + previewWindow->removeEventFilter (this); + break; + default: + break; + } + } while(false); + return QObject::eventFilter(watched, event); +} + +int PreviewManager::previewCount() const +{ + return d->m_previews.size(); +} + +QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, int deviceProfileIndex, QString *errorMessage) +{ + return createPreviewPixmap(fw, configurationFromSettings(fw->core(), style), deviceProfileIndex, errorMessage); +} + +QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage) +{ + return createPreviewPixmap(fw, style, -1, errorMessage); +} + +QPixmap PreviewManager::createPreviewPixmap(const QDesignerFormWindowInterface *fw, + const PreviewConfiguration &pc, + int deviceProfileIndex, + QString *errorMessage) +{ + QWidget *widget = createPreview(fw, pc, deviceProfileIndex, errorMessage); + if (!widget) + return QPixmap(); + const QPixmap rc = widget->grab(QRect(0, 0, -1, -1)); + widget->deleteLater(); + return rc; +} + +void PreviewManager::slotZoomChanged(int z) +{ + if (d->m_core) { // Save the last zoom chosen by the user. + QDesignerSharedSettings settings(d->m_core); + settings.setZoom(z); + } +} +} + +QT_END_NAMESPACE + +#include "previewmanager.moc" diff --git a/src/tools/designer/src/lib/shared/previewmanager_p.h b/src/tools/designer/src/lib/shared/previewmanager_p.h new file mode 100644 index 00000000000..aa17d384bd3 --- /dev/null +++ b/src/tools/designer/src/lib/shared/previewmanager_p.h @@ -0,0 +1,146 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PREVIEWMANAGER_H +#define PREVIEWMANAGER_H + +#include "shared_global_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QWidget; +class QPixmap; +class QAction; +class QActionGroup; +class QMenu; +class QWidget; +class QDesignerSettingsInterface; + +namespace qdesigner_internal { + +// ----------- PreviewConfiguration + +class PreviewConfigurationData; + +class QDESIGNER_SHARED_EXPORT PreviewConfiguration { +public: + PreviewConfiguration(); + explicit PreviewConfiguration(const QString &style, + const QString &applicationStyleSheet = QString(), + const QString &deviceSkin = QString()); + + PreviewConfiguration(const PreviewConfiguration&); + PreviewConfiguration& operator=(const PreviewConfiguration&); + ~PreviewConfiguration(); + + QString style() const; + void setStyle(const QString &); + + // Style sheet to prepend (to simulate the effect od QApplication::setSyleSheet()). + QString applicationStyleSheet() const; + void setApplicationStyleSheet(const QString &); + + QString deviceSkin() const; + void setDeviceSkin(const QString &); + + void clear(); + void toSettings(const QString &prefix, QDesignerSettingsInterface *settings) const; + void fromSettings(const QString &prefix, const QDesignerSettingsInterface *settings); + +private: + QSharedDataPointer m_d; +}; + +QDESIGNER_SHARED_EXPORT bool operator<(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2); +QDESIGNER_SHARED_EXPORT bool operator==(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2); +QDESIGNER_SHARED_EXPORT bool operator!=(const PreviewConfiguration &pc1, const PreviewConfiguration &pc2); + +// ----------- Preview window manager. +// Maintains a list of preview widgets with their associated form windows and configuration. + +class PreviewManagerPrivate; + +class QDESIGNER_SHARED_EXPORT PreviewManager : public QObject +{ + Q_OBJECT +public: + + enum PreviewMode { + // Modal preview. Do not use on Macs as dialogs would have no close button + ApplicationModalPreview, + // Non modal previewing of one form in different configurations (closes if form window changes) + SingleFormNonModalPreview, + // Non modal previewing of several forms in different configurations + MultipleFormNonModalPreview }; + + explicit PreviewManager(PreviewMode mode, QObject *parent); + ~PreviewManager() override; + + // Show preview. Raise existing preview window if there is one with a matching + // configuration, else create a new preview. + QWidget *showPreview(const QDesignerFormWindowInterface *, const PreviewConfiguration &pc, int deviceProfileIndex /*=-1*/, QString *errorMessage); + // Convenience that creates a preview using a configuration taken from the settings. + QWidget *showPreview(const QDesignerFormWindowInterface *, const QString &style, int deviceProfileIndex /*=-1*/, QString *errorMessage); + QWidget *showPreview(const QDesignerFormWindowInterface *, const QString &style, QString *errorMessage); + + int previewCount() const; + + // Create a pixmap for printing. + QPixmap createPreviewPixmap(const QDesignerFormWindowInterface *fw, const PreviewConfiguration &pc, int deviceProfileIndex /*=-1*/, QString *errorMessage); + // Convenience that creates a pixmap using a configuration taken from the settings. + QPixmap createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, int deviceProfileIndex /*=-1*/, QString *errorMessage); + QPixmap createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &style, QString *errorMessage); + + bool eventFilter(QObject *watched, QEvent *event) override; + +public slots: + void closeAllPreviews(); + +signals: + void firstPreviewOpened(); + void lastPreviewClosed(); + +private slots: + void slotZoomChanged(int); + +private: + + virtual Qt::WindowFlags previewWindowFlags(const QWidget *widget) const; + virtual QWidget *createDeviceSkinContainer(const QDesignerFormWindowInterface *) const; + + QWidget *raise(const QDesignerFormWindowInterface *, const PreviewConfiguration &pc); + QWidget *createPreview(const QDesignerFormWindowInterface *, + const PreviewConfiguration &pc, + int deviceProfileIndex /* = -1 */, + QString *errorMessage, + /*Disabled by default, <0 */ + int initialZoom = -1); + + void updatePreviewClosed(QWidget *w); + + PreviewManagerPrivate *d; + + PreviewManager(const PreviewManager &other); + PreviewManager &operator =(const PreviewManager &other); +}; +} + +QT_END_NAMESPACE + +#endif // PREVIEWMANAGER_H diff --git a/src/tools/designer/src/lib/shared/promotionmodel.cpp b/src/tools/designer/src/lib/shared/promotionmodel.cpp new file mode 100644 index 00000000000..8d985193689 --- /dev/null +++ b/src/tools/designer/src/lib/shared/promotionmodel.cpp @@ -0,0 +1,169 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "promotionmodel_p.h" +#include "widgetdatabase_p.h" + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace { + using StandardItemList = QList; + + // Model columns. + enum { ClassNameColumn, IncludeFileColumn, IncludeTypeColumn, ReferencedColumn, NumColumns }; + + // Create a model row. + StandardItemList modelRow() { + StandardItemList rc; + for (int i = 0; i < NumColumns; i++) { + rc.push_back(new QStandardItem()); + } + return rc; + } + + // Create a model row for a base class (read-only, cannot be selected). + StandardItemList baseModelRow(const QDesignerWidgetDataBaseItemInterface *dbItem) { + StandardItemList rc = modelRow(); + + rc[ClassNameColumn]->setText(dbItem->name()); + for (int i = 0; i < NumColumns; i++) { + rc[i]->setFlags(Qt::ItemIsEnabled); + } + return rc; + } + + // Create an editable model row for a promoted class. + StandardItemList promotedModelRow(QDesignerWidgetDataBaseItemInterface *baseItem, + QDesignerWidgetDataBaseItemInterface *dbItem, + bool referenced) + { + qdesigner_internal::PromotionModel::ModelData data; + data.baseItem = baseItem; + data.promotedItem = dbItem; + data.referenced = referenced; + + const QVariant userData = QVariant::fromValue(data); + + StandardItemList rc = modelRow(); + // name + rc[ClassNameColumn]->setText(dbItem->name()); + rc[ClassNameColumn]->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable); + rc[ClassNameColumn]->setData(userData); + // header + const qdesigner_internal::IncludeSpecification spec = qdesigner_internal::includeSpecification(dbItem->includeFile()); + rc[IncludeFileColumn]->setText(spec.first); + rc[IncludeFileColumn]->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable); + rc[IncludeFileColumn]->setData(userData); + // global include + rc[IncludeTypeColumn]->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled|Qt::ItemIsEditable|Qt::ItemIsUserCheckable); + rc[IncludeTypeColumn]->setData(userData); + rc[IncludeTypeColumn]->setCheckState(spec.second == qdesigner_internal::IncludeGlobal ? Qt::Checked : Qt::Unchecked); + // referenced + rc[ReferencedColumn]->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); + rc[ClassNameColumn]->setData(userData); + if (!referenced) { + //: Usage of promoted widgets + static const QString notUsed = QCoreApplication::translate("PromotionModel", "Not used"); + rc[ReferencedColumn]->setText(notUsed); + } + return rc; + } +} + +namespace qdesigner_internal { + + PromotionModel::PromotionModel(QDesignerFormEditorInterface *core) : + m_core(core) + { + connect(this, &QStandardItemModel::itemChanged, this, &PromotionModel::slotItemChanged); + } + + void PromotionModel::initializeHeaders() { + setColumnCount(NumColumns); + QStringList horizontalLabels(tr("Name")); + horizontalLabels += tr("Header file"); + horizontalLabels += tr("Global include"); + horizontalLabels += tr("Usage"); + setHorizontalHeaderLabels (horizontalLabels); + } + + void PromotionModel::updateFromWidgetDatabase() { + using PromotedClasses = QDesignerPromotionInterface::PromotedClasses; + + clear(); + initializeHeaders(); + + // retrieve list of pairs from DB and convert into a tree structure. + // Set the item index as user data on the item. + const PromotedClasses promotedClasses = m_core->promotion()->promotedClasses(); + + if (promotedClasses.isEmpty()) + return; + + const QSet usedPromotedClasses = m_core->promotion()->referencedPromotedClassNames(); + + QDesignerWidgetDataBaseItemInterface *baseClass = nullptr; + QStandardItem *baseItem = nullptr; + + for (auto &pi : promotedClasses) { + // Start a new base class? + if (baseClass != pi.baseItem) { + baseClass = pi.baseItem; + const StandardItemList baseRow = baseModelRow(pi.baseItem); + baseItem = baseRow.constFirst(); + appendRow(baseRow); + } + Q_ASSERT(baseItem); + // Append derived + baseItem->appendRow(promotedModelRow(pi.baseItem, pi.promotedItem, + usedPromotedClasses.contains(pi.promotedItem->name()))); + } + } + + void PromotionModel::slotItemChanged(QStandardItem * changedItem) { + // Retrieve DB item + const ModelData data = modelData(changedItem); + Q_ASSERT(data.isValid()); + QDesignerWidgetDataBaseItemInterface *dbItem = data.promotedItem; + // Change header or type + switch (changedItem->column()) { + case ClassNameColumn: + emit classNameChanged(dbItem, changedItem->text()); + break; + case IncludeTypeColumn: + case IncludeFileColumn: { + // Get both file and type items via parent. + const QStandardItem *baseClassItem = changedItem->parent(); + const QStandardItem *fileItem = baseClassItem->child(changedItem->row(), IncludeFileColumn); + const QStandardItem *typeItem = baseClassItem->child(changedItem->row(), IncludeTypeColumn); + emit includeFileChanged(dbItem, buildIncludeFile(fileItem->text(), typeItem->checkState() == Qt::Checked ? IncludeGlobal : IncludeLocal)); + } + break; + } + } + + PromotionModel::ModelData PromotionModel::modelData(const QStandardItem *item) const + { + const QVariant userData = item->data(); + return userData.canConvert() ? userData.value() : ModelData(); + } + + PromotionModel::ModelData PromotionModel::modelData(const QModelIndex &index) const + { + return index.isValid() ? modelData(itemFromIndex(index)) : ModelData(); + } + + QModelIndex PromotionModel::indexOfClass(const QString &className) const { + const StandardItemList matches = findItems (className, Qt::MatchFixedString|Qt::MatchCaseSensitive|Qt::MatchRecursive); + return matches.isEmpty() ? QModelIndex() : indexFromItem (matches.constFirst()); + } +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/promotionmodel_p.h b/src/tools/designer/src/lib/shared/promotionmodel_p.h new file mode 100644 index 00000000000..c92017b8681 --- /dev/null +++ b/src/tools/designer/src/lib/shared/promotionmodel_p.h @@ -0,0 +1,69 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PROMOTIONMODEL_H +#define PROMOTIONMODEL_H + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerWidgetDataBaseItemInterface; + +namespace qdesigner_internal { + + // Item model representing the promoted widgets. + class PromotionModel : public QStandardItemModel { + Q_OBJECT + + public: + struct ModelData { + bool isValid() const { return promotedItem != nullptr; } + + QDesignerWidgetDataBaseItemInterface *baseItem{nullptr}; + QDesignerWidgetDataBaseItemInterface *promotedItem{nullptr}; + bool referenced{false}; + }; + + explicit PromotionModel(QDesignerFormEditorInterface *core); + + void updateFromWidgetDatabase(); + + ModelData modelData(const QModelIndex &index) const; + ModelData modelData(const QStandardItem *item) const; + + QModelIndex indexOfClass(const QString &className) const; + + signals: + void includeFileChanged(QDesignerWidgetDataBaseItemInterface *, const QString &includeFile); + void classNameChanged(QDesignerWidgetDataBaseItemInterface *, const QString &newName); + + private slots: + void slotItemChanged(QStandardItem * item); + + private: + void initializeHeaders(); + + QDesignerFormEditorInterface *m_core; + }; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(qdesigner_internal::PromotionModel::ModelData) + +#endif // PROMOTIONMODEL_H diff --git a/src/tools/designer/src/lib/shared/promotiontaskmenu.cpp b/src/tools/designer/src/lib/shared/promotiontaskmenu.cpp new file mode 100644 index 00000000000..7cedb226ba7 --- /dev/null +++ b/src/tools/designer/src/lib/shared/promotiontaskmenu.cpp @@ -0,0 +1,313 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "promotiontaskmenu_p.h" +#include "qdesigner_promotiondialog_p.h" +#include "widgetfactory_p.h" +#include "metadatabase_p.h" +#include "widgetdatabase_p.h" +#include "qdesigner_command_p.h" +#include "signalslotdialog_p.h" +#include "qdesigner_objectinspector_p.h" +#include "abstractintrospection_p.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +static QAction *separatorAction(QObject *parent) +{ + QAction *rc = new QAction(parent); + rc->setSeparator(true); + return rc; +} + +static inline QDesignerLanguageExtension *languageExtension(QDesignerFormEditorInterface *core) +{ + return qt_extension(core->extensionManager(), core); +} + +namespace qdesigner_internal { + +PromotionTaskMenu::PromotionTaskMenu(QWidget *widget,Mode mode, QObject *parent) : + QObject(parent), + m_mode(mode), + m_widget(widget), + m_globalEditAction(new QAction(tr("Promoted widgets..."), this)), + m_EditPromoteToAction(new QAction(tr("Promote to ..."), this)), + m_EditSignalsSlotsAction(new QAction(tr("Change signals/slots..."), this)), + m_promoteLabel(tr("Promote to")), + m_demoteLabel(tr("Demote to %1")) +{ + connect(m_globalEditAction, &QAction::triggered, this, &PromotionTaskMenu::slotEditPromotedWidgets); + connect(m_EditPromoteToAction, &QAction::triggered, this, &PromotionTaskMenu::slotEditPromoteTo); + connect(m_EditSignalsSlotsAction, &QAction::triggered, this, &PromotionTaskMenu::slotEditSignalsSlots); +} + +PromotionTaskMenu::Mode PromotionTaskMenu::mode() const +{ + return m_mode; +} + +void PromotionTaskMenu::setMode(Mode m) +{ + m_mode = m; +} + +void PromotionTaskMenu::setWidget(QWidget *widget) +{ + m_widget = widget; +} + +void PromotionTaskMenu::setPromoteLabel(const QString &promoteLabel) +{ + m_promoteLabel = promoteLabel; +} + +void PromotionTaskMenu::setEditPromoteToLabel(const QString &promoteEditLabel) +{ + m_EditPromoteToAction->setText(promoteEditLabel); +} + +void PromotionTaskMenu::setDemoteLabel(const QString &demoteLabel) +{ + m_demoteLabel = demoteLabel; +} + +PromotionTaskMenu::PromotionState PromotionTaskMenu::createPromotionActions(QDesignerFormWindowInterface *formWindow) +{ + // clear out old + if (!m_promotionActions.isEmpty()) { + qDeleteAll(m_promotionActions); + m_promotionActions.clear(); + } + // No promotion of main container + if (formWindow->mainContainer() == m_widget) + return NotApplicable; + + // Check for a homogenous selection + const PromotionSelectionList promotionSelection = promotionSelectionList(formWindow); + + if (promotionSelection.isEmpty()) + return NoHomogenousSelection; + + QDesignerFormEditorInterface *core = formWindow->core(); + // if it is promoted: demote only. + if (isPromoted(formWindow->core(), m_widget)) { + const QString label = m_demoteLabel.arg( promotedExtends(core , m_widget)); + QAction *demoteAction = new QAction(label, this); + connect(demoteAction, &QAction::triggered, this, &PromotionTaskMenu::slotDemoteFromCustomWidget); + m_promotionActions.push_back(demoteAction); + return CanDemote; + } + // figure out candidates + const QString baseClassName = WidgetFactory::classNameOf(core, m_widget); + const WidgetDataBaseItemList candidates = promotionCandidates(core->widgetDataBase(), baseClassName ); + if (candidates.isEmpty()) { + // Is this thing promotable at all? + return QDesignerPromotionDialog::baseClassNames(core->promotion()).contains(baseClassName) ? CanPromote : NotApplicable; + } + + QMenu *candidatesMenu = new QMenu(); + // Create a sub menu + // Set up actions and map class names + for (auto *item : candidates) { + const QString customClassName = item->name(); + candidatesMenu->addAction(customClassName, + this, [this, customClassName] { this->slotPromoteToCustomWidget(customClassName); }); + } + // Sub menu action + QAction *subMenuAction = new QAction(m_promoteLabel, this); + subMenuAction->setMenu(candidatesMenu); + m_promotionActions.push_back(subMenuAction); + return CanPromote; +} + +void PromotionTaskMenu::addActions(unsigned separatorFlags, ActionList &actionList) +{ + addActions(formWindow(), separatorFlags, actionList); +} + +void PromotionTaskMenu::addActions(QDesignerFormWindowInterface *fw, unsigned flags, + ActionList &actionList) +{ + Q_ASSERT(m_widget); + const auto previousSize = actionList.size(); + const PromotionState promotionState = createPromotionActions(fw); + + // Promotion candidates/demote + actionList += m_promotionActions; + + // Edit action depending on context + switch (promotionState) { + case CanPromote: + actionList += m_EditPromoteToAction; + break; + case CanDemote: + if (!(flags & SuppressGlobalEdit)) + actionList += m_globalEditAction; + if (!languageExtension(fw->core())) { + actionList += separatorAction(this); + actionList += m_EditSignalsSlotsAction; + } + break; + default: + if (!(flags & SuppressGlobalEdit)) + actionList += m_globalEditAction; + break; + } + // Add separators if required + if (actionList.size() > previousSize) { + if (flags & LeadingSeparator) + actionList.insert(previousSize, separatorAction(this)); + if (flags & TrailingSeparator) + actionList += separatorAction(this); + } +} + +void PromotionTaskMenu::addActions(QDesignerFormWindowInterface *fw, unsigned flags, QMenu *menu) +{ + ActionList actionList; + addActions(fw, flags, actionList); + menu->addActions(actionList); +} + +void PromotionTaskMenu::addActions(unsigned flags, QMenu *menu) +{ + addActions(formWindow(), flags, menu); +} + +void PromotionTaskMenu::promoteTo(QDesignerFormWindowInterface *fw, const QString &customClassName) +{ + Q_ASSERT(m_widget); + PromoteToCustomWidgetCommand *cmd = new PromoteToCustomWidgetCommand(fw); + cmd->init(promotionSelectionList(fw), customClassName); + fw->commandHistory()->push(cmd); +} + + +void PromotionTaskMenu::slotPromoteToCustomWidget(const QString &customClassName) +{ + promoteTo(formWindow(), customClassName); +} + +void PromotionTaskMenu::slotDemoteFromCustomWidget() +{ + QDesignerFormWindowInterface *fw = formWindow(); + const PromotionSelectionList promotedWidgets = promotionSelectionList(fw); + Q_ASSERT(!promotedWidgets.isEmpty() && isPromoted(fw->core(), promotedWidgets.constFirst())); + + // ### use the undo stack + DemoteFromCustomWidgetCommand *cmd = new DemoteFromCustomWidgetCommand(fw); + cmd->init(promotedWidgets); + fw->commandHistory()->push(cmd); +} + +void PromotionTaskMenu::slotEditPromoteTo() +{ + Q_ASSERT(m_widget); + // Check whether invoked over a promotable widget + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + const QString base_class_name = WidgetFactory::classNameOf(core, m_widget); + Q_ASSERT(QDesignerPromotionDialog::baseClassNames(core->promotion()).contains(base_class_name)); + // Show over promotable widget + QString promoteToClassName; + QDialog *promotionEditor = nullptr; + if (QDesignerLanguageExtension *lang = languageExtension(core)) + promotionEditor = lang->createPromotionDialog(core, base_class_name, &promoteToClassName, fw); + if (!promotionEditor) + promotionEditor = new QDesignerPromotionDialog(core, fw, base_class_name, &promoteToClassName); + if (promotionEditor->exec() == QDialog::Accepted && !promoteToClassName.isEmpty()) { + promoteTo(fw, promoteToClassName); + } + delete promotionEditor; +} + +void PromotionTaskMenu::slotEditPromotedWidgets() +{ + // Global context, show over non-promotable widget + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + editPromotedWidgets(fw->core(), fw); +} + +PromotionTaskMenu::PromotionSelectionList PromotionTaskMenu::promotionSelectionList(QDesignerFormWindowInterface *formWindow) const +{ + // In multi selection mode, check for a homogenous selection (same class, same promotion state) + // and return the list if this is the case. Also make sure m_widget + // is the last widget in the list so that it is re-selected as the last + // widget by the promotion commands. + + PromotionSelectionList rc; + + if (m_mode != ModeSingleWidget) { + QDesignerFormEditorInterface *core = formWindow->core(); + const QDesignerIntrospectionInterface *intro = core->introspection(); + const QString className = intro->metaObject(m_widget)->className(); + const bool promoted = isPromoted(formWindow->core(), m_widget); + // Just in case someone plugged an old-style Object Inspector + if (QDesignerObjectInspector *designerObjectInspector = qobject_cast(core->objectInspector())) { + Selection s; + designerObjectInspector->getSelection(s); + // Find objects of similar state + const QWidgetList &source = m_mode == ModeManagedMultiSelection ? s.managed : s.unmanaged; + for (auto *w : source) { + if (w != m_widget) { + // Selection state mismatch + if (intro->metaObject(w)->className() != className || isPromoted(core, w) != promoted) + return PromotionSelectionList(); + rc.push_back(w); + } + } + } + } + + rc.push_back(m_widget); + return rc; +} + +QDesignerFormWindowInterface *PromotionTaskMenu::formWindow() const +{ + // Use the QObject overload of QDesignerFormWindowInterface::findFormWindow since that works + // for QDesignerMenus also. + QObject *o = m_widget; + QDesignerFormWindowInterface *result = QDesignerFormWindowInterface::findFormWindow(o); + Q_ASSERT(result != nullptr); + return result; +} + +void PromotionTaskMenu::editPromotedWidgets(QDesignerFormEditorInterface *core, QWidget* parent) { + QDesignerLanguageExtension *lang = languageExtension(core); + // Show over non-promotable widget + QDialog *promotionEditor = nullptr; + if (lang) + lang->createPromotionDialog(core, parent); + if (!promotionEditor) + promotionEditor = new QDesignerPromotionDialog(core, parent); + promotionEditor->exec(); + delete promotionEditor; +} + +void PromotionTaskMenu::slotEditSignalsSlots() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + SignalSlotDialog::editPromotedClass(fw->core(), m_widget, fw); +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/promotiontaskmenu_p.h b/src/tools/designer/src/lib/shared/promotiontaskmenu_p.h new file mode 100644 index 00000000000..371d95d6d7f --- /dev/null +++ b/src/tools/designer/src/lib/shared/promotiontaskmenu_p.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PROMOTIONTASKMENU_H +#define PROMOTIONTASKMENU_H + +#include "shared_global_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; + +class QAction; +class QMenu; +class QWidget; + +namespace qdesigner_internal { + +// A helper class for creating promotion context menus and handling promotion actions. + +class QDESIGNER_SHARED_EXPORT PromotionTaskMenu: public QObject +{ + Q_OBJECT +public: + enum Mode { + ModeSingleWidget, + ModeManagedMultiSelection, + ModeUnmanagedMultiSelection + }; + + explicit PromotionTaskMenu(QWidget *widget,Mode mode = ModeManagedMultiSelection, QObject *parent = nullptr); + + Mode mode() const; + void setMode(Mode m); + + void setWidget(QWidget *widget); + + // Set menu labels + void setPromoteLabel(const QString &promoteLabel); + void setEditPromoteToLabel(const QString &promoteEditLabel); + // Defaults to "Demote to %1".arg(class). + void setDemoteLabel(const QString &demoteLabel); + + using ActionList = QList; + + enum AddFlags { LeadingSeparator = 1, TrailingSeparator = 2, SuppressGlobalEdit = 4}; + + // Adds a list of promotion actions according to the current promotion state of the widget. + void addActions(QDesignerFormWindowInterface *fw, unsigned flags, ActionList &actionList); + // Convenience that finds the form window. + void addActions(unsigned flags, ActionList &actionList); + + void addActions(QDesignerFormWindowInterface *fw, unsigned flags, QMenu *menu); + void addActions(unsigned flags, QMenu *menu); + + // Pop up the editor in a global context. + static void editPromotedWidgets(QDesignerFormEditorInterface *core, QWidget* parent); + +private slots: + void slotPromoteToCustomWidget(const QString &customClassName); + void slotDemoteFromCustomWidget(); + void slotEditPromotedWidgets(); + void slotEditPromoteTo(); + void slotEditSignalsSlots(); + +private: + void promoteTo(QDesignerFormWindowInterface *fw, const QString &customClassName); + + enum PromotionState { NotApplicable, NoHomogenousSelection, CanPromote, CanDemote }; + PromotionState createPromotionActions(QDesignerFormWindowInterface *formWindow); + QDesignerFormWindowInterface *formWindow() const; + + using PromotionSelectionList = QList >; + PromotionSelectionList promotionSelectionList(QDesignerFormWindowInterface *formWindow) const; + + Mode m_mode; + + QPointer m_widget; + + // Per-Widget actions + QList m_promotionActions; + + QAction *m_globalEditAction; + QAction *m_EditPromoteToAction; + QAction *m_EditSignalsSlotsAction; + + QString m_promoteLabel; + QString m_demoteLabel; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PROMOTIONTASKMENU_H diff --git a/src/tools/designer/src/lib/shared/propertylineedit.cpp b/src/tools/designer/src/lib/shared/propertylineedit.cpp new file mode 100644 index 00000000000..bea91010a59 --- /dev/null +++ b/src/tools/designer/src/lib/shared/propertylineedit.cpp @@ -0,0 +1,58 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "propertylineedit_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + PropertyLineEdit::PropertyLineEdit(QWidget *parent) : + QLineEdit(parent), m_wantNewLine(false) + { + } + + bool PropertyLineEdit::event(QEvent *e) + { + // handle 'Select all' here as it is not done in the QLineEdit + if (e->type() == QEvent::ShortcutOverride && !isReadOnly()) { + QKeyEvent* ke = static_cast (e); + if (ke->modifiers() & Qt::ControlModifier) { + if(ke->key() == Qt::Key_A) { + ke->accept(); + return true; + } + } + } + return QLineEdit::event(e); + } + + void PropertyLineEdit::insertNewLine() { + insertText(u"\\n"_s); + } + + void PropertyLineEdit::insertText(const QString &text) { + // position cursor after new text and grab focus + const int oldCursorPosition = cursorPosition (); + insert(text); + setCursorPosition (oldCursorPosition + text.size()); + setFocus(Qt::OtherFocusReason); + } + + void PropertyLineEdit::contextMenuEvent(QContextMenuEvent *event) { + QMenu *menu = createStandardContextMenu (); + + if (m_wantNewLine) { + menu->addSeparator(); + menu->addAction(tr("Insert line break"), this, &PropertyLineEdit::insertNewLine); + } + + menu->exec(event->globalPos()); + } +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/propertylineedit_p.h b/src/tools/designer/src/lib/shared/propertylineedit_p.h new file mode 100644 index 00000000000..d1a1c942827 --- /dev/null +++ b/src/tools/designer/src/lib/shared/propertylineedit_p.h @@ -0,0 +1,47 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PROPERTYLINEEDIT_H +#define PROPERTYLINEEDIT_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + + // A line edit with a special context menu allowing for adding (escaped) new lines + class PropertyLineEdit : public QLineEdit { + Q_OBJECT + public: + explicit PropertyLineEdit(QWidget *parent); + void setWantNewLine(bool nl) { m_wantNewLine = nl; } + bool wantNewLine() const { return m_wantNewLine; } + + bool event(QEvent *e) override; + protected: + void contextMenuEvent (QContextMenuEvent *event ) override; + private slots: + void insertNewLine(); + private: + void insertText(const QString &); + bool m_wantNewLine; + }; +} + +QT_END_NAMESPACE + +#endif // PROPERTYLINEEDIT_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_command.cpp b/src/tools/designer/src/lib/shared/qdesigner_command.cpp new file mode 100644 index 00000000000..2633224e69d --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_command.cpp @@ -0,0 +1,2872 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "qdesigner_utils_p.h" +#include "layout_p.h" +#include "qlayout_widget_p.h" +#include "qdesigner_widget_p.h" +#include "qdesigner_menu_p.h" +#include "shared_enums_p.h" +#include "metadatabase_p.h" +#include "formwindowbase_p.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Q_DECLARE_METATYPE(QWidgetList) + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static inline void setPropertySheetWindowTitle(const QDesignerFormEditorInterface *core, QObject *o, const QString &t) +{ + if (QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), o)) { + const int idx = sheet->indexOf(u"windowTitle"_s); + if (idx != -1) { + sheet->setProperty(idx, t); + sheet->setChanged(idx, true); + } + } +} + +namespace qdesigner_internal { + +// Helpers for the dynamic properties that store Z/Widget order +static const char widgetOrderPropertyC[] = "_q_widgetOrder"; +static const char zOrderPropertyC[] = "_q_zOrder"; + +static void addToWidgetListDynamicProperty(QWidget *parentWidget, QWidget *widget, const char *name, int index = -1) +{ + QWidgetList list = qvariant_cast(parentWidget->property(name)); + list.removeAll(widget); + if (index >= 0 && index < list.size()) { + list.insert(index, widget); + } else { + list.append(widget); + } + parentWidget->setProperty(name, QVariant::fromValue(list)); +} + +static int removeFromWidgetListDynamicProperty(QWidget *parentWidget, QWidget *widget, const char *name) +{ + QWidgetList list = qvariant_cast(parentWidget->property(name)); + const int firstIndex = list.indexOf(widget); + if (firstIndex != -1) { + list.removeAll(widget); + parentWidget->setProperty(name, QVariant::fromValue(list)); + } + return firstIndex; +} + +// ---- InsertWidgetCommand ---- +InsertWidgetCommand::InsertWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_insertMode(QDesignerLayoutDecorationExtension::InsertWidgetMode), + m_layoutHelper(nullptr), + m_widgetWasManaged(false) +{ +} + +InsertWidgetCommand::~InsertWidgetCommand() +{ + delete m_layoutHelper; +} + +void InsertWidgetCommand::init(QWidget *widget, bool already_in_form, int layoutRow, int layoutColumn) +{ + m_widget = widget; + + setText(QApplication::translate("Command", "Insert '%1'").arg(widget->objectName())); + + QWidget *parentWidget = m_widget->parentWidget(); + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), parentWidget); + + m_insertMode = deco ? deco->currentInsertMode() : QDesignerLayoutDecorationExtension::InsertWidgetMode; + if (layoutRow >= 0 && layoutColumn >= 0) { + m_cell.first = layoutRow; + m_cell.second = layoutColumn; + } else { + m_cell = deco ? deco->currentCell() : std::make_pair(0, 0); + } + m_widgetWasManaged = already_in_form; +} + +static void recursiveUpdate(QWidget *w) +{ + w->update(); + + for (auto *child : w->children()) { + if (QWidget *w = qobject_cast(child)) + recursiveUpdate(w); + } +} + +void InsertWidgetCommand::redo() +{ + QWidget *parentWidget = m_widget->parentWidget(); + Q_ASSERT(parentWidget); + + addToWidgetListDynamicProperty(parentWidget, m_widget, widgetOrderPropertyC); + addToWidgetListDynamicProperty(parentWidget, m_widget, zOrderPropertyC); + + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), parentWidget); + + if (deco != nullptr) { + const LayoutInfo::Type type = LayoutInfo::layoutType(core, LayoutInfo::managedLayout(core, parentWidget)); + m_layoutHelper = LayoutHelper::createLayoutHelper(type); + m_layoutHelper->pushState(core, parentWidget); + if (type == LayoutInfo::Grid) { + switch (m_insertMode) { + case QDesignerLayoutDecorationExtension::InsertRowMode: { + deco->insertRow(m_cell.first); + } break; + + case QDesignerLayoutDecorationExtension::InsertColumnMode: { + deco->insertColumn(m_cell.second); + } break; + + default: break; + } // end switch + } + deco->insertWidget(m_widget, m_cell); + } + + if (!m_widgetWasManaged) + formWindow()->manageWidget(m_widget); + m_widget->show(); + formWindow()->emitSelectionChanged(); + + if (parentWidget && parentWidget->layout()) { + recursiveUpdate(parentWidget); + parentWidget->layout()->invalidate(); + } + + refreshBuddyLabels(); +} + +void InsertWidgetCommand::undo() +{ + QWidget *parentWidget = m_widget->parentWidget(); + + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), parentWidget); + + if (deco) { + deco->removeWidget(m_widget); + m_layoutHelper->popState(core, parentWidget); + } + + if (!m_widgetWasManaged) { + formWindow()->unmanageWidget(m_widget); + m_widget->hide(); + } + + removeFromWidgetListDynamicProperty(parentWidget, m_widget, widgetOrderPropertyC); + removeFromWidgetListDynamicProperty(parentWidget, m_widget, zOrderPropertyC); + + formWindow()->emitSelectionChanged(); + + refreshBuddyLabels(); +} + +void InsertWidgetCommand::refreshBuddyLabels() +{ + const auto label_list = formWindow()->findChildren(); + if (label_list.isEmpty()) + return; + + const QString buddyProperty = u"buddy"_s; + const QByteArray objectNameU8 = m_widget->objectName().toUtf8(); + // Re-set the buddy (The sheet locates the object by name and sets it) + for (QLabel *label : label_list) { + if (QDesignerPropertySheetExtension* sheet = propertySheet(label)) { + const int idx = sheet->indexOf(buddyProperty); + if (idx != -1) { + const QVariant value = sheet->property(idx); + if (value.toByteArray() == objectNameU8) + sheet->setProperty(idx, value); + } + } + } +} + +// ---- ChangeZOrderCommand ---- +ChangeZOrderCommand::ChangeZOrderCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ +} + +void ChangeZOrderCommand::init(QWidget *widget) +{ + Q_ASSERT(widget); + + m_widget = widget; + + setText(QApplication::translate("Command", "Change Z-order of '%1'").arg(widget->objectName())); + + m_oldParentZOrder = qvariant_cast(widget->parentWidget()->property("_q_zOrder")); + const qsizetype index = m_oldParentZOrder.indexOf(m_widget); + if (index != -1 && index + 1 < m_oldParentZOrder.size()) + m_oldPreceding = m_oldParentZOrder.at(index + 1); +} + +void ChangeZOrderCommand::redo() +{ + m_widget->parentWidget()->setProperty("_q_zOrder", QVariant::fromValue(reorderWidget(m_oldParentZOrder, m_widget))); + + reorder(m_widget); +} + +void ChangeZOrderCommand::undo() +{ + m_widget->parentWidget()->setProperty("_q_zOrder", QVariant::fromValue(m_oldParentZOrder)); + + if (m_oldPreceding) + m_widget->stackUnder(m_oldPreceding); + else + m_widget->raise(); +} + +// ---- RaiseWidgetCommand ---- +RaiseWidgetCommand::RaiseWidgetCommand(QDesignerFormWindowInterface *formWindow) + : ChangeZOrderCommand(formWindow) +{ +} + +void RaiseWidgetCommand::init(QWidget *widget) +{ + ChangeZOrderCommand::init(widget); + setText(QApplication::translate("Command", "Raise '%1'").arg(widget->objectName())); +} + +QWidgetList RaiseWidgetCommand::reorderWidget(const QWidgetList &list, QWidget *widget) const +{ + QWidgetList l = list; + l.removeAll(widget); + l.append(widget); + return l; +} + +void RaiseWidgetCommand::reorder(QWidget *widget) const +{ + widget->raise(); +} + +// ---- LowerWidgetCommand ---- +LowerWidgetCommand::LowerWidgetCommand(QDesignerFormWindowInterface *formWindow) + : ChangeZOrderCommand(formWindow) +{ +} + +QWidgetList LowerWidgetCommand::reorderWidget(const QWidgetList &list, QWidget *widget) const +{ + QWidgetList l = list; + l.removeAll(widget); + l.prepend(widget); + return l; +} + +void LowerWidgetCommand::init(QWidget *widget) +{ + ChangeZOrderCommand::init(widget); + setText(QApplication::translate("Command", "Lower '%1'").arg(widget->objectName())); +} + +void LowerWidgetCommand::reorder(QWidget *widget) const +{ + widget->lower(); +} + +// ---- ManageWidgetCommandHelper +ManageWidgetCommandHelper::ManageWidgetCommandHelper() = default; + +void ManageWidgetCommandHelper::init(const QDesignerFormWindowInterface *fw, QWidget *widget) +{ + m_widget = widget; + m_managedChildren.clear(); + + const QWidgetList children = m_widget->findChildren(); + m_managedChildren.reserve(children.size()); + for (auto *w : children) { + if (fw->isManaged(w)) + m_managedChildren.push_back(w); + } +} + +void ManageWidgetCommandHelper::init(QWidget *widget, const QWidgetList &managedChildren) +{ + m_widget = widget; + m_managedChildren = managedChildren; +} + +void ManageWidgetCommandHelper::manage(QDesignerFormWindowInterface *fw) +{ + // Manage the managed children after parent + fw->manageWidget(m_widget); + for (auto *w : std::as_const(m_managedChildren)) + fw->manageWidget(w); +} + +void ManageWidgetCommandHelper::unmanage(QDesignerFormWindowInterface *fw) +{ + // Unmanage the managed children first + for (auto *w : std::as_const(m_managedChildren)) + fw->unmanageWidget(w); + fw->unmanageWidget(m_widget); +} + +// ---- DeleteWidgetCommand ---- +DeleteWidgetCommand::DeleteWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_layoutType(LayoutInfo::NoLayout), + m_layoutHelper(nullptr), + m_flags(0), + m_splitterIndex(-1), + m_layoutSimplified(false), + m_formItem(nullptr), + m_tabOrderIndex(-1), + m_widgetOrderIndex(-1), + m_zOrderIndex(-1) +{ +} + +DeleteWidgetCommand::~DeleteWidgetCommand() +{ + delete m_layoutHelper; +} + +void DeleteWidgetCommand::init(QWidget *widget, unsigned flags) +{ + m_widget = widget; + m_parentWidget = widget->parentWidget(); + m_geometry = widget->geometry(); + m_flags = flags; + m_layoutType = LayoutInfo::NoLayout; + m_splitterIndex = -1; + bool isManaged; // Check for a managed layout + QLayout *layout; + m_layoutType = LayoutInfo::laidoutWidgetType(formWindow()->core(), m_widget, &isManaged, &layout); + if (!isManaged) + m_layoutType = LayoutInfo::NoLayout; + switch (m_layoutType) { + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: { + QSplitter *splitter = qobject_cast(m_parentWidget); + Q_ASSERT(splitter); + m_splitterIndex = splitter->indexOf(widget); + } + break; + case LayoutInfo::NoLayout: + break; + default: + m_layoutHelper = LayoutHelper::createLayoutHelper(m_layoutType); + m_layoutPosition = m_layoutHelper->itemInfo(layout, m_widget); + break; + } + + m_formItem = formWindow()->core()->metaDataBase()->item(formWindow()); + m_tabOrderIndex = m_formItem->tabOrder().indexOf(widget); + + // Build the list of managed children + m_manageHelper.init(formWindow(), m_widget); + + setText(QApplication::translate("Command", "Delete '%1'").arg(widget->objectName())); +} + +void DeleteWidgetCommand::redo() +{ + formWindow()->clearSelection(); + QDesignerFormEditorInterface *core = formWindow()->core(); + + if (QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_parentWidget)) { + const int count = c->count(); + for (int i=0; iwidget(i) == m_widget) { + c->remove(i); + return; + } + } + } + + m_widgetOrderIndex = removeFromWidgetListDynamicProperty(m_parentWidget, m_widget, widgetOrderPropertyC); + m_zOrderIndex = removeFromWidgetListDynamicProperty(m_parentWidget, m_widget, zOrderPropertyC); + + if (QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), m_parentWidget)) + deco->removeWidget(m_widget); + + if (m_layoutHelper) + switch (m_layoutType) { + case LayoutInfo::NoLayout: + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: + break; + default: + // Attempt to simplify grids if a row/column becomes empty + m_layoutSimplified = (m_flags & DoNotSimplifyLayout) ? false : m_layoutHelper->canSimplify(core, m_parentWidget, m_layoutPosition); + if (m_layoutSimplified) { + m_layoutHelper->pushState(core, m_parentWidget); + m_layoutHelper->simplify(core, m_parentWidget, m_layoutPosition); + } + break; + } + + if (!(m_flags & DoNotUnmanage)) + m_manageHelper.unmanage(formWindow()); + + m_widget->setParent(formWindow()); + m_widget->hide(); + + if (m_tabOrderIndex != -1) { + QWidgetList tab_order = m_formItem->tabOrder(); + tab_order.removeAt(m_tabOrderIndex); + m_formItem->setTabOrder(tab_order); + } +} + +void DeleteWidgetCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + formWindow()->clearSelection(); + + m_widget->setParent(m_parentWidget); + + if (QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_parentWidget)) { + c->addWidget(m_widget); + return; + } + + addToWidgetListDynamicProperty(m_parentWidget, m_widget, widgetOrderPropertyC, m_widgetOrderIndex); + addToWidgetListDynamicProperty(m_parentWidget, m_widget, zOrderPropertyC, m_zOrderIndex); + + m_widget->setGeometry(m_geometry); + + if (!(m_flags & DoNotUnmanage)) + m_manageHelper.manage(formWindow()); + // ### set up alignment + switch (m_layoutType) { + case LayoutInfo::NoLayout: + break; + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: { + QSplitter *splitter = qobject_cast(m_widget->parent()); + Q_ASSERT(splitter); + splitter->insertWidget(m_splitterIndex, m_widget); + } break; + default: { + Q_ASSERT(m_layoutHelper); + if (m_layoutSimplified) + m_layoutHelper->popState(core, m_parentWidget); + QLayout *layout = LayoutInfo::managedLayout(core, m_parentWidget); + Q_ASSERT(m_layoutType == LayoutInfo::layoutType(core, layout)); + m_layoutHelper->insertWidget(layout, m_layoutPosition, m_widget); + } + break; + } + + m_widget->show(); + + if (m_tabOrderIndex != -1) { + QWidgetList tab_order = m_formItem->tabOrder(); + tab_order.insert(m_tabOrderIndex, m_widget); + m_formItem->setTabOrder(tab_order); + } +} + +// ---- ReparentWidgetCommand ---- +ReparentWidgetCommand::ReparentWidgetCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ +} + +void ReparentWidgetCommand::init(QWidget *widget, QWidget *parentWidget) +{ + Q_ASSERT(widget); + + m_widget = widget; + m_oldParentWidget = widget->parentWidget(); + m_newParentWidget = parentWidget; + + m_oldPos = m_widget->pos(); + m_newPos = m_newParentWidget->mapFromGlobal(m_oldParentWidget->mapToGlobal(m_oldPos)); + + setText(QApplication::translate("Command", "Reparent '%1'").arg(widget->objectName())); + + m_oldParentList = qvariant_cast(m_oldParentWidget->property("_q_widgetOrder")); + m_oldParentZOrder = qvariant_cast(m_oldParentWidget->property("_q_zOrder")); +} + +void ReparentWidgetCommand::redo() +{ + m_widget->setParent(m_newParentWidget); + m_widget->move(m_newPos); + + QWidgetList oldList = m_oldParentList; + oldList.removeAll(m_widget); + m_oldParentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(oldList)); + + QWidgetList newList = qvariant_cast(m_newParentWidget->property("_q_widgetOrder")); + newList.append(m_widget); + m_newParentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(newList)); + + QWidgetList oldZOrder = m_oldParentZOrder; + oldZOrder.removeAll(m_widget); + m_oldParentWidget->setProperty("_q_zOrder", QVariant::fromValue(oldZOrder)); + + QWidgetList newZOrder = qvariant_cast(m_newParentWidget->property("_q_zOrder")); + newZOrder.append(m_widget); + m_newParentWidget->setProperty("_q_zOrder", QVariant::fromValue(newZOrder)); + + m_widget->show(); + core()->objectInspector()->setFormWindow(formWindow()); +} + +void ReparentWidgetCommand::undo() +{ + m_widget->setParent(m_oldParentWidget); + m_widget->move(m_oldPos); + + m_oldParentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(m_oldParentList)); + + QWidgetList newList = qvariant_cast(m_newParentWidget->property("_q_widgetOrder")); + newList.removeAll(m_widget); + m_newParentWidget->setProperty("_q_widgetOrder", QVariant::fromValue(newList)); + + m_oldParentWidget->setProperty("_q_zOrder", QVariant::fromValue(m_oldParentZOrder)); + + QWidgetList newZOrder = qvariant_cast(m_newParentWidget->property("_q_zOrder")); + newZOrder.removeAll(m_widget); + m_newParentWidget->setProperty("_q_zOrder", QVariant::fromValue(newZOrder)); + + m_widget->show(); + core()->objectInspector()->setFormWindow(formWindow()); +} + +PromoteToCustomWidgetCommand::PromoteToCustomWidgetCommand + (QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Promote to custom widget"), formWindow) +{ +} + +void PromoteToCustomWidgetCommand::init(const WidgetPointerList &widgets,const QString &customClassName) +{ + m_widgets = widgets; + m_customClassName = customClassName; +} + +void PromoteToCustomWidgetCommand::redo() +{ + for (QWidget *w : std::as_const(m_widgets)) { + if (w) + promoteWidget(core(), w, m_customClassName); + } + updateSelection(); +} + +void PromoteToCustomWidgetCommand::updateSelection() +{ + // Update class names in ObjectInspector, PropertyEditor + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = fw->core(); + core->objectInspector()->setFormWindow(fw); + if (QObject *o = core->propertyEditor()->object()) + core->propertyEditor()->setObject(o); +} + +void PromoteToCustomWidgetCommand::undo() +{ + for (QWidget *w : std::as_const(m_widgets)) { + if (w) + demoteWidget(core(), w); + } + updateSelection(); +} + +// ---- DemoteFromCustomWidgetCommand ---- + +DemoteFromCustomWidgetCommand::DemoteFromCustomWidgetCommand + (QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Demote from custom widget"), formWindow), + m_promote_cmd(formWindow) +{ +} + +void DemoteFromCustomWidgetCommand::init(const WidgetList &promoted) +{ + m_promote_cmd.init(promoted, promotedCustomClassName(core(), promoted.constFirst())); +} + +void DemoteFromCustomWidgetCommand::redo() +{ + m_promote_cmd.undo(); +} + +void DemoteFromCustomWidgetCommand::undo() +{ + m_promote_cmd.redo(); +} + +// ---------- CursorSelectionState +CursorSelectionState::CursorSelectionState() = default; + +void CursorSelectionState::save(const QDesignerFormWindowInterface *formWindow) +{ + const QDesignerFormWindowCursorInterface *cursor = formWindow->cursor(); + m_selection.clear(); + m_current = cursor->current(); + if (cursor->hasSelection()) { + const int count = cursor->selectedWidgetCount(); + for(int i = 0; i < count; i++) + m_selection.push_back(cursor->selectedWidget(i)); + } +} + +void CursorSelectionState::restore(QDesignerFormWindowInterface *formWindow) const +{ + if (m_selection.isEmpty()) { + formWindow->clearSelection(true); + } else { + // Select current as last + formWindow->clearSelection(false); + for (const auto &wp : m_selection) { + if (!wp.isNull() && wp.data() != m_current) + formWindow->selectWidget(wp.data(), true); + } + if (m_current) + formWindow->selectWidget(m_current, true); + } +} + +// ---- LayoutCommand ---- + +LayoutCommand::LayoutCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_setup(false) +{ +} + +LayoutCommand::~LayoutCommand() +{ + delete m_layout; +} + +void LayoutCommand::init(QWidget *parentWidget, const QWidgetList &widgets, + LayoutInfo::Type layoutType, QWidget *layoutBase, + bool reparentLayoutWidget) +{ + m_parentWidget = parentWidget; + m_widgets = widgets; + formWindow()->simplifySelection(&m_widgets); + m_layout = Layout::createLayout(widgets, parentWidget, formWindow(), layoutBase, layoutType); + m_layout->setReparentLayoutWidget(reparentLayoutWidget); + + switch (layoutType) { + case LayoutInfo::Grid: + setText(QApplication::translate("Command", "Lay out using grid")); + break; + case LayoutInfo::VBox: + setText(QApplication::translate("Command", "Lay out vertically")); + break; + case LayoutInfo::HBox: + setText(QApplication::translate("Command", "Lay out horizontally")); + break; + default: + break; + } + // Delayed setup to avoid confusion in case we are chained + // with a BreakLayout in a morph layout macro + m_setup = false; +} + +void LayoutCommand::redo() +{ + if (!m_setup) { + m_layout->setup(); + m_cursorSelectionState.save(formWindow()); + m_setup = true; + } + m_layout->doLayout(); + core()->objectInspector()->setFormWindow(formWindow()); +} + +void LayoutCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + + QWidget *lb = m_layout->layoutBaseWidget(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), lb); + m_layout->undoLayout(); + delete deco; // release the extension + + // ### generalize (put in function) + if (!m_layoutBase && lb != nullptr && !(qobject_cast(lb) || qobject_cast(lb))) { + core->metaDataBase()->add(lb); + lb->show(); + } + m_cursorSelectionState.restore(formWindow()); + core->objectInspector()->setFormWindow(formWindow()); +} + +// ---- BreakLayoutCommand ---- +BreakLayoutCommand::BreakLayoutCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Break layout"), formWindow), + m_layoutHelper(nullptr), + m_properties(nullptr), + m_propertyMask(0) +{ +} + +BreakLayoutCommand::~BreakLayoutCommand() +{ + delete m_layoutHelper; + delete m_layout; + delete m_properties; +} + +const LayoutProperties *BreakLayoutCommand::layoutProperties() const +{ + return m_properties; +} + +int BreakLayoutCommand::propertyMask() const +{ + return m_propertyMask; +} + +void BreakLayoutCommand::init(const QWidgetList &widgets, QWidget *layoutBase, bool reparentLayoutWidget) +{ + enum Type { SplitterLayout, LayoutHasMarginSpacing, LayoutHasState }; + + const QDesignerFormEditorInterface *core = formWindow()->core(); + m_widgets = widgets; + m_layoutBase = core->widgetFactory()->containerOfWidget(layoutBase); + QLayout *layoutToBeBroken; + const LayoutInfo::Type layoutType = LayoutInfo::managedLayoutType(core, m_layoutBase, &layoutToBeBroken); + m_layout = Layout::createLayout(widgets, m_layoutBase, formWindow(), layoutBase, layoutType); + m_layout->setReparentLayoutWidget(reparentLayoutWidget); + + Type type = LayoutHasState; + switch (layoutType) { + case LayoutInfo::NoLayout: + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: + type = SplitterLayout; + break; + case LayoutInfo::HBox: + case LayoutInfo::VBox: // Margin/spacing need to be saved + type = LayoutHasMarginSpacing; + break; + default: // Margin/spacing need to be saved + has a state (empty rows/columns of a grid) + type = LayoutHasState; + break; + } + Q_ASSERT(m_layout != nullptr); + m_layout->sort(); + + + if (type >= LayoutHasMarginSpacing) { + m_properties = new LayoutProperties; + m_propertyMask = m_properties->fromPropertySheet(core, layoutToBeBroken, LayoutProperties::AllProperties); + } + if (type >= LayoutHasState) + m_layoutHelper = LayoutHelper::createLayoutHelper(layoutType); + m_cursorSelectionState.save(formWindow()); +} + +void BreakLayoutCommand::redo() +{ + if (!m_layout) + return; + + QDesignerFormEditorInterface *core = formWindow()->core(); + QWidget *lb = m_layout->layoutBaseWidget(); + QDesignerLayoutDecorationExtension *deco = qt_extension(core->extensionManager(), lb); + formWindow()->clearSelection(false); + if (m_layoutHelper) + m_layoutHelper->pushState(core, m_layoutBase); + m_layout->breakLayout(); + delete deco; // release the extension + + for (QWidget *widget : std::as_const(m_widgets)) { + widget->resize(widget->size().expandedTo(QSize(16, 16))); + } + // Update unless we are in an intermediate state of morphing layout + // in which a QLayoutWidget will have no layout at all. + if (m_layout->reparentLayoutWidget()) + core->objectInspector()->setFormWindow(formWindow()); +} + +void BreakLayoutCommand::undo() +{ + if (!m_layout) + return; + + formWindow()->clearSelection(false); + m_layout->doLayout(); + if (m_layoutHelper) + m_layoutHelper->popState(formWindow()->core(), m_layoutBase); + + QLayout *layoutToRestored = LayoutInfo::managedLayout(formWindow()->core(), m_layoutBase); + if (m_properties && m_layoutBase && layoutToRestored) + m_properties->toPropertySheet(formWindow()->core(), layoutToRestored, m_propertyMask); + m_cursorSelectionState.restore(formWindow()); + core()->objectInspector()->setFormWindow(formWindow()); +} +// ---- SimplifyLayoutCommand +SimplifyLayoutCommand::SimplifyLayoutCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Simplify Grid Layout"), formWindow), + m_area(0, 0, 32767, 32767), + m_layoutBase(nullptr), + m_layoutHelper(nullptr), + m_layoutSimplified(false) +{ +} + +SimplifyLayoutCommand::~SimplifyLayoutCommand() +{ + delete m_layoutHelper; +} + +bool SimplifyLayoutCommand::canSimplify(QDesignerFormEditorInterface *core, const QWidget *w, int *layoutType) +{ + if (!w) + return false; + QLayout *layout; + const LayoutInfo::Type type = LayoutInfo::managedLayoutType(core, w, &layout); + if (layoutType) + *layoutType = type; + if (!layout) + return false; + switch (type) { // Known negatives + case LayoutInfo::NoLayout: + case LayoutInfo::UnknownLayout: + case LayoutInfo::HSplitter: + case LayoutInfo::VSplitter: + case LayoutInfo::HBox: + case LayoutInfo::VBox: + return false; + default: + break; + } + switch (type) { + case LayoutInfo::Grid: + return QLayoutSupport::canSimplifyQuickCheck(qobject_cast(layout)); + case LayoutInfo::Form: + return QLayoutSupport::canSimplifyQuickCheck(qobject_cast(layout)); + default: + break; + } + return false; +} + +bool SimplifyLayoutCommand::init(QWidget *layoutBase) +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + m_layoutSimplified = false; + int type; + if (canSimplify(core, layoutBase, &type)) { + m_layoutBase = layoutBase; + m_layoutHelper = LayoutHelper::createLayoutHelper(type); + m_layoutSimplified = m_layoutHelper->canSimplify(core, layoutBase, m_area); + } + return m_layoutSimplified; +} + +void SimplifyLayoutCommand::redo() +{ + const QDesignerFormEditorInterface *core = formWindow()->core(); + if (m_layoutSimplified) { + m_layoutHelper->pushState(core, m_layoutBase); + m_layoutHelper->simplify(core, m_layoutBase, m_area); + } +} +void SimplifyLayoutCommand::undo() +{ + if (m_layoutSimplified) + m_layoutHelper->popState(formWindow()->core(), m_layoutBase); +} + +// ---- ToolBoxCommand ---- +ToolBoxCommand::ToolBoxCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_index(-1) +{ +} + +ToolBoxCommand::~ToolBoxCommand() = default; + +void ToolBoxCommand::init(QToolBox *toolBox) +{ + m_toolBox = toolBox; + m_index = m_toolBox->currentIndex(); + m_widget = m_toolBox->widget(m_index); + m_itemText = m_toolBox->itemText(m_index); + m_itemIcon = m_toolBox->itemIcon(m_index); +} + +void ToolBoxCommand::removePage() +{ + m_toolBox->removeItem(m_index); + + m_widget->hide(); + m_widget->setParent(formWindow()); + formWindow()->clearSelection(); + formWindow()->selectWidget(m_toolBox, true); + +} + +void ToolBoxCommand::addPage() +{ + m_widget->setParent(m_toolBox); + m_toolBox->insertItem(m_index, m_widget, m_itemIcon, m_itemText); + m_toolBox->setCurrentIndex(m_index); + + QDesignerPropertySheetExtension *sheet = qt_extension(formWindow()->core()->extensionManager(), m_toolBox); + if (sheet) { + qdesigner_internal::PropertySheetStringValue itemText(m_itemText); + sheet->setProperty(sheet->indexOf(u"currentItemText"_s), QVariant::fromValue(itemText)); + } + + m_widget->show(); + formWindow()->clearSelection(); + formWindow()->selectWidget(m_toolBox, true); +} + +// ---- MoveToolBoxPageCommand ---- +MoveToolBoxPageCommand::MoveToolBoxPageCommand(QDesignerFormWindowInterface *formWindow) : + ToolBoxCommand(formWindow), + m_newIndex(-1), + m_oldIndex(-1) +{ +} + +MoveToolBoxPageCommand::~MoveToolBoxPageCommand() = default; + +void MoveToolBoxPageCommand::init(QToolBox *toolBox, QWidget *page, int newIndex) +{ + ToolBoxCommand::init(toolBox); + setText(QApplication::translate("Command", "Move Page")); + + m_widget = page; + m_oldIndex = m_toolBox->indexOf(m_widget); + m_itemText = m_toolBox->itemText(m_oldIndex); + m_itemIcon = m_toolBox->itemIcon(m_oldIndex); + m_newIndex = newIndex; +} + +void MoveToolBoxPageCommand::redo() +{ + m_toolBox->removeItem(m_oldIndex); + m_toolBox->insertItem(m_newIndex, m_widget, m_itemIcon, m_itemText); +} + +void MoveToolBoxPageCommand::undo() +{ + m_toolBox->removeItem(m_newIndex); + m_toolBox->insertItem(m_oldIndex, m_widget, m_itemIcon, m_itemText); +} + +// ---- DeleteToolBoxPageCommand ---- +DeleteToolBoxPageCommand::DeleteToolBoxPageCommand(QDesignerFormWindowInterface *formWindow) + : ToolBoxCommand(formWindow) +{ +} + +DeleteToolBoxPageCommand::~DeleteToolBoxPageCommand() = default; + +void DeleteToolBoxPageCommand::init(QToolBox *toolBox) +{ + ToolBoxCommand::init(toolBox); + setText(QApplication::translate("Command", "Delete Page")); +} + +void DeleteToolBoxPageCommand::redo() +{ + removePage(); + cheapUpdate(); +} + +void DeleteToolBoxPageCommand::undo() +{ + addPage(); + cheapUpdate(); +} + +// ---- AddToolBoxPageCommand ---- +AddToolBoxPageCommand::AddToolBoxPageCommand(QDesignerFormWindowInterface *formWindow) + : ToolBoxCommand(formWindow) +{ +} + +AddToolBoxPageCommand::~AddToolBoxPageCommand() = default; + +void AddToolBoxPageCommand::init(QToolBox *toolBox) +{ + init(toolBox, InsertBefore); +} + +void AddToolBoxPageCommand::init(QToolBox *toolBox, InsertionMode mode) +{ + m_toolBox = toolBox; + + m_index = m_toolBox->currentIndex(); + if (mode == InsertAfter) + m_index++; + m_widget = new QDesignerWidget(formWindow(), m_toolBox); + m_itemText = QApplication::translate("Command", "Page"); + m_itemIcon = QIcon(); + m_widget->setObjectName(u"page"_s); + formWindow()->ensureUniqueObjectName(m_widget); + + setText(QApplication::translate("Command", "Insert Page")); + + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->add(m_widget); +} + +void AddToolBoxPageCommand::redo() +{ + addPage(); + cheapUpdate(); +} + +void AddToolBoxPageCommand::undo() +{ + removePage(); + cheapUpdate(); +} + +// ---- TabWidgetCommand ---- +TabWidgetCommand::TabWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_index(-1) +{ +} + +TabWidgetCommand::~TabWidgetCommand() = default; + +void TabWidgetCommand::init(QTabWidget *tabWidget) +{ + m_tabWidget = tabWidget; + m_index = m_tabWidget->currentIndex(); + m_widget = m_tabWidget->widget(m_index); + m_itemText = m_tabWidget->tabText(m_index); + m_itemIcon = m_tabWidget->tabIcon(m_index); +} + +void TabWidgetCommand::removePage() +{ + m_tabWidget->removeTab(m_index); + + m_widget->hide(); + m_widget->setParent(formWindow()); + m_tabWidget->setCurrentIndex(qMin(m_index, m_tabWidget->count())); + + formWindow()->clearSelection(); + formWindow()->selectWidget(m_tabWidget, true); +} + +void TabWidgetCommand::addPage() +{ + m_widget->setParent(nullptr); + m_tabWidget->insertTab(m_index, m_widget, m_itemIcon, m_itemText); + m_widget->show(); + m_tabWidget->setCurrentIndex(m_index); + + QDesignerPropertySheetExtension *sheet = qt_extension(formWindow()->core()->extensionManager(), m_tabWidget); + if (sheet) { + qdesigner_internal::PropertySheetStringValue itemText(m_itemText); + sheet->setProperty(sheet->indexOf(u"currentTabText"_s), + QVariant::fromValue(itemText)); + } + + formWindow()->clearSelection(); + formWindow()->selectWidget(m_tabWidget, true); +} + +// ---- DeleteTabPageCommand ---- +DeleteTabPageCommand::DeleteTabPageCommand(QDesignerFormWindowInterface *formWindow) + : TabWidgetCommand(formWindow) +{ +} + +DeleteTabPageCommand::~DeleteTabPageCommand() = default; + +void DeleteTabPageCommand::init(QTabWidget *tabWidget) +{ + TabWidgetCommand::init(tabWidget); + setText(QApplication::translate("Command", "Delete Page")); +} + +void DeleteTabPageCommand::redo() +{ + removePage(); + cheapUpdate(); +} + +void DeleteTabPageCommand::undo() +{ + addPage(); + cheapUpdate(); +} + +// ---- AddTabPageCommand ---- +AddTabPageCommand::AddTabPageCommand(QDesignerFormWindowInterface *formWindow) + : TabWidgetCommand(formWindow) +{ +} + +AddTabPageCommand::~AddTabPageCommand() = default; + +void AddTabPageCommand::init(QTabWidget *tabWidget) +{ + init(tabWidget, InsertBefore); +} + +void AddTabPageCommand::init(QTabWidget *tabWidget, InsertionMode mode) +{ + m_tabWidget = tabWidget; + + m_index = m_tabWidget->currentIndex(); + if (mode == InsertAfter) + m_index++; + m_widget = new QDesignerWidget(formWindow(), m_tabWidget); + m_itemText = QApplication::translate("Command", "Page"); + m_itemIcon = QIcon(); + m_widget->setObjectName(u"tab"_s); + formWindow()->ensureUniqueObjectName(m_widget); + + setText(QApplication::translate("Command", "Insert Page")); + + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->add(m_widget); +} + +void AddTabPageCommand::redo() +{ + addPage(); + cheapUpdate(); +} + +void AddTabPageCommand::undo() +{ + removePage(); + cheapUpdate(); +} + +// ---- MoveTabPageCommand ---- +MoveTabPageCommand::MoveTabPageCommand(QDesignerFormWindowInterface *formWindow) : + TabWidgetCommand(formWindow), + m_newIndex(-1), + m_oldIndex(-1) +{ +} + +MoveTabPageCommand::~MoveTabPageCommand() = default; + +void MoveTabPageCommand::init(QTabWidget *tabWidget, QWidget *page, + const QIcon &icon, const QString &label, + int index, int newIndex) +{ + TabWidgetCommand::init(tabWidget); + setText(QApplication::translate("Command", "Move Page")); + + m_page = page; + m_newIndex = newIndex; + m_oldIndex = index; + m_label = label; + m_icon = icon; +} + +void MoveTabPageCommand::redo() +{ + m_tabWidget->removeTab(m_oldIndex); + m_tabWidget->insertTab(m_newIndex, m_page, m_icon, m_label); + m_tabWidget->setCurrentIndex(m_newIndex); +} + +void MoveTabPageCommand::undo() +{ + m_tabWidget->removeTab(m_newIndex); + m_tabWidget->insertTab(m_oldIndex, m_page, m_icon, m_label); + m_tabWidget->setCurrentIndex(m_oldIndex); +} + +// ---- StackedWidgetCommand ---- +StackedWidgetCommand::StackedWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_index(-1) +{ +} + +StackedWidgetCommand::~StackedWidgetCommand() = default; + +void StackedWidgetCommand::init(QStackedWidget *stackedWidget) +{ + m_stackedWidget = stackedWidget; + m_index = m_stackedWidget->currentIndex(); + m_widget = m_stackedWidget->widget(m_index); +} + +void StackedWidgetCommand::removePage() +{ + m_stackedWidget->removeWidget(m_stackedWidget->widget(m_index)); + + m_widget->hide(); + m_widget->setParent(formWindow()); + + formWindow()->clearSelection(); + formWindow()->selectWidget(m_stackedWidget, true); +} + +void StackedWidgetCommand::addPage() +{ + m_stackedWidget->insertWidget(m_index, m_widget); + + m_widget->show(); + m_stackedWidget->setCurrentIndex(m_index); + + formWindow()->clearSelection(); + formWindow()->selectWidget(m_stackedWidget, true); +} + +// ---- MoveStackedWidgetCommand ---- +MoveStackedWidgetCommand::MoveStackedWidgetCommand(QDesignerFormWindowInterface *formWindow) : + StackedWidgetCommand(formWindow), + m_newIndex(-1), + m_oldIndex(-1) +{ +} + +MoveStackedWidgetCommand::~MoveStackedWidgetCommand() = default; + +void MoveStackedWidgetCommand::init(QStackedWidget *stackedWidget, QWidget *page, int newIndex) +{ + StackedWidgetCommand::init(stackedWidget); + setText(QApplication::translate("Command", "Move Page")); + + m_widget = page; + m_newIndex = newIndex; + m_oldIndex = m_stackedWidget->indexOf(m_widget); +} + +void MoveStackedWidgetCommand::redo() +{ + m_stackedWidget->removeWidget(m_widget); + m_stackedWidget->insertWidget(m_newIndex, m_widget); +} + +void MoveStackedWidgetCommand::undo() +{ + m_stackedWidget->removeWidget(m_widget); + m_stackedWidget->insertWidget(m_oldIndex, m_widget); +} + +// ---- DeleteStackedWidgetPageCommand ---- +DeleteStackedWidgetPageCommand::DeleteStackedWidgetPageCommand(QDesignerFormWindowInterface *formWindow) + : StackedWidgetCommand(formWindow) +{ +} + +DeleteStackedWidgetPageCommand::~DeleteStackedWidgetPageCommand() = default; + +void DeleteStackedWidgetPageCommand::init(QStackedWidget *stackedWidget) +{ + StackedWidgetCommand::init(stackedWidget); + setText(QApplication::translate("Command", "Delete Page")); +} + +void DeleteStackedWidgetPageCommand::redo() +{ + removePage(); + cheapUpdate(); +} + +void DeleteStackedWidgetPageCommand::undo() +{ + addPage(); + cheapUpdate(); +} + +// ---- AddStackedWidgetPageCommand ---- +AddStackedWidgetPageCommand::AddStackedWidgetPageCommand(QDesignerFormWindowInterface *formWindow) + : StackedWidgetCommand(formWindow) +{ +} + +AddStackedWidgetPageCommand::~AddStackedWidgetPageCommand() = default; + +void AddStackedWidgetPageCommand::init(QStackedWidget *stackedWidget) +{ + init(stackedWidget, InsertBefore); +} + +void AddStackedWidgetPageCommand::init(QStackedWidget *stackedWidget, InsertionMode mode) +{ + m_stackedWidget = stackedWidget; + + m_index = m_stackedWidget->currentIndex(); + if (mode == InsertAfter) + m_index++; + m_widget = new QDesignerWidget(formWindow(), m_stackedWidget); + m_widget->setObjectName(u"page"_s); + formWindow()->ensureUniqueObjectName(m_widget); + + setText(QApplication::translate("Command", "Insert Page")); + + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->add(m_widget); +} + +void AddStackedWidgetPageCommand::redo() +{ + addPage(); + cheapUpdate(); +} + +void AddStackedWidgetPageCommand::undo() +{ + removePage(); + cheapUpdate(); +} + +// ---- TabOrderCommand ---- +TabOrderCommand::TabOrderCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Change Tab order"), formWindow), + m_widgetItem(nullptr) +{ +} + +void TabOrderCommand::init(const QWidgetList &newTabOrder) +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + Q_ASSERT(core); + + m_widgetItem = core->metaDataBase()->item(formWindow()); + Q_ASSERT(m_widgetItem); + m_oldTabOrder = m_widgetItem->tabOrder(); + m_newTabOrder = newTabOrder; +} + +void TabOrderCommand::redo() +{ + m_widgetItem->setTabOrder(m_newTabOrder); +} + +void TabOrderCommand::undo() +{ + m_widgetItem->setTabOrder(m_oldTabOrder); +} + +// ---- CreateMenuBarCommand ---- +CreateMenuBarCommand::CreateMenuBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Create Menu Bar"), formWindow) +{ +} + +void CreateMenuBarCommand::init(QMainWindow *mainWindow) +{ + m_mainWindow = mainWindow; + QDesignerFormEditorInterface *core = formWindow()->core(); + m_menuBar = qobject_cast(core->widgetFactory()->createWidget(u"QMenuBar"_s, m_mainWindow)); + core->widgetFactory()->initialize(m_menuBar); +} + +void CreateMenuBarCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c; + c = qt_extension(core->extensionManager(), m_mainWindow); + c->addWidget(m_menuBar); + + m_menuBar->setObjectName(u"menuBar"_s); + formWindow()->ensureUniqueObjectName(m_menuBar); + core->metaDataBase()->add(m_menuBar); + formWindow()->emitSelectionChanged(); + m_menuBar->setFocus(); +} + +void CreateMenuBarCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c; + c = qt_extension(core->extensionManager(), m_mainWindow); + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == m_menuBar) { + c->remove(i); + break; + } + } + + core->metaDataBase()->remove(m_menuBar); + formWindow()->emitSelectionChanged(); +} + +// ---- DeleteMenuBarCommand ---- +DeleteMenuBarCommand::DeleteMenuBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Delete Menu Bar"), formWindow) +{ +} + +void DeleteMenuBarCommand::init(QMenuBar *menuBar) +{ + m_menuBar = menuBar; + m_mainWindow = qobject_cast(menuBar->parentWidget()); +} + +void DeleteMenuBarCommand::redo() +{ + if (m_mainWindow) { + QDesignerContainerExtension *c; + c = qt_extension(core()->extensionManager(), m_mainWindow); + Q_ASSERT(c != nullptr); + for (int i=0; icount(); ++i) { + if (c->widget(i) == m_menuBar) { + c->remove(i); + break; + } + } + } + + core()->metaDataBase()->remove(m_menuBar); + m_menuBar->hide(); + m_menuBar->setParent(formWindow()); + formWindow()->emitSelectionChanged(); +} + +void DeleteMenuBarCommand::undo() +{ + if (m_mainWindow) { + m_menuBar->setParent(m_mainWindow); + QDesignerContainerExtension *c; + c = qt_extension(core()->extensionManager(), m_mainWindow); + + c->addWidget(m_menuBar); + + core()->metaDataBase()->add(m_menuBar); + m_menuBar->show(); + formWindow()->emitSelectionChanged(); + } +} + +// ---- CreateStatusBarCommand ---- +CreateStatusBarCommand::CreateStatusBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Create Status Bar"), formWindow) +{ +} + +void CreateStatusBarCommand::init(QMainWindow *mainWindow) +{ + m_mainWindow = mainWindow; + QDesignerFormEditorInterface *core = formWindow()->core(); + m_statusBar = qobject_cast(core->widgetFactory()->createWidget(u"QStatusBar"_s, m_mainWindow)); + core->widgetFactory()->initialize(m_statusBar); +} + +void CreateStatusBarCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c; + c = qt_extension(core->extensionManager(), m_mainWindow); + c->addWidget(m_statusBar); + + m_statusBar->setObjectName(u"statusBar"_s); + formWindow()->ensureUniqueObjectName(m_statusBar); + core->metaDataBase()->add(m_statusBar); + formWindow()->emitSelectionChanged(); +} + +void CreateStatusBarCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == m_statusBar) { + c->remove(i); + break; + } + } + + core->metaDataBase()->remove(m_statusBar); + formWindow()->emitSelectionChanged(); +} + +// ---- DeleteStatusBarCommand ---- +DeleteStatusBarCommand::DeleteStatusBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Delete Status Bar"), formWindow) +{ +} + +void DeleteStatusBarCommand::init(QStatusBar *statusBar) +{ + m_statusBar = statusBar; + m_mainWindow = qobject_cast(statusBar->parentWidget()); +} + +void DeleteStatusBarCommand::redo() +{ + if (m_mainWindow) { + QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), m_mainWindow); + Q_ASSERT(c != nullptr); + for (int i=0; icount(); ++i) { + if (c->widget(i) == m_statusBar) { + c->remove(i); + break; + } + } + } + + core()->metaDataBase()->remove(m_statusBar); + m_statusBar->hide(); + m_statusBar->setParent(formWindow()); + formWindow()->emitSelectionChanged(); +} + +void DeleteStatusBarCommand::undo() +{ + if (m_mainWindow) { + m_statusBar->setParent(m_mainWindow); + QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), m_mainWindow); + + c->addWidget(m_statusBar); + + core()->metaDataBase()->add(m_statusBar); + m_statusBar->show(); + formWindow()->emitSelectionChanged(); + } +} + +// ---- AddToolBarCommand ---- +AddToolBarCommand::AddToolBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Add Tool Bar"), formWindow) +{ +} + +void AddToolBarCommand::init(QMainWindow *mainWindow, Qt::ToolBarArea area) +{ + m_mainWindow = mainWindow; + QDesignerWidgetFactoryInterface * wf = formWindow()->core()->widgetFactory(); + // Pass on 0 parent first to avoid reparenting flicker. + m_toolBar = qobject_cast(wf->createWidget(u"QToolBar"_s, nullptr)); + m_toolBar->setProperty("_q_desiredArea", QVariant(area)); + wf->initialize(m_toolBar); + m_toolBar->hide(); +} + +void AddToolBarCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->add(m_toolBar); + + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + c->addWidget(m_toolBar); + + m_toolBar->setObjectName(u"toolBar"_s); + formWindow()->ensureUniqueObjectName(m_toolBar); + setPropertySheetWindowTitle(core, m_toolBar, m_toolBar->objectName()); + formWindow()->emitSelectionChanged(); +} + +void AddToolBarCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->remove(m_toolBar); + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == m_toolBar) { + c->remove(i); + break; + } + } + formWindow()->emitSelectionChanged(); +} + +// ---- DockWidgetCommand:: ---- +DockWidgetCommand::DockWidgetCommand(const QString &description, QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(description, formWindow) +{ +} + +DockWidgetCommand::~DockWidgetCommand() = default; + +void DockWidgetCommand::init(QDockWidget *dockWidget) +{ + m_dockWidget = dockWidget; +} + +// ---- AddDockWidgetCommand ---- +AddDockWidgetCommand::AddDockWidgetCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Add Dock Window"), formWindow) +{ +} + +void AddDockWidgetCommand::init(QMainWindow *mainWindow, QDockWidget *dockWidget) +{ + m_mainWindow = mainWindow; + m_dockWidget = dockWidget; +} + +void AddDockWidgetCommand::init(QMainWindow *mainWindow) +{ + m_mainWindow = mainWindow; + QDesignerFormEditorInterface *core = formWindow()->core(); + m_dockWidget = qobject_cast(core->widgetFactory()->createWidget(u"QDockWidget"_s, m_mainWindow)); +} + +void AddDockWidgetCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + c->addWidget(m_dockWidget); + + m_dockWidget->setObjectName(u"dockWidget"_s); + formWindow()->ensureUniqueObjectName(m_dockWidget); + formWindow()->manageWidget(m_dockWidget); + formWindow()->emitSelectionChanged(); +} + +void AddDockWidgetCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c = qt_extension(core->extensionManager(), m_mainWindow); + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == m_dockWidget) { + c->remove(i); + break; + } + } + + formWindow()->unmanageWidget(m_dockWidget); + formWindow()->emitSelectionChanged(); +} + +// ---- AdjustWidgetSizeCommand ---- +AdjustWidgetSizeCommand::AdjustWidgetSizeCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ +} + +void AdjustWidgetSizeCommand::init(QWidget *widget) +{ + m_widget = widget; + setText(QApplication::translate("Command", "Adjust Size of '%1'").arg(widget->objectName())); +} + +QWidget *AdjustWidgetSizeCommand::widgetForAdjust() const +{ + QDesignerFormWindowInterface *fw = formWindow(); + // Return the outer, embedding widget if it is the main container + if (Utils::isCentralWidget(fw, m_widget)) + return fw->core()->integration()->containerWindow(m_widget); + return m_widget; +} + +void AdjustWidgetSizeCommand::redo() +{ + QWidget *aw = widgetForAdjust(); + m_geometry = aw->geometry(); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + aw->adjustSize(); + const bool isMainContainer = aw != m_widget; + if (!isMainContainer) { + /* When doing adjustsize on a selected non-laid out child that has been enlarged + * and pushed partially over the top/left edge[s], it is possible that it "disappears" + * when shrinking. In that case, move it back so that it remains visible. */ + if (aw->parentWidget()->layout() == nullptr) { + const QRect contentsRect = aw->parentWidget()->contentsRect(); + const QRect newGeometry = aw->geometry(); + QPoint newPos = m_geometry.topLeft(); + if (newGeometry.bottom() <= contentsRect.y()) + newPos.setY(contentsRect.y()); + if (newGeometry.right() <= contentsRect.x()) + newPos.setX(contentsRect.x()); + if (newPos != m_geometry.topLeft()) + aw->move(newPos); + } + } + updatePropertyEditor(); +} + +void AdjustWidgetSizeCommand::undo() +{ + QWidget *aw = widgetForAdjust(); + aw->resize(m_geometry.size()); + if (m_geometry.topLeft() != aw->geometry().topLeft()) + aw->move(m_geometry.topLeft()); + updatePropertyEditor(); +} + +void AdjustWidgetSizeCommand::updatePropertyEditor() const +{ + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == m_widget) + propertyEditor->setPropertyValue(u"geometry"_s, m_widget->geometry(), true); + } +} +// ------------ ChangeFormLayoutItemRoleCommand + +ChangeFormLayoutItemRoleCommand::ChangeFormLayoutItemRoleCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Change Form Layout Item Geometry"), formWindow), + m_operation(SpanningToLabel) +{ +} + +void ChangeFormLayoutItemRoleCommand::init(QWidget *widget, Operation op) +{ + m_widget = widget; + m_operation = op; +} + +void ChangeFormLayoutItemRoleCommand::redo() +{ + doOperation(m_operation); +} + +void ChangeFormLayoutItemRoleCommand::undo() +{ + doOperation(reverseOperation(m_operation)); +} + +ChangeFormLayoutItemRoleCommand::Operation ChangeFormLayoutItemRoleCommand::reverseOperation(Operation op) +{ + switch (op) { + case SpanningToLabel: + return LabelToSpanning; + case SpanningToField: + return FieldToSpanning; + case LabelToSpanning: + return SpanningToLabel; + case FieldToSpanning: + return SpanningToField; + } + return SpanningToField; +} + +void ChangeFormLayoutItemRoleCommand::doOperation(Operation op) +{ + QFormLayout *fl = ChangeFormLayoutItemRoleCommand::managedFormLayoutOf(formWindow()->core(), m_widget); + const int index = fl->indexOf(m_widget); + Q_ASSERT(index != -1); + int row; + QFormLayout::ItemRole role; + fl->getItemPosition (index, &row, &role); + Q_ASSERT(index != -1); + QLayoutItem *item = fl->takeAt(index); + const QRect area = QRect(0, row, 2, 1); + switch (op) { + case SpanningToLabel: + fl->setItem(row, QFormLayout::LabelRole, item); + QLayoutSupport::createEmptyCells(fl); + break; + case SpanningToField: + fl->setItem(row, QFormLayout::FieldRole, item); + QLayoutSupport::createEmptyCells(fl); + break; + case LabelToSpanning: + case FieldToSpanning: + QLayoutSupport::removeEmptyCells(fl, area); + fl->setItem(row, QFormLayout::SpanningRole, item); + break; + } +} + +unsigned ChangeFormLayoutItemRoleCommand::possibleOperations(QDesignerFormEditorInterface *core, QWidget *w) +{ + QFormLayout *fl = managedFormLayoutOf(core, w); + if (!fl) + return 0; + const int index = fl->indexOf(w); + if (index == -1) + return 0; + int row, col, colspan; + getFormLayoutItemPosition(fl, index, &row, &col, nullptr, &colspan); + // Spanning item? + if (colspan > 1) + return SpanningToLabel|SpanningToField; + // Is the neighbouring column free, that is, can the current item be expanded? + const QFormLayout::ItemRole neighbouringRole = col == 0 ? QFormLayout::FieldRole : QFormLayout::LabelRole; + const bool empty = LayoutInfo::isEmptyItem(fl->itemAt(row, neighbouringRole)); + if (empty) + return col == 0 ? LabelToSpanning : FieldToSpanning; + return 0; +} + +QFormLayout *ChangeFormLayoutItemRoleCommand::managedFormLayoutOf(QDesignerFormEditorInterface *core, QWidget *w) +{ + if (QLayout *layout = LayoutInfo::managedLayout(core, w->parentWidget())) + if (QFormLayout *fl = qobject_cast(layout)) + return fl; + return nullptr; +} + +// ---- ChangeLayoutItemGeometry ---- +ChangeLayoutItemGeometry::ChangeLayoutItemGeometry(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Change Layout Item Geometry"), formWindow) +{ +} + +void ChangeLayoutItemGeometry::init(QWidget *widget, int row, int column, int rowspan, int colspan) +{ + m_widget = widget; + Q_ASSERT(m_widget->parentWidget() != nullptr); + + QLayout *layout = LayoutInfo::managedLayout(formWindow()->core(), m_widget->parentWidget()); + Q_ASSERT(layout != nullptr); + + QGridLayout *grid = qobject_cast(layout); + Q_ASSERT(grid != nullptr); + + const int itemIndex = grid->indexOf(m_widget); + Q_ASSERT(itemIndex != -1); + + int current_row, current_column, current_rowspan, current_colspan; + grid->getItemPosition(itemIndex, ¤t_row, ¤t_column, ¤t_rowspan, ¤t_colspan); + + m_oldInfo.setRect(current_column, current_row, current_colspan, current_rowspan); + m_newInfo.setRect(column, row, colspan, rowspan); +} + +void ChangeLayoutItemGeometry::changeItemPosition(const QRect &g) +{ + QLayout *layout = LayoutInfo::managedLayout(formWindow()->core(), m_widget->parentWidget()); + Q_ASSERT(layout != nullptr); + + QGridLayout *grid = qobject_cast(layout); + Q_ASSERT(grid != nullptr); + + const int itemIndex = grid->indexOf(m_widget); + Q_ASSERT(itemIndex != -1); + + QLayoutItem *item = grid->takeAt(itemIndex); + delete item; + + if (!QLayoutSupport::removeEmptyCells(grid, g)) + qWarning() << "ChangeLayoutItemGeometry::changeItemPosition: Nonempty cell at " << g << '.'; + + grid->addWidget(m_widget, g.top(), g.left(), g.height(), g.width()); + + grid->invalidate(); + grid->activate(); + + QLayoutSupport::createEmptyCells(grid); + + formWindow()->clearSelection(false); + formWindow()->selectWidget(m_widget, true); +} + +void ChangeLayoutItemGeometry::redo() +{ + changeItemPosition(m_newInfo); +} + +void ChangeLayoutItemGeometry::undo() +{ + changeItemPosition(m_oldInfo); +} + +// ---- ContainerWidgetCommand ---- +ContainerWidgetCommand::ContainerWidgetCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_index(-1) +{ +} + +ContainerWidgetCommand::~ContainerWidgetCommand() = default; + +QDesignerContainerExtension *ContainerWidgetCommand::containerExtension() const +{ + QExtensionManager *mgr = core()->extensionManager(); + return qt_extension(mgr, m_containerWidget); +} + +void ContainerWidgetCommand::init(QWidget *containerWidget) +{ + m_containerWidget = containerWidget; + + if (QDesignerContainerExtension *c = containerExtension()) { + m_index = c->currentIndex(); + m_widget = c->widget(m_index); + } +} + +void ContainerWidgetCommand::removePage() +{ + if (QDesignerContainerExtension *c = containerExtension()) { + if (const int count = c->count()) { + // Undo add after last? + const int deleteIndex = m_index >= 0 ? m_index : count - 1; + c->remove(deleteIndex); + m_widget->hide(); + m_widget->setParent(formWindow()); + } + } +} + +void ContainerWidgetCommand::addPage() +{ + if (QDesignerContainerExtension *c = containerExtension()) { + int newCurrentIndex; + if (m_index >= 0) { + c->insertWidget(m_index, m_widget); + newCurrentIndex = m_index; + } else { + c->addWidget(m_widget); + newCurrentIndex = c->count() -1 ; + } + m_widget->show(); + c->setCurrentIndex(newCurrentIndex); + } +} + +// ---- DeleteContainerWidgetPageCommand ---- +DeleteContainerWidgetPageCommand::DeleteContainerWidgetPageCommand(QDesignerFormWindowInterface *formWindow) + : ContainerWidgetCommand(formWindow) +{ +} + +DeleteContainerWidgetPageCommand::~DeleteContainerWidgetPageCommand() = default; + +void DeleteContainerWidgetPageCommand::init(QWidget *containerWidget, ContainerType ct) +{ + ContainerWidgetCommand::init(containerWidget); + switch (ct) { + case WizardContainer: + case PageContainer: + setText(QApplication::translate("Command", "Delete Page")); + break; + case MdiContainer: + setText(QApplication::translate("Command", "Delete Subwindow")); + break; + } +} + +void DeleteContainerWidgetPageCommand::redo() +{ + removePage(); + cheapUpdate(); +} + +void DeleteContainerWidgetPageCommand::undo() +{ + addPage(); + cheapUpdate(); +} + +// ---- AddContainerWidgetPageCommand ---- +AddContainerWidgetPageCommand::AddContainerWidgetPageCommand(QDesignerFormWindowInterface *formWindow) + : ContainerWidgetCommand(formWindow) +{ +} + +AddContainerWidgetPageCommand::~AddContainerWidgetPageCommand() = default; + +void AddContainerWidgetPageCommand::init(QWidget *containerWidget, ContainerType ct, InsertionMode mode) +{ + m_containerWidget = containerWidget; + + if (QDesignerContainerExtension *c = containerExtension()) { + m_index = c->currentIndex(); + if (m_index >= 0 && mode == InsertAfter) + m_index++; + m_widget = nullptr; + const QDesignerFormEditorInterface *core = formWindow()->core(); + switch (ct) { + case PageContainer: + setText(QApplication::translate("Command", "Insert Page")); + m_widget = new QDesignerWidget(formWindow(), m_containerWidget); + m_widget->setObjectName(u"page"_s); + break; + case MdiContainer: + setText(QApplication::translate("Command", "Insert Subwindow")); + m_widget = new QDesignerWidget(formWindow(), m_containerWidget); + m_widget->setObjectName(u"subwindow"_s); + setPropertySheetWindowTitle(core, m_widget, QApplication::translate("Command", "Subwindow")); + break; + case WizardContainer: // Apply style, don't manage + m_widget = core->widgetFactory()->createWidget(u"QWizardPage"_s, nullptr); + break; + } + formWindow()->ensureUniqueObjectName(m_widget); + core->metaDataBase()->add(m_widget); + } +} + +void AddContainerWidgetPageCommand::redo() +{ + addPage(); + cheapUpdate(); +} + +void AddContainerWidgetPageCommand::undo() +{ + removePage(); + cheapUpdate(); +} + +ChangeCurrentPageCommand::ChangeCurrentPageCommand(QDesignerFormWindowInterface *formWindow) + : + QDesignerFormWindowCommand(QString(), formWindow), m_oldIndex(0), m_newIndex(0) +{ +} + +ChangeCurrentPageCommand::~ChangeCurrentPageCommand() = default; + +QDesignerContainerExtension *ChangeCurrentPageCommand::containerExtension() const +{ + QExtensionManager *mgr = core()->extensionManager(); + return qt_extension(mgr, m_containerWidget); +} + +void ChangeCurrentPageCommand::init(QWidget *containerWidget, int newIndex) +{ + m_containerWidget = containerWidget; + + if (QDesignerContainerExtension *c = containerExtension()) { + m_newIndex = newIndex; + m_oldIndex = c->currentIndex(); + m_widget = c->widget(m_oldIndex); + } +} + +void ChangeCurrentPageCommand::redo() +{ + containerExtension()->setCurrentIndex(m_newIndex); +} + +void ChangeCurrentPageCommand::undo() +{ + containerExtension()->setCurrentIndex(m_oldIndex); +} + +static const int itemRoles[] = { + Qt::DecorationPropertyRole, + Qt::DisplayPropertyRole, + Qt::ToolTipPropertyRole, + Qt::StatusTipPropertyRole, + Qt::WhatsThisPropertyRole, + Qt::FontRole, + Qt::TextAlignmentRole, + Qt::BackgroundRole, + Qt::ForegroundRole, + Qt::CheckStateRole +}; + +template +static void copyRoleFromItem(ItemData *id, int role, const T *item) +{ + QVariant v = item->data(role); + if (v.isValid()) + id->m_properties.insert(role, v); +} + +template +static void copyRolesFromItem(ItemData *id, const T *item, bool editor) +{ + static const Qt::ItemFlags defaultFlags = T().flags(); + + for (int i : itemRoles) + copyRoleFromItem(id, i, item); + + if (editor) + copyRoleFromItem(id, ItemFlagsShadowRole, item); + else if (item->flags() != defaultFlags) + id->m_properties.insert(ItemFlagsShadowRole, QVariant::fromValue(int(item->flags()))); +} + +template +static void copyRolesToItem(const ItemData *id, T *item, DesignerIconCache *iconCache, bool editor) +{ + for (auto it = id->m_properties.cbegin(), end = id->m_properties.cend(); it != end; ++it) { + if (it.value().isValid()) { + if (!editor && it.key() == ItemFlagsShadowRole) { + item->setFlags((Qt::ItemFlags)it.value().toInt()); + } else { + item->setData(it.key(), it.value()); + switch (it.key()) { + case Qt::DecorationPropertyRole: + if (iconCache) + item->setIcon(iconCache->icon(qvariant_cast(it.value()))); + break; + case Qt::DisplayPropertyRole: + item->setText(qvariant_cast(it.value()).value()); + break; + case Qt::ToolTipPropertyRole: + item->setToolTip(qvariant_cast(it.value()).value()); + break; + case Qt::StatusTipPropertyRole: + item->setStatusTip(qvariant_cast(it.value()).value()); + break; + case Qt::WhatsThisPropertyRole: + item->setWhatsThis(qvariant_cast(it.value()).value()); + break; + } + } + } + } + + if (editor) + item->setFlags(item->flags() | Qt::ItemIsEditable); +} + +ItemData::ItemData(const QListWidgetItem *item, bool editor) +{ + copyRolesFromItem(this, item, editor); +} + +QListWidgetItem *ItemData::createListItem(DesignerIconCache *iconCache, bool editor) const +{ + QListWidgetItem *item = new QListWidgetItem(); + copyRolesToItem(this, item, iconCache, editor); + return item; +} + +ItemData::ItemData(const QTableWidgetItem *item, bool editor) +{ + copyRolesFromItem(this, item, editor); +} + +QTableWidgetItem *ItemData::createTableItem(DesignerIconCache *iconCache, bool editor) const +{ + QTableWidgetItem *item = new QTableWidgetItem; + copyRolesToItem(this, item, iconCache, editor); + return item; +} + +static void copyRoleFromItem(ItemData *id, int role, const QTreeWidgetItem *item, int column) +{ + QVariant v = item->data(column, role); + if (v.isValid()) + id->m_properties.insert(role, v); +} + +ItemData::ItemData(const QTreeWidgetItem *item, int column) +{ + copyRoleFromItem(this, Qt::EditRole, item, column); + PropertySheetStringValue str(item->text(column)); + m_properties.insert(Qt::DisplayPropertyRole, QVariant::fromValue(str)); + + for (int i : itemRoles) + copyRoleFromItem(this, i, item, column); +} + +void ItemData::fillTreeItemColumn(QTreeWidgetItem *item, int column, DesignerIconCache *iconCache) const +{ + for (auto it = m_properties.cbegin(), end = m_properties.cend(); it != end; ++it) { + if (it.value().isValid()) { + item->setData(column, it.key(), it.value()); + switch (it.key()) { + case Qt::DecorationPropertyRole: + if (iconCache) + item->setIcon(column, iconCache->icon(qvariant_cast(it.value()))); + break; + case Qt::DisplayPropertyRole: + item->setText(column, qvariant_cast(it.value()).value()); + break; + case Qt::ToolTipPropertyRole: + item->setToolTip(column, qvariant_cast(it.value()).value()); + break; + case Qt::StatusTipPropertyRole: + item->setStatusTip(column, qvariant_cast(it.value()).value()); + break; + case Qt::WhatsThisPropertyRole: + item->setWhatsThis(column, qvariant_cast(it.value()).value()); + break; + } + } + } +} + +ListContents::ListContents(const QTreeWidgetItem *item) +{ + for (int i = 0; i < item->columnCount(); i++) + m_items.append(ItemData(item, i)); +} + +QTreeWidgetItem *ListContents::createTreeItem(DesignerIconCache *iconCache) const +{ + QTreeWidgetItem *item = new QTreeWidgetItem; + int i = 0; + for (const ItemData &id : m_items) + id.fillTreeItemColumn(item, i++, iconCache); + return item; +} + +void ListContents::createFromListWidget(const QListWidget *listWidget, bool editor) +{ + m_items.clear(); + + for (int i = 0; i < listWidget->count(); i++) + m_items.append(ItemData(listWidget->item(i), editor)); +} + +void ListContents::applyToListWidget(QListWidget *listWidget, DesignerIconCache *iconCache, + bool editor, Qt::Alignment alignmentDefault) const +{ + listWidget->clear(); + + int i = 0; + for (const ItemData &entry : m_items) { + auto *item = entry.isValid() + ? entry.createListItem(iconCache, editor) + : new QListWidgetItem(TableWidgetContents::defaultHeaderText(i)); + if (item->textAlignment() == 0) + item->setTextAlignment(alignmentDefault); + listWidget->addItem(item); + i++; + } +} + +void ListContents::createFromComboBox(const QComboBox *comboBox) +{ + m_items.clear(); + + const int count = comboBox->count(); + for (int i = 0; i < count; i++) { + // We might encounter items added in a custom combo + // constructor. Ignore those. + const QVariant textValue = comboBox->itemData(i, Qt::DisplayPropertyRole); + if (!textValue.isNull()) { + ItemData entry; + entry.m_properties.insert(Qt::DisplayPropertyRole, textValue); + const QVariant iconValue = comboBox->itemData(i, Qt::DecorationPropertyRole); + if (!iconValue.isNull()) + entry.m_properties.insert(Qt::DecorationPropertyRole, iconValue); + m_items.append(entry); + } + } +} + +void ListContents::applyToComboBox(QComboBox *comboBox, DesignerIconCache *iconCache) const +{ + comboBox->clear(); + + for (const ItemData &hash : m_items) { + QIcon icon; + if (iconCache) + icon = iconCache->icon(qvariant_cast( + hash.m_properties[Qt::DecorationPropertyRole])); + QVariant var = hash.m_properties[Qt::DisplayPropertyRole]; + PropertySheetStringValue str = qvariant_cast(var); + comboBox->addItem(icon, str.value()); + comboBox->setItemData(comboBox->count() - 1, + var, + Qt::DisplayPropertyRole); + comboBox->setItemData(comboBox->count() - 1, + hash.m_properties[Qt::DecorationPropertyRole], + Qt::DecorationPropertyRole); + } +} + +// --------- TableWidgetContents + +TableWidgetContents::TableWidgetContents() = default; + +void TableWidgetContents::clear() +{ + m_horizontalHeader.m_items.clear(); + m_verticalHeader.m_items.clear(); + m_items.clear(); + m_columnCount = 0; + m_rowCount = 0; +} + +QString TableWidgetContents::defaultHeaderText(int i) +{ + return QString::number(i + 1); +} + +bool TableWidgetContents::nonEmpty(const QTableWidgetItem *item, int headerColumn) +{ + static const Qt::ItemFlags defaultFlags = QTableWidgetItem().flags(); + + if (item->flags() != defaultFlags) + return true; + + QString text = qvariant_cast(item->data(Qt::DisplayPropertyRole)).value(); + if (!text.isEmpty()) { + if (headerColumn < 0 || text != defaultHeaderText(headerColumn)) + return true; + } else { + // FIXME: This doesn't seem to make sense + return true; + } + + for (int i : itemRoles) { + if (i != Qt::DisplayPropertyRole && item->data(i).isValid()) + return true; + } + + return false; +} + +void TableWidgetContents::insertHeaderItem(const QTableWidgetItem *item, int i, ListContents *header, bool editor) +{ + if (nonEmpty(item, i)) + header->m_items.append(ItemData(item, editor)); + else + header->m_items.append(ItemData()); +} + +void TableWidgetContents::fromTableWidget(const QTableWidget *tableWidget, bool editor) +{ + clear(); + m_columnCount = tableWidget->columnCount(); + m_rowCount = tableWidget->rowCount(); + // horiz header: Legacy behaviour: auto-generate number for empty items + for (int col = 0; col < m_columnCount; col++) + if (const QTableWidgetItem *item = tableWidget->horizontalHeaderItem(col)) + insertHeaderItem(item, col, &m_horizontalHeader, editor); + // vertical header: Legacy behaviour: auto-generate number for empty items + for (int row = 0; row < m_rowCount; row++) + if (const QTableWidgetItem *item = tableWidget->verticalHeaderItem(row)) + insertHeaderItem(item, row, &m_verticalHeader, editor); + // cell data + for (int col = 0; col < m_columnCount; col++) + for (int row = 0; row < m_rowCount; row++) + if (const QTableWidgetItem *item = tableWidget->item(row, col)) + if (nonEmpty(item, -1)) + m_items.insert(CellRowColumnAddress(row, col), ItemData(item, editor)); +} + +void TableWidgetContents::applyToTableWidget(QTableWidget *tableWidget, DesignerIconCache *iconCache, bool editor) const +{ + tableWidget->clear(); + + tableWidget->setColumnCount(m_columnCount); + tableWidget->setRowCount(m_rowCount); + + // horiz header + int col = 0; + for (const ItemData &id : m_horizontalHeader.m_items) { + if (id.isValid()) + tableWidget->setHorizontalHeaderItem(col, id.createTableItem(iconCache, editor)); + col++; + } + // vertical header + int row = 0; + for (const ItemData &id : m_verticalHeader.m_items) { + if (id.isValid()) + tableWidget->setVerticalHeaderItem(row, id.createTableItem(iconCache, editor)); + row++; + } + // items + for (auto it = m_items.cbegin(), icend = m_items.cend(); it != icend; ++ it) { + tableWidget->setItem(it.key().first, it.key().second, + it.value().createTableItem(iconCache, editor)); + } +} + +bool comparesEqual(const TableWidgetContents &lhs, + const TableWidgetContents &rhs) noexcept +{ + return lhs.m_columnCount == rhs.m_columnCount && lhs.m_rowCount == rhs.m_rowCount && + lhs.m_horizontalHeader.m_items == rhs.m_horizontalHeader.m_items && + lhs.m_verticalHeader.m_items == rhs.m_verticalHeader.m_items && + lhs.m_items == rhs.m_items; +} + +// ---- ChangeTableContentsCommand ---- +ChangeTableContentsCommand::ChangeTableContentsCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Change Table Contents"), + formWindow), m_iconCache(nullptr) +{ + FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + m_iconCache = fwb->iconCache(); +} + +void ChangeTableContentsCommand::init(QTableWidget *tableWidget, + const TableWidgetContents &oldCont, const TableWidgetContents &newCont) +{ + m_tableWidget = tableWidget; + m_oldContents = oldCont; + m_newContents = newCont; +} + +void ChangeTableContentsCommand::redo() +{ + m_newContents.applyToTableWidget(m_tableWidget, m_iconCache, false); + QMetaObject::invokeMethod(m_tableWidget, "updateGeometries"); +} + +void ChangeTableContentsCommand::undo() +{ + m_oldContents.applyToTableWidget(m_tableWidget, m_iconCache, false); + QMetaObject::invokeMethod(m_tableWidget, "updateGeometries"); +} + +// --------- TreeWidgetContents +TreeWidgetContents::ItemContents::ItemContents(const QTreeWidgetItem *item, bool editor) : + ListContents(item) +{ + static const Qt::ItemFlags defaultFlags = QTreeWidgetItem().flags(); + + if (editor) { + QVariant v = item->data(0, ItemFlagsShadowRole); + m_itemFlags = v.isValid() ? v.toInt() : -1; + } else { + m_itemFlags = (item->flags() != defaultFlags) ? int(item->flags()) : -1; + } + + for (int i = 0; i < item->childCount(); i++) + m_children.append(ItemContents(item->child(i), editor)); +} + +QTreeWidgetItem *TreeWidgetContents::ItemContents::createTreeItem(DesignerIconCache *iconCache, bool editor) const +{ + QTreeWidgetItem *item = ListContents::createTreeItem(iconCache); + + if (editor) + item->setFlags(item->flags() | Qt::ItemIsEditable); + + if (m_itemFlags != -1) { + if (editor) + item->setData(0, ItemFlagsShadowRole, QVariant::fromValue(m_itemFlags)); + else + item->setFlags((Qt::ItemFlags)m_itemFlags); + } + + for (const ItemContents &ic : m_children) + item->addChild(ic.createTreeItem(iconCache, editor)); + + return item; +} + +bool comparesEqual(const TreeWidgetContents::ItemContents &lhs, + const TreeWidgetContents::ItemContents &rhs) noexcept +{ + return lhs.m_itemFlags == rhs.m_itemFlags && lhs.m_items == rhs.m_items + && lhs.m_children == rhs.m_children; +} + +void TreeWidgetContents::clear() +{ + m_headerItem.m_items.clear(); + m_rootItems.clear(); +} + +void TreeWidgetContents::fromTreeWidget(const QTreeWidget *treeWidget, bool editor) +{ + clear(); + m_headerItem = ListContents(treeWidget->headerItem()); + for (int col = 0; col < treeWidget->topLevelItemCount(); col++) + m_rootItems.append(ItemContents(treeWidget->topLevelItem(col), editor)); +} + +void TreeWidgetContents::applyToTreeWidget(QTreeWidget *treeWidget, DesignerIconCache *iconCache, bool editor) const +{ + treeWidget->clear(); + + treeWidget->setColumnCount(m_headerItem.m_items.size()); + treeWidget->setHeaderItem(m_headerItem.createTreeItem(iconCache)); + for (const ItemContents &ic : m_rootItems) + treeWidget->addTopLevelItem(ic.createTreeItem(iconCache, editor)); + treeWidget->expandAll(); +} + +// ---- ChangeTreeContentsCommand ---- +ChangeTreeContentsCommand::ChangeTreeContentsCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Change Tree Contents"), formWindow), + m_iconCache(nullptr) +{ + FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + m_iconCache = fwb->iconCache(); +} + +void ChangeTreeContentsCommand::init(QTreeWidget *treeWidget, + const TreeWidgetContents &oldState, const TreeWidgetContents &newState) +{ + m_treeWidget = treeWidget; + m_oldState = oldState; + m_newState = newState; +} + +void ChangeTreeContentsCommand::redo() +{ + m_newState.applyToTreeWidget(m_treeWidget, m_iconCache, false); +} + +void ChangeTreeContentsCommand::undo() +{ + m_oldState.applyToTreeWidget(m_treeWidget, m_iconCache, false); +} + +// ---- ChangeListContentsCommand ---- +ChangeListContentsCommand::ChangeListContentsCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow), m_iconCache(nullptr) +{ + FormWindowBase *fwb = qobject_cast(formWindow); + if (fwb) + m_iconCache = fwb->iconCache(); +} + +void ChangeListContentsCommand::init(QListWidget *listWidget, + const ListContents &oldItems, const ListContents &items) +{ + m_listWidget = listWidget; + m_comboBox = nullptr; + + m_newItemsState = items; + m_oldItemsState = oldItems; +} + +void ChangeListContentsCommand::init(QComboBox *comboBox, + const ListContents &oldItems, const ListContents &items) +{ + m_listWidget = nullptr; + m_comboBox = comboBox; + + m_newItemsState = items; + m_oldItemsState = oldItems; +} + +void ChangeListContentsCommand::redo() +{ + if (m_listWidget) + m_newItemsState.applyToListWidget(m_listWidget, m_iconCache, false); + else if (m_comboBox) + m_newItemsState.applyToComboBox(m_comboBox, m_iconCache); +} + +void ChangeListContentsCommand::undo() +{ + if (m_listWidget) + m_oldItemsState.applyToListWidget(m_listWidget, m_iconCache, false); + else if (m_comboBox) + m_oldItemsState.applyToComboBox(m_comboBox, m_iconCache); +} + +// ---- AddActionCommand ---- + +AddActionCommand::AddActionCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Add action"), formWindow) +{ + m_action = nullptr; +} + +void AddActionCommand::init(QAction *action) +{ + Q_ASSERT(m_action == nullptr); + m_action = action; +} + +void AddActionCommand::redo() +{ + core()->actionEditor()->setFormWindow(formWindow()); + core()->actionEditor()->manageAction(m_action); +} + +void AddActionCommand::undo() +{ + core()->actionEditor()->setFormWindow(formWindow()); + core()->actionEditor()->unmanageAction(m_action); +} + +// ---- RemoveActionCommand ---- + +RemoveActionCommand::RemoveActionCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Remove action"), formWindow), + m_action(0) +{ +} + +static RemoveActionCommand::ActionData findActionIn(QAction *action) +{ + RemoveActionCommand::ActionData result; + // We only want menus and toolbars, no toolbuttons. + const QObjectList associatedObjects = action->associatedObjects(); + for (QObject *obj : associatedObjects) { + if (!qobject_cast(obj) && !qobject_cast(obj)) + continue; + QWidget *widget = static_cast(obj); + const auto actionList = widget->actions(); + for (qsizetype i = 0, size = actionList.size(); i < size; ++i) { + if (actionList.at(i) == action) { + QAction *before = nullptr; + if (i + 1 < size) + before = actionList.at(i + 1); + result.append(RemoveActionCommand::ActionDataItem(before, widget)); + break; + } + } + } + return result; +} + +void RemoveActionCommand::init(QAction *action) +{ + Q_ASSERT(m_action == nullptr); + m_action = action; + + m_actionData = findActionIn(action); +} + +void RemoveActionCommand::redo() +{ + QDesignerFormWindowInterface *fw = formWindow(); + for (const ActionDataItem &item : std::as_const(m_actionData)) { + item.widget->removeAction(m_action); + } + // Notify components (for example, signal slot editor) + if (qdesigner_internal::FormWindowBase *fwb = qobject_cast(fw)) + fwb->emitObjectRemoved(m_action); + + core()->actionEditor()->setFormWindow(fw); + core()->actionEditor()->unmanageAction(m_action); + if (!m_actionData.isEmpty()) + core()->objectInspector()->setFormWindow(fw); +} + +void RemoveActionCommand::undo() +{ + core()->actionEditor()->setFormWindow(formWindow()); + core()->actionEditor()->manageAction(m_action); + for (const ActionDataItem &item : std::as_const(m_actionData)) + item.widget->insertAction(item.before, m_action); + if (!m_actionData.isEmpty()) + core()->objectInspector()->setFormWindow(formWindow()); +} + +// ---- ActionInsertionCommand ---- + +ActionInsertionCommand::ActionInsertionCommand(const QString &text, QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(text, formWindow), + m_parentWidget(nullptr), + m_action(nullptr), + m_beforeAction(nullptr), + m_update(false) +{ +} + +void ActionInsertionCommand::init(QWidget *parentWidget, QAction *action, QAction *beforeAction, bool update) +{ + Q_ASSERT(m_parentWidget == nullptr); + Q_ASSERT(m_action == nullptr); + + m_parentWidget = parentWidget; + m_action = action; + m_beforeAction = beforeAction; + m_update = update; +} + +void ActionInsertionCommand::insertAction() +{ + Q_ASSERT(m_action != nullptr); + Q_ASSERT(m_parentWidget != nullptr); + + if (m_beforeAction) + m_parentWidget->insertAction(m_beforeAction, m_action); + else + m_parentWidget->addAction(m_action); + + if (m_update) { + cheapUpdate(); + if (QMenu *menu = m_action->menu()) + selectUnmanagedObject(menu); + else + selectUnmanagedObject(m_action); + PropertyHelper::triggerActionChanged(m_action); // Update Used column in action editor. + } +} +void ActionInsertionCommand::removeAction() +{ + Q_ASSERT(m_action != nullptr); + Q_ASSERT(m_parentWidget != nullptr); + + if (QDesignerMenu *menu = qobject_cast(m_parentWidget)) + menu->hideSubMenu(); + + m_parentWidget->removeAction(m_action); + + if (m_update) { + cheapUpdate(); + selectUnmanagedObject(m_parentWidget); + PropertyHelper::triggerActionChanged(m_action); // Update Used column in action editor. + } +} + +InsertActionIntoCommand::InsertActionIntoCommand(QDesignerFormWindowInterface *formWindow) : + ActionInsertionCommand(QApplication::translate("Command", "Add action"), formWindow) +{ +} +// ---- RemoveActionFromCommand ---- + +RemoveActionFromCommand::RemoveActionFromCommand(QDesignerFormWindowInterface *formWindow) : + ActionInsertionCommand(QApplication::translate("Command", "Remove action"), formWindow) +{ +} + +// ---- AddMenuActionCommand ---- + +MenuActionCommand::MenuActionCommand(const QString &text, QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(text, formWindow), + m_action(nullptr), + m_actionBefore(nullptr), + m_menuParent(nullptr), + m_associatedWidget(nullptr), + m_objectToSelect(nullptr) +{ +} + +void MenuActionCommand::init(QAction *action, QAction *actionBefore, + QWidget *associatedWidget, QWidget *objectToSelect) +{ + QMenu *menu = action->menu(); + Q_ASSERT(menu); + m_menuParent = menu->parentWidget(); + m_action = action; + m_actionBefore = actionBefore; + m_associatedWidget = associatedWidget; + m_objectToSelect = objectToSelect; +} + +void MenuActionCommand::insertMenu() +{ + core()->metaDataBase()->add(m_action); + QMenu *menu = m_action->menu(); + if (m_menuParent && menu->parentWidget() != m_menuParent) + menu->setParent(m_menuParent); + core()->metaDataBase()->add(menu); + m_associatedWidget->insertAction(m_actionBefore, m_action); + cheapUpdate(); + selectUnmanagedObject(menu); +} + +void MenuActionCommand::removeMenu() +{ + m_action->menu()->setParent(nullptr); + QMenu *menu = m_action->menu(); + core()->metaDataBase()->remove(menu); + menu->setParent(nullptr); + core()->metaDataBase()->remove(m_action); + m_associatedWidget->removeAction(m_action); + cheapUpdate(); + selectUnmanagedObject(m_objectToSelect); +} + +AddMenuActionCommand::AddMenuActionCommand(QDesignerFormWindowInterface *formWindow) : + MenuActionCommand(QApplication::translate("Command", "Add menu"), formWindow) +{ +} + +// ---- RemoveMenuActionCommand ---- +RemoveMenuActionCommand::RemoveMenuActionCommand(QDesignerFormWindowInterface *formWindow) : + MenuActionCommand(QApplication::translate("Command", "Remove menu"), formWindow) +{ +} + +// ---- CreateSubmenuCommand ---- +CreateSubmenuCommand::CreateSubmenuCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Create submenu"), formWindow), + m_action(nullptr), + m_menu(nullptr), + m_objectToSelect(nullptr) +{ +} + +void CreateSubmenuCommand::init(QDesignerMenu *menu, QAction *action, QObject *objectToSelect) +{ + m_menu = menu; + m_action = action; + m_objectToSelect = objectToSelect; +} + +void CreateSubmenuCommand::redo() +{ + m_menu->createRealMenuAction(m_action); + cheapUpdate(); + if (m_objectToSelect) + selectUnmanagedObject(m_objectToSelect); +} + +void CreateSubmenuCommand::undo() +{ + m_menu->removeRealMenu(m_action); + cheapUpdate(); + selectUnmanagedObject(m_menu); +} + +// ---- DeleteToolBarCommand ---- +DeleteToolBarCommand::DeleteToolBarCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QApplication::translate("Command", "Delete Tool Bar"), formWindow) +{ +} + +void DeleteToolBarCommand::init(QToolBar *toolBar) +{ + m_toolBar = toolBar; + m_mainWindow = qobject_cast(toolBar->parentWidget()); +} + +void DeleteToolBarCommand::redo() +{ + if (m_mainWindow) { + QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), m_mainWindow); + Q_ASSERT(c != nullptr); + for (int i=0; icount(); ++i) { + if (c->widget(i) == m_toolBar) { + c->remove(i); + break; + } + } + } + + core()->metaDataBase()->remove(m_toolBar); + m_toolBar->hide(); + m_toolBar->setParent(formWindow()); + formWindow()->emitSelectionChanged(); +} + +void DeleteToolBarCommand::undo() +{ + if (m_mainWindow) { + m_toolBar->setParent(m_mainWindow); + QDesignerContainerExtension *c = qt_extension(core()->extensionManager(), m_mainWindow); + + c->addWidget(m_toolBar); + + core()->metaDataBase()->add(m_toolBar); + m_toolBar->show(); + formWindow()->emitSelectionChanged(); + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_command2.cpp b/src/tools/designer/src/lib/shared/qdesigner_command2.cpp new file mode 100644 index 00000000000..cc8cff4f652 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_command2.cpp @@ -0,0 +1,183 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_command2_p.h" +#include "formwindowbase_p.h" +#include "layoutinfo_p.h" +#include "qdesigner_command_p.h" +#include "widgetfactory_p.h" +#include "qlayout_widget_p.h" + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +MorphLayoutCommand::MorphLayoutCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QString(), formWindow), + m_breakLayoutCommand(new BreakLayoutCommand(formWindow)), + m_layoutCommand(new LayoutCommand(formWindow)), + m_newType(LayoutInfo::VBox), + m_layoutBase(nullptr) +{ +} + +MorphLayoutCommand::~MorphLayoutCommand() +{ + delete m_layoutCommand; + delete m_breakLayoutCommand; +} + +bool MorphLayoutCommand::init(QWidget *w, int newType) +{ + int oldType; + QDesignerFormWindowInterface *fw = formWindow(); + if (!canMorph(fw, w, &oldType) || oldType == newType) + return false; + m_layoutBase = w; + m_newType = newType; + // Find all managed widgets + m_widgets.clear(); + const QLayout *layout = LayoutInfo::managedLayout(fw->core(), w); + const int count = layout->count(); + for (int i = 0; i < count ; i++) { + if (QWidget *w = layout->itemAt(i)->widget()) + if (fw->isManaged(w)) + m_widgets.push_back(w); + } + const bool reparentLayoutWidget = false; // leave QLayoutWidget intact + m_breakLayoutCommand->init(m_widgets, m_layoutBase, reparentLayoutWidget); + m_layoutCommand->init(m_layoutBase, m_widgets, static_cast(m_newType), m_layoutBase, reparentLayoutWidget); + setText(formatDescription(core(), m_layoutBase, oldType, newType)); + return true; +} + +bool MorphLayoutCommand::canMorph(const QDesignerFormWindowInterface *formWindow, QWidget *w, int *ptrToCurrentType) +{ + if (ptrToCurrentType) + *ptrToCurrentType = LayoutInfo::NoLayout; + // We want a managed widget or a container page + // with a level-0 managed layout + QDesignerFormEditorInterface *core = formWindow->core(); + QLayout *layout = LayoutInfo::managedLayout(core, w); + if (!layout) + return false; + const LayoutInfo::Type type = LayoutInfo::layoutType(core, layout); + if (ptrToCurrentType) + *ptrToCurrentType = type; + switch (type) { + case LayoutInfo::HBox: + case LayoutInfo::VBox: + case LayoutInfo::Grid: + case LayoutInfo::Form: + return true; + break; + case LayoutInfo::NoLayout: + case LayoutInfo::HSplitter: // Nothing doing + case LayoutInfo::VSplitter: + case LayoutInfo::UnknownLayout: + break; + } + return false; +} + +void MorphLayoutCommand::redo() +{ + m_breakLayoutCommand->redo(); + m_layoutCommand->redo(); + /* Transfer applicable properties which is a cross-section of the modified + * properties except object name. */ + if (const LayoutProperties *properties = m_breakLayoutCommand->layoutProperties()) { + const int oldMask = m_breakLayoutCommand->propertyMask(); + QLayout *newLayout = LayoutInfo::managedLayout(core(), m_layoutBase); + const int newMask = LayoutProperties::visibleProperties(newLayout); + const int applicableMask = (oldMask & newMask) & ~LayoutProperties::ObjectNameProperty; + if (applicableMask) + properties->toPropertySheet(core(), newLayout, applicableMask); + } +} + +void MorphLayoutCommand::undo() +{ + m_layoutCommand->undo(); + m_breakLayoutCommand->undo(); +} + +QString MorphLayoutCommand::formatDescription(QDesignerFormEditorInterface * /* core*/, const QWidget *w, int oldType, int newType) +{ + const QString oldName = LayoutInfo::layoutName(static_cast(oldType)); + const QString newName = LayoutInfo::layoutName(static_cast(newType)); + const QString widgetName = qobject_cast(w) ? w->layout()->objectName() : w->objectName(); + return QApplication::translate("Command", "Change layout of '%1' from %2 to %3").arg(widgetName, oldName, newName); +} + +LayoutAlignmentCommand::LayoutAlignmentCommand(QDesignerFormWindowInterface *formWindow) : + QDesignerFormWindowCommand(QApplication::translate("Command", "Change layout alignment"), formWindow), + m_widget(nullptr) +{ +} + +bool LayoutAlignmentCommand::init(QWidget *w, Qt::Alignment alignment) +{ + bool enabled; + m_newAlignment = alignment; + m_oldAlignment = LayoutAlignmentCommand::alignmentOf(core(), w, &enabled); + m_widget = w; + return enabled; +} + +void LayoutAlignmentCommand::redo() +{ + LayoutAlignmentCommand::applyAlignment(core(), m_widget, m_newAlignment); +} + +void LayoutAlignmentCommand::undo() +{ + LayoutAlignmentCommand::applyAlignment(core(), m_widget, m_oldAlignment); +} + +// Find out alignment and return whether command is enabled. +Qt::Alignment LayoutAlignmentCommand::alignmentOf(const QDesignerFormEditorInterface *core, QWidget *w, bool *enabledIn) +{ + bool managed; + QLayout *layout; + + if (enabledIn) + *enabledIn = false; + // Can only work on a managed layout + const LayoutInfo::Type type = LayoutInfo::laidoutWidgetType(core, w, &managed, &layout); + const bool enabled = layout && managed && + (type == LayoutInfo::HBox || type == LayoutInfo::VBox + || type == LayoutInfo::Grid); + if (!enabled) + return {}; + // Get alignment + const int index = layout->indexOf(w); + Q_ASSERT(index >= 0); + if (enabledIn) + *enabledIn = true; + return layout->itemAt(index)->alignment(); +} + +void LayoutAlignmentCommand::applyAlignment(const QDesignerFormEditorInterface *core, QWidget *w, Qt::Alignment a) +{ + // Find layout and apply to item + QLayout *layout; + LayoutInfo::laidoutWidgetType(core, w, nullptr, &layout); + if (layout) { + const int index = layout->indexOf(w); + if (index >= 0) { + layout->itemAt(index)->setAlignment(a); + layout->update(); + } + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_command2_p.h b/src/tools/designer/src/lib/shared/qdesigner_command2_p.h new file mode 100644 index 00000000000..bfe32cdd4b9 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_command2_p.h @@ -0,0 +1,85 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_COMMAND2_H +#define QDESIGNER_COMMAND2_H + +#include "shared_global_p.h" +#include "qdesigner_formwindowcommand_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class LayoutCommand; +class BreakLayoutCommand; + +/* This command changes the type of a managed layout on a widget (including + * red layouts of type 'QLayoutWidget') into another type, maintaining the + * applicable properties. It does this by chaining BreakLayoutCommand and + * LayoutCommand, parametrizing them not to actually delete/reparent + * QLayoutWidget's. */ + +class QDESIGNER_SHARED_EXPORT MorphLayoutCommand : public QDesignerFormWindowCommand { + Q_DISABLE_COPY_MOVE(MorphLayoutCommand) +public: + explicit MorphLayoutCommand(QDesignerFormWindowInterface *formWindow); + ~MorphLayoutCommand() override; + + bool init(QWidget *w, int newType); + + static bool canMorph(const QDesignerFormWindowInterface *formWindow, QWidget *w, int *ptrToCurrentType = nullptr); + + void redo() override; + void undo() override; + +private: + static QString formatDescription(QDesignerFormEditorInterface *core, const QWidget *w, int oldType, int newType); + + BreakLayoutCommand *m_breakLayoutCommand; + LayoutCommand *m_layoutCommand; + int m_newType; + QWidgetList m_widgets; + QWidget *m_layoutBase; +}; + +// Change the alignment of a widget in a managed grid/box layout cell. +class LayoutAlignmentCommand : public QDesignerFormWindowCommand { + Q_DISABLE_COPY_MOVE(LayoutAlignmentCommand) +public: + explicit LayoutAlignmentCommand(QDesignerFormWindowInterface *formWindow); + + bool init(QWidget *w, Qt::Alignment alignment); + + void redo() override; + void undo() override; + + // Find out alignment and return whether command is enabled. + static Qt::Alignment alignmentOf(const QDesignerFormEditorInterface *core, QWidget *w, bool *enabled = nullptr); + +private: + static void applyAlignment(const QDesignerFormEditorInterface *core, QWidget *w, Qt::Alignment a); + + Qt::Alignment m_newAlignment; + Qt::Alignment m_oldAlignment; + QWidget *m_widget; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_COMMAND2_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_command_p.h b/src/tools/designer/src/lib/shared/qdesigner_command_p.h new file mode 100644 index 00000000000..e3239ce5cd6 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_command_p.h @@ -0,0 +1,1116 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_COMMAND_H +#define QDESIGNER_COMMAND_H + +#include "layoutinfo_p.h" +#include "qdesigner_formeditorcommand_p.h" +#include "qdesigner_formwindowcommand_p.h" +#include "qdesigner_utils_p.h" +#include "shared_enums_p.h" +#include "shared_global_p.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerContainerExtension; +class QDesignerMetaDataBaseItemInterface; +class QDesignerMenu; + +class QMenuBar; +class QStatusBar; +class QToolBar; +class QToolBox; +class QTabWidget; +class QTableWidget; +class QTableWidgetItem; +class QTreeWidget; +class QTreeWidgetItem; +class QListWidget; +class QListWidgetItem; +class QComboBox; +class QStackedWidget; +class QDockWidget; +class QMainWindow; +class QFormLayout; + +namespace qdesigner_internal { + +class Layout; +class LayoutHelper; +class PropertySheetIconValue; +class DesignerIconCache; +struct LayoutProperties; + +class QDESIGNER_SHARED_EXPORT InsertWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit InsertWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~InsertWidgetCommand() override; + + void init(QWidget *widget, bool already_in_form = false, int layoutRow = -1, int layoutColumn = -1); + + void redo() override; + void undo() override; + +private: + void refreshBuddyLabels(); + + QPointer m_widget; + QDesignerLayoutDecorationExtension::InsertMode m_insertMode; + std::pair m_cell; + LayoutHelper* m_layoutHelper; + bool m_widgetWasManaged; +}; + +class QDESIGNER_SHARED_EXPORT ChangeZOrderCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeZOrderCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget); + + void redo() override; + void undo() override; +protected: + virtual QWidgetList reorderWidget(const QWidgetList &list, QWidget *widget) const = 0; + virtual void reorder(QWidget *widget) const = 0; + +private: + QPointer m_widget; + QPointer m_oldPreceding; + QWidgetList m_oldParentZOrder; +}; + +class QDESIGNER_SHARED_EXPORT RaiseWidgetCommand: public ChangeZOrderCommand +{ + +public: + explicit RaiseWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget); + +protected: + QWidgetList reorderWidget(const QWidgetList &list, QWidget *widget) const override; + void reorder(QWidget *widget) const override; +}; + +class QDESIGNER_SHARED_EXPORT LowerWidgetCommand: public ChangeZOrderCommand +{ + +public: + explicit LowerWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget); + +protected: + QWidgetList reorderWidget(const QWidgetList &list, QWidget *widget) const override; + void reorder(QWidget *widget) const override; +}; + +class QDESIGNER_SHARED_EXPORT AdjustWidgetSizeCommand: public QDesignerFormWindowCommand +{ + +public: + explicit AdjustWidgetSizeCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget); + + void redo() override; + void undo() override; + +private: + QWidget *widgetForAdjust() const; + bool adjustNonLaidOutMainContainer(QWidget *integrationContainer); + void updatePropertyEditor() const; + + QPointer m_widget; + QRect m_geometry; +}; + +// Helper to correctly unmanage a widget and its children for delete operations +class QDESIGNER_SHARED_EXPORT ManageWidgetCommandHelper { +public: + ManageWidgetCommandHelper(); + void init(const QDesignerFormWindowInterface *fw, QWidget *widget); + void init(QWidget *widget, const QWidgetList &managedChildren); + + void manage(QDesignerFormWindowInterface *fw); + void unmanage(QDesignerFormWindowInterface *fw); + + const QWidgetList &managedChildren() const { return m_managedChildren; } +private: + QWidget *m_widget = nullptr; + QWidgetList m_managedChildren; +}; + +class QDESIGNER_SHARED_EXPORT DeleteWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DeleteWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteWidgetCommand() override; + + enum DeleteFlags { DoNotUnmanage = 0x1, DoNotSimplifyLayout = 0x2 }; + + void init(QWidget *widget, unsigned flags = 0); + + void redo() override; + void undo() override; + +private: + QPointer m_widget; + QPointer m_parentWidget; + QRect m_geometry; + LayoutInfo::Type m_layoutType; + LayoutHelper* m_layoutHelper; + unsigned m_flags; + QRect m_layoutPosition; + int m_splitterIndex; + bool m_layoutSimplified; + QDesignerMetaDataBaseItemInterface *m_formItem; + int m_tabOrderIndex; + int m_widgetOrderIndex; + int m_zOrderIndex; + ManageWidgetCommandHelper m_manageHelper; +}; + +class QDESIGNER_SHARED_EXPORT ReparentWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ReparentWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget, QWidget *parentWidget); + + void redo() override; + void undo() override; + +private: + QPointer m_widget; + QPoint m_oldPos; + QPoint m_newPos; + QPointer m_oldParentWidget; + QPointer m_newParentWidget; + QWidgetList m_oldParentList; + QWidgetList m_oldParentZOrder; +}; + +class QDESIGNER_SHARED_EXPORT ChangeFormLayoutItemRoleCommand : public QDesignerFormWindowCommand +{ +public: + enum Operation { SpanningToLabel = 0x1, SpanningToField = 0x2, LabelToSpanning = 0x4, FieldToSpanning =0x8 }; + + explicit ChangeFormLayoutItemRoleCommand(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget, Operation op); + + void redo() override; + void undo() override; + + // Return a mask of possible operations of that item + static unsigned possibleOperations(QDesignerFormEditorInterface *core, QWidget *w); + +private: + static QFormLayout *managedFormLayoutOf(QDesignerFormEditorInterface *core, QWidget *w); + static Operation reverseOperation(Operation op); + void doOperation(Operation op); + + QPointer m_widget; + Operation m_operation; +}; + +class QDESIGNER_SHARED_EXPORT ChangeLayoutItemGeometry: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeLayoutItemGeometry(QDesignerFormWindowInterface *formWindow); + + void init(QWidget *widget, int row, int column, int rowspan, int colspan); + + void redo() override; + void undo() override; + +protected: + void changeItemPosition(const QRect &g); + +private: + QPointer m_widget; + QRect m_oldInfo; + QRect m_newInfo; +}; + +class QDESIGNER_SHARED_EXPORT TabOrderCommand: public QDesignerFormWindowCommand +{ + +public: + explicit TabOrderCommand(QDesignerFormWindowInterface *formWindow); + + void init(const QWidgetList &newTabOrder); + + inline QWidgetList oldTabOrder() const + { return m_oldTabOrder; } + + inline QWidgetList newTabOrder() const + { return m_newTabOrder; } + + void redo() override; + void undo() override; + +private: + QDesignerMetaDataBaseItemInterface *m_widgetItem; + QWidgetList m_oldTabOrder; + QWidgetList m_newTabOrder; +}; + +class QDESIGNER_SHARED_EXPORT PromoteToCustomWidgetCommand : public QDesignerFormWindowCommand +{ +public: + using WidgetPointerList = QList >; + + explicit PromoteToCustomWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(const WidgetPointerList &widgets, const QString &customClassName); + void redo() override; + void undo() override; + +private: + void updateSelection(); + WidgetPointerList m_widgets; + QString m_customClassName; +}; + +class QDESIGNER_SHARED_EXPORT DemoteFromCustomWidgetCommand : public QDesignerFormWindowCommand +{ +public: + using WidgetList = PromoteToCustomWidgetCommand::WidgetPointerList; + + explicit DemoteFromCustomWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(const WidgetList &promoted); + void redo() override; + void undo() override; +private: + PromoteToCustomWidgetCommand m_promote_cmd; +}; + +// Mixin class for storing the selection state +class QDESIGNER_SHARED_EXPORT CursorSelectionState { + Q_DISABLE_COPY_MOVE(CursorSelectionState) +public: + CursorSelectionState(); + + void save(const QDesignerFormWindowInterface *formWindow); + void restore(QDesignerFormWindowInterface *formWindow) const; + +private: + using WidgetPointerList = QList >; + WidgetPointerList m_selection; + QPointer m_current; +}; + +class QDESIGNER_SHARED_EXPORT LayoutCommand: public QDesignerFormWindowCommand +{ + +public: + explicit LayoutCommand(QDesignerFormWindowInterface *formWindow); + ~LayoutCommand() override; + + inline QWidgetList widgets() const { return m_widgets; } + + void init(QWidget *parentWidget, const QWidgetList &widgets, LayoutInfo::Type layoutType, + QWidget *layoutBase = nullptr, + // Reparent/Hide instances of QLayoutWidget. + bool reparentLayoutWidget = true); + + void redo() override; + void undo() override; + +private: + QPointer m_parentWidget; + QWidgetList m_widgets; + QPointer m_layoutBase; + QPointer m_layout; + CursorSelectionState m_cursorSelectionState; + bool m_setup; +}; + +class QDESIGNER_SHARED_EXPORT BreakLayoutCommand: public QDesignerFormWindowCommand +{ + +public: + explicit BreakLayoutCommand(QDesignerFormWindowInterface *formWindow); + ~BreakLayoutCommand() override; + + inline QWidgetList widgets() const { return m_widgets; } + + void init(const QWidgetList &widgets, QWidget *layoutBase, + // Reparent/Hide instances of QLayoutWidget. + bool reparentLayoutWidget = true); + + void redo() override; + void undo() override; + + // Access the properties of the layout, 0 in case of splitters. + const LayoutProperties *layoutProperties() const; + int propertyMask() const; + +private: + QWidgetList m_widgets; + QPointer m_layoutBase; + QPointer m_layout; + LayoutHelper* m_layoutHelper; + LayoutProperties *m_properties; + int m_propertyMask; + CursorSelectionState m_cursorSelectionState; +}; + +class QDESIGNER_SHARED_EXPORT SimplifyLayoutCommand: public QDesignerFormWindowCommand +{ +public: + explicit SimplifyLayoutCommand(QDesignerFormWindowInterface *formWindow); + ~SimplifyLayoutCommand() override; + + bool init(QWidget *layoutBase); + + // Quick check + static bool canSimplify(QDesignerFormEditorInterface *core, const QWidget *w, int *layoutType = nullptr); + + void redo() override; + void undo() override; + +private: + const QRect m_area; + QWidget *m_layoutBase; + LayoutHelper* m_layoutHelper; + bool m_layoutSimplified; +}; + +class ToolBoxCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ToolBoxCommand(QDesignerFormWindowInterface *formWindow); + ~ToolBoxCommand() override; + + void init(QToolBox *toolBox); + +protected: + void removePage(); + void addPage(); + + QPointer m_toolBox; + QPointer m_widget; + int m_index; + QString m_itemText; + QIcon m_itemIcon; +}; + +class MoveToolBoxPageCommand: public ToolBoxCommand +{ + +public: + explicit MoveToolBoxPageCommand(QDesignerFormWindowInterface *formWindow); + ~MoveToolBoxPageCommand() override; + + void init(QToolBox *toolBox, QWidget *page, int newIndex); + + void redo() override; + void undo() override; + +private: + int m_newIndex; + int m_oldIndex; +}; + +class DeleteToolBoxPageCommand: public ToolBoxCommand +{ + +public: + explicit DeleteToolBoxPageCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteToolBoxPageCommand() override; + + void init(QToolBox *toolBox); + + void redo() override; + void undo() override; +}; + +class AddToolBoxPageCommand: public ToolBoxCommand +{ + +public: + enum InsertionMode { + InsertBefore, + InsertAfter + }; + explicit AddToolBoxPageCommand(QDesignerFormWindowInterface *formWindow); + ~AddToolBoxPageCommand() override; + + void init(QToolBox *toolBox); + void init(QToolBox *toolBox, InsertionMode mode); + + void redo() override; + void undo() override; +}; + +class TabWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit TabWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~TabWidgetCommand() override; + + void init(QTabWidget *tabWidget); + +protected: + void removePage(); + void addPage(); + + QPointer m_tabWidget; + QPointer m_widget; + int m_index; + QString m_itemText; + QIcon m_itemIcon; +}; + +class DeleteTabPageCommand: public TabWidgetCommand +{ + +public: + explicit DeleteTabPageCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteTabPageCommand() override; + + void init(QTabWidget *tabWidget); + + void redo() override; + void undo() override; +}; + +class AddTabPageCommand: public TabWidgetCommand +{ + +public: + enum InsertionMode { + InsertBefore, + InsertAfter + }; + explicit AddTabPageCommand(QDesignerFormWindowInterface *formWindow); + ~AddTabPageCommand() override; + + void init(QTabWidget *tabWidget); + void init(QTabWidget *tabWidget, InsertionMode mode); + + void redo() override; + void undo() override; +}; + +class MoveTabPageCommand: public TabWidgetCommand +{ + +public: + explicit MoveTabPageCommand(QDesignerFormWindowInterface *formWindow); + ~MoveTabPageCommand() override; + + void init(QTabWidget *tabWidget, QWidget *page, + const QIcon &icon, const QString &label, + int index, int newIndex); + + void redo() override; + void undo() override; + +private: + int m_newIndex; + int m_oldIndex; + QPointer m_page; + QString m_label; + QIcon m_icon; +}; + +class StackedWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit StackedWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~StackedWidgetCommand() override; + + void init(QStackedWidget *stackedWidget); + +protected: + void removePage(); + void addPage(); + + QPointer m_stackedWidget; + QPointer m_widget; + int m_index; +}; + +class MoveStackedWidgetCommand: public StackedWidgetCommand +{ + +public: + explicit MoveStackedWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~MoveStackedWidgetCommand() override; + + void init(QStackedWidget *stackedWidget, QWidget *page, int newIndex); + + void redo() override; + void undo() override; + +private: + int m_newIndex; + int m_oldIndex; +}; + +class DeleteStackedWidgetPageCommand: public StackedWidgetCommand +{ + +public: + explicit DeleteStackedWidgetPageCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteStackedWidgetPageCommand() override; + + void init(QStackedWidget *stackedWidget); + + void redo() override; + void undo() override; +}; + +class AddStackedWidgetPageCommand: public StackedWidgetCommand +{ + +public: + enum InsertionMode { + InsertBefore, + InsertAfter + }; + explicit AddStackedWidgetPageCommand(QDesignerFormWindowInterface *formWindow); + ~AddStackedWidgetPageCommand() override; + + void init(QStackedWidget *stackedWidget); + void init(QStackedWidget *stackedWidget, InsertionMode mode); + + void redo() override; + void undo() override; +}; + +class CreateMenuBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit CreateMenuBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMainWindow *mainWindow); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_menuBar; +}; + +class DeleteMenuBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DeleteMenuBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMenuBar *menuBar); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_menuBar; +}; + +class CreateStatusBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit CreateStatusBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMainWindow *mainWindow); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_statusBar; +}; + +class QDESIGNER_SHARED_EXPORT DeleteStatusBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DeleteStatusBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QStatusBar *statusBar); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_statusBar; +}; + +class AddToolBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit AddToolBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMainWindow *mainWindow, Qt::ToolBarArea area); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_toolBar; +}; + +class DeleteToolBarCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DeleteToolBarCommand(QDesignerFormWindowInterface *formWindow); + + void init(QToolBar *toolBar); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_toolBar; +}; + +class DockWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit DockWidgetCommand(const QString &description, QDesignerFormWindowInterface *formWindow); + ~DockWidgetCommand() override; + + void init(QDockWidget *dockWidget); + +protected: + QPointer m_dockWidget; +}; + +class QDESIGNER_SHARED_EXPORT AddDockWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit AddDockWidgetCommand(QDesignerFormWindowInterface *formWindow); + + void init(QMainWindow *mainWindow, QDockWidget *dockWidget); + void init(QMainWindow *mainWindow); + + void undo() override; + void redo() override; + +private: + QPointer m_mainWindow; + QPointer m_dockWidget; +}; + +class QDESIGNER_SHARED_EXPORT ContainerWidgetCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ContainerWidgetCommand(QDesignerFormWindowInterface *formWindow); + ~ContainerWidgetCommand() override; + + QDesignerContainerExtension *containerExtension() const; + + void init(QWidget *containerWidget); + +protected: + void removePage(); + void addPage(); + + QPointer m_containerWidget; + QPointer m_widget; + int m_index; +}; + +class QDESIGNER_SHARED_EXPORT DeleteContainerWidgetPageCommand: public ContainerWidgetCommand +{ + +public: + explicit DeleteContainerWidgetPageCommand(QDesignerFormWindowInterface *formWindow); + ~DeleteContainerWidgetPageCommand() override; + + void init(QWidget *containerWidget, ContainerType ct); + + void redo() override; + void undo() override; +}; + +class QDESIGNER_SHARED_EXPORT AddContainerWidgetPageCommand: public ContainerWidgetCommand +{ + +public: + enum InsertionMode { + InsertBefore, + InsertAfter + }; + explicit AddContainerWidgetPageCommand(QDesignerFormWindowInterface *formWindow); + ~AddContainerWidgetPageCommand() override; + + void init(QWidget *containerWidget, ContainerType ct, InsertionMode mode); + + void redo() override; + void undo() override; +}; + +class QDESIGNER_SHARED_EXPORT ChangeCurrentPageCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeCurrentPageCommand(QDesignerFormWindowInterface *formWindow); + ~ChangeCurrentPageCommand() override; + + QDesignerContainerExtension *containerExtension() const; + + void init(QWidget *containerWidget, int newIndex); + + void redo() override; + void undo() override; + +protected: + QPointer m_containerWidget; + QPointer m_widget; + int m_oldIndex; + int m_newIndex; +}; + +struct QDESIGNER_SHARED_EXPORT ItemData { + ItemData() = default; + + ItemData(const QListWidgetItem *item, bool editor); + ItemData(const QTableWidgetItem *item, bool editor); + ItemData(const QTreeWidgetItem *item, int column); + QListWidgetItem *createListItem(DesignerIconCache *iconCache, bool editor) const; + QTableWidgetItem *createTableItem(DesignerIconCache *iconCache, bool editor) const; + void fillTreeItemColumn(QTreeWidgetItem *item, int column, DesignerIconCache *iconCache) const; + + bool isValid() const { return !m_properties.isEmpty(); } + + QHash m_properties; + + friend bool comparesEqual(const ItemData &lhs, const ItemData &rhs) noexcept + { + return lhs.m_properties == rhs.m_properties; + } + Q_DECLARE_EQUALITY_COMPARABLE(ItemData) +}; + +struct QDESIGNER_SHARED_EXPORT ListContents { + ListContents() = default; + + ListContents(const QTreeWidgetItem *item); + QTreeWidgetItem *createTreeItem(DesignerIconCache *iconCache) const; + + void createFromListWidget(const QListWidget *listWidget, bool editor); + void applyToListWidget(QListWidget *listWidget, DesignerIconCache *iconCache, + bool editor, + Qt::Alignment alignmentDefault = Qt::AlignLeading | Qt::AlignVCenter) const; + void createFromComboBox(const QComboBox *listWidget); + void applyToComboBox(QComboBox *listWidget, DesignerIconCache *iconCache) const; + + QList m_items; + + friend bool comparesEqual(const ListContents &lhs, const ListContents &rhs) noexcept + { + return lhs.m_items == rhs.m_items; + } + Q_DECLARE_EQUALITY_COMPARABLE(ListContents) +}; + +// Data structure representing the contents of a QTableWidget with +// methods to retrieve and apply for ChangeTableContentsCommand +struct QDESIGNER_SHARED_EXPORT TableWidgetContents { + + using CellRowColumnAddress = std::pair; + + TableWidgetContents(); + void clear(); + + void fromTableWidget(const QTableWidget *tableWidget, bool editor); + void applyToTableWidget(QTableWidget *tableWidget, DesignerIconCache *iconCache, bool editor) const; + + static bool nonEmpty(const QTableWidgetItem *item, int headerColumn); + static QString defaultHeaderText(int i); + static void insertHeaderItem(const QTableWidgetItem *item, int i, ListContents *header, bool editor); + + int m_columnCount = 0; + int m_rowCount = 0; + ListContents m_horizontalHeader; + ListContents m_verticalHeader; + QMap m_items; + + friend QDESIGNER_SHARED_EXPORT + bool comparesEqual(const TableWidgetContents &lhs, + const TableWidgetContents &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(TableWidgetContents) +}; + +class QDESIGNER_SHARED_EXPORT ChangeTableContentsCommand: public QDesignerFormWindowCommand +{ +public: + explicit ChangeTableContentsCommand(QDesignerFormWindowInterface *formWindow); + + void init(QTableWidget *tableWidget, const TableWidgetContents &oldCont, const TableWidgetContents &newCont); + void redo() override; + void undo() override; + +private: + QPointer m_tableWidget; + TableWidgetContents m_oldContents; + TableWidgetContents m_newContents; + DesignerIconCache *m_iconCache; +}; + +// Data structure representing the contents of a QTreeWidget with +// methods to retrieve and apply for ChangeTreeContentsCommand +struct QDESIGNER_SHARED_EXPORT TreeWidgetContents { + + struct ItemContents : public ListContents { + ItemContents() = default; + ItemContents(const QTreeWidgetItem *item, bool editor); + QTreeWidgetItem *createTreeItem(DesignerIconCache *iconCache, bool editor) const; + + int m_itemFlags = -1; + //bool m_firstColumnSpanned:1; + //bool m_hidden:1; + //bool m_expanded:1; + QList m_children; + + friend QDESIGNER_SHARED_EXPORT + bool comparesEqual(const ItemContents &lhs, + const ItemContents &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(ItemContents) + }; + + void clear(); + + void fromTreeWidget(const QTreeWidget *treeWidget, bool editor); + void applyToTreeWidget(QTreeWidget *treeWidget, DesignerIconCache *iconCache, bool editor) const; + + ListContents m_headerItem; + QList m_rootItems; + + friend bool comparesEqual(const TreeWidgetContents &lhs, + const TreeWidgetContents &rhs) noexcept + { + return lhs.m_headerItem == rhs.m_headerItem && lhs.m_rootItems == rhs.m_rootItems; + } + Q_DECLARE_EQUALITY_COMPARABLE(TreeWidgetContents) +}; + +class QDESIGNER_SHARED_EXPORT ChangeTreeContentsCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeTreeContentsCommand(QDesignerFormWindowInterface *formWindow); + + void init(QTreeWidget *treeWidget, const TreeWidgetContents &oldState, const TreeWidgetContents &newState); + void redo() override; + void undo() override; + enum ApplyIconStrategy { + SetIconStrategy, + ResetIconStrategy + }; +private: + QPointer m_treeWidget; + TreeWidgetContents m_oldState; + TreeWidgetContents m_newState; + DesignerIconCache *m_iconCache; +}; + +class QDESIGNER_SHARED_EXPORT ChangeListContentsCommand: public QDesignerFormWindowCommand +{ + +public: + explicit ChangeListContentsCommand(QDesignerFormWindowInterface *formWindow); + + void init(QListWidget *listWidget, const ListContents &oldItems, const ListContents &items); + void init(QComboBox *comboBox, const ListContents &oldItems, const ListContents &items); + void redo() override; + void undo() override; +private: + QPointer m_listWidget; + QPointer m_comboBox; + ListContents m_oldItemsState; + ListContents m_newItemsState; + DesignerIconCache *m_iconCache; +}; + +class QDESIGNER_SHARED_EXPORT AddActionCommand : public QDesignerFormWindowCommand +{ + +public: + explicit AddActionCommand(QDesignerFormWindowInterface *formWindow); + void init(QAction *action); + void redo() override; + void undo() override; +private: + QAction *m_action; +}; + +// Note: This command must be executed within a macro since it +// makes the form emit objectRemoved() which might cause other components +// to add commands (for example, removal of signals and slots +class QDESIGNER_SHARED_EXPORT RemoveActionCommand : public QDesignerFormWindowCommand +{ + +public: + explicit RemoveActionCommand(QDesignerFormWindowInterface *formWindow); + void init(QAction *action); + void redo() override; + void undo() override; + + struct ActionDataItem { + ActionDataItem(QAction *_before = nullptr, QWidget *_widget = nullptr) + : before(_before), widget(_widget) {} + QAction *before; + QWidget *widget; + }; + using ActionData = QList; + +private: + QAction *m_action; + + ActionData m_actionData; +}; + +class ActionInsertionCommand : public QDesignerFormWindowCommand +{ + +protected: + ActionInsertionCommand(const QString &text, QDesignerFormWindowInterface *formWindow); + +public: + void init(QWidget *parentWidget, QAction *action, QAction *beforeAction = nullptr, bool update = true); + +protected: + void insertAction(); + void removeAction(); + +private: + QWidget *m_parentWidget; + QAction *m_action; + QAction *m_beforeAction; + bool m_update; +}; + +class InsertActionIntoCommand : public ActionInsertionCommand +{ + +public: + explicit InsertActionIntoCommand(QDesignerFormWindowInterface *formWindow); + + void redo() override { insertAction(); } + void undo() override { removeAction(); } +}; + +class RemoveActionFromCommand : public ActionInsertionCommand +{ + +public: + explicit RemoveActionFromCommand(QDesignerFormWindowInterface *formWindow); + + void redo() override { removeAction(); } + void undo() override { insertAction(); } +}; + +class MenuActionCommand : public QDesignerFormWindowCommand +{ +public: + void init(QAction *action, QAction *actionBefore, QWidget *associatedWidget, QWidget *objectToSelect); + +protected: + MenuActionCommand(const QString &text, QDesignerFormWindowInterface *formWindow); + void insertMenu(); + void removeMenu(); + +private: + QAction *m_action; + QAction *m_actionBefore; + QWidget *m_menuParent; + QWidget *m_associatedWidget; + QWidget *m_objectToSelect; +}; + +class AddMenuActionCommand : public MenuActionCommand +{ + +public: + explicit AddMenuActionCommand(QDesignerFormWindowInterface *formWindow); + + void redo() override { insertMenu(); } + void undo() override { removeMenu(); } +}; + +class RemoveMenuActionCommand : public MenuActionCommand +{ + +public: + explicit RemoveMenuActionCommand(QDesignerFormWindowInterface *formWindow); + + void redo() override { removeMenu(); } + void undo() override { insertMenu(); } +}; + +class CreateSubmenuCommand : public QDesignerFormWindowCommand +{ + +public: + explicit CreateSubmenuCommand(QDesignerFormWindowInterface *formWindow); + void init(QDesignerMenu *menu, QAction *action, QObject *m_objectToSelect = nullptr); + void redo() override; + void undo() override; +private: + QAction *m_action; + QDesignerMenu *m_menu; + QObject *m_objectToSelect; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_COMMAND_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_dnditem.cpp b/src/tools/designer/src/lib/shared/qdesigner_dnditem.cpp new file mode 100644 index 00000000000..d7875f55cb0 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_dnditem.cpp @@ -0,0 +1,265 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_dnditem_p.h" +#include "formwindowbase_p.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +QDesignerDnDItem::QDesignerDnDItem(DropType type, QWidget *source) : + m_source(source), + m_type(type), + m_dom_ui(nullptr), + m_widget(nullptr), + m_decoration(nullptr) +{ +} + +void QDesignerDnDItem::init(DomUI *ui, QWidget *widget, QWidget *decoration, + const QPoint &global_mouse_pos) +{ + Q_ASSERT(widget != nullptr || ui != nullptr); + Q_ASSERT(decoration != nullptr); + + m_dom_ui = ui; + m_widget = widget; + m_decoration = decoration; + + m_hot_spot = global_mouse_pos - m_decoration->geometry().topLeft(); +} + +QDesignerDnDItem::~QDesignerDnDItem() +{ + if (m_decoration != nullptr) + m_decoration->deleteLater(); + delete m_dom_ui; +} + +DomUI *QDesignerDnDItem::domUi() const +{ + return m_dom_ui; +} + +QWidget *QDesignerDnDItem::decoration() const +{ + return m_decoration; +} + +QPoint QDesignerDnDItem::hotSpot() const +{ + return m_hot_spot; +} + +QWidget *QDesignerDnDItem::widget() const +{ + return m_widget; +} + +QDesignerDnDItem::DropType QDesignerDnDItem::type() const +{ + return m_type; +} + +QWidget *QDesignerDnDItem::source() const +{ + return m_source; +} + +void QDesignerDnDItem::setDomUi(DomUI *dom_ui) +{ + delete m_dom_ui; + m_dom_ui = dom_ui; +} + +// ---------- QDesignerMimeData + +// Make pixmap transparent on Windows only. Mac is transparent by default, Unix usually does not work. +#ifdef Q_OS_WIN +# define TRANSPARENT_DRAG_PIXMAP +#endif + +QDesignerMimeData::QDesignerMimeData(const QDesignerDnDItems &items, QDrag *drag) : + m_items(items) +{ + enum { Alpha = 200 }; + QPoint decorationTopLeft; + switch (m_items.size()) { + case 0: + break; + case 1: { + QWidget *deco = m_items.first()->decoration(); + decorationTopLeft = deco->pos(); + const QPixmap widgetPixmap = deco->grab(QRect(0, 0, -1, -1)); +#ifdef TRANSPARENT_DRAG_PIXMAP + QImage image(widgetPixmap.size(), QImage::Format_ARGB32); + image.setDevicePixelRatio(widgetPixmap.devicePixelRatio()); + image.fill(QColor(Qt::transparent).rgba()); + QPainter painter(&image); + painter.drawPixmap(QPoint(0, 0), widgetPixmap); + painter.end(); + setImageTransparency(image, Alpha); + drag->setPixmap(QPixmap::fromImage(image)); +#else + drag->setPixmap(widgetPixmap); +#endif + } + break; + default: { + // determine size of drag decoration by uniting all geometries + const auto cend = m_items.cend(); + auto it = m_items.cbegin(); + QRect unitedGeometry = (*it)->decoration()->geometry(); + const qreal devicePixelRatio = (*it)->decoration()->devicePixelRatioF(); + for (++it; it != cend; ++it ) + unitedGeometry = unitedGeometry .united((*it)->decoration()->geometry()); + + // paint with offset. At the same time, create a mask bitmap, containing widget rectangles. + const QSize imageSize = (QSizeF(unitedGeometry.size()) * devicePixelRatio).toSize(); + QImage image(imageSize, QImage::Format_ARGB32); + image.setDevicePixelRatio(devicePixelRatio); + image.fill(QColor(Qt::transparent).rgba()); + QBitmap mask(imageSize); + mask.setDevicePixelRatio(devicePixelRatio); + mask.clear(); + // paint with offset, determine action + QPainter painter(&image); + QPainter maskPainter(&mask); + decorationTopLeft = unitedGeometry.topLeft(); + for (auto *item : std::as_const(m_items)) { + QWidget *w = item->decoration(); + const QPixmap wp = w->grab(QRect(0, 0, -1, -1)); + const QPoint pos = w->pos() - decorationTopLeft; + painter.drawPixmap(pos, wp); + maskPainter.fillRect(QRect(pos, w->size()), Qt::color1); + } + painter.end(); + maskPainter.end(); +#ifdef TRANSPARENT_DRAG_PIXMAP + setImageTransparency(image, Alpha); +#endif + QPixmap pixmap = QPixmap::fromImage(image); + pixmap.setMask(mask); + drag->setPixmap(pixmap); + } + break; + } + // determine hot spot and reconstruct the exact starting position as form window + // introduces some offset when detecting DnD + m_globalStartPos = m_items.first()->decoration()->pos() + m_items.first()->hotSpot(); + m_hotSpot = m_globalStartPos - decorationTopLeft; + drag->setHotSpot(m_hotSpot); + + drag->setMimeData(this); +} + +QDesignerMimeData::~QDesignerMimeData() +{ + qDeleteAll(m_items); +} + +Qt::DropAction QDesignerMimeData::proposedDropAction() const +{ + return m_items.first()->type() == QDesignerDnDItemInterface::CopyDrop ? Qt::CopyAction : Qt::MoveAction; +} + +Qt::DropAction QDesignerMimeData::execDrag(const QDesignerDnDItems &items, QWidget * dragSource) +{ + if (items.isEmpty()) + return Qt::IgnoreAction; + + QDrag *drag = new QDrag(dragSource); + QDesignerMimeData *mimeData = new QDesignerMimeData(items, drag); + + // Store pointers to widgets that are to be re-shown if a move operation is canceled + QWidgetList reshowWidgets; + for (auto *item : items) { + if (QWidget *w = item->widget()) { + if (item->type() == QDesignerDnDItemInterface::MoveDrop) + reshowWidgets.push_back(w); + } + } + + const Qt::DropAction executedAction = drag->exec(Qt::CopyAction|Qt::MoveAction, mimeData->proposedDropAction()); + + if (executedAction == Qt::IgnoreAction) { + for (QWidget *w : std::as_const(reshowWidgets)) + w->show(); + } + + return executedAction; +} + + +void QDesignerMimeData::moveDecoration(const QPoint &globalPos) const +{ + const QPoint relativeDistance = globalPos - m_globalStartPos; + for (auto *item : m_items) { + QWidget *w = item->decoration(); + w->move(w->pos() + relativeDistance); + } +} + +void QDesignerMimeData::removeMovedWidgetsFromSourceForm(const QDesignerDnDItems &items) +{ + QMultiMap formWidgetMap; + // Find moved widgets per form + for (auto *item : items) { + if (item->type() == QDesignerDnDItemInterface::MoveDrop) { + if (QWidget *w = item->widget()) { + if (FormWindowBase *fb = qobject_cast(item->source())) + formWidgetMap.insert(fb, w); + } + } + } + + const auto &formWindows = formWidgetMap.uniqueKeys(); + for (FormWindowBase *fb : formWindows) + fb->deleteWidgetList(formWidgetMap.values(fb)); +} + +void QDesignerMimeData::acceptEventWithAction(Qt::DropAction desiredAction, QDropEvent *e) +{ + if (e->proposedAction() == desiredAction) { + e->acceptProposedAction(); + } else { + e->setDropAction(desiredAction); + e->accept(); + } +} + +void QDesignerMimeData::acceptEvent(QDropEvent *e) const +{ + acceptEventWithAction(proposedDropAction(), e); +} + +void QDesignerMimeData::setImageTransparency(QImage &image, int alpha) +{ + const int height = image.height(); + for (int l = 0; l < height; l++) { + QRgb *line = reinterpret_cast(image.scanLine(l)); + QRgb *lineEnd = line + image.width(); + for ( ; line < lineEnd; line++) { + const QRgb rgba = *line; + *line = qRgba(qRed(rgba), qGreen(rgba), qBlue(rgba), alpha); + } + } +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_dnditem_p.h b/src/tools/designer/src/lib/shared/qdesigner_dnditem_p.h new file mode 100644 index 00000000000..aecd626a9d1 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_dnditem_p.h @@ -0,0 +1,109 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_DNDITEM_H +#define QDESIGNER_DNDITEM_H + +#include "shared_global_p.h" +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDrag; +class QImage; +class QDropEvent; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT QDesignerDnDItem: public QDesignerDnDItemInterface +{ +public: + explicit QDesignerDnDItem(DropType type, QWidget *source = nullptr); + ~QDesignerDnDItem() override; + + DomUI *domUi() const override; + QWidget *decoration() const override; + QWidget *widget() const override; + QPoint hotSpot() const override; + QWidget *source() const override; + + DropType type() const override; + +protected: + void setDomUi(DomUI *dom_ui); + void init(DomUI *ui, QWidget *widget, QWidget *decoration, const QPoint &global_mouse_pos); + +private: + QWidget *m_source; + const DropType m_type; + const QPoint m_globalStartPos; + DomUI *m_dom_ui; + QWidget *m_widget; + QWidget *m_decoration; + QPoint m_hot_spot; + + Q_DISABLE_COPY_MOVE(QDesignerDnDItem) +}; + +// Mime data for use with designer drag and drop operations. + +class QDESIGNER_SHARED_EXPORT QDesignerMimeData : public QMimeData { + Q_OBJECT + +public: + using QDesignerDnDItems = QList; + + ~QDesignerMimeData() override; + + const QDesignerDnDItems &items() const { return m_items; } + + // Execute a drag and drop operation. + static Qt::DropAction execDrag(const QDesignerDnDItems &items, QWidget * dragSource); + + QPoint hotSpot() const { return m_hotSpot; } + + // Move the decoration. Required for drops over form windows as the position + // is derived from the decoration position. + void moveDecoration(const QPoint &globalPos) const; + + // For a move operation, create the undo command sequence to remove + // the widgets from the source form. + static void removeMovedWidgetsFromSourceForm(const QDesignerDnDItems &items); + + // Accept an event with the proper action. + void acceptEvent(QDropEvent *e) const; + + // Helper to accept an event with the desired action. + static void acceptEventWithAction(Qt::DropAction desiredAction, QDropEvent *e); + +private: + QDesignerMimeData(const QDesignerDnDItems &items, QDrag *drag); + Qt::DropAction proposedDropAction() const; + + static void setImageTransparency(QImage &image, int alpha); + + const QDesignerDnDItems m_items; + QPoint m_globalStartPos; + QPoint m_hotSpot; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_DNDITEM_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_dockwidget.cpp b/src/tools/designer/src/lib/shared/qdesigner_dockwidget.cpp new file mode 100644 index 00000000000..9b857efff7e --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_dockwidget.cpp @@ -0,0 +1,114 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_dockwidget_p.h" +#include "layoutinfo_p.h" + +#include +#include +#include +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +bool QDockWidgetPropertySheet::isEnabled(int index) const +{ + const QString &name = propertyName(index); + if (name == "dockWidgetArea"_L1) + return static_cast(object())->docked(); + if (name == "docked"_L1) + return static_cast(object())->inMainWindow(); + return QDesignerPropertySheet::isEnabled(index); +} + +QDesignerDockWidget::QDesignerDockWidget(QWidget *parent) + : QDockWidget(parent) +{ +} + +QDesignerDockWidget::~QDesignerDockWidget() = default; + +bool QDesignerDockWidget::docked() const +{ + return qobject_cast(parentWidget()) != 0; +} + +void QDesignerDockWidget::setDocked(bool b) +{ + if (QMainWindow *mainWindow = findMainWindow()) { + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerContainerExtension *c; + c = qt_extension(core->extensionManager(), mainWindow); + if (b && !docked()) { + // Dock it + // ### undo/redo stack + setParent(nullptr); + c->addWidget(this); + formWindow()->selectWidget(this, formWindow()->cursor()->isWidgetSelected(this)); + } else if (!b && docked()) { + // Undock it + for (int i = 0; i < c->count(); ++i) { + if (c->widget(i) == this) { + c->remove(i); + break; + } + } + // #### restore the position + setParent(mainWindow->centralWidget()); + show(); + formWindow()->selectWidget(this, formWindow()->cursor()->isWidgetSelected(this)); + } + } +} + +Qt::DockWidgetArea QDesignerDockWidget::dockWidgetArea() const +{ + if (QMainWindow *mainWindow = qobject_cast(parentWidget())) + return mainWindow->dockWidgetArea(const_cast(this)); + + return Qt::LeftDockWidgetArea; +} + +void QDesignerDockWidget::setDockWidgetArea(Qt::DockWidgetArea dockWidgetArea) +{ + if (QMainWindow *mainWindow = qobject_cast(parentWidget())) { + if ((dockWidgetArea != Qt::NoDockWidgetArea) + && isAreaAllowed(dockWidgetArea)) { + mainWindow->addDockWidget(dockWidgetArea, this); + } + } +} + +bool QDesignerDockWidget::inMainWindow() const +{ + QMainWindow *mw = findMainWindow(); + if (mw && !mw->centralWidget()->layout()) { + if (mw == parentWidget()) + return true; + if (mw->centralWidget() == parentWidget()) + return true; + } + return false; +} + +QDesignerFormWindowInterface *QDesignerDockWidget::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(const_cast(this)); +} + +QMainWindow *QDesignerDockWidget::findMainWindow() const +{ + if (QDesignerFormWindowInterface *fw = formWindow()) + return qobject_cast(fw->mainContainer()); + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_dockwidget_p.h b/src/tools/designer/src/lib/shared/qdesigner_dockwidget_p.h new file mode 100644 index 00000000000..f8031c04522 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_dockwidget_p.h @@ -0,0 +1,64 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_DOCKWIDGET_H +#define QDESIGNER_DOCKWIDGET_H + +#include "shared_global_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +class QDESIGNER_SHARED_EXPORT QDesignerDockWidget: public QDockWidget +{ + Q_OBJECT + Q_PROPERTY(Qt::DockWidgetArea dockWidgetArea READ dockWidgetArea WRITE setDockWidgetArea DESIGNABLE true STORED false) + Q_PROPERTY(bool docked READ docked WRITE setDocked DESIGNABLE true STORED false) +public: + QDesignerDockWidget(QWidget *parent = nullptr); + ~QDesignerDockWidget() override; + + bool docked() const; + void setDocked(bool b); + + Qt::DockWidgetArea dockWidgetArea() const; + void setDockWidgetArea(Qt::DockWidgetArea dockWidgetArea); + + bool inMainWindow() const; + +private: + QDesignerFormWindowInterface *formWindow() const; + QMainWindow *findMainWindow() const; +}; + +class QDESIGNER_SHARED_EXPORT QDockWidgetPropertySheet : public QDesignerPropertySheet +{ + Q_OBJECT +public: + using QDesignerPropertySheet::QDesignerPropertySheet; + + bool isEnabled(int index) const override; +}; + +using QDockWidgetPropertySheetFactory = + QDesignerPropertySheetFactory; + +QT_END_NAMESPACE + +#endif // QDESIGNER_DOCKWIDGET_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_formbuilder.cpp b/src/tools/designer/src/lib/shared/qdesigner_formbuilder.cpp new file mode 100644 index 00000000000..ff52bfd4bc9 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_formbuilder.cpp @@ -0,0 +1,385 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_formbuilder_p.h" +#include "dynamicpropertysheet.h" +#include "qsimpleresource_p.h" +#include "widgetfactory_p.h" +#include "qdesigner_introspection_p.h" + +#include +#include +// sdk +#include +#include +#include +#include +#include +#include +#include + +#include + +// shared +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +QDesignerFormBuilder::QDesignerFormBuilder(QDesignerFormEditorInterface *core, + const DeviceProfile &deviceProfile) : + m_core(core), + m_deviceProfile(deviceProfile), + m_pixmapCache(nullptr), + m_iconCache(nullptr), + m_ignoreCreateResources(false), + m_tempResourceSet(nullptr), + m_mainWidget(true) +{ + Q_ASSERT(m_core); +} + +QString QDesignerFormBuilder::systemStyle() const +{ + return m_deviceProfile.isEmpty() ? + QString::fromUtf8(QApplication::style()->metaObject()->className()) : + m_deviceProfile.style(); +} + +QWidget *QDesignerFormBuilder::create(DomUI *ui, QWidget *parentWidget) +{ + m_mainWidget = true; + QtResourceSet *resourceSet = core()->resourceModel()->currentResourceSet(); + + // reload resource properties; + createResources(ui->elementResources()); + core()->resourceModel()->setCurrentResourceSet(m_tempResourceSet); + + m_ignoreCreateResources = true; + DesignerPixmapCache pixmapCache; + DesignerIconCache iconCache(&pixmapCache); + m_pixmapCache = &pixmapCache; + m_iconCache = &iconCache; + + QWidget *widget = QFormBuilder::create(ui, parentWidget); + + core()->resourceModel()->setCurrentResourceSet(resourceSet); + core()->resourceModel()->removeResourceSet(m_tempResourceSet); + m_tempResourceSet = nullptr; + m_ignoreCreateResources = false; + m_pixmapCache = nullptr; + m_iconCache = nullptr; + + m_customWidgetsWithScript.clear(); + return widget; +} + +QWidget *QDesignerFormBuilder::createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) +{ + QWidget *widget = nullptr; + + if (widgetName == "QToolBar"_L1) { + widget = new QToolBar(parentWidget); + } else if (widgetName == "QMenu"_L1) { + widget = new QMenu(parentWidget); + } else if (widgetName == "QMenuBar"_L1) { + widget = new QMenuBar(parentWidget); + } else { + widget = core()->widgetFactory()->createWidget(widgetName, parentWidget); + } + + if (widget) { + widget->setObjectName(name); + if (QSimpleResource::hasCustomWidgetScript(m_core, widget)) + m_customWidgetsWithScript.insert(widget); + } + + if (m_mainWidget) { // We need to apply the DPI here to take effect on size hints, etc. + m_deviceProfile.apply(m_core, widget, DeviceProfile::ApplyPreview); + m_mainWidget = false; + } + return widget; +} + +bool QDesignerFormBuilder::addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) +{ + // Use container extension or rely on scripts unless main window. + if (QFormBuilder::addItem(ui_widget, widget, parentWidget)) + return true; + + if (QDesignerContainerExtension *container = qt_extension(m_core->extensionManager(), parentWidget)) { + container->addWidget(widget); + return true; + } + return false; +} + +bool QDesignerFormBuilder::addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) +{ + return QFormBuilder::addItem(ui_item, item, layout); +} + +QIcon QDesignerFormBuilder::nameToIcon(const QString &filePath, const QString &qrcPath) +{ + Q_UNUSED(filePath); + Q_UNUSED(qrcPath); + qWarning() << "QDesignerFormBuilder::nameToIcon() is obsoleted"; + return QIcon(); +} + +QPixmap QDesignerFormBuilder::nameToPixmap(const QString &filePath, const QString &qrcPath) +{ + Q_UNUSED(filePath); + Q_UNUSED(qrcPath); + qWarning() << "QDesignerFormBuilder::nameToPixmap() is obsoleted"; + return QPixmap(); +} + +/* If the property is a enum or flag value, retrieve + * the existing enum/flag type via property sheet and use it to convert */ + +static bool readDomEnumerationValue(const DomProperty *p, + const QDesignerPropertySheetExtension* sheet, + QVariant &v) +{ + switch (p->kind()) { + case DomProperty::Set: { + const int index = sheet->indexOf(p->attributeName()); + if (index == -1) + return false; + const QVariant sheetValue = sheet->property(index); + if (sheetValue.canConvert()) { + const PropertySheetFlagValue f = qvariant_cast(sheetValue); + bool ok = false; + v = f.metaFlags.parseFlags(p->elementSet(), &ok); + if (!ok) + designerWarning(f.metaFlags.messageParseFailed(p->elementSet())); + return true; + } + } + break; + case DomProperty::Enum: { + const int index = sheet->indexOf(p->attributeName()); + if (index == -1) + return false; + const QVariant sheetValue = sheet->property(index); + if (sheetValue.canConvert()) { + const PropertySheetEnumValue e = qvariant_cast(sheetValue); + bool ok = false; + v = e.metaEnum.parseEnum(p->elementEnum(), &ok); + if (!ok) + designerWarning(e.metaEnum.messageParseFailed(p->elementEnum())); + return true; + } + } + break; + default: + break; + } + return false; +} + +void QDesignerFormBuilder::applyProperties(QObject *o, const QList &properties) +{ + if (properties.isEmpty()) + return; + + const QDesignerPropertySheetExtension *sheet = qt_extension(core()->extensionManager(), o); + const QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core()->extensionManager(), o); + const bool changingMetaObject = WidgetFactory::classNameOf(core(), o) == "QAxWidget"_L1; + const QDesignerMetaObjectInterface *meta = core()->introspection()->metaObject(o); + const bool dynamicPropertiesAllowed = dynamicSheet && dynamicSheet->dynamicPropertiesAllowed(); + + QDesignerPropertySheet *designerPropertySheet = qobject_cast( + core()->extensionManager()->extension(o, Q_TYPEID(QDesignerPropertySheetExtension))); + + if (designerPropertySheet) { + if (designerPropertySheet->pixmapCache()) + designerPropertySheet->setPixmapCache(m_pixmapCache); + if (designerPropertySheet->iconCache()) + designerPropertySheet->setIconCache(m_iconCache); + } + + for (DomProperty *p : properties) { + QVariant v; + if (!readDomEnumerationValue(p, sheet, v)) + v = toVariant(o->metaObject(), p); + + if (v.isNull()) + continue; + + const QString attributeName = p->attributeName(); + if (d->applyPropertyInternally(o, attributeName, v)) + continue; + + // refuse fake properties like current tab name (weak test) + if (!dynamicPropertiesAllowed) { + if (changingMetaObject) // Changes after setting control of QAxWidget + meta = core()->introspection()->metaObject(o); + if (meta->indexOfProperty(attributeName) == -1) + continue; + } + + QObject *obj = o; + QAbstractScrollArea *scroll = qobject_cast(o); + if (scroll && attributeName == "cursor"_L1 && scroll->viewport()) + obj = scroll->viewport(); + + // a real property + obj->setProperty(attributeName.toUtf8(), v); + } +} + +DomWidget *QDesignerFormBuilder::createDom(QWidget *widget, DomWidget *ui_parentWidget, bool recursive) +{ + DomWidget *ui_widget = QFormBuilder::createDom(widget, ui_parentWidget, recursive); + QSimpleResource::addExtensionDataToDOM(this, m_core, ui_widget, widget); + return ui_widget; +} + +QWidget *QDesignerFormBuilder::create(DomWidget *ui_widget, QWidget *parentWidget) +{ + QWidget *widget = QFormBuilder::create(ui_widget, parentWidget); + // Do not apply state if scripts are to be run in preview mode + QSimpleResource::applyExtensionDataFromDOM(this, m_core, ui_widget, widget); + return widget; +} + +void QDesignerFormBuilder::createResources(DomResources *resources) +{ + if (m_ignoreCreateResources) + return; + QStringList paths; + if (resources != nullptr) { + const auto &dom_include = resources->elementInclude(); + for (DomResource *res : dom_include) { + QString path = QDir::cleanPath(workingDirectory().absoluteFilePath(res->attributeLocation())); + paths << path; + } + } + + m_tempResourceSet = core()->resourceModel()->addResourceSet(paths); +} + +QLayout *QDesignerFormBuilder::create(DomLayout *ui_layout, QLayout *layout, QWidget *parentWidget) +{ + return QFormBuilder::create(ui_layout, layout, parentWidget); +} + +void QDesignerFormBuilder::loadExtraInfo(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) +{ + QFormBuilder::loadExtraInfo(ui_widget, widget, parentWidget); +} + +QWidget *QDesignerFormBuilder::createPreview(const QDesignerFormWindowInterface *fw, + const QString &styleName, + const QString &appStyleSheet, + const DeviceProfile &deviceProfile, + QString *errorMessage) +{ + // load + QDesignerFormBuilder builder(fw->core(), deviceProfile); + builder.setWorkingDirectory(fw->absoluteDir()); + + QByteArray bytes = fw->contents().toUtf8(); + + QBuffer buffer(&bytes); + buffer.open(QIODevice::ReadOnly); + + QWidget *widget = builder.load(&buffer, nullptr); + if (!widget) { // Shouldn't happen + *errorMessage = QCoreApplication::translate("QDesignerFormBuilder", "The preview failed to build."); + return nullptr; + } + // Make sure palette is applied + const QString styleToUse = styleName.isEmpty() ? builder.deviceProfile().style() : styleName; + if (!styleToUse.isEmpty()) { + if (WidgetFactory *wf = qobject_cast(fw->core()->widgetFactory())) { + if (styleToUse != wf->styleName()) + WidgetFactory::applyStyleToTopLevel(wf->getStyle(styleToUse), widget); + } + } + // Fake application style sheet by prepending. (If this doesn't work, fake by nesting + // into parent widget). + if (!appStyleSheet.isEmpty()) + widget->setStyleSheet(appStyleSheet + u'\n' + widget->styleSheet()); + return widget; +} + +QWidget *QDesignerFormBuilder::createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName) +{ + return createPreview(fw, styleName, QString()); +} + +QWidget *QDesignerFormBuilder::createPreview(const QDesignerFormWindowInterface *fw, + const QString &styleName, + const QString &appStyleSheet, + QString *errorMessage) +{ + return createPreview(fw, styleName, appStyleSheet, DeviceProfile(), errorMessage); +} + +QWidget *QDesignerFormBuilder::createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName, const QString &appStyleSheet) +{ + QString errorMessage; + QWidget *widget = createPreview(fw, styleName, appStyleSheet, DeviceProfile(), &errorMessage); + if (!widget && !errorMessage.isEmpty()) { + // Display Script errors or message box + QWidget *dialogParent = fw->core()->topLevel(); + fw->core()->dialogGui()->message(dialogParent, QDesignerDialogGuiInterface::PreviewFailureMessage, + QMessageBox::Warning, QCoreApplication::translate("QDesignerFormBuilder", "Designer"), + errorMessage, QMessageBox::Ok); + return nullptr; + } + return widget; +} + +QPixmap QDesignerFormBuilder::createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &styleName, const QString &appStyleSheet) +{ + QWidget *widget = createPreview(fw, styleName, appStyleSheet); + if (!widget) + return QPixmap(); + + const QPixmap rc = widget->grab(QRect(0, 0, -1, -1)); + widget->deleteLater(); + return rc; +} + +// ---------- NewFormWidgetFormBuilder + +NewFormWidgetFormBuilder::NewFormWidgetFormBuilder(QDesignerFormEditorInterface *core, + const DeviceProfile &deviceProfile) : + QDesignerFormBuilder(core, deviceProfile) +{ +} + +void NewFormWidgetFormBuilder::createCustomWidgets(DomCustomWidgets *dc) +{ + QSimpleResource::handleDomCustomWidgets(core(), dc); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_formbuilder_p.h b/src/tools/designer/src/lib/shared/qdesigner_formbuilder_p.h new file mode 100644 index 00000000000..a4652f60a5a --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_formbuilder_p.h @@ -0,0 +1,130 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_FORMBUILDER_H +#define QDESIGNER_FORMBUILDER_H + +#include "shared_global_p.h" +#include "deviceprofile_p.h" + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; + +class QPixmap; +class QtResourceSet; + +namespace qdesigner_internal { + +class DesignerPixmapCache; +class DesignerIconCache; + +/* Form builder used for previewing forms and widget box. + * It applies the system settings to its toplevel window. */ + +class QDESIGNER_SHARED_EXPORT QDesignerFormBuilder: public QFormBuilder +{ +public: + QDesignerFormBuilder(QDesignerFormEditorInterface *core, + const DeviceProfile &deviceProfile = DeviceProfile()); + + virtual QWidget *createWidget(DomWidget *ui_widget, QWidget *parentWidget = nullptr) + { return QFormBuilder::create(ui_widget, parentWidget); } + + inline QDesignerFormEditorInterface *core() const + { return m_core; } + + QString systemStyle() const; + + // Create a preview widget (for integrations) or return 0. The widget has to be embedded into a main window. + // Experimental, depending on script support. + static QWidget *createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName /* ="" */, + const QString &appStyleSheet /* ="" */, + const DeviceProfile &deviceProfile, + QString *errorMessage); + // Convenience that pops up message boxes in case of failures. + static QWidget *createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName = QString()); + // Create a preview widget (for integrations) or return 0. The widget has to be embedded into a main window. + static QWidget *createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName, const QString &appStyleSheet, QString *errorMessage); + // Convenience that pops up message boxes in case of failures. + static QWidget *createPreview(const QDesignerFormWindowInterface *fw, const QString &styleName, const QString &appStyleSheet); + + // Create a preview image + static QPixmap createPreviewPixmap(const QDesignerFormWindowInterface *fw, const QString &styleName = QString(), const QString &appStyleSheet = QString()); + +protected: + using QFormBuilder::createDom; + using QFormBuilder::create; + + QWidget *create(DomUI *ui, QWidget *parentWidget) override; + DomWidget *createDom(QWidget *widget, DomWidget *ui_parentWidget, bool recursive = true) override; + QWidget *create(DomWidget *ui_widget, QWidget *parentWidget) override; + QLayout *create(DomLayout *ui_layout, QLayout *layout, QWidget *parentWidget) override; + void createResources(DomResources *resources) override; + + QWidget *createWidget(const QString &widgetName, QWidget *parentWidget, const QString &name) override; + bool addItem(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) override; + bool addItem(DomLayoutItem *ui_item, QLayoutItem *item, QLayout *layout) override; + + virtual QIcon nameToIcon(const QString &filePath, const QString &qrcPath); + virtual QPixmap nameToPixmap(const QString &filePath, const QString &qrcPath); + + void applyProperties(QObject *o, const QList &properties) override; + + void loadExtraInfo(DomWidget *ui_widget, QWidget *widget, QWidget *parentWidget) override; + + QtResourceSet *internalResourceSet() const { return m_tempResourceSet; } + + DeviceProfile deviceProfile() const { return m_deviceProfile; } + +private: + QDesignerFormEditorInterface *m_core; + + using WidgetSet = QSet; + WidgetSet m_customWidgetsWithScript; + + const DeviceProfile m_deviceProfile; + + DesignerPixmapCache *m_pixmapCache; + DesignerIconCache *m_iconCache; + bool m_ignoreCreateResources; + QtResourceSet *m_tempResourceSet; + bool m_mainWidget; +}; + +// Form builder for a new form widget (preview). To allow for promoted +// widgets in the template, it implements the handling of custom widgets +// (adding of them to the widget database). + +class QDESIGNER_SHARED_EXPORT NewFormWidgetFormBuilder: public QDesignerFormBuilder { +public: + NewFormWidgetFormBuilder(QDesignerFormEditorInterface *core, + const DeviceProfile &deviceProfile = DeviceProfile()); + +protected: + void createCustomWidgets(DomCustomWidgets *) override; +}; + + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_FORMBUILDER_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_formeditorcommand.cpp b/src/tools/designer/src/lib/shared/qdesigner_formeditorcommand.cpp new file mode 100644 index 00000000000..89727103f10 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_formeditorcommand.cpp @@ -0,0 +1,26 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "qdesigner_formeditorcommand_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// ---- QDesignerFormEditorCommand ---- +QDesignerFormEditorCommand::QDesignerFormEditorCommand(const QString &description, QDesignerFormEditorInterface *core) + : QUndoCommand(description), + m_core(core) +{ +} + +QDesignerFormEditorInterface *QDesignerFormEditorCommand::core() const +{ + return m_core; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_formeditorcommand_p.h b/src/tools/designer/src/lib/shared/qdesigner_formeditorcommand_p.h new file mode 100644 index 00000000000..8b7d8c9ac3f --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_formeditorcommand_p.h @@ -0,0 +1,48 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_FORMEDITORCOMMAND_H +#define QDESIGNER_FORMEDITORCOMMAND_H + +#include "shared_global_p.h" + +#include + +#include + + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT QDesignerFormEditorCommand: public QUndoCommand +{ + +public: + QDesignerFormEditorCommand(const QString &description, QDesignerFormEditorInterface *core); + +protected: + QDesignerFormEditorInterface *core() const; + +private: + QPointer m_core; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_FORMEDITORCOMMAND_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_formwindowcommand.cpp b/src/tools/designer/src/lib/shared/qdesigner_formwindowcommand.cpp new file mode 100644 index 00000000000..48687277e54 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_formwindowcommand.cpp @@ -0,0 +1,113 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + + +#include "qdesigner_formwindowcommand_p.h" +#include "qdesigner_objectinspector_p.h" +#include "layout_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +// ---- QDesignerFormWindowCommand ---- +QDesignerFormWindowCommand::QDesignerFormWindowCommand(const QString &description, + QDesignerFormWindowInterface *formWindow, + QUndoCommand *parent) + : QUndoCommand(description, parent), + m_formWindow(formWindow) +{ +} + +QDesignerFormWindowInterface *QDesignerFormWindowCommand::formWindow() const +{ + return m_formWindow; +} + +QDesignerFormEditorInterface *QDesignerFormWindowCommand::core() const +{ + if (QDesignerFormWindowInterface *fw = formWindow()) + return fw->core(); + + return nullptr; +} + +void QDesignerFormWindowCommand::undo() +{ + cheapUpdate(); +} + +void QDesignerFormWindowCommand::redo() +{ + cheapUpdate(); +} + +void QDesignerFormWindowCommand::cheapUpdate() +{ + if (core()->objectInspector()) + core()->objectInspector()->setFormWindow(formWindow()); + + if (core()->actionEditor()) + core()->actionEditor()->setFormWindow(formWindow()); +} + +QDesignerPropertySheetExtension* QDesignerFormWindowCommand::propertySheet(QObject *object) const +{ + return qt_extension(formWindow()->core()->extensionManager(), object); +} + +void QDesignerFormWindowCommand::updateBuddies(QDesignerFormWindowInterface *form, + const QString &old_name, + const QString &new_name) +{ + QExtensionManager* extensionManager = form->core()->extensionManager(); + + const auto label_list = form->findChildren(); + if (label_list.isEmpty()) + return; + + const QString buddyProperty = u"buddy"_s; + const QByteArray oldNameU8 = old_name.toUtf8(); + const QByteArray newNameU8 = new_name.toUtf8(); + + for (QLabel *label : label_list) { + if (QDesignerPropertySheetExtension* sheet = + qt_extension(extensionManager, label)) { + const int idx = sheet->indexOf(buddyProperty); + if (idx != -1) { + const QByteArray oldBuddy = sheet->property(idx).toByteArray(); + if (oldBuddy == oldNameU8) + sheet->setProperty(idx, newNameU8); + } + } + } +} + +void QDesignerFormWindowCommand::selectUnmanagedObject(QObject *unmanagedObject) +{ + // Keep selection in sync + if (QDesignerObjectInspector *oi = qobject_cast(core()->objectInspector())) { + oi->clearSelection(); + oi->selectObject(unmanagedObject); + } + core()->propertyEditor()->setObject(unmanagedObject); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_formwindowcommand_p.h b/src/tools/designer/src/lib/shared/qdesigner_formwindowcommand_p.h new file mode 100644 index 00000000000..a2d2ad165fd --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_formwindowcommand_p.h @@ -0,0 +1,61 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_FORMWINDOWCOMMAND_H +#define QDESIGNER_FORMWINDOWCOMMAND_H + +#include "shared_global_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QDesignerPropertySheetExtension; + +namespace qdesigner_internal { + +class QDESIGNER_SHARED_EXPORT QDesignerFormWindowCommand: public QUndoCommand +{ + +public: + QDesignerFormWindowCommand(const QString &description, + QDesignerFormWindowInterface *formWindow, + QUndoCommand *parent = nullptr); + + void undo() override; + void redo() override; + + static void updateBuddies(QDesignerFormWindowInterface *form, + const QString &old_name, const QString &new_name); +protected: + QDesignerFormWindowInterface *formWindow() const; + QDesignerFormEditorInterface *core() const; + QDesignerPropertySheetExtension* propertySheet(QObject *object) const; + + void cheapUpdate(); + + void selectUnmanagedObject(QObject *unmanagedObject); +private: + QPointer m_formWindow; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_COMMAND_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_formwindowmanager.cpp b/src/tools/designer/src/lib/shared/qdesigner_formwindowmanager.cpp new file mode 100644 index 00000000000..449161993a3 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_formwindowmanager.cpp @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_formwindowmanager_p.h" +#include "plugindialog_p.h" + +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +/*! + \class qdesigner_internal::QDesignerFormWindowManager + \inmodule QtDesigner + \internal + + Extends QDesignerFormWindowManagerInterface with methods to control + the preview and printing of forms. It provides a facade that simplifies + the complexity of the more general PreviewConfiguration & PreviewManager + interfaces. + + \since 4.5 + */ + + +QDesignerFormWindowManager::QDesignerFormWindowManager(QObject *parent) + : QDesignerFormWindowManagerInterface(parent) +{ +} + +QDesignerFormWindowManager::~QDesignerFormWindowManager() = default; + +/*! + \fn PreviewManager *qdesigner_internal::QDesignerFormWindowManager::previewManager() const + + Accesses the previewmanager implementation. + + \since 4.5 + \internal + */ + +void QDesignerFormWindowManager::showPluginDialog() +{ + PluginDialog dlg(core(), core()->topLevel()); + dlg.exec(); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_formwindowmanager_p.h b/src/tools/designer/src/lib/shared/qdesigner_formwindowmanager_p.h new file mode 100644 index 00000000000..e4864d71029 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_formwindowmanager_p.h @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_FORMWINDOMANAGER_H +#define QDESIGNER_FORMWINDOMANAGER_H + +#include "shared_global_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +class PreviewManager; + +// +// Convenience methods to manage form previews (ultimately forwarded to PreviewManager). +// +class QDESIGNER_SHARED_EXPORT QDesignerFormWindowManager + : public QDesignerFormWindowManagerInterface +{ + Q_OBJECT +public: + explicit QDesignerFormWindowManager(QObject *parent = nullptr); + ~QDesignerFormWindowManager() override; + + virtual PreviewManager *previewManager() const = 0; + + void showPluginDialog() override; + +private: + void *m_unused = nullptr; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_FORMWINDOMANAGER_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_introspection.cpp b/src/tools/designer/src/lib/shared/qdesigner_introspection.cpp new file mode 100644 index 00000000000..60a4272d737 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_introspection.cpp @@ -0,0 +1,331 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_introspection_p.h" + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// Qt Implementation +static QStringList byteArrayListToStringList(const QByteArrayList &l) +{ + if (l.isEmpty()) + return QStringList(); + QStringList rc; + for (const QByteArray &b : l) + rc += QString::fromUtf8(b); + return rc; +} + +static inline QString charToQString(const char *c) +{ + if (!c) + return QString(); + return QString::fromUtf8(c); +} + +namespace { + // ------- QDesignerMetaEnum + class QDesignerMetaEnum : public QDesignerMetaEnumInterface { + public: + QDesignerMetaEnum(const QMetaEnum &qEnum); + bool isFlag() const override { return m_enum.isFlag(); } + QString key(int index) const override { return charToQString(m_enum.key(index)); } + int keyCount() const override { return m_enum.keyCount(); } + int keyToValue(const QString &key) const override { return m_enum.keyToValue(key.toUtf8()); } + int keysToValue(const QString &keys) const override { return m_enum.keysToValue(keys.toUtf8()); } + QString name() const override { return m_name; } + QString enumName() const override { return charToQString(m_enum.enumName()); } + QString scope() const override { return m_scope; } + QString separator() const override; + int value(int index) const override { return m_enum.value(index); } + QString valueToKey(int value) const override { return charToQString(m_enum.valueToKey(value)); } + QString valueToKeys(int value) const override { return charToQString(m_enum.valueToKeys(value)); } + + private: + const QMetaEnum m_enum; + const QString m_name; + const QString m_scope; + }; + + QDesignerMetaEnum::QDesignerMetaEnum(const QMetaEnum &qEnum) : + m_enum(qEnum), + m_name(charToQString(m_enum.name())), + m_scope(charToQString(m_enum.scope())) + { + } + + QString QDesignerMetaEnum::separator() const + { + return u"::"_s; + } + + // ------- QDesignerMetaProperty + class QDesignerMetaProperty : public QDesignerMetaPropertyInterface { + public: + QDesignerMetaProperty(const QMetaProperty &property); + ~QDesignerMetaProperty() override; + + const QDesignerMetaEnumInterface *enumerator() const override { return m_enumerator; } + + Kind kind() const override { return m_kind; } + + AccessFlags accessFlags() const override { return m_access; } + Attributes attributes() const override; + + int type() const override { return m_property.metaType().id(); } + QString name() const override { return m_name; } + QString typeName() const override { return m_typeName; } + int userType() const override { return m_property.userType(); } + bool hasSetter() const override { return m_property.hasStdCppSet(); } + + QVariant read(const QObject *object) const override { return m_property.read(object); } + bool reset(QObject *object) const override { return m_property.reset(object); } + bool write(QObject *object, const QVariant &value) const override { return m_property.write(object, value); } + + private: + const QMetaProperty m_property; + const QString m_name; + const QString m_typeName; + Kind m_kind; + AccessFlags m_access; + Attributes m_defaultAttributes; + QDesignerMetaEnumInterface *m_enumerator; + }; + + QDesignerMetaProperty::QDesignerMetaProperty(const QMetaProperty &property) : + m_property(property), + m_name(charToQString(m_property.name())), + m_typeName(charToQString(m_property.typeName())), + m_kind(OtherKind), + m_enumerator(nullptr) + { + if (m_property.isFlagType() || m_property.isEnumType()) { + const QMetaEnum metaEnum = m_property.enumerator(); + Q_ASSERT(metaEnum.isValid()); + m_enumerator = new QDesignerMetaEnum(metaEnum); + } + // kind + if (m_property.isFlagType()) + m_kind = FlagKind; + else + if (m_property.isEnumType()) + m_kind = EnumKind; + // flags and attributes + if (m_property.isReadable()) + m_access |= ReadAccess; + if (m_property.isWritable()) + m_access |= WriteAccess; + if (m_property.isResettable()) + m_access |= ResetAccess; + + if (m_property.isDesignable()) + m_defaultAttributes |= DesignableAttribute; + if (m_property.isScriptable()) + m_defaultAttributes |= ScriptableAttribute; + if (m_property.isStored()) + m_defaultAttributes |= StoredAttribute; + if (m_property.isUser()) + m_defaultAttributes |= UserAttribute; + } + + QDesignerMetaProperty::~QDesignerMetaProperty() + { + delete m_enumerator; + } + + QDesignerMetaProperty::Attributes QDesignerMetaProperty::attributes() const + { + return m_defaultAttributes; + } + + // -------------- QDesignerMetaMethod + + class QDesignerMetaMethod : public QDesignerMetaMethodInterface { + public: + QDesignerMetaMethod(const QMetaMethod &method); + + Access access() const override { return m_access; } + MethodType methodType() const override { return m_methodType; } + QStringList parameterNames() const override { return m_parameterNames; } + QStringList parameterTypes() const override { return m_parameterTypes; } + QString signature() const override { return m_signature; } + QString normalizedSignature() const override { return m_normalizedSignature; } + QString tag() const override { return m_tag; } + QString typeName() const override { return m_typeName; } + + private: + Access m_access; + MethodType m_methodType; + const QStringList m_parameterNames; + const QStringList m_parameterTypes; + const QString m_signature; + const QString m_normalizedSignature; + const QString m_tag; + const QString m_typeName; + }; + + QDesignerMetaMethod::QDesignerMetaMethod(const QMetaMethod &method) : + m_parameterNames(byteArrayListToStringList(method.parameterNames())), + m_parameterTypes(byteArrayListToStringList(method.parameterTypes())), + m_signature(QString::fromLatin1(method.methodSignature())), + m_normalizedSignature(QString::fromLatin1(QMetaObject::normalizedSignature(method.methodSignature().constData()))), + m_tag(charToQString(method.tag())), + m_typeName(charToQString(method.typeName())) + { + switch (method.access()) { + case QMetaMethod::Public: + m_access = Public; + break; + case QMetaMethod::Protected: + m_access = Protected; + break; + case QMetaMethod::Private: + m_access = Private; + break; + + } + switch (method.methodType()) { + case QMetaMethod::Constructor: + m_methodType = Constructor; + break; + case QMetaMethod::Method: + m_methodType = Method; + break; + case QMetaMethod::Signal: + m_methodType = Signal; + break; + + case QMetaMethod::Slot: + m_methodType = Slot; + break; + } + } + + // ------------- QDesignerMetaObject + class QDesignerMetaObject : public QDesignerMetaObjectInterface { + public: + QDesignerMetaObject(const qdesigner_internal::QDesignerIntrospection *introspection, const QMetaObject *metaObject); + ~QDesignerMetaObject() override; + + QString className() const override { return m_className; } + const QDesignerMetaEnumInterface *enumerator(int index) const override + { return m_enumerators[index]; } + int enumeratorCount() const override { return m_enumerators.size(); } + int enumeratorOffset() const override { return m_metaObject->enumeratorOffset(); } + + int indexOfEnumerator(const QString &name) const override + { return m_metaObject->indexOfEnumerator(name.toUtf8()); } + int indexOfMethod(const QString &method) const override + { return m_metaObject->indexOfMethod(method.toUtf8()); } + int indexOfProperty(const QString &name) const override + { return m_metaObject->indexOfProperty(name.toUtf8()); } + int indexOfSignal(const QString &signal) const override + { return m_metaObject->indexOfSignal(signal.toUtf8()); } + int indexOfSlot(const QString &slot) const override + { return m_metaObject->indexOfSlot(slot.toUtf8()); } + + const QDesignerMetaMethodInterface *method(int index) const override + { return m_methods[index]; } + int methodCount() const override { return m_methods.size(); } + int methodOffset() const override { return m_metaObject->methodOffset(); } + + const QDesignerMetaPropertyInterface *property(int index) const override + { return m_properties[index]; } + int propertyCount() const override { return m_properties.size(); } + int propertyOffset() const override { return m_metaObject->propertyOffset(); } + + const QDesignerMetaObjectInterface *superClass() const override; + const QDesignerMetaPropertyInterface *userProperty() const override + { return m_userProperty; } + + private: + const QString m_className; + const qdesigner_internal::QDesignerIntrospection *m_introspection; + const QMetaObject *m_metaObject; + + using Enumerators = QList; + Enumerators m_enumerators; + + using Methods = QList; + Methods m_methods; + + using Properties = QList; + Properties m_properties; + + QDesignerMetaPropertyInterface *m_userProperty; + }; + + QDesignerMetaObject::QDesignerMetaObject(const qdesigner_internal::QDesignerIntrospection *introspection, const QMetaObject *metaObject) : + m_className(charToQString(metaObject->className())), + m_introspection(introspection), + m_metaObject(metaObject), + m_userProperty(nullptr) + { + const int numEnumerators = metaObject->enumeratorCount(); + m_enumerators.reserve(numEnumerators); + for (int i = 0; i < numEnumerators; i++) + m_enumerators.push_back(new QDesignerMetaEnum(metaObject->enumerator(i))); + const int numMethods = metaObject->methodCount(); + m_methods.reserve(numMethods); + for (int i = 0; i < numMethods; i++) + m_methods.push_back(new QDesignerMetaMethod(metaObject->method(i))); + + const int numProperties = metaObject->propertyCount(); + m_properties.reserve(numProperties); + for (int i = 0; i < numProperties; i++) + m_properties.push_back(new QDesignerMetaProperty(metaObject->property(i))); + + const QMetaProperty userProperty = metaObject->userProperty(); + if (userProperty.isValid()) + m_userProperty = new QDesignerMetaProperty(userProperty); + } + + QDesignerMetaObject::~QDesignerMetaObject() + { + qDeleteAll(m_enumerators); + qDeleteAll(m_methods); + qDeleteAll(m_properties); + delete m_userProperty; + } + + const QDesignerMetaObjectInterface *QDesignerMetaObject::superClass() const + { + const QMetaObject *qSuperClass = m_metaObject->superClass(); + if (!qSuperClass) + return nullptr; + return m_introspection->metaObjectForQMetaObject(qSuperClass); + } + +} + +namespace qdesigner_internal { + + QDesignerIntrospection::QDesignerIntrospection() = default; + + QDesignerIntrospection::~QDesignerIntrospection() + { + qDeleteAll(m_metaObjectMap.values()); + } + + const QDesignerMetaObjectInterface* QDesignerIntrospection::metaObject(const QObject *object) const + { + return metaObjectForQMetaObject(object->metaObject()); + } + + const QDesignerMetaObjectInterface* QDesignerIntrospection::metaObjectForQMetaObject(const QMetaObject *metaObject) const + { + auto it = m_metaObjectMap.find(metaObject); + if (it == m_metaObjectMap.end()) + it = m_metaObjectMap.insert(metaObject, new QDesignerMetaObject(this, metaObject)); + return it.value(); + } +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_introspection_p.h b/src/tools/designer/src/lib/shared/qdesigner_introspection_p.h new file mode 100644 index 00000000000..1cdf0aa4f08 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_introspection_p.h @@ -0,0 +1,45 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DESIGNERINTROSPECTION +#define DESIGNERINTROSPECTION + +#include "shared_global_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +struct QMetaObject; +class QWidget; + +namespace qdesigner_internal { + // Qt C++ introspection with helpers to find core and meta object for an object + class QDESIGNER_SHARED_EXPORT QDesignerIntrospection : public QDesignerIntrospectionInterface { + public: + QDesignerIntrospection(); + ~QDesignerIntrospection() override; + + const QDesignerMetaObjectInterface* metaObject(const QObject *object) const override; + + const QDesignerMetaObjectInterface* metaObjectForQMetaObject(const QMetaObject *metaObject) const; + + private: + mutable QHash m_metaObjectMap; + }; +} + +QT_END_NAMESPACE + +#endif // DESIGNERINTROSPECTION diff --git a/src/tools/designer/src/lib/shared/qdesigner_membersheet.cpp b/src/tools/designer/src/lib/shared/qdesigner_membersheet.cpp new file mode 100644 index 00000000000..5451222feab --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_membersheet.cpp @@ -0,0 +1,212 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_membersheet_p.h" +#include "qdesigner_propertysheet_p.h" + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QByteArrayList stringListToByteArray(const QStringList &l) +{ + QByteArrayList rc; + for (const auto &s : l) + rc += s.toUtf8(); + return rc; +} + +// ------------ QDesignerMemberSheetPrivate +class QDesignerMemberSheetPrivate { +public: + explicit QDesignerMemberSheetPrivate(QObject *object, QObject *sheetParent); + + QDesignerFormEditorInterface *m_core; + const QDesignerMetaObjectInterface *m_meta; + + class Info { + public: + QString group; + bool visible{true}; + }; + + Info &ensureInfo(int index); + + QHash m_info; +}; + +QDesignerMemberSheetPrivate::QDesignerMemberSheetPrivate(QObject *object, QObject *sheetParent) : + m_core(QDesignerPropertySheet::formEditorForObject(sheetParent)), + m_meta(m_core->introspection()->metaObject(object)) +{ +} + +QDesignerMemberSheetPrivate::Info &QDesignerMemberSheetPrivate::ensureInfo(int index) +{ + auto it = m_info.find(index); + if (it == m_info.end()) + it = m_info.insert(index, Info()); + return it.value(); +} + +// --------- QDesignerMemberSheet + +QDesignerMemberSheet::QDesignerMemberSheet(QObject *object, QObject *parent) : + QObject(parent), + d(new QDesignerMemberSheetPrivate(object, parent)) +{ +} + +QDesignerMemberSheet::~QDesignerMemberSheet() +{ + delete d; +} + +int QDesignerMemberSheet::count() const +{ + return d->m_meta->methodCount(); +} + +int QDesignerMemberSheet::indexOf(const QString &name) const +{ + return d->m_meta->indexOfMethod(name); +} + +QString QDesignerMemberSheet::memberName(int index) const +{ + return d->m_meta->method(index)->tag(); +} + +QString QDesignerMemberSheet::declaredInClass(int index) const +{ + const QString member = d->m_meta->method(index)->signature(); + + // Find class whose superclass does not contain the method. + const QDesignerMetaObjectInterface *meta_obj = d->m_meta; + + for (;;) { + const QDesignerMetaObjectInterface *tmp = meta_obj->superClass(); + if (tmp == nullptr) + break; + if (tmp->indexOfMethod(member) == -1) + break; + meta_obj = tmp; + } + return meta_obj->className(); +} + +QString QDesignerMemberSheet::memberGroup(int index) const +{ + return d->m_info.value(index).group; +} + +void QDesignerMemberSheet::setMemberGroup(int index, const QString &group) +{ + d->ensureInfo(index).group = group; +} + +QString QDesignerMemberSheet::signature(int index) const +{ + return d->m_meta->method(index)->normalizedSignature(); +} + +bool QDesignerMemberSheet::isVisible(int index) const +{ + const auto it = d->m_info.constFind(index); + if (it != d->m_info.constEnd()) + return it.value().visible; + + return d->m_meta->method(index)->methodType() == QDesignerMetaMethodInterface::Signal + || d->m_meta->method(index)->access() == QDesignerMetaMethodInterface::Public; +} + +void QDesignerMemberSheet::setVisible(int index, bool visible) +{ + d->ensureInfo(index).visible = visible; +} + +bool QDesignerMemberSheet::isSignal(int index) const +{ + return d->m_meta->method(index)->methodType() == QDesignerMetaMethodInterface::Signal; +} + +bool QDesignerMemberSheet::isSlot(int index) const +{ + return d->m_meta->method(index)->methodType() == QDesignerMetaMethodInterface::Slot; +} + +bool QDesignerMemberSheet::inheritedFromWidget(int index) const +{ + return declaredInClass(index) == "QWidget"_L1 || declaredInClass(index) == "QObject"_L1; +} + + +QList QDesignerMemberSheet::parameterTypes(int index) const +{ + return stringListToByteArray(d->m_meta->method(index)->parameterTypes()); +} + +QList QDesignerMemberSheet::parameterNames(int index) const +{ + return stringListToByteArray(d->m_meta->method(index)->parameterNames()); +} + +bool QDesignerMemberSheet::signalMatchesSlot(const QString &signal, const QString &slot) +{ + bool result = true; + + do { + qsizetype signal_idx = signal.indexOf(u'('); + qsizetype slot_idx = slot.indexOf(u'('); + if (signal_idx == -1 || slot_idx == -1) + break; + + ++signal_idx; ++slot_idx; + + if (slot.at(slot_idx) == u')') + break; + + while (signal_idx < signal.size() && slot_idx < slot.size()) { + const QChar signal_c = signal.at(signal_idx); + const QChar slot_c = slot.at(slot_idx); + + if (signal_c == u',' && slot_c == u')') + break; + + if (signal_c == u')' && slot_c == u')') + break; + + if (signal_c != slot_c) { + result = false; + break; + } + + ++signal_idx; ++slot_idx; + } + } while (false); + + return result; +} + +// ------------ QDesignerMemberSheetFactory + +QDesignerMemberSheetFactory::QDesignerMemberSheetFactory(QExtensionManager *parent) + : QExtensionFactory(parent) +{ +} + +QObject *QDesignerMemberSheetFactory::createExtension(QObject *object, const QString &iid, QObject *parent) const +{ + if (iid == Q_TYPEID(QDesignerMemberSheetExtension)) { + return new QDesignerMemberSheet(object, parent); + } + + return nullptr; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_membersheet_p.h b/src/tools/designer/src/lib/shared/qdesigner_membersheet_p.h new file mode 100644 index 00000000000..b8df5e2fb0a --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_membersheet_p.h @@ -0,0 +1,79 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_MEMBERSHEET_H +#define QDESIGNER_MEMBERSHEET_H + +#include "shared_global_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerMemberSheetPrivate; + +class QDESIGNER_SHARED_EXPORT QDesignerMemberSheet: public QObject, public QDesignerMemberSheetExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerMemberSheetExtension) + +public: + explicit QDesignerMemberSheet(QObject *object, QObject *parent = nullptr); + ~QDesignerMemberSheet() override; + + int indexOf(const QString &name) const override; + + int count() const override; + QString memberName(int index) const override; + + QString memberGroup(int index) const override; + void setMemberGroup(int index, const QString &group) override; + + bool isVisible(int index) const override; + void setVisible(int index, bool b) override; + + bool isSignal(int index) const override; + bool isSlot(int index) const override; + + bool inheritedFromWidget(int index) const override; + + static bool signalMatchesSlot(const QString &signal, const QString &slot); + + QString declaredInClass(int index) const override; + + QString signature(int index) const override; + QList parameterTypes(int index) const override; + QList parameterNames(int index) const override; + +private: + QDesignerMemberSheetPrivate *d; +}; + +class QDESIGNER_SHARED_EXPORT QDesignerMemberSheetFactory: public QExtensionFactory +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionFactory) + +public: + QDesignerMemberSheetFactory(QExtensionManager *parent = nullptr); + +protected: + QObject *createExtension(QObject *object, const QString &iid, QObject *parent) const override; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_MEMBERSHEET_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_menu.cpp b/src/tools/designer/src/lib/shared/qdesigner_menu.cpp new file mode 100644 index 00000000000..c19f98dec5b --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_menu.cpp @@ -0,0 +1,1363 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_menu_p.h" +#include "qdesigner_menubar_p.h" +#include "qdesigner_toolbar_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "actionrepository_p.h" +#include "actionprovider_p.h" +#include "actioneditor_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_objectinspector_p.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +// give the user a little more space to click on the sub menu rectangle +static inline void extendClickableArea(QRect *subMenuRect, Qt::LayoutDirection dir) +{ + switch (dir) { + case Qt::LayoutDirectionAuto: // Should never happen + case Qt::LeftToRight: + subMenuRect->setLeft(subMenuRect->left() - 20); + break; + case Qt::RightToLeft: + subMenuRect->setRight(subMenuRect->right() + 20); + break; + } +} + +QDesignerMenu::QDesignerMenu(QWidget *parent) : + QMenu(parent), + m_subMenuPixmap(QPixmap(u":/qt-project.org/formeditor/images/submenu.png"_s)), + m_currentIndex(0), + m_addItem(new qdesigner_internal::SpecialMenuAction(this)), + m_addSeparator(new qdesigner_internal::SpecialMenuAction(this)), + m_showSubMenuTimer(new QTimer(this)), + m_deactivateWindowTimer(new QTimer(this)), + m_adjustSizeTimer(new QTimer(this)), + m_editor(new QLineEdit(this)), + m_dragging(false), + m_lastSubMenuIndex(-1) +{ + setContextMenuPolicy(Qt::DefaultContextMenu); + setAcceptDrops(true); // ### fake + setSeparatorsCollapsible(false); + + connect(m_adjustSizeTimer, &QTimer::timeout, this, &QDesignerMenu::slotAdjustSizeNow); + m_addItem->setText(tr("Type Here")); + addAction(m_addItem); + + m_addSeparator->setText(tr("Add Separator")); + addAction(m_addSeparator); + + connect(m_showSubMenuTimer, &QTimer::timeout, this, &QDesignerMenu::slotShowSubMenuNow); + + connect(m_deactivateWindowTimer, &QTimer::timeout, this, &QDesignerMenu::slotDeactivateNow); + + m_editor->setObjectName(u"__qt__passive_editor"_s); + m_editor->hide(); + + m_editor->installEventFilter(this); + installEventFilter(this); +} + +QDesignerMenu::~QDesignerMenu() = default; + +void QDesignerMenu::slotAdjustSizeNow() +{ + // Not using a single-shot, since we want to compress the timers if many items are being + // adjusted + m_adjustSizeTimer->stop(); + adjustSize(); +} + +bool QDesignerMenu::handleEvent(QWidget *widget, QEvent *event) +{ + if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) { + update(); + + if (widget == m_editor) + return false; + } + + switch (event->type()) { + default: break; + + case QEvent::MouseButtonPress: + return handleMousePressEvent(widget, static_cast(event)); + case QEvent::MouseButtonRelease: + return handleMouseReleaseEvent(widget, static_cast(event)); + case QEvent::MouseButtonDblClick: + return handleMouseDoubleClickEvent(widget, static_cast(event)); + case QEvent::MouseMove: + return handleMouseMoveEvent(widget, static_cast(event)); + case QEvent::ContextMenu: + return handleContextMenuEvent(widget, static_cast(event)); + case QEvent::KeyPress: + return handleKeyPressEvent(widget, static_cast(event)); + } + + return true; +} + +void QDesignerMenu::startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers) +{ + using namespace qdesigner_internal; + + const int index = findAction(pos); + if (index >= realActionCount()) + return; + + QAction *action = safeActionAt(index); + + QDesignerFormWindowInterface *fw = formWindow(); + const Qt::DropAction dropAction = (modifiers & Qt::ControlModifier) ? Qt::CopyAction : Qt::MoveAction; + if (dropAction == Qt::MoveAction) { + auto *cmd = new RemoveActionFromCommand(fw); + cmd->init(this, action, actions().at(index + 1)); + fw->commandHistory()->push(cmd); + } + + QDrag *drag = new QDrag(this); + drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(action)); + drag->setMimeData(new ActionRepositoryMimeData(action, dropAction)); + + const int old_index = m_currentIndex; + m_currentIndex = -1; + + if (drag->exec(dropAction) == Qt::IgnoreAction) { + if (dropAction == Qt::MoveAction) { + QAction *previous = safeActionAt(index); + auto *cmd = new InsertActionIntoCommand(fw); + cmd->init(this, action, previous); + fw->commandHistory()->push(cmd); + } + + m_currentIndex = old_index; + } +} + +bool QDesignerMenu::handleKeyPressEvent(QWidget * /*widget*/, QKeyEvent *e) +{ + m_showSubMenuTimer->stop(); + + if (m_editor->isHidden() && hasFocus()) { // In navigation mode + switch (e->key()) { + + case Qt::Key_Delete: + if (m_currentIndex == -1 || m_currentIndex >= realActionCount()) + break; + hideSubMenu(); + deleteAction(); + break; + + case Qt::Key_Left: + e->accept(); + moveLeft(); + return true; + + case Qt::Key_Right: + e->accept(); + moveRight(); + return true; // no update + + case Qt::Key_Up: + e->accept(); + moveUp(e->modifiers() & Qt::ControlModifier); + return true; + + case Qt::Key_Down: + e->accept(); + moveDown(e->modifiers() & Qt::ControlModifier); + return true; + + case Qt::Key_PageUp: + m_currentIndex = 0; + break; + + case Qt::Key_PageDown: + m_currentIndex = actions().size() - 1; + break; + + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_F2: + e->accept(); + enterEditMode(); + return true; // no update + + case Qt::Key_Escape: + e->ignore(); + setFocus(); + hide(); + closeMenuChain(); + return true; + + case Qt::Key_Alt: + case Qt::Key_Shift: + case Qt::Key_Control: + e->ignore(); + setFocus(); // FIXME: this is because some other widget get the focus when CTRL is pressed + return true; // no update + + default: { + QAction *action = currentAction(); + if (!action || action->isSeparator() || action == m_addSeparator) { + e->ignore(); + return true; + } + if (!e->text().isEmpty() && e->text().at(0).toLatin1() >= 32) { + showLineEdit(); + QApplication::sendEvent(m_editor, e); + e->accept(); + } else { + e->ignore(); + } + } + return true; + } + } else if (m_editor->hasFocus()) { // In edit mode + switch (e->key()) { + default: + e->ignore(); + return false; + + case Qt::Key_Enter: + case Qt::Key_Return: + if (!m_editor->text().isEmpty()) { + leaveEditMode(ForceAccept); + m_editor->hide(); + setFocus(); + moveDown(false); + break; + } + Q_FALLTHROUGH(); + + case Qt::Key_Escape: + m_editor->hide(); + setFocus(); + break; + } + } + + e->accept(); + update(); + + return true; +} + +static void sendMouseEventTo(QWidget *target, const QPoint &targetPoint, const QMouseEvent *event) +{ + QMouseEvent e(event->type(), targetPoint, event->globalPosition().toPoint(), event->button(), event->buttons(), event->modifiers()); + QApplication::sendEvent(target, &e); +} + +bool QDesignerMenu::handleMouseDoubleClickEvent(QWidget *, QMouseEvent *event) +{ + event->accept(); + m_startPosition = QPoint(); + + if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) + return true; + + if (!rect().contains(event->position().toPoint())) { + // special case for menubar + QWidget *target = QApplication::widgetAt(event->globalPosition().toPoint()); + QMenuBar *mb = qobject_cast(target); + QDesignerMenu *menu = qobject_cast(target); + if (mb != nullptr || menu != nullptr) { + const QPoint pt = target->mapFromGlobal(event->globalPosition().toPoint()); + QAction *action = mb == nullptr ? menu->actionAt(pt) : mb->actionAt(pt); + if (action) + sendMouseEventTo(target, pt, event); + } + return true; + } + + m_currentIndex = findAction(event->position().toPoint()); + QAction *action = safeActionAt(m_currentIndex); + + QRect pm_rect; + if (action->menu() || hasSubMenuPixmap(action)) { + pm_rect = subMenuPixmapRect(action); + extendClickableArea(&pm_rect, layoutDirection()); + } + + if (!pm_rect.contains(event->position().toPoint()) && m_currentIndex != -1) + enterEditMode(); + + return true; +} + +bool QDesignerMenu::handleMousePressEvent(QWidget * /*widget*/, QMouseEvent *event) +{ + if (!rect().contains(event->position().toPoint())) { + QWidget *clickedWidget = QApplication::widgetAt(event->globalPosition().toPoint()); + if (QMenuBar *mb = qobject_cast(clickedWidget)) { + const QPoint pt = mb->mapFromGlobal(event->globalPosition().toPoint()); + if (QAction *action = mb->actionAt(pt)) { + QMenu * menu = action->menu(); + if (menu == findRootMenu()) { + // propagate the mouse press event (but don't close the popup) + sendMouseEventTo(mb, pt, event); + return true; + } + } + } + + if (QDesignerMenu *m = qobject_cast(clickedWidget)) { + m->hideSubMenu(); + sendMouseEventTo(m, m->mapFromGlobal(event->globalPosition().toPoint()), event); + } else { + QDesignerMenu *root = findRootMenu(); + root->hide(); + root->hideSubMenu(); + } + if (clickedWidget) { + if (QWidget *focusProxy = clickedWidget->focusProxy()) + clickedWidget = focusProxy; + if (clickedWidget->focusPolicy() != Qt::NoFocus) + clickedWidget->setFocus(Qt::OtherFocusReason); + } + return true; + } + + m_showSubMenuTimer->stop(); + m_startPosition = QPoint(); + event->accept(); + + if (event->button() != Qt::LeftButton) + return true; + + m_startPosition = mapFromGlobal(event->globalPosition().toPoint()); + + const int index = findAction(m_startPosition); + + QAction *action = safeActionAt(index); + QRect pm_rect = subMenuPixmapRect(action); + extendClickableArea(&pm_rect, layoutDirection()); + + const int old_index = m_currentIndex; + m_currentIndex = index; + if ((hasSubMenuPixmap(action) || action->menu() != nullptr) + && pm_rect.contains(m_startPosition)) { + if (m_currentIndex == m_lastSubMenuIndex) { + hideSubMenu(); + } else + slotShowSubMenuNow(); + } else { + if (index == old_index) { + if (m_currentIndex == m_lastSubMenuIndex) + hideSubMenu(); + } else { + hideSubMenu(); + } + } + + update(); + if (index != old_index) + selectCurrentAction(); + + return true; +} + +bool QDesignerMenu::handleMouseReleaseEvent(QWidget *, QMouseEvent *event) +{ + event->accept(); + m_startPosition = QPoint(); + + return true; +} + +bool QDesignerMenu::handleMouseMoveEvent(QWidget *, QMouseEvent *event) +{ + if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) + return true; + + if (!rect().contains(event->position().toPoint())) { + + if (QMenuBar *mb = qobject_cast(QApplication::widgetAt(event->globalPosition().toPoint()))) { + const QPoint pt = mb->mapFromGlobal(event->globalPosition().toPoint()); + QAction *action = mb->actionAt(pt); + if (action && action->menu() == findRootMenu()) { + // propagate the mouse press event (but don't close the popup) + sendMouseEventTo(mb, pt, event); + return true; + } + // hide the popup Qt will replay the event + slotDeactivateNow(); + } + return true; + } + + if (m_startPosition.isNull()) + return true; + + event->accept(); + + const QPoint pos = mapFromGlobal(event->globalPosition().toPoint()); + + if ((pos - m_startPosition).manhattanLength() < qApp->startDragDistance()) + return true; + + startDrag(m_startPosition, event->modifiers()); + m_startPosition = QPoint(); + + return true; +} + +bool QDesignerMenu::handleContextMenuEvent(QWidget *, QContextMenuEvent *event) +{ + event->accept(); + + const int index = findAction(mapFromGlobal(event->globalPos())); + QAction *action = safeActionAt(index); + if (qobject_cast(action)) + return true; + + QMenu menu; + QVariant itemData; + itemData.setValue(action); + + QAction *addSeparatorAction = menu.addAction(tr("Insert separator")); + addSeparatorAction->setData(itemData); + + QAction *removeAction = nullptr; + if (action->isSeparator()) + removeAction = menu.addAction(tr("Remove separator")); + else + removeAction = menu.addAction(tr("Remove action '%1'").arg(action->objectName())); + removeAction->setData(itemData); + + connect(addSeparatorAction, &QAction::triggered, this, &QDesignerMenu::slotAddSeparator); + connect(removeAction, &QAction::triggered, this, &QDesignerMenu::slotRemoveSelectedAction); + menu.exec(event->globalPos()); + + return true; +} + +void QDesignerMenu::slotAddSeparator() +{ + QAction *action = qobject_cast(sender()); + if (!action) + return; + + QAction *a = qvariant_cast(action->data()); + Q_ASSERT(a != nullptr); + + const int pos = actions().indexOf(a); + QAction *action_before = nullptr; + if (pos != -1) + action_before = safeActionAt(pos); + + QDesignerFormWindowInterface *fw = formWindow(); + fw->beginCommand(tr("Add separator")); + QAction *sep = createAction(QString(), true); + + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, sep, action_before); + fw->commandHistory()->push(cmd); + + if (parentMenu()) { + QAction *parent_action = parentMenu()->currentAction(); + if (parent_action->menu() == nullptr) { + auto *cmd = new qdesigner_internal::CreateSubmenuCommand(fw); + cmd->init(parentMenu(), parentMenu()->currentAction()); + fw->commandHistory()->push(cmd); + } + } + + fw->endCommand(); +} + +void QDesignerMenu::slotRemoveSelectedAction() +{ + if (QAction *action = qobject_cast(sender())) + if (QAction *a = qvariant_cast(action->data())) + deleteAction(a); +} + +void QDesignerMenu::deleteAction(QAction *a) +{ + const int pos = actions().indexOf(a); + QAction *action_before = nullptr; + if (pos != -1) + action_before = safeActionAt(pos + 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd->init(this, a, action_before); + fw->commandHistory()->push(cmd); +} + +QRect QDesignerMenu::subMenuPixmapRect(QAction *action) const +{ + const QRect g = actionGeometry(action); + const int x = layoutDirection() == Qt::LeftToRight ? (g.right() - m_subMenuPixmap.width() - 2) : 2; + const int y = g.top() + (g.height() - m_subMenuPixmap.height())/2 + 1; + return QRect(x, y, m_subMenuPixmap.width(), m_subMenuPixmap.height()); +} + +bool QDesignerMenu::hasSubMenuPixmap(QAction *action) const +{ + return action != nullptr + && qobject_cast(action) == nullptr + && !action->isSeparator() + && !action->menu() + && canCreateSubMenu(action); +} + +void QDesignerMenu::showEvent ( QShowEvent * event ) +{ + selectCurrentAction(); + QMenu::showEvent (event); +} + +void QDesignerMenu::paintEvent(QPaintEvent *event) +{ + using namespace qdesigner_internal; + + QMenu::paintEvent(event); + + QPainter p(this); + + QAction *current = currentAction(); + + const auto &actionList = actions(); + for (QAction *a : actionList) { + const QRect g = actionGeometry(a); + + if (qobject_cast(a)) { + QLinearGradient lg(g.left(), g.top(), g.left(), g.bottom()); + lg.setColorAt(0.0, Qt::transparent); + lg.setColorAt(0.7, QColor(0, 0, 0, 32)); + lg.setColorAt(1.0, Qt::transparent); + + p.fillRect(g, lg); + } else if (hasSubMenuPixmap(a)) { + p.drawPixmap(subMenuPixmapRect(a).topLeft(), m_subMenuPixmap); + } + } + + if (!hasFocus() || !current || m_dragging) + return; + + if (QDesignerMenu *menu = parentMenu()) { + if (menu->dragging()) + return; + } + + if (QDesignerMenuBar *menubar = qobject_cast(parentWidget())) { + if (menubar->dragging()) + return; + } + + const QRect g = actionGeometry(current); + drawSelection(&p, g.adjusted(1, 1, -3, -3)); +} + +bool QDesignerMenu::dragging() const +{ + return m_dragging; +} + +QDesignerMenu *QDesignerMenu::findRootMenu() const +{ + if (parentMenu()) + return parentMenu()->findRootMenu(); + + return const_cast(this); +} + +QDesignerMenu *QDesignerMenu::findActivatedMenu() const +{ + if (QDesignerMenu *activeDesignerMenu = qobject_cast(QApplication::activeWindow())) { + if (activeDesignerMenu == this || findChildren().contains(activeDesignerMenu)) + return activeDesignerMenu; + } + + return nullptr; +} + +bool QDesignerMenu::eventFilter(QObject *object, QEvent *event) +{ + if (object != this && object != m_editor) { + return false; + } + + if (!m_editor->isHidden() && object == m_editor && event->type() == QEvent::FocusOut) { + leaveEditMode(Default); + m_editor->hide(); + update(); + return false; + } + + bool dispatch = true; + + switch (event->type()) { + default: break; + + case QEvent::WindowDeactivate: + deactivateMenu(); + break; + case QEvent::ContextMenu: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + + while (QApplication::activePopupWidget() && !qobject_cast(QApplication::activePopupWidget())) { + QApplication::activePopupWidget()->close(); + } + + Q_FALLTHROUGH(); // fall through + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::MouseMove: + dispatch = (object != m_editor); + Q_FALLTHROUGH(); // no break + + case QEvent::Enter: + case QEvent::Leave: + case QEvent::FocusIn: + case QEvent::FocusOut: + if (dispatch) + if (QWidget *widget = qobject_cast(object)) + if (widget == this || isAncestorOf(widget)) + return handleEvent(widget, event); + break; + } + + return false; +}; + +int QDesignerMenu::findAction(const QPoint &pos) const +{ + const int index = actionIndexAt(this, pos, Qt::Vertical); + if (index == -1) + return realActionCount(); + + return index; +} + +void QDesignerMenu::adjustIndicator(const QPoint &pos) +{ + if (QDesignerActionProviderExtension *a = actionProvider()) { + a->adjustIndicator(pos); + } +} + +QDesignerMenu::ActionDragCheck QDesignerMenu::checkAction(QAction *action) const +{ + if (!action || (action->menu() && action->menu()->parentWidget() != const_cast(this))) + return NoActionDrag; // menu action!! nothing to do + + if (!qdesigner_internal::Utils::isObjectAncestorOf(formWindow()->mainContainer(), action)) + return NoActionDrag; // the action belongs to another form window + + if (actions().contains(action)) + return ActionDragOnSubMenu; // we already have the action in the menu + + return AcceptActionDrag; +} + +void QDesignerMenu::dragEnterEvent(QDragEnterEvent *event) +{ + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + + QAction *action = d->actionList().first(); + + switch (checkAction(action)) { + case NoActionDrag: + event->ignore(); + break; + case ActionDragOnSubMenu: + d->accept(event); + m_dragging = true; + break; + case AcceptActionDrag: + d->accept(event); + m_dragging = true; + adjustIndicator(event->position().toPoint()); + break; + } +} + +void QDesignerMenu::dragMoveEvent(QDragMoveEvent *event) +{ + if (actionGeometry(m_addSeparator).contains(event->position().toPoint())) { + event->ignore(); + adjustIndicator(QPoint(-1, -1)); + return; + } + + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + + QAction *action = d->actionList().first(); + const ActionDragCheck dc = checkAction(action); + switch (dc) { + case NoActionDrag: + event->ignore(); + break; + case ActionDragOnSubMenu: + case AcceptActionDrag: { // Do not pop up submenu of action being dragged + const int newIndex = findAction(event->position().toPoint()); + if (safeActionAt(newIndex) != action) { + m_currentIndex = newIndex; + if (m_lastSubMenuIndex != m_currentIndex) + m_showSubMenuTimer->start(300); + } + if (dc == AcceptActionDrag) { + adjustIndicator(event->position().toPoint()); + d->accept(event); + } else { + event->ignore(); + } + } + break; + } +} + +void QDesignerMenu::dragLeaveEvent(QDragLeaveEvent *) +{ + m_dragging = false; + adjustIndicator(QPoint(-1, -1)); + m_showSubMenuTimer->stop(); +} + +void QDesignerMenu::dropEvent(QDropEvent *event) +{ + m_showSubMenuTimer->stop(); + hideSubMenu(); + m_dragging = false; + + QDesignerFormWindowInterface *fw = formWindow(); + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + QAction *action = d->actionList().first(); + if (action && checkAction(action) == AcceptActionDrag) { + event->acceptProposedAction(); + int index = findAction(event->position().toPoint()); + index = qMin(index, actions().size() - 1); + + fw->beginCommand(tr("Insert action")); + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, action, safeActionAt(index)); + fw->commandHistory()->push(cmd); + + m_currentIndex = index; + + if (parentMenu()) { + QAction *parent_action = parentMenu()->currentAction(); + if (parent_action->menu() == nullptr) { + auto *cmd = new qdesigner_internal::CreateSubmenuCommand(fw); + cmd->init(parentMenu(), parentMenu()->currentAction(), action); + fw->commandHistory()->push(cmd); + } + } + update(); + fw->endCommand(); + } else { + event->ignore(); + } + adjustIndicator(QPoint(-1, -1)); +} + +void QDesignerMenu::actionEvent(QActionEvent *event) +{ + QMenu::actionEvent(event); + m_adjustSizeTimer->start(0); +} + +QDesignerFormWindowInterface *QDesignerMenu::formWindow() const +{ + if (parentMenu()) + return parentMenu()->formWindow(); + + return QDesignerFormWindowInterface::findFormWindow(parentWidget()); +} + +QDesignerActionProviderExtension *QDesignerMenu::actionProvider() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QDesignerFormEditorInterface *core = fw->core(); + return qt_extension(core->extensionManager(), this); + } + + return nullptr; +} + +void QDesignerMenu::closeMenuChain() +{ + m_showSubMenuTimer->stop(); + + QWidget *w = this; + while (w && qobject_cast(w)) + w = w->parentWidget(); + + if (w) { + const auto &menus = w->findChildren(); + for (QMenu *subMenu : menus) + subMenu->hide(); + } + + m_lastSubMenuIndex = -1; +} + +// Close submenu using the left/right keys according to layoutDirection(). +// Return false to indicate the event must be propagated to the menu bar. +bool QDesignerMenu::hideSubMenuOnCursorKey() +{ + if (parentMenu()) { + hide(); + return true; + } + closeMenuChain(); + update(); + return parentMenuBar() == nullptr; +} + +// Open a submenu using the left/right keys according to layoutDirection(). +// Return false to indicate the event must be propagated to the menu bar. +bool QDesignerMenu::showSubMenuOnCursorKey() +{ + using namespace qdesigner_internal; + + const QAction *action = currentAction(); + + if (qobject_cast(action) || action->isSeparator()) { + closeMenuChain(); + if (parentMenuBar()) + return false; + return true; + } + m_lastSubMenuIndex = -1; // force a refresh + slotShowSubMenuNow(); + return true; +} + +void QDesignerMenu::moveLeft() +{ + const bool handled = layoutDirection() == Qt::LeftToRight ? + hideSubMenuOnCursorKey() : showSubMenuOnCursorKey(); + if (!handled) + parentMenuBar()->moveLeft(); +} + +void QDesignerMenu::moveRight() +{ + const bool handled = layoutDirection() == Qt::LeftToRight ? + showSubMenuOnCursorKey() : hideSubMenuOnCursorKey(); + if (!handled) + parentMenuBar()->moveRight(); +} + +void QDesignerMenu::moveUp(bool ctrl) +{ + if (m_currentIndex == 0) { + hide(); + return; + } + + if (ctrl) + (void) swap(m_currentIndex, m_currentIndex - 1); + --m_currentIndex; + m_currentIndex = qMax(0, m_currentIndex); + // Always re-select, swapping destroys order + update(); + selectCurrentAction(); +} + +void QDesignerMenu::moveDown(bool ctrl) +{ + if (m_currentIndex == actions().size() - 1) { + return; + } + + if (ctrl) + (void) swap(m_currentIndex + 1, m_currentIndex); + + ++m_currentIndex; + m_currentIndex = qMin(actions().size() - 1, m_currentIndex); + update(); + if (!ctrl) + selectCurrentAction(); +} + +QAction *QDesignerMenu::currentAction() const +{ + if (m_currentIndex < 0 || m_currentIndex >= actions().size()) + return nullptr; + + return safeActionAt(m_currentIndex); +} + +int QDesignerMenu::realActionCount() const +{ + return actions().size() - 2; // 2 fake actions +} + +void QDesignerMenu::selectCurrentAction() +{ + using namespace qdesigner_internal; + + QAction *action = currentAction(); + if (!action || action == m_addSeparator || action == m_addItem) + return; + + QDesignerObjectInspector *oi = nullptr; + ActionEditor *ae = nullptr; + if (QDesignerFormWindowInterface *fw = formWindow()) { + auto core = fw->core(); + oi = qobject_cast(core->objectInspector()); + ae = qobject_cast(core->actionEditor()); + } + + if (!oi) + return; + + oi->clearSelection(); + if (QMenu *menu = action->menu()) { + oi->selectObject(menu); + if (ae) + ae->clearSelection(); + } else { + oi->selectObject(action); + if (ae) + ae->selectAction(action); + } +} + +void QDesignerMenu::createRealMenuAction(QAction *action) +{ + using namespace qdesigner_internal; + + if (action->menu()) + return; // nothing to do + + QDesignerFormWindowInterface *fw = formWindow(); + QDesignerFormEditorInterface *core = formWindow()->core(); + + QDesignerMenu *menu = findOrCreateSubMenu(action); + m_subMenus.remove(action); + + action->setMenu(menu); + menu->setTitle(action->text()); + + Q_ASSERT(fw); + + core->widgetFactory()->initialize(menu); + + const QString niceObjectName = ActionEditor::actionTextToName(menu->title(), u"menu"_s); + menu->setObjectName(niceObjectName); + + core->metaDataBase()->add(menu); + fw->ensureUniqueObjectName(menu); + + QAction *menuAction = menu->menuAction(); + core->metaDataBase()->add(menuAction); +} + +void QDesignerMenu::removeRealMenu(QAction *action) +{ + QDesignerMenu *menu = qobject_cast(action->menu()); + if (menu == nullptr) + return; + action->setMenu(nullptr); + m_subMenus.insert(action, menu); + QDesignerFormEditorInterface *core = formWindow()->core(); + core->metaDataBase()->remove(menu); +} + +QDesignerMenu *QDesignerMenu::findOrCreateSubMenu(QAction *action) +{ + if (action->menu()) + return qobject_cast(action->menu()); + + QDesignerMenu *menu = m_subMenus.value(action); + if (!menu) { + menu = new QDesignerMenu(this); + m_subMenus.insert(action, menu); + } + + return menu; +} + +bool QDesignerMenu::canCreateSubMenu(QAction *action) const // ### improve it's a bit too slow +{ + const QObjectList associatedObjects = action->associatedObjects(); + for (const QObject *ao : associatedObjects) { + if (ao != this) { + if (const QMenu *m = qobject_cast(ao)) { + if (m->actions().contains(action)) + return false; // sorry + } else if (const QToolBar *tb = qobject_cast(ao)) { + if (tb->actions().contains(action)) + return false; // sorry + } + } + } + return true; +} + +void QDesignerMenu::slotShowSubMenuNow() +{ + m_showSubMenuTimer->stop(); + + if (m_lastSubMenuIndex == m_currentIndex) + return; + + if (m_lastSubMenuIndex != -1) + hideSubMenu(); + + if (m_currentIndex >= realActionCount()) + return; + + QAction *action = currentAction(); + + if (action->isSeparator() || !canCreateSubMenu(action)) + return; + + if (QMenu *menu = findOrCreateSubMenu(action)) { + if (!menu->isVisible()) { + if ((menu->windowFlags() & Qt::Popup) != Qt::Popup) + menu->setWindowFlags(Qt::Popup); + const QRect g = actionGeometry(action); + if (layoutDirection() == Qt::LeftToRight) { + menu->move(mapToGlobal(g.topRight())); + } else { + // The position is not initially correct due to the unknown width, + // causing it to overlap a bit the first time it is invoked. + QPoint point = g.topLeft() - QPoint(menu->width() + 10, 0); + menu->move(mapToGlobal(point)); + } + menu->show(); + menu->setFocus(); + } else { + menu->raise(); + } + menu->setFocus(); + + m_lastSubMenuIndex = m_currentIndex; + } +} + +void QDesignerMenu::showSubMenu(QAction *action) +{ + using namespace qdesigner_internal; + + m_showSubMenuTimer->stop(); + + if (m_editor->isVisible() || !action || qobject_cast(action) + || action->isSeparator() || !isVisible()) + return; + + m_showSubMenuTimer->start(300); +} + +QDesignerMenu *QDesignerMenu::parentMenu() const +{ + return qobject_cast(parentWidget()); +} + +QDesignerMenuBar *QDesignerMenu::parentMenuBar() const +{ + if (QDesignerMenuBar *mb = qobject_cast(parentWidget())) + return mb; + if (QDesignerMenu *m = parentMenu()) + return m->parentMenuBar(); + + return nullptr; +} + +void QDesignerMenu::setVisible(bool visible) +{ + if (visible) + m_currentIndex = 0; + else + m_lastSubMenuIndex = -1; + + QMenu::setVisible(visible); + +} + +void QDesignerMenu::adjustSpecialActions() +{ + removeAction(m_addItem); + removeAction(m_addSeparator); + addAction(m_addItem); + addAction(m_addSeparator); +} + +void QDesignerMenu::enterEditMode() +{ + if (m_currentIndex >= 0 && m_currentIndex <= realActionCount()) { + showLineEdit(); + } else { + hideSubMenu(); + QDesignerFormWindowInterface *fw = formWindow(); + fw->beginCommand(tr("Add separator")); + QAction *sep = createAction(QString(), true); + + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, sep, safeActionAt(realActionCount())); + fw->commandHistory()->push(cmd); + + if (parentMenu()) { + QAction *parent_action = parentMenu()->currentAction(); + if (parent_action->menu() == nullptr) { + auto *cmd = new qdesigner_internal::CreateSubmenuCommand(fw); + cmd->init(parentMenu(), parentMenu()->currentAction()); + fw->commandHistory()->push(cmd); + } + } + + fw->endCommand(); + + m_currentIndex = actions().indexOf(m_addItem); + update(); + } +} + +void QDesignerMenu::leaveEditMode(LeaveEditMode mode) +{ + using namespace qdesigner_internal; + + if (mode == Default) + return; + + QAction *action = nullptr; + + QDesignerFormWindowInterface *fw = formWindow(); + if (m_currentIndex < realActionCount()) { + action = safeActionAt(m_currentIndex); + fw->beginCommand(QApplication::translate("Command", "Set action text")); + } else { + Q_ASSERT(fw != nullptr); + fw->beginCommand(QApplication::translate("Command", "Insert action")); + action = createAction(ActionEditor::actionTextToName(m_editor->text())); + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, action, currentAction()); + fw->commandHistory()->push(cmd); + } + + auto *cmd = new qdesigner_internal::SetPropertyCommand(fw); + cmd->init(action, u"text"_s, m_editor->text()); + fw->commandHistory()->push(cmd); + + if (parentMenu()) { + QAction *parent_action = parentMenu()->currentAction(); + if (parent_action->menu() == nullptr) { + auto *cmd = new qdesigner_internal::CreateSubmenuCommand(fw); + cmd->init(parentMenu(), parentMenu()->currentAction(), action); + fw->commandHistory()->push(cmd); + } + } + + update(); + fw->endCommand(); +} + +QAction *QDesignerMenu::safeMenuAction(QDesignerMenu *menu) const +{ + QAction *action = menu->menuAction(); + + if (!action) + action = m_subMenus.key(menu); + + return action; +} + +void QDesignerMenu::showLineEdit() +{ + m_showSubMenuTimer->stop(); + + QAction *action = nullptr; + + if (m_currentIndex < realActionCount()) + action = safeActionAt(m_currentIndex); + else + action = m_addItem; + + if (action->isSeparator()) + return; + + hideSubMenu(); + + // open edit field for item name + setFocus(); + + const QString text = action != m_addItem ? action->text() : QString(); + m_editor->setText(text); + m_editor->selectAll(); + m_editor->setGeometry(actionGeometry(action).adjusted(1, 1, -2, -2)); + m_editor->show(); + m_editor->setFocus(); +} + +QAction *QDesignerMenu::createAction(const QString &objectName, bool separator) +{ + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw); + return qdesigner_internal::ToolBarEventFilter::createAction(fw, objectName, separator); +} + +// ### share with QDesignerMenu::swap +bool QDesignerMenu::swap(int a, int b) +{ + using namespace qdesigner_internal; + + const int left = qMin(a, b); + int right = qMax(a, b); + + QAction *action_a = safeActionAt(left); + QAction *action_b = safeActionAt(right); + + if (action_a == action_b + || !action_a + || !action_b + || qobject_cast(action_a) + || qobject_cast(action_b)) + return false; // nothing to do + + right = qMin(right, realActionCount()); + if (right < 0) + return false; // nothing to do + + QDesignerFormWindowInterface *fw = formWindow(); + fw->beginCommand(QApplication::translate("Command", "Move action")); + + QAction *action_b_before = safeActionAt(right + 1); + + auto *cmd1 = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd1->init(this, action_b, action_b_before, false); + fw->commandHistory()->push(cmd1); + + QAction *action_a_before = safeActionAt(left + 1); + + auto *cmd2 = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd2->init(this, action_b, action_a_before, false); + fw->commandHistory()->push(cmd2); + + auto *cmd3 = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd3->init(this, action_a, action_b, false); + fw->commandHistory()->push(cmd3); + + auto *cmd4 = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd4->init(this, action_a, action_b_before, true); + fw->commandHistory()->push(cmd4); + + fw->endCommand(); + + return true; +} + +QAction *QDesignerMenu::safeActionAt(int index) const +{ + if (index < 0 || index >= actions().size()) + return nullptr; + + return actions().at(index); +} + +void QDesignerMenu::hideSubMenu() +{ + m_lastSubMenuIndex = -1; + const auto &menus = findChildren(); + for (QMenu *subMenu : menus) + subMenu->hide(); +} + +void QDesignerMenu::deleteAction() +{ + QAction *action = currentAction(); + const int pos = actions().indexOf(action); + QAction *action_before = nullptr; + if (pos != -1) + action_before = safeActionAt(pos + 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd->init(this, action, action_before); + fw->commandHistory()->push(cmd); + + update(); +} + +void QDesignerMenu::deactivateMenu() +{ + m_deactivateWindowTimer->start(10); +} + +void QDesignerMenu::slotDeactivateNow() +{ + m_deactivateWindowTimer->stop(); + + if (m_dragging) + return; + + QDesignerMenu *root = findRootMenu(); + + if (! root->findActivatedMenu()) { + root->hide(); + root->hideSubMenu(); + } +} + +void QDesignerMenu::drawSelection(QPainter *p, const QRect &r) +{ + p->save(); + + QColor c = Qt::blue; + p->setPen(QPen(c, 1)); + c.setAlpha(32); + p->setBrush(c); + p->drawRect(r); + + p->restore(); +} + +void QDesignerMenu::keyPressEvent(QKeyEvent *event) +{ + event->ignore(); +} + +void QDesignerMenu::keyReleaseEvent(QKeyEvent *event) +{ + event->ignore(); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_menu_p.h b/src/tools/designer/src/lib/shared/qdesigner_menu_p.h new file mode 100644 index 00000000000..a3212ad694a --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_menu_p.h @@ -0,0 +1,170 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_MENU_H +#define QDESIGNER_MENU_H + +#include "shared_global_p.h" + +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QTimer; +class QLineEdit; + +class QDesignerFormWindowInterface; +class QDesignerActionProviderExtension; +class QDesignerMenu; +class QDesignerMenuBar; +class QPainter; +class QMimeData; + +namespace qdesigner_internal { + class CreateSubmenuCommand; + class ActionInsertionCommand; +} + +class QDESIGNER_SHARED_EXPORT QDesignerMenu: public QMenu +{ + Q_OBJECT +public: + QDesignerMenu(QWidget *parent = nullptr); + ~QDesignerMenu() override; + + bool eventFilter(QObject *object, QEvent *event) override; + + QDesignerFormWindowInterface *formWindow() const; + QDesignerActionProviderExtension *actionProvider(); + + QDesignerMenu *parentMenu() const; + QDesignerMenuBar *parentMenuBar() const; + + void setVisible(bool visible) override; + + void adjustSpecialActions(); + + void createRealMenuAction(QAction *action); + void removeRealMenu(QAction *action); + + static void drawSelection(QPainter *p, const QRect &r); + + bool dragging() const; + + void closeMenuChain(); + + void moveLeft(); + void moveRight(); + void moveUp(bool ctrl); + void moveDown(bool ctrl); + + // Helper for MenuTaskMenu extension + void deleteAction(QAction *a); + +private slots: + void slotAddSeparator(); + void slotRemoveSelectedAction(); + void slotShowSubMenuNow(); + void slotDeactivateNow(); + void slotAdjustSizeNow(); + +protected: + void actionEvent(QActionEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + void showEvent(QShowEvent *event) override; + + bool handleEvent(QWidget *widget, QEvent *event); + bool handleMouseDoubleClickEvent(QWidget *widget, QMouseEvent *event); + bool handleMousePressEvent(QWidget *widget, QMouseEvent *event); + bool handleMouseReleaseEvent(QWidget *widget, QMouseEvent *event); + bool handleMouseMoveEvent(QWidget *widget, QMouseEvent *event); + bool handleContextMenuEvent(QWidget *widget, QContextMenuEvent *event); + bool handleKeyPressEvent(QWidget *widget, QKeyEvent *event); + + void startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers); + + void adjustIndicator(const QPoint &pos); + int findAction(const QPoint &pos) const; + + QAction *currentAction() const; + int realActionCount() const; + enum ActionDragCheck { NoActionDrag, ActionDragOnSubMenu, AcceptActionDrag }; + ActionDragCheck checkAction(QAction *action) const; + + void showSubMenu(QAction *action); + + enum LeaveEditMode { + Default = 0, + ForceAccept + }; + + void enterEditMode(); + void leaveEditMode(LeaveEditMode mode); + void showLineEdit(); + + QAction *createAction(const QString &text, bool separator = false); + QDesignerMenu *findOrCreateSubMenu(QAction *action); + + QAction *safeActionAt(int index) const; + QAction *safeMenuAction(QDesignerMenu *menu) const; + bool swap(int a, int b); + + void hideSubMenu(); + void deleteAction(); + void deactivateMenu(); + + bool canCreateSubMenu(QAction *action) const; + QDesignerMenu *findRootMenu() const; + QDesignerMenu *findActivatedMenu() const; + + QRect subMenuPixmapRect(QAction *action) const; + bool hasSubMenuPixmap(QAction *action) const; + + void selectCurrentAction(); + +private: + bool hideSubMenuOnCursorKey(); + bool showSubMenuOnCursorKey(); + const QPixmap m_subMenuPixmap; + + QPoint m_startPosition; + int m_currentIndex = 0; + QAction *m_addItem; + QAction *m_addSeparator; + QHash m_subMenus; + QTimer *m_showSubMenuTimer; + QTimer *m_deactivateWindowTimer; + QTimer *m_adjustSizeTimer; + QLineEdit *m_editor; + bool m_dragging = false; + int m_lastSubMenuIndex = -1; + + friend class qdesigner_internal::CreateSubmenuCommand; + friend class qdesigner_internal::ActionInsertionCommand; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_MENU_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_menubar.cpp b/src/tools/designer/src/lib/shared/qdesigner_menubar.cpp new file mode 100644 index 00000000000..7b442ed3141 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_menubar.cpp @@ -0,0 +1,932 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_menubar_p.h" +#include "qdesigner_menu_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "actionrepository_p.h" +#include "actionprovider_p.h" +#include "actioneditor_p.h" +#include "qdesigner_utils_p.h" +#include "promotiontaskmenu_p.h" +#include "qdesigner_objectinspector_p.h" + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using ActionList = QList; + +namespace qdesigner_internal +{ + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +SpecialMenuAction::SpecialMenuAction(QObject *parent) + : QAction(parent) +{ +} + +SpecialMenuAction::~SpecialMenuAction() = default; + +} // namespace qdesigner_internal + + +///////////////////////////////////////////////////////////////////////////////////////////////////////// +QDesignerMenuBar::QDesignerMenuBar(QWidget *parent) : + QMenuBar(parent), + m_addMenu(new qdesigner_internal::SpecialMenuAction(this)), + m_editor(new QLineEdit(this)), + m_promotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(this, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this)) +{ + setContextMenuPolicy(Qt::DefaultContextMenu); + + setAcceptDrops(true); // ### fake + // Fake property: Keep the menu bar editable in the form even if a native menu bar is used. + setNativeMenuBar(false); + + m_addMenu->setText(tr("Type Here")); + addAction(m_addMenu); + + QFont italic; + italic.setItalic(true); + m_addMenu->setFont(italic); + + m_editor->setObjectName(u"__qt__passive_editor"_s); + m_editor->hide(); + m_editor->installEventFilter(this); + installEventFilter(this); +} + +QDesignerMenuBar::~QDesignerMenuBar() = default; + +void QDesignerMenuBar::paintEvent(QPaintEvent *event) +{ + QMenuBar::paintEvent(event); + + QPainter p(this); + + const auto &actionList = actions(); + for (QAction *a : actionList) { + if (qobject_cast(a)) { + const QRect g = actionGeometry(a); + QLinearGradient lg(g.left(), g.top(), g.left(), g.bottom()); + lg.setColorAt(0.0, Qt::transparent); + lg.setColorAt(0.7, QColor(0, 0, 0, 32)); + lg.setColorAt(1.0, Qt::transparent); + + p.fillRect(g, lg); + } + } + + QAction *action = currentAction(); + + if (m_dragging || !action) + return; + + if (hasFocus()) { + const QRect g = actionGeometry(action); + QDesignerMenu::drawSelection(&p, g.adjusted(1, 1, -1, -1)); + } else if (action->menu() && action->menu()->isVisible()) { + const QRect g = actionGeometry(action); + p.drawRect(g.adjusted(1, 1, -1, -1)); + } +} + +bool QDesignerMenuBar::handleEvent(QWidget *widget, QEvent *event) +{ + if (!formWindow()) + return false; + + if (event->type() == QEvent::FocusIn || event->type() == QEvent::FocusOut) + update(); + + switch (event->type()) { + default: break; + + case QEvent::MouseButtonDblClick: + return handleMouseDoubleClickEvent(widget, static_cast(event)); + case QEvent::MouseButtonPress: + return handleMousePressEvent(widget, static_cast(event)); + case QEvent::MouseButtonRelease: + return handleMouseReleaseEvent(widget, static_cast(event)); + case QEvent::MouseMove: + return handleMouseMoveEvent(widget, static_cast(event)); + case QEvent::ContextMenu: + return handleContextMenuEvent(widget, static_cast(event)); + case QEvent::KeyPress: + return handleKeyPressEvent(widget, static_cast(event)); + case QEvent::FocusIn: + case QEvent::FocusOut: + return widget != m_editor; + } + + return true; +} + +bool QDesignerMenuBar::handleMouseDoubleClickEvent(QWidget *, QMouseEvent *event) +{ + if (!rect().contains(event->position().toPoint())) + return true; + + if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) + return true; + + event->accept(); + + m_startPosition = QPoint(); + + m_currentIndex = actionIndexAt(this, event->position().toPoint(), Qt::Horizontal); + if (m_currentIndex != -1) { + showLineEdit(); + } + + return true; +} + +bool QDesignerMenuBar::handleKeyPressEvent(QWidget *, QKeyEvent *e) +{ + if (m_editor->isHidden()) { // In navigation mode + switch (e->key()) { + + case Qt::Key_Delete: + if (m_currentIndex == -1 || m_currentIndex >= realActionCount()) + break; + hideMenu(); + deleteMenu(); + break; + + case Qt::Key_Left: + e->accept(); + moveLeft(e->modifiers() & Qt::ControlModifier); + return true; + + case Qt::Key_Right: + e->accept(); + moveRight(e->modifiers() & Qt::ControlModifier); + return true; // no update + + case Qt::Key_Up: + e->accept(); + moveUp(); + return true; + + case Qt::Key_Down: + e->accept(); + moveDown(); + return true; + + case Qt::Key_PageUp: + m_currentIndex = 0; + break; + + case Qt::Key_PageDown: + m_currentIndex = actions().size() - 1; + break; + + case Qt::Key_Enter: + case Qt::Key_Return: + e->accept(); + enterEditMode(); + return true; // no update + + case Qt::Key_Alt: + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Escape: + e->ignore(); + setFocus(); // FIXME: this is because some other widget get the focus when CTRL is pressed + return true; // no update + + default: + if (!e->text().isEmpty() && e->text().at(0).toLatin1() >= 32) { + showLineEdit(); + QApplication::sendEvent(m_editor, e); + e->accept(); + } else { + e->ignore(); + } + return true; + } + } else { // In edit mode + switch (e->key()) { + default: + return false; + + case Qt::Key_Control: + e->ignore(); + return true; + + case Qt::Key_Enter: + case Qt::Key_Return: + if (!m_editor->text().isEmpty()) { + leaveEditMode(ForceAccept); + if (m_lastFocusWidget) + m_lastFocusWidget->setFocus(); + + m_editor->hide(); + showMenu(); + break; + } + Q_FALLTHROUGH(); + + case Qt::Key_Escape: + update(); + setFocus(); + break; + } + } + + e->accept(); + update(); + + return true; +} + +void QDesignerMenuBar::startDrag(const QPoint &pos) +{ + using namespace qdesigner_internal; + + const int index = findAction(pos); + if (m_currentIndex == -1 || index >= realActionCount()) + return; + + QAction *action = safeActionAt(index); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd->init(this, action, actions().at(index + 1)); + fw->commandHistory()->push(cmd); + + adjustSize(); + + hideMenu(index); + + QDrag *drag = new QDrag(this); + drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap(action)); + drag->setMimeData(new ActionRepositoryMimeData(action, Qt::MoveAction)); + + const int old_index = m_currentIndex; + m_currentIndex = -1; + + if (drag->exec(Qt::MoveAction) == Qt::IgnoreAction) { + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, action, safeActionAt(index)); + fw->commandHistory()->push(cmd); + + m_currentIndex = old_index; + adjustSize(); + } +} + +bool QDesignerMenuBar::handleMousePressEvent(QWidget *, QMouseEvent *event) +{ + m_startPosition = QPoint(); + event->accept(); + + if (event->button() != Qt::LeftButton) + return true; + + m_startPosition = event->position().toPoint(); + const int newIndex = actionIndexAt(this, m_startPosition, Qt::Horizontal); + const bool changed = newIndex != m_currentIndex; + m_currentIndex = newIndex; + updateCurrentAction(changed); + + return true; +} + +bool QDesignerMenuBar::handleMouseReleaseEvent(QWidget *, QMouseEvent *event) +{ + m_startPosition = QPoint(); + + if (event->button() != Qt::LeftButton) + return true; + + event->accept(); + m_currentIndex = actionIndexAt(this, event->position().toPoint(), Qt::Horizontal); + if (!m_editor->isVisible() && m_currentIndex != -1 && m_currentIndex < realActionCount()) + showMenu(); + + return true; +} + +bool QDesignerMenuBar::handleMouseMoveEvent(QWidget *, QMouseEvent *event) +{ + if ((event->buttons() & Qt::LeftButton) != Qt::LeftButton) + return true; + + if (m_startPosition.isNull()) + return true; + + const QPoint pos = mapFromGlobal(event->globalPosition().toPoint()); + + if ((pos - m_startPosition).manhattanLength() < qApp->startDragDistance()) + return true; + + const int index = actionIndexAt(this, m_startPosition, Qt::Horizontal); + if (index < actions().size()) { + hideMenu(index); + update(); + } + + startDrag(m_startPosition); + m_startPosition = QPoint(); + + return true; +} + +ActionList QDesignerMenuBar::contextMenuActions() +{ + using namespace qdesigner_internal; + + ActionList rc; + if (QAction *action = safeActionAt(m_currentIndex)) { + if (!qobject_cast(action)) { + QVariant itemData; + itemData.setValue(action); + + QAction *remove_action = new QAction(tr("Remove Menu '%1'").arg(action->menu()->objectName()), nullptr); + remove_action->setData(itemData); + connect(remove_action, &QAction::triggered, this, &QDesignerMenuBar::deleteMenu); + rc.push_back(remove_action); + QAction *sep = new QAction(nullptr); + sep->setSeparator(true); + rc.push_back(sep); + } + } + + m_promotionTaskMenu->addActions(formWindow(), PromotionTaskMenu::TrailingSeparator, rc); + + QAction *remove_menubar = new QAction(tr("Remove Menu Bar"), nullptr); + connect(remove_menubar, &QAction::triggered, this, &QDesignerMenuBar::slotRemoveMenuBar); + rc.push_back(remove_menubar); + return rc; +} + +bool QDesignerMenuBar::handleContextMenuEvent(QWidget *, QContextMenuEvent *event) +{ + event->accept(); + + m_currentIndex = actionIndexAt(this, mapFromGlobal(event->globalPos()), Qt::Horizontal); + + update(); + + QMenu menu; + const ActionList al = contextMenuActions(); + for (auto *a : al) + menu.addAction(a); + menu.exec(event->globalPos()); + return true; +} + +void QDesignerMenuBar::slotRemoveMenuBar() +{ + Q_ASSERT(formWindow() != nullptr); + + QDesignerFormWindowInterface *fw = formWindow(); + + auto *cmd = new qdesigner_internal::DeleteMenuBarCommand(fw); + cmd->init(this); + fw->commandHistory()->push(cmd); +} + +void QDesignerMenuBar::focusOutEvent(QFocusEvent *event) +{ + QMenuBar::focusOutEvent(event); +} + +void QDesignerMenuBar::enterEditMode() +{ + if (m_currentIndex >= 0 && m_currentIndex <= realActionCount()) { + showLineEdit(); + } +} + +void QDesignerMenuBar::leaveEditMode(LeaveEditMode mode) +{ + using namespace qdesigner_internal; + + m_editor->releaseKeyboard(); + + if (mode == Default) + return; + + if (m_editor->text().isEmpty()) + return; + + QAction *action = nullptr; + + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw); + + if (m_currentIndex >= 0 && m_currentIndex < realActionCount()) { + action = safeActionAt(m_currentIndex); + fw->beginCommand(QApplication::translate("Command", "Change Title")); + } else { + fw->beginCommand(QApplication::translate("Command", "Insert Menu")); + const QString niceObjectName = ActionEditor::actionTextToName(m_editor->text(), u"menu"_s); + QMenu *menu = qobject_cast(fw->core()->widgetFactory()->createWidget(u"QMenu"_s, this)); + fw->core()->widgetFactory()->initialize(menu); + menu->setObjectName(niceObjectName); + menu->setTitle(tr("Menu")); + fw->ensureUniqueObjectName(menu); + action = menu->menuAction(); + auto *cmd = new qdesigner_internal::AddMenuActionCommand(fw); + cmd->init(action, m_addMenu, this, this); + fw->commandHistory()->push(cmd); + } + + auto *cmd = new qdesigner_internal::SetPropertyCommand(fw); + cmd->init(action, u"text"_s, m_editor->text()); + fw->commandHistory()->push(cmd); + fw->endCommand(); +} + +void QDesignerMenuBar::showLineEdit() +{ + QAction *action = nullptr; + + if (m_currentIndex >= 0 && m_currentIndex < realActionCount()) + action = safeActionAt(m_currentIndex); + else + action = m_addMenu; + + if (action->isSeparator()) + return; + + // hideMenu(); + + m_lastFocusWidget = qApp->focusWidget(); + + // open edit field for item name + const QString text = action != m_addMenu ? action->text() : QString(); + + m_editor->setText(text); + m_editor->selectAll(); + m_editor->setGeometry(actionGeometry(action)); + m_editor->show(); + m_editor->activateWindow(); + m_editor->setFocus(); + m_editor->grabKeyboard(); +} + +bool QDesignerMenuBar::eventFilter(QObject *object, QEvent *event) +{ + if (object != this && object != m_editor) + return false; + + if (!m_editor->isHidden() && object == m_editor && event->type() == QEvent::FocusOut) { + leaveEditMode(Default); + m_editor->hide(); + update(); + return true; + } + + bool dispatch = true; + + switch (event->type()) { + default: break; + + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::ContextMenu: + case QEvent::MouseMove: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + dispatch = (object != m_editor); + Q_FALLTHROUGH(); // no break + + case QEvent::Enter: + case QEvent::Leave: + case QEvent::FocusIn: + case QEvent::FocusOut: + { + QWidget *widget = qobject_cast(object); + + if (dispatch && widget && (widget == this || isAncestorOf(widget))) + return handleEvent(widget, event); + } break; + + case QEvent::Shortcut: + event->accept(); + return true; + } + + return false; +}; + +int QDesignerMenuBar::findAction(const QPoint &pos) const +{ + const int index = actionIndexAt(this, pos, Qt::Horizontal); + if (index == -1) + return realActionCount(); + + return index; +} + +void QDesignerMenuBar::adjustIndicator(const QPoint &pos) +{ + const int index = findAction(pos); + QAction *action = safeActionAt(index); + Q_ASSERT(action != nullptr); + + if (pos != QPoint(-1, -1)) { + QDesignerMenu *m = qobject_cast(action->menu()); + if (!m || m->parentMenu()) { + m_currentIndex = index; + showMenu(index); + } + } + + if (QDesignerActionProviderExtension *a = actionProvider()) { + a->adjustIndicator(pos); + } +} + +QDesignerMenuBar::ActionDragCheck QDesignerMenuBar::checkAction(QAction *action) const +{ + // action belongs to another form + if (!action || !qdesigner_internal::Utils::isObjectAncestorOf(formWindow()->mainContainer(), action)) + return NoActionDrag; + + if (!action->menu()) + return ActionDragOnSubMenu; // simple action only on sub menus + + QDesignerMenu *m = qobject_cast(action->menu()); + if (m && m->parentMenu()) + return ActionDragOnSubMenu; // it looks like a submenu + + if (actions().contains(action)) + return ActionDragOnSubMenu; // we already have the action in the menubar + + return AcceptActionDrag; +} + +void QDesignerMenuBar::dragEnterEvent(QDragEnterEvent *event) +{ + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + + QAction *action = d->actionList().first(); + switch (checkAction(action)) { + case NoActionDrag: + event->ignore(); + break; + case ActionDragOnSubMenu: + m_dragging = true; + d->accept(event); + break; + case AcceptActionDrag: + m_dragging = true; + d->accept(event); + adjustIndicator(event->position().toPoint()); + break; + } +} + +void QDesignerMenuBar::dragMoveEvent(QDragMoveEvent *event) +{ + auto *d = qobject_cast(event->mimeData()); + if (!d || d->actionList().isEmpty()) { + event->ignore(); + return; + } + QAction *action = d->actionList().first(); + + switch (checkAction(action)) { + case NoActionDrag: + event->ignore(); + break; + case ActionDragOnSubMenu: + event->ignore(); + showMenu(findAction(event->position().toPoint())); + break; + case AcceptActionDrag: + d->accept(event); + adjustIndicator(event->position().toPoint()); + break; + } +} + +void QDesignerMenuBar::dragLeaveEvent(QDragLeaveEvent *) +{ + m_dragging = false; + + adjustIndicator(QPoint(-1, -1)); +} + +void QDesignerMenuBar::dropEvent(QDropEvent *event) +{ + m_dragging = false; + + if (auto *d = qobject_cast(event->mimeData())) { + + QAction *action = d->actionList().first(); + if (checkAction(action) == AcceptActionDrag) { + event->acceptProposedAction(); + int index = findAction(event->position().toPoint()); + index = qMin(index, actions().size() - 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd->init(this, action, safeActionAt(index)); + fw->commandHistory()->push(cmd); + + m_currentIndex = index; + update(); + adjustIndicator(QPoint(-1, -1)); + return; + } + } + event->ignore(); +} + +void QDesignerMenuBar::actionEvent(QActionEvent *event) +{ + QMenuBar::actionEvent(event); +} + +QDesignerFormWindowInterface *QDesignerMenuBar::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(const_cast(this)); +} + +QDesignerActionProviderExtension *QDesignerMenuBar::actionProvider() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QDesignerFormEditorInterface *core = fw->core(); + return qt_extension(core->extensionManager(), this); + } + + return nullptr; +} + +QAction *QDesignerMenuBar::currentAction() const +{ + if (m_currentIndex < 0 || m_currentIndex >= actions().size()) + return nullptr; + + return safeActionAt(m_currentIndex); +} + +int QDesignerMenuBar::realActionCount() const +{ + return actions().size() - 1; // 1 fake actions +} + +bool QDesignerMenuBar::dragging() const +{ + return m_dragging; +} + +void QDesignerMenuBar::moveLeft(bool ctrl) +{ + if (layoutDirection() == Qt::LeftToRight) { + movePrevious(ctrl); + } else { + moveNext(ctrl); + } +} + +void QDesignerMenuBar::moveRight(bool ctrl) +{ + if (layoutDirection() == Qt::LeftToRight) { + moveNext(ctrl); + } else { + movePrevious(ctrl); + } +} + +void QDesignerMenuBar::movePrevious(bool ctrl) +{ + const bool swapped = ctrl && swapActions(m_currentIndex, m_currentIndex - 1); + const int newIndex = qMax(0, m_currentIndex - 1); + // Always re-select, swapping destroys order + if (swapped || newIndex != m_currentIndex) { + m_currentIndex = newIndex; + updateCurrentAction(true); + } +} + +void QDesignerMenuBar::moveNext(bool ctrl) +{ + const bool swapped = ctrl && swapActions(m_currentIndex + 1, m_currentIndex); + const int newIndex = qMin(actions().size() - 1, m_currentIndex + 1); + if (swapped || newIndex != m_currentIndex) { + m_currentIndex = newIndex; + updateCurrentAction(!ctrl); + } +} + +void QDesignerMenuBar::moveUp() +{ + update(); +} + +void QDesignerMenuBar::moveDown() +{ + showMenu(); +} + +void QDesignerMenuBar::adjustSpecialActions() +{ + removeAction(m_addMenu); + addAction(m_addMenu); +} + +void QDesignerMenuBar::hideMenu(int index) +{ + if (index < 0 && m_currentIndex >= 0) + index = m_currentIndex; + + if (index < 0 || index >= realActionCount()) + return; + + QAction *action = safeActionAt(index); + + if (action && action->menu()) { + action->menu()->hide(); + + if (QDesignerMenu *menu = qobject_cast(action->menu())) { + menu->closeMenuChain(); + } + } +} + +void QDesignerMenuBar::deleteMenu() +{ + deleteMenuAction(currentAction()); +} + +void QDesignerMenuBar::deleteMenuAction(QAction *action) +{ + if (action && !qobject_cast(action)) { + const int pos = actions().indexOf(action); + QAction *action_before = nullptr; + if (pos != -1) + action_before = safeActionAt(pos + 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd = new qdesigner_internal::RemoveMenuActionCommand(fw); + cmd->init(action, action_before, this, this); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerMenuBar::showMenu(int index) +{ + if (index < 0 && m_currentIndex >= 0) + index = m_currentIndex; + + if (index < 0 || index >= realActionCount()) + return; + + m_currentIndex = index; + QAction *action = currentAction(); + + if (action && action->menu()) { + if (m_lastMenuActionIndex != -1 && m_lastMenuActionIndex != index) { + hideMenu(m_lastMenuActionIndex); + } + + m_lastMenuActionIndex = index; + QMenu *menu = action->menu(); + const QRect g = actionGeometry(action); + + if (!menu->isVisible()) { + if ((menu->windowFlags() & Qt::Popup) != Qt::Popup) + menu->setWindowFlags(Qt::Popup); + menu->adjustSize(); + if (layoutDirection() == Qt::LeftToRight) { + menu->move(mapToGlobal(g.bottomLeft())); + } else { + // The position is not initially correct due to the unknown width, + // causing it to overlap a bit the first time it is invoked. + QPoint point = g.bottomRight() - QPoint(menu->width(), 0); + menu->move(mapToGlobal(point)); + } + menu->setFocus(Qt::MouseFocusReason); + menu->raise(); + menu->show(); + } else { + menu->raise(); + } + } +} + +QAction *QDesignerMenuBar::safeActionAt(int index) const +{ + if (index < 0 || index >= actions().size()) + return nullptr; + + return actions().at(index); +} + +bool QDesignerMenuBar::swapActions(int a, int b) +{ + using namespace qdesigner_internal; + + const int left = qMin(a, b); + int right = qMax(a, b); + + QAction *action_a = safeActionAt(left); + QAction *action_b = safeActionAt(right); + + if (action_a == action_b + || !action_a + || !action_b + || qobject_cast(action_a) + || qobject_cast(action_b)) + return false; // nothing to do + + right = qMin(right, realActionCount()); + if (right < 0) + return false; // nothing to do + + formWindow()->beginCommand(QApplication::translate("Command", "Move action")); + + QAction *action_b_before = safeActionAt(right + 1); + + QDesignerFormWindowInterface *fw = formWindow(); + auto *cmd1 = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd1->init(this, action_b, action_b_before, false); + fw->commandHistory()->push(cmd1); + + QAction *action_a_before = safeActionAt(left + 1); + + auto *cmd2 = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd2->init(this, action_b, action_a_before, false); + fw->commandHistory()->push(cmd2); + + auto *cmd3 = new qdesigner_internal::RemoveActionFromCommand(fw); + cmd3->init(this, action_a, action_b, false); + fw->commandHistory()->push(cmd3); + + auto *cmd4 = new qdesigner_internal::InsertActionIntoCommand(fw); + cmd4->init(this, action_a, action_b_before, true); + fw->commandHistory()->push(cmd4); + + fw->endCommand(); + + return true; +} + +void QDesignerMenuBar::keyPressEvent(QKeyEvent *event) +{ + event->ignore(); +} + +void QDesignerMenuBar::keyReleaseEvent(QKeyEvent *event) +{ + event->ignore(); +} + +void QDesignerMenuBar::updateCurrentAction(bool selectAction) +{ + using namespace qdesigner_internal; + + update(); + + if (!selectAction) + return; + + QAction *action = currentAction(); + if (!action || action == m_addMenu) + return; + + QMenu *menu = action->menu(); + if (!menu) + return; + + QDesignerObjectInspector *oi = nullptr; + if (QDesignerFormWindowInterface *fw = formWindow()) + oi = qobject_cast(fw->core()->objectInspector()); + + if (!oi) + return; + + oi->clearSelection(); + oi->selectObject(menu); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_menubar_p.h b/src/tools/designer/src/lib/shared/qdesigner_menubar_p.h new file mode 100644 index 00000000000..e1d02e3fa54 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_menubar_p.h @@ -0,0 +1,140 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_MENUBAR_H +#define QDESIGNER_MENUBAR_H + +#include "shared_global_p.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerActionProviderExtension; + +class QLineEdit; +class QMimeData; + +namespace qdesigner_internal { +class PromotionTaskMenu; + +class SpecialMenuAction: public QAction +{ + Q_OBJECT +public: + SpecialMenuAction(QObject *parent = nullptr); + ~SpecialMenuAction() override; +}; + +} // namespace qdesigner_internal + +class QDESIGNER_SHARED_EXPORT QDesignerMenuBar: public QMenuBar +{ + Q_OBJECT +public: + QDesignerMenuBar(QWidget *parent = nullptr); + ~QDesignerMenuBar() override; + + bool eventFilter(QObject *object, QEvent *event) override; + + QDesignerFormWindowInterface *formWindow() const; + QDesignerActionProviderExtension *actionProvider(); + + void adjustSpecialActions(); + bool dragging() const; + + void moveLeft(bool ctrl = false); + void moveRight(bool ctrl = false); + void moveUp(); + void moveDown(); + + // Helpers for MenuTaskMenu/MenuBarTaskMenu extensions + QList contextMenuActions(); + void deleteMenuAction(QAction *action); + +private slots: + void deleteMenu(); + void slotRemoveMenuBar(); + +protected: + void actionEvent(QActionEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dragLeaveEvent(QDragLeaveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void paintEvent(QPaintEvent *event) override; + void focusOutEvent(QFocusEvent *event) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent *event) override; + + bool handleEvent(QWidget *widget, QEvent *event); + bool handleMouseDoubleClickEvent(QWidget *widget, QMouseEvent *event); + bool handleMousePressEvent(QWidget *widget, QMouseEvent *event); + bool handleMouseReleaseEvent(QWidget *widget, QMouseEvent *event); + bool handleMouseMoveEvent(QWidget *widget, QMouseEvent *event); + bool handleContextMenuEvent(QWidget *widget, QContextMenuEvent *event); + bool handleKeyPressEvent(QWidget *widget, QKeyEvent *event); + + void startDrag(const QPoint &pos); + + enum ActionDragCheck { NoActionDrag, ActionDragOnSubMenu, AcceptActionDrag }; + ActionDragCheck checkAction(QAction *action) const; + + void adjustIndicator(const QPoint &pos); + int findAction(const QPoint &pos) const; + + QAction *currentAction() const; + int realActionCount() const; + + enum LeaveEditMode { + Default = 0, + ForceAccept + }; + + void enterEditMode(); + void leaveEditMode(LeaveEditMode mode); + void showLineEdit(); + + void showMenu(int index = -1); + void hideMenu(int index = -1); + + QAction *safeActionAt(int index) const; + + bool swapActions(int a, int b); + +private: + void updateCurrentAction(bool selectAction); + void movePrevious(bool ctrl); + void moveNext(bool ctrl); + + QAction *m_addMenu; + QPointer m_activeMenu; + QPoint m_startPosition; + int m_currentIndex = 0; + QLineEdit *m_editor; + bool m_dragging = false; + int m_lastMenuActionIndex = -1; + QPointer m_lastFocusWidget; + qdesigner_internal::PromotionTaskMenu* m_promotionTaskMenu; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_MENUBAR_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_objectinspector.cpp b/src/tools/designer/src/lib/shared/qdesigner_objectinspector.cpp new file mode 100644 index 00000000000..9d546503164 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_objectinspector.cpp @@ -0,0 +1,42 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_objectinspector_p.h" + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +QDesignerObjectInspector::QDesignerObjectInspector(QWidget *parent, Qt::WindowFlags flags) : + QDesignerObjectInspectorInterface(parent, flags) +{ +} + +void QDesignerObjectInspector::mainContainerChanged() +{ +} + +void Selection::clear() +{ + managed.clear(); + unmanaged.clear(); + objects.clear(); +} + +bool Selection::empty() const +{ + return managed.isEmpty() && unmanaged.isEmpty() && objects.isEmpty(); +} + +QObjectList Selection::selection() const +{ + QObjectList rc(objects); + for (QObject *o : managed) + rc.push_back(o); + for (QObject *o : unmanaged) + rc.push_back(o); + return rc; +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_objectinspector_p.h b/src/tools/designer/src/lib/shared/qdesigner_objectinspector_p.h new file mode 100644 index 00000000000..e4395439212 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_objectinspector_p.h @@ -0,0 +1,65 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DESIGNEROBJECTINSPECTOR_H +#define DESIGNEROBJECTINSPECTOR_H + +#include "shared_global_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerDnDItemInterface; + +namespace qdesigner_internal { + +struct QDESIGNER_SHARED_EXPORT Selection { + bool empty() const; + void clear(); + + // Merge all lists + QObjectList selection() const; + + // Selection in cursor (managed widgets) + QWidgetList managed; + // Unmanaged widgets + QWidgetList unmanaged; + // Remaining selected objects (non-widgets) + QObjectList objects; +}; + +// Extends the QDesignerObjectInspectorInterface by functionality +// to access the selection + +class QDESIGNER_SHARED_EXPORT QDesignerObjectInspector: public QDesignerObjectInspectorInterface +{ + Q_OBJECT +public: + explicit QDesignerObjectInspector(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + + // Select a qobject unmanaged by form window + virtual bool selectObject(QObject *o) = 0; + virtual void getSelection(Selection &s) const = 0; + virtual void clearSelection() = 0; + +public slots: + virtual void mainContainerChanged(); +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DESIGNEROBJECTINSPECTOR_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_promotion.cpp b/src/tools/designer/src/lib/shared/qdesigner_promotion.cpp new file mode 100644 index 00000000000..eb78dd41335 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_promotion.cpp @@ -0,0 +1,359 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_promotion_p.h" +#include "widgetdatabase_p.h" +#include "metadatabase_p.h" +#include "widgetdatabase_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { + // Return a set of on-promotable classes + const QSet &nonPromotableClasses() { + static const QSet rc = { + u"Line"_s, + u"QAction"_s, + u"Spacer"_s, + u"QMainWindow"_s, + u"QDialog"_s, + u"QMdiArea"_s, + u"QMdiSubWindow"_s + }; + return rc; + } + + // Return widget database index of a promoted class or -1 with error message + int promotedWidgetDataBaseIndex(const QDesignerWidgetDataBaseInterface *widgetDataBase, + const QString &className, + QString *errorMessage) { + const int index = widgetDataBase->indexOfClassName(className); + if (index == -1 || !widgetDataBase->item(index)->isPromoted()) { + *errorMessage = QCoreApplication::tr("%1 is not a promoted class.").arg(className); + return -1; + } + return index; + } + + // Return widget database item of a promoted class or 0 with error message + QDesignerWidgetDataBaseItemInterface *promotedWidgetDataBaseItem(const QDesignerWidgetDataBaseInterface *widgetDataBase, + const QString &className, + QString *errorMessage) { + + const int index = promotedWidgetDataBaseIndex(widgetDataBase, className, errorMessage); + if (index == -1) + return nullptr; + return widgetDataBase->item(index); + } + + // extract class name from xml "". Quite a hack. + QString classNameFromXml(QString xml) + { + constexpr auto tag = "class=\""_L1; + const int pos = xml.indexOf(tag); + if (pos == -1) + return QString(); + xml.remove(0, pos + tag.size()); + const auto closingPos = xml.indexOf(u'"'); + if (closingPos == -1) + return QString(); + xml.remove(closingPos, xml.size() - closingPos); + return xml; + } + + // return a list of class names in the scratch pad + QStringList getScratchPadClasses(const QDesignerWidgetBoxInterface *wb) { + QStringList rc; + const int catCount = wb->categoryCount(); + for (int c = 0; c < catCount; c++) { + const QDesignerWidgetBoxInterface::Category category = wb->category(c); + if (category.type() == QDesignerWidgetBoxInterface::Category::Scratchpad) { + const int widgetCount = category.widgetCount(); + for (int w = 0; w < widgetCount; w++) { + const QString className = classNameFromXml( category.widget(w).domXml()); + if (!className.isEmpty()) + rc += className; + } + } + } + return rc; + } +} + +static void markFormsDirty(const QDesignerFormEditorInterface *core) +{ + const QDesignerFormWindowManagerInterface *fwm = core->formWindowManager(); + for (int f = 0, count = fwm->formWindowCount(); f < count; ++f) + fwm->formWindow(f)->setDirty(true); +} + +namespace qdesigner_internal { + + QDesignerPromotion::QDesignerPromotion(QDesignerFormEditorInterface *core) : + m_core(core) { + } + + bool QDesignerPromotion::addPromotedClass(const QString &baseClass, + const QString &className, + const QString &includeFile, + QString *errorMessage) + { + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + const int baseClassIndex = widgetDataBase->indexOfClassName(baseClass); + + if (baseClassIndex == -1) { + *errorMessage = QCoreApplication::tr("The base class %1 is invalid.").arg(baseClass); + return false; + } + + const int existingClassIndex = widgetDataBase->indexOfClassName(className); + + if (existingClassIndex != -1) { + *errorMessage = QCoreApplication::tr("The class %1 already exists.").arg(className); + return false; + } + // Clone derived item. + QDesignerWidgetDataBaseItemInterface *promotedItem = WidgetDataBaseItem::clone(widgetDataBase->item(baseClassIndex)); + // Also inherit the container flag in case of QWidget-derived classes + // as it is most likely intended for stacked pages. + // set new props + promotedItem->setName(className); + promotedItem->setGroup(QCoreApplication::tr("Promoted Widgets")); + promotedItem->setCustom(true); + promotedItem->setPromoted(true); + promotedItem->setExtends(baseClass); + promotedItem->setIncludeFile(includeFile); + widgetDataBase->append(promotedItem); + markFormsDirty(m_core); + return true; + } + + QList QDesignerPromotion::promotionBaseClasses() const + { + using SortedDatabaseItemMap = QMap; + SortedDatabaseItemMap sortedDatabaseItemMap; + + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + + const int cnt = widgetDataBase->count(); + for (int i = 0; i < cnt; i++) { + QDesignerWidgetDataBaseItemInterface *dbItem = widgetDataBase->item(i); + if (canBePromoted(dbItem)) { + sortedDatabaseItemMap.insert(dbItem->name(), dbItem); + } + } + + return sortedDatabaseItemMap.values(); + } + + + bool QDesignerPromotion::canBePromoted(const QDesignerWidgetDataBaseItemInterface *dbItem) const + { + if (dbItem->isPromoted() || !dbItem->extends().isEmpty()) + return false; + + const QString name = dbItem->name(); + + if (nonPromotableClasses().contains(name)) + return false; + + if (name.startsWith("QDesigner"_L1) || name.startsWith("QLayout"_L1)) + return false; + + return true; + } + + QDesignerPromotion::PromotedClasses QDesignerPromotion::promotedClasses() const + { + using ClassNameItemMap = QMap; + // A map containing base classes and their promoted classes. + QMap baseClassPromotedMap; + + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + // Look for promoted classes and insert into map according to base class. + const int cnt = widgetDataBase->count(); + for (int i = 0; i < cnt; i++) { + QDesignerWidgetDataBaseItemInterface *dbItem = widgetDataBase->item(i); + if (dbItem->isPromoted()) { + const QString baseClassName = dbItem->extends(); + auto it = baseClassPromotedMap.find(baseClassName); + if (it == baseClassPromotedMap.end()) { + it = baseClassPromotedMap.insert(baseClassName, ClassNameItemMap()); + } + it.value().insert(dbItem->name(), dbItem); + } + } + // convert map into list. + PromotedClasses rc; + + if (baseClassPromotedMap.isEmpty()) + return rc; + + for (auto bit = baseClassPromotedMap.cbegin(), bcend = baseClassPromotedMap.cend(); bit != bcend; ++bit) { + const int baseIndex = widgetDataBase->indexOfClassName(bit.key()); + Q_ASSERT(baseIndex >= 0); + QDesignerWidgetDataBaseItemInterface *baseItem = widgetDataBase->item(baseIndex); + // promoted + for (auto pit = bit.value().cbegin(), pcend = bit.value().cend(); pit != pcend; ++pit) { + PromotedClass item; + item.baseItem = baseItem; + item.promotedItem = pit.value(); + rc.push_back(item); + } + } + + return rc; + } + + QSet QDesignerPromotion::referencedPromotedClassNames() const { + QSet rc; + const MetaDataBase *metaDataBase = qobject_cast(m_core->metaDataBase()); + if (!metaDataBase) + return rc; + + const QObjectList &objects = metaDataBase->objects(); + for (QObject *object : objects) { + const QString customClass = metaDataBase->metaDataBaseItem(object)->customClassName(); + if (!customClass.isEmpty()) + rc.insert(customClass); + + } + // check the scratchpad of the widget box + if (QDesignerWidgetBoxInterface *widgetBox = m_core->widgetBox()) { + const QStringList scratchPadClasses = getScratchPadClasses(widgetBox); + if (!scratchPadClasses.isEmpty()) { + // Check whether these are actually promoted + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + for (const auto &scItem : scratchPadClasses) { + const int index = widgetDataBase->indexOfClassName(scItem); + if (index != -1 && widgetDataBase->item(index)->isPromoted()) + rc.insert(scItem); + } + } + } + return rc; + } + + bool QDesignerPromotion::removePromotedClass(const QString &className, QString *errorMessage) { + // check if it exists and is promoted + WidgetDataBase *widgetDataBase = qobject_cast(m_core->widgetDataBase()); + if (!widgetDataBase) { + *errorMessage = QCoreApplication::tr("The class %1 cannot be removed").arg(className); + return false; + } + + const int index = promotedWidgetDataBaseIndex(widgetDataBase, className, errorMessage); + if (index == -1) + return false; + + if (referencedPromotedClassNames().contains(className)) { + *errorMessage = QCoreApplication::tr("The class %1 cannot be removed because it is still referenced.").arg(className); + return false; + } + // QTBUG-52963: Check for classes that specify the to-be-removed class as + // base class of a promoted class. This should not happen in the normal case + // as promoted classes cannot serve as base for further promotion. It is possible + // though if a class provided by a plugin (say Qt WebKit's QWebView) is used as + // a base class for a promoted widget B and the plugin is removed in the next + // launch. QWebView will then appear as promoted class itself and the promoted + // class B will depend on it. When removing QWebView, the base class of B will + // be changed to that of QWebView by the below code. + const PromotedClasses promotedList = promotedClasses(); + for (const auto &pc : promotedList) { + if (pc.baseItem->name() == className) { + const QString extends = widgetDataBase->item(index)->extends(); + qWarning().nospace() << "Warning: Promoted class " << pc.promotedItem->name() + << " extends " << className << ", changing its base class to " << extends << '.'; + pc.promotedItem->setExtends(extends); + } + } + widgetDataBase->remove(index); + markFormsDirty(m_core); + return true; + } + + bool QDesignerPromotion::changePromotedClassName(const QString &oldclassName, const QString &newClassName, QString *errorMessage) { + const MetaDataBase *metaDataBase = qobject_cast(m_core->metaDataBase()); + if (!metaDataBase) { + *errorMessage = QCoreApplication::tr("The class %1 cannot be renamed").arg(oldclassName); + return false; + } + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + + // check the new name + if (newClassName.isEmpty()) { + *errorMessage = QCoreApplication::tr("The class %1 cannot be renamed to an empty name.").arg(oldclassName); + return false; + } + const int existingIndex = widgetDataBase->indexOfClassName(newClassName); + if (existingIndex != -1) { + *errorMessage = QCoreApplication::tr("There is already a class named %1.").arg(newClassName); + return false; + } + // Check old class + QDesignerWidgetDataBaseItemInterface *dbItem = promotedWidgetDataBaseItem(widgetDataBase, oldclassName, errorMessage); + if (!dbItem) + return false; + + // Change the name in the data base and change all referencing objects in the meta database + dbItem->setName(newClassName); + bool foundReferences = false; + const QObjectList &dbObjects = metaDataBase->objects(); + for (QObject* object : dbObjects) { + MetaDataBaseItem *item = metaDataBase->metaDataBaseItem(object); + Q_ASSERT(item); + if (item->customClassName() == oldclassName) { + item->setCustomClassName(newClassName); + foundReferences = true; + } + } + // set state + if (foundReferences) + refreshObjectInspector(); + + markFormsDirty(m_core); + return true; + } + + bool QDesignerPromotion::setPromotedClassIncludeFile(const QString &className, const QString &includeFile, QString *errorMessage) { + // check file + if (includeFile.isEmpty()) { + *errorMessage = QCoreApplication::tr("Cannot set an empty include file."); + return false; + } + // check item + QDesignerWidgetDataBaseInterface *widgetDataBase = m_core->widgetDataBase(); + QDesignerWidgetDataBaseItemInterface *dbItem = promotedWidgetDataBaseItem(widgetDataBase, className, errorMessage); + if (!dbItem) + return false; + if (dbItem->includeFile() != includeFile) { + dbItem->setIncludeFile(includeFile); + markFormsDirty(m_core); + } + return true; + } + + void QDesignerPromotion::refreshObjectInspector() { + if (QDesignerFormWindowManagerInterface *fwm = m_core->formWindowManager()) { + if (QDesignerFormWindowInterface *fw = fwm->activeFormWindow()) + if ( QDesignerObjectInspectorInterface *oi = m_core->objectInspector()) { + oi->setFormWindow(fw); + } + } + } +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_promotion_p.h b/src/tools/designer/src/lib/shared/qdesigner_promotion_p.h new file mode 100644 index 00000000000..e268296ac91 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_promotion_p.h @@ -0,0 +1,60 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNERPROMOTION_H +#define QDESIGNERPROMOTION_H + +#include "shared_global_p.h" + +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + + class QDESIGNER_SHARED_EXPORT QDesignerPromotion : public QDesignerPromotionInterface + { + public: + explicit QDesignerPromotion(QDesignerFormEditorInterface *core); + + PromotedClasses promotedClasses() const override; + + QSet referencedPromotedClassNames() const override; + + bool addPromotedClass(const QString &baseClass, + const QString &className, + const QString &includeFile, + QString *errorMessage) override; + + bool removePromotedClass(const QString &className, QString *errorMessage) override; + + bool changePromotedClassName(const QString &oldclassName, const QString &newClassName, QString *errorMessage) override; + + bool setPromotedClassIncludeFile(const QString &className, const QString &includeFile, QString *errorMessage) override; + + QList promotionBaseClasses() const override; + + private: + bool canBePromoted(const QDesignerWidgetDataBaseItemInterface *) const; + void refreshObjectInspector(); + + QDesignerFormEditorInterface *m_core; + }; +} + +QT_END_NAMESPACE + +#endif // QDESIGNERPROMOTION_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_promotiondialog.cpp b/src/tools/designer/src/lib/shared/qdesigner_promotiondialog.cpp new file mode 100644 index 00000000000..286ee3538c8 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_promotiondialog.cpp @@ -0,0 +1,432 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_promotiondialog_p.h" +#include "promotionmodel_p.h" +#include "iconloader_p.h" +#include "widgetdatabase_p.h" +#include "signalslotdialog_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + // PromotionParameters + struct PromotionParameters { + QString m_baseClass; + QString m_className; + QString m_includeFile; + }; + + // NewPromotedClassPanel + NewPromotedClassPanel::NewPromotedClassPanel(const QStringList &baseClasses, + int selectedBaseClass, + QWidget *parent) : + QGroupBox(parent), + m_baseClassCombo(new QComboBox), + m_classNameEdit(new QLineEdit), + m_includeFileEdit(new QLineEdit), + m_globalIncludeCheckBox(new QCheckBox), + m_addButton(new QPushButton(tr("Add"))) + { + setTitle(tr("New Promoted Class")); + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum); + QHBoxLayout *hboxLayout = new QHBoxLayout(this); + + m_classNameEdit->setValidator(new QRegularExpressionValidator(QRegularExpression(u"^[_a-zA-Z:][:_a-zA-Z0-9]*$"_s), m_classNameEdit)); + connect(m_classNameEdit, &QLineEdit::textChanged, + this, &NewPromotedClassPanel::slotNameChanged); + connect(m_includeFileEdit, &QLineEdit::textChanged, + this, &NewPromotedClassPanel::slotIncludeFileChanged); + + m_baseClassCombo->setEditable(false); + m_baseClassCombo->addItems(baseClasses); + if (selectedBaseClass != -1) + m_baseClassCombo->setCurrentIndex(selectedBaseClass); + + // Grid + QFormLayout *formLayout = new QFormLayout(); + formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); // Mac + formLayout->addRow(tr("Base class name:"), m_baseClassCombo); + formLayout->addRow(tr("Promoted class name:"), m_classNameEdit); + + QString toolTip = tr("Header file for C++ classes or module name for Qt for Python."); + auto *label = new QLabel(tr("Header file:")); + label->setToolTip(toolTip); + formLayout->addRow(label, m_includeFileEdit); + m_includeFileEdit->setToolTip(toolTip); + + toolTip = tr("Indicates that the header file is a global header file. Does not have any effect on Qt for Python."); + label = new QLabel(tr("Global include")); + label->setToolTip(toolTip); + formLayout->addRow(label, m_globalIncludeCheckBox); + m_globalIncludeCheckBox->setToolTip(toolTip); + + hboxLayout->addLayout(formLayout); + hboxLayout->addItem(new QSpacerItem(15, 0, QSizePolicy::Fixed, QSizePolicy::Ignored)); + // Button box + QVBoxLayout *buttonLayout = new QVBoxLayout(); + + m_addButton->setAutoDefault(false); + connect(m_addButton, &QAbstractButton::clicked, this, &NewPromotedClassPanel::slotAdd); + m_addButton->setEnabled(false); + buttonLayout->addWidget(m_addButton); + + QPushButton *resetButton = new QPushButton(tr("Reset")); + resetButton->setAutoDefault(false); + connect(resetButton, &QAbstractButton::clicked, this, &NewPromotedClassPanel::slotReset); + + buttonLayout->addWidget(resetButton); + buttonLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding)); + hboxLayout->addLayout(buttonLayout); + + enableButtons(); + } + + void NewPromotedClassPanel::slotAdd() { + bool ok = false; + emit newPromotedClass(promotionParameters(), &ok); + if (ok) + slotReset(); + } + + void NewPromotedClassPanel::slotReset() { + const QString empty; + m_classNameEdit->setText(empty); + m_includeFileEdit->setText(empty); + m_globalIncludeCheckBox->setCheckState(Qt::Unchecked); + } + + void NewPromotedClassPanel::grabFocus() { + m_classNameEdit->setFocus(Qt::OtherFocusReason); + } + + void NewPromotedClassPanel::slotNameChanged(const QString &className) { + // Suggest a name + if (!className.isEmpty()) { + QString suggestedHeader = m_promotedHeaderLowerCase ? + className.toLower() : className; + suggestedHeader.replace("::"_L1, "_"_L1); + if (!m_promotedHeaderSuffix.startsWith(u'.')) + suggestedHeader += u'.'; + suggestedHeader += m_promotedHeaderSuffix; + + const bool blocked = m_includeFileEdit->blockSignals(true); + m_includeFileEdit->setText(suggestedHeader); + m_includeFileEdit->blockSignals(blocked); + } + enableButtons(); + } + + void NewPromotedClassPanel::slotIncludeFileChanged(const QString &){ + enableButtons(); + } + + void NewPromotedClassPanel::enableButtons() { + const bool enabled = !m_classNameEdit->text().isEmpty() && !m_includeFileEdit->text().isEmpty(); + m_addButton->setEnabled(enabled); + m_addButton->setDefault(enabled); + } + + PromotionParameters NewPromotedClassPanel::promotionParameters() const { + PromotionParameters rc; + rc.m_baseClass = m_baseClassCombo->currentText(); + rc.m_className = m_classNameEdit->text(); + rc.m_includeFile = buildIncludeFile(m_includeFileEdit->text(), + m_globalIncludeCheckBox->checkState() == Qt::Checked ? IncludeGlobal : IncludeLocal); + return rc; + } + + void NewPromotedClassPanel::chooseBaseClass(const QString &baseClass) { + const int index = m_baseClassCombo->findText (baseClass); + if (index != -1) + m_baseClassCombo->setCurrentIndex (index); + } + + // --------------- QDesignerPromotionDialog + QDesignerPromotionDialog::QDesignerPromotionDialog(QDesignerFormEditorInterface *core, + QWidget *parent, + const QString &promotableWidgetClassName, + QString *promoteTo) : + QDialog(parent), + m_mode(promotableWidgetClassName.isEmpty() || promoteTo == nullptr ? ModeEdit : ModeEditChooseClass), + m_promotableWidgetClassName(promotableWidgetClassName), + m_core(core), + m_promoteTo(promoteTo), + m_promotion(core->promotion()), + m_model(new PromotionModel(core)), + m_treeView(new QTreeView), + m_buttonBox(nullptr), + m_removeButton(new QPushButton(createIconSet("minus.png"_L1), QString())) + { + m_buttonBox = createButtonBox(); + setModal(true); + setWindowTitle(tr("Promoted Widgets")); + + QVBoxLayout *vboxLayout = new QVBoxLayout(this); + + // tree view group + QGroupBox *treeViewGroup = new QGroupBox(); + treeViewGroup->setTitle(tr("Promoted Classes")); + QVBoxLayout *treeViewVBoxLayout = new QVBoxLayout(treeViewGroup); + // tree view + m_treeView->setModel (m_model); + m_treeView->setMinimumWidth(450); + m_treeView->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(m_treeView->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &QDesignerPromotionDialog::slotSelectionChanged); + + connect(m_treeView, &QWidget::customContextMenuRequested, + this, &QDesignerPromotionDialog::slotTreeViewContextMenu); + + QHeaderView *headerView = m_treeView->header(); + headerView->setSectionResizeMode(QHeaderView::ResizeToContents); + treeViewVBoxLayout->addWidget(m_treeView); + // remove button + QHBoxLayout *hboxLayout = new QHBoxLayout(); + hboxLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); + + m_removeButton->setAutoDefault(false); + connect(m_removeButton, &QAbstractButton::clicked, this, &QDesignerPromotionDialog::slotRemove); + m_removeButton->setEnabled(false); + hboxLayout->addWidget(m_removeButton); + treeViewVBoxLayout->addLayout(hboxLayout); + vboxLayout->addWidget(treeViewGroup); + // Create new panel: Try to be smart and preselect a base class. Default to QFrame + const QStringList &baseClassNameList = baseClassNames(m_promotion); + int preselectedBaseClass = -1; + if (m_mode == ModeEditChooseClass) { + preselectedBaseClass = baseClassNameList.indexOf(m_promotableWidgetClassName); + } + if (preselectedBaseClass == -1) + preselectedBaseClass = baseClassNameList.indexOf("QFrame"_L1); + + NewPromotedClassPanel *newPromotedClassPanel = new NewPromotedClassPanel(baseClassNameList, preselectedBaseClass); + newPromotedClassPanel->setPromotedHeaderSuffix(core->integration()->headerSuffix()); + newPromotedClassPanel->setPromotedHeaderLowerCase(core->integration()->isHeaderLowercase()); + connect(newPromotedClassPanel, &NewPromotedClassPanel::newPromotedClass, + this, &QDesignerPromotionDialog::slotNewPromotedClass); + connect(this, &QDesignerPromotionDialog::selectedBaseClassChanged, + newPromotedClassPanel, &NewPromotedClassPanel::chooseBaseClass); + vboxLayout->addWidget(newPromotedClassPanel); + // button box + vboxLayout->addWidget(m_buttonBox); + // connect model + connect(m_model, &PromotionModel::includeFileChanged, + this, &QDesignerPromotionDialog::slotIncludeFileChanged); + + connect(m_model, &PromotionModel::classNameChanged, + this, &QDesignerPromotionDialog::slotClassNameChanged); + + // focus + if (m_mode == ModeEditChooseClass) + newPromotedClassPanel->grabFocus(); + + slotUpdateFromWidgetDatabase(); + } + + QDialogButtonBox *QDesignerPromotionDialog::createButtonBox() { + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Close); + + connect(buttonBox, &QDialogButtonBox::accepted, + this, &QDesignerPromotionDialog::slotAcceptPromoteTo); + buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Promote")); + buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + return buttonBox; + } + + void QDesignerPromotionDialog::slotUpdateFromWidgetDatabase() { + m_model->updateFromWidgetDatabase(); + m_treeView->expandAll(); + m_removeButton->setEnabled(false); + } + + void QDesignerPromotionDialog::delayedUpdateFromWidgetDatabase() { + QTimer::singleShot(0, this, &QDesignerPromotionDialog::slotUpdateFromWidgetDatabase); + } + + const QStringList &QDesignerPromotionDialog::baseClassNames(const QDesignerPromotionInterface *promotion) { + using WidgetDataBaseItemList = QList; + static QStringList rc; + if (rc.isEmpty()) { + // Convert the item list into a string list. + const WidgetDataBaseItemList dbItems = promotion->promotionBaseClasses(); + for (auto *item : dbItems) + rc.append(item->name()); + } + return rc; + } + + void QDesignerPromotionDialog::slotAcceptPromoteTo() { + Q_ASSERT(m_mode == ModeEditChooseClass); + unsigned flags; + // Ok pressed: Promote to selected class + if (QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags)) { + if (flags & CanPromote) { + *m_promoteTo = dbItem ->name(); + accept(); + } + } + } + + void QDesignerPromotionDialog::slotRemove() { + unsigned flags; + QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); + if (!dbItem || (flags & Referenced)) + return; + + QString errorMessage; + if (m_promotion->removePromotedClass(dbItem->name(), &errorMessage)) { + slotUpdateFromWidgetDatabase(); + } else { + displayError(errorMessage); + } + } + + void QDesignerPromotionDialog::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &) { + // Enable deleting non-referenced items + unsigned flags; + const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(selected, flags); + m_removeButton->setEnabled(dbItem && !(flags & Referenced)); + // In choose mode, can we promote to the class? + if (m_mode == ModeEditChooseClass) { + const bool enablePromoted = flags & CanPromote; + m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enablePromoted); + m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(enablePromoted); + } + // different base? + if (dbItem) { + const QString baseClass = dbItem->extends(); + if (baseClass != m_lastSelectedBaseClass) { + m_lastSelectedBaseClass = baseClass; + emit selectedBaseClassChanged(m_lastSelectedBaseClass); + } + } + } + + QDesignerWidgetDataBaseItemInterface *QDesignerPromotionDialog::databaseItemAt(const QItemSelection &selected, unsigned &flags) const { + flags = 0; + const QModelIndexList indexes = selected.indexes(); + if (indexes.isEmpty()) + return nullptr; + const PromotionModel::ModelData data = m_model->modelData(indexes.constFirst()); + QDesignerWidgetDataBaseItemInterface *dbItem = data.promotedItem; + + if (dbItem) { + if (data.referenced) + flags |= Referenced; + // In choose mode, can we promote to the class? + if (m_mode == ModeEditChooseClass && dbItem && dbItem->isPromoted() && dbItem->extends() == m_promotableWidgetClassName) + flags |= CanPromote; + + } + return dbItem; + } + + void QDesignerPromotionDialog::slotNewPromotedClass(const PromotionParameters &p, bool *ok) { + QString errorMessage; + *ok = m_promotion->addPromotedClass(p.m_baseClass, p.m_className, p.m_includeFile, &errorMessage); + if (*ok) { + // update and select + slotUpdateFromWidgetDatabase(); + const QModelIndex newClassIndex = m_model->indexOfClass(p.m_className); + if (newClassIndex.isValid()) { + m_treeView->selectionModel()->select(newClassIndex, QItemSelectionModel::SelectCurrent|QItemSelectionModel::Rows); + } + } else { + displayError(errorMessage); + } + } + + void QDesignerPromotionDialog::slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &includeFile) { + if (includeFile.isEmpty()) { + delayedUpdateFromWidgetDatabase(); + return; + } + + if (dbItem->includeFile() == includeFile) + return; + + QString errorMessage; + if (!m_promotion->setPromotedClassIncludeFile(dbItem->name(), includeFile, &errorMessage)) { + displayError(errorMessage); + delayedUpdateFromWidgetDatabase(); + } + } + + void QDesignerPromotionDialog::slotClassNameChanged(QDesignerWidgetDataBaseItemInterface *dbItem, const QString &newName) { + if (newName.isEmpty()) { + delayedUpdateFromWidgetDatabase(); + return; + } + const QString oldName = dbItem->name(); + if (newName == oldName) + return; + + QString errorMessage; + if (!m_promotion->changePromotedClassName(oldName , newName, &errorMessage)) { + displayError(errorMessage); + delayedUpdateFromWidgetDatabase(); + } + } + + void QDesignerPromotionDialog::slotTreeViewContextMenu(const QPoint &pos) { + unsigned flags; + const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); + if (!dbItem) + return; + + QMenu menu; + QAction *signalSlotAction = menu.addAction(tr("Change signals/slots...")); + connect(signalSlotAction, &QAction::triggered, + this, &QDesignerPromotionDialog::slotEditSignalsSlots); + + menu.exec(m_treeView->viewport()->mapToGlobal(pos)); + } + + void QDesignerPromotionDialog::slotEditSignalsSlots() { + unsigned flags; + const QDesignerWidgetDataBaseItemInterface *dbItem = databaseItemAt(m_treeView->selectionModel()->selection(), flags); + if (!dbItem) + return; + + SignalSlotDialog::editPromotedClass(m_core, dbItem->name(), this); + } + + void QDesignerPromotionDialog::displayError(const QString &message) { + m_core->dialogGui()->message(this, QDesignerDialogGuiInterface::PromotionErrorMessage, QMessageBox::Warning, + tr("%1 - Error").arg(windowTitle()), message, QMessageBox::Close); + } +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_promotiondialog_p.h b/src/tools/designer/src/lib/shared/qdesigner_promotiondialog_p.h new file mode 100644 index 00000000000..529578fbc87 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_promotiondialog_p.h @@ -0,0 +1,132 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef PROMOTIONEDITORDIALOG_H +#define PROMOTIONEDITORDIALOG_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; +class QDesignerFormWindowInterface; +class QDesignerPromotionInterface; +class QDesignerWidgetDataBaseItemInterface; + +class QTreeView; +class QPushButton; +class QItemSelection; +class QDialogButtonBox; +class QComboBox; +class QLineEdit; +class QCheckBox; + +namespace qdesigner_internal { + struct PromotionParameters; + class PromotionModel; + + + // Panel for adding a new promoted class. Separate class for code cleanliness. + class NewPromotedClassPanel : public QGroupBox { + Q_OBJECT + public: + explicit NewPromotedClassPanel(const QStringList &baseClasses, + int selectedBaseClass = -1, + QWidget *parent = nullptr); + + QString promotedHeaderSuffix() const { return m_promotedHeaderSuffix; } + void setPromotedHeaderSuffix(const QString &s) { m_promotedHeaderSuffix = s; } + + bool isPromotedHeaderLowerCase() const { return m_promotedHeaderLowerCase; } + void setPromotedHeaderLowerCase(bool l) { m_promotedHeaderLowerCase = l; } + + signals: + void newPromotedClass(const PromotionParameters &, bool *ok); + + public slots: + void grabFocus(); + void chooseBaseClass(const QString &); + private slots: + void slotNameChanged(const QString &); + void slotIncludeFileChanged(const QString &); + void slotAdd(); + void slotReset(); + + private: + PromotionParameters promotionParameters() const; + void enableButtons(); + + QString m_promotedHeaderSuffix; + bool m_promotedHeaderLowerCase = false; + + QComboBox *m_baseClassCombo; + QLineEdit *m_classNameEdit; + QLineEdit *m_includeFileEdit; + QCheckBox *m_globalIncludeCheckBox; + QPushButton *m_addButton; + }; + + // Dialog for editing promoted classes. + class QDesignerPromotionDialog : public QDialog { + Q_OBJECT + + public: + enum Mode { ModeEdit, ModeEditChooseClass }; + + explicit QDesignerPromotionDialog(QDesignerFormEditorInterface *core, + QWidget *parent = nullptr, + const QString &promotableWidgetClassName = QString(), + QString *promoteTo = nullptr); + // Return an alphabetically ordered list of base class names for adding new classes. + static const QStringList &baseClassNames(const QDesignerPromotionInterface *promotion); + + signals: + void selectedBaseClassChanged(const QString &); + private slots: + void slotRemove(); + void slotAcceptPromoteTo(); + void slotSelectionChanged(const QItemSelection &, const QItemSelection &); + void slotNewPromotedClass(const PromotionParameters &, bool *ok); + + void slotIncludeFileChanged(QDesignerWidgetDataBaseItemInterface *, const QString &includeFile); + void slotClassNameChanged(QDesignerWidgetDataBaseItemInterface *, const QString &newName); + void slotUpdateFromWidgetDatabase(); + void slotTreeViewContextMenu(const QPoint &); + void slotEditSignalsSlots(); + + private: + QDialogButtonBox *createButtonBox(); + void delayedUpdateFromWidgetDatabase(); + // Return item at model index and a combination of flags or 0. + enum { Referenced = 1, CanPromote = 2 }; + QDesignerWidgetDataBaseItemInterface *databaseItemAt(const QItemSelection &, unsigned &flags) const; + void displayError(const QString &message); + + const Mode m_mode; + const QString m_promotableWidgetClassName; + QDesignerFormEditorInterface *m_core; + QString *m_promoteTo; + QDesignerPromotionInterface *m_promotion; + PromotionModel *m_model; + QTreeView *m_treeView; + QDialogButtonBox *m_buttonBox; + QPushButton *m_removeButton; + QString m_lastSelectedBaseClass; + }; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // PROMOTIONEDITORDIALOG_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_propertycommand.cpp b/src/tools/designer/src/lib/shared/qdesigner_propertycommand.cpp new file mode 100644 index 00000000000..f24675e7b47 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_propertycommand.cpp @@ -0,0 +1,1528 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_propertycommand_p.h" +#include "qdesigner_utils_p.h" +#include "dynamicpropertysheet.h" +#include "qdesigner_propertyeditor_p.h" +#include "spacer_widget_p.h" +#include "qdesigner_propertysheet_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace { +enum { debugPropertyCommands = 0 }; + +const unsigned QFontFamiliesResolved = (QFont::FamilyResolved | QFont::FamiliesResolved); + +// Debug resolve mask of font +QString fontMask(unsigned m) +{ + QString rc; + if (m & QFontFamiliesResolved) + rc += "Family"_L1; + if (m & QFont::SizeResolved) + rc += "Size "_L1; + if (m & QFont::WeightResolved) + rc += "Bold "_L1; + if (m & QFont::StyleResolved) + rc += "Style "_L1; + if (m & QFont::UnderlineResolved) + rc += "Underline "_L1; + if (m & QFont::StrikeOutResolved) + rc += "StrikeOut "_L1; + if (m & QFont::KerningResolved) + rc += "Kerning "_L1; + if (m & QFont::StyleStrategyResolved) + rc += "StyleStrategy"_L1; + return rc; +} + +// Debug font +QString fontString(const QFont &f) +{ + QString rc; { + QTextStream str(&rc); + str << "QFont(\"" << f.family() << ',' << f.pointSize(); + if (f.bold()) + str << ',' << "bold"; + if (f.italic()) + str << ',' << "italic"; + if (f.underline()) + str << ',' << "underline"; + if (f.strikeOut()) + str << ',' << "strikeOut"; + if (f.kerning()) + str << ',' << "kerning"; + str << ',' << f.styleStrategy() << " resolve: " + << fontMask(f.resolveMask()) << ')'; + } + return rc; +} +QSize checkSize(const QSize &size) +{ + return size.boundedTo(QSize(0xFFFFFF, 0xFFFFFF)); +} + +QSize diffSize(QDesignerFormWindowInterface *fw) +{ + const QWidget *container = fw->core()->integration()->containerWindow(fw); + if (!container) + return QSize(); + + const QSize diff = container->size() - fw->size(); // decoration offset of container window + return diff; +} + +void checkSizes(QDesignerFormWindowInterface *fw, const QSize &size, QSize *formSize, QSize *containerSize) +{ + const QWidget *container = fw->core()->integration()->containerWindow(fw); + if (!container) + return; + + const QSize diff = diffSize(fw); // decoration offset of container window + + QSize newFormSize = checkSize(size).expandedTo(fw->mainContainer()->minimumSizeHint()); // don't try to resize to smaller size than minimumSizeHint + QSize newContainerSize = newFormSize + diff; + + newContainerSize = newContainerSize.expandedTo(container->minimumSizeHint()); + newContainerSize = newContainerSize.expandedTo(container->minimumSize()); + + newFormSize = newContainerSize - diff; + + newContainerSize = checkSize(newContainerSize); + + if (formSize) + *formSize = newFormSize; + if (containerSize) + *containerSize = newContainerSize; +} + +/* SubProperties: When applying a changed property to a multiselection, it sometimes makes + * sense to apply only parts (subproperties) of the property. + * For example, if someone changes the x-value of a geometry in the property editor + * and applies it to a multi-selection, y should not be applied as this would cause all + * the widgets to overlap. + * The following routines can be used to find out the changed subproperties of a property, + * which are represented as a mask, and to apply them while leaving the others intact. */ + +enum RectSubPropertyMask { SubPropertyX=1, SubPropertyY = 2, SubPropertyWidth = 4, SubPropertyHeight = 8 }; +enum SizePolicySubPropertyMask { SubPropertyHSizePolicy = 1, SubPropertyHStretch = 2, SubPropertyVSizePolicy = 4, SubPropertyVStretch = 8 }; +enum AlignmentSubPropertyMask { SubPropertyHorizontalAlignment = 1, SubPropertyVerticalAlignment = 2 }; +enum StringSubPropertyMask { SubPropertyStringValue = 1, SubPropertyStringComment = 2, + SubPropertyStringTranslatable = 4, SubPropertyStringDisambiguation = 8, + SubPropertyStringId = 16 }; +enum StringListSubPropertyMask { SubPropertyStringListValue = 1, SubPropertyStringListComment = 2, + SubPropertyStringListTranslatable = 4, SubPropertyStringListDisambiguation = 8, + SubPropertyStringListId = 16 }; +enum KeySequenceSubPropertyMask { SubPropertyKeySequenceValue = 1, SubPropertyKeySequenceComment = 2, + SubPropertyKeySequenceTranslatable = 4, SubPropertyKeySequenceDisambiguation = 8, + SubPropertyKeySequenceId = 16 }; + +enum CommonSubPropertyMask : quint64 { SubPropertyAll = quint64(-1) }; + +// Set the mask flag in mask if the properties do not match. +#define COMPARE_SUBPROPERTY(object1, object2, getter, mask, maskFlag) \ + if (object1.getter() != object2.getter()) (mask) |= (maskFlag); + +// find changed subproperties of a rectangle +quint64 compareSubProperties(const QRect & r1, const QRect & r2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(r1, r2, x, rc, SubPropertyX) + COMPARE_SUBPROPERTY(r1, r2, y, rc, SubPropertyY) + COMPARE_SUBPROPERTY(r1, r2, width, rc, SubPropertyWidth) + COMPARE_SUBPROPERTY(r1, r2, height, rc, SubPropertyHeight) + return rc; +} + +// find changed subproperties of a QSize +quint64 compareSubProperties(const QSize & r1, const QSize & r2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(r1, r2, width, rc, SubPropertyWidth) + COMPARE_SUBPROPERTY(r1, r2, height, rc, SubPropertyHeight) + return rc; +} +// find changed subproperties of a QSizePolicy +quint64 compareSubProperties(const QSizePolicy & sp1, const QSizePolicy & sp2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(sp1, sp2, horizontalPolicy, rc, SubPropertyHSizePolicy) + COMPARE_SUBPROPERTY(sp1, sp2, horizontalStretch, rc, SubPropertyHStretch) + COMPARE_SUBPROPERTY(sp1, sp2, verticalPolicy, rc, SubPropertyVSizePolicy) + COMPARE_SUBPROPERTY(sp1, sp2, verticalStretch, rc, SubPropertyVStretch) + return rc; +} +// find changed subproperties of qdesigner_internal::PropertySheetStringValue +quint64 compareSubProperties(const qdesigner_internal::PropertySheetStringValue & str1, const qdesigner_internal::PropertySheetStringValue & str2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(str1, str2, value, rc, SubPropertyStringValue) + COMPARE_SUBPROPERTY(str1, str2, comment, rc, SubPropertyStringComment) + COMPARE_SUBPROPERTY(str1, str2, translatable, rc, SubPropertyStringTranslatable) + COMPARE_SUBPROPERTY(str1, str2, disambiguation, rc, SubPropertyStringDisambiguation) + COMPARE_SUBPROPERTY(str1, str2, id, rc, SubPropertyStringId) + return rc; +} +// find changed subproperties of qdesigner_internal::PropertySheetStringListValue +quint64 compareSubProperties(const qdesigner_internal::PropertySheetStringListValue & str1, const qdesigner_internal::PropertySheetStringListValue & str2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(str1, str2, value, rc, SubPropertyStringListValue) + COMPARE_SUBPROPERTY(str1, str2, comment, rc, SubPropertyStringListComment) + COMPARE_SUBPROPERTY(str1, str2, translatable, rc, SubPropertyStringListTranslatable) + COMPARE_SUBPROPERTY(str1, str2, disambiguation, rc, SubPropertyStringListDisambiguation) + COMPARE_SUBPROPERTY(str1, str2, id, rc, SubPropertyStringListId) + return rc; +} +// find changed subproperties of qdesigner_internal::PropertySheetKeySequenceValue +quint64 compareSubProperties(const qdesigner_internal::PropertySheetKeySequenceValue & str1, const qdesigner_internal::PropertySheetKeySequenceValue & str2) +{ + quint64 rc = 0; + COMPARE_SUBPROPERTY(str1, str2, value, rc, SubPropertyKeySequenceValue) + COMPARE_SUBPROPERTY(str1, str2, comment, rc, SubPropertyKeySequenceComment) + COMPARE_SUBPROPERTY(str1, str2, translatable, rc, SubPropertyKeySequenceTranslatable) + COMPARE_SUBPROPERTY(str1, str2, disambiguation, rc, SubPropertyKeySequenceDisambiguation) + COMPARE_SUBPROPERTY(str1, str2, id, rc, SubPropertyKeySequenceId) + return rc; +} + +// Compare font-subproperties taking the [undocumented] +// resolve flag into account +template +void compareFontSubProperty(const QFont & f1, + const QFont & f2, + Property (QFont::*getter) () const, + quint64 maskBit, + quint64 &mask) +{ + const bool f1Changed = f1.resolveMask() & maskBit; + const bool f2Changed = f2.resolveMask() & maskBit; + // Role has been set/reset in editor + if (f1Changed != f2Changed) { + mask |= maskBit; + } else { + // Was modified in both palettes: Compare values. + if (f1Changed && f2Changed && (f1.*getter)() != (f2.*getter)()) + mask |= maskBit; + } +} +// find changed subproperties of a QFont +quint64 compareSubProperties(const QFont & f1, const QFont & f2) +{ + quint64 rc = 0; + compareFontSubProperty(f1, f2, &QFont::family, QFontFamiliesResolved, rc); + compareFontSubProperty(f1, f2, &QFont::pointSize, QFont::SizeResolved, rc); + compareFontSubProperty(f1, f2, &QFont::weight, QFont::WeightResolved, rc); + compareFontSubProperty(f1, f2, &QFont::italic, QFont::StyleResolved, rc); + compareFontSubProperty(f1, f2, &QFont::underline, QFont::UnderlineResolved, rc); + compareFontSubProperty(f1, f2, &QFont::strikeOut, QFont::StrikeOutResolved, rc); + compareFontSubProperty(f1, f2, &QFont::kerning, QFont::KerningResolved, rc); + compareFontSubProperty(f1, f2, &QFont::styleStrategy, QFont::StyleStrategyResolved, rc); + compareFontSubProperty(f1, f2, &QFont::hintingPreference, QFont::HintingPreferenceResolved, rc); + if (debugPropertyCommands) + qDebug() << "compareSubProperties " << fontString(f1) << fontString(f2) << "\n\treturns " << fontMask(rc); + return rc; +} + +// find changed subproperties of a QPalette taking the [undocumented] resolve flags into account +quint64 compareSubProperties(const QPalette & p1, const QPalette & p2) +{ + quint64 rc = 0; + // generate a mask for each role + const auto p1Changed = p1.resolveMask(); + const auto p2Changed = p2.resolveMask(); + + for (int r = 0; r < static_cast(QPalette::NColorRoles); ++r) { + for (int g = 0; g < static_cast(QPalette::NColorGroups); ++g) { + const auto role = static_cast(r); + const auto group = static_cast(g); + const auto maskBit = qdesigner_internal::paletteResolveMask(group, role); + const bool p1RoleChanged = p1Changed & maskBit; + const bool p2RoleChanged = p2Changed & maskBit; + if (p1RoleChanged != p2RoleChanged // Role has been set/reset in editor + // Was modified in both palettes: Compare values. + || (p1RoleChanged && p2RoleChanged + && p1.brush(group, role).color() != p2.brush(group, role).color())) { + rc |= maskBit; + } + } + } + + return rc; +} + +// find changed subproperties of a QAlignment which is a flag combination of vertical and horizontal + +quint64 compareSubProperties(Qt::Alignment a1, Qt::Alignment a2) +{ + quint64 rc = 0; + if ((a1 & Qt::AlignHorizontal_Mask) != (a2 & Qt::AlignHorizontal_Mask)) + rc |= SubPropertyHorizontalAlignment; + if ((a1 & Qt::AlignVertical_Mask) != (a2 & Qt::AlignVertical_Mask)) + rc |= SubPropertyVerticalAlignment; + return rc; +} + +Qt::Alignment variantToAlignment(const QVariant & q) +{ + return Qt::Alignment(qdesigner_internal::Utils::valueOf(q)); +} +// find changed subproperties of a variant +quint64 compareSubProperties(const QVariant & q1, const QVariant & q2, qdesigner_internal::SpecialProperty specialProperty) +{ + // Do not clobber new value in the comparison function in + // case someone sets a QString on a PropertySheetStringValue. + const int t1 = q1.metaType().id(); + const int t2 = q2.metaType().id(); + if (t1 != t2) + return SubPropertyAll; + switch (t1) { + case QMetaType::QRect: + return compareSubProperties(q1.toRect(), q2.toRect()); + case QMetaType::QSize: + return compareSubProperties(q1.toSize(), q2.toSize()); + case QMetaType::QSizePolicy: + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + case QMetaType::QFont: + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + case QMetaType::QPalette: + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + default: + if (q1.userType() == qMetaTypeId()) + return qvariant_cast(q1).compare(qvariant_cast(q2)); + else if (q1.userType() == qMetaTypeId()) + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + else if (q1.userType() == qMetaTypeId()) + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + else if (q1.userType() == qMetaTypeId()) + return compareSubProperties(qvariant_cast(q1), qvariant_cast(q2)); + // Enumerations, flags + switch (specialProperty) { + case qdesigner_internal::SP_Alignment: + return compareSubProperties(variantToAlignment(q1), variantToAlignment(q2)); + default: + break; + } + break; + } + return SubPropertyAll; +} + +// Apply the sub property if mask flag is set in mask +#define SET_SUBPROPERTY(rc, newValue, getter, setter, mask, maskFlag) \ + if ((mask) & (maskFlag)) rc.setter((newValue).getter()); + +// apply changed subproperties to a rectangle +QRect applyRectSubProperty(const QRect &oldValue, const QRect &newValue, unsigned mask) +{ + QRect rc = oldValue; + SET_SUBPROPERTY(rc, newValue, x, moveLeft, mask, SubPropertyX) + SET_SUBPROPERTY(rc, newValue, y, moveTop, mask, SubPropertyY) + SET_SUBPROPERTY(rc, newValue, width, setWidth, mask, SubPropertyWidth) + SET_SUBPROPERTY(rc, newValue, height, setHeight, mask, SubPropertyHeight) + return rc; +} + + +// apply changed subproperties to a rectangle QSize +QSize applySizeSubProperty(const QSize &oldValue, const QSize &newValue, unsigned mask) +{ + QSize rc = oldValue; + SET_SUBPROPERTY(rc, newValue, width, setWidth, mask, SubPropertyWidth) + SET_SUBPROPERTY(rc, newValue, height, setHeight, mask, SubPropertyHeight) + return rc; +} + + +// apply changed subproperties to a SizePolicy +QSizePolicy applySizePolicySubProperty(const QSizePolicy &oldValue, const QSizePolicy &newValue, unsigned mask) +{ + QSizePolicy rc = oldValue; + SET_SUBPROPERTY(rc, newValue, horizontalPolicy, setHorizontalPolicy, mask, SubPropertyHSizePolicy) + SET_SUBPROPERTY(rc, newValue, horizontalStretch, setHorizontalStretch, mask, SubPropertyHStretch) + SET_SUBPROPERTY(rc, newValue, verticalPolicy, setVerticalPolicy, mask, SubPropertyVSizePolicy) + SET_SUBPROPERTY(rc, newValue, verticalStretch, setVerticalStretch, mask, SubPropertyVStretch) + return rc; +} + +// apply changed subproperties to a qdesigner_internal::PropertySheetStringValue +qdesigner_internal::PropertySheetStringValue applyStringSubProperty(const qdesigner_internal::PropertySheetStringValue &oldValue, + const qdesigner_internal::PropertySheetStringValue &newValue, unsigned mask) +{ + qdesigner_internal::PropertySheetStringValue rc = oldValue; + SET_SUBPROPERTY(rc, newValue, value, setValue, mask, SubPropertyStringValue) + SET_SUBPROPERTY(rc, newValue, comment, setComment, mask, SubPropertyStringComment) + SET_SUBPROPERTY(rc, newValue, translatable, setTranslatable, mask, SubPropertyStringTranslatable) + SET_SUBPROPERTY(rc, newValue, disambiguation, setDisambiguation, mask, SubPropertyStringDisambiguation) + SET_SUBPROPERTY(rc, newValue, id, setId, mask, SubPropertyStringId) + return rc; +} + +// apply changed subproperties to a qdesigner_internal::PropertySheetStringListValue +qdesigner_internal::PropertySheetStringListValue applyStringListSubProperty(const qdesigner_internal::PropertySheetStringListValue &oldValue, + const qdesigner_internal::PropertySheetStringListValue &newValue, unsigned mask) +{ + qdesigner_internal::PropertySheetStringListValue rc = oldValue; + SET_SUBPROPERTY(rc, newValue, value, setValue, mask, SubPropertyStringListValue) + SET_SUBPROPERTY(rc, newValue, comment, setComment, mask, SubPropertyStringListComment) + SET_SUBPROPERTY(rc, newValue, translatable, setTranslatable, mask, SubPropertyStringListTranslatable) + SET_SUBPROPERTY(rc, newValue, disambiguation, setDisambiguation, mask, SubPropertyStringListDisambiguation) + SET_SUBPROPERTY(rc, newValue, id, setId, mask, SubPropertyStringListId) + return rc; +} + +// apply changed subproperties to a qdesigner_internal::PropertySheetKeySequenceValue +qdesigner_internal::PropertySheetKeySequenceValue applyKeySequenceSubProperty(const qdesigner_internal::PropertySheetKeySequenceValue &oldValue, + const qdesigner_internal::PropertySheetKeySequenceValue &newValue, unsigned mask) +{ + qdesigner_internal::PropertySheetKeySequenceValue rc = oldValue; + SET_SUBPROPERTY(rc, newValue, value, setValue, mask, SubPropertyKeySequenceValue) + SET_SUBPROPERTY(rc, newValue, comment, setComment, mask, SubPropertyKeySequenceComment) + SET_SUBPROPERTY(rc, newValue, translatable, setTranslatable, mask, SubPropertyKeySequenceTranslatable) + SET_SUBPROPERTY(rc, newValue, disambiguation, setDisambiguation, mask, SubPropertyKeySequenceDisambiguation) + SET_SUBPROPERTY(rc, newValue, id, setId, mask, SubPropertyKeySequenceId) + return rc; +} + +// Apply the font-subproperties keeping the [undocumented] +// resolve flag in sync (note that PropertySetterType might be something like const T&). +template +inline void setFontSubProperty(unsigned mask, + const QFont &newValue, + unsigned maskBit, + PropertyReturnType (QFont::*getter) () const, + void (QFont::*setter) (PropertySetterType), + QFont &value) +{ + if (mask & maskBit) { + (value.*setter)((newValue.*getter)()); + // Set the resolve bit from NewValue in return value + uint r = value.resolveMask(); + const bool origFlag = newValue.resolveMask() & maskBit; + if (origFlag) + r |= maskBit; + else + r &= ~maskBit; + value.setResolveMask(r); + if (debugPropertyCommands) + qDebug() << "setFontSubProperty " << fontMask(maskBit) << " resolve=" << origFlag; + } +} +// apply changed subproperties to a QFont +QFont applyFontSubProperty(const QFont &oldValue, const QFont &newValue, unsigned mask) +{ + QFont rc = oldValue; + setFontSubProperty(mask, newValue, QFontFamiliesResolved, &QFont::family, &QFont::setFamily, rc); + setFontSubProperty(mask, newValue, QFont::SizeResolved, &QFont::pointSize, &QFont::setPointSize, rc); + setFontSubProperty(mask, newValue, QFont::WeightResolved, &QFont::weight, &QFont::setWeight, rc); + setFontSubProperty(mask, newValue, QFont::StyleResolved, &QFont::italic, &QFont::setItalic, rc); + setFontSubProperty(mask, newValue, QFont::UnderlineResolved, &QFont::underline, &QFont::setUnderline, rc); + setFontSubProperty(mask, newValue, QFont::StrikeOutResolved, &QFont::strikeOut, &QFont::setStrikeOut, rc); + setFontSubProperty(mask, newValue, QFont::KerningResolved, &QFont::kerning, &QFont::setKerning, rc); + setFontSubProperty(mask, newValue, QFont::StyleStrategyResolved, &QFont::styleStrategy, &QFont::setStyleStrategy, rc); + setFontSubProperty(mask, newValue, QFont::HintingPreferenceResolved, &QFont::hintingPreference, &QFont::setHintingPreference, rc); + if (debugPropertyCommands) + qDebug() << "applyFontSubProperty old " << fontMask(oldValue.resolveMask()) << " new " << fontMask(newValue.resolveMask()) << " return: " << fontMask(rc.resolveMask()); + return rc; +} + +// apply changed subproperties to a QPalette +QPalette applyPaletteSubProperty(const QPalette &oldValue, const QPalette &newValue, + quint64 mask) +{ + QPalette rc = oldValue; + // apply a mask for each role/group + for (int r = 0; r < static_cast(QPalette::NColorRoles); ++r) { + for (int g = 0; g < static_cast(QPalette::NColorGroups); ++g) { + const auto role = static_cast(r); + const auto group = static_cast(g); + const auto maskBit = qdesigner_internal::paletteResolveMask(group, role); + if (mask & maskBit) { + rc.setColor(group, role, newValue.color(group, role)); + // Set the resolve bit from NewValue in return value + auto resolveMask = rc.resolveMask(); + const bool origFlag = newValue.resolveMask() & maskBit; + if (origFlag) + resolveMask |= maskBit; + else + resolveMask &= ~maskBit; + rc.setResolveMask(resolveMask); + } + } + } + return rc; +} + +// apply changed subproperties to a QAlignment which is a flag combination of vertical and horizontal +Qt::Alignment applyAlignmentSubProperty(Qt::Alignment oldValue, Qt::Alignment newValue, unsigned mask) +{ + // easy: both changed. + if (mask == (SubPropertyHorizontalAlignment|SubPropertyVerticalAlignment)) + return newValue; + // Change subprop + const Qt::Alignment changeMask = (mask & SubPropertyHorizontalAlignment) ? Qt::AlignHorizontal_Mask : Qt::AlignVertical_Mask; + const Qt::Alignment takeOverMask = (mask & SubPropertyHorizontalAlignment) ? Qt::AlignVertical_Mask : Qt::AlignHorizontal_Mask; + return (oldValue & takeOverMask) | (newValue & changeMask); +} + +} + +namespace qdesigner_internal { + +// apply changed subproperties to a variant +PropertyHelper::Value applySubProperty(const QVariant &oldValue, const QVariant &newValue, + qdesigner_internal::SpecialProperty specialProperty, + quint64 mask, bool changed) +{ + if (mask == SubPropertyAll) + return PropertyHelper::Value(newValue, changed); + + switch (oldValue.metaType().id()) { + case QMetaType::QRect: + return PropertyHelper::Value(applyRectSubProperty(oldValue.toRect(), newValue.toRect(), mask), changed); + case QMetaType::QSize: + return PropertyHelper::Value(applySizeSubProperty(oldValue.toSize(), newValue.toSize(), mask), changed); + case QMetaType::QSizePolicy: + return PropertyHelper::Value(QVariant::fromValue(applySizePolicySubProperty(qvariant_cast(oldValue), qvariant_cast(newValue), mask)), changed); + case QMetaType::QFont: { + // Changed flag in case of font and palette depends on resolve mask only, not on the passed "changed" value. + + // The first case: the user changed bold subproperty and then pressed reset button for this subproperty (not for + // the whole font property). We instantiate SetPropertyCommand passing changed=true. But in this case no + // subproperty is changed and the whole property should be marked an unchanged. + + // The second case: there are 2 pushbuttons, for 1st the user set bold and italic subproperties, + // for the 2nd he set bold only. He does multiselection so that the current widget is the 2nd one. + // He press reset next to bold subproperty. In result the 2nd widget should have the whole + // font property marked as unchanged and the 1st widget should have the font property + // marked as changed and only italic subproperty should be marked as changed (the bold should be reset). + + // The third case: there are 2 pushbuttons, for 1st the user set bold and italic subproperties, + // for the 2nd he set bold only. He does multiselection so that the current widget is the 2nd one. + // He press reset button for the whole font property. In result whole font properties for both + // widgets should be marked as unchanged. + QFont font = applyFontSubProperty(qvariant_cast(oldValue), qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(font), font.resolveMask()); + } + case QMetaType::QPalette: { + QPalette palette = applyPaletteSubProperty(qvariant_cast(oldValue), qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(palette), palette.resolveMask()); + } + default: + if (oldValue.userType() == qMetaTypeId()) { + PropertySheetIconValue icon = qvariant_cast(oldValue); + icon.assign(qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(icon), icon.mask()); + } else if (oldValue.userType() == qMetaTypeId()) { + qdesigner_internal::PropertySheetStringValue str = applyStringSubProperty( + qvariant_cast(oldValue), + qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(str), changed); + } else if (oldValue.userType() == qMetaTypeId()) { + qdesigner_internal::PropertySheetStringListValue str = applyStringListSubProperty( + qvariant_cast(oldValue), + qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(str), changed); + } else if (oldValue.userType() == qMetaTypeId()) { + qdesigner_internal::PropertySheetKeySequenceValue key = applyKeySequenceSubProperty( + qvariant_cast(oldValue), + qvariant_cast(newValue), mask); + return PropertyHelper::Value(QVariant::fromValue(key), changed); + } + // Enumerations, flags + switch (specialProperty) { + case qdesigner_internal::SP_Alignment: { + qdesigner_internal::PropertySheetFlagValue f = qvariant_cast(oldValue); + f.value = applyAlignmentSubProperty(variantToAlignment(oldValue), variantToAlignment(newValue), mask); + QVariant v; + v.setValue(f); + return PropertyHelper::Value(v, changed); + } + default: + break; + } + break; + } + return PropertyHelper::Value(newValue, changed); + +} +// figure out special property +enum SpecialProperty getSpecialProperty(const QString& propertyName) +{ + if (propertyName == "objectName"_L1) + return SP_ObjectName; + if (propertyName == "layoutName"_L1) + return SP_LayoutName; + if (propertyName == "spacerName"_L1) + return SP_SpacerName; + if (propertyName == "icon"_L1) + return SP_Icon; + if (propertyName == "currentTabName"_L1) + return SP_CurrentTabName; + if (propertyName == "currentItemName"_L1) + return SP_CurrentItemName; + if (propertyName == "currentPageName"_L1) + return SP_CurrentPageName; + if (propertyName == "geometry"_L1) + return SP_Geometry; + if (propertyName == "windowTitle"_L1) + return SP_WindowTitle; + if (propertyName == "minimumSize"_L1) + return SP_MinimumSize; + if (propertyName == "maximumSize"_L1) + return SP_MaximumSize; + if (propertyName == "alignment"_L1) + return SP_Alignment; + if (propertyName == "autoDefault"_L1) + return SP_AutoDefault; + if (propertyName == "shortcut"_L1) + return SP_Shortcut; + if (propertyName == "orientation"_L1) + return SP_Orientation; + return SP_None; +} + + +PropertyHelper::PropertyHelper(QObject* object, + SpecialProperty specialProperty, + QDesignerPropertySheetExtension *sheet, + int index) : + m_specialProperty(specialProperty), + m_object(object), + m_objectType(OT_Object), + m_propertySheet(sheet), m_index(index), + m_oldValue(m_propertySheet->property(m_index), m_propertySheet->isChanged(m_index)) +{ + if (object->isWidgetType()) { + m_parentWidget = (qobject_cast(object))->parentWidget(); + m_objectType = OT_Widget; + } else { + if (const QAction *action = qobject_cast(m_object)) { + const QObjectList associatedObjects = action->associatedObjects(); + auto it = std::find_if(associatedObjects.cbegin(), associatedObjects.cend(), + [](QObject *obj) { + return qobject_cast(obj) != nullptr; + }); + m_objectType = (it == associatedObjects.cend()) ? OT_FreeAction : OT_AssociatedAction; + } + } + + if(debugPropertyCommands) + qDebug() << "PropertyHelper on " << m_object->objectName() << " index= " << m_index << " type = " << m_objectType; +} + +QDesignerIntegration *PropertyHelper::integration(QDesignerFormWindowInterface *fw) const +{ + return qobject_cast(fw->core()->integration()); +} + +// Set widget value, apply corrections and checks in case of main window. +void PropertyHelper::checkApplyWidgetValue(QDesignerFormWindowInterface *fw, QWidget* w, + SpecialProperty specialProperty, QVariant &value) +{ + + bool isMainContainer = false; + if (QDesignerFormWindowCursorInterface *cursor = fw->cursor()) { + if (cursor->isWidgetSelected(w)) { + if (cursor->isWidgetSelected(fw->mainContainer())) { + isMainContainer = true; + } + } + } + if (!isMainContainer) + return; + + QWidget *container = fw->core()->integration()->containerWindow(fw); + if (!container) + return; + + + switch (specialProperty) { + case SP_MinimumSize: { + const QSize size = checkSize(value.toSize()); + value.setValue(size); + } + + break; + case SP_MaximumSize: { + QSize fs, cs; + checkSizes(fw, value.toSize(), &fs, &cs); + container->setMaximumSize(cs); + fw->mainContainer()->setMaximumSize(fs); + value.setValue(fs); + + } + break; + case SP_Geometry: { + QRect r = value.toRect(); + QSize fs, cs; + checkSizes(fw, r.size(), &fs, &cs); + container->resize(cs); + r.setSize(fs); + value.setValue(r); + } + break; + default: + break; + } +} + +unsigned PropertyHelper::updateMask() const +{ + unsigned rc = 0; + switch (m_specialProperty) { + case SP_ObjectName: + case SP_LayoutName: + case SP_SpacerName: + case SP_CurrentTabName: + case SP_CurrentItemName: + case SP_CurrentPageName: + if (m_objectType != OT_FreeAction) + rc |= UpdateObjectInspector; + break; + case SP_Icon: + if (m_objectType == OT_AssociatedAction) + rc |= UpdateObjectInspector; + break; + case SP_Orientation: // for updating splitter icon + rc |= UpdateObjectInspector; + break; + default: + break; + + } + return rc; +} + + +bool PropertyHelper::canMerge(const PropertyHelper &other) const +{ + return m_object == other.m_object && m_index == other.m_index; +} + +void PropertyHelper::triggerActionChanged(QAction *a) +{ + a->setData(QVariant(true)); // this triggers signal "changed" in QAction + a->setData(QVariant(false)); +} + +// Update the object to reflect the changes +void PropertyHelper::updateObject(QDesignerFormWindowInterface *fw, const QVariant &oldValue, const QVariant &newValue) +{ + if(debugPropertyCommands){ + qDebug() << "PropertyHelper::updateObject(" << m_object->objectName() << ") " << oldValue << " -> " << newValue; + } + switch (m_objectType) { + case OT_Widget: { + switch (m_specialProperty) { + case SP_ObjectName: { + const QString oldName = qvariant_cast(oldValue).value(); + const QString newName = qvariant_cast(newValue).value(); + QDesignerFormWindowCommand::updateBuddies(fw, oldName, newName); + } + break; + default: + break; + } + } break; + case OT_AssociatedAction: + case OT_FreeAction: + // SP_Shortcut is a fake property, so, QAction::changed does not trigger. + if (m_specialProperty == SP_ObjectName || m_specialProperty == SP_Shortcut) + triggerActionChanged(qobject_cast(m_object)); + break; + default: + break; + } + + switch (m_specialProperty) { + case SP_ObjectName: + case SP_LayoutName: + case SP_SpacerName: + if (QDesignerIntegration *integr = integration(fw)) { + const QString oldName = qvariant_cast(oldValue).value(); + const QString newName = qvariant_cast(newValue).value(); + integr->emitObjectNameChanged(fw, m_object, newName, oldName); + } + break; + default: + break; + } +} + +void PropertyHelper::ensureUniqueObjectName(QDesignerFormWindowInterface *fw, QObject *object) const +{ + switch (m_specialProperty) { + case SP_SpacerName: + if (object->isWidgetType()) { + if (Spacer *sp = qobject_cast(object)) { + fw->ensureUniqueObjectName(sp); + return; + } + } + fw->ensureUniqueObjectName(object); + break; + case SP_LayoutName: // Layout name is invoked on the parent widget. + if (object->isWidgetType()) { + const QWidget * w = qobject_cast(object); + if (QLayout *wlayout = w->layout()) { + fw->ensureUniqueObjectName(wlayout); + return; + } + } + fw->ensureUniqueObjectName(object); + break; + case SP_ObjectName: + fw->ensureUniqueObjectName(object); + break; + default: + break; + } +} + +PropertyHelper::Value PropertyHelper::setValue(QDesignerFormWindowInterface *fw, const QVariant &value, + bool changed, quint64 subPropertyMask) +{ + // Set new whole value + if (subPropertyMask == SubPropertyAll) + return applyValue(fw, m_oldValue.first, Value(value, changed)); + + // apply subproperties + const PropertyHelper::Value maskedNewValue = applySubProperty(m_oldValue.first, value, m_specialProperty, subPropertyMask, changed); + return applyValue(fw, m_oldValue.first, maskedNewValue); +} + +// Apply the value and update. Returns corrected value +PropertyHelper::Value PropertyHelper::applyValue(QDesignerFormWindowInterface *fw, const QVariant &oldValue, Value newValue) +{ + if(debugPropertyCommands){ + qDebug() << "PropertyHelper::applyValue(" << m_object << ") " << oldValue << " -> " << newValue.first << " changed=" << newValue.second; + } + + if (m_objectType == OT_Widget) { + checkApplyWidgetValue(fw, qobject_cast(m_object), m_specialProperty, newValue.first); + } + + m_propertySheet->setProperty(m_index, newValue.first); + m_propertySheet->setChanged(m_index, newValue.second); + + switch (m_specialProperty) { + case SP_LayoutName: + case SP_ObjectName: + case SP_SpacerName: + ensureUniqueObjectName(fw, m_object); + newValue.first = m_propertySheet->property(m_index); + break; + default: + break; + } + + updateObject(fw, oldValue, newValue.first); + return newValue; +} + +PropertyHelper::Value PropertyHelper::restoreOldValue(QDesignerFormWindowInterface *fw) +{ + return applyValue(fw, m_propertySheet->property(m_index), m_oldValue); +} + +// find the default value in widget DB in case PropertySheet::reset fails +QVariant PropertyHelper::findDefaultValue(QDesignerFormWindowInterface *fw) const +{ + if (m_specialProperty == SP_AutoDefault && qobject_cast(m_object)) { + // AutoDefault defaults to true on dialogs + const bool isDialog = qobject_cast(fw->mainContainer()); + return QVariant(isDialog); + } + + const int item_idx = fw->core()->widgetDataBase()->indexOfObject(m_object); + if (item_idx == -1) + return m_oldValue.first; // We simply don't know the value in this case + + const QDesignerWidgetDataBaseItemInterface *item = fw->core()->widgetDataBase()->item(item_idx); + const auto default_prop_values = item->defaultPropertyValues(); + if (m_index < default_prop_values.size()) + return default_prop_values.at(m_index); + + if (m_oldValue.first.metaType().id() == QMetaType::QColor) + return QColor(); + + return m_oldValue.first; // Again, we just don't know +} + +PropertyHelper::Value PropertyHelper::restoreDefaultValue(QDesignerFormWindowInterface *fw) +{ + + Value defaultValue{{}, false}; + const QVariant currentValue = m_propertySheet->property(m_index); + // try to reset sheet, else try to find default + if (m_propertySheet->reset(m_index)) { + defaultValue.first = m_propertySheet->property(m_index); + } else { + defaultValue.first = findDefaultValue(fw); + m_propertySheet->setProperty(m_index, defaultValue.first); + } + + m_propertySheet->setChanged(m_index, defaultValue.second); + + if (m_objectType == OT_Widget) { + checkApplyWidgetValue(fw, qobject_cast(m_object), m_specialProperty, defaultValue.first); + } + + switch (m_specialProperty) { + case SP_LayoutName: + case SP_ObjectName: + case SP_SpacerName: + ensureUniqueObjectName(fw, m_object); + defaultValue.first = m_propertySheet->property(m_index); + break; + default: + break; + } + + updateObject(fw, currentValue, defaultValue.first); + return defaultValue; +} + +// ---- PropertyListCommand::PropertyDescription( + + +PropertyListCommand::PropertyDescription::PropertyDescription(const QString &propertyName, + QDesignerPropertySheetExtension *propertySheet, + int index) : + m_propertyName(propertyName), + m_propertyGroup(propertySheet->propertyGroup(index)), + m_propertyType(propertySheet->property(index).metaType().id()), + m_specialProperty(getSpecialProperty(propertyName)) +{ +} + +void PropertyListCommand::PropertyDescription::debug() const +{ + qDebug() << m_propertyName << m_propertyGroup << m_propertyType << m_specialProperty; +} + +bool PropertyListCommand::PropertyDescription::equals(const PropertyDescription &p) const +{ + return m_propertyType == p.m_propertyType && m_specialProperty == p.m_specialProperty && + m_propertyName == p.m_propertyName && m_propertyGroup == p.m_propertyGroup; +} + + +// ---- PropertyListCommand +PropertyListCommand::PropertyListCommand(QDesignerFormWindowInterface *formWindow, + QUndoCommand *parent) : + QDesignerFormWindowCommand(QString(), formWindow, parent) +{ +} + +const QString PropertyListCommand::propertyName() const +{ + return m_propertyDescription.m_propertyName; +} + +SpecialProperty PropertyListCommand::specialProperty() const +{ + return m_propertyDescription.m_specialProperty; +} + +// add an object +bool PropertyListCommand::add(QObject *object, const QString &propertyName) +{ + QDesignerPropertySheetExtension* sheet = propertySheet(object); + Q_ASSERT(sheet); + + const int index = sheet->indexOf(propertyName); + if (index == -1) + return false; + + if (!sheet->isEnabled(index)) + return false; + + const PropertyDescription description(propertyName, sheet, index); + + if (m_propertyHelperList.empty()) { + // first entry + m_propertyDescription = description; + } else { + // checks: mismatch or only one object in case of name + const bool match = m_propertyDescription.equals(description); + if (!match || m_propertyDescription.m_specialProperty == SP_ObjectName) + return false; + } + + auto ph = createPropertyHelper(object, m_propertyDescription.m_specialProperty, sheet, index); + m_propertyHelperList.push_back(std::move(ph)); + return true; +} + +std::unique_ptr +PropertyListCommand::createPropertyHelper(QObject *object, SpecialProperty sp, + QDesignerPropertySheetExtension *sheet, int sheetIndex) const +{ + return std::make_unique(object, sp, sheet, sheetIndex); +} + +// Init from a list and make sure referenceObject is added first to obtain the right property group +bool PropertyListCommand::initList(const QObjectList &list, const QString &apropertyName, QObject *referenceObject) +{ + m_propertyHelperList.clear(); + + // Ensure the referenceObject (property editor) is first, so the right property group is chosen. + if (referenceObject) { + if (!add(referenceObject, apropertyName)) + return false; + } + for (QObject *o : list) { + if (o != referenceObject) + add(o, apropertyName); + } + + return !m_propertyHelperList.empty(); +} + + +QObject* PropertyListCommand::object(int index) const +{ + Q_ASSERT(size_t(index) < m_propertyHelperList.size()); + return m_propertyHelperList[index]->object(); +} + +QVariant PropertyListCommand::oldValue(int index) const +{ + Q_ASSERT(size_t(index) < m_propertyHelperList.size()); + return m_propertyHelperList[index]->oldValue(); +} + +void PropertyListCommand::setOldValue(const QVariant &oldValue, int index) +{ + Q_ASSERT(size_t(index) < m_propertyHelperList.size()); + m_propertyHelperList[index]->setOldValue(oldValue); +} +// ----- SetValueFunction: Set a new value when applied to a PropertyHelper. +class SetValueFunction { +public: + SetValueFunction(QDesignerFormWindowInterface *formWindow, const PropertyHelper::Value &newValue, + quint64 subPropertyMask); + + PropertyHelper::Value operator()(PropertyHelper&); +private: + QDesignerFormWindowInterface *m_formWindow; + const PropertyHelper::Value m_newValue; + const quint64 m_subPropertyMask; +}; + + +SetValueFunction::SetValueFunction(QDesignerFormWindowInterface *formWindow, + const PropertyHelper::Value &newValue, + quint64 subPropertyMask) : + m_formWindow(formWindow), + m_newValue(newValue), + m_subPropertyMask(subPropertyMask) +{ +} + +PropertyHelper::Value SetValueFunction::operator()(PropertyHelper &ph) { + return ph.setValue(m_formWindow, m_newValue.first, m_newValue.second, m_subPropertyMask); +} + +// ----- UndoSetValueFunction: Restore old value when applied to a PropertyHelper. +class UndoSetValueFunction { +public: + UndoSetValueFunction(QDesignerFormWindowInterface *formWindow) : m_formWindow(formWindow) {} + PropertyHelper::Value operator()(PropertyHelper& ph) { return ph.restoreOldValue(m_formWindow); } +private: + QDesignerFormWindowInterface *m_formWindow; +}; + +// ----- RestoreDefaultFunction: Restore default value when applied to a PropertyHelper. +class RestoreDefaultFunction { +public: + RestoreDefaultFunction(QDesignerFormWindowInterface *formWindow) : m_formWindow(formWindow) {} + PropertyHelper::Value operator()(PropertyHelper& ph) { return ph.restoreDefaultValue(m_formWindow); } +private: + QDesignerFormWindowInterface *m_formWindow; +}; + +// ----- changePropertyList: Iterates over a sequence of PropertyHelpers and +// applies a function to them. +// The function returns the corrected value which is then set in the property editor. +// Returns a combination of update flags. +template + unsigned changePropertyList(QDesignerFormEditorInterface *core, + const QString &propertyName, + PropertyListIterator begin, + PropertyListIterator end, + Function function) +{ + unsigned updateMask = 0; + QDesignerPropertyEditorInterface *propertyEditor = core->propertyEditor(); + bool updatedPropertyEditor = false; + + for (auto it = begin; it != end; ++it) { + PropertyHelper *ph = it->get(); + if (QObject* object = ph->object()) { // Might have been deleted in the meantime + const PropertyHelper::Value newValue = function( *ph ); + updateMask |= ph->updateMask(); + // Update property editor if it is the current object + if (!updatedPropertyEditor && propertyEditor && object == propertyEditor->object()) { + propertyEditor->setPropertyValue(propertyName, newValue.first, newValue.second); + updatedPropertyEditor = true; + } + } + } + if (!updatedPropertyEditor) updateMask |= PropertyHelper::UpdatePropertyEditor; + return updateMask; +} + + +// set a new value, return update mask +unsigned PropertyListCommand::setValue(const QVariant &value, bool changed, quint64 subPropertyMask) +{ + if(debugPropertyCommands) + qDebug() << "PropertyListCommand::setValue(" << value + << changed << subPropertyMask << ')'; + return changePropertyList(formWindow()->core(), + m_propertyDescription.m_propertyName, + m_propertyHelperList.begin(), m_propertyHelperList.end(), + SetValueFunction(formWindow(), PropertyHelper::Value(value, changed), subPropertyMask)); +} + +// restore old value, return update mask +unsigned PropertyListCommand::restoreOldValue() +{ + if(debugPropertyCommands) + qDebug() << "PropertyListCommand::restoreOldValue()"; + + return changePropertyList(formWindow()->core(), + m_propertyDescription.m_propertyName, m_propertyHelperList.begin(), m_propertyHelperList.end(), + UndoSetValueFunction(formWindow())); +} +// set default value, return update mask +unsigned PropertyListCommand::restoreDefaultValue() +{ + if(debugPropertyCommands) + qDebug() << "PropertyListCommand::restoreDefaultValue()"; + + return changePropertyList(formWindow()->core(), + m_propertyDescription.m_propertyName, m_propertyHelperList.begin(), m_propertyHelperList.end(), + RestoreDefaultFunction(formWindow())); +} + +// update +void PropertyListCommand::update(unsigned updateMask) +{ + if(debugPropertyCommands) + qDebug() << "PropertyListCommand::update(" << updateMask << ')'; + + if (updateMask & PropertyHelper::UpdateObjectInspector) { + if (QDesignerObjectInspectorInterface *oi = formWindow()->core()->objectInspector()) + oi->setFormWindow(formWindow()); + } + + if (updateMask & PropertyHelper::UpdatePropertyEditor) { + // this is needed when f.ex. undo, changes parent's palette, but + // the child is the active widget, + // TODO: current object? + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + propertyEditor->setObject(propertyEditor->object()); + } + } +} + +void PropertyListCommand::undo() +{ + update(restoreOldValue()); + QDesignerPropertyEditor *designerPropertyEditor = qobject_cast(core()->propertyEditor()); + if (designerPropertyEditor) + designerPropertyEditor->updatePropertySheet(); +} + +// check if lists are aequivalent for command merging (same widgets and props) +bool PropertyListCommand::canMergeLists(const PropertyHelperList& other) const +{ + if (m_propertyHelperList.size() != other.size()) + return false; + for (size_t i = 0; i < m_propertyHelperList.size(); ++i) { + if (!m_propertyHelperList[i]->canMerge(*other[i])) + return false; + } + return true; +} + +// ---- SetPropertyCommand ---- +SetPropertyCommand::SetPropertyCommand(QDesignerFormWindowInterface *formWindow, + QUndoCommand *parent) + : PropertyListCommand(formWindow, parent), + m_subPropertyMask(SubPropertyAll) +{ +} + +bool SetPropertyCommand::init(QObject *object, const QString &apropertyName, const QVariant &newValue) +{ + Q_ASSERT(object); + + m_newValue = newValue; + + propertyHelperList().clear(); + if (!add(object, apropertyName)) + return false; + + setDescription(); + return true; +} + +bool SetPropertyCommand::init(const QObjectList &list, const QString &apropertyName, const QVariant &newValue, + QObject *referenceObject, bool enableSubPropertyHandling) +{ + if (!initList(list, apropertyName, referenceObject)) + return false; + + m_newValue = newValue; + + if(debugPropertyCommands) + qDebug() << "SetPropertyCommand::init()" << propertyHelperList().size() << '/' << list.size() << " reference " << referenceObject; + + setDescription(); + + if (enableSubPropertyHandling) + m_subPropertyMask = subPropertyMask(newValue, referenceObject); + return true; +} + +quint64 SetPropertyCommand::subPropertyMask(const QVariant &newValue, QObject *referenceObject) +{ + // figure out the mask of changed sub properties when comparing newValue to the current value of the reference object. + if (!referenceObject) + return SubPropertyAll; + + QDesignerPropertySheetExtension* sheet = propertySheet(referenceObject); + Q_ASSERT(sheet); + + const int index = sheet->indexOf(propertyName()); + if (index == -1 || !sheet->isVisible(index)) + return SubPropertyAll; + + return compareSubProperties(sheet->property(index), newValue, specialProperty()); +} + +void SetPropertyCommand::setDescription() +{ + if (propertyHelperList().size() == 1) { + setText(QApplication::translate("Command", "Changed '%1' of '%2'") + .arg(propertyName(), propertyHelperList().front()->object()->objectName())); + } else { + int count = static_cast(propertyHelperList().size()); + setText(QCoreApplication::translate("Command", "Changed '%1' of %n objects", "", count).arg(propertyName())); + } +} + +void SetPropertyCommand::redo() +{ + update(setValue(m_newValue, true, m_subPropertyMask)); + QDesignerPropertyEditor *designerPropertyEditor = qobject_cast(core()->propertyEditor()); + if (designerPropertyEditor) + designerPropertyEditor->updatePropertySheet(); +} + + +int SetPropertyCommand::id() const +{ + return 1976; +} + +QVariant SetPropertyCommand::mergeValue(const QVariant &newValue) +{ + return newValue; +} + +bool SetPropertyCommand::mergeWith(const QUndoCommand *other) +{ + if (id() != other->id() || !formWindow()->isDirty()) + return false; + + // Merging: When for example when the user types ahead in an inplace-editor, + // it makes sense to merge all the generated commands containing the one-character changes. + // In the case of subproperties, if the user changes the font size from 10 to 30 via 20 + // and then changes to bold, it makes sense to merge the font size commands only. + // This is why the m_subPropertyMask is checked. + + const SetPropertyCommand *cmd = static_cast(other); + if (!propertyDescription().equals(cmd->propertyDescription()) || + m_subPropertyMask != cmd->m_subPropertyMask || + !canMergeLists(cmd->propertyHelperList())) + return false; + + const QVariant newValue = mergeValue(cmd->newValue()); + if (!newValue.isValid()) + return false; + m_newValue = newValue; + m_subPropertyMask |= cmd->m_subPropertyMask; + if(debugPropertyCommands) + qDebug() << "SetPropertyCommand::mergeWith() succeeded " << propertyName(); + + return true; +} + +// ---- ResetPropertyCommand ---- +ResetPropertyCommand::ResetPropertyCommand(QDesignerFormWindowInterface *formWindow) + : PropertyListCommand(formWindow) +{ +} + +bool ResetPropertyCommand::init(QObject *object, const QString &apropertyName) +{ + Q_ASSERT(object); + + propertyHelperList().clear(); + if (!add(object, apropertyName)) + return false; + + setDescription(); + return true; +} + +bool ResetPropertyCommand::init(const QObjectList &list, const QString &apropertyName, QObject *referenceObject) +{ + QObjectList modifiedList = list; // filter out modified properties + for (auto it = modifiedList.begin(); it != modifiedList.end() ; ) { + QDesignerPropertySheetExtension* sheet = propertySheet(*it); + Q_ASSERT(sheet); + const int index = sheet->indexOf(apropertyName); + if (index == -1 || !sheet->isChanged(index)) + it = modifiedList.erase(it); + else + ++it; + } + if (!modifiedList.contains(referenceObject)) + referenceObject = nullptr; + if (modifiedList.isEmpty() || !initList(modifiedList, apropertyName, referenceObject)) + return false; + + if(debugPropertyCommands) + qDebug() << "ResetPropertyCommand::init()" << propertyHelperList().size() << '/' << list.size(); + + setDescription(); + return true; +} + +void ResetPropertyCommand::setDescription() +{ + if (propertyHelperList().size() == 1) { + setText(QCoreApplication::translate("Command", "Reset '%1' of '%2'") + .arg(propertyName(), propertyHelperList().front()->object()->objectName())); + } else { + int count = static_cast(propertyHelperList().size()); + setText(QCoreApplication::translate("Command", "Reset '%1' of %n objects", "", count).arg(propertyName())); + } +} + +void ResetPropertyCommand::redo() +{ + update(restoreDefaultValue()); + QDesignerPropertyEditor *designerPropertyEditor = qobject_cast(core()->propertyEditor()); + if (designerPropertyEditor) + designerPropertyEditor->updatePropertySheet(); +} + +AddDynamicPropertyCommand::AddDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ + +} + +bool AddDynamicPropertyCommand::init(const QObjectList &selection, QObject *current, + const QString &propertyName, const QVariant &value) +{ + Q_ASSERT(current); + m_propertyName = propertyName; + + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), current); + Q_ASSERT(dynamicSheet); + + m_selection.clear(); + + if (!value.isValid()) + return false; + + if (!dynamicSheet->canAddDynamicProperty(m_propertyName)) + return false; + + m_selection.append(current); + + m_value = value; + + for (QObject *obj : selection) { + if (m_selection.contains(obj)) + continue; + dynamicSheet = qt_extension(core->extensionManager(), obj); + Q_ASSERT(dynamicSheet); + if (dynamicSheet->canAddDynamicProperty(m_propertyName)) + m_selection.append(obj); + } + + setDescription(); + return true; +} + +void AddDynamicPropertyCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + for (QObject *obj : std::as_const(m_selection)) { + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), obj); + dynamicSheet->addDynamicProperty(m_propertyName, m_value); + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == obj) + propertyEditor->setObject(obj); + } + } +} + +void AddDynamicPropertyCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + for (QObject *obj : std::as_const(m_selection)) { + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), obj); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), obj); + dynamicSheet->removeDynamicProperty(sheet->indexOf(m_propertyName)); + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == obj) + propertyEditor->setObject(obj); + } + } +} + +void AddDynamicPropertyCommand::setDescription() +{ + if (m_selection.size() == 1) { + setText(QApplication::translate("Command", "Add dynamic property '%1' to '%2'") + .arg(m_propertyName, m_selection.first()->objectName())); + } else { + int count = m_selection.size(); + setText(QCoreApplication::translate("Command", "Add dynamic property '%1' to %n objects", "", count) + .arg(m_propertyName)); + } +} + + +RemoveDynamicPropertyCommand::RemoveDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow) + : QDesignerFormWindowCommand(QString(), formWindow) +{ + +} + +bool RemoveDynamicPropertyCommand::init(const QObjectList &selection, QObject *current, + const QString &propertyName) +{ + Q_ASSERT(current); + m_propertyName = propertyName; + + QDesignerFormEditorInterface *core = formWindow()->core(); + QDesignerPropertySheetExtension *propertySheet = qt_extension(core->extensionManager(), current); + Q_ASSERT(propertySheet); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), current); + Q_ASSERT(dynamicSheet); + + m_objectToValueAndChanged.clear(); + + const int index = propertySheet->indexOf(m_propertyName); + if (!dynamicSheet->isDynamicProperty(index)) + return false; + + m_objectToValueAndChanged[current] = {propertySheet->property(index), + propertySheet->isChanged(index)}; + + for (QObject *obj : selection) { + if (m_objectToValueAndChanged.contains(obj)) + continue; + + propertySheet = qt_extension(core->extensionManager(), obj); + dynamicSheet = qt_extension(core->extensionManager(), obj); + const int idx = propertySheet->indexOf(m_propertyName); + if (dynamicSheet->isDynamicProperty(idx)) + m_objectToValueAndChanged[obj] = {propertySheet->property(idx), + propertySheet->isChanged(idx)}; + } + + setDescription(); + return true; +} + +void RemoveDynamicPropertyCommand::redo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + for (auto it = m_objectToValueAndChanged.cbegin(), end = m_objectToValueAndChanged.cend(); it != end; ++it) { + QObject *obj = it.key(); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), obj); + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), obj); + dynamicSheet->removeDynamicProperty(sheet->indexOf(m_propertyName)); + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == obj) + propertyEditor->setObject(obj); + } + } +} + +void RemoveDynamicPropertyCommand::undo() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + for (auto it = m_objectToValueAndChanged.cbegin(), end = m_objectToValueAndChanged.cend(); it != end; ++it) { + QObject *obj = it.key(); + QDesignerPropertySheetExtension *propertySheet = qt_extension(core->extensionManager(), obj); + QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), obj); + const int index = dynamicSheet->addDynamicProperty(m_propertyName, it.value().first); + propertySheet->setChanged(index, it.value().second); + if (QDesignerPropertyEditorInterface *propertyEditor = formWindow()->core()->propertyEditor()) { + if (propertyEditor->object() == obj) + propertyEditor->setObject(obj); + } + } +} + +void RemoveDynamicPropertyCommand::setDescription() +{ + if (m_objectToValueAndChanged.size() == 1) { + setText(QApplication::translate("Command", + "Remove dynamic property '%1' from '%2'") + .arg(m_propertyName, m_objectToValueAndChanged.constBegin().key()->objectName())); + } else { + int count = m_objectToValueAndChanged.size(); + setText(QApplication::translate("Command", + "Remove dynamic property '%1' from %n objects", "", count) + .arg(m_propertyName)); + } +} + + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_propertycommand_p.h b/src/tools/designer/src/lib/shared/qdesigner_propertycommand_p.h new file mode 100644 index 00000000000..d069e76b13d --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_propertycommand_p.h @@ -0,0 +1,272 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_PROPERTYCOMMAND_H +#define QDESIGNER_PROPERTYCOMMAND_H + +#include "qdesigner_formwindowcommand_p.h" + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerPropertySheetExtension; +class QDesignerIntegration; + +namespace qdesigner_internal { + +enum SpecialProperty { + SP_None, SP_ObjectName, SP_LayoutName, SP_SpacerName,SP_WindowTitle, + SP_MinimumSize, SP_MaximumSize, SP_Geometry, SP_Icon, SP_CurrentTabName, SP_CurrentItemName, SP_CurrentPageName, + SP_AutoDefault, SP_Alignment, SP_Shortcut, SP_Orientation +}; + +//Determine special property +enum SpecialProperty getSpecialProperty(const QString& propertyName); + +// A helper class for applying properties to objects. +// Can be used for Set commands (setValue(), restoreOldValue()) or +// Reset Commands restoreDefaultValue(), restoreOldValue()). +// +class QDESIGNER_SHARED_EXPORT PropertyHelper { + Q_DISABLE_COPY(PropertyHelper) +public: + // A pair of Value and changed flag + using Value = std::pair; + + enum ObjectType {OT_Object, OT_FreeAction, OT_AssociatedAction, OT_Widget}; + + PropertyHelper(QObject* object, + SpecialProperty specialProperty, + QDesignerPropertySheetExtension *sheet, + int index); + virtual ~PropertyHelper() = default; + + QObject *object() const { return m_object; } + SpecialProperty specialProperty() const { return m_specialProperty; } + // set a new value. Can be overwritten to perform a transformation (see + // handling of Arrow key move in FormWindow class). + virtual Value setValue(QDesignerFormWindowInterface *fw, const QVariant &value, + bool changed, quint64 subPropertyMask); + + // restore old value + Value restoreOldValue(QDesignerFormWindowInterface *fw); + // set default value + Value restoreDefaultValue(QDesignerFormWindowInterface *fw); + + inline QVariant oldValue() const + { return m_oldValue.first; } + + inline void setOldValue(const QVariant &oldValue) + { m_oldValue.first = oldValue; } + + // required updates for this property (bit mask) + enum UpdateMask { UpdatePropertyEditor=1, UpdateObjectInspector=2 }; + unsigned updateMask() const; + + // can be merged into one command (that is, object and name match) + bool canMerge(const PropertyHelper &other) const; + QDesignerIntegration *integration(QDesignerFormWindowInterface *fw) const; + + static void triggerActionChanged(QAction *a); + +private: + // Apply the value and update. Returns corrected value + Value applyValue(QDesignerFormWindowInterface *fw, const QVariant &oldValue, Value newValue); + + static void checkApplyWidgetValue(QDesignerFormWindowInterface *fw, QWidget* w, + SpecialProperty specialProperty, QVariant &v); + + void updateObject(QDesignerFormWindowInterface *fw, const QVariant &oldValue, const QVariant &newValue); + QVariant findDefaultValue(QDesignerFormWindowInterface *fw) const; + void ensureUniqueObjectName(QDesignerFormWindowInterface *fw, QObject *object) const; + SpecialProperty m_specialProperty; + + QPointer m_object; + ObjectType m_objectType; + QPointer m_parentWidget; + + QDesignerPropertySheetExtension *m_propertySheet; + int m_index; + + Value m_oldValue; +}; + +// Base class for commands that can be applied to several widgets + +class QDESIGNER_SHARED_EXPORT PropertyListCommand : public QDesignerFormWindowCommand { +public: + explicit PropertyListCommand(QDesignerFormWindowInterface *formWindow, QUndoCommand *parent = nullptr); + + QObject* object(int index = 0) const; + + QVariant oldValue(int index = 0) const; + + void setOldValue(const QVariant &oldValue, int index = 0); + + // Calls restoreDefaultValue() and update() + void undo() override; + +protected: + using PropertyHelperPtr = std::unique_ptr; + using PropertyHelperList = std::vector; + + // add an object + bool add(QObject *object, const QString &propertyName); + + // Init from a list and make sure referenceObject is added first to obtain the right property group + bool initList(const QObjectList &list, const QString &apropertyName, QObject *referenceObject = nullptr); + + // set a new value, return update mask + unsigned setValue(const QVariant &value, bool changed, quint64 subPropertyMask); + + // restore old value, return update mask + unsigned restoreOldValue(); + // set default value, return update mask + unsigned restoreDefaultValue(); + + // update designer + void update(unsigned updateMask); + + // check if lists are aequivalent for command merging (same widgets and props) + bool canMergeLists(const PropertyHelperList& other) const; + + PropertyHelperList& propertyHelperList() { return m_propertyHelperList; } + const PropertyHelperList& propertyHelperList() const { return m_propertyHelperList; } + + const QString propertyName() const; + SpecialProperty specialProperty() const; + + // Helper struct describing a property used for checking whether + // properties of different widgets are equivalent + struct PropertyDescription { + public: + PropertyDescription() = default; + PropertyDescription(const QString &propertyName, QDesignerPropertySheetExtension *propertySheet, int index); + bool equals(const PropertyDescription &p) const; + void debug() const; + + QString m_propertyName; + QString m_propertyGroup; + int m_propertyType = QMetaType::UnknownType; + SpecialProperty m_specialProperty = SP_None; + }; + const PropertyDescription &propertyDescription() const { return m_propertyDescription; } + +protected: + virtual std::unique_ptr + createPropertyHelper(QObject *o, SpecialProperty sp, + QDesignerPropertySheetExtension *sheet, int sheetIndex) const; + +private: + PropertyDescription m_propertyDescription; + PropertyHelperList m_propertyHelperList; +}; + +class QDESIGNER_SHARED_EXPORT SetPropertyCommand: public PropertyListCommand +{ + +public: + explicit SetPropertyCommand(QDesignerFormWindowInterface *formWindow, QUndoCommand *parent = nullptr); + + bool init(QObject *object, const QString &propertyName, const QVariant &newValue); + bool init(const QObjectList &list, const QString &propertyName, const QVariant &newValue, + QObject *referenceObject = nullptr, bool enableSubPropertyHandling = true); + + + inline QVariant newValue() const + { return m_newValue; } + + inline void setNewValue(const QVariant &newValue) + { m_newValue = newValue; } + + int id() const override; + bool mergeWith(const QUndoCommand *other) override; + + void redo() override; + +protected: + virtual QVariant mergeValue(const QVariant &newValue); + +private: + quint64 subPropertyMask(const QVariant &newValue, QObject *referenceObject); + void setDescription(); + QVariant m_newValue; + quint64 m_subPropertyMask; +}; + +class QDESIGNER_SHARED_EXPORT ResetPropertyCommand: public PropertyListCommand +{ + +public: + explicit ResetPropertyCommand(QDesignerFormWindowInterface *formWindow); + + bool init(QObject *object, const QString &propertyName); + bool init(const QObjectList &list, const QString &propertyName, QObject *referenceObject = nullptr); + + void redo() override; + +protected: + bool mergeWith(const QUndoCommand *) override { return false; } + +private: + void setDescription(); + QString m_propertyName; +}; + + +class QDESIGNER_SHARED_EXPORT AddDynamicPropertyCommand: public QDesignerFormWindowCommand +{ + +public: + explicit AddDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow); + + bool init(const QObjectList &selection, QObject *current, const QString &propertyName, const QVariant &value); + + void redo() override; + void undo() override; +private: + void setDescription(); + QString m_propertyName; + QObjectList m_selection; + QVariant m_value; +}; + +class QDESIGNER_SHARED_EXPORT RemoveDynamicPropertyCommand: public QDesignerFormWindowCommand +{ + +public: + explicit RemoveDynamicPropertyCommand(QDesignerFormWindowInterface *formWindow); + + bool init(const QObjectList &selection, QObject *current, const QString &propertyName); + + void redo() override; + void undo() override; +private: + void setDescription(); + QString m_propertyName; + QHash > m_objectToValueAndChanged; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_PROPERTYCOMMAND_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_propertyeditor.cpp b/src/tools/designer/src/lib/shared/qdesigner_propertyeditor.cpp new file mode 100644 index 00000000000..42a04b7531f --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_propertyeditor.cpp @@ -0,0 +1,155 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_propertyeditor_p.h" +#include "pluginmanager_p.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +using StringPropertyParameters = QDesignerPropertyEditor::StringPropertyParameters; +// A map of property name to type +using PropertyNameTypeMap = QHash; + +// Compile a map of hard-coded string property types +static const PropertyNameTypeMap &stringPropertyTypes() +{ + static PropertyNameTypeMap propertyNameTypeMap; + if (propertyNameTypeMap.isEmpty()) { + const StringPropertyParameters richtext(ValidationRichText, true); + // Accessibility. Both are texts the narrator reads + propertyNameTypeMap.insert(u"accessibleDescription"_s, richtext); + propertyNameTypeMap.insert(u"accessibleName"_s, richtext); + // object names + const StringPropertyParameters objectName(ValidationObjectName, false); + propertyNameTypeMap.insert(u"buddy"_s, objectName); + propertyNameTypeMap.insert(u"currentItemName"_s, objectName); + propertyNameTypeMap.insert(u"currentPageName"_s, objectName); + propertyNameTypeMap.insert(u"currentTabName"_s, objectName); + propertyNameTypeMap.insert(u"layoutName"_s, objectName); + propertyNameTypeMap.insert(u"spacerName"_s, objectName); + // Style sheet + propertyNameTypeMap.insert(u"styleSheet"_s, StringPropertyParameters(ValidationStyleSheet, false)); + // Buttons/ QCommandLinkButton + const StringPropertyParameters multiline(ValidationMultiLine, true); + propertyNameTypeMap.insert(u"description"_s, multiline); + propertyNameTypeMap.insert(u"iconText"_s, multiline); + // Tooltips, etc. + propertyNameTypeMap.insert(u"toolTip"_s, richtext); + propertyNameTypeMap.insert(u"whatsThis"_s, richtext); + propertyNameTypeMap.insert(u"windowIconText"_s, richtext); + propertyNameTypeMap.insert(u"html"_s, richtext); + // A QWizard page id + propertyNameTypeMap.insert(u"pageId"_s, StringPropertyParameters(ValidationSingleLine, false)); + // QPlainTextEdit + propertyNameTypeMap.insert(u"plainText"_s, StringPropertyParameters(ValidationMultiLine, true)); + } + return propertyNameTypeMap; +} + +QDesignerPropertyEditor::QDesignerPropertyEditor(QWidget *parent, Qt::WindowFlags flags) : + QDesignerPropertyEditorInterface(parent, flags) +{ + // Make old signal work for compatibility + connect(this, &QDesignerPropertyEditorInterface::propertyChanged, + this, &QDesignerPropertyEditor::slotPropertyChanged); +} + +static inline bool isDynamicProperty(QDesignerFormEditorInterface *core, QObject *object, + const QString &propertyName) +{ + if (const QDesignerDynamicPropertySheetExtension *dynamicSheet = qt_extension(core->extensionManager(), object)) { + if (dynamicSheet->dynamicPropertiesAllowed()) { + if (QDesignerPropertySheetExtension *propertySheet = qt_extension(core->extensionManager(), object)) { + const int index = propertySheet->indexOf(propertyName); + return index >= 0 && dynamicSheet->isDynamicProperty(index); + } + } + } + return false; +} + +QDesignerPropertyEditor::StringPropertyParameters QDesignerPropertyEditor::textPropertyValidationMode( + QDesignerFormEditorInterface *core, const QObject *object, + const QString &propertyName, bool isMainContainer) +{ + // object name - no comment + if (propertyName == "objectName"_L1) { + const TextPropertyValidationMode vm = isMainContainer ? ValidationObjectNameScope : ValidationObjectName; + return StringPropertyParameters(vm, false); + } + + // Check custom widgets by class. + const QString className = WidgetFactory::classNameOf(core, object); + const QDesignerCustomWidgetData customData = core->pluginManager()->customWidgetData(className); + if (!customData.isNull()) { + StringPropertyParameters customType; + if (customData.xmlStringPropertyType(propertyName, &customType)) + return customType; + } + + if (isDynamicProperty(core, const_cast(object), propertyName)) + return StringPropertyParameters(ValidationMultiLine, true); + + // Check hardcoded property ames + const auto hit = stringPropertyTypes().constFind(propertyName); + if (hit != stringPropertyTypes().constEnd()) + return hit.value(); + + // text: Check according to widget type. + if (propertyName == "text"_L1) { + if (qobject_cast(object) || qobject_cast(object)) + return StringPropertyParameters(ValidationSingleLine, true); + if (qobject_cast(object)) + return StringPropertyParameters(ValidationMultiLine, true); + return StringPropertyParameters(ValidationRichText, true); + } + + // Fuzzy matching + if (propertyName.endsWith("Name"_L1)) + return StringPropertyParameters(ValidationSingleLine, true); + + if (propertyName.endsWith("ToolTip"_L1)) + return StringPropertyParameters(ValidationRichText, true); + +#ifdef Q_OS_WIN // No translation for the active X "control" property + if (propertyName == "control"_L1 && className == "QAxWidget"_L1) + return StringPropertyParameters(ValidationSingleLine, false); +#endif + + // default to single + return StringPropertyParameters(ValidationSingleLine, true); +} + +void QDesignerPropertyEditor::emitPropertyValueChanged(const QString &name, const QVariant &value, bool enableSubPropertyHandling) +{ + // Avoid duplicate signal emission - see below + m_propertyChangedForwardingBlocked = true; + emit propertyValueChanged(name, value, enableSubPropertyHandling); + emit propertyChanged(name, value); + m_propertyChangedForwardingBlocked = false; +} + +void QDesignerPropertyEditor::slotPropertyChanged(const QString &name, const QVariant &value) +{ + // Forward signal from Integration using the old interfaces. + if (!m_propertyChangedForwardingBlocked) + emit propertyValueChanged(name, value, true); +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_propertyeditor_p.h b/src/tools/designer/src/lib/shared/qdesigner_propertyeditor_p.h new file mode 100644 index 00000000000..0aa1feb456b --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_propertyeditor_p.h @@ -0,0 +1,74 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + + +#ifndef DESIGNERPROPERTYEDITOR_H +#define DESIGNERPROPERTYEDITOR_H + +#include "shared_global_p.h" +#include "shared_enums_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + +// Extends the QDesignerPropertyEditorInterface by property comment handling and +// a signal for resetproperty. + +class QDESIGNER_SHARED_EXPORT QDesignerPropertyEditor: public QDesignerPropertyEditorInterface +{ + Q_OBJECT +public: + explicit QDesignerPropertyEditor(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + + // A pair . + using StringPropertyParameters = std::pair; + + // Return a pair of validation mode and flag indicating whether property is translatable + // for textual properties. + static StringPropertyParameters textPropertyValidationMode(QDesignerFormEditorInterface *core, + const QObject *object, const QString &propertyName, bool isMainContainer); + +Q_SIGNALS: + void propertyValueChanged(const QString &name, const QVariant &value, bool enableSubPropertyHandling); + void resetProperty(const QString &name); + void addDynamicProperty(const QString &name, const QVariant &value); + void removeDynamicProperty(const QString &name); + void editorOpened(); + void editorClosed(); + +public Q_SLOTS: + /* Quick update that assumes the actual count of properties has not changed + * (as opposed to setObject()). N/A when for example executing a + * layout command and margin properties appear. */ + virtual void updatePropertySheet() = 0; + virtual void reloadResourceProperties() = 0; + +private Q_SLOTS: + void slotPropertyChanged(const QString &name, const QVariant &value); + +protected: + void emitPropertyValueChanged(const QString &name, const QVariant &value, bool enableSubPropertyHandling); + +private: + bool m_propertyChangedForwardingBlocked = false; + +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // DESIGNERPROPERTYEDITOR_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_propertysheet.cpp b/src/tools/designer/src/lib/shared/qdesigner_propertysheet.cpp new file mode 100644 index 00000000000..573cfed4a48 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_propertysheet.cpp @@ -0,0 +1,1691 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_propertysheet_p.h" +#include "qdesigner_utils_p.h" +#include "formwindowbase_p.h" +#include "layoutinfo_p.h" +#include "qlayout_widget_p.h" +#include "qdesigner_introspection_p.h" + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +#define USE_LAYOUT_SIZE_CONSTRAINT + +static const QDesignerMetaObjectInterface *propertyIntroducedBy(const QDesignerMetaObjectInterface *meta, int index) +{ + if (index >= meta->propertyOffset()) + return meta; + + if (meta->superClass()) + return propertyIntroducedBy(meta->superClass(), index); + + return nullptr; +} + +// Layout fake properties (prefixed by 'layout' to distinguish them from other 'margins' +// that might be around. These are forwarded to the layout sheet (after name transformation). +// +// 'layoutObjectName' is new for 4.4. It is the name of the actual layout. +// Up to 4.3, QLayoutWidget's name was displayed in the objectinspector. +// This changes with 4.4; the layout name is displayed. This means that for +// old forms, QLayoutWidget will show up as ''; however, the uic code will +// still use 'verticalLayout' (in case someone accesses it). New Layouts get autogenerated names, +// legacy forms will keep their empty names (unless someone types in a new name). +static constexpr auto layoutObjectNameC = "layoutName"_L1; +static constexpr auto layoutLeftMarginC = "layoutLeftMargin"_L1; +static constexpr auto layoutTopMarginC = "layoutTopMargin"_L1; +static constexpr auto layoutRightMarginC = "layoutRightMargin"_L1; +static constexpr auto layoutBottomMarginC = "layoutBottomMargin"_L1; +static constexpr auto layoutSpacingC = "layoutSpacing"_L1; +static constexpr auto layoutHorizontalSpacingC = "layoutHorizontalSpacing"_L1; +static constexpr auto layoutVerticalSpacingC = "layoutVerticalSpacing"_L1; +static constexpr auto layoutSizeConstraintC = "layoutSizeConstraint"_L1; +// form layout +static constexpr auto layoutFieldGrowthPolicyC = "layoutFieldGrowthPolicy"_L1; +static constexpr auto layoutRowWrapPolicyC = "layoutRowWrapPolicy"_L1; +static constexpr auto layoutLabelAlignmentC = "layoutLabelAlignment"_L1; +static constexpr auto layoutFormAlignmentC = "layoutFormAlignment"_L1; +// stretches +static constexpr auto layoutboxStretchPropertyC = "layoutStretch"_L1; +static constexpr auto layoutGridRowStretchPropertyC = "layoutRowStretch"_L1; +static constexpr auto layoutGridColumnStretchPropertyC = "layoutColumnStretch"_L1; +static constexpr auto layoutGridRowMinimumHeightC = "layoutRowMinimumHeight"_L1; +static constexpr auto layoutGridColumnMinimumWidthC = "layoutColumnMinimumWidth"_L1; + +static bool hasLayoutAttributes(QDesignerFormEditorInterface *core, QObject *object) +{ + if (!object->isWidgetType()) + return false; + + QWidget *w = qobject_cast(object); + if (const QDesignerWidgetDataBaseInterface *db = core->widgetDataBase()) { + if (db->isContainer(w)) + return true; + } + return false; +} + +// Cache DesignerMetaEnum by scope/name of a QMetaEnum +static const qdesigner_internal::DesignerMetaEnum &designerMetaEnumFor(const QDesignerMetaEnumInterface *me) +{ + using ScopeNameKey = std::pair; + static QMap cache; + + const QString name = me->name(); + const QString scope = me->scope(); + + const ScopeNameKey key = ScopeNameKey(scope, name); + auto it = cache.find(key); + if (it == cache.end()) { + qdesigner_internal::DesignerMetaEnum dme = qdesigner_internal::DesignerMetaEnum(name, scope, me->separator()); + const int keyCount = me->keyCount(); + for (int i=0; i < keyCount; ++i) + dme.addKey(me->value(i), me->key(i)); + it = cache.insert(key, dme); + } + return it.value(); +} + +// Cache DesignerMetaFlags by scope/name of a QMetaEnum +static const qdesigner_internal::DesignerMetaFlags &designerMetaFlagsFor(const QDesignerMetaEnumInterface *me) +{ + using ScopeNameKey = std::pair; + static QMap cache; + + const QString name = me->name(); + const QString scope = me->scope(); + + const ScopeNameKey key = ScopeNameKey(scope, name); + auto it = cache.find(key); + if (it == cache.end()) { + auto dme = qdesigner_internal::DesignerMetaFlags(me->enumName(), scope, me->separator()); + const int keyCount = me->keyCount(); + for (int i=0; i < keyCount; ++i) + dme.addKey(me->value(i), me->key(i)); + it = cache.insert(key, dme); + } + return it.value(); +} + +// ------------ QDesignerMemberSheetPrivate +class QDesignerPropertySheetPrivate { +public: + using PropertyType = QDesignerPropertySheet::PropertyType; + using ObjectType = QDesignerPropertySheet::ObjectType; + using ObjectFlags = QDesignerPropertySheet::ObjectFlags; + + explicit QDesignerPropertySheetPrivate(QDesignerPropertySheet *sheetPublic, QObject *object, QObject *sheetParent); + + bool invalidIndex(const char *functionName, int index) const; + inline int count() const { return m_meta->propertyCount() + m_addProperties.size(); } + + PropertyType propertyType(int index) const; + QString transformLayoutPropertyName(int index) const; + QLayout* layout(QDesignerPropertySheetExtension **layoutPropertySheet = nullptr) const; + static ObjectType objectType(const QObject *o); + + bool isReloadableProperty(int index) const; + bool isResourceProperty(int index) const; + void addResourceProperty(int index, int type); + QVariant resourceProperty(int index) const; + void setResourceProperty(int index, const QVariant &value); + QVariant emptyResourceProperty(int index) const; // of type PropertySheetPixmapValue / PropertySheetIconValue + QVariant defaultResourceProperty(int index) const; // of type QPixmap / QIcon (maybe it can be generalized for all types, not resource only) + + bool isStringProperty(int index) const; + void addStringProperty(int index); + qdesigner_internal::PropertySheetStringValue stringProperty(int index) const; + void setStringProperty(int index, const qdesigner_internal::PropertySheetStringValue &value); + bool isStringListProperty(int index) const; + void addStringListProperty(int index); + qdesigner_internal::PropertySheetStringListValue stringListProperty(int index) const; + void setStringListProperty(int index, const qdesigner_internal::PropertySheetStringListValue &value); + + bool isKeySequenceProperty(int index) const; + void addKeySequenceProperty(int index); + qdesigner_internal::PropertySheetKeySequenceValue keySequenceProperty(int index) const; + void setKeySequenceProperty(int index, const qdesigner_internal::PropertySheetKeySequenceValue &value); + + enum PropertyKind { NormalProperty, FakeProperty, DynamicProperty, DefaultDynamicProperty }; + class Info { + public: + Info() = default; + + QString group; + QVariant defaultValue; + bool changed = false; + bool visible = true; + bool attribute = false; + bool reset = true; + PropertyType propertyType = QDesignerPropertySheet::PropertyNone; + PropertyKind kind = NormalProperty; + }; + + Info &ensureInfo(int index); + + QDesignerPropertySheet *q; + QDesignerFormEditorInterface *m_core; + const QDesignerMetaObjectInterface *m_meta; + const ObjectType m_objectType; + const ObjectFlags m_objectFlags; + + QHash m_info; + QHash m_fakeProperties; + QHash m_addProperties; + QHash m_addIndex; + QHash m_resourceProperties; // only PropertySheetPixmapValue snd PropertySheetIconValue here + QHash m_stringProperties; // only PropertySheetStringValue + QHash m_stringListProperties; // only PropertySheetStringListValue + QHash m_keySequenceProperties; // only PropertySheetKeySequenceValue + + const bool m_canHaveLayoutAttributes; + + // Variables used for caching the layout, access via layout(). + QPointer m_object; + mutable QPointer m_lastLayout; + mutable QDesignerPropertySheetExtension *m_lastLayoutPropertySheet; + mutable bool m_LastLayoutByDesigner; + + qdesigner_internal::DesignerPixmapCache *m_pixmapCache; + qdesigner_internal::DesignerIconCache *m_iconCache; + QPointer m_fwb; + + // Enable Qt's internal properties starting with prefix "_q_" + static bool m_internalDynamicPropertiesEnabled; +}; + +bool QDesignerPropertySheetPrivate::m_internalDynamicPropertiesEnabled = false; + +/* + The property is reloadable if its contents depends on resource. +*/ +bool QDesignerPropertySheetPrivate::isReloadableProperty(int index) const +{ + return isResourceProperty(index) + || propertyType(index) == QDesignerPropertySheet::PropertyStyleSheet + || propertyType(index) == QDesignerPropertySheet::PropertyText + || q->property(index).metaType().id() == QMetaType::QUrl; +} + +/* + Resource properties are those which: + 1) are reloadable + 2) their state is associated with a file which can be taken from resources + 3) we don't store them in Qt meta object system (because designer keeps different data structure for them) +*/ + +bool QDesignerPropertySheetPrivate::isResourceProperty(int index) const +{ + return m_resourceProperties.contains(index); +} + +void QDesignerPropertySheetPrivate::addResourceProperty(int index, int type) +{ + if (type == QMetaType::QPixmap) + m_resourceProperties.insert(index, QVariant::fromValue(qdesigner_internal::PropertySheetPixmapValue())); + else if (type == QMetaType::QIcon) + m_resourceProperties.insert(index, QVariant::fromValue(qdesigner_internal::PropertySheetIconValue())); +} + +QVariant QDesignerPropertySheetPrivate::emptyResourceProperty(int index) const +{ + QVariant v = m_resourceProperties.value(index); + if (v.canConvert()) + return QVariant::fromValue(qdesigner_internal::PropertySheetPixmapValue()); + if (v.canConvert()) + return QVariant::fromValue(qdesigner_internal::PropertySheetIconValue()); + return v; +} + +QVariant QDesignerPropertySheetPrivate::defaultResourceProperty(int index) const +{ + return m_info.value(index).defaultValue; +} + +QVariant QDesignerPropertySheetPrivate::resourceProperty(int index) const +{ + return m_resourceProperties.value(index); +} + +void QDesignerPropertySheetPrivate::setResourceProperty(int index, const QVariant &value) +{ + Q_ASSERT(isResourceProperty(index)); + + QVariant &v = m_resourceProperties[index]; + if ((value.canConvert() && v.canConvert()) + || (value.canConvert() && v.canConvert())) + v = value; +} + +bool QDesignerPropertySheetPrivate::isStringProperty(int index) const +{ + return m_stringProperties.contains(index); +} + +void QDesignerPropertySheetPrivate::addStringProperty(int index) +{ + m_stringProperties.insert(index, qdesigner_internal::PropertySheetStringValue()); +} + +qdesigner_internal::PropertySheetStringValue QDesignerPropertySheetPrivate::stringProperty(int index) const +{ + return m_stringProperties.value(index); +} + +void QDesignerPropertySheetPrivate::setStringProperty(int index, const qdesigner_internal::PropertySheetStringValue &value) +{ + Q_ASSERT(isStringProperty(index)); + + m_stringProperties[index] = value; +} + +bool QDesignerPropertySheetPrivate::isStringListProperty(int index) const +{ + return m_stringListProperties.contains(index); +} + +void QDesignerPropertySheetPrivate::addStringListProperty(int index) +{ + m_stringListProperties.insert(index, qdesigner_internal::PropertySheetStringListValue()); +} + +qdesigner_internal::PropertySheetStringListValue QDesignerPropertySheetPrivate::stringListProperty(int index) const +{ + return m_stringListProperties.value(index); +} + +void QDesignerPropertySheetPrivate::setStringListProperty(int index, const qdesigner_internal::PropertySheetStringListValue &value) +{ + Q_ASSERT(isStringListProperty(index)); + + m_stringListProperties[index] = value; +} + +bool QDesignerPropertySheetPrivate::isKeySequenceProperty(int index) const +{ + return m_keySequenceProperties.contains(index); +} + +void QDesignerPropertySheetPrivate::addKeySequenceProperty(int index) +{ + m_keySequenceProperties.insert(index, qdesigner_internal::PropertySheetKeySequenceValue()); +} + +qdesigner_internal::PropertySheetKeySequenceValue QDesignerPropertySheetPrivate::keySequenceProperty(int index) const +{ + return m_keySequenceProperties.value(index); +} + +void QDesignerPropertySheetPrivate::setKeySequenceProperty(int index, const qdesigner_internal::PropertySheetKeySequenceValue &value) +{ + Q_ASSERT(isKeySequenceProperty(index)); + + m_keySequenceProperties[index] = value; +} + +QDesignerPropertySheetPrivate::QDesignerPropertySheetPrivate(QDesignerPropertySheet *sheetPublic, QObject *object, QObject *sheetParent) : + q(sheetPublic), + m_core(QDesignerPropertySheet::formEditorForObject(sheetParent)), + m_meta(m_core->introspection()->metaObject(object)), + m_objectType(QDesignerPropertySheet::objectTypeFromObject(object)), + m_objectFlags(QDesignerPropertySheet::objectFlagsFromObject(object)), + m_canHaveLayoutAttributes(hasLayoutAttributes(m_core, object)), + m_object(object), + m_lastLayout(nullptr), + m_lastLayoutPropertySheet(nullptr), + m_LastLayoutByDesigner(false), + m_pixmapCache(nullptr), + m_iconCache(nullptr) +{ +} + +qdesigner_internal::FormWindowBase *QDesignerPropertySheet::formWindowBase() const +{ + return d->m_fwb; +} + +bool QDesignerPropertySheetPrivate::invalidIndex(const char *functionName, int index) const +{ + if (index < 0 || index >= count()) { + qWarning() << "** WARNING " << functionName << " invoked for " << m_object->objectName() << " was passed an invalid index " << index << '.'; + return true; + } + return false; +} + +QLayout* QDesignerPropertySheetPrivate::layout(QDesignerPropertySheetExtension **layoutPropertySheet) const +{ + // Return the layout and its property sheet + // only if it is managed by designer and not one created on a custom widget. + // (attempt to cache the value as this requires some hoops). + if (layoutPropertySheet) + *layoutPropertySheet = nullptr; + + if (!m_object->isWidgetType() || !m_canHaveLayoutAttributes) + return nullptr; + + QWidget *widget = qobject_cast(m_object); + QLayout *widgetLayout = qdesigner_internal::LayoutInfo::internalLayout(widget); + if (!widgetLayout) { + m_lastLayout = nullptr; + m_lastLayoutPropertySheet = nullptr; + return nullptr; + } + // Smart logic to avoid retrieving the meta DB from the widget every time. + if (widgetLayout != m_lastLayout) { + m_lastLayout = widgetLayout; + m_LastLayoutByDesigner = false; + m_lastLayoutPropertySheet = nullptr; + // Is this a layout managed by designer or some layout on a custom widget? + if (qdesigner_internal::LayoutInfo::managedLayout(m_core ,widgetLayout)) { + m_LastLayoutByDesigner = true; + m_lastLayoutPropertySheet = qt_extension(m_core->extensionManager(), m_lastLayout); + } + } + if (!m_LastLayoutByDesigner) + return nullptr; + + if (layoutPropertySheet) + *layoutPropertySheet = m_lastLayoutPropertySheet; + + return m_lastLayout; +} + +QDesignerPropertySheetPrivate::Info &QDesignerPropertySheetPrivate::ensureInfo(int index) +{ + auto it = m_info.find(index); + if (it == m_info.end()) + it = m_info.insert(index, Info()); + return it.value(); +} + +QDesignerPropertySheet::PropertyType QDesignerPropertySheetPrivate::propertyType(int index) const +{ + const auto it = m_info.constFind(index); + if (it == m_info.constEnd()) + return QDesignerPropertySheet::PropertyNone; + return it.value().propertyType; +} + +QString QDesignerPropertySheetPrivate::transformLayoutPropertyName(int index) const +{ + using TypeNameMap = QMap; + static const TypeNameMap typeNameMap = { + {QDesignerPropertySheet::PropertyLayoutObjectName, u"objectName"_s}, + {QDesignerPropertySheet::PropertyLayoutLeftMargin, u"leftMargin"_s}, + {QDesignerPropertySheet::PropertyLayoutTopMargin, u"topMargin"_s}, + {QDesignerPropertySheet::PropertyLayoutRightMargin, u"rightMargin"_s}, + {QDesignerPropertySheet::PropertyLayoutBottomMargin, u"bottomMargin"_s}, + {QDesignerPropertySheet::PropertyLayoutSpacing, u"spacing"_s}, + {QDesignerPropertySheet::PropertyLayoutHorizontalSpacing, u"horizontalSpacing"_s}, + {QDesignerPropertySheet::PropertyLayoutVerticalSpacing, u"verticalSpacing"_s}, + {QDesignerPropertySheet::PropertyLayoutSizeConstraint, u"sizeConstraint"_s}, + {QDesignerPropertySheet::PropertyLayoutFieldGrowthPolicy, u"fieldGrowthPolicy"_s}, + {QDesignerPropertySheet::PropertyLayoutRowWrapPolicy, u"rowWrapPolicy"_s}, + {QDesignerPropertySheet::PropertyLayoutLabelAlignment, u"labelAlignment"_s}, + {QDesignerPropertySheet::PropertyLayoutFormAlignment, u"formAlignment"_s}, + {QDesignerPropertySheet::PropertyLayoutBoxStretch, u"stretch"_s}, + {QDesignerPropertySheet::PropertyLayoutGridRowStretch, u"rowStretch"_s}, + {QDesignerPropertySheet::PropertyLayoutGridColumnStretch, u"columnStretch"_s}, + {QDesignerPropertySheet::PropertyLayoutGridRowMinimumHeight, u"rowMinimumHeight"_s}, + {QDesignerPropertySheet::PropertyLayoutGridColumnMinimumWidth, u"columnMinimumWidth"_s} + }; + const auto it = typeNameMap.constFind(propertyType(index)); + if (it != typeNameMap.constEnd()) + return it.value(); + return QString(); +} + +// ----------- QDesignerPropertySheet + +QDesignerPropertySheet::ObjectType QDesignerPropertySheet::objectTypeFromObject(const QObject *o) +{ + if (qobject_cast(o)) + return ObjectLayout; + + if (!o->isWidgetType()) + return ObjectNone; + + if (qobject_cast(o)) + return ObjectLayoutWidget; + + if (qobject_cast(o)) + return ObjectLabel; + + return ObjectNone; +} + +QDesignerPropertySheet::ObjectFlags QDesignerPropertySheet::objectFlagsFromObject(const QObject *o) +{ + ObjectFlags result; + if ((o->isWidgetType() && (qobject_cast(o) + || qobject_cast(o))) + || qobject_cast(o)) { + result |= CheckableProperty; + } + return result; +} + +QDesignerPropertySheet::PropertyType QDesignerPropertySheet::propertyTypeFromName(const QString &name) +{ + static const QHash propertyTypeHash = { + {layoutObjectNameC, PropertyLayoutObjectName}, + {layoutLeftMarginC, PropertyLayoutLeftMargin}, + {layoutTopMarginC, PropertyLayoutTopMargin}, + {layoutRightMarginC, PropertyLayoutRightMargin}, + {layoutBottomMarginC, PropertyLayoutBottomMargin}, + {layoutSpacingC, PropertyLayoutSpacing}, + {layoutHorizontalSpacingC, PropertyLayoutHorizontalSpacing}, + {layoutVerticalSpacingC, PropertyLayoutVerticalSpacing}, + {layoutSizeConstraintC, PropertyLayoutSizeConstraint}, + {layoutFieldGrowthPolicyC, PropertyLayoutFieldGrowthPolicy}, + {layoutRowWrapPolicyC, PropertyLayoutRowWrapPolicy}, + {layoutLabelAlignmentC, PropertyLayoutLabelAlignment}, + {layoutFormAlignmentC, PropertyLayoutFormAlignment}, + {layoutboxStretchPropertyC, PropertyLayoutBoxStretch}, + {layoutGridRowStretchPropertyC, PropertyLayoutGridRowStretch}, + {layoutGridColumnStretchPropertyC, PropertyLayoutGridColumnStretch}, + {layoutGridRowMinimumHeightC, PropertyLayoutGridRowMinimumHeight}, + {layoutGridColumnMinimumWidthC, PropertyLayoutGridColumnMinimumWidth}, + {u"buddy"_s, PropertyBuddy}, + {u"geometry"_s, PropertyGeometry}, + {u"checked"_s, PropertyChecked}, + {u"checkable"_s, PropertyCheckable}, + {u"accessibleName"_s, PropertyAccessibility}, + {u"accessibleDescription"_s, PropertyAccessibility}, + {u"visible"_s, PropertyVisible}, + {u"windowTitle"_s, PropertyWindowTitle}, + {u"windowIcon"_s, PropertyWindowIcon}, + {u"windowFilePath"_s, PropertyWindowFilePath}, + {u"windowOpacity"_s, PropertyWindowOpacity}, + {u"windowIconText"_s, PropertyWindowIconText}, + {u"windowModality"_s, PropertyWindowModality}, + {u"windowModified"_s, PropertyWindowModified}, + {u"styleSheet"_s, PropertyStyleSheet}, + {u"text"_s, PropertyText} + }; + return propertyTypeHash.value(name, PropertyNone); +} + +QDesignerPropertySheet::QDesignerPropertySheet(QObject *object, QObject *parent) : + QObject(parent), + d(new QDesignerPropertySheetPrivate(this, object, parent)) +{ + using Info = QDesignerPropertySheetPrivate::Info; + const QDesignerMetaObjectInterface *baseMeta = d->m_meta; + + while (baseMeta &&baseMeta->className().startsWith("QDesigner"_L1)) { + baseMeta = baseMeta->superClass(); + } + Q_ASSERT(baseMeta != nullptr); + + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(d->m_object); + d->m_fwb = qobject_cast(formWindow); + if (d->m_fwb) { + d->m_pixmapCache = d->m_fwb->pixmapCache(); + d->m_iconCache = d->m_fwb->iconCache(); + d->m_fwb->addReloadablePropertySheet(this, object); + } + + for (int index=0; indexm_meta->property(index); + const QString name = p->name(); + if (p->type() == QMetaType::QKeySequence) { + createFakeProperty(name); + } else { + setVisible(index, false); // use the default for `real' properties + } + + QString pgroup = baseMeta->className(); + + if (const QDesignerMetaObjectInterface *pmeta = propertyIntroducedBy(baseMeta, index)) { + pgroup = pmeta->className(); + } + + Info &info = d->ensureInfo(index); + info.group = pgroup; + info.propertyType = propertyTypeFromName(name); + + const int type = p->type(); + switch (type) { + case QMetaType::QCursor: + case QMetaType::QIcon: + case QMetaType::QPixmap: + info.defaultValue = p->read(d->m_object); + if (type == QMetaType::QIcon || type == QMetaType::QPixmap) + d->addResourceProperty(index, type); + break; + case QMetaType::QString: + d->addStringProperty(index); + break; + case QMetaType::QStringList: + d->addStringListProperty(index); + break; + case QMetaType::QKeySequence: + d->addKeySequenceProperty(index); + break; + default: + break; + } + } + + if (object->isWidgetType()) { + createFakeProperty(u"focusPolicy"_s); + createFakeProperty(u"cursor"_s); + createFakeProperty(u"toolTip"_s); + createFakeProperty(u"whatsThis"_s); + createFakeProperty(u"acceptDrops"_s); + createFakeProperty(u"dragEnabled"_s); + // windowModality/Opacity is visible only for the main container, in which case the form windows enables it on loading + setVisible(createFakeProperty(u"windowModality"_s), false); + setVisible(createFakeProperty(u"windowOpacity"_s, double(1.0)), false); + if (qobject_cast(d->m_object)) { // prevent toolbars from being dragged off + createFakeProperty(u"floatable"_s, QVariant(true)); + } else { + if (qobject_cast(d->m_object)) { + // Keep the menu bar editable in the form even if a native menu bar is used. + const bool nativeMenuBarDefault = + !QCoreApplication::testAttribute(Qt::AA_DontUseNativeMenuBar); + createFakeProperty(u"nativeMenuBar"_s, QVariant(nativeMenuBarDefault)); + } + } + if (d->m_canHaveLayoutAttributes) { + const QString layoutGroup = u"Layout"_s; + static constexpr QLatin1StringView fakeLayoutProperties[] = { + layoutObjectNameC, layoutLeftMarginC, layoutTopMarginC, layoutRightMarginC, layoutBottomMarginC, layoutSpacingC, layoutHorizontalSpacingC, layoutVerticalSpacingC, + layoutFieldGrowthPolicyC, layoutRowWrapPolicyC, layoutLabelAlignmentC, layoutFormAlignmentC, + layoutboxStretchPropertyC, layoutGridRowStretchPropertyC, layoutGridColumnStretchPropertyC, + layoutGridRowMinimumHeightC, layoutGridColumnMinimumWidthC +#ifdef USE_LAYOUT_SIZE_CONSTRAINT + , layoutSizeConstraintC +#endif + }; + static constexpr int fakeLayoutPropertyCount = sizeof(fakeLayoutProperties)/sizeof(fakeLayoutProperties[0]); + const int size = count(); + for (int i = 0; i < fakeLayoutPropertyCount; i++) { + createFakeProperty(fakeLayoutProperties[i], 0); + setAttribute(size + i, true); + setPropertyGroup(size + i, layoutGroup); + } + } + + if (d->m_objectType == ObjectLabel) + createFakeProperty(u"buddy"_s, QVariant(QByteArray())); + /* We need to create a fake property since the property does not work + * for non-toplevel windows or on other systems than Mac and only if + * it is above a certain Mac OS version. */ + if (qobject_cast(d->m_object)) + createFakeProperty(u"unifiedTitleAndToolBarOnMac"_s, false); + } + + if (qobject_cast(object)) { + createFakeProperty(u"modal"_s); + } + if (qobject_cast(object)) { + createFakeProperty(u"floating"_s); + } + + const QByteArrayList names = object->dynamicPropertyNames(); + for (const auto &nameB : names) { + const QString name = QString::fromLatin1(nameB); + const int idx = addDynamicProperty(name, object->property(nameB.constData())); + if (idx != -1) + d->ensureInfo(idx).kind = QDesignerPropertySheetPrivate::DefaultDynamicProperty; + } +} + +QDesignerPropertySheet::~QDesignerPropertySheet() +{ + delete d; +} + +QObject *QDesignerPropertySheet::object() const +{ + return d->m_object; +} + +bool QDesignerPropertySheet::dynamicPropertiesAllowed() const +{ + return true; +} + +bool QDesignerPropertySheet::canAddDynamicProperty(const QString &propName) const +{ + // used internally + if (propName == "database"_L1 || propName == "buttonGroupId"_L1) + return false; + const int index = d->m_meta->indexOfProperty(propName); + if (index != -1) + return false; // property already exists and is not a dynamic one + if (d->m_addIndex.contains(propName)) { + const int idx = d->m_addIndex.value(propName); + return !isVisible(idx); // dynamic property already exists + } + return QDesignerPropertySheet::internalDynamicPropertiesEnabled() + || !propName.startsWith("_q_"_L1); +} + +int QDesignerPropertySheet::addDynamicProperty(const QString &propName, const QVariant &value) +{ + using Info = QDesignerPropertySheetPrivate::Info; + if (!value.isValid()) + return -1; // property has invalid type + if (!canAddDynamicProperty(propName)) + return -1; + + QVariant v = value; + switch (value.metaType().id()) { + case QMetaType::QIcon: + v = QVariant::fromValue(qdesigner_internal::PropertySheetIconValue()); + break; + case QMetaType::QPixmap: + v = QVariant::fromValue(qdesigner_internal::PropertySheetPixmapValue()); + break; + case QMetaType::QString: + v = QVariant::fromValue(qdesigner_internal::PropertySheetStringValue(value.toString())); + break; + case QMetaType::QStringList: + v = QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue(value.toStringList())); + break; + case QMetaType::QKeySequence: { + const QKeySequence keySequence = qvariant_cast(value); + v = QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue(keySequence)); + } + break; + } + + if (d->m_addIndex.contains(propName)) { + const int idx = d->m_addIndex.value(propName); + // have to be invisible, this was checked in canAddDynamicProperty() method + setVisible(idx, true); + d->m_addProperties.insert(idx, v); + setChanged(idx, false); + const int index = d->m_meta->indexOfProperty(propName); + Info &info = d->ensureInfo(index); + info.defaultValue = value; + info.kind = QDesignerPropertySheetPrivate::DynamicProperty; + switch (value.metaType().id()) { + case QMetaType::QIcon: + case QMetaType::QPixmap: + d->addResourceProperty(idx, value.metaType().id()); + break; + case QMetaType::QString: + d->addStringProperty(idx); + break; + case QMetaType::QKeySequence: + d->addKeySequenceProperty(idx); + break; + } + return idx; + } + + const int index = count(); + d->m_addIndex.insert(propName, index); + d->m_addProperties.insert(index, v); + Info &info = d->ensureInfo(index); + info.visible = true; + info.changed = false; + info.defaultValue = value; + info.kind = QDesignerPropertySheetPrivate::DynamicProperty; + setPropertyGroup(index, tr("Dynamic Properties")); + switch (value.metaType().id()) { + case QMetaType::QIcon: + case QMetaType::QPixmap: + d->addResourceProperty(index, value.metaType().id()); + break; + case QMetaType::QString: + d->addStringProperty(index); + break; + case QMetaType::QStringList: + d->addStringListProperty(index); + break; + case QMetaType::QKeySequence: + d->addKeySequenceProperty(index); + break; + default: + break; + } + return index; +} + +bool QDesignerPropertySheet::removeDynamicProperty(int index) +{ + if (!d->m_addIndex.contains(propertyName(index))) + return false; + + setVisible(index, false); + return true; +} + +bool QDesignerPropertySheet::isDynamic(int index) const +{ + if (!d->m_addProperties.contains(index)) + return false; + + switch (propertyType(index)) { + case PropertyBuddy: + if (d->m_objectType == ObjectLabel) + return false; + break; + case PropertyLayoutLeftMargin: + case PropertyLayoutTopMargin: + case PropertyLayoutRightMargin: + case PropertyLayoutBottomMargin: + case PropertyLayoutSpacing: + case PropertyLayoutHorizontalSpacing: + case PropertyLayoutVerticalSpacing: + case PropertyLayoutObjectName: + case PropertyLayoutSizeConstraint: + case PropertyLayoutFieldGrowthPolicy: + case PropertyLayoutRowWrapPolicy: + case PropertyLayoutLabelAlignment: + case PropertyLayoutFormAlignment: + case PropertyLayoutBoxStretch: + case PropertyLayoutGridRowStretch: + case PropertyLayoutGridColumnStretch: + case PropertyLayoutGridRowMinimumHeight: + case PropertyLayoutGridColumnMinimumWidth: + if (d->m_object->isWidgetType() && d->m_canHaveLayoutAttributes) + return false; + break; + default: + break; + } + return true; +} + +bool QDesignerPropertySheet::isDynamicProperty(int index) const +{ + // Do not complain here, as an invalid index might be encountered + // if someone implements a property sheet only, omitting the dynamic sheet. + if (index < 0 || index >= count()) + return false; + return d->m_info.value(index).kind == QDesignerPropertySheetPrivate::DynamicProperty; +} + +bool QDesignerPropertySheet::isDefaultDynamicProperty(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + return d->m_info.value(index).kind == QDesignerPropertySheetPrivate::DefaultDynamicProperty; +} + +bool QDesignerPropertySheet::isResourceProperty(int index) const +{ + return d->isResourceProperty(index); +} + +QVariant QDesignerPropertySheet::defaultResourceProperty(int index) const +{ + return d->defaultResourceProperty(index); +} + +qdesigner_internal::DesignerPixmapCache *QDesignerPropertySheet::pixmapCache() const +{ + return d->m_pixmapCache; +} + +void QDesignerPropertySheet::setPixmapCache(qdesigner_internal::DesignerPixmapCache *cache) +{ + d->m_pixmapCache = cache; +} + +qdesigner_internal::DesignerIconCache *QDesignerPropertySheet::iconCache() const +{ + return d->m_iconCache; +} + +void QDesignerPropertySheet::setIconCache(qdesigner_internal::DesignerIconCache *cache) +{ + d->m_iconCache = cache; +} + +int QDesignerPropertySheet::createFakeProperty(const QString &propertyName, const QVariant &value) +{ + using Info = QDesignerPropertySheetPrivate::Info; + // fake properties + const int index = d->m_meta->indexOfProperty(propertyName); + if (index != -1) { + if (!(d->m_meta->property(index)->attributes() & QDesignerMetaPropertyInterface::DesignableAttribute)) + return -1; + Info &info = d->ensureInfo(index); + info.visible = false; + info.kind = QDesignerPropertySheetPrivate::FakeProperty; + QVariant v = value.isValid() ? value : metaProperty(index); + switch (v.metaType().id()) { + case QMetaType::QString: + v = QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + break; + case QMetaType::QStringList: + v = QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue()); + break; + case QMetaType::QKeySequence: + v = QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue()); + break; + } + d->m_fakeProperties.insert(index, v); + return index; + } + if (!value.isValid()) + return -1; + + const int newIndex = count(); + d->m_addIndex.insert(propertyName, newIndex); + d->m_addProperties.insert(newIndex, value); + Info &info = d->ensureInfo(newIndex); + info.propertyType = propertyTypeFromName(propertyName); + info.kind = QDesignerPropertySheetPrivate::FakeProperty; + return newIndex; +} + +bool QDesignerPropertySheet::isAdditionalProperty(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + return d->m_addProperties.contains(index); +} + +bool QDesignerPropertySheet::isFakeProperty(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + // additional properties must be fake + return (d->m_fakeProperties.contains(index) || isAdditionalProperty(index)); +} + +int QDesignerPropertySheet::count() const +{ + return d->count(); +} + +int QDesignerPropertySheet::indexOf(const QString &name) const +{ + int index = d->m_meta->indexOfProperty(name); + + if (index == -1) + index = d->m_addIndex.value(name, -1); + + return index; +} + +QDesignerPropertySheet::PropertyType QDesignerPropertySheet::propertyType(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return PropertyNone; + return d->propertyType(index); +} + +QDesignerPropertySheet::ObjectType QDesignerPropertySheet::objectType() const +{ + return d->m_objectType; +} + +QString QDesignerPropertySheet::propertyName(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return QString(); + if (isAdditionalProperty(index)) + return d->m_addIndex.key(index); + + return d->m_meta->property(index)->name(); +} + +QString QDesignerPropertySheet::propertyGroup(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return QString(); + const QString g = d->m_info.value(index).group; + + if (!g.isEmpty()) + return g; + + if (propertyType(index) == PropertyAccessibility) + return u"Accessibility"_s; + + if (isAdditionalProperty(index)) + return d->m_meta->className(); + + return g; +} + +void QDesignerPropertySheet::setPropertyGroup(int index, const QString &group) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + d->ensureInfo(index).group = group; +} + +QVariant QDesignerPropertySheet::property(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return QVariant(); + if (isAdditionalProperty(index)) { + if (isFakeLayoutProperty(index)) { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) { + const QString newPropName = d->transformLayoutPropertyName(index); + if (!newPropName.isEmpty()) { + const int newIndex = layoutPropertySheet->indexOf(newPropName); + if (newIndex != -1) + return layoutPropertySheet->property(newIndex); + return QVariant(); + } + } + } + return d->m_addProperties.value(index); + } + + if (isFakeProperty(index)) { + return d->m_fakeProperties.value(index); + } + + if (d->isResourceProperty(index)) + return d->resourceProperty(index); + + if (d->isStringProperty(index)) { + QString strValue = metaProperty(index).toString(); + qdesigner_internal::PropertySheetStringValue value = d->stringProperty(index); + if (strValue != value.value()) { + value.setValue(strValue); + d->setStringProperty(index, value); // cache it + } + return QVariant::fromValue(value); + } + + if (d->isStringListProperty(index)) { + const QStringList listValue = metaProperty(index).toStringList(); + qdesigner_internal::PropertySheetStringListValue value = d->stringListProperty(index); + if (listValue != value.value()) { + value.setValue(listValue); + d->setStringListProperty(index, value); // cache it + } + return QVariant::fromValue(value); + } + + if (d->isKeySequenceProperty(index)) { + QKeySequence keyValue = qvariant_cast(metaProperty(index)); + qdesigner_internal::PropertySheetKeySequenceValue value = d->keySequenceProperty(index); + if (keyValue != value.value()) { + value.setValue(keyValue); + d->setKeySequenceProperty(index, value); // cache it + } + return QVariant::fromValue(value); + } + + QVariant result = metaProperty(index); + // QTBUG-49591: "visible" is only exposed for QHeaderView as a fake + // property ("headerVisible") for the item view. If the item view is not + // visible (on a page based container), check the WA_WState_Hidden instead, + // since otherwise false is returned when saving. + if (result.typeId() == QMetaType::Bool && !result.toBool() + && d->m_object->isWidgetType() + && propertyType(index) == PropertyVisible) { + if (auto *hv = qobject_cast(d->m_object)) { + if (auto *parent = hv->parentWidget()) { + if (!parent->isVisible()) + result = QVariant(!hv->testAttribute(Qt::WA_WState_Hidden)); + } + } + } + return result; +} + +QVariant QDesignerPropertySheet::metaProperty(int index) const +{ + Q_ASSERT(!isFakeProperty(index)); + + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + QVariant v = p->read(d->m_object); + switch (p->kind()) { + case QDesignerMetaPropertyInterface::FlagKind: { + qdesigner_internal::PropertySheetFlagValue psflags = qdesigner_internal::PropertySheetFlagValue(v.toInt(), designerMetaFlagsFor(p->enumerator())); + v.setValue(psflags); + } + break; + case QDesignerMetaPropertyInterface::EnumKind: { + qdesigner_internal::PropertySheetEnumValue pse = qdesigner_internal::PropertySheetEnumValue(v.toInt(), designerMetaEnumFor(p->enumerator())); + v.setValue(pse); + } + break; + case QDesignerMetaPropertyInterface::OtherKind: + break; + } + return v; +} + +QVariant QDesignerPropertySheet::resolvePropertyValue(int index, const QVariant &value) const +{ + if (value.canConvert()) + return qvariant_cast(value).value; + + if (value.canConvert()) + return qvariant_cast(value).value; + + if (value.canConvert()) + return qvariant_cast(value).value(); + + if (value.canConvert()) + return qvariant_cast(value).value(); + + if (value.canConvert()) + return QVariant::fromValue(qvariant_cast(value).value()); + + if (value.canConvert()) { + const QString path = qvariant_cast(value).path(); + if (path.isEmpty()) + return defaultResourceProperty(index); + if (d->m_pixmapCache) { + return d->m_pixmapCache->pixmap(qvariant_cast(value)); + } + } + + if (value.canConvert()) { + const unsigned mask = qvariant_cast(value).mask(); + if (mask == 0) + return defaultResourceProperty(index); + if (d->m_iconCache) + return d->m_iconCache->icon(qvariant_cast(value)); + } + + return value; +} + +void QDesignerPropertySheet::setFakeProperty(int index, const QVariant &value) +{ + Q_ASSERT(isFakeProperty(index)); + + QVariant &v = d->m_fakeProperties[index]; + + // set resource properties also (if we are going to have fake resource properties) + if (value.canConvert() || value.canConvert()) { + v = value; + } else if (v.canConvert()) { + qdesigner_internal::PropertySheetFlagValue f = qvariant_cast(v); + f.value = value.toInt(); + v.setValue(f); + Q_ASSERT(value.metaType().id() == QMetaType::Int); + } else if (v.canConvert()) { + qdesigner_internal::PropertySheetEnumValue e = qvariant_cast(v); + e.value = value.toInt(); + v.setValue(e); + Q_ASSERT(value.metaType().id() == QMetaType::Int); + } else { + v = value; + } +} + +void QDesignerPropertySheet::clearFakeProperties() +{ + d->m_fakeProperties.clear(); +} + +// Buddy needs to be byte array, else uic won't work +static QVariant toByteArray(const QVariant &value) { + if (value.metaType().id() == QMetaType::QByteArray) + return value; + const QByteArray ba = value.toString().toUtf8(); + return QVariant(ba); +} + +void QDesignerPropertySheet::setProperty(int index, const QVariant &value) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + if (isAdditionalProperty(index)) { + if (d->m_objectType == ObjectLabel && propertyType(index) == PropertyBuddy) { + QFormBuilderExtra::applyBuddy(value.toString(), QFormBuilderExtra::BuddyApplyVisibleOnly, qobject_cast(d->m_object)); + d->m_addProperties[index] = toByteArray(value); + return; + } + + if (isFakeLayoutProperty(index)) { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) { + const QString newPropName = d->transformLayoutPropertyName(index); + if (!newPropName.isEmpty()) { + const int newIndex = layoutPropertySheet->indexOf(newPropName); + if (newIndex != -1) + layoutPropertySheet->setProperty(newIndex, value); + } + } + } + + if (isDynamicProperty(index) || isDefaultDynamicProperty(index)) { + if (d->isResourceProperty(index)) + d->setResourceProperty(index, value); + if (d->isStringProperty(index)) + d->setStringProperty(index, qvariant_cast(value)); + if (d->isStringListProperty(index)) + d->setStringListProperty(index, qvariant_cast(value)); + if (d->isKeySequenceProperty(index)) + d->setKeySequenceProperty(index, qvariant_cast(value)); + d->m_object->setProperty(propertyName(index).toUtf8(), resolvePropertyValue(index, value)); + if (d->m_object->isWidgetType()) { + QWidget *w = qobject_cast(d->m_object); + w->setStyleSheet(w->styleSheet()); + } + } + d->m_addProperties[index] = value; + } else if (isFakeProperty(index)) { + setFakeProperty(index, value); + } else { + if (d->isResourceProperty(index)) + d->setResourceProperty(index, value); + if (d->isStringProperty(index)) + d->setStringProperty(index, qvariant_cast(value)); + if (d->isStringListProperty(index)) + d->setStringListProperty(index, qvariant_cast(value)); + if (d->isKeySequenceProperty(index)) + d->setKeySequenceProperty(index, qvariant_cast(value)); + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + p->write(d->m_object, resolvePropertyValue(index, value)); + if (qobject_cast(d->m_object) && propertyType(index) == PropertyCheckable) { + const int idx = indexOf(u"focusPolicy"_s); + if (!isChanged(idx)) { + qdesigner_internal::PropertySheetEnumValue e = qvariant_cast(property(idx)); + if (value.toBool()) { + const QDesignerMetaPropertyInterface *p = d->m_meta->property(idx); + p->write(d->m_object, Qt::NoFocus); + e.value = Qt::StrongFocus; + QVariant v; + v.setValue(e); + setFakeProperty(idx, v); + } else { + e.value = Qt::NoFocus; + QVariant v; + v.setValue(e); + setFakeProperty(idx, v); + } + } + } + } +} + +bool QDesignerPropertySheet::hasReset(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (isAdditionalProperty(index)) + return d->m_info.value(index).reset; + return true; +} + +bool QDesignerPropertySheet::reset(int index) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (d->isStringProperty(index)) { + qdesigner_internal::PropertySheetStringValue value; + // Main container: Reset to stored class name as not to change the file names generated by uic. + if (propertyName(index) == "objectName"_L1) { + const QVariant classNameDefaultV = d->m_object->property("_q_classname"); + if (classNameDefaultV.isValid()) + value.setValue(classNameDefaultV.toString()); + } else if (!isAdditionalProperty(index)) { + const QDesignerMetaPropertyInterface *property = d->m_meta->property(index); + if ((property->accessFlags() & QDesignerMetaPropertyInterface::ResetAccess) && property->reset(d->m_object)) + value.setValue(property->read(d->m_object).toString()); + else + return false; + } + setProperty(index, QVariant::fromValue(value)); + return true; + } + if (d->isStringListProperty(index)) + setProperty(index, QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue())); + if (d->isKeySequenceProperty(index)) + setProperty(index, QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue())); + if (d->isResourceProperty(index)) { + setProperty(index, d->emptyResourceProperty(index)); + return true; + } + if (isDynamic(index)) { + const QString propName = propertyName(index); + const QVariant oldValue = d->m_addProperties.value(index); + const QVariant defaultValue = d->m_info.value(index).defaultValue; + QVariant newValue = defaultValue; + if (d->isStringProperty(index)) { + newValue = QVariant::fromValue(qdesigner_internal::PropertySheetStringValue(newValue.toString())); + } else if (d->isStringListProperty(index)) { + newValue = QVariant::fromValue(qdesigner_internal::PropertySheetStringListValue(newValue.toStringList())); + } else if (d->isKeySequenceProperty(index)) { + const QKeySequence keySequence = qvariant_cast(newValue); + newValue = QVariant::fromValue(qdesigner_internal::PropertySheetKeySequenceValue(keySequence)); + } + if (oldValue == newValue) + return true; + d->m_object->setProperty(propName.toUtf8(), defaultValue); + d->m_addProperties[index] = newValue; + return true; + } else if (!d->m_info.value(index).defaultValue.isNull()) { + setProperty(index, d->m_info.value(index).defaultValue); + return true; + } + if (isAdditionalProperty(index)) { + const PropertyType pType = propertyType(index); + if (d->m_objectType == ObjectLabel && pType == PropertyBuddy) { + setProperty(index, QVariant(QByteArray())); + return true; + } + if (isFakeLayoutProperty(index)) { + // special properties + switch (pType) { + case PropertyLayoutObjectName: + setProperty(index, QString()); + return true; + case PropertyLayoutSizeConstraint: + setProperty(index, QVariant(QLayout::SetDefaultConstraint)); + return true; + case PropertyLayoutBoxStretch: + case PropertyLayoutGridRowStretch: + case PropertyLayoutGridColumnStretch: + case PropertyLayoutGridRowMinimumHeight: + case PropertyLayoutGridColumnMinimumWidth: + case PropertyLayoutFieldGrowthPolicy: + case PropertyLayoutRowWrapPolicy: + case PropertyLayoutLabelAlignment: + case PropertyLayoutFormAlignment: { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) + return layoutPropertySheet->reset(layoutPropertySheet->indexOf(d->transformLayoutPropertyName(index))); + } + break; + default: + break; + } + // special margins + int value = -1; + switch (d->m_objectType) { + case ObjectLayoutWidget: + if (pType == PropertyLayoutLeftMargin || + pType == PropertyLayoutTopMargin || + pType == PropertyLayoutRightMargin || + pType == PropertyLayoutBottomMargin) + value = 0; + break; + default: + break; + } + setProperty(index, value); + return true; + } + return false; + } + if (isFakeProperty(index)) { + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + const bool result = p->reset(d->m_object); + d->m_fakeProperties[index] = p->read(d->m_object); + return result; + } + if (propertyType(index) == PropertyGeometry && d->m_object->isWidgetType()) { + if (QWidget *w = qobject_cast(d->m_object)) { + QWidget *widget = w; + if (qdesigner_internal::Utils::isCentralWidget(d->m_fwb, widget) && d->m_fwb->parentWidget()) + widget = d->m_fwb->parentWidget(); + + if (widget != w && widget->parentWidget()) { + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + widget->parentWidget()->adjustSize(); + } + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); + widget->adjustSize(); + return true; + } + } + // ### TODO: reset for fake properties. + + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + return p->reset(d->m_object); +} + +bool QDesignerPropertySheet::isChanged(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (isAdditionalProperty(index)) { + if (isFakeLayoutProperty(index)) { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) { + const QString newPropName = d->transformLayoutPropertyName(index); + if (!newPropName.isEmpty()) { + const int newIndex = layoutPropertySheet->indexOf(newPropName); + if (newIndex != -1) + return layoutPropertySheet->isChanged(newIndex); + return false; + } + } + } + } + return d->m_info.value(index).changed; +} + +void QDesignerPropertySheet::setChanged(int index, bool changed) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + if (isAdditionalProperty(index)) { + if (isFakeLayoutProperty(index)) { + QDesignerPropertySheetExtension *layoutPropertySheet; + if (d->layout(&layoutPropertySheet) && layoutPropertySheet) { + const QString newPropName = d->transformLayoutPropertyName(index); + if (!newPropName.isEmpty()) { + const int newIndex = layoutPropertySheet->indexOf(newPropName); + if (newIndex != -1) + layoutPropertySheet->setChanged(newIndex, changed); + } + } + } + } + if (d->isReloadableProperty(index)) { + if (d->m_fwb) { + if (changed) + d->m_fwb->addReloadableProperty(this, index); + else + d->m_fwb->removeReloadableProperty(this, index); + } + } + d->ensureInfo(index).changed = changed; +} + +bool QDesignerPropertySheet::isFakeLayoutProperty(int index) const +{ + if (!isAdditionalProperty(index)) + return false; + + switch (propertyType(index)) { + case PropertyLayoutObjectName: + case PropertyLayoutSizeConstraint: + return true; + case PropertyLayoutLeftMargin: + case PropertyLayoutTopMargin: + case PropertyLayoutRightMargin: + case PropertyLayoutBottomMargin: + case PropertyLayoutSpacing: + case PropertyLayoutHorizontalSpacing: + case PropertyLayoutVerticalSpacing: + case PropertyLayoutFieldGrowthPolicy: + case PropertyLayoutRowWrapPolicy: + case PropertyLayoutLabelAlignment: + case PropertyLayoutFormAlignment: + case PropertyLayoutBoxStretch: + case PropertyLayoutGridRowStretch: + case PropertyLayoutGridColumnStretch: + case PropertyLayoutGridRowMinimumHeight: + case PropertyLayoutGridColumnMinimumWidth: + return d->m_canHaveLayoutAttributes; + default: + break; + } + return false; +} + +// Visible vs. Enabled: In Qt 5, it was possible to define a boolean function +// for the DESIGNABLE attribute of Q_PROPERTY. Qt Widgets Designer would use that to +// determine isEnabled() for the property and return isVisible() = false +// for properties that specified 'false' for DESIGNABLE. +// This was used for example for the "checked" property of QAbstractButton, +// QGroupBox and QAction, where "checkable" would determine isEnabled(). +// This is now implemented by querying the property directly. + +bool QDesignerPropertySheet::isVisible(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + + const PropertyType type = propertyType(index); + if (isAdditionalProperty(index)) { + if (isFakeLayoutProperty(index) && d->m_object->isWidgetType()) { + const QLayout *currentLayout = d->layout(); + if (!currentLayout) + return false; + const int visibleMask = qdesigner_internal::LayoutProperties::visibleProperties(currentLayout); + switch (type) { + case PropertyLayoutSpacing: + return visibleMask & qdesigner_internal::LayoutProperties::SpacingProperty; + case PropertyLayoutHorizontalSpacing: + case PropertyLayoutVerticalSpacing: + return visibleMask & qdesigner_internal::LayoutProperties::HorizSpacingProperty; + case PropertyLayoutFieldGrowthPolicy: + return visibleMask & qdesigner_internal::LayoutProperties::FieldGrowthPolicyProperty; + case PropertyLayoutRowWrapPolicy: + return visibleMask & qdesigner_internal::LayoutProperties::RowWrapPolicyProperty; + case PropertyLayoutLabelAlignment: + return visibleMask & qdesigner_internal::LayoutProperties::LabelAlignmentProperty; + case PropertyLayoutFormAlignment: + return visibleMask & qdesigner_internal::LayoutProperties::FormAlignmentProperty; + case PropertyLayoutBoxStretch: + return visibleMask & qdesigner_internal::LayoutProperties::BoxStretchProperty; + case PropertyLayoutGridRowStretch: + return visibleMask & qdesigner_internal::LayoutProperties::GridRowStretchProperty; + case PropertyLayoutGridColumnStretch: + return visibleMask & qdesigner_internal::LayoutProperties::GridColumnStretchProperty; + case PropertyLayoutGridRowMinimumHeight: + return visibleMask & qdesigner_internal::LayoutProperties::GridRowMinimumHeightProperty; + case PropertyLayoutGridColumnMinimumWidth: + return visibleMask & qdesigner_internal::LayoutProperties::GridColumnMinimumWidthProperty; + default: + break; + } + return true; + } + return d->m_info.value(index).visible; + } + + if (isFakeProperty(index)) { + switch (type) { + case PropertyWindowModality: // Hidden for child widgets + case PropertyWindowOpacity: + return d->m_info.value(index).visible; + default: + break; + } + return true; + } + + const bool visible = d->m_info.value(index).visible; + switch (type) { + case PropertyWindowTitle: + case PropertyWindowIcon: + case PropertyWindowFilePath: + case PropertyWindowOpacity: + case PropertyWindowIconText: + case PropertyWindowModified: + return visible; + default: + if (visible) + return true; + break; + } + + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + if (!(p->accessFlags() & QDesignerMetaPropertyInterface::WriteAccess)) + return false; + + return p->attributes().testFlag(QDesignerMetaPropertyInterface::DesignableAttribute); +} + +void QDesignerPropertySheet::setVisible(int index, bool visible) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + d->ensureInfo(index).visible = visible; +} + +bool QDesignerPropertySheet::isEnabled(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (isAdditionalProperty(index)) + return true; + + if (isFakeProperty(index)) + return true; + + // Grey out geometry of laid-out widgets (including splitter) + if (propertyType(index) == PropertyGeometry && d->m_object->isWidgetType()) { + bool isManaged; + const qdesigner_internal::LayoutInfo::Type lt = qdesigner_internal::LayoutInfo::laidoutWidgetType(d->m_core, qobject_cast(d->m_object), &isManaged); + return !isManaged || lt == qdesigner_internal::LayoutInfo::NoLayout; + } + + if (d->m_info.value(index).visible) + return true; + + // Enable setting of properties for statically non-designable properties + // as this might be done via TaskMenu/Cursor::setProperty. Note that those + // properties are not visible. + const QDesignerMetaPropertyInterface *p = d->m_meta->property(index); + if (!p->accessFlags().testFlag(QDesignerMetaPropertyInterface::WriteAccess)) + return false; + + if (!p->attributes().testFlag(QDesignerMetaPropertyInterface::DesignableAttribute)) + return false; + + const PropertyType type = propertyType(index); + if (type == PropertyChecked && d->m_objectFlags.testFlag(CheckableProperty)) + return d->m_object->property("checkable").toBool(); + return true; +} + +bool QDesignerPropertySheet::isAttribute(int index) const +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return false; + if (isAdditionalProperty(index)) + return d->m_info.value(index).attribute; + + if (isFakeProperty(index)) + return false; + + return d->m_info.value(index).attribute; +} + +void QDesignerPropertySheet::setAttribute(int index, bool attribute) +{ + if (d->invalidIndex(Q_FUNC_INFO, index)) + return; + d->ensureInfo(index).attribute = attribute; +} + +QDesignerFormEditorInterface *QDesignerPropertySheet::core() const +{ + return d->m_core; +} + +bool QDesignerPropertySheet::internalDynamicPropertiesEnabled() +{ + return QDesignerPropertySheetPrivate::m_internalDynamicPropertiesEnabled; +} + +void QDesignerPropertySheet::setInternalDynamicPropertiesEnabled(bool v) +{ + QDesignerPropertySheetPrivate::m_internalDynamicPropertiesEnabled = v; +} + +// Find the form editor in the hierarchy. +// We know that the parent of the sheet is the extension manager +// whose parent is the core. +QDesignerFormEditorInterface *QDesignerPropertySheet::formEditorForObject(QObject *o) +{ + do { + if (auto *core = qobject_cast(o)) + return core; + o = o->parent(); + } while (o); + Q_ASSERT(o); + return nullptr; +} + +// ---------- QDesignerAbstractPropertySheetFactory + +struct QDesignerAbstractPropertySheetFactory::PropertySheetFactoryPrivate { + PropertySheetFactoryPrivate(); + const QString m_propertySheetId; + const QString m_dynamicPropertySheetId; + + QHash m_extensions; +}; + +QDesignerAbstractPropertySheetFactory::PropertySheetFactoryPrivate::PropertySheetFactoryPrivate() : + m_propertySheetId(Q_TYPEID(QDesignerPropertySheetExtension)), + m_dynamicPropertySheetId(Q_TYPEID(QDesignerDynamicPropertySheetExtension)) +{ +} + +// ---------- QDesignerAbstractPropertySheetFactory + + +QDesignerAbstractPropertySheetFactory::QDesignerAbstractPropertySheetFactory(QExtensionManager *parent) : + QExtensionFactory(parent), + m_impl(new PropertySheetFactoryPrivate) +{ +} + +QDesignerAbstractPropertySheetFactory::~QDesignerAbstractPropertySheetFactory() +{ + delete m_impl; +} + +QObject *QDesignerAbstractPropertySheetFactory::extension(QObject *object, const QString &iid) const +{ + if (!object) + return nullptr; + + if (iid != m_impl->m_propertySheetId && iid != m_impl->m_dynamicPropertySheetId) + return nullptr; + + QObject *ext = m_impl->m_extensions.value(object, 0); + if (!ext && (ext = createPropertySheet(object, const_cast(this)))) { + connect(ext, &QObject::destroyed, this, &QDesignerAbstractPropertySheetFactory::objectDestroyed); + connect(object, &QObject::destroyed, this, &QDesignerAbstractPropertySheetFactory::objectDestroyed); + m_impl->m_extensions.insert(object, ext); + } + + return ext; +} + +void QDesignerAbstractPropertySheetFactory::objectDestroyed(QObject *object) +{ + for (auto it = m_impl->m_extensions.begin(), end = m_impl->m_extensions.end(); it != end; /*erasing*/) { + if (it.key() == object || it.value() == object) { + if (it.key() == object) { + QObject *ext = it.value(); + disconnect(ext, &QObject::destroyed, this, &QDesignerAbstractPropertySheetFactory::objectDestroyed); + delete ext; + } + it = m_impl->m_extensions.erase(it); + } else { + ++it; + } + } +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_propertysheet_p.h b/src/tools/designer/src/lib/shared/qdesigner_propertysheet_p.h new file mode 100644 index 00000000000..b9f88626fb8 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_propertysheet_p.h @@ -0,0 +1,240 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_PROPERTYSHEET_H +#define QDESIGNER_PROPERTYSHEET_H + +#include "shared_global_p.h" +#include "dynamicpropertysheet.h" +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QLayout; +class QDesignerFormEditorInterface; +class QDesignerPropertySheetPrivate; + +namespace qdesigner_internal +{ + class DesignerPixmapCache; + class DesignerIconCache; + class FormWindowBase; +} + +class QDESIGNER_SHARED_EXPORT QDesignerPropertySheet: public QObject, public QDesignerPropertySheetExtension, public QDesignerDynamicPropertySheetExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerPropertySheetExtension QDesignerDynamicPropertySheetExtension) +public: + explicit QDesignerPropertySheet(QObject *object, QObject *parent = nullptr); + ~QDesignerPropertySheet() override; + + int indexOf(const QString &name) const override; + + int count() const override; + QString propertyName(int index) const override; + + QString propertyGroup(int index) const override; + void setPropertyGroup(int index, const QString &group) override; + + bool hasReset(int index) const override; + bool reset(int index) override; + + bool isAttribute(int index) const override; + void setAttribute(int index, bool b) override; + + bool isVisible(int index) const override; + void setVisible(int index, bool b) override; + + QVariant property(int index) const override; + void setProperty(int index, const QVariant &value) override; + + bool isChanged(int index) const override; + + void setChanged(int index, bool changed) override; + + bool dynamicPropertiesAllowed() const override; + int addDynamicProperty(const QString &propertyName, const QVariant &value) override; + bool removeDynamicProperty(int index) override; + bool isDynamicProperty(int index) const override; + bool canAddDynamicProperty(const QString &propertyName) const override; + + bool isDefaultDynamicProperty(int index) const; + + bool isResourceProperty(int index) const; + QVariant defaultResourceProperty(int index) const; + + qdesigner_internal::DesignerPixmapCache *pixmapCache() const; + void setPixmapCache(qdesigner_internal::DesignerPixmapCache *cache); + qdesigner_internal::DesignerIconCache *iconCache() const; + void setIconCache(qdesigner_internal::DesignerIconCache *cache); + int createFakeProperty(const QString &propertyName, const QVariant &value = QVariant()); + + bool isEnabled(int index) const override; + QObject *object() const; + + static bool internalDynamicPropertiesEnabled(); + static void setInternalDynamicPropertiesEnabled(bool v); + + static QDesignerFormEditorInterface *formEditorForObject(QObject *o); + +protected: + bool isAdditionalProperty(int index) const; + bool isFakeProperty(int index) const; + QVariant resolvePropertyValue(int index, const QVariant &value) const; + QVariant metaProperty(int index) const; + void setFakeProperty(int index, const QVariant &value); + void clearFakeProperties(); + + bool isFakeLayoutProperty(int index) const; + bool isDynamic(int index) const; + qdesigner_internal::FormWindowBase *formWindowBase() const; + QDesignerFormEditorInterface *core() const; + +public: + enum PropertyType { PropertyNone, + PropertyLayoutObjectName, + PropertyLayoutLeftMargin, + PropertyLayoutTopMargin, + PropertyLayoutRightMargin, + PropertyLayoutBottomMargin, + PropertyLayoutSpacing, + PropertyLayoutHorizontalSpacing, + PropertyLayoutVerticalSpacing, + PropertyLayoutSizeConstraint, + PropertyLayoutFieldGrowthPolicy, + PropertyLayoutRowWrapPolicy, + PropertyLayoutLabelAlignment, + PropertyLayoutFormAlignment, + PropertyLayoutBoxStretch, + PropertyLayoutGridRowStretch, + PropertyLayoutGridColumnStretch, + PropertyLayoutGridRowMinimumHeight, + PropertyLayoutGridColumnMinimumWidth, + PropertyBuddy, + PropertyAccessibility, + PropertyGeometry, + PropertyChecked, + PropertyCheckable, + PropertyVisible, + PropertyWindowTitle, + PropertyWindowIcon, + PropertyWindowFilePath, + PropertyWindowOpacity, + PropertyWindowIconText, + PropertyWindowModality, + PropertyWindowModified, + PropertyStyleSheet, + PropertyText + }; + + enum ObjectType { ObjectNone, ObjectLabel, ObjectLayout, ObjectLayoutWidget }; + enum ObjectFlag + { + CheckableProperty = 0x1 // Has a "checked" property depending on "checkable" + }; + Q_DECLARE_FLAGS(ObjectFlags, ObjectFlag) + + static ObjectType objectTypeFromObject(const QObject *o); + static ObjectFlags objectFlagsFromObject(const QObject *o); + static PropertyType propertyTypeFromName(const QString &name); + +protected: + PropertyType propertyType(int index) const; + ObjectType objectType() const; + +private: + QDesignerPropertySheetPrivate *d; +}; + +/* Abstract base class for factories that register a property sheet that implements + * both QDesignerPropertySheetExtension and QDesignerDynamicPropertySheetExtension + * by multiple inheritance. The factory maintains ownership of + * the extension and returns it for both id's. */ + +class QDESIGNER_SHARED_EXPORT QDesignerAbstractPropertySheetFactory: public QExtensionFactory +{ + Q_OBJECT + Q_INTERFACES(QAbstractExtensionFactory) +public: + explicit QDesignerAbstractPropertySheetFactory(QExtensionManager *parent = nullptr); + ~QDesignerAbstractPropertySheetFactory() override; + + QObject *extension(QObject *object, const QString &iid) const override; + +private slots: + void objectDestroyed(QObject *object); + +private: + virtual QObject *createPropertySheet(QObject *qObject, QObject *parent) const = 0; + + struct PropertySheetFactoryPrivate; + PropertySheetFactoryPrivate *m_impl; +}; + +/* Convenience factory template for property sheets that implement + * QDesignerPropertySheetExtension and QDesignerDynamicPropertySheetExtension + * by multiple inheritance. */ + +template +class QDesignerPropertySheetFactory : public QDesignerAbstractPropertySheetFactory { +public: + explicit QDesignerPropertySheetFactory(QExtensionManager *parent = nullptr); + + static void registerExtension(QExtensionManager *mgr); + +private: + // Does a qobject_cast on the object. + QObject *createPropertySheet(QObject *qObject, QObject *parent) const override; +}; + +template +QDesignerPropertySheetFactory::QDesignerPropertySheetFactory(QExtensionManager *parent) : + QDesignerAbstractPropertySheetFactory(parent) +{ +} + +template +QObject *QDesignerPropertySheetFactory::createPropertySheet(QObject *qObject, QObject *parent) const +{ + Object *object = qobject_cast(qObject); + if (!object) + return nullptr; + return new PropertySheet(object, parent); +} + +template +void QDesignerPropertySheetFactory::registerExtension(QExtensionManager *mgr) +{ + QDesignerPropertySheetFactory *factory = new QDesignerPropertySheetFactory(mgr); + mgr->registerExtensions(factory, Q_TYPEID(QDesignerPropertySheetExtension)); + mgr->registerExtensions(factory, Q_TYPEID(QDesignerDynamicPropertySheetExtension)); +} + + +// Standard property sheet +using QDesignerDefaultPropertySheetFactory = QDesignerPropertySheetFactory; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QDesignerPropertySheet::ObjectFlags) + +QT_END_NAMESPACE + +#endif // QDESIGNER_PROPERTYSHEET_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_qsettings.cpp b/src/tools/designer/src/lib/shared/qdesigner_qsettings.cpp new file mode 100644 index 00000000000..3105132a0af --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_qsettings.cpp @@ -0,0 +1,50 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_qsettings_p.h" + +#include +#include +#include +#include +#include + +QDesignerQSettings::QDesignerQSettings() : + m_settings(qApp->organizationName(), settingsApplicationName()) +{ +} + +QString QDesignerQSettings::settingsApplicationName() +{ + return qApp->applicationName(); +} + +void QDesignerQSettings::beginGroup(const QString &prefix) +{ + m_settings.beginGroup(prefix); +} + +void QDesignerQSettings::endGroup() +{ + m_settings.endGroup(); +} + +bool QDesignerQSettings::contains(const QString &key) const +{ + return m_settings.contains(key); +} + +void QDesignerQSettings::setValue(const QString &key, const QVariant &value) +{ + m_settings.setValue(key, value); +} + +QVariant QDesignerQSettings::value(const QString &key, const QVariant &defaultValue) const +{ + return m_settings.value(key, defaultValue); +} + +void QDesignerQSettings::remove(const QString &key) +{ + m_settings.remove(key); +} diff --git a/src/tools/designer/src/lib/shared/qdesigner_qsettings_p.h b/src/tools/designer/src/lib/shared/qdesigner_qsettings_p.h new file mode 100644 index 00000000000..12c6efd25e8 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_qsettings_p.h @@ -0,0 +1,51 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_QSETTINGS_H +#define QDESIGNER_QSETTINGS_H + +#include "shared_global_p.h" + +#include + +#include + +QT_BEGIN_NAMESPACE + +// Implements QDesignerSettingsInterface by calls to QSettings +class QDESIGNER_SHARED_EXPORT QDesignerQSettings : public QDesignerSettingsInterface +{ +public: + QDesignerQSettings(); + + void beginGroup(const QString &prefix) override; + void endGroup() override; + + bool contains(const QString &key) const override; + void setValue(const QString &key, const QVariant &value) override; + QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const override; + void remove(const QString &key) override; + + // The application name to be used for settings. Allows for including + // the Qt version to prevent settings of different Qt versions from + // interfering. + static QString settingsApplicationName(); + +private: + QSettings m_settings; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_QSETTINGS_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_stackedbox.cpp b/src/tools/designer/src/lib/shared/qdesigner_stackedbox.cpp new file mode 100644 index 00000000000..993dd6089da --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_stackedbox.cpp @@ -0,0 +1,366 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_stackedbox_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "orderdialog_p.h" +#include "promotiontaskmenu_p.h" +#include "widgetfactory_p.h" + +#include + +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static QToolButton *createToolButton(QWidget *parent, Qt::ArrowType at, const QString &name) +{ + QToolButton *rc = new QToolButton(); + rc->setAttribute(Qt::WA_NoChildEventsForParent, true); + rc->setParent(parent); + rc->setObjectName(name); + rc->setArrowType(at); + rc->setAutoRaise(true); + rc->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + rc->setFixedSize(QSize(15, 15)); + return rc; +} + +// --------------- QStackedWidgetPreviewEventFilter +QStackedWidgetPreviewEventFilter::QStackedWidgetPreviewEventFilter(QStackedWidget *parent) : + QObject(parent), + m_buttonToolTipEnabled(false), // Not on preview + m_stackedWidget(parent), + m_prev(createToolButton(m_stackedWidget, Qt::LeftArrow, u"__qt__passive_prev"_s)), + m_next(createToolButton(m_stackedWidget, Qt::RightArrow, u"__qt__passive_next"_s)) +{ + connect(m_prev, &QAbstractButton::clicked, this, &QStackedWidgetPreviewEventFilter::prevPage); + connect(m_next, &QAbstractButton::clicked, this, &QStackedWidgetPreviewEventFilter::nextPage); + + updateButtons(); + m_stackedWidget->installEventFilter(this); + m_prev->installEventFilter(this); + m_next->installEventFilter(this); +} + +void QStackedWidgetPreviewEventFilter::install(QStackedWidget *stackedWidget) +{ + new QStackedWidgetPreviewEventFilter(stackedWidget); +} + +void QStackedWidgetPreviewEventFilter::updateButtons() +{ + m_prev->move(m_stackedWidget->width() - 31, 1); + m_prev->show(); + m_prev->raise(); + + m_next->move(m_stackedWidget->width() - 16, 1); + m_next->show(); + m_next->raise(); +} + +void QStackedWidgetPreviewEventFilter::prevPage() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + fw->clearSelection(); + fw->selectWidget(stackedWidget(), true); + } + const int count = m_stackedWidget->count(); + if (count > 1) { + int newIndex = m_stackedWidget->currentIndex() - 1; + if (newIndex < 0) + newIndex = count - 1; + gotoPage(newIndex); + } +} + +void QStackedWidgetPreviewEventFilter::nextPage() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + fw->clearSelection(); + fw->selectWidget(stackedWidget(), true); + } + const int count = m_stackedWidget->count(); + if (count > 1) + gotoPage((m_stackedWidget->currentIndex() + 1) % count); +} + +bool QStackedWidgetPreviewEventFilter::eventFilter(QObject *watched, QEvent *event) +{ + if (watched->isWidgetType()) { + if (watched == m_stackedWidget) { + switch (event->type()) { + case QEvent::LayoutRequest: + updateButtons(); + break; + case QEvent::ChildAdded: + case QEvent::ChildRemoved: + case QEvent::Resize: + case QEvent::Show: + updateButtons(); + break; + default: + break; + } + } + if (m_buttonToolTipEnabled && (watched == m_next || watched == m_prev)) { + switch (event->type()) { + case QEvent::ToolTip: + updateButtonToolTip(watched); // Tooltip includes page number, so, refresh on demand + break; + default: + break; + } + } + } + return QObject::eventFilter(watched, event); +} + +void QStackedWidgetPreviewEventFilter::gotoPage(int page) +{ + m_stackedWidget->setCurrentIndex(page); + updateButtons(); +} + +static inline QString stackedClassName(QStackedWidget *w) +{ + if (const QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w)) + return qdesigner_internal::WidgetFactory::classNameOf(fw->core(), w); + return u"Stacked widget"_s; +} + +void QStackedWidgetPreviewEventFilter::updateButtonToolTip(QObject *o) +{ + if (o == m_prev) { + const QString msg = tr("Go to previous page of %1 '%2' (%3/%4).") + .arg(stackedClassName(m_stackedWidget), m_stackedWidget->objectName()) + .arg(m_stackedWidget->currentIndex() + 1) + .arg(m_stackedWidget->count()); + m_prev->setToolTip(msg); + } else { + if (o == m_next) { + const QString msg = tr("Go to next page of %1 '%2' (%3/%4).") + .arg(stackedClassName(m_stackedWidget), m_stackedWidget->objectName()) + .arg(m_stackedWidget->currentIndex() + 1) + .arg(m_stackedWidget->count()); + m_next->setToolTip(msg); + } + } +} + +// --------------- QStackedWidgetEventFilter +QStackedWidgetEventFilter::QStackedWidgetEventFilter(QStackedWidget *parent) : + QStackedWidgetPreviewEventFilter(parent), + m_actionPreviousPage(new QAction(tr("Previous Page"), this)), + m_actionNextPage(new QAction(tr("Next Page"), this)), + m_actionDeletePage(new QAction(tr("Delete"), this)), + m_actionInsertPage(new QAction(tr("Before Current Page"), this)), + m_actionInsertPageAfter(new QAction(tr("After Current Page"), this)), + m_actionChangePageOrder(new QAction(tr("Change Page Order..."), this)), + m_pagePromotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(nullptr, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this)) +{ + setButtonToolTipEnabled(true); + connect(m_actionPreviousPage, &QAction::triggered, this, &QStackedWidgetEventFilter::prevPage); + connect(m_actionNextPage, &QAction::triggered, this, &QStackedWidgetEventFilter::nextPage); + connect(m_actionDeletePage, &QAction::triggered, this, &QStackedWidgetEventFilter::removeCurrentPage); + connect(m_actionInsertPage, &QAction::triggered, this, &QStackedWidgetEventFilter::addPage); + connect(m_actionInsertPageAfter, &QAction::triggered, this, &QStackedWidgetEventFilter::addPageAfter); + connect(m_actionChangePageOrder, &QAction::triggered, this, &QStackedWidgetEventFilter::changeOrder); +} + +void QStackedWidgetEventFilter::install(QStackedWidget *stackedWidget) +{ + new QStackedWidgetEventFilter(stackedWidget); +} + +QStackedWidgetEventFilter *QStackedWidgetEventFilter::eventFilterOf(const QStackedWidget *stackedWidget) +{ + // Look for 1st order children only..otherwise, we might get filters of nested widgets + for (QObject *o : stackedWidget->children()) { + if (!o->isWidgetType()) + if (QStackedWidgetEventFilter *ef = qobject_cast(o)) + return ef; + } + return nullptr; +} + +QMenu *QStackedWidgetEventFilter::addStackedWidgetContextMenuActions(const QStackedWidget *stackedWidget, QMenu *popup) +{ + QStackedWidgetEventFilter *filter = eventFilterOf(stackedWidget); + if (!filter) + return nullptr; + return filter->addContextMenuActions(popup); +} + +void QStackedWidgetEventFilter::removeCurrentPage() +{ + if (stackedWidget()->currentIndex() == -1) + return; + + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + qdesigner_internal::DeleteStackedWidgetPageCommand *cmd = new qdesigner_internal::DeleteStackedWidgetPageCommand(fw); + cmd->init(stackedWidget()); + fw->commandHistory()->push(cmd); + } +} + +void QStackedWidgetEventFilter::changeOrder() +{ + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget()); + + if (!fw) + return; + + const QWidgetList oldPages = qdesigner_internal::OrderDialog::pagesOfContainer(fw->core(), stackedWidget()); + const int pageCount = oldPages.size(); + if (pageCount < 2) + return; + + qdesigner_internal::OrderDialog dlg(fw); + dlg.setPageList(oldPages); + if (dlg.exec() == QDialog::Rejected) + return; + + const QWidgetList newPages = dlg.pageList(); + if (newPages == oldPages) + return; + + fw->beginCommand(tr("Change Page Order")); + for(int i=0; i < pageCount; ++i) { + if (newPages.at(i) == stackedWidget()->widget(i)) + continue; + qdesigner_internal::MoveStackedWidgetCommand *cmd = new qdesigner_internal::MoveStackedWidgetCommand(fw); + cmd->init(stackedWidget(), newPages.at(i), i); + fw->commandHistory()->push(cmd); + } + fw->endCommand(); +} + +void QStackedWidgetEventFilter::addPage() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + qdesigner_internal::AddStackedWidgetPageCommand *cmd = new qdesigner_internal::AddStackedWidgetPageCommand(fw); + cmd->init(stackedWidget(), qdesigner_internal::AddStackedWidgetPageCommand::InsertBefore); + fw->commandHistory()->push(cmd); + } +} + +void QStackedWidgetEventFilter::addPageAfter() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + qdesigner_internal::AddStackedWidgetPageCommand *cmd = new qdesigner_internal::AddStackedWidgetPageCommand(fw); + cmd->init(stackedWidget(), qdesigner_internal::AddStackedWidgetPageCommand::InsertAfter); + fw->commandHistory()->push(cmd); + } +} + +void QStackedWidgetEventFilter::gotoPage(int page) { + // Are we on a form or in a preview? + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(stackedWidget())) { + qdesigner_internal::SetPropertyCommand *cmd = new qdesigner_internal::SetPropertyCommand(fw); + cmd->init(stackedWidget(), u"currentIndex"_s, page); + fw->commandHistory()->push(cmd); + fw->emitSelectionChanged(); // Magically prevent an endless loop triggered by auto-repeat. + updateButtons(); + } else { + QStackedWidgetPreviewEventFilter::gotoPage(page); + } +} + +QMenu *QStackedWidgetEventFilter::addContextMenuActions(QMenu *popup) +{ + QMenu *pageMenu = nullptr; + const int count = stackedWidget()->count(); + const bool hasSeveralPages = count > 1; + m_actionDeletePage->setEnabled(count); + if (count) { + const QString pageSubMenuLabel = tr("Page %1 of %2").arg(stackedWidget()->currentIndex() + 1).arg(count); + pageMenu = popup->addMenu(pageSubMenuLabel); + pageMenu->addAction(m_actionDeletePage); + // Set up promotion menu for current widget. + if (QWidget *page = stackedWidget()->currentWidget ()) { + m_pagePromotionTaskMenu->setWidget(page); + m_pagePromotionTaskMenu->addActions(QDesignerFormWindowInterface::findFormWindow(stackedWidget()), + qdesigner_internal::PromotionTaskMenu::SuppressGlobalEdit, + pageMenu); + } + QMenu *insertPageMenu = popup->addMenu(tr("Insert Page")); + insertPageMenu->addAction(m_actionInsertPageAfter); + insertPageMenu->addAction(m_actionInsertPage); + } else { + QAction *insertPageAction = popup->addAction(tr("Insert Page")); + connect(insertPageAction, &QAction::triggered, this, &QStackedWidgetEventFilter::addPage); + } + popup->addAction(m_actionNextPage); + m_actionNextPage->setEnabled(hasSeveralPages); + popup->addAction(m_actionPreviousPage); + m_actionPreviousPage->setEnabled(hasSeveralPages); + popup->addAction(m_actionChangePageOrder); + m_actionChangePageOrder->setEnabled(hasSeveralPages); + popup->addSeparator(); + return pageMenu; +} + +// -------- QStackedWidgetPropertySheet + +static constexpr auto pagePropertyName = "currentPageName"_L1; + +QStackedWidgetPropertySheet::QStackedWidgetPropertySheet(QStackedWidget *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_stackedWidget(object) +{ + createFakeProperty(pagePropertyName, QString()); +} + +bool QStackedWidgetPropertySheet::isEnabled(int index) const +{ + if (propertyName(index) != pagePropertyName) + return QDesignerPropertySheet::isEnabled(index); + return m_stackedWidget->currentWidget() != nullptr; +} + +void QStackedWidgetPropertySheet::setProperty(int index, const QVariant &value) +{ + if (propertyName(index) == pagePropertyName) { + if (QWidget *w = m_stackedWidget->currentWidget()) + w->setObjectName(value.toString()); + } else { + QDesignerPropertySheet::setProperty(index, value); + } +} + +QVariant QStackedWidgetPropertySheet::property(int index) const +{ + if (propertyName(index) == pagePropertyName) { + if (const QWidget *w = m_stackedWidget->currentWidget()) + return w->objectName(); + return QString(); + } + return QDesignerPropertySheet::property(index); +} + +bool QStackedWidgetPropertySheet::reset(int index) +{ + if (propertyName(index) == pagePropertyName) { + setProperty(index, QString()); + return true; + } + return QDesignerPropertySheet::reset(index); +} + +bool QStackedWidgetPropertySheet::checkProperty(const QString &propertyName) +{ + return propertyName != pagePropertyName; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_stackedbox_p.h b/src/tools/designer/src/lib/shared/qdesigner_stackedbox_p.h new file mode 100644 index 00000000000..9ef21fcfd28 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_stackedbox_p.h @@ -0,0 +1,126 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_STACKEDBOX_H +#define QDESIGNER_STACKEDBOX_H + +#include "shared_global_p.h" +#include "qdesigner_propertysheet_p.h" + +QT_BEGIN_NAMESPACE + +class QStackedWidget; +class QWidget; +class QAction; +class QMenu; +class QToolButton; + +namespace qdesigner_internal { + class PromotionTaskMenu; +} + +// Event filter to be installed on a QStackedWidget in preview mode. +// Create two buttons to switch pages. + +class QDESIGNER_SHARED_EXPORT QStackedWidgetPreviewEventFilter : public QObject +{ + Q_OBJECT +public: + explicit QStackedWidgetPreviewEventFilter(QStackedWidget *parent); + + // Install helper on QStackedWidget + static void install(QStackedWidget *stackedWidget); + bool eventFilter(QObject *watched, QEvent *event) override; + + void setButtonToolTipEnabled(bool v) { m_buttonToolTipEnabled = v; } + bool buttonToolTipEnabled() const { return m_buttonToolTipEnabled; } + +public slots: + void updateButtons(); + void prevPage(); + void nextPage(); + +protected: + QStackedWidget *stackedWidget() const { return m_stackedWidget; } + virtual void gotoPage(int page); + +private: + void updateButtonToolTip(QObject *o); + + bool m_buttonToolTipEnabled; + QStackedWidget *m_stackedWidget; + QToolButton *m_prev; + QToolButton *m_next; +}; + +// Event filter to be installed on a QStackedWidget in editing mode. +// In addition to the browse buttons, handles context menu and everything + +class QDESIGNER_SHARED_EXPORT QStackedWidgetEventFilter : public QStackedWidgetPreviewEventFilter +{ + Q_OBJECT +public: + explicit QStackedWidgetEventFilter(QStackedWidget *parent); + + // Install helper on QStackedWidget + static void install(QStackedWidget *stackedWidget); + static QStackedWidgetEventFilter *eventFilterOf(const QStackedWidget *stackedWidget); + // Convenience to add a menu on a tackedWidget + static QMenu *addStackedWidgetContextMenuActions(const QStackedWidget *stackedWidget, QMenu *popup); + + // Add context menu and return page submenu or 0. + QMenu *addContextMenuActions(QMenu *popup); + +private slots: + void removeCurrentPage(); + void addPage(); + void addPageAfter(); + void changeOrder(); + +protected: + void gotoPage(int page) override; + +private: + QAction *m_actionPreviousPage; + QAction *m_actionNextPage; + QAction *m_actionDeletePage; + QAction *m_actionInsertPage; + QAction *m_actionInsertPageAfter; + QAction *m_actionChangePageOrder; + qdesigner_internal::PromotionTaskMenu* m_pagePromotionTaskMenu; +}; + +// PropertySheet to handle the "currentPageName" property +class QDESIGNER_SHARED_EXPORT QStackedWidgetPropertySheet : public QDesignerPropertySheet { +public: + explicit QStackedWidgetPropertySheet(QStackedWidget *object, QObject *parent = nullptr); + + void setProperty(int index, const QVariant &value) override; + QVariant property(int index) const override; + bool reset(int index) override; + bool isEnabled(int index) const override; + + // Check whether the property is to be saved. Returns false for the page + // properties (as the property sheet has no concept of 'stored') + static bool checkProperty(const QString &propertyName); + +private: + QStackedWidget *m_stackedWidget; +}; + +using QStackedWidgetPropertySheetFactory = QDesignerPropertySheetFactory; + +QT_END_NAMESPACE + +#endif // QDESIGNER_STACKEDBOX_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_tabwidget.cpp b/src/tools/designer/src/lib/shared/qdesigner_tabwidget.cpp new file mode 100644 index 00000000000..212ee44af82 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_tabwidget.cpp @@ -0,0 +1,531 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_tabwidget_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_propertycommand_p.h" +#include "promotiontaskmenu_p.h" +#include "formwindowbase_p.h" + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { +// Store tab widget as drag source +class MyMimeData : public QMimeData +{ + Q_OBJECT +public: + MyMimeData(const QTabWidget *tab) : m_tab(tab) {} + static bool fromMyTab(const QMimeData *mimeData, const QTabWidget *tab) { + if (!mimeData) + return false; + const MyMimeData *m = qobject_cast(mimeData); + return m && m->m_tab == tab; + } +private: + const QTabWidget *m_tab; +}; + +} // namespace qdesigner_internal + +// ------------- QTabWidgetEventFilter + +QTabWidgetEventFilter::QTabWidgetEventFilter(QTabWidget *parent) : + QObject(parent), + m_tabWidget(parent), + m_actionDeletePage(new QAction(tr("Delete"), this)), + m_actionInsertPage(new QAction(tr("Before Current Page"), this)), + m_actionInsertPageAfter(new QAction(tr("After Current Page"), this)), + m_pagePromotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(nullptr, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this)) +{ + tabBar()->setAcceptDrops(true); + tabBar()->installEventFilter(this); + + connect(m_actionInsertPage, &QAction::triggered, this, &QTabWidgetEventFilter::addPage); + connect(m_actionInsertPageAfter, &QAction::triggered, this, &QTabWidgetEventFilter::addPageAfter); + connect(m_actionDeletePage, &QAction::triggered, this, &QTabWidgetEventFilter::removeCurrentPage); +} + +QTabWidgetEventFilter::~QTabWidgetEventFilter() = default; + +void QTabWidgetEventFilter::install(QTabWidget *tabWidget) +{ + new QTabWidgetEventFilter(tabWidget); +} + +QTabWidgetEventFilter *QTabWidgetEventFilter::eventFilterOf(const QTabWidget *tabWidget) +{ + // Look for 1st order children only..otherwise, we might get filters of nested tab widgets + for (QObject *o : tabWidget->children()) { + if (!o->isWidgetType()) + if (QTabWidgetEventFilter *ef = qobject_cast(o)) + return ef; + } + return nullptr; +} + +QMenu *QTabWidgetEventFilter::addTabWidgetContextMenuActions(const QTabWidget *tabWidget, QMenu *popup) +{ + QTabWidgetEventFilter *filter = eventFilterOf(tabWidget); + if (!filter) + return nullptr; + return filter->addContextMenuActions(popup); +} + +QTabBar *QTabWidgetEventFilter::tabBar() const +{ + // QTabWidget::tabBar() accessor is protected, grmbl... + if (!m_cachedTabBar) { + const auto tabBars = m_tabWidget->findChildren(); + Q_ASSERT(tabBars.size() == 1); + m_cachedTabBar = tabBars.constFirst(); + } + return m_cachedTabBar; + +} + +static bool canMove(const QPoint &pressPoint, const QMouseEvent *e) +{ + const QPoint pt = pressPoint - e->position().toPoint(); + return pt.manhattanLength() > QApplication::startDragDistance(); +} + +bool QTabWidgetEventFilter::eventFilter(QObject *o, QEvent *e) +{ + const QEvent::Type type = e->type(); + // Do not try to locate tab bar and form window, etc. for uninteresting events and + // avoid asserts about missing tab bars when being destroyed + switch (type) { + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + case QEvent::DragLeave: + case QEvent::DragEnter: + case QEvent::DragMove: + case QEvent::Drop: + break; + default: + return false; + } + + if (o != tabBar()) + return false; + + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return false; + + bool handled = true; + switch (type) { + case QEvent::MouseButtonDblClick: + break; + case QEvent::MouseButtonPress: { + QMouseEvent *mev = static_cast(e); + if (QDesignerFormWindowInterface *fw = formWindow()) { + fw->clearSelection(); + fw->selectWidget(m_tabWidget, true); + } + if (mev->button() & Qt::LeftButton) { + m_mousePressed = true; + m_pressPoint = mev->position().toPoint(); + + QTabBar *tabbar = tabBar(); + const int count = tabbar->count(); + for (int i = 0; i < count; ++i) { + if (tabbar->tabRect(i).contains(m_pressPoint)) { + if (i != tabbar->currentIndex()) { + qdesigner_internal::SetPropertyCommand *cmd = new qdesigner_internal::SetPropertyCommand(fw); + cmd->init(m_tabWidget, u"currentIndex"_s, i); + fw->commandHistory()->push(cmd); + } + break; + } + } + } + } break; + + case QEvent::MouseButtonRelease: + m_mousePressed = false; + break; + + case QEvent::MouseMove: { + QMouseEvent *mouseEvent = static_cast(e); + if (m_mousePressed && canMove(m_pressPoint, mouseEvent)) { + const int index = m_tabWidget->currentIndex(); + if (index == -1) + break; + + m_mousePressed = false; + QDrag *drg = new QDrag(m_tabWidget); + drg->setMimeData(new qdesigner_internal::MyMimeData(m_tabWidget)); + + m_dragIndex = index; + m_dragPage = m_tabWidget->currentWidget(); + m_dragLabel = m_tabWidget->tabText(index); + m_dragIcon = m_tabWidget->tabIcon(index); + if (m_dragIcon.isNull()) { + QLabel *label = new QLabel(m_dragLabel); + label->adjustSize(); + drg->setPixmap(label->grab(QRect(0, 0, -1, -1))); + label->deleteLater(); + } else { + drg->setPixmap(m_dragIcon.pixmap(22, 22)); + } + + m_tabWidget->removeTab(m_dragIndex); + + const Qt::DropActions dropAction = drg->exec(Qt::MoveAction); + + if (dropAction == Qt::IgnoreAction) { + // abort + m_tabWidget->insertTab(m_dragIndex, m_dragPage, m_dragIcon, m_dragLabel); + m_tabWidget->setCurrentIndex(m_dragIndex); + } + + if (m_dropIndicator) + m_dropIndicator->hide(); + } + } break; + + case QEvent::DragLeave: { + if (m_dropIndicator) + m_dropIndicator->hide(); + } break; + + case QEvent::DragEnter: + case QEvent::DragMove: { + QDragMoveEvent *de = static_cast(e); + if (!qdesigner_internal::MyMimeData::fromMyTab(de->mimeData(), m_tabWidget)) + return false; + + if (de->proposedAction() == Qt::MoveAction) + de->acceptProposedAction(); + else { + de->setDropAction(Qt::MoveAction); + de->accept(); + } + + QRect rect; + const int index = pageFromPosition(de->position().toPoint(), rect); + + if (!m_dropIndicator) { + m_dropIndicator = new QWidget(m_tabWidget); + QPalette p = m_dropIndicator->palette(); + p.setColor(m_tabWidget->backgroundRole(), Qt::red); + m_dropIndicator->setPalette(p); + } + + QPoint pos; + if (index == m_tabWidget->count()) + pos = tabBar()->mapToParent(QPoint(rect.x() + rect.width(), rect.y())); + else + pos = tabBar()->mapToParent(QPoint(rect.x(), rect.y())); + + m_dropIndicator->setGeometry(pos.x(), pos.y() , 3, rect.height()); + m_dropIndicator->show(); + } break; + + case QEvent::Drop: { + QDropEvent *de = static_cast(e); + if (!qdesigner_internal::MyMimeData::fromMyTab(de->mimeData(), m_tabWidget)) + return false; + de->acceptProposedAction(); + de->accept(); + + QRect rect; + const int newIndex = pageFromPosition(de->position().toPoint(), rect); + + qdesigner_internal::MoveTabPageCommand *cmd = new qdesigner_internal::MoveTabPageCommand(fw); + m_tabWidget->insertTab(m_dragIndex, m_dragPage, m_dragIcon, m_dragLabel); + cmd->init(m_tabWidget, m_dragPage, m_dragIcon, m_dragLabel, m_dragIndex, newIndex); + fw->commandHistory()->push(cmd); + } break; + + default: + handled = false; + break; + } + + return handled; +} + +void QTabWidgetEventFilter::removeCurrentPage() +{ + if (!m_tabWidget->currentWidget()) + return; + + if (QDesignerFormWindowInterface *fw = formWindow()) { + qdesigner_internal::DeleteTabPageCommand *cmd = new qdesigner_internal::DeleteTabPageCommand(fw); + cmd->init(m_tabWidget); + fw->commandHistory()->push(cmd); + } +} + +void QTabWidgetEventFilter::addPage() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + qdesigner_internal::AddTabPageCommand *cmd = new qdesigner_internal::AddTabPageCommand(fw); + cmd->init(m_tabWidget, qdesigner_internal::AddTabPageCommand::InsertBefore); + fw->commandHistory()->push(cmd); + } +} + +void QTabWidgetEventFilter::addPageAfter() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + qdesigner_internal::AddTabPageCommand *cmd = new qdesigner_internal::AddTabPageCommand(fw); + cmd->init(m_tabWidget, qdesigner_internal::AddTabPageCommand::InsertAfter); + fw->commandHistory()->push(cmd); + } +} + +QDesignerFormWindowInterface *QTabWidgetEventFilter::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(const_cast(m_tabWidget)); +} + +// Get page from mouse position. Default to new page if in right half of last page? +int QTabWidgetEventFilter::pageFromPosition(const QPoint &pos, QRect &rect) const +{ + int index = 0; + const QTabBar *tabbar = tabBar(); + const int count = m_tabWidget->count(); + for (; index < count; index++) { + const QRect rc = tabbar->tabRect(index); + if (rc.contains(pos)) { + rect = rc; + break; + } + } + + if (index == count -1) { + QRect rect2 = rect; + rect2.setLeft(rect2.left() + rect2.width() / 2); + if (rect2.contains(pos)) + index++; + } + return index; +} + +QMenu *QTabWidgetEventFilter::addContextMenuActions(QMenu *popup) +{ + QMenu *pageMenu = nullptr; + const int count = m_tabWidget->count(); + m_actionDeletePage->setEnabled(count); + if (count) { + const int currentIndex = m_tabWidget->currentIndex(); + const QString pageSubMenuLabel = tr("Page %1 of %2").arg(currentIndex + 1).arg(count); + pageMenu = popup->addMenu(pageSubMenuLabel); + pageMenu->addAction(m_actionDeletePage); + // Set up promotion menu for current widget. + if (QWidget *page = m_tabWidget->currentWidget ()) { + m_pagePromotionTaskMenu->setWidget(page); + m_pagePromotionTaskMenu->addActions(QDesignerFormWindowInterface::findFormWindow(m_tabWidget), + qdesigner_internal::PromotionTaskMenu::SuppressGlobalEdit, + pageMenu); + } + QMenu *insertPageMenu = popup->addMenu(tr("Insert Page")); + insertPageMenu->addAction(m_actionInsertPageAfter); + insertPageMenu->addAction(m_actionInsertPage); + } else { + QAction *insertPageAction = popup->addAction(tr("Insert Page")); + connect(insertPageAction, &QAction::triggered, this, &QTabWidgetEventFilter::addPage); + } + popup->addSeparator(); + return pageMenu; +} + +// ----------- QTabWidgetPropertySheet + +static constexpr auto currentTabTextKey = "currentTabText"_L1; +static constexpr auto currentTabNameKey = "currentTabName"_L1; +static constexpr auto currentTabIconKey = "currentTabIcon"_L1; +static constexpr auto currentTabToolTipKey = "currentTabToolTip"_L1; +static constexpr auto currentTabWhatsThisKey = "currentTabWhatsThis"_L1; +static constexpr auto tabMovableKey = "movable"_L1; + +QTabWidgetPropertySheet::QTabWidgetPropertySheet(QTabWidget *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_tabWidget(object) +{ + createFakeProperty(currentTabTextKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + createFakeProperty(currentTabNameKey, QString()); + createFakeProperty(currentTabIconKey, QVariant::fromValue(qdesigner_internal::PropertySheetIconValue())); + if (formWindowBase()) + formWindowBase()->addReloadableProperty(this, indexOf(currentTabIconKey)); + createFakeProperty(currentTabToolTipKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + createFakeProperty(currentTabWhatsThisKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + // Prevent the tab widget's drag and drop handling from interfering with Designer's + createFakeProperty(tabMovableKey, QVariant(false)); +} + +QTabWidgetPropertySheet::TabWidgetProperty QTabWidgetPropertySheet::tabWidgetPropertyFromName(const QString &name) +{ + static const QHash tabWidgetPropertyHash = { + {currentTabTextKey, PropertyCurrentTabText}, + {currentTabNameKey, PropertyCurrentTabName}, + {currentTabIconKey, PropertyCurrentTabIcon}, + {currentTabToolTipKey, PropertyCurrentTabToolTip}, + {currentTabWhatsThisKey, PropertyCurrentTabWhatsThis} + }; + return tabWidgetPropertyHash.value(name, PropertyTabWidgetNone); +} + +void QTabWidgetPropertySheet::setProperty(int index, const QVariant &value) +{ + const TabWidgetProperty tabWidgetProperty = tabWidgetPropertyFromName(propertyName(index)); + if (tabWidgetProperty == PropertyTabWidgetNone) { + QDesignerPropertySheet::setProperty(index, value); + return; + } + + // index-dependent + const int currentIndex = m_tabWidget->currentIndex(); + QWidget *currentWidget = m_tabWidget->currentWidget(); + if (!currentWidget) + return; + + switch (tabWidgetProperty) { + case PropertyCurrentTabText: + m_tabWidget->setTabText(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].text = qvariant_cast(value); + break; + case PropertyCurrentTabName: + currentWidget->setObjectName(value.toString()); + break; + case PropertyCurrentTabIcon: + m_tabWidget->setTabIcon(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].icon = qvariant_cast(value); + break; + case PropertyCurrentTabToolTip: + m_tabWidget->setTabToolTip(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].tooltip = qvariant_cast(value); + break; + case PropertyCurrentTabWhatsThis: + m_tabWidget->setTabWhatsThis(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].whatsthis = qvariant_cast(value); + break; + case PropertyTabWidgetNone: + break; + } +} + +bool QTabWidgetPropertySheet::isEnabled(int index) const +{ + if (tabWidgetPropertyFromName(propertyName(index)) == PropertyTabWidgetNone) + return QDesignerPropertySheet::isEnabled(index); + return m_tabWidget->currentIndex() != -1; +} + +QVariant QTabWidgetPropertySheet::property(int index) const +{ + const TabWidgetProperty tabWidgetProperty = tabWidgetPropertyFromName(propertyName(index)); + if (tabWidgetProperty == PropertyTabWidgetNone) + return QDesignerPropertySheet::property(index); + + // index-dependent + QWidget *currentWidget = m_tabWidget->currentWidget(); + if (!currentWidget) { + if (tabWidgetProperty == PropertyCurrentTabIcon) + return QVariant::fromValue(qdesigner_internal::PropertySheetIconValue()); + if (tabWidgetProperty == PropertyCurrentTabText) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + if (tabWidgetProperty == PropertyCurrentTabToolTip) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + if (tabWidgetProperty == PropertyCurrentTabWhatsThis) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + return QVariant(QString()); + } + + // index-dependent + switch (tabWidgetProperty) { + case PropertyCurrentTabText: + return QVariant::fromValue(m_pageToData.value(currentWidget).text); + case PropertyCurrentTabName: + return currentWidget->objectName(); + case PropertyCurrentTabIcon: + return QVariant::fromValue(m_pageToData.value(currentWidget).icon); + case PropertyCurrentTabToolTip: + return QVariant::fromValue(m_pageToData.value(currentWidget).tooltip); + case PropertyCurrentTabWhatsThis: + return QVariant::fromValue(m_pageToData.value(currentWidget).whatsthis); + case PropertyTabWidgetNone: + break; + } + return QVariant(); +} + +bool QTabWidgetPropertySheet::reset(int index) +{ + const TabWidgetProperty tabWidgetProperty = tabWidgetPropertyFromName(propertyName(index)); + if (tabWidgetProperty == PropertyTabWidgetNone) + return QDesignerPropertySheet::reset(index); + + // index-dependent + QWidget *currentWidget = m_tabWidget->currentWidget(); + if (!currentWidget) + return false; + + // index-dependent + switch (tabWidgetProperty) { + case PropertyCurrentTabName: + setProperty(index, QString()); + break; + case PropertyCurrentTabToolTip: + m_pageToData[currentWidget].tooltip = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentTabWhatsThis: + m_pageToData[currentWidget].whatsthis = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentTabText: + m_pageToData[currentWidget].text = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentTabIcon: + m_pageToData[currentWidget].icon = qdesigner_internal::PropertySheetIconValue(); + setProperty(index, QIcon()); + break; + case PropertyTabWidgetNone: + break; + } + return true; +} + +bool QTabWidgetPropertySheet::checkProperty(const QString &propertyName) +{ + switch (tabWidgetPropertyFromName(propertyName)) { + case PropertyCurrentTabText: + case PropertyCurrentTabName: + case PropertyCurrentTabToolTip: + case PropertyCurrentTabWhatsThis: + case PropertyCurrentTabIcon: + return false; + default: + break; + } + return true; +} + +QT_END_NAMESPACE + +#include "qdesigner_tabwidget.moc" // required for MyMimeData diff --git a/src/tools/designer/src/lib/shared/qdesigner_tabwidget_p.h b/src/tools/designer/src/lib/shared/qdesigner_tabwidget_p.h new file mode 100644 index 00000000000..430c6958601 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_tabwidget_p.h @@ -0,0 +1,116 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_TABWIDGET_H +#define QDESIGNER_TABWIDGET_H + +#include "shared_global_p.h" +#include "qdesigner_propertysheet_p.h" +#include "qdesigner_utils_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QTabWidget; +class QTabBar; +class QMenu; +class QAction; + +namespace qdesigner_internal { + class PromotionTaskMenu; +} + +class QDESIGNER_SHARED_EXPORT QTabWidgetEventFilter : public QObject +{ + Q_OBJECT +public: + explicit QTabWidgetEventFilter(QTabWidget *parent); + ~QTabWidgetEventFilter(); + + // Install helper on QTabWidget + static void install(QTabWidget *tabWidget); + static QTabWidgetEventFilter *eventFilterOf(const QTabWidget *tabWidget); + // Convenience to add a menu on a tackedWidget + static QMenu *addTabWidgetContextMenuActions(const QTabWidget *tabWidget, QMenu *popup); + + // Add context menu and return page submenu or 0. + QMenu *addContextMenuActions(QMenu *popup); + + bool eventFilter(QObject *o, QEvent *e) override; + + QDesignerFormWindowInterface *formWindow() const; + +private slots: + void removeCurrentPage(); + void addPage(); + void addPageAfter(); + +private: + int pageFromPosition(const QPoint &pos, QRect &rect) const; + QTabBar *tabBar() const; + + QTabWidget *m_tabWidget; + mutable QPointer m_cachedTabBar; + QPoint m_pressPoint; + QWidget *m_dropIndicator = nullptr; + int m_dragIndex = -1; + QWidget *m_dragPage = nullptr; + QString m_dragLabel; + QIcon m_dragIcon; + bool m_mousePressed = false; + QAction *m_actionDeletePage; + QAction *m_actionInsertPage; + QAction *m_actionInsertPageAfter; + qdesigner_internal::PromotionTaskMenu* m_pagePromotionTaskMenu; +}; + +// PropertySheet to handle the page properties +class QDESIGNER_SHARED_EXPORT QTabWidgetPropertySheet : public QDesignerPropertySheet { +public: + explicit QTabWidgetPropertySheet(QTabWidget *object, QObject *parent = nullptr); + + void setProperty(int index, const QVariant &value) override; + QVariant property(int index) const override; + bool reset(int index) override; + bool isEnabled(int index) const override; + + // Check whether the property is to be saved. Returns false for the page + // properties (as the property sheet has no concept of 'stored') + static bool checkProperty(const QString &propertyName); + +private: + enum TabWidgetProperty { PropertyCurrentTabText, PropertyCurrentTabName, PropertyCurrentTabIcon, + PropertyCurrentTabToolTip, PropertyCurrentTabWhatsThis, PropertyTabWidgetNone }; + + static TabWidgetProperty tabWidgetPropertyFromName(const QString &name); + QTabWidget *m_tabWidget; + struct PageData + { + qdesigner_internal::PropertySheetStringValue text; + qdesigner_internal::PropertySheetStringValue tooltip; + qdesigner_internal::PropertySheetStringValue whatsthis; + qdesigner_internal::PropertySheetIconValue icon; + }; + QHash m_pageToData; +}; + +using QTabWidgetPropertySheetFactory = QDesignerPropertySheetFactory; + +QT_END_NAMESPACE + +#endif // QDESIGNER_TABWIDGET_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_taskmenu.cpp b/src/tools/designer/src/lib/shared/qdesigner_taskmenu.cpp new file mode 100644 index 00000000000..8bdf01f37b3 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_taskmenu.cpp @@ -0,0 +1,726 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_taskmenu_p.h" +#include "qdesigner_command_p.h" +#include "qdesigner_command2_p.h" +#include "richtexteditor_p.h" +#include "plaintexteditor_p.h" +#include "stylesheeteditor_p.h" +#include "qlayout_widget_p.h" +#include "layout_p.h" +#include "selectsignaldialog_p.h" +#include "spacer_widget_p.h" +#include "textpropertyeditor_p.h" +#include "promotiontaskmenu_p.h" +#include "metadatabase_p.h" +#include "signalslotdialog_p.h" +#include "qdesigner_membersheet_p.h" +#include "qdesigner_propertycommand_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_objectinspector_p.h" +#include "morphmenu_p.h" +#include "formlayoutmenu_p.h" +#include "widgetfactory_p.h" +#include "abstractintrospection_p.h" +#include "widgetdatabase_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static inline QAction *createSeparatorHelper(QObject *parent) { + QAction *rc = new QAction(parent); + rc->setSeparator(true); + return rc; +} + +static QString objName(const QDesignerFormEditorInterface *core, QObject *object) { + QDesignerPropertySheetExtension *sheet + = qt_extension(core->extensionManager(), object); + Q_ASSERT(sheet != nullptr); + + const int index = sheet->indexOf(u"objectName"_s); + const QVariant v = sheet->property(index); + if (v.canConvert()) + return v.value().value(); + return v.toString(); +} + +enum { ApplyMinimumWidth = 0x1, ApplyMinimumHeight = 0x2, ApplyMaximumWidth = 0x4, ApplyMaximumHeight = 0x8 }; + +namespace { +// --------------- ObjectNameDialog +class ObjectNameDialog : public QDialog +{ + public: + ObjectNameDialog(QWidget *parent, const QString &oldName); + QString newObjectName() const; + + private: + qdesigner_internal::TextPropertyEditor *m_editor; +}; + +ObjectNameDialog::ObjectNameDialog(QWidget *parent, const QString &oldName) + : QDialog(parent), + m_editor( new qdesigner_internal::TextPropertyEditor(this, qdesigner_internal::TextPropertyEditor::EmbeddingNone, + qdesigner_internal::ValidationObjectName)) +{ + setWindowTitle(QCoreApplication::translate("ObjectNameDialog", "Change Object Name")); + + QVBoxLayout *vboxLayout = new QVBoxLayout(this); + vboxLayout->addWidget(new QLabel(QCoreApplication::translate("ObjectNameDialog", "Object Name"))); + + m_editor->setText(oldName); + m_editor->selectAll(); + m_editor->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + vboxLayout->addWidget(m_editor); + + QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, + Qt::Horizontal, this); + QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok); + okButton->setDefault(true); + vboxLayout->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +QString ObjectNameDialog::newObjectName() const +{ + return m_editor->text(); +} +} // namespace + +namespace qdesigner_internal { + +// Sub menu displaying the alignment options of a widget in a managed +// grid/box layout cell. +class LayoutAlignmentMenu : public QObject { + Q_OBJECT +public: + explicit LayoutAlignmentMenu(QObject *parent); + + QAction *subMenuAction() const { return m_subMenuAction; } + + // Set up enabled state and checked actions according to widget (managed box/grid) + bool setAlignment(const QDesignerFormEditorInterface *core, QWidget *w); + + // Return the currently checked alignment + Qt::Alignment alignment() const; + +signals: + void changed(); + +private: + enum Actions { HorizNone, Left, HorizCenter, Right, VerticalNone, Top, VerticalCenter, Bottom }; + static QAction *createAction(const QString &text, int data, QMenu *menu, QActionGroup *ag); + + QAction *m_subMenuAction; + QActionGroup *m_horizGroup; + QActionGroup *m_verticalGroup; + QAction *m_actions[Bottom + 1]; +}; + +QAction *LayoutAlignmentMenu::createAction(const QString &text, int data, QMenu *menu, QActionGroup *ag) +{ + QAction * a = new QAction(text, nullptr); + a->setCheckable(true); + a->setData(QVariant(data)); + menu->addAction(a); + ag->addAction(a); + return a; +} + +LayoutAlignmentMenu::LayoutAlignmentMenu(QObject *parent) : QObject(parent), + m_subMenuAction(new QAction(QDesignerTaskMenu::tr("Layout Alignment"), parent)), + m_horizGroup(new QActionGroup(parent)), + m_verticalGroup(new QActionGroup(parent)) +{ + m_horizGroup->setExclusive(true); + m_verticalGroup->setExclusive(true); + connect(m_horizGroup, &QActionGroup::triggered, this, &LayoutAlignmentMenu::changed); + connect(m_verticalGroup, &QActionGroup::triggered, this, &LayoutAlignmentMenu::changed); + + QMenu *menu = new QMenu; + m_subMenuAction->setMenu(menu); + + m_actions[HorizNone] = createAction(QDesignerTaskMenu::tr("No Horizontal Alignment"), 0, menu, m_horizGroup); + m_actions[Left] = createAction(QDesignerTaskMenu::tr("Left"), Qt::AlignLeft, menu, m_horizGroup); + m_actions[HorizCenter] = createAction(QDesignerTaskMenu::tr("Center Horizontally"), Qt::AlignHCenter, menu, m_horizGroup); + m_actions[Right] = createAction(QDesignerTaskMenu::tr("Right"), Qt::AlignRight, menu, m_horizGroup); + menu->addSeparator(); + m_actions[VerticalNone] = createAction(QDesignerTaskMenu::tr("No Vertical Alignment"), 0, menu, m_verticalGroup); + m_actions[Top] = createAction(QDesignerTaskMenu::tr("Top"), Qt::AlignTop, menu, m_verticalGroup); + m_actions[VerticalCenter] = createAction(QDesignerTaskMenu::tr("Center Vertically"), Qt::AlignVCenter, menu, m_verticalGroup); + m_actions[Bottom] = createAction(QDesignerTaskMenu::tr("Bottom"), Qt::AlignBottom, menu, m_verticalGroup); +} + +bool LayoutAlignmentMenu::setAlignment(const QDesignerFormEditorInterface *core, QWidget *w) +{ + bool enabled; + const Qt::Alignment alignment = LayoutAlignmentCommand::alignmentOf(core, w, &enabled); + m_subMenuAction->setEnabled(enabled); + if (!enabled) { + m_actions[HorizNone]->setChecked(true); + m_actions[VerticalNone]->setChecked(true); + return false; + } + // Get alignment + switch (alignment & Qt::AlignHorizontal_Mask) { + case Qt::AlignLeft: + m_actions[Left]->setChecked(true); + break; + case Qt::AlignHCenter: + m_actions[HorizCenter]->setChecked(true); + break; + case Qt::AlignRight: + m_actions[Right]->setChecked(true); + break; + default: + m_actions[HorizNone]->setChecked(true); + break; + } + switch (alignment & Qt::AlignVertical_Mask) { + case Qt::AlignTop: + m_actions[Top]->setChecked(true); + break; + case Qt::AlignVCenter: + m_actions[VerticalCenter]->setChecked(true); + break; + case Qt::AlignBottom: + m_actions[Bottom]->setChecked(true); + break; + default: + m_actions[VerticalNone]->setChecked(true); + break; + } + return true; +} + +Qt::Alignment LayoutAlignmentMenu::alignment() const +{ + Qt::Alignment alignment; + if (const QAction *horizAction = m_horizGroup->checkedAction()) + if (const int horizAlign = horizAction->data().toInt()) + alignment |= static_cast(horizAlign); + if (const QAction *vertAction = m_verticalGroup->checkedAction()) + if (const int vertAlign = vertAction->data().toInt()) + alignment |= static_cast(vertAlign); + return alignment; +} + +// -------------- QDesignerTaskMenuPrivate +class QDesignerTaskMenuPrivate { +public: + QDesignerTaskMenuPrivate(QWidget *widget, QObject *parent); + + QDesignerTaskMenu *m_q; + QPointer m_widget; + QAction *m_separator; + QAction *m_separator2; + QAction *m_separator3; + QAction *m_separator4; + QAction *m_separator5; + QAction *m_separator6; + QAction *m_separator7; + QAction *m_changeObjectNameAction; + QAction *m_changeToolTip; + QAction *m_changeWhatsThis; + QAction *m_changeStyleSheet; + MorphMenu *m_morphMenu; + FormLayoutMenu *m_formLayoutMenu; + + QAction *m_addMenuBar; + QAction *m_addToolBar; + QAction *m_addAreaSubMenu; + QAction *m_addStatusBar; + QAction *m_removeStatusBar; + QAction *m_containerFakeMethods; + QAction *m_navigateToSlot; + PromotionTaskMenu* m_promotionTaskMenu; + QActionGroup *m_sizeActionGroup; + LayoutAlignmentMenu m_layoutAlignmentMenu; + QAction *m_sizeActionsSubMenu; +}; + +QDesignerTaskMenuPrivate::QDesignerTaskMenuPrivate(QWidget *widget, QObject *parent) : + m_q(nullptr), + m_widget(widget), + m_separator(createSeparatorHelper(parent)), + m_separator2(createSeparatorHelper(parent)), + m_separator3(createSeparatorHelper(parent)), + m_separator4(createSeparatorHelper(parent)), + m_separator5(createSeparatorHelper(parent)), + m_separator6(createSeparatorHelper(parent)), + m_separator7(createSeparatorHelper(parent)), + m_changeObjectNameAction(new QAction(QDesignerTaskMenu::tr("Change objectName..."), parent)), + m_changeToolTip(new QAction(QDesignerTaskMenu::tr("Change toolTip..."), parent)), + m_changeWhatsThis(new QAction(QDesignerTaskMenu::tr("Change whatsThis..."), parent)), + m_changeStyleSheet(new QAction(QDesignerTaskMenu::tr("Change styleSheet..."), parent)), + m_morphMenu(new MorphMenu(parent)), + m_formLayoutMenu(new FormLayoutMenu(parent)), + m_addMenuBar(new QAction(QDesignerTaskMenu::tr("Create Menu Bar"), parent)), + m_addToolBar(new QAction(QDesignerTaskMenu::tr("Add Tool Bar"), parent)), + m_addAreaSubMenu(new QAction(QDesignerTaskMenu::tr("Add Tool Bar to Other Area"), parent)), + m_addStatusBar(new QAction(QDesignerTaskMenu::tr("Create Status Bar"), parent)), + m_removeStatusBar(new QAction(QDesignerTaskMenu::tr("Remove Status Bar"), parent)), + m_containerFakeMethods(new QAction(QDesignerTaskMenu::tr("Change signals/slots..."), parent)), + m_navigateToSlot(new QAction(QDesignerTaskMenu::tr("Go to slot..."), parent)), + m_promotionTaskMenu(new PromotionTaskMenu(widget, PromotionTaskMenu::ModeManagedMultiSelection, parent)), + m_sizeActionGroup(new QActionGroup(parent)), + m_layoutAlignmentMenu(parent), + m_sizeActionsSubMenu(new QAction(QDesignerTaskMenu::tr("Size Constraints"), parent)) +{ + QMenu *sizeMenu = new QMenu; + m_sizeActionsSubMenu->setMenu(sizeMenu); + QAction *sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Minimum Width")); + sizeAction->setData(ApplyMinimumWidth); + sizeMenu->addAction(sizeAction); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Minimum Height")); + sizeAction->setData(ApplyMinimumHeight); + sizeMenu->addAction(sizeAction); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Minimum Size")); + sizeAction->setData(ApplyMinimumWidth|ApplyMinimumHeight); + sizeMenu->addAction(sizeAction); + + sizeMenu->addSeparator(); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Maximum Width")); + sizeAction->setData(ApplyMaximumWidth); + sizeMenu->addAction(sizeAction); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Maximum Height")); + sizeAction->setData(ApplyMaximumHeight); + sizeMenu->addAction(sizeAction); + + sizeAction = m_sizeActionGroup->addAction(QDesignerTaskMenu::tr("Set Maximum Size")); + sizeAction->setData(ApplyMaximumWidth|ApplyMaximumHeight); + sizeMenu->addAction(sizeAction); +} + +// --------- QDesignerTaskMenu +QDesignerTaskMenu::QDesignerTaskMenu(QWidget *widget, QObject *parent) : + QObject(parent), + d(new QDesignerTaskMenuPrivate(widget, parent)) +{ + d->m_q = this; + Q_ASSERT(qobject_cast(widget) == 0); + + connect(d->m_changeObjectNameAction, &QAction::triggered, this, &QDesignerTaskMenu::changeObjectName); + connect(d->m_changeToolTip, &QAction::triggered, this, &QDesignerTaskMenu::changeToolTip); + connect(d->m_changeWhatsThis, &QAction::triggered, this, &QDesignerTaskMenu::changeWhatsThis); + connect(d->m_changeStyleSheet, &QAction::triggered, this, &QDesignerTaskMenu::changeStyleSheet); + connect(d->m_addMenuBar, &QAction::triggered, this, &QDesignerTaskMenu::createMenuBar); + connect(d->m_addToolBar, &QAction::triggered, this, + [this] () { this->addToolBar(Qt::TopToolBarArea); }); + auto areaMenu = new QMenu; + d->m_addAreaSubMenu->setMenu(areaMenu); + areaMenu->addAction(QDesignerTaskMenu::tr("Left"), this, + [this] () { this->addToolBar(Qt::LeftToolBarArea); }); + areaMenu->addAction(QDesignerTaskMenu::tr("Right"), this, + [this] () { this->addToolBar(Qt::RightToolBarArea); }); + areaMenu->addAction(QDesignerTaskMenu::tr("Bottom"), this, + [this] () { this->addToolBar(Qt::BottomToolBarArea); }); + connect(d->m_addStatusBar, &QAction::triggered, this, &QDesignerTaskMenu::createStatusBar); + connect(d->m_removeStatusBar, &QAction::triggered, this, &QDesignerTaskMenu::removeStatusBar); + connect(d->m_containerFakeMethods, &QAction::triggered, this, &QDesignerTaskMenu::containerFakeMethods); + connect(d->m_navigateToSlot, &QAction::triggered, this, &QDesignerTaskMenu::slotNavigateToSlot); + connect(d->m_sizeActionGroup, &QActionGroup::triggered, this, &QDesignerTaskMenu::applySize); + connect(&d->m_layoutAlignmentMenu, &LayoutAlignmentMenu::changed, + this, &QDesignerTaskMenu::slotLayoutAlignment); +} + +QDesignerTaskMenu::~QDesignerTaskMenu() +{ + delete d; +} + +QAction *QDesignerTaskMenu::createSeparator() +{ + return createSeparatorHelper(this); +} + +QWidget *QDesignerTaskMenu::widget() const +{ + return d->m_widget; +} + +QDesignerFormWindowInterface *QDesignerTaskMenu::formWindow() const +{ + QDesignerFormWindowInterface *result = QDesignerFormWindowInterface::findFormWindow(widget()); + Q_ASSERT(result != nullptr); + return result; +} + +void QDesignerTaskMenu::createMenuBar() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QMainWindow *mw = qobject_cast(fw->mainContainer()); + if (!mw) { + // ### warning message + return; + } + + CreateMenuBarCommand *cmd = new CreateMenuBarCommand(fw); + cmd->init(mw); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerTaskMenu::addToolBar(Qt::ToolBarArea area) +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QMainWindow *mw = qobject_cast(fw->mainContainer()); + if (!mw) { + // ### warning message + return; + } + + AddToolBarCommand *cmd = new AddToolBarCommand(fw); + cmd->init(mw, area); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerTaskMenu::createStatusBar() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QMainWindow *mw = qobject_cast(fw->mainContainer()); + if (!mw) { + // ### warning message + return; + } + + CreateStatusBarCommand *cmd = new CreateStatusBarCommand(fw); + cmd->init(mw); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerTaskMenu::removeStatusBar() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QMainWindow *mw = qobject_cast(fw->mainContainer()); + if (!mw) { + // ### warning message + return; + } + + DeleteStatusBarCommand *cmd = new DeleteStatusBarCommand(fw); + cmd->init(mw->findChild(QString(), Qt::FindDirectChildrenOnly)); + fw->commandHistory()->push(cmd); + } +} + +QList QDesignerTaskMenu::taskActions() const +{ + QDesignerFormWindowInterface *formWindow = QDesignerFormWindowInterface::findFormWindow(widget()); + Q_ASSERT(formWindow); + + const bool isMainContainer = formWindow->mainContainer() == widget(); + + QList actions; + + if (const QMainWindow *mw = qobject_cast(formWindow->mainContainer())) { + if (isMainContainer || mw->centralWidget() == widget()) { + if (mw->findChild(QString(), Qt::FindDirectChildrenOnly) == nullptr) + actions.append(d->m_addMenuBar); + + actions.append(d->m_addToolBar); + actions.append(d->m_addAreaSubMenu); + // ### create the status bar + if (mw->findChild(QString(), Qt::FindDirectChildrenOnly)) + actions.append(d->m_removeStatusBar); + else + actions.append(d->m_addStatusBar); + + actions.append(d->m_separator); + } + } + actions.append(d->m_changeObjectNameAction); + d->m_morphMenu->populate(d->m_widget, formWindow, actions); + d->m_formLayoutMenu->populate(d->m_widget, formWindow, actions); + actions.append(d->m_separator2); + actions.append(d->m_changeToolTip); + actions.append(d->m_changeWhatsThis); + actions.append(d->m_changeStyleSheet); + actions.append(d->m_separator6); + actions.append(d->m_sizeActionsSubMenu); + if (d->m_layoutAlignmentMenu.setAlignment(formWindow->core(), d->m_widget)) + actions.append(d->m_layoutAlignmentMenu.subMenuAction()); + + d->m_promotionTaskMenu->setMode(formWindow->isManaged(d->m_widget) ? + PromotionTaskMenu::ModeManagedMultiSelection : PromotionTaskMenu::ModeUnmanagedMultiSelection); + d->m_promotionTaskMenu->addActions(formWindow, PromotionTaskMenu::LeadingSeparator, actions); + + if (isMainContainer && !qt_extension(formWindow->core()->extensionManager(), formWindow->core())) { + actions.append(d->m_separator5); + actions.append(d->m_containerFakeMethods); + } + + if (isSlotNavigationEnabled(formWindow->core())) { + actions.append(d->m_separator7); + actions.append(d->m_navigateToSlot); + } + + return actions; +} + +void QDesignerTaskMenu::changeObjectName() +{ + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw != nullptr); + + const QString oldObjectName = objName(fw->core(), widget()); + + ObjectNameDialog dialog(fw, oldObjectName); + if (dialog.exec() == QDialog::Accepted) { + const QString newObjectName = dialog.newObjectName(); + if (!newObjectName.isEmpty() && newObjectName != oldObjectName ) { + PropertySheetStringValue objectNameValue; + objectNameValue.setValue(newObjectName); + setProperty(fw, CurrentWidgetMode, u"objectName"_s, + QVariant::fromValue(objectNameValue)); + } + } +} + +void QDesignerTaskMenu::changeTextProperty(const QString &propertyName, const QString &windowTitle, PropertyMode pm, Qt::TextFormat desiredFormat) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + Q_ASSERT(d->m_widget->parentWidget() != nullptr); + + const QDesignerPropertySheetExtension *sheet = qt_extension(fw->core()->extensionManager(), d->m_widget); + const int index = sheet->indexOf(propertyName); + if (index == -1) { + qDebug() << "** WARNING Invalid property" << propertyName << " passed to changeTextProperty!"; + return; + } + PropertySheetStringValue textValue = qvariant_cast(sheet->property(index)); + const QString oldText = textValue.value(); + // Pop up respective dialog + bool accepted = false; + QString newText; + switch (desiredFormat) { + case Qt::PlainText: { + PlainTextEditorDialog dlg(fw->core(), fw); + if (!windowTitle.isEmpty()) + dlg.setWindowTitle(windowTitle); + dlg.setDefaultFont(d->m_widget->font()); + dlg.setText(oldText); + accepted = dlg.showDialog() == QDialog::Accepted; + newText = dlg.text(); + } + break; + default: { + RichTextEditorDialog dlg(fw->core(), fw); + if (!windowTitle.isEmpty()) + dlg.setWindowTitle(windowTitle); + dlg.setDefaultFont(d->m_widget->font()); + dlg.setText(oldText); + accepted = dlg.showDialog() == QDialog::Accepted; + newText = dlg.text(desiredFormat); + } + break; + } + // change property + if (!accepted || oldText == newText) + return; + + + textValue.setValue(newText); + setProperty(fw, pm, propertyName, QVariant::fromValue(textValue)); +} + +void QDesignerTaskMenu::changeToolTip() +{ + changeTextProperty(u"toolTip"_s, tr("Edit ToolTip"), MultiSelectionMode, Qt::AutoText); +} + +void QDesignerTaskMenu::changeWhatsThis() +{ + changeTextProperty(u"whatsThis"_s, tr("Edit WhatsThis"), MultiSelectionMode, Qt::AutoText); +} + +void QDesignerTaskMenu::changeStyleSheet() +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + StyleSheetPropertyEditorDialog dlg(fw, fw, d->m_widget); + dlg.exec(); + } +} + +void QDesignerTaskMenu::containerFakeMethods() +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + SignalSlotDialog::editMetaDataBase(fw, d->m_widget, fw); +} + +bool QDesignerTaskMenu::isSlotNavigationEnabled(const QDesignerFormEditorInterface *core) +{ + return core->integration()->hasFeature(QDesignerIntegration::SlotNavigationFeature); +} + +void QDesignerTaskMenu::slotNavigateToSlot() +{ + QDesignerFormEditorInterface *core = formWindow()->core(); + Q_ASSERT(core); + navigateToSlot(core, widget()); +} + +void QDesignerTaskMenu::navigateToSlot(QDesignerFormEditorInterface *core, + QObject *object, + const QString &defaultSignal) +{ + SelectSignalDialog dialog; + dialog.populate(core, object, defaultSignal); + if (dialog.exec() != QDialog::Accepted) + return; + // TODO: Check whether signal is connected to slot + const SelectSignalDialog::Method method = dialog.selectedMethod(); + if (method.isValid()) { + core->integration()->emitNavigateToSlot(objName(core, object), + method.signature, + method.parameterNames); + } +} + +// Add a command that takes over the value of the current geometry as +// minimum/maximum size according to the flags. +static void createSizeCommand(QDesignerFormWindowInterface *fw, QWidget *w, int flags) +{ + const QSize size = w->size(); + if (flags & (ApplyMinimumWidth|ApplyMinimumHeight)) { + QSize minimumSize = w-> minimumSize(); + if (flags & ApplyMinimumWidth) + minimumSize.setWidth(size.width()); + if (flags & ApplyMinimumHeight) + minimumSize.setHeight(size.height()); + SetPropertyCommand* cmd = new SetPropertyCommand(fw); + cmd->init(w, u"minimumSize"_s, minimumSize); + fw->commandHistory()->push(cmd); + } + if (flags & (ApplyMaximumWidth|ApplyMaximumHeight)) { + QSize maximumSize = w-> maximumSize(); + if (flags & ApplyMaximumWidth) + maximumSize.setWidth(size.width()); + if (flags & ApplyMaximumHeight) + maximumSize.setHeight(size.height()); + SetPropertyCommand* cmd = new SetPropertyCommand(fw); + cmd->init(w, u"maximumSize"_s, maximumSize); + fw->commandHistory()->push(cmd); + } +} + +void QDesignerTaskMenu::applySize(QAction *a) +{ + QDesignerFormWindowInterface *fw = formWindow(); + if (!fw) + return; + + const QWidgetList selection = applicableWidgets(fw, MultiSelectionMode); + if (selection.isEmpty()) + return; + + const int mask = a->data().toInt(); + fw->commandHistory()->beginMacro(tr("Set size constraint on %n widget(s)", nullptr, + int(selection.size()))); + for (auto *w : selection) + createSizeCommand(fw, w, mask); + fw->commandHistory()->endMacro(); +} + +template + static void getApplicableObjects(const QDesignerFormWindowInterface *fw, QWidget *current, + QDesignerTaskMenu::PropertyMode pm, Container *c) +{ + // Current is always first + c->push_back(current); + if (pm == QDesignerTaskMenu::CurrentWidgetMode) + return; + QDesignerObjectInspector *designerObjectInspector = qobject_cast(fw->core()->objectInspector()); + if (!designerObjectInspector) + return; // Ooops, someone plugged an old-style Object Inspector + // Add managed or unmanaged selection according to current type, make current first + Selection s; + designerObjectInspector->getSelection(s); + const QWidgetList &source = fw->isManaged(current) ? s.managed : s.unmanaged; + for (auto *w : source) { + if (w != current) // was first + c->append(w); + } +} + +QObjectList QDesignerTaskMenu::applicableObjects(const QDesignerFormWindowInterface *fw, PropertyMode pm) const +{ + QObjectList rc; + getApplicableObjects(fw, d->m_widget, pm, &rc); + return rc; +} + +QWidgetList QDesignerTaskMenu::applicableWidgets(const QDesignerFormWindowInterface *fw, PropertyMode pm) const +{ + QWidgetList rc; + getApplicableObjects(fw, d->m_widget, pm, &rc); + return rc; +} + +void QDesignerTaskMenu::setProperty(QDesignerFormWindowInterface *fw, PropertyMode pm, const QString &name, const QVariant &newValue) +{ + SetPropertyCommand* setPropertyCommand = new SetPropertyCommand(fw); + if (setPropertyCommand->init(applicableObjects(fw, pm), name, newValue, d->m_widget)) { + fw->commandHistory()->push(setPropertyCommand); + } else { + delete setPropertyCommand; + qDebug() << "Unable to set property " << name << '.'; + } +} + +void QDesignerTaskMenu::slotLayoutAlignment() +{ + QDesignerFormWindowInterface *fw = formWindow(); + const Qt::Alignment newAlignment = d->m_layoutAlignmentMenu.alignment(); + LayoutAlignmentCommand *cmd = new LayoutAlignmentCommand(fw); + if (cmd->init(d->m_widget, newAlignment)) { + fw->commandHistory()->push(cmd); + } else { + delete cmd; + } +} +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#include "qdesigner_taskmenu.moc" diff --git a/src/tools/designer/src/lib/shared/qdesigner_taskmenu_p.h b/src/tools/designer/src/lib/shared/qdesigner_taskmenu_p.h new file mode 100644 index 00000000000..9c504f89308 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_taskmenu_p.h @@ -0,0 +1,93 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_TASKMENU_H +#define QDESIGNER_TASKMENU_H + +#include "shared_global_p.h" +#include "extensionfactory_p.h" +#include + +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { +class QDesignerTaskMenuPrivate; + +class QDESIGNER_SHARED_EXPORT QDesignerTaskMenu: public QObject, public QDesignerTaskMenuExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerTaskMenuExtension) +public: + QDesignerTaskMenu(QWidget *widget, QObject *parent); + ~QDesignerTaskMenu() override; + + QWidget *widget() const; + + QList taskActions() const override; + + enum PropertyMode { CurrentWidgetMode, MultiSelectionMode }; + + static bool isSlotNavigationEnabled(const QDesignerFormEditorInterface *core); + static void navigateToSlot(QDesignerFormEditorInterface *core, QObject *o, + const QString &defaultSignal = QString()); + +protected: + + QDesignerFormWindowInterface *formWindow() const; + void changeTextProperty(const QString &propertyName, const QString &windowTitle, PropertyMode pm, Qt::TextFormat desiredFormat); + + QAction *createSeparator(); + + /* Retrieve the list of objects the task menu is supposed to act on. Note that a task menu can be invoked for + * an unmanaged widget [as of 4.5], in which case it must not use the cursor selection, + * but the unmanaged selection of the object inspector. */ + QObjectList applicableObjects(const QDesignerFormWindowInterface *fw, PropertyMode pm) const; + QWidgetList applicableWidgets(const QDesignerFormWindowInterface *fw, PropertyMode pm) const; + + void setProperty(QDesignerFormWindowInterface *fw, PropertyMode pm, const QString &name, const QVariant &newValue); + +private slots: + void changeObjectName(); + void changeToolTip(); + void changeWhatsThis(); + void changeStyleSheet(); + void createMenuBar(); + void addToolBar(Qt::ToolBarArea area); + void createStatusBar(); + void removeStatusBar(); + void containerFakeMethods(); + void slotNavigateToSlot(); + void applySize(QAction *a); + void slotLayoutAlignment(); + +private: + QDesignerTaskMenuPrivate *d; +}; + +using QDesignerTaskMenuFactory = ExtensionFactory; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_TASKMENU_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_toolbar.cpp b/src/tools/designer/src/lib/shared/qdesigner_toolbar.cpp new file mode 100644 index 00000000000..a5d75d23f86 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_toolbar.cpp @@ -0,0 +1,463 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_toolbar_p.h" +#include "qdesigner_command_p.h" +#include "actionrepository_p.h" +#include "actionprovider_p.h" +#include "qdesigner_utils_p.h" +#include "qdesigner_objectinspector_p.h" +#include "promotiontaskmenu_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +using ActionList = QList; + +namespace qdesigner_internal { +// ------------------- ToolBarEventFilter +void ToolBarEventFilter::install(QToolBar *tb) +{ + ToolBarEventFilter *tf = new ToolBarEventFilter(tb); + tb->installEventFilter(tf); + tb->setAcceptDrops(true); // ### fake +} + +ToolBarEventFilter::ToolBarEventFilter(QToolBar *tb) : + QObject(tb), + m_toolBar(tb), + m_promotionTaskMenu(nullptr) +{ +} + +ToolBarEventFilter *ToolBarEventFilter::eventFilterOf(const QToolBar *tb) +{ + // Look for 1st order children only..otherwise, we might get filters of nested widgets + for (QObject *o : tb->children()) { + if (!o->isWidgetType()) + if (ToolBarEventFilter *ef = qobject_cast(o)) + return ef; + } + return nullptr; +} + +bool ToolBarEventFilter::eventFilter (QObject *watched, QEvent *event) +{ + if (watched != m_toolBar) + return QObject::eventFilter (watched, event); + + bool handled = false; + switch (event->type()) { + case QEvent::ChildAdded: { + // Children should not interact with the mouse + const QChildEvent *ce = static_cast(event); + if (QWidget *w = qobject_cast(ce->child())) { + w->setAttribute(Qt::WA_TransparentForMouseEvents, true); + w->setFocusPolicy(Qt::NoFocus); + } + } + break; + case QEvent::ContextMenu: + handled = handleContextMenuEvent(static_cast(event)); + break; + case QEvent::DragEnter: + case QEvent::DragMove: + handled = handleDragEnterMoveEvent(static_cast(event)); + break; + case QEvent::DragLeave: + handled = handleDragLeaveEvent(static_cast(event)); + break; + case QEvent::Drop: + handled = handleDropEvent(static_cast(event)); + break; + case QEvent::MouseButtonPress: + handled = handleMousePressEvent(static_cast(event)); + break; + case QEvent::MouseButtonRelease: + handled = handleMouseReleaseEvent(static_cast(event)); + break; + case QEvent::MouseMove: + handled = handleMouseMoveEvent(static_cast(event)); + break; + default: + break; + } + + return handled || QObject::eventFilter(watched, event); +} + +ActionList ToolBarEventFilter::contextMenuActions(const QPoint &globalPos) +{ + ActionList rc; + const int index = actionIndexAt(m_toolBar, m_toolBar->mapFromGlobal(globalPos), m_toolBar->orientation()); + const auto actions = m_toolBar->actions(); + QAction *action = index != -1 ?actions.at(index) : 0; + QVariant itemData; + + // Insert before + if (action && index != 0 && !action->isSeparator()) { + QAction *newSeperatorAct = new QAction(tr("Insert Separator before '%1'").arg(action->objectName()), nullptr); + itemData.setValue(action); + newSeperatorAct->setData(itemData); + connect(newSeperatorAct, &QAction::triggered, this, &ToolBarEventFilter::slotInsertSeparator); + rc.push_back(newSeperatorAct); + } + + // Append separator + if (actions.isEmpty() || !actions.constLast()->isSeparator()) { + QAction *newSeperatorAct = new QAction(tr("Append Separator"), nullptr); + itemData.setValue(static_cast(nullptr)); + newSeperatorAct->setData(itemData); + connect(newSeperatorAct, &QAction::triggered, this, &ToolBarEventFilter::slotInsertSeparator); + rc.push_back(newSeperatorAct); + } + // Promotion + if (!m_promotionTaskMenu) + m_promotionTaskMenu = new PromotionTaskMenu(m_toolBar, PromotionTaskMenu::ModeSingleWidget, this); + m_promotionTaskMenu->addActions(formWindow(), PromotionTaskMenu::LeadingSeparator|PromotionTaskMenu::TrailingSeparator, rc); + // Remove + if (action) { + QAction *a = new QAction(tr("Remove action '%1'").arg(action->objectName()), nullptr); + itemData.setValue(action); + a->setData(itemData); + connect(a, &QAction::triggered, this, &ToolBarEventFilter::slotRemoveSelectedAction); + rc.push_back(a); + } + + QAction *remove_toolbar = new QAction(tr("Remove Toolbar '%1'").arg(m_toolBar->objectName()), nullptr); + connect(remove_toolbar, &QAction::triggered, this, &ToolBarEventFilter::slotRemoveToolBar); + rc.push_back(remove_toolbar); + return rc; +} + +bool ToolBarEventFilter::handleContextMenuEvent(QContextMenuEvent * event ) +{ + event->accept(); + + const QPoint globalPos = event->globalPos(); + const ActionList al = contextMenuActions(event->globalPos()); + + QMenu menu(nullptr); + for (auto *a : al) + menu.addAction(a); + menu.exec(globalPos); + return true; +} + +void ToolBarEventFilter::slotRemoveSelectedAction() +{ + QAction *action = qobject_cast(sender()); + if (!action) + return; + + QAction *a = qvariant_cast(action->data()); + Q_ASSERT(a != nullptr); + + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw); + + const ActionList actions = m_toolBar->actions(); + const int pos = actions.indexOf(a); + QAction *action_before = nullptr; + if (pos != -1 && actions.size() > pos + 1) + action_before = actions.at(pos + 1); + + RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw); + cmd->init(m_toolBar, a, action_before); + fw->commandHistory()->push(cmd); +} + +void ToolBarEventFilter::slotRemoveToolBar() +{ + QDesignerFormWindowInterface *fw = formWindow(); + Q_ASSERT(fw); + DeleteToolBarCommand *cmd = new DeleteToolBarCommand(fw); + cmd->init(m_toolBar); + fw->commandHistory()->push(cmd); +} + +void ToolBarEventFilter::slotInsertSeparator() +{ + QDesignerFormWindowInterface *fw = formWindow(); + QAction *theSender = qobject_cast(sender()); + QAction *previous = qvariant_cast(theSender->data()); + fw->beginCommand(tr("Insert Separator")); + QAction *action = createAction(fw, u"separator"_s, true); + InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); + cmd->init(m_toolBar, action, previous); + fw->commandHistory()->push(cmd); + fw->endCommand(); +} + +QDesignerFormWindowInterface *ToolBarEventFilter::formWindow() const +{ + return QDesignerFormWindowInterface::findFormWindow(m_toolBar); +} + +QAction *ToolBarEventFilter::createAction(QDesignerFormWindowInterface *fw, const QString &objectName, bool separator) +{ + QAction *action = new QAction(fw); + fw->core()->widgetFactory()->initialize(action); + if (separator) + action->setSeparator(true); + + action->setObjectName(objectName); + fw->ensureUniqueObjectName(action); + + qdesigner_internal::AddActionCommand *cmd = new qdesigner_internal::AddActionCommand(fw); + cmd->init(action); + fw->commandHistory()->push(cmd); + + return action; +} + +void ToolBarEventFilter::adjustDragIndicator(const QPoint &pos) +{ + if (QDesignerFormWindowInterface *fw = formWindow()) { + QDesignerFormEditorInterface *core = fw->core(); + if (QDesignerActionProviderExtension *a = qt_extension(core->extensionManager(), m_toolBar)) + a->adjustIndicator(pos); + } +} + +void ToolBarEventFilter::hideDragIndicator() +{ + adjustDragIndicator(QPoint(-1, -1)); +} + +bool ToolBarEventFilter::handleMousePressEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton || withinHandleArea(m_toolBar, event->position().toPoint())) + return false; + + if (QDesignerFormWindowInterface *fw = formWindow()) { + QDesignerFormEditorInterface *core = fw->core(); + // Keep selection in sync + fw->clearSelection(false); + if (QDesignerObjectInspector *oi = qobject_cast(core->objectInspector())) { + oi->clearSelection(); + oi->selectObject(m_toolBar); + } + core->propertyEditor()->setObject(m_toolBar); + } + const auto pos = m_toolBar->mapFromGlobal(event->globalPosition().toPoint()); + if (actionIndexAt(m_toolBar, pos, m_toolBar->orientation()) != -1) { + m_startPosition = pos; + event->accept(); + return true; + } + return false; +} + +bool ToolBarEventFilter::handleMouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() != Qt::LeftButton || m_startPosition.isNull() || withinHandleArea(m_toolBar, event->position().toPoint())) + return false; + + // Accept the event, otherwise, form window selection will trigger + m_startPosition = QPoint(); + event->accept(); + return true; +} + +bool ToolBarEventFilter::handleMouseMoveEvent(QMouseEvent *event) +{ + if (m_startPosition.isNull() || withinHandleArea(m_toolBar, event->position().toPoint())) + return false; + + const QPoint pos = m_toolBar->mapFromGlobal(event->globalPosition().toPoint()); + if ((pos - m_startPosition).manhattanLength() > QApplication::startDragDistance() + && startDrag(m_startPosition, event->modifiers())) { + m_startPosition = QPoint(); + event->accept(); + return true; + } + return false; +} + +bool ToolBarEventFilter::handleDragEnterMoveEvent(QDragMoveEvent *event) +{ + const ActionRepositoryMimeData *d = qobject_cast(event->mimeData()); + if (!d) + return false; + + if (d->actionList().isEmpty()) { + event->ignore(); + hideDragIndicator(); + return true; + } + + QAction *action = d->actionList().first(); + if (!action || action->menu() || m_toolBar->actions().contains(action) || !Utils::isObjectAncestorOf(formWindow()->mainContainer(), action)) { + event->ignore(); + hideDragIndicator(); + return true; + } + + d->accept(event); + adjustDragIndicator(event->position().toPoint()); + return true; +} + +bool ToolBarEventFilter::handleDragLeaveEvent(QDragLeaveEvent *) +{ + hideDragIndicator(); + return false; +} + +bool ToolBarEventFilter::handleDropEvent(QDropEvent *event) +{ + const ActionRepositoryMimeData *d = qobject_cast(event->mimeData()); + if (!d) + return false; + + if (d->actionList().isEmpty()) { + event->ignore(); + hideDragIndicator(); + return true; + } + + QAction *action = d->actionList().first(); + + const ActionList actions = m_toolBar->actions(); + if (!action || actions.contains(action)) { + event->ignore(); + hideDragIndicator(); + return true; + } + + // Try to find action to 'insert before'. Click on action or in free area, else ignore. + QAction *beforeAction = nullptr; + const QPoint pos = event->position().toPoint(); + const int index = actionIndexAt(m_toolBar, pos, m_toolBar->orientation()); + if (index != -1) { + beforeAction = actions.at(index); + } else { + if (!freeArea(m_toolBar).contains(pos)) { + event->ignore(); + hideDragIndicator(); + return true; + } + } + + event->acceptProposedAction(); + QDesignerFormWindowInterface *fw = formWindow(); + InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); + cmd->init(m_toolBar, action, beforeAction); + fw->commandHistory()->push(cmd); + hideDragIndicator(); + return true; +} + +bool ToolBarEventFilter::startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers) +{ + const int index = actionIndexAt(m_toolBar, pos, m_toolBar->orientation()); + if (index == - 1) + return false; + + const ActionList actions = m_toolBar->actions(); + QAction *action = actions.at(index); + QDesignerFormWindowInterface *fw = formWindow(); + + const Qt::DropAction dropAction = (modifiers & Qt::ControlModifier) ? Qt::CopyAction : Qt::MoveAction; + if (dropAction == Qt::MoveAction) { + RemoveActionFromCommand *cmd = new RemoveActionFromCommand(fw); + const int nextIndex = index + 1; + QAction *nextAction = nextIndex < actions.size() ? actions.at(nextIndex) : 0; + cmd->init(m_toolBar, action, nextAction); + fw->commandHistory()->push(cmd); + } + + QDrag *drag = new QDrag(m_toolBar); + drag->setPixmap(ActionRepositoryMimeData::actionDragPixmap( action)); + drag->setMimeData(new ActionRepositoryMimeData(action, dropAction)); + + if (drag->exec(dropAction) == Qt::IgnoreAction) { + hideDragIndicator(); + if (dropAction == Qt::MoveAction) { + const ActionList currentActions = m_toolBar->actions(); + QAction *previous = nullptr; + if (index >= 0 && index < currentActions.size()) + previous = currentActions.at(index); + InsertActionIntoCommand *cmd = new InsertActionIntoCommand(fw); + cmd->init(m_toolBar, action, previous); + fw->commandHistory()->push(cmd); + } + } + return true; +} + +QAction *ToolBarEventFilter::actionAt(const QToolBar *tb, const QPoint &pos) +{ + const int index = actionIndexAt(tb, pos, tb->orientation()); + if (index == -1) + return nullptr; + return tb->actions().at(index); +} + +//that's a trick to get access to the initStyleOption which is a protected member +class FriendlyToolBar : public QToolBar { +public: + friend class ToolBarEventFilter; +}; + +QRect ToolBarEventFilter::handleArea(const QToolBar *tb) +{ + QStyleOptionToolBar opt; + static_cast(tb)->initStyleOption(&opt); + return tb->style()->subElementRect(QStyle::SE_ToolBarHandle, &opt, tb); +} + +bool ToolBarEventFilter::withinHandleArea(const QToolBar *tb, const QPoint &pos) +{ + return handleArea(tb).contains(pos); +} + +// Determine the free area behind the last action. +QRect ToolBarEventFilter::freeArea(const QToolBar *tb) +{ + QRect rc = QRect(QPoint(0, 0), tb->size()); + const ActionList actionList = tb->actions(); + QRect exclusionRectangle = actionList.isEmpty() + ? handleArea(tb) : tb->actionGeometry(actionList.constLast()); + switch (tb->orientation()) { + case Qt::Horizontal: + switch (tb->layoutDirection()) { + case Qt::LayoutDirectionAuto: // Should never happen + case Qt::LeftToRight: + rc.setX(exclusionRectangle.right() + 1); + break; + case Qt::RightToLeft: + rc.setRight(exclusionRectangle.x()); + break; + } + break; + case Qt::Vertical: + rc.setY(exclusionRectangle.bottom() + 1); + break; + } + return rc; +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_toolbar_p.h b/src/tools/designer/src/lib/shared/qdesigner_toolbar_p.h new file mode 100644 index 00000000000..bd897ae0a66 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_toolbar_p.h @@ -0,0 +1,98 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_TOOLBAR_H +#define QDESIGNER_TOOLBAR_H + +#include "shared_global_p.h" + +#include + +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QToolBar; +class QRect; +class QAction; + +namespace qdesigner_internal { + +class PromotionTaskMenu; + +// Special event filter for tool bars in designer. +// Handles drag and drop to and from. Ensures that each +// child widget is WA_TransparentForMouseEvents to enable drag and drop. + +class QDESIGNER_SHARED_EXPORT ToolBarEventFilter : public QObject { + Q_OBJECT + +public: + static void install(QToolBar *tb); + + // Find action by position. Note that QToolBar::actionAt() will + // not work as designer sets WA_TransparentForMouseEvents on its tool bar buttons + // to be able to drag them. This function will return the dummy + // sentinel action when applied to tool bars created by designer if the position matches. + static QAction *actionAt(const QToolBar *tb, const QPoint &pos); + + static bool withinHandleArea(const QToolBar *tb, const QPoint &pos); + static QRect handleArea(const QToolBar *tb); + static QRect freeArea(const QToolBar *tb); + + // Utility to create an action + static QAction *createAction(QDesignerFormWindowInterface *fw, const QString &objectName, bool separator); + + bool eventFilter (QObject *watched, QEvent *event) override; + + // Helper for task menu extension + QList contextMenuActions(const QPoint &globalPos = QPoint(-1, -1)); + + static ToolBarEventFilter *eventFilterOf(const QToolBar *tb); + +private slots: + void slotRemoveSelectedAction(); + void slotRemoveToolBar(); + void slotInsertSeparator(); + +private: + explicit ToolBarEventFilter(QToolBar *tb); + + bool handleContextMenuEvent(QContextMenuEvent * event); + bool handleDragEnterMoveEvent(QDragMoveEvent *event); + bool handleDragLeaveEvent(QDragLeaveEvent *); + bool handleDropEvent(QDropEvent *event); + bool handleMousePressEvent(QMouseEvent *event); + bool handleMouseReleaseEvent(QMouseEvent *event); + bool handleMouseMoveEvent(QMouseEvent *event); + + QDesignerFormWindowInterface *formWindow() const; + void adjustDragIndicator(const QPoint &pos); + void hideDragIndicator(); + bool startDrag(const QPoint &pos, Qt::KeyboardModifiers modifiers); + bool withinHandleArea(const QPoint &pos) const; + + QToolBar *m_toolBar; + PromotionTaskMenu *m_promotionTaskMenu; + QPoint m_startPosition; +}; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_TOOLBAR_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_toolbox.cpp b/src/tools/designer/src/lib/shared/qdesigner_toolbox.cpp new file mode 100644 index 00000000000..0295e4d0785 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_toolbox.cpp @@ -0,0 +1,397 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_toolbox_p.h" +#include "qdesigner_command_p.h" +#include "orderdialog_p.h" +#include "promotiontaskmenu_p.h" +#include "formwindowbase_p.h" + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +QToolBoxHelper::QToolBoxHelper(QToolBox *toolbox) : + QObject(toolbox), + m_toolbox(toolbox), + m_actionDeletePage(new QAction(tr("Delete Page"), this)), + m_actionInsertPage(new QAction(tr("Before Current Page"), this)), + m_actionInsertPageAfter(new QAction(tr("After Current Page"), this)), + m_actionChangePageOrder(new QAction(tr("Change Page Order..."), this)), + m_pagePromotionTaskMenu(new qdesigner_internal::PromotionTaskMenu(nullptr, qdesigner_internal::PromotionTaskMenu::ModeSingleWidget, this)) +{ + connect(m_actionDeletePage, &QAction::triggered, this, &QToolBoxHelper::removeCurrentPage); + connect(m_actionInsertPage, &QAction::triggered, this, &QToolBoxHelper::addPage); + connect(m_actionInsertPageAfter, &QAction::triggered, this, &QToolBoxHelper::addPageAfter); + connect(m_actionChangePageOrder, &QAction::triggered, this, &QToolBoxHelper::changeOrder); + + m_toolbox->installEventFilter(this); +} + +void QToolBoxHelper::install(QToolBox *toolbox) +{ + new QToolBoxHelper(toolbox); +} + +bool QToolBoxHelper::eventFilter(QObject *watched, QEvent *event) +{ + switch (event->type()) { + case QEvent::ChildPolished: + // Install on the buttons + if (watched == m_toolbox) { + QChildEvent *ce = static_cast(event); + if (!qstrcmp(ce->child()->metaObject()->className(), "QToolBoxButton")) + ce->child()->installEventFilter(this); + } + break; + case QEvent::ContextMenu: + if (watched != m_toolbox) { + // An action invoked from the passive interactor (ToolBox button) might + // cause its deletion within its event handler, triggering a warning. Re-post + // the event to the toolbox. + QContextMenuEvent *current = static_cast(event); + QContextMenuEvent *copy = new QContextMenuEvent(current->reason(), current->pos(), current-> globalPos(), current->modifiers()); + QApplication::postEvent(m_toolbox, copy); + current->accept(); + return true; + } + break; + case QEvent::MouseButtonRelease: + if (watched != m_toolbox) + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox)) { + fw->clearSelection(); + fw->selectWidget(m_toolbox, true); + } + break; + default: + break; + } + return QObject::eventFilter(watched, event); +} + +QToolBoxHelper *QToolBoxHelper::helperOf(const QToolBox *toolbox) +{ + // Look for 1st order children only..otherwise, we might get filters of nested widgets + for (QObject *o : toolbox->children()) { + if (!o->isWidgetType()) + if (QToolBoxHelper *h = qobject_cast(o)) + return h; + } + return nullptr; +} + +QMenu *QToolBoxHelper::addToolBoxContextMenuActions(const QToolBox *toolbox, QMenu *popup) +{ + QToolBoxHelper *helper = helperOf(toolbox); + if (!helper) + return nullptr; + return helper->addContextMenuActions(popup); +} + +void QToolBoxHelper::removeCurrentPage() +{ + if (m_toolbox->currentIndex() == -1 || !m_toolbox->widget(m_toolbox->currentIndex())) + return; + + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox)) { + qdesigner_internal::DeleteToolBoxPageCommand *cmd = new qdesigner_internal::DeleteToolBoxPageCommand(fw); + cmd->init(m_toolbox); + fw->commandHistory()->push(cmd); + } +} + +void QToolBoxHelper::addPage() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox)) { + qdesigner_internal::AddToolBoxPageCommand *cmd = new qdesigner_internal::AddToolBoxPageCommand(fw); + cmd->init(m_toolbox, qdesigner_internal::AddToolBoxPageCommand::InsertBefore); + fw->commandHistory()->push(cmd); + } +} + +void QToolBoxHelper::changeOrder() +{ + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox); + + if (!fw) + return; + + const QWidgetList oldPages = qdesigner_internal::OrderDialog::pagesOfContainer(fw->core(), m_toolbox); + const int pageCount = oldPages.size(); + if (pageCount < 2) + return; + + qdesigner_internal::OrderDialog dlg(fw); + dlg.setPageList(oldPages); + if (dlg.exec() == QDialog::Rejected) + return; + + const QWidgetList newPages = dlg.pageList(); + if (newPages == oldPages) + return; + + fw->beginCommand(tr("Change Page Order")); + for(int i=0; i < pageCount; ++i) { + if (newPages.at(i) == m_toolbox->widget(i)) + continue; + qdesigner_internal::MoveToolBoxPageCommand *cmd = new qdesigner_internal::MoveToolBoxPageCommand(fw); + cmd->init(m_toolbox, newPages.at(i), i); + fw->commandHistory()->push(cmd); + } + fw->endCommand(); +} + +void QToolBoxHelper::addPageAfter() +{ + if (QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(m_toolbox)) { + qdesigner_internal::AddToolBoxPageCommand *cmd = new qdesigner_internal::AddToolBoxPageCommand(fw); + cmd->init(m_toolbox, qdesigner_internal::AddToolBoxPageCommand::InsertAfter); + fw->commandHistory()->push(cmd); + } +} + +QPalette::ColorRole QToolBoxHelper::currentItemBackgroundRole() const +{ + const QWidget *w = m_toolbox->widget(0); + if (!w) + return QPalette::Window; + return w->backgroundRole(); +} + +void QToolBoxHelper::setCurrentItemBackgroundRole(QPalette::ColorRole role) +{ + const int count = m_toolbox->count(); + for (int i = 0; i < count; ++i) { + QWidget *w = m_toolbox->widget(i); + w->setBackgroundRole(role); + w->update(); + } +} + +QMenu *QToolBoxHelper::addContextMenuActions(QMenu *popup) const +{ + QMenu *pageMenu = nullptr; + const int count = m_toolbox->count(); + m_actionDeletePage->setEnabled(count > 1); + if (count) { + const QString pageSubMenuLabel = tr("Page %1 of %2").arg(m_toolbox->currentIndex() + 1).arg(count); + pageMenu = popup->addMenu(pageSubMenuLabel); + + pageMenu->addAction(m_actionDeletePage); + // Set up promotion menu for current widget. + if (QWidget *page = m_toolbox->currentWidget ()) { + m_pagePromotionTaskMenu->setWidget(page); + m_pagePromotionTaskMenu->addActions(QDesignerFormWindowInterface::findFormWindow(m_toolbox), + qdesigner_internal::PromotionTaskMenu::SuppressGlobalEdit, + pageMenu); + } + } + QMenu *insertPageMenu = popup->addMenu(tr("Insert Page")); + insertPageMenu->addAction(m_actionInsertPageAfter); + insertPageMenu->addAction(m_actionInsertPage); + if (count > 1) { + popup->addAction(m_actionChangePageOrder); + } + popup->addSeparator(); + return pageMenu; +} + +// -------- QToolBoxWidgetPropertySheet + +static constexpr auto currentItemTextKey = "currentItemText"_L1; +static constexpr auto currentItemNameKey = "currentItemName"_L1; +static constexpr auto currentItemIconKey = "currentItemIcon"_L1; +static constexpr auto currentItemToolTipKey = "currentItemToolTip"_L1; +static constexpr auto tabSpacingKey = "tabSpacing"_L1; + +enum { tabSpacingDefault = -1 }; + +QToolBoxWidgetPropertySheet::QToolBoxWidgetPropertySheet(QToolBox *object, QObject *parent) : + QDesignerPropertySheet(object, parent), + m_toolBox(object) +{ + createFakeProperty(currentItemTextKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + createFakeProperty(currentItemNameKey, QString()); + createFakeProperty(currentItemIconKey, QVariant::fromValue(qdesigner_internal::PropertySheetIconValue())); + if (formWindowBase()) + formWindowBase()->addReloadableProperty(this, indexOf(currentItemIconKey)); + createFakeProperty(currentItemToolTipKey, QVariant::fromValue(qdesigner_internal::PropertySheetStringValue())); + createFakeProperty(tabSpacingKey, QVariant(tabSpacingDefault)); +} + +QToolBoxWidgetPropertySheet::ToolBoxProperty QToolBoxWidgetPropertySheet::toolBoxPropertyFromName(const QString &name) +{ + static const QHash toolBoxPropertyHash = { + {currentItemTextKey, PropertyCurrentItemText}, + {currentItemNameKey, PropertyCurrentItemName}, + {currentItemIconKey, PropertyCurrentItemIcon}, + {currentItemToolTipKey, PropertyCurrentItemToolTip}, + {tabSpacingKey, PropertyTabSpacing} + }; + return toolBoxPropertyHash.value(name, PropertyToolBoxNone); +} + +void QToolBoxWidgetPropertySheet::setProperty(int index, const QVariant &value) +{ + const ToolBoxProperty toolBoxProperty = toolBoxPropertyFromName(propertyName(index)); + // independent of index + switch (toolBoxProperty) { + case PropertyTabSpacing: + m_toolBox->layout()->setSpacing(value.toInt()); + return; + case PropertyToolBoxNone: + QDesignerPropertySheet::setProperty(index, value); + return; + default: + break; + } + // index-dependent + const int currentIndex = m_toolBox->currentIndex(); + QWidget *currentWidget = m_toolBox->currentWidget(); + if (!currentWidget) + return; + + switch (toolBoxProperty) { + case PropertyCurrentItemText: + m_toolBox->setItemText(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].text = qvariant_cast(value); + break; + case PropertyCurrentItemName: + currentWidget->setObjectName(value.toString()); + break; + case PropertyCurrentItemIcon: + m_toolBox->setItemIcon(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].icon = qvariant_cast(value); + break; + case PropertyCurrentItemToolTip: + m_toolBox->setItemToolTip(currentIndex, qvariant_cast(resolvePropertyValue(index, value))); + m_pageToData[currentWidget].tooltip = qvariant_cast(value); + break; + case PropertyTabSpacing: + case PropertyToolBoxNone: + break; + } +} + +bool QToolBoxWidgetPropertySheet::isEnabled(int index) const +{ + switch (toolBoxPropertyFromName(propertyName(index))) { + case PropertyToolBoxNone: // independent of index + case PropertyTabSpacing: + return QDesignerPropertySheet::isEnabled(index); + default: + break; + } + return m_toolBox->currentIndex() != -1; +} + +QVariant QToolBoxWidgetPropertySheet::property(int index) const +{ + const ToolBoxProperty toolBoxProperty = toolBoxPropertyFromName(propertyName(index)); + // independent of index + switch (toolBoxProperty) { + case PropertyTabSpacing: + return m_toolBox->layout()->spacing(); + case PropertyToolBoxNone: + return QDesignerPropertySheet::property(index); + default: + break; + } + // index-dependent + QWidget *currentWidget = m_toolBox->currentWidget(); + if (!currentWidget) { + if (toolBoxProperty == PropertyCurrentItemIcon) + return QVariant::fromValue(qdesigner_internal::PropertySheetIconValue()); + if (toolBoxProperty == PropertyCurrentItemText) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + if (toolBoxProperty == PropertyCurrentItemToolTip) + return QVariant::fromValue(qdesigner_internal::PropertySheetStringValue()); + return QVariant(QString()); + } + + // index-dependent + switch (toolBoxProperty) { + case PropertyCurrentItemText: + return QVariant::fromValue(m_pageToData.value(currentWidget).text); + case PropertyCurrentItemName: + return currentWidget->objectName(); + case PropertyCurrentItemIcon: + return QVariant::fromValue(m_pageToData.value(currentWidget).icon); + case PropertyCurrentItemToolTip: + return QVariant::fromValue(m_pageToData.value(currentWidget).tooltip); + case PropertyTabSpacing: + case PropertyToolBoxNone: + break; + } + return QVariant(); +} + +bool QToolBoxWidgetPropertySheet::reset(int index) +{ + const ToolBoxProperty toolBoxProperty = toolBoxPropertyFromName(propertyName(index)); + // independent of index + switch (toolBoxProperty) { + case PropertyTabSpacing: + setProperty(index, QVariant(tabSpacingDefault)); + return true; + case PropertyToolBoxNone: + return QDesignerPropertySheet::reset(index); + default: + break; + } + // index-dependent + QWidget *currentWidget = m_toolBox->currentWidget(); + if (!currentWidget) + return false; + + // index-dependent + switch (toolBoxProperty) { + case PropertyCurrentItemName: + setProperty(index, QString()); + break; + case PropertyCurrentItemToolTip: + m_pageToData[currentWidget].tooltip = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentItemText: + m_pageToData[currentWidget].text = qdesigner_internal::PropertySheetStringValue(); + setProperty(index, QString()); + break; + case PropertyCurrentItemIcon: + m_pageToData[currentWidget].icon = qdesigner_internal::PropertySheetIconValue(); + setProperty(index, QIcon()); + break; + case PropertyTabSpacing: + case PropertyToolBoxNone: + break; + } + return true; +} + +bool QToolBoxWidgetPropertySheet::checkProperty(const QString &propertyName) +{ + switch (toolBoxPropertyFromName(propertyName)) { + case PropertyCurrentItemText: + case PropertyCurrentItemName: + case PropertyCurrentItemToolTip: + case PropertyCurrentItemIcon: + return false; + default: + break; + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_toolbox_p.h b/src/tools/designer/src/lib/shared/qdesigner_toolbox_p.h new file mode 100644 index 00000000000..8a86e23e6e4 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_toolbox_p.h @@ -0,0 +1,102 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_TOOLBOX_H +#define QDESIGNER_TOOLBOX_H + +#include "shared_global_p.h" +#include "qdesigner_propertysheet_p.h" +#include "qdesigner_utils_p.h" +#include + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + class PromotionTaskMenu; +} + +class QToolBox; + +class QAction; +class QMenu; + +class QDESIGNER_SHARED_EXPORT QToolBoxHelper : public QObject +{ + Q_OBJECT + + explicit QToolBoxHelper(QToolBox *toolbox); +public: + // Install helper on QToolBox + static void install(QToolBox *toolbox); + static QToolBoxHelper *helperOf(const QToolBox *toolbox); + // Convenience to add a menu on a toolbox + static QMenu *addToolBoxContextMenuActions(const QToolBox *toolbox, QMenu *popup); + + QPalette::ColorRole currentItemBackgroundRole() const; + void setCurrentItemBackgroundRole(QPalette::ColorRole role); + + bool eventFilter(QObject *watched, QEvent *event) override; + // Add context menu and return page submenu or 0. + + QMenu *addContextMenuActions(QMenu *popup) const; + +private slots: + void removeCurrentPage(); + void addPage(); + void addPageAfter(); + void changeOrder(); + +private: + QToolBox *m_toolbox; + QAction *m_actionDeletePage; + QAction *m_actionInsertPage; + QAction *m_actionInsertPageAfter; + QAction *m_actionChangePageOrder; + qdesigner_internal::PromotionTaskMenu* m_pagePromotionTaskMenu; +}; + +// PropertySheet to handle the page properties +class QDESIGNER_SHARED_EXPORT QToolBoxWidgetPropertySheet : public QDesignerPropertySheet { +public: + explicit QToolBoxWidgetPropertySheet(QToolBox *object, QObject *parent = nullptr); + + void setProperty(int index, const QVariant &value) override; + QVariant property(int index) const override; + bool reset(int index) override; + bool isEnabled(int index) const override; + + // Check whether the property is to be saved. Returns false for the page + // properties (as the property sheet has no concept of 'stored') + static bool checkProperty(const QString &propertyName); + +private: + enum ToolBoxProperty { PropertyCurrentItemText, PropertyCurrentItemName, PropertyCurrentItemIcon, + PropertyCurrentItemToolTip, PropertyTabSpacing, PropertyToolBoxNone }; + + static ToolBoxProperty toolBoxPropertyFromName(const QString &name); + QToolBox *m_toolBox; + struct PageData + { + qdesigner_internal::PropertySheetStringValue text; + qdesigner_internal::PropertySheetStringValue tooltip; + qdesigner_internal::PropertySheetIconValue icon; + }; + QHash m_pageToData; +}; + +using QToolBoxWidgetPropertySheetFactory = QDesignerPropertySheetFactory; + +QT_END_NAMESPACE + +#endif // QDESIGNER_TOOLBOX_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_utils.cpp b/src/tools/designer/src/lib/shared/qdesigner_utils.cpp new file mode 100644 index 00000000000..67ff4df2db9 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_utils.cpp @@ -0,0 +1,804 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_utils_p.h" +#include "qdesigner_propertycommand_p.h" +#include "abstractformbuilder.h" +#include "formwindowbase_p.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal +{ + // ### FIXME Qt 8: Remove (QTBUG-96005) + QString legacyDataDirectory() + { + return QDir::homePath() + u"/.designer"_s; + } + + QString dataDirectory() + { +#if QT_VERSION >= QT_VERSION_CHECK(7, 0, 0) + return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + + u'/' + QCoreApplication::organizationName() + u"/Designer"_s; +#else + return legacyDataDirectory(); +#endif + } + + + QDESIGNER_SHARED_EXPORT void designerWarning(const QString &message) + { + qWarning("Designer: %s", qPrintable(message)); + } + + void reloadTreeItem(DesignerIconCache *iconCache, QTreeWidgetItem *item) + { + if (!item) + return; + + for (int c = 0; c < item->columnCount(); c++) { + const QVariant v = item->data(c, Qt::DecorationPropertyRole); + if (v.canConvert()) + item->setIcon(c, iconCache->icon(qvariant_cast(v))); + } + } + + void reloadListItem(DesignerIconCache *iconCache, QListWidgetItem *item) + { + if (!item) + return; + + const QVariant v = item->data(Qt::DecorationPropertyRole); + if (v.canConvert()) + item->setIcon(iconCache->icon(qvariant_cast(v))); + } + + void reloadTableItem(DesignerIconCache *iconCache, QTableWidgetItem *item) + { + if (!item) + return; + + const QVariant v = item->data(Qt::DecorationPropertyRole); + if (v.canConvert()) + item->setIcon(iconCache->icon(qvariant_cast(v))); + } + + void reloadIconResources(DesignerIconCache *iconCache, QObject *object) + { + if (QListWidget *listWidget = qobject_cast(object)) { + for (int i = 0; i < listWidget->count(); i++) + reloadListItem(iconCache, listWidget->item(i)); + } else if (QComboBox *comboBox = qobject_cast(object)) { + for (int i = 0; i < comboBox->count(); i++) { + const QVariant v = comboBox->itemData(i, Qt::DecorationPropertyRole); + if (v.canConvert()) { + QIcon icon = iconCache->icon(qvariant_cast(v)); + comboBox->setItemIcon(i, icon); + comboBox->setItemData(i, icon); + } + } + } else if (QTreeWidget *treeWidget = qobject_cast(object)) { + reloadTreeItem(iconCache, treeWidget->headerItem()); + QQueue itemsQueue; + for (int i = 0; i < treeWidget->topLevelItemCount(); i++) + itemsQueue.enqueue(treeWidget->topLevelItem(i)); + while (!itemsQueue.isEmpty()) { + QTreeWidgetItem *item = itemsQueue.dequeue(); + for (int i = 0; i < item->childCount(); i++) + itemsQueue.enqueue(item->child(i)); + reloadTreeItem(iconCache, item); + } + } else if (QTableWidget *tableWidget = qobject_cast(object)) { + const int columnCount = tableWidget->columnCount(); + const int rowCount = tableWidget->rowCount(); + for (int c = 0; c < columnCount; c++) + reloadTableItem(iconCache, tableWidget->horizontalHeaderItem(c)); + for (int r = 0; r < rowCount; r++) + reloadTableItem(iconCache, tableWidget->verticalHeaderItem(r)); + for (int c = 0; c < columnCount; c++) + for (int r = 0; r < rowCount; r++) + reloadTableItem(iconCache, tableWidget->item(r, c)); + } + } + + // ------------- DesignerMetaEnum + DesignerMetaEnum::DesignerMetaEnum(const QString &name, const QString &scope, const QString &separator) : + MetaEnum(name, scope, separator) + { + } + + + QString DesignerMetaEnum::toString(int value, SerializationMode sm, bool *ok) const + { + // find value + bool valueOk; + const QString item = valueToKey(value, &valueOk); + if (ok) + *ok = valueOk; + + if (!valueOk || sm == NameOnly) + return item; + + QString qualifiedItem; + appendQualifiedName(item, qualifiedItem); + return qualifiedItem; + } + + QString DesignerMetaEnum::messageToStringFailed(int value) const + { + return QCoreApplication::translate("DesignerMetaEnum", + "%1 is not a valid enumeration value of '%2'.") + .arg(value).arg(enumName()); + } + + QString DesignerMetaEnum::messageParseFailed(const QString &s) const + { + return QCoreApplication::translate("DesignerMetaEnum", + "'%1' could not be converted to an enumeration value of type '%2'.") + .arg(s, enumName()); + } + // -------------- DesignerMetaFlags + DesignerMetaFlags::DesignerMetaFlags(const QString &enumName, const QString &scope, + const QString &separator) : + MetaEnum(enumName, scope, separator) + { + } + + QStringList DesignerMetaFlags::flags(int ivalue) const + { + QStringList rc; + const uint v = static_cast(ivalue); + for (auto it = keyToValueMap().begin(), end = keyToValueMap().end(); it != end; ++it) { + const uint itemValue = it->second; + // Check for equality first as flag values can be 0 or -1, too. Takes preference over a bitwise flag + if (v == itemValue) { + rc.clear(); + rc.push_back(it->first); + return rc; + } + // Do not add 0-flags (None-flags) + if (itemValue) + if ((v & itemValue) == itemValue) + rc.push_back(it->first); + } + return rc; + } + + + QString DesignerMetaFlags::toString(int value, SerializationMode sm) const + { + const QStringList flagIds = flags(value); + if (flagIds.isEmpty()) + return QString(); + + QString rc; + for (const auto &id : flagIds) { + if (!rc.isEmpty()) + rc += u'|'; + if (sm == FullyQualified) + appendQualifiedName(id, rc); + else + rc += id; + } + return rc; + } + + + int DesignerMetaFlags::parseFlags(const QString &s, bool *ok) const + { + if (s.isEmpty()) { + if (ok) + *ok = true; + return 0; + } + uint flags = 0; + bool valueOk = true; + const auto keys = QStringView{s}.split(u'|'); + for (const auto &key : keys) { + const uint flagValue = keyToValue(key, &valueOk); + if (!valueOk) { + flags = 0; + break; + } + flags |= flagValue; + } + if (ok) + *ok = valueOk; + return static_cast(flags); + } + + QString DesignerMetaFlags::messageParseFailed(const QString &s) const + { + return QCoreApplication::translate("DesignerMetaFlags", + "'%1' could not be converted to a flag value of type '%2'.") + .arg(s, enumName()); + } + + // ---------- PropertySheetEnumValue + + PropertySheetEnumValue::PropertySheetEnumValue(int v, const DesignerMetaEnum &me) : + value(v), + metaEnum(me) + { + } + + PropertySheetEnumValue::PropertySheetEnumValue() = default; + + // ---------------- PropertySheetFlagValue + PropertySheetFlagValue::PropertySheetFlagValue(int v, const DesignerMetaFlags &mf) : + value(v), + metaFlags(mf) + { + } + + PropertySheetFlagValue::PropertySheetFlagValue() = default; + + // ---------------- PropertySheetPixmapValue + PropertySheetPixmapValue::PropertySheetPixmapValue(const QString &path) : m_path(path) + { + } + + PropertySheetPixmapValue::PropertySheetPixmapValue() = default; + + PropertySheetPixmapValue::PixmapSource PropertySheetPixmapValue::getPixmapSource(QDesignerFormEditorInterface *core, const QString & path) + { + if (const QDesignerLanguageExtension *lang = qt_extension(core->extensionManager(), core)) + return lang->isLanguageResource(path) ? LanguageResourcePixmap : FilePixmap; + return path.startsWith(u':') ? ResourcePixmap : FilePixmap; + } + + QString PropertySheetPixmapValue::path() const + { + return m_path; + } + + void PropertySheetPixmapValue::setPath(const QString &path) + { + if (m_path == path) + return; + m_path = path; + } + + // ---------- PropertySheetIconValue + + class PropertySheetIconValueData : public QSharedData { + public: + PropertySheetIconValue::ModeStateToPixmapMap m_paths; + QString m_theme; + int m_themeEnum = -1; + }; + + PropertySheetIconValue::PropertySheetIconValue(const PropertySheetPixmapValue &pixmap) : + m_data(new PropertySheetIconValueData) + { + setPixmap(QIcon::Normal, QIcon::Off, pixmap); + } + + PropertySheetIconValue::PropertySheetIconValue() : + m_data(new PropertySheetIconValueData) + { + } + + PropertySheetIconValue::~PropertySheetIconValue() = default; + + PropertySheetIconValue::PropertySheetIconValue(const PropertySheetIconValue &rhs) noexcept = default; + PropertySheetIconValue &PropertySheetIconValue::operator=(const PropertySheetIconValue &rhs) = default; + + PropertySheetIconValue::PropertySheetIconValue(PropertySheetIconValue &&) noexcept = default; + PropertySheetIconValue &PropertySheetIconValue::operator=(PropertySheetIconValue &&) noexcept = default; + +} // namespace qdesigner_internal + +namespace qdesigner_internal { + + size_t qHash(const PropertySheetIconValue &p, size_t seed) noexcept + { + // qHash for paths making use of the existing QPair hash functions. + const auto *d = p.m_data.constData(); + return qHashMulti(seed, d->m_paths, d->m_themeEnum, d->m_theme); + } + + bool comparesEqual(const PropertySheetIconValue &lhs, + const PropertySheetIconValue &rhs) noexcept + { + const auto *lhsd = lhs.m_data.constData(); + const auto *rhsd = rhs.m_data.constData(); + return lhsd == rhsd + || (lhsd->m_themeEnum == rhsd->m_themeEnum + && lhsd->m_theme == rhsd->m_theme && lhsd->m_paths == rhsd->m_paths); + } + + bool PropertySheetIconValue::isEmpty() const + { + return m_data->m_themeEnum == -1 && m_data->m_theme.isEmpty() + && m_data->m_paths.isEmpty(); + } + + QString PropertySheetIconValue::theme() const + { + return m_data->m_theme; + } + + void PropertySheetIconValue::setTheme(const QString &t) + { + m_data->m_theme = t; + } + + int PropertySheetIconValue::themeEnum() const + { + return m_data->m_themeEnum; + } + + void PropertySheetIconValue::setThemeEnum(int e) + { + m_data->m_themeEnum = e; + } + + PropertySheetPixmapValue PropertySheetIconValue::pixmap(QIcon::Mode mode, QIcon::State state) const + { + const ModeStateKey pair{mode, state}; + return m_data->m_paths.value(pair); + } + + void PropertySheetIconValue::setPixmap(QIcon::Mode mode, QIcon::State state, const PropertySheetPixmapValue &pixmap) + { + const ModeStateKey pair{mode, state}; + if (pixmap.path().isEmpty()) + m_data->m_paths.remove(pair); + else + m_data->m_paths.insert(pair, pixmap); + } + + QPixmap DesignerPixmapCache::pixmap(const PropertySheetPixmapValue &value) const + { + const auto it = m_cache.constFind(value); + if (it != m_cache.constEnd()) + return it.value(); + + QPixmap pix = QPixmap(value.path()); + m_cache.insert(value, pix); + return pix; + } + + void DesignerPixmapCache::clear() + { + m_cache.clear(); + } + + DesignerPixmapCache::DesignerPixmapCache(QObject *parent) + : QObject(parent) + { + } + + QIcon DesignerIconCache::icon(const PropertySheetIconValue &value) const + { + const auto it = m_cache.constFind(value); + if (it != m_cache.constEnd()) + return it.value(); + + // Match on the theme first if it is available. + if (value.themeEnum() != -1) { + const QIcon themeIcon = QIcon::fromTheme(static_cast(value.themeEnum())); + m_cache.insert(value, themeIcon); + return themeIcon; + } + if (!value.theme().isEmpty()) { + const QString theme = value.theme(); + if (QIcon::hasThemeIcon(theme)) { + const QIcon themeIcon = QIcon::fromTheme(theme); + m_cache.insert(value, themeIcon); + return themeIcon; + } + } + + QIcon icon; + const PropertySheetIconValue::ModeStateToPixmapMap &paths = value.paths(); + for (auto it = paths.constBegin(), cend = paths.constEnd(); it != cend; ++it) { + const auto pair = it.key(); + icon.addFile(it.value().path(), QSize(), pair.first, pair.second); + } + m_cache.insert(value, icon); + return icon; + } + + void DesignerIconCache::clear() + { + m_cache.clear(); + } + + DesignerIconCache::DesignerIconCache(DesignerPixmapCache *pixmapCache, QObject *parent) + : QObject(parent), + m_pixmapCache(pixmapCache) + { + + } + + PropertySheetTranslatableData::PropertySheetTranslatableData(bool translatable, const QString &disambiguation, const QString &comment) : + m_translatable(translatable), m_disambiguation(disambiguation), m_comment(comment) { } + + PropertySheetStringValue::PropertySheetStringValue(const QString &value, + bool translatable, const QString &disambiguation, const QString &comment) : + PropertySheetTranslatableData(translatable, disambiguation, comment), m_value(value) {} + + QString PropertySheetStringValue::value() const + { + return m_value; + } + + void PropertySheetStringValue::setValue(const QString &value) + { + m_value = value; + } + + PropertySheetStringListValue::PropertySheetStringListValue(const QStringList &value, + bool translatable, + const QString &disambiguation, + const QString &comment) : + PropertySheetTranslatableData(translatable, disambiguation, comment), m_value(value) + { + } + + QStringList PropertySheetStringListValue::value() const + { + return m_value; + } + + void PropertySheetStringListValue::setValue(const QStringList &value) + { + m_value = value; + } + + QStringList m_value; + + + PropertySheetKeySequenceValue::PropertySheetKeySequenceValue(const QKeySequence &value, + bool translatable, const QString &disambiguation, const QString &comment) + : PropertySheetTranslatableData(translatable, disambiguation, comment), + m_value(value), m_standardKey(QKeySequence::UnknownKey) {} + + PropertySheetKeySequenceValue::PropertySheetKeySequenceValue(const QKeySequence::StandardKey &standardKey, + bool translatable, const QString &disambiguation, const QString &comment) + : PropertySheetTranslatableData(translatable, disambiguation, comment), + m_value(QKeySequence(standardKey)), m_standardKey(standardKey) {} + + QKeySequence PropertySheetKeySequenceValue::value() const + { + return m_value; + } + + void PropertySheetKeySequenceValue::setValue(const QKeySequence &value) + { + m_value = value; + m_standardKey = QKeySequence::UnknownKey; + } + + QKeySequence::StandardKey PropertySheetKeySequenceValue::standardKey() const + { + return m_standardKey; + } + + void PropertySheetKeySequenceValue::setStandardKey(const QKeySequence::StandardKey &standardKey) + { + m_value = QKeySequence(standardKey); + m_standardKey = standardKey; + } + + bool PropertySheetKeySequenceValue::isStandardKey() const + { + return m_standardKey != QKeySequence::UnknownKey; + } + + /* IconSubPropertyMask: Assign each icon sub-property (pixmaps for the + * various states/modes and the theme) a flag bit (see QFont) so that they + * can be handled individually when assigning property values to + * multiselections in the set-property-commands (that is, do not clobber + * other subproperties when assigning just one). + * Provide back-and-forth mapping functions for the icon states. */ + + enum IconSubPropertyMask { + NormalOffIconMask = 0x01, + NormalOnIconMask = 0x02, + DisabledOffIconMask = 0x04, + DisabledOnIconMask = 0x08, + ActiveOffIconMask = 0x10, + ActiveOnIconMask = 0x20, + SelectedOffIconMask = 0x40, + SelectedOnIconMask = 0x80, + ThemeIconMask = 0x10000, + ThemeEnumIconMask = 0x20000 + }; + + static inline uint iconStateToSubPropertyFlag(QIcon::Mode mode, QIcon::State state) + { + switch (mode) { + case QIcon::Disabled: + return state == QIcon::On ? DisabledOnIconMask : DisabledOffIconMask; + case QIcon::Active: + return state == QIcon::On ? ActiveOnIconMask : ActiveOffIconMask; + case QIcon::Selected: + return state == QIcon::On ? SelectedOnIconMask : SelectedOffIconMask; + case QIcon::Normal: + break; + } + return state == QIcon::On ? NormalOnIconMask : NormalOffIconMask; + } + + static inline std::pair subPropertyFlagToIconModeState(unsigned flag) + { + switch (flag) { + case NormalOnIconMask: + return {QIcon::Normal, QIcon::On}; + case DisabledOffIconMask: + return {QIcon::Disabled, QIcon::Off}; + case DisabledOnIconMask: + return {QIcon::Disabled, QIcon::On}; + case ActiveOffIconMask: + return {QIcon::Active, QIcon::Off}; + case ActiveOnIconMask: + return {QIcon::Active, QIcon::On}; + case SelectedOffIconMask: + return {QIcon::Selected, QIcon::Off}; + case SelectedOnIconMask: + return {QIcon::Selected, QIcon::On}; + case NormalOffIconMask: + default: + break; + } + return {QIcon::Normal, QIcon::Off}; + } + + uint PropertySheetIconValue::mask() const + { + uint flags = 0; + for (auto it = m_data->m_paths.constBegin(), cend = m_data->m_paths.constEnd(); it != cend; ++it) + flags |= iconStateToSubPropertyFlag(it.key().first, it.key().second); + if (!m_data->m_theme.isEmpty()) + flags |= ThemeIconMask; + if (m_data->m_themeEnum != -1) + flags |= ThemeEnumIconMask; + return flags; + } + + uint PropertySheetIconValue::compare(const PropertySheetIconValue &other) const + { + uint diffMask = mask() | other.mask(); + for (int i = 0; i < 8; i++) { + const uint flag = 1 << i; + if (diffMask & flag) { // if state is set in both icons, compare the values + const auto state = subPropertyFlagToIconModeState(flag); + if (pixmap(state.first, state.second) == other.pixmap(state.first, state.second)) + diffMask &= ~flag; + } + } + if ((diffMask & ThemeIconMask) && theme() == other.theme()) + diffMask &= ~ThemeIconMask; + if ((diffMask & ThemeEnumIconMask) && themeEnum() == other.themeEnum()) + diffMask &= ~ThemeEnumIconMask; + + return diffMask; + } + + PropertySheetIconValue PropertySheetIconValue::themed() const + { + PropertySheetIconValue rc(*this); + rc.m_data->m_paths.clear(); + return rc; + } + + PropertySheetIconValue PropertySheetIconValue::unthemed() const + { + PropertySheetIconValue rc(*this); + rc.m_data->m_theme.clear(); + rc.m_data->m_themeEnum = -1; + return rc; + } + + void PropertySheetIconValue::assign(const PropertySheetIconValue &other, uint mask) + { + for (int i = 0; i < 8; i++) { + uint flag = 1 << i; + if (mask & flag) { + const ModeStateKey state = subPropertyFlagToIconModeState(flag); + setPixmap(state.first, state.second, other.pixmap(state.first, state.second)); + } + } + if (mask & ThemeIconMask) + setTheme(other.theme()); + if (mask & ThemeEnumIconMask) + setThemeEnum(other.themeEnum()); + } + + const PropertySheetIconValue::ModeStateToPixmapMap &PropertySheetIconValue::paths() const + { + return m_data->m_paths; + } + + QDESIGNER_SHARED_EXPORT QDebug operator<<(QDebug debug, const PropertySheetIconValue &p) + { + QDebugStateSaver saver(debug); + debug.nospace(); + debug.noquote(); + debug << "PropertySheetIconValue(mask=0x" << Qt::hex << p.mask() << Qt::dec << ", "; + if (p.themeEnum() != -1) + debug << "theme=" << p.themeEnum() << ", "; + if (!p.theme().isEmpty()) + debug << "XDG theme=\"" << p.theme() << "\", "; + + const PropertySheetIconValue::ModeStateToPixmapMap &paths = p.paths(); + for (auto it = paths.constBegin(), cend = paths.constEnd(); it != cend; ++it) { + debug << " mode=" << it.key().first << ",state=" << it.key().second + << ", \"" << it.value().path() << '"'; + } + debug << ')'; + return debug; + } + + QDESIGNER_SHARED_EXPORT QDesignerFormWindowCommand *createTextPropertyCommand(const QString &propertyName, const QString &text, QObject *object, QDesignerFormWindowInterface *fw) + { + if (text.isEmpty()) { + ResetPropertyCommand *cmd = new ResetPropertyCommand(fw); + cmd->init(object, propertyName); + return cmd; + } + SetPropertyCommand *cmd = new SetPropertyCommand(fw); + cmd->init(object, propertyName, text); + return cmd; + } + + QDESIGNER_SHARED_EXPORT QAction *preferredEditAction(QDesignerFormEditorInterface *core, QWidget *managedWidget) + { + QAction *action = nullptr; + if (const QDesignerTaskMenuExtension *taskMenu = qt_extension(core->extensionManager(), managedWidget)) { + action = taskMenu->preferredEditAction(); + if (!action) { + const auto actions = taskMenu->taskActions(); + if (!actions.isEmpty()) + action = actions.first(); + } + } + if (!action) { + if (const auto *taskMenu = qobject_cast( + core->extensionManager()->extension(managedWidget, u"QDesignerInternalTaskMenuExtension"_s))) { + action = taskMenu->preferredEditAction(); + if (!action) { + const auto actions = taskMenu->taskActions(); + if (!actions.isEmpty()) + action = actions.first(); + } + } + } + return action; + } + + QDESIGNER_SHARED_EXPORT bool runUIC(const QString &fileName, UicLanguage language, + QByteArray& ba, QString &errorMessage) + { + QProcess uic; + QStringList arguments; + static constexpr auto uicBinary = + QOperatingSystemVersion::currentType() != QOperatingSystemVersion::Windows + ? "/uic"_L1 : "/uic.exe"_L1; + QString binary = QLibraryInfo::path(QLibraryInfo::LibraryExecutablesPath) + uicBinary; + // In a PySide6 installation, there is no libexec directory; uic.exe is + // in the main wheel directory next to designer.exe. + if (!QFileInfo::exists(binary)) + binary = QCoreApplication::applicationDirPath() + uicBinary; + if (!QFileInfo::exists(binary)) { + errorMessage = QApplication::translate("Designer", "%1 does not exist."). + arg(QDir::toNativeSeparators(binary)); + return false; + } + + switch (language) { + case UicLanguage::Cpp: + break; + case UicLanguage::Python: + arguments << u"-g"_s << u"python"_s; + break; + } + arguments << fileName; + + uic.start(binary, arguments); + if (!uic.waitForStarted()) { + errorMessage = QApplication::translate("Designer", "Unable to launch %1: %2"). + arg(QDir::toNativeSeparators(binary), uic.errorString()); + return false; + } + if (!uic.waitForFinished()) { + errorMessage = QApplication::translate("Designer", "%1 timed out.").arg(binary); + return false; + } + if (uic.exitCode()) { + errorMessage = QString::fromLatin1(uic.readAllStandardError()); + return false; + } + ba = uic.readAllStandardOutput(); + return true; + } + + QDESIGNER_SHARED_EXPORT QString qtify(const QString &name) + { + QString qname = name; + + Q_ASSERT(qname.isEmpty() == false); + + + if (qname.size() > 1 && qname.at(1).isUpper()) { + const QChar first = qname.at(0); + if (first == u'Q' || first == u'K') + qname.remove(0, 1); + } + + const qsizetype len = qname.size(); + for (qsizetype i = 0; i < len && qname.at(i).isUpper(); ++i) + qname[i] = qname.at(i).toLower(); + + return qname; + } + + // --------------- UpdateBlocker + UpdateBlocker::UpdateBlocker(QWidget *w) : + m_widget(w), + m_enabled(w->updatesEnabled() && w->isVisible()) + { + if (m_enabled) + m_widget->setUpdatesEnabled(false); + } + + UpdateBlocker::~UpdateBlocker() + { + if (m_enabled) + m_widget->setUpdatesEnabled(true); + } + +// from qpalette.cpp +quint64 paletteResolveMask(QPalette::ColorGroup colorGroup, + QPalette::ColorRole colorRole) +{ + if (colorRole == QPalette::Accent) + colorRole = QPalette::NoRole; // See qtbase/17c589df94a2245ee92d45839c2cba73566d7310 + const auto offset = quint64(QPalette::NColorRoles - 1) * quint64(colorGroup); + const auto bitPos = quint64(colorRole) + offset; + return 1ull << bitPos; +} + +quint64 paletteResolveMask(QPalette::ColorRole colorRole) +{ + return paletteResolveMask(QPalette::Active, colorRole) + | paletteResolveMask(QPalette::Inactive, colorRole) + | paletteResolveMask(QPalette::Disabled, colorRole); +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_utils_p.h b/src/tools/designer/src/lib/shared/qdesigner_utils_p.h new file mode 100644 index 00000000000..223a12337a6 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_utils_p.h @@ -0,0 +1,549 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_UTILS_H +#define QDESIGNER_UTILS_H + +#include "shared_global_p.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QDebug; + +namespace qdesigner_internal { +class QDesignerFormWindowCommand; +class DesignerIconCache; +class FormWindowBase; + + +QDESIGNER_SHARED_EXPORT QString dataDirectory(); + +QDESIGNER_SHARED_EXPORT QString legacyDataDirectory(); + +QDESIGNER_SHARED_EXPORT void designerWarning(const QString &message); + +QDESIGNER_SHARED_EXPORT void reloadIconResources(DesignerIconCache *iconCache, QObject *object); + +/* Flag/Enumeration helpers for the property sheet: Enumeration or flag values are returned by the property sheet + * as a pair of meta type and integer value. + * The meta type carries all the information required for the property editor and serialization + * by the form builders (names, etc). + * Note that the property editor uses unqualified names ("Cancel") while the form builder serialization (uic) + * requires the whole string + * ("QDialogButtonBox::Cancel" or "org.qt-project.qt.gui.QDialogButtonBox.StandardButton.Cancel").*/ + +/* --------- MetaEnum: Base class representing a QMetaEnum with lookup functions + * in both ways. Template of int type since unsigned is more suitable for flags. + * The keyToValue() is ignorant of scopes, it can handle fully qualified or unqualified names. */ + +template +class MetaEnum +{ +public: + using KeyToValueMap = std::map>; + + MetaEnum(const QString &enumName, const QString &scope, const QString &separator); + MetaEnum() = default; + void addKey(IntType value, const QString &name); + + QString valueToKey(IntType value, bool *ok = nullptr) const; + // Ignorant of scopes. + IntType keyToValue(QStringView key, bool *ok = nullptr) const; + + const QString &enumName() const { return m_enumName; } + const QString &scope() const { return m_scope; } + const QString &separator() const { return m_separator; } + + const QStringList &keys() const { return m_keys; } + const KeyToValueMap &keyToValueMap() const { return m_keyToValueMap; } + +protected: + void appendQualifiedName(const QString &key, QString &target) const; + +private: + QString m_enumName; + QString m_scope; + QString m_separator; + KeyToValueMap m_keyToValueMap; + QStringList m_keys; +}; + +template +MetaEnum::MetaEnum(const QString &enumName, const QString &scope, const QString &separator) : + m_enumName(enumName), + m_scope(scope), + m_separator(separator) +{ +} + +template +void MetaEnum::addKey(IntType value, const QString &name) +{ + m_keyToValueMap.insert({name, value}); + m_keys.append(name); +} + +template +QString MetaEnum::valueToKey(IntType value, bool *ok) const +{ + QString rc; + for (auto it = m_keyToValueMap.begin(), end = m_keyToValueMap.end(); it != end; ++it) { + if (it->second == value) { + rc = it->first; + break; + } + } + if (ok) + *ok = !rc.isEmpty(); + return rc; +} + +template +IntType MetaEnum::keyToValue(QStringView key, bool *ok) const +{ + const auto lastSep = key.lastIndexOf(m_separator); + if (lastSep != -1) + key = key.sliced(lastSep + m_separator.size()); + const auto it = m_keyToValueMap.find(key); + const bool found = it != m_keyToValueMap.end(); + if (ok) + *ok = found; + return found ? it->second : IntType(0); +} + +template +void MetaEnum::appendQualifiedName(const QString &key, QString &target) const +{ + if (!m_scope.isEmpty()) { + target += m_scope; + target += m_separator; + } + target += m_enumName + m_separator + key; +} + +// -------------- DesignerMetaEnum: Meta type for enumerations + +class QDESIGNER_SHARED_EXPORT DesignerMetaEnum : public MetaEnum +{ +public: + DesignerMetaEnum(const QString &name, const QString &scope, const QString &separator); + DesignerMetaEnum() = default; + + enum SerializationMode { FullyQualified, NameOnly }; + QString toString(int value, SerializationMode sm, bool *ok = nullptr) const; + + QString messageToStringFailed(int value) const; + QString messageParseFailed(const QString &s) const; + + // parse a string (ignorant of scopes) + int parseEnum(const QString &s, bool *ok = nullptr) const { return keyToValue(s, ok); } +}; + +// -------------- DesignerMetaFlags: Meta type for flags. +// Note that while the handling of flags is done using unsigned integers, the actual values returned +// by the property system are integers. + +class QDESIGNER_SHARED_EXPORT DesignerMetaFlags : public MetaEnum +{ +public: + explicit DesignerMetaFlags(const QString &enumName, const QString &scope, + const QString &separator); + DesignerMetaFlags() = default; + + enum SerializationMode { FullyQualified, NameOnly }; + QString toString(int value, SerializationMode sm) const; + QStringList flags(int value) const; + + QString messageParseFailed(const QString &s) const; + // parse a string (ignorant of scopes) + int parseFlags(const QString &s, bool *ok = nullptr) const; +}; + +// -------------- EnumValue: Returned by the property sheet for enumerations + +struct QDESIGNER_SHARED_EXPORT PropertySheetEnumValue +{ + PropertySheetEnumValue(int v, const DesignerMetaEnum &me); + PropertySheetEnumValue(); + + int value{0}; + DesignerMetaEnum metaEnum; +}; + +// -------------- FlagValue: Returned by the property sheet for flags + +struct QDESIGNER_SHARED_EXPORT PropertySheetFlagValue +{ + PropertySheetFlagValue(int v, const DesignerMetaFlags &mf); + PropertySheetFlagValue(); + + int value{0}; + DesignerMetaFlags metaFlags; +}; + +// -------------- PixmapValue: Returned by the property sheet for pixmaps +class QDESIGNER_SHARED_EXPORT PropertySheetPixmapValue +{ +public: + PropertySheetPixmapValue(const QString &path); + PropertySheetPixmapValue(); + + // Check where a pixmap comes from + enum PixmapSource { LanguageResourcePixmap , ResourcePixmap, FilePixmap }; + static PixmapSource getPixmapSource(QDesignerFormEditorInterface *core, const QString & path); + + PixmapSource pixmapSource(QDesignerFormEditorInterface *core) const { return getPixmapSource(core, m_path); } + + QString path() const; + void setPath(const QString &path); // passing the empty path resets the pixmap + +private: + friend size_t qHash(const PropertySheetPixmapValue &p, size_t seed = 0) noexcept + { + return qHash(p.m_path, seed); + } + friend bool comparesEqual(const PropertySheetPixmapValue &lhs, + const PropertySheetPixmapValue &rhs) noexcept + { + return lhs.m_path == rhs.m_path; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetPixmapValue) + + QString m_path; +}; + +// -------------- IconValue: Returned by the property sheet for icons + +class PropertySheetIconValueData; + +class QDESIGNER_SHARED_EXPORT PropertySheetIconValue +{ + public: + explicit PropertySheetIconValue(const PropertySheetPixmapValue &pixmap); + PropertySheetIconValue(); + ~PropertySheetIconValue(); + PropertySheetIconValue(const PropertySheetIconValue &) noexcept; + PropertySheetIconValue &operator=(const PropertySheetIconValue &); + PropertySheetIconValue(PropertySheetIconValue &&) noexcept; + PropertySheetIconValue &operator=(PropertySheetIconValue &&) noexcept; + + bool isEmpty() const; + + QString theme() const; + void setTheme(const QString &); + + int themeEnum() const; + void setThemeEnum(int e); + + PropertySheetPixmapValue pixmap(QIcon::Mode mode, QIcon::State state) const; + void setPixmap(QIcon::Mode mode, QIcon::State state, const PropertySheetPixmapValue &path); // passing the empty path resets the pixmap + + uint mask() const; + uint compare(const PropertySheetIconValue &other) const; + void assign(const PropertySheetIconValue &other, uint mask); + + // Convenience accessors to get themed/unthemed icons. + PropertySheetIconValue themed() const; + PropertySheetIconValue unthemed() const; + + using ModeStateKey = std::pair; + using ModeStateToPixmapMap = QMap; + + const ModeStateToPixmapMap &paths() const; + +private: + friend QDESIGNER_SHARED_EXPORT + size_t qHash(const PropertySheetIconValue &p, size_t seed) noexcept; + friend size_t qHash(const PropertySheetIconValue &p) noexcept + { return qHash(p, 0); } + friend QDESIGNER_SHARED_EXPORT + bool comparesEqual(const PropertySheetIconValue &lhs, + const PropertySheetIconValue &rhs) noexcept; + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetIconValue) + + QSharedDataPointer m_data; +}; + +QDESIGNER_SHARED_EXPORT QDebug operator<<(QDebug, const PropertySheetIconValue &); + +class QDESIGNER_SHARED_EXPORT DesignerPixmapCache : public QObject +{ + Q_OBJECT +public: + DesignerPixmapCache(QObject *parent = nullptr); + QPixmap pixmap(const PropertySheetPixmapValue &value) const; + void clear(); +signals: + void reloaded(); +private: + mutable QHash m_cache; + friend class FormWindowBase; +}; + +class QDESIGNER_SHARED_EXPORT DesignerIconCache : public QObject +{ + Q_OBJECT +public: + explicit DesignerIconCache(DesignerPixmapCache *pixmapCache, QObject *parent = nullptr); + QIcon icon(const PropertySheetIconValue &value) const; + void clear(); +signals: + void reloaded(); +private: + mutable QHash m_cache; + DesignerPixmapCache *m_pixmapCache; + friend class FormWindowBase; +}; + +// -------------- PropertySheetTranslatableData: Base class for translatable properties. +class QDESIGNER_SHARED_EXPORT PropertySheetTranslatableData +{ +protected: + PropertySheetTranslatableData(bool translatable = true, + const QString &disambiguation = QString(), + const QString &comment = QString()); + +public: + bool translatable() const { return m_translatable; } + void setTranslatable(bool translatable) { m_translatable = translatable; } + QString disambiguation() const { return m_disambiguation; } + void setDisambiguation(const QString &d) { m_disambiguation = d; } + QString comment() const { return m_comment; } + void setComment(const QString &comment) { m_comment = comment; } + QString id() const { return m_id; } + void setId(const QString &id) { m_id = id; } + +private: + friend bool comparesEqual(const PropertySheetTranslatableData &lhs, + const PropertySheetTranslatableData &rhs) noexcept + { + return lhs.m_translatable == rhs.m_translatable + && lhs.m_disambiguation == rhs.m_disambiguation + && lhs.m_comment == rhs.m_comment + && lhs.m_id == rhs.m_id; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetTranslatableData) + + bool m_translatable; + QString m_disambiguation; + QString m_comment; + QString m_id; +}; + +// -------------- StringValue: Returned by the property sheet for strings +class QDESIGNER_SHARED_EXPORT PropertySheetStringValue : public PropertySheetTranslatableData +{ +public: + PropertySheetStringValue(const QString &value = QString(), bool translatable = true, + const QString &disambiguation = QString(), const QString &comment = QString()); + + QString value() const; + void setValue(const QString &value); + +private: + friend bool comparesEqual(const PropertySheetStringValue &lhs, + const PropertySheetStringValue &rhs) noexcept + { + const PropertySheetTranslatableData &upLhs = lhs; + const PropertySheetTranslatableData &upRhs = rhs; + return lhs.m_value == rhs.m_value && upLhs == upRhs; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetStringValue) + + QString m_value; +}; + +// -------------- StringValue: Returned by the property sheet for string lists +class QDESIGNER_SHARED_EXPORT PropertySheetStringListValue : public PropertySheetTranslatableData +{ +public: + PropertySheetStringListValue(const QStringList &value = QStringList(), + bool translatable = true, + const QString &disambiguation = QString(), + const QString &comment = QString()); + + QStringList value() const; + void setValue(const QStringList &value); + +private: + friend bool comparesEqual(const PropertySheetStringListValue &lhs, + const PropertySheetStringListValue &rhs) noexcept + { + const PropertySheetTranslatableData &upLhs = lhs; + const PropertySheetTranslatableData &upRhs = rhs; + return lhs.m_value == rhs.m_value && upLhs == upRhs; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetStringListValue) + + QStringList m_value; +}; + +// -------------- StringValue: Returned by the property sheet for strings +class QDESIGNER_SHARED_EXPORT PropertySheetKeySequenceValue : public PropertySheetTranslatableData +{ +public: + PropertySheetKeySequenceValue(const QKeySequence &value = QKeySequence(), + bool translatable = true, + const QString &disambiguation = QString(), + const QString &comment = QString()); + PropertySheetKeySequenceValue(const QKeySequence::StandardKey &standardKey, + bool translatable = true, + const QString &disambiguation = QString(), + const QString &comment = QString()); + + QKeySequence value() const; + void setValue(const QKeySequence &value); + QKeySequence::StandardKey standardKey() const; + void setStandardKey(const QKeySequence::StandardKey &standardKey); + bool isStandardKey() const; + +private: + friend bool comparesEqual(const PropertySheetKeySequenceValue &lhs, + const PropertySheetKeySequenceValue &rhs) noexcept + { + const PropertySheetTranslatableData &upLhs = lhs; + const PropertySheetTranslatableData &upRhs = rhs; + return lhs.m_value == rhs.m_value && lhs.m_standardKey == rhs.m_standardKey + && upLhs == upRhs; + } + Q_DECLARE_EQUALITY_COMPARABLE(PropertySheetKeySequenceValue) + + QKeySequence m_value; + QKeySequence::StandardKey m_standardKey; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + + +// NOTE: Do not move this code, needed for GCC 3.3 +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetEnumValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetFlagValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetPixmapValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetIconValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetStringValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetStringListValue) +Q_DECLARE_METATYPE(qdesigner_internal::PropertySheetKeySequenceValue) + + +QT_BEGIN_NAMESPACE + +namespace qdesigner_internal { + + +// Create a command to change a text property (that is, create a reset property command if the text is empty) +QDESIGNER_SHARED_EXPORT QDesignerFormWindowCommand *createTextPropertyCommand(const QString &propertyName, const QString &text, QObject *object, QDesignerFormWindowInterface *fw); + +// Returns preferred task menu action for managed widget +QDESIGNER_SHARED_EXPORT QAction *preferredEditAction(QDesignerFormEditorInterface *core, QWidget *managedWidget); + +enum class UicLanguage +{ + Cpp, + Python, +}; + +// Convenience to run UIC +QDESIGNER_SHARED_EXPORT bool runUIC(const QString &fileName, UicLanguage language, + QByteArray& ba, QString &errorMessage); + +// Find a suitable variable name for a class. +QDESIGNER_SHARED_EXPORT QString qtify(const QString &name); + +/* UpdateBlocker: Blocks the updates of the widget passed on while in scope. + * Does nothing if the incoming widget already has updatesEnabled==false + * which is important to avoid side-effects when putting it into QStackedLayout. */ + +class QDESIGNER_SHARED_EXPORT UpdateBlocker { + Q_DISABLE_COPY_MOVE(UpdateBlocker) + +public: + UpdateBlocker(QWidget *w); + ~UpdateBlocker(); + +private: + QWidget *m_widget; + const bool m_enabled; +}; + +// QPalette helpers: Mask for a single color role/group +QDESIGNER_SHARED_EXPORT quint64 paletteResolveMask(QPalette::ColorGroup colorGroup, + QPalette::ColorRole colorRole); +// Mask for the colors of a role in all groups (Active/Inactive/Disabled) +QDESIGNER_SHARED_EXPORT quint64 paletteResolveMask(QPalette::ColorRole colorRole); + +namespace Utils { + +inline int valueOf(const QVariant &value, bool *ok = nullptr) +{ + if (value.canConvert()) { + if (ok) + *ok = true; + return qvariant_cast(value).value; + } + if (value.canConvert()) { + if (ok) + *ok = true; + return qvariant_cast(value).value; + } + return value.toInt(ok); +} + +inline bool isObjectAncestorOf(QObject *ancestor, QObject *child) +{ + QObject *obj = child; + while (obj != nullptr) { + if (obj == ancestor) + return true; + obj = obj->parent(); + } + return false; +} + +inline bool isCentralWidget(QDesignerFormWindowInterface *fw, QWidget *widget) +{ + if (! fw || ! widget) + return false; + + if (widget == fw->mainContainer()) + return true; + + // ### generalize for other containers + if (QMainWindow *mw = qobject_cast(fw->mainContainer())) { + return mw->centralWidget() == widget; + } + + return false; +} + +} // namespace Utils + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_UTILS_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_widget.cpp b/src/tools/designer/src/lib/shared/qdesigner_widget.cpp new file mode 100644 index 00000000000..88f43d5abaf --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_widget.cpp @@ -0,0 +1,68 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_widget_p.h" +#include "formwindowbase_p.h" +#include "grid_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/* QDesignerDialog / QDesignerWidget are used to paint a grid on QDialog and QWidget main containers + * and container extension pages. + * The paint routines work as follows: + * We need to clean the background here (to make the parent grid disappear in case we are a container page + * and to make palette background settings take effect), + * which would normally break style sheets with background settings. + * So, we manually make the style paint after cleaning. On top comes the grid + * In addition, this code works around + * the QStyleSheetStyle setting Qt::WA_StyledBackground to false for subclasses of QWidget. + */ + +QDesignerDialog::QDesignerDialog(QDesignerFormWindowInterface *fw, QWidget *parent) : + QDialog(parent), + m_formWindow(qobject_cast(fw)) +{ +} + +void QDesignerDialog::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + QStyleOption opt; + opt.initFrom(this); + p.fillRect(e->rect(), palette().brush(backgroundRole())); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + if (m_formWindow && m_formWindow->gridVisible()) + m_formWindow->designerGrid().paint(p, this, e); +} + +QDesignerWidget::QDesignerWidget(QDesignerFormWindowInterface* formWindow, QWidget *parent) : + QWidget(parent), + m_formWindow(qobject_cast(formWindow)) +{ +} + +QDesignerWidget::~QDesignerWidget() = default; + +QDesignerFormWindowInterface* QDesignerWidget::formWindow() const +{ + return m_formWindow; +} + +void QDesignerWidget::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + QStyleOption opt; + opt.initFrom(this); + p.fillRect(e->rect(), palette().brush(backgroundRole())); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); + if (m_formWindow && m_formWindow->gridVisible()) + m_formWindow->designerGrid().paint(p, this, e); +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_widget_p.h b/src/tools/designer/src/lib/shared/qdesigner_widget_p.h new file mode 100644 index 00000000000..4deeaaf8301 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_widget_p.h @@ -0,0 +1,84 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_WIDGET_H +#define QDESIGNER_WIDGET_H + +#include "shared_global_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; + +namespace qdesigner_internal { + class FormWindowBase; +} + +class QDESIGNER_SHARED_EXPORT QDesignerWidget : public QWidget +{ + Q_OBJECT +public: + explicit QDesignerWidget(QDesignerFormWindowInterface* formWindow, QWidget *parent = nullptr); + ~QDesignerWidget() override; + + QDesignerFormWindowInterface* formWindow() const; + + void updatePixmap(); + + QSize minimumSizeHint() const override + { return QWidget::minimumSizeHint().expandedTo(QSize(16, 16)); } + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + qdesigner_internal::FormWindowBase* m_formWindow; +}; + +class QDESIGNER_SHARED_EXPORT QDesignerDialog : public QDialog +{ + Q_OBJECT +public: + explicit QDesignerDialog(QDesignerFormWindowInterface *fw, QWidget *parent); + + QSize minimumSizeHint() const override + { return QDialog::minimumSizeHint().expandedTo(QSize(16, 16)); } + +protected: + void paintEvent(QPaintEvent *e) override; + +private: + qdesigner_internal::FormWindowBase* m_formWindow; +}; + +class QDESIGNER_SHARED_EXPORT Line : public QFrame +{ + Q_OBJECT + Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation) +public: + explicit Line(QWidget *parent) : QFrame(parent) + { setAttribute(Qt::WA_MouseNoMask); setFrameStyle(HLine | Sunken); } + + inline void setOrientation(Qt::Orientation orient) + { setFrameShape(orient == Qt::Horizontal ? HLine : VLine); } + + inline Qt::Orientation orientation() const + { return frameShape() == HLine ? Qt::Horizontal : Qt::Vertical; } +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_WIDGET_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_widgetbox.cpp b/src/tools/designer/src/lib/shared/qdesigner_widgetbox.cpp new file mode 100644 index 00000000000..260f68a9c8f --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_widgetbox.cpp @@ -0,0 +1,233 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_widgetbox_p.h" +#include "qdesigner_utils_p.h" + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +class QDesignerWidgetBoxWidgetData : public QSharedData +{ +public: + QDesignerWidgetBoxWidgetData(const QString &aname, const QString &xml, + const QString &icon_name, + QDesignerWidgetBoxInterface::Widget::Type atype); + QString m_name; + QString m_xml; + QString m_icon_name; + QDesignerWidgetBoxInterface::Widget::Type m_type; +}; + +QDesignerWidgetBoxWidgetData::QDesignerWidgetBoxWidgetData(const QString &aname, + const QString &xml, + const QString &icon_name, + QDesignerWidgetBoxInterface::Widget::Type atype) : + m_name(aname), m_xml(xml), m_icon_name(icon_name), m_type(atype) +{ +} + +QDesignerWidgetBoxInterface::Widget::Widget(const QString &aname, const QString &xml, + const QString &icon_name, Type atype) : + m_data(new QDesignerWidgetBoxWidgetData(aname, xml, icon_name, atype)) +{ +} + +QDesignerWidgetBoxInterface::Widget::~Widget() = default; + +QDesignerWidgetBoxInterface::Widget::Widget(const Widget &w) : + m_data(w.m_data) +{ +} + +QDesignerWidgetBoxInterface::Widget &QDesignerWidgetBoxInterface::Widget::operator=(const Widget &rhs) +{ + if (this != &rhs) { + m_data = rhs.m_data; + } + return *this; +} + +QString QDesignerWidgetBoxInterface::Widget::name() const +{ + return m_data->m_name; +} + +void QDesignerWidgetBoxInterface::Widget::setName(const QString &aname) +{ + if (m_data->m_name != aname) + m_data->m_name = aname; +} + +QString QDesignerWidgetBoxInterface::Widget::domXml() const +{ + return m_data->m_xml; +} + +void QDesignerWidgetBoxInterface::Widget::setDomXml(const QString &xml) +{ + if (m_data->m_xml != xml) + m_data->m_xml = xml; +} + +QString QDesignerWidgetBoxInterface::Widget::iconName() const +{ + return m_data->m_icon_name; +} + +void QDesignerWidgetBoxInterface::Widget::setIconName(const QString &icon_name) +{ + if (m_data->m_icon_name != icon_name) + m_data->m_icon_name = icon_name; +} + +QDesignerWidgetBoxInterface::Widget::Type QDesignerWidgetBoxInterface::Widget::type() const +{ + return m_data->m_type; +} + +void QDesignerWidgetBoxInterface::Widget::setType(Type atype) +{ + if (m_data->m_type != atype) + m_data->m_type = atype; +} + +bool QDesignerWidgetBoxInterface::Widget::isNull() const +{ + return m_data->m_name.isEmpty(); +} + +namespace qdesigner_internal { +QDesignerWidgetBox::QDesignerWidgetBox(QWidget *parent, Qt::WindowFlags flags) + : QDesignerWidgetBoxInterface(parent, flags) +{ + +} + +QDesignerWidgetBox::LoadMode QDesignerWidgetBox::loadMode() const +{ + return m_loadMode; +} + +void QDesignerWidgetBox::setLoadMode(LoadMode lm) +{ + m_loadMode = lm; +} + +// Convenience to find a widget by class name +bool QDesignerWidgetBox::findWidget(const QDesignerWidgetBoxInterface *wbox, + const QString &className, + const QString &category, + Widget *widgetData) +{ + // Note that entry names do not necessarily match the class name + // (at least, not for the standard widgets), so, + // look in the XML for the class name of the first widget to appear + QString pattern = QStringLiteral("^categoryCount(); + for (int c = 0; c < catCount; c++) { + const Category cat = wbox->category(c); + if (category.isEmpty() || cat.name() == category) { + const int widgetCount = cat.widgetCount(); + for (int w = 0; w < widgetCount; w++) { + const Widget widget = cat.widget(w); + QString xml = widget.domXml(); // Erase the tag that can be present starting from 4.4 + const auto widgetTagIndex = xml.indexOf("").arg(name.toString())); + continue; + } + + if (name.compare("widget"_L1, Qt::CaseInsensitive) == 0) { // 4.3 legacy, wrap into DomUI + ui = new DomUI; + DomWidget *widget = new DomWidget; + widget->read(reader); + ui->setElementWidget(widget); + } else if (name.compare("ui"_L1, Qt::CaseInsensitive) == 0) { // 4.4 + ui = new DomUI; + ui->read(reader); + } else { + reader.raiseError(tr("Unexpected element <%1>").arg(name.toString())); + } + } + } + + if (reader.hasError()) { + delete ui; + *errorMessage = tr("A parse error occurred at line %1, column %2 of the XML code " + "specified for the widget %3: %4\n%5") + .arg(reader.lineNumber()).arg(reader.columnNumber()) + .arg(name, reader.errorString(), xml); + return nullptr; + } + + if (!ui || !ui->elementWidget()) { + delete ui; + *errorMessage = tr("The XML code specified for the widget %1 does not contain " + "any widget elements.\n%2").arg(name, xml); + return nullptr; + } + + if (insertFakeTopLevel) { + DomWidget *fakeTopLevel = new DomWidget; + fakeTopLevel->setAttributeClass(u"QWidget"_s); + QList children; + children.push_back(ui->takeElementWidget()); + fakeTopLevel->setElementWidget(children); + ui->setElementWidget(fakeTopLevel); + } + + return ui; +} + +// Convenience to create a Dom Widget from widget box xml code. +DomUI *QDesignerWidgetBox::xmlToUi(const QString &name, const QString &xml, bool insertFakeTopLevel) +{ + QString errorMessage; + DomUI *rc = xmlToUi(name, xml, insertFakeTopLevel, &errorMessage); + if (!rc) + qdesigner_internal::designerWarning(errorMessage); + return rc; +} + +} // namespace qdesigner_internal + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_widgetbox_p.h b/src/tools/designer/src/lib/shared/qdesigner_widgetbox_p.h new file mode 100644 index 00000000000..813d6589371 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_widgetbox_p.h @@ -0,0 +1,63 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QDESIGNER_WIDGETBOX_H +#define QDESIGNER_WIDGETBOX_H + +#include "shared_global_p.h" +#include + +QT_BEGIN_NAMESPACE + +class DomUI; + +namespace qdesigner_internal { + +// A widget box with a load mode that allows for updating custom widgets. + +class QDESIGNER_SHARED_EXPORT QDesignerWidgetBox : public QDesignerWidgetBoxInterface +{ + Q_OBJECT +public: + enum LoadMode { LoadMerge, LoadReplace, LoadCustomWidgetsOnly }; + + explicit QDesignerWidgetBox(QWidget *parent = nullptr, Qt::WindowFlags flags = {}); + + LoadMode loadMode() const; + void setLoadMode(LoadMode lm); + + virtual bool loadContents(const QString &contents) = 0; + + // Convenience to access the widget box icon of a widget. Empty category + // matches all + virtual QIcon iconForWidget(const QString &className, + const QString &category = QString()) const = 0; + + // Convenience to find a widget by class name. Empty category matches all + static bool findWidget(const QDesignerWidgetBoxInterface *wbox, + const QString &className, + const QString &category /* = QString() */, + Widget *widgetData); + // Convenience functions to create a DomWidget from widget box xml. + static DomUI *xmlToUi(const QString &name, const QString &xml, bool insertFakeTopLevel, QString *errorMessage); + static DomUI *xmlToUi(const QString &name, const QString &xml, bool insertFakeTopLevel); + +private: + LoadMode m_loadMode = LoadMerge; +}; +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif // QDESIGNER_WIDGETBOX_H diff --git a/src/tools/designer/src/lib/shared/qdesigner_widgetitem.cpp b/src/tools/designer/src/lib/shared/qdesigner_widgetitem.cpp new file mode 100644 index 00000000000..b986aede86e --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_widgetitem.cpp @@ -0,0 +1,308 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qdesigner_widgetitem_p.h" +#include "qdesigner_widget_p.h" +#include "widgetfactory_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +enum { DebugWidgetItem = 0 }; +enum { MinimumLength = 10 }; + +// Widget item creation function to be registered as factory method with +// QLayoutPrivate +static QWidgetItem *createDesignerWidgetItem(const QLayout *layout, QWidget *widget) +{ + Qt::Orientations orientations; + if (qdesigner_internal::QDesignerWidgetItem::check(layout, widget, &orientations)) { + if (DebugWidgetItem) + qDebug() << "QDesignerWidgetItem: Creating on " << layout << widget << orientations; + return new qdesigner_internal::QDesignerWidgetItem(layout, widget, orientations); + } + if (DebugWidgetItem) + qDebug() << "QDesignerWidgetItem: Noncontainer: " << layout << widget; + + return nullptr; +} + +static QString sizePolicyToString(const QSizePolicy &p) +{ + QString rc; { + QTextStream str(&rc); + str << "Control=" << p.controlType() << " expdirs=" << p.expandingDirections() + << " hasHeightForWidth=" << p.hasHeightForWidth() + << " H: Policy=" << p.horizontalPolicy() + << " stretch=" << p.horizontalStretch() + << " V: Policy=" << p.verticalPolicy() + << " stretch=" << p.verticalStretch(); + } + return rc; +} + +// Find the layout the item is contained in, recursing over +// child layouts +static const QLayout *findLayoutOfItem(const QLayout *haystack, const QLayoutItem *needle) +{ + const int count = haystack->count(); + for (int i = 0; i < count; i++) { + QLayoutItem *item = haystack->itemAt(i); + if (item == needle) + return haystack; + if (QLayout *childLayout = item->layout()) + if (const QLayout *containing = findLayoutOfItem(childLayout, needle)) + return containing; + } + return nullptr; +} + + +namespace qdesigner_internal { + +// ------------------ QDesignerWidgetItem +QDesignerWidgetItem::QDesignerWidgetItem(const QLayout *containingLayout, QWidget *w, Qt::Orientations o) : + QWidgetItemV2(w), + m_orientations(o), + m_nonLaidOutMinSize(w->minimumSizeHint()), + m_nonLaidOutSizeHint(w->sizeHint()), + m_cachedContainingLayout(containingLayout) +{ + // Initialize the minimum size to prevent nonlaid-out frames/widgets + // from being slammed to zero + const QSize minimumSize = w->minimumSize(); + if (!minimumSize.isEmpty()) + m_nonLaidOutMinSize = minimumSize; + expand(&m_nonLaidOutMinSize); + expand(&m_nonLaidOutSizeHint); + w->installEventFilter(this); + connect(containingLayout, &QObject::destroyed, this, &QDesignerWidgetItem::layoutChanged); + if (DebugWidgetItem ) + qDebug() << "QDesignerWidgetItem" << w << sizePolicyToString(w->sizePolicy()) << m_nonLaidOutMinSize << m_nonLaidOutSizeHint; +} + +void QDesignerWidgetItem::expand(QSize *s) const +{ + // Expand the size if its too small + if (m_orientations & Qt::Horizontal && s->width() <= 0) + s->setWidth(MinimumLength); + if (m_orientations & Qt::Vertical && s->height() <= 0) + s->setHeight(MinimumLength); +} + +QSize QDesignerWidgetItem::minimumSize() const +{ + // Just track the size in case we are laid-out or stretched. + const QSize baseMinSize = QWidgetItemV2::minimumSize(); + QWidget * w = constWidget(); + if (w->layout() || subjectToStretch(containingLayout(), w)) { + m_nonLaidOutMinSize = baseMinSize; + return baseMinSize; + } + // Nonlaid out: Maintain last laid-out size + const QSize rc = baseMinSize.expandedTo(m_nonLaidOutMinSize); + if (DebugWidgetItem > 1) + qDebug() << "minimumSize" << constWidget() << baseMinSize << rc; + return rc; +} + +QSize QDesignerWidgetItem::sizeHint() const +{ + // Just track the size in case we are laid-out or stretched. + const QSize baseSizeHint = QWidgetItemV2::sizeHint(); + QWidget * w = constWidget(); + if (w->layout() || subjectToStretch(containingLayout(), w)) { + m_nonLaidOutSizeHint = baseSizeHint; + return baseSizeHint; + } + // Nonlaid out: Maintain last laid-out size + const QSize rc = baseSizeHint.expandedTo(m_nonLaidOutSizeHint); + if (DebugWidgetItem > 1) + qDebug() << "sizeHint" << constWidget() << baseSizeHint << rc; + return rc; +} + +bool QDesignerWidgetItem::subjectToStretch(const QLayout *layout, QWidget *w) +{ + if (!layout) + return false; + // Are we under some stretch factor? + if (const QBoxLayout *bl = qobject_cast(layout)) { + const int index = bl->indexOf(w); + Q_ASSERT(index != -1); + return bl->stretch(index) != 0; + } + if (const QGridLayout *cgl = qobject_cast(layout)) { + QGridLayout *gl = const_cast(cgl); + const int index = cgl->indexOf(w); + Q_ASSERT(index != -1); + int row, column, rowSpan, columnSpan; + gl->getItemPosition (index, &row, &column, &rowSpan, &columnSpan); + const int rend = row + rowSpan; + const int cend = column + columnSpan; + for (int r = row; r < rend; r++) + if (cgl->rowStretch(r) != 0) + return true; + for (int c = column; c < cend; c++) + if (cgl->columnStretch(c) != 0) + return true; + } + return false; +} + +/* Return the orientations mask for a layout, specifying + * in which directions squeezing should be prevented. */ +static Qt::Orientations layoutOrientation(const QLayout *layout) +{ + if (const QBoxLayout *bl = qobject_cast(layout)) { + const QBoxLayout::Direction direction = bl->direction(); + return direction == QBoxLayout::LeftToRight || direction == QBoxLayout::RightToLeft ? Qt::Horizontal : Qt::Vertical; + } + if (qobject_cast(layout)) + return Qt::Vertical; + return Qt::Horizontal|Qt::Vertical; +} + +// Check for a non-container extension container +bool QDesignerWidgetItem::isContainer(const QDesignerFormEditorInterface *core, QWidget *w) +{ + if (!WidgetFactory::isFormEditorObject(w)) + return false; + const QDesignerWidgetDataBaseInterface *wdb = core->widgetDataBase(); + const int widx = wdb->indexOfObject(w); + if (widx == -1 || !wdb->item(widx)->isContainer()) + return false; + if (qt_extension(core->extensionManager(), w)) + return false; + return true; +} + +bool QDesignerWidgetItem::check(const QLayout *layout, QWidget *w, Qt::Orientations *ptrToOrientations) +{ + // Check for form-editor non-containerextension-containers (QFrame, etc) + // within laid-out form editor widgets. No check for managed() here as we + // want container pages and widgets in the process of being morphed as + // well. Avoid nested layouts (as the effective stretch cannot be easily + // computed and may mess things up). + if (ptrToOrientations) + *ptrToOrientations = {}; + + const QObject *layoutParent = layout->parent(); + if (!layoutParent || !layoutParent->isWidgetType() || !WidgetFactory::isFormEditorObject(layoutParent)) + return false; + + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(w); + if (!fw || !isContainer(fw->core(), w)) + return false; + + // If it is a box, restrict to its orientation + if (ptrToOrientations) + *ptrToOrientations = layoutOrientation(layout); + + return true; +} + +QSize QDesignerWidgetItem::nonLaidOutMinSize() const +{ + return m_nonLaidOutMinSize; +} + +void QDesignerWidgetItem::setNonLaidOutMinSize(const QSize &s) +{ + if (DebugWidgetItem > 1) + qDebug() << "setNonLaidOutMinSize" << constWidget() << s; + m_nonLaidOutMinSize = s; +} + +QSize QDesignerWidgetItem::nonLaidOutSizeHint() const +{ + return m_nonLaidOutSizeHint; +} + +void QDesignerWidgetItem::setNonLaidOutSizeHint(const QSize &s) +{ + if (DebugWidgetItem > 1) + qDebug() << "setNonLaidOutSizeHint" << constWidget() << s; + m_nonLaidOutSizeHint = s; +} + +void QDesignerWidgetItem::install() +{ + QLayoutPrivate::widgetItemFactoryMethod = createDesignerWidgetItem; +} + +void QDesignerWidgetItem::deinstall() +{ + QLayoutPrivate::widgetItemFactoryMethod = nullptr; +} + +const QLayout *QDesignerWidgetItem::containingLayout() const +{ + if (!m_cachedContainingLayout) { + if (QWidget *parentWidget = constWidget()->parentWidget()) + if (QLayout *parentLayout = parentWidget->layout()) { + m_cachedContainingLayout = findLayoutOfItem(parentLayout, this); + if (m_cachedContainingLayout) { + connect(m_cachedContainingLayout, &QObject::destroyed, + this, &QDesignerWidgetItem::layoutChanged); + } + } + if (DebugWidgetItem) + qDebug() << Q_FUNC_INFO << " found " << m_cachedContainingLayout << " after reparenting " << constWidget(); + } + return m_cachedContainingLayout; +} + +void QDesignerWidgetItem::layoutChanged() +{ + if (DebugWidgetItem) + qDebug() << Q_FUNC_INFO; + m_cachedContainingLayout = nullptr; +} + +bool QDesignerWidgetItem::eventFilter(QObject * /* watched */, QEvent *event) +{ + if (event->type() == QEvent::ParentChange) + layoutChanged(); + return false; +} + +// ------------------ QDesignerWidgetItemInstaller + +int QDesignerWidgetItemInstaller::m_instanceCount = 0; + +QDesignerWidgetItemInstaller::QDesignerWidgetItemInstaller() +{ + if (m_instanceCount++ == 0) { + if (DebugWidgetItem) + qDebug() << "QDesignerWidgetItemInstaller: installing"; + QDesignerWidgetItem::install(); + } +} + +QDesignerWidgetItemInstaller::~QDesignerWidgetItemInstaller() +{ + if (--m_instanceCount == 0) { + if (DebugWidgetItem) + qDebug() << "QDesignerWidgetItemInstaller: deinstalling"; + QDesignerWidgetItem::deinstall(); + } +} + +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qdesigner_widgetitem_p.h b/src/tools/designer/src/lib/shared/qdesigner_widgetitem_p.h new file mode 100644 index 00000000000..b7a533e08a6 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qdesigner_widgetitem_p.h @@ -0,0 +1,109 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef DESIGNERWIDGETITEM_H +#define DESIGNERWIDGETITEM_H + +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +// QDesignerWidgetItem: A Layout Item that is used for non-containerextension- +// containers (QFrame, etc) on Designer forms. It prevents its widget +// from being slammed to size 0 if the widget has no layout: +// Pre 4.5, this item ensured only that QWidgets and QFrames were not squeezed +// to size 0 since they have an invalid size hint when non-laid out. +// Since 4.5, the item is used for every non-containerextension-container. +// In case the container has itself a layout, it merely tracks the minimum +// size. If the container has no layout and is not subject to some stretch +// factor, it will return the last valid size. The effect is that after +// breaking a layout on a container within a layout, it just maintains its +// last size and is not slammed to 0,0. In addition, it can be resized. +// The class keeps track of the containing layout by tracking widget reparent +// and destroyed slots as Designer will for example re-create grid layouts to +// shrink them. + +class QDESIGNER_SHARED_EXPORT QDesignerWidgetItem : public QObject, public QWidgetItemV2 { + Q_DISABLE_COPY_MOVE(QDesignerWidgetItem) + Q_OBJECT +public: + explicit QDesignerWidgetItem(const QLayout *containingLayout, QWidget *w, Qt::Orientations o = Qt::Horizontal|Qt::Vertical); + + const QLayout *containingLayout() const; + + inline QWidget *constWidget() const { return const_cast(this)->widget(); } + + QSize minimumSize() const override; + QSize sizeHint() const override; + + // Resize: Takes effect if the contained widget does not have a layout + QSize nonLaidOutMinSize() const; + void setNonLaidOutMinSize(const QSize &s); + + QSize nonLaidOutSizeHint() const; + void setNonLaidOutSizeHint(const QSize &s); + + // Check whether a QDesignerWidgetItem should be installed + static bool check(const QLayout *layout, QWidget *w, Qt::Orientations *ptrToOrientations = nullptr); + + // Register itself using QLayoutPrivate's widget item factory method hook + static void install(); + static void deinstall(); + + // Check for a non-container extension container + static bool isContainer(const QDesignerFormEditorInterface *core, QWidget *w); + + static bool subjectToStretch(const QLayout *layout, QWidget *w); + + bool eventFilter(QObject * watched, QEvent * event) override; + +private slots: + void layoutChanged(); + +private: + void expand(QSize *s) const; + bool subjectToStretch() const; + + const Qt::Orientations m_orientations; + mutable QSize m_nonLaidOutMinSize; + mutable QSize m_nonLaidOutSizeHint; + mutable const QLayout *m_cachedContainingLayout; +}; + +// Helper class that ensures QDesignerWidgetItem is installed while an +// instance is in scope. + +class QDESIGNER_SHARED_EXPORT QDesignerWidgetItemInstaller { + Q_DISABLE_COPY_MOVE(QDesignerWidgetItemInstaller) + +public: + QDesignerWidgetItemInstaller(); + ~QDesignerWidgetItemInstaller(); + +private: + static int m_instanceCount; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/designer/src/lib/shared/qlayout_widget.cpp b/src/tools/designer/src/lib/shared/qlayout_widget.cpp new file mode 100644 index 00000000000..28b25ce8467 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qlayout_widget.cpp @@ -0,0 +1,2048 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qlayout_widget_p.h" +#include "qdesigner_utils_p.h" +#include "layout_p.h" +#include "layoutinfo_p.h" +#include "invisible_widget_p.h" +#include "qdesigner_widgetitem_p.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +enum { ShiftValue = 1 }; +enum { debugLayout = 0 }; +enum { FormLayoutColumns = 2 }; +enum { indicatorSize = 2 }; +// Grid/form Helpers: get info (overloads to make templates work) + +namespace { // Do not use static, will break HP-UX due to templates + +QT_USE_NAMESPACE + +// overloads to make templates over QGridLayout/QFormLayout work +inline int gridRowCount(const QGridLayout *gridLayout) +{ + return gridLayout->rowCount(); +} + +inline int gridColumnCount(const QGridLayout *gridLayout) +{ + return gridLayout->columnCount(); +} + +// QGridLayout/QFormLayout Helpers: get item position (overloads to make templates work) +inline void getGridItemPosition(QGridLayout *gridLayout, int index, + int *row, int *column, int *rowspan, int *colspan) +{ + gridLayout->getItemPosition(index, row, column, rowspan, colspan); +} + +QRect gridItemInfo(QGridLayout *grid, int index) +{ + int row, column, rowSpan, columnSpan; + // getItemPosition is not const, grmbl.. + grid->getItemPosition(index, &row, &column, &rowSpan, &columnSpan); + return QRect(column, row, columnSpan, rowSpan); +} + +inline int gridRowCount(const QFormLayout *formLayout) { return formLayout->rowCount(); } +inline int gridColumnCount(const QFormLayout *) { return FormLayoutColumns; } + +inline void getGridItemPosition(QFormLayout *formLayout, int index, int *row, int *column, int *rowspan, int *colspan) +{ + qdesigner_internal::getFormLayoutItemPosition(formLayout, index, row, column, rowspan, colspan); +} +} // namespace anonymous + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto objectNameC = "objectName"_L1; +static constexpr auto sizeConstraintC = "sizeConstraint"_L1; + +/* A padding spacer element that is used to represent an empty form layout cell. It should grow with its cell. + * Should not be used on a grid as it causes resizing inconsistencies */ +namespace qdesigner_internal { + class PaddingSpacerItem : public QSpacerItem { + public: + PaddingSpacerItem() : QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding) {} + + Qt::Orientations expandingDirections () const override + { return Qt::Vertical | Qt::Horizontal; } + }; +} + +static inline QSpacerItem *createGridSpacer() +{ + return new QSpacerItem(0, 0); +} + +static inline QSpacerItem *createFormSpacer() +{ + return new qdesigner_internal::PaddingSpacerItem; +} + +// QGridLayout/QFormLayout Helpers: Debug items of GridLikeLayout +template +static QDebug debugGridLikeLayout(QDebug str, const GridLikeLayout &gl) +{ + const int count = gl.count(); + str << "Grid: " << gl.objectName() << gridRowCount(&gl) << " rows x " << gridColumnCount(&gl) + << " cols " << count << " items\n"; + for (int i = 0; i < count; i++) { + QLayoutItem *item = gl.itemAt(i); + str << "Item " << i << item << item->widget() << gridItemInfo(const_cast(&gl), i) << " empty=" << qdesigner_internal::LayoutInfo::isEmptyItem(item) << "\n"; + } + return str; +} + +static inline QDebug operator<<(QDebug str, const QGridLayout &gl) { return debugGridLikeLayout(str, gl); } + +static inline bool isEmptyFormLayoutRow(const QFormLayout *fl, int row) +{ + // Spanning can never be empty + if (fl->itemAt(row, QFormLayout::SpanningRole)) + return false; + return qdesigner_internal::LayoutInfo::isEmptyItem(fl->itemAt(row, QFormLayout::LabelRole)) && qdesigner_internal::LayoutInfo::isEmptyItem(fl->itemAt(row, QFormLayout::FieldRole)); +} + +static inline bool canSimplifyFormLayout(const QFormLayout *formLayout, const QRect &restrictionArea) +{ + if (restrictionArea.x() >= FormLayoutColumns) + return false; + // Try to find empty rows + const int bottomCheckRow = qMin(formLayout->rowCount(), restrictionArea.top() + restrictionArea.height()); + for (int r = restrictionArea.y(); r < bottomCheckRow; r++) + if (isEmptyFormLayoutRow(formLayout, r)) + return true; + return false; +} + +// recreate a managed layout (which does not automagically remove +// empty rows/columns like grid or form layout) in case it needs to shrink + +static QLayout *recreateManagedLayout(const QDesignerFormEditorInterface *core, QWidget *w, QLayout *lt) +{ + const qdesigner_internal::LayoutInfo::Type t = qdesigner_internal::LayoutInfo::layoutType(core, lt); + qdesigner_internal::LayoutProperties properties; + const int mask = properties.fromPropertySheet(core, lt, qdesigner_internal::LayoutProperties::AllProperties); + qdesigner_internal::LayoutInfo::deleteLayout(core, w); + QLayout *rc = core->widgetFactory()->createLayout(w, nullptr, t); + properties.toPropertySheet(core, rc, mask, true); + return rc; +} + +// QGridLayout/QFormLayout Helpers: find an item on a form/grid. Return index +template +int findGridItemAt(GridLikeLayout *gridLayout, int at_row, int at_column) +{ + Q_ASSERT(gridLayout); + const int count = gridLayout->count(); + for (int index = 0; index < count; index++) { + int row, column, rowspan, colspan; + getGridItemPosition(gridLayout, index, &row, &column, &rowspan, &colspan); + if (at_row >= row && at_row < (row + rowspan) + && at_column >= column && at_column < (column + colspan)) { + return index; + } + } + return -1; +} +// QGridLayout/QFormLayout Helpers: remove dummy spacers on form/grid +template +static bool removeEmptyCellsOnGrid(GridLikeLayout *grid, const QRect &area) +{ + // check if there are any items in the way. Should be only spacers + // Unique out items that span rows/columns. + QList indexesToBeRemoved; + indexesToBeRemoved.reserve(grid->count()); + const int rightColumn = area.x() + area.width(); + const int bottomRow = area.y() + area.height(); + for (int c = area.x(); c < rightColumn; c++) + for (int r = area.y(); r < bottomRow; r++) { + const int index = findGridItemAt(grid, r ,c); + if (index != -1) + if (QLayoutItem *item = grid->itemAt(index)) { + if (qdesigner_internal::LayoutInfo::isEmptyItem(item)) { + if (indexesToBeRemoved.indexOf(index) == -1) + indexesToBeRemoved.push_back(index); + } else { + return false; + } + } + } + // remove, starting from last + if (!indexesToBeRemoved.isEmpty()) { + std::stable_sort(indexesToBeRemoved.begin(), indexesToBeRemoved.end()); + std::reverse(indexesToBeRemoved.begin(), indexesToBeRemoved.end()); + for (auto i : std::as_const(indexesToBeRemoved)) + delete grid->takeAt(i); + } + return true; +} + +namespace qdesigner_internal { +// --------- LayoutProperties + +LayoutProperties::LayoutProperties() +{ + clear(); +} + +void LayoutProperties::clear() +{ + std::fill(m_margins, m_margins + MarginCount, 0); + std::fill(m_marginsChanged, m_marginsChanged + MarginCount, false); + std::fill(m_spacings, m_spacings + SpacingsCount, 0); + std::fill(m_spacingsChanged, m_spacingsChanged + SpacingsCount, false); + + m_objectName = QVariant(); + m_objectNameChanged = false; + m_sizeConstraint = QVariant(QLayout::SetDefaultConstraint); + m_sizeConstraintChanged = false; + + m_fieldGrowthPolicyChanged = m_rowWrapPolicyChanged = m_labelAlignmentChanged = m_formAlignmentChanged = false; + m_fieldGrowthPolicy = m_rowWrapPolicy = m_formAlignment = QVariant(); + + m_boxStretchChanged = m_gridRowStretchChanged = m_gridColumnStretchChanged = m_gridRowMinimumHeightChanged = false; + m_boxStretch = m_gridRowStretch = m_gridColumnStretch = m_gridRowMinimumHeight = QVariant(); +} + +int LayoutProperties::visibleProperties(const QLayout *layout) +{ + // Grid like layout have 2 spacings. + const bool isFormLayout = qobject_cast(layout); + const bool isGridLike = qobject_cast(layout) || isFormLayout; + int rc = ObjectNameProperty|LeftMarginProperty|TopMarginProperty|RightMarginProperty|BottomMarginProperty| + SizeConstraintProperty; + + rc |= isGridLike ? (HorizSpacingProperty|VertSpacingProperty) : SpacingProperty; + if (isFormLayout) { + rc |= FieldGrowthPolicyProperty|RowWrapPolicyProperty|LabelAlignmentProperty|FormAlignmentProperty; + } else { + if (isGridLike) { + rc |= GridRowStretchProperty|GridColumnStretchProperty|GridRowMinimumHeightProperty|GridColumnMinimumWidthProperty; + } else { + rc |= BoxStretchProperty; + } + } + return rc; +} + +static const char *marginPropertyNamesC[] = {"leftMargin", "topMargin", "rightMargin", "bottomMargin"}; +static const char *spacingPropertyNamesC[] = {"spacing", "horizontalSpacing", "verticalSpacing" }; +static constexpr auto fieldGrowthPolicyPropertyC = "fieldGrowthPolicy"_L1; +static constexpr auto rowWrapPolicyPropertyC = "rowWrapPolicy"_L1; +static constexpr auto labelAlignmentPropertyC = "labelAlignment"_L1; +static constexpr auto formAlignmentPropertyC = "formAlignment"_L1; +static constexpr auto boxStretchPropertyC = "stretch"_L1; +static constexpr auto gridRowStretchPropertyC = "rowStretch"_L1; +static constexpr auto gridColumnStretchPropertyC = "columnStretch"_L1; +static constexpr auto gridRowMinimumHeightPropertyC = "rowMinimumHeight"_L1; +static constexpr auto gridColumnMinimumWidthPropertyC = "columnMinimumWidth"_L1; + +static bool intValueFromSheet(const QDesignerPropertySheetExtension *sheet, const QString &name, int *value, bool *changed) +{ + const int sheetIndex = sheet->indexOf(name); + if (sheetIndex == -1) + return false; + *value = sheet->property(sheetIndex).toInt(); + *changed = sheet->isChanged(sheetIndex); + return true; +} + +static void variantPropertyFromSheet(int mask, int flag, const QDesignerPropertySheetExtension *sheet, const QString &name, + QVariant *value, bool *changed, int *returnMask) +{ + if (mask & flag) { + const int sIndex = sheet->indexOf(name); + if (sIndex != -1) { + *value = sheet->property(sIndex); + *changed = sheet->isChanged(sIndex); + *returnMask |= flag; + } + } +} + +int LayoutProperties::fromPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask) +{ + int rc = 0; + const QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), l); + Q_ASSERT(sheet); + // name + if (mask & ObjectNameProperty) { + const int nameIndex = sheet->indexOf(objectNameC); + Q_ASSERT(nameIndex != -1); + m_objectName = sheet->property(nameIndex); + m_objectNameChanged = sheet->isChanged(nameIndex); + rc |= ObjectNameProperty; + } + // -- Margins + const int marginFlags[MarginCount] = { LeftMarginProperty, TopMarginProperty, RightMarginProperty, BottomMarginProperty}; + for (int i = 0; i < MarginCount; i++) + if (mask & marginFlags[i]) + if (intValueFromSheet(sheet, QLatin1StringView(marginPropertyNamesC[i]), m_margins + i, m_marginsChanged + i)) + rc |= marginFlags[i]; + + const int spacingFlags[] = { SpacingProperty, HorizSpacingProperty, VertSpacingProperty}; + for (int i = 0; i < SpacingsCount; i++) + if (mask & spacingFlags[i]) + if (intValueFromSheet(sheet, QLatin1StringView(spacingPropertyNamesC[i]), m_spacings + i, m_spacingsChanged + i)) + rc |= spacingFlags[i]; + // sizeConstraint, flags + variantPropertyFromSheet(mask, SizeConstraintProperty, sheet, sizeConstraintC, &m_sizeConstraint, &m_sizeConstraintChanged, &rc); + variantPropertyFromSheet(mask, FieldGrowthPolicyProperty, sheet, fieldGrowthPolicyPropertyC, &m_fieldGrowthPolicy, &m_fieldGrowthPolicyChanged, &rc); + variantPropertyFromSheet(mask, RowWrapPolicyProperty, sheet, rowWrapPolicyPropertyC, &m_rowWrapPolicy, &m_rowWrapPolicyChanged, &rc); + variantPropertyFromSheet(mask, LabelAlignmentProperty, sheet, labelAlignmentPropertyC, &m_labelAlignment, &m_labelAlignmentChanged, &rc); + variantPropertyFromSheet(mask, FormAlignmentProperty, sheet, formAlignmentPropertyC, &m_formAlignment, &m_formAlignmentChanged, &rc); + variantPropertyFromSheet(mask, BoxStretchProperty, sheet, boxStretchPropertyC, &m_boxStretch, & m_boxStretchChanged, &rc); + variantPropertyFromSheet(mask, GridRowStretchProperty, sheet, gridRowStretchPropertyC, &m_gridRowStretch, &m_gridRowStretchChanged, &rc); + variantPropertyFromSheet(mask, GridColumnStretchProperty, sheet, gridColumnStretchPropertyC, &m_gridColumnStretch, &m_gridColumnStretchChanged, &rc); + variantPropertyFromSheet(mask, GridRowMinimumHeightProperty, sheet, gridRowMinimumHeightPropertyC, &m_gridRowMinimumHeight, &m_gridRowMinimumHeightChanged, &rc); + variantPropertyFromSheet(mask, GridColumnMinimumWidthProperty, sheet, gridColumnMinimumWidthPropertyC, &m_gridColumnMinimumWidth, &m_gridColumnMinimumWidthChanged, &rc); + return rc; +} + +static bool intValueToSheet(QDesignerPropertySheetExtension *sheet, const QString &name, int value, bool changed, bool applyChanged) + +{ + + const int sheetIndex = sheet->indexOf(name); + if (sheetIndex == -1) { + qWarning() << " LayoutProperties: Attempt to set property " << name << " that does not exist for the layout."; + return false; + } + sheet->setProperty(sheetIndex, QVariant(value)); + if (applyChanged) + sheet->setChanged(sheetIndex, changed); + return true; +} + +static void variantPropertyToSheet(int mask, int flag, bool applyChanged, QDesignerPropertySheetExtension *sheet, const QString &name, + const QVariant &value, bool changed, int *returnMask) +{ + if (mask & flag) { + const int sIndex = sheet->indexOf(name); + if (sIndex != -1) { + sheet->setProperty(sIndex, value); + if (applyChanged) + sheet->setChanged(sIndex, changed); + *returnMask |= flag; + } + } +} + +int LayoutProperties::toPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask, bool applyChanged) const +{ + int rc = 0; + QDesignerPropertySheetExtension *sheet = qt_extension(core->extensionManager(), l); + Q_ASSERT(sheet); + // name + if (mask & ObjectNameProperty) { + const int nameIndex = sheet->indexOf(objectNameC); + Q_ASSERT(nameIndex != -1); + sheet->setProperty(nameIndex, m_objectName); + if (applyChanged) + sheet->setChanged(nameIndex, m_objectNameChanged); + rc |= ObjectNameProperty; + } + // margins + const int marginFlags[MarginCount] = { LeftMarginProperty, TopMarginProperty, RightMarginProperty, BottomMarginProperty}; + for (int i = 0; i < MarginCount; i++) + if (mask & marginFlags[i]) + if (intValueToSheet(sheet, QLatin1StringView(marginPropertyNamesC[i]), m_margins[i], m_marginsChanged[i], applyChanged)) + rc |= marginFlags[i]; + + const int spacingFlags[] = { SpacingProperty, HorizSpacingProperty, VertSpacingProperty}; + for (int i = 0; i < SpacingsCount; i++) + if (mask & spacingFlags[i]) + if (intValueToSheet(sheet, QLatin1StringView(spacingPropertyNamesC[i]), m_spacings[i], m_spacingsChanged[i], applyChanged)) + rc |= spacingFlags[i]; + // sizeConstraint + variantPropertyToSheet(mask, SizeConstraintProperty, applyChanged, sheet, sizeConstraintC, m_sizeConstraint, m_sizeConstraintChanged, &rc); + variantPropertyToSheet(mask, FieldGrowthPolicyProperty, applyChanged, sheet, fieldGrowthPolicyPropertyC, m_fieldGrowthPolicy, m_fieldGrowthPolicyChanged, &rc); + variantPropertyToSheet(mask, RowWrapPolicyProperty, applyChanged, sheet, rowWrapPolicyPropertyC, m_rowWrapPolicy, m_rowWrapPolicyChanged, &rc); + variantPropertyToSheet(mask, LabelAlignmentProperty, applyChanged, sheet, labelAlignmentPropertyC, m_labelAlignment, m_labelAlignmentChanged, &rc); + variantPropertyToSheet(mask, FormAlignmentProperty, applyChanged, sheet, formAlignmentPropertyC, m_formAlignment, m_formAlignmentChanged, &rc); + variantPropertyToSheet(mask, BoxStretchProperty, applyChanged, sheet, boxStretchPropertyC, m_boxStretch, m_boxStretchChanged, &rc); + variantPropertyToSheet(mask, GridRowStretchProperty, applyChanged, sheet, gridRowStretchPropertyC, m_gridRowStretch, m_gridRowStretchChanged, &rc); + variantPropertyToSheet(mask, GridColumnStretchProperty, applyChanged, sheet, gridColumnStretchPropertyC, m_gridColumnStretch, m_gridColumnStretchChanged, &rc); + variantPropertyToSheet(mask, GridRowMinimumHeightProperty, applyChanged, sheet, gridRowMinimumHeightPropertyC, m_gridRowMinimumHeight, m_gridRowMinimumHeightChanged, &rc); + variantPropertyToSheet(mask, GridColumnMinimumWidthProperty, applyChanged, sheet, gridColumnMinimumWidthPropertyC, m_gridColumnMinimumWidth, m_gridColumnMinimumWidthChanged, &rc); + return rc; +} + +// ---------------- LayoutHelper +LayoutHelper::LayoutHelper() = default; + +LayoutHelper::~LayoutHelper() = default; + +int LayoutHelper::indexOf(const QLayout *lt, const QWidget *widget) +{ + if (!lt) + return -1; + + const int itemCount = lt->count(); + for (int i = 0; i < itemCount; i++) + if (lt->itemAt(i)->widget() == widget) + return i; + return -1; +} + +QRect LayoutHelper::itemInfo(QLayout *lt, const QWidget *widget) const +{ + const int index = indexOf(lt, widget); + if (index == -1) { + qWarning() << "LayoutHelper::itemInfo: " << widget << " not in layout " << lt; + return QRect(0, 0, 1, 1); + } + return itemInfo(lt, index); +} + + // ---------------- BoxLayoutHelper + class BoxLayoutHelper : public LayoutHelper { + public: + BoxLayoutHelper(const Qt::Orientation orientation) : m_orientation(orientation) {} + + QRect itemInfo(QLayout *lt, int index) const override; + void insertWidget(QLayout *lt, const QRect &info, QWidget *w) override; + void removeWidget(QLayout *lt, QWidget *widget) override; + void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) override; + + void pushState(const QDesignerFormEditorInterface *, const QWidget *) override; + void popState(const QDesignerFormEditorInterface *, QWidget *) override; + + bool canSimplify(const QDesignerFormEditorInterface *, const QWidget *, const QRect &) const override { return false; } + void simplify(const QDesignerFormEditorInterface *, QWidget *, const QRect &) override {} + + // Helper for restoring layout states + using LayoutItemVector = QList; + static LayoutItemVector disassembleLayout(QLayout *lt); + static QLayoutItem *findItemOfWidget(const LayoutItemVector &lv, QWidget *w); + + private: + using BoxLayoutState = QList; + + static BoxLayoutState state(const QBoxLayout*lt); + + QStack m_states; + const Qt::Orientation m_orientation; + }; + + QRect BoxLayoutHelper::itemInfo(QLayout * /*lt*/, int index) const + { + return m_orientation == Qt::Horizontal ? QRect(index, 0, 1, 1) : QRect(0, index, 1, 1); + } + + void BoxLayoutHelper::insertWidget(QLayout *lt, const QRect &info, QWidget *w) + { + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + QBoxLayout *boxLayout = qobject_cast(lt); + Q_ASSERT(boxLayout); + boxLayout->insertWidget(m_orientation == Qt::Horizontal ? info.x() : info.y(), w); + } + + void BoxLayoutHelper::removeWidget(QLayout *lt, QWidget *widget) + { + QBoxLayout *boxLayout = qobject_cast(lt); + Q_ASSERT(boxLayout); + boxLayout->removeWidget(widget); + } + + void BoxLayoutHelper::replaceWidget(QLayout *lt, QWidget *before, QWidget *after) + { + bool ok = false; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + if (QBoxLayout *boxLayout = qobject_cast(lt)) { + const int index = boxLayout->indexOf(before); + if (index != -1) { + const bool visible = before->isVisible(); + delete boxLayout->takeAt(index); + if (visible) + before->hide(); + before->setParent(nullptr); + boxLayout->insertWidget(index, after); + ok = true; + } + } + if (!ok) + qWarning() << "BoxLayoutHelper::replaceWidget : Unable to replace " << before << " by " << after << " in " << lt; + } + + BoxLayoutHelper::BoxLayoutState BoxLayoutHelper::state(const QBoxLayout*lt) + { + BoxLayoutState rc; + if (const int count = lt->count()) { + rc.reserve(count); + for (int i = 0; i < count; i++) + if (QWidget *w = lt->itemAt(i)->widget()) + rc.push_back(w); + } + return rc; + } + + void BoxLayoutHelper::pushState(const QDesignerFormEditorInterface *core, const QWidget *w) + { + const QBoxLayout *boxLayout = qobject_cast(LayoutInfo::managedLayout(core, w)); + Q_ASSERT(boxLayout); + m_states.push(state(boxLayout)); + } + + QLayoutItem *BoxLayoutHelper::findItemOfWidget(const LayoutItemVector &lv, QWidget *w) + { + for (auto *l : lv) { + if (l->widget() == w) + return l; + } + return nullptr; + } + + BoxLayoutHelper::LayoutItemVector BoxLayoutHelper::disassembleLayout(QLayout *lt) + { + // Take items + const int count = lt->count(); + if (count == 0) + return LayoutItemVector(); + LayoutItemVector rc; + rc.reserve(count); + for (int i = count - 1; i >= 0; i--) + rc.push_back(lt->takeAt(i)); + return rc; + } + + void BoxLayoutHelper::popState(const QDesignerFormEditorInterface *core, QWidget *w) + { + QBoxLayout *boxLayout = qobject_cast(LayoutInfo::managedLayout(core, w)); + Q_ASSERT(boxLayout); + const BoxLayoutState savedState = m_states.pop(); + const BoxLayoutState currentState = state(boxLayout); + // Check for equality/empty. Note that this will currently + // always trigger as box layouts do not have a state apart from + // the order and there is no layout order editor yet. + if (savedState == state(boxLayout)) + return; + + Q_ASSERT(savedState.size() == currentState.size()); + // Take items and reassemble in saved order + const LayoutItemVector items = disassembleLayout(boxLayout); + for (auto *w : savedState) { + QLayoutItem *item = findItemOfWidget(items, w); + Q_ASSERT(item); + boxLayout->addItem(item); + } + } + + // Grid Layout state. Datatype storing the state of a GridLayout as a map of + // widgets to QRect(columns, rows) and size. Used to store the state for undo operations + // that do not change the widgets within the layout; also provides some manipulation + // functions and ability to apply the state to a layout provided its widgets haven't changed. + struct GridLayoutState { + GridLayoutState() = default; + + void fromLayout(QGridLayout *l); + void applyToLayout(const QDesignerFormEditorInterface *core, QWidget *w) const; + + void insertRow(int row); + void insertColumn(int column); + + bool simplify(const QRect &r, bool testOnly); + void removeFreeRow(int row); + void removeFreeColumn(int column); + + + // State of a cell in one dimension + enum DimensionCellState { + Free, + Spanned, // Item spans it + Occupied // Item bordering on it + }; + // Horiontal, Vertical pair of state + using CellState = std::pair; + using CellStates = QList; + + // Figure out states of a cell and return as a flat vector of + // [column1, column2,...] (address as row * columnCount + col) + static CellStates cellStates(const QList &rects, int numRows, int numColumns); + + QHash widgetItemMap; + QHash widgetAlignmentMap; + + int rowCount = 0; + int colCount = 0; + }; + + static inline bool needsSpacerItem(const GridLayoutState::CellState &cs) { + return cs.first == GridLayoutState::Free && cs.second == GridLayoutState::Free; + } + + static inline QDebug operator<<(QDebug str, const GridLayoutState &gs) + { + str << "GridLayoutState: " << gs.rowCount << " rows x " << gs.colCount + << " cols " << gs.widgetItemMap.size() << " items\n"; + + const auto wcend = gs.widgetItemMap.constEnd(); + for (auto it = gs.widgetItemMap.constBegin(); it != wcend; ++it) + str << "Item " << it.key() << it.value() << '\n'; + return str; + } + + GridLayoutState::CellStates GridLayoutState::cellStates(const QList &rects, int numRows, int numColumns) + { + CellStates rc = CellStates(numRows * numColumns, CellState(Free, Free)); + for (const auto &rect : rects) { + const int leftColumn = rect.x(); + const int topRow = rect.y(); + const int rightColumn = leftColumn + rect.width() - 1; + const int bottomRow = topRow + rect.height() - 1; + for (int r = topRow; r <= bottomRow; r++) + for (int c = leftColumn; c <= rightColumn; c++) { + const int flatIndex = r * numColumns + c; + // Bordering horizontally? + DimensionCellState &horizState = rc[flatIndex].first; + if (c == leftColumn || c == rightColumn) { + horizState = Occupied; + } else { + if (horizState < Spanned) + horizState = Spanned; + } + // Bordering vertically? + DimensionCellState &vertState = rc[flatIndex].second; + if (r == topRow || r == bottomRow) { + vertState = Occupied; + } else { + if (vertState < Spanned) + vertState = Spanned; + } + } + } + if (debugLayout) { + qDebug() << "GridLayoutState::cellStates: " << numRows << " x " << numColumns; + for (int r = 0; r < numRows; r++) + for (int c = 0; c < numColumns; c++) + qDebug() << " Row: " << r << " column: " << c << rc[r * numColumns + c]; + } + return rc; + } + + void GridLayoutState::fromLayout(QGridLayout *l) + { + rowCount = l->rowCount(); + colCount = l->columnCount(); + const int count = l->count(); + for (int i = 0; i < count; i++) { + QLayoutItem *item = l->itemAt(i); + if (!LayoutInfo::isEmptyItem(item)) { + widgetItemMap.insert(item->widget(), gridItemInfo(l, i)); + if (item->alignment()) + widgetAlignmentMap.insert(item->widget(), item->alignment()); + } + } + } + + void GridLayoutState::applyToLayout(const QDesignerFormEditorInterface *core, QWidget *w) const + { + QGridLayout *grid = qobject_cast(LayoutInfo::managedLayout(core, w)); + Q_ASSERT(grid); + if (debugLayout) + qDebug() << ">GridLayoutState::applyToLayout" << *this << *grid; + const bool shrink = grid->rowCount() > rowCount || grid->columnCount() > colCount; + // Build a map of existing items to rectangles via widget map, delete spacers + QHash itemMap; + while (grid->count()) { + QLayoutItem *item = grid->takeAt(0); + if (!LayoutInfo::isEmptyItem(item)) { + QWidget *itemWidget = item->widget(); + const auto it = widgetItemMap.constFind(itemWidget); + if (it == widgetItemMap.constEnd()) + qFatal("GridLayoutState::applyToLayout: Attempt to apply to a layout that has a widget '%s'/'%s' added after saving the state.", + itemWidget->metaObject()->className(), itemWidget->objectName().toUtf8().constData()); + itemMap.insert(item, it.value()); + } else { + delete item; + } + } + Q_ASSERT(itemMap.size() == widgetItemMap.size()); + // recreate if shrink + if (shrink) + grid = static_cast(recreateManagedLayout(core, w, grid)); + + // Add widgets items + for (auto it = itemMap.cbegin(), icend = itemMap.cend(); it != icend; ++it) { + const QRect info = it.value(); + const Qt::Alignment alignment = widgetAlignmentMap.value(it.key()->widget(), {}); + grid->addItem(it.key(), info.y(), info.x(), info.height(), info.width(), alignment); + } + // create spacers + const CellStates cs = cellStates(itemMap.values(), rowCount, colCount); + for (int r = 0; r < rowCount; r++) + for (int c = 0; c < colCount; c++) + if (needsSpacerItem(cs[r * colCount + c])) + grid->addItem(createGridSpacer(), r, c); + grid->activate(); + if (debugLayout) + qDebug() << "= row) { + it.value().translate(0, 1); + } else { //Over it: Does it span it -> widen? + const int rowSpan = it.value().height(); + if (rowSpan > 1 && topRow + rowSpan > row) + it.value().setHeight(rowSpan + 1); + } + } + } + + void GridLayoutState::insertColumn(int column) + { + colCount++; + for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) { + const int leftColumn = it.value().x(); + if (leftColumn >= column) { + it.value().translate(1, 0); + } else { // Left of it: Does it span it -> widen? + const int colSpan = it.value().width(); + if (colSpan > 1 && leftColumn + colSpan > column) + it.value().setWidth(colSpan + 1); + } + } + } + + // Simplify: Remove empty columns/rows and such ones that are only spanned (shrink + // spanning items). + // 'AB.C.' 'ABC' + // 'DDDD.' ==> 'DDD' + // 'EF.G.' 'EFG' + bool GridLayoutState::simplify(const QRect &r, bool testOnly) + { + // figure out free rows/columns. + QList occupiedRows(rowCount, false); + QList occupiedColumns(colCount, false); + // Mark everything outside restriction rectangle as occupied + const int restrictionLeftColumn = r.x(); + const int restrictionRightColumn = restrictionLeftColumn + r.width(); + const int restrictionTopRow = r.y(); + const int restrictionBottomRow = restrictionTopRow + r.height(); + if (restrictionLeftColumn > 0 || restrictionRightColumn < colCount || + restrictionTopRow > 0 || restrictionBottomRow < rowCount) { + for (int r = 0; r < rowCount; r++) + if (r < restrictionTopRow || r >= restrictionBottomRow) + occupiedRows[r] = true; + for (int c = 0; c < colCount; c++) + if (c < restrictionLeftColumn || c >= restrictionRightColumn) + occupiedColumns[c] = true; + } + // figure out free fields and tick off occupied rows and columns + const CellStates cs = cellStates(widgetItemMap.values(), rowCount, colCount); + for (int r = 0; r < rowCount; r++) + for (int c = 0; c < colCount; c++) { + const CellState &state = cs[r * colCount + c]; + if (state.first == Occupied) + occupiedColumns[c] = true; + if (state.second == Occupied) + occupiedRows[r] = true; + } + // Any free rows/columns? + if (occupiedRows.indexOf(false) == -1 && occupiedColumns.indexOf(false) == -1) + return false; + if (testOnly) + return true; + // remove rows + for (int r = rowCount - 1; r >= 0; r--) + if (!occupiedRows[r]) + removeFreeRow(r); + // remove columns + for (int c = colCount - 1; c >= 0; c--) + if (!occupiedColumns[c]) + removeFreeColumn(c); + return true; + } + + void GridLayoutState::removeFreeRow(int removeRow) + { + for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) { + const int r = it.value().y(); + Q_ASSERT(r != removeRow); // Free rows only + if (r < removeRow) { // Does the item span it? - shrink it + const int rowSpan = it.value().height(); + if (rowSpan > 1) { + const int bottomRow = r + rowSpan; + if (bottomRow > removeRow) + it.value().setHeight(rowSpan - 1); + } + } else + if (r > removeRow) // Item below it? - move. + it.value().translate(0, -1); + } + rowCount--; + } + + void GridLayoutState::removeFreeColumn(int removeColumn) + { + for (auto it = widgetItemMap.begin(), iend = widgetItemMap.end(); it != iend; ++it) { + const int c = it.value().x(); + Q_ASSERT(c != removeColumn); // Free columns only + if (c < removeColumn) { // Does the item span it? - shrink it + const int colSpan = it.value().width(); + if (colSpan > 1) { + const int rightColumn = c + colSpan; + if (rightColumn > removeColumn) + it.value().setWidth(colSpan - 1); + } + } else + if (c > removeColumn) // Item to the right of it? - move. + it.value().translate(-1, 0); + } + colCount--; + } + + // ---------------- GridLayoutHelper + class GridLayoutHelper : public LayoutHelper { + public: + GridLayoutHelper() = default; + + QRect itemInfo(QLayout *lt, int index) const override; + void insertWidget(QLayout *lt, const QRect &info, QWidget *w) override; + void removeWidget(QLayout *lt, QWidget *widget) override; + void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) override; + + void pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) override; + void popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) override; + + bool canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const override; + void simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea) override; + + static void insertRow(QGridLayout *grid, int row); + + private: + QStack m_states; + }; + + void GridLayoutHelper::insertRow(QGridLayout *grid, int row) + { + GridLayoutState state; + state.fromLayout(grid); + state.insertRow(row); + QDesignerFormWindowInterface *fw = QDesignerFormWindowInterface::findFormWindow(grid); + state.applyToLayout(fw->core(), grid->parentWidget()); + } + + QRect GridLayoutHelper::itemInfo(QLayout * lt, int index) const + { + QGridLayout *grid = qobject_cast(lt); + Q_ASSERT(grid); + return gridItemInfo(grid, index); + } + + void GridLayoutHelper::insertWidget(QLayout *lt, const QRect &info, QWidget *w) + { + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + QGridLayout *gridLayout = qobject_cast(lt); + Q_ASSERT(gridLayout); + // check if there are any items. Should be only spacers, else something is wrong + const int row = info.y(); + int column = info.x(); + int colSpan = info.width(); + int rowSpan = info.height(); + // If not empty: A multiselection was dropped on an empty item, insert row + // and spread items along new row + if (!removeEmptyCellsOnGrid(gridLayout, info)) { + int freeColumn = -1; + colSpan = rowSpan = 1; + // First look to the right for a free column + const int columnCount = gridLayout->columnCount(); + for (int c = column; c < columnCount; c++) { + const int idx = findGridItemAt(gridLayout, row, c); + if (idx != -1 && LayoutInfo::isEmptyItem(gridLayout->itemAt(idx))) { + freeColumn = c; + break; + } + } + if (freeColumn != -1) { + removeEmptyCellsOnGrid(gridLayout, QRect(freeColumn, row, 1, 1)); + column = freeColumn; + } else { + GridLayoutHelper::insertRow(gridLayout, row); + column = 0; + } + } + gridLayout->addWidget(w, row , column, rowSpan, colSpan); + } + + void GridLayoutHelper::removeWidget(QLayout *lt, QWidget *widget) + { + QGridLayout *gridLayout = qobject_cast(lt); + Q_ASSERT(gridLayout); + const int index = gridLayout->indexOf(widget); + if (index == -1) { + qWarning() << "GridLayoutHelper::removeWidget : Attempt to remove " << widget << " which is not in the layout."; + return; + } + // delete old item and pad with by spacer items + int row, column, rowspan, colspan; + gridLayout->getItemPosition(index, &row, &column, &rowspan, &colspan); + delete gridLayout->takeAt(index); + const int rightColumn = column + colspan; + const int bottomRow = row + rowspan; + for (int c = column; c < rightColumn; c++) + for (int r = row; r < bottomRow; r++) + gridLayout->addItem(createGridSpacer(), r, c); + } + + void GridLayoutHelper::replaceWidget(QLayout *lt, QWidget *before, QWidget *after) + { + bool ok = false; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + if (QGridLayout *gridLayout = qobject_cast(lt)) { + const int index = gridLayout->indexOf(before); + if (index != -1) { + int row, column, rowSpan, columnSpan; + gridLayout->getItemPosition (index, &row, &column, &rowSpan, &columnSpan); + const bool visible = before->isVisible(); + delete gridLayout->takeAt(index); + if (visible) + before->hide(); + before->setParent(nullptr); + gridLayout->addWidget(after, row, column, rowSpan, columnSpan); + ok = true; + } + } + if (!ok) + qWarning() << "GridLayoutHelper::replaceWidget : Unable to replace " << before << " by " << after << " in " << lt; + } + + void GridLayoutHelper::pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) + { + QGridLayout *gridLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(gridLayout); + GridLayoutState gs; + gs.fromLayout(gridLayout); + m_states.push(gs); + } + + void GridLayoutHelper::popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) + { + Q_ASSERT(!m_states.isEmpty()); + const GridLayoutState state = m_states.pop(); + state.applyToLayout(core, widgetWithManagedLayout); + } + + bool GridLayoutHelper::canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const + { + QGridLayout *gridLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(gridLayout); + GridLayoutState gs; + gs.fromLayout(gridLayout); + return gs.simplify(restrictionArea, true); + } + + void GridLayoutHelper::simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea) + { + QGridLayout *gridLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(gridLayout); + if (debugLayout) + qDebug() << ">GridLayoutHelper::simplify" << *gridLayout; + GridLayoutState gs; + gs.fromLayout(gridLayout); + if (gs.simplify(restrictionArea, false)) + gs.applyToLayout(core, widgetWithManagedLayout); + if (debugLayout) + qDebug() << ">; + + FormLayoutHelper() = default; + + QRect itemInfo(QLayout *lt, int index) const override; + void insertWidget(QLayout *lt, const QRect &info, QWidget *w) override; + void removeWidget(QLayout *lt, QWidget *widget) override; + void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) override; + + void pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) override; + void popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) override; + + bool canSimplify(const QDesignerFormEditorInterface *core, const QWidget *, const QRect &) const override; + void simplify(const QDesignerFormEditorInterface *, QWidget *, const QRect &) override; + + private: + static FormLayoutState state(const QFormLayout *lt); + + QStack m_states; + }; + + QRect FormLayoutHelper::itemInfo(QLayout * lt, int index) const + { + QFormLayout *form = qobject_cast(lt); + Q_ASSERT(form); + int row, column, colspan; + getFormLayoutItemPosition(form, index, &row, &column, nullptr, &colspan); + return QRect(column, row, colspan, 1); + } + + void FormLayoutHelper::insertWidget(QLayout *lt, const QRect &info, QWidget *w) + { + if (debugLayout) + qDebug() << "FormLayoutHelper::insertWidget:" << w << info; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + QFormLayout *formLayout = qobject_cast(lt); + Q_ASSERT(formLayout); + // check if there are any nonspacer items? (Drop on 3rd column or drop of a multiselection + // on an empty item. As the Form layout does not have insert semantics; we need to manually insert a row + const bool insert = !removeEmptyCellsOnGrid(formLayout, info); + formLayoutAddWidget(formLayout, w, info, insert); + QLayoutSupport::createEmptyCells(formLayout); + } + + void FormLayoutHelper::removeWidget(QLayout *lt, QWidget *widget) + { + QFormLayout *formLayout = qobject_cast(lt); + Q_ASSERT(formLayout); + const int index = formLayout->indexOf(widget); + if (index == -1) { + qWarning() << "FormLayoutHelper::removeWidget : Attempt to remove " << widget << " which is not in the layout."; + return; + } + // delete old item and pad with by spacer items + int row, column, colspan; + getFormLayoutItemPosition(formLayout, index, &row, &column, nullptr, &colspan); + if (debugLayout) + qDebug() << "FormLayoutHelper::removeWidget: #" << index << widget << " at " << row << column << colspan; + delete formLayout->takeAt(index); + if (colspan > 1 || column == 0) + formLayout->setItem(row, QFormLayout::LabelRole, createFormSpacer()); + if (colspan > 1 || column == 1) + formLayout->setItem(row, QFormLayout::FieldRole, createFormSpacer()); + } + + void FormLayoutHelper::replaceWidget(QLayout *lt, QWidget *before, QWidget *after) + { + bool ok = false; + QDesignerWidgetItemInstaller wii; // Make sure we use QDesignerWidgetItem. + if (QFormLayout *formLayout = qobject_cast(lt)) { + const int index = formLayout->indexOf(before); + if (index != -1) { + int row; + QFormLayout::ItemRole role; + formLayout->getItemPosition (index, &row, &role); + const bool visible = before->isVisible(); + delete formLayout->takeAt(index); + if (visible) + before->hide(); + before->setParent(nullptr); + formLayout->setWidget(row, role, after); + ok = true; + } + } + if (!ok) + qWarning() << "FormLayoutHelper::replaceWidget : Unable to replace " << before << " by " << after << " in " << lt; + } + + FormLayoutHelper::FormLayoutState FormLayoutHelper::state(const QFormLayout *lt) + { + const int rowCount = lt->rowCount(); + if (rowCount == 0) + return FormLayoutState(); + FormLayoutState rc(rowCount, {nullptr, nullptr}); + const int count = lt->count(); + int row, column, colspan; + for (int i = 0; i < count; i++) { + QLayoutItem *item = lt->itemAt(i); + if (!LayoutInfo::isEmptyItem(item)) { + QWidget *w = item->widget(); + Q_ASSERT(w); + getFormLayoutItemPosition(lt, i, &row, &column, nullptr, &colspan); + if (colspan > 1 || column == 0) + rc[row].first = w; + if (colspan > 1 || column == 1) + rc[row].second = w; + } + } + if (debugLayout) { + qDebug() << "FormLayoutHelper::state: " << rowCount; + for (int r = 0; r < rowCount; r++) + qDebug() << " Row: " << r << rc[r].first << rc[r].second; + } + return rc; + } + + void FormLayoutHelper::pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) + { + QFormLayout *formLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(formLayout); + m_states.push(state(formLayout)); + } + + void FormLayoutHelper::popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) + { + QFormLayout *formLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(!m_states.isEmpty() && formLayout); + + const FormLayoutState storedState = m_states.pop(); + const FormLayoutState currentState = state(formLayout); + if (currentState == storedState) + return; + const int rowCount = storedState.size(); + // clear out, shrink if required, but maintain items via map, pad spacers + const BoxLayoutHelper::LayoutItemVector items = BoxLayoutHelper::disassembleLayout(formLayout); + if (rowCount < formLayout->rowCount()) + formLayout = static_cast(recreateManagedLayout(core, widgetWithManagedLayout, formLayout )); + for (int r = 0; r < rowCount; r++) { + QWidget *widgets[FormLayoutColumns] = { storedState[r].first, storedState[r].second }; + const bool spanning = widgets[0] != nullptr && widgets[0] == widgets[1]; + if (spanning) { + formLayout->setWidget(r, QFormLayout::SpanningRole, widgets[0]); + } else { + for (int c = 0; c < FormLayoutColumns; c++) { + const QFormLayout::ItemRole role = c == 0 ? QFormLayout::LabelRole : QFormLayout::FieldRole; + if (widgets[c] && BoxLayoutHelper::findItemOfWidget(items, widgets[c])) { + formLayout->setWidget(r, role, widgets[c]); + } else { + formLayout->setItem(r, role, createFormSpacer()); + } + } + } + } + } + + bool FormLayoutHelper::canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const + { + const QFormLayout *formLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(formLayout); + return canSimplifyFormLayout(formLayout, restrictionArea); + } + + void FormLayoutHelper::simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea) + { + using LayoutItemPair = std::pair; + using LayoutItemPairs = QList; + + QFormLayout *formLayout = qobject_cast(LayoutInfo::managedLayout(core, widgetWithManagedLayout)); + Q_ASSERT(formLayout); + if (debugLayout) + qDebug() << "FormLayoutHelper::simplify"; + // Transform into vector of item pairs + const int rowCount = formLayout->rowCount(); + LayoutItemPairs pairs(rowCount, LayoutItemPair(0, 0)); + for (int i = formLayout->count() - 1; i >= 0; i--) { + int row, col,colspan; + getFormLayoutItemPosition(formLayout, i, &row, &col, nullptr, &colspan); + if (colspan > 1) { + pairs[row].first = pairs[row].second = formLayout->takeAt(i); + } else { + if (col == 0) + pairs[row].first = formLayout->takeAt(i); + else + pairs[row].second = formLayout->takeAt(i); + } + } + // Weed out empty ones + const int bottomCheckRow = qMin(rowCount, restrictionArea.y() + restrictionArea.height()); + for (int r = bottomCheckRow - 1; r >= restrictionArea.y(); r--) + if (LayoutInfo::isEmptyItem(pairs[r].first) && LayoutInfo::isEmptyItem(pairs[r].second)) { + delete pairs[r].first; + delete pairs[r].second; + pairs.remove(r); + } + const int simpleRowCount = pairs.size(); + if (simpleRowCount < rowCount) + formLayout = static_cast(recreateManagedLayout(core, widgetWithManagedLayout, formLayout)); + // repopulate + for (int r = 0; r < simpleRowCount; r++) { + const bool spanning = pairs[r].first == pairs[r].second; + if (spanning) { + formLayout->setItem(r, QFormLayout::SpanningRole, pairs[r].first); + } else { + formLayout->setItem(r, QFormLayout::LabelRole, pairs[r].first); + formLayout->setItem(r, QFormLayout::FieldRole, pairs[r].second); + } + } + } + +LayoutHelper *LayoutHelper::createLayoutHelper(int type) +{ + LayoutHelper *rc = nullptr; + switch (type) { + case LayoutInfo::HBox: + rc = new BoxLayoutHelper(Qt::Horizontal); + break; + case LayoutInfo::VBox: + rc = new BoxLayoutHelper(Qt::Vertical); + break; + case LayoutInfo::Grid: + rc = new GridLayoutHelper; + break; + case LayoutInfo::Form: + return new FormLayoutHelper; + default: + break; + } + Q_ASSERT(rc); + return rc; +} + +// ---- QLayoutSupport (LayoutDecorationExtension) +QLayoutSupport::QLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, LayoutHelper *helper, QObject *parent) : + QObject(parent), + m_formWindow(formWindow), + m_helper(helper), + m_widget(widget), + m_currentIndex(-1), + m_currentInsertMode(QDesignerLayoutDecorationExtension::InsertWidgetMode) +{ +} + +QLayout * QLayoutSupport::layout() const +{ + return LayoutInfo::managedLayout(m_formWindow->core(), m_widget); +} + +void QLayoutSupport::hideIndicator(Indicator i) +{ + if (m_indicators[i]) + m_indicators[i]->hide(); +} + +void QLayoutSupport::showIndicator(Indicator i, const QRect &geometry, const QPalette &p) +{ + if (!m_indicators[i]) + m_indicators[i] = new qdesigner_internal::InvisibleWidget(m_widget); + QWidget *indicator = m_indicators[i]; + indicator->setAutoFillBackground(true); + indicator->setPalette(p); + indicator->setGeometry(geometry); + indicator->show(); + indicator->raise(); +} + +QLayoutSupport::~QLayoutSupport() +{ + delete m_helper; + for (const QPointer &w : m_indicators) { + if (!w.isNull()) + w->deleteLater(); + } +} + +QGridLayout * QLayoutSupport::gridLayout() const +{ + return qobject_cast(LayoutInfo::managedLayout(m_formWindow->core(), m_widget)); +} + +QRect QLayoutSupport::itemInfo(int index) const +{ + return m_helper->itemInfo(LayoutInfo::managedLayout(m_formWindow->core(), m_widget), index); +} + +void QLayoutSupport::setInsertMode(InsertMode im) +{ + m_currentInsertMode = im; +} + +void QLayoutSupport::setCurrentCell(const std::pair &cell) +{ + m_currentCell = cell; +} + +void QLayoutSupport::adjustIndicator(const QPoint &pos, int index) +{ + if (index == -1) { // first item goes anywhere + hideIndicator(LeftIndicator); + hideIndicator(TopIndicator); + hideIndicator(RightIndicator); + hideIndicator(BottomIndicator); + return; + } + m_currentIndex = index; + m_currentInsertMode = QDesignerLayoutDecorationExtension::InsertWidgetMode; + + QLayoutItem *item = layout()->itemAt(index); + const QRect g = extendedGeometry(index); + // ### cleanup + if (LayoutInfo::isEmptyItem(item)) { + // Empty grid/form cell. Draw a rectangle + QPalette redPalette; + redPalette.setColor(QPalette::Window, Qt::red); + + showIndicator(LeftIndicator, QRect(g.x(), g.y(), indicatorSize, g.height()), redPalette); + showIndicator(TopIndicator, QRect(g.x(), g.y(), g.width(), indicatorSize), redPalette); + showIndicator(RightIndicator, QRect(g.right(), g.y(), indicatorSize, g.height()), redPalette); + showIndicator(BottomIndicator, QRect(g.x(), g.bottom(), g.width(), indicatorSize), redPalette); + setCurrentCellFromIndicatorOnEmptyCell(m_currentIndex); + } else { + // Append/Insert. Draw a bar left/right or above/below + QPalette bluePalette; + bluePalette.setColor(QPalette::Window, Qt::blue); + hideIndicator(LeftIndicator); + hideIndicator(TopIndicator); + + const int fromRight = g.right() - pos.x(); + const int fromBottom = g.bottom() - pos.y(); + + const int fromLeft = pos.x() - g.x(); + const int fromTop = pos.y() - g.y(); + + const int fromLeftRight = qMin(fromRight, fromLeft ); + const int fromBottomTop = qMin(fromBottom, fromTop); + + const Qt::Orientation indicatorOrientation = fromLeftRight < fromBottomTop ? Qt::Vertical : Qt::Horizontal; + + if (supportsIndicatorOrientation(indicatorOrientation)) { + const QRect r(layout()->geometry().topLeft(), layout()->parentWidget()->size()); + switch (indicatorOrientation) { + case Qt::Vertical: { + hideIndicator(BottomIndicator); + const bool closeToLeft = fromLeftRight == fromLeft; + showIndicator(RightIndicator, QRect(closeToLeft ? g.x() : g.right() + 1 - indicatorSize, 0, indicatorSize, r.height()), bluePalette); + + const QWidget *parent = layout()->parentWidget(); + const bool leftToRight = Qt::LeftToRight == (parent ? parent->layoutDirection() : QApplication::layoutDirection()); + const int incr = leftToRight == closeToLeft ? 0 : +1; + setCurrentCellFromIndicator(indicatorOrientation, m_currentIndex, incr); + } + break; + case Qt::Horizontal: { + hideIndicator(RightIndicator); + const bool closeToTop = fromBottomTop == fromTop; + showIndicator(BottomIndicator, QRect(r.x(), closeToTop ? g.y() : g.bottom() + 1 - indicatorSize, r.width(), indicatorSize), bluePalette); + + const int incr = closeToTop ? 0 : +1; + setCurrentCellFromIndicator(indicatorOrientation, m_currentIndex, incr); + } + break; + } + } else { + hideIndicator(RightIndicator); + hideIndicator(BottomIndicator); + } // can handle indicatorOrientation + } +} + +int QLayoutSupport::indexOf(QLayoutItem *i) const +{ + const QLayout *lt = layout(); + if (!lt) + return -1; + + int index = 0; + + while (QLayoutItem *item = lt->itemAt(index)) { + if (item == i) + return index; + + ++index; + } + + return -1; +} + +int QLayoutSupport::indexOf(QWidget *widget) const +{ + const QLayout *lt = layout(); + if (!lt) + return -1; + + int index = 0; + while (QLayoutItem *item = lt->itemAt(index)) { + if (item->widget() == widget) + return index; + + ++index; + } + + return -1; +} + +QWidgetList QLayoutSupport::widgets(QLayout *layout) const +{ + if (!layout) + return QWidgetList(); + + QWidgetList lst; + int index = 0; + while (QLayoutItem *item = layout->itemAt(index)) { + ++index; + + QWidget *widget = item->widget(); + if (widget && formWindow()->isManaged(widget)) + lst.append(widget); + } + + return lst; +} + +int QLayoutSupport::findItemAt(QGridLayout *gridLayout, int at_row, int at_column) +{ + return findGridItemAt(gridLayout, at_row, at_column); +} + +// Quick check whether simplify should be enabled for grids. May return false positives. +// Note: Calculating the occupied area does not work as spanning items may also be simplified. + +bool QLayoutSupport::canSimplifyQuickCheck(const QGridLayout *gl) +{ + if (!gl) + return false; + const int colCount = gl->columnCount(); + const int rowCount = gl->rowCount(); + if (colCount < 2 || rowCount < 2) + return false; + // try to find a spacer. + const int count = gl->count(); + for (int index = 0; index < count; index++) + if (LayoutInfo::isEmptyItem(gl->itemAt(index))) + return true; + return false; +} + +bool QLayoutSupport::canSimplifyQuickCheck(const QFormLayout *fl) +{ + return canSimplifyFormLayout(fl, QRect(QPoint(0, 0), QSize(32767, 32767))); +} + +// remove dummy spacers +bool QLayoutSupport::removeEmptyCells(QGridLayout *grid, const QRect &area) +{ + return removeEmptyCellsOnGrid(grid, area); +} + +void QLayoutSupport::createEmptyCells(QGridLayout *gridLayout) +{ + Q_ASSERT(gridLayout); + GridLayoutState gs; + gs.fromLayout(gridLayout); + + const GridLayoutState::CellStates cs = GridLayoutState::cellStates(gs.widgetItemMap.values(), gs.rowCount, gs.colCount); + for (int c = 0; c < gs.colCount; c++) + for (int r = 0; r < gs.rowCount; r++) + if (needsSpacerItem(cs[r * gs.colCount + c])) { + const int existingItemIndex = findItemAt(gridLayout, r, c); + if (existingItemIndex == -1) + gridLayout->addItem(createGridSpacer(), r, c); + } +} + +bool QLayoutSupport::removeEmptyCells(QFormLayout *formLayout, const QRect &area) +{ + return removeEmptyCellsOnGrid(formLayout, area); +} + +void QLayoutSupport::createEmptyCells(QFormLayout *formLayout) +{ + // No spanning items here.. + if (const int rowCount = formLayout->rowCount()) + for (int c = 0; c < FormLayoutColumns; c++) + for (int r = 0; r < rowCount; r++) + if (findGridItemAt(formLayout, r, c) == -1) + formLayout->setItem(r, c == 0 ? QFormLayout::LabelRole : QFormLayout::FieldRole, createFormSpacer()); +} + +int QLayoutSupport::findItemAt(const QPoint &pos) const +{ + if (!layout()) + return -1; + + const QLayout *lt = layout(); + const int count = lt->count(); + + if (count == 0) + return -1; + + int best = -1; + int bestIndex = -1; + + for (int index = 0; index < count; index++) { + QLayoutItem *item = lt->itemAt(index); + bool visible = true; + // When dragging widgets within layout, the source widget is invisible and must not be hit + if (const QWidget *w = item->widget()) + visible = w->isVisible(); + if (visible) { + const QRect g = item->geometry(); + + const int dist = (g.center() - pos).manhattanLength(); + if (best == -1 || dist < best) { + best = dist; + bestIndex = index; + } + } + } + return bestIndex; +} + +// ------------ QBoxLayoutSupport (LayoutDecorationExtension) +namespace { +class QBoxLayoutSupport: public QLayoutSupport +{ +public: + QBoxLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, Qt::Orientation orientation, QObject *parent = nullptr); + + void insertWidget(QWidget *widget, const std::pair &cell) override; + void removeWidget(QWidget *widget) override; + void simplify() override {} + void insertRow(int /*row*/) override {} + void insertColumn(int /*column*/) override {} + + int findItemAt(int /*at_row*/, int /*at_column*/) const override { return -1; } + using QLayoutSupport::findItemAt; + +private: + void setCurrentCellFromIndicatorOnEmptyCell(int index) override; + void setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) override; + bool supportsIndicatorOrientation(Qt::Orientation indicatorOrientation) const override; + QRect extendedGeometry(int index) const override; + + const Qt::Orientation m_orientation; +}; + +void QBoxLayoutSupport::removeWidget(QWidget *widget) +{ + QLayout *lt = layout(); + const int index = lt->indexOf(widget); + // Adjust the current cell in case a widget was dragged within the same layout to a position + // of higher index, which happens as follows: + // Drag start: The widget is hidden + // Drop: Current cell is stored, widget is removed and re-added, causing an index offset that needs to be compensated + std::pair currCell = currentCell(); + switch (m_orientation) { + case Qt::Horizontal: + if (currCell.second > 0 && index < currCell.second ) { + currCell.second--; + setCurrentCell(currCell); + } + break; + case Qt::Vertical: + if (currCell.first > 0 && index < currCell.first) { + currCell.first--; + setCurrentCell(currCell); + } + break; + } + helper()->removeWidget(lt, widget); +} + +QBoxLayoutSupport::QBoxLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, Qt::Orientation orientation, QObject *parent) : + QLayoutSupport(formWindow, widget, new BoxLayoutHelper(orientation), parent), + m_orientation(orientation) +{ +} + +void QBoxLayoutSupport::setCurrentCellFromIndicatorOnEmptyCell(int index) +{ + qDebug() << "QBoxLayoutSupport::setCurrentCellFromIndicatorOnEmptyCell(): Warning: found a fake spacer inside a vbox layout at " << index; + setCurrentCell({0, 0}); +} + +void QBoxLayoutSupport::insertWidget(QWidget *widget, const std::pair &cell) +{ + switch (m_orientation) { + case Qt::Horizontal: + helper()->insertWidget(layout(), QRect(cell.second, 0, 1, 1), widget); + break; + case Qt::Vertical: + helper()->insertWidget(layout(), QRect(0, cell.first, 1, 1), widget); + break; + } +} + +void QBoxLayoutSupport::setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) +{ + if (m_orientation == Qt::Horizontal && indicatorOrientation == Qt::Vertical) + setCurrentCell({0, index + increment}); + else if (m_orientation == Qt::Vertical && indicatorOrientation == Qt::Horizontal) + setCurrentCell({index + increment, 0}); +} + +bool QBoxLayoutSupport::supportsIndicatorOrientation(Qt::Orientation indicatorOrientation) const +{ + return m_orientation != indicatorOrientation; +} + +QRect QBoxLayoutSupport::extendedGeometry(int index) const +{ + QLayoutItem *item = layout()->itemAt(index); + // start off with item geometry + QRect g = item->geometry(); + + const QRect info = itemInfo(index); + + // On left border: extend to widget border + if (info.x() == 0) { + QPoint topLeft = g.topLeft(); + topLeft.rx() = layout()->geometry().left(); + g.setTopLeft(topLeft); + } + + // On top border: extend to widget border + if (info.y() == 0) { + QPoint topLeft = g.topLeft(); + topLeft.ry() = layout()->geometry().top(); + g.setTopLeft(topLeft); + } + + // is this the last item? + const QBoxLayout *box = static_cast(layout()); + if (index < box->count() -1) + return g; // Nope. + + // extend to widget border + QPoint bottomRight = g.bottomRight(); + switch (m_orientation) { + case Qt::Vertical: + bottomRight.ry() = layout()->geometry().bottom(); + break; + case Qt::Horizontal: + bottomRight.rx() = layout()->geometry().right(); + break; + } + g.setBottomRight(bottomRight); + return g; +} + +// -------------- Base class for QGridLayout-like support classes (LayoutDecorationExtension) +template +class GridLikeLayoutSupportBase: public QLayoutSupport +{ +public: + + GridLikeLayoutSupportBase(QDesignerFormWindowInterface *formWindow, QWidget *widget, LayoutHelper *helper, QObject *parent = nullptr) : + QLayoutSupport(formWindow, widget, helper, parent) {} + + void insertWidget(QWidget *widget, const std::pair &cell) override; + void removeWidget(QWidget *widget) override { helper()->removeWidget(layout(), widget); } + int findItemAt(int row, int column) const override; + using QLayoutSupport::findItemAt; + +protected: + GridLikeLayout *gridLikeLayout() const { + return qobject_cast(LayoutInfo::managedLayout(formWindow()->core(), widget())); + } + +private: + + void setCurrentCellFromIndicatorOnEmptyCell(int index) override; + void setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) override; + bool supportsIndicatorOrientation(Qt::Orientation) const override { return true; } + + QRect extendedGeometry(int index) const override; + + // Overwrite to check the insertion position (if there are limits) + virtual void checkCellForInsertion(int * /*row*/, int * /*col*/) const {} +}; + +template +void GridLikeLayoutSupportBase::setCurrentCellFromIndicatorOnEmptyCell(int index) +{ + GridLikeLayout *grid = gridLikeLayout(); + Q_ASSERT(grid); + + setInsertMode(InsertWidgetMode); + int row, column, rowspan, colspan; + + getGridItemPosition(grid, index, &row, &column, &rowspan, &colspan); + setCurrentCell({row, column}); +} + +template +void GridLikeLayoutSupportBase::setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) { + const QRect info = itemInfo(index); + switch (indicatorOrientation) { + case Qt::Vertical: { + setInsertMode(InsertColumnMode); + int row = info.top(); + int column = increment ? info.right() + 1 : info.left(); + checkCellForInsertion(&row, &column); + setCurrentCell({row, column}); + } + break; + case Qt::Horizontal: { + setInsertMode(InsertRowMode); + int row = increment ? info.bottom() + 1 : info.top(); + int column = info.left(); + checkCellForInsertion(&row, &column); + setCurrentCell({row, column}); + } + break; + } +} + +template +void GridLikeLayoutSupportBase::insertWidget(QWidget *widget, const std::pair &cell) +{ + helper()->insertWidget(layout(), QRect(cell.second, cell.first, 1, 1), widget); +} + +template +int GridLikeLayoutSupportBase::findItemAt(int at_row, int at_column) const +{ + GridLikeLayout *grid = gridLikeLayout(); + Q_ASSERT(grid); + return findGridItemAt(grid, at_row, at_column); +} + +template +QRect GridLikeLayoutSupportBase::extendedGeometry(int index) const +{ + QLayoutItem *item = layout()->itemAt(index); + // start off with item geometry + QRect g = item->geometry(); + + const QRect info = itemInfo(index); + + // On left border: extend to widget border + if (info.x() == 0) { + QPoint topLeft = g.topLeft(); + topLeft.rx() = layout()->geometry().left(); + g.setTopLeft(topLeft); + } + + // On top border: extend to widget border + if (info.y() == 0) { + QPoint topLeft = g.topLeft(); + topLeft.ry() = layout()->geometry().top(); + g.setTopLeft(topLeft); + } + const GridLikeLayout *grid = gridLikeLayout(); + Q_ASSERT(grid); + + // extend to widget border + QPoint bottomRight = g.bottomRight(); + if (gridRowCount(grid) == info.y()) + bottomRight.ry() = layout()->geometry().bottom(); + if (gridColumnCount(grid) == info.x()) + bottomRight.rx() = layout()->geometry().right(); + g.setBottomRight(bottomRight); + return g; +} + +// -------------- QGridLayoutSupport (LayoutDecorationExtension) +class QGridLayoutSupport: public GridLikeLayoutSupportBase +{ +public: + + QGridLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent = nullptr); + + void simplify() override; + void insertRow(int row) override; + void insertColumn(int column) override; + +private: +}; + +QGridLayoutSupport::QGridLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) : + GridLikeLayoutSupportBase(formWindow, widget, new GridLayoutHelper, parent) +{ +} + +void QGridLayoutSupport::insertRow(int row) +{ + QGridLayout *grid = gridLayout(); + Q_ASSERT(grid); + GridLayoutHelper::insertRow(grid, row); +} + +void QGridLayoutSupport::insertColumn(int column) +{ + QGridLayout *grid = gridLayout(); + Q_ASSERT(grid); + GridLayoutState state; + state.fromLayout(grid); + state.insertColumn(column); + state.applyToLayout(formWindow()->core(), widget()); +} + +void QGridLayoutSupport::simplify() +{ + QGridLayout *grid = gridLayout(); + Q_ASSERT(grid); + GridLayoutState state; + state.fromLayout(grid); + + const QRect fullArea = QRect(0, 0, state.colCount, state.rowCount); + if (state.simplify(fullArea, false)) + state.applyToLayout(formWindow()->core(), widget()); +} + +// -------------- QFormLayoutSupport (LayoutDecorationExtension) +class QFormLayoutSupport: public GridLikeLayoutSupportBase +{ +public: + QFormLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent = nullptr); + + void simplify() override {} + void insertRow(int /*row*/) override {} + void insertColumn(int /*column*/) override {} + +private: + void checkCellForInsertion(int * row, int *col) const override; +}; + +QFormLayoutSupport::QFormLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) : + GridLikeLayoutSupportBase(formWindow, widget, new FormLayoutHelper, parent) +{ +} + +void QFormLayoutSupport::checkCellForInsertion(int *row, int *col) const +{ + if (*col >= FormLayoutColumns) { // Clamp to 2 columns + *col = 1; + (*row)++; + } +} +} // anonymous namespace + +QLayoutSupport *QLayoutSupport::createLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent) +{ + const QLayout *layout = LayoutInfo::managedLayout(formWindow->core(), widget); + Q_ASSERT(layout); + QLayoutSupport *rc = nullptr; + switch (LayoutInfo::layoutType(formWindow->core(), layout)) { + case LayoutInfo::HBox: + rc = new QBoxLayoutSupport(formWindow, widget, Qt::Horizontal, parent); + break; + case LayoutInfo::VBox: + rc = new QBoxLayoutSupport(formWindow, widget, Qt::Vertical, parent); + break; + case LayoutInfo::Grid: + rc = new QGridLayoutSupport(formWindow, widget, parent); + break; + case LayoutInfo::Form: + rc = new QFormLayoutSupport(formWindow, widget, parent); + break; + default: + break; + } + Q_ASSERT(rc); + return rc; +} +} // namespace qdesigner_internal + +// -------------- QLayoutWidget +QLayoutWidget::QLayoutWidget(QDesignerFormWindowInterface *formWindow, QWidget *parent) + : QWidget(parent), m_formWindow(formWindow), + m_leftMargin(0), m_topMargin(0), m_rightMargin(0), m_bottomMargin(0) +{ +} + +void QLayoutWidget::paintEvent(QPaintEvent*) +{ + if (m_formWindow->currentTool() != 0) + return; + + // only draw red borders if we're editting widgets + + QPainter p(this); + + QMap > excludedRowsForColumn; + QMap > excludedColumnsForRow; + + QLayout *lt = layout(); + QGridLayout *grid = qobject_cast(lt); + if (lt) { + if (const int count = lt->count()) { + p.setPen(QPen(QColor(255, 0, 0, 35), 1)); + for (int i = 0; i < count; i++) { + QLayoutItem *item = lt->itemAt(i); + if (grid) { + int row, column, rowSpan, columnSpan; + grid->getItemPosition(i, &row, &column, &rowSpan, &columnSpan); + QMap rows; + QMap columns; + for (int i = rowSpan; i > 1; i--) + rows[row + i - 2] = true; + for (int i = columnSpan; i > 1; i--) + columns[column + i - 2] = true; + + while (rowSpan > 0) { + excludedColumnsForRow[row + rowSpan - 1].insert(columns); + rowSpan--; + } + while (columnSpan > 0) { + excludedRowsForColumn[column + columnSpan - 1].insert(rows); + columnSpan--; + } + } + if (item->spacerItem()) { + const QRect geometry = item->geometry(); + if (!geometry.isNull()) + p.drawRect(geometry.adjusted(1, 1, -2, -2)); + } + } + } + } + if (grid) { + p.setPen(QPen(QColor(0, 0x80, 0, 0x80), 1)); + const int rowCount = grid->rowCount(); + const int columnCount = grid->columnCount(); + for (int i = 0; i < rowCount; i++) { + for (int j = 0; j < columnCount; j++) { + const QRect cellRect = grid->cellRect(i, j); + if (j < columnCount - 1 && !excludedColumnsForRow.value(i).value(j, false)) { + const double y0 = (i == 0) + ? 0 : (grid->cellRect(i - 1, j).bottom() + cellRect.top()) / 2.0; + const double y1 = (i == rowCount - 1) + ? height() - 1 : (cellRect.bottom() + grid->cellRect(i + 1, j).top()) / 2.0; + const double x = (cellRect.right() + grid->cellRect(i, j + 1).left()) / 2.0; + p.drawLine(QPointF(x, y0), QPointF(x, y1)); + } + if (i < rowCount - 1 && !excludedRowsForColumn.value(j).value(i, false)) { + const double x0 = (j == 0) + ? 0 : (grid->cellRect(i, j - 1).right() + cellRect.left()) / 2.0; + const double x1 = (j == columnCount - 1) + ? width() - 1 : (cellRect.right() + grid->cellRect(i, j + 1).left()) / 2.0; + const double y = (cellRect.bottom() + grid->cellRect(i + 1, j).top()) / 2.0; + p.drawLine(QPointF(x0, y), QPointF(x1, y)); + } + } + } + } + p.setPen(QPen(QColor(255, 0, 0, 128), 1)); + p.drawRect(0, 0, width() - 1, height() - 1); +} + +bool QLayoutWidget::event(QEvent *e) +{ + switch (e->type()) { + case QEvent::LayoutRequest: { + (void) QWidget::event(e); + // Magic: We are layouted, but the parent is not.. + if (layout() && qdesigner_internal::LayoutInfo::layoutType(formWindow()->core(), parentWidget()) == qdesigner_internal::LayoutInfo::NoLayout) { + resize(layout()->totalMinimumSize().expandedTo(size())); + } + + update(); + + return true; + } + + default: + break; + } + + return QWidget::event(e); +} + +int QLayoutWidget::layoutLeftMargin() const +{ + if (m_leftMargin < 0 && layout()) { + int margin; + layout()->getContentsMargins(&margin, nullptr, nullptr, nullptr); + return margin; + } + return m_leftMargin; +} + +void QLayoutWidget::setLayoutLeftMargin(int layoutMargin) +{ + m_leftMargin = layoutMargin; + if (layout()) { + int newMargin = m_leftMargin; + if (newMargin >= 0 && newMargin < ShiftValue) + newMargin = ShiftValue; + int left, top, right, bottom; + layout()->getContentsMargins(&left, &top, &right, &bottom); + layout()->setContentsMargins(newMargin, top, right, bottom); + } +} + +int QLayoutWidget::layoutTopMargin() const +{ + if (m_topMargin < 0 && layout()) { + int margin; + layout()->getContentsMargins(nullptr, &margin, nullptr, nullptr); + return margin; + } + return m_topMargin; +} + +void QLayoutWidget::setLayoutTopMargin(int layoutMargin) +{ + m_topMargin = layoutMargin; + if (layout()) { + int newMargin = m_topMargin; + if (newMargin >= 0 && newMargin < ShiftValue) + newMargin = ShiftValue; + int left, top, right, bottom; + layout()->getContentsMargins(&left, &top, &right, &bottom); + layout()->setContentsMargins(left, newMargin, right, bottom); + } +} + +int QLayoutWidget::layoutRightMargin() const +{ + if (m_rightMargin < 0 && layout()) { + int margin; + layout()->getContentsMargins(nullptr, nullptr, &margin, nullptr); + return margin; + } + return m_rightMargin; +} + +void QLayoutWidget::setLayoutRightMargin(int layoutMargin) +{ + m_rightMargin = layoutMargin; + if (layout()) { + int newMargin = m_rightMargin; + if (newMargin >= 0 && newMargin < ShiftValue) + newMargin = ShiftValue; + int left, top, right, bottom; + layout()->getContentsMargins(&left, &top, &right, &bottom); + layout()->setContentsMargins(left, top, newMargin, bottom); + } +} + +int QLayoutWidget::layoutBottomMargin() const +{ + if (m_bottomMargin < 0 && layout()) { + int margin; + layout()->getContentsMargins(nullptr, nullptr, nullptr, &margin); + return margin; + } + return m_bottomMargin; +} + +void QLayoutWidget::setLayoutBottomMargin(int layoutMargin) +{ + m_bottomMargin = layoutMargin; + if (layout()) { + int newMargin = m_bottomMargin; + if (newMargin >= 0 && newMargin < ShiftValue) + newMargin = ShiftValue; + int left, top, right, bottom; + layout()->getContentsMargins(&left, &top, &right, &bottom); + layout()->setContentsMargins(left, top, right, newMargin); + } +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qlayout_widget_p.h b/src/tools/designer/src/lib/shared/qlayout_widget_p.h new file mode 100644 index 00000000000..16185d5ecd8 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qlayout_widget_p.h @@ -0,0 +1,257 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QLAYOUT_WIDGET_H +#define QLAYOUT_WIDGET_H + +#include "shared_global_p.h" + +#include + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDesignerFormWindowInterface; +class QDesignerFormEditorInterface; +class QGridLayout; +class QFormLayout; + +namespace qdesigner_internal { +// ---- LayoutProperties: Helper struct that stores all layout-relevant properties +// with functions to retrieve and apply to property sheets. Can be used to store the state +// for undo commands and while rebuilding layouts. +struct QDESIGNER_SHARED_EXPORT LayoutProperties +{ + LayoutProperties(); + void clear(); + + enum Margins { LeftMargin, TopMargin, RightMargin, BottomMargin, MarginCount }; + enum Spacings { Spacing, HorizSpacing, VertSpacing, SpacingsCount }; + + enum PropertyMask { + ObjectNameProperty = 0x1, + LeftMarginProperty = 0x2, TopMarginProperty = 0x4, RightMarginProperty = 0x8, BottomMarginProperty = 0x10, + SpacingProperty = 0x20, HorizSpacingProperty = 0x40, VertSpacingProperty = 0x80, + SizeConstraintProperty = 0x100, + FieldGrowthPolicyProperty = 0x200, RowWrapPolicyProperty = 0x400, LabelAlignmentProperty = 0x0800, FormAlignmentProperty = 0x1000, + BoxStretchProperty = 0x2000, GridRowStretchProperty = 0x4000, GridColumnStretchProperty = 0x8000, + GridRowMinimumHeightProperty = 0x10000, GridColumnMinimumWidthProperty = 0x20000, + AllProperties = 0xFFFF}; + + // return a PropertyMask of visible properties + static int visibleProperties(const QLayout *layout); + + // Retrieve from /apply to sheet: A property mask is returned indicating the properties found in the sheet + int fromPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask = AllProperties); + int toPropertySheet(const QDesignerFormEditorInterface *core, QLayout *l, int mask = AllProperties, bool applyChanged = true) const; + + int m_margins[MarginCount]; + bool m_marginsChanged[MarginCount]; + + int m_spacings[SpacingsCount]; + bool m_spacingsChanged[SpacingsCount]; + + QVariant m_objectName; // receives a PropertySheetStringValue + bool m_objectNameChanged; + QVariant m_sizeConstraint; + bool m_sizeConstraintChanged; + + bool m_fieldGrowthPolicyChanged; + QVariant m_fieldGrowthPolicy; + bool m_rowWrapPolicyChanged; + QVariant m_rowWrapPolicy; + bool m_labelAlignmentChanged; + QVariant m_labelAlignment; + bool m_formAlignmentChanged; + QVariant m_formAlignment; + + bool m_boxStretchChanged; + QVariant m_boxStretch; + + bool m_gridRowStretchChanged; + QVariant m_gridRowStretch; + + bool m_gridColumnStretchChanged; + QVariant m_gridColumnStretch; + + bool m_gridRowMinimumHeightChanged; + QVariant m_gridRowMinimumHeight; + + bool m_gridColumnMinimumWidthChanged; + QVariant m_gridColumnMinimumWidth; +}; + +// -- LayoutHelper: For use with the 'insert widget'/'delete widget' command, +// able to store and restore states. +// This could become part of 'QDesignerLayoutDecorationExtensionV2', +// but to keep any existing old extensions working, it is provided as +// separate class with a factory function. +class LayoutHelper { +protected: + LayoutHelper(); + +public: + Q_DISABLE_COPY(LayoutHelper) + + virtual ~LayoutHelper(); + + static LayoutHelper *createLayoutHelper(int type); + + static int indexOf(const QLayout *lt, const QWidget *widget); + + // Return area of an item (x == columns) + QRect itemInfo(QLayout *lt, const QWidget *widget) const; + + virtual QRect itemInfo(QLayout *lt, int index) const = 0; + virtual void insertWidget(QLayout *lt, const QRect &info, QWidget *w) = 0; + virtual void removeWidget(QLayout *lt, QWidget *widget) = 0; + // Since 4.5: The 'morphing' feature requires an API for replacing widgets on layouts. + virtual void replaceWidget(QLayout *lt, QWidget *before, QWidget *after) = 0; + + // Simplify a grid, remove empty columns, rows within the rectangle + // The DeleteWidget command restricts the area to be simplified. + virtual bool canSimplify(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout, const QRect &restrictionArea) const = 0; + virtual void simplify(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout, const QRect &restrictionArea) = 0; + + // Push and pop a state. Can be used for implementing undo for + // simplify/row, column insertion commands, provided that + // the widgets remain the same. + virtual void pushState(const QDesignerFormEditorInterface *core, const QWidget *widgetWithManagedLayout) = 0; + virtual void popState(const QDesignerFormEditorInterface *core, QWidget *widgetWithManagedLayout) = 0; +}; + +// Base class for layout decoration extensions. +class QDESIGNER_SHARED_EXPORT QLayoutSupport: public QObject, public QDesignerLayoutDecorationExtension +{ + Q_OBJECT + Q_INTERFACES(QDesignerLayoutDecorationExtension) + +protected: + QLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, LayoutHelper *helper, QObject *parent = nullptr); + +public: + ~QLayoutSupport() override; + + inline QDesignerFormWindowInterface *formWindow() const { return m_formWindow; } + + // DecorationExtension V2 + LayoutHelper* helper() const { return m_helper; } + + // DecorationExtension + int currentIndex() const override { return m_currentIndex; } + + InsertMode currentInsertMode() const override { return m_currentInsertMode; } + + std::pair currentCell() const override { return m_currentCell; } + + int findItemAt(const QPoint &pos) const override; + int indexOf(QWidget *widget) const override; + int indexOf(QLayoutItem *item) const override; + + void adjustIndicator(const QPoint &pos, int index) override; + + QWidgetList widgets(QLayout *layout) const override; + + // Pad empty cells with dummy spacers. Called by layouting commands. + static void createEmptyCells(QGridLayout *gridLayout); + // remove dummy spacers in the area. Returns false if there are non-empty items in the way + static bool removeEmptyCells(QGridLayout *gridLayout, const QRect &area); + static void createEmptyCells(QFormLayout *formLayout); // ditto. + static bool removeEmptyCells(QFormLayout *formLayout, const QRect &area); + + // grid helpers: find item index + static int findItemAt(QGridLayout *, int row, int column); + using QDesignerLayoutDecorationExtension::findItemAt; + // grid helpers: Quick check whether simplify should be enabled for grids. May return false positives. + static bool canSimplifyQuickCheck(const QGridLayout *); + static bool canSimplifyQuickCheck(const QFormLayout *fl); + // Factory function, create layout support according to layout type of widget + static QLayoutSupport *createLayoutSupport(QDesignerFormWindowInterface *formWindow, QWidget *widget, QObject *parent = nullptr); + +protected: + // figure out insertion position and mode from indicator on empty cell if supported + virtual void setCurrentCellFromIndicatorOnEmptyCell(int index) = 0; + // figure out insertion position and mode from indicator + virtual void setCurrentCellFromIndicator(Qt::Orientation indicatorOrientation, int index, int increment) = 0; + + // Overwrite to return the extended geometry of an item, that is, + // if it is a border item, include the widget border for the indicator to work correctly + virtual QRect extendedGeometry(int index) const = 0; + virtual bool supportsIndicatorOrientation(Qt::Orientation indicatorOrientation) const = 0; + + QRect itemInfo(int index) const override; + QLayout *layout() const; + QGridLayout *gridLayout() const; + QWidget *widget() const { return m_widget; } + + void setInsertMode(InsertMode im); + void setCurrentCell(const std::pair &cell); + +private: + enum Indicator { LeftIndicator, TopIndicator, RightIndicator, BottomIndicator, NumIndicators }; + + void hideIndicator(Indicator i); + void showIndicator(Indicator i, const QRect &geometry, const QPalette &); + + QDesignerFormWindowInterface *m_formWindow; + LayoutHelper* m_helper; + + QPointer m_widget; + QPointer m_indicators[NumIndicators]; + int m_currentIndex; + InsertMode m_currentInsertMode; + std::pair m_currentCell; +}; +} // namespace qdesigner_internal + +// Red layout widget. +class QDESIGNER_SHARED_EXPORT QLayoutWidget: public QWidget +{ + Q_OBJECT +public: + explicit QLayoutWidget(QDesignerFormWindowInterface *formWindow, QWidget *parent = nullptr); + + int layoutLeftMargin() const; + void setLayoutLeftMargin(int layoutMargin); + + int layoutTopMargin() const; + void setLayoutTopMargin(int layoutMargin); + + int layoutRightMargin() const; + void setLayoutRightMargin(int layoutMargin); + + int layoutBottomMargin() const; + void setLayoutBottomMargin(int layoutMargin); + + inline QDesignerFormWindowInterface *formWindow() const { return m_formWindow; } + +protected: + bool event(QEvent *e) override; + void paintEvent(QPaintEvent *e) override; + +private: + QDesignerFormWindowInterface *m_formWindow; + int m_leftMargin; + int m_topMargin; + int m_rightMargin; + int m_bottomMargin; +}; + +QT_END_NAMESPACE + +#endif // QDESIGNER_WIDGET_H diff --git a/src/tools/designer/src/lib/shared/qsimpleresource.cpp b/src/tools/designer/src/lib/shared/qsimpleresource.cpp new file mode 100644 index 00000000000..2b0ef4a07ba --- /dev/null +++ b/src/tools/designer/src/lib/shared/qsimpleresource.cpp @@ -0,0 +1,233 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qsimpleresource_p.h" +#include "widgetfactory_p.h" +#include "widgetdatabase_p.h" +#include + +#include +#include + +#include +#include +#include +#include + +#include + +#include + +#include +#include + +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +namespace qdesigner_internal { + +bool QSimpleResource::m_warningsEnabled = true; + +QSimpleResource::QSimpleResource(QDesignerFormEditorInterface *core) : + QAbstractFormBuilder(), + m_core(core) +{ + setWorkingDirectory(QDir(dataDirectory())); +} + +QSimpleResource::~QSimpleResource() = default; + +QBrush QSimpleResource::setupBrush(DomBrush *brush) +{ + return QAbstractFormBuilder::setupBrush(brush); +} + +DomBrush *QSimpleResource::saveBrush(const QBrush &brush) +{ + return QAbstractFormBuilder::saveBrush(brush); +} + +void QSimpleResource::addExtensionDataToDOM(QAbstractFormBuilder * /* afb */, + QDesignerFormEditorInterface *core, + DomWidget *ui_widget, QWidget *widget) +{ + QExtensionManager *emgr = core->extensionManager(); + if (QDesignerExtraInfoExtension *extra = qt_extension(emgr, widget)) { + extra->saveWidgetExtraInfo(ui_widget); + } +} + +void QSimpleResource::applyExtensionDataFromDOM(QAbstractFormBuilder * /* afb */, + QDesignerFormEditorInterface *core, + DomWidget *ui_widget, QWidget *widget) +{ + QExtensionManager *emgr = core->extensionManager(); + if (QDesignerExtraInfoExtension *extra = qt_extension(emgr, widget)) { + extra->loadWidgetExtraInfo(ui_widget); + } +} + +QString QSimpleResource::customWidgetScript(QDesignerFormEditorInterface *core, QObject *object) +{ + return customWidgetScript(core, qdesigner_internal::WidgetFactory::classNameOf(core, object)); +} + +bool QSimpleResource::hasCustomWidgetScript(QDesignerFormEditorInterface *, QObject *) +{ + return false; +} + +QString QSimpleResource::customWidgetScript(QDesignerFormEditorInterface *, const QString &) +{ + return QString(); +} + +// Custom widgets handling helpers + +// Add unique fake slots and signals to lists +bool QSimpleResource::addFakeMethods(const DomSlots *domSlots, QStringList &fakeSlots, QStringList &fakeSignals) +{ + if (!domSlots) + return false; + + bool rc = false; + const QStringList &elementSlots = domSlots->elementSlot(); + for (const QString &fakeSlot : elementSlots) + if (fakeSlots.indexOf(fakeSlot) == -1) { + fakeSlots += fakeSlot; + rc = true; + } + + const QStringList &elementSignals = domSlots->elementSignal(); + for (const QString &fakeSignal : elementSignals) + if (fakeSignals.indexOf(fakeSignal) == -1) { + fakeSignals += fakeSignal; + rc = true; + } + return rc; +} + +void QSimpleResource::addFakeMethodsToWidgetDataBase(const DomCustomWidget *domCustomWidget, WidgetDataBaseItem *item) +{ + const DomSlots *domSlots = domCustomWidget->elementSlots(); + if (!domSlots) + return; + + // Merge in new slots, signals + QStringList fakeSlots = item->fakeSlots(); + QStringList fakeSignals = item->fakeSignals(); + if (addFakeMethods(domSlots, fakeSlots, fakeSignals)) { + item->setFakeSlots(fakeSlots); + item->setFakeSignals(fakeSignals); + } +} + +// Perform one iteration of adding the custom widgets to the database, +// looking up the base class and inheriting its data. +// Remove the succeeded custom widgets from the list. +// Classes whose base class could not be found are left in the list. + +void QSimpleResource::addCustomWidgetsToWidgetDatabase(const QDesignerFormEditorInterface *core, + QList &custom_widget_list) +{ + QDesignerWidgetDataBaseInterface *db = core->widgetDataBase(); + for (qsizetype i = 0; i < custom_widget_list.size(); ) { + bool classInserted = false; + DomCustomWidget *custom_widget = custom_widget_list.at(i); + const QString customClassName = custom_widget->elementClass(); + const QString base_class = custom_widget->elementExtends(); + QString includeFile; + IncludeType includeType = IncludeLocal; + if (const DomHeader *header = custom_widget->elementHeader()) { + includeFile = header->text(); + if (header->hasAttributeLocation() && header->attributeLocation() == "global"_L1) + includeType = IncludeGlobal; + } + const bool domIsContainer = custom_widget->elementContainer(); + // Append a new item + if (base_class.isEmpty()) { + WidgetDataBaseItem *item = new WidgetDataBaseItem(customClassName); + item->setPromoted(false); + item->setGroup(QCoreApplication::translate("Designer", "Custom Widgets")); + item->setIncludeFile(buildIncludeFile(includeFile, includeType)); + item->setContainer(domIsContainer); + item->setCustom(true); + addFakeMethodsToWidgetDataBase(custom_widget, item); + db->append(item); + custom_widget_list.removeAt(i); + classInserted = true; + } else { + // Create a new entry cloned from base class. Note that this will ignore existing + // classes, eg, plugin custom widgets. + QDesignerWidgetDataBaseItemInterface *item = + appendDerived(db, customClassName, QCoreApplication::translate("Designer", "Promoted Widgets"), + base_class, + buildIncludeFile(includeFile, includeType), + true,true); + // Ok, base class found. + if (item) { + // Hack to accommodate for old UI-files in which "container" is not set properly: + // Apply "container" from DOM only if true (else, eg classes from QFrame might not accept + // dropping child widgets on them as container=false). This also allows for + // QWidget-derived stacked pages. + if (domIsContainer) + item->setContainer(domIsContainer); + + addFakeMethodsToWidgetDataBase(custom_widget, static_cast(item)); + custom_widget_list.removeAt(i); + classInserted = true; + } + } + // Skip failed item. + if (!classInserted) + i++; + } + +} + +void QSimpleResource::handleDomCustomWidgets(const QDesignerFormEditorInterface *core, + const DomCustomWidgets *dom_custom_widgets) +{ + if (dom_custom_widgets == nullptr) + return; + auto custom_widget_list = dom_custom_widgets->elementCustomWidget(); + // Attempt to insert each item derived from its base class. + // This should at most require two iterations in the event that the classes are out of order + // (derived first, max depth: promoted custom plugin = 2) + for (int iteration = 0; iteration < 2; iteration++) { + addCustomWidgetsToWidgetDatabase(core, custom_widget_list); + if (custom_widget_list.isEmpty()) + return; + } + // Oops, there are classes left whose base class could not be found. + // Default them to QWidget with warnings. + const QString fallBackBaseClass = u"QWidget"_s; + for (DomCustomWidget *custom_widget : std::as_const(custom_widget_list)) { + const QString customClassName = custom_widget->elementClass(); + const QString base_class = custom_widget->elementExtends(); + qDebug() << "** WARNING The base class " << base_class << " of the custom widget class " << customClassName + << " could not be found. Defaulting to " << fallBackBaseClass << '.'; + custom_widget->setElementExtends(fallBackBaseClass); + } + // One more pass. + addCustomWidgetsToWidgetDatabase(core, custom_widget_list); +} + +// ------------ FormBuilderClipboard + +FormBuilderClipboard::FormBuilderClipboard(QWidget *w) +{ + m_widgets += w; +} + +bool FormBuilderClipboard::empty() const +{ + return m_widgets.isEmpty() && m_actions.isEmpty(); +} +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/qsimpleresource_p.h b/src/tools/designer/src/lib/shared/qsimpleresource_p.h new file mode 100644 index 00000000000..37b9f5c0478 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qsimpleresource_p.h @@ -0,0 +1,111 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QSIMPLERESOURCE_H +#define QSIMPLERESOURCE_H + +#include "abstractformbuilder.h" +#include "shared_global_p.h" + +#include +#include + +QT_BEGIN_NAMESPACE + +class DomCustomWidgets; +class DomCustomWidget; +class DomSlots; + +class QDesignerFormEditorInterface; + +namespace qdesigner_internal { + +class WidgetDataBaseItem; + +class QDESIGNER_SHARED_EXPORT QSimpleResource : public QAbstractFormBuilder +{ +public: + explicit QSimpleResource(QDesignerFormEditorInterface *core); + ~QSimpleResource() override; + + QBrush setupBrush(DomBrush *brush); + DomBrush *saveBrush(const QBrush &brush); + + inline QDesignerFormEditorInterface *core() const + { return m_core; } + + // Query extensions for additional data + static void addExtensionDataToDOM(QAbstractFormBuilder *afb, + QDesignerFormEditorInterface *core, + DomWidget *ui_widget, QWidget *widget); + static void applyExtensionDataFromDOM(QAbstractFormBuilder *afb, + QDesignerFormEditorInterface *core, + DomWidget *ui_widget, QWidget *widget); + // Return the script returned by the CustomWidget codeTemplate API + static QString customWidgetScript(QDesignerFormEditorInterface *core, QObject *object); + static QString customWidgetScript(QDesignerFormEditorInterface *core, const QString &className); + static bool hasCustomWidgetScript(QDesignerFormEditorInterface *core, QObject *object); + + // Implementation for FormBuilder::createDomCustomWidgets() that adds + // the custom widgets to the widget database + static void handleDomCustomWidgets(const QDesignerFormEditorInterface *core, + const DomCustomWidgets *dom_custom_widgets); + +protected: + static bool addFakeMethods(const DomSlots *domSlots, QStringList &fakeSlots, QStringList &fakeSignals); + +private: + static void addCustomWidgetsToWidgetDatabase(const QDesignerFormEditorInterface *core, + QList &custom_widget_list); + static void addFakeMethodsToWidgetDataBase(const DomCustomWidget *domCustomWidget, WidgetDataBaseItem *item); + + static bool m_warningsEnabled; + QDesignerFormEditorInterface *m_core; +}; + +// Contents of clipboard for formbuilder copy and paste operations +// (Actions and widgets) +struct QDESIGNER_SHARED_EXPORT FormBuilderClipboard { + using ActionList = QList; + + FormBuilderClipboard() = default; + FormBuilderClipboard(QWidget *w); + + bool empty() const; + + QWidgetList m_widgets; + ActionList m_actions; +}; + +// Base class for a form builder used in the editor that +// provides copy and paste.(move into base interface) +class QDESIGNER_SHARED_EXPORT QEditorFormBuilder : public QSimpleResource +{ +public: + explicit QEditorFormBuilder(QDesignerFormEditorInterface *core) : QSimpleResource(core) {} + + virtual bool copy(QIODevice *dev, const FormBuilderClipboard &selection) = 0; + virtual DomUI *copy(const FormBuilderClipboard &selection) = 0; + + // A widget parent needs to be specified, otherwise, the widget factory cannot locate the form window via parent + // and thus is not able to construct special widgets (QLayoutWidget). + virtual FormBuilderClipboard paste(DomUI *ui, QWidget *widgetParent, QObject *actionParent = nullptr) = 0; + virtual FormBuilderClipboard paste(QIODevice *dev, QWidget *widgetParent, QObject *actionParent = nullptr) = 0; +}; + +} // namespace qdesigner_internal + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/designer/src/lib/shared/qtresourceeditordialog.cpp b/src/tools/designer/src/lib/shared/qtresourceeditordialog.cpp new file mode 100644 index 00000000000..fff75d8237a --- /dev/null +++ b/src/tools/designer/src/lib/shared/qtresourceeditordialog.cpp @@ -0,0 +1,2164 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtresourceeditordialog_p.h" +#include "ui_qtresourceeditordialog.h" +#include "qtresourcemodel_p.h" +#include "iconloader_p.h" + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto rccRootTag = "RCC"_L1; +static constexpr auto rccTag = "qresource"_L1; +static constexpr auto rccFileTag = "file"_L1; +static constexpr auto rccAliasAttribute = "alias"_L1; +static constexpr auto rccPrefixAttribute = "prefix"_L1; +static constexpr auto rccLangAttribute = "lang"_L1; +static constexpr auto SplitterPosition = "SplitterPosition"_L1; +static constexpr auto ResourceEditorGeometry = "Geometry"_L1; +static constexpr auto QrcDialogC = "QrcDialog"_L1; + +static QString msgOverwrite(const QString &fname) +{ + return QCoreApplication::translate("QtResourceEditorDialog", "%1 already exists.\nDo you want to replace it?").arg(fname); +} + +static QString msgTagMismatch(const QString &got, const QString &expected) +{ + return QCoreApplication::translate("QtResourceEditorDialog", "The file does not appear to be a resource file; element '%1' was found where '%2' was expected.").arg(got, expected); +} + +namespace qdesigner_internal { + +// below 3 data classes should be derived from QSharedData and made implicit shared class +struct QtResourceFileData +{ + QString path; + QString alias; + + friend bool comparesEqual(const QtResourceFileData &lhs, + const QtResourceFileData &rhs) noexcept + { + return lhs.path == rhs.path && lhs.alias == rhs.alias; + } + Q_DECLARE_EQUALITY_COMPARABLE(QtResourceFileData) +}; + +struct QtResourcePrefixData +{ + QString prefix; + QString language; + QList resourceFileList; + + friend bool comparesEqual(const QtResourcePrefixData &lhs, + const QtResourcePrefixData &rhs) noexcept + { + return lhs.prefix == rhs.prefix && lhs.language == rhs.language + && lhs.resourceFileList == rhs.resourceFileList; + } + Q_DECLARE_EQUALITY_COMPARABLE(QtResourcePrefixData) +}; + +struct QtQrcFileData +{ + QString qrcPath; + QList resourceList; + + friend bool comparesEqual(const QtQrcFileData &lhs, + const QtQrcFileData &rhs) noexcept + { + return lhs.qrcPath == rhs.qrcPath && lhs.resourceList == rhs.resourceList; + } + Q_DECLARE_EQUALITY_COMPARABLE(QtQrcFileData) +}; + +} // namespace qdesigner_internal + +using QtResourcePrefixData = qdesigner_internal::QtResourcePrefixData; +using QtResourceFileData = qdesigner_internal::QtResourceFileData; +using QtQrcFileData = qdesigner_internal::QtQrcFileData; + +static bool loadResourceFileData(const QDomElement &fileElem, QtResourceFileData *fileData, + QString *errorMessage) +{ + if (!fileData) + return false; + + if (fileElem.tagName() != rccFileTag) { + *errorMessage = msgTagMismatch(fileElem.tagName(), rccFileTag); + return false; + } + + QtResourceFileData &data = *fileData; + + data.path = fileElem.text(); + data.alias = fileElem.attribute(rccAliasAttribute); + + return true; +} + +static bool loadResourcePrefixData(const QDomElement &prefixElem, QtResourcePrefixData *prefixData, QString *errorMessage) +{ + if (!prefixData) + return false; + + if (prefixElem.tagName() != rccTag) { + *errorMessage = msgTagMismatch(prefixElem.tagName(), rccTag); + return false; + } + + QtResourcePrefixData &data = *prefixData; + + data.prefix = prefixElem.attribute(rccPrefixAttribute); + data.language = prefixElem.attribute(rccLangAttribute); + QDomElement fileElem = prefixElem.firstChildElement(); + while (!fileElem.isNull()) { + QtResourceFileData fileData; + if (!loadResourceFileData(fileElem, &fileData, errorMessage)) + return false; + data.resourceFileList.append(fileData); + fileElem = fileElem.nextSiblingElement(); + } + return true; +} + +static bool loadQrcFileData(const QDomDocument &doc, const QString &path, QtQrcFileData *qrcFileData, QString *errorMessage) +{ + if (!qrcFileData) + return false; + + QtQrcFileData &data = *qrcFileData; + + QDomElement docElem = doc.documentElement(); + if (docElem.tagName() != rccRootTag) { + *errorMessage = msgTagMismatch(docElem.tagName(), rccRootTag); + return false; + } + + QDomElement prefixElem = docElem.firstChildElement(); + while (!prefixElem.isNull()) { + QtResourcePrefixData prefixData; + if (!loadResourcePrefixData(prefixElem, &prefixData, errorMessage)) + return false; + data.resourceList.append(prefixData); + prefixElem = prefixElem.nextSiblingElement(); + } + + data.qrcPath = path; + + return true; +} + +static QDomElement saveResourceFileData(QDomDocument &doc, const QtResourceFileData &fileData) +{ + QDomElement fileElem = doc.createElement(rccFileTag); + if (!fileData.alias.isEmpty()) + fileElem.setAttribute(rccAliasAttribute, fileData.alias); + + QDomText textElem = doc.createTextNode(fileData.path); + fileElem.appendChild(textElem); + + return fileElem; +} + +static QDomElement saveResourcePrefixData(QDomDocument &doc, const QtResourcePrefixData &prefixData) +{ + QDomElement prefixElem = doc.createElement(rccTag); + if (!prefixData.prefix.isEmpty()) + prefixElem.setAttribute(rccPrefixAttribute, prefixData.prefix); + if (!prefixData.language.isEmpty()) + prefixElem.setAttribute(rccLangAttribute, prefixData.language); + + for (const QtResourceFileData &rfd : prefixData.resourceFileList) { + QDomElement fileElem = saveResourceFileData(doc, rfd); + prefixElem.appendChild(fileElem); + } + + return prefixElem; +} + +static QDomDocument saveQrcFileData(const QtQrcFileData &qrcFileData) +{ + QDomDocument doc; + QDomElement docElem = doc.createElement(rccRootTag); + for (const QtResourcePrefixData &prefixData : qrcFileData.resourceList) { + QDomElement prefixElem = saveResourcePrefixData(doc, prefixData); + + docElem.appendChild(prefixElem); + } + doc.appendChild(docElem); + + return doc; +} + +namespace qdesigner_internal { + +// --------------- QtResourceFile +class QtResourceFile { +public: + friend class QtQrcManager; + + QString path() const { return m_path; } + QString alias() const { return m_alias; } + QString fullPath() const { return m_fullPath; } +private: + QtResourceFile() = default; + + QString m_path; + QString m_alias; + QString m_fullPath; +}; + +class QtResourcePrefix { +public: + friend class QtQrcManager; + + QString prefix() const { return m_prefix; } + QString language() const { return m_language; } + QList resourceFiles() const { return m_resourceFiles; } +private: + QtResourcePrefix() = default; + + QString m_prefix; + QString m_language; + QList m_resourceFiles; + +}; +// ------------------- QtQrcFile +class QtQrcFile { +public: + friend class QtQrcManager; + + QString path() const { return m_path; } + QString fileName() const { return m_fileName; } + QList resourcePrefixList() const { return m_resourcePrefixes; } + QtQrcFileData initialState() const { return m_initialState; } + +private: + QtQrcFile() = default; + + void setPath(const QString &path) { + m_path = path; + QFileInfo fi(path); + m_fileName = fi.fileName(); + } + + QString m_path; + QString m_fileName; + QList m_resourcePrefixes; + QtQrcFileData m_initialState; +}; + +// ------------------ QtQrcManager +class QtQrcManager : public QObject +{ + Q_OBJECT +public: + QtQrcManager(QObject *parent = nullptr); + ~QtQrcManager() override; + + QList qrcFiles() const; + + // helpers + QtQrcFile *qrcFileOf(const QString &path) const; + QtQrcFile *qrcFileOf(QtResourcePrefix *resourcePrefix) const; + QtResourcePrefix *resourcePrefixOf(QtResourceFile *resourceFile) const; + + QtQrcFile *importQrcFile(const QtQrcFileData &qrcFileData, QtQrcFile *beforeQrcFile = nullptr); + void exportQrcFile(QtQrcFile *qrcFile, QtQrcFileData *qrcFileData) const; + + QIcon icon(const QString &resourceFullPath) const; + bool exists(const QString &resourceFullPath) const; + bool exists(QtQrcFile *qrcFile) const; + + QtQrcFile *prevQrcFile(QtQrcFile *qrcFile) const; + QtQrcFile *nextQrcFile(QtQrcFile *qrcFile) const; + QtResourcePrefix *prevResourcePrefix(QtResourcePrefix *resourcePrefix) const; + QtResourcePrefix *nextResourcePrefix(QtResourcePrefix *resourcePrefix) const; + QtResourceFile *prevResourceFile(QtResourceFile *resourceFile) const; + QtResourceFile *nextResourceFile(QtResourceFile *resourceFile) const; + + void clear(); + +public slots: + + QtQrcFile *insertQrcFile(const QString &path, QtQrcFile *beforeQrcFile = nullptr, bool newFile = false); + void moveQrcFile(QtQrcFile *qrcFile, QtQrcFile *beforeQrcFile); + void setInitialState(QtQrcFile *qrcFile, const QtQrcFileData &initialState); + void removeQrcFile(QtQrcFile *qrcFile); + + QtResourcePrefix *insertResourcePrefix(QtQrcFile *qrcFile, const QString &prefix, + const QString &language, QtResourcePrefix *beforeResourcePrefix = nullptr); + void moveResourcePrefix(QtResourcePrefix *resourcePrefix, QtResourcePrefix *beforeResourcePrefix); // the same qrc file??? + void changeResourcePrefix(QtResourcePrefix *resourcePrefix, const QString &newPrefix); + void changeResourceLanguage(QtResourcePrefix *resourcePrefix, const QString &newLanguage); + void removeResourcePrefix(QtResourcePrefix *resourcePrefix); + + QtResourceFile *insertResourceFile(QtResourcePrefix *resourcePrefix, const QString &path, + const QString &alias, QtResourceFile *beforeResourceFile = nullptr); + void moveResourceFile(QtResourceFile *resourceFile, QtResourceFile *beforeResourceFile); // the same prefix??? + void changeResourceAlias(QtResourceFile *resourceFile, const QString &newAlias); + void removeResourceFile(QtResourceFile *resourceFile); + +signals: + void qrcFileInserted(QtQrcFile *qrcFile); + void qrcFileMoved(QtQrcFile *qrcFile, QtQrcFile *oldBeforeQrcFile); + void qrcFileRemoved(QtQrcFile *qrcFile); + + void resourcePrefixInserted(QtResourcePrefix *resourcePrefix); + void resourcePrefixMoved(QtResourcePrefix *resourcePrefix, QtResourcePrefix *oldBeforeResourcePrefix); + void resourcePrefixChanged(QtResourcePrefix *resourcePrefix, const QString &oldPrefix); + void resourceLanguageChanged(QtResourcePrefix *resourcePrefix, const QString &oldLanguage); + void resourcePrefixRemoved(QtResourcePrefix *resourcePrefix); + + void resourceFileInserted(QtResourceFile *resourceFile); + void resourceFileMoved(QtResourceFile *resourceFile, QtResourceFile *oldBeforeResourceFile); + void resourceAliasChanged(QtResourceFile *resourceFile, const QString &oldAlias); + void resourceFileRemoved(QtResourceFile *resourceFile); +private: + + QList m_qrcFiles; + QMap m_pathToQrc; + QHash m_qrcFileToExists; + QHash m_prefixToQrc; + QHash m_fileToPrefix; + QMap > m_fullPathToResourceFiles; + QMap m_fullPathToIcon; + QMap m_fullPathToExists; +}; + +QtQrcManager::QtQrcManager(QObject *parent) + : QObject(parent) +{ + +} + +QtQrcManager::~QtQrcManager() +{ + clear(); +} + +QList QtQrcManager::qrcFiles() const +{ + return m_qrcFiles; +} + +QtQrcFile *QtQrcManager::qrcFileOf(const QString &path) const +{ + return m_pathToQrc.value(path); +} + +QtQrcFile *QtQrcManager::qrcFileOf(QtResourcePrefix *resourcePrefix) const +{ + return m_prefixToQrc.value(resourcePrefix); +} + +QtResourcePrefix *QtQrcManager::resourcePrefixOf(QtResourceFile *resourceFile) const +{ + return m_fileToPrefix.value(resourceFile); +} + +QtQrcFile *QtQrcManager::importQrcFile(const QtQrcFileData &qrcFileData, QtQrcFile *beforeQrcFile) +{ + QtQrcFile *qrcFile = insertQrcFile(qrcFileData.qrcPath, beforeQrcFile); + if (!qrcFile) + return nullptr; + for (const QtResourcePrefixData &prefixData : qrcFileData.resourceList) { + QtResourcePrefix *resourcePrefix = insertResourcePrefix(qrcFile, prefixData.prefix, prefixData.language, nullptr); + for (const QtResourceFileData &fileData : prefixData.resourceFileList) + insertResourceFile(resourcePrefix, fileData.path, fileData.alias, nullptr); + } + setInitialState(qrcFile, qrcFileData); + return qrcFile; +} + +void QtQrcManager::exportQrcFile(QtQrcFile *qrcFile, QtQrcFileData *qrcFileData) const +{ + if (!qrcFileData) + return; + + if (!qrcFile) + return; + + QtQrcFileData &data = *qrcFileData; + + QList resourceList; + + const auto resourcePrefixes = qrcFile->resourcePrefixList(); + for (const QtResourcePrefix *prefix : resourcePrefixes) { + QList resourceFileList; + const auto resourceFiles = prefix->resourceFiles(); + for (QtResourceFile *file : resourceFiles) { + QtResourceFileData fileData; + fileData.path = file->path(); + fileData.alias = file->alias(); + resourceFileList << fileData; + } + QtResourcePrefixData prefixData; + prefixData.prefix = prefix->prefix(); + prefixData.language = prefix->language(); + prefixData.resourceFileList = resourceFileList; + + resourceList << prefixData; + } + data = QtQrcFileData(); + data.qrcPath = qrcFile->path(); + data.resourceList = resourceList; +} + +QIcon QtQrcManager::icon(const QString &resourceFullPath) const +{ + return m_fullPathToIcon.value(resourceFullPath); +} + +bool QtQrcManager::exists(const QString &resourceFullPath) const +{ + return m_fullPathToExists.value(resourceFullPath, false); +} + +bool QtQrcManager::exists(QtQrcFile *qrcFile) const +{ + return m_qrcFileToExists.value(qrcFile, false); +} + +QtQrcFile *QtQrcManager::prevQrcFile(QtQrcFile *qrcFile) const +{ + if (!qrcFile) + return nullptr; + const int idx = m_qrcFiles.indexOf(qrcFile); + if (idx <= 0) + return nullptr; + return m_qrcFiles.at(idx - 1); +} + +QtQrcFile *QtQrcManager::nextQrcFile(QtQrcFile *qrcFile) const +{ + if (!qrcFile) + return nullptr; + const int idx = m_qrcFiles.indexOf(qrcFile); + if (idx < 0 || idx == m_qrcFiles.size() - 1) + return nullptr; + return m_qrcFiles.at(idx + 1); +} + +QtResourcePrefix *QtQrcManager::prevResourcePrefix(QtResourcePrefix *resourcePrefix) const +{ + if (!resourcePrefix) + return nullptr; + const auto prefixes = qrcFileOf(resourcePrefix)->resourcePrefixList(); + const int idx = prefixes.indexOf(resourcePrefix); + if (idx <= 0) + return nullptr; + return prefixes.at(idx - 1); +} + +QtResourcePrefix *QtQrcManager::nextResourcePrefix(QtResourcePrefix *resourcePrefix) const +{ + if (!resourcePrefix) + return nullptr; + const auto prefixes = qrcFileOf(resourcePrefix)->resourcePrefixList(); + const int idx = prefixes.indexOf(resourcePrefix); + if (idx < 0 || idx == prefixes.size() - 1) + return nullptr; + return prefixes.at(idx + 1); +} + +QtResourceFile *QtQrcManager::prevResourceFile(QtResourceFile *resourceFile) const +{ + if (!resourceFile) + return nullptr; + const auto files = resourcePrefixOf(resourceFile)->resourceFiles(); + const int idx = files.indexOf(resourceFile); + if (idx <= 0) + return nullptr; + return files.at(idx - 1); +} + +QtResourceFile *QtQrcManager::nextResourceFile(QtResourceFile *resourceFile) const +{ + if (!resourceFile) + return nullptr; + const auto files = resourcePrefixOf(resourceFile)->resourceFiles(); + const int idx = files.indexOf(resourceFile); + if (idx < 0 || idx == files.size() - 1) + return nullptr; + return files.at(idx + 1); +} + +void QtQrcManager::clear() +{ + const auto oldQrcFiles = qrcFiles(); + for (QtQrcFile *qf : oldQrcFiles) + removeQrcFile(qf); +} + +QtQrcFile *QtQrcManager::insertQrcFile(const QString &path, QtQrcFile *beforeQrcFile, bool newFile) +{ + if (m_pathToQrc.contains(path)) + return nullptr; + + int idx = m_qrcFiles.indexOf(beforeQrcFile); + if (idx < 0) + idx = m_qrcFiles.size(); + + QtQrcFile *qrcFile = new QtQrcFile(); + qrcFile->setPath(path); + + m_qrcFiles.insert(idx, qrcFile); + m_pathToQrc[path] = qrcFile; + + const QFileInfo fi(path); + m_qrcFileToExists[qrcFile] = fi.exists() || newFile; + + emit qrcFileInserted(qrcFile); + return qrcFile; +} + +void QtQrcManager::moveQrcFile(QtQrcFile *qrcFile, QtQrcFile *beforeQrcFile) +{ + if (qrcFile == beforeQrcFile) + return; + + const int idx = m_qrcFiles.indexOf(qrcFile); + if (idx < 0) + return; + + int beforeIdx = m_qrcFiles.indexOf(beforeQrcFile); + if (beforeIdx < 0) + beforeIdx = m_qrcFiles.size(); + + if (idx == beforeIdx - 1) // the same position, nothing changes + return; + + QtQrcFile *oldBefore = nullptr; + if (idx < m_qrcFiles.size() - 1) + oldBefore = m_qrcFiles.at(idx + 1); + + m_qrcFiles.removeAt(idx); + if (idx < beforeIdx) + beforeIdx -= 1; + + m_qrcFiles.insert(beforeIdx, qrcFile); + + emit qrcFileMoved(qrcFile, oldBefore); +} + +void QtQrcManager::setInitialState(QtQrcFile *qrcFile, const QtQrcFileData &initialState) +{ + qrcFile->m_initialState = initialState; +} + +void QtQrcManager::removeQrcFile(QtQrcFile *qrcFile) +{ + const int idx = m_qrcFiles.indexOf(qrcFile); + if (idx < 0) + return; + + const auto resourcePrefixes = qrcFile->resourcePrefixList(); + for (QtResourcePrefix *rp : resourcePrefixes) + removeResourcePrefix(rp); + + emit qrcFileRemoved(qrcFile); + + m_qrcFiles.removeAt(idx); + m_pathToQrc.remove(qrcFile->path()); + m_qrcFileToExists.remove(qrcFile); + delete qrcFile; +} + +QtResourcePrefix *QtQrcManager::insertResourcePrefix(QtQrcFile *qrcFile, const QString &prefix, + const QString &language, QtResourcePrefix *beforeResourcePrefix) +{ + if (!qrcFile) + return nullptr; + + int idx = qrcFile->m_resourcePrefixes.indexOf(beforeResourcePrefix); + if (idx < 0) + idx = qrcFile->m_resourcePrefixes.size(); + + QtResourcePrefix *resourcePrefix = new QtResourcePrefix(); + resourcePrefix->m_prefix = prefix; + resourcePrefix->m_language = language; + + qrcFile->m_resourcePrefixes.insert(idx, resourcePrefix); + m_prefixToQrc[resourcePrefix] = qrcFile; + + emit resourcePrefixInserted(resourcePrefix); + return resourcePrefix; +} + +void QtQrcManager::moveResourcePrefix(QtResourcePrefix *resourcePrefix, QtResourcePrefix *beforeResourcePrefix) +{ + if (resourcePrefix == beforeResourcePrefix) + return; + + QtQrcFile *qrcFile = qrcFileOf(resourcePrefix); + if (!qrcFile) + return; + + if (beforeResourcePrefix && qrcFileOf(beforeResourcePrefix) != qrcFile) + return; + + const int idx = qrcFile->m_resourcePrefixes.indexOf(resourcePrefix); + + int beforeIdx = qrcFile->m_resourcePrefixes.indexOf(beforeResourcePrefix); + if (beforeIdx < 0) + beforeIdx = qrcFile->m_resourcePrefixes.size(); + + if (idx == beforeIdx - 1) // the same position, nothing changes + return; + + QtResourcePrefix *oldBefore = nullptr; + if (idx < qrcFile->m_resourcePrefixes.size() - 1) + oldBefore = qrcFile->m_resourcePrefixes.at(idx + 1); + + qrcFile->m_resourcePrefixes.removeAt(idx); + if (idx < beforeIdx) + beforeIdx -= 1; + + qrcFile->m_resourcePrefixes.insert(beforeIdx, resourcePrefix); + + emit resourcePrefixMoved(resourcePrefix, oldBefore); +} + +void QtQrcManager::changeResourcePrefix(QtResourcePrefix *resourcePrefix, const QString &newPrefix) +{ + if (!resourcePrefix) + return; + + const QString oldPrefix = resourcePrefix->m_prefix; + if (oldPrefix == newPrefix) + return; + + resourcePrefix->m_prefix = newPrefix; + + emit resourcePrefixChanged(resourcePrefix, oldPrefix); +} + +void QtQrcManager::changeResourceLanguage(QtResourcePrefix *resourcePrefix, const QString &newLanguage) +{ + if (!resourcePrefix) + return; + + const QString oldLanguage = resourcePrefix->m_language; + if (oldLanguage == newLanguage) + return; + + resourcePrefix->m_language = newLanguage; + + emit resourceLanguageChanged(resourcePrefix, oldLanguage); +} + +void QtQrcManager::removeResourcePrefix(QtResourcePrefix *resourcePrefix) +{ + QtQrcFile *qrcFile = qrcFileOf(resourcePrefix); + if (!qrcFile) + return; + + const int idx = qrcFile->m_resourcePrefixes.indexOf(resourcePrefix); + + const auto resourceFiles = resourcePrefix->resourceFiles(); + for (QtResourceFile *rf : resourceFiles) + removeResourceFile(rf); + + emit resourcePrefixRemoved(resourcePrefix); + + qrcFile->m_resourcePrefixes.removeAt(idx); + m_prefixToQrc.remove(resourcePrefix); + delete resourcePrefix; +} + +QtResourceFile *QtQrcManager::insertResourceFile(QtResourcePrefix *resourcePrefix, const QString &path, + const QString &alias, QtResourceFile *beforeResourceFile) +{ + if (!resourcePrefix) + return nullptr; + + int idx = resourcePrefix->m_resourceFiles.indexOf(beforeResourceFile); + if (idx < 0) + idx = resourcePrefix->m_resourceFiles.size(); + + QtResourceFile *resourceFile = new QtResourceFile(); + resourceFile->m_path = path; + resourceFile->m_alias = alias; + const QFileInfo fi(qrcFileOf(resourcePrefix)->path()); + const QDir dir(fi.absolutePath()); + const QString fullPath = dir.absoluteFilePath(path); + resourceFile->m_fullPath = fullPath; + + resourcePrefix->m_resourceFiles.insert(idx, resourceFile); + m_fileToPrefix[resourceFile] = resourcePrefix; + m_fullPathToResourceFiles[fullPath].append(resourceFile); + if (!m_fullPathToIcon.contains(fullPath)) { + m_fullPathToIcon[fullPath] = QIcon(fullPath); + const QFileInfo fullInfo(fullPath); + m_fullPathToExists[fullPath] = fullInfo.exists(); + } + + emit resourceFileInserted(resourceFile); + return resourceFile; +} + +void QtQrcManager::moveResourceFile(QtResourceFile *resourceFile, QtResourceFile *beforeResourceFile) +{ + if (resourceFile == beforeResourceFile) + return; + + QtResourcePrefix *resourcePrefix = resourcePrefixOf(resourceFile); + if (!resourcePrefix) + return; + + if (beforeResourceFile && resourcePrefixOf(beforeResourceFile) != resourcePrefix) + return; + + const int idx = resourcePrefix->m_resourceFiles.indexOf(resourceFile); + + int beforeIdx = resourcePrefix->m_resourceFiles.indexOf(beforeResourceFile); + if (beforeIdx < 0) + beforeIdx = resourcePrefix->m_resourceFiles.size(); + + if (idx == beforeIdx - 1) // the same position, nothing changes + return; + + QtResourceFile *oldBefore = nullptr; + if (idx < resourcePrefix->m_resourceFiles.size() - 1) + oldBefore = resourcePrefix->m_resourceFiles.at(idx + 1); + + resourcePrefix->m_resourceFiles.removeAt(idx); + if (idx < beforeIdx) + beforeIdx -= 1; + + resourcePrefix->m_resourceFiles.insert(beforeIdx, resourceFile); + + emit resourceFileMoved(resourceFile, oldBefore); +} + +void QtQrcManager::changeResourceAlias(QtResourceFile *resourceFile, const QString &newAlias) +{ + if (!resourceFile) + return; + + const QString oldAlias = resourceFile->m_alias; + if (oldAlias == newAlias) + return; + + resourceFile->m_alias = newAlias; + + emit resourceAliasChanged(resourceFile, oldAlias); +} + +void QtQrcManager::removeResourceFile(QtResourceFile *resourceFile) +{ + QtResourcePrefix *resourcePrefix = resourcePrefixOf(resourceFile); + if (!resourcePrefix) + return; + + const int idx = resourcePrefix->m_resourceFiles.indexOf(resourceFile); + + emit resourceFileRemoved(resourceFile); + + resourcePrefix->m_resourceFiles.removeAt(idx); + m_fileToPrefix.remove(resourceFile); + const QString fullPath = resourceFile->fullPath(); + m_fullPathToResourceFiles[fullPath].removeAll(resourceFile); // optimize me + if (m_fullPathToResourceFiles[fullPath].isEmpty()) { + m_fullPathToResourceFiles.remove(fullPath); + m_fullPathToIcon.remove(fullPath); + m_fullPathToExists.remove(fullPath); + } + delete resourceFile; +} + +} // namespace qdesigner_internal + +using QtResourceFile = qdesigner_internal::QtResourceFile; +using QtResourcePrefix = qdesigner_internal::QtResourcePrefix; +using QtQrcFile = qdesigner_internal::QtQrcFile; +using QtQrcManager = qdesigner_internal::QtQrcManager; + +// ----------------- QtResourceEditorDialogPrivate +class QtResourceEditorDialogPrivate +{ + QtResourceEditorDialog *q_ptr{}; + Q_DECLARE_PUBLIC(QtResourceEditorDialog) +public: + QtResourceEditorDialogPrivate() = default; + + void slotQrcFileInserted(QtQrcFile *qrcFile); + void slotQrcFileMoved(QtQrcFile *qrcFile); + void slotQrcFileRemoved(QtQrcFile *qrcFile); + + QStandardItem *insertResourcePrefix(QtResourcePrefix *resourcePrefix); + + void slotResourcePrefixInserted(QtResourcePrefix *resourcePrefix) { insertResourcePrefix(resourcePrefix); } + void slotResourcePrefixMoved(QtResourcePrefix *resourcePrefix); + void slotResourcePrefixChanged(QtResourcePrefix *resourcePrefix); + void slotResourceLanguageChanged(QtResourcePrefix *resourcePrefix); + void slotResourcePrefixRemoved(QtResourcePrefix *resourcePrefix); + void slotResourceFileInserted(QtResourceFile *resourceFile); + void slotResourceFileMoved(QtResourceFile *resourceFile); + void slotResourceAliasChanged(QtResourceFile *resourceFile); + void slotResourceFileRemoved(QtResourceFile *resourceFile); + + void slotCurrentQrcFileChanged(QListWidgetItem *item); + void slotCurrentTreeViewItemChanged(const QModelIndex &index); + void slotListWidgetContextMenuRequested(const QPoint &pos); + void slotTreeViewContextMenuRequested(const QPoint &pos); + void slotTreeViewItemChanged(QStandardItem *item); + + void slotNewQrcFile(); + void slotImportQrcFile(); + void slotRemoveQrcFile(); + void slotMoveUpQrcFile(); + void slotMoveDownQrcFile(); + + void slotNewPrefix(); + void slotAddFiles(); + void slotChangePrefix(); + void slotChangeLanguage(); + void slotChangeAlias(); + void slotClonePrefix(); + void slotRemove(); + void slotMoveUp(); + void slotMoveDown(); + + bool loadQrcFile(const QString &path, QtQrcFileData *qrcFileData, QString *errorMessage); + bool loadQrcFile(const QString &path, QtQrcFileData *qrcFileData); + bool saveQrcFile(const QtQrcFileData &qrcFileData); + + QString qrcFileText(QtQrcFile *qrcFile) const; + + QMessageBox::StandardButton warning(const QString &title, const QString &text, QMessageBox::StandardButtons buttons = QMessageBox::Ok, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) const; + + QString browseForNewLocation(const QString &resourceFile, const QDir &rootDir) const; + QString copyResourceFile(const QString &resourceFile, const QString &destPath) const; + QtResourceFile *getCurrentResourceFile() const; + QtResourcePrefix *getCurrentResourcePrefix() const; + void selectTreeRow(QStandardItem *item); + QString getSaveFileNameWithExtension(QWidget *parent, + const QString &title, QString dir, const QString &filter, const QString &extension) const; + QString qrcStartDirectory() const; + + Ui::QtResourceEditorDialog m_ui; + QDesignerFormEditorInterface *m_core = nullptr; + QtResourceModel *m_resourceModel = nullptr; + QDesignerDialogGuiInterface *m_dlgGui = nullptr; + QtQrcManager *m_qrcManager = nullptr; + QList m_initialState; + + QHash m_qrcFileToItem; + QHash m_itemToQrcFile; + QHash m_resourcePrefixToPrefixItem; + QHash m_resourcePrefixToLanguageItem; + QHash m_prefixItemToResourcePrefix; + QHash m_languageItemToResourcePrefix; + QHash m_resourceFileToPathItem; + QHash m_resourceFileToAliasItem; + QHash m_pathItemToResourceFile; + QHash m_aliasItemToResourceFile; + + bool m_ignoreCurrentChanged = false; + bool m_firstQrcFileDialog = true; + QtQrcFile *m_currentQrcFile = nullptr; + + QAction *m_newQrcFileAction = nullptr; + QAction *m_importQrcFileAction = nullptr; + QAction *m_removeQrcFileAction = nullptr; + QAction *m_moveUpQrcFileAction = nullptr; + QAction *m_moveDownQrcFileAction = nullptr; + + QAction *m_newPrefixAction = nullptr; + QAction *m_addResourceFileAction = nullptr; + QAction *m_changePrefixAction = nullptr; + QAction *m_changeLanguageAction = nullptr; + QAction *m_changeAliasAction = nullptr; + QAction *m_clonePrefixAction = nullptr; + QAction *m_moveUpAction = nullptr; + QAction *m_moveDownAction = nullptr; + QAction *m_removeAction = nullptr; + + QStandardItemModel *m_treeModel = nullptr; + QItemSelectionModel *m_treeSelection = nullptr; +}; + +QMessageBox::StandardButton QtResourceEditorDialogPrivate::warning(const QString &title, const QString &text, QMessageBox::StandardButtons buttons, + QMessageBox::StandardButton defaultButton) const +{ + return m_dlgGui->message(q_ptr, QDesignerDialogGuiInterface::ResourceEditorMessage, QMessageBox::Warning, title, text, buttons, defaultButton); +} + +QString QtResourceEditorDialogPrivate::qrcFileText(QtQrcFile *qrcFile) const +{ + const QString path = qrcFile->path(); + const QString fileName = qrcFile->fileName(); + const QFileInfo fi(path); + if (fi.exists() && !fi.isWritable()) + return QApplication::translate("QtResourceEditorDialog", "%1 [read-only]").arg(fileName); + if (!m_qrcManager->exists(qrcFile)) + return QApplication::translate("QtResourceEditorDialog", "%1 [missing]").arg(fileName); + return fileName; +} + +void QtResourceEditorDialogPrivate::slotQrcFileInserted(QtQrcFile *qrcFile) +{ + QListWidgetItem *currentItem = m_ui.qrcFileList->currentItem(); + int idx = m_ui.qrcFileList->count(); + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(qrcFile); + QListWidgetItem *nextItem = m_qrcFileToItem.value(nextQrcFile); + if (nextItem) { + const int row = m_ui.qrcFileList->row(nextItem); + if (row >= 0) + idx = row; + } + const QString path = qrcFile->path(); + QListWidgetItem *item = new QListWidgetItem(qrcFileText(qrcFile)); + item->setToolTip(path); + m_ignoreCurrentChanged = true; + m_ui.qrcFileList->insertItem(idx, item); + m_ui.qrcFileList->setCurrentItem(currentItem); + m_ignoreCurrentChanged = false; + m_qrcFileToItem[qrcFile] = item; + m_itemToQrcFile[item] = qrcFile; + if (!m_qrcManager->exists(qrcFile)) + item->setForeground(QBrush(Qt::red)); +} + +void QtResourceEditorDialogPrivate::slotQrcFileMoved(QtQrcFile *qrcFile) +{ + QListWidgetItem *currentItem = m_ui.qrcFileList->currentItem(); + QListWidgetItem *item = m_qrcFileToItem.value(qrcFile); + m_ignoreCurrentChanged = true; + m_ui.qrcFileList->takeItem(m_ui.qrcFileList->row(item)); + + int idx = m_ui.qrcFileList->count(); + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(qrcFile); + QListWidgetItem *nextItem = m_qrcFileToItem.value(nextQrcFile); + if (nextItem) { + int row = m_ui.qrcFileList->row(nextItem); + if (row >= 0) + idx = row; + } + m_ui.qrcFileList->insertItem(idx, item); + if (currentItem == item) + m_ui.qrcFileList->setCurrentItem(item); + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotQrcFileRemoved(QtQrcFile *qrcFile) +{ + QListWidgetItem *item = m_qrcFileToItem.value(qrcFile); + if (item == m_ui.qrcFileList->currentItem()) + m_ui.qrcFileList->setCurrentItem(nullptr); // this should trigger list view signal currentItemChanged(0), and slot should set m_currentQrcFile to 0 + m_ignoreCurrentChanged = true; + delete item; + m_ignoreCurrentChanged = false; + m_itemToQrcFile.remove(item); + m_qrcFileToItem.remove(qrcFile); +} + +QStandardItem *QtResourceEditorDialogPrivate::insertResourcePrefix(QtResourcePrefix *resourcePrefix) +{ + if (m_qrcManager->qrcFileOf(resourcePrefix) != m_currentQrcFile) + return nullptr; + + QtResourcePrefix *prevResourcePrefix = m_qrcManager->prevResourcePrefix(resourcePrefix); + QStandardItem *prevItem = m_resourcePrefixToPrefixItem.value(prevResourcePrefix); + + int row = 0; + if (prevItem) + row = m_treeModel->indexFromItem(prevItem).row() + 1; + + QStandardItem *prefixItem = new QStandardItem(); + QStandardItem *languageItem = new QStandardItem(); + QList items; + items << prefixItem; + items << languageItem; + m_treeModel->insertRow(row, items); + const QModelIndex newIndex = m_treeModel->indexFromItem(prefixItem); + m_ui.resourceTreeView->setExpanded(newIndex, true); + prefixItem->setFlags(prefixItem->flags() | Qt::ItemIsEditable); + languageItem->setFlags(languageItem->flags() | Qt::ItemIsEditable); + m_resourcePrefixToPrefixItem[resourcePrefix] = prefixItem; + m_resourcePrefixToLanguageItem[resourcePrefix] = languageItem; + m_prefixItemToResourcePrefix[prefixItem] = resourcePrefix; + m_languageItemToResourcePrefix[languageItem] = resourcePrefix; + slotResourcePrefixChanged(resourcePrefix); + slotResourceLanguageChanged(resourcePrefix); + return prefixItem; +} + +void QtResourceEditorDialogPrivate::slotResourcePrefixMoved(QtResourcePrefix *resourcePrefix) +{ + QStandardItem *prefixItem = m_resourcePrefixToPrefixItem.value(resourcePrefix); + if (!prefixItem) + return; + + QStandardItem *languageItem = m_resourcePrefixToLanguageItem.value(resourcePrefix); + if (!languageItem) + return; + + const QModelIndex index = m_treeModel->indexFromItem(prefixItem); + const bool expanded = m_ui.resourceTreeView->isExpanded(index); + m_ignoreCurrentChanged = true; + const auto items = m_treeModel->takeRow(index.row()); + + int row = m_treeModel->rowCount(); + QtResourcePrefix *nextResourcePrefix = m_qrcManager->nextResourcePrefix(resourcePrefix); + QStandardItem *nextItem = m_resourcePrefixToPrefixItem.value(nextResourcePrefix); + if (nextItem) + row = m_treeModel->indexFromItem(nextItem).row(); + m_treeModel->insertRow(row, items); + m_ignoreCurrentChanged = false; + m_ui.resourceTreeView->setExpanded(m_treeModel->indexFromItem(items.at(0)), expanded); +} + +void QtResourceEditorDialogPrivate::slotResourcePrefixChanged(QtResourcePrefix *resourcePrefix) +{ + QStandardItem *item = m_resourcePrefixToPrefixItem.value(resourcePrefix); + if (!item) + return; + + m_ignoreCurrentChanged = true; + QString prefix = resourcePrefix->prefix(); + if (prefix.isEmpty()) + prefix = QCoreApplication::translate("QtResourceEditorDialog", ""); + item->setText(prefix); + item->setToolTip(prefix); + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotResourceLanguageChanged(QtResourcePrefix *resourcePrefix) +{ + QStandardItem *item = m_resourcePrefixToLanguageItem.value(resourcePrefix); + if (!item) + return; + + m_ignoreCurrentChanged = true; + const QString language = resourcePrefix->language(); + item->setText(language); + item->setToolTip(language); + + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotResourcePrefixRemoved(QtResourcePrefix *resourcePrefix) +{ + QStandardItem *prefixItem = m_resourcePrefixToPrefixItem.value(resourcePrefix); + if (!prefixItem) + return; + + QStandardItem *languageItem = m_resourcePrefixToLanguageItem.value(resourcePrefix); + if (!languageItem) + return; + + m_ignoreCurrentChanged = true; + m_treeModel->takeRow(m_treeModel->indexFromItem(prefixItem).row()); + delete prefixItem; + delete languageItem; + m_ignoreCurrentChanged = false; + m_prefixItemToResourcePrefix.remove(prefixItem); + m_languageItemToResourcePrefix.remove(languageItem); + m_resourcePrefixToPrefixItem.remove(resourcePrefix); + m_resourcePrefixToLanguageItem.remove(resourcePrefix); +} + +void QtResourceEditorDialogPrivate::slotResourceFileInserted(QtResourceFile *resourceFile) +{ + QtResourcePrefix *resourcePrefix = m_qrcManager->resourcePrefixOf(resourceFile); + if (m_qrcManager->qrcFileOf(resourcePrefix) != m_currentQrcFile) + return; + + QtResourceFile *prevResourceFile = m_qrcManager->prevResourceFile(resourceFile); + QStandardItem *prevItem = m_resourceFileToPathItem.value(prevResourceFile); + + QStandardItem *pathItem = new QStandardItem(resourceFile->path()); + QStandardItem *aliasItem = new QStandardItem(); + QStandardItem *parentItem = m_resourcePrefixToPrefixItem.value(resourcePrefix); + QList items; + items << pathItem; + items << aliasItem; + + int row = 0; + if (prevItem) + row = m_treeModel->indexFromItem(prevItem).row() + 1; + + parentItem->insertRow(row, items); + + pathItem->setFlags(pathItem->flags() & ~Qt::ItemIsEditable); + aliasItem->setFlags(aliasItem->flags() | Qt::ItemIsEditable); + m_resourceFileToPathItem[resourceFile] = pathItem; + m_resourceFileToAliasItem[resourceFile] = aliasItem; + m_pathItemToResourceFile[pathItem] = resourceFile; + m_aliasItemToResourceFile[aliasItem] = resourceFile; + pathItem->setToolTip(resourceFile->path()); + pathItem->setIcon(m_qrcManager->icon(resourceFile->fullPath())); + if (!m_qrcManager->exists(resourceFile->fullPath())) { + pathItem->setText(QApplication::translate("QtResourceEditorDialog", "%1 [missing]").arg(resourceFile->path())); + QBrush redBrush(Qt::red); + pathItem->setForeground(redBrush); + aliasItem->setForeground(redBrush); + } + slotResourceAliasChanged(resourceFile); +} + +void QtResourceEditorDialogPrivate::slotResourceFileMoved(QtResourceFile *resourceFile) +{ + QStandardItem *pathItem = m_resourceFileToPathItem.value(resourceFile); + if (!pathItem) + return; + + QStandardItem *aliasItem = m_resourceFileToAliasItem.value(resourceFile); + if (!aliasItem) + return; + + QStandardItem *parentItem = pathItem->parent(); + m_ignoreCurrentChanged = true; + const auto items = parentItem->takeRow(m_treeModel->indexFromItem(pathItem).row()); + + int row = parentItem->rowCount(); + QtResourceFile *nextResourceFile = m_qrcManager->nextResourceFile(resourceFile); + QStandardItem *nextItem = m_resourceFileToPathItem.value(nextResourceFile); + if (nextItem) + row = m_treeModel->indexFromItem(nextItem).row(); + parentItem->insertRow(row, items); + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotResourceAliasChanged(QtResourceFile *resourceFile) +{ + QStandardItem *item = m_resourceFileToAliasItem.value(resourceFile); + if (!item) + return; + + m_ignoreCurrentChanged = true; + const QString alias = resourceFile->alias(); + item->setText(alias); + item->setToolTip(alias); + + m_ignoreCurrentChanged = false; +} + +void QtResourceEditorDialogPrivate::slotResourceFileRemoved(QtResourceFile *resourceFile) +{ + QStandardItem *pathItem = m_resourceFileToPathItem.value(resourceFile); + if (!pathItem) + return; + + QStandardItem *aliasItem = m_resourceFileToAliasItem.value(resourceFile); + if (!aliasItem) + return; + + QStandardItem *parentItem = pathItem->parent(); + + m_ignoreCurrentChanged = true; + parentItem->takeRow(m_treeModel->indexFromItem(pathItem).row()); + delete pathItem; + delete aliasItem; + m_ignoreCurrentChanged = false; + m_pathItemToResourceFile.remove(pathItem); + m_aliasItemToResourceFile.remove(aliasItem); + m_resourceFileToPathItem.remove(resourceFile); + m_resourceFileToAliasItem.remove(resourceFile); +} + + +void QtResourceEditorDialogPrivate::slotCurrentQrcFileChanged(QListWidgetItem *item) +{ + if (m_ignoreCurrentChanged) + return; + + QtQrcFile *newCurrentQrcFile = m_itemToQrcFile.value(item); + + if (newCurrentQrcFile == m_currentQrcFile) + return; + + if (m_currentQrcFile) { + QHash currentPrefixList = m_resourcePrefixToPrefixItem; + for (auto it = currentPrefixList.cbegin(), end = currentPrefixList.cend(); it != end; ++it) { + QtResourcePrefix *resourcePrefix = it.key(); + const auto currentResourceFiles = resourcePrefix->resourceFiles(); + for (QtResourceFile *rf : currentResourceFiles) + slotResourceFileRemoved(rf); + slotResourcePrefixRemoved(resourcePrefix); + } + } + + m_currentQrcFile = newCurrentQrcFile; + slotCurrentTreeViewItemChanged(QModelIndex()); + QStandardItem *firstPrefix = nullptr; // select first prefix + if (m_currentQrcFile) { + const auto newPrefixList = m_currentQrcFile->resourcePrefixList(); + for (QtResourcePrefix *resourcePrefix : newPrefixList) { + if (QStandardItem *newPrefixItem = insertResourcePrefix(resourcePrefix)) + if (!firstPrefix) + firstPrefix = newPrefixItem; + const auto newResourceFiles = resourcePrefix->resourceFiles(); + for (QtResourceFile *rf : newResourceFiles) + slotResourceFileInserted(rf); + } + } + m_ui.resourceTreeView->setCurrentIndex(firstPrefix ? m_treeModel->indexFromItem(firstPrefix) : QModelIndex()); + + m_removeQrcFileAction->setEnabled(m_currentQrcFile); + m_moveUpQrcFileAction->setEnabled(m_currentQrcFile && m_qrcManager->prevQrcFile(m_currentQrcFile)); + m_moveDownQrcFileAction->setEnabled(m_currentQrcFile && m_qrcManager->nextQrcFile(m_currentQrcFile)); +} + +void QtResourceEditorDialogPrivate::slotCurrentTreeViewItemChanged(const QModelIndex &index) +{ + QStandardItem *item = m_treeModel->itemFromIndex(index); + QtResourceFile *resourceFile = m_pathItemToResourceFile.value(item); + if (!resourceFile) + resourceFile = m_aliasItemToResourceFile.value(item); + QtResourcePrefix *resourcePrefix = m_prefixItemToResourcePrefix.value(item); + if (!resourcePrefix) + resourcePrefix = m_languageItemToResourcePrefix.value(item); + + bool moveUpEnabled = false; + bool moveDownEnabled = false; + bool currentItem = resourceFile || resourcePrefix; + + if (resourceFile) { + if (m_qrcManager->prevResourceFile(resourceFile)) + moveUpEnabled = true; + if (m_qrcManager->nextResourceFile(resourceFile)) + moveDownEnabled = true; + } else if (resourcePrefix) { + if (m_qrcManager->prevResourcePrefix(resourcePrefix)) + moveUpEnabled = true; + if (m_qrcManager->nextResourcePrefix(resourcePrefix)) + moveDownEnabled = true; + } + + m_newPrefixAction->setEnabled(m_currentQrcFile); + m_addResourceFileAction->setEnabled(currentItem); + m_changePrefixAction->setEnabled(currentItem); + m_changeLanguageAction->setEnabled(currentItem); + m_changeAliasAction->setEnabled(resourceFile); + m_removeAction->setEnabled(currentItem); + m_moveUpAction->setEnabled(moveUpEnabled); + m_moveDownAction->setEnabled(moveDownEnabled); + m_clonePrefixAction->setEnabled(currentItem); +} + +void QtResourceEditorDialogPrivate::slotListWidgetContextMenuRequested(const QPoint &pos) +{ + QMenu menu(q_ptr); + menu.addAction(m_newQrcFileAction); + menu.addAction(m_importQrcFileAction); + menu.addAction(m_removeQrcFileAction); + menu.addSeparator(); + menu.addAction(m_moveUpQrcFileAction); + menu.addAction(m_moveDownQrcFileAction); + menu.exec(m_ui.qrcFileList->mapToGlobal(pos)); +} + +void QtResourceEditorDialogPrivate::slotTreeViewContextMenuRequested(const QPoint &pos) +{ + QMenu menu(q_ptr); + menu.addAction(m_newPrefixAction); + menu.addAction(m_addResourceFileAction); + menu.addAction(m_removeAction); + menu.addSeparator(); + menu.addAction(m_changePrefixAction); + menu.addAction(m_changeLanguageAction); + menu.addAction(m_changeAliasAction); + menu.addSeparator(); + menu.addAction(m_clonePrefixAction); + menu.addSeparator(); + menu.addAction(m_moveUpAction); + menu.addAction(m_moveDownAction); + menu.exec(m_ui.resourceTreeView->mapToGlobal(pos)); +} + +void QtResourceEditorDialogPrivate::slotTreeViewItemChanged(QStandardItem *item) +{ + if (m_ignoreCurrentChanged) + return; + + const QString newValue = item->text(); + QtResourceFile *resourceFile = m_aliasItemToResourceFile.value(item); + if (resourceFile) { + m_qrcManager->changeResourceAlias(resourceFile, newValue); + return; + } + + QtResourcePrefix *resourcePrefix = m_prefixItemToResourcePrefix.value(item); + if (resourcePrefix) { + m_qrcManager->changeResourcePrefix(resourcePrefix, newValue); + return; + } + + resourcePrefix = m_languageItemToResourcePrefix.value(item); + if (resourcePrefix) { + m_qrcManager->changeResourceLanguage(resourcePrefix, newValue); + return; + } +} + +QString QtResourceEditorDialogPrivate::getSaveFileNameWithExtension(QWidget *parent, + const QString &title, QString dir, const QString &filter, const QString &extension) const +{ + QString saveFile; + while (true) { + saveFile = m_dlgGui->getSaveFileName(parent, title, dir, filter, nullptr, QFileDialog::DontConfirmOverwrite); + if (saveFile.isEmpty()) + return saveFile; + + const QFileInfo fInfo(saveFile); + if (fInfo.suffix().isEmpty() && !fInfo.fileName().endsWith(u'.')) + saveFile += u'.' + extension; + + const QFileInfo fi(saveFile); + if (!fi.exists()) + break; + + if (warning(title, msgOverwrite(fi.fileName()), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) + break; + + dir = saveFile; + } + return saveFile; +} + +QString QtResourceEditorDialogPrivate::qrcStartDirectory() const +{ + if (!m_currentQrcFile) + return QString(); + const QDir dir = QFileInfo(m_currentQrcFile->path()).dir(); + return dir.exists() ? dir.absolutePath() : QString(); +} + +void QtResourceEditorDialogPrivate::slotNewQrcFile() +{ + const QString qrcPath = getSaveFileNameWithExtension(q_ptr, + QCoreApplication::translate("QtResourceEditorDialog", "New Resource File"), + m_firstQrcFileDialog ? qrcStartDirectory() : QString(), + QCoreApplication::translate("QtResourceEditorDialog", "Resource files (*.qrc)"), + u"qrc"_s); + if (qrcPath.isEmpty()) + return; + + m_firstQrcFileDialog = false; + if (QtQrcFile *sameQrcFile = m_qrcManager->qrcFileOf(qrcPath)) { + // QMessageBox ??? + QListWidgetItem *item = m_qrcFileToItem.value(sameQrcFile); + m_ui.qrcFileList->setCurrentItem(item); + item->setSelected(true); + return; + } + + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(m_currentQrcFile); + + QtQrcFile *qrcFile = m_qrcManager->insertQrcFile(qrcPath, nextQrcFile, true); + m_ui.qrcFileList->setCurrentItem(m_qrcFileToItem.value(qrcFile)); +} + +void QtResourceEditorDialogPrivate::slotImportQrcFile() +{ + const QString qrcPath = m_dlgGui->getOpenFileName(q_ptr, + QCoreApplication::translate("QtResourceEditorDialog", "Import Resource File"), + m_firstQrcFileDialog ? qrcStartDirectory() : QString(), + QCoreApplication::translate("QtResourceEditorDialog", "Resource files (*.qrc)")); + if (qrcPath.isEmpty()) + return; + m_firstQrcFileDialog = false; + if (QtQrcFile *sameQrcFile = m_qrcManager->qrcFileOf(qrcPath)) { + // QMessageBox ??? + QListWidgetItem *item = m_qrcFileToItem.value(sameQrcFile); + m_ui.qrcFileList->setCurrentItem(item); + item->setSelected(true); + return; + } + + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(m_currentQrcFile); + + QtQrcFileData qrcFileData; + loadQrcFile(qrcPath, &qrcFileData); + QtQrcFile *qrcFile = m_qrcManager->importQrcFile(qrcFileData, nextQrcFile); + m_ui.qrcFileList->setCurrentItem(m_qrcFileToItem.value(qrcFile)); +} + +void QtResourceEditorDialogPrivate::slotRemoveQrcFile() +{ + if (!m_currentQrcFile) + return; + + QtQrcFile *currentQrcFile = m_qrcManager->nextQrcFile(m_currentQrcFile); + if (!currentQrcFile) + currentQrcFile = m_qrcManager->prevQrcFile(m_currentQrcFile); + + m_qrcManager->removeQrcFile(m_currentQrcFile); + QListWidgetItem *item = m_qrcFileToItem.value(currentQrcFile); + if (item) { + m_ui.qrcFileList->setCurrentItem(item); + item->setSelected(true); + } +} + +void QtResourceEditorDialogPrivate::slotMoveUpQrcFile() +{ + if (!m_currentQrcFile) + return; + + QtQrcFile *prevQrcFile = m_qrcManager->prevQrcFile(m_currentQrcFile); + if (!prevQrcFile) + return; + + m_qrcManager->moveQrcFile(m_currentQrcFile, prevQrcFile); +} + +void QtResourceEditorDialogPrivate::slotMoveDownQrcFile() +{ + if (!m_currentQrcFile) + return; + + QtQrcFile *nextQrcFile = m_qrcManager->nextQrcFile(m_currentQrcFile); + if (!nextQrcFile) + return; + nextQrcFile = m_qrcManager->nextQrcFile(nextQrcFile); + + m_qrcManager->moveQrcFile(m_currentQrcFile, nextQrcFile); +} + +QtResourceFile *QtResourceEditorDialogPrivate::getCurrentResourceFile() const +{ + QStandardItem *currentItem = m_treeModel->itemFromIndex(m_treeSelection->currentIndex()); + + + QtResourceFile *currentResourceFile = nullptr; + if (currentItem) { + currentResourceFile = m_pathItemToResourceFile.value(currentItem); + if (!currentResourceFile) + currentResourceFile = m_aliasItemToResourceFile.value(currentItem); + } + return currentResourceFile; +} + +QtResourcePrefix *QtResourceEditorDialogPrivate::getCurrentResourcePrefix() const +{ + QStandardItem *currentItem = m_treeModel->itemFromIndex(m_treeSelection->currentIndex()); + + QtResourcePrefix *currentResourcePrefix = nullptr; + if (currentItem) { + currentResourcePrefix = m_prefixItemToResourcePrefix.value(currentItem); + if (!currentResourcePrefix) { + currentResourcePrefix = m_languageItemToResourcePrefix.value(currentItem); + if (!currentResourcePrefix) { + QtResourceFile *currentResourceFile = getCurrentResourceFile(); + if (currentResourceFile) + currentResourcePrefix = m_qrcManager->resourcePrefixOf(currentResourceFile); + } + } + } + return currentResourcePrefix; +} + +void QtResourceEditorDialogPrivate::selectTreeRow(QStandardItem *item) +{ + const QModelIndex index = m_treeModel->indexFromItem(item); + m_treeSelection->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + m_treeSelection->setCurrentIndex(index, QItemSelectionModel::Select); +} + +void QtResourceEditorDialogPrivate::slotNewPrefix() +{ + if (!m_currentQrcFile) + return; + + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + QtResourcePrefix *nextResourcePrefix = m_qrcManager->nextResourcePrefix(currentResourcePrefix); + QtResourcePrefix *newResourcePrefix = m_qrcManager->insertResourcePrefix(m_currentQrcFile, + QCoreApplication::translate("QtResourceEditorDialog", "newPrefix"), + QString(), nextResourcePrefix); + if (!newResourcePrefix) + return; + + QStandardItem *newItem = m_resourcePrefixToPrefixItem.value(newResourcePrefix); + if (!newItem) + return; + + const QModelIndex index = m_treeModel->indexFromItem(newItem); + selectTreeRow(newItem); + m_ui.resourceTreeView->edit(index); +} + +static inline QString outOfPathWarning(const QString &fname) +{ + return QApplication::translate("QtResourceEditorDialog", + "

Warning: The file

" + "

%1

" + "

is outside of the current resource file's parent directory.

").arg(fname); +} + +static inline QString outOfPathWarningInfo() +{ + return QApplication::translate("QtResourceEditorDialog", + "

To resolve the issue, press:

" + "" + "" + "" + "
Copyto copy the file to the resource file's parent directory.
Copy As...to copy the file into a subdirectory of the resource file's parent directory.
Keepto use its current location.
"); +} + +void QtResourceEditorDialogPrivate::slotAddFiles() +{ + if (!m_currentQrcFile) + return; + + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + QtResourceFile *currentResourceFile = getCurrentResourceFile(); + if (!currentResourcePrefix) + return; + + QString initialPath = m_currentQrcFile->path(); + if (currentResourceFile) { + QFileInfo fi(currentResourceFile->fullPath()); + initialPath = fi.absolutePath(); + } + + const QStringList resourcePaths = m_dlgGui->getOpenImageFileNames(q_ptr, + QCoreApplication::translate("QtResourceEditorDialog", "Add Files"), + initialPath); + if (resourcePaths.isEmpty()) + return; + + QtResourceFile *nextResourceFile = m_qrcManager->nextResourceFile(currentResourceFile); + if (!currentResourceFile) { + const auto resourceFiles = currentResourcePrefix->resourceFiles(); + if (!resourceFiles.isEmpty()) + nextResourceFile = resourceFiles.first(); + } + + const QFileInfo fi(m_currentQrcFile->path()); + const QString destDir = fi.absolutePath(); + const QDir dir(fi.absolutePath()); + for (QString resourcePath : resourcePaths) { + QString relativePath = dir.relativeFilePath(resourcePath); + if (relativePath.startsWith(".."_L1)) { + QMessageBox msgBox(QMessageBox::Warning, + QCoreApplication::translate("QtResourceEditorDialog", "Incorrect Path"), + outOfPathWarning(relativePath), QMessageBox::Cancel); + msgBox.setInformativeText(outOfPathWarningInfo()); + QPushButton *copyButton = msgBox.addButton(QCoreApplication::translate("QtResourceEditorDialog", + "Copy"), QMessageBox::ActionRole); + QPushButton *copyAsButton = msgBox.addButton(QCoreApplication::translate("QtResourceEditorDialog", + "Copy As..."), QMessageBox::ActionRole); + QPushButton *keepButton = msgBox.addButton(QCoreApplication::translate("QtResourceEditorDialog", + "Keep"), QMessageBox::ActionRole); + QPushButton *skipButton = msgBox.addButton(QCoreApplication::translate("QtResourceEditorDialog", + "Skip"), QMessageBox::ActionRole); + msgBox.setEscapeButton(QMessageBox::Cancel); + msgBox.setDefaultButton(copyButton); + msgBox.exec(); + QString destPath; + if (msgBox.clickedButton() == keepButton) { + // nothing + } else if (msgBox.clickedButton() == copyButton) { + QFileInfo resInfo(resourcePath); + QDir dd(destDir); + destPath = dd.absoluteFilePath(resInfo.fileName()); + if (dd.exists(resInfo.fileName())) { + if (warning(QCoreApplication::translate("QtResourceEditorDialog", "Copy"), + msgOverwrite(resInfo.fileName()), + QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Yes) + continue; + } + resourcePath = copyResourceFile(resourcePath, destPath); // returns empty string in case copy failed or was canceled + } else if (msgBox.clickedButton() == copyAsButton) { + destPath = browseForNewLocation(resourcePath, dir); // returns empty string in case browsing was canceled + if (destPath.isEmpty()) + continue; + resourcePath = copyResourceFile(resourcePath, destPath); // returns empty string in case copy failed or was canceled + } else if (msgBox.clickedButton() == skipButton) { // skipped + continue; + } else { // canceled + return; + } + if (resourcePath.isEmpty()) + continue; + } + relativePath = dir.relativeFilePath(resourcePath); + QtResourceFile *newResourceFile = m_qrcManager->insertResourceFile(currentResourcePrefix, relativePath, QString(), nextResourceFile); + + QStandardItem *newItem = m_resourceFileToPathItem.value(newResourceFile); + if (newItem) + selectTreeRow(newItem); + } +} + +void QtResourceEditorDialogPrivate::slotChangePrefix() +{ + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + if (!currentResourcePrefix) + return; + + QStandardItem *item = m_resourcePrefixToPrefixItem.value(currentResourcePrefix); + QModelIndex index = m_treeModel->indexFromItem(item); + selectTreeRow(item); + m_ui.resourceTreeView->scrollTo(index); + m_ui.resourceTreeView->edit(index); +} + +void QtResourceEditorDialogPrivate::slotChangeLanguage() +{ + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + if (!currentResourcePrefix) + return; + + QStandardItem *item = m_resourcePrefixToLanguageItem.value(currentResourcePrefix); + QModelIndex index = m_treeModel->indexFromItem(item); + selectTreeRow(item); + m_ui.resourceTreeView->scrollTo(index); + m_ui.resourceTreeView->edit(index); +} + +void QtResourceEditorDialogPrivate::slotChangeAlias() +{ + QtResourceFile *currentResourceFile = getCurrentResourceFile(); + if (!currentResourceFile) + return; + + QStandardItem *item = m_resourceFileToAliasItem.value(currentResourceFile); + QModelIndex index = m_treeModel->indexFromItem(item); + selectTreeRow(item); + m_ui.resourceTreeView->scrollTo(index); + m_ui.resourceTreeView->edit(index); +} + +void QtResourceEditorDialogPrivate::slotClonePrefix() +{ + QtResourcePrefix *currentResourcePrefix = getCurrentResourcePrefix(); + if (!currentResourcePrefix) + return; + + bool ok; + QString suffix = QInputDialog::getText(q_ptr, QApplication::translate("QtResourceEditorDialog", "Clone Prefix"), + QCoreApplication::translate("QtResourceEditorDialog", "Enter the suffix which you want to add to the names of the cloned files.\n" + "This could for example be a language extension like \"_de\"."), + QLineEdit::Normal, QString(), &ok); + if (!ok) + return; + + QtResourcePrefix *newResourcePrefix = m_qrcManager->insertResourcePrefix(m_currentQrcFile, currentResourcePrefix->prefix(), + currentResourcePrefix->language(), m_qrcManager->nextResourcePrefix(currentResourcePrefix)); + if (newResourcePrefix) { + const auto files = currentResourcePrefix->resourceFiles(); + for (QtResourceFile *resourceFile : files) { + QString path = resourceFile->path(); + QFileInfo fi(path); + QDir dir(fi.dir()); + QString oldSuffix = fi.completeSuffix(); + if (!oldSuffix.isEmpty()) + oldSuffix = u'.' + oldSuffix; + const QString newBaseName = fi.baseName() + suffix + oldSuffix; + const QString newPath = QDir::cleanPath(dir.filePath(newBaseName)); + m_qrcManager->insertResourceFile(newResourcePrefix, newPath, + resourceFile->alias()); + } + } +} + +void QtResourceEditorDialogPrivate::slotRemove() +{ + QStandardItem *item = m_treeModel->itemFromIndex(m_treeSelection->currentIndex()); + if (!item) + return; + + QtResourceFile *resourceFile = m_pathItemToResourceFile.value(item); + if (!resourceFile) + resourceFile = m_aliasItemToResourceFile.value(item); + QtResourcePrefix *resourcePrefix = m_prefixItemToResourcePrefix.value(item); + if (!resourcePrefix) + resourcePrefix = m_languageItemToResourcePrefix.value(item); + + QStandardItem *newCurrentItem = nullptr; + + if (resourceFile) { + QtResourceFile *nextFile = m_qrcManager->nextResourceFile(resourceFile); + if (!nextFile) + nextFile = m_qrcManager->prevResourceFile(resourceFile); + newCurrentItem = m_resourceFileToPathItem.value(nextFile); + if (!newCurrentItem) + newCurrentItem = m_resourcePrefixToPrefixItem.value(m_qrcManager->resourcePrefixOf(resourceFile)); + } + if (!newCurrentItem) { + QtResourcePrefix *nextPrefix = m_qrcManager->nextResourcePrefix(resourcePrefix); + if (!nextPrefix) + nextPrefix = m_qrcManager->prevResourcePrefix(resourcePrefix); + newCurrentItem = m_resourcePrefixToPrefixItem.value(nextPrefix); + } + + selectTreeRow(newCurrentItem); + + if (resourcePrefix) + m_qrcManager->removeResourcePrefix(resourcePrefix); + else if (resourceFile) + m_qrcManager->removeResourceFile(resourceFile); +} + +void QtResourceEditorDialogPrivate::slotMoveUp() +{ + if (QtResourceFile *resourceFile = getCurrentResourceFile()) { + QtResourceFile *prevFile = m_qrcManager->prevResourceFile(resourceFile); + + if (!prevFile) + return; + + m_qrcManager->moveResourceFile(resourceFile, prevFile); + selectTreeRow(m_resourceFileToPathItem.value(resourceFile)); + } else if (QtResourcePrefix *resourcePrefix = getCurrentResourcePrefix()) { + QtResourcePrefix *prevPrefix = m_qrcManager->prevResourcePrefix(resourcePrefix); + + if (!prevPrefix) + return; + + m_qrcManager->moveResourcePrefix(resourcePrefix, prevPrefix); + selectTreeRow(m_resourcePrefixToPrefixItem.value(resourcePrefix)); + } +} + +void QtResourceEditorDialogPrivate::slotMoveDown() +{ + if (QtResourceFile *resourceFile = getCurrentResourceFile()) { + QtResourceFile *nextFile = m_qrcManager->nextResourceFile(resourceFile); + + if (!nextFile) + return; + + m_qrcManager->moveResourceFile(resourceFile, m_qrcManager->nextResourceFile(nextFile)); + selectTreeRow(m_resourceFileToPathItem.value(resourceFile)); + } else if (QtResourcePrefix *resourcePrefix = getCurrentResourcePrefix()) { + QtResourcePrefix *nextPrefix = m_qrcManager->nextResourcePrefix(resourcePrefix); + + if (!nextPrefix) + return; + + m_qrcManager->moveResourcePrefix(resourcePrefix, m_qrcManager->nextResourcePrefix(nextPrefix)); + selectTreeRow(m_resourcePrefixToPrefixItem.value(resourcePrefix)); + } +} + +QString QtResourceEditorDialogPrivate::browseForNewLocation(const QString &resourceFile, const QDir &rootDir) const +{ + QFileInfo fi(resourceFile); + const QString initialPath = rootDir.absoluteFilePath(fi.fileName()); + while (true) { + QString newPath = m_dlgGui->getSaveFileName(q_ptr, + QCoreApplication::translate("QtResourceEditorDialog", "Copy As"), + initialPath); + QString relativePath = rootDir.relativeFilePath(newPath); + if (relativePath.startsWith(".."_L1)) { + if (warning(QCoreApplication::translate("QtResourceEditorDialog", "Copy As"), + QCoreApplication::translate("QtResourceEditorDialog", "

The selected file:

" + "

%1

is outside of the current resource file's directory:

%2

" + "

Please select another path within this directory.

"). + arg(relativePath, rootDir.absolutePath()), + QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok) != QMessageBox::Ok) + return QString(); + } else { + return newPath; + } + } + + return QString(); +} + +QString QtResourceEditorDialogPrivate::copyResourceFile(const QString &resourceFile, const QString &destPath) const +{ + QFileInfo fi(destPath); + if (fi.exists()) { + while (fi.exists() && !QFile::remove(destPath)) { + if (warning(QCoreApplication::translate("QtResourceEditorDialog", "Copy"), + QCoreApplication::translate("QtResourceEditorDialog", "Could not overwrite %1.").arg(fi.fileName()), + QMessageBox::Retry | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Retry) + return QString(); + } + } + while (!QFile::copy(resourceFile, destPath)) { + if (warning(QCoreApplication::translate("QtResourceEditorDialog", "Copy"), + QCoreApplication::translate("QtResourceEditorDialog", "Could not copy\n%1\nto\n%2") + .arg(resourceFile, destPath), + QMessageBox::Retry | QMessageBox::Cancel, QMessageBox::Cancel) != QMessageBox::Retry) + return QString(); + } + return destPath; +} +bool QtResourceEditorDialogPrivate::loadQrcFile(const QString &path, QtQrcFileData *qrcFileData) +{ + QString errorMessage; + const bool rc = loadQrcFile(path, qrcFileData, &errorMessage); +// if (!rc) +// warning(QApplication::translate("QtResourceEditorDialog", "Resource File Load Error"), errorMessage); + return rc; +} +bool QtResourceEditorDialogPrivate::loadQrcFile(const QString &path, QtQrcFileData *qrcFileData, QString *errorMessage) +{ + if (!qrcFileData) + return false; + + qrcFileData->qrcPath = path; + + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + // there is sufficient hint while loading a form and after opening the editor (qrc marked marked with red and with [missing] text) + //*errorMessage = QApplication::translate("QtResourceEditorDialog", "Unable to open %1 for reading: %2").arg(path).arg(file.errorString()); + return false; + } + + QByteArray dataArray = file.readAll(); + file.close(); + + QDomDocument doc; + if (QDomDocument::ParseResult result = doc.setContent(dataArray)) { + return loadQrcFileData(doc, path, qrcFileData, errorMessage); + } else { + *errorMessage = + QCoreApplication::translate("QtResourceEditorDialog", + "A parse error occurred at line %1, column %2 of %3:\n%4") + .arg(result.errorLine).arg(result.errorColumn).arg(path, result.errorMessage); + return false; + } +} + +bool QtResourceEditorDialogPrivate::saveQrcFile(const QtQrcFileData &qrcFileData) +{ + QFile file(qrcFileData.qrcPath); + while (!file.open(QIODevice::WriteOnly)) { + QMessageBox msgBox(QMessageBox::Warning, + QCoreApplication::translate("QtResourceEditorDialog", + "Save Resource File"), + QCoreApplication::translate("QtResourceEditorDialog", + "Could not write %1: %2") + .arg(qrcFileData.qrcPath, + file.errorString()), + QMessageBox::Cancel|QMessageBox::Ignore|QMessageBox::Retry); + msgBox.setEscapeButton(QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Ignore); + switch (msgBox.exec()) { + case QMessageBox::Retry: + break; // nothing + case QMessageBox::Ignore: + return true; + default: + return false; + } + } + + QDomDocument doc = saveQrcFileData(qrcFileData); + + QByteArray dataArray = doc.toByteArray(2); + file.write(dataArray); + + file.close(); + return true; +} + +QtResourceEditorDialog::QtResourceEditorDialog(QDesignerFormEditorInterface *core, QDesignerDialogGuiInterface *dlgGui, QWidget *parent) + : QDialog(parent), d_ptr(new QtResourceEditorDialogPrivate()) +{ + d_ptr->q_ptr = this; + d_ptr->m_ui.setupUi(this); + d_ptr->m_qrcManager = new QtQrcManager(this); + d_ptr->m_dlgGui = dlgGui; + d_ptr->m_core = core; + + setWindowTitle(tr("Edit Resources")); + + connect(d_ptr->m_qrcManager, &QtQrcManager::qrcFileInserted, + this, [this](QtQrcFile *file) { d_ptr->slotQrcFileInserted(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::qrcFileMoved, + this, [this](QtQrcFile *file) { d_ptr->slotQrcFileMoved(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::qrcFileRemoved, + this, [this](QtQrcFile *file) { d_ptr->slotQrcFileRemoved(file); }); + + connect(d_ptr->m_qrcManager, &QtQrcManager::resourcePrefixInserted, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourcePrefixInserted(prefix); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourcePrefixMoved, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourcePrefixMoved(prefix); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourcePrefixChanged, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourcePrefixChanged(prefix); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceLanguageChanged, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourceLanguageChanged(prefix); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourcePrefixRemoved, + this, [this](QtResourcePrefix *prefix) { d_ptr->slotResourcePrefixRemoved(prefix); }); + + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceFileInserted, + this, [this](QtResourceFile *file) { d_ptr->slotResourceFileInserted(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceFileMoved, + this, [this](QtResourceFile *file) { d_ptr->slotResourceFileMoved(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceAliasChanged, + this, [this](QtResourceFile *file) { d_ptr->slotResourceAliasChanged(file); }); + connect(d_ptr->m_qrcManager, &QtQrcManager::resourceFileRemoved, + this, [this](QtResourceFile *file) { d_ptr->slotResourceFileRemoved(file); }); + + QIcon upIcon = qdesigner_internal::createIconSet("up.png"_L1); + QIcon downIcon = qdesigner_internal::createIconSet("down.png"_L1); + QIcon minusIcon = qdesigner_internal::createIconSet("minus-16.png"_L1); + QIcon newIcon = qdesigner_internal::createIconSet("filenew-16.png"_L1); + QIcon openIcon = qdesigner_internal::createIconSet("fileopen-16.png"_L1); + QIcon removeIcon = qdesigner_internal::createIconSet("editdelete-16.png"_L1); + QIcon addPrefixIcon = qdesigner_internal::createIconSet("prefix-add.png"_L1); + + d_ptr->m_newQrcFileAction = new QAction(newIcon, tr("New..."), this); + d_ptr->m_newQrcFileAction->setToolTip(tr("New Resource File")); + d_ptr->m_importQrcFileAction = new QAction(openIcon, tr("Open..."), this); + d_ptr->m_importQrcFileAction->setToolTip(tr("Open Resource File")); + d_ptr->m_removeQrcFileAction = new QAction(removeIcon, tr("Remove"), this); + d_ptr->m_moveUpQrcFileAction = new QAction(upIcon, tr("Move Up"), this); + d_ptr->m_moveDownQrcFileAction = new QAction(downIcon, tr("Move Down"), this); + + d_ptr->m_newPrefixAction = new QAction(addPrefixIcon, tr("Add Prefix"), this); + d_ptr->m_newPrefixAction->setToolTip(tr("Add Prefix")); + d_ptr->m_addResourceFileAction = new QAction(openIcon, tr("Add Files..."), this); + d_ptr->m_changePrefixAction = new QAction(tr("Change Prefix"), this); + d_ptr->m_changeLanguageAction = new QAction(tr("Change Language"), this); + d_ptr->m_changeAliasAction = new QAction(tr("Change Alias"), this); + d_ptr->m_clonePrefixAction = new QAction(tr("Clone Prefix..."), this); + d_ptr->m_removeAction = new QAction(minusIcon, tr("Remove"), this); + d_ptr->m_moveUpAction = new QAction(upIcon, tr("Move Up"), this); + d_ptr->m_moveDownAction = new QAction(downIcon, tr("Move Down"), this); + + d_ptr->m_ui.newQrcButton->setDefaultAction(d_ptr->m_newQrcFileAction); + d_ptr->m_ui.importQrcButton->setDefaultAction(d_ptr->m_importQrcFileAction); + d_ptr->m_ui.removeQrcButton->setDefaultAction(d_ptr->m_removeQrcFileAction); + + d_ptr->m_ui.newResourceButton->setDefaultAction(d_ptr->m_newPrefixAction); + d_ptr->m_ui.addResourceButton->setDefaultAction(d_ptr->m_addResourceFileAction); + d_ptr->m_ui.removeResourceButton->setDefaultAction(d_ptr->m_removeAction); + + connect(d_ptr->m_newQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotNewQrcFile(); }); + connect(d_ptr->m_importQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotImportQrcFile(); }); + connect(d_ptr->m_removeQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotRemoveQrcFile(); }); + connect(d_ptr->m_moveUpQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotMoveUpQrcFile(); }); + connect(d_ptr->m_moveDownQrcFileAction, &QAction::triggered, + this, [this] { d_ptr->slotMoveDownQrcFile(); }); + + connect(d_ptr->m_newPrefixAction, &QAction::triggered, + this, [this] { d_ptr->slotNewPrefix(); }); + connect(d_ptr->m_addResourceFileAction, &QAction::triggered, + this, [this] { d_ptr->slotAddFiles(); }); + connect(d_ptr->m_changePrefixAction, &QAction::triggered, + this, [this] { d_ptr->slotChangePrefix(); }); + connect(d_ptr->m_changeLanguageAction, &QAction::triggered, + this, [this] { d_ptr->slotChangeLanguage(); }); + connect(d_ptr->m_changeAliasAction, &QAction::triggered, + this, [this] { d_ptr->slotChangeAlias(); }); + connect(d_ptr->m_clonePrefixAction, &QAction::triggered, + this, [this] { d_ptr->slotClonePrefix(); }); + connect(d_ptr->m_removeAction, &QAction::triggered, + this, [this] { d_ptr->slotRemove(); }); + connect(d_ptr->m_moveUpAction, &QAction::triggered, + this, [this] { d_ptr->slotMoveUp(); }); + connect(d_ptr->m_moveDownAction, &QAction::triggered, + this, [this] { d_ptr->slotMoveDown(); }); + + d_ptr->m_ui.qrcFileList->setContextMenuPolicy(Qt::CustomContextMenu); + connect(d_ptr->m_ui.qrcFileList, &QListWidget::customContextMenuRequested, + this, [this](const QPoint &point) { d_ptr->slotListWidgetContextMenuRequested(point); }); + connect(d_ptr->m_ui.qrcFileList, &QListWidget::currentItemChanged, + this, [this](QListWidgetItem *item) { d_ptr->slotCurrentQrcFileChanged(item); }); + + d_ptr->m_treeModel = new QStandardItemModel(this); + d_ptr->m_treeModel->setColumnCount(2); + d_ptr->m_treeModel->setHorizontalHeaderItem(0, new QStandardItem(tr("Prefix / Path"))); + d_ptr->m_treeModel->setHorizontalHeaderItem(1, new QStandardItem(tr("Language / Alias"))); + d_ptr->m_ui.resourceTreeView->setModel(d_ptr->m_treeModel); + d_ptr->m_ui.resourceTreeView->setContextMenuPolicy(Qt::CustomContextMenu); + d_ptr->m_treeSelection = d_ptr->m_ui.resourceTreeView->selectionModel(); + connect(d_ptr->m_ui.resourceTreeView->header(), &QHeaderView::sectionDoubleClicked, + d_ptr->m_ui.resourceTreeView, &QTreeView::resizeColumnToContents); + d_ptr->m_ui.resourceTreeView->setTextElideMode(Qt::ElideLeft); + + connect(d_ptr->m_ui.resourceTreeView, &QTreeView::customContextMenuRequested, + this, [this](const QPoint &point) { d_ptr->slotTreeViewContextMenuRequested(point); }); + connect(d_ptr->m_treeModel, &QStandardItemModel::itemChanged, + this, [this](QStandardItem *item) { d_ptr->slotTreeViewItemChanged(item); }); + connect(d_ptr->m_treeSelection, &QItemSelectionModel::currentChanged, + this, [this](const QModelIndex &index) { d_ptr->slotCurrentTreeViewItemChanged(index); }); + + d_ptr->m_ui.resourceTreeView->setColumnWidth(0, 200); + + d_ptr->slotCurrentTreeViewItemChanged(QModelIndex()); + d_ptr->m_removeQrcFileAction->setEnabled(false); + d_ptr->m_moveUpQrcFileAction->setEnabled(false); + d_ptr->m_moveDownQrcFileAction->setEnabled(false); + + QDesignerSettingsInterface *settings = core->settingsManager(); + settings->beginGroup(QrcDialogC); + + d_ptr->m_ui.splitter->restoreState(settings->value(SplitterPosition).toByteArray()); + const QVariant geometry = settings->value(ResourceEditorGeometry); + if (geometry.metaType().id() == QMetaType::QByteArray) // Used to be a QRect up until 5.4.0, QTBUG-43374 + restoreGeometry(geometry.toByteArray()); + + settings->endGroup(); +} + +QtResourceEditorDialog::~QtResourceEditorDialog() +{ + QDesignerSettingsInterface *settings = d_ptr->m_core->settingsManager(); + settings->beginGroup(QrcDialogC); + + settings->setValue(SplitterPosition, d_ptr->m_ui.splitter->saveState()); + settings->setValue(ResourceEditorGeometry, saveGeometry()); + settings->endGroup(); + + disconnect(d_ptr->m_qrcManager, nullptr, this, nullptr); +} + +QtResourceModel *QtResourceEditorDialog::model() const +{ + return d_ptr->m_resourceModel; +} + +void QtResourceEditorDialog::setResourceModel(QtResourceModel *model) +{ + d_ptr->m_resourceModel = model; + + QtResourceSet *resourceSet = d_ptr->m_resourceModel->currentResourceSet(); + if (!resourceSet) { + // disable everything but cancel button + return; + } + + d_ptr->m_initialState.clear(); + + // enable qrcBox + + const QStringList paths = resourceSet->activeResourceFilePaths(); + for (const QString &path : paths) { + QtQrcFileData qrcFileData; + d_ptr->loadQrcFile(path, &qrcFileData); + d_ptr->m_initialState << qrcFileData; + d_ptr->m_qrcManager->importQrcFile(qrcFileData); + } + if (d_ptr->m_ui.qrcFileList->count() > 0) { + d_ptr->m_ui.qrcFileList->item(0)->setSelected(true); + } +} + +QString QtResourceEditorDialog::selectedResource() const +{ + //QtResourcePrefix *currentResourcePrefix = d_ptr->m_qrcManager->resourcePrefixOf(currentResourceFile); + QtResourcePrefix *currentResourcePrefix = d_ptr->getCurrentResourcePrefix(); + if (!currentResourcePrefix) + return QString(); + + const QChar slash(u'/'); + QString resource = currentResourcePrefix->prefix(); + if (!resource.startsWith(slash)) + resource.prepend(slash); + if (!resource.endsWith(slash)) + resource.append(slash); + resource.prepend(u':'); + + QtResourceFile *currentResourceFile = d_ptr->getCurrentResourceFile(); + if (!currentResourceFile) + return resource; + + QString resourceEnding = currentResourceFile->path(); + if (!currentResourceFile->alias().isEmpty()) + resourceEnding = currentResourceFile->alias(); + + const auto dotSlash = "./"_L1; + const auto dotDotSlash = "../"_L1; + while (true) { + if (resourceEnding.startsWith(slash)) + resourceEnding = resourceEnding.mid(1); + else if (resourceEnding.startsWith(dotSlash)) + resourceEnding = resourceEnding.mid(dotSlash.size()); + else if (resourceEnding.startsWith(dotDotSlash)) + resourceEnding = resourceEnding.mid(dotDotSlash.size()); + else + break; + } + + resource.append(resourceEnding); + + return resource; +} + +void QtResourceEditorDialog::displayResourceFailures(const QString &logOutput, QDesignerDialogGuiInterface *dlgGui, QWidget *parent) +{ + const QString msg = tr("

Warning: There have been problems while reloading the resources:

%1
").arg(logOutput); + dlgGui->message(parent, QDesignerDialogGuiInterface::ResourceEditorMessage, QMessageBox::Warning, + tr("Resource Warning"), msg); +} + +void QtResourceEditorDialog::accept() +{ + QStringList newQrcPaths; + QList currentState; + + const auto qrcFiles = d_ptr->m_qrcManager->qrcFiles(); + for (QtQrcFile *qrcFile : qrcFiles) { + QtQrcFileData qrcFileData; + d_ptr->m_qrcManager->exportQrcFile(qrcFile, &qrcFileData); + currentState << qrcFileData; + if (qrcFileData == qrcFile->initialState()) { + // nothing + } else { + d_ptr->m_resourceModel->setWatcherEnabled(qrcFileData.qrcPath, false); + bool ok = d_ptr->saveQrcFile(qrcFileData); + d_ptr->m_resourceModel->setWatcherEnabled(qrcFileData.qrcPath, true); + if (!ok) + return; + + d_ptr->m_resourceModel->setModified(qrcFileData.qrcPath); + } + newQrcPaths << qrcFileData.qrcPath; + } + + if (currentState == d_ptr->m_initialState) { + // nothing + } else { + int errorCount; + QString errorMessages; + d_ptr->m_resourceModel->currentResourceSet()->activateResourceFilePaths(newQrcPaths, &errorCount, &errorMessages); + if (errorCount) + displayResourceFailures(errorMessages, d_ptr->m_dlgGui, this); + } + QDialog::accept(); +} + +QString QtResourceEditorDialog::editResources(QDesignerFormEditorInterface *core, + QtResourceModel *model, + QDesignerDialogGuiInterface *dlgGui, + QWidget *parent) +{ + QtResourceEditorDialog dialog(core, dlgGui, parent); + dialog.setResourceModel(model); + if (dialog.exec() == QDialog::Accepted) + return dialog.selectedResource(); + return QString(); +} + +QT_END_NAMESPACE + +#include "moc_qtresourceeditordialog_p.cpp" +#include "qtresourceeditordialog.moc" diff --git a/src/tools/designer/src/lib/shared/qtresourceeditordialog.ui b/src/tools/designer/src/lib/shared/qtresourceeditordialog.ui new file mode 100644 index 00000000000..fa760d9a1c3 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qtresourceeditordialog.ui @@ -0,0 +1,177 @@ + + QtResourceEditorDialog + + + + 0 + 0 + 469 + 317 + + + + Dialog + + + + + + Qt::Horizontal + + + false + + + + + + + + 0 + 0 + + + + + + + + New File + + + N + + + + + + + Remove File + + + R + + + + + + + Qt::Horizontal + + + QSizePolicy::Ignored + + + + 21 + 20 + + + + + + + + I + + + + + + + + + + + + + + New Resource + + + N + + + + + + + A + + + + + + + Remove Resource or File + + + R + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + QtResourceEditorDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + QtResourceEditorDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/tools/designer/src/lib/shared/qtresourceeditordialog_p.h b/src/tools/designer/src/lib/shared/qtresourceeditordialog_p.h new file mode 100644 index 00000000000..cc832a881e7 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qtresourceeditordialog_p.h @@ -0,0 +1,57 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTRESOURCEEDITOR_H +#define QTRESOURCEEDITOR_H + +#include +#include + +QT_BEGIN_NAMESPACE + +class QtResourceModel; +class QDesignerDialogGuiInterface; +class QDesignerFormEditorInterface; + +class QtResourceEditorDialog : public QDialog +{ + Q_OBJECT +public: + QtResourceModel *model() const; + void setResourceModel(QtResourceModel *model); + + QString selectedResource() const; + + static QString editResources(QDesignerFormEditorInterface *core, QtResourceModel *model, + QDesignerDialogGuiInterface *dlgGui, QWidget *parent = nullptr); + + // Helper to display a message box with rcc logs in case of errors. + static void displayResourceFailures(const QString &logOutput, QDesignerDialogGuiInterface *dlgGui, QWidget *parent = nullptr); + +public slots: + void accept() override; + +private: + QtResourceEditorDialog(QDesignerFormEditorInterface *core, QDesignerDialogGuiInterface *dlgGui, QWidget *parent = nullptr); + ~QtResourceEditorDialog() override; + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceEditorDialog) + Q_DISABLE_COPY_MOVE(QtResourceEditorDialog) +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/tools/designer/src/lib/shared/qtresourcemodel.cpp b/src/tools/designer/src/lib/shared/qtresourcemodel.cpp new file mode 100644 index 00000000000..acee5cc9814 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qtresourcemodel.cpp @@ -0,0 +1,573 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtresourcemodel_p.h" +#include "rcc_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +enum { debugResourceModel = 0 }; + +// ------------------- QtResourceSetPrivate +class QtResourceSetPrivate +{ + QtResourceSet *q_ptr; + Q_DECLARE_PUBLIC(QtResourceSet) +public: + QtResourceSetPrivate(QtResourceModel *model = nullptr); + + QtResourceModel *m_resourceModel; +}; + +QtResourceSetPrivate::QtResourceSetPrivate(QtResourceModel *model) : + q_ptr(nullptr), + m_resourceModel(model) +{ +} + +// -------------------- QtResourceModelPrivate +class QtResourceModelPrivate +{ + QtResourceModel *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtResourceModel) + Q_DISABLE_COPY_MOVE(QtResourceModelPrivate) +public: + QtResourceModelPrivate(); + void activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCount = nullptr, QString *errorMessages = nullptr); + void removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths); + + QMap m_pathToModified; + QHash m_resourceSetToPaths; + QHash m_resourceSetToReload; // while path is recreated it needs to be reregistered + // (it is - in the new current resource set, but when the path was used in + // other resource set + // then later when that resource set is activated it needs to be reregistered) + QHash m_newlyCreated; // all created but not activated yet + // (if was active at some point and it's not now it will not be on that map) + QMap> m_pathToResourceSet; + QtResourceSet *m_currentResourceSet = nullptr; + + QMap m_pathToData; + + QMap m_pathToContents; // qrc path to its contents. + QMap m_fileToQrc; // this map contains the content of active resource set only. + // Activating different resource set changes the contents. + + QFileSystemWatcher *m_fileWatcher = nullptr; + bool m_fileWatcherEnabled = true; + QMap m_fileWatchedMap; +private: + void registerResourceSet(QtResourceSet *resourceSet); + void unregisterResourceSet(QtResourceSet *resourceSet); + void setWatcherEnabled(const QString &path, bool enable); + void addWatcher(const QString &path); + void removeWatcher(const QString &path); + + void slotFileChanged(const QString &); + + const QByteArray *createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const; + void deleteResource(const QByteArray *data) const; +}; + +QtResourceModelPrivate::QtResourceModelPrivate() = default; + +// --------------------- QtResourceSet +QtResourceSet::QtResourceSet() : + d_ptr(new QtResourceSetPrivate) +{ + d_ptr->q_ptr = this; +} + +QtResourceSet::QtResourceSet(QtResourceModel *model) : + d_ptr(new QtResourceSetPrivate(model)) +{ + d_ptr->q_ptr = this; +} + +QtResourceSet::~QtResourceSet() = default; + +QStringList QtResourceSet::activeResourceFilePaths() const +{ + QtResourceSet *that = const_cast(this); + return d_ptr->m_resourceModel->d_ptr->m_resourceSetToPaths.value(that); +} + +void QtResourceSet::activateResourceFilePaths(const QStringList &paths, int *errorCount, QString *errorMessages) +{ + d_ptr->m_resourceModel->d_ptr->activate(this, paths, errorCount, errorMessages); +} + +bool QtResourceSet::isModified(const QString &path) const +{ + return d_ptr->m_resourceModel->isModified(path); +} + +void QtResourceSet::setModified(const QString &path) +{ + d_ptr->m_resourceModel->setModified(path); +} + +// ------------------- QtResourceModelPrivate +const QByteArray *QtResourceModelPrivate::createResource(const QString &path, QStringList *contents, int *errorCount, QIODevice &errorDevice) const +{ + using ResourceDataFileMap = RCCResourceLibrary::ResourceDataFileMap; + const QByteArray *rc = nullptr; + *errorCount = -1; + contents->clear(); + do { + // run RCC + RCCResourceLibrary library(3); + library.setVerbose(true); + library.setInputFiles(QStringList(path)); + library.setFormat(RCCResourceLibrary::Binary); + + QBuffer buffer; + buffer.open(QIODevice::WriteOnly); + if (!library.readFiles(/* ignore errors*/ true, errorDevice)) + break; + // return code cannot be fully trusted, might still be empty + const ResourceDataFileMap resMap = library.resourceDataFileMap(); + if (!library.output(buffer, buffer /* tempfile, unused */, errorDevice)) + break; + + *errorCount = library.failedResources().size(); + *contents = resMap.keys(); + + if (resMap.isEmpty()) + break; + + buffer.close(); + rc = new QByteArray(buffer.data()); + } while (false); + + if (debugResourceModel) + qDebug() << "createResource" << path << "returns data=" << rc << " hasWarnings=" << *errorCount; + return rc; +} + +void QtResourceModelPrivate::deleteResource(const QByteArray *data) const +{ + if (data) { + if (debugResourceModel) + qDebug() << "deleteResource"; + delete data; + } +} + +void QtResourceModelPrivate::registerResourceSet(QtResourceSet *resourceSet) +{ + if (!resourceSet) + return; + + // unregister old paths (all because the order of registration is important), later it can be optimized a bit + const QStringList toRegister = resourceSet->activeResourceFilePaths(); + for (const QString &path : toRegister) { + if (debugResourceModel) + qDebug() << "registerResourceSet " << path; + const auto itRcc = m_pathToData.constFind(path); + if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet + const QByteArray *data = itRcc.value(); + if (data) { + if (!QResource::registerResource(reinterpret_cast(data->constData()))) { + qWarning() << "** WARNING: Failed to register " << path << " (QResource failure)."; + } else { + const QStringList contents = m_pathToContents.value(path); + for (const QString &filePath : contents) { + if (!m_fileToQrc.contains(filePath)) // the first loaded resource has higher priority in qt resource system + m_fileToQrc.insert(filePath, path); + } + } + } + } + } +} + +void QtResourceModelPrivate::unregisterResourceSet(QtResourceSet *resourceSet) +{ + if (!resourceSet) + return; + + // unregister old paths (all because the order of registration is importans), later it can be optimized a bit + const QStringList toUnregister = resourceSet->activeResourceFilePaths(); + for (const QString &path : toUnregister) { + if (debugResourceModel) + qDebug() << "unregisterResourceSet " << path; + const auto itRcc = m_pathToData.constFind(path); + if (itRcc != m_pathToData.constEnd()) { // otherwise data was not created yet + const QByteArray *data = itRcc.value(); + if (data) { + if (!QResource::unregisterResource(reinterpret_cast(itRcc.value()->constData()))) + qWarning() << "** WARNING: Failed to unregister " << path << " (QResource failure)."; + } + } + } + m_fileToQrc.clear(); +} + +void QtResourceModelPrivate::activate(QtResourceSet *resourceSet, const QStringList &newPaths, int *errorCountPtr, QString *errorMessages) +{ + if (debugResourceModel) + qDebug() << "activate " << resourceSet; + if (errorCountPtr) + *errorCountPtr = 0; + if (errorMessages) + errorMessages->clear(); + + QBuffer errorStream; + errorStream.open(QIODevice::WriteOnly); + + int errorCount = 0; + int generatedCount = 0; + bool newResourceSetChanged = false; + + if (resourceSet && resourceSet->activeResourceFilePaths() != newPaths && !m_newlyCreated.contains(resourceSet)) + newResourceSetChanged = true; + + auto newPathToData = m_pathToData; + + for (const QString &path : newPaths) { + if (resourceSet && !m_pathToResourceSet[path].contains(resourceSet)) + m_pathToResourceSet[path].append(resourceSet); + const auto itMod = m_pathToModified.find(path); + if (itMod == m_pathToModified.end() || itMod.value()) { // new path or path is already created, but needs to be recreated + QStringList contents; + int qrcErrorCount; + generatedCount++; + const QByteArray *data = createResource(path, &contents, &qrcErrorCount, errorStream); + + newPathToData.insert(path, data); + if (qrcErrorCount) // Count single failed files as sort of 1/2 error + errorCount++; + addWatcher(path); + + m_pathToModified.insert(path, false); + m_pathToContents.insert(path, contents); + newResourceSetChanged = true; + const auto itReload = m_pathToResourceSet.find(path); + if (itReload != m_pathToResourceSet.end()) { + const auto resources = itReload.value(); + for (QtResourceSet *res : resources) { + if (res != resourceSet) { + m_resourceSetToReload[res] = true; + } + } + } + } else { // path is already created, don't need to recreate + } + } + + const auto oldData = m_pathToData.values(); + const auto newData = newPathToData.values(); + + QList toDelete; + for (const QByteArray *array : oldData) { + if (array && !newData.contains(array)) + toDelete.append(array); + } + + // Nothing can fail below here? + if (generatedCount) { + if (errorCountPtr) + *errorCountPtr = errorCount; + errorStream.close(); + const QString stderrOutput = QString::fromUtf8(errorStream.data()); + if (debugResourceModel) + qDebug() << "Output: (" << errorCount << ")\n" << stderrOutput; + if (errorMessages) + *errorMessages = stderrOutput; + } + // register + const auto itReload = m_resourceSetToReload.find(resourceSet); + if (itReload != m_resourceSetToReload.end()) { + if (itReload.value()) { + newResourceSetChanged = true; + m_resourceSetToReload.insert(resourceSet, false); + } + } + + QStringList oldActivePaths; + if (m_currentResourceSet) + oldActivePaths = m_currentResourceSet->activeResourceFilePaths(); + + const bool needReregister = (oldActivePaths != newPaths) || newResourceSetChanged; + + const auto itNew = m_newlyCreated.find(resourceSet); + if (itNew != m_newlyCreated.end()) { + m_newlyCreated.remove(resourceSet); + if (needReregister) + newResourceSetChanged = true; + } + + if (!newResourceSetChanged && !needReregister && (m_currentResourceSet == resourceSet)) { + for (const QByteArray *data : std::as_const(toDelete)) + deleteResource(data); + + return; // nothing changed + } + + if (needReregister) + unregisterResourceSet(m_currentResourceSet); + + for (const QByteArray *data : std::as_const(toDelete)) + deleteResource(data); + + m_pathToData = newPathToData; + m_currentResourceSet = resourceSet; + + if (resourceSet) + removeOldPaths(resourceSet, newPaths); + + if (needReregister) + registerResourceSet(m_currentResourceSet); + + emit q_ptr->resourceSetActivated(m_currentResourceSet, newResourceSetChanged); + + // deactivates the paths from old current resource set + // add new paths to the new current resource set + // reloads all paths which are marked as modified from the current resource set; + // activates the paths from current resource set + // emits resourceSetActivated() (don't emit only in case when old resource set is the same as new one + // AND no path was reloaded AND the list of paths is exactly the same) +} + +void QtResourceModelPrivate::removeOldPaths(QtResourceSet *resourceSet, const QStringList &newPaths) +{ + const QStringList oldPaths = m_resourceSetToPaths.value(resourceSet); + if (oldPaths != newPaths) { + // remove old + for (const QString &oldPath : oldPaths) { + if (!newPaths.contains(oldPath)) { + const auto itRemove = m_pathToResourceSet.find(oldPath); + if (itRemove != m_pathToResourceSet.end()) { + const int idx = itRemove.value().indexOf(resourceSet); + if (idx >= 0) + itRemove.value().removeAt(idx); + if (itRemove.value().isEmpty()) { + const auto it = m_pathToData.find(oldPath); + if (it != m_pathToData.end()) + deleteResource(it.value()); + m_pathToResourceSet.erase(itRemove); + m_pathToModified.remove(oldPath); + m_pathToContents.remove(oldPath); + m_pathToData.remove(oldPath); + removeWatcher(oldPath); + } + } + } + } + m_resourceSetToPaths[resourceSet] = newPaths; + } +} + +void QtResourceModelPrivate::setWatcherEnabled(const QString &path, bool enable) +{ + if (!enable) { + m_fileWatcher->removePath(path); + return; + } + + QFileInfo fi(path); + if (fi.exists()) + m_fileWatcher->addPath(path); +} + +void QtResourceModelPrivate::addWatcher(const QString &path) +{ + const auto it = m_fileWatchedMap.constFind(path); + if (it != m_fileWatchedMap.constEnd() && !it.value()) + return; + + m_fileWatchedMap.insert(path, true); + if (!m_fileWatcherEnabled) + return; + setWatcherEnabled(path, true); +} + +void QtResourceModelPrivate::removeWatcher(const QString &path) +{ + if (!m_fileWatchedMap.contains(path)) + return; + + m_fileWatchedMap.remove(path); + if (!m_fileWatcherEnabled) + return; + setWatcherEnabled(path, false); +} + +void QtResourceModelPrivate::slotFileChanged(const QString &path) +{ + setWatcherEnabled(path, false); + emit q_ptr->qrcFileModifiedExternally(path); + setWatcherEnabled(path, true); //readd +} + +// ----------------------- QtResourceModel +QtResourceModel::QtResourceModel(QObject *parent) : + QObject(parent), + d_ptr(new QtResourceModelPrivate) +{ + d_ptr->q_ptr = this; + + d_ptr->m_fileWatcher = new QFileSystemWatcher(this); + connect(d_ptr->m_fileWatcher, &QFileSystemWatcher::fileChanged, + this, [this](const QString &fileName) { d_ptr->slotFileChanged(fileName); }); +} + +QtResourceModel::~QtResourceModel() +{ + blockSignals(true); + const auto resourceList = resourceSets(); + for (QtResourceSet *rs : resourceList) + removeResourceSet(rs); + blockSignals(false); +} + +QStringList QtResourceModel::loadedQrcFiles() const +{ + return d_ptr->m_pathToModified.keys(); +} + +bool QtResourceModel::isModified(const QString &path) const +{ + return d_ptr->m_pathToModified.value(path, true); +} + +void QtResourceModel::setModified(const QString &path) +{ + if (!d_ptr->m_pathToModified.contains(path)) + return; + + d_ptr->m_pathToModified[path] = true; + const auto it = d_ptr->m_pathToResourceSet.constFind(path); + if (it == d_ptr->m_pathToResourceSet.constEnd()) + return; + + const auto resourceList = it.value(); + for (QtResourceSet *rs : resourceList) + d_ptr->m_resourceSetToReload.insert(rs, true); +} + +QList QtResourceModel::resourceSets() const +{ + return d_ptr->m_resourceSetToPaths.keys(); +} + +QtResourceSet *QtResourceModel::currentResourceSet() const +{ + return d_ptr->m_currentResourceSet; +} + +void QtResourceModel::setCurrentResourceSet(QtResourceSet *resourceSet, int *errorCount, QString *errorMessages) +{ + d_ptr->activate(resourceSet, d_ptr->m_resourceSetToPaths.value(resourceSet), errorCount, errorMessages); +} + +QtResourceSet *QtResourceModel::addResourceSet(const QStringList &paths) +{ + QtResourceSet *newResource = new QtResourceSet(this); + d_ptr->m_resourceSetToPaths.insert(newResource, paths); + d_ptr->m_resourceSetToReload.insert(newResource, false); + d_ptr->m_newlyCreated.insert(newResource, true); + for (const QString &path : paths) + d_ptr->m_pathToResourceSet[path].append(newResource); + return newResource; +} + +// TODO +void QtResourceModel::removeResourceSet(QtResourceSet *resourceSet) +{ + if (!resourceSet) + return; + if (currentResourceSet() == resourceSet) + setCurrentResourceSet(nullptr); + + // remove rcc files for those paths which are not used in any other resource set + d_ptr->removeOldPaths(resourceSet, QStringList()); + + d_ptr->m_resourceSetToPaths.remove(resourceSet); + d_ptr->m_resourceSetToReload.remove(resourceSet); + d_ptr->m_newlyCreated.remove(resourceSet); + delete resourceSet; +} + +void QtResourceModel::reload(const QString &path, int *errorCount, QString *errorMessages) +{ + setModified(path); + + d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages); +} + +void QtResourceModel::reload(int *errorCount, QString *errorMessages) +{ + for (auto it = d_ptr->m_pathToModified.begin(), end = d_ptr->m_pathToModified.end(); it != end; ++it) + it.value() = true; + + // empty resourceSets could be omitted here + for (auto itReload = d_ptr->m_resourceSetToReload.begin(), end = d_ptr->m_resourceSetToReload.end(); itReload != end; ++itReload) + itReload.value() = true; + + d_ptr->activate(d_ptr->m_currentResourceSet, d_ptr->m_resourceSetToPaths.value(d_ptr->m_currentResourceSet), errorCount, errorMessages); +} + +QMap QtResourceModel::contents() const +{ + return d_ptr->m_fileToQrc; +} + +QString QtResourceModel::qrcPath(const QString &file) const +{ + return d_ptr->m_fileToQrc.value(file); +} + +void QtResourceModel::setWatcherEnabled(bool enable) +{ + if (d_ptr->m_fileWatcherEnabled == enable) + return; + + d_ptr->m_fileWatcherEnabled = enable; + + if (!d_ptr->m_fileWatchedMap.isEmpty()) + d_ptr->setWatcherEnabled(d_ptr->m_fileWatchedMap.firstKey(), d_ptr->m_fileWatcherEnabled); +} + +bool QtResourceModel::isWatcherEnabled() const +{ + return d_ptr->m_fileWatcherEnabled; +} + +void QtResourceModel::setWatcherEnabled(const QString &path, bool enable) +{ + const auto it = d_ptr->m_fileWatchedMap.find(path); + if (it == d_ptr->m_fileWatchedMap.end()) + return; + + if (it.value() == enable) + return; + + it.value() = enable; + + if (!d_ptr->m_fileWatcherEnabled) + return; + + d_ptr->setWatcherEnabled(it.key(), enable); +} + +bool QtResourceModel::isWatcherEnabled(const QString &path) +{ + return d_ptr->m_fileWatchedMap.value(path, false); +} + +QT_END_NAMESPACE + +#include "moc_qtresourcemodel_p.cpp" diff --git a/src/tools/designer/src/lib/shared/qtresourcemodel_p.h b/src/tools/designer/src/lib/shared/qtresourcemodel_p.h new file mode 100644 index 00000000000..43afe5c500c --- /dev/null +++ b/src/tools/designer/src/lib/shared/qtresourcemodel_p.h @@ -0,0 +1,105 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTRESOURCEMODEL_H +#define QTRESOURCEMODEL_H + +#include "shared_global_p.h" +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QtResourceModel; + +class QDESIGNER_SHARED_EXPORT QtResourceSet // one instance per one form +{ +public: + QStringList activeResourceFilePaths() const; + + // activateQrcPaths(): if this QtResourceSet is active it emits resourceSetActivated(); + // otherwise only in case if active QtResource set contains one of + // paths which was marked as modified by this resource set, the signal + // is emitted (with reload = true); + // If new path appears on the list it is automatically added to + // loaded list of QtResourceModel. In addition it is marked as modified in case + // QtResourceModel didn't contain the path. + // If some path is removed from that list (and is not used in any other + // resource set) it is automatically unloaded. The removed file can also be + // marked as modified (later when another resource set which contains + // removed path is activated will be reloaded) + void activateResourceFilePaths(const QStringList &paths, int *errorCount = nullptr, QString *errorMessages = nullptr); + + bool isModified(const QString &path) const; // for all paths in resource model (redundant here, maybe it should be removed from here) + void setModified(const QString &path); // for all paths in resource model (redundant here, maybe it should be removed from here) +private: + QtResourceSet(); + QtResourceSet(QtResourceModel *model); + ~QtResourceSet(); + friend class QtResourceModel; + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceSet) + Q_DISABLE_COPY_MOVE(QtResourceSet) +}; + +class QDESIGNER_SHARED_EXPORT QtResourceModel : public QObject // one instance per whole designer +{ + Q_OBJECT +public: + QtResourceModel(QObject *parent = nullptr); + ~QtResourceModel(); + + QStringList loadedQrcFiles() const; + bool isModified(const QString &path) const; // only for paths which are on loadedQrcFiles() list + void setModified(const QString &path); // only for paths which are on loadedQrcPaths() list + + QList resourceSets() const; + + QtResourceSet *currentResourceSet() const; + void setCurrentResourceSet(QtResourceSet *resourceSet, int *errorCount = nullptr, QString *errorMessages = nullptr); + + QtResourceSet *addResourceSet(const QStringList &paths); + void removeResourceSet(QtResourceSet *resourceSet); + + void reload(const QString &path, int *errorCount = nullptr, QString *errorMessages = nullptr); + void reload(int *errorCount = nullptr, QString *errorMessages = nullptr); + + // Contents of the current resource set (content file to qrc path) + QMap contents() const; + // Find the qrc file belonging to the contained file (from current resource set) + QString qrcPath(const QString &file) const; + + void setWatcherEnabled(bool enable); + bool isWatcherEnabled() const; + + void setWatcherEnabled(const QString &path, bool enable); + bool isWatcherEnabled(const QString &path); + +signals: + void resourceSetActivated(QtResourceSet *resourceSet, bool resourceSetChanged); // resourceSetChanged since last time it was activated! + void qrcFileModifiedExternally(const QString &path); + +private: + friend class QtResourceSet; + + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceModel) + Q_DISABLE_COPY_MOVE(QtResourceModel) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/designer/src/lib/shared/qtresourceview.cpp b/src/tools/designer/src/lib/shared/qtresourceview.cpp new file mode 100644 index 00000000000..5663b0ddc14 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qtresourceview.cpp @@ -0,0 +1,868 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "qtresourceview_p.h" +#include "qtresourcemodel_p.h" +#include "qtresourceeditordialog_p.h" +#include "iconloader_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if QT_CONFIG(clipboard) +# include +#endif +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto elementResourceData = "resource"_L1; +static constexpr auto typeAttribute = "type"_L1; +static constexpr auto typeImage = "image"_L1; +static constexpr auto typeStyleSheet = "stylesheet"_L1; +static constexpr auto typeOther = "other"_L1; +static constexpr auto fileAttribute = "file"_L1; +static constexpr auto qrvSplitterPosition = "SplitterPosition"_L1; +static constexpr auto qrvGeometry = "Geometry"_L1; +static constexpr auto ResourceViewDialogC = "ResourceDialog"_L1; + +// ---------------- ResourceListWidget: A list widget that has drag enabled +class ResourceListWidget : public QListWidget { +public: + ResourceListWidget(QWidget *parent = nullptr); + +protected: + void startDrag(Qt::DropActions supportedActions) override; +}; + +ResourceListWidget::ResourceListWidget(QWidget *parent) : + QListWidget(parent) +{ + setDragEnabled(true); +} + +void ResourceListWidget::startDrag(Qt::DropActions supportedActions) +{ + if (supportedActions == Qt::MoveAction) + return; + + QListWidgetItem *item = currentItem(); + if (!item) + return; + + const QString filePath = item->data(Qt::UserRole).toString(); + const QIcon icon = item->icon(); + + QMimeData *mimeData = new QMimeData; + const QtResourceView::ResourceType type = icon.isNull() ? QtResourceView::ResourceOther : QtResourceView::ResourceImage; + mimeData->setText(QtResourceView::encodeMimeData(type , filePath)); + + QDrag *drag = new QDrag(this); + if (!icon.isNull()) { + const QSize size = icon.actualSize(iconSize()); + drag->setPixmap(icon.pixmap(size)); + drag->setHotSpot(QPoint(size.width() / 2, size.height() / 2)); + } + + drag->setMimeData(mimeData); + drag->exec(Qt::CopyAction); +} + +/* TODO + + 1) load the icons in separate thread...Hmm..if Qt is configured with threads.... +*/ + +// ---------------------------- QtResourceViewPrivate +class QtResourceViewPrivate +{ + QtResourceView *q_ptr = nullptr; + Q_DECLARE_PUBLIC(QtResourceView) +public: + QtResourceViewPrivate(QDesignerFormEditorInterface *core); + + void slotResourceSetActivated(QtResourceSet *resourceSet); + void slotCurrentPathChanged(QTreeWidgetItem *); + void slotCurrentResourceChanged(QListWidgetItem *); + void slotResourceActivated(QListWidgetItem *); + void slotEditResources(); + void slotReloadResources(); +#if QT_CONFIG(clipboard) + void slotCopyResourcePath(); +#endif + void slotListWidgetContextMenuRequested(const QPoint &pos); + void slotFilterChanged(const QString &pattern); + void createPaths(); + QTreeWidgetItem *createPath(const QString &path, QTreeWidgetItem *parent); + void createResources(const QString &path); + void storeExpansionState(); + void applyExpansionState(); + void restoreSettings(); + void saveSettings(); + void updateActions(); + void filterOutResources(); + + QPixmap makeThumbnail(const QPixmap &pix) const; + + QDesignerFormEditorInterface *m_core; + QtResourceModel *m_resourceModel = nullptr; + QToolBar *m_toolBar; + QWidget *m_filterWidget = nullptr; + QTreeWidget *m_treeWidget; + QListWidget *m_listWidget; + QSplitter *m_splitter = nullptr; + QMap m_pathToContents; // full path to contents file names (full path to its resource filenames) + QMap m_pathToParentPath; // full path to full parent path + QMap m_pathToSubPaths; // full path to full sub paths + QMap m_pathToItem; + QHash m_itemToPath; + QMap m_resourceToItem; + QHash m_itemToResource; + QAction *m_editResourcesAction = nullptr; + QAction *m_reloadResourcesAction = nullptr; + QAction *m_copyResourcePathAction = nullptr; + + QMap m_expansionState; + + QString m_settingsKey; + QString m_filterPattern; + bool m_ignoreGuiSignals = false; + bool m_resourceEditingEnabled = true; +}; + +QtResourceViewPrivate::QtResourceViewPrivate(QDesignerFormEditorInterface *core) : + m_core(core), + m_toolBar(new QToolBar), + m_treeWidget(new QTreeWidget), + m_listWidget(new ResourceListWidget) +{ + m_toolBar->setIconSize(QSize(22, 22)); +} + +void QtResourceViewPrivate::restoreSettings() +{ + if (m_settingsKey.isEmpty()) + return; + + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(m_settingsKey); + + m_splitter->restoreState(settings->value(qrvSplitterPosition).toByteArray()); + settings->endGroup(); +} + +void QtResourceViewPrivate::saveSettings() +{ + if (m_settingsKey.isEmpty()) + return; + + QDesignerSettingsInterface *settings = m_core->settingsManager(); + settings->beginGroup(m_settingsKey); + + settings->setValue(qrvSplitterPosition, m_splitter->saveState()); + settings->endGroup(); +} + +void QtResourceViewPrivate::slotEditResources() +{ + const QString selectedResource + = QtResourceEditorDialog::editResources(m_core, m_resourceModel, + m_core->dialogGui(), q_ptr); + if (!selectedResource.isEmpty()) + q_ptr->selectResource(selectedResource); +} + +void QtResourceViewPrivate::slotReloadResources() +{ + if (m_resourceModel) { + int errorCount; + QString errorMessages; + m_resourceModel->reload(&errorCount, &errorMessages); + if (errorCount) + QtResourceEditorDialog::displayResourceFailures(errorMessages, m_core->dialogGui(), q_ptr); + } +} + +#if QT_CONFIG(clipboard) +void QtResourceViewPrivate::slotCopyResourcePath() +{ + const QString path = q_ptr->selectedResource(); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(path); +} +#endif + +void QtResourceViewPrivate::slotListWidgetContextMenuRequested(const QPoint &pos) +{ + QMenu menu(q_ptr); + menu.addAction(m_copyResourcePathAction); + menu.exec(m_listWidget->mapToGlobal(pos)); +} + +void QtResourceViewPrivate::slotFilterChanged(const QString &pattern) +{ + m_filterPattern = pattern; + filterOutResources(); +} + +void QtResourceViewPrivate::storeExpansionState() +{ + for (auto it = m_pathToItem.cbegin(), end = m_pathToItem.cend(); it != end; ++it) + m_expansionState.insert(it.key(), it.value()->isExpanded()); +} + +void QtResourceViewPrivate::applyExpansionState() +{ + for (auto it = m_pathToItem.cbegin(), end = m_pathToItem.cend(); it != end; ++it) + it.value()->setExpanded(m_expansionState.value(it.key(), true)); +} + +QPixmap QtResourceViewPrivate::makeThumbnail(const QPixmap &pix) const +{ + int w = qMax(48, pix.width()); + int h = qMax(48, pix.height()); + QRect imgRect(0, 0, w, h); + QImage img(w, h, QImage::Format_ARGB32_Premultiplied); + img.fill(0); + if (!pix.isNull()) { + QRect r(0, 0, pix.width(), pix.height()); + r.moveCenter(imgRect.center()); + QPainter p(&img); + p.drawPixmap(r.topLeft(), pix); + } + return QPixmap::fromImage(img); +} + +void QtResourceViewPrivate::updateActions() +{ + bool resourceActive = false; + if (m_resourceModel) + resourceActive = m_resourceModel->currentResourceSet(); + + m_editResourcesAction->setVisible(m_resourceEditingEnabled); + m_editResourcesAction->setEnabled(resourceActive); + m_reloadResourcesAction->setEnabled(resourceActive); + m_filterWidget->setEnabled(resourceActive); +} + +void QtResourceViewPrivate::slotResourceSetActivated(QtResourceSet *resourceSet) +{ + Q_UNUSED(resourceSet); + + updateActions(); + + storeExpansionState(); + const QString currentPath = m_itemToPath.value(m_treeWidget->currentItem()); + const QString currentResource = m_itemToResource.value(m_listWidget->currentItem()); + m_treeWidget->clear(); + m_pathToContents.clear(); + m_pathToParentPath.clear(); + m_pathToSubPaths.clear(); + m_pathToItem.clear(); + m_itemToPath.clear(); + m_listWidget->clear(); + m_resourceToItem.clear(); + m_itemToResource.clear(); + + createPaths(); + applyExpansionState(); + + if (!currentResource.isEmpty()) + q_ptr->selectResource(currentResource); + else if (!currentPath.isEmpty()) + q_ptr->selectResource(currentPath); + filterOutResources(); +} + +void QtResourceViewPrivate::slotCurrentPathChanged(QTreeWidgetItem *item) +{ + if (m_ignoreGuiSignals) + return; + + m_listWidget->clear(); + m_resourceToItem.clear(); + m_itemToResource.clear(); + + if (!item) + return; + + const QString currentPath = m_itemToPath.value(item); + createResources(currentPath); +} + +void QtResourceViewPrivate::slotCurrentResourceChanged(QListWidgetItem *item) +{ + m_copyResourcePathAction->setEnabled(item); + if (m_ignoreGuiSignals) + return; + + emit q_ptr->resourceSelected(m_itemToResource.value(item)); +} + +void QtResourceViewPrivate::slotResourceActivated(QListWidgetItem *item) +{ + if (m_ignoreGuiSignals) + return; + + emit q_ptr->resourceActivated(m_itemToResource.value(item)); +} + +void QtResourceViewPrivate::createPaths() +{ + if (!m_resourceModel) + return; + + // Resource root up until 4.6 was ':', changed to ":/" as of 4.7 + const QString root(u":/"_s); + + QMap contents = m_resourceModel->contents(); + for (auto it = contents.cbegin(), end = contents.cend(); it != end; ++it) { + const QFileInfo fi(it.key()); + QString dirPath = fi.absolutePath(); + m_pathToContents[dirPath].append(fi.fileName()); + while (!m_pathToParentPath.contains(dirPath) && dirPath != root) { // create all parent paths + const QFileInfo fd(dirPath); + const QString parentDirPath = fd.absolutePath(); + m_pathToParentPath[dirPath] = parentDirPath; + m_pathToSubPaths[parentDirPath].append(dirPath); + dirPath = parentDirPath; + } + } + + QQueue> pathToParentItemQueue; + pathToParentItemQueue.enqueue(std::make_pair(root, static_cast(nullptr))); + while (!pathToParentItemQueue.isEmpty()) { + std::pair pathToParentItem = pathToParentItemQueue.dequeue(); + const QString path = pathToParentItem.first; + QTreeWidgetItem *item = createPath(path, pathToParentItem.second); + const QStringList subPaths = m_pathToSubPaths.value(path); + for (const QString &subPath : subPaths) + pathToParentItemQueue.enqueue(std::make_pair(subPath, item)); + } +} + +void QtResourceViewPrivate::filterOutResources() +{ + QMap pathToMatchingContents; // true means the path has any matching contents + QMap pathToVisible; // true means the path has to be shown + + // 1) we go from root path recursively. + // 2) we check every path if it contains at least one matching resource - if empty we add it + // to pathToMatchingContents and pathToVisible with false, if non empty + // we add it with true and change every parent path in pathToVisible to true. + // 3) we hide these items which has pathToVisible value false. + + const bool matchAll = m_filterPattern.isEmpty(); + const QString root(u":/"_s); + + QQueue pathQueue; + pathQueue.enqueue(root); + while (!pathQueue.isEmpty()) { + const QString path = pathQueue.dequeue(); + + bool hasContents = matchAll; + if (!matchAll) { // the case filter is not empty - we check if the path contains anything + // the path contains at least one resource which matches the filter + const QStringList fileNames = m_pathToContents.value(path); + hasContents = + std::any_of(fileNames.cbegin(), fileNames.cend(), + [this] (const QString &f) { return f.contains(this->m_filterPattern, Qt::CaseInsensitive); }); + } + + pathToMatchingContents[path] = hasContents; + pathToVisible[path] = hasContents; + + if (hasContents) { // if the path is going to be shown we need to show all its parent paths + QString parentPath = m_pathToParentPath.value(path); + while (!parentPath.isEmpty()) { + QString p = parentPath; + if (pathToVisible.value(p)) // parent path is already shown, we break the loop + break; + pathToVisible[p] = true; + parentPath = m_pathToParentPath.value(p); + } + } + + const QStringList subPaths = m_pathToSubPaths.value(path); // we do the same for children paths + for (const QString &subPath : subPaths) + pathQueue.enqueue(subPath); + } + + // we setup here new path and resource to be activated + const QString currentPath = m_itemToPath.value(m_treeWidget->currentItem()); + QString newCurrentPath = currentPath; + QString currentResource = m_itemToResource.value(m_listWidget->currentItem()); + if (!matchAll) { + bool searchForNewPathWithContents = true; + + if (!currentPath.isEmpty()) { // if the currentPath is empty we will search for a new path too + const auto it = pathToMatchingContents.constFind(currentPath); + if (it != pathToMatchingContents.constEnd() && it.value()) // the current item has contents, we don't need to search for another path + searchForNewPathWithContents = false; + } + + if (searchForNewPathWithContents) { + // we find the first path with the matching contents + for (auto itContents = pathToMatchingContents.cbegin(), cend = pathToMatchingContents.cend(); itContents != cend; ++itContents) { + if (itContents.value()) { + newCurrentPath = itContents.key(); // the new path will be activated + break; + } + } + } + + QFileInfo fi(currentResource); + if (!fi.fileName().contains(m_filterPattern, Qt::CaseInsensitive)) { // the case when the current resource is filtered out + const QStringList fileNames = m_pathToContents.value(newCurrentPath); + // we try to select the first matching resource from the newCurrentPath + for (const QString &fileName : fileNames) { + if (fileName.contains(m_filterPattern, Qt::CaseInsensitive)) { + QDir dirPath(newCurrentPath); + currentResource = dirPath.absoluteFilePath(fileName); // the new resource inside newCurrentPath will be activated + break; + } + } + } + } + + QTreeWidgetItem *newCurrentItem = m_pathToItem.value(newCurrentPath); + if (currentPath != newCurrentPath) + m_treeWidget->setCurrentItem(newCurrentItem); + else + slotCurrentPathChanged(newCurrentItem); // trigger filtering on the current path + + QListWidgetItem *currentResourceItem = m_resourceToItem.value(currentResource); + if (currentResourceItem) { + m_listWidget->setCurrentItem(currentResourceItem); + m_listWidget->scrollToItem(currentResourceItem); + } + + // hide all paths filtered out + for (auto it = pathToVisible.cbegin(), end = pathToVisible.cend(); it != end; ++it) { + if (QTreeWidgetItem *item = m_pathToItem.value(it.key())) + item->setHidden(!it.value()); + } +} + +QTreeWidgetItem *QtResourceViewPrivate::createPath(const QString &path, QTreeWidgetItem *parent) +{ + QTreeWidgetItem *item = nullptr; + if (parent) + item = new QTreeWidgetItem(parent); + else + item = new QTreeWidgetItem(m_treeWidget); + m_pathToItem[path] = item; + m_itemToPath[item] = path; + QString substPath; + if (parent) { + QFileInfo di(path); + substPath = di.fileName(); + } else { + substPath = u""_s; + } + item->setText(0, substPath); + item->setToolTip(0, path); + return item; +} + +void QtResourceViewPrivate::createResources(const QString &path) +{ + const bool matchAll = m_filterPattern.isEmpty(); + + QDir dir(path); + const QStringList fileNames = m_pathToContents.value(path); + for (const QString &fileName : fileNames) { + const bool showProperty = matchAll || fileName.contains(m_filterPattern, Qt::CaseInsensitive); + if (showProperty) { + QString filePath = dir.absoluteFilePath(fileName); + QFileInfo fi(filePath); + if (fi.isFile()) { + QListWidgetItem *item = new QListWidgetItem(fi.fileName(), m_listWidget); + const QPixmap pix = QPixmap(filePath); + if (pix.isNull()) { + item->setToolTip(filePath); + } else { + item->setIcon(QIcon(makeThumbnail(pix))); + const QSize size = pix.size(); + item->setToolTip(QtResourceView::tr("Size: %1 x %2\n%3").arg(size.width()).arg(size.height()).arg(filePath)); + } + item->setFlags(item->flags() | Qt::ItemIsDragEnabled); + item->setData(Qt::UserRole, filePath); + m_itemToResource[item] = filePath; + m_resourceToItem[filePath] = item; + } + } + } +} + +// -------------- QtResourceView + +QtResourceView::QtResourceView(QDesignerFormEditorInterface *core, QWidget *parent) : + QWidget(parent), + d_ptr(new QtResourceViewPrivate(core)) +{ + d_ptr->q_ptr = this; + + QIcon editIcon = qdesigner_internal::createIconSet(QIcon::ThemeIcon::DocumentProperties, + "edit.png"_L1); + d_ptr->m_editResourcesAction = new QAction(editIcon, tr("Edit Resources..."), this); + d_ptr->m_toolBar->addAction(d_ptr->m_editResourcesAction); + connect(d_ptr->m_editResourcesAction, &QAction::triggered, + this, [this] { d_ptr->slotEditResources(); }); + d_ptr->m_editResourcesAction->setEnabled(false); + + QIcon refreshIcon = qdesigner_internal::createIconSet(QIcon::ThemeIcon::ViewRefresh, + "reload.png"_L1); + d_ptr->m_reloadResourcesAction = new QAction(refreshIcon, tr("Reload"), this); + + d_ptr->m_toolBar->addAction(d_ptr->m_reloadResourcesAction); + connect(d_ptr->m_reloadResourcesAction, &QAction::triggered, + this, [this] { d_ptr->slotReloadResources(); }); + d_ptr->m_reloadResourcesAction->setEnabled(false); + +#if QT_CONFIG(clipboard) + QIcon copyIcon = qdesigner_internal::createIconSet(QIcon::ThemeIcon::EditCopy, + "editcopy.png"_L1); + d_ptr->m_copyResourcePathAction = new QAction(copyIcon, tr("Copy Path"), this); + connect(d_ptr->m_copyResourcePathAction, &QAction::triggered, + this, [this] { d_ptr->slotCopyResourcePath(); }); + d_ptr->m_copyResourcePathAction->setEnabled(false); +#endif + + d_ptr->m_filterWidget = new QWidget(d_ptr->m_toolBar); + QHBoxLayout *filterLayout = new QHBoxLayout(d_ptr->m_filterWidget); + filterLayout->setContentsMargins(0, 0, 0, 0); + QLineEdit *filterLineEdit = new QLineEdit(d_ptr->m_filterWidget); + connect(filterLineEdit, &QLineEdit::textChanged, + this, [this](const QString &text) { d_ptr->slotFilterChanged(text); }); + filterLineEdit->setPlaceholderText(tr("Filter")); + filterLineEdit->setClearButtonEnabled(true); + filterLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::MinimumExpanding, QSizePolicy::Ignored)); + filterLayout->addWidget(filterLineEdit); + d_ptr->m_toolBar->addWidget(d_ptr->m_filterWidget); + + d_ptr->m_splitter = new QSplitter; + d_ptr->m_splitter->setChildrenCollapsible(false); + d_ptr->m_splitter->addWidget(d_ptr->m_treeWidget); + d_ptr->m_splitter->addWidget(d_ptr->m_listWidget); + + QLayout *layout = new QVBoxLayout(this); + layout->setContentsMargins(QMargins()); + layout->setSpacing(0); + layout->addWidget(d_ptr->m_toolBar); + layout->addWidget(d_ptr->m_splitter); + + d_ptr->m_treeWidget->setColumnCount(1); + d_ptr->m_treeWidget->header()->hide(); + d_ptr->m_treeWidget->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding)); + + d_ptr->m_listWidget->setViewMode(QListView::IconMode); + d_ptr->m_listWidget->setResizeMode(QListView::Adjust); + d_ptr->m_listWidget->setIconSize(QSize(48, 48)); + d_ptr->m_listWidget->setGridSize(QSize(64, 64)); + + connect(d_ptr->m_treeWidget, &QTreeWidget::currentItemChanged, + this, [this](QTreeWidgetItem *item) { d_ptr->slotCurrentPathChanged(item); }); + connect(d_ptr->m_listWidget, &QListWidget::currentItemChanged, + this, [this](QListWidgetItem *item) { d_ptr->slotCurrentResourceChanged(item); }); + connect(d_ptr->m_listWidget, &QListWidget::itemActivated, + this, [this](QListWidgetItem *item) { d_ptr->slotResourceActivated(item); }); + d_ptr->m_listWidget->setContextMenuPolicy(Qt::CustomContextMenu); + connect(d_ptr->m_listWidget, &QListWidget::customContextMenuRequested, + this, [this](const QPoint &point) { d_ptr->slotListWidgetContextMenuRequested(point); }); +} + +QtResourceView::~QtResourceView() +{ + if (!d_ptr->m_settingsKey.isEmpty()) + d_ptr->saveSettings(); +} + +bool QtResourceView::event(QEvent *event) +{ + if (event->type() == QEvent::Show) { + d_ptr->m_listWidget->scrollToItem(d_ptr->m_listWidget->currentItem()); + d_ptr->m_treeWidget->scrollToItem(d_ptr->m_treeWidget->currentItem()); + } + return QWidget::event(event); +} + +QtResourceModel *QtResourceView::model() const +{ + return d_ptr->m_resourceModel; +} + +QString QtResourceView::selectedResource() const +{ + QListWidgetItem *item = d_ptr->m_listWidget->currentItem(); + return d_ptr->m_itemToResource.value(item); +} + +void QtResourceView::selectResource(const QString &resource) +{ + if (resource.isEmpty()) + return; + QFileInfo fi(resource); + QDir dir = fi.absoluteDir(); + if (fi.isDir()) + dir = QDir(resource); + QString dirPath = dir.absolutePath(); + const auto cend = d_ptr->m_pathToItem.constEnd(); + auto it = cend; + while ((it = d_ptr->m_pathToItem.constFind(dirPath)) == cend) { + if (!dir.cdUp()) + break; + dirPath = dir.absolutePath(); + } + if (it != cend) { + QTreeWidgetItem *treeItem = it.value(); + d_ptr->m_treeWidget->setCurrentItem(treeItem); + d_ptr->m_treeWidget->scrollToItem(treeItem); + // expand all up to current one is done by qt + // list widget is already propagated (currrent changed was sent by qt) + QListWidgetItem *item = d_ptr->m_resourceToItem.value(resource); + if (item) { + d_ptr->m_listWidget->setCurrentItem(item); + d_ptr->m_listWidget->scrollToItem(item); + } + } +} + +QString QtResourceView::settingsKey() const +{ + return d_ptr->m_settingsKey; +} + +void QtResourceView::setSettingsKey(const QString &key) +{ + if (d_ptr->m_settingsKey == key) + return; + + d_ptr->m_settingsKey = key; + + if (key.isEmpty()) + return; + + d_ptr->restoreSettings(); +} + +void QtResourceView::setResourceModel(QtResourceModel *model) +{ + if (d_ptr->m_resourceModel) + disconnect(d_ptr->m_resourceModel, &QtResourceModel::resourceSetActivated, this, nullptr); + + // clear here + d_ptr->m_treeWidget->clear(); + d_ptr->m_listWidget->clear(); + + d_ptr->m_resourceModel = model; + + if (!d_ptr->m_resourceModel) + return; + + connect(d_ptr->m_resourceModel, &QtResourceModel::resourceSetActivated, + this, [this](QtResourceSet *resource) { d_ptr->slotResourceSetActivated(resource); }); + + // fill new here + d_ptr->slotResourceSetActivated(d_ptr->m_resourceModel->currentResourceSet()); +} + +bool QtResourceView::isResourceEditingEnabled() const +{ + return d_ptr->m_resourceEditingEnabled; +} + +void QtResourceView::setResourceEditingEnabled(bool enable) +{ + d_ptr->m_resourceEditingEnabled = enable; + d_ptr->updateActions(); +} + +void QtResourceView::setDragEnabled(bool dragEnabled) +{ + d_ptr->m_listWidget->setDragEnabled(dragEnabled); +} + +bool QtResourceView::dragEnabled() const +{ + return d_ptr->m_listWidget->dragEnabled(); +} + +QString QtResourceView::encodeMimeData(ResourceType resourceType, const QString &path) +{ + QDomDocument doc; + QDomElement elem = doc.createElement(elementResourceData); + switch (resourceType) { + case ResourceImage: + elem.setAttribute(typeAttribute, typeImage); + break; + case ResourceStyleSheet: + elem.setAttribute(typeAttribute, typeStyleSheet); + break; + case ResourceOther: + elem.setAttribute(typeAttribute, typeOther); + break; + } + elem.setAttribute(fileAttribute, path); + doc.appendChild(elem); + return doc.toString(); +} + +bool QtResourceView::decodeMimeData(const QMimeData *md, ResourceType *t, QString *file) +{ + return md->hasText() ? decodeMimeData(md->text(), t, file) : false; +} + +bool QtResourceView::decodeMimeData(const QString &text, ResourceType *t, QString *file) +{ + + static auto docElementName = elementResourceData; + static const QString docElementString = u'<' + docElementName; + + if (text.isEmpty() || text.indexOf(docElementString) == -1) + return false; + + QDomDocument doc; + if (!doc.setContent(text)) + return false; + + const QDomElement domElement = doc.documentElement(); + if (domElement.tagName() != docElementName) + return false; + + if (t) { + const QString typeAttr = typeAttribute; + if (domElement.hasAttribute (typeAttr)) { + const QString typeValue = domElement.attribute(typeAttr, typeOther); + if (typeValue == typeImage) { + *t = ResourceImage; + } else { + *t = typeValue == typeStyleSheet ? ResourceStyleSheet : ResourceOther; + } + } + } + if (file) { + const QString fileAttr = fileAttribute; + if (domElement.hasAttribute(fileAttr)) { + *file = domElement.attribute(fileAttr, QString()); + } else { + file->clear(); + } + } + return true; +} + +// ---------------------------- QtResourceViewDialogPrivate + +class QtResourceViewDialogPrivate +{ + QtResourceViewDialog *q_ptr; + Q_DECLARE_PUBLIC(QtResourceViewDialog) +public: + QtResourceViewDialogPrivate(QDesignerFormEditorInterface *core); + + void slotResourceSelected(const QString &resource) { setOkButtonEnabled(!resource.isEmpty()); } + void setOkButtonEnabled(bool v) { m_box->button(QDialogButtonBox::Ok)->setEnabled(v); } + + QDesignerFormEditorInterface *m_core; + QtResourceView *m_view; + QDialogButtonBox *m_box; +}; + +QtResourceViewDialogPrivate::QtResourceViewDialogPrivate(QDesignerFormEditorInterface *core) : + q_ptr(nullptr), + m_core(core), + m_view(new QtResourceView(core)), + m_box(new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)) +{ + m_view->setSettingsKey(ResourceViewDialogC); +} + +// ------------ QtResourceViewDialog +QtResourceViewDialog::QtResourceViewDialog(QDesignerFormEditorInterface *core, QWidget *parent) : + QDialog(parent), + d_ptr(new QtResourceViewDialogPrivate(core)) +{ + setWindowTitle(tr("Select Resource")); + d_ptr->q_ptr = this; + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(d_ptr->m_view); + layout->addWidget(d_ptr->m_box); + connect(d_ptr->m_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(d_ptr->m_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(d_ptr->m_view, &QtResourceView::resourceActivated, this, &QDialog::accept); + connect(d_ptr->m_view, &QtResourceView::resourceSelected, + this, [this](const QString &resource) { d_ptr->slotResourceSelected(resource); }); + d_ptr->setOkButtonEnabled(false); + d_ptr->m_view->setResourceModel(core->resourceModel()); + + QDesignerSettingsInterface *settings = core->settingsManager(); + settings->beginGroup(ResourceViewDialogC); + + const QVariant geometry = settings->value(qrvGeometry); + if (geometry.metaType().id() == QMetaType::QByteArray) // Used to be a QRect up until 5.4.0, QTBUG-43374. + restoreGeometry(geometry.toByteArray()); + + settings->endGroup(); +} + +QtResourceViewDialog::~QtResourceViewDialog() +{ + QDesignerSettingsInterface *settings = d_ptr->m_core->settingsManager(); + settings->beginGroup(ResourceViewDialogC); + + settings->setValue(qrvGeometry, saveGeometry()); + + settings->endGroup(); +} + +QString QtResourceViewDialog::selectedResource() const +{ + return d_ptr->m_view->selectedResource(); +} + +void QtResourceViewDialog::selectResource(const QString &path) +{ + d_ptr->m_view->selectResource(path); +} + +bool QtResourceViewDialog::isResourceEditingEnabled() const +{ + return d_ptr->m_view->isResourceEditingEnabled(); +} + +void QtResourceViewDialog::setResourceEditingEnabled(bool enable) +{ + d_ptr->m_view->setResourceEditingEnabled(enable); +} + +QT_END_NAMESPACE + +#include "moc_qtresourceview_p.cpp" diff --git a/src/tools/designer/src/lib/shared/qtresourceview_p.h b/src/tools/designer/src/lib/shared/qtresourceview_p.h new file mode 100644 index 00000000000..7d2ed6235c8 --- /dev/null +++ b/src/tools/designer/src/lib/shared/qtresourceview_p.h @@ -0,0 +1,92 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#ifndef QTRESOURCEVIEW_H +#define QTRESOURCEVIEW_H + +#include "shared_global_p.h" +#include +#include + +QT_BEGIN_NAMESPACE + +class QtResourceModel; +class QtResourceSet; +class QDesignerFormEditorInterface; +class QMimeData; + +class QDESIGNER_SHARED_EXPORT QtResourceView : public QWidget +{ + Q_OBJECT +public: + explicit QtResourceView(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~QtResourceView(); + + void setDragEnabled(bool dragEnabled); + bool dragEnabled() const; + + QtResourceModel *model() const; + void setResourceModel(QtResourceModel *model); + + QString selectedResource() const; + void selectResource(const QString &resource); + + QString settingsKey() const; + void setSettingsKey(const QString &key); + + bool isResourceEditingEnabled() const; + void setResourceEditingEnabled(bool enable); + + // Helpers for handling the drag originating in QtResourceView (Xml/text) + enum ResourceType { ResourceImage, ResourceStyleSheet, ResourceOther }; + static QString encodeMimeData(ResourceType resourceType, const QString &path); + + static bool decodeMimeData(const QMimeData *md, ResourceType *t = nullptr, QString *file = nullptr); + static bool decodeMimeData(const QString &text, ResourceType *t = nullptr, QString *file = nullptr); + +signals: + void resourceSelected(const QString &resource); + void resourceActivated(const QString &resource); + +protected: + bool event(QEvent *event) override; + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceView) + Q_DISABLE_COPY_MOVE(QtResourceView) +}; + +class QDESIGNER_SHARED_EXPORT QtResourceViewDialog : public QDialog +{ + Q_OBJECT +public: + explicit QtResourceViewDialog(QDesignerFormEditorInterface *core, QWidget *parent = nullptr); + ~QtResourceViewDialog() override; + + QString selectedResource() const; + void selectResource(const QString &path); + + bool isResourceEditingEnabled() const; + void setResourceEditingEnabled(bool enable); + +private: + QScopedPointer d_ptr; + Q_DECLARE_PRIVATE(QtResourceViewDialog) + Q_DISABLE_COPY_MOVE(QtResourceViewDialog) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/tools/designer/src/lib/shared/rcc.cpp b/src/tools/designer/src/lib/shared/rcc.cpp new file mode 100644 index 00000000000..38772d8a139 --- /dev/null +++ b/src/tools/designer/src/lib/shared/rcc.cpp @@ -0,0 +1,1549 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +/* Note: This is a copy of qtbase/src/tools/rcc/rcc.cpp. */ + +#include "rcc_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if QT_CONFIG(zstd) +# include +#endif + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +enum { + CONSTANT_USENAMESPACE = 1, + CONSTANT_COMPRESSLEVEL_DEFAULT = -1, + CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1, // Zstd level to check if compressing is a good idea + CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14, // Zstd level to actually store the data + CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70 +}; + +void RCCResourceLibrary::write(const char *str, int len) +{ + int n = m_out.size(); + m_out.resize(n + len); + memcpy(m_out.data() + n, str, len); +} + +void RCCResourceLibrary::writeByteArray(const QByteArray &other) +{ + if (m_format == Pass2) { + m_outDevice->write(other); + } else { + m_out.append(other); + } +} + +static inline QString msgOpenReadFailed(const QString &fname, const QString &why) +{ + return QString::fromLatin1("Unable to open %1 for reading: %2\n").arg(fname, why); +} + + +/////////////////////////////////////////////////////////// +// +// RCCFileInfo +// +/////////////////////////////////////////////////////////// + +class RCCFileInfo +{ +public: + enum Flags + { + // must match qresource.cpp + NoFlags = 0x00, + Compressed = 0x01, + Directory = 0x02, + CompressedZstd = 0x04 + }; + + + RCCFileInfo() = default; + RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language, + QLocale::Territory territory, uint flags, + RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, + int compressThreshold, bool noZstd, bool isEmpty); + + ~RCCFileInfo(); + RCCFileInfo(const RCCFileInfo &) = delete; + RCCFileInfo &operator=(const RCCFileInfo &) = delete; + RCCFileInfo(RCCFileInfo &&) = default; + RCCFileInfo &operator=(RCCFileInfo &&other) = delete; + + QString resourceName() const; + +public: + qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage); + qint64 writeDataName(RCCResourceLibrary &, qint64 offset); + void writeDataInfo(RCCResourceLibrary &lib); + + int m_flags = NoFlags; + QLocale::Language m_language = QLocale::C; + QLocale::Territory m_territory = QLocale::AnyTerritory; + QString m_name; + QFileInfo m_fileInfo; + RCCFileInfo *m_parent = nullptr; + QMultiHash m_children; + + RCCResourceLibrary::CompressionAlgorithm m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Best; + int m_compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT; + int m_compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT; + bool m_noZstd = false; + bool m_isEmpty = false; + + qint64 m_nameOffset = 0; + qint64 m_dataOffset = 0; + qint64 m_childOffset = 0; +}; + +RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo, QLocale::Language language, + QLocale::Territory territory, uint flags, + RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, + int compressThreshold, bool noZstd, bool isEmpty) + : m_flags(flags), + m_language(language), + m_territory(territory), + m_name(name), + m_fileInfo(fileInfo), + m_compressAlgo(compressAlgo), + m_compressLevel(compressLevel), + m_compressThreshold(compressThreshold), + m_noZstd(noZstd), + m_isEmpty(isEmpty) +{ +} + +RCCFileInfo::~RCCFileInfo() +{ + qDeleteAll(m_children); +} + +QString RCCFileInfo::resourceName() const +{ + QString resource = m_name; + for (RCCFileInfo *p = m_parent; p; p = p->m_parent) + resource = resource.prepend(p->m_name + u'/'); + resource.prepend(u':'); + return resource; +} + +void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib) +{ + const bool text = lib.m_format == RCCResourceLibrary::C_Code; + const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; + const bool python = lib.m_format == RCCResourceLibrary::Python_Code; + //some info + if (text || pass1) { + if (m_language != QLocale::C) { + lib.writeString(" // "); + lib.writeByteArray(resourceName().toLocal8Bit()); + lib.writeString(" ["); + lib.writeByteArray(QByteArray::number(m_territory)); + lib.writeString("::"); + lib.writeByteArray(QByteArray::number(m_language)); + lib.writeString("[\n "); + } else { + lib.writeString(" // "); + lib.writeByteArray(resourceName().toLocal8Bit()); + lib.writeString("\n "); + } + } + + //pointer data + if (m_flags & RCCFileInfo::Directory) { + // name offset + lib.writeNumber4(m_nameOffset); + + // flags + lib.writeNumber2(m_flags); + + // child count + lib.writeNumber4(m_children.size()); + + // first child offset + lib.writeNumber4(m_childOffset); + } else { + // name offset + lib.writeNumber4(m_nameOffset); + + // flags + lib.writeNumber2(m_flags); + + // locale + lib.writeNumber2(m_territory); + lib.writeNumber2(m_language); + + //data offset + lib.writeNumber4(m_dataOffset); + } + if (text || pass1) + lib.writeChar('\n'); + else if (python) + lib.writeString("\\\n"); + + if (lib.formatVersion() >= 2) { + // last modified time stamp + const QDateTime lastModified = m_fileInfo.lastModified(QTimeZone::UTC); + quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0); + static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong(); + if (sourceDate != 0) + lastmod = sourceDate; + static const quint64 sourceDate2 = 1000 * qgetenv("SOURCE_DATE_EPOCH").toULongLong(); + if (sourceDate2 != 0) + lastmod = sourceDate2; + lib.writeNumber8(lastmod); + if (text || pass1) + lib.writeChar('\n'); + else if (python) + lib.writeString("\\\n"); + } +} + +qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset, + QString *errorMessage) +{ + const bool text = lib.m_format == RCCResourceLibrary::C_Code; + const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; + const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2; + const bool binary = lib.m_format == RCCResourceLibrary::Binary; + const bool python = lib.m_format == RCCResourceLibrary::Python_Code; + + //capture the offset + m_dataOffset = offset; + QByteArray data; + + if (!m_isEmpty) { + //find the data to be written + QFile file(m_fileInfo.absoluteFilePath()); + if (!file.open(QFile::ReadOnly)) { + *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString()); + return 0; + } + + data = file.readAll(); + } + + // Check if compression is useful for this file + if (data.size() != 0) { +#if QT_CONFIG(zstd) + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best && !m_noZstd) { + m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd; + m_compressLevel = 19; // not ZSTD_maxCLevel(), as 20+ are experimental + } + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd && !m_noZstd) { + if (lib.m_zstdCCtx == nullptr) + lib.m_zstdCCtx = ZSTD_createCCtx(); + qsizetype size = data.size(); + size = ZSTD_COMPRESSBOUND(size); + + int compressLevel = m_compressLevel; + if (compressLevel < 0) + compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK; + + QByteArray compressed(size, Qt::Uninitialized); + char *dst = const_cast(compressed.constData()); + size_t n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, + data.constData(), data.size(), + compressLevel); + if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) { + // compressing is worth it + if (m_compressLevel < 0) { + // heuristic compression, so recompress + n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size, + data.constData(), data.size(), + CONSTANT_ZSTDCOMPRESSLEVEL_STORE); + } + if (ZSTD_isError(n)) { + QString msg = QString::fromLatin1("%1: error: compression with zstd failed: %2\n") + .arg(m_name, QString::fromUtf8(ZSTD_getErrorName(n))); + lib.m_errorDevice->write(msg.toUtf8()); + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: compressed using zstd (%2 -> %3)\n") + .arg(m_name).arg(data.size()).arg(n); + lib.m_errorDevice->write(msg.toUtf8()); + } + + lib.m_overallFlags |= CompressedZstd; + m_flags |= CompressedZstd; + data = std::move(compressed); + data.truncate(n); + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); + lib.m_errorDevice->write(msg.toUtf8()); + } + } +#endif +#ifndef QT_NO_COMPRESS + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) { + m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib; + m_compressLevel = 9; + } + if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) { + QByteArray compressed = + qCompress(reinterpret_cast(data.data()), data.size(), m_compressLevel); + + int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size()); + if (compressRatio >= m_compressThreshold) { + if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: compressed using zlib (%2 -> %3)\n") + .arg(m_name).arg(data.size()).arg(compressed.size()); + lib.m_errorDevice->write(msg.toUtf8()); + } + data = compressed; + lib.m_overallFlags |= Compressed; + m_flags |= Compressed; + } else if (lib.verbose()) { + QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name); + lib.m_errorDevice->write(msg.toUtf8()); + } + } +#endif // QT_NO_COMPRESS + } + + // some info + if (text || pass1) { + lib.writeString(" // "); + lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit()); + lib.writeString("\n "); + } + + // write the length + if (text || binary || pass2 || python) + lib.writeNumber4(data.size()); + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + offset += 4; + + // write the payload + const char *p = data.constData(); + if (text || python) { + for (int i = data.size(), j = 0; --i >= 0; --j) { + lib.writeHex(*p++); + if (j == 0) { + if (text) + lib.writeString("\n "); + else + lib.writeString("\\\n"); + j = 16; + } + } + } else if (binary || pass2) { + lib.writeByteArray(data); + } + offset += data.size(); + + // done + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + + return offset; +} + +qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset) +{ + const bool text = lib.m_format == RCCResourceLibrary::C_Code; + const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1; + const bool python = lib.m_format == RCCResourceLibrary::Python_Code; + + // capture the offset + m_nameOffset = offset; + + // some info + if (text || pass1) { + lib.writeString(" // "); + lib.writeByteArray(m_name.toLocal8Bit()); + lib.writeString("\n "); + } + + // write the length + lib.writeNumber2(m_name.size()); + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + offset += 2; + + // write the hash + lib.writeNumber4(qt_hash(m_name)); + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + offset += 4; + + // write the m_name + const QChar *unicode = m_name.unicode(); + for (int i = 0; i < m_name.size(); ++i) { + lib.writeNumber2(unicode[i].unicode()); + if ((text || pass1) && i % 16 == 0) + lib.writeString("\n "); + else if (python && i % 16 == 0) + lib.writeString("\\\n"); + } + offset += m_name.size()*2; + + // done + if (text || pass1) + lib.writeString("\n "); + else if (python) + lib.writeString("\\\n"); + + return offset; +} + + +/////////////////////////////////////////////////////////// +// +// RCCResourceLibrary +// +/////////////////////////////////////////////////////////// + +RCCResourceLibrary::Strings::Strings() : + TAG_RCC("RCC"_L1), + TAG_RESOURCE("qresource"_L1), + TAG_FILE("file"_L1), + ATTRIBUTE_LANG("lang"_L1), + ATTRIBUTE_PREFIX("prefix"_L1), + ATTRIBUTE_ALIAS("alias"_L1), + ATTRIBUTE_EMPTY("empty"_L1), + ATTRIBUTE_THRESHOLD("threshold"_L1), + ATTRIBUTE_COMPRESS("compress"_L1), + ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm")) +{ +} + +RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion) + : m_root(nullptr), + m_format(C_Code), + m_verbose(false), + m_compressionAlgo(CompressionAlgorithm::Best), + m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT), + m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT), + m_treeOffset(0), + m_namesOffset(0), + m_dataOffset(0), + m_overallFlags(0), + m_useNameSpace(CONSTANT_USENAMESPACE), + m_errorDevice(nullptr), + m_outDevice(nullptr), + m_formatVersion(formatVersion), + m_noZstd(false) +{ + m_out.reserve(30 * 1000 * 1000); +#if QT_CONFIG(zstd) + m_zstdCCtx = nullptr; +#endif +} + +RCCResourceLibrary::~RCCResourceLibrary() +{ + delete m_root; +#if QT_CONFIG(zstd) + ZSTD_freeCCtx(m_zstdCCtx); +#endif +} + +enum RCCXmlTag { + RccTag, + ResourceTag, + FileTag +}; +Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE); + +static bool parseBoolean(QStringView value, QString *errorMsg) +{ + if (value.compare("true"_L1, Qt::CaseInsensitive) == 0) + return true; + if (value.compare("false"_L1, Qt::CaseInsensitive) == 0) + return false; + + *errorMsg = QString::fromLatin1("Invalid value for boolean attribute: '%1'").arg(value); + return false; +} + +bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice, + const QString &fname, QString currentPath, bool listMode) +{ + Q_ASSERT(m_errorDevice); + const QChar slash = u'/'; + if (!currentPath.isEmpty() && !currentPath.endsWith(slash)) + currentPath += slash; + + QXmlStreamReader reader(inputDevice); + QStack tokens; + + QString prefix; + QLocale::Language language = QLocale::c().language(); + QLocale::Territory territory = QLocale::c().territory(); + QString alias; + bool empty = false; + auto compressAlgo = m_compressionAlgo; + int compressLevel = m_compressLevel; + int compressThreshold = m_compressThreshold; + + while (!reader.atEnd()) { + QXmlStreamReader::TokenType t = reader.readNext(); + switch (t) { + case QXmlStreamReader::StartElement: + if (reader.name() == m_strings.TAG_RCC) { + if (!tokens.isEmpty()) + reader.raiseError("expected tag"_L1); + else + tokens.push(RccTag); + } else if (reader.name() == m_strings.TAG_RESOURCE) { + if (tokens.isEmpty() || tokens.top() != RccTag) { + reader.raiseError("unexpected tag"_L1); + } else { + tokens.push(ResourceTag); + + QXmlStreamAttributes attributes = reader.attributes(); + language = QLocale::c().language(); + territory = QLocale::c().territory(); + + if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) { + QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString(); + QLocale lang = QLocale(attribute); + language = lang.language(); + if (2 == attribute.size()) { + // Language only + territory = QLocale::AnyTerritory; + } else { + territory = lang.territory(); + } + } + + prefix.clear(); + if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX)) + prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString(); + if (!prefix.startsWith(slash)) + prefix.prepend(slash); + if (!prefix.endsWith(slash)) + prefix += slash; + } + } else if (reader.name() == m_strings.TAG_FILE) { + if (tokens.isEmpty() || tokens.top() != ResourceTag) { + reader.raiseError("unexpected tag"_L1); + } else { + tokens.push(FileTag); + + QXmlStreamAttributes attributes = reader.attributes(); + alias.clear(); + if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS)) + alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString(); + + compressAlgo = m_compressionAlgo; + compressLevel = m_compressLevel; + compressThreshold = m_compressThreshold; + + QString errorString; + if (attributes.hasAttribute(m_strings.ATTRIBUTE_EMPTY)) + empty = parseBoolean(attributes.value(m_strings.ATTRIBUTE_EMPTY), &errorString); + else + empty = false; + + if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO)) + compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString); + if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) { + QString value = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString(); + compressLevel = parseCompressionLevel(compressAlgo, value, &errorString); + } + + // Special case for -no-compress + if (m_compressLevel == -2) + compressAlgo = CompressionAlgorithm::None; + + if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD)) + compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt(); + + if (!errorString.isEmpty()) + reader.raiseError(errorString); + } + } else { + reader.raiseError("unexpected tag: %1"_L1.arg(reader.name().toString())); + } + break; + + case QXmlStreamReader::EndElement: + if (reader.name() == m_strings.TAG_RCC) { + if (!tokens.isEmpty() && tokens.top() == RccTag) + tokens.pop(); + else + reader.raiseError("unexpected closing tag"_L1); + } else if (reader.name() == m_strings.TAG_RESOURCE) { + if (!tokens.isEmpty() && tokens.top() == ResourceTag) + tokens.pop(); + else + reader.raiseError("unexpected closing tag"_L1); + } else if (reader.name() == m_strings.TAG_FILE) { + if (!tokens.isEmpty() && tokens.top() == FileTag) + tokens.pop(); + else + reader.raiseError("unexpected closing tag"_L1); + } + break; + + case QXmlStreamReader::Characters: + if (reader.isWhitespace()) + break; + if (tokens.isEmpty() || tokens.top() != FileTag) { + reader.raiseError("unexpected text"_L1); + } else { + QString fileName = reader.text().toString(); + if (fileName.isEmpty()) { + const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + } + + if (alias.isNull()) + alias = fileName; + + alias = QDir::cleanPath(alias); + while (alias.startsWith("../"_L1)) + alias.remove(0, 3); + alias = QDir::cleanPath(m_resourceRoot) + prefix + alias; + + QString absFileName = fileName; + if (QDir::isRelativePath(absFileName)) + absFileName.prepend(currentPath); + QFileInfo file(absFileName); + if (file.isDir()) { + QDir dir(file.filePath()); + if (!alias.endsWith(slash)) + alias += slash; + + QStringList filePaths; + QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + if (it.fileName() == "."_L1 || it.fileName() == ".."_L1) + continue; + filePaths.append(it.filePath()); + } + + // make rcc output deterministic + std::sort(filePaths.begin(), filePaths.end()); + + for (const QString &filePath : filePaths) { + QFileInfo child(filePath); + const bool arc = + addFile(alias + child.fileName(), + RCCFileInfo(child.fileName(), child, language, territory, + child.isDir() ? RCCFileInfo::Directory + : RCCFileInfo::NoFlags, + compressAlgo, compressLevel, compressThreshold, + m_noZstd, empty)); + if (!arc) + m_failedResources.push_back(child.fileName()); + } + } else if (listMode || file.isFile()) { + const bool arc = + addFile(alias, + RCCFileInfo(alias.section(slash, -1), + file, + language, + territory, + RCCFileInfo::NoFlags, + compressAlgo, + compressLevel, + compressThreshold, + m_noZstd, empty) + ); + if (!arc) + m_failedResources.push_back(absFileName); + } else if (file.exists()) { + m_failedResources.push_back(absFileName); + const QString msg = QString::fromLatin1("RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n") + .arg(fname, fileName); + m_errorDevice->write(msg.toUtf8()); + return false; + } else { + m_failedResources.push_back(absFileName); + const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n") + .arg(fname, fileName); + m_errorDevice->write(msg.toUtf8()); + return false; + } + } + break; + + default: + break; + } + } + + if (reader.hasError()) { + int errorLine = reader.lineNumber(); + int errorColumn = reader.columnNumber(); + QString errorMessage = reader.errorString(); + QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage); + m_errorDevice->write(msg.toUtf8()); + return false; + } + + if (m_root == nullptr) { + const QString msg = QString::fromLatin1("RCC: Warning: No resources in '%1'.\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + if (!listMode && m_format == Binary) { + // create dummy entry, otherwise loading with QResource will crash + m_root = new RCCFileInfo{}; + m_root->m_flags = RCCFileInfo::Directory; + } + } + + return true; +} + +bool RCCResourceLibrary::addFile(const QString &alias, RCCFileInfo file) +{ + Q_ASSERT(m_errorDevice); + if (file.m_fileInfo.size() > 0xffffffff) { + const QString msg = QString::fromLatin1("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath()); + m_errorDevice->write(msg.toUtf8()); + return false; + } + if (!m_root) { + m_root = new RCCFileInfo{}; + m_root->m_flags = RCCFileInfo::Directory; + } + + RCCFileInfo *parent = m_root; + const QStringList nodes = alias.split(u'/'); + for (int i = 1; i < nodes.size()-1; ++i) { + const QString node = nodes.at(i); + if (node.isEmpty()) + continue; + if (!parent->m_children.contains(node)) { + RCCFileInfo *s = new RCCFileInfo{}; + s->m_name = node; + s->m_flags = RCCFileInfo::Directory; + s->m_parent = parent; + parent->m_children.insert(node, s); + parent = s; + } else { + parent = *parent->m_children.constFind(node); + } + } + + const QString filename = nodes.at(nodes.size()-1); + RCCFileInfo *s = new RCCFileInfo(std::move(file)); + s->m_parent = parent; + auto cbegin = parent->m_children.constFind(filename); + auto cend = parent->m_children.constEnd(); + for (auto it = cbegin; it != cend; ++it) { + if (it.key() == filename && it.value()->m_language == s->m_language && + it.value()->m_territory == s->m_territory) { + for (const QString &name : std::as_const(m_fileNames)) { + qWarning("%s: Warning: potential duplicate alias detected: '%s'", + qPrintable(name), qPrintable(filename)); + } + break; + } + } + parent->m_children.insert(filename, s); + return true; +} + +void RCCResourceLibrary::reset() +{ + if (m_root) { + delete m_root; + m_root = nullptr; + } + m_errorDevice = nullptr; + m_failedResources.clear(); +} + + +bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice) +{ + reset(); + m_errorDevice = &errorDevice; + //read in data + if (m_verbose) { + const QString msg = QString::fromLatin1("Processing %1 files [listMode=%2]\n") + .arg(m_fileNames.size()).arg(static_cast(listMode)); + m_errorDevice->write(msg.toUtf8()); + } + for (int i = 0; i < m_fileNames.size(); ++i) { + QFile fileIn; + QString fname = m_fileNames.at(i); + QString pwd; + if (fname == "-"_L1) { + fname = "(stdin)"_L1; + pwd = QDir::currentPath(); + fileIn.setFileName(fname); + if (!fileIn.open(stdin, QIODevice::ReadOnly)) { + m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); + return false; + } + } else { + pwd = QFileInfo(fname).path(); + fileIn.setFileName(fname); + if (!fileIn.open(QIODevice::ReadOnly)) { + m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8()); + return false; + } + } + if (m_verbose) { + const QString msg = QString::fromLatin1("Interpreting %1\n").arg(fname); + m_errorDevice->write(msg.toUtf8()); + } + + if (!interpretResourceFile(&fileIn, fname, pwd, listMode)) + return false; + } + return true; +} + +QStringList RCCResourceLibrary::dataFiles() const +{ + QStringList ret; + QStack pending; + + if (!m_root) + return ret; + pending.push(m_root); + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (auto it = file->m_children.begin(); + it != file->m_children.end(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + else + ret.append(child->m_fileInfo.filePath()); + } + } + return ret; +} + +// Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion +static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m) +{ + const QChar slash = u'/'; + const auto cend = m_root->m_children.constEnd(); + for (auto it = m_root->m_children.constBegin(); it != cend; ++it) { + const RCCFileInfo *child = it.value(); + const QString childName = path + slash + child->m_name; + if (child->m_flags & RCCFileInfo::Directory) { + resourceDataFileMapRecursion(child, childName, m); + } else { + m.insert(childName, child->m_fileInfo.filePath()); + } + } +} + +RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const +{ + ResourceDataFileMap rc; + if (m_root) + resourceDataFileMapRecursion(m_root, QString(u':'), rc); + return rc; +} + +RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg) +{ + if (value == "best"_L1) + return CompressionAlgorithm::Best; + if (value == "zlib"_L1) { +#ifdef QT_NO_COMPRESS + *errorMsg = "zlib support not compiled in"_L1; +#else + return CompressionAlgorithm::Zlib; +#endif + } else if (value == "zstd"_L1) { +#if QT_CONFIG(zstd) + return CompressionAlgorithm::Zstd; +#else + *errorMsg = "Zstandard support not compiled in"_L1; +#endif + } else if (value != "none"_L1) { + *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'").arg(value); + } + + return CompressionAlgorithm::None; +} + +int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg) +{ + bool ok; + int c = level.toInt(&ok); + if (ok) { + switch (algo) { + case CompressionAlgorithm::None: + case CompressionAlgorithm::Best: + return 0; + case CompressionAlgorithm::Zlib: + if (c >= 1 && c <= 9) + return c; + break; + case CompressionAlgorithm::Zstd: +#if QT_CONFIG(zstd) + if (c >= 0 && c <= ZSTD_maxCLevel()) + return c; +#endif + break; + } + } + + *errorMsg = QString::fromLatin1("invalid compression level '%1'").arg(level); + return 0; +} + +bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice) +{ + m_errorDevice = &errorDevice; + + if (m_format == Pass2) { + const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' }; + bool foundSignature = false; + + while (true) { + char c; + for (int i = 0; i < 8; ) { + if (!tempDevice.getChar(&c)) { + if (foundSignature) + return true; + m_errorDevice->write("No data signature found\n"); + return false; + } + + if (c != pattern[i]) { + for (int k = 0; k < i; ++k) + outDevice.putChar(pattern[k]); + i = 0; + } + + if (c == pattern[i]) { + ++i; + } else { + outDevice.putChar(c); + } + } + + m_outDevice = &outDevice; + quint64 start = outDevice.pos(); + writeDataBlobs(); + quint64 len = outDevice.pos() - start; + + tempDevice.seek(tempDevice.pos() + len - 8); + foundSignature = true; + } + } + + //write out + if (m_verbose) + m_errorDevice->write("Outputting code\n"); + if (!writeHeader()) { + m_errorDevice->write("Could not write header\n"); + return false; + } + if (m_root) { + if (!writeDataBlobs()) { + m_errorDevice->write("Could not write data blobs.\n"); + return false; + } + if (!writeDataNames()) { + m_errorDevice->write("Could not write file names\n"); + return false; + } + if (!writeDataStructure()) { + m_errorDevice->write("Could not write data tree\n"); + return false; + } + } + if (!writeInitializer()) { + m_errorDevice->write("Could not write footer\n"); + return false; + } + outDevice.write(m_out.constData(), m_out.size()); + return true; +} + +void RCCResourceLibrary::writeDecimal(int value) +{ + Q_ASSERT(m_format != RCCResourceLibrary::Binary); + char buf[std::numeric_limits::digits10 + 2]; + int n = snprintf(buf, sizeof(buf), "%d", value); + write(buf, n); +} + +static const char hexDigits[] = "0123456789abcdef"; + +inline void RCCResourceLibrary::write2HexDigits(quint8 number) +{ + writeChar(hexDigits[number >> 4]); + writeChar(hexDigits[number & 0xf]); +} + +void RCCResourceLibrary::writeHex(quint8 tmp) +{ + switch (m_format) { + case RCCResourceLibrary::Python_Code: + if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') { + writeChar(char(tmp)); + } else { + writeChar('\\'); + writeChar('x'); + write2HexDigits(tmp); + } + break; + default: + writeChar('0'); + writeChar('x'); + if (tmp < 16) + writeChar(hexDigits[tmp]); + else + write2HexDigits(tmp); + writeChar(','); + break; + } +} + +void RCCResourceLibrary::writeNumber2(quint16 number) +{ + if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 8); + writeHex(number); + } +} + +void RCCResourceLibrary::writeNumber4(quint32 number) +{ + if (m_format == RCCResourceLibrary::Pass2) { + m_outDevice->putChar(char(number >> 24)); + m_outDevice->putChar(char(number >> 16)); + m_outDevice->putChar(char(number >> 8)); + m_outDevice->putChar(char(number)); + } else if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 24); + writeChar(number >> 16); + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 24); + writeHex(number >> 16); + writeHex(number >> 8); + writeHex(number); + } +} + +void RCCResourceLibrary::writeNumber8(quint64 number) +{ + if (m_format == RCCResourceLibrary::Pass2) { + m_outDevice->putChar(char(number >> 56)); + m_outDevice->putChar(char(number >> 48)); + m_outDevice->putChar(char(number >> 40)); + m_outDevice->putChar(char(number >> 32)); + m_outDevice->putChar(char(number >> 24)); + m_outDevice->putChar(char(number >> 16)); + m_outDevice->putChar(char(number >> 8)); + m_outDevice->putChar(char(number)); + } else if (m_format == RCCResourceLibrary::Binary) { + writeChar(number >> 56); + writeChar(number >> 48); + writeChar(number >> 40); + writeChar(number >> 32); + writeChar(number >> 24); + writeChar(number >> 16); + writeChar(number >> 8); + writeChar(number); + } else { + writeHex(number >> 56); + writeHex(number >> 48); + writeHex(number >> 40); + writeHex(number >> 32); + writeHex(number >> 24); + writeHex(number >> 16); + writeHex(number >> 8); + writeHex(number); + } +} + +bool RCCResourceLibrary::writeHeader() +{ + switch (m_format) { + case C_Code: + case Pass1: + writeString("/****************************************************************************\n"); + writeString("** Resource object code\n"); + writeString("**\n"); + writeString("** Created by: The Resource Compiler for Qt version "); + writeByteArray(QT_VERSION_STR); + writeString("\n**\n"); + writeString("** WARNING! All changes made in this file will be lost!\n"); + writeString( "*****************************************************************************/\n\n"); + break; + case Python_Code: + writeString("# Resource object code (Python 3)\n"); + writeString("# Created by: object code\n"); + writeString("# Created by: The Resource Compiler for Qt version "); + writeByteArray(QT_VERSION_STR); + writeString("\n"); + writeString("# WARNING! All changes made in this file will be lost!\n\n"); + writeString("from PySide"); + writeByteArray(QByteArray::number(QT_VERSION_MAJOR)); + writeString(" import QtCore\n\n"); + break; + case Binary: + writeString("qres"); + writeNumber4(0); + writeNumber4(0); + writeNumber4(0); + writeNumber4(0); + if (m_formatVersion >= 3) + writeNumber4(m_overallFlags); + break; + default: + break; + } + return true; +} + +bool RCCResourceLibrary::writeDataBlobs() +{ + Q_ASSERT(m_errorDevice); + switch (m_format) { + case C_Code: + writeString("static const unsigned char qt_resource_data[] = {\n"); + break; + case Python_Code: + writeString("qt_resource_data = b\"\\\n"); + break; + case Binary: + m_dataOffset = m_out.size(); + break; + default: + break; + } + + if (!m_root) + return false; + + QStack pending; + pending.push(m_root); + qint64 offset = 0; + QString errorMessage; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + else { + offset = child->writeDataBlob(*this, offset, &errorMessage); + if (offset == 0) { + m_errorDevice->write(errorMessage.toUtf8()); + return false; + } + } + } + } + switch (m_format) { + case C_Code: + writeString("\n};\n\n"); + break; + case Python_Code: + writeString("\"\n\n"); + break; + case Pass1: + if (offset < 8) + offset = 8; + writeString("\nstatic const unsigned char qt_resource_data["); + writeByteArray(QByteArray::number(offset)); + writeString("] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n"); + break; + default: + break; + } + return true; +} + +bool RCCResourceLibrary::writeDataNames() +{ + switch (m_format) { + case C_Code: + case Pass1: + writeString("static const unsigned char qt_resource_name[] = {\n"); + break; + case Python_Code: + writeString("qt_resource_name = b\"\\\n"); + break; + case Binary: + m_namesOffset = m_out.size(); + break; + default: + break; + } + + QHash names; + QStack pending; + + if (!m_root) + return false; + + pending.push(m_root); + qint64 offset = 0; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + for (auto it = file->m_children.cbegin(); it != file->m_children.cend(); ++it) { + RCCFileInfo *child = it.value(); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + if (names.contains(child->m_name)) { + child->m_nameOffset = names.value(child->m_name); + } else { + names.insert(child->m_name, offset); + offset = child->writeDataName(*this, offset); + } + } + } + switch (m_format) { + case C_Code: + case Pass1: + writeString("\n};\n\n"); + break; + case Python_Code: + writeString("\"\n\n"); + break; + default: + break; + } + return true; +} + +struct qt_rcc_compare_hash +{ + typedef bool result_type; + result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const + { + return qt_hash(left->m_name) < qt_hash(right->m_name); + } +}; + +bool RCCResourceLibrary::writeDataStructure() +{ + switch (m_format) { + case C_Code: + case Pass1: + writeString("static const unsigned char qt_resource_struct[] = {\n"); + break; + case Python_Code: + writeString("qt_resource_struct = b\"\\\n"); + break; + case Binary: + m_treeOffset = m_out.size(); + break; + default: + break; + } + + QStack pending; + + if (!m_root) + return false; + + //calculate the child offsets (flat) + pending.push(m_root); + int offset = 1; + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + file->m_childOffset = offset; + + //sort by hash value for binary lookup + QList m_children = file->m_children.values(); + std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); + + //write out the actual data now + for (int i = 0; i < m_children.size(); ++i) { + RCCFileInfo *child = m_children.at(i); + ++offset; + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + } + } + + //write out the structure (ie iterate again!) + pending.push(m_root); + m_root->writeDataInfo(*this); + while (!pending.isEmpty()) { + RCCFileInfo *file = pending.pop(); + + //sort by hash value for binary lookup + QList m_children = file->m_children.values(); + std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash()); + + //write out the actual data now + for (int i = 0; i < m_children.size(); ++i) { + RCCFileInfo *child = m_children.at(i); + child->writeDataInfo(*this); + if (child->m_flags & RCCFileInfo::Directory) + pending.push(child); + } + } + switch (m_format) { + case C_Code: + case Pass1: + writeString("\n};\n\n"); + break; + case Python_Code: + writeString("\"\n\n"); + break; + default: + break; + } + + return true; +} + +void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name) +{ + if (m_useNameSpace) { + writeString("QT_RCC_MANGLE_NAMESPACE("); + writeByteArray(name); + writeChar(')'); + } else { + writeByteArray(name); + } +} + +void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name) +{ + if (m_useNameSpace) { + writeString("QT_RCC_PREPEND_NAMESPACE("); + writeByteArray(name); + writeChar(')'); + } else { + writeByteArray(name); + } +} + +bool RCCResourceLibrary::writeInitializer() +{ + if (m_format == C_Code || m_format == Pass1) { + //write("\nQT_BEGIN_NAMESPACE\n"); + QString initNameStr = m_initName; + if (!initNameStr.isEmpty()) { + initNameStr.prepend(u'_'); + auto isAsciiLetterOrNumber = [] (QChar c) -> bool { + ushort ch = c.unicode(); + return (ch >= '0' && ch <= '9') || + (ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z') || + ch == '_'; + }; + for (QChar &c : initNameStr) { + if (!isAsciiLetterOrNumber(c)) + c = u'_'; + } + } + QByteArray initName = initNameStr.toLatin1(); + + //init + if (m_useNameSpace) { + writeString("#ifdef QT_NAMESPACE\n" + "# define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n" + "# define QT_RCC_MANGLE_NAMESPACE0(x) x\n" + "# define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n" + "# define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n" + "# define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n" + " QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n" + "#else\n" + "# define QT_RCC_PREPEND_NAMESPACE(name) name\n" + "# define QT_RCC_MANGLE_NAMESPACE(name) name\n" + "#endif\n\n"); + + writeString("#ifdef QT_NAMESPACE\n" + "namespace QT_NAMESPACE {\n" + "#endif\n\n"); + } + + if (m_root) { + writeString("bool qRegisterResourceData" + "(int, const unsigned char *, " + "const unsigned char *, const unsigned char *);\n"); + writeString("bool qUnregisterResourceData" + "(int, const unsigned char *, " + "const unsigned char *, const unsigned char *);\n\n"); + + if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) { + // use variable relocations with ELF and Mach-O + writeString("#if defined(__ELF__) || defined(__APPLE__)\n"); + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("static inline unsigned char qResourceFeatureZlib()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZlib;\n" + " return qt_resourceFeatureZlib;\n" + "}\n"); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("static inline unsigned char qResourceFeatureZstd()\n" + "{\n" + " extern const unsigned char qt_resourceFeatureZstd;\n" + " return qt_resourceFeatureZstd;\n" + "}\n"); + } + writeString("#else\n"); + if (m_overallFlags & RCCFileInfo::Compressed) + writeString("unsigned char qResourceFeatureZlib();\n"); + if (m_overallFlags & RCCFileInfo::CompressedZstd) + writeString("unsigned char qResourceFeatureZstd();\n"); + writeString("#endif\n\n"); + } + } + + if (m_useNameSpace) + writeString("#ifdef QT_NAMESPACE\n}\n#endif\n\n"); + + QByteArray initResources = "qInitResources"; + initResources += initName; + + // Work around -Wmissing-declarations warnings. + writeString("int "); + writeMangleNamespaceFunction(initResources); + writeString("();\n"); + + writeString("int "); + writeMangleNamespaceFunction(initResources); + writeString("()\n{\n"); + + if (m_root) { + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); + writeAddNamespaceFunction("qRegisterResourceData"); + writeString("\n (version, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); + } + writeString(" return 1;\n"); + writeString("}\n\n"); + + //cleanup + QByteArray cleanResources = "qCleanupResources"; + cleanResources += initName; + + // Work around -Wmissing-declarations warnings. + writeString("int "); + writeMangleNamespaceFunction(cleanResources); + writeString("();\n"); + + writeString("int "); + writeMangleNamespaceFunction(cleanResources); + writeString("()\n{\n"); + if (m_root) { + writeString(" int version = "); + writeDecimal(m_formatVersion); + writeString(";\n "); + + // ODR-use certain symbols from QtCore if we require optional features + if (m_overallFlags & RCCFileInfo::Compressed) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZlib()"); + writeString(";\n "); + } + if (m_overallFlags & RCCFileInfo::CompressedZstd) { + writeString("version += "); + writeAddNamespaceFunction("qResourceFeatureZstd()"); + writeString(";\n "); + } + + writeAddNamespaceFunction("qUnregisterResourceData"); + writeString("\n (version, qt_resource_struct, " + "qt_resource_name, qt_resource_data);\n"); + } + writeString(" return 1;\n"); + writeString("}\n\n"); + + // -Wexit-time-destructors was added to clang 3.0.0 in 2011. + writeString("#ifdef __clang__\n" + "# pragma clang diagnostic push\n" + "# pragma clang diagnostic ignored \"-Wexit-time-destructors\"\n" + "#endif\n\n"); + + writeString("namespace {\n" + " struct initializer {\n"); + + if (m_useNameSpace) { + writeByteArray(" initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n" + " ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n"); + } else { + writeByteArray(" initializer() { " + initResources + "(); }\n" + " ~initializer() { " + cleanResources + "(); }\n"); + } + writeString(" } dummy;\n" + "}\n\n"); + + writeString("#ifdef __clang__\n" + "# pragma clang diagnostic pop\n" + "#endif\n"); + + + } else if (m_format == Binary) { + int i = 4; + char *p = m_out.data(); + p[i++] = 0; + p[i++] = 0; + p[i++] = 0; + p[i++] = m_formatVersion; + + p[i++] = (m_treeOffset >> 24) & 0xff; + p[i++] = (m_treeOffset >> 16) & 0xff; + p[i++] = (m_treeOffset >> 8) & 0xff; + p[i++] = (m_treeOffset >> 0) & 0xff; + + p[i++] = (m_dataOffset >> 24) & 0xff; + p[i++] = (m_dataOffset >> 16) & 0xff; + p[i++] = (m_dataOffset >> 8) & 0xff; + p[i++] = (m_dataOffset >> 0) & 0xff; + + p[i++] = (m_namesOffset >> 24) & 0xff; + p[i++] = (m_namesOffset >> 16) & 0xff; + p[i++] = (m_namesOffset >> 8) & 0xff; + p[i++] = (m_namesOffset >> 0) & 0xff; + + if (m_formatVersion >= 3) { + p[i++] = (m_overallFlags >> 24) & 0xff; + p[i++] = (m_overallFlags >> 16) & 0xff; + p[i++] = (m_overallFlags >> 8) & 0xff; + p[i++] = (m_overallFlags >> 0) & 0xff; + } + } else if (m_format == Python_Code) { + writeString("def qInitResources():\n"); + writeString(" QtCore.qRegisterResourceData(0x"); + write2HexDigits(m_formatVersion); + writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); + writeString("def qCleanupResources():\n"); + writeString(" QtCore.qUnregisterResourceData(0x"); + write2HexDigits(m_formatVersion); + writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n"); + writeString("qInitResources()\n"); + } + return true; +} + +QT_END_NAMESPACE diff --git a/src/tools/designer/src/lib/shared/rcc_p.h b/src/tools/designer/src/lib/shared/rcc_p.h new file mode 100644 index 00000000000..82d17c875cb --- /dev/null +++ b/src/tools/designer/src/lib/shared/rcc_p.h @@ -0,0 +1,166 @@ +// Copyright (C) 2018 The Qt Company Ltd. +// Copyright (C) 2018 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of Qt Designer. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// Note: This is a copy of qtbase/src/tools/rcc/rcc.h. + +#ifndef RCC_H +#define RCC_H + +#include +#include +#include + +typedef struct ZSTD_CCtx_s ZSTD_CCtx; + +QT_BEGIN_NAMESPACE + +class RCCFileInfo; +class QIODevice; +class QTextStream; + + +class RCCResourceLibrary +{ + RCCResourceLibrary(const RCCResourceLibrary &); + RCCResourceLibrary &operator=(const RCCResourceLibrary &); + +public: + RCCResourceLibrary(quint8 formatVersion); + ~RCCResourceLibrary(); + + bool output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice); + + bool readFiles(bool listMode, QIODevice &errorDevice); + + enum Format { Binary, C_Code, Pass1, Pass2, Python_Code }; + void setFormat(Format f) { m_format = f; } + Format format() const { return m_format; } + + void setInputFiles(const QStringList &files) { m_fileNames = files; } + QStringList inputFiles() const { return m_fileNames; } + + QStringList dataFiles() const; + + // Return a map of resource identifier (':/newPrefix/images/p1.png') to file. + typedef QHash ResourceDataFileMap; + ResourceDataFileMap resourceDataFileMap() const; + + void setVerbose(bool b) { m_verbose = b; } + bool verbose() const { return m_verbose; } + + void setInitName(const QString &name) { m_initName = name; } + QString initName() const { return m_initName; } + + void setOutputName(const QString &name) { m_outputName = name; } + QString outputName() const { return m_outputName; } + + enum class CompressionAlgorithm { + Zlib, + Zstd, + + Best = 99, + None = -1 + }; + + static CompressionAlgorithm parseCompressionAlgorithm(QStringView algo, QString *errorMsg); + void setCompressionAlgorithm(CompressionAlgorithm algo) { m_compressionAlgo = algo; } + CompressionAlgorithm compressionAlgorithm() const { return m_compressionAlgo; } + + static int parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg); + void setCompressLevel(int c) { m_compressLevel = c; } + int compressLevel() const { return m_compressLevel; } + + void setCompressThreshold(int t) { m_compressThreshold = t; } + int compressThreshold() const { return m_compressThreshold; } + + void setResourceRoot(const QString &root) { m_resourceRoot = root; } + QString resourceRoot() const { return m_resourceRoot; } + + void setUseNameSpace(bool v) { m_useNameSpace = v; } + bool useNameSpace() const { return m_useNameSpace; } + + QStringList failedResources() const { return m_failedResources; } + + int formatVersion() const { return m_formatVersion; } + + void setNoZstd(bool v) { m_noZstd = v; } + bool noZstd() const { return m_noZstd; } + +private: + struct Strings { + Strings(); + const QString TAG_RCC; + const QString TAG_RESOURCE; + const QString TAG_FILE; + const QString ATTRIBUTE_LANG; + const QString ATTRIBUTE_PREFIX; + const QString ATTRIBUTE_ALIAS; + const QString ATTRIBUTE_EMPTY; + const QString ATTRIBUTE_THRESHOLD; + const QString ATTRIBUTE_COMPRESS; + const QString ATTRIBUTE_COMPRESSALGO; + }; + friend class RCCFileInfo; + void reset(); + bool addFile(const QString &alias, RCCFileInfo file); + bool interpretResourceFile(QIODevice *inputDevice, const QString &file, + QString currentPath = QString(), bool listMode = false); + bool writeHeader(); + bool writeDataBlobs(); + bool writeDataNames(); + bool writeDataStructure(); + bool writeInitializer(); + void writeMangleNamespaceFunction(const QByteArray &name); + void writeAddNamespaceFunction(const QByteArray &name); + void writeDecimal(int value); + void writeHex(quint8 number); + void write2HexDigits(quint8 number); + void writeNumber2(quint16 number); + void writeNumber4(quint32 number); + void writeNumber8(quint64 number); + void writeChar(char c) { m_out.append(c); } + void writeByteArray(const QByteArray &); + void write(const char *, int len); + void writeString(const char *s) { write(s, static_cast(strlen(s))); } + +#if QT_CONFIG(zstd) + ZSTD_CCtx *m_zstdCCtx; +#endif + + const Strings m_strings; + RCCFileInfo *m_root; + QStringList m_fileNames; + QString m_resourceRoot; + QString m_initName; + QString m_outputName; + Format m_format; + bool m_verbose; + CompressionAlgorithm m_compressionAlgo; + int m_compressLevel; + int m_compressThreshold; + int m_treeOffset; + int m_namesOffset; + int m_dataOffset; + quint32 m_overallFlags; + bool m_useNameSpace; + QStringList m_failedResources; + QIODevice *m_errorDevice; + QIODevice *m_outDevice; + QByteArray m_out; + quint8 m_formatVersion; + bool m_noZstd; +}; + +QT_END_NAMESPACE + +#endif // RCC_H diff --git a/src/tools/designer/src/lib/shared/richtexteditor.cpp b/src/tools/designer/src/lib/shared/richtexteditor.cpp new file mode 100644 index 00000000000..e6b41414956 --- /dev/null +++ b/src/tools/designer/src/lib/shared/richtexteditor.cpp @@ -0,0 +1,883 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +#include "richtexteditor_p.h" +#include "htmlhighlighter_p.h" +#include "iconselector_p.h" +#include "ui_addlinkdialog.h" + +#include "iconloader_p.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +using namespace Qt::StringLiterals; + +static constexpr auto RichTextDialogGroupC = "RichTextDialog"_L1; +static constexpr auto GeometryKeyC = "Geometry"_L1; +static constexpr auto TabKeyC = "Tab"_L1; + +const bool simplifyRichTextDefault = true; + +namespace qdesigner_internal { + +// Richtext simplification filter helpers: Elements to be discarded +static inline bool filterElement(QStringView name) +{ + return name != "meta"_L1 && name != "style"_L1; +} + +// Richtext simplification filter helpers: Filter attributes of elements +static inline void filterAttributes(QStringView name, + QXmlStreamAttributes *atts, + bool *paragraphAlignmentFound) +{ + if (atts->isEmpty()) + return; + + // No style attributes for + if (name == "body"_L1) { + atts->clear(); + return; + } + + // Clean out everything except 'align' for 'p' + if (name == "p"_L1) { + for (auto it = atts->begin(); it != atts->end(); ) { + if (it->name() == "align"_L1) { + ++it; + *paragraphAlignmentFound = true; + } else { + it = atts->erase(it); + } + } + return; + } +} + +// Richtext simplification filter helpers: Check for blank QStringView. +static inline bool isWhiteSpace(QStringView in) +{ + return std::all_of(in.cbegin(), in.cend(), + [](QChar c) { return c.isSpace(); }); +} + +// Richtext simplification filter: Remove hard-coded font settings, +//