1 /*******************************************************************
2 
3 Part of the Fritzing project - http://fritzing.org
4 Copyright (c) 2007-2014 Fachhochschule Potsdam - http://fh-potsdam.de
5 
6 Fritzing is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10 
11 Fritzing is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU General Public License for more details.
15 
16 You should have received a copy of the GNU General Public License
17 along with Fritzing.  If not, see <http://www.gnu.org/licenses/>.
18 
19 ********************************************************************
20 
21 $Revision: 6980 $:
22 $Author: irascibl@gmail.com $:
23 $Date: 2013-04-22 01:45:43 +0200 (Mo, 22. Apr 2013) $
24 
25 ********************************************************************/
26 
27 #include "note.h"
28 #include "../debugdialog.h"
29 #include "../sketch/infographicsview.h"
30 #include "../model/modelpart.h"
31 #include "../utils/resizehandle.h"
32 #include "../utils/textutils.h"
33 #include "../utils/graphicsutils.h"
34 #include "../fsvgrenderer.h"
35 
36 #include <QTextFrame>
37 #include <QTextLayout>
38 #include <QTextFrameFormat>
39 #include <QApplication>
40 #include <QTextDocumentFragment>
41 #include <QTimer>
42 #include <QFormLayout>
43 #include <QGroupBox>
44 #include <QLineEdit>
45 #include <QDialogButtonBox>
46 #include <QPushButton>
47 
48 // TODO:
49 //		** search for ModelPart:: and fix up
50 //		check which menu items don't apply
51 //		** selection
52 //		** delete
53 //		** move
54 //		** undo delete + text
55 //		** resize
56 //		** undo resize
57 //		anchor
58 //		** undo change text
59 //		** undo selection
60 //		** undo move
61 //		** layers and z order
62 //		** hide and show layer
63 //		** save and load
64 //		format: bold, italic, size (small normal large huge), color?,
65 //		undo format
66 //		heads-up controls
67 //		copy/paste
68 //		** z-order manipulation
69 //		hover
70 //		** multiple selection
71 //		** icon in taskbar (why does it show up as text until you update it?)
72 
73 const int Note::emptyMinWidth = 40;
74 const int Note::emptyMinHeight = 25;
75 const int Note::initialMinWidth = 140;
76 const int Note::initialMinHeight = 45;
77 const int borderWidth = 7;
78 const int TriangleOffset = 7;
79 
80 const double InactiveOpacity = 0.5;
81 
82 QString Note::initialTextString;
83 
84 QRegExp UrlTag("<a.*href=[\"']([^\"]+[.\\s]*)[\"'].*>");
85 
86 ///////////////////////////////////////
87 
findA(QDomElement element,QList<QDomElement> & aElements)88 void findA(QDomElement element, QList<QDomElement> & aElements)
89 {
90 	if (element.tagName().compare("a", Qt::CaseInsensitive) == 0) {
91 		aElements.append(element);
92 		return;
93 	}
94 
95 	QDomElement c = element.firstChildElement();
96 	while (!c.isNull()) {
97 		findA(c, aElements);
98 
99 		c = c.nextSiblingElement();
100 	}
101 }
102 
103 
addText(const QString & text,bool inUrl)104 QString addText(const QString & text, bool inUrl)
105 {
106 	if (text.isEmpty()) return "";
107 
108 	return QString("<tspan fill='%1' >%2</tspan>\n")
109 				.arg(inUrl ? "#0000ff" : "#000000")
110 				.arg(TextUtils::convertExtendedChars(TextUtils::escapeAnd(text)))
111 				;
112 }
113 
114 ///////////////////////////////////////
115 
116 class NoteGraphicsTextItem : public QGraphicsTextItem
117 {
118 public:
119 	NoteGraphicsTextItem(QGraphicsItem * parent = NULL);
120 
121 protected:
122 	void focusInEvent(QFocusEvent *);
123 	void focusOutEvent(QFocusEvent *);
124 };
125 
NoteGraphicsTextItem(QGraphicsItem * parent)126 NoteGraphicsTextItem::NoteGraphicsTextItem(QGraphicsItem * parent) : QGraphicsTextItem(parent)
127 {
128 	const QTextFrameFormat format = document()->rootFrame()->frameFormat();
129 	QTextFrameFormat altFormat(format);
130 	altFormat.setMargin(0);										// so document never thinks a mouse click is a move event
131 	document()->rootFrame()->setFrameFormat(altFormat);
132 }
133 
focusInEvent(QFocusEvent * event)134 void NoteGraphicsTextItem::focusInEvent(QFocusEvent * event) {
135 	InfoGraphicsView * igv = InfoGraphicsView::getInfoGraphicsView(this);
136 	if (igv != NULL) {
137 		igv->setNoteFocus(this, true);
138 	}
139 	QApplication::instance()->installEventFilter((Note *) this->parentItem());
140 	QGraphicsTextItem::focusInEvent(event);
141 	DebugDialog::debug("note focus in");
142 }
143 
focusOutEvent(QFocusEvent * event)144 void NoteGraphicsTextItem::focusOutEvent(QFocusEvent * event) {
145 	InfoGraphicsView * igv = InfoGraphicsView::getInfoGraphicsView(this);
146 	if (igv != NULL) {
147 		igv->setNoteFocus(this, false);
148 	}
149 	QApplication::instance()->removeEventFilter((Note *) this->parentItem());
150 	QGraphicsTextItem::focusOutEvent(event);
151 	DebugDialog::debug("note focus out");
152 }
153 
154 //////////////////////////////////////////
155 
LinkDialog(QWidget * parent)156 LinkDialog::LinkDialog(QWidget *parent) : QDialog(parent)
157 {
158 	this->setWindowTitle(QObject::tr("Edit link"));
159 
160 	QVBoxLayout * vLayout = new QVBoxLayout(this);
161 
162 	QGroupBox * formGroupBox = new QGroupBox(this);
163 
164 	QFormLayout * formLayout = new QFormLayout();
165 
166 	m_urlEdit = new QLineEdit(this);
167 	m_urlEdit->setFixedHeight(25);
168 	m_urlEdit->setFixedWidth(200);
169 	formLayout->addRow(tr("url:"), m_urlEdit );
170 
171 	m_textEdit = new QLineEdit(this);
172 	m_textEdit->setFixedHeight(25);
173 	m_textEdit->setFixedWidth(200);
174 	formLayout->addRow(tr("text:"), m_textEdit );
175 
176 	formGroupBox->setLayout(formLayout);
177 
178 	vLayout->addWidget(formGroupBox);
179 
180     QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
181 	buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel"));
182 	buttonBox->button(QDialogButtonBox::Ok)->setText(tr("OK"));
183 
184     connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
185     connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
186 
187 	vLayout->addWidget(buttonBox);
188 
189 	this->setLayout(vLayout);
190 }
191 
~LinkDialog()192 LinkDialog::~LinkDialog() {
193 }
194 
setText(const QString & text)195 void LinkDialog::setText(const QString & text) {
196 	m_textEdit->setText(text);
197 }
198 
setUrl(const QString & url)199 void LinkDialog::setUrl(const QString & url) {
200 	m_urlEdit->setText(url);
201 }
202 
text()203 QString LinkDialog::text() {
204 	return m_textEdit->text();
205 }
206 
url()207 QString LinkDialog::url() {
208 	return m_urlEdit->text();
209 }
210 
211 /////////////////////////////////////////////
212 
Note(ModelPart * modelPart,ViewLayer::ViewID viewID,const ViewGeometry & viewGeometry,long id,QMenu * itemMenu)213 Note::Note( ModelPart * modelPart, ViewLayer::ViewID viewID,  const ViewGeometry & viewGeometry, long id, QMenu* itemMenu)
214 	: ItemBase(modelPart, viewID, viewGeometry, id, itemMenu)
215 {
216 	m_charsAdded = 0;
217 	if (initialTextString.isEmpty()) {
218 		initialTextString = tr("[write your note here]");
219 	}
220 
221 	m_inResize = NULL;
222 	this->setCursor(Qt::ArrowCursor);
223 
224     setFlag(QGraphicsItem::ItemIsSelectable, true);
225 
226 	if (viewGeometry.rect().width() == 0 || viewGeometry.rect().height() == 0) {
227 		m_rect.setRect(0, 0, Note::initialMinWidth, Note::initialMinHeight);
228 	}
229 	else {
230 		m_rect.setRect(0, 0, viewGeometry.rect().width(), viewGeometry.rect().height());
231 	}
232 	m_pen.setWidth(borderWidth);
233 	m_pen.setCosmetic(false);
234     m_pen.setBrush(QColor(0xfa, 0xbc, 0x4f));
235 
236     m_brush.setColor(QColor(0xff, 0xe9, 0xc8));
237 	m_brush.setStyle(Qt::SolidPattern);
238 
239 	setPos(m_viewGeometry.loc());
240 
241 	QPixmap pixmap(":/resources/images/icons/noteResizeGrip.png");
242 	m_resizeGrip = new ResizeHandle(pixmap, Qt::SizeFDiagCursor, false, this);
243 	connect(m_resizeGrip, SIGNAL(mousePressSignal(QGraphicsSceneMouseEvent *, ResizeHandle *)), this, SLOT(handleMousePressSlot(QGraphicsSceneMouseEvent *, ResizeHandle *)));
244 	connect(m_resizeGrip, SIGNAL(mouseMoveSignal(QGraphicsSceneMouseEvent *, ResizeHandle *)), this, SLOT(handleMouseMoveSlot(QGraphicsSceneMouseEvent *, ResizeHandle *)));
245 	connect(m_resizeGrip, SIGNAL(mouseReleaseSignal(QGraphicsSceneMouseEvent *, ResizeHandle *)), this, SLOT(handleMouseReleaseSlot(QGraphicsSceneMouseEvent *, ResizeHandle *)));
246 	//connect(m_resizeGrip, SIGNAL(zoomChangedSignal(double)), this, SLOT(handleZoomChangedSlot(double)));
247 
248 	m_graphicsTextItem = new NoteGraphicsTextItem();
249 	QFont font("Droid Sans", 9, QFont::Normal);
250 	m_graphicsTextItem->setFont(font);
251 	m_graphicsTextItem->document()->setDefaultFont(font);
252 	m_graphicsTextItem->setParentItem(this);
253 	m_graphicsTextItem->setVisible(true);
254 	m_graphicsTextItem->setPlainText(initialTextString);
255 	m_graphicsTextItem->setTextInteractionFlags(Qt::TextEditorInteraction | Qt::LinksAccessibleByMouse | Qt::LinksAccessibleByKeyboard);
256 	m_graphicsTextItem->setCursor(Qt::IBeamCursor);
257 	m_graphicsTextItem->setOpenExternalLinks(true);
258 
259 
260 	connectSlots();
261 
262 	positionGrip();
263 
264 	setAcceptHoverEvents(true);
265 }
266 
saveGeometry()267 void Note::saveGeometry() {
268 	m_viewGeometry.setRect(boundingRect());
269 	m_viewGeometry.setLoc(this->pos());
270 	m_viewGeometry.setSelected(this->isSelected());
271 	m_viewGeometry.setZ(this->zValue());
272 }
273 
itemMoved()274 bool Note::itemMoved() {
275 	return (this->pos() != m_viewGeometry.loc());
276 }
277 
saveInstanceLocation(QXmlStreamWriter & streamWriter)278 void Note::saveInstanceLocation(QXmlStreamWriter & streamWriter) {
279 	QRectF rect = m_viewGeometry.rect();
280 	QPointF loc = m_viewGeometry.loc();
281 	streamWriter.writeAttribute("x", QString::number(loc.x()));
282 	streamWriter.writeAttribute("y", QString::number(loc.y()));
283 	streamWriter.writeAttribute("width", QString::number(rect.width()));
284 	streamWriter.writeAttribute("height", QString::number(rect.height()));
285 }
286 
moveItem(ViewGeometry & viewGeometry)287 void Note::moveItem(ViewGeometry & viewGeometry) {
288 	this->setPos(viewGeometry.loc());
289 }
290 
findConnectorsUnder()291 void Note::findConnectorsUnder() {
292 }
293 
paint(QPainter * painter,const QStyleOptionGraphicsItem * option,QWidget * widget)294 void Note::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
295 {
296 	Q_UNUSED(widget);
297 
298 	if (m_hidden) return;
299 
300 	if (m_inactive) {
301 		painter->save();
302 		painter->setOpacity(InactiveOpacity);
303 	}
304 
305 	painter->setPen(Qt::NoPen);
306 	painter->setBrush(m_brush);
307     QPolygonF poly;
308     poly.append(m_rect.topLeft());
309     poly.append(m_rect.topRight() + QPointF(-TriangleOffset, 0));
310     poly.append(m_rect.topRight() + QPointF(0, TriangleOffset));
311     poly.append(m_rect.bottomRight());
312     poly.append(m_rect.bottomLeft());
313     painter->drawPolygon(poly);
314 
315     painter->setBrush(m_pen.brush());
316     poly.clear();
317     poly.append(m_rect.topRight() + QPointF(-TriangleOffset, 0));
318     poly.append(m_rect.topRight() + QPointF(0, TriangleOffset));
319     poly.append(m_rect.topRight() + QPointF(-TriangleOffset, TriangleOffset));
320     painter->drawPolygon(poly);
321 
322     painter->setPen(m_pen);
323     poly.clear();
324     poly.append(QPointF(0, m_rect.bottom() - borderWidth / 2.0));
325     poly.append(QPointF(m_rect.right(), m_rect.bottom() - borderWidth / 2.0));
326     painter->drawPolygon(poly);
327 
328 	if (option->state & QStyle::State_Selected) {
329 		GraphicsUtils::qt_graphicsItem_highlightSelected(painter, option, boundingRect(), QPainterPath());
330     }
331 
332 	if (m_inactive) {
333 		painter->restore();
334 	}
335 }
336 
boundingRect() const337 QRectF Note::boundingRect() const
338 {
339 	return m_rect;
340 }
341 
shape() const342 QPainterPath Note::shape() const
343 {
344     QPainterPath path;
345     path.addRect(boundingRect());
346     return path;
347 }
348 
positionGrip()349 void Note::positionGrip() {
350 	QSizeF gripSize = m_resizeGrip->boundingRect().size();
351 	QSizeF sz = this->boundingRect().size();
352 	QPointF p(sz.width() - gripSize.width(), sz.height() - gripSize.height());
353 	m_resizeGrip->setPos(p);
354 	m_graphicsTextItem->setPos(TriangleOffset / 2, TriangleOffset / 2);
355 	m_graphicsTextItem->setTextWidth(sz.width() - TriangleOffset);
356 }
357 
mousePressEvent(QGraphicsSceneMouseEvent * event)358 void Note::mousePressEvent(QGraphicsSceneMouseEvent * event) {
359 	InfoGraphicsView *infographics = InfoGraphicsView::getInfoGraphicsView(this);
360 	if (infographics != NULL && infographics->spaceBarIsPressed()) {
361 		m_spaceBarWasPressed = true;
362 		event->ignore();
363 		return;
364 	}
365 
366 	m_spaceBarWasPressed = false;
367 	m_inResize = NULL;
368 	ItemBase::mousePressEvent(event);
369 }
370 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)371 void Note::mouseMoveEvent(QGraphicsSceneMouseEvent * event) {
372 	if (m_spaceBarWasPressed) {
373 		event->ignore();
374 		return;
375 	}
376 
377 	ItemBase::mouseMoveEvent(event);
378 }
379 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)380 void Note::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) {
381 	if (m_spaceBarWasPressed) {
382 		event->ignore();
383 		return;
384 	}
385 
386 	ItemBase::mouseReleaseEvent(event);
387 }
388 
contentsChangeSlot(int position,int charsRemoved,int charsAdded)389 void Note::contentsChangeSlot(int position, int charsRemoved, int charsAdded) {
390 	Q_UNUSED(charsRemoved);
391 
392 	m_charsAdded = charsAdded;
393 	m_charsPosition = position;
394 }
395 
forceFormat(int position,int charsAdded)396 void Note::forceFormat(int position, int charsAdded) {
397 	disconnectSlots();
398 	QTextCursor textCursor = m_graphicsTextItem->textCursor();
399 
400 	QTextCharFormat f;
401 	QFont font("Droid Sans", 9, QFont::Normal);
402 
403 	f.setFont(font);
404 	f.setFontFamily("Droid Sans");
405 	f.setFontPointSize(9);
406 
407 	int cc = m_graphicsTextItem->document()->characterCount();
408 	textCursor.setPosition(position, QTextCursor::MoveAnchor);
409 	if (position + charsAdded >= cc) {
410 		charsAdded--;
411 	}
412 	textCursor.setPosition(position + charsAdded, QTextCursor::KeepAnchor);
413 
414 	//textCursor.setCharFormat(f);
415 	textCursor.mergeCharFormat(f);
416 	//DebugDialog::debug(QString("setting font tc:%1,%2 params:%3,%4")
417 		//.arg(textCursor.anchor()).arg(textCursor.position())
418 		//.arg(position).arg(position + charsAdded));
419 
420 	/*
421 	textCursor = m_graphicsTextItem->textCursor();
422 	for (int i = 0; i < charsAdded; i++) {
423 		textCursor.setPosition(position + i, QTextCursor::MoveAnchor);
424 		f = textCursor.charFormat();
425 		DebugDialog::debug(QString("1format %1 %2 %3").arg(f.fontPointSize()).arg(f.fontFamily()).arg(f.fontWeight()));
426 		//f.setFont(font);
427 		//f.setFontPointSize(9);
428 		//f.setFontWeight(QFont::Normal);
429 		//textCursor.setCharFormat(f);
430 		//QTextCharFormat g = textCursor.charFormat();
431 		//DebugDialog::debug(QString("2format %1 %2 %3").arg(g.fontPointSize()).arg(g.fontFamily()).arg(g.fontWeight()));
432 	}
433 	*/
434 
435 	connectSlots();
436 }
437 
contentsChangedSlot()438 void Note::contentsChangedSlot() {
439 
440     //DebugDialog::debug(QString("contents changed ") + m_graphicsTextItem->document()->toPlainText());
441 	if (m_charsAdded > 0) {
442 		forceFormat(m_charsPosition, m_charsAdded);
443 	}
444 
445 	InfoGraphicsView *infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
446 	if (infoGraphicsView != NULL) {
447 		QString oldText;
448 		if (m_modelPart) {
449 			oldText = m_modelPart->instanceText();
450 		}
451 
452 		QSizeF oldSize = m_rect.size();
453 		QSizeF newSize = oldSize;
454 		checkSize(newSize);
455 
456 		infoGraphicsView->noteChanged(this, oldText, m_graphicsTextItem->document()->toHtml(), oldSize, newSize);
457 	}
458 	if (m_modelPart) {
459 		m_modelPart->setInstanceText(m_graphicsTextItem->document()->toHtml());
460 	}
461 }
462 
checkSize(QSizeF & newSize)463 void Note::checkSize(QSizeF & newSize) {
464 	QSizeF gripSize = m_resizeGrip->boundingRect().size();
465 	QSizeF size = m_graphicsTextItem->document()->size();
466 	if (size.height() + gripSize.height() + gripSize.height() > m_rect.height()) {
467 		prepareGeometryChange();
468 		m_rect.setHeight(size.height() + gripSize.height() + gripSize.height());
469 		newSize.setHeight(m_rect.height());
470 		positionGrip();
471 		this->update();
472 	}
473 }
474 
disconnectSlots()475 void Note::disconnectSlots() {
476 	disconnect(m_graphicsTextItem->document(), SIGNAL(contentsChanged()),
477 			this, SLOT(contentsChangedSlot()));
478 	disconnect(m_graphicsTextItem->document(), SIGNAL(contentsChange(int, int, int)),
479 			this, SLOT(contentsChangeSlot(int, int, int)));
480 }
481 
connectSlots()482 void Note::connectSlots() {
483 	connect(m_graphicsTextItem->document(), SIGNAL(contentsChanged()),
484 		this, SLOT(contentsChangedSlot()), Qt::DirectConnection);
485 	connect(m_graphicsTextItem->document(), SIGNAL(contentsChange(int, int, int)),
486 		this, SLOT(contentsChangeSlot(int, int, int)), Qt::DirectConnection);
487 }
488 
setText(const QString & text,bool check)489 void Note::setText(const QString & text, bool check) {
490 	// disconnect the signal so it doesn't fire recursively
491 	disconnectSlots();
492 	QString oldText = text;
493 	m_graphicsTextItem->document()->setHtml(text);
494 	connectSlots();
495 
496 	if (check) {
497 		QSizeF newSize;
498 		checkSize(newSize);
499 		forceFormat(0, m_graphicsTextItem->document()->characterCount());
500 	}
501 }
502 
text()503 QString Note::text() {
504 	return m_graphicsTextItem->document()->toHtml();
505 }
506 
setSize(const QSizeF & size)507 void Note::setSize(const QSizeF & size)
508 {
509 	prepareGeometryChange();
510 	m_rect.setWidth(size.width());
511 	m_rect.setHeight(size.height());
512 	positionGrip();
513 }
514 
setHidden(bool hide)515 void Note::setHidden(bool hide)
516 {
517 	ItemBase::setHidden(hide);
518 	m_graphicsTextItem->setVisible(!hide);
519 	m_resizeGrip->setVisible(!hide);
520 }
521 
setInactive(bool inactivate)522 void Note::setInactive(bool inactivate)
523 {
524 	ItemBase::setInactive(inactivate);
525 }
526 
eventFilter(QObject * object,QEvent * event)527 bool Note::eventFilter(QObject * object, QEvent * event)
528 {
529 	if (event->type() == QEvent::Shortcut || event->type() == QEvent::ShortcutOverride)
530 	{
531 		if (!object->inherits("QGraphicsView"))
532 		{
533 			event->accept();
534 			return true;
535 		}
536 	}
537 
538 	if (event->type() == QEvent::KeyPress) {
539 		QKeyEvent * kevent = static_cast<QKeyEvent *>(event);
540 		if (kevent->matches(QKeySequence::Bold)) {
541 			QTextCursor textCursor = m_graphicsTextItem->textCursor();
542 			QTextCharFormat cf = textCursor.charFormat();
543 			bool isBold = cf.fontWeight() == QFont::Bold;
544 			QTextCharFormat textCharFormat;
545 			textCharFormat.setFontWeight(isBold ? QFont::Normal : QFont::Bold);
546 			textCursor.mergeCharFormat(textCharFormat);
547 			event->accept();
548 			return true;
549 		}
550 		if (kevent->matches(QKeySequence::Italic)) {
551 			QTextCursor textCursor = m_graphicsTextItem->textCursor();
552 			QTextCharFormat cf = textCursor.charFormat();
553 			QTextCharFormat textCharFormat;
554 			textCharFormat.setFontItalic(!cf.fontItalic());
555 			textCursor.mergeCharFormat(textCharFormat);
556 			event->accept();
557 			return true;
558 		}
559 		if ((kevent->key() == Qt::Key_L) && (kevent->modifiers() & Qt::ControlModifier)) {
560 			QTimer::singleShot(75, this, SLOT(linkDialog()));
561 			event->accept();
562 			return true;
563 
564 		}
565 	}
566 	return false;
567 }
568 
linkDialog()569 void Note::linkDialog() {
570 	QTextCursor textCursor = m_graphicsTextItem->textCursor();
571 	bool gotUrl = false;
572 	if (textCursor.anchor() == textCursor.selectionStart()) {
573 		// the selection returns empty since we're between characters
574 		// so select one character forward or one character backward
575 		// to see whether we're in a url
576 		int wasAnchor = textCursor.anchor();
577 		bool atEnd = textCursor.atEnd();
578 		bool atStart = textCursor.atStart();
579 		if (!atStart) {
580 			textCursor.setPosition(wasAnchor - 1, QTextCursor::KeepAnchor);
581 			QString html = textCursor.selection().toHtml();
582 			if (UrlTag.indexIn(html) >= 0) {
583 				gotUrl = true;
584 			}
585 		}
586 		if (!gotUrl && !atEnd) {
587 			textCursor.setPosition(wasAnchor + 1, QTextCursor::KeepAnchor);
588 			QString html = textCursor.selection().toHtml();
589 			if (UrlTag.indexIn(html) >= 0) {
590 				gotUrl = true;
591 			}
592 		}
593 		textCursor.setPosition(wasAnchor, QTextCursor::MoveAnchor);
594 	}
595 	else {
596 		QString html = textCursor.selection().toHtml();
597 		DebugDialog::debug(html);
598 		if (UrlTag.indexIn(html) >= 0) {
599 			gotUrl = true;
600 		}
601 	}
602 
603 	LinkDialog ld;
604 	QString originalText;
605 	QString originalUrl;
606 	if (gotUrl) {
607 		originalUrl = UrlTag.cap(1);
608 		ld.setUrl(originalUrl);
609 		QString html = m_graphicsTextItem->toHtml();
610 
611 		// assumes html is in xml form
612 		QString errorStr;
613 		int errorLine;
614 		int errorColumn;
615 
616 		QDomDocument domDocument;
617 		if (!domDocument.setContent(html, &errorStr, &errorLine, &errorColumn)) {
618 			return;
619 		}
620 
621 		QDomElement root = domDocument.documentElement();
622 		if (root.isNull()) {
623 			return;
624 		}
625 
626 		if (root.tagName() != "html") {
627 			return;
628 		}
629 
630 		DebugDialog::debug(html);
631 		QList<QDomElement> aElements;
632 		findA(root, aElements);
633 		foreach (QDomElement a, aElements) {
634 			// TODO: if multiple hrefs point to the same url this will only find the first one
635 			QString href = a.attribute("href");
636 			if (href.isEmpty()) {
637 				href = a.attribute("HREF");
638 			}
639 			if (href.compare(originalUrl) == 0) {
640 				QString text;
641 				if (TextUtils::findText(a, text)) {
642 					ld.setText(text);
643 					break;
644 				}
645 				else {
646 					return;
647 				}
648 			}
649 		}
650 	}
651 	int result = ld.exec();
652 	if (result == QDialog::Accepted) {
653 		if (gotUrl) {
654 			int from = 0;
655 			while (true) {
656 				QTextCursor cursor = m_graphicsTextItem->document()->find(originalText, from);
657 				if (cursor.isNull()) {
658 					// TODO: tell the user
659 					return;
660 				}
661 
662 				QString html = cursor.selection().toHtml();
663 				if (html.contains(originalUrl)) {
664 					cursor.insertHtml(QString("<a href=\"%1\">%2</a>").arg(ld.url()).arg(ld.text()));
665 					break;
666 				}
667 
668 				from = cursor.selectionEnd();
669 			}
670 		}
671 		else {
672 			textCursor.insertHtml(QString("<a href=\"%1\">%2</a>").arg(ld.url()).arg(ld.text()));
673 		}
674 	}
675 }
676 
handleZoomChangedSlot(double scale)677 void Note::handleZoomChangedSlot(double scale) {
678 	Q_UNUSED(scale);
679 	positionGrip();
680 }
681 
handleMousePressSlot(QGraphicsSceneMouseEvent * event,ResizeHandle * resizeHandle)682 void Note::handleMousePressSlot(QGraphicsSceneMouseEvent * event, ResizeHandle * resizeHandle) {
683 	if (m_spaceBarWasPressed) return;
684 
685 	saveGeometry();
686 
687 	QSizeF sz = this->boundingRect().size();
688 	resizeHandle->setResizeOffset(this->pos() + QPointF(sz.width(), sz.height()) - event->scenePos());
689 
690 	m_inResize = resizeHandle;
691 }
692 
handleMouseMoveSlot(QGraphicsSceneMouseEvent * event,ResizeHandle * resizeHandle)693 void Note::handleMouseMoveSlot(QGraphicsSceneMouseEvent * event, ResizeHandle * resizeHandle) {
694     Q_UNUSED(resizeHandle);
695 
696     if (!m_inResize) return;
697 
698 	double minWidth = emptyMinWidth;
699 	double minHeight = emptyMinHeight;
700 	QSizeF gripSize = m_resizeGrip->boundingRect().size();
701 	QSizeF minSize = m_graphicsTextItem->document()->size() + gripSize + gripSize;
702 	if (minSize.height() > minHeight) minHeight = minSize.height();
703 
704 	QRectF rect = boundingRect();
705 	rect.moveTopLeft(this->pos());
706 
707 	double oldX1 = rect.x();
708 	double oldY1 = rect.y();
709 	double newX = event->scenePos().x() + m_inResize->resizeOffset().x();
710 	double newY = event->scenePos().y() + m_inResize->resizeOffset().y();
711 	QRectF newR;
712 
713 	if (newX - oldX1 < minWidth) {
714 		newX = oldX1 + minWidth;
715 	}
716 	if (newY - oldY1 < minHeight) {
717 		newY = oldY1 + minHeight;
718 	}
719 	newR.setRect(0, 0, newX - oldX1, newY - oldY1);
720 
721 	prepareGeometryChange();
722 	m_rect = newR;
723 	positionGrip();
724 }
725 
handleMouseReleaseSlot(QGraphicsSceneMouseEvent * event,ResizeHandle * resizeHandle)726 void Note::handleMouseReleaseSlot(QGraphicsSceneMouseEvent * event, ResizeHandle * resizeHandle) {
727     Q_UNUSED(resizeHandle);
728     Q_UNUSED(event);
729 
730 	if (!m_inResize) return;
731 
732 	m_inResize = NULL;
733 	InfoGraphicsView *infoGraphicsView = InfoGraphicsView::getInfoGraphicsView(this);
734 	if (infoGraphicsView != NULL) {
735         infoGraphicsView->noteSizeChanged(this, m_viewGeometry.rect().size(), m_rect.size());
736 	}
737 }
738 
hasPartLabel()739 bool Note::hasPartLabel() {
740 
741 	return false;
742 }
743 
stickyEnabled()744 bool Note::stickyEnabled() {
745 	return false;
746 }
747 
hasPartNumberProperty()748 bool Note::hasPartNumberProperty()
749 {
750 	return false;
751 }
752 
rotationAllowed()753 bool Note::rotationAllowed() {
754 	return false;
755 }
756 
rotation45Allowed()757 bool Note::rotation45Allowed() {
758 	return false;
759 }
760 
retrieveSvg(ViewLayer::ViewLayerID viewLayerID,QHash<QString,QString> & svgHash,bool blackOnly,double dpi,double & factor)761 QString Note::retrieveSvg(ViewLayer::ViewLayerID viewLayerID, QHash<QString, QString> & svgHash, bool blackOnly, double dpi, double & factor)
762 {
763 	Q_UNUSED(svgHash);
764     factor = 1;
765 
766 	switch (viewLayerID) {
767 		case ViewLayer::BreadboardNote:
768 			if (viewID() != ViewLayer::BreadboardView) return "";
769 			break;
770 		case ViewLayer::PcbNote:
771 			if (viewID() != ViewLayer::PCBView) return "";
772 			break;
773 		case ViewLayer::SchematicNote:
774 			if (viewID() != ViewLayer::SchematicView) return "";
775 			break;
776 		default:
777 			return "";
778 	}
779 
780 	QString svg = "<g>";
781 
782 	QString penColor = blackOnly ? "#000000" : m_pen.color().name();
783 	double penWidth = m_pen.widthF() * dpi / GraphicsUtils::SVGDPI;
784 	QString brushColor = blackOnly ? "none" : m_brush.color().name();
785 	svg += QString("<rect x='%1' y='%2' width='%3' height='%4' fill='%5' stroke='%6' stroke-width='%7' />")
786 		.arg(penWidth / 2)
787 		.arg(penWidth / 2)
788 		.arg((m_rect.width() * dpi / GraphicsUtils::SVGDPI) - penWidth)
789 		.arg((m_rect.height() * dpi / GraphicsUtils::SVGDPI) - penWidth)
790 		.arg(brushColor)
791 		.arg(penColor)
792 		.arg(penWidth)
793 		;
794 
795 	QTextCursor textCursor = m_graphicsTextItem->textCursor();
796 	QSizeF gripSize = m_resizeGrip->boundingRect().size();
797 	double docLeft = gripSize.width();
798 	double docTop = gripSize.height();
799 	for (QTextBlock block = m_graphicsTextItem->document()->begin(); block.isValid(); block = block.next()) {
800 		QTextLayout * layout = block.layout();
801 		double left = block.blockFormat().leftMargin() + docLeft + layout->position().x();
802 		double top = block.blockFormat().topMargin() + docTop + layout->position().y();
803 		for (int i = 0; i < layout->lineCount(); i++) {
804 			QTextLine line = layout->lineAt(i);
805 			QRectF r = line.rect();
806 			int start = line.textStart();
807 			int count = line.textLength();
808 
809 			QString soFar;
810 
811 			svg += QString("<text  x='%1' y='%2' font-family='%3' stroke='none' fill='#000000' text-anchor='left' font-size='%4' >\n")
812 				.arg((left + r.left()) * dpi / GraphicsUtils::SVGDPI)
813 				.arg((top + r.top() + line.ascent()) * dpi / GraphicsUtils::SVGDPI)
814 				.arg("Droid Sans")
815 				.arg(line.ascent() * dpi / GraphicsUtils::SVGDPI)
816 				;
817 
818 
819 			bool inUrl = false;
820 			for (int i = 0; i < count; i++) {
821 				textCursor.setPosition(i + start + block.position(), QTextCursor::MoveAnchor);
822 				textCursor.setPosition(i + start + block.position() + 1, QTextCursor::KeepAnchor);
823 				QString html = textCursor.selection().toHtml();
824 				if (UrlTag.indexIn(html) >= 0) {
825 					if (inUrl) {
826 						soFar += block.text().mid(start + i, 1);
827 						continue;
828 					}
829 
830 					svg += addText(soFar, false);
831 					soFar = block.text().mid(start + i, 1);
832 					inUrl = true;
833 				}
834 				else {
835 					if (!inUrl) {
836 						soFar += block.text().mid(start + i, 1);
837 						continue;
838 					}
839 
840 					svg += addText(soFar, !blackOnly);
841 					soFar = block.text().mid(start + i, 1);
842 					inUrl = false;
843 				}
844 			}
845 			svg += addText(soFar, inUrl && !blackOnly);
846 			svg += "</text>";
847 		}
848 	}
849 
850 
851 	svg += "</g>";
852 
853 
854 	return svg;
855 }
856 
useViewIDForPixmap(ViewLayer::ViewID,bool)857 ViewLayer::ViewID Note::useViewIDForPixmap(ViewLayer::ViewID, bool)
858 {
859     return ViewLayer::IconView;
860 }
861 
addedToScene(bool temporary)862 void Note::addedToScene(bool temporary)
863 {
864 	positionGrip();
865 
866     return ItemBase::addedToScene(temporary);
867 }
868