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\"> &nbsp;" +
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\"> &nbsp;" +
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\"> &nbsp;" +
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\"> &nbsp;" +
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