Updated the README file with some contributor tips.
[basket4.git] / src / basket.cpp
blob25c0ae94d57597764a154115c30e6c72e8f6a88e
1 /***************************************************************************
2 * Copyright (C) 2003 by S�astien Laot *
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 ***************************************************************************/
21 #include <q3dragobject.h>
22 #include <qdom.h>
23 #include <qpainter.h>
24 #include <qstyle.h>
25 //Added by qt3to4:
26 #include <Q3CString>
27 #include <QWheelEvent>
28 #include <QContextMenuEvent>
29 #include <QFocusEvent>
30 #include <QPaintEvent>
31 #include <QDragMoveEvent>
32 #include <Q3VBoxLayout>
33 #include <QDragLeaveEvent>
34 #include <QKeyEvent>
35 #include <Q3Frame>
36 #include <QResizeEvent>
37 #include <QLabel>
38 #include <QDropEvent>
39 #include <Q3PopupMenu>
40 #include <QDragEnterEvent>
41 #include <Q3HBoxLayout>
42 #include <Q3ValueList>
43 #include <QMouseEvent>
44 #include <QCloseEvent>
45 #include <Q3GridLayout>
46 #include <kstyle.h>
47 #include <qtooltip.h>
48 #include <q3listview.h>
49 #include <qcursor.h>
50 #include <q3simplerichtext.h>
51 #include <qpushbutton.h>
52 #include <ktextedit.h>
53 #include <qpoint.h>
54 #include <qstringlist.h>
55 #include <kapplication.h>
56 #include <kglobalsettings.h>
57 #include <kopenwith.h>
58 #include <kservice.h>
59 #include <klocale.h>
60 #include <kglobalaccel.h>
61 #include <qdir.h>
62 #include <qfile.h>
63 #include <qfileinfo.h>
64 #include <kfiledialog.h>
65 #include <kaboutdata.h>
66 #include <klineedit.h>
67 #include <ksavefile.h>
68 #include <kdebug.h>
69 #include <q3vbox.h>
71 #include <unistd.h> // For sleep()
73 #include <kmenu.h>
74 #include <kiconloader.h>
75 #include <krun.h>
77 #include <q3toolbar.h>
78 #include <qclipboard.h>
80 #include <kmessagebox.h>
81 #include <qinputdialog.h>
83 #include <qlayout.h>
85 #include <stdlib.h> // rand() function
86 #include <qdatetime.h> // seed for rand()
88 #include "basket.h"
89 #include "note.h"
90 #include "notedrag.h"
91 #include "notefactory.h"
92 #include "noteedit.h"
93 #include "tagsedit.h"
94 #include "xmlwork.h"
95 #include "global.h"
96 #include "backgroundmanager.h"
97 #include "settings.h"
98 #include "tools.h"
99 #include "debugwindow.h"
100 #include "exporterdialog.h"
101 #include "config.h"
102 #include "popupmenu.h"
103 #ifdef HAVE_LIBGPGME
104 #include "kgpgme.h"
105 #endif
107 #include <iostream>
109 /** Class NoteSelection: */
111 NoteSelection* NoteSelection::nextStacked()
113 // First, search in the childs:
114 if (firstChild)
115 if (firstChild->note && firstChild->note->content())
116 return firstChild;
117 else
118 return firstChild->nextStacked();
120 // Then, in the next:
121 if (next)
122 if (next->note && next->note->content())
123 return next;
124 else
125 return next->nextStacked();
127 // And finally, in the parent:
128 NoteSelection *node = parent;
129 while (node)
130 if (node->next)
131 if (node->next->note && node->next->note->content())
132 return node->next;
133 else
134 return node->next->nextStacked();
135 else
136 node = node->parent;
138 // Not found:
139 return 0;
142 NoteSelection* NoteSelection::firstStacked()
144 if (!this)
145 return 0;
147 if (note && note->content())
148 return this;
149 else
150 return nextStacked();
153 void NoteSelection::append(NoteSelection *node)
155 if (!this || !node)
156 return;
158 if (firstChild) {
159 NoteSelection *last = firstChild;
160 while (last->next)
161 last = last->next;
162 last->next = node;
163 } else
164 firstChild = node;
166 while (node) {
167 node->parent = this;
168 node = node->next;
172 int NoteSelection::count()
174 if (!this)
175 return 0;
177 int count = 0;
179 for (NoteSelection *node = this; node; node = node->next)
180 if (node->note && node->note->content())
181 ++count;
182 else
183 count += node->firstChild->count();
185 return count;
188 Q3ValueList<Note*> NoteSelection::parentGroups()
190 Q3ValueList<Note*> groups;
192 // For each note:
193 for (NoteSelection *node = firstStacked(); node; node = node->nextStacked())
194 // For each parent groups of the note:
195 for (Note *note = node->note->parentNote(); note; note = note->parentNote())
196 // Add it (if it was not already in the list):
197 if (!note->isColumn() && !groups.contains(note))
198 groups.append(note);
200 return groups;
203 /** Class DecoratedBasket: */
205 DecoratedBasket::DecoratedBasket(QWidget *parent, const QString &folderName, const char *name, Qt::WFlags fl)
206 : QWidget(parent, name, fl)
208 m_layout = new Q3VBoxLayout(this);
209 m_filter = new FilterBar(this);
210 m_basket = new Basket(this, folderName);
211 m_layout->addWidget(m_basket);
212 setFilterBarPosition(Settings::filterOnTop());
214 m_filter->setShown(true);
215 m_basket->setFocus(); // To avoid the filter bar have focus on load
217 connect( m_filter, SIGNAL(newFilter(const FilterData&)), m_basket, SLOT(newFilter(const FilterData&)) );
218 connect( m_filter, SIGNAL(escapePressed()), m_basket, SLOT(cancelFilter()) );
219 connect( m_filter, SIGNAL(returnPressed()), m_basket, SLOT(validateFilter()) );
221 connect( m_basket, SIGNAL(postMessage(const QString&)), Global::bnpView, SLOT(postStatusbarMessage(const QString&)) );
222 connect( m_basket, SIGNAL(setStatusBarText(const QString&)), Global::bnpView, SLOT(setStatusBarHint(const QString&)) );
223 connect( m_basket, SIGNAL(resetStatusBarText()), Global::bnpView, SLOT(updateStatusBarHint()) );
226 DecoratedBasket::~DecoratedBasket()
230 void DecoratedBasket::setFilterBarPosition(bool onTop)
232 m_layout->remove(m_filter);
233 if (onTop) {
234 m_layout->insertWidget(0, m_filter);
235 setTabOrder(this/*(QWidget*)parent()*/, m_filter);
236 setTabOrder(m_filter, m_basket);
237 setTabOrder(m_basket, (QWidget*)parent());
238 } else {
239 m_layout->addWidget(m_filter);
240 setTabOrder(this/*(QWidget*)parent()*/, m_basket);
241 setTabOrder(m_basket, m_filter);
242 setTabOrder(m_filter, (QWidget*)parent());
246 void DecoratedBasket::setFilterBarShown(bool show, bool switchFocus)
248 // m_basket->setShowFilterBar(true);//show);
249 // m_basket->save();
250 // In this order (m_basket and then m_filter) because setShown(false)
251 // will call resetFilter() that will update actions, and then check the
252 // Ctrl+F action whereas it should be unchecked
253 // FIXME: It's very uggly all those things
254 m_filter->setShown(true);//show);
255 if (show) {
256 if (switchFocus)
257 m_filter->setEditFocus();
258 } else if (m_filter->hasEditFocus())
259 m_basket->setFocus();
262 void DecoratedBasket::resetFilter()
264 m_filter->reset();
267 /** Class TransparentWidget */
269 TransparentWidget::TransparentWidget(Basket *basket)
270 : QWidget(basket->viewport(), "", Qt::WNoAutoErase), m_basket(basket)
272 setFocusPolicy(Qt::NoFocus);
273 setWFlags(Qt::WNoAutoErase);
274 setMouseTracking(true); // To receive mouseMoveEvents
276 basket->viewport()->installEventFilter(this);
279 /*void TransparentWidget::reparent(QWidget *parent, Qt::WFlags f, const QPoint &p, bool showIt)
281 QWidget::reparent(parent, Qt::WNoAutoErase, p, showIt);
284 void TransparentWidget::setPosition(int x, int y)
286 m_x = x;
287 m_y = y;
290 void TransparentWidget::paintEvent(QPaintEvent*event)
292 QWidget::paintEvent(event);
293 QPainter painter(this);
295 // painter.save();
297 painter.translate(-m_x, -m_y);
298 m_basket->drawContents(&painter, m_x, m_y, width(), height());
300 // painter.restore();
301 // painter.setPen(Qt::blue);
302 // painter.drawRect(0, 0, width(), height());
305 void TransparentWidget::mouseMoveEvent(QMouseEvent *event)
307 QMouseEvent *translated = new QMouseEvent(QEvent::MouseMove, event->pos() + QPoint(m_x, m_y), event->button(), event->state());
308 m_basket->contentsMouseMoveEvent(translated);
309 delete translated;
312 bool TransparentWidget::eventFilter(QObject */*object*/, QEvent *event)
314 // If the parent basket viewport has changed, we should change too:
315 if (event->type() == QEvent::Paint)
316 update();
318 return false; // Event not consumed, in every cases (because it's only a notification)!
321 /** Class Basket: */
323 const int Basket::FRAME_DELAY = 50/*1500*/; // Delay between two animation "frames" in milliseconds
326 * Convenient function (defined in note.cpp !):
328 void drawGradient( QPainter *p, const QColor &colorTop, const QColor & colorBottom,
329 int x, int y, int w, int h,
330 bool sunken, bool horz, bool flat );
333 * Defined in note.cpp:
335 extern void substractRectOnAreas(const QRect &rectToSubstract, Q3ValueList<QRect> &areas, bool andRemove = true);
337 void debugZone(int zone)
339 QString s;
340 switch (zone) {
341 case Note::Handle: s = "Handle"; break;
342 case Note::Group: s = "Group"; break;
343 case Note::TagsArrow: s = "TagsArrow"; break;
344 case Note::Custom0: s = "Custom0"; break;
345 case Note::GroupExpander: s = "GroupExpander"; break;
346 case Note::Content: s = "Content"; break;
347 case Note::Link: s = "Link"; break;
348 case Note::TopInsert: s = "TopInsert"; break;
349 case Note::TopGroup: s = "TopGroup"; break;
350 case Note::BottomInsert: s = "BottomInsert"; break;
351 case Note::BottomGroup: s = "BottomGroup"; break;
352 case Note::BottomColumn: s = "BottomColumn"; break;
353 case Note::None: s = "None"; break;
354 default:
355 if (zone == Note::Emblem0)
356 s = "Emblem0";
357 else
358 s = "Emblem0+" + QString::number(zone - Note::Emblem0);
359 break;
361 std::cout << s << std::endl;
364 #define FOR_EACH_NOTE(noteVar) \
365 for (Note *noteVar = firstNote(); noteVar; noteVar = noteVar->next())
367 void Basket::prependNoteIn(Note *note, Note *in)
369 if (!note)
370 // No note to prepend:
371 return;
373 if (in) {
374 // The normal case:
375 preparePlug(note);
377 Note *last = note->lastSibling();
379 for (Note *n = note; n; n = n->next())
380 n->setParentNote(in);
381 // note->setPrev(0L);
382 last->setNext(in->firstChild());
384 if (in->firstChild())
385 in->firstChild()->setPrev(last);
387 in->setFirstChild(note);
389 if (m_loaded)
390 signalCountsChanged();
391 } else
392 // Prepend it directly in the basket:
393 appendNoteBefore(note, firstNote());
396 void Basket::appendNoteIn(Note *note, Note *in)
398 if (!note)
399 // No note to append:
400 return;
402 if (in) {
403 // The normal case:
404 preparePlug(note);
406 // Note *last = note->lastSibling();
407 Note *lastChild = in->lastChild();
409 for (Note *n = note; n; n = n->next())
410 n->setParentNote(in);
411 note->setPrev(lastChild);
412 // last->setNext(0L);
414 if (!in->firstChild())
415 in->setFirstChild(note);
417 if (lastChild)
418 lastChild->setNext(note);
420 if (m_loaded)
421 signalCountsChanged();
422 } else
423 // Prepend it directly in the basket:
424 appendNoteAfter(note, lastNote());
427 void Basket::appendNoteAfter(Note *note, Note *after)
429 if (!note)
430 // No note to append:
431 return;
433 if (!after)
434 // By default, insert after the last note:
435 after = lastNote();
437 if (m_loaded && after && !after->isFree() && !after->isColumn())
438 for (Note *n = note; n; n = n->next())
439 n->inheritTagsOf(after);
441 // if (!alreadyInBasket)
442 preparePlug(note);
444 Note *last = note->lastSibling();
445 if (after) {
446 // The normal case:
447 for (Note *n = note; n; n = n->next())
448 n->setParentNote(after->parentNote());
449 note->setPrev(after);
450 last->setNext(after->next());
451 after->setNext(note);
452 if (last->next())
453 last->next()->setPrev(last);
454 } else {
455 // There is no note in the basket:
456 for (Note *n = note; n; n = n->next())
457 n->setParentNote(0);
458 m_firstNote = note;
459 // note->setPrev(0);
460 // last->setNext(0);
463 // if (!alreadyInBasket)
464 if (m_loaded)
465 signalCountsChanged();
468 void Basket::appendNoteBefore(Note *note, Note *before)
470 if (!note)
471 // No note to append:
472 return;
474 if (!before)
475 // By default, insert before the first note:
476 before = firstNote();
478 if (m_loaded && before && !before->isFree() && !before->isColumn())
479 for (Note *n = note; n; n = n->next())
480 n->inheritTagsOf(before);
482 preparePlug(note);
484 Note *last = note->lastSibling();
485 if (before) {
486 // The normal case:
487 for (Note *n = note; n; n = n->next())
488 n->setParentNote(before->parentNote());
489 note->setPrev(before->prev());
490 last->setNext(before);
491 before->setPrev(last);
492 if (note->prev())
493 note->prev()->setNext(note);
494 else {
495 if (note->parentNote())
496 note->parentNote()->setFirstChild(note);
497 else
498 m_firstNote = note;
500 } else {
501 // There is no note in the basket:
502 for (Note *n = note; n; n = n->next())
503 n->setParentNote(0);
504 m_firstNote = note;
505 // note->setPrev(0);
506 // last->setNext(0);
509 if (m_loaded)
510 signalCountsChanged();
513 DecoratedBasket* Basket::decoration()
515 return (DecoratedBasket*)parent();
518 void Basket::preparePlug(Note *note)
520 // Select only the new notes, compute the new notes count and the new number of found notes:
521 if (m_loaded)
522 unselectAll();
523 int count = 0;
524 int founds = 0;
525 Note *last = 0;
526 for (Note *n = note; n; n = n->next()) {
527 if (m_loaded)
528 n->setSelectedRecursivly(true); // Notes should have a parent basket (and they have, so that's OK).
529 count += n->count();
530 founds += n->newFilter(decoration()->filterData());
531 last = n;
533 m_count += count;
534 m_countFounds += founds;
536 // Focus the last inserted note:
537 if (m_loaded && last) {
538 setFocusedNote(last);
539 m_startOfShiftSelectionNote = (last->isGroup() ? last->lastRealChild() : last);
542 // If some notes don't match (are hidden), tell it to the user:
543 if (m_loaded && founds < count) {
544 if (count == 1) postMessage( i18n("The new note does not match the filter and is hidden.") );
545 else if (founds == count - 1) postMessage( i18n("A new note does not match the filter and is hidden.") );
546 else if (founds > 0) postMessage( i18n("Some new notes do not match the filter and are hidden.") );
547 else postMessage( i18n("The new notes do not match the filter and are hidden.") );
551 void Basket::unplugNote(Note *note)
553 // If there is nothing to do...
554 if (!note)
555 return;
557 // if (!willBeReplugged) {
558 note->setSelectedRecursivly(false); // To removeSelectedNote() and decrease the selectedsCount.
559 m_count -= note->count();
560 m_countFounds -= note->newFilter(decoration()->filterData());
561 signalCountsChanged();
562 // }
564 // If it was the first note, change the first note:
565 if (m_firstNote == note)
566 m_firstNote = note->next();
568 // Change previous and next notes:
569 if (note->prev())
570 note->prev()->setNext(note->next());
571 if (note->next())
572 note->next()->setPrev(note->prev());
574 if (note->parentNote()) {
575 // If it was the first note of a group, change the first note of the group:
576 if (note->parentNote()->firstChild() == note)
577 note->parentNote()->setFirstChild( note->next() );
579 if (!note->parentNote()->isColumn()) {
580 // Ungroup if still 0 note inside parent group:
581 if ( ! note->parentNote()->firstChild() )
582 unplugNote(note->parentNote()); // TODO delete
584 // Ungroup if still 1 note inside parent group:
585 else if ( ! note->parentNote()->firstChild()->next() )
586 ungroupNote(note->parentNote());
590 note->setParentNote(0);
591 note->setPrev(0);
592 note->setNext(0);
594 // 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.
597 void Basket::ungroupNote(Note *group)
599 Note *note = group->firstChild();
600 Note *lastGroupedNote = group;
601 Note *nextNote;
603 // Move all notes after the group (not before, to avoid to change m_firstNote or group->m_firstChild):
604 while (note) {
605 nextNote = note->next();
607 if (lastGroupedNote->next())
608 lastGroupedNote->next()->setPrev(note);
609 note->setNext(lastGroupedNote->next());
610 lastGroupedNote->setNext(note);
611 note->setParentNote(group->parentNote());
612 note->setPrev(lastGroupedNote);
614 note->setGroupWidth(group->groupWidth() - Note::GROUP_WIDTH);
615 lastGroupedNote = note;
616 note = nextNote;
619 // Unplug the group:
620 group->setFirstChild(0);
621 unplugNote(group); // TODO: delete
623 relayoutNotes(true);
626 void Basket::groupNoteBefore(Note *note, Note *with)
628 if (!note || !with)
629 // No note to group or nowhere to group it:
630 return;
632 // if (m_loaded && before && !with->isFree() && !with->isColumn())
633 for (Note *n = note; n; n = n->next())
634 n->inheritTagsOf(with);
636 preparePlug(note);
638 Note *last = note->lastSibling();
640 Note *group = new Note(this);
641 group->setPrev(with->prev());
642 group->setNext(with->next());
643 group->setX(with->x());
644 group->setY(with->y());
645 if (with->parentNote() && with->parentNote()->firstChild() == with)
646 with->parentNote()->setFirstChild(group);
647 else if (m_firstNote == with)
648 m_firstNote = group;
649 group->setParentNote(with->parentNote());
650 group->setFirstChild(note);
651 group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH);
653 if (with->prev())
654 with->prev()->setNext(group);
655 if (with->next())
656 with->next()->setPrev(group);
657 with->setParentNote(group);
658 with->setPrev(last);
659 with->setNext(0L);
661 for (Note *n = note; n; n = n->next())
662 n->setParentNote(group);
663 // note->setPrev(0L);
664 last->setNext(with);
666 if (m_loaded)
667 signalCountsChanged();
670 void Basket::groupNoteAfter(Note *note, Note *with)
672 if (!note || !with)
673 // No note to group or nowhere to group it:
674 return;
676 // if (m_loaded && before && !with->isFree() && !with->isColumn())
677 for (Note *n = note; n; n = n->next())
678 n->inheritTagsOf(with);
680 preparePlug(note);
682 // Note *last = note->lastSibling();
684 Note *group = new Note(this);
685 group->setPrev(with->prev());
686 group->setNext(with->next());
687 group->setX(with->x());
688 group->setY(with->y());
689 if (with->parentNote() && with->parentNote()->firstChild() == with)
690 with->parentNote()->setFirstChild(group);
691 else if (m_firstNote == with)
692 m_firstNote = group;
693 group->setParentNote(with->parentNote());
694 group->setFirstChild(with);
695 group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH);
697 if (with->prev())
698 with->prev()->setNext(group);
699 if (with->next())
700 with->next()->setPrev(group);
701 with->setParentNote(group);
702 with->setPrev(0L);
703 with->setNext(note);
705 for (Note *n = note; n; n = n->next())
706 n->setParentNote(group);
707 note->setPrev(with);
708 // last->setNext(0L);
710 if (m_loaded)
711 signalCountsChanged();
714 void Basket::loadNotes(const QDomElement &notes, Note *parent)
716 Note *note;
717 for (QDomNode n = notes.firstChild(); !n.isNull(); n = n.nextSibling()) {
718 QDomElement e = n.toElement();
719 if (e.isNull()) // Cannot handle that!
720 continue;
721 note = 0;
722 // Load a Group:
723 if (e.tagName() == "group") {
724 note = new Note(this); // 1. Create the group...
725 loadNotes(e, note); // 3. ... And populate it with child notes.
726 int noteCount = note->count();
727 if (noteCount > 0 || (parent == 0 && !isFreeLayout())) { // But don't remove columns!
728 appendNoteIn(note, parent); // 2. ... Insert it... FIXME: Initially, the if() the insrtion was the step 2. Was it on purpose?
729 // The notes in the group are counted two times (it's why appendNoteIn() was called before loadNotes):
730 m_count -= noteCount;// TODO: Recompute note count every time noteCount() is emitted!
731 m_countFounds -= noteCount;
734 // Load a Content-Based Note:
735 if (e.tagName() == "note" || e.tagName() == "item") { // Keep compatible with 0.6.0 Alpha 1
736 note = new Note(this); // Create the note...
737 NoteFactory__loadNode(XMLWork::getElement(e, "content"), e.attribute("type"), note, /*lazyLoad=*/m_finishLoadOnFirstShow); // ... Populate it with content...
738 if (e.attribute("type") == "text")
739 m_shouldConvertPlainTextNotes = true; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded!
740 appendNoteIn(note, parent); // ... And insert it.
741 // Load dates:
742 if (e.hasAttribute("added"))
743 note->setAddedDate( QDateTime::fromString(e.attribute("added"), Qt::ISODate));
744 if (e.hasAttribute("lastModification"))
745 note->setLastModificationDate(QDateTime::fromString(e.attribute("lastModification"), Qt::ISODate));
747 // If we successfully loaded a note:
748 if (note) {
749 // Free Note Properties:
750 if (note->isFree()) {
751 int x = e.attribute("x").toInt();
752 int y = e.attribute("y").toInt();
753 note->setX(x < 0 ? 0 : x);
754 note->setY(y < 0 ? 0 : y);
756 // Resizeable Note Properties:
757 if (note->hasResizer() || note->isColumn())
758 note->setGroupWidth(e.attribute("width", "200").toInt());
759 // Group Properties:
760 if (note->isGroup() && !note->isColumn() && XMLWork::trueOrFalse(e.attribute("folded", "false")))
761 note->toggleFolded(false);
762 // Tags:
763 if (note->content()) {
764 QString tagsString = XMLWork::getElementText(e, "tags", "");
765 QStringList tagsId = QStringList::split(";", tagsString);
766 for (QStringList::iterator it = tagsId.begin(); it != tagsId.end(); ++it) {
767 State *state = Tag::stateForId(*it);
768 if (state)
769 note->addState(state, /*orReplace=*/true);
773 // kapp->processEvents();
777 void Basket::saveNotes(QDomDocument &document, QDomElement &element, Note *parent)
779 Note *note = (parent ? parent->firstChild() : firstNote());
780 while (note) {
781 // Create Element:
782 QDomElement noteElement = document.createElement(note->isGroup() ? "group" : "note");
783 element.appendChild(noteElement);
784 // Free Note Properties:
785 if (note->isFree()) {
786 noteElement.setAttribute("x", note->finalX());
787 noteElement.setAttribute("y", note->finalY());
789 // Resizeable Note Properties:
790 if (note->hasResizer())
791 noteElement.setAttribute("width", note->groupWidth());
792 // Group Properties:
793 if (note->isGroup() && !note->isColumn())
794 noteElement.setAttribute("folded", XMLWork::trueOrFalse(note->isFolded()));
795 // Save Content:
796 if (note->content()) {
797 // Save Dates:
798 noteElement.setAttribute("added", note->addedDate().toString(Qt::ISODate) );
799 noteElement.setAttribute("lastModification", note->lastModificationDate().toString(Qt::ISODate));
800 // Save Content:
801 noteElement.setAttribute("type", note->content()->lowerTypeName());
802 QDomElement content = document.createElement("content");
803 noteElement.appendChild(content);
804 note->content()->saveToNode(document, content);
805 // Save Tags:
806 if (note->states().count() > 0) {
807 QString tags;
808 for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it)
809 tags += (tags.isEmpty() ? "" : ";") + (*it)->id();
810 XMLWork::addElement(document, noteElement, "tags", tags);
812 } else
813 // Save Child Notes:
814 saveNotes(document, noteElement, note);
815 // Go to the Next One:
816 note = note->next();
820 void Basket::loadProperties(const QDomElement &properties)
822 // Compute Default Values for When Loading the Properties:
823 QString defaultBackgroundColor = (backgroundColorSetting().isValid() ? backgroundColorSetting().name() : "");
824 QString defaultTextColor = (textColorSetting().isValid() ? textColorSetting().name() : "");
826 // Load the Properties:
827 QString icon = XMLWork::getElementText(properties, "icon", this->icon() );
828 QString name = XMLWork::getElementText(properties, "name", basketName() );
830 QDomElement appearance = XMLWork::getElement(properties, "appearance");
831 // In 0.6.0-Alpha versions, there was a typo error: "backround" instead of "background"
832 QString backgroundImage = appearance.attribute( "backgroundImage", appearance.attribute( "backroundImage", backgroundImageName() ) );
833 QString backgroundColorString = appearance.attribute( "backgroundColor", appearance.attribute( "backroundColor", defaultBackgroundColor ) );
834 QString textColorString = appearance.attribute( "textColor", defaultTextColor );
835 QColor backgroundColor = (backgroundColorString.isEmpty() ? QColor() : QColor(backgroundColorString));
836 QColor textColor = (textColorString.isEmpty() ? QColor() : QColor(textColorString) );
838 QDomElement disposition = XMLWork::getElement(properties, "disposition");
839 bool free = XMLWork::trueOrFalse( disposition.attribute( "free", XMLWork::trueOrFalse(isFreeLayout()) ) );
840 int columnCount = disposition.attribute( "columnCount", QString::number(this->columnsCount()) ).toInt();
841 bool mindMap = XMLWork::trueOrFalse( disposition.attribute( "mindMap", XMLWork::trueOrFalse(isMindMap()) ) );
843 QDomElement shortcut = XMLWork::getElement(properties, "shortcut");
844 QString actionStrings[] = { "show", "globalShow", "globalSwitch" };
845 KShortcut combination = KShortcut( shortcut.attribute( "combination", m_action->shortcut().toStringInternal() ) );
846 QString actionString = shortcut.attribute( "action" );
847 int action = shortcutAction();
848 if (actionString == actionStrings[0]) action = 0;
849 if (actionString == actionStrings[1]) action = 1;
850 if (actionString == actionStrings[2]) action = 2;
852 QDomElement protection = XMLWork::getElement(properties, "protection");
853 m_encryptionType = protection.attribute( "type" ).toInt();
854 m_encryptionKey = protection.attribute( "key" );
856 // Apply the Properties:
857 setDisposition((free ? (mindMap ? 2 : 1) : 0), columnCount);
858 setShortcut(combination, action);
859 setAppearance(icon, name, backgroundImage, backgroundColor, textColor); // Will emit propertiesChanged(this)
862 void Basket::saveProperties(QDomDocument &document, QDomElement &properties)
864 XMLWork::addElement( document, properties, "name", basketName() );
865 XMLWork::addElement( document, properties, "icon", icon() );
867 QDomElement appearance = document.createElement("appearance");
868 properties.appendChild(appearance);
869 appearance.setAttribute( "backgroundImage", backgroundImageName() );
870 appearance.setAttribute( "backgroundColor", backgroundColorSetting().isValid() ? backgroundColorSetting().name() : "" );
871 appearance.setAttribute( "textColor", textColorSetting().isValid() ? textColorSetting().name() : "" );
873 QDomElement disposition = document.createElement("disposition");
874 properties.appendChild(disposition);
875 disposition.setAttribute( "free", XMLWork::trueOrFalse(isFreeLayout()) );
876 disposition.setAttribute( "columnCount", QString::number(columnsCount()) );
877 disposition.setAttribute( "mindMap", XMLWork::trueOrFalse(isMindMap()) );
879 QDomElement shortcut = document.createElement("shortcut");
880 properties.appendChild(shortcut);
881 QString actionStrings[] = { "show", "globalShow", "globalSwitch" };
882 shortcut.setAttribute( "combination", m_action->shortcut().toStringInternal() );
883 shortcut.setAttribute( "action", actionStrings[shortcutAction()] );
885 QDomElement protection = document.createElement("protection");
886 properties.appendChild(protection);
887 protection.setAttribute( "type", m_encryptionType );
888 protection.setAttribute( "key", m_encryptionKey );
891 void Basket::subscribeBackgroundImages()
893 if (!m_backgroundImageName.isEmpty()) {
894 Global::backgroundManager->subscribe(m_backgroundImageName);
895 Global::backgroundManager->subscribe(m_backgroundImageName, this->backgroundColor());
896 Global::backgroundManager->subscribe(m_backgroundImageName, selectionRectInsideColor());
897 m_backgroundPixmap = Global::backgroundManager->pixmap(m_backgroundImageName);
898 m_opaqueBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, this->backgroundColor());
899 m_selectedBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, selectionRectInsideColor());
900 m_backgroundTiled = Global::backgroundManager->tiled(m_backgroundImageName);
904 void Basket::unsubscribeBackgroundImages()
906 if (hasBackgroundImage()) {
907 Global::backgroundManager->unsubscribe(m_backgroundImageName);
908 Global::backgroundManager->unsubscribe(m_backgroundImageName, this->backgroundColor());
909 Global::backgroundManager->unsubscribe(m_backgroundImageName, selectionRectInsideColor());
910 m_backgroundPixmap = 0;
911 m_opaqueBackgroundPixmap = 0;
912 m_selectedBackgroundPixmap = 0;
916 void Basket::setAppearance(const QString &icon, const QString &name, const QString &backgroundImage, const QColor &backgroundColor, const QColor &textColor)
918 unsubscribeBackgroundImages();
920 m_icon = icon;
921 m_basketName = name;
922 m_backgroundImageName = backgroundImage;
923 m_backgroundColorSetting = backgroundColor;
924 m_textColorSetting = textColor;
926 m_action->setText("BASKET SHORTCUT: " + name);
928 // Basket should ALWAYS have an icon (the "basket" icon by default):
929 QPixmap iconTest = kapp->iconLoader()->loadIcon(m_icon, KIconLoader::NoGroup, 16, KIconLoader::DefaultState, 0L, /*canReturnNull=*/true);
930 if (iconTest.isNull())
931 m_icon = "basket";
933 // We don't request the background images if it's not loaded yet (to make the application startup fast).
934 // When the basket is loading (because requested by the user: he/she want to access it)
935 // it load the properties, subscribe to (and then load) the images, update the "Loading..." message with the image,
936 // load all the notes and it's done!
937 if (m_loadingLaunched)
938 subscribeBackgroundImages();
940 recomputeAllStyles(); // If a note have a tag with the same background color as the basket one, then display a "..."
941 recomputeBlankRects(); // See the drawing of blank areas in Basket::drawContents()
942 unbufferizeAll();
943 updateContents();
945 if (isDuringEdit() && m_editor->widget()) {
946 m_editor->widget()->setPaletteBackgroundColor( m_editor->note()->backgroundColor() );
947 m_editor->widget()->setPaletteForegroundColor( m_editor->note()->textColor() );
950 emit propertiesChanged(this);
953 void Basket::setDisposition(int disposition, int columnCount)
955 static const int COLUMNS_LAYOUT = 0;
956 static const int FREE_LAYOUT = 1;
957 static const int MINDMAPS_LAYOUT = 2;
959 int currentDisposition = (isFreeLayout() ? (isMindMap() ? MINDMAPS_LAYOUT : FREE_LAYOUT) : COLUMNS_LAYOUT);
961 if (currentDisposition == COLUMNS_LAYOUT && disposition == COLUMNS_LAYOUT) {
962 if (firstNote() && columnCount > m_columnsCount) {
963 // Insert each new columns:
964 for (int i = m_columnsCount; i < columnCount; ++i) {
965 Note *newColumn = new Note(this);
966 insertNote(newColumn, /*clicked=*/lastNote(), /*zone=*/Note::BottomInsert, QPoint(), /*animateNewPosition=*/false);
968 } else if (firstNote() && columnCount < m_columnsCount) {
969 Note *column = firstNote();
970 Note *cuttedNotes = 0;
971 for (int i = 1; i <= m_columnsCount; ++i) {
972 Note *columnToRemove = column;
973 column = column->next();
974 if (i > columnCount) {
975 // Remove the columns that are too much:
976 unplugNote(columnToRemove);
977 // "Cut" the content in the columns to be deleted:
978 if (columnToRemove->firstChild()) {
979 for (Note *it = columnToRemove->firstChild(); it; it = it->next())
980 it->setParentNote(0);
981 if (!cuttedNotes)
982 cuttedNotes = columnToRemove->firstChild();
983 else {
984 Note *lastCuttedNote = cuttedNotes;
985 while (lastCuttedNote->next())
986 lastCuttedNote = lastCuttedNote->next();
987 lastCuttedNote->setNext(columnToRemove->firstChild());
988 columnToRemove->firstChild()->setPrev(lastCuttedNote);
990 columnToRemove->setFirstChild(0);
994 // Paste the content in the last column:
995 if (cuttedNotes)
996 insertNote(cuttedNotes, /*clicked=*/lastNote(), /*zone=*/Note::BottomColumn, QPoint(), /*animateNewPosition=*/true);
997 unselectAll();
999 if (columnCount != m_columnsCount) {
1000 m_columnsCount = (columnCount <= 0 ? 1 : columnCount);
1001 equalizeColumnSizes(); // Will relayoutNotes()
1003 } else if (currentDisposition == COLUMNS_LAYOUT && (disposition == FREE_LAYOUT || disposition == MINDMAPS_LAYOUT)) {
1004 Note *column = firstNote();
1005 m_columnsCount = 0; // Now, so relayoutNotes() will not relayout the free notes as if they were columns!
1006 while (column) {
1007 // Move all childs on the first level:
1008 Note *nextColumn = column->next();
1009 ungroupNote(column);
1010 column = nextColumn;
1012 unselectAll();
1013 m_mindMap = (disposition == MINDMAPS_LAYOUT);
1014 relayoutNotes(true);
1015 } else if ((currentDisposition == FREE_LAYOUT || currentDisposition == MINDMAPS_LAYOUT) && disposition == COLUMNS_LAYOUT) {
1016 if (firstNote()) {
1017 // TODO: Reorder notes!
1018 // Remove all notes (but keep a reference to them, we're not crazy ;-) ):
1019 Note *notes = m_firstNote;
1020 m_firstNote = 0;
1021 m_count = 0;
1022 m_countFounds = 0;
1023 // Insert the number of columns that is needed:
1024 Note *lastInsertedColumn = 0;
1025 for (int i = 0; i < columnCount; ++i) {
1026 Note *column = new Note(this);
1027 if (lastInsertedColumn)
1028 insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, QPoint(), /*animateNewPosition=*/false);
1029 else
1030 m_firstNote = column;
1031 lastInsertedColumn = column;
1033 // Reinsert the old notes in the first column:
1034 insertNote(notes, /*clicked=*/firstNote(), /*zone=*/Note::BottomColumn, QPoint(), /*animateNewPosition=*/true);
1035 unselectAll();
1036 } else {
1037 // Insert the number of columns that is needed:
1038 Note *lastInsertedColumn = 0;
1039 for (int i = 0; i < columnCount; ++i) {
1040 Note *column = new Note(this);
1041 if (lastInsertedColumn)
1042 insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, QPoint(), /*animateNewPosition=*/false);
1043 else
1044 m_firstNote = column;
1045 lastInsertedColumn = column;
1048 m_columnsCount = (columnCount <= 0 ? 1 : columnCount);
1049 equalizeColumnSizes(); // Will relayoutNotes()
1053 void Basket::equalizeColumnSizes()
1055 if (!firstNote())
1056 return;
1058 // Necessary to know the available space;
1059 relayoutNotes(true);
1061 int availableSpace = visibleWidth();
1062 int columnWidth = (visibleWidth() - (columnsCount()-1)*Note::GROUP_WIDTH) / columnsCount();
1063 int columnCount = columnsCount();
1064 Note *column = firstNote();
1065 while (column) {
1066 int minGroupWidth = column->minRight() - column->x();
1067 if (minGroupWidth > columnWidth) {
1068 availableSpace -= minGroupWidth;
1069 --columnCount;
1071 column = column->next();
1073 columnWidth = (availableSpace - (columnsCount()-1)*Note::GROUP_WIDTH) / columnCount;
1075 column = firstNote();
1076 while (column) {
1077 int minGroupWidth = column->minRight() - column->x();
1078 if (minGroupWidth > columnWidth)
1079 column->setGroupWidth(minGroupWidth);
1080 else
1081 column->setGroupWidth(columnWidth);
1082 column = column->next();
1085 relayoutNotes(true);
1088 void Basket::enableActions()
1090 Global::bnpView->enableActions();
1091 setFocusPolicy(isLocked() ? Qt::NoFocus : Qt::StrongFocus);
1092 if (isLocked())
1093 viewport()->setCursor(Qt::ArrowCursor); // When locking, the cursor stays the last form it was
1096 bool Basket::save()
1098 if (!m_loaded)
1099 return false;
1101 DEBUG_WIN << "Basket[" + folderName() + "]: Saving...";
1103 // Create Document:
1104 QDomDocument document(/*doctype=*/"basket");
1105 QDomElement root = document.createElement("basket");
1106 document.appendChild(root);
1108 // Create Properties Element and Populate It:
1109 QDomElement properties = document.createElement("properties");
1110 saveProperties(document, properties);
1111 root.appendChild(properties);
1113 // Create Notes Element and Populate It:
1114 QDomElement notes = document.createElement("notes");
1115 saveNotes(document, notes, 0);
1116 root.appendChild(notes);
1118 // Write to Disk:
1119 if(!saveToFile(fullPath() + ".basket", "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n" + document.toString()))
1121 DEBUG_WIN << "Basket[" + folderName() + "]: <font color=red>FAILED to save</font>!";
1122 return false;
1125 Global::bnpView->setUnsavedStatus(false);
1126 return true;
1129 void Basket::aboutToBeActivated()
1131 if (m_finishLoadOnFirstShow) {
1132 FOR_EACH_NOTE (note)
1133 note->finishLazyLoad();
1135 //relayoutNotes(/*animate=*/false);
1136 setFocusedNote(0); // So that during the focusInEvent that will come shortly, the FIRST note is focused.
1138 if (Settings::playAnimations() && !decoration()->filterBar()->filterData().isFiltering && Global::bnpView->currentBasket() == this) // No animation when filtering all!
1139 animateLoad();//QTimer::singleShot( 0, this, SLOT(animateLoad()) );
1141 m_finishLoadOnFirstShow = false;
1145 void Basket::reload()
1147 closeEditor();
1148 unbufferizeAll(); // Keep the memory footprint low
1150 m_firstNote = 0;
1152 m_loaded = false;
1153 m_loadingLaunched = false;
1155 updateContents();
1158 void Basket::load()
1160 // Load only once:
1161 if (m_loadingLaunched)
1162 return;
1163 m_loadingLaunched = true;
1165 // StopWatch::start(10);
1167 DEBUG_WIN << "Basket[" + folderName() + "]: Loading...";
1168 QDomDocument *doc = 0;
1169 QString content;
1171 // StopWatch::start(0);
1173 if (loadFromFile(fullPath() + ".basket", &content)) {
1174 doc = new QDomDocument("basket");
1175 if ( ! doc->setContent(content) ) {
1176 DEBUG_WIN << "Basket[" + folderName() + "]: <font color=red>FAILED to parse XML</font>!";
1177 delete doc;
1178 doc = 0;
1181 if(isEncrypted())
1182 DEBUG_WIN << "Basket is encrypted.";
1183 if ( ! doc) {
1184 DEBUG_WIN << "Basket[" + folderName() + "]: <font color=red>FAILED to load</font>!";
1185 m_loadingLaunched = false;
1186 if (isEncrypted())
1187 m_locked = true;
1188 Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar
1189 return;
1191 m_locked = false;
1193 QDomElement docElem = doc->documentElement();
1194 QDomElement properties = XMLWork::getElement(docElem, "properties");
1196 loadProperties(properties); // Since we are loading, this time the background image will also be loaded!
1197 // Now that the background image is loaded and subscribed, we display it during the load process:
1198 delete doc;
1199 updateContents();
1200 // kapp->processEvents();
1202 //BEGIN Compatibility with 0.6.0 Pre-Alpha versions:
1203 QDomElement notes = XMLWork::getElement(docElem, "notes");
1204 if (notes.isNull())
1205 notes = XMLWork::getElement(docElem, "items");
1206 m_watcher->stopScan();
1207 m_shouldConvertPlainTextNotes = false; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded!
1208 // StopWatch::check(0);
1210 // StopWatch::start(1);
1211 m_finishLoadOnFirstShow = (Global::bnpView->currentBasket() != this);
1212 loadNotes(notes, 0L);
1213 // StopWatch::check(1);
1214 // StopWatch::start(2);
1216 if (m_shouldConvertPlainTextNotes)
1217 convertTexts();
1218 m_watcher->startScan();
1219 //loadNotes(XMLWork::getElement(docElem, "notes"), 0L);
1220 //END
1222 // StopWatch::check(0);
1224 signalCountsChanged();
1225 if (isColumnsLayout()) {
1226 // Count the number of columns:
1227 int columnsCount = 0;
1228 Note *column = firstNote();
1229 while (column) {
1230 ++columnsCount;
1231 column = column->next();
1233 m_columnsCount = columnsCount;
1236 relayoutNotes(false);
1238 // On application start, the current basket is not focused yet, so the focus rectangle is not shown when calling focusANote():
1239 if (Global::bnpView->currentBasket() == this)
1240 setFocus();
1241 focusANote();
1243 if (Settings::playAnimations() && !decoration()->filterBar()->filterData().isFiltering && Global::bnpView->currentBasket() == this) // No animation when filtering all!
1244 animateLoad();//QTimer::singleShot( 0, this, SLOT(animateLoad()) );
1245 else
1246 m_loaded = true;
1247 enableActions();
1248 // StopWatch::check(2);
1250 // StopWatch::check(10);
1253 void Basket::filterAgain(bool andEnsureVisible/* = true*/)
1255 newFilter(decoration()->filterData(), andEnsureVisible);
1258 void Basket::filterAgainDelayed()
1260 QTimer::singleShot( 0, this, SLOT(filterAgain()) );
1263 void Basket::newFilter(const FilterData &data, bool andEnsureVisible/* = true*/)
1265 if (!isLoaded())
1266 return;
1268 //StopWatch::start(20);
1270 m_countFounds = 0;
1271 for (Note *note = firstNote(); note; note = note->next())
1272 m_countFounds += note->newFilter(data);
1274 relayoutNotes(true);
1275 signalCountsChanged();
1277 if (hasFocus()) // if (!hasFocus()), focusANote() will be called at focusInEvent()
1278 focusANote(); // so, we avoid de-focus a note if it will be re-shown soon
1279 if (andEnsureVisible && m_focusedNote != 0L)
1280 ensureNoteVisible(m_focusedNote);
1282 Global::bnpView->setFiltering(data.isFiltering);
1284 //StopWatch::check(20);
1287 void Basket::cancelFilter()
1289 decoration()->filterBar()->reset();
1290 validateFilter();
1293 void Basket::validateFilter()
1295 if (isDuringEdit())
1296 m_editor->widget()->setFocus();
1297 else
1298 setFocus();
1301 bool Basket::isFiltering()
1303 return decoration()->filterBar()->filterData().isFiltering;
1308 QString Basket::fullPath()
1310 return Global::basketsFolder() + folderName();
1313 QString Basket::fullPathForFileName(const QString &fileName)
1315 return fullPath() + fileName;
1318 /*static*/ QString Basket::fullPathForFolderName(const QString &folderName)
1320 return Global::basketsFolder() + folderName;
1324 void Basket::setShortcut(KShortcut shortcut, int action)
1326 if(!Global::globalAccel)
1327 return;
1328 QString sAction = "global_basket_activate_" + folderName();
1329 Global::globalAccel->remove(sAction);
1330 Global::globalAccel->updateConnections();
1332 m_action->setShortcut(shortcut);
1333 m_shortcutAction = action;
1335 if (action > 0)
1336 Global::globalAccel->insert(sAction, m_action->text(), /*whatsThis=*/"", m_action->shortcut(), KShortcut(), this, SLOT(activatedShortcut()), /*configurable=*/false);
1337 Global::globalAccel->updateConnections();
1340 void Basket::activatedShortcut()
1342 Global::bnpView->setCurrentBasket(this);
1344 if (m_shortcutAction == 1)
1345 Global::bnpView->setActive(true);
1348 void Basket::signalCountsChanged()
1350 if (!m_timerCountsChanged.isActive())
1351 m_timerCountsChanged.start(0/*ms*/, /*singleShot=*/true);
1354 void Basket::countsChangedTimeOut()
1356 emit countsChanged(this);
1360 Basket::Basket(QWidget *parent, const QString &folderName)
1361 : Q3ScrollView(parent),
1362 QToolTip(viewport()),
1363 m_noActionOnMouseRelease(false), m_ignoreCloseEditorOnNextMouseRelease(false), m_pressPos(-100, -100), m_canDrag(false),
1364 m_firstNote(0), m_columnsCount(1), m_mindMap(false), m_resizingNote(0L), m_pickedResizer(0), m_movingNote(0L), m_pickedHandle(0, 0),
1365 m_clickedToInsert(0), m_zoneToInsert(0), m_posToInsert(-1, -1),
1366 m_isInsertPopupMenu(false),
1367 m_loaded(false), m_loadingLaunched(false), m_locked(false), m_decryptBox(0), m_button(0), m_encryptionType(NoEncryption),
1368 #ifdef HAVE_LIBGPGME
1369 m_gpg(0),
1370 #endif
1371 m_backgroundPixmap(0), m_opaqueBackgroundPixmap(0), m_selectedBackgroundPixmap(0),
1372 m_action(0), m_shortcutAction(0),
1373 m_hoveredNote(0), m_hoveredZone(Note::None), m_lockedHovering(false), m_underMouse(false),
1374 m_inserterRect(), m_inserterShown(false), m_inserterSplit(true), m_inserterTop(false), m_inserterGroup(false),
1375 m_isSelecting(false), m_selectionStarted(false),
1376 m_count(0), m_countFounds(0), m_countSelecteds(0),
1377 m_folderName(folderName),
1378 m_editor(0), m_leftEditorBorder(0), m_rightEditorBorder(0), m_redirectEditActions(false), m_editorWidth(-1), m_editorHeight(-1),
1379 m_doNotCloseEditor(false), m_editParagraph(0), m_editIndex(0),
1380 m_isDuringDrag(false), m_draggedNotes(),
1381 m_focusedNote(0), m_startOfShiftSelectionNote(0),
1382 m_finishLoadOnFirstShow(false), m_relayoutOnNextShow(false)
1384 QString sAction = "local_basket_activate_" + folderName;
1385 m_action = new KAction("FAKE TEXT", "FAKE ICON", KShortcut(), this, SLOT(activatedShortcut()), Global::bnpView->actionCollection(), sAction);
1386 m_action->setShortcutConfigurable(false); // We do it in the basket properties dialog (and keep it in sync with the global one)
1388 if (!m_folderName.endsWith("/"))
1389 m_folderName += "/";
1391 setFocusPolicy(Qt::StrongFocus);
1392 setWFlags(Qt::WNoAutoErase);
1393 setDragAutoScroll(true);
1395 // By default, there is no corner widget: we set one for the corner area to be painted!
1396 // If we don't set one and there are two scrollbars present, slowly resizing up the window show graphical glitches in that area!
1397 m_cornerWidget = new QWidget(this);
1398 setCornerWidget(m_cornerWidget);
1400 viewport()->setAcceptDrops(true);
1401 viewport()->setMouseTracking(true);
1402 viewport()->setBackgroundMode(NoBackground); // Do not clear the widget before paintEvent() because we always draw every pixels (faster and flicker-free)
1404 // File Watcher:
1405 m_watcher = new KDirWatch(this);
1406 connect( m_watcher, SIGNAL(dirty(const QString&)), this, SLOT(watchedFileModified(const QString&)) );
1407 connect( m_watcher, SIGNAL(deleted(const QString&)), this, SLOT(watchedFileDeleted(const QString&)) );
1408 connect( &m_watcherTimer, SIGNAL(timeout()), this, SLOT(updateModifiedNotes()) );
1410 // Various Connections:
1411 connect( &m_animationTimer, SIGNAL(timeout()), this, SLOT(animateObjects()) );
1412 connect( &m_autoScrollSelectionTimer, SIGNAL(timeout()), this, SLOT(doAutoScrollSelection()) );
1413 connect( &m_timerCountsChanged, SIGNAL(timeout()), this, SLOT(countsChangedTimeOut()) );
1414 connect( &m_inactivityAutoSaveTimer, SIGNAL(timeout()), this, SLOT(inactivityAutoSaveTimeout()) );
1415 connect( &m_inactivityAutoLockTimer, SIGNAL(timeout()), this, SLOT(inactivityAutoLockTimeout()) );
1416 connect( this, SIGNAL(contentsMoving(int, int)), this, SLOT(contentsMoved()) );
1417 #ifdef HAVE_LIBGPGME
1418 m_gpg = new KGpgMe();
1419 #endif
1420 m_locked = isFileEncrypted();
1423 void Basket::contentsMoved()
1425 // This slot is called BEFORE the content move, so we delay the hover effects:
1426 QTimer::singleShot(0, this, SLOT(doHoverEffects()));
1429 void Basket::enterEvent(QEvent *)
1431 m_underMouse = true;
1432 doHoverEffects();
1435 void Basket::leaveEvent(QEvent *)
1437 m_underMouse = false;
1438 doHoverEffects();
1440 if (m_lockedHovering)
1441 return;
1443 removeInserter();
1444 if (m_hoveredNote) {
1445 m_hoveredNote->setHovered(false);
1446 m_hoveredNote->setHoveredZone(Note::None);
1447 updateNote(m_hoveredNote);
1449 m_hoveredNote = 0;
1452 void Basket::setFocusIfNotInPopupMenu()
1454 if (!kapp->activePopupWidget())
1455 if (isDuringEdit())
1456 m_editor->widget()->setFocus();
1457 else
1458 setFocus();
1461 void Basket::contentsMousePressEvent(QMouseEvent *event)
1463 // If user click the basket, focus it!
1464 // The focus is delayed because if the click results in showing a popup menu,
1465 // the interface flicker by showing the focused rectangle (as the basket gets focus)
1466 // and immediatly removing it (because the popup menu now have focus).
1467 if (!isDuringEdit())
1468 QTimer::singleShot(0, this, SLOT(setFocusIfNotInPopupMenu()));
1470 // Convenient variables:
1471 bool controlPressed = event->stateAfter() & Qt::ControlModifier;
1472 bool shiftPressed = event->stateAfter() & Qt::ShiftModifier;
1474 // Do nothing if we disabled the click some milliseconds sooner.
1475 // For instance when a popup menu has been closed with click, we should not do action:
1476 if (event->button() == Qt::LeftButton && (kapp->activePopupWidget() || m_lastDisableClick.msecsTo(QTime::currentTime()) <= 80)) {
1477 doHoverEffects();
1478 m_noActionOnMouseRelease = true;
1479 // But we allow to select:
1480 // The code is the same as at the bottom of this method:
1481 if (event->button() == Qt::LeftButton) {
1482 m_selectionStarted = true;
1483 m_selectionBeginPoint = event->pos();
1484 m_selectionInvert = controlPressed || shiftPressed;
1486 return;
1489 // Figure out what is the clicked note and zone:
1490 Note *clicked = noteAt(event->pos().x(), event->pos().y());
1491 Note::Zone zone = (clicked ? clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()) ) : Note::None);
1493 // Popup Tags menu:
1494 if (zone == Note::TagsArrow && !controlPressed && !shiftPressed && event->button() != Qt::MidButton) {
1495 if (!clicked->isSelected())
1496 unselectAllBut(clicked);
1497 setFocusedNote(clicked); /// /// ///
1498 m_startOfShiftSelectionNote = clicked;
1499 m_noActionOnMouseRelease = true;
1500 popupTagsMenu(clicked);
1501 return;
1504 if (event->button() == Qt::LeftButton) {
1505 // Prepare to allow drag and drop when moving mouse further:
1506 if ( (zone == Note::Handle || zone == Note::Group) ||
1507 (clicked && clicked->isSelected() &&
1508 (zone == Note::TagsArrow || zone == Note::Custom0 || zone == Note::Content || zone == Note::Link /**/ || zone >= Note::Emblem0 /**/)) ) {
1509 if (!shiftPressed && !controlPressed) {
1510 m_pressPos = event->pos(); // TODO: Allow to drag emblems to assign them to other notes. Then don't allow drag at Emblem0!!
1511 m_canDrag = true;
1512 // Saving where we were editing, because during a drag, the mouse can fly over the text edit and move the cursor position:
1513 if (m_editor && m_editor->textEdit()) {
1514 Q3TextEdit *editor = m_editor->textEdit();
1515 editor->getCursorPosition(&m_editParagraph, &m_editIndex);
1520 // Initializing Resizer move:
1521 if (zone == Note::Resizer) {
1522 m_resizingNote = clicked;
1523 m_pickedResizer = event->pos().x() - clicked->rightLimit();
1524 m_noActionOnMouseRelease = true;
1525 m_lockedHovering = true;
1526 return;
1529 // Select note(s):
1530 if (zone == Note::Handle || zone == Note::Group || (zone == Note::GroupExpander && (controlPressed || shiftPressed))) {
1531 Note *end = clicked;
1532 if (clicked->isGroup() && shiftPressed) {
1533 if (clicked->contains(m_startOfShiftSelectionNote)) {
1534 m_startOfShiftSelectionNote = clicked->firstRealChild();
1535 end = clicked->lastRealChild();
1536 } else if (clicked->firstRealChild()->isAfter(m_startOfShiftSelectionNote))
1537 end = clicked->lastRealChild();
1538 else
1539 end = clicked->firstRealChild();
1541 if (controlPressed && shiftPressed)
1542 selectRange(m_startOfShiftSelectionNote, end, /*unselectOthers=*/false);
1543 else if (shiftPressed)
1544 selectRange(m_startOfShiftSelectionNote, end);
1545 else if (controlPressed)
1546 clicked->setSelectedRecursivly(!clicked->allSelected());
1547 else if (!clicked->allSelected())
1548 unselectAllBut(clicked);
1549 setFocusedNote(end); /// /// ///
1550 m_startOfShiftSelectionNote = (end->isGroup() ? end->firstRealChild() : end);
1551 //m_noActionOnMouseRelease = false;
1552 m_noActionOnMouseRelease = true;
1553 return;
1555 // MOVED TO RELEASE EVENT:
1556 /* else if (clicked && zone != Note::None && zone != Note::BottomColumn && zone != Note::Resizer && (controlPressed || shiftPressed)) {
1557 if (controlPressed && shiftPressed)
1558 selectRange(m_startOfShiftSelectionNote, clicked, / *unselectOthers=* /false);
1559 else if (shiftPressed)
1560 selectRange(m_startOfShiftSelectionNote, clicked);
1561 else if (controlPressed)
1562 clicked->setSelectedRecursivly(!clicked->allSelected());
1563 setFocusedNote(clicked); /// /// ///
1564 m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked);
1565 m_noActionOnMouseRelease = true;
1566 return;
1569 // Initializing Note move:
1570 /* if ((zone == Note::Group || zone == Note::Handle) && clicked->isFree()) {
1571 m_movingNote = clicked;
1572 m_pickedHandle = QPoint(event->pos().x() - clicked->x(), event->pos().y() - clicked->y());
1573 m_noActionOnMouseRelease = true;
1574 m_lockedHovering = true;
1575 return;
1579 // Folding/Unfolding group:
1580 if (zone == Note::GroupExpander) {
1581 clicked->toggleFolded(Settings::playAnimations());
1582 relayoutNotes(true);
1583 m_noActionOnMouseRelease = true;
1584 return;
1588 // Popup menu for tag emblems:
1589 if (event->button() == Qt::RightButton && zone >= Note::Emblem0) {
1590 if (!clicked->isSelected())
1591 unselectAllBut(clicked);
1592 setFocusedNote(clicked); /// /// ///
1593 m_startOfShiftSelectionNote = clicked;
1594 popupEmblemMenu(clicked, zone - Note::Emblem0);
1595 m_noActionOnMouseRelease = true;
1596 return;
1599 // Insertion Popup Menu:
1600 if ( (event->button() == Qt::RightButton) &&
1601 ((!clicked && isFreeLayout()) ||
1602 (clicked && (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn))) ) {
1603 unselectAll();
1604 m_clickedToInsert = clicked;
1605 m_zoneToInsert = zone;
1606 m_posToInsert = event->pos();
1607 KMenu* menu = (KMenu*)(Global::bnpView->popupMenu("insert_popup"));
1608 if (!menu->title(/*id=*/120).isEmpty()) // If we already added a title, remove it because it would be kept and then added several times:
1609 menu->removeItem(/*id=*/120);
1610 menu->insertTitle((zone == Note::TopGroup || zone == Note::BottomGroup ? i18n("The verb (Group New Note)", "Group") : i18n("The verb (Insert New Note)", "Insert")), /*id=*/120, /*index=*/0);
1611 setInsertPopupMenu();
1612 connect( menu, SIGNAL(aboutToHide()), this, SLOT(delayedCancelInsertPopupMenu()) );
1613 connect( menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering()) );
1614 connect( menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick()) );
1615 connect( menu, SIGNAL(aboutToHide()), this, SLOT(hideInsertPopupMenu()) );
1616 doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually!
1617 m_lockedHovering = true;
1618 menu->exec(QCursor::pos());
1619 m_noActionOnMouseRelease = true;
1620 return;
1623 // Note Context Menu:
1624 if (event->button() == Qt::RightButton && clicked && !clicked->isColumn() && zone != Note::Resizer) {
1625 if (!clicked->isSelected())
1626 unselectAllBut(clicked);
1627 setFocusedNote(clicked); /// /// ///
1628 if (editedNote() == clicked) {
1629 closeEditor();
1630 clicked->setSelected(true);
1632 m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked);
1633 Q3PopupMenu* menu = Global::bnpView->popupMenu("note_popup");
1634 connect( menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering()) );
1635 connect( menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick()) );
1636 doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually!
1637 m_lockedHovering = true;
1638 menu->exec(QCursor::pos());
1639 m_noActionOnMouseRelease = true;
1640 return;
1643 // Paste selection under cursor (but not "create new primary note under cursor" because this is on moveRelease):
1644 if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) {
1645 if ((Settings::middleAction() != 0) && (event->state() == Qt::ShiftModifier)) {
1646 m_clickedToInsert = clicked;
1647 m_zoneToInsert = zone;
1648 m_posToInsert = event->pos();
1649 closeEditor();
1650 removeInserter(); // If clicked at an insertion line and the new note shows a dialog for editing,
1651 NoteType::Id type = (NoteType::Id)0; // hide that inserter before the note edition instead of after the dialog is closed
1652 switch (Settings::middleAction()) {
1653 case 1:
1654 m_isInsertPopupMenu = true;
1655 pasteNote();
1656 break;
1657 case 2: type = NoteType::Image; break;
1658 case 3: type = NoteType::Link; break;
1659 case 4: type = NoteType::Launcher; break;
1660 default:
1661 m_noActionOnMouseRelease = false;
1662 return; // Other options should be done on mouse release (to avoid mouse release to cancel them!)
1663 /* case 5: type = NoteType::Color; break;
1664 case 6:
1665 Global::bnpView->grabScreenshot();
1666 break;
1667 case 7:
1668 Global::bnpView->slotColorFromScreen();
1669 break;
1670 case 8:
1671 Global::bnpView->insertWizard(3); // loadFromFile
1672 break;
1673 case 9:
1674 Global::bnpView->insertWizard(1); // importKMenuLauncher
1675 break;
1676 case 10:
1677 Global::bnpView->insertWizard(2); // importIcon
1678 break;
1679 */ }
1680 if (type != 0) {
1681 m_ignoreCloseEditorOnNextMouseRelease = true;
1682 Global::bnpView->insertEmpty(type);
1684 } else {
1685 if (clicked)
1686 zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()), true );
1687 closeEditor();
1688 clickedToInsert(event, clicked, zone);
1689 save();
1691 m_noActionOnMouseRelease = true;
1692 return;
1695 // Finally, no action has been done durint pressEvent, so an action can be done on releaseEvent:
1696 m_noActionOnMouseRelease = false;
1698 /* Selection scenario:
1699 * On contentsMousePressEvent, put m_selectionStarted to true and init Begin and End selection point.
1700 * On contentsMouseMoveEvent, if m_selectionStarted, update End selection point, update selection rect,
1701 * and if it's larger, switching to m_isSelecting mode: we can draw the selection rectangle.
1703 // Prepare selection:
1704 if (event->button() == Qt::LeftButton) {
1705 m_selectionStarted = true;
1706 m_selectionBeginPoint = event->pos();
1707 // We usualy invert the selection with the Ctrl key, but some environements (like GNOME or The Gimp) do it with the Shift key.
1708 // Since the Shift key has no specific usage, we allow to invert selection ALSO with Shift for Gimp people
1709 m_selectionInvert = controlPressed || shiftPressed;
1713 void Basket::delayedCancelInsertPopupMenu()
1715 QTimer::singleShot( 0, this, SLOT(cancelInsertPopupMenu()) );
1718 void Basket::contentsContextMenuEvent(QContextMenuEvent *event)
1720 if (event->reason() == QContextMenuEvent::Keyboard) {
1721 if (countFounds/*countShown*/() == 0) { // TODO: Count shown!!
1722 QRect basketRect( mapToGlobal(QPoint(0,0)), size() );
1723 Q3PopupMenu *menu = Global::bnpView->popupMenu("insert_popup");
1724 setInsertPopupMenu();
1725 connect( menu, SIGNAL(aboutToHide()), this, SLOT(delayedCancelInsertPopupMenu()) );
1726 connect( menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering()) );
1727 connect( menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick()) );
1728 removeInserter();
1729 m_lockedHovering = true;
1730 PopupMenu::execAtRectCenter(*menu, basketRect); // Popup at center or the basket
1731 } else {
1732 if ( ! m_focusedNote->isSelected() )
1733 unselectAllBut(m_focusedNote);
1734 setFocusedNote(m_focusedNote); /// /// ///
1735 m_startOfShiftSelectionNote = (m_focusedNote->isGroup() ? m_focusedNote->firstRealChild() : m_focusedNote);
1736 // Popup at bottom (or top) of the focused note, if visible :
1737 Q3PopupMenu *menu = Global::bnpView->popupMenu("note_popup");
1738 connect( menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering()) );
1739 connect( menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick()) );
1740 doHoverEffects(m_focusedNote, Note::Content); // In the case where another popup menu was open, we should do that manually!
1741 m_lockedHovering = true;
1742 PopupMenu::execAtRectBottom(*menu, noteVisibleRect(m_focusedNote), true);
1747 QRect Basket::noteVisibleRect(Note *note)
1749 QRect rect( contentsToViewport(QPoint(note->x(), note->y())), QSize(note->width(),note->height()) );
1750 QPoint basketPoint = mapToGlobal(QPoint(0,0));
1751 rect.moveTopLeft( rect.topLeft() + basketPoint + QPoint(frameWidth(), frameWidth()) );
1753 // Now, rect contain the global note rectangle on the screen.
1754 // We have to clip it by the basket widget :
1755 if (rect.bottom() > basketPoint.y() + visibleHeight() + 1) { // Bottom too... bottom
1756 rect.setBottom( basketPoint.y() + visibleHeight() + 1);
1757 if (rect.height() <= 0) // Have at least one visible pixel of height
1758 rect.setTop(rect.bottom());
1760 if (rect.top() < basketPoint.y() + frameWidth()) { // Top too... top
1761 rect.setTop( basketPoint.y() + frameWidth());
1762 if (rect.height() <= 0)
1763 rect.setBottom(rect.top());
1765 if (rect.right() > basketPoint.x() + visibleWidth() + 1) { // Right too... right
1766 rect.setRight( basketPoint.x() + visibleWidth() + 1);
1767 if (rect.width() <= 0) // Have at least one visible pixel of width
1768 rect.setLeft(rect.right());
1770 if (rect.left() < basketPoint.x() + frameWidth()) { // Left too... left
1771 rect.setLeft( basketPoint.x() + frameWidth());
1772 if (rect.width() <= 0)
1773 rect.setRight(rect.left());
1776 return rect;
1779 void Basket::disableNextClick()
1781 m_lastDisableClick = QTime::currentTime();
1784 void Basket::recomputeAllStyles()
1786 FOR_EACH_NOTE (note)
1787 note->recomputeAllStyles();
1790 void Basket::removedStates(const Q3ValueList<State*> &deletedStates)
1792 bool modifiedBasket = false;
1794 FOR_EACH_NOTE (note)
1795 if (note->removedStates(deletedStates))
1796 modifiedBasket = true;
1798 if (modifiedBasket)
1799 save();
1802 void Basket::insertNote(Note *note, Note *clicked, int zone, const QPoint &pos, bool animateNewPosition)
1804 if (!note) {
1805 std::cout << "Wanted to insert NO note" << std::endl;
1806 return;
1809 if (clicked && zone == Note::BottomColumn) {
1810 // When inserting at the bottom of a column, it's obvious the new note SHOULD inherit tags.
1811 // We ensure that by changing the insertion point after the last note of the column:
1812 Note *last = clicked->lastChild();
1813 if (last) {
1814 clicked = last;
1815 zone = Note::BottomInsert;
1819 /// Insertion at the bottom of a column:
1820 if (clicked && zone == Note::BottomColumn) {
1821 note->setWidth(clicked->rightLimit() - clicked->x());
1822 Note *lastChild = clicked->lastChild();
1823 if (!animateNewPosition || !Settings::playAnimations())
1824 for (Note *n = note; n; n = n->next()) {
1825 n->setXRecursivly(clicked->x());
1826 n->setYRecursivly((lastChild ? lastChild : clicked)->bottom() + 1);
1828 appendNoteIn(note, clicked);
1830 /// Insertion relative to a note (top/bottom, insert/group):
1831 } else if (clicked) {
1832 note->setWidth(clicked->width());
1833 if (!animateNewPosition || !Settings::playAnimations())
1834 for (Note *n = note; n; n = n->next()) {
1835 if (zone == Note::TopGroup || zone == Note::BottomGroup)
1836 n->setXRecursivly(clicked->x() + Note::GROUP_WIDTH);
1837 else
1838 n->setXRecursivly(clicked->x());
1839 if (zone == Note::TopInsert || zone == Note::TopGroup)
1840 n->setYRecursivly(clicked->y());
1841 else
1842 n->setYRecursivly(clicked->bottom() + 1);
1845 if (zone == Note::TopInsert) { appendNoteBefore(note, clicked); }
1846 else if (zone == Note::BottomInsert) { appendNoteAfter(note, clicked); }
1847 else if (zone == Note::TopGroup) { groupNoteBefore(note, clicked); }
1848 else if (zone == Note::BottomGroup) { groupNoteAfter(note, clicked); }
1850 /// Free insertion:
1851 } else if (isFreeLayout()) {
1852 // Group if note have siblings:
1853 if (note->next()) {
1854 Note *group = new Note(this);
1855 for (Note *n = note; n; n = n->next())
1856 n->setParentNote(group);
1857 group->setFirstChild(note);
1858 note = group;
1860 // Insert at cursor position:
1861 const int initialWidth = 250;
1862 note->setWidth(note->isGroup() ? Note::GROUP_WIDTH : initialWidth);
1863 if (note->isGroup() && note->firstChild())
1864 note->setInitialHeight(note->firstChild()->height());
1865 //note->setGroupWidth(initialWidth);
1866 if (animateNewPosition && Settings::playAnimations())
1867 note->setFinalPosition(pos.x(), pos.y());
1868 else {
1869 note->setXRecursivly(pos.x());
1870 note->setYRecursivly(pos.y());
1872 appendNoteAfter(note, lastNote());
1875 relayoutNotes(true);
1878 void Basket::clickedToInsert(QMouseEvent *event, Note *clicked, /*Note::Zone*/int zone)
1880 Note *note;
1881 if (event->button() == Qt::MidButton)
1882 note = NoteFactory::dropNote(KApplication::clipboard()->data(QClipboard::Selection), this);
1883 else
1884 note = NoteFactory::createNoteText("", this);
1886 if (!note)
1887 return;
1889 insertNote(note, clicked, zone, event->pos(), /*animateNewPosition=*/false);
1891 // ensureNoteVisible(lastInsertedNote()); // TODO: in insertNote()
1893 if (event->button() != Qt::MidButton) {
1894 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.
1895 closeEditor();
1896 noteEdit(note, /*justAdded=*/true);
1900 void Basket::contentsDragEnterEvent(QDragEnterEvent *event)
1902 m_isDuringDrag = true;
1903 Global::bnpView->updateStatusBarHint();
1904 if (NoteDrag::basketOf(event) == this)
1905 m_draggedNotes = NoteDrag::notesOf(event);
1908 void Basket::contentsDragMoveEvent(QDragMoveEvent *event)
1910 // m_isDuringDrag = true;
1912 // if (isLocked())
1913 // return;
1915 // FIXME: viewportToContents does NOT work !!!
1916 // QPoint pos = viewportToContents(event->pos());
1917 // QPoint pos( event->pos().x() + contentsX(), event->pos().y() + contentsY() );
1919 // if (insertAtCursorPos())
1920 // computeInsertPlace(pos);
1921 doHoverEffects(event->pos());
1923 // showFrameInsertTo();
1924 if (isFreeLayout() || noteAt(event->pos().x(), event->pos().y())) // Cursor before rightLimit() or hovering the dragged source notes
1925 acceptDropEvent(event);
1926 else {
1927 event->acceptAction(false);
1928 event->accept(false);
1931 /* Note *hoveredNote = noteAt(event->pos().x(), event->pos().y());
1932 if ( (isColumnsLayout() && !hoveredNote) || (draggedNotes().contains(hoveredNote)) ) {
1933 event->acceptAction(false);
1934 event->accept(false);
1935 } else
1936 acceptDropEvent(event);*/
1938 // A workarround since QScrollView::dragAutoScroll seem to have no effect :
1939 // ensureVisible(event->pos().x() + contentsX(), event->pos().y() + contentsY(), 30, 30);
1940 // QScrollView::dragMoveEvent(event);
1943 void Basket::contentsDragLeaveEvent(QDragLeaveEvent*)
1945 // resetInsertTo();
1946 m_isDuringDrag = false;
1947 m_draggedNotes.clear();
1948 m_noActionOnMouseRelease = true;
1949 emit resetStatusBarText();
1950 doHoverEffects();
1953 void Basket::contentsDropEvent(QDropEvent *event)
1955 QPoint pos = event->pos();
1956 std::cout << "Contents Drop Event at position " << pos.x() << ":" << pos.y() << std::endl;
1958 m_isDuringDrag = false;
1959 emit resetStatusBarText();
1961 // if (isLocked())
1962 // return;
1964 // Do NOT check the bottom&right borders.
1965 // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars),
1966 // the note is first removed, and relayoutNotes() compute the new height that is smaller
1967 // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!!
1968 // Should, of course, not return 0:
1969 Note *clicked = noteAt(event->pos().x(), event->pos().y());
1971 if (NoteFactory::movingNotesInTheSameBasket(event, this, event->action()) && event->action() == QDropEvent::Move) {
1972 m_doNotCloseEditor = true;
1975 Note *note = NoteFactory::dropNote( event, this, true, event->action(), dynamic_cast<Note*>(event->source()) );
1977 if (note) {
1978 Note::Zone zone = (clicked ? clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()), /*toAdd=*/true ) : Note::None);
1979 bool animateNewPosition = NoteFactory::movingNotesInTheSameBasket(event, this, event->action());
1980 if (animateNewPosition) {
1981 FOR_EACH_NOTE (n)
1982 n->setOnTop(false);
1983 // FOR_EACH_NOTE_IN_CHUNK(note)
1984 for (Note *n = note; n; n = n->next())
1985 n->setOnTop(true);
1988 insertNote(note, clicked, zone, event->pos(), animateNewPosition);
1990 // If moved a note on bottom, contentsHeight has been disminished, then view scrolled up, and we should re-scroll the view down:
1991 ensureNoteVisible(note);
1993 // if (event->button() != Qt::MidButton) {
1994 // removeInserter(); // Case: user clicked below a column to insert, the note is inserted and doHoverEffects() put a new inserter below. We don't want it.
1995 // }
1997 // resetInsertTo();
1998 // doHoverEffects(); called by insertNote()
1999 save();
2002 m_draggedNotes.clear();
2004 m_doNotCloseEditor = false;
2005 // When starting the drag, we saved where we were editing.
2006 // This is because during a drag, the mouse can fly over the text edit and move the cursor position, and even HIDE the cursor.
2007 // So we re-show the cursor, and re-position it at the right place:
2008 if (m_editor && m_editor->textEdit()) {
2009 Q3TextEdit *editor = m_editor->textEdit();
2010 editor->setCursorPosition(m_editParagraph, m_editIndex);
2014 // handles dropping of a note to basket that is not shown
2015 // (usually through its entry in the basket list)
2016 void Basket::blindDrop(QDropEvent* event)
2018 if (!m_isInsertPopupMenu && redirectEditActions()) {
2019 if (m_editor->textEdit())
2020 m_editor->textEdit()->paste();
2021 else if (m_editor->lineEdit())
2022 m_editor->lineEdit()->paste();
2023 } else {
2024 if (!isLoaded()) {
2025 Global::bnpView->showPassiveLoading(this);
2026 load();
2028 closeEditor();
2029 unselectAll();
2030 Note *note = NoteFactory::dropNote( event, this, true, event->action(),
2031 dynamic_cast<Note*>(event->source()) );
2032 if (note) {
2033 insertCreatedNote(note);
2034 //unselectAllBut(note);
2035 if (Settings::usePassivePopup())
2036 Global::bnpView->showPassiveDropped(i18n("Dropped to basket <i>%1</i>"));
2039 save();
2042 void Basket::insertEmptyNote(int type)
2044 if (!isLoaded())
2045 load();
2046 if (isDuringEdit())
2047 closeEditor();
2048 Note *note = NoteFactory::createEmptyNote((NoteType::Id)type, this);
2049 insertCreatedNote(note/*, / *edit=* /true*/);
2050 noteEdit(note, /*justAdded=*/true);
2053 void Basket::insertWizard(int type)
2055 saveInsertionData();
2056 Note *note = 0;
2057 switch (type) {
2058 default:
2059 case 1: note = NoteFactory::importKMenuLauncher(this); break;
2060 case 2: note = NoteFactory::importIcon(this); break;
2061 case 3: note = NoteFactory::importFileContent(this); break;
2063 if (!note)
2064 return;
2065 restoreInsertionData();
2066 insertCreatedNote(note);
2067 unselectAllBut(note);
2068 resetInsertionData();
2071 void Basket::insertColor(const QColor &color)
2073 Note *note = NoteFactory::createNoteColor(color, this);
2074 restoreInsertionData();
2075 insertCreatedNote(note);
2076 unselectAllBut(note);
2077 resetInsertionData();
2080 void Basket::insertImage(const QPixmap &image)
2082 Note *note = NoteFactory::createNoteImage(image, this);
2083 restoreInsertionData();
2084 insertCreatedNote(note);
2085 unselectAllBut(note);
2086 resetInsertionData();
2089 void Basket::pasteNote(QClipboard::Mode mode)
2091 if (!m_isInsertPopupMenu && redirectEditActions()) {
2092 if (m_editor->textEdit())
2093 m_editor->textEdit()->paste();
2094 else if (m_editor->lineEdit())
2095 m_editor->lineEdit()->paste();
2096 } else {
2097 if (!isLoaded()) {
2098 Global::bnpView->showPassiveLoading(this);
2099 load();
2101 closeEditor();
2102 unselectAll();
2103 Note *note = NoteFactory::dropNote(KApplication::clipboard()->data(mode), this);
2104 if (note) {
2105 insertCreatedNote(note);
2106 //unselectAllBut(note);
2111 void Basket::insertCreatedNote(Note *note)
2113 // Get the insertion data if the user clicked inside the basket:
2114 Note *clicked = m_clickedToInsert;
2115 int zone = m_zoneToInsert;
2116 QPoint pos = m_posToInsert;
2118 // If it isn't the case, use the default position:
2119 if (!clicked && (pos.x() < 0 || pos.y() < 0)) {
2120 // Insert right after the focused note:
2121 focusANote();
2122 if (m_focusedNote) {
2123 clicked = m_focusedNote;
2124 zone = (m_focusedNote->isFree() ? Note::BottomGroup : Note::BottomInsert);
2125 pos = QPoint(m_focusedNote->x(), m_focusedNote->finalBottom());
2126 // Insert at the end of the last column:
2127 } else if (isColumnsLayout()) {
2128 Note *column = /*(Settings::newNotesPlace == 0 ?*/ firstNote() /*: lastNote())*/;
2129 /*if (Settings::newNotesPlace == 0 && column->firstChild()) { // On Top, if at least one child in the column
2130 clicked = column->firstChild();
2131 zone = Note::TopInsert;
2132 } else { // On Bottom*/
2133 clicked = column;
2134 zone = Note::BottomColumn;
2135 /*}*/
2136 // Insert at free position:
2137 } else {
2138 pos = QPoint(0, 0);
2142 insertNote(note, clicked, zone, pos);
2143 // ensureNoteVisible(lastInsertedNote());
2144 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.
2145 // resetInsertTo();
2146 save();
2149 void Basket::saveInsertionData()
2151 m_savedClickedToInsert = m_clickedToInsert;
2152 m_savedZoneToInsert = m_zoneToInsert;
2153 m_savedPosToInsert = m_posToInsert;
2156 void Basket::restoreInsertionData()
2158 m_clickedToInsert = m_savedClickedToInsert;
2159 m_zoneToInsert = m_savedZoneToInsert;
2160 m_posToInsert = m_savedPosToInsert;
2163 void Basket::resetInsertionData()
2165 m_clickedToInsert = 0;
2166 m_zoneToInsert = 0;
2167 m_posToInsert = QPoint(-1, -1);
2170 void Basket::hideInsertPopupMenu()
2172 QTimer::singleShot( 50/*ms*/, this, SLOT(timeoutHideInsertPopupMenu()) );
2175 void Basket::timeoutHideInsertPopupMenu()
2177 resetInsertionData();
2180 void Basket::acceptDropEvent(QDropEvent *event, bool preCond)
2182 // FIXME: Should not accept all actions! Or not all actions (link not supported?!)
2183 event->acceptAction(preCond && 1);
2184 event->accept(preCond);
2187 void Basket::contentsMouseReleaseEvent(QMouseEvent *event)
2189 // Now disallow drag:
2190 m_canDrag = false;
2192 // Cancel Resizer move:
2193 if (m_resizingNote) {
2194 m_resizingNote = 0;
2195 m_pickedResizer = 0;
2196 m_lockedHovering = false;
2197 doHoverEffects();
2198 save();
2201 // Cancel Note move:
2202 /* if (m_movingNote) {
2203 m_movingNote = 0;
2204 m_pickedHandle = QPoint(0, 0);
2205 m_lockedHovering = false;
2206 //doHoverEffects();
2207 save();
2211 // Cancel Selection rectangle:
2212 if (m_isSelecting) {
2213 m_isSelecting = false;
2214 stopAutoScrollSelection();
2215 resetWasInLastSelectionRect();
2216 doHoverEffects();
2217 updateContents(m_selectionRect);
2219 m_selectionStarted = false;
2221 Note *clicked = noteAt(event->pos().x(), event->pos().y());
2222 Note::Zone zone = (clicked ? clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()) ) : Note::None);
2223 if ((zone == Note::Handle || zone == Note::Group) && editedNote() && editedNote() == clicked) {
2224 if (m_ignoreCloseEditorOnNextMouseRelease)
2225 m_ignoreCloseEditorOnNextMouseRelease = false;
2226 else {
2227 bool editedNoteStillThere = closeEditor();
2228 if (editedNoteStillThere)
2229 //clicked->setSelected(true);
2230 unselectAllBut(clicked);
2235 if (event->stateAfter() == 0 && (zone == Note::Group || zone == Note::Handle)) {
2236 closeEditor();
2237 unselectAllBut(clicked);
2241 // Do nothing if an action has already been made during mousePressEvent,
2242 // or if user made a selection and canceled it by regressing to a very small rectangle.
2243 if (m_noActionOnMouseRelease)
2244 return;
2245 // We immediatly set it to true, to avoid actions set on mouseRelease if NO mousePress event has been triggered.
2246 // This is the case when a popup menu is shown, and user click to the basket area to close it:
2247 // the menu then receive the mousePress event and the basket area ONLY receive the mouseRelease event.
2248 // Obviously, nothing should be done in this case:
2249 m_noActionOnMouseRelease = true;
2253 if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) {
2254 if ((Settings::middleAction() != 0) && (event->stateAfter() == Qt::ShiftModifier)) {
2255 m_clickedToInsert = clicked;
2256 m_zoneToInsert = zone;
2257 m_posToInsert = event->pos();
2258 closeEditor();
2259 removeInserter(); // If clicked at an insertion line and the new note shows a dialog for editing,
2260 NoteType::Id type = (NoteType::Id)0; // hide that inserter before the note edition instead of after the dialog is closed
2261 switch (Settings::middleAction()) {
2262 case 5: type = NoteType::Color; break;
2263 case 6:
2264 Global::bnpView->grabScreenshot();
2265 return;
2266 case 7:
2267 Global::bnpView->slotColorFromScreen();
2268 return;
2269 case 8:
2270 Global::bnpView->insertWizard(3); // loadFromFile
2271 return;
2272 case 9:
2273 Global::bnpView->insertWizard(1); // importKMenuLauncher
2274 return;
2275 case 10:
2276 Global::bnpView->insertWizard(2); // importIcon
2277 return;
2279 if (type != 0) {
2280 m_ignoreCloseEditorOnNextMouseRelease = true;
2281 Global::bnpView->insertEmpty(type);
2282 return;
2287 // Note *clicked = noteAt(event->pos().x(), event->pos().y());
2288 if ( ! clicked ) {
2289 if (isFreeLayout() && event->button() == Qt::LeftButton) {
2290 clickedToInsert(event);
2291 save();
2293 return;
2295 // Note::Zone zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()) );
2297 // Convenient variables:
2298 bool controlPressed = event->stateAfter() & Qt::ControlModifier;
2299 bool shiftPressed = event->stateAfter() & Qt::ShiftModifier;
2301 if (clicked && zone != Note::None && zone != Note::BottomColumn && zone != Note::Resizer && (controlPressed || shiftPressed)) {
2302 if (controlPressed && shiftPressed)
2303 selectRange(m_startOfShiftSelectionNote, clicked, /*unselectOthers=*/false);
2304 else if (shiftPressed)
2305 selectRange(m_startOfShiftSelectionNote, clicked);
2306 else if (controlPressed)
2307 clicked->setSelectedRecursivly(!clicked->allSelected());
2308 setFocusedNote(clicked); /// /// ///
2309 m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked);
2310 m_noActionOnMouseRelease = true;
2311 return;
2314 // Switch tag states:
2315 if (zone >= Note::Emblem0) {
2316 if (event->button() == Qt::LeftButton) {
2317 int icons = -1;
2318 for (State::List::iterator it = clicked->states().begin(); it != clicked->states().end(); ++it) {
2319 if ( ! (*it)->emblem().isEmpty() )
2320 icons++;
2321 if (icons == zone - Note::Emblem0) {
2322 State *state = (*it)->nextState();
2323 if (!state)
2324 return;
2325 it = clicked->states().insert(it, state);
2326 ++it;
2327 clicked->states().remove(it);
2328 clicked->recomputeStyle();
2329 clicked->unbufferize();
2330 updateNote(clicked);
2331 updateEditorAppearance();
2332 filterAgain();
2333 save();
2334 break;
2337 return;
2338 }/* else if (event->button() == Qt::RightButton) {
2339 popupEmblemMenu(clicked, zone - Note::Emblem0);
2340 return;
2344 // Insert note or past clipboard:
2345 QString text;
2346 // Note *note;
2347 QString link;
2348 //int zone = zone;
2349 if (event->button() == Qt::MidButton && zone == Note::Resizer)
2350 return; //zone = clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()), true );
2351 if (event->button() == Qt::RightButton && (clicked->isColumn() || zone == Note::Resizer))
2352 return;
2353 if (clicked->isGroup() && zone == Note::None)
2354 return;
2355 switch (zone) {
2356 case Note::Handle:
2357 case Note::Group:
2358 // We select note on mousePress if it was unselected or Ctrl is pressed.
2359 // But the user can want to drag select_s_ notes, so it the note is selected, we only select it alone on mouseRelease:
2360 if (event->stateAfter() == 0) {
2361 std::cout << "EXEC" << std::endl;
2362 if ( !(event->stateAfter() & Qt::ControlModifier) && clicked->allSelected())
2363 unselectAllBut(clicked);
2364 if (zone == Note::Handle && isDuringEdit() && editedNote() == clicked) {
2365 closeEditor();
2366 clicked->setSelected(true);
2369 break;
2371 case Note::Custom0:
2372 //unselectAllBut(clicked);
2373 setFocusedNote(clicked);
2374 noteOpen(clicked);
2375 break;
2377 case Note::GroupExpander:
2378 case Note::TagsArrow:
2379 break;
2381 case Note::Link:
2382 link = clicked->linkAt(event->pos() - QPoint(clicked->x(), clicked->y()));
2383 if ( ! link.isEmpty() ) {
2384 if (link == "basket-internal-remove-basket") {
2385 // 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."
2386 Global::bnpView->doBasketDeletion(this);
2387 } else if (link == "basket-internal-import") {
2388 Q3PopupMenu *menu = Global::bnpView->popupMenu("fileimport");
2389 menu->exec(event->globalPos());
2390 } else {
2391 KRun *run = new KRun(link); // open the URL.
2392 run->setAutoDelete(true);
2394 break;
2395 } // If there is no link, edit note content
2396 case Note::Content:
2397 closeEditor();
2398 unselectAllBut(clicked);
2399 noteEdit(clicked, /*justAdded=*/false, event->pos());
2400 break;
2402 case Note::TopInsert:
2403 case Note::TopGroup:
2404 case Note::BottomInsert:
2405 case Note::BottomGroup:
2406 case Note::BottomColumn:
2407 clickedToInsert(event, clicked, zone);
2408 save();
2409 break;
2411 case Note::None:
2412 default:
2413 KMessageBox::information(viewport(),
2414 i18n("This message should never appear. If it does, this program is buggy! "
2415 "Please report the bug to the developer."));
2416 break;
2420 void Basket::contentsMouseDoubleClickEvent(QMouseEvent *event)
2422 Note *clicked = noteAt(event->pos().x(), event->pos().y());
2423 Note::Zone zone = (clicked ? clicked->zoneAt( event->pos() - QPoint(clicked->x(), clicked->y()) ) : Note::None);
2425 if (event->button() == Qt::LeftButton && (zone == Note::Group || zone == Note::Handle)) {
2426 doCopy(CopyToSelection);
2427 m_noActionOnMouseRelease = true;
2428 } else
2429 contentsMousePressEvent(event);
2432 void Basket::contentsMouseMoveEvent(QMouseEvent *event)
2434 // Drag the notes:
2435 if (m_canDrag && (m_pressPos - event->pos()).manhattanLength() > KApplication::startDragDistance()) {
2436 m_canDrag = false;
2437 m_isSelecting = false; // Don't draw selection rectangle ater drag!
2438 m_selectionStarted = false;
2439 NoteSelection *selection = selectedNotes();
2440 if (selection->firstStacked()) {
2441 Q3DragObject *d = NoteDrag::dragObject(selection, /*cutting=*/false, /*source=*/this); // d will be deleted by QT
2442 /*bool shouldRemove = */d->drag();
2443 // delete selection;
2445 // Never delete because URL is dragged and the file must be available for the extern appliation
2446 // if (shouldRemove && d->target() == 0) // If target is another application that request to remove the note
2447 // emit wantDelete(this);
2449 return;
2452 // Moving a Resizer:
2453 if (m_resizingNote) {
2454 int groupWidth = event->pos().x() - m_resizingNote->x() - m_pickedResizer;
2455 int minRight = m_resizingNote->minRight();
2456 int maxRight = 100 * contentsWidth(); // A big enough value (+infinity) for free layouts.
2457 Note *nextColumn = m_resizingNote->next();
2458 if (m_resizingNote->isColumn()) {
2459 if (nextColumn)
2460 maxRight = nextColumn->x() + nextColumn->rightLimit() - nextColumn->minRight() - Note::RESIZER_WIDTH;
2461 else
2462 maxRight = contentsWidth();
2464 if (groupWidth > maxRight - m_resizingNote->x())
2465 groupWidth = maxRight - m_resizingNote->x();
2466 if (groupWidth < minRight - m_resizingNote->x())
2467 groupWidth = minRight - m_resizingNote->x();
2468 int delta = groupWidth - m_resizingNote->groupWidth();
2469 m_resizingNote->setGroupWidth(groupWidth);
2470 // If resizing columns:
2471 if (m_resizingNote->isColumn()) {
2472 Note *column = m_resizingNote;
2473 if ( (column = column->next()) ) {
2474 // Next columns should not have them X coordinate animated, because it would flicker:
2475 column->setXRecursivly(column->x() + delta);
2476 // And the resizer should resize the TWO sibling columns, and not push the other columns on th right:
2477 column->setGroupWidth(column->groupWidth() - delta);
2480 relayoutNotes(true);
2483 // Moving a Note:
2484 /* if (m_movingNote) {
2485 int x = event->pos().x() - m_pickedHandle.x();
2486 int y = event->pos().y() - m_pickedHandle.y();
2487 if (x < 0) x = 0;
2488 if (y < 0) y = 0;
2489 m_movingNote->setX(x);
2490 m_movingNote->setY(y);
2491 m_movingNote->relayoutAt(x, y, / *animate=* /false);
2492 relayoutNotes(true);
2496 // Dragging the selection rectangle:
2497 if (m_selectionStarted)
2498 doAutoScrollSelection();
2500 doHoverEffects(event->pos());
2503 void Basket::doAutoScrollSelection()
2505 static const int AUTO_SCROLL_MARGIN = 50; // pixels
2506 static const int AUTO_SCROLL_DELAY = 100; // milliseconds
2508 QPoint pos = viewport()->mapFromGlobal(QCursor::pos());
2510 // Do the selection:
2512 if (m_isSelecting)
2513 updateContents(m_selectionRect);
2515 m_selectionEndPoint = viewportToContents(pos);
2516 m_selectionRect = QRect(m_selectionBeginPoint, m_selectionEndPoint).normalize();
2517 if (m_selectionRect.left() < 0) m_selectionRect.setLeft(0);
2518 if (m_selectionRect.top() < 0) m_selectionRect.setTop(0);
2519 if (m_selectionRect.right() >= contentsWidth()) m_selectionRect.setRight(contentsWidth() - 1);
2520 if (m_selectionRect.bottom() >= contentsHeight()) m_selectionRect.setBottom(contentsHeight() - 1);
2522 if ( (m_selectionBeginPoint - m_selectionEndPoint).manhattanLength() > QApplication::startDragDistance() ) {
2523 m_isSelecting = true;
2524 selectNotesIn(m_selectionRect, m_selectionInvert);
2525 updateContents(m_selectionRect);
2526 m_noActionOnMouseRelease = true;
2527 } else {
2528 // If the user was selecting but cancel by making the rectangle too small, cancel it really!!!
2529 if (m_isSelecting) {
2530 if (m_selectionInvert)
2531 selectNotesIn(QRect(), m_selectionInvert);
2532 else
2533 unselectAllBut(0); // TODO: unselectAll();
2535 if (m_isSelecting)
2536 resetWasInLastSelectionRect();
2537 m_isSelecting = false;
2538 stopAutoScrollSelection();
2539 return;
2542 // Do the auto-scrolling:
2543 // FIXME: It's still flickering
2545 QRect insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, visibleWidth() - 2*AUTO_SCROLL_MARGIN, visibleHeight() - 2*AUTO_SCROLL_MARGIN);
2547 int dx = 0;
2548 int dy = 0;
2550 if (pos.y() < AUTO_SCROLL_MARGIN)
2551 dy = pos.y() - AUTO_SCROLL_MARGIN;
2552 else if (pos.y() > visibleHeight() - AUTO_SCROLL_MARGIN)
2553 dy = pos.y() - visibleHeight() + AUTO_SCROLL_MARGIN;
2555 if (pos.x() < AUTO_SCROLL_MARGIN)
2556 dx = pos.x() - AUTO_SCROLL_MARGIN;
2557 else if (pos.x() > visibleWidth() - AUTO_SCROLL_MARGIN)
2558 dx = pos.x() - visibleWidth() + AUTO_SCROLL_MARGIN;
2560 if (dx || dy) {
2561 kapp->sendPostedEvents(); // Do the repaints, because the scrolling will make the area to repaint to be wrong
2562 scrollBy(dx, dy);
2563 if (!m_autoScrollSelectionTimer.isActive())
2564 m_autoScrollSelectionTimer.start(AUTO_SCROLL_DELAY);
2565 } else
2566 stopAutoScrollSelection();
2569 void Basket::stopAutoScrollSelection()
2571 m_autoScrollSelectionTimer.stop();
2574 void Basket::resetWasInLastSelectionRect()
2576 Note *note = m_firstNote;
2577 while (note) {
2578 note->resetWasInLastSelectionRect();
2579 note = note->next();
2583 void Basket::selectAll()
2585 if (redirectEditActions()) {
2586 if (m_editor->textEdit())
2587 m_editor->textEdit()->selectAll();
2588 else if (m_editor->lineEdit())
2589 m_editor->lineEdit()->selectAll();
2590 } else {
2591 // First select all in the group, then in the parent group...
2592 Note *child = m_focusedNote;
2593 Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0);
2594 while (parent) {
2595 if (!parent->allSelected()) {
2596 parent->setSelectedRecursivly(true);
2597 return;
2599 child = parent;
2600 parent = parent->parentNote();
2602 // Then, select all:
2603 FOR_EACH_NOTE (note)
2604 note->setSelectedRecursivly(true);
2608 void Basket::unselectAll()
2610 if (redirectEditActions()) {
2611 if (m_editor->textEdit()) {
2612 m_editor->textEdit()->removeSelection();
2613 selectionChangedInEditor(); // THIS IS NOT EMITED BY Qt!!!
2614 } else if (m_editor->lineEdit())
2615 m_editor->lineEdit()->deselect();
2616 } else {
2617 if (countSelecteds() > 0) // Optimisation
2618 FOR_EACH_NOTE (note)
2619 note->setSelectedRecursivly(false);
2623 void Basket::invertSelection()
2625 FOR_EACH_NOTE (note)
2626 note->invertSelectionRecursivly();
2629 void Basket::unselectAllBut(Note *toSelect)
2631 FOR_EACH_NOTE (note)
2632 note->unselectAllBut(toSelect);
2635 void Basket::invertSelectionOf(Note *toSelect)
2637 FOR_EACH_NOTE (note)
2638 note->invertSelectionOf(toSelect);
2641 void Basket::selectNotesIn(const QRect &rect, bool invertSelection, bool unselectOthers /*= true*/)
2643 FOR_EACH_NOTE (note)
2644 note->selectIn(rect, invertSelection, unselectOthers);
2647 void Basket::doHoverEffects()
2649 doHoverEffects( viewportToContents( viewport()->mapFromGlobal(QCursor::pos()) ) );
2652 void Basket::doHoverEffects(Note *note, Note::Zone zone, const QPoint &pos)
2654 // Inform the old and new hovered note (if any):
2655 Note *oldHoveredNote = m_hoveredNote;
2656 if (note != m_hoveredNote) {
2657 if (m_hoveredNote) {
2658 m_hoveredNote->setHovered(false);
2659 m_hoveredNote->setHoveredZone(Note::None);
2660 updateNote(m_hoveredNote);
2662 m_hoveredNote = note;
2663 if (note)
2664 note->setHovered(true);
2667 // If we are hovering a note, compute which zone is hovered and inform the note:
2668 if (m_hoveredNote) {
2669 if (zone != m_hoveredZone || oldHoveredNote != m_hoveredNote) {
2670 m_hoveredZone = zone;
2671 m_hoveredNote->setCursor(zone);
2672 updateNote(m_hoveredNote);
2674 m_hoveredNote->setHoveredZone(zone);
2675 // If we are hovering an insert line zone, update this thing:
2676 if (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn)
2677 placeInserter(m_hoveredNote, zone);
2678 else
2679 removeInserter();
2680 // If we are hovering an embedded link in a rich text element, show the destination in the statusbar:
2681 if (zone == Note::Link)
2682 emit setStatusBarText(m_hoveredNote->linkAt( pos - QPoint(m_hoveredNote->x(), m_hoveredNote->y()) ));
2683 else if (m_hoveredNote->content())
2684 emit setStatusBarText(m_hoveredNote->content()->statusBarMessage(m_hoveredZone));//resetStatusBarText();
2685 // If we aren't hovering a note, reset all:
2686 } else {
2687 if (isFreeLayout() && !isSelecting())
2688 viewport()->setCursor(Qt::CrossCursor);
2689 else
2690 viewport()->unsetCursor();
2691 m_hoveredZone = Note::None;
2692 removeInserter();
2693 emit resetStatusBarText();
2697 void Basket::doHoverEffects(const QPoint &pos)
2699 // if (isDuringEdit())
2700 // viewport()->unsetCursor();
2702 // Do we have the right to do hover effects?
2703 if ( ! m_loaded || m_lockedHovering)
2704 return;
2706 // enterEvent() (mouse enter in the widget) set m_underMouse to true, and leaveEvent() make it false.
2707 // But some times the enterEvent() is not trigerred: eg. when dragging the scrollbar:
2708 // Ending the drag INSIDE the basket area will make NO hoverEffects() because m_underMouse is false.
2709 // User need to leave the area and re-enter it to get effects.
2710 // This hack solve that by dismissing the m_underMouse variable:
2711 bool underMouse = Global::bnpView->currentBasket() == this && QRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()).contains(pos);
2713 // Don't do hover effects when a popup menu is opened.
2714 // Primarily because the basket area will only receive mouseEnterEvent and mouveLeaveEvent.
2715 // It willn't be noticed of mouseMoveEvent, which would result in a apparently broken application state:
2716 if (kapp->activePopupWidget())
2717 underMouse = false;
2719 // Compute which note is hovered:
2720 Note *note = (m_isSelecting || !underMouse ? 0 : noteAt(pos.x(), pos.y()));
2721 Note::Zone zone = (note ? note->zoneAt( pos - QPoint(note->x(), note->y()), isDuringDrag() ) : Note::None);
2723 // Inform the old and new hovered note (if any) and update the areas:
2724 doHoverEffects(note, zone, pos);
2727 void Basket::mouseEnteredEditorWidget()
2729 if (!m_lockedHovering && !kapp->activePopupWidget())
2730 doHoverEffects(editedNote(), Note::Content, QPoint());
2733 void Basket::removeInserter()
2735 if (m_inserterShown) { // Do not hide (and then update/repaint the view) if it is already hidden!
2736 m_inserterShown = false;
2737 updateContents(m_inserterRect);
2741 void Basket::placeInserter(Note *note, int zone)
2743 // Remove the inserter:
2744 if (!note) {
2745 removeInserter();
2746 return;
2749 // Update the old position:
2750 if (inserterShown())
2751 updateContents(m_inserterRect);
2752 // Some comodities:
2753 m_inserterShown = true;
2754 m_inserterTop = (zone == Note::TopGroup || zone == Note::TopInsert);
2755 m_inserterGroup = (zone == Note::TopGroup || zone == Note::BottomGroup);
2756 // X and width:
2757 int groupIndent = (note->isGroup() ? note->width() : Note::HANDLE_WIDTH);
2758 int x = note->x();
2759 int width = (note->isGroup() ? note->rightLimit() - note->x() : note->width());
2760 if (m_inserterGroup) {
2761 x += groupIndent;
2762 width -= groupIndent;
2764 m_inserterSplit = (Settings::groupOnInsertionLine() && note && !note->isGroup() && !note->isFree() && !note->isColumn());
2765 // if (note->isGroup())
2766 // width = note->rightLimit() - note->x() - (m_inserterGroup ? groupIndent : 0);
2767 // Y:
2768 int y = note->y() - (m_inserterGroup && m_inserterTop ? 1 : 3);
2769 if (!m_inserterTop)
2770 y += (note->isColumn() ? note->finalHeight() : note->height());
2771 // Assigning result:
2772 m_inserterRect = QRect(x, y, width, 6 - (m_inserterGroup ? 2 : 0));
2773 // Update the new position:
2774 updateContents(m_inserterRect);
2777 inline void drawLineByRect(QPainter &painter, int x, int y, int width, int height)
2779 painter.drawLine(x, y, x + width - 1, y + height - 1);
2782 void Basket::drawInserter(QPainter &painter, int xPainter, int yPainter)
2784 if (!m_inserterShown)
2785 return;
2787 QRect rect = m_inserterRect; // For shorter code-lines when drawing!
2788 rect.moveBy(-xPainter, -yPainter);
2789 int lineY = (m_inserterGroup && m_inserterTop ? 0 : 2);
2790 int roundY = (m_inserterGroup && m_inserterTop ? 0 : 1);
2792 QColor dark = KApplication::palette().active().dark();
2793 QColor light = dark.light().light();
2794 if (m_inserterGroup && Settings::groupOnInsertionLine())
2795 light = Tools::mixColor(light, KGlobalSettings::highlightColor());
2796 painter.setPen(dark);
2797 // The horizontal line:
2798 //painter.drawRect( rect.x(), rect.y() + lineY, rect.width(), 2);
2799 int width = rect.width() - 4;
2800 drawGradient(&painter, dark, light, rect.x() + 2, rect.y() + lineY, width/2, 2, /*sunken=*/false, /*horz=*/false, /*flat=*/false);
2801 drawGradient(&painter, light, dark, rect.x() + 2 + width/2, rect.y() + lineY, width - width/2, 2, /*sunken=*/false, /*horz=*/false, /*flat=*/false);
2802 // The left-most and right-most edges (biggest vertical lines):
2803 drawLineByRect(painter, rect.x(), rect.y(), 1, (m_inserterGroup ? 4 : 6));
2804 drawLineByRect(painter, rect.x() + rect.width() - 1, rect.y(), 1, (m_inserterGroup ? 4 : 6));
2805 // The left and right mid vertical lines:
2806 drawLineByRect(painter, rect.x() + 1, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4));
2807 drawLineByRect(painter, rect.x() + rect.width() - 2, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4));
2808 // Draw the split as a feedback to know where is the limit between insert and group:
2809 if (m_inserterSplit) {
2810 int noteWidth = rect.width() + (m_inserterGroup ? Note::HANDLE_WIDTH : 0);
2811 int xSplit = rect.x() - (m_inserterGroup ? Note::HANDLE_WIDTH : 0) + noteWidth / 2;
2812 painter.setPen(Tools::mixColor(dark, light));
2813 painter.drawRect(xSplit - 2, rect.y() + lineY, 4, 2);
2814 painter.setPen(dark);
2815 painter.drawRect(xSplit - 1, rect.y() + lineY, 2, 2);
2819 void Basket::maybeTip(const QPoint &pos)
2821 if ( !m_loaded || !Settings::showNotesToolTip() )
2822 return;
2824 QString message;
2825 QRect rect;
2827 QPoint contentPos = viewportToContents(pos);
2828 Note *note = noteAt(contentPos.x(), contentPos.y());
2830 if (!note && isFreeLayout()) {
2831 message = i18n("Insert note here\nRight click for more options");
2832 QRect itRect;
2833 for (Q3ValueList<QRect>::iterator it = m_blankAreas.begin(); it != m_blankAreas.end(); ++it) {
2834 itRect = QRect(0, 0, visibleWidth(), visibleHeight()).intersect(*it);
2835 if (itRect.contains(contentPos)) {
2836 rect = itRect;
2837 rect.moveLeft(rect.left() - contentsX());
2838 rect.moveTop( rect.top() - contentsY());
2839 break;
2842 } else {
2843 if (!note)
2844 return;
2846 Note::Zone zone = note->zoneAt( contentPos - QPoint(note->x(), note->y()) );
2847 switch (zone) {
2848 case Note::Resizer: message = (note->isColumn() ?
2849 i18n("Resize those columns") :
2850 (note->isGroup() ?
2851 i18n("Resize this group") :
2852 i18n("Resize this note"))); break;
2853 case Note::Handle: message = i18n("Select or move this note"); break;
2854 case Note::Group: message = i18n("Select or move this group"); break;
2855 case Note::TagsArrow: message = i18n("Assign or remove tags from this note");
2856 if (note->states().count() > 0) {
2857 message = "<qt><nobr>" + message + "</nobr><br>" + i18n("<b>Assigned Tags</b>: %1");
2858 QString tagsString = "";
2859 for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) {
2860 QString tagName = "<nobr>" + Tools::textToHTMLWithoutP((*it)->fullName()) + "</nobr>";
2861 if (tagsString.isEmpty())
2862 tagsString = tagName;
2863 else
2864 tagsString = i18n("%1, %2").arg(tagsString, tagName);
2866 message = message.arg(tagsString);
2868 break;
2869 case Note::Custom0: message = note->content()->zoneTip(zone); break; //"Open this link/Open this file/Open this sound file/Launch this application"
2870 case Note::GroupExpander: message = (note->isFolded() ?
2871 i18n("Expand this group") :
2872 i18n("Collapse this group")); break;
2873 case Note::Link:
2874 case Note::Content: message = note->content()->editToolTipText(); break;
2875 case Note::TopInsert:
2876 case Note::BottomInsert: message = i18n("Insert note here\nRight click for more options"); break;
2877 case Note::TopGroup: message = i18n("Group note with the one below\nRight click for more options"); break;
2878 case Note::BottomGroup: message = i18n("Group note with the one above\nRight click for more options"); break;
2879 case Note::BottomColumn: message = i18n("Insert note here\nRight click for more options"); break;
2880 case Note::None: message = "** Zone NONE: internal error **"; break;
2881 default:
2882 if (zone >= Note::Emblem0)
2883 message = note->stateForEmblemNumber(zone - Note::Emblem0)->fullName();
2884 else
2885 message = "";
2886 break;
2889 if (zone == Note::Content || zone == Note::Link || zone == Note::Custom0) {
2890 QStringList keys;
2891 QStringList values;
2893 note->content()->toolTipInfos(&keys, &values);
2894 keys.append(i18n("Added"));
2895 keys.append(i18n("Last Modification"));
2896 values.append(note->addedStringDate());
2897 values.append(note->lastModificationStringDate());
2899 message = "<qt><nobr>" + message;
2900 QStringList::iterator key;
2901 QStringList::iterator value;
2902 for (key = keys.begin(), value = values.begin(); key != keys.end() && value != values.end(); ++key, ++value)
2903 message += "<br>" + i18n("of the form 'key: value'", "<b>%1</b>: %2").arg(*key, *value);
2904 message += "</nobr></qt>";
2905 } else if (m_inserterSplit && (zone == Note::TopInsert || zone == Note::BottomInsert))
2906 message += "\n" + i18n("Click on the right to group instead of insert");
2907 else if (m_inserterSplit && (zone == Note::TopGroup || zone == Note::BottomGroup))
2908 message += "\n" + i18n("Click on the left to insert instead of group");
2910 rect = note->zoneRect( zone, contentPos - QPoint(note->x(), note->y()) );
2912 rect.moveLeft(rect.left() - contentsX());
2913 rect.moveTop( rect.top() - contentsY());
2915 rect.moveLeft(rect.left() + note->x());
2916 rect.moveTop( rect.top() + note->y());
2919 tip(rect, message);
2922 Note* Basket::lastNote()
2924 Note *note = firstNote();
2925 while (note && note->next())
2926 note = note->next();
2927 return note;
2930 void Basket::deleteNotes()
2932 Note *note = m_firstNote;
2934 while (note){
2935 Note *tmp = note->next();
2936 delete note;
2937 note = tmp;
2939 m_firstNote = 0;
2940 m_resizingNote = 0;
2941 m_movingNote = 0;
2942 m_focusedNote = 0;
2943 m_startOfShiftSelectionNote = 0;
2944 m_tagPopupNote = 0;
2945 m_clickedToInsert = 0;
2946 m_savedClickedToInsert = 0;
2947 m_hoveredNote = 0;
2948 m_count = 0;
2949 m_countFounds = 0;
2950 m_countSelecteds = 0;
2952 emit resetStatusBarText();
2953 emit countsChanged(this);
2956 Note* Basket::noteAt(int x, int y)
2958 //NO:
2959 // // Do NOT check the bottom&right borders.
2960 // // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars),
2961 // // the note is first removed, and relayoutNotes() compute the new height that is smaller
2962 // // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!!
2963 // // Should, of course, not return 0:
2964 if (x < 0 || x > contentsWidth() || y < 0 || y > contentsHeight())
2965 return 0;
2967 // When resizing a note/group, keep it highlighted:
2968 if (m_resizingNote)
2969 return m_resizingNote;
2971 // Search and return the hovered note:
2972 Note *note = m_firstNote;
2973 Note *possibleNote;
2974 while (note) {
2975 possibleNote = note->noteAt(x, y);
2976 if (possibleNote) {
2977 if (draggedNotes().contains(possibleNote))
2978 return 0;
2979 else
2980 return possibleNote;
2982 note = note->next();
2985 // If the basket is layouted in columns, return one of the columns to be able to add notes in them:
2986 if (isColumnsLayout()) {
2987 Note *column = m_firstNote;
2988 while (column) {
2989 if (x >= column->x() && x < column->rightLimit())
2990 return column;
2991 column = column->next();
2995 // Nothing found, no note is hovered:
2996 return NULL;
2999 Basket::~Basket()
3001 //delete m_action;
3002 if(m_decryptBox)
3003 delete m_decryptBox;
3004 #ifdef HAVE_LIBGPGME
3005 delete m_gpg;
3006 #endif
3007 deleteNotes();
3010 void Basket::viewportResizeEvent(QResizeEvent *event)
3012 relayoutNotes(true);
3013 //cornerWidget()->setShown(horizontalScrollBar()->isShown() && verticalScrollBar()->isShown());
3014 if (horizontalScrollBar()->isShown() && verticalScrollBar()->isShown()) {
3015 if (!cornerWidget())
3016 setCornerWidget(m_cornerWidget);
3017 } else {
3018 if (cornerWidget())
3019 setCornerWidget(0);
3021 // if (isDuringEdit())
3022 // ensureNoteVisible(editedNote());
3023 Q3ScrollView::viewportResizeEvent(event);
3026 void Basket::animateLoad()
3028 const int viewHeight = contentsY() + visibleHeight();
3030 QTime t = QTime::currentTime(); // Set random seed
3031 srand(t.hour()*12 + t.minute()*60 + t.second()*60);
3033 Note *note = firstNote();
3034 while (note) {
3035 if ((note->finalY() < viewHeight) && note->matching())
3036 note->initAnimationLoad();
3037 note = note->next();
3040 m_loaded = true;
3043 QColor Basket::selectionRectInsideColor()
3045 return Tools::mixColor(Tools::mixColor(backgroundColor(), KGlobalSettings::highlightColor()), backgroundColor());
3048 QColor alphaBlendColors(const QColor &bgColor, const QColor &fgColor, const int a)
3050 // normal button...
3051 QRgb rgb = bgColor.rgb();
3052 QRgb rgb_b = fgColor.rgb();
3053 int alpha = a;
3054 if (alpha>255) alpha = 255;
3055 if (alpha<0) alpha = 0;
3056 int inv_alpha = 255 - alpha;
3057 QColor result = QColor( qRgb(qRed(rgb_b)*inv_alpha/255 + qRed(rgb)*alpha/255,
3058 qGreen(rgb_b)*inv_alpha/255 + qGreen(rgb)*alpha/255,
3059 qBlue(rgb_b)*inv_alpha/255 + qBlue(rgb)*alpha/255) );
3061 return result;
3064 void Basket::unlock()
3066 QTimer::singleShot( 0, this, SLOT(load()) );
3069 void Basket::inactivityAutoLockTimeout()
3071 lock();
3074 void Basket::drawContents(QPainter *painter, int clipX, int clipY, int clipWidth, int clipHeight)
3076 // Start the load the first time the basket is shown:
3077 if (!m_loadingLaunched)
3079 if(!m_locked)
3080 QTimer::singleShot( 0, this, SLOT(load()) );
3081 else {
3082 Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar
3086 QBrush brush(backgroundColor()); // FIXME: share it for all the basket?
3087 QRect clipRect(clipX, clipY, clipWidth, clipHeight);
3089 if(m_locked)
3091 if(!m_decryptBox)
3093 m_decryptBox = new Q3Frame( this, "m_decryptBox" );
3094 m_decryptBox->setFrameShape( Q3Frame::StyledPanel );
3095 m_decryptBox->setFrameShadow( Q3Frame::Plain );
3096 m_decryptBox->setLineWidth( 1 );
3098 Q3GridLayout* layout = new Q3GridLayout( m_decryptBox, 1, 1, 11, 6, "decryptBoxLayout");
3100 #ifdef HAVE_LIBGPGME
3101 m_button = new QPushButton( m_decryptBox, "button" );
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, "label" );
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.").arg(kapp->aboutData()->programName()) );
3112 #endif
3113 label->setAlignment( int( Qt::AlignTop ) );
3114 layout->addMultiCellWidget( label, 0, 0, 1, 2 );
3115 QLabel* pixmap = new QLabel( m_decryptBox, "pixmap" );
3116 pixmap->setPixmap( KIconLoader::global()->loadIcon("encrypted", KIconLoader::NoGroup, KIconLoader::SizeHuge) );
3117 layout->addMultiCellWidget( pixmap, 0, 1, 0, 0 );
3119 QSpacerItem* spacer = new QSpacerItem( 40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum );
3120 layout->addItem( spacer, 1, 1 );
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->setFixedWidth(label->sizeHint().width() / 2);
3127 label->setAlignment( int( Qt::AlignTop ) );
3128 layout->addMultiCellWidget( label, 2,2,0,2 );
3130 m_decryptBox->resize(layout->sizeHint());
3132 if(m_decryptBox->isHidden())
3134 m_decryptBox->show();
3136 #ifdef HAVE_LIBGPGME
3137 m_button->setFocus();
3138 #endif
3139 m_decryptBox->move((visibleWidth() - m_decryptBox->width()) / 2,
3140 (visibleHeight() - m_decryptBox->height()) / 2);
3142 else
3144 if(m_decryptBox && m_decryptBox->isShown())
3145 m_decryptBox->hide();
3148 // Draw notes (but not when it's not loaded or locked yet):
3149 Note *note = ((m_loaded || m_locked) ? m_firstNote : 0);
3150 while (note) {
3151 note->draw(painter, clipRect);
3152 note = note->next();
3154 enableActions();
3156 // Draw loading message:
3157 if (!m_loaded) {
3158 QPixmap pixmap(visibleWidth(), visibleHeight()); // TODO: Clip it to asked size only!
3159 QPainter painter2(&pixmap);
3160 Q3SimpleRichText rt(QString("<center>%1</center>").arg(i18n("Loading...")), Q3ScrollView::font());
3161 rt.setWidth(visibleWidth());
3162 int hrt = rt.height();
3163 painter2.fillRect(0, 0, visibleWidth(), visibleHeight(), brush);
3164 blendBackground(painter2, QRect(0, 0, visibleWidth(), visibleHeight()), -1, -1, /*opaque=*/true);
3165 QColorGroup cg = colorGroup();
3166 cg.setColor(QColorGroup::Text, textColor());
3167 rt.draw(&painter2, 0, (visibleHeight() - hrt) / 2, QRect(), cg);
3168 painter2.end();
3169 painter->drawPixmap(0, 0, pixmap);
3170 return; // TODO: Clip to the wanted rectangle
3173 // We will draw blank areas below.
3174 // For each rectangle to be draw there is three ways to do so:
3175 // - The rectangle is full of the background COLOR => we fill a rect directly on screen
3176 // - The rectangle is full of the background PIXMAP => we draw it directly on screen (we draw m_opaqueBackgroundPixmap that is not transparent)
3177 // - The rectangle contains the resizer => We draw it on an offscreen buffer and then paint the buffer on screen
3178 // If the background image is not tiled, we know that recomputeBlankRects() broken rects so that they are full of either background pixmap or color, but not a mix.
3180 // Draw blank areas (see the last preparation above):
3181 QPixmap pixmap;
3182 QPainter painter2;
3183 QRect rect;
3184 for (Q3ValueList<QRect>::iterator it = m_blankAreas.begin(); it != m_blankAreas.end(); ++it) {
3185 rect = clipRect.intersect(*it);
3186 if (rect.width() > 0 && rect.height() > 0) {
3187 // If there is an inserter to draw, draw the image off screen,
3188 // apply the inserter and then draw the image on screen:
3189 if ( (inserterShown() && rect.intersects(inserterRect())) || (m_isSelecting && rect.intersects(m_selectionRect)) ) {
3190 pixmap.resize(rect.width(), rect.height());
3191 painter2.begin(&pixmap);
3192 painter2.fillRect(0, 0, rect.width(), rect.height(), backgroundColor());
3193 blendBackground(painter2, rect, -1, -1, /*opaque=*/true);
3194 // Draw inserter:
3195 if (inserterShown() && rect.intersects(inserterRect()))
3196 drawInserter(painter2, rect.x(), rect.y());
3197 // Draw selection rect:
3198 if (m_isSelecting && rect.intersects(m_selectionRect)) {
3199 QRect selectionRect = m_selectionRect;
3200 selectionRect.moveBy(-rect.x(), -rect.y());
3201 QRect selectionRectInside(selectionRect.x() + 1, selectionRect.y() + 1, selectionRect.width() - 2, selectionRect.height() - 2);
3202 if (selectionRectInside.width() > 0 && selectionRectInside.height() > 0) {
3203 QColor insideColor = selectionRectInsideColor();
3204 painter2.fillRect(selectionRectInside, insideColor);
3205 selectionRectInside.moveBy(rect.x(), rect.y());
3206 blendBackground(painter2, selectionRectInside, rect.x(), rect.y(), true, /*&*/m_selectedBackgroundPixmap);
3208 painter2.setPen(KGlobalSettings::highlightColor().dark());
3209 painter2.drawRect(selectionRect);
3210 painter2.setPen(Tools::mixColor(KGlobalSettings::highlightColor().dark(), backgroundColor()));
3211 painter2.drawPoint(selectionRect.topLeft());
3212 painter2.drawPoint(selectionRect.topRight());
3213 painter2.drawPoint(selectionRect.bottomLeft());
3214 painter2.drawPoint(selectionRect.bottomRight());
3216 painter2.end();
3217 painter->drawPixmap(rect.x(), rect.y(), pixmap);
3218 // If it's only a blank rectangle to draw, draw it directly on screen (faster!!!):
3219 } else if ( ! hasBackgroundImage() ) {
3220 painter->fillRect(rect, backgroundColor());
3221 // It's either a background pixmap to draw or a background color to fill:
3222 } else {
3223 if (isTiledBackground() || (rect.x() < backgroundPixmap()->width() && rect.y() < backgroundPixmap()->height()))
3224 blendBackground(*painter, rect, 0, 0, /*opaque=*/true);
3225 else
3226 painter->fillRect(rect, backgroundColor());
3232 /* rect(x,y,width,height)==(xBackgroundToDraw,yBackgroundToDraw,widthToDraw,heightToDraw)
3234 void Basket::blendBackground(QPainter &painter, const QRect &rect, int xPainter, int yPainter, bool opaque, QPixmap *bg)
3236 if (xPainter == -1 && yPainter == -1) {
3237 xPainter = rect.x();
3238 yPainter = rect.y();
3241 if (hasBackgroundImage()) {
3242 const QPixmap *bgPixmap = (bg ? /* * */bg : (opaque ? m_opaqueBackgroundPixmap : m_backgroundPixmap));
3243 if (isTiledBackground())
3244 painter.drawTiledPixmap(rect.x() - xPainter, rect.y() - yPainter, rect.width(), rect.height(), *bgPixmap, rect.x(), rect.y());
3245 else
3246 painter.drawPixmap(rect.x() - xPainter, rect.y() - yPainter, *bgPixmap, rect.x(), rect.y(), rect.width(), rect.height());
3250 void Basket::recomputeBlankRects()
3252 m_blankAreas.clear();
3253 m_blankAreas.append( QRect(0, 0, contentsWidth(), contentsHeight()) );
3255 FOR_EACH_NOTE (note)
3256 note->recomputeBlankRects(m_blankAreas);
3258 // See the drawing of blank areas in Basket::drawContents()
3259 if (hasBackgroundImage() && ! isTiledBackground())
3260 substractRectOnAreas( QRect(0, 0, backgroundPixmap()->width(), backgroundPixmap()->height()), m_blankAreas, false );
3263 void Basket::addAnimatedNote(Note *note)
3265 if (m_animatedNotes.isEmpty()) {
3266 m_animationTimer.start(FRAME_DELAY);
3267 m_lastFrameTime = QTime::currentTime();
3270 m_animatedNotes.append(note);
3273 void Basket::unsetNotesWidth()
3275 Note *note = m_firstNote;
3276 while (note) {
3277 note->unsetWidth();
3278 note = note->next();
3282 void Basket::relayoutNotes(bool animate)
3284 if (Global::bnpView->currentBasket() != this)
3285 return; // Optimize load time, and basket will be relaid out when activated, anyway
3287 if (!Settings::playAnimations())
3288 animate = false;
3290 if (!animate) {
3291 m_animatedNotes.clear();
3292 m_animationTimer.stop();
3295 int h = 0;
3296 tmpWidth = 0;
3297 tmpHeight = 0;
3298 Note *note = m_firstNote;
3299 while (note) {
3300 if (note->matching()) {
3301 note->relayoutAt(0, h, animate);
3302 if (note->hasResizer()) {
3303 int minGroupWidth = note->minRight() - note->finalX();
3304 if (note->groupWidth() < minGroupWidth) {
3305 note->setGroupWidth(minGroupWidth);
3306 relayoutNotes(animate); // Redo the thing, but this time it should not recurse
3307 return;
3310 h += note->finalHeight();
3312 note = note->next();
3315 if (isFreeLayout())
3316 tmpHeight += 100;
3317 else
3318 tmpHeight += 15;
3320 resizeContents( qMax(tmpWidth, visibleWidth()), qMax(tmpHeight, visibleHeight()) );
3321 recomputeBlankRects();
3322 placeEditor();
3323 doHoverEffects();
3324 updateContents();
3327 void Basket::updateNote(Note *note)
3329 updateContents(note->rect());
3330 if (note->hasResizer())
3331 updateContents(note->resizerRect());
3334 void Basket::animateObjects()
3336 Q3ValueList<Note*>::iterator it;
3337 for (it = m_animatedNotes.begin(); it != m_animatedNotes.end(); )
3338 // if ((*it)->y() >= contentsY() && (*it)->bottom() <= contentsY() + contentsWidth())
3339 // updateNote(*it);
3340 if ((*it)->advance())
3341 it = m_animatedNotes.remove(it);
3342 else {
3343 // if ((*it)->y() >= contentsY() && (*it)->bottom() <= contentsY() + contentsWidth())
3344 // updateNote(*it);
3345 ++it;
3348 if (m_animatedNotes.isEmpty()) {
3349 // Stop animation timer:
3350 m_animationTimer.stop();
3351 // Reset all onTop notes:
3352 Note* note = m_firstNote;
3353 while (note) {
3354 note->setOnTop(false);
3355 note = note->next();
3359 if (m_focusedNote)
3360 ensureNoteVisible(m_focusedNote);
3362 // We refresh content if it was the last frame,
3363 // or if the drawing of the last frame was not too long.
3364 if (!m_animationTimer.isActive() || (m_lastFrameTime.msecsTo(QTime::currentTime()) < FRAME_DELAY*11/10)) { // *11/10 == *1.1 : We keep a 0.1 security margin
3365 m_lastFrameTime = m_lastFrameTime.addMSecs(FRAME_DELAY); // because timers are not accurate and can trigger late
3366 //m_lastFrameTime = QTime::currentTime();
3367 //std::cout << ">>" << m_lastFrameTime.toString("hh:mm:ss.zzz") << std::endl;
3368 if (m_underMouse)
3369 doHoverEffects();
3370 recomputeBlankRects();
3371 //relayoutNotes(true); // In case an animated note was to the contents view boundaries, resize the view!
3372 updateContents();
3373 // If the drawing of the last frame was too long, we skip the drawing of the current and do the next one:
3374 } else {
3375 m_lastFrameTime = m_lastFrameTime.addMSecs(FRAME_DELAY);
3376 //std::cout << "+=" << m_lastFrameTime.toString("hh:mm:ss.zzz") << std::endl;
3377 animateObjects();
3380 doHoverEffects();
3381 placeEditor();
3383 /* int delta = m_deltaY / 3;
3384 if (delta == 0 && m_deltaY != 0)
3385 delta = (m_deltaY > 0 ? 1 : -1);
3386 m_deltaY -= delta;
3387 resizeContents(contentsWidth(), contentsHeight() + delta); //m_lastNote->y() + m_lastNote->height()
3391 void Basket::popupEmblemMenu(Note *note, int emblemNumber)
3393 m_tagPopupNote = note;
3394 State *state = note->stateForEmblemNumber(emblemNumber);
3395 State *nextState = state->nextState(/*cycle=*/false);
3396 Tag *tag = state->parentTag();
3397 m_tagPopup = tag;
3399 QKeySequence sequence = tag->shortcut().operator QKeySequence();
3400 bool sequenceOnDelete = (nextState == 0 && !tag->shortcut().isNull());
3402 KMenu menu(this);
3403 if (tag->countStates() == 1) {
3404 menu.insertTitle(/*SmallIcon(state->icon()), */tag->name());
3405 menu.insertItem( SmallIconSet("editdelete"), i18n("&Remove"), 1 );
3406 menu.insertItem( SmallIconSet("configure"), i18n("&Customize..."), 2 );
3407 menu.insertSeparator();
3408 menu.insertItem( SmallIconSet("filter"), i18n("&Filter by this Tag"), 3 );
3409 } else {
3410 menu.insertTitle(tag->name());
3411 Q3ValueList<State*>::iterator it;
3412 State *currentState;
3414 int i = 10;
3415 for (it = tag->states().begin(); it != tag->states().end(); ++it) {
3416 currentState = *it;
3417 QKeySequence sequence;
3418 if (currentState == nextState && !tag->shortcut().isNull())
3419 sequence = tag->shortcut().operator QKeySequence();
3420 menu.insertItem(StateMenuItem::radioButtonIconSet(state == currentState, menu.colorGroup()), new StateMenuItem(currentState, sequence, false), i );
3421 if (currentState == nextState && !tag->shortcut().isNull())
3422 menu.setAccel(sequence, i);
3423 ++i;
3425 menu.insertSeparator();
3426 menu.insertItem( new IndentedMenuItem(i18n("&Remove"), "editdelete", (sequenceOnDelete ? sequence : QKeySequence())), 1 );
3427 menu.insertItem( new IndentedMenuItem(i18n("&Customize..."), "configure"), 2 );
3428 menu.insertSeparator();
3429 menu.insertItem( new IndentedMenuItem(i18n("&Filter by this Tag"), "filter"), 3 );
3430 menu.insertItem( new IndentedMenuItem(i18n("Filter by this &State"), "filter"), 4 );
3432 if (sequenceOnDelete)
3433 menu.setAccel(sequence, 1);
3435 connect( &menu, SIGNAL(activated(int)), this, SLOT(toggledStateInMenu(int)) );
3436 connect( &menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering()) );
3437 connect( &menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick()) );
3439 m_lockedHovering = true;
3440 menu.exec(QCursor::pos());
3443 void Basket::toggledStateInMenu(int id)
3445 if (id == 1) {
3446 removeTagFromSelectedNotes(m_tagPopup);
3447 //m_tagPopupNote->removeTag(m_tagPopup);
3448 //m_tagPopupNote->setWidth(0); // To force a new layout computation
3449 updateEditorAppearance();
3450 filterAgain();
3451 save();
3452 return;
3454 if (id == 2) { // Customize this State:
3455 TagsEditDialog dialog(this, m_tagPopupNote->stateOfTag(m_tagPopup));
3456 dialog.exec();
3457 return;
3459 if (id == 3) { // Filter by this Tag
3460 decoration()->filterBar()->filterTag(m_tagPopup);
3461 return;
3463 if (id == 4) { // Filter by this State
3464 decoration()->filterBar()->filterState(m_tagPopupNote->stateOfTag(m_tagPopup));
3465 return;
3468 /*addStateToSelectedNotes*/changeStateOfSelectedNotes(m_tagPopup->states()[id - 10] /*, orReplace=true*/);
3469 //m_tagPopupNote->addState(m_tagPopup->states()[id - 10], true);
3470 filterAgain();
3471 save();
3474 State* Basket::stateForTagFromSelectedNotes(Tag *tag)
3476 State *state = 0;
3478 FOR_EACH_NOTE (note)
3479 if (note->stateForTagFromSelectedNotes(tag, &state) && state == 0)
3480 return 0;
3481 return state;
3484 void Basket::activatedTagShortcut(Tag *tag)
3486 // Compute the next state to set:
3487 State *state = stateForTagFromSelectedNotes(tag);
3488 if (state)
3489 state = state->nextState(/*cycle=*/false);
3490 else
3491 state = tag->states().first();
3493 // Set or unset it:
3494 if (state) {
3495 FOR_EACH_NOTE (note)
3496 note->addStateToSelectedNotes(state, /*orReplace=*/true);
3497 updateEditorAppearance();
3498 } else
3499 removeTagFromSelectedNotes(tag);
3501 filterAgain();
3502 save();
3505 void Basket::popupTagsMenu(Note *note)
3507 m_tagPopupNote = note;
3509 KMenu menu(this);
3510 menu.insertTitle(i18n("Tags"));
3511 // QValueList<Tag*>::iterator it;
3512 // Tag *currentTag;
3513 // State *currentState;
3514 // int i = 10;
3515 // for (it = Tag::all.begin(); it != Tag::all.end(); ++it) {
3516 // // Current tag and first state of it:
3517 // currentTag = *it;
3518 // currentState = currentTag->states().first();
3519 // QKeySequence sequence;
3520 // if (!currentTag->shortcut().isNull())
3521 // sequence = currentTag->shortcut().operator QKeySequence();
3522 // menu.insertItem(StateMenuItem::checkBoxIconSet(note->hasTag(currentTag), menu.colorGroup()), new StateMenuItem(currentState, sequence, true), i );
3523 // if (!currentTag->shortcut().isNull())
3524 // menu.setAccel(sequence, i);
3525 // ++i;
3526 // }
3528 // menu.insertSeparator();
3529 // // menu.insertItem( /*SmallIconSet("editdelete"),*/ "&Assign New Tag...", 1 );
3530 // //id = menu.insertItem( SmallIconSet("editdelete"), "&Remove All", -2 );
3531 // //if (note->states().isEmpty())
3532 // // menu.setItemEnabled(id, false);
3533 // // menu.insertItem( SmallIconSet("configure"), "&Customize...", 3 );
3534 // menu.insertItem( new IndentedMenuItem(i18n("&Assign New Tag...")), 1 );
3535 // menu.insertItem( new IndentedMenuItem(i18n("&Remove All"), "editdelete"), 2 );
3536 // menu.insertItem( new IndentedMenuItem(i18n("&Customize..."), "configure"), 3 );
3538 // if (!selectedNotesHaveTags())//note->states().isEmpty())
3539 // menu.setItemEnabled(2, false);
3541 // connect( &menu, SIGNAL(activated(int)), this, SLOT(toggledTagInMenu(int)) );
3542 // connect( &menu, SIGNAL(aboutToHide()), this, SLOT(unlockHovering()) );
3543 // connect( &menu, SIGNAL(aboutToHide()), this, SLOT(disableNextClick()) );
3545 Global::bnpView->populateTagsMenu(menu, note);
3547 m_lockedHovering = true;
3548 menu.exec(QCursor::pos());
3551 void Basket::unlockHovering()
3553 m_lockedHovering = false;
3554 doHoverEffects();
3557 void Basket::toggledTagInMenu(int id)
3559 if (id == 1) { // Assign new Tag...
3560 TagsEditDialog dialog(this, /*stateToEdit=*/0, /*addNewTag=*/true);
3561 dialog.exec();
3562 if (!dialog.addedStates().isEmpty()) {
3563 State::List states = dialog.addedStates();
3564 for (State::List::iterator itState = states.begin(); itState != states.end(); ++itState)
3565 FOR_EACH_NOTE (note)
3566 note->addStateToSelectedNotes(*itState);
3567 updateEditorAppearance();
3568 filterAgain();
3569 save();
3571 return;
3573 if (id == 2) { // Remove All
3574 removeAllTagsFromSelectedNotes();
3575 filterAgain();
3576 save();
3577 return;
3579 if (id == 3) { // Customize...
3580 TagsEditDialog dialog(this);
3581 dialog.exec();
3582 return;
3585 Tag *tag = Tag::all[id - 10];
3586 if (!tag)
3587 return;
3589 if (m_tagPopupNote->hasTag(tag))
3590 removeTagFromSelectedNotes(tag);
3591 else
3592 addTagToSelectedNotes(tag);
3593 m_tagPopupNote->setWidth(0); // To force a new layout computation
3594 filterAgain();
3595 save();
3598 void Basket::addTagToSelectedNotes(Tag *tag)
3600 FOR_EACH_NOTE (note)
3601 note->addTagToSelectedNotes(tag);
3602 updateEditorAppearance();
3605 void Basket::removeTagFromSelectedNotes(Tag *tag)
3607 FOR_EACH_NOTE (note)
3608 note->removeTagFromSelectedNotes(tag);
3609 updateEditorAppearance();
3612 void Basket::addStateToSelectedNotes(State *state)
3614 FOR_EACH_NOTE (note)
3615 note->addStateToSelectedNotes(state);
3616 updateEditorAppearance();
3619 void Basket::updateEditorAppearance()
3621 if (isDuringEdit() && m_editor->widget()) {
3622 m_editor->widget()->setFont(m_editor->note()->font());
3623 m_editor->widget()->setPaletteBackgroundColor(m_editor->note()->backgroundColor());
3624 m_editor->widget()->setPaletteForegroundColor(m_editor->note()->textColor());
3626 // Uggly Hack arround Qt bugs: placeCursor() don't call any signal:
3627 HtmlEditor *htmlEditor = dynamic_cast<HtmlEditor*>(m_editor);
3628 if (htmlEditor) {
3629 int para, index;
3630 m_editor->textEdit()->getCursorPosition(&para, &index);
3631 if (para == 0 && index == 0) {
3632 m_editor->textEdit()->moveCursor(Q3TextEdit::MoveForward, /*select=*/false);
3633 m_editor->textEdit()->moveCursor(Q3TextEdit::MoveBackward, /*select=*/false);
3634 } else {
3635 m_editor->textEdit()->moveCursor(Q3TextEdit::MoveBackward, /*select=*/false);
3636 m_editor->textEdit()->moveCursor(Q3TextEdit::MoveForward, /*select=*/false);
3638 htmlEditor->cursorPositionChanged(); // Does not work anyway :-( (when clicking on a red bold text, the toolbar still show black normal text)
3643 void Basket::editorPropertiesChanged()
3645 if (isDuringEdit() && m_editor->note()->content()->type() == NoteType::Html) {
3646 m_editor->textEdit()->setAutoFormatting(Settings::autoBullet() ? Q3TextEdit::AutoAll : Q3TextEdit::AutoNone);
3650 void Basket::changeStateOfSelectedNotes(State *state)
3652 FOR_EACH_NOTE (note)
3653 note->changeStateOfSelectedNotes(state);
3654 updateEditorAppearance();
3657 void Basket::removeAllTagsFromSelectedNotes()
3659 FOR_EACH_NOTE (note)
3660 note->removeAllTagsFromSelectedNotes();
3661 updateEditorAppearance();
3664 bool Basket::selectedNotesHaveTags()
3666 FOR_EACH_NOTE (note)
3667 if (note->selectedNotesHaveTags())
3668 return true;
3669 return false;
3672 QColor Basket::backgroundColor()
3674 if (m_backgroundColorSetting.isValid())
3675 return m_backgroundColorSetting;
3676 else
3677 return KGlobalSettings::baseColor();
3680 QColor Basket::textColor()
3682 if (m_textColorSetting.isValid())
3683 return m_textColorSetting;
3684 else
3685 return KGlobalSettings::textColor();
3688 void Basket::unbufferizeAll()
3690 FOR_EACH_NOTE (note)
3691 note->unbufferizeAll();
3694 Note* Basket::editedNote()
3696 if (m_editor)
3697 return m_editor->note();
3698 else
3699 return 0;
3702 bool Basket::hasTextInEditor()
3704 if (!isDuringEdit() || !redirectEditActions())
3705 return false;
3707 if (m_editor->textEdit())
3708 return ! m_editor->textEdit()->text().isEmpty();
3709 else if (m_editor->lineEdit())
3710 return ! m_editor->lineEdit()->text().isEmpty();
3711 else
3712 return false;
3715 bool Basket::hasSelectedTextInEditor()
3717 if (!isDuringEdit() || !redirectEditActions())
3718 return false;
3720 if (m_editor->textEdit()) {
3721 // The following line does NOT work if one letter is selected and the user press Shift+Left or Shift+Right to unselect than letter:
3722 // Qt misteriously tell us there is an invisible selection!!
3723 //return m_editor->textEdit()->hasSelectedText();
3724 return !m_editor->textEdit()->selectedText().isEmpty();
3725 } else if (m_editor->lineEdit())
3726 return m_editor->lineEdit()->hasSelectedText();
3727 else
3728 return false;
3731 bool Basket::selectedAllTextInEditor()
3733 if (!isDuringEdit() || !redirectEditActions())
3734 return false;
3736 if (m_editor->textEdit())
3737 return m_editor->textEdit()->text().isEmpty() || m_editor->textEdit()->text() == m_editor->textEdit()->selectedText();
3738 else if (m_editor->lineEdit())
3739 return m_editor->lineEdit()->text().isEmpty() || m_editor->lineEdit()->text() == m_editor->lineEdit()->selectedText();
3740 else
3741 return false;
3744 void Basket::selectionChangedInEditor()
3746 Global::bnpView->notesStateChanged();
3749 void Basket::contentChangedInEditor()
3751 // 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):
3752 if (m_editor->textEdit())
3753 m_editor->autoSave(/*toFileToo=*/false);
3754 // else {
3755 if (m_inactivityAutoSaveTimer.isActive())
3756 m_inactivityAutoSaveTimer.stop();
3757 m_inactivityAutoSaveTimer.start(3 * 1000, /*singleShot=*/true);
3758 Global::bnpView->setUnsavedStatus(true);
3759 // }
3762 void Basket::inactivityAutoSaveTimeout()
3764 if (m_editor)
3765 m_editor->autoSave(/*toFileToo=*/true);
3768 void Basket::placeEditorAndEnsureVisible()
3770 placeEditor(/*andEnsureVisible=*/true);
3773 void Basket::placeEditor(bool /*andEnsureVisible*/ /*= false*/)
3775 if (!isDuringEdit())
3776 return;
3778 Q3Frame *editorQFrame = dynamic_cast<Q3Frame*>(m_editor->widget());
3779 KTextEdit *textEdit = m_editor->textEdit();
3780 // QLineEdit *lineEdit = m_editor->lineEdit();
3781 Note *note = m_editor->note();
3783 int frameWidth = (editorQFrame ? editorQFrame->frameWidth() : 0);
3784 int x = note->x() + note->contentX() + note->content()->xEditorIndent() - frameWidth;
3785 int y;
3786 int maxHeight = qMax(visibleHeight(), contentsHeight());
3787 int height, width;
3789 if (textEdit) {
3790 x -= 4;
3791 // Need to do it 2 times, because it's wrong overwise
3792 // (sometimes, width depends on height, and sometimes, height depends on with):
3793 for (int i = 0; i < 2; i++) {
3794 // FIXME: CRASH: Select all text, press Del or [<--] and editor->removeSelectedText() is called:
3795 // editor->sync() CRASH!!
3796 // editor->sync();
3797 y = note->y() + Note::NOTE_MARGIN - frameWidth;
3798 height = textEdit->contentsHeight() + 2*frameWidth;
3799 // height = /*qMax(*/height/*, note->height())*/;
3800 // height = qMin(height, visibleHeight());
3801 width = note->x() + note->width() - x + 1;// /*note->x() + note->width()*/note->rightLimit() - x + 2*frameWidth + 1;
3802 //width=qMax(width,textEdit->contentsWidth()+2*frameWidth);
3803 if (y + height > maxHeight)
3804 y = maxHeight - height;
3805 textEdit->setFixedSize(width, height);
3807 } else {
3808 height = note->height() - 2*Note::NOTE_MARGIN + 2*frameWidth;
3809 width = note->x() + note->width() - x;//note->rightLimit() - x + 2*frameWidth;
3810 m_editor->widget()->setFixedSize(width, height);
3811 x -= 1;
3812 y = note->y() + Note::NOTE_MARGIN - frameWidth;
3814 if ((m_editorWidth > 0 && m_editorWidth != width) || (m_editorHeight > 0 && m_editorHeight != height)) {
3815 m_editorWidth = width; // Avoid infinite recursion!!!
3816 m_editorHeight = height;
3817 m_editor->autoSave(/*toFileToo=*/true);
3819 m_editorWidth = width;
3820 m_editorHeight = height;
3821 addChild(m_editor->widget(), x, y);
3822 m_editorX = x;
3823 m_editorY = y;
3825 m_leftEditorBorder->setFixedSize( (m_editor->textEdit() ? 3 : 0), height);
3826 // m_leftEditorBorder->raise();
3827 addChild(m_leftEditorBorder, x, y );
3828 m_leftEditorBorder->setPosition( x, y );
3830 m_rightEditorBorder->setFixedSize(3, height);
3831 // m_rightEditorBorder->raise();
3832 // addChild(m_rightEditorBorder, note->rightLimit() - Note::NOTE_MARGIN, note->y() + Note::NOTE_MARGIN );
3833 // m_rightEditorBorder->setPosition( note->rightLimit() - Note::NOTE_MARGIN, note->y() + Note::NOTE_MARGIN );
3834 addChild(m_rightEditorBorder, note->x() + note->width() - Note::NOTE_MARGIN, note->y() + Note::NOTE_MARGIN );
3835 m_rightEditorBorder->setPosition( note->x() + note->width() - Note::NOTE_MARGIN, note->y() + Note::NOTE_MARGIN );
3837 // if (andEnsureVisible)
3838 // ensureNoteVisible(note);
3841 #include <iostream>
3842 #include <private/qrichtext_p.h>
3843 void Basket::editorCursorPositionChanged()
3845 if (!isDuringEdit())
3846 return;
3848 FocusedTextEdit *textEdit = (FocusedTextEdit*) m_editor->textEdit();
3849 const QTextCursor *cursor = textEdit->textCursor();
3850 // std::cout << cursor->x() << ";" << cursor->y() << " "
3851 // << cursor->globalX() << ";" << cursor->globalY() << " "
3852 // << cursor->offsetX() << ";" << cursor->offsetY() << ";" << std::endl;
3854 ensureVisible(m_editorX + cursor->globalX(), m_editorY + cursor->globalY(), 50, 50);
3857 void Basket::closeEditorDelayed()
3859 setFocus();
3860 QTimer::singleShot( 0, this, SLOT(closeEditor()) );
3863 bool Basket::closeEditor()
3865 if (!isDuringEdit())
3866 return true;
3868 if (m_doNotCloseEditor)
3869 return true;
3871 if (m_redirectEditActions) {
3872 disconnect( m_editor->widget(), SIGNAL(selectionChanged()), this, SLOT(selectionChangedInEditor()) );
3873 if (m_editor->textEdit()) {
3874 disconnect( m_editor->textEdit(), SIGNAL(textChanged()), this, SLOT(selectionChangedInEditor()) );
3875 disconnect( m_editor->textEdit(), SIGNAL(textChanged()), this, SLOT(contentChangedInEditor()) );
3876 } else if (m_editor->lineEdit()) {
3877 disconnect( m_editor->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(selectionChangedInEditor()) );
3878 disconnect( m_editor->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(contentChangedInEditor()) );
3881 m_editor->widget()->disconnect();
3882 m_editor->widget()->hide();
3883 m_editor->validate();
3885 delete m_leftEditorBorder;
3886 delete m_rightEditorBorder;
3887 m_leftEditorBorder = 0;
3888 m_rightEditorBorder = 0;
3890 Note *note = m_editor->note();
3891 note->setWidth(0); // For relayoutNotes() to succeed to take care of the change
3893 // Delete the editor BEFORE unselecting the note because unselecting the note would trigger closeEditor() recursivly:
3894 bool isEmpty = m_editor->isEmpty();
3895 delete m_editor;
3896 m_editor = 0;
3897 m_redirectEditActions = false;
3898 m_editorWidth = -1;
3899 m_editorHeight = -1;
3900 m_inactivityAutoSaveTimer.stop();
3902 // Delete the note if it is now empty:
3903 if (isEmpty) {
3904 focusANonSelectedNoteAboveOrThenBelow();
3905 note->setSelected(true);
3906 note->deleteSelectedNotes();
3907 save();
3908 note = 0;
3911 unlockHovering();
3912 filterAgain(/*andEnsureVisible=*/false);
3914 // Does not work:
3915 // if (Settings::playAnimations())
3916 // note->setOnTop(true); // So if it grew, do not obscure it temporarily while the notes below it are moving
3918 if (note)
3919 note->setSelected(false);//unselectAll();
3920 doHoverEffects();
3921 // save();
3923 Global::bnpView->m_actEditNote->setEnabled( !isLocked() && countSelecteds() == 1 /*&& !isDuringEdit()*/ );
3925 emit resetStatusBarText(); // Remove the "Editing. ... to validate." text.
3927 //if (kapp->activeWindow() == Global::mainContainer)
3929 // 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:
3930 if (!decoration()->filterBar()->lineEdit()->hasFocus())
3931 setFocus();
3933 // Return true if the note is still there:
3934 return (note != 0);
3937 void Basket::closeBasket()
3939 closeEditor();
3940 unbufferizeAll(); // Keep the memory footprint low
3941 if (isEncrypted()) {
3942 if (Settings::enableReLockTimeout()) {
3943 int seconds = Settings::reLockTimeoutMinutes() * 60;
3944 m_inactivityAutoLockTimer.start(seconds * 1000, /*singleShot=*/true);
3949 void Basket::openBasket()
3951 if (m_inactivityAutoLockTimer.isActive())
3952 m_inactivityAutoLockTimer.stop();
3955 Note* Basket::theSelectedNote()
3957 if (countSelecteds() != 1) {
3958 std::cout << "NO SELECTED NOTE !!!!" << std::endl;
3959 return 0;
3962 Note *selectedOne;
3963 FOR_EACH_NOTE (note) {
3964 selectedOne = note->theSelectedNote();
3965 if (selectedOne)
3966 return selectedOne;
3969 std::cout << "One selected note, BUT NOT FOUND !!!!" << std::endl;
3971 return 0;
3974 void debugSel(NoteSelection* sel, int n = 0)
3976 for (NoteSelection *node = sel; node; node = node->next) {
3977 for (int i = 0; i < n; i++)
3978 std::cout << "-";
3979 std::cout << (node->firstChild ? "Group" : node->note->content()->toText("")) << std::endl;
3980 if (node->firstChild)
3981 debugSel(node->firstChild, n+1);
3985 NoteSelection* Basket::selectedNotes()
3987 NoteSelection selection;
3989 FOR_EACH_NOTE (note)
3990 selection.append(note->selectedNotes());
3992 if (!selection.firstChild)
3993 return 0;
3995 for (NoteSelection *node = selection.firstChild; node; node = node->next)
3996 node->parent = 0;
3998 // If the top-most groups are columns, export only childs of those groups
3999 // (because user is not consciencious that columns are groups, and don't care: it's not what she want):
4000 if (selection.firstChild->note->isColumn()) {
4001 NoteSelection tmpSelection;
4002 NoteSelection *nextNode;
4003 NoteSelection *nextSubNode;
4004 for (NoteSelection *node = selection.firstChild; node; node = nextNode) {
4005 nextNode = node->next;
4006 if (node->note->isColumn()) {
4007 for (NoteSelection *subNode = node->firstChild; subNode; subNode = nextSubNode) {
4008 nextSubNode = subNode->next;
4009 tmpSelection.append(subNode);
4010 subNode->parent = 0;
4011 subNode->next = 0;
4013 } else {
4014 tmpSelection.append(node);
4015 node->parent = 0;
4016 node->next = 0;
4019 // debugSel(tmpSelection.firstChild);
4020 return tmpSelection.firstChild;
4021 } else {
4022 // debugSel(selection.firstChild);
4023 return selection.firstChild;
4027 void Basket::showEditedNoteWhileFiltering()
4029 if (m_editor) {
4030 Note *note = m_editor->note();
4031 filterAgain();
4032 note->setSelected(true);
4033 relayoutNotes(false);
4034 note->setX(note->finalX());
4035 note->setY(note->finalY());
4036 filterAgainDelayed();
4040 void Basket::noteEdit(Note *note, bool justAdded, const QPoint &clickedPoint) // TODO: Remove the first parameter!!!
4042 if (!note)
4043 note = theSelectedNote(); // TODO: Or pick the focused note!
4044 if (!note)
4045 return;
4047 if (isDuringEdit()) {
4048 closeEditor(); // Validate the noteeditors in KLineEdit that does not intercept Enter key press (and edit is triggered with Enter too... Can conflict)
4049 return;
4052 if (note != m_focusedNote) {
4053 setFocusedNote(note);
4054 m_startOfShiftSelectionNote = note;
4057 if (justAdded && isFiltering()) {
4058 QTimer::singleShot( 0, this, SLOT(showEditedNoteWhileFiltering()) );
4061 doHoverEffects(note, Note::Content); // Be sure (in the case Edit was triggered by menu or Enter key...): better feedback!
4062 //m_lockedHovering = true;
4064 //m_editorWidget = note->content()->launchEdit(this);
4065 NoteEditor *editor = NoteEditor::editNoteContent(note->content(), this);
4067 if (editor->widget()) {
4068 m_editor = editor;
4069 m_leftEditorBorder = new TransparentWidget(this);
4070 m_rightEditorBorder = new TransparentWidget(this);
4071 m_editor->widget()->reparent(viewport(), QPoint(0,0), true);
4072 m_leftEditorBorder->reparent(viewport(), QPoint(0,0), true);
4073 m_rightEditorBorder->reparent(viewport(), QPoint(0,0), true);
4074 addChild(m_editor->widget(), 0, 0);
4075 placeEditorAndEnsureVisible(); // placeEditor(); // FIXME: After?
4076 m_redirectEditActions = m_editor->lineEdit() || m_editor->textEdit();
4077 if (m_redirectEditActions) {
4078 connect( m_editor->widget(), SIGNAL(selectionChanged()), this, SLOT(selectionChangedInEditor()) );
4079 // In case there is NO text, "Select All" is disabled. But if the user press a key the there is now a text:
4080 // selection has not changed but "Select All" should be re-enabled:
4081 if (m_editor->textEdit()) {
4082 connect( m_editor->textEdit(), SIGNAL(textChanged()), this, SLOT(selectionChangedInEditor()) );
4083 connect( m_editor->textEdit(), SIGNAL(textChanged()), this, SLOT(contentChangedInEditor()) );
4084 } else if (m_editor->lineEdit()) {
4085 connect( m_editor->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(selectionChangedInEditor()) );
4086 connect( m_editor->lineEdit(), SIGNAL(textChanged(const QString&)), this, SLOT(contentChangedInEditor()) );
4089 m_editor->widget()->show();
4090 //m_editor->widget()->raise();
4091 m_editor->widget()->setFocus();
4092 connect( m_editor, SIGNAL(askValidation()), this, SLOT(closeEditorDelayed()) );
4093 connect( m_editor, SIGNAL(mouseEnteredEditorWidget()), this, SLOT(mouseEnteredEditorWidget()) );
4094 if (m_editor->textEdit()) {
4095 connect( m_editor->textEdit(), SIGNAL(textChanged()), this, SLOT(placeEditorAndEnsureVisible()) );
4096 if (clickedPoint != QPoint()) {
4097 QPoint pos(clickedPoint.x() - note->x() - note->contentX() + m_editor->textEdit()->frameWidth() + 4 - m_editor->textEdit()->frameWidth(),
4098 clickedPoint.y() - note->y() - m_editor->textEdit()->frameWidth());
4099 // Do it right before the kapp->processEvents() to not have the cursor to quickly flicker at end (and sometimes stay at end AND where clicked):
4100 m_editor->textEdit()->moveCursor(KTextEdit::MoveHome, false);
4101 m_editor->textEdit()->ensureCursorVisible();
4102 m_editor->textEdit()->placeCursor(pos);
4103 updateEditorAppearance();
4106 // kapp->processEvents(); // Show the editor toolbar before ensuring the note is visible
4107 ensureNoteVisible(note); // because toolbar can create a new line and then partially hide the note
4108 m_editor->widget()->setFocus(); // When clicking in the basket, a QTimer::singleShot(0, ...) focus the basket! So we focus the the widget after kapp->processEvents()
4109 emit resetStatusBarText(); // Display "Editing. ... to validate."
4110 } else {
4111 // Delete the note user have canceled the addition:
4112 if ((justAdded && editor->canceled()) || editor->isEmpty() /*) && editor->note()->states().count() <= 0*/) {
4113 focusANonSelectedNoteAboveOrThenBelow();
4114 editor->note()->setSelected(true);
4115 editor->note()->deleteSelectedNotes();
4116 save();
4118 delete editor;
4119 unlockHovering();
4120 filterAgain();
4121 unselectAll();
4123 Global::bnpView->m_actEditNote->setEnabled(false);
4126 void Basket::noteDelete()
4128 if (redirectEditActions()) {
4129 if (m_editor->textEdit())
4130 m_editor->textEdit()->del();
4131 else if (m_editor->lineEdit())
4132 m_editor->lineEdit()->del();
4133 return;
4136 if (countSelecteds() <= 0)
4137 return;
4138 int really = KMessageBox::Yes;
4139 if (Settings::confirmNoteDeletion())
4140 really = KMessageBox::questionYesNo( this,
4141 i18n("<qt>Do you really want to delete this note?</qt>",
4142 "<qt>Do you really want to delete those <b>%n</b> notes?</qt>",
4143 countSelecteds()),
4144 i18n("Delete Note", "Delete Notes", countSelecteds())
4145 #if KDE_IS_VERSION( 3, 2, 90 ) // KDE 3.3.x
4146 , KStandardGuiItem::del(), KStandardGuiItem::cancel());
4147 #else
4149 #endif
4150 if (really == KMessageBox::No)
4151 return;
4153 noteDeleteWithoutConfirmation();
4156 void Basket::focusANonSelectedNoteBelow(bool inSameColumn)
4158 // First focus another unselected one below it...:
4159 if (m_focusedNote && m_focusedNote->isSelected()) {
4160 Note *next = m_focusedNote->nextShownInStack();
4161 while (next && next->isSelected())
4162 next = next->nextShownInStack();
4163 if (next) {
4164 if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == next->parentPrimaryNote()) {
4165 setFocusedNote(next);
4166 m_startOfShiftSelectionNote = next;
4172 void Basket::focusANonSelectedNoteAbove(bool inSameColumn)
4174 // ... Or above it:
4175 if (m_focusedNote && m_focusedNote->isSelected()) {
4176 Note *prev = m_focusedNote->prevShownInStack();
4177 while (prev && prev->isSelected())
4178 prev = prev->prevShownInStack();
4179 if (prev) {
4180 if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == prev->parentPrimaryNote()) {
4181 setFocusedNote(prev);
4182 m_startOfShiftSelectionNote = prev;
4188 void Basket::focusANonSelectedNoteBelowOrThenAbove()
4190 focusANonSelectedNoteBelow(/*inSameColumn=*/true);
4191 focusANonSelectedNoteAbove(/*inSameColumn=*/true);
4192 focusANonSelectedNoteBelow(/*inSameColumn=*/false);
4193 focusANonSelectedNoteAbove(/*inSameColumn=*/false);
4196 void Basket::focusANonSelectedNoteAboveOrThenBelow()
4198 focusANonSelectedNoteAbove(/*inSameColumn=*/true);
4199 focusANonSelectedNoteBelow(/*inSameColumn=*/true);
4200 focusANonSelectedNoteAbove(/*inSameColumn=*/false);
4201 focusANonSelectedNoteBelow(/*inSameColumn=*/false);
4204 void Basket::noteDeleteWithoutConfirmation(bool deleteFilesToo)
4206 // If the currently focused note is selected, it will be deleted.
4207 focusANonSelectedNoteBelowOrThenAbove();
4209 // Do the deletion:
4210 Note *note = firstNote();
4211 Note *next;
4212 while (note) {
4213 next = note->next(); // If we delete 'note' on the next line, note->next() will be 0!
4214 note->deleteSelectedNotes(deleteFilesToo);
4215 note = next;
4218 relayoutNotes(true); // FIXME: filterAgain()?
4219 save();
4222 void Basket::doCopy(CopyMode copyMode)
4224 QClipboard *cb = KApplication::clipboard();
4225 QClipboard::Mode mode = (copyMode == CopyToSelection ? QClipboard::Selection : QClipboard::Clipboard);
4227 NoteSelection *selection = selectedNotes();
4228 int countCopied = countSelecteds();
4229 if (selection->firstStacked()) {
4230 Q3DragObject *d = NoteDrag::dragObject(selection, copyMode == CutToClipboard, /*source=*/0); // d will be deleted by QT
4231 // /*bool shouldRemove = */d->drag();
4232 // delete selection;
4233 cb->setData(d, mode); // NoteMultipleDrag will be deleted by QT
4234 // if (copyMode == CutToClipboard && !note->useFile()) // If useFile(), NoteDrag::dragObject() will delete it TODO
4235 // note->slotDelete();
4237 if (copyMode == CutToClipboard)
4238 noteDeleteWithoutConfirmation(/*deleteFilesToo=*/false);
4240 switch (copyMode) {
4241 default:
4242 case CopyToClipboard: emit postMessage(i18n("Copied note to clipboard.", "Copied notes to clipboard.", countCopied)); break;
4243 case CutToClipboard: emit postMessage(i18n("Cut note to clipboard.", "Cut notes to clipboard.", countCopied)); break;
4244 case CopyToSelection: emit postMessage(i18n("Copied note to selection.", "Copied notes to selection.", countCopied)); break;
4249 void Basket::noteCopy()
4251 if (redirectEditActions()) {
4252 if (m_editor->textEdit())
4253 m_editor->textEdit()->copy();
4254 else if (m_editor->lineEdit())
4255 m_editor->lineEdit()->copy();
4256 } else
4257 doCopy(CopyToClipboard);
4260 void Basket::noteCut()
4262 if (redirectEditActions()) {
4263 if (m_editor->textEdit())
4264 m_editor->textEdit()->cut();
4265 else if (m_editor->lineEdit())
4266 m_editor->lineEdit()->cut();
4267 } else
4268 doCopy(CutToClipboard);
4271 void Basket::noteOpen(Note *note)
4274 GetSelectedNotes
4275 NoSelectedNote || Count == 0 ? return
4276 AllTheSameType ?
4277 Get { url, message(count) }
4280 // TODO: Open ALL selected notes!
4281 if (!note)
4282 note = theSelectedNote();
4283 if (!note)
4284 return;
4286 KUrl url = note->content()->urlToOpen(/*with=*/false);
4287 QString message = note->content()->messageWhenOpenning(NoteContent::OpenOne /*NoteContent::OpenSeveral*/);
4288 if (url.isEmpty()) {
4289 if (message.isEmpty())
4290 emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/);
4291 else {
4292 int result = KMessageBox::warningContinueCancel(this, message, /*caption=*/QString::null, KGuiItem(i18n("&Edit"), "edit"));
4293 if (result == KMessageBox::Continue)
4294 noteEdit(note);
4296 } else {
4297 emit postMessage(message); // "Openning link target..." / "Launching application..." / "Openning note file..."
4298 // Finally do the opening job:
4299 QString customCommand = note->content()->customOpenCommand();
4300 if (customCommand.isEmpty()) {
4301 KRun *run = new KRun(url);
4302 run->setAutoDelete(true);
4303 } else
4304 KRun::run(customCommand, url);
4308 /** Code from bool KRun::displayOpenWithDialog(const KUrl::List& lst, bool tempFiles)
4309 * It does not allow to set a text, so I ripped it to do that:
4311 bool KRun__displayOpenWithDialog(const KUrl::List& lst, bool tempFiles, const QString &text)
4313 if (kapp && !KAuthorized::authorizeKAction("openwith")) {
4314 KMessageBox::sorry(0L, i18n("You are not authorized to open this file.")); // TODO: Better message, i18n freeze :-(
4315 return false;
4317 KOpenWithDlg l(lst, text, QString::null, 0L);
4318 if (l.exec()) {
4319 KService::Ptr service = l.service();
4320 if (!!service)
4321 return KRun::run(*service, lst, tempFiles);
4322 //kDebug(250) << "No service set, running " << l.text() << endl;
4323 return KRun::run(l.text(), lst); // TODO handle tempFiles
4325 return false;
4328 void Basket::noteOpenWith(Note *note)
4330 if (!note)
4331 note = theSelectedNote();
4332 if (!note)
4333 return;
4335 KUrl url = note->content()->urlToOpen(/*with=*/true);
4336 QString message = note->content()->messageWhenOpenning(NoteContent::OpenOneWith /*NoteContent::OpenSeveralWith*/);
4337 QString text = note->content()->messageWhenOpenning(NoteContent::OpenOneWithDialog /*NoteContent::OpenSeveralWithDialog*/);
4338 if (url.isEmpty())
4339 emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/);
4340 else if (KRun__displayOpenWithDialog(url, false, text))
4341 emit postMessage(message); // "Openning link target with..." / "Openning note file with..."
4344 void Basket::noteSaveAs()
4346 // if (!note)
4347 // note = theSelectedNote();
4348 Note *note = theSelectedNote();
4349 if (!note)
4350 return;
4352 KUrl url = note->content()->urlToOpen(/*with=*/false);
4353 if (url.isEmpty())
4354 return;
4356 QString fileName = KFileDialog::getSaveFileName(url.fileName(), note->content()->saveAsFilters(), this, i18n("Save to File"));
4357 // TODO: Ask to overwrite !
4358 if (fileName.isEmpty())
4359 return;
4361 // TODO: Convert format, etc. (use NoteContent::saveAs(fileName))
4362 KIO::copy(url, KUrl(fileName));
4365 Note* Basket::selectedGroup()
4367 FOR_EACH_NOTE (note) {
4368 Note *selectedGroup = note->selectedGroup();
4369 if (selectedGroup) {
4370 // If the selected group is one group in a column, then return that group, and not the column,
4371 // because the column is not ungrouppage, and the Ungroup action would be disabled.
4372 if (selectedGroup->isColumn() && selectedGroup->firstChild() && !selectedGroup->firstChild()->next()) {
4373 return selectedGroup->firstChild();
4375 return selectedGroup;
4378 return 0;
4381 bool Basket::selectionIsOneGroup()
4383 return (selectedGroup() != 0);
4386 Note* Basket::firstSelected()
4388 Note *first = 0;
4389 FOR_EACH_NOTE (note) {
4390 first = note->firstSelected();
4391 if (first)
4392 return first;
4394 return 0;
4397 Note* Basket::lastSelected()
4399 Note *last = 0, *tmp = 0;
4400 FOR_EACH_NOTE (note) {
4401 tmp = note->lastSelected();
4402 if (tmp)
4403 last = tmp;
4405 return last;
4408 bool Basket::convertTexts()
4410 m_watcher->stopScan();
4411 bool convertedNotes = false;
4413 if (!isLoaded())
4414 load();
4416 FOR_EACH_NOTE (note)
4417 if (note->convertTexts())
4418 convertedNotes = true;
4420 if (convertedNotes)
4421 save();
4422 m_watcher->startScan();
4423 return convertedNotes;
4426 void Basket::noteGroup()
4428 /* // Nothing to do?
4429 if (isLocked() || countSelecteds() <= 1)
4430 return;
4432 // If every selected notes are ALREADY in one group, then don't touch anything:
4433 Note *selectedGroup = this->selectedGroup();
4434 if (selectedGroup && !selectedGroup->isColumn())
4435 return;
4438 // Copied from BNPView::updateNotesActions()
4439 bool severalSelected = countSelecteds() >= 2;
4440 Note *selectedGroup = (severalSelected ? this->selectedGroup() : 0);
4441 bool enabled = !isLocked() && severalSelected && (!selectedGroup || selectedGroup->isColumn());
4442 if (!enabled)
4443 return;
4445 // Get the first selected note: we will group selected items just before:
4446 Note *first = firstSelected();
4447 // if (selectedGroup != 0 || first == 0)
4448 // return;
4450 m_loaded = false; // Hack to avoid notes to be unselected and new notes to be selected:
4452 // Create and insert the receiving group:
4453 Note *group = new Note(this);
4454 if (first->isFree()) {
4455 insertNote(group, 0L, Note::BottomColumn, QPoint(first->finalX(), first->finalY()), /*animateNewPosition=*/false);
4456 } else {
4457 insertNote(group, first, Note::TopInsert, QPoint(), /*animateNewPosition=*/false);
4460 // 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!
4461 Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4462 insertNote(fakeNote, group, Note::BottomColumn, QPoint(), /*animateNewPosition=*/false);
4464 // Group the notes:
4465 Note *nextNote;
4466 Note *note = firstNote();
4467 while (note) {
4468 nextNote = note->next();
4469 note->groupIn(group);
4470 note = nextNote;
4473 m_loaded = true; // Part 2 / 2 of the workarround!
4475 // Do cleanup:
4476 unplugNote(fakeNote);
4477 unselectAll();
4478 group->setSelectedRecursivly(true); // Notes were unselected by unplugging
4480 relayoutNotes(true);
4481 save();
4484 void Basket::noteUngroup()
4486 Note *group = selectedGroup();
4487 if (group && !group->isColumn())
4488 ungroupNote(group);
4489 save();
4492 void Basket::unplugSelection(NoteSelection *selection)
4494 for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked())
4495 unplugNote(toUnplug->note);
4498 void Basket::insertSelection(NoteSelection *selection, Note *after)
4500 for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) {
4501 if (toUnplug->note->isGroup()) {
4502 Note *group = new Note(this);
4503 insertNote(group, after, Note::BottomInsert, QPoint(), /*animateNewPosition=*/false);
4504 Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4505 insertNote(fakeNote, group, Note::BottomColumn, QPoint(), /*animateNewPosition=*/false);
4506 insertSelection(toUnplug->firstChild, fakeNote);
4507 unplugNote(fakeNote);
4508 after = group;
4509 } else {
4510 Note *note = toUnplug->note;
4511 note->setPrev(0);
4512 note->setNext(0);
4513 insertNote(note, after, Note::BottomInsert, QPoint(), /*animateNewPosition=*/true);
4514 after = note;
4519 void Basket::selectSelection(NoteSelection *selection)
4521 for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) {
4522 if (toUnplug->note->isGroup())
4523 selectSelection(toUnplug);
4524 else
4525 toUnplug->note->setSelected(true);
4529 void Basket::noteMoveOnTop()
4531 // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket
4532 // TODO: Move on top/bottom... of the column or basjet
4534 NoteSelection *selection = selectedNotes();
4535 unplugSelection(selection);
4536 // Replug the notes:
4537 Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4538 if (isColumnsLayout()) {
4539 if (firstNote()->firstChild())
4540 insertNote(fakeNote, firstNote()->firstChild(), Note::TopInsert, QPoint(), /*animateNewPosition=*/false);
4541 else
4542 insertNote(fakeNote, firstNote(), Note::BottomColumn, QPoint(), /*animateNewPosition=*/false);
4543 } else {
4544 // TODO: Also allow to move notes on top of a group!!!!!!!
4545 insertNote(fakeNote, 0, Note::BottomInsert, QPoint(0, 0), /*animateNewPosition=*/false);
4547 insertSelection(selection, fakeNote);
4548 unplugNote(fakeNote);
4549 selectSelection(selection);
4550 relayoutNotes(true);
4551 save();
4554 void Basket::noteMoveOnBottom()
4557 // TODO: Duplicate code: void noteMoveOn();
4559 // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket
4560 // TODO: Move on top/bottom... of the column or basjet
4562 NoteSelection *selection = selectedNotes();
4563 unplugSelection(selection);
4564 // Replug the notes:
4565 Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4566 if (isColumnsLayout())
4567 insertNote(fakeNote, firstNote(), Note::BottomColumn, QPoint(), /*animateNewPosition=*/false);
4568 else {
4569 // TODO: Also allow to move notes on top of a group!!!!!!!
4570 insertNote(fakeNote, 0, Note::BottomInsert, QPoint(0, 0), /*animateNewPosition=*/false);
4572 insertSelection(selection, fakeNote);
4573 unplugNote(fakeNote);
4574 selectSelection(selection);
4575 relayoutNotes(true);
4576 save();
4579 void Basket::moveSelectionTo(Note *here, bool below/* = true*/)
4581 NoteSelection *selection = selectedNotes();
4582 unplugSelection(selection);
4583 // Replug the notes:
4584 Note *fakeNote = NoteFactory::createNoteColor(Qt::red, this);
4585 // if (isColumnsLayout())
4586 insertNote(fakeNote, here, (below ? Note::BottomInsert : Note::TopInsert), QPoint(), /*animateNewPosition=*/false);
4587 // else {
4588 // // TODO: Also allow to move notes on top of a group!!!!!!!
4589 // insertNote(fakeNote, 0, Note::BottomInsert, QPoint(0, 0), /*animateNewPosition=*/false);
4590 // }
4591 insertSelection(selection, fakeNote);
4592 unplugNote(fakeNote);
4593 selectSelection(selection);
4594 relayoutNotes(true);
4595 save();
4598 void Basket::noteMoveNoteUp()
4601 // TODO: Move between columns, even if they are empty !!!!!!!
4603 // TODO: if first note of a group, move just above the group! And let that even if there is no note before that group!!!
4605 Note *first = firstSelected();
4606 Note *previous = first->prevShownInStack();
4607 if (previous)
4608 moveSelectionTo(previous, /*below=*/false);
4611 void Basket::noteMoveNoteDown()
4613 Note *first = lastSelected();
4614 Note *next = first->nextShownInStack();
4615 if (next)
4616 moveSelectionTo(next, /*below=*/true);
4619 void Basket::wheelEvent(QWheelEvent *event)
4621 Q3ScrollView::wheelEvent(event);
4624 void Basket::linkLookChanged()
4626 Note *note = m_firstNote;
4627 while (note) {
4628 note->linkLookChanged();
4629 note = note->next();
4631 relayoutNotes(true);
4634 void Basket::slotCopyingDone2(KIO::Job *job)
4636 if (job->error()) {
4637 DEBUG_WIN << "Copy finished, ERROR";
4638 return;
4640 KIO::FileCopyJob *fileCopyJob = (KIO::FileCopyJob*)job;
4641 Note *note = noteForFullPath(fileCopyJob->destURL().path());
4642 DEBUG_WIN << "Copy finished, load note: " + fileCopyJob->destURL().path() + (note ? "" : " --- NO CORRESPONDING NOTE");
4643 if (note != 0L) {
4644 note->content()->loadFromFile(/*lazyLoad=*/false);
4645 if(isEncrypted())
4646 note->content()->saveToFile();
4647 if (m_focusedNote == note) // When inserting a new note we ensure it visble
4648 ensureNoteVisible(note); // But after loading it has certainly grown and if it was
4649 } // on bottom of the basket it's not visible entirly anymore
4652 Note* Basket::noteForFullPath(const QString &path)
4654 Note *note = firstNote();
4655 Note *found;
4656 while (note) {
4657 found = note->noteForFullPath(path);
4658 if (found)
4659 return found;
4660 note = note->next();
4662 return 0;
4665 void Basket::deleteFiles()
4667 m_watcher->stopScan();
4668 Tools::deleteRecursively(fullPath());
4671 Q3ValueList<State*> Basket::usedStates()
4673 Q3ValueList<State*> states;
4674 FOR_EACH_NOTE (note)
4675 note->usedStates(states);
4676 return states;
4679 QString Basket::saveGradientBackground(const QColor &color, const QFont &font, const QString &folder)
4681 // Construct file name and return if the file already exists:
4682 QString fileName = "note_background_" + color.name().toLower().mid(1) + ".png";
4683 QString fullPath = folder + fileName;
4684 if (QFile::exists(fullPath))
4685 return fileName;
4687 // Get the gradient top and bottom colors:
4688 QColor topBgColor;
4689 QColor bottomBgColor;
4690 Note::getGradientColors(color, &topBgColor, &bottomBgColor);
4692 // Draw and save the gradient image:
4693 int sampleTextHeight = QFontMetrics(font)
4694 .boundingRect(0, 0, /*width=*/10000, /*height=*/0, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, "Test text")
4695 .height();
4696 QPixmap noteGradient(100, sampleTextHeight + Note::NOTE_MARGIN);
4697 QPainter painter(&noteGradient);
4698 drawGradient(&painter, topBgColor, bottomBgColor, 0, 0, noteGradient.width(), noteGradient.height(), /*sunken=*/false, /*horz=*/true, /*flat=*/false);
4699 painter.end();
4700 noteGradient.save(fullPath, "PNG");
4702 // Return the name of the created file:
4703 return fileName;
4706 void Basket::listUsedTags(Q3ValueList<Tag*> &list)
4708 if (!isLoaded()) {
4709 load();
4712 FOR_EACH_NOTE (child)
4713 child->listUsedTags(list);
4717 /** Unfocus the previously focused note (unless it was null)
4718 * and focus the new @param note (unless it is null) if hasFocus()
4719 * Update m_focusedNote to the new one
4721 void Basket::setFocusedNote(Note *note) // void Basket::changeFocusTo(Note *note)
4723 // Don't focus an hidden note:
4724 if (note != 0L && !note->isShown())
4725 return;
4726 // When clicking a group, this group gets focused. But only content-based notes should be focused:
4727 if (note && note->isGroup())
4728 note = note->firstRealChild();
4729 // The first time a note is focused, it becomes the start of the Shift selection:
4730 if (m_startOfShiftSelectionNote == 0)
4731 m_startOfShiftSelectionNote = note;
4732 // Unfocus the old focused note:
4733 if (m_focusedNote != 0L)
4734 m_focusedNote->setFocused(false);
4735 // Notify the new one to draw a focus rectangle... only if the basket is focused:
4736 if (hasFocus() && note != 0L)
4737 note->setFocused(true);
4738 // Save the new focused note:
4739 m_focusedNote = note;
4742 /** If no shown note is currently focused, try to find a shown note and focus it
4743 * Also update m_focusedNote to the new one (or null if there isn't)
4745 void Basket::focusANote()
4747 if (countFounds() == 0) { // No note to focus
4748 setFocusedNote(0L);
4749 // m_startOfShiftSelectionNote = 0;
4750 return;
4753 if (m_focusedNote == 0L) { // No focused note yet : focus the first shown
4754 Note *toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack());
4755 setFocusedNote(toFocus);
4756 // m_startOfShiftSelectionNote = m_focusedNote;
4757 return;
4760 // Search a visible note to focus if the focused one isn't shown :
4761 Note *toFocus = m_focusedNote;
4762 if (toFocus && !toFocus->isShown())
4763 toFocus = toFocus->nextShownInStack();
4764 if (!toFocus && m_focusedNote)
4765 toFocus = m_focusedNote->prevShownInStack();
4766 setFocusedNote(toFocus);
4767 // m_startOfShiftSelectionNote = toFocus;
4770 Note* Basket::firstNoteInStack()
4772 if (!firstNote())
4773 return 0;
4775 if (firstNote()->content())
4776 return firstNote();
4777 else
4778 return firstNote()->nextInStack();
4781 Note* Basket::lastNoteInStack()
4783 Note *note = lastNote();
4784 while (note) {
4785 if (note->content())
4786 return note;
4787 Note *possibleNote = note->lastRealChild();
4788 if (possibleNote && possibleNote->content())
4789 return possibleNote;
4790 note = note->prev();
4792 return 0;
4795 Note* Basket::firstNoteShownInStack()
4797 Note *first = firstNoteInStack();
4798 while (first && !first->isShown())
4799 first = first->nextInStack();
4800 return first;
4803 Note* Basket::lastNoteShownInStack()
4805 Note *last = lastNoteInStack();
4806 while (last && !last->isShown())
4807 last = last->prevInStack();
4808 return last;
4811 inline int abs(int n)
4813 return (n < 0 ? -n : n);
4816 Note* Basket::noteOn(NoteOn side)
4818 Note *bestNote = 0;
4819 int distance = -1;
4820 int bestDistance = contentsWidth() * contentsHeight() * 10;
4822 Note *note = firstNoteShownInStack();
4823 Note *primary = m_focusedNote->parentPrimaryNote();
4824 while (note) {
4825 switch (side) {
4826 case LEFT_SIDE: distance = m_focusedNote->distanceOnLeftRight(note, LEFT_SIDE); break;
4827 case RIGHT_SIDE: distance = m_focusedNote->distanceOnLeftRight(note, RIGHT_SIDE); break;
4828 case TOP_SIDE: distance = m_focusedNote->distanceOnTopBottom(note, TOP_SIDE); break;
4829 case BOTTOM_SIDE: distance = m_focusedNote->distanceOnTopBottom(note, BOTTOM_SIDE); break;
4831 if ((side == TOP_SIDE || side == BOTTOM_SIDE || primary != note->parentPrimaryNote()) && note != m_focusedNote && distance > 0 && distance < bestDistance) {
4832 bestNote = note;
4833 bestDistance = distance;
4835 note = note ->nextShownInStack();
4838 return bestNote;
4841 Note* Basket::firstNoteInGroup()
4843 Note *child = m_focusedNote;
4844 Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0);
4845 while (parent) {
4846 if (parent->firstChild() != child && !parent->isColumn())
4847 return parent->firstRealChild();
4848 child = parent;
4849 parent = parent->parentNote();
4851 return 0;
4854 Note* Basket::noteOnHome()
4856 // First try to find the first note of the group containing the focused note:
4857 Note *child = m_focusedNote;
4858 Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0);
4859 while (parent) {
4860 if (parent->nextShownInStack() != m_focusedNote)
4861 return parent->nextShownInStack();
4862 child = parent;
4863 parent = parent->parentNote();
4866 // If it was not found, then focus the very first note in the basket:
4867 if (isFreeLayout()) {
4868 Note *first = firstNoteShownInStack(); // The effective first note found
4869 Note *note = first; // The current note, to conpare with the previous first note, if this new note is more on top
4870 if (note)
4871 note = note->nextShownInStack();
4872 while (note) {
4873 if (note->finalY() < first->finalY() || (note->finalY() == first->finalY() && note->finalX() < first->finalX()))
4874 first = note;
4875 note = note->nextShownInStack();
4877 return first;
4878 } else
4879 return firstNoteShownInStack();
4882 Note* Basket::noteOnEnd()
4884 Note *child = m_focusedNote;
4885 Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0);
4886 Note *lastChild;
4887 while (parent) {
4888 lastChild = parent->lastRealChild();
4889 if (lastChild && lastChild != m_focusedNote) {
4890 if (lastChild->isShown())
4891 return lastChild;
4892 lastChild = lastChild->prevShownInStack();
4893 if (lastChild && lastChild->isShown() && lastChild != m_focusedNote)
4894 return lastChild;
4896 child = parent;
4897 parent = parent->parentNote();
4899 if (isFreeLayout()) {
4900 Note *last;
4901 Note *note;
4902 last = note = firstNoteShownInStack();
4903 note = note->nextShownInStack();
4904 while (note) {
4905 if (note->finalBottom() > last->finalBottom() || (note->finalBottom() == last->finalBottom() && note->finalX() > last->finalX()))
4906 last = note;
4907 note = note->nextShownInStack();
4909 return last;
4910 } else
4911 return lastNoteShownInStack();
4915 void Basket::keyPressEvent(QKeyEvent *event)
4917 if (isDuringEdit() && event->key() == Qt::Key_Return) {
4918 //if (m_editor->lineEdit())
4919 // closeEditor();
4920 //else
4921 m_editor->widget()->setFocus();
4922 } else if (event->key() == Qt::Key_Escape) {
4923 if (isDuringEdit())
4924 closeEditor();
4925 else if (decoration()->filterData().isFiltering)
4926 cancelFilter();
4927 else
4928 unselectAll();
4931 if (countFounds() == 0)
4932 return;
4934 if (!m_focusedNote)
4935 return;
4937 Note *toFocus = 0L;
4939 switch (event->key()) {
4940 case Qt::Key_Down:
4941 toFocus = (isFreeLayout() ? noteOn(BOTTOM_SIDE) : m_focusedNote->nextShownInStack());
4942 if (toFocus)
4943 break;
4944 scrollBy(0, 30); // This cases do not move focus to another note...
4945 return;
4946 case Qt::Key_Up:
4947 toFocus = (isFreeLayout() ? noteOn(TOP_SIDE) : m_focusedNote->prevShownInStack());
4948 if (toFocus)
4949 break;
4950 scrollBy(0, -30); // This cases do not move focus to another note...
4951 return;
4952 case Qt::Key_PageDown:
4953 if (isFreeLayout()) {
4954 Note *lastFocused = m_focusedNote;
4955 for (int i = 0; i < 10 && m_focusedNote; ++i)
4956 m_focusedNote = noteOn(BOTTOM_SIDE);
4957 toFocus = m_focusedNote;
4958 m_focusedNote = lastFocused;
4959 } else {
4960 toFocus = m_focusedNote;
4961 for (int i = 0; i < 10 && toFocus; ++i)
4962 toFocus = toFocus->nextShownInStack();
4964 if (toFocus == 0L)
4965 toFocus = (isFreeLayout() ? noteOnEnd() : lastNoteShownInStack());
4966 if (toFocus && toFocus != m_focusedNote)
4967 break;
4968 scrollBy(0, visibleHeight() / 2); // This cases do not move focus to another note...
4969 return;
4970 case Qt::Key_PageUp:
4971 if (isFreeLayout()) {
4972 Note *lastFocused = m_focusedNote;
4973 for (int i = 0; i < 10 && m_focusedNote; ++i)
4974 m_focusedNote = noteOn(TOP_SIDE);
4975 toFocus = m_focusedNote;
4976 m_focusedNote = lastFocused;
4977 } else {
4978 toFocus = m_focusedNote;
4979 for (int i = 0; i < 10 && toFocus; ++i)
4980 toFocus = toFocus->prevShownInStack();
4982 if (toFocus == 0L)
4983 toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack());
4984 if (toFocus && toFocus != m_focusedNote)
4985 break;
4986 scrollBy(0, - visibleHeight() / 2); // This cases do not move focus to another note...
4987 return;
4988 case Qt::Key_Home:
4989 toFocus = noteOnHome();
4990 break;
4991 case Qt::Key_End:
4992 toFocus = noteOnEnd();
4993 break;
4994 case Qt::Key_Left:
4995 if (m_focusedNote->tryFoldParent())
4996 return;
4997 if ( (toFocus = noteOn(LEFT_SIDE)) )
4998 break;
4999 if ( (toFocus = firstNoteInGroup()) )
5000 break;
5001 scrollBy(-30, 0); // This cases do not move focus to another note...
5002 return;
5003 case Qt::Key_Right:
5004 if (m_focusedNote->tryExpandParent())
5005 return;
5006 if ( (toFocus = noteOn(RIGHT_SIDE)) )
5007 break;
5008 scrollBy(30, 0); // This cases do not move focus to another note...
5009 return;
5010 case Qt::Key_Space: // This case do not move focus to another note...
5011 if (m_focusedNote) {
5012 m_focusedNote->setSelected( ! m_focusedNote->isSelected() );
5013 event->accept();
5014 } else
5015 event->ignore();
5016 return; // ... so we return after the process
5017 default:
5018 return;
5021 if (toFocus == 0L) { // If no direction keys have been pressed OR reached out the begin or end
5022 event->ignore(); // Important !!
5023 return;
5026 if (event->state() & Qt::ShiftModifier) { // Shift+arrowKeys selection
5027 if (m_startOfShiftSelectionNote == 0L)
5028 m_startOfShiftSelectionNote = toFocus;
5029 ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part!
5030 selectRange(m_startOfShiftSelectionNote, toFocus);
5031 setFocusedNote(toFocus);
5032 event->accept();
5033 return;
5034 } else /*if (toFocus != m_focusedNote)*/ { // Move focus to ANOTHER note...
5035 ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part!
5036 setFocusedNote(toFocus);
5037 m_startOfShiftSelectionNote = toFocus;
5038 if ( ! (event->state() & Qt::ControlModifier) ) // ... select only current note if Control
5039 unselectAllBut(m_focusedNote);
5040 event->accept();
5041 return;
5044 event->ignore(); // Important !!
5047 /** Select a range of notes and deselect the others.
5048 * The order between start and end has no importance (end could be before start)
5050 void Basket::selectRange(Note *start, Note *end, bool unselectOthers /*= true*/)
5052 Note *cur;
5053 Note *realEnd = 0L;
5055 // Avoid crash when start (or end) is null
5056 if (start == 0L)
5057 start = end;
5058 else if (end == 0L)
5059 end = start;
5060 // And if *both* are null
5061 if (start == 0L) {
5062 if (unselectOthers)
5063 unselectAll();
5064 return;
5066 // In case there is only one note to select
5067 if (start == end) {
5068 if (unselectOthers)
5069 unselectAllBut(start);
5070 else
5071 start->setSelected(true);
5072 return;
5075 // Free layout baskets should select range as if we were drawing a rectangle between start and end:
5076 if (isFreeLayout()) {
5077 QRect startRect( start->finalX(), start->finalY(), start->width(), start->finalHeight() );
5078 QRect endRect( end->finalX(), end->finalY(), end->width(), end->finalHeight() );
5079 QRect toSelect = startRect.unite(endRect);
5080 selectNotesIn(toSelect, /*invertSelection=*/false, unselectOthers);
5081 return;
5084 // Search the REAL first (and deselect the others before it) :
5085 for (cur = firstNoteInStack(); cur != 0L; cur = cur->nextInStack()) {
5086 if (cur == start || cur == end)
5087 break;
5088 if (unselectOthers)
5089 cur->setSelected(false);
5092 // Select the notes after REAL start, until REAL end :
5093 if (cur == start)
5094 realEnd = end;
5095 else if (cur == end)
5096 realEnd = start;
5098 for (/*cur = cur*/; cur != 0L; cur = cur->nextInStack()) {
5099 cur->setSelected(cur->isShown()); // Select all notes in the range, but only if they are shown
5100 if (cur == realEnd)
5101 break;
5104 if (!unselectOthers)
5105 return;
5107 // Deselect the remaining notes :
5108 if (cur)
5109 cur = cur->nextInStack();
5110 for (/*cur = cur*/; cur != 0L; cur = cur->nextInStack())
5111 cur->setSelected(false);
5114 void Basket::focusInEvent(QFocusEvent*)
5116 // Focus cannot be get with Tab when locked, but a click can focus the basket!
5117 if (isLocked()) {
5118 if (m_button)
5119 QTimer::singleShot( 0, m_button, SLOT(setFocus()) );
5120 } else
5121 focusANote(); // hasFocus() is true at this stage, note will be focused
5124 void Basket::focusOutEvent(QFocusEvent*)
5126 if (m_focusedNote != 0L)
5127 m_focusedNote->setFocused(false);
5130 void Basket::ensureNoteVisible(Note *note)
5132 if (!note->isShown()) // Logical!
5133 return;
5135 if (note == editedNote()) // HACK: When filtering while editing big notes, etc... cause unwanted scrolls
5136 return;
5138 int finalBottom = note->finalY() + qMin(note->finalHeight(), visibleHeight());
5139 int finalRight = note->finalX() + qMin(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0), visibleWidth());
5140 ensureVisible( finalRight, finalBottom, 0,0 );
5141 ensureVisible( note->finalX(), note->finalY(), 0,0 );
5144 void Basket::addWatchedFile(const QString &fullPath)
5146 // DEBUG_WIN << "Watcher>Add Monitoring Of : <font color=blue>" + fullPath + "</font>";
5147 m_watcher->addFile(fullPath);
5150 void Basket::removeWatchedFile(const QString &fullPath)
5152 // DEBUG_WIN << "Watcher>Remove Monitoring Of : <font color=blue>" + fullPath + "</font>";
5153 m_watcher->removeFile(fullPath);
5156 void Basket::watchedFileModified(const QString &fullPath)
5158 if (!m_modifiedFiles.contains(fullPath))
5159 m_modifiedFiles.append(fullPath);
5160 // If a big file is saved by an application, notifications are send several times.
5161 // We wait they are not sent anymore to considere the file complete!
5162 m_watcherTimer.start(200/*ms*/, true);
5163 DEBUG_WIN << "Watcher>Modified : <font color=blue>" + fullPath + "</font>";
5166 void Basket::watchedFileDeleted(const QString &fullPath)
5168 Note *note = noteForFullPath(fullPath);
5169 removeWatchedFile(fullPath);
5170 if (note) {
5171 NoteSelection *selection = selectedNotes();
5172 unselectAllBut(note);
5173 noteDeleteWithoutConfirmation();
5174 while (selection) {
5175 selection->note->setSelected(true);
5176 selection = selection->nextStacked();
5179 DEBUG_WIN << "Watcher>Removed : <font color=blue>" + fullPath + "</font>";
5182 void Basket::updateModifiedNotes()
5184 for (Q3ValueList<QString>::iterator it = m_modifiedFiles.begin(); it != m_modifiedFiles.end(); ++it) {
5185 Note *note = noteForFullPath(*it);
5186 if (note)
5187 note->content()->loadFromFile(/*lazyLoad=*/false);
5189 m_modifiedFiles.clear();
5192 bool Basket::setProtection(int type, QString key)
5194 #ifdef HAVE_LIBGPGME
5195 if(type == PasswordEncryption || // Ask a new password
5196 m_encryptionType != type || m_encryptionKey != key)
5198 int savedType = m_encryptionType;
5199 QString savedKey = m_encryptionKey;
5201 m_encryptionType = type;
5202 m_encryptionKey = key;
5203 m_gpg->clearCache();
5205 if(saveAgain())
5207 emit propertiesChanged(this);
5209 else
5211 m_encryptionType = savedType;
5212 m_encryptionKey = savedKey;
5213 m_gpg->clearCache();
5214 return false;
5217 return true;
5218 #else
5219 m_encryptionType = type;
5220 m_encryptionKey = key;
5221 return false;
5222 #endif
5225 bool Basket::saveAgain()
5227 bool result = false;
5229 m_watcher->stopScan();
5230 // Re-encrypt basket file:
5231 result = save();
5232 // Re-encrypt every note files recursively:
5233 if(result)
5235 FOR_EACH_NOTE (note)
5237 result = note->saveAgain();
5238 if(!result)
5239 break;
5242 m_watcher->startScan();
5243 return result;
5246 bool Basket::loadFromFile(const QString &fullPath, QString *string, bool isLocalEncoding)
5248 QByteArray array;
5250 if(loadFromFile(fullPath, &array)){
5251 if (isLocalEncoding)
5252 *string = QString::fromLocal8Bit(array.data(), array.size());
5253 else
5254 *string = QString::fromUtf8(array.data(), array.size());
5255 return true;
5257 else
5258 return false;
5261 bool Basket::isEncrypted()
5263 return (m_encryptionType != NoEncryption);
5266 bool Basket::isFileEncrypted()
5268 QFile file(fullPath() + ".basket");
5270 if (file.open(QIODevice::ReadOnly)){
5271 QString line;
5273 file.readLine(line, 32);
5274 if(line.startsWith("-----BEGIN PGP MESSAGE-----"))
5275 return true;
5277 return false;
5280 bool Basket::loadFromFile(const QString &fullPath, QByteArray *array)
5282 QFile file(fullPath);
5283 bool encrypted = false;
5285 if (file.open(QIODevice::ReadOnly)){
5286 *array = file.readAll();
5287 const char* magic = "-----BEGIN PGP MESSAGE-----";
5288 uint i = 0;
5290 if(array->size() > strlen(magic))
5291 for (i = 0; array->at(i) == magic[i]; ++i)
5293 if (i == strlen(magic))
5295 encrypted = true;
5297 file.close();
5298 #ifdef HAVE_LIBGPGME
5299 if(encrypted)
5301 QByteArray tmp(*array);
5303 tmp.detach();
5304 // Only use gpg-agent for private key encryption since it doesn't
5305 // cache password used in symmetric encryption.
5306 m_gpg->setUseGnuPGAgent(Settings::useGnuPGAgent() && m_encryptionType == PrivateKeyEncryption);
5307 if(m_encryptionType == PrivateKeyEncryption)
5308 m_gpg->setText(i18n("Please enter the password for the following private key:"), false);
5309 else
5310 m_gpg->setText(i18n("Please enter the password for the basket <b>%1</b>:").arg(basketName()), false); // Used when decrypting
5311 return m_gpg->decrypt(tmp, array);
5313 #else
5314 if(encrypted)
5316 return false;
5318 #endif
5319 return true;
5320 } else
5321 return false;
5324 bool Basket::saveToFile(const QString& fullPath, const QString& string, bool isLocalEncoding)
5326 Q3CString bytes = (isLocalEncoding ? string.local8Bit() : string.utf8());
5327 return saveToFile(fullPath, bytes, bytes.length());
5330 bool Basket::saveToFile(const QString& fullPath, const QByteArray& array)
5332 return saveToFile(fullPath, array, array.size());
5335 bool Basket::saveToFile(const QString& fullPath, const QByteArray& array, Q_ULONG length)
5337 bool success = true;
5338 QByteArray tmp;
5340 #ifdef HAVE_LIBGPGME
5341 if(isEncrypted())
5343 QString key = QString::null;
5345 // We only use gpg-agent for private key encryption and saving without
5346 // public key doesn't need one.
5347 m_gpg->setUseGnuPGAgent(false);
5348 if(m_encryptionType == PrivateKeyEncryption)
5350 key = m_encryptionKey;
5351 // public key doesn't need password
5352 m_gpg->setText("", false);
5354 else
5355 m_gpg->setText(i18n("Please assign a password to the basket <b>%1</b>:").arg(basketName()), true); // Used when defining a new password
5357 success = m_gpg->encrypt(array, length, &tmp, key);
5358 length = tmp.size();
5360 else
5361 tmp = array;
5363 #else
5364 success = !isEncrypted();
5365 if(success)
5366 tmp = array;
5367 #endif
5368 /*if (success && (success = file.open(QIODevice::WriteOnly))){
5369 success = (file.write(tmp) == (Q_LONG)tmp.size());
5370 file.close();
5373 if (success)
5374 return safelySaveToFile(fullPath, tmp, length);
5375 else
5376 return false;
5379 /** Same as saveToFile(), but it is static, and does not crypt the data if needed.
5380 * Basically, to save a file owned by a basket (a basket or a note file), use saveToFile().
5381 * But to save another file (eg. the basket hierarchy), use this safelySaveToFile() static method.
5383 /*static*/ bool Basket::safelySaveToFile(const QString& fullPath, const QByteArray& array, Q_ULONG length)
5385 // Here, we take a double protection:
5386 // - We use KSaveFile to write atomically to the file (either it's a success or the file is untouched)
5387 // - We show a modal dialog to the user when no disk space is left or access is denied and retry every couple of seconds
5389 // Static, because safelySaveToFile() can be called a second time while blocked.
5390 // Example:
5391 // User type something and press Enter: safelySaveToFile() is called and block.
5392 // Three seconds later, a timer ask to save changes, and this second safelySaveToFile() block too.
5393 // Do not show the dialog twice in this case!
5394 static DiskErrorDialog *dialog = 0;
5396 //std::cout << "---------- Saving " << fullPath << ":" << std::endl;
5397 bool openSuccess;
5398 bool closeSuccess;
5399 bool errorWhileWritting;
5400 do {
5401 KSaveFile saveFile(fullPath);
5402 //std::cout << "==>>" << std::endl << "SAVE FILE CREATED: " << strerror(saveFile.status()) << std::endl;
5403 openSuccess = (saveFile.status() == 0 && saveFile.file() != 0);
5404 if (openSuccess) {
5405 saveFile.file()->write(array, length);
5406 //std::cout << "FILE WRITTEN: " << strerror(saveFile.status()) << std::endl;
5407 closeSuccess = saveFile.close();
5408 //std::cout << "FILE CLOSED: " << (closeSuccess ? "well" : "erroneous") << std::endl;
5410 errorWhileWritting = (!openSuccess || !closeSuccess || saveFile.status() != 0);
5411 if (errorWhileWritting) {
5412 //std::cout << "ERROR DETECTED" << std::endl;
5413 if (dialog == 0) {
5414 //std::cout << "Opening dialog for " << fullPath << std::endl;
5415 dialog = new DiskErrorDialog(
5416 (openSuccess
5417 ? i18n("Insufficient Disk Space to Save Basket Data")
5418 : i18n("Wrong Basket File Permissions")
5420 (openSuccess
5421 ? i18n("Please remove files on the disk <b>%1</b> to let the application safely save your changes.")
5422 .arg(KIO::findPathMountPoint(fullPath))
5423 : i18n("File permissions are bad for <b>%1</b>. Please check that you have write access to it and the parent folders.")
5424 .arg(fullPath)
5426 kapp->activeWindow()
5429 if (!dialog->isShown())
5430 dialog->show();
5431 const int retryDelay = 1000/*ms*/;
5432 const int sleepDelay = 50/*ms*/;
5433 for (int i = 0; i < retryDelay / sleepDelay; ++i) {
5434 kapp->processEvents();
5435 usleep(sleepDelay);
5438 } while (errorWhileWritting);
5439 if (dialog) {
5440 delete dialog;
5441 dialog = 0;
5444 return true; // Hum...?!
5447 /*static*/ bool Basket::safelySaveToFile(const QString& fullPath, const QString& string, bool isLocalEncoding)
5449 Q3CString bytes = (isLocalEncoding ? string.local8Bit() : string.utf8());
5450 return safelySaveToFile(fullPath, bytes, bytes.length() - 1);
5453 /*static*/ bool Basket::safelySaveToFile(const QString& fullPath, const QByteArray& array)
5455 return safelySaveToFile(fullPath, array, array.size());
5458 DiskErrorDialog::DiskErrorDialog(const QString &titleMessage, const QString &message, QWidget *parent)
5459 : KDialog(parent)
5461 setObjectName("DiskError");
5462 setCaption(i18n("Save Error"));
5463 setMainWidget(new QWidget(this));
5464 //enableButtonCancel(false);
5465 //enableButtonClose(false);
5466 //enableButton(Close, false);
5467 //enableButtonOk(false);
5468 setModal(true);
5469 Q3HBoxLayout *layout = new Q3HBoxLayout(mainWidget(), /*margin=*/0, spacingHint());
5470 QPixmap icon = kapp->iconLoader()->loadIcon("hdd_unmount", KIconLoader::NoGroup, 64, KIconLoader::DefaultState, /*path_store=*/0L, /*canReturnNull=*/true);
5471 QLabel *iconLabel = new QLabel(mainWidget());
5472 iconLabel->setPixmap(icon);
5473 iconLabel->setFixedSize(iconLabel->sizeHint());
5474 QLabel *label = new QLabel("<p><nobr><b><font size='+1'>" + titleMessage + "</font></b></nobr></p><p>" + message + "</p>", mainWidget());
5475 if (!icon.isNull())
5476 layout->addWidget(iconLabel);
5477 layout->addWidget(label);
5480 DiskErrorDialog::~DiskErrorDialog()
5484 void DiskErrorDialog::closeEvent(QCloseEvent *event)
5486 event->ignore();
5489 void DiskErrorDialog::keyPressEvent(QKeyEvent*)
5491 // Escape should not close the window...
5494 void Basket::lock()
5496 #ifdef HAVE_LIBGPGME
5497 closeEditor();
5498 m_gpg->clearCache();
5499 m_locked = true;
5500 enableActions();
5501 deleteNotes();
5502 m_loaded = false;
5503 m_loadingLaunched = false;
5504 updateContents();
5505 #endif
5508 #if 0
5510 #include <qlayout.h>
5511 #include <q3vbox.h>
5512 #include <qstring.h>
5513 #include <qpixmap.h>
5514 #include <qcolor.h>
5515 #include <kmenu.h>
5516 #include <kurllabel.h>
5517 #include <qcheckbox.h>
5518 #include <qpalette.h>
5519 #include <qcursor.h>
5520 #include <qaction.h>
5521 #include <kstdaccel.h>
5522 #include <kglobalsettings.h>
5523 #include <qevent.h>
5525 #include <kapplication.h>
5526 #include <kaboutdata.h>
5527 #include <qinputdialog.h>
5528 #include <q3dragobject.h>
5529 #include <k3urldrag.h>
5530 #include <kiconloader.h>
5531 #include <klocale.h>
5532 #include <kmimetype.h>
5533 #include <kfiledialog.h>
5534 #include <qdir.h>
5535 #include <kiconloader.h>
5536 #include <qregexp.h>
5537 #include <qfileinfo.h>
5539 #include <qstringlist.h>
5540 #include <qdir.h>
5541 #include <kurl.h>
5542 #include <krun.h>
5543 #include <kmessagebox.h>
5544 #include <kdeversion.h>
5546 #include "kdirwatch.h"
5547 #include <qstringlist.h>
5548 #include <klineedit.h>
5550 #include <config.h>
5551 #include <qtextcodec.h>
5552 #include <kauthorized.h>
5554 #include "basket.h"
5555 #include "note.h"
5556 #include "notefactory.h"
5557 #include "variouswidgets.h"
5558 #include "linklabel.h"
5559 #include "global.h"
5560 #include "container.h"
5561 #include "xmlwork.h"
5562 #include "settings.h"
5563 #include "popupmenu.h"
5564 #include "debugwindow.h"
5565 #include "exporterdialog.h"
5568 /** Basket */
5570 const int Basket::c_updateTime = 200;
5574 // Remove the note from the basket and delete the associated file
5575 // If the note mirror a file, it will ask before deleting or not the file
5576 // But if askForMirroredFile is false, it willn't ask NOR delete the MIRRORED file
5577 // (it will anyway delete the file if it is not a mirror)
5578 void Basket::delNote(Note *note, bool askForMirroredFile)
5580 //...
5581 if (hasFocus())
5582 focusANote(); // We need note->next() and note->previous() here [BUT deleted note should be hidden]
5583 if (note->isSelected())
5584 note->setSelected(false); //removeSelectedNote();
5586 relayoutNotes();
5587 recolorizeNotes();
5588 resetInsertTo(); // If we delete the first or the last, pointer to it is invalid
5589 save();
5591 if (note == m_startOfShiftSelectionNote)
5592 m_startOfShiftSelectionNote = 0L;
5594 if (isDuringEdit() && m_editor->editedNote() == note)
5595 closeEditor(false);
5596 //...
5600 // Calculate where to paste or drop
5601 void Basket::computeInsertPlace(const QPoint &cursorPosition)
5603 int y = cursorPosition.y();
5605 if (countShown() == 0)
5606 return;
5608 // TODO: Memorize the last hovered note to avoid a new computation on dragMoveEvent !!
5609 // If the mouse is not over the last note, compute which new is :
5610 // TODO: Optimization : start from m_insertAtNote and compare y position to search before or after (or the same)
5611 for (Note *it = firstNote(); it != 0L; it = it->next())
5612 if ( (it->isShown()) && (it->y() + it->height() >= y) && (it->y() < y) ) {
5613 int center = it->y() + (it->height() / 2);
5614 m_insertAtNote = it;
5615 m_insertAfter = y > center;
5616 return;
5618 // Else, there is at least one shown note but cursor hover NO note, so we are after the last shown note
5619 m_insertAtNote = lastShownNote();
5620 m_insertAfter = true;
5622 // Code for rectangular notes :
5623 /*QRect globalRect = it->rect();
5624 globalRect.moveTopLeft(it->pos() + contentsY());
5625 if ( globalRect.contains(curPos) ) {
5626 it->doInterestingThing();
5630 void Basket::dragMoveEvent(QDragMoveEvent* event)
5632 // m_isDuringDrag = true;
5634 if (isLocked())
5635 return;
5637 // FIXME: viewportToContents does NOT work !!!
5638 // QPoint pos = viewportToContents(event->pos());
5639 QPoint pos( event->pos().x() + contentsX(), event->pos().y() + contentsY() );
5641 // if (insertAtCursorPos())
5642 computeInsertPlace(pos);
5644 showFrameInsertTo();
5645 acceptDropEvent(event);
5647 // A workarround since QScrollView::dragAutoScroll seem to have no effect :
5648 ensureVisible(event->pos().x() + contentsX(), event->pos().y() + contentsY(), 30, 30);
5649 // QScrollView::dragMoveEvent(event);
5652 void Basket::dropEvent(QDropEvent *event)
5654 QPoint pos = event->pos();
5655 std::cout << "Drop Event at position " << pos.x() << ":" << pos.y() << std::endl;
5656 m_isDuringDrag = false;
5657 emit resetStatusBarText();
5659 if (isLocked())
5660 return;
5662 NoteFactory::dropNote( event, this, true, event->action(), dynamic_cast<Note*>(event->source()) );
5663 // TODO: need to know if we really inserted an (or several!!!!) note !!!
5664 ensureNoteVisible(lastInsertedNote());
5665 unselectAllBut(lastInsertedNote());
5666 setFocusedNote(lastInsertedNote());
5668 resetInsertTo();
5671 void Basket::moveOnTop()
5673 if (m_countSelecteds == 0)
5674 return;
5676 Note *endOfBrowse = firstShownNote();
5677 Note *topNote = firstNote();
5678 Note *prev;
5679 for (Note *it = lastShownNote(); it != 0L; ) {
5680 prev = it->previous();
5681 if (it->isSelected()) {
5682 m_insertAtNote = topNote;
5683 m_insertAfter = false;
5684 changeNotePlace(it);
5685 topNote = it;
5687 if (it == endOfBrowse)
5688 break;
5689 it = prev;
5691 ensureNoteVisible(firstShownNote());
5692 ensureNoteVisible(m_focusedNote);
5695 void Basket::moveOnBottom()
5697 if (m_countSelecteds == 0)
5698 return;
5700 Note *endOfBrowse = lastShownNote();
5701 Note *bottomNote = lastNote();
5702 Note *next;
5703 for (Note *it = firstShownNote(); it != 0L; ) {
5704 next = it->next();
5705 if (it->isSelected()) {
5706 m_insertAtNote = bottomNote;
5707 m_insertAfter = true;
5708 changeNotePlace(it);
5709 bottomNote = it;
5711 if (it == endOfBrowse)
5712 break;
5713 it = next;
5715 ensureNoteVisible(lastShownNote());
5716 ensureNoteVisible(m_focusedNote);
5719 void Basket::moveNoteUp()
5721 if (m_countSelecteds == 0)
5722 return;
5724 // Begin from the top (important move all selected notes one note up
5725 // AND to quit early if a selected note is the first shown one
5726 for (Note *it = firstShownNote(); it != 0L; it = it->next()) {
5727 if (it->isSelected() && it->isShown()) { // it->isShown() not necessary, but in case...
5728 if (it == firstShownNote())
5729 return; // No way...
5730 m_insertAtNote = nextShownNoteFrom(it, -1); // Previous shown note
5731 if (m_insertAtNote == 0L) { // Should not appends, since it's not the first shown note,
5732 resetInsertTo(); // there SHOULD be one before
5733 return;
5735 m_insertAfter = false;
5736 changeNotePlace(it);
5738 if (it == lastShownNote())
5739 break;
5741 ensureNoteVisible(m_focusedNote);
5744 void Basket::moveNoteDown()
5746 if (m_countSelecteds == 0)
5747 return;
5749 // Begin from the bottom (important move all selected notes one note down
5750 // AND to quit early if a selected note is the last shown one
5751 for (Note *it = lastShownNote(); it != 0L; it = it->previous()) {
5752 if (it->isSelected() && it->isShown()) { // it->isShown() not necessary, but in case...
5753 if (it == lastShownNote())
5754 return; // No way...
5755 m_insertAtNote = nextShownNoteFrom(it, 1); // Next shown note
5756 if (m_insertAtNote == 0L) { // Should not appends, since it's not the last shown note,
5757 resetInsertTo(); // there SHOULD be one before
5758 return;
5760 m_insertAfter = true;
5761 changeNotePlace(it);
5763 if (it == firstShownNote())
5764 break;
5766 ensureNoteVisible(m_focusedNote);
5769 #endif // #if 0
5771 #include "basket.moc"