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"