Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:significant reason:default
++
++ #include "stylesheeteditor_p.h"
++ #include "csshighlighter_p.h"
++ #include "iconloader_p.h"
++ #include "iconselector_p.h"
++ #include "qtgradientmanager_p.h"
++ #include "qtgradientviewdialog_p.h"
++ #include "qtgradientutils_p.h"
++ #include "qdesigner_utils_p.h"
++
++ #include <QtDesigner/abstractformwindow.h>
++ #include <QtDesigner/abstractformwindowcursor.h>
++ #include <QtDesigner/abstractformeditor.h>
++ #include <QtDesigner/propertysheet.h>
++ #include <QtDesigner/abstractintegration.h>
++ #include <QtDesigner/abstractsettings.h>
++ #include <QtDesigner/qextensionmanager.h>
++
++ #include <texteditfindwidget_p.h>
++
++ #include <QtWidgets/qcolordialog.h>
++ #include <QtWidgets/qdialogbuttonbox.h>
++ #include <QtWidgets/qfontdialog.h>
++ #include <QtWidgets/qmenu.h>
++ #include <QtWidgets/qpushbutton.h>
++ #include <QtWidgets/qtoolbar.h>
++ #include <QtWidgets/qboxlayout.h>
++
++ #include <QtGui/qaction.h>
++ #include <QtGui/qevent.h>
++ #include <QtGui/qtextdocument.h>
++
++ #include <private/qcssparser_p.h>
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ static constexpr auto styleSheetProperty = "styleSheet"_L1;
++ static constexpr auto StyleSheetDialogC = "StyleSheetDialog"_L1;
++ static constexpr auto seGeometry = "Geometry"_L1;
++
++ namespace qdesigner_internal {
++
++ StyleSheetEditor::StyleSheetEditor(QWidget *parent)
++ : QTextEdit(parent)
++ {
++ setTabStopDistance(fontMetrics().horizontalAdvance(u' ') * 4);
++ setAcceptRichText(false);
++
++ const bool darkMode = isDarkMode();
++
++ CssHighlightColors colors;
++ colors.selector = darkMode ? QColor(Qt::red).lighter() : QColor(Qt::darkRed);
++ const QColor blue(Qt::blue);
++ colors.property = darkMode ? blue.lighter() : blue;
++ colors.pseudo1 = colors.pseudo2 = colors.value = palette().color(QPalette::WindowText);
++ colors.quote = darkMode ? Qt::magenta : Qt::darkMagenta;
++ colors.comment = darkMode ? Qt::green : Qt::darkGreen;
++
++ new CssHighlighter(colors, document());
++ }
++
++ // --- StyleSheetEditorDialog
++ StyleSheetEditorDialog::StyleSheetEditorDialog(QDesignerFormEditorInterface *core, QWidget *parent, Mode mode):
++ QDialog(parent),
++ m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Help)),
++ m_editor(new StyleSheetEditor),
++ m_findWidget(new TextEditFindWidget),
++ m_validityLabel(new QLabel(tr("Valid Style Sheet"))),
++ m_core(core),
++ m_addResourceAction(new QAction(tr("Add Resource..."), this)),
++ m_addGradientAction(new QAction(tr("Add Gradient..."), this)),
++ m_addColorAction(new QAction(tr("Add Color..."), this)),
++ m_addFontAction(new QAction(tr("Add Font..."), this))
++ {
++ setWindowTitle(tr("Edit Style Sheet"));
++
++ connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
++ connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
++ connect(m_buttonBox, &QDialogButtonBox::helpRequested,
++ this, &StyleSheetEditorDialog::slotRequestHelp);
++ m_buttonBox->button(QDialogButtonBox::Help)->setShortcut(QKeySequence::HelpContents);
++
++ connect(m_editor, &QTextEdit::textChanged, this, &StyleSheetEditorDialog::validateStyleSheet);
++ m_findWidget->setTextEdit(m_editor);
++
++ QToolBar *toolBar = new QToolBar;
++
++ QGridLayout *layout = new QGridLayout;
++ layout->addWidget(toolBar, 0, 0, 1, 2);
++ layout->addWidget(m_editor, 1, 0, 1, 2);
++ layout->addWidget(m_findWidget, 2, 0, 1, 2);
++ layout->addWidget(m_validityLabel, 3, 0, 1, 1);
++ layout->addWidget(m_buttonBox, 3, 1, 1, 1);
++ setLayout(layout);
++
++ m_editor->setContextMenuPolicy(Qt::CustomContextMenu);
++ connect(m_editor, &QWidget::customContextMenuRequested,
++ this, &StyleSheetEditorDialog::slotContextMenuRequested);
++
++ connect(m_addResourceAction, &QAction::triggered,
++ this, [this] { this->slotAddResource(QString()); });
++ connect(m_addGradientAction, &QAction::triggered,
++ this, [this] { this->slotAddGradient(QString()); });
++ connect(m_addColorAction, &QAction::triggered,
++ this, [this] { this->slotAddColor(QString()); });
++ connect(m_addFontAction, &QAction::triggered, this, &StyleSheetEditorDialog::slotAddFont);
++
++ m_addResourceAction->setEnabled(mode == ModePerForm);
++
++ const char * const resourceProperties[] = {
++ "background-image",
++ "border-image",
++ "image",
++ nullptr
++ };
++
++ const char * const colorProperties[] = {
++ "color",
++ "background-color",
++ "alternate-background-color",
++ "border-color",
++ "border-top-color",
++ "border-right-color",
++ "border-bottom-color",
++ "border-left-color",
++ "gridline-color",
++ "selection-color",
++ "selection-background-color",
++ nullptr
++ };
++
++ QMenu *resourceActionMenu = new QMenu(this);
++ QMenu *gradientActionMenu = new QMenu(this);
++ QMenu *colorActionMenu = new QMenu(this);
++
++ for (int resourceProperty = 0; resourceProperties[resourceProperty]; ++resourceProperty) {
++ const QString resourcePropertyName = QLatin1StringView(resourceProperties[resourceProperty]);
++ resourceActionMenu->addAction(resourcePropertyName,
++ this, [this, resourcePropertyName] { this->slotAddResource(resourcePropertyName); });
++ }
++
++ for (int colorProperty = 0; colorProperties[colorProperty]; ++colorProperty) {
++ const QString colorPropertyName = QLatin1StringView(colorProperties[colorProperty]);
++ colorActionMenu->addAction(colorPropertyName,
++ this, [this, colorPropertyName] { this->slotAddColor(colorPropertyName); });
++ gradientActionMenu->addAction(colorPropertyName,
++ this, [this, colorPropertyName] { this->slotAddGradient(colorPropertyName); } );
++ }
++
++ m_addResourceAction->setMenu(resourceActionMenu);
++ m_addGradientAction->setMenu(gradientActionMenu);
++ m_addColorAction->setMenu(colorActionMenu);
++
++
++ toolBar->addAction(m_addResourceAction);
++ toolBar->addAction(m_addGradientAction);
++ toolBar->addAction(m_addColorAction);
++ toolBar->addAction(m_addFontAction);
++ m_findAction = m_findWidget->createFindAction(toolBar);
++ toolBar->addAction(m_findAction);
++
++ m_editor->setFocus();
++
++ QDesignerSettingsInterface *settings = core->settingsManager();
++ settings->beginGroup(StyleSheetDialogC);
++
++ if (settings->contains(seGeometry))
++ restoreGeometry(settings->value(seGeometry).toByteArray());
++
++ settings->endGroup();
++ }
++
++ StyleSheetEditorDialog::~StyleSheetEditorDialog()
++ {
++ QDesignerSettingsInterface *settings = m_core->settingsManager();
++ settings->beginGroup(StyleSheetDialogC);
++
++ settings->setValue(seGeometry, saveGeometry());
++ settings->endGroup();
++ }
++
++ void StyleSheetEditorDialog::setOkButtonEnabled(bool v)
++ {
++ m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(v);
++ if (QPushButton *applyButton = m_buttonBox->button(QDialogButtonBox::Apply))
++ applyButton->setEnabled(v);
++ }
++
++ void StyleSheetEditorDialog::slotContextMenuRequested(const QPoint &pos)
++ {
++ QMenu *menu = m_editor->createStandardContextMenu();
++ menu->addSeparator();
++ menu->addAction(m_findAction);
++ menu->addSeparator();
++ menu->addAction(m_addResourceAction);
++ menu->addAction(m_addGradientAction);
++ menu->exec(mapToGlobal(pos));
++ delete menu;
++ }
++
++ void StyleSheetEditorDialog::slotAddResource(const QString &property)
++ {
++ const QString path = IconSelector::choosePixmapResource(m_core, m_core->resourceModel(), QString(), this);
++ if (!path.isEmpty())
++ insertCssProperty(property, "url("_L1 + path + u')');
++ }
++
++ void StyleSheetEditorDialog::slotAddGradient(const QString &property)
++ {
++ bool ok;
++ const QGradient grad = QtGradientViewDialog::getGradient(&ok, m_core->gradientManager(), this);
++ if (ok)
++ insertCssProperty(property, QtGradientUtils::styleSheetCode(grad));
++ }
++
++ void StyleSheetEditorDialog::slotAddColor(const QString &property)
++ {
++ const QColor color = QColorDialog::getColor(0xffffffff, this, QString(), QColorDialog::ShowAlphaChannel);
++ if (!color.isValid())
++ return;
++
++ QString colorStr;
++
++ if (color.alpha() == 255) {
++ colorStr = QString::asprintf("rgb(%d, %d, %d)",
++ color.red(), color.green(), color.blue());
++ } else {
++ colorStr = QString::asprintf("rgba(%d, %d, %d, %d)",
++ color.red(), color.green(), color.blue(),
++ color.alpha());
++ }
++
++ insertCssProperty(property, colorStr);
++ }
++
++ void StyleSheetEditorDialog::slotAddFont()
++ {
++ bool ok;
++ QFont font = QFontDialog::getFont(&ok, this);
++ if (ok) {
++ QString fontStr;
++ if (font.weight() != QFont::Normal)
++ fontStr += QString::number(font.weight()) + u' ';
++
++ switch (font.style()) {
++ case QFont::StyleItalic:
++ fontStr += "italic "_L1;
++ break;
++ case QFont::StyleOblique:
++ fontStr += "oblique "_L1;
++ break;
++ default:
++ break;
++ }
++ fontStr += QString::number(font.pointSize());
++ fontStr += "pt \""_L1;
++ fontStr += font.family();
++ fontStr += u'"';
++
++ insertCssProperty(u"font"_s, fontStr);
++ QString decoration;
++ if (font.underline())
++ decoration += "underline"_L1;
++ if (font.strikeOut()) {
++ if (!decoration.isEmpty())
++ decoration += u' ';
++ decoration += "line-through"_L1;
++ }
++ insertCssProperty(u"text-decoration"_s, decoration);
++ }
++ }
++
++ void StyleSheetEditorDialog::insertCssProperty(const QString &name, const QString &value)
++ {
++ if (!value.isEmpty()) {
++ QTextCursor cursor = m_editor->textCursor();
++ if (!name.isEmpty()) {
++ cursor.beginEditBlock();
++ cursor.removeSelectedText();
++ cursor.movePosition(QTextCursor::EndOfLine);
++
++ // Simple check to see if we're in a selector scope
++ const QTextDocument *doc = m_editor->document();
++ const QTextCursor closing = doc->find(u"}"_s, cursor, QTextDocument::FindBackward);
++ const QTextCursor opening = doc->find(u"{"_s, cursor, QTextDocument::FindBackward);
++ const bool inSelector = !opening.isNull() && (closing.isNull() ||
++ closing.position() < opening.position());
++ QString insertion;
++ if (m_editor->textCursor().block().length() != 1)
++ insertion += u'\n';
++ if (inSelector)
++ insertion += u'\t';
++ insertion += name;
++ insertion += ": "_L1;
++ insertion += value;
++ insertion += u';';
++ cursor.insertText(insertion);
++ cursor.endEditBlock();
++ } else {
++ cursor.insertText(value);
++ }
++ }
++ }
++
++ void StyleSheetEditorDialog::slotRequestHelp()
++ {
++ m_core->integration()->emitHelpRequested(u"qtwidgets"_s,
++ u"stylesheet-reference.html"_s);
++ }
++
++ // See QDialog::keyPressEvent()
++ static inline bool isEnter(const QKeyEvent *e)
++ {
++ const bool isEnter = e->key() == Qt::Key_Enter;
++ const bool isReturn = e->key() == Qt::Key_Return;
++ return (e->modifiers() == Qt::KeyboardModifiers() && (isEnter || isReturn))
++ || (e->modifiers().testFlag(Qt::KeypadModifier) && isEnter);
++ }
++
++ void StyleSheetEditorDialog::keyPressEvent(QKeyEvent *e)
++ {
++ // As long as the find widget is visible, suppress the default button
++ // behavior (close on Enter) of QDialog.
++ if (!(m_findWidget->isVisible() && isEnter(e)))
++ QDialog::keyPressEvent(e);
++ }
++
++ QDialogButtonBox * StyleSheetEditorDialog::buttonBox() const
++ {
++ return m_buttonBox;
++ }
++
++ QString StyleSheetEditorDialog::text() const
++ {
++ return m_editor->toPlainText();
++ }
++
++ void StyleSheetEditorDialog::setText(const QString &t)
++ {
++ m_editor->setText(t);
++ }
++
++ bool StyleSheetEditorDialog::isStyleSheetValid(const QString &styleSheet)
++ {
++ QCss::Parser parser(styleSheet);
++ QCss::StyleSheet sheet;
++ if (parser.parse(&sheet))
++ return true;
++ QCss::Parser parser2("* { "_L1 + styleSheet + '}'_L1);
++ return parser2.parse(&sheet);
++ }
++
++ void StyleSheetEditorDialog::validateStyleSheet()
++ {
++ const bool valid = isStyleSheetValid(m_editor->toPlainText());
++ setOkButtonEnabled(valid);
++ if (valid) {
++ m_validityLabel->setText(tr("Valid Style Sheet"));
++ m_validityLabel->setStyleSheet(u"color: green"_s);
++ } else {
++ m_validityLabel->setText(tr("Invalid Style Sheet"));
++ m_validityLabel->setStyleSheet(u"color: red"_s);
++ }
++ }
++
++ // --- StyleSheetPropertyEditorDialog
++ StyleSheetPropertyEditorDialog::StyleSheetPropertyEditorDialog(QWidget *parent,
++ QDesignerFormWindowInterface *fw,
++ QWidget *widget):
++ StyleSheetEditorDialog(fw->core(), parent),
++ m_fw(fw),
++ m_widget(widget)
++ {
++ Q_ASSERT(m_fw != nullptr);
++
++ QPushButton *apply = buttonBox()->addButton(QDialogButtonBox::Apply);
++ QObject::connect(apply, &QAbstractButton::clicked,
++ this, &StyleSheetPropertyEditorDialog::applyStyleSheet);
++ QObject::connect(buttonBox(), &QDialogButtonBox::accepted,
++ this, &StyleSheetPropertyEditorDialog::applyStyleSheet);
++
++ QDesignerPropertySheetExtension *sheet =
++ qt_extension<QDesignerPropertySheetExtension*>(m_fw->core()->extensionManager(), m_widget);
++ Q_ASSERT(sheet != nullptr);
++ const int index = sheet->indexOf(styleSheetProperty);
++ const PropertySheetStringValue value = qvariant_cast<PropertySheetStringValue>(sheet->property(index));
++ setText(value.value());
++ }
++
++ void StyleSheetPropertyEditorDialog::applyStyleSheet()
++ {
++ const PropertySheetStringValue value(text(), false);
++ m_fw->cursor()->setWidgetProperty(m_widget, styleSheetProperty, QVariant::fromValue(value));
++ }
++
++ } // namespace qdesigner_internal
++
++ QT_END_NAMESPACE