KParts

browseropenorsavequestion.cpp
1 /*
2  SPDX-FileCopyrightText: 2009, 2010 David Faure <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5 */
6 
7 #include "browseropenorsavequestion.h"
8 
9 #include <KConfigGroup>
10 #include <KFileItemActions>
11 #include <KGuiItem>
12 #include <KLocalizedString>
13 #include <KMessageBox>
14 #include <KSharedConfig>
15 #include <KSqueezedTextLabel>
16 #include <KStandardGuiItem>
17 
18 #include <QAction>
19 #include <QCheckBox>
20 #include <QDialog>
21 #include <QDialogButtonBox>
22 #include <QLabel>
23 #include <QMenu>
24 #include <QMimeDatabase>
25 #include <QPushButton>
26 #include <QStyle>
27 #include <QStyleOption>
28 #include <QVBoxLayout>
29 
30 using namespace KParts;
31 Q_DECLARE_METATYPE(KService::Ptr)
32 
33 class KParts::BrowserOpenOrSaveQuestionPrivate : public QDialog
34 {
35  Q_OBJECT
36 public:
37  enum {
39  OpenDefault = Save + 1,
40  OpenWith = OpenDefault + 1,
41  Cancel = QDialog::Rejected,
42  };
43 
44  BrowserOpenOrSaveQuestionPrivate(QWidget *parent, const QUrl &url, const QString &mimeType)
45  : QDialog(parent)
46  , url(url)
48  , features(BrowserOpenOrSaveQuestion::BasicFeatures)
49  {
50  // Use askSave or askEmbedOrSave from filetypesrc
51  dontAskConfig = KSharedConfig::openConfig(QStringLiteral("filetypesrc"), KConfig::NoGlobals);
52 
53  setWindowTitle(url.host());
54  setObjectName(QStringLiteral("questionYesNoCancel"));
55 
56  QVBoxLayout *mainLayout = new QVBoxLayout(this);
57  const int verticalSpacing = style()->pixelMetric(QStyle::PM_LayoutVerticalSpacing);
58  mainLayout->setSpacing(verticalSpacing * 2); // provide extra spacing
59 
60  QHBoxLayout *hLayout = new QHBoxLayout();
61  mainLayout->addLayout(hLayout, 5);
62 
63  QLabel *iconLabel = new QLabel(this);
64  QStyleOption option;
65  option.initFrom(this);
66  QIcon icon = QIcon::fromTheme(QStringLiteral("dialog-information"));
67  iconLabel->setPixmap(icon.pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, this)));
68 
69  hLayout->addWidget(iconLabel, 0, Qt::AlignCenter);
70  const int horizontalSpacing = style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing);
71  hLayout->addSpacing(horizontalSpacing);
72 
73  QVBoxLayout *textVLayout = new QVBoxLayout;
74  questionLabel = new KSqueezedTextLabel(this);
75  textVLayout->addWidget(questionLabel);
76 
77  fileNameLabel = new QLabel(this);
78  fileNameLabel->hide();
79  textVLayout->addWidget(fileNameLabel);
80 
81  QMimeDatabase db;
82  mime = db.mimeTypeForName(mimeType);
83  QString mimeDescription(mimeType);
84  if (mime.isValid()) {
85  // Always prefer the mime-type comment over the raw type for display
86  mimeDescription = (mime.comment().isEmpty() ? mime.name() : mime.comment());
87  }
88  QLabel *mimeTypeLabel = new QLabel(this);
89  mimeTypeLabel->setText(i18nc("@label Type of file", "Type: %1", mimeDescription));
91  textVLayout->addWidget(mimeTypeLabel);
92 
93  hLayout->addLayout(textVLayout, 5);
94 
95  mainLayout->addStretch(15);
96  dontAskAgainCheckBox = new QCheckBox(this);
97  dontAskAgainCheckBox->setText(i18nc("@label:checkbox", "Remember action for files of this type"));
98  mainLayout->addWidget(dontAskAgainCheckBox);
99 
100  buttonBox = new QDialogButtonBox(this);
101 
102  saveButton = buttonBox->addButton(QDialogButtonBox::Yes);
103  saveButton->setObjectName(QStringLiteral("saveButton"));
105  saveButton->setDefault(true);
106 
107  openDefaultButton = new QPushButton;
108  openDefaultButton->setObjectName(QStringLiteral("openDefaultButton"));
109  buttonBox->addButton(openDefaultButton, QDialogButtonBox::ActionRole);
110 
111  openWithButton = new QPushButton;
112  openWithButton->setObjectName(QStringLiteral("openWithButton"));
113  buttonBox->addButton(openWithButton, QDialogButtonBox::ActionRole);
114 
115  QPushButton *cancelButton = buttonBox->addButton(QDialogButtonBox::Cancel);
116  cancelButton->setObjectName(QStringLiteral("cancelButton"));
117 
118  connect(saveButton, &QPushButton::clicked, this, &BrowserOpenOrSaveQuestionPrivate::slotYesClicked);
119  connect(openDefaultButton, &QPushButton::clicked, this, &BrowserOpenOrSaveQuestionPrivate::slotOpenDefaultClicked);
120  connect(openWithButton, &QPushButton::clicked, this, &BrowserOpenOrSaveQuestionPrivate::slotOpenWithClicked);
121  connect(buttonBox, &QDialogButtonBox::rejected, this, &BrowserOpenOrSaveQuestionPrivate::reject);
122 
123  mainLayout->addWidget(buttonBox);
124  }
125 
126  bool autoEmbedMimeType(int flags);
127 
128  int executeDialog(const QString &dontShowAgainName)
129  {
130  KConfigGroup cg(dontAskConfig, "Notification Messages"); // group name comes from KMessageBox
131  const QString dontAsk = cg.readEntry(dontShowAgainName, QString()).toLower();
132  if (dontAsk == QLatin1String("yes") || dontAsk == QLatin1String("true")) {
133  return Save;
134  } else if (dontAsk == QLatin1String("no") || dontAsk == QLatin1String("false")) {
135  return OpenDefault;
136  }
137 
138  const int result = exec();
139 
140  if (dontAskAgainCheckBox->isChecked()) {
141  cg.writeEntry(dontShowAgainName, result == BrowserOpenOrSaveQuestion::Save);
142  cg.sync();
143  }
144  return result;
145  }
146 
147  void showService(KService::Ptr selectedService)
148  {
149  KGuiItem openItem(i18nc("@label:button", "&Open with %1", selectedService->name()), selectedService->icon());
150  KGuiItem::assign(openWithButton, openItem);
151  }
152 
153  QUrl url;
155  QMimeType mime;
156  KService::Ptr selectedService;
157  KSqueezedTextLabel *questionLabel;
159  QLabel *fileNameLabel;
160  QDialogButtonBox *buttonBox;
161  QPushButton *saveButton;
162  QPushButton *openDefaultButton;
163  QPushButton *openWithButton;
164 
165 private:
166  QCheckBox *dontAskAgainCheckBox;
167  KSharedConfig::Ptr dontAskConfig;
168 
169 public Q_SLOTS:
170  void reject() override
171  {
172  selectedService = nullptr;
173  QDialog::reject();
174  }
175 
176  void slotYesClicked()
177  {
178  selectedService = nullptr;
179  done(Save);
180  }
181 
182  void slotOpenDefaultClicked()
183  {
184  done(OpenDefault);
185  }
186 
187  void slotOpenWithClicked()
188  {
189  if (!openWithButton->menu()) {
190  selectedService = nullptr;
191  done(OpenWith);
192  }
193  }
194 
195  void slotAppSelected(QAction *action)
196  {
197  selectedService = action->data().value<KService::Ptr>();
198  // showService(selectedService);
199  done(OpenDefault);
200  }
201 };
202 
204  : d(new BrowserOpenOrSaveQuestionPrivate(parent, url, mimeType))
205 {
206 }
207 
208 BrowserOpenOrSaveQuestion::~BrowserOpenOrSaveQuestion() = default;
209 
210 static QAction *createAppAction(const KService::Ptr &service, QObject *parent)
211 {
212  QString actionName(service->name().replace(QLatin1Char('&'), QLatin1String("&&")));
213  actionName = i18nc("@action:inmenu", "Open &with %1", actionName);
214 
215  QAction *act = new QAction(parent);
216  act->setIcon(QIcon::fromTheme(service->icon()));
217  act->setText(actionName);
218  act->setData(QVariant::fromValue(service));
219  return act;
220 }
221 
222 BrowserOpenOrSaveQuestion::Result BrowserOpenOrSaveQuestion::askOpenOrSave()
223 {
224  d->questionLabel->setText(i18nc("@info", "Open '%1'?", d->url.toDisplayString(QUrl::PreferLocalFile)));
225  d->questionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
226  d->openWithButton->hide();
227 
228  KGuiItem openWithDialogItem(i18nc("@label:button", "&Open with..."), QStringLiteral("document-open"));
229 
230  // I thought about using KFileItemActions, but we don't want a submenu, nor the slots....
231  // and we want no menu at all if there's only one offer.
233  if (apps.isEmpty()) {
234  KGuiItem::assign(d->openDefaultButton, openWithDialogItem);
235  } else {
236  KService::Ptr offer = apps.first();
237  KGuiItem openItem(i18nc("@label:button", "&Open with %1", offer->name()), offer->icon());
238  KGuiItem::assign(d->openDefaultButton, openItem);
239  if (d->features & ServiceSelection) {
240  // OpenDefault shall use this service
241  d->selectedService = apps.first();
242  d->openWithButton->show();
243  QMenu *menu = new QMenu(d.get());
244  if (apps.count() > 1) {
245  // Provide an additional button with a menu of associated apps
246  KGuiItem openWithItem(i18nc("@label:button", "&Open with"), QStringLiteral("document-open"));
247  KGuiItem::assign(d->openWithButton, openWithItem);
248  d->openWithButton->setMenu(menu);
249  QObject::connect(menu, &QMenu::triggered, d.get(), &BrowserOpenOrSaveQuestionPrivate::slotAppSelected);
250  for (const auto &app : apps) {
251  QAction *act = createAppAction(app, d.get());
252  menu->addAction(act);
253  }
254  QAction *openWithDialogAction = new QAction(d.get());
255  openWithDialogAction->setIcon(QIcon::fromTheme(QStringLiteral("document-open")));
256  openWithDialogAction->setText(openWithDialogItem.text());
257  menu->addAction(openWithDialogAction);
258  } else {
259  // Only one associated app, already offered by the other menu -> add "Open With..." button
260  KGuiItem::assign(d->openWithButton, openWithDialogItem);
261  }
262  } else {
263  // qDebug() << "Not using new feature ServiceSelection; port the caller to BrowserOpenOrSaveQuestion::setFeature(ServiceSelection)";
264  }
265  }
266 
267  // KEEP IN SYNC with kdebase/runtime/keditfiletype/filetypedetails.cpp!!!
268  const QString dontAskAgain = QLatin1String("askSave") + d->mimeType;
269 
270  const int choice = d->executeDialog(dontAskAgain);
271  return choice == BrowserOpenOrSaveQuestionPrivate::Save ? Save : (choice == BrowserOpenOrSaveQuestionPrivate::Cancel ? Cancel : Open);
272 }
273 
275 {
276  return d->selectedService;
277 }
278 
279 bool BrowserOpenOrSaveQuestionPrivate::autoEmbedMimeType(int flags)
280 {
281  // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC
282  // NOTE: Keep this function in sync with
283  // kdebase/runtime/keditfiletype/filetypedetails.cpp
284  // FileTypeDetails::updateAskSave()
285 
286  // Don't ask for:
287  // - html (even new tabs would ask, due to about:blank!)
288  // - dirs obviously (though not common over HTTP :),
289  // - images (reasoning: no need to save, most of the time, because fast to see)
290  // e.g. postscript is different, because takes longer to read, so
291  // it's more likely that the user might want to save it.
292  // - multipart/* ("server push", see kmultipart)
293  // KEEP IN SYNC!!!
294  // clang-format off
295  if (flags != static_cast<int>(BrowserOpenOrSaveQuestion::AttachmentDisposition) && mime.isValid() && (
296  mime.inherits(QStringLiteral("text/html")) ||
297  mime.inherits(QStringLiteral("application/xml")) ||
298  mime.inherits(QStringLiteral("inode/directory")) ||
299  mimeType.startsWith(QLatin1String("image")) ||
300  mime.inherits(QStringLiteral("multipart/x-mixed-replace")) ||
301  mime.inherits(QStringLiteral("multipart/replace")))) {
302  return true;
303  }
304  // clang-format on
305  return false;
306 }
307 
308 BrowserOpenOrSaveQuestion::Result BrowserOpenOrSaveQuestion::askEmbedOrSave(int flags)
309 {
310  if (d->autoEmbedMimeType(flags)) {
311  return Embed;
312  }
313 
314  // don't use KStandardGuiItem::open() here which has trailing ellipsis!
315  KGuiItem::assign(d->openDefaultButton, KGuiItem(i18nc("@label:button", "&Open"), QStringLiteral("document-open")));
316  d->openWithButton->hide();
317 
318  d->questionLabel->setText(i18nc("@info", "Open '%1'?", d->url.toDisplayString(QUrl::PreferLocalFile)));
319  d->questionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
320 
321  const QString dontAskAgain = QLatin1String("askEmbedOrSave") + d->mimeType; // KEEP IN SYNC!!!
322  // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC
323 
324  const int choice = d->executeDialog(dontAskAgain);
325  return choice == BrowserOpenOrSaveQuestionPrivate::Save ? Save : (choice == BrowserOpenOrSaveQuestionPrivate::Cancel ? Cancel : Embed);
326 }
327 
329 {
330  d->features = features;
331 }
332 
334 {
335  if (suggestedFileName.isEmpty()) {
336  return;
337  }
338 
339  d->fileNameLabel->setText(i18nc("@label File name", "Name: %1", suggestedFileName));
340  d->fileNameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
341  d->fileNameLabel->setWhatsThis(i18nc("@info:whatsthis", "This is the file name suggested by the server"));
342  d->fileNameLabel->show();
343 }
344 
345 #include "browseropenorsavequestion.moc"
Q_OBJECTQ_OBJECT
void triggered(QAction *action)
T & first()
bool inherits(const QString &mimeTypeName) const const
AlignCenter
void setSuggestedFileName(const QString &suggestedFileName)
Sets the suggested filename, shown in the dialog.
void setText(const QString &)
VehicleSection::Features features(QStringView coachNumber, QStringView coachClassification)
virtual void reject()
QVariant fromValue(const T &value)
Q_SLOTSQ_SLOTS
int count(const T &value) const const
T value() const const
QString text() const
void clicked(bool checked)
QIcon fromTheme(const QString &name)
void setFeatures(Features features)
Enables the given features in the dialog.
void initFrom(const QWidget *widget)
KCALUTILS_EXPORT QString mimeType()
void setTextInteractionFlags(Qt::TextInteractionFlags flags)
void addStretch(int stretch)
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QMenu * menu() const const
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
virtual int pixelMetric(QStyle::PixelMetric metric, const QStyleOption *option, const QWidget *widget) const const=0
static KService::List associatedApplications(const QStringList &mimeTypeList)
QAction * addAction(const QString &text)
void setIcon(const QIcon &icon)
TextSelectableByMouse
static void assign(QPushButton *button, const KGuiItem &item)
QStyle * style() const const
Result askEmbedOrSave(int flags=0)
Ask the user whether to save or open a url in another application.
PreferLocalFile
QMimeType mimeTypeForName(const QString &nameOrAlias) const const
This class shows the dialog that asks the user whether to save a url or open a url in another applica...
Result askOpenOrSave()
Ask the user whether to save or open a url in another application.
@ ServiceSelection
Shows "Open With..." with the associated applications for the mimetype.
bool isEmpty() const const
void setWindowTitle(const QString &)
void setText(const QString &text)
bool isEmpty() const const
virtual int exec()
bool isValid() const const
virtual void done(int r)
int result() const const
PM_LayoutVerticalSpacing
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QVariant data() const const
void setData(const QVariant &userData)
QString toLower() const const
QString host(QUrl::ComponentFormattingOptions options) const const
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) const const
void setSpacing(int spacing)
void setObjectName(const QString &name)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void addLayout(QLayout *layout, int stretch)
void addSpacing(int size)
void setPixmap(const QPixmap &)
BrowserOpenOrSaveQuestion(QWidget *parent, const QUrl &url, const QString &mimeType)
Constructor, for all kinds of dialogs shown in this class.
KGuiItem saveAs()
The KParts namespace,.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Tue Aug 9 2022 03:51:33 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.