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 }