File indexing completed on 2024-04-28 15:29:19
0001 /* 0002 SPDX-FileCopyrightText: 2009, 2010 David Faure <faure@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "browseropenorsavequestion.h" 0008 0009 #include <KConfigGroup> 0010 #include <KFileItemActions> 0011 #include <KGuiItem> 0012 #include <KLocalizedString> 0013 #include <KMessageBox> 0014 #include <KSharedConfig> 0015 #include <KSqueezedTextLabel> 0016 #include <KStandardGuiItem> 0017 0018 #include <QAction> 0019 #include <QCheckBox> 0020 #include <QDialog> 0021 #include <QDialogButtonBox> 0022 #include <QLabel> 0023 #include <QMenu> 0024 #include <QMimeDatabase> 0025 #include <QPushButton> 0026 #include <QStyle> 0027 #include <QStyleOption> 0028 #include <QVBoxLayout> 0029 0030 using namespace KParts; 0031 Q_DECLARE_METATYPE(KService::Ptr) 0032 0033 class KParts::BrowserOpenOrSaveQuestionPrivate : public QDialog 0034 { 0035 Q_OBJECT 0036 public: 0037 enum { 0038 Save = QDialog::Accepted, 0039 OpenDefault = Save + 1, 0040 OpenWith = OpenDefault + 1, 0041 Cancel = QDialog::Rejected, 0042 }; 0043 0044 BrowserOpenOrSaveQuestionPrivate(QWidget *parent, const QUrl &url, const QString &mimeType) 0045 : QDialog(parent) 0046 , url(url) 0047 , mimeType(mimeType) 0048 , features(BrowserOpenOrSaveQuestion::BasicFeatures) 0049 { 0050 // Use askSave or askEmbedOrSave from filetypesrc 0051 dontAskConfig = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals); 0052 0053 setWindowTitle(url.host()); 0054 setObjectName(QStringLiteral("questionYesNoCancel")); 0055 0056 QVBoxLayout *mainLayout = new QVBoxLayout(this); 0057 const int verticalSpacing = style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing); 0058 mainLayout->setSpacing(verticalSpacing * 2); // provide extra spacing 0059 0060 QHBoxLayout *hLayout = new QHBoxLayout(); 0061 mainLayout->addLayout(hLayout, 5); 0062 0063 QLabel *iconLabel = new QLabel(this); 0064 QStyleOption option; 0065 option.initFrom(this); 0066 QIcon icon = QIcon::fromTheme(QStringLiteral("dialog-information")); 0067 iconLabel->setPixmap(icon.pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, this))); 0068 0069 hLayout->addWidget(iconLabel, 0, Qt::AlignCenter); 0070 const int horizontalSpacing = style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); 0071 hLayout->addSpacing(horizontalSpacing); 0072 0073 QVBoxLayout *textVLayout = new QVBoxLayout; 0074 questionLabel = new KSqueezedTextLabel(this); 0075 textVLayout->addWidget(questionLabel); 0076 0077 fileNameLabel = new QLabel(this); 0078 fileNameLabel->hide(); 0079 textVLayout->addWidget(fileNameLabel); 0080 0081 QMimeDatabase db; 0082 mime = db.mimeTypeForName(mimeType); 0083 QString mimeDescription(mimeType); 0084 if (mime.isValid()) { 0085 // Always prefer the mime-type comment over the raw type for display 0086 mimeDescription = (mime.comment().isEmpty() ? mime.name() : mime.comment()); 0087 } 0088 QLabel *mimeTypeLabel = new QLabel(this); 0089 mimeTypeLabel->setText(i18nc("@label Type of file", "Type: %1", mimeDescription)); 0090 mimeTypeLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); 0091 textVLayout->addWidget(mimeTypeLabel); 0092 0093 hLayout->addLayout(textVLayout, 5); 0094 0095 mainLayout->addStretch(15); 0096 dontAskAgainCheckBox = new QCheckBox(this); 0097 dontAskAgainCheckBox->setText(i18nc("@label:checkbox", "Remember action for files of this type")); 0098 mainLayout->addWidget(dontAskAgainCheckBox); 0099 0100 buttonBox = new QDialogButtonBox(this); 0101 0102 saveButton = buttonBox->addButton(QDialogButtonBox::Yes); 0103 saveButton->setObjectName(QStringLiteral("saveButton")); 0104 KGuiItem::assign(saveButton, KStandardGuiItem::saveAs()); 0105 saveButton->setDefault(true); 0106 0107 openDefaultButton = new QPushButton; 0108 openDefaultButton->setObjectName(QStringLiteral("openDefaultButton")); 0109 buttonBox->addButton(openDefaultButton, QDialogButtonBox::ActionRole); 0110 0111 openWithButton = new QPushButton; 0112 openWithButton->setObjectName(QStringLiteral("openWithButton")); 0113 buttonBox->addButton(openWithButton, QDialogButtonBox::ActionRole); 0114 0115 QPushButton *cancelButton = buttonBox->addButton(QDialogButtonBox::Cancel); 0116 cancelButton->setObjectName(QStringLiteral("cancelButton")); 0117 0118 connect(saveButton, &QPushButton::clicked, this, &BrowserOpenOrSaveQuestionPrivate::slotYesClicked); 0119 connect(openDefaultButton, &QPushButton::clicked, this, &BrowserOpenOrSaveQuestionPrivate::slotOpenDefaultClicked); 0120 connect(openWithButton, &QPushButton::clicked, this, &BrowserOpenOrSaveQuestionPrivate::slotOpenWithClicked); 0121 connect(buttonBox, &QDialogButtonBox::rejected, this, &BrowserOpenOrSaveQuestionPrivate::reject); 0122 0123 mainLayout->addWidget(buttonBox); 0124 } 0125 0126 bool autoEmbedMimeType(int flags); 0127 0128 int executeDialog(const QString &dontShowAgainName) 0129 { 0130 KConfigGroup cg(dontAskConfig, "Notification Messages"); // group name comes from KMessageBox 0131 const QString dontAsk = cg.readEntry(dontShowAgainName, QString()).toLower(); 0132 if (dontAsk == QLatin1String("yes") || dontAsk == QLatin1String("true")) { 0133 return Save; 0134 } else if (dontAsk == QLatin1String("no") || dontAsk == QLatin1String("false")) { 0135 return OpenDefault; 0136 } 0137 0138 const int result = exec(); 0139 0140 if (dontAskAgainCheckBox->isChecked()) { 0141 cg.writeEntry(dontShowAgainName, result == BrowserOpenOrSaveQuestion::Save); 0142 cg.sync(); 0143 } 0144 return result; 0145 } 0146 0147 void showService(KService::Ptr selectedService) 0148 { 0149 KGuiItem openItem(i18nc("@label:button", "&Open with %1", selectedService->name()), selectedService->icon()); 0150 KGuiItem::assign(openWithButton, openItem); 0151 } 0152 0153 QUrl url; 0154 QString mimeType; 0155 QMimeType mime; 0156 KService::Ptr selectedService; 0157 KSqueezedTextLabel *questionLabel; 0158 BrowserOpenOrSaveQuestion::Features features; 0159 QLabel *fileNameLabel; 0160 QDialogButtonBox *buttonBox; 0161 QPushButton *saveButton; 0162 QPushButton *openDefaultButton; 0163 QPushButton *openWithButton; 0164 0165 private: 0166 QCheckBox *dontAskAgainCheckBox; 0167 KSharedConfig::Ptr dontAskConfig; 0168 0169 public Q_SLOTS: 0170 void reject() override 0171 { 0172 selectedService = nullptr; 0173 QDialog::reject(); 0174 } 0175 0176 void slotYesClicked() 0177 { 0178 selectedService = nullptr; 0179 done(Save); 0180 } 0181 0182 void slotOpenDefaultClicked() 0183 { 0184 done(OpenDefault); 0185 } 0186 0187 void slotOpenWithClicked() 0188 { 0189 if (!openWithButton->menu()) { 0190 selectedService = nullptr; 0191 done(OpenWith); 0192 } 0193 } 0194 0195 void slotAppSelected(QAction *action) 0196 { 0197 selectedService = action->data().value<KService::Ptr>(); 0198 // showService(selectedService); 0199 done(OpenDefault); 0200 } 0201 }; 0202 0203 BrowserOpenOrSaveQuestion::BrowserOpenOrSaveQuestion(QWidget *parent, const QUrl &url, const QString &mimeType) 0204 : d(new BrowserOpenOrSaveQuestionPrivate(parent, url, mimeType)) 0205 { 0206 } 0207 0208 BrowserOpenOrSaveQuestion::~BrowserOpenOrSaveQuestion() = default; 0209 0210 static QAction *createAppAction(const KService::Ptr &service, QObject *parent) 0211 { 0212 QString actionName(service->name().replace(QLatin1Char('&'), QLatin1String("&&"))); 0213 actionName = i18nc("@action:inmenu", "Open &with %1", actionName); 0214 0215 QAction *act = new QAction(parent); 0216 act->setIcon(QIcon::fromTheme(service->icon())); 0217 act->setText(actionName); 0218 act->setData(QVariant::fromValue(service)); 0219 return act; 0220 } 0221 0222 BrowserOpenOrSaveQuestion::Result BrowserOpenOrSaveQuestion::askOpenOrSave() 0223 { 0224 d->questionLabel->setText(i18nc("@info", "Open '%1'?", d->url.toDisplayString(QUrl::PreferLocalFile))); 0225 d->questionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); 0226 d->openWithButton->hide(); 0227 0228 KGuiItem openWithDialogItem(i18nc("@label:button", "&Open with..."), QStringLiteral("document-open")); 0229 0230 // I thought about using KFileItemActions, but we don't want a submenu, nor the slots.... 0231 // and we want no menu at all if there's only one offer. 0232 const KService::List apps = KFileItemActions::associatedApplications(QStringList{d->mimeType}); 0233 if (apps.isEmpty()) { 0234 KGuiItem::assign(d->openDefaultButton, openWithDialogItem); 0235 } else { 0236 KService::Ptr offer = apps.first(); 0237 KGuiItem openItem(i18nc("@label:button", "&Open with %1", offer->name()), offer->icon()); 0238 KGuiItem::assign(d->openDefaultButton, openItem); 0239 if (d->features & ServiceSelection) { 0240 // OpenDefault shall use this service 0241 d->selectedService = apps.first(); 0242 d->openWithButton->show(); 0243 QMenu *menu = new QMenu(d.get()); 0244 if (apps.count() > 1) { 0245 // Provide an additional button with a menu of associated apps 0246 KGuiItem openWithItem(i18nc("@label:button", "&Open with"), QStringLiteral("document-open")); 0247 KGuiItem::assign(d->openWithButton, openWithItem); 0248 d->openWithButton->setMenu(menu); 0249 QObject::connect(menu, &QMenu::triggered, d.get(), &BrowserOpenOrSaveQuestionPrivate::slotAppSelected); 0250 for (const auto &app : apps) { 0251 QAction *act = createAppAction(app, d.get()); 0252 menu->addAction(act); 0253 } 0254 QAction *openWithDialogAction = new QAction(d.get()); 0255 openWithDialogAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open"))); 0256 openWithDialogAction->setText(openWithDialogItem.text()); 0257 menu->addAction(openWithDialogAction); 0258 } else { 0259 // Only one associated app, already offered by the other menu -> add "Open With..." button 0260 KGuiItem::assign(d->openWithButton, openWithDialogItem); 0261 } 0262 } else { 0263 // qDebug() << "Not using new feature ServiceSelection; port the caller to BrowserOpenOrSaveQuestion::setFeature(ServiceSelection)"; 0264 } 0265 } 0266 0267 // KEEP IN SYNC with kdebase/runtime/keditfiletype/filetypedetails.cpp!!! 0268 const QString dontAskAgain = QLatin1String("askSave") + d->mimeType; 0269 0270 const int choice = d->executeDialog(dontAskAgain); 0271 return choice == BrowserOpenOrSaveQuestionPrivate::Save ? Save : (choice == BrowserOpenOrSaveQuestionPrivate::Cancel ? Cancel : Open); 0272 } 0273 0274 KService::Ptr BrowserOpenOrSaveQuestion::selectedService() const 0275 { 0276 return d->selectedService; 0277 } 0278 0279 bool BrowserOpenOrSaveQuestionPrivate::autoEmbedMimeType(int flags) 0280 { 0281 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC 0282 // NOTE: Keep this function in sync with 0283 // kdebase/runtime/keditfiletype/filetypedetails.cpp 0284 // FileTypeDetails::updateAskSave() 0285 0286 // Don't ask for: 0287 // - html (even new tabs would ask, due to about:blank!) 0288 // - dirs obviously (though not common over HTTP :), 0289 // - images (reasoning: no need to save, most of the time, because fast to see) 0290 // e.g. postscript is different, because takes longer to read, so 0291 // it's more likely that the user might want to save it. 0292 // - multipart/* ("server push", see kmultipart) 0293 // KEEP IN SYNC!!! 0294 // clang-format off 0295 if (flags != static_cast<int>(BrowserOpenOrSaveQuestion::AttachmentDisposition) && mime.isValid() && ( 0296 mime.inherits(QStringLiteral("text/html")) || 0297 mime.inherits(QStringLiteral("application/xml")) || 0298 mime.inherits(QStringLiteral("inode/directory")) || 0299 mimeType.startsWith(QLatin1String("image")) || 0300 mime.inherits(QStringLiteral("multipart/x-mixed-replace")) || 0301 mime.inherits(QStringLiteral("multipart/replace")))) { 0302 return true; 0303 } 0304 // clang-format on 0305 return false; 0306 } 0307 0308 BrowserOpenOrSaveQuestion::Result BrowserOpenOrSaveQuestion::askEmbedOrSave(int flags) 0309 { 0310 if (d->autoEmbedMimeType(flags)) { 0311 return Embed; 0312 } 0313 0314 // don't use KStandardGuiItem::open() here which has trailing ellipsis! 0315 KGuiItem::assign(d->openDefaultButton, KGuiItem(i18nc("@label:button", "&Open"), QStringLiteral("document-open"))); 0316 d->openWithButton->hide(); 0317 0318 d->questionLabel->setText(i18nc("@info", "Open '%1'?", d->url.toDisplayString(QUrl::PreferLocalFile))); 0319 d->questionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); 0320 0321 const QString dontAskAgain = QLatin1String("askEmbedOrSave") + d->mimeType; // KEEP IN SYNC!!! 0322 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC 0323 0324 const int choice = d->executeDialog(dontAskAgain); 0325 return choice == BrowserOpenOrSaveQuestionPrivate::Save ? Save : (choice == BrowserOpenOrSaveQuestionPrivate::Cancel ? Cancel : Embed); 0326 } 0327 0328 void BrowserOpenOrSaveQuestion::setFeatures(Features features) 0329 { 0330 d->features = features; 0331 } 0332 0333 void BrowserOpenOrSaveQuestion::setSuggestedFileName(const QString &suggestedFileName) 0334 { 0335 if (suggestedFileName.isEmpty()) { 0336 return; 0337 } 0338 0339 d->fileNameLabel->setText(i18nc("@label File name", "Name: %1", suggestedFileName)); 0340 d->fileNameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); 0341 d->fileNameLabel->setWhatsThis(i18nc("@info:whatsthis", "This is the file name suggested by the server")); 0342 d->fileNameLabel->show(); 0343 } 0344 0345 #include "browseropenorsavequestion.moc"