FreeCAD

Форк
0
/
Widgets.cpp 
1739 строк · 51.5 Кб
1
/***************************************************************************
2
 *   Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net>     *
3
 *                                                                         *
4
 *   This file is part of the FreeCAD CAx development system.              *
5
 *                                                                         *
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.      *
10
 *                                                                         *
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.                  *
15
 *                                                                         *
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                                *
20
 *                                                                         *
21
 ***************************************************************************/
22

23

24
#include "PreCompiled.h"
25
#ifndef _PreComp_
26
# include <QColorDialog>
27
# include <QDebug>
28
# include <QDesktopServices>
29
# include <QDialogButtonBox>
30
# include <QDrag>
31
# include <QEventLoop>
32
# include <QKeyEvent>
33
# include <QMessageBox>
34
# include <QMimeData>
35
# include <QPainter>
36
# include <QPlainTextEdit>
37
# include <QStylePainter>
38
# include <QTextBlock>
39
# include <QTimer>
40
# include <QToolTip>
41
#endif
42

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>
48

49
#include "Widgets.h"
50
#include "Action.h"
51
#include "Application.h"
52
#include "BitmapFactory.h"
53
#include "Command.h"
54
#include "DlgExpressionInput.h"
55
#include "PrefWidgets.h"
56
#include "QuantitySpinBox_p.h"
57
#include "Tools.h"
58
#include "ui_DlgTreeWidget.h"
59

60
using namespace Gui;
61
using namespace App;
62
using namespace Base;
63

64
/**
65
 * Constructs an empty command view with parent \a parent.
66
 */
67
CommandIconView::CommandIconView ( QWidget * parent )
68
  : QListWidget(parent)
69
{
70
    connect(this, &QListWidget::currentItemChanged,
71
            this, &CommandIconView::onSelectionChanged);
72
}
73

74
/**
75
 * Destroys the icon view and deletes all items.
76
 */
77
CommandIconView::~CommandIconView () = default;
78

79
/**
80
 * Stores the name of the selected commands for drag and drop.
81
 */
82
void CommandIconView::startDrag (Qt::DropActions supportedActions)
83
{
84
    Q_UNUSED(supportedActions);
85
    QList<QListWidgetItem*> items = selectedItems();
86
    QByteArray itemData;
87
    QDataStream dataStream(&itemData, QIODevice::WriteOnly);
88

89
    QPixmap pixmap;
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();
95
    }
96

97
    auto mimeData = new QMimeData;
98
    mimeData->setData(QString::fromLatin1("text/x-action-items"), itemData);
99

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);
105
}
106

107
/**
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.
111
 */
112
void CommandIconView::onSelectionChanged(QListWidgetItem * item, QListWidgetItem *)
113
{
114
    if (item)
115
        Q_EMIT emitSelectionChanged(item->toolTip());
116
}
117

118
// ------------------------------------------------------------------------------
119

120
/* TRANSLATOR Gui::ActionSelector */
121

122
ActionSelector::ActionSelector(QWidget* parent)
123
  : QWidget(parent)
124
{
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);
131

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);
136

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);
143

144
    gridLayout->addWidget(removeButton, 2, 1, 1, 1);
145

146
    upButton = new QPushButton(this);
147
    upButton->setObjectName(QLatin1String("upButton"));
148
    upButton->setMinimumSize(QSize(30, 30));
149
    upButton->setIcon(BitmapFactory().pixmap("button_up"));
150

151
    gridLayout->addWidget(upButton, 3, 1, 1, 1);
152

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);
158

159
    gridLayout->addWidget(downButton, 4, 1, 1, 1);
160

161
    vboxLayout = new QVBoxLayout();
162
    vboxLayout->setContentsMargins(0, 0, 0, 0);
163
    labelAvailable = new QLabel(this);
164
    vboxLayout->addWidget(labelAvailable);
165

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);
172

173
    gridLayout->addLayout(vboxLayout, 0, 0, 6, 1);
174

175
    vboxLayout1 = new QVBoxLayout();
176
    vboxLayout1->setContentsMargins(0, 0, 0, 0);
177
    labelSelected = new QLabel(this);
178
    vboxLayout1->addWidget(labelSelected);
179

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);
186

187
    gridLayout->addLayout(vboxLayout1, 0, 2, 6, 1);
188

189
    addButton->setText(QString());
190
    removeButton->setText(QString());
191
    upButton->setText(QString());
192
    downButton->setText(QString());
193

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);
198

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);
203

204
    retranslateUi();
205
    setButtonsEnabled();
206
}
207

208
ActionSelector::~ActionSelector() = default;
209

210
void ActionSelector::setSelectedLabel(const QString& label)
211
{
212
    labelSelected->setText(label);
213
}
214

215
QString ActionSelector::selectedLabel() const
216
{
217
    return labelSelected->text();
218
}
219

220
void ActionSelector::setAvailableLabel(const QString& label)
221
{
222
    labelAvailable->setText(label);
223
}
224

225
QString ActionSelector::availableLabel() const
226
{
227
    return labelAvailable->text();
228
}
229

230
void ActionSelector::retranslateUi()
231
{
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"));
238
}
239

240
void ActionSelector::changeEvent(QEvent* event)
241
{
242
    if (event->type() == QEvent::LanguageChange) {
243
        retranslateUi();
244
    }
245
    QWidget::changeEvent(event);
246
}
247

248
void ActionSelector::keyPressEvent(QKeyEvent* event)
249
{
250
    if ((event->modifiers() & Qt::ControlModifier)) {
251
        switch (event->key())
252
        {
253
        case Qt::Key_Right:
254
            onAddButtonClicked();
255
            break;
256
        case Qt::Key_Left:
257
            onRemoveButtonClicked();
258
            break;
259
        case Qt::Key_Up:
260
            onUpButtonClicked();
261
            break;
262
        case Qt::Key_Down:
263
            onDownButtonClicked();
264
            break;
265
        default:
266
            event->ignore();
267
            return;
268
        }
269
    }
270
}
271

