1 /***************************************************************************
2 * Copyright (C) 2006 by Sebastien Laout *
3 * slaout@linux62.org *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU Library General Public License as *
7 * published by the Free Software Foundation; either version 2 of the *
8 * License, or (at your option) any later version. *
9 * *
10 * This program 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 *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU Library General Public *
16 * License along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *
19 ***************************************************************************/
20
21 #include "likeback.h"
22 #include "likeback_p.h"
23 #include "aboutdata.h"
24 #include "global.h"
25
26 #include <KAboutData>
27 #include <KConfig>
28 #include <KActionCollection>
29 #include <KMessageBox>
30 #include <KTextEdit>
31
32 #include <KGuiItem>
33 #include <KUser>
34 #include <KLocalizedString>
35 #include <KSharedConfig>
36 #include <KIconLoader>
37 #include <KConfigGroup>
38 #include <KGuiItem>
39
40 #include <KIO/AccessManager>
41
42 #include <QtCore/QBuffer>
43 #include <QtCore/QPointer>
44 #include <QToolButton>
45 #include <QHBoxLayout>
46 #include <QGridLayout>
47 #include <QtGui/QPixmap>
48 #include <QVBoxLayout>
49 #include <QCheckBox>
50 #include <QRadioButton>
51 #include <QGroupBox>
52 #include <QLabel>
53 #include <QtNetwork/QNetworkReply>
54 #include <QtNetwork/QNetworkRequest>
55 #include <QAction>
56 #include <QtGui/QValidator>
57 #include <QDesktopWidget>
58 #include <QDialogButtonBox>
59 #include <QPushButton>
60 #include <QVBoxLayout>
61 #include <QApplication>
62 #include <QAction>
63 #include <QLocale>
64 #include <QPushButton>
65 #include <QDialog>
66 #include <QInputDialog>
67
68 /****************************************/
69 /********** class LikeBackBar: **********/
70 /****************************************/
71
LikeBackBar(LikeBack * likeBack)72 LikeBackBar::LikeBackBar(LikeBack *likeBack)
73 : QWidget(0, Qt::X11BypassWindowManagerHint | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint)
74 , m_likeBack(likeBack)
75 {
76 QHBoxLayout *layout = new QHBoxLayout(this);
77
78 QIcon likeIconSet = QIcon::fromTheme("likeback_like.png");
79 QIcon dislikeIconSet = QIcon::fromTheme("likeback_dislike.png");
80 QIcon bugIconSet = QIcon::fromTheme("likeback_bug.png");
81 QIcon featureIconSet = QIcon::fromTheme("likeback_feature.png");
82
83 m_likeButton = new QToolButton(this);
84 m_likeButton->setIcon(likeIconSet);
85 m_likeButton->setText("<p>" + i18n("Send application developers a comment about something you like"));
86 m_likeButton->setAutoRaise(true);
87 connect(m_likeButton, SIGNAL(clicked()), this, SLOT(clickedLike()));
88 layout->addWidget(m_likeButton);
89
90 m_dislikeButton = new QToolButton(this);
91 m_dislikeButton->setIcon(dislikeIconSet);
92 m_dislikeButton->setText("<p>" + i18n("Send application developers a comment about something you dislike"));
93 m_dislikeButton->setAutoRaise(true);
94 connect(m_dislikeButton, SIGNAL(clicked()), this, SLOT(clickedDislike()));
95 layout->addWidget(m_dislikeButton);
96
97 m_bugButton = new QToolButton(this);
98 m_bugButton->setIcon(bugIconSet);
99 m_bugButton->setText("<p>" + i18n("Send application developers a comment about an improper behavior of the application"));
100 m_bugButton->setAutoRaise(true);
101 connect(m_bugButton, SIGNAL(clicked()), this, SLOT(clickedBug()));
102 layout->addWidget(m_bugButton);
103
104 m_featureButton = new QToolButton(this);
105 m_featureButton->setIcon(featureIconSet);
106 m_featureButton->setText("<p>" + i18n("Send application developers a comment about a new feature you desire"));
107 m_featureButton->setAutoRaise(true);
108 connect(m_featureButton, SIGNAL(clicked()), this, SLOT(clickedFeature()));
109 layout->addWidget(m_featureButton);
110
111 connect(&m_timer, SIGNAL(timeout()), this, SLOT(autoMove()));
112
113 LikeBack::Button buttons = likeBack->buttons();
114 m_likeButton->setVisible(buttons & LikeBack::Like);
115 m_dislikeButton->setVisible(buttons & LikeBack::Dislike);
116 m_bugButton->setVisible(buttons & LikeBack::Bug);
117 m_featureButton->setVisible(buttons & LikeBack::Feature);
118 }
119
~LikeBackBar()120 LikeBackBar::~LikeBackBar()
121 {
122 }
123
startTimer()124 void LikeBackBar::startTimer()
125 {
126 m_timer.start(10);
127 }
128
stopTimer()129 void LikeBackBar::stopTimer()
130 {
131 m_timer.stop();
132 }
133
autoMove()134 void LikeBackBar::autoMove()
135 {
136 static QWidget *lastWindow = 0;
137
138 QWidget *window = qApp->activeWindow();
139 // When a Kicker applet has the focus, like the Commandline QLineEdit,
140 // the systemtray icon indicates to be the current window and the LikeBack is shown next to the system tray icon.
141 // It's obviously bad ;-) :
142 bool shouldShow = (m_likeBack->userWantsToShowBar() && m_likeBack->enabledBar() && window && !window->inherits("KSystemTray"));
143 if (shouldShow) {
144 //move(window->x() + window->width() - 100 - width(), window->y());
145 //move(window->x() + window->width() - 100 - width(), window->mapToGlobal(QPoint(0, 0)).y() - height());
146 move(window->mapToGlobal(QPoint(0, 0)).x() + window->width() - width(), window->mapToGlobal(QPoint(0, 0)).y() + 1);
147
148 if (window != lastWindow && m_likeBack->windowNamesListing() != LikeBack::NoListing) {
149 if (window->objectName().isEmpty() || window->objectName() == "unnamed") {
150 qDebug() << "===== LikeBack ===== UNNAMED ACTIVE WINDOW OF TYPE " << window->metaObject()->className() << " ======" << LikeBack::activeWindowPath();
151 } else if (m_likeBack->windowNamesListing() == LikeBack::AllWindows) {
152 qDebug() << "LikeBack: Active Window: " << LikeBack::activeWindowPath();
153 }
154 }
155 lastWindow = window;
156 }
157
158 // Show or hide the bar accordingly:
159 if (shouldShow && !isVisible()) {
160 show();
161 } else if (!shouldShow && isVisible()) {
162 hide();
163 }
164 }
165
clickedLike()166 void LikeBackBar::clickedLike()
167 {
168 m_likeBack->execCommentDialog(LikeBack::Like);
169 }
170
clickedDislike()171 void LikeBackBar::clickedDislike()
172 {
173 m_likeBack->execCommentDialog(LikeBack::Dislike);
174 }
175
clickedBug()176 void LikeBackBar::clickedBug()
177 {
178 m_likeBack->execCommentDialog(LikeBack::Bug);
179 }
180
clickedFeature()181 void LikeBackBar::clickedFeature()
182 {
183 m_likeBack->execCommentDialog(LikeBack::Feature);
184 }
185
186 /********************************************/
187 /********** class LikeBackPrivate: **********/
188 /********************************************/
189
LikeBackPrivate()190 LikeBackPrivate::LikeBackPrivate()
191 : bar(0)
192 , config(0)
193 , aboutData(0)
194 , buttons(LikeBack::DefaultButtons)
195 , hostName()
196 , remotePath()
197 , hostPort(80)
198 , acceptedLocales()
199 , acceptedLanguagesMessage()
200 , windowListing(LikeBack::NoListing)
201 , showBar(false)
202 , disabledCount(0)
203 , fetchedEmail()
204 , action(0)
205 {
206 }
207
~LikeBackPrivate()208 LikeBackPrivate::~LikeBackPrivate()
209 {
210 delete bar;
211 delete action;
212
213 config = 0;
214 aboutData = 0;
215 }
216
217 /*************************************/
218 /********** class LikeBack: **********/
219 /*************************************/
220
LikeBack(Button buttons,bool showBarByDefault,KConfig * config,const KAboutData * aboutData)221 LikeBack::LikeBack(Button buttons, bool showBarByDefault, KConfig *config, const KAboutData *aboutData)
222 : QObject()
223 {
224 // Initialize properties (1/2):
225 d = new LikeBackPrivate();
226 d->buttons = buttons;
227 d->config = config;
228 d->aboutData = aboutData;
229 d->showBarByDefault = showBarByDefault;
230
231 // Use default KApplication config and aboutData if not provided:
232 if (d->config == 0)
233 d->config = KSharedConfig::openConfig().data();
234 if (d->aboutData == 0)
235 d->aboutData = new KAboutData(KAboutData::applicationData());
236
237 // Initialize properties (2/2) [Needs aboutData to be set]:
238 d->showBar = userWantsToShowBar();
239
240 // Fetch the KControl user email address as a default one:
241 if (!emailAddressAlreadyProvided())
242 fetchUserEmail();
243
244 // Initialize the button-bar:
245 d->bar = new LikeBackBar(this);
246 d->bar->resize(d->bar->sizeHint());
247
248 // Show the information message if it is the first time, and if the button-bar is shown:
249 static const char *messageShown = "LikeBack_starting_information";
250 if (d->showBar && KMessageBox::shouldBeShownContinue(messageShown)) {
251 showInformationMessage();
252 KMessageBox::saveDontShowAgainContinue(messageShown);
253 }
254
255 // Show the bar if that's wanted by the developer or the user:
256 if (d->showBar)
257 QTimer::singleShot(0, d->bar, SLOT(startTimer()));
258
259 #if 0
260 disableBar();
261 // Alex: Oh, it drove me nuts
262 d->buttons = (Button)(0); showInformationMessage();
263 d->buttons = (Button)(Feature); showInformationMessage();
264 d->buttons = (Button)(Bug); showInformationMessage();
265 d->buttons = (Button)(Bug | Feature); showInformationMessage();
266 d->buttons = (Button)(Dislike); showInformationMessage();
267 d->buttons = (Button)(Dislike | Feature); showInformationMessage();
268 d->buttons = (Button)(Dislike | Bug); showInformationMessage();
269 d->buttons = (Button)(Dislike | Bug | Feature); showInformationMessage();
270 d->buttons = (Button)(Like); showInformationMessage();
271 d->buttons = (Button)(Like | Feature); showInformationMessage();
272 d->buttons = (Button)(Like | Bug); showInformationMessage();
273 d->buttons = (Button)(Like | Bug | Feature); showInformationMessage();
274 d->buttons = (Button)(Like | Dislike); showInformationMessage();
275 d->buttons = (Button)(Like | Dislike | Feature); showInformationMessage();
276 d->buttons = (Button)(Like | Dislike | Bug); showInformationMessage();
277 d->buttons = (Button)(Like | Dislike | Bug | Feature); showInformationMessage();
278 enableBar();
279 #endif
280 }
281
~LikeBack()282 LikeBack::~LikeBack()
283 {
284 delete d;
285 }
286
setWindowNamesListing(WindowListing windowListing)287 void LikeBack::setWindowNamesListing(WindowListing windowListing)
288 {
289 d->windowListing = windowListing;
290 }
291
windowNamesListing()292 LikeBack::WindowListing LikeBack::windowNamesListing()
293 {
294 return d->windowListing;
295 }
296
setAcceptedLanguages(const QStringList & locales,const QString & message)297 void LikeBack::setAcceptedLanguages(const QStringList &locales, const QString &message)
298 {
299 d->acceptedLocales = locales;
300 d->acceptedLanguagesMessage = message;
301 }
302
acceptedLocales()303 QStringList LikeBack::acceptedLocales()
304 {
305 return d->acceptedLocales;
306 }
307
acceptedLanguagesMessage()308 QString LikeBack::acceptedLanguagesMessage()
309 {
310 return d->acceptedLanguagesMessage;
311 }
312
setServer(const QString & hostName,const QString & remotePath,quint16 hostPort)313 void LikeBack::setServer(const QString &hostName, const QString &remotePath, quint16 hostPort)
314 {
315 d->hostName = hostName;
316 d->remotePath = remotePath;
317 d->hostPort = hostPort;
318 }
319
hostName()320 QString LikeBack::hostName()
321 {
322 return d->hostName;
323 }
324
remotePath()325 QString LikeBack::remotePath()
326 {
327 return d->remotePath;
328 }
329
hostPort()330 quint16 LikeBack::hostPort()
331 {
332 return d->hostPort;
333 }
334
disableBar()335 void LikeBack::disableBar()
336 {
337 d->disabledCount++;
338 if (d->bar && d->disabledCount > 0) {
339 d->bar->hide();
340 d->bar->stopTimer();
341 }
342 }
343
enableBar()344 void LikeBack::enableBar()
345 {
346 d->disabledCount--;
347 if (d->disabledCount < 0)
348 qDebug() << "===== LikeBack ===== Enabled more times than it was disabled. Please refer to the disableBar() documentation for more information and hints.";
349 if (d->bar && d->disabledCount <= 0) {
350 d->bar->startTimer();
351 }
352 }
353
enabledBar()354 bool LikeBack::enabledBar()
355 {
356 return d->disabledCount <= 0;
357 }
358
execCommentDialog(Button type,const QString & initialComment,const QString & windowPath,const QString & context)359 void LikeBack::execCommentDialog(Button type, const QString &initialComment, const QString &windowPath, const QString &context)
360 {
361 disableBar();
362 QPointer<LikeBackDialog> dialog = new LikeBackDialog(type, initialComment, windowPath, context, this);
363 dialog->exec();
364 enableBar();
365 }
366
execCommentDialogFromHelp()367 void LikeBack::execCommentDialogFromHelp()
368 {
369 execCommentDialog(AllButtons, /*initialComment=*/"", /*windowPath=*/"HelpMenuAction");
370 }
371
buttons()372 LikeBack::Button LikeBack::buttons()
373 {
374 return d->buttons;
375 }
376
aboutData()377 const KAboutData* LikeBack::aboutData()
378 {
379 return d->aboutData;
380 }
381
config()382 KConfig* LikeBack::config()
383 {
384 return d->config;
385 }
386
sendACommentAction(KActionCollection * parent)387 QAction * LikeBack::sendACommentAction(KActionCollection *parent)
388 {
389 if (d->action == 0) {
390 d->action = parent->addAction("likeback_send_a_comment", this,
391 SLOT(execCommentDialog()));
392 d->action->setText(i18n("&Send a Comment to Developers"));
393 d->action->setIcon(QIcon::fromTheme("mail-message-new"));
394 }
395
396 return d->action;
397 }
398
userWantsToShowBar()399 bool LikeBack::userWantsToShowBar()
400 {
401 // Store the button-bar per version, so it can be disabled by the developer for the final version:
402 KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack");
403 return configGroup.readEntry("userWantToShowBarForVersion_" + d->aboutData->version(), d->showBarByDefault);
404 }
405
setUserWantsToShowBar(bool showBar)406 void LikeBack::setUserWantsToShowBar(bool showBar)
407 {
408 if (showBar == d->showBar)
409 return;
410
411 d->showBar = showBar;
412
413 // Store the button-bar per version, so it can be disabled by the developer for the final version:
414 KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack");
415 configGroup.writeEntry("userWantToShowBarForVersion_" + d->aboutData->version(), showBar);
416 configGroup.sync(); // Make sure the option is saved, even if the application crashes after that.
417
418 if (showBar)
419 d->bar->startTimer();
420 }
421
showInformationMessage()422 void LikeBack::showInformationMessage()
423 {
424 // Show a message reflecting the allowed types of comment:
425 Button buttons = d->buttons;
426 int nbButtons = (buttons & Like ? 1 : 0) +
427 (buttons & Dislike ? 1 : 0) +
428 (buttons & Bug ? 1 : 0) +
429 (buttons & Feature ? 1 : 0);
430 KMessageBox::information(0,
431 "<p><b>" + (isDevelopmentVersion(d->aboutData->version()) ?
432 i18n("Welcome to this testing version of %1.", QGuiApplication::applicationDisplayName()) :
433 i18n("Welcome to %1.", QGuiApplication::applicationDisplayName())
434 ) + "</b></p>"
435 "<p>" + i18n("To help us improve it, your comments are important.") + "</p>"
436 "<p>" +
437 ((buttons & LikeBack::Like) && (buttons & LikeBack::Dislike) ?
438 i18n("Each time you have a great or frustrating experience, "
439 "please click the appropriate face below the window title-bar, "
440 "briefly describe what you like or dislike and click Send.")
441 : (buttons & LikeBack::Like ?
442 i18n("Each time you have a great experience, "
443 "please click the smiling face below the window title-bar, "
444 "briefly describe what you like and click Send.")
445 : (buttons & LikeBack::Dislike ?
446 i18n("Each time you have a frustrating experience, "
447 "please click the frowning face below the window title-bar, "
448 "briefly describe what you dislike and click Send.")
449 :
450 QString()
451 ))) + "</p>" +
452 (buttons & LikeBack::Bug ?
453 "<p>" +
454 (buttons & (LikeBack::Like | LikeBack::Dislike) ?
455 i18n("Follow the same principle to quickly report a bug: "
456 "just click the broken-object icon in the top-right corner of the window, describe it and click Send.")
457 :
458 i18n("Each time you discover a bug in the application, "
459 "please click the broken-object icon below the window title-bar, "
460 "briefly describe the mis-behaviour and click Send.")
461 ) + "</p>"
462 : "") +
463 "<p>" + i18np("Example:", "Examples:", nbButtons) + "</p>" +
464 (buttons & LikeBack::Like ?
465 "<p><img source=\":images/16-actions-likeback_like.png\"> " +
466 i18n("<b>I like</b> the new artwork. Very refreshing.") + "</p>"
467 : "") +
468 (buttons & LikeBack::Dislike ?
469 "<p><img source=\":images/16-actions-likeback_dislike.png\"> " +
470 i18n("<b>I dislike</b> the welcome page of that assistant. Too time consuming.") + "</p>"
471 : "") +
472 (buttons & LikeBack::Bug ?
473 "<p><img source=\":images/16-actions-likeback_bug.png\"> " +
474 i18n("<b>The application has an improper behaviour</b> when clicking the Add button. Nothing happens.") + "</p>"
475 : "") +
476 (buttons & LikeBack::Feature ?
477 "<p><img source=\":images/16-actions-likeback_feature.png\"> " +
478 i18n("<b>I desire a new feature</b> allowing me to send my work by email.") + "</p>"
479 : "") +
480 "</tr></table>",
481 i18n("Help Improve the Application"));
482
483 }
484
activeWindowPath()485 QString LikeBack::activeWindowPath()
486 {
487 // Compute the window hierarchy (from the latest to the oldest):
488 QStringList windowNames;
489 QWidget *window = qApp->activeWindow();
490 while (window) {
491 QString name = window->objectName();
492 // Append the class name to the window name if it is unnamed:
493 if (name == "unnamed")
494 name += QString(":") + window->metaObject()->className();
495 windowNames.append(name);
496 window = dynamic_cast<QWidget*>(window->parent());
497 }
498
499 // Create the string of windows starting by the end (from the oldest to the latest):
500 QString windowPath;
501 for (int i = ((int)windowNames.count()) - 1; i >= 0; i--) {
502 if (windowPath.isEmpty())
503 windowPath = windowNames[i];
504 else
505 windowPath += QString("~~") + windowNames[i];
506 }
507
508 // Finally return the computed path:
509 return windowPath;
510 }
511
emailAddressAlreadyProvided()512 bool LikeBack::emailAddressAlreadyProvided()
513 {
514 KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack");
515 return configGroup.readEntry("emailAlreadyAsked", false);
516 }
517
emailAddress()518 QString LikeBack::emailAddress()
519 {
520 if (!emailAddressAlreadyProvided())
521 askEmailAddress();
522
523 KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack");
524 return configGroup.readEntry("emailAddress", "");
525 }
526
setEmailAddress(const QString & address,bool userProvided)527 void LikeBack::setEmailAddress(const QString &address, bool userProvided)
528 {
529 KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack");
530 configGroup.writeEntry("emailAddress", address);
531 configGroup.writeEntry("emailAlreadyAsked", userProvided || emailAddressAlreadyProvided());
532 configGroup.sync(); // Make sure the option is saved, even if the application crashes after that.
533 }
534
askEmailAddress()535 void LikeBack::askEmailAddress()
536 {
537 KConfigGroup configGroup = KSharedConfig::openConfig()->group("LikeBack");
538
539 QString currentEmailAddress = configGroup.readEntry("emailAddress", "");
540 if (!emailAddressAlreadyProvided() && !d->fetchedEmail.isEmpty())
541 currentEmailAddress = d->fetchedEmail;
542
543 bool ok;
544
545 /*QString emailExpString = "[\\w-\\.]+@[\\w-\\.]+\\.[\\w]+";
546 //QString namedEmailExpString = "[.]*[ \\t]+<" + emailExpString + '>';
547 //QRegExp emailExp("^(|" + emailExpString + '|' + namedEmailExpString + ")$");
548 QRegExp emailExp("^(|" + emailExpString + ")$");
549 QRegExpValidator emailValidator(emailExp, this);*/
550
551 disableBar();
552 QString email = QInputDialog::getText(
553 qApp->activeWindow(),
554 i18n("Email Address"),
555 "<p><b>" + i18n("Please provide your email address.") + "</b></p>" +
556 "<p>" + i18n("It will only be used to contact you back if more information is needed about your comments, ask you how to reproduce the bugs you report, send bug corrections for you to test, etc.") + "</p>" +
557 "<p>" + i18n("The email address is optional. If you do not provide any, your comments will be sent anonymously.") + "</p>",
558 QLineEdit::Normal,
559 currentEmailAddress, &ok/*, &emailValidator*/);
560 enableBar();
561
562 if (ok)
563 setEmailAddress(email);
564 }
565
566 // FIXME: Should be moved to KAboutData? Cigogne will also need it.
isDevelopmentVersion(const QString & version)567 bool LikeBack::isDevelopmentVersion(const QString &version)
568 {
569 return version.contains(QRegExp(".*(alpha|beta|rc|svn|cvs).*", Qt::CaseInsensitive));
570 }
571
572 /**
573 * Code from KBugReport::slotSetFrom() in kdeui/kbugreport.cpp:
574 */
fetchUserEmail()575 void LikeBack::fetchUserEmail()
576 {
577 // delete m_process;
578 // m_process = 0;
579 // m_configureEmail->setEnabled(true);
580
581 // ### KDE4: why oh why is KEmailSettings in kio?
582 KConfig emailConf(QString::fromLatin1("emaildefaults"));
583
584 // find out the default profile
585 KConfigGroup configGroup = KConfigGroup(&emailConf, QString::fromLatin1("Defaults"));
586 QString profile = QString::fromLatin1("PROFILE_");
587 profile += configGroup.readEntry(QString::fromLatin1("Profile"), QString::fromLatin1("Default"));
588
589 configGroup = KConfigGroup(&emailConf, profile);
590 QString fromaddr = configGroup.readEntry(QString::fromLatin1("EmailAddress"));
591 if (fromaddr.isEmpty()) {
592 KUser userInfo;
593 d->fetchedEmail = userInfo.property(KUser::FullName).toString();
594 } else {
595 QString name = configGroup.readEntry(QString::fromLatin1("FullName"));
596 if (!name.isEmpty())
597 d->fetchedEmail = /*name + QString::fromLatin1(" <") +*/ fromaddr /*+ QString::fromLatin1(">")*/;
598 }
599 // m_from->setText( fromaddr );
600 }
601
602 /*******************************************/
603 /********** class LikeBackDialog: **********/
604 /*******************************************/
605
LikeBackDialog(LikeBack::Button reason,const QString & initialComment,const QString & windowPath,const QString & context,LikeBack * likeBack)606 LikeBackDialog::LikeBackDialog(LikeBack::Button reason, const QString &initialComment, const QString &windowPath, const QString &context, LikeBack *likeBack)
607 : QDialog(qApp->activeWindow())
608 , m_likeBack(likeBack)
609 , m_windowPath(windowPath)
610 , m_context(context)
611 {
612 // QDialog Options
613 setWindowTitle(i18n("Send a Comment to Developers"));
614 buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel|QDialogButtonBox::RestoreDefaults);
615 QWidget *mainWidget = new QWidget(this);
616 QVBoxLayout *mainLayout = new QVBoxLayout;
617 setLayout(mainLayout);
618 mainLayout->addWidget(mainWidget);
619 QPushButton *okButton = buttonBox->button(QDialogButtonBox::Ok);
620 okButton->setDefault(true);
621 okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
622 connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
623 connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
624 okButton->setDefault(true);
625 setParent(qApp->activeWindow());
626 setObjectName("_likeback_feedback_window_");
627 setModal(true);
628 connect(okButton, SIGNAL(clicked()), SLOT(slotOk()));
629 connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), SIGNAL(clicked()), SLOT(slotDefault()));
630
631 // If no specific "reason" is provided, choose the first one:
632 if (reason == LikeBack::AllButtons) {
633 LikeBack::Button buttons = m_likeBack->buttons();
634 int firstButton = 0;
635 if (firstButton == 0 && (buttons & LikeBack::Like)) firstButton = LikeBack::Like;
636 if (firstButton == 0 && (buttons & LikeBack::Dislike)) firstButton = LikeBack::Dislike;
637 if (firstButton == 0 && (buttons & LikeBack::Bug)) firstButton = LikeBack::Bug;
638 if (firstButton == 0 && (buttons & LikeBack::Feature)) firstButton = LikeBack::Feature;
639 reason = (LikeBack::Button) firstButton;
640 }
641
642 // If no window path is provided, get the current active window path:
643 if (m_windowPath.isEmpty())
644 m_windowPath = LikeBack::activeWindowPath();
645
646 QWidget *page = new QWidget(this);
647 QVBoxLayout *pageLayout = new QVBoxLayout(page);
648
649 // The introduction message:
650 QLabel *introduction = new QLabel(introductionText(), page);
651 introduction->setWordWrap(true);
652 pageLayout->addWidget(introduction);
653
654 // The comment group:
655 QGroupBox *box = new QGroupBox(i18n("Send Application Developers a Comment About:"), page);
656 QVBoxLayout* boxLayout = new QVBoxLayout;
657 box->setLayout(boxLayout);
658 pageLayout->addWidget(box);
659
660 // The radio buttons:
661 QWidget *buttons = new QWidget(box);
662 boxLayout->addWidget(buttons);
663 //QGridLayout *buttonsGrid = new QGridLayout(buttons, /*nbRows=*/4, /*nbColumns=*/2, /*margin=*/0, spacingHint());
664 QGridLayout *buttonsGrid = new QGridLayout(buttons);
665 if (m_likeBack->buttons() & LikeBack::Like) {
666 QPixmap likePixmap = KIconLoader::global()->loadIcon(
667 ":images/16-actions-likeback_like.png", KIconLoader::NoGroup, 16,
668 KIconLoader::DefaultState, QStringList(), 0L, true
669 );
670 QLabel *likeIcon = new QLabel(buttons);
671 likeIcon->setPixmap(likePixmap);
672 likeIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
673 likeButton = new QRadioButton(i18n("Something you &like"), buttons);
674 buttonsGrid->addWidget(likeIcon, /*row=*/0, /*column=*/0);
675 buttonsGrid->addWidget(likeButton, /*row=*/0, /*column=*/1);
676 }
677 if (m_likeBack->buttons() & LikeBack::Dislike) {
678 QPixmap dislikePixmap = KIconLoader::global()->loadIcon(
679 ":images/16-actions-likeback_dislike.png", KIconLoader::NoGroup, 16,
680 KIconLoader::DefaultState, QStringList(), 0L, true
681 );
682 QLabel *dislikeIcon = new QLabel(buttons);
683 dislikeIcon->setPixmap(dislikePixmap);
684 dislikeIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
685 dislikeButton = new QRadioButton(i18n("Something you &dislike"), buttons);
686 buttonsGrid->addWidget(dislikeIcon, /*row=*/1, /*column=*/0);
687 buttonsGrid->addWidget(dislikeButton, /*row=*/1, /*column=*/1);
688 }
689 if (m_likeBack->buttons() & LikeBack::Bug) {
690 QPixmap bugPixmap = KIconLoader::global()->loadIcon(
691 ":images/16-actions-likeback_bug.png", KIconLoader::NoGroup, 16, KIconLoader::DefaultState,
692 QStringList(), 0L, true
693 );
694 QLabel *bugIcon = new QLabel(buttons);
695 bugIcon->setPixmap(bugPixmap);
696 bugIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
697 bugButton = new QRadioButton(i18n("An improper &behavior of this application"), buttons);
698 buttonsGrid->addWidget(bugIcon, /*row=*/2, /*column=*/0);
699 buttonsGrid->addWidget(bugButton, /*row=*/2, /*column=*/1);
700 }
701 if (m_likeBack->buttons() & LikeBack::Feature) {
702 QPixmap featurePixmap = KIconLoader::global()->loadIcon(
703 ":images/16-actions-likeback_feature.png", KIconLoader::NoGroup, 16,
704 KIconLoader::DefaultState, QStringList(), 0L, true);
705 QLabel *featureIcon = new QLabel(buttons);
706 featureIcon->setPixmap(featurePixmap);
707 featureIcon->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
708 featureButton = new QRadioButton(i18n("A new &feature you desire"), buttons);
709 buttonsGrid->addWidget(featureIcon, /*row=*/3, /*column=*/0);
710 buttonsGrid->addWidget(featureButton, /*row=*/3, /*column=*/1);
711 }
712
713 // The comment text box:
714 m_comment = new KTextEdit(box);
715 boxLayout->addWidget(m_comment);
716 m_comment->setTabChangesFocus(true);
717 m_comment->setPlainText(initialComment);
718
719 m_showButtons = new QCheckBox(i18n("Show comment buttons below &window titlebars"), page);
720 m_showButtons->setChecked(m_likeBack->userWantsToShowBar());
721 pageLayout->addWidget(m_showButtons);
722 connect(m_showButtons, SIGNAL(stateChanged(int)), this, SLOT(changeButtonBarVisible()));
723
724 KGuiItem::assign(okButton, KGuiItem(i18n("&Send Comment")));
725 okButton->setEnabled(false);
726 connect(m_comment, SIGNAL(textChanged()), this, SLOT(commentChanged()));
727
728 KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KGuiItem(i18n("&Email Address...")));
729
730 resize(QSize(qApp->desktop()->width() * 1 / 2, qApp->desktop()->height() * 3 / 5).expandedTo(sizeHint()));
731
732 QAction *sendShortcut = new QAction(this);
733 sendShortcut->setShortcut(Qt::CTRL + Qt::Key_Return);
734 connect(sendShortcut, SIGNAL(triggered()), buttonBox->button(QDialogButtonBox::Ok), SLOT(animateClick()));
735
736 mainLayout->addWidget(page);
737 mainLayout->addWidget(buttonBox);
738 }
739
~LikeBackDialog()740 LikeBackDialog::~LikeBackDialog()
741 {
742 }
743
introductionText()744 QString LikeBackDialog::introductionText()
745 {
746 QString text = "<p>" + i18n("Please provide a brief description of your opinion of %1.", QGuiApplication::applicationDisplayName()) + " ";
747
748 QString languagesMessage = "";
749 if (!m_likeBack->acceptedLocales().isEmpty() && !m_likeBack->acceptedLanguagesMessage().isEmpty()) {
750 languagesMessage = m_likeBack->acceptedLanguagesMessage();
751 QStringList locales = m_likeBack->acceptedLocales();
752 for (QStringList::Iterator it = locales.begin(); it != locales.end(); ++it) {
753 QString locale = *it;
754 if (QLocale().language() == QLocale(locale).language())
755 languagesMessage = "";
756 }
757 } else {
758 if (!QLocale().language() == QLocale::English)
759 languagesMessage = i18n("Please write in English.");
760 }
761
762 if (!languagesMessage.isEmpty())
763 // TODO: Replace the URL with a localized one:
764 text += languagesMessage + " " +
765 i18n("You may be able to use an <a href=\"%1\">online translation tool</a>."
766 , "http://www.google.com/language_tools?hl=" + QLocale().language()) + " ";
767
768 // If both "I Like" and "I Dislike" buttons are shown and one is clicked:
769 if ((m_likeBack->buttons() & LikeBack::Like) && (m_likeBack->buttons() & LikeBack::Dislike))
770 text += i18n("To make the comments you send more useful in improving this application, try to send the same amount of positive and negative comments.") + " ";
771
772 if (!(m_likeBack->buttons() & LikeBack::Feature))
773 text += i18n("Do <b>not</b> ask for new features: your requests will be ignored.");
774
775 return text;
776 }
777
ensurePolished()778 void LikeBackDialog::ensurePolished()
779 {
780 QDialog::ensurePolished();
781 m_comment->setFocus();
782 }
783
slotDefault()784 void LikeBackDialog::slotDefault()
785 {
786 m_likeBack->askEmailAddress();
787 }
788
slotOk()789 void LikeBackDialog::slotOk()
790 {
791 send();
792 }
793
changeButtonBarVisible()794 void LikeBackDialog::changeButtonBarVisible()
795 {
796 m_likeBack->setUserWantsToShowBar(m_showButtons->isChecked());
797 }
798
commentChanged()799 void LikeBackDialog::commentChanged()
800 {
801 QPushButton *sendButton = buttonBox->button(QDialogButtonBox::Ok);
802 sendButton->setEnabled(!m_comment->document()->isEmpty());
803 }
804
send()805 void LikeBackDialog::send()
806 {
807 QString emailAddress = m_likeBack->emailAddress();
808
809 QString type = (likeButton->isChecked() ? "Like" : (dislikeButton->isChecked() ? "Dislike" : (bugButton->isChecked() ? "Bug" : "Feature")));
810 QString data =
811 "protocol=" + QUrl::toPercentEncoding("1.0") + '&' +
812 "type=" + QUrl::toPercentEncoding(type) + '&' +
813 "version=" + QUrl::toPercentEncoding(m_likeBack->aboutData()->version()) + '&' +
814 "locale=" + QUrl::toPercentEncoding(QLocale().languageToString(QLocale().language())) + '&' +
815 "window=" + QUrl::toPercentEncoding(m_windowPath) + '&' +
816 "context=" + QUrl::toPercentEncoding(m_context) + '&' +
817 "comment=" + QUrl::toPercentEncoding(m_comment->toPlainText()) + '&' +
818 "email=" + QUrl::toPercentEncoding(emailAddress);
819
820 QByteArray dataUtf8 = data.toUtf8();
821 QBuffer buffer(&dataUtf8);
822
823 KIO::Integration::AccessManager *http = new KIO::Integration::AccessManager(this);
824 QString urlString;
825 urlString = "http://" + m_likeBack->hostName() + ":" + m_likeBack->hostPort() + m_likeBack->remotePath();
826 QUrl url(urlString);
827 QNetworkRequest request(url);
828 request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
829
830 qDebug() << "http://" << m_likeBack->hostName() << ":" << m_likeBack->hostPort() << m_likeBack->remotePath();
831 qDebug() << data;
832
833 connect(http, SIGNAL(finished(QNetworkReply*)), this, SLOT(requestFinished(QNetworkReply*)));
834
835 http->post(request, &buffer);
836
837 m_comment->setEnabled(false);
838 }
839
requestFinished(QNetworkReply * reply)840 void LikeBackDialog::requestFinished(QNetworkReply *reply)
841 {
842 // TODO: Save to file if error (connection not present at the moment)
843 m_comment->setEnabled(true);
844 m_likeBack->disableBar();
845 if (reply->error() != QNetworkReply::NoError) {
846 KMessageBox::error(this, i18n("<p>Error while trying to send the report.</p><p>Please retry later.</p>"), i18n("Transfer Error"));
847 } else {
848 KMessageBox::information(
849 this,
850 i18n("<p>Your comment has been sent successfully. It will help improve the application.</p><p>Thanks for your time.</p>"),
851 i18n("Comment Sent")
852 );
853 close();
854 }
855 m_likeBack->enableBar();
856
857 QDialog::accept();
858 reply->deleteLater();
859 }
860