File indexing completed on 2023-09-24 09:50:57
0001 /** 0002 * SPDX-FileCopyrightText: (C) 2003 Sébastien Laoût <slaout@linux62.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "notefactory.h" 0008 0009 #include <QDomElement> 0010 #include <QFileDialog> 0011 #include <QGraphicsView> 0012 #include <QGuiApplication> 0013 #include <QLocale> 0014 #include <QMenu> 0015 #include <QMimeDatabase> 0016 #include <QMimeType> 0017 #include <QUrl> 0018 #include <QtCore/QDir> 0019 #include <QtCore/QFile> 0020 #include <QtCore/QFileInfo> 0021 #include <QtCore/QMimeData> 0022 #include <QtCore/QRegExp> 0023 #include <QtCore/QString> 0024 #include <QtCore/QTextStream> 0025 #include <QtCore/QVector> 0026 #include <QtCore/qnamespace.h> 0027 #include <QtGui/QBitmap> //For createHeuristicMask 0028 #include <QtGui/QColor> 0029 #include <QtGui/QImage> 0030 #include <QtGui/QImageReader> 0031 #include <QtGui/QMovie> 0032 #include <QtGui/QPixmap> 0033 #include <QtGui/QTextDocument> //For Qt::mightBeRichText(...) 0034 0035 #include <KAboutData> //For KGlobal::mainComponent().aboutData(...) 0036 #include <KIconDialog> 0037 #include <KIconLoader> 0038 #include <KLocalizedString> 0039 #include <KMessageBox> 0040 #include <KModifierKeyInfo> 0041 #include <KOpenWithDialog> 0042 #include <KUriFilter> 0043 0044 #include <KIO/CopyJob> 0045 0046 #include "basketlistview.h" 0047 #include "basketscene.h" 0048 #include "file_mimetypes.h" 0049 #include "global.h" 0050 #include "note.h" 0051 #include "notedrag.h" 0052 #include "settings.h" 0053 #include "tools.h" 0054 #include "variouswidgets.h" //For IconSizeDialog 0055 #include "xmlwork.h" 0056 0057 #include "debugwindow.h" 0058 0059 /** Create notes from scratch (just a content) */ 0060 0061 Note *NoteFactory::createNoteText(const QString &text, BasketScene *parent, bool reallyPlainText /* = false*/) 0062 { 0063 QList<State*> tags; int tagsLength = 0; Note* note; 0064 0065 if (Settings::detectTextTags()) 0066 { 0067 tags = Tools::detectTags(text, tagsLength); 0068 } 0069 QString textConverted = text.mid(tagsLength); 0070 0071 if (reallyPlainText) 0072 { 0073 note = new Note(parent); 0074 TextContent *content = new TextContent(note, createFileForNewNote(parent, "txt")); 0075 content->setText(text); 0076 content->saveToFile(); 0077 } 0078 else 0079 { 0080 textConverted = QString("<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"><meta name=\"qrichtext\" content=\"1\" /></head><body>%1</body></html>") 0081 .arg(Tools::textToHTMLWithoutP(textConverted)); 0082 0083 note = createNoteHtml(textConverted, parent); 0084 } 0085 0086 if (note) 0087 for (State* state: tags) 0088 { 0089 note->addState(state, true); 0090 } 0091 0092 return note; 0093 } 0094 0095 Note *NoteFactory::createNoteHtml(const QString &html, BasketScene *parent) 0096 { 0097 Note *note = new Note(parent); 0098 HtmlContent *content = new HtmlContent(note, createFileForNewNote(parent, "html")); 0099 content->setHtml(html); 0100 content->saveToFile(); 0101 return note; 0102 } 0103 0104 Note *NoteFactory::createNoteLink(const QUrl &url, BasketScene *parent) 0105 { 0106 Note *note = new Note(parent); 0107 new LinkContent(note, url, titleForURL(url), iconForURL(url), /*autoTitle=*/true, /*autoIcon=*/true); 0108 return note; 0109 } 0110 0111 Note *NoteFactory::createNoteLink(const QUrl &url, const QString &title, BasketScene *parent) 0112 { 0113 Note *note = new Note(parent); 0114 new LinkContent(note, url, title, iconForURL(url), /*autoTitle=*/false, /*autoIcon=*/true); 0115 return note; 0116 } 0117 0118 Note *NoteFactory::createNoteCrossReference(const QUrl &url, BasketScene *parent) 0119 { 0120 Note *note = new Note(parent); 0121 new CrossReferenceContent(note, url, titleForURL(url), iconForURL(url)); 0122 return note; 0123 } 0124 0125 Note *NoteFactory::createNoteCrossReference(const QUrl &url, const QString &title, BasketScene *parent) 0126 { 0127 Note *note = new Note(parent); 0128 new CrossReferenceContent(note, url, title, iconForURL(url)); 0129 return note; 0130 } 0131 0132 Note *NoteFactory::createNoteCrossReference(const QUrl &url, const QString &title, const QString &icon, BasketScene *parent) 0133 { 0134 Note *note = new Note(parent); 0135 new CrossReferenceContent(note, url, title, icon); 0136 return note; 0137 } 0138 0139 Note *NoteFactory::createNoteImage(const QPixmap &image, BasketScene *parent) 0140 { 0141 Note *note = new Note(parent); 0142 ImageContent *content = new ImageContent(note, createFileForNewNote(parent, "png")); 0143 content->setPixmap(image); 0144 content->saveToFile(); 0145 return note; 0146 } 0147 0148 Note *NoteFactory::createNoteColor(const QColor &color, BasketScene *parent) 0149 { 0150 Note *note = new Note(parent); 0151 new ColorContent(note, color); 0152 return note; 0153 } 0154 0155 /** Return a string list containing {url1, title1, url2, title2, url3, title3...} 0156 */ 0157 QStringList NoteFactory::textToURLList(const QString &text) 0158 { 0159 // List to return: 0160 QStringList list; 0161 0162 // Split lines: 0163 QStringList texts = text.split('\n'); 0164 0165 // For each lines: 0166 QStringList::iterator it; 0167 for (it = texts.begin(); it != texts.end(); ++it) { 0168 // Strip white spaces: 0169 (*it) = (*it).trimmed(); 0170 0171 // Don't care of empty entries: 0172 if ((*it).isEmpty()) 0173 continue; 0174 0175 // Compute lower case equivalent: 0176 QString ltext = (*it).toLower(); 0177 0178 /* Search for mail address ("*@*.*" ; "*" can contain '_', '-', or '.') and add protocol to it */ 0179 QString mailExpString = "[\\w-\\.]+@[\\w-\\.]+\\.[\\w]+"; 0180 QRegExp mailExp("^" + mailExpString + '$'); 0181 if (mailExp.exactMatch(ltext)) { 0182 ltext.insert(0, "mailto:"); 0183 (*it).insert(0, "mailto:"); 0184 } 0185 0186 // TODO: Recognize "<link>" (link between '<' and '>') 0187 // TODO: Replace " at " by "@" and " dot " by "." to look for e-mail addresses 0188 0189 /* Search for mail address like "Name <address@provider.net>" */ 0190 QRegExp namedMailExp("^([\\w\\s]+)\\s<(" + mailExpString + ")>$"); 0191 // namedMailExp.setCaseSensitive(true); // For the name to be keeped with uppercases // DOESN'T WORK ! 0192 if (namedMailExp.exactMatch(ltext)) { 0193 QString name = namedMailExp.cap(1); 0194 QString address = "mailto:" + namedMailExp.cap(2); 0195 // Threat it NOW, as it's an exception (it have a title): 0196 list.append(address); 0197 list.append(name); 0198 continue; 0199 } 0200 0201 /* Search for an url and create an URL note */ 0202 if ((ltext.startsWith('/') && ltext[1] != '/' && ltext[1] != '*') || // Take files but not C/C++/... comments ! 0203 ltext.startsWith(QLatin1String("file:")) || ltext.startsWith(QLatin1String("http://")) || ltext.startsWith(QLatin1String("https://")) || ltext.startsWith(QLatin1String("www.")) || ltext.startsWith(QLatin1String("ftp.")) || 0204 ltext.startsWith(QLatin1String("ftp://")) || ltext.startsWith(QLatin1String("mailto:"))) { 0205 // First, correct the text to use the good format for the url 0206 if (ltext.startsWith('/')) 0207 (*it).insert(0, "file:"); 0208 if (ltext.startsWith(QLatin1String("www."))) 0209 (*it).insert(0, "http://"); 0210 if (ltext.startsWith(QLatin1String("ftp."))) 0211 (*it).insert(0, "ftp://"); 0212 0213 // And create the Url note (or launcher if URL point a .desktop file) 0214 list.append(*it); 0215 list.append(QString()); // We don't have any title 0216 } else 0217 return QStringList(); // FAILED: treat the text as a text, and not as a URL list! 0218 } 0219 return list; 0220 } 0221 0222 Note *NoteFactory::createNoteFromText(const QString &text, BasketScene *parent) 0223 { 0224 /* Search for a color (#RGB , #RRGGBB , #RRRGGGBBB , #RRRRGGGGBBBB) and create a color note */ 0225 QRegExp exp("^#(?:[a-fA-F\\d]{3}){1,4}$"); 0226 if (exp.exactMatch(text)) 0227 return createNoteColor(QColor(text), parent); 0228 0229 /* Try to convert the text as a URL or a list of URLs */ 0230 QStringList uriList = textToURLList(text); 0231 if (!uriList.isEmpty()) { 0232 // TODO: This code is almost duplicated from fropURLs()! 0233 Note *note; 0234 Note *firstNote = nullptr; 0235 Note *lastInserted = nullptr; 0236 QStringList::iterator it; 0237 for (it = uriList.begin(); it != uriList.end(); ++it) { 0238 QString url = (*it); 0239 ++it; 0240 QString title = (*it); 0241 if (title.isEmpty()) 0242 note = createNoteLinkOrLauncher(QUrl::fromUserInput(url), parent); 0243 else 0244 note = createNoteLink(QUrl::fromUserInput(url), title, parent); 0245 0246 // If we got a new note, insert it in a linked list (we will return the first note of that list): 0247 if (note) { 0248 // qDebug() << "Drop URL: " << (*it).toDisplayString(); 0249 if (!firstNote) 0250 firstNote = note; 0251 else { 0252 lastInserted->setNext(note); 0253 note->setPrev(lastInserted); 0254 } 0255 lastInserted = note; 0256 } 0257 } 0258 return firstNote; // It don't return ALL inserted notes ! 0259 } 0260 0261 // QString newText = text.trimmed(); // The text for a new note, without useless spaces 0262 /* Else, it's a text or an HTML note, so, create it */ 0263 if (Qt::mightBeRichText(/*newT*/ text)) 0264 return createNoteHtml(/*newT*/ text, parent); 0265 else 0266 return createNoteText(/*newT*/ text, parent); 0267 } 0268 0269 Note *NoteFactory::createNoteLauncher(const QUrl &url, BasketScene *parent) 0270 { 0271 if (url.isEmpty()) 0272 return createNoteLauncher(QString(), QString(), QString(), parent); 0273 else 0274 return copyFileAndLoad(url, parent); 0275 } 0276 0277 Note *NoteFactory::createNoteLauncher(const QString &command, const QString &name, const QString &icon, BasketScene *parent) 0278 { 0279 QString fileName = createNoteLauncherFile(command, name, icon, parent); 0280 if (fileName.isEmpty()) 0281 return nullptr; 0282 else 0283 return loadFile(fileName, parent); 0284 } 0285 0286 QString NoteFactory::createNoteLauncherFile(const QString &command, const QString &name, const QString &icon, BasketScene *parent) 0287 { 0288 QString content = QString( 0289 "[Desktop Entry]\n" 0290 "Exec=%1\n" 0291 "Name=%2\n" 0292 "Icon=%3\n" 0293 "Encoding=UTF-8\n" 0294 "Type=Application\n") 0295 .arg(command, name, icon.isEmpty() ? QString("exec") : icon); 0296 QString fileName = fileNameForNewNote(parent, "launcher.desktop"); 0297 QString fullPath = parent->fullPathForFileName(fileName); 0298 // parent->dontCareOfCreation(fullPath); 0299 QFile file(fullPath); 0300 if (file.open(QIODevice::WriteOnly)) { 0301 QTextStream stream(&file); 0302 stream.setCodec("UTF-8"); 0303 stream << content; 0304 file.close(); 0305 return fileName; 0306 } else 0307 return QString(); 0308 } 0309 0310 Note *NoteFactory::createNoteLinkOrLauncher(const QUrl &url, BasketScene *parent) 0311 { 0312 // IMPORTANT: we create the service ONLY if the extension is ".desktop". 0313 // Otherwise, KService take a long time to analyze all the file 0314 // and output such things to stdout: 0315 // "Invalid entry (missing '=') at /my/file.ogg:11984" 0316 // "Invalid entry (missing ']') at /my/file.ogg:11984"... 0317 KService::Ptr service; 0318 if (url.fileName().endsWith(QLatin1String(".desktop"))) 0319 service = new KService(url.path()); 0320 0321 // If link point to a .desktop file then add a launcher, otherwise it's a link 0322 if (service && service->isValid()) 0323 return createNoteLauncher(url, parent); 0324 else 0325 return createNoteLink(url, parent); 0326 } 0327 0328 bool NoteFactory::movingNotesInTheSameBasket(const QMimeData *source, BasketScene *parent, Qt::DropAction action) 0329 { 0330 if (NoteDrag::canDecode(source)) 0331 return action == Qt::MoveAction && NoteDrag::basketOf(source) == parent; 0332 else 0333 return false; 0334 } 0335 0336 Note *NoteFactory::dropNote(const QMimeData *source, BasketScene *parent, bool fromDrop, Qt::DropAction action, Note * /*noteSource*/) 0337 { 0338 if (source == nullptr) { 0339 return nullptr; 0340 } 0341 0342 Note *note = nullptr; 0343 0344 QStringList formats = source->formats(); 0345 /* No data */ 0346 if (formats.size() == 0) { 0347 // TODO: add a parameter to say if it's from a clipboard paste, a selection paste, or a drop 0348 // To be able to say "The clipboard/selection/drop is empty". 0349 // KMessageBox::error(parent, i18n("There is no data to insert."), i18n("No Data")); 0350 return nullptr; 0351 } 0352 0353 /* Debug */ 0354 if (Global::debugWindow) { 0355 *Global::debugWindow << "<b>Drop :</b>"; 0356 for (int i = 0; i < formats.size(); ++i) 0357 *Global::debugWindow << "\t[" + QString::number(i) + "] " + formats[i]; 0358 switch (action) { // The source want that we: 0359 case Qt::CopyAction: 0360 *Global::debugWindow << ">> Drop action: Copy"; 0361 break; 0362 case Qt::MoveAction: 0363 *Global::debugWindow << ">> Drop action: Move"; 0364 break; 0365 case Qt::LinkAction: 0366 *Global::debugWindow << ">> Drop action: Link"; 0367 break; 0368 default: 0369 *Global::debugWindow << ">> Drop action: Unknown"; // supported by Qt! 0370 } 0371 } 0372 0373 /* Copy or move a Note */ 0374 if (NoteDrag::canDecode(source)) { 0375 bool moveFiles = fromDrop && action == Qt::MoveAction; 0376 bool moveNotes = moveFiles; 0377 return NoteDrag::decode(source, parent, moveFiles, moveNotes); // Filename will be kept 0378 } 0379 0380 /* Else : Drop object to note */ 0381 0382 QImage image = qvariant_cast<QImage>(source->imageData()); 0383 if (!image.isNull()) 0384 return createNoteImage(QPixmap::fromImage(image), parent); 0385 0386 if (source->hasColor()) { 0387 return createNoteColor(qvariant_cast<QColor>(source->colorData()), parent); 0388 } 0389 0390 // And then the hack (if provide color MIME type or a text that contains color), using createNote Color RegExp: 0391 QString hack; 0392 QRegExp exp("^#(?:[a-fA-F\\d]{3}){1,4}$"); 0393 hack = source->text(); 0394 if (source->hasFormat("application/x-color") || (!hack.isNull() && exp.exactMatch(hack))) { 0395 QColor color = qvariant_cast<QColor>(source->colorData()); 0396 if (color.isValid()) 0397 return createNoteColor(color, parent); 0398 // if ( (note = createNoteColor(color, parent)) ) 0399 // return note; 0400 // // Theoretically it should be returned. If not, continue by dropping other things 0401 } 0402 0403 QList<QUrl> urls = source->urls(); 0404 if (!urls.isEmpty()) { 0405 // If it's a Paste, we should know if files should be copied (copy&paste) or moved (cut&paste): 0406 if (!fromDrop && Tools::isAFileCut(source)) 0407 action = Qt::MoveAction; 0408 return dropURLs(urls, parent, action, fromDrop); 0409 } 0410 0411 // FIXME: use dropURLs() also from Mozilla? 0412 0413 /* 0414 * Mozilla's stuff sometimes uses utf-16-le - little-endian UTF-16. 0415 * 0416 * This has the property that for the ASCII subset case (And indeed, the 0417 * ISO-8859-1 subset, I think), if you treat it as a C-style string, 0418 * it'll come out to one character long in most cases, since it looks 0419 * like: 0420 * 0421 * "<\0H\0T\0M\0L\0>\0" 0422 * 0423 * A strlen() call on that will give you 1, which simply isn't correct. 0424 * That might, I suppose, be the answer, or something close. 0425 * 0426 * Also, Mozilla's drag/drop code predates the use of MIME types in XDnD 0427 * - hence it'll throw about STRING and UTF8_STRING quite happily, hence 0428 * the odd named types. 0429 * 0430 * Thanks to Dave Cridland for having said me that. 0431 */ 0432 if (source->hasFormat("text/x-moz-url")) { // FOR MOZILLA 0433 // Get the array and create a QChar array of 1/2 of the size 0434 QByteArray mozilla = source->data("text/x-moz-url"); 0435 QVector<QChar> chars(mozilla.count() / 2); 0436 // A small debug work to know the value of each bytes 0437 if (Global::debugWindow) 0438 for (int i = 0; i < mozilla.count(); i++) 0439 *Global::debugWindow << QString("'") + QChar(mozilla[i]) + "' " + QString::number(int(mozilla[i])); 0440 // text/x-moz-url give the URL followed by the link title and separated by OxOA (10 decimal: new line?) 0441 uint size = 0; 0442 QChar *name = nullptr; 0443 // For each little endian mozilla chars, copy it to the array of QChars 0444 for (int i = 0; i < mozilla.count(); i += 2) { 0445 chars[i / 2] = QChar(mozilla[i], mozilla[i + 1]); 0446 if (mozilla.at(i) == 0x0A) { 0447 size = i / 2; 0448 name = &(chars[i / 2 + 1]); 0449 } 0450 } 0451 // Create a QString that take the address of the first QChar and a length 0452 if (name == nullptr) { // We haven't found name (FIXME: Is it possible ?) 0453 QString normalHtml(&(chars[0]), chars.size()); 0454 return createNoteLink(normalHtml, parent); 0455 } else { 0456 QString normalHtml(&(chars[0]), size); 0457 QString normalTitle(name, chars.size() - size - 1); 0458 return createNoteLink(normalHtml, normalTitle, parent); 0459 } 0460 } 0461 0462 if (source->hasFormat("text/html")) { 0463 QString html; 0464 QString subtype("html"); 0465 // If the text/html comes from Mozilla or GNOME it can be UTF-16 encoded: we need ExtendedTextDrag to check that 0466 ExtendedTextDrag::decode(source, html, subtype); 0467 return createNoteHtml(html, parent); 0468 } 0469 0470 QString text; 0471 // If the text/plain comes from GEdit or GNOME it can be empty: we need ExtendedTextDrag to check other MIME types 0472 if (ExtendedTextDrag::decode(source, text)) 0473 return createNoteFromText(text, parent); 0474 0475 /* Create a cross reference note */ 0476 0477 if (source->hasFormat(BasketTreeListView::TREE_ITEM_MIME_STRING)) { 0478 QByteArray data = source->data(BasketTreeListView::TREE_ITEM_MIME_STRING); 0479 QDataStream stream(&data, QIODevice::ReadOnly); 0480 QString basketName, folderName, icon; 0481 0482 while (!stream.atEnd()) 0483 stream >> basketName >> folderName >> icon; 0484 0485 return createNoteCrossReference(QUrl("basket://" + folderName), basketName, icon, parent); 0486 } 0487 0488 /* Unsuccessful drop */ 0489 note = createNoteUnknown(source, parent); 0490 QString message = i18n( 0491 "<p>%1 doesn't support the data you've dropped.<br>" 0492 "It however created a generic note, allowing you to drag or copy it to an application that understand it.</p>" 0493 "<p>If you want the support of these data, please contact developer.</p>", 0494 QGuiApplication::applicationDisplayName()); 0495 KMessageBox::information(parent->graphicsView()->viewport(), message, i18n("Unsupported MIME Type(s)"), "unsupportedDropInfo", KMessageBox::AllowLink); 0496 return note; 0497 } 0498 0499 Note *NoteFactory::createNoteUnknown(const QMimeData *source, BasketScene *parent /*, const QString &annotations*/) 0500 { 0501 // Save the MimeSource in a file: create and open the file: 0502 QString fileName = createFileForNewNote(parent, "unknown"); 0503 QFile file(parent->fullPath() + fileName); 0504 if (!file.open(QIODevice::WriteOnly)) 0505 return nullptr; 0506 QDataStream stream(&file); 0507 0508 // Echo MIME types: 0509 QStringList formats = source->formats(); 0510 for (int i = 0; i < formats.size(); ++i) 0511 stream << QString(formats[i]); // Output the '\0'-terminated format name string 0512 0513 // Echo end of MIME types list delimiter: 0514 stream << QString(); 0515 0516 // Echo the length (in bytes) and then the data, and then same for next MIME type: 0517 for (int i = 0; i < formats.size(); ++i) { 0518 QByteArray data = source->data(formats[i]); 0519 stream << (quint32)data.count(); 0520 stream.writeRawData(data.data(), data.count()); 0521 } 0522 file.close(); 0523 0524 Note *note = new Note(parent); 0525 new UnknownContent(note, fileName); 0526 return note; 0527 } 0528 0529 Note *NoteFactory::dropURLs(QList<QUrl> urls, BasketScene *parent, Qt::DropAction action, bool fromDrop) 0530 { 0531 KModifierKeyInfo keyinfo; 0532 int shouldAsk = 0; // shouldAsk==0: don't ask ; shouldAsk==1: ask for "file" ; shouldAsk>=2: ask for "files" 0533 bool shiftPressed = keyinfo.isKeyPressed(Qt::Key_Shift); 0534 bool ctrlPressed = keyinfo.isKeyPressed(Qt::Key_Control); 0535 bool modified = fromDrop && (shiftPressed || ctrlPressed); 0536 0537 if (modified) // Then no menu + modified action 0538 ; // action is already set: no work to do 0539 else if (fromDrop) { // Compute if user should be asked or not 0540 for (QList<QUrl>::iterator it = urls.begin(); it != urls.end(); ++it) 0541 if ((*it).scheme() != "mailto") { // Do not ask when dropping mail address :-) 0542 shouldAsk++; 0543 if (shouldAsk == 1 /*2*/) // Sufficient 0544 break; 0545 } 0546 if (shouldAsk) { 0547 QMenu menu(parent->graphicsView()); 0548 QList<QAction *> actList; 0549 actList << new QAction(QIcon::fromTheme("go-jump"), i18n("&Move Here\tShift"), &menu) << new QAction(QIcon::fromTheme("edit-copy"), i18n("&Copy Here\tCtrl"), &menu) 0550 << new QAction(QIcon::fromTheme("insert-link"), i18n("&Link Here\tCtrl+Shift"), &menu); 0551 0552 Q_FOREACH (QAction *a, actList) 0553 menu.addAction(a); 0554 0555 menu.addSeparator(); 0556 menu.addAction(QIcon::fromTheme("dialog-cancel"), i18n("C&ancel\tEscape")); 0557 int id = actList.indexOf(menu.exec(QCursor::pos())); 0558 switch (id) { 0559 case 0: 0560 action = Qt::MoveAction; 0561 break; 0562 case 1: 0563 action = Qt::CopyAction; 0564 break; 0565 case 2: 0566 action = Qt::LinkAction; 0567 break; 0568 default: 0569 return nullptr; 0570 } 0571 modified = true; 0572 } 0573 } else { // fromPaste 0574 ; 0575 } 0576 0577 /* Policy of drops of URL: 0578 * Email: [Modifier keys: Useless] 0579 + - Link mail address 0580 * Remote URL: [Modifier keys: {Copy,Link}] 0581 + - Download as Image, Animation and Launcher 0582 + - Link other URLs 0583 * Local URL: [Modifier keys: {Copy,Move,Link}] 0584 * - Copy as Image, Animation and Launcher [Modifier keys: {Copy,Move,Link}] 0585 * - Link folder [Modifier keys: Useless] 0586 * - Make Launcher of executable [Modifier keys: {Copy_exec,Move_exec,Link_Launcher}] 0587 * - Ask for file (if use want to copy and it is a sound: make Sound) 0588 * Policy of pastes of URL: [NO modifier keys] 0589 * - Same as drops 0590 * - But copy when ask should be done 0591 * - Unless cut-selection is true: move files instead 0592 * Policy of file created in the basket dir: [NO modifier keys] 0593 * - View as Image, Animation, Sound, Launcher 0594 * - View as File 0595 */ 0596 Note *note; 0597 Note *firstNote = nullptr; 0598 Note *lastInserted = nullptr; 0599 for (QList<QUrl>::iterator it = urls.begin(); it != urls.end(); ++it) { 0600 if (((*it).scheme() == "mailto") || (action == Qt::LinkAction)) 0601 note = createNoteLinkOrLauncher(*it, parent); 0602 // else if (!(*it).isLocalFile()) { 0603 // if (action != Qt::LinkAction && (maybeImageOrAnimation(*it)/* || maybeSound(*it)*/)) 0604 // note = copyFileAndLoad(*it, parent); 0605 // else 0606 // note = createNoteLinkOrLauncher(*it, parent); 0607 // } 0608 else { 0609 if (action == Qt::CopyAction) 0610 note = copyFileAndLoad(*it, parent); 0611 else if (action == Qt::MoveAction) 0612 note = moveFileAndLoad(*it, parent); 0613 else 0614 note = createNoteLinkOrLauncher(*it, parent); 0615 } 0616 0617 // If we got a new note, insert it in a linked list (we will return the first note of that list): 0618 if (note) { 0619 DEBUG_WIN << "Drop URL: " + (*it).toDisplayString(); 0620 if (!firstNote) 0621 firstNote = note; 0622 else { 0623 lastInserted->setNext(note); 0624 note->setPrev(lastInserted); 0625 } 0626 lastInserted = note; 0627 } 0628 } 0629 return firstNote; 0630 } 0631 0632 void NoteFactory::consumeContent(QDataStream &stream, NoteType::Id type) 0633 { 0634 if (type == NoteType::Link) { 0635 QUrl url; 0636 QString title, icon; 0637 quint64 autoTitle64, autoIcon64; 0638 stream >> url >> title >> icon >> autoTitle64 >> autoIcon64; 0639 } else if (type == NoteType::CrossReference) { 0640 QUrl url; 0641 QString title, icon; 0642 stream >> url >> title >> icon; 0643 } else if (type == NoteType::Color) { 0644 QColor color; 0645 stream >> color; 0646 } 0647 } 0648 0649 Note *NoteFactory::decodeContent(QDataStream &stream, NoteType::Id type, BasketScene *parent) 0650 { 0651 /* if (type == NoteType::Text) { 0652 QString text; 0653 stream >> text; 0654 return NoteFactory::createNoteText(text, parent); 0655 } else if (type == NoteType::Html) { 0656 QString html; 0657 stream >> html; 0658 return NoteFactory::createNoteHtml(html, parent); 0659 } else if (type == NoteType::Image) { 0660 QPixmap pixmap; 0661 stream >> pixmap; 0662 return NoteFactory::createNoteImage(pixmap, parent); 0663 } else */ 0664 if (type == NoteType::Link) { 0665 QUrl url; 0666 QString title, icon; 0667 quint64 autoTitle64, autoIcon64; 0668 bool autoTitle, autoIcon; 0669 stream >> url >> title >> icon >> autoTitle64 >> autoIcon64; 0670 autoTitle = (bool)autoTitle64; 0671 autoIcon = (bool)autoIcon64; 0672 Note *note = NoteFactory::createNoteLink(url, parent); 0673 ((LinkContent *)(note->content()))->setLink(url, title, icon, autoTitle, autoIcon); 0674 return note; 0675 } else if (type == NoteType::CrossReference) { 0676 QUrl url; 0677 QString title, icon; 0678 stream >> url >> title >> icon; 0679 Note *note = NoteFactory::createNoteCrossReference(url, parent); 0680 ((CrossReferenceContent *)(note->content()))->setCrossReference(url, title, icon); 0681 return note; 0682 } else if (type == NoteType::Color) { 0683 QColor color; 0684 stream >> color; 0685 return NoteFactory::createNoteColor(color, parent); 0686 } else 0687 return nullptr; // NoteFactory::loadFile() is sufficient 0688 } 0689 0690 bool NoteFactory::maybeText(const QMimeType &mimeType) 0691 { 0692 return mimeType.inherits(MimeTypes::TEXT); 0693 } 0694 0695 bool NoteFactory::maybeHtml(const QMimeType &mimeType) 0696 { 0697 return mimeType.inherits(MimeTypes::HTML); 0698 } 0699 0700 bool NoteFactory::maybeImage(const QMimeType &mimeType) 0701 { 0702 return mimeType.name().startsWith(MimeTypes::IMAGE); 0703 } 0704 0705 bool NoteFactory::maybeAnimation(const QMimeType &mimeType) 0706 { 0707 return mimeType.inherits(MimeTypes::ANIMATION) || mimeType.name() == MimeTypes::ANIMATION_MNG; 0708 } 0709 0710 bool NoteFactory::maybeSound(const QMimeType &mimeType) 0711 { 0712 return mimeType.name().startsWith(MimeTypes::AUDIO); 0713 } 0714 0715 bool NoteFactory::maybeLauncher(const QMimeType &mimeType) 0716 { 0717 return mimeType.inherits(MimeTypes::LAUNCHER); 0718 } 0719 0720 ////////////// NEW: 0721 0722 Note *NoteFactory::copyFileAndLoad(const QUrl &url, BasketScene *parent) 0723 { 0724 QString fileName = fileNameForNewNote(parent, url.fileName()); 0725 QString fullPath = parent->fullPathForFileName(fileName); 0726 0727 if (Global::debugWindow) 0728 *Global::debugWindow << "copyFileAndLoad: " + url.toDisplayString() + " to " + fullPath; 0729 0730 // QString annotations = i18n("Original file: %1", url.toDisplayString()); 0731 // parent->dontCareOfCreation(fullPath); 0732 0733 KIO::CopyJob *copyJob = KIO::copy(url, QUrl::fromLocalFile(fullPath), KIO::Overwrite | KIO::Resume); 0734 parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2); 0735 0736 NoteType::Id type = typeForURL(url, parent); // Use the type of the original file because the target doesn't exist yet 0737 return loadFile(fileName, type, parent); 0738 } 0739 0740 Note *NoteFactory::moveFileAndLoad(const QUrl &url, BasketScene *parent) 0741 { 0742 // Globally the same as copyFileAndLoad() but move instead of copy (KIO::move()) 0743 QString fileName = fileNameForNewNote(parent, url.fileName()); 0744 QString fullPath = parent->fullPathForFileName(fileName); 0745 0746 if (Global::debugWindow) 0747 *Global::debugWindow << "moveFileAndLoad: " + url.toDisplayString() + " to " + fullPath; 0748 0749 // QString annotations = i18n("Original file: %1", url.toDisplayString()); 0750 // parent->dontCareOfCreation(fullPath); 0751 0752 KIO::CopyJob *copyJob = KIO::move(url, QUrl::fromLocalFile(fullPath), KIO::Overwrite | KIO::Resume); 0753 0754 parent->connect(copyJob, &KIO::CopyJob::copyingDone, parent, &BasketScene::slotCopyingDone2); 0755 0756 NoteType::Id type = typeForURL(url, parent); // Use the type of the original file because the target doesn't exist yet 0757 return loadFile(fileName, type, parent); 0758 } 0759 0760 Note *NoteFactory::loadFile(const QString &fileName, BasketScene *parent) 0761 { 0762 // The file MUST exists 0763 QFileInfo file(QUrl::fromLocalFile(parent->fullPathForFileName(fileName)).path()); 0764 if (!file.exists()) 0765 return nullptr; 0766 0767 NoteType::Id type = typeForURL(parent->fullPathForFileName(fileName), parent); 0768 Note *note = loadFile(fileName, type, parent); 0769 return note; 0770 } 0771 0772 Note *NoteFactory::loadFile(const QString &fileName, NoteType::Id type, BasketScene *parent) 0773 { 0774 Note *note = new Note(parent); 0775 switch (type) { 0776 case NoteType::Text: 0777 new TextContent(note, fileName); 0778 break; 0779 case NoteType::Html: 0780 new HtmlContent(note, fileName); 0781 break; 0782 case NoteType::Image: 0783 new ImageContent(note, fileName); 0784 break; 0785 case NoteType::Animation: 0786 new AnimationContent(note, fileName); 0787 break; 0788 case NoteType::Sound: 0789 new SoundContent(note, fileName); 0790 break; 0791 case NoteType::File: 0792 new FileContent(note, fileName); 0793 break; 0794 case NoteType::Launcher: 0795 new LauncherContent(note, fileName); 0796 break; 0797 case NoteType::Unknown: 0798 new UnknownContent(note, fileName); 0799 break; 0800 0801 default: 0802 case NoteType::Link: 0803 case NoteType::CrossReference: 0804 case NoteType::Color: 0805 return nullptr; 0806 } 0807 0808 return note; 0809 } 0810 0811 NoteType::Id NoteFactory::typeForURL(const QUrl &url, BasketScene * /*parent*/) 0812 { 0813 bool viewText = Settings::viewTextFileContent(); 0814 bool viewHTML = Settings::viewHtmlFileContent(); 0815 bool viewImage = Settings::viewImageFileContent(); 0816 bool viewSound = Settings::viewSoundFileContent(); 0817 0818 QMimeDatabase db; 0819 QMimeType mimeType = db.mimeTypeForUrl(url); 0820 0821 if (Global::debugWindow) { 0822 if (mimeType.isValid()) 0823 *Global::debugWindow << "typeForURL: " + url.toDisplayString() + " ; MIME type = " + mimeType.name(); 0824 else 0825 *Global::debugWindow << "typeForURL: mimeType is empty for " + url.toDisplayString(); 0826 } 0827 0828 // Go from specific to more generic 0829 if (maybeLauncher(mimeType)) 0830 return NoteType::Launcher; 0831 else if (viewHTML && (maybeHtml(mimeType))) 0832 return NoteType::Html; 0833 else if (viewText && maybeText(mimeType)) 0834 return NoteType::Text; 0835 else if (viewImage && maybeAnimation(mimeType)) 0836 return NoteType::Animation; // See Note::movieStatus(int) 0837 else if (viewImage && maybeImage(mimeType)) 0838 return NoteType::Image; // for more explanations 0839 else if (viewSound && maybeSound(mimeType)) 0840 return NoteType::Sound; 0841 else 0842 return NoteType::File; 0843 } 0844 0845 QString NoteFactory::fileNameForNewNote(BasketScene *parent, const QString &wantedName) 0846 { 0847 return Tools::fileNameForNewFile(wantedName, parent->fullPath()); 0848 } 0849 0850 // Create a file to store a new note in Basket parent and with extension extension. 0851 // If wantedName is provided, the function will first try to use this file name, or derive it if it's impossible 0852 // (extension willn't be used for that case) 0853 QString NoteFactory::createFileForNewNote(BasketScene *parent, const QString &extension, const QString &wantedName) 0854 { 0855 QString fileName; 0856 QString fullName; 0857 0858 if (wantedName.isEmpty()) { // TODO: fileNameForNewNote(parent, "note1."+extension); 0859 QDir dir; 0860 int nb = parent->count() + 1; 0861 QString time = QTime::currentTime().toString("hhmmss"); 0862 0863 for (;; ++nb) { 0864 fileName = QString("note%1-%2.%3").arg(nb).arg(time).arg(extension); 0865 fullName = parent->fullPath() + fileName; 0866 dir = QDir(fullName); 0867 if (!dir.exists(fullName)) 0868 break; 0869 } 0870 } else { 0871 fileName = fileNameForNewNote(parent, wantedName); 0872 fullName = parent->fullPath() + fileName; 0873 } 0874 0875 // Create the file 0876 // parent->dontCareOfCreation(fullName); 0877 QFile file(fullName); 0878 file.open(QIODevice::WriteOnly); 0879 file.close(); 0880 0881 return fileName; 0882 } 0883 0884 QUrl NoteFactory::filteredURL(const QUrl &url) 0885 { 0886 // KURIFilter::filteredURI() is slow if the URL contains only letters, digits and '-' or '+'. 0887 // So, we don't use that function is that case: 0888 bool isSlow = true; 0889 for (int i = 0; i < url.url().length(); ++i) { 0890 QChar c = url.url()[i]; 0891 if (!c.isLetterOrNumber() && c != '-' && c != '+') { 0892 isSlow = false; 0893 break; 0894 } 0895 } 0896 if (isSlow) 0897 return url; 0898 else { 0899 QStringList list; 0900 list << QLatin1String("kshorturifilter") << QLatin1String("kuriikwsfilter") << QLatin1String("kurisearchfilter") << QLatin1String("localdomainfilter") << QLatin1String("fixuphosturifilter"); 0901 return KUriFilter::self()->filteredUri(url, list); 0902 } 0903 } 0904 0905 QString NoteFactory::titleForURL(const QUrl &url) 0906 { 0907 QString title = url.toDisplayString(); 0908 QString home = "file:" + QDir::homePath() + '/'; 0909 0910 if (title.startsWith(QLatin1String("mailto:"))) 0911 return title.remove(0, 7); 0912 0913 if (title.startsWith(home)) 0914 title = "~/" + title.remove(0, home.length()); 0915 0916 if (title.startsWith(QLatin1String("file://"))) 0917 title = title.remove(0, 7); // 7 == QString("file://").length() - 1 0918 else if (title.startsWith(QLatin1String("file:"))) 0919 title = title.remove(0, 5); // 5 == QString("file:").length() - 1 0920 else if (title.startsWith(QLatin1String("http://www."))) 0921 title = title.remove(0, 11); // 11 == QString("http://www.").length() - 1 0922 else if (title.startsWith(QLatin1String("https://www."))) 0923 title = title.remove(0, 12); // 12 == QString("https://www.").length() - 1 0924 else if (title.startsWith(QLatin1String("http://"))) 0925 title = title.remove(0, 7); // 7 == QString("http://").length() - 1 0926 else if (title.startsWith(QLatin1String("https://"))) 0927 title = title.remove(0, 8); // 8 == QString("https://").length() - 1 0928 0929 if (!url.isLocalFile()) { 0930 if (title.endsWith(QLatin1String("/index.html")) && title.length() > 11) 0931 title.truncate(title.length() - 11); // 11 == QString("/index.html").length() 0932 else if (title.endsWith(QLatin1String("/index.htm")) && title.length() > 10) 0933 title.truncate(title.length() - 10); // 10 == QString("/index.htm").length() 0934 else if (title.endsWith(QLatin1String("/index.xhtml")) && title.length() > 12) 0935 title.truncate(title.length() - 12); // 12 == QString("/index.xhtml").length() 0936 else if (title.endsWith(QLatin1String("/index.php")) && title.length() > 10) 0937 title.truncate(title.length() - 10); // 10 == QString("/index.php").length() 0938 else if (title.endsWith(QLatin1String("/index.asp")) && title.length() > 10) 0939 title.truncate(title.length() - 10); // 10 == QString("/index.asp").length() 0940 else if (title.endsWith(QLatin1String("/index.php3")) && title.length() > 11) 0941 title.truncate(title.length() - 11); // 11 == QString("/index.php3").length() 0942 else if (title.endsWith(QLatin1String("/index.php4")) && title.length() > 11) 0943 title.truncate(title.length() - 11); // 11 == QString("/index.php4").length() 0944 else if (title.endsWith(QLatin1String("/index.php5")) && title.length() > 11) 0945 title.truncate(title.length() - 11); // 11 == QString("/index.php5").length() 0946 } 0947 0948 if (title.length() > 2 && title.endsWith('/')) // length > 2 because "/" and "~/" shouldn't be transformed to QString() and "~" 0949 title.truncate(title.length() - 1); // eg. transform "www.kde.org/" to "www.kde.org" 0950 0951 return title; 0952 } 0953 0954 QString NoteFactory::iconForURL(const QUrl &url) 0955 { 0956 0957 if (url.scheme() == "mailto") { 0958 return QStringLiteral("message"); 0959 } else if (url.scheme() == "http" || url.scheme() == "https") { 0960 return QStringLiteral("text-html"); 0961 } else { 0962 return QStringLiteral("unknown"); 0963 } 0964 } 0965 0966 // TODO: Can I add "autoTitle" and "autoIcon" entries to .desktop files? or just store them in basket, as now... 0967 0968 /* Try our better to find an icon suited to the command line 0969 * eg. "/usr/bin/kwrite-3.2 ~/myfile.txt /home/other/file.xml" 0970 * will give the "kwrite" icon! 0971 */ 0972 QString NoteFactory::iconForCommand(const QString &command) 0973 { 0974 QString icon; 0975 0976 // 1. Use first word as icon (typically the program without argument) 0977 icon = command.split(' ').first(); 0978 // 2. If the command is a full path, take only the program file name 0979 icon = icon.mid(icon.lastIndexOf('/') + 1); // strip path if given [But it doesn't care of such 0980 // "myprogram /my/path/argument" -> return "argument". Would 0981 // must first strip first word and then strip path... Useful ?? 0982 // 3. Use characters before any '-' (e.g. use "gimp" icon if run command is "gimp-1.3") 0983 if (!isIconExist(icon)) 0984 icon = icon.split('-').first(); 0985 // 4. If the icon still not findable, use a generic icon 0986 if (!isIconExist(icon)) 0987 icon = "exec"; 0988 0989 return icon; 0990 } 0991 0992 bool NoteFactory::isIconExist(const QString &icon) 0993 { 0994 return !KIconLoader::global()->loadIcon(icon, KIconLoader::NoGroup, 16, KIconLoader::DefaultState, QStringList(), nullptr, true).isNull(); 0995 } 0996 0997 Note *NoteFactory::createEmptyNote(NoteType::Id type, BasketScene *parent) 0998 { 0999 QPixmap *pixmap; 1000 switch (type) { 1001 case NoteType::Text: 1002 return NoteFactory::createNoteText(QString(), parent, /*reallyPlainText=*/true); 1003 case NoteType::Html: 1004 return NoteFactory::createNoteHtml(QString(), parent); 1005 case NoteType::Image: 1006 pixmap = new QPixmap(QSize(Settings::defImageX(), Settings::defImageY())); 1007 pixmap->fill(); 1008 pixmap->setMask(pixmap->createHeuristicMask()); 1009 return NoteFactory::createNoteImage(*pixmap, parent); 1010 case NoteType::Link: 1011 return NoteFactory::createNoteLink(QUrl(), parent); 1012 case NoteType::CrossReference: 1013 return NoteFactory::createNoteCrossReference(QUrl(), parent); 1014 case NoteType::Launcher: 1015 return NoteFactory::createNoteLauncher(QUrl(), parent); 1016 case NoteType::Color: 1017 return NoteFactory::createNoteColor(Qt::black, parent); 1018 default: 1019 case NoteType::Animation: 1020 case NoteType::Sound: 1021 case NoteType::File: 1022 case NoteType::Unknown: 1023 return nullptr; // Not possible! 1024 } 1025 } 1026 1027 Note *NoteFactory::importKMenuLauncher(BasketScene *parent) 1028 { 1029 QPointer<KOpenWithDialog> dialog = new KOpenWithDialog(parent->graphicsView()->viewport()); 1030 dialog->setSaveNewApplications(true); // To create temp file, needed by createNoteLauncher() 1031 dialog->exec(); 1032 if (dialog->service()) { 1033 // * locateLocal() return a local file even if it is a system wide one (local one doesn't exists) 1034 // * desktopEntryPath() returns the full path for system wide resources, but relative path if in home 1035 QString serviceFilePath = dialog->service()->entryPath(); 1036 if (!serviceFilePath.startsWith('/')) 1037 serviceFilePath = dialog->service()->locateLocal(); 1038 return createNoteLauncher(QUrl::fromUserInput(serviceFilePath), parent); 1039 } 1040 return nullptr; 1041 } 1042 1043 Note *NoteFactory::importIcon(BasketScene *parent) 1044 { 1045 QString iconName = KIconDialog::getIcon(KIconLoader::Desktop, KIconLoader::Application, false, Settings::defIconSize()); 1046 if (!iconName.isEmpty()) { 1047 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(), nullptr); 1048 dialog->exec(); 1049 if (dialog->iconSize() > 0) { 1050 Settings::setDefIconSize(dialog->iconSize()); 1051 Settings::saveConfig(); 1052 return createNoteImage(QIcon::fromTheme(iconName).pixmap(dialog->iconSize()), parent); // TODO: wantedName = iconName ! 1053 } 1054 } 1055 return nullptr; 1056 } 1057 1058 Note *NoteFactory::importFileContent(BasketScene *parent) 1059 { 1060 QUrl url = QFileDialog::getOpenFileUrl(parent->graphicsView(), i18n("Load File Content into a Note"), QUrl(), QString()); 1061 if (!url.isEmpty()) 1062 return copyFileAndLoad(url, parent); 1063 return nullptr; 1064 } 1065 1066 void NoteFactory::loadNode(const QDomElement &content, const QString &lowerTypeName, Note *parent, bool lazyLoad) 1067 { 1068 if (lowerTypeName == "text") { 1069 new TextContent(parent, content.text(), lazyLoad); 1070 } else if (lowerTypeName == "html") { 1071 new HtmlContent(parent, content.text(), lazyLoad); 1072 } else if (lowerTypeName == "image") { 1073 new ImageContent(parent, content.text(), lazyLoad); 1074 } else if (lowerTypeName == "animation") { 1075 new AnimationContent(parent, content.text(), lazyLoad); 1076 } else if (lowerTypeName == "sound") { 1077 new SoundContent(parent, content.text()); 1078 } else if (lowerTypeName == "file") { 1079 new FileContent(parent, content.text()); 1080 } else if (lowerTypeName == "link") { 1081 bool autoTitle = content.attribute("title") == content.text(); 1082 bool autoIcon = content.attribute("icon") == NoteFactory::iconForURL(QUrl::fromUserInput(content.text())); 1083 autoTitle = XMLWork::trueOrFalse(content.attribute("autoTitle"), autoTitle); 1084 autoIcon = XMLWork::trueOrFalse(content.attribute("autoIcon"), autoIcon); 1085 new LinkContent(parent, QUrl::fromUserInput(content.text()), content.attribute("title"), content.attribute("icon"), autoTitle, autoIcon); 1086 } else if (lowerTypeName == "cross_reference") { 1087 new CrossReferenceContent(parent, QUrl::fromUserInput(content.text()), content.attribute("title"), content.attribute("icon")); 1088 } else if (lowerTypeName == "launcher") { 1089 new LauncherContent(parent, content.text()); 1090 } else if (lowerTypeName == "color") { 1091 new ColorContent(parent, QColor(content.text())); 1092 } else if (lowerTypeName == "unknown") { 1093 new UnknownContent(parent, content.text()); 1094 } 1095 }