/*************************************************************************** * Copyright (C) 2003 by S�astien Laot * * slaout@linux62.org * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // For sleep() #include #include #include #include #include #include #include #include #include // rand() function #include // seed for rand() #include "basket.h" #include "note.h" #include "notedrag.h" #include "notefactory.h" #include "noteedit.h" #include "tagsedit.h" #include "xmlwork.h" #include "global.h" #include "backgroundmanager.h" #include "settings.h" #include "tools.h" #include "debugwindow.h" #include "exporterdialog.h" #include "config.h" #include "popupmenu.h" #ifdef HAVE_LIBGPGME #include "kgpgme.h" #endif #include /** Class NoteSelection: */ NoteSelection* NoteSelection::nextStacked() { // First, search in the childs: if (firstChild) { if (firstChild->note && firstChild->note->content()) { return firstChild; } else { return firstChild->nextStacked(); } } // Then, in the next: if (next) { if (next->note && next->note->content()) { return next; } else { return next->nextStacked(); } } // And finally, in the parent: NoteSelection *node = parent; while (node) if (node->next) if (node->next->note && node->next->note->content()) return node->next; else return node->next->nextStacked(); else node = node->parent; // Not found: return 0; } NoteSelection* NoteSelection::firstStacked() { if (!this) return 0; if (note && note->content()) return this; else return nextStacked(); } void NoteSelection::append(NoteSelection *node) { if (!this || !node) return; if (firstChild) { NoteSelection *last = firstChild; while (last->next) last = last->next; last->next = node; } else firstChild = node; while (node) { node->parent = this; node = node->next; } } int NoteSelection::count() { if (!this) return 0; int count = 0; for (NoteSelection *node = this; node; node = node->next) if (node->note && node->note->content()) ++count; else count += node->firstChild->count(); return count; } TQValueList NoteSelection::parentGroups() { TQValueList groups; // For each note: for (NoteSelection *node = firstStacked(); node; node = node->nextStacked()) // For each parent groups of the note: for (Note *note = node->note->parentNote(); note; note = note->parentNote()) // Add it (if it was not already in the list): if (!note->isColumn() && !groups.contains(note)) groups.append(note); return groups; } /** Class DecoratedBasket: */ DecoratedBasket::DecoratedBasket(TQWidget *parent, const TQString &folderName, const char *name, WFlags fl) : TQWidget(parent, name, fl) { m_layout = new TQVBoxLayout(this); m_filter = new FilterBar(this); m_basket = new Basket(this, folderName); m_layout->addWidget(m_basket); setFilterBarPosition(Settings::filterOnTop()); m_filter->setShown(true); m_basket->setFocus(); // To avoid the filter bar have focus on load connect( m_filter, TQT_SIGNAL(newFilter(const FilterData&)), m_basket, TQT_SLOT(newFilter(const FilterData&)) ); connect( m_filter, TQT_SIGNAL(escapePressed()), m_basket, TQT_SLOT(cancelFilter()) ); connect( m_filter, TQT_SIGNAL(returnPressed()), m_basket, TQT_SLOT(validateFilter()) ); connect( m_basket, TQT_SIGNAL(postMessage(const TQString&)), Global::bnpView, TQT_SLOT(postStatusbarMessage(const TQString&)) ); connect( m_basket, TQT_SIGNAL(setStatusBarText(const TQString&)), Global::bnpView, TQT_SLOT(setStatusBarHint(const TQString&)) ); connect( m_basket, TQT_SIGNAL(resetStatusBarText()), Global::bnpView, TQT_SLOT(updateStatusBarHint()) ); } DecoratedBasket::~DecoratedBasket() { } void DecoratedBasket::setFilterBarPosition(bool onTop) { m_layout->remove(m_filter); if (onTop) { m_layout->insertWidget(0, m_filter); setTabOrder(this/*(TQWidget*)parent()*/, m_filter); setTabOrder(m_filter, m_basket); setTabOrder(m_basket, (TQWidget*)parent()); } else { m_layout->addWidget(m_filter); setTabOrder(this/*(TQWidget*)parent()*/, m_basket); setTabOrder(m_basket, m_filter); setTabOrder(m_filter, (TQWidget*)parent()); } } void DecoratedBasket::setFilterBarShown(bool show, bool switchFocus) { // m_basket->setShowFilterBar(true);//show); // m_basket->save(); // In this order (m_basket and then m_filter) because setShown(false) // will call resetFilter() that will update actions, and then check the // Ctrl+F action whereas it should be unchecked // FIXME: It's very uggly all those things m_filter->setShown(true);//show); if (show) { if (switchFocus) m_filter->setEditFocus(); } else if (m_filter->hasEditFocus()) m_basket->setFocus(); } void DecoratedBasket::resetFilter() { m_filter->reset(); } /** Class TransparentWidget */ TransparentWidget::TransparentWidget(Basket *basket) : TQWidget(basket->viewport(), "", TQt::WNoAutoErase), m_basket(basket) { setFocusPolicy(TQ_NoFocus); setWFlags(TQt::WNoAutoErase); setMouseTracking(true); // To receive mouseMoveEvents basket->viewport()->installEventFilter(this); } /*void TransparentWidget::reparent(TQWidget *parent, WFlags f, const TQPoint &p, bool showIt) { TQWidget::reparent(parent, TQt::WNoAutoErase, p, showIt); }*/ void TransparentWidget::setPosition(int x, int y) { m_x = x; m_y = y; } void TransparentWidget::paintEvent(TQPaintEvent*event) { TQWidget::paintEvent(event); TQPainter painter(this); // painter.save(); painter.translate(-m_x, -m_y); m_basket->drawContents(&painter, m_x, m_y, width(), height()); // painter.restore(); // painter.setPen(TQt::blue); // painter.drawRect(0, 0, width(), height()); } void TransparentWidget::mouseMoveEvent(TQMouseEvent *event) { TQMouseEvent *translated = new TQMouseEvent(TQEvent::MouseMove, event->pos() + TQPoint(m_x, m_y), event->button(), event->state()); m_basket->contentsMouseMoveEvent(translated); delete translated; } bool TransparentWidget::eventFilter(TQObject */*object*/, TQEvent *event) { // If the parent basket viewport has changed, we should change too: if (event->type() == TQEvent::Paint) update(); return false; // Event not consumed, in every cases (because it's only a notification)! } /** Class Basket: */ const int Basket::FRAME_DELAY = 50/*1500*/; // Delay between two animation "frames" in milliseconds /* * Convenient function (defined in note.cpp !): */ void drawGradient( TQPainter *p, const TQColor &colorTop, const TQColor & colorBottom, int x, int y, int w, int h, bool sunken, bool horz, bool flat ); /* * Defined in note.cpp: */ extern void substractRectOnAreas(const TQRect &rectToSubstract, TQValueList &areas, bool andRemove = true); void debugZone(int zone) { TQString s; switch (zone) { case Note::Handle: s = "Handle"; break; case Note::Group: s = "Group"; break; case Note::TagsArrow: s = "TagsArrow"; break; case Note::Custom0: s = "Custom0"; break; case Note::GroupExpander: s = "GroupExpander"; break; case Note::Content: s = "Content"; break; case Note::Link: s = "Link"; break; case Note::TopInsert: s = "TopInsert"; break; case Note::TopGroup: s = "TopGroup"; break; case Note::BottomInsert: s = "BottomInsert"; break; case Note::BottomGroup: s = "BottomGroup"; break; case Note::BottomColumn: s = "BottomColumn"; break; case Note::None: s = "None"; break; default: if (zone == Note::Emblem0) s = "Emblem0"; else s = "Emblem0+" + TQString::number(zone - Note::Emblem0); break; } std::cout << s << std::endl; } #define FOR_EACH_NOTE(noteVar) \ for (Note *noteVar = firstNote(); noteVar; noteVar = noteVar->next()) void Basket::prependNoteIn(Note *note, Note *in) { if (!note) // No note to prepend: return; if (in) { // The normal case: preparePlug(note); Note *last = note->lastSibling(); for (Note *n = note; n; n = n->next()) n->setParentNote(in); // note->setPrev(0L); last->setNext(in->firstChild()); if (in->firstChild()) in->firstChild()->setPrev(last); in->setFirstChild(note); if (m_loaded) signalCountsChanged(); } else // Prepend it directly in the basket: appendNoteBefore(note, firstNote()); } void Basket::appendNoteIn(Note *note, Note *in) { if (!note) // No note to append: return; if (in) { // The normal case: preparePlug(note); // Note *last = note->lastSibling(); Note *lastChild = in->lastChild(); for (Note *n = note; n; n = n->next()) n->setParentNote(in); note->setPrev(lastChild); // last->setNext(0L); if (!in->firstChild()) in->setFirstChild(note); if (lastChild) lastChild->setNext(note); if (m_loaded) signalCountsChanged(); } else // Prepend it directly in the basket: appendNoteAfter(note, lastNote()); } void Basket::appendNoteAfter(Note *note, Note *after) { if (!note) // No note to append: return; if (!after) // By default, insert after the last note: after = lastNote(); if (m_loaded && after && !after->isFree() && !after->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(after); // if (!alreadyInBasket) preparePlug(note); Note *last = note->lastSibling(); if (after) { // The normal case: for (Note *n = note; n; n = n->next()) n->setParentNote(after->parentNote()); note->setPrev(after); last->setNext(after->next()); after->setNext(note); if (last->next()) last->next()->setPrev(last); } else { // There is no note in the basket: for (Note *n = note; n; n = n->next()) n->setParentNote(0); m_firstNote = note; // note->setPrev(0); // last->setNext(0); } // if (!alreadyInBasket) if (m_loaded) signalCountsChanged(); } void Basket::appendNoteBefore(Note *note, Note *before) { if (!note) // No note to append: return; if (!before) // By default, insert before the first note: before = firstNote(); if (m_loaded && before && !before->isFree() && !before->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(before); preparePlug(note); Note *last = note->lastSibling(); if (before) { // The normal case: for (Note *n = note; n; n = n->next()) n->setParentNote(before->parentNote()); note->setPrev(before->prev()); last->setNext(before); before->setPrev(last); if (note->prev()) note->prev()->setNext(note); else { if (note->parentNote()) note->parentNote()->setFirstChild(note); else m_firstNote = note; } } else { // There is no note in the basket: for (Note *n = note; n; n = n->next()) n->setParentNote(0); m_firstNote = note; // note->setPrev(0); // last->setNext(0); } if (m_loaded) signalCountsChanged(); } DecoratedBasket* Basket::decoration() { return (DecoratedBasket*)parent(); } void Basket::preparePlug(Note *note) { // Select only the new notes, compute the new notes count and the new number of found notes: if (m_loaded) unselectAll(); int count = 0; int founds = 0; Note *last = 0; for (Note *n = note; n; n = n->next()) { if (m_loaded) n->setSelectedRecursivly(true); // Notes should have a parent basket (and they have, so that's OK). count += n->count(); founds += n->newFilter(decoration()->filterData()); last = n; } m_count += count; m_countFounds += founds; // Focus the last inserted note: if (m_loaded && last) { setFocusedNote(last); m_startOfShiftSelectionNote = (last->isGroup() ? last->lastRealChild() : last); } // If some notes don't match (are hidden), tell it to the user: if (m_loaded && founds < count) { if (count == 1) postMessage( i18n("The new note does not match the filter and is hidden.") ); else if (founds == count - 1) postMessage( i18n("A new note does not match the filter and is hidden.") ); else if (founds > 0) postMessage( i18n("Some new notes do not match the filter and are hidden.") ); else postMessage( i18n("The new notes do not match the filter and are hidden.") ); } } void Basket::unplugNote(Note *note) { // If there is nothing to do... if (!note) return; // if (!willBeReplugged) { note->setSelectedRecursivly(false); // To removeSelectedNote() and decrease the selectedsCount. m_count -= note->count(); m_countFounds -= note->newFilter(decoration()->filterData()); signalCountsChanged(); // } // If it was the first note, change the first note: if (m_firstNote == note) m_firstNote = note->next(); // Change previous and next notes: if (note->prev()) note->prev()->setNext(note->next()); if (note->next()) note->next()->setPrev(note->prev()); if (note->parentNote()) { // If it was the first note of a group, change the first note of the group: if (note->parentNote()->firstChild() == note) note->parentNote()->setFirstChild( note->next() ); if (!note->parentNote()->isColumn()) { // Ungroup if still 0 note inside parent group: if ( ! note->parentNote()->firstChild() ) unplugNote(note->parentNote()); // TODO delete // Ungroup if still 1 note inside parent group: else if ( ! note->parentNote()->firstChild()->next() ) ungroupNote(note->parentNote()); } } note->setParentNote(0); note->setPrev(0); note->setNext(0); // 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. } void Basket::ungroupNote(Note *group) { Note *note = group->firstChild(); Note *lastGroupedNote = group; Note *nextNote; // Move all notes after the group (not before, to avoid to change m_firstNote or group->m_firstChild): while (note) { nextNote = note->next(); if (lastGroupedNote->next()) lastGroupedNote->next()->setPrev(note); note->setNext(lastGroupedNote->next()); lastGroupedNote->setNext(note); note->setParentNote(group->parentNote()); note->setPrev(lastGroupedNote); note->setGroupWidth(group->groupWidth() - Note::GROUP_WIDTH); lastGroupedNote = note; note = nextNote; } // Unplug the group: group->setFirstChild(0); unplugNote(group); // TODO: delete relayoutNotes(true); } void Basket::groupNoteBefore(Note *note, Note *with) { if (!note || !with) // No note to group or nowhere to group it: return; // if (m_loaded && before && !with->isFree() && !with->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(with); preparePlug(note); Note *last = note->lastSibling(); Note *group = new Note(this); group->setPrev(with->prev()); group->setNext(with->next()); group->setX(with->x()); group->setY(with->y()); if (with->parentNote() && with->parentNote()->firstChild() == with) with->parentNote()->setFirstChild(group); else if (m_firstNote == with) m_firstNote = group; group->setParentNote(with->parentNote()); group->setFirstChild(note); group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH); if (with->prev()) with->prev()->setNext(group); if (with->next()) with->next()->setPrev(group); with->setParentNote(group); with->setPrev(last); with->setNext(0L); for (Note *n = note; n; n = n->next()) n->setParentNote(group); // note->setPrev(0L); last->setNext(with); if (m_loaded) signalCountsChanged(); } void Basket::groupNoteAfter(Note *note, Note *with) { if (!note || !with) // No note to group or nowhere to group it: return; // if (m_loaded && before && !with->isFree() && !with->isColumn()) for (Note *n = note; n; n = n->next()) n->inheritTagsOf(with); preparePlug(note); // Note *last = note->lastSibling(); Note *group = new Note(this); group->setPrev(with->prev()); group->setNext(with->next()); group->setX(with->x()); group->setY(with->y()); if (with->parentNote() && with->parentNote()->firstChild() == with) with->parentNote()->setFirstChild(group); else if (m_firstNote == with) m_firstNote = group; group->setParentNote(with->parentNote()); group->setFirstChild(with); group->setGroupWidth(with->groupWidth() + Note::GROUP_WIDTH); if (with->prev()) with->prev()->setNext(group); if (with->next()) with->next()->setPrev(group); with->setParentNote(group); with->setPrev(0L); with->setNext(note); for (Note *n = note; n; n = n->next()) n->setParentNote(group); note->setPrev(with); // last->setNext(0L); if (m_loaded) signalCountsChanged(); } void Basket::loadNotes(const TQDomElement ¬es, Note *parent) { Note *note; for (TQDomNode n = notes.firstChild(); !n.isNull(); n = n.nextSibling()) { TQDomElement e = n.toElement(); if (e.isNull()) // Cannot handle that! continue; note = 0; // Load a Group: if (e.tagName() == "group") { note = new Note(this); // 1. Create the group... loadNotes(e, note); // 3. ... And populate it with child notes. int noteCount = note->count(); if (noteCount > 0 || (parent == 0 && !isFreeLayout())) { // But don't remove columns! appendNoteIn(note, parent); // 2. ... Insert it... FIXME: Initially, the if() the insrtion was the step 2. Was it on purpose? // The notes in the group are counted two times (it's why appendNoteIn() was called before loadNotes): m_count -= noteCount;// TODO: Recompute note count every time noteCount() is emitted! m_countFounds -= noteCount; } } // Load a Content-Based Note: if (e.tagName() == "note" || e.tagName() == "item") { // Keep compatible with 0.6.0 Alpha 1 note = new Note(this); // Create the note... NoteFactory__loadNode(XMLWork::getElement(e, "content"), e.attribute("type"), note, /*lazyLoad=*/m_finishLoadOnFirstShow); // ... Populate it with content... if (e.attribute("type") == "text") m_shouldConvertPlainTextNotes = true; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded! appendNoteIn(note, parent); // ... And insert it. // Load dates: if (e.hasAttribute("added")) note->setAddedDate( TQDateTime::fromString(e.attribute("added"), Qt::ISODate)); if (e.hasAttribute("lastModification")) note->setLastModificationDate(TQDateTime::fromString(e.attribute("lastModification"), Qt::ISODate)); } // If we successfully loaded a note: if (note) { // Free Note Properties: if (note->isFree()) { int x = e.attribute("x").toInt(); int y = e.attribute("y").toInt(); note->setX(x < 0 ? 0 : x); note->setY(y < 0 ? 0 : y); } // Resizeable Note Properties: if (note->hasResizer() || note->isColumn()) note->setGroupWidth(e.attribute("width", "200").toInt()); // Group Properties: if (note->isGroup() && !note->isColumn() && XMLWork::trueOrFalse(e.attribute("folded", "false"))) note->toggleFolded(false); // Tags: if (note->content()) { TQString tagsString = XMLWork::getElementText(e, "tags", ""); TQStringList tagsId = TQStringList::split(";", tagsString); for (TQStringList::iterator it = tagsId.begin(); it != tagsId.end(); ++it) { State *state = Tag::stateForId(*it); if (state) note->addState(state, /*orReplace=*/true); } } } // kapp->processEvents(); } } void Basket::saveNotes(TQDomDocument &document, TQDomElement &element, Note *parent) { Note *note = (parent ? parent->firstChild() : firstNote()); while (note) { // Create Element: TQDomElement noteElement = document.createElement(note->isGroup() ? "group" : "note"); element.appendChild(noteElement); // Free Note Properties: if (note->isFree()) { noteElement.setAttribute("x", note->finalX()); noteElement.setAttribute("y", note->finalY()); } // Resizeable Note Properties: if (note->hasResizer()) noteElement.setAttribute("width", note->groupWidth()); // Group Properties: if (note->isGroup() && !note->isColumn()) noteElement.setAttribute("folded", XMLWork::trueOrFalse(note->isFolded())); // Save Content: if (note->content()) { // Save Dates: noteElement.setAttribute("added", note->addedDate().toString(Qt::ISODate) ); noteElement.setAttribute("lastModification", note->lastModificationDate().toString(Qt::ISODate)); // Save Content: noteElement.setAttribute("type", note->content()->lowerTypeName()); TQDomElement content = document.createElement("content"); noteElement.appendChild(content); note->content()->saveToNode(document, content); // Save Tags: if (note->states().count() > 0) { TQString tags; for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) tags += (tags.isEmpty() ? "" : ";") + (*it)->id(); XMLWork::addElement(document, noteElement, "tags", tags); } } else // Save Child Notes: saveNotes(document, noteElement, note); // Go to the Next One: note = note->next(); } } void Basket::loadProperties(const TQDomElement &properties) { // Compute Default Values for When Loading the Properties: TQString defaultBackgroundColor = (backgroundColorSetting().isValid() ? backgroundColorSetting().name() : ""); TQString defaultTextColor = (textColorSetting().isValid() ? textColorSetting().name() : ""); // Load the Properties: TQString icon = XMLWork::getElementText(properties, "icon", this->icon() ); TQString name = XMLWork::getElementText(properties, "name", basketName() ); TQDomElement appearance = XMLWork::getElement(properties, "appearance"); // In 0.6.0-Alpha versions, there was a typo error: "backround" instead of "background" TQString backgroundImage = appearance.attribute( "backgroundImage", appearance.attribute( "backroundImage", backgroundImageName() ) ); TQString backgroundColorString = appearance.attribute( "backgroundColor", appearance.attribute( "backroundColor", defaultBackgroundColor ) ); TQString textColorString = appearance.attribute( "textColor", defaultTextColor ); TQColor backgroundColor = (backgroundColorString.isEmpty() ? TQColor() : TQColor(backgroundColorString)); TQColor textColor = (textColorString.isEmpty() ? TQColor() : TQColor(textColorString) ); TQDomElement disposition = XMLWork::getElement(properties, "disposition"); bool free = XMLWork::trueOrFalse( disposition.attribute( "free", XMLWork::trueOrFalse(isFreeLayout()) ) ); int columnCount = disposition.attribute( "columnCount", TQString::number(this->columnsCount()) ).toInt(); bool mindMap = XMLWork::trueOrFalse( disposition.attribute( "mindMap", XMLWork::trueOrFalse(isMindMap()) ) ); TQDomElement shortcut = XMLWork::getElement(properties, "shortcut"); TQString actionStrings[] = { "show", "globalShow", "globalSwitch" }; KShortcut combination = KShortcut( shortcut.attribute( "combination", m_action->shortcut().toStringInternal() ) ); TQString actionString = shortcut.attribute( "action" ); int action = shortcutAction(); if (actionString == actionStrings[0]) action = 0; if (actionString == actionStrings[1]) action = 1; if (actionString == actionStrings[2]) action = 2; TQDomElement protection = XMLWork::getElement(properties, "protection"); m_encryptionType = protection.attribute( "type" ).toInt(); m_encryptionKey = protection.attribute( "key" ); // Apply the Properties: setDisposition((free ? (mindMap ? 2 : 1) : 0), columnCount); setShortcut(combination, action); setAppearance(icon, name, backgroundImage, backgroundColor, textColor); // Will emit propertiesChanged(this) } void Basket::saveProperties(TQDomDocument &document, TQDomElement &properties) { XMLWork::addElement( document, properties, "name", basketName() ); XMLWork::addElement( document, properties, "icon", icon() ); TQDomElement appearance = document.createElement("appearance"); properties.appendChild(appearance); appearance.setAttribute( "backgroundImage", backgroundImageName() ); appearance.setAttribute( "backgroundColor", backgroundColorSetting().isValid() ? backgroundColorSetting().name() : "" ); appearance.setAttribute( "textColor", textColorSetting().isValid() ? textColorSetting().name() : "" ); TQDomElement disposition = document.createElement("disposition"); properties.appendChild(disposition); disposition.setAttribute( "free", XMLWork::trueOrFalse(isFreeLayout()) ); disposition.setAttribute( "columnCount", TQString::number(columnsCount()) ); disposition.setAttribute( "mindMap", XMLWork::trueOrFalse(isMindMap()) ); TQDomElement shortcut = document.createElement("shortcut"); properties.appendChild(shortcut); TQString actionStrings[] = { "show", "globalShow", "globalSwitch" }; shortcut.setAttribute( "combination", m_action->shortcut().toStringInternal() ); shortcut.setAttribute( "action", actionStrings[shortcutAction()] ); TQDomElement protection = document.createElement("protection"); properties.appendChild(protection); protection.setAttribute( "type", m_encryptionType ); protection.setAttribute( "key", m_encryptionKey ); } void Basket::subscribeBackgroundImages() { if (!m_backgroundImageName.isEmpty()) { Global::backgroundManager->subscribe(m_backgroundImageName); Global::backgroundManager->subscribe(m_backgroundImageName, this->backgroundColor()); Global::backgroundManager->subscribe(m_backgroundImageName, selectionRectInsideColor()); m_backgroundPixmap = Global::backgroundManager->pixmap(m_backgroundImageName); m_opaqueBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, this->backgroundColor()); m_selectedBackgroundPixmap = Global::backgroundManager->opaquePixmap(m_backgroundImageName, selectionRectInsideColor()); m_backgroundTiled = Global::backgroundManager->tiled(m_backgroundImageName); } } void Basket::unsubscribeBackgroundImages() { if (hasBackgroundImage()) { Global::backgroundManager->unsubscribe(m_backgroundImageName); Global::backgroundManager->unsubscribe(m_backgroundImageName, this->backgroundColor()); Global::backgroundManager->unsubscribe(m_backgroundImageName, selectionRectInsideColor()); m_backgroundPixmap = 0; m_opaqueBackgroundPixmap = 0; m_selectedBackgroundPixmap = 0; } } void Basket::setAppearance(const TQString &icon, const TQString &name, const TQString &backgroundImage, const TQColor &backgroundColor, const TQColor &textColor) { unsubscribeBackgroundImages(); m_icon = icon; m_basketName = name; m_backgroundImageName = backgroundImage; m_backgroundColorSetting = backgroundColor; m_textColorSetting = textColor; m_action->setText("BASKET SHORTCUT: " + name); // Basket should ALWAYS have an icon (the "basket" icon by default): TQPixmap iconTest = kapp->iconLoader()->loadIcon(m_icon, KIcon::NoGroup, 16, KIcon::DefaultState, 0L, /*canReturnNull=*/true); if (iconTest.isNull()) m_icon = "basket"; // We don't request the background images if it's not loaded yet (to make the application startup fast). // When the basket is loading (because requested by the user: he/she want to access it) // it load the properties, subscribe to (and then load) the images, update the "Loading..." message with the image, // load all the notes and it's done! if (m_loadingLaunched) subscribeBackgroundImages(); recomputeAllStyles(); // If a note have a tag with the same background color as the basket one, then display a "..." recomputeBlankRects(); // See the drawing of blank areas in Basket::drawContents() unbufferizeAll(); updateContents(); if (isDuringEdit() && m_editor->widget()) { m_editor->widget()->setPaletteBackgroundColor( m_editor->note()->backgroundColor() ); m_editor->widget()->setPaletteForegroundColor( m_editor->note()->textColor() ); } emit propertiesChanged(this); } void Basket::setDisposition(int disposition, int columnCount) { static const int COLUMNS_LAYOUT = 0; static const int FREE_LAYOUT = 1; static const int MINDMAPS_LAYOUT = 2; int currentDisposition = (isFreeLayout() ? (isMindMap() ? MINDMAPS_LAYOUT : FREE_LAYOUT) : COLUMNS_LAYOUT); if (currentDisposition == COLUMNS_LAYOUT && disposition == COLUMNS_LAYOUT) { if (firstNote() && columnCount > m_columnsCount) { // Insert each new columns: for (int i = m_columnsCount; i < columnCount; ++i) { Note *newColumn = new Note(this); insertNote(newColumn, /*clicked=*/lastNote(), /*zone=*/Note::BottomInsert, TQPoint(), /*animateNewPosition=*/false); } } else if (firstNote() && columnCount < m_columnsCount) { Note *column = firstNote(); Note *cuttedNotes = 0; for (int i = 1; i <= m_columnsCount; ++i) { Note *columnToRemove = column; column = column->next(); if (i > columnCount) { // Remove the columns that are too much: unplugNote(columnToRemove); // "Cut" the content in the columns to be deleted: if (columnToRemove->firstChild()) { for (Note *it = columnToRemove->firstChild(); it; it = it->next()) it->setParentNote(0); if (!cuttedNotes) cuttedNotes = columnToRemove->firstChild(); else { Note *lastCuttedNote = cuttedNotes; while (lastCuttedNote->next()) lastCuttedNote = lastCuttedNote->next(); lastCuttedNote->setNext(columnToRemove->firstChild()); columnToRemove->firstChild()->setPrev(lastCuttedNote); } columnToRemove->setFirstChild(0); } } } // Paste the content in the last column: if (cuttedNotes) insertNote(cuttedNotes, /*clicked=*/lastNote(), /*zone=*/Note::BottomColumn, TQPoint(), /*animateNewPosition=*/true); unselectAll(); } if (columnCount != m_columnsCount) { m_columnsCount = (columnCount <= 0 ? 1 : columnCount); equalizeColumnSizes(); // Will relayoutNotes() } } else if (currentDisposition == COLUMNS_LAYOUT && (disposition == FREE_LAYOUT || disposition == MINDMAPS_LAYOUT)) { Note *column = firstNote(); m_columnsCount = 0; // Now, so relayoutNotes() will not relayout the free notes as if they were columns! while (column) { // Move all childs on the first level: Note *nextColumn = column->next(); ungroupNote(column); column = nextColumn; } unselectAll(); m_mindMap = (disposition == MINDMAPS_LAYOUT); relayoutNotes(true); } else if ((currentDisposition == FREE_LAYOUT || currentDisposition == MINDMAPS_LAYOUT) && disposition == COLUMNS_LAYOUT) { if (firstNote()) { // TODO: Reorder notes! // Remove all notes (but keep a reference to them, we're not crazy ;-) ): Note *notes = m_firstNote; m_firstNote = 0; m_count = 0; m_countFounds = 0; // Insert the number of columns that is needed: Note *lastInsertedColumn = 0; for (int i = 0; i < columnCount; ++i) { Note *column = new Note(this); if (lastInsertedColumn) insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, TQPoint(), /*animateNewPosition=*/false); else m_firstNote = column; lastInsertedColumn = column; } // Reinsert the old notes in the first column: insertNote(notes, /*clicked=*/firstNote(), /*zone=*/Note::BottomColumn, TQPoint(), /*animateNewPosition=*/true); unselectAll(); } else { // Insert the number of columns that is needed: Note *lastInsertedColumn = 0; for (int i = 0; i < columnCount; ++i) { Note *column = new Note(this); if (lastInsertedColumn) insertNote(column, /*clicked=*/lastInsertedColumn, /*zone=*/Note::BottomInsert, TQPoint(), /*animateNewPosition=*/false); else m_firstNote = column; lastInsertedColumn = column; } } m_columnsCount = (columnCount <= 0 ? 1 : columnCount); equalizeColumnSizes(); // Will relayoutNotes() } } void Basket::equalizeColumnSizes() { if (!firstNote()) return; // Necessary to know the available space; relayoutNotes(true); int availableSpace = visibleWidth(); int columnWidth = (visibleWidth() - (columnsCount()-1)*Note::GROUP_WIDTH) / columnsCount(); int columnCount = columnsCount(); Note *column = firstNote(); while (column) { int minGroupWidth = column->minRight() - column->x(); if (minGroupWidth > columnWidth) { availableSpace -= minGroupWidth; --columnCount; } column = column->next(); } columnWidth = (availableSpace - (columnsCount()-1)*Note::GROUP_WIDTH) / columnCount; column = firstNote(); while (column) { int minGroupWidth = column->minRight() - column->x(); if (minGroupWidth > columnWidth) column->setGroupWidth(minGroupWidth); else column->setGroupWidth(columnWidth); column = column->next(); } relayoutNotes(true); } void Basket::enableActions() { Global::bnpView->enableActions(); setFocusPolicy(isLocked() ? TQ_NoFocus : TQ_StrongFocus); if (isLocked()) viewport()->setCursor(TQt::ArrowCursor); // When locking, the cursor stays the last form it was } bool Basket::save() { if (!m_loaded) return false; DEBUG_WIN << "Basket[" + folderName() + "]: Saving..."; // Create Document: TQDomDocument document(/*doctype=*/"basket"); TQDomElement root = document.createElement("basket"); document.appendChild(root); // Create Properties Element and Populate It: TQDomElement properties = document.createElement("properties"); saveProperties(document, properties); root.appendChild(properties); // Create Notes Element and Populate It: TQDomElement notes = document.createElement("notes"); saveNotes(document, notes, 0); root.appendChild(notes); // Write to Disk: if(!saveToFile(fullPath() + ".basket", "\n" + document.toString())) { DEBUG_WIN << "Basket[" + folderName() + "]: FAILED to save!"; return false; } Global::bnpView->setUnsavedStatus(false); return true; } void Basket::aboutToBeActivated() { if (m_finishLoadOnFirstShow) { FOR_EACH_NOTE (note) note->finishLazyLoad(); //relayoutNotes(/*animate=*/false); setFocusedNote(0); // So that during the focusInEvent that will come shortly, the FIRST note is focused. if (Settings::playAnimations() && !decoration()->filterBar()->filterData().isFiltering && Global::bnpView->currentBasket() == this) // No animation when filtering all! animateLoad();//TQTimer::singleShot( 0, this, TQT_SLOT(animateLoad()) ); m_finishLoadOnFirstShow = false; } } void Basket::load() { // Load only once: if (m_loadingLaunched) return; m_loadingLaunched = true; // StopWatch::start(10); DEBUG_WIN << "Basket[" + folderName() + "]: Loading..."; TQDomDocument *doc = 0; TQString content; // StopWatch::start(0); if (loadFromFile(fullPath() + ".basket", &content)) { doc = new TQDomDocument("basket"); if ( ! doc->setContent(content) ) { DEBUG_WIN << "Basket[" + folderName() + "]: FAILED to parse XML!"; delete doc; doc = 0; } } if(isEncrypted()) DEBUG_WIN << "Basket is encrypted."; if ( ! doc) { DEBUG_WIN << "Basket[" + folderName() + "]: FAILED to load!"; m_loadingLaunched = false; if (isEncrypted()) m_locked = true; Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar return; } m_locked = false; TQDomElement docElem = doc->documentElement(); TQDomElement properties = XMLWork::getElement(docElem, "properties"); loadProperties(properties); // Since we are loading, this time the background image will also be loaded! // Now that the background image is loaded and subscribed, we display it during the load process: delete doc; updateContents(); // kapp->processEvents(); //BEGIN Compatibility with 0.6.0 Pre-Alpha versions: TQDomElement notes = XMLWork::getElement(docElem, "notes"); if (notes.isNull()) notes = XMLWork::getElement(docElem, "items"); m_watcher->stopScan(); m_shouldConvertPlainTextNotes = false; // Convert Pre-0.6.0 baskets: plain text notes should be converted to rich text ones once all is loaded! // StopWatch::check(0); // StopWatch::start(1); m_finishLoadOnFirstShow = (Global::bnpView->currentBasket() != this); loadNotes(notes, 0L); // StopWatch::check(1); // StopWatch::start(2); if (m_shouldConvertPlainTextNotes) convertTexts(); m_watcher->startScan(); //loadNotes(XMLWork::getElement(docElem, "notes"), 0L); //END // StopWatch::check(0); signalCountsChanged(); if (isColumnsLayout()) { // Count the number of columns: int columnsCount = 0; Note *column = firstNote(); while (column) { ++columnsCount; column = column->next(); } m_columnsCount = columnsCount; } relayoutNotes(false); // On application start, the current basket is not focused yet, so the focus rectangle is not shown when calling focusANote(): if (Global::bnpView->currentBasket() == this) setFocus(); focusANote(); if (Settings::playAnimations() && !decoration()->filterBar()->filterData().isFiltering && Global::bnpView->currentBasket() == this) // No animation when filtering all! animateLoad();//TQTimer::singleShot( 0, this, TQT_SLOT(animateLoad()) ); else m_loaded = true; enableActions(); // StopWatch::check(2); // StopWatch::check(10); } void Basket::filterAgain(bool andEnsureVisible/* = true*/) { newFilter(decoration()->filterData(), andEnsureVisible); } void Basket::filterAgainDelayed() { TQTimer::singleShot( 0, this, TQT_SLOT(filterAgain()) ); } void Basket::newFilter(const FilterData &data, bool andEnsureVisible/* = true*/) { if (!isLoaded()) return; //StopWatch::start(20); m_countFounds = 0; for (Note *note = firstNote(); note; note = note->next()) m_countFounds += note->newFilter(data); relayoutNotes(true); signalCountsChanged(); if (hasFocus()) // if (!hasFocus()), focusANote() will be called at focusInEvent() focusANote(); // so, we avoid de-focus a note if it will be re-shown soon if (andEnsureVisible && m_focusedNote != 0L) ensureNoteVisible(m_focusedNote); Global::bnpView->setFiltering(data.isFiltering); //StopWatch::check(20); } void Basket::cancelFilter() { decoration()->filterBar()->reset(); validateFilter(); } void Basket::validateFilter() { if (isDuringEdit()) m_editor->widget()->setFocus(); else setFocus(); } bool Basket::isFiltering() { return decoration()->filterBar()->filterData().isFiltering; } TQString Basket::fullPath() { return Global::basketsFolder() + folderName(); } TQString Basket::fullPathForFileName(const TQString &fileName) { return fullPath() + fileName; } /*static*/ TQString Basket::fullPathForFolderName(const TQString &folderName) { return Global::basketsFolder() + folderName; } void Basket::setShortcut(KShortcut shortcut, int action) { if(!Global::globalAccel) return; TQString sAction = "global_basket_activate_" + folderName(); Global::globalAccel->remove(sAction); Global::globalAccel->updateConnections(); m_action->setShortcut(shortcut); m_shortcutAction = action; if (action > 0) Global::globalAccel->insert(sAction, m_action->text(), /*whatsThis=*/"", m_action->shortcut(), KShortcut(), TQT_TQOBJECT(this), TQT_SLOT(activatedShortcut()), /*configurable=*/false); Global::globalAccel->updateConnections(); } void Basket::activatedShortcut() { Global::bnpView->setCurrentBasket(this); if (m_shortcutAction == 1) Global::bnpView->setActive(true); } void Basket::signalCountsChanged() { if (!m_timerCountsChanged.isActive()) m_timerCountsChanged.start(0/*ms*/, /*singleShot=*/true); } void Basket::countsChangedTimeOut() { emit countsChanged(this); } Basket::Basket(TQWidget *parent, const TQString &folderName) : TQScrollView(parent), TQToolTip(viewport()), m_noActionOnMouseRelease(false), m_ignoreCloseEditorOnNextMouseRelease(false), m_pressPos(-100, -100), m_canDrag(false), m_firstNote(0), m_columnsCount(1), m_mindMap(false), m_resizingNote(0L), m_pickedResizer(0), m_movingNote(0L), m_pickedHandle(0, 0), m_clickedToInsert(0), m_zoneToInsert(0), m_posToInsert(-1, -1), m_isInsertPopupMenu(false), m_loaded(false), m_loadingLaunched(false), m_locked(false), m_decryptBox(0), m_button(0), m_encryptionType(NoEncryption), #ifdef HAVE_LIBGPGME m_gpg(0), #endif m_backgroundPixmap(0), m_opaqueBackgroundPixmap(0), m_selectedBackgroundPixmap(0), m_action(0), m_shortcutAction(0), m_hoveredNote(0), m_hoveredZone(Note::None), m_lockedHovering(false), m_underMouse(false), m_inserterRect(), m_inserterShown(false), m_inserterSplit(true), m_inserterTop(false), m_inserterGroup(false), m_isSelecting(false), m_selectionStarted(false), m_count(0), m_countFounds(0), m_countSelecteds(0), m_folderName(folderName), m_editor(0), m_leftEditorBorder(0), m_rightEditorBorder(0), m_redirectEditActions(false), m_editorWidth(-1), m_editorHeight(-1), m_doNotCloseEditor(false), m_editParagraph(0), m_editIndex(0), m_isDuringDrag(false), m_draggedNotes(), m_focusedNote(0), m_startOfShiftSelectionNote(0), m_finishLoadOnFirstShow(false), m_relayoutOnNextShow(false) { TQString sAction = "local_basket_activate_" + folderName; m_action = new KAction("FAKE TEXT", "FAKE ICON", KShortcut(), TQT_TQOBJECT(this), TQT_SLOT(activatedShortcut()), Global::bnpView->actionCollection(), sAction); m_action->setShortcutConfigurable(false); // We do it in the basket properties dialog (and keep it in sync with the global one) if (!m_folderName.endsWith("/")) m_folderName += "/"; setFocusPolicy(TQ_StrongFocus); setWFlags(TQt::WNoAutoErase); setDragAutoScroll(true); // By default, there is no corner widget: we set one for the corner area to be painted! // If we don't set one and there are two scrollbars present, slowly resizing up the window show graphical glitches in that area! m_cornerWidget = new TQWidget(this); setCornerWidget(m_cornerWidget); viewport()->setAcceptDrops(true); viewport()->setMouseTracking(true); viewport()->setBackgroundMode(NoBackground); // Do not clear the widget before paintEvent() because we always draw every pixels (faster and flicker-free) // File Watcher: m_watcher = new KDirWatch(TQT_TQOBJECT(this)); connect( m_watcher, TQT_SIGNAL(dirty(const TQString&)), this, TQT_SLOT(watchedFileModified(const TQString&)) ); connect( m_watcher, TQT_SIGNAL(deleted(const TQString&)), this, TQT_SLOT(watchedFileDeleted(const TQString&)) ); connect( &m_watcherTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(updateModifiedNotes()) ); // Various Connections: connect( &m_animationTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(animateObjects()) ); connect( &m_autoScrollSelectionTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(doAutoScrollSelection()) ); connect( &m_timerCountsChanged, TQT_SIGNAL(timeout()), this, TQT_SLOT(countsChangedTimeOut()) ); connect( &m_inactivityAutoSaveTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(inactivityAutoSaveTimeout()) ); connect( &m_inactivityAutoLockTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(inactivityAutoLockTimeout()) ); connect( this, TQT_SIGNAL(contentsMoving(int, int)), this, TQT_SLOT(contentsMoved()) ); #ifdef HAVE_LIBGPGME m_gpg = new KGpgMe(); #endif m_locked = isFileEncrypted(); } void Basket::contentsMoved() { // This slot is called BEFORE the content move, so we delay the hover effects: TQTimer::singleShot(0, this, TQT_SLOT(doHoverEffects())); } void Basket::enterEvent(TQEvent *) { m_underMouse = true; doHoverEffects(); } void Basket::leaveEvent(TQEvent *) { m_underMouse = false; doHoverEffects(); if (m_lockedHovering) return; removeInserter(); if (m_hoveredNote) { m_hoveredNote->setHovered(false); m_hoveredNote->setHoveredZone(Note::None); updateNote(m_hoveredNote); } m_hoveredNote = 0; } void Basket::setFocusIfNotInPopupMenu() { if (!kapp->activePopupWidget()) { if (isDuringEdit()) { m_editor->widget()->setFocus(); } else { setFocus(); } } } void Basket::contentsMousePressEvent(TQMouseEvent *event) { // If user click the basket, focus it! // The focus is delayed because if the click results in showing a popup menu, // the interface flicker by showing the focused rectangle (as the basket gets focus) // and immediatly removing it (because the popup menu now have focus). if (!isDuringEdit()) TQTimer::singleShot(0, this, TQT_SLOT(setFocusIfNotInPopupMenu())); // Convenient variables: bool controlPressed = event->stateAfter() & TQt::ControlButton; bool shiftPressed = event->stateAfter() & TQt::ShiftButton; // Do nothing if we disabled the click some milliseconds sooner. // For instance when a popup menu has been closed with click, we should not do action: if (event->button() == Qt::LeftButton && (kapp->activePopupWidget() || m_lastDisableClick.msecsTo(TQTime::currentTime()) <= 80)) { doHoverEffects(); m_noActionOnMouseRelease = true; // But we allow to select: // The code is the same as at the bottom of this method: if (event->button() == Qt::LeftButton) { m_selectionStarted = true; m_selectionBeginPoint = event->pos(); m_selectionInvert = controlPressed || shiftPressed; } return; } // Figure out what is the clicked note and zone: Note *clicked = noteAt(event->pos().x(), event->pos().y()); Note::Zone zone = (clicked ? clicked->zoneAt( event->pos() - TQPoint(clicked->x(), clicked->y()) ) : Note::None); // Popup Tags menu: if (zone == Note::TagsArrow && !controlPressed && !shiftPressed && event->button() != Qt::MidButton) { if (!clicked->isSelected()) unselectAllBut(clicked); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = clicked; m_noActionOnMouseRelease = true; popupTagsMenu(clicked); return; } if (event->button() == Qt::LeftButton) { // Prepare to allow drag and drop when moving mouse further: if ( (zone == Note::Handle || zone == Note::Group) || (clicked && clicked->isSelected() && (zone == Note::TagsArrow || zone == Note::Custom0 || zone == Note::Content || zone == Note::Link /**/ || zone >= Note::Emblem0 /**/)) ) { if (!shiftPressed && !controlPressed) { m_pressPos = event->pos(); // TODO: Allow to drag emblems to assign them to other notes. Then don't allow drag at Emblem0!! m_canDrag = true; // Saving where we were editing, because during a drag, the mouse can fly over the text edit and move the cursor position: if (m_editor && m_editor->textEdit()) { TQTextEdit *editor = m_editor->textEdit(); editor->getCursorPosition(&m_editParagraph, &m_editIndex); } } } // Initializing Resizer move: if (zone == Note::Resizer) { m_resizingNote = clicked; m_pickedResizer = event->pos().x() - clicked->rightLimit(); m_noActionOnMouseRelease = true; m_lockedHovering = true; return; } // Select note(s): if (zone == Note::Handle || zone == Note::Group || (zone == Note::GroupExpander && (controlPressed || shiftPressed))) { Note *end = clicked; if (clicked->isGroup() && shiftPressed) { if (clicked->contains(m_startOfShiftSelectionNote)) { m_startOfShiftSelectionNote = clicked->firstRealChild(); end = clicked->lastRealChild(); } else if (clicked->firstRealChild()->isAfter(m_startOfShiftSelectionNote)) end = clicked->lastRealChild(); else end = clicked->firstRealChild(); } if (controlPressed && shiftPressed) selectRange(m_startOfShiftSelectionNote, end, /*unselectOthers=*/false); else if (shiftPressed) selectRange(m_startOfShiftSelectionNote, end); else if (controlPressed) clicked->setSelectedRecursivly(!clicked->allSelected()); else if (!clicked->allSelected()) unselectAllBut(clicked); setFocusedNote(end); /// /// /// m_startOfShiftSelectionNote = (end->isGroup() ? end->firstRealChild() : end); //m_noActionOnMouseRelease = false; m_noActionOnMouseRelease = true; return; } // MOVED TO RELEASE EVENT: /* else if (clicked && zone != Note::None && zone != Note::BottomColumn && zone != Note::Resizer && (controlPressed || shiftPressed)) { if (controlPressed && shiftPressed) selectRange(m_startOfShiftSelectionNote, clicked, / *unselectOthers=* /false); else if (shiftPressed) selectRange(m_startOfShiftSelectionNote, clicked); else if (controlPressed) clicked->setSelectedRecursivly(!clicked->allSelected()); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked); m_noActionOnMouseRelease = true; return; }*/ // Initializing Note move: /* if ((zone == Note::Group || zone == Note::Handle) && clicked->isFree()) { m_movingNote = clicked; m_pickedHandle = TQPoint(event->pos().x() - clicked->x(), event->pos().y() - clicked->y()); m_noActionOnMouseRelease = true; m_lockedHovering = true; return; } */ // Folding/Unfolding group: if (zone == Note::GroupExpander) { clicked->toggleFolded(Settings::playAnimations()); relayoutNotes(true); m_noActionOnMouseRelease = true; return; } } // Popup menu for tag emblems: if (event->button() == Qt::RightButton && zone >= Note::Emblem0) { if (!clicked->isSelected()) unselectAllBut(clicked); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = clicked; popupEmblemMenu(clicked, zone - Note::Emblem0); m_noActionOnMouseRelease = true; return; } // Insertion Popup Menu: if ( (event->button() == Qt::RightButton) && ((!clicked && isFreeLayout()) || (clicked && (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn))) ) { unselectAll(); m_clickedToInsert = clicked; m_zoneToInsert = zone; m_posToInsert = event->pos(); KPopupMenu* menu = (KPopupMenu*)(Global::bnpView->popupMenu("insert_popup")); if (!menu->title(/*id=*/120).isEmpty()) // If we already added a title, remove it because it would be kept and then added several times: menu->removeItem(/*id=*/120); 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); setInsertPopupMenu(); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(delayedCancelInsertPopupMenu()) ); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(unlockHovering()) ); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(disableNextClick()) ); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(hideInsertPopupMenu()) ); doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually! m_lockedHovering = true; menu->exec(TQCursor::pos()); m_noActionOnMouseRelease = true; return; } // Note Context Menu: if (event->button() == Qt::RightButton && clicked && !clicked->isColumn() && zone != Note::Resizer) { if (!clicked->isSelected()) unselectAllBut(clicked); setFocusedNote(clicked); /// /// /// if (editedNote() == clicked) { closeEditor(); clicked->setSelected(true); } m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked); TQPopupMenu* menu = Global::bnpView->popupMenu("note_popup"); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(unlockHovering()) ); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(disableNextClick()) ); doHoverEffects(clicked, zone); // In the case where another popup menu was open, we should do that manually! m_lockedHovering = true; menu->exec(TQCursor::pos()); m_noActionOnMouseRelease = true; return; } // Paste selection under cursor (but not "create new primary note under cursor" because this is on moveRelease): if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) { if ((Settings::middleAction() != 0) && (event->state() == TQt::ShiftButton)) { m_clickedToInsert = clicked; m_zoneToInsert = zone; m_posToInsert = event->pos(); closeEditor(); removeInserter(); // If clicked at an insertion line and the new note shows a dialog for editing, NoteType::Id type = (NoteType::Id)0; // hide that inserter before the note edition instead of after the dialog is closed switch (Settings::middleAction()) { case 1: m_isInsertPopupMenu = true; pasteNote(); break; case 2: type = NoteType::Image; break; case 3: type = NoteType::Link; break; case 4: type = NoteType::Launcher; break; default: m_noActionOnMouseRelease = false; return; // Other options should be done on mouse release (to avoid mouse release to cancel them!) /* case 5: type = NoteType::Color; break; case 6: Global::bnpView->grabScreenshot(); break; case 7: Global::bnpView->slotColorFromScreen(); break; case 8: Global::bnpView->insertWizard(3); // loadFromFile break; case 9: Global::bnpView->insertWizard(1); // importKMenuLauncher break; case 10: Global::bnpView->insertWizard(2); // importIcon break; */ } if (type != 0) { m_ignoreCloseEditorOnNextMouseRelease = true; Global::bnpView->insertEmpty(type); } } else { if (clicked) zone = clicked->zoneAt( event->pos() - TQPoint(clicked->x(), clicked->y()), true ); closeEditor(); clickedToInsert(event, clicked, zone); save(); } m_noActionOnMouseRelease = true; return; } // Finally, no action has been done durint pressEvent, so an action can be done on releaseEvent: m_noActionOnMouseRelease = false; /* Selection scenario: * On contentsMousePressEvent, put m_selectionStarted to true and init Begin and End selection point. * On contentsMouseMoveEvent, if m_selectionStarted, update End selection point, update selection rect, * and if it's larger, switching to m_isSelecting mode: we can draw the selection rectangle. */ // Prepare selection: if (event->button() == Qt::LeftButton) { m_selectionStarted = true; m_selectionBeginPoint = event->pos(); // We usualy invert the selection with the Ctrl key, but some environements (like GNOME or The Gimp) do it with the Shift key. // Since the Shift key has no specific usage, we allow to invert selection ALSO with Shift for Gimp people m_selectionInvert = controlPressed || shiftPressed; } } void Basket::delayedCancelInsertPopupMenu() { TQTimer::singleShot( 0, this, TQT_SLOT(cancelInsertPopupMenu()) ); } void Basket::contentsContextMenuEvent(TQContextMenuEvent *event) { if (event->reason() == TQContextMenuEvent::Keyboard) { if (countFounds/*countShown*/() == 0) { // TODO: Count shown!! TQRect basketRect( mapToGlobal(TQPoint(0,0)), size() ); TQPopupMenu *menu = Global::bnpView->popupMenu("insert_popup"); setInsertPopupMenu(); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(delayedCancelInsertPopupMenu()) ); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(unlockHovering()) ); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(disableNextClick()) ); removeInserter(); m_lockedHovering = true; PopupMenu::execAtRectCenter(*menu, basketRect); // Popup at center or the basket } else { if ( ! m_focusedNote->isSelected() ) unselectAllBut(m_focusedNote); setFocusedNote(m_focusedNote); /// /// /// m_startOfShiftSelectionNote = (m_focusedNote->isGroup() ? m_focusedNote->firstRealChild() : m_focusedNote); // Popup at bottom (or top) of the focused note, if visible : TQPopupMenu *menu = Global::bnpView->popupMenu("note_popup"); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(unlockHovering()) ); connect( menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(disableNextClick()) ); doHoverEffects(m_focusedNote, Note::Content); // In the case where another popup menu was open, we should do that manually! m_lockedHovering = true; PopupMenu::execAtRectBottom(*menu, noteVisibleRect(m_focusedNote), true); } } } TQRect Basket::noteVisibleRect(Note *note) { TQRect rect( contentsToViewport(TQPoint(note->x(), note->y())), TQSize(note->width(),note->height()) ); TQPoint basketPoint = mapToGlobal(TQPoint(0,0)); rect.moveTopLeft( rect.topLeft() + basketPoint + TQPoint(frameWidth(), frameWidth()) ); // Now, rect contain the global note rectangle on the screen. // We have to clip it by the basket widget : if (rect.bottom() > basketPoint.y() + visibleHeight() + 1) { // Bottom too... bottom rect.setBottom( basketPoint.y() + visibleHeight() + 1); if (rect.height() <= 0) // Have at least one visible pixel of height rect.setTop(rect.bottom()); } if (rect.top() < basketPoint.y() + frameWidth()) { // Top too... top rect.setTop( basketPoint.y() + frameWidth()); if (rect.height() <= 0) rect.setBottom(rect.top()); } if (rect.right() > basketPoint.x() + visibleWidth() + 1) { // Right too... right rect.setRight( basketPoint.x() + visibleWidth() + 1); if (rect.width() <= 0) // Have at least one visible pixel of width rect.setLeft(rect.right()); } if (rect.left() < basketPoint.x() + frameWidth()) { // Left too... left rect.setLeft( basketPoint.x() + frameWidth()); if (rect.width() <= 0) rect.setRight(rect.left()); } return rect; } void Basket::disableNextClick() { m_lastDisableClick = TQTime::currentTime(); } void Basket::recomputeAllStyles() { FOR_EACH_NOTE (note) note->recomputeAllStyles(); } void Basket::removedStates(const TQValueList &deletedStates) { bool modifiedBasket = false; FOR_EACH_NOTE (note) if (note->removedStates(deletedStates)) modifiedBasket = true; if (modifiedBasket) save(); } void Basket::insertNote(Note *note, Note *clicked, int zone, const TQPoint &pos, bool animateNewPosition) { if (!note) { std::cout << "Wanted to insert NO note" << std::endl; return; } if (clicked && zone == Note::BottomColumn) { // When inserting at the bottom of a column, it's obvious the new note SHOULD inherit tags. // We ensure that by changing the insertion point after the last note of the column: Note *last = clicked->lastChild(); if (last) { clicked = last; zone = Note::BottomInsert; } } /// Insertion at the bottom of a column: if (clicked && zone == Note::BottomColumn) { note->setWidth(clicked->rightLimit() - clicked->x()); Note *lastChild = clicked->lastChild(); if (!animateNewPosition || !Settings::playAnimations()) for (Note *n = note; n; n = n->next()) { n->setXRecursivly(clicked->x()); n->setYRecursivly((lastChild ? lastChild : clicked)->bottom() + 1); } appendNoteIn(note, clicked); /// Insertion relative to a note (top/bottom, insert/group): } else if (clicked) { note->setWidth(clicked->width()); if (!animateNewPosition || !Settings::playAnimations()) for (Note *n = note; n; n = n->next()) { if (zone == Note::TopGroup || zone == Note::BottomGroup) n->setXRecursivly(clicked->x() + Note::GROUP_WIDTH); else n->setXRecursivly(clicked->x()); if (zone == Note::TopInsert || zone == Note::TopGroup) n->setYRecursivly(clicked->y()); else n->setYRecursivly(clicked->bottom() + 1); } if (zone == Note::TopInsert) { appendNoteBefore(note, clicked); } else if (zone == Note::BottomInsert) { appendNoteAfter(note, clicked); } else if (zone == Note::TopGroup) { groupNoteBefore(note, clicked); } else if (zone == Note::BottomGroup) { groupNoteAfter(note, clicked); } /// Free insertion: } else if (isFreeLayout()) { // Group if note have siblings: if (note->next()) { Note *group = new Note(this); for (Note *n = note; n; n = n->next()) n->setParentNote(group); group->setFirstChild(note); note = group; } // Insert at cursor position: const int initialWidth = 250; note->setWidth(note->isGroup() ? Note::GROUP_WIDTH : initialWidth); if (note->isGroup() && note->firstChild()) note->setInitialHeight(note->firstChild()->height()); //note->setGroupWidth(initialWidth); if (animateNewPosition && Settings::playAnimations()) note->setFinalPosition(pos.x(), pos.y()); else { note->setXRecursivly(pos.x()); note->setYRecursivly(pos.y()); } appendNoteAfter(note, lastNote()); } relayoutNotes(true); } void Basket::clickedToInsert(TQMouseEvent *event, Note *clicked, /*Note::Zone*/int zone) { Note *note; if (event->button() == Qt::MidButton) note = NoteFactory::dropNote(KApplication::clipboard()->data(TQClipboard::Selection), this); else note = NoteFactory::createNoteText("", this); if (!note) return; insertNote(note, clicked, zone, event->pos(), /*animateNewPosition=*/false); // ensureNoteVisible(lastInsertedNote()); // TODO: in insertNote() if (event->button() != Qt::MidButton) { 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. closeEditor(); noteEdit(note, /*justAdded=*/true); } } void Basket::contentsDragEnterEvent(TQDragEnterEvent *event) { m_isDuringDrag = true; Global::bnpView->updateStatusBarHint(); if (NoteDrag::basketOf(event) == this) m_draggedNotes = NoteDrag::notesOf(event); } void Basket::contentsDragMoveEvent(TQDragMoveEvent *event) { // m_isDuringDrag = true; // if (isLocked()) // return; // FIXME: viewportToContents does NOT work !!! // TQPoint pos = viewportToContents(event->pos()); // TQPoint pos( event->pos().x() + contentsX(), event->pos().y() + contentsY() ); // if (insertAtCursorPos()) // computeInsertPlace(pos); doHoverEffects(event->pos()); // showFrameInsertTo(); if (isFreeLayout() || noteAt(event->pos().x(), event->pos().y())) // Cursor before rightLimit() or hovering the dragged source notes acceptDropEvent(event); else { event->acceptAction(false); event->accept(false); } /* Note *hoveredNote = noteAt(event->pos().x(), event->pos().y()); if ( (isColumnsLayout() && !hoveredNote) || (draggedNotes().contains(hoveredNote)) ) { event->acceptAction(false); event->accept(false); } else acceptDropEvent(event);*/ // A workarround since TQScrollView::dragAutoScroll seem to have no effect : // ensureVisible(event->pos().x() + contentsX(), event->pos().y() + contentsY(), 30, 30); // TQScrollView::dragMoveEvent(event); } void Basket::contentsDragLeaveEvent(TQDragLeaveEvent*) { // resetInsertTo(); m_isDuringDrag = false; m_draggedNotes.clear(); m_noActionOnMouseRelease = true; emit resetStatusBarText(); doHoverEffects(); } void Basket::contentsDropEvent(TQDropEvent *event) { TQPoint pos = event->pos(); std::cout << "Contents Drop Event at position " << pos.x() << ":" << pos.y() << std::endl; m_isDuringDrag = false; emit resetStatusBarText(); // if (isLocked()) // return; // Do NOT check the bottom&right borders. // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars), // the note is first removed, and relayoutNotes() compute the new height that is smaller // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!! // Should, of course, not return 0: Note *clicked = noteAt(event->pos().x(), event->pos().y()); if (NoteFactory::movingNotesInTheSameBasket(event, this, event->action()) && event->action() == TQDropEvent::Move) { m_doNotCloseEditor = true; } Note *note = NoteFactory::dropNote( event, this, true, event->action(), dynamic_cast(event->source()) ); if (note) { Note::Zone zone = (clicked ? clicked->zoneAt( event->pos() - TQPoint(clicked->x(), clicked->y()), /*toAdd=*/true ) : Note::None); bool animateNewPosition = NoteFactory::movingNotesInTheSameBasket(event, this, event->action()); if (animateNewPosition) { FOR_EACH_NOTE (n) n->setOnTop(false); // FOR_EACH_NOTE_IN_CHUNK(note) for (Note *n = note; n; n = n->next()) n->setOnTop(true); } insertNote(note, clicked, zone, event->pos(), animateNewPosition); // If moved a note on bottom, contentsHeight has been disminished, then view scrolled up, and we should re-scroll the view down: ensureNoteVisible(note); // if (event->button() != Qt::MidButton) { // 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. // } // resetInsertTo(); // doHoverEffects(); called by insertNote() save(); } m_draggedNotes.clear(); m_doNotCloseEditor = false; // When starting the drag, we saved where we were editing. // This is because during a drag, the mouse can fly over the text edit and move the cursor position, and even HIDE the cursor. // So we re-show the cursor, and re-position it at the right place: if (m_editor && m_editor->textEdit()) { TQTextEdit *editor = m_editor->textEdit(); editor->setCursorPosition(m_editParagraph, m_editIndex); } } // handles dropping of a note to basket that is not shown // (usually through its entry in the basket list) void Basket::blindDrop(TQDropEvent* event) { if (!m_isInsertPopupMenu && redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->paste(); else if (m_editor->lineEdit()) m_editor->lineEdit()->paste(); } else { if (!isLoaded()) { Global::bnpView->showPassiveLoading(this); load(); } closeEditor(); unselectAll(); Note *note = NoteFactory::dropNote( event, this, true, event->action(), dynamic_cast(event->source()) ); if (note) { insertCreatedNote(note); //unselectAllBut(note); if (Settings::usePassivePopup()) Global::bnpView->showPassiveDropped(i18n("Dropped to basket %1")); } } save(); } void Basket::insertEmptyNote(int type) { if (!isLoaded()) load(); if (isDuringEdit()) closeEditor(); Note *note = NoteFactory::createEmptyNote((NoteType::Id)type, this); insertCreatedNote(note/*, / *edit=* /true*/); noteEdit(note, /*justAdded=*/true); } void Basket::insertWizard(int type) { saveInsertionData(); Note *note = 0; switch (type) { default: case 1: note = NoteFactory::importKMenuLauncher(this); break; case 2: note = NoteFactory::importIcon(this); break; case 3: note = NoteFactory::importFileContent(this); break; } if (!note) return; restoreInsertionData(); insertCreatedNote(note); unselectAllBut(note); resetInsertionData(); } void Basket::insertColor(const TQColor &color) { Note *note = NoteFactory::createNoteColor(color, this); restoreInsertionData(); insertCreatedNote(note); unselectAllBut(note); resetInsertionData(); } void Basket::insertImage(const TQPixmap &image) { Note *note = NoteFactory::createNoteImage(image, this); restoreInsertionData(); insertCreatedNote(note); unselectAllBut(note); resetInsertionData(); } void Basket::pasteNote(TQClipboard::Mode mode) { if (!m_isInsertPopupMenu && redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->paste(); else if (m_editor->lineEdit()) m_editor->lineEdit()->paste(); } else { if (!isLoaded()) { Global::bnpView->showPassiveLoading(this); load(); } closeEditor(); unselectAll(); Note *note = NoteFactory::dropNote(KApplication::clipboard()->data(mode), this); if (note) { insertCreatedNote(note); //unselectAllBut(note); } } } void Basket::insertCreatedNote(Note *note) { // Get the insertion data if the user clicked inside the basket: Note *clicked = m_clickedToInsert; int zone = m_zoneToInsert; TQPoint pos = m_posToInsert; // If it isn't the case, use the default position: if (!clicked && (pos.x() < 0 || pos.y() < 0)) { // Insert right after the focused note: focusANote(); if (m_focusedNote) { clicked = m_focusedNote; zone = (m_focusedNote->isFree() ? Note::BottomGroup : Note::BottomInsert); pos = TQPoint(m_focusedNote->x(), m_focusedNote->finalBottom()); // Insert at the end of the last column: } else if (isColumnsLayout()) { Note *column = /*(Settings::newNotesPlace == 0 ?*/ firstNote() /*: lastNote())*/; /*if (Settings::newNotesPlace == 0 && column->firstChild()) { // On Top, if at least one child in the column clicked = column->firstChild(); zone = Note::TopInsert; } else { // On Bottom*/ clicked = column; zone = Note::BottomColumn; /*}*/ // Insert at free position: } else { pos = TQPoint(0, 0); } } insertNote(note, clicked, zone, pos); // ensureNoteVisible(lastInsertedNote()); 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. // resetInsertTo(); save(); } void Basket::saveInsertionData() { m_savedClickedToInsert = m_clickedToInsert; m_savedZoneToInsert = m_zoneToInsert; m_savedPosToInsert = m_posToInsert; } void Basket::restoreInsertionData() { m_clickedToInsert = m_savedClickedToInsert; m_zoneToInsert = m_savedZoneToInsert; m_posToInsert = m_savedPosToInsert; } void Basket::resetInsertionData() { m_clickedToInsert = 0; m_zoneToInsert = 0; m_posToInsert = TQPoint(-1, -1); } void Basket::hideInsertPopupMenu() { TQTimer::singleShot( 50/*ms*/, this, TQT_SLOT(timeoutHideInsertPopupMenu()) ); } void Basket::timeoutHideInsertPopupMenu() { resetInsertionData(); } void Basket::acceptDropEvent(TQDropEvent *event, bool preCond) { // FIXME: Should not accept all actions! Or not all actions (link not supported?!) event->acceptAction(preCond && 1); event->accept(preCond); } void Basket::contentsMouseReleaseEvent(TQMouseEvent *event) { // Now disallow drag: m_canDrag = false; // Cancel Resizer move: if (m_resizingNote) { m_resizingNote = 0; m_pickedResizer = 0; m_lockedHovering = false; doHoverEffects(); save(); } // Cancel Note move: /* if (m_movingNote) { m_movingNote = 0; m_pickedHandle = TQPoint(0, 0); m_lockedHovering = false; //doHoverEffects(); save(); } */ // Cancel Selection rectangle: if (m_isSelecting) { m_isSelecting = false; stopAutoScrollSelection(); resetWasInLastSelectionRect(); doHoverEffects(); updateContents(m_selectionRect); } m_selectionStarted = false; Note *clicked = noteAt(event->pos().x(), event->pos().y()); Note::Zone zone = (clicked ? clicked->zoneAt( event->pos() - TQPoint(clicked->x(), clicked->y()) ) : Note::None); if ((zone == Note::Handle || zone == Note::Group) && editedNote() && editedNote() == clicked) { if (m_ignoreCloseEditorOnNextMouseRelease) m_ignoreCloseEditorOnNextMouseRelease = false; else { bool editedNoteStillThere = closeEditor(); if (editedNoteStillThere) //clicked->setSelected(true); unselectAllBut(clicked); } } if (event->stateAfter() == 0 && (zone == Note::Group || zone == Note::Handle)) { closeEditor(); unselectAllBut(clicked); } // Do nothing if an action has already been made during mousePressEvent, // or if user made a selection and canceled it by regressing to a very small rectangle. if (m_noActionOnMouseRelease) return; // We immediatly set it to true, to avoid actions set on mouseRelease if NO mousePress event has been triggered. // This is the case when a popup menu is shown, and user click to the basket area to close it: // the menu then receive the mousePress event and the basket area ONLY receive the mouseRelease event. // Obviously, nothing should be done in this case: m_noActionOnMouseRelease = true; if (event->button() == Qt::MidButton && zone != Note::Resizer && (!isDuringEdit() || clicked != editedNote())) { if ((Settings::middleAction() != 0) && (event->stateAfter() == TQt::ShiftButton)) { m_clickedToInsert = clicked; m_zoneToInsert = zone; m_posToInsert = event->pos(); closeEditor(); removeInserter(); // If clicked at an insertion line and the new note shows a dialog for editing, NoteType::Id type = (NoteType::Id)0; // hide that inserter before the note edition instead of after the dialog is closed switch (Settings::middleAction()) { case 5: type = NoteType::Color; break; case 6: Global::bnpView->grabScreenshot(); return; case 7: Global::bnpView->slotColorFromScreen(); return; case 8: Global::bnpView->insertWizard(3); // loadFromFile return; case 9: Global::bnpView->insertWizard(1); // importKMenuLauncher return; case 10: Global::bnpView->insertWizard(2); // importIcon return; } if (type != 0) { m_ignoreCloseEditorOnNextMouseRelease = true; Global::bnpView->insertEmpty(type); return; } } } // Note *clicked = noteAt(event->pos().x(), event->pos().y()); if ( ! clicked ) { if (isFreeLayout() && event->button() == Qt::LeftButton) { clickedToInsert(event); save(); } return; } // Note::Zone zone = clicked->zoneAt( event->pos() - TQPoint(clicked->x(), clicked->y()) ); // Convenient variables: bool controlPressed = event->stateAfter() & TQt::ControlButton; bool shiftPressed = event->stateAfter() & TQt::ShiftButton; if (clicked && zone != Note::None && zone != Note::BottomColumn && zone != Note::Resizer && (controlPressed || shiftPressed)) { if (controlPressed && shiftPressed) selectRange(m_startOfShiftSelectionNote, clicked, /*unselectOthers=*/false); else if (shiftPressed) selectRange(m_startOfShiftSelectionNote, clicked); else if (controlPressed) clicked->setSelectedRecursivly(!clicked->allSelected()); setFocusedNote(clicked); /// /// /// m_startOfShiftSelectionNote = (clicked->isGroup() ? clicked->firstRealChild() : clicked); m_noActionOnMouseRelease = true; return; } // Switch tag states: if (zone >= Note::Emblem0) { if (event->button() == Qt::LeftButton) { int icons = -1; for (State::List::iterator it = clicked->states().begin(); it != clicked->states().end(); ++it) { if ( ! (*it)->emblem().isEmpty() ) icons++; if (icons == zone - Note::Emblem0) { State *state = (*it)->nextState(); if (!state) return; it = clicked->states().insert(it, state); ++it; clicked->states().remove(it); clicked->recomputeStyle(); clicked->unbufferize(); updateNote(clicked); updateEditorAppearance(); filterAgain(); save(); break; } } return; }/* else if (event->button() == Qt::RightButton) { popupEmblemMenu(clicked, zone - Note::Emblem0); return; }*/ } // Insert note or past clipboard: TQString text; // Note *note; TQString link; //int zone = zone; if (event->button() == Qt::MidButton && zone == Note::Resizer) return; //zone = clicked->zoneAt( event->pos() - TQPoint(clicked->x(), clicked->y()), true ); if (event->button() == Qt::RightButton && (clicked->isColumn() || zone == Note::Resizer)) return; if (clicked->isGroup() && zone == Note::None) return; switch (zone) { case Note::Handle: case Note::Group: // We select note on mousePress if it was unselected or Ctrl is pressed. // But the user can want to drag select_s_ notes, so it the note is selected, we only select it alone on mouseRelease: if (event->stateAfter() == 0) { std::cout << "EXEC" << std::endl; if ( !(event->stateAfter() & TQt::ControlButton) && clicked->allSelected()) unselectAllBut(clicked); if (zone == Note::Handle && isDuringEdit() && editedNote() == clicked) { closeEditor(); clicked->setSelected(true); } } break; case Note::Custom0: //unselectAllBut(clicked); setFocusedNote(clicked); noteOpen(clicked); break; case Note::GroupExpander: case Note::TagsArrow: break; case Note::Link: link = clicked->linkAt(event->pos() - TQPoint(clicked->x(), clicked->y())); if ( ! link.isEmpty() ) { if (link == "basket-internal-remove-basket") { // 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." Global::bnpView->doBasketDeletion(this); } else if (link == "basket-internal-import") { TQPopupMenu *menu = Global::bnpView->popupMenu("fileimport"); menu->exec(event->globalPos()); } else { KRun *run = new KRun(link); // open the URL. run->setAutoDelete(true); } break; } // If there is no link, edit note content case Note::Content: closeEditor(); unselectAllBut(clicked); noteEdit(clicked, /*justAdded=*/false, event->pos()); break; case Note::TopInsert: case Note::TopGroup: case Note::BottomInsert: case Note::BottomGroup: case Note::BottomColumn: clickedToInsert(event, clicked, zone); save(); break; case Note::None: default: KMessageBox::information(viewport(), i18n("This message should never appear. If it does, this program is buggy! " "Please report the bug to the developer.")); break; } } void Basket::contentsMouseDoubleClickEvent(TQMouseEvent *event) { Note *clicked = noteAt(event->pos().x(), event->pos().y()); Note::Zone zone = (clicked ? clicked->zoneAt( event->pos() - TQPoint(clicked->x(), clicked->y()) ) : Note::None); if (event->button() == Qt::LeftButton && (zone == Note::Group || zone == Note::Handle)) { doCopy(CopyToSelection); m_noActionOnMouseRelease = true; } else contentsMousePressEvent(event); } void Basket::contentsMouseMoveEvent(TQMouseEvent *event) { // Drag the notes: if (m_canDrag && (m_pressPos - event->pos()).manhattanLength() > KApplication::startDragDistance()) { m_canDrag = false; m_isSelecting = false; // Don't draw selection rectangle ater drag! m_selectionStarted = false; NoteSelection *selection = selectedNotes(); if (selection->firstStacked()) { TQDragObject *d = NoteDrag::dragObject(selection, /*cutting=*/false, /*source=*/this); // d will be deleted by QT /*bool shouldRemove = */d->drag(); // delete selection; // Never delete because URL is dragged and the file must be available for the extern appliation // if (shouldRemove && d->target() == 0) // If target is another application that request to remove the note // emit wantDelete(this); } return; } // Moving a Resizer: if (m_resizingNote) { int groupWidth = event->pos().x() - m_resizingNote->x() - m_pickedResizer; int minRight = m_resizingNote->minRight(); int maxRight = 100 * contentsWidth(); // A big enough value (+infinity) for free layouts. Note *nextColumn = m_resizingNote->next(); if (m_resizingNote->isColumn()) { if (nextColumn) maxRight = nextColumn->x() + nextColumn->rightLimit() - nextColumn->minRight() - Note::RESIZER_WIDTH; else maxRight = contentsWidth(); } if (groupWidth > maxRight - m_resizingNote->x()) groupWidth = maxRight - m_resizingNote->x(); if (groupWidth < minRight - m_resizingNote->x()) groupWidth = minRight - m_resizingNote->x(); int delta = groupWidth - m_resizingNote->groupWidth(); m_resizingNote->setGroupWidth(groupWidth); // If resizing columns: if (m_resizingNote->isColumn()) { Note *column = m_resizingNote; if ( (column = column->next()) ) { // Next columns should not have them X coordinate animated, because it would flicker: column->setXRecursivly(column->x() + delta); // And the resizer should resize the TWO sibling columns, and not push the other columns on th right: column->setGroupWidth(column->groupWidth() - delta); } } relayoutNotes(true); } // Moving a Note: /* if (m_movingNote) { int x = event->pos().x() - m_pickedHandle.x(); int y = event->pos().y() - m_pickedHandle.y(); if (x < 0) x = 0; if (y < 0) y = 0; m_movingNote->setX(x); m_movingNote->setY(y); m_movingNote->relayoutAt(x, y, / *animate=* /false); relayoutNotes(true); } */ // Dragging the selection rectangle: if (m_selectionStarted) doAutoScrollSelection(); doHoverEffects(event->pos()); } void Basket::doAutoScrollSelection() { static const int AUTO_SCROLL_MARGIN = 50; // pixels static const int AUTO_SCROLL_DELAY = 100; // milliseconds TQPoint pos = viewport()->mapFromGlobal(TQCursor::pos()); // Do the selection: if (m_isSelecting) updateContents(m_selectionRect); m_selectionEndPoint = viewportToContents(pos); m_selectionRect = TQRect(m_selectionBeginPoint, m_selectionEndPoint).normalize(); if (m_selectionRect.left() < 0) m_selectionRect.setLeft(0); if (m_selectionRect.top() < 0) m_selectionRect.setTop(0); if (m_selectionRect.right() >= contentsWidth()) m_selectionRect.setRight(contentsWidth() - 1); if (m_selectionRect.bottom() >= contentsHeight()) m_selectionRect.setBottom(contentsHeight() - 1); if ( (m_selectionBeginPoint - m_selectionEndPoint).manhattanLength() > TQApplication::startDragDistance() ) { m_isSelecting = true; selectNotesIn(m_selectionRect, m_selectionInvert); updateContents(m_selectionRect); m_noActionOnMouseRelease = true; } else { // If the user was selecting but cancel by making the rectangle too small, cancel it really!!! if (m_isSelecting) { if (m_selectionInvert) selectNotesIn(TQRect(), m_selectionInvert); else unselectAllBut(0); // TODO: unselectAll(); } if (m_isSelecting) resetWasInLastSelectionRect(); m_isSelecting = false; stopAutoScrollSelection(); return; } // Do the auto-scrolling: // FIXME: It's still flickering TQRect insideRect(AUTO_SCROLL_MARGIN, AUTO_SCROLL_MARGIN, visibleWidth() - 2*AUTO_SCROLL_MARGIN, visibleHeight() - 2*AUTO_SCROLL_MARGIN); int dx = 0; int dy = 0; if (pos.y() < AUTO_SCROLL_MARGIN) dy = pos.y() - AUTO_SCROLL_MARGIN; else if (pos.y() > visibleHeight() - AUTO_SCROLL_MARGIN) dy = pos.y() - visibleHeight() + AUTO_SCROLL_MARGIN; if (pos.x() < AUTO_SCROLL_MARGIN) dx = pos.x() - AUTO_SCROLL_MARGIN; else if (pos.x() > visibleWidth() - AUTO_SCROLL_MARGIN) dx = pos.x() - visibleWidth() + AUTO_SCROLL_MARGIN; if (dx || dy) { kapp->sendPostedEvents(); // Do the repaints, because the scrolling will make the area to repaint to be wrong scrollBy(dx, dy); if (!m_autoScrollSelectionTimer.isActive()) m_autoScrollSelectionTimer.start(AUTO_SCROLL_DELAY); } else stopAutoScrollSelection(); } void Basket::stopAutoScrollSelection() { m_autoScrollSelectionTimer.stop(); } void Basket::resetWasInLastSelectionRect() { Note *note = m_firstNote; while (note) { note->resetWasInLastSelectionRect(); note = note->next(); } } void Basket::selectAll() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->selectAll(); else if (m_editor->lineEdit()) m_editor->lineEdit()->selectAll(); } else { // First select all in the group, then in the parent group... Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0); while (parent) { if (!parent->allSelected()) { parent->setSelectedRecursivly(true); return; } child = parent; parent = parent->parentNote(); } // Then, select all: FOR_EACH_NOTE (note) note->setSelectedRecursivly(true); } } void Basket::unselectAll() { if (redirectEditActions()) { if (m_editor->textEdit()) { m_editor->textEdit()->removeSelection(); selectionChangedInEditor(); // THIS IS NOT EMITED BY TQt!!! } else if (m_editor->lineEdit()) m_editor->lineEdit()->deselect(); } else { if (countSelecteds() > 0) // Optimisation FOR_EACH_NOTE (note) note->setSelectedRecursivly(false); } } void Basket::invertSelection() { FOR_EACH_NOTE (note) note->invertSelectionRecursivly(); } void Basket::unselectAllBut(Note *toSelect) { FOR_EACH_NOTE (note) note->unselectAllBut(toSelect); } void Basket::invertSelectionOf(Note *toSelect) { FOR_EACH_NOTE (note) note->invertSelectionOf(toSelect); } void Basket::selectNotesIn(const TQRect &rect, bool invertSelection, bool unselectOthers /*= true*/) { FOR_EACH_NOTE (note) note->selectIn(rect, invertSelection, unselectOthers); } void Basket::doHoverEffects() { doHoverEffects( viewportToContents( viewport()->mapFromGlobal(TQCursor::pos()) ) ); } void Basket::doHoverEffects(Note *note, Note::Zone zone, const TQPoint &pos) { // Inform the old and new hovered note (if any): Note *oldHoveredNote = m_hoveredNote; if (note != m_hoveredNote) { if (m_hoveredNote) { m_hoveredNote->setHovered(false); m_hoveredNote->setHoveredZone(Note::None); updateNote(m_hoveredNote); } m_hoveredNote = note; if (note) note->setHovered(true); } // If we are hovering a note, compute which zone is hovered and inform the note: if (m_hoveredNote) { if (zone != m_hoveredZone || oldHoveredNote != m_hoveredNote) { m_hoveredZone = zone; m_hoveredNote->setCursor(zone); updateNote(m_hoveredNote); } m_hoveredNote->setHoveredZone(zone); // If we are hovering an insert line zone, update this thing: if (zone == Note::TopInsert || zone == Note::TopGroup || zone == Note::BottomInsert || zone == Note::BottomGroup || zone == Note::BottomColumn) placeInserter(m_hoveredNote, zone); else removeInserter(); // If we are hovering an embedded link in a rich text element, show the destination in the statusbar: if (zone == Note::Link) emit setStatusBarText(m_hoveredNote->linkAt( pos - TQPoint(m_hoveredNote->x(), m_hoveredNote->y()) )); else if (m_hoveredNote->content()) emit setStatusBarText(m_hoveredNote->content()->statusBarMessage(m_hoveredZone));//resetStatusBarText(); // If we aren't hovering a note, reset all: } else { if (isFreeLayout() && !isSelecting()) viewport()->setCursor(TQt::CrossCursor); else viewport()->unsetCursor(); m_hoveredZone = Note::None; removeInserter(); emit resetStatusBarText(); } } void Basket::doHoverEffects(const TQPoint &pos) { // if (isDuringEdit()) // viewport()->unsetCursor(); // Do we have the right to do hover effects? if ( ! m_loaded || m_lockedHovering) return; // enterEvent() (mouse enter in the widget) set m_underMouse to true, and leaveEvent() make it false. // But some times the enterEvent() is not trigerred: eg. when dragging the scrollbar: // Ending the drag INSIDE the basket area will make NO hoverEffects() because m_underMouse is false. // User need to leave the area and re-enter it to get effects. // This hack solve that by dismissing the m_underMouse variable: bool underMouse = Global::bnpView->currentBasket() == this && TQRect(contentsX(), contentsY(), visibleWidth(), visibleHeight()).contains(pos); // Don't do hover effects when a popup menu is opened. // Primarily because the basket area will only receive mouseEnterEvent and mouveLeaveEvent. // It willn't be noticed of mouseMoveEvent, which would result in a apparently broken application state: if (kapp->activePopupWidget()) underMouse = false; // Compute which note is hovered: Note *note = (m_isSelecting || !underMouse ? 0 : noteAt(pos.x(), pos.y())); Note::Zone zone = (note ? note->zoneAt( pos - TQPoint(note->x(), note->y()), isDuringDrag() ) : Note::None); // Inform the old and new hovered note (if any) and update the areas: doHoverEffects(note, zone, pos); } void Basket::mouseEnteredEditorWidget() { if (!m_lockedHovering && !kapp->activePopupWidget()) doHoverEffects(editedNote(), Note::Content, TQPoint()); } void Basket::removeInserter() { if (m_inserterShown) { // Do not hide (and then update/repaint the view) if it is already hidden! m_inserterShown = false; updateContents(m_inserterRect); } } void Basket::placeInserter(Note *note, int zone) { // Remove the inserter: if (!note) { removeInserter(); return; } // Update the old position: if (inserterShown()) updateContents(m_inserterRect); // Some comodities: m_inserterShown = true; m_inserterTop = (zone == Note::TopGroup || zone == Note::TopInsert); m_inserterGroup = (zone == Note::TopGroup || zone == Note::BottomGroup); // X and width: int groupIndent = (note->isGroup() ? note->width() : Note::HANDLE_WIDTH); int x = note->x(); int width = (note->isGroup() ? note->rightLimit() - note->x() : note->width()); if (m_inserterGroup) { x += groupIndent; width -= groupIndent; } m_inserterSplit = (Settings::groupOnInsertionLine() && note && !note->isGroup() && !note->isFree() && !note->isColumn()); // if (note->isGroup()) // width = note->rightLimit() - note->x() - (m_inserterGroup ? groupIndent : 0); // Y: int y = note->y() - (m_inserterGroup && m_inserterTop ? 1 : 3); if (!m_inserterTop) y += (note->isColumn() ? note->finalHeight() : note->height()); // Assigning result: m_inserterRect = TQRect(x, y, width, 6 - (m_inserterGroup ? 2 : 0)); // Update the new position: updateContents(m_inserterRect); } inline void drawLineByRect(TQPainter &painter, int x, int y, int width, int height) { painter.drawLine(x, y, x + width - 1, y + height - 1); } void Basket::drawInserter(TQPainter &painter, int xPainter, int yPainter) { if (!m_inserterShown) return; TQRect rect = m_inserterRect; // For shorter code-lines when drawing! rect.moveBy(-xPainter, -yPainter); int lineY = (m_inserterGroup && m_inserterTop ? 0 : 2); int roundY = (m_inserterGroup && m_inserterTop ? 0 : 1); TQColor dark = KApplication::palette().active().dark(); TQColor light = dark.light().light(); if (m_inserterGroup && Settings::groupOnInsertionLine()) light = Tools::mixColor(light, KGlobalSettings::highlightColor()); painter.setPen(dark); // The horizontal line: //painter.drawRect( rect.x(), rect.y() + lineY, rect.width(), 2); int width = rect.width() - 4; drawGradient(&painter, dark, light, rect.x() + 2, rect.y() + lineY, width/2, 2, /*sunken=*/false, /*horz=*/false, /*flat=*/false); drawGradient(&painter, light, dark, rect.x() + 2 + width/2, rect.y() + lineY, width - width/2, 2, /*sunken=*/false, /*horz=*/false, /*flat=*/false); // The left-most and right-most edges (biggest vertical lines): drawLineByRect(painter, rect.x(), rect.y(), 1, (m_inserterGroup ? 4 : 6)); drawLineByRect(painter, rect.x() + rect.width() - 1, rect.y(), 1, (m_inserterGroup ? 4 : 6)); // The left and right mid vertical lines: drawLineByRect(painter, rect.x() + 1, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4)); drawLineByRect(painter, rect.x() + rect.width() - 2, rect.y() + roundY, 1, (m_inserterGroup ? 3 : 4)); // Draw the split as a feedback to know where is the limit between insert and group: if (m_inserterSplit) { int noteWidth = rect.width() + (m_inserterGroup ? Note::HANDLE_WIDTH : 0); int xSplit = rect.x() - (m_inserterGroup ? Note::HANDLE_WIDTH : 0) + noteWidth / 2; painter.setPen(Tools::mixColor(dark, light)); painter.drawRect(xSplit - 2, rect.y() + lineY, 4, 2); painter.setPen(dark); painter.drawRect(xSplit - 1, rect.y() + lineY, 2, 2); } } void Basket::maybeTip(const TQPoint &pos) { if ( !m_loaded || !Settings::showNotesToolTip() ) return; TQString message; TQRect rect; TQPoint contentPos = viewportToContents(pos); Note *note = noteAt(contentPos.x(), contentPos.y()); if (!note && isFreeLayout()) { message = i18n("Insert note here\nRight click for more options"); TQRect itRect; for (TQValueList::iterator it = m_blankAreas.begin(); it != m_blankAreas.end(); ++it) { itRect = TQRect(0, 0, visibleWidth(), visibleHeight()).intersect(*it); if (itRect.contains(contentPos)) { rect = itRect; rect.moveLeft(rect.left() - contentsX()); rect.moveTop( rect.top() - contentsY()); break; } } } else { if (!note) return; Note::Zone zone = note->zoneAt( contentPos - TQPoint(note->x(), note->y()) ); switch (zone) { case Note::Resizer: message = (note->isColumn() ? i18n("Resize those columns") : (note->isGroup() ? i18n("Resize this group") : i18n("Resize this note"))); break; case Note::Handle: message = i18n("Select or move this note"); break; case Note::Group: message = i18n("Select or move this group"); break; case Note::TagsArrow: message = i18n("Assign or remove tags from this note"); if (note->states().count() > 0) { message = "" + message + "
" + i18n("Assigned Tags: %1"); TQString tagsString = ""; for (State::List::iterator it = note->states().begin(); it != note->states().end(); ++it) { TQString tagName = "" + Tools::textToHTMLWithoutP((*it)->fullName()) + ""; if (tagsString.isEmpty()) tagsString = tagName; else tagsString = i18n("%1, %2").arg(tagsString, tagName); } message = message.arg(tagsString); } break; case Note::Custom0: message = note->content()->zoneTip(zone); break; //"Open this link/Open this file/Open this sound file/Launch this application" case Note::GroupExpander: message = (note->isFolded() ? i18n("Expand this group") : i18n("Collapse this group")); break; case Note::Link: case Note::Content: message = note->content()->editToolTipText(); break; case Note::TopInsert: case Note::BottomInsert: message = i18n("Insert note here\nRight click for more options"); break; case Note::TopGroup: message = i18n("Group note with the one below\nRight click for more options"); break; case Note::BottomGroup: message = i18n("Group note with the one above\nRight click for more options"); break; case Note::BottomColumn: message = i18n("Insert note here\nRight click for more options"); break; case Note::None: message = "** Zone NONE: internal error **"; break; default: if (zone >= Note::Emblem0) message = note->stateForEmblemNumber(zone - Note::Emblem0)->fullName(); else message = ""; break; } if (zone == Note::Content || zone == Note::Link || zone == Note::Custom0) { TQStringList keys; TQStringList values; note->content()->toolTipInfos(&keys, &values); keys.append(i18n("Added")); keys.append(i18n("Last Modification")); values.append(note->addedStringDate()); values.append(note->lastModificationStringDate()); message = "" + message; TQStringList::iterator key; TQStringList::iterator value; for (key = keys.begin(), value = values.begin(); key != keys.end() && value != values.end(); ++key, ++value) message += "
" + i18n("of the form 'key: value'", "%1: %2").arg(*key, *value); message += "
"; } else if (m_inserterSplit && (zone == Note::TopInsert || zone == Note::BottomInsert)) message += "\n" + i18n("Click on the right to group instead of insert"); else if (m_inserterSplit && (zone == Note::TopGroup || zone == Note::BottomGroup)) message += "\n" + i18n("Click on the left to insert instead of group"); rect = note->zoneRect( zone, contentPos - TQPoint(note->x(), note->y()) ); rect.moveLeft(rect.left() - contentsX()); rect.moveTop( rect.top() - contentsY()); rect.moveLeft(rect.left() + note->x()); rect.moveTop( rect.top() + note->y()); } tip(rect, message); } Note* Basket::lastNote() { Note *note = firstNote(); while (note && note->next()) note = note->next(); return note; } void Basket::deleteNotes() { Note *note = m_firstNote; while (note){ Note *tmp = note->next(); delete note; note = tmp; } m_firstNote = 0; m_resizingNote = 0; m_movingNote = 0; m_focusedNote = 0; m_startOfShiftSelectionNote = 0; m_tagPopupNote = 0; m_clickedToInsert = 0; m_savedClickedToInsert = 0; m_hoveredNote = 0; m_count = 0; m_countFounds = 0; m_countSelecteds = 0; emit resetStatusBarText(); emit countsChanged(this); } Note* Basket::noteAt(int x, int y) { //NO: // // Do NOT check the bottom&right borders. // // Because imagine someone drag&drop a big note from the top to the bottom of a big basket (with big vertical scrollbars), // // the note is first removed, and relayoutNotes() compute the new height that is smaller // // Then noteAt() is called for the mouse pointer position, because the basket is now smaller, the cursor is out of boundaries!!! // // Should, of course, not return 0: if (x < 0 || x > contentsWidth() || y < 0 || y > contentsHeight()) return 0; // When resizing a note/group, keep it highlighted: if (m_resizingNote) return m_resizingNote; // Search and return the hovered note: Note *note = m_firstNote; Note *possibleNote; while (note) { possibleNote = note->noteAt(x, y); if (possibleNote) { if (draggedNotes().contains(possibleNote)) return 0; else return possibleNote; } note = note->next(); } // If the basket is layouted in columns, return one of the columns to be able to add notes in them: if (isColumnsLayout()) { Note *column = m_firstNote; while (column) { if (x >= column->x() && x < column->rightLimit()) return column; column = column->next(); } } // Nothing found, no note is hovered: return NULL; } Basket::~Basket() { //delete m_action; if(m_decryptBox) delete m_decryptBox; #ifdef HAVE_LIBGPGME delete m_gpg; #endif deleteNotes(); } void Basket::viewportResizeEvent(TQResizeEvent *event) { relayoutNotes(true); //cornerWidget()->setShown(horizontalScrollBar()->isShown() && verticalScrollBar()->isShown()); if (horizontalScrollBar()->isShown() && verticalScrollBar()->isShown()) { if (!cornerWidget()) setCornerWidget(m_cornerWidget); } else { if (cornerWidget()) setCornerWidget(0); } // if (isDuringEdit()) // ensureNoteVisible(editedNote()); TQScrollView::viewportResizeEvent(event); } void Basket::animateLoad() { const int viewHeight = contentsY() + visibleHeight(); TQTime t = TQTime::currentTime(); // Set random seed srand(t.hour()*12 + t.minute()*60 + t.second()*60); Note *note = firstNote(); while (note) { if ((note->finalY() < viewHeight) && note->matching()) note->initAnimationLoad(); note = note->next(); } m_loaded = true; } TQColor Basket::selectionRectInsideColor() { return Tools::mixColor(Tools::mixColor(backgroundColor(), KGlobalSettings::highlightColor()), backgroundColor()); } TQColor alphaBlendColors(const TQColor &bgColor, const TQColor &fgColor, const int a) { // normal button... TQRgb rgb = bgColor.rgb(); TQRgb rgb_b = fgColor.rgb(); int alpha = a; if (alpha>255) alpha = 255; if (alpha<0) alpha = 0; int inv_alpha = 255 - alpha; TQColor result = TQColor( tqRgb(tqRed(rgb_b)*inv_alpha/255 + tqRed(rgb)*alpha/255, tqGreen(rgb_b)*inv_alpha/255 + tqGreen(rgb)*alpha/255, tqBlue(rgb_b)*inv_alpha/255 + tqBlue(rgb)*alpha/255) ); return result; } void Basket::unlock() { TQTimer::singleShot( 0, this, TQT_SLOT(load()) ); } void Basket::inactivityAutoLockTimeout() { lock(); } void Basket::drawContents(TQPainter *painter, int clipX, int clipY, int clipWidth, int clipHeight) { // Start the load the first time the basket is shown: if (!m_loadingLaunched) { if(!m_locked) TQTimer::singleShot( 0, this, TQT_SLOT(load()) ); else { Global::bnpView->notesStateChanged(); // Show "Locked" instead of "Loading..." in the statusbar } } TQBrush brush(backgroundColor()); // FIXME: share it for all the basket? TQRect clipRect(clipX, clipY, clipWidth, clipHeight); if(m_locked) { if(!m_decryptBox) { m_decryptBox = new TQFrame( this, "m_decryptBox" ); m_decryptBox->setFrameShape( TQFrame::StyledPanel ); m_decryptBox->setFrameShadow( TQFrame::Plain ); m_decryptBox->setLineWidth( 1 ); TQGridLayout* layout = new TQGridLayout( m_decryptBox, 1, 1, 11, 6, "decryptBoxLayout"); #ifdef HAVE_LIBGPGME m_button = new TQPushButton( m_decryptBox, "button" ); m_button->setText( i18n( "&Unlock" ) ); layout->addWidget( m_button, 1, 2 ); connect( m_button, TQT_SIGNAL( clicked() ), this, TQT_SLOT( unlock() ) ); #endif TQLabel* label = new TQLabel( m_decryptBox, "label" ); TQString text = "" + i18n("Password protected basket.") + "
"; #ifdef HAVE_LIBGPGME label->setText( text + i18n("Press Unlock to access it.") ); #else label->setText( text + i18n("Encryption is not supported by
this version of %1.").arg(kapp->aboutData()->programName()) ); #endif label->setAlignment( int( TQLabel::AlignTop ) ); layout->addMultiCellWidget( label, 0, 0, 1, 2 ); TQLabel* pixmap = new TQLabel( m_decryptBox, "pixmap" ); pixmap->setPixmap( KGlobal::iconLoader()->loadIcon("encrypted", KIcon::NoGroup, KIcon::SizeHuge) ); layout->addMultiCellWidget( pixmap, 0, 1, 0, 0 ); TQSpacerItem* spacer = new TQSpacerItem( 40, 20, TQSizePolicy::Expanding, TQSizePolicy::Minimum ); layout->addItem( spacer, 1, 1 ); label = new TQLabel("" + i18n("To make baskets stay unlocked, change the automatic
" "locking duration in the application settings.") + "
", m_decryptBox); //label->setFixedWidth(label->sizeHint().width() / 2); label->setAlignment( int( TQLabel::AlignTop ) ); layout->addMultiCellWidget( label, 2,2,0,2 ); m_decryptBox->resize(layout->sizeHint()); } if(m_decryptBox->isHidden()) { m_decryptBox->show(); } #ifdef HAVE_LIBGPGME m_button->setFocus(); #endif m_decryptBox->move((visibleWidth() - m_decryptBox->width()) / 2, (visibleHeight() - m_decryptBox->height()) / 2); } else { if(m_decryptBox && m_decryptBox->isShown()) m_decryptBox->hide(); } // Draw notes (but not when it's not loaded or locked yet): Note *note = ((m_loaded || m_locked) ? m_firstNote : 0); while (note) { note->draw(painter, clipRect); note = note->next(); } enableActions(); // Draw loading message: if (!m_loaded) { TQPixmap pixmap(visibleWidth(), visibleHeight()); // TODO: Clip it to asked size only! TQPainter painter2(&pixmap); TQSimpleRichText rt(TQString("
%1
").arg(i18n("Loading...")), TQScrollView::font()); rt.setWidth(visibleWidth()); int hrt = rt.height(); painter2.fillRect(0, 0, visibleWidth(), visibleHeight(), brush); blendBackground(painter2, TQRect(0, 0, visibleWidth(), visibleHeight()), -1, -1, /*opaque=*/true); TQColorGroup cg = colorGroup(); cg.setColor(TQColorGroup::Text, textColor()); rt.draw(&painter2, 0, (visibleHeight() - hrt) / 2, TQRect(), cg); painter2.end(); painter->drawPixmap(0, 0, pixmap); return; // TODO: Clip to the wanted rectangle } // We will draw blank areas below. // For each rectangle to be draw there is three ways to do so: // - The rectangle is full of the background COLOR => we fill a rect directly on screen // - The rectangle is full of the background PIXMAP => we draw it directly on screen (we draw m_opaqueBackgroundPixmap that is not transparent) // - The rectangle contains the resizer => We draw it on an offscreen buffer and then paint the buffer on screen // 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. // Draw blank areas (see the last preparation above): TQPixmap pixmap; TQPainter painter2; TQRect rect; for (TQValueList::iterator it = m_blankAreas.begin(); it != m_blankAreas.end(); ++it) { rect = clipRect.intersect(*it); if (rect.width() > 0 && rect.height() > 0) { // If there is an inserter to draw, draw the image off screen, // apply the inserter and then draw the image on screen: if ( (inserterShown() && rect.intersects(inserterRect())) || (m_isSelecting && rect.intersects(m_selectionRect)) ) { pixmap.resize(rect.width(), rect.height()); painter2.begin(&pixmap); painter2.fillRect(0, 0, rect.width(), rect.height(), backgroundColor()); blendBackground(painter2, rect, -1, -1, /*opaque=*/true); // Draw inserter: if (inserterShown() && rect.intersects(inserterRect())) drawInserter(painter2, rect.x(), rect.y()); // Draw selection rect: if (m_isSelecting && rect.intersects(m_selectionRect)) { TQRect selectionRect = m_selectionRect; selectionRect.moveBy(-rect.x(), -rect.y()); TQRect selectionRectInside(selectionRect.x() + 1, selectionRect.y() + 1, selectionRect.width() - 2, selectionRect.height() - 2); if (selectionRectInside.width() > 0 && selectionRectInside.height() > 0) { TQColor insideColor = selectionRectInsideColor(); painter2.fillRect(selectionRectInside, insideColor); selectionRectInside.moveBy(rect.x(), rect.y()); blendBackground(painter2, selectionRectInside, rect.x(), rect.y(), true, /*&*/m_selectedBackgroundPixmap); } painter2.setPen(KGlobalSettings::highlightColor().dark()); painter2.drawRect(selectionRect); painter2.setPen(Tools::mixColor(KGlobalSettings::highlightColor().dark(), backgroundColor())); painter2.drawPoint(selectionRect.topLeft()); painter2.drawPoint(selectionRect.topRight()); painter2.drawPoint(selectionRect.bottomLeft()); painter2.drawPoint(selectionRect.bottomRight()); } painter2.end(); painter->drawPixmap(rect.x(), rect.y(), pixmap); // If it's only a blank rectangle to draw, draw it directly on screen (faster!!!): } else if ( ! hasBackgroundImage() ) { painter->fillRect(rect, backgroundColor()); // It's either a background pixmap to draw or a background color to fill: } else { if (isTiledBackground() || (rect.x() < backgroundPixmap()->width() && rect.y() < backgroundPixmap()->height())) blendBackground(*painter, rect, 0, 0, /*opaque=*/true); else painter->fillRect(rect, backgroundColor()); } } } } /* rect(x,y,width,height)==(xBackgroundToDraw,yBackgroundToDraw,widthToDraw,heightToDraw) */ void Basket::blendBackground(TQPainter &painter, const TQRect &rect, int xPainter, int yPainter, bool opaque, TQPixmap *bg) { if (xPainter == -1 && yPainter == -1) { xPainter = rect.x(); yPainter = rect.y(); } if (hasBackgroundImage()) { const TQPixmap *bgPixmap = (bg ? /* * */bg : (opaque ? m_opaqueBackgroundPixmap : m_backgroundPixmap)); if (isTiledBackground()) painter.drawTiledPixmap(rect.x() - xPainter, rect.y() - yPainter, rect.width(), rect.height(), *bgPixmap, rect.x(), rect.y()); else painter.drawPixmap(rect.x() - xPainter, rect.y() - yPainter, *bgPixmap, rect.x(), rect.y(), rect.width(), rect.height()); } } void Basket::recomputeBlankRects() { m_blankAreas.clear(); m_blankAreas.append( TQRect(0, 0, contentsWidth(), contentsHeight()) ); FOR_EACH_NOTE (note) note->recomputeBlankRects(m_blankAreas); // See the drawing of blank areas in Basket::drawContents() if (hasBackgroundImage() && ! isTiledBackground()) substractRectOnAreas( TQRect(0, 0, backgroundPixmap()->width(), backgroundPixmap()->height()), m_blankAreas, false ); } void Basket::addAnimatedNote(Note *note) { if (m_animatedNotes.isEmpty()) { m_animationTimer.start(FRAME_DELAY); m_lastFrameTime = TQTime::currentTime(); } m_animatedNotes.append(note); } void Basket::unsetNotesWidth() { Note *note = m_firstNote; while (note) { note->unsetWidth(); note = note->next(); } } void Basket::relayoutNotes(bool animate) { if (Global::bnpView->currentBasket() != this) return; // Optimize load time, and basket will be relaid out when activated, anyway if (!Settings::playAnimations()) animate = false; if (!animate) { m_animatedNotes.clear(); m_animationTimer.stop(); } int h = 0; tmpWidth = 0; tmpHeight = 0; Note *note = m_firstNote; while (note) { if (note->matching()) { note->relayoutAt(0, h, animate); if (note->hasResizer()) { int minGroupWidth = note->minRight() - note->finalX(); if (note->groupWidth() < minGroupWidth) { note->setGroupWidth(minGroupWidth); relayoutNotes(animate); // Redo the thing, but this time it should not recurse return; } } h += note->finalHeight(); } note = note->next(); } if (isFreeLayout()) tmpHeight += 100; else tmpHeight += 15; resizeContents( TQMAX(tmpWidth, visibleWidth()), TQMAX(tmpHeight, visibleHeight()) ); recomputeBlankRects(); placeEditor(); doHoverEffects(); updateContents(); } void Basket::updateNote(Note *note) { updateContents(note->rect()); if (note->hasResizer()) updateContents(note->resizerRect()); } void Basket::animateObjects() { TQValueList::iterator it; for (it = m_animatedNotes.begin(); it != m_animatedNotes.end(); ) // if ((*it)->y() >= contentsY() && (*it)->bottom() <= contentsY() + contentsWidth()) // updateNote(*it); if ((*it)->advance()) it = m_animatedNotes.remove(it); else { // if ((*it)->y() >= contentsY() && (*it)->bottom() <= contentsY() + contentsWidth()) // updateNote(*it); ++it; } if (m_animatedNotes.isEmpty()) { // Stop animation timer: m_animationTimer.stop(); // Reset all onTop notes: Note* note = m_firstNote; while (note) { note->setOnTop(false); note = note->next(); } } if (m_focusedNote) ensureNoteVisible(m_focusedNote); // We refresh content if it was the last frame, // or if the drawing of the last frame was not too long. if (!m_animationTimer.isActive() || (m_lastFrameTime.msecsTo(TQTime::currentTime()) < FRAME_DELAY*11/10)) { // *11/10 == *1.1 : We keep a 0.1 security margin m_lastFrameTime = m_lastFrameTime.addMSecs(FRAME_DELAY); // because timers are not accurate and can trigger late //m_lastFrameTime = TQTime::currentTime(); //std::cout << ">>" << m_lastFrameTime.toString("hh:mm:ss.zzz") << std::endl; if (m_underMouse) doHoverEffects(); recomputeBlankRects(); //relayoutNotes(true); // In case an animated note was to the contents view boundaries, resize the view! updateContents(); // If the drawing of the last frame was too long, we skip the drawing of the current and do the next one: } else { m_lastFrameTime = m_lastFrameTime.addMSecs(FRAME_DELAY); //std::cout << "+=" << m_lastFrameTime.toString("hh:mm:ss.zzz") << std::endl; animateObjects(); } doHoverEffects(); placeEditor(); /* int delta = m_deltaY / 3; if (delta == 0 && m_deltaY != 0) delta = (m_deltaY > 0 ? 1 : -1); m_deltaY -= delta; resizeContents(contentsWidth(), contentsHeight() + delta); //m_lastNote->y() + m_lastNote->height() */ } void Basket::popupEmblemMenu(Note *note, int emblemNumber) { m_tagPopupNote = note; State *state = note->stateForEmblemNumber(emblemNumber); State *nextState = state->nextState(/*cycle=*/false); Tag *tag = state->parentTag(); m_tagPopup = tag; TQKeySequence sequence = tag->shortcut().operator TQKeySequence(); bool sequenceOnDelete = (nextState == 0 && !tag->shortcut().isNull()); KPopupMenu menu(this); if (tag->countStates() == 1) { menu.insertTitle(/*SmallIcon(state->icon()), */tag->name()); menu.insertItem( SmallIconSet("editdelete"), i18n("&Remove"), 1 ); menu.insertItem( SmallIconSet("configure"), i18n("&Customize..."), 2 ); menu.insertSeparator(); menu.insertItem( SmallIconSet("filter"), i18n("&Filter by this Tag"), 3 ); } else { menu.insertTitle(tag->name()); TQValueList::iterator it; State *currentState; int i = 10; for (it = tag->states().begin(); it != tag->states().end(); ++it) { currentState = *it; TQKeySequence sequence; if (currentState == nextState && !tag->shortcut().isNull()) sequence = tag->shortcut().operator TQKeySequence(); menu.insertItem(StateMenuItem::radioButtonIconSet(state == currentState, menu.colorGroup()), new StateMenuItem(currentState, sequence, false), i ); if (currentState == nextState && !tag->shortcut().isNull()) menu.setAccel(sequence, i); ++i; } menu.insertSeparator(); menu.insertItem( new IndentedMenuItem(i18n("&Remove"), "editdelete", (sequenceOnDelete ? sequence : TQKeySequence())), 1 ); menu.insertItem( new IndentedMenuItem(i18n("&Customize..."), "configure"), 2 ); menu.insertSeparator(); menu.insertItem( new IndentedMenuItem(i18n("&Filter by this Tag"), "filter"), 3 ); menu.insertItem( new IndentedMenuItem(i18n("Filter by this &State"), "filter"), 4 ); } if (sequenceOnDelete) menu.setAccel(sequence, 1); connect( &menu, TQT_SIGNAL(activated(int)), this, TQT_SLOT(toggledStateInMenu(int)) ); connect( &menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(unlockHovering()) ); connect( &menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(disableNextClick()) ); m_lockedHovering = true; menu.exec(TQCursor::pos()); } void Basket::toggledStateInMenu(int id) { if (id == 1) { removeTagFromSelectedNotes(m_tagPopup); //m_tagPopupNote->removeTag(m_tagPopup); //m_tagPopupNote->setWidth(0); // To force a new layout computation updateEditorAppearance(); filterAgain(); save(); return; } if (id == 2) { // Customize this State: TagsEditDialog dialog(this, m_tagPopupNote->stateOfTag(m_tagPopup)); dialog.exec(); return; } if (id == 3) { // Filter by this Tag decoration()->filterBar()->filterTag(m_tagPopup); return; } if (id == 4) { // Filter by this State decoration()->filterBar()->filterState(m_tagPopupNote->stateOfTag(m_tagPopup)); return; } /*addStateToSelectedNotes*/changeStateOfSelectedNotes(m_tagPopup->states()[id - 10] /*, orReplace=true*/); //m_tagPopupNote->addState(m_tagPopup->states()[id - 10], true); filterAgain(); save(); } State* Basket::stateForTagFromSelectedNotes(Tag *tag) { State *state = 0; FOR_EACH_NOTE (note) if (note->stateForTagFromSelectedNotes(tag, &state) && state == 0) return 0; return state; } void Basket::activatedTagShortcut(Tag *tag) { // Compute the next state to set: State *state = stateForTagFromSelectedNotes(tag); if (state) state = state->nextState(/*cycle=*/false); else state = tag->states().first(); // Set or unset it: if (state) { FOR_EACH_NOTE (note) note->addStateToSelectedNotes(state, /*orReplace=*/true); updateEditorAppearance(); } else removeTagFromSelectedNotes(tag); filterAgain(); save(); } void Basket::popupTagsMenu(Note *note) { m_tagPopupNote = note; KPopupMenu menu(this); menu.insertTitle(i18n("Tags")); // TQValueList::iterator it; // Tag *currentTag; // State *currentState; // int i = 10; // for (it = Tag::all.begin(); it != Tag::all.end(); ++it) { // // Current tag and first state of it: // currentTag = *it; // currentState = currentTag->states().first(); // TQKeySequence sequence; // if (!currentTag->shortcut().isNull()) // sequence = currentTag->shortcut().operator TQKeySequence(); // menu.insertItem(StateMenuItem::checkBoxIconSet(note->hasTag(currentTag), menu.colorGroup()), new StateMenuItem(currentState, sequence, true), i ); // if (!currentTag->shortcut().isNull()) // menu.setAccel(sequence, i); // ++i; // } // // menu.insertSeparator(); // // menu.insertItem( /*SmallIconSet("editdelete"),*/ "&Assign New Tag...", 1 ); // //id = menu.insertItem( SmallIconSet("editdelete"), "&Remove All", -2 ); // //if (note->states().isEmpty()) // // menu.setItemEnabled(id, false); // // menu.insertItem( SmallIconSet("configure"), "&Customize...", 3 ); // menu.insertItem( new IndentedMenuItem(i18n("&Assign New Tag...")), 1 ); // menu.insertItem( new IndentedMenuItem(i18n("&Remove All"), "editdelete"), 2 ); // menu.insertItem( new IndentedMenuItem(i18n("&Customize..."), "configure"), 3 ); // // if (!selectedNotesHaveTags())//note->states().isEmpty()) // menu.setItemEnabled(2, false); // // connect( &menu, TQT_SIGNAL(activated(int)), this, TQT_SLOT(toggledTagInMenu(int)) ); // connect( &menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(unlockHovering()) ); // connect( &menu, TQT_SIGNAL(aboutToHide()), this, TQT_SLOT(disableNextClick()) ); Global::bnpView->populateTagsMenu(menu, note); m_lockedHovering = true; menu.exec(TQCursor::pos()); } void Basket::unlockHovering() { m_lockedHovering = false; doHoverEffects(); } void Basket::toggledTagInMenu(int id) { if (id == 1) { // Assign new Tag... TagsEditDialog dialog(this, /*stateToEdit=*/0, /*addNewTag=*/true); dialog.exec(); if (!dialog.addedStates().isEmpty()) { State::List states = dialog.addedStates(); for (State::List::iterator itState = states.begin(); itState != states.end(); ++itState) FOR_EACH_NOTE (note) note->addStateToSelectedNotes(*itState); updateEditorAppearance(); filterAgain(); save(); } return; } if (id == 2) { // Remove All removeAllTagsFromSelectedNotes(); filterAgain(); save(); return; } if (id == 3) { // Customize... TagsEditDialog dialog(this); dialog.exec(); return; } Tag *tag = Tag::all[id - 10]; if (!tag) return; if (m_tagPopupNote->hasTag(tag)) removeTagFromSelectedNotes(tag); else addTagToSelectedNotes(tag); m_tagPopupNote->setWidth(0); // To force a new layout computation filterAgain(); save(); } void Basket::addTagToSelectedNotes(Tag *tag) { FOR_EACH_NOTE (note) note->addTagToSelectedNotes(tag); updateEditorAppearance(); } void Basket::removeTagFromSelectedNotes(Tag *tag) { FOR_EACH_NOTE (note) note->removeTagFromSelectedNotes(tag); updateEditorAppearance(); } void Basket::addStateToSelectedNotes(State *state) { FOR_EACH_NOTE (note) note->addStateToSelectedNotes(state); updateEditorAppearance(); } void Basket::updateEditorAppearance() { if (isDuringEdit() && m_editor->widget()) { m_editor->widget()->setFont(m_editor->note()->font()); m_editor->widget()->setPaletteBackgroundColor(m_editor->note()->backgroundColor()); m_editor->widget()->setPaletteForegroundColor(m_editor->note()->textColor()); // Uggly Hack arround TQt bugs: placeCursor() don't call any signal: HtmlEditor *htmlEditor = dynamic_cast(m_editor); if (htmlEditor) { int para, index; m_editor->textEdit()->getCursorPosition(¶, &index); if (para == 0 && index == 0) { m_editor->textEdit()->moveCursor(TQTextEdit::MoveForward, /*select=*/false); m_editor->textEdit()->moveCursor(TQTextEdit::MoveBackward, /*select=*/false); } else { m_editor->textEdit()->moveCursor(TQTextEdit::MoveBackward, /*select=*/false); m_editor->textEdit()->moveCursor(TQTextEdit::MoveForward, /*select=*/false); } htmlEditor->cursorPositionChanged(); // Does not work anyway :-( (when clicking on a red bold text, the toolbar still show black normal text) } } } void Basket::editorPropertiesChanged() { if (isDuringEdit() && m_editor->note()->content()->type() == NoteType::Html) { m_editor->textEdit()->setAutoFormatting(Settings::autoBullet() ? TQTextEdit::AutoAll : TQTextEdit::AutoNone); } } void Basket::changeStateOfSelectedNotes(State *state) { FOR_EACH_NOTE (note) note->changeStateOfSelectedNotes(state); updateEditorAppearance(); } void Basket::removeAllTagsFromSelectedNotes() { FOR_EACH_NOTE (note) note->removeAllTagsFromSelectedNotes(); updateEditorAppearance(); } bool Basket::selectedNotesHaveTags() { FOR_EACH_NOTE (note) if (note->selectedNotesHaveTags()) return true; return false; } TQColor Basket::backgroundColor() { if (m_backgroundColorSetting.isValid()) return m_backgroundColorSetting; else return KGlobalSettings::baseColor(); } TQColor Basket::textColor() { if (m_textColorSetting.isValid()) return m_textColorSetting; else return KGlobalSettings::textColor(); } void Basket::unbufferizeAll() { FOR_EACH_NOTE (note) note->unbufferizeAll(); } Note* Basket::editedNote() { if (m_editor) return m_editor->note(); else return 0; } bool Basket::hasTextInEditor() { if (!isDuringEdit() || !redirectEditActions()) return false; if (m_editor->textEdit()) return ! m_editor->textEdit()->text().isEmpty(); else if (m_editor->lineEdit()) return ! m_editor->lineEdit()->text().isEmpty(); else return false; } bool Basket::hasSelectedTextInEditor() { if (!isDuringEdit() || !redirectEditActions()) return false; if (m_editor->textEdit()) { // The following line does NOT work if one letter is selected and the user press Shift+Left or Shift+Right to unselect than letter: // TQt misteriously tell us there is an invisible selection!! //return m_editor->textEdit()->hasSelectedText(); return !m_editor->textEdit()->selectedText().isEmpty(); } else if (m_editor->lineEdit()) return m_editor->lineEdit()->hasSelectedText(); else return false; } bool Basket::selectedAllTextInEditor() { if (!isDuringEdit() || !redirectEditActions()) return false; if (m_editor->textEdit()) return m_editor->textEdit()->text().isEmpty() || m_editor->textEdit()->text() == m_editor->textEdit()->selectedText(); else if (m_editor->lineEdit()) return m_editor->lineEdit()->text().isEmpty() || m_editor->lineEdit()->text() == m_editor->lineEdit()->selectedText(); else return false; } void Basket::selectionChangedInEditor() { Global::bnpView->notesStateChanged(); } void Basket::contentChangedInEditor() { // 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): if (m_editor->textEdit()) m_editor->autoSave(/*toFileToo=*/false); // else { if (m_inactivityAutoSaveTimer.isActive()) m_inactivityAutoSaveTimer.stop(); m_inactivityAutoSaveTimer.start(3 * 1000, /*singleShot=*/true); Global::bnpView->setUnsavedStatus(true); // } } void Basket::inactivityAutoSaveTimeout() { if (m_editor) m_editor->autoSave(/*toFileToo=*/true); } void Basket::placeEditorAndEnsureVisible() { placeEditor(/*andEnsureVisible=*/true); } void Basket::placeEditor(bool /*andEnsureVisible*/ /*= false*/) { if (!isDuringEdit()) return; TQFrame *editorTQFrame = dynamic_cast(m_editor->widget()); KTextEdit *textEdit = m_editor->textEdit(); // TQLineEdit *lineEdit = m_editor->lineEdit(); Note *note = m_editor->note(); int frameWidth = (editorTQFrame ? editorTQFrame->frameWidth() : 0); int x = note->x() + note->contentX() + note->content()->xEditorIndent() - frameWidth; int y; int maxHeight = TQMAX(visibleHeight(), contentsHeight()); int height, width; if (textEdit) { x -= 4; // Need to do it 2 times, because it's wrong overwise // (sometimes, width depends on height, and sometimes, height depends on with): for (int i = 0; i < 2; i++) { // FIXME: CRASH: Select all text, press Del or [<--] and editor->removeSelectedText() is called: // editor->sync() CRASH!! // editor->sync(); y = note->y() + Note::NOTE_MARGIN - frameWidth; height = textEdit->contentsHeight() + 2*frameWidth; // height = /*TQMAX(*/height/*, note->height())*/; // height = TQMIN(height, visibleHeight()); width = note->x() + note->width() - x + 1;// /*note->x() + note->width()*/note->rightLimit() - x + 2*frameWidth + 1; //width=TQMAX(width,textEdit->contentsWidth()+2*frameWidth); if (y + height > maxHeight) y = maxHeight - height; textEdit->setFixedSize(width, height); } } else { height = note->height() - 2*Note::NOTE_MARGIN + 2*frameWidth; width = note->x() + note->width() - x;//note->rightLimit() - x + 2*frameWidth; m_editor->widget()->setFixedSize(width, height); x -= 1; y = note->y() + Note::NOTE_MARGIN - frameWidth; } if ((m_editorWidth > 0 && m_editorWidth != width) || (m_editorHeight > 0 && m_editorHeight != height)) { m_editorWidth = width; // Avoid infinite recursion!!! m_editorHeight = height; m_editor->autoSave(/*toFileToo=*/true); } m_editorWidth = width; m_editorHeight = height; addChild(m_editor->widget(), x, y); m_editorX = x; m_editorY = y; m_leftEditorBorder->setFixedSize( (m_editor->textEdit() ? 3 : 0), height); // m_leftEditorBorder->raise(); addChild(m_leftEditorBorder, x, y ); m_leftEditorBorder->setPosition( x, y ); m_rightEditorBorder->setFixedSize(3, height); // m_rightEditorBorder->raise(); // addChild(m_rightEditorBorder, note->rightLimit() - Note::NOTE_MARGIN, note->y() + Note::NOTE_MARGIN ); // m_rightEditorBorder->setPosition( note->rightLimit() - Note::NOTE_MARGIN, note->y() + Note::NOTE_MARGIN ); addChild(m_rightEditorBorder, note->x() + note->width() - Note::NOTE_MARGIN, note->y() + Note::NOTE_MARGIN ); m_rightEditorBorder->setPosition( note->x() + note->width() - Note::NOTE_MARGIN, note->y() + Note::NOTE_MARGIN ); // if (andEnsureVisible) // ensureNoteVisible(note); } #include #include void Basket::editorCursorPositionChanged() { if (!isDuringEdit()) return; FocusedTextEdit *textEdit = (FocusedTextEdit*) m_editor->textEdit(); const TQTextCursor *cursor = textEdit->textCursor(); // std::cout << cursor->x() << ";" << cursor->y() << " " // << cursor->globalX() << ";" << cursor->globalY() << " " // << cursor->offsetX() << ";" << cursor->offsetY() << ";" << std::endl; ensureVisible(m_editorX + cursor->globalX(), m_editorY + cursor->globalY(), 50, 50); } void Basket::closeEditorDelayed() { setFocus(); TQTimer::singleShot( 0, this, TQT_SLOT(closeEditor()) ); } bool Basket::closeEditor() { if (!isDuringEdit()) return true; if (m_doNotCloseEditor) return true; if (m_redirectEditActions) { disconnect( m_editor->widget(), TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(selectionChangedInEditor()) ); if (m_editor->textEdit()) { disconnect( m_editor->textEdit(), TQT_SIGNAL(textChanged()), this, TQT_SLOT(selectionChangedInEditor()) ); disconnect( m_editor->textEdit(), TQT_SIGNAL(textChanged()), this, TQT_SLOT(contentChangedInEditor()) ); } else if (m_editor->lineEdit()) { disconnect( m_editor->lineEdit(), TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(selectionChangedInEditor()) ); disconnect( m_editor->lineEdit(), TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(contentChangedInEditor()) ); } } m_editor->widget()->disconnect(); m_editor->widget()->hide(); m_editor->validate(); delete m_leftEditorBorder; delete m_rightEditorBorder; m_leftEditorBorder = 0; m_rightEditorBorder = 0; Note *note = m_editor->note(); note->setWidth(0); // For relayoutNotes() to succeed to take care of the change // Delete the editor BEFORE unselecting the note because unselecting the note would trigger closeEditor() recursivly: bool isEmpty = m_editor->isEmpty(); delete m_editor; m_editor = 0; m_redirectEditActions = false; m_editorWidth = -1; m_editorHeight = -1; m_inactivityAutoSaveTimer.stop(); // Delete the note if it is now empty: if (isEmpty) { focusANonSelectedNoteAboveOrThenBelow(); note->setSelected(true); note->deleteSelectedNotes(); save(); note = 0; } unlockHovering(); filterAgain(/*andEnsureVisible=*/false); // Does not work: // if (Settings::playAnimations()) // note->setOnTop(true); // So if it grew, do not obscure it temporarily while the notes below it are moving if (note) note->setSelected(false);//unselectAll(); doHoverEffects(); // save(); Global::bnpView->m_actEditNote->setEnabled( !isLocked() && countSelecteds() == 1 /*&& !isDuringEdit()*/ ); emit resetStatusBarText(); // Remove the "Editing. ... to validate." text. //if (kapp->activeWindow() == Global::mainContainer) // 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: if (!decoration()->filterBar()->lineEdit()->hasFocus()) setFocus(); // Return true if the note is still there: return (note != 0); } void Basket::closeBasket() { closeEditor(); unbufferizeAll(); // Keep the memory footprint low if (isEncrypted()) { if (Settings::enableReLockTimeout()) { int seconds = Settings::reLockTimeoutMinutes() * 60; m_inactivityAutoLockTimer.start(seconds * 1000, /*singleShot=*/true); } } } void Basket::openBasket() { if (m_inactivityAutoLockTimer.isActive()) m_inactivityAutoLockTimer.stop(); } Note* Basket::theSelectedNote() { if (countSelecteds() != 1) { std::cout << "NO SELECTED NOTE !!!!" << std::endl; return 0; } Note *selectedOne; FOR_EACH_NOTE (note) { selectedOne = note->theSelectedNote(); if (selectedOne) return selectedOne; } std::cout << "One selected note, BUT NOT FOUND !!!!" << std::endl; return 0; } void debugSel(NoteSelection* sel, int n = 0) { for (NoteSelection *node = sel; node; node = node->next) { for (int i = 0; i < n; i++) std::cout << "-"; std::cout << (node->firstChild ? "Group" : node->note->content()->toText("")) << std::endl; if (node->firstChild) debugSel(node->firstChild, n+1); } } NoteSelection* Basket::selectedNotes() { NoteSelection selection; FOR_EACH_NOTE (note) selection.append(note->selectedNotes()); if (!selection.firstChild) return 0; for (NoteSelection *node = selection.firstChild; node; node = node->next) node->parent = 0; // If the top-most groups are columns, export only childs of those groups // (because user is not consciencious that columns are groups, and don't care: it's not what she want): if (selection.firstChild->note->isColumn()) { NoteSelection tmpSelection; NoteSelection *nextNode; NoteSelection *nextSubNode; for (NoteSelection *node = selection.firstChild; node; node = nextNode) { nextNode = node->next; if (node->note->isColumn()) { for (NoteSelection *subNode = node->firstChild; subNode; subNode = nextSubNode) { nextSubNode = subNode->next; tmpSelection.append(subNode); subNode->parent = 0; subNode->next = 0; } } else { tmpSelection.append(node); node->parent = 0; node->next = 0; } } // debugSel(tmpSelection.firstChild); return tmpSelection.firstChild; } else { // debugSel(selection.firstChild); return selection.firstChild; } } void Basket::showEditedNoteWhileFiltering() { if (m_editor) { Note *note = m_editor->note(); filterAgain(); note->setSelected(true); relayoutNotes(false); note->setX(note->finalX()); note->setY(note->finalY()); filterAgainDelayed(); } } void Basket::noteEdit(Note *note, bool justAdded, const TQPoint &clickedPoint) // TODO: Remove the first parameter!!! { if (!note) note = theSelectedNote(); // TODO: Or pick the focused note! if (!note) return; if (isDuringEdit()) { closeEditor(); // Validate the noteeditors in KLineEdit that does not intercept Enter key press (and edit is triggered with Enter too... Can conflict) return; } if (note != m_focusedNote) { setFocusedNote(note); m_startOfShiftSelectionNote = note; } if (justAdded && isFiltering()) { TQTimer::singleShot( 0, this, TQT_SLOT(showEditedNoteWhileFiltering()) ); } doHoverEffects(note, Note::Content); // Be sure (in the case Edit was triggered by menu or Enter key...): better feedback! //m_lockedHovering = true; //m_editorWidget = note->content()->launchEdit(this); NoteEditor *editor = NoteEditor::editNoteContent(note->content(), this); if (editor->widget()) { m_editor = editor; m_leftEditorBorder = new TransparentWidget(this); m_rightEditorBorder = new TransparentWidget(this); m_editor->widget()->reparent(viewport(), TQPoint(0,0), true); m_leftEditorBorder->reparent(viewport(), TQPoint(0,0), true); m_rightEditorBorder->reparent(viewport(), TQPoint(0,0), true); addChild(m_editor->widget(), 0, 0); placeEditorAndEnsureVisible(); // placeEditor(); // FIXME: After? m_redirectEditActions = m_editor->lineEdit() || m_editor->textEdit(); if (m_redirectEditActions) { connect( m_editor->widget(), TQT_SIGNAL(selectionChanged()), this, TQT_SLOT(selectionChangedInEditor()) ); // In case there is NO text, "Select All" is disabled. But if the user press a key the there is now a text: // selection has not changed but "Select All" should be re-enabled: if (m_editor->textEdit()) { connect( m_editor->textEdit(), TQT_SIGNAL(textChanged()), this, TQT_SLOT(selectionChangedInEditor()) ); connect( m_editor->textEdit(), TQT_SIGNAL(textChanged()), this, TQT_SLOT(contentChangedInEditor()) ); } else if (m_editor->lineEdit()) { connect( m_editor->lineEdit(), TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(selectionChangedInEditor()) ); connect( m_editor->lineEdit(), TQT_SIGNAL(textChanged(const TQString&)), this, TQT_SLOT(contentChangedInEditor()) ); } } m_editor->widget()->show(); //m_editor->widget()->raise(); m_editor->widget()->setFocus(); connect( m_editor, TQT_SIGNAL(askValidation()), this, TQT_SLOT(closeEditorDelayed()) ); connect( m_editor, TQT_SIGNAL(mouseEnteredEditorWidget()), this, TQT_SLOT(mouseEnteredEditorWidget()) ); if (m_editor->textEdit()) { connect( m_editor->textEdit(), TQT_SIGNAL(textChanged()), this, TQT_SLOT(placeEditorAndEnsureVisible()) ); if (clickedPoint != TQPoint()) { TQPoint pos(clickedPoint.x() - note->x() - note->contentX() + m_editor->textEdit()->frameWidth() + 4 - m_editor->textEdit()->frameWidth(), clickedPoint.y() - note->y() - m_editor->textEdit()->frameWidth()); // 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): m_editor->textEdit()->moveCursor(KTextEdit::MoveHome, false); m_editor->textEdit()->ensureCursorVisible(); m_editor->textEdit()->placeCursor(pos); updateEditorAppearance(); } } // kapp->processEvents(); // Show the editor toolbar before ensuring the note is visible ensureNoteVisible(note); // because toolbar can create a new line and then partially hide the note m_editor->widget()->setFocus(); // When clicking in the basket, a TQTimer::singleShot(0, ...) focus the basket! So we focus the the widget after kapp->processEvents() emit resetStatusBarText(); // Display "Editing. ... to validate." } else { // Delete the note user have canceled the addition: if ((justAdded && editor->canceled()) || editor->isEmpty() /*) && editor->note()->states().count() <= 0*/) { focusANonSelectedNoteAboveOrThenBelow(); editor->note()->setSelected(true); editor->note()->deleteSelectedNotes(); save(); } delete editor; unlockHovering(); filterAgain(); unselectAll(); } Global::bnpView->m_actEditNote->setEnabled(false); } void Basket::noteDelete() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->del(); else if (m_editor->lineEdit()) m_editor->lineEdit()->del(); return; } if (countSelecteds() <= 0) return; int really = KMessageBox::Yes; if (Settings::confirmNoteDeletion()) really = KMessageBox::questionYesNo( this, i18n("Do you really want to delete this note?", "Do you really want to delete those %n notes?", countSelecteds()), i18n("Delete Note", "Delete Notes", countSelecteds()) #if KDE_IS_VERSION( 3, 2, 90 ) // KDE 3.3.x , KStdGuiItem::del(), KStdGuiItem::cancel()); #else ); #endif if (really == KMessageBox::No) return; noteDeleteWithoutConfirmation(); } void Basket::focusANonSelectedNoteBelow(bool inSameColumn) { // First focus another unselected one below it...: if (m_focusedNote && m_focusedNote->isSelected()) { Note *next = m_focusedNote->nextShownInStack(); while (next && next->isSelected()) next = next->nextShownInStack(); if (next) { if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == next->parentPrimaryNote()) { setFocusedNote(next); m_startOfShiftSelectionNote = next; } } } } void Basket::focusANonSelectedNoteAbove(bool inSameColumn) { // ... Or above it: if (m_focusedNote && m_focusedNote->isSelected()) { Note *prev = m_focusedNote->prevShownInStack(); while (prev && prev->isSelected()) prev = prev->prevShownInStack(); if (prev) { if (inSameColumn && isColumnsLayout() && m_focusedNote->parentPrimaryNote() == prev->parentPrimaryNote()) { setFocusedNote(prev); m_startOfShiftSelectionNote = prev; } } } } void Basket::focusANonSelectedNoteBelowOrThenAbove() { focusANonSelectedNoteBelow(/*inSameColumn=*/true); focusANonSelectedNoteAbove(/*inSameColumn=*/true); focusANonSelectedNoteBelow(/*inSameColumn=*/false); focusANonSelectedNoteAbove(/*inSameColumn=*/false); } void Basket::focusANonSelectedNoteAboveOrThenBelow() { focusANonSelectedNoteAbove(/*inSameColumn=*/true); focusANonSelectedNoteBelow(/*inSameColumn=*/true); focusANonSelectedNoteAbove(/*inSameColumn=*/false); focusANonSelectedNoteBelow(/*inSameColumn=*/false); } void Basket::noteDeleteWithoutConfirmation(bool deleteFilesToo) { // If the currently focused note is selected, it will be deleted. focusANonSelectedNoteBelowOrThenAbove(); // Do the deletion: Note *note = firstNote(); Note *next; while (note) { next = note->next(); // If we delete 'note' on the next line, note->next() will be 0! note->deleteSelectedNotes(deleteFilesToo); note = next; } relayoutNotes(true); // FIXME: filterAgain()? save(); } void Basket::doCopy(CopyMode copyMode) { TQClipboard *cb = KApplication::clipboard(); TQClipboard::Mode mode = (copyMode == CopyToSelection ? TQClipboard::Selection : TQClipboard::Clipboard); NoteSelection *selection = selectedNotes(); int countCopied = countSelecteds(); if (selection->firstStacked()) { TQDragObject *d = NoteDrag::dragObject(selection, copyMode == CutToClipboard, /*source=*/0); // d will be deleted by QT // /*bool shouldRemove = */d->drag(); // delete selection; cb->setData(d, mode); // NoteMultipleDrag will be deleted by QT // if (copyMode == CutToClipboard && !note->useFile()) // If useFile(), NoteDrag::dragObject() will delete it TODO // note->slotDelete(); if (copyMode == CutToClipboard) noteDeleteWithoutConfirmation(/*deleteFilesToo=*/false); switch (copyMode) { default: case CopyToClipboard: emit postMessage(i18n("Copied note to clipboard.", "Copied notes to clipboard.", countCopied)); break; case CutToClipboard: emit postMessage(i18n("Cut note to clipboard.", "Cut notes to clipboard.", countCopied)); break; case CopyToSelection: emit postMessage(i18n("Copied note to selection.", "Copied notes to selection.", countCopied)); break; } } } void Basket::noteCopy() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->copy(); else if (m_editor->lineEdit()) m_editor->lineEdit()->copy(); } else doCopy(CopyToClipboard); } void Basket::noteCut() { if (redirectEditActions()) { if (m_editor->textEdit()) m_editor->textEdit()->cut(); else if (m_editor->lineEdit()) m_editor->lineEdit()->cut(); } else doCopy(CutToClipboard); } void Basket::noteOpen(Note *note) { /* GetSelectedNotes NoSelectedNote || Count == 0 ? return AllTheSameType ? Get { url, message(count) } */ // TODO: Open ALL selected notes! if (!note) note = theSelectedNote(); if (!note) return; KURL url = note->content()->urlToOpen(/*with=*/false); TQString message = note->content()->messageWhenOpenning(NoteContent::OpenOne /*NoteContent::OpenSeveral*/); if (url.isEmpty()) { if (message.isEmpty()) emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/); else { int result = KMessageBox::warningContinueCancel(this, message, /*caption=*/TQString(), KGuiItem(i18n("&Edit"), "edit")); if (result == KMessageBox::Continue) noteEdit(note); } } else { emit postMessage(message); // "Openning link target..." / "Launching application..." / "Openning note file..." // Finally do the opening job: TQString customCommand = note->content()->customOpenCommand(); if (customCommand.isEmpty()) { KRun *run = new KRun(url); run->setAutoDelete(true); } else KRun::run(customCommand, url); } } /** Code from bool KRun::displayOpenWithDialog(const KURL::List& lst, bool tempFiles) * It does not allow to set a text, so I ripped it to do that: */ bool KRun__displayOpenWithDialog(const KURL::List& lst, bool tempFiles, const TQString &text) { if (kapp && !kapp->authorizeKAction("openwith")) { KMessageBox::sorry(0L, i18n("You are not authorized to open this file.")); // TODO: Better message, i18n freeze :-( return false; } KOpenWithDlg l(lst, text, TQString(), 0L); if (l.exec()) { KService::Ptr service = l.service(); if (!!service) return KRun::run(*service, lst, tempFiles); //kdDebug(250) << "No service set, running " << l.text() << endl; return KRun::run(l.text(), lst); // TODO handle tempFiles } return false; } void Basket::noteOpenWith(Note *note) { if (!note) note = theSelectedNote(); if (!note) return; KURL url = note->content()->urlToOpen(/*with=*/true); TQString message = note->content()->messageWhenOpenning(NoteContent::OpenOneWith /*NoteContent::OpenSeveralWith*/); TQString text = note->content()->messageWhenOpenning(NoteContent::OpenOneWithDialog /*NoteContent::OpenSeveralWithDialog*/); if (url.isEmpty()) emit postMessage(i18n("Unable to open this note.") /*"Unable to open those notes."*/); else if (KRun__displayOpenWithDialog(url, false, text)) emit postMessage(message); // "Openning link target with..." / "Openning note file with..." } void Basket::noteSaveAs() { // if (!note) // note = theSelectedNote(); Note *note = theSelectedNote(); if (!note) return; KURL url = note->content()->urlToOpen(/*with=*/false); if (url.isEmpty()) return; TQString fileName = KFileDialog::getSaveFileName(url.fileName(), note->content()->saveAsFilters(), this, i18n("Save to File")); // TODO: Ask to overwrite ! if (fileName.isEmpty()) return; // TODO: Convert format, etc. (use NoteContent::saveAs(fileName)) KIO::copy(url, KURL(fileName)); } Note* Basket::selectedGroup() { FOR_EACH_NOTE (note) { Note *selectedGroup = note->selectedGroup(); if (selectedGroup) { // If the selected group is one group in a column, then return that group, and not the column, // because the column is not ungrouppage, and the Ungroup action would be disabled. if (selectedGroup->isColumn() && selectedGroup->firstChild() && !selectedGroup->firstChild()->next()) { return selectedGroup->firstChild(); } return selectedGroup; } } return 0; } bool Basket::selectionIsOneGroup() { return (selectedGroup() != 0); } Note* Basket::firstSelected() { Note *first = 0; FOR_EACH_NOTE (note) { first = note->firstSelected(); if (first) return first; } return 0; } Note* Basket::lastSelected() { Note *last = 0, *tmp = 0; FOR_EACH_NOTE (note) { tmp = note->lastSelected(); if (tmp) last = tmp; } return last; } bool Basket::convertTexts() { m_watcher->stopScan(); bool convertedNotes = false; if (!isLoaded()) load(); FOR_EACH_NOTE (note) if (note->convertTexts()) convertedNotes = true; if (convertedNotes) save(); m_watcher->startScan(); return convertedNotes; } void Basket::noteGroup() { /* // Nothing to do? if (isLocked() || countSelecteds() <= 1) return; // If every selected notes are ALREADY in one group, then don't touch anything: Note *selectedGroup = this->selectedGroup(); if (selectedGroup && !selectedGroup->isColumn()) return; */ // Copied from BNPView::updateNotesActions() bool severalSelected = countSelecteds() >= 2; Note *selectedGroup = (severalSelected ? this->selectedGroup() : 0); bool enabled = !isLocked() && severalSelected && (!selectedGroup || selectedGroup->isColumn()); if (!enabled) return; // Get the first selected note: we will group selected items just before: Note *first = firstSelected(); // if (selectedGroup != 0 || first == 0) // return; m_loaded = false; // Hack to avoid notes to be unselected and new notes to be selected: // Create and insert the receiving group: Note *group = new Note(this); if (first->isFree()) { insertNote(group, 0L, Note::BottomColumn, TQPoint(first->finalX(), first->finalY()), /*animateNewPosition=*/false); } else { insertNote(group, first, Note::TopInsert, TQPoint(), /*animateNewPosition=*/false); } // 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! Note *fakeNote = NoteFactory::createNoteColor(TQt::red, this); insertNote(fakeNote, group, Note::BottomColumn, TQPoint(), /*animateNewPosition=*/false); // Group the notes: Note *nextNote; Note *note = firstNote(); while (note) { nextNote = note->next(); note->groupIn(group); note = nextNote; } m_loaded = true; // Part 2 / 2 of the workarround! // Do cleanup: unplugNote(fakeNote); unselectAll(); group->setSelectedRecursivly(true); // Notes were unselected by unplugging relayoutNotes(true); save(); } void Basket::noteUngroup() { Note *group = selectedGroup(); if (group && !group->isColumn()) ungroupNote(group); save(); } void Basket::unplugSelection(NoteSelection *selection) { for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) unplugNote(toUnplug->note); } void Basket::insertSelection(NoteSelection *selection, Note *after) { for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) { if (toUnplug->note->isGroup()) { Note *group = new Note(this); insertNote(group, after, Note::BottomInsert, TQPoint(), /*animateNewPosition=*/false); Note *fakeNote = NoteFactory::createNoteColor(TQt::red, this); insertNote(fakeNote, group, Note::BottomColumn, TQPoint(), /*animateNewPosition=*/false); insertSelection(toUnplug->firstChild, fakeNote); unplugNote(fakeNote); after = group; } else { Note *note = toUnplug->note; note->setPrev(0); note->setNext(0); insertNote(note, after, Note::BottomInsert, TQPoint(), /*animateNewPosition=*/true); after = note; } } } void Basket::selectSelection(NoteSelection *selection) { for (NoteSelection *toUnplug = selection->firstStacked(); toUnplug; toUnplug = toUnplug->nextStacked()) { if (toUnplug->note->isGroup()) selectSelection(toUnplug); else toUnplug->note->setSelected(true); } } void Basket::noteMoveOnTop() { // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket // TODO: Move on top/bottom... of the column or basjet NoteSelection *selection = selectedNotes(); unplugSelection(selection); // Replug the notes: Note *fakeNote = NoteFactory::createNoteColor(TQt::red, this); if (isColumnsLayout()) { if (firstNote()->firstChild()) insertNote(fakeNote, firstNote()->firstChild(), Note::TopInsert, TQPoint(), /*animateNewPosition=*/false); else insertNote(fakeNote, firstNote(), Note::BottomColumn, TQPoint(), /*animateNewPosition=*/false); } else { // TODO: Also allow to move notes on top of a group!!!!!!! insertNote(fakeNote, 0, Note::BottomInsert, TQPoint(0, 0), /*animateNewPosition=*/false); } insertSelection(selection, fakeNote); unplugNote(fakeNote); selectSelection(selection); relayoutNotes(true); save(); } void Basket::noteMoveOnBottom() { // TODO: Duplicate code: void noteMoveOn(); // TODO: Get the group containing the selected notes and first move inside the group, then inside parent group, then in the basket // TODO: Move on top/bottom... of the column or basjet NoteSelection *selection = selectedNotes(); unplugSelection(selection); // Replug the notes: Note *fakeNote = NoteFactory::createNoteColor(TQt::red, this); if (isColumnsLayout()) insertNote(fakeNote, firstNote(), Note::BottomColumn, TQPoint(), /*animateNewPosition=*/false); else { // TODO: Also allow to move notes on top of a group!!!!!!! insertNote(fakeNote, 0, Note::BottomInsert, TQPoint(0, 0), /*animateNewPosition=*/false); } insertSelection(selection, fakeNote); unplugNote(fakeNote); selectSelection(selection); relayoutNotes(true); save(); } void Basket::moveSelectionTo(Note *here, bool below/* = true*/) { NoteSelection *selection = selectedNotes(); unplugSelection(selection); // Replug the notes: Note *fakeNote = NoteFactory::createNoteColor(TQt::red, this); // if (isColumnsLayout()) insertNote(fakeNote, here, (below ? Note::BottomInsert : Note::TopInsert), TQPoint(), /*animateNewPosition=*/false); // else { // // TODO: Also allow to move notes on top of a group!!!!!!! // insertNote(fakeNote, 0, Note::BottomInsert, TQPoint(0, 0), /*animateNewPosition=*/false); // } insertSelection(selection, fakeNote); unplugNote(fakeNote); selectSelection(selection); relayoutNotes(true); save(); } void Basket::noteMoveNoteUp() { // TODO: Move between columns, even if they are empty !!!!!!! // TODO: if first note of a group, move just above the group! And let that even if there is no note before that group!!! Note *first = firstSelected(); Note *previous = first->prevShownInStack(); if (previous) moveSelectionTo(previous, /*below=*/false); } void Basket::noteMoveNoteDown() { Note *first = lastSelected(); Note *next = first->nextShownInStack(); if (next) moveSelectionTo(next, /*below=*/true); } void Basket::wheelEvent(TQWheelEvent *event) { TQScrollView::wheelEvent(event); } void Basket::linkLookChanged() { Note *note = m_firstNote; while (note) { note->linkLookChanged(); note = note->next(); } relayoutNotes(true); } void Basket::slotCopyingDone2(KIO::Job *job) { if (job->error()) { DEBUG_WIN << "Copy finished, ERROR"; return; } KIO::FileCopyJob *fileCopyJob = (KIO::FileCopyJob*)job; Note *note = noteForFullPath(fileCopyJob->destURL().path()); DEBUG_WIN << "Copy finished, load note: " + fileCopyJob->destURL().path() + (note ? "" : " --- NO CORRESPONDING NOTE"); if (note != 0L) { note->content()->loadFromFile(/*lazyLoad=*/false); if(isEncrypted()) note->content()->saveToFile(); if (m_focusedNote == note) // When inserting a new note we ensure it visble ensureNoteVisible(note); // But after loading it has certainly grown and if it was } // on bottom of the basket it's not visible entirly anymore } Note* Basket::noteForFullPath(const TQString &path) { Note *note = firstNote(); Note *found; while (note) { found = note->noteForFullPath(path); if (found) return found; note = note->next(); } return 0; } void Basket::deleteFiles() { m_watcher->stopScan(); Tools::deleteRecursively(fullPath()); } TQValueList Basket::usedStates() { TQValueList states; FOR_EACH_NOTE (note) note->usedStates(states); return states; } TQString Basket::saveGradientBackground(const TQColor &color, const TQFont &font, const TQString &folder) { // Construct file name and return if the file already exists: TQString fileName = "note_background_" + TQString(color.name()).lower().mid(1) + ".png"; TQString fullPath = folder + fileName; if (TQFile::exists(fullPath)) return fileName; // Get the gradient top and bottom colors: TQColor topBgColor; TQColor bottomBgColor; Note::getGradientColors(color, &topBgColor, &bottomBgColor); // Draw and save the gradient image: int sampleTextHeight = TQFontMetrics(font) .boundingRect(0, 0, /*width=*/10000, /*height=*/0, TQt::AlignAuto | TQt::AlignTop | TQt::WordBreak, "Test text") .height(); TQPixmap noteGradient(100, sampleTextHeight + Note::NOTE_MARGIN); TQPainter painter(¬eGradient); drawGradient(&painter, topBgColor, bottomBgColor, 0, 0, noteGradient.width(), noteGradient.height(), /*sunken=*/false, /*horz=*/true, /*flat=*/false); painter.end(); noteGradient.save(fullPath, "PNG"); // Return the name of the created file: return fileName; } void Basket::listUsedTags(TQValueList &list) { if (!isLoaded()) { load(); } FOR_EACH_NOTE (child) child->listUsedTags(list); } /** Unfocus the previously focused note (unless it was null) * and focus the new @param note (unless it is null) if hasFocus() * Update m_focusedNote to the new one */ void Basket::setFocusedNote(Note *note) // void Basket::changeFocusTo(Note *note) { // Don't focus an hidden note: if (note != 0L && !note->isShown()) return; // When clicking a group, this group gets focused. But only content-based notes should be focused: if (note && note->isGroup()) note = note->firstRealChild(); // The first time a note is focused, it becomes the start of the Shift selection: if (m_startOfShiftSelectionNote == 0) m_startOfShiftSelectionNote = note; // Unfocus the old focused note: if (m_focusedNote != 0L) m_focusedNote->setFocused(false); // Notify the new one to draw a focus rectangle... only if the basket is focused: if (hasFocus() && note != 0L) note->setFocused(true); // Save the new focused note: m_focusedNote = note; } /** If no shown note is currently focused, try to find a shown note and focus it * Also update m_focusedNote to the new one (or null if there isn't) */ void Basket::focusANote() { if (countFounds() == 0) { // No note to focus setFocusedNote(0L); // m_startOfShiftSelectionNote = 0; return; } if (m_focusedNote == 0L) { // No focused note yet : focus the first shown Note *toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack()); setFocusedNote(toFocus); // m_startOfShiftSelectionNote = m_focusedNote; return; } // Search a visible note to focus if the focused one isn't shown : Note *toFocus = m_focusedNote; if (toFocus && !toFocus->isShown()) toFocus = toFocus->nextShownInStack(); if (!toFocus && m_focusedNote) toFocus = m_focusedNote->prevShownInStack(); setFocusedNote(toFocus); // m_startOfShiftSelectionNote = toFocus; } Note* Basket::firstNoteInStack() { if (!firstNote()) return 0; if (firstNote()->content()) return firstNote(); else return firstNote()->nextInStack(); } Note* Basket::lastNoteInStack() { Note *note = lastNote(); while (note) { if (note->content()) return note; Note *possibleNote = note->lastRealChild(); if (possibleNote && possibleNote->content()) return possibleNote; note = note->prev(); } return 0; } Note* Basket::firstNoteShownInStack() { Note *first = firstNoteInStack(); while (first && !first->isShown()) first = first->nextInStack(); return first; } Note* Basket::lastNoteShownInStack() { Note *last = lastNoteInStack(); while (last && !last->isShown()) last = last->prevInStack(); return last; } inline int abs(int n) { return (n < 0 ? -n : n); } Note* Basket::noteOn(NoteOn side) { Note *bestNote = 0; int distance = -1; int bestDistance = contentsWidth() * contentsHeight() * 10; Note *note = firstNoteShownInStack(); Note *primary = m_focusedNote->parentPrimaryNote(); while (note) { switch (side) { case LEFT_SIDE: distance = m_focusedNote->distanceOnLeftRight(note, LEFT_SIDE); break; case RIGHT_SIDE: distance = m_focusedNote->distanceOnLeftRight(note, RIGHT_SIDE); break; case TOP_SIDE: distance = m_focusedNote->distanceOnTopBottom(note, TOP_SIDE); break; case BOTTOM_SIDE: distance = m_focusedNote->distanceOnTopBottom(note, BOTTOM_SIDE); break; } if ((side == TOP_SIDE || side == BOTTOM_SIDE || primary != note->parentPrimaryNote()) && note != m_focusedNote && distance > 0 && distance < bestDistance) { bestNote = note; bestDistance = distance; } note = note ->nextShownInStack(); } return bestNote; } Note* Basket::firstNoteInGroup() { Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0); while (parent) { if (parent->firstChild() != child && !parent->isColumn()) return parent->firstRealChild(); child = parent; parent = parent->parentNote(); } return 0; } Note* Basket::noteOnHome() { // First try to find the first note of the group containing the focused note: Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0); while (parent) { if (parent->nextShownInStack() != m_focusedNote) return parent->nextShownInStack(); child = parent; parent = parent->parentNote(); } // If it was not found, then focus the very first note in the basket: if (isFreeLayout()) { Note *first = firstNoteShownInStack(); // The effective first note found Note *note = first; // The current note, to conpare with the previous first note, if this new note is more on top if (note) note = note->nextShownInStack(); while (note) { if (note->finalY() < first->finalY() || (note->finalY() == first->finalY() && note->finalX() < first->finalX())) first = note; note = note->nextShownInStack(); } return first; } else return firstNoteShownInStack(); } Note* Basket::noteOnEnd() { Note *child = m_focusedNote; Note *parent = (m_focusedNote ? m_focusedNote->parentNote() : 0); Note *lastChild; while (parent) { lastChild = parent->lastRealChild(); if (lastChild && lastChild != m_focusedNote) { if (lastChild->isShown()) return lastChild; lastChild = lastChild->prevShownInStack(); if (lastChild && lastChild->isShown() && lastChild != m_focusedNote) return lastChild; } child = parent; parent = parent->parentNote(); } if (isFreeLayout()) { Note *last; Note *note; last = note = firstNoteShownInStack(); note = note->nextShownInStack(); while (note) { if (note->finalBottom() > last->finalBottom() || (note->finalBottom() == last->finalBottom() && note->finalX() > last->finalX())) last = note; note = note->nextShownInStack(); } return last; } else return lastNoteShownInStack(); } void Basket::keyPressEvent(TQKeyEvent *event) { if (isDuringEdit() && event->key() == TQt::Key_Return) { //if (m_editor->lineEdit()) // closeEditor(); //else m_editor->widget()->setFocus(); } else if (event->key() == TQt::Key_Escape) { if (isDuringEdit()) closeEditor(); else if (decoration()->filterData().isFiltering) cancelFilter(); else unselectAll(); } if (countFounds() == 0) return; if (!m_focusedNote) return; Note *toFocus = 0L; switch (event->key()) { case TQt::Key_Down: toFocus = (isFreeLayout() ? noteOn(BOTTOM_SIDE) : m_focusedNote->nextShownInStack()); if (toFocus) break; scrollBy(0, 30); // This cases do not move focus to another note... return; case TQt::Key_Up: toFocus = (isFreeLayout() ? noteOn(TOP_SIDE) : m_focusedNote->prevShownInStack()); if (toFocus) break; scrollBy(0, -30); // This cases do not move focus to another note... return; case TQt::Key_PageDown: if (isFreeLayout()) { Note *lastFocused = m_focusedNote; for (int i = 0; i < 10 && m_focusedNote; ++i) m_focusedNote = noteOn(BOTTOM_SIDE); toFocus = m_focusedNote; m_focusedNote = lastFocused; } else { toFocus = m_focusedNote; for (int i = 0; i < 10 && toFocus; ++i) toFocus = toFocus->nextShownInStack(); } if (toFocus == 0L) toFocus = (isFreeLayout() ? noteOnEnd() : lastNoteShownInStack()); if (toFocus && toFocus != m_focusedNote) break; scrollBy(0, visibleHeight() / 2); // This cases do not move focus to another note... return; case TQt::Key_PageUp: if (isFreeLayout()) { Note *lastFocused = m_focusedNote; for (int i = 0; i < 10 && m_focusedNote; ++i) m_focusedNote = noteOn(TOP_SIDE); toFocus = m_focusedNote; m_focusedNote = lastFocused; } else { toFocus = m_focusedNote; for (int i = 0; i < 10 && toFocus; ++i) toFocus = toFocus->prevShownInStack(); } if (toFocus == 0L) toFocus = (isFreeLayout() ? noteOnHome() : firstNoteShownInStack()); if (toFocus && toFocus != m_focusedNote) break; scrollBy(0, - visibleHeight() / 2); // This cases do not move focus to another note... return; case TQt::Key_Home: toFocus = noteOnHome(); break; case TQt::Key_End: toFocus = noteOnEnd(); break; case TQt::Key_Left: if (m_focusedNote->tryFoldParent()) return; if ( (toFocus = noteOn(LEFT_SIDE)) ) break; if ( (toFocus = firstNoteInGroup()) ) break; scrollBy(-30, 0); // This cases do not move focus to another note... return; case TQt::Key_Right: if (m_focusedNote->tryExpandParent()) return; if ( (toFocus = noteOn(RIGHT_SIDE)) ) break; scrollBy(30, 0); // This cases do not move focus to another note... return; case TQt::Key_Space: // This case do not move focus to another note... if (m_focusedNote) { m_focusedNote->setSelected( ! m_focusedNote->isSelected() ); event->accept(); } else event->ignore(); return; // ... so we return after the process default: return; } if (toFocus == 0L) { // If no direction keys have been pressed OR reached out the begin or end event->ignore(); // Important !! return; } if (event->state() & TQt::ShiftButton) { // Shift+arrowKeys selection if (m_startOfShiftSelectionNote == 0L) m_startOfShiftSelectionNote = toFocus; ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part! selectRange(m_startOfShiftSelectionNote, toFocus); setFocusedNote(toFocus); event->accept(); return; } else /*if (toFocus != m_focusedNote)*/ { // Move focus to ANOTHER note... ensureNoteVisible(toFocus); // Important: this line should be before the other ones because else repaint would be done on the wrong part! setFocusedNote(toFocus); m_startOfShiftSelectionNote = toFocus; if ( ! (event->state() & TQt::ControlButton) ) // ... select only current note if Control unselectAllBut(m_focusedNote); event->accept(); return; } event->ignore(); // Important !! } /** Select a range of notes and deselect the others. * The order between start and end has no importance (end could be before start) */ void Basket::selectRange(Note *start, Note *end, bool unselectOthers /*= true*/) { Note *cur; Note *realEnd = 0L; // Avoid crash when start (or end) is null if (start == 0L) start = end; else if (end == 0L) end = start; // And if *both* are null if (start == 0L) { if (unselectOthers) unselectAll(); return; } // In case there is only one note to select if (start == end) { if (unselectOthers) unselectAllBut(start); else start->setSelected(true); return; } // Free layout baskets should select range as if we were drawing a rectangle between start and end: if (isFreeLayout()) { TQRect startRect( start->finalX(), start->finalY(), start->width(), start->finalHeight() ); TQRect endRect( end->finalX(), end->finalY(), end->width(), end->finalHeight() ); TQRect toSelect = startRect.unite(endRect); selectNotesIn(toSelect, /*invertSelection=*/false, unselectOthers); return; } // Search the REAL first (and deselect the others before it) : for (cur = firstNoteInStack(); cur != 0L; cur = cur->nextInStack()) { if (cur == start || cur == end) break; if (unselectOthers) cur->setSelected(false); } // Select the notes after REAL start, until REAL end : if (cur == start) realEnd = end; else if (cur == end) realEnd = start; for (/*cur = cur*/; cur != 0L; cur = cur->nextInStack()) { cur->setSelected(cur->isShown()); // Select all notes in the range, but only if they are shown if (cur == realEnd) break; } if (!unselectOthers) return; // Deselect the remaining notes : if (cur) cur = cur->nextInStack(); for (/*cur = cur*/; cur != 0L; cur = cur->nextInStack()) cur->setSelected(false); } void Basket::focusInEvent(TQFocusEvent*) { // Focus cannot be get with Tab when locked, but a click can focus the basket! if (isLocked()) { if (m_button) TQTimer::singleShot( 0, m_button, TQT_SLOT(setFocus()) ); } else focusANote(); // hasFocus() is true at this stage, note will be focused } void Basket::focusOutEvent(TQFocusEvent*) { if (m_focusedNote != 0L) m_focusedNote->setFocused(false); } void Basket::ensureNoteVisible(Note *note) { if (!note->isShown()) // Logical! return; if (note == editedNote()) // HACK: When filtering while editing big notes, etc... cause unwanted scrolls return; int finalBottom = note->finalY() + TQMIN(note->finalHeight(), visibleHeight()); int finalRight = note->finalX() + TQMIN(note->width() + (note->hasResizer() ? Note::RESIZER_WIDTH : 0), visibleWidth()); ensureVisible( finalRight, finalBottom, 0,0 ); ensureVisible( note->finalX(), note->finalY(), 0,0 ); } void Basket::addWatchedFile(const TQString &fullPath) { // DEBUG_WIN << "Watcher>Add Monitoring Of : " + fullPath + ""; m_watcher->addFile(fullPath); } void Basket::removeWatchedFile(const TQString &fullPath) { // DEBUG_WIN << "Watcher>Remove Monitoring Of : " + fullPath + ""; m_watcher->removeFile(fullPath); } void Basket::watchedFileModified(const TQString &fullPath) { if (!m_modifiedFiles.contains(fullPath)) m_modifiedFiles.append(fullPath); // If a big file is saved by an application, notifications are send several times. // We wait they are not sent anymore to considere the file complete! m_watcherTimer.start(200/*ms*/, true); DEBUG_WIN << "Watcher>Modified : " + fullPath + ""; } void Basket::watchedFileDeleted(const TQString &fullPath) { Note *note = noteForFullPath(fullPath); removeWatchedFile(fullPath); if (note) { NoteSelection *selection = selectedNotes(); unselectAllBut(note); noteDeleteWithoutConfirmation(); while (selection) { selection->note->setSelected(true); selection = selection->nextStacked(); } } DEBUG_WIN << "Watcher>Removed : " + fullPath + ""; } void Basket::updateModifiedNotes() { for (TQValueList::iterator it = m_modifiedFiles.begin(); it != m_modifiedFiles.end(); ++it) { Note *note = noteForFullPath(*it); if (note) note->content()->loadFromFile(/*lazyLoad=*/false); } m_modifiedFiles.clear(); } bool Basket::setProtection(int type, TQString key) { #ifdef HAVE_LIBGPGME if(type == PasswordEncryption || // Ask a new password m_encryptionType != type || m_encryptionKey != key) { int savedType = m_encryptionType; TQString savedKey = m_encryptionKey; m_encryptionType = type; m_encryptionKey = key; m_gpg->clearCache(); if(saveAgain()) { emit propertiesChanged(this); } else { m_encryptionType = savedType; m_encryptionKey = savedKey; m_gpg->clearCache(); return false; } } return true; #else m_encryptionType = type; m_encryptionKey = key; return false; #endif } bool Basket::saveAgain() { bool result = false; m_watcher->stopScan(); // Re-encrypt basket file: result = save(); // Re-encrypt every note files recursively: if(result) { FOR_EACH_NOTE (note) { result = note->saveAgain(); if(!result) break; } } m_watcher->startScan(); return result; } bool Basket::loadFromFile(const TQString &fullPath, TQString *string, bool isLocalEncoding) { TQByteArray array; if(loadFromFile(fullPath, &array)){ if (isLocalEncoding) *string = TQString::fromLocal8Bit(array.data(), array.size()); else *string = TQString::fromUtf8(array.data(), array.size()); return true; } else return false; } bool Basket::isEncrypted() { return (m_encryptionType != NoEncryption); } bool Basket::isFileEncrypted() { TQFile file(fullPath() + ".basket"); if (file.open(IO_ReadOnly)){ TQString line; file.readLine(line, 32); if(line.startsWith("-----BEGIN PGP MESSAGE-----")) return true; } return false; } bool Basket::loadFromFile(const TQString &fullPath, TQByteArray *array) { TQFile file(fullPath); bool encrypted = false; if (file.open(IO_ReadOnly)){ *array = file.readAll(); const char* magic = "-----BEGIN PGP MESSAGE-----"; uint i = 0; if(array->size() > strlen(magic)) for (i = 0; array->at(i) == magic[i]; ++i) ; if (i == strlen(magic)) { encrypted = true; } file.close(); #ifdef HAVE_LIBGPGME if(encrypted) { TQByteArray tmp(*array); tmp.detach(); // Only use gpg-agent for private key encryption since it doesn't // cache password used in symmetric encryption. m_gpg->setUseGnuPGAgent(Settings::useGnuPGAgent() && m_encryptionType == PrivateKeyEncryption); if(m_encryptionType == PrivateKeyEncryption) m_gpg->setText(i18n("Please enter the password for the following private key:"), false); else m_gpg->setText(i18n("Please enter the password for the basket %1:").arg(basketName()), false); // Used when decrypting return m_gpg->decrypt(tmp, array); } #else if(encrypted) { return false; } #endif return true; } else return false; } bool Basket::saveToFile(const TQString& fullPath, const TQString& string, bool isLocalEncoding) { TQCString bytes = (isLocalEncoding ? string.local8Bit() : string.utf8()); return saveToFile(fullPath, bytes, bytes.length()); } bool Basket::saveToFile(const TQString& fullPath, const TQByteArray& array) { return saveToFile(fullPath, array, array.size()); } bool Basket::saveToFile(const TQString& fullPath, const TQByteArray& array, TQ_ULONG length) { bool success = true; TQByteArray tmp; #ifdef HAVE_LIBGPGME if(isEncrypted()) { TQString key = TQString(); // We only use gpg-agent for private key encryption and saving without // public key doesn't need one. m_gpg->setUseGnuPGAgent(false); if(m_encryptionType == PrivateKeyEncryption) { key = m_encryptionKey; // public key doesn't need password m_gpg->setText("", false); } else m_gpg->setText(i18n("Please assign a password to the basket %1:").arg(basketName()), true); // Used when defining a new password success = m_gpg->encrypt(array, length, &tmp, key); length = tmp.size(); } else tmp = array; #else success = !isEncrypted(); if(success) tmp = array; #endif /*if (success && (success = file.open(IO_WriteOnly))){ success = (file.writeBlock(tmp) == (TQ_LONG)tmp.size()); file.close(); }*/ if (success) return safelySaveToFile(fullPath, tmp, length); else return false; } /** Same as saveToFile(), but it is static, and does not crypt the data if needed. * Basically, to save a file owned by a basket (a basket or a note file), use saveToFile(). * But to save another file (eg. the basket hierarchy), use this safelySaveToFile() static method. */ /*static*/ bool Basket::safelySaveToFile(const TQString& fullPath, const TQByteArray& array, TQ_ULONG length) { // Here, we take a double protection: // - We use KSaveFile to write atomically to the file (either it's a success or the file is untouched) // - We show a modal dialog to the user when no disk space is left or access is denied and retry every couple of seconds // Static, because safelySaveToFile() can be called a second time while blocked. // Example: // User type something and press Enter: safelySaveToFile() is called and block. // Three seconds later, a timer ask to save changes, and this second safelySaveToFile() block too. // Do not show the dialog twice in this case! static DiskErrorDialog *dialog = 0; //std::cout << "---------- Saving " << fullPath << ":" << std::endl; bool openSuccess; bool closeSuccess; bool errorWhileWritting; do { KSaveFile saveFile(fullPath); //std::cout << "==>>" << std::endl << "SAVE FILE CREATED: " << strerror(saveFile.status()) << std::endl; openSuccess = (saveFile.status() == 0 && saveFile.file() != 0); if (openSuccess) { saveFile.file()->writeBlock(array, length); //std::cout << "FILE WRITTEN: " << strerror(saveFile.status()) << std::endl; closeSuccess = saveFile.close(); //std::cout << "FILE CLOSED: " << (closeSuccess ? "well" : "erroneous") << std::endl; } errorWhileWritting = (!openSuccess || !closeSuccess || saveFile.status() != 0); if (errorWhileWritting) { //std::cout << "ERROR DETECTED" << std::endl; if (dialog == 0) { //std::cout << "Opening dialog for " << fullPath << std::endl; dialog = new DiskErrorDialog( (openSuccess ? i18n("Insufficient Disk Space to Save Basket Data") : i18n("Wrong Basket File Permissions") ), (openSuccess ? i18n("Please remove files on the disk %1 to let the application safely save your changes.") .arg(KIO::findPathMountPoint(fullPath)) : i18n("File permissions are bad for %1. Please check that you have write access to it and the parent folders.") .arg(fullPath) ), TQT_TQWIDGET(kapp->activeWindow()) ); } if (!dialog->isShown()) dialog->show(); const int retryDelay = 1000/*ms*/; const int sleepDelay = 50/*ms*/; for (int i = 0; i < retryDelay / sleepDelay; ++i) { kapp->processEvents(); usleep(sleepDelay); } } } while (errorWhileWritting); if (dialog) { delete dialog; dialog = 0; } return true; // Hum...?! } /*static*/ bool Basket::safelySaveToFile(const TQString& fullPath, const TQString& string, bool isLocalEncoding) { TQCString bytes = (isLocalEncoding ? string.local8Bit() : string.utf8()); return safelySaveToFile(fullPath, bytes, bytes.length() - 1); } /*static*/ bool Basket::safelySaveToFile(const TQString& fullPath, const TQByteArray& array) { return safelySaveToFile(fullPath, array, array.size()); } DiskErrorDialog::DiskErrorDialog(const TQString &titleMessage, const TQString &message, TQWidget *parent) : KDialogBase(KDialogBase::Plain, i18n("Save Error"), (KDialogBase::ButtonCode)0, (KDialogBase::ButtonCode)0, parent, /*name=*/"DiskError") { //enableButtonCancel(false); //enableButtonClose(false); //enableButton(Close, false); //enableButtonOK(false); setModal(true); TQHBoxLayout *layout = new TQHBoxLayout(plainPage(), /*margin=*/0, spacingHint()); TQPixmap icon = kapp->iconLoader()->loadIcon("hdd_unmount", KIcon::NoGroup, 64, KIcon::DefaultState, /*path_store=*/0L, /*canReturnNull=*/true); TQLabel *iconLabel = new TQLabel(plainPage()); iconLabel->setPixmap(icon); iconLabel->setFixedSize(iconLabel->sizeHint()); TQLabel *label = new TQLabel("

" + titleMessage + "

" + message + "

", plainPage()); if (!icon.isNull()) layout->addWidget(iconLabel); layout->addWidget(label); } DiskErrorDialog::~DiskErrorDialog() { } void DiskErrorDialog::closeEvent(TQCloseEvent *event) { event->ignore(); } void DiskErrorDialog::keyPressEvent(TQKeyEvent*) { // Escape should not close the window... } void Basket::lock() { #ifdef HAVE_LIBGPGME closeEditor(); m_gpg->clearCache(); m_locked = true; enableActions(); deleteNotes(); m_loaded = false; m_loadingLaunched = false; updateContents(); #endif } #if 0 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdirwatch.h" #include #include #include #include #include "basket.h" #include "note.h" #include "notefactory.h" #include "variouswidgets.h" #include "linklabel.h" #include "global.h" #include "container.h" #include "xmlwork.h" #include "settings.h" #include "popupmenu.h" #include "debugwindow.h" #include "exporterdialog.h" /** Basket */ const int Basket::c_updateTime = 200; // Remove the note from the basket and delete the associated file // If the note mirror a file, it will ask before deleting or not the file // But if askForMirroredFile is false, it willn't ask NOR delete the MIRRORED file // (it will anyway delete the file if it is not a mirror) void Basket::delNote(Note *note, bool askForMirroredFile) { //... if (hasFocus()) focusANote(); // We need note->next() and note->previous() here [BUT deleted note should be hidden] if (note->isSelected()) note->setSelected(false); //removeSelectedNote(); relayoutNotes(); recolorizeNotes(); resetInsertTo(); // If we delete the first or the last, pointer to it is invalid save(); if (note == m_startOfShiftSelectionNote) m_startOfShiftSelectionNote = 0L; if (isDuringEdit() && m_editor->editedNote() == note) closeEditor(false); //... } // Calculate where to paste or drop void Basket::computeInsertPlace(const TQPoint &cursorPosition) { int y = cursorPosition.y(); if (countShown() == 0) return; // TODO: Memorize the last hovered note to avoid a new computation on dragMoveEvent !! // If the mouse is not over the last note, compute which new is : // TODO: Optimization : start from m_insertAtNote and compare y position to search before or after (or the same) for (Note *it = firstNote(); it != 0L; it = it->next()) if ( (it->isShown()) && (it->y() + it->height() >= y) && (it->y() < y) ) { int center = it->y() + (it->height() / 2); m_insertAtNote = it; m_insertAfter = y > center; return; } // Else, there is at least one shown note but cursor hover NO note, so we are after the last shown note m_insertAtNote = lastShownNote(); m_insertAfter = true; // Code for rectangular notes : /*TQRect globalRect = it->rect(); globalRect.moveTopLeft(it->pos() + contentsY()); if ( globalRect.contains(curPos) ) { it->doInterestingThing(); }*/ } void Basket::dragMoveEvent(TQDragMoveEvent* event) { // m_isDuringDrag = true; if (isLocked()) return; // FIXME: viewportToContents does NOT work !!! // TQPoint pos = viewportToContents(event->pos()); TQPoint pos( event->pos().x() + contentsX(), event->pos().y() + contentsY() ); // if (insertAtCursorPos()) computeInsertPlace(pos); showFrameInsertTo(); acceptDropEvent(event); // A workarround since TQScrollView::dragAutoScroll seem to have no effect : ensureVisible(event->pos().x() + contentsX(), event->pos().y() + contentsY(), 30, 30); // TQScrollView::dragMoveEvent(event); } void Basket::dropEvent(TQDropEvent *event) { TQPoint pos = event->pos(); std::cout << "Drop Event at position " << pos.x() << ":" << pos.y() << std::endl; m_isDuringDrag = false; emit resetStatusBarText(); if (isLocked()) return; NoteFactory::dropNote( event, this, true, event->action(), dynamic_cast(event->source()) ); // TODO: need to know if we really inserted an (or several!!!!) note !!! ensureNoteVisible(lastInsertedNote()); unselectAllBut(lastInsertedNote()); setFocusedNote(lastInsertedNote()); resetInsertTo(); } void Basket::moveOnTop() { if (m_countSelecteds == 0) return; Note *endOfBrowse = firstShownNote(); Note *topNote = firstNote(); Note *prev; for (Note *it = lastShownNote(); it != 0L; ) { prev = it->previous(); if (it->isSelected()) { m_insertAtNote = topNote; m_insertAfter = false; changeNotePlace(it); topNote = it; } if (it == endOfBrowse) break; it = prev; } ensureNoteVisible(firstShownNote()); ensureNoteVisible(m_focusedNote); } void Basket::moveOnBottom() { if (m_countSelecteds == 0) return; Note *endOfBrowse = lastShownNote(); Note *bottomNote = lastNote(); Note *next; for (Note *it = firstShownNote(); it != 0L; ) { next = it->next(); if (it->isSelected()) { m_insertAtNote = bottomNote; m_insertAfter = true; changeNotePlace(it); bottomNote = it; } if (it == endOfBrowse) break; it = next; } ensureNoteVisible(lastShownNote()); ensureNoteVisible(m_focusedNote); } void Basket::moveNoteUp() { if (m_countSelecteds == 0) return; // Begin from the top (important move all selected notes one note up // AND to quit early if a selected note is the first shown one for (Note *it = firstShownNote(); it != 0L; it = it->next()) { if (it->isSelected() && it->isShown()) { // it->isShown() not necessary, but in case... if (it == firstShownNote()) return; // No way... m_insertAtNote = nextShownNoteFrom(it, -1); // Previous shown note if (m_insertAtNote == 0L) { // Should not appends, since it's not the first shown note, resetInsertTo(); // there SHOULD be one before return; } m_insertAfter = false; changeNotePlace(it); } if (it == lastShownNote()) break; } ensureNoteVisible(m_focusedNote); } void Basket::moveNoteDown() { if (m_countSelecteds == 0) return; // Begin from the bottom (important move all selected notes one note down // AND to quit early if a selected note is the last shown one for (Note *it = lastShownNote(); it != 0L; it = it->previous()) { if (it->isSelected() && it->isShown()) { // it->isShown() not necessary, but in case... if (it == lastShownNote()) return; // No way... m_insertAtNote = nextShownNoteFrom(it, 1); // Next shown note if (m_insertAtNote == 0L) { // Should not appends, since it's not the last shown note, resetInsertTo(); // there SHOULD be one before return; } m_insertAfter = true; changeNotePlace(it); } if (it == firstShownNote()) break; } ensureNoteVisible(m_focusedNote); } #endif // #if 0 #include "basket.moc"