Warning, file /sdk/ktechlab/src/iteminterface.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /***************************************************************************
0002  *   Copyright (C) 2004-2005 by David Saxton                               *
0003  *   david@bluehaze.org                                                    *
0004  *                                                                         *
0005  *   This program is free software; you can redistribute it and/or modify  *
0006  *   it under the terms of the GNU General Public License as published by  *
0007  *   the Free Software Foundation; either version 2 of the License, or     *
0008  *   (at your option) any later version.                                   *
0009  ***************************************************************************/
0010 
0011 #include "iteminterface.h"
0012 #include "circuitview.h"
0013 #include "cnitem.h"
0014 #include "cnitemgroup.h"
0015 #include "colorutils.h"
0016 #include "contexthelp.h"
0017 #include "doublespinbox.h"
0018 #include "itemdocument.h"
0019 #include "itemeditor.h"
0020 #include "itemview.h"
0021 #include "ktechlab.h"
0022 
0023 #include <KColorCombo>
0024 #include <KComboBox>
0025 #include <KLineEdit>
0026 #include <KToolBar>
0027 #include <KUrlRequester>
0028 #include <KXMLGUIFactory>
0029 
0030 #include <QApplication>
0031 #include <QCheckBox>
0032 #include <QLabel>
0033 #include <QSpinBox>
0034 
0035 #include <cassert>
0036 
0037 #include <ktechlab_debug.h>
0038 
0039 ItemInterface *ItemInterface::m_pSelf = nullptr;
0040 
0041 ItemInterface *ItemInterface::self()
0042 {
0043     if (!m_pSelf)
0044         m_pSelf = new ItemInterface();
0045 
0046     return m_pSelf;
0047 }
0048 
0049 ItemInterface::ItemInterface()
0050     : QObject(KTechlab::self())
0051     , m_isInTbDataChanged(false)
0052 {
0053     m_pActiveItemEditorToolBar = nullptr;
0054     p_cvb = nullptr;
0055     p_itemGroup = nullptr;
0056     p_lastItem = nullptr;
0057     m_currentActionTicket = -1;
0058     m_toolBarWidgetID = -1;
0059 }
0060 
0061 ItemInterface::~ItemInterface()
0062 {
0063 }
0064 
0065 void ItemInterface::slotGetActionTicket()
0066 {
0067     m_currentActionTicket = p_cvb ? p_cvb->getActionTicket() : -1;
0068 }
0069 
0070 void ItemInterface::slotItemDocumentChanged(ItemDocument *doc)
0071 {
0072     slotClearAll();
0073     if (ItemDocument *itemDocument = dynamic_cast<ItemDocument *>((Document *)p_cvb)) {
0074         disconnect(itemDocument, &ItemDocument::selectionChanged, this, &ItemInterface::slotUpdateItemInterface);
0075     }
0076 
0077     p_itemGroup = nullptr;
0078     p_cvb = doc;
0079 
0080     slotGetActionTicket();
0081 
0082     if (!p_cvb)
0083         return;
0084 
0085     connect(p_cvb, &ItemDocument::selectionChanged, this, &ItemInterface::slotUpdateItemInterface);
0086 
0087     p_itemGroup = p_cvb->selectList();
0088 
0089     slotUpdateItemInterface();
0090 }
0091 
0092 void ItemInterface::clearItemEditorToolBar()
0093 {
0094     if (m_pActiveItemEditorToolBar && m_toolBarWidgetID != -1) {
0095         // m_pActiveItemEditorToolBar->removeItem(m_toolBarWidgetID); // TODO add proper replacmenet
0096         m_pActiveItemEditorToolBar->clear();
0097     }
0098     m_toolBarWidgetID = -1;
0099     itemEditTBCleared();
0100 }
0101 
0102 void ItemInterface::slotClearAll()
0103 {
0104     ContextHelp::self()->slotClear();
0105     ItemEditor::self()->slotClear();
0106     clearItemEditorToolBar();
0107     p_lastItem = nullptr;
0108 }
0109 
0110 void ItemInterface::slotMultipleSelected()
0111 {
0112     ContextHelp::self()->slotMultipleSelected();
0113     ItemEditor::self()->slotMultipleSelected();
0114     clearItemEditorToolBar();
0115     p_lastItem = nullptr;
0116 }
0117 
0118 void ItemInterface::slotUpdateItemInterface()
0119 {
0120     if (!p_itemGroup)
0121         return;
0122 
0123     slotGetActionTicket();
0124     updateItemActions();
0125 
0126     if (!p_itemGroup->itemsAreSameType()) {
0127         slotMultipleSelected();
0128         return;
0129     }
0130     if (p_lastItem && p_itemGroup->activeItem()) {
0131         ItemEditor::self()->itemGroupUpdated(p_itemGroup);
0132         return;
0133     }
0134 
0135     p_lastItem = p_itemGroup->activeItem();
0136     if (!p_lastItem) {
0137         slotClearAll();
0138         return;
0139     }
0140 
0141     ContextHelp::self()->slotUpdate(p_lastItem);
0142     ItemEditor::self()->slotUpdate(p_itemGroup);
0143     if (CNItem *cnItem = dynamic_cast<CNItem *>((Item *)p_lastItem)) {
0144         ItemEditor::self()->slotUpdate(cnItem);
0145     }
0146 
0147     // Update item editor toolbar
0148     if (ItemView *itemView = dynamic_cast<ItemView *>(p_cvb->activeView())) {
0149         if (KTechlab *ktl = KTechlab::self()) {
0150             if ((m_pActiveItemEditorToolBar = dynamic_cast<KToolBar *>(ktl->factory()->container("itemEditorTB", itemView)))) {
0151                 // m_pActiveItemEditorToolBar->setFullSize( true ); // TODO proper replacement
0152                 m_pActiveItemEditorToolBar->adjustSize();
0153                 QWidget *widget = configWidget();
0154                 m_toolBarWidgetID = 1;
0155                 // m_pActiveItemEditorToolBar->insertWidget( m_toolBarWidgetID, 0, widget ); // TODO properly fix
0156                 m_pActiveItemEditorToolBar->addWidget(widget);
0157             }
0158         }
0159     }
0160 }
0161 
0162 void ItemInterface::updateItemActions()
0163 {
0164     ItemView *itemView = ((ItemDocument *)p_cvb) ? dynamic_cast<ItemView *>(p_cvb->activeView()) : nullptr;
0165     if (!itemView)
0166         return;
0167 
0168     bool itemsSelected = p_itemGroup && p_itemGroup->itemCount();
0169 
0170     itemView->actionByName("edit_raise")->setEnabled(itemsSelected);
0171     itemView->actionByName("edit_lower")->setEnabled(itemsSelected);
0172 
0173     if (KTechlab::self()) {
0174         KTechlab::self()->actionByName("edit_cut")->setEnabled(itemsSelected);
0175         KTechlab::self()->actionByName("edit_copy")->setEnabled(itemsSelected);
0176     }
0177 
0178     CNItemGroup *cnItemGroup = dynamic_cast<CNItemGroup *>((ItemGroup *)p_itemGroup);
0179     CircuitView *circuitView = dynamic_cast<CircuitView *>(itemView);
0180 
0181     if (cnItemGroup && circuitView) {
0182         bool canFlip = cnItemGroup->canFlip();
0183         circuitView->actionByName("edit_flip_horizontally")->setEnabled(canFlip);
0184         circuitView->actionByName("edit_flip_vertically")->setEnabled(canFlip);
0185 
0186         bool canRotate = cnItemGroup->canRotate();
0187         circuitView->actionByName("edit_rotate_ccw")->setEnabled(canRotate);
0188         circuitView->actionByName("edit_rotate_cw")->setEnabled(canRotate);
0189     }
0190 }
0191 
0192 void ItemInterface::setFlowPartOrientation(unsigned orientation)
0193 {
0194     CNItemGroup *cnItemGroup = dynamic_cast<CNItemGroup *>((ItemGroup *)p_itemGroup);
0195     if (!cnItemGroup)
0196         return;
0197 
0198     cnItemGroup->setFlowPartOrientation(orientation);
0199 }
0200 
0201 void ItemInterface::setComponentOrientation(int angleDegrees, bool flipped)
0202 {
0203     CNItemGroup *cnItemGroup = dynamic_cast<CNItemGroup *>((ItemGroup *)p_itemGroup);
0204     if (!cnItemGroup)
0205         return;
0206 
0207     cnItemGroup->setComponentOrientation(angleDegrees, flipped);
0208 }
0209 
0210 void ItemInterface::itemEditTBCleared()
0211 {
0212     m_stringLineEditMap.clear();
0213     m_stringComboBoxMap.clear();
0214     m_stringURLReqMap.clear();
0215     m_intSpinBoxMap.clear();
0216     m_doubleSpinBoxMap.clear();
0217     m_colorComboMap.clear();
0218     m_boolCheckMap.clear();
0219 }
0220 
0221 // The bool specifies whether advanced data should be shown
0222 QWidget *ItemInterface::configWidget()
0223 {
0224     if (!p_itemGroup || !p_itemGroup->activeItem() || !m_pActiveItemEditorToolBar)
0225         return nullptr;
0226 
0227     VariantDataMap *variantMap = p_itemGroup->activeItem()->variantMap();
0228 
0229     QWidget *parent = m_pActiveItemEditorToolBar;
0230 
0231     // Create new widget with the toolbar or dialog as the parent
0232     QWidget *configWidget = new QWidget(parent /*, "tbConfigWidget" */);
0233     configWidget->setObjectName("tbConfigWidget");
0234     {
0235         // 2018.12.02
0236         // configWidget->setSizePolicy( QSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding, 1, 1 ) );
0237         QSizePolicy p(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
0238         p.setHorizontalStretch(1);
0239         p.setVerticalStretch(1);
0240         configWidget->setSizePolicy(p);
0241     }
0242 
0243     QHBoxLayout *configLayout = new QHBoxLayout(configWidget);
0244     //  configLayout->setAutoAdd( true );
0245     configLayout->setSpacing(6);
0246     configLayout->setMargin(0);
0247 
0248     //  configLayout->addItem( new QSpacerItem( 0, 0,  QSizePolicy::Expanding, QSizePolicy::Fixed ) );
0249 
0250     const VariantDataMap::iterator vaEnd = variantMap->end();
0251     for (VariantDataMap::iterator vait = variantMap->begin(); vait != vaEnd; ++vait) {
0252         if (vait.value()->isHidden() || vait.value()->isAdvanced())
0253             continue;
0254 
0255         const Variant::Type::Value type = vait.value()->type();
0256 
0257         // common to all types apart from bool
0258         QString toolbarCaption = vait.value()->toolbarCaption();
0259         if (type != Variant::Type::Bool && !toolbarCaption.isEmpty())
0260             configLayout->addWidget(new QLabel(toolbarCaption, configWidget));
0261 
0262         QWidget *editWidget = nullptr; // Should be set to the created widget
0263 
0264         switch (type) {
0265         case Variant::Type::Port:
0266         case Variant::Type::Pin:
0267         case Variant::Type::VarName:
0268         case Variant::Type::Combo:
0269         case Variant::Type::Select:
0270         case Variant::Type::KeyPad:
0271         case Variant::Type::SevenSegment: {
0272             QString value = vait.value()->displayString();
0273             if (!value.isEmpty() && !vait.value()->allowed().contains(value))
0274                 vait.value()->appendAllowed(value);
0275 
0276             const QStringList allowed = vait.value()->allowed();
0277 
0278             KComboBox *box = new KComboBox(configWidget);
0279 
0280             box->insertItems(box->count(), allowed);
0281             box->setCurrentItem(value);
0282 
0283             if (type == Variant::Type::VarName || type == Variant::Type::Combo)
0284                 box->setEditable(true);
0285 
0286             m_stringComboBoxMap[vait.key()] = box;
0287             connectMapWidget(box, SIGNAL(editTextChanged(const QString &)));
0288             connectMapWidget(box, SIGNAL(activated(const QString &)));
0289 
0290             connect(*vait, SIGNAL(valueChangedStrAndTrue(const QString &, bool)), box, SLOT(setCurrentItem(const QString &, bool)));
0291 
0292             editWidget = box;
0293             break;
0294         }
0295         case Variant::Type::FileName: {
0296             qCDebug(KTL_LOG) << "create FileName";
0297             QString value = vait.value()->value().toString();
0298             if (!vait.value()->allowed().contains(value))
0299                 vait.value()->appendAllowed(value);
0300 
0301             const QStringList allowed = vait.value()->allowed();
0302 
0303             KUrlComboRequester *urlreq = new KUrlComboRequester(configWidget);
0304             urlreq->setFilter(vait.value()->filter());
0305             connectMapWidget(urlreq, SIGNAL(urlSelected(QUrl)));
0306             m_stringURLReqMap[vait.key()] = urlreq;
0307 
0308             KComboBox *box = urlreq->comboBox();
0309             box->insertItems(box->count(), allowed);
0310             box->setEditable(true);
0311 
0312             // Note this has to be called after inserting the allowed list
0313             urlreq->setUrl(QUrl::fromLocalFile(vait.value()->value().toString()));
0314 
0315             // Generally we only want a file name once the user has finished typing out the full file name.
0316             connectMapWidget(box, SIGNAL(returnPressed(const QString &)));
0317             connectMapWidget(box, SIGNAL(activated(const QString &)));
0318 
0319             connect(*vait, SIGNAL(valueChanged(const QString &)), box, SLOT(setEditText(const QString &)));
0320 
0321             editWidget = urlreq;
0322             break;
0323         }
0324         case Variant::Type::String: {
0325             KLineEdit *edit = new KLineEdit(configWidget);
0326 
0327             edit->setText(vait.value()->value().toString());
0328             connectMapWidget(edit, SIGNAL(textChanged(const QString &)));
0329             m_stringLineEditMap[vait.key()] = edit;
0330             editWidget = edit;
0331 
0332             connect(*vait, SIGNAL(valueChanged(const QString &)), edit, SLOT(setText(const QString &)));
0333 
0334             break;
0335         }
0336         case Variant::Type::Int: {
0337             QSpinBox *spin = new QSpinBox(configWidget);
0338             spin->setMinimum((int)vait.value()->minValue());
0339             spin->setMaximum((int)vait.value()->maxValue());
0340             spin->setValue(vait.value()->value().toInt());
0341 
0342             connectMapWidget(spin, SIGNAL(valueChanged(int)));
0343             m_intSpinBoxMap[vait.key()] = spin;
0344             editWidget = spin;
0345 
0346             connect(*vait, SIGNAL(valueChanged(int)), spin, SLOT(setValue(int)));
0347 
0348             break;
0349         }
0350         case Variant::Type::Double: {
0351             DoubleSpinBox *spin = new DoubleSpinBox(vait.value()->minValue(), vait.value()->maxValue(), vait.value()->minAbsValue(), vait.value()->value().toDouble(), vait.value()->unit(), configWidget);
0352 
0353             connectMapWidget(spin, SIGNAL(valueChanged(double)));
0354             m_doubleSpinBoxMap[vait.key()] = spin;
0355             editWidget = spin;
0356 
0357             connect(*vait, SIGNAL(valueChanged(double)), spin, SLOT(setValue(double)));
0358 
0359             break;
0360         }
0361         case Variant::Type::Color: {
0362             QColor value = vait.value()->value().value<QColor>();
0363 
0364             KColorCombo *colorBox = ColorUtils::createColorCombo((ColorUtils::ColorScheme)vait.value()->colorScheme(), configWidget);
0365 
0366             colorBox->setColor(value);
0367             connectMapWidget(colorBox, SIGNAL(activated(const QColor &)));
0368             m_colorComboMap[vait.key()] = colorBox;
0369 
0370             connect(*vait, SIGNAL(valueChanged(const QColor &)), colorBox, SLOT(setColor(const QColor &)));
0371 
0372             editWidget = colorBox;
0373             break;
0374         }
0375         case Variant::Type::Bool: {
0376             const bool value = vait.value()->value().toBool();
0377             QCheckBox *box = new QCheckBox(vait.value()->toolbarCaption(), configWidget);
0378 
0379             box->setChecked(value);
0380             connectMapWidget(box, SIGNAL(toggled(bool)));
0381             m_boolCheckMap[vait.key()] = box;
0382 
0383             connect(*vait, SIGNAL(valueChanged(bool)), box, SLOT(setChecked(bool)));
0384 
0385             editWidget = box;
0386             break;
0387         }
0388         case Variant::Type::Raw:
0389         case Variant::Type::PenStyle:
0390         case Variant::Type::PenCapStyle:
0391         case Variant::Type::Multiline:
0392         case Variant::Type::RichText:
0393         case Variant::Type::None: {
0394             // Do nothing, as these data types are not handled in the toolbar
0395             break;
0396         }
0397         }
0398 
0399         if (!editWidget)
0400             continue;
0401 
0402         const int widgetH = QFontMetrics(configWidget->font()).height() + 2;
0403         editWidget->setMinimumHeight(widgetH); // note: this is hack-ish; something is not ok with the layout
0404         editWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0405 
0406         // In the case of the toolbar, we don't want it too high
0407         if (editWidget->height() > parent->height() - 2)
0408             editWidget->setMaximumHeight(parent->height() - 2);
0409 
0410         switch (type) {
0411         case Variant::Type::VarName:
0412         case Variant::Type::Combo:
0413         case Variant::Type::String: {
0414             QSizePolicy p(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed /*, 1, 1 */);
0415             p.setHorizontalStretch(1);
0416             p.setVerticalStretch(1);
0417 
0418             editWidget->setSizePolicy(p);
0419             editWidget->setMaximumWidth(250);
0420             break;
0421         }
0422 
0423         case Variant::Type::FileName:
0424         case Variant::Type::Port:
0425         case Variant::Type::Pin:
0426         case Variant::Type::Select:
0427         case Variant::Type::KeyPad:
0428         case Variant::Type::SevenSegment:
0429         case Variant::Type::Int:
0430         case Variant::Type::Double:
0431         case Variant::Type::Color:
0432         case Variant::Type::Bool:
0433         case Variant::Type::Raw:
0434         case Variant::Type::PenStyle:
0435         case Variant::Type::PenCapStyle:
0436         case Variant::Type::Multiline:
0437         case Variant::Type::RichText:
0438         case Variant::Type::None:
0439             break;
0440         }
0441 
0442         configLayout->addWidget(editWidget);
0443     }
0444 
0445     configLayout->addItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Fixed));
0446 
0447     return configWidget;
0448 }
0449 
0450 void ItemInterface::connectMapWidget(QWidget *widget, const char *_signal)
0451 {
0452     connect(widget, _signal, this, SLOT(tbDataChanged()));
0453 }
0454 
0455 // TODO move to separate file
0456 struct BoolLock {
0457     bool *m_flagPtr;
0458     BoolLock(bool *flagPtr)
0459         : m_flagPtr(flagPtr)
0460     {
0461         if (m_flagPtr == nullptr) {
0462             qCCritical(KTL_LOG) << "nullptr flagPtr";
0463             return;
0464         }
0465         if (*m_flagPtr == true) {
0466             qCWarning(KTL_LOG) << "flag expected to be false, addr=" << m_flagPtr << " Doing nothing";
0467             m_flagPtr = nullptr;
0468         } else {
0469             *m_flagPtr = true;
0470         }
0471     }
0472     ~BoolLock()
0473     {
0474         if (m_flagPtr != nullptr) {
0475             *m_flagPtr = false;
0476         }
0477     }
0478 };
0479 
0480 void ItemInterface::tbDataChanged()
0481 {
0482     qCDebug(KTL_LOG) << "begin";
0483     if (m_isInTbDataChanged) {
0484         qCDebug(KTL_LOG) << "avoiding recursion, returning";
0485         return;
0486     }
0487     BoolLock inTbChangedLock(&m_isInTbDataChanged);
0488     // Manual string values
0489     const KLineEditMap::iterator m_stringLineEditMapEnd = m_stringLineEditMap.end();
0490     for (KLineEditMap::iterator leit = m_stringLineEditMap.begin(); leit != m_stringLineEditMapEnd; ++leit) {
0491         slotSetData(leit.key(), leit.value()->text());
0492     }
0493 
0494     // String values from comboboxes
0495     const KComboBoxMap::iterator m_stringComboBoxMapEnd = m_stringComboBoxMap.end();
0496     for (KComboBoxMap::iterator cmit = m_stringComboBoxMap.begin(); cmit != m_stringComboBoxMapEnd; ++cmit) {
0497         qCDebug(KTL_LOG) << "set KCombo data for " << cmit.key() << " to " << cmit.value()->currentText();
0498         slotSetData(cmit.key(), cmit.value()->currentText());
0499     }
0500 
0501     // Colors values from colorcombos
0502     const KColorComboMap::iterator m_colorComboMapEnd = m_colorComboMap.end();
0503     for (KColorComboMap::iterator ccit = m_colorComboMap.begin(); ccit != m_colorComboMapEnd; ++ccit) {
0504         slotSetData(ccit.key(), ccit.value()->color());
0505     }
0506 
0507     // Bool values from checkboxes
0508     const QCheckBoxMap::iterator m_boolCheckMapEnd = m_boolCheckMap.end();
0509     for (QCheckBoxMap::iterator chit = m_boolCheckMap.begin(); chit != m_boolCheckMapEnd; ++chit) {
0510         slotSetData(chit.key(), chit.value()->isChecked());
0511     }
0512 
0513     const IntSpinBoxMap::iterator m_intSpinBoxMapEnd = m_intSpinBoxMap.end();
0514     for (IntSpinBoxMap::iterator it = m_intSpinBoxMap.begin(); it != m_intSpinBoxMapEnd; ++it) {
0515         slotSetData(it.key(), it.value()->value());
0516     }
0517 
0518     // (?) Combined values from spin boxes and combo boxes
0519     // (?) Get values from all spin boxes
0520 
0521     const DoubleSpinBoxMap::iterator m_doubleSpinBoxMapEnd = m_doubleSpinBoxMap.end();
0522     for (DoubleSpinBoxMap::iterator sbit = m_doubleSpinBoxMap.begin(); sbit != m_doubleSpinBoxMapEnd; ++sbit) {
0523         //      VariantDataMap::iterator vait = variantData.find(sbit.key());
0524         slotSetData(sbit.key(), sbit.value()->value());
0525     }
0526 
0527     // Filenames from KUrlRequesters
0528     const KUrlReqMap::iterator m_stringURLReqMapEnd = m_stringURLReqMap.end();
0529     for (KUrlReqMap::iterator urlit = m_stringURLReqMap.begin(); urlit != m_stringURLReqMapEnd; ++urlit) {
0530         qCDebug(KTL_LOG) << "set kurlrequester data for " << urlit.key() << " to " << urlit.value()->url();
0531         QVariant urlVar(urlit.value()->url().path());
0532         qCDebug(KTL_LOG) << "urlVar=" << urlVar << " urlVar.toUrl=" << urlVar.toUrl();
0533         slotSetData(urlit.key(), urlVar);
0534     }
0535 
0536     if (p_cvb)
0537         p_cvb->setModified(true);
0538 }
0539 
0540 void ItemInterface::setProperty(Variant *v)
0541 {
0542     slotSetData(v->id(), v->value());
0543 }
0544 
0545 void ItemInterface::slotSetData(const QString &id, QVariant value)
0546 {
0547     if (!p_itemGroup || (p_itemGroup->itemCount() == 0)) {
0548         qCDebug(KTL_LOG) << "p_itemGroup not valid:" << p_itemGroup;
0549         return;
0550     }
0551 
0552     if (!p_itemGroup->itemsAreSameType()) {
0553         qCDebug(KTL_LOG) << "Items are not the same type!";
0554         return;
0555     }
0556     qCDebug(KTL_LOG) << "id=" << id << " value=" << value;
0557 
0558     const ItemList itemList = p_itemGroup->items(true);
0559     const ItemList::const_iterator end = itemList.end();
0560     for (ItemList::const_iterator it = itemList.begin(); it != end; ++it) {
0561         if (*it)
0562             (*it)->property(id)->setValue(value);
0563     }
0564 
0565     if (p_cvb)
0566         p_cvb->setModified(true);
0567 
0568     ItemEditor::self()->itemGroupUpdated(p_itemGroup);
0569 
0570     if (p_cvb)
0571         p_cvb->requestStateSave(m_currentActionTicket);
0572 }