272
void ActionSelector::setButtonsEnabled()
273
{
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);
279
}
280

281
void ActionSelector::onCurrentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*)
282
{
283
    setButtonsEnabled();
284
}
285

286
void ActionSelector::onItemDoubleClicked(QTreeWidgetItem * item, int column)
287
{
288
    Q_UNUSED(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);
296
    }
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);
303
    }
304
}
305

306
void ActionSelector::onAddButtonClicked()
307
{
308
    QTreeWidgetItem* item = availableWidget->currentItem();
309
    if (item) {
310
        int index = availableWidget->indexOfTopLevelItem(item);
311
        item = availableWidget->takeTopLevelItem(index);
312
        availableWidget->setCurrentItem(nullptr);
313
        selectedWidget->addTopLevelItem(item);
314
        selectedWidget->setCurrentItem(item);
315
    }
316
}
317

318
void ActionSelector::onRemoveButtonClicked()
319
{
320
    QTreeWidgetItem* item = selectedWidget->currentItem();
321
    if (item) {
322
        int index = selectedWidget->indexOfTopLevelItem(item);
323
        item = selectedWidget->takeTopLevelItem(index);
324
        selectedWidget->setCurrentItem(nullptr);
325
        availableWidget->addTopLevelItem(item);
326
        availableWidget->setCurrentItem(item);
327
    }
328
}
329

330
void ActionSelector::onUpButtonClicked()
331
{
332
    QTreeWidgetItem* item = selectedWidget->currentItem();
333
    if (item && item->isSelected()) {
334
        int index = selectedWidget->indexOfTopLevelItem(item);
335
        if (index > 0) {
336
            selectedWidget->takeTopLevelItem(index);
337
            selectedWidget->insertTopLevelItem(index-1, item);
338
            selectedWidget->setCurrentItem(item);
339
        }
340
    }
341
}
342

343
void ActionSelector::onDownButtonClicked()
344
{
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);
352
        }
353
    }
354
}
355

356
// ------------------------------------------------------------------------------
357

358
/* TRANSLATOR Gui::AccelLineEdit */
359

360
/**
361
 * Constructs a line edit with no text.
362
 * The \a parent argument is sent to the QLineEdit constructor.
363
 */
364
AccelLineEdit::AccelLineEdit ( QWidget * parent )
365
  : QLineEdit(parent)
366
{
367
    setPlaceholderText(tr("Press a keyboard shortcut"));
368
    setClearButtonEnabled(true);
369
    keyPressedCount = 0;
370
}
371

372
bool AccelLineEdit::isNone() const
373
{
374
    return text().isEmpty();
375
}
376

377
/**
378
 * Checks which keys are pressed and show it as text.
379
 */
380
void AccelLineEdit::keyPressEvent (QKeyEvent * e)
381
{
382
    if (isReadOnly()) {
383
        QLineEdit::keyPressEvent(e);
384
        return;
385
    }
386

387
    QString txtLine = text();
388

389
    int key = e->key();
390
    Qt::KeyboardModifiers state = e->modifiers();
391

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.
395
    switch(key) {
396
    case Qt::Key_Backspace:
397
    case Qt::Key_Delete:
398
        if (state == Qt::NoModifier) {
399
            keyPressedCount = 0;
400
            if (isNone()) {
401
                QKeySequence ks(key);
402
                setText(ks.toString(QKeySequence::NativeText));
403
            }
404
            else {
405
                clear();
406
            }
407
        }
408
    case Qt::Key_Control:
409
    case Qt::Key_Shift:
410
    case Qt::Key_Alt:
411
    case Qt::Key_Meta:
412
    case Qt::Key_AltGr:
413
        return;
414
    default:
415
        break;
416
    }
417

418
    if (txtLine.isEmpty()) {
419
        // Text maybe cleared by QLineEdit's built in clear button
420
        keyPressedCount = 0;
421
    } else {
422
        // 4 keys are allowed for QShortcut
423
        switch (keyPressedCount) {
424
        case 4:
425
            keyPressedCount = 0;
426
            txtLine.clear();
427
            break;
428
        case 0:
429
            txtLine.clear();
430
            break;
431
        default:
432
            txtLine += QString::fromLatin1(",");
433
            break;
434
        }
435
    }
436

437
    // Handles modifiers applying a mask.
438
    if ((state & Qt::ControlModifier) == Qt::ControlModifier) {
439
        QKeySequence ks(Qt::CTRL);
440
        txtLine += ks.toString(QKeySequence::NativeText);
441
    }
442
    if ((state & Qt::AltModifier) == Qt::AltModifier) {
443
        QKeySequence ks(Qt::ALT);
444
        txtLine += ks.toString(QKeySequence::NativeText);
445
    }
446
    if ((state & Qt::ShiftModifier) == Qt::ShiftModifier) {
447
        QKeySequence ks(Qt::SHIFT);
448
        txtLine += ks.toString(QKeySequence::NativeText);
449
    }
450
    if ((state & Qt::MetaModifier) == Qt::MetaModifier) {
451
        QKeySequence ks(Qt::META);
452
        txtLine += ks.toString(QKeySequence::NativeText);
453
    }
454

455
    // Handles normal keys
456
    QKeySequence ks(key);
457
    txtLine += ks.toString(QKeySequence::NativeText);
458

459
    setText(txtLine);
460
    keyPressedCount++;
461
}
462

463
// ------------------------------------------------------------------------------
464

465
/* TRANSLATOR Gui::ModifierLineEdit */
466

467
/**
468
 * Constructs a line edit with no text.
469
 * The \a parent argument is sent to the QLineEdit constructor.
470
 */
471
ModifierLineEdit::ModifierLineEdit (QWidget * parent )
472
  : QLineEdit(parent)
