File indexing completed on 2024-05-12 15:55:06

0001 /************************************************************************
0002  *                                  *
0003  *  This file is part of Kooka, a scanning/OCR application using    *
0004  *  Qt <http://www.qt.io> and KDE Frameworks <http://www.kde.org>.  *
0005  *                                  *
0006  *  Copyright (C) 2021      Jonathan Marten <jjm@keelhaul.me.uk>    *
0007  *                                  *
0008  *  Kooka is free software; you can redistribute it and/or modify it    *
0009  *  under the terms of the GNU Library General Public License as    *
0010  *  published by the Free Software Foundation and appearing in the  *
0011  *  file COPYING included in the packaging of this file;  either    *
0012  *  version 2 of the License, or (at your option) any later version.    *
0013  *                                  *
0014  *  As a special exception, permission is given to link this program    *
0015  *  with any version of the KADMOS OCR/ICR engine (a product of     *
0016  *  reRecognition GmbH, Kreuzlingen), and distribute the resulting  *
0017  *  executable without including the source code for KADMOS in the  *
0018  *  source distribution.                        *
0019  *                                  *
0020  *  This program is distributed in the hope that it will be useful, *
0021  *  but WITHOUT ANY WARRANTY; without even the implied warranty of  *
0022  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the   *
0023  *  GNU General Public License for more details.            *
0024  *                                  *
0025  *  You should have received a copy of the GNU General Public       *
0026  *  License along with this program;  see the file COPYING.  If     *
0027  *  not, see <http://www.gnu.org/licenses/>.                *
0028  *                                  *
0029  ************************************************************************/
0030 
0031 #include "destinationshare.h"
0032 
0033 #include <qcombobox.h>
0034 #include <qjsonarray.h>
0035 
0036 #include <kpluginfactory.h>
0037 #include <klocalizedstring.h>
0038 #include <kmessagebox.h>
0039 
0040 #include <kio/global.h>
0041 
0042 #include <purpose/alternativesmodel.h>
0043 #include <purpose/menu.h>
0044 
0045 #include "scanparamspage.h"
0046 #include "kookasettings.h"
0047 #include "destination_logging.h"
0048 
0049 
0050 K_PLUGIN_FACTORY_WITH_JSON(DestinationShareFactory, "kookadestination-share.json", registerPlugin<DestinationShare>();)
0051 #include "destinationshare.moc"
0052 
0053 
0054 DestinationShare::DestinationShare(QObject *pnt, const QVariantList &args)
0055     : AbstractDestination(pnt, "DestinationShare")
0056 {
0057     // The list of available share destinations can be obtained simply from
0058     // an AlternativesModel, but we create a Purpose::Menu so as to be able
0059     // to also use it for launching the share job.  Otherwise the whole of
0060     // Purpose::MenuPrivate::trigger() would need to be duplicated in
0061     // imageScanned().
0062     mMenu = new Purpose::Menu(parentWidget());
0063     connect(mMenu, &Purpose::Menu::finished, this, &DestinationShare::slotShareFinished);
0064 
0065     // This is the Purpose::AlternativesModel created by the Purpose::Menu.
0066     mModel = mMenu->model();
0067 }
0068 
0069 
0070 void DestinationShare::imageScanned(ScanImage::Ptr img)
0071 {
0072     qCDebug(DESTINATION_LOG) << "received image size" << img->size();
0073     const QString shareService = mShareCombo->currentData().toString();
0074     const QString mimeName = mFormatCombo->currentData().toString();
0075     qCDebug(DESTINATION_LOG) << "share" << shareService << "mime" << mimeName;
0076 
0077     ImageFormat fmt = getSaveFormat(mimeName, img); // get format for saving image
0078     if (!fmt.isValid()) return;             // must have this now
0079     mSaveUrl = saveTempImage(fmt, img);         // save to temporary file
0080     if (!mSaveUrl.isValid()) return;            // could not save image
0081 
0082     // Because we did not know the specific MIME type at the time, the
0083     // original menu and hence the share destination combo box will have
0084     // been filled with actions for the generic "image/*" MIME type.
0085     // Now the MIME type is known and we need to specify it.
0086     // 
0087     // Hopefully the list of shared destinations will not change as a
0088     // result of the more specific MIME type.  Just in case it does,
0089     // note the selected share ID before setting the MIME type and
0090     // find it again before triggering the menu action.
0091 
0092     QJsonObject dataObject;
0093     dataObject.insert("mimeType", QJsonValue(mimeName));
0094     QJsonArray dataUrls;
0095     dataUrls.append(mSaveUrl.url());
0096     dataObject.insert("urls", dataUrls);
0097 
0098     mModel->setInputData(dataObject);           // set MIME type and URL
0099     mMenu->reload();                    // regenerate the menu
0100 
0101     int foundRow = -1;
0102     for (int i = 0; i<mModel->rowCount(); ++i)      // search through new model
0103     {
0104         QModelIndex idx = mModel->index(i, 0);
0105         const QString key = mModel->data(idx, Purpose::AlternativesModel::PluginIdRole).toString();
0106         if (key==shareService)
0107         {
0108             foundRow = i;
0109             break;
0110         }
0111     }
0112 
0113     if (foundRow==-1)                   // couldn't find share in new menu
0114     {
0115         qCWarning(DESTINATION_LOG) << "Cannot find service for updated MIME type, count" << mModel->rowCount();
0116         return;
0117     }
0118 
0119     QAction *act = mMenu->actions().value(foundRow);    // get action from menu
0120     Q_ASSERT(act!=nullptr);
0121     act->trigger();                 // do the share action
0122 
0123     // There is nothing more to do here, the temporary file will eventually
0124     // be deleted in slotShareFinished().
0125 }
0126 
0127 
0128 void DestinationShare::createGUI(ScanParamsPage *page)
0129 {
0130     qCDebug(DESTINATION_LOG);
0131 
0132     // The MIME types that can be selected for sharing the image.
0133     QStringList mimeTypes;
0134     mimeTypes << "image/png" << "image/jpeg" << "image/tiff";
0135     mFormatCombo = createFormatCombo(mimeTypes, KookaSettings::destinationShareMime());
0136     connect(mFormatCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &DestinationShare::slotUpdateShareCombo);
0137     page->addRow(i18n("Image format:"), mFormatCombo);
0138 
0139     // The share destinations that are available depend on the MIME type
0140     // as selected above.
0141     mShareCombo = new QComboBox;
0142     slotUpdateShareCombo();
0143     page->addRow(i18n("Share to:"), mShareCombo);
0144 }
0145 
0146 
0147 KLocalizedString DestinationShare::scanDestinationString()
0148 {
0149     // Can't say "Sharing to..." or anything like that, because it
0150     // looks clumsy when it says "Sharing to Send via Bluetooth".
0151     return (kxi18n("<application>%1</application>").subs(mShareCombo->currentText()));
0152 }
0153 
0154 
0155 void DestinationShare::saveSettings() const
0156 {
0157     KookaSettings::setDestinationShareDest(mShareCombo->currentData().toString());
0158     KookaSettings::setDestinationShareMime(mFormatCombo->currentData().toString());
0159 }
0160 
0161 
0162 void DestinationShare::slotUpdateShareCombo()
0163 {
0164     QString mimeType = mFormatCombo->currentData().toString();
0165     // If the MIME type is "Other" then we do not have the type
0166     // available at this stage, so accept plugins that can share
0167     // any image type.
0168     if (mimeType.isEmpty()) mimeType = "image/*";
0169     qCDebug(DESTINATION_LOG) << "for MIME" << mimeType;
0170 
0171     QString configuredShare = mShareCombo->currentData().toString();
0172     if (configuredShare.isEmpty()) configuredShare = KookaSettings::destinationShareDest();
0173     int configuredIndex = -1;
0174     qCDebug(DESTINATION_LOG) << "current" << configuredShare;
0175 
0176     QJsonObject dataObject;
0177     dataObject.insert("mimeType", QJsonValue(mimeType));
0178     dataObject.insert("urls", QJsonArray());        // not relevant at this stage
0179     mModel->setInputData(dataObject);
0180     mModel->setPluginType(QStringLiteral("Export"));
0181     qCDebug(DESTINATION_LOG) << "have" << mModel->rowCount() << "share destinations";
0182 
0183     mShareCombo->clear();
0184     for (int i = 0; i<mModel->rowCount(); ++i)
0185     {
0186         QModelIndex idx = mModel->index(i, 0);
0187         const QString key = mModel->data(idx, Purpose::AlternativesModel::PluginIdRole).toString();
0188         qCDebug(DESTINATION_LOG) << "  " << i << key << mModel->data(idx, Qt::DisplayRole).toString();
0189 
0190         if (!configuredShare.isEmpty() && key==configuredShare) configuredIndex = mShareCombo->count();
0191 
0192         mShareCombo->addItem(mModel->data(idx, Qt::DecorationRole).value<QIcon>(),
0193                              mModel->data(idx, Qt::DisplayRole).toString(), key);
0194     }
0195 
0196     if (configuredIndex!=-1) mShareCombo->setCurrentIndex(configuredIndex);
0197 }
0198 
0199 
0200 void DestinationShare::slotShareFinished(const QJsonObject &output, int error, const QString &errorMessage)
0201 {
0202     // Based on the lambda in ShareFileItemAction::ShareFileItemAction()
0203     // The finished() signal is emitted by purpose/src/widgets/JobDialog.qml
0204     qCDebug(DESTINATION_LOG) << "error" << error << "output" << output;
0205     if (error==0 || error==KIO::ERR_USER_CANCELED)
0206     {
0207         // Report the result URL if there is one in the share output.
0208         // Test case for this is Imgur (no account/password needed).
0209         if (output.contains(QLatin1String("url")))
0210         {
0211             QString url = output.value(QLatin1String("url")).toString();
0212 
0213             // It may be more friendly to use a KMessageWidget (including
0214             // a "Copy link" button) for this, but vertical space in the
0215             // ScanParams area is already at a premium.
0216             KMessageBox::information(parentWidget(),
0217                                      xi18nc("@info", "The scan was shared to<nl/><link>%1</link>", url),
0218                                      i18n("Scan Shared"),
0219                                      QString(),
0220                                      KMessageBox::Notify|KMessageBox::AllowLink);
0221         }
0222     }
0223     else
0224     {
0225         qCWarning(DESTINATION_LOG) << "job failed with error" << error << errorMessage << output;
0226         KMessageBox::error(parentWidget(),
0227                            xi18nc("@info", "Cannot share the scanned image<nl/><nl/><message>%1</message>", errorMessage));
0228     }
0229 
0230     delayedDelete(mSaveUrl);                // delete temporary file, eventually
0231     mSaveUrl.clear();                   // have dealt with this now
0232 }