1/*
2 Copyright (c) 2009, 2010 David Faure <faure@kde.org>
3
4 This library is free software; you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as published by
6 the Free Software Foundation; either version 2 of the License or ( at
7 your option ) version 3 or, at the discretion of KDE e.V. ( which shall
8 act as a proxy as in section 14 of the GPLv3 ), any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19*/
20
21#include "browseropenorsavequestion.h"
22#include <kdebug.h>
23#include <kaction.h>
24#include <kfileitemactions.h>
25#include <kpushbutton.h>
26#include <kmenu.h>
27#include <ksqueezedtextlabel.h>
28#include <knotification.h>
29#include <kdialog.h>
30#include <kmimetypetrader.h>
31#include <kstandardguiitem.h>
32#include <kguiitem.h>
33#include <kmessagebox.h>
34#include <kmimetype.h>
35#include <QStyle>
36#include <QStyleOption>
37#include <QVBoxLayout>
38#include <QCheckBox>
39#include <QLabel>
40
41using namespace KParts;
42Q_DECLARE_METATYPE(KService::Ptr)
43
44class KParts::BrowserOpenOrSaveQuestionPrivate : public KDialog
45{
46 Q_OBJECT
47public:
48 // Mapping to KDialog button codes
49 static const KDialog::ButtonCode Save = KDialog::Yes;
50 static const KDialog::ButtonCode OpenDefault = KDialog::User2;
51 static const KDialog::ButtonCode OpenWith = KDialog::User1;
52 static const KDialog::ButtonCode Cancel = KDialog::Cancel;
53
54 BrowserOpenOrSaveQuestionPrivate(QWidget* parent, const KUrl& url, const QString& mimeType)
55 : KDialog(parent), url(url), mimeType(mimeType),
56 features(0)
57 {
58 // Use askSave or askEmbedOrSave from filetypesrc
59 dontAskConfig = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
60
61 setCaption(url.host());
62 setButtons(Save | OpenDefault | OpenWith | Cancel);
63 setObjectName("questionYesNoCancel");
64 setButtonGuiItem(Save, KStandardGuiItem::saveAs());
65 setButtonGuiItem(Cancel, KStandardGuiItem::cancel());
66 setDefaultButton(Save);
67
68 QVBoxLayout *mainLayout = new QVBoxLayout(mainWidget());
69 mainLayout->setSpacing(KDialog::spacingHint() * 2); // provide extra spacing
70 mainLayout->setMargin(0);
71
72 QHBoxLayout *hLayout = new QHBoxLayout();
73 hLayout->setMargin(0);
74 hLayout->setSpacing(-1); // use default spacing
75 mainLayout->addLayout(hLayout, 5);
76
77 QLabel *iconLabel = new QLabel(mainWidget());
78 QStyleOption option;
79 option.initFrom(this);
80 KIcon icon("dialog-information");
81 iconLabel->setPixmap(icon.pixmap(style()->pixelMetric(QStyle::PM_MessageBoxIconSize, &option, this)));
82
83 hLayout->addWidget(iconLabel, 0, Qt::AlignCenter);
84 hLayout->addSpacing(KDialog::spacingHint());
85
86 QVBoxLayout* textVLayout = new QVBoxLayout;
87 questionLabel = new KSqueezedTextLabel(mainWidget());
88 textVLayout->addWidget(questionLabel);
89
90 fileNameLabel = new QLabel(mainWidget());
91 fileNameLabel->hide();
92 textVLayout->addWidget(fileNameLabel);
93
94 mime = KMimeType::mimeType(mimeType, KMimeType::ResolveAliases);
95 QString mimeDescription (mimeType);
96 if (mime) {
97 // Always prefer the mime-type comment over the raw type for display
98 mimeDescription = (mime->comment().isEmpty() ? mime->name() : mime->comment());
99 }
100 QLabel* mimeTypeLabel = new QLabel(mainWidget());
101 mimeTypeLabel->setText(i18nc("@label Type of file", "Type: %1", mimeDescription));
102 mimeTypeLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
103 textVLayout->addWidget(mimeTypeLabel);
104
105 hLayout->addLayout(textVLayout,5);
106
107 mainLayout->addStretch(15);
108 dontAskAgainCheckBox = new QCheckBox(mainWidget());
109 dontAskAgainCheckBox->setText(i18nc("@label:checkbox", "Remember action for files of this type"));
110 mainLayout->addWidget(dontAskAgainCheckBox);
111 }
112
113 bool autoEmbedMimeType(int flags);
114
115 int executeDialog(const QString& dontShowAgainName)
116 {
117 KConfigGroup cg(dontAskConfig, "Notification Messages"); // group name comes from KMessageBox
118 const QString dontAsk = cg.readEntry(dontShowAgainName, QString()).toLower();
119 if (dontAsk == "yes" || dontAsk == "true") {
120 return Save;
121 } else if (dontAsk == "no" || dontAsk == "false") {
122 return OpenDefault;
123 }
124
125 KNotification::event("messageQuestion", // why does KMessageBox uses Information for questionYesNoCancel?
126 questionLabel->text(), // also include mimetype?
127 QPixmap(),
128 window());
129 const int result = exec();
130
131 if (dontAskAgainCheckBox->isChecked()) {
132 cg.writeEntry(dontShowAgainName, result == Save);
133 cg.sync();
134 }
135 return result;
136 }
137
138 void showService(KService::Ptr selectedService)
139 {
140 KGuiItem openItem(i18nc("@label:button", "&Open with %1", selectedService->name()), selectedService->icon());
141 setButtonGuiItem(OpenWith, openItem);
142 }
143
144 KUrl url;
145 QString mimeType;
146 KMimeType::Ptr mime;
147 KService::Ptr selectedService;
148 KSqueezedTextLabel* questionLabel;
149 BrowserOpenOrSaveQuestion::Features features;
150 QLabel* fileNameLabel;
151
152protected:
153 virtual void slotButtonClicked(int buttonId)
154 {
155 if (buttonId != OpenDefault)
156 selectedService = 0;
157 KPushButton* button = KDialog::button(KDialog::ButtonCode(buttonId));
158 if (button && !button->menu()) {
159 done(buttonId);
160 }
161 }
162private:
163 QCheckBox* dontAskAgainCheckBox;
164 KSharedConfig::Ptr dontAskConfig;
165
166public Q_SLOTS:
167 void slotAppSelected(QAction* action)
168 {
169 selectedService = action->data().value<KService::Ptr>();
170 //showService(selectedService);
171 done(OpenDefault);
172 }
173};
174
175
176BrowserOpenOrSaveQuestion::BrowserOpenOrSaveQuestion(QWidget* parent, const KUrl& url, const QString& mimeType)
177 : d(new BrowserOpenOrSaveQuestionPrivate(parent, url, mimeType))
178{
179}
180
181BrowserOpenOrSaveQuestion::~BrowserOpenOrSaveQuestion()
182{
183 delete d;
184}
185
186static KAction* createAppAction(const KService::Ptr& service, QObject* parent)
187{
188 QString actionName(service->name().replace('&', "&&"));
189 actionName = i18nc("@action:inmenu", "Open &with %1", actionName);
190
191 KAction *act = new KAction(parent);
192 act->setIcon(KIcon(service->icon()));
193 act->setText(actionName);
194 act->setData(QVariant::fromValue(service));
195 return act;
196}
197
198BrowserOpenOrSaveQuestion::Result BrowserOpenOrSaveQuestion::askOpenOrSave()
199{
200 d->questionLabel->setText(i18nc("@info", "Open '%1'?", d->url.pathOrUrl()));
201 d->questionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
202 d->showButton(BrowserOpenOrSaveQuestionPrivate::OpenWith, false);
203
204 KGuiItem openWithDialogItem(i18nc("@label:button", "&Open with..."), "document-open");
205
206 // I thought about using KFileItemActions, but we don't want a submenu, nor the slots....
207 // and we want no menu at all if there's only one offer.
208 // TODO: we probably need a setTraderConstraint(), to exclude the current application?
209 const KService::List apps = KFileItemActions::associatedApplications(QStringList() << d->mimeType,
210 QString() /* TODO trader constraint */);
211 if (apps.isEmpty()) {
212 d->setButtonGuiItem(BrowserOpenOrSaveQuestionPrivate::OpenDefault, openWithDialogItem);
213 } else {
214 KService::Ptr offer = apps.first();
215 KGuiItem openItem(i18nc("@label:button", "&Open with %1", offer->name()), offer->icon());
216 d->setButtonGuiItem(BrowserOpenOrSaveQuestionPrivate::OpenDefault, openItem);
217 if (d->features & ServiceSelection) {
218 // OpenDefault shall use this service
219 d->selectedService = apps.first();
220 d->showButton(BrowserOpenOrSaveQuestionPrivate::OpenWith, true);
221 KMenu* menu = new KMenu(d);
222 if (apps.count() > 1) {
223 // Provide an additional button with a menu of associated apps
224 KGuiItem openWithItem(i18nc("@label:button", "&Open with"), "document-open");
225 d->setButtonGuiItem(BrowserOpenOrSaveQuestionPrivate::OpenWith, openWithItem);
226 d->setButtonMenu(BrowserOpenOrSaveQuestionPrivate::OpenWith, menu, KDialog::InstantPopup);
227 QObject::connect(menu, SIGNAL(triggered(QAction*)), d, SLOT(slotAppSelected(QAction*)));
228 for (KService::List::const_iterator it = apps.begin(); it != apps.end(); ++it) {
229 KAction* act = createAppAction(*it, d);
230 menu->addAction(act);
231 }
232 KAction* openWithDialogAction = new KAction(d);
233 openWithDialogAction->setIcon(KIcon("document-open"));
234 openWithDialogAction->setText(openWithDialogItem.text());
235 menu->addAction(openWithDialogAction);
236 } else {
237 // Only one associated app, already offered by the other menu -> add "Open With..." button
238 d->setButtonGuiItem(BrowserOpenOrSaveQuestionPrivate::OpenWith, openWithDialogItem);
239 }
240 } else {
241 kDebug() << "Not using new feature ServiceSelection; port the caller to BrowserOpenOrSaveQuestion::setFeature(ServiceSelection)";
242 //kDebug() << kBacktrace();
243 }
244 }
245
246 // KEEP IN SYNC with kdebase/runtime/keditfiletype/filetypedetails.cpp!!!
247 const QString dontAskAgain = QLatin1String("askSave") + d->mimeType;
248
249 const int choice = d->executeDialog(dontAskAgain);
250 return choice == BrowserOpenOrSaveQuestionPrivate::Save ? Save
251 : (choice == BrowserOpenOrSaveQuestionPrivate::Cancel ? Cancel : Open);
252}
253
254KService::Ptr BrowserOpenOrSaveQuestion::selectedService() const
255{
256 return d->selectedService;
257}
258
259bool BrowserOpenOrSaveQuestionPrivate::autoEmbedMimeType(int flags)
260{
261 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC
262 // NOTE: Keep this function in sync with
263 // kdebase/runtime/keditfiletype/filetypedetails.cpp
264 // FileTypeDetails::updateAskSave()
265
266 // Don't ask for:
267 // - html (even new tabs would ask, due to about:blank!)
268 // - dirs obviously (though not common over HTTP :),
269 // - images (reasoning: no need to save, most of the time, because fast to see)
270 // e.g. postscript is different, because takes longer to read, so
271 // it's more likely that the user might want to save it.
272 // - multipart/* ("server push", see kmultipart)
273 // KEEP IN SYNC!!!
274 if (flags != (int)BrowserRun::AttachmentDisposition && mime && (
275 mime->is("text/html") ||
276 mime->is("application/xml") ||
277 mime->is("inode/directory") ||
278 mimeType.startsWith(QLatin1String("image")) ||
279 mime->is("multipart/x-mixed-replace") ||
280 mime->is("multipart/replace")))
281 return true;
282 return false;
283}
284
285BrowserOpenOrSaveQuestion::Result BrowserOpenOrSaveQuestion::askEmbedOrSave(int flags)
286{
287 if (d->autoEmbedMimeType(flags))
288 return Embed;
289
290 // don't use KStandardGuiItem::open() here which has trailing ellipsis!
291 d->setButtonGuiItem(BrowserOpenOrSaveQuestionPrivate::OpenDefault, KGuiItem(i18nc("@label:button", "&Open"), "document-open"));
292 d->showButton(BrowserOpenOrSaveQuestionPrivate::OpenWith, false);
293
294 d->questionLabel->setText(i18nc("@info", "Open '%1'?", d->url.pathOrUrl()));
295 d->questionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
296
297 const QString dontAskAgain = QLatin1String("askEmbedOrSave")+ d->mimeType; // KEEP IN SYNC!!!
298 // SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC SYNC
299
300 const int choice = d->executeDialog(dontAskAgain);
301 return choice == BrowserOpenOrSaveQuestionPrivate::Save ? Save
302 : (choice == BrowserOpenOrSaveQuestionPrivate::Cancel ? Cancel : Embed);
303}
304
305void BrowserOpenOrSaveQuestion::setFeatures(Features features)
306{
307 d->features = features;
308}
309
310void BrowserOpenOrSaveQuestion::setSuggestedFileName(const QString& suggestedFileName)
311{
312 if (suggestedFileName.isEmpty()) {
313 return;
314 }
315
316 d->fileNameLabel->setText(i18nc("@label File name", "Name: %1", suggestedFileName));
317 d->fileNameLabel->setTextInteractionFlags(Qt::TextSelectableByMouse);
318 d->fileNameLabel->setWhatsThis(i18nc("@info:whatsthis", "This is the file name suggested by the server"));
319 d->fileNameLabel->show();
320}
321
322#include "browseropenorsavequestion.moc"
323