473
{
474
    setPlaceholderText(tr("Press modifier keys"));
475
}
476

477
/**
478
 * Checks which modifiers are pressed and show it as text.
479
 */
480
void ModifierLineEdit::keyPressEvent (QKeyEvent * e)
481
{
482
    int key = e->key();
483
    Qt::KeyboardModifiers state = e->modifiers();
484

485
    switch (key) {
486
    case Qt::Key_Backspace:
487
    case Qt::Key_Delete:
488
        clear();
489
        return;
490
    case Qt::Key_Control:
491
    case Qt::Key_Shift:
492
    case Qt::Key_Alt:
493
    case Qt::Key_Meta:
494
        break;
495
    default:
496
        return;
497
    }
498

499
    clear();
500
    QString txtLine;
501

502
    // Handles modifiers applying a mask.
503
    if ((state & Qt::ControlModifier) == Qt::ControlModifier) {
504
        QKeySequence ks(Qt::CTRL);
505
        txtLine += ks.toString(QKeySequence::NativeText);
506
    }
507
    if ((state & Qt::AltModifier) == Qt::AltModifier) {
508
        QKeySequence ks(Qt::ALT);
509
        txtLine += ks.toString(QKeySequence::NativeText);
510
    }
511
    if ((state & Qt::ShiftModifier) == Qt::ShiftModifier) {
512
        QKeySequence ks(Qt::SHIFT);
513
        txtLine += ks.toString(QKeySequence::NativeText);
514
    }
515
    if ((state & Qt::MetaModifier) == Qt::MetaModifier) {
516
        QKeySequence ks(Qt::META);
517
        txtLine += ks.toString(QKeySequence::NativeText);
518
    }
519

520
    setText(txtLine);
521
}
522

523
// ------------------------------------------------------------------------------
524

525
ClearLineEdit::ClearLineEdit (QWidget * parent)
526
  : QLineEdit(parent)
527
{
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);
532
}
533

534
void ClearLineEdit::resizeEvent(QResizeEvent *e)
535
{
536
    QLineEdit::resizeEvent(e);
537
}
538

539
void ClearLineEdit::updateClearButton(const QString& text)
540
{
541
    clearAction->setVisible(!text.isEmpty());
542
}
543

544
// ------------------------------------------------------------------------------
545

546
/* TRANSLATOR Gui::CheckListDialog */
547

548
/**
549
 *  Constructs a CheckListDialog which is a child of 'parent', with the
550
 *  name 'name' and widget flags set to 'f'
551
 *
552
 *  The dialog will by default be modeless, unless you set 'modal' to
553
 *  true to construct a modal dialog.
554
 */
555
CheckListDialog::CheckListDialog( QWidget* parent, Qt::WindowFlags fl )
556
    : QDialog( parent, fl )
557
    , ui(new Ui_DlgTreeWidget)
558
{
559
    ui->setupUi(this);
560
}
561

562
/**
563
 *  Destroys the object and frees any allocated resources
564
 */
565
CheckListDialog::~CheckListDialog() = default;
566

567
/**
568
 * Sets the items to the dialog's list view. By default all items are checkable..
569
 */
570
void CheckListDialog::setCheckableItems( const QStringList& items )
571
{
572
    for (const auto & it : items) {
573
        auto item = new QTreeWidgetItem(ui->treeWidget);
574
        item->setText(0, it);
575
        item->setCheckState(0, Qt::Unchecked);
576
    }
577
}
578

579
/**
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.
582
 */
583
void CheckListDialog::setCheckableItems( const QList<CheckListItem>& items )
584
{
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));
589
    }
590
}
591

592
/**
593
 * Returns a list of the check items.
594
 */
595
QStringList CheckListDialog::getCheckedItems() const
596
{
597
    return checked;
598
}
599

600
/**
601
 * Collects all checked items to be able to return them by call \ref getCheckedItems().
602
 */
603
void CheckListDialog::accept ()
604
{
605
    QTreeWidgetItemIterator it(ui->treeWidget, QTreeWidgetItemIterator::Checked);
606
    while (*it) {
607
        checked.push_back((*it)->text(0));
608
        ++it;
609
    }
610

611
    QDialog::accept();
612
}
613

614
// ------------------------------------------------------------------------------
615

616
namespace Gui {
617
struct ColorButtonP
618
{
619
    QColor old, col;
620
    QPointer<QColorDialog> cd;
621
    bool allowChange{true};
622
    bool autoChange{false};
623
    bool drawFrame{true};
624
    bool allowTransparency{false};
625
    bool modal{true};
626
    bool dirty{true};
627
};
628
}
629

630
/**
631
 * Constructs a colored button called \a name with parent \a parent.
632
 */
633
ColorButton::ColorButton(QWidget* parent)
634
    : QPushButton(parent)
635
{
636
    d = new ColorButtonP();
637
    d->col = palette().color(QPalette::Active,QPalette::Midlight);
638
    connect(this, &ColorButton::clicked, this, &ColorButton::onChooseColor);
639

640
    int e = style()->pixelMetric(QStyle::PM_ButtonIconSize);
641
    setIconSize(QSize(2*e, e));
642
}
643

644
/**
645
 * Destroys the button.
646
 */
647
ColorButton::~ColorButton()
648
{
649
    delete d;
650
}
651

652
/**
653
 * Sets the color \a c to the button.
654
 */
655
void ColorButton::setColor(const QColor& c)
656
{
657
    d->col = c;
658
    d->dirty = true;
659
    update();
660
}
661

662
/**
663
 * Returns the current color of the button.
664
 */
665
QColor ColorButton::color() const
666
{
667
    return d->col;
668
}
669

670
/**
671
 * Sets the packed color \a c to the button.
672
 */
673
void ColorButton::setPackedColor(uint32_t c)
674
{
675
    App::Color color;
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);
681
    d->dirty = true;
682
    update();
