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