File indexing completed on 2024-12-01 11:20:48

0001 /***************************************************************************
0002  *   Copyright (C) 2004-2006 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 "itemdocument.h"
0012 #include "itemdocumentdata.h"
0013 #include "ktechlab.h"
0014 #include "richtexteditor.h"
0015 
0016 #include <KStandardGuiItem>
0017 #include <KTextEdit>
0018 #include <cmath>
0019 
0020 #include <QBitArray>
0021 #include <QDialog>
0022 #include <QDialogButtonBox>
0023 #include <QPushButton>
0024 #include <QTimer>
0025 #include <QVBoxLayout>
0026 
0027 #include <ktlconfig.h>
0028 #include <ktechlab_debug.h>
0029 
0030 const int minPrefixExp = -24;
0031 const int maxPrefixExp = 24;
0032 const int numPrefix = int((maxPrefixExp - minPrefixExp) / 3) + 1;
0033 const QString SIprefix[] = {"y", "z", "a", "f", "p", "n", QChar(0xB5), "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y"};
0034 
0035 Item::Item(ItemDocument *itemDocument, bool newItem, const QString &id)
0036     : // QObject(),
0037     KtlQCanvasPolygon(itemDocument ? itemDocument->canvas() : nullptr)
0038 {
0039     QString name(QString("Item-%1").arg(id));
0040     setObjectName(name.toLatin1().data());
0041     qCDebug(KTL_LOG) << " this=" << this;
0042 
0043     m_bDynamicContent = false;
0044     m_bIsRaised = false;
0045     m_bDoneCreation = false;
0046     p_parentItem = nullptr;
0047     b_deleted = false;
0048     p_itemDocument = itemDocument;
0049     m_baseZ = -1;
0050 
0051     if (p_itemDocument) {
0052         if (newItem)
0053             m_id = p_itemDocument->generateUID(id);
0054         else {
0055             m_id = id;
0056             p_itemDocument->registerUID(id);
0057         }
0058     }
0059 
0060     m_pPropertyChangedTimer = new QTimer(this);
0061     connect(m_pPropertyChangedTimer, &QTimer::timeout, this, &Item::dataChanged);
0062 }
0063 
0064 Item::~Item()
0065 {
0066     if (p_itemDocument) {
0067         p_itemDocument->requestEvent(ItemDocument::ItemDocumentEvent::ResizeCanvasToItems);
0068         p_itemDocument->unregisterUID(id());
0069     }
0070 
0071     KtlQCanvasPolygon::hide();
0072 
0073     qDeleteAll(m_variantData);
0074     m_variantData.clear();
0075 }
0076 
0077 void Item::removeItem()
0078 {
0079     if (b_deleted)
0080         return;
0081     b_deleted = true;
0082 
0083     hide();
0084     setCanvas(nullptr);
0085     emit removed(this);
0086     p_itemDocument->appendDeleteList(this);
0087 }
0088 
0089 QFont Item::font() const
0090 {
0091     if (KTechlab::self())
0092         return KTechlab::self()->itemFont();
0093     else
0094         return QFont();
0095 }
0096 
0097 void Item::moveBy(double dx, double dy)
0098 {
0099     KtlQCanvasPolygon::moveBy(dx, dy);
0100     emit movedBy(dx, dy);
0101 }
0102 
0103 void Item::setChanged()
0104 {
0105     if (b_deleted)
0106         return;
0107 
0108     if (canvas())
0109         canvas()->setChanged(boundingRect());
0110 }
0111 
0112 void Item::setItemPoints(const QPolygon &pa, bool setSizeFromPoints)
0113 {
0114     m_itemPoints = pa;
0115     if (setSizeFromPoints)
0116         setSize(m_itemPoints.boundingRect());
0117     itemPointsChanged();
0118 }
0119 
0120 void Item::itemPointsChanged()
0121 {
0122     setPoints(m_itemPoints);
0123 }
0124 
0125 void Item::setSize(QRect sizeRect, bool forceItemPoints)
0126 {
0127     if (!canvas())
0128         return;
0129 
0130     if (m_sizeRect == sizeRect && !forceItemPoints)
0131         return;
0132 
0133     if (!preResize(sizeRect))
0134         return;
0135 
0136     canvas()->setChanged(areaPoints().boundingRect());
0137     m_sizeRect = sizeRect;
0138     if (m_itemPoints.isEmpty() || forceItemPoints) {
0139         setItemPoints(QPolygon(m_sizeRect), false);
0140     }
0141     canvas()->setChanged(areaPoints().boundingRect());
0142     postResize();
0143     emit resized();
0144 }
0145 
0146 ItemData Item::itemData() const
0147 {
0148     ItemData itemData;
0149 
0150     itemData.type = m_type;
0151     itemData.x = x();
0152     itemData.y = y();
0153 
0154     if (!parentItem())
0155         itemData.z = m_baseZ;
0156 
0157     itemData.size = m_sizeRect;
0158     itemData.setSize = canResize();
0159 
0160     if (p_parentItem)
0161         itemData.parentId = p_parentItem->id();
0162 
0163     const VariantDataMap::const_iterator end = m_variantData.end();
0164     for (VariantDataMap::const_iterator it = m_variantData.begin(); it != end; ++it) {
0165         switch (it.value()->type()) {
0166         case Variant::Type::String:
0167         case Variant::Type::FileName:
0168         case Variant::Type::Port:
0169         case Variant::Type::Pin:
0170         case Variant::Type::VarName:
0171         case Variant::Type::Combo:
0172         case Variant::Type::Select:
0173         case Variant::Type::Multiline:
0174         case Variant::Type::RichText:
0175         case Variant::Type::SevenSegment:
0176         case Variant::Type::KeyPad: {
0177             itemData.dataString[it.key()] = it.value()->value().toString();
0178             break;
0179         }
0180         case Variant::Type::Int:
0181         case Variant::Type::Double: {
0182             itemData.dataNumber[it.key()] = it.value()->value().toDouble();
0183             break;
0184         }
0185         case Variant::Type::Color: {
0186             itemData.dataColor[it.key()] = it.value()->value().value<QColor>();
0187             break;
0188         }
0189         case Variant::Type::Bool: {
0190             itemData.dataBool[it.key()] = it.value()->value().toBool();
0191             break;
0192         }
0193         case Variant::Type::Raw: {
0194             itemData.dataRaw[it.key()] = it.value()->value().toBitArray();
0195             break;
0196         }
0197         case Variant::Type::PenStyle:
0198         case Variant::Type::PenCapStyle: {
0199             // These types are only created from DrawPart, and that class
0200             // deals with these, so we can ignore them
0201             break;
0202         }
0203         case Variant::Type::None: {
0204             // ? Maybe obsoleted data...
0205             break;
0206         }
0207         }
0208     }
0209 
0210     return itemData;
0211 }
0212 
0213 void Item::restoreFromItemData(const ItemData &itemData)
0214 {
0215     move(itemData.x, itemData.y);
0216     if (canResize())
0217         setSize(itemData.size);
0218 
0219     Item *parentItem = p_itemDocument->itemWithID(itemData.parentId);
0220     if (parentItem)
0221         setParentItem(parentItem);
0222     else
0223         m_baseZ = itemData.z;
0224 
0225     // BEGIN Restore data
0226     const QStringMap::const_iterator stringEnd = itemData.dataString.end();
0227     for (QStringMap::const_iterator it = itemData.dataString.begin(); it != stringEnd; ++it) {
0228         if (hasProperty(it.key()))
0229             property(it.key())->setValue(it.value());
0230     }
0231 
0232     const DoubleMap::const_iterator numberEnd = itemData.dataNumber.end();
0233     for (DoubleMap::const_iterator it = itemData.dataNumber.begin(); it != numberEnd; ++it) {
0234         if (hasProperty(it.key()))
0235             property(it.key())->setValue(it.value());
0236     }
0237 
0238     const QColorMap::const_iterator colorEnd = itemData.dataColor.end();
0239     for (QColorMap::const_iterator it = itemData.dataColor.begin(); it != colorEnd; ++it) {
0240         if (hasProperty(it.key()))
0241             property(it.key())->setValue(it.value());
0242     }
0243 
0244     const BoolMap::const_iterator boolEnd = itemData.dataBool.end();
0245     for (BoolMap::const_iterator it = itemData.dataBool.begin(); it != boolEnd; ++it) {
0246         if (hasProperty(it.key()))
0247             property(it.key())->setValue(QVariant(it.value() /*, 0*/));
0248     }
0249 
0250     const QBitArrayMap::const_iterator rawEnd = itemData.dataRaw.end();
0251     for (QBitArrayMap::const_iterator it = itemData.dataRaw.begin(); it != rawEnd; ++it) {
0252         if (hasProperty(it.key()))
0253             property(it.key())->setValue(it.value());
0254     }
0255     // END Restore Data
0256 }
0257 
0258 bool Item::mousePressEvent(const EventInfo &eventInfo)
0259 {
0260     Q_UNUSED(eventInfo);
0261     return false;
0262 }
0263 bool Item::mouseReleaseEvent(const EventInfo &eventInfo)
0264 {
0265     Q_UNUSED(eventInfo);
0266     return false;
0267 }
0268 bool Item::mouseMoveEvent(const EventInfo &eventInfo)
0269 {
0270     Q_UNUSED(eventInfo);
0271     return false;
0272 }
0273 bool Item::wheelEvent(const EventInfo &eventInfo)
0274 {
0275     Q_UNUSED(eventInfo);
0276     return false;
0277 }
0278 void Item::enterEvent(QEvent *)
0279 {
0280 }
0281 void Item::leaveEvent(QEvent *)
0282 {
0283 }
0284 
0285 bool Item::mouseDoubleClickEvent(const EventInfo &eventInfo)
0286 {
0287     Q_UNUSED(eventInfo);
0288 
0289     Property *property = nullptr;
0290     Variant::Type::Value type = Variant::Type::None;
0291 
0292     const VariantDataMap::iterator variantDataEnd = m_variantData.end();
0293     for (VariantDataMap::iterator it = m_variantData.begin(); it != variantDataEnd; ++it) {
0294         Property *current = *it;
0295 
0296         if (current->type() == Variant::Type::Multiline || current->type() == Variant::Type::RichText) {
0297             property = current;
0298             type = current->type();
0299             break;
0300         }
0301     }
0302     if (!property)
0303         return false;
0304 
0305     if (type == Variant::Type::Multiline) {
0306         QDialog *dlg = new QDialog(nullptr);
0307         dlg->setModal(true);
0308         dlg->setWindowTitle(property->editorCaption());
0309         QVBoxLayout *mainLayout = new QVBoxLayout;
0310         dlg->setLayout(mainLayout);
0311 
0312         // QFrame *frame = dlg->makeMainWidget();
0313         QFrame *frame = new QFrame(dlg);
0314         mainLayout->addWidget(frame);
0315         QVBoxLayout *layout = new QVBoxLayout(frame);
0316         layout->setMargin(0);
0317         KTextEdit *textEdit = new KTextEdit(frame);
0318         // textEdit->setTextFormat( Qt::PlainText ); // 2018.12.02
0319         textEdit->setAcceptRichText(false);
0320         textEdit->setText(property->value().toString());
0321         layout->addWidget(textEdit, 10);
0322         textEdit->setFocus();
0323         QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0324         QPushButton *clearButton = new QPushButton(buttonBox);
0325         KGuiItem::assign(clearButton, KStandardGuiItem::clear());
0326         buttonBox->addButton(clearButton, QDialogButtonBox::ActionRole);
0327         mainLayout->addWidget(buttonBox);
0328         QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
0329         okButton->setDefault(true);
0330         okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
0331         connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept);
0332         connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject);
0333         connect(clearButton, &QPushButton::clicked, textEdit, &KTextEdit::clear);
0334         dlg->setMinimumWidth(600);
0335 
0336         if (dlg->exec() == QDialog::Accepted) {
0337             property->setValue(textEdit->toPlainText());
0338             dataChanged();
0339             p_itemDocument->setModified(true);
0340         }
0341         delete dlg;
0342     } else {
0343         // Is rich text
0344         RichTextEditorDlg *dlg = new RichTextEditorDlg(nullptr, property->editorCaption());
0345         dlg->setText(property->value().toString());
0346 
0347         if (dlg->exec() == QDialog::Accepted) {
0348             property->setValue(dlg->text());
0349             dataChanged();
0350             p_itemDocument->setModified(true);
0351         }
0352         delete dlg;
0353     }
0354 
0355     return true;
0356 }
0357 
0358 void Item::setSelected(bool yes)
0359 {
0360     if (isSelected() == yes)
0361         return;
0362     KtlQCanvasPolygon::setSelected(yes);
0363     emit selectionChanged();
0364 }
0365 
0366 void Item::setParentItem(Item *newParentItem)
0367 {
0368     //  qCDebug(KTL_LOG) << "this = "<<this<<" newParentItem = "<<newParentItem;
0369     if (newParentItem == p_parentItem)
0370         return;
0371 
0372     Item *oldParentItem = p_parentItem;
0373 
0374     if (oldParentItem) {
0375         disconnect(oldParentItem, &Item::removed, this, &Item::removeItem);
0376         oldParentItem->removeChild(this);
0377     }
0378 
0379     if (newParentItem) {
0380         if (newParentItem->contains(this))
0381             ;
0382         //          qCCritical(KTL_LOG) << "Already a child of " << newParentItem;
0383         else {
0384             connect(newParentItem, &Item::removed, this, &Item::removeItem);
0385             newParentItem->addChild(this);
0386         }
0387     }
0388 
0389     p_parentItem = newParentItem;
0390     (void)level();
0391     reparented(oldParentItem, newParentItem);
0392     p_itemDocument->slotUpdateZOrdering();
0393 }
0394 
0395 int Item::level() const
0396 {
0397     return p_parentItem ? p_parentItem->level() + 1 : 0;
0398 }
0399 
0400 ItemList Item::children(bool includeGrandChildren) const
0401 {
0402     if (!includeGrandChildren)
0403         return m_children;
0404 
0405     ItemList children = m_children;
0406     ItemList::const_iterator end = m_children.end();
0407     for (ItemList::const_iterator it = m_children.begin(); it != end; ++it) {
0408         if (!*it)
0409             continue;
0410 
0411         children += (*it)->children(true);
0412     }
0413 
0414     return children;
0415 }
0416 
0417 void Item::addChild(Item *child)
0418 {
0419     if (!child)
0420         return;
0421 
0422     if (child->contains(this)) {
0423         //      qCCritical(KTL_LOG) << "Attempting to add a child to this item that is already a parent of this item. Incest results in stack overflow.";
0424         return;
0425     }
0426 
0427     if (contains(child, true)) {
0428         //      qCCritical(KTL_LOG) << "Already have child " << child;
0429         return;
0430     }
0431 
0432     m_children.append(child);
0433     connect(child, &Item::removed, this, &Item::removeChild);
0434 
0435     child->setParentItem(this);
0436     childAdded(child);
0437     p_itemDocument->slotUpdateZOrdering();
0438 }
0439 
0440 void Item::removeChild(Item *child)
0441 {
0442     if (!child || !m_children.contains(child))
0443         return;
0444 
0445     m_children.removeAll(child);
0446     disconnect(child, &Item::removed, this, &Item::removeChild);
0447 
0448     childRemoved(child);
0449     p_itemDocument->slotUpdateZOrdering();
0450 }
0451 
0452 bool Item::contains(Item *item, bool direct) const
0453 {
0454     const ItemList::const_iterator end = m_children.end();
0455     for (ItemList::const_iterator it = m_children.begin(); it != end; ++it) {
0456         if (static_cast<Item *>(*it) == item || (!direct && (*it)->contains(item, false)))
0457             return true;
0458     }
0459     return false;
0460 }
0461 
0462 void Item::setRaised(bool isRaised)
0463 {
0464     m_bIsRaised = isRaised;
0465     // We'll get called later to update our Z
0466 }
0467 
0468 void Item::updateZ(int baseZ)
0469 {
0470     m_baseZ = baseZ;
0471     double z = ItemDocument::Z::Item + (ItemDocument::Z::DeltaItem)*baseZ;
0472 
0473     if (isRaised())
0474         z += ItemDocument::Z::RaisedItem - ItemDocument::Z::Item;
0475 
0476     setZ(z);
0477 
0478     const ItemList::const_iterator end = m_children.end();
0479     for (ItemList::const_iterator it = m_children.begin(); it != end; ++it) {
0480         if (*it)
0481             (*it)->updateZ(baseZ + 1);
0482     }
0483 }
0484 
0485 int Item::getNumberPre(double num)
0486 {
0487     return int(num / getMultiplier(num));
0488 }
0489 
0490 QString Item::getNumberMag(double num)
0491 {
0492     if (num == 0.)
0493         return "";
0494     const double exp_n = std::log10(std::abs(num));
0495     if (exp_n < minPrefixExp + 3)
0496         return SIprefix[0];
0497     else if (exp_n >= maxPrefixExp)
0498         return SIprefix[numPrefix - 1];
0499     else
0500         return SIprefix[int(std::floor(double(exp_n / 3))) - int(floor(double(minPrefixExp / 3)))];
0501 }
0502 
0503 double Item::getMultiplier(double num)
0504 {
0505     if (num == 0.)
0506         return 1.;
0507     else
0508         return std::pow(10, 3 * std::floor(std::log10(std::abs(num)) / 3));
0509 }
0510 
0511 double Item::getMultiplier(const QString &_mag)
0512 {
0513     QString mag;
0514     // Allow the user to enter in "u" instead of mu, as unfortunately many keyboards don't have the mu key
0515     if (_mag == "u")
0516         mag = QChar(0xB5);
0517     else
0518         mag = _mag;
0519 
0520     for (int i = 0; i < numPrefix; ++i) {
0521         if (mag == SIprefix[i]) {
0522             return std::pow(10., (i * 3) + minPrefixExp);
0523         }
0524     }
0525 
0526     // I think it is safer to return '1' if the unit is unknown
0527     return 1.;
0528     //  return pow( 10., maxPrefixExp+3. );
0529 }
0530 
0531 // BEGIN Data stuff
0532 double Item::dataDouble(const QString &id) const
0533 {
0534     Variant *variant = property(id);
0535     return variant ? variant->value().toDouble() : 0.0;
0536 }
0537 
0538 int Item::dataInt(const QString &id) const
0539 {
0540     Variant *variant = property(id);
0541     return variant ? variant->value().toInt() : 0;
0542 }
0543 
0544 bool Item::dataBool(const QString &id) const
0545 {
0546     Variant *variant = property(id);
0547     return variant ? variant->value().toBool() : false;
0548 }
0549 
0550 QString Item::dataString(const QString &id) const
0551 {
0552     Variant *variant = property(id);
0553     return variant ? variant->value().toString() : QString();
0554 }
0555 
0556 QColor Item::dataColor(const QString &id) const
0557 {
0558     Variant *variant = property(id);
0559     return variant ? variant->value().value<QColor>() : Qt::black;
0560 }
0561 
0562 Variant *Item::createProperty(const QString &id, Variant::Type::Value type)
0563 {
0564     if (!m_variantData.contains(id)) {
0565         m_variantData[id] = new Variant(id, type);
0566         connect(m_variantData[id], qOverload<QVariant, QVariant>(&Variant::valueChanged), this, &Item::propertyChangedInitial);
0567     }
0568 
0569     return m_variantData[id];
0570 }
0571 
0572 Variant *Item::property(const QString &id) const
0573 {
0574     if (m_variantData.contains(id))
0575         return m_variantData[id];
0576 
0577     qCCritical(KTL_LOG) << " No such property with id " << id;
0578     return nullptr;
0579 }
0580 
0581 bool Item::hasProperty(const QString &id) const
0582 {
0583     return m_variantData.contains(id);
0584 }
0585 
0586 void Item::finishedCreation()
0587 {
0588     m_bDoneCreation = true;
0589     dataChanged();
0590 }
0591 
0592 void Item::propertyChangedInitial()
0593 {
0594     if (!m_bDoneCreation)
0595         return;
0596 
0597     m_pPropertyChangedTimer->setSingleShot(true);
0598     m_pPropertyChangedTimer->start(0 /*, true */);
0599 }
0600 // END Data stuff
0601 
0602 #include "moc_item.cpp"