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