/*************************************************************************** * Copyright (C) 2007 by Thomas Georgiou * * Artur Duque de Souza * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "pastebin.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Pastebin::Pastebin(QObject *parent, const QVariantList &args) : Plasma::Applet(parent, args), m_signalMapper(new QSignalMapper()), m_paste(0), m_topSeparator(0), m_bottomSeparator(0), m_historySize(3) { setAcceptDrops(true); setHasConfigurationInterface(true); setAspectRatioMode(Plasma::ConstrainedSquare); setMinimumSize(16, 16); resize(150, 150); m_timer = new QTimer(this); connect(m_timer, SIGNAL(timeout()), this, SLOT(showErrors())); connect(m_signalMapper, SIGNAL(mapped(QString)), this, SLOT(copyToClipboard(QString))); connect(this, SIGNAL(activate()), this, SLOT(postClipboard())); // connect to all sources of our 'share' dataengine m_engine = dataEngine("org.kde.plasma.dataengine.share"); m_engine->connectAllSources(this); // to detect when the mimetypes were added again after a refresh connect(m_engine, SIGNAL(sourceAdded(QString)), this, SLOT(sourceAdded(QString))); connect(m_engine, SIGNAL(sourceRemoved(QString)), this, SLOT(sourceRemoved(QString))); } // save history of URLs void Pastebin::saveHistory() { QString history; const int numberOfActionHistory = m_actionHistory.size(); for (int i = 0; i < numberOfActionHistory; ++i) { history.prepend(m_actionHistory.at(i)->toolTip()); history.prepend('|'); } KConfigGroup cg = config(); cg.writeEntry("History", history); } Pastebin::~Pastebin() { delete m_topSeparator; delete m_bottomSeparator; saveHistory(); const int numberOfActionHistory = m_actionHistory.size(); for (int i = 0; i < numberOfActionHistory; ++i) { delete m_actionHistory.at(i); } } void Pastebin::init() { configChanged(); setActionState(Idle); setInteractionState(Waiting); updateTheme(); connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), SLOT(updateTheme())); Plasma::ToolTipManager::self()->registerWidget(this); Plasma::ToolTipManager::self()->setContent(this, toolTipData); } void Pastebin::dataUpdated(const QString &source, const Plasma::DataEngine::Data &data) { // update the options if (source != "Mimetypes") { const QString mimetype = data.value("Mimetypes").toString(); if (mimetype.startsWith("text/")) { m_txtServers.insert(data.value("Name").toString(), source); } else if (mimetype.startsWith("image/")) { m_imgServers.insert(data.value("Name").toString(), source); } else { kDebug() << "Mimetype not supported by this applet"; } } } void Pastebin::sourceAdded(const QString &source) { // update the options if (source != "Mimetypes") { const Plasma::DataEngine::Data data = m_engine->query(source); const QString mimetype = data.value("Mimetypes").toString(); if (mimetype.startsWith("text/")) { m_txtServers.insert(data.value("Name").toString(), source); } else if (mimetype.startsWith("image/")) { m_imgServers.insert(data.value("Name").toString(), source); } else { kDebug() << "Mimetype not supported by this applet"; } } } void Pastebin::sourceRemoved(const QString &source) { // update the options if (source != "Mimetypes") { QString key = m_txtServers.key(source); m_txtServers.remove(key); key = m_imgServers.key(source); m_imgServers.remove(key); } } void Pastebin::setHistorySize(int max) { if (max <= 0) { max = 0; } while (max < m_actionHistory.count()) { delete m_actionHistory.takeFirst(); } m_historySize = max; } void Pastebin::updateTheme() { m_font = Plasma::Theme::defaultTheme()->font(Plasma::Theme::DefaultFont); m_font.setBold(true); m_fgColor = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor); m_bgColor = Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor); m_linePen = QPen(m_fgColor); kDebug() << "Color" << m_bgColor << m_fgColor; update(); } void Pastebin::setInteractionState(InteractionState state) { switch (state ) { case Hovered: m_linePen.setStyle(Qt::DotLine); m_linePen.setWidth(2); showOverlay(true); break; case Waiting: showOverlay(false); break; case DraggedOver: m_linePen.setStyle(Qt::DashLine); m_linePen.setWidth(2); showOverlay(true); break; case Rejected: break; default: break; } m_interactionState = state; } void Pastebin::setActionState(ActionState state) { toolTipData = Plasma::ToolTipContent(); toolTipData.setAutohide(true); toolTipData.setMainText("Pastebin"); //TODO: choose icons for each state switch (state ) { case Unset: toolTipData.setSubText(i18nc("The status of the applet has not been set - i.e. it is unset.", "Unset")); toolTipData.setImage(KIcon("edit-paste")); break; case Idle: setBusy(false); toolTipData.setSubText(i18n("Drop text or an image onto me to upload it to Pastebin.")); toolTipData.setImage(KIcon("edit-paste")); break; case IdleError: setBusy(false); toolTipData.setSubText(i18n("Error during upload. Try again.")); toolTipData.setImage(KIcon("dialog-cancel")); // Notification ... QTimer::singleShot(15000, this, SLOT(resetActionState())); m_timer->stop(); break; case IdleSuccess: setBusy(false); toolTipData.setSubText(i18n("Successfully uploaded to %1.", m_url)); toolTipData.setImage(KIcon("dialog-ok")); // Notification ... QTimer::singleShot(15000, this, SLOT(resetActionState())); m_timer->stop(); break; case Sending: setBusy(true); toolTipData.setSubText(i18n("Sending....")); toolTipData.setImage(KIcon("view-history")); break; default: break; } Plasma::ToolTipManager::self()->setContent(this, toolTipData); m_actionState = state; update(); } void Pastebin::constraintsEvent(Plasma::Constraints constraints) { if (constraints & Plasma::FormFactorConstraint) { if (formFactor() == Plasma::Horizontal || formFactor() == Plasma::Vertical ) { setPreferredSize(-1, -1); } else { setPreferredSize(150, 150); } } } int Pastebin::iconSize() { // return the biggest fitting icon size from KIconLoader int c = qMin(contentsRect().width(), contentsRect().height()); int s; if (c >= KIconLoader::SizeEnormous) { // 128 s = KIconLoader::SizeEnormous; } else if (c >= KIconLoader::SizeHuge) { // 64 s = KIconLoader::SizeHuge; } else if (c >= KIconLoader::SizeLarge) { // 48 s = KIconLoader::SizeLarge; } else if (c >= KIconLoader::SizeMedium) { // 32 s = KIconLoader::SizeMedium; } else if (c >= KIconLoader::SizeSmallMedium) { // 32 s = KIconLoader::SizeSmallMedium; } else { // 16 s = KIconLoader::SizeSmall; } return s; } void Pastebin::paintPixmap(QPainter *painter, QPixmap &pixmap, const QRectF &rect, qreal opacity) { int size = pixmap.size().width(); QPointF iconOrigin = QPointF(rect.left() + (rect.width() - size) / 2, rect.top() + (rect.height() - size) / 2); painter->setRenderHint(QPainter::SmoothPixmapTransform); painter->setRenderHint(QPainter::Antialiasing); if (!painter->paintEngine()->hasFeature(QPaintEngine::ConstantOpacity)) { QPixmap temp(QSize(size, size)); temp.fill(Qt::transparent); QPainter p; p.begin(&temp); p.setCompositionMode(QPainter::CompositionMode_Source); p.drawPixmap(QPoint(0,0), pixmap); p.setCompositionMode(QPainter::CompositionMode_DestinationIn); p.fillRect(pixmap.rect(), QColor(0, 0, 0, opacity * 254)); p.end(); // draw the pixmap painter->drawPixmap(iconOrigin, temp); } else { // FIXME: Works, but makes hw acceleration impossible, use above code path qreal old = painter->opacity(); painter->setOpacity(opacity); painter->drawPixmap(iconOrigin, pixmap); painter->setOpacity(old); } } void Pastebin::paintInterface(QPainter *p, const QStyleOptionGraphicsItem *, const QRect &contentsRect) { if (!contentsRect.isValid() || isBusy()) { return; } // BusyWidget is being shown if (m_actionState == Sending) { return; } // Fade in the icon on top of it int iconsize = iconSize(); qreal pix_alpha = 1.0 - (0.5 * m_alpha); // Fading out to .5 QPointF iconOrigin = QPointF(contentsRect.left() + (contentsRect.width() - iconsize) / 2, contentsRect.top() + (contentsRect.height() - iconsize) / 2); QRectF iconRect = QRectF(iconOrigin, QSize(iconsize, iconsize)); if (m_actionState == IdleSuccess) { QPixmap iconok = KIcon("dialog-ok").pixmap(QSize(iconsize, iconsize)); paintPixmap(p, iconok, iconRect, pix_alpha); } else if (m_actionState == IdleError) { QPixmap iconok = KIcon("dialog-cancel").pixmap(QSize(iconsize, iconsize)); paintPixmap(p, iconok, iconRect, pix_alpha); } else { QPixmap iconpix = KIcon("edit-paste").pixmap(QSize(iconsize, iconsize)); if (!iconpix.isNull()) { paintPixmap(p, iconpix, iconRect, pix_alpha); } } // Draw background if (m_interactionState == DraggedOver) { m_fgColor.setAlphaF(m_alpha); } else if (m_interactionState == Hovered) { m_fgColor.setAlphaF(m_alpha * 0.15); } else { // Whatever, as long as it goes down to 0 m_fgColor.setAlphaF(m_alpha * 0.15); } m_bgColor.setAlphaF(m_alpha * 0.3); p->setBrush(m_bgColor); m_linePen.setColor(m_fgColor); p->setPen(m_linePen); p->setFont(m_font); qreal proportion = contentsRect.width() / contentsRect.height(); qreal round_radius = 35.0; p->drawRoundedRect(contentsRect, round_radius / proportion, round_radius, Qt::RelativeSize); } void Pastebin::hoverEnterEvent(QGraphicsSceneHoverEvent *event) { setInteractionState(Hovered); Applet::hoverEnterEvent(event); } void Pastebin::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) { setInteractionState(Waiting); Applet::hoverLeaveEvent(event); } void Pastebin::showOverlay(bool show) { if (m_fadeIn == show) { return; } m_fadeIn = show; QPropertyAnimation *animation = m_animation.data(); if (!animation) { animation = new QPropertyAnimation(this, "animationUpdate"); animation->setDuration(400); animation->setStartValue(0.0); animation->setEndValue(1.0); animation->setEasingCurve(QEasingCurve::Linear); m_animation = animation; } else if (animation->state() == QAbstractAnimation::Running) { animation->pause(); } if (m_fadeIn) { animation->setDirection(QAbstractAnimation::Forward); animation->start(QAbstractAnimation::KeepWhenStopped); } else { animation->setDirection(QAbstractAnimation::Backward); animation->start(QAbstractAnimation::DeleteWhenStopped); } } qreal Pastebin::animationValue() const { return m_alpha; } void Pastebin::animationUpdate(qreal progress) { m_alpha = progress; update(); } void Pastebin::refreshConfigDialog() { // setup text uiConfig.textServer->clear(); uiConfig.textServer->addItems(m_txtServers.keys()); // setup image uiConfig.imageServer->clear(); uiConfig.imageServer->addItems(m_imgServers.keys()); } QString Pastebin::getDefaultTextServer() { QString defaultServer = "paste.kde.org"; if ( m_txtServers.contains(defaultServer) ) { return defaultServer; } else { return m_txtServers.keys().at(0); } } void Pastebin::createConfigurationInterface(KConfigDialog *parent) { KConfigGroup cg = config(); QWidget *general = new QWidget(); uiConfig.setupUi(general); connect(parent, SIGNAL(okClicked()), this, SLOT(configAccepted())); connect(parent, SIGNAL(applyClicked()), this, SLOT(configAccepted())); parent->addPage(general, i18n("General"), Applet::icon()); refreshConfigDialog(); uiConfig.textServer->setCurrentItem(cg.readEntry("TextProvider", getDefaultTextServer())); uiConfig.imageServer->setCurrentItem(cg.readEntry("ImageProvider", m_imgServers.keys().at(0))); uiConfig.historySize->setValue(m_historySize); connect(uiConfig.textServer , SIGNAL(currentIndexChanged(int)) , parent, SLOT(settingsModified())); connect(uiConfig.imageServer , SIGNAL(currentIndexChanged(int)) , parent, SLOT(settingsModified())); connect(uiConfig.historySize , SIGNAL(valueChanged(int)) , parent, SLOT(settingsModified())); } void Pastebin::configAccepted() { KConfigGroup cg = config(); int historySize = uiConfig.historySize->value(); setHistorySize(historySize); cg.writeEntry("TextProvider", uiConfig.textServer->currentText()); cg.writeEntry("ImageProvider", uiConfig.imageServer->currentText()); cg.writeEntry("HistorySize", historySize); saveHistory(); emit configNeedsSaving(); } void Pastebin::configChanged() { KConfigGroup cg = config(); int historySize = cg.readEntry("HistorySize", "3").toInt(); QStringList history = cg.readEntry("History", "").split('|', QString::SkipEmptyParts); m_actionHistory.clear(); setHistorySize(historySize); const int numberOfItems = history.size(); for (int i = 0; i < numberOfItems; ++i) { addToHistory(history.at(i)); } } void Pastebin::showResults(const QString &url) { m_timer->stop(); m_url = url; setActionState(IdleSuccess); copyToClipboard(url); addToHistory(url); } void Pastebin::copyToClipboard(const QString &url) { QApplication::clipboard()->setText(url, lastMode); kDebug() << "Copying:" << url; m_oldUrl = url; QPixmap pix = KIcon("edit-paste").pixmap(KIconLoader::SizeMedium, KIconLoader::SizeMedium); KNotification *notify = new KNotification("urlcopied"); notify->setComponentData(KComponentData("plasma_pastebin")); notify->setText(i18nc("Notification when the pastebin applet has copied the URL to the clipboard", "The URL for your paste has been copied to the clipboard")); notify->setPixmap(pix); notify->setActions(QStringList(i18n("Open browser"))); connect(notify, SIGNAL(action1Activated()), this, SLOT(openLink())); notify->sendEvent(); } void Pastebin::showErrors() { setActionState(IdleError); } void Pastebin::openLink(bool old) { if(old) { KToolInvocation::invokeBrowser(m_oldUrl); } else { KToolInvocation::invokeBrowser(m_url); } } void Pastebin::resetActionState() { setActionState(Idle); update(); } void Pastebin::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (m_url.isEmpty() || event->button() != Qt::LeftButton) { Plasma::Applet::mousePressEvent(event); } else { openLink(false); } if (event->button() == Qt::MidButton) { if (m_actionState == Idle) { // paste clipboard content postClipboard(true); } else { // Now releasing the middlebutton click copies to clipboard event->accept(); } } } void Pastebin::mouseReleaseEvent(QGraphicsSceneMouseEvent * event) { if (!m_url.isEmpty() && event->button() == Qt::MidButton && m_actionState == IdleSuccess) { copyToClipboard(m_url); } } void Pastebin::dragEnterEvent(QGraphicsSceneDragDropEvent *event) { InteractionState istate = Rejected; if (event->mimeData()->hasFormat("text/plain")) { event->acceptProposedAction(); } foreach (const QString &f, event->mimeData()->formats()) { if (f.indexOf("image/") != -1) { istate = DraggedOver; } } if (event->mimeData()->hasImage() || event->mimeData()->hasText()) { istate = DraggedOver; } setInteractionState(istate); } void Pastebin::dragLeaveEvent(QGraphicsSceneDragDropEvent *event) { Q_UNUSED(event); setInteractionState(Waiting); } void Pastebin::dragMoveEvent(QGraphicsSceneDragDropEvent *event) { event->accept(); } void Pastebin::dropEvent(QGraphicsSceneDragDropEvent *event) { if (event->mimeData()->objectName() != QString("Pastebin-applet")) { lastMode = QClipboard::Clipboard; #ifdef Q_WS_WIN // Apparently, Windows doesn't pass any actual image data when drag'n'dropping // image files. Though, it does provide us with those files' Urls. Since posting // multiple images isn't yet implemented - we'll use first Url from list QImage image; QString imageFileName; if (event->mimeData()->hasUrls()) { imageFileName = event->mimeData()->urls().at(0).toLocalFile(); image.load(imageFileName); postContent(imageFileName, image); } else { postContent(event->mimeData()->text(), image); }; #else QImage image = qvariant_cast(event->mimeData()->imageData()); postContent(event->mimeData()->text(), image); #endif //Q_WS_WIN event->acceptProposedAction(); } } void Pastebin::addToHistory(const QString &url) { if (m_historySize <= 0) { return; } if (m_actionHistory.size() >= m_historySize) { delete m_actionHistory.takeLast(); } QAction *ac = new QAction(url, this); m_actionHistory.insert(0, ac); m_signalMapper->setMapping(ac, url); connect(ac, SIGNAL(triggered(bool)), m_signalMapper, SLOT(map())); } QList Pastebin::contextualActions() { if (!m_paste) { m_paste = KStandardAction::paste(this); connect(m_paste, SIGNAL(triggered(bool)), this, SLOT(postClipboard())); } if (!m_topSeparator) { m_topSeparator = new QAction(this); m_topSeparator->setSeparator(true); } if (!m_bottomSeparator) { m_bottomSeparator = new QAction(this); m_bottomSeparator->setSeparator(true); } m_contextualActions.clear(); m_contextualActions.append(m_paste); m_contextualActions.append(m_topSeparator); m_contextualActions.append(m_actionHistory); if (!m_actionHistory.isEmpty()) { m_contextualActions.append(m_bottomSeparator); } return m_contextualActions; } void Pastebin::postClipboard() { postClipboard(false); } void Pastebin::postClipboard(bool preferSelection) { lastMode = QClipboard::Clipboard; #ifdef Q_WS_WIN // Same as for D'n'D, Windows doesn't pass any actual image data when pasting // image files. Though, it does provide us with those files' Urls. Since posting // multiple images isn't yet implemented - we'll use first Url from list QImage image; QString imageFileName; if (QApplication::clipboard()->mimeData()->hasUrls()) { imageFileName = QApplication::clipboard()->mimeData()->urls().at(0).toLocalFile(); image.load(imageFileName); postContent(imageFileName, image); } else { postContent(QApplication::clipboard()->mimeData()->text(), image); }; #else if (preferSelection) { lastMode = QApplication::clipboard()->supportsSelection() ? QClipboard::Selection : QClipboard::Clipboard; postContent(QApplication::clipboard()->text(lastMode), QApplication::clipboard()->image(lastMode)); } else { postContent(QApplication::clipboard()->text(), QApplication::clipboard()->image()); } #endif //Q_WS_WIN } void Pastebin::postContent(QString text, const QImage& imageData) { QString sourceName; KUrl testPath(text); bool validPath = true; // use KIO to check if the file exists using mimetype job KIO::MimetypeJob *mjob = KIO::mimetype(testPath); if (!mjob->exec()) { // it's not a file - usually this happens when we are // just sharing plain text or image validPath = false; } KConfigGroup cg = config(); // This is needed to provide smooth transition between old config and new one const QString txtProvider = cg.readEntry("TextProvider", getDefaultTextServer()); const QString imgProvider = cg.readEntry("ImageProvider", m_imgServers.keys().at(0)); bool isTemporary = false; if (validPath) { KMimeType::Ptr type = KMimeType::findByPath(testPath.path()); if (type->name().indexOf("image/") != -1) { // its image sourceName = m_imgServers.value(imgProvider); } else { // its text sourceName = m_txtServers.value(txtProvider); } } else if (imageData.isNull()) { sourceName = m_txtServers.value(txtProvider); } else { sourceName = m_imgServers.value(imgProvider); KTemporaryFile tempFile; tempFile.setSuffix(".png"); if (tempFile.open()) { tempFile.setAutoRemove(false); imageData.save(&tempFile, "PNG"); tempFile.close(); text = tempFile.fileName(); isTemporary = true; } else { setActionState(IdleError); return; } } kDebug() << "Is valid path: " << validPath; kDebug() << "Provider used: " << sourceName; if (sourceName.isEmpty()) { // no provider was configured showErrors(); return; } m_postingService = m_engine->serviceForSource(sourceName); KConfigGroup ops = m_postingService->operationDescription("share"); ops.writeEntry("content", text); Plasma::ServiceJob *job = m_postingService->startOperationCall(ops); if (isTemporary) // Store tempfile-job mapping for cleanup when finished. m_pendingTempFileJobs[job] = text; connect(job, SIGNAL(finished(KJob*)), this, SLOT(postingFinished(KJob*))); setActionState(Sending); m_timer->start(20000); } void Pastebin::postingFinished(KJob *job) { Plasma::ServiceJob *sjob = static_cast(job); if (sjob->error()) { showErrors(); } else { showResults(sjob->result().toString()); } // Cleanup of temp file... QString tempUrl = m_pendingTempFileJobs.take(job); if (!tempUrl.isEmpty()) KIO::file_delete(KUrl(tempUrl), KIO::HideProgressInfo); } #include "moc_pastebin.cpp"