683
}
684

685
/**
686
 * Returns the current packed color of the button.
687
 */
688
uint32_t ColorButton::packedColor() const
689
{
690
    App::Color color(d->col.redF(), d->col.greenF(), d->col.blueF(), d->col.alphaF());
691
    return color.getPackedValue();
692
}
693

694
void ColorButton::setAllowChangeColor(bool ok)
695
{
696
    d->allowChange = ok;
697
}
698

699
bool ColorButton::allowChangeColor() const
700
{
701
    return d->allowChange;
702
}
703

704
void ColorButton::setDrawFrame(bool ok)
705
{
706
    d->drawFrame = ok;
707
}
708

709
bool ColorButton::drawFrame() const
710
{
711
    return d->drawFrame;
712
}
713

714
void Gui::ColorButton::setAllowTransparency(bool allow)
715
{
716
    d->allowTransparency = allow;
717
    if (d->cd)
718
        d->cd->setOption(QColorDialog::ColorDialogOption::ShowAlphaChannel, allow);
719
}
720

721
bool Gui::ColorButton::allowTransparency() const
722
{
723
    if (d->cd)
724
        return d->cd->testOption(QColorDialog::ColorDialogOption::ShowAlphaChannel);
725
    else
726
        return d->allowTransparency;
727
}
728

729
void ColorButton::setModal(bool b)
730
{
731
    d->modal = b;
732
}
733

734
bool ColorButton::isModal() const
735
{
736
    return d->modal;
737
}
738

739
void ColorButton::setAutoChangeColor(bool on)
740
{
741
    d->autoChange = on;
742
}
743

744
bool ColorButton::autoChangeColor() const
745
{
746
    return d->autoChange;
747
}
748

749
/**
750
 * Draws the button label.
751
 */
752
void ColorButton::paintEvent (QPaintEvent * e)
753
{
754
    if (d->dirty) {
755
        QSize isize = iconSize();
756
        QPixmap pix(isize);
757
        pix.fill(palette().button().color());
758

759
        QPainter p(&pix);
760

761
        int w = pix.width();
762
        int h = pix.height();
763
        p.setPen(QPen(Qt::gray));
764
        if (d->drawFrame) {
765
            p.setBrush(d->col);
766
            p.drawRect(2, 2, w - 5, h - 5);
767
        }
768
        else {
769
            p.fillRect(0, 0, w, h, QBrush(d->col));
770
        }
771
        setIcon(QIcon(pix));
772

773
        d->dirty = false;
774
    }
775

776
    QPushButton::paintEvent(e);
777
}
778

779
void ColorButton::showModeless()
780
{
781
    if (d->cd.isNull()) {
782
        d->old = d->col;
783

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);
792
        d->cd = dlg;
793
    }
794
    d->cd->show();
795
}
796

797
void ColorButton::showModal()
798
{
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);
805

806
    if (d->autoChange) {
807
        connect(dlg, &QColorDialog::currentColorChanged, this, &ColorButton::onColorChosen);
808
    }
809

810
    dlg->setCurrentColor(currentColor);
811
    dlg->adjustSize();
812

813
    connect(dlg, &QColorDialog::finished, this, [&](int result) {
814
        if (result == QDialog::Accepted) {
815
            QColor c = dlg->selectedColor();
816
            if (c.isValid()) {
817
                setColor(c);
818
                Q_EMIT changed();
819
            }
820
        }
821
        else if (d->autoChange) {
822
            setColor(currentColor);
823
            Q_EMIT changed();
824
        }
825
    });
826

827
    dlg->exec();
828
}
829

830
/**
831
 * Opens a QColorDialog to set a new color.
832
 */
833
void ColorButton::onChooseColor()
834
{
835
    if (!d->allowChange)
836
        return;
837
    if (d->modal) {
838
        showModal();
839
    }
840
    else {
841
        showModeless();
842
    }
843
}
844

845
void ColorButton::onColorChosen(const QColor& c)
846
{
847
    setColor(c);
848
    Q_EMIT changed();
849
}
850

851
void ColorButton::onRejected()
852
{
853
    setColor(d->old);
854
    Q_EMIT changed();
855
}
856

857
// ------------------------------------------------------------------------------
858

859
UrlLabel::UrlLabel(QWidget* parent, Qt::WindowFlags f)
860
    : QLabel(parent, f)
861
    , _url (QStringLiteral("http://localhost"))
862
    , _launchExternal(true)
863
{
864
    setToolTip(this->_url);    
865
    setCursor(Qt::PointingHandCursor);
866
    if (qApp->styleSheet().isEmpty())
867
        setStyleSheet(QStringLiteral("Gui--UrlLabel {color: #0000FF;text-decoration: underline;}"));
868
}
869

870
UrlLabel::~UrlLabel() = default;
871

872
void Gui::UrlLabel::setLaunchExternal(bool l)
873
{
874
    _launchExternal = l;
875
}
876

877
void UrlLabel::mouseReleaseEvent(QMouseEvent*)
878
{
879
    if (_launchExternal)
880
        QDesktopServices::openUrl(this->_url);
881
    else
882
        // Someone else will deal with it...
883
        Q_EMIT linkClicked(_url);
884
}
885

886
QString UrlLabel::url() const
887
{
888
    return this->_url;
889
}
890

891
bool Gui::UrlLabel::launchExternal() const
892
{
893
    return _launchExternal;
894
}
895

896
void UrlLabel::setUrl(const QString& u)
897
{
898
    this->_url = u;
899
    setToolTip(this->_url);
900
}
901

902
// --------------------------------------------------------------------
903

904
StatefulLabel::StatefulLabel(QWidget* parent)
905
    : QLabel(parent)
906
    , _overridePreference(false)
907
{
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);
911
}
912

913
StatefulLabel::~StatefulLabel()
914
{
915
    if (_parameterGroup.isValid())
916
        _parameterGroup->Detach(this);
917
    _stylesheetGroup->Detach(this);
918
}
919

