You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
basket/src/notedrag.cpp

598 lines
22 KiB

/***************************************************************************
* 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 <tqdragobject.h>
#include <tqdir.h>
#include <tqpainter.h>
#include <tqtextcodec.h>
#include <tqbuffer.h>
#include <kurldrag.h>
#include <tdeversion.h>
#include <tdeapplication.h>
#include <tqdesktopwidget.h>
#include "basket.h"
#include "notedrag.h"
#include "notefactory.h"
#include "tools.h"
#include "global.h"
#include <iostream>
/** 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<Note*> groups = noteList->parentGroups();
for (TQValueList<Note*>::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() ? "<br>\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 = ("<!--StartFragment--><p>" + htmlEquivalent).local8Bit();
TQStoredDrag *richTextDrag = new TQStoredDrag("application/x-qrichtext");
richTextDrag->setEncodedData(byteArray);
multipleDrag->addDragObject(richTextDrag);
}
}
void NoteDrag::serializeImage(NoteSelection *noteList, KMultipleDrag *multipleDrag)
{
TQValueList<TQPixmap> 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<TQPixmap>::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<TQPixmap>::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<TQPixmap> pixmaps;
TQValueList<TQColor> backgrounds;
TQValueList<bool> 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<TQPixmap>::iterator it;
TQValueList<TQColor>::iterator it2;
TQValueList<bool>::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<Note*> 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<Note*> notes;
do {
stream >> notePointer;
if (notePointer != 0)
notes.append((Note*)notePointer);
} while (notePointer);
// Done:
return notes;
} else
return TQValueList<Note*>();
}
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<Note*> 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"