1 /***************************************************************************
2 * Copyright (C) 2005 by David Saxton *
3 * david@bluehaze.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 ***************************************************************************/
10
11 #include "canvasitemparts.h"
12 #include "canvasmanipulator.h"
13 #include "cells.h"
14 #include "circuitdocument.h"
15 #include "connector.h"
16 #include "cnitem.h"
17 #include "drawpart.h"
18 #include "ecnode.h"
19 #include "flowcodedocument.h"
20 #include "icnview.h"
21 #include "itemdocumentdata.h"
22 #include "itemgroup.h"
23 #include "itemselector.h"
24 #include "ktechlab.h"
25 #include "pin.h"
26 #include "resizeoverlay.h"
27 #include "simulator.h"
28 #include "imageexportdlg.h"
29
30 #include <KLocalizedString>
31 #include <KMessageBox>
32 // #include <k3popupmenu.h>
33 //#include <kprinter.h>
34 #include <KActionMenu>
35 #include <KXMLGUIFactory>
36
37 #include <QDebug>
38 #include <QApplication>
39 #include <QCheckBox>
40 #include <QClipboard>
41 #include <QCursor>
42 #include <QImage>
43 #include <QMenu>
44 // #include <q3paintdevicemetrics.h>
45 #include <QPainter>
46 #include <QPicture>
47 #include <QRegExp>
48 // #include <q3simplerichtext.h> // 2018.08.13 - not needed
49 #include <QTimer>
50 #include <QPrinter>
51 #include <QTextEdit>
52 #include <QPrintDialog>
53 #include <QFile>
54
55 #include <cmath>
56 #include <cassert>
57
58 #include <ktlconfig.h>
59
60
61 //BEGIN class ItemDocument
62 int ItemDocument::m_nextActionTicket = 0;
63
ItemDocument(const QString & caption,const char * name)64 ItemDocument::ItemDocument( const QString &caption, const char *name)
65 : Document( caption, name )
66 {
67 m_queuedEvents = 0;
68 m_nextIdNum = 1;
69 m_savedState = nullptr;
70 m_currentState = nullptr;
71 m_bIsLoading = false;
72
73 m_canvas = new Canvas( this, "canvas" );
74 m_canvasTip = new CanvasTip(this,m_canvas);
75 m_cmManager = new CMManager(this);
76
77 updateBackground();
78
79 m_pUpdateItemViewScrollbarsTimer = new QTimer(this);
80 connect( m_pUpdateItemViewScrollbarsTimer, SIGNAL(timeout()), this, SLOT(updateItemViewScrollbars()) );
81
82 m_pEventTimer = new QTimer(this);
83 connect( m_pEventTimer, SIGNAL(timeout()), this, SLOT(processItemDocumentEvents()) );
84
85 connect( this, SIGNAL(selectionChanged()), this, SLOT(slotInitItemActions()) );
86
87 connect( ComponentSelector::self(), SIGNAL(itemClicked(const QString& )), this, SLOT(slotUnsetRepeatedItemId()) );
88 connect( FlowPartSelector::self(), SIGNAL(itemClicked(const QString& )), this, SLOT(slotUnsetRepeatedItemId()) );
89 #ifdef MECHANICS
90 connect( MechanicsSelector::self(), SIGNAL(itemClicked(const QString& )), this, SLOT(slotUnsetRepeatedItemId()) );
91 #endif
92
93 m_pAlignmentAction = new KActionMenu( i18n("Alignment") /*, "format-justify-right" */ , this );
94 m_pAlignmentAction->setObjectName("rightjust");
95 m_pAlignmentAction->setIcon( QIcon::fromTheme("format-justify-right") );
96
97 slotUpdateConfiguration();
98 }
99
~ItemDocument()100 ItemDocument::~ItemDocument()
101 {
102 m_bDeleted = true;
103
104 // ItemMap toDelete = m_itemList;
105
106 const ItemMap::iterator end = m_itemList.end();
107 for ( ItemMap::iterator it = m_itemList.begin(); it != end; ++it ) {
108 qDebug() << "ItemDocument::~ItemDocument: deleting [" << it.key() << "] " << it.value();
109 //delete *it; // 2015.07.31 - this will crash
110 it.value()->deleteLater();
111 }
112 m_itemList.clear();
113
114 cleanClearStack( m_undoStack );
115 cleanClearStack( m_redoStack );
116
117 delete m_cmManager;
118 delete m_currentState;
119 delete m_canvasTip;
120 }
121
handleNewView(View * view)122 void ItemDocument::handleNewView( View * view )
123 {
124 Document::handleNewView(view);
125 requestEvent( ItemDocument::ItemDocumentEvent::ResizeCanvasToItems );
126 }
127
registerItem(KtlQCanvasItem * qcanvasItem)128 bool ItemDocument::registerItem(KtlQCanvasItem *qcanvasItem)
129 {
130 if (!qcanvasItem) return false;
131
132 requestEvent( ItemDocument::ItemDocumentEvent::ResizeCanvasToItems );
133
134 if(Item *item = dynamic_cast<Item*>(qcanvasItem) )
135 {
136 m_itemList[ item->id() ] = item;
137 connect( item, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()) );
138 itemAdded(item);
139 return true;
140 }
141
142 return false;
143 }
144
145
slotSetDrawAction(QAction * selected)146 void ItemDocument::slotSetDrawAction(QAction *selected)
147 {
148 int drawAction = selected->data().toInt();
149 m_cmManager->setDrawAction(drawAction);
150 }
151
cancelCurrentOperation()152 void ItemDocument::cancelCurrentOperation()
153 {
154 m_cmManager->cancelCurrentManipulation();
155 }
156
slotSetRepeatedItemId(const QString & id)157 void ItemDocument::slotSetRepeatedItemId( const QString &id )
158 {
159 m_cmManager->setCMState( CMManager::cms_repeated_add, true );
160 m_cmManager->setRepeatedAddId(id);
161 }
162
slotUnsetRepeatedItemId()163 void ItemDocument::slotUnsetRepeatedItemId()
164 {
165 m_cmManager->setCMState( CMManager::cms_repeated_add, false );
166 }
167
168
fileSave()169 void ItemDocument::fileSave()
170 {
171 if ( url().isEmpty() && !getURL(m_fileExtensionInfo) ) return;
172 writeFile();
173 }
174
175
fileSaveAs()176 void ItemDocument::fileSaveAs()
177 {
178 if ( !getURL(m_fileExtensionInfo) ) return;
179 writeFile();
180
181 // Our modified state may not have changed, but we emit this to force the
182 // main window to update our caption.
183 emit modifiedStateChanged();
184 }
185
186
writeFile()187 void ItemDocument::writeFile()
188 {
189 ItemDocumentData data( type() );
190 data.saveDocumentState(this);
191
192 if ( data.saveData(url()) )
193 {
194 m_savedState = m_currentState;
195 setModified(false);
196 }
197 }
198
199
openURL(const QUrl & url)200 bool ItemDocument::openURL( const QUrl &url )
201 {
202 ItemDocumentData data( type() );
203
204 if ( !data.loadData(url) )
205 return false;
206
207 // Why do we stop simulating while loading a document?
208 // Crash possible when loading a circuit document, and the Qt event loop is
209 // reentered (such as when a PIC component pops-up a message box), which
210 // will then call the Simulator::step function, which might use components
211 // that have not fully initialized themselves.
212
213 m_bIsLoading = true;
214 bool wasSimulating = Simulator::self()->isSimulating();
215 Simulator::self()->slotSetSimulating( false );
216 data.restoreDocument(this);
217 Simulator::self()->slotSetSimulating( wasSimulating );
218 m_bIsLoading = false;
219
220 setURL(url);
221 clearHistory();
222 m_savedState = m_currentState;
223 setModified(false);
224
225 if ( FlowCodeDocument *fcd = dynamic_cast<FlowCodeDocument*>(this) )
226 {
227 // We need to tell all pic-depedent components about what pic type is in use
228 emit fcd->picTypeChanged();
229 }
230
231 requestEvent( ItemDocument::ItemDocumentEvent::ResizeCanvasToItems );
232
233 // Load Z-position info
234 m_zOrder.clear();
235 ItemMap::iterator end = m_itemList.end();
236 for ( ItemMap::iterator it = m_itemList.begin(); it != end; ++it )
237 {
238 if ( !*it || (*it)->parentItem() )
239 continue;
240
241 m_zOrder[(*it)->baseZ()] = *it;
242 }
243 slotUpdateZOrdering();
244
245 return true;
246 }
247
print()248 void ItemDocument::print()
249 {
250 static QPrinter * printer = new QPrinter;
251
252 //if ( ! printer->setup( KTechlab::self() ) )
253 // return;
254 QPrintDialog printDialog(printer, KTechlab::self());
255 if ( ! printDialog.exec() ) {
256 return;
257 }
258
259 // setup the printer. with Qt, you always "print" to a
260 // QPainter.. whether the output medium is a pixmap, a screen,
261 // or paper
262 QPainter p;
263 p.begin( printer );
264
265 // we let our view do the actual printing
266 //Q3PaintDeviceMetrics metrics( printer ); // 2018.08.13 - replaced with method call
267 QRect pageRect = printer->pageRect();
268
269 // Round to 16 so that we cut in the middle of squares
270 int w = pageRect.width();
271 w = (w & 0xFFFFFFF0) + ((w << 1) & 0x10);
272
273 int h = pageRect.height();
274 h = (h & 0xFFFFFFF0) + ((h << 1) & 0x10);
275
276 p.setClipping( true );
277 p.setClipRect( 0, 0, w, h, /* QPainter::CoordPainter */ Qt::ReplaceClip ); // TODO is this correct?
278
279 // Send off the painter for drawing
280 // note: What was this doing?? // set "null" background, so the background horiznotal and vertial lines are not visible
281 m_canvas->setBackgroundPixmap( QPixmap(0,0) /* 0 */ );
282
283 QRect bounding = canvasBoundingRect();
284 unsigned int rows = (unsigned) std::ceil( double( bounding.height() ) / double( h ) );
285 unsigned int cols = (unsigned) std::ceil( double( bounding.width() ) / double( w ) );
286 int offset_x = bounding.x();
287 int offset_y = bounding.y();
288
289 for ( unsigned row = 0; row < rows; ++row )
290 {
291 for ( unsigned col = 0; col < cols; ++col )
292 {
293 if ( row != 0 || col != 0 )
294 printer->newPage();
295
296 QRect drawArea( offset_x + (col * w), offset_y + (row * h), w, h );
297 m_canvas->drawArea( drawArea, & p );
298
299 p.translate( -w, 0 );
300 }
301 p.translate( w * cols, -h );
302 }
303
304 updateBackground();
305
306 // and send the result to the printer
307 p.end();
308 }
309
requestStateSave(int actionTicket)310 void ItemDocument::requestStateSave( int actionTicket )
311 {
312 if ( m_bIsLoading ) return;
313
314 cleanClearStack( m_redoStack );
315
316 if ( (actionTicket >= 0) && (actionTicket == m_currentActionTicket) )
317 {
318 delete m_currentState;
319 m_currentState = nullptr;
320 }
321
322 m_currentActionTicket = actionTicket;
323
324 //FIXME: it is possible, that we push something here, also nothing has changed, yet.
325 // to reproduce do:
326 // 1. select an item -> something is pushed onto undoStack, but nothing changed
327 // 2. select Undo -> pushed on redoStack, pop from undoStack
328 // 3. deselect item -> there is still something on the redoStack
329 //
330 // this way you can fill up the redoStack, as you like :-/
331 if (m_currentState)
332 m_undoStack.push(m_currentState);
333
334 m_currentState = new ItemDocumentData( type() );
335 m_currentState->saveDocumentState(this);
336
337 if (!m_savedState)
338 m_savedState = m_currentState;
339
340 setModified( m_savedState != m_currentState );
341
342 emit undoRedoStateChanged();
343
344 //FIXME To resize undo queue, have to pop and push everything
345 // In Qt4 QStack is used and QStack inherits QVector, that should
346 // make it a bit more easy
347 int maxUndo = KTLConfig::maxUndo();
348 if ( maxUndo <= 0 || m_undoStack.count() < maxUndo )
349 return;
350 IDDStack tempStack;
351 int pushed = 0;
352 while ( !m_undoStack.isEmpty() && pushed < maxUndo ) {
353 tempStack.push( m_undoStack.pop() );
354 pushed++;
355 }
356 cleanClearStack( m_undoStack );
357 while ( !tempStack.isEmpty() )
358 m_undoStack.push( tempStack.pop() );
359 }
360
cleanClearStack(IDDStack & stack)361 void ItemDocument::cleanClearStack( IDDStack &stack )
362 {
363 while ( !stack.isEmpty() )
364 {
365 ItemDocumentData * idd = stack.pop();
366 if ( m_currentState != idd )
367 delete idd;
368 }
369 }
370
clearHistory()371 void ItemDocument::clearHistory()
372 {
373 cleanClearStack( m_undoStack );
374 cleanClearStack( m_redoStack );
375 delete m_currentState;
376 m_currentState = nullptr;
377 requestStateSave();
378 }
379
380
isUndoAvailable() const381 bool ItemDocument::isUndoAvailable() const
382 {
383 return !m_undoStack.isEmpty();
384 }
385
386
isRedoAvailable() const387 bool ItemDocument::isRedoAvailable() const
388 {
389 return !m_redoStack.isEmpty();
390 }
391
392
undo()393 void ItemDocument::undo()
394 {
395 if (m_undoStack.empty()) {
396 return;
397 }
398 ItemDocumentData *idd = m_undoStack.pop();
399 if (!idd) return;
400
401 if (m_currentState) m_redoStack.push(m_currentState);
402
403 idd->restoreDocument(this);
404 m_currentState = idd;
405
406 setModified( m_savedState != m_currentState );
407 emit undoRedoStateChanged();
408 }
409
redo()410 void ItemDocument::redo()
411 {
412 if (m_redoStack.empty()) {
413 return;
414 }
415 ItemDocumentData *idd = m_redoStack.pop();
416 if (!idd) return;
417
418 if (m_currentState)
419 m_undoStack.push(m_currentState);
420
421 idd->restoreDocument(this);
422 m_currentState = idd;
423
424 setModified( m_savedState != m_currentState );
425 emit undoRedoStateChanged();
426 }
427
cut()428 void ItemDocument::cut()
429 {
430 copy();
431 deleteSelection();
432 }
433
434
paste()435 void ItemDocument::paste()
436 {
437 QString xml = QApplication::clipboard()->text( QClipboard::Clipboard );
438 if ( xml.isEmpty() )
439 return;
440
441 unselectAll();
442
443 ItemDocumentData data( type() );
444
445 if ( !data.fromXML(xml) )
446 return;
447
448 data.generateUniqueIDs(this);
449 // data.translateContents( 64, 64 );
450 data.mergeWithDocument( this, true );
451
452 // Get rid of any garbage that shouldn't be around / merge connectors / etc
453 flushDeleteList();
454
455 requestStateSave();
456 }
457
458
itemWithID(const QString & id)459 Item *ItemDocument::itemWithID( const QString &id )
460 {
461 if ( m_itemList.contains( id ) )
462 return m_itemList[id];
463 else return nullptr;
464 }
465
466
unselectAll()467 void ItemDocument::unselectAll()
468 {
469 selectList()->removeAllItems();
470 }
471
472
select(KtlQCanvasItem * item)473 void ItemDocument::select( KtlQCanvasItem * item )
474 {
475 if (!item) return;
476
477 item->setSelected( selectList()->contains( item ) || selectList()->addQCanvasItem( item ) );
478 }
479
480
select(const KtlQCanvasItemList & list)481 void ItemDocument::select( const KtlQCanvasItemList & list )
482 {
483 const KtlQCanvasItemList::const_iterator end = list.end();
484 for ( KtlQCanvasItemList::const_iterator it = list.begin(); it != end; ++it )
485 selectList()->addQCanvasItem(*it);
486
487 selectList()->setSelected(true);
488 }
489
490
unselect(KtlQCanvasItem * qcanvasItem)491 void ItemDocument::unselect( KtlQCanvasItem *qcanvasItem )
492 {
493 selectList()->removeQCanvasItem(qcanvasItem);
494 qcanvasItem->setSelected(false);
495 }
496
497
slotUpdateConfiguration()498 void ItemDocument::slotUpdateConfiguration()
499 {
500 updateBackground();
501 m_canvas->setUpdatePeriod( int(1000./KTLConfig::refreshRate()) );
502 }
503
504
itemAtTop(const QPoint & pos) const505 KtlQCanvasItem* ItemDocument::itemAtTop( const QPoint &pos ) const
506 {
507 KtlQCanvasItemList list = m_canvas->collisions( QRect( pos.x()-1, pos.y()-1, 3, 3 ) ); // note: m_canvas is actually modified here
508 KtlQCanvasItemList::const_iterator it = list.begin();
509 const KtlQCanvasItemList::const_iterator end = list.end();
510
511 while ( it != end ) {
512 KtlQCanvasItem *item = *it;
513 if( !dynamic_cast<Item*>(item) &&
514 !dynamic_cast<ConnectorLine*>(item) &&
515 !dynamic_cast<Node*>(item) &&
516 !dynamic_cast<Widget*>(item) &&
517 !dynamic_cast<ResizeHandle*>(item) )
518 {
519 ++it;
520 } else {
521 if ( ConnectorLine * l = dynamic_cast<ConnectorLine*>(item) )
522 return l->parent();
523
524 return item;
525 }
526 }
527
528 return nullptr;
529 }
530
531
532 // these look dangerous., see todo in header file.
alignHorizontally()533 void ItemDocument::alignHorizontally( )
534 {
535 selectList()->slotAlignHorizontally();
536 if ( ICNDocument *icnd = dynamic_cast<ICNDocument*>(this) )
537 icnd->requestRerouteInvalidatedConnectors();
538 }
539
alignVertically()540 void ItemDocument::alignVertically( )
541 {
542 selectList()->slotAlignVertically();
543 if ( ICNDocument *icnd = dynamic_cast<ICNDocument*>(this) )
544 icnd->requestRerouteInvalidatedConnectors();
545 }
546
distributeHorizontally()547 void ItemDocument::distributeHorizontally( )
548 {
549 selectList()->slotDistributeHorizontally();
550 if ( ICNDocument *icnd = dynamic_cast<ICNDocument*>(this) )
551 icnd->requestRerouteInvalidatedConnectors();
552 }
553
distributeVertically()554 void ItemDocument::distributeVertically( )
555 {
556 selectList()->slotDistributeVertically();
557 if ( ICNDocument *icnd = dynamic_cast<ICNDocument*>(this) )
558 icnd->requestRerouteInvalidatedConnectors();
559 }
560 // ###########################
561
562
563
registerUID(const QString & UID)564 bool ItemDocument::registerUID( const QString &UID )
565 {
566 return m_idList.insert(UID).second;
567 }
568
569
unregisterUID(const QString & uid)570 void ItemDocument::unregisterUID( const QString & uid )
571 {
572 m_idList.erase(uid);
573 m_itemList.remove(uid);
574 }
575
576
generateUID(QString name)577 QString ItemDocument::generateUID( QString name )
578 {
579 name.remove( QRegExp("__.*") ); // Change 'node__13' to 'node', for example
580 QString idAttempt = name;
581
582 while ( !registerUID(idAttempt) )
583 idAttempt = name + "__" + QString::number(m_nextIdNum++);
584
585 return idAttempt;
586 }
587
588 // FIXME: popup menu doesn't seem to work these days. =(
canvasRightClick(const QPoint & pos,KtlQCanvasItem * item)589 void ItemDocument::canvasRightClick( const QPoint &pos, KtlQCanvasItem* item )
590 {
591 if (item) {
592 if ( dynamic_cast<CNItem*>(item) &&
593 !item->isSelected() )
594 {
595 unselectAll();
596 select(item);
597 }
598 }
599
600 KTechlab::self()->unplugActionList("alignment_actionlist");
601 KTechlab::self()->unplugActionList("orientation_actionlist");
602 fillContextMenu(pos);
603
604 QMenu *pop = static_cast<QMenu*>(KTechlab::self()->factory()->container("item_popup", KTechlab::self() ));
605
606 if (!pop) return;
607
608 pop->popup(pos);
609 }
610
611
fillContextMenu(const QPoint & pos)612 void ItemDocument::fillContextMenu( const QPoint & pos )
613 {
614 Q_UNUSED(pos);
615
616 ItemView * activeItemView = dynamic_cast<ItemView*>(activeView());
617 if ( !KTechlab::self() || !activeItemView )
618 return;
619
620 QAction * align_actions[] = {
621 activeItemView->actionByName("align_horizontally"),
622 activeItemView->actionByName("align_vertically"),
623 activeItemView->actionByName("distribute_horizontally"),
624 activeItemView->actionByName("distribute_vertically") };
625
626 bool enableAlignment = selectList()->itemCount() > 1;
627
628 if ( !enableAlignment ) return;
629
630 for ( unsigned i = 0; i < 4; ++i )
631 {
632 align_actions[i]->setEnabled(true);
633 m_pAlignmentAction->removeAction( align_actions[i] );
634 //m_pAlignmentAction->insert( align_actions[i] );
635 m_pAlignmentAction->addAction( align_actions[i] );
636 }
637 QList<QAction*> alignment_actions;
638 alignment_actions.append( m_pAlignmentAction );
639 KTechlab::self()->plugActionList( "alignment_actionlist", alignment_actions );
640 }
641
642
slotInitItemActions()643 void ItemDocument::slotInitItemActions()
644 {
645 ItemView * activeItemView = dynamic_cast<ItemView*>(activeView());
646 if ( !KTechlab::self() || !activeItemView )
647 return;
648
649 QAction * align_actions[] = {
650 activeItemView->actionByName("align_horizontally"),
651 activeItemView->actionByName("align_vertically"),
652 activeItemView->actionByName("distribute_horizontally"),
653 activeItemView->actionByName("distribute_vertically") };
654
655 bool enableAlignment = selectList()->itemCount() > 1;
656 for ( unsigned i = 0; i < 4; ++i )
657 align_actions[i]->setEnabled(enableAlignment);
658 }
659
660
updateBackground()661 void ItemDocument::updateBackground()
662 {
663 // Also used in the constructor to make the background initially.
664
665 // Thoughts.
666 // ~The pixmap could be done somehow with 1bpp. It might save some waste
667 // I expect it won't hurt for now.
668 // ~This is all rather static, only works with square etc... should be no prob. for most uses. IMO.
669 // ~If you want, decide what maximum and minimum spacing should be, then enforce them
670 // in the Config (I suppose you can use <max></max> tags?)
671 // ~Defaults based on the existing grid background png. It should produce identical results, to your
672 // original png.
673
674 // **** Below where it says "interval * 10", that decides how big the pixmap will be (always square)
675 // Originally I set this to 32, which give 256x256 with 8 spacing, as that was the size of your pixmap
676 // Are there any good reasons to make the a certain size? (i.e. big or small ?).
677
678 int interval = 8;
679 int bigness = interval * 10;
680 QPixmap pm( bigness, bigness );
681 // pm.fill( KTLConfig::bgColor() ); // first fill the background colour in
682 pm.fill( Qt::white );
683
684 if( KTLConfig::showGrid() ){
685 //QPainter p(&pm); // setup painter to draw on pixmap
686 QPainter p;
687 const bool isSuccess = p.begin(&pm);
688 if (!isSuccess) {
689 qWarning() << Q_FUNC_INFO << " painter is not active";
690 }
691 p.setPen( KTLConfig::gridColor() ); // set forecolour
692 // note: anything other than 8 borks this
693 for( int i = (interval / 2); i < bigness; i+=interval ){
694 p.drawLine( 0, i, bigness, i ); // horizontal
695 p.drawLine( i, 0, i, bigness ); // vertical
696 }
697 p.end(); // all done
698 }
699
700 //pm.setDefaultOptimization( QPixmap::BestOptim ); // TODO no longer available?
701 m_canvas->setBackgroundPixmap(pm); // and the finale.
702 }
703
704
requestCanvasResize()705 void ItemDocument::requestCanvasResize()
706 {
707 requestEvent( ItemDocumentEvent::ResizeCanvasToItems );
708 }
709
710
requestEvent(ItemDocumentEvent::type type)711 void ItemDocument::requestEvent( ItemDocumentEvent::type type )
712 {
713 m_queuedEvents |= type;
714 m_pEventTimer->stop();
715 m_pEventTimer->setSingleShot(true);
716 m_pEventTimer->start( 0 /*, true */ );
717 }
718
719
processItemDocumentEvents()720 void ItemDocument::processItemDocumentEvents()
721 {
722 // Copy it incase we have new events requested while doing this...
723 unsigned queuedEvents = m_queuedEvents;
724 m_queuedEvents = 0;
725
726 if ( queuedEvents & ItemDocumentEvent::ResizeCanvasToItems )
727 resizeCanvasToItems();
728
729 if ( queuedEvents & ItemDocumentEvent::UpdateZOrdering )
730 slotUpdateZOrdering();
731
732 ICNDocument * icnd = dynamic_cast<ICNDocument*>(this);
733
734 if ( icnd && (queuedEvents & ItemDocumentEvent::UpdateNodeGroups) )
735 icnd->slotAssignNodeGroups();
736
737 if ( icnd && (queuedEvents & ItemDocumentEvent::RerouteInvalidatedConnectors) )
738 icnd->rerouteInvalidatedConnectors();
739 }
740
741
resizeCanvasToItems()742 void ItemDocument::resizeCanvasToItems()
743 {
744 QRect bound = canvasBoundingRect();
745
746 m_viewList.removeAll((View*)nullptr);
747 const ViewList::iterator end = m_viewList.end();
748 for ( ViewList::iterator it = m_viewList.begin(); it != end; ++it ) {
749 ItemView * iv = static_cast<ItemView*>((View*)*it);
750 CVBEditor * cvbEditor = iv->cvbEditor();
751
752 QPoint topLeft = iv->mousePosToCanvasPos( QPoint( 0, 0 ) );
753 int width = int( cvbEditor->visibleWidth() / iv->zoomLevel() );
754 int height = int( cvbEditor->visibleHeight() / iv->zoomLevel() );
755 QRect r( topLeft, QSize( width, height ) );
756
757 bound |= r;
758
759 // qDebug() << "r="<<r<<endl;
760 // qDebug() << "bound="<<bound<<endl;
761 }
762
763 // Make it so that the rectangular offset is a multiple of 8
764 bound.setLeft( bound.left() - (bound.left()%8) );
765 bound.setTop( bound.top() - (bound.top()%8) );
766
767 m_pUpdateItemViewScrollbarsTimer->setSingleShot(true);
768 m_pUpdateItemViewScrollbarsTimer->start( 10 /*, true */ );
769
770 bool changedSize = canvas()->rect() != bound;
771 if ( changedSize ) {
772 canvas()->resize( bound );
773 requestEvent( ItemDocumentEvent::ResizeCanvasToItems );
774 } else if ( ICNDocument * icnd = dynamic_cast<ICNDocument*>(this) ) {
775 icnd->createCellMap();
776 }
777 }
778
779
updateItemViewScrollbars()780 void ItemDocument::updateItemViewScrollbars()
781 {
782 int w = canvas()->width();
783 int h = canvas()->height();
784
785 const ViewList::iterator end = m_viewList.end();
786 for ( ViewList::iterator it = m_viewList.begin(); it != end; ++it )
787 {
788 ItemView * itemView = static_cast<ItemView*>((View*)*it);
789 CVBEditor * cvbEditor = itemView->cvbEditor();
790 // TODO QT3
791 cvbEditor->setVScrollBarMode( ((h*itemView->zoomLevel()) > cvbEditor->visibleHeight()) ? KtlQ3ScrollView::AlwaysOn : KtlQ3ScrollView::AlwaysOff );
792 cvbEditor->setHScrollBarMode( ((w*itemView->zoomLevel()) > cvbEditor->visibleWidth()) ? KtlQ3ScrollView::AlwaysOn : KtlQ3ScrollView::AlwaysOff );
793 }
794 }
795
796
canvasBoundingRect() const797 QRect ItemDocument::canvasBoundingRect() const
798 {
799 QRect bound;
800
801 // Don't include items used for dragging
802 Item *dragItem = nullptr;
803 const ViewList::const_iterator viewsEnd = m_viewList.end();
804 for ( ViewList::const_iterator it = m_viewList.begin(); it != viewsEnd; ++it )
805 {
806 dragItem = (static_cast<ItemView*>((View*)*it))->dragItem();
807 if ( dragItem ) break;
808 }
809
810 const KtlQCanvasItemList allItems = canvas()->allItems();
811 const KtlQCanvasItemList::const_iterator end = allItems.end();
812
813 for ( KtlQCanvasItemList::const_iterator it = allItems.begin(); it != end; ++it )
814 {
815 if( !(*it)->isVisible() ) continue;
816
817 if(dragItem ) {
818 if(*it == dragItem ) continue;
819
820 if(Node *n = dynamic_cast<Node*>(*it) ) {
821 if ( n->parentItem() == dragItem )
822 continue;
823 }
824
825 if(GuiPart *gp = dynamic_cast<GuiPart*>(*it) ) {
826 if ( gp->parent() == dragItem )
827 continue;
828 }
829 }
830
831 bound |= (*it)->boundingRect();
832 }
833
834 if ( !bound.isNull() )
835 {
836 bound.setLeft( bound.left() - 16 );
837 bound.setTop( bound.top() - 16 );
838 bound.setRight( bound.right() + 16 );
839 bound.setBottom( bound.bottom() + 16 );
840 }
841
842 return bound;
843 }
844
845
exportToImage()846 void ItemDocument::exportToImage()
847 {
848 // scaralously copied from print.
849 // this slot is called whenever the File->Export menu is selected,
850 // the Export shortcut is pressed or the Export toolbar
851 // button is clicked
852
853 // we need an object so we can retrieve which image type was selected by the user
854 // so setup the filedialog.
855 ImageExportDialog exportDialog(KTechlab::self());
856
857 // now actually show it
858 if ( exportDialog.exec() == QDialog::Rejected )
859 return;
860 const QString filePath = exportDialog.filePath();
861
862 if ( filePath.isEmpty() ) return;
863
864 if ( QFile::exists(filePath) )
865 {
866 int query = KMessageBox::warningYesNo(
867 KTechlab::self(),
868 i18n( "A file named \"%1\" already exists. " "Are you sure you want to overwrite it?", filePath ),
869 i18n( "Overwrite File?" ));
870
871 if ( query == KMessageBox::No ) return;
872 }
873
874 const bool crop = exportDialog.isCropSelected();
875 // with Qt, you always "print" to a
876 // QPainter.. whether the output medium is a pixmap, a screen,
877 // or paper
878
879 // needs to be something like QPicture to do SVG etc...
880
881 QRect saveArea;
882 QRect cropArea;
883 QPaintDevice *outputImage;
884 const QString type = exportDialog.formatType();
885
886 // did have a switch here but seems you can't use that on strings
887 if ( type == "SVG" ) {
888 KMessageBox::information( nullptr, i18n("SVG export is sub-functional"), i18n("Export As Image") );
889 }
890
891 if (crop) {
892 cropArea = canvasBoundingRect();
893 if ( cropArea.isNull() ) {
894 KMessageBox::sorry( nullptr, i18n("There is nothing to crop"), i18n("Export As Image") );
895 return;
896 } else {
897 cropArea &= canvas()->rect();
898 }
899 }
900
901 saveArea = m_canvas->rect();
902
903 if ( type == "PNG" || type == "BMP" )
904 outputImage = new QPixmap( saveArea.size() );
905 else if ( type == "SVG" ) {
906 setSVGExport(true);
907 outputImage = new QPicture();
908 // svg can't be cropped using the qimage method.
909 saveArea = cropArea;
910 } else {
911 qWarning() << "Unknown type!" << endl;
912 return;
913 }
914
915 //2018.05.05 - extract to a method
916 // //QPainter p(outputImage); // 2016.05.03 - explicitly initialize painter
917 // QPainter p;
918 // const bool isBeginSuccess = p.begin(outputImage);
919 // if (!isBeginSuccess) {
920 // qWarning() << Q_FUNC_INFO << " painter not active";
921 // }
922 //
923 // m_canvas->setBackgroundPixmap(QPixmap());
924 // m_canvas->drawArea( saveArea, &p );
925 // updateBackground();
926 //
927 // p.end();
928 exportToImageDraw(saveArea, *outputImage);
929
930 bool saveResult;
931
932 // if cropping we need to convert to an image,
933 // crop, then save.
934 if (crop) {
935 if( type == "SVG" )
936 saveResult = dynamic_cast<QPicture*>(outputImage)->save(filePath, type.toLatin1().data());
937 else {
938 QImage img = dynamic_cast<QPixmap*>(outputImage)->toImage();
939 if ( saveArea.x() < 0 ) {
940 cropArea.translate( - saveArea.x(), 0 );
941 }
942 if ( saveArea.y() < 0 ) {
943 cropArea.translate( 0, - saveArea.y() );
944 }
945 qDebug() << Q_FUNC_INFO << " cropArea " << cropArea;
946 QImage imgCropped = img.copy(cropArea);
947 saveResult = imgCropped.save(filePath,type.toLatin1().data());
948 }
949 } else {
950 if ( type=="SVG" )
951 saveResult = dynamic_cast<QPicture*>(outputImage)->save( filePath, type.toLatin1().data() );
952 else saveResult = dynamic_cast<QPixmap*>(outputImage)->save( filePath, type.toLatin1().data() );
953 }
954
955 //if(saveResult == true) KMessageBox::information( this, i18n("Sucessfully exported to \"%1\"", url.filename() ), i18n("Image Export") );
956 //else KMessageBox::information( this, i18n("Export failed"), i18n("Image Export") );
957
958 if ( type == "SVG" ) setSVGExport(false);
959
960 if (saveResult == false)
961 KMessageBox::information( KTechlab::self(), i18n("Export failed"), i18n("Image Export") );
962
963 delete outputImage;
964 }
965
exportToImageDraw(const QRect & saveArea,QPaintDevice & pDev)966 void ItemDocument::exportToImageDraw( const QRect &saveArea, QPaintDevice &pDev) {
967 qDebug() << Q_FUNC_INFO << " saveArea " << saveArea;
968 //QPainter p(outputImage); // 2016.05.03 - explicitly initialize painter
969 QPainter p;
970 const bool isBeginSuccess = p.begin(&pDev);
971 if (!isBeginSuccess) {
972 qWarning() << Q_FUNC_INFO << " painter not active";
973 }
974
975 QTransform transf;
976 transf.translate( -saveArea.x(), -saveArea.y());
977 p.setTransform(transf);
978
979 m_canvas->setBackgroundPixmap(QPixmap());
980 m_canvas->drawArea( saveArea, &p );
981 updateBackground();
982
983 p.end();
984 }
985
setSVGExport(bool svgExport)986 void ItemDocument::setSVGExport( bool svgExport )
987 {
988 // Find any items and tell them not to draw buttons or sliders
989 KtlQCanvasItemList items = m_canvas->allItems();
990 const KtlQCanvasItemList::iterator end = items.end();
991 for ( KtlQCanvasItemList::Iterator it = items.begin(); it != end; ++it )
992 {
993 if ( CNItem * cnItem = dynamic_cast<CNItem*>(*it) )
994 cnItem->setDrawWidgets(!svgExport);
995 }
996 }
997
raiseZ()998 void ItemDocument::raiseZ()
999 {
1000 raiseZ( selectList()->items(true) );
1001 }
raiseZ(const ItemList & itemList)1002 void ItemDocument::raiseZ( const ItemList & itemList )
1003 {
1004 if ( m_zOrder.isEmpty() ) slotUpdateZOrdering();
1005
1006 if ( m_zOrder.isEmpty() ) return;
1007
1008 IntItemMap::iterator begin = m_zOrder.begin();
1009 IntItemMap::iterator previous = m_zOrder.end();
1010 IntItemMap::iterator it = --m_zOrder.end();
1011 do {
1012 Item * previousData = (previous == m_zOrder.end()) ? nullptr : previous.value();
1013 Item * currentData = it.value();
1014
1015 if ( currentData && previousData && itemList.contains(currentData) && !itemList.contains(previousData) )
1016 {
1017 previous.value() = currentData;
1018 it.value() = previousData;
1019 }
1020
1021 previous = it;
1022 --it;
1023 } while ( previous != begin );
1024
1025 slotUpdateZOrdering();
1026 }
1027
1028
lowerZ()1029 void ItemDocument::lowerZ()
1030 {
1031 lowerZ( selectList()->items(true) );
1032 }
1033
lowerZ(const ItemList & itemList)1034 void ItemDocument::lowerZ( const ItemList &itemList )
1035 {
1036 if ( m_zOrder.isEmpty() ) slotUpdateZOrdering();
1037
1038 if ( m_zOrder.isEmpty() ) return;
1039
1040 IntItemMap::iterator previous = m_zOrder.begin();
1041 IntItemMap::iterator end = m_zOrder.end();
1042 for ( IntItemMap::iterator it = m_zOrder.begin(); it != end; ++it )
1043 {
1044 Item * previousData = previous.value();
1045 Item * currentData = it.value();
1046
1047 if ( currentData && previousData && itemList.contains(currentData) && !itemList.contains(previousData) )
1048 {
1049 previous.value() = currentData;
1050 it.value() = previousData;
1051 }
1052
1053 previous = it;
1054 }
1055
1056 slotUpdateZOrdering();
1057 }
1058
1059
itemAdded(Item *)1060 void ItemDocument::itemAdded( Item * )
1061 {
1062 requestEvent( ItemDocument::ItemDocumentEvent::UpdateZOrdering );
1063 }
1064
1065
slotUpdateZOrdering()1066 void ItemDocument::slotUpdateZOrdering()
1067 {
1068 ItemMap toAdd = m_itemList;
1069
1070 IntItemMap newZOrder;
1071 int atLevel = 0;
1072
1073 IntItemMap::iterator zEnd = m_zOrder.end();
1074 for ( IntItemMap::iterator it = m_zOrder.begin(); it != zEnd; ++it )
1075 {
1076 Item * item = it.value();
1077 if (!item) continue;
1078
1079 toAdd.remove( item->id() );
1080
1081 if ( !item->parentItem() && item->isMovable() )
1082 newZOrder[atLevel++] = item;
1083 }
1084
1085 ItemMap::iterator addEnd = toAdd.end();
1086 for ( ItemMap::iterator it = toAdd.begin(); it != addEnd; ++it )
1087 {
1088 Item * item = *it;
1089 if ( item->parentItem() || !item->isMovable() )
1090 continue;
1091
1092 newZOrder[atLevel++] = item;
1093 }
1094
1095 m_zOrder = newZOrder;
1096
1097 zEnd = m_zOrder.end();
1098 for ( IntItemMap::iterator it = m_zOrder.begin(); it != zEnd; ++it )
1099 it.value()->updateZ( it.key() );
1100 }
1101
1102
update()1103 void ItemDocument::update( )
1104 {
1105 ItemMap::iterator end = m_itemList.end();
1106 for ( ItemMap::iterator it = m_itemList.begin(); it != end; ++it )
1107 {
1108 if ( (*it)->contentChanged() )
1109 (*it)->setChanged();
1110 }
1111 }
1112
1113
itemList() const1114 ItemList ItemDocument::itemList( ) const
1115 {
1116 ItemList l;
1117
1118 ItemMap::const_iterator end = m_itemList.end();
1119 for ( ItemMap::const_iterator it = m_itemList.begin(); it != end; ++it )
1120 l << it.value();
1121
1122 return l;
1123 }
1124 //END class ItemDocument
1125
1126
1127
1128 //BEGIN class CanvasTip
CanvasTip(ItemDocument * itemDocument,KtlQCanvas * qcanvas)1129 CanvasTip::CanvasTip( ItemDocument *itemDocument, KtlQCanvas *qcanvas )
1130 : KtlQCanvasRectangle( qcanvas )
1131 {
1132 p_itemDocument = itemDocument;
1133
1134 setZ( ICNDocument::Z::Tip );
1135 }
1136
~CanvasTip()1137 CanvasTip::~CanvasTip()
1138 {
1139 }
1140
displayVI(ECNode * node,const QPoint & pos)1141 void CanvasTip::displayVI( ECNode *node, const QPoint &pos )
1142 {
1143 if ( !node || !updateVI() )
1144 return;
1145
1146 unsigned num = node->numPins();
1147
1148 m_v.resize(num);
1149 m_i.resize(num);
1150
1151 for ( unsigned i = 0; i < num; i++ )
1152 {
1153 if ( Pin * pin = node->pin(i) )
1154 {
1155 m_v[i] = pin->voltage();
1156 m_i[i] = pin->current();
1157 }
1158 }
1159
1160 display(pos);
1161 }
1162
1163
displayVI(Connector * connector,const QPoint & pos)1164 void CanvasTip::displayVI( Connector *connector, const QPoint &pos )
1165 {
1166 if ( !connector || !updateVI())
1167 return;
1168
1169 unsigned num = connector->numWires();
1170
1171 m_v.resize(num);
1172 m_i.resize(num);
1173
1174 for ( unsigned i = 0; i < num; i++ )
1175 {
1176 if ( Wire * wire = connector->wire(i) )
1177 {
1178 m_v[i] = wire->voltage();
1179 m_i[i] = std::abs(wire->current());
1180 }
1181 }
1182
1183 display(pos);
1184 }
1185
1186
updateVI()1187 bool CanvasTip::updateVI()
1188 {
1189 CircuitDocument *circuitDocument = dynamic_cast<CircuitDocument*>(p_itemDocument);
1190 if ( !circuitDocument || !Simulator::self()->isSimulating() )
1191 return false;
1192
1193 circuitDocument->calculateConnectorCurrents();
1194 return true;
1195 }
1196
1197
display(const QPoint & pos)1198 void CanvasTip::display( const QPoint &pos )
1199 {
1200 unsigned num = m_v.size();
1201
1202 for ( unsigned i = 0; i < num; i++ ) {
1203 if ( !std::isfinite(m_v[i]) || std::abs(m_v[i]) < 1e-9 )
1204 m_v[i] = 0.;
1205
1206 if ( !std::isfinite(m_i[i]) || std::abs(m_i[i]) < 1e-9 )
1207 m_i[i] = 0.;
1208 }
1209
1210 move( pos.x()+20, pos.y()+4 );
1211
1212 if ( num == 0 ) return;
1213
1214 if ( num == 1 )
1215 setText( displayText(0) );
1216 else {
1217 QString text;
1218 for ( unsigned i = 0; i < num; i++ )
1219 text += QString("%1: %2\n").arg( QString::number(i) ).arg( displayText(i) );
1220 setText(text);
1221 }
1222 }
1223
1224
displayText(unsigned num) const1225 QString CanvasTip::displayText( unsigned num ) const
1226 {
1227 if ( m_v.size() <= (int)num )
1228 return QString::null;
1229
1230 return QString("%1%2V %3%4A")
1231 .arg( QString::number( m_v[num] / CNItem::getMultiplier(m_v[num]), 'g', 3 ) )
1232 .arg( CNItem::getNumberMag( m_v[num] ) )
1233 .arg( QString::number( m_i[num] / CNItem::getMultiplier(m_i[num]), 'g', 3 ) )
1234 .arg( CNItem::getNumberMag( m_i[num] ) );
1235 }
1236
1237
draw(QPainter & p)1238 void CanvasTip::draw( QPainter &p )
1239 {
1240 CircuitDocument *circuitDocument = dynamic_cast<CircuitDocument*>(p_itemDocument);
1241 if ( !circuitDocument || !Simulator::self()->isSimulating() )
1242 return;
1243
1244 p.setBrush( QColor( 0xff, 0xff, 0xdc ) );
1245 p.setPen( Qt::black );
1246 p.drawRect( boundingRect() );
1247
1248 QRect textRect = boundingRect();
1249 textRect.setLeft( textRect.left() + 3 );
1250 textRect.setTop( textRect.top() + 1 );
1251 p.drawText( textRect, 0, m_text );
1252 }
1253
1254
setText(const QString & text)1255 void CanvasTip::setText( const QString & text )
1256 {
1257 m_text = text;
1258 canvas()->setChanged( boundingRect() );
1259
1260 QRect r = QFontMetrics( qApp->font() ).boundingRect( 0, 0, 0, 0, 0, m_text );
1261 setSize( r.width() + 4, r.height() - 1 );
1262 }
1263 //END class CanvasTip
1264
1265
1266 //BEGIN class Canvas
Canvas(ItemDocument * itemDocument,const char * name)1267 Canvas::Canvas( ItemDocument *itemDocument, const char * name )
1268 : KtlQCanvas( itemDocument, name )
1269 {
1270 p_itemDocument = itemDocument;
1271 m_pMessageTimeout = new QTimer(this);
1272 connect( m_pMessageTimeout, SIGNAL(timeout()), this, SLOT(slotSetAllChanged()) );
1273 }
1274
1275
resize(const QRect & size)1276 void Canvas::resize( const QRect & size )
1277 {
1278 if ( rect() == size )
1279 return;
1280 QRect oldSize = rect();
1281 KtlQCanvas::resize( size );
1282 emit resized( oldSize, size );
1283 }
1284
1285
setMessage(const QString & message)1286 void Canvas::setMessage( const QString & message )
1287 {
1288 m_message = message;
1289
1290 if ( message.isEmpty() ) {
1291 m_pMessageTimeout->stop();
1292 } else {
1293 m_pMessageTimeout->setSingleShot(true);
1294 m_pMessageTimeout->start( 2000 /*, true */ );
1295 }
1296
1297 setAllChanged();
1298 }
1299
1300
drawBackground(QPainter & p,const QRect & clip)1301 void Canvas::drawBackground ( QPainter &p, const QRect & clip )
1302 {
1303 KtlQCanvas::drawBackground( p, clip );
1304 #if 0
1305 const int scx = (int)((clip.left()-4)/8);
1306 const int ecx = (int)((clip.right()+4)/8);
1307 const int scy = (int)((clip.top()-4)/8);
1308 const int ecy = (int)((clip.bottom()+4)/8);
1309
1310 ICNDocument * icnd = dynamic_cast<ICNDocument*>(p_itemDocument);
1311 if ( !icnd )
1312 return;
1313
1314 Cells * c = icnd->cells();
1315
1316 if ( !c->haveCell( scx, scy ) || !c->haveCell( ecx, ecy ) )
1317 return;
1318
1319 for ( int x=scx; x<=ecx; x++ )
1320 {
1321 for ( int y=scy; y<=ecy; y++ )
1322 {
1323 const double score = c->cell( x, y ).CIpenalty + c->cell( x, y ).Cpenalty;
1324 int value = (int)std::log(score)*20;
1325 if ( value>255 )
1326 value=255;
1327 else if (value<0 )
1328 value=0;
1329 p.setBrush( QColor( 255, (255-value), (255-value) ) );
1330 p.setPen( Qt::NoPen );
1331 p.drawRect( (x*8), (y*8), 8, 8 );
1332 }
1333 }
1334 #endif
1335 }
1336
1337
drawForeground(QPainter & p,const QRect & clip)1338 void Canvas::drawForeground ( QPainter &p, const QRect & clip )
1339 {
1340 KtlQCanvas::drawForeground( p, clip );
1341
1342 if ( !m_pMessageTimeout->isActive() )
1343 return;
1344
1345 // Following code stolen and adapted from amarok/src/playlist.cpp :)
1346
1347 // Find out width of smallest view
1348 QSize minSize;
1349 const ViewList viewList = p_itemDocument->viewList();
1350 ViewList::const_iterator end = viewList.end();
1351 View * firstView = nullptr;
1352 for ( ViewList::const_iterator it = viewList.begin(); it != end; ++it )
1353 {
1354 if ( !*it ) continue;
1355
1356 if ( !firstView )
1357 {
1358 firstView = *it;
1359 minSize = (*it)->size();
1360 } else minSize = minSize.boundedTo( (*it)->size() );
1361 }
1362
1363 if ( !firstView ) return;
1364
1365 // Q3SimpleRichText * t = new Q3SimpleRichText( m_message, QApplication::font() );
1366 QTextEdit * t = new QTextEdit( m_message );
1367
1368 {
1369 QFont tf = t->document()->defaultFont();
1370 QFontMetrics tfm(tf);
1371 QSize textSize = tfm.size(0, m_message);
1372
1373 t->resize( textSize );
1374 }
1375
1376 int w = t->width();
1377 int h = t->height();
1378 int x = rect().left() + 15;
1379 int y = rect().top() + 15;
1380 int b = 10; // text padding
1381
1382 // if ( w+2*b >= minSize.width() || h+2*b >= minSize.height() )
1383 // {
1384 // qWarning() << Q_FUNC_INFO << "size not good w=" << w << " h=" << h << "b=" << b << " minSize=" << minSize;
1385 // delete t;
1386 // return;
1387 // }
1388
1389 //p.setBrush( firstView->colorGroup().background() ); // 2018.12.02
1390 p.setBrush( firstView->palette().window() );
1391 p.drawRoundRect( x, y, w+2*b, h+2*b, (8*200)/(w+2*b), (8*200)/(h+2*b) );
1392 // t->draw( &p, x+b, y+b, QRect(), firstView->colorGroup() );
1393 t->resize(w+2*b, h+2*b);
1394 t->viewport()->setAutoFillBackground( false );
1395 t->setFrameStyle(QFrame::NoFrame);
1396 t->render( &p, QPoint( x, y ) , QRegion(), QWidget::DrawChildren );
1397 delete t;
1398 }
1399
1400
update()1401 void Canvas::update()
1402 {
1403 p_itemDocument->update();
1404 KtlQCanvas::update();
1405 }
1406 //END class Canvas
1407