920
void StatefulLabel::setDefaultStyle(const QString& defaultStyle)
921
{
922
    _defaultStyle = defaultStyle;
923
}
924

925
void StatefulLabel::setParameterGroup(const std::string& groupName)
926
{
927
    if (_parameterGroup.isValid())
928
        _parameterGroup->Detach(this);
929
        
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);
934
}
935

936
void StatefulLabel::registerState(const QString& state, const QString& styleCSS,
937
    const std::string& preferenceName)
938
{
939
    _availableStates[state] = { styleCSS, preferenceName };
940
}
941

942
void StatefulLabel::registerState(const QString& state, const QColor& color,
943
    const std::string& preferenceName)
944
{
945
    QString css;
946
    if (color.isValid())
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 };
949
}
950

951
void StatefulLabel::registerState(const QString& state, const QColor& fg, const QColor& bg,
952
    const std::string& preferenceName)
953
{
954
    QString colorEntries;
955
    if (fg.isValid())
956
        colorEntries.append(QString::fromUtf8("color : rgba(%1,%2,%3,%4);").arg(fg.red()).arg(fg.green()).arg(fg.blue()).arg(fg.alpha()));
957
    if (bg.isValid())
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 };
961
}
962

963
/** Observes the parameter group and clears the cache if it changes */
964
void StatefulLabel::OnChange(Base::Subject<const char*>& rCaller, const char* rcReason)
965
{
966
    Q_UNUSED(rCaller);
967
    auto changedItem = std::string(rcReason);
968
    if (changedItem == "StyleSheet") {
969
        _styleCache.clear();
970
    }
971
    else {
972
        for (const auto& state : _availableStates) {
973
            if (state.second.preferenceString == changedItem) {
974
                _styleCache.erase(_styleCache.find(state.first));
975
            }
976
        }
977
    }
978
}
979

980
void StatefulLabel::setOverridePreference(bool overridePreference)
981
{
982
    _overridePreference = overridePreference;
983
}
984

985
void StatefulLabel::setState(QString state)
986
{
987
    _state = state;
988
    this->ensurePolished();
989

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)
993
        return;
994

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);
999
        return;
1000
    }
1001

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();
1016
                    return;
1017
                }
1018
            }
1019

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();
1027
                    return;
1028
                }
1029
            }
1030
        }
1031

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();
1037
            return;
1038
        }
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);
1044
    }
1045
    else {
1046
        if (styleSheet().isEmpty()) {
1047
            this->setStyleSheet(_defaultStyle);
1048
            _styleCache[state] = this->styleSheet();
1049
        }
1050
    }
1051
}
1052

1053
// --------------------------------------------------------------------
1054

1055
/* TRANSLATOR Gui::LabelButton */
1056

1057
/**
1058
 * Constructs a file chooser called \a name with the parent \a parent.
1059
 */
1060
LabelButton::LabelButton (QWidget * parent)
1061
  : QWidget(parent)
1062
{
1063
    auto layout = new QHBoxLayout(this);
1064
    layout->setContentsMargins(0, 0, 0, 0);
1065
    layout->setSpacing(1);
1066

1067
    label = new QLabel(this);
1068
    label->setAutoFillBackground(true);
1069
    layout->addWidget(label);
1070

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
1074
#endif
1075
    layout->addWidget(button);
1076

1077
    connect(button, &QPushButton::clicked, this, &LabelButton::browse);
1078
    connect(button, &QPushButton::clicked, this, &LabelButton::buttonClicked);
1079
}
1080

1081
LabelButton::~LabelButton() = default;
1082

1083
void LabelButton::resizeEvent(QResizeEvent* e)
1084
{
1085
    button->setFixedWidth(e->size().height());
1086
    button->setFixedHeight(e->size().height());
1087
}
1088

1089
QLabel *LabelButton::getLabel() const
1090
{
1091
    return label;
1092
}
1093

1094
QPushButton *LabelButton::getButton() const
1095
{
1096
    return button;
1097
}
1098

1099
QVariant LabelButton::value() const
1100
{
1101
    return _val;
1102
}
1103

1104
void LabelButton::setValue(const QVariant& val)
1105
{
1106
    _val = val;
1107
    showValue(_val);
1108
    Q_EMIT valueChanged(_val);
1109
}
1110

1111
void LabelButton::showValue(const QVariant& data)
1112
{
1113
    label->setText(data.toString());
1114
}
1115

1116
void LabelButton::browse()
1117
{
1118
}
1119

1120
// ----------------------------------------------------------------------
1121

1122
ToolTip* ToolTip::inst = nullptr;
1123

1124
ToolTip* ToolTip::instance()
1125
{
1126
    if (!inst)
1127
        inst = new ToolTip();
1128
    return inst;
1129
}
1130

1131
ToolTip::ToolTip() : installed(false), hidden(true)
1132
{
1133
}
1134

1135
ToolTip::~ToolTip() = default;
1136

1137
void ToolTip::installEventFilter()
1138
{
1139
    if (this->installed)
1140
        return;
1141
    qApp->installEventFilter(this);
1142
    this->installed = true;
1143
}
1144

1145
void ToolTip::removeEventFilter()
1146
{
1147
    if (!this->installed)
1148
        return;
1149
    qApp->removeEventFilter(this);
1150
    this->installed = false;
1151
}
1152

1153
void ToolTip::showText(const QPoint & pos, const QString & text, QWidget * w)
1154
{
1155
    ToolTip* tip = instance();
1156
    if (!text.isEmpty()) {
1157
        // install this object to filter timer events for the tooltip label
1158
        tip->installEventFilter();
1159
        tip->pos = pos;
1160
        tip->text = text;
1161
        tip->w = w;
1162
        // show text with a short delay
1163
        tip->tooltipTimer.start(80, tip);
1164
        tip->displayTime.start();
1165
    }
1166
    else {
1167
        hideText();
1168
    }
1169
}
1170

