1 /***************************************************************************
2 * Copyright (C) 2003 by Sébastien Laoût *
3 * slaout@linux62.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 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
20
21 #include "note.h"
22
23 #include <QtCore/QList>
24 #include <QGraphicsItemAnimation>
25 #include <QGraphicsView>
26 #include <QtGui/QPainter>
27 #include <QtGui/QPixmap>
28 #include <QStyle>
29 #include <QStyleOption>
30 #include <QtGui/QImage>
31 #include <QApplication>
32 #include <QLocale> //For KGLobal::locale(
33
34 #include <KIconLoader>
35
36 #include <stdlib.h> // rand() function
37 #include <math.h> // sqrt() and pow() functions
38
39 #include "basketscene.h"
40 #include "filter.h"
41 #include "tag.h"
42 #include "noteselection.h"
43 #include "tools.h"
44 #include "settings.h"
45 #include "notefactory.h" // For NoteFactory::filteredURL()
46 #include "debugwindow.h"
47
48 /** class Note: */
49
50 #define FOR_EACH_CHILD(childVar) \
51 for (Note *childVar = firstChild(); childVar; childVar = childVar->next())
52
53 class NotePrivate
54 {
55 public:
NotePrivate()56 NotePrivate()
57 : prev(0), next(0), width(-1), height(Note::MIN_HEIGHT)
58 {
59 }
60 Note* prev;
61 Note* next;
62 qreal width;
63 qreal height;
64 };
65
66 qreal Note::NOTE_MARGIN = 2;
67 qreal Note::INSERTION_HEIGHT = 5;
68 qreal Note::EXPANDER_WIDTH = 9;
69 qreal Note::EXPANDER_HEIGHT = 9;
70 qreal Note::GROUP_WIDTH = 2 * NOTE_MARGIN + EXPANDER_WIDTH;
71 qreal Note::HANDLE_WIDTH = GROUP_WIDTH;
72 qreal Note::RESIZER_WIDTH = GROUP_WIDTH;
73 qreal Note::TAG_ARROW_WIDTH = 5;
74 qreal Note::EMBLEM_SIZE = 16;
75 qreal Note::MIN_HEIGHT = 2 * NOTE_MARGIN + EMBLEM_SIZE;
76
Note(BasketScene * parent)77 Note::Note(BasketScene *parent)
78 : d(new NotePrivate),
79 m_groupWidth(250),
80 m_isFolded(false),
81 m_firstChild(0L),
82 m_parentNote(0),
83 m_basket(parent),
84 m_content(0),
85 m_addedDate(QDateTime::currentDateTime()),
86 m_lastModificationDate(QDateTime::currentDateTime()),
87 m_computedAreas(false),
88 m_onTop(false),
89 m_animation(0),
90 m_hovered(false),
91 m_hoveredZone(Note::None),
92 m_focused(false),
93 m_selected(false),
94 m_wasInLastSelectionRect(false),
95 m_computedState(),
96 m_emblemsCount(0),
97 m_haveInvisibleTags(false),
98 m_matching(true)
99 {
100 setHeight(MIN_HEIGHT);
101 if(m_basket)
102 {
103 m_basket->addItem(this);
104 }
105 }
106
~Note()107 Note::~Note()
108 {
109 if(m_basket)
110 {
111 if(m_content && m_content->graphicsItem())
112 {
113 m_basket->removeItem(m_content->graphicsItem());
114 }
115 m_basket->removeItem(this);
116 }
117 delete m_content;
118 delete m_animation;
119 deleteChilds();
120 }
121
setNext(Note * next)122 void Note::setNext(Note* next)
123 {
124 d->next = next;
125 }
126
next() const127 Note* Note::next() const
128 {
129 return d->next;
130 }
131
setPrev(Note * prev)132 void Note::setPrev(Note* prev)
133 {
134 d->prev = prev;
135 }
136
prev() const137 Note* Note::prev() const
138 {
139 return d->prev;
140 }
141
bottom() const142 qreal Note::bottom() const
143 {
144 return y() + height() - 1;
145 }
146
setParentBasket(BasketScene * basket)147 void Note::setParentBasket(BasketScene *basket)
148 {
149 if(m_basket) m_basket->removeItem(this);
150 m_basket = basket;
151 if(m_basket) m_basket->addItem(this);
152 }
153
addedStringDate()154 QString Note::addedStringDate()
155 {
156 return m_addedDate.toString();
157 }
158
lastModificationStringDate()159 QString Note::lastModificationStringDate()
160 {
161 return m_lastModificationDate.toString();
162 }
163
toText(const QString & cuttedFullPath)164 QString Note::toText(const QString &cuttedFullPath)
165 {
166 if (content()) {
167 // Convert note to text:
168 QString text = content()->toText(cuttedFullPath);
169 // If we should not export tags with the text, return immediately:
170 if (!Settings::exportTextTags())
171 return text;
172 // Compute the text equivalent of the tag states:
173 QString firstLine;
174 QString otherLines;
175 for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) {
176 if (!(*it)->textEquivalent().isEmpty()) {
177 firstLine += (*it)->textEquivalent() + " ";
178 if ((*it)->onAllTextLines())
179 otherLines += (*it)->textEquivalent() + " ";
180 }
181 }
182 // Merge the texts:
183 if (firstLine.isEmpty())
184 return text;
185 if (otherLines.isEmpty())
186 return firstLine + text;
187 QStringList lines = text.split('\n');
188 QString result = firstLine + lines[0] + (lines.count() > 1 ? "\n" : "");
189 for (int i = 1/*Skip the first line*/; i < lines.count(); ++i)
190 result += otherLines + lines[i] + (i < lines.count() - 1 ? "\n" : "");
191
192 return result;
193 } else
194 return "";
195 }
196
computeMatching(const FilterData & data)197 bool Note::computeMatching(const FilterData &data)
198 {
199 // Groups are always matching:
200 if (!content())
201 return true;
202
203 // If we were editing this note and there is a save operation in the middle, then do not hide it suddently:
204 if (basket()->editedNote() == this)
205 return true;
206
207 bool matching;
208 // First match tags (they are fast to compute):
209 switch (data.tagFilterType) {
210 default:
211 case FilterData::DontCareTagsFilter: matching = true; break;
212 case FilterData::NotTaggedFilter: matching = m_states.count() <= 0; break;
213 case FilterData::TaggedFilter: matching = m_states.count() > 0; break;
214 case FilterData::TagFilter: matching = hasTag(data.tag); break;
215 case FilterData::StateFilter: matching = hasState(data.state); break;
216 }
217
218 // Don't try to match the content text if we are not matching now (the filter is of 'AND' type) or if we shouldn't try to match the string:
219 if (matching && !data.string.isEmpty())
220 matching = content()->match(data);
221
222 return matching;
223 }
224
newFilter(const FilterData & data)225 int Note::newFilter(const FilterData &data)
226 {
227 bool wasMatching = matching();
228 m_matching = computeMatching(data);
229 setOnTop(wasMatching && matching());
230 if (!matching())
231 {
232 setSelected(false);
233 hide();
234 }
235 else if(!wasMatching)
236 {
237 show();
238 }
239
240 int countMatches = (content() && matching() ? 1 : 0);
241
242 FOR_EACH_CHILD(child) {
243 countMatches += child->newFilter(data);
244 }
245
246 return countMatches;
247 }
248
deleteSelectedNotes(bool deleteFilesToo,QSet<Note * > * notesToBeDeleted)249 void Note::deleteSelectedNotes(bool deleteFilesToo, QSet<Note *> *notesToBeDeleted)
250 {
251 if (content())
252 {
253 if(isSelected()) {
254 basket()->unplugNote(this);
255 if (deleteFilesToo && content()->useFile())
256 {
257 Tools::deleteRecursively(fullPath());//basket()->deleteFiles(fullPath()); // Also delete the folder if it's a folder
258 }
259 if(notesToBeDeleted)
260 {
261 notesToBeDeleted->insert(this);
262 }
263 }
264 return;
265 }
266
267 bool isColumn = this->isColumn();
268 Note *child = firstChild();
269 Note *next;
270 while (child) {
271 next = child->next(); // If we delete 'child' on the next line, child->next() will be 0!
272 child->deleteSelectedNotes(deleteFilesToo, notesToBeDeleted);
273 child = next;
274 }
275
276 // if it remains at least two notes, the group must not be deleted
277 if(!isColumn && !(firstChild() && firstChild()->next()))
278 {
279 if(notesToBeDeleted)
280 {
281 notesToBeDeleted->insert(this);
282 }
283 }
284 }
285
count()286 int Note::count()
287 {
288 if (content())
289 return 1;
290
291 int count = 0;
292 FOR_EACH_CHILD(child)
293 {
294 count += child->count();
295 }
296 return count;
297 }
298
countDirectChilds()299 int Note::countDirectChilds()
300 {
301 int count = 0;
302 FOR_EACH_CHILD(child)
303 {
304 ++count;
305 }
306 return count;
307 }
308
fullPath()309 QString Note::fullPath()
310 {
311 if (content())
312 return basket()->fullPath() + content()->fileName();
313 else
314 return "";
315 }
316
317 /*void Note::update()
318 {
319 update(0,0,boundingRect().width,boundingRect().height);
320 }*/
321
setFocused(bool focused)322 void Note::setFocused(bool focused)
323 {
324 if (m_focused == focused)
325 return;
326
327 m_focused = focused;
328 unbufferize();
329 update(); // FIXME: ???
330 }
331
setSelected(bool selected)332 void Note::setSelected(bool selected)
333 {
334 if (isGroup())
335 selected = false; // A group cannot be selected!
336
337 if (m_selected == selected)
338 return;
339
340 if (!selected && basket()->editedNote() == this) {
341 //basket()->closeEditor();
342 return; // To avoid a bug that would count 2 less selected notes instead of 1 less! Because m_selected is modified only below.
343 }
344
345 if (selected)
346 basket()->addSelectedNote();
347 else
348 basket()->removeSelectedNote();
349
350 m_selected = selected;
351 unbufferize();
352 update(); // FIXME: ???
353 }
354
resetWasInLastSelectionRect()355 void Note::resetWasInLastSelectionRect()
356 {
357 m_wasInLastSelectionRect = false;
358
359 FOR_EACH_CHILD(child)
360 {
361 child->resetWasInLastSelectionRect();
362 }
363 }
364
finishLazyLoad()365 void Note::finishLazyLoad()
366 {
367 if (content())
368 content()->finishLazyLoad();
369
370 FOR_EACH_CHILD(child)
371 {
372 child->finishLazyLoad();
373 }
374 }
375
selectIn(const QRectF & rect,bool invertSelection,bool unselectOthers)376 void Note::selectIn(const QRectF &rect, bool invertSelection, bool unselectOthers /*= true*/)
377 {
378 // QRect myRect(x(), y(), width(), height());
379
380 // bool intersects = myRect.intersects(rect);
381
382 // Only intersects with visible areas.
383 // If the note is not visible, the user don't think it will be selected while selecting the note(s) that hide this, so act like the user think:
384 bool intersects = false;
385 for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
386 QRectF &r = *it;
387 if (r.intersects(rect)) {
388 intersects = true;
389 break;
390 }
391 }
392
393 bool toSelect = intersects || (!unselectOthers && isSelected());
394 if (invertSelection) {
395 toSelect = (m_wasInLastSelectionRect == intersects) ? isSelected() : !isSelected();
396 }
397
398 setSelected(toSelect);
399 m_wasInLastSelectionRect = intersects;
400
401 Note *child = firstChild();
402 bool first = true;
403 while (child) {
404 if ((showSubNotes() || first) && child->matching())
405 child->selectIn(rect, invertSelection, unselectOthers);
406 else
407 child->setSelectedRecursively(false);
408 child = child->next();
409 first = false;
410 }
411 }
412
allSelected()413 bool Note::allSelected()
414 {
415 if (isGroup()) {
416 Note *child = firstChild();
417 bool first = true;
418 while (child) {
419 if ((showSubNotes() || first) && child->matching())
420 if (!child->allSelected())
421 return false;;
422 child = child->next();
423 first = false;
424 }
425 return true;
426 } else
427 return isSelected();
428 }
429
setSelectedRecursively(bool selected)430 void Note::setSelectedRecursively(bool selected)
431 {
432 setSelected(selected && matching());
433
434 FOR_EACH_CHILD(child)
435 {
436 child->setSelectedRecursively(selected);
437 }
438 }
439
invertSelectionRecursively()440 void Note::invertSelectionRecursively()
441 {
442 if (content())
443 setSelected(!isSelected() && matching());
444
445 FOR_EACH_CHILD(child)
446 {
447 child->invertSelectionRecursively();
448 }
449 }
450
unselectAllBut(Note * toSelect)451 void Note::unselectAllBut(Note *toSelect)
452 {
453 if (this == toSelect)
454 setSelectedRecursively(true);
455 else {
456 setSelected(false);
457
458 Note *child = firstChild();
459 bool first = true;
460 while (child) {
461 if ((showSubNotes() || first) && child->matching())
462 child->unselectAllBut(toSelect);
463 else
464 child->setSelectedRecursively(false);
465 child = child->next();
466 first = false;
467 }
468 }
469 }
470
invertSelectionOf(Note * toSelect)471 void Note::invertSelectionOf(Note *toSelect)
472 {
473 if (this == toSelect)
474 setSelectedRecursively(!isSelected());
475 else {
476 Note *child = firstChild();
477 bool first = true;
478 while (child) {
479 if ((showSubNotes() || first) && child->matching())
480 child->invertSelectionOf(toSelect);
481 child = child->next();
482 first = false;
483 }
484 }
485 }
486
theSelectedNote()487 Note* Note::theSelectedNote()
488 {
489 if (!isGroup() && isSelected())
490 return this;
491
492 Note *selectedOne;
493 Note *child = firstChild();
494 while (child) {
495 selectedOne = child->theSelectedNote();
496 if (selectedOne)
497 return selectedOne;
498 child = child->next();
499 }
500
501 return 0;
502 }
503
selectedNotes()504 NoteSelection* Note::selectedNotes()
505 {
506 if (content()) {
507 if (isSelected())
508 return new NoteSelection(this);
509 else
510 return 0;
511 }
512
513 NoteSelection *selection = new NoteSelection(this);
514
515 FOR_EACH_CHILD(child)
516 {
517 selection->append(child->selectedNotes());
518 }
519
520 if (selection->firstChild) {
521 if (selection->firstChild->next)
522 return selection;
523 else {
524 // If 'selection' is a groupe with only one content, return directly that content:
525 NoteSelection *reducedSelection = selection->firstChild;
526 // delete selection; // TODO: Cut all connexions of 'selection' before deleting it!
527 for (NoteSelection *node = reducedSelection; node; node = node->next)
528 node->parent = 0;
529 return reducedSelection;
530 }
531 } else {
532 delete selection;
533 return 0;
534 }
535 }
536
isAfter(Note * note)537 bool Note::isAfter(Note *note)
538 {
539 if (this == 0 || note == 0)
540 return true;
541
542 Note *next = this;
543 while (next) {
544 if (next == note)
545 return false;
546 next = next->nextInStack();
547 }
548 return true;
549 }
550
containsNote(Note * note)551 bool Note::containsNote(Note *note)
552 {
553 // if (this == note)
554 // return true;
555
556 while (note)
557 if (note == this)
558 return true;
559 else
560 note = note->parentNote();
561
562 // FOR_EACH_CHILD (child)
563 // if (child->containsNote(note))
564 // return true;
565 return false;
566 }
567
firstRealChild()568 Note* Note::firstRealChild()
569 {
570 Note *child = m_firstChild;
571 while (child) {
572 if (!child->isGroup() /*&& child->matching()*/)
573 return child;
574 child = child->firstChild();
575 }
576 // Empty group:
577 return 0;
578 }
579
lastRealChild()580 Note* Note::lastRealChild()
581 {
582 Note *child = lastChild();
583 while (child) {
584 if (child->content())
585 return child;
586 Note *possibleChild = child->lastRealChild();
587 if (possibleChild && possibleChild->content())
588 return possibleChild;
589 child = child->prev();
590 }
591 return 0;
592 }
593
lastChild()594 Note* Note::lastChild()
595 {
596 Note *child = m_firstChild;
597 while (child && child->next())
598 child = child->next();
599
600 return child;
601 }
602
lastSibling()603 Note* Note::lastSibling()
604 {
605 Note *last = this;
606 while (last && last->next())
607 last = last->next();
608
609 return last;
610 }
611
yExpander()612 qreal Note::yExpander()
613 {
614 Note *child = firstRealChild();
615 if (child && !child->isShown())
616 child = child->nextShownInStack(); // FIXME: Restrict scope to 'this'
617
618 if (child)
619 return (child->boundingRect().height() - EXPANDER_HEIGHT) / 2;
620 else // Groups always have at least 2 notes, except for columns which can have no child (but should exists anyway):
621 return 0;
622 }
623
isFree() const624 bool Note::isFree() const
625 {
626 return parentNote() == 0 && basket() && basket()->isFreeLayout();
627 }
628
isColumn() const629 bool Note::isColumn() const
630 {
631 return parentNote() == 0 && basket() && basket()->isColumnsLayout();
632 }
633
hasResizer() const634 bool Note::hasResizer() const
635 {
636 // "isFree" || "isColumn but not the last"
637 return parentNote() == 0 && ((basket() && basket()->isFreeLayout()) || d->next != 0L);
638 }
639
resizerHeight() const640 qreal Note::resizerHeight() const
641 {
642 return (isColumn() ? basket()->sceneRect().height() : d->height);
643 }
644
setHoveredZone(Zone zone)645 void Note::setHoveredZone(Zone zone) // TODO: Remove setHovered(bool) and assume it is hovered if zone != None !!!!!!!
646 {
647 if (m_hoveredZone != zone) {
648 if (content())
649 content()->setHoveredZone(m_hoveredZone, zone);
650 m_hoveredZone = zone;
651 unbufferize();
652 }
653 }
654
zoneAt(const QPointF & pos,bool toAdd)655 Note::Zone Note::zoneAt(const QPointF &pos, bool toAdd)
656 {
657 // Keep the resizer highlighted when resizong, even if the cursor is over another note:
658 if (basket()->resizingNote() == this)
659 return Resizer;
660
661 // When dropping/pasting something on a column resizer, add it at the bottom of the column, and don't group it with the whole column:
662 if (toAdd && isColumn() && hasResizer()) {
663 qreal right = rightLimit() - x();
664 if ((pos.x() >= right) && (pos.x() < right + RESIZER_WIDTH) && (pos.y() >= 0) && (pos.y() < resizerHeight())) // Code copied from below
665 return BottomColumn;
666 }
667
668 // Below a column:
669 if (isColumn()) {
670 if (pos.y() >= height() && pos.x() < rightLimit() - x())
671 return BottomColumn;
672 }
673
674 // If toAdd, return only TopInsert, TopGroup, BottomInsert or BottomGroup
675 // (by spanning those areas in 4 equal rectangles in the note):
676 if (toAdd) {
677 if (!isFree() && !Settings::groupOnInsertionLine())
678 {
679 if(pos.y() < height() / 2)
680 return TopInsert;
681 else
682 return BottomInsert;
683 }
684 if (isColumn() && pos.y() >= height())
685 return BottomGroup;
686 if (pos.y() < height() / 2)
687 if (pos.x() < width() / 2 && !isFree())
688 return TopInsert;
689 else
690 return TopGroup;
691 else if (pos.x() < width() / 2 && !isFree())
692 return BottomInsert;
693 else
694 return BottomGroup;
695 }
696
697 // If in the resizer:
698 if (hasResizer()) {
699 qreal right = rightLimit() - x();
700 if ((pos.x() >= right) && (pos.x() < right + RESIZER_WIDTH) && (pos.y() >= 0) && (pos.y() < resizerHeight()))
701 return Resizer;
702 }
703
704 // If isGroup, return only Group, GroupExpander, TopInsert or BottomInsert:
705 if (isGroup()) {
706 if (pos.y() < INSERTION_HEIGHT)
707 {
708 if(isFree())
709 return TopGroup;
710 else
711 return TopInsert;
712 }
713 if (pos.y() >= height() - INSERTION_HEIGHT)
714 {
715 if(isFree())
716 return BottomGroup;
717 else
718 return BottomInsert;
719 }
720 if (pos.x() >= NOTE_MARGIN && pos.x() < NOTE_MARGIN + EXPANDER_WIDTH) {
721 qreal yExp = yExpander();
722 if (pos.y() >= yExp && pos.y() < yExp + EXPANDER_HEIGHT)
723 return GroupExpander;
724 }
725 if (pos.x() < width())
726 return Group;
727 else
728 return Note::None;
729 }
730
731 // Else, it's a normal note:
732
733 if (pos.x() < HANDLE_WIDTH)
734 return Handle;
735
736 if (pos.y() < INSERTION_HEIGHT) {
737 if ((!isFree() && !Settings::groupOnInsertionLine()) || (pos.x() < width() / 2 && !isFree()))
738 return TopInsert;
739 else
740 return TopGroup;
741 }
742
743 if (pos.y() >= height() - INSERTION_HEIGHT) {
744 if ((!isFree() && !Settings::groupOnInsertionLine()) || (pos.x() < width() / 2 && !isFree()))
745 return BottomInsert;
746 else
747 return BottomGroup;
748 }
749
750 for (int i = 0; i < m_emblemsCount; i++) {
751 if (pos.x() >= HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE)*i &&
752 pos.x() < HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE)*i + NOTE_MARGIN + EMBLEM_SIZE)
753 return (Zone)(Emblem0 + i);
754 }
755
756 if (pos.x() < HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE)*m_emblemsCount + NOTE_MARGIN + TAG_ARROW_WIDTH + NOTE_MARGIN)
757 return TagsArrow;
758
759 if (!linkAt(pos).isEmpty())
760 return Link;
761
762 int customZone = content()->zoneAt(pos - QPointF(contentX(), NOTE_MARGIN));
763 if (customZone)
764 return (Note::Zone)customZone;
765
766 return Content;
767 }
768
linkAt(const QPointF & pos)769 QString Note::linkAt(const QPointF &pos)
770 {
771 QString link = m_content->linkAt(pos - QPointF(contentX(), NOTE_MARGIN));
772 if (link.isEmpty() || link.startsWith(QLatin1String("basket://")))
773 return link;
774 else
775 return NoteFactory::filteredURL(QUrl::fromUserInput(link)).toDisplayString();//KURIFilter::self()->filteredURI(link);
776 }
777
contentX() const778 qreal Note::contentX() const
779 {
780 return HANDLE_WIDTH + NOTE_MARGIN + (EMBLEM_SIZE + NOTE_MARGIN)*m_emblemsCount + TAG_ARROW_WIDTH + NOTE_MARGIN;
781 }
782
zoneRect(Note::Zone zone,const QPointF & pos)783 QRectF Note::zoneRect(Note::Zone zone, const QPointF &pos)
784 {
785 if (zone >= Emblem0)
786 return QRect(HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE)*(zone - Emblem0),
787 INSERTION_HEIGHT,
788 NOTE_MARGIN + EMBLEM_SIZE,
789 height() - 2*INSERTION_HEIGHT);
790
791 qreal yExp;
792 qreal right;
793 qreal xGroup = (isFree() ? (isGroup() ? 0 : GROUP_WIDTH) : width() / 2);
794 QRectF rect;
795 qreal insertSplit = (Settings::groupOnInsertionLine() ? 2 : 1);
796 switch (zone) {
797 case Note::Handle:
798 return QRectF(0, 0, HANDLE_WIDTH, d->height);
799 case Note::Group:
800 yExp = yExpander();
801 if (pos.y() < yExp) {
802 return QRectF(0, INSERTION_HEIGHT, d->width, yExp - INSERTION_HEIGHT);
803 }
804 if (pos.y() > yExp + EXPANDER_HEIGHT) {
805 return QRectF(0, yExp + EXPANDER_HEIGHT, d->width, d->height - yExp - EXPANDER_HEIGHT - INSERTION_HEIGHT);
806 }
807 if (pos.x() < NOTE_MARGIN) {
808 return QRectF(0, 0, NOTE_MARGIN, d->height);
809 }
810 else {
811 return QRectF(d->width - NOTE_MARGIN, 0, NOTE_MARGIN, d->height);
812 }
813 case Note::TagsArrow:
814 return QRectF(HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * m_emblemsCount,
815 INSERTION_HEIGHT,
816 NOTE_MARGIN + TAG_ARROW_WIDTH + NOTE_MARGIN,
817 d->height - 2*INSERTION_HEIGHT);
818 case Note::Custom0:
819 case Note::Content:
820 rect = content()->zoneRect(zone, pos - QPointF(contentX(), NOTE_MARGIN));
821 rect.translate(contentX(), NOTE_MARGIN);
822 return rect.intersected(QRectF(contentX(), INSERTION_HEIGHT, d->width - contentX(), d->height - 2*INSERTION_HEIGHT)); // Only IN contentRect
823 case Note::GroupExpander:
824 return QRectF(NOTE_MARGIN, yExpander(), EXPANDER_WIDTH, EXPANDER_HEIGHT);
825 case Note::Resizer:
826 right = rightLimit();
827 return QRectF(right - x(), 0, RESIZER_WIDTH, resizerHeight());
828 case Note::Link:
829 case Note::TopInsert:
830 if (isGroup()) return QRectF(0, 0, d->width, INSERTION_HEIGHT);
831 else return QRectF(HANDLE_WIDTH, 0, d->width / insertSplit - HANDLE_WIDTH, INSERTION_HEIGHT);
832 case Note::TopGroup:
833 return QRectF(xGroup, 0, d->width - xGroup, INSERTION_HEIGHT);
834 case Note::BottomInsert:
835 if (isGroup()) return QRectF(0, d->height - INSERTION_HEIGHT, d->width, INSERTION_HEIGHT);
836 else return QRectF(HANDLE_WIDTH, d->height - INSERTION_HEIGHT, d->width / insertSplit - HANDLE_WIDTH, INSERTION_HEIGHT);
837 case Note::BottomGroup:
838 return QRectF(xGroup, d->height - INSERTION_HEIGHT, d->width - xGroup, INSERTION_HEIGHT);
839 case Note::BottomColumn:
840 return QRectF(0, d->height, rightLimit() - x(), basket()->sceneRect().height() - d->height);
841 case Note::None:
842 return QRectF(/*0, 0, -1, -1*/);
843 default:
844 return QRectF(/*0, 0, -1, -1*/);
845 }
846 }
847
cursorFromZone(Zone zone) const848 Qt::CursorShape Note::cursorFromZone(Zone zone) const
849 {
850 switch (zone) {
851 case Note::Handle:
852 case Note::Group:
853 return Qt::SizeAllCursor;
854 break;
855 case Note::Resizer:
856 if (isColumn())
857 {
858 return Qt::SplitHCursor;
859 }
860 else
861 {
862 return Qt::SizeHorCursor;
863 }
864 break;
865
866 case Note::Custom0:
867 return m_content->cursorFromZone(zone);
868 break;
869
870 case Note::Link:
871 case Note::TagsArrow:
872 case Note::GroupExpander:
873 return Qt::PointingHandCursor;
874 break;
875
876 case Note::Content:
877 return Qt::IBeamCursor;
878 break;
879
880 case Note::TopInsert:
881 case Note::TopGroup:
882 case Note::BottomInsert:
883 case Note::BottomGroup:
884 case Note::BottomColumn:
885 return Qt::CrossCursor;
886 break;
887 case Note::None:
888 return Qt::ArrowCursor;
889 break;
890 default:
891 State *state = stateForEmblemNumber(zone - Emblem0);
892 if (state && state->parentTag()->states().count() > 1)
893 return Qt::PointingHandCursor;
894 else
895 return Qt::ArrowCursor;
896 }
897 }
898
initAnimationLoad(QTimeLine * timeLine)899 bool Note::initAnimationLoad(QTimeLine *timeLine)
900 {
901 bool needAnimation = false;
902
903 if( ! isColumn() )
904 {
905 qreal x, y;
906 switch (rand() % 4) {
907 case 0: // Put it on top:
908 x = basket()->sceneRect().x() + rand() % (int)basket()->sceneRect().width();
909 y = -height();
910 break;
911 case 1: // Put it on bottom:
912 x = basket()->sceneRect().x() + rand() % (int)basket()->sceneRect().width();
913 y = basket()->sceneRect().y() + basket()->graphicsView()->viewport()->height();
914 break;
915 case 2: // Put it on left:
916 x = -width() - (hasResizer() ? Note::RESIZER_WIDTH : 0);
917 y = basket()->sceneRect().y() + rand() % basket()->graphicsView()->viewport()->height();
918 break;
919 case 3: // Put it on right:
920 default: // In the case of...
921 x = basket()->sceneRect().x() + basket()->graphicsView()->viewport()->width();
922 y = basket()->sceneRect().y() + rand() % basket()->graphicsView()->viewport()->height();
923 break;
924 }
925
926 m_animation = new QGraphicsItemAnimation;
927 m_animation->setItem(this);
928 m_animation->setTimeLine(timeLine);
929
930 for (int i = 0; i <= 100; i++)
931 {
932 m_animation->setPosAt(i/100.0, QPointF(this->x()-x*(100-i)/100,this->y()-y*(100-i)/100));
933 }
934
935 needAnimation = true;
936 }
937
938 if (isGroup()) {
939 const qreal viewHeight = basket()->sceneRect().y() + basket()->graphicsView()->viewport()->height();
940 Note *child = firstChild();
941 bool first = true;
942 while (child) {
943 if (child->y() < viewHeight) {
944 if ((showSubNotes() || first) && child->matching())
945 needAnimation |= child->initAnimationLoad(timeLine);
946 } else
947 break; // 'child' are not a free notes (because child of at least one note, 'this'), so 'child' is ordered vertically.
948 child = child->next();
949 first = false;
950 }
951 }
952
953 return needAnimation;
954 }
955
animationFinished()956 void Note::animationFinished()
957 {
958 unbufferize();
959 delete m_animation;
960 m_animation = 0;
961 Note *child = firstChild();
962 while (child) {
963 child->animationFinished();
964 child = child->next();
965 }
966 }
967
height() const968 qreal Note::height() const
969 {
970 return d->height;
971 }
972
setHeight(qreal height)973 void Note::setHeight(qreal height)
974 {
975 setInitialHeight(height);
976 }
977
setInitialHeight(qreal height)978 void Note::setInitialHeight(qreal height)
979 {
980 prepareGeometryChange();
981 d->height = height;
982 }
983
unsetWidth()984 void Note::unsetWidth()
985 {
986 prepareGeometryChange();
987
988 d->width = 0;
989 unbufferize();
990
991 FOR_EACH_CHILD(child)
992 child->unsetWidth();
993 }
994
width() const995 qreal Note::width() const
996 {
997 return (isGroup() ? (isColumn() ? 0 : GROUP_WIDTH) : d->width);
998 }
999
requestRelayout()1000 void Note::requestRelayout()
1001 {
1002 prepareGeometryChange();
1003
1004 d->width = 0;
1005 unbufferize();
1006 basket()->relayoutNotes(true); // TODO: A signal that will relayout ONCE and DELAYED if called several times
1007 }
1008
setWidth(qreal width)1009 void Note::setWidth(qreal width) // TODO: inline ?
1010 {
1011 if (d->width != width)
1012 setWidthForceRelayout(width);
1013 }
1014
setWidthForceRelayout(qreal width)1015 void Note::setWidthForceRelayout(qreal width)
1016 {
1017 prepareGeometryChange();
1018 unbufferize();
1019 d->width = (width < minWidth() ? minWidth() : width);
1020 int contentWidth = width - contentX() - NOTE_MARGIN;
1021 if (m_content) { ///// FIXME: is this OK?
1022 if (contentWidth < 1)
1023 contentWidth = 1;
1024 if (contentWidth < m_content->minWidth())
1025 contentWidth = m_content->minWidth();
1026 setHeight(m_content->setWidthAndGetHeight(contentWidth/* < 1 ? 1 : contentWidth*/) + 2 * NOTE_MARGIN);
1027 if (d->height < 3 * INSERTION_HEIGHT) // Assure a minimal size...
1028 setHeight(3 * INSERTION_HEIGHT);
1029 }
1030 }
1031
minWidth() const1032 qreal Note::minWidth() const
1033 {
1034 if (m_content)
1035 return contentX() + m_content->minWidth() + NOTE_MARGIN;
1036 else
1037 return GROUP_WIDTH; ///// FIXME: is this OK?
1038 }
1039
minRight()1040 qreal Note::minRight()
1041 {
1042 if (isGroup()) {
1043 qreal right = x() + width();
1044 Note* child = firstChild();
1045 bool first = true;
1046 while (child) {
1047 if ((showSubNotes() || first) && child->matching())
1048 right = qMax(right, child->minRight());
1049 child = child->next();
1050 first = false;
1051 }
1052 if (isColumn()) {
1053 qreal minColumnRight = x() + 2 * HANDLE_WIDTH;
1054 if (right < minColumnRight)
1055 return minColumnRight;
1056 }
1057 return right;
1058 } else
1059 return x() + minWidth();
1060 }
1061
toggleFolded()1062 bool Note::toggleFolded()
1063 {
1064 // Close the editor if it was editing a note that we are about to hide after collapsing:
1065 if (!m_isFolded && basket() && basket()->isDuringEdit()) {
1066 if (containsNote(basket()->editedNote()) && firstRealChild() != basket()->editedNote())
1067 basket()->closeEditor();
1068 }
1069
1070 // Important to close the editor FIRST, because else, the last edited note would not show during folding animation (don't ask me why ;-) ):
1071 m_isFolded = ! m_isFolded;
1072
1073 unbufferize();
1074
1075 return true;
1076 }
1077
1078
noteAt(QPointF pos)1079 Note* Note::noteAt(QPointF pos)
1080 {
1081 if (matching() && hasResizer()) {
1082 int right = rightLimit();
1083 // TODO: This code is dupliacted 3 times: !!!!
1084 if ((pos.x() >= right) && (pos.x() < right + RESIZER_WIDTH) && (pos.y() >= y()) && (pos.y() < y() + resizerHeight())) {
1085 if (! m_computedAreas)
1086 recomputeAreas();
1087 for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
1088 QRectF &rect = *it;
1089 if (rect.contains(pos.x(), pos.y()))
1090 return this;
1091 }
1092 }
1093 }
1094
1095 if (isGroup()) {
1096 if ((pos.x() >= x()) && (pos.x() < x() + width()) && (pos.y() >= y()) && (pos.y() < y() + d->height)) {
1097 if (! m_computedAreas)
1098 recomputeAreas();
1099 for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
1100 QRectF &rect = *it;
1101 if (rect.contains(pos.x(), pos.y()))
1102 return this;
1103 }
1104 return NULL;
1105 }
1106 Note *child = firstChild();
1107 Note *found;
1108 bool first = true;
1109 while (child) {
1110 if ((showSubNotes() || first) && child->matching()) {
1111 found = child->noteAt(pos);
1112 if (found)
1113 return found;
1114 }
1115 child = child->next();
1116 first = false;
1117 }
1118 } else if (matching() && pos.y() >= y() && pos.y() < y() + d->height && pos.x() >= x() && pos.x() < x() + d->width) {
1119 if (! m_computedAreas)
1120 recomputeAreas();
1121 for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
1122 QRectF &rect = *it;
1123 if (rect.contains(pos.x(), pos.y()))
1124 return this;
1125 }
1126 return NULL;
1127 }
1128
1129 return NULL;
1130 }
1131
boundingRect() const1132 QRectF Note::boundingRect() const
1133 {
1134 if(hasResizer())
1135 {
1136 return QRectF(0,0,rightLimit()-x()+RESIZER_WIDTH,resizerHeight());
1137 }
1138 return QRectF(0, 0, width(), height());
1139 }
1140
resizerRect()1141 QRectF Note::resizerRect()
1142 {
1143 return QRectF(rightLimit(), y(), RESIZER_WIDTH, resizerHeight());
1144 }
1145
1146
showSubNotes()1147 bool Note::showSubNotes()
1148 {
1149 return !m_isFolded || basket()->isFiltering();
1150 }
1151
relayoutAt(qreal ax,qreal ay,bool animate)1152 void Note::relayoutAt(qreal ax, qreal ay, bool animate)
1153 {
1154 if (!matching())
1155 return;
1156
1157 m_computedAreas = false;
1158 m_areas.clear();
1159
1160 // Don't relayout free notes one under the other, because by definition they are freely positionned!
1161 if (isFree()) {
1162 ax = x();
1163 ay = y();
1164 // If it's a column, it always have the same "fixed" position (no animation):
1165 } else if (isColumn()) {
1166 ax = (prev() ? prev()->rightLimit() + RESIZER_WIDTH : 0);
1167 ay = 0;
1168 setX(ax);
1169 setY(ay);
1170 // But relayout others vertically if they are inside such primary groups or if it is a "normal" basket:
1171 } else {
1172 setX(ax);
1173 setY(ay);
1174 }
1175
1176 // Then, relayout sub-notes (only the first, if the group is folded) and so, assign an height to the group:
1177 if (isGroup()) {
1178 qreal h = 0;
1179 Note *child = firstChild();
1180 bool first = true;
1181 while (child) {
1182 if (child->matching() && (!m_isFolded || first || basket()->isFiltering())) { // Don't use showSubNotes() but use !m_isFolded because we don't want a relayout for the animated collapsing notes
1183 child->relayoutAt(ax + width(), ay + h, animate);
1184 h += child->height();
1185 if(!child->isVisible()) child->show();
1186 } else { // In case the user collapse a group, then move it and then expand it:
1187 child->setXRecursively(x() + width()); // notes SHOULD have a good X coordonate, and not the old one!
1188 if(child->isVisible()) child->hideRecursively();
1189 }
1190 // For future animation when re-match, but on bottom of already matched notes!
1191 // Find parent primary note and set the Y to THAT y:
1192 //if (!child->matching())
1193 // child->setY(parentPrimaryNote()->y());
1194 child = child->next();
1195 first = false;
1196 }
1197 if (height() != h || d->height != h) {
1198 unbufferize();
1199 /*if (animate)
1200 addAnimation(0, 0, h - height());
1201 else {*/
1202 setHeight(h);
1203 unbufferize();
1204 //}
1205 }
1206 } else {
1207 // If rightLimit is excedded, set the top-level right limit!!!
1208 // and NEED RELAYOUT
1209 setWidth(finalRightLimit() - x());
1210 }
1211
1212 // Set the basket area limits (but not for child notes: no need, because they will look for theire parent note):
1213 if (!parentNote()) {
1214 if (basket()->tmpWidth < finalRightLimit() + (hasResizer() ? RESIZER_WIDTH : 0))
1215 basket()->tmpWidth = finalRightLimit() + (hasResizer() ? RESIZER_WIDTH : 0);
1216 if (basket()->tmpHeight < y() + height())
1217 basket()->tmpHeight = y() + height();
1218 // However, if the note exceed the allowed size, let it! :
1219 } else if (!isGroup()) {
1220 if (basket()->tmpWidth < x() + width() + (hasResizer() ? RESIZER_WIDTH : 0))
1221 basket()->tmpWidth = x() + width() + (hasResizer() ? RESIZER_WIDTH : 0);
1222 if (basket()->tmpHeight < y() + height())
1223 basket()->tmpHeight = y() + height();
1224 }
1225 }
1226
setXRecursively(qreal x)1227 void Note::setXRecursively(qreal x)
1228 {
1229 setX(x);
1230
1231 FOR_EACH_CHILD(child)
1232 child->setXRecursively(x + width());
1233 }
1234
setYRecursively(qreal y)1235 void Note::setYRecursively(qreal y)
1236 {
1237 setY(y);
1238
1239 FOR_EACH_CHILD(child)
1240 child->setYRecursively(y);
1241 }
1242
hideRecursively()1243 void Note::hideRecursively()
1244 {
1245 hide();
1246
1247 FOR_EACH_CHILD(child)
1248 child->hideRecursively();
1249 }
setGroupWidth(qreal width)1250 void Note::setGroupWidth(qreal width)
1251 {
1252 m_groupWidth = width;
1253 }
1254
groupWidth() const1255 qreal Note::groupWidth() const
1256 {
1257 if (hasResizer())
1258 return m_groupWidth;
1259 else
1260 return rightLimit() - x();
1261 }
1262
rightLimit() const1263 qreal Note::rightLimit() const
1264 {
1265 if (isColumn() && d->next == 0L) // The last column
1266 return qMax((x() + minWidth()), (qreal)basket()->graphicsView()->viewport()->width());
1267 else if (parentNote())
1268 return parentNote()->rightLimit();
1269 else
1270 return x() + m_groupWidth;
1271 }
1272
finalRightLimit() const1273 qreal Note::finalRightLimit() const
1274 {
1275 if (isColumn() && d->next == 0L) // The last column
1276 return qMax(x() + minWidth(), (qreal)basket()->graphicsView()->viewport()->width());
1277 else if (parentNote())
1278 return parentNote()->finalRightLimit();
1279 else
1280 return x() + m_groupWidth;
1281 }
1282
1283 /*
1284 * This code is derivated from drawMetalGradient() from the Qt documentation:
1285 */
drawGradient(QPainter * p,const QColor & colorTop,const QColor & colorBottom,qreal x,qreal y,qreal w,qreal h,bool sunken,bool horz,bool flat)1286 void drawGradient(QPainter *p, const QColor &colorTop, const QColor & colorBottom,
1287 qreal x, qreal y, qreal w, qreal h,
1288 bool sunken, bool horz, bool flat) /*const*/
1289 {
1290 w++; //proper width in kde4 version
1291
1292 QColor highlight(colorBottom);
1293 QColor subh1(colorTop);
1294 QColor subh2(colorTop);
1295
1296 QColor topgrad(colorTop);
1297 QColor botgrad(colorBottom);
1298
1299
1300 if (flat && !sunken)
1301 p->fillRect(x, y, w, h, colorTop);
1302 else {
1303 int i = 0;
1304 int x1 = x;
1305 int y1 = y;
1306 int x2 = x + w - 1;
1307 int y2 = y + h - 1;
1308 if (horz)
1309 x2 = x2;
1310 else
1311 y2 = y2;
1312
1313 #define DRAWLINE if (horz) \
1314 p->drawLine( x1, y1+i, x2, y1+i ); \
1315 else \
1316 p->drawLine( x1+i, y1, x1+i, y2 ); \
1317 i++;
1318
1319 // Gradient:
1320 int ng = (horz ? h : w); // how many lines for the gradient?
1321
1322 int h1, h2, s1, s2, v1, v2;
1323 if (!sunken) {
1324 topgrad.getHsv(&h1, &s1, &v1);
1325 botgrad.getHsv(&h2, &s2, &v2);
1326 } else {
1327 botgrad.getHsv(&h1, &s1, &v1);
1328 topgrad.getHsv(&h2, &s2, &v2);
1329 }
1330
1331 if (ng > 1) {
1332 for (int j = 0; j < ng; j++) {
1333 p->setPen(QColor::fromHsv(h1 + ((h2 - h1)*j) / (ng - 1),
1334 s1 + ((s2 - s1)*j) / (ng - 1),
1335 v1 + ((v2 - v1)*j) / (ng - 1)));
1336 DRAWLINE;
1337 }
1338 } else if (ng == 1) {
1339 p->setPen(QColor::fromHsv((h1 + h2) / 2, (s1 + s2) / 2, (v1 + v2) / 2));
1340 DRAWLINE;
1341 }
1342 }
1343 }
1344
drawExpander(QPainter * painter,qreal x,qreal y,const QColor & background,bool expand,BasketScene * basket)1345 void Note::drawExpander(QPainter *painter, qreal x, qreal y,
1346 const QColor &background, bool expand,
1347 BasketScene *basket)
1348 {
1349 QStyleOption opt;
1350 opt.state = (expand ? QStyle::State_On : QStyle::State_Off);
1351 opt.rect = QRect(x, y, 9, 9);
1352 opt.palette = basket->palette();
1353 opt.palette.setColor(QPalette::Base, background);
1354
1355 painter->fillRect(opt.rect, background);
1356
1357 QStyle *style = basket->style();
1358 if (!expand){
1359 style->drawPrimitive(QStyle::PE_IndicatorArrowDown, &opt, painter,
1360 basket->graphicsView()->viewport());
1361 }
1362 else{
1363 style->drawPrimitive(QStyle::PE_IndicatorArrowRight, &opt, painter,
1364 basket->graphicsView()->viewport());
1365 }
1366 }
1367
expanderBackground(qreal height,qreal y,const QColor & foreground)1368 QColor expanderBackground(qreal height, qreal y, const QColor &foreground)
1369 {
1370 // We will divide height per two, subtract one and use that below a division bar:
1371 // To avoid division by zero error, height should be bigger than 3.
1372 // And to avoid y errors or if y is on the borders, we return the border color: the background color.
1373 if (height <= 3 || y <= 0 || y >= height - 1)
1374 return foreground;
1375
1376 QColor dark = foreground.dark(110); // 1/1.1 of brightness
1377 QColor light = foreground.light(150); // 50% brighter
1378
1379 qreal h1, h2, s1, s2, v1, v2;
1380 int ng;
1381 if (y <= (height - 2) / 2) {
1382 light.getHsvF(&h1, &s1, &v1);
1383 dark.getHsvF(&h2, &s2, &v2);
1384 ng = (height - 2) / 2;
1385 y -= 1;
1386 } else {
1387 dark.getHsvF(&h1, &s1, &v1);
1388 foreground.getHsvF(&h2, &s2, &v2);
1389 ng = (height - 2) - (height - 2) / 2;
1390 y -= 1 + (height - 2) / 2;
1391 }
1392
1393 return QColor::fromHsvF(h1 + ((h2 - h1)*y) / (ng - 1),
1394 s1 + ((s2 - s1)*y) / (ng - 1),
1395 v1 + ((v2 - v1)*y) / (ng - 1));
1396 }
1397
drawHandle(QPainter * painter,qreal x,qreal y,qreal width,qreal height,const QColor & background,const QColor & foreground)1398 void Note::drawHandle(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QColor &background, const QColor &foreground)
1399 {
1400 QPen backgroundPen(background);
1401 QPen foregroundPen(foreground);
1402
1403 QColor dark = foreground.dark(110); // 1/1.1 of brightness
1404 QColor light = foreground.light(150); // 50% brighter
1405
1406 // Draw the surrounding rectangle:
1407 painter->setPen(foregroundPen);
1408 painter->drawLine(0, 0, width - 1, 0);
1409 painter->drawLine(0, 0, 0, height - 1);
1410 painter->drawLine(width - 1, 0, width - 1, height - 1);
1411 painter->drawLine(0, height - 1, width - 1, height - 1);
1412
1413 // Draw the gradients:
1414 drawGradient(painter, light, dark, 1 + x, 1 + y, width - 2, (height - 1) / 2, /*sunken=*/false, /*horz=*/true, /*flat=*/false);
1415 drawGradient(painter, dark, foreground, 1 + x, 1 + y + (height - 1) / 2, width - 2, (height - 1) - (height - 1) / 2, /*sunken=*/false, /*horz=*/true, /*flat=*/false);
1416
1417 // Round the top corner with background color:
1418 painter->setPen(backgroundPen);
1419 painter->drawLine(0, 0, 0, 3);
1420 painter->drawLine(1, 0, 3, 0);
1421 painter->drawPoint(1, 1);
1422 // Round the bottom corner with background color:
1423 painter->drawLine(0, height - 1, 0, height - 4);
1424 painter->drawLine(1, height - 1, 3, height - 1);
1425 painter->drawPoint(1, height - 2);
1426
1427 // Surrounding line of the rounded top-left corner:
1428 painter->setPen(foregroundPen);
1429 painter->drawLine(1, 2, 1, 3);
1430 painter->drawLine(2, 1, 3, 1);
1431
1432 // Anti-aliased rounded top corner (1/2):
1433 painter->setPen(Tools::mixColor(foreground, background));
1434 painter->drawPoint(0, 3);
1435 painter->drawPoint(3, 0);
1436 // Anti-aliased rounded bottom corner:
1437 painter->drawPoint(0, height - 4);
1438 painter->drawPoint(3, height - 1);
1439 // Anti-aliased rounded top corner (2/2):
1440 painter->setPen(Tools::mixColor(foreground, light));
1441 painter->drawPoint(2, 2);
1442
1443 // Draw the grips:
1444 qreal xGrips = 4;
1445 qreal marginedHeight = (height * 80 / 100); // 10% empty on top, and 10% empty on bottom, so 20% of the height should be empty of any grip, and 80% should be in the grips
1446 int nbGrips = (marginedHeight - 3) / 6;
1447 if (nbGrips < 2)
1448 nbGrips = 2;
1449 qreal yGrips = (height + 1 - nbGrips * 6 - 3) / 2; // +1 to avoid rounding errors, -nbGrips*6-3 the size of the grips
1450 QColor darker = foreground.dark(130);
1451 QColor lighter = foreground.light(130);
1452 for (int i = 0; i < nbGrips; ++i) {
1453 /// Dark color:
1454 painter->setPen(darker);
1455 // Top-left point:
1456 painter->drawPoint(xGrips, yGrips);
1457 painter->drawPoint(xGrips + 1, yGrips);
1458 painter->drawPoint(xGrips, yGrips + 1);
1459 // Bottom-right point:
1460 painter->drawPoint(xGrips + 4, yGrips + 3);
1461 painter->drawPoint(xGrips + 5, yGrips + 3);
1462 painter->drawPoint(xGrips + 4, yGrips + 4);
1463 /// Light color:
1464 painter->setPen(lighter);
1465 // Top-left point:
1466 painter->drawPoint(xGrips + 1, yGrips + 1);
1467 // Bottom-right point:
1468 painter->drawPoint(xGrips + 5, yGrips + 4);
1469 yGrips += 6;
1470 }
1471 // The remaining point:
1472 painter->setPen(darker);
1473 painter->drawPoint(xGrips, yGrips);
1474 painter->drawPoint(xGrips + 1, yGrips);
1475 painter->drawPoint(xGrips, yGrips + 1);
1476 painter->setPen(lighter);
1477 painter->drawPoint(xGrips + 1, yGrips + 1);
1478 }
1479
drawResizer(QPainter * painter,qreal x,qreal y,qreal width,qreal height,const QColor & background,const QColor & foreground,bool rounded)1480 void Note::drawResizer(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QColor &background, const QColor &foreground, bool rounded)
1481 {
1482 QPen backgroundPen(background);
1483 QPen foregroundPen(foreground);
1484
1485 QColor dark = foreground.dark(110); // 1/1.1 of brightness
1486 QColor light = foreground.light(150); // 50% brighter
1487 QColor midLight = foreground.light(105); // 5% brighter
1488
1489 // Draw the surrounding rectangle:
1490 painter->setPen(foregroundPen);
1491 painter->fillRect(0, 0, width, height,foreground);
1492
1493 // Draw the gradients:
1494 drawGradient(painter, light, dark, 1 + x, 1 + y, width - 2, (height - 2) / 2, /*sunken=*/false, /*horz=*/true, /*flat=*/false);
1495 drawGradient(painter, dark, foreground, 1 + x, 1 + y + (height - 2) / 2, width - 2, (height - 2) - (height - 2) / 2, /*sunken=*/false, /*horz=*/true, /*flat=*/false);
1496
1497 if (rounded) {
1498 // Round the top corner with background color:
1499 painter->setPen(backgroundPen);
1500 painter->drawLine(width - 1, 0, width - 3, 0);
1501 painter->drawLine(width - 1, 1, width - 1, 2);
1502 painter->drawPoint(width - 2, 1);
1503 // Round the bottom corner with background color:
1504 painter->drawLine(width - 1, height - 1, width - 1, height - 4);
1505 painter->drawLine(width - 2, height - 1, width - 4, height - 1);
1506 painter->drawPoint(width - 2, height - 2);
1507
1508 // Surrounding line of the rounded top-left corner:
1509 painter->setPen(foregroundPen);
1510 painter->drawLine(width - 2, 2, width - 2, 3);
1511 painter->drawLine(width - 3, 1, width - 4, 1);
1512
1513 // Anti-aliased rounded top corner (1/2):
1514 painter->setPen(Tools::mixColor(foreground, background));
1515 painter->drawPoint(width - 1, 3);
1516 painter->drawPoint(width - 4, 0);
1517 // Anti-aliased rounded bottom corner:
1518 painter->drawPoint(width - 1, height - 4);
1519 painter->drawPoint(width - 4, height - 1);
1520 // Anti-aliased rounded top corner (2/2):
1521 painter->setPen(Tools::mixColor(foreground, light));
1522 painter->drawPoint(width - 3, 2);
1523 }
1524
1525 // Draw the arows:
1526 qreal xArrow = 2;
1527 qreal hMargin = 9;
1528 int countArrows = (height >= hMargin * 4 + 6 * 3 ? 3 : (height >= hMargin * 3 + 6 * 2 ? 2 : 1));
1529 QColor darker = foreground.dark(130);
1530 QColor lighter = foreground.light(130);
1531 for (int i = 0; i < countArrows; ++i) {
1532 qreal yArrow;
1533 switch (countArrows) {
1534 default:
1535 case 1: yArrow = (height-6) / 2; break;
1536 case 2: yArrow = (i == 1 ? hMargin : height - hMargin - 6); break;
1537 case 3: yArrow = (i == 1 ? hMargin : (i == 2 ? (height-6) / 2 : height - hMargin - 6)); break;
1538 }
1539 /// Dark color:
1540 painter->setPen(darker);
1541 // Left arrow:
1542 painter->drawLine(xArrow, yArrow + 2, xArrow + 2, yArrow);
1543 painter->drawLine(xArrow, yArrow + 2, xArrow + 2, yArrow + 4);
1544 // Right arrow:
1545 painter->drawLine(width - 1 - xArrow, yArrow + 2, width - 1 - xArrow - 2, yArrow);
1546 painter->drawLine(width - 1 - xArrow, yArrow + 2, width - 1 - xArrow - 2, yArrow + 4);
1547 /// Light color:
1548 painter->setPen(lighter);
1549 // Left arrow:
1550 painter->drawLine(xArrow, yArrow + 2 + 1, xArrow + 2, yArrow + 1);
1551 painter->drawLine(xArrow, yArrow + 2 + 1, xArrow + 2, yArrow + 4 + 1);
1552 // Right arrow:
1553 painter->drawLine(width - 1 - xArrow, yArrow + 2 + 1, width - 1 - xArrow - 2, yArrow + 1);
1554 painter->drawLine(width - 1 - xArrow, yArrow + 2 + 1, width - 1 - xArrow - 2, yArrow + 4 + 1);
1555 }
1556 }
1557
drawInactiveResizer(QPainter * painter,qreal x,qreal y,qreal height,const QColor & background,bool column)1558 void Note::drawInactiveResizer(QPainter *painter, qreal x, qreal y, qreal height, const QColor &background, bool column)
1559 {
1560 // If background color is too dark, we compute a lighter color instead of a darker:
1561 QColor darkBgColor = (Tools::tooDark(background) ? background.light(120) : background.dark(105));
1562 if (column) {
1563 qreal halfWidth = RESIZER_WIDTH / 2;
1564 drawGradient(painter, darkBgColor, background, x, y, halfWidth, height, /*sunken=*/false, /*horz=*/false, /*flat=*/false);
1565 drawGradient(painter, background, darkBgColor, halfWidth, y, RESIZER_WIDTH - halfWidth, height, /*sunken=*/false, /*horz=*/false, /*flat=*/false);
1566 } else
1567 drawGradient(painter, darkBgColor, background, x, y, RESIZER_WIDTH, height, /*sunken=*/false, /*horz=*/false, /*flat=*/false);
1568 }
1569
palette() const1570 QPalette Note::palette() const
1571 {
1572 return (m_basket ? m_basket->palette() : qApp->palette());
1573 }
1574
1575 /* type: 1: topLeft
1576 * 2: bottomLeft
1577 * 3: topRight
1578 * 4: bottomRight
1579 * 5: fourCorners
1580 * 6: noteInsideAndOutsideCorners
1581 * (x,y) relate to the painter origin
1582 * (width,height) only used for 5:fourCorners type
1583 */
drawRoundings(QPainter * painter,qreal x,qreal y,int type,qreal width,qreal height)1584 void Note::drawRoundings(QPainter *painter, qreal x, qreal y, int type, qreal width, qreal height)
1585 {
1586 qreal right;
1587
1588 switch (type) {
1589 case 1:
1590 x += this->x();
1591 y += this->y();
1592 basket()->blendBackground(*painter, QRectF(x, y, 4, 1), this->x(), this->y());
1593 basket()->blendBackground(*painter, QRectF(x, y + 1, 2, 1), this->x(), this->y());
1594 basket()->blendBackground(*painter, QRectF(x, y + 2, 1, 1), this->x(), this->y());
1595 basket()->blendBackground(*painter, QRectF(x, y + 3, 1, 1), this->x(), this->y());
1596 break;
1597 case 2:
1598 x += this->x();
1599 y += this->y();
1600 basket()->blendBackground(*painter, QRectF(x, y - 1, 1, 1), this->x(), this->y());
1601 basket()->blendBackground(*painter, QRectF(x, y, 1, 1), this->x(), this->y());
1602 basket()->blendBackground(*painter, QRectF(x, y + 1, 2, 1), this->x(), this->y());
1603 basket()->blendBackground(*painter, QRectF(x, y + 2, 4, 1), this->x(), this->y());
1604 break;
1605 case 3:
1606 right = rightLimit();
1607 x += right;
1608 y += this->y();
1609 basket()->blendBackground(*painter, QRectF(x - 1, y, 4, 1), right, this->y());
1610 basket()->blendBackground(*painter, QRectF(x + 1, y + 1, 2, 1), right, this->y());
1611 basket()->blendBackground(*painter, QRectF(x + 2, y + 2, 1, 1), right, this->y());
1612 basket()->blendBackground(*painter, QRectF(x + 2, y + 3, 1, 1), right, this->y());
1613 break;
1614 case 4:
1615 right = rightLimit();
1616 x += right;
1617 y += this->y();
1618 basket()->blendBackground(*painter, QRectF(x + 2, y - 1, 1, 1), right, this->y());
1619 basket()->blendBackground(*painter, QRectF(x + 2, y, 1, 1), right, this->y());
1620 basket()->blendBackground(*painter, QRectF(x + 1, y + 1, 2, 1), right, this->y());
1621 basket()->blendBackground(*painter, QRectF(x - 1, y + 2, 4, 1), right, this->y());
1622 break;
1623 case 5:
1624 // First make sure the corners are white (depending on the widget style):
1625 painter->setPen(basket()->backgroundColor());
1626 painter->drawPoint(x, y);
1627 painter->drawPoint(x + width - 1, y);
1628 painter->drawPoint(x + width - 1, y + height - 1);
1629 painter->drawPoint(x, y + height - 1);
1630 // And then blend corners:
1631 x += this->x();
1632 y += this->y();
1633 basket()->blendBackground(*painter, QRectF(x, y, 1, 1), this->x(), this->y());
1634 basket()->blendBackground(*painter, QRectF(x + width - 1, y, 1, 1), this->x(), this->y());
1635 basket()->blendBackground(*painter, QRectF(x + width - 1, y + height - 1, 1, 1), this->x(), this->y());
1636 basket()->blendBackground(*painter, QRectF(x, y + height - 1, 1, 1), this->x(), this->y());
1637 break;
1638 case 6:
1639 x += this->x();
1640 y += this->y();
1641 //if (!isSelected()) {
1642 // Inside left corners:
1643 basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH + 1, y + 1, 1, 1), this->x(), this->y());
1644 basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH, y + 2, 1, 1), this->x(), this->y());
1645 basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH + 1, y + height - 2, 1, 1), this->x(), this->y());
1646 basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH, y + height - 3, 1, 1), this->x(), this->y());
1647 // Inside right corners:
1648 basket()->blendBackground(*painter, QRectF(x + width - 4, y + 1, 1, 1), this->x(), this->y());
1649 basket()->blendBackground(*painter, QRectF(x + width - 3, y + 2, 1, 1), this->x(), this->y());
1650 basket()->blendBackground(*painter, QRectF(x + width - 4, y + height - 2, 1, 1), this->x(), this->y());
1651 basket()->blendBackground(*painter, QRectF(x + width - 3, y + height - 3, 1, 1), this->x(), this->y());
1652 //}
1653 // Outside right corners:
1654 basket()->blendBackground(*painter, QRectF(x + width - 1, y, 1, 1), this->x(), this->y());
1655 basket()->blendBackground(*painter, QRectF(x + width - 1, y + height - 1, 1, 1), this->x(), this->y());
1656 break;
1657 }
1658 }
1659
1660 /// Blank Spaces Drawing:
1661
setOnTop(bool onTop)1662 void Note::setOnTop(bool onTop)
1663 {
1664 setZValue( onTop ? 100 : 0 );
1665 m_onTop = onTop;
1666
1667 Note *note = firstChild();
1668 while (note) {
1669 note->setOnTop(onTop);
1670 note = note->next();
1671 }
1672 }
1673
substractRectOnAreas(const QRectF & rectToSubstract,QList<QRectF> & areas,bool andRemove)1674 void substractRectOnAreas(const QRectF &rectToSubstract, QList<QRectF> &areas, bool andRemove)
1675 {
1676 for (int i = 0; i < areas.size();) {
1677 QRectF &rect = areas[i];
1678 // Split the rectangle if it intersects with rectToSubstract:
1679 if (rect.intersects(rectToSubstract)) {
1680 // Create the top rectangle:
1681 if (rectToSubstract.top() > rect.top()) {
1682 areas.insert(i++, QRectF(rect.left(), rect.top(), rect.width(), rectToSubstract.top() - rect.top()));
1683 rect.setTop(rectToSubstract.top());
1684 }
1685 // Create the bottom rectangle:
1686 if (rectToSubstract.bottom() < rect.bottom()) {
1687 areas.insert(i++, QRectF(rect.left(), rectToSubstract.bottom(), rect.width(), rect.bottom() - rectToSubstract.bottom()));
1688 rect.setBottom(rectToSubstract.bottom());
1689 }
1690 // Create the left rectangle:
1691 if (rectToSubstract.left() > rect.left()) {
1692 areas.insert(i++, QRectF(rect.left(), rect.top(), rectToSubstract.left() - rect.left(), rect.height()));
1693 rect.setLeft(rectToSubstract.left());
1694 }
1695 // Create the right rectangle:
1696 if (rectToSubstract.right() < rect.right()) {
1697 areas.insert(i++, QRectF(rectToSubstract.right(), rect.top(), rect.right() - rectToSubstract.right(), rect.height()));
1698 rect.setRight(rectToSubstract.right());
1699 }
1700 // Remove the rectangle if it's entirely contained:
1701 if (andRemove && rectToSubstract.contains(rect))
1702 areas.removeAt(i);
1703 else
1704 ++i;
1705 } else
1706 ++i;
1707 }
1708 }
1709
recomputeAreas()1710 void Note::recomputeAreas()
1711 {
1712 // Initialize the areas with the note rectangle(s):
1713 m_areas.clear();
1714 m_areas.append(visibleRect());
1715 if (hasResizer())
1716 m_areas.append(resizerRect());
1717
1718 // Cut the areas where other notes are on top of this note:
1719 Note *note = basket()->firstNote();
1720 bool noteIsAfterThis = false;
1721 while (note) {
1722 noteIsAfterThis = recomputeAreas(note, noteIsAfterThis);
1723 note = note->next();
1724 }
1725 }
1726
recomputeAreas(Note * note,bool noteIsAfterThis)1727 bool Note::recomputeAreas(Note *note, bool noteIsAfterThis)
1728 {
1729 if (note == this)
1730 noteIsAfterThis = true;
1731 // Only compute overlapping of notes AFTER this, or ON TOP this:
1732 //else if ( note->matching() && noteIsAfterThis && (!isOnTop() || (isOnTop() && note->isOnTop())) || (!isOnTop() && note->isOnTop()) ) {
1733 else if (note->matching() && noteIsAfterThis && ((!(isOnTop() || isEditing()) || ((isOnTop() || isEditing()) && (note->isOnTop() || note->isEditing()))) ||
1734 (!(isOnTop() || isEditing()) && (note->isOnTop() || note->isEditing())))) {
1735 //if (!(isSelected() && !note->isSelected())) { // FIXME: FIXME: FIXME: FIXME: This last condition was added LATE, so we should look if it's ALWAYS good:
1736 substractRectOnAreas(note->visibleRect(), m_areas, true);
1737 if (note->hasResizer())
1738 substractRectOnAreas(note->resizerRect(), m_areas, true);
1739 //}
1740 }
1741
1742 if (note->isGroup()) {
1743 Note *child = note->firstChild();
1744 bool first = true;
1745 while (child) {
1746 if ((note->showSubNotes() || first) && note->matching())
1747 noteIsAfterThis = recomputeAreas(child, noteIsAfterThis);
1748 child = child->next();
1749 first = false;
1750 }
1751 }
1752
1753 return noteIsAfterThis;
1754 }
1755
isEditing()1756 bool Note::isEditing()
1757 {
1758 return basket()->editedNote() == this;
1759 }
1760
getGradientColors(const QColor & originalBackground,QColor * colorTop,QColor * colorBottom)1761 void Note::getGradientColors(const QColor &originalBackground, QColor *colorTop, QColor *colorBottom)
1762 {
1763 bool wasTooDark = Tools::tooDark(originalBackground);
1764 if (wasTooDark) {
1765 *colorTop = originalBackground;
1766 *colorBottom = originalBackground.light(120);
1767 } else {
1768 *colorTop = originalBackground.dark(105);
1769 *colorBottom = originalBackground;
1770 }
1771 }
1772
1773 /* Drawing policy:
1774 * ==============
1775 * - Draw the note on a pixmap and then draw the pixmap on screen is faster and
1776 * flicker-free, rather than drawing directly on screen
1777 * - The next time the pixmap can be directly redrawn on screen without
1778 * (relatively low, for small texts) time-consuming text-drawing
1779 * - To keep memory footprint low, we can destruct the bufferPixmap because
1780 * redrawing it offscreen and applying it onscreen is nearly as fast as just
1781 * drawing the pixmap onscreen
1782 * - But as drawing the pixmap offscreen is little time consuming we can keep
1783 * last visible notes buffered and then the redraw of the entire window is
1784 * INSTANTANEOUS
1785 * - We keep bufferized note/group draws BUT NOT the resizer: such objects are
1786 * small and fast to draw, so we don't complexify code for that
1787 */
1788
draw(QPainter * painter,const QRectF &)1789 void Note::draw(QPainter *painter, const QRectF &/*clipRect*/)
1790 {
1791 if (!matching())
1792 return;
1793
1794 /** Compute visible areas: */
1795 if (! m_computedAreas)
1796 recomputeAreas();
1797 if (m_areas.isEmpty())
1798 return;
1799
1800 /** Directly draw pixmap on screen if it is already buffered: */
1801 if (isBufferized()) {
1802 drawBufferOnScreen(painter, m_bufferedPixmap);
1803 return;
1804 }
1805
1806 /** If pixmap is Null (size 0), no point in painting: **/
1807 if (!width() || !height())
1808 return;
1809
1810 /** Initialise buffer painter: */
1811 m_bufferedPixmap = QPixmap(width(), height());
1812 Q_ASSERT(!m_bufferedPixmap.isNull());
1813 QPainter painter2(&m_bufferedPixmap);
1814
1815 /** Initialise colors: */
1816 QColor baseColor(basket()->backgroundColor());
1817 QColor highColor(palette().color(QPalette::Highlight));
1818 QColor midColor = Tools::mixColor(baseColor, highColor);
1819
1820 /** Initialise brushs and pens: */
1821 QBrush baseBrush(baseColor);
1822 QBrush highBrush(highColor);
1823 QPen basePen(baseColor);
1824 QPen highPen(highColor);
1825 QPen midPen(midColor);
1826
1827 /** Figure out the state of the note: */
1828 bool hovered = m_hovered && m_hoveredZone != TopInsert && m_hoveredZone != BottomInsert && m_hoveredZone != Resizer;
1829
1830 /** And then draw the group: */
1831 if (isGroup()) {
1832 //Draw background or handle:
1833 if (hovered) {
1834 drawHandle(&painter2, 0, 0, width(), height(), baseColor, highColor);
1835 drawRoundings(&painter2, 0, 0, /*type=*/1);
1836 drawRoundings(&painter2, 0, height() - 3, /*type=*/2);
1837 } else {
1838 painter2.fillRect(0, 0, width(), height(), baseBrush);
1839 basket()->blendBackground(painter2, boundingRect().translated(x(), y()), -1, -1, /*opaque=*/true);
1840 }
1841
1842 // Draw expander:
1843 qreal yExp = yExpander();
1844 drawExpander(&painter2, NOTE_MARGIN, yExp, (hovered ? expanderBackground(height(), yExp + EXPANDER_HEIGHT / 2, highColor) : baseColor), m_isFolded, basket());
1845 // Draw expander rounded edges:
1846 if (hovered) {
1847 QColor color1 = expanderBackground(height(), yExp, highColor);
1848 QColor color2 = expanderBackground(height(), yExp + EXPANDER_HEIGHT - 1, highColor);
1849 painter2.setPen(color1);
1850 painter2.drawPoint(NOTE_MARGIN, yExp);
1851 painter2.drawPoint(NOTE_MARGIN + 9 - 1, yExp);
1852 painter2.setPen(color2);
1853 painter2.drawPoint(NOTE_MARGIN, yExp + 9 - 1);
1854 painter2.drawPoint(NOTE_MARGIN + 9 - 1, yExp + 9 - 1);
1855 } else
1856 drawRoundings(&painter2, NOTE_MARGIN, yExp, /*type=*/5, 9, 9);
1857 // Draw on screen:
1858 painter2.end();
1859 drawBufferOnScreen(painter, m_bufferedPixmap);
1860
1861 return;
1862 }
1863
1864
1865 /** Or draw the note: */
1866 // What are the background colors:
1867 QColor background = basket()->backgroundColor();
1868 if (isSelected())
1869 {
1870 if (m_computedState.backgroundColor().isValid())
1871 {
1872 background = Tools::mixColor(Tools::mixColor(m_computedState.backgroundColor(), palette().color(QPalette::Highlight)), palette().color(QPalette::Highlight));
1873 }
1874 else
1875 {
1876 background = palette().color(QPalette::Highlight);
1877 }
1878 }
1879 else if (m_computedState.backgroundColor().isValid())
1880 {
1881 background = m_computedState.backgroundColor();
1882 }
1883
1884 QColor bgColor;
1885 QColor darkBgColor;
1886 getGradientColors(background, &darkBgColor, &bgColor);
1887 // Draw background (color, gradient and pixmap):
1888 drawGradient(&painter2, bgColor, darkBgColor, 0, 0, width(), height(), /*sunken=*/!hovered, /*horz=*/true, /*flat=*/false);
1889 basket()->blendBackground(painter2, boundingRect().translated(x(),y()));
1890
1891 if (!hovered)
1892 {
1893 painter2.setPen(Tools::mixColor(bgColor, darkBgColor));
1894 painter2.drawLine(0, height() - 1, width(), height() - 1);
1895 }
1896 else
1897 {
1898 // Top/Bottom lines:
1899 painter2.setPen(highPen);
1900 painter2.drawLine(0, height() - 1, width(), height() - 1);
1901 painter2.drawLine(0, 0, width(), 0);
1902 // The handle:
1903 drawHandle(&painter2, 0, 0, HANDLE_WIDTH, height(), baseColor, highColor);
1904 drawRoundings(&painter2, 0, 0, /*type=*/1);
1905 drawRoundings(&painter2, 0, height() - 3, /*type=*/2);
1906 // Round handle-right-side border:
1907 painter2.setPen(highPen);
1908 painter2.drawPoint(HANDLE_WIDTH, 1);
1909 painter2.drawPoint(HANDLE_WIDTH, height() - 2);
1910 // Light handle top-right round corner:
1911 painter2.setPen(QPen(highColor.light(150)));
1912 painter2.drawPoint(HANDLE_WIDTH - 1, 1);
1913 // Handle anti-aliased rounded handle-right-side corners:
1914 QColor insideMidColor = Tools::mixColor(bgColor, highColor);
1915 painter2.setPen(insideMidColor);
1916 // Left inside round corners:
1917 painter2.drawPoint(HANDLE_WIDTH + 1, 1);
1918 painter2.drawPoint(HANDLE_WIDTH, 2);
1919 painter2.drawPoint(HANDLE_WIDTH + 1, height() - 2);
1920 painter2.drawPoint(HANDLE_WIDTH, height() - 3);
1921 // Right inside round corners:
1922 painter2.drawPoint(width() - 4, 1);
1923 painter2.drawPoint(width() - 3, 2);
1924 painter2.drawPoint(width() - 4, height() - 2);
1925 painter2.drawPoint(width() - 3, height() - 3);
1926 // Right rounded edge:
1927 painter2.setPen(highPen);
1928 painter2.fillRect(width() - 2, 0, 2, height(), highBrush);
1929 painter2.drawPoint(width() - 3, 1);
1930 painter2.drawPoint(width() - 3, height() - 2);
1931 // Right anti-aliased rounded edge:
1932 painter2.setPen(midPen);
1933 painter2.drawPoint(width() - 1, 0);
1934 painter2.drawPoint(width() - 1, height() - 1);
1935 // Blend background pixmap:
1936 drawRoundings(&painter2, 0, 0, /*type=*/6, width(), height());
1937 }
1938
1939 // Draw the Emblems:
1940 qreal yIcon = (height() - EMBLEM_SIZE) / 2;
1941 qreal xIcon = HANDLE_WIDTH + NOTE_MARGIN;
1942 for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) {
1943 if (!(*it)->emblem().isEmpty()) {
1944 QPixmap stateEmblem = KIconLoader::global()->loadIcon(
1945 (*it)->emblem(), KIconLoader::NoGroup, 16,
1946 KIconLoader::DefaultState, QStringList(), 0L, false
1947 );
1948
1949 painter2.drawPixmap(xIcon, yIcon, stateEmblem);
1950 xIcon += NOTE_MARGIN + EMBLEM_SIZE;
1951 }
1952 }
1953
1954 // Determine the colors (for the richText drawing) and the text color (for the tags arrow too):
1955 QPalette notePalette(basket()->palette());
1956 notePalette.setColor(QPalette::Text, (m_computedState.textColor().isValid() ? m_computedState.textColor() : basket()->textColor()));
1957 notePalette.setColor(QPalette::Background, bgColor);
1958 if (isSelected())
1959 notePalette.setColor(QPalette::Text, palette().color(QPalette::HighlightedText));
1960
1961 // Draw the Tags Arrow:
1962 if (hovered) {
1963 QColor textColor = notePalette.color(QPalette::Text);
1964 QColor light = Tools::mixColor(textColor, bgColor);
1965 QColor mid = Tools::mixColor(textColor, light);
1966 painter2.setPen(light);//QPen(basket()->colorGroup().dark().light(150)));
1967 painter2.drawLine(xIcon, yIcon + 6, xIcon + 4, yIcon + 6);
1968 painter2.setPen(mid);//QPen(basket()->colorGroup().dark()));
1969 painter2.drawLine(xIcon + 1, yIcon + 7, xIcon + 3, yIcon + 7);
1970 painter2.setPen(textColor);//QPen(basket()->colorGroup().foreground()));
1971 painter2.drawPoint(xIcon + 2, yIcon + 8);
1972 } else if (m_haveInvisibleTags) {
1973 painter2.setPen(notePalette.color(QPalette::Text)/*QPen(basket()->colorGroup().foreground())*/);
1974 painter2.drawPoint(xIcon, yIcon + 7);
1975 painter2.drawPoint(xIcon + 2, yIcon + 7);
1976 painter2.drawPoint(xIcon + 4, yIcon + 7);
1977 }
1978
1979 if (isFocused()) {
1980 QRect focusRect(HANDLE_WIDTH, NOTE_MARGIN - 1,
1981 width() - HANDLE_WIDTH - 2, height() - 2*NOTE_MARGIN + 2);
1982
1983 // TODO: make this look right/better
1984 QStyleOptionFocusRect opt;
1985 opt.initFrom(m_basket->graphicsView());
1986 opt.rect = focusRect;
1987 qApp->style()->drawPrimitive(QStyle::PE_FrameFocusRect, &opt,
1988 &painter2);
1989 }
1990
1991 // Draw on screen:
1992 painter2.end();
1993 drawBufferOnScreen(painter, m_bufferedPixmap);
1994 }
1995
paint(QPainter * painter,const QStyleOptionGraphicsItem *,QWidget *)1996 void Note::paint(QPainter *painter,
1997 const QStyleOptionGraphicsItem */*option*/,
1998 QWidget */*widget*/)
1999 {
2000 if(!m_basket->isLoaded())
2001 return;
2002
2003 if(boundingRect().width()<=0.1 || boundingRect().height()<=0.1)
2004 return;
2005
2006 draw(painter,boundingRect());
2007
2008 if (hasResizer())
2009 {
2010 qreal right = rightLimit()-x();
2011 QRectF resizerRect(0, 0, RESIZER_WIDTH, resizerHeight());
2012 // Prepare to draw the resizer:
2013 QPixmap pixmap(RESIZER_WIDTH, resizerHeight());
2014 QPainter painter2(&pixmap);
2015 // Draw gradient or resizer:
2016 if (m_hovered && m_hoveredZone == Resizer)
2017 {
2018 QColor baseColor(basket()->backgroundColor());
2019 QColor highColor(palette().color(QPalette::Highlight));
2020 drawResizer(&painter2, 0, 0, RESIZER_WIDTH, resizerHeight(), baseColor, highColor, /*rounded=*/!isColumn());
2021 if (!isColumn()) {
2022 drawRoundings(&painter2, RESIZER_WIDTH - 3, 0, /*type=*/3);
2023 drawRoundings(&painter2, RESIZER_WIDTH - 3, resizerHeight() - 3, /*type=*/4);
2024 }
2025 }
2026 else
2027 {
2028 drawInactiveResizer(&painter2, /*x=*/0, /*y=*/0, /*height=*/resizerHeight(), basket()->backgroundColor(), isColumn());
2029 resizerRect.translate(rightLimit(),y());
2030 basket()->blendBackground(painter2, resizerRect);
2031 }
2032 // Draw on screen:
2033 painter2.end();
2034 painter->drawPixmap(right, 0, pixmap);
2035 }
2036 }
2037
drawBufferOnScreen(QPainter * painter,const QPixmap & contentPixmap)2038 void Note::drawBufferOnScreen(QPainter *painter, const QPixmap &contentPixmap)
2039 {
2040 for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
2041 QRectF rect = (*it).translated(-x(),-y());
2042
2043 if (rect.x() >= width()) // It's a rect of the resizer, don't draw it!
2044 continue;
2045
2046 painter->drawPixmap(rect.x(), rect.y(), contentPixmap, rect.x(), rect.y(), rect.width(), rect.height());
2047 }
2048 }
2049
setContent(NoteContent * content)2050 void Note::setContent(NoteContent *content)
2051 {
2052 m_content = content;
2053 }
2054
states() const2055 /*const */State::List& Note::states() const
2056 {
2057 return (State::List&)m_states;
2058 }
2059
addState(State * state,bool orReplace)2060 void Note::addState(State *state, bool orReplace)
2061 {
2062 if (!content())
2063 return;
2064
2065 Tag *tag = state->parentTag();
2066 State::List::iterator itStates = m_states.begin();
2067 // Browse all tags, see if the note has it, increment itSates if yes, and then insert the state at this position...
2068 // For each existing tags:
2069 for (Tag::List::iterator it = Tag::all.begin(); it != Tag::all.end(); ++it) {
2070 // If the current tag isn't the one to assign or the current one on the note, go to the next tag:
2071 if (*it != tag && itStates != m_states.end() && *it != (*itStates)->parentTag())
2072 continue;
2073 // We found the tag to insert:
2074 if (*it == tag) {
2075 // And the note already have the tag:
2076 if (itStates != m_states.end() && *it == (*itStates)->parentTag()) {
2077 // We replace the state if wanted:
2078 if (orReplace) {
2079 itStates = m_states.insert(itStates, state);
2080 ++itStates;
2081 m_states.erase(itStates);
2082 recomputeStyle();
2083 }
2084 } else {
2085 m_states.insert(itStates, state);
2086 recomputeStyle();
2087 }
2088 return;
2089 }
2090 // The note has this tag:
2091 if (itStates != m_states.end() && *it == (*itStates)->parentTag())
2092 ++itStates;
2093 }
2094 }
2095
font()2096 QFont Note::font()
2097 {
2098 return m_computedState.font(basket()->QGraphicsScene::font());
2099 }
2100
backgroundColor()2101 QColor Note::backgroundColor()
2102 {
2103 if (m_computedState.backgroundColor().isValid())
2104 return m_computedState.backgroundColor();
2105 else
2106 return basket()->backgroundColor();
2107 }
2108
textColor()2109 QColor Note::textColor()
2110 {
2111 if (m_computedState.textColor().isValid())
2112 return m_computedState.textColor();
2113 else
2114 return basket()->textColor();
2115 }
2116
recomputeStyle()2117 void Note::recomputeStyle()
2118 {
2119 State::merge(m_states, &m_computedState, &m_emblemsCount, &m_haveInvisibleTags, basket()->backgroundColor());
2120 // unsetWidth();
2121 if (content())
2122 {
2123 if(content()->graphicsItem())
2124 content()->graphicsItem()->setPos(contentX(),NOTE_MARGIN);
2125 content()->fontChanged();
2126 }
2127 // requestRelayout(); // TODO!
2128 }
2129
recomputeAllStyles()2130 void Note::recomputeAllStyles()
2131 {
2132 if (content()) // We do the merge ourself, without calling recomputeStyle(), so there is no infinite recursion:
2133 //State::merge(m_states, &m_computedState, &m_emblemsCount, &m_haveInvisibleTags, basket()->backgroundColor());
2134 recomputeStyle();
2135 else if (isGroup())
2136 FOR_EACH_CHILD(child)
2137 child->recomputeAllStyles();
2138 }
2139
removedStates(const QList<State * > & deletedStates)2140 bool Note::removedStates(const QList<State*> &deletedStates)
2141 {
2142 bool modifiedBasket = false;
2143
2144 if (!states().isEmpty()) {
2145 for (QList<State*>::const_iterator it = deletedStates.begin(); it != deletedStates.end(); ++it)
2146 if (hasState(*it)) {
2147 removeState(*it);
2148 modifiedBasket = true;
2149 }
2150 }
2151
2152 FOR_EACH_CHILD(child)
2153 if (child->removedStates(deletedStates))
2154 modifiedBasket = true;
2155
2156 return modifiedBasket;
2157 }
2158
2159
addTag(Tag * tag)2160 void Note::addTag(Tag *tag)
2161 {
2162 addState(tag->states().first(), /*but do not replace:*/false);
2163 }
2164
removeState(State * state)2165 void Note::removeState(State *state)
2166 {
2167 for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
2168 if (*it == state) {
2169 m_states.erase(it);
2170 recomputeStyle();
2171 return;
2172 }
2173 }
2174
removeTag(Tag * tag)2175 void Note::removeTag(Tag *tag)
2176 {
2177 for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
2178 if ((*it)->parentTag() == tag) {
2179 m_states.erase(it);
2180 recomputeStyle();
2181 return;
2182 }
2183 }
2184
removeAllTags()2185 void Note::removeAllTags()
2186 {
2187 m_states.clear();
2188 recomputeStyle();
2189 }
2190
addTagToSelectedNotes(Tag * tag)2191 void Note::addTagToSelectedNotes(Tag *tag)
2192 {
2193 if (content() && isSelected())
2194 addTag(tag);
2195
2196 FOR_EACH_CHILD(child)
2197 child->addTagToSelectedNotes(tag);
2198 }
2199
removeTagFromSelectedNotes(Tag * tag)2200 void Note::removeTagFromSelectedNotes(Tag *tag)
2201 {
2202 if (content() && isSelected()) {
2203 if (hasTag(tag))
2204 setWidth(0);
2205 removeTag(tag);
2206 }
2207
2208 FOR_EACH_CHILD(child)
2209 child->removeTagFromSelectedNotes(tag);
2210 }
2211
removeAllTagsFromSelectedNotes()2212 void Note::removeAllTagsFromSelectedNotes()
2213 {
2214 if (content() && isSelected()) {
2215 if (m_states.count() > 0)
2216 setWidth(0);
2217 removeAllTags();
2218 }
2219
2220 FOR_EACH_CHILD(child)
2221 child->removeAllTagsFromSelectedNotes();
2222 }
2223
addStateToSelectedNotes(State * state,bool orReplace)2224 void Note::addStateToSelectedNotes(State *state, bool orReplace)
2225 {
2226 if (content() && isSelected())
2227 addState(state, orReplace);
2228
2229 FOR_EACH_CHILD(child)
2230 child->addStateToSelectedNotes(state, orReplace); // TODO: BasketScene::addStateToSelectedNotes() does not have orReplace
2231 }
2232
changeStateOfSelectedNotes(State * state)2233 void Note::changeStateOfSelectedNotes(State *state)
2234 {
2235 if (content() && isSelected() && hasTag(state->parentTag()))
2236 addState(state);
2237
2238 FOR_EACH_CHILD(child)
2239 child->changeStateOfSelectedNotes(state);
2240 }
2241
selectedNotesHaveTags()2242 bool Note::selectedNotesHaveTags()
2243 {
2244 if (content() && isSelected() && m_states.count() > 0)
2245 return true;
2246
2247 FOR_EACH_CHILD(child)
2248 if (child->selectedNotesHaveTags())
2249 return true;
2250 return false;
2251 }
2252
hasState(State * state)2253 bool Note::hasState(State *state)
2254 {
2255 for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
2256 if (*it == state)
2257 return true;
2258 return false;
2259 }
2260
hasTag(Tag * tag)2261 bool Note::hasTag(Tag *tag)
2262 {
2263 for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
2264 if ((*it)->parentTag() == tag)
2265 return true;
2266 return false;
2267 }
2268
stateOfTag(Tag * tag)2269 State* Note::stateOfTag(Tag *tag)
2270 {
2271 for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
2272 if ((*it)->parentTag() == tag)
2273 return *it;
2274 return 0;
2275 }
2276
allowCrossReferences()2277 bool Note::allowCrossReferences()
2278 {
2279 for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
2280 if (!(*it)->allowCrossReferences())
2281 return false;
2282 return true;
2283 }
2284
stateForEmblemNumber(int number) const2285 State* Note::stateForEmblemNumber(int number) const
2286 {
2287 int i = -1;
2288 for (State::List::const_iterator it = m_states.begin(); it != m_states.end(); ++it)
2289 if (!(*it)->emblem().isEmpty()) {
2290 ++i;
2291 if (i == number)
2292 return *it;
2293 }
2294 return 0;
2295 }
2296
stateForTagFromSelectedNotes(Tag * tag,State ** state)2297 bool Note::stateForTagFromSelectedNotes(Tag *tag, State **state)
2298 {
2299 if (content() && isSelected()) {
2300 // What state is the tag on this note?
2301 State* stateOfTag = this->stateOfTag(tag);
2302 // This tag is not assigned to this note, the action will assign it, then:
2303 if (stateOfTag == 0)
2304 *state = 0;
2305 else {
2306 // Take the LOWEST state of all the selected notes:
2307 // Say the two selected notes have the state "Done" and "To Do".
2308 // The first note set *state to "Done".
2309 // When reaching the second note, we should recognize "To Do" is first in the tag states, then take it
2310 // Because pressing the tag shortcut key should first change state before removing the tag!
2311 if (*state == 0)
2312 *state = stateOfTag;
2313 else {
2314 bool stateIsFirst = true;
2315 for (State *nextState = stateOfTag->nextState(); nextState; nextState = nextState->nextState(/*cycle=*/false))
2316 if (nextState == *state)
2317 stateIsFirst = false;
2318 if (!stateIsFirst)
2319 *state = stateOfTag;
2320 }
2321 }
2322 return true; // We encountered a selected note
2323 }
2324
2325 bool encounteredSelectedNote = false;
2326 FOR_EACH_CHILD(child) {
2327 bool encountered = child->stateForTagFromSelectedNotes(tag, state);
2328 if (encountered && *state == 0)
2329 return true;
2330 if (encountered)
2331 encounteredSelectedNote = true;
2332 }
2333 return encounteredSelectedNote;
2334 }
2335
2336
inheritTagsOf(Note * note)2337 void Note::inheritTagsOf(Note *note)
2338 {
2339 if (!note || !content())
2340 return;
2341
2342 for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it)
2343 if ((*it)->parentTag() && (*it)->parentTag()->inheritedBySiblings())
2344 addTag((*it)->parentTag());
2345 }
2346
unbufferizeAll()2347 void Note::unbufferizeAll()
2348 {
2349 unbufferize();
2350
2351 if (isGroup()) {
2352 Note *child = firstChild();
2353 while (child) {
2354 child->unbufferizeAll();
2355 child = child->next();
2356 }
2357 }
2358 }
2359
visibleRect()2360 QRectF Note::visibleRect()
2361 {
2362 QList<QRectF> areas;
2363 areas.append(QRectF(x(),y(),width(),height()));
2364
2365 // When we are folding a parent group, if this note is bigger than the first real note of the group, cut the top of this:
2366 /*Note *parent = parentNote();
2367 while (parent) {
2368 if (parent->expandingOrCollapsing())
2369 substractRectOnAreas(QRect(x(), parent->y() - height(), width(), height()), areas, true);
2370 parent = parent->parentNote();
2371 }*/
2372
2373 if (areas.count() > 0)
2374 return areas.first();
2375 else
2376 return QRectF();
2377 }
2378
recomputeBlankRects(QList<QRectF> & blankAreas)2379 void Note::recomputeBlankRects(QList<QRectF> &blankAreas)
2380 {
2381 if (!matching())
2382 return;
2383
2384 // visibleRect() instead of rect() because if we are folding/expanding a smaller parent group, then some part is hidden!
2385 // But anyway, a resizer is always a primary note and is never hidden by a parent group, so no visibleResizerRect() method!
2386 substractRectOnAreas(visibleRect(), blankAreas, true);
2387 if (hasResizer())
2388 substractRectOnAreas(resizerRect(), blankAreas, true);
2389
2390 if (isGroup()) {
2391 Note *child = firstChild();
2392 bool first = true;
2393 while (child) {
2394 if ((showSubNotes() || first) && child->matching())
2395 child->recomputeBlankRects(blankAreas);
2396 child = child->next();
2397 first = false;
2398 }
2399 }
2400 }
2401
linkLookChanged()2402 void Note::linkLookChanged()
2403 {
2404 if (isGroup()) {
2405 Note *child = firstChild();
2406 while (child) {
2407 child->linkLookChanged();
2408 child = child->next();
2409 }
2410 } else
2411 content()->linkLookChanged();
2412 }
2413
noteForFullPath(const QString & path)2414 Note* Note::noteForFullPath(const QString &path)
2415 {
2416 if (content() && fullPath() == path)
2417 return this;
2418
2419 Note *child = firstChild();
2420 Note *found;
2421 while (child) {
2422 found = child->noteForFullPath(path);
2423 if (found)
2424 return found;
2425 child = child->next();
2426 }
2427 return 0;
2428 }
2429
listUsedTags(QList<Tag * > & list)2430 void Note::listUsedTags(QList<Tag*> &list)
2431 {
2432 for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) {
2433 Tag *tag = (*it)->parentTag();
2434 if (!list.contains(tag))
2435 list.append(tag);
2436 }
2437
2438 FOR_EACH_CHILD(child)
2439 child->listUsedTags(list);
2440 }
2441
2442
usedStates(QList<State * > & states)2443 void Note::usedStates(QList<State*> &states)
2444 {
2445 if (content())
2446 for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it)
2447 if (!states.contains(*it))
2448 states.append(*it);
2449
2450 FOR_EACH_CHILD(child)
2451 child->usedStates(states);
2452 }
2453
nextInStack()2454 Note* Note::nextInStack()
2455 {
2456 // First, search in the children:
2457 if (firstChild()) {
2458 if (firstChild()->content())
2459 return firstChild();
2460 else
2461 return firstChild()->nextInStack();
2462 }
2463 // Then, in the next:
2464 if (next()) {
2465 if (next()->content())
2466 return next();
2467 else
2468 return next()->nextInStack();
2469 }
2470 // And finally, in the parent:
2471 Note *note = parentNote();
2472 while (note)
2473 if (note->next())
2474 if (note->next()->content())
2475 return note->next();
2476 else
2477 return note->next()->nextInStack();
2478 else
2479 note = note->parentNote();
2480
2481 // Not found:
2482 return 0;
2483 }
2484
prevInStack()2485 Note* Note::prevInStack()
2486 {
2487 // First, search in the previous:
2488 if (prev() && prev()->content())
2489 return prev();
2490
2491 // Else, it's a group, get the last item in that group:
2492 if (prev()) {
2493 Note *note = prev()->lastRealChild();
2494 if (note)
2495 return note;
2496 }
2497
2498 if (parentNote())
2499 return parentNote()->prevInStack();
2500 else
2501 return 0;
2502 }
2503
nextShownInStack()2504 Note* Note::nextShownInStack()
2505 {
2506 Note *next = nextInStack();
2507 while (next && !next->isShown())
2508 next = next->nextInStack();
2509 return next;
2510 }
2511
prevShownInStack()2512 Note* Note::prevShownInStack()
2513 {
2514 Note *prev = prevInStack();
2515 while (prev && !prev->isShown())
2516 prev = prev->prevInStack();
2517 return prev;
2518 }
2519
isShown()2520 bool Note::isShown()
2521 {
2522 // First, the easy one: groups are always shown:
2523 if (isGroup())
2524 return true;
2525
2526 // And another easy part: non-matching notes are hidden:
2527 if (!matching())
2528 return false;
2529
2530 if (basket()->isFiltering()) // And isMatching() because of the line above!
2531 return true;
2532
2533 // So, here we go to the complexe case: if the note is inside a collapsed group:
2534 Note *group = parentNote();
2535 Note *child = this;
2536 while (group) {
2537 if (group->isFolded() && group->firstChild() != child)
2538 return false;
2539 child = group;
2540 group = group->parentNote();
2541 }
2542 return true;
2543 }
2544
debug()2545 void Note::debug()
2546 {
2547 qDebug() << "Note@" << (quint64)this;
2548 if (!this) {
2549 qDebug();
2550 return;
2551 }
2552
2553 if (isColumn())
2554 qDebug() << ": Column";
2555 else if (isGroup())
2556 qDebug() << ": Group";
2557 else
2558 qDebug() << ": Content[" << content()->lowerTypeName() << "]: " << toText("");
2559 qDebug();
2560 }
2561
firstSelected()2562 Note* Note::firstSelected()
2563 {
2564 if (isSelected())
2565 return this;
2566
2567 Note *first = 0;
2568 FOR_EACH_CHILD(child) {
2569 first = child->firstSelected();
2570 if (first)
2571 break;
2572 }
2573 return first;
2574 }
2575
lastSelected()2576 Note* Note::lastSelected()
2577 {
2578 if (isSelected())
2579 return this;
2580
2581 Note *last = 0, *tmp = 0;
2582 FOR_EACH_CHILD(child) {
2583 tmp = child->lastSelected();
2584 if (tmp)
2585 last = tmp;
2586 }
2587 return last;
2588 }
2589
selectedGroup()2590 Note* Note::selectedGroup()
2591 {
2592 if (isGroup() && allSelected() && count() == basket()->countSelecteds())
2593 return this;
2594
2595 FOR_EACH_CHILD(child) {
2596 Note *selectedGroup = child->selectedGroup();
2597 if (selectedGroup)
2598 return selectedGroup;
2599 }
2600
2601 return 0;
2602 }
2603
groupIn(Note * group)2604 void Note::groupIn(Note *group)
2605 {
2606 if (this == group)
2607 return;
2608
2609 if (allSelected() && !isColumn()) {
2610 basket()->unplugNote(this);
2611 basket()->insertNote(this, group, Note::BottomColumn, QPointF(), /*animateNewPosition=*/true);
2612 } else {
2613 Note *next;
2614 Note *child = firstChild();
2615 while (child) {
2616 next = child->next();
2617 child->groupIn(group);
2618 child = next;
2619 }
2620 }
2621 }
2622
tryExpandParent()2623 bool Note::tryExpandParent()
2624 {
2625 Note *parent = parentNote();
2626 Note *child = this;
2627 while (parent) {
2628 if (parent->firstChild() != child)
2629 return false;
2630 if (parent->isColumn())
2631 return false;
2632 if (parent->isFolded()) {
2633 parent->toggleFolded();
2634 basket()->relayoutNotes(true);
2635 return true;
2636 }
2637 child = parent;
2638 parent = parent->parentNote();
2639 }
2640 return false;
2641 }
2642
tryFoldParent()2643 bool Note::tryFoldParent() // TODO: withCtrl ? withShift ?
2644 {
2645 Note *parent = parentNote();
2646 Note *child = this;
2647 while (parent) {
2648 if (parent->firstChild() != child)
2649 return false;
2650 if (parent->isColumn())
2651 return false;
2652 if (!parent->isFolded()) {
2653 parent->toggleFolded();
2654 basket()->relayoutNotes(true);
2655 return true;
2656 }
2657 child = parent;
2658 parent = parent->parentNote();
2659 }
2660 return false;
2661 }
2662
2663
distanceOnLeftRight(Note * note,int side)2664 qreal Note::distanceOnLeftRight(Note *note, int side)
2665 {
2666 if (side == BasketScene::RIGHT_SIDE) {
2667 // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key:
2668 if (x() > note->x() || finalRightLimit() > note->finalRightLimit())
2669 return -1;
2670 } else { /*LEFT_SIDE:*/
2671 // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key:
2672 if (x() < note->x() || finalRightLimit() < note->finalRightLimit())
2673 return -1;
2674 }
2675 if (x() == note->x() && finalRightLimit() == note->finalRightLimit())
2676 return -1;
2677
2678 qreal thisCenterX = x() + (side == BasketScene::LEFT_SIDE ? width() : /*RIGHT_SIDE:*/ 0);
2679 qreal thisCenterY = y() + height() / 2;
2680 qreal noteCenterX = note->x() + note->width() / 2;
2681 qreal noteCenterY = note->y() + note->height() / 2;
2682
2683 if (thisCenterY > note->bottom())
2684 noteCenterY = note->bottom();
2685 else if (thisCenterY < note->y())
2686 noteCenterY = note->y();
2687 else
2688 noteCenterY = thisCenterY;
2689
2690 qreal angle = 0;
2691 if (noteCenterX - thisCenterX != 0)
2692 angle = 1000 * ((noteCenterY - thisCenterY) / (noteCenterX - thisCenterX));
2693 if (angle < 0)
2694 angle = -angle;
2695
2696 return sqrt(pow(noteCenterX - thisCenterX, 2) + pow(noteCenterY - thisCenterY, 2)) + angle;
2697 }
2698
distanceOnTopBottom(Note * note,int side)2699 qreal Note::distanceOnTopBottom(Note *note, int side)
2700 {
2701 if (side == BasketScene::BOTTOM_SIDE) {
2702 // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key:
2703 if (y() > note->y() || bottom() > note->bottom())
2704 return -1;
2705 } else { /*TOP_SIDE:*/
2706 // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key:
2707 if (y() < note->y() || bottom() < note->bottom())
2708 return -1;
2709 }
2710 if (y() == note->y() && bottom() == note->bottom())
2711 return -1;
2712
2713 qreal thisCenterX = x() + width() / 2;
2714 qreal thisCenterY = y() + (side == BasketScene::TOP_SIDE ? height() : /*BOTTOM_SIDE:*/ 0);
2715 qreal noteCenterX = note->x() + note->width() / 2;
2716 qreal noteCenterY = note->y() + note->height() / 2;
2717
2718 if (thisCenterX > note->finalRightLimit())
2719 noteCenterX = note->finalRightLimit();
2720 else if (thisCenterX < note->x())
2721 noteCenterX = note->x();
2722 else
2723 noteCenterX = thisCenterX;
2724
2725 qreal angle = 0;
2726 if (noteCenterX - thisCenterX != 0)
2727 angle = 1000 * ((noteCenterY - thisCenterY) / (noteCenterX - thisCenterX));
2728 if (angle < 0)
2729 angle = -angle;
2730
2731 return sqrt(pow(noteCenterX - thisCenterX, 2) + pow(noteCenterY - thisCenterY, 2)) + angle;
2732 }
2733
parentPrimaryNote()2734 Note* Note::parentPrimaryNote()
2735 {
2736 Note *primary = this;
2737 while (primary->parentNote())
2738 primary = primary->parentNote();
2739 return primary;
2740 }
2741
deleteChilds()2742 void Note::deleteChilds()
2743 {
2744 Note *child = firstChild();
2745
2746 while (child) {
2747 Note *tmp = child->next();
2748 delete child;
2749 child = tmp;
2750 }
2751 }
2752
saveAgain()2753 bool Note::saveAgain()
2754 {
2755 bool result = true;
2756
2757 if (content()) {
2758 if (!content()->saveToFile())
2759 result = false;
2760 }
2761 FOR_EACH_CHILD(child) {
2762 if (!child->saveAgain())
2763 result = false;
2764 }
2765 if (!result)
2766 DEBUG_WIN << QString("Note::saveAgain returned false for %1:%2").arg((content() != NULL) ? content()->typeName() : "null", toText(""));
2767 return result;
2768 }
2769
convertTexts()2770 bool Note::convertTexts()
2771 {
2772 bool convertedNotes = false;
2773
2774 if (content() && content()->lowerTypeName() == "text") {
2775 QString text = ((TextContent*)content())->text();
2776 QString html = "<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"><meta name=\"qrichtext\" content=\"1\" /></head><body>" + Tools::textToHTMLWithoutP(text) + "</body></html>";
2777 basket()->saveToFile(fullPath(), html);
2778 setContent(new HtmlContent(this, content()->fileName()));
2779 convertedNotes = true;
2780 }
2781
2782 FOR_EACH_CHILD(child)
2783 if (child->convertTexts())
2784 convertedNotes = true;
2785
2786 return convertedNotes;
2787 }
2788
2789 /* vim: set et sts=4 sw=4 ts=16 tw=78 : */
2790 /* kate: indent-width 4; replace-tabs on; */
2791