1
/***************************************************************************
2
* Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net> *
4
* This file is part of the FreeCAD CAx development system. *
6
* This library is free software; you can redistribute it and/or *
7
* modify it under the terms of the GNU Library General Public *
8
* License as published by the Free Software Foundation; either *
9
* version 2 of the License, or (at your option) any later version. *
11
* This library is distributed in the hope that it will be useful, *
12
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14
* GNU Library General Public License for more details. *
16
* You should have received a copy of the GNU Library General Public *
17
* License along with this library; see the file COPYING.LIB. If not, *
18
* write to the Free Software Foundation, Inc., 59 Temple Place, *
19
* Suite 330, Boston, MA 02111-1307, USA *
21
***************************************************************************/
24
#include "PreCompiled.h"
26
# include <QColorDialog>
28
# include <QDesktopServices>
29
# include <QDialogButtonBox>
33
# include <QMessageBox>
36
# include <QPlainTextEdit>
37
# include <QStylePainter>
43
#include <Base/Tools.h>
44
#include <Base/Exception.h>
45
#include <Base/Interpreter.h>
46
#include <App/ExpressionParser.h>
47
#include <App/Material.h>
51
#include "Application.h"
52
#include "BitmapFactory.h"
54
#include "DlgExpressionInput.h"
55
#include "PrefWidgets.h"
56
#include "QuantitySpinBox_p.h"
58
#include "ui_DlgTreeWidget.h"
65
* Constructs an empty command view with parent \a parent.
67
CommandIconView::CommandIconView ( QWidget * parent )
70
connect(this, &QListWidget::currentItemChanged,
71
this, &CommandIconView::onSelectionChanged);
75
* Destroys the icon view and deletes all items.
77
CommandIconView::~CommandIconView () = default;
80
* Stores the name of the selected commands for drag and drop.
82
void CommandIconView::startDrag (Qt::DropActions supportedActions)
84
Q_UNUSED(supportedActions);
85
QList<QListWidgetItem*> items = selectedItems();
87
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
90
dataStream << items.count();
91
for (QList<QListWidgetItem*>::Iterator it = items.begin(); it != items.end(); ++it) {
92
if (it == items.begin())
93
pixmap = ((*it)->data(Qt::UserRole)).value<QPixmap>();
94
dataStream << (*it)->text();
97
auto mimeData = new QMimeData;
98
mimeData->setData(QString::fromLatin1("text/x-action-items"), itemData);
100
auto drag = new QDrag(this);
101
drag->setMimeData(mimeData);
102
drag->setHotSpot(QPoint(pixmap.width()/2, pixmap.height()/2));
103
drag->setPixmap(pixmap);
104
drag->exec(Qt::MoveAction);
108
* This slot is called when a new item becomes current. \a item is the new current item
109
* (or 0 if no item is now current). This slot emits the emitSelectionChanged()
110
* signal for its part.
112
void CommandIconView::onSelectionChanged(QListWidgetItem * item, QListWidgetItem *)
115
Q_EMIT emitSelectionChanged(item->toolTip());
118
// ------------------------------------------------------------------------------
120
/* TRANSLATOR Gui::ActionSelector */
122
ActionSelector::ActionSelector(QWidget* parent)
125
addButton = new QPushButton(this);
126
addButton->setObjectName(QLatin1String("addButton"));
127
addButton->setMinimumSize(QSize(30, 30));
128
addButton->setIcon(BitmapFactory().pixmap("button_right"));
129
gridLayout = new QGridLayout(this);
130
gridLayout->addWidget(addButton, 1, 1, 1, 1);
132
spacerItem = new QSpacerItem(33, 57, QSizePolicy::Minimum, QSizePolicy::Expanding);
133
gridLayout->addItem(spacerItem, 5, 1, 1, 1);
134
spacerItem1 = new QSpacerItem(33, 58, QSizePolicy::Minimum, QSizePolicy::Expanding);
135
gridLayout->addItem(spacerItem1, 0, 1, 1, 1);
137
removeButton = new QPushButton(this);
138
removeButton->setObjectName(QLatin1String("removeButton"));
139
removeButton->setMinimumSize(QSize(30, 30));
140
removeButton->setIcon(BitmapFactory().pixmap("button_left"));
141
removeButton->setAutoDefault(true);
142
removeButton->setDefault(false);
144
gridLayout->addWidget(removeButton, 2, 1, 1, 1);
146
upButton = new QPushButton(this);
147
upButton->setObjectName(QLatin1String("upButton"));
148
upButton->setMinimumSize(QSize(30, 30));
149
upButton->setIcon(BitmapFactory().pixmap("button_up"));
151
gridLayout->addWidget(upButton, 3, 1, 1, 1);
153
downButton = new QPushButton(this);
154
downButton->setObjectName(QLatin1String("downButton"));
155
downButton->setMinimumSize(QSize(30, 30));
156
downButton->setIcon(BitmapFactory().pixmap("button_down"));
157
downButton->setAutoDefault(true);
159
gridLayout->addWidget(downButton, 4, 1, 1, 1);
161
vboxLayout = new QVBoxLayout();
162
vboxLayout->setContentsMargins(0, 0, 0, 0);
163
labelAvailable = new QLabel(this);
164
vboxLayout->addWidget(labelAvailable);
166
availableWidget = new QTreeWidget(this);
167
availableWidget->setObjectName(QLatin1String("availableTreeWidget"));
168
availableWidget->setRootIsDecorated(false);
169
availableWidget->setHeaderLabels(QStringList() << QString());
170
availableWidget->header()->hide();
171
vboxLayout->addWidget(availableWidget);
173
gridLayout->addLayout(vboxLayout, 0, 0, 6, 1);
175
vboxLayout1 = new QVBoxLayout();
176
vboxLayout1->setContentsMargins(0, 0, 0, 0);
177
labelSelected = new QLabel(this);
178
vboxLayout1->addWidget(labelSelected);
180
selectedWidget = new QTreeWidget(this);
181
selectedWidget->setObjectName(QLatin1String("selectedTreeWidget"));
182
selectedWidget->setRootIsDecorated(false);
183
selectedWidget->setHeaderLabels(QStringList() << QString());
184
selectedWidget->header()->hide();
185
vboxLayout1->addWidget(selectedWidget);
187
gridLayout->addLayout(vboxLayout1, 0, 2, 6, 1);
189
addButton->setText(QString());
190
removeButton->setText(QString());
191
upButton->setText(QString());
192
downButton->setText(QString());
194
connect(addButton, &QPushButton::clicked, this, &ActionSelector::onAddButtonClicked);
195
connect(removeButton, &QPushButton::clicked, this, &ActionSelector::onRemoveButtonClicked);
196
connect(upButton, &QPushButton::clicked, this, &ActionSelector::onUpButtonClicked);
197
connect(downButton, &QPushButton::clicked, this, &ActionSelector::onDownButtonClicked);
199
connect(availableWidget, &QTreeWidget::itemDoubleClicked, this, &ActionSelector::onItemDoubleClicked);
200
connect(availableWidget, &QTreeWidget::currentItemChanged, this, &ActionSelector::onCurrentItemChanged);
201
connect(selectedWidget, &QTreeWidget::itemDoubleClicked, this, &ActionSelector::onItemDoubleClicked);
202
connect(selectedWidget, &QTreeWidget::currentItemChanged, this, &ActionSelector::onCurrentItemChanged);
208
ActionSelector::~ActionSelector() = default;
210
void ActionSelector::setSelectedLabel(const QString& label)
212
labelSelected->setText(label);
215
QString ActionSelector::selectedLabel() const
217
return labelSelected->text();
220
void ActionSelector::setAvailableLabel(const QString& label)
222
labelAvailable->setText(label);
225
QString ActionSelector::availableLabel() const
227
return labelAvailable->text();
230
void ActionSelector::retranslateUi()
232
labelAvailable->setText(QApplication::translate("Gui::ActionSelector", "Available:"));
233
labelSelected->setText(QApplication::translate("Gui::ActionSelector", "Selected:"));
234
addButton->setToolTip(QApplication::translate("Gui::ActionSelector", "Add"));
235
removeButton->setToolTip(QApplication::translate("Gui::ActionSelector", "Remove"));
236
upButton->setToolTip(QApplication::translate("Gui::ActionSelector", "Move up"));
237
downButton->setToolTip(QApplication::translate("Gui::ActionSelector", "Move down"));
240
void ActionSelector::changeEvent(QEvent* event)
242
if (event->type() == QEvent::LanguageChange) {
245
QWidget::changeEvent(event);
248
void ActionSelector::keyPressEvent(QKeyEvent* event)
250
if ((event->modifiers() & Qt::ControlModifier)) {
251
switch (event->key())
254
onAddButtonClicked();
257
onRemoveButtonClicked();
263
onDownButtonClicked();
272
void ActionSelector::setButtonsEnabled()
274
addButton->setEnabled(availableWidget->indexOfTopLevelItem(availableWidget->currentItem()) > -1);
275
removeButton->setEnabled(selectedWidget->indexOfTopLevelItem(selectedWidget->currentItem()) > -1);
276
upButton->setEnabled(selectedWidget->indexOfTopLevelItem(selectedWidget->currentItem()) > 0);
277
downButton->setEnabled(selectedWidget->indexOfTopLevelItem(selectedWidget->currentItem()) > -1 &&
278
selectedWidget->indexOfTopLevelItem(selectedWidget->currentItem()) < selectedWidget->topLevelItemCount() - 1);
281
void ActionSelector::onCurrentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)
286
void ActionSelector::onItemDoubleClicked(QTreeWidgetItem * item, int column)
289
QTreeWidget* treeWidget = item->treeWidget();
290
if (treeWidget == availableWidget) {
291
int index = availableWidget->indexOfTopLevelItem(item);
292
item = availableWidget->takeTopLevelItem(index);
293
availableWidget->setCurrentItem(nullptr);
294
selectedWidget->addTopLevelItem(item);
295
selectedWidget->setCurrentItem(item);
297
else if (treeWidget == selectedWidget) {
298
int index = selectedWidget->indexOfTopLevelItem(item);
299
item = selectedWidget->takeTopLevelItem(index);
300
selectedWidget->setCurrentItem(nullptr);
301
availableWidget->addTopLevelItem(item);
302
availableWidget->setCurrentItem(item);
306
void ActionSelector::onAddButtonClicked()
308
QTreeWidgetItem* item = availableWidget->currentItem();
310
int index = availableWidget->indexOfTopLevelItem(item);
311
item = availableWidget->takeTopLevelItem(index);
312
availableWidget->setCurrentItem(nullptr);
313
selectedWidget->addTopLevelItem(item);
314
selectedWidget->setCurrentItem(item);
318
void ActionSelector::onRemoveButtonClicked()
320
QTreeWidgetItem* item = selectedWidget->currentItem();
322
int index = selectedWidget->indexOfTopLevelItem(item);
323
item = selectedWidget->takeTopLevelItem(index);
324
selectedWidget->setCurrentItem(nullptr);
325
availableWidget->addTopLevelItem(item);
326
availableWidget->setCurrentItem(item);
330
void ActionSelector::onUpButtonClicked()
332
QTreeWidgetItem* item = selectedWidget->currentItem();
333
if (item && item->isSelected()) {
334
int index = selectedWidget->indexOfTopLevelItem(item);
336
selectedWidget->takeTopLevelItem(index);
337
selectedWidget->insertTopLevelItem(index-1, item);
338
selectedWidget->setCurrentItem(item);
343
void ActionSelector::onDownButtonClicked()
345
QTreeWidgetItem* item = selectedWidget->currentItem();
346
if (item && item->isSelected()) {
347
int index = selectedWidget->indexOfTopLevelItem(item);
348
if (index < selectedWidget->topLevelItemCount()-1) {
349
selectedWidget->takeTopLevelItem(index);
350
selectedWidget->insertTopLevelItem(index+1, item);
351
selectedWidget->setCurrentItem(item);
356
// ------------------------------------------------------------------------------
358
/* TRANSLATOR Gui::AccelLineEdit */
361
* Constructs a line edit with no text.
362
* The \a parent argument is sent to the QLineEdit constructor.
364
AccelLineEdit::AccelLineEdit ( QWidget * parent )
367
setPlaceholderText(tr("Press a keyboard shortcut"));
368
setClearButtonEnabled(true);
372
bool AccelLineEdit::isNone() const
374
return text().isEmpty();
378
* Checks which keys are pressed and show it as text.
380
void AccelLineEdit::keyPressEvent (QKeyEvent * e)
383
QLineEdit::keyPressEvent(e);
387
QString txtLine = text();
390
Qt::KeyboardModifiers state = e->modifiers();
392
// Backspace clears the shortcut if text is present, else sets Backspace as shortcut.
393
// If a modifier is pressed without any other key, return.
394
// AltGr is not a modifier but doesn't have a QString representation.
396
case Qt::Key_Backspace:
398
if (state == Qt::NoModifier) {
401
QKeySequence ks(key);
402
setText(ks.toString(QKeySequence::NativeText));
408
case Qt::Key_Control:
418
if (txtLine.isEmpty()) {
419
// Text maybe cleared by QLineEdit's built in clear button
422
// 4 keys are allowed for QShortcut
423
switch (keyPressedCount) {
432
txtLine += QString::fromLatin1(",");
437
// Handles modifiers applying a mask.
438
if ((state & Qt::ControlModifier) == Qt::ControlModifier) {
439
QKeySequence ks(Qt::CTRL);
440
txtLine += ks.toString(QKeySequence::NativeText);
442
if ((state & Qt::AltModifier) == Qt::AltModifier) {
443
QKeySequence ks(Qt::ALT);
444
txtLine += ks.toString(QKeySequence::NativeText);
446
if ((state & Qt::ShiftModifier) == Qt::ShiftModifier) {
447
QKeySequence ks(Qt::SHIFT);
448
txtLine += ks.toString(QKeySequence::NativeText);
450
if ((state & Qt::MetaModifier) == Qt::MetaModifier) {
451
QKeySequence ks(Qt::META);
452
txtLine += ks.toString(QKeySequence::NativeText);
455
// Handles normal keys
456
QKeySequence ks(key);
457
txtLine += ks.toString(QKeySequence::NativeText);
463
// ------------------------------------------------------------------------------
465
/* TRANSLATOR Gui::ModifierLineEdit */
468
* Constructs a line edit with no text.
469
* The \a parent argument is sent to the QLineEdit constructor.
471
ModifierLineEdit::ModifierLineEdit (QWidget * parent )
474
setPlaceholderText(tr("Press modifier keys"));
478
* Checks which modifiers are pressed and show it as text.
480
void ModifierLineEdit::keyPressEvent (QKeyEvent * e)
483
Qt::KeyboardModifiers state = e->modifiers();
486
case Qt::Key_Backspace:
490
case Qt::Key_Control:
502
// Handles modifiers applying a mask.
503
if ((state & Qt::ControlModifier) == Qt::ControlModifier) {
504
QKeySequence ks(Qt::CTRL);
505
txtLine += ks.toString(QKeySequence::NativeText);
507
if ((state & Qt::AltModifier) == Qt::AltModifier) {
508
QKeySequence ks(Qt::ALT);
509
txtLine += ks.toString(QKeySequence::NativeText);
511
if ((state & Qt::ShiftModifier) == Qt::ShiftModifier) {
512
QKeySequence ks(Qt::SHIFT);
513
txtLine += ks.toString(QKeySequence::NativeText);
515
if ((state & Qt::MetaModifier) == Qt::MetaModifier) {
516
QKeySequence ks(Qt::META);
517
txtLine += ks.toString(QKeySequence::NativeText);
523
// ------------------------------------------------------------------------------
525
ClearLineEdit::ClearLineEdit (QWidget * parent)
528
clearAction = this->addAction(QIcon(QString::fromLatin1(":/icons/edit-cleartext.svg")),
529
QLineEdit::TrailingPosition);
530
connect(clearAction, &QAction::triggered, this, &ClearLineEdit::clear);
531
connect(this, &QLineEdit::textChanged, this, &ClearLineEdit::updateClearButton);
534
void ClearLineEdit::resizeEvent(QResizeEvent *e)
536
QLineEdit::resizeEvent(e);
539
void ClearLineEdit::updateClearButton(const QString& text)
541
clearAction->setVisible(!text.isEmpty());
544
// ------------------------------------------------------------------------------
546
/* TRANSLATOR Gui::CheckListDialog */
549
* Constructs a CheckListDialog which is a child of 'parent', with the
550
* name 'name' and widget flags set to 'f'
552
* The dialog will by default be modeless, unless you set 'modal' to
553
* true to construct a modal dialog.
555
CheckListDialog::CheckListDialog( QWidget* parent, Qt::WindowFlags fl )
556
: QDialog( parent, fl )
557
, ui(new Ui_DlgTreeWidget)
563
* Destroys the object and frees any allocated resources
565
CheckListDialog::~CheckListDialog() = default;
568
* Sets the items to the dialog's list view. By default all items are checkable..
570
void CheckListDialog::setCheckableItems( const QStringList& items )
572
for (const auto & it : items) {
573
auto item = new QTreeWidgetItem(ui->treeWidget);
574
item->setText(0, it);
575
item->setCheckState(0, Qt::Unchecked);
580
* Sets the items to the dialog's list view. If the boolean type of a CheckListItem
581
* is set to false the item is not checkable any more.
583
void CheckListDialog::setCheckableItems( const QList<CheckListItem>& items )
585
for (const auto & it : items) {
586
auto item = new QTreeWidgetItem(ui->treeWidget);
587
item->setText(0, it.first);
588
item->setCheckState(0, ( it.second ? Qt::Checked : Qt::Unchecked));
593
* Returns a list of the check items.
595
QStringList CheckListDialog::getCheckedItems() const
601
* Collects all checked items to be able to return them by call \ref getCheckedItems().
603
void CheckListDialog::accept ()
605
QTreeWidgetItemIterator it(ui->treeWidget, QTreeWidgetItemIterator::Checked);
607
checked.push_back((*it)->text(0));
614
// ------------------------------------------------------------------------------
620
QPointer<QColorDialog> cd;
621
bool allowChange{true};
622
bool autoChange{false};
623
bool drawFrame{true};
624
bool allowTransparency{false};
631
* Constructs a colored button called \a name with parent \a parent.
633
ColorButton::ColorButton(QWidget* parent)
634
: QPushButton(parent)
636
d = new ColorButtonP();
637
d->col = palette().color(QPalette::Active,QPalette::Midlight);
638
connect(this, &ColorButton::clicked, this, &ColorButton::onChooseColor);
640
int e = style()->pixelMetric(QStyle::PM_ButtonIconSize);
641
setIconSize(QSize(2*e, e));
645
* Destroys the button.
647
ColorButton::~ColorButton()
653
* Sets the color \a c to the button.
655
void ColorButton::setColor(const QColor& c)
663
* Returns the current color of the button.
665
QColor ColorButton::color() const
671
* Sets the packed color \a c to the button.
673
void ColorButton::setPackedColor(uint32_t c)
676
color.setPackedValue(c);
677
d->col.setRedF(color.r);
678
d->col.setGreenF(color.g);
679
d->col.setBlueF(color.b);
680
d->col.setAlphaF(color.a);
686
* Returns the current packed color of the button.
688
uint32_t ColorButton::packedColor() const
690
App::Color color(d->col.redF(), d->col.greenF(), d->col.blueF(), d->col.alphaF());
691
return color.getPackedValue();
694
void ColorButton::setAllowChangeColor(bool ok)
699
bool ColorButton::allowChangeColor() const
701
return d->allowChange;
704
void ColorButton::setDrawFrame(bool ok)
709
bool ColorButton::drawFrame() const
714
void Gui::ColorButton::setAllowTransparency(bool allow)
716
d->allowTransparency = allow;
718
d->cd->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel, allow);
721
bool Gui::ColorButton::allowTransparency() const
724
return d->cd->testOption(QColorDialog::ColorDialogOption::ShowAlphaChannel);
726
return d->allowTransparency;
729
void ColorButton::setModal(bool b)
734
bool ColorButton::isModal() const
739
void ColorButton::setAutoChangeColor(bool on)
744
bool ColorButton::autoChangeColor() const
746
return d->autoChange;
750
* Draws the button label.
752
void ColorButton::paintEvent (QPaintEvent * e)
755
QSize isize = iconSize();
757
pix.fill(palette().button().color());
762
int h = pix.height();
763
p.setPen(QPen(Qt::gray));
766
p.drawRect(2, 2, w - 5, h - 5);
769
p.fillRect(0, 0, w, h, QBrush(d->col));
776
QPushButton::paintEvent(e);
779
void ColorButton::showModeless()
781
if (d->cd.isNull()) {
784
QColorDialog* dlg = new QColorDialog(d->col, this);
785
dlg->setAttribute(Qt::WA_DeleteOnClose);
786
if (DialogOptions::dontUseNativeColorDialog())
787
dlg->setOptions(QColorDialog::DontUseNativeDialog);
788
dlg->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel, d->allowTransparency);
789
dlg->setCurrentColor(d->old);
790
connect(dlg, &QColorDialog::rejected, this, &ColorButton::onRejected);
791
connect(dlg, &QColorDialog::currentColorChanged, this, &ColorButton::onColorChosen);
797
void ColorButton::showModal()
799
QColor currentColor = d->col;
800
QColorDialog* dlg = new QColorDialog(d->col, this);
801
dlg->setAttribute(Qt::WA_DeleteOnClose);
802
if (DialogOptions::dontUseNativeColorDialog())
803
dlg->setOptions(QColorDialog::DontUseNativeDialog);
804
dlg->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel, d->allowTransparency);
807
connect(dlg, &QColorDialog::currentColorChanged, this, &ColorButton::onColorChosen);
810
dlg->setCurrentColor(currentColor);
813
connect(dlg, &QColorDialog::finished, this, [&](int result) {
814
if (result == QDialog::Accepted) {
815
QColor c = dlg->selectedColor();
821
else if (d->autoChange) {
822
setColor(currentColor);
831
* Opens a QColorDialog to set a new color.
833
void ColorButton::onChooseColor()
845
void ColorButton::onColorChosen(const QColor& c)
851
void ColorButton::onRejected()
857
// ------------------------------------------------------------------------------
859
UrlLabel::UrlLabel(QWidget* parent, Qt::WindowFlags f)
861
, _url (QStringLiteral("http://localhost"))
862
, _launchExternal(true)
864
setToolTip(this->_url);
865
setCursor(Qt::PointingHandCursor);
866
if (qApp->styleSheet().isEmpty())
867
setStyleSheet(QStringLiteral("Gui--UrlLabel {color: #0000FF;text-decoration: underline;}"));
870
UrlLabel::~UrlLabel() = default;
872
void Gui::UrlLabel::setLaunchExternal(bool l)
877
void UrlLabel::mouseReleaseEvent(QMouseEvent*)
880
QDesktopServices::openUrl(this->_url);
882
// Someone else will deal with it...
883
Q_EMIT linkClicked(_url);
886
QString UrlLabel::url() const
891
bool Gui::UrlLabel::launchExternal() const
893
return _launchExternal;
896
void UrlLabel::setUrl(const QString& u)
899
setToolTip(this->_url);
902
// --------------------------------------------------------------------
904
StatefulLabel::StatefulLabel(QWidget* parent)
906
, _overridePreference(false)
908
// Always attach to the parameter group that stores the main FreeCAD stylesheet
909
_stylesheetGroup = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General");
910
_stylesheetGroup->Attach(this);
913
StatefulLabel::~StatefulLabel()
915
if (_parameterGroup.isValid())
916
_parameterGroup->Detach(this);
917
_stylesheetGroup->Detach(this);
920
void StatefulLabel::setDefaultStyle(const QString& defaultStyle)
922
_defaultStyle = defaultStyle;
925
void StatefulLabel::setParameterGroup(const std::string& groupName)
927
if (_parameterGroup.isValid())
928
_parameterGroup->Detach(this);
930
// Attach to the Parametergroup so we know when it changes
931
_parameterGroup = App::GetApplication().GetParameterGroupByPath(groupName.c_str());
932
if (_parameterGroup.isValid())
933
_parameterGroup->Attach(this);
936
void StatefulLabel::registerState(const QString& state, const QString& styleCSS,
937
const std::string& preferenceName)
939
_availableStates[state] = { styleCSS, preferenceName };
942
void StatefulLabel::registerState(const QString& state, const QColor& color,
943
const std::string& preferenceName)
947
css = QString::fromUtf8("Gui--StatefulLabel{ color : rgba(%1,%2,%3,%4) ;}").arg(color.red()).arg(color.green()).arg(color.blue()).arg(color.alpha());
948
_availableStates[state] = { css, preferenceName };
951
void StatefulLabel::registerState(const QString& state, const QColor& fg, const QColor& bg,
952
const std::string& preferenceName)
954
QString colorEntries;
956
colorEntries.append(QString::fromUtf8("color : rgba(%1,%2,%3,%4);").arg(fg.red()).arg(fg.green()).arg(fg.blue()).arg(fg.alpha()));
958
colorEntries.append(QString::fromUtf8("background-color : rgba(%1,%2,%3,%4);").arg(bg.red()).arg(bg.green()).arg(bg.blue()).arg(bg.alpha()));
959
QString css = QString::fromUtf8("Gui--StatefulLabel{ %1 }").arg(colorEntries);
960
_availableStates[state] = { css, preferenceName };
963
/** Observes the parameter group and clears the cache if it changes */
964
void StatefulLabel::OnChange(Base::Subject<const char*>& rCaller, const char* rcReason)
967
auto changedItem = std::string(rcReason);
968
if (changedItem == "StyleSheet") {
972
for (const auto& state : _availableStates) {
973
if (state.second.preferenceString == changedItem) {
974
_styleCache.erase(_styleCache.find(state.first));
980
void StatefulLabel::setOverridePreference(bool overridePreference)
982
_overridePreference = overridePreference;
985
void StatefulLabel::setState(QString state)
988
this->ensurePolished();
990
// If the stylesheet insists, ignore all other logic and let it do its thing. This
991
// property is *only* set by the stylesheet.
992
if (_overridePreference)
995
// Check the cache first:
996
if (auto style = _styleCache.find(_state); style != _styleCache.end()) {
997
auto test = style->second.toStdString();
998
this->setStyleSheet(style->second);
1002
if (auto entry = _availableStates.find(state); entry != _availableStates.end()) {
1003
// Order of precedence: first, check if the user has set this in their preferences:
1004
if (!entry->second.preferenceString.empty()) {
1005
// First, try to see if it's just stored a color (as an unsigned int):
1006
auto availableColorPrefs = _parameterGroup->GetUnsignedMap();
1007
std::string lookingForGroup = entry->second.preferenceString;
1008
for (const auto &unsignedEntry : availableColorPrefs) {
1009
std::string foundGroup = unsignedEntry.first;
1010
if (unsignedEntry.first == entry->second.preferenceString) {
1011
// Convert the stored Uint into usable color data:
1012
unsigned int col = unsignedEntry.second;
1013
QColor qcolor(App::Color::fromPackedRGB<QColor>(col));
1014
this->setStyleSheet(QString::fromUtf8("Gui--StatefulLabel{ color : rgba(%1,%2,%3,%4) ;}").arg(qcolor.red()).arg(qcolor.green()).arg(qcolor.blue()).arg(qcolor.alpha()));
1015
_styleCache[state] = this->styleSheet();
1020
// If not, try to see if there's an entire style string set as ASCII:
1021
auto availableStringPrefs = _parameterGroup->GetASCIIMap();
1022
for (const auto& stringEntry : availableStringPrefs) {
1023
if (stringEntry.first == entry->second.preferenceString) {
1024
QString css = QString::fromUtf8("Gui--StatefulLabel{ %1 }").arg(QString::fromStdString(stringEntry.second));
1025
this->setStyleSheet(css);
1026
_styleCache[state] = this->styleSheet();
1032
// If there is no preferences entry for this label, allow the stylesheet to set it, and only set to the default
1033
// formatting if there is no stylesheet entry
1034
if (qApp->styleSheet().isEmpty()) {
1035
this->setStyleSheet(entry->second.defaultCSS);
1036
_styleCache[state] = this->styleSheet();
1039
// else the stylesheet sets our appearance: make sure it recalculates the appearance:
1040
this->setStyleSheet(QString());
1041
this->setStyle(qApp->style());
1042
this->style()->unpolish(this);
1043
this->style()->polish(this);
1046
if (styleSheet().isEmpty()) {
1047
this->setStyleSheet(_defaultStyle);
1048
_styleCache[state] = this->styleSheet();
1053
// --------------------------------------------------------------------
1055
/* TRANSLATOR Gui::LabelButton */
1058
* Constructs a file chooser called \a name with the parent \a parent.
1060
LabelButton::LabelButton (QWidget * parent)
1063
auto layout = new QHBoxLayout(this);
1064
layout->setContentsMargins(0, 0, 0, 0);
1065
layout->setSpacing(1);
1067
label = new QLabel(this);
1068
label->setAutoFillBackground(true);
1069
layout->addWidget(label);
1071
button = new QPushButton(QLatin1String("..."), this);
1072
#if defined (Q_OS_MACOS)
1073
button->setAttribute(Qt::WA_LayoutUsesWidgetRect); // layout size from QMacStyle was not correct
1075
layout->addWidget(button);
1077
connect(button, &QPushButton::clicked, this, &LabelButton::browse);
1078
connect(button, &QPushButton::clicked, this, &LabelButton::buttonClicked);
1081
LabelButton::~LabelButton() = default;
1083
void LabelButton::resizeEvent(QResizeEvent* e)
1085
button->setFixedWidth(e->size().height());
1086
button->setFixedHeight(e->size().height());
1089
QLabel *LabelButton::getLabel() const
1094
QPushButton *LabelButton::getButton() const
1099
QVariant LabelButton::value() const
1104
void LabelButton::setValue(const QVariant& val)
1108
Q_EMIT valueChanged(_val);
1111
void LabelButton::showValue(const QVariant& data)
1113
label->setText(data.toString());
1116
void LabelButton::browse()
1120
// ----------------------------------------------------------------------
1122
ToolTip* ToolTip::inst = nullptr;
1124
ToolTip* ToolTip::instance()
1127
inst = new ToolTip();
1131
ToolTip::ToolTip() : installed(false), hidden(true)
1135
ToolTip::~ToolTip() = default;
1137
void ToolTip::installEventFilter()
1139
if (this->installed)
1141
qApp->installEventFilter(this);
1142
this->installed = true;
1145
void ToolTip::removeEventFilter()
1147
if (!this->installed)
1149
qApp->removeEventFilter(this);
1150
this->installed = false;
1153
void ToolTip::showText(const QPoint & pos, const QString & text, QWidget * w)
1155
ToolTip* tip = instance();
1156
if (!text.isEmpty()) {
1157
// install this object to filter timer events for the tooltip label
1158
tip->installEventFilter();
1162
// show text with a short delay
1163
tip->tooltipTimer.start(80, tip);
1164
tip->displayTime.start();
1171
void ToolTip::hideText()
1173
instance()->tooltipTimer.stop();
1174
instance()->hidden = true;
1175
QToolTip::hideText();
1178
void ToolTip::timerEvent(QTimerEvent *e)
1180
if (e->timerId() == tooltipTimer.timerId()) {
1181
QToolTip::showText(pos, text, w);
1182
tooltipTimer.stop();
1183
displayTime.restart();
1187
bool ToolTip::eventFilter(QObject* o, QEvent*e)
1189
if (!o->isWidgetType())
1192
case QEvent::MouseButtonPress:
1195
case QEvent::KeyPress:
1196
if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape)
1205
if (auto label = qobject_cast<QLabel*>(o)) {
1206
if (label->objectName() == QStringLiteral("qtooltip_label")) {
1207
// This is a trick to circumvent that the tooltip gets hidden immediately
1208
// after it gets visible. We just filter out all timer events to keep the
1211
// Ignore the timer events to prevent from being closed
1212
if (e->type() == QEvent::Show) {
1213
this->hidden = false;
1215
else if (e->type() == QEvent::Hide) {
1216
// removeEventFilter();
1217
this->hidden = true;
1219
else if (e->type() == QEvent::Timer &&
1220
!this->hidden && displayTime.elapsed() < 5000) {
1232
// ----------------------------------------------------------------------
1234
StatusWidget::StatusWidget(QWidget* parent)
1235
: QDialog(parent, Qt::Dialog | Qt::FramelessWindowHint)
1237
label = new QLabel(this);
1238
label->setAlignment(Qt::AlignCenter);
1240
auto gridLayout = new QGridLayout(this);
1241
gridLayout->setSpacing(6);
1242
gridLayout->setContentsMargins(9, 9, 9, 9);
1243
gridLayout->addWidget(label, 0, 0, 1, 1);
1246
StatusWidget::~StatusWidget() = default;
1248
void StatusWidget::setStatusText(const QString& s)
1253
void StatusWidget::showText(int ms)
1258
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1260
loop.exec(QEventLoop::ExcludeUserInputEvents);
1264
QSize StatusWidget::sizeHint () const
1269
void StatusWidget::showEvent(QShowEvent* event)
1271
QDialog::showEvent(event);
1274
void StatusWidget::hideEvent(QHideEvent*)
1278
// --------------------------------------------------------------------
1280
class LineNumberArea : public QWidget
1283
explicit LineNumberArea(PropertyListEditor *editor) : QWidget(editor) {
1284
codeEditor = editor;
1287
QSize sizeHint() const override {
1288
return {codeEditor->lineNumberAreaWidth(), 0};
1292
void paintEvent(QPaintEvent *event) override {
1293
codeEditor->lineNumberAreaPaintEvent(event);
1297
PropertyListEditor *codeEditor;
1300
PropertyListEditor::PropertyListEditor(QWidget *parent) : QPlainTextEdit(parent)
1302
lineNumberArea = new LineNumberArea(this);
1304
connect(this, &QPlainTextEdit::blockCountChanged,
1305
this, &PropertyListEditor::updateLineNumberAreaWidth);
1306
connect(this, &QPlainTextEdit::updateRequest,
1307
this, &PropertyListEditor::updateLineNumberArea);
1308
connect(this, &QPlainTextEdit::cursorPositionChanged,
1309
this, &PropertyListEditor::highlightCurrentLine);
1311
updateLineNumberAreaWidth(0);
1312
highlightCurrentLine();
1315
int PropertyListEditor::lineNumberAreaWidth()
1318
int max = qMax(1, blockCount());
1324
int space = 3 + QtTools::horizontalAdvance(fontMetrics(), QLatin1Char('9')) * digits;
1329
void PropertyListEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
1331
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
1334
void PropertyListEditor::updateLineNumberArea(const QRect &rect, int dy)
1337
lineNumberArea->scroll(0, dy);
1339
lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
1341
if (rect.contains(viewport()->rect()))
1342
updateLineNumberAreaWidth(0);
1345
void PropertyListEditor::resizeEvent(QResizeEvent *e)
1347
QPlainTextEdit::resizeEvent(e);
1349
QRect cr = contentsRect();
1350
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
1353
void PropertyListEditor::highlightCurrentLine()
1355
QList<QTextEdit::ExtraSelection> extraSelections;
1356
if (!isReadOnly()) {
1357
QTextEdit::ExtraSelection selection;
1359
QColor lineColor = QColor(Qt::yellow).lighter(160);
1361
selection.format.setBackground(lineColor);
1362
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
1363
selection.cursor = textCursor();
1364
selection.cursor.clearSelection();
1365
extraSelections.append(selection);
1368
setExtraSelections(extraSelections);
1371
void PropertyListEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
1373
QPainter painter(lineNumberArea);
1374
painter.fillRect(event->rect(), Qt::lightGray);
1376
QTextBlock block = firstVisibleBlock();
1377
int blockNumber = block.blockNumber();
1378
int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
1379
int bottom = top + (int) blockBoundingRect(block).height();
1381
while (block.isValid() && top <= event->rect().bottom()) {
1382
if (block.isVisible() && bottom >= event->rect().top()) {
1383
QString number = QString::number(blockNumber + 1);
1384
painter.setPen(Qt::black);
1385
painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
1386
Qt::AlignRight, number);
1389
block = block.next();
1391
bottom = top + (int) blockBoundingRect(block).height();
1396
class PropertyListDialog : public QDialog
1401
PropertyListDialog(int type, QWidget* parent) : QDialog(parent),type(type)
1405
void accept() override
1407
auto edit = this->findChild<PropertyListEditor*>();
1410
QString inputText = edit->toPlainText();
1411
if (!inputText.isEmpty()) // let pass empty input, regardless of the type, so user can void the value
1412
lines = inputText.split(QString::fromLatin1("\n"));
1414
if (!lines.isEmpty()) {
1415
if (type == 1) { // floats
1418
for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it, ++line) {
1421
QMessageBox::critical(this, tr("Invalid input"), tr("Input in line %1 is not a number").arg(line));
1426
else if (type == 2) { // integers
1429
for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it, ++line) {
1432
QMessageBox::critical(this, tr("Invalid input"), tr("Input in line %1 is not a number").arg(line));
1442
// --------------------------------------------------------------------
1444
LabelEditor::LabelEditor (QWidget * parent)
1448
auto layout = new QHBoxLayout(this);
1449
layout->setContentsMargins(0, 0, 0, 0);
1450
layout->setSpacing(2);
1452
lineEdit = new QLineEdit(this);
1453
layout->addWidget(lineEdit);
1455
connect(lineEdit, &QLineEdit::textChanged,
1456
this, &LabelEditor::validateText);
1458
button = new QPushButton(QLatin1String("..."), this);
1459
#if defined (Q_OS_MACOS)
1460
button->setAttribute(Qt::WA_LayoutUsesWidgetRect); // layout size from QMacStyle was not correct
1462
layout->addWidget(button);
1464
connect(button, &QPushButton::clicked, this, &LabelEditor::changeText);
1466
setFocusProxy(lineEdit);
1469
LabelEditor::~LabelEditor() = default;
1471
void LabelEditor::resizeEvent(QResizeEvent* e)
1473
button->setFixedWidth(e->size().height());
1474
button->setFixedHeight(e->size().height());
1477
QString LabelEditor::text() const
1479
return this->plainText;
1482
void LabelEditor::setText(const QString& s)
1484
this->plainText = s;
1486
QString text = QString::fromLatin1("[%1]").arg(this->plainText);
1487
lineEdit->setText(text);
1490
void LabelEditor::changeText()
1492
PropertyListDialog* dlg = new PropertyListDialog(static_cast<int>(type), this);
1493
dlg->setAttribute(Qt::WA_DeleteOnClose);
1494
dlg->setWindowTitle(tr("List"));
1496
auto hboxLayout = new QVBoxLayout(dlg);
1497
auto buttonBox = new QDialogButtonBox(dlg);
1498
buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
1500
auto edit = new PropertyListEditor(dlg);
1501
edit->setPlainText(this->plainText);
1503
hboxLayout->addWidget(edit);
1504
hboxLayout->addWidget(buttonBox);
1505
connect(buttonBox, &QDialogButtonBox::accepted, dlg, &PropertyListDialog::accept);
1506
connect(buttonBox, &QDialogButtonBox::rejected, dlg, &PropertyListDialog::reject);
1507
connect(dlg, &PropertyListDialog::accepted, this, [&] {
1508
QString inputText = edit->toPlainText();
1509
QString text = QString::fromLatin1("[%1]").arg(inputText);
1510
lineEdit->setText(text);
1517
* Validates if the input of the lineedit is a valid list.
1519
void LabelEditor::validateText(const QString& text)
1521
if (text.startsWith(QLatin1String("[")) && text.endsWith(QLatin1String("]"))) {
1522
this->plainText = text.mid(1, text.size()-2);
1523
Q_EMIT textChanged(this->plainText);
1528
* Sets the browse button's text to \a txt.
1530
void LabelEditor::setButtonText(const QString& txt)
1532
button->setText(txt);
1533
int w1 = 2 * QtTools::horizontalAdvance(button->fontMetrics(), txt);
1534
int w2 = 2 * QtTools::horizontalAdvance(button->fontMetrics(), QLatin1String(" ... "));
1535
button->setFixedWidth((w1 > w2 ? w1 : w2));
1539
* Returns the browse button's text.
1541
QString LabelEditor::buttonText() const
1543
return button->text();
1546
void LabelEditor::setInputType(InputType t)
1551
// --------------------------------------------------------------------
1553
ExpLineEdit::ExpLineEdit(QWidget* parent, bool expressionOnly)
1554
: QLineEdit(parent), autoClose(expressionOnly)
1558
QObject::connect(iconLabel, &ExpressionLabel::clicked, this, &ExpLineEdit::openFormulaDialog);
1560
QMetaObject::invokeMethod(this, "openFormulaDialog", Qt::QueuedConnection, QGenericReturnArgument());
1563
bool ExpLineEdit::apply(const std::string& propName) {
1565
if (!ExpressionBinding::apply(propName)) {
1567
QString val = QString::fromUtf8(Base::Interpreter().strToPython(text().toUtf8()).c_str());
1568
Gui::Command::doCommand(Gui::Command::Doc, "%s = \"%s\"", propName.c_str(), val.constData());
1576
void ExpLineEdit::bind(const ObjectIdentifier& _path) {
1578
ExpressionBinding::bind(_path);
1580
int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth);
1581
setStyleSheet(QString::fromLatin1("QLineEdit { padding-right: %1px } ").arg(iconLabel->sizeHint().width() + frameWidth + 1));
1586
void ExpLineEdit::setExpression(std::shared_ptr<Expression> expr)
1588
Q_ASSERT(isBound());
1591
ExpressionBinding::setExpression(expr);
1593
catch (const Base::Exception&) {
1595
QPalette p(palette());
1596
p.setColor(QPalette::Active, QPalette::Text, Qt::red);
1598
iconLabel->setToolTip(tr("An error occurred -- see Report View for information"));
1602
void ExpLineEdit::onChange() {
1604
if (getExpression()) {
1605
std::unique_ptr<Expression> result(getExpression()->eval());
1606
if(result->isDerivedFrom(App::StringExpression::getClassTypeId()))
1607
setText(QString::fromUtf8(static_cast<App::StringExpression*>(
1608
result.get())->getText().c_str()));
1610
setText(QString::fromUtf8(result->toString().c_str()));
1612
iconLabel->setPixmap(getIcon(":/icons/bound-expression.svg", QSize(iconHeight, iconHeight)));
1614
QPalette p(palette());
1615
p.setColor(QPalette::Text, Qt::lightGray);
1617
iconLabel->setExpressionText(Base::Tools::fromStdString(getExpression()->toString()));
1621
iconLabel->setPixmap(getIcon(":/icons/bound-expression-unset.svg", QSize(iconHeight, iconHeight)));
1622
QPalette p(palette());
1623
p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text));
1625
iconLabel->setExpressionText(QString());
1629
void ExpLineEdit::resizeEvent(QResizeEvent * event)
1631
QLineEdit::resizeEvent(event);
1633
int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth);
1635
QSize sz = iconLabel->sizeHint();
1636
iconLabel->move(rect().right() - frameWidth - sz.width(), 0);
1639
if (isBound() && getExpression()) {
1641
QPixmap pixmap = getIcon(":/icons/bound-expression.svg", QSize(iconHeight, iconHeight));
1642
iconLabel->setPixmap(pixmap);
1644
QPalette p(palette());
1645
p.setColor(QPalette::Text, Qt::lightGray);
1647
iconLabel->setExpressionText(Base::Tools::fromStdString(getExpression()->toString()));
1651
QPixmap pixmap = getIcon(":/icons/bound-expression-unset.svg", QSize(iconHeight, iconHeight));
1652
iconLabel->setPixmap(pixmap);
1654
QPalette p(palette());
1655
p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text));
1657
iconLabel->setExpressionText(QString());
1660
catch (const Base::Exception&) {
1662
QPalette p(palette());
1663
p.setColor(QPalette::Active, QPalette::Text, Qt::red);
1665
iconLabel->setToolTip(tr("An error occurred -- see Report View for information"));
1669
void ExpLineEdit::openFormulaDialog()
1671
Q_ASSERT(isBound());
1673
auto box = new Gui::Dialog::DlgExpressionInput(
1674
getPath(), getExpression(),Unit(), this);
1675
connect(box, &Dialog::DlgExpressionInput::finished, this, &ExpLineEdit::finishFormulaDialog);
1678
QPoint pos = mapToGlobal(QPoint(0,0));
1679
box->move(pos-box->expressionPosition());
1680
box->setExpressionInputSize(width(), height());
1683
void ExpLineEdit::finishFormulaDialog()
1685
auto box = qobject_cast<Gui::Dialog::DlgExpressionInput*>(sender());
1687
qWarning() << "Sender is not a Gui::Dialog::DlgExpressionInput";
1691
if (box->result() == QDialog::Accepted)
1692
setExpression(box->getExpression());
1693
else if (box->discardedFormula())
1694
setExpression(std::shared_ptr<Expression>());
1699
this->deleteLater();
1702
void ExpLineEdit::keyPressEvent(QKeyEvent *event)
1704
if (!hasExpression())
1705
QLineEdit::keyPressEvent(event);
1708
// --------------------------------------------------------------------
1710
ButtonGroup::ButtonGroup(QObject *parent)
1711
: QButtonGroup(parent)
1714
QButtonGroup::setExclusive(false);
1716
connect(this, qOverload<QAbstractButton *>(&QButtonGroup::buttonClicked),
1717
[this](QAbstractButton *button) {
1719
const auto btns = buttons();
1720
for (auto btn : btns) {
1721
if (btn && btn != button && btn->isCheckable())
1722
btn->setChecked(false);
1728
void ButtonGroup::setExclusive(bool on)
1733
bool ButtonGroup::exclusive() const
1739
#include "moc_Widgets.cpp"