File indexing completed on 2022-09-27 16:29:21

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 "notecontent.h"
0008 
0009 #include <QLocale>
0010 #include <QMimeData>
0011 #include <QMimeDatabase>
0012 #include <QTextBlock>
0013 #include <QTextCodec>
0014 #include <QWidget>
0015 #include <QtCore/QBuffer>
0016 #include <QtCore/QDateTime>
0017 #include <QtCore/QDir>
0018 #include <QtCore/QFile>
0019 #include <QtCore/QFileInfo>
0020 #include <QtCore/QRegExp>
0021 #include <QtCore/QStringList>
0022 #include <QtGui/QAbstractTextDocumentLayout>
0023 #include <QtGui/QBitmap>                     //For QPixmap::createHeuristicMask()
0024 #include <QtGui/QFontMetrics>
0025 #include <QtGui/QMovie>
0026 #include <QtGui/QPainter>
0027 #include <QtGui/QPixmap>
0028 #include <QtNetwork/QNetworkReply>
0029 #include <QtXml/QDomElement>
0030 
0031 #include <KEncodingProber>
0032 #include <KFileItem>
0033 #include <KFileMetaData/KFileMetaData/Extractor>
0034 #include <KIO/PreviewJob> //For KIO::file_preview(...)
0035 #include <KLocalizedString>
0036 #include <KService>
0037 
0038 #include <phonon/AudioOutput>
0039 #include <phonon/MediaObject>
0040 
0041 #include "basketscene.h"
0042 #include "common.h"
0043 #include "config.h"
0044 #include "debugwindow.h"
0045 #include "file_metadata.h"
0046 #include "filter.h"
0047 #include "global.h"
0048 #include "htmlexporter.h"
0049 #include "note.h"
0050 #include "notefactory.h"
0051 #include "settings.h"
0052 #include "tools.h"
0053 #include "xmlwork.h"
0054 
0055 /**
0056  * LinkDisplayItem definition
0057  *
0058  */
0059 
0060 QRectF LinkDisplayItem::boundingRect() const
0061 {
0062     if (m_note) {
0063         return QRect(0, 0, m_note->width() - m_note->contentX() - Note::NOTE_MARGIN, m_note->height() - 2 * Note::NOTE_MARGIN);
0064     }
0065     return QRectF();
0066 }
0067 
0068 void LinkDisplayItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
0069 {
0070     if (!m_note)
0071         return;
0072 
0073     QRectF rect = boundingRect();
0074     m_linkDisplay.paint(painter, 0, 0, rect.width(), rect.height(), m_note->palette(), true, m_note->isSelected(), m_note->hovered(), m_note->hovered() && m_note->hoveredZone() == Note::Custom0);
0075 }
0076 
0077 //** NoteType functions
0078 QString NoteType::typeToName(const NoteType::Id noteType)
0079 {
0080     switch (noteType) {
0081     case NoteType::Group:
0082         return i18n("Group");
0083     case NoteType::Text:
0084         return i18n("Plain Text");
0085     case NoteType::Html:
0086         return i18n("Text");
0087     case NoteType::Image:
0088         return i18n("Image");
0089     case NoteType::Animation:
0090         return i18n("Animation");
0091     case NoteType::Sound:
0092         return i18n("Sound");
0093     case NoteType::File:
0094         return i18n("File");
0095     case NoteType::Link:
0096         return i18n("Link");
0097     case NoteType::CrossReference:
0098         return i18n("Cross Reference");
0099     case NoteType::Launcher:
0100         return i18n("Launcher");
0101     case NoteType::Color:
0102         return i18n("Color");
0103     case NoteType::Unknown:
0104         return i18n("Unknown");
0105     }
0106     return i18n("Unknown");
0107 }
0108 
0109 QString NoteType::typeToLowerName(const NoteType::Id noteType)
0110 {
0111     switch (noteType) {
0112     case NoteType::Group:
0113         return "group";
0114     case NoteType::Text:
0115         return "text";
0116     case NoteType::Html:
0117         return "html";
0118     case NoteType::Image:
0119         return "image";
0120     case NoteType::Animation:
0121         return "animation";
0122     case NoteType::Sound:
0123         return "sound";
0124     case NoteType::File:
0125         return "file";
0126     case NoteType::Link:
0127         return "link";
0128     case NoteType::CrossReference:
0129         return "cross_reference";
0130     case NoteType::Launcher:
0131         return "launcher";
0132     case NoteType::Color:
0133         return "color";
0134     case NoteType::Unknown:
0135         return "unknown";
0136     }
0137     return "unknown";
0138 }
0139 
0140 NoteType::Id NoteType::typeFromLowerName(const QString& lowerTypeName)
0141 {
0142     if (lowerTypeName == "group") {
0143         return NoteType::Group;
0144     } else if (lowerTypeName == "text") {
0145         return NoteType::Text;
0146     } else if (lowerTypeName == "html") {
0147         return NoteType::Html;
0148     } else if (lowerTypeName == "image") {
0149         return NoteType::Image;
0150     } else if (lowerTypeName == "animation") {
0151         return NoteType::Animation;
0152     } else if (lowerTypeName == "sound") {
0153         return NoteType::Sound;
0154     } else if (lowerTypeName == "file")  {
0155         return NoteType::File;
0156     } else if (lowerTypeName == "link") {
0157         return NoteType::Link;
0158     } else if (lowerTypeName == "cross_reference") {
0159         return NoteType::CrossReference;
0160     } else if (lowerTypeName == "launcher") {
0161         return NoteType::Launcher;
0162     } else if (lowerTypeName == "color") {
0163         return NoteType::Color;
0164     } else if (lowerTypeName == "unknown") {
0165         return NoteType::Unknown;
0166     }
0167     return NoteType::Unknown;
0168 }
0169 
0170 
0171 /** class NoteContent:
0172  */
0173 
0174 const int NoteContent::FEEDBACK_DARKING = 105;
0175 
0176 NoteContent::NoteContent(Note *parent, const NoteType::Id type, const QString &fileName)
0177     : m_type(type)
0178     , m_note(parent)
0179 {
0180     if (parent) {
0181         parent->setContent(this);
0182     }
0183     NoteContent::setFileName(fileName);
0184 }
0185 
0186 void NoteContent::saveToNode(QXmlStreamWriter &stream)
0187 {
0188     if (useFile()) {
0189         stream.writeStartElement("content");
0190         stream.writeCharacters(fileName());
0191         stream.writeEndElement();
0192     }
0193 }
0194 
0195 QRectF NoteContent::zoneRect(int zone, const QPointF & /*pos*/)
0196 {
0197     if (zone == Note::Content)
0198         return QRectF(0, 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect()
0199     else
0200         return QRectF();
0201 }
0202 
0203 QUrl NoteContent::urlToOpen(bool /*with*/)
0204 {
0205     return (useFile() ? QUrl::fromLocalFile(fullPath()) : QUrl());
0206 }
0207 
0208 void NoteContent::setFileName(const QString &fileName)
0209 {
0210     m_fileName = fileName;
0211 }
0212 
0213 bool NoteContent::trySetFileName(const QString &fileName)
0214 {
0215     if (useFile() && fileName != m_fileName) {
0216         QString newFileName = Tools::fileNameForNewFile(fileName, basket()->fullPath());
0217         QDir dir;
0218         dir.rename(fullPath(), basket()->fullPathForFileName(newFileName));
0219         return true;
0220     }
0221 
0222     return false; // !useFile() or unsuccessful rename
0223 }
0224 
0225 QString NoteContent::fullPath()
0226 {
0227     if (note() && useFile())
0228         return note()->fullPath();
0229     else
0230         return QString();
0231 }
0232 
0233 QUrl NoteContent::fullPathUrl()
0234 {
0235     return QUrl::fromLocalFile(fullPath());
0236 }
0237 
0238 void NoteContent::contentChanged(qreal newMinWidth)
0239 {
0240     m_minWidth = newMinWidth;
0241     if (note()) {
0242         //      note()->unbufferize();
0243         note()->requestRelayout(); // TODO: It should re-set the width!  m_width = 0 ?   contentChanged: setWidth, geteight, if size havent changed, only repaint and not relayout
0244     }
0245 }
0246 
0247 BasketScene *NoteContent::basket()
0248 {
0249     if (note())
0250         return note()->basket();
0251     else
0252         return nullptr;
0253 }
0254 
0255 void NoteContent::setEdited()
0256 {
0257     note()->setLastModificationDate(QDateTime::currentDateTime());
0258     basket()->save();
0259 }
0260 
0261 /** All the Content Classes:
0262  */
0263 
0264 QString NoteContent::toText(const QString &cuttedFullPath)
0265 {
0266     return (cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath);
0267 }
0268 
0269 QString TextContent::toText(const QString & /*cuttedFullPath*/)
0270 {
0271     return text();
0272 }
0273 QString HtmlContent::toText(const QString & /*cuttedFullPath*/)
0274 {
0275     return Tools::htmlToText(html());
0276 }
0277 QString LinkContent::toText(const QString & /*cuttedFullPath*/)
0278 {
0279     if (autoTitle())
0280         return url().toDisplayString();
0281     else if (title().isEmpty() && url().isEmpty())
0282         return QString();
0283     else if (url().isEmpty())
0284         return title();
0285     else if (title().isEmpty())
0286         return url().toDisplayString();
0287     else
0288         return QString("%1 <%2>").arg(title(), url().toDisplayString());
0289 }
0290 QString CrossReferenceContent::toText(const QString & /*cuttedFullPath*/)
0291 {
0292     if (title().isEmpty() && url().isEmpty())
0293         return QString();
0294     else if (url().isEmpty())
0295         return title();
0296     else if (title().isEmpty())
0297         return url().toDisplayString();
0298     else
0299         return QString("%1 <%2>").arg(title(), url().toDisplayString());
0300 }
0301 QString ColorContent::toText(const QString & /*cuttedFullPath*/)
0302 {
0303     return color().name();
0304 }
0305 QString UnknownContent::toText(const QString & /*cuttedFullPath*/)
0306 {
0307     return QString();
0308 }
0309 
0310 // TODO: If imageName.isEmpty() return fullPath() because it's for external use, else return fileName() because it's to display in a tooltip
0311 QString TextContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/)
0312 {
0313     return Tools::textToHTMLWithoutP(text());
0314 }
0315 
0316 QString HtmlContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/)
0317 {
0318     //extract HTML content exactly as is, with no further processing applied
0319     return m_graphicsTextItem.document()->toHtml("utf-8");
0320 }
0321 
0322 QString ImageContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath)
0323 {
0324     return QString("<img src=\"%1\">").arg(cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath);
0325 }
0326 
0327 QString AnimationContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath)
0328 {
0329     return QString("<img src=\"%1\">").arg(cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath);
0330 }
0331 
0332 QString SoundContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath)
0333 {
0334     return QString("<a href=\"%1\">%2</a>").arg((cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath), fileName());
0335 } // With the icon?
0336 
0337 QString FileContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath)
0338 {
0339     return QString("<a href=\"%1\">%2</a>").arg((cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath), fileName());
0340 } // With the icon?
0341 
0342 QString LinkContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/)
0343 {
0344     return QString("<a href=\"%1\">%2</a>").arg(url().toDisplayString(), title());
0345 } // With the icon?
0346 
0347 QString CrossReferenceContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/)
0348 {
0349     return QString("<a href=\"%1\">%2</a>").arg(url().toDisplayString(), title());
0350 } // With the icon?
0351 
0352 QString LauncherContent::toHtml(const QString & /*imageName*/, const QString &cuttedFullPath)
0353 {
0354     return QString("<a href=\"%1\">%2</a>").arg((cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath), name());
0355 } // With the icon?
0356 
0357 QString ColorContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/)
0358 {
0359     return QString("<span style=\"color: %1\">%2</span>").arg(color().name(), color().name());
0360 }
0361 
0362 QString UnknownContent::toHtml(const QString & /*imageName*/, const QString & /*cuttedFullPath*/)
0363 {
0364     return QString();
0365 }
0366 
0367 QPixmap ImageContent::toPixmap()
0368 {
0369     return pixmap();
0370 }
0371 QPixmap AnimationContent::toPixmap()
0372 {
0373     return m_movie->currentPixmap();
0374 }
0375 
0376 void NoteContent::toLink(QUrl *url, QString *title, const QString &cuttedFullPath)
0377 {
0378     *url = QUrl();
0379     title->clear();
0380 }
0381 
0382 void LinkContent::toLink(QUrl *url, QString *title, const QString & /*cuttedFullPath*/)
0383 {
0384     *url = this->url();
0385     *title = this->title();
0386 }
0387 
0388 void CrossReferenceContent::toLink(QUrl *url, QString *title, const QString & /*cuttedFullPath*/)
0389 {
0390     *url = this->url();
0391     *title = this->title();
0392 }
0393 
0394 void LauncherContent::toLink(QUrl *url, QString *title, const QString &cuttedFullPath)
0395 {
0396     *url = QUrl::fromUserInput(cuttedFullPath.isEmpty() ? fullPath() : cuttedFullPath);
0397     *title = name();
0398 }
0399 
0400 bool TextContent::useFile() const
0401 {
0402     return true;
0403 }
0404 bool HtmlContent::useFile() const
0405 {
0406     return true;
0407 }
0408 bool ImageContent::useFile() const
0409 {
0410     return true;
0411 }
0412 bool AnimationContent::useFile() const
0413 {
0414     return true;
0415 }
0416 bool SoundContent::useFile() const
0417 {
0418     return true;
0419 }
0420 bool FileContent::useFile() const
0421 {
0422     return true;
0423 }
0424 bool LinkContent::useFile() const
0425 {
0426     return false;
0427 }
0428 bool CrossReferenceContent::useFile() const
0429 {
0430     return false;
0431 }
0432 bool LauncherContent::useFile() const
0433 {
0434     return true;
0435 }
0436 bool ColorContent::useFile() const
0437 {
0438     return false;
0439 }
0440 bool UnknownContent::useFile() const
0441 {
0442     return true;
0443 }
0444 
0445 bool TextContent::canBeSavedAs() const
0446 {
0447     return true;
0448 }
0449 bool HtmlContent::canBeSavedAs() const
0450 {
0451     return true;
0452 }
0453 bool ImageContent::canBeSavedAs() const
0454 {
0455     return true;
0456 }
0457 bool AnimationContent::canBeSavedAs() const
0458 {
0459     return true;
0460 }
0461 bool SoundContent::canBeSavedAs() const
0462 {
0463     return true;
0464 }
0465 bool FileContent::canBeSavedAs() const
0466 {
0467     return true;
0468 }
0469 bool LinkContent::canBeSavedAs() const
0470 {
0471     return true;
0472 }
0473 bool CrossReferenceContent::canBeSavedAs() const
0474 {
0475     return true;
0476 }
0477 bool LauncherContent::canBeSavedAs() const
0478 {
0479     return true;
0480 }
0481 bool ColorContent::canBeSavedAs() const
0482 {
0483     return false;
0484 }
0485 bool UnknownContent::canBeSavedAs() const
0486 {
0487     return false;
0488 }
0489 
0490 QString TextContent::saveAsFilters() const
0491 {
0492     return "text/plain";
0493 }
0494 QString HtmlContent::saveAsFilters() const
0495 {
0496     return "text/html";
0497 }
0498 QString ImageContent::saveAsFilters() const
0499 {
0500     return "image/png";
0501 } // TODO: Offer more types
0502 QString AnimationContent::saveAsFilters() const
0503 {
0504     return "image/gif";
0505 } // TODO: MNG...
0506 QString SoundContent::saveAsFilters() const
0507 {
0508     return "audio/mp3 audio/ogg";
0509 } // TODO: OGG...
0510 QString FileContent::saveAsFilters() const
0511 {
0512     return "*";
0513 } // TODO: Get MIME type of the url target
0514 QString LinkContent::saveAsFilters() const
0515 {
0516     return "*";
0517 } // TODO: idem File + If isDir() const: return
0518 QString CrossReferenceContent::saveAsFilters() const
0519 {
0520     return "*";
0521 } // TODO: idem File + If isDir() const: return
0522 QString LauncherContent::saveAsFilters() const
0523 {
0524     return "application/x-desktop";
0525 }
0526 QString ColorContent::saveAsFilters() const
0527 {
0528     return QString();
0529 }
0530 QString UnknownContent::saveAsFilters() const
0531 {
0532     return QString();
0533 }
0534 
0535 bool TextContent::match(const FilterData &data)
0536 {
0537     return text().contains(data.string);
0538 }
0539 bool HtmlContent::match(const FilterData &data)
0540 {
0541     return m_textEquivalent.contains(data.string, Qt::CaseInsensitive);
0542 } // OPTIM_FILTER
0543 bool ImageContent::match(const FilterData & /*data*/)
0544 {
0545     return false;
0546 }
0547 bool AnimationContent::match(const FilterData & /*data*/)
0548 {
0549     return false;
0550 }
0551 bool SoundContent::match(const FilterData &data)
0552 {
0553     return fileName().contains(data.string);
0554 }
0555 bool FileContent::match(const FilterData &data)
0556 {
0557     return fileName().contains(data.string);
0558 }
0559 bool LinkContent::match(const FilterData &data)
0560 {
0561     return title().contains(data.string) || url().toDisplayString().contains(data.string);
0562 }
0563 bool CrossReferenceContent::match(const FilterData &data)
0564 {
0565     return title().contains(data.string) || url().toDisplayString().contains(data.string);
0566 }
0567 bool LauncherContent::match(const FilterData &data)
0568 {
0569     return exec().contains(data.string) || name().contains(data.string);
0570 }
0571 bool ColorContent::match(const FilterData &data)
0572 {
0573     return color().name().contains(data.string);
0574 }
0575 bool UnknownContent::match(const FilterData &data)
0576 {
0577     return mimeTypes().contains(data.string);
0578 }
0579 
0580 QString TextContent::editToolTipText() const
0581 {
0582     return i18n("Edit this plain text");
0583 }
0584 QString HtmlContent::editToolTipText() const
0585 {
0586     return i18n("Edit this text");
0587 }
0588 QString ImageContent::editToolTipText() const
0589 {
0590     return i18n("Edit this image");
0591 }
0592 QString AnimationContent::editToolTipText() const
0593 {
0594     return i18n("Edit this animation");
0595 }
0596 QString SoundContent::editToolTipText() const
0597 {
0598     return i18n("Edit the file name of this sound");
0599 }
0600 QString FileContent::editToolTipText() const
0601 {
0602     return i18n("Edit the name of this file");
0603 }
0604 QString LinkContent::editToolTipText() const
0605 {
0606     return i18n("Edit this link");
0607 }
0608 QString CrossReferenceContent::editToolTipText() const
0609 {
0610     return i18n("Edit this cross reference");
0611 }
0612 QString LauncherContent::editToolTipText() const
0613 {
0614     return i18n("Edit this launcher");
0615 }
0616 QString ColorContent::editToolTipText() const
0617 {
0618     return i18n("Edit this color");
0619 }
0620 QString UnknownContent::editToolTipText() const
0621 {
0622     return i18n("Edit this unknown object");
0623 }
0624 
0625 QString TextContent::cssClass() const
0626 {
0627     return QString();
0628 }
0629 QString HtmlContent::cssClass() const
0630 {
0631     return QString();
0632 }
0633 QString ImageContent::cssClass() const
0634 {
0635     return QString();
0636 }
0637 QString AnimationContent::cssClass() const
0638 {
0639     return QString();
0640 }
0641 QString SoundContent::cssClass() const
0642 {
0643     return "sound";
0644 }
0645 QString FileContent::cssClass() const
0646 {
0647     return "file";
0648 }
0649 QString LinkContent::cssClass() const
0650 {
0651     return (LinkLook::lookForURL(m_url) == LinkLook::localLinkLook ? "local" : "network");
0652 }
0653 QString CrossReferenceContent::cssClass() const
0654 {
0655     return "cross_reference";
0656 }
0657 QString LauncherContent::cssClass() const
0658 {
0659     return "launcher";
0660 }
0661 QString ColorContent::cssClass() const
0662 {
0663     return QString();
0664 }
0665 QString UnknownContent::cssClass() const
0666 {
0667     return QString();
0668 }
0669 
0670 void TextContent::fontChanged()
0671 {
0672     setText(text());
0673 }
0674 void HtmlContent::fontChanged()
0675 {
0676     QTextDocument *richDoc = m_graphicsTextItem.document();
0677     // This check is important when applying style to a note which is not loaded yet. Example:
0678     // Filter all -> open some basket for the first time -> close filter: if a note was tagged as TODO, then it would display no text
0679     if (!richDoc->isEmpty())
0680         setHtml(Tools::textDocumentToMinimalHTML(richDoc));
0681 }
0682 void ImageContent::fontChanged()
0683 {
0684     setPixmap(pixmap());
0685 }
0686 void AnimationContent::fontChanged()
0687 {
0688     /*startMovie();*/
0689 }
0690 void FileContent::fontChanged()
0691 {
0692     setFileName(fileName());
0693 }
0694 void LinkContent::fontChanged()
0695 {
0696     setLink(url(), title(), icon(), autoTitle(), autoIcon());
0697 }
0698 void CrossReferenceContent::fontChanged()
0699 {
0700     setCrossReference(url(), title(), icon());
0701 }
0702 void LauncherContent::fontChanged()
0703 {
0704     setLauncher(name(), icon(), exec());
0705 }
0706 void ColorContent::fontChanged()
0707 {
0708     setColor(color());
0709 }
0710 void UnknownContent::fontChanged()
0711 {
0712     loadFromFile(/*lazyLoad=*/false);
0713 } // TODO: Optimize: setMimeTypes()
0714 
0715 // QString TextContent::customOpenCommand()      { return (Settings::isTextUseProg()      && ! Settings::textProg().isEmpty()      ? Settings::textProg()      : QString()); }
0716 QString HtmlContent::customOpenCommand()
0717 {
0718     return (Settings::isHtmlUseProg() && !Settings::htmlProg().isEmpty() ? Settings::htmlProg() : QString());
0719 }
0720 QString ImageContent::customOpenCommand()
0721 {
0722     return (Settings::isImageUseProg() && !Settings::imageProg().isEmpty() ? Settings::imageProg() : QString());
0723 }
0724 QString AnimationContent::customOpenCommand()
0725 {
0726     return (Settings::isAnimationUseProg() && !Settings::animationProg().isEmpty() ? Settings::animationProg() : QString());
0727 }
0728 QString SoundContent::customOpenCommand()
0729 {
0730     return (Settings::isSoundUseProg() && !Settings::soundProg().isEmpty() ? Settings::soundProg() : QString());
0731 }
0732 
0733 void LinkContent::serialize(QDataStream &stream)
0734 {
0735     stream << url() << title() << icon() << (quint64)autoTitle() << (quint64)autoIcon();
0736 }
0737 void CrossReferenceContent::serialize(QDataStream &stream)
0738 {
0739     stream << url() << title() << icon();
0740 }
0741 void ColorContent::serialize(QDataStream &stream)
0742 {
0743     stream << color();
0744 }
0745 
0746 QPixmap TextContent::feedbackPixmap(qreal width, qreal height)
0747 {
0748     QRectF textRect = QFontMetrics(note()->font()).boundingRect(0, 0, width, height, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, text());
0749     QPixmap pixmap(qMin(width, textRect.width()), qMin(height, textRect.height()));
0750     pixmap.fill(note()->backgroundColor().darker(FEEDBACK_DARKING));
0751     QPainter painter(&pixmap);
0752     painter.setPen(note()->textColor());
0753     painter.setFont(note()->font());
0754     painter.drawText(0, 0, pixmap.width(), pixmap.height(), Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, text());
0755     painter.end();
0756 
0757     return pixmap;
0758 }
0759 
0760 QPixmap HtmlContent::feedbackPixmap(qreal width, qreal height)
0761 {
0762     QTextDocument richText;
0763     richText.setHtml(html());
0764     richText.setDefaultFont(note()->font());
0765     richText.setTextWidth(width);
0766     QPalette palette;
0767     palette = basket()->palette();
0768     palette.setColor(QPalette::Text, note()->textColor());
0769     palette.setColor(QPalette::Background, note()->backgroundColor().darker(FEEDBACK_DARKING));
0770     QPixmap pixmap(qMin(width, richText.idealWidth()), qMin(height, richText.size().height()));
0771     pixmap.fill(note()->backgroundColor().darker(FEEDBACK_DARKING));
0772     QPainter painter(&pixmap);
0773     painter.setPen(note()->textColor());
0774     painter.translate(0, 0);
0775     richText.drawContents(&painter, QRectF(0, 0, pixmap.width(), pixmap.height()));
0776     painter.end();
0777 
0778     return pixmap;
0779 }
0780 
0781 QPixmap ImageContent::feedbackPixmap(qreal width, qreal height)
0782 {
0783     if (width >= m_pixmapItem.pixmap().width() && height >= m_pixmapItem.pixmap().height()) { // Full size
0784         if (m_pixmapItem.pixmap().hasAlpha()) {
0785             QPixmap opaque(m_pixmapItem.pixmap().width(), m_pixmapItem.pixmap().height());
0786             opaque.fill(note()->backgroundColor().darker(FEEDBACK_DARKING));
0787             QPainter painter(&opaque);
0788             painter.drawPixmap(0, 0, m_pixmapItem.pixmap());
0789             painter.end();
0790             return opaque;
0791         } else {
0792             return m_pixmapItem.pixmap();
0793         }
0794     } else { // Scaled down
0795         QImage imageToScale = m_pixmapItem.pixmap().toImage();
0796         QPixmap pmScaled;
0797         pmScaled = QPixmap::fromImage(imageToScale.scaled(width, height, Qt::KeepAspectRatio));
0798         if (pmScaled.hasAlpha()) {
0799             QPixmap opaque(pmScaled.width(), pmScaled.height());
0800             opaque.fill(note()->backgroundColor().darker(FEEDBACK_DARKING));
0801             QPainter painter(&opaque);
0802             painter.drawPixmap(0, 0, pmScaled);
0803             painter.end();
0804             return opaque;
0805         } else {
0806             return pmScaled;
0807         }
0808     }
0809 }
0810 
0811 QPixmap AnimationContent::feedbackPixmap(qreal width, qreal height)
0812 {
0813     QPixmap pixmap = m_movie->currentPixmap();
0814     if (width >= pixmap.width() && height >= pixmap.height()) // Full size
0815         return pixmap;
0816     else { // Scaled down
0817         QImage imageToScale = pixmap.toImage();
0818         QPixmap pmScaled;
0819         pmScaled = QPixmap::fromImage(imageToScale.scaled(width, height, Qt::KeepAspectRatio));
0820         return pmScaled;
0821     }
0822 }
0823 
0824 QPixmap LinkContent::feedbackPixmap(qreal width, qreal height)
0825 {
0826     QPalette palette;
0827     palette = basket()->palette();
0828     palette.setColor(QPalette::WindowText, note()->textColor());
0829     palette.setColor(QPalette::Background, note()->backgroundColor().darker(FEEDBACK_DARKING));
0830     return m_linkDisplayItem.linkDisplay().feedbackPixmap(width, height, palette, /*isDefaultColor=*/note()->textColor() == basket()->textColor());
0831 }
0832 
0833 QPixmap CrossReferenceContent::feedbackPixmap(qreal width, qreal height)
0834 {
0835     QPalette palette;
0836     palette = basket()->palette();
0837     palette.setColor(QPalette::WindowText, note()->textColor());
0838     palette.setColor(QPalette::Background, note()->backgroundColor().darker(FEEDBACK_DARKING));
0839     return m_linkDisplayItem.linkDisplay().feedbackPixmap(width, height, palette, /*isDefaultColor=*/note()->textColor() == basket()->textColor());
0840 }
0841 
0842 QPixmap ColorContent::feedbackPixmap(qreal width, qreal height)
0843 {
0844     // TODO: Duplicate code: make a rect() method!
0845     QRectF boundingRect = m_colorItem.boundingRect();
0846 
0847     QPalette palette;
0848     palette = basket()->palette();
0849     palette.setColor(QPalette::WindowText, note()->textColor());
0850     palette.setColor(QPalette::Background, note()->backgroundColor().darker(FEEDBACK_DARKING));
0851 
0852     QPixmap pixmap(qMin(width, boundingRect.width()), qMin(height, boundingRect.height()));
0853     pixmap.fill(note()->backgroundColor().darker(FEEDBACK_DARKING));
0854     QPainter painter(&pixmap);
0855     m_colorItem.paint(&painter, nullptr, nullptr); //, pixmap.width(), pixmap.height(), palette, false, false, false); // We don't care of the three last boolean parameters.
0856     painter.end();
0857 
0858     return pixmap;
0859 }
0860 
0861 QPixmap FileContent::feedbackPixmap(qreal width, qreal height)
0862 {
0863     QPalette palette;
0864     palette = basket()->palette();
0865     palette.setColor(QPalette::WindowText, note()->textColor());
0866     palette.setColor(QPalette::Background, note()->backgroundColor().darker(FEEDBACK_DARKING));
0867     return m_linkDisplayItem.linkDisplay().feedbackPixmap(width, height, palette, /*isDefaultColor=*/note()->textColor() == basket()->textColor());
0868 }
0869 
0870 QPixmap LauncherContent::feedbackPixmap(qreal width, qreal height)
0871 {
0872     QPalette palette;
0873     palette = basket()->palette();
0874     palette.setColor(QPalette::WindowText, note()->textColor());
0875     palette.setColor(QPalette::Background, note()->backgroundColor().darker(FEEDBACK_DARKING));
0876     return m_linkDisplayItem.linkDisplay().feedbackPixmap(width, height, palette, /*isDefaultColor=*/note()->textColor() == basket()->textColor());
0877 }
0878 
0879 QPixmap UnknownContent::feedbackPixmap(qreal width, qreal height)
0880 {
0881     QRectF boundingRect = m_unknownItem.boundingRect();
0882 
0883     QPalette palette;
0884     palette = basket()->palette();
0885     palette.setColor(QPalette::WindowText, note()->textColor());
0886     palette.setColor(QPalette::Background, note()->backgroundColor().darker(FEEDBACK_DARKING));
0887 
0888     QPixmap pixmap(qMin(width, boundingRect.width()), qMin(height, boundingRect.height()));
0889     QPainter painter(&pixmap);
0890     m_unknownItem.paint(&painter, nullptr, nullptr); //, pixmap.width() + 1, pixmap.height(), palette, false, false, false); // We don't care of the three last boolean parameters.
0891     painter.setPen(note()->backgroundColor().darker(FEEDBACK_DARKING));
0892     painter.drawPoint(0, 0);
0893     painter.drawPoint(pixmap.width() - 1, 0);
0894     painter.drawPoint(0, pixmap.height() - 1);
0895     painter.drawPoint(pixmap.width() - 1, pixmap.height() - 1);
0896     painter.end();
0897 
0898     return pixmap;
0899 }
0900 
0901 /** class TextContent:
0902  */
0903 
0904 TextContent::TextContent(Note *parent, const QString &fileName, bool lazyLoad)
0905     : NoteContent(parent, NoteType::Text, fileName)
0906     , m_graphicsTextItem(parent)
0907 {
0908     if (parent) {
0909         parent->addToGroup(&m_graphicsTextItem);
0910         m_graphicsTextItem.setPos(parent->contentX(), Note::NOTE_MARGIN);
0911     }
0912 
0913     basket()->addWatchedFile(fullPath());
0914     TextContent::loadFromFile(lazyLoad);
0915 }
0916 
0917 TextContent::~TextContent()
0918 {
0919     if (note())
0920         note()->removeFromGroup(&m_graphicsTextItem);
0921 }
0922 
0923 qreal TextContent::setWidthAndGetHeight(qreal /*width*/)
0924 {
0925     return m_graphicsTextItem.boundingRect().height();
0926 }
0927 
0928 bool TextContent::loadFromFile(bool lazyLoad)
0929 {
0930     DEBUG_WIN << "Loading TextContent From " + basket()->folderName() + fileName();
0931 
0932     QString content;
0933     bool success = FileStorage::loadFromFile(fullPath(), &content);
0934 
0935     if (success)
0936         setText(content, lazyLoad);
0937     else {
0938         qDebug() << "FAILED TO LOAD TextContent: " << fullPath();
0939         setText(QString(), lazyLoad);
0940         if (!QFile::exists(fullPath()))
0941             TextContent::saveToFile(); // Reserve the fileName so no new note will have the same name!
0942     }
0943     return success;
0944 }
0945 
0946 bool TextContent::finishLazyLoad()
0947 {
0948     m_graphicsTextItem.setFont(note()->font());
0949     contentChanged(m_graphicsTextItem.boundingRect().width() + 1);
0950     return true;
0951 }
0952 
0953 bool TextContent::saveToFile()
0954 {
0955     return FileStorage::saveToFile(fullPath(), text());
0956 }
0957 
0958 QString TextContent::linkAt(const QPointF & /*pos*/)
0959 {
0960     return QString();
0961 }
0962 
0963 QString TextContent::messageWhenOpening(OpenMessage where)
0964 {
0965     switch (where) {
0966     case OpenOne:
0967         return i18n("Opening plain text...");
0968     case OpenSeveral:
0969         return i18n("Opening plain texts...");
0970     case OpenOneWith:
0971         return i18n("Opening plain text with...");
0972     case OpenSeveralWith:
0973         return i18n("Opening plain texts with...");
0974     case OpenOneWithDialog:
0975         return i18n("Open plain text with:");
0976     case OpenSeveralWithDialog:
0977         return i18n("Open plain texts with:");
0978     default:
0979         return QString();
0980     }
0981 }
0982 
0983 void TextContent::setText(const QString &text, bool lazyLoad)
0984 {
0985     m_graphicsTextItem.setText(text);
0986     if (!lazyLoad)
0987         TextContent::finishLazyLoad();
0988     else
0989         contentChanged(m_graphicsTextItem.boundingRect().width());
0990 }
0991 
0992 void TextContent::exportToHTML(HTMLExporter *exporter, int indent)
0993 {
0994     QString spaces;
0995     QString html = "<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\"><meta name=\"qrichtext\" content=\"1\" /></head><body>" +
0996             Tools::detectCrossReferences(
0997                     Tools::detectURLs(Tools::textToHTMLWithoutP(text().replace(QChar('\t'), "                "))),
0998                     false, exporter); // Don't collapse multiple spaces!
0999     exporter->stream << html.replace("  ", " &nbsp;").replace(QChar('\n'), '\n' + spaces.fill(' ', indent + 1));
1000 }
1001 
1002 /** class HtmlContent:
1003  */
1004 
1005 HtmlContent::HtmlContent(Note *parent, const QString &fileName, bool lazyLoad)
1006     : NoteContent(parent, NoteType::Html, fileName)
1007     , m_graphicsTextItem(parent)
1008 {
1009     if (parent) {
1010         parent->addToGroup(&m_graphicsTextItem);
1011         m_graphicsTextItem.setPos(parent->contentX(), Note::NOTE_MARGIN);
1012     }
1013     basket()->addWatchedFile(fullPath());
1014     HtmlContent::loadFromFile(lazyLoad);
1015 }
1016 
1017 HtmlContent::~HtmlContent()
1018 {
1019     if (note())
1020         note()->removeFromGroup(&m_graphicsTextItem);
1021 }
1022 
1023 qreal HtmlContent::setWidthAndGetHeight(qreal width)
1024 {
1025     width -= 1;
1026     m_graphicsTextItem.setTextWidth(width);
1027     return m_graphicsTextItem.boundingRect().height();
1028 }
1029 
1030 bool HtmlContent::loadFromFile(bool lazyLoad)
1031 {
1032     DEBUG_WIN << "Loading HtmlContent From " + basket()->folderName() + fileName();
1033 
1034     QString content;
1035     bool success = FileStorage::loadFromFile(fullPath(), &content);
1036 
1037     if (success)
1038         setHtml(content, lazyLoad);
1039     else {
1040         setHtml(QString(), lazyLoad);
1041         if (!QFile::exists(fullPath()))
1042             HtmlContent::saveToFile(); // Reserve the fileName so no new note will have the same name!
1043     }
1044     return success;
1045 }
1046 
1047 bool HtmlContent::finishLazyLoad()
1048 {
1049     qreal width = m_graphicsTextItem.document()->idealWidth();
1050 
1051     m_graphicsTextItem.setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsFocusable);
1052     m_graphicsTextItem.setTextInteractionFlags(Qt::TextEditorInteraction);
1053 
1054     /*QString css = ".cross_reference { display: block; width: 100%; text-decoration: none; color: #336600; }"
1055        "a:hover.cross_reference { text-decoration: underline; color: #ff8000; }";
1056     m_graphicsTextItem.document()->setDefaultStyleSheet(css);*/
1057     QString convert = Tools::detectURLs(m_html);
1058     if (note()->allowCrossReferences())
1059         convert = Tools::detectCrossReferences(convert);
1060     m_graphicsTextItem.setHtml(convert);
1061     m_graphicsTextItem.setDefaultTextColor(note()->textColor());
1062     m_graphicsTextItem.setFont(note()->font());
1063     m_graphicsTextItem.setTextWidth(1); // We put a width of 1 pixel, so usedWidth() is equal to the minimum width
1064     int minWidth = m_graphicsTextItem.document()->idealWidth();
1065     m_graphicsTextItem.setTextWidth(width);
1066     contentChanged(minWidth + 1);
1067 
1068     return true;
1069 }
1070 
1071 bool HtmlContent::saveToFile()
1072 {
1073     return FileStorage::saveToFile(fullPath(), html());
1074 }
1075 
1076 QString HtmlContent::linkAt(const QPointF &pos)
1077 {
1078     return m_graphicsTextItem.document()->documentLayout()->anchorAt(pos);
1079 }
1080 
1081 QString HtmlContent::messageWhenOpening(OpenMessage where)
1082 {
1083     switch (where) {
1084     case OpenOne:
1085         return i18n("Opening text...");
1086     case OpenSeveral:
1087         return i18n("Opening texts...");
1088     case OpenOneWith:
1089         return i18n("Opening text with...");
1090     case OpenSeveralWith:
1091         return i18n("Opening texts with...");
1092     case OpenOneWithDialog:
1093         return i18n("Open text with:");
1094     case OpenSeveralWithDialog:
1095         return i18n("Open texts with:");
1096     default:
1097         return QString();
1098     }
1099 }
1100 
1101 void HtmlContent::setHtml(const QString &html, bool lazyLoad)
1102 {
1103     m_html = html;
1104     /* The code was commented, so now non-Latin text is stored directly in Unicode.
1105      * If testing doesn't show any bugs, this block should be deleted
1106     QRegExp rx("([^\\x00-\\x7f])");
1107     while (m_html.contains(rx)) {
1108         m_html.replace( rx.cap().unicode()[0], QString("&#%1;").arg(rx.cap().unicode()[0].unicode()) );
1109     }*/
1110     m_textEquivalent = HtmlContent::toText(QString()); // OPTIM_FILTER
1111     if (!lazyLoad)
1112         HtmlContent::finishLazyLoad();
1113     else
1114         contentChanged(10);
1115 }
1116 
1117 void HtmlContent::exportToHTML(HTMLExporter *exporter, int indent)
1118 {
1119     QString spaces;
1120     QString convert = Tools::detectURLs(html().replace("\t", "                "));
1121     if (note()->allowCrossReferences())
1122         convert = Tools::detectCrossReferences(convert, false, exporter);
1123 
1124     exporter->stream << Tools::htmlToParagraph(convert).replace("  ", " &nbsp;").replace("\n", '\n' + spaces.fill(' ', indent + 1));
1125 }
1126 
1127 /** class ImageContent:
1128  */
1129 
1130 ImageContent::ImageContent(Note *parent, const QString &fileName, bool lazyLoad)
1131     : NoteContent(parent, NoteType::Image, fileName)
1132     , m_pixmapItem(parent)
1133     , m_format()
1134 {
1135     if (parent) {
1136         parent->addToGroup(&m_pixmapItem);
1137         m_pixmapItem.setPos(parent->contentX(), Note::NOTE_MARGIN);
1138     }
1139 
1140     basket()->addWatchedFile(fullPath());
1141     ImageContent::loadFromFile(lazyLoad);
1142 }
1143 
1144 ImageContent::~ImageContent()
1145 {
1146     if (note())
1147         note()->removeFromGroup(&m_pixmapItem);
1148 }
1149 
1150 qreal ImageContent::setWidthAndGetHeight(qreal width)
1151 {
1152     width -= 1;
1153     // Don't store width: we will get it on paint!
1154     if (width >= m_pixmapItem.pixmap().width()) // Full size
1155     {
1156         m_pixmapItem.setScale(1.0);
1157         return m_pixmapItem.boundingRect().height();
1158     } else { // Scaled down
1159         qreal scaleFactor = width / m_pixmapItem.pixmap().width();
1160         m_pixmapItem.setScale(scaleFactor);
1161         return m_pixmapItem.boundingRect().height() * scaleFactor;
1162     }
1163 }
1164 
1165 bool ImageContent::loadFromFile(bool lazyLoad)
1166 {
1167     if (lazyLoad)
1168         return true;
1169     else
1170         return ImageContent::finishLazyLoad();
1171 }
1172 
1173 bool ImageContent::finishLazyLoad()
1174 {
1175     DEBUG_WIN << "Loading ImageContent From " + basket()->folderName() + fileName();
1176 
1177     QByteArray content;
1178     QPixmap pixmap;
1179 
1180     if (FileStorage::loadFromFile(fullPath(), &content)) {
1181         QBuffer buffer(&content);
1182 
1183         buffer.open(QIODevice::ReadOnly);
1184         m_format = QImageReader::imageFormat(&buffer); // See QImageIO to know what formats can be supported.
1185         buffer.close();
1186         if (!m_format.isNull()) {
1187             pixmap.loadFromData(content);
1188             setPixmap(pixmap);
1189             return true;
1190         }
1191     }
1192 
1193     qDebug() << "FAILED TO LOAD ImageContent: " << fullPath();
1194     m_format = "PNG";       // If the image is set later, it should be saved without destruction, so we use PNG by default.
1195     pixmap = QPixmap(1, 1); // Create a 1x1 pixels image instead of an undefined one.
1196     pixmap.fill();
1197     pixmap.setMask(pixmap.createHeuristicMask());
1198     setPixmap(pixmap);
1199     if (!QFile::exists(fullPath()))
1200         ImageContent::saveToFile(); // Reserve the fileName so no new note will have the same name!
1201     return false;
1202 }
1203 
1204 bool ImageContent::saveToFile()
1205 {
1206     QByteArray ba;
1207     QBuffer buffer(&ba);
1208 
1209     buffer.open(QIODevice::WriteOnly);
1210     m_pixmapItem.pixmap().save(&buffer, m_format);
1211     return FileStorage::saveToFile(fullPath(), ba);
1212 }
1213 
1214 QMap<QString, QString> ImageContent::toolTipInfos()
1215 {
1216     return {
1217         {i18n("Size"), i18n("%1 by %2 pixels", QString::number(m_pixmapItem.pixmap().width()),
1218                                                QString::number(m_pixmapItem.pixmap().height()))}
1219     };
1220 }
1221 
1222 QString ImageContent::messageWhenOpening(OpenMessage where)
1223 {
1224     switch (where) {
1225     case OpenOne:
1226         return i18n("Opening image...");
1227     case OpenSeveral:
1228         return i18n("Opening images...");
1229     case OpenOneWith:
1230         return i18n("Opening image with...");
1231     case OpenSeveralWith:
1232         return i18n("Opening images with...");
1233     case OpenOneWithDialog:
1234         return i18n("Open image with:");
1235     case OpenSeveralWithDialog:
1236         return i18n("Open images with:");
1237     default:
1238         return QString();
1239     }
1240 }
1241 
1242 void ImageContent::setPixmap(const QPixmap &pixmap)
1243 {
1244     m_pixmapItem.setPixmap(pixmap);
1245     // Since it's scaled, the height is always greater or equal to the size of the tag emblems (16)
1246     contentChanged(16 + 1); // TODO: always good? I don't think...
1247 }
1248 
1249 void ImageContent::exportToHTML(HTMLExporter *exporter, int /*indent*/)
1250 {
1251     qreal width = m_pixmapItem.pixmap().width();
1252     qreal height = m_pixmapItem.pixmap().height();
1253     qreal contentWidth = note()->width() - note()->contentX() - 1 - Note::NOTE_MARGIN;
1254 
1255     QString imageName = exporter->copyFile(fullPath(), /*createIt=*/true);
1256 
1257     if (contentWidth <= m_pixmapItem.pixmap().width()) { // Scaled down
1258         qreal scale = contentWidth / m_pixmapItem.pixmap().width();
1259         width = m_pixmapItem.pixmap().width() * scale;
1260         height = m_pixmapItem.pixmap().height() * scale;
1261         exporter->stream << "<a href=\"" << exporter->dataFolderName << imageName << "\" title=\"" << i18n("Click for full size view") << "\">";
1262     }
1263 
1264     exporter->stream << "<img src=\"" << exporter->dataFolderName << imageName << "\" width=\"" << width << "\" height=\"" << height << "\" alt=\"\">";
1265 
1266     if (contentWidth <= m_pixmapItem.pixmap().width()) // Scaled down
1267         exporter->stream << "</a>";
1268 }
1269 
1270 /** class AnimationContent:
1271  */
1272 
1273 AnimationContent::AnimationContent(Note *parent, const QString &fileName, bool lazyLoad)
1274     : NoteContent(parent, NoteType::Animation, fileName)
1275     , m_buffer(new QBuffer(this))
1276     , m_movie(new QMovie(this))
1277     , m_currentWidth(0)
1278     , m_graphicsPixmap(parent)
1279 {
1280     if (parent) {
1281         parent->addToGroup(&m_graphicsPixmap);
1282         m_graphicsPixmap.setPos(parent->contentX(), Note::NOTE_MARGIN);
1283         connect(parent->basket(), SIGNAL(activated()), m_movie, SLOT(start()));
1284         connect(parent->basket(), SIGNAL(closed()), m_movie, SLOT(stop()));
1285     }
1286 
1287     basket()->addWatchedFile(fullPath());
1288     connect(m_movie, SIGNAL(resized(QSize)), this, SLOT(movieResized()));
1289     connect(m_movie, SIGNAL(frameChanged(int)), this, SLOT(movieFrameChanged()));
1290 
1291     AnimationContent::loadFromFile(lazyLoad);
1292 }
1293 
1294 AnimationContent::~AnimationContent()
1295 {
1296     note()->removeFromGroup(&m_graphicsPixmap);
1297 }
1298 
1299 qreal AnimationContent::setWidthAndGetHeight(qreal width)
1300 {
1301     m_currentWidth = width;
1302     QPixmap pixmap = m_graphicsPixmap.pixmap();
1303     if (pixmap.width() > m_currentWidth) {
1304         qreal scaleFactor = m_currentWidth / pixmap.width();
1305         m_graphicsPixmap.setScale(scaleFactor);
1306         return pixmap.height() * scaleFactor;
1307     } else {
1308         m_graphicsPixmap.setScale(1.0);
1309         return pixmap.height();
1310     }
1311 
1312     return 0;
1313 }
1314 
1315 bool AnimationContent::loadFromFile(bool lazyLoad)
1316 {
1317     if (lazyLoad)
1318         return true;
1319     else
1320         return AnimationContent::finishLazyLoad();
1321 }
1322 
1323 bool AnimationContent::finishLazyLoad()
1324 {
1325     QByteArray content;
1326     if (FileStorage::loadFromFile(fullPath(), &content)) {
1327         m_buffer->setData(content);
1328         startMovie();
1329         contentChanged(16);
1330         return true;
1331     }
1332     m_buffer->setData(nullptr);
1333     return false;
1334 }
1335 
1336 bool AnimationContent::saveToFile()
1337 {
1338     // Impossible!
1339     return false;
1340 }
1341 
1342 QString AnimationContent::messageWhenOpening(OpenMessage where)
1343 {
1344     switch (where) {
1345     case OpenOne:
1346         return i18n("Opening animation...");
1347     case OpenSeveral:
1348         return i18n("Opening animations...");
1349     case OpenOneWith:
1350         return i18n("Opening animation with...");
1351     case OpenSeveralWith:
1352         return i18n("Opening animations with...");
1353     case OpenOneWithDialog:
1354         return i18n("Open animation with:");
1355     case OpenSeveralWithDialog:
1356         return i18n("Open animations with:");
1357     default:
1358         return QString();
1359     }
1360 }
1361 
1362 bool AnimationContent::startMovie()
1363 {
1364     if (m_buffer->data().isEmpty())
1365         return false;
1366     m_movie->setDevice(m_buffer);
1367     m_movie->start();
1368     return true;
1369 }
1370 
1371 void AnimationContent::movieUpdated()
1372 {
1373     m_graphicsPixmap.setPixmap(m_movie->currentPixmap());
1374 }
1375 
1376 void AnimationContent::movieResized()
1377 {
1378     m_graphicsPixmap.setPixmap(m_movie->currentPixmap());
1379 }
1380 
1381 void AnimationContent::movieFrameChanged()
1382 {
1383     m_graphicsPixmap.setPixmap(m_movie->currentPixmap());
1384 }
1385 
1386 void AnimationContent::exportToHTML(HTMLExporter *exporter, int /*indent*/)
1387 {
1388     exporter->stream << QString("<img src=\"%1\" width=\"%2\" height=\"%3\" alt=\"\">")
1389                             .arg(exporter->dataFolderName + exporter->copyFile(fullPath(), /*createIt=*/true), QString::number(m_movie->currentPixmap().size().width()), QString::number(m_movie->currentPixmap().size().height()));
1390 }
1391 
1392 /** class FileContent:
1393  */
1394 
1395 FileContent::FileContent(Note *parent, const QString &fileName)
1396     : NoteContent(parent, NoteType::File, fileName)
1397     , m_linkDisplayItem(parent)
1398     , m_previewJob(nullptr)
1399 {
1400     basket()->addWatchedFile(fullPath());
1401     FileContent::setFileName(fileName); // FIXME: TO THAT HERE BECAUSE NoteContent() constructor seems to don't be able to call virtual methods???
1402     if (parent) {
1403         parent->addToGroup(&m_linkDisplayItem);
1404         m_linkDisplayItem.setPos(parent->contentX(), Note::NOTE_MARGIN);
1405     }
1406 }
1407 
1408 FileContent::~FileContent()
1409 {
1410     if (note())
1411         note()->removeFromGroup(&m_linkDisplayItem);
1412 }
1413 
1414 qreal FileContent::setWidthAndGetHeight(qreal width)
1415 {
1416     m_linkDisplayItem.linkDisplay().setWidth(width);
1417     return m_linkDisplayItem.linkDisplay().height();
1418 }
1419 
1420 bool FileContent::loadFromFile(bool /*lazyLoad*/)
1421 {
1422     setFileName(fileName()); // File changed: get new file preview!
1423     return true;
1424 }
1425 
1426 QMap<QString, QString> FileContent::toolTipInfos()
1427 {
1428     QMap<QString, QString> toolTip;
1429 
1430     // Get the size of the file:
1431     uint size = QFileInfo(fullPath()).size();
1432     QString humanFileSize = KIO::convertSize((KIO::filesize_t)size);
1433     toolTip.insert(i18n("Size"), humanFileSize);
1434 
1435     QMimeDatabase db;
1436     QMimeType mime = db.mimeTypeForUrl(QUrl::fromLocalFile(fullPath()));
1437     if (mime.isValid()) {
1438         toolTip.insert(i18n("Type"), mime.comment());
1439     }
1440 
1441     MetaDataExtractionResult result(fullPath(), mime.name());
1442 
1443     KFileMetaData::ExtractorCollection extractorCollection;
1444     const QList<KFileMetaData::Extractor*> exList = extractorCollection.fetchExtractors(mime.name());
1445     for (KFileMetaData::Extractor *ex : exList) {
1446         ex->extract(&result);
1447         const auto groups = result.preferredGroups();
1448         DEBUG_WIN << "Metadata Extractor result has " << QString::number(groups.count()) << " groups";
1449 
1450         for (const auto &group : groups) {
1451             if (!group.second.isEmpty()) {
1452                 toolTip.insert(group.first, group.second);
1453             }
1454         }
1455     }
1456 
1457     return toolTip;
1458 }
1459 
1460 int FileContent::zoneAt(const QPointF &pos)
1461 {
1462     return (m_linkDisplayItem.linkDisplay().iconButtonAt(pos) ? 0 : Note::Custom0);
1463 }
1464 
1465 QRectF FileContent::zoneRect(int zone, const QPointF & /*pos*/)
1466 {
1467     QRectF linkRect = m_linkDisplayItem.linkDisplay().iconButtonRect();
1468 
1469     if (zone == Note::Custom0)
1470         return QRectF(linkRect.width(), 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect()
1471     else if (zone == Note::Content)
1472         return linkRect;
1473     else
1474         return QRectF();
1475 }
1476 
1477 QString FileContent::zoneTip(int zone)
1478 {
1479     return (zone == Note::Custom0 ? i18n("Open this file") : QString());
1480 }
1481 
1482 Qt::CursorShape FileContent::cursorFromZone(int zone) const
1483 {
1484     if (zone == Note::Custom0)
1485         return Qt::PointingHandCursor;
1486     return Qt::ArrowCursor;
1487 }
1488 
1489 int FileContent::xEditorIndent()
1490 {
1491     return m_linkDisplayItem.linkDisplay().iconButtonRect().width() + 2;
1492 }
1493 
1494 QString FileContent::messageWhenOpening(OpenMessage where)
1495 {
1496     switch (where) {
1497     case OpenOne:
1498         return i18n("Opening file...");
1499     case OpenSeveral:
1500         return i18n("Opening files...");
1501     case OpenOneWith:
1502         return i18n("Opening file with...");
1503     case OpenSeveralWith:
1504         return i18n("Opening files with...");
1505     case OpenOneWithDialog:
1506         return i18n("Open file with:");
1507     case OpenSeveralWithDialog:
1508         return i18n("Open files with:");
1509     default:
1510         return QString();
1511     }
1512 }
1513 
1514 void FileContent::setFileName(const QString &fileName)
1515 {
1516     NoteContent::setFileName(fileName);
1517     QUrl url = QUrl::fromLocalFile(fullPath());
1518     if (FileContent::linkLook()->previewEnabled())
1519         m_linkDisplayItem.linkDisplay().setLink(fileName, NoteFactory::iconForURL(url), FileContent::linkLook(), note()->font()); // FIXME: move iconForURL outside of NoteFactory !!!!!
1520     else
1521         m_linkDisplayItem.linkDisplay().setLink(fileName, NoteFactory::iconForURL(url), QPixmap(), FileContent::linkLook(), note()->font());
1522     startFetchingUrlPreview();
1523     contentChanged(m_linkDisplayItem.linkDisplay().minWidth());
1524 }
1525 
1526 void FileContent::linkLookChanged()
1527 {
1528     fontChanged();
1529     // setFileName(fileName());
1530     // startFetchingUrlPreview();
1531 }
1532 
1533 void FileContent::newPreview(const KFileItem &, const QPixmap &preview)
1534 {
1535     LinkLook *linkLook = this->linkLook();
1536     m_linkDisplayItem.linkDisplay().setLink(fileName(), NoteFactory::iconForURL(QUrl::fromLocalFile(fullPath())), (linkLook->previewEnabled() ? preview : QPixmap()), linkLook, note()->font());
1537     contentChanged(m_linkDisplayItem.linkDisplay().minWidth());
1538 }
1539 
1540 void FileContent::removePreview(const KFileItem &ki)
1541 {
1542     newPreview(ki, QPixmap());
1543 }
1544 
1545 void FileContent::startFetchingUrlPreview()
1546 {
1547     /*
1548     KUrl url(fullPath());
1549     LinkLook *linkLook = this->linkLook();
1550 
1551 //  delete m_previewJob;
1552     if (!url.isEmpty() && linkLook->previewSize() > 0) {
1553         QUrl filteredUrl = NoteFactory::filteredURL(url);//KURIFilter::self()->filteredURI(url);
1554         KUrl::List urlList;
1555         urlList.append(filteredUrl);
1556         m_previewJob = KIO::filePreview(urlList, linkLook->previewSize(), linkLook->previewSize(), linkLook->iconSize());
1557         connect(m_previewJob, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)), this, SLOT(newPreview(const KFileItem&, const QPixmap&)));
1558         connect(m_previewJob, SIGNAL(failed(const KFileItem&)),                     this, SLOT(removePreview(const KFileItem&)));
1559     }
1560     */
1561 }
1562 
1563 void FileContent::exportToHTML(HTMLExporter *exporter, int indent)
1564 {
1565     QString spaces;
1566     QString fileName = exporter->copyFile(fullPath(), true);
1567     exporter->stream << m_linkDisplayItem.linkDisplay().toHtml(exporter, QUrl::fromLocalFile(exporter->dataFolderName + fileName), QString()).replace("\n", '\n' + spaces.fill(' ', indent + 1));
1568 }
1569 
1570 /** class SoundContent:
1571  */
1572 
1573 SoundContent::SoundContent(Note *parent, const QString &fileName)
1574     : FileContent(parent, fileName)
1575 {
1576     SoundContent::setFileName(fileName);
1577     music = new Phonon::MediaObject(this);
1578     music->setCurrentSource(Phonon::MediaSource(fullPathUrl()));
1579     auto *audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this);
1580     Phonon::createPath(music, audioOutput);
1581     connect(music, &Phonon::MediaObject::stateChanged, this, &SoundContent::stateChanged);
1582 }
1583 
1584 void SoundContent::stateChanged(Phonon::State newState, Phonon::State oldState)
1585 {
1586     qDebug() << "stateChanged " << oldState << " to " << newState;
1587 }
1588 
1589 QString SoundContent::zoneTip(int zone)
1590 {
1591     return (zone == Note::Custom0 ? i18n("Open this sound") : QString());
1592 }
1593 
1594 void SoundContent::setHoveredZone(int oldZone, int newZone)
1595 {
1596     if (newZone == Note::Custom0 || newZone == Note::Content) {
1597         // Start the sound preview:
1598         if (oldZone != Note::Custom0 && oldZone != Note::Content) { // Don't restart if it was already in one of those zones
1599             if (music->state() == 1) {
1600                 music->play();
1601             }
1602         }
1603     } else {
1604         //       Stop the sound preview, if it was started:
1605         if (music->state() != 1) {
1606             music->stop();
1607             //          delete music;//TODO implement this in slot connected with music alted signal
1608             //          music = 0;
1609         }
1610     }
1611 }
1612 
1613 QString SoundContent::messageWhenOpening(OpenMessage where)
1614 {
1615     switch (where) {
1616     case OpenOne:
1617         return i18n("Opening sound...");
1618     case OpenSeveral:
1619         return i18n("Opening sounds...");
1620     case OpenOneWith:
1621         return i18n("Opening sound with...");
1622     case OpenSeveralWith:
1623         return i18n("Opening sounds with...");
1624     case OpenOneWithDialog:
1625         return i18n("Open sound with:");
1626     case OpenSeveralWithDialog:
1627         return i18n("Open sounds with:");
1628     default:
1629         return QString();
1630     }
1631 }
1632 
1633 
1634 void SoundContent::setFileName(const QString &fileName)
1635 {
1636     NoteContent::setFileName(fileName);
1637     QUrl url = QUrl::fromLocalFile(fullPath());
1638     if (SoundContent::linkLook()->previewEnabled()) {
1639         m_linkDisplayItem.linkDisplay().setLink(fileName, NoteFactory::iconForURL(url), SoundContent::linkLook(), note()->font()); // FIXME: move iconForURL outside of NoteFactory !!!!!
1640     } else {
1641         m_linkDisplayItem.linkDisplay().setLink(fileName, NoteFactory::iconForURL(url), QPixmap(), SoundContent::linkLook(), note()->font());
1642     }
1643     startFetchingUrlPreview();
1644     contentChanged(m_linkDisplayItem.linkDisplay().minWidth());
1645 }
1646 
1647 /** class LinkContent:
1648  */
1649 
1650 LinkContent::LinkContent(Note *parent, const QUrl &url, const QString &title, const QString &icon, bool autoTitle, bool autoIcon)
1651     : NoteContent(parent, NoteType::Link)
1652     , m_linkDisplayItem(parent)
1653     , m_access_manager(nullptr)
1654     , m_acceptingData(false)
1655     , m_previewJob(nullptr)
1656 {
1657     setLink(url, title, icon, autoTitle, autoIcon);
1658     if (parent) {
1659         parent->addToGroup(&m_linkDisplayItem);
1660         m_linkDisplayItem.setPos(parent->contentX(), Note::NOTE_MARGIN);
1661     }
1662 }
1663 LinkContent::~LinkContent()
1664 {
1665     if (note())
1666         note()->removeFromGroup(&m_linkDisplayItem);
1667 }
1668 
1669 qreal LinkContent::setWidthAndGetHeight(qreal width)
1670 {
1671     m_linkDisplayItem.linkDisplay().setWidth(width);
1672     return m_linkDisplayItem.linkDisplay().height();
1673 }
1674 
1675 void LinkContent::saveToNode(QXmlStreamWriter &stream)
1676 {
1677     stream.writeStartElement("content");
1678     stream.writeAttribute("title", title());
1679     stream.writeAttribute("icon", icon());
1680     stream.writeAttribute("autoIcon", (autoIcon() ? "true" : "false"));
1681     stream.writeAttribute("autoTitle", (autoTitle() ? "true" : "false"));
1682     stream.writeCharacters(url().toDisplayString());
1683     stream.writeEndElement();
1684 }
1685 
1686 QMap<QString, QString> LinkContent::toolTipInfos()
1687 {
1688     return {
1689         { i18n("Target"), m_url.toDisplayString() }
1690     };
1691 }
1692 
1693 int LinkContent::zoneAt(const QPointF &pos)
1694 {
1695     return (m_linkDisplayItem.linkDisplay().iconButtonAt(pos) ? 0 : Note::Custom0);
1696 }
1697 
1698 QRectF LinkContent::zoneRect(int zone, const QPointF & /*pos*/)
1699 {
1700     QRectF linkRect = m_linkDisplayItem.linkDisplay().iconButtonRect();
1701 
1702     if (zone == Note::Custom0)
1703         return QRectF(linkRect.width(), 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect()
1704     else if (zone == Note::Content)
1705         return linkRect;
1706     else
1707         return QRectF();
1708 }
1709 
1710 QString LinkContent::zoneTip(int zone)
1711 {
1712     return (zone == Note::Custom0 ? i18n("Open this link") : QString());
1713 }
1714 
1715 Qt::CursorShape LinkContent::cursorFromZone(int zone) const
1716 {
1717     if (zone == Note::Custom0)
1718         return Qt::PointingHandCursor;
1719     return Qt::ArrowCursor;
1720 }
1721 
1722 QString LinkContent::statusBarMessage(int zone)
1723 {
1724     if (zone == Note::Custom0 || zone == Note::Content)
1725         return m_url.toDisplayString();
1726     else
1727         return QString();
1728 }
1729 
1730 QUrl LinkContent::urlToOpen(bool /*with*/)
1731 {
1732     return NoteFactory::filteredURL(url()); // KURIFilter::self()->filteredURI(url());
1733 }
1734 
1735 QString LinkContent::messageWhenOpening(OpenMessage where)
1736 {
1737     if (url().isEmpty())
1738         return i18n("Link have no URL to open.");
1739 
1740     switch (where) {
1741     case OpenOne:
1742         return i18n("Opening link target...");
1743     case OpenSeveral:
1744         return i18n("Opening link targets...");
1745     case OpenOneWith:
1746         return i18n("Opening link target with...");
1747     case OpenSeveralWith:
1748         return i18n("Opening link targets with...");
1749     case OpenOneWithDialog:
1750         return i18n("Open link target with:");
1751     case OpenSeveralWithDialog:
1752         return i18n("Open link targets with:");
1753     default:
1754         return QString();
1755     }
1756 }
1757 
1758 void LinkContent::setLink(const QUrl &url, const QString &title, const QString &icon, bool autoTitle, bool autoIcon)
1759 {
1760     m_autoTitle = autoTitle;
1761     m_autoIcon = autoIcon;
1762     m_url = NoteFactory::filteredURL(url); // KURIFilter::self()->filteredURI(url);
1763     m_title = (autoTitle ? NoteFactory::titleForURL(m_url) : title);
1764     m_icon = (autoIcon ? NoteFactory::iconForURL(m_url) : icon);
1765 
1766     LinkLook *look = LinkLook::lookForURL(m_url);
1767     if (look->previewEnabled())
1768         m_linkDisplayItem.linkDisplay().setLink(m_title, m_icon, look, note()->font());
1769     else
1770         m_linkDisplayItem.linkDisplay().setLink(m_title, m_icon, QPixmap(), look, note()->font());
1771     startFetchingUrlPreview();
1772     if (autoTitle)
1773         startFetchingLinkTitle();
1774     contentChanged(m_linkDisplayItem.linkDisplay().minWidth());
1775 }
1776 
1777 void LinkContent::linkLookChanged()
1778 {
1779     fontChanged();
1780 }
1781 
1782 void LinkContent::newPreview(const KFileItem &, const QPixmap &preview)
1783 {
1784     LinkLook *linkLook = LinkLook::lookForURL(url());
1785     m_linkDisplayItem.linkDisplay().setLink(title(), icon(), (linkLook->previewEnabled() ? preview : QPixmap()), linkLook, note()->font());
1786     contentChanged(m_linkDisplayItem.linkDisplay().minWidth());
1787 }
1788 
1789 void LinkContent::removePreview(const KFileItem &ki)
1790 {
1791     newPreview(ki, QPixmap());
1792 }
1793 
1794 // QHttp slots for getting link title
1795 void LinkContent::httpReadyRead()
1796 {
1797     if (!m_acceptingData)
1798         return;
1799 
1800     // Check for availability
1801     qint64 bytesAvailable = m_reply->bytesAvailable();
1802     if (bytesAvailable <= 0)
1803         return;
1804 
1805     QByteArray buf = m_reply->read(bytesAvailable);
1806     m_httpBuff.append(buf);
1807 
1808     // Stop at 10k bytes
1809     if (m_httpBuff.length() > 10000) {
1810         m_acceptingData = false;
1811         m_reply->abort();
1812         endFetchingLinkTitle();
1813     }
1814 }
1815 
1816 void LinkContent::httpDone(QNetworkReply *reply)
1817 {
1818     if (m_acceptingData) {
1819         m_acceptingData = false;
1820         endFetchingLinkTitle();
1821     }
1822 
1823     // If all done, close and delete the reply.
1824     reply->deleteLater();
1825 }
1826 
1827 void LinkContent::startFetchingLinkTitle()
1828 {
1829     QUrl newUrl = this->url();
1830 
1831     // If this is not an HTTP request, just ignore it.
1832     if (newUrl.scheme() != QLatin1String("http") && newUrl.scheme() != QLatin1String("https")) {
1833         return;
1834     }
1835 
1836     // If we have no access_manager, create one.
1837     if (m_access_manager == nullptr) {
1838         m_access_manager = new QNetworkAccessManager(this);
1839         connect(m_access_manager, &QNetworkAccessManager::finished, this, &LinkContent::httpDone);
1840     }
1841 
1842     // If no explicit port, default to port 80.
1843     if (newUrl.port() == 0) {
1844         newUrl.setPort(80);
1845     }
1846 
1847     // If no path or query part, default to /
1848     if ((newUrl.path() + newUrl.query()).isEmpty()) {
1849         newUrl = QUrl::fromLocalFile("/");
1850     }
1851 
1852     // Issue request
1853     m_reply = m_access_manager->get(QNetworkRequest(newUrl));
1854     m_acceptingData = true;
1855     connect(m_reply, &QNetworkReply::readyRead, this, &LinkContent::httpReadyRead);
1856 }
1857 
1858 // Code duplicated from FileContent::startFetchingUrlPreview()
1859 void LinkContent::startFetchingUrlPreview()
1860 {
1861     QUrl url = this->url();
1862     LinkLook *linkLook = LinkLook::lookForURL(this->url());
1863 
1864     //  delete m_previewJob;
1865     if (!url.isEmpty() && linkLook->previewSize() > 0) {
1866         KFileItem itemUrl(NoteFactory::filteredURL(url)); // KURIFilter::self()->filteredURI(url);
1867         m_previewJob = KIO::filePreview({itemUrl}, QSize(linkLook->previewSize(), linkLook->previewSize()), nullptr);
1868         m_previewJob->setOverlayIconSize(linkLook->iconSize());
1869         connect(m_previewJob, SIGNAL(gotPreview(const KFileItem &, const QPixmap &)), this, SLOT(newPreview(const KFileItem &, const QPixmap &)));
1870         connect(m_previewJob, SIGNAL(failed(const KFileItem &)), this, SLOT(removePreview(const KFileItem &)));
1871     }
1872 }
1873 
1874 void LinkContent::endFetchingLinkTitle()
1875 {
1876     if (m_httpBuff.length() > 0) {
1877         decodeHtmlTitle();
1878         m_httpBuff.clear();
1879     } else
1880         DEBUG_WIN << "LinkContent: empty buffer on endFetchingLinkTitle for " + m_url.toString();
1881 }
1882 
1883 void LinkContent::exportToHTML(HTMLExporter *exporter, int indent)
1884 {
1885     QString linkTitle = title();
1886 
1887     // TODO:
1888     //  // Append address (useful for print version of the page/basket):
1889     //  if (exportData.formatForImpression && (!autoTitle() && title() != NoteFactory::titleForURL(url().toDisplayString()))) {
1890     //      // The address is on a new line, unless title is empty (empty lines was replaced by &nbsp;):
1891     //      if (linkTitle == " "/*"&nbsp;"*/)
1892     //          linkTitle = url().toDisplayString()/*QString()*/;
1893     //      else
1894     //          linkTitle = linkTitle + " <" + url().toDisplayString() + ">"/*+ "<br>"*/;
1895     //      //linkTitle += "<i>" + url().toDisplayString() + "</i>";
1896     //  }
1897 
1898     QUrl linkURL;
1899     /*
1900         QFileInfo fInfo(url().path());
1901     //  DEBUG_WIN << url().path()
1902     //            << "IsFile:" + QString::number(fInfo.isFile())
1903     //            << "IsDir:"  + QString::number(fInfo.isDir());
1904         if (exportData.embedLinkedFiles && fInfo.isFile()) {
1905     //      DEBUG_WIN << "Embed file";
1906             linkURL = exportData.dataFolderName + BasketScene::copyFile(url().path(), exportData.dataFolderPath, true);
1907         } else if (exportData.embedLinkedFolders && fInfo.isDir()) {
1908     //      DEBUG_WIN << "Embed folder";
1909             linkURL = exportData.dataFolderName + BasketScene::copyFile(url().path(), exportData.dataFolderPath, true);
1910         } else {
1911     //      DEBUG_WIN << "Embed LINK";
1912     */
1913     linkURL = url();
1914     /*
1915         }
1916     */
1917 
1918     QString spaces;
1919     exporter->stream << m_linkDisplayItem.linkDisplay().toHtml(exporter, linkURL, linkTitle).replace("\n", '\n' + spaces.fill(' ', indent + 1));
1920 }
1921 
1922 /** class CrossReferenceContent:
1923  */
1924 
1925 CrossReferenceContent::CrossReferenceContent(Note *parent, const QUrl &url, const QString &title, const QString &icon)
1926     : NoteContent(parent, NoteType::CrossReference)
1927     , m_linkDisplayItem(parent)
1928 {
1929     this->setCrossReference(url, title, icon);
1930     if (parent)
1931         parent->addToGroup(&m_linkDisplayItem);
1932 }
1933 
1934 CrossReferenceContent::~CrossReferenceContent()
1935 {
1936     if (note())
1937         note()->removeFromGroup(&m_linkDisplayItem);
1938 }
1939 
1940 qreal CrossReferenceContent::setWidthAndGetHeight(qreal width)
1941 {
1942     m_linkDisplayItem.linkDisplay().setWidth(width);
1943     return m_linkDisplayItem.linkDisplay().height();
1944 }
1945 
1946 void CrossReferenceContent::saveToNode(QXmlStreamWriter &stream)
1947 {
1948     stream.writeStartElement("content");
1949     stream.writeAttribute("title", title());
1950     stream.writeAttribute("icon", icon());
1951     stream.writeCharacters(url().toDisplayString());
1952     stream.writeEndElement();
1953 }
1954 
1955 QMap<QString, QString> CrossReferenceContent::toolTipInfos()
1956 {
1957     return {
1958         { i18n("Target"), m_url.toDisplayString() }
1959     };
1960 }
1961 
1962 int CrossReferenceContent::zoneAt(const QPointF &pos)
1963 {
1964     return (m_linkDisplayItem.linkDisplay().iconButtonAt(pos) ? 0 : Note::Custom0);
1965 }
1966 
1967 QRectF CrossReferenceContent::zoneRect(int zone, const QPointF & /*pos*/)
1968 {
1969     QRectF linkRect = m_linkDisplayItem.linkDisplay().iconButtonRect();
1970 
1971     if (zone == Note::Custom0)
1972         return QRectF(linkRect.width(), 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect()
1973     else if (zone == Note::Content)
1974         return linkRect;
1975     else
1976         return QRectF();
1977 }
1978 
1979 QString CrossReferenceContent::zoneTip(int zone)
1980 {
1981     return (zone == Note::Custom0 ? i18n("Open this link") : QString());
1982 }
1983 
1984 Qt::CursorShape CrossReferenceContent::cursorFromZone(int zone) const
1985 {
1986     if (zone == Note::Custom0)
1987         return Qt::PointingHandCursor;
1988     return Qt::ArrowCursor;
1989 }
1990 
1991 QString CrossReferenceContent::statusBarMessage(int zone)
1992 {
1993     if (zone == Note::Custom0 || zone == Note::Content)
1994         return i18n("Link to %1", this->title());
1995     else
1996         return QString();
1997 }
1998 
1999 QUrl CrossReferenceContent::urlToOpen(bool /*with*/)
2000 {
2001     return m_url;
2002 }
2003 
2004 QString CrossReferenceContent::messageWhenOpening(OpenMessage where)
2005 {
2006     if (url().isEmpty())
2007         return i18n("Link has no basket to open.");
2008 
2009     switch (where) {
2010     case OpenOne:
2011         return i18n("Opening basket...");
2012     default:
2013         return QString();
2014     }
2015 }
2016 
2017 void CrossReferenceContent::setLink(const QUrl &url, const QString &title, const QString &icon)
2018 {
2019     this->setCrossReference(url, title, icon);
2020 }
2021 
2022 void CrossReferenceContent::setCrossReference(const QUrl &url, const QString &title, const QString &icon)
2023 {
2024     m_url = url;
2025     m_title = (title.isEmpty() ? url.url() : title);
2026     m_icon = icon;
2027 
2028     LinkLook *look = LinkLook::crossReferenceLook;
2029     m_linkDisplayItem.linkDisplay().setLink(m_title, m_icon, look, note()->font());
2030 
2031     contentChanged(m_linkDisplayItem.linkDisplay().minWidth());
2032 }
2033 
2034 void CrossReferenceContent::linkLookChanged()
2035 {
2036     fontChanged();
2037 }
2038 
2039 void CrossReferenceContent::exportToHTML(HTMLExporter *exporter, int /*indent*/)
2040 {
2041     QString url = m_url.url();
2042     QString title;
2043 
2044     if (url.startsWith(QLatin1String("basket://")))
2045         url = url.mid(9, url.length() - 9);
2046     if (url.endsWith('/'))
2047         url = url.left(url.length() - 1);
2048 
2049     BasketScene *basket = Global::bnpView->basketForFolderName(url);
2050 
2051     if (!basket)
2052         title = "unknown basket";
2053     else
2054         title = basket->basketName();
2055 
2056     // if the basket we're trying to link to is the basket that was exported then
2057     // we have to use a special way to refer to it for the links.
2058     if (basket == exporter->exportedBasket)
2059         url = "../../" + exporter->fileName;
2060     else {
2061         // if we're in the exported basket then the links have to include
2062         // the sub directories.
2063         if (exporter->currentBasket == exporter->exportedBasket)
2064             url.prepend(exporter->basketsFolderName);
2065         url.append(".html");
2066     }
2067 
2068     QString linkIcon = exporter->iconsFolderName + exporter->copyIcon(m_icon, LinkLook::crossReferenceLook->iconSize());
2069     linkIcon = QString("<img src=\"%1\" alt=\"\">").arg(linkIcon);
2070 
2071     exporter->stream << QString("<a href=\"%1\">%2 %3</a>").arg(url, linkIcon, title);
2072 }
2073 
2074 /** class LauncherContent:
2075  */
2076 
2077 LauncherContent::LauncherContent(Note *parent, const QString &fileName)
2078     : NoteContent(parent, NoteType::Launcher, fileName)
2079     , m_linkDisplayItem(parent)
2080 {
2081     basket()->addWatchedFile(fullPath());
2082     LauncherContent::loadFromFile(/*lazyLoad=*/false);
2083     if (parent) {
2084         parent->addToGroup(&m_linkDisplayItem);
2085         m_linkDisplayItem.setPos(parent->contentX(), Note::NOTE_MARGIN);
2086     }
2087 }
2088 
2089 LauncherContent::~LauncherContent()
2090 {
2091     if (note())
2092         note()->removeFromGroup(&m_linkDisplayItem);
2093 }
2094 
2095 qreal LauncherContent::setWidthAndGetHeight(qreal width)
2096 {
2097     m_linkDisplayItem.linkDisplay().setWidth(width);
2098     return m_linkDisplayItem.linkDisplay().height();
2099 }
2100 
2101 bool LauncherContent::loadFromFile(bool /*lazyLoad*/) // TODO: saveToFile() ?? Is it possible?
2102 {
2103     DEBUG_WIN << "Loading LauncherContent From " + basket()->folderName() + fileName();
2104     KService service(fullPath());
2105     setLauncher(service.name(), service.icon(), service.exec());
2106     return true;
2107 }
2108 
2109 QMap<QString, QString> LauncherContent::toolTipInfos()
2110 {
2111     QMap<QString, QString> toolTip;
2112     KService service(fullPath());
2113 
2114     QString exec = service.exec();
2115     if (service.terminal())
2116         exec = i18n("%1 <i>(run in terminal)</i>", exec);
2117 
2118     if (!service.comment().isEmpty() && service.comment() != service.name()) {
2119         toolTip.insert(i18n("Comment"), service.comment());
2120     }
2121     toolTip.insert(i18n("Command"), exec);
2122 
2123     return toolTip;
2124 }
2125 
2126 int LauncherContent::zoneAt(const QPointF &pos)
2127 {
2128     return (m_linkDisplayItem.linkDisplay().iconButtonAt(pos) ? 0 : Note::Custom0);
2129 }
2130 
2131 QRectF LauncherContent::zoneRect(int zone, const QPointF & /*pos*/)
2132 {
2133     QRectF linkRect = m_linkDisplayItem.linkDisplay().iconButtonRect();
2134 
2135     if (zone == Note::Custom0)
2136         return QRectF(linkRect.width(), 0, note()->width(), note()->height()); // Too wide and height, but it will be clipped by Note::zoneRect()
2137     else if (zone == Note::Content)
2138         return linkRect;
2139     else
2140         return QRectF();
2141 }
2142 
2143 QString LauncherContent::zoneTip(int zone)
2144 {
2145     return (zone == Note::Custom0 ? i18n("Launch this application") : QString());
2146 }
2147 
2148 Qt::CursorShape LauncherContent::cursorFromZone(int zone) const
2149 {
2150     if (zone == Note::Custom0)
2151         return Qt::PointingHandCursor;
2152     return Qt::ArrowCursor;
2153 }
2154 
2155 QUrl LauncherContent::urlToOpen(bool with)
2156 {
2157     if (KService(fullPath()).exec().isEmpty())
2158         return QUrl();
2159 
2160     return (with ? QUrl() : QUrl::fromLocalFile(fullPath())); // Can open the application, but not with another application :-)
2161 }
2162 
2163 QString LauncherContent::messageWhenOpening(OpenMessage where)
2164 {
2165     if (KService(fullPath()).exec().isEmpty())
2166         return i18n("The launcher have no command to run.");
2167 
2168     switch (where) {
2169     case OpenOne:
2170         return i18n("Launching application...");
2171     case OpenSeveral:
2172         return i18n("Launching applications...");
2173     case OpenOneWith:
2174     case OpenSeveralWith:
2175     case OpenOneWithDialog:
2176     case OpenSeveralWithDialog: // TODO: "Open this application with this file as parameter"?
2177     default:
2178         return QString();
2179     }
2180 }
2181 
2182 void LauncherContent::setLauncher(const QString &name, const QString &icon, const QString &exec)
2183 {
2184     m_name = name;
2185     m_icon = icon;
2186     m_exec = exec;
2187 
2188     m_linkDisplayItem.linkDisplay().setLink(name, icon, LinkLook::launcherLook, note()->font());
2189     contentChanged(m_linkDisplayItem.linkDisplay().minWidth());
2190 }
2191 
2192 void LauncherContent::exportToHTML(HTMLExporter *exporter, int indent)
2193 {
2194     QString spaces;
2195     QString fileName = exporter->copyFile(fullPath(), /*createIt=*/true);
2196     exporter->stream << m_linkDisplayItem.linkDisplay().toHtml(exporter, QUrl::fromLocalFile(exporter->dataFolderName + fileName), QString()).replace("\n", '\n' + spaces.fill(' ', indent + 1));
2197 }
2198 
2199 /** class ColorItem:
2200  */
2201 const int ColorItem::RECT_MARGIN = 2;
2202 
2203 ColorItem::ColorItem(Note *parent, const QColor &color)
2204     : QGraphicsItem(parent)
2205     , m_note(parent)
2206 {
2207     ColorItem::setColor(color);
2208 }
2209 
2210 void ColorItem::setColor(const QColor &color)
2211 {
2212     m_color = color;
2213     m_textRect = QFontMetrics(m_note->font()).boundingRect(m_color.name());
2214 }
2215 
2216 QRectF ColorItem::boundingRect() const
2217 {
2218     qreal rectHeight = (m_textRect.height() + 2) * 3 / 2;
2219     qreal rectWidth = rectHeight * 14 / 10; // 1.4 times the height, like A4 papers.
2220     return QRectF(0, 0, rectWidth + RECT_MARGIN + m_textRect.width() + RECT_MARGIN, rectHeight);
2221 }
2222 
2223 void ColorItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
2224 {
2225     QRectF boundingRect = this->boundingRect();
2226     qreal rectHeight = (m_textRect.height() + 2) * 3 / 2;
2227     qreal rectWidth = rectHeight * 14 / 10; // 1.4 times the height, like A4 papers.
2228 
2229     // FIXME: Duplicate from CommonColorSelector::drawColorRect:
2230     // Fill:
2231     painter->fillRect(1, 1, rectWidth - 2, rectHeight - 2, color());
2232     // Stroke:
2233     QColor stroke = color().darker(125);
2234     painter->setPen(stroke);
2235     painter->drawLine(1, 0, rectWidth - 2, 0);
2236     painter->drawLine(0, 1, 0, rectHeight - 2);
2237     painter->drawLine(1, rectHeight - 1, rectWidth - 2, rectHeight - 1);
2238     painter->drawLine(rectWidth - 1, 1, rectWidth - 1, rectHeight - 2);
2239     // Round corners:
2240     painter->setPen(Tools::mixColor(color(), stroke));
2241     painter->drawPoint(1, 1);
2242     painter->drawPoint(1, rectHeight - 2);
2243     painter->drawPoint(rectWidth - 2, rectHeight - 2);
2244     painter->drawPoint(rectWidth - 2, 1);
2245 
2246     // Draw the text:
2247     painter->setFont(m_note->font());
2248     painter->setPen(m_note->palette().color(QPalette::Active, QPalette::WindowText));
2249     painter->drawText(rectWidth + RECT_MARGIN, 0, m_textRect.width(), boundingRect.height(), Qt::AlignLeft | Qt::AlignVCenter, color().name());
2250 }
2251 
2252 /** class ColorContent:
2253  */
2254 
2255 ColorContent::ColorContent(Note *parent, const QColor &color)
2256     : NoteContent(parent, NoteType::Color)
2257     , m_colorItem(parent, color)
2258 {
2259     if (parent) {
2260         parent->addToGroup(&m_colorItem);
2261         m_colorItem.setPos(parent->contentX(), Note::NOTE_MARGIN);
2262     }
2263 }
2264 
2265 ColorContent::~ColorContent()
2266 {
2267     if (note())
2268         note()->removeFromGroup(&m_colorItem);
2269 }
2270 
2271 qreal ColorContent::setWidthAndGetHeight(qreal /*width*/) // We do not need width because we can't word-break, and width is always >= minWidth()
2272 {
2273     return m_colorItem.boundingRect().height();
2274 }
2275 
2276 void ColorContent::saveToNode(QXmlStreamWriter &stream)
2277 {
2278     stream.writeStartElement("content");
2279     stream.writeCharacters(color().name());
2280     stream.writeEndElement();
2281 }
2282 
2283 QMap<QString, QString> ColorContent::toolTipInfos()
2284 {
2285     QMap<QString, QString> toolTip;
2286 
2287     int hue, saturation, value;
2288     color().getHsv(&hue, &saturation, &value);
2289 
2290     toolTip.insert(i18nc("RGB Colorspace: Red/Green/Blue", "RGB"),
2291                    i18n("<i>Red</i>: %1, <i>Green</i>: %2, <i>Blue</i>: %3,", QString::number(color().red()), QString::number(color().green()), QString::number(color().blue())));
2292     toolTip.insert(i18nc("HSV Colorspace: Hue/Saturation/Value", "HSV"),
2293                    i18n("<i>Hue</i>: %1, <i>Saturation</i>: %2, <i>Value</i>: %3,", QString::number(hue), QString::number(saturation), QString::number(value)));
2294 
2295     const QString colorName = Tools::cssColorName(color().name());
2296     if (!colorName.isEmpty()) {
2297         toolTip.insert(i18n("CSS Color Name"), colorName);
2298     }
2299 
2300     toolTip.insert(i18n("Is Web Color"), Tools::isWebColor(color()) ? i18n("Yes") : i18n("No"));
2301 
2302     return toolTip;
2303 }
2304 
2305 void ColorContent::setColor(const QColor &color)
2306 {
2307     m_colorItem.setColor(color);
2308     contentChanged(m_colorItem.boundingRect().width());
2309 }
2310 
2311 void ColorContent::addAlternateDragObjects(QMimeData *dragObject)
2312 {
2313     dragObject->setColorData(color());
2314 }
2315 
2316 void ColorContent::exportToHTML(HTMLExporter *exporter, int /*indent*/)
2317 {
2318     // FIXME: Duplicate from setColor(): TODO: rectSize()
2319     QRectF textRect = QFontMetrics(note()->font()).boundingRect(color().name());
2320     int rectHeight = (textRect.height() + 2) * 3 / 2;
2321     int rectWidth = rectHeight * 14 / 10; // 1.4 times the height, like A4 papers.
2322 
2323     QString fileName = /*Tools::fileNameForNewFile(*/ QString("color_%1.png").arg(color().name().toLower().mid(1)) /*, exportData.iconsFolderPath)*/;
2324     QString fullPath = exporter->iconsFolderPath + fileName;
2325     QPixmap colorIcon(rectWidth, rectHeight);
2326     QPainter painter(&colorIcon);
2327     painter.setBrush(color());
2328     painter.drawRoundedRect(0, 0, rectWidth, rectHeight, 2, 2);
2329     colorIcon.save(fullPath, "PNG");
2330     QString iconHtml = QString("<img src=\"%1\" width=\"%2\" height=\"%3\" alt=\"\">").arg(exporter->iconsFolderName + fileName, QString::number(colorIcon.width()), QString::number(colorIcon.height()));
2331 
2332     exporter->stream << iconHtml + ' ' + color().name();
2333 }
2334 
2335 /** class UnknownItem:
2336  */
2337 
2338 const qreal UnknownItem::DECORATION_MARGIN = 2;
2339 
2340 UnknownItem::UnknownItem(Note *parent)
2341     : QGraphicsItem(parent)
2342     , m_note(parent)
2343 {
2344 }
2345 
2346 QRectF UnknownItem::boundingRect() const
2347 {
2348     return QRectF(0, 0, m_textRect.width() + 2 * DECORATION_MARGIN, m_textRect.height() + 2 * DECORATION_MARGIN);
2349 }
2350 
2351 void UnknownItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *)
2352 {
2353     QPalette palette = m_note->basket()->palette();
2354     qreal width = boundingRect().width();
2355     qreal height = boundingRect().height();
2356     painter->setPen(palette.color(QPalette::Active, QPalette::WindowText));
2357 
2358     // Stroke:
2359     QColor stroke = Tools::mixColor(palette.color(QPalette::Active, QPalette::Background), palette.color(QPalette::Active, QPalette::WindowText));
2360     painter->setPen(stroke);
2361     painter->drawLine(1, 0, width - 2, 0);
2362     painter->drawLine(0, 1, 0, height - 2);
2363     painter->drawLine(1, height - 1, width - 2, height - 1);
2364     painter->drawLine(width - 1, 1, width - 1, height - 2);
2365     // Round corners:
2366     painter->setPen(Tools::mixColor(palette.color(QPalette::Active, QPalette::Background), stroke));
2367     painter->drawPoint(1, 1);
2368     painter->drawPoint(1, height - 2);
2369     painter->drawPoint(width - 2, height - 2);
2370     painter->drawPoint(width - 2, 1);
2371 
2372     painter->setPen(palette.color(QPalette::Active, QPalette::WindowText));
2373     painter->drawText(DECORATION_MARGIN, DECORATION_MARGIN, width - 2 * DECORATION_MARGIN, height - 2 * DECORATION_MARGIN, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextWordWrap, m_mimeTypes);
2374 }
2375 
2376 void UnknownItem::setMimeTypes(QString mimeTypes)
2377 {
2378     m_mimeTypes = mimeTypes;
2379     m_textRect = QFontMetrics(m_note->font()).boundingRect(0, 0, 1, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_mimeTypes);
2380 }
2381 
2382 void UnknownItem::setWidth(qreal width)
2383 {
2384     prepareGeometryChange();
2385     m_textRect = QFontMetrics(m_note->font()).boundingRect(0, 0, width, 500000, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap, m_mimeTypes);
2386 }
2387 
2388 /** class UnknownContent:
2389  */
2390 
2391 UnknownContent::UnknownContent(Note *parent, const QString &fileName)
2392     : NoteContent(parent, NoteType::Unknown, fileName)
2393     , m_unknownItem(parent)
2394 {
2395     if (parent) {
2396         parent->addToGroup(&m_unknownItem);
2397         m_unknownItem.setPos(parent->contentX(), Note::NOTE_MARGIN);
2398     }
2399 
2400     basket()->addWatchedFile(fullPath());
2401     UnknownContent::loadFromFile(/*lazyLoad=*/false);
2402 }
2403 
2404 UnknownContent::~UnknownContent()
2405 {
2406     if (note())
2407         note()->removeFromGroup(&m_unknownItem);
2408 }
2409 
2410 qreal UnknownContent::setWidthAndGetHeight(qreal width)
2411 {
2412     m_unknownItem.setWidth(width);
2413     return m_unknownItem.boundingRect().height();
2414 }
2415 
2416 bool UnknownContent::loadFromFile(bool /*lazyLoad*/)
2417 {
2418     DEBUG_WIN << "Loading UnknownContent From " + basket()->folderName() + fileName();
2419     QString mimeTypes;
2420     QFile file(fullPath());
2421     if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
2422         QTextStream stream(&file);
2423         QString line;
2424         // Get the MIME-types names:
2425         do {
2426             if (!stream.atEnd()) {
2427                 line = stream.readLine();
2428                 if (!line.isEmpty()) {
2429                     if (mimeTypes.isEmpty())
2430                         mimeTypes += line;
2431                     else
2432                         mimeTypes += QString("\n") + line;
2433                 }
2434             }
2435         } while (!line.isEmpty() && !stream.atEnd());
2436         file.close();
2437     }
2438 
2439     m_unknownItem.setMimeTypes(mimeTypes);
2440     contentChanged(m_unknownItem.boundingRect().width() + 1);
2441 
2442     return true;
2443 }
2444 
2445 void UnknownContent::addAlternateDragObjects(QMimeData *dragObject)
2446 {
2447     QFile file(fullPath());
2448     if (file.open(QIODevice::ReadOnly)) {
2449         QDataStream stream(&file);
2450         // Get the MIME types names:
2451         QStringList mimes;
2452         QString line;
2453         do {
2454             if (!stream.atEnd()) {
2455                 stream >> line;
2456                 if (!line.isEmpty())
2457                     mimes.append(line);
2458             }
2459         } while (!line.isEmpty() && !stream.atEnd());
2460         // Add the streams:
2461         quint64 size; // TODO: It was quint32 in version 0.5.0 !
2462         QByteArray *array;
2463         for (int i = 0; i < mimes.count(); ++i) {
2464             // Get the size:
2465             stream >> size;
2466             // Allocate memory to retrieve size bytes and store them:
2467             array = new QByteArray;
2468             array->resize(size);
2469             stream.readRawData(array->data(), size);
2470             // Creata and add the QDragObject:
2471             dragObject->setData(mimes.at(i).toLatin1(), *array);
2472             delete array; // FIXME: Should we?
2473         }
2474         file.close();
2475     }
2476 }
2477 
2478 void UnknownContent::exportToHTML(HTMLExporter *exporter, int indent)
2479 {
2480     QString spaces;
2481     exporter->stream << "<div class=\"unknown\">" << mimeTypes().replace("\n", '\n' + spaces.fill(' ', indent + 1 + 1)) << "</div>";
2482 }
2483 
2484 void LinkContent::decodeHtmlTitle()
2485 {
2486     KEncodingProber prober;
2487     prober.feed(m_httpBuff);
2488 
2489     // Fallback scheme: KEncodingProber - QTextCodec::codecForHtml - UTF-8
2490     QTextCodec *textCodec;
2491     if (prober.confidence() > 0.5)
2492         textCodec = QTextCodec::codecForName(prober.encoding());
2493     else
2494         textCodec = QTextCodec::codecForHtml(m_httpBuff, QTextCodec::codecForName("utf-8"));
2495 
2496     QString httpBuff = textCodec->toUnicode(m_httpBuff.data(), m_httpBuff.size());
2497 
2498     // todo: this should probably strip odd html tags like &nbsp; etc
2499     QRegExp reg("<title>[\\s]*(&nbsp;)?([^<]+)[\\s]*</title>", Qt::CaseInsensitive);
2500     reg.setMinimal(true);
2501     // qDebug() << *m_httpBuff << " bytes: " << bytes_read;
2502 
2503     if (reg.indexIn(httpBuff) >= 0) {
2504         m_title = reg.cap(2);
2505         m_autoTitle = false;
2506         setEdited();
2507 
2508         // refresh the title
2509         setLink(url(), title(), icon(), autoTitle(), autoIcon());
2510     }
2511 }