File indexing completed on 2024-04-14 15:50:52

0001 /**
0002  * SPDX-FileCopyrightText: (C) 2003 by Sébastien Laoût <slaout@linux62.org>
0003  * SPDX-License-Identifier: GPL-2.0-or-later
0004  */
0005 
0006 #include "basketscene.h"
0007 
0008 #include <QApplication>
0009 #include <QFileDialog>
0010 #include <QFrame>
0011 #include <QGraphicsProxyWidget>
0012 #include <QGridLayout>
0013 #include <QInputDialog>
0014 #include <QLabel>
0015 #include <QLineEdit>
0016 #include <QLocale>
0017 #include <QMenu>
0018 #include <QPushButton>
0019 #include <QSaveFile>
0020 #include <QScrollBar>
0021 #include <QToolTip>
0022 #include <QtCore/QDateTime> // seed for rand()
0023 #include <QtCore/QDir>
0024 #include <QtCore/QFile>
0025 #include <QtCore/QFileInfo>
0026 #include <QtCore/QList>
0027 #include <QtCore/QPoint>
0028 #include <QtCore/QStringList>
0029 #include <QtCore/QTimeLine>
0030 #include <QtGui/QAbstractTextDocumentLayout>
0031 #include <QtGui/QClipboard>
0032 #include <QtGui/QContextMenuEvent>
0033 #include <QtGui/QCursor>
0034 #include <QtGui/QDrag>
0035 #include <QtGui/QDragEnterEvent>
0036 #include <QtGui/QDragLeaveEvent>
0037 #include <QtGui/QDragMoveEvent>
0038 #include <QtGui/QDropEvent>
0039 #include <QtGui/QFocusEvent>
0040 #include <QtGui/QKeyEvent>
0041 #include <QtGui/QMouseEvent>
0042 #include <QtGui/QPainter>
0043 #include <QtGui/QResizeEvent>
0044 #include <QtGui/QTextDocument>
0045 #include <QtGui/QWheelEvent>
0046 #include <QtXml/QDomDocument>
0047 
0048 #include <KTextEdit>
0049 #include <KAboutData>
0050 #include <KActionCollection>
0051 #include <KAuthorized>
0052 #include <KColorScheme> // for KStatefulBrush
0053 #include <KDirWatch>
0054 #include <KGlobalAccel>
0055 #include <KIconLoader>
0056 #include <KLocalizedString>
0057 #include <KMessageBox>
0058 #include <KOpenWithDialog>
0059 #include <KRun>
0060 #include <KService>
0061 #include <KDialogJobUiDelegate>
0062 
0063 #include <KIO/ApplicationLauncherJob>
0064 #include <KIO/CopyJob>
0065 
0066 #include <stdlib.h> // rand() function
0067 
0068 #include "backgroundmanager.h"
0069 #include "basketview.h"
0070 #include "common.h"
0071 #include "debugwindow.h"
0072 #include "decoratedbasket.h"
0073 #include "focusedwidgets.h"
0074 #include "gitwrapper.h"
0075 #include "global.h"
0076 #include "note.h"
0077 #include "notedrag.h"
0078 #include "noteedit.h"
0079 #include "notefactory.h"
0080 #include "noteselection.h"
0081 #include "settings.h"
0082 #include "tagsedit.h"
0083 #include "tools.h"
0084 #include "xmlwork.h"
0085 
0086 #include "config.h"
0087 
0088 #ifdef HAVE_LIBGPGME
0089 #include "kgpgme.h"
0090 #endif
0091 
0092 void debugZone(int zone)
0093 {
0094     QString s;
0095     switch (zone) {
0096     case Note::Handle:
0097         s = "Handle";
0098         break;
0099     case Note::Group:
0100         s = "Group";
0101         break;
0102     case Note::TagsArrow:
0103         s = "TagsArrow";
0104         break;
0105     case Note::Custom0:
0106         s = "Custom0";
0107         break;
0108     case Note::GroupExpander:
0109         s = "GroupExpander";
0110         break;
0111     case Note::Content:
0112         s = "Content";
0113         break;
0114     case Note::Link:
0115         s = "Link";
0116         break;
0117     case Note::TopInsert:
0118         s = "TopInsert";
0119         break;
0120     case Note::TopGroup:
0121         s = "TopGroup";
0122         break;
0123     case Note::BottomInsert:
0124         s = "BottomInsert";
0125         break;
0126     case Note::BottomGroup:
0127         s = "BottomGroup";
0128         break;
0129     case Note::BottomColumn:
0130         s = "BottomColumn";
0131         break;
0132     case Note::None:
0133         s = "None";
0134         break;
0135     default:
0136         if (zone == Note::Emblem0)
0137             s = "Emblem0";
0138         else
0139             s = "Emblem0+" + QString::number(zone - Note::Emblem0);
0140         break;
0141     }
0142     qDebug() << s;
0143 }
0144 
0145 #define FOR_EACH_NOTE(noteVar) for (Note *noteVar = firstNote(); noteVar; noteVar = noteVar->next())
0146 
0147 
0148 void BasketScene::appendNoteIn(Note *note, Note *in)
0149 {
0150     if (!note)
0151         // No note to append:
0152         return;
0153 
0154     if (in) {
0155         // The normal case:
0156         preparePlug(note);
0157 
0158         //      Note *last = note->lastSibling();
0159         Note *lastChild = in->lastChild();
0160 
0161         for (Note *n = note; n; n = n->next())
0162             n->setParentNote(in);
0163         note->setPrev(lastChild);
0164         //      last->setNext(0L);
0165 
0166         if (!in->firstChild())
0167             in->setFirstChild(note);
0168 
0169         if (lastChild)
0170             lastChild->setNext(note);
0171 
0172         if (m_loaded)
0173             signalCountsChanged();
0174     } else
0175         // Prepend it directly in the basket:
0176         appendNoteAfter(note, lastNote());
0177 }
0178 
0179 void BasketScene::appendNoteAfter(Note *note, Note *after)
0180 {
0181     if (!note)
0182         // No note to append:
0183         return;
0184 
0185     if (!after)
0186         // By default, insert after the last note:
0187         after = lastNote();
0188 
0189     if (m_loaded && after && !after->isFree() && !after->isColumn())
0190         for (Note *n = note; n; n = n->next())
0191             n->inheritTagsOf(after);
0192 
0193     //  if (!alreadyInBasket)
0194     preparePlug(note);
0195 
0196     Note *last = note->lastSibling();
0197     if (after) {
0198         // The normal case:
0199         for (Note *n = note; n; n = n->next())
0200             n->setParentNote(after->parentNote());
0201         note->setPrev(after);
0202         last->setNext(after->next());
0203         after->setNext(note);
0204         if (last->next())
0205             last->next()->setPrev(last);
0206     } else {
0207         // There is no note in the basket:
0208         for (Note *n = note; n; n = n->next())
0209             n->setParentNote(nullptr);
0210         m_firstNote = note;
0211         //      note->setPrev(0);
0212         //      last->setNext(0);
0213     }
0214 
0215     //  if (!alreadyInBasket)
0216     if (m_loaded)
0217         signalCountsChanged();
0218 }
0219 
0220 void BasketScene::appendNoteBefore(Note *note, Note *before)
0221 {
0222     if (!note)
0223         // No note to append:
0224         return;
0225 
0226     if (!before)
0227         // By default, insert before the first note:
0228         before = firstNote();
0229 
0230     if (m_loaded && before && !before->isFree() && !before->isColumn())
0231         for (Note *n = note; n; n = n->next())
0232             n->inheritTagsOf(before);
0233 
0234     preparePlug(note);
0235 
0236     Note *last = note->lastSibling();
0237     if (before) {
0238         // The normal case:
0239         for (Note *n = note; n; n = n->next())
0240             n->setParentNote(before->parentNote());
0241         note->setPrev(before->prev());
0242         last->setNext(before);
0243         before->setPrev(last);
0244         if (note->prev())
0245             note->prev()->setNext(note);
0246         else {
0247             if (note->parentNote())
0248                 note->parentNote()->setFirstChild(note);
0249             else
0250                 m_firstNote = note;
0251         }
0252     } else {
0253         // There is no note in the basket:
0254         for (Note *n = note; n; n = n->next())
0255             n->setParentNote(nullptr);
0256         m_firstNote = note;
0257         //      note->setPrev(0);
0258         //      last->setNext(0);
0259     }
0260 
0261     if (m_loaded)
0262         signalCountsChanged();
0263 }
0264 
0265 DecoratedBasket *BasketScene::decoration()
0266 {
0267     return (DecoratedBasket *)parent();
0268 }
0269 
0270 void BasketScene::preparePlug(Note *note)
0271 {
0272     // Select only the new notes, compute the new notes count and the new number of found notes:
0273     if (m_loaded)
0274         unselectAll();
0275     int count = 0;
0276     int founds = 0;
0277     Note *last = nullptr;
0278     for (Note *n = note; n; n = n->next()) {
0279         if (m_loaded)
0280             n->setSelectedRecursively(true); // Notes should have a parent basket (and they have, so that's OK).
0281         count += n->count();
0282         founds += n->newFilter(decoration()->filterData());
0283         last = n;
0284     }
0285     m_count += count;
0286     m_countFounds += founds;
0287 
0288     // Focus the last inserted note:
0289     if (m_loaded && last) {
0290         setFocusedNote(last);
0291         m_startOfShiftSelectionNote = (last->isGroup() ? last->lastRealChild() : last);
0292     }
0293 
0294     // If some notes don't match (are hidden), tell it to the user:
0295     if (m_loaded && founds < count) {
0296         if (count == 1) {
0297             Q_EMIT postMessage(i18n("The new note does not match the filter and is hidden."));
0298         } else if (founds == count - 1) {
0299             Q_EMIT postMessage(i18n("A new note does not match the filter and is hidden."));
0300         } else if (founds > 0) {
0301             Q_EMIT postMessage(i18n("Some new notes do not match the filter and are hidden."));
0302         } else {
0303             Q_EMIT postMessage(i18n("The new notes do not match the filter and are hidden."));
0304         }
0305     }
0306 }
0307 
0308 void BasketScene::unplugNote(Note *note)
0309 {
0310     // If there is nothing to do...
0311     if (!note)
0312         return;
0313 
0314     //  if (!willBeReplugged) {
0315     note->setSelectedRecursively(false); // To removeSelectedNote() and decrease the selectedsCount.
0316     m_count -= note->count();
0317     m_countFounds -= note->newFilter(decoration()->filterData());
0318     signalCountsChanged();
0319     //  }
0320 
0321     // If it was the first note, change the first note:
0322     if (m_firstNote == note)
0323         m_firstNote = note->next();
0324 
0325     // Change previous and next notes:
0326     if (note->prev())
0327         note->prev()->setNext(note->next());
0328     if (note->next())
0329         note->next()->setPrev(note->prev());
0330 
0331     if (note->parentNote()) {
0332         // If it was the first note of a group, change the first note of the group:
0333         if (note->parentNote()->firstChild() == note)
0334             note->parentNote()->setFirstChild(note->next());
0335 
0336         if (!note->parentNote()->isColumn()) {
0337             // Delete parent if now 0 notes inside parent group:
0338             if (!note->parentNote()->firstChild()) {
0339                 unplugNote(note->parentNote());
0340                 // a group could call this method for one or more of its children,
0341                 // each children could call this method for its parent's group...
0342                 // we have to do the deletion later otherwise we may corrupt the current process
0343                 m_notesToBeDeleted << note;
0344                 if (m_notesToBeDeleted.count() == 1) {
0345                     QTimer::singleShot(0, this, SLOT(doCleanUp()));
0346                 }
0347             }
0348             // Ungroup if still 1 note inside parent group:
0349             else if (!note->parentNote()->firstChild()->next()) {
0350                 ungroupNote(note->parentNote());
0351             }
0352         }
0353     }
0354 
0355     note->setParentNote(nullptr);
0356     note->setPrev(nullptr);
0357     note->setNext(nullptr);
0358 
0359     // Reste focus and hover note if necessary
0360     if (m_focusedNote == note)
0361         m_focusedNote = nullptr;
0362     if (m_hoveredNote == note)
0363         m_hoveredNote = nullptr;
0364 
0365     //  recomputeBlankRects(); // FIXME: called too much time. It's here because when dragging and moving a note to another basket and then go back to the original basket, the note is deleted but the note rect is not painter anymore.
0366 }
0367 
0368 void BasketScene::ungroupNote(Note *group)
0369 {
0370     Note *note = group->firstChild();
0371     Note *lastGroupedNote = group;
0372     Note *nextNote;
0373 
0374     // Move all notes after the group (not before, to avoid to change m_firstNote or group->m_firstChild):
0375     while (note) {
0376         nextNote = note->next();
0377 
0378         if (lastGroupedNote->next())
0379             lastGroupedNote->next()->setPrev(note);
0380         note->setNext(lastGroupedNote->next());
0381         lastGroupedNote->setNext(note);
0382         note->setParentNote(group->parentNote());
0383         note->setPrev(lastGroupedNote);
0384 
0385         note->setGroupWidth(group->groupWidth() - Note::GROUP_WIDTH);
0386         lastGroupedNote = note;
0387         note = nextNote;
0388     }
0389 
0390     // Unplug the group:
0391     group->setFirstChild(nullptr);
0392     unplugNote(group);
0393     // a group could call this method for one or more of its children,
0394     // each children could call this method for its parent's group...
0395     // we have to do the deletion later otherwise we may corrupt the current process
0396     m_notesToBeDeleted << group;
0397     if (m_notesToBeDeleted.count() == 1) {
0398         QTimer::singleShot(0, this, SLOT(doCleanUp()));
0399     }
0400 }
0401 
0402 void BasketScene::groupNoteBefore(Note *note, Note *with)
0403 {
0404     if (!note || !with)
0405         // No note to group or nowhere to group it:
0406         return;
0407 
0408     //  if (m_loaded && before && !with->isFree() && !with->isColumn())
0409     for (Note *n = note; n; n = n->next())
0410         n->inheritTagsOf(with);
0411 
0412     preparePlug(note);
0413 
0414     Note *last = note->lastSibling();
0415 
0416     Note *group = new Note(this);
0417     group->setPrev(with->prev());
0418     group->setNext(with->next());
0419     group->setX(with->x());
0420     group->setY(with->y());
0421     if (with->parentNote() && with->parentNote()->firstChild() == with)
0422         with->parentNote()->setFirstChild(group);
0423     else if (m_firstNote == with)
0424         m_firstNote = group;
0425     group->setParentNote(with->parentNote());
0426     group->setFirstChild(note);
0427     group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH);
0428 
0429     if (with->prev())
0430         with->prev()->setNext(group);
0431     if (with->next())
0432         with->next()->setPrev(group);
0433     with->setParentNote(group);
0434     with->setPrev(last);
0435     with->setNext(nullptr);
0436 
0437     for (Note *n = note; n; n = n->next())
0438         n->setParentNote(group);
0439     //  note->setPrev(0L);
0440     last->setNext(with);
0441 
0442     if (m_loaded)
0443         signalCountsChanged();
0444 }
0445 
0446 void BasketScene::groupNoteAfter(Note *note, Note *with)
0447 {
0448     if (!note || !with)
0449         // No note to group or nowhere to group it:
0450         return;
0451 
0452     //  if (m_loaded && before && !with->isFree() && !with->isColumn())
0453     for (Note *n = note; n; n = n->next())
0454         n->inheritTagsOf(with);
0455 
0456     preparePlug(note);
0457 
0458     //  Note *last = note->lastSibling();
0459 
0460     Note *group = new Note(this);
0461     group->setPrev(with->prev());
0462     group->setNext(with->next());
0463     group->setX(with->x());
0464     group->setY(with->y());
0465     if (with->parentNote() && with->parentNote()->firstChild() == with)
0466         with->parentNote()->setFirstChild(group);
0467     else if (m_firstNote == with)
0468         m_firstNote = group;
0469     group->setParentNote(with->parentNote());
0470     group->setFirstChild(with);
0471     group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH);
0472 
0473     if (with->prev())
0474         with->prev()->setNext(group);
0475     if (with->next())
0476         with->next()->setPrev(group);
0477     with->setParentNote(group);
0478     with->setPrev(nullptr);
0479     with->setNext(note);
0480 
0481     for (Note *n = note; n; n = n->next())
0482         n->setParentNote(group);
0483     note->setPrev(with);
0484     //  last->setNext(0L);
0485 
0486     if (m_loaded)
0487         signalCountsChanged();
0488 }
0489 
0490 void BasketScene::doCleanUp()
0491 {
0492     QSet<Note *>::iterator it = m_notesToBeDeleted.begin();
0493     while (it != m_notesToBeDeleted.end()) {
0494         delete *it;
0495         it = m_notesToBeDeleted.erase(it);
0496     }
0497 }
0498 
0499 void BasketScene::loadNotes(const QDomElement &notes, Note *parent)
0500 {
0501     Note *note;
0502     for (QDomNode n = notes.firstChild(); !n.isNull(); n = n.nextSibling()) {
0503         QDomElement e = n.toElement();
0504         if (e.isNull()) // Cannot handle that!
0505             continue;
0506         note = nullptr;
0507         // Load a Group:
0508         if (e.tagName() == "group") {
0509             note = new Note(this); // 1. Create the group...
0510             loadNotes(e, note);    // 3. ... And populate it with child notes.
0511             int noteCount = note->count();
0512             if (noteCount > 0 || (parent == nullptr && !isFreeLayout())) { // But don't remove columns!
0513                 appendNoteIn(note, parent);                                // 2. ... Insert it... FIXME: Initially, the if() the insertion was the step 2. Was it on purpose?
0514                 // The notes in the group are counted two times (it's why appendNoteIn() was called before loadNotes):
0515                 m_count -= noteCount; // TODO: Recompute note count every time noteCount() is emitted!
0516                 m_countFounds -= noteCount;
0517             }
0518         }
0519         // Load a Content-Based Note:
0520         if (e.tagName() == "note" || e.tagName() == "item") {                                                                          // Keep compatible with 0.6.0 Alpha 1
0521             note = new Note(this);                                                                                                     // Create the note...
0522             NoteFactory::loadNode(XMLWork::getElement(e, "content"), e.attribute("type"), note, /*lazyLoad=*/m_finishLoadOnFirstShow); // ... Populate it with content...
0523             if (e.attribute("type") == "text")
0524                 m_shouldConvertPlainTextNotes = true; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded!
0525             appendNoteIn(note, parent);               // ... And insert it.
0526             // Load dates:
0527             if (e.hasAttribute("added"))
0528                 note->setAddedDate(QDateTime::fromString(e.attribute("added"), Qt::ISODate));
0529             if (e.hasAttribute("lastModification"))
0530                 note->setLastModificationDate(QDateTime::fromString(e.attribute("lastModification"), Qt::ISODate));
0531         }
0532         // If we successfully loaded a note:
0533         if (note) {
0534             // Free Note Properties:
0535             if (note->isFree()) {
0536                 int x = e.attribute("x").toInt();
0537                 int y = e.attribute("y").toInt();
0538                 note->setX(x < 0 ? 0 : x);
0539                 note->setY(y < 0 ? 0 : y);
0540             }
0541             // Resizeable Note Properties:
0542             if (note->hasResizer() || note->isColumn())
0543                 note->setGroupWidth(e.attribute("width", "200").toInt());
0544             // Group Properties:
0545             if (note->isGroup() && !note->isColumn() && XMLWork::trueOrFalse(e.attribute("folded", "false")))
0546                 note->toggleFolded();
0547             // Tags:
0548             if (note->content()) {
0549                 QString tagsString = XMLWork::getElementText(e, QStringLiteral("tags"), QString());
0550                 QStringList tagsId = tagsString.split(';');
0551                 for (QStringList::iterator it = tagsId.begin(); it != tagsId.end(); ++it) {
0552                     State *state = Tag::stateById(*it);
0553                     if (state)
0554                         note->addState(state, /*orReplace=*/true);
0555                 }
0556             }
0557         }
0558         qApp->processEvents();
0559     }
0560 }
0561 
0562 void BasketScene::saveNotes(QXmlStreamWriter &stream, Note *parent)
0563 {
0564     Note *note = (parent ? parent->firstChild() : firstNote());
0565     while (note) {
0566         // Create Element:
0567         stream.writeStartElement(note->isGroup() ? "group" : "note");
0568         // Free Note Properties:
0569         if (note->isFree()) {
0570             stream.writeAttribute("x", QString::number(note->x()));
0571             stream.writeAttribute("y", QString::number(note->y()));
0572         }
0573         // Resizeable Note Properties:
0574         if (note->hasResizer())
0575             stream.writeAttribute("width", QString::number(note->groupWidth()));
0576         // Group Properties:
0577         if (note->isGroup() && !note->isColumn())
0578             stream.writeAttribute("folded", XMLWork::trueOrFalse(note->isFolded()));
0579         // Save Content:
0580         if (note->content()) {
0581             // Save Dates:
0582             stream.writeAttribute("added", note->addedDate().toString(Qt::ISODate));
0583             stream.writeAttribute("lastModification", note->lastModificationDate().toString(Qt::ISODate));
0584             // Save Content:
0585             stream.writeAttribute("type", note->content()->lowerTypeName());
0586             note->content()->saveToNode(stream);
0587             // Save Tags:
0588             if (note->states().count() > 0) {
0589                 QString tags;
0590                 for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) {
0591                     tags += (tags.isEmpty() ? QString() : QStringLiteral(";")) + (*it)->id();
0592                 }
0593                 stream.writeTextElement("tags", tags);
0594             }
0595         } else {
0596             // Save Child Notes:
0597             saveNotes(stream, note);
0598         }
0599         stream.writeEndElement();
0600         // Go to the Next One:
0601         note = note->next();
0602     }
0603 }
0604 
0605 void BasketScene::loadProperties(const QDomElement &properties)
0606 {
0607     // Compute Default Values for When Loading the Properties:
0608     QString defaultBackgroundColor = (backgroundColorSetting().isValid() ? backgroundColorSetting().name() : QString());
0609     QString defaultTextColor = (textColorSetting().isValid() ? textColorSetting().name() : QString());
0610 
0611     // Load the Properties:
0612     QString icon = XMLWork::getElementText(properties, "icon", this->icon());
0613     QString name = XMLWork::getElementText(properties, "name", basketName());
0614 
0615     QDomElement appearance = XMLWork::getElement(properties, "appearance");
0616     // In 0.6.0-Alpha versions, there was a typo error: "backround" instead of "background"
0617     QString backgroundImage = appearance.attribute("backgroundImage", appearance.attribute("backroundImage", backgroundImageName()));
0618     QString backgroundColorString = appearance.attribute("backgroundColor", appearance.attribute("backroundColor", defaultBackgroundColor));
0619     QString textColorString = appearance.attribute("textColor", defaultTextColor);
0620     QColor backgroundColor = (backgroundColorString.isEmpty() ? QColor() : QColor(backgroundColorString));
0621     QColor textColor = (textColorString.isEmpty() ? QColor() : QColor(textColorString));
0622 
0623     QDomElement disposition = XMLWork::getElement(properties, "disposition");
0624     bool free = XMLWork::trueOrFalse(disposition.attribute("free", XMLWork::trueOrFalse(isFreeLayout())));
0625     int columnCount = disposition.attribute("columnCount", QString::number(this->columnsCount())).toInt();
0626     bool mindMap = XMLWork::trueOrFalse(disposition.attribute("mindMap", XMLWork::trueOrFalse(isMindMap())));
0627 
0628     QDomElement shortcut = XMLWork::getElement(properties, "shortcut");
0629     QString actionStrings[] = {"show", "globalShow", "globalSwitch"};
0630     QKeySequence combination = QKeySequence(shortcut.attribute("combination", m_action->shortcut().toString()));
0631     QString actionString = shortcut.attribute("action");
0632     int action = shortcutAction();
0633     if (actionString == actionStrings[0])
0634         action = 0;
0635     if (actionString == actionStrings[1])
0636         action = 1;
0637     if (actionString == actionStrings[2])
0638         action = 2;
0639 
0640     QDomElement protection = XMLWork::getElement(properties, "protection");
0641     m_encryptionType = protection.attribute("type").toInt();
0642     m_encryptionKey = protection.attribute("key");
0643 
0644     // Apply the Properties:
0645     setDisposition((free ? (mindMap ? 2 : 1) : 0), columnCount);
0646     setShortcut(combination, action);
0647     setAppearance(icon, name, backgroundImage, backgroundColor, textColor); // Will emit propertiesChanged(this)
0648 }
0649 
0650 void BasketScene::saveProperties(QXmlStreamWriter &stream)
0651 {
0652     stream.writeStartElement("properties");
0653 
0654     stream.writeTextElement("name", basketName());
0655     stream.writeTextElement("icon", icon());
0656 
0657     stream.writeStartElement("appearance");
0658     stream.writeAttribute("backgroundColor", backgroundColorSetting().isValid() ? backgroundColorSetting().name() : QString());
0659     stream.writeAttribute("backgroundImage", backgroundImageName());
0660     stream.writeAttribute("textColor", textColorSetting().isValid() ? textColorSetting().name() : QString());
0661     stream.writeEndElement();
0662 
0663     stream.writeStartElement("disposition");
0664     stream.writeAttribute("columnCount", QString::number(columnsCount()));
0665     stream.writeAttribute("free", XMLWork::trueOrFalse(isFreeLayout()));
0666     stream.writeAttribute("mindMap", XMLWork::trueOrFalse(isMindMap()));
0667     stream.writeEndElement();
0668 
0669     stream.writeStartElement("shortcut");
0670     QString actionStrings[] = {"show", "globalShow", "globalSwitch"};
0671     stream.writeAttribute("action", actionStrings[shortcutAction()]);
0672     stream.writeAttribute("combination", m_action->shortcut().toString());
0673     stream.writeEndElement();
0674 
0675     stream.writeStartElement("protection");
0676     stream.writeAttribute("key", m_encryptionKey);
0677     stream.writeAttribute("type", QString::number(m_encryptionType));
0678     stream.writeEndElement();
0679 
0680     stream.writeEndElement();
0681 }
0682 
0683 void BasketScene::subscribeBackgroundImages()
0684 {
0685     if (!m_backgroundImageName.isEmpty()) {
0686         Global::backgroundManager->subscribe(m_backgroundImageName);
0687         Global::backgroundManager->subscribe(m_backgroundImageName, this->backgroundColor());
0688         Global::backgroundManager->subscribe(m_backgroundImageName, selectionRectInsideColor());
0689         m_backgroundPixmap = Global::backgroundManager->pixmap(m_backgroundImageName);
0690         m_opaqueBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, this->backgroundColor());
0691         m_selectedBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, selectionRectInsideColor());
0692         m_backgroundTiled = Global::backgroundManager->tiled(m_backgroundImageName);
0693     }
0694 }
0695 
0696 void BasketScene::unsubscribeBackgroundImages()
0697 {
0698     if (hasBackgroundImage()) {
0699         Global::backgroundManager->unsubscribe(m_backgroundImageName);
0700         Global::backgroundManager->unsubscribe(m_backgroundImageName, this->backgroundColor());
0701         Global::backgroundManager->unsubscribe(m_backgroundImageName, selectionRectInsideColor());
0702         m_backgroundPixmap = nullptr;
0703         m_opaqueBackgroundPixmap = nullptr;
0704         m_selectedBackgroundPixmap = nullptr;
0705     }
0706 }
0707 
0708 void BasketScene::setAppearance(const QString &icon, const QString &name, const QString &backgroundImage, const QColor &backgroundColor, const QColor &textColor)
0709 {
0710     unsubscribeBackgroundImages();
0711 
0712     m_basketName = name;
0713     m_backgroundImageName = backgroundImage;
0714     m_backgroundColorSetting = backgroundColor;
0715     m_textColorSetting = textColor;
0716 
0717     // Where is this shown?
0718     m_action->setText("BASKET SHORTCUT: " + name);
0719 
0720     // Basket should ALWAYS have an icon (the "basket" icon by default):
0721     QPixmap iconTest = KIconLoader::global()->loadIcon(icon, KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), nullptr, /*canReturnNull=*/true);
0722     if (!iconTest.isNull())
0723         m_icon = icon;
0724 
0725     // We don't request the background images if it's not loaded yet (to make the application startup fast).
0726     // When the basket is loading (because requested by the user: he/she want to access it)
0727     // it load the properties, subscribe to (and then load) the images, update the "Loading..." message with the image,
0728     // load all the notes and it's done!
0729     if (m_loadingLaunched)
0730         subscribeBackgroundImages();
0731 
0732     recomputeAllStyles();  // If a note have a tag with the same background color as the basket one, then display a "..."
0733     recomputeBlankRects(); // See the drawing of blank areas in BasketScene::drawContents()
0734     unbufferizeAll();
0735 
0736     if (isDuringEdit() && m_editor->graphicsWidget()) {
0737         QPalette palette;
0738         palette.setColor(m_editor->graphicsWidget()->widget()->backgroundRole(), m_editor->note()->backgroundColor());
0739         palette.setColor(m_editor->graphicsWidget()->widget()->foregroundRole(), m_editor->note()->textColor());
0740         m_editor->graphicsWidget()->setPalette(palette);
0741     }
0742 
0743     Q_EMIT propertiesChanged(this);
0744 }
0745 
0746 void BasketScene::setDisposition(int disposition, int columnCount)
0747 {
0748     static const int COLUMNS_LAYOUT = 0;
0749     static const int FREE_LAYOUT = 1;
0750     static const int MINDMAPS_LAYOUT = 2;
0751 
0752     int currentDisposition = (isFreeLayout() ? (isMindMap() ? MINDMAPS_LAYOUT : FREE_LAYOUT) : COLUMNS_LAYOUT);
0753 
0754     if (currentDisposition == COLUMNS_LAYOUT && disposition == COLUMNS_LAYOUT) {
0755         if (firstNote() && columnCount > m_columnsCount) {
0756             // Insert each new columns:
0757             for (int i = m_columnsCount; i < columnCount; ++i) {
0758                 Note *newColumn = new Note(this);
0759                 insertNote(newColumn, /*clicked=*/lastNote(), /*zone=*/Note::BottomInsert);
0760             }
0761         } else if (firstNote() && columnCount < m_columnsCount) {
0762             Note *column = firstNote();
0763             Note *cuttedNotes = nullptr;
0764             for (int i = 1; i <= m_columnsCount; ++i) {
0765                 Note *columnToRemove = column;
0766                 column = column->next();
0767                 if (i > columnCount) {
0768                     // Remove the columns that are too much:
0769                     unplugNote(columnToRemove);
0770                     // "Cut" the content in the columns to be deleted:
0771                     if (columnToRemove->firstChild()) {
0772                         for (Note *it = columnToRemove->firstChild(); it; it = it->next())
0773                             it->setParentNote(nullptr);
0774                         if (!cuttedNotes)
0775                             cuttedNotes = columnToRemove->firstChild();
0776                         else {
0777                             Note *lastCuttedNote = cuttedNotes;
0778                             while (lastCuttedNote->next())
0779                                 lastCuttedNote = lastCuttedNote->next();
0780                             lastCuttedNote->setNext(columnToRemove->firstChild());
0781                             columnToRemove->firstChild()->setPrev(lastCuttedNote);
0782                         }
0783                         columnToRemove->setFirstChild(nullptr);
0784                     }
0785                     delete columnToRemove;
0786                 }
0787             }
0788             // Paste the content in the last column:
0789             if (cuttedNotes)
0790                 insertNote(cuttedNotes, /*clicked=*/lastNote(), /*zone=*/Note::BottomColumn);
0791             unselectAll();
0792         }
0793         if (columnCount != m_columnsCount) {
0794             m_columnsCount = (columnCount <= 0 ? 1 : columnCount);
0795             equalizeColumnSizes(); // Will relayoutNotes()
0796         }
0797     } else if (currentDisposition == COLUMNS_LAYOUT && (disposition == FREE_LAYOUT || disposition == MINDMAPS_LAYOUT)) {
0798         Note *column = firstNote();
0799         m_columnsCount = 0; // Now, so relayoutNotes() will not relayout the free notes as if they were columns!
0800         while (column) {
0801             // Move all childs on the first level:
0802             Note *nextColumn = column->next();
0803             ungroupNote(column);
0804             column = nextColumn;
0805         }
0806         unselectAll();
0807         m_mindMap = (disposition == MINDMAPS_LAYOUT);
0808         relayoutNotes();
0809     } else if ((currentDisposition == FREE_LAYOUT || currentDisposition == MINDMAPS_LAYOUT) && disposition == COLUMNS_LAYOUT) {
0810         if (firstNote()) {
0811             // TODO: Reorder notes!
0812             // Remove all notes (but keep a reference to them, we're not crazy ;-) ):
0813             Note *notes = m_firstNote;
0814             m_firstNote = nullptr;
0815             m_count = 0;
0816             m_countFounds = 0;
0817             // Insert the number of columns that is needed:
0818             Note *lastInsertedColumn = nullptr;
0819             for (int i = 0; i < columnCount; ++i) {
0820                 Note *column = new Note(this);
0821                 if (lastInsertedColumn)
0822                     insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert);
0823                 else
0824                     m_firstNote = column;
0825                 lastInsertedColumn = column;
0826             }
0827             // Reinsert the old notes in the first column:
0828             insertNote(notes, /*clicked=*/firstNote(), /*zone=*/Note::BottomColumn);
0829             unselectAll();
0830         } else {
0831             // Insert the number of columns that is needed:
0832             Note *lastInsertedColumn = nullptr;
0833             for (int i = 0; i < columnCount; ++i) {
0834                 Note *column = new Note(this);
0835                 if (lastInsertedColumn)
0836                     insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert);
0837                 else
0838                     m_firstNote = column;
0839                 lastInsertedColumn = column;
0840             }
0841         }
0842         m_columnsCount = (columnCount <= 0 ? 1 : columnCount);
0843         equalizeColumnSizes(); // Will relayoutNotes()
0844     }
0845 }
0846 
0847 void BasketScene::equalizeColumnSizes()
0848 {
0849     if (!firstNote())
0850         return;
0851 
0852     // Necessary to know the available space;
0853     relayoutNotes();
0854 
0855     int availableSpace = m_view->viewport()->width();
0856     int columnWidth = (availableSpace - (columnsCount() - 1) * Note::GROUP_WIDTH) / columnsCount();
0857     int columnCount = columnsCount();
0858     Note *column = firstNote();
0859     while (column) {
0860         int minGroupWidth = column->minRight() - column->x();
0861         if (minGroupWidth > columnWidth) {
0862             availableSpace -= minGroupWidth;
0863             --columnCount;
0864         }
0865         column = column->next();
0866     }
0867     columnWidth = (availableSpace - (columnsCount() - 1) * Note::GROUP_WIDTH) / columnCount;
0868 
0869     column = firstNote();
0870     while (column) {
0871         int minGroupWidth = column->minRight() - column->x();
0872         if (minGroupWidth > columnWidth)
0873             column->setGroupWidth(minGroupWidth);
0874         else
0875             column->setGroupWidth(columnWidth);
0876         column = column->next();
0877     }
0878 
0879     relayoutNotes();
0880 }
0881 
0882 void BasketScene::enableActions()
0883 {
0884     Global::bnpView->enableActions();
0885     m_view->setFocusPolicy(isLocked() ? Qt::NoFocus : Qt::StrongFocus);
0886     if (isLocked())
0887         m_view->viewport()->setCursor(Qt::ArrowCursor); // When locking, the cursor stays the last form it was
0888 }
0889 
0890 bool BasketScene::save()
0891 {
0892     if (!m_loaded)
0893         return false;
0894 
0895     DEBUG_WIN << "Basket[" + folderName() + "]: Saving...";
0896 
0897     QString data;
0898     QXmlStreamWriter stream(&data);
0899     XMLWork::setupXmlStream(stream, "basket");
0900 
0901     // Create Properties Element and Populate It:
0902     saveProperties(stream);
0903 
0904     // Create Notes Element and Populate It:
0905     stream.writeStartElement("notes");
0906     saveNotes(stream, nullptr);
0907     stream.writeEndElement();
0908 
0909     stream.writeEndElement();
0910     stream.writeEndDocument();
0911 
0912     // Write to Disk:
0913     if (!FileStorage::saveToFile(fullPath() + ".basket", data, isEncrypted())) {
0914         DEBUG_WIN << "Basket[" + folderName() + "]: <font color=red>FAILED to save</font>!";
0915         return false;
0916     }
0917 
0918     Global::bnpView->setUnsavedStatus(false);
0919 
0920     m_commitdelay.start(10000); // delay is 10 seconds
0921 
0922     return true;
0923 }
0924 
0925 void BasketScene::commitEdit()
0926 {
0927     GitWrapper::commitBasket(this);
0928 }
0929 
0930 void BasketScene::aboutToBeActivated()
0931 {
0932     if (m_finishLoadOnFirstShow) {
0933         FOR_EACH_NOTE(note)
0934         note->finishLazyLoad();
0935 
0936         // relayoutNotes(/*animate=*/false);
0937         setFocusedNote(nullptr); // So that during the focusInEvent that will come shortly, the FIRST note is focused.
0938 
0939         m_finishLoadOnFirstShow = false;
0940         m_loaded = true;
0941     }
0942 }
0943 
0944 void BasketScene::reload()
0945 {
0946     closeEditor();
0947     unbufferizeAll(); // Keep the memory footprint low
0948 
0949     m_firstNote = nullptr;
0950 
0951     m_loaded = false;
0952     m_loadingLaunched = false;
0953 
0954     invalidate();
0955 }
0956 
0957 void BasketScene::load()
0958 {
0959     // Load only once:
0960     if (m_loadingLaunched)
0961         return;
0962     m_loadingLaunched = true;
0963 
0964     DEBUG_WIN << "Basket[" + folderName() + "]: Loading...";
0965     QDomDocument *doc = nullptr;
0966     QString content;
0967 
0968     // Load properties
0969     if (FileStorage::loadFromFile(fullPath() + ".basket", &content)) {
0970         doc = new QDomDocument("basket");
0971         if (!doc->setContent(content)) {
0972             DEBUG_WIN << "Basket[" + folderName() + "]: <font color=red>FAILED to parse XML</font>!";
0973             delete doc;
0974             doc = nullptr;
0975         }
0976     }
0977     if (isEncrypted())
0978         DEBUG_WIN << "Basket is encrypted.";
0979     if (!doc) {
0980         DEBUG_WIN << "Basket[" + folderName() + "]: <font color=red>FAILED to load</font>!";
0981         m_loadingLaunched = false;
0982         if (isEncrypted())
0983             m_locked = true;
0984         Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar
0985         return;
0986     }
0987     m_locked = false;
0988 
0989     QDomElement docElem = doc->documentElement();
0990     QDomElement properties = XMLWork::getElement(docElem, "properties");
0991 
0992     loadProperties(properties); // Since we are loading, this time the background image will also be loaded!
0993     // Now that the background image is loaded and subscribed, we display it during the load process:
0994     delete doc;
0995 
0996     // BEGIN Compatibility with 0.6.0 Pre-Alpha versions:
0997     QDomElement notes = XMLWork::getElement(docElem, "notes");
0998     if (notes.isNull())
0999         notes = XMLWork::getElement(docElem, "items");
1000     m_watcher->stopScan();
1001     m_shouldConvertPlainTextNotes = false; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded!
1002 
1003     // Load notes
1004     m_finishLoadOnFirstShow = (Global::bnpView->currentBasket() != this);
1005     loadNotes(notes, nullptr);
1006     if (m_shouldConvertPlainTextNotes)
1007         convertTexts();
1008     m_watcher->startScan();
1009 
1010     signalCountsChanged();
1011     if (isColumnsLayout()) {
1012         // Count the number of columns:
1013         int columnsCount = 0;
1014         Note *column = firstNote();
1015         while (column) {
1016             ++columnsCount;
1017             column = column->next();
1018         }
1019         m_columnsCount = columnsCount;
1020     }
1021 
1022     relayoutNotes();
1023 
1024     // On application start, the current basket is not focused yet, so the focus rectangle is not shown when calling focusANote():
1025     if (Global::bnpView->currentBasket() == this)
1026         setFocus();
1027     focusANote();
1028 
1029     m_loaded = true;
1030     enableActions();
1031 }
1032 
1033 void BasketScene::filterAgain(bool andEnsureVisible /* = true*/)
1034 {
1035     newFilter(decoration()->filterData(), andEnsureVisible);
1036 }
1037 
1038 void BasketScene::filterAgainDelayed()
1039 {
1040     QTimer::singleShot(0, this, SLOT(filterAgain()));
1041 }
1042 
1043 void BasketScene::newFilter(const FilterData &data, bool andEnsureVisible /* = true*/)
1044 {
1045     if (!isLoaded())
1046         return;
1047 
1048     // StopWatch::start(20);
1049 
1050     m_countFounds = 0;
1051     //Search within basket titles as well
1052     if (data.tagFilterType == FilterData::DontCareTagsFilter)
1053         if (!data.string.isEmpty())
1054             if (basketName().contains(data.string, Qt::CaseInsensitive)){
1055                 ++m_countFounds;
1056             }
1057 
1058     for (Note *note = firstNote(); note; note = note->next())
1059         m_countFounds += note->newFilter(data);
1060 
1061     relayoutNotes();
1062     signalCountsChanged();
1063 
1064     if (hasFocus())   // if (!hasFocus()), focusANote() will be called at focusInEvent()
1065         focusANote(); //  so, we avoid de-focus a note if it will be re-shown soon
1066     if (andEnsureVisible && m_focusedNote != nullptr)
1067         ensureNoteVisible(m_focusedNote);
1068 
1069     Global::bnpView->setFiltering(data.isFiltering);
1070 
1071     // StopWatch::check(20);
1072 }
1073 
1074 bool BasketScene::isFiltering()
1075 {
1076     return decoration()->filterBar()->filterData().isFiltering;
1077 }
1078 
1079 QString BasketScene::fullPath()
1080 {
1081     return Global::basketsFolder() + folderName();
1082 }
1083 
1084 QString BasketScene::fullPathForFileName(const QString &fileName)
1085 {
1086     return fullPath() + fileName;
1087 }
1088 
1089 /*static*/ QString BasketScene::fullPathForFolderName(const QString &folderName)
1090 {
1091     return Global::basketsFolder() + folderName;
1092 }
1093 
1094 void BasketScene::setShortcut(QKeySequence shortcut, int action)
1095 {
1096     QList<QKeySequence> shortcuts {shortcut};
1097     m_action->setShortcuts(shortcuts);
1098     m_shortcutAction = action;
1099 
1100     if (!shortcut.isEmpty()) {
1101         if (action > 0) {
1102             KGlobalAccel::self()->setShortcut(m_action, shortcuts, KGlobalAccel::Autoloading);
1103             KGlobalAccel::self()->setDefaultShortcut(m_action, shortcuts, KGlobalAccel::Autoloading);
1104         }
1105     }
1106 }
1107 
1108 void BasketScene::activatedShortcut()
1109 {
1110     Global::bnpView->setCurrentBasket(this);
1111 
1112     if (m_shortcutAction == 1)
1113         Global::bnpView->setActive(true);
1114 }
1115 
1116 void BasketScene::signalCountsChanged()
1117 {
1118     if (!m_timerCountsChanged.isActive()) {
1119         m_timerCountsChanged.setSingleShot(true);
1120         m_timerCountsChanged.start(0);
1121     }
1122 }
1123 
1124 void BasketScene::countsChangedTimeOut()
1125 {
1126     Q_EMIT countsChanged(this);
1127 }
1128 
1129 BasketScene::BasketScene(QWidget *parent, const QString &folderName)
1130     //: Q3ScrollView(parent)
1131     : QGraphicsScene(parent)
1132     , m_noActionOnMouseRelease(false)
1133     , m_ignoreCloseEditorOnNextMouseRelease(false)
1134     , m_pressPos(-100, -100)
1135     , m_canDrag(false)
1136     , m_firstNote(nullptr)
1137     , m_columnsCount(1)
1138     , m_mindMap(false)
1139     , m_resizingNote(nullptr)
1140     , m_pickedResizer(0)
1141     , m_movingNote(nullptr)
1142     , m_pickedHandle(0, 0)
1143     , m_notesToBeDeleted()
1144     , m_clickedToInsert(nullptr)
1145     , m_zoneToInsert(0)
1146     , m_posToInsert(-1, -1)
1147     , m_isInsertPopupMenu(false)
1148     , m_insertMenuTitle(nullptr)
1149     , m_loaded(false)
1150     , m_loadingLaunched(false)
1151     , m_locked(false)
1152     , m_decryptBox(nullptr)
1153     , m_button(nullptr)
1154     , m_encryptionType(NoEncryption)
1155 #ifdef HAVE_LIBGPGME
1156     , m_gpg(0)
1157 #endif
1158     , m_backgroundPixmap(nullptr)
1159     , m_opaqueBackgroundPixmap(nullptr)
1160     , m_selectedBackgroundPixmap(nullptr)
1161     , m_action(nullptr)
1162     , m_shortcutAction(0)
1163     , m_hoveredNote(nullptr)
1164     , m_hoveredZone(Note::None)
1165     , m_lockedHovering(false)
1166     , m_underMouse(false)
1167     , m_inserterRect()
1168     , m_inserterShown(false)
1169     , m_inserterSplit(true)
1170     , m_inserterTop(false)
1171     , m_inserterGroup(false)
1172     , m_lastDisableClick(QTime::currentTime())
1173     , m_isSelecting(false)
1174     , m_selectionStarted(false)
1175     , m_count(0)
1176     , m_countFounds(0)
1177     , m_countSelecteds(0)
1178     , m_icon("org.kde.basket")
1179     , m_folderName(folderName)
1180     , m_editor(nullptr)
1181     , m_redirectEditActions(false)
1182     , m_editorTrackMouseEvent(false)
1183     , m_editorWidth(-1)
1184     , m_editorHeight(-1)
1185     , m_doNotCloseEditor(false)
1186     , m_isDuringDrag(false)
1187     , m_draggedNotes()
1188     , m_focusedNote(nullptr)
1189     , m_startOfShiftSelectionNote(nullptr)
1190     , m_finishLoadOnFirstShow(false)
1191     , m_relayoutOnNextShow(false)
1192 {
1193     m_view = new BasketView(this);
1194     m_view->setFocusPolicy(Qt::StrongFocus);
1195     m_view->setAlignment(Qt::AlignLeft | Qt::AlignTop);
1196 
1197     // We do this in the basket properties dialog (and keep it in sync with the
1198     // global one)
1199     if (!m_folderName.endsWith('/'))
1200         m_folderName += '/';
1201 
1202     KActionCollection *ac = Global::bnpView->actionCollection();
1203     m_action = ac->addAction(m_folderName, this, SLOT(activatedShortcut()));
1204     ac->setShortcutsConfigurable(m_action, false);
1205     KGlobalAccel::setGlobalShortcut(m_action, (QKeySequence()));
1206 
1207     //    setDragAutoScroll(true);
1208 
1209     // By default, there is no corner widget: we set one for the corner area to be painted!
1210     // If we don't set one and there are two scrollbars present, slowly resizing up the window show graphical glitches in that area!
1211     m_cornerWidget = new QWidget(m_view);
1212     m_view->setCornerWidget(m_cornerWidget);
1213 
1214     m_view->viewport()->setAcceptDrops(true);
1215     m_view->viewport()->setMouseTracking(true);
1216     m_view->viewport()->setAutoFillBackground(false); // Do not clear the widget before paintEvent() because we always draw every pixels (faster and flicker-free)
1217 
1218     // File Watcher:
1219     m_watcher = new KDirWatch(this);
1220 
1221     connect(m_watcher, &KDirWatch::dirty, this, &BasketScene::watchedFileModified);
1222     //connect(m_watcher, &KDirWatch::deleted, this, &BasketScene::watchedFileDeleted);
1223     connect(&m_watcherTimer, &QTimer::timeout, this, &BasketScene::updateModifiedNotes);
1224 
1225     // Various Connections:
1226     connect(&m_autoScrollSelectionTimer, &QTimer::timeout, this, &BasketScene::doAutoScrollSelection);
1227     connect(&m_timerCountsChanged, &QTimer::timeout, this, &BasketScene::countsChangedTimeOut);
1228     connect(&m_inactivityAutoSaveTimer, &QTimer::timeout, this, &BasketScene::inactivityAutoSaveTimeout);
1229     connect(&m_inactivityAutoLockTimer, &QTimer::timeout, this, &BasketScene::inactivityAutoLockTimeout);
1230 
1231 #ifdef HAVE_LIBGPGME
1232     m_gpg = new KGpgMe();
1233 #endif
1234     m_locked = isFileEncrypted();
1235 
1236     // setup the delayed commit timer
1237     m_commitdelay.setSingleShot(true);
1238     connect(&m_commitdelay, &QTimer::timeout, this, &BasketScene::commitEdit);
1239 }
1240 
1241 void BasketScene::enterEvent(QEvent *)
1242 {
1243     m_underMouse = true;
1244     doHoverEffects();
1245 }
1246 
1247 void BasketScene::leaveEvent(QEvent *)
1248 {
1249     m_underMouse = false;
1250     doHoverEffects();
1251 
1252     if (m_lockedHovering)
1253         return;
1254 
1255     removeInserter();
1256     if (m_hoveredNote) {
1257         m_hoveredNote->setHovered(false);
1258         m_hoveredNote->setHoveredZone(Note::None);
1259         m_hoveredNote->update();
1260     }
1261     m_hoveredNote = nullptr;
1262 }
1263 
1264 void BasketScene::setFocusIfNotInPopupMenu()
1265 {
1266     if (!qApp->activePopupWidget()) {
1267         if (isDuringEdit())
1268             m_editor->graphicsWidget()->setFocus();
1269         else
1270             setFocus();
1271     }
1272 }
1273 
1274 void BasketScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
1275 {
1276     // If user click the basket, focus it!
1277     // The focus is delayed because if the click results in showing a popup menu,
1278     // the interface flicker by showing the focused rectangle (as the basket gets focus)
1279     // and immediately removing it (because the popup menu now have focus).
1280     if (!isDuringEdit())
1281         QTimer::singleShot(0, this, SLOT(setFocusIfNotInPopupMenu()));
1282 
1283     // Convenient variables:
1284     bool controlPressed = event->modifiers() & Qt::ControlModifier;
1285     bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
1286 
1287     // Do nothing if we disabled the click some milliseconds sooner.
1288     // For instance when a popup menu has been closed with click, we should not do action:
1289     if (event->button() == Qt::LeftButton && (qApp->activePopupWidget() || m_lastDisableClick.msecsTo(QTime::currentTime()) <= 80)) {
1290         doHoverEffects();
1291         m_noActionOnMouseRelease = true;
1292         // But we allow to select:
1293         // The code is the same as at the bottom of this method:
1294         if (event->button() == Qt::LeftButton) {
1295             m_selectionStarted = true;
1296             m_selectionBeginPoint = event->scenePos();
1297             m_selectionInvert = controlPressed || shiftPressed;
1298         }
1299 
1300         return;
1301     }
1302 
1303     // if we are editing and no control key are pressed
1304     if (m_editor && !shiftPressed && !controlPressed) {
1305         // if the mouse is over the editor
1306         QPoint view_shift(m_view->horizontalScrollBar()->value(), m_view->verticalScrollBar()->value());
1307         QGraphicsWidget *widget = dynamic_cast<QGraphicsWidget *>(m_view->itemAt((event->scenePos() - view_shift).toPoint()));
1308         if (widget && m_editor->graphicsWidget() == widget) {
1309             if (event->button() == Qt::LeftButton) {
1310                 m_editorTrackMouseEvent = true;
1311                 m_editor->startSelection(event->scenePos());
1312                 return;
1313             } else if (event->button() == Qt::MiddleButton) {
1314                 m_editor->paste(event->scenePos(), QClipboard::Selection);
1315                 return;
1316             }
1317         }
1318     }
1319 
1320     // Figure out what is the clicked note and zone:
1321     Note *clicked = noteAt(event->scenePos());
1322     if (m_editor && (!clicked || (clicked && !(editedNote() == clicked)))) {
1323         closeEditor();
1324     }
1325     Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None);
1326 
1327     // Popup Tags menu:
1328     if (zone == Note::TagsArrow && !controlPressed && !shiftPressed && event->button() != Qt::MidButton) {
1329         if (!clicked->allSelected())
1330             unselectAllBut(clicked);
1331         setFocusedNote(clicked); /// /// ///
1332         m_startOfShiftSelectionNote = clicked;
1333         m_noActionOnMouseRelease = true;
1334         popupTagsMenu(clicked);
1335 
1336         return;
1337     }
1338 
1339     if (event->button() == Qt::LeftButton) {
1340         // Prepare to allow drag and drop when moving mouse further:
1341         if ((zone == Note::Handle || zone == Note::Group) || (clicked && clicked->allSelected() && (zone == Note::TagsArrow || zone == Note::Custom0 || zone == Note::Content || zone == Note::Link /**/ || zone >= Note::Emblem0 /**/))) {
1342             if (!shiftPressed && !controlPressed) {
1343                 m_pressPos = event->scenePos(); // TODO: Allow to drag emblems to assign them to other notes. Then don't allow drag at Emblem0!!
1344 
1345                 m_canDrag = true;
1346 
1347                 // Saving where we were editing, because during a drag, the mouse can fly over the text edit and move the cursor position:
1348                 if (m_editor && m_editor->textEdit()) {
1349                     KTextEdit *editor = m_editor->textEdit();
1350                     m_textCursor = editor->textCursor();
1351                 }
1352             }
1353         }
1354 
1355         // Initializing Resizer move:
1356         if (zone == Note::Resizer) {
1357             m_resizingNote = clicked;
1358             m_pickedResizer = event->scenePos().x() - clicked->rightLimit();
1359             m_noActionOnMouseRelease = true;
1360             m_lockedHovering = true;
1361 
1362             return;
1363         }
1364 
1365         // Select note(s):
1366         if (zone == Note::Handle || zone == Note::Group || (zone == Note::GroupExpander && (controlPressed || shiftPressed))) {
1367             // closeEditor();
1368             Note *end = clicked;
1369             if (clicked->isGroup() && shiftPressed) {
1370                 if (clicked->containsNote(m_startOfShiftSelectionNote)) {
1371                     m_startOfShiftSelectionNote = clicked->firstRealChild();
1372                     end = clicked->lastRealChild();
1373                 } else if (clicked->firstRealChild()->isAfter(m_startOfShiftSelectionNote)) {
1374                     end = clicked->lastRealChild();
1375                 } else {
1376                     end = clicked->firstRealChild();
1377                 }
1378             }
1379             if (controlPressed && shiftPressed)
1380                 selectRange(m_startOfShiftSelectionNote, end, /*unselectOthers=*/false);
1381             else if (shiftPressed)
1382                 selectRange(m_startOfShiftSelectionNote, end);
1383             else if (controlPressed)
1384                 clicked->setSelectedRecursively(!clicked->allSelected());
1385             else if (!clicked->allSelected())
1386                 unselectAllBut(clicked);
1387             setFocusedNote(end); /// /// ///
1388             m_startOfShiftSelectionNote = (end->isGroup() ? end->firstRealChild() : end);
1389             // m_noActionOnMouseRelease = false;
1390             m_noActionOnMouseRelease = true;
1391 
1392             return;
1393         }
1394 
1395         // Folding/Unfolding group:
1396         if (zone == Note::GroupExpander) {
1397             clicked->toggleFolded();
1398 
1399             relayoutNotes();
1400             m_noActionOnMouseRelease = true;
1401 
1402             return;
1403         }
1404     }
1405 
1406     // Popup menu for tag emblems:
1407     if (event->button() == Qt::RightButton && zone >= Note::Emblem0) {
1408         if (!clicked->allSelected())
1409             unselectAllBut(clicked);
1410         setFocusedNote(clicked); /// /// ///
1411         m_startOfShiftSelectionNote = clicked;
1412         popupEmblemMenu(clicked, zone - Note::Emblem0);
1413         m_noActionOnMouseRelease = true;
1414 
1415         return;
1416     }
1417 
1418     // Insertion Popup Menu:
1419     if ((event->button() == Qt::RightButton) && ((!clicked && isFreeLayout()) || (clicked && (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn)))) {
1420         unselectAll();
1421         m_clickedToInsert = clicked;
1422         m_zoneToInsert = zone;
1423         m_posToInsert = event->scenePos();
1424 
1425         QMenu menu(m_view);
1426         menu.addActions(Global::bnpView->popupMenu("insert_popup")->actions());
1427 
1428         // If we already added a title, remove it because it would be kept and
1429         // then added several times.
1430         if (m_insertMenuTitle && menu.actions().contains(m_insertMenuTitle))
1431             menu.removeAction(m_insertMenuTitle);
1432 
1433         QAction *first = menu.actions().value(0);
1434 
1435         // i18n: Verbs (for the "insert" menu)
1436         if (zone == Note::TopGroup || zone == Note::BottomGroup)
1437             m_insertMenuTitle = menu.insertSection(first, i18n("Group"));
1438         else
1439             m_insertMenuTitle = menu.insertSection(first, i18n("Insert"));
1440 
1441         setInsertPopupMenu();
1442         connect(&menu, &QMenu::aboutToHide, this, &BasketScene::delayedCancelInsertPopupMenu);
1443         connect(&menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering);
1444         connect(&menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick);
1445         connect(&menu, &QMenu::aboutToHide, this, &BasketScene::hideInsertPopupMenu);
1446         doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually!
1447         m_lockedHovering = true;
1448         menu.exec(QCursor::pos());
1449         m_noActionOnMouseRelease = true;
1450         return;
1451     }
1452 
1453     // Note Context Menu:
1454     if (event->button() == Qt::RightButton && clicked && !clicked->isColumn() && zone != Note::Resizer) {
1455         if (!clicked->allSelected())
1456             unselectAllBut(clicked);
1457         setFocusedNote(clicked); /// /// ///
1458         if (editedNote() == clicked) {
1459             closeEditor(false);
1460             clicked->setSelected(true);
1461         }
1462         m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked);
1463         QMenu *menu = Global::bnpView->popupMenu("note_popup");
1464         connect(menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering);
1465         connect(menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick);
1466         doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually!
1467         m_lockedHovering = true;
1468         menu->exec(QCursor::pos());
1469         m_noActionOnMouseRelease = true;
1470         return;
1471     }
1472 
1473     // Paste selection under cursor (but not "create new primary note under cursor" because this is on moveRelease):
1474     if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) {
1475         if ((Settings::middleAction() != 0) && (event->modifiers() == Qt::ShiftModifier)) {
1476             m_clickedToInsert = clicked;
1477             m_zoneToInsert = zone;
1478             m_posToInsert = event->scenePos();
1479             // closeEditor();
1480             removeInserter();                    // If clicked at an insertion line and the new note shows a dialog for editing,
1481             NoteType::Id type = (NoteType::Id)0; //  hide that inserter before the note edition instead of after the dialog is closed
1482             switch (Settings::middleAction()) {
1483             case 1:
1484                 m_isInsertPopupMenu = true;
1485                 pasteNote();
1486                 break;
1487             case 2:
1488                 type = NoteType::Image;
1489                 break;
1490             case 3:
1491                 type = NoteType::Link;
1492                 break;
1493             case 4:
1494                 type = NoteType::Launcher;
1495                 break;
1496             default:
1497                 m_noActionOnMouseRelease = false;
1498                 return;
1499             }
1500             if (type != 0) {
1501                 m_ignoreCloseEditorOnNextMouseRelease = true;
1502                 Global::bnpView->insertEmpty(type);
1503             }
1504         } else {
1505             if (clicked)
1506                 zone = clicked->zoneAt(event->scenePos() - QPoint(clicked->x(), clicked->y()), true);
1507             // closeEditor();
1508             clickedToInsert(event, clicked, zone);
1509             save();
1510         }
1511         m_noActionOnMouseRelease = true;
1512         return;
1513     }
1514 
1515     // Finally, no action has been done during pressEvent, so an action can be done on releaseEvent:
1516     m_noActionOnMouseRelease = false;
1517 
1518     /* Selection scenario:
1519      * On contentsMousePressEvent, put m_selectionStarted to true and init Begin and End selection point.
1520      * On contentsMouseMoveEvent, if m_selectionStarted, update End selection point, update selection rect,
1521      * and if it's larger, switching to m_isSelecting mode: we can draw the selection rectangle.
1522      */
1523     // Prepare selection:
1524     if (event->button() == Qt::LeftButton) {
1525         m_selectionStarted = true;
1526         m_selectionBeginPoint = event->scenePos();
1527         // We usually invert the selection with the Ctrl key, but some environments (like GNOME or The Gimp) do it with the Shift key.
1528         // Since the Shift key has no specific usage, we allow to invert selection ALSO with Shift for Gimp people
1529         m_selectionInvert = controlPressed || shiftPressed;
1530     }
1531 }
1532 
1533 void BasketScene::delayedCancelInsertPopupMenu()
1534 {
1535     QTimer::singleShot(0, this, SLOT(cancelInsertPopupMenu()));
1536 }
1537 
1538 void BasketScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
1539 {
1540     if (event->reason() == QGraphicsSceneContextMenuEvent::Keyboard) {
1541         if (countFounds /*countShown*/ () == 0) { // TODO: Count shown!!
1542             QMenu *menu = Global::bnpView->popupMenu("insert_popup");
1543             setInsertPopupMenu();
1544             connect(menu, &QMenu::aboutToHide, this, &BasketScene::delayedCancelInsertPopupMenu);
1545             connect(menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering);
1546             connect(menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick);
1547             removeInserter();
1548             m_lockedHovering = true;
1549             menu->exec(m_view->mapToGlobal(QPoint(0, 0)));
1550         } else {
1551             if (!m_focusedNote->isSelected())
1552                 unselectAllBut(m_focusedNote);
1553             setFocusedNote(m_focusedNote); /// /// ///
1554             m_startOfShiftSelectionNote = (m_focusedNote->isGroup() ? m_focusedNote->firstRealChild() : m_focusedNote);
1555             // Popup at bottom (or top) of the focused note, if visible :
1556             QMenu *menu = Global::bnpView->popupMenu("note_popup");
1557             connect(menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering);
1558             connect(menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick);
1559             doHoverEffects(m_focusedNote, Note::Content); // In the case where another popup menu was open, we should do that manually!
1560             m_lockedHovering = true;
1561             menu->exec(noteVisibleRect(m_focusedNote).bottomLeft().toPoint());
1562         }
1563     }
1564 }
1565 
1566 QRectF BasketScene::noteVisibleRect(Note *note)
1567 {
1568     QRectF rect(QPointF(note->x(), note->y()), QSizeF(note->width(), note->height()));
1569     QPoint basketPoint = m_view->mapToGlobal(QPoint(0, 0));
1570     rect.moveTopLeft(rect.topLeft() + basketPoint + QPoint(m_view->frameWidth(), m_view->frameWidth()));
1571 
1572     // Now, rect contain the global note rectangle on the screen.
1573     // We have to clip it by the basket widget :
1574     //    if (rect.bottom() > basketPoint.y() + visibleHeight() + 1) { // Bottom too... bottom
1575     //        rect.setBottom(basketPoint.y() + visibleHeight() + 1);
1576     if (rect.bottom() > basketPoint.y() + m_view->viewport()->height() + 1) { // Bottom too... bottom
1577         rect.setBottom(basketPoint.y() + m_view->viewport()->height() + 1);
1578         if (rect.height() <= 0) // Have at least one visible pixel of height
1579             rect.setTop(rect.bottom());
1580     }
1581     if (rect.top() < basketPoint.y() + m_view->frameWidth()) { // Top too... top
1582         rect.setTop(basketPoint.y() + m_view->frameWidth());
1583         if (rect.height() <= 0)
1584             rect.setBottom(rect.top());
1585     }
1586     //    if (rect.right() > basketPoint.x() + visibleWidth() + 1) { // Right too... right
1587     //        rect.setRight(basketPoint.x() + visibleWidth() + 1);
1588     if (rect.right() > basketPoint.x() + m_view->viewport()->width() + 1) { // Right too... right
1589         rect.setRight(basketPoint.x() + m_view->viewport()->width() + 1);
1590         if (rect.width() <= 0) // Have at least one visible pixel of width
1591             rect.setLeft(rect.right());
1592     }
1593     if (rect.left() < basketPoint.x() + m_view->frameWidth()) { // Left too... left
1594         rect.setLeft(basketPoint.x() + m_view->frameWidth());
1595         if (rect.width() <= 0)
1596             rect.setRight(rect.left());
1597     }
1598     return rect;
1599 }
1600 
1601 void BasketScene::disableNextClick()
1602 {
1603     m_lastDisableClick = QTime::currentTime();
1604 }
1605 
1606 void BasketScene::recomputeAllStyles()
1607 {
1608     FOR_EACH_NOTE(note)
1609     note->recomputeAllStyles();
1610 }
1611 
1612 void BasketScene::removedStates(const QList<State *> &deletedStates)
1613 {
1614     bool modifiedBasket = false;
1615 
1616     FOR_EACH_NOTE(note)
1617     if (note->removedStates(deletedStates))
1618         modifiedBasket = true;
1619 
1620     if (modifiedBasket)
1621         save();
1622 }
1623 
1624 void BasketScene::insertNote(Note *note, Note *clicked, int zone, const QPointF &pos)
1625 {
1626     if (!note) {
1627         qDebug() << "Wanted to insert NO note";
1628         return;
1629     }
1630 
1631     if (clicked && zone == Note::BottomColumn) {
1632         // When inserting at the bottom of a column, it's obvious the new note SHOULD inherit tags.
1633         // We ensure that by changing the insertion point after the last note of the column:
1634         Note *last = clicked->lastChild();
1635         if (last) {
1636             clicked = last;
1637             zone = Note::BottomInsert;
1638         }
1639     }
1640 
1641     /// Insertion at the bottom of a column:
1642     if (clicked && zone == Note::BottomColumn) {
1643         note->setWidth(clicked->rightLimit() - clicked->x());
1644         Note *lastChild = clicked->lastChild();
1645         for (Note *n = note; n; n = n->next()) {
1646             n->setXRecursively(clicked->x());
1647             n->setYRecursively((lastChild ? lastChild : clicked)->bottom() + 1);
1648         }
1649         appendNoteIn(note, clicked);
1650 
1651         /// Insertion relative to a note (top/bottom, insert/group):
1652     } else if (clicked) {
1653         note->setWidth(clicked->width());
1654         for (Note *n = note; n; n = n->next()) {
1655             if (zone == Note::TopGroup || zone == Note::BottomGroup) {
1656                 n->setXRecursively(clicked->x() + Note::GROUP_WIDTH);
1657             } else {
1658                 n->setXRecursively(clicked->x());
1659             }
1660             if (zone == Note::TopInsert || zone == Note::TopGroup) {
1661                 n->setYRecursively(clicked->y());
1662             } else {
1663                 n->setYRecursively(clicked->bottom() + 1);
1664             }
1665         }
1666 
1667         if (zone == Note::TopInsert) {
1668             appendNoteBefore(note, clicked);
1669         } else if (zone == Note::BottomInsert) {
1670             appendNoteAfter(note, clicked);
1671         } else if (zone == Note::TopGroup) {
1672             groupNoteBefore(note, clicked);
1673         } else if (zone == Note::BottomGroup) {
1674             groupNoteAfter(note, clicked);
1675         }
1676 
1677         /// Free insertion:
1678     } else if (isFreeLayout()) {
1679         // Group if note have siblings:
1680         if (note->next()) {
1681             Note *group = new Note(this);
1682             for (Note *n = note; n; n = n->next())
1683                 n->setParentNote(group);
1684             group->setFirstChild(note);
1685             note = group;
1686         }
1687         // Insert at cursor position:
1688         const int initialWidth = 250;
1689         note->setWidth(note->isGroup() ? Note::GROUP_WIDTH : initialWidth);
1690         if (note->isGroup() && note->firstChild())
1691             note->setInitialHeight(note->firstChild()->height());
1692         // note->setGroupWidth(initialWidth);
1693         note->setXRecursively(pos.x());
1694         note->setYRecursively(pos.y());
1695         appendNoteAfter(note, lastNote());
1696     }
1697 
1698     relayoutNotes();
1699 }
1700 
1701 void BasketScene::clickedToInsert(QGraphicsSceneMouseEvent *event, Note *clicked, /*Note::Zone*/ int zone)
1702 {
1703     Note *note;
1704     if (event->button() == Qt::MidButton)
1705         note = NoteFactory::dropNote(QApplication::clipboard()->mimeData(QClipboard::Selection), this);
1706     else
1707         note = NoteFactory::createNoteText(QString(), this);
1708 
1709     if (!note)
1710         return;
1711 
1712     insertNote(note, clicked, zone, QPointF(event->scenePos()));
1713 
1714     //  ensureNoteVisible(lastInsertedNote()); // TODO: in insertNote()
1715 
1716     if (event->button() != Qt::MidButton) {
1717         removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it.
1718         closeEditor();
1719         noteEdit(note, /*justAdded=*/true);
1720     }
1721 }
1722 
1723 void BasketScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
1724 {
1725     m_isDuringDrag = true;
1726     Global::bnpView->updateStatusBarHint();
1727     if (NoteDrag::basketOf(event->mimeData()) == this) {
1728         m_draggedNotes = NoteDrag::notesOf(event);
1729         NoteDrag::saveNoteSelectionToList(selectedNotes());
1730     }
1731     event->accept();
1732 }
1733 
1734 void BasketScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
1735 {
1736     //  m_isDuringDrag = true;
1737 
1738     //  if (isLocked())
1739     //      return;
1740 
1741     //  FIXME: viewportToContents does NOT work !!!
1742     //  QPoint pos = viewportToContents(event->pos());
1743     //  QPoint pos( event->pos().x() + contentsX(), event->pos().y() + contentsY() );
1744 
1745     //  if (insertAtCursorPos())
1746     //      computeInsertPlace(pos);
1747     doHoverEffects(event->scenePos());
1748 
1749     //  showFrameInsertTo();
1750     if (isFreeLayout() || noteAt(event->scenePos())) // Cursor before rightLimit() or hovering the dragged source notes
1751         acceptDropEvent(event);
1752     else {
1753         event->setAccepted(false);
1754     }
1755 
1756     /*  Note *hoveredNote = noteAt(event->pos().x(), event->pos().y());
1757         if ( (isColumnsLayout() && !hoveredNote) || (draggedNotes().contains(hoveredNote)) ) {
1758             event->acceptAction(false);
1759             event->accept(false);
1760         } else
1761             acceptDropEvent(event);*/
1762 
1763     // A workaround since QScrollView::dragAutoScroll seem to have no effect :
1764     //  ensureVisible(event->pos().x() + contentsX(), event->pos().y() + contentsY(), 30, 30);
1765     //  QScrollView::dragMoveEvent(event);
1766 }
1767 
1768 void BasketScene::dragLeaveEvent(QGraphicsSceneDragDropEvent *)
1769 {
1770     //  resetInsertTo();
1771     m_isDuringDrag = false;
1772     m_draggedNotes.clear();
1773     NoteDrag::selectedNotes.clear();
1774     m_noActionOnMouseRelease = true;
1775     Q_EMIT resetStatusBarText();
1776     doHoverEffects();
1777 }
1778 
1779 void BasketScene::dropEvent(QGraphicsSceneDragDropEvent *event)
1780 {
1781     QPointF pos = event->scenePos();
1782     qDebug() << "Drop Event at position " << pos.x() << ":" << pos.y();
1783 
1784     m_isDuringDrag = false;
1785     Q_EMIT resetStatusBarText();
1786 
1787     //  if (isLocked())
1788     //      return;
1789 
1790     // Do NOT check the bottom&right borders.
1791     // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars),
1792     // the note is first removed, and relayoutNotes() compute the new height that is smaller
1793     // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!!
1794     // Should, of course, not return 0:
1795     Note *clicked = noteAt(pos);
1796 
1797     if (NoteFactory::movingNotesInTheSameBasket(event->mimeData(), this, event->dropAction()) && event->dropAction() == Qt::MoveAction) {
1798         m_doNotCloseEditor = true;
1799     }
1800 
1801     Note *note = NoteFactory::dropNote(event->mimeData(), this, true, event->dropAction(), dynamic_cast<Note *>(event->source()));
1802 
1803     if (note) {
1804         Note::Zone zone = (clicked ? clicked->zoneAt(pos - QPointF(clicked->x(), clicked->y()), /*toAdd=*/true) : Note::None);
1805         bool animateNewPosition = NoteFactory::movingNotesInTheSameBasket(event->mimeData(), this, event->dropAction());
1806         if (animateNewPosition) {
1807             FOR_EACH_NOTE(n)
1808             n->setOnTop(false);
1809             // FOR_EACH_NOTE_IN_CHUNK(note)
1810             for (Note *n = note; n; n = n->next())
1811                 n->setOnTop(true);
1812         }
1813 
1814         insertNote(note, clicked, zone, pos);
1815 
1816         // If moved a note on bottom, contentsHeight has been diminished, then view scrolled up, and we should re-scroll the view down:
1817         ensureNoteVisible(note);
1818 
1819         //      if (event->button() != Qt::MidButton) {
1820         //          removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it.
1821         //      }
1822 
1823         //      resetInsertTo();
1824         //      doHoverEffects(); called by insertNote()
1825         save();
1826     }
1827 
1828     m_draggedNotes.clear();
1829     NoteDrag::selectedNotes.clear();
1830 
1831     m_doNotCloseEditor = false;
1832     // When starting the drag, we saved where we were editing.
1833     // This is because during a drag, the mouse can fly over the text edit and move the cursor position, and even HIDE the cursor.
1834     // So we re-show the cursor, and re-position it at the right place:
1835     if (m_editor && m_editor->textEdit()) {
1836         KTextEdit *editor = m_editor->textEdit();
1837         editor->setTextCursor(m_textCursor);
1838     }
1839 }
1840 
1841 // handles dropping of a note to basket that is not shown
1842 // (usually through its entry in the basket list)
1843 void BasketScene::blindDrop(QGraphicsSceneDragDropEvent *event)
1844 {
1845     if (!m_isInsertPopupMenu && redirectEditActions()) {
1846         if (m_editor->textEdit())
1847             m_editor->textEdit()->paste();
1848         else if (m_editor->lineEdit())
1849             m_editor->lineEdit()->paste();
1850     } else {
1851         if (!isLoaded()) {
1852             load();
1853         }
1854         closeEditor();
1855         unselectAll();
1856         Note *note = NoteFactory::dropNote(event->mimeData(), this, true, event->dropAction(), dynamic_cast<Note *>(event->source()));
1857         if (note) {
1858             insertCreatedNote(note);
1859             // unselectAllBut(note);
1860         }
1861     }
1862     save();
1863 }
1864 
1865 void BasketScene::blindDrop(const QMimeData *mimeData, Qt::DropAction dropAction, QObject *source)
1866 {
1867     if (!m_isInsertPopupMenu && redirectEditActions()) {
1868         if (m_editor->textEdit())
1869             m_editor->textEdit()->paste();
1870         else if (m_editor->lineEdit())
1871             m_editor->lineEdit()->paste();
1872     } else {
1873         if (!isLoaded()) {
1874             load();
1875         }
1876         closeEditor();
1877         unselectAll();
1878         Note *note = NoteFactory::dropNote(mimeData, this, true, dropAction, dynamic_cast<Note *>(source));
1879         if (note) {
1880             insertCreatedNote(note);
1881             // unselectAllBut(note);
1882         }
1883     }
1884     save();
1885 }
1886 
1887 void BasketScene::insertEmptyNote(int type)
1888 {
1889     if (!isLoaded())
1890         load();
1891     if (isDuringEdit())
1892         closeEditor();
1893     Note *note = NoteFactory::createEmptyNote((NoteType::Id)type, this);
1894     insertCreatedNote(note /*, / *edit=* /true*/);
1895     noteEdit(note, /*justAdded=*/true);
1896 }
1897 
1898 void BasketScene::insertWizard(int type)
1899 {
1900     saveInsertionData();
1901     Note *note = nullptr;
1902     switch (type) {
1903     default:
1904     case 1:
1905         note = NoteFactory::importKMenuLauncher(this);
1906         break;
1907     case 2:
1908         note = NoteFactory::importIcon(this);
1909         break;
1910     case 3:
1911         note = NoteFactory::importFileContent(this);
1912         break;
1913     }
1914     if (!note)
1915         return;
1916     restoreInsertionData();
1917     insertCreatedNote(note);
1918     unselectAllBut(note);
1919     resetInsertionData();
1920 }
1921 
1922 void BasketScene::insertColor(const QColor &color)
1923 {
1924     Note *note = NoteFactory::createNoteColor(color, this);
1925     restoreInsertionData();
1926     insertCreatedNote(note);
1927     unselectAllBut(note);
1928     resetInsertionData();
1929 }
1930 
1931 void BasketScene::insertImage(const QPixmap &image)
1932 {
1933     Note *note = NoteFactory::createNoteImage(image, this);
1934     restoreInsertionData();
1935     insertCreatedNote(note);
1936     unselectAllBut(note);
1937     resetInsertionData();
1938 }
1939 
1940 void BasketScene::pasteNote(QClipboard::Mode mode)
1941 {
1942     if (!m_isInsertPopupMenu && redirectEditActions()) {
1943         if (m_editor->textEdit())
1944             m_editor->textEdit()->paste();
1945         else if (m_editor->lineEdit())
1946             m_editor->lineEdit()->paste();
1947     } else {
1948         if (!isLoaded()) {
1949             load();
1950         }
1951         closeEditor();
1952         unselectAll();
1953         Note *note = NoteFactory::dropNote(QApplication::clipboard()->mimeData(mode), this);
1954         if (note) {
1955             insertCreatedNote(note);
1956             // unselectAllBut(note);
1957         }
1958     }
1959 }
1960 
1961 void BasketScene::insertCreatedNote(Note *note)
1962 {
1963     // Get the insertion data if the user clicked inside the basket:
1964     Note *clicked = m_clickedToInsert;
1965     int zone = m_zoneToInsert;
1966     QPointF pos = m_posToInsert;
1967 
1968     // If it isn't the case, use the default position:
1969     if (!clicked && (pos.x() < 0 || pos.y() < 0)) {
1970         // Insert right after the focused note:
1971         focusANote();
1972         if (m_focusedNote) {
1973             clicked = m_focusedNote;
1974             zone = (m_focusedNote->isFree() ? Note::BottomGroup : Note::BottomInsert);
1975             pos = QPointF(m_focusedNote->x(), m_focusedNote->bottom());
1976             // Insert at the end of the last column:
1977         } else if (isColumnsLayout()) {
1978             Note *column = /*(Settings::newNotesPlace == 0 ?*/ firstNote() /*: lastNote())*/;
1979             /*if (Settings::newNotesPlace == 0 && column->firstChild()) { // On Top, if at least one child in the column
1980                 clicked = column->firstChild();
1981                 zone    = Note::TopInsert;
1982             } else { // On Bottom*/
1983             clicked = column;
1984             zone = Note::BottomColumn;
1985             /*}*/
1986             // Insert at free position:
1987         } else {
1988             pos = QPointF(0, 0);
1989         }
1990     }
1991 
1992     insertNote(note, clicked, zone, pos);
1993     //  ensureNoteVisible(lastInsertedNote());
1994     removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it.
1995                       //  resetInsertTo();
1996     save();
1997 }
1998 
1999 void BasketScene::saveInsertionData()
2000 {
2001     m_savedClickedToInsert = m_clickedToInsert;
2002     m_savedZoneToInsert = m_zoneToInsert;
2003     m_savedPosToInsert = m_posToInsert;
2004 }
2005 
2006 void BasketScene::restoreInsertionData()
2007 {
2008     m_clickedToInsert = m_savedClickedToInsert;
2009     m_zoneToInsert = m_savedZoneToInsert;
2010     m_posToInsert = m_savedPosToInsert;
2011 }
2012 
2013 void BasketScene::resetInsertionData()
2014 {
2015     m_clickedToInsert = nullptr;
2016     m_zoneToInsert = 0;
2017     m_posToInsert = QPoint(-1, -1);
2018 }
2019 
2020 void BasketScene::hideInsertPopupMenu()
2021 {
2022     QTimer::singleShot(50 /*ms*/, this, SLOT(timeoutHideInsertPopupMenu()));
2023 }
2024 
2025 void BasketScene::timeoutHideInsertPopupMenu()
2026 {
2027     resetInsertionData();
2028 }
2029 
2030 void BasketScene::acceptDropEvent(QGraphicsSceneDragDropEvent *event, bool preCond)
2031 {
2032     // FIXME: Should not accept all actions! Or not all actions (link not supported?!)
2033     // event->acceptAction(preCond && 1);
2034     // event->accept(preCond);
2035     event->setAccepted(preCond);
2036 }
2037 
2038 void BasketScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
2039 {
2040     // Now disallow drag and mouse redirection
2041     m_canDrag = false;
2042 
2043     if (m_editorTrackMouseEvent) {
2044         m_editorTrackMouseEvent = false;
2045         m_editor->endSelection(m_pressPos);
2046         return;
2047     }
2048 
2049     // Cancel Resizer move:
2050     if (m_resizingNote) {
2051         m_resizingNote = nullptr;
2052         m_pickedResizer = 0;
2053         m_lockedHovering = false;
2054         doHoverEffects();
2055         save();
2056     }
2057 
2058     // Cancel Note move:
2059     /*  if (m_movingNote) {
2060             m_movingNote   = 0;
2061             m_pickedHandle = QPoint(0, 0);
2062             m_lockedHovering = false;
2063             //doHoverEffects();
2064             save();
2065     }*/
2066 
2067     // Cancel Selection rectangle:
2068     if (m_isSelecting) {
2069         m_isSelecting = false;
2070         stopAutoScrollSelection();
2071         resetWasInLastSelectionRect();
2072         doHoverEffects();
2073         invalidate(m_selectionRect);
2074     }
2075     m_selectionStarted = false;
2076 
2077     Note *clicked = noteAt(event->scenePos());
2078     Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None);
2079     if ((zone == Note::Handle || zone == Note::Group) && editedNote() && editedNote() == clicked) {
2080         if (m_ignoreCloseEditorOnNextMouseRelease)
2081             m_ignoreCloseEditorOnNextMouseRelease = false;
2082         else {
2083             bool editedNoteStillThere = closeEditor();
2084             if (editedNoteStillThere)
2085                 // clicked->setSelected(true);
2086                 unselectAllBut(clicked);
2087         }
2088     }
2089 
2090     /*
2091         if (event->buttons() == 0 && (zone == Note::Group || zone == Note::Handle)) {
2092             closeEditor();
2093             unselectAllBut(clicked);
2094         }
2095     */
2096 
2097     // Do nothing if an action has already been made during mousePressEvent,
2098     // or if user made a selection and canceled it by regressing to a very small rectangle.
2099     if (m_noActionOnMouseRelease)
2100         return;
2101 
2102     // We immediately set it to true, to avoid actions set on mouseRelease if NO mousePress event has been triggered.
2103     // This is the case when a popup menu is shown, and user click to the basket area to close it:
2104     // the menu then receive the mousePress event and the basket area ONLY receive the mouseRelease event.
2105     // Obviously, nothing should be done in this case:
2106     m_noActionOnMouseRelease = true;
2107 
2108     if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) {
2109         if ((Settings::middleAction() != 0) && (event->modifiers() == Qt::ShiftModifier)) {
2110             m_clickedToInsert = clicked;
2111             m_zoneToInsert = zone;
2112             m_posToInsert = event->scenePos();
2113             closeEditor();
2114             removeInserter();                    // If clicked at an insertion line and the new note shows a dialog for editing,
2115             NoteType::Id type = (NoteType::Id)0; //  hide that inserter before the note edition instead of after the dialog is closed
2116             switch (Settings::middleAction()) {
2117             case 5:
2118                 type = NoteType::Color;
2119                 break;
2120             case 6:
2121                 Global::bnpView->grabScreenshot();
2122                 return;
2123             case 7:
2124                 Global::bnpView->slotColorFromScreen();
2125                 return;
2126             case 8:
2127                 Global::bnpView->insertWizard(3); // loadFromFile
2128                 return;
2129             case 9:
2130                 Global::bnpView->insertWizard(1); // importKMenuLauncher
2131                 return;
2132             case 10:
2133                 Global::bnpView->insertWizard(2); // importIcon
2134                 return;
2135             }
2136             if (type != 0) {
2137                 m_ignoreCloseEditorOnNextMouseRelease = true;
2138                 Global::bnpView->insertEmpty(type);
2139                 return;
2140             }
2141         }
2142     }
2143 
2144     //  Note *clicked = noteAt(event->pos().x(), event->pos().y());
2145     if (!clicked) {
2146         if (isFreeLayout() && event->button() == Qt::LeftButton) {
2147             clickedToInsert(event);
2148             save();
2149         }
2150 
2151         return;
2152     }
2153     //  Note::Zone zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()) );
2154 
2155     // Convenient variables:
2156     bool controlPressed = event->modifiers() & Qt::ControlModifier;
2157     bool shiftPressed = event->modifiers() & Qt::ShiftModifier;
2158 
2159     if (clicked && zone != Note::None && zone != Note::BottomColumn && zone != Note::Resizer && (controlPressed || shiftPressed)) {
2160         if (controlPressed && shiftPressed)
2161             selectRange(m_startOfShiftSelectionNote, clicked, /*unselectOthers=*/false);
2162         else if (shiftPressed)
2163             selectRange(m_startOfShiftSelectionNote, clicked);
2164         else if (controlPressed)
2165             clicked->setSelectedRecursively(!clicked->allSelected());
2166         setFocusedNote(clicked); /// /// ///
2167         m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked);
2168         m_noActionOnMouseRelease = true;
2169 
2170         return;
2171     }
2172 
2173     // Switch tag states:
2174     if (zone >= Note::Emblem0) {
2175         if (event->button() == Qt::LeftButton) {
2176             int icons = -1;
2177             for (State::List::iterator it = clicked->states().begin(); it != clicked->states().end(); ++it) {
2178                 if (!(*it)->emblem().isEmpty())
2179                     icons++;
2180                 if (icons == zone - Note::Emblem0) {
2181                     State *state = (*it)->nextState();
2182                     if (!state)
2183                         return;
2184                     it = clicked->states().insert(it, state);
2185                     ++it;
2186                     clicked->states().erase(it);
2187                     clicked->recomputeStyle();
2188                     clicked->unbufferize();
2189                     clicked->update();
2190                     updateEditorAppearance();
2191                     filterAgain();
2192                     save();
2193                     break;
2194                 }
2195             }
2196 
2197             return;
2198         } /* else if (event->button() == Qt::RightButton) {
2199          popupEmblemMenu(clicked, zone - Note::Emblem0);
2200          return;
2201      }*/
2202     }
2203 
2204     // Insert note or past clipboard:
2205     QString link;
2206     if (event->button() == Qt::MidButton && zone == Note::Resizer) {
2207         return; // zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()), true );
2208     }
2209     if (event->button() == Qt::RightButton && (clicked->isColumn() || zone == Note::Resizer)) {
2210         return;
2211     }
2212     if (clicked->isGroup() && zone == Note::None) {
2213         return;
2214     }
2215     switch (zone) {
2216     case Note::Handle:
2217     case Note::Group:
2218         // We select note on mousePress if it was unselected or Ctrl is pressed.
2219         // But the user can want to drag select_s_ notes, so it the note is selected, we only select it alone on mouseRelease:
2220         if (event->buttons() == 0) {
2221             qDebug() << "EXEC";
2222             if (!(event->modifiers() & Qt::ControlModifier) && clicked->allSelected())
2223                 unselectAllBut(clicked);
2224             if (zone == Note::Handle && isDuringEdit() && editedNote() == clicked) {
2225                 closeEditor();
2226                 clicked->setSelected(true);
2227             }
2228         }
2229         break;
2230 
2231     case Note::Custom0:
2232         // unselectAllBut(clicked);
2233         setFocusedNote(clicked);
2234         noteOpen(clicked);
2235         break;
2236 
2237     case Note::GroupExpander:
2238     case Note::TagsArrow:
2239         break;
2240 
2241     case Note::Link:
2242         link = clicked->linkAt(event->scenePos() - QPoint(clicked->x(), clicked->y()));
2243         if (!link.isEmpty()) {
2244             if (link == "basket-internal-remove-basket") {
2245                 // TODO: ask confirmation: "Do you really want to delete the welcome baskets?\n You can re-add them at any time in the Help menu."
2246                 Global::bnpView->doBasketDeletion(this);
2247             } else if (link == "basket-internal-import") {
2248                 QMenu *menu = Global::bnpView->popupMenu("fileimport");
2249                 menu->exec(event->screenPos());
2250             } else if (link.startsWith("basket://")) {
2251                 Q_EMIT crossReference(link);
2252             } else {
2253                 KRun *run = new KRun(QUrl::fromUserInput(link), m_view->window()); //  open the URL.
2254                 run->setAutoDelete(true);
2255             }
2256             break;
2257         } // If there is no link, edit note content
2258         Q_FALLTHROUGH();
2259     case Note::Content: {
2260         if (m_editor && m_editor->note() == clicked && m_editor->graphicsWidget()) {
2261             m_editor->setCursorTo(event->scenePos());
2262         } else {
2263             closeEditor();
2264             unselectAllBut(clicked);
2265             noteEdit(clicked, /*justAdded=*/false, event->scenePos());
2266             QGraphicsScene::mouseReleaseEvent(event);
2267         }
2268         break;
2269     }
2270     case Note::TopInsert:
2271     case Note::TopGroup:
2272     case Note::BottomInsert:
2273     case Note::BottomGroup:
2274     case Note::BottomColumn:
2275         clickedToInsert(event, clicked, zone);
2276         save();
2277         break;
2278 
2279     case Note::None:
2280     default:
2281         KMessageBox::information(m_view->viewport(),
2282                                  i18n("This message should never appear. If it does, this program is buggy! "
2283                                       "Please report the bug to the developer."));
2284         break;
2285     }
2286 }
2287 
2288 void BasketScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
2289 {
2290     Note *clicked = noteAt(event->scenePos());
2291     Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None);
2292 
2293     if (event->button() == Qt::LeftButton && (zone == Note::Group || zone == Note::Handle)) {
2294         doCopy(CopyToSelection);
2295         m_noActionOnMouseRelease = true;
2296     } else
2297         mousePressEvent(event);
2298 }
2299 
2300 void BasketScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
2301 {
2302     // redirect this event to the editor if track mouse event is active
2303     if (m_editorTrackMouseEvent && (m_pressPos - event->scenePos()).manhattanLength() > QApplication::startDragDistance()) {
2304         m_editor->updateSelection(event->scenePos());
2305         return;
2306     }
2307 
2308     // Drag the notes:
2309     if (m_canDrag && (m_pressPos - event->scenePos()).manhattanLength() > QApplication::startDragDistance()) {
2310         m_canDrag = false;
2311         m_isSelecting = false; // Don't draw selection rectangle after drag!
2312         m_selectionStarted = false;
2313 
2314         NoteSelection *selection = selectedNotes();
2315         if (selection->firstStacked()) {
2316             QDrag *d = NoteDrag::dragObject(selection, /*cutting=*/false, /*source=*/m_view); // d will be deleted by QT
2317             /*bool shouldRemove = */ d->exec();
2318             //      delete selection;
2319 
2320             // Never delete because URL is dragged and the file must be available for the extern application
2321             //      if (shouldRemove && d->target() == 0) // If target is another application that request to remove the note
2322             //          Q_EMIT wantDelete(this);
2323         }
2324         return;
2325     }
2326 
2327     // Moving a Resizer:
2328     if (m_resizingNote) {
2329         qreal groupWidth = event->scenePos().x() - m_resizingNote->x() - m_pickedResizer;
2330         qreal minRight = m_resizingNote->minRight();
2331         //        int maxRight   = 100 * contentsWidth(); // A big enough value (+infinity) for free layouts.
2332         qreal maxRight = 100 * sceneRect().width(); // A big enough value (+infinity) for free layouts.
2333         Note *nextColumn = m_resizingNote->next();
2334         if (m_resizingNote->isColumn()) {
2335             if (nextColumn)
2336                 maxRight = nextColumn->x() + nextColumn->rightLimit() - nextColumn->minRight() - Note::RESIZER_WIDTH;
2337             else
2338                 //                maxRight = contentsWidth();
2339                 maxRight = sceneRect().width();
2340         }
2341         if (groupWidth > maxRight - m_resizingNote->x())
2342             groupWidth = maxRight - m_resizingNote->x();
2343         if (groupWidth < minRight - m_resizingNote->x())
2344             groupWidth = minRight - m_resizingNote->x();
2345         qreal delta = groupWidth - m_resizingNote->groupWidth();
2346         m_resizingNote->setGroupWidth(groupWidth);
2347         // If resizing columns:
2348         if (m_resizingNote->isColumn()) {
2349             Note *column = m_resizingNote;
2350             if ((column = column->next())) {
2351                 // Next columns should not have them X coordinate animated, because it would flicker:
2352                 column->setXRecursively(column->x() + delta);
2353                 // And the resizer should resize the TWO sibling columns, and not push the other columns on th right:
2354                 column->setGroupWidth(column->groupWidth() - delta);
2355             }
2356         }
2357         relayoutNotes();
2358     }
2359 
2360     // Moving a Note:
2361     /*  if (m_movingNote) {
2362             int x = event->pos().x() - m_pickedHandle.x();
2363             int y = event->pos().y() - m_pickedHandle.y();
2364             if (x < 0) x = 0;
2365             if (y < 0) y = 0;
2366             m_movingNote->setX(x);
2367             m_movingNote->setY(y);
2368             m_movingNote->relayoutAt(x, y, / *animate=* /false);
2369             relayoutNotes(true);
2370         }
2371     */
2372 
2373     // Dragging the selection rectangle:
2374     if (m_selectionStarted)
2375         doAutoScrollSelection();
2376 
2377     doHoverEffects(event->scenePos());
2378 }
2379 
2380 void BasketScene::doAutoScrollSelection()
2381 {
2382     static const int AUTO_SCROLL_MARGIN = 50; // pixels
2383     static const int AUTO_SCROLL_DELAY = 100; // milliseconds
2384 
2385     QPoint pos = m_view->mapFromGlobal(QCursor::pos());
2386     // Do the selection:
2387     if (m_isSelecting)
2388         invalidate(m_selectionRect);
2389 
2390     m_selectionEndPoint = m_view->mapToScene(pos);
2391     m_selectionRect = QRectF(m_selectionBeginPoint, m_selectionEndPoint).normalized();
2392     if (m_selectionRect.left() < 0)
2393         m_selectionRect.setLeft(0);
2394     if (m_selectionRect.top() < 0)
2395         m_selectionRect.setTop(0);
2396     //    if (m_selectionRect.right() >= contentsWidth())    m_selectionRect.setRight(contentsWidth() - 1);
2397     //    if (m_selectionRect.bottom() >= contentsHeight())  m_selectionRect.setBottom(contentsHeight() - 1);
2398     if (m_selectionRect.right() >= sceneRect().width())
2399         m_selectionRect.setRight(sceneRect().width() - 1);
2400     if (m_selectionRect.bottom() >= sceneRect().height())
2401         m_selectionRect.setBottom(sceneRect().height() - 1);
2402 
2403     if ((m_selectionBeginPoint - m_selectionEndPoint).manhattanLength() > QApplication::startDragDistance()) {
2404         m_isSelecting = true;
2405         selectNotesIn(m_selectionRect, m_selectionInvert);
2406         invalidate(m_selectionRect);
2407         m_noActionOnMouseRelease = true;
2408     } else {
2409         // If the user was selecting but cancel by making the rectangle too small, cancel it really!!!
2410         if (m_isSelecting) {
2411             if (m_selectionInvert)
2412                 selectNotesIn(QRectF(), m_selectionInvert);
2413             else
2414                 unselectAllBut(nullptr); // TODO: unselectAll();
2415         }
2416         if (m_isSelecting)
2417             resetWasInLastSelectionRect();
2418         m_isSelecting = false;
2419         stopAutoScrollSelection();
2420 
2421         return;
2422     }
2423 
2424     // Do the auto-scrolling:
2425     // FIXME: It's still flickering
2426 
2427     //    QRectF insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, visibleWidth() - 2*AUTO_SCROLL_MARGIN, visibleHeight() - 2*AUTO_SCROLL_MARGIN);
2428     // QRectF insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, m_view->viewport()->width() - 2 * AUTO_SCROLL_MARGIN, m_view->viewport()->height() - 2 * AUTO_SCROLL_MARGIN);
2429 
2430     int dx = 0;
2431     int dy = 0;
2432 
2433     if (pos.y() < AUTO_SCROLL_MARGIN)
2434         dy = pos.y() - AUTO_SCROLL_MARGIN;
2435     else if (pos.y() > m_view->viewport()->height() - AUTO_SCROLL_MARGIN)
2436         dy = pos.y() - m_view->viewport()->height() + AUTO_SCROLL_MARGIN;
2437     //    else if (pos.y() > visibleHeight() - AUTO_SCROLL_MARGIN)
2438     //        dy = pos.y() - visibleHeight() + AUTO_SCROLL_MARGIN;
2439 
2440     if (pos.x() < AUTO_SCROLL_MARGIN)
2441         dx = pos.x() - AUTO_SCROLL_MARGIN;
2442     else if (pos.x() > m_view->viewport()->width() - AUTO_SCROLL_MARGIN)
2443         dx = pos.x() - m_view->viewport()->width() + AUTO_SCROLL_MARGIN;
2444     //    else if (pos.x() > visibleWidth() - AUTO_SCROLL_MARGIN)
2445     //        dx = pos.x() - visibleWidth() + AUTO_SCROLL_MARGIN;
2446 
2447     if (dx || dy) {
2448         qApp->sendPostedEvents(); // Do the repaints, because the scrolling will make the area to repaint to be wrong
2449                                   //        scrollBy(dx, dy);
2450         if (!m_autoScrollSelectionTimer.isActive())
2451             m_autoScrollSelectionTimer.start(AUTO_SCROLL_DELAY);
2452     } else
2453         stopAutoScrollSelection();
2454 }
2455 
2456 void BasketScene::stopAutoScrollSelection()
2457 {
2458     m_autoScrollSelectionTimer.stop();
2459 }
2460 
2461 void BasketScene::resetWasInLastSelectionRect()
2462 {
2463     Note *note = m_firstNote;
2464     while (note) {
2465         note->resetWasInLastSelectionRect();
2466         note = note->next();
2467     }
2468 }
2469 
2470 void BasketScene::selectAll()
2471 {
2472     if (redirectEditActions()) {
2473         if (m_editor->textEdit())
2474             m_editor->textEdit()->selectAll();
2475         else if (m_editor->lineEdit())
2476             m_editor->lineEdit()->selectAll();
2477     } else {
2478         // First select all in the group, then in the parent group...
2479         Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : nullptr);
2480         while (parent) {
2481             if (!parent->allSelected()) {
2482                 parent->setSelectedRecursively(true);
2483                 return;
2484             }
2485             parent = parent->parentNote();
2486         }
2487         // Then, select all:
2488         FOR_EACH_NOTE(note)
2489         note->setSelectedRecursively(true);
2490     }
2491 }
2492 
2493 void BasketScene::unselectAll()
2494 {
2495     if (redirectEditActions()) {
2496         if (m_editor->textEdit()) {
2497             QTextCursor cursor = m_editor->textEdit()->textCursor();
2498             cursor.clearSelection();
2499             m_editor->textEdit()->setTextCursor(cursor);
2500             selectionChangedInEditor(); // THIS IS NOT EMITTED BY Qt!!!
2501         } else if (m_editor->lineEdit())
2502             m_editor->lineEdit()->deselect();
2503     } else {
2504         if (countSelecteds() > 0) // Optimization
2505             FOR_EACH_NOTE(note)
2506         note->setSelectedRecursively(false);
2507     }
2508 }
2509 
2510 void BasketScene::invertSelection()
2511 {
2512     FOR_EACH_NOTE(note)
2513     note->invertSelectionRecursively();
2514 }
2515 
2516 void BasketScene::unselectAllBut(Note *toSelect)
2517 {
2518     FOR_EACH_NOTE(note)
2519     note->unselectAllBut(toSelect);
2520 }
2521 
2522 void BasketScene::invertSelectionOf(Note *toSelect)
2523 {
2524     FOR_EACH_NOTE(note)
2525     note->invertSelectionOf(toSelect);
2526 }
2527 
2528 void BasketScene::selectNotesIn(const QRectF &rect, bool invertSelection, bool unselectOthers /*= true*/)
2529 {
2530     FOR_EACH_NOTE(note)
2531     note->selectIn(rect, invertSelection, unselectOthers);
2532 }
2533 
2534 void BasketScene::doHoverEffects()
2535 {
2536     doHoverEffects(m_view->mapToScene(m_view->viewport()->mapFromGlobal(QCursor::pos())));
2537 }
2538 
2539 void BasketScene::doHoverEffects(Note *note, Note::Zone zone, const QPointF &pos)
2540 {
2541     // Inform the old and new hovered note (if any):
2542     Note *oldHoveredNote = m_hoveredNote;
2543     if (note != m_hoveredNote) {
2544         if (m_hoveredNote) {
2545             m_hoveredNote->setHovered(false);
2546             m_hoveredNote->setHoveredZone(Note::None);
2547             m_hoveredNote->update();
2548         }
2549         m_hoveredNote = note;
2550         if (m_hoveredNote) {
2551             m_hoveredNote->setHovered(true);
2552         }
2553     }
2554 
2555     // If we are hovering a note, compute which zone is hovered and inform the note:
2556     if (m_hoveredNote) {
2557         if (zone != m_hoveredZone || oldHoveredNote != m_hoveredNote) {
2558             m_hoveredZone = zone;
2559             m_hoveredNote->setHoveredZone(zone);
2560             m_view->viewport()->setCursor(m_hoveredNote->cursorFromZone(zone));
2561 
2562             m_hoveredNote->update();
2563         }
2564 
2565         // If we are hovering an insert line zone, update this thing:
2566         if (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn) {
2567             placeInserter(m_hoveredNote, zone);
2568         } else {
2569             removeInserter();
2570         }
2571         // If we are hovering an embedded link in a rich text element, show the destination in the statusbar:
2572         if (zone == Note::Link)
2573             Q_EMIT setStatusBarText(m_hoveredNote->linkAt(pos - QPoint(m_hoveredNote->x(), m_hoveredNote->y())));
2574         else if (m_hoveredNote->content())
2575             Q_EMIT setStatusBarText(m_hoveredNote->content()->statusBarMessage(m_hoveredZone)); // resetStatusBarText();
2576         // If we aren't hovering a note, reset all:
2577     } else {
2578         if (isFreeLayout() && !isSelecting())
2579             m_view->viewport()->setCursor(Qt::CrossCursor);
2580         else
2581             m_view->viewport()->unsetCursor();
2582         m_hoveredZone = Note::None;
2583         removeInserter();
2584         Q_EMIT resetStatusBarText();
2585     }
2586 }
2587 
2588 void BasketScene::doHoverEffects(const QPointF &pos)
2589 {
2590     //  if (isDuringEdit())
2591     //      viewport()->unsetCursor();
2592 
2593     // Do we have the right to do hover effects?
2594     if (!m_loaded || m_lockedHovering) {
2595         return;
2596     }
2597 
2598     // enterEvent() (mouse enter in the widget) set m_underMouse to true, and leaveEvent() make it false.
2599     // But some times the enterEvent() is not trigerred: eg. when dragging the scrollbar:
2600     // Ending the drag INSIDE the basket area will make NO hoverEffects() because m_underMouse is false.
2601     // User need to leave the area and re-enter it to get effects.
2602     // This hack solve that by dismissing the m_underMouse variable:
2603 
2604     // Don't do hover effects when a popup menu is opened.
2605     // Primarily because the basket area will only receive mouseEnterEvent and mouveLeaveEvent.
2606     // It willn't be noticed of mouseMoveEvent, which would result in a apparently broken application state:
2607     bool underMouse = !qApp->activePopupWidget();
2608     // if (qApp->activePopupWidget())
2609     //    underMouse = false;
2610 
2611     // Compute which note is hovered:
2612     Note *note = (m_isSelecting || !underMouse ? nullptr : noteAt(pos));
2613     Note::Zone zone = (note ? note->zoneAt(pos - QPointF(note->x(), note->y()), isDuringDrag()) : Note::None);
2614 
2615     // Inform the old and new hovered note (if any) and update the areas:
2616     doHoverEffects(note, zone, pos);
2617 }
2618 
2619 void BasketScene::mouseEnteredEditorWidget()
2620 {
2621     if (!m_lockedHovering && !qApp->activePopupWidget())
2622         doHoverEffects(editedNote(), Note::Content, QPoint());
2623 }
2624 
2625 void BasketScene::removeInserter()
2626 {
2627     if (m_inserterShown) { // Do not hide (and then update/repaint the view) if it is already hidden!
2628         m_inserterShown = false;
2629         invalidate(m_inserterRect);
2630     }
2631 }
2632 
2633 void BasketScene::placeInserter(Note *note, int zone)
2634 {
2635     // Remove the inserter:
2636     if (!note) {
2637         removeInserter();
2638         return;
2639     }
2640 
2641     // Update the old position:
2642     if (inserterShown()) {
2643         invalidate(m_inserterRect);
2644     }
2645     // Some commodities:
2646     m_inserterShown = true;
2647     m_inserterTop = (zone == Note::TopGroup || zone == Note::TopInsert);
2648     m_inserterGroup = (zone == Note::TopGroup || zone == Note::BottomGroup);
2649     // X and width:
2650     qreal groupIndent = (note->isGroup() ? note->width() : Note::HANDLE_WIDTH);
2651     qreal x = note->x();
2652     qreal width = (note->isGroup() ? note->rightLimit() - note->x() : note->width());
2653     if (m_inserterGroup) {
2654         x += groupIndent;
2655         width -= groupIndent;
2656     }
2657     m_inserterSplit = (Settings::groupOnInsertionLine() && note && !note->isGroup() && !note->isFree() && !note->isColumn());
2658     //  if (note->isGroup())
2659     //      width = note->rightLimit() - note->x() - (m_inserterGroup ? groupIndent : 0);
2660     // Y:
2661     qreal y = note->y() - (m_inserterGroup && m_inserterTop ? 1 : 3);
2662     if (!m_inserterTop)
2663         y += (note->isColumn() ? note->height() : note->height());
2664     // Assigning result:
2665     m_inserterRect = QRectF(x, y, width, 6 - (m_inserterGroup ? 2 : 0));
2666     // Update the new position:
2667     invalidate(m_inserterRect);
2668 }
2669 
2670 inline void drawLineByRect(QPainter &painter, qreal x, qreal y, qreal width, qreal height)
2671 {
2672     painter.drawLine(x, y, x + width - 1, y + height - 1);
2673 }
2674 
2675 void BasketScene::drawInserter(QPainter &painter, qreal xPainter, qreal yPainter)
2676 {
2677     if (!m_inserterShown)
2678         return;
2679 
2680     QRectF rect = m_inserterRect; // For shorter code-lines when drawing!
2681     rect.translate(-xPainter, -yPainter);
2682     int lineY = (m_inserterGroup && m_inserterTop ? 0 : 2);
2683     int roundY = (m_inserterGroup && m_inserterTop ? 0 : 1);
2684 
2685     KStatefulBrush statefulBrush(KColorScheme::View, KColorScheme::HoverColor);
2686     const QColor highlightColor = palette().color(QPalette::Highlight).lighter();
2687     painter.setPen(highlightColor);
2688     // The horizontal line:
2689     // painter.drawRect(       rect.x(),                    rect.y() + lineY,  rect.width(), 2);
2690     int width = rect.width() - 4;
2691     painter.fillRect(rect.x() + 2, rect.y() + lineY, width, 2, highlightColor);
2692     // The left-most and right-most edges (biggest vertical lines):
2693     drawLineByRect(painter, rect.x(), rect.y(), 1, (m_inserterGroup ? 4 : 6));
2694     drawLineByRect(painter, rect.x() + rect.width() - 1, rect.y(), 1, (m_inserterGroup ? 4 : 6));
2695     // The left and right mid vertical lines:
2696     drawLineByRect(painter, rect.x() + 1, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4));
2697     drawLineByRect(painter, rect.x() + rect.width() - 2, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4));
2698     // Draw the split as a feedback to know where is the limit between insert and group:
2699     if (m_inserterSplit) {
2700         int noteWidth = rect.width() + (m_inserterGroup ? Note::HANDLE_WIDTH : 0);
2701         int xSplit = rect.x() - (m_inserterGroup ? Note::HANDLE_WIDTH : 0) + noteWidth / 2;
2702         painter.drawRect(xSplit - 2, rect.y() + lineY, 4, 2);
2703         painter.drawRect(xSplit - 1, rect.y() + lineY, 2, 2);
2704     }
2705 }
2706 
2707 void BasketScene::helpEvent(QGraphicsSceneHelpEvent *event)
2708 {
2709     if (!m_loaded || !Settings::showNotesToolTip())
2710         return;
2711 
2712     QString message;
2713     QRectF rect;
2714 
2715     QPointF contentPos = event->scenePos();
2716     Note *note = noteAt(contentPos);
2717 
2718     if (!note && isFreeLayout()) {
2719         message = i18n("Insert note here\nRight click for more options");
2720         QRectF itRect;
2721         for (QList<QRectF>::iterator it = m_blankAreas.begin(); it != m_blankAreas.end(); ++it) {
2722             itRect = QRectF(0, 0, m_view->viewport()->width(), m_view->viewport()->height()).intersected(*it);
2723             if (itRect.contains(contentPos)) {
2724                 rect = itRect;
2725                 rect.moveLeft(rect.left() - sceneRect().x());
2726                 rect.moveTop(rect.top() - sceneRect().y());
2727                 break;
2728             }
2729         }
2730     } else {
2731         if (!note)
2732             return;
2733 
2734         Note::Zone zone = note->zoneAt(contentPos - QPointF(note->x(), note->y()));
2735 
2736         switch (zone) {
2737         case Note::Resizer:
2738             message = (note->isColumn() ? i18n("Resize those columns") : (note->isGroup() ? i18n("Resize this group") : i18n("Resize this note")));
2739             break;
2740         case Note::Handle:
2741             message = i18n("Select or move this note");
2742             break;
2743         case Note::Group:
2744             message = i18n("Select or move this group");
2745             break;
2746         case Note::TagsArrow:
2747             message = i18n("Assign or remove tags from this note");
2748             if (note->states().count() > 0) {
2749                 QString tagsString;
2750                 for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) {
2751                     QString tagName = "<nobr>" + Tools::textToHTMLWithoutP((*it)->fullName()) + "</nobr>";
2752                     if (tagsString.isEmpty())
2753                         tagsString = tagName;
2754                     else
2755                         tagsString = i18n("%1, %2", tagsString, tagName);
2756                 }
2757                 message = "<qt><nobr>" + message + "</nobr><br>" + i18n("<b>Assigned Tags</b>: %1", tagsString);
2758             }
2759             break;
2760         case Note::Custom0:
2761             message = note->content()->zoneTip(zone);
2762             break; //"Open this link/Open this file/Open this sound file/Launch this application"
2763         case Note::GroupExpander:
2764             message = (note->isFolded() ? i18n("Expand this group") : i18n("Collapse this group"));
2765             break;
2766         case Note::Link:
2767         case Note::Content:
2768             message = note->content()->editToolTipText();
2769             break;
2770         case Note::TopInsert:
2771         case Note::BottomInsert:
2772             message = i18n("Insert note here\nRight click for more options");
2773             break;
2774         case Note::TopGroup:
2775             message = i18n("Group note with the one below\nRight click for more options");
2776             break;
2777         case Note::BottomGroup:
2778             message = i18n("Group note with the one above\nRight click for more options");
2779             break;
2780         case Note::BottomColumn:
2781             message = i18n("Insert note here\nRight click for more options");
2782             break;
2783         case Note::None:
2784             message = "** Zone NONE: internal error **";
2785             break;
2786         default:
2787             if (zone >= Note::Emblem0)
2788                 message = note->stateForEmblemNumber(zone - Note::Emblem0)->fullName();
2789             else
2790                 message = QString();
2791             break;
2792         }
2793 
2794         if (zone == Note::Content || zone == Note::Link || zone == Note::Custom0) {
2795             const auto toolTip = QMultiMap<QString, QString> {
2796                 { i18n("Added"), note->addedStringDate() },
2797                 { i18n("Last Modification"), note->lastModificationStringDate() }
2798             }.unite(note->content()->toolTipInfos());
2799 
2800             message = "<qt><nobr>" + message;
2801             for (const QString &key : toolTip.keys()) {
2802                 message += "<br>" + i18nc("of the form 'key: value'", "<b>%1</b>: %2", key, toolTip.value(key));
2803             }
2804             message += "</nobr></qt>";
2805         } else if (m_inserterSplit && (zone == Note::TopInsert || zone == Note::BottomInsert))
2806             message += '\n' + i18n("Click on the right to group instead of insert");
2807         else if (m_inserterSplit && (zone == Note::TopGroup || zone == Note::BottomGroup))
2808             message += '\n' + i18n("Click on the left to insert instead of group");
2809 
2810         rect = note->zoneRect(zone, contentPos - QPoint(note->x(), note->y()));
2811 
2812         rect.moveLeft(rect.left() - sceneRect().x());
2813         rect.moveTop(rect.top() - sceneRect().y());
2814 
2815         rect.moveLeft(rect.left() + note->x());
2816         rect.moveTop(rect.top() + note->y());
2817     }
2818 
2819     QToolTip::showText(event->screenPos(), message, m_view, rect.toRect());
2820 }
2821 
2822 Note *BasketScene::lastNote()
2823 {
2824     Note *note = firstNote();
2825     while (note && note->next())
2826         note = note->next();
2827     return note;
2828 }
2829 
2830 void BasketScene::deleteNotes()
2831 {
2832     Note *note = m_firstNote;
2833 
2834     while (note) {
2835         Note *tmp = note->next();
2836         delete note;
2837         note = tmp;
2838     }
2839     m_firstNote = nullptr;
2840     m_resizingNote = nullptr;
2841     m_movingNote = nullptr;
2842     m_focusedNote = nullptr;
2843     m_startOfShiftSelectionNote = nullptr;
2844     m_tagPopupNote = nullptr;
2845     m_clickedToInsert = nullptr;
2846     m_savedClickedToInsert = nullptr;
2847     m_hoveredNote = nullptr;
2848     m_count = 0;
2849     m_countFounds = 0;
2850     m_countSelecteds = 0;
2851 
2852     Q_EMIT resetStatusBarText();
2853     Q_EMIT countsChanged(this);
2854 }
2855 
2856 Note *BasketScene::noteAt(QPointF pos)
2857 {
2858     qreal x = pos.x();
2859     qreal y = pos.y();
2860     // NO:
2861     //  // Do NOT check the bottom&right borders.
2862     //  // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars),
2863     //  // the note is first removed, and relayoutNotes() compute the new height that is smaller
2864     //  // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!!
2865     //  // Should, of course, not return 0:
2866     if (x < 0 || x > sceneRect().width() || y < 0 || y > sceneRect().height())
2867         return nullptr;
2868 
2869     // When resizing a note/group, keep it highlighted:
2870     if (m_resizingNote)
2871         return m_resizingNote;
2872 
2873     // Search and return the hovered note:
2874     Note *note = m_firstNote;
2875     Note *possibleNote;
2876     while (note) {
2877         possibleNote = note->noteAt(pos);
2878         if (possibleNote) {
2879             if (NoteDrag::selectedNotes.contains(possibleNote) || draggedNotes().contains(possibleNote))
2880                 return nullptr;
2881             else
2882                 return possibleNote;
2883         }
2884         note = note->next();
2885     }
2886 
2887     // If the basket is layouted in columns, return one of the columns to be able to add notes in them:
2888     if (isColumnsLayout()) {
2889         Note *column = m_firstNote;
2890         while (column) {
2891             if (x >= column->x() && x < column->rightLimit())
2892                 return column;
2893             column = column->next();
2894         }
2895     }
2896 
2897     // Nothing found, no note is hovered:
2898     return nullptr;
2899 }
2900 
2901 BasketScene::~BasketScene()
2902 {
2903     m_commitdelay.stop(); // we don't know how long deleteNotes() last so we want to make extra sure that nobody will commit in between
2904     if (m_decryptBox)
2905         delete m_decryptBox;
2906 #ifdef HAVE_LIBGPGME
2907     delete m_gpg;
2908 #endif
2909     blockSignals(true);
2910     deleteNotes();
2911 
2912     if (m_view)
2913         delete m_view;
2914 }
2915 
2916 QColor BasketScene::selectionRectInsideColor()
2917 {
2918     return Tools::mixColor(Tools::mixColor(backgroundColor(), palette().color(QPalette::Highlight)), backgroundColor());
2919 }
2920 
2921 QColor alphaBlendColors(const QColor &bgColor, const QColor &fgColor, const int a)
2922 {
2923     // normal button...
2924     QRgb rgb = bgColor.rgb();
2925     QRgb rgb_b = fgColor.rgb();
2926     int alpha = a;
2927     if (alpha > 255)
2928         alpha = 255;
2929     if (alpha < 0)
2930         alpha = 0;
2931     int inv_alpha = 255 - alpha;
2932     QColor result = QColor(qRgb(qRed(rgb_b) * inv_alpha / 255 + qRed(rgb) * alpha / 255, qGreen(rgb_b) * inv_alpha / 255 + qGreen(rgb) * alpha / 255, qBlue(rgb_b) * inv_alpha / 255 + qBlue(rgb) * alpha / 255));
2933 
2934     return result;
2935 }
2936 
2937 void BasketScene::unlock()
2938 {
2939     QTimer::singleShot(0, this, SLOT(load()));
2940 }
2941 
2942 void BasketScene::inactivityAutoLockTimeout()
2943 {
2944     lock();
2945 }
2946 
2947 void BasketScene::drawBackground(QPainter *painter, const QRectF &rect)
2948 {
2949     if (!m_loadingLaunched) {
2950         if (!m_locked) {
2951             QTimer::singleShot(0, this, SLOT(load()));
2952             return;
2953         } else {
2954             Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar
2955         }
2956     }
2957 
2958     if (!hasBackgroundImage()) {
2959         painter->fillRect(rect, backgroundColor());
2960         // It's either a background pixmap to draw or a background color to fill:
2961     } else if (isTiledBackground() || (rect.x() < backgroundPixmap()->width() && rect.y() < backgroundPixmap()->height())) {
2962         painter->fillRect(rect, backgroundColor());
2963         blendBackground(*painter, rect, 0, 0, /*opaque=*/true);
2964     } else {
2965         painter->fillRect(rect, backgroundColor());
2966     }
2967 }
2968 
2969 void BasketScene::drawForeground(QPainter *painter, const QRectF &rect)
2970 {
2971     if (m_locked) {
2972         if (!m_decryptBox) {
2973             m_decryptBox = new QFrame(m_view);
2974             m_decryptBox->setFrameShape(QFrame::StyledPanel);
2975             m_decryptBox->setFrameShadow(QFrame::Plain);
2976             m_decryptBox->setLineWidth(1);
2977 
2978             QGridLayout *layout = new QGridLayout(m_decryptBox);
2979             layout->setContentsMargins(11, 11, 11, 11);
2980             layout->setSpacing(6);
2981 
2982 #ifdef HAVE_LIBGPGME
2983             m_button = new QPushButton(m_decryptBox);
2984             m_button->setText(i18n("&Unlock"));
2985             layout->addWidget(m_button, 1, 2);
2986             connect(m_button, &QButton::clicked, this, &BasketScene::unlock);
2987 #endif
2988             QLabel *label = new QLabel(m_decryptBox);
2989             QString text = "<b>" + i18n("Password protected basket.") + "</b><br/>";
2990 #ifdef HAVE_LIBGPGME
2991             label->setText(text + i18n("Press Unlock to access it."));
2992 #else
2993             label->setText(text + i18n("Encryption is not supported by<br/>this version of %1.", QGuiApplication::applicationDisplayName()));
2994 #endif
2995             label->setAlignment(Qt::AlignTop);
2996             layout->addWidget(label, 0, 1, 1, 2);
2997             QLabel *pixmap = new QLabel(m_decryptBox);
2998             pixmap->setPixmap(KIconLoader::global()->loadIcon("encrypted", KIconLoader::NoGroup, KIconLoader::SizeHuge));
2999             layout->addWidget(pixmap, 0, 0, 2, 1);
3000 
3001             QSpacerItem *spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
3002             layout->addItem(spacer, 1, 1);
3003 
3004             label = new QLabel("<small>" +
3005                                    i18n("To make baskets stay unlocked, change the automatic<br>"
3006                                         "locking duration in the application settings.") +
3007                                    "</small>",
3008                                m_decryptBox);
3009             label->setAlignment(Qt::AlignTop);
3010             layout->addWidget(label, 2, 0, 1, 3);
3011 
3012             m_decryptBox->resize(layout->sizeHint());
3013         }
3014         if (m_decryptBox->isHidden()) {
3015             m_decryptBox->show();
3016         }
3017 #ifdef HAVE_LIBGPGME
3018         m_button->setFocus();
3019 #endif
3020         m_decryptBox->move((m_view->viewport()->width() - m_decryptBox->width()) / 2, (m_view->viewport()->height() - m_decryptBox->height()) / 2);
3021     } else {
3022         if (m_decryptBox && !m_decryptBox->isHidden())
3023             m_decryptBox->hide();
3024     }
3025 
3026     if (!m_loaded) {
3027         setSceneRect(0, 0, m_view->viewport()->width(), m_view->viewport()->height());
3028         QBrush brush(backgroundColor());
3029         QPixmap pixmap(m_view->viewport()->width(), m_view->viewport()->height()); // TODO: Clip it to asked size only!
3030         QPainter painter2(&pixmap);
3031         QTextDocument rt;
3032         rt.setHtml(QString("<center>%1</center>").arg(i18n("Loading...")));
3033         rt.setTextWidth(m_view->viewport()->width());
3034         int hrt = rt.size().height();
3035         painter2.fillRect(0, 0, m_view->viewport()->width(), m_view->viewport()->height(), brush);
3036         blendBackground(painter2, QRectF(0, 0, m_view->viewport()->width(), m_view->viewport()->height()), -1, -1, /*opaque=*/true);
3037         QPalette pal = palette();
3038         pal.setColor(QPalette::WindowText, textColor());
3039         painter2.translate(0, (m_view->viewport()->height() - hrt) / 2);
3040         QAbstractTextDocumentLayout::PaintContext context;
3041         context.palette = pal;
3042         rt.documentLayout()->draw(&painter2, context);
3043         painter2.end();
3044         painter->drawPixmap(0, 0, pixmap);
3045         return; // TODO: Clip to the wanted rectangle
3046     }
3047 
3048     enableActions();
3049 
3050     if ((inserterShown() && rect.intersects(inserterRect())) || (m_isSelecting && rect.intersects(m_selectionRect))) {
3051         // Draw inserter:
3052         if (inserterShown() && rect.intersects(inserterRect())) {
3053             drawInserter(*painter, 0, 0);
3054         }
3055         // Draw selection rect:
3056         if (m_isSelecting && rect.intersects(m_selectionRect)) {
3057             QRectF selectionRect = m_selectionRect;
3058             QRectF selectionRectInside(selectionRect.x() + 1, selectionRect.y() + 1, selectionRect.width() - 2, selectionRect.height() - 2);
3059             if (selectionRectInside.width() > 0 && selectionRectInside.height() > 0) {
3060                 QColor insideColor = selectionRectInsideColor();
3061                 painter->fillRect(selectionRectInside, QBrush(insideColor, Qt::Dense4Pattern));
3062             }
3063             painter->setPen(palette().color(QPalette::Highlight).darker());
3064             painter->drawRect(selectionRect);
3065             painter->setPen(Tools::mixColor(palette().color(QPalette::Highlight).darker(), backgroundColor()));
3066             painter->drawPoint(selectionRect.topLeft());
3067             painter->drawPoint(selectionRect.topRight());
3068             painter->drawPoint(selectionRect.bottomLeft());
3069             painter->drawPoint(selectionRect.bottomRight());
3070         }
3071     }
3072 }
3073 
3074 /*  rect(x,y,width,height)==(xBackgroundToDraw,yBackgroundToDraw,widthToDraw,heightToDraw)
3075  */
3076 void BasketScene::blendBackground(QPainter &painter, const QRectF &rect, qreal xPainter, qreal yPainter, bool opaque, QPixmap *bg)
3077 {
3078     painter.save();
3079     if (xPainter == -1 && yPainter == -1) {
3080         xPainter = rect.x();
3081         yPainter = rect.y();
3082     }
3083 
3084     if (hasBackgroundImage()) {
3085         const QPixmap *bgPixmap = (bg ? /* * */ bg : (opaque ? m_opaqueBackgroundPixmap : m_backgroundPixmap));
3086         if (isTiledBackground()) {
3087             painter.drawTiledPixmap(rect.x() - xPainter, rect.y() - yPainter, rect.width(), rect.height(), *bgPixmap, rect.x(), rect.y());
3088         } else {
3089             painter.drawPixmap(QPointF(rect.x() - xPainter, rect.y() - yPainter), *bgPixmap, rect);
3090         }
3091     }
3092     painter.restore();
3093 }
3094 
3095 void BasketScene::recomputeBlankRects()
3096 {
3097     m_blankAreas.clear();
3098     return;
3099 
3100     m_blankAreas.append(QRectF(0, 0, sceneRect().width(), sceneRect().height()));
3101 
3102     FOR_EACH_NOTE(note)
3103     note->recomputeBlankRects(m_blankAreas);
3104 
3105     // See the drawing of blank areas in BasketScene::drawContents()
3106     if (hasBackgroundImage() && !isTiledBackground())
3107         substractRectOnAreas(QRectF(0, 0, backgroundPixmap()->width(), backgroundPixmap()->height()), m_blankAreas, false);
3108 }
3109 
3110 void BasketScene::unsetNotesWidth()
3111 {
3112     Note *note = m_firstNote;
3113     while (note) {
3114         note->unsetWidth();
3115         note = note->next();
3116     }
3117 }
3118 
3119 void BasketScene::relayoutNotes()
3120 {
3121     if (Global::bnpView->currentBasket() != this)
3122         return; // Optimize load time, and basket will be relaid out when activated, anyway
3123 
3124     int h = 0;
3125     tmpWidth = 0;
3126     tmpHeight = 0;
3127     Note *note = m_firstNote;
3128     while (note) {
3129         if (note->matching()) {
3130             note->relayoutAt(0, h);
3131             if (note->hasResizer()) {
3132                 int minGroupWidth = note->minRight() - note->x();
3133                 if (note->groupWidth() < minGroupWidth) {
3134                     note->setGroupWidth(minGroupWidth);
3135                     relayoutNotes(); // Redo the thing, but this time it should not recurse
3136                     return;
3137                 }
3138             }
3139             h += note->height();
3140         }
3141         note = note->next();
3142     }
3143 
3144     if (isFreeLayout())
3145         tmpHeight += 100;
3146     else
3147         tmpHeight += 15;
3148 
3149     setSceneRect(0, 0, qMax((qreal)m_view->viewport()->width(), tmpWidth), qMax((qreal)m_view->viewport()->height(), tmpHeight));
3150 
3151     recomputeBlankRects();
3152     placeEditor();
3153     doHoverEffects();
3154     invalidate();
3155 }
3156 
3157 void BasketScene::popupEmblemMenu(Note *note, int emblemNumber)
3158 {
3159     m_tagPopupNote = note;
3160     State *state = note->stateForEmblemNumber(emblemNumber);
3161     State *nextState = state->nextState(/*cycle=*/false);
3162     Tag *tag = state->parentTag();
3163     m_tagPopup = tag;
3164 
3165     QKeySequence sequence = tag->shortcut();
3166     bool sequenceOnDelete = (nextState == nullptr && !tag->shortcut().isEmpty());
3167 
3168     QMenu menu(m_view);
3169     if (tag->countStates() == 1) {
3170         menu.addSection(/*SmallIcon(state->icon()), */ tag->name());
3171         QAction *act;
3172         act = new QAction(QIcon::fromTheme("edit-delete"), i18n("&Remove"), &menu);
3173         act->setData(1);
3174         menu.addAction(act);
3175         act = new QAction(QIcon::fromTheme("configure"), i18n("&Customize..."), &menu);
3176         act->setData(2);
3177         menu.addAction(act);
3178 
3179         menu.addSeparator();
3180 
3181         act = new QAction(QIcon::fromTheme("search-filter"), i18n("&Filter by this Tag"), &menu);
3182         act->setData(3);
3183         menu.addAction(act);
3184     } else {
3185         menu.addSection(tag->name());
3186         QList<State *>::iterator it;
3187         State *currentState;
3188 
3189         int i = 10;
3190         // QActionGroup makes the actions exclusive; turns checkboxes into radio
3191         // buttons on some styles.
3192         QActionGroup *emblemGroup = new QActionGroup(&menu);
3193         for (it = tag->states().begin(); it != tag->states().end(); ++it) {
3194             currentState = *it;
3195             QKeySequence sequence;
3196             if (currentState == nextState && !tag->shortcut().isEmpty())
3197                 sequence = tag->shortcut();
3198 
3199             StateAction *sa = new StateAction(currentState, QKeySequence(sequence), nullptr, false);
3200             sa->setChecked(state == currentState);
3201             sa->setActionGroup(emblemGroup);
3202             sa->setData(i);
3203 
3204             menu.addAction(sa);
3205             if (currentState == nextState && !tag->shortcut().isEmpty())
3206                 sa->setShortcut(sequence);
3207             ++i;
3208         }
3209 
3210         menu.addSeparator();
3211 
3212         QAction *act = new QAction(&menu);
3213         act->setIcon(QIcon::fromTheme("edit-delete"));
3214         act->setText(i18n("&Remove"));
3215         act->setShortcut(sequenceOnDelete ? sequence : QKeySequence());
3216         act->setData(1);
3217         menu.addAction(act);
3218         act = new QAction(QIcon::fromTheme("configure"), i18n("&Customize..."), &menu);
3219         act->setData(2);
3220         menu.addAction(act);
3221 
3222         menu.addSeparator();
3223 
3224         act = new QAction(QIcon::fromTheme("search-filter"), i18n("&Filter by this Tag"), &menu);
3225         act->setData(3);
3226         menu.addAction(act);
3227         act = new QAction(QIcon::fromTheme("search-filter"), i18n("Filter by this &State"), &menu);
3228         act->setData(4);
3229         menu.addAction(act);
3230     }
3231 
3232     connect(&menu, &QMenu::triggered, this, &BasketScene::toggledStateInMenu);
3233     connect(&menu, &QMenu::aboutToHide, this, &BasketScene::unlockHovering);
3234     connect(&menu, &QMenu::aboutToHide, this, &BasketScene::disableNextClick);
3235 
3236     m_lockedHovering = true;
3237     menu.exec(QCursor::pos());
3238 }
3239 
3240 void BasketScene::toggledStateInMenu(QAction *action)
3241 {
3242     int id = action->data().toInt();
3243     if (id == 1) {
3244         removeTagFromSelectedNotes(m_tagPopup);
3245         // m_tagPopupNote->removeTag(m_tagPopup);
3246         // m_tagPopupNote->setWidth(0); // To force a new layout computation
3247         updateEditorAppearance();
3248         filterAgain();
3249         save();
3250         return;
3251     }
3252     if (id == 2) { // Customize this State:
3253         TagsEditDialog dialog(m_view, m_tagPopupNote->stateOfTag(m_tagPopup));
3254         dialog.exec();
3255         return;
3256     }
3257     if (id == 3) { // Filter by this Tag
3258         decoration()->filterBar()->filterTag(m_tagPopup);
3259         return;
3260     }
3261     if (id == 4) { // Filter by this State
3262         decoration()->filterBar()->filterState(m_tagPopupNote->stateOfTag(m_tagPopup));
3263         return;
3264     }
3265 
3266     /*addStateToSelectedNotes*/ changeStateOfSelectedNotes(m_tagPopup->states()[id - 10] /*, orReplace=true*/);
3267     // m_tagPopupNote->addState(m_tagPopup->states()[id - 10], true);
3268     filterAgain();
3269     save();
3270 }
3271 
3272 State *BasketScene::stateForTagFromSelectedNotes(Tag *tag)
3273 {
3274     State *state = nullptr;
3275 
3276     FOR_EACH_NOTE(note)
3277     if (note->stateForTagFromSelectedNotes(tag, &state) && state == nullptr)
3278         return nullptr;
3279     return state;
3280 }
3281 
3282 void BasketScene::activatedTagShortcut(Tag *tag)
3283 {
3284     // Compute the next state to set:
3285     State *state = stateForTagFromSelectedNotes(tag);
3286     if (state)
3287         state = state->nextState(/*cycle=*/false);
3288     else
3289         state = tag->states().first();
3290 
3291     // Set or unset it:
3292     if (state) {
3293         FOR_EACH_NOTE(note)
3294         note->addStateToSelectedNotes(state, /*orReplace=*/true);
3295         updateEditorAppearance();
3296     } else
3297         removeTagFromSelectedNotes(tag);
3298 
3299     filterAgain();
3300     save();
3301 }
3302 
3303 void BasketScene::popupTagsMenu(Note *note)
3304 {
3305     m_tagPopupNote = note;
3306 
3307     QMenu menu(m_view);
3308     menu.addSection(i18n("Tags"));
3309 
3310     Global::bnpView->populateTagsMenu(menu, note);
3311 
3312     m_lockedHovering = true;
3313     menu.exec(QCursor::pos());
3314 }
3315 
3316 void BasketScene::unlockHovering()
3317 {
3318     m_lockedHovering = false;
3319     doHoverEffects();
3320 }
3321 
3322 void BasketScene::toggledTagInMenu(QAction *act)
3323 {
3324     int id = act->data().toInt();
3325     if (id == 1) { // Assign new Tag...
3326         TagsEditDialog dialog(m_view, /*stateToEdit=*/nullptr, /*addNewTag=*/true);
3327         dialog.exec();
3328         if (!dialog.addedStates().isEmpty()) {
3329             State::List states = dialog.addedStates();
3330             for (State::List::iterator itState = states.begin(); itState != states.end(); ++itState)
3331                 FOR_EACH_NOTE(note)
3332             note->addStateToSelectedNotes(*itState);
3333             updateEditorAppearance();
3334             filterAgain();
3335             save();
3336         }
3337         return;
3338     }
3339     if (id == 2) { // Remove All
3340         removeAllTagsFromSelectedNotes();
3341         filterAgain();
3342         save();
3343         return;
3344     }
3345     if (id == 3) { // Customize...
3346         TagsEditDialog dialog(m_view);
3347         dialog.exec();
3348         return;
3349     }
3350 
3351     Tag *tag = Tag::all[id - 10];
3352     if (!tag)
3353         return;
3354 
3355     if (m_tagPopupNote->hasTag(tag))
3356         removeTagFromSelectedNotes(tag);
3357     else
3358         addTagToSelectedNotes(tag);
3359     m_tagPopupNote->setWidth(0); // To force a new layout computation
3360     filterAgain();
3361     save();
3362 }
3363 
3364 void BasketScene::addTagToSelectedNotes(Tag *tag)
3365 {
3366     FOR_EACH_NOTE(note)
3367     note->addTagToSelectedNotes(tag);
3368     updateEditorAppearance();
3369 }
3370 
3371 void BasketScene::removeTagFromSelectedNotes(Tag *tag)
3372 {
3373     FOR_EACH_NOTE(note)
3374     note->removeTagFromSelectedNotes(tag);
3375     updateEditorAppearance();
3376 }
3377 
3378 void BasketScene::addStateToSelectedNotes(State *state)
3379 {
3380     FOR_EACH_NOTE(note)
3381     note->addStateToSelectedNotes(state);
3382     updateEditorAppearance();
3383 }
3384 
3385 void BasketScene::updateEditorAppearance()
3386 {
3387     if (isDuringEdit() && m_editor->graphicsWidget()) {
3388         m_editor->graphicsWidget()->setFont(m_editor->note()->font());
3389 
3390         if (m_editor->graphicsWidget()->widget()) {
3391             QPalette palette;
3392             palette.setColor(m_editor->graphicsWidget()->widget()->backgroundRole(), m_editor->note()->backgroundColor());
3393             palette.setColor(m_editor->graphicsWidget()->widget()->foregroundRole(), m_editor->note()->textColor());
3394             m_editor->graphicsWidget()->setPalette(palette);
3395         }
3396 
3397         // Ugly Hack around Qt bugs: placeCursor() don't call any signal:
3398         HtmlEditor *htmlEditor = dynamic_cast<HtmlEditor *>(m_editor);
3399         if (htmlEditor) {
3400             if (m_editor->textEdit()->textCursor().atStart()) {
3401                 m_editor->textEdit()->moveCursor(QTextCursor::Right);
3402                 m_editor->textEdit()->moveCursor(QTextCursor::Left);
3403             } else {
3404                 m_editor->textEdit()->moveCursor(QTextCursor::Left);
3405                 m_editor->textEdit()->moveCursor(QTextCursor::Right);
3406             }
3407             htmlEditor->cursorPositionChanged(); // Does not work anyway :-( (when clicking on a red bold text, the toolbar still show black normal text)
3408         }
3409     }
3410 }
3411 
3412 void BasketScene::editorPropertiesChanged()
3413 {
3414     if (isDuringEdit() && m_editor->note()->content()->type() == NoteType::Html) {
3415         m_editor->textEdit()->setAutoFormatting(Settings::autoBullet() ? QTextEdit::AutoAll : QTextEdit::AutoNone);
3416     }
3417 }
3418 
3419 void BasketScene::changeStateOfSelectedNotes(State *state)
3420 {
3421     FOR_EACH_NOTE(note)
3422     note->changeStateOfSelectedNotes(state);
3423     updateEditorAppearance();
3424 }
3425 
3426 void BasketScene::removeAllTagsFromSelectedNotes()
3427 {
3428     FOR_EACH_NOTE(note)
3429     note->removeAllTagsFromSelectedNotes();
3430     updateEditorAppearance();
3431 }
3432 
3433 bool BasketScene::selectedNotesHaveTags()
3434 {
3435     FOR_EACH_NOTE(note)
3436     if (note->selectedNotesHaveTags())
3437         return true;
3438     return false;
3439 }
3440 
3441 QColor BasketScene::backgroundColor() const
3442 {
3443     if (m_backgroundColorSetting.isValid())
3444         return m_backgroundColorSetting;
3445     else
3446         return palette().color(QPalette::Base);
3447 }
3448 
3449 QColor BasketScene::textColor() const
3450 {
3451     if (m_textColorSetting.isValid())
3452         return m_textColorSetting;
3453     else
3454         return palette().color(QPalette::Text);
3455 }
3456 
3457 void BasketScene::unbufferizeAll()
3458 {
3459     FOR_EACH_NOTE(note)
3460     note->unbufferizeAll();
3461 }
3462 
3463 Note *BasketScene::editedNote()
3464 {
3465     if (m_editor)
3466         return m_editor->note();
3467     else
3468         return nullptr;
3469 }
3470 
3471 bool BasketScene::hasTextInEditor()
3472 {
3473     if (!isDuringEdit() || !redirectEditActions())
3474         return false;
3475 
3476     if (m_editor->textEdit())
3477         return !m_editor->textEdit()->document()->isEmpty();
3478     else if (m_editor->lineEdit())
3479         return !m_editor->lineEdit()->displayText().isEmpty();
3480     else
3481         return false;
3482 }
3483 
3484 bool BasketScene::hasSelectedTextInEditor()
3485 {
3486     if (!isDuringEdit() || !redirectEditActions())
3487         return false;
3488 
3489     if (m_editor->textEdit()) {
3490         // The following line does NOT work if one letter is selected and the user press Shift+Left or Shift+Right to unselect than letter:
3491         // Qt mysteriously tell us there is an invisible selection!!
3492         // return m_editor->textEdit()->hasSelectedText();
3493         return !m_editor->textEdit()->textCursor().selectedText().isEmpty();
3494     } else if (m_editor->lineEdit())
3495         return m_editor->lineEdit()->hasSelectedText();
3496     else
3497         return false;
3498 }
3499 
3500 bool BasketScene::selectedAllTextInEditor()
3501 {
3502     if (!isDuringEdit() || !redirectEditActions())
3503         return false;
3504 
3505     if (m_editor->textEdit()) {
3506         return m_editor->textEdit()->document()->isEmpty() || m_editor->textEdit()->toPlainText() == m_editor->textEdit()->textCursor().selectedText();
3507     } else if (m_editor->lineEdit())
3508         return m_editor->lineEdit()->displayText().isEmpty() || m_editor->lineEdit()->displayText() == m_editor->lineEdit()->selectedText();
3509     else
3510         return false;
3511 }
3512 
3513 void BasketScene::selectionChangedInEditor()
3514 {
3515     Global::bnpView->notesStateChanged();
3516 }
3517 
3518 void BasketScene::contentChangedInEditor()
3519 {
3520     // Do not wait 3 seconds, because we need the note to expand as needed (if a line is too wider... the note should grow wider):
3521     if (m_editor->textEdit())
3522         m_editor->autoSave(/*toFileToo=*/false);
3523     //  else {
3524     if (m_inactivityAutoSaveTimer.isActive())
3525         m_inactivityAutoSaveTimer.stop();
3526     m_inactivityAutoSaveTimer.setSingleShot(true);
3527     m_inactivityAutoSaveTimer.start(3 * 1000);
3528     Global::bnpView->setUnsavedStatus(true);
3529     //  }
3530 }
3531 
3532 void BasketScene::inactivityAutoSaveTimeout()
3533 {
3534     if (m_editor)
3535         m_editor->autoSave(/*toFileToo=*/true);
3536 }
3537 
3538 void BasketScene::placeEditorAndEnsureVisible()
3539 {
3540     placeEditor(/*andEnsureVisible=*/true);
3541 }
3542 
3543 // TODO: [kw] Oh boy, this will probably require some tweaking.
3544 void BasketScene::placeEditor(bool /*andEnsureVisible*/ /*= false*/)
3545 {
3546     if (!isDuringEdit())
3547         return;
3548 
3549     QFrame *editorQFrame = dynamic_cast<QFrame *>(m_editor->graphicsWidget()->widget());
3550     KTextEdit *textEdit = m_editor->textEdit();
3551     Note *note = m_editor->note();
3552 
3553     qreal frameWidth = (editorQFrame ? editorQFrame->frameWidth() : 0);
3554     qreal x = note->x() + note->contentX() + note->content()->xEditorIndent() - frameWidth;
3555     qreal y;
3556     qreal maxHeight = qMax((qreal)m_view->viewport()->height(), sceneRect().height());
3557     qreal height, width;
3558 
3559     if (textEdit) {
3560         // Need to do it 2 times, because it's wrong otherwise
3561         // (sometimes, width depends on height, and sometimes, height depends on with):
3562         for (int i = 0; i < 2; i++) {
3563             // FIXME: CRASH: Select all text, press Del or [<--] and editor->removeSelectedText() is called:
3564             //        editor->sync() CRASH!!
3565             //      editor->sync();
3566             y = note->y() + Note::NOTE_MARGIN - frameWidth;
3567             height = note->height() - 2 * frameWidth - 2 * Note::NOTE_MARGIN;
3568             width = note->x() + note->width() - x + 1;
3569             if (y + height > maxHeight)
3570                 y = maxHeight - height;
3571 
3572             m_editor->graphicsWidget()->setMaximumSize(width, height);
3573             textEdit->setFixedSize(width, height);
3574             textEdit->viewport()->setFixedSize(width, height);
3575         }
3576     } else {
3577         height = note->height() - 2 * Note::NOTE_MARGIN + 2 * frameWidth;
3578         width = note->x() + note->width() - x; // note->rightLimit() - x + 2*m_view->frameWidth;
3579         if (m_editor->graphicsWidget())
3580             m_editor->graphicsWidget()->widget()->setFixedSize(width, height);
3581         x -= 1;
3582         y = note->y() + Note::NOTE_MARGIN - frameWidth;
3583     }
3584     if ((m_editorWidth > 0 && m_editorWidth != width) || (m_editorHeight > 0 && m_editorHeight != height)) {
3585         m_editorWidth = width; // Avoid infinite recursion!!!
3586         m_editorHeight = height;
3587         m_editor->autoSave(/*toFileToo=*/true);
3588     }
3589     m_editorWidth = width;
3590     m_editorHeight = height;
3591     m_editor->graphicsWidget()->setPos(x, y);
3592     m_editorX = x;
3593     m_editorY = y;
3594 
3595     //  if (andEnsureVisible)
3596     //      ensureNoteVisible(note);
3597 }
3598 
3599 void BasketScene::editorCursorPositionChanged()
3600 {
3601     if (!isDuringEdit())
3602         return;
3603 
3604     FocusedTextEdit *textEdit = dynamic_cast<FocusedTextEdit *>(m_editor->textEdit());
3605 
3606     if (textEdit) {
3607         QPoint cursorPoint = textEdit->viewport()->mapToGlobal(textEdit->cursorRect().center());
3608 
3609         // QPointF contentsCursor = m_view->mapToScene( m_view->viewport()->mapFromGlobal(cursorPoint) );
3610         // m_view->ensureVisible(contentsCursor.x(), contentsCursor.y(),1,1);
3611     }
3612 }
3613 
3614 void BasketScene::closeEditorDelayed()
3615 {
3616     setFocus();
3617     QTimer::singleShot(0, this, SLOT(closeEditor()));
3618 }
3619 
3620 bool BasketScene::closeEditor(bool deleteEmptyNote /* =true*/)
3621 {
3622     if (!isDuringEdit())
3623         return true;
3624 
3625     if (m_doNotCloseEditor)
3626         return true;
3627 
3628     if (m_redirectEditActions) {
3629         if (m_editor->textEdit()) {
3630             disconnect(m_editor->textEdit(), &KTextEdit::selectionChanged, this, &BasketScene::selectionChangedInEditor);
3631             disconnect(m_editor->textEdit(), &KTextEdit::textChanged, this, &BasketScene::selectionChangedInEditor);
3632             disconnect(m_editor->textEdit(), &KTextEdit::textChanged, this, &BasketScene::contentChangedInEditor);
3633         } else if (m_editor->lineEdit()) {
3634             disconnect(m_editor->lineEdit(), &QLineEdit::selectionChanged, this, &BasketScene::selectionChangedInEditor);
3635             disconnect(m_editor->lineEdit(), &QLineEdit::textChanged, this, &BasketScene::selectionChangedInEditor);
3636             disconnect(m_editor->lineEdit(), &QLineEdit::textChanged, this, &BasketScene::contentChangedInEditor);
3637         }
3638     }
3639     m_editorTrackMouseEvent = false;
3640     m_editor->graphicsWidget()->widget()->disconnect();
3641     removeItem(m_editor->graphicsWidget());
3642     m_editor->validate();
3643 
3644     Note *note = m_editor->note();
3645 
3646     // Delete the editor BEFORE unselecting the note because unselecting the note would trigger closeEditor() recursivly:
3647     bool isEmpty = m_editor->isEmpty();
3648     delete m_editor;
3649 
3650     m_editor = nullptr;
3651     m_redirectEditActions = false;
3652     m_editorWidth = -1;
3653     m_editorHeight = -1;
3654     m_inactivityAutoSaveTimer.stop();
3655 
3656     // Delete the note if it is now empty:
3657     if (isEmpty && deleteEmptyNote) {
3658         focusANonSelectedNoteAboveOrThenBelow();
3659         note->setSelected(true);
3660         note->deleteSelectedNotes();
3661         if (m_hoveredNote == note)
3662             m_hoveredNote = nullptr;
3663         if (m_focusedNote == note)
3664             m_focusedNote = nullptr;
3665         delete note;
3666         save();
3667         note = nullptr;
3668     }
3669 
3670     unlockHovering();
3671     filterAgain(/*andEnsureVisible=*/false);
3672 
3673     // Does not work:
3674     //  if (Settings::playAnimations())
3675     //      note->setOnTop(true); // So if it grew, do not obscure it temporarily while the notes below it are moving
3676 
3677     if (note)
3678         note->setSelected(false); // unselectAll();
3679     doHoverEffects();
3680     //  save();
3681 
3682     Global::bnpView->m_actEditNote->setEnabled(!isLocked() && countSelecteds() == 1 /*&& !isDuringEdit()*/);
3683 
3684     Q_EMIT resetStatusBarText(); // Remove the "Editing. ... to validate." text.
3685 
3686     // if (qApp->activeWindow() == Global::mainContainer)
3687 
3688     // Set focus to the basket, unless the user pressed a letter key in the filter bar and the currently edited note came hidden, then editing closed:
3689     if (!decoration()->filterBar()->lineEdit()->hasFocus())
3690         setFocus();
3691 
3692     // Return true if the note is still there:
3693     return (note != nullptr);
3694 }
3695 
3696 void BasketScene::closeBasket()
3697 {
3698     closeEditor();
3699     unbufferizeAll(); // Keep the memory footprint low
3700     if (isEncrypted()) {
3701         if (Settings::enableReLockTimeout()) {
3702             int seconds = Settings::reLockTimeoutMinutes() * 60;
3703             m_inactivityAutoLockTimer.setSingleShot(true);
3704             m_inactivityAutoLockTimer.start(seconds * 1000);
3705         }
3706     }
3707 }
3708 
3709 void BasketScene::openBasket()
3710 {
3711     if (m_inactivityAutoLockTimer.isActive())
3712         m_inactivityAutoLockTimer.stop();
3713 }
3714 
3715 Note *BasketScene::theSelectedNote()
3716 {
3717     if (countSelecteds() != 1) {
3718         qDebug() << "NO SELECTED NOTE !!!!";
3719         return nullptr;
3720     }
3721 
3722     Note *selectedOne;
3723     FOR_EACH_NOTE(note)
3724     {
3725         selectedOne = note->theSelectedNote();
3726         if (selectedOne)
3727             return selectedOne;
3728     }
3729 
3730     qDebug() << "One selected note, BUT NOT FOUND !!!!";
3731 
3732     return nullptr;
3733 }
3734 
3735 NoteSelection *BasketScene::selectedNotes()
3736 {
3737     NoteSelection selection;
3738 
3739     FOR_EACH_NOTE(note)
3740     selection.append(note->selectedNotes());
3741 
3742     if (!selection.firstChild)
3743         return nullptr;
3744 
3745     for (NoteSelection *node = selection.firstChild; node; node = node->next)
3746         node->parent = nullptr;
3747 
3748     // If the top-most groups are columns, export only children of those groups
3749     // (because user is not aware that columns are groups, and don't care: it's not what she want):
3750     if (selection.firstChild->note->isColumn()) {
3751         NoteSelection tmpSelection;
3752         NoteSelection *nextNode;
3753         NoteSelection *nextSubNode;
3754         for (NoteSelection *node = selection.firstChild; node; node = nextNode) {
3755             nextNode = node->next;
3756             if (node->note->isColumn()) {
3757                 for (NoteSelection *subNode = node->firstChild; subNode; subNode = nextSubNode) {
3758                     nextSubNode = subNode->next;
3759                     tmpSelection.append(subNode);
3760                     subNode->parent = nullptr;
3761                     subNode->next = nullptr;
3762                 }
3763             } else {
3764                 tmpSelection.append(node);
3765                 node->parent = nullptr;
3766                 node->next = nullptr;
3767             }
3768         }
3769         //      debugSel(tmpSelection.firstChild);
3770         return tmpSelection.firstChild;
3771     } else {
3772         //      debugSel(selection.firstChild);
3773         return selection.firstChild;
3774     }
3775 }
3776 
3777 void BasketScene::showEditedNoteWhileFiltering()
3778 {
3779     if (m_editor) {
3780         Note *note = m_editor->note();
3781         filterAgain();
3782         note->setSelected(true);
3783         relayoutNotes();
3784         note->setX(note->x());
3785         note->setY(note->y());
3786         filterAgainDelayed();
3787     }
3788 }
3789 
3790 void BasketScene::noteEdit(Note *note, bool justAdded, const QPointF &clickedPoint) // TODO: Remove the first parameter!!!
3791 {
3792     if (!note)
3793         note = theSelectedNote(); // TODO: Or pick the focused note!
3794     if (!note)
3795         return;
3796 
3797     if (isDuringEdit()) {
3798         closeEditor(); // Validate the noteeditors in QLineEdit that does not intercept Enter key press (and edit is triggered with Enter too... Can conflict)
3799         return;
3800     }
3801 
3802     if (note != m_focusedNote) {
3803         setFocusedNote(note);
3804         m_startOfShiftSelectionNote = note;
3805     }
3806 
3807     if (justAdded && isFiltering()) {
3808         QTimer::singleShot(0, this, SLOT(showEditedNoteWhileFiltering()));
3809     }
3810 
3811     doHoverEffects(note, Note::Content); // Be sure (in the case Edit was triggered by menu or Enter key...): better feedback!
3812 
3813     NoteEditor *editor = NoteEditor::editNoteContent(note->content(), nullptr);
3814     if (editor->graphicsWidget()) {
3815         m_editor = editor;
3816 
3817         addItem(m_editor->graphicsWidget());
3818 
3819         placeEditorAndEnsureVisible(); //       placeEditor(); // FIXME: After?
3820         m_redirectEditActions = m_editor->lineEdit() || m_editor->textEdit();
3821         if (m_redirectEditActions) {
3822             // In case there is NO text, "Select All" is disabled. But if the user press a key the there is now a text:
3823             // selection has not changed but "Select All" should be re-enabled:
3824             m_editor->connectActions(this);
3825         }
3826 
3827         m_editor->graphicsWidget()->setFocus();
3828         connect(m_editor, &NoteEditor::askValidation, this, &BasketScene::closeEditorDelayed);
3829         connect(m_editor, &NoteEditor::mouseEnteredEditorWidget, this, &BasketScene::mouseEnteredEditorWidget);
3830 
3831         if (clickedPoint != QPoint()) {
3832             m_editor->setCursorTo(clickedPoint);
3833             updateEditorAppearance();
3834         }
3835 
3836         //      qApp->processEvents();     // Show the editor toolbar before ensuring the note is visible
3837         ensureNoteVisible(note);                //  because toolbar can create a new line and then partially hide the note
3838         m_editor->graphicsWidget()->setFocus(); // When clicking in the basket, a QTimer::singleShot(0, ...) focus the basket! So we focus the widget after qApp->processEvents()
3839         Q_EMIT resetStatusBarText();              // Display "Editing. ... to validate."
3840     } else {
3841         // Delete the note user have canceled the addition:
3842         if ((justAdded && editor->canceled()) || editor->isEmpty() /*) && editor->note()->states().count() <= 0*/) {
3843             focusANonSelectedNoteAboveOrThenBelow();
3844             editor->note()->setSelected(true);
3845             editor->note()->deleteSelectedNotes();
3846             if (m_hoveredNote == editor->note())
3847                 m_hoveredNote = nullptr;
3848             if (m_focusedNote == editor->note())
3849                 m_focusedNote = nullptr;
3850             delete editor->note();
3851             save();
3852         }
3853         editor->deleteLater();
3854         unlockHovering();
3855         filterAgain();
3856         unselectAll();
3857     }
3858     // Must set focus to the editor, otherwise edit cursor is not seen and precomposed characters cannot be entered
3859     if (m_editor != nullptr && m_editor->textEdit() != nullptr)
3860         m_editor->textEdit()->setFocus();
3861 
3862     Global::bnpView->m_actEditNote->setEnabled(false);
3863 }
3864 
3865 void BasketScene::noteDelete()
3866 {
3867     if (redirectEditActions()) {
3868         if (m_editor->textEdit())
3869             m_editor->textEdit()->textCursor().deleteChar();
3870         else if (m_editor->lineEdit())
3871             m_editor->lineEdit()->del();
3872         return;
3873     }
3874 
3875     if (countSelecteds() <= 0)
3876         return;
3877     int really = KMessageBox::Yes;
3878     if (Settings::confirmNoteDeletion())
3879         really = KMessageBox::questionYesNo(m_view,
3880                                             i18np("<qt>Do you really want to delete this note?</qt>", "<qt>Do you really want to delete these <b>%1</b> notes?</qt>", countSelecteds()),
3881                                             i18np("Delete Note", "Delete Notes", countSelecteds()),
3882                                             KStandardGuiItem::del(),
3883                                             KStandardGuiItem::cancel());
3884     if (really == KMessageBox::No)
3885         return;
3886 
3887     noteDeleteWithoutConfirmation();
3888 }
3889 
3890 void BasketScene::focusANonSelectedNoteBelow(bool inSameColumn)
3891 {
3892     // First focus another unselected one below it...:
3893     if (m_focusedNote && m_focusedNote->isSelected()) {
3894         Note *next = m_focusedNote->nextShownInStack();
3895         while (next && next->isSelected())
3896             next = next->nextShownInStack();
3897         if (next) {
3898             if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == next->parentPrimaryNote()) {
3899                 setFocusedNote(next);
3900                 m_startOfShiftSelectionNote = next;
3901             }
3902         }
3903     }
3904 }
3905 
3906 void BasketScene::focusANonSelectedNoteAbove(bool inSameColumn)
3907 {
3908     // ... Or above it:
3909     if (m_focusedNote && m_focusedNote->isSelected()) {
3910         Note *prev = m_focusedNote->prevShownInStack();
3911         while (prev && prev->isSelected())
3912             prev = prev->prevShownInStack();
3913         if (prev) {
3914             if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == prev->parentPrimaryNote()) {
3915                 setFocusedNote(prev);
3916                 m_startOfShiftSelectionNote = prev;
3917             }
3918         }
3919     }
3920 }
3921 
3922 void BasketScene::focusANonSelectedNoteBelowOrThenAbove()
3923 {
3924     focusANonSelectedNoteBelow(/*inSameColumn=*/true);
3925     focusANonSelectedNoteAbove(/*inSameColumn=*/true);
3926     focusANonSelectedNoteBelow(/*inSameColumn=*/false);
3927     focusANonSelectedNoteAbove(/*inSameColumn=*/false);
3928 }
3929 
3930 void BasketScene::focusANonSelectedNoteAboveOrThenBelow()
3931 {
3932     focusANonSelectedNoteAbove(/*inSameColumn=*/true);
3933     focusANonSelectedNoteBelow(/*inSameColumn=*/true);
3934     focusANonSelectedNoteAbove(/*inSameColumn=*/false);
3935     focusANonSelectedNoteBelow(/*inSameColumn=*/false);
3936 }
3937 
3938 void BasketScene::noteDeleteWithoutConfirmation(bool deleteFilesToo)
3939 {
3940     // If the currently focused note is selected, it will be deleted.
3941     focusANonSelectedNoteBelowOrThenAbove();
3942 
3943     // Do the deletion:
3944     Note *note = firstNote();
3945     Note *next;
3946     while (note) {
3947         next = note->next(); // If we delete 'note' on the next line, note->next() will be 0!
3948         note->deleteSelectedNotes(deleteFilesToo, &m_notesToBeDeleted);
3949         note = next;
3950     }
3951 
3952     if (!m_notesToBeDeleted.isEmpty()) {
3953         doCleanUp();
3954     }
3955 
3956     relayoutNotes(); // FIXME: filterAgain()?
3957     save();
3958 }
3959 
3960 void BasketScene::doCopy(CopyMode copyMode)
3961 {
3962     QClipboard *cb = QApplication::clipboard();
3963     QClipboard::Mode mode = ((copyMode == CopyToSelection) ? QClipboard::Selection : QClipboard::Clipboard);
3964 
3965     NoteSelection *selection = selectedNotes();
3966     int countCopied = countSelecteds();
3967     if (selection->firstStacked()) {
3968         QDrag *d = NoteDrag::dragObject(selection, copyMode == CutToClipboard, /*source=*/nullptr); // d will be deleted by QT
3969 
3970         cb->setMimeData(d->mimeData(), mode);
3971 
3972         if (copyMode == CutToClipboard) {
3973             noteDeleteWithoutConfirmation(/*deleteFilesToo=*/false);
3974             focusANote();
3975         }
3976 
3977         switch (copyMode) {
3978         default:
3979         case CopyToClipboard:
3980             Q_EMIT postMessage(i18np("Copied note to clipboard.", "Copied notes to clipboard.", countCopied));
3981             break;
3982         case CutToClipboard:
3983             Q_EMIT postMessage(i18np("Cut note to clipboard.", "Cut notes to clipboard.", countCopied));
3984             break;
3985         case CopyToSelection:
3986             Q_EMIT postMessage(i18np("Copied note to selection.", "Copied notes to selection.", countCopied));
3987             break;
3988         }
3989     }
3990 }
3991 
3992 void BasketScene::noteCopy()
3993 {
3994     if (redirectEditActions()) {
3995         if (m_editor->textEdit())
3996             m_editor->textEdit()->copy();
3997         else if (m_editor->lineEdit())
3998             m_editor->lineEdit()->copy();
3999     } else
4000         doCopy(CopyToClipboard);
4001 }
4002 
4003 void BasketScene::noteCut()
4004 {
4005     if (redirectEditActions()) {
4006         if (m_editor->textEdit())
4007             m_editor->textEdit()->cut();
4008         else if (m_editor->lineEdit())
4009             m_editor->lineEdit()->cut();
4010     } else
4011         doCopy(CutToClipboard);
4012 }
4013 
4014 void BasketScene::clearFormattingNote(Note *note)
4015 {
4016     if (!note)
4017         note = theSelectedNote();
4018     if (!note)
4019         return;
4020 
4021     HtmlContent* contentHtml = dynamic_cast<HtmlContent*>(note->content());
4022     if (contentHtml){
4023         contentHtml->setHtml(Tools::textToHTML(note->content()->toText("")));
4024     }
4025 
4026     noteEdit(note);
4027 }
4028 
4029 void BasketScene::noteOpen(Note *note)
4030 {
4031     /*
4032     GetSelectedNotes
4033     NoSelectedNote || Count == 0 ? return
4034     AllTheSameType ?
4035     Get { url, message(count) }
4036     */
4037 
4038     // TODO: Open ALL selected notes!
4039     if (note == nullptr) {
4040         note = theSelectedNote();
4041     }
4042     if (note == nullptr) {
4043         return;
4044     }
4045 
4046     QUrl url = note->content()->urlToOpen(/*with=*/false);
4047     QString message = note->content()->messageWhenOpening(NoteContent::OpenOne /*NoteContent::OpenSeveral*/);
4048     if (url.isEmpty()) {
4049         if (message.isEmpty()) {
4050             Q_EMIT postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/);
4051         } else {
4052             int result = KMessageBox::warningContinueCancel(m_view, message, /*caption=*/QString(), KGuiItem(i18n("&Edit"), "edit"));
4053             if (result == KMessageBox::Continue) {
4054                 noteEdit(note);
4055             }
4056         }
4057     } else {
4058         Q_EMIT postMessage(message); // "Opening link target..." / "Launching application..." / "Opening note file..."
4059         // Finally do the opening job:
4060         QString customCommand = note->content()->customOpenCommand();
4061 
4062         if (url.url().startsWith(QStringLiteral("basket://"))) {
4063             Q_EMIT crossReference(url.url());
4064         } else if (customCommand.isEmpty()) {
4065             KRun *run = new KRun(url, m_view->window());
4066             run->setAutoDelete(true);
4067         } else {
4068             QList<QUrl> urls {url};
4069             KService::Ptr service(new KService(QStringLiteral("noteOpen"), customCommand, QString()));
4070             auto *job = new KIO::ApplicationLauncherJob(service);
4071             job->setUrls(urls);
4072             job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, m_view->window()));
4073             job->start();
4074         }
4075     }
4076 }
4077 
4078 /** Code from bool KRun::displayOpenWithDialog(const KUrl::List& lst, bool tempFiles)
4079  * It does not allow to set a text, so I ripped it to do that:
4080  */
4081 bool KRun__displayOpenWithDialog(const QList<QUrl> &lst, QWidget *window, bool tempFiles, const QString &text)
4082 {
4083     if (qApp && !KAuthorized::authorizeAction(QStringLiteral("openwith"))) {
4084         KMessageBox::error(window, i18n("You are not authorized to open this file.")); // TODO: Better message, i18n freeze :-(
4085         return false;
4086     }
4087     KOpenWithDialog l(lst, text, QString(), nullptr);
4088     if (l.exec() > 0) {
4089         KService::Ptr service = l.service();
4090         if (!service) {
4091             service = new KService(QStringLiteral("noteOpenWith"), l.text(), QString());
4092         }
4093 
4094         auto *job = new KIO::ApplicationLauncherJob(service);
4095         job->setUrls(lst);
4096         job->setUiDelegate(new KDialogJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, window));
4097         if (tempFiles) {
4098             job->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles);
4099         }
4100 
4101         /// \todo job runs synchronously... API suggests asynchronous start() instead.
4102         /// This requires a result handler. noteOpenWith also just emits a message to the status bar.
4103         return job->exec();
4104 
4105     }
4106     return false;
4107 }
4108 
4109 void BasketScene::noteOpenWith(Note *note)
4110 {
4111     if (note == nullptr) {
4112         note = theSelectedNote();
4113     }
4114     if (note == nullptr) {
4115         return;
4116     }
4117 
4118     QUrl url = note->content()->urlToOpen(/*with=*/true);
4119     QString message = note->content()->messageWhenOpening(NoteContent::OpenOneWith);
4120     QString text = note->content()->messageWhenOpening(NoteContent::OpenOneWithDialog);
4121     if (url.isEmpty()) {
4122         Q_EMIT postMessage(i18n("Unable to open this note."));
4123     } else {
4124         QList<QUrl> urls{url};
4125         if (KRun__displayOpenWithDialog(urls, m_view->window(), false, text)) {
4126             Q_EMIT postMessage(message); // "Opening link target with..." / "Opening note file with..."
4127         }
4128     }
4129 }
4130 
4131 void BasketScene::noteSaveAs()
4132 {
4133     //  if (!note)
4134     //      note = theSelectedNote();
4135     Note *note = theSelectedNote();
4136     if (!note)
4137         return;
4138 
4139     QUrl url = note->content()->urlToOpen(/*with=*/false);
4140     if (url.isEmpty())
4141         return;
4142 
4143     QString fileName = QFileDialog::getSaveFileName(m_view, i18n("Save to File"), url.fileName(), note->content()->saveAsFilters());
4144     // TODO: Ask to overwrite !
4145     if (fileName.isEmpty())
4146         return;
4147 
4148     // TODO: Convert format, etc. (use NoteContent::saveAs(fileName))
4149     KIO::copy(url, QUrl::fromLocalFile(fileName));
4150 }
4151 
4152 Note *BasketScene::selectedGroup()
4153 {
4154     FOR_EACH_NOTE(note)
4155     {
4156         Note *selectedGroup = note->selectedGroup();
4157         if (selectedGroup) {
4158             // If the selected group is one group in a column, then return that group, and not the column,
4159             // because the column is not ungrouppage, and the Ungroup action would be disabled.
4160             if (selectedGroup->isColumn() && selectedGroup->firstChild() && !selectedGroup->firstChild()->next()) {
4161                 return selectedGroup->firstChild();
4162             }
4163             return selectedGroup;
4164         }
4165     }
4166     return nullptr;
4167 }
4168 
4169 bool BasketScene::selectionIsOneGroup()
4170 {
4171     return (selectedGroup() != nullptr);
4172 }
4173 
4174 Note *BasketScene::firstSelected()
4175 {
4176     Note *first = nullptr;
4177     FOR_EACH_NOTE(note)
4178     {
4179         first = note->firstSelected();
4180         if (first)
4181             return first;
4182     }
4183     return nullptr;
4184 }
4185 
4186 Note *BasketScene::lastSelected()
4187 {
4188     Note *last = nullptr, *tmp = nullptr;
4189     FOR_EACH_NOTE(note)
4190     {
4191         tmp = note->lastSelected();
4192         if (tmp)
4193             last = tmp;
4194     }
4195     return last;
4196 }
4197 
4198 bool BasketScene::convertTexts()
4199 {
4200     m_watcher->stopScan();
4201     bool convertedNotes = false;
4202 
4203     if (!isLoaded())
4204         load();
4205 
4206     FOR_EACH_NOTE(note)
4207     if (note->convertTexts())
4208         convertedNotes = true;
4209 
4210     if (convertedNotes)
4211         save();
4212     m_watcher->startScan();
4213     return convertedNotes;
4214 }
4215 
4216 void BasketScene::noteGroup()
4217 {
4218     /*  // Nothing to do?
4219         if (isLocked() || countSelecteds() <= 1)
4220             return;
4221 
4222         // If every selected notes are ALREADY in one group, then don't touch anything:
4223         Note *selectedGroup = this->selectedGroup();
4224         if (selectedGroup && !selectedGroup->isColumn())
4225             return;
4226     */
4227 
4228     // Copied from BNPView::updateNotesActions()
4229     bool severalSelected = countSelecteds() >= 2;
4230     Note *selectedGroup = (severalSelected ? this->selectedGroup() : nullptr);
4231     bool enabled = !isLocked() && severalSelected && (!selectedGroup || selectedGroup->isColumn());
4232     if (!enabled)
4233         return;
4234 
4235     // Get the first selected note: we will group selected items just before:
4236     Note *first = firstSelected();
4237     //  if (selectedGroup != 0 || first == 0)
4238     //      return;
4239 
4240     m_loaded = false; // Hack to avoid notes to be unselected and new notes to be selected:
4241 
4242     // Create and insert the receiving group:
4243     Note *group = new Note(this);
4244     if (first->isFree()) {
4245         insertNote(group, nullptr, Note::BottomColumn, QPointF(first->x(), first->y()));
4246     } else {
4247         insertNote(group, first, Note::TopInsert);
4248     }
4249 
4250     // Put a FAKE UNSELECTED note in the new group, so if the new group is inside an allSelected() group, the parent group is not moved inside the new group!
4251     Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4252     insertNote(fakeNote, group, Note::BottomColumn);
4253 
4254     // Group the notes:
4255     Note *nextNote;
4256     Note *note = firstNote();
4257     while (note) {
4258         nextNote = note->next();
4259         note->groupIn(group);
4260         note = nextNote;
4261     }
4262 
4263     m_loaded = true; // Part 2 / 2 of the workaround!
4264 
4265     // Do cleanup:
4266     unplugNote(fakeNote);
4267     delete fakeNote;
4268     unselectAll();
4269     group->setSelectedRecursively(true); // Notes were unselected by unplugging
4270 
4271     relayoutNotes();
4272     save();
4273 }
4274 
4275 void BasketScene::noteUngroup()
4276 {
4277     Note *group = selectedGroup();
4278     if (group && !group->isColumn()) {
4279         ungroupNote(group);
4280         relayoutNotes();
4281     }
4282     save();
4283 }
4284 
4285 void BasketScene::unplugSelection(NoteSelection *selection)
4286 {
4287     for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) {
4288         unplugNote(toUnplug->note);
4289     }
4290 }
4291 
4292 void BasketScene::insertSelection(NoteSelection *selection, Note *after)
4293 {
4294     for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) {
4295         if (toUnplug->note->isGroup()) {
4296             Note *group = new Note(this);
4297             insertNote(group, after, Note::BottomInsert);
4298             Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4299             insertNote(fakeNote, group, Note::BottomColumn);
4300             insertSelection(toUnplug->firstChild, fakeNote);
4301             unplugNote(fakeNote);
4302             delete fakeNote;
4303             after = group;
4304         } else {
4305             Note *note = toUnplug->note;
4306             note->setPrev(nullptr);
4307             note->setNext(nullptr);
4308             insertNote(note, after, Note::BottomInsert);
4309             after = note;
4310         }
4311     }
4312 }
4313 
4314 void BasketScene::selectSelection(NoteSelection *selection)
4315 {
4316     for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) {
4317         if (toUnplug->note->isGroup())
4318             selectSelection(toUnplug);
4319         else
4320             toUnplug->note->setSelected(true);
4321     }
4322 }
4323 
4324 void BasketScene::noteMoveOnTop()
4325 {
4326     // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket
4327     // TODO: Move on top/bottom... of the column or basjet
4328 
4329     NoteSelection *selection = selectedNotes();
4330     unplugSelection(selection);
4331     // Replug the notes:
4332     Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4333     if (isColumnsLayout()) {
4334         if (firstNote()->firstChild())
4335             insertNote(fakeNote, firstNote()->firstChild(), Note::TopInsert);
4336         else
4337             insertNote(fakeNote, firstNote(), Note::BottomColumn);
4338     } else {
4339         // TODO: Also allow to move notes on top of a group!!!!!!!
4340         insertNote(fakeNote, nullptr, Note::BottomInsert);
4341     }
4342     insertSelection(selection, fakeNote);
4343     unplugNote(fakeNote);
4344     delete fakeNote;
4345     selectSelection(selection);
4346     relayoutNotes();
4347     save();
4348 }
4349 
4350 void BasketScene::noteMoveOnBottom()
4351 {
4352     // TODO: Duplicate code: void noteMoveOn();
4353 
4354     // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket
4355     // TODO: Move on top/bottom... of the column or basjet
4356 
4357     NoteSelection *selection = selectedNotes();
4358     unplugSelection(selection);
4359     // Replug the notes:
4360     Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4361     if (isColumnsLayout())
4362         insertNote(fakeNote, firstNote(), Note::BottomColumn);
4363     else {
4364         // TODO: Also allow to move notes on top of a group!!!!!!!
4365         insertNote(fakeNote, nullptr, Note::BottomInsert);
4366     }
4367     insertSelection(selection, fakeNote);
4368     unplugNote(fakeNote);
4369     delete fakeNote;
4370     selectSelection(selection);
4371     relayoutNotes();
4372     save();
4373 }
4374 
4375 void BasketScene::moveSelectionTo(Note *here, bool below /* = true*/)
4376 {
4377     NoteSelection *selection = selectedNotes();
4378     unplugSelection(selection);
4379     // Replug the notes:
4380     Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4381     //  if (isColumnsLayout())
4382     insertNote(fakeNote, here, (below ? Note::BottomInsert : Note::TopInsert));
4383     //  else {
4384     //      // TODO: Also allow to move notes on top of a group!!!!!!!
4385     //      insertNote(fakeNote, 0, Note::BottomInsert, QPoint(0, 0), /*animateNewPosition=*/false);
4386     //  }
4387     insertSelection(selection, fakeNote);
4388     unplugNote(fakeNote);
4389     delete fakeNote;
4390     selectSelection(selection);
4391     relayoutNotes();
4392     save();
4393 }
4394 
4395 void BasketScene::noteMoveNoteUp()
4396 {
4397     // TODO: Move between columns, even if they are empty !!!!!!!
4398 
4399     // TODO: if first note of a group, move just above the group! And let that even if there is no note before that group!!!
4400 
4401     Note *first = firstSelected();
4402     Note *previous = first->prevShownInStack();
4403     if (previous)
4404         moveSelectionTo(previous, /*below=*/false);
4405 }
4406 
4407 void BasketScene::noteMoveNoteDown()
4408 {
4409     Note *first = lastSelected();
4410     Note *next = first->nextShownInStack();
4411     if (next)
4412         moveSelectionTo(next, /*below=*/true);
4413 }
4414 
4415 void BasketScene::wheelEvent(QGraphicsSceneWheelEvent *event)
4416 {
4417     // Q3ScrollView::wheelEvent(event);
4418     QGraphicsScene::wheelEvent(event);
4419 }
4420 
4421 void BasketScene::linkLookChanged()
4422 {
4423     Note *note = m_firstNote;
4424     while (note) {
4425         note->linkLookChanged();
4426         note = note->next();
4427     }
4428     relayoutNotes();
4429 }
4430 
4431 void BasketScene::slotCopyingDone2(KIO::Job *job, const QUrl & /*from*/, const QUrl &to)
4432 {
4433     if (job->error()) {
4434         DEBUG_WIN << "Copy finished, ERROR";
4435         return;
4436     }
4437     Note *note = noteForFullPath(to.path());
4438     DEBUG_WIN << "Copy finished, load note: " + to.path() + (note ? QString() : " --- NO CORRESPONDING NOTE");
4439     if (note != nullptr) {
4440         note->content()->loadFromFile(/*lazyLoad=*/false);
4441         if (isEncrypted())
4442             note->content()->saveToFile();
4443         if (m_focusedNote == note)   // When inserting a new note we ensure it visible
4444             ensureNoteVisible(note); //  But after loading it has certainly grown and if it was
4445     }                                //  on bottom of the basket it's not visible entirely anymore
4446 }
4447 
4448 Note *BasketScene::noteForFullPath(const QString &path)
4449 {
4450     Note *note = firstNote();
4451     Note *found;
4452     while (note) {
4453         found = note->noteForFullPath(path);
4454         if (found)
4455             return found;
4456         note = note->next();
4457     }
4458     return nullptr;
4459 }
4460 
4461 void BasketScene::deleteFiles()
4462 {
4463     m_watcher->stopScan();
4464     Tools::deleteRecursively(fullPath());
4465 }
4466 
4467 QList<State *> BasketScene::usedStates()
4468 {
4469     QList<State *> states;
4470     FOR_EACH_NOTE(note)
4471     note->usedStates(states);
4472     return states;
4473 }
4474 
4475 void BasketScene::listUsedTags(QList<Tag *> &list)
4476 {
4477     if (!isLoaded()) {
4478         load();
4479     }
4480 
4481     FOR_EACH_NOTE(child)
4482     child->listUsedTags(list);
4483 }
4484 
4485 /** Unfocus the previously focused note (unless it was null)
4486  * and focus the new @param note (unless it is null) if hasFocus()
4487  * Update m_focusedNote to the new one
4488  */
4489 void BasketScene::setFocusedNote(Note *note) // void BasketScene::changeFocusTo(Note *note)
4490 {
4491     // Don't focus an hidden note:
4492     if (note != nullptr && !note->isShown())
4493         return;
4494     // When clicking a group, this group gets focused. But only content-based notes should be focused:
4495     if (note && note->isGroup())
4496         note = note->firstRealChild();
4497     // The first time a note is focused, it becomes the start of the Shift selection:
4498     if (m_startOfShiftSelectionNote == nullptr)
4499         m_startOfShiftSelectionNote = note;
4500     // Unfocus the old focused note:
4501     if (m_focusedNote != nullptr)
4502         m_focusedNote->setFocused(false);
4503     // Notify the new one to draw a focus rectangle... only if the basket is focused:
4504     if (hasFocus() && note != nullptr)
4505         note->setFocused(true);
4506     // Save the new focused note:
4507     m_focusedNote = note;
4508 }
4509 
4510 /** If no shown note is currently focused, try to find a shown note and focus it
4511  * Also update m_focusedNote to the new one (or null if there isn't)
4512  */
4513 void BasketScene::focusANote()
4514 {
4515     if (countFounds() == 0) { // No note to focus
4516         setFocusedNote(nullptr);
4517         //      m_startOfShiftSelectionNote = 0;
4518         return;
4519     }
4520 
4521     if (m_focusedNote == nullptr) { // No focused note yet : focus the first shown
4522         Note *toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack());
4523         setFocusedNote(toFocus);
4524         //      m_startOfShiftSelectionNote = m_focusedNote;
4525         return;
4526     }
4527 
4528     // Search a visible note to focus if the focused one isn't shown :
4529     Note *toFocus = m_focusedNote;
4530     if (toFocus && !toFocus->isShown())
4531         toFocus = toFocus->nextShownInStack();
4532     if (!toFocus && m_focusedNote)
4533         toFocus = m_focusedNote->prevShownInStack();
4534     setFocusedNote(toFocus);
4535     //  m_startOfShiftSelectionNote = toFocus;
4536 }
4537 
4538 Note *BasketScene::firstNoteInStack()
4539 {
4540     if (!firstNote())
4541         return nullptr;
4542 
4543     if (firstNote()->content())
4544         return firstNote();
4545     else
4546         return firstNote()->nextInStack();
4547 }
4548 
4549 Note *BasketScene::lastNoteInStack()
4550 {
4551     Note *note = lastNote();
4552     while (note) {
4553         if (note->content())
4554             return note;
4555         Note *possibleNote = note->lastRealChild();
4556         if (possibleNote && possibleNote->content())
4557             return possibleNote;
4558         note = note->prev();
4559     }
4560     return nullptr;
4561 }
4562 
4563 Note *BasketScene::firstNoteShownInStack()
4564 {
4565     Note *first = firstNoteInStack();
4566     while (first && !first->isShown())
4567         first = first->nextInStack();
4568     return first;
4569 }
4570 
4571 Note *BasketScene::lastNoteShownInStack()
4572 {
4573     Note *last = lastNoteInStack();
4574     while (last && !last->isShown())
4575         last = last->prevInStack();
4576     return last;
4577 }
4578 
4579 Note *BasketScene::noteOn(NoteOn side)
4580 {
4581     Note *bestNote = nullptr;
4582     int distance = -1;
4583     //    int   bestDistance = contentsWidth() * contentsHeight() * 10;
4584     int bestDistance = sceneRect().width() * sceneRect().height() * 10;
4585 
4586     Note *note = firstNoteShownInStack();
4587     Note *primary = m_focusedNote->parentPrimaryNote();
4588     while (note) {
4589         switch (side) {
4590         case LEFT_SIDE:
4591             distance = m_focusedNote->distanceOnLeftRight(note, LEFT_SIDE);
4592             break;
4593         case RIGHT_SIDE:
4594             distance = m_focusedNote->distanceOnLeftRight(note, RIGHT_SIDE);
4595             break;
4596         case TOP_SIDE:
4597             distance = m_focusedNote->distanceOnTopBottom(note, TOP_SIDE);
4598             break;
4599         case BOTTOM_SIDE:
4600             distance = m_focusedNote->distanceOnTopBottom(note, BOTTOM_SIDE);
4601             break;
4602         }
4603         if ((side == TOP_SIDE || side == BOTTOM_SIDE || primary != note->parentPrimaryNote()) && note != m_focusedNote && distance > 0 && distance < bestDistance) {
4604             bestNote = note;
4605             bestDistance = distance;
4606         }
4607         note = note->nextShownInStack();
4608     }
4609 
4610     return bestNote;
4611 }
4612 
4613 Note *BasketScene::firstNoteInGroup()
4614 {
4615     Note *child = m_focusedNote;
4616     Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : nullptr);
4617     while (parent) {
4618         if (parent->firstChild() != child && !parent->isColumn())
4619             return parent->firstRealChild();
4620         child = parent;
4621         parent = parent->parentNote();
4622     }
4623     return nullptr;
4624 }
4625 
4626 Note *BasketScene::noteOnHome()
4627 {
4628     // First try to find the first note of the group containing the focused note:
4629     Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : nullptr);
4630     while (parent) {
4631         if (parent->nextShownInStack() != m_focusedNote)
4632             return parent->nextShownInStack();
4633         parent = parent->parentNote();
4634     }
4635 
4636     // If it was not found, then focus the very first note in the basket:
4637     if (isFreeLayout()) {
4638         Note *first = firstNoteShownInStack(); // The effective first note found
4639         Note *note = first;                    // The current note, to compare with the previous first note, if this new note is more on top
4640         if (note)
4641             note = note->nextShownInStack();
4642         while (note) {
4643             if (note->y() < first->y() || (note->y() == first->y() && note->x() < first->x()))
4644                 first = note;
4645             note = note->nextShownInStack();
4646         }
4647         return first;
4648     } else
4649         return firstNoteShownInStack();
4650 }
4651 
4652 Note *BasketScene::noteOnEnd()
4653 {
4654     Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : nullptr);
4655     Note *lastChild;
4656     while (parent) {
4657         lastChild = parent->lastRealChild();
4658         if (lastChild && lastChild != m_focusedNote) {
4659             if (lastChild->isShown())
4660                 return lastChild;
4661             lastChild = lastChild->prevShownInStack();
4662             if (lastChild && lastChild->isShown() && lastChild != m_focusedNote)
4663                 return lastChild;
4664         }
4665         parent = parent->parentNote();
4666     }
4667     if (isFreeLayout()) {
4668         Note *last;
4669         Note *note;
4670         last = note = firstNoteShownInStack();
4671         note = note->nextShownInStack();
4672         while (note) {
4673             if (note->bottom() > last->bottom() || (note->bottom() == last->bottom() && note->x() > last->x()))
4674                 last = note;
4675             note = note->nextShownInStack();
4676         }
4677         return last;
4678     } else
4679         return lastNoteShownInStack();
4680 }
4681 
4682 void BasketScene::keyPressEvent(QKeyEvent *event)
4683 {
4684     if (isDuringEdit()) {
4685         QGraphicsScene::keyPressEvent(event);
4686         /*if( event->key() == Qt::Key_Return )
4687         {
4688       m_editor->graphicsWidget()->setFocus();
4689         }
4690         else if( event->key() == Qt::Key_Escape)
4691         {
4692       closeEditor();
4693         }*/
4694         event->accept();
4695         return;
4696     }
4697 
4698     if (event->key() == Qt::Key_Escape) {
4699         if (decoration()->filterData().isFiltering)
4700             decoration()->filterBar()->reset();
4701         else
4702             unselectAll();
4703     }
4704 
4705     if (countFounds() == 0)
4706         return;
4707 
4708     if (!m_focusedNote)
4709         return;
4710 
4711     Note *toFocus = nullptr;
4712 
4713     switch (event->key()) {
4714     case Qt::Key_Down:
4715         toFocus = (isFreeLayout() ? noteOn(BOTTOM_SIDE) : m_focusedNote->nextShownInStack());
4716         if (toFocus)
4717             break;
4718         //        scrollBy(0, 30); // This cases do not move focus to another note...
4719         return;
4720     case Qt::Key_Up:
4721         toFocus = (isFreeLayout() ? noteOn(TOP_SIDE) : m_focusedNote->prevShownInStack());
4722         if (toFocus)
4723             break;
4724         //  scrollBy(0, -30); // This cases do not move focus to another note...
4725         return;
4726     case Qt::Key_PageDown:
4727         if (isFreeLayout()) {
4728             Note *lastFocused = m_focusedNote;
4729             for (int i = 0; i < 10 && m_focusedNote; ++i)
4730                 m_focusedNote = noteOn(BOTTOM_SIDE);
4731             toFocus = m_focusedNote;
4732             m_focusedNote = lastFocused;
4733         } else {
4734             toFocus = m_focusedNote;
4735             for (int i = 0; i < 10 && toFocus; ++i)
4736                 toFocus = toFocus->nextShownInStack();
4737         }
4738         if (toFocus == nullptr)
4739             toFocus = (isFreeLayout() ? noteOnEnd() : lastNoteShownInStack());
4740         if (toFocus && toFocus != m_focusedNote)
4741             break;
4742         //        scrollBy(0, visibleHeight() / 2); // This cases do not move focus to another note...
4743         //        scrollBy(0, viewport()->height() / 2); // This cases do not move focus to another note...
4744         return;
4745     case Qt::Key_PageUp:
4746         if (isFreeLayout()) {
4747             Note *lastFocused = m_focusedNote;
4748             for (int i = 0; i < 10 && m_focusedNote; ++i)
4749                 m_focusedNote = noteOn(TOP_SIDE);
4750             toFocus = m_focusedNote;
4751             m_focusedNote = lastFocused;
4752         } else {
4753             toFocus = m_focusedNote;
4754             for (int i = 0; i < 10 && toFocus; ++i)
4755                 toFocus = toFocus->prevShownInStack();
4756         }
4757         if (toFocus == nullptr)
4758             toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack());
4759         if (toFocus && toFocus != m_focusedNote)
4760             break;
4761         //        scrollBy(0, - visibleHeight() / 2); // This cases do not move focus to another note...
4762         //    scrollBy(0, - viewport()->height() / 2); // This cases do not move focus to another note...
4763         return;
4764     case Qt::Key_Home:
4765         toFocus = noteOnHome();
4766         break;
4767     case Qt::Key_End:
4768         toFocus = noteOnEnd();
4769         break;
4770     case Qt::Key_Left:
4771         if (m_focusedNote->tryFoldParent())
4772             return;
4773         if ((toFocus = noteOn(LEFT_SIDE)))
4774             break;
4775         if ((toFocus = firstNoteInGroup()))
4776             break;
4777         //        scrollBy(-30, 0); // This cases do not move focus to another note...
4778         return;
4779     case Qt::Key_Right:
4780         if (m_focusedNote->tryExpandParent())
4781             return;
4782         if ((toFocus = noteOn(RIGHT_SIDE)))
4783             break;
4784         //  scrollBy(30, 0); // This cases do not move focus to another note...
4785         return;
4786     case Qt::Key_Space: // This case do not move focus to another note...
4787         if (m_focusedNote) {
4788             m_focusedNote->setSelected(!m_focusedNote->isSelected());
4789             event->accept();
4790         } else
4791             event->ignore();
4792         return; // ... so we return after the process
4793     default:
4794         return;
4795     }
4796 
4797     if (toFocus == nullptr) { // If no direction keys have been pressed OR reached out the begin or end
4798         event->ignore();      // Important !!
4799         return;
4800     }
4801 
4802     if (event->modifiers() & Qt::ShiftModifier) { // Shift+arrowKeys selection
4803         if (m_startOfShiftSelectionNote == nullptr)
4804             m_startOfShiftSelectionNote = toFocus;
4805         ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part!
4806         selectRange(m_startOfShiftSelectionNote, toFocus);
4807         setFocusedNote(toFocus);
4808         event->accept();
4809         return;
4810     } else /*if (toFocus != m_focusedNote)*/ { // Move focus to ANOTHER note...
4811         ensureNoteVisible(toFocus);            // Important: this line should be before the other ones because else repaint would be done on the wrong part!
4812         setFocusedNote(toFocus);
4813         m_startOfShiftSelectionNote = toFocus;
4814         if (!(event->modifiers() & Qt::ControlModifier)) // ... select only current note if Control
4815             unselectAllBut(m_focusedNote);
4816         event->accept();
4817         return;
4818     }
4819 
4820     event->ignore(); // Important !!
4821 }
4822 
4823 /** Select a range of notes and deselect the others.
4824  * The order between start and end has no importance (end could be before start)
4825  */
4826 void BasketScene::selectRange(Note *start, Note *end, bool unselectOthers /*= true*/)
4827 {
4828     Note *cur;
4829     Note *realEnd = nullptr;
4830 
4831     // Avoid crash when start (or end) is null
4832     if (start == nullptr)
4833         start = end;
4834     else if (end == nullptr)
4835         end = start;
4836     // And if *both* are null
4837     if (start == nullptr) {
4838         if (unselectOthers)
4839             unselectAll();
4840         return;
4841     }
4842     // In case there is only one note to select
4843     if (start == end) {
4844         if (unselectOthers)
4845             unselectAllBut(start);
4846         else
4847             start->setSelected(true);
4848         return;
4849     }
4850 
4851     // Free layout baskets should select range as if we were drawing a rectangle between start and end:
4852     if (isFreeLayout()) {
4853         QRectF startRect(start->x(), start->y(), start->width(), start->height());
4854         QRectF endRect(end->x(), end->y(), end->width(), end->height());
4855         QRectF toSelect = startRect.united(endRect);
4856         selectNotesIn(toSelect, /*invertSelection=*/false, unselectOthers);
4857         return;
4858     }
4859 
4860     // Search the REAL first (and deselect the others before it) :
4861     for (cur = firstNoteInStack(); cur != nullptr; cur = cur->nextInStack()) {
4862         if (cur == start || cur == end)
4863             break;
4864         if (unselectOthers)
4865             cur->setSelected(false);
4866     }
4867 
4868     // Select the notes after REAL start, until REAL end :
4869     if (cur == start)
4870         realEnd = end;
4871     else if (cur == end)
4872         realEnd = start;
4873 
4874     for (/*cur = cur*/; cur != nullptr; cur = cur->nextInStack()) {
4875         cur->setSelected(cur->isShown()); // Select all notes in the range, but only if they are shown
4876         if (cur == realEnd)
4877             break;
4878     }
4879 
4880     if (!unselectOthers)
4881         return;
4882 
4883     // Deselect the remaining notes :
4884     if (cur)
4885         cur = cur->nextInStack();
4886     for (/*cur = cur*/; cur != nullptr; cur = cur->nextInStack())
4887         cur->setSelected(false);
4888 }
4889 
4890 void BasketScene::focusInEvent(QFocusEvent *event)
4891 {
4892     // Focus cannot be get with Tab when locked, but a click can focus the basket!
4893     if (isLocked()) {
4894         if (m_button) {
4895             QGraphicsScene::focusInEvent(event);
4896             QTimer::singleShot(0, m_button, SLOT(setFocus()));
4897         }
4898     } else {
4899         QGraphicsScene::focusInEvent(event);
4900         focusANote(); // hasFocus() is true at this stage, note will be focused
4901     }
4902 }
4903 
4904 void BasketScene::focusOutEvent(QFocusEvent *)
4905 {
4906     if (m_focusedNote != nullptr)
4907         m_focusedNote->setFocused(false);
4908 }
4909 
4910 void BasketScene::ensureNoteVisible(Note *note)
4911 {
4912     if (!note->isShown()) // Logical!
4913         return;
4914 
4915     if (note == editedNote()) // HACK: When filtering while editing big notes, etc... cause unwanted scrolls
4916         return;
4917 
4918     m_view->ensureVisible(note);
4919     /*//    int bottom = note->y() + qMin(note->height(),                                             visibleHeight());
4920     //    int finalRight  = note->x() + qMin(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0),  visibleWidth());
4921         qreal bottom = note->y() + qMin(note->height(),                                             (qreal)m_view->viewport()->height());
4922         qreal finalRight  = note->x() + qMin(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0),  (qreal)m_view->viewport()->width());
4923         m_view->ensureVisible(finalRight,     bottom,    0, 0);
4924         m_view->ensureVisible(note->x(), note->y(), 0, 0);*/
4925 }
4926 
4927 void BasketScene::addWatchedFile(const QString &fullPath)
4928 {
4929     //  DEBUG_WIN << "Watcher>Add Monitoring Of : <font color=blue>" + fullPath + "</font>";
4930     m_watcher->addFile(fullPath);
4931 }
4932 
4933 void BasketScene::removeWatchedFile(const QString &fullPath)
4934 {
4935     //  DEBUG_WIN << "Watcher>Remove Monitoring Of : <font color=blue>" + fullPath + "</font>";
4936     m_watcher->removeFile(fullPath);
4937 }
4938 
4939 void BasketScene::watchedFileModified(const QString &fullPath)
4940 {
4941     if (!m_modifiedFiles.contains(fullPath))
4942         m_modifiedFiles.append(fullPath);
4943     // If a big file is saved by an application, notifications are send several times.
4944     // We wait they are not sent anymore to consider the file complete!
4945     m_watcherTimer.setSingleShot(true);
4946     m_watcherTimer.start(200);
4947     DEBUG_WIN << "Watcher>Modified : <font color=blue>" + fullPath + "</font>";
4948 }
4949 
4950 void BasketScene::watchedFileDeleted(const QString &fullPath)
4951 {
4952     Note *note = noteForFullPath(fullPath);
4953     removeWatchedFile(fullPath);
4954     if (note) {
4955         NoteSelection *selection = selectedNotes();
4956         unselectAllBut(note);
4957         noteDeleteWithoutConfirmation();
4958         while (selection) {
4959             selection->note->setSelected(true);
4960             selection = selection->nextStacked();
4961         }
4962     }
4963     DEBUG_WIN << "Watcher>Removed : <font color=blue>" + fullPath + "</font>";
4964 }
4965 
4966 void BasketScene::updateModifiedNotes()
4967 {
4968     for (QList<QString>::iterator it = m_modifiedFiles.begin(); it != m_modifiedFiles.end(); ++it) {
4969         Note *note = noteForFullPath(*it);
4970         if (note)
4971             note->content()->loadFromFile(/*lazyLoad=*/false);
4972     }
4973     m_modifiedFiles.clear();
4974 }
4975 
4976 bool BasketScene::setProtection(int type, QString key)
4977 {
4978 #ifdef HAVE_LIBGPGME
4979     if (type == PasswordEncryption || // Ask a new password
4980         m_encryptionType != type || m_encryptionKey != key) {
4981         int savedType = m_encryptionType;
4982         QString savedKey = m_encryptionKey;
4983 
4984         m_encryptionType = type;
4985         m_encryptionKey = key;
4986         m_gpg->clearCache();
4987 
4988         if (saveAgain()) {
4989             Q_EMIT propertiesChanged(this);
4990         } else {
4991             m_encryptionType = savedType;
4992             m_encryptionKey = savedKey;
4993             m_gpg->clearCache();
4994             return false;
4995         }
4996     }
4997     return true;
4998 #else
4999     m_encryptionType = type;
5000     m_encryptionKey = key;
5001 
5002     return false;
5003 #endif
5004 }
5005 
5006 bool BasketScene::saveAgain()
5007 {
5008     bool result = false;
5009 
5010     m_watcher->stopScan();
5011     // Re-encrypt basket file:
5012     result = save();
5013     // Re-encrypt every note files recursively:
5014     if (result) {
5015         FOR_EACH_NOTE(note)
5016         {
5017             result = note->saveAgain();
5018             if (!result)
5019                 break;
5020         }
5021     }
5022     m_watcher->startScan();
5023     return result;
5024 }
5025 
5026 bool BasketScene::isEncrypted()
5027 {
5028     return (m_encryptionType != NoEncryption);
5029 }
5030 
5031 bool BasketScene::isFileEncrypted()
5032 {
5033     QFile file(fullPath() + ".basket");
5034 
5035     if (file.open(QIODevice::ReadOnly)) {
5036         // Should be ASCII anyways
5037         QString line = file.readLine(32);
5038         if (line.startsWith("-----BEGIN PGP MESSAGE-----"))
5039             return true;
5040     }
5041     return false;
5042 }
5043 
5044 void BasketScene::lock()
5045 {
5046 #ifdef HAVE_LIBGPGME
5047     closeEditor();
5048     m_gpg->clearCache();
5049     m_locked = true;
5050     enableActions();
5051     deleteNotes();
5052     m_loaded = false;
5053     m_loadingLaunched = false;
5054 #endif
5055 }
5056 
5057 #include "moc_basketscene.cpp"