1171
void ToolTip::hideText()
1172
{
1173
    instance()->tooltipTimer.stop();
1174
    instance()->hidden = true;
1175
    QToolTip::hideText();
1176
}
1177

1178
void ToolTip::timerEvent(QTimerEvent *e)
1179
{
1180
    if (e->timerId() == tooltipTimer.timerId()) {
1181
        QToolTip::showText(pos, text, w);
1182
        tooltipTimer.stop();
1183
        displayTime.restart();
1184
    }
1185
}
1186

1187
bool ToolTip::eventFilter(QObject* o, QEvent*e)
1188
{
1189
    if (!o->isWidgetType())
1190
        return false;
1191
    switch(e->type()) {
1192
    case QEvent::MouseButtonPress:
1193
        hideText();
1194
        break;
1195
    case QEvent::KeyPress:
1196
        if (static_cast<QKeyEvent*>(e)->key() == Qt::Key_Escape)
1197
            hideText();
1198
        break;
1199
    case QEvent::Leave:
1200
        hideText();
1201
        break;
1202
    case QEvent::Timer:
1203
    case QEvent::Show:
1204
    case QEvent::Hide:
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
1209
                // label visible.
1210

1211
                // Ignore the timer events to prevent from being closed
1212
                if (e->type() == QEvent::Show) {
1213
                    this->hidden = false;
1214
                }
1215
                else if (e->type() == QEvent::Hide) {
1216
                    // removeEventFilter();
1217
                    this->hidden = true;
1218
                }
1219
                else if (e->type() == QEvent::Timer &&
1220
                    !this->hidden && displayTime.elapsed() < 5000) {
1221
                    return true;
1222
                }
1223
            }
1224
        }
1225
        break;
1226
    default:
1227
        break;
1228
    }
1229
    return false;
1230
}
1231

1232
// ----------------------------------------------------------------------
1233

1234
StatusWidget::StatusWidget(QWidget* parent)
1235
  : QDialog(parent, Qt::Dialog | Qt::FramelessWindowHint)
1236
{
1237
    label = new QLabel(this);
1238
    label->setAlignment(Qt::AlignCenter);
1239

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);
1244
}
1245

1246
StatusWidget::~StatusWidget() = default;
1247

1248
void StatusWidget::setStatusText(const QString& s)
1249
{
1250
    label->setText(s);
1251
}
1252

1253
void StatusWidget::showText(int ms)
1254
{
1255
    show();
1256
    QTimer timer;
1257
    QEventLoop loop;
1258
    QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
1259
    timer.start(ms);
1260
    loop.exec(QEventLoop::ExcludeUserInputEvents);
1261
    hide();
1262
}
1263

1264
QSize StatusWidget::sizeHint () const
1265
{
1266
    return {250,100};
1267
}
1268

1269
void StatusWidget::showEvent(QShowEvent* event)
1270
{
1271
    QDialog::showEvent(event);
1272
}
1273

1274
void StatusWidget::hideEvent(QHideEvent*)
1275
{
1276
}
1277

1278
// --------------------------------------------------------------------
1279

1280
class LineNumberArea : public QWidget
1281
{
1282
public:
1283
    explicit LineNumberArea(PropertyListEditor *editor) : QWidget(editor) {
1284
        codeEditor = editor;
1285
    }
1286

1287
    QSize sizeHint() const override {
1288
        return {codeEditor->lineNumberAreaWidth(), 0};
1289
    }
1290

1291
protected:
1292
    void paintEvent(QPaintEvent *event) override {
1293
        codeEditor->lineNumberAreaPaintEvent(event);
1294
    }
1295

1296
private:
1297
    PropertyListEditor *codeEditor;
1298
};
1299

1300
PropertyListEditor::PropertyListEditor(QWidget *parent) : QPlainTextEdit(parent)
1301
{
1302
    lineNumberArea = new LineNumberArea(this);
1303

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);
1310

1311
    updateLineNumberAreaWidth(0);
1312
    highlightCurrentLine();
1313
}
1314

1315
int PropertyListEditor::lineNumberAreaWidth()
1316
{
1317
    int digits = 1;
1318
    int max = qMax(1, blockCount());
1319
    while (max >= 10) {
1320
        max /= 10;
1321
        ++digits;
1322
    }
1323

1324
    int space = 3 + QtTools::horizontalAdvance(fontMetrics(), QLatin1Char('9')) * digits;
1325

1326
    return space;
1327
}
1328

1329
void PropertyListEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
1330
{
1331
    setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
1332
}
1333

1334
void PropertyListEditor::updateLineNumberArea(const QRect &rect, int dy)
1335
{
1336
    if (dy)
1337
        lineNumberArea->scroll(0, dy);
1338
    else
1339
        lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
1340

1341
    if (rect.contains(viewport()->rect()))
1342
        updateLineNumberAreaWidth(0);
1343
}
1344

1345
void PropertyListEditor::resizeEvent(QResizeEvent *e)
1346
{
1347
    QPlainTextEdit::resizeEvent(e);
1348

1349
    QRect cr = contentsRect();
1350
    lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
1351
}
1352

1353
void PropertyListEditor::highlightCurrentLine()
1354
{
1355
    QList<QTextEdit::ExtraSelection> extraSelections;
1356
    if (!isReadOnly()) {
1357
        QTextEdit::ExtraSelection selection;
1358

1359
        QColor lineColor = QColor(Qt::yellow).lighter(160);
1360

1361
        selection.format.setBackground(lineColor);
1362
        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
1363
        selection.cursor = textCursor();
1364
        selection.cursor.clearSelection();
1365
        extraSelections.append(selection);
1366
    }
1367

1368
    setExtraSelections(extraSelections);
1369
}
1370

