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