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 | |
41 | using namespace KParts; |
42 | Q_DECLARE_METATYPE(KService::Ptr) |
43 | |
44 | class KParts::BrowserOpenOrSaveQuestionPrivate : public KDialog |
45 | { |
46 | Q_OBJECT |
47 | public: |
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 | |
152 | protected: |
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 | } |
162 | private: |
163 | QCheckBox* dontAskAgainCheckBox; |
164 | KSharedConfig::Ptr dontAskConfig; |
165 | |
166 | public 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 | |
176 | BrowserOpenOrSaveQuestion::BrowserOpenOrSaveQuestion(QWidget* parent, const KUrl& url, const QString& mimeType) |
177 | : d(new BrowserOpenOrSaveQuestionPrivate(parent, url, mimeType)) |
178 | { |
179 | } |
180 | |
181 | BrowserOpenOrSaveQuestion::~BrowserOpenOrSaveQuestion() |
182 | { |
183 | delete d; |
184 | } |
185 | |
186 | static 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 | |
198 | BrowserOpenOrSaveQuestion::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* = 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 | |
254 | KService::Ptr BrowserOpenOrSaveQuestion::selectedService() const |
255 | { |
256 | return d->selectedService; |
257 | } |
258 | |
259 | bool 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 | |
285 | BrowserOpenOrSaveQuestion::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 | |
305 | void BrowserOpenOrSaveQuestion::setFeatures(Features features) |
306 | { |
307 | d->features = features; |
308 | } |
309 | |
310 | void 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 | |