1371
void PropertyListEditor::lineNumberAreaPaintEvent(QPaintEvent *event)
1372
{
1373
    QPainter painter(lineNumberArea);
1374
    painter.fillRect(event->rect(), Qt::lightGray);
1375

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();
1380

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);
1387
        }
1388

1389
        block = block.next();
1390
        top = bottom;
1391
        bottom = top + (int) blockBoundingRect(block).height();
1392
        ++blockNumber;
1393
    }
1394
}
1395

1396
class PropertyListDialog : public QDialog
1397
{
1398
    int type;
1399

1400
public:
1401
    PropertyListDialog(int type, QWidget* parent) : QDialog(parent),type(type)
1402
    {
1403
    }
1404

1405
    void accept() override
1406
    {
1407
        auto edit = this->findChild<PropertyListEditor*>();
1408
        QStringList lines;
1409
        if (edit) {
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"));
1413
        }
1414
        if (!lines.isEmpty()) {
1415
            if (type == 1) { // floats
1416
                bool ok;
1417
                int line=1;
1418
                for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it, ++line) {
1419
                    it->toDouble(&ok);
1420
                    if (!ok) {
1421
                        QMessageBox::critical(this, tr("Invalid input"), tr("Input in line %1 is not a number").arg(line));
1422
                        return;
1423
                    }
1424
                }
1425
            }
1426
            else if (type == 2) { // integers
1427
                bool ok;
1428
                int line=1;
1429
                for (QStringList::iterator it = lines.begin(); it != lines.end(); ++it, ++line) {
1430
                    it->toInt(&ok);
1431
                    if (!ok) {
1432
                        QMessageBox::critical(this, tr("Invalid input"), tr("Input in line %1 is not a number").arg(line));
1433
                        return;
1434
                    }
1435
                }
1436
            }
1437
        }
1438
        QDialog::accept();
1439
    }
1440
};
1441

1442
// --------------------------------------------------------------------
1443

1444
LabelEditor::LabelEditor (QWidget * parent)
1445
  : QWidget(parent)
1446
{
1447
    type = String;
1448
    auto layout = new QHBoxLayout(this);
1449
    layout->setContentsMargins(0, 0, 0, 0);
1450
    layout->setSpacing(2);
1451

1452
    lineEdit = new QLineEdit(this);
1453
    layout->addWidget(lineEdit);
1454

1455
    connect(lineEdit, &QLineEdit::textChanged,
1456
            this, &LabelEditor::validateText);
1457

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
1461
#endif
1462
    layout->addWidget(button);
1463

1464
    connect(button, &QPushButton::clicked, this, &LabelEditor::changeText);
1465

1466
    setFocusProxy(lineEdit);
1467
}
1468

1469
LabelEditor::~LabelEditor() = default;
1470

1471
void LabelEditor::resizeEvent(QResizeEvent* e)
1472
{
1473
    button->setFixedWidth(e->size().height());
1474
    button->setFixedHeight(e->size().height());
1475
}
1476

1477
QString LabelEditor::text() const
1478
{
1479
    return this->plainText;
1480
}
1481

1482
void LabelEditor::setText(const QString& s)
1483
{
1484
    this->plainText = s;
1485

1486
    QString text = QString::fromLatin1("[%1]").arg(this->plainText);
1487
    lineEdit->setText(text);
1488
}
1489

1490
void LabelEditor::changeText()
1491
{
1492
    PropertyListDialog* dlg = new PropertyListDialog(static_cast<int>(type), this);
1493
    dlg->setAttribute(Qt::WA_DeleteOnClose);
1494
    dlg->setWindowTitle(tr("List"));
1495

1496
    auto hboxLayout = new QVBoxLayout(dlg);
1497
    auto buttonBox = new QDialogButtonBox(dlg);
1498
    buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
1499

1500
    auto edit = new PropertyListEditor(dlg);
1501
    edit->setPlainText(this->plainText);
1502

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);
1511
    });
1512

1513
    dlg->exec();
1514
}
1515

1516
/**
1517
 * Validates if the input of the lineedit is a valid list.
1518
 */
1519
void LabelEditor::validateText(const QString& text)
1520
{
1521
    if (text.startsWith(QLatin1String("[")) && text.endsWith(QLatin1String("]"))) {
1522
        this->plainText = text.mid(1, text.size()-2);
1523
        Q_EMIT textChanged(this->plainText);
1524
    }
1525
}
1526

1527
/**
1528
 * Sets the browse button's text to \a txt.
1529
 */
1530
void LabelEditor::setButtonText(const QString& txt)
1531
{
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));
1536
}
1537

1538
/**
1539
 * Returns the browse button's text.
1540
 */
1541
QString LabelEditor::buttonText() const
1542
{
1543
    return button->text();
1544
}
1545

1546
void LabelEditor::setInputType(InputType t)
1547
{
1548
    this->type = t;
1549
}
1550

1551
// --------------------------------------------------------------------
1552

1553
ExpLineEdit::ExpLineEdit(QWidget* parent, bool expressionOnly)
1554
    : QLineEdit(parent), autoClose(expressionOnly)
1555
{
1556
    makeLabel(this);
1557

1558
    QObject::connect(iconLabel, &ExpressionLabel::clicked, this, &ExpLineEdit::openFormulaDialog);
1559
    if (expressionOnly)
1560
        QMetaObject::invokeMethod(this, "openFormulaDialog", Qt::QueuedConnection, QGenericReturnArgument());
1561
}
1562

1563
bool ExpLineEdit::apply(const std::string& propName) {
1564

1565
    if (!ExpressionBinding::apply(propName)) {
1566
        if(!autoClose) {
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());
1569
        }
1570
        return true;
1571
    }
1572

1573
    return false;
1574
}
1575

1576
void ExpLineEdit::bind(const ObjectIdentifier& _path) {
1577

1578
    ExpressionBinding::bind(_path);
1579

1580
    int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth);
