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