1 /***************************************************************************
2  *   Copyright (C) 2003 by Sébastien Laoût                                 *
3  *   slaout@linux62.org                                                    *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
19  ***************************************************************************/
20 
21 #include "notefactory.h"
22 
23 #include <QtCore/QString>
24 #include <QtCore/QTextStream>
25 #include <QtCore/QVector>
26 #include <QtCore/QRegExp>
27 #include <QtCore/QDir>
28 #include <QtCore/QFile>
29 #include <QtCore/QFileInfo>
30 #include <QtCore/QMimeData>
31 #include <QtGui/QImage>
32 #include <QtGui/QPixmap>
33 #include <QtGui/QColor>
34 #include <QGraphicsView>
35 #include <QtGui/QImageReader>
36 #include <QtGui/QMovie>
37 #include <QtGui/QTextDocument> //For Qt::mightBeRichText(...)
38 #include <QtGui/QBitmap> //For createHeuristicMask
39 #include <QtCore/qnamespace.h>
40 #include <QGuiApplication>
41 #include <QFileDialog>
42 #include <QUrl>
43 #include <QMimeType>
44 #include <QMimeDatabase>
45 #include <QLocale>
46 #include <QMenu>
47 
48 #include <KMessageBox>
49 #include <KOpenWithDialog>
50 #include <KIconLoader>
51 #include <KUriFilter>
52 #include <KIconDialog>
53 #include <KModifierKeyInfo>
54 #include <KAboutData> //For KGlobal::mainComponent().aboutData(...)
55 #include <KLocalizedString>
56 
57 #include <KIO/CopyJob>
58 
59 #include "basketscene.h"
60 #include "basketlistview.h"
61 #include "note.h"
62 #include "notedrag.h"
63 #include "global.h"
64 #include "settings.h"
65 #include "variouswidgets.h" //For IconSizeDialog
66 #include "tools.h"
67 #include "file_mimetypes.h"
68 
69 #include "debugwindow.h"
70 
71 
72 /** Create notes from scratch (just a content) */
73 
createNoteText(const QString & text,BasketScene * parent,bool reallyPlainText)74 Note* NoteFactory::createNoteText(const QString &text, BasketScene *parent, bool reallyPlainText/* = false*/)
75 {
76     Note *note = new Note(parent);
77     if (reallyPlainText) {
78         TextContent *content = new TextContent(note, createFileForNewNote(parent, "txt"));
79         content->setText(text);
80         content->saveToFile();
81     } else {
82         HtmlContent *content = new HtmlContent(note, createFileForNewNote(parent, "html"));
83         QString html = "<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"><meta name=\"qrichtext\" content=\"1\" /></head><body>" + Tools::textToHTMLWithoutP(text) + "</body></html>";
84         content->setHtml(html);
85         content->saveToFile();
86     }
87     return note;
88 }
89 
createNoteHtml(const QString & html,BasketScene * parent)90 Note* NoteFactory::createNoteHtml(const QString &html, BasketScene *parent)
91 {
92     Note *note = new Note(parent);
93     HtmlContent *content = new HtmlContent(note, createFileForNewNote(parent, "html"));
94     content->setHtml(html);
95     content->saveToFile();
96     return note;
97 }
98 
createNoteLink(const QUrl & url,BasketScene * parent)99 Note* NoteFactory::createNoteLink(const QUrl &url, BasketScene *parent)
100 {
101     Note *note = new Note(parent);
102     new LinkContent(note, url, titleForURL(url), iconForURL(url), /*autoTitle=*/true, /*autoIcon=*/true);
103     return note;
104 }
105 
createNoteLink(const QUrl & url,const QString & title,BasketScene * parent)106 Note* NoteFactory::createNoteLink(const QUrl &url, const QString &title, BasketScene *parent)
107 {
108     Note *note = new Note(parent);
109     new LinkContent(note, url, title, iconForURL(url), /*autoTitle=*/false, /*autoIcon=*/true);
110     return note;
111 }
112 
createNoteCrossReference(const QUrl & url,BasketScene * parent)113 Note* NoteFactory::createNoteCrossReference(const QUrl &url, BasketScene *parent)
114 {
115     Note *note = new Note(parent);
116     new CrossReferenceContent(note, url, titleForURL(url), iconForURL(url));
117     return note;
118 }
119 
createNoteCrossReference(const QUrl & url,const QString & title,BasketScene * parent)120 Note* NoteFactory::createNoteCrossReference(const QUrl &url, const QString &title, BasketScene *parent)
121 {
122     Note *note = new Note(parent);
123     new CrossReferenceContent(note, url, title, iconForURL(url));
124     return note;
125 }
126 
createNoteCrossReference(const QUrl & url,const QString & title,const QString & icon,BasketScene * parent)127 Note* NoteFactory::createNoteCrossReference(const QUrl &url, const QString &title, const QString &icon, BasketScene *parent)
128 {
129     Note *note = new Note(parent);
130     new CrossReferenceContent(note, url, title, icon);
131     return note;
132 }
133 
createNoteImage(const QPixmap & image,BasketScene * parent)134 Note* NoteFactory::createNoteImage(const QPixmap &image, BasketScene *parent)
135 {
136     Note *note = new Note(parent);
137     ImageContent *content = new ImageContent(note, createFileForNewNote(parent, "png"));
138     content->setPixmap(image);
139     content->saveToFile();
140     return note;
141 }
142 
createNoteColor(const QColor & color,BasketScene * parent)143 Note* NoteFactory::createNoteColor(const QColor &color, BasketScene *parent)
144 {
145     Note *note = new Note(parent);
146     new ColorContent(note, color);
147     return note;
148 }
149 
150 /** Return a string list containing {url1, title1, url2, title2, url3, title3...}
151  */
textToURLList(const QString & text)152 QStringList NoteFactory::textToURLList(const QString &text)
153 {
154     // List to return:
155     QStringList list;
156 
157     // Split lines:
158     QStringList texts = text.split('\n');
159 
160     // For each lines:
161     QStringList::iterator it;
162     for (it = texts.begin(); it != texts.end(); ++it) {
163         // Strip white spaces:
164         (*it) = (*it).trimmed();
165 
166         // Don't care of empty entries:
167         if ((*it).isEmpty())
168             continue;
169 
170         // Compute lower case equivalent:
171         QString ltext = (*it).toLower();
172 
173         /* Search for mail address ("*@*.*" ; "*" can contain '_', '-', or '.') and add protocol to it */
174         QString mailExpString = "[\\w-\\.]+@[\\w-\\.]+\\.[\\w]+";
175         QRegExp mailExp("^" + mailExpString + "$");
176         if (mailExp.exactMatch(ltext)) {
177             ltext.insert(0, "mailto:");
178             (*it).insert(0, "mailto:");
179         }
180 
181         // TODO: Recognize "<link>" (link between '<' and '>')
182         // TODO: Replace " at " by "@" and " dot " by "." to look for e-mail addresses
183 
184         /* Search for mail address like "Name <address@provider.net>" */
185         QRegExp namedMailExp("^([\\w\\s]+)\\s<(" + mailExpString + ")>$");
186         //namedMailExp.setCaseSensitive(true); // For the name to be keeped with uppercases // DOESN'T WORK !
187         if (namedMailExp.exactMatch(ltext)) {
188             QString name    = namedMailExp.cap(1);
189             QString address = "mailto:" + namedMailExp.cap(2);
190             // Threat it NOW, as it's an exception (it have a title):
191             list.append(address);
192             list.append(name);
193             continue;
194         }
195 
196         /* Search for an url and create an URL note */
197         if ((ltext.startsWith('/') && ltext[1] != '/' && ltext[1] != '*') ||  // Take files but not C/C++/... comments !
198                 ltext.startsWith(QLatin1String("file:"))    ||
199                 ltext.startsWith(QLatin1String("http://"))  ||
200                 ltext.startsWith(QLatin1String("https://")) ||
201                 ltext.startsWith(QLatin1String("www."))     ||
202                 ltext.startsWith(QLatin1String("ftp."))     ||
203                 ltext.startsWith(QLatin1String("ftp://"))   ||
204                 ltext.startsWith(QLatin1String("mailto:"))) {
205 
206             // First, correct the text to use the good format for the url
207             if (ltext.startsWith('/'))
208                 (*it).insert(0, "file:");
209             if (ltext.startsWith(QLatin1String("www.")))
210                 (*it).insert(0, "http://");
211             if (ltext.startsWith(QLatin1String("ftp.")))
212                 (*it).insert(0, "ftp://");
213 
214             // And create the Url note (or launcher if URL point a .desktop file)
215             list.append(*it);
216             list.append(""); // We don't have any title
217         } else
218             return QStringList(); // FAILED: treat the text as a text, and not as a URL list!
219     }
220     return list;
221 }
222 
createNoteFromText(const QString & text,BasketScene * parent)223 Note* NoteFactory::createNoteFromText(const QString &text, BasketScene *parent)
224 {
225     /* Search for a color (#RGB , #RRGGBB , #RRRGGGBBB , #RRRRGGGGBBBB) and create a color note */
226     QRegExp exp("^#(?:[a-fA-F\\d]{3}){1,4}$");
227     if (exp.exactMatch(text))
228         return createNoteColor(QColor(text), parent);
229 
230     /* Try to convert the text as a URL or a list of URLs */
231     QStringList uriList = textToURLList(text);
232     if (! uriList.isEmpty()) {
233         // TODO: This code is almost duplicated from fropURLs()!
234         Note *note;
235         Note *firstNote = 0;
236         Note *lastInserted = 0;
237         QStringList::iterator it;
238         for (it = uriList.begin(); it != uriList.end(); ++it) {
239             QString url = (*it);
240             ++it;
241             QString title = (*it);
242             if (title.isEmpty())
243                 note = createNoteLinkOrLauncher(QUrl::fromUserInput(url), parent);
244             else
245                 note = createNoteLink(QUrl::fromUserInput(url), title, parent);
246 
247             // If we got a new note, insert it in a linked list (we will return the first note of that list):
248             if (note) {
249 //              qDebug() << "Drop URL: " << (*it).toDisplayString();
250                 if (!firstNote)
251                     firstNote = note;
252                 else {
253                     lastInserted->setNext(note);
254                     note->setPrev(lastInserted);
255                 }
256                 lastInserted = note;
257             }
258 
259         }
260         return firstNote; // It don't return ALL inserted notes !
261     }
262 
263     //QString newText = text.trimmed(); // The text for a new note, without useless spaces
264     /* Else, it's a text or an HTML note, so, create it */
265     if (Qt::mightBeRichText(/*newT*/text))
266         return createNoteHtml(/*newT*/text, parent);
267     else
268         return createNoteText(/*newT*/text, parent);
269 }
270 
createNoteLauncher(const QUrl & url,BasketScene * parent)271 Note* NoteFactory::createNoteLauncher(const QUrl &url, BasketScene *parent)
272 {
273     if (url.isEmpty())
274         return createNoteLauncher("", "", "", parent);
275     else
276         return copyFileAndLoad(url, parent);
277 }
278 
createNoteLauncher(const QString & command,const QString & name,const QString & icon,BasketScene * parent)279 Note* NoteFactory::createNoteLauncher(const QString &command, const QString &name, const QString &icon, BasketScene *parent)
280 {
281     QString fileName = createNoteLauncherFile(command, name, icon, parent);
282     if (fileName.isEmpty())
283         return 0L;
284     else
285         return loadFile(fileName, parent);
286 }
287 
createNoteLauncherFile(const QString & command,const QString & name,const QString & icon,BasketScene * parent)288 QString NoteFactory::createNoteLauncherFile(const QString &command, const QString &name, const QString &icon, BasketScene *parent)
289 {
290     QString content = QString(
291                           "[Desktop Entry]\n"
292                           "Exec=%1\n"
293                           "Name=%2\n"
294                           "Icon=%3\n"
295                           "Encoding=UTF-8\n"
296                           "Type=Application\n").arg(command, name, icon.isEmpty() ? QString("exec") : icon);
297     QString fileName = fileNameForNewNote(parent, "launcher.desktop");
298     QString fullPath = parent->fullPathForFileName(fileName);
299 //  parent->dontCareOfCreation(fullPath);
300     QFile file(fullPath);
301     if (file.open(QIODevice::WriteOnly)) {
302         QTextStream stream(&file);
303         stream.setCodec("UTF-8");
304         stream << content;
305         file.close();
306         return fileName;
307     } else
308         return QString();
309 }
310 
createNoteLinkOrLauncher(const QUrl & url,BasketScene * parent)311 Note* NoteFactory::createNoteLinkOrLauncher(const QUrl &url, BasketScene *parent)
312 {
313     // IMPORTANT: we create the service ONLY if the extension is ".desktop".
314     //            Otherwise, KService take a long time to analyze all the file
315     //            and output such things to stdout:
316     //            "Invalid entry (missing '=') at /my/file.ogg:11984"
317     //            "Invalid entry (missing ']') at /my/file.ogg:11984"...
318     KService::Ptr service;
319     if (url.fileName().endsWith(QLatin1String(".desktop")))
320         service = new KService(url.path());
321 
322     // If link point to a .desktop file then add a launcher, otherwise it's a link
323     if (service && service->isValid())
324         return createNoteLauncher(url, parent);
325     else
326         return createNoteLink(url, parent);
327 }
328 
329 
movingNotesInTheSameBasket(const QMimeData * source,BasketScene * parent,Qt::DropAction action)330 bool NoteFactory::movingNotesInTheSameBasket(const QMimeData *source, BasketScene *parent, Qt::DropAction action)
331 {
332     if (NoteDrag::canDecode(source))
333         return action == Qt::MoveAction && NoteDrag::basketOf(source) == parent;
334     else
335         return false;
336 }
337 
dropNote(const QMimeData * source,BasketScene * parent,bool fromDrop,Qt::DropAction action,Note *)338 Note* NoteFactory::dropNote(const QMimeData *source, BasketScene *parent, bool fromDrop, Qt::DropAction action, Note */*noteSource*/)
339 {
340     if (source == 0)
341     {
342         return 0;
343     }
344 
345     Note *note = 0L;
346 
347     QStringList formats = source->formats();
348     /* No data */
349     if (formats.size() == 0) {
350         // TODO: add a parameter to say if it's from a clipboard paste, a selection paste, or a drop
351         //       To be able to say "The clipboard/selection/drop is empty".
352 //      KMessageBox::error(parent, i18n("There is no data to insert."), i18n("No Data"));
353         return 0;
354     }
355 
356     /* Debug */
357     if (Global::debugWindow) {
358         *Global::debugWindow << "<b>Drop :</b>";
359         for (int i = 0; i < formats.size(); ++i)
360             *Global::debugWindow << "\t[" + QString::number(i) + "] " + formats[i];
361         switch (action) { // The source want that we:
362         case Qt::CopyAction:       *Global::debugWindow << ">> Drop action: Copy";       break;
363         case Qt::MoveAction:       *Global::debugWindow << ">> Drop action: Move";       break;
364         case Qt::LinkAction:       *Global::debugWindow << ">> Drop action: Link";       break;
365         default:                     *Global::debugWindow << ">> Drop action: Unknown";           //  supported by Qt!
366         }
367     }
368 
369     /* Copy or move a Note */
370     if (NoteDrag::canDecode(source)) {
371         bool moveFiles = fromDrop && action == Qt::MoveAction;
372         bool moveNotes = moveFiles;
373         return NoteDrag::decode(source, parent, moveFiles, moveNotes); // Filename will be kept
374     }
375 
376     /* Else : Drop object to note */
377 
378     QImage image = qvariant_cast<QImage>(source->imageData()) ;
379     if (!image.isNull())
380         return createNoteImage(QPixmap::fromImage(image), parent);
381 
382     if (source->hasColor()) {
383         return createNoteColor(qvariant_cast<QColor>(source->colorData()), parent);
384     }
385 
386     // And then the hack (if provide color MIME type or a text that contains color), using createNote Color RegExp:
387     QString hack;
388     QRegExp exp("^#(?:[a-fA-F\\d]{3}){1,4}$");
389     hack = source->text();
390     if (source->hasFormat("application/x-color") || (!hack.isNull() && exp.exactMatch(hack))) {
391         QColor color = qvariant_cast<QColor>(source->colorData()) ;
392         if (color.isValid())
393             return createNoteColor(color, parent);
394 //          if ( (note = createNoteColor(color, parent)) )
395 //              return note;
396 //          // Theorically it should be returned. If not, continue by dropping other things
397     }
398 
399     QList<QUrl> urls = source->urls();
400     if (!urls.isEmpty()) {
401         // If it's a Paste, we should know if files should be copied (copy&paste) or moved (cut&paste):
402         if (!fromDrop && Tools::isAFileCut(source))
403             action = Qt::MoveAction;
404         return dropURLs(urls, parent, action, fromDrop);
405     }
406 
407     // FIXME: use dropURLs() also from Mozilla?
408 
409     /*
410     * Mozilla's stuff sometimes uses utf-16-le - little-endian UTF-16.
411     *
412     * This has the property that for the ASCII subset case (And indeed, the
413     * ISO-8859-1 subset, I think), if you treat it as a C-style string,
414     * it'll come out to one character long in most cases, since it looks
415      * like:
416     *
417     * "<\0H\0T\0M\0L\0>\0"
418     *
419     * A strlen() call on that will give you 1, which simply isn't correct.
420     * That might, I suppose, be the answer, or something close.
421     *
422     * Also, Mozilla's drag/drop code predates the use of MIME types in XDnD
423     * - hence it'll throw about STRING and UTF8_STRING quite happily, hence
424     * the odd named types.
425     *
426     * Thanks to Dave Cridland for having said me that.
427     */
428     if (source->hasFormat("text/x-moz-url")) { // FOR MOZILLA
429         // Get the array and create a QChar array of 1/2 of the size
430         QByteArray mozilla = source->data("text/x-moz-url");
431         QVector<QChar> chars(mozilla.count() / 2);
432         // A small debug work to know the value of each bytes
433         if (Global::debugWindow)
434             for (int i = 0; i < mozilla.count(); i++)
435                 *Global::debugWindow << QString("'") + QChar(mozilla[i]) + "' " + QString::number(int(mozilla[i]));
436         // text/x-moz-url give the URL followed by the link title and separated by OxOA (10 decimal: new line?)
437         uint size   = 0;
438         QChar *name = 0L;
439         // For each little endian mozilla chars, copy it to the array of QChars
440         for (int i = 0; i < mozilla.count(); i += 2) {
441             chars[i/2] = QChar(mozilla[i], mozilla[i+1]);
442             if (mozilla.at(i) == 0x0A) {
443                 size = i / 2;
444                 name = &(chars[i/2+1]);
445             }
446         }
447         // Create a QString that take the address of the first QChar and a length
448         if (name == 0L) { // We haven't found name (FIXME: Is it possible ?)
449             QString normalHtml(&(chars[0]), chars.size());
450             return createNoteLink(normalHtml, parent);
451         } else {
452             QString normalHtml(&(chars[0]), size);
453             QString normalTitle(name,        chars.size() - size - 1);
454             return createNoteLink(normalHtml, normalTitle, parent);
455         }
456     }
457 
458     if (source->hasFormat("text/html")) {
459         QString html;
460         QString subtype("html");
461         // If the text/html comes from Mozilla or GNOME it can be UTF-16 encoded: we need ExtendedTextDrag to check that
462         ExtendedTextDrag::decode(source, html, subtype);
463         return createNoteHtml(html, parent);
464     }
465 
466     QString text;
467     // If the text/plain comes from GEdit or GNOME it can be empty: we need ExtendedTextDrag to check other MIME types
468     if (ExtendedTextDrag::decode(source, text))
469         return createNoteFromText(text, parent);
470 
471     /* Create a cross reference note */
472 
473     if(source->hasFormat(BasketTreeListView::TREE_ITEM_MIME_STRING)) {
474         QByteArray data = source->data(BasketTreeListView::TREE_ITEM_MIME_STRING);
475         QDataStream stream(&data, QIODevice::ReadOnly);
476         QString basketName, folderName, icon;
477 
478         while (!stream.atEnd())
479             stream >> basketName >> folderName >> icon;
480 
481         return createNoteCrossReference(QUrl("basket://" + folderName), basketName, icon, parent);
482     }
483 
484     /* Unsucceful drop */
485     note = createNoteUnknown(source, parent);
486     QString message = i18n("<p>%1 doesn't support the data you've dropped.<br>"
487                            "It however created a generic note, allowing you to drag or copy it to an application that understand it.</p>"
488                            "<p>If you want the support of these data, please contact developer.</p>", QGuiApplication::applicationDisplayName());
489     KMessageBox::information(parent->graphicsView()->viewport(), message, i18n("Unsupported MIME Type(s)"),
490                              "unsupportedDropInfo", KMessageBox::AllowLink);
491     return note;
492 }
493 
createNoteUnknown(const QMimeData * source,BasketScene * parent)494 Note* NoteFactory::createNoteUnknown(const QMimeData *source, BasketScene *parent/*, const QString &annotations*/)
495 {
496     // Save the MimeSource in a file: create and open the file:
497     QString fileName = createFileForNewNote(parent, "unknown");
498     QFile file(parent->fullPath() + fileName);
499     if (! file.open(QIODevice::WriteOnly))
500         return 0L;
501     QDataStream stream(&file);
502 
503     // Echo MIME types:
504     QStringList formats = source->formats();
505     for (int i = 0; formats.size(); ++i)
506         stream << QString(formats[i]); // Output the '\0'-terminated format name string
507 
508     // Echo end of MIME types list delimiter:
509     stream << "";
510 
511     // Echo the length (in bytes) and then the data, and then same for next MIME type:
512     for (int i = 0; formats.size(); ++i) {
513         QByteArray data = source->data(formats[i]);
514         stream << (quint32)data.count();
515         stream.writeRawData(data.data(), data.count());
516     }
517     file.close();
518 
519     Note *note = new Note(parent);
520     new UnknownContent(note, fileName);
521     return note;
522 }
523 
dropURLs(QList<QUrl> urls,BasketScene * parent,Qt::DropAction action,bool fromDrop)524 Note* NoteFactory::dropURLs( QList<QUrl> urls, BasketScene *parent, Qt::DropAction action, bool fromDrop)
525 {
526     KModifierKeyInfo keyinfo;
527     int  shouldAsk    = 0; // shouldAsk==0: don't ask ; shouldAsk==1: ask for "file" ; shouldAsk>=2: ask for "files"
528     bool shiftPressed = keyinfo.isKeyPressed(Qt::Key_Shift);
529     bool ctrlPressed  = keyinfo.isKeyPressed(Qt::Key_Control);
530     bool modified     = fromDrop && (shiftPressed || ctrlPressed);
531 
532     if (modified) // Then no menu + modified action
533         ; // action is already set: no work to do
534     else if (fromDrop) { // Compute if user should be asked or not
535         for ( QList<QUrl>::iterator it = urls.begin(); it != urls.end(); ++it)
536             if ((*it).scheme() != "mailto") { // Do not ask when dropping mail address :-)
537                 shouldAsk++;
538                 if (shouldAsk == 1/*2*/) // Sufficient
539                     break;
540             }
541         if (shouldAsk) {
542             QMenu menu(parent->graphicsView());
543             QList<QAction *> actList;
544             actList << new QAction(QIcon::fromTheme("go-jump"),
545                                    i18n("&Move Here\tShift"),
546                                    &menu)
547             << new QAction(QIcon::fromTheme("edit-copy"),
548                            i18n("&Copy Here\tCtrl"),
549                            &menu)
550             << new QAction(QIcon::fromTheme("insert-link"),
551                            i18n("&Link Here\tCtrl+Shift"),
552                            &menu);
553 
554             foreach(QAction *a, actList)
555             menu.addAction(a);
556 
557             menu.addSeparator();
558             menu.addAction(QIcon::fromTheme("dialog-cancel"), i18n("C&ancel\tEscape"));
559             int id = actList.indexOf(menu.exec(QCursor::pos()));
560             switch (id) {
561             case 0: action = Qt::MoveAction; break;
562             case 1: action = Qt::CopyAction; break;
563             case 2: action = Qt::LinkAction; break;
564             default: return 0;
565             }
566             modified = true;
567         }
568     } else { // fromPaste
569         ;
570     }
571 
572     /* Policy of drops of URL:
573     *   Email: [Modifier keys: Useless]
574     +    - Link mail address
575     *   Remote URL: [Modifier keys: {Copy,Link}]
576     +    - Download as Image, Animation and Launcher
577     +    - Link other URLs
578     *   Local URL: [Modifier keys: {Copy,Move,Link}]
579     *    - Copy as Image, Animation and Launcher [Modifier keys: {Copy,Move,Link}]
580     *    - Link folder [Modifier keys: Useless]
581     *    - Make Launcher of executable [Modifier keys: {Copy_exec,Move_exec,Link_Launcher}]
582     *    - Ask for file (if use want to copy and it is a sound: make Sound)
583     * Policy of pastes of URL: [NO modifier keys]
584     *   - Same as drops
585     *   - But copy when ask should be done
586     *   - Unless cut-selection is true: move files instead
587     * Policy of file created in the basket dir: [NO modifier keys]
588     *   - View as Image, Animation, Sound, Launcher
589     *   - View as File
590     */
591     Note *note;
592     Note *firstNote = 0;
593     Note *lastInserted = 0;
594     for ( QList<QUrl>::iterator it = urls.begin(); it != urls.end(); ++it) {
595         if (((*it).scheme() == "mailto") ||
596                 (action == Qt::LinkAction))
597             note = createNoteLinkOrLauncher(*it, parent);
598 //         else if (!(*it).isLocalFile()) {
599 //             if (action != Qt::LinkAction && (maybeImageOrAnimation(*it)/* || maybeSound(*it)*/))
600 //                 note = copyFileAndLoad(*it, parent);
601 //             else
602 //                 note = createNoteLinkOrLauncher(*it, parent);
603 //         }
604 	else {
605             if (action == Qt::CopyAction)
606                 note = copyFileAndLoad(*it, parent);
607             else if (action == Qt::MoveAction)
608                 note = moveFileAndLoad(*it, parent);
609             else
610                 note = createNoteLinkOrLauncher(*it, parent);
611         }
612 
613         // If we got a new note, insert it in a linked list (we will return the first note of that list):
614         if (note) {
615             DEBUG_WIN << "Drop URL: " + (*it).toDisplayString();
616             if (!firstNote)
617                 firstNote = note;
618             else {
619                 lastInserted->setNext(note);
620                 note->setPrev(lastInserted);
621             }
622             lastInserted = note;
623         }
624     }
625     return firstNote;
626 }
627 
consumeContent(QDataStream & stream,NoteType::Id type)628 void NoteFactory::consumeContent(QDataStream &stream, NoteType::Id type)
629 {
630     if (type == NoteType::Link) {
631         QUrl url;
632         QString title, icon;
633         quint64 autoTitle64, autoIcon64;
634         stream >> url >> title >> icon >> autoTitle64 >> autoIcon64;
635     } else if (type == NoteType::CrossReference) {
636         QUrl url;
637         QString title, icon;
638         stream >> url >> title >> icon;
639     } else if (type == NoteType::Color) {
640         QColor color;
641         stream >> color;
642     }
643 }
644 
decodeContent(QDataStream & stream,NoteType::Id type,BasketScene * parent)645 Note* NoteFactory::decodeContent(QDataStream &stream, NoteType::Id type, BasketScene *parent)
646 {
647     /*  if (type == NoteType::Text) {
648         QString text;
649         stream >> text;
650         return NoteFactory::createNoteText(text, parent);
651     } else if (type == NoteType::Html) {
652         QString html;
653         stream >> html;
654         return NoteFactory::createNoteHtml(html, parent);
655     } else if (type == NoteType::Image) {
656         QPixmap pixmap;
657         stream >> pixmap;
658         return NoteFactory::createNoteImage(pixmap, parent);
659     } else */
660     if (type == NoteType::Link) {
661         QUrl url;
662         QString title, icon;
663         quint64 autoTitle64, autoIcon64;
664         bool autoTitle, autoIcon;
665         stream >> url >> title >> icon >> autoTitle64 >> autoIcon64;
666         autoTitle = (bool)autoTitle64;
667         autoIcon  = (bool)autoIcon64;
668         Note *note = NoteFactory::createNoteLink(url, parent);
669         ((LinkContent*)(note->content()))->setLink(url, title, icon, autoTitle, autoIcon);
670         return note;
671     } else if (type == NoteType::CrossReference) {
672         QUrl url;
673         QString title, icon;
674         stream >> url >> title >> icon;
675         Note *note = NoteFactory::createNoteCrossReference(url, parent);
676         ((CrossReferenceContent*)(note->content()))->setCrossReference(url, title, icon);
677         return note;
678     } else if (type == NoteType::Color) {
679         QColor color;
680         stream >> color;
681         return NoteFactory::createNoteColor(color, parent);
682     } else
683         return 0; // NoteFactory::loadFile() is sufficient
684 }
685 
maybeText(const QMimeType & mimeType)686 bool NoteFactory::maybeText(const QMimeType &mimeType)
687 {
688     return mimeType.inherits(MimeTypes::TEXT);
689 }
690 
maybeHtml(const QMimeType & mimeType)691 bool NoteFactory::maybeHtml(const QMimeType &mimeType)
692 {
693     return mimeType.inherits(MimeTypes::HTML);
694 }
695 
maybeImage(const QMimeType & mimeType)696 bool NoteFactory::maybeImage(const QMimeType &mimeType)
697 {
698     return mimeType.name().startsWith(MimeTypes::IMAGE);
699 }
700 
maybeAnimation(const QMimeType & mimeType)701 bool NoteFactory::maybeAnimation(const QMimeType &mimeType)
702 {
703     return mimeType.inherits(MimeTypes::ANIMATION) || mimeType.name() == MimeTypes::ANIMATION_MNG;
704 }
705 
maybeSound(const QMimeType & mimeType)706 bool NoteFactory::maybeSound(const QMimeType &mimeType)
707 {
708     return mimeType.name().startsWith(MimeTypes::AUDIO);
709 }
710 
maybeLauncher(const QMimeType & mimeType)711 bool NoteFactory::maybeLauncher(const QMimeType &mimeType)
712 {
713     return mimeType.inherits(MimeTypes::LAUNCHER);
714 }
715 
716 ////////////// NEW:
717 
copyFileAndLoad(const QUrl & url,BasketScene * parent)718 Note* NoteFactory::copyFileAndLoad(const QUrl &url, BasketScene *parent)
719 {
720     QString fileName = fileNameForNewNote(parent, url.fileName());
721     QString fullPath = parent->fullPathForFileName(fileName);
722 
723     if (Global::debugWindow)
724         *Global::debugWindow << "copyFileAndLoad: " + url.toDisplayString() + " to " + fullPath;
725 
726 //  QString annotations = i18n("Original file: %1", url.toDisplayString());
727 //  parent->dontCareOfCreation(fullPath);
728 
729     KIO::CopyJob *copyJob = KIO::copy(url, QUrl::fromLocalFile(fullPath),
730                                       KIO::Overwrite | KIO::Resume);
731     parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2);
732 
733     NoteType::Id type = typeForURL(url, parent); // Use the type of the original file because the target doesn't exist yet
734     return loadFile(fileName, type, parent);
735 }
736 
moveFileAndLoad(const QUrl & url,BasketScene * parent)737 Note* NoteFactory::moveFileAndLoad(const QUrl &url, BasketScene *parent)
738 {
739     // Globally the same as copyFileAndLoad() but move instead of copy (KIO::move())
740     QString fileName = fileNameForNewNote(parent, url.fileName());
741     QString fullPath = parent->fullPathForFileName(fileName);
742 
743     if (Global::debugWindow)
744         *Global::debugWindow << "moveFileAndLoad: " + url.toDisplayString() + " to " + fullPath;
745 
746 //  QString annotations = i18n("Original file: %1", url.toDisplayString());
747 //  parent->dontCareOfCreation(fullPath);
748 
749     KIO::CopyJob *copyJob = KIO::move(url, QUrl::fromLocalFile(fullPath),
750                                       KIO::Overwrite | KIO::Resume);
751 
752     parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2);
753 
754 
755     NoteType::Id type = typeForURL(url, parent); // Use the type of the original file because the target doesn't exist yet
756     return loadFile(fileName, type, parent);
757 }
758 
loadFile(const QString & fileName,BasketScene * parent)759 Note* NoteFactory::loadFile(const QString &fileName, BasketScene *parent)
760 {
761     // The file MUST exists
762     QFileInfo file(QUrl::fromLocalFile(parent->fullPathForFileName(fileName)).path());
763     if (! file.exists())
764         return 0L;
765 
766     NoteType::Id type = typeForURL(parent->fullPathForFileName(fileName), parent);
767     Note *note = loadFile(fileName, type, parent);
768     return note;
769 }
770 
loadFile(const QString & fileName,NoteType::Id type,BasketScene * parent)771 Note* NoteFactory::loadFile(const QString &fileName, NoteType::Id type, BasketScene *parent)
772 {
773     Note *note = new Note(parent);
774     switch (type) {
775     case NoteType::Text:      new TextContent(note, fileName); break;
776     case NoteType::Html:      new HtmlContent(note, fileName); break;
777     case NoteType::Image:     new ImageContent(note, fileName); break;
778     case NoteType::Animation: new AnimationContent(note, fileName); break;
779     case NoteType::Sound:     new SoundContent(note, fileName); break;
780     case NoteType::File:      new FileContent(note, fileName); break;
781     case NoteType::Launcher:  new LauncherContent(note, fileName); break;
782     case NoteType::Unknown:   new UnknownContent(note, fileName); break;
783 
784     default:
785     case NoteType::Link:
786     case NoteType::CrossReference:
787     case NoteType::Color:
788         return 0;
789     }
790 
791     return note;
792 }
793 
typeForURL(const QUrl & url,BasketScene *)794 NoteType::Id NoteFactory::typeForURL(const QUrl &url, BasketScene */*parent*/)
795 {
796     bool viewText  = Settings::viewTextFileContent();
797     bool viewHTML  = Settings::viewHtmlFileContent();
798     bool viewImage = Settings::viewImageFileContent();
799     bool viewSound = Settings::viewSoundFileContent();
800 
801     QMimeDatabase db;
802     QMimeType mimeType = db.mimeTypeForUrl(url);
803 
804     if (Global::debugWindow) {
805         if (mimeType.isValid())
806             *Global::debugWindow << "typeForURL: " + url.toDisplayString() + " ; MIME type = " + mimeType.name();
807         else
808             *Global::debugWindow << "typeForURL: mimeType is empty for " + url.toDisplayString();
809     }
810 
811     //Go from specific to more generic
812     if (maybeLauncher(mimeType))                           return NoteType::Launcher;
813     else if (viewHTML  && (maybeHtml(mimeType)))           return NoteType::Html;
814     else if (viewText  && maybeText(mimeType))             return NoteType::Text;
815     else if (viewImage && maybeAnimation(mimeType))        return NoteType::Animation; // See Note::movieStatus(int)
816     else if (viewImage && maybeImage(mimeType))            return NoteType::Image;     //  for more explanations
817     else if (viewSound && maybeSound(mimeType))            return NoteType::Sound;
818     else                                                   return NoteType::File;
819 }
820 
fileNameForNewNote(BasketScene * parent,const QString & wantedName)821 QString NoteFactory::fileNameForNewNote(BasketScene *parent, const QString &wantedName)
822 {
823     return Tools::fileNameForNewFile(wantedName, parent->fullPath());
824 }
825 
826 // Create a file to store a new note in Basket parent and with extension extension.
827 // If wantedName is provided, the function will first try to use this file name, or derive it if it's impossible
828 //  (extension willn't be used for that case)
createFileForNewNote(BasketScene * parent,const QString & extension,const QString & wantedName)829 QString NoteFactory::createFileForNewNote(BasketScene *parent, const QString &extension, const QString &wantedName)
830 {
831     QString fileName;
832     QString fullName;
833 
834     if (wantedName.isEmpty()) { // TODO: fileNameForNewNote(parent, "note1."+extension);
835         QDir dir;
836         int nb = parent->count() + 1;
837         QString time = QTime::currentTime().toString("hhmmss");
838 
839         for (; ; ++nb) {
840             fileName = QString("note%1-%2.%3").arg(nb).arg(time).arg(extension);
841             fullName = parent->fullPath() + fileName;
842             dir = QDir(fullName);
843             if (! dir.exists(fullName))
844                 break;
845         }
846     } else {
847         fileName = fileNameForNewNote(parent, wantedName);
848         fullName = parent->fullPath() + fileName;
849     }
850 
851     // Create the file
852 //  parent->dontCareOfCreation(fullName);
853     QFile file(fullName);
854     file.open(QIODevice::WriteOnly);
855     file.close();
856 
857     return fileName;
858 }
859 
filteredURL(const QUrl & url)860 QUrl NoteFactory::filteredURL(const QUrl &url)
861 {
862     // KURIFilter::filteredURI() is slow if the URL contains only letters, digits and '-' or '+'.
863     // So, we don't use that function is that case:
864     bool isSlow = true;
865     for (int i = 0; i < url.url().length(); ++i) {
866         QChar c = url.url()[i];
867         if (!c.isLetterOrNumber() && c != '-' && c != '+') {
868             isSlow = false;
869             break;
870         }
871     }
872     if (isSlow)
873         return url;
874     else {
875         QStringList list;
876         list << QLatin1String("kshorturifilter")
877             << QLatin1String("kuriikwsfilter")
878             << QLatin1String("kurisearchfilter")
879             << QLatin1String("localdomainfilter")
880             << QLatin1String("fixuphosturifilter");
881         return KUriFilter::self()->filteredUri(url, list);
882     }
883 }
884 
titleForURL(const QUrl & url)885 QString NoteFactory::titleForURL(const QUrl &url)
886 {
887     QString title = url.toDisplayString();
888     QString home  = "file:" + QDir::homePath() + "/";
889 
890     if (title.startsWith(QLatin1String("mailto:")))
891         return title.remove(0, 7);
892 
893     if (title.startsWith(home))
894         title = "~/" + title.remove(0, home.length());
895 
896     if (title.startsWith(QLatin1String("file://")))
897         title = title.remove(0, 7); // 7 == QString("file://").length() - 1
898     else if (title.startsWith(QLatin1String("file:")))
899         title = title.remove(0, 5); // 5 == QString("file:").length() - 1
900     else if (title.startsWith(QLatin1String("http://www.")))
901         title = title.remove(0, 11); // 11 == QString("http://www.").length() - 1
902     else if (title.startsWith(QLatin1String("http://")))
903         title = title.remove(0, 7); // 7 == QString("http://").length() - 1
904 
905     if (! url.isLocalFile()) {
906         if (title.endsWith(QLatin1String("/index.html")) && title.length() > 11)
907             title.truncate(title.length() - 11); // 11 == QString("/index.html").length()
908         else if (title.endsWith(QLatin1String("/index.htm")) && title.length() > 10)
909             title.truncate(title.length() - 10); // 10 == QString("/index.htm").length()
910         else if (title.endsWith(QLatin1String("/index.xhtml")) && title.length() > 12)
911             title.truncate(title.length() - 12); // 12 == QString("/index.xhtml").length()
912         else if (title.endsWith(QLatin1String("/index.php")) && title.length() > 10)
913             title.truncate(title.length() - 10); // 10 == QString("/index.php").length()
914         else if (title.endsWith(QLatin1String("/index.asp")) && title.length() > 10)
915             title.truncate(title.length() - 10); // 10 == QString("/index.asp").length()
916         else if (title.endsWith(QLatin1String("/index.php3")) && title.length() > 11)
917             title.truncate(title.length() - 11); // 11 == QString("/index.php3").length()
918         else if (title.endsWith(QLatin1String("/index.php4")) && title.length() > 11)
919             title.truncate(title.length() - 11); // 11 == QString("/index.php4").length()
920         else if (title.endsWith(QLatin1String("/index.php5")) && title.length() > 11)
921             title.truncate(title.length() - 11); // 11 == QString("/index.php5").length()
922     }
923 
924     if (title.length() > 2 && title.endsWith('/')) // length > 2 because "/" and "~/" shouldn't be transformed to "" and "~"
925         title.truncate(title.length() - 1); // eg. transform "www.kde.org/" to "www.kde.org"
926 
927     return title;
928 }
929 
iconForURL(const QUrl & url)930 QString NoteFactory::iconForURL(const QUrl &url)
931 {
932     QString icon = "";
933     if (url.scheme() == "mailto")
934         icon = "message";
935 //    else
936 //        icon = KMimeType::iconNameForUrl(url.url());
937     return icon;
938 }
939 
940 // TODO: Can I add "autoTitle" and "autoIcon" entries to .desktop files? or just store them in basket, as now...
941 
942 /* Try our better to find an icon suited to the command line
943  * eg. "/usr/bin/kwrite-3.2 ~/myfile.txt /home/other/file.xml"
944  * will give the "kwrite" icon!
945  */
iconForCommand(const QString & command)946 QString NoteFactory::iconForCommand(const QString &command)
947 {
948     QString icon;
949 
950     // 1. Use first word as icon (typically the program without argument)
951     icon = command.split(' ').first();
952     // 2. If the command is a full path, take only the program file name
953     icon = icon.mid(icon.lastIndexOf('/') + 1); // strip path if given [But it doesn't care of such
954     // "myprogram /my/path/argument" -> return "argument". Would
955     // must first strip first word and then strip path... Useful ??
956     // 3. Use characters before any '-' (e.g. use "gimp" icon if run command is "gimp-1.3")
957     if (! isIconExist(icon))
958         icon = icon.split('-').first();
959     // 4. If the icon still not findable, use a generic icon
960     if (! isIconExist(icon))
961         icon = "exec";
962 
963     return icon;
964 }
965 
isIconExist(const QString & icon)966 bool NoteFactory::isIconExist(const QString &icon)
967 {
968     return ! KIconLoader::global()->loadIcon(
969                icon, KIconLoader::NoGroup, 16, KIconLoader::DefaultState,
970                QStringList(), 0L, true
971            ).isNull();
972 }
973 
createEmptyNote(NoteType::Id type,BasketScene * parent)974 Note* NoteFactory::createEmptyNote(NoteType::Id type, BasketScene *parent)
975 {
976     QPixmap *pixmap;
977     switch (type) {
978     case NoteType::Text:
979         return NoteFactory::createNoteText("", parent, /*reallyPlainText=*/true);
980     case NoteType::Html:
981         return NoteFactory::createNoteHtml("", parent);
982     case NoteType::Image:
983         pixmap = new QPixmap(QSize(Settings::defImageX(), Settings::defImageY()));
984         pixmap->fill();
985         pixmap->setMask(pixmap->createHeuristicMask());
986         return NoteFactory::createNoteImage(*pixmap, parent);
987     case NoteType::Link:
988         return NoteFactory::createNoteLink(QUrl(), parent);
989     case NoteType::CrossReference:
990         return NoteFactory::createNoteCrossReference(QUrl(), parent);
991     case NoteType::Launcher:
992         return NoteFactory::createNoteLauncher(QUrl(), parent);
993     case NoteType::Color:
994         return NoteFactory::createNoteColor(Qt::black, parent);
995     default:
996     case NoteType::Animation:
997     case NoteType::Sound:
998     case NoteType::File:
999     case NoteType::Unknown:
1000         return 0; // Not possible!
1001     }
1002 }
1003 
importKMenuLauncher(BasketScene * parent)1004 Note* NoteFactory::importKMenuLauncher(BasketScene *parent)
1005 {
1006     QPointer<KOpenWithDialog> dialog = new KOpenWithDialog(parent->graphicsView()->viewport());
1007     dialog->setSaveNewApplications(true); // To create temp file, needed by createNoteLauncher()
1008     dialog->exec();
1009     if (dialog->service()) {
1010         // * locateLocal() return a local file even if it is a system wide one (local one doesn't exists)
1011         // * desktopEntryPath() returns the full path for system wide resources, but relative path if in home
1012         QString serviceFilePath = dialog->service()->entryPath();
1013         if (! serviceFilePath.startsWith('/'))
1014             serviceFilePath = dialog->service()->locateLocal();
1015         return createNoteLauncher(QUrl::fromUserInput(serviceFilePath), parent);
1016     }
1017     return 0;
1018 }
1019 
importIcon(BasketScene * parent)1020 Note* NoteFactory::importIcon(BasketScene *parent)
1021 {
1022     QString iconName = KIconDialog::getIcon(KIconLoader::Desktop, KIconLoader::Application, false, Settings::defIconSize());
1023     if (! iconName.isEmpty()) {
1024         QPointer<IconSizeDialog> dialog = new IconSizeDialog(i18n("Import Icon as Image"), i18n("Choose the size of the icon to import as an image:"), iconName, Settings::defIconSize(), 0);
1025         dialog->exec();
1026         if (dialog->iconSize() > 0) {
1027             Settings::setDefIconSize(dialog->iconSize());
1028             Settings::saveConfig();
1029             return createNoteImage(DesktopIcon(iconName, dialog->iconSize()), parent);   // TODO: wantedName = iconName !
1030         }
1031     }
1032     return 0;
1033 }
1034 
importFileContent(BasketScene * parent)1035 Note* NoteFactory::importFileContent(BasketScene *parent)
1036 {
1037     QUrl url = QFileDialog::getOpenFileUrl(parent->graphicsView(), i18n("Load File Content into a Note"),
1038                                            QUrl(), "");
1039     if (! url.isEmpty())
1040         return copyFileAndLoad(url, parent);
1041     return 0;
1042 }
1043