/*************************************************************************** * 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. * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include #include #include #include #include #include "basket.h" #include "notedrag.h" #include "notefactory.h" #include "tools.h" #include "global.h" #include /** NoteDrag */ const char * NoteDrag::NOTE_MIME_STRING = "application/x-basket-note"; void NoteDrag::createAndEmptyCuttingTmpFolder() { Tools::deleteRecursively(Global::tempCutFolder()); TQDir dir; dir.mkdir(Global::tempCutFolder()); } TQDragObject* NoteDrag::dragObject(NoteSelection *noteList, bool cutting, TQWidget *source) { if (noteList->count() <= 0) return 0; // The MimeSource: KMultipleDrag *multipleDrag = new KMultipleDrag(source); // Make sure the temporary folder exists and is empty (we delete previously moved file(s) (if exists) // since we override the content of the clipboard and previous file willn't be accessable anymore): createAndEmptyCuttingTmpFolder(); // The "Native Format" Serialization: TQBuffer buffer; if (buffer.open(IO_WriteOnly)) { TQDataStream stream(&buffer); // First append a pointer to the basket: stream << (TQ_UINT64)(noteList->firstStacked()->note->basket()); // Then a list of pointers to all notes, and parent groups: for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) stream << (TQ_UINT64)(node->note); TQValueList groups = noteList->parentGroups(); for (TQValueList::iterator it = groups.begin(); it != groups.end(); ++it) stream << (TQ_UINT64)(*it); stream << (TQ_UINT64)0; // And finally the notes themselves: serializeNotes(noteList, stream, cutting); // Append the object: buffer.close(); TQStoredDrag *dragObject = new TQStoredDrag(NOTE_MIME_STRING, source); dragObject->setEncodedData(buffer.buffer()); multipleDrag->addDragObject(dragObject); } // The "Other Flavours" Serialization: serializeText( noteList, multipleDrag ); serializeHtml( noteList, multipleDrag ); serializeImage( noteList, multipleDrag ); serializeLinks( noteList, multipleDrag, cutting ); // The Alternate Flavours: if (noteList->count() == 1) noteList->firstStacked()->note->content()->addAlternateDragObjects(multipleDrag); // If it is a drag, and not a copy/cut, add the feedback pixmap: if (source) setFeedbackPixmap(noteList, multipleDrag); return multipleDrag; } void NoteDrag::serializeNotes(NoteSelection *noteList, TQDataStream &stream, bool cutting) { for (NoteSelection *node = noteList; node; node = node->next) { stream << (TQ_UINT64)(node->note); if (node->firstChild) { stream << (TQ_UINT64)(NoteType::Group) << (TQ_UINT64)(node->note->groupWidth()) << (TQ_UINT64)(node->note->isFolded()); serializeNotes(node->firstChild, stream, cutting); } else { NoteContent *content = node->note->content(); stream << (TQ_UINT64)(content->type()) << (TQ_UINT64)(node->note->groupWidth()); // Serialize file name, and move the file to a temporary place if the note is to be cuttted. // If note does not have file name, we append empty string to be able to easily decode the notes later: stream << content->fileName(); if (content->shouldSerializeFile()) { if (cutting) { // Move file in a temporary place: TQString fullPath = Global::tempCutFolder() + Tools::fileNameForNewFile(content->fileName(), Global::tempCutFolder()); TDEIO::move(KURL(content->fullPath()), KURL(fullPath), /*showProgressInfo=*/false); node->fullPath = fullPath; stream << fullPath; } else stream << content->fullPath(); } else stream << TQString(""); stream << content->note()->addedDate() << content->note()->lastModificationDate(); content->serialize(stream); State::List states = node->note->states(); for (State::List::Iterator it = states.begin(); it != states.end(); ++it) stream << (TQ_UINT64)(*it); stream << (TQ_UINT64)0; } } stream << (TQ_UINT64)0; // Mark the end of the notes in this group/hierarchy. } void NoteDrag::serializeText(NoteSelection *noteList, KMultipleDrag *multipleDrag) { TQString textEquivalent; TQString text; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) { text = node->note->toText(node->fullPath); // note->toText() and not note->content()->toText() because the first one will also export the tags as text. if (!text.isEmpty()) textEquivalent += (!textEquivalent.isEmpty() ? "\n" : "") + text; } if (!textEquivalent.isEmpty()) multipleDrag->addDragObject( new TQTextDrag(textEquivalent) ); } void NoteDrag::serializeHtml(NoteSelection *noteList, KMultipleDrag *multipleDrag) { TQString htmlEquivalent; TQString html; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) { html = node->note->content()->toHtml("", node->fullPath); if (!html.isEmpty()) htmlEquivalent += (!htmlEquivalent.isEmpty() ? "
\n" : "") + html; } if (!htmlEquivalent.isEmpty()) { // Add HTML flavour: TQTextDrag *htmlDrag = new TQTextDrag(htmlEquivalent); htmlDrag->setSubtype("html"); multipleDrag->addDragObject(htmlDrag); // But also TQTextEdit flavour, to be able to paste several notes to a text edit: TQByteArray byteArray = ("

" + htmlEquivalent).local8Bit(); TQStoredDrag *richTextDrag = new TQStoredDrag("application/x-qrichtext"); richTextDrag->setEncodedData(byteArray); multipleDrag->addDragObject(richTextDrag); } } void NoteDrag::serializeImage(NoteSelection *noteList, KMultipleDrag *multipleDrag) { TQValueList pixmaps; TQPixmap pixmap; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) { pixmap = node->note->content()->toPixmap(); if (!pixmap.isNull()) pixmaps.append(pixmap); } if (!pixmaps.isEmpty()) { TQPixmap pixmapEquivalent; if (pixmaps.count() == 1) pixmapEquivalent = pixmaps[0]; else { // Search the total size: int height = 0; int width = 0; for (TQValueList::iterator it = pixmaps.begin(); it != pixmaps.end(); ++it) { height += (*it).height(); if ((*it).width() > width) width = (*it).width(); } // Create the image by painting all image into one big image: pixmapEquivalent.resize(width, height); pixmapEquivalent.fill(TQt::white); TQPainter painter(&pixmapEquivalent); height = 0; for (TQValueList::iterator it = pixmaps.begin(); it != pixmaps.end(); ++it) { painter.drawPixmap(0, height, *it); height += (*it).height(); } } TQImageDrag *imageDrag = new TQImageDrag(pixmapEquivalent.convertToImage()); multipleDrag->addDragObject(imageDrag); } } void NoteDrag::serializeLinks(NoteSelection *noteList, KMultipleDrag *multipleDrag, bool cutting) { KURL::List urls; TQStringList titles; KURL url; TQString title; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked()) { node->note->content()->toLink(&url, &title, node->fullPath); if (!url.isEmpty()) { urls.append(url); titles.append(title); } } if (!urls.isEmpty()) { // First, the standard text/uri-list MIME format: #if KDE_IS_VERSION( 3, 3, 90 ) KURLDrag *urlsDrag = new KURLDrag(urls); // ONLY export as text/uri-list, and not as text/plain* as we wil do that better ourself urlsDrag->setExportAsText(false); multipleDrag->addDragObject(urlsDrag); #else KURLDrag2 *urlsDrag = new KURLDrag2(urls); TQByteArray byteArray = urlsDrag->encodedData2("text/uri-list"); TQStoredDrag *uriListDrag = new TQStoredDrag("text/uri-list"); uriListDrag->setEncodedData(byteArray); multipleDrag->addDragObject(uriListDrag); delete urlsDrag; #endif // Then, also provide it in the Mozilla proprietary format (that also allow to add titles to URLs): // A version for Mozilla applications (convert to "theUrl\ntheTitle", into UTF-16): // FIXME: Does Mozilla support the drag of several URLs at once? // FIXME: If no, only provide that if theire is only ONE URL. TQString xMozUrl; for (uint i = 0; i < urls.count(); ++i) xMozUrl += (xMozUrl.isEmpty() ? "" : "\n") + urls[i].prettyURL() + "\n" + titles[i]; /* Code for only one: =============== xMozUrl = note->title() + "\n" + note->url().prettyURL();*/ TQByteArray baMozUrl; TQTextStream stream(baMozUrl, IO_WriteOnly); stream.setEncoding(TQTextStream::RawUnicode); // It's UTF16 (aka UCS2), but with the first two order bytes stream << xMozUrl; TQStoredDrag *xMozUrlDrag = new TQStoredDrag("text/x-moz-url"); xMozUrlDrag->setEncodedData(baMozUrl); multipleDrag->addDragObject(xMozUrlDrag); if (cutting) { TQByteArray arrayCut(2); TQStoredDrag *storedDragCut = new TQStoredDrag("application/x-tde-cutselection"); arrayCut[0] = '1'; arrayCut[1] = 0; storedDragCut->setEncodedData(arrayCut); multipleDrag->addDragObject(storedDragCut); } } } void NoteDrag::setFeedbackPixmap(NoteSelection *noteList, KMultipleDrag *multipleDrag) { TQPixmap pixmap = feedbackPixmap(noteList); if (!pixmap.isNull()) multipleDrag->setPixmap(pixmap, TQPoint(-8, -8)); } TQPixmap NoteDrag::feedbackPixmap(NoteSelection *noteList) { if (noteList == 0) return TQPixmap(); static const int MARGIN = 2; static const int SPACING = 1; TQColor textColor = noteList->firstStacked()->note->basket()->textColor(); TQColor backgroundColor = noteList->firstStacked()->note->basket()->backgroundColor().dark(NoteContent::FEEDBACK_DARKING); TQValueList pixmaps; TQValueList backgrounds; TQValueList spaces; TQPixmap pixmap; int height = 0; int width = 0; int i = 0; bool elipsisImage = false; TQColor bgColor; bool needSpace; for (NoteSelection *node = noteList->firstStacked(); node; node = node->nextStacked(), ++i) { if (elipsisImage) { pixmap = TQPixmap(7, 2); pixmap.fill(backgroundColor); TQPainter painter(&pixmap); painter.setPen(textColor); painter.drawPoint(1, 1); painter.drawPoint(3, 1); painter.drawPoint(5, 1); painter.end(); bgColor = node->note->basket()->backgroundColor(); needSpace = false; } else { pixmap = node->note->content()->feedbackPixmap(/*maxWidth=*/kapp->desktop()->width() / 2, /*maxHeight=*/96); bgColor = node->note->backgroundColor(); needSpace = node->note->content()->needSpaceForFeedbackPixmap(); } if (!pixmap.isNull()) { if (pixmap.width() > width) width = pixmap.width(); pixmaps.append(pixmap); backgrounds.append(bgColor); spaces.append(needSpace); height += (i > 0 && needSpace ? 1 : 0) + pixmap.height() + SPACING + (needSpace ? 1 : 0); if (elipsisImage) break; if (height > kapp->desktop()->height() / 2) elipsisImage = true; } } if (!pixmaps.isEmpty()) { TQPixmap result(MARGIN + width + MARGIN, MARGIN + height - SPACING + MARGIN - (spaces.last() ? 1 : 0)); TQPainter painter(&result); // Draw all the images: height = MARGIN; TQValueList::iterator it; TQValueList::iterator it2; TQValueList::iterator it3; int i = 0; for (it = pixmaps.begin(), it2 = backgrounds.begin(), it3 = spaces.begin(); it != pixmaps.end(); ++it, ++it2, ++it3, ++i) { if (i != 0 && (*it3)) { painter.fillRect(MARGIN, height, result.width() - 2 * MARGIN, SPACING, (*it2).dark(NoteContent::FEEDBACK_DARKING)); ++height; } painter.drawPixmap(MARGIN, height, *it); if ((*it).width() < width) painter.fillRect(MARGIN + (*it).width(), height, width - (*it).width(), (*it).height(), (*it2).dark(NoteContent::FEEDBACK_DARKING)); if (*it3) { painter.fillRect(MARGIN, height + (*it).height(), result.width() - 2 * MARGIN, SPACING, (*it2).dark(NoteContent::FEEDBACK_DARKING)); ++height; } painter.fillRect(MARGIN, height + (*it).height(), result.width() - 2 * MARGIN, SPACING, Tools::mixColor(textColor, backgroundColor)); height += (*it).height() + SPACING; } // Draw the border: painter.setPen(textColor); painter.drawLine(0, 0, result.width() - 1, 0); painter.drawLine(0, 0, 0, result.height() - 1); painter.drawLine(0, result.height() - 1, result.width() - 1, result.height() - 1); painter.drawLine(result.width() - 1, 0, result.width() - 1, result.height() - 1); // Draw the "lightly rounded" border: painter.setPen(Tools::mixColor(textColor, backgroundColor)); painter.drawPoint(0, 0); painter.drawPoint(0, result.height() - 1); painter.drawPoint(result.width() - 1, result.height() - 1); painter.drawPoint(result.width() - 1, 0); // Draw the background in the margin (the inside will be painted above, anyway): painter.setPen(backgroundColor); painter.drawLine(1, 1, result.width() - 2, 1); painter.drawLine(1, 1, 1, result.height() - 2); painter.drawLine(1, result.height() - 2, result.width() - 2, result.height() - 2); painter.drawLine(result.width() - 2, 1, result.width() - 2, result.height() - 2); // And assign the feedback pixmap to the drag object: //multipleDrag->setPixmap(result, TQPoint(-8, -8)); return result; } return TQPixmap(); } bool NoteDrag::canDecode(TQMimeSource *source) { return source->provides(NOTE_MIME_STRING); } Basket* NoteDrag::basketOf(TQMimeSource *source) { TQBuffer buffer(source->encodedData(NOTE_MIME_STRING)); if (buffer.open(IO_ReadOnly)) { TQDataStream stream(&buffer); // Get the parent basket: TQ_UINT64 basketPointer; stream >> (TQ_UINT64&)basketPointer; return (Basket*)basketPointer; } else return 0; } TQValueList NoteDrag::notesOf(TQMimeSource *source) { TQBuffer buffer(source->encodedData(NOTE_MIME_STRING)); if (buffer.open(IO_ReadOnly)) { TQDataStream stream(&buffer); // Get the parent basket: TQ_UINT64 basketPointer; stream >> (TQ_UINT64&)basketPointer; // Get the note list: TQ_UINT64 notePointer; TQValueList notes; do { stream >> notePointer; if (notePointer != 0) notes.append((Note*)notePointer); } while (notePointer); // Done: return notes; } else return TQValueList(); } Note* NoteDrag::decode(TQMimeSource *source, Basket *parent, bool moveFiles, bool moveNotes) { TQBuffer buffer(source->encodedData(NOTE_MIME_STRING)); if (buffer.open(IO_ReadOnly)) { TQDataStream stream(&buffer); // Get the parent basket: TQ_UINT64 basketPointer; stream >> (TQ_UINT64&)basketPointer; Basket *basket = (Basket*)basketPointer; // Get the note list: TQ_UINT64 notePointer; TQValueList notes; do { stream >> notePointer; if (notePointer != 0) notes.append((Note*)notePointer); } while (notePointer); // Decode the note hierarchy: Note *hierarchy = decodeHierarchy(stream, parent, moveFiles, moveNotes, basket); // In case we moved notes from one basket to another, save the source basket where notes were removed: basket->filterAgainDelayed(); // Delayed, because if a note is moved to the same basket, the note is not at its basket->save(); // new position yet, and the call to ensureNoteVisible would make the interface flicker!! return hierarchy; } else return 0; } Note* NoteDrag::decodeHierarchy(TQDataStream &stream, Basket *parent, bool moveFiles, bool moveNotes, Basket *originalBasket) { TQ_UINT64 notePointer; TQ_UINT64 type; TQString fileName; TQString fullPath; TQDateTime addedDate; TQDateTime lastModificationDate; Note *firstNote = 0; // TODO: class NoteTreeChunk Note *lastInserted = 0; do { stream >> notePointer; if (notePointer == 0) return firstNote; Note *oldNote = (Note*)notePointer; Note *note = 0; TQ_UINT64 groupWidth; stream >> type >> groupWidth; if (type == NoteType::Group) { note = new Note(parent); note->setGroupWidth(groupWidth); TQ_UINT64 isFolded; stream >> isFolded; if (isFolded) note->toggleFolded(/*animate=*/false); if (moveNotes) { note->setX(oldNote->x()); // We don't move groups but re-create them (every childs can to not be selected) note->setY(oldNote->y()); // We just set the position of the copied group so the animation seems as if the group is the same as (or a copy of) the old. note->setHeight(oldNote->height()); // Idem: the only use of Note::setHeight() } Note* childs = decodeHierarchy(stream, parent, moveFiles, moveNotes, originalBasket); if (childs) { for (Note *n = childs; n; n = n->next()) n->setParentNote(note); note->setFirstChild(childs); } } else { stream >> fileName >> fullPath >> addedDate >> lastModificationDate; if (moveNotes) { originalBasket->unplugNote(oldNote); note = oldNote; if (note->basket() != parent) { TQString newFileName = NoteFactory::createFileForNewNote(parent, "", fileName); note->content()->setFileName(newFileName); TDEIO::FileCopyJob *copyJob = TDEIO::file_move(KURL(fullPath), KURL(parent->fullPath() + newFileName), /*perms=*/-1, /*override=*/true, /*resume=*/false, /*showProgressInfo=*/false); parent->connect( copyJob, TQ_SIGNAL(result(TDEIO::Job *)), parent, TQ_SLOT(slotCopyingDone2(TDEIO::Job *)) ); } note->setGroupWidth(groupWidth); note->setParentNote(0); note->setPrev(0); note->setNext(0); note->setParentBasket(parent); NoteFactory::consumeContent(stream, (NoteType::Id)type); } else if ( (note = NoteFactory::decodeContent(stream, (NoteType::Id)type, parent)) ) { note->setGroupWidth(groupWidth); note->setAddedDate(addedDate); note->setLastModificationDate(lastModificationDate); } else if (!fileName.isEmpty()) { // Here we are CREATING a new EMPTY file, so the name is RESERVED // (while dropping several files at once a filename cannot be used by two of them). // Later on, file_copy/file_move will copy/move the file to the new location. TQString newFileName = NoteFactory::createFileForNewNote(parent, "", fileName); note = NoteFactory::loadFile(newFileName, (NoteType::Id)type, parent); TDEIO::FileCopyJob *copyJob; if (moveFiles) copyJob = TDEIO::file_move(KURL(fullPath), KURL(parent->fullPath() + newFileName), /*perms=*/-1, /*override=*/true, /*resume=*/false, /*showProgressInfo=*/false); else copyJob = TDEIO::file_copy(KURL(fullPath), KURL(parent->fullPath() + newFileName), /*perms=*/-1, /*override=*/true, /*resume=*/false, /*showProgressInfo=*/false); parent->connect( copyJob, TQ_SIGNAL(result(TDEIO::Job *)), parent, TQ_SLOT(slotCopyingDone2(TDEIO::Job *)) ); note->setGroupWidth(groupWidth); note->setAddedDate(addedDate); note->setLastModificationDate(lastModificationDate); } } // Retreive the states (tags) and assign them to the note: if (note && note->content()) { TQ_UINT64 statePointer; do { stream >> statePointer; if (statePointer) note->addState((State*)statePointer); } while (statePointer); } // Now that we have created the note, insert it: if (note) { if (!firstNote) firstNote = note; else { lastInserted->setNext(note); note->setPrev(lastInserted); } lastInserted = note; } } while (true); // We've done: return! return firstNote; } /** ExtendedTextDrag */ bool ExtendedTextDrag::decode(const TQMimeSource *e, TQString &str) { TQCString subtype("plain"); return decode(e, str, subtype); } bool ExtendedTextDrag::decode(const TQMimeSource *e, TQString &str, TQCString &subtype) { // Get the string: bool ok = TQTextDrag::decode(e, str, subtype); // Test if it was a UTF-16 string (from eg. Mozilla): if (str.length() >= 2) { if ((str[0] == 0xFF && str[1] == 0xFE) || (str[0] == 0xFE && str[1] == 0xFF)) { TQByteArray utf16 = e->encodedData(TQString("text/" + subtype).local8Bit()); str = TQTextCodec::codecForName("utf16")->toUnicode(utf16); return true; } } // Test if it was empty (sometimes, from GNOME or Mozilla) if (str.length() == 0 && subtype == "plain") { if (e->provides("UTF8_STRING")) { TQByteArray utf8 = e->encodedData("UTF8_STRING"); str = TQTextCodec::codecForName("utf8")->toUnicode(utf8); return true; } if (e->provides("text/unicode")) { // FIXME: It's UTF-16 without order bytes!!! TQByteArray utf16 = e->encodedData("text/unicode"); str = TQTextCodec::codecForName("utf16")->toUnicode(utf16); return true; } if (e->provides("TEXT")) { // local encoding TQByteArray text = e->encodedData("TEXT"); str = TQString(text); return true; } if (e->provides("COMPOUND_TEXT")) { // local encoding TQByteArray text = e->encodedData("COMPOUND_TEXT"); str = TQString(text); return true; } } return ok; } #include "notedrag.moc"