File indexing completed on 2024-04-21 05:48:20

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût <slaout@linux62.org>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "note.h"
0008 
0009 #include <QApplication>
0010 #include <QGraphicsItemAnimation>
0011 #include <QGraphicsView>
0012 #include <QLocale> //For KGLobal::locale(
0013 #include <QStyle>
0014 #include <QStyleOption>
0015 #include <QtCore/QList>
0016 #include <QtGui/QImage>
0017 #include <QtGui/QPainter>
0018 #include <QtGui/QPixmap>
0019 
0020 #include <KIconLoader>
0021 
0022 #include <math.h>   // sqrt() and pow() functions
0023 #include <stdlib.h> // rand() function
0024 
0025 #include "basketscene.h"
0026 #include "common.h"
0027 #include "debugwindow.h"
0028 #include "filter.h"
0029 #include "notefactory.h" // For NoteFactory::filteredURL()
0030 #include "noteselection.h"
0031 #include "settings.h"
0032 #include "tag.h"
0033 #include "tools.h"
0034 
0035 /** class Note: */
0036 
0037 #define FOR_EACH_CHILD(childVar) for (Note *childVar = firstChild(); childVar; childVar = childVar->next())
0038 
0039 class NotePrivate
0040 {
0041 public:
0042     NotePrivate()
0043         : prev(nullptr)
0044         , next(nullptr)
0045         , width(-1)
0046         , height(Note::MIN_HEIGHT)
0047     {
0048     }
0049     Note *prev;
0050     Note *next;
0051     qreal width;
0052     qreal height;
0053 };
0054 
0055 qreal Note::NOTE_MARGIN = 2;
0056 qreal Note::INSERTION_HEIGHT = 5;
0057 qreal Note::EXPANDER_WIDTH = 9;
0058 qreal Note::EXPANDER_HEIGHT = 9;
0059 qreal Note::GROUP_WIDTH = 2 * NOTE_MARGIN + EXPANDER_WIDTH;
0060 qreal Note::HANDLE_WIDTH = GROUP_WIDTH;
0061 qreal Note::RESIZER_WIDTH = GROUP_WIDTH;
0062 qreal Note::TAG_ARROW_WIDTH = 5;
0063 qreal Note::EMBLEM_SIZE = 16;
0064 qreal Note::MIN_HEIGHT = 2 * NOTE_MARGIN + EMBLEM_SIZE;
0065 
0066 Note::Note(BasketScene *parent)
0067     : d(new NotePrivate)
0068     , m_groupWidth(250)
0069     , m_isFolded(false)
0070     , m_firstChild(nullptr)
0071     , m_parentNote(nullptr)
0072     , m_basket(parent)
0073     , m_content(nullptr)
0074     , m_addedDate(QDateTime::currentDateTime())
0075     , m_lastModificationDate(QDateTime::currentDateTime())
0076     , m_computedAreas(false)
0077     , m_onTop(false)
0078     , m_hovered(false)
0079     , m_hoveredZone(Note::None)
0080     , m_focused(false)
0081     , m_selected(false)
0082     , m_wasInLastSelectionRect(false)
0083     , m_computedState()
0084     , m_emblemsCount(0)
0085     , m_haveInvisibleTags(false)
0086     , m_matching(true)
0087 {
0088     setHeight(MIN_HEIGHT);
0089     if (m_basket) {
0090         m_basket->addItem(this);
0091     }
0092 }
0093 
0094 Note::~Note()
0095 {
0096     if (m_basket) {
0097         if (m_content && m_content->graphicsItem()) {
0098             m_basket->removeItem(m_content->graphicsItem());
0099         }
0100         m_basket->removeItem(this);
0101     }
0102     delete m_content;
0103     deleteChilds();
0104 }
0105 
0106 void Note::setNext(Note *next)
0107 {
0108     d->next = next;
0109 }
0110 
0111 Note *Note::next() const
0112 {
0113     return d->next;
0114 }
0115 
0116 void Note::setPrev(Note *prev)
0117 {
0118     d->prev = prev;
0119 }
0120 
0121 Note *Note::prev() const
0122 {
0123     return d->prev;
0124 }
0125 
0126 qreal Note::bottom() const
0127 {
0128     return y() + height() - 1;
0129 }
0130 
0131 void Note::setParentBasket(BasketScene *basket)
0132 {
0133     if (m_basket)
0134         m_basket->removeItem(this);
0135     m_basket = basket;
0136     if (m_basket)
0137         m_basket->addItem(this);
0138 }
0139 
0140 QString Note::addedStringDate()
0141 {
0142     return m_addedDate.toString();
0143 }
0144 
0145 QString Note::lastModificationStringDate()
0146 {
0147     return m_lastModificationDate.toString();
0148 }
0149 
0150 QString Note::toText(const QString &cuttedFullPath)
0151 {
0152     if (content()) {
0153         // Convert note to text:
0154         QString text = content()->toText(cuttedFullPath);
0155         // If we should not export tags with the text, return immediately:
0156         if (!Settings::exportTextTags())
0157             return text;
0158         // Compute the text equivalent of the tag states:
0159         QString firstLine;
0160         QString otherLines;
0161         for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) {
0162             if (!(*it)->textEquivalent().isEmpty()) {
0163                 firstLine += (*it)->textEquivalent() + ' ';
0164                 if ((*it)->onAllTextLines())
0165                     otherLines += (*it)->textEquivalent() + ' ';
0166             }
0167         }
0168         // Merge the texts:
0169         if (firstLine.isEmpty())
0170             return text;
0171         if (otherLines.isEmpty())
0172             return firstLine + text;
0173         QStringList lines = text.split('\n');
0174         QString result = firstLine + lines[0] + (lines.count() > 1 ? "\n" : QString());
0175         for (int i = 1 /*Skip the first line*/; i < lines.count(); ++i)
0176             result += otherLines + lines[i] + (i < lines.count() - 1 ? "\n" : QString());
0177 
0178         return result;
0179     } else
0180         return QString();
0181 }
0182 
0183 bool Note::computeMatching(const FilterData &data)
0184 {
0185     // Groups are always matching:
0186     if (!content())
0187         return true;
0188 
0189     // If we were editing this note and there is a save operation in the middle, then do not hide it suddenly:
0190     if (basket()->editedNote() == this)
0191         return true;
0192 
0193     bool matching;
0194     // First match tags (they are fast to compute):
0195     switch (data.tagFilterType) {
0196     default:
0197     case FilterData::DontCareTagsFilter:
0198         matching = true;
0199         break;
0200     case FilterData::NotTaggedFilter:
0201         matching = m_states.count() <= 0;
0202         break;
0203     case FilterData::TaggedFilter:
0204         matching = m_states.count() > 0;
0205         break;
0206     case FilterData::TagFilter:
0207         matching = hasTag(data.tag);
0208         break;
0209     case FilterData::StateFilter:
0210         matching = hasState(data.state);
0211         break;
0212     }
0213 
0214     // 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:
0215     if (matching && !data.string.isEmpty())
0216         matching = content()->match(data);
0217 
0218     return matching;
0219 }
0220 
0221 int Note::newFilter(const FilterData &data)
0222 {
0223     bool wasMatching = matching();
0224     m_matching = computeMatching(data);
0225     setOnTop(wasMatching && matching());
0226     if (!matching()) {
0227         setSelected(false);
0228         hide();
0229     } else if (!wasMatching) {
0230         show();
0231     }
0232 
0233     int countMatches = (content() && matching() ? 1 : 0);
0234 
0235     FOR_EACH_CHILD(child)
0236     {
0237         countMatches += child->newFilter(data);
0238     }
0239 
0240     return countMatches;
0241 }
0242 
0243 void Note::deleteSelectedNotes(bool deleteFilesToo, QSet<Note *> *notesToBeDeleted)
0244 {
0245     if (content()) {
0246         if (isSelected()) {
0247             basket()->unplugNote(this);
0248             if (deleteFilesToo && content()->useFile()) {
0249                 Tools::deleteRecursively(fullPath()); // basket()->deleteFiles(fullPath()); // Also delete the folder if it's a folder
0250             }
0251             if (notesToBeDeleted) {
0252                 notesToBeDeleted->insert(this);
0253             }
0254         }
0255         return;
0256     }
0257 
0258     bool isColumn = this->isColumn();
0259     Note *child = firstChild();
0260     Note *next;
0261     while (child) {
0262         next = child->next(); // If we delete 'child' on the next line, child->next() will be 0!
0263         child->deleteSelectedNotes(deleteFilesToo, notesToBeDeleted);
0264         child = next;
0265     }
0266 
0267     // if it remains at least two notes, the group must not be deleted
0268     if (!isColumn && !(firstChild() && firstChild()->next())) {
0269         if (notesToBeDeleted) {
0270             notesToBeDeleted->insert(this);
0271         }
0272     }
0273 }
0274 
0275 int Note::count()
0276 {
0277     if (content())
0278         return 1;
0279 
0280     int count = 0;
0281     FOR_EACH_CHILD(child)
0282     {
0283         count += child->count();
0284     }
0285     return count;
0286 }
0287 
0288 int Note::countDirectChilds()
0289 {
0290     int count = 0;
0291     FOR_EACH_CHILD(child)
0292     {
0293         ++count;
0294     }
0295     return count;
0296 }
0297 
0298 QString Note::fullPath()
0299 {
0300     if (content())
0301         return basket()->fullPath() + content()->fileName();
0302     else
0303         return QString();
0304 }
0305 
0306 /*void Note::update()
0307 {
0308     update(0,0,boundingRect().width,boundingRect().height);
0309 }*/
0310 
0311 void Note::setFocused(bool focused)
0312 {
0313     if (m_focused == focused)
0314         return;
0315 
0316     m_focused = focused;
0317     unbufferize();
0318     update(); // FIXME: ???
0319 }
0320 
0321 void Note::setSelected(bool selected)
0322 {
0323     if (isGroup())
0324         selected = false; // A group cannot be selected!
0325 
0326     if (m_selected == selected)
0327         return;
0328 
0329     if (!selected && basket()->editedNote() == this) {
0330         // basket()->closeEditor();
0331         return; // To avoid a bug that would count 2 less selected notes instead of 1 less! Because m_selected is modified only below.
0332     }
0333 
0334     if (selected)
0335         basket()->addSelectedNote();
0336     else
0337         basket()->removeSelectedNote();
0338 
0339     m_selected = selected;
0340     unbufferize();
0341     update(); // FIXME: ???
0342 }
0343 
0344 void Note::resetWasInLastSelectionRect()
0345 {
0346     m_wasInLastSelectionRect = false;
0347 
0348     FOR_EACH_CHILD(child)
0349     {
0350         child->resetWasInLastSelectionRect();
0351     }
0352 }
0353 
0354 void Note::finishLazyLoad()
0355 {
0356     if (content())
0357         content()->finishLazyLoad();
0358 
0359     FOR_EACH_CHILD(child)
0360     {
0361         child->finishLazyLoad();
0362     }
0363 }
0364 
0365 void Note::selectIn(const QRectF &rect, bool invertSelection, bool unselectOthers /*= true*/)
0366 {
0367     //  QRect myRect(x(), y(), width(), height());
0368 
0369     //  bool intersects = myRect.intersects(rect);
0370 
0371     // Only intersects with visible areas.
0372     // 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:
0373     bool intersects = false;
0374     for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
0375         QRectF &r = *it;
0376         if (r.intersects(rect)) {
0377             intersects = true;
0378             break;
0379         }
0380     }
0381 
0382     bool toSelect = intersects || (!unselectOthers && isSelected());
0383     if (invertSelection) {
0384         toSelect = (m_wasInLastSelectionRect == intersects) ? isSelected() : !isSelected();
0385     }
0386 
0387     setSelected(toSelect);
0388     m_wasInLastSelectionRect = intersects;
0389 
0390     Note *child = firstChild();
0391     bool first = true;
0392     while (child) {
0393         if ((showSubNotes() || first) && child->matching())
0394             child->selectIn(rect, invertSelection, unselectOthers);
0395         else
0396             child->setSelectedRecursively(false);
0397         child = child->next();
0398         first = false;
0399     }
0400 }
0401 
0402 bool Note::allSelected()
0403 {
0404     if (isGroup()) {
0405         Note *child = firstChild();
0406         bool first = true;
0407         while (child) {
0408             if ((showSubNotes() || first) && child->matching())
0409                 if (!child->allSelected())
0410                     return false;
0411             ;
0412             child = child->next();
0413             first = false;
0414         }
0415         return true;
0416     } else
0417         return isSelected();
0418 }
0419 
0420 void Note::setSelectedRecursively(bool selected)
0421 {
0422     setSelected(selected && matching());
0423 
0424     FOR_EACH_CHILD(child)
0425     {
0426         child->setSelectedRecursively(selected);
0427     }
0428 }
0429 
0430 void Note::invertSelectionRecursively()
0431 {
0432     if (content())
0433         setSelected(!isSelected() && matching());
0434 
0435     FOR_EACH_CHILD(child)
0436     {
0437         child->invertSelectionRecursively();
0438     }
0439 }
0440 
0441 void Note::unselectAllBut(Note *toSelect)
0442 {
0443     if (this == toSelect)
0444         setSelectedRecursively(true);
0445     else {
0446         setSelected(false);
0447 
0448         Note *child = firstChild();
0449         bool first = true;
0450         while (child) {
0451             if ((showSubNotes() || first) && child->matching())
0452                 child->unselectAllBut(toSelect);
0453             else
0454                 child->setSelectedRecursively(false);
0455             child = child->next();
0456             first = false;
0457         }
0458     }
0459 }
0460 
0461 void Note::invertSelectionOf(Note *toSelect)
0462 {
0463     if (this == toSelect)
0464         setSelectedRecursively(!isSelected());
0465     else {
0466         Note *child = firstChild();
0467         bool first = true;
0468         while (child) {
0469             if ((showSubNotes() || first) && child->matching())
0470                 child->invertSelectionOf(toSelect);
0471             child = child->next();
0472             first = false;
0473         }
0474     }
0475 }
0476 
0477 Note *Note::theSelectedNote()
0478 {
0479     if (!isGroup() && isSelected())
0480         return this;
0481 
0482     Note *selectedOne;
0483     Note *child = firstChild();
0484     while (child) {
0485         selectedOne = child->theSelectedNote();
0486         if (selectedOne)
0487             return selectedOne;
0488         child = child->next();
0489     }
0490 
0491     return nullptr;
0492 }
0493 
0494 NoteSelection *Note::selectedNotes()
0495 {
0496     if (content()) {
0497         if (isSelected())
0498             return new NoteSelection(this);
0499         else
0500             return nullptr;
0501     }
0502 
0503     NoteSelection *selection = new NoteSelection(this);
0504 
0505     FOR_EACH_CHILD(child)
0506     {
0507         selection->append(child->selectedNotes());
0508     }
0509 
0510     if (selection->firstChild) {
0511         if (selection->firstChild->next)
0512             return selection;
0513         else {
0514             // If 'selection' is a group with only one content, return directly that content:
0515             NoteSelection *reducedSelection = selection->firstChild;
0516             //          delete selection; // TODO: Cut all connections of 'selection' before deleting it!
0517             for (NoteSelection *node = reducedSelection; node; node = node->next)
0518                 node->parent = nullptr;
0519             return reducedSelection;
0520         }
0521     } else {
0522         delete selection;
0523         return nullptr;
0524     }
0525 }
0526 
0527 bool Note::isAfter(Note *note)
0528 {
0529     if (this == nullptr || note == nullptr)
0530         return true;
0531 
0532     Note *next = this;
0533     while (next) {
0534         if (next == note)
0535             return false;
0536         next = next->nextInStack();
0537     }
0538     return true;
0539 }
0540 
0541 bool Note::containsNote(Note *note)
0542 {
0543     //  if (this == note)
0544     //      return true;
0545 
0546     while (note)
0547         if (note == this)
0548             return true;
0549         else
0550             note = note->parentNote();
0551 
0552     //  FOR_EACH_CHILD (child)
0553     //      if (child->containsNote(note))
0554     //          return true;
0555     return false;
0556 }
0557 
0558 Note *Note::firstRealChild()
0559 {
0560     Note *child = m_firstChild;
0561     while (child) {
0562         if (!child->isGroup() /*&& child->matching()*/)
0563             return child;
0564         child = child->firstChild();
0565     }
0566     // Empty group:
0567     return nullptr;
0568 }
0569 
0570 Note *Note::lastRealChild()
0571 {
0572     Note *child = lastChild();
0573     while (child) {
0574         if (child->content())
0575             return child;
0576         Note *possibleChild = child->lastRealChild();
0577         if (possibleChild && possibleChild->content())
0578             return possibleChild;
0579         child = child->prev();
0580     }
0581     return nullptr;
0582 }
0583 
0584 Note *Note::lastChild()
0585 {
0586     Note *child = m_firstChild;
0587     while (child && child->next())
0588         child = child->next();
0589 
0590     return child;
0591 }
0592 
0593 Note *Note::lastSibling()
0594 {
0595     Note *last = this;
0596     while (last && last->next())
0597         last = last->next();
0598 
0599     return last;
0600 }
0601 
0602 qreal Note::yExpander()
0603 {
0604     Note *child = firstRealChild();
0605     if (child && !child->isShown())
0606         child = child->nextShownInStack(); // FIXME: Restrict scope to 'this'
0607 
0608     if (child)
0609         return (child->boundingRect().height() - EXPANDER_HEIGHT) / 2;
0610     else // Groups always have at least 2 notes, except for columns which can have no child (but should exists anyway):
0611         return 0;
0612 }
0613 
0614 bool Note::isFree() const
0615 {
0616     return parentNote() == nullptr && basket() && basket()->isFreeLayout();
0617 }
0618 
0619 bool Note::isColumn() const
0620 {
0621     return parentNote() == nullptr && basket() && basket()->isColumnsLayout();
0622 }
0623 
0624 bool Note::hasResizer() const
0625 {
0626     // "isFree" || "isColumn but not the last"
0627     return parentNote() == nullptr && ((basket() && basket()->isFreeLayout()) || d->next != nullptr);
0628 }
0629 
0630 qreal Note::resizerHeight() const
0631 {
0632     return (isColumn() ? basket()->sceneRect().height() : d->height);
0633 }
0634 
0635 void Note::setHoveredZone(Zone zone) // TODO: Remove setHovered(bool) and assume it is hovered if zone != None !!!!!!!
0636 {
0637     if (m_hoveredZone != zone) {
0638         if (content())
0639             content()->setHoveredZone(m_hoveredZone, zone);
0640         m_hoveredZone = zone;
0641         unbufferize();
0642     }
0643 }
0644 
0645 Note::Zone Note::zoneAt(const QPointF &pos, bool toAdd)
0646 {
0647     // Keep the resizer highlighted when resizong, even if the cursor is over another note:
0648     if (basket()->resizingNote() == this)
0649         return Resizer;
0650 
0651     // 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:
0652     if (toAdd && isColumn() && hasResizer()) {
0653         qreal right = rightLimit() - x();
0654         if ((pos.x() >= right) && (pos.x() < right + RESIZER_WIDTH) && (pos.y() >= 0) && (pos.y() < resizerHeight())) // Code copied from below
0655             return BottomColumn;
0656     }
0657 
0658     // Below a column:
0659     if (isColumn()) {
0660         if (pos.y() >= height() && pos.x() < rightLimit() - x())
0661             return BottomColumn;
0662     }
0663 
0664     // If toAdd, return only TopInsert, TopGroup, BottomInsert or BottomGroup
0665     // (by spanning those areas in 4 equal rectangles in the note):
0666     if (toAdd) {
0667         if (!isFree() && !Settings::groupOnInsertionLine()) {
0668             if (pos.y() < height() / 2)
0669                 return TopInsert;
0670             else
0671                 return BottomInsert;
0672         }
0673         if (isColumn() && pos.y() >= height())
0674             return BottomGroup;
0675         if (pos.y() < height() / 2)
0676             if (pos.x() < width() / 2 && !isFree())
0677                 return TopInsert;
0678             else
0679                 return TopGroup;
0680         else if (pos.x() < width() / 2 && !isFree())
0681             return BottomInsert;
0682         else
0683             return BottomGroup;
0684     }
0685 
0686     // If in the resizer:
0687     if (hasResizer()) {
0688         qreal right = rightLimit() - x();
0689         if ((pos.x() >= right) && (pos.x() < right + RESIZER_WIDTH) && (pos.y() >= 0) && (pos.y() < resizerHeight()))
0690             return Resizer;
0691     }
0692 
0693     // If isGroup, return only Group, GroupExpander, TopInsert or BottomInsert:
0694     if (isGroup()) {
0695         if (pos.y() < INSERTION_HEIGHT) {
0696             if (isFree())
0697                 return TopGroup;
0698             else
0699                 return TopInsert;
0700         }
0701         if (pos.y() >= height() - INSERTION_HEIGHT) {
0702             if (isFree())
0703                 return BottomGroup;
0704             else
0705                 return BottomInsert;
0706         }
0707         if (pos.x() >= NOTE_MARGIN && pos.x() < NOTE_MARGIN + EXPANDER_WIDTH) {
0708             qreal yExp = yExpander();
0709             if (pos.y() >= yExp && pos.y() < yExp + EXPANDER_HEIGHT)
0710                 return GroupExpander;
0711         }
0712         if (pos.x() < width())
0713             return Group;
0714         else
0715             return Note::None;
0716     }
0717 
0718     // Else, it's a normal note:
0719 
0720     if (pos.x() < HANDLE_WIDTH)
0721         return Handle;
0722 
0723     if (pos.y() < INSERTION_HEIGHT) {
0724         if ((!isFree() && !Settings::groupOnInsertionLine()) || (pos.x() < width() / 2 && !isFree()))
0725             return TopInsert;
0726         else
0727             return TopGroup;
0728     }
0729 
0730     if (pos.y() >= height() - INSERTION_HEIGHT) {
0731         if ((!isFree() && !Settings::groupOnInsertionLine()) || (pos.x() < width() / 2 && !isFree()))
0732             return BottomInsert;
0733         else
0734             return BottomGroup;
0735     }
0736 
0737     for (int i = 0; i < m_emblemsCount; i++) {
0738         if (pos.x() >= HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * i && pos.x() < HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * i + NOTE_MARGIN + EMBLEM_SIZE)
0739             return (Zone)(Emblem0 + i);
0740     }
0741 
0742     if (pos.x() < HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * m_emblemsCount + NOTE_MARGIN + TAG_ARROW_WIDTH + NOTE_MARGIN)
0743         return TagsArrow;
0744 
0745     if (!linkAt(pos).isEmpty())
0746         return Link;
0747 
0748     int customZone = content()->zoneAt(pos - QPointF(contentX(), NOTE_MARGIN));
0749     if (customZone)
0750         return (Note::Zone)customZone;
0751 
0752     return Content;
0753 }
0754 
0755 QString Note::linkAt(const QPointF &pos)
0756 {
0757     QString link = m_content->linkAt(pos - QPointF(contentX(), NOTE_MARGIN));
0758     if (link.isEmpty() || link.startsWith(QLatin1String("basket://")))
0759         return link;
0760     else
0761         return NoteFactory::filteredURL(QUrl::fromUserInput(link)).toDisplayString(); // KURIFilter::self()->filteredURI(link);
0762 }
0763 
0764 qreal Note::contentX() const
0765 {
0766     return HANDLE_WIDTH + NOTE_MARGIN + (EMBLEM_SIZE + NOTE_MARGIN) * m_emblemsCount + TAG_ARROW_WIDTH + NOTE_MARGIN;
0767 }
0768 
0769 QRectF Note::zoneRect(Note::Zone zone, const QPointF &pos)
0770 {
0771     if (zone >= Emblem0)
0772         return QRect(HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * (zone - Emblem0), INSERTION_HEIGHT, NOTE_MARGIN + EMBLEM_SIZE, height() - 2 * INSERTION_HEIGHT);
0773 
0774     qreal yExp;
0775     qreal right;
0776     qreal xGroup = (isFree() ? (isGroup() ? 0 : GROUP_WIDTH) : width() / 2);
0777     QRectF rect;
0778     qreal insertSplit = (Settings::groupOnInsertionLine() ? 2 : 1);
0779     switch (zone) {
0780     case Note::Handle:
0781         return QRectF(0, 0, HANDLE_WIDTH, d->height);
0782     case Note::Group:
0783         yExp = yExpander();
0784         if (pos.y() < yExp) {
0785             return QRectF(0, INSERTION_HEIGHT, d->width, yExp - INSERTION_HEIGHT);
0786         }
0787         if (pos.y() > yExp + EXPANDER_HEIGHT) {
0788             return QRectF(0, yExp + EXPANDER_HEIGHT, d->width, d->height - yExp - EXPANDER_HEIGHT - INSERTION_HEIGHT);
0789         }
0790         if (pos.x() < NOTE_MARGIN) {
0791             return QRectF(0, 0, NOTE_MARGIN, d->height);
0792         } else {
0793             return QRectF(d->width - NOTE_MARGIN, 0, NOTE_MARGIN, d->height);
0794         }
0795     case Note::TagsArrow:
0796         return QRectF(HANDLE_WIDTH + (NOTE_MARGIN + EMBLEM_SIZE) * m_emblemsCount, INSERTION_HEIGHT, NOTE_MARGIN + TAG_ARROW_WIDTH + NOTE_MARGIN, d->height - 2 * INSERTION_HEIGHT);
0797     case Note::Custom0:
0798     case Note::Content:
0799         rect = content()->zoneRect(zone, pos - QPointF(contentX(), NOTE_MARGIN));
0800         rect.translate(contentX(), NOTE_MARGIN);
0801         return rect.intersected(QRectF(contentX(), INSERTION_HEIGHT, d->width - contentX(), d->height - 2 * INSERTION_HEIGHT)); // Only IN contentRect
0802     case Note::GroupExpander:
0803         return QRectF(NOTE_MARGIN, yExpander(), EXPANDER_WIDTH, EXPANDER_HEIGHT);
0804     case Note::Resizer:
0805         right = rightLimit();
0806         return QRectF(right - x(), 0, RESIZER_WIDTH, resizerHeight());
0807     case Note::Link:
0808     case Note::TopInsert:
0809         if (isGroup())
0810             return QRectF(0, 0, d->width, INSERTION_HEIGHT);
0811         else
0812             return QRectF(HANDLE_WIDTH, 0, d->width / insertSplit - HANDLE_WIDTH, INSERTION_HEIGHT);
0813     case Note::TopGroup:
0814         return QRectF(xGroup, 0, d->width - xGroup, INSERTION_HEIGHT);
0815     case Note::BottomInsert:
0816         if (isGroup())
0817             return QRectF(0, d->height - INSERTION_HEIGHT, d->width, INSERTION_HEIGHT);
0818         else
0819             return QRectF(HANDLE_WIDTH, d->height - INSERTION_HEIGHT, d->width / insertSplit - HANDLE_WIDTH, INSERTION_HEIGHT);
0820     case Note::BottomGroup:
0821         return QRectF(xGroup, d->height - INSERTION_HEIGHT, d->width - xGroup, INSERTION_HEIGHT);
0822     case Note::BottomColumn:
0823         return QRectF(0, d->height, rightLimit() - x(), basket()->sceneRect().height() - d->height);
0824     case Note::None:
0825         return QRectF(/*0, 0, -1, -1*/);
0826     default:
0827         return QRectF(/*0, 0, -1, -1*/);
0828     }
0829 }
0830 
0831 Qt::CursorShape Note::cursorFromZone(Zone zone) const
0832 {
0833     switch (zone) {
0834     case Note::Handle:
0835     case Note::Group:
0836         return Qt::SizeAllCursor;
0837         break;
0838     case Note::Resizer:
0839         if (isColumn()) {
0840             return Qt::SplitHCursor;
0841         } else {
0842             return Qt::SizeHorCursor;
0843         }
0844         break;
0845 
0846     case Note::Custom0:
0847         return m_content->cursorFromZone(zone);
0848         break;
0849 
0850     case Note::Link:
0851     case Note::TagsArrow:
0852     case Note::GroupExpander:
0853         return Qt::PointingHandCursor;
0854         break;
0855 
0856     case Note::Content:
0857         return Qt::IBeamCursor;
0858         break;
0859 
0860     case Note::TopInsert:
0861     case Note::TopGroup:
0862     case Note::BottomInsert:
0863     case Note::BottomGroup:
0864     case Note::BottomColumn:
0865         return Qt::CrossCursor;
0866         break;
0867     case Note::None:
0868         return Qt::ArrowCursor;
0869         break;
0870     default:
0871         State *state = stateForEmblemNumber(zone - Emblem0);
0872         if (state && state->parentTag()->states().count() > 1)
0873             return Qt::PointingHandCursor;
0874         else
0875             return Qt::ArrowCursor;
0876     }
0877 }
0878 
0879 qreal Note::height() const
0880 {
0881     return d->height;
0882 }
0883 
0884 void Note::setHeight(qreal height)
0885 {
0886     setInitialHeight(height);
0887 }
0888 
0889 void Note::setInitialHeight(qreal height)
0890 {
0891     prepareGeometryChange();
0892     d->height = height;
0893 }
0894 
0895 void Note::unsetWidth()
0896 {
0897     prepareGeometryChange();
0898 
0899     d->width = 0;
0900     unbufferize();
0901 
0902     FOR_EACH_CHILD(child)
0903     child->unsetWidth();
0904 }
0905 
0906 qreal Note::width() const
0907 {
0908     return (isGroup() ? (isColumn() ? 0 : GROUP_WIDTH) : d->width);
0909 }
0910 
0911 void Note::requestRelayout()
0912 {
0913     prepareGeometryChange();
0914 
0915     d->width = 0;
0916     unbufferize();
0917     basket()->relayoutNotes(); // TODO: A signal that will relayout ONCE and DELAYED if called several times
0918 }
0919 
0920 void Note::setWidth(qreal width) // TODO: inline ?
0921 {
0922     if (d->width != width)
0923         setWidthForceRelayout(width);
0924 }
0925 
0926 void Note::setWidthForceRelayout(qreal width)
0927 {
0928     prepareGeometryChange();
0929     unbufferize();
0930     d->width = (width < minWidth() ? minWidth() : width);
0931     int contentWidth = width - contentX() - NOTE_MARGIN;
0932     if (m_content) { ///// FIXME: is this OK?
0933         if (contentWidth < 1)
0934             contentWidth = 1;
0935         if (contentWidth < m_content->minWidth())
0936             contentWidth = m_content->minWidth();
0937         setHeight(m_content->setWidthAndGetHeight(contentWidth /* < 1 ? 1 : contentWidth*/) + 2 * NOTE_MARGIN);
0938         if (d->height < 3 * INSERTION_HEIGHT) // Assure a minimal size...
0939             setHeight(3 * INSERTION_HEIGHT);
0940     }
0941 }
0942 
0943 qreal Note::minWidth() const
0944 {
0945     if (m_content)
0946         return contentX() + m_content->minWidth() + NOTE_MARGIN;
0947     else
0948         return GROUP_WIDTH; ///// FIXME: is this OK?
0949 }
0950 
0951 qreal Note::minRight()
0952 {
0953     if (isGroup()) {
0954         qreal right = x() + width();
0955         Note *child = firstChild();
0956         bool first = true;
0957         while (child) {
0958             if ((showSubNotes() || first) && child->matching())
0959                 right = qMax(right, child->minRight());
0960             child = child->next();
0961             first = false;
0962         }
0963         if (isColumn()) {
0964             qreal minColumnRight = x() + 2 * HANDLE_WIDTH;
0965             if (right < minColumnRight)
0966                 return minColumnRight;
0967         }
0968         return right;
0969     } else
0970         return x() + minWidth();
0971 }
0972 
0973 bool Note::toggleFolded()
0974 {
0975     // Close the editor if it was editing a note that we are about to hide after collapsing:
0976     if (!m_isFolded && basket() && basket()->isDuringEdit()) {
0977         if (containsNote(basket()->editedNote()) && firstRealChild() != basket()->editedNote())
0978             basket()->closeEditor();
0979     }
0980 
0981     // Important to close the editor FIRST, because else, the last edited note would not show during folding animation (don't ask me why ;-) ):
0982     m_isFolded = !m_isFolded;
0983 
0984     unbufferize();
0985 
0986     return true;
0987 }
0988 
0989 Note *Note::noteAt(QPointF pos)
0990 {
0991     if (matching() && hasResizer()) {
0992         int right = rightLimit();
0993         // TODO: This code is duplicated 3 times: !!!!
0994         if ((pos.x() >= right) && (pos.x() < right + RESIZER_WIDTH) && (pos.y() >= y()) && (pos.y() < y() + resizerHeight())) {
0995             if (!m_computedAreas)
0996                 recomputeAreas();
0997             for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
0998                 QRectF &rect = *it;
0999                 if (rect.contains(pos.x(), pos.y()))
1000                     return this;
1001             }
1002         }
1003     }
1004 
1005     if (isGroup()) {
1006         if ((pos.x() >= x()) && (pos.x() < x() + width()) && (pos.y() >= y()) && (pos.y() < y() + d->height)) {
1007             if (!m_computedAreas)
1008                 recomputeAreas();
1009             for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
1010                 QRectF &rect = *it;
1011                 if (rect.contains(pos.x(), pos.y()))
1012                     return this;
1013             }
1014             return nullptr;
1015         }
1016         Note *child = firstChild();
1017         Note *found;
1018         bool first = true;
1019         while (child) {
1020             if ((showSubNotes() || first) && child->matching()) {
1021                 found = child->noteAt(pos);
1022                 if (found)
1023                     return found;
1024             }
1025             child = child->next();
1026             first = false;
1027         }
1028     } else if (matching() && pos.y() >= y() && pos.y() < y() + d->height && pos.x() >= x() && pos.x() < x() + d->width) {
1029         if (!m_computedAreas)
1030             recomputeAreas();
1031         for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
1032             QRectF &rect = *it;
1033             if (rect.contains(pos.x(), pos.y()))
1034                 return this;
1035         }
1036         return nullptr;
1037     }
1038 
1039     return nullptr;
1040 }
1041 
1042 QRectF Note::boundingRect() const
1043 {
1044     if (hasResizer()) {
1045         return QRectF(0, 0, rightLimit() - x() + RESIZER_WIDTH, resizerHeight());
1046     }
1047     return QRectF(0, 0, width(), height());
1048 }
1049 
1050 QRectF Note::resizerRect()
1051 {
1052     return QRectF(rightLimit(), y(), RESIZER_WIDTH, resizerHeight());
1053 }
1054 
1055 bool Note::showSubNotes()
1056 {
1057     return !m_isFolded || basket()->isFiltering();
1058 }
1059 
1060 void Note::relayoutAt(qreal ax, qreal ay)
1061 {
1062     if (!matching())
1063         return;
1064 
1065     m_computedAreas = false;
1066     m_areas.clear();
1067 
1068     // Don't relayout free notes one under the other, because by definition they are freely positioned!
1069     if (isFree()) {
1070         ax = x();
1071         ay = y();
1072         // If it's a column, it always have the same "fixed" position (no animation):
1073     } else if (isColumn()) {
1074         ax = (prev() ? prev()->rightLimit() + RESIZER_WIDTH : 0);
1075         ay = 0;
1076         setX(ax);
1077         setY(ay);
1078         // But relayout others vertically if they are inside such primary groups or if it is a "normal" basket:
1079     } else {
1080         setX(ax);
1081         setY(ay);
1082     }
1083 
1084     // Then, relayout sub-notes (only the first, if the group is folded) and so, assign an height to the group:
1085     if (isGroup()) {
1086         qreal h = 0;
1087         Note *child = firstChild();
1088         bool first = true;
1089         while (child) {
1090             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
1091                 child->relayoutAt(ax + width(), ay + h);
1092                 h += child->height();
1093                 if (!child->isVisible())
1094                     child->show();
1095             } else {                                   // In case the user collapse a group, then move it and then expand it:
1096                 child->setXRecursively(x() + width()); //  notes SHOULD have a good X coordinate, and not the old one!
1097                 if (child->isVisible())
1098                     child->hideRecursively();
1099             }
1100             // For future animation when re-match, but on bottom of already matched notes!
1101             // Find parent primary note and set the Y to THAT y:
1102             // if (!child->matching())
1103             //    child->setY(parentPrimaryNote()->y());
1104             child = child->next();
1105             first = false;
1106         }
1107         if (height() != h || d->height != h) {
1108             unbufferize();
1109             /*if (animate)
1110                 addAnimation(0, 0, h - height());
1111             else {*/
1112             setHeight(h);
1113             unbufferize();
1114             //}
1115         }
1116     } else {
1117         // If rightLimit is exceeded, set the top-level right limit!!!
1118         // and NEED RELAYOUT
1119         setWidth(finalRightLimit() - x());
1120     }
1121 
1122     // Set the basket area limits (but not for child notes: no need, because they will look for their parent note):
1123     if (!parentNote()) {
1124         if (basket()->tmpWidth < finalRightLimit() + (hasResizer() ? RESIZER_WIDTH : 0))
1125             basket()->tmpWidth = finalRightLimit() + (hasResizer() ? RESIZER_WIDTH : 0);
1126         if (basket()->tmpHeight < y() + height())
1127             basket()->tmpHeight = y() + height();
1128         // However, if the note exceed the allowed size, let it! :
1129     } else if (!isGroup()) {
1130         if (basket()->tmpWidth < x() + width() + (hasResizer() ? RESIZER_WIDTH : 0))
1131             basket()->tmpWidth = x() + width() + (hasResizer() ? RESIZER_WIDTH : 0);
1132         if (basket()->tmpHeight < y() + height())
1133             basket()->tmpHeight = y() + height();
1134     }
1135 }
1136 
1137 void Note::setXRecursively(qreal x)
1138 {
1139     setX(x);
1140 
1141     FOR_EACH_CHILD(child)
1142     child->setXRecursively(x + width());
1143 }
1144 
1145 void Note::setYRecursively(qreal y)
1146 {
1147     setY(y);
1148 
1149     FOR_EACH_CHILD(child)
1150     child->setYRecursively(y);
1151 }
1152 
1153 void Note::hideRecursively()
1154 {
1155     hide();
1156 
1157     FOR_EACH_CHILD(child)
1158     child->hideRecursively();
1159 }
1160 void Note::setGroupWidth(qreal width)
1161 {
1162     m_groupWidth = width;
1163 }
1164 
1165 qreal Note::groupWidth() const
1166 {
1167     if (hasResizer())
1168         return m_groupWidth;
1169     else
1170         return rightLimit() - x();
1171 }
1172 
1173 qreal Note::rightLimit() const
1174 {
1175     if (isColumn() && d->next == nullptr) // The last column
1176         return qMax((x() + minWidth()), (qreal)basket()->graphicsView()->viewport()->width());
1177     else if (parentNote())
1178         return parentNote()->rightLimit();
1179     else
1180         return x() + m_groupWidth;
1181 }
1182 
1183 qreal Note::finalRightLimit() const
1184 {
1185     if (isColumn() && d->next == nullptr) // The last column
1186         return qMax(x() + minWidth(), (qreal)basket()->graphicsView()->viewport()->width());
1187     else if (parentNote())
1188         return parentNote()->finalRightLimit();
1189     else
1190         return x() + m_groupWidth;
1191 }
1192 
1193 void Note::drawExpander(QPainter *painter, qreal x, qreal y, const QColor &background, bool expand, BasketScene *basket)
1194 {
1195     QStyleOption opt;
1196     opt.state = (expand ? QStyle::State_On : QStyle::State_Off);
1197     opt.rect = QRect(x, y, 9, 9);
1198     opt.palette = basket->palette();
1199     opt.palette.setColor(QPalette::Base, background);
1200 
1201     painter->fillRect(opt.rect, background);
1202 
1203     QStyle *style = basket->style();
1204     if (!expand) {
1205         style->drawPrimitive(QStyle::PE_IndicatorArrowDown, &opt, painter, basket->graphicsView()->viewport());
1206     } else {
1207         style->drawPrimitive(QStyle::PE_IndicatorArrowRight, &opt, painter, basket->graphicsView()->viewport());
1208     }
1209 }
1210 
1211 QColor expanderBackground(qreal height, qreal y, const QColor &foreground)
1212 {
1213     // We will divide height per two, subtract one and use that below a division bar:
1214     // To avoid division by zero error, height should be bigger than 3.
1215     // And to avoid y errors or if y is on the borders, we return the border color: the background color.
1216     if (height <= 3 || y <= 0 || y >= height - 1)
1217         return foreground;
1218 
1219     const QColor dark = foreground.darker(110);   // 1/1.1 of brightness
1220     const QColor light = foreground.lighter(150); // 50% brighter
1221 
1222     qreal h1, h2, s1, s2, v1, v2;
1223     int ng;
1224     if (y <= (height - 2) / 2) {
1225         light.getHsvF(&h1, &s1, &v1);
1226         dark.getHsvF(&h2, &s2, &v2);
1227         ng = (height - 2) / 2;
1228         y -= 1;
1229     } else {
1230         dark.getHsvF(&h1, &s1, &v1);
1231         foreground.getHsvF(&h2, &s2, &v2);
1232         ng = (height - 2) - (height - 2) / 2;
1233         y -= 1 + (height - 2) / 2;
1234     }
1235 
1236     return QColor::fromHsvF(h1 + ((h2 - h1) * y) / (ng - 1), s1 + ((s2 - s1) * y) / (ng - 1), v1 + ((v2 - v1) * y) / (ng - 1));
1237 }
1238 
1239 void Note::drawHandle(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QColor &background, const QColor &foreground, const QColor &lightForeground)
1240 {
1241     const QPen backgroundPen(background);
1242     const QPen foregroundPen(foreground);
1243 
1244     // Draw the surrounding rectangle:
1245     painter->setPen(foregroundPen);
1246     painter->drawLine(0, 0, width - 1, 0);
1247     painter->drawLine(0, 0, 0, height - 1);
1248     painter->drawLine(0, height - 1, width - 1, height - 1);
1249 
1250     // Draw the gradients:
1251     painter->fillRect(1 + x, 1 + y, width - 2, height - 2, lightForeground);
1252 
1253     // Round the top corner with background color:
1254     painter->setPen(backgroundPen);
1255     painter->drawLine(0, 0, 0, 3);
1256     painter->drawLine(1, 0, 3, 0);
1257     painter->drawPoint(1, 1);
1258     // Round the bottom corner with background color:
1259     painter->drawLine(0, height - 1, 0, height - 4);
1260     painter->drawLine(1, height - 1, 3, height - 1);
1261     painter->drawPoint(1, height - 2);
1262 
1263     // Surrounding line of the rounded top-left corner:
1264     painter->setPen(foregroundPen);
1265     painter->drawLine(1, 2, 1, 3);
1266     painter->drawLine(2, 1, 3, 1);
1267     // Surrounding line of the rounded bottom-left corner:
1268     painter->drawLine(1, height - 3, 1, height - 4);
1269     painter->drawLine(2, height - 2, 3, height - 2);
1270 
1271     // Anti-aliased rounded top corner (1/2):
1272     painter->setPen(lightForeground);
1273     painter->drawPoint(0, 3);
1274     painter->drawPoint(3, 0);
1275     painter->drawPoint(2, 2);
1276     // Anti-aliased rounded bottom corner:
1277     painter->drawPoint(0, height - 4);
1278     painter->drawPoint(3, height - 1);
1279     painter->drawPoint(2, height - 3);
1280 
1281     // Draw the grips:
1282     const qreal middleHeight = (height - 1) / 2;
1283     const qreal middleWidth = width / 2;
1284     painter->fillRect(middleWidth - 2, middleHeight, 2, 2, foreground);
1285     painter->fillRect(middleWidth + 2, middleHeight, 2, 2, foreground);
1286     painter->fillRect(middleWidth - 2, middleHeight - 4, 2, 2, foreground);
1287     painter->fillRect(middleWidth + 2, middleHeight - 4, 2, 2, foreground);
1288     painter->fillRect(middleWidth - 2, middleHeight + 4, 2, 2, foreground);
1289     painter->fillRect(middleWidth + 2, middleHeight + 4, 2, 2, foreground);
1290 }
1291 
1292 void Note::drawResizer(QPainter *painter, qreal x, qreal y, qreal width, qreal height, const QColor &background, const QColor &foreground, bool rounded)
1293 {
1294     const QPen backgroundPen(background);
1295     const QPen foregroundPen(foreground);
1296 
1297     const QColor lightForeground = Tools::mixColor(background, foreground, 2);
1298 
1299     // Draw the surrounding rectangle:
1300     painter->setPen(foregroundPen);
1301     painter->fillRect(0, 0, width, height, lightForeground);
1302     painter->drawLine(0, 0, width - 2, 0);
1303     painter->drawLine(0, height - 1, width - 2, height - 1);
1304     painter->drawLine(width - 1, 2, width - 1, height - 2);
1305     if (isColumn()) {
1306         painter->drawLine(0, 2, 0, height - 2);
1307     }
1308 
1309     if (rounded) {
1310         // Round the top corner with background color:
1311         painter->setPen(backgroundPen);
1312         painter->drawLine(width - 1, 0, width - 3, 0);
1313         painter->drawLine(width - 1, 1, width - 1, 2);
1314         painter->drawPoint(width - 2, 1);
1315         // Round the bottom corner with background color:
1316         painter->drawLine(width - 1, height - 1, width - 1, height - 4);
1317         painter->drawLine(width - 2, height - 1, width - 4, height - 1);
1318         painter->drawPoint(width - 2, height - 2);
1319         // Surrounding line of the rounded top-left corner:
1320         painter->setPen(foregroundPen);
1321         painter->drawLine(width - 2, 2, width - 2, 3);
1322         painter->drawLine(width - 3, 1, width - 4, 1);
1323         // Surrounding line of the rounded bottom-left corner:
1324         painter->drawLine(width - 2, height - 3, width - 2, height - 4);
1325         painter->drawLine(width - 3, height - 2, width - 4, height - 2);
1326 
1327         // Anti-aliased rounded top corner (1/2):
1328         painter->setPen(Tools::mixColor(foreground, background, 2));
1329         painter->drawPoint(width - 1, 3);
1330         painter->drawPoint(width - 4, 0);
1331         // Anti-aliased rounded bottom corner:
1332         painter->drawPoint(width - 1, height - 4);
1333         painter->drawPoint(width - 4, height - 1);
1334         // Anti-aliased rounded top corner (2/2):
1335         painter->setPen(foreground);
1336         painter->drawPoint(width - 3, 2);
1337         // Anti-aliased rounded bottom corner (2/2):
1338         painter->drawPoint(width - 3, height - 3);
1339     }
1340 
1341     // Draw the arrows:
1342     qreal xArrow = 2;
1343     qreal hMargin = 9;
1344     int countArrows = (height >= hMargin * 4 + 6 * 3 ? 3 : (height >= hMargin * 3 + 6 * 2 ? 2 : 1));
1345     for (int i = 0; i < countArrows; ++i) {
1346         qreal yArrow;
1347         switch (countArrows) {
1348         default:
1349         case 1:
1350             yArrow = (height - 6) / 2;
1351             break;
1352         case 2:
1353             yArrow = (i == 1 ? hMargin : height - hMargin - 6);
1354             break;
1355         case 3:
1356             yArrow = (i == 1 ? hMargin : (i == 2 ? (height - 6) / 2 : height - hMargin - 6));
1357             break;
1358         }
1359         /// Dark color:
1360         painter->setPen(foreground);
1361         // Left arrow:
1362         painter->drawLine(xArrow, yArrow + 2, xArrow + 2, yArrow);
1363         painter->drawLine(xArrow, yArrow + 2, xArrow + 2, yArrow + 4);
1364         // Right arrow:
1365         painter->drawLine(width - 1 - xArrow, yArrow + 2, width - 1 - xArrow - 2, yArrow);
1366         painter->drawLine(width - 1 - xArrow, yArrow + 2, width - 1 - xArrow - 2, yArrow + 4);
1367         /// Light color:
1368         painter->setPen(background);
1369         // Left arrow:
1370         painter->drawLine(xArrow, yArrow + 2 + 1, xArrow + 2, yArrow + 1);
1371         painter->drawLine(xArrow, yArrow + 2 + 1, xArrow + 2, yArrow + 4 + 1);
1372         // Right arrow:
1373         painter->drawLine(width - 1 - xArrow, yArrow + 2 + 1, width - 1 - xArrow - 2, yArrow + 1);
1374         painter->drawLine(width - 1 - xArrow, yArrow + 2 + 1, width - 1 - xArrow - 2, yArrow + 4 + 1);
1375     }
1376 }
1377 
1378 void Note::drawInactiveResizer(QPainter *painter, qreal x, qreal y, qreal height, const QColor &background, bool column)
1379 {
1380     // If background color is too dark, we compute a lighter color instead of a darker:
1381     QColor darkBgColor = (Tools::tooDark(background) ? background.lighter(120) : background.darker(105));
1382     painter->fillRect(x, y, RESIZER_WIDTH, height, Tools::mixColor(background, darkBgColor, 2));
1383 }
1384 
1385 QPalette Note::palette() const
1386 {
1387     return (m_basket ? m_basket->palette() : qApp->palette());
1388 }
1389 
1390 /* type: 1: topLeft
1391  *       2: bottomLeft
1392  *       3: topRight
1393  *       4: bottomRight
1394  *       5: fourCorners
1395  *       6: noteInsideAndOutsideCorners
1396  * (x,y) relate to the painter origin
1397  * (width,height) only used for 5:fourCorners type
1398  */
1399 void Note::drawRoundings(QPainter *painter, qreal x, qreal y, int type, qreal width, qreal height)
1400 {
1401     qreal right;
1402 
1403     switch (type) {
1404     case 1:
1405         x += this->x();
1406         y += this->y();
1407         basket()->blendBackground(*painter, QRectF(x, y, 4, 1), this->x(), this->y());
1408         basket()->blendBackground(*painter, QRectF(x, y + 1, 2, 1), this->x(), this->y());
1409         basket()->blendBackground(*painter, QRectF(x, y + 2, 1, 1), this->x(), this->y());
1410         basket()->blendBackground(*painter, QRectF(x, y + 3, 1, 1), this->x(), this->y());
1411         break;
1412     case 2:
1413         x += this->x();
1414         y += this->y();
1415         basket()->blendBackground(*painter, QRectF(x, y - 1, 1, 1), this->x(), this->y());
1416         basket()->blendBackground(*painter, QRectF(x, y, 1, 1), this->x(), this->y());
1417         basket()->blendBackground(*painter, QRectF(x, y + 1, 2, 1), this->x(), this->y());
1418         basket()->blendBackground(*painter, QRectF(x, y + 2, 4, 1), this->x(), this->y());
1419         break;
1420     case 3:
1421         right = rightLimit();
1422         x += right;
1423         y += this->y();
1424         basket()->blendBackground(*painter, QRectF(x - 1, y, 4, 1), right, this->y());
1425         basket()->blendBackground(*painter, QRectF(x + 1, y + 1, 2, 1), right, this->y());
1426         basket()->blendBackground(*painter, QRectF(x + 2, y + 2, 1, 1), right, this->y());
1427         basket()->blendBackground(*painter, QRectF(x + 2, y + 3, 1, 1), right, this->y());
1428         break;
1429     case 4:
1430         right = rightLimit();
1431         x += right;
1432         y += this->y();
1433         basket()->blendBackground(*painter, QRectF(x + 2, y - 1, 1, 1), right, this->y());
1434         basket()->blendBackground(*painter, QRectF(x + 2, y, 1, 1), right, this->y());
1435         basket()->blendBackground(*painter, QRectF(x + 1, y + 1, 2, 1), right, this->y());
1436         basket()->blendBackground(*painter, QRectF(x - 1, y + 2, 4, 1), right, this->y());
1437         break;
1438     case 5:
1439         // First make sure the corners are white (depending on the widget style):
1440         painter->setPen(basket()->backgroundColor());
1441         painter->drawPoint(x, y);
1442         painter->drawPoint(x + width - 1, y);
1443         painter->drawPoint(x + width - 1, y + height - 1);
1444         painter->drawPoint(x, y + height - 1);
1445         // And then blend corners:
1446         x += this->x();
1447         y += this->y();
1448         basket()->blendBackground(*painter, QRectF(x, y, 1, 1), this->x(), this->y());
1449         basket()->blendBackground(*painter, QRectF(x + width - 1, y, 1, 1), this->x(), this->y());
1450         basket()->blendBackground(*painter, QRectF(x + width - 1, y + height - 1, 1, 1), this->x(), this->y());
1451         basket()->blendBackground(*painter, QRectF(x, y + height - 1, 1, 1), this->x(), this->y());
1452         break;
1453     case 6:
1454         x += this->x();
1455         y += this->y();
1456         // if (!isSelected()) {
1457         // Inside left corners:
1458         basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH + 1, y + 1, 1, 1), this->x(), this->y());
1459         basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH, y + 2, 1, 1), this->x(), this->y());
1460         basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH + 1, y + height - 2, 1, 1), this->x(), this->y());
1461         basket()->blendBackground(*painter, QRectF(x + HANDLE_WIDTH, y + height - 3, 1, 1), this->x(), this->y());
1462         // Inside right corners:
1463         basket()->blendBackground(*painter, QRectF(x + width - 4, y + 1, 1, 1), this->x(), this->y());
1464         basket()->blendBackground(*painter, QRectF(x + width - 3, y + 2, 1, 1), this->x(), this->y());
1465         basket()->blendBackground(*painter, QRectF(x + width - 4, y + height - 2, 1, 1), this->x(), this->y());
1466         basket()->blendBackground(*painter, QRectF(x + width - 3, y + height - 3, 1, 1), this->x(), this->y());
1467         //}
1468         // Outside right corners:
1469         basket()->blendBackground(*painter, QRectF(x + width - 1, y, 1, 1), this->x(), this->y());
1470         basket()->blendBackground(*painter, QRectF(x + width - 1, y + height - 1, 1, 1), this->x(), this->y());
1471         break;
1472     }
1473 }
1474 
1475 /// Blank Spaces Drawing:
1476 
1477 void Note::setOnTop(bool onTop)
1478 {
1479     setZValue(onTop ? 100 : 0);
1480     m_onTop = onTop;
1481 
1482     Note *note = firstChild();
1483     while (note) {
1484         note->setOnTop(onTop);
1485         note = note->next();
1486     }
1487 }
1488 
1489 void substractRectOnAreas(const QRectF &rectToSubstract, QList<QRectF> &areas, bool andRemove)
1490 {
1491     for (int i = 0; i < areas.size();) {
1492         QRectF &rect = areas[i];
1493         // Split the rectangle if it intersects with rectToSubstract:
1494         if (rect.intersects(rectToSubstract)) {
1495             // Create the top rectangle:
1496             if (rectToSubstract.top() > rect.top()) {
1497                 areas.insert(i++, QRectF(rect.left(), rect.top(), rect.width(), rectToSubstract.top() - rect.top()));
1498                 rect.setTop(rectToSubstract.top());
1499             }
1500             // Create the bottom rectangle:
1501             if (rectToSubstract.bottom() < rect.bottom()) {
1502                 areas.insert(i++, QRectF(rect.left(), rectToSubstract.bottom(), rect.width(), rect.bottom() - rectToSubstract.bottom()));
1503                 rect.setBottom(rectToSubstract.bottom());
1504             }
1505             // Create the left rectangle:
1506             if (rectToSubstract.left() > rect.left()) {
1507                 areas.insert(i++, QRectF(rect.left(), rect.top(), rectToSubstract.left() - rect.left(), rect.height()));
1508                 rect.setLeft(rectToSubstract.left());
1509             }
1510             // Create the right rectangle:
1511             if (rectToSubstract.right() < rect.right()) {
1512                 areas.insert(i++, QRectF(rectToSubstract.right(), rect.top(), rect.right() - rectToSubstract.right(), rect.height()));
1513                 rect.setRight(rectToSubstract.right());
1514             }
1515             // Remove the rectangle if it's entirely contained:
1516             if (andRemove && rectToSubstract.contains(rect))
1517                 areas.removeAt(i);
1518             else
1519                 ++i;
1520         } else
1521             ++i;
1522     }
1523 }
1524 
1525 void Note::recomputeAreas()
1526 {
1527     // Initialize the areas with the note rectangle(s):
1528     m_areas.clear();
1529     m_areas.append(visibleRect());
1530     if (hasResizer())
1531         m_areas.append(resizerRect());
1532 
1533     // Cut the areas where other notes are on top of this note:
1534     Note *note = basket()->firstNote();
1535     bool noteIsAfterThis = false;
1536     while (note) {
1537         noteIsAfterThis = recomputeAreas(note, noteIsAfterThis);
1538         note = note->next();
1539     }
1540 }
1541 
1542 bool Note::recomputeAreas(Note *note, bool noteIsAfterThis)
1543 {
1544     if (note == this)
1545         noteIsAfterThis = true;
1546     // Only compute overlapping of notes AFTER this, or ON TOP this:
1547     // else if ( note->matching() && noteIsAfterThis && (!isOnTop() || (isOnTop() && note->isOnTop())) || (!isOnTop() && note->isOnTop()) ) {
1548     else if (note->matching() && noteIsAfterThis && ((!(isOnTop() || isEditing()) || ((isOnTop() || isEditing()) && (note->isOnTop() || note->isEditing()))) || (!(isOnTop() || isEditing()) && (note->isOnTop() || note->isEditing())))) {
1549         // if (!(isSelected() && !note->isSelected())) { // FIXME: FIXME: FIXME: FIXME: This last condition was added LATE, so we should look if it's ALWAYS good:
1550         substractRectOnAreas(note->visibleRect(), m_areas, true);
1551         if (note->hasResizer())
1552             substractRectOnAreas(note->resizerRect(), m_areas, true);
1553         //}
1554     }
1555 
1556     if (note->isGroup()) {
1557         Note *child = note->firstChild();
1558         bool first = true;
1559         while (child) {
1560             if ((note->showSubNotes() || first) && note->matching())
1561                 noteIsAfterThis = recomputeAreas(child, noteIsAfterThis);
1562             child = child->next();
1563             first = false;
1564         }
1565     }
1566 
1567     return noteIsAfterThis;
1568 }
1569 
1570 bool Note::isEditing()
1571 {
1572     return basket()->editedNote() == this;
1573 }
1574 
1575 /* Drawing policy:
1576  * ==============
1577  * - Draw the note on a pixmap and then draw the pixmap on screen is faster and
1578  *   flicker-free, rather than drawing directly on screen
1579  * - The next time the pixmap can be directly redrawn on screen without
1580  *   (relatively low, for small texts) time-consuming text-drawing
1581  * - To keep memory footprint low, we can destruct the bufferPixmap because
1582  *   redrawing it offscreen and applying it onscreen is nearly as fast as just
1583  *   drawing the pixmap onscreen
1584  * - But as drawing the pixmap offscreen is little time consuming we can keep
1585  *   last visible notes buffered and then the redraw of the entire window is
1586  *   INSTANTANEOUS
1587  * - We keep buffered note/group draws BUT NOT the resizer: such objects are
1588  *   small and fast to draw, so we don't complexify code for that
1589  */
1590 
1591 void Note::draw(QPainter *painter, const QRectF & /*clipRect*/)
1592 {
1593     if (!matching())
1594         return;
1595 
1596     /** Compute visible areas: */
1597     if (!m_computedAreas)
1598         recomputeAreas();
1599     if (m_areas.isEmpty())
1600         return;
1601 
1602     /** Directly draw pixmap on screen if it is already buffered: */
1603     if (isBufferized()) {
1604         drawBufferOnScreen(painter, m_bufferedPixmap);
1605         return;
1606     }
1607 
1608     /** If pixmap is Null (size 0), no point in painting: **/
1609     if (!width() || !height())
1610         return;
1611 
1612     /** Initialise buffer painter: */
1613     m_bufferedPixmap = QPixmap(width(), height());
1614     Q_ASSERT(!m_bufferedPixmap.isNull());
1615     QPainter painter2(&m_bufferedPixmap);
1616 
1617     /** Initialise colors: */
1618     QColor baseColor(basket()->backgroundColor());
1619     QColor highColor(palette().color(QPalette::Highlight));
1620     QColor midColor = Tools::mixColor(baseColor, highColor, 2);
1621 
1622     /** Initialise brushes and pens: */
1623     QBrush baseBrush(baseColor);
1624     QBrush highBrush(highColor);
1625     QPen basePen(baseColor);
1626     QPen highPen(highColor);
1627     QPen midPen(midColor);
1628 
1629     /** Figure out the state of the note: */
1630     bool hovered = m_hovered && m_hoveredZone != TopInsert && m_hoveredZone != BottomInsert && m_hoveredZone != Resizer;
1631 
1632     /** And then draw the group: */
1633     if (isGroup()) {
1634         // Draw background or handle:
1635         if (hovered) {
1636             drawHandle(&painter2, 0, 0, width(), height(), baseColor, highColor, midColor);
1637             drawRoundings(&painter2, 0, 0, /*type=*/1);
1638             drawRoundings(&painter2, 0, height() - 3, /*type=*/2);
1639         } else {
1640             painter2.fillRect(0, 0, width(), height(), baseBrush);
1641             basket()->blendBackground(painter2, boundingRect().translated(x(), y()), -1, -1, /*opaque=*/true);
1642         }
1643 
1644         // Draw expander:
1645         qreal yExp = yExpander();
1646         drawExpander(&painter2, NOTE_MARGIN, yExp, hovered ? midColor : baseColor, m_isFolded, basket());
1647         // Draw expander rounded edges:
1648         if (hovered) {
1649             QColor color1 = expanderBackground(height(), yExp, highColor);
1650             QColor color2 = expanderBackground(height(), yExp + EXPANDER_HEIGHT - 1, highColor);
1651             painter2.setPen(color1);
1652             painter2.drawPoint(NOTE_MARGIN, yExp);
1653             painter2.drawPoint(NOTE_MARGIN + 9 - 1, yExp);
1654             painter2.setPen(color2);
1655             painter2.drawPoint(NOTE_MARGIN, yExp + 9 - 1);
1656             painter2.drawPoint(NOTE_MARGIN + 9 - 1, yExp + 9 - 1);
1657         } else
1658             drawRoundings(&painter2, NOTE_MARGIN, yExp, /*type=*/5, 9, 9);
1659         // Draw on screen:
1660         painter2.end();
1661         drawBufferOnScreen(painter, m_bufferedPixmap);
1662 
1663         return;
1664     }
1665 
1666     /** Or draw the note: */
1667     // What are the background colors:
1668     QColor background;
1669     if (m_computedState.backgroundColor().isValid()) {
1670         background = m_computedState.backgroundColor();
1671     } else {
1672         background = basket()->backgroundColor();
1673     }
1674 
1675     if (!hovered && !isSelected()) {
1676         // Draw background:
1677         painter2.fillRect(0, 0, width(), height(), background);
1678         basket()->blendBackground(painter2, boundingRect().translated(x(), y()));
1679     } else {
1680         // Draw selection background:
1681         painter2.fillRect(0, 0, width(), height(), midColor);
1682         // Top/Bottom lines:
1683         painter2.setPen(highPen);
1684         painter2.drawLine(0, height() - 1, width(), height() - 1);
1685         painter2.drawLine(0, 0, width(), 0);
1686         // The handle:
1687         drawHandle(&painter2, 0, 0, HANDLE_WIDTH, height(), baseColor, highColor, midColor);
1688         drawRoundings(&painter2, 0, 0, /*type=*/1);
1689         drawRoundings(&painter2, 0, height() - 3, /*type=*/2);
1690         painter2.setPen(midColor);
1691         drawRoundings(&painter2, 0, 0, /*type=*/6, width(), height());
1692     }
1693 
1694     // Draw the Emblems:
1695     qreal yIcon = (height() - EMBLEM_SIZE) / 2;
1696     qreal xIcon = HANDLE_WIDTH + NOTE_MARGIN;
1697     for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) {
1698         if (!(*it)->emblem().isEmpty()) {
1699             QPixmap stateEmblem = KIconLoader::global()->loadIcon((*it)->emblem(), KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), nullptr, false);
1700 
1701             painter2.drawPixmap(xIcon, yIcon, stateEmblem);
1702             xIcon += NOTE_MARGIN + EMBLEM_SIZE;
1703         }
1704     }
1705 
1706     // Determine the colors (for the richText drawing) and the text color (for the tags arrow too):
1707     QPalette notePalette(basket()->palette());
1708     notePalette.setColor(QPalette::Text, (m_computedState.textColor().isValid() ? m_computedState.textColor() : basket()->textColor()));
1709     notePalette.setColor(QPalette::Background, background);
1710     if (isSelected())
1711         notePalette.setColor(QPalette::Text, palette().color(QPalette::HighlightedText));
1712 
1713     // Draw the Tags Arrow:
1714     if (hovered) {
1715         QColor textColor = notePalette.color(QPalette::Text);
1716         QColor light = Tools::mixColor(textColor, background);
1717         QColor mid = Tools::mixColor(textColor, light);
1718         painter2.setPen(light); // QPen(basket()->colorGroup().darker().lighter(150)));
1719         painter2.drawLine(xIcon, yIcon + 6, xIcon + 4, yIcon + 6);
1720         painter2.setPen(mid); // QPen(basket()->colorGroup().darker()));
1721         painter2.drawLine(xIcon + 1, yIcon + 7, xIcon + 3, yIcon + 7);
1722         painter2.setPen(textColor); // QPen(basket()->colorGroup().foreground()));
1723         painter2.drawPoint(xIcon + 2, yIcon + 8);
1724     } else if (m_haveInvisibleTags) {
1725         painter2.setPen(notePalette.color(QPalette::Text) /*QPen(basket()->colorGroup().foreground())*/);
1726         painter2.drawPoint(xIcon, yIcon + 7);
1727         painter2.drawPoint(xIcon + 2, yIcon + 7);
1728         painter2.drawPoint(xIcon + 4, yIcon + 7);
1729     }
1730 
1731     // Draw on screen:
1732     painter2.end();
1733     drawBufferOnScreen(painter, m_bufferedPixmap);
1734 }
1735 
1736 void Note::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
1737 {
1738     if (!m_basket->isLoaded())
1739         return;
1740 
1741     if (boundingRect().width() <= 0.1 || boundingRect().height() <= 0.1)
1742         return;
1743 
1744     draw(painter, boundingRect());
1745 
1746     if (hasResizer()) {
1747         qreal right = rightLimit() - x();
1748         QRectF resizerRect(0, 0, RESIZER_WIDTH, resizerHeight());
1749         // Prepare to draw the resizer:
1750         QPixmap pixmap(RESIZER_WIDTH, resizerHeight());
1751         QPainter painter2(&pixmap);
1752         // Draw gradient or resizer:
1753         if ((m_hovered && m_hoveredZone == Resizer) || ((m_hovered || isSelected()) && !isColumn())) {
1754             QColor baseColor(basket()->backgroundColor());
1755             QColor highColor(palette().color(QPalette::Highlight));
1756             drawResizer(&painter2, 0, 0, RESIZER_WIDTH, resizerHeight(), baseColor, highColor, /*rounded=*/!isColumn());
1757             if (!isColumn()) {
1758                 drawRoundings(&painter2, RESIZER_WIDTH - 3, 0, /*type=*/3);
1759                 drawRoundings(&painter2, RESIZER_WIDTH - 3, resizerHeight() - 3, /*type=*/4);
1760             }
1761         } else {
1762             drawInactiveResizer(&painter2, /*x=*/0, /*y=*/0, /*height=*/resizerHeight(), basket()->backgroundColor(), isColumn());
1763             resizerRect.translate(rightLimit(), y());
1764             basket()->blendBackground(painter2, resizerRect);
1765         }
1766         // Draw on screen:
1767         painter2.end();
1768         painter->drawPixmap(right, 0, pixmap);
1769     }
1770 }
1771 
1772 void Note::drawBufferOnScreen(QPainter *painter, const QPixmap &contentPixmap)
1773 {
1774     for (QList<QRectF>::iterator it = m_areas.begin(); it != m_areas.end(); ++it) {
1775         QRectF rect = (*it).translated(-x(), -y());
1776 
1777         if (rect.x() >= width()) // It's a rect of the resizer, don't draw it!
1778             continue;
1779 
1780         painter->drawPixmap(rect.x(), rect.y(), contentPixmap, rect.x(), rect.y(), rect.width(), rect.height());
1781     }
1782 }
1783 
1784 void Note::setContent(NoteContent *content)
1785 {
1786     m_content = content;
1787 }
1788 
1789 /*const */ State::List &Note::states() const
1790 {
1791     return (State::List &)m_states;
1792 }
1793 
1794 void Note::addState(State *state, bool orReplace)
1795 {
1796     if (!content())
1797         return;
1798 
1799     Tag *tag = state->parentTag();
1800     State::List::iterator itStates = m_states.begin();
1801     // Browse all tags, see if the note has it, increment itSates if yes, and then insert the state at this position...
1802     // For each existing tags:
1803     for (Tag::List::iterator it = Tag::all.begin(); it != Tag::all.end(); ++it) {
1804         // If the current tag isn't the one to assign or the current one on the note, go to the next tag:
1805         if (*it != tag && itStates != m_states.end() && *it != (*itStates)->parentTag())
1806             continue;
1807         // We found the tag to insert:
1808         if (*it == tag) {
1809             // And the note already have the tag:
1810             if (itStates != m_states.end() && *it == (*itStates)->parentTag()) {
1811                 // We replace the state if wanted:
1812                 if (orReplace) {
1813                     itStates = m_states.insert(itStates, state);
1814                     ++itStates;
1815                     m_states.erase(itStates);
1816                     recomputeStyle();
1817                 }
1818             } else {
1819                 m_states.insert(itStates, state);
1820                 recomputeStyle();
1821             }
1822             return;
1823         }
1824         // The note has this tag:
1825         if (itStates != m_states.end() && *it == (*itStates)->parentTag())
1826             ++itStates;
1827     }
1828 }
1829 
1830 QFont Note::font()
1831 {
1832     return m_computedState.font(basket()->QGraphicsScene::font());
1833 }
1834 
1835 QColor Note::backgroundColor()
1836 {
1837     if (m_computedState.backgroundColor().isValid())
1838         return m_computedState.backgroundColor();
1839     else
1840         return basket()->backgroundColor();
1841 }
1842 
1843 QColor Note::textColor()
1844 {
1845     if (m_computedState.textColor().isValid())
1846         return m_computedState.textColor();
1847     else
1848         return basket()->textColor();
1849 }
1850 
1851 void Note::recomputeStyle()
1852 {
1853     State::merge(m_states, &m_computedState, &m_emblemsCount, &m_haveInvisibleTags, basket()->backgroundColor());
1854     //  unsetWidth();
1855     if (content()) {
1856         if (content()->graphicsItem())
1857             content()->graphicsItem()->setPos(contentX(), NOTE_MARGIN);
1858         content()->fontChanged();
1859     }
1860     //  requestRelayout(); // TODO!
1861 }
1862 
1863 void Note::recomputeAllStyles()
1864 {
1865     if (content()) // We do the merge ourself, without calling recomputeStyle(), so there is no infinite recursion:
1866         // State::merge(m_states, &m_computedState, &m_emblemsCount, &m_haveInvisibleTags, basket()->backgroundColor());
1867         recomputeStyle();
1868     else if (isGroup())
1869         FOR_EACH_CHILD(child)
1870     child->recomputeAllStyles();
1871 }
1872 
1873 bool Note::removedStates(const QList<State *> &deletedStates)
1874 {
1875     bool modifiedBasket = false;
1876 
1877     if (!states().isEmpty()) {
1878         for (QList<State *>::const_iterator it = deletedStates.begin(); it != deletedStates.end(); ++it)
1879             if (hasState(*it)) {
1880                 removeState(*it);
1881                 modifiedBasket = true;
1882             }
1883     }
1884 
1885     FOR_EACH_CHILD(child)
1886     if (child->removedStates(deletedStates))
1887         modifiedBasket = true;
1888 
1889     return modifiedBasket;
1890 }
1891 
1892 void Note::addTag(Tag *tag)
1893 {
1894     addState(tag->states().first(), /*but do not replace:*/ false);
1895 }
1896 
1897 void Note::removeState(State *state)
1898 {
1899     for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
1900         if (*it == state) {
1901             m_states.erase(it);
1902             recomputeStyle();
1903             return;
1904         }
1905 }
1906 
1907 void Note::removeTag(Tag *tag)
1908 {
1909     for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
1910         if ((*it)->parentTag() == tag) {
1911             m_states.erase(it);
1912             recomputeStyle();
1913             return;
1914         }
1915 }
1916 
1917 void Note::removeAllTags()
1918 {
1919     m_states.clear();
1920     recomputeStyle();
1921 }
1922 
1923 void Note::addTagToSelectedNotes(Tag *tag)
1924 {
1925     if (content() && isSelected())
1926         addTag(tag);
1927 
1928     FOR_EACH_CHILD(child)
1929     child->addTagToSelectedNotes(tag);
1930 }
1931 
1932 void Note::removeTagFromSelectedNotes(Tag *tag)
1933 {
1934     if (content() && isSelected()) {
1935         if (hasTag(tag))
1936             setWidth(0);
1937         removeTag(tag);
1938     }
1939 
1940     FOR_EACH_CHILD(child)
1941     child->removeTagFromSelectedNotes(tag);
1942 }
1943 
1944 void Note::removeAllTagsFromSelectedNotes()
1945 {
1946     if (content() && isSelected()) {
1947         if (m_states.count() > 0)
1948             setWidth(0);
1949         removeAllTags();
1950     }
1951 
1952     FOR_EACH_CHILD(child)
1953     child->removeAllTagsFromSelectedNotes();
1954 }
1955 
1956 void Note::addStateToSelectedNotes(State *state, bool orReplace)
1957 {
1958     if (content() && isSelected())
1959         addState(state, orReplace);
1960 
1961     FOR_EACH_CHILD(child)
1962     child->addStateToSelectedNotes(state, orReplace); // TODO: BasketScene::addStateToSelectedNotes() does not have orReplace
1963 }
1964 
1965 void Note::changeStateOfSelectedNotes(State *state)
1966 {
1967     if (content() && isSelected() && hasTag(state->parentTag()))
1968         addState(state);
1969 
1970     FOR_EACH_CHILD(child)
1971     child->changeStateOfSelectedNotes(state);
1972 }
1973 
1974 bool Note::selectedNotesHaveTags()
1975 {
1976     if (content() && isSelected() && m_states.count() > 0)
1977         return true;
1978 
1979     FOR_EACH_CHILD(child)
1980     if (child->selectedNotesHaveTags())
1981         return true;
1982     return false;
1983 }
1984 
1985 bool Note::hasState(State *state)
1986 {
1987     for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
1988         if (*it == state)
1989             return true;
1990     return false;
1991 }
1992 
1993 bool Note::hasTag(Tag *tag)
1994 {
1995     for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
1996         if ((*it)->parentTag() == tag)
1997             return true;
1998     return false;
1999 }
2000 
2001 State *Note::stateOfTag(Tag *tag)
2002 {
2003     for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
2004         if ((*it)->parentTag() == tag)
2005             return *it;
2006     return nullptr;
2007 }
2008 
2009 bool Note::allowCrossReferences()
2010 {
2011     for (State::List::iterator it = m_states.begin(); it != m_states.end(); ++it)
2012         if (!(*it)->allowCrossReferences())
2013             return false;
2014     return true;
2015 }
2016 
2017 State *Note::stateForEmblemNumber(int number) const
2018 {
2019     int i = -1;
2020     for (State::List::const_iterator it = m_states.begin(); it != m_states.end(); ++it)
2021         if (!(*it)->emblem().isEmpty()) {
2022             ++i;
2023             if (i == number)
2024                 return *it;
2025         }
2026     return nullptr;
2027 }
2028 
2029 bool Note::stateForTagFromSelectedNotes(Tag *tag, State **state)
2030 {
2031     if (content() && isSelected()) {
2032         // What state is the tag on this note?
2033         State *stateOfTag = this->stateOfTag(tag);
2034         // This tag is not assigned to this note, the action will assign it, then:
2035         if (stateOfTag == nullptr)
2036             *state = nullptr;
2037         else {
2038             // Take the LOWEST state of all the selected notes:
2039             // Say the two selected notes have the state "Done" and "To Do".
2040             // The first note set *state to "Done".
2041             // When reaching the second note, we should recognize "To Do" is first in the tag states, then take it
2042             // Because pressing the tag shortcut key should first change state before removing the tag!
2043             if (*state == nullptr)
2044                 *state = stateOfTag;
2045             else {
2046                 bool stateIsFirst = true;
2047                 for (State *nextState = stateOfTag->nextState(); nextState; nextState = nextState->nextState(/*cycle=*/false))
2048                     if (nextState == *state)
2049                         stateIsFirst = false;
2050                 if (!stateIsFirst)
2051                     *state = stateOfTag;
2052             }
2053         }
2054         return true; // We encountered a selected note
2055     }
2056 
2057     bool encounteredSelectedNote = false;
2058     FOR_EACH_CHILD(child)
2059     {
2060         bool encountered = child->stateForTagFromSelectedNotes(tag, state);
2061         if (encountered && *state == nullptr)
2062             return true;
2063         if (encountered)
2064             encounteredSelectedNote = true;
2065     }
2066     return encounteredSelectedNote;
2067 }
2068 
2069 void Note::inheritTagsOf(Note *note)
2070 {
2071     if (!note || !content())
2072         return;
2073 
2074     for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it)
2075         if ((*it)->parentTag() && (*it)->parentTag()->inheritedBySiblings())
2076             addTag((*it)->parentTag());
2077 }
2078 
2079 void Note::unbufferizeAll()
2080 {
2081     unbufferize();
2082 
2083     if (isGroup()) {
2084         Note *child = firstChild();
2085         while (child) {
2086             child->unbufferizeAll();
2087             child = child->next();
2088         }
2089     }
2090 }
2091 
2092 QRectF Note::visibleRect()
2093 {
2094     QList<QRectF> areas;
2095     areas.append(QRectF(x(), y(), width(), height()));
2096 
2097     // 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:
2098     /*Note *parent = parentNote();
2099     while (parent) {
2100         if (parent->expandingOrCollapsing())
2101             substractRectOnAreas(QRect(x(), parent->y() - height(), width(), height()), areas, true);
2102         parent = parent->parentNote();
2103     }*/
2104 
2105     if (areas.count() > 0)
2106         return areas.first();
2107     else
2108         return QRectF();
2109 }
2110 
2111 void Note::recomputeBlankRects(QList<QRectF> &blankAreas)
2112 {
2113     if (!matching())
2114         return;
2115 
2116     // visibleRect() instead of rect() because if we are folding/expanding a smaller parent group, then some part is hidden!
2117     // But anyway, a resizer is always a primary note and is never hidden by a parent group, so no visibleResizerRect() method!
2118     substractRectOnAreas(visibleRect(), blankAreas, true);
2119     if (hasResizer())
2120         substractRectOnAreas(resizerRect(), blankAreas, true);
2121 
2122     if (isGroup()) {
2123         Note *child = firstChild();
2124         bool first = true;
2125         while (child) {
2126             if ((showSubNotes() || first) && child->matching())
2127                 child->recomputeBlankRects(blankAreas);
2128             child = child->next();
2129             first = false;
2130         }
2131     }
2132 }
2133 
2134 void Note::linkLookChanged()
2135 {
2136     if (isGroup()) {
2137         Note *child = firstChild();
2138         while (child) {
2139             child->linkLookChanged();
2140             child = child->next();
2141         }
2142     } else
2143         content()->linkLookChanged();
2144 }
2145 
2146 Note *Note::noteForFullPath(const QString &path)
2147 {
2148     if (content() && fullPath() == path)
2149         return this;
2150 
2151     Note *child = firstChild();
2152     Note *found;
2153     while (child) {
2154         found = child->noteForFullPath(path);
2155         if (found)
2156             return found;
2157         child = child->next();
2158     }
2159     return nullptr;
2160 }
2161 
2162 void Note::listUsedTags(QList<Tag *> &list)
2163 {
2164     for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it) {
2165         Tag *tag = (*it)->parentTag();
2166         if (!list.contains(tag))
2167             list.append(tag);
2168     }
2169 
2170     FOR_EACH_CHILD(child)
2171     child->listUsedTags(list);
2172 }
2173 
2174 void Note::usedStates(QList<State *> &states)
2175 {
2176     if (content())
2177         for (State::List::Iterator it = m_states.begin(); it != m_states.end(); ++it)
2178             if (!states.contains(*it))
2179                 states.append(*it);
2180 
2181     FOR_EACH_CHILD(child)
2182     child->usedStates(states);
2183 }
2184 
2185 Note *Note::nextInStack()
2186 {
2187     // First, search in the children:
2188     if (firstChild()) {
2189         if (firstChild()->content())
2190             return firstChild();
2191         else
2192             return firstChild()->nextInStack();
2193     }
2194     // Then, in the next:
2195     if (next()) {
2196         if (next()->content())
2197             return next();
2198         else
2199             return next()->nextInStack();
2200     }
2201     // And finally, in the parent:
2202     Note *note = parentNote();
2203     while (note)
2204         if (note->next())
2205             if (note->next()->content())
2206                 return note->next();
2207             else
2208                 return note->next()->nextInStack();
2209         else
2210             note = note->parentNote();
2211 
2212     // Not found:
2213     return nullptr;
2214 }
2215 
2216 Note *Note::prevInStack()
2217 {
2218     // First, search in the previous:
2219     if (prev() && prev()->content())
2220         return prev();
2221 
2222     // Else, it's a group, get the last item in that group:
2223     if (prev()) {
2224         Note *note = prev()->lastRealChild();
2225         if (note)
2226             return note;
2227     }
2228 
2229     if (parentNote())
2230         return parentNote()->prevInStack();
2231     else
2232         return nullptr;
2233 }
2234 
2235 Note *Note::nextShownInStack()
2236 {
2237     Note *next = nextInStack();
2238     while (next && !next->isShown())
2239         next = next->nextInStack();
2240     return next;
2241 }
2242 
2243 Note *Note::prevShownInStack()
2244 {
2245     Note *prev = prevInStack();
2246     while (prev && !prev->isShown())
2247         prev = prev->prevInStack();
2248     return prev;
2249 }
2250 
2251 bool Note::isShown()
2252 {
2253     // First, the easy one: groups are always shown:
2254     if (isGroup())
2255         return true;
2256 
2257     // And another easy part: non-matching notes are hidden:
2258     if (!matching())
2259         return false;
2260 
2261     if (basket()->isFiltering()) // And isMatching() because of the line above!
2262         return true;
2263 
2264     // So, here we go to the complex case: if the note is inside a collapsed group:
2265     Note *group = parentNote();
2266     Note *child = this;
2267     while (group) {
2268         if (group->isFolded() && group->firstChild() != child)
2269             return false;
2270         child = group;
2271         group = group->parentNote();
2272     }
2273     return true;
2274 }
2275 
2276 void Note::debug()
2277 {
2278     qDebug() << "Note@" << (quint64)this;
2279     if (!this) {
2280         qDebug();
2281         return;
2282     }
2283 
2284     if (isColumn())
2285         qDebug() << ": Column";
2286     else if (isGroup())
2287         qDebug() << ": Group";
2288     else
2289         qDebug() << ": Content[" << content()->lowerTypeName() << "]: " << toText(QString());
2290     qDebug();
2291 }
2292 
2293 Note *Note::firstSelected()
2294 {
2295     if (isSelected())
2296         return this;
2297 
2298     Note *first = nullptr;
2299     FOR_EACH_CHILD(child)
2300     {
2301         first = child->firstSelected();
2302         if (first)
2303             break;
2304     }
2305     return first;
2306 }
2307 
2308 Note *Note::lastSelected()
2309 {
2310     if (isSelected())
2311         return this;
2312 
2313     Note *last = nullptr, *tmp = nullptr;
2314     FOR_EACH_CHILD(child)
2315     {
2316         tmp = child->lastSelected();
2317         if (tmp)
2318             last = tmp;
2319     }
2320     return last;
2321 }
2322 
2323 Note *Note::selectedGroup()
2324 {
2325     if (isGroup() && allSelected() && count() == basket()->countSelecteds())
2326         return this;
2327 
2328     FOR_EACH_CHILD(child)
2329     {
2330         Note *selectedGroup = child->selectedGroup();
2331         if (selectedGroup)
2332             return selectedGroup;
2333     }
2334 
2335     return nullptr;
2336 }
2337 
2338 void Note::groupIn(Note *group)
2339 {
2340     if (this == group)
2341         return;
2342 
2343     if (allSelected() && !isColumn()) {
2344         basket()->unplugNote(this);
2345         basket()->insertNote(this, group, Note::BottomColumn);
2346     } else {
2347         Note *next;
2348         Note *child = firstChild();
2349         while (child) {
2350             next = child->next();
2351             child->groupIn(group);
2352             child = next;
2353         }
2354     }
2355 }
2356 
2357 bool Note::tryExpandParent()
2358 {
2359     Note *parent = parentNote();
2360     Note *child = this;
2361     while (parent) {
2362         if (parent->firstChild() != child)
2363             return false;
2364         if (parent->isColumn())
2365             return false;
2366         if (parent->isFolded()) {
2367             parent->toggleFolded();
2368             basket()->relayoutNotes();
2369             return true;
2370         }
2371         child = parent;
2372         parent = parent->parentNote();
2373     }
2374     return false;
2375 }
2376 
2377 bool Note::tryFoldParent() // TODO: withCtrl  ? withShift  ?
2378 {
2379     Note *parent = parentNote();
2380     Note *child = this;
2381     while (parent) {
2382         if (parent->firstChild() != child)
2383             return false;
2384         if (parent->isColumn())
2385             return false;
2386         if (!parent->isFolded()) {
2387             parent->toggleFolded();
2388             basket()->relayoutNotes();
2389             return true;
2390         }
2391         child = parent;
2392         parent = parent->parentNote();
2393     }
2394     return false;
2395 }
2396 
2397 qreal Note::distanceOnLeftRight(Note *note, int side)
2398 {
2399     if (side == BasketScene::RIGHT_SIDE) {
2400         // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key:
2401         if (x() > note->x() || finalRightLimit() > note->finalRightLimit())
2402             return -1;
2403     } else { /*LEFT_SIDE:*/
2404         // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key:
2405         if (x() < note->x() || finalRightLimit() < note->finalRightLimit())
2406             return -1;
2407     }
2408     if (x() == note->x() && finalRightLimit() == note->finalRightLimit())
2409         return -1;
2410 
2411     qreal thisCenterX = x() + (side == BasketScene::LEFT_SIDE ? width() : /*RIGHT_SIDE:*/ 0);
2412     qreal thisCenterY = y() + height() / 2;
2413     qreal noteCenterX = note->x() + note->width() / 2;
2414     qreal noteCenterY = note->y() + note->height() / 2;
2415 
2416     if (thisCenterY > note->bottom())
2417         noteCenterY = note->bottom();
2418     else if (thisCenterY < note->y())
2419         noteCenterY = note->y();
2420     else
2421         noteCenterY = thisCenterY;
2422 
2423     qreal angle = 0;
2424     if (noteCenterX - thisCenterX != 0)
2425         angle = 1000 * ((noteCenterY - thisCenterY) / (noteCenterX - thisCenterX));
2426     if (angle < 0)
2427         angle = -angle;
2428 
2429     return sqrt(pow(noteCenterX - thisCenterX, 2) + pow(noteCenterY - thisCenterY, 2)) + angle;
2430 }
2431 
2432 qreal Note::distanceOnTopBottom(Note *note, int side)
2433 {
2434     if (side == BasketScene::BOTTOM_SIDE) {
2435         // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key:
2436         if (y() > note->y() || bottom() > note->bottom())
2437             return -1;
2438     } else { /*TOP_SIDE:*/
2439         // If 'note' is on left of 'this': cannot switch from this to note by pressing Right key:
2440         if (y() < note->y() || bottom() < note->bottom())
2441             return -1;
2442     }
2443     if (y() == note->y() && bottom() == note->bottom())
2444         return -1;
2445 
2446     qreal thisCenterX = x() + width() / 2;
2447     qreal thisCenterY = y() + (side == BasketScene::TOP_SIDE ? height() : /*BOTTOM_SIDE:*/ 0);
2448     qreal noteCenterX = note->x() + note->width() / 2;
2449     qreal noteCenterY = note->y() + note->height() / 2;
2450 
2451     if (thisCenterX > note->finalRightLimit())
2452         noteCenterX = note->finalRightLimit();
2453     else if (thisCenterX < note->x())
2454         noteCenterX = note->x();
2455     else
2456         noteCenterX = thisCenterX;
2457 
2458     qreal angle = 0;
2459     if (noteCenterX - thisCenterX != 0)
2460         angle = 1000 * ((noteCenterY - thisCenterY) / (noteCenterX - thisCenterX));
2461     if (angle < 0)
2462         angle = -angle;
2463 
2464     return sqrt(pow(noteCenterX - thisCenterX, 2) + pow(noteCenterY - thisCenterY, 2)) + angle;
2465 }
2466 
2467 Note *Note::parentPrimaryNote()
2468 {
2469     Note *primary = this;
2470     while (primary->parentNote())
2471         primary = primary->parentNote();
2472     return primary;
2473 }
2474 
2475 void Note::deleteChilds()
2476 {
2477     Note *child = firstChild();
2478 
2479     while (child) {
2480         Note *tmp = child->next();
2481         delete child;
2482         child = tmp;
2483     }
2484 }
2485 
2486 bool Note::saveAgain()
2487 {
2488     bool result = true;
2489 
2490     if (content()) {
2491         if (!content()->saveToFile())
2492             result = false;
2493     }
2494     FOR_EACH_CHILD(child)
2495     {
2496         if (!child->saveAgain())
2497             result = false;
2498     }
2499     if (!result) {
2500         DEBUG_WIN << QString("Note::saveAgain returned false for %1:%2").arg((content() != nullptr) ? content()->typeName() : "null", toText(QString()));
2501     }
2502     return result;
2503 }
2504 
2505 bool Note::convertTexts()
2506 {
2507     bool convertedNotes = false;
2508 
2509     if (content() && content()->lowerTypeName() == "text") {
2510         QString text = ((TextContent *)content())->text();
2511         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>";
2512         FileStorage::saveToFile(fullPath(), html);
2513         setContent(new HtmlContent(this, content()->fileName()));
2514         convertedNotes = true;
2515     }
2516 
2517     FOR_EACH_CHILD(child)
2518     if (child->convertTexts())
2519         convertedNotes = true;
2520 
2521     return convertedNotes;
2522 }
2523 
2524 /* vim: set et sts=4 sw=4 ts=16 tw=78 : */
2525 /* kate: indent-width 4; replace-tabs on; */