File indexing completed on 2025-02-02 09:10:28
0001 /*************************************************************************** 0002 * Copyright (C) 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 "canvasitemparts.h" 0012 #include "canvasmanipulator.h" 0013 #include "cells.h" 0014 #include "circuitdocument.h" 0015 #include "cnitem.h" 0016 #include "connector.h" 0017 #include "drawpart.h" 0018 #include "ecnode.h" 0019 #include "flowcodedocument.h" 0020 #include "icnview.h" 0021 #include "imageexportdlg.h" 0022 #include "itemdocumentdata.h" 0023 #include "itemgroup.h" 0024 #include "itemselector.h" 0025 #include "ktechlab.h" 0026 #include "pin.h" 0027 #include "resizeoverlay.h" 0028 #include "simulator.h" 0029 0030 #include <KLocalizedString> 0031 #include <KMessageBox> 0032 // #include <k3popupmenu.h> 0033 //#include <kprinter.h> 0034 #include <KActionMenu> 0035 #include <KXMLGUIFactory> 0036 0037 #include <QApplication> 0038 #include <QCheckBox> 0039 #include <QClipboard> 0040 #include <QCursor> 0041 #include <QImage> 0042 #include <QMenu> 0043 // #include <q3paintdevicemetrics.h> 0044 #include <QPainter> 0045 #include <QPicture> 0046 #include <QRegExp> 0047 // #include <q3simplerichtext.h> // 2018.08.13 - not needed 0048 #include <QFile> 0049 #include <QPrintDialog> 0050 #include <QPrinter> 0051 #include <QTextEdit> 0052 #include <QTimer> 0053 0054 #include <cassert> 0055 #include <cmath> 0056 0057 #include <ktlconfig.h> 0058 #include <ktechlab_debug.h> 0059 0060 // BEGIN class ItemDocument 0061 int ItemDocument::m_nextActionTicket = 0; 0062 0063 ItemDocument::ItemDocument(const QString &caption) 0064 : Document(caption) 0065 { 0066 m_queuedEvents = 0; 0067 m_nextIdNum = 1; 0068 m_savedState = nullptr; 0069 m_currentState = nullptr; 0070 m_bIsLoading = false; 0071 0072 m_canvas = new Canvas(this); 0073 m_canvas->setObjectName("canvas"); 0074 m_canvasTip = new CanvasTip(this, m_canvas); 0075 m_cmManager = new CMManager(this); 0076 0077 updateBackground(); 0078 0079 m_pUpdateItemViewScrollbarsTimer = new QTimer(this); 0080 connect(m_pUpdateItemViewScrollbarsTimer, &QTimer::timeout, this, &ItemDocument::updateItemViewScrollbars); 0081 0082 m_pEventTimer = new QTimer(this); 0083 connect(m_pEventTimer, &QTimer::timeout, this, &ItemDocument::processItemDocumentEvents); 0084 0085 connect(this, &ItemDocument::selectionChanged, this, &ItemDocument::slotInitItemActions); 0086 0087 connect(ComponentSelector::self(), qOverload<const QString &>(&ComponentSelector::itemClicked), this, &ItemDocument::slotUnsetRepeatedItemId); 0088 connect(FlowPartSelector::self(), qOverload<const QString &>(&FlowPartSelector::itemClicked), this, &ItemDocument::slotUnsetRepeatedItemId); 0089 0090 #ifdef MECHANICS 0091 connect(MechanicsSelector::self(), SIGNAL(itemClicked(const QString &)), this, SLOT(slotUnsetRepeatedItemId())); 0092 #endif 0093 0094 m_pAlignmentAction = new KActionMenu(i18n("Alignment") /*, "format-justify-right" */, this); 0095 m_pAlignmentAction->setObjectName("rightjust"); 0096 m_pAlignmentAction->setIcon(QIcon::fromTheme("format-justify-right")); 0097 0098 slotUpdateConfiguration(); 0099 } 0100 0101 ItemDocument::~ItemDocument() 0102 { 0103 m_bDeleted = true; 0104 0105 // ItemMap toDelete = m_itemList; 0106 0107 const ItemMap::iterator end = m_itemList.end(); 0108 for (ItemMap::iterator it = m_itemList.begin(); it != end; ++it) { 0109 qCDebug(KTL_LOG) << "ItemDocument::~ItemDocument: deleting [" << it.key() << "] " << it.value(); 0110 // delete *it; // 2015.07.31 - this will crash 0111 it.value()->deleteLater(); 0112 } 0113 m_itemList.clear(); 0114 0115 cleanClearStack(m_undoStack); 0116 cleanClearStack(m_redoStack); 0117 0118 delete m_cmManager; 0119 delete m_currentState; 0120 delete m_canvasTip; 0121 } 0122 0123 void ItemDocument::handleNewView(View *view) 0124 { 0125 Document::handleNewView(view); 0126 requestEvent(ItemDocument::ItemDocumentEvent::ResizeCanvasToItems); 0127 } 0128 0129 bool ItemDocument::registerItem(KtlQCanvasItem *qcanvasItem) 0130 { 0131 if (!qcanvasItem) 0132 return false; 0133 0134 requestEvent(ItemDocument::ItemDocumentEvent::ResizeCanvasToItems); 0135 0136 if (Item *item = dynamic_cast<Item *>(qcanvasItem)) { 0137 m_itemList[item->id()] = item; 0138 connect(item, &Item::selectionChanged, this, &ItemDocument::selectionChanged); 0139 itemAdded(item); 0140 return true; 0141 } 0142 0143 return false; 0144 } 0145 0146 void ItemDocument::slotSetDrawAction(QAction *selected) 0147 { 0148 int drawAction = selected->data().toInt(); 0149 m_cmManager->setDrawAction(drawAction); 0150 } 0151 0152 void ItemDocument::cancelCurrentOperation() 0153 { 0154 m_cmManager->cancelCurrentManipulation(); 0155 } 0156 0157 void ItemDocument::slotSetRepeatedItemId(const QString &id) 0158 { 0159 m_cmManager->setCMState(CMManager::cms_repeated_add, true); 0160 m_cmManager->setRepeatedAddId(id); 0161 } 0162 0163 void ItemDocument::slotUnsetRepeatedItemId() 0164 { 0165 m_cmManager->setCMState(CMManager::cms_repeated_add, false); 0166 } 0167 0168 void ItemDocument::fileSave() 0169 { 0170 if (url().isEmpty() && !getURL(m_fileExtensionInfo, m_fileExtensionValue)) 0171 return; 0172 writeFile(); 0173 } 0174 0175 void ItemDocument::fileSaveAs() 0176 { 0177 if (!getURL(m_fileExtensionInfo, m_fileExtensionValue)) 0178 return; 0179 writeFile(); 0180 0181 // Our modified state may not have changed, but we emit this to force the 0182 // main window to update our caption. 0183 emit modifiedStateChanged(); 0184 } 0185 0186 void ItemDocument::writeFile() 0187 { 0188 ItemDocumentData data(type()); 0189 data.saveDocumentState(this); 0190 0191 if (data.saveData(url())) { 0192 m_savedState = m_currentState; 0193 setModified(false); 0194 } 0195 } 0196 0197 bool ItemDocument::openURL(const QUrl &url) 0198 { 0199 ItemDocumentData data(type()); 0200 0201 if (!data.loadData(url)) 0202 return false; 0203 0204 // Why do we stop simulating while loading a document? 0205 // Crash possible when loading a circuit document, and the Qt event loop is 0206 // reentered (such as when a PIC component pops-up a message box), which 0207 // will then call the Simulator::step function, which might use components 0208 // that have not fully initialized themselves. 0209 0210 m_bIsLoading = true; 0211 bool wasSimulating = Simulator::self()->isSimulating(); 0212 Simulator::self()->slotSetSimulating(false); 0213 data.restoreDocument(this); 0214 Simulator::self()->slotSetSimulating(wasSimulating); 0215 m_bIsLoading = false; 0216 0217 setURL(url); 0218 clearHistory(); 0219 m_savedState = m_currentState; 0220 setModified(false); 0221 0222 if (FlowCodeDocument *fcd = dynamic_cast<FlowCodeDocument *>(this)) { 0223 // We need to tell all pic-depedent components about what pic type is in use 0224 emit fcd->picTypeChanged(); 0225 } 0226 0227 requestEvent(ItemDocument::ItemDocumentEvent::ResizeCanvasToItems); 0228 0229 // Load Z-position info 0230 m_zOrder.clear(); 0231 ItemMap::iterator end = m_itemList.end(); 0232 for (ItemMap::iterator it = m_itemList.begin(); it != end; ++it) { 0233 if (!*it || (*it)->parentItem()) 0234 continue; 0235 0236 m_zOrder[(*it)->baseZ()] = *it; 0237 } 0238 slotUpdateZOrdering(); 0239 0240 return true; 0241 } 0242 0243 void ItemDocument::print() 0244 { 0245 static QPrinter *printer = new QPrinter; 0246 0247 // if ( ! printer->setup( KTechlab::self() ) ) 0248 // return; 0249 QPrintDialog printDialog(printer, KTechlab::self()); 0250 if (!printDialog.exec()) { 0251 return; 0252 } 0253 0254 // setup the printer. with Qt, you always "print" to a 0255 // QPainter.. whether the output medium is a pixmap, a screen, 0256 // or paper 0257 QPainter p; 0258 p.begin(printer); 0259 0260 // we let our view do the actual printing 0261 // Q3PaintDeviceMetrics metrics( printer ); // 2018.08.13 - replaced with method call 0262 QRect pageRect = printer->pageLayout().paintRectPixels(printer->resolution()); 0263 0264 // Round to 16 so that we cut in the middle of squares 0265 int w = pageRect.width(); 0266 w = (w & 0xFFFFFFF0) + ((w << 1) & 0x10); 0267 0268 int h = pageRect.height(); 0269 h = (h & 0xFFFFFFF0) + ((h << 1) & 0x10); 0270 0271 p.setClipping(true); 0272 p.setClipRect(0, 0, w, h, /* QPainter::CoordPainter */ Qt::ReplaceClip); // TODO is this correct? 0273 0274 // Send off the painter for drawing 0275 // note: What was this doing?? // set "null" background, so the background horiznotal and vertial lines are not visible 0276 m_canvas->setBackgroundPixmap(QPixmap(0, 0) /* 0 */); 0277 0278 QRect bounding = canvasBoundingRect(); 0279 unsigned int rows = unsigned(std::ceil(double(bounding.height()) / double(h))); 0280 unsigned int cols = unsigned(std::ceil(double(bounding.width()) / double(w))); 0281 int offset_x = bounding.x(); 0282 int offset_y = bounding.y(); 0283 0284 for (unsigned row = 0; row < rows; ++row) { 0285 for (unsigned col = 0; col < cols; ++col) { 0286 if (row != 0 || col != 0) 0287 printer->newPage(); 0288 0289 QRect drawArea(offset_x + (col * w), offset_y + (row * h), w, h); 0290 m_canvas->drawArea(drawArea, &p); 0291 0292 p.translate(-w, 0); 0293 } 0294 p.translate(w * cols, -h); 0295 } 0296 0297 updateBackground(); 0298 0299 // and send the result to the printer 0300 p.end(); 0301 } 0302 0303 void ItemDocument::requestStateSave(int actionTicket) 0304 { 0305 if (m_bIsLoading) 0306 return; 0307 0308 cleanClearStack(m_redoStack); 0309 0310 if ((actionTicket >= 0) && (actionTicket == m_currentActionTicket)) { 0311 delete m_currentState; 0312 m_currentState = nullptr; 0313 } 0314 0315 m_currentActionTicket = actionTicket; 0316 0317 // FIXME: it is possible, that we push something here, also nothing has changed, yet. 0318 // to reproduce do: 0319 // 1. select an item -> something is pushed onto undoStack, but nothing changed 0320 // 2. select Undo -> pushed on redoStack, pop from undoStack 0321 // 3. deselect item -> there is still something on the redoStack 0322 // 0323 // this way you can fill up the redoStack, as you like :-/ 0324 if (m_currentState) 0325 m_undoStack.push(m_currentState); 0326 0327 m_currentState = new ItemDocumentData(type()); 0328 m_currentState->saveDocumentState(this); 0329 0330 if (!m_savedState) 0331 m_savedState = m_currentState; 0332 0333 setModified(m_savedState != m_currentState); 0334 0335 emit undoRedoStateChanged(); 0336 0337 // FIXME To resize undo queue, have to pop and push everything 0338 // In Qt4 QStack is used and QStack inherits QVector, that should 0339 // make it a bit more easy 0340 int maxUndo = KTLConfig::maxUndo(); 0341 if (maxUndo <= 0 || m_undoStack.count() < maxUndo) 0342 return; 0343 IDDStack tempStack; 0344 int pushed = 0; 0345 while (!m_undoStack.isEmpty() && pushed < maxUndo) { 0346 tempStack.push(m_undoStack.pop()); 0347 pushed++; 0348 } 0349 cleanClearStack(m_undoStack); 0350 while (!tempStack.isEmpty()) 0351 m_undoStack.push(tempStack.pop()); 0352 } 0353 0354 void ItemDocument::cleanClearStack(IDDStack &stack) 0355 { 0356 while (!stack.isEmpty()) { 0357 ItemDocumentData *idd = stack.pop(); 0358 if (m_currentState != idd) 0359 delete idd; 0360 } 0361 } 0362 0363 void ItemDocument::clearHistory() 0364 { 0365 cleanClearStack(m_undoStack); 0366 cleanClearStack(m_redoStack); 0367 delete m_currentState; 0368 m_currentState = nullptr; 0369 requestStateSave(); 0370 } 0371 0372 bool ItemDocument::isUndoAvailable() const 0373 { 0374 return !m_undoStack.isEmpty(); 0375 } 0376 0377 bool ItemDocument::isRedoAvailable() const 0378 { 0379 return !m_redoStack.isEmpty(); 0380 } 0381 0382 void ItemDocument::undo() 0383 { 0384 if (m_undoStack.empty()) { 0385 return; 0386 } 0387 ItemDocumentData *idd = m_undoStack.pop(); 0388 if (!idd) 0389 return; 0390 0391 if (m_currentState) 0392 m_redoStack.push(m_currentState); 0393 0394 idd->restoreDocument(this); 0395 m_currentState = idd; 0396 0397 setModified(m_savedState != m_currentState); 0398 emit undoRedoStateChanged(); 0399 } 0400 0401 void ItemDocument::redo() 0402 { 0403 if (m_redoStack.empty()) { 0404 return; 0405 } 0406 ItemDocumentData *idd = m_redoStack.pop(); 0407 if (!idd) 0408 return; 0409 0410 if (m_currentState) 0411 m_undoStack.push(m_currentState); 0412 0413 idd->restoreDocument(this); 0414 m_currentState = idd; 0415 0416 setModified(m_savedState != m_currentState); 0417 emit undoRedoStateChanged(); 0418 } 0419 0420 void ItemDocument::cut() 0421 { 0422 copy(); 0423 deleteSelection(); 0424 } 0425 0426 void ItemDocument::paste() 0427 { 0428 QString xml = QApplication::clipboard()->text(QClipboard::Clipboard); 0429 if (xml.isEmpty()) 0430 return; 0431 0432 unselectAll(); 0433 0434 ItemDocumentData data(type()); 0435 0436 if (!data.fromXML(xml)) 0437 return; 0438 0439 data.generateUniqueIDs(this); 0440 // data.translateContents( 64, 64 ); 0441 data.mergeWithDocument(this, true); 0442 0443 // Get rid of any garbage that shouldn't be around / merge connectors / etc 0444 flushDeleteList(); 0445 0446 requestStateSave(); 0447 } 0448 0449 Item *ItemDocument::itemWithID(const QString &id) 0450 { 0451 if (m_itemList.contains(id)) 0452 return m_itemList[id]; 0453 else 0454 return nullptr; 0455 } 0456 0457 void ItemDocument::unselectAll() 0458 { 0459 selectList()->removeAllItems(); 0460 } 0461 0462 void ItemDocument::select(KtlQCanvasItem *item) 0463 { 0464 if (!item) 0465 return; 0466 0467 item->setSelected(selectList()->contains(item) || selectList()->addQCanvasItem(item)); 0468 } 0469 0470 void ItemDocument::select(const KtlQCanvasItemList &list) 0471 { 0472 const KtlQCanvasItemList::const_iterator end = list.end(); 0473 for (KtlQCanvasItemList::const_iterator it = list.begin(); it != end; ++it) 0474 selectList()->addQCanvasItem(*it); 0475 0476 selectList()->setSelected(true); 0477 } 0478 0479 void ItemDocument::unselect(KtlQCanvasItem *qcanvasItem) 0480 { 0481 selectList()->removeQCanvasItem(qcanvasItem); 0482 qcanvasItem->setSelected(false); 0483 } 0484 0485 void ItemDocument::slotUpdateConfiguration() 0486 { 0487 updateBackground(); 0488 m_canvas->setUpdatePeriod(int(1000. / KTLConfig::refreshRate())); 0489 } 0490 0491 KtlQCanvasItem *ItemDocument::itemAtTop(const QPoint &pos) const 0492 { 0493 KtlQCanvasItemList list = m_canvas->collisions(QRect(pos.x() - 1, pos.y() - 1, 3, 3)); // note: m_canvas is actually modified here 0494 KtlQCanvasItemList::const_iterator it = list.begin(); 0495 const KtlQCanvasItemList::const_iterator end = list.end(); 0496 0497 while (it != end) { 0498 KtlQCanvasItem *item = *it; 0499 if (!dynamic_cast<Item *>(item) && !dynamic_cast<ConnectorLine *>(item) && !dynamic_cast<Node *>(item) && !dynamic_cast<Widget *>(item) && !dynamic_cast<ResizeHandle *>(item)) { 0500 ++it; 0501 } else { 0502 if (ConnectorLine *l = dynamic_cast<ConnectorLine *>(item)) 0503 return l->parent(); 0504 0505 return item; 0506 } 0507 } 0508 0509 return nullptr; 0510 } 0511 0512 // these look dangerous., see todo in header file. 0513 void ItemDocument::alignHorizontally() 0514 { 0515 selectList()->slotAlignHorizontally(); 0516 if (ICNDocument *icnd = dynamic_cast<ICNDocument *>(this)) 0517 icnd->requestRerouteInvalidatedConnectors(); 0518 } 0519 0520 void ItemDocument::alignVertically() 0521 { 0522 selectList()->slotAlignVertically(); 0523 if (ICNDocument *icnd = dynamic_cast<ICNDocument *>(this)) 0524 icnd->requestRerouteInvalidatedConnectors(); 0525 } 0526 0527 void ItemDocument::distributeHorizontally() 0528 { 0529 selectList()->slotDistributeHorizontally(); 0530 if (ICNDocument *icnd = dynamic_cast<ICNDocument *>(this)) 0531 icnd->requestRerouteInvalidatedConnectors(); 0532 } 0533 0534 void ItemDocument::distributeVertically() 0535 { 0536 selectList()->slotDistributeVertically(); 0537 if (ICNDocument *icnd = dynamic_cast<ICNDocument *>(this)) 0538 icnd->requestRerouteInvalidatedConnectors(); 0539 } 0540 // ########################### 0541 0542 bool ItemDocument::registerUID(const QString &UID) 0543 { 0544 return m_idList.insert(UID).second; 0545 } 0546 0547 void ItemDocument::unregisterUID(const QString &uid) 0548 { 0549 m_idList.erase(uid); 0550 m_itemList.remove(uid); 0551 } 0552 0553 QString ItemDocument::generateUID(QString name) 0554 { 0555 name.remove(QRegExp("__.*")); // Change 'node__13' to 'node', for example 0556 QString idAttempt = name; 0557 0558 while (!registerUID(idAttempt)) 0559 idAttempt = name + "__" + QString::number(m_nextIdNum++); 0560 0561 return idAttempt; 0562 } 0563 0564 // FIXME: popup menu doesn't seem to work these days. =( 0565 void ItemDocument::canvasRightClick(const QPoint &pos, KtlQCanvasItem *item) 0566 { 0567 if (item) { 0568 if (dynamic_cast<CNItem *>(item) && !item->isSelected()) { 0569 unselectAll(); 0570 select(item); 0571 } 0572 } 0573 0574 KTechlab::self()->unplugActionList("alignment_actionlist"); 0575 KTechlab::self()->unplugActionList("orientation_actionlist"); 0576 fillContextMenu(pos); 0577 0578 QMenu *pop = static_cast<QMenu *>(KTechlab::self()->factory()->container("item_popup", KTechlab::self())); 0579 0580 if (!pop) 0581 return; 0582 0583 pop->popup(pos); 0584 } 0585 0586 void ItemDocument::fillContextMenu(const QPoint &pos) 0587 { 0588 Q_UNUSED(pos); 0589 0590 ItemView *activeItemView = dynamic_cast<ItemView *>(activeView()); 0591 if (!KTechlab::self() || !activeItemView) 0592 return; 0593 0594 QAction *align_actions[] = { 0595 activeItemView->actionByName("align_horizontally"), activeItemView->actionByName("align_vertically"), activeItemView->actionByName("distribute_horizontally"), activeItemView->actionByName("distribute_vertically")}; 0596 0597 bool enableAlignment = selectList()->itemCount() > 1; 0598 0599 if (!enableAlignment) 0600 return; 0601 0602 for (unsigned i = 0; i < 4; ++i) { 0603 align_actions[i]->setEnabled(true); 0604 m_pAlignmentAction->removeAction(align_actions[i]); 0605 // m_pAlignmentAction->insert( align_actions[i] ); 0606 m_pAlignmentAction->addAction(align_actions[i]); 0607 } 0608 QList<QAction *> alignment_actions; 0609 alignment_actions.append(m_pAlignmentAction); 0610 KTechlab::self()->plugActionList("alignment_actionlist", alignment_actions); 0611 } 0612 0613 void ItemDocument::slotInitItemActions() 0614 { 0615 ItemView *activeItemView = dynamic_cast<ItemView *>(activeView()); 0616 if (!KTechlab::self() || !activeItemView) 0617 return; 0618 0619 QAction *align_actions[] = { 0620 activeItemView->actionByName("align_horizontally"), activeItemView->actionByName("align_vertically"), activeItemView->actionByName("distribute_horizontally"), activeItemView->actionByName("distribute_vertically")}; 0621 0622 bool enableAlignment = selectList()->itemCount() > 1; 0623 for (unsigned i = 0; i < 4; ++i) 0624 align_actions[i]->setEnabled(enableAlignment); 0625 } 0626 0627 void ItemDocument::updateBackground() 0628 { 0629 // Also used in the constructor to make the background initially. 0630 0631 // Thoughts. 0632 // ~The pixmap could be done somehow with 1bpp. It might save some waste 0633 // I expect it won't hurt for now. 0634 // ~This is all rather static, only works with square etc... should be no prob. for most uses. IMO. 0635 // ~If you want, decide what maximum and minimum spacing should be, then enforce them 0636 // in the Config (I suppose you can use <max></max> tags?) 0637 // ~Defaults based on the existing grid background png. It should produce identical results, to your 0638 // original png. 0639 0640 // **** Below where it says "interval * 10", that decides how big the pixmap will be (always square) 0641 // Originally I set this to 32, which give 256x256 with 8 spacing, as that was the size of your pixmap 0642 // Are there any good reasons to make the a certain size? (i.e. big or small ?). 0643 0644 int interval = 8; 0645 int bigness = interval * 10; 0646 QPixmap pm(bigness, bigness); 0647 // pm.fill( KTLConfig::bgColor() ); // first fill the background colour in 0648 pm.fill(Qt::white); 0649 0650 if (KTLConfig::showGrid()) { 0651 // QPainter p(&pm); // setup painter to draw on pixmap 0652 QPainter p; 0653 const bool isSuccess = p.begin(&pm); 0654 if (!isSuccess) { 0655 qCWarning(KTL_LOG) << " painter is not active"; 0656 } 0657 p.setPen(KTLConfig::gridColor()); // set forecolour 0658 // note: anything other than 8 borks this 0659 for (int i = (interval / 2); i < bigness; i += interval) { 0660 p.drawLine(0, i, bigness, i); // horizontal 0661 p.drawLine(i, 0, i, bigness); // vertical 0662 } 0663 p.end(); // all done 0664 } 0665 0666 // pm.setDefaultOptimization( QPixmap::BestOptim ); // TODO no longer available? 0667 m_canvas->setBackgroundPixmap(pm); // and the finale. 0668 } 0669 0670 void ItemDocument::requestCanvasResize() 0671 { 0672 requestEvent(ItemDocumentEvent::ResizeCanvasToItems); 0673 } 0674 0675 void ItemDocument::requestEvent(ItemDocumentEvent::type type) 0676 { 0677 m_queuedEvents |= type; 0678 m_pEventTimer->stop(); 0679 m_pEventTimer->setSingleShot(true); 0680 m_pEventTimer->start(0 /*, true */); 0681 } 0682 0683 void ItemDocument::processItemDocumentEvents() 0684 { 0685 // Copy it incase we have new events requested while doing this... 0686 unsigned queuedEvents = m_queuedEvents; 0687 m_queuedEvents = 0; 0688 0689 if (queuedEvents & ItemDocumentEvent::ResizeCanvasToItems) 0690 resizeCanvasToItems(); 0691 0692 if (queuedEvents & ItemDocumentEvent::UpdateZOrdering) 0693 slotUpdateZOrdering(); 0694 0695 ICNDocument *icnd = dynamic_cast<ICNDocument *>(this); 0696 0697 if (icnd && (queuedEvents & ItemDocumentEvent::UpdateNodeGroups)) 0698 icnd->slotAssignNodeGroups(); 0699 0700 if (icnd && (queuedEvents & ItemDocumentEvent::RerouteInvalidatedConnectors)) 0701 icnd->rerouteInvalidatedConnectors(); 0702 } 0703 0704 void ItemDocument::resizeCanvasToItems() 0705 { 0706 QRect bound = canvasBoundingRect(); 0707 0708 m_viewList.removeAll(static_cast<View *>(nullptr)); 0709 const ViewList::iterator end = m_viewList.end(); 0710 for (ViewList::iterator it = m_viewList.begin(); it != end; ++it) { 0711 ItemView *iv = static_cast<ItemView *>(static_cast<View *>(*it)); 0712 CVBEditor *cvbEditor = iv->cvbEditor(); 0713 0714 QPoint topLeft = iv->mousePosToCanvasPos(QPoint(0, 0)); 0715 int width = int(cvbEditor->visibleWidth() / iv->zoomLevel()); 0716 int height = int(cvbEditor->visibleHeight() / iv->zoomLevel()); 0717 QRect r(topLeft, QSize(width, height)); 0718 0719 bound |= r; 0720 0721 // qCDebug(KTL_LOG) << "r="<<r; 0722 // qCDebug(KTL_LOG) << "bound="<<bound; 0723 } 0724 0725 // Make it so that the rectangular offset is a multiple of 8 0726 bound.setLeft(bound.left() - (bound.left() % 8)); 0727 bound.setTop(bound.top() - (bound.top() % 8)); 0728 0729 m_pUpdateItemViewScrollbarsTimer->setSingleShot(true); 0730 m_pUpdateItemViewScrollbarsTimer->start(10 /*, true */); 0731 0732 bool changedSize = canvas()->rect() != bound; 0733 if (changedSize) { 0734 canvas()->resize(bound); 0735 requestEvent(ItemDocumentEvent::ResizeCanvasToItems); 0736 } else if (ICNDocument *icnd = dynamic_cast<ICNDocument *>(this)) { 0737 icnd->createCellMap(); 0738 } 0739 } 0740 0741 void ItemDocument::updateItemViewScrollbars() 0742 { 0743 int w = canvas()->width(); 0744 int h = canvas()->height(); 0745 0746 const ViewList::iterator end = m_viewList.end(); 0747 for (ViewList::iterator it = m_viewList.begin(); it != end; ++it) { 0748 ItemView *itemView = static_cast<ItemView *>(static_cast<View *>(*it)); 0749 CVBEditor *cvbEditor = itemView->cvbEditor(); 0750 // TODO QT3 0751 cvbEditor->setVScrollBarMode(((h * itemView->zoomLevel()) > cvbEditor->visibleHeight()) ? KtlQ3ScrollView::AlwaysOn : KtlQ3ScrollView::AlwaysOff); 0752 cvbEditor->setHScrollBarMode(((w * itemView->zoomLevel()) > cvbEditor->visibleWidth()) ? KtlQ3ScrollView::AlwaysOn : KtlQ3ScrollView::AlwaysOff); 0753 } 0754 } 0755 0756 QRect ItemDocument::canvasBoundingRect() const 0757 { 0758 QRect bound; 0759 0760 // Don't include items used for dragging 0761 Item *dragItem = nullptr; 0762 const ViewList::const_iterator viewsEnd = m_viewList.end(); 0763 for (ViewList::const_iterator it = m_viewList.begin(); it != viewsEnd; ++it) { 0764 dragItem = (static_cast<ItemView *>(static_cast<View *>(*it)))->dragItem(); 0765 if (dragItem) 0766 break; 0767 } 0768 0769 const KtlQCanvasItemList allItems = canvas()->allItems(); 0770 const KtlQCanvasItemList::const_iterator end = allItems.end(); 0771 0772 for (KtlQCanvasItemList::const_iterator it = allItems.begin(); it != end; ++it) { 0773 if (!(*it)->isVisible()) 0774 continue; 0775 0776 if (dragItem) { 0777 if (*it == dragItem) 0778 continue; 0779 0780 if (Node *n = dynamic_cast<Node *>(*it)) { 0781 if (n->parentItem() == dragItem) 0782 continue; 0783 } 0784 0785 if (GuiPart *gp = dynamic_cast<GuiPart *>(*it)) { 0786 if (gp->parent() == dragItem) 0787 continue; 0788 } 0789 } 0790 0791 bound |= (*it)->boundingRect(); 0792 } 0793 0794 if (!bound.isNull()) { 0795 bound.setLeft(bound.left() - 16); 0796 bound.setTop(bound.top() - 16); 0797 bound.setRight(bound.right() + 16); 0798 bound.setBottom(bound.bottom() + 16); 0799 } 0800 0801 return bound; 0802 } 0803 0804 void ItemDocument::exportToImage() 0805 { 0806 // scaralously copied from print. 0807 // this slot is called whenever the File->Export menu is selected, 0808 // the Export shortcut is pressed or the Export toolbar 0809 // button is clicked 0810 0811 // we need an object so we can retrieve which image type was selected by the user 0812 // so setup the filedialog. 0813 ImageExportDialog exportDialog(KTechlab::self()); 0814 0815 // now actually show it 0816 if (exportDialog.exec() == QDialog::Rejected) 0817 return; 0818 const QString filePath = exportDialog.filePath(); 0819 0820 if (filePath.isEmpty()) 0821 return; 0822 0823 if (QFile::exists(filePath)) { 0824 int query = KMessageBox::warningYesNo(KTechlab::self(), 0825 i18n("A file named \"%1\" already exists. " 0826 "Are you sure you want to overwrite it?", 0827 filePath), 0828 i18n("Overwrite File?")); 0829 0830 if (query == KMessageBox::No) 0831 return; 0832 } 0833 0834 const bool crop = exportDialog.isCropSelected(); 0835 // with Qt, you always "print" to a 0836 // QPainter.. whether the output medium is a pixmap, a screen, 0837 // or paper 0838 0839 // needs to be something like QPicture to do SVG etc... 0840 0841 QRect saveArea; 0842 QRect cropArea; 0843 QPaintDevice *outputImage; 0844 const QString type = exportDialog.formatType(); 0845 0846 // did have a switch here but seems you can't use that on strings 0847 if (type == "SVG") { 0848 KMessageBox::information(nullptr, i18n("SVG export is sub-functional"), i18n("Export As Image")); 0849 } 0850 0851 if (crop) { 0852 cropArea = canvasBoundingRect(); 0853 if (cropArea.isNull()) { 0854 KMessageBox::error(nullptr, i18n("There is nothing to crop"), i18n("Export As Image")); 0855 return; 0856 } else { 0857 cropArea &= canvas()->rect(); 0858 } 0859 } 0860 0861 saveArea = m_canvas->rect(); 0862 0863 if (type == "PNG" || type == "BMP") 0864 outputImage = new QPixmap(saveArea.size()); 0865 else if (type == "SVG") { 0866 setSVGExport(true); 0867 outputImage = new QPicture(); 0868 // svg can't be cropped using the qimage method. 0869 saveArea = cropArea; 0870 } else { 0871 qCWarning(KTL_LOG) << "Unknown type!"; 0872 return; 0873 } 0874 0875 // 2018.05.05 - extract to a method 0876 // //QPainter p(outputImage); // 2016.05.03 - explicitly initialize painter 0877 // QPainter p; 0878 // const bool isBeginSuccess = p.begin(outputImage); 0879 // if (!isBeginSuccess) { 0880 // qCWarning(KTL_LOG) << " painter not active"; 0881 // } 0882 // 0883 // m_canvas->setBackgroundPixmap(QPixmap()); 0884 // m_canvas->drawArea( saveArea, &p ); 0885 // updateBackground(); 0886 // 0887 // p.end(); 0888 exportToImageDraw(saveArea, *outputImage); 0889 0890 bool saveResult; 0891 0892 // if cropping we need to convert to an image, 0893 // crop, then save. 0894 if (crop) { 0895 if (type == "SVG") 0896 saveResult = dynamic_cast<QPicture *>(outputImage)->save(filePath, type.toLatin1().data()); 0897 else { 0898 QImage img = dynamic_cast<QPixmap *>(outputImage)->toImage(); 0899 if (saveArea.x() < 0) { 0900 cropArea.translate(-saveArea.x(), 0); 0901 } 0902 if (saveArea.y() < 0) { 0903 cropArea.translate(0, -saveArea.y()); 0904 } 0905 qCDebug(KTL_LOG) << " cropArea " << cropArea; 0906 QImage imgCropped = img.copy(cropArea); 0907 saveResult = imgCropped.save(filePath, type.toLatin1().data()); 0908 } 0909 } else { 0910 if (type == "SVG") 0911 saveResult = dynamic_cast<QPicture *>(outputImage)->save(filePath, type.toLatin1().data()); 0912 else 0913 saveResult = dynamic_cast<QPixmap *>(outputImage)->save(filePath, type.toLatin1().data()); 0914 } 0915 0916 // if(saveResult == true) KMessageBox::information( this, i18n("Sucessfully exported to \"%1\"", url.filename() ), i18n("Image Export") ); 0917 // else KMessageBox::information( this, i18n("Export failed"), i18n("Image Export") ); 0918 0919 if (type == "SVG") 0920 setSVGExport(false); 0921 0922 if (saveResult == false) 0923 KMessageBox::information(KTechlab::self(), i18n("Export failed"), i18n("Image Export")); 0924 0925 delete outputImage; 0926 } 0927 0928 void ItemDocument::exportToImageDraw(const QRect &saveArea, QPaintDevice &pDev) 0929 { 0930 qCDebug(KTL_LOG) << " saveArea " << saveArea; 0931 // QPainter p(outputImage); // 2016.05.03 - explicitly initialize painter 0932 QPainter p; 0933 const bool isBeginSuccess = p.begin(&pDev); 0934 if (!isBeginSuccess) { 0935 qCWarning(KTL_LOG) << " painter not active"; 0936 } 0937 0938 QTransform transf; 0939 transf.translate(-saveArea.x(), -saveArea.y()); 0940 p.setTransform(transf); 0941 0942 m_canvas->setBackgroundPixmap(QPixmap()); 0943 m_canvas->drawArea(saveArea, &p); 0944 updateBackground(); 0945 0946 p.end(); 0947 } 0948 0949 void ItemDocument::setSVGExport(bool svgExport) 0950 { 0951 // Find any items and tell them not to draw buttons or sliders 0952 KtlQCanvasItemList items = m_canvas->allItems(); 0953 const KtlQCanvasItemList::iterator end = items.end(); 0954 for (KtlQCanvasItemList::Iterator it = items.begin(); it != end; ++it) { 0955 if (CNItem *cnItem = dynamic_cast<CNItem *>(*it)) 0956 cnItem->setDrawWidgets(!svgExport); 0957 } 0958 } 0959 0960 void ItemDocument::raiseZ() 0961 { 0962 raiseZ(selectList()->items(true)); 0963 } 0964 void ItemDocument::raiseZ(const ItemList &itemList) 0965 { 0966 if (m_zOrder.isEmpty()) 0967 slotUpdateZOrdering(); 0968 0969 if (m_zOrder.isEmpty()) 0970 return; 0971 0972 IntItemMap::iterator begin = m_zOrder.begin(); 0973 IntItemMap::iterator previous = m_zOrder.end(); 0974 IntItemMap::iterator it = --m_zOrder.end(); 0975 do { 0976 Item *previousData = (previous == m_zOrder.end()) ? nullptr : previous.value(); 0977 Item *currentData = it.value(); 0978 0979 if (currentData && previousData && itemList.contains(currentData) && !itemList.contains(previousData)) { 0980 previous.value() = currentData; 0981 it.value() = previousData; 0982 } 0983 0984 previous = it; 0985 --it; 0986 } while (previous != begin); 0987 0988 slotUpdateZOrdering(); 0989 } 0990 0991 void ItemDocument::lowerZ() 0992 { 0993 lowerZ(selectList()->items(true)); 0994 } 0995 0996 void ItemDocument::lowerZ(const ItemList &itemList) 0997 { 0998 if (m_zOrder.isEmpty()) 0999 slotUpdateZOrdering(); 1000 1001 if (m_zOrder.isEmpty()) 1002 return; 1003 1004 IntItemMap::iterator previous = m_zOrder.begin(); 1005 IntItemMap::iterator end = m_zOrder.end(); 1006 for (IntItemMap::iterator it = m_zOrder.begin(); it != end; ++it) { 1007 Item *previousData = previous.value(); 1008 Item *currentData = it.value(); 1009 1010 if (currentData && previousData && itemList.contains(currentData) && !itemList.contains(previousData)) { 1011 previous.value() = currentData; 1012 it.value() = previousData; 1013 } 1014 1015 previous = it; 1016 } 1017 1018 slotUpdateZOrdering(); 1019 } 1020 1021 void ItemDocument::itemAdded(Item *) 1022 { 1023 requestEvent(ItemDocument::ItemDocumentEvent::UpdateZOrdering); 1024 } 1025 1026 void ItemDocument::slotUpdateZOrdering() 1027 { 1028 ItemMap toAdd = m_itemList; 1029 1030 IntItemMap newZOrder; 1031 int atLevel = 0; 1032 1033 IntItemMap::iterator zEnd = m_zOrder.end(); 1034 for (IntItemMap::iterator it = m_zOrder.begin(); it != zEnd; ++it) { 1035 Item *item = it.value(); 1036 if (!item) 1037 continue; 1038 1039 toAdd.remove(item->id()); 1040 1041 if (!item->parentItem() && item->isMovable()) 1042 newZOrder[atLevel++] = item; 1043 } 1044 1045 ItemMap::iterator addEnd = toAdd.end(); 1046 for (ItemMap::iterator it = toAdd.begin(); it != addEnd; ++it) { 1047 Item *item = *it; 1048 if (item->parentItem() || !item->isMovable()) 1049 continue; 1050 1051 newZOrder[atLevel++] = item; 1052 } 1053 1054 m_zOrder = newZOrder; 1055 1056 zEnd = m_zOrder.end(); 1057 for (IntItemMap::iterator it = m_zOrder.begin(); it != zEnd; ++it) 1058 it.value()->updateZ(it.key()); 1059 } 1060 1061 void ItemDocument::update() 1062 { 1063 ItemMap::iterator end = m_itemList.end(); 1064 for (ItemMap::iterator it = m_itemList.begin(); it != end; ++it) { 1065 if ((*it)->contentChanged()) 1066 (*it)->setChanged(); 1067 } 1068 } 1069 1070 ItemList ItemDocument::itemList() const 1071 { 1072 ItemList l; 1073 1074 ItemMap::const_iterator end = m_itemList.end(); 1075 for (ItemMap::const_iterator it = m_itemList.begin(); it != end; ++it) 1076 l << it.value(); 1077 1078 return l; 1079 } 1080 // END class ItemDocument 1081 1082 // BEGIN class CanvasTip 1083 CanvasTip::CanvasTip(ItemDocument *itemDocument, KtlQCanvas *qcanvas) 1084 : KtlQCanvasRectangle(qcanvas) 1085 { 1086 p_itemDocument = itemDocument; 1087 1088 setZ(ICNDocument::Z::Tip); 1089 } 1090 1091 CanvasTip::~CanvasTip() 1092 { 1093 } 1094 1095 void CanvasTip::displayVI(ECNode *node, const QPoint &pos) 1096 { 1097 if (!node || !updateVI()) 1098 return; 1099 1100 unsigned num = node->numPins(); 1101 1102 m_v.resize(num); 1103 m_i.resize(num); 1104 1105 for (unsigned i = 0; i < num; i++) { 1106 if (Pin *pin = node->pin(i)) { 1107 m_v[i] = pin->voltage(); 1108 m_i[i] = pin->current(); 1109 } 1110 } 1111 1112 display(pos); 1113 } 1114 1115 void CanvasTip::displayVI(Connector *connector, const QPoint &pos) 1116 { 1117 if (!connector || !updateVI()) 1118 return; 1119 1120 unsigned num = connector->numWires(); 1121 1122 m_v.resize(num); 1123 m_i.resize(num); 1124 1125 for (unsigned i = 0; i < num; i++) { 1126 if (Wire *wire = connector->wire(i)) { 1127 m_v[i] = wire->voltage(); 1128 m_i[i] = std::abs(wire->current()); 1129 } 1130 } 1131 1132 display(pos); 1133 } 1134 1135 bool CanvasTip::updateVI() 1136 { 1137 CircuitDocument *circuitDocument = dynamic_cast<CircuitDocument *>(p_itemDocument); 1138 if (!circuitDocument || !Simulator::self()->isSimulating()) 1139 return false; 1140 1141 circuitDocument->calculateConnectorCurrents(); 1142 return true; 1143 } 1144 1145 void CanvasTip::display(const QPoint &pos) 1146 { 1147 unsigned num = m_v.size(); 1148 1149 for (unsigned i = 0; i < num; i++) { 1150 if (!std::isfinite(m_v[i]) || std::abs(m_v[i]) < 1e-9) 1151 m_v[i] = 0.; 1152 1153 if (!std::isfinite(m_i[i]) || std::abs(m_i[i]) < 1e-9) 1154 m_i[i] = 0.; 1155 } 1156 1157 move(pos.x() + 20, pos.y() + 4); 1158 1159 if (num == 0) 1160 return; 1161 1162 if (num == 1) 1163 setText(displayText(0)); 1164 else { 1165 QString text; 1166 for (unsigned i = 0; i < num; i++) 1167 text += QString("%1: %2\n").arg(QString::number(i)).arg(displayText(i)); 1168 setText(text); 1169 } 1170 } 1171 1172 QString CanvasTip::displayText(unsigned num) const 1173 { 1174 if (m_v.size() <= int(num)) 1175 return QString(); 1176 1177 return QString("%1%2V %3%4A") 1178 .arg(QString::number(m_v[num] / CNItem::getMultiplier(m_v[num]), 'g', 3)) 1179 .arg(CNItem::getNumberMag(m_v[num])) 1180 .arg(QString::number(m_i[num] / CNItem::getMultiplier(m_i[num]), 'g', 3)) 1181 .arg(CNItem::getNumberMag(m_i[num])); 1182 } 1183 1184 void CanvasTip::draw(QPainter &p) 1185 { 1186 CircuitDocument *circuitDocument = dynamic_cast<CircuitDocument *>(p_itemDocument); 1187 if (!circuitDocument || !Simulator::self()->isSimulating()) 1188 return; 1189 1190 p.setBrush(QColor(0xff, 0xff, 0xdc)); 1191 p.setPen(Qt::black); 1192 p.drawRect(boundingRect()); 1193 1194 QRect textRect = boundingRect(); 1195 textRect.setLeft(textRect.left() + 3); 1196 textRect.setTop(textRect.top() + 1); 1197 p.drawText(textRect, 0, m_text); 1198 } 1199 1200 void CanvasTip::setText(const QString &text) 1201 { 1202 m_text = text; 1203 canvas()->setChanged(boundingRect()); 1204 1205 QRect r = QFontMetrics(qApp->font()).boundingRect(0, 0, 0, 0, 0, m_text); 1206 setSize(r.width() + 4, r.height() - 1); 1207 } 1208 // END class CanvasTip 1209 1210 // BEGIN class Canvas 1211 Canvas::Canvas(ItemDocument *itemDocument) 1212 : KtlQCanvas(itemDocument) 1213 { 1214 p_itemDocument = itemDocument; 1215 m_pMessageTimeout = new QTimer(this); 1216 connect(m_pMessageTimeout, SIGNAL(timeout()), this, SLOT(slotSetAllChanged())); 1217 } 1218 1219 void Canvas::resize(const QRect &size) 1220 { 1221 if (rect() == size) 1222 return; 1223 QRect oldSize = rect(); 1224 KtlQCanvas::resize(size); 1225 emit resized(oldSize, size); 1226 } 1227 1228 void Canvas::setMessage(const QString &message) 1229 { 1230 m_message = message; 1231 1232 if (message.isEmpty()) { 1233 m_pMessageTimeout->stop(); 1234 } else { 1235 m_pMessageTimeout->setSingleShot(true); 1236 m_pMessageTimeout->start(2000 /*, true */); 1237 } 1238 1239 setAllChanged(); 1240 } 1241 1242 void Canvas::drawBackground(QPainter &p, const QRect &clip) 1243 { 1244 KtlQCanvas::drawBackground(p, clip); 1245 #if 0 1246 const int scx = (int)((clip.left()-4)/8); 1247 const int ecx = (int)((clip.right()+4)/8); 1248 const int scy = (int)((clip.top()-4)/8); 1249 const int ecy = (int)((clip.bottom()+4)/8); 1250 1251 ICNDocument * icnd = dynamic_cast<ICNDocument*>(p_itemDocument); 1252 if ( !icnd ) 1253 return; 1254 1255 Cells * c = icnd->cells(); 1256 1257 if ( !c->haveCell( scx, scy ) || !c->haveCell( ecx, ecy ) ) 1258 return; 1259 1260 for ( int x=scx; x<=ecx; x++ ) 1261 { 1262 for ( int y=scy; y<=ecy; y++ ) 1263 { 1264 const double score = c->cell( x, y ).CIpenalty + c->cell( x, y ).Cpenalty; 1265 int value = (int)std::log(score)*20; 1266 if ( value>255 ) 1267 value=255; 1268 else if (value<0 ) 1269 value=0; 1270 p.setBrush( QColor( 255, (255-value), (255-value) ) ); 1271 p.setPen( Qt::NoPen ); 1272 p.drawRect( (x*8), (y*8), 8, 8 ); 1273 } 1274 } 1275 #endif 1276 } 1277 1278 void Canvas::drawForeground(QPainter &p, const QRect &clip) 1279 { 1280 KtlQCanvas::drawForeground(p, clip); 1281 1282 if (!m_pMessageTimeout->isActive()) 1283 return; 1284 1285 // Following code stolen and adapted from amarok/src/playlist.cpp :) 1286 1287 // Find out width of smallest view 1288 QSize minSize; 1289 const ViewList viewList = p_itemDocument->viewList(); 1290 ViewList::const_iterator end = viewList.end(); 1291 View *firstView = nullptr; 1292 for (ViewList::const_iterator it = viewList.begin(); it != end; ++it) { 1293 if (!*it) 1294 continue; 1295 1296 if (!firstView) { 1297 firstView = *it; 1298 minSize = (*it)->size(); 1299 } else 1300 minSize = minSize.boundedTo((*it)->size()); 1301 } 1302 1303 if (!firstView) 1304 return; 1305 1306 // Q3SimpleRichText * t = new Q3SimpleRichText( m_message, QApplication::font() ); 1307 QTextEdit *t = new QTextEdit(m_message); 1308 1309 { 1310 QFont tf = t->document()->defaultFont(); 1311 QFontMetrics tfm(tf); 1312 QSize textSize = tfm.size(0, m_message); 1313 1314 t->resize(textSize); 1315 } 1316 1317 int w = t->width(); 1318 int h = t->height(); 1319 int x = rect().left() + 15; 1320 int y = rect().top() + 15; 1321 int b = 10; // text padding 1322 1323 // if ( w+2*b >= minSize.width() || h+2*b >= minSize.height() ) 1324 // { 1325 // qCWarning(KTL_LOG) << "size not good w=" << w << " h=" << h << "b=" << b << " minSize=" << minSize; 1326 // delete t; 1327 // return; 1328 // } 1329 1330 // p.setBrush( firstView->colorGroup().background() ); // 2018.12.02 1331 p.setBrush(firstView->palette().window()); 1332 p.drawRoundedRect(x, y, w + 2 * b, h + 2 * b, (8 * 200) / (w + 2 * b), (8 * 200) / (h + 2 * b), Qt::RelativeSize); 1333 // t->draw( &p, x+b, y+b, QRect(), firstView->colorGroup() ); 1334 t->resize(w + 2 * b, h + 2 * b); 1335 t->viewport()->setAutoFillBackground(false); 1336 t->setFrameStyle(QFrame::NoFrame); 1337 t->render(&p, QPoint(x, y), QRegion(), QWidget::DrawChildren); 1338 delete t; 1339 } 1340 1341 void Canvas::update() 1342 { 1343 p_itemDocument->update(); 1344 KtlQCanvas::update(); 1345 } 1346 // END class Canvas 1347 1348 #include "moc_itemdocument.cpp"