1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
3
4#include "stylesheeteditor_p.h"
5#include "csshighlighter_p.h"
6#include "iconselector_p.h"
7#include "qtgradientmanager.h"
8#include "qtgradientviewdialog.h"
9#include "qtgradientutils.h"
10#include "qdesigner_utils_p.h"
11
12#include <QtDesigner/abstractformwindow.h>
13#include <QtDesigner/abstractformwindowcursor.h>
14#include <QtDesigner/abstractformeditor.h>
15#include <QtDesigner/propertysheet.h>
16#include <QtDesigner/abstractintegration.h>
17#include <QtDesigner/abstractsettings.h>
18#include <QtDesigner/qextensionmanager.h>
19
20#include <texteditfindwidget.h>
21
22#include <QtWidgets/qcolordialog.h>
23#include <QtWidgets/qdialogbuttonbox.h>
24#include <QtWidgets/qfontdialog.h>
25#include <QtWidgets/qmenu.h>
26#include <QtWidgets/qpushbutton.h>
27#include <QtWidgets/qtoolbar.h>
28#include <QtWidgets/qboxlayout.h>
29
30#include <QtGui/qaction.h>
31#include <QtGui/qevent.h>
32#include <QtGui/qtextdocument.h>
33
34#include <private/qcssparser_p.h>
35
36QT_BEGIN_NAMESPACE
37
38using namespace Qt::StringLiterals;
39
40static const char styleSheetProperty[] = "styleSheet";
41static const char StyleSheetDialogC[] = "StyleSheetDialog";
42static const char seGeometry[] = "Geometry";
43
44namespace qdesigner_internal {
45
46StyleSheetEditor::StyleSheetEditor(QWidget *parent)
47 : QTextEdit(parent)
48{
49 enum : int { DarkThreshold = 200 }; // Observed 239 on KDE/Dark
50
51 setTabStopDistance(fontMetrics().horizontalAdvance(u' ') * 4);
52 setAcceptRichText(false);
53
54 const QColor textColor = palette().color(cr: QPalette::WindowText);
55 const bool darkMode = textColor.red() > DarkThreshold
56 && textColor.green() > DarkThreshold
57 && textColor.blue() > DarkThreshold;
58
59 CssHighlightColors colors;
60 colors.selector = darkMode ? QColor(Qt::red).lighter() : QColor(Qt::darkRed);
61 const QColor blue(Qt::blue);
62 colors.property = darkMode ? blue.lighter() : blue;
63 colors.pseudo1 = colors.pseudo2 = colors.value = textColor;
64 colors.quote = darkMode ? Qt::magenta : Qt::darkMagenta;
65 colors.comment = darkMode ? Qt::green : Qt::darkGreen;
66
67 new CssHighlighter(colors, document());
68}
69
70// --- StyleSheetEditorDialog
71StyleSheetEditorDialog::StyleSheetEditorDialog(QDesignerFormEditorInterface *core, QWidget *parent, Mode mode):
72 QDialog(parent),
73 m_buttonBox(new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::Help)),
74 m_editor(new StyleSheetEditor),
75 m_findWidget(new TextEditFindWidget),
76 m_validityLabel(new QLabel(tr(s: "Valid Style Sheet"))),
77 m_core(core),
78 m_addResourceAction(new QAction(tr(s: "Add Resource..."), this)),
79 m_addGradientAction(new QAction(tr(s: "Add Gradient..."), this)),
80 m_addColorAction(new QAction(tr(s: "Add Color..."), this)),
81 m_addFontAction(new QAction(tr(s: "Add Font..."), this))
82{
83 setWindowTitle(tr(s: "Edit Style Sheet"));
84
85 connect(sender: m_buttonBox, signal: &QDialogButtonBox::accepted, context: this, slot: &QDialog::accept);
86 connect(sender: m_buttonBox, signal: &QDialogButtonBox::rejected, context: this, slot: &QDialog::reject);
87 connect(sender: m_buttonBox, signal: &QDialogButtonBox::helpRequested,
88 context: this, slot: &StyleSheetEditorDialog::slotRequestHelp);
89 m_buttonBox->button(which: QDialogButtonBox::Help)->setShortcut(QKeySequence::HelpContents);
90
91 connect(sender: m_editor, signal: &QTextEdit::textChanged, context: this, slot: &StyleSheetEditorDialog::validateStyleSheet);
92 m_findWidget->setTextEdit(m_editor);
93
94 QToolBar *toolBar = new QToolBar;
95
96 QGridLayout *layout = new QGridLayout;
97 layout->addWidget(toolBar, row: 0, column: 0, rowSpan: 1, columnSpan: 2);
98 layout->addWidget(m_editor, row: 1, column: 0, rowSpan: 1, columnSpan: 2);
99 layout->addWidget(m_findWidget, row: 2, column: 0, rowSpan: 1, columnSpan: 2);
100 layout->addWidget(m_validityLabel, row: 3, column: 0, rowSpan: 1, columnSpan: 1);
101 layout->addWidget(m_buttonBox, row: 3, column: 1, rowSpan: 1, columnSpan: 1);
102 setLayout(layout);
103
104 m_editor->setContextMenuPolicy(Qt::CustomContextMenu);
105 connect(sender: m_editor, signal: &QWidget::customContextMenuRequested,
106 context: this, slot: &StyleSheetEditorDialog::slotContextMenuRequested);
107
108 connect(sender: m_addResourceAction, signal: &QAction::triggered,
109 context: this, slot: [this] { this->slotAddResource(property: QString()); });
110 connect(sender: m_addGradientAction, signal: &QAction::triggered,
111 context: this, slot: [this] { this->slotAddGradient(property: QString()); });
112 connect(sender: m_addColorAction, signal: &QAction::triggered,
113 context: this, slot: [this] { this->slotAddColor(property: QString()); });
114 connect(sender: m_addFontAction, signal: &QAction::triggered, context: this, slot: &StyleSheetEditorDialog::slotAddFont);
115
116 m_addResourceAction->setEnabled(mode == ModePerForm);
117
118 const char * const resourceProperties[] = {
119 "background-image",
120 "border-image",
121 "image",
122 nullptr
123 };
124
125 const char * const colorProperties[] = {
126 "color",
127 "background-color",
128 "alternate-background-color",
129 "border-color",
130 "border-top-color",
131 "border-right-color",
132 "border-bottom-color",
133 "border-left-color",
134 "gridline-color",
135 "selection-color",
136 "selection-background-color",
137 nullptr
138 };
139
140 QMenu *resourceActionMenu = new QMenu(this);
141 QMenu *gradientActionMenu = new QMenu(this);
142 QMenu *colorActionMenu = new QMenu(this);
143
144 for (int resourceProperty = 0; resourceProperties[resourceProperty]; ++resourceProperty) {
145 const QString resourcePropertyName = QLatin1StringView(resourceProperties[resourceProperty]);
146 resourceActionMenu->addAction(text: resourcePropertyName,
147 args: this, args: [this, resourcePropertyName] { this->slotAddResource(property: resourcePropertyName); });
148 }
149
150 for (int colorProperty = 0; colorProperties[colorProperty]; ++colorProperty) {
151 const QString colorPropertyName = QLatin1StringView(colorProperties[colorProperty]);
152 colorActionMenu->addAction(text: colorPropertyName,
153 args: this, args: [this, colorPropertyName] { this->slotAddColor(property: colorPropertyName); });
154 gradientActionMenu->addAction(text: colorPropertyName,
155 args: this, args: [this, colorPropertyName] { this->slotAddGradient(property: colorPropertyName); } );
156 }
157
158 m_addResourceAction->setMenu(resourceActionMenu);
159 m_addGradientAction->setMenu(gradientActionMenu);
160 m_addColorAction->setMenu(colorActionMenu);
161
162
163 toolBar->addAction(action: m_addResourceAction);
164 toolBar->addAction(action: m_addGradientAction);
165 toolBar->addAction(action: m_addColorAction);
166 toolBar->addAction(action: m_addFontAction);
167 m_findAction = m_findWidget->createFindAction(parent: toolBar);
168 toolBar->addAction(action: m_findAction);
169
170 m_editor->setFocus();
171
172 QDesignerSettingsInterface *settings = core->settingsManager();
173 settings->beginGroup(prefix: QLatin1StringView(StyleSheetDialogC));
174
175 if (settings->contains(key: QLatin1StringView(seGeometry)))
176 restoreGeometry(geometry: settings->value(key: QLatin1StringView(seGeometry)).toByteArray());
177
178 settings->endGroup();
179}
180
181StyleSheetEditorDialog::~StyleSheetEditorDialog()
182{
183 QDesignerSettingsInterface *settings = m_core->settingsManager();
184 settings->beginGroup(prefix: QLatin1StringView(StyleSheetDialogC));
185
186 settings->setValue(key: QLatin1StringView(seGeometry), value: saveGeometry());
187 settings->endGroup();
188}
189
190void StyleSheetEditorDialog::setOkButtonEnabled(bool v)
191{
192 m_buttonBox->button(which: QDialogButtonBox::Ok)->setEnabled(v);
193 if (QPushButton *applyButton = m_buttonBox->button(which: QDialogButtonBox::Apply))
194 applyButton->setEnabled(v);
195}
196
197void StyleSheetEditorDialog::slotContextMenuRequested(const QPoint &pos)
198{
199 QMenu *menu = m_editor->createStandardContextMenu();
200 menu->addSeparator();
201 menu->addAction(action: m_findAction);
202 menu->addSeparator();
203 menu->addAction(action: m_addResourceAction);
204 menu->addAction(action: m_addGradientAction);
205 menu->exec(pos: mapToGlobal(pos));
206 delete menu;
207}
208
209void StyleSheetEditorDialog::slotAddResource(const QString &property)
210{
211 const QString path = IconSelector::choosePixmapResource(core: m_core, resourceModel: m_core->resourceModel(), oldPath: QString(), parent: this);
212 if (!path.isEmpty())
213 insertCssProperty(name: property, value: "url("_L1 + path + u')');
214}
215
216void StyleSheetEditorDialog::slotAddGradient(const QString &property)
217{
218 bool ok;
219 const QGradient grad = QtGradientViewDialog::getGradient(ok: &ok, manager: m_core->gradientManager(), parent: this);
220 if (ok)
221 insertCssProperty(name: property, value: QtGradientUtils::styleSheetCode(gradient: grad));
222}
223
224void StyleSheetEditorDialog::slotAddColor(const QString &property)
225{
226 const QColor color = QColorDialog::getColor(initial: 0xffffffff, parent: this, title: QString(), options: QColorDialog::ShowAlphaChannel);
227 if (!color.isValid())
228 return;
229
230 QString colorStr;
231
232 if (color.alpha() == 255) {
233 colorStr = QString::asprintf(format: "rgb(%d, %d, %d)",
234 color.red(), color.green(), color.blue());
235 } else {
236 colorStr = QString::asprintf(format: "rgba(%d, %d, %d, %d)",
237 color.red(), color.green(), color.blue(),
238 color.alpha());
239 }
240
241 insertCssProperty(name: property, value: colorStr);
242}
243
244void StyleSheetEditorDialog::slotAddFont()
245{
246 bool ok;
247 QFont font = QFontDialog::getFont(ok: &ok, parent: this);
248 if (ok) {
249 QString fontStr;
250 if (font.weight() != QFont::Normal)
251 fontStr += QString::number(font.weight()) + u' ';
252
253 switch (font.style()) {
254 case QFont::StyleItalic:
255 fontStr += "italic "_L1;
256 break;
257 case QFont::StyleOblique:
258 fontStr += "oblique "_L1;
259 break;
260 default:
261 break;
262 }
263 fontStr += QString::number(font.pointSize());
264 fontStr += "pt \""_L1;
265 fontStr += font.family();
266 fontStr += u'"';
267
268 insertCssProperty(name: u"font"_s, value: fontStr);
269 QString decoration;
270 if (font.underline())
271 decoration += "underline"_L1;
272 if (font.strikeOut()) {
273 if (!decoration.isEmpty())
274 decoration += u' ';
275 decoration += "line-through"_L1;
276 }
277 insertCssProperty(name: u"text-decoration"_s, value: decoration);
278 }
279}
280
281void StyleSheetEditorDialog::insertCssProperty(const QString &name, const QString &value)
282{
283 if (!value.isEmpty()) {
284 QTextCursor cursor = m_editor->textCursor();
285 if (!name.isEmpty()) {
286 cursor.beginEditBlock();
287 cursor.removeSelectedText();
288 cursor.movePosition(op: QTextCursor::EndOfLine);
289
290 // Simple check to see if we're in a selector scope
291 const QTextDocument *doc = m_editor->document();
292 const QTextCursor closing = doc->find(subString: u"}"_s, cursor, options: QTextDocument::FindBackward);
293 const QTextCursor opening = doc->find(subString: u"{"_s, cursor, options: QTextDocument::FindBackward);
294 const bool inSelector = !opening.isNull() && (closing.isNull() ||
295 closing.position() < opening.position());
296 QString insertion;
297 if (m_editor->textCursor().block().length() != 1)
298 insertion += u'\n';
299 if (inSelector)
300 insertion += u'\t';
301 insertion += name;
302 insertion += ": "_L1;
303 insertion += value;
304 insertion += u';';
305 cursor.insertText(text: insertion);
306 cursor.endEditBlock();
307 } else {
308 cursor.insertText(text: value);
309 }
310 }
311}
312
313void StyleSheetEditorDialog::slotRequestHelp()
314{
315 m_core->integration()->emitHelpRequested(manual: u"qtwidgets"_s,
316 document: u"stylesheet-reference.html"_s);
317}
318
319// See QDialog::keyPressEvent()
320static inline bool isEnter(const QKeyEvent *e)
321{
322 const bool isEnter = e->key() == Qt::Key_Enter;
323 const bool isReturn = e->key() == Qt::Key_Return;
324 return (e->modifiers() == Qt::KeyboardModifiers() && (isEnter || isReturn))
325 || (e->modifiers().testFlag(flag: Qt::KeypadModifier) && isEnter);
326}
327
328void StyleSheetEditorDialog::keyPressEvent(QKeyEvent *e)
329{
330 // As long as the find widget is visible, suppress the default button
331 // behavior (close on Enter) of QDialog.
332 if (!(m_findWidget->isVisible() && isEnter(e)))
333 QDialog::keyPressEvent(e);
334}
335
336QDialogButtonBox * StyleSheetEditorDialog::buttonBox() const
337{
338 return m_buttonBox;
339}
340
341QString StyleSheetEditorDialog::text() const
342{
343 return m_editor->toPlainText();
344}
345
346void StyleSheetEditorDialog::setText(const QString &t)
347{
348 m_editor->setText(t);
349}
350
351bool StyleSheetEditorDialog::isStyleSheetValid(const QString &styleSheet)
352{
353 QCss::Parser parser(styleSheet);
354 QCss::StyleSheet sheet;
355 if (parser.parse(styleSheet: &sheet))
356 return true;
357 QCss::Parser parser2("* { "_L1 + styleSheet + '}'_L1);
358 return parser2.parse(styleSheet: &sheet);
359}
360
361void StyleSheetEditorDialog::validateStyleSheet()
362{
363 const bool valid = isStyleSheetValid(styleSheet: m_editor->toPlainText());
364 setOkButtonEnabled(valid);
365 if (valid) {
366 m_validityLabel->setText(tr(s: "Valid Style Sheet"));
367 m_validityLabel->setStyleSheet(u"color: green"_s);
368 } else {
369 m_validityLabel->setText(tr(s: "Invalid Style Sheet"));
370 m_validityLabel->setStyleSheet(u"color: red"_s);
371 }
372}
373
374// --- StyleSheetPropertyEditorDialog
375StyleSheetPropertyEditorDialog::StyleSheetPropertyEditorDialog(QWidget *parent,
376 QDesignerFormWindowInterface *fw,
377 QWidget *widget):
378 StyleSheetEditorDialog(fw->core(), parent),
379 m_fw(fw),
380 m_widget(widget)
381{
382 Q_ASSERT(m_fw != nullptr);
383
384 QPushButton *apply = buttonBox()->addButton(button: QDialogButtonBox::Apply);
385 QObject::connect(sender: apply, signal: &QAbstractButton::clicked,
386 context: this, slot: &StyleSheetPropertyEditorDialog::applyStyleSheet);
387 QObject::connect(sender: buttonBox(), signal: &QDialogButtonBox::accepted,
388 context: this, slot: &StyleSheetPropertyEditorDialog::applyStyleSheet);
389
390 QDesignerPropertySheetExtension *sheet =
391 qt_extension<QDesignerPropertySheetExtension*>(manager: m_fw->core()->extensionManager(), object: m_widget);
392 Q_ASSERT(sheet != nullptr);
393 const int index = sheet->indexOf(name: QLatin1StringView(styleSheetProperty));
394 const PropertySheetStringValue value = qvariant_cast<PropertySheetStringValue>(v: sheet->property(index));
395 setText(value.value());
396}
397
398void StyleSheetPropertyEditorDialog::applyStyleSheet()
399{
400 const PropertySheetStringValue value(text(), false);
401 m_fw->cursor()->setWidgetProperty(widget: m_widget, name: QLatin1StringView(styleSheetProperty), value: QVariant::fromValue(value));
402}
403
404} // namespace qdesigner_internal
405
406QT_END_NAMESPACE
407

source code of qttools/src/designer/src/lib/shared/stylesheeteditor.cpp