1 /***************************************************************************
2  *   Copyright (C) 2003 by Sébastien Laoût                                 *
3  *   slaout@linux62.org                                                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 
21 #include "basketscene.h"
22 
23 #include <QtCore/QFile>
24 #include <QtCore/QFileInfo>
25 #include <QtCore/QDir>
26 #include <QtCore/QPoint>
27 #include <QtCore/QList>
28 #include <QtCore/QStringList>
29 #include <QtCore/QDateTime>  // seed for rand()
30 #include <QtCore/QTimeLine>
31 #include <QApplication>
32 #include <QtGui/QDrag>
33 #include <QtGui/QDragMoveEvent>
34 #include <QtGui/QDragEnterEvent>
35 #include <QtGui/QDragLeaveEvent>
36 #include <QtGui/QDropEvent>
37 #include <QtGui/QMouseEvent>
38 #include <QtGui/QWheelEvent>
39 #include <QtGui/QKeyEvent>
40 #include <QtGui/QContextMenuEvent>
41 #include <QtGui/QFocusEvent>
42 #include <QtGui/QResizeEvent>
43 #include <QtGui/QPainter>
44 #include <QFrame>
45 #include <QLabel>
46 #include <QPushButton>
47 #include <QtGui/QTextDocument>
48 #include <QtGui/QAbstractTextDocumentLayout>
49 #include <QGridLayout>
50 #include <QToolTip>
51 #include <QtGui/QCursor>
52 #include <QtGui/QClipboard>
53 #include <QInputDialog>
54 #include <QGraphicsProxyWidget>
55 #include <QScrollBar>
56 #include <QtXml/QDomDocument>
57 #include <QFileDialog>
58 #include <QApplication>
59 #include <QLocale>
60 #include <QLineEdit>
61 #include <QSaveFile>
62 #include <QMenu>
63 
64 #include <KTextEdit>
65 
66 #include <KColorScheme> // for KStatefulBrush
67 #include <KOpenWithDialog>
68 #include <KService>
69 #include <KAboutData>
70 #include <KAuthorized>
71 #include <KIconLoader>
72 #include <KRun>
73 #include <KMessageBox>
74 #include <KDirWatch>
75 #include <KGlobalAccel>
76 #include <KLocalizedString>
77 #include <KActionCollection>
78 
79 #include <KIO/CopyJob>
80 
81 #include <stdlib.h>     // rand() function
82 
83 #include "basketview.h"
84 #include "decoratedbasket.h"
85 #include "diskerrordialog.h"
86 #include "note.h"
87 #include "notedrag.h"
88 #include "notefactory.h"
89 #include "noteedit.h"
90 #include "noteselection.h"
91 #include "tagsedit.h"
92 #include "transparentwidget.h"
93 #include "xmlwork.h"
94 #include "global.h"
95 #include "backgroundmanager.h"
96 #include "settings.h"
97 #include "tools.h"
98 #include "debugwindow.h"
99 #include "focusedwidgets.h"
100 #include "gitwrapper.h"
101 
102 #include "config.h"
103 
104 #ifdef HAVE_LIBGPGME
105 #include "kgpgme.h"
106 #endif
107 
108 #ifdef HAVE_BALOO
109 #include "nepomukintegration.h"
110 #endif
111 
112 const int BasketScene::ANIMATION_DELAY = 2000;
113 
debugZone(int zone)114 void debugZone(int zone)
115 {
116     QString s;
117     switch (zone) {
118     case Note::Handle:        s = "Handle";              break;
119     case Note::Group:         s = "Group";               break;
120     case Note::TagsArrow:     s = "TagsArrow";           break;
121     case Note::Custom0:       s = "Custom0";             break;
122     case Note::GroupExpander: s = "GroupExpander";       break;
123     case Note::Content:       s = "Content";             break;
124     case Note::Link:          s = "Link";                break;
125     case Note::TopInsert:     s = "TopInsert";           break;
126     case Note::TopGroup:      s = "TopGroup";            break;
127     case Note::BottomInsert:  s = "BottomInsert";        break;
128     case Note::BottomGroup:   s = "BottomGroup";         break;
129     case Note::BottomColumn:  s = "BottomColumn";        break;
130     case Note::None:          s = "None";                break;
131     default:
132         if (zone == Note::Emblem0)
133             s = "Emblem0";
134         else
135             s = "Emblem0+" + QString::number(zone - Note::Emblem0);
136         break;
137     }
138     qDebug() << s;
139 }
140 
141 #define FOR_EACH_NOTE(noteVar) \
142     for (Note *noteVar = firstNote(); noteVar; noteVar = noteVar->next())
143 
prependNoteIn(Note * note,Note * in)144 void BasketScene::prependNoteIn(Note *note, Note *in)
145 {
146     if (!note)
147         // No note to prepend:
148         return;
149 
150     if (in) {
151         // The normal case:
152         preparePlug(note);
153 
154         Note *last = note->lastSibling();
155 
156         for (Note *n = note; n; n = n->next())
157             n->setParentNote(in);
158 //      note->setPrev(0L);
159         last->setNext(in->firstChild());
160 
161         if (in->firstChild())
162             in->firstChild()->setPrev(last);
163 
164         in->setFirstChild(note);
165 
166         if (m_loaded)
167             signalCountsChanged();
168     } else
169         // Prepend it directly in the basket:
170         appendNoteBefore(note, firstNote());
171 }
172 
appendNoteIn(Note * note,Note * in)173 void BasketScene::appendNoteIn(Note *note, Note *in)
174 {
175     if (!note)
176         // No note to append:
177         return;
178 
179     if (in) {
180         // The normal case:
181         preparePlug(note);
182 
183 //      Note *last = note->lastSibling();
184         Note *lastChild = in->lastChild();
185 
186         for (Note *n = note; n; n = n->next())
187             n->setParentNote(in);
188         note->setPrev(lastChild);
189 //      last->setNext(0L);
190 
191         if (!in->firstChild())
192             in->setFirstChild(note);
193 
194         if (lastChild)
195             lastChild->setNext(note);
196 
197         if (m_loaded)
198             signalCountsChanged();
199     } else
200         // Prepend it directly in the basket:
201         appendNoteAfter(note, lastNote());
202 }
203 
appendNoteAfter(Note * note,Note * after)204 void BasketScene::appendNoteAfter(Note *note, Note *after)
205 {
206     if (!note)
207         // No note to append:
208         return;
209 
210     if (!after)
211         // By default, insert after the last note:
212         after = lastNote();
213 
214     if (m_loaded && after && !after->isFree() && !after->isColumn())
215         for (Note *n = note; n; n = n->next())
216             n->inheritTagsOf(after);
217 
218 //  if (!alreadyInBasket)
219     preparePlug(note);
220 
221     Note *last = note->lastSibling();
222     if (after) {
223         // The normal case:
224         for (Note *n = note; n; n = n->next())
225             n->setParentNote(after->parentNote());
226         note->setPrev(after);
227         last->setNext(after->next());
228         after->setNext(note);
229         if (last->next())
230             last->next()->setPrev(last);
231     } else {
232         // There is no note in the basket:
233         for (Note *n = note; n; n = n->next())
234             n->setParentNote(0);
235         m_firstNote = note;
236 //      note->setPrev(0);
237 //      last->setNext(0);
238     }
239 
240 //  if (!alreadyInBasket)
241     if (m_loaded)
242         signalCountsChanged();
243 }
244 
appendNoteBefore(Note * note,Note * before)245 void BasketScene::appendNoteBefore(Note *note, Note *before)
246 {
247     if (!note)
248         // No note to append:
249         return;
250 
251     if (!before)
252         // By default, insert before the first note:
253         before = firstNote();
254 
255     if (m_loaded && before && !before->isFree() && !before->isColumn())
256         for (Note *n = note; n; n = n->next())
257             n->inheritTagsOf(before);
258 
259     preparePlug(note);
260 
261     Note *last = note->lastSibling();
262     if (before) {
263         // The normal case:
264         for (Note *n = note; n; n = n->next())
265             n->setParentNote(before->parentNote());
266         note->setPrev(before->prev());
267         last->setNext(before);
268         before->setPrev(last);
269         if (note->prev())
270             note->prev()->setNext(note);
271         else {
272             if (note->parentNote())
273                 note->parentNote()->setFirstChild(note);
274             else
275                 m_firstNote = note;
276         }
277     } else {
278         // There is no note in the basket:
279         for (Note *n = note; n; n = n->next())
280             n->setParentNote(0);
281         m_firstNote = note;
282 //      note->setPrev(0);
283 //      last->setNext(0);
284     }
285 
286     if (m_loaded)
287         signalCountsChanged();
288 }
289 
decoration()290 DecoratedBasket* BasketScene::decoration()
291 {
292     return (DecoratedBasket*)parent();
293 }
294 
preparePlug(Note * note)295 void BasketScene::preparePlug(Note *note)
296 {
297     // Select only the new notes, compute the new notes count and the new number of found notes:
298     if (m_loaded)
299         unselectAll();
300     int count  = 0;
301     int founds = 0;
302     Note *last = 0;
303     for (Note *n = note; n; n = n->next()) {
304         if (m_loaded)
305             n->setSelectedRecursively(true); // Notes should have a parent basket (and they have, so that's OK).
306         count  += n->count();
307         founds += n->newFilter(decoration()->filterData());
308         last = n;
309     }
310     m_count += count;
311     m_countFounds += founds;
312 
313     // Focus the last inserted note:
314     if (m_loaded && last) {
315         setFocusedNote(last);
316         m_startOfShiftSelectionNote = (last->isGroup() ? last->lastRealChild() : last);
317     }
318 
319     // If some notes don't match (are hidden), tell it to the user:
320     if (m_loaded && founds < count) {
321         if (count == 1)          postMessage(i18n("The new note does not match the filter and is hidden."));
322         else if (founds == count - 1) postMessage(i18n("A new note does not match the filter and is hidden."));
323         else if (founds > 0)          postMessage(i18n("Some new notes do not match the filter and are hidden."));
324         else                          postMessage(i18n("The new notes do not match the filter and are hidden."));
325     }
326 }
327 
unplugNote(Note * note)328 void BasketScene::unplugNote(Note *note)
329 {
330     // If there is nothing to do...
331     if (!note)
332         return;
333 
334 //  if (!willBeReplugged) {
335     note->setSelectedRecursively(false); // To removeSelectedNote() and decrease the selectedsCount.
336     m_count -= note->count();
337     m_countFounds -= note->newFilter(decoration()->filterData());
338     signalCountsChanged();
339 //  }
340 
341     // If it was the first note, change the first note:
342     if (m_firstNote == note)
343         m_firstNote = note->next();
344 
345     // Change previous and next notes:
346     if (note->prev())
347         note->prev()->setNext(note->next());
348     if (note->next())
349         note->next()->setPrev(note->prev());
350 
351     if (note->parentNote()) {
352         // If it was the first note of a group, change the first note of the group:
353         if (note->parentNote()->firstChild() == note)
354             note->parentNote()->setFirstChild(note->next());
355 
356         if (!note->parentNote()->isColumn()) {
357             // Delete parent if now 0 notes inside parent group:
358             if (! note->parentNote()->firstChild())
359 	    {
360                 unplugNote(note->parentNote());
361 		// a group could call this method for one or more of its children,
362 		// each children could call this method for its parent's group...
363 		// we have to do the deletion later otherwise we may corrupt the current process
364 		m_notesToBeDeleted << note;
365 		if(m_notesToBeDeleted.count() == 1)
366 		{
367 		  QTimer::singleShot(0, this, SLOT(doCleanUp()));
368 		}
369 	    }
370             // Ungroup if still 1 note inside parent group:
371             else if (! note->parentNote()->firstChild()->next())
372 	    {
373                 ungroupNote(note->parentNote());
374 	    }
375         }
376     }
377 
378     note->setParentNote(0);
379     note->setPrev(0);
380     note->setNext(0);
381 
382     // Reste focus and hover note if necessary
383     if(m_focusedNote == note) m_focusedNote = 0;
384     if(m_hoveredNote == note) m_hoveredNote = 0;
385 
386     //  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.
387 }
388 
ungroupNote(Note * group)389 void BasketScene::ungroupNote(Note *group)
390 {
391     Note *note            = group->firstChild();
392     Note *lastGroupedNote = group;
393     Note *nextNote;
394 
395     // Move all notes after the group (not before, to avoid to change m_firstNote or group->m_firstChild):
396     while (note) {
397         nextNote = note->next();
398 
399         if (lastGroupedNote->next())
400             lastGroupedNote->next()->setPrev(note);
401         note->setNext(lastGroupedNote->next());
402         lastGroupedNote->setNext(note);
403         note->setParentNote(group->parentNote());
404         note->setPrev(lastGroupedNote);
405 
406         note->setGroupWidth(group->groupWidth() - Note::GROUP_WIDTH);
407         lastGroupedNote = note;
408         note = nextNote;
409     }
410 
411     // Unplug the group:
412     group->setFirstChild(0);
413     unplugNote(group);
414     // a group could call this method for one or more of its children,
415     // each children could call this method for its parent's group...
416     // we have to do the deletion later otherwise we may corrupt the current process
417     m_notesToBeDeleted << group;
418     if(m_notesToBeDeleted.count() == 1)
419     {
420       QTimer::singleShot(0, this, SLOT(doCleanUp()));
421     }
422 }
423 
groupNoteBefore(Note * note,Note * with)424 void BasketScene::groupNoteBefore(Note *note, Note *with)
425 {
426     if (!note || !with)
427         // No note to group or nowhere to group it:
428         return;
429 
430 //  if (m_loaded && before && !with->isFree() && !with->isColumn())
431     for (Note *n = note; n; n = n->next())
432         n->inheritTagsOf(with);
433 
434     preparePlug(note);
435 
436     Note *last = note->lastSibling();
437 
438     Note *group = new Note(this);
439     group->setPrev(with->prev());
440     group->setNext(with->next());
441     group->setX(with->x());
442     group->setY(with->y());
443     if (with->parentNote() && with->parentNote()->firstChild() == with)
444         with->parentNote()->setFirstChild(group);
445     else if (m_firstNote == with)
446         m_firstNote = group;
447     group->setParentNote(with->parentNote());
448     group->setFirstChild(note);
449     group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH);
450 
451     if (with->prev())
452         with->prev()->setNext(group);
453     if (with->next())
454         with->next()->setPrev(group);
455     with->setParentNote(group);
456     with->setPrev(last);
457     with->setNext(0L);
458 
459     for (Note *n = note; n; n = n->next())
460         n->setParentNote(group);
461 //  note->setPrev(0L);
462     last->setNext(with);
463 
464     if (m_loaded)
465         signalCountsChanged();
466 }
467 
groupNoteAfter(Note * note,Note * with)468 void BasketScene::groupNoteAfter(Note *note, Note *with)
469 {
470     if (!note || !with)
471         // No note to group or nowhere to group it:
472         return;
473 
474 //  if (m_loaded && before && !with->isFree() && !with->isColumn())
475     for (Note *n = note; n; n = n->next())
476         n->inheritTagsOf(with);
477 
478     preparePlug(note);
479 
480 //  Note *last = note->lastSibling();
481 
482     Note *group = new Note(this);
483     group->setPrev(with->prev());
484     group->setNext(with->next());
485     group->setX(with->x());
486     group->setY(with->y());
487     if (with->parentNote() && with->parentNote()->firstChild() == with)
488         with->parentNote()->setFirstChild(group);
489     else if (m_firstNote == with)
490         m_firstNote = group;
491     group->setParentNote(with->parentNote());
492     group->setFirstChild(with);
493     group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH);
494 
495     if (with->prev())
496         with->prev()->setNext(group);
497     if (with->next())
498         with->next()->setPrev(group);
499     with->setParentNote(group);
500     with->setPrev(0L);
501     with->setNext(note);
502 
503     for (Note *n = note; n; n = n->next())
504         n->setParentNote(group);
505     note->setPrev(with);
506 //  last->setNext(0L);
507 
508     if (m_loaded)
509         signalCountsChanged();
510 }
511 
doCleanUp()512 void BasketScene::doCleanUp()
513 {
514     QSet<Note *>::iterator it = m_notesToBeDeleted.begin();
515     while(it != m_notesToBeDeleted.end())
516     {
517       delete *it;
518       it = m_notesToBeDeleted.erase(it);
519     }
520 }
521 
loadNotes(const QDomElement & notes,Note * parent)522 void BasketScene::loadNotes(const QDomElement &notes, Note *parent)
523 {
524     Note *note;
525     for (QDomNode n = notes.firstChild(); !n.isNull(); n = n.nextSibling()) {
526         QDomElement e = n.toElement();
527         if (e.isNull()) // Cannot handle that!
528             continue;
529         note = 0;
530         // Load a Group:
531         if (e.tagName() == "group") {
532             note = new Note(this);      // 1. Create the group...
533             loadNotes(e, note);         // 3. ... And populate it with child notes.
534             int noteCount = note->count();
535             if (noteCount > 0 || (parent == 0 && !isFreeLayout())) { // But don't remove columns!
536                 appendNoteIn(note, parent); // 2. ... Insert it... FIXME: Initially, the if() the insrtion was the step 2. Was it on purpose?
537                 // The notes in the group are counted two times (it's why appendNoteIn() was called before loadNotes):
538                 m_count       -= noteCount;// TODO: Recompute note count every time noteCount() is emitted!
539                 m_countFounds -= noteCount;
540             }
541         }
542         // Load a Content-Based Note:
543         if (e.tagName() == "note" || e.tagName() == "item") { // Keep compatible with 0.6.0 Alpha 1
544             note = new Note(this);      // Create the note...
545             NoteFactory__loadNode(XMLWork::getElement(e, "content"), e.attribute("type"), note, /*lazyLoad=*/m_finishLoadOnFirstShow); // ... Populate it with content...
546             if (e.attribute("type") == "text")
547                 m_shouldConvertPlainTextNotes = true; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded!
548             appendNoteIn(note, parent); // ... And insert it.
549             // Load dates:
550             if (e.hasAttribute("added"))
551                 note->setAddedDate(QDateTime::fromString(e.attribute("added"),            Qt::ISODate));
552             if (e.hasAttribute("lastModification"))
553                 note->setLastModificationDate(QDateTime::fromString(e.attribute("lastModification"), Qt::ISODate));
554         }
555         // If we successfully loaded a note:
556         if (note) {
557             // Free Note Properties:
558             if (note->isFree()) {
559                 int x = e.attribute("x").toInt();
560                 int y = e.attribute("y").toInt();
561                 note->setX(x < 0 ? 0 : x);
562                 note->setY(y < 0 ? 0 : y);
563             }
564             // Resizeable Note Properties:
565             if (note->hasResizer() || note->isColumn())
566                 note->setGroupWidth(e.attribute("width", "200").toInt());
567             // Group Properties:
568             if (note->isGroup() && !note->isColumn() && XMLWork::trueOrFalse(e.attribute("folded", "false")))
569                 note->toggleFolded();
570             // Tags:
571             if (note->content()) {
572                 QString tagsString = XMLWork::getElementText(e, "tags", "");
573                 QStringList tagsId = tagsString.split(";");
574                 for (QStringList::iterator it = tagsId.begin(); it != tagsId.end(); ++it) {
575                     State *state = Tag::stateForId(*it);
576                     if (state)
577                         note->addState(state, /*orReplace=*/true);
578                 }
579             }
580         }
581       qApp->processEvents();
582     }
583 }
584 
saveNotes(QXmlStreamWriter & stream,Note * parent)585 void BasketScene::saveNotes(QXmlStreamWriter &stream, Note *parent)
586 {
587     Note *note = (parent ? parent->firstChild() : firstNote());
588     while (note) {
589         // Create Element:
590         stream.writeStartElement(note->isGroup() ? "group" : "note");
591         // Free Note Properties:
592         if (note->isFree()) {
593             stream.writeAttribute("x", QString::number(note->x()));
594             stream.writeAttribute("y", QString::number(note->y()));
595         }
596         // Resizeable Note Properties:
597         if (note->hasResizer())
598             stream.writeAttribute("width", QString::number(note->groupWidth()));
599         // Group Properties:
600         if (note->isGroup() && !note->isColumn())
601             stream.writeAttribute("folded", XMLWork::trueOrFalse(note->isFolded()));
602         // Save Content:
603         if (note->content()) {
604             // Save Dates:
605             stream.writeAttribute("added",            note->addedDate().toString(Qt::ISODate));
606             stream.writeAttribute("lastModification", note->lastModificationDate().toString(Qt::ISODate));
607             // Save Content:
608             stream.writeAttribute("type", note->content()->lowerTypeName());
609             note->content()->saveToNode(stream);
610             // Save Tags:
611             if (note->states().count() > 0) {
612                 QString tags;
613                 for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it)
614                     tags += (tags.isEmpty() ? "" : ";") + (*it)->id();
615                 stream.writeTextElement("tags", tags);
616             }
617         } else {
618             // Save Child Notes:
619             saveNotes(stream, note);
620         }
621         stream.writeEndElement();
622         // Go to the Next One:
623         note = note->next();
624     }
625 }
626 
loadProperties(const QDomElement & properties)627 void BasketScene::loadProperties(const QDomElement &properties)
628 {
629     // Compute Default Values for When Loading the Properties:
630     QString defaultBackgroundColor = (backgroundColorSetting().isValid() ? backgroundColorSetting().name() : "");
631     QString defaultTextColor       = (textColorSetting().isValid()       ? textColorSetting().name()       : "");
632 
633     // Load the Properties:
634     QString icon = XMLWork::getElementText(properties, "icon", this->icon());
635     QString name = XMLWork::getElementText(properties, "name", basketName());
636 
637     QDomElement appearance = XMLWork::getElement(properties, "appearance");
638     // In 0.6.0-Alpha versions, there was a typo error: "backround" instead of "background"
639     QString backgroundImage       = appearance.attribute("backgroundImage", appearance.attribute("backroundImage", backgroundImageName()));
640     QString backgroundColorString = appearance.attribute("backgroundColor", appearance.attribute("backroundColor", defaultBackgroundColor));
641     QString textColorString       = appearance.attribute("textColor",      defaultTextColor);
642     QColor  backgroundColor = (backgroundColorString.isEmpty() ? QColor() : QColor(backgroundColorString));
643     QColor  textColor       = (textColorString.isEmpty()       ? QColor() : QColor(textColorString));
644 
645     QDomElement disposition = XMLWork::getElement(properties, "disposition");
646     bool free        = XMLWork::trueOrFalse(disposition.attribute("free",        XMLWork::trueOrFalse(isFreeLayout())));
647     int  columnCount =                       disposition.attribute("columnCount", QString::number(this->columnsCount())).toInt();
648     bool mindMap     = XMLWork::trueOrFalse(disposition.attribute("mindMap",     XMLWork::trueOrFalse(isMindMap())));
649 
650     QDomElement shortcut = XMLWork::getElement(properties, "shortcut");
651     QString actionStrings[] = { "show", "globalShow", "globalSwitch" };
652     QKeySequence combination  = QKeySequence(shortcut.attribute(
653                                            "combination",
654                                            m_action->shortcut().toString()));
655     QString   actionString =            shortcut.attribute("action");
656     int action = shortcutAction();
657     if (actionString == actionStrings[0]) action = 0;
658     if (actionString == actionStrings[1]) action = 1;
659     if (actionString == actionStrings[2]) action = 2;
660 
661     QDomElement protection = XMLWork::getElement(properties, "protection");
662     m_encryptionType = protection.attribute("type").toInt();
663     m_encryptionKey = protection.attribute("key");
664 
665     // Apply the Properties:
666     setDisposition((free ? (mindMap ? 2 : 1) : 0), columnCount);
667     setShortcut(combination, action);
668     setAppearance(icon, name, backgroundImage, backgroundColor, textColor); // Will emit propertiesChanged(this)
669 }
670 
saveProperties(QXmlStreamWriter & stream)671 void BasketScene::saveProperties(QXmlStreamWriter &stream)
672 {
673     stream.writeStartElement("properties");
674 
675     stream.writeTextElement("name", basketName());
676     stream.writeTextElement("icon", icon());
677 
678     stream.writeStartElement("appearance");
679     stream.writeAttribute("backgroundColor", backgroundColorSetting().isValid() ? backgroundColorSetting().name() : "");
680     stream.writeAttribute("backgroundImage", backgroundImageName());
681     stream.writeAttribute("textColor",       textColorSetting().isValid()       ? textColorSetting().name()       : "");
682     stream.writeEndElement();
683 
684     stream.writeStartElement("disposition");
685     stream.writeAttribute("columnCount", QString::number(columnsCount()));
686     stream.writeAttribute("free",        XMLWork::trueOrFalse(isFreeLayout()));
687     stream.writeAttribute("mindMap",     XMLWork::trueOrFalse(isMindMap()));
688     stream.writeEndElement();
689 
690     stream.writeStartElement("shortcut");
691     QString actionStrings[] = { "show", "globalShow", "globalSwitch" };
692     stream.writeAttribute("action",      actionStrings[shortcutAction()]);
693     stream.writeAttribute("combination", m_action->shortcut().toString());
694     stream.writeEndElement();
695 
696     stream.writeStartElement("protection");
697     stream.writeAttribute("key",  m_encryptionKey);
698     stream.writeAttribute("type", QString::number(m_encryptionType));
699     stream.writeEndElement();
700 
701     stream.writeEndElement();
702 }
703 
subscribeBackgroundImages()704 void BasketScene::subscribeBackgroundImages()
705 {
706     if (!m_backgroundImageName.isEmpty()) {
707         Global::backgroundManager->subscribe(m_backgroundImageName);
708         Global::backgroundManager->subscribe(m_backgroundImageName, this->backgroundColor());
709         Global::backgroundManager->subscribe(m_backgroundImageName, selectionRectInsideColor());
710         m_backgroundPixmap         = Global::backgroundManager->pixmap(m_backgroundImageName);
711         m_opaqueBackgroundPixmap   = Global::backgroundManager->opaquePixmap(m_backgroundImageName, this->backgroundColor());
712         m_selectedBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, selectionRectInsideColor());
713         m_backgroundTiled          = Global::backgroundManager->tiled(m_backgroundImageName);
714     }
715 }
716 
unsubscribeBackgroundImages()717 void BasketScene::unsubscribeBackgroundImages()
718 {
719     if (hasBackgroundImage()) {
720         Global::backgroundManager->unsubscribe(m_backgroundImageName);
721         Global::backgroundManager->unsubscribe(m_backgroundImageName, this->backgroundColor());
722         Global::backgroundManager->unsubscribe(m_backgroundImageName, selectionRectInsideColor());
723         m_backgroundPixmap         = 0;
724         m_opaqueBackgroundPixmap   = 0;
725         m_selectedBackgroundPixmap = 0;
726     }
727 }
728 
setAppearance(const QString & icon,const QString & name,const QString & backgroundImage,const QColor & backgroundColor,const QColor & textColor)729 void BasketScene::setAppearance(const QString &icon, const QString &name, const QString &backgroundImage, const QColor &backgroundColor, const QColor &textColor)
730 {
731     unsubscribeBackgroundImages();
732 
733     m_icon                   = icon;
734     m_basketName             = name;
735     m_backgroundImageName    = backgroundImage;
736     m_backgroundColorSetting = backgroundColor;
737     m_textColorSetting       = textColor;
738 
739     // Where is this shown?
740     m_action->setText("BASKET SHORTCUT: " + name);
741 
742     // Basket should ALWAYS have an icon (the "basket" icon by default):
743     QPixmap iconTest = KIconLoader::global()->loadIcon(
744                            m_icon, KIconLoader::NoGroup, 16, KIconLoader::DefaultState,
745                            QStringList(), 0L, /*canReturnNull=*/true
746                        );
747     if (iconTest.isNull())
748         m_icon = "basket";
749 
750     // We don't request the background images if it's not loaded yet (to make the application startup fast).
751     // When the basket is loading (because requested by the user: he/she want to access it)
752     // it load the properties, subscribe to (and then load) the images, update the "Loading..." message with the image,
753     // load all the notes and it's done!
754     if (m_loadingLaunched)
755         subscribeBackgroundImages();
756 
757     recomputeAllStyles(); // If a note have a tag with the same background color as the basket one, then display a "..."
758     recomputeBlankRects(); // See the drawing of blank areas in BasketScene::drawContents()
759     unbufferizeAll();
760 
761     if (isDuringEdit() && m_editor->graphicsWidget()) {
762         QPalette palette;
763         palette.setColor(m_editor->graphicsWidget()->widget()->backgroundRole(), m_editor->note()->backgroundColor());
764         palette.setColor(m_editor->graphicsWidget()->widget()->foregroundRole(), m_editor->note()->textColor());
765         m_editor->graphicsWidget()->setPalette(palette);
766     }
767 
768     emit propertiesChanged(this);
769 }
770 
setDisposition(int disposition,int columnCount)771 void BasketScene::setDisposition(int disposition, int columnCount)
772 {
773     static const int COLUMNS_LAYOUT  = 0;
774     static const int FREE_LAYOUT     = 1;
775     static const int MINDMAPS_LAYOUT = 2;
776 
777     int currentDisposition = (isFreeLayout() ? (isMindMap() ? MINDMAPS_LAYOUT : FREE_LAYOUT) : COLUMNS_LAYOUT);
778 
779     if (currentDisposition == COLUMNS_LAYOUT && disposition == COLUMNS_LAYOUT) {
780         if (firstNote() && columnCount > m_columnsCount) {
781             // Insert each new columns:
782             for (int i = m_columnsCount; i < columnCount; ++i) {
783                 Note *newColumn = new Note(this);
784                 insertNote(newColumn, /*clicked=*/lastNote(), /*zone=*/Note::BottomInsert, QPointF(), /*animateNewPosition=*/false);
785             }
786         } else if (firstNote() && columnCount < m_columnsCount) {
787             Note *column = firstNote();
788             Note *cuttedNotes = 0;
789             for (int i = 1; i <= m_columnsCount; ++i) {
790                 Note *columnToRemove = column;
791                 column = column->next();
792                 if (i > columnCount) {
793                     // Remove the columns that are too much:
794                     unplugNote(columnToRemove);
795                     // "Cut" the content in the columns to be deleted:
796                     if (columnToRemove->firstChild()) {
797                         for (Note *it = columnToRemove->firstChild(); it; it = it->next())
798                             it->setParentNote(0);
799                         if (!cuttedNotes)
800                             cuttedNotes = columnToRemove->firstChild();
801                         else {
802                             Note *lastCuttedNote = cuttedNotes;
803                             while (lastCuttedNote->next())
804                                 lastCuttedNote = lastCuttedNote->next();
805                             lastCuttedNote->setNext(columnToRemove->firstChild());
806                             columnToRemove->firstChild()->setPrev(lastCuttedNote);
807                         }
808                         columnToRemove->setFirstChild(0);
809                     }
810                     delete columnToRemove;
811                 }
812             }
813             // Paste the content in the last column:
814             if (cuttedNotes)
815                 insertNote(cuttedNotes, /*clicked=*/lastNote(), /*zone=*/Note::BottomColumn, QPointF(), /*animateNewPosition=*/true);
816             unselectAll();
817         }
818         if (columnCount != m_columnsCount) {
819             m_columnsCount = (columnCount <= 0 ? 1 : columnCount);
820             equalizeColumnSizes(); // Will relayoutNotes()
821         }
822     } else if (currentDisposition == COLUMNS_LAYOUT && (disposition == FREE_LAYOUT || disposition == MINDMAPS_LAYOUT)) {
823         Note *column = firstNote();
824         m_columnsCount = 0; // Now, so relayoutNotes() will not relayout the free notes as if they were columns!
825         while (column) {
826             // Move all childs on the first level:
827             Note *nextColumn = column->next();
828             ungroupNote(column);
829             column = nextColumn;
830         }
831         unselectAll();
832         m_mindMap = (disposition == MINDMAPS_LAYOUT);
833         relayoutNotes(true);
834     } else if ((currentDisposition == FREE_LAYOUT || currentDisposition == MINDMAPS_LAYOUT) && disposition == COLUMNS_LAYOUT) {
835         if (firstNote()) {
836             // TODO: Reorder notes!
837             // Remove all notes (but keep a reference to them, we're not crazy ;-) ):
838             Note *notes = m_firstNote;
839             m_firstNote = 0;
840             m_count = 0;
841             m_countFounds = 0;
842             // Insert the number of columns that is needed:
843             Note *lastInsertedColumn = 0;
844             for (int i = 0; i < columnCount; ++i) {
845                 Note *column = new Note(this);
846                 if (lastInsertedColumn)
847                     insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, QPointF(), /*animateNewPosition=*/false);
848                 else
849                     m_firstNote = column;
850                 lastInsertedColumn = column;
851             }
852             // Reinsert the old notes in the first column:
853             insertNote(notes, /*clicked=*/firstNote(), /*zone=*/Note::BottomColumn, QPointF(), /*animateNewPosition=*/true);
854             unselectAll();
855         } else {
856             // Insert the number of columns that is needed:
857             Note *lastInsertedColumn = 0;
858             for (int i = 0; i < columnCount; ++i) {
859                 Note *column = new Note(this);
860                 if (lastInsertedColumn)
861                     insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, QPointF(), /*animateNewPosition=*/false);
862                 else
863                     m_firstNote = column;
864                 lastInsertedColumn = column;
865             }
866         }
867         m_columnsCount = (columnCount <= 0 ? 1 : columnCount);
868         equalizeColumnSizes(); // Will relayoutNotes()
869     }
870 }
871 
equalizeColumnSizes()872 void BasketScene::equalizeColumnSizes()
873 {
874     if (!firstNote())
875         return;
876 
877     // Necessary to know the available space;
878     relayoutNotes(true);
879 
880     int availableSpace = m_view->viewport()->width();
881     int columnWidth = (availableSpace - (columnsCount() - 1) * Note::GROUP_WIDTH) / columnsCount();
882     int columnCount = columnsCount();
883     Note *column = firstNote();
884     while (column) {
885         int minGroupWidth = column->minRight() - column->x();
886         if (minGroupWidth > columnWidth) {
887             availableSpace -= minGroupWidth;
888             --columnCount;
889         }
890         column = column->next();
891     }
892     columnWidth = (availableSpace - (columnsCount() - 1) * Note::GROUP_WIDTH) / columnCount;
893 
894     column = firstNote();
895     while (column) {
896         int minGroupWidth = column->minRight() - column->x();
897         if (minGroupWidth > columnWidth)
898             column->setGroupWidth(minGroupWidth);
899         else
900             column->setGroupWidth(columnWidth);
901         column = column->next();
902     }
903 
904     relayoutNotes(true);
905 }
906 
enableActions()907 void BasketScene::enableActions()
908 {
909     Global::bnpView->enableActions();
910     m_view->setFocusPolicy(isLocked() ? Qt::NoFocus : Qt::StrongFocus);
911     if (isLocked())
912         m_view->viewport()->setCursor(Qt::ArrowCursor); // When locking, the cursor stays the last form it was
913 }
914 
save()915 bool BasketScene::save()
916 {
917     if (!m_loaded)
918         return false;
919 
920     DEBUG_WIN << "Basket[" + folderName() + "]: Saving...";
921 
922     QString data;
923     QXmlStreamWriter stream(&data);
924     XMLWork::setupXmlStream(stream, "basket");
925 
926     // Create Properties Element and Populate It:
927     saveProperties(stream);
928 
929     // Create Notes Element and Populate It:
930     stream.writeStartElement("notes");
931     saveNotes(stream, NULL);
932     stream.writeEndElement();
933 
934     stream.writeEndElement();
935     stream.writeEndDocument();
936 
937     // Write to Disk:
938     if (!saveToFile(fullPath() + ".basket", data)) {
939         DEBUG_WIN << "Basket[" + folderName() + "]: <font color=red>FAILED to save</font>!";
940         return false;
941 #ifdef HAVE_BALOO
942     } else {
943         //The .basket file is saved; now updating the Metadata in Nepomuk
944         DEBUG_WIN << "NepomukIntegration: Updating Basket[" + folderName() + "]:"; // <font color=red>Updating Metadata</font>!";
945         nepomukIntegration::updateMetadata(this);
946 #endif
947     }
948 
949     Global::bnpView->setUnsavedStatus(false);
950 
951 
952     m_commitdelay.start(10000); //delay is 10 seconds
953 
954     return true;
955 }
956 
commitEdit()957 void BasketScene::commitEdit()
958 {
959     GitWrapper::commitBasket(this);
960 }
961 
aboutToBeActivated()962 void BasketScene::aboutToBeActivated()
963 {
964     if (m_finishLoadOnFirstShow) {
965         FOR_EACH_NOTE(note)
966         note->finishLazyLoad();
967 
968         //relayoutNotes(/*animate=*/false);
969         setFocusedNote(0); // So that during the focusInEvent that will come shortly, the FIRST note is focused.
970 
971         if (Settings::playAnimations() && !decoration()->filterBar()->filterData().isFiltering && Global::bnpView->currentBasket() == this) // No animation when filtering all!
972             animateLoad();//QTimer::singleShot( 0, this, SLOT(animateLoad()) );
973 
974         m_finishLoadOnFirstShow = false;
975     }
976 }
977 
reload()978 void BasketScene::reload()
979 {
980     closeEditor();
981     unbufferizeAll(); // Keep the memory footprint low
982 
983     m_firstNote = 0;
984 
985     m_loaded = false;
986     m_loadingLaunched = false;
987 
988     invalidate();
989 }
990 
load()991 void BasketScene::load()
992 {
993     // Load only once:
994     if (m_loadingLaunched)
995         return;
996     m_loadingLaunched = true;
997 
998     DEBUG_WIN << "Basket[" + folderName() + "]: Loading...";
999     QDomDocument *doc = 0;
1000     QString content;
1001 
1002     // Load properties
1003     if (loadFromFile(fullPath() + ".basket", &content)) {
1004         doc = new QDomDocument("basket");
1005         if (! doc->setContent(content)) {
1006             DEBUG_WIN << "Basket[" + folderName() + "]: <font color=red>FAILED to parse XML</font>!";
1007             delete doc;
1008             doc = 0;
1009         }
1010     }
1011     if (isEncrypted())
1012         DEBUG_WIN << "Basket is encrypted.";
1013     if (! doc) {
1014         DEBUG_WIN << "Basket[" + folderName() + "]: <font color=red>FAILED to load</font>!";
1015         m_loadingLaunched = false;
1016         if (isEncrypted())
1017             m_locked = true;
1018         Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar
1019         return;
1020     }
1021     m_locked = false;
1022 
1023     QDomElement docElem = doc->documentElement();
1024     QDomElement properties = XMLWork::getElement(docElem, "properties");
1025 
1026     loadProperties(properties); // Since we are loading, this time the background image will also be loaded!
1027     // Now that the background image is loaded and subscribed, we display it during the load process:
1028     delete doc;
1029 
1030     //BEGIN Compatibility with 0.6.0 Pre-Alpha versions:
1031     QDomElement notes = XMLWork::getElement(docElem, "notes");
1032     if (notes.isNull())
1033         notes = XMLWork::getElement(docElem, "items");
1034     m_watcher->stopScan();
1035     m_shouldConvertPlainTextNotes = false; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded!
1036 
1037     // Load notes
1038     m_finishLoadOnFirstShow = (Global::bnpView->currentBasket() != this);
1039     loadNotes(notes, 0L);
1040     if (m_shouldConvertPlainTextNotes)
1041         convertTexts();
1042     m_watcher->startScan();
1043 
1044     signalCountsChanged();
1045     if (isColumnsLayout()) {
1046         // Count the number of columns:
1047         int columnsCount = 0;
1048         Note *column = firstNote();
1049         while (column) {
1050             ++columnsCount;
1051             column = column->next();
1052         }
1053         m_columnsCount = columnsCount;
1054     }
1055 
1056     relayoutNotes(false);
1057 
1058     // On application start, the current basket is not focused yet, so the focus rectangle is not shown when calling focusANote():
1059     if (Global::bnpView->currentBasket() == this)
1060         setFocus();
1061     focusANote();
1062 
1063     if (Settings::playAnimations() && !decoration()->filterBar()->filterData().isFiltering && Global::bnpView->currentBasket() == this) // No animation when filtering all!
1064         animateLoad();//QTimer::singleShot( 0, this, SLOT(animateLoad()) );
1065     else
1066         m_loaded = true;
1067     enableActions();
1068 }
1069 
filterAgain(bool andEnsureVisible)1070 void BasketScene::filterAgain(bool andEnsureVisible/* = true*/)
1071 {
1072     newFilter(decoration()->filterData(), andEnsureVisible);
1073 }
1074 
filterAgainDelayed()1075 void BasketScene::filterAgainDelayed()
1076 {
1077     QTimer::singleShot(0, this, SLOT(filterAgain()));
1078 }
1079 
newFilter(const FilterData & data,bool andEnsureVisible)1080 void BasketScene::newFilter(const FilterData &data, bool andEnsureVisible/* = true*/)
1081 {
1082     if (!isLoaded())
1083         return;
1084 
1085 //StopWatch::start(20);
1086 
1087     m_countFounds = 0;
1088     for (Note *note = firstNote(); note; note = note->next())
1089         m_countFounds += note->newFilter(data);
1090 
1091     relayoutNotes(true);
1092     signalCountsChanged();
1093 
1094     if (hasFocus())   // if (!hasFocus()), focusANote() will be called at focusInEvent()
1095         focusANote(); //  so, we avoid de-focus a note if it will be re-shown soon
1096     if (andEnsureVisible && m_focusedNote != 0L)
1097         ensureNoteVisible(m_focusedNote);
1098 
1099     Global::bnpView->setFiltering(data.isFiltering);
1100 
1101 //StopWatch::check(20);
1102 }
1103 
isFiltering()1104 bool BasketScene::isFiltering()
1105 {
1106     return decoration()->filterBar()->filterData().isFiltering;
1107 }
1108 
1109 
1110 
fullPath()1111 QString BasketScene::fullPath()
1112 {
1113     return Global::basketsFolder() + folderName();
1114 }
1115 
fullPathForFileName(const QString & fileName)1116 QString BasketScene::fullPathForFileName(const QString &fileName)
1117 {
1118     return fullPath() + fileName;
1119 }
1120 
fullPathForFolderName(const QString & folderName)1121 /*static*/ QString BasketScene::fullPathForFolderName(const QString &folderName)
1122 {
1123     return Global::basketsFolder() + folderName;
1124 }
1125 
1126 
setShortcut(QKeySequence shortcut,int action)1127 void BasketScene::setShortcut(QKeySequence shortcut, int action)
1128 {
1129     QList<QKeySequence> shortcuts{shortcut};
1130     if (action > 0) {
1131         KGlobalAccel::self()->setShortcut(m_action, shortcuts, KGlobalAccel::Autoloading);
1132         KGlobalAccel::self()->setDefaultShortcut(m_action, shortcuts, KGlobalAccel::Autoloading);
1133     }
1134     m_shortcutAction = action;
1135 }
1136 
activatedShortcut()1137 void BasketScene::activatedShortcut()
1138 {
1139     Global::bnpView->setCurrentBasket(this);
1140 
1141     if (m_shortcutAction == 1)
1142         Global::bnpView->setActive(true);
1143 }
1144 
signalCountsChanged()1145 void BasketScene::signalCountsChanged()
1146 {
1147     if (!m_timerCountsChanged.isActive()) {
1148         m_timerCountsChanged.setSingleShot(true);
1149         m_timerCountsChanged.start(0);
1150     }
1151 }
1152 
countsChangedTimeOut()1153 void BasketScene::countsChangedTimeOut()
1154 {
1155     emit countsChanged(this);
1156 }
1157 
1158 
BasketScene(QWidget * parent,const QString & folderName)1159 BasketScene::BasketScene(QWidget *parent, const QString &folderName)
1160         //: Q3ScrollView(parent)
1161         : QGraphicsScene(parent)
1162         , m_noActionOnMouseRelease(false)
1163         , m_ignoreCloseEditorOnNextMouseRelease(false)
1164         , m_pressPos(-100, -100)
1165         , m_canDrag(false)
1166         , m_firstNote(0)
1167         , m_columnsCount(1)
1168         , m_mindMap(false)
1169         , m_resizingNote(0L)
1170         , m_pickedResizer(0)
1171         , m_movingNote(0L)
1172         , m_pickedHandle(0 , 0)
1173 	, m_notesToBeDeleted()
1174         , m_clickedToInsert(0)
1175         , m_zoneToInsert(0)
1176         , m_posToInsert(-1 , -1)
1177         , m_isInsertPopupMenu(false)
1178         , m_insertMenuTitle(0)
1179         , m_animationTimeLine(0)
1180         , m_loaded(false)
1181         , m_loadingLaunched(false)
1182         , m_locked(false)
1183         , m_decryptBox(0)
1184         , m_button(0)
1185         , m_encryptionType(NoEncryption)
1186 #ifdef HAVE_LIBGPGME
1187         , m_gpg(0)
1188 #endif
1189         , m_backgroundPixmap(0)
1190         , m_opaqueBackgroundPixmap(0)
1191         , m_selectedBackgroundPixmap(0)
1192         , m_action(0)
1193         , m_shortcutAction(0)
1194         , m_hoveredNote(0)
1195         , m_hoveredZone(Note::None)
1196         , m_lockedHovering(false)
1197         , m_underMouse(false)
1198         , m_inserterRect()
1199         , m_inserterShown(false)
1200         , m_inserterSplit(true)
1201         , m_inserterTop(false)
1202         , m_inserterGroup(false)
1203         , m_lastDisableClick(QTime::currentTime())
1204         , m_isSelecting(false)
1205         , m_selectionStarted(false)
1206         , m_count(0)
1207         , m_countFounds(0)
1208         , m_countSelecteds(0)
1209         , m_folderName(folderName)
1210         , m_editor(0)
1211         , m_leftEditorBorder(0)
1212         , m_rightEditorBorder(0)
1213         , m_redirectEditActions(false)
1214 	, m_editorTrackMouseEvent(false)
1215         , m_editorWidth(-1)
1216         , m_editorHeight(-1)
1217         , m_doNotCloseEditor(false)
1218         , m_isDuringDrag(false)
1219         , m_draggedNotes()
1220         , m_focusedNote(0)
1221         , m_startOfShiftSelectionNote(0)
1222         , m_finishLoadOnFirstShow(false)
1223         , m_relayoutOnNextShow(false)
1224 {
1225     m_view = new BasketView(this);
1226     m_view->setFocusPolicy(Qt::StrongFocus);
1227     m_view->setAlignment(Qt::AlignLeft|Qt::AlignTop);
1228 
1229     m_action = new QAction(this);
1230     connect(m_action, SIGNAL(triggered()), this, SLOT(activatedShortcut()));
1231     m_action->setObjectName(folderName);
1232     KGlobalAccel::self()->setGlobalShortcut(m_action, (QKeySequence()));
1233     // We do this in the basket properties dialog (and keep it in sync with the
1234     // global one)
1235     KActionCollection* ac = Global::bnpView->actionCollection();
1236     ac->setShortcutsConfigurable(m_action, false);
1237 
1238     if (!m_folderName.endsWith("/"))
1239         m_folderName += "/";
1240 
1241 //    setDragAutoScroll(true);
1242 
1243     // By default, there is no corner widget: we set one for the corner area to be painted!
1244     // If we don't set one and there are two scrollbars present, slowly resizing up the window show graphical glitches in that area!
1245     m_cornerWidget = new QWidget(m_view);
1246     m_view->setCornerWidget(m_cornerWidget);
1247 
1248     m_view->viewport()->setAcceptDrops(true);
1249     m_view->viewport()->setMouseTracking(true);
1250     m_view->viewport()->setAutoFillBackground(false); // Do not clear the widget before paintEvent() because we always draw every pixels (faster and flicker-free)
1251 
1252     // File Watcher:
1253     m_watcher = new KDirWatch(this);
1254 
1255     connect(m_watcher,       SIGNAL(dirty(const QString&)),   this, SLOT(watchedFileModified(const QString&)));
1256     //connect(m_watcher,       SIGNAL(deleted(const QString&)), this, SLOT(watchedFileDeleted(const QString&)));
1257     connect(&m_watcherTimer, SIGNAL(timeout()),               this, SLOT(updateModifiedNotes()));
1258 
1259     // Various Connections:
1260     connect(&m_autoScrollSelectionTimer, SIGNAL(timeout()),   this, SLOT(doAutoScrollSelection()));
1261     connect(&m_timerCountsChanged,       SIGNAL(timeout()),   this, SLOT(countsChangedTimeOut()));
1262     connect(&m_inactivityAutoSaveTimer,  SIGNAL(timeout()),   this, SLOT(inactivityAutoSaveTimeout()));
1263     connect(&m_inactivityAutoLockTimer,  SIGNAL(timeout()),   this, SLOT(inactivityAutoLockTimeout()));
1264 
1265 #ifdef HAVE_LIBGPGME
1266     m_gpg = new KGpgMe();
1267 #endif
1268     m_locked = isFileEncrypted();
1269 
1270     //setup the delayed commit timer
1271     m_commitdelay.setSingleShot(true);
1272     connect(&m_commitdelay, SIGNAL(timeout()), this, SLOT(commitEdit()));
1273 }
1274 
enterEvent(QEvent *)1275 void BasketScene::enterEvent(QEvent *)
1276 {
1277     m_underMouse = true;
1278     doHoverEffects();
1279 }
1280 
leaveEvent(QEvent *)1281 void BasketScene::leaveEvent(QEvent *)
1282 {
1283     m_underMouse = false;
1284     doHoverEffects();
1285 
1286     if (m_lockedHovering)
1287 	return;
1288 
1289     removeInserter();
1290     if (m_hoveredNote) {
1291         m_hoveredNote->setHovered(false);
1292         m_hoveredNote->setHoveredZone(Note::None);
1293         m_hoveredNote->update();
1294     }
1295     m_hoveredNote = 0;
1296 }
1297 
setFocusIfNotInPopupMenu()1298 void BasketScene::setFocusIfNotInPopupMenu()
1299 {
1300     if (!qApp->activePopupWidget()) {
1301         if (isDuringEdit())
1302             m_editor->graphicsWidget()->setFocus();
1303         else
1304             setFocus();
1305     }
1306 }
1307 
mousePressEvent(QGraphicsSceneMouseEvent * event)1308 void BasketScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
1309 {
1310     // If user click the basket, focus it!
1311     // The focus is delayed because if the click results in showing a popup menu,
1312     // the interface flicker by showing the focused rectangle (as the basket gets focus)
1313     // and immediatly removing it (because the popup menu now have focus).
1314     if (!isDuringEdit())
1315         QTimer::singleShot(0, this, SLOT(setFocusIfNotInPopupMenu()));
1316 
1317     // Convenient variables:
1318     bool controlPressed = event->modifiers() & Qt::ControlModifier;
1319     bool shiftPressed   = event->modifiers() & Qt::ShiftModifier;
1320 
1321     // Do nothing if we disabled the click some milliseconds sooner.
1322     // For instance when a popup menu has been closed with click, we should not do action:
1323     if (event->button() == Qt::LeftButton && (qApp->activePopupWidget() || m_lastDisableClick.msecsTo(QTime::currentTime()) <= 80)) {
1324         doHoverEffects();
1325         m_noActionOnMouseRelease = true;
1326         // But we allow to select:
1327         // The code is the same as at the bottom of this method:
1328         if (event->button() == Qt::LeftButton) {
1329             m_selectionStarted = true;
1330             m_selectionBeginPoint = event->scenePos();
1331             m_selectionInvert = controlPressed || shiftPressed;
1332         }
1333 
1334         return;
1335     }
1336 
1337     // if we are editing and no control key are pressed
1338     if( m_editor && !shiftPressed && !controlPressed )
1339     {
1340 	//if the mouse is over the editor
1341     QPoint view_shift(m_view->horizontalScrollBar()->value(), m_view->verticalScrollBar()->value());
1342     QGraphicsWidget *widget = dynamic_cast<QGraphicsWidget*>(m_view->itemAt((event->scenePos() - view_shift).toPoint()));
1343 	if(widget && m_editor->graphicsWidget() == widget)
1344 	{
1345 	    if(event->button() == Qt::LeftButton)
1346 	    {
1347 	      m_editorTrackMouseEvent = true;
1348 	      m_editor->startSelection(event->scenePos());
1349 	      return;
1350 	    }
1351 	    else if(event->button() == Qt::MiddleButton)
1352         {
1353           m_editor->paste(event->scenePos(), QClipboard::Selection);
1354 	      return;
1355 	    }
1356     }
1357     }
1358 
1359     // Figure out what is the clicked note and zone:
1360     Note *clicked = noteAt(event->scenePos());
1361     if( m_editor && (!clicked || ( clicked && !(editedNote() == clicked) )) )
1362     {
1363         closeEditor();
1364     }
1365     Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None);
1366 
1367     // Popup Tags menu:
1368     if (zone == Note::TagsArrow && !controlPressed && !shiftPressed && event->button() != Qt::MidButton) {
1369         if (!clicked->allSelected())
1370             unselectAllBut(clicked);
1371         setFocusedNote(clicked); /// /// ///
1372         m_startOfShiftSelectionNote = clicked;
1373         m_noActionOnMouseRelease = true;
1374         popupTagsMenu(clicked);
1375 
1376         return;
1377     }
1378 
1379     if (event->button() == Qt::LeftButton) {
1380         // Prepare to allow drag and drop when moving mouse further:
1381         if ((zone == Note::Handle || zone == Note::Group) ||
1382                 (clicked && clicked->allSelected() &&
1383                  (zone == Note::TagsArrow || zone == Note::Custom0 || zone == Note::Content || zone == Note::Link /**/ || zone >= Note::Emblem0 /**/))) {
1384             if (!shiftPressed && !controlPressed) {
1385                 m_pressPos = event->scenePos(); // TODO: Allow to drag emblems to assign them to other notes. Then don't allow drag at Emblem0!!
1386 
1387 		m_canDrag  = true;
1388 
1389                // Saving where we were editing, because during a drag, the mouse can fly over the text edit and move the cursor position:
1390                  if (m_editor && m_editor->textEdit()) {
1391                      KTextEdit *editor = m_editor->textEdit();
1392                      m_textCursor = editor->textCursor();
1393                  }
1394             }
1395         }
1396 
1397         // Initializing Resizer move:
1398         if (zone == Note::Resizer) {
1399             m_resizingNote  = clicked;
1400             m_pickedResizer = event->scenePos().x() - clicked->rightLimit();
1401             m_noActionOnMouseRelease = true;
1402             m_lockedHovering = true;
1403 
1404             return;
1405         }
1406 
1407         // Select note(s):
1408         if (zone == Note::Handle || zone == Note::Group || (zone == Note::GroupExpander && (controlPressed || shiftPressed))) {
1409         //closeEditor();
1410             Note *end = clicked;
1411             if (clicked->isGroup() && shiftPressed) {
1412                 if (clicked->containsNote(m_startOfShiftSelectionNote)) {
1413                     m_startOfShiftSelectionNote = clicked->firstRealChild();
1414                     end = clicked->lastRealChild();
1415                 }
1416                 else if (clicked->firstRealChild()->isAfter(m_startOfShiftSelectionNote))
1417 		{
1418                     end = clicked->lastRealChild();
1419 		}
1420                 else
1421 		{
1422                     end = clicked->firstRealChild();
1423 		}
1424             }
1425             if (controlPressed && shiftPressed)
1426                 selectRange(m_startOfShiftSelectionNote, end, /*unselectOthers=*/false);
1427             else if (shiftPressed)
1428                 selectRange(m_startOfShiftSelectionNote, end);
1429             else if (controlPressed)
1430                 clicked->setSelectedRecursively(!clicked->allSelected());
1431             else if (!clicked->allSelected())
1432                 unselectAllBut(clicked);
1433             setFocusedNote(end); /// /// ///
1434             m_startOfShiftSelectionNote = (end->isGroup() ? end->firstRealChild() : end);
1435             //m_noActionOnMouseRelease = false;
1436             m_noActionOnMouseRelease = true;
1437 
1438             return;
1439         }
1440 
1441         // Folding/Unfolding group:
1442         if (zone == Note::GroupExpander) {
1443 	    clicked->toggleFolded();
1444 
1445 	    if( /*m_animationTimeLine == 0 && */Settings::playAnimations())
1446 	    {
1447         qWarning()<<"Folding animation to be done";
1448 	    }
1449 
1450 	    relayoutNotes(true);
1451             m_noActionOnMouseRelease = true;
1452 
1453             return;
1454         }
1455     }
1456 
1457     // Popup menu for tag emblems:
1458     if (event->button() == Qt::RightButton && zone >= Note::Emblem0) {
1459         if (!clicked->allSelected())
1460             unselectAllBut(clicked);
1461         setFocusedNote(clicked); /// /// ///
1462         m_startOfShiftSelectionNote = clicked;
1463         popupEmblemMenu(clicked, zone - Note::Emblem0);
1464         m_noActionOnMouseRelease = true;
1465 
1466         return;
1467     }
1468 
1469     // Insertion Popup Menu:
1470     if ((event->button() == Qt::RightButton)
1471             && ((!clicked && isFreeLayout())
1472                 || (clicked
1473                     && (zone == Note::TopInsert
1474                         || zone == Note::TopGroup
1475                         || zone == Note::BottomInsert
1476                         || zone == Note::BottomGroup
1477                         || zone == Note::BottomColumn)))) {
1478         unselectAll();
1479         m_clickedToInsert = clicked;
1480         m_zoneToInsert    = zone;
1481         m_posToInsert     = event->scenePos();
1482 
1483         QMenu menu(m_view);
1484         menu.addActions(Global::bnpView->popupMenu("insert_popup")->actions());
1485 
1486         // If we already added a title, remove it because it would be kept and
1487         // then added several times.
1488         if (m_insertMenuTitle && menu.actions().contains(m_insertMenuTitle))
1489             menu.removeAction(m_insertMenuTitle);
1490 
1491         QAction *first = menu.actions().value(0);
1492 
1493         // i18n: Verbs (for the "insert" menu)
1494         if (zone == Note::TopGroup || zone == Note::BottomGroup)
1495             m_insertMenuTitle = menu.insertSection(first, i18n("Group"));
1496         else
1497             m_insertMenuTitle = menu.insertSection(first, i18n("Insert"));
1498 
1499         setInsertPopupMenu();
1500         connect(&menu, SIGNAL(aboutToHide()),  this, SLOT(delayedCancelInsertPopupMenu()));
1501         connect(&menu, SIGNAL(aboutToHide()),  this, SLOT(unlockHovering()));
1502         connect(&menu, SIGNAL(aboutToHide()),  this, SLOT(disableNextClick()));
1503         connect(&menu, SIGNAL(aboutToHide()),  this, SLOT(hideInsertPopupMenu()));
1504         doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually!
1505         m_lockedHovering = true;
1506         menu.exec(QCursor::pos());
1507         m_noActionOnMouseRelease = true;
1508         return;
1509     }
1510 
1511     // Note Context Menu:
1512     if (event->button() == Qt::RightButton && clicked && !clicked->isColumn() && zone != Note::Resizer) {
1513         if (!clicked->allSelected())
1514             unselectAllBut(clicked);
1515         setFocusedNote(clicked); /// /// ///
1516         if (editedNote() == clicked) {
1517             closeEditor(false);
1518             clicked->setSelected(true);
1519         }
1520         m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked);
1521         QMenu* menu = Global::bnpView->popupMenu("note_popup");
1522         connect(menu, SIGNAL(aboutToHide()),  this, SLOT(unlockHovering()));
1523         connect(menu, SIGNAL(aboutToHide()),  this, SLOT(disableNextClick()));
1524         doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually!
1525         m_lockedHovering = true;
1526         menu->exec(QCursor::pos());
1527         m_noActionOnMouseRelease = true;
1528         return;
1529     }
1530 
1531     // Paste selection under cursor (but not "create new primary note under cursor" because this is on moveRelease):
1532     if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) {
1533         if ((Settings::middleAction() != 0) && (event->modifiers() == Qt::ShiftModifier)) {
1534             m_clickedToInsert = clicked;
1535             m_zoneToInsert    = zone;
1536             m_posToInsert     = event->scenePos();
1537             //closeEditor();
1538             removeInserter();                     // If clicked at an insertion line and the new note shows a dialog for editing,
1539             NoteType::Id type = (NoteType::Id)0;  //  hide that inserter before the note edition instead of after the dialog is closed
1540             switch (Settings::middleAction()) {
1541             case 1:
1542                 m_isInsertPopupMenu = true;
1543                 pasteNote();
1544                 break;
1545             case 2: type = NoteType::Image;    break;
1546             case 3: type = NoteType::Link;     break;
1547             case 4: type = NoteType::Launcher; break;
1548             default:
1549                 m_noActionOnMouseRelease = false;
1550                 return;
1551             }
1552             if (type != 0) {
1553                 m_ignoreCloseEditorOnNextMouseRelease = true;
1554                 Global::bnpView->insertEmpty(type);
1555             }
1556         } else {
1557             if (clicked)
1558                 zone = clicked->zoneAt(event->scenePos() - QPoint(clicked->x(), clicked->y()), true);
1559             //closeEditor();
1560             clickedToInsert(event, clicked, zone);
1561             save();
1562         }
1563         m_noActionOnMouseRelease = true;
1564         return;
1565     }
1566 
1567     // Finally, no action has been done durint pressEvent, so an action can be done on releaseEvent:
1568     m_noActionOnMouseRelease = false;
1569 
1570     /* Selection scenario:
1571      * On contentsMousePressEvent, put m_selectionStarted to true and init Begin and End selection point.
1572      * On contentsMouseMoveEvent, if m_selectionStarted, update End selection point, update selection rect,
1573      * and if it's larger, switching to m_isSelecting mode: we can draw the selection rectangle.
1574      */
1575     // Prepare selection:
1576     if (event->button() == Qt::LeftButton) {
1577         m_selectionStarted = true;
1578         m_selectionBeginPoint = event->scenePos();
1579         // We usualy invert the selection with the Ctrl key, but some environements (like GNOME or The Gimp) do it with the Shift key.
1580         // Since the Shift key has no specific usage, we allow to invert selection ALSO with Shift for Gimp people
1581         m_selectionInvert = controlPressed || shiftPressed;
1582     }
1583 }
1584 
delayedCancelInsertPopupMenu()1585 void BasketScene::delayedCancelInsertPopupMenu()
1586 {
1587     QTimer::singleShot(0, this, SLOT(cancelInsertPopupMenu()));
1588 }
1589 
contextMenuEvent(QGraphicsSceneContextMenuEvent * event)1590 void BasketScene::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
1591 {
1592     if (event->reason() == QGraphicsSceneContextMenuEvent::Keyboard) {
1593         if (countFounds/*countShown*/() == 0) { // TODO: Count shown!!
1594             QMenu *menu = Global::bnpView->popupMenu("insert_popup");
1595             setInsertPopupMenu();
1596             connect(menu, SIGNAL(aboutToHide()),  this, SLOT(delayedCancelInsertPopupMenu()));
1597             connect(menu, SIGNAL(aboutToHide()),  this, SLOT(unlockHovering()));
1598             connect(menu, SIGNAL(aboutToHide()),  this, SLOT(disableNextClick()));
1599             removeInserter();
1600             m_lockedHovering = true;
1601             menu->exec(m_view->mapToGlobal(QPoint(0, 0)));
1602         } else {
1603             if (! m_focusedNote->isSelected())
1604                 unselectAllBut(m_focusedNote);
1605             setFocusedNote(m_focusedNote); /// /// ///
1606             m_startOfShiftSelectionNote = (m_focusedNote->isGroup() ? m_focusedNote->firstRealChild() : m_focusedNote);
1607             // Popup at bottom (or top) of the focused note, if visible :
1608             QMenu *menu = Global::bnpView->popupMenu("note_popup");
1609             connect(menu, SIGNAL(aboutToHide()),  this, SLOT(unlockHovering()));
1610             connect(menu, SIGNAL(aboutToHide()),  this, SLOT(disableNextClick()));
1611             doHoverEffects(m_focusedNote, Note::Content); // In the case where another popup menu was open, we should do that manually!
1612             m_lockedHovering = true;
1613             menu->exec(noteVisibleRect(m_focusedNote).bottomLeft().toPoint());
1614         }
1615     }
1616 }
1617 
noteVisibleRect(Note * note)1618 QRectF BasketScene::noteVisibleRect(Note *note)
1619 {
1620     QRectF rect(QPointF(note->x(), note->y()), QSizeF(note->width(), note->height()));
1621     QPoint basketPoint = m_view->mapToGlobal(QPoint(0, 0));
1622     rect.moveTopLeft(rect.topLeft() + basketPoint + QPoint(m_view->frameWidth(), m_view->frameWidth()));
1623 
1624     // Now, rect contain the global note rectangle on the screen.
1625     // We have to clip it by the basket widget :
1626 //    if (rect.bottom() > basketPoint.y() + visibleHeight() + 1) { // Bottom too... bottom
1627 //        rect.setBottom(basketPoint.y() + visibleHeight() + 1);
1628     if (rect.bottom() > basketPoint.y() + m_view->viewport()->height() + 1) { // Bottom too... bottom
1629         rect.setBottom(basketPoint.y() + m_view->viewport()->height() + 1);
1630         if (rect.height() <= 0) // Have at least one visible pixel of height
1631             rect.setTop(rect.bottom());
1632     }
1633     if (rect.top() < basketPoint.y() + m_view->frameWidth()) { // Top too... top
1634         rect.setTop(basketPoint.y() + m_view->frameWidth());
1635         if (rect.height() <= 0)
1636             rect.setBottom(rect.top());
1637     }
1638 //    if (rect.right() > basketPoint.x() + visibleWidth() + 1) { // Right too... right
1639 //        rect.setRight(basketPoint.x() + visibleWidth() + 1);
1640     if (rect.right() > basketPoint.x() + m_view->viewport()->width() + 1) { // Right too... right
1641         rect.setRight(basketPoint.x() + m_view->viewport()->width() + 1);
1642         if (rect.width() <= 0) // Have at least one visible pixel of width
1643             rect.setLeft(rect.right());
1644     }
1645     if (rect.left() < basketPoint.x() + m_view->frameWidth()) { // Left too... left
1646         rect.setLeft(basketPoint.x() + m_view->frameWidth());
1647         if (rect.width() <= 0)
1648             rect.setRight(rect.left());
1649     }
1650     return rect;
1651 }
1652 
disableNextClick()1653 void BasketScene::disableNextClick()
1654 {
1655     m_lastDisableClick = QTime::currentTime();
1656 }
1657 
recomputeAllStyles()1658 void BasketScene::recomputeAllStyles()
1659 {
1660     FOR_EACH_NOTE(note)
1661     note->recomputeAllStyles();
1662 }
1663 
removedStates(const QList<State * > & deletedStates)1664 void BasketScene::removedStates(const QList<State*> &deletedStates)
1665 {
1666     bool modifiedBasket = false;
1667 
1668     FOR_EACH_NOTE(note)
1669     if (note->removedStates(deletedStates))
1670         modifiedBasket = true;
1671 
1672     if (modifiedBasket)
1673         save();
1674 }
1675 
insertNote(Note * note,Note * clicked,int zone,const QPointF & pos,bool animateNewPosition)1676 void BasketScene::insertNote(Note *note, Note *clicked, int zone, const QPointF &pos, bool animateNewPosition)
1677 {
1678     if (!note) {
1679         qDebug() << "Wanted to insert NO note";
1680        return;
1681     }
1682 
1683     if (clicked && zone == Note::BottomColumn) {
1684         // When inserting at the bottom of a column, it's obvious the new note SHOULD inherit tags.
1685         // We ensure that by changing the insertion point after the last note of the column:
1686         Note *last = clicked->lastChild();
1687         if (last) {
1688             clicked = last;
1689             zone = Note::BottomInsert;
1690         }
1691     }
1692 
1693     /// Insertion at the bottom of a column:
1694     if (clicked && zone == Note::BottomColumn) {
1695         note->setWidth(clicked->rightLimit() - clicked->x());
1696         Note *lastChild = clicked->lastChild();
1697         if (!animateNewPosition || !Settings::playAnimations())
1698             for (Note *n = note; n; n = n->next()) {
1699                 n->setXRecursively(clicked->x());
1700                 n->setYRecursively((lastChild ? lastChild : clicked)->bottom() + 1);
1701             }
1702         appendNoteIn(note, clicked);
1703 
1704         /// Insertion relative to a note (top/bottom, insert/group):
1705     } else if (clicked) {
1706         note->setWidth(clicked->width());
1707         if (!animateNewPosition || !Settings::playAnimations())
1708             for (Note *n = note; n; n = n->next()) {
1709                 if (zone == Note::TopGroup || zone == Note::BottomGroup)
1710                     n->setXRecursively(clicked->x() + Note::GROUP_WIDTH);
1711                 else
1712                     n->setXRecursively(clicked->x());
1713                 if (zone == Note::TopInsert || zone == Note::TopGroup)
1714                     n->setYRecursively(clicked->y());
1715                 else
1716                     n->setYRecursively(clicked->bottom() + 1);
1717             }
1718 
1719         if (zone == Note::TopInsert)    {
1720             appendNoteBefore(note, clicked);
1721         } else if (zone == Note::BottomInsert) {
1722             appendNoteAfter(note,  clicked);
1723         } else if (zone == Note::TopGroup)     {
1724             groupNoteBefore(note,  clicked);
1725         } else if (zone == Note::BottomGroup)  {
1726             groupNoteAfter(note,   clicked);
1727         }
1728 
1729         /// Free insertion:
1730     } else if (isFreeLayout()) {
1731         // Group if note have siblings:
1732         if (note->next()) {
1733             Note *group = new Note(this);
1734             for (Note *n = note; n; n = n->next())
1735                 n->setParentNote(group);
1736             group->setFirstChild(note);
1737             note = group;
1738         }
1739         // Insert at cursor position:
1740         const int initialWidth = 250;
1741         note->setWidth(note->isGroup() ? Note::GROUP_WIDTH : initialWidth);
1742         if (note->isGroup() && note->firstChild())
1743             note->setInitialHeight(note->firstChild()->height());
1744         //note->setGroupWidth(initialWidth);
1745         /*if (animateNewPosition && Settings::playAnimations())
1746             note->setFinalPosition(pos.x(), pos.y());
1747         else {*/
1748             note->setXRecursively(pos.x());
1749             note->setYRecursively(pos.y());
1750         //}
1751         appendNoteAfter(note, lastNote());
1752     }
1753 
1754     relayoutNotes(true);
1755 }
1756 
clickedToInsert(QGraphicsSceneMouseEvent * event,Note * clicked,int zone)1757 void BasketScene::clickedToInsert(QGraphicsSceneMouseEvent *event, Note *clicked, /*Note::Zone*/int zone)
1758 {
1759     Note *note;
1760     if (event->button() == Qt::MidButton)
1761         note = NoteFactory::dropNote(QApplication::clipboard()->mimeData(QClipboard::Selection), this);
1762     else
1763         note = NoteFactory::createNoteText("", this);
1764 
1765     if (!note)
1766         return;
1767 
1768     insertNote(note, clicked, zone, QPointF(event->scenePos()), /*animateNewPosition=*/false);
1769 
1770 //  ensureNoteVisible(lastInsertedNote()); // TODO: in insertNote()
1771 
1772     if (event->button() != Qt::MidButton) {
1773         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.
1774         closeEditor();
1775         noteEdit(note, /*justAdded=*/true);
1776     }
1777 }
1778 
dragEnterEvent(QGraphicsSceneDragDropEvent * event)1779 void BasketScene::dragEnterEvent(QGraphicsSceneDragDropEvent *event)
1780 {
1781     m_isDuringDrag = true;
1782     Global::bnpView->updateStatusBarHint();
1783     if (NoteDrag::basketOf(event->mimeData()) == this) {
1784         m_draggedNotes = NoteDrag::notesOf(event);
1785         NoteDrag::saveNoteSelectionToList(selectedNotes());
1786     }
1787     event->accept();
1788 }
1789 
dragMoveEvent(QGraphicsSceneDragDropEvent * event)1790 void BasketScene::dragMoveEvent(QGraphicsSceneDragDropEvent *event)
1791 {
1792 //  m_isDuringDrag = true;
1793 
1794 //  if (isLocked())
1795 //      return;
1796 
1797 //  FIXME: viewportToContents does NOT work !!!
1798 //  QPoint pos = viewportToContents(event->pos());
1799 //  QPoint pos( event->pos().x() + contentsX(), event->pos().y() + contentsY() );
1800 
1801 //  if (insertAtCursorPos())
1802 //      computeInsertPlace(pos);
1803     doHoverEffects(event->scenePos());
1804 
1805 //  showFrameInsertTo();
1806     if (isFreeLayout() || noteAt(event->scenePos())) // Cursor before rightLimit() or hovering the dragged source notes
1807         acceptDropEvent(event);
1808     else {
1809         event->setAccepted(false);
1810     }
1811 
1812     /*  Note *hoveredNote = noteAt(event->pos().x(), event->pos().y());
1813         if ( (isColumnsLayout() && !hoveredNote) || (draggedNotes().contains(hoveredNote)) ) {
1814             event->acceptAction(false);
1815             event->accept(false);
1816         } else
1817             acceptDropEvent(event);*/
1818 
1819     // A workarround since QScrollView::dragAutoScroll seem to have no effect :
1820 //  ensureVisible(event->pos().x() + contentsX(), event->pos().y() + contentsY(), 30, 30);
1821 //  QScrollView::dragMoveEvent(event);
1822 }
1823 
dragLeaveEvent(QGraphicsSceneDragDropEvent *)1824 void BasketScene::dragLeaveEvent(QGraphicsSceneDragDropEvent *)
1825 {
1826 //  resetInsertTo();
1827     m_isDuringDrag = false;
1828     m_draggedNotes.clear();
1829     NoteDrag::selectedNotes.clear();
1830     m_noActionOnMouseRelease = true;
1831     emit resetStatusBarText();
1832     doHoverEffects();
1833 }
1834 
dropEvent(QGraphicsSceneDragDropEvent * event)1835 void BasketScene::dropEvent(QGraphicsSceneDragDropEvent *event)
1836 {
1837     QPointF pos = event->scenePos();
1838     qDebug() << "Drop Event at position " << pos.x() << ":" << pos.y();
1839 
1840     m_isDuringDrag = false;
1841     emit resetStatusBarText();
1842 
1843 //  if (isLocked())
1844 //      return;
1845 
1846     // Do NOT check the bottom&right borders.
1847     // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars),
1848     // the note is first removed, and relayoutNotes() compute the new height that is smaller
1849     // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!!
1850     // Should, of course, not return 0:
1851     Note *clicked = noteAt(pos);
1852 
1853     if (NoteFactory::movingNotesInTheSameBasket(event->mimeData(), this, event->dropAction()) && event->dropAction() == Qt::MoveAction) {
1854         m_doNotCloseEditor = true;
1855     }
1856 
1857     Note *note = NoteFactory::dropNote(event->mimeData(), this, true, event->dropAction(), dynamic_cast<Note*>(event->source()));
1858 
1859     if (note) {
1860         Note::Zone zone = (clicked ? clicked->zoneAt(pos - QPointF(clicked->x(), clicked->y()), /*toAdd=*/true) : Note::None);
1861         bool animateNewPosition = NoteFactory::movingNotesInTheSameBasket(event->mimeData(), this, event->dropAction());
1862         if (animateNewPosition) {
1863             FOR_EACH_NOTE(n)
1864             n->setOnTop(false);
1865             // FOR_EACH_NOTE_IN_CHUNK(note)
1866             for (Note *n = note; n; n = n->next())
1867                 n->setOnTop(true);
1868         }
1869 
1870         insertNote(note, clicked, zone, pos, animateNewPosition);
1871 
1872         // If moved a note on bottom, contentsHeight has been disminished, then view scrolled up, and we should re-scroll the view down:
1873         ensureNoteVisible(note);
1874 
1875 //      if (event->button() != Qt::MidButton) {
1876 //          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.
1877 //      }
1878 
1879 //      resetInsertTo();
1880 //      doHoverEffects(); called by insertNote()
1881         save();
1882     }
1883 
1884     m_draggedNotes.clear();
1885     NoteDrag::selectedNotes.clear();
1886 
1887     m_doNotCloseEditor = false;
1888     // When starting the drag, we saved where we were editing.
1889     // This is because during a drag, the mouse can fly over the text edit and move the cursor position, and even HIDE the cursor.
1890     // So we re-show the cursor, and re-position it at the right place:
1891     if (m_editor && m_editor->textEdit()) {
1892         KTextEdit *editor = m_editor->textEdit();
1893         editor->setTextCursor(m_textCursor);
1894     }
1895 }
1896 
1897 // handles dropping of a note to basket that is not shown
1898 // (usually through its entry in the basket list)
blindDrop(QGraphicsSceneDragDropEvent * event)1899 void BasketScene::blindDrop(QGraphicsSceneDragDropEvent* event)
1900 {
1901     if (!m_isInsertPopupMenu && redirectEditActions()) {
1902         if (m_editor->textEdit())
1903             m_editor->textEdit()->paste();
1904         else if (m_editor->lineEdit())
1905             m_editor->lineEdit()->paste();
1906     } else {
1907         if (!isLoaded()) {
1908             Global::bnpView->showPassiveLoading(this);
1909             load();
1910         }
1911         closeEditor();
1912         unselectAll();
1913         Note *note = NoteFactory::dropNote(event->mimeData(), this, true, event->dropAction(),
1914                                            dynamic_cast<Note*>(event->source()));
1915         if (note) {
1916             insertCreatedNote(note);
1917             //unselectAllBut(note);
1918             if (Settings::usePassivePopup())
1919                 Global::bnpView->showPassiveDropped(i18n("Dropped to basket <i>%1</i>", m_basketName));
1920         }
1921     }
1922     save();
1923 }
1924 
blindDrop(const QMimeData * mimeData,Qt::DropAction dropAction,QObject * source)1925 void BasketScene::blindDrop(const QMimeData *mimeData, Qt::DropAction dropAction, QObject *source)
1926 {
1927     if (!m_isInsertPopupMenu && redirectEditActions()) {
1928         if (m_editor->textEdit())
1929             m_editor->textEdit()->paste();
1930         else if (m_editor->lineEdit())
1931             m_editor->lineEdit()->paste();
1932     } else {
1933         if (!isLoaded()) {
1934             Global::bnpView->showPassiveLoading(this);
1935             load();
1936         }
1937         closeEditor();
1938         unselectAll();
1939         Note *note = NoteFactory::dropNote(mimeData, this, true, dropAction,
1940                                            dynamic_cast<Note*>(source));
1941         if (note) {
1942             insertCreatedNote(note);
1943             //unselectAllBut(note);
1944             if (Settings::usePassivePopup())
1945                 Global::bnpView->showPassiveDropped(i18n("Dropped to basket <i>%1</i>", m_basketName));
1946         }
1947     }
1948     save();
1949 }
1950 
insertEmptyNote(int type)1951 void BasketScene::insertEmptyNote(int type)
1952 {
1953     if (!isLoaded())
1954         load();
1955     if (isDuringEdit())
1956         closeEditor();
1957     Note *note = NoteFactory::createEmptyNote((NoteType::Id)type, this);
1958     insertCreatedNote(note/*, / *edit=* /true*/);
1959     noteEdit(note, /*justAdded=*/true);
1960 }
1961 
insertWizard(int type)1962 void BasketScene::insertWizard(int type)
1963 {
1964     saveInsertionData();
1965     Note *note = 0;
1966     switch (type) {
1967     default:
1968     case 1: note = NoteFactory::importKMenuLauncher(this); break;
1969     case 2: note = NoteFactory::importIcon(this);          break;
1970     case 3: note = NoteFactory::importFileContent(this);   break;
1971     }
1972     if (!note)
1973         return;
1974     restoreInsertionData();
1975     insertCreatedNote(note);
1976     unselectAllBut(note);
1977     resetInsertionData();
1978 }
1979 
insertColor(const QColor & color)1980 void BasketScene::insertColor(const QColor &color)
1981 {
1982     Note *note = NoteFactory::createNoteColor(color, this);
1983     restoreInsertionData();
1984     insertCreatedNote(note);
1985     unselectAllBut(note);
1986     resetInsertionData();
1987 }
1988 
insertImage(const QPixmap & image)1989 void BasketScene::insertImage(const QPixmap &image)
1990 {
1991     Note *note = NoteFactory::createNoteImage(image, this);
1992     restoreInsertionData();
1993     insertCreatedNote(note);
1994     unselectAllBut(note);
1995     resetInsertionData();
1996 }
1997 
pasteNote(QClipboard::Mode mode)1998 void BasketScene::pasteNote(QClipboard::Mode mode)
1999 {
2000     if (!m_isInsertPopupMenu && redirectEditActions()) {
2001         if (m_editor->textEdit())
2002             m_editor->textEdit()->paste();
2003         else if (m_editor->lineEdit())
2004             m_editor->lineEdit()->paste();
2005     } else {
2006         if (!isLoaded()) {
2007             Global::bnpView->showPassiveLoading(this);
2008             load();
2009         }
2010         closeEditor();
2011         unselectAll();
2012         Note *note = NoteFactory::dropNote(QApplication::clipboard()->mimeData(mode), this);
2013         if (note) {
2014             insertCreatedNote(note);
2015             //unselectAllBut(note);
2016         }
2017     }
2018 }
2019 
insertCreatedNote(Note * note)2020 void BasketScene::insertCreatedNote(Note *note)
2021 {
2022     // Get the insertion data if the user clicked inside the basket:
2023     Note *clicked = m_clickedToInsert;
2024     int zone      = m_zoneToInsert;
2025     QPointF pos    = m_posToInsert;
2026 
2027     // If it isn't the case, use the default position:
2028     if (!clicked && (pos.x() < 0 || pos.y() < 0)) {
2029         // Insert right after the focused note:
2030         focusANote();
2031         if (m_focusedNote) {
2032             clicked = m_focusedNote;
2033             zone    = (m_focusedNote->isFree() ? Note::BottomGroup : Note::BottomInsert);
2034             pos     = QPointF(m_focusedNote->x(), m_focusedNote->bottom());
2035             // Insert at the end of the last column:
2036         } else if (isColumnsLayout()) {
2037             Note *column = /*(Settings::newNotesPlace == 0 ?*/ firstNote() /*: lastNote())*/;
2038             /*if (Settings::newNotesPlace == 0 && column->firstChild()) { // On Top, if at least one child in the column
2039                 clicked = column->firstChild();
2040                 zone    = Note::TopInsert;
2041             } else { // On Bottom*/
2042             clicked = column;
2043             zone    = Note::BottomColumn;
2044             /*}*/
2045             // Insert at free position:
2046         } else {
2047             pos = QPointF(0, 0);
2048         }
2049     }
2050 
2051     insertNote(note, clicked, zone, pos);
2052 //  ensureNoteVisible(lastInsertedNote());
2053     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.
2054 //  resetInsertTo();
2055     save();
2056 }
2057 
saveInsertionData()2058 void BasketScene::saveInsertionData()
2059 {
2060     m_savedClickedToInsert = m_clickedToInsert;
2061     m_savedZoneToInsert    = m_zoneToInsert;
2062     m_savedPosToInsert     = m_posToInsert;
2063 }
2064 
restoreInsertionData()2065 void BasketScene::restoreInsertionData()
2066 {
2067     m_clickedToInsert = m_savedClickedToInsert;
2068     m_zoneToInsert    = m_savedZoneToInsert;
2069     m_posToInsert     = m_savedPosToInsert;
2070 }
2071 
resetInsertionData()2072 void BasketScene::resetInsertionData()
2073 {
2074     m_clickedToInsert = 0;
2075     m_zoneToInsert    = 0;
2076     m_posToInsert     = QPoint(-1, -1);
2077 }
2078 
hideInsertPopupMenu()2079 void BasketScene::hideInsertPopupMenu()
2080 {
2081     QTimer::singleShot(50/*ms*/, this, SLOT(timeoutHideInsertPopupMenu()));
2082 }
2083 
timeoutHideInsertPopupMenu()2084 void BasketScene::timeoutHideInsertPopupMenu()
2085 {
2086     resetInsertionData();
2087 }
2088 
acceptDropEvent(QGraphicsSceneDragDropEvent * event,bool preCond)2089 void BasketScene::acceptDropEvent(QGraphicsSceneDragDropEvent *event, bool preCond)
2090 {
2091     // FIXME: Should not accept all actions! Or not all actions (link not supported?!)
2092     //event->acceptAction(preCond && 1);
2093     //event->accept(preCond);
2094     event->setAccepted(preCond);
2095 }
2096 
mouseReleaseEvent(QGraphicsSceneMouseEvent * event)2097 void BasketScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
2098 {
2099     // Now disallow drag and mouse redirection
2100     m_canDrag = false;
2101 
2102     if(m_editorTrackMouseEvent)
2103     {
2104       m_editorTrackMouseEvent = false;
2105       m_editor->endSelection(m_pressPos);
2106       return;
2107     }
2108 
2109     // Cancel Resizer move:
2110     if (m_resizingNote) {
2111         m_resizingNote  = 0;
2112         m_pickedResizer = 0;
2113         m_lockedHovering = false;
2114         doHoverEffects();
2115         save();
2116     }
2117 
2118     // Cancel Note move:
2119     /*  if (m_movingNote) {
2120             m_movingNote   = 0;
2121             m_pickedHandle = QPoint(0, 0);
2122             m_lockedHovering = false;
2123             //doHoverEffects();
2124             save();
2125     }*/
2126 
2127     // Cancel Selection rectangle:
2128     if (m_isSelecting) {
2129         m_isSelecting = false;
2130         stopAutoScrollSelection();
2131         resetWasInLastSelectionRect();
2132         doHoverEffects();
2133 	invalidate(m_selectionRect);
2134     }
2135     m_selectionStarted = false;
2136 
2137     Note *clicked = noteAt(event->scenePos());
2138     Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None);
2139     if ((zone == Note::Handle || zone == Note::Group) && editedNote() && editedNote() == clicked) {
2140         if (m_ignoreCloseEditorOnNextMouseRelease)
2141             m_ignoreCloseEditorOnNextMouseRelease = false;
2142         else {
2143             bool editedNoteStillThere = closeEditor();
2144             if (editedNoteStillThere)
2145                 //clicked->setSelected(true);
2146                 unselectAllBut(clicked);
2147         }
2148     }
2149 
2150 /*
2151     if (event->buttons() == 0 && (zone == Note::Group || zone == Note::Handle)) {
2152         closeEditor();
2153         unselectAllBut(clicked);
2154     }
2155 */
2156 
2157     // Do nothing if an action has already been made during mousePressEvent,
2158     // or if user made a selection and canceled it by regressing to a very small rectangle.
2159     if (m_noActionOnMouseRelease)
2160         return;
2161 
2162     // We immediately set it to true, to avoid actions set on mouseRelease if NO mousePress event has been triggered.
2163     // This is the case when a popup menu is shown, and user click to the basket area to close it:
2164     // the menu then receive the mousePress event and the basket area ONLY receive the mouseRelease event.
2165     // Obviously, nothing should be done in this case:
2166     m_noActionOnMouseRelease = true;
2167 
2168     if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) {
2169         if ((Settings::middleAction() != 0) && (event->modifiers() == Qt::ShiftModifier)) {
2170             m_clickedToInsert = clicked;
2171             m_zoneToInsert    = zone;
2172             m_posToInsert     = event->scenePos();
2173             closeEditor();
2174             removeInserter();                     // If clicked at an insertion line and the new note shows a dialog for editing,
2175             NoteType::Id type = (NoteType::Id)0;  //  hide that inserter before the note edition instead of after the dialog is closed
2176             switch (Settings::middleAction()) {
2177             case 5: type = NoteType::Color;    break;
2178             case 6:
2179                 Global::bnpView->grabScreenshot();
2180                 return;
2181             case 7:
2182                 Global::bnpView->slotColorFromScreen();
2183                 return;
2184             case 8:
2185                 Global::bnpView->insertWizard(3); // loadFromFile
2186                 return;
2187             case 9:
2188                 Global::bnpView->insertWizard(1); // importKMenuLauncher
2189                 return;
2190             case 10:
2191                 Global::bnpView->insertWizard(2); // importIcon
2192                 return;
2193             }
2194             if (type != 0) {
2195                 m_ignoreCloseEditorOnNextMouseRelease = true;
2196                 Global::bnpView->insertEmpty(type);
2197                 return;
2198             }
2199         }
2200     }
2201 
2202 //  Note *clicked = noteAt(event->pos().x(), event->pos().y());
2203     if (! clicked) {
2204         if (isFreeLayout() && event->button() == Qt::LeftButton) {
2205             clickedToInsert(event);
2206             save();
2207         }
2208 
2209         return;
2210     }
2211 //  Note::Zone zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()) );
2212 
2213     // Convenient variables:
2214     bool controlPressed = event->modifiers() & Qt::ControlModifier;
2215     bool shiftPressed   = event->modifiers() & Qt::ShiftModifier;
2216 
2217     if (clicked && zone != Note::None && zone != Note::BottomColumn && zone != Note::Resizer && (controlPressed || shiftPressed)) {
2218         if (controlPressed && shiftPressed)
2219             selectRange(m_startOfShiftSelectionNote, clicked, /*unselectOthers=*/false);
2220         else if (shiftPressed)
2221             selectRange(m_startOfShiftSelectionNote, clicked);
2222         else if (controlPressed)
2223             clicked->setSelectedRecursively(!clicked->allSelected());
2224         setFocusedNote(clicked); /// /// ///
2225         m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked);
2226         m_noActionOnMouseRelease = true;
2227 
2228         return;
2229     }
2230 
2231     // Switch tag states:
2232     if (zone >= Note::Emblem0) {
2233         if (event->button() == Qt::LeftButton) {
2234             int icons = -1;
2235             for (State::List::iterator it = clicked->states().begin(); it != clicked->states().end(); ++it) {
2236                 if (!(*it)->emblem().isEmpty())
2237                     icons++;
2238                 if (icons == zone - Note::Emblem0) {
2239                     State *state = (*it)->nextState();
2240                     if (!state)
2241                         return;
2242                     it = clicked->states().insert(it, state);
2243                     ++it;
2244                     clicked->states().erase(it);
2245                     clicked->recomputeStyle();
2246                     clicked->unbufferize();
2247                     clicked->update();
2248                     updateEditorAppearance();
2249                     filterAgain();
2250                     save();
2251                     break;
2252                 }
2253             }
2254 
2255             return;
2256         }/* else if (event->button() == Qt::RightButton) {
2257         popupEmblemMenu(clicked, zone - Note::Emblem0);
2258         return;
2259     }*/
2260     }
2261 
2262     // Insert note or past clipboard:
2263     QString  text;
2264 //  Note *note;
2265     QString  link;
2266     //int zone = zone;
2267     if (event->button() == Qt::MidButton && zone == Note::Resizer)
2268         return; //zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()), true );
2269     if (event->button() == Qt::RightButton && (clicked->isColumn() || zone == Note::Resizer))
2270 	return;
2271     if (clicked->isGroup() && zone == Note::None)
2272 	return;
2273     switch (zone) {
2274     case Note::Handle:
2275     case Note::Group:
2276         // We select note on mousePress if it was unselected or Ctrl is pressed.
2277         // But the user can want to drag select_s_ notes, so it the note is selected, we only select it alone on mouseRelease:
2278         if (event->buttons() == 0) {
2279             qDebug() << "EXEC";
2280             if (!(event->modifiers() & Qt::ControlModifier) && clicked->allSelected())
2281                 unselectAllBut(clicked);
2282             if (zone == Note::Handle && isDuringEdit() && editedNote() == clicked) {
2283                 closeEditor();
2284                 clicked->setSelected(true);
2285             }
2286         }
2287         break;
2288 
2289     case Note::Custom0:
2290         //unselectAllBut(clicked);
2291         setFocusedNote(clicked);
2292         noteOpen(clicked);
2293         break;
2294 
2295     case Note::GroupExpander:
2296     case Note::TagsArrow:
2297         break;
2298 
2299     case Note::Link:
2300         link = clicked->linkAt(event->scenePos() - QPoint(clicked->x(), clicked->y()));
2301         if (! link.isEmpty()) {
2302             if (link == "basket-internal-remove-basket") {
2303                 // 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."
2304                 Global::bnpView->doBasketDeletion(this);
2305             } else if (link == "basket-internal-import") {
2306                 QMenu *menu = Global::bnpView->popupMenu("fileimport");
2307                 menu->exec(event->screenPos());
2308             } else if (link.startsWith("basket://")) {
2309                 emit crossReference(link);
2310             } else {
2311                 KRun *run = new KRun(QUrl::fromUserInput(link), m_view->window()); //  open the URL.
2312                 run->setAutoDelete(true);
2313             }
2314             break;
2315         } // If there is no link, edit note content
2316     case Note::Content:
2317     {
2318 	if(m_editor && m_editor->note() == clicked && m_editor->graphicsWidget())
2319 	{
2320 	  m_editor->setCursorTo(event->scenePos());
2321 	}
2322 	else
2323 	{
2324 	  closeEditor();
2325 	  unselectAllBut(clicked);
2326 	  noteEdit(clicked, /*justAdded=*/false, event->scenePos());
2327 	  QGraphicsScene::mouseReleaseEvent(event);
2328 	}
2329         break;
2330     }
2331     case Note::TopInsert:
2332     case Note::TopGroup:
2333     case Note::BottomInsert:
2334     case Note::BottomGroup:
2335     case Note::BottomColumn:
2336         clickedToInsert(event, clicked, zone);
2337         save();
2338         break;
2339 
2340     case Note::None:
2341     default:
2342         KMessageBox::information(m_view->viewport(),
2343                                  i18n("This message should never appear. If it does, this program is buggy! "
2344                                       "Please report the bug to the developer."));
2345         break;
2346     }
2347 }
2348 
mouseDoubleClickEvent(QGraphicsSceneMouseEvent * event)2349 void BasketScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
2350 {
2351     Note *clicked = noteAt(event->scenePos());
2352     Note::Zone zone = (clicked ? clicked->zoneAt(event->scenePos() - QPointF(clicked->x(), clicked->y())) : Note::None);
2353 
2354     if (event->button() == Qt::LeftButton && (zone == Note::Group || zone == Note::Handle)) {
2355         doCopy(CopyToSelection);
2356         m_noActionOnMouseRelease = true;
2357     } else
2358         mousePressEvent(event);
2359 }
2360 
mouseMoveEvent(QGraphicsSceneMouseEvent * event)2361 void BasketScene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
2362 {
2363     // redirect this event to the editor if track mouse event is active
2364     if (m_editorTrackMouseEvent && (m_pressPos - event->scenePos()).manhattanLength() > QApplication::startDragDistance()) {
2365 	m_editor->updateSelection(event->scenePos());
2366 	return;
2367     }
2368 
2369     // Drag the notes:
2370     if (m_canDrag && (m_pressPos - event->scenePos()).manhattanLength() > QApplication::startDragDistance()) {
2371         m_canDrag          = false;
2372         m_isSelecting      = false; // Don't draw selection rectangle ater drag!
2373         m_selectionStarted = false;
2374 
2375         NoteSelection *selection = selectedNotes();
2376         if (selection->firstStacked()) {
2377             QDrag *d = NoteDrag::dragObject(selection, /*cutting=*/false, /*source=*/m_view); // d will be deleted by QT
2378             /*bool shouldRemove = */d->exec();
2379 //      delete selection;
2380 
2381             // Never delete because URL is dragged and the file must be available for the extern appliation
2382 //      if (shouldRemove && d->target() == 0) // If target is another application that request to remove the note
2383 //          emit wantDelete(this);
2384         }
2385         return;
2386     }
2387 
2388     // Moving a Resizer:
2389     if (m_resizingNote) {
2390         qreal groupWidth = event->scenePos().x() - m_resizingNote->x() - m_pickedResizer;
2391         qreal minRight   = m_resizingNote->minRight();
2392 //        int maxRight   = 100 * contentsWidth(); // A big enough value (+infinity) for free layouts.
2393         qreal maxRight   = 100 * sceneRect().width(); // A big enough value (+infinity) for free layouts.
2394 		Note *nextColumn = m_resizingNote->next();
2395         if (m_resizingNote->isColumn()) {
2396             if (nextColumn)
2397                 maxRight = nextColumn->x() + nextColumn->rightLimit() - nextColumn->minRight() - Note::RESIZER_WIDTH;
2398             else
2399 //                maxRight = contentsWidth();
2400                 maxRight = sceneRect().width();
2401         }
2402         if (groupWidth > maxRight - m_resizingNote->x())
2403             groupWidth = maxRight - m_resizingNote->x();
2404         if (groupWidth < minRight - m_resizingNote->x())
2405             groupWidth = minRight - m_resizingNote->x();
2406         qreal delta = groupWidth - m_resizingNote->groupWidth();
2407         m_resizingNote->setGroupWidth(groupWidth);
2408         // If resizing columns:
2409         if (m_resizingNote->isColumn()) {
2410             Note *column = m_resizingNote;
2411             if ((column = column->next())) {
2412                 // Next columns should not have them X coordinate animated, because it would flicker:
2413                 column->setXRecursively(column->x() + delta);
2414                 // And the resizer should resize the TWO sibling columns, and not push the other columns on th right:
2415                 column->setGroupWidth(column->groupWidth() - delta);
2416             }
2417         }
2418         relayoutNotes(true);
2419     }
2420 
2421     // Moving a Note:
2422     /*  if (m_movingNote) {
2423             int x = event->pos().x() - m_pickedHandle.x();
2424             int y = event->pos().y() - m_pickedHandle.y();
2425             if (x < 0) x = 0;
2426             if (y < 0) y = 0;
2427             m_movingNote->setX(x);
2428             m_movingNote->setY(y);
2429             m_movingNote->relayoutAt(x, y, / *animate=* /false);
2430             relayoutNotes(true);
2431         }
2432     */
2433 
2434     // Dragging the selection rectangle:
2435     if (m_selectionStarted)
2436         doAutoScrollSelection();
2437 
2438     doHoverEffects(event->scenePos());
2439 }
2440 
doAutoScrollSelection()2441 void BasketScene::doAutoScrollSelection()
2442 {
2443     static const int AUTO_SCROLL_MARGIN = 50;  // pixels
2444     static const int AUTO_SCROLL_DELAY  = 100; // milliseconds
2445 
2446     QPoint pos = m_view->mapFromGlobal(QCursor::pos());
2447     // Do the selection:
2448     if (m_isSelecting)
2449 	invalidate(m_selectionRect);
2450 
2451 
2452     m_selectionEndPoint = m_view->mapToScene(pos);
2453     m_selectionRect = QRectF(m_selectionBeginPoint, m_selectionEndPoint).normalized();
2454     if (m_selectionRect.left() < 0)                    m_selectionRect.setLeft(0);
2455     if (m_selectionRect.top() < 0)                     m_selectionRect.setTop(0);
2456 //    if (m_selectionRect.right() >= contentsWidth())    m_selectionRect.setRight(contentsWidth() - 1);
2457 //    if (m_selectionRect.bottom() >= contentsHeight())  m_selectionRect.setBottom(contentsHeight() - 1);
2458     if (m_selectionRect.right() >= sceneRect().width())    m_selectionRect.setRight(sceneRect().width() - 1);
2459     if (m_selectionRect.bottom() >= sceneRect().height())  m_selectionRect.setBottom(sceneRect().height() - 1);
2460 
2461     if ((m_selectionBeginPoint - m_selectionEndPoint).manhattanLength() > QApplication::startDragDistance()) {
2462         m_isSelecting = true;
2463         selectNotesIn(m_selectionRect, m_selectionInvert);
2464 	invalidate(m_selectionRect);
2465         m_noActionOnMouseRelease = true;
2466     } else {
2467         // If the user was selecting but cancel by making the rectangle too small, cancel it really!!!
2468         if (m_isSelecting) {
2469             if (m_selectionInvert)
2470                 selectNotesIn(QRectF(), m_selectionInvert);
2471             else
2472                 unselectAllBut(0); // TODO: unselectAll();
2473         }
2474         if (m_isSelecting)
2475             resetWasInLastSelectionRect();
2476         m_isSelecting = false;
2477         stopAutoScrollSelection();
2478 
2479 	return;
2480     }
2481 
2482     // Do the auto-scrolling:
2483     // FIXME: It's still flickering
2484 
2485 //    QRectF insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, visibleWidth() - 2*AUTO_SCROLL_MARGIN, visibleHeight() - 2*AUTO_SCROLL_MARGIN);
2486     QRectF insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, m_view->viewport()->width() - 2*AUTO_SCROLL_MARGIN, m_view->viewport()->height() - 2*AUTO_SCROLL_MARGIN);
2487 
2488     int dx = 0;
2489     int dy = 0;
2490 
2491     if (pos.y() < AUTO_SCROLL_MARGIN)
2492         dy = pos.y() - AUTO_SCROLL_MARGIN;
2493     else if (pos.y() > m_view->viewport()->height() - AUTO_SCROLL_MARGIN)
2494         dy = pos.y() - m_view->viewport()->height() + AUTO_SCROLL_MARGIN;
2495 //    else if (pos.y() > visibleHeight() - AUTO_SCROLL_MARGIN)
2496 //        dy = pos.y() - visibleHeight() + AUTO_SCROLL_MARGIN;
2497 
2498     if (pos.x() < AUTO_SCROLL_MARGIN)
2499         dx = pos.x() - AUTO_SCROLL_MARGIN;
2500     else if (pos.x() > m_view->viewport()->width() - AUTO_SCROLL_MARGIN)
2501         dx = pos.x() - m_view->viewport()->width() + AUTO_SCROLL_MARGIN;
2502 //    else if (pos.x() > visibleWidth() - AUTO_SCROLL_MARGIN)
2503 //        dx = pos.x() - visibleWidth() + AUTO_SCROLL_MARGIN;
2504 
2505     if (dx || dy) {
2506         qApp->sendPostedEvents(); // Do the repaints, because the scrolling will make the area to repaint to be wrong
2507 //        scrollBy(dx, dy);
2508         if (!m_autoScrollSelectionTimer.isActive())
2509             m_autoScrollSelectionTimer.start(AUTO_SCROLL_DELAY);
2510     } else
2511         stopAutoScrollSelection();
2512 }
2513 
stopAutoScrollSelection()2514 void BasketScene::stopAutoScrollSelection()
2515 {
2516     m_autoScrollSelectionTimer.stop();
2517 }
2518 
resetWasInLastSelectionRect()2519 void BasketScene::resetWasInLastSelectionRect()
2520 {
2521     Note *note = m_firstNote;
2522     while (note) {
2523         note->resetWasInLastSelectionRect();
2524         note = note->next();
2525     }
2526 }
2527 
selectAll()2528 void BasketScene::selectAll()
2529 {
2530     if (redirectEditActions()) {
2531         if (m_editor->textEdit())
2532             m_editor->textEdit()->selectAll();
2533         else if (m_editor->lineEdit())
2534             m_editor->lineEdit()->selectAll();
2535     } else {
2536         // First select all in the group, then in the parent group...
2537         Note *child  = m_focusedNote;
2538         Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0);
2539         while (parent) {
2540             if (!parent->allSelected()) {
2541                 parent->setSelectedRecursively(true);
2542                 return;
2543             }
2544             child  = parent;
2545             parent = parent->parentNote();
2546         }
2547         // Then, select all:
2548         FOR_EACH_NOTE(note)
2549         note->setSelectedRecursively(true);
2550     }
2551 }
2552 
unselectAll()2553 void BasketScene::unselectAll()
2554 {
2555     if (redirectEditActions()) {
2556         if (m_editor->textEdit()) {
2557             QTextCursor cursor = m_editor->textEdit()->textCursor();
2558             cursor.clearSelection();
2559             m_editor->textEdit()->setTextCursor(cursor);
2560             selectionChangedInEditor(); // THIS IS NOT EMITED BY Qt!!!
2561         } else if (m_editor->lineEdit())
2562             m_editor->lineEdit()->deselect();
2563     } else {
2564         if (countSelecteds() > 0) // Optimisation
2565             FOR_EACH_NOTE(note)
2566             note->setSelectedRecursively(false);
2567     }
2568 }
2569 
invertSelection()2570 void BasketScene::invertSelection()
2571 {
2572     FOR_EACH_NOTE(note)
2573     note->invertSelectionRecursively();
2574 }
2575 
unselectAllBut(Note * toSelect)2576 void BasketScene::unselectAllBut(Note *toSelect)
2577 {
2578     FOR_EACH_NOTE(note)
2579     note->unselectAllBut(toSelect);
2580 }
2581 
invertSelectionOf(Note * toSelect)2582 void BasketScene::invertSelectionOf(Note *toSelect)
2583 {
2584     FOR_EACH_NOTE(note)
2585     note->invertSelectionOf(toSelect);
2586 }
2587 
selectNotesIn(const QRectF & rect,bool invertSelection,bool unselectOthers)2588 void BasketScene::selectNotesIn(const QRectF &rect, bool invertSelection, bool unselectOthers /*= true*/)
2589 {
2590     FOR_EACH_NOTE(note)
2591     note->selectIn(rect, invertSelection, unselectOthers);
2592 }
2593 
doHoverEffects()2594 void BasketScene::doHoverEffects()
2595 {
2596     doHoverEffects(m_view->mapToScene(m_view->viewport()->mapFromGlobal(QCursor::pos())));
2597 }
2598 
doHoverEffects(Note * note,Note::Zone zone,const QPointF & pos)2599 void BasketScene::doHoverEffects(Note *note, Note::Zone zone, const QPointF &pos)
2600 {
2601     // Inform the old and new hovered note (if any):
2602     Note *oldHoveredNote = m_hoveredNote;
2603     if (note != m_hoveredNote) {
2604         if (m_hoveredNote) {
2605             m_hoveredNote->setHovered(false);
2606             m_hoveredNote->setHoveredZone(Note::None);
2607             m_hoveredNote->update();
2608         }
2609         m_hoveredNote = note;
2610         if (m_hoveredNote)
2611 	{
2612             m_hoveredNote->setHovered(true);
2613 	}
2614     }
2615 
2616     // If we are hovering a note, compute which zone is hovered and inform the note:
2617     if (m_hoveredNote) {
2618         if (zone != m_hoveredZone || oldHoveredNote != m_hoveredNote) {
2619             m_hoveredZone = zone;
2620 	    m_hoveredNote->setHoveredZone(zone);
2621             m_view->viewport()->setCursor(m_hoveredNote->cursorFromZone(zone));
2622 
2623 	    m_hoveredNote->update();
2624 	}
2625 
2626 	// If we are hovering an insert line zone, update this thing:
2627         if (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn)
2628 	{
2629 	    placeInserter(m_hoveredNote, zone);
2630 	}
2631         else
2632 	{
2633             removeInserter();
2634 	}
2635         // If we are hovering an embedded link in a rich text element, show the destination in the statusbar:
2636         if (zone == Note::Link)
2637             emit setStatusBarText(m_hoveredNote->linkAt(pos - QPoint(m_hoveredNote->x(), m_hoveredNote->y())));
2638         else if (m_hoveredNote->content())
2639             emit setStatusBarText(m_hoveredNote->content()->statusBarMessage(m_hoveredZone));//resetStatusBarText();
2640         // If we aren't hovering a note, reset all:
2641     } else {
2642         if (isFreeLayout() && !isSelecting())
2643             m_view->viewport()->setCursor(Qt::CrossCursor);
2644         else
2645             m_view->viewport()->unsetCursor();
2646         m_hoveredZone = Note::None;
2647         removeInserter();
2648         emit resetStatusBarText();
2649     }
2650 }
2651 
doHoverEffects(const QPointF & pos)2652 void BasketScene::doHoverEffects(const QPointF &pos)
2653 {
2654 //  if (isDuringEdit())
2655 //      viewport()->unsetCursor();
2656 
2657     // Do we have the right to do hover effects?
2658     if (! m_loaded || m_lockedHovering)
2659     {
2660       return;
2661     }
2662 
2663     // enterEvent() (mouse enter in the widget) set m_underMouse to true, and leaveEvent() make it false.
2664     // But some times the enterEvent() is not trigerred: eg. when dragging the scrollbar:
2665     // Ending the drag INSIDE the basket area will make NO hoverEffects() because m_underMouse is false.
2666     // User need to leave the area and re-enter it to get effects.
2667     // This hack solve that by dismissing the m_underMouse variable:
2668 
2669     // Don't do hover effects when a popup menu is opened.
2670     // Primarily because the basket area will only receive mouseEnterEvent and mouveLeaveEvent.
2671     // It willn't be noticed of mouseMoveEvent, which would result in a apparently broken application state:
2672     bool underMouse = !qApp->activePopupWidget();
2673     //if (qApp->activePopupWidget())
2674     //    underMouse = false;
2675 
2676     // Compute which note is hovered:
2677     Note       *note = (m_isSelecting || !underMouse ? 0 : noteAt(pos));
2678     Note::Zone  zone = (note ? note->zoneAt(pos - QPointF(note->x(), note->y()), isDuringDrag()) : Note::None);
2679 
2680     // Inform the old and new hovered note (if any) and update the areas:
2681     doHoverEffects(note, zone, pos);
2682 }
2683 
mouseEnteredEditorWidget()2684 void BasketScene::mouseEnteredEditorWidget()
2685 {
2686     if (!m_lockedHovering && !qApp->activePopupWidget())
2687         doHoverEffects(editedNote(), Note::Content, QPoint());
2688 }
2689 
removeInserter()2690 void BasketScene::removeInserter()
2691 {
2692     if (m_inserterShown) { // Do not hide (and then update/repaint the view) if it is already hidden!
2693         m_inserterShown = false;
2694 	invalidate(m_inserterRect);
2695     }
2696 }
2697 
placeInserter(Note * note,int zone)2698 void BasketScene::placeInserter(Note *note, int zone)
2699 {
2700     // Remove the inserter:
2701     if (!note) {
2702         removeInserter();
2703         return;
2704     }
2705 
2706     // Update the old position:
2707 	if (inserterShown()){
2708 	  invalidate(m_inserterRect);
2709 	}
2710     // Some comodities:
2711     m_inserterShown = true;
2712     m_inserterTop   = (zone == Note::TopGroup || zone == Note::TopInsert);
2713     m_inserterGroup = (zone == Note::TopGroup || zone == Note::BottomGroup);
2714     // X and width:
2715     qreal groupIndent = (note->isGroup() ? note->width() : Note::HANDLE_WIDTH);
2716     qreal x     = note->x();
2717     qreal width = (note->isGroup() ? note->rightLimit() - note->x() : note->width());
2718     if (m_inserterGroup) {
2719         x     += groupIndent;
2720         width -= groupIndent;
2721     }
2722     m_inserterSplit = (Settings::groupOnInsertionLine() && note && !note->isGroup() && !note->isFree() && !note->isColumn());
2723 //  if (note->isGroup())
2724 //      width = note->rightLimit() - note->x() - (m_inserterGroup ? groupIndent : 0);
2725     // Y:
2726     qreal y = note->y() - (m_inserterGroup && m_inserterTop ? 1 : 3);
2727     if (!m_inserterTop)
2728         y += (note->isColumn() ? note->height() : note->height());
2729     // Assigning result:
2730     m_inserterRect = QRectF(x, y, width, 6 - (m_inserterGroup ? 2 : 0));
2731     // Update the new position:
2732     invalidate(m_inserterRect);
2733 }
2734 
drawLineByRect(QPainter & painter,qreal x,qreal y,qreal width,qreal height)2735 inline void drawLineByRect(QPainter &painter, qreal x, qreal y, qreal width, qreal height)
2736 {
2737     painter.drawLine(x, y, x + width - 1, y + height - 1);
2738 }
2739 
drawInserter(QPainter & painter,qreal xPainter,qreal yPainter)2740 void BasketScene::drawInserter(QPainter &painter, qreal xPainter, qreal yPainter)
2741 {
2742     if (!m_inserterShown)
2743         return;
2744 
2745     QRectF rect = m_inserterRect; // For shorter code-lines when drawing!
2746     rect.translate(-xPainter, -yPainter);
2747     int lineY  = (m_inserterGroup && m_inserterTop ? 0 : 2);
2748     int roundY = (m_inserterGroup && m_inserterTop ? 0 : 1);
2749 
2750     KStatefulBrush statefulBrush(KColorScheme::View, KColorScheme::HoverColor);
2751     QColor dark = statefulBrush.brush(palette()).color();
2752     QColor light = dark.lighter().lighter();
2753     if (m_inserterGroup && Settings::groupOnInsertionLine())
2754         light = Tools::mixColor(light, palette().color(QPalette::Highlight));
2755     painter.setPen(dark);
2756     // The horizontal line:
2757     //painter.drawRect(       rect.x(),                    rect.y() + lineY,  rect.width(), 2);
2758     int width = rect.width() - 4;
2759     drawGradient(&painter, dark,  light, rect.x() + 2,           rect.y() + lineY, width / 2,         2, /*sunken=*/false, /*horz=*/false, /*flat=*/false);
2760     drawGradient(&painter, light, dark,  rect.x() + 2 + width / 2, rect.y() + lineY, width - width / 2, 2, /*sunken=*/false, /*horz=*/false, /*flat=*/false);
2761     // The left-most and right-most edges (biggest vertical lines):
2762     drawLineByRect(painter, rect.x(),                    rect.y(),          1, (m_inserterGroup ? 4 : 6));
2763     drawLineByRect(painter, rect.x() + rect.width() - 1, rect.y(),          1, (m_inserterGroup ? 4 : 6));
2764     // The left and right mid vertical lines:
2765     drawLineByRect(painter, rect.x() + 1,                rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4));
2766     drawLineByRect(painter, rect.x() + rect.width() - 2, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4));
2767     // Draw the split as a feedback to know where is the limit between insert and group:
2768     if (m_inserterSplit) {
2769         int noteWidth = rect.width() + (m_inserterGroup ? Note::HANDLE_WIDTH : 0);
2770         int xSplit = rect.x() - (m_inserterGroup ? Note::HANDLE_WIDTH : 0) + noteWidth / 2;
2771         painter.setPen(Tools::mixColor(dark, light));
2772         painter.drawRect(xSplit - 2, rect.y() + lineY, 4, 2);
2773         painter.setPen(dark);
2774         painter.drawRect(xSplit - 1, rect.y() + lineY, 2, 2);
2775     }
2776 }
2777 
helpEvent(QGraphicsSceneHelpEvent * event)2778 void BasketScene::helpEvent(QGraphicsSceneHelpEvent* event)
2779 {
2780     if (!m_loaded || !Settings::showNotesToolTip())
2781 	return;
2782 
2783     QString message;
2784     QRectF   rect;
2785 
2786     QPointF contentPos = event->scenePos();
2787     Note *note = noteAt(contentPos);
2788 
2789     if (!note && isFreeLayout()) {
2790         message = i18n("Insert note here\nRight click for more options");
2791         QRectF itRect;
2792         for (QList<QRectF>::iterator it = m_blankAreas.begin(); it != m_blankAreas.end(); ++it) {
2793             itRect = QRectF(0, 0, m_view->viewport()->width(), m_view->viewport()->height()).intersected(*it);
2794             if (itRect.contains(contentPos)) {
2795                 rect = itRect;
2796                 rect.moveLeft(rect.left() - sceneRect().x());
2797                 rect.moveTop(rect.top()  - sceneRect().y());
2798                 break;
2799             }
2800         }
2801     } else {
2802 	if (!note)
2803 	  return;
2804 
2805         Note::Zone zone = note->zoneAt(contentPos - QPointF(note->x(), note->y()));
2806 
2807         switch (zone) {
2808         case Note::Resizer:       message = (note->isColumn() ?
2809                                                  i18n("Resize those columns") :
2810                                                  (note->isGroup() ?
2811                                                   i18n("Resize this group") :
2812                                                   i18n("Resize this note")));                 break;
2813         case Note::Handle:        message = i18n("Select or move this note");           break;
2814         case Note::Group:         message = i18n("Select or move this group");          break;
2815         case Note::TagsArrow:     message = i18n("Assign or remove tags from this note");
2816             if (note->states().count() > 0) {
2817                 QString tagsString = "";
2818                 for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) {
2819                     QString tagName = "<nobr>" + Tools::textToHTMLWithoutP((*it)->fullName()) + "</nobr>";
2820                     if (tagsString.isEmpty())
2821                         tagsString = tagName;
2822                     else
2823                         tagsString = i18n("%1, %2", tagsString, tagName);
2824                 }
2825                 message = "<qt><nobr>" + message + "</nobr><br>" + i18n("<b>Assigned Tags</b>: %1", tagsString);
2826             }
2827             break;
2828         case Note::Custom0:       message = note->content()->zoneTip(zone);             break; //"Open this link/Open this file/Open this sound file/Launch this application"
2829         case Note::GroupExpander: message = (note->isFolded() ?
2830                                                  i18n("Expand this group") :
2831                                                  i18n("Collapse this group"));               break;
2832         case Note::Link:
2833         case Note::Content:       message = note->content()->editToolTipText();         break;
2834         case Note::TopInsert:
2835         case Note::BottomInsert:  message = i18n("Insert note here\nRight click for more options");              break;
2836         case Note::TopGroup:      message = i18n("Group note with the one below\nRight click for more options"); break;
2837         case Note::BottomGroup:   message = i18n("Group note with the one above\nRight click for more options"); break;
2838         case Note::BottomColumn:  message = i18n("Insert note here\nRight click for more options");              break;
2839         case Note::None:          message = "** Zone NONE: internal error **";                                   break;
2840         default:
2841             if (zone >= Note::Emblem0)
2842                 message = note->stateForEmblemNumber(zone - Note::Emblem0)->fullName();
2843             else
2844                 message = "";
2845             break;
2846         }
2847 
2848         if (zone == Note::Content || zone == Note::Link || zone == Note::Custom0) {
2849             QStringList keys;
2850             QStringList values;
2851 
2852             note->content()->toolTipInfos(&keys, &values);
2853             keys.append(i18n("Added"));
2854             keys.append(i18n("Last Modification"));
2855             values.append(note->addedStringDate());
2856             values.append(note->lastModificationStringDate());
2857 
2858             message = "<qt><nobr>" + message;
2859             QStringList::iterator key;
2860             QStringList::iterator value;
2861             for (key = keys.begin(), value = values.begin(); key != keys.end() && value != values.end(); ++key, ++value)
2862                 message += "<br>" + i18nc("of the form 'key: value'", "<b>%1</b>: %2", *key, *value);
2863             message += "</nobr></qt>";
2864         } else if (m_inserterSplit && (zone == Note::TopInsert || zone == Note::BottomInsert))
2865             message += "\n" + i18n("Click on the right to group instead of insert");
2866         else if (m_inserterSplit && (zone == Note::TopGroup || zone == Note::BottomGroup))
2867             message += "\n" + i18n("Click on the left to insert instead of group");
2868 
2869         rect = note->zoneRect(zone, contentPos - QPoint(note->x(), note->y()));
2870 
2871 	rect.moveLeft(rect.left() - sceneRect().x());
2872         rect.moveTop(rect.top()  - sceneRect().y());
2873 
2874         rect.moveLeft(rect.left() + note->x());
2875         rect.moveTop(rect.top()  + note->y());
2876     }
2877 
2878     QToolTip::showText(event->screenPos(), message, m_view, rect.toRect());
2879 }
2880 
lastNote()2881 Note* BasketScene::lastNote()
2882 {
2883     Note *note = firstNote();
2884     while (note && note->next())
2885         note = note->next();
2886     return note;
2887 }
2888 
deleteNotes()2889 void BasketScene::deleteNotes()
2890 {
2891     Note *note = m_firstNote;
2892 
2893     while (note) {
2894         Note *tmp = note->next();
2895         delete note;
2896         note = tmp;
2897     }
2898     m_firstNote = 0;
2899     m_resizingNote = 0;
2900     m_movingNote = 0;
2901     m_focusedNote = 0;
2902     m_startOfShiftSelectionNote = 0;
2903     m_tagPopupNote = 0;
2904     m_clickedToInsert = 0;
2905     m_savedClickedToInsert = 0;
2906     m_hoveredNote = 0;
2907     m_count = 0;
2908     m_countFounds = 0;
2909     m_countSelecteds = 0;
2910 
2911     emit resetStatusBarText();
2912     emit countsChanged(this);
2913 }
2914 
noteAt(QPointF pos)2915 Note* BasketScene::noteAt(QPointF pos)
2916 {
2917   qreal x = pos.x();
2918   qreal y = pos.y();
2919 //NO:
2920 //  // Do NOT check the bottom&right borders.
2921 //  // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars),
2922 //  // the note is first removed, and relayoutNotes() compute the new height that is smaller
2923 //  // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!!
2924 //  // Should, of course, not return 0:
2925     if (x < 0 || x > sceneRect().width() || y < 0 || y > sceneRect().height())
2926       return 0;
2927 
2928     // When resizing a note/group, keep it highlighted:
2929     if (m_resizingNote)
2930       return m_resizingNote;
2931 
2932     // Search and return the hovered note:
2933     Note *note = m_firstNote;
2934     Note *possibleNote;
2935     while (note) {
2936         possibleNote = note->noteAt(pos);
2937         if (possibleNote) {
2938             if (NoteDrag::selectedNotes.contains(possibleNote) || draggedNotes().contains(possibleNote))
2939                 return 0;
2940             else
2941                 return possibleNote;
2942         }
2943         note = note->next();
2944     }
2945 
2946     // If the basket is layouted in columns, return one of the columns to be able to add notes in them:
2947     if (isColumnsLayout()) {
2948         Note *column = m_firstNote;
2949         while (column) {
2950 	  if (x >= column->x() && x < column->rightLimit())
2951 	    return column;
2952 	  column = column->next();
2953         }
2954     }
2955 
2956     // Nothing found, no note is hovered:
2957     return NULL;
2958 }
2959 
~BasketScene()2960 BasketScene::~BasketScene()
2961 {
2962     m_commitdelay.stop();	//we don't know how long deleteNotes() last so we want to make extra sure that nobody will commit in between
2963     if (m_decryptBox)
2964         delete m_decryptBox;
2965 #ifdef HAVE_LIBGPGME
2966     delete m_gpg;
2967 #endif
2968     deleteNotes();
2969 
2970 	if(m_view)
2971 		delete m_view;
2972 }
2973 
animateLoad()2974 void BasketScene::animateLoad()
2975 {
2976     const int viewHeight = sceneRect().y() + m_view->viewport()->height();
2977 
2978     QTime t = QTime::currentTime(); // Set random seed
2979     srand(t.hour()*12 + t.minute()*60 + t.second()*60);
2980 
2981     bool needAnimation = false;
2982     m_animationTimeLine = new QTimeLine(ANIMATION_DELAY);
2983     m_animationTimeLine->setFrameRange(0, 100);
2984     connect(m_animationTimeLine, SIGNAL(frameChanged(int)), this, SLOT(animationFrameChanged(int)));
2985     connect(m_animationTimeLine, SIGNAL(finished()), this, SLOT(finishAnimation()));
2986 
2987     Note *note = firstNote();
2988     while (note)
2989     {
2990         if ((note->y() < viewHeight) && note->matching())
2991 	{
2992             needAnimation |= note->initAnimationLoad(m_animationTimeLine);
2993 	}
2994         note = note->next();
2995     }
2996 
2997     if(needAnimation)
2998     {
2999       m_animationTimeLine->start();
3000     }
3001     else
3002     {
3003       m_loaded = true;
3004       delete m_animationTimeLine;
3005       m_animationTimeLine = 0;
3006     }
3007 }
3008 
animationFrameChanged(int)3009 void BasketScene::animationFrameChanged(int /*frame*/)
3010 {
3011   FOR_EACH_NOTE(note) note->unbufferizeAll();
3012 
3013   if(!m_loaded)
3014   {
3015     m_loaded = true;
3016     update();
3017   }
3018 }
3019 
finishAnimation()3020 void BasketScene::finishAnimation()
3021 {
3022   m_animationTimeLine->deleteLater();
3023   m_animationTimeLine = 0;
3024   FOR_EACH_NOTE(note) note->animationFinished();
3025   update();
3026 }
3027 
selectionRectInsideColor()3028 QColor BasketScene::selectionRectInsideColor()
3029 {
3030     return Tools::mixColor(Tools::mixColor(backgroundColor(),
3031                                            palette().color(QPalette::Highlight)),
3032                            backgroundColor());
3033 }
3034 
alphaBlendColors(const QColor & bgColor,const QColor & fgColor,const int a)3035 QColor alphaBlendColors(const QColor &bgColor, const QColor &fgColor, const int a)
3036 {
3037     // normal button...
3038     QRgb rgb = bgColor.rgb();
3039     QRgb rgb_b = fgColor.rgb();
3040     int alpha = a;
3041     if (alpha > 255) alpha = 255;
3042     if (alpha < 0) alpha = 0;
3043     int inv_alpha = 255 - alpha;
3044     QColor result = QColor(qRgb(qRed(rgb_b) * inv_alpha / 255 + qRed(rgb) * alpha / 255,
3045                                 qGreen(rgb_b) * inv_alpha / 255 + qGreen(rgb) * alpha / 255,
3046                                 qBlue(rgb_b) * inv_alpha / 255 + qBlue(rgb) * alpha / 255));
3047 
3048     return result;
3049 }
3050 
unlock()3051 void BasketScene::unlock()
3052 {
3053     QTimer::singleShot(0, this, SLOT(load()));
3054 }
3055 
inactivityAutoLockTimeout()3056 void BasketScene::inactivityAutoLockTimeout()
3057 {
3058     lock();
3059 }
3060 
drawBackground(QPainter * painter,const QRectF & rect)3061 void BasketScene::drawBackground ( QPainter * painter, const QRectF & rect )
3062 {
3063   if (!m_loadingLaunched) {
3064     if (!m_locked) {
3065       QTimer::singleShot(0, this, SLOT(load()));
3066       return;
3067     }
3068     else {
3069       Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar
3070     }
3071   }
3072 
3073   if (! hasBackgroundImage())
3074   {
3075     painter->fillRect(rect, backgroundColor());
3076     // It's either a background pixmap to draw or a background color to fill:
3077   } else if (isTiledBackground() || (rect.x() < backgroundPixmap()->width() && rect.y() < backgroundPixmap()->height())){
3078     painter->fillRect(rect, backgroundColor());
3079     blendBackground(*painter, rect, 0, 0, /*opaque=*/true);
3080   } else {
3081     painter->fillRect(rect, backgroundColor());
3082   }
3083 }
3084 
drawForeground(QPainter * painter,const QRectF & rect)3085 void BasketScene::drawForeground ( QPainter * painter, const QRectF & rect )
3086 {
3087     if (m_locked)
3088     {
3089         if (!m_decryptBox)
3090 	{
3091             m_decryptBox = new QFrame(m_view);
3092             m_decryptBox->setFrameShape(QFrame::StyledPanel);
3093             m_decryptBox->setFrameShadow(QFrame::Plain);
3094             m_decryptBox->setLineWidth(1);
3095 
3096             QGridLayout *layout = new QGridLayout(m_decryptBox);
3097             layout->setContentsMargins(11, 11, 11, 11);
3098             layout->setSpacing(6);
3099 
3100 #ifdef HAVE_LIBGPGME
3101             m_button = new QPushButton(m_decryptBox);
3102             m_button->setText(i18n("&Unlock"));
3103             layout->addWidget(m_button, 1, 2);
3104             connect(m_button, SIGNAL(clicked()), this, SLOT(unlock()));
3105 #endif
3106             QLabel* label = new QLabel(m_decryptBox);
3107             QString text = "<b>" + i18n("Password protected basket.") + "</b><br/>";
3108 #ifdef HAVE_LIBGPGME
3109             label->setText(text + i18n("Press Unlock to access it."));
3110 #else
3111             label->setText(text + i18n("Encryption is not supported by<br/>this version of %1.", QGuiApplication::applicationDisplayName()));
3112 #endif
3113             label->setAlignment(Qt::AlignTop);
3114             layout->addWidget(label, 0, 1, 1, 2);
3115             QLabel* pixmap = new QLabel(m_decryptBox);
3116             pixmap->setPixmap(KIconLoader::global()->loadIcon("encrypted", KIconLoader::NoGroup, KIconLoader::SizeHuge));
3117             layout->addWidget(pixmap, 0, 0, 2, 1);
3118 
3119             QSpacerItem* spacer = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
3120             layout->addItem(spacer, 1, 1);
3121 
3122             label = new QLabel("<small>" +
3123                                i18n("To make baskets stay unlocked, change the automatic<br>"
3124                                     "locking duration in the application settings.") + "</small>",
3125                                m_decryptBox);
3126             label->setAlignment(Qt::AlignTop);
3127             layout->addWidget(label, 2, 0, 1, 3);
3128 
3129             m_decryptBox->resize(layout->sizeHint());
3130         }
3131         if (m_decryptBox->isHidden())
3132 	{
3133             m_decryptBox->show();
3134         }
3135 #ifdef HAVE_LIBGPGME
3136         m_button->setFocus();
3137 #endif
3138         m_decryptBox->move((m_view->viewport()->width() - m_decryptBox->width()) / 2,
3139                            (m_view->viewport()->height() - m_decryptBox->height()) / 2);
3140     }
3141     else
3142     {
3143         if (m_decryptBox && !m_decryptBox->isHidden())
3144             m_decryptBox->hide();
3145     }
3146 
3147     if (!m_loaded)
3148     {
3149 	setSceneRect(0,0,m_view->viewport()->width(),m_view->viewport()->height());
3150 	QBrush brush(backgroundColor());
3151         QPixmap pixmap(m_view->viewport()->width(), m_view->viewport()->height()); // TODO: Clip it to asked size only!
3152         QPainter painter2(&pixmap);
3153         QTextDocument rt;
3154         rt.setHtml(QString("<center>%1</center>").arg(i18n("Loading...")));
3155         rt.setTextWidth(m_view->viewport()->width());
3156         int hrt = rt.size().height();
3157         painter2.fillRect(0, 0, m_view->viewport()->width(), m_view->viewport()->height(), brush);
3158         blendBackground(painter2, QRectF(0, 0, m_view->viewport()->width(), m_view->viewport()->height()), -1, -1, /*opaque=*/true);
3159         QPalette pal = palette();
3160         pal.setColor(QPalette::WindowText, textColor());
3161         painter2.translate(0, (m_view->viewport()->height() - hrt) / 2);
3162         QAbstractTextDocumentLayout::PaintContext context;
3163         context.palette = pal;
3164         rt.documentLayout()->draw(&painter2, context);
3165         painter2.end();
3166         painter->drawPixmap(0, 0, pixmap);
3167         return; // TODO: Clip to the wanted rectangle
3168     }
3169 
3170     enableActions();
3171 
3172     if ((inserterShown() && rect.intersects(inserterRect()))  || (m_isSelecting && rect.intersects(m_selectionRect)))
3173     {
3174       // Draw inserter:
3175       if (inserterShown() && rect.intersects(inserterRect()))
3176       {
3177 	drawInserter(*painter, 0, 0);
3178       }
3179       // Draw selection rect:
3180       if (m_isSelecting && rect.intersects(m_selectionRect))
3181       {
3182         QRectF selectionRect = m_selectionRect;
3183         QRectF selectionRectInside(selectionRect.x() + 1, selectionRect.y() + 1, selectionRect.width() - 2, selectionRect.height() - 2);
3184         if (selectionRectInside.width() > 0 && selectionRectInside.height() > 0) {
3185             QColor insideColor = selectionRectInsideColor();
3186             painter->fillRect(selectionRectInside, QBrush(insideColor,Qt::Dense4Pattern));
3187         }
3188         painter->setPen(palette().color(QPalette::Highlight).darker());
3189         painter->drawRect(selectionRect);
3190         painter->setPen(Tools::mixColor(palette().color(QPalette::Highlight).darker(), backgroundColor()));
3191         painter->drawPoint(selectionRect.topLeft());
3192         painter->drawPoint(selectionRect.topRight());
3193         painter->drawPoint(selectionRect.bottomLeft());
3194         painter->drawPoint(selectionRect.bottomRight());
3195       }
3196     }
3197 }
3198 
3199 /*  rect(x,y,width,height)==(xBackgroundToDraw,yBackgroundToDraw,widthToDraw,heightToDraw)
3200  */
blendBackground(QPainter & painter,const QRectF & rect,qreal xPainter,qreal yPainter,bool opaque,QPixmap * bg)3201 void BasketScene::blendBackground(QPainter &painter, const QRectF &rect, qreal xPainter, qreal yPainter, bool opaque, QPixmap *bg)
3202 {
3203   painter.save();
3204   if (xPainter == -1 && yPainter == -1)
3205   {
3206     xPainter = rect.x();
3207     yPainter = rect.y();
3208   }
3209 
3210   if (hasBackgroundImage())
3211   {
3212     const QPixmap *bgPixmap = (bg ? /* * */bg : (opaque ? m_opaqueBackgroundPixmap : m_backgroundPixmap));
3213     if (isTiledBackground())
3214     {
3215       painter.drawTiledPixmap(rect.x() - xPainter, rect.y() - yPainter, rect.width(), rect.height(), *bgPixmap, rect.x(), rect.y());
3216     }
3217     else
3218     {
3219       painter.drawPixmap(QPointF(rect.x() - xPainter, rect.y() - yPainter), *bgPixmap, rect);
3220     }
3221   }
3222   painter.restore();
3223 }
3224 
recomputeBlankRects()3225 void BasketScene::recomputeBlankRects()
3226 {
3227     m_blankAreas.clear();
3228     return;
3229 
3230     m_blankAreas.append(QRectF(0, 0, sceneRect().width(), sceneRect().height()));
3231 
3232     FOR_EACH_NOTE(note)
3233     note->recomputeBlankRects(m_blankAreas);
3234 
3235     // See the drawing of blank areas in BasketScene::drawContents()
3236     if (hasBackgroundImage() && ! isTiledBackground())
3237         substractRectOnAreas(QRectF(0, 0, backgroundPixmap()->width(), backgroundPixmap()->height()), m_blankAreas, false);
3238 }
3239 
unsetNotesWidth()3240 void BasketScene::unsetNotesWidth()
3241 {
3242     Note *note = m_firstNote;
3243     while (note) {
3244         note->unsetWidth();
3245         note = note->next();
3246     }
3247 }
3248 
relayoutNotes(bool animate)3249 void BasketScene::relayoutNotes(bool animate)
3250 {
3251     if (Global::bnpView->currentBasket() != this)
3252         return; // Optimize load time, and basket will be relaid out when activated, anyway
3253 
3254     if (!Settings::playAnimations())
3255         animate = false;
3256 
3257     int h     = 0;
3258     tmpWidth = 0;
3259     tmpHeight = 0;
3260     Note *note = m_firstNote;
3261     while (note) {
3262         if (note->matching()) {
3263             note->relayoutAt(0, h, animate);
3264             if (note->hasResizer()) {
3265                 int minGroupWidth = note->minRight() - note->x();
3266                 if (note->groupWidth() < minGroupWidth) {
3267                     note->setGroupWidth(minGroupWidth);
3268                     relayoutNotes(animate); // Redo the thing, but this time it should not recurse
3269                     return;
3270                 }
3271             }
3272             h += note->height();
3273         }
3274         note = note->next();
3275     }
3276 
3277     if (isFreeLayout())
3278         tmpHeight += 100;
3279     else
3280         tmpHeight += 15;
3281 
3282     setSceneRect(0,0,qMax((qreal)m_view->viewport()->width(), tmpWidth),
3283 		 qMax((qreal)m_view->viewport()->height(), tmpHeight));
3284 
3285     recomputeBlankRects();
3286     placeEditor();
3287     doHoverEffects();
3288     invalidate();
3289 }
3290 
popupEmblemMenu(Note * note,int emblemNumber)3291 void BasketScene::popupEmblemMenu(Note *note, int emblemNumber)
3292 {
3293     m_tagPopupNote = note;
3294     State *state = note->stateForEmblemNumber(emblemNumber);
3295     State *nextState = state->nextState(/*cycle=*/false);
3296     Tag *tag = state->parentTag();
3297     m_tagPopup = tag;
3298 
3299     QKeySequence sequence = tag->shortcut();
3300     bool sequenceOnDelete = (nextState == 0 && !tag->shortcut().isEmpty());
3301 
3302     QMenu menu(m_view);
3303     if (tag->countStates() == 1) {
3304         menu.addSection(/*SmallIcon(state->icon()), */tag->name());
3305         QAction * act;
3306         act = new QAction(QIcon::fromTheme("edit-delete"), i18n("&Remove"), &menu);
3307         act->setData(1);
3308         menu.addAction(act);
3309         act = new QAction(QIcon::fromTheme("configure"),  i18n("&Customize..."), &menu);
3310         act->setData(2);
3311         menu.addAction(act);
3312 
3313         menu.addSeparator();
3314 
3315         act = new QAction(QIcon::fromTheme("search-filter"),     i18n("&Filter by this Tag"), &menu);
3316         act->setData(3);
3317         menu.addAction(act);
3318     } else {
3319         menu.addSection(tag->name());
3320         QList<State*>::iterator it;
3321         State *currentState;
3322 
3323         int i = 10;
3324         // QActionGroup makes the actions exclusive; turns checkboxes into radio
3325         // buttons on some styles.
3326         QActionGroup *emblemGroup = new QActionGroup(&menu);
3327         for (it = tag->states().begin(); it != tag->states().end(); ++it) {
3328             currentState = *it;
3329             QKeySequence sequence;
3330             if (currentState == nextState && !tag->shortcut().isEmpty())
3331                 sequence = tag->shortcut();
3332 
3333             StateAction *sa = new StateAction(currentState, QKeySequence(sequence), 0, false);
3334             sa->setChecked(state == currentState);
3335             sa->setActionGroup(emblemGroup);
3336             sa->setData(i);
3337 
3338             menu.addAction(sa);
3339             if (currentState == nextState && !tag->shortcut().isEmpty())
3340                 sa->setShortcut(sequence);
3341             ++i;
3342         }
3343 
3344         menu.addSeparator();
3345 
3346         QAction *act = new QAction(&menu);
3347         act->setIcon(QIcon::fromTheme("edit-delete"));
3348         act->setText(i18n("&Remove"));
3349         act->setShortcut(sequenceOnDelete ? sequence : QKeySequence());
3350         act->setData(1);
3351         menu.addAction(act);
3352         act = new QAction(
3353             QIcon::fromTheme("configure"),
3354             i18n("&Customize..."),
3355             &menu
3356         );
3357         act->setData(2);
3358         menu.addAction(act);
3359 
3360         menu.addSeparator();
3361 
3362         act = new QAction(QIcon::fromTheme("search-filter"),
3363                           i18n("&Filter by this Tag"),
3364                           &menu);
3365         act->setData(3);
3366         menu.addAction(act);
3367         act = new QAction(
3368             QIcon::fromTheme("search-filter"),
3369             i18n("Filter by this &State"),
3370             &menu);
3371         act->setData(4);
3372         menu.addAction(act);
3373     }
3374 
3375     connect(&menu, SIGNAL(triggered(QAction *)), this, SLOT(toggledStateInMenu(QAction *)));
3376     connect(&menu, SIGNAL(aboutToHide()),  this, SLOT(unlockHovering()));
3377     connect(&menu, SIGNAL(aboutToHide()),  this, SLOT(disableNextClick()));
3378 
3379     m_lockedHovering = true;
3380     menu.exec(QCursor::pos());
3381 }
3382 
toggledStateInMenu(QAction * action)3383 void BasketScene::toggledStateInMenu(QAction* action)
3384 {
3385     int id = action->data().toInt();
3386     if (id == 1) {
3387         removeTagFromSelectedNotes(m_tagPopup);
3388         //m_tagPopupNote->removeTag(m_tagPopup);
3389         //m_tagPopupNote->setWidth(0); // To force a new layout computation
3390         updateEditorAppearance();
3391         filterAgain();
3392         save();
3393         return;
3394     }
3395     if (id == 2) { // Customize this State:
3396         TagsEditDialog dialog(m_view, m_tagPopupNote->stateOfTag(m_tagPopup));
3397         dialog.exec();
3398         return;
3399     }
3400     if (id == 3) { // Filter by this Tag
3401         decoration()->filterBar()->filterTag(m_tagPopup);
3402         return;
3403     }
3404     if (id == 4) { // Filter by this State
3405         decoration()->filterBar()->filterState(m_tagPopupNote->stateOfTag(m_tagPopup));
3406         return;
3407     }
3408 
3409     /*addStateToSelectedNotes*/changeStateOfSelectedNotes(m_tagPopup->states()[id - 10] /*, orReplace=true*/);
3410     //m_tagPopupNote->addState(m_tagPopup->states()[id - 10], true);
3411     filterAgain();
3412     save();
3413 }
3414 
stateForTagFromSelectedNotes(Tag * tag)3415 State* BasketScene::stateForTagFromSelectedNotes(Tag *tag)
3416 {
3417     State *state = 0;
3418 
3419     FOR_EACH_NOTE(note)
3420     if (note->stateForTagFromSelectedNotes(tag, &state) && state == 0)
3421         return 0;
3422     return state;
3423 }
3424 
activatedTagShortcut(Tag * tag)3425 void BasketScene::activatedTagShortcut(Tag *tag)
3426 {
3427     // Compute the next state to set:
3428     State *state = stateForTagFromSelectedNotes(tag);
3429     if (state)
3430         state = state->nextState(/*cycle=*/false);
3431     else
3432         state = tag->states().first();
3433 
3434     // Set or unset it:
3435     if (state) {
3436         FOR_EACH_NOTE(note)
3437         note->addStateToSelectedNotes(state, /*orReplace=*/true);
3438         updateEditorAppearance();
3439     } else
3440         removeTagFromSelectedNotes(tag);
3441 
3442     filterAgain();
3443     save();
3444 }
3445 
popupTagsMenu(Note * note)3446 void BasketScene::popupTagsMenu(Note *note)
3447 {
3448     m_tagPopupNote = note;
3449 
3450     QMenu menu(m_view);
3451     menu.addSection(i18n("Tags"));
3452 
3453     Global::bnpView->populateTagsMenu(menu, note);
3454 
3455     m_lockedHovering = true;
3456     menu.exec(QCursor::pos());
3457 }
3458 
unlockHovering()3459 void BasketScene::unlockHovering()
3460 {
3461     m_lockedHovering = false;
3462     doHoverEffects();
3463 }
3464 
toggledTagInMenu(QAction * act)3465 void BasketScene::toggledTagInMenu(QAction *act)
3466 {
3467     int id = act->data().toInt();
3468     if (id == 1) { // Assign new Tag...
3469         TagsEditDialog dialog(m_view, /*stateToEdit=*/0, /*addNewTag=*/true);
3470         dialog.exec();
3471         if (!dialog.addedStates().isEmpty()) {
3472             State::List states = dialog.addedStates();
3473             for (State::List::iterator itState = states.begin(); itState != states.end(); ++itState)
3474                 FOR_EACH_NOTE(note)
3475                 note->addStateToSelectedNotes(*itState);
3476             updateEditorAppearance();
3477             filterAgain();
3478             save();
3479         }
3480         return;
3481     }
3482     if (id == 2) { // Remove All
3483         removeAllTagsFromSelectedNotes();
3484         filterAgain();
3485         save();
3486         return;
3487     }
3488     if (id == 3) { // Customize...
3489         TagsEditDialog dialog(m_view);
3490         dialog.exec();
3491         return;
3492     }
3493 
3494     Tag *tag = Tag::all[id - 10];
3495     if (!tag)
3496         return;
3497 
3498     if (m_tagPopupNote->hasTag(tag))
3499         removeTagFromSelectedNotes(tag);
3500     else
3501         addTagToSelectedNotes(tag);
3502     m_tagPopupNote->setWidth(0); // To force a new layout computation
3503     filterAgain();
3504     save();
3505 }
3506 
addTagToSelectedNotes(Tag * tag)3507 void BasketScene::addTagToSelectedNotes(Tag *tag)
3508 {
3509     FOR_EACH_NOTE(note)
3510     note->addTagToSelectedNotes(tag);
3511     updateEditorAppearance();
3512 }
3513 
removeTagFromSelectedNotes(Tag * tag)3514 void BasketScene::removeTagFromSelectedNotes(Tag *tag)
3515 {
3516     FOR_EACH_NOTE(note)
3517     note->removeTagFromSelectedNotes(tag);
3518     updateEditorAppearance();
3519 }
3520 
addStateToSelectedNotes(State * state)3521 void BasketScene::addStateToSelectedNotes(State *state)
3522 {
3523     FOR_EACH_NOTE(note)
3524     note->addStateToSelectedNotes(state);
3525     updateEditorAppearance();
3526 }
3527 
updateEditorAppearance()3528 void BasketScene::updateEditorAppearance()
3529 {
3530     if (isDuringEdit() && m_editor->graphicsWidget()) {
3531       m_editor->graphicsWidget()->setFont(m_editor->note()->font());
3532 
3533       if(m_editor->graphicsWidget()->widget())
3534       {
3535 	QPalette palette;
3536 	palette.setColor(m_editor->graphicsWidget()->widget()->backgroundRole(), m_editor->note()->backgroundColor());
3537 	palette.setColor(m_editor->graphicsWidget()->widget()->foregroundRole(), m_editor->note()->textColor());
3538 	m_editor->graphicsWidget()->setPalette(palette);
3539       }
3540 
3541       // Uggly Hack arround Qt bugs: placeCursor() don't call any signal:
3542       HtmlEditor *htmlEditor = dynamic_cast<HtmlEditor*>(m_editor);
3543       if (htmlEditor) {
3544 	if (m_editor->textEdit()->textCursor().atStart()) {
3545 	  m_editor->textEdit()->moveCursor(QTextCursor::Right);
3546           m_editor->textEdit()->moveCursor(QTextCursor::Left);
3547         } else {
3548 	  m_editor->textEdit()->moveCursor(QTextCursor::Left);
3549           m_editor->textEdit()->moveCursor(QTextCursor::Right);
3550 
3551 	}
3552         htmlEditor->cursorPositionChanged(); // Does not work anyway :-( (when clicking on a red bold text, the toolbar still show black normal text)
3553       }
3554     }
3555 }
3556 
editorPropertiesChanged()3557 void BasketScene::editorPropertiesChanged()
3558 {
3559     if (isDuringEdit() && m_editor->note()->content()->type() == NoteType::Html) {
3560         m_editor->textEdit()->setAutoFormatting(Settings::autoBullet() ? QTextEdit::AutoAll : QTextEdit::AutoNone);
3561     }
3562 }
3563 
changeStateOfSelectedNotes(State * state)3564 void BasketScene::changeStateOfSelectedNotes(State *state)
3565 {
3566     FOR_EACH_NOTE(note)
3567     note->changeStateOfSelectedNotes(state);
3568     updateEditorAppearance();
3569 }
3570 
removeAllTagsFromSelectedNotes()3571 void BasketScene::removeAllTagsFromSelectedNotes()
3572 {
3573     FOR_EACH_NOTE(note)
3574     note->removeAllTagsFromSelectedNotes();
3575     updateEditorAppearance();
3576 }
3577 
selectedNotesHaveTags()3578 bool BasketScene::selectedNotesHaveTags()
3579 {
3580     FOR_EACH_NOTE(note)
3581     if (note->selectedNotesHaveTags())
3582         return true;
3583     return false;
3584 }
3585 
backgroundColor() const3586 QColor BasketScene::backgroundColor() const
3587 {
3588     if (m_backgroundColorSetting.isValid())
3589         return m_backgroundColorSetting;
3590     else
3591         return palette().color(QPalette::Base);
3592 }
3593 
textColor() const3594 QColor BasketScene::textColor() const
3595 {
3596     if (m_textColorSetting.isValid())
3597         return m_textColorSetting;
3598     else
3599         return palette().color(QPalette::Text);
3600 }
3601 
unbufferizeAll()3602 void BasketScene::unbufferizeAll()
3603 {
3604     FOR_EACH_NOTE(note)
3605     note->unbufferizeAll();
3606 }
3607 
editedNote()3608 Note* BasketScene::editedNote()
3609 {
3610     if (m_editor)
3611         return m_editor->note();
3612     else
3613         return 0;
3614 }
3615 
hasTextInEditor()3616 bool BasketScene::hasTextInEditor()
3617 {
3618     if (!isDuringEdit() || !redirectEditActions())
3619         return false;
3620 
3621     if (m_editor->textEdit())
3622         return ! m_editor->textEdit()->document()->isEmpty();
3623     else if (m_editor->lineEdit())
3624         return ! m_editor->lineEdit()->displayText().isEmpty();
3625     else
3626         return false;
3627 }
3628 
hasSelectedTextInEditor()3629 bool BasketScene::hasSelectedTextInEditor()
3630 {
3631     if (!isDuringEdit() || !redirectEditActions())
3632         return false;
3633 
3634     if (m_editor->textEdit()) {
3635         // The following line does NOT work if one letter is selected and the user press Shift+Left or Shift+Right to unselect than letter:
3636         // Qt misteriously tell us there is an invisible selection!!
3637         //return m_editor->textEdit()->hasSelectedText();
3638         return !m_editor->textEdit()->textCursor().selectedText().isEmpty();
3639     } else if (m_editor->lineEdit())
3640         return m_editor->lineEdit()->hasSelectedText();
3641     else
3642         return false;
3643 }
3644 
selectedAllTextInEditor()3645 bool BasketScene::selectedAllTextInEditor()
3646 {
3647     if (!isDuringEdit() || !redirectEditActions())
3648         return false;
3649 
3650     if (m_editor->textEdit()) {
3651         return m_editor->textEdit()->document()->isEmpty() || m_editor->textEdit()->toPlainText() == m_editor->textEdit()->textCursor().selectedText();
3652     } else if (m_editor->lineEdit())
3653         return m_editor->lineEdit()->displayText().isEmpty() || m_editor->lineEdit()->displayText() == m_editor->lineEdit()->selectedText();
3654     else
3655         return false;
3656 }
3657 
selectionChangedInEditor()3658 void BasketScene::selectionChangedInEditor()
3659 {
3660     Global::bnpView->notesStateChanged();
3661 }
3662 
contentChangedInEditor()3663 void BasketScene::contentChangedInEditor()
3664 {
3665     // 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):
3666     if (m_editor->textEdit())
3667         m_editor->autoSave(/*toFileToo=*/false);
3668 //  else {
3669     if (m_inactivityAutoSaveTimer.isActive())
3670         m_inactivityAutoSaveTimer.stop();
3671     m_inactivityAutoSaveTimer.setSingleShot(true);
3672     m_inactivityAutoSaveTimer.start(3 * 1000);
3673     Global::bnpView->setUnsavedStatus(true);
3674 //  }
3675 }
3676 
inactivityAutoSaveTimeout()3677 void BasketScene::inactivityAutoSaveTimeout()
3678 {
3679     if (m_editor)
3680         m_editor->autoSave(/*toFileToo=*/true);
3681 }
3682 
placeEditorAndEnsureVisible()3683 void BasketScene::placeEditorAndEnsureVisible()
3684 {
3685     placeEditor(/*andEnsureVisible=*/true);
3686 }
3687 
3688 // TODO: [kw] Oh boy, this will probably require some tweaking.
placeEditor(bool)3689 void BasketScene::placeEditor(bool /*andEnsureVisible*/ /*= false*/)
3690 {
3691     if (!isDuringEdit())
3692         return;
3693 
3694     QFrame    *editorQFrame = dynamic_cast<QFrame*>(m_editor->graphicsWidget()->widget());
3695     KTextEdit *textEdit     = m_editor->textEdit();
3696     Note      *note         = m_editor->note();
3697 
3698     qreal frameWidth = (editorQFrame ? editorQFrame->frameWidth() : 0);
3699     qreal x          = note->x() + note->contentX() + note->content()->xEditorIndent() - frameWidth;
3700     qreal y;
3701     qreal maxHeight  = qMax((qreal)m_view->viewport()->height(), sceneRect().height());
3702     qreal height, width;
3703 
3704     if (textEdit) {
3705         // Need to do it 2 times, because it's wrong overwise
3706         // (sometimes, width depends on height, and sometimes, height depends on with):
3707         for (int i = 0; i < 2; i++) {
3708             // FIXME: CRASH: Select all text, press Del or [<--] and editor->removeSelectedText() is called:
3709             //        editor->sync() CRASH!!
3710             //      editor->sync();
3711             y = note->y() + Note::NOTE_MARGIN - frameWidth;
3712             height = note->height() - 2 * frameWidth - 2 * Note::NOTE_MARGIN;
3713             width  = note->x() + note->width() - x + 1;
3714             if (y + height > maxHeight)
3715                 y = maxHeight - height;
3716 
3717             m_editor->graphicsWidget()->setMaximumSize(width,height);
3718 	    textEdit->setFixedSize(width, height);
3719             textEdit->viewport()->setFixedSize(width, height);
3720         }
3721     } else {
3722         height = note->height() - 2 * Note::NOTE_MARGIN + 2 * frameWidth;
3723         width  = note->x() + note->width() - x;//note->rightLimit() - x + 2*m_view->frameWidth;
3724         if(m_editor->graphicsWidget())
3725 	  m_editor->graphicsWidget()->widget()->setFixedSize(width, height);
3726         x -= 1;
3727         y = note->y() + Note::NOTE_MARGIN - frameWidth;
3728     }
3729     if ((m_editorWidth > 0 && m_editorWidth != width) || (m_editorHeight > 0 && m_editorHeight != height)) {
3730         m_editorWidth  = width; // Avoid infinite recursion!!!
3731         m_editorHeight = height;
3732         m_editor->autoSave(/*toFileToo=*/true);
3733     }
3734     m_editorWidth  = width;
3735     m_editorHeight = height;
3736     m_editor->graphicsWidget()->setPos(x,y);
3737     m_editorX = x;
3738     m_editorY = y;
3739 
3740 //  if (andEnsureVisible)
3741 //      ensureNoteVisible(note);
3742 }
3743 
editorCursorPositionChanged()3744 void BasketScene::editorCursorPositionChanged()
3745 {
3746     if (!isDuringEdit())
3747       return;
3748 
3749     FocusedTextEdit *textEdit = dynamic_cast<FocusedTextEdit*>(m_editor->textEdit());
3750 
3751     if( textEdit )
3752     {
3753       QPoint cursorPoint = textEdit->viewport()->mapToGlobal(textEdit->cursorRect().center());
3754 
3755       //QPointF contentsCursor = m_view->mapToScene( m_view->viewport()->mapFromGlobal(cursorPoint) );
3756       //m_view->ensureVisible(contentsCursor.x(), contentsCursor.y(),1,1);
3757     }
3758 }
3759 
closeEditorDelayed()3760 void BasketScene::closeEditorDelayed()
3761 {
3762     setFocus();
3763     QTimer::singleShot(0, this, SLOT(closeEditor()));
3764 }
3765 
closeEditor(bool deleteEmptyNote)3766 bool BasketScene::closeEditor(bool deleteEmptyNote /* =true*/)
3767 {
3768     if (!isDuringEdit())
3769         return true;
3770 
3771     if (m_doNotCloseEditor)
3772         return true;
3773 
3774     if (m_redirectEditActions) {
3775         if (m_editor->textEdit()) {
3776 	    disconnect(m_editor->textEdit(), SIGNAL(selectionChanged()), this, SLOT(selectionChangedInEditor()));
3777             disconnect(m_editor->textEdit(), SIGNAL(textChanged()),               this, SLOT(selectionChangedInEditor()));
3778             disconnect(m_editor->textEdit(), SIGNAL(textChanged()),               this, SLOT(contentChangedInEditor()));
3779         } else if (m_editor->lineEdit()) {
3780             disconnect(m_editor->lineEdit(), SIGNAL(selectionChanged()), this, SLOT(selectionChangedInEditor()));
3781             disconnect(m_editor->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(selectionChangedInEditor()));
3782             disconnect(m_editor->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(contentChangedInEditor()));
3783         }
3784     }
3785     m_editorTrackMouseEvent = false;
3786     m_editor->graphicsWidget()->widget()->disconnect();
3787     removeItem(m_editor->graphicsWidget());
3788     m_editor->validate();
3789 
3790     Note *note = m_editor->note();
3791 
3792     // Delete the editor BEFORE unselecting the note because unselecting the note would trigger closeEditor() recursivly:
3793     bool isEmpty = m_editor->isEmpty();
3794     delete m_editor;
3795 
3796     m_editor = 0;
3797     m_redirectEditActions = false;
3798     m_editorWidth  = -1;
3799     m_editorHeight = -1;
3800     m_inactivityAutoSaveTimer.stop();
3801 
3802     // Delete the note if it is now empty:
3803     if (isEmpty && deleteEmptyNote) {
3804         focusANonSelectedNoteAboveOrThenBelow();
3805         note->setSelected(true);
3806         note->deleteSelectedNotes();
3807 	if( m_hoveredNote == note ) m_hoveredNote = 0;
3808 	if( m_focusedNote == note ) m_focusedNote = 0;
3809 	delete note;
3810 	save();
3811         note = 0;
3812     }
3813 
3814     unlockHovering();
3815     filterAgain(/*andEnsureVisible=*/false);
3816 
3817 // Does not work:
3818 //  if (Settings::playAnimations())
3819 //      note->setOnTop(true); // So if it grew, do not obscure it temporarily while the notes below it are moving
3820 
3821     if (note)
3822         note->setSelected(false);//unselectAll();
3823     doHoverEffects();
3824 //  save();
3825 
3826     Global::bnpView->m_actEditNote->setEnabled(!isLocked() && countSelecteds() == 1 /*&& !isDuringEdit()*/);
3827 
3828     emit resetStatusBarText(); // Remove the "Editing. ... to validate." text.
3829 
3830     //if (qApp->activeWindow() == Global::mainContainer)
3831 
3832     // 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:
3833     if (!decoration()->filterBar()->lineEdit()->hasFocus())
3834         setFocus();
3835 
3836     // Return true if the note is still there:
3837     return (note != 0);
3838 }
3839 
closeBasket()3840 void BasketScene::closeBasket()
3841 {
3842     closeEditor();
3843     unbufferizeAll(); // Keep the memory footprint low
3844     if (isEncrypted()) {
3845         if (Settings::enableReLockTimeout()) {
3846             int seconds = Settings::reLockTimeoutMinutes() * 60;
3847             m_inactivityAutoLockTimer.setSingleShot(true);
3848             m_inactivityAutoLockTimer.start(seconds * 1000);
3849         }
3850     }
3851 }
3852 
openBasket()3853 void BasketScene::openBasket()
3854 {
3855     if (m_inactivityAutoLockTimer.isActive())
3856         m_inactivityAutoLockTimer.stop();
3857 }
3858 
theSelectedNote()3859 Note* BasketScene::theSelectedNote()
3860 {
3861     if (countSelecteds() != 1) {
3862         qDebug() << "NO SELECTED NOTE !!!!";
3863         return 0;
3864     }
3865 
3866     Note *selectedOne;
3867     FOR_EACH_NOTE(note) {
3868         selectedOne = note->theSelectedNote();
3869         if (selectedOne)
3870             return selectedOne;
3871     }
3872 
3873     qDebug() << "One selected note, BUT NOT FOUND !!!!";
3874 
3875     return 0;
3876 }
3877 
selectedNotes()3878 NoteSelection* BasketScene::selectedNotes()
3879 {
3880     NoteSelection selection;
3881 
3882     FOR_EACH_NOTE(note)
3883     selection.append(note->selectedNotes());
3884 
3885     if (!selection.firstChild)
3886         return 0;
3887 
3888     for (NoteSelection *node = selection.firstChild; node; node = node->next)
3889         node->parent = 0;
3890 
3891     // If the top-most groups are columns, export only childs of those groups
3892     // (because user is not consciencious that columns are groups, and don't care: it's not what she want):
3893     if (selection.firstChild->note->isColumn()) {
3894         NoteSelection tmpSelection;
3895         NoteSelection *nextNode;
3896         NoteSelection *nextSubNode;
3897         for (NoteSelection *node = selection.firstChild; node; node = nextNode) {
3898             nextNode = node->next;
3899             if (node->note->isColumn()) {
3900                 for (NoteSelection *subNode = node->firstChild; subNode; subNode = nextSubNode) {
3901                     nextSubNode = subNode->next;
3902                     tmpSelection.append(subNode);
3903                     subNode->parent = 0;
3904                     subNode->next = 0;
3905                 }
3906             } else {
3907                 tmpSelection.append(node);
3908                 node->parent = 0;
3909                 node->next = 0;
3910             }
3911         }
3912 //      debugSel(tmpSelection.firstChild);
3913         return tmpSelection.firstChild;
3914     } else {
3915 //      debugSel(selection.firstChild);
3916         return selection.firstChild;
3917     }
3918 }
3919 
showEditedNoteWhileFiltering()3920 void BasketScene::showEditedNoteWhileFiltering()
3921 {
3922     if (m_editor) {
3923         Note *note = m_editor->note();
3924         filterAgain();
3925         note->setSelected(true);
3926         relayoutNotes(false);
3927         note->setX(note->x());
3928         note->setY(note->y());
3929         filterAgainDelayed();
3930     }
3931 }
3932 
noteEdit(Note * note,bool justAdded,const QPointF & clickedPoint)3933 void BasketScene::noteEdit(Note *note, bool justAdded, const QPointF &clickedPoint) // TODO: Remove the first parameter!!!
3934 {
3935     if (!note)
3936         note = theSelectedNote(); // TODO: Or pick the focused note!
3937     if (!note)
3938         return;
3939 
3940     if (isDuringEdit()) {
3941         closeEditor(); // Validate the noteeditors in QLineEdit that does not intercept Enter key press (and edit is triggered with Enter too... Can conflict)
3942         return;
3943     }
3944 
3945     if (note != m_focusedNote) {
3946         setFocusedNote(note);
3947         m_startOfShiftSelectionNote = note;
3948     }
3949 
3950     if (justAdded && isFiltering()) {
3951         QTimer::singleShot(0, this, SLOT(showEditedNoteWhileFiltering()));
3952     }
3953 
3954     doHoverEffects(note, Note::Content); // Be sure (in the case Edit was triggered by menu or Enter key...): better feedback!
3955 
3956     NoteEditor *editor = NoteEditor::editNoteContent(note->content(),0);
3957     if (editor->graphicsWidget()) {
3958         m_editor = editor;
3959 
3960 	addItem(m_editor->graphicsWidget());
3961 
3962 	placeEditorAndEnsureVisible(); //       placeEditor(); // FIXME: After?
3963         m_redirectEditActions = m_editor->lineEdit() || m_editor->textEdit();
3964         if (m_redirectEditActions) {
3965             // In case there is NO text, "Select All" is disabled. But if the user press a key the there is now a text:
3966             // selection has not changed but "Select All" should be re-enabled:
3967             m_editor->connectActions(this);
3968         }
3969 
3970 	m_editor->graphicsWidget()->setFocus();
3971         connect(m_editor, SIGNAL(askValidation()),
3972                 this, SLOT(closeEditorDelayed()));
3973         connect(m_editor, SIGNAL(mouseEnteredEditorWidget()),
3974                 this, SLOT(mouseEnteredEditorWidget()));
3975 
3976         if (clickedPoint != QPoint()) {
3977 	  m_editor->setCursorTo(clickedPoint);
3978           updateEditorAppearance();
3979 	}
3980 
3981 //      qApp->processEvents();     // Show the editor toolbar before ensuring the note is visible
3982         ensureNoteVisible(note);   //  because toolbar can create a new line and then partially hide the note
3983         m_editor->graphicsWidget()->setFocus(); // When clicking in the basket, a QTimer::singleShot(0, ...) focus the basket! So we focus the the widget after qApp->processEvents()
3984         emit resetStatusBarText(); // Display "Editing. ... to validate."
3985     } else {
3986         // Delete the note user have canceled the addition:
3987         if ((justAdded && editor->canceled()) || editor->isEmpty() /*) && editor->note()->states().count() <= 0*/) {
3988             focusANonSelectedNoteAboveOrThenBelow();
3989             editor->note()->setSelected(true);
3990             editor->note()->deleteSelectedNotes();
3991 	    if( m_hoveredNote == editor->note() ) m_hoveredNote = 0;
3992 	    if( m_focusedNote == editor->note() ) m_focusedNote = 0;
3993 	    delete editor->note();
3994             save();
3995         }
3996         editor->deleteLater();
3997         unlockHovering();
3998         filterAgain();
3999         unselectAll();
4000     }
4001     // Must set focus to the editor, otherwise edit cursor is not seen and precomposed characters cannot be entered
4002     if (m_editor != NULL && m_editor->textEdit() != NULL)
4003         m_editor->textEdit()->setFocus();
4004 
4005     Global::bnpView->m_actEditNote->setEnabled(false);
4006 }
4007 
noteDelete()4008 void BasketScene::noteDelete()
4009 {
4010     if (redirectEditActions()) {
4011         if (m_editor->textEdit())
4012             m_editor->textEdit()->textCursor().deleteChar();
4013         else if (m_editor->lineEdit())
4014             m_editor->lineEdit()->del();
4015         return;
4016     }
4017 
4018     if (countSelecteds() <= 0)
4019         return;
4020     int really = KMessageBox::Yes;
4021     if (Settings::confirmNoteDeletion())
4022         really = KMessageBox::questionYesNo(m_view,
4023                                             i18np("<qt>Do you really want to delete this note?</qt>",
4024                                                   "<qt>Do you really want to delete these <b>%1</b> notes?</qt>",
4025                                                   countSelecteds()),
4026                                             i18np("Delete Note", "Delete Notes", countSelecteds())
4027                                             , KStandardGuiItem::del(), KStandardGuiItem::cancel());
4028     if (really == KMessageBox::No)
4029         return;
4030 
4031     noteDeleteWithoutConfirmation();
4032 }
4033 
focusANonSelectedNoteBelow(bool inSameColumn)4034 void BasketScene::focusANonSelectedNoteBelow(bool inSameColumn)
4035 {
4036     // First focus another unselected one below it...:
4037     if (m_focusedNote && m_focusedNote->isSelected()) {
4038         Note *next = m_focusedNote->nextShownInStack();
4039         while (next && next->isSelected())
4040             next = next->nextShownInStack();
4041         if (next) {
4042             if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == next->parentPrimaryNote()) {
4043                 setFocusedNote(next);
4044                 m_startOfShiftSelectionNote = next;
4045             }
4046         }
4047     }
4048 }
4049 
focusANonSelectedNoteAbove(bool inSameColumn)4050 void BasketScene::focusANonSelectedNoteAbove(bool inSameColumn)
4051 {
4052     // ... Or above it:
4053     if (m_focusedNote && m_focusedNote->isSelected()) {
4054         Note *prev = m_focusedNote->prevShownInStack();
4055         while (prev && prev->isSelected())
4056             prev = prev->prevShownInStack();
4057         if (prev) {
4058             if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == prev->parentPrimaryNote()) {
4059                 setFocusedNote(prev);
4060                 m_startOfShiftSelectionNote = prev;
4061             }
4062         }
4063     }
4064 }
4065 
focusANonSelectedNoteBelowOrThenAbove()4066 void BasketScene::focusANonSelectedNoteBelowOrThenAbove()
4067 {
4068     focusANonSelectedNoteBelow(/*inSameColumn=*/true);
4069     focusANonSelectedNoteAbove(/*inSameColumn=*/true);
4070     focusANonSelectedNoteBelow(/*inSameColumn=*/false);
4071     focusANonSelectedNoteAbove(/*inSameColumn=*/false);
4072 }
4073 
focusANonSelectedNoteAboveOrThenBelow()4074 void BasketScene::focusANonSelectedNoteAboveOrThenBelow()
4075 {
4076     focusANonSelectedNoteAbove(/*inSameColumn=*/true);
4077     focusANonSelectedNoteBelow(/*inSameColumn=*/true);
4078     focusANonSelectedNoteAbove(/*inSameColumn=*/false);
4079     focusANonSelectedNoteBelow(/*inSameColumn=*/false);
4080 }
4081 
noteDeleteWithoutConfirmation(bool deleteFilesToo)4082 void BasketScene::noteDeleteWithoutConfirmation(bool deleteFilesToo)
4083 {
4084     // If the currently focused note is selected, it will be deleted.
4085     focusANonSelectedNoteBelowOrThenAbove();
4086 
4087     // Do the deletion:
4088     Note *note = firstNote();
4089     Note *next;
4090     while (note) {
4091         next = note->next(); // If we delete 'note' on the next line, note->next() will be 0!
4092         note->deleteSelectedNotes(deleteFilesToo, &m_notesToBeDeleted);
4093         note = next;
4094     }
4095 
4096     if(!m_notesToBeDeleted.isEmpty())
4097     {
4098       doCleanUp();
4099     }
4100 
4101     relayoutNotes(true); // FIXME: filterAgain()?
4102     save();
4103 }
4104 
doCopy(CopyMode copyMode)4105 void BasketScene::doCopy(CopyMode copyMode)
4106 {
4107     QClipboard *cb = QApplication::clipboard();
4108     QClipboard::Mode mode = ((copyMode == CopyToSelection) ? QClipboard::Selection : QClipboard::Clipboard);
4109 
4110     NoteSelection *selection = selectedNotes();
4111     int countCopied = countSelecteds();
4112     if (selection->firstStacked()) {
4113         QDrag *d = NoteDrag::dragObject(selection, copyMode == CutToClipboard, /*source=*/0); // d will be deleted by QT
4114 //      /*bool shouldRemove = */d->drag();
4115 //      delete selection;
4116         cb->setMimeData(d->mimeData(), mode); // NoteMultipleDrag will be deleted by QT
4117 //      if (copyMode == CutToClipboard && !note->useFile()) // If useFile(), NoteDrag::dragObject() will delete it TODO
4118 //          note->slotDelete();
4119 
4120         if (copyMode == CutToClipboard)
4121 	{
4122             noteDeleteWithoutConfirmation(/*deleteFilesToo=*/false);
4123 	    focusANote();
4124 	}
4125 
4126         switch (copyMode) {
4127         default:
4128         case CopyToClipboard: emit postMessage(i18np("Copied note to clipboard.", "Copied notes to clipboard.", countCopied)); break;
4129         case CutToClipboard:  emit postMessage(i18np("Cut note to clipboard.",    "Cut notes to clipboard.",    countCopied)); break;
4130         case CopyToSelection: emit postMessage(i18np("Copied note to selection.", "Copied notes to selection.", countCopied)); break;
4131         }
4132     }
4133 }
4134 
noteCopy()4135 void BasketScene::noteCopy()
4136 {
4137     if (redirectEditActions()) {
4138         if (m_editor->textEdit())
4139             m_editor->textEdit()->copy();
4140         else if (m_editor->lineEdit())
4141             m_editor->lineEdit()->copy();
4142     } else
4143         doCopy(CopyToClipboard);
4144 }
4145 
noteCut()4146 void BasketScene::noteCut()
4147 {
4148     if (redirectEditActions()) {
4149         if (m_editor->textEdit())
4150             m_editor->textEdit()->cut();
4151         else if (m_editor->lineEdit())
4152             m_editor->lineEdit()->cut();
4153     } else
4154         doCopy(CutToClipboard);
4155 }
4156 
noteOpen(Note * note)4157 void BasketScene::noteOpen(Note *note)
4158 {
4159     /*
4160     GetSelectedNotes
4161     NoSelectedNote || Count == 0 ? return
4162     AllTheSameType ?
4163     Get { url, message(count) }
4164     */
4165 
4166     // TODO: Open ALL selected notes!
4167     if (!note)
4168         note = theSelectedNote();
4169     if (!note)
4170         return;
4171 
4172     QUrl    url     = note->content()->urlToOpen(/*with=*/false);
4173     QString message = note->content()->messageWhenOpening(NoteContent::OpenOne /*NoteContent::OpenSeveral*/);
4174     if (url.isEmpty()) {
4175         if (message.isEmpty())
4176             emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/);
4177         else {
4178             int result = KMessageBox::warningContinueCancel(m_view, message, /*caption=*/QString::null, KGuiItem(i18n("&Edit"), "edit"));
4179             if (result == KMessageBox::Continue)
4180                 noteEdit(note);
4181         }
4182     } else {
4183         emit postMessage(message); // "Openning link target..." / "Launching application..." / "Openning note file..."
4184         // Finally do the opening job:
4185         QString customCommand = note->content()->customOpenCommand();
4186 
4187         if (url.url().startsWith("basket://")) {
4188             emit crossReference(url.url());
4189         } else if (customCommand.isEmpty()) {
4190             KRun *run = new KRun(url, m_view->window());
4191             run->setAutoDelete(true);
4192         } else {
4193             QList<QUrl> urls{url};
4194             KRun::run(customCommand, urls, m_view->window());
4195         }
4196     }
4197 }
4198 
4199 /** Code from bool KRun::displayOpenWithDialog(const KUrl::List& lst, bool tempFiles)
4200   * It does not allow to set a text, so I ripped it to do that:
4201   */
KRun__displayOpenWithDialog(const QList<QUrl> & lst,QWidget * window,bool tempFiles,const QString & text)4202 bool KRun__displayOpenWithDialog(const QList<QUrl>& lst, QWidget *window, bool tempFiles, const QString &text)
4203 {
4204     if (qApp && !KAuthorized::authorizeKAction("openwith")) {
4205         KMessageBox::sorry(window, i18n("You are not authorized to open this file.")); // TODO: Better message, i18n freeze :-(
4206         return false;
4207     }
4208     KOpenWithDialog l(lst, text, QString::null, 0L);
4209     if (l.exec()) {
4210         KService::Ptr service = l.service();
4211         if (!!service)
4212             return KRun::run(*service, lst, window, tempFiles);
4213         //qDebug(250) << "No service set, running " << l.text() << endl;
4214         return KRun::run(l.text(), lst, window); // TODO handle tempFiles
4215     }
4216     return false;
4217 }
4218 
noteOpenWith(Note * note)4219 void BasketScene::noteOpenWith(Note *note)
4220 {
4221     if (!note)
4222         note = theSelectedNote();
4223     if (!note)
4224         return;
4225 
4226     QUrl    url     = note->content()->urlToOpen(/*with=*/true);
4227     QString message = note->content()->messageWhenOpening(NoteContent::OpenOneWith /*NoteContent::OpenSeveralWith*/);
4228     QString text    = note->content()->messageWhenOpening(NoteContent::OpenOneWithDialog /*NoteContent::OpenSeveralWithDialog*/);
4229     if (url.isEmpty()) {
4230         emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/);
4231     } else {
4232         QList<QUrl> urls{url};
4233         if (KRun__displayOpenWithDialog(urls, m_view->window(), false, text))
4234             emit postMessage(message); // "Opening link target with..." / "Opening note file with..."
4235     }
4236 }
4237 
noteSaveAs()4238 void BasketScene::noteSaveAs()
4239 {
4240 //  if (!note)
4241 //      note = theSelectedNote();
4242     Note *note = theSelectedNote();
4243     if (!note)
4244         return;
4245 
4246     QUrl url = note->content()->urlToOpen(/*with=*/false);
4247     if (url.isEmpty())
4248         return;
4249 
4250     QString fileName = QFileDialog::getSaveFileName(m_view, i18n("Save to File"), url.fileName(), note->content()->saveAsFilters());
4251     // TODO: Ask to overwrite !
4252     if (fileName.isEmpty())
4253         return;
4254 
4255     // TODO: Convert format, etc. (use NoteContent::saveAs(fileName))
4256     KIO::copy(url, QUrl::fromLocalFile(fileName));
4257 }
4258 
selectedGroup()4259 Note* BasketScene::selectedGroup()
4260 {
4261     FOR_EACH_NOTE(note) {
4262         Note *selectedGroup = note->selectedGroup();
4263         if (selectedGroup) {
4264             // If the selected group is one group in a column, then return that group, and not the column,
4265             // because the column is not ungrouppage, and the Ungroup action would be disabled.
4266             if (selectedGroup->isColumn() && selectedGroup->firstChild() && !selectedGroup->firstChild()->next()) {
4267                 return selectedGroup->firstChild();
4268             }
4269             return selectedGroup;
4270         }
4271     }
4272     return 0;
4273 }
4274 
selectionIsOneGroup()4275 bool BasketScene::selectionIsOneGroup()
4276 {
4277     return (selectedGroup() != 0);
4278 }
4279 
firstSelected()4280 Note* BasketScene::firstSelected()
4281 {
4282     Note *first = 0;
4283     FOR_EACH_NOTE(note) {
4284         first = note->firstSelected();
4285         if (first)
4286             return first;
4287     }
4288     return 0;
4289 }
4290 
lastSelected()4291 Note* BasketScene::lastSelected()
4292 {
4293     Note *last = 0, *tmp = 0;
4294     FOR_EACH_NOTE(note) {
4295         tmp = note->lastSelected();
4296         if (tmp)
4297             last = tmp;
4298     }
4299     return last;
4300 }
4301 
convertTexts()4302 bool BasketScene::convertTexts()
4303 {
4304     m_watcher->stopScan();
4305     bool convertedNotes = false;
4306 
4307     if (!isLoaded())
4308         load();
4309 
4310     FOR_EACH_NOTE(note)
4311     if (note->convertTexts())
4312         convertedNotes = true;
4313 
4314     if (convertedNotes)
4315         save();
4316     m_watcher->startScan();
4317     return convertedNotes;
4318 }
4319 
noteGroup()4320 void BasketScene::noteGroup()
4321 {
4322     /*  // Nothing to do?
4323         if (isLocked() || countSelecteds() <= 1)
4324             return;
4325 
4326         // If every selected notes are ALREADY in one group, then don't touch anything:
4327         Note *selectedGroup = this->selectedGroup();
4328         if (selectedGroup && !selectedGroup->isColumn())
4329             return;
4330     */
4331 
4332     // Copied from BNPView::updateNotesActions()
4333     bool severalSelected = countSelecteds() >= 2;
4334     Note *selectedGroup = (severalSelected ? this->selectedGroup() : 0);
4335     bool enabled = !isLocked() && severalSelected && (!selectedGroup || selectedGroup->isColumn());
4336     if (!enabled)
4337         return;
4338 
4339     // Get the first selected note: we will group selected items just before:
4340     Note *first = firstSelected();
4341 //  if (selectedGroup != 0 || first == 0)
4342 //      return;
4343 
4344     m_loaded = false; // Hack to avoid notes to be unselected and new notes to be selected:
4345 
4346     // Create and insert the receiving group:
4347     Note *group = new Note(this);
4348     if (first->isFree()) {
4349         insertNote(group, 0L, Note::BottomColumn, QPointF(first->x(), first->y()), /*animateNewPosition=*/false);
4350     } else {
4351         insertNote(group, first, Note::TopInsert, QPointF(), /*animateNewPosition=*/false);
4352     }
4353 
4354     // 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!
4355     Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4356     insertNote(fakeNote, group, Note::BottomColumn, QPointF(), /*animateNewPosition=*/false);
4357 
4358     // Group the notes:
4359     Note *nextNote;
4360     Note *note = firstNote();
4361     while (note) {
4362         nextNote = note->next();
4363         note->groupIn(group);
4364         note = nextNote;
4365     }
4366 
4367     m_loaded = true; // Part 2 / 2 of the workarround!
4368 
4369     // Do cleanup:
4370     unplugNote(fakeNote);
4371     delete fakeNote;
4372     unselectAll();
4373     group->setSelectedRecursively(true); // Notes were unselected by unplugging
4374 
4375     relayoutNotes(true);
4376     save();
4377 }
4378 
noteUngroup()4379 void BasketScene::noteUngroup()
4380 {
4381     Note *group = selectedGroup();
4382     if (group && !group->isColumn())
4383     {
4384         ungroupNote(group);
4385 	relayoutNotes(true);
4386     }
4387     save();
4388 }
4389 
unplugSelection(NoteSelection * selection)4390 void BasketScene::unplugSelection(NoteSelection *selection)
4391 {
4392     for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked())
4393     {
4394         unplugNote(toUnplug->note);
4395     }
4396 }
4397 
insertSelection(NoteSelection * selection,Note * after)4398 void BasketScene::insertSelection(NoteSelection *selection, Note *after)
4399 {
4400     for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) {
4401         if (toUnplug->note->isGroup()) {
4402             Note *group = new Note(this);
4403             insertNote(group, after, Note::BottomInsert, QPointF(), /*animateNewPosition=*/false);
4404             Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4405             insertNote(fakeNote, group, Note::BottomColumn, QPointF(), /*animateNewPosition=*/false);
4406             insertSelection(toUnplug->firstChild, fakeNote);
4407             unplugNote(fakeNote);
4408 	    delete fakeNote;
4409             after = group;
4410         } else {
4411             Note *note = toUnplug->note;
4412             note->setPrev(0);
4413             note->setNext(0);
4414             insertNote(note, after, Note::BottomInsert, QPointF(), /*animateNewPosition=*/true);
4415             after = note;
4416         }
4417     }
4418 }
4419 
selectSelection(NoteSelection * selection)4420 void BasketScene::selectSelection(NoteSelection *selection)
4421 {
4422     for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) {
4423         if (toUnplug->note->isGroup())
4424             selectSelection(toUnplug);
4425         else
4426             toUnplug->note->setSelected(true);
4427     }
4428 }
4429 
noteMoveOnTop()4430 void BasketScene::noteMoveOnTop()
4431 {
4432     // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket
4433     // TODO: Move on top/bottom... of the column or basjet
4434 
4435     NoteSelection *selection = selectedNotes();
4436     unplugSelection(selection);
4437     // Replug the notes:
4438     Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4439     if (isColumnsLayout()) {
4440         if (firstNote()->firstChild())
4441             insertNote(fakeNote, firstNote()->firstChild(), Note::TopInsert, QPointF(), /*animateNewPosition=*/false);
4442         else
4443             insertNote(fakeNote, firstNote(), Note::BottomColumn, QPointF(), /*animateNewPosition=*/false);
4444     } else {
4445         // TODO: Also allow to move notes on top of a group!!!!!!!
4446         insertNote(fakeNote, 0, Note::BottomInsert, QPointF(0, 0), /*animateNewPosition=*/false);
4447     }
4448     insertSelection(selection, fakeNote);
4449     unplugNote(fakeNote);
4450     delete fakeNote;
4451     selectSelection(selection);
4452     relayoutNotes(true);
4453     save();
4454 }
4455 
noteMoveOnBottom()4456 void BasketScene::noteMoveOnBottom()
4457 {
4458 
4459     // TODO: Duplicate code: void noteMoveOn();
4460 
4461     // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket
4462     // TODO: Move on top/bottom... of the column or basjet
4463 
4464     NoteSelection *selection = selectedNotes();
4465     unplugSelection(selection);
4466     // Replug the notes:
4467     Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4468     if (isColumnsLayout())
4469         insertNote(fakeNote, firstNote(), Note::BottomColumn, QPointF(), /*animateNewPosition=*/false);
4470     else {
4471         // TODO: Also allow to move notes on top of a group!!!!!!!
4472         insertNote(fakeNote, 0, Note::BottomInsert, QPointF(0, 0), /*animateNewPosition=*/false);
4473     }
4474     insertSelection(selection, fakeNote);
4475     unplugNote(fakeNote);
4476     delete fakeNote;
4477     selectSelection(selection);
4478     relayoutNotes(true);
4479     save();
4480 }
4481 
moveSelectionTo(Note * here,bool below)4482 void BasketScene::moveSelectionTo(Note *here, bool below/* = true*/)
4483 {
4484     NoteSelection *selection = selectedNotes();
4485     unplugSelection(selection);
4486     // Replug the notes:
4487     Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4488 //  if (isColumnsLayout())
4489     insertNote(fakeNote, here, (below ? Note::BottomInsert : Note::TopInsert), QPointF(), /*animateNewPosition=*/false);
4490 //  else {
4491 //      // TODO: Also allow to move notes on top of a group!!!!!!!
4492 //      insertNote(fakeNote, 0, Note::BottomInsert, QPoint(0, 0), /*animateNewPosition=*/false);
4493 //  }
4494     insertSelection(selection, fakeNote);
4495     unplugNote(fakeNote);
4496     delete fakeNote;
4497     selectSelection(selection);
4498     relayoutNotes(true);
4499     save();
4500 }
4501 
noteMoveNoteUp()4502 void BasketScene::noteMoveNoteUp()
4503 {
4504 
4505     // TODO: Move between columns, even if they are empty !!!!!!!
4506 
4507     // TODO: if first note of a group, move just above the group! And let that even if there is no note before that group!!!
4508 
4509     Note *first    = firstSelected();
4510     Note *previous = first->prevShownInStack();
4511     if (previous)
4512         moveSelectionTo(previous, /*below=*/false);
4513 }
4514 
noteMoveNoteDown()4515 void BasketScene::noteMoveNoteDown()
4516 {
4517     Note *first = lastSelected();
4518     Note *next  = first->nextShownInStack();
4519     if (next)
4520         moveSelectionTo(next, /*below=*/true);
4521 }
4522 
wheelEvent(QGraphicsSceneWheelEvent * event)4523 void BasketScene::wheelEvent(QGraphicsSceneWheelEvent *event)
4524 {
4525     //Q3ScrollView::wheelEvent(event);
4526 	QGraphicsScene::wheelEvent(event);
4527 }
4528 
linkLookChanged()4529 void BasketScene::linkLookChanged()
4530 {
4531     Note *note = m_firstNote;
4532     while (note) {
4533         note->linkLookChanged();
4534         note = note->next();
4535     }
4536     relayoutNotes(true);
4537 }
4538 
slotCopyingDone2(KIO::Job * job,const QUrl &,const QUrl & to)4539 void BasketScene::slotCopyingDone2(KIO::Job *job,
4540                               const QUrl &/*from*/,
4541                               const QUrl &to)
4542 {
4543     if (job->error()) {
4544         DEBUG_WIN << "Copy finished, ERROR";
4545         return;
4546     }
4547     Note *note = noteForFullPath(to.path());
4548     DEBUG_WIN << "Copy finished, load note: " + to.path() + (note ? "" : " --- NO CORRESPONDING NOTE");
4549     if (note != 0L) {
4550         note->content()->loadFromFile(/*lazyLoad=*/false);
4551         if (isEncrypted())
4552             note->content()->saveToFile();
4553         if (m_focusedNote == note)   // When inserting a new note we ensure it visble
4554             ensureNoteVisible(note); //  But after loading it has certainly grown and if it was
4555     }                                //  on bottom of the basket it's not visible entirly anymore
4556 }
4557 
noteForFullPath(const QString & path)4558 Note* BasketScene::noteForFullPath(const QString &path)
4559 {
4560     Note *note = firstNote();
4561     Note *found;
4562     while (note) {
4563         found = note->noteForFullPath(path);
4564         if (found)
4565             return found;
4566         note = note->next();
4567     }
4568     return 0;
4569 }
4570 
deleteFiles()4571 void BasketScene::deleteFiles()
4572 {
4573     m_watcher->stopScan();
4574     Tools::deleteRecursively(fullPath());
4575 }
4576 
usedStates()4577 QList<State*> BasketScene::usedStates()
4578 {
4579     QList<State*> states;
4580     FOR_EACH_NOTE(note)
4581     note->usedStates(states);
4582     return states;
4583 }
4584 
saveGradientBackground(const QColor & color,const QFont & font,const QString & folder)4585 QString BasketScene::saveGradientBackground(const QColor &color, const QFont &font, const QString &folder)
4586 {
4587     // Construct file name and return if the file already exists:
4588     QString fileName = "note_background_" + color.name().toLower().mid(1) + ".png";
4589     QString fullPath = folder + fileName;
4590     if (QFile::exists(fullPath))
4591         return fileName;
4592 
4593     // Get the gradient top and bottom colors:
4594     QColor topBgColor;
4595     QColor bottomBgColor;
4596     Note::getGradientColors(color, &topBgColor, &bottomBgColor);
4597 
4598     // Draw and save the gradient image:
4599     int sampleTextHeight = QFontMetrics(font)
4600                            .boundingRect(0, 0, /*width=*/10000, /*height=*/0, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, "Test text")
4601                            .height();
4602     QPixmap noteGradient(100, sampleTextHeight + Note::NOTE_MARGIN);
4603     QPainter painter(&noteGradient);
4604     drawGradient(&painter, topBgColor, bottomBgColor, 0, 0, noteGradient.width(), noteGradient.height(), /*sunken=*/false, /*horz=*/true, /*flat=*/false);
4605     painter.end();
4606     noteGradient.save(fullPath, "PNG");
4607 
4608     // Return the name of the created file:
4609     return fileName;
4610 }
4611 
listUsedTags(QList<Tag * > & list)4612 void BasketScene::listUsedTags(QList<Tag*> &list)
4613 {
4614     if (!isLoaded()) {
4615         load();
4616     }
4617 
4618     FOR_EACH_NOTE(child)
4619     child->listUsedTags(list);
4620 }
4621 
4622 
4623 /** Unfocus the previously focused note (unless it was null)
4624   * and focus the new @param note (unless it is null) if hasFocus()
4625   * Update m_focusedNote to the new one
4626   */
setFocusedNote(Note * note)4627 void BasketScene::setFocusedNote(Note *note) // void BasketScene::changeFocusTo(Note *note)
4628 {
4629     // Don't focus an hidden note:
4630     if (note != 0L && !note->isShown())
4631 	return;
4632     // When clicking a group, this group gets focused. But only content-based notes should be focused:
4633     if (note && note->isGroup())
4634         note = note->firstRealChild();
4635     // The first time a note is focused, it becomes the start of the Shift selection:
4636     if (m_startOfShiftSelectionNote == 0)
4637         m_startOfShiftSelectionNote = note;
4638     // Unfocus the old focused note:
4639     if (m_focusedNote != 0L)
4640         m_focusedNote->setFocused(false);
4641     // Notify the new one to draw a focus rectangle... only if the basket is focused:
4642     if (hasFocus() && note != 0L)
4643         note->setFocused(true);
4644     // Save the new focused note:
4645     m_focusedNote = note;
4646 }
4647 
4648 /** If no shown note is currently focused, try to find a shown note and focus it
4649   * Also update m_focusedNote to the new one (or null if there isn't)
4650   */
focusANote()4651 void BasketScene::focusANote()
4652 {
4653     if (countFounds() == 0) { // No note to focus
4654         setFocusedNote(0L);
4655 //      m_startOfShiftSelectionNote = 0;
4656         return;
4657     }
4658 
4659     if (m_focusedNote == 0L) { // No focused note yet : focus the first shown
4660         Note *toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack());
4661         setFocusedNote(toFocus);
4662 //      m_startOfShiftSelectionNote = m_focusedNote;
4663         return;
4664     }
4665 
4666     // Search a visible note to focus if the focused one isn't shown :
4667     Note *toFocus = m_focusedNote;
4668     if (toFocus && !toFocus->isShown())
4669         toFocus = toFocus->nextShownInStack();
4670     if (!toFocus && m_focusedNote)
4671         toFocus = m_focusedNote->prevShownInStack();
4672     setFocusedNote(toFocus);
4673 //  m_startOfShiftSelectionNote = toFocus;
4674 }
4675 
firstNoteInStack()4676 Note* BasketScene::firstNoteInStack()
4677 {
4678     if (!firstNote())
4679         return 0;
4680 
4681     if (firstNote()->content())
4682         return firstNote();
4683     else
4684         return firstNote()->nextInStack();
4685 }
4686 
lastNoteInStack()4687 Note* BasketScene::lastNoteInStack()
4688 {
4689     Note *note = lastNote();
4690     while (note) {
4691         if (note->content())
4692             return note;
4693         Note *possibleNote = note->lastRealChild();
4694         if (possibleNote && possibleNote->content())
4695             return possibleNote;
4696         note = note->prev();
4697     }
4698     return 0;
4699 }
4700 
firstNoteShownInStack()4701 Note* BasketScene::firstNoteShownInStack()
4702 {
4703     Note *first = firstNoteInStack();
4704     while (first && !first->isShown())
4705         first = first->nextInStack();
4706     return first;
4707 }
4708 
lastNoteShownInStack()4709 Note* BasketScene::lastNoteShownInStack()
4710 {
4711     Note *last = lastNoteInStack();
4712     while (last && !last->isShown())
4713         last = last->prevInStack();
4714     return last;
4715 }
4716 
noteOn(NoteOn side)4717 Note* BasketScene::noteOn(NoteOn side)
4718 {
4719     Note *bestNote = 0;
4720     int   distance = -1;
4721 //    int   bestDistance = contentsWidth() * contentsHeight() * 10;
4722    int   bestDistance = sceneRect().width() * sceneRect().height() * 10;
4723 
4724     Note *note    = firstNoteShownInStack();
4725     Note *primary = m_focusedNote->parentPrimaryNote();
4726     while (note) {
4727         switch (side) {
4728         case LEFT_SIDE:   distance = m_focusedNote->distanceOnLeftRight(note, LEFT_SIDE);   break;
4729         case RIGHT_SIDE:  distance = m_focusedNote->distanceOnLeftRight(note, RIGHT_SIDE);  break;
4730         case TOP_SIDE:    distance = m_focusedNote->distanceOnTopBottom(note, TOP_SIDE);    break;
4731         case BOTTOM_SIDE: distance = m_focusedNote->distanceOnTopBottom(note, BOTTOM_SIDE); break;
4732         }
4733         if ((side == TOP_SIDE || side == BOTTOM_SIDE || primary != note->parentPrimaryNote()) && note != m_focusedNote && distance > 0 && distance < bestDistance) {
4734             bestNote     = note;
4735             bestDistance = distance;
4736         }
4737         note = note ->nextShownInStack();
4738     }
4739 
4740     return bestNote;
4741 }
4742 
firstNoteInGroup()4743 Note* BasketScene::firstNoteInGroup()
4744 {
4745     Note *child  = m_focusedNote;
4746     Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0);
4747     while (parent) {
4748         if (parent->firstChild() != child && !parent->isColumn())
4749             return parent->firstRealChild();
4750         child  = parent;
4751         parent = parent->parentNote();
4752     }
4753     return 0;
4754 }
4755 
noteOnHome()4756 Note* BasketScene::noteOnHome()
4757 {
4758     // First try to find the first note of the group containing the focused note:
4759     Note *child  = m_focusedNote;
4760     Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0);
4761     while (parent) {
4762         if (parent->nextShownInStack() != m_focusedNote)
4763             return parent->nextShownInStack();
4764         child  = parent;
4765         parent = parent->parentNote();
4766     }
4767 
4768     // If it was not found, then focus the very first note in the basket:
4769     if (isFreeLayout()) {
4770         Note *first = firstNoteShownInStack(); // The effective first note found
4771         Note *note  = first; // The current note, to conpare with the previous first note, if this new note is more on top
4772         if (note)
4773             note = note->nextShownInStack();
4774         while (note) {
4775             if (note->y() < first->y() || (note->y() == first->y() && note->x() < first->x()))
4776                 first = note;
4777             note = note->nextShownInStack();
4778         }
4779         return first;
4780     } else
4781         return firstNoteShownInStack();
4782 }
4783 
noteOnEnd()4784 Note* BasketScene::noteOnEnd()
4785 {
4786     Note *child     = m_focusedNote;
4787     Note *parent    = (m_focusedNote ? m_focusedNote->parentNote() : 0);
4788     Note *lastChild;
4789     while (parent) {
4790         lastChild = parent->lastRealChild();
4791         if (lastChild && lastChild != m_focusedNote) {
4792             if (lastChild->isShown())
4793                 return lastChild;
4794             lastChild = lastChild->prevShownInStack();
4795             if (lastChild && lastChild->isShown() && lastChild != m_focusedNote)
4796                 return lastChild;
4797         }
4798         child  = parent;
4799         parent = parent->parentNote();
4800     }
4801     if (isFreeLayout()) {
4802         Note *last;
4803         Note *note;
4804         last = note = firstNoteShownInStack();
4805         note = note->nextShownInStack();
4806         while (note) {
4807             if (note->bottom() > last->bottom() || (note->bottom() == last->bottom() && note->x() > last->x()))
4808                 last = note;
4809             note = note->nextShownInStack();
4810         }
4811         return last;
4812     } else
4813         return lastNoteShownInStack();
4814 }
4815 
4816 
keyPressEvent(QKeyEvent * event)4817 void BasketScene::keyPressEvent(QKeyEvent *event)
4818 {
4819     if(isDuringEdit())
4820     {
4821       QGraphicsScene::keyPressEvent(event);
4822       /*if( event->key() == Qt::Key_Return )
4823       {
4824 	m_editor->graphicsWidget()->setFocus();
4825       }
4826       else if( event->key() == Qt::Key_Escape)
4827       {
4828 	closeEditor();
4829       }*/
4830       event->accept();
4831       return;
4832     }
4833 
4834     if (event->key() == Qt::Key_Escape)
4835     {
4836         if (decoration()->filterData().isFiltering)
4837             decoration()->filterBar()->reset();
4838         else
4839             unselectAll();
4840     }
4841 
4842     if (countFounds() == 0)
4843         return;
4844 
4845     if (!m_focusedNote)
4846         return;
4847 
4848     Note *toFocus = 0L;
4849 
4850     switch (event->key()) {
4851     case Qt::Key_Down:
4852         toFocus = (isFreeLayout() ? noteOn(BOTTOM_SIDE) : m_focusedNote->nextShownInStack());
4853         if (toFocus)
4854             break;
4855 //        scrollBy(0, 30); // This cases do not move focus to another note...
4856         return;
4857     case Qt::Key_Up:
4858         toFocus = (isFreeLayout() ? noteOn(TOP_SIDE) : m_focusedNote->prevShownInStack());
4859         if (toFocus)
4860             break;
4861 //	scrollBy(0, -30); // This cases do not move focus to another note...
4862         return;
4863     case Qt::Key_PageDown:
4864         if (isFreeLayout()) {
4865             Note *lastFocused = m_focusedNote;
4866             for (int i = 0; i < 10 && m_focusedNote; ++i)
4867                 m_focusedNote = noteOn(BOTTOM_SIDE);
4868             toFocus = m_focusedNote;
4869             m_focusedNote = lastFocused;
4870         } else {
4871             toFocus = m_focusedNote;
4872             for (int i = 0; i < 10 && toFocus; ++i)
4873                 toFocus = toFocus->nextShownInStack();
4874         }
4875         if (toFocus == 0L)
4876             toFocus = (isFreeLayout() ? noteOnEnd() : lastNoteShownInStack());
4877         if (toFocus && toFocus != m_focusedNote)
4878             break;
4879 //        scrollBy(0, visibleHeight() / 2); // This cases do not move focus to another note...
4880 //        scrollBy(0, viewport()->height() / 2); // This cases do not move focus to another note...
4881         return;
4882     case Qt::Key_PageUp:
4883         if (isFreeLayout()) {
4884             Note *lastFocused = m_focusedNote;
4885             for (int i = 0; i < 10 && m_focusedNote; ++i)
4886                 m_focusedNote = noteOn(TOP_SIDE);
4887             toFocus = m_focusedNote;
4888             m_focusedNote = lastFocused;
4889         } else {
4890             toFocus = m_focusedNote;
4891             for (int i = 0; i < 10 && toFocus; ++i)
4892                 toFocus = toFocus->prevShownInStack();
4893         }
4894         if (toFocus == 0L)
4895             toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack());
4896         if (toFocus && toFocus != m_focusedNote)
4897             break;
4898 //        scrollBy(0, - visibleHeight() / 2); // This cases do not move focus to another note...
4899 //	  scrollBy(0, - viewport()->height() / 2); // This cases do not move focus to another note...
4900         return;
4901     case Qt::Key_Home:
4902         toFocus = noteOnHome();
4903         break;
4904     case Qt::Key_End:
4905         toFocus = noteOnEnd();
4906         break;
4907     case Qt::Key_Left:
4908         if (m_focusedNote->tryFoldParent())
4909             return;
4910         if ((toFocus = noteOn(LEFT_SIDE)))
4911             break;
4912         if ((toFocus = firstNoteInGroup()))
4913             break;
4914 //        scrollBy(-30, 0); // This cases do not move focus to another note...
4915         return;
4916     case Qt::Key_Right:
4917         if (m_focusedNote->tryExpandParent())
4918             return;
4919         if ((toFocus = noteOn(RIGHT_SIDE)))
4920             break;
4921 //	scrollBy(30, 0); // This cases do not move focus to another note...
4922         return;
4923     case Qt::Key_Space:  // This case do not move focus to another note...
4924         if (m_focusedNote) {
4925             m_focusedNote->setSelected(! m_focusedNote->isSelected());
4926             event->accept();
4927         } else
4928             event->ignore();
4929         return;          // ... so we return after the process
4930     default:
4931         return;
4932     }
4933 
4934     if (toFocus == 0L) { // If no direction keys have been pressed OR reached out the begin or end
4935         event->ignore(); // Important !!
4936         return;
4937     }
4938 
4939     if (event->modifiers() & Qt::ShiftModifier) { // Shift+arrowKeys selection
4940         if (m_startOfShiftSelectionNote == 0L)
4941             m_startOfShiftSelectionNote = toFocus;
4942         ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part!
4943         selectRange(m_startOfShiftSelectionNote, toFocus);
4944         setFocusedNote(toFocus);
4945         event->accept();
4946         return;
4947     } else /*if (toFocus != m_focusedNote)*/ {  // Move focus to ANOTHER note...
4948         ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part!
4949         setFocusedNote(toFocus);
4950         m_startOfShiftSelectionNote = toFocus;
4951         if (!(event->modifiers() & Qt::ControlModifier))          // ... select only current note if Control
4952             unselectAllBut(m_focusedNote);
4953         event->accept();
4954         return;
4955     }
4956 
4957     event->ignore(); // Important !!
4958 }
4959 
4960 /** Select a range of notes and deselect the others.
4961   * The order between start and end has no importance (end could be before start)
4962   */
selectRange(Note * start,Note * end,bool unselectOthers)4963 void BasketScene::selectRange(Note *start, Note *end, bool unselectOthers /*= true*/)
4964 {
4965     Note *cur;
4966     Note *realEnd = 0L;
4967 
4968     // Avoid crash when start (or end) is null
4969     if (start == 0L)
4970         start = end;
4971     else if (end == 0L)
4972         end = start;
4973     // And if *both* are null
4974     if (start == 0L) {
4975         if (unselectOthers)
4976             unselectAll();
4977         return;
4978     }
4979     // In case there is only one note to select
4980     if (start == end) {
4981         if (unselectOthers)
4982             unselectAllBut(start);
4983         else
4984             start->setSelected(true);
4985         return;
4986     }
4987 
4988     // Free layout baskets should select range as if we were drawing a rectangle between start and end:
4989     if (isFreeLayout()) {
4990         QRectF startRect(start->x(), start->y(), start->width(), start->height());
4991         QRectF endRect(end->x(),   end->y(),   end->width(),   end->height());
4992         QRectF toSelect = startRect.united(endRect);
4993         selectNotesIn(toSelect, /*invertSelection=*/false, unselectOthers);
4994         return;
4995     }
4996 
4997     // Search the REAL first (and deselect the others before it) :
4998     for (cur = firstNoteInStack(); cur != 0L; cur = cur->nextInStack()) {
4999         if (cur == start || cur == end)
5000             break;
5001         if (unselectOthers)
5002             cur->setSelected(false);
5003     }
5004 
5005     // Select the notes after REAL start, until REAL end :
5006     if (cur == start)
5007         realEnd = end;
5008     else if (cur == end)
5009         realEnd = start;
5010 
5011     for (/*cur = cur*/; cur != 0L; cur = cur->nextInStack()) {
5012         cur->setSelected(cur->isShown()); // Select all notes in the range, but only if they are shown
5013         if (cur == realEnd)
5014             break;
5015     }
5016 
5017     if (!unselectOthers)
5018         return;
5019 
5020     // Deselect the remaining notes :
5021     if (cur)
5022         cur = cur->nextInStack();
5023     for (/*cur = cur*/; cur != 0L; cur = cur->nextInStack())
5024         cur->setSelected(false);
5025 }
5026 
focusInEvent(QFocusEvent * event)5027 void BasketScene::focusInEvent(QFocusEvent *event)
5028 {
5029     // Focus cannot be get with Tab when locked, but a click can focus the basket!
5030     if (isLocked()) {
5031         if (m_button)
5032 	{
5033 	    QGraphicsScene::focusInEvent(event);
5034             QTimer::singleShot(0, m_button, SLOT(setFocus()));
5035 	}
5036     } else {
5037 	QGraphicsScene::focusInEvent(event);
5038         focusANote();      // hasFocus() is true at this stage, note will be focused
5039     }
5040 }
5041 
focusOutEvent(QFocusEvent *)5042 void BasketScene::focusOutEvent(QFocusEvent*)
5043 {
5044     if (m_focusedNote != 0L)
5045         m_focusedNote->setFocused(false);
5046 }
5047 
ensureNoteVisible(Note * note)5048 void BasketScene::ensureNoteVisible(Note *note)
5049 {
5050     if (!note->isShown()) // Logical!
5051         return;
5052 
5053     if (note == editedNote()) // HACK: When filtering while editing big notes, etc... cause unwanted scrolls
5054         return;
5055 
5056 	m_view->ensureVisible(note);
5057 /*//    int bottom = note->y() + qMin(note->height(),                                             visibleHeight());
5058 //    int finalRight  = note->x() + qMin(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0),  visibleWidth());
5059     qreal bottom = note->y() + qMin(note->height(),                                             (qreal)m_view->viewport()->height());
5060     qreal finalRight  = note->x() + qMin(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0),  (qreal)m_view->viewport()->width());
5061     m_view->ensureVisible(finalRight,     bottom,    0, 0);
5062     m_view->ensureVisible(note->x(), note->y(), 0, 0);*/
5063 }
5064 
addWatchedFile(const QString & fullPath)5065 void BasketScene::addWatchedFile(const QString &fullPath)
5066 {
5067 //  DEBUG_WIN << "Watcher>Add Monitoring Of : <font color=blue>" + fullPath + "</font>";
5068     m_watcher->addFile(fullPath);
5069 }
5070 
removeWatchedFile(const QString & fullPath)5071 void BasketScene::removeWatchedFile(const QString &fullPath)
5072 {
5073 //  DEBUG_WIN << "Watcher>Remove Monitoring Of : <font color=blue>" + fullPath + "</font>";
5074     m_watcher->removeFile(fullPath);
5075 }
5076 
watchedFileModified(const QString & fullPath)5077 void BasketScene::watchedFileModified(const QString &fullPath)
5078 {
5079     if (!m_modifiedFiles.contains(fullPath))
5080         m_modifiedFiles.append(fullPath);
5081     // If a big file is saved by an application, notifications are send several times.
5082     // We wait they are not sent anymore to considere the file complete!
5083     m_watcherTimer.setSingleShot(true);
5084     m_watcherTimer.start(200);
5085     DEBUG_WIN << "Watcher>Modified : <font color=blue>" + fullPath + "</font>";
5086 }
5087 
watchedFileDeleted(const QString & fullPath)5088 void BasketScene::watchedFileDeleted(const QString &fullPath)
5089 {
5090     Note *note = noteForFullPath(fullPath);
5091     removeWatchedFile(fullPath);
5092     if (note) {
5093         NoteSelection *selection = selectedNotes();
5094         unselectAllBut(note);
5095         noteDeleteWithoutConfirmation();
5096         while (selection) {
5097             selection->note->setSelected(true);
5098             selection = selection->nextStacked();
5099         }
5100     }
5101     DEBUG_WIN << "Watcher>Removed : <font color=blue>" + fullPath + "</font>";
5102 }
5103 
updateModifiedNotes()5104 void BasketScene::updateModifiedNotes()
5105 {
5106     for (QList<QString>::iterator it = m_modifiedFiles.begin(); it != m_modifiedFiles.end(); ++it) {
5107         Note *note = noteForFullPath(*it);
5108         if (note)
5109             note->content()->loadFromFile(/*lazyLoad=*/false);
5110     }
5111     m_modifiedFiles.clear();
5112 }
5113 
setProtection(int type,QString key)5114 bool BasketScene::setProtection(int type, QString key)
5115 {
5116 #ifdef HAVE_LIBGPGME
5117     if (type == PasswordEncryption || // Ask a new password
5118             m_encryptionType != type || m_encryptionKey != key) {
5119         int savedType = m_encryptionType;
5120         QString savedKey = m_encryptionKey;
5121 
5122         m_encryptionType = type;
5123         m_encryptionKey = key;
5124         m_gpg->clearCache();
5125 
5126         if (saveAgain()) {
5127             emit propertiesChanged(this);
5128         } else {
5129             m_encryptionType = savedType;
5130             m_encryptionKey = savedKey;
5131             m_gpg->clearCache();
5132             return false;
5133         }
5134     }
5135     return true;
5136 #else
5137     m_encryptionType = type;
5138     m_encryptionKey = key;
5139 
5140     return false;
5141 #endif
5142 }
5143 
saveAgain()5144 bool BasketScene::saveAgain()
5145 {
5146     bool result = false;
5147 
5148     m_watcher->stopScan();
5149     // Re-encrypt basket file:
5150     result = save();
5151     // Re-encrypt every note files recursively:
5152     if (result) {
5153         FOR_EACH_NOTE(note) {
5154             result = note->saveAgain();
5155             if (!result)
5156                 break;
5157         }
5158     }
5159     m_watcher->startScan();
5160     return result;
5161 }
5162 
loadFromFile(const QString & fullPath,QString * string)5163 bool BasketScene::loadFromFile(const QString &fullPath, QString *string)
5164 {
5165     QByteArray array;
5166 
5167     if (loadFromFile(fullPath, &array)) {
5168         *string = QString::fromUtf8(array.data(), array.size());
5169         return true;
5170     } else
5171         return false;
5172 }
5173 
isEncrypted()5174 bool BasketScene::isEncrypted()
5175 {
5176     return (m_encryptionType != NoEncryption);
5177 }
5178 
isFileEncrypted()5179 bool BasketScene::isFileEncrypted()
5180 {
5181     QFile file(fullPath() + ".basket");
5182 
5183     if (file.open(QIODevice::ReadOnly)) {
5184         // Should be ASCII anyways
5185         QString line = file.readLine(32);
5186         if (line.startsWith("-----BEGIN PGP MESSAGE-----"))
5187             return true;
5188     }
5189     return false;
5190 }
5191 
loadFromFile(const QString & fullPath,QByteArray * array)5192 bool BasketScene::loadFromFile(const QString &fullPath, QByteArray *array)
5193 {
5194     QFile file(fullPath);
5195     bool encrypted = false;
5196 
5197     if (file.open(QIODevice::ReadOnly)) {
5198         *array = file.readAll();
5199         QByteArray magic = "-----BEGIN PGP MESSAGE-----";
5200         int i = 0;
5201 
5202         if (array->size() > magic.size())
5203             for (i = 0; array->at(i) == magic[i]; ++i)
5204                 ;
5205         if (i == magic.size()) {
5206             encrypted = true;
5207         }
5208         file.close();
5209 #ifdef HAVE_LIBGPGME
5210         if (encrypted) {
5211             QByteArray tmp(*array);
5212 
5213             tmp.detach();
5214             // Only use gpg-agent for private key encryption since it doesn't
5215             // cache password used in symmetric encryption.
5216             m_gpg->setUseGnuPGAgent(Settings::useGnuPGAgent() && m_encryptionType == PrivateKeyEncryption);
5217             if (m_encryptionType == PrivateKeyEncryption)
5218                 m_gpg->setText(i18n("Please enter the password for the following private key:"), false);
5219             else
5220                 m_gpg->setText(i18n("Please enter the password for the basket <b>%1</b>:", basketName()), false); // Used when decrypting
5221             return m_gpg->decrypt(tmp, array);
5222         }
5223 #else
5224         if (encrypted) {
5225             return false;
5226         }
5227 #endif
5228         return true;
5229     } else
5230         return false;
5231 }
5232 
saveToFile(const QString & fullPath,const QString & string)5233 bool BasketScene::saveToFile(const QString& fullPath, const QString& string)
5234 {
5235     QByteArray array = string.toUtf8();
5236     return saveToFile(fullPath, array);
5237 }
5238 
saveToFile(const QString & fullPath,const QByteArray & array)5239 bool BasketScene::saveToFile(const QString &fullPath, const QByteArray &array)
5240 {
5241     ulong length = array.size();
5242 
5243     bool success = true;
5244     QByteArray tmp;
5245 
5246 #ifdef HAVE_LIBGPGME
5247     if (isEncrypted()) {
5248         QString key = QString::null;
5249 
5250         // We only use gpg-agent for private key encryption and saving without
5251         // public key doesn't need one.
5252         m_gpg->setUseGnuPGAgent(false);
5253         if (m_encryptionType == PrivateKeyEncryption) {
5254             key = m_encryptionKey;
5255             // public key doesn't need password
5256             m_gpg->setText("", false);
5257         } else
5258             m_gpg->setText(i18n("Please assign a password to the basket <b>%1</b>:", basketName()), true); // Used when defining a new password
5259 
5260         success = m_gpg->encrypt(array, length, &tmp, key);
5261         length = tmp.size();
5262     } else
5263         tmp = array;
5264 
5265 #else
5266     success = !isEncrypted();
5267     if (success)
5268         tmp = array;
5269 #endif
5270     /*if (success && (success = file.open(QIODevice::WriteOnly))){
5271         success = (file.write(tmp) == (Q_LONG)tmp.size());
5272         file.close();
5273     }*/
5274 
5275 
5276     if (success)
5277       return safelySaveToFile(fullPath, tmp, length);
5278     else
5279       return false;
5280 }
5281 
5282 /**
5283  * A safer version of saveToFile, that doesn't perform encryption.  To save a
5284  * file owned by a basket (i.e. a basket or a note file), use saveToFile(), but
5285  * to save to another file, (e.g. the basket hierarchy), use this function
5286  * instead.
5287  */
safelySaveToFile(const QString & fullPath,const QByteArray & array,unsigned long length)5288 /*static*/ bool BasketScene::safelySaveToFile(const QString& fullPath,
5289         const QByteArray& array,
5290         unsigned long length)
5291 {
5292     // Modulus operandi:
5293     // 1. Use QSaveFile to try and save the file
5294     // 2. Show a modal dialog (with the error) when bad things happen
5295     // 3. We keep trying (at increasing intervals, up until every minute)
5296     //    until we finally save the file.
5297 
5298     // The error dialog is static to make sure we never show the dialog twice,
5299     static DiskErrorDialog *dialog = 0;
5300     static const uint maxDelay = 60 * 1000; // ms
5301     uint retryDelay = 1000; // ms
5302     bool success = false;
5303     do {
5304         QSaveFile saveFile(fullPath);
5305         if (saveFile.open(QIODevice::WriteOnly)) {
5306             saveFile.write(array, length);
5307             if (saveFile.commit())
5308                 success = true;
5309         }
5310 
5311         if (!success) {
5312             if (!dialog) {
5313                 dialog = new DiskErrorDialog(i18n("Error while saving"),
5314                                              saveFile.errorString(),
5315                                              qApp->activeWindow());
5316             }
5317 
5318             if (!dialog->isVisible())
5319                 dialog->show();
5320 
5321             static const uint sleepDelay = 50; // ms
5322             for (uint i = 0; i < retryDelay / sleepDelay; ++i) {
5323                 qApp->processEvents();
5324             }
5325             // Double the retry delay, but don't go over the max.
5326             retryDelay = qMin(maxDelay, retryDelay * 2); // ms
5327         }
5328     } while (!success);
5329 
5330     if (dialog)
5331 	dialog->deleteLater();
5332     dialog = NULL;
5333 
5334     return true; // Guess we can't really return a fail
5335 }
5336 
safelySaveToFile(const QString & fullPath,const QString & string)5337 /*static*/ bool BasketScene::safelySaveToFile(const QString& fullPath, const QString& string)
5338 {
5339     QByteArray bytes = string.toUtf8();
5340     return safelySaveToFile(fullPath, bytes, bytes.length());
5341 }
5342 
lock()5343 void BasketScene::lock()
5344 {
5345 #ifdef HAVE_LIBGPGME
5346     closeEditor();
5347     m_gpg->clearCache();
5348     m_locked = true;
5349     enableActions();
5350     deleteNotes();
5351     m_loaded = false;
5352     m_loadingLaunched = false;
5353 #endif
5354 }
5355