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 ¬es, 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(¬eGradient);
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