1581
    setStyleSheet(QString::fromLatin1("QLineEdit { padding-right: %1px } ").arg(iconLabel->sizeHint().width() + frameWidth + 1));
1582

1583
    iconLabel->show();
1584
}
1585

1586
void ExpLineEdit::setExpression(std::shared_ptr<Expression> expr)
1587
{
1588
    Q_ASSERT(isBound());
1589

1590
    try {
1591
        ExpressionBinding::setExpression(expr);
1592
    }
1593
    catch (const Base::Exception&) {
1594
        setReadOnly(true);
1595
        QPalette p(palette());
1596
        p.setColor(QPalette::Active, QPalette::Text, Qt::red);
1597
        setPalette(p);
1598
        iconLabel->setToolTip(tr("An error occurred -- see Report View for information"));
1599
    }
1600
}
1601

1602
void ExpLineEdit::onChange() {
1603

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()));
1609
        else
1610
            setText(QString::fromUtf8(result->toString().c_str()));
1611
        setReadOnly(true);
1612
        iconLabel->setPixmap(getIcon(":/icons/bound-expression.svg", QSize(iconHeight, iconHeight)));
1613

1614
        QPalette p(palette());
1615
        p.setColor(QPalette::Text, Qt::lightGray);
1616
        setPalette(p);
1617
        iconLabel->setExpressionText(Base::Tools::fromStdString(getExpression()->toString()));
1618
    }
1619
    else {
1620
        setReadOnly(false);
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));
1624
        setPalette(p);
1625
        iconLabel->setExpressionText(QString());
1626
    }
1627
}
1628

1629
void ExpLineEdit::resizeEvent(QResizeEvent * event)
1630
{
1631
    QLineEdit::resizeEvent(event);
1632

1633
    int frameWidth = style()->pixelMetric(QStyle::PM_SpinBoxFrameWidth);
1634

1635
    QSize sz = iconLabel->sizeHint();
1636
    iconLabel->move(rect().right() - frameWidth - sz.width(), 0);
1637

1638
    try {
1639
        if (isBound() && getExpression()) {
1640
            setReadOnly(true);
1641
            QPixmap pixmap = getIcon(":/icons/bound-expression.svg", QSize(iconHeight, iconHeight));
1642
            iconLabel->setPixmap(pixmap);
1643

1644
            QPalette p(palette());
1645
            p.setColor(QPalette::Text, Qt::lightGray);
1646
            setPalette(p);
1647
            iconLabel->setExpressionText(Base::Tools::fromStdString(getExpression()->toString()));
1648
        }
1649
        else {
1650
            setReadOnly(false);
1651
            QPixmap pixmap = getIcon(":/icons/bound-expression-unset.svg", QSize(iconHeight, iconHeight));
1652
            iconLabel->setPixmap(pixmap);
1653

1654
            QPalette p(palette());
1655
            p.setColor(QPalette::Active, QPalette::Text, defaultPalette.color(QPalette::Text));
1656
            setPalette(p);
1657
            iconLabel->setExpressionText(QString());
1658
        }
1659
    }
1660
    catch (const Base::Exception&) {
1661
        setReadOnly(true);
1662
        QPalette p(palette());
1663
        p.setColor(QPalette::Active, QPalette::Text, Qt::red);
1664
        setPalette(p);
1665
        iconLabel->setToolTip(tr("An error occurred -- see Report View for information"));
1666
    }
1667
}
1668

1669
void ExpLineEdit::openFormulaDialog()
1670
{
1671
    Q_ASSERT(isBound());
1672

1673
    auto box = new Gui::Dialog::DlgExpressionInput(
1674
            getPath(), getExpression(),Unit(), this);
1675
    connect(box, &Dialog::DlgExpressionInput::finished, this, &ExpLineEdit::finishFormulaDialog);
1676
    box->show();
1677

1678
    QPoint pos = mapToGlobal(QPoint(0,0));
1679
    box->move(pos-box->expressionPosition());
1680
    box->setExpressionInputSize(width(), height());
1681
}
1682

1683
void ExpLineEdit::finishFormulaDialog()
1684
{
1685
    auto box = qobject_cast<Gui::Dialog::DlgExpressionInput*>(sender());
1686
    if (!box) {
1687
        qWarning() << "Sender is not a Gui::Dialog::DlgExpressionInput";
1688
        return;
1689
    }
1690

1691
    if (box->result() == QDialog::Accepted)
1692
        setExpression(box->getExpression());
1693
    else if (box->discardedFormula())
1694
        setExpression(std::shared_ptr<Expression>());
1695

1696
    box->deleteLater();
1697

1698
    if(autoClose)
1699
        this->deleteLater();
1700
}
1701

1702
void ExpLineEdit::keyPressEvent(QKeyEvent *event)
1703
{
1704
    if (!hasExpression())
1705
        QLineEdit::keyPressEvent(event);
1706
}
1707

1708
// --------------------------------------------------------------------
1709

1710
ButtonGroup::ButtonGroup(QObject *parent)
1711
  : QButtonGroup(parent)
1712
  , _exclusive(true)
1713
{
1714
    QButtonGroup::setExclusive(false);
1715

1716
    connect(this, qOverload<QAbstractButton *>(&QButtonGroup::buttonClicked),
1717
            [this](QAbstractButton *button) {
1718
        if (exclusive()) {
1719
            const auto btns = buttons();
1720
            for (auto btn : btns) {
1721
                if (btn && btn != button && btn->isCheckable())
1722
                    btn->setChecked(false);
1723
            }
1724
        }
1725
    });
1726
}
1727

1728
void ButtonGroup::setExclusive(bool on)
1729
{
1730
    _exclusive = on;
1731
}
1732

1733
bool ButtonGroup::exclusive() const
1734
{
1735
    return _exclusive;
1736
}
1737

1738

1739
#include "moc_Widgets.cpp"
1740

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.