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