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) 2020       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 "destinationapplication.h"
0032 
0033 #include <qmimetype.h>
0034 #include <qcombobox.h>
0035 #include <qlabel.h>
0036 
0037 #include <kapplicationtrader.h>
0038 #include <kpluginfactory.h>
0039 #include <kservice.h>
0040 #include <klocalizedstring.h>
0041 #include <kio/applicationlauncherjob.h>
0042 #include <kio/jobuidelegatefactory.h>
0043 
0044 #include "scanparamspage.h"
0045 #include "kookasettings.h"
0046 #include "destination_logging.h"
0047 
0048 
0049 K_PLUGIN_FACTORY_WITH_JSON(DestinationApplicationFactory, "kookadestination-application.json", registerPlugin<DestinationApplication>();)
0050 #include "destinationapplication.moc"
0051 
0052 
0053 DestinationApplication::DestinationApplication(QObject *pnt, const QVariantList &args)
0054     : AbstractDestination(pnt, "DestinationApplication")
0055 {
0056 }
0057 
0058 
0059 void DestinationApplication::imageScanned(ScanImage::Ptr img)
0060 {
0061     qCDebug(DESTINATION_LOG) << "received image size" << img->size() << "type" << img->imageType();
0062     const QString appService = mAppsCombo->currentData().toString();
0063     const QString mimeName = mFormatCombo->currentData().toString();
0064     qCDebug(DESTINATION_LOG) << "app" << appService << "mime" << mimeName;
0065 
0066     ImageFormat fmt = getSaveFormat(mimeName, img); // get format for saving image
0067     if (!fmt.isValid()) return;             // must have this now
0068     const QUrl saveUrl = saveTempImage(fmt, img);   // save to temporary file
0069     if (!saveUrl.isValid()) return;         // could not save image
0070 
0071     // Open the temporary file with the selected application service.
0072     // If the service is "Other" (appService is empty), or if there is
0073     // a problem finding the service, then leave 'service' as null and
0074     // the ApplicationLauncherJob will prompt for an application.
0075     // The temporary file will eventually be removed by KIO.
0076     KService::Ptr service;
0077     if (!appService.isEmpty())
0078     {
0079         service = KService::serviceByDesktopName(appService);
0080         if (service==nullptr) qCWarning(DESTINATION_LOG) << "Cannot find service" << appService;
0081     }
0082 
0083     KIO::ApplicationLauncherJob *job = new KIO::ApplicationLauncherJob(service);
0084     job->setUrls(QList<QUrl>() << saveUrl);
0085     job->setRunFlags(KIO::ApplicationLauncherJob::DeleteTemporaryFiles);
0086     job->setUiDelegate(KIO::createDefaultJobUiDelegate(KJobUiDelegate::AutoHandlingEnabled, parentWidget()));
0087     job->start();                   // all done
0088 }
0089 
0090 
0091 KLocalizedString DestinationApplication::scanDestinationString()
0092 {
0093     const QString appService = mAppsCombo->currentData().toString();
0094     if (appService.isEmpty()) return (ki18n("Sending to application"));
0095                             // selected "Other"
0096     return (kxi18n("Sending to <application>%1</application>").subs(mAppsCombo->currentText()));
0097 }                           // with application name
0098 
0099 
0100 void DestinationApplication::createGUI(ScanParamsPage *page)
0101 {
0102     // We do not yet know the eventual image format of the scanned image.
0103     // Therefore we would like, here in the GUI, to offer all of the known
0104     // applications that can handle any image type.  However, it does not seem
0105     // to be possible to express this in the trader query language;  according
0106     // to https://techbase.kde.org/Development/Tutorials/Services/Traders a
0107     // query such as
0108     //
0109     //   'image/' subin ServiceTypes
0110     //
0111     // should perform a substring match on all of the list entries.  However,
0112     // this sort of query appears to return nothing.
0113     //
0114     // Instead we query all of the application service types and then examine
0115     // their supported MIME types.  The criterion for including an application
0116     // is that it supports an image MIME type (starting with "image/") which is
0117     // also supported as a QImageWriter format (that is, a format that Kooka can
0118     // save to).
0119 
0120     const KService::List allServices = KApplicationTrader::query([](const KService::Ptr &)
0121     {
0122         return (true);
0123     });
0124     qCDebug(DESTINATION_LOG) << "have" << allServices.count() << "services";
0125     const QList<QMimeType> *imageMimeTypes = ImageFormat::mimeTypes();
0126 
0127     KService::List validServices;
0128     for (const KService::Ptr &service : allServices)
0129     {
0130         // Okular is an odd case.  For whatever reason, the application does
0131         // not have just one desktop file listing all of the MIME types that
0132         // it supports, but a number of such files each listing one or a
0133         // closely related set of MIME types.  I suspect that this is so that
0134         // there can be one desktop file for each generator with the MIME
0135         // types that the generator supports.  There was a similar hack for
0136         // Ark (https://git.reviewboard.kde.org/r/129617 from 2011) that
0137         // collected a list of MIME types supported by each plugin and passed
0138         // them up to the top level CMakeLists.txt, then using the collected
0139         // list to generate the desktop files, but it was remarked at the time
0140         // that it would be more complicated to do this for Okular.
0141         //
0142         // Since the criteria used to select an application for including in
0143         // the "preferrred applications" list is that it supports at least one
0144         // image MIME type that we also support, this would result in multiple
0145         // entries for Okular.  Because the command used to start Okular is the
0146         // same in every case, we special case detect the main Okular application
0147         // here and ignore all of the others (along with any other NoDisplay
0148         // services).
0149         if (service->desktopEntryName()=="org.kde.okular")
0150         {
0151             qCDebug(DESTINATION_LOG) << "accept" << service->desktopEntryName() << "by name, pref" << service->initialPreference();
0152             validServices.append(service);
0153             continue;
0154         }
0155 
0156         if (service->noDisplay()) continue;     // ignore hidden services
0157         if (service->mimeTypes().isEmpty()) continue;   // ignore those with no MIME types
0158         //qCDebug(DESTINATION_LOG) << "  " << service->mimeTypes();
0159 
0160         for (const QString &mimeType : service->mimeTypes())
0161         {
0162             if (!mimeType.startsWith("image/")) continue;
0163             for (const QMimeType &imt : *imageMimeTypes)
0164             {                       // supports a MIME type that we also do
0165                 if (imt.inherits(mimeType)) goto found;
0166             }
0167         }
0168         continue;                   // service not accepted
0169 
0170 found:  qCDebug(DESTINATION_LOG) << "accept" << service->desktopEntryName() << "by MIME, pref" << service->initialPreference();
0171         validServices.append(service);
0172     }
0173 
0174     // Now all of the applications that accept file formats that can be
0175     // saved by Kooka are listed.  Fortunately the original trader query
0176     // returned them in priority order, so there is no need to sort them.
0177     qCDebug(DESTINATION_LOG) << "have" << validServices.count() << "valid services";
0178 
0179     mAppsCombo = new QComboBox;
0180 
0181     const QString configuredApp = KookaSettings::destinationApplicationApp();
0182     int configuredIndex = -1;
0183     for (const KService::Ptr &service : qAsConst(validServices))
0184     {
0185         const QString key = service->desktopEntryName();
0186         if (key==configuredApp) configuredIndex = mAppsCombo->count();
0187         mAppsCombo->addItem(QIcon::fromTheme(service->icon()), service->name(), key);
0188     }
0189     if (configuredApp=="") configuredIndex = mAppsCombo->count();
0190     mAppsCombo->addItem(QIcon::fromTheme("system-run"), i18n("Other..."));
0191     if (configuredIndex!=-1) mAppsCombo->setCurrentIndex(configuredIndex);
0192     page->addRow(i18n("Application:"), mAppsCombo);
0193 
0194     // The MIME types that can be selected for sending the image.
0195     QStringList mimeTypes;
0196     mimeTypes << "image/png" << "image/jpeg" << "image/tiff" << "image/x-eps" << "image/bmp";
0197     mFormatCombo = createFormatCombo(mimeTypes, KookaSettings::destinationApplicationMime());
0198     page->addRow(i18n("Image format:"), mFormatCombo);
0199 }
0200 
0201 
0202 void DestinationApplication::saveSettings() const
0203 {
0204     KookaSettings::setDestinationApplicationApp(mAppsCombo->currentData().toString());
0205     KookaSettings::setDestinationApplicationMime(mFormatCombo->currentData().toString());
0206 }