diff --git a/src/main/startup/KexiWelcomeStatusBar.cpp b/src/main/startup/KexiWelcomeStatusBar.cpp index d504bcbf4..ad34cee32 100644 --- a/src/main/startup/KexiWelcomeStatusBar.cpp +++ b/src/main/startup/KexiWelcomeStatusBar.cpp @@ -1,1230 +1,782 @@ /* This file is part of the KDE project Copyright (C) 2011-2012 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KexiWelcomeStatusBar.h" #include "KexiWelcomeStatusBar_p.h" #include #include #include #include #include #include "KexiUserFeedbackAgent.h" #define KEXI_SKIP_SETUPPRIVATEICONSRESOURCE #define KEXI_SKIP_SETUPBREEZEICONTHEME #define KEXI_SKIP_REGISTERICONSRESOURCE #define KEXI_SKIP_REGISTERRESOURCE #include "KexiRegisterResource_p.h" #include #include #include #include #include #include #include #include #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) # include # include # include # include #else # define USE_KIO_COPY # include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class QNetworkReply; class KJob; static const int GUI_UPDATE_INTERVAL = 60; // update interval for GUI, in minutes static const int DONATION_INTERVAL = 10; // donation interval, in days static const int UPDATE_FILES_LIST_SIZE_LIMIT = 1024 * 128; static const int UPDATE_FILES_COUNT_LIMIT = 128; //! @return x.y.0 static QString stableVersionStringDot0() { return QString::number(Kexi::stableVersionMajor()) + '.' + QString::number(Kexi::stableVersionMinor()) + ".0"; } static QString uiPath(const QString &fname) { KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); return f->serviceUrl() + QString("/ui/%1/").arg(stableVersionStringDot0()) + fname; } static QString basePath() { return QString(KEXI_BASE_PATH "/status"); } static QString findFileName(const QString &guiFileName) { QStringList triedLocations; QString result = locateFile(QString(), basePath() + '/' + guiFileName, QStandardPaths::GenericDataLocation, QString(), &triedLocations); if (result.isEmpty()) { // last chance: file from the source tree result = QFileInfo(QFile::decodeName(CMAKE_CURRENT_SOURCE_DIR "/status/") + guiFileName) .canonicalFilePath(); } //qDebug() << result; return result; } // --- class Q_DECL_HIDDEN KexiWelcomeStatusBarGuiUpdater::Private : public QObject { Q_OBJECT public: Private() : configGroup(KConfigGroup(KSharedConfig::openConfig()->group("User Feedback"))) { } KConfigGroup configGroup; public Q_SLOTS: void sendRequestListFilesFinished(KJob* job) { if (job->error()) { qWarning() << "Error while receiving .list file - no files will be updated"; //! @todo error... return; } KIO::StoredTransferJob* sendJob = qobject_cast(job); QString result = sendJob->data(); if (result.length() > UPDATE_FILES_LIST_SIZE_LIMIT) { // anti-DOS protection qWarning() << "Too large .list file (" << result.length() << "); the limit is" << UPDATE_FILES_LIST_SIZE_LIMIT << "- no files will be updated"; return; } //qDebug() << result; QStringList data = result.split('\n', QString::SkipEmptyParts); result.clear(); m_fileNamesToUpdate.clear(); if (data.count() > UPDATE_FILES_COUNT_LIMIT) { // anti-DOS protection qWarning() << "Too many files to update (" << data.count() << "); the limit is" << UPDATE_FILES_COUNT_LIMIT << "- no files will be updated"; return; } // OK, try to update (stage 1: check, stage 2: checking) for (int stage = 1; stage <= 2; stage++) { int i = 0; for (QStringList::ConstIterator it(data.constBegin()); it!=data.constEnd(); ++it, i++) { const QByteArray hash((*it).left(32).toLatin1()); const QString remoteFname((*it).mid(32 + 2)); if (stage == 1) { if (hash.length() != 32) { qWarning() << "Invalid hash" << hash << "in line" << i+1 << "- no files will be updated"; return; } if ((*it).mid(32, 2) != " ") { qWarning() << "Two spaces expected but found" << (*it).mid(32, 2) << "in line" << i+1 << "- no files will be updated"; return; } if (remoteFname.contains(QRegularExpression("\\s"))) { qWarning() << "Filename expected without whitespace but found" << remoteFname << "in line" << i+1 << "- no files will be updated"; return; } } else if (stage == 2) { checkFile(hash, remoteFname, &m_fileNamesToUpdate); } } } if (m_fileNamesToUpdate.isEmpty()) { //qDebug() << "No files to update."; return; } // update files QList sourceFiles; foreach (const QString &fname, m_fileNamesToUpdate) { sourceFiles.append(QUrl(uiPath(fname))); } m_tempDir.reset(new QTemporaryDir(QDir::tempPath() + "/kexi-status")); //qDebug() << m_tempDir->path(); #ifdef USE_KIO_COPY KIO::CopyJob *copyJob = KIO::copy(sourceFiles, QUrl::fromLocalFile(m_tempDir->path()), KIO::HideProgressInfo | KIO::Overwrite); connect(copyJob, &KIO::CopyJob::result, this, &Private::filesCopyFinished); #else if (!m_downloadManager) { m_downloadManager = new QNetworkAccessManager(this); connect(m_downloadManager.data(), &QNetworkAccessManager::finished, this, &Private::fileDownloadFinished); } m_sourceFilesToDownload = sourceFiles; downloadNextFile(); #endif //qDebug() << "copying from" << QUrl(uiPath(fname)) << "to" // << (dir + fname); } private Q_SLOTS: void filesCopyFinished(KJob* job) { #ifdef USE_KIO_COPY if (job->error()) { //! @todo error... qDebug() << "ERROR:" << job->errorString(); m_tempDir.reset(); return; } KIO::CopyJob* copyJob = qobject_cast(job); Q_UNUSED(copyJob) //qDebug() << "DONE" << copyJob->destUrl(); (void)copyFilesToDestinationDir(); #else Q_UNUSED(job) #endif } void fileDownloadFinished(QNetworkReply* reply) { #ifdef USE_KIO_COPY Q_UNUSED(reply) #else const bool ok = copyFile(reply); reply->deleteLater(); if (!ok) { qWarning() << "Error downloading file" << m_sourceFilesToDownload.first(); delete m_downloadManager; m_sourceFilesToDownload.clear(); m_tempDir.reset(); } m_sourceFilesToDownload.removeFirst(); downloadNextFile(); #endif } bool copyFile(QNetworkReply* reply) { #ifdef USE_KIO_COPY Q_UNUSED(reply) #else #define DOWNLOAD_BUFFER_SIZE 1024 * 50 if (reply->error() != QNetworkReply::NoError) { return false; } const QString filename(m_sourceFilesToDownload.first().fileName()); QString path(m_tempDir->path() + '/' + filename); QSaveFile f(path); if (!f.open(QIODevice::WriteOnly)) { return false; } QByteArray buf(DOWNLOAD_BUFFER_SIZE, Qt::Uninitialized); while (!reply->atEnd()) { const qint64 size = reply->read(buf.data(), buf.size()); if (size < 0) { return false; } if (f.write(buf.data(), size) != size) { return false; } } if (!f.commit()) { return false; } #endif return true; } private: #ifndef USE_KIO_COPY void downloadNextFile() { if (m_sourceFilesToDownload.isEmpty()) { // success (void)copyFilesToDestinationDir(); return; } m_downloadManager->get(QNetworkRequest(m_sourceFilesToDownload.first())); } #endif private: bool copyFilesToDestinationDir() { const QString dir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QLatin1Char('/') + basePath() + '/'); bool ok = true; if (!QDir(dir).exists()) { if (!QDir().mkpath(dir)) { ok = false; qWarning() << "Could not create" << dir; } } if (ok) { foreach (const QString &fname, m_fileNamesToUpdate) { const QByteArray oldName(QFile::encodeName(m_tempDir->path() + '/' + fname)), newName(QFile::encodeName(dir + fname)); if (0 != ::rename(oldName.constData(), newName.constData())) { qWarning() << "cannot move" << (m_tempDir->path() + '/' + fname) << "to" << (dir + fname); } } } QDir(m_tempDir->path()).removeRecursively(); m_tempDir.reset(); m_fileNamesToUpdate.clear(); return ok; } void checkFile(const QByteArray &hash, const QString &remoteFname, QStringList *fileNamesToUpdate) { QString localFname = findFileName(remoteFname); if (localFname.isEmpty()) { fileNamesToUpdate->append(remoteFname); qDebug() << "missing filename" << remoteFname << "- download it"; return; } QFile file(localFname); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "could not open file" << localFname << "- update it"; fileNamesToUpdate->append(remoteFname); return; } QCryptographicHash md5(QCryptographicHash::Md5); if (!md5.addData(&file)) { qWarning() << "could not check MD5 for file" << localFname << "- update it"; fileNamesToUpdate->append(remoteFname); return; } if (md5.result().toHex() != hash) { qDebug() << "not matching file" << localFname << "- update it"; fileNamesToUpdate->append(remoteFname); } } QStringList m_fileNamesToUpdate; QScopedPointer m_tempDir; #ifndef USE_KIO_COPY QList m_sourceFilesToDownload; QPointer m_downloadManager; #endif }; KexiWelcomeStatusBarGuiUpdater::KexiWelcomeStatusBarGuiUpdater() : QObject() , d(new Private) { } KexiWelcomeStatusBarGuiUpdater::~KexiWelcomeStatusBarGuiUpdater() { delete d; } void KexiWelcomeStatusBarGuiUpdater::update() { QDateTime lastStatusBarUpdate = d->configGroup.readEntry("LastStatusBarUpdate", QDateTime()); if (lastStatusBarUpdate.isValid()) { int minutes = lastStatusBarUpdate.secsTo(QDateTime::currentDateTime()) / 60; if (minutes < GUI_UPDATE_INTERVAL) { qDebug() << "gui updated" << minutes << "min. ago, next auto-update in" << (GUI_UPDATE_INTERVAL - minutes) << "min."; return; } } d->configGroup.writeEntry("LastStatusBarUpdate", QDateTime::currentDateTime()); KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); f->waitForRedirect(this, SLOT(slotRedirectLoaded())); } void KexiWelcomeStatusBarGuiUpdater::slotRedirectLoaded() { QByteArray postData = stableVersionStringDot0().toLatin1(); KIO::Job* sendJob = KIO::storedHttpPost(postData, QUrl(uiPath(".list")), KIO::HideProgressInfo); connect(sendJob, SIGNAL(result(KJob*)), d, SLOT(sendRequestListFilesFinished(KJob*))); sendJob->addMetaData("content-type", "Content-Type: application/x-www-form-urlencoded"); } // --- //! @internal class ScrollArea : public QScrollArea { Q_OBJECT public: explicit ScrollArea(QWidget *parent = 0) : QScrollArea(parent) { setFrameShape(QFrame::NoFrame); setBackgroundRole(QPalette::Base); setWidgetResizable(true); } void setEnabled(bool set) { if (set != isEnabled()) { QScrollArea::setEnabled(set); updateColors(); } } protected: virtual void changeEvent(QEvent* event) { switch (event->type()) { case QEvent::EnabledChange: case QEvent::PaletteChange: updateColors(); break; default:; } QScrollArea::changeEvent(event); } void updateColors() { if (!widget()) return; KColorScheme scheme(palette().currentColorGroup()); QColor linkColor = scheme.foreground(KColorScheme::LinkText).color(); //qDebug() << "_____________" << isEnabled(); foreach(QLabel* lbl, widget()->findChildren()) { QString t = lbl->text(); QRegularExpression re("", QRegularExpression::InvertedGreedinessOption); int pos = 0; int oldPos = 0; QString newText; QRegularExpressionMatch match = re.match(t); //qDebug() << "t:" << t; while ((pos = match.capturedStart(pos)) != -1) { //qDebug() << "pos:" << pos; //qDebug() << "newText += t.mid(oldPos, pos - oldPos)" // << t.mid(oldPos, pos - oldPos); newText += t.midRef(oldPos, pos - oldPos); //qDebug() << "newText1:" << newText; //qDebug() << lbl->objectName() << "~~~~" << t.mid(pos, re.matchedLength()); QString a = t.mid(pos, match.capturedLength()); //qDebug() << "a:" << a; int colPos = a.indexOf("color:"); if (colPos == -1) { // add color a.insert(a.length() - 1, " style=\"color:" + linkColor.name() + ";\""); } else { // replace color colPos += qstrlen("color:"); for (;colPos < a.length() && a[colPos] == ' '; colPos++) { } if (colPos < a.length() && a[colPos] == '#') { colPos++; int i = colPos; for (;i < a.length(); i++) { if (a[i] == ';' || a[i] == ' ' || a[i] == '"' || a[i] == '\'') break; } //qDebug() << "******" << a.mid(colPos, i - colPos); a.replace(colPos, i - colPos, linkColor.name().mid(1)); } } //qDebug() << "a2:" << a; newText += a; //qDebug() << "newText2:" << newText; pos += match.capturedLength(); oldPos = pos; //qDebug() << "pos2:" << pos; } //qDebug() << "oldPos:" << oldPos; newText += t.midRef(oldPos); //qDebug() << "newText3:" << newText; lbl->setText(newText); } #if 0 QString text; text = QString("%3") .arg(link).arg(linkColor.name()).arg(linkText); if (!format.isEmpty()) { text = QString(format).replace("%L", text); } q->setText(text); #endif } }; // --- class Q_DECL_HIDDEN KexiWelcomeStatusBar::Private { public: explicit Private(KexiWelcomeStatusBar* _q) - : statusWidget(0), helpAction(0), shareAction(0), cancelAction(0), + : statusWidget(0), q(_q) { rccFname = findFileName("status.rcc"); if (!rccFname.isEmpty()) { QResource::registerResource(rccFname); } - - scores.insert(KexiUserFeedbackAgent::BasicArea, 4); - scores.insert(KexiUserFeedbackAgent::SystemInfoArea, 4); - scores.insert(KexiUserFeedbackAgent::ScreenInfoArea, 2); - scores.insert(KexiUserFeedbackAgent::RegionalSettingsArea, 2); - totalFeedbackScore = 0; - foreach (int s, scores.values()) { - totalFeedbackScore += s; - } - donationScore = 20; - donated = false; - //qDebug() << "totalFeedbackScore:" << totalFeedbackScore; } ~Private() { delete msgWidget; if (!rccFname.isEmpty()) { QResource::unregisterResource(rccFname); } } - int currentFeedbackScore() const - { - int score = 0; - KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); - KexiUserFeedbackAgent::Areas areas = f->enabledAreas(); - for (QMap::ConstIterator it(scores.constBegin()); - it!=scores.constEnd(); ++it) - { - if (areas & it.key()) { - score += it.value(); - } - } - //qDebug() << score; - return score; - } - template T widgetOfClass(T parent, const char *widgetName) const { T w = parent->template findChild(widgetName); if (!w) { qWarning() << "NO SUCH widget" << widgetName << "in" << parent; } return w; } QWidget* widget(QWidget *parent, const char *widgetName) const { return widgetOfClass(parent, widgetName); } QObject* object(QObject *parent, const char *objectName) const { QObject *o = parent->findChild(objectName); if (!o) { qWarning() << "NO SUCH object" << objectName << "in" << parent; } return o; } void setProperty(QWidget *parent, const char *widgetName, const char *propertyName, const QVariant &value) { QWidget *w = widget(parent, widgetName); if (w) { w->setProperty(propertyName, value); } } QVariant property(QWidget *parent, const char *widgetName, const char *propertyName) const { QWidget *w = widget(parent, widgetName); return w ? w->property(propertyName) : QVariant(); } void connect(QWidget *parent, const char *widgetName, const char *signalName, QObject *receiver, const char *slotName) { QWidget *w = widget(parent, widgetName); if (w) { QObject::connect(w, signalName, receiver, slotName); } } void animatedHide(QWidget *parent, const char *widgetName) { QWidget *w = widget(parent, widgetName); if (!w) return; KexiFadeWidgetEffect *animation = new KexiFadeWidgetEffect(w); QObject::connect(animation, SIGNAL(destroyed()), w, SLOT(hide())); animation->start(); } QWidget* loadGui(const QString &guiFileName, QWidget *parentWidget = 0) { QString fname = findFileName(guiFileName); if (fname.isEmpty()) { qWarning() << "filename" << fname << "not found"; return 0; } QFile file(fname); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "could not open file" << fname; return 0; } QUiLoader loader; QWidget* widget = loader.load(&file, parentWidget); if (!widget) { qWarning() << "could load ui from file" << fname; } file.close(); return widget; } void updateStatusWidget() { QWidget *widget = loadGui("status.ui", statusScrollArea); if (!widget) { return; } int smallFontSize = qFloor((QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont).pointSizeF() + q->font().pointSizeF()) / 2.0); smallFont = q->font(); smallFont.setPointSizeF(smallFontSize); widget->setFont(smallFont); //delete statusWidget; statusWidget = widget; statusScrollArea->setWidget(statusWidget); - setProperty(statusWidget, "contribution_progress", "minimumHeight", - q->fontMetrics().height()); - setProperty(statusWidget, "contribution_progress", "maximumHeight", - q->fontMetrics().height()); - label_involved_text_mask = property(statusWidget, "label_involved", "text").toString(); - setProperty(statusWidget, "link_share_usage_info", "text", - property(statusWidget, "link_share_usage_info", "text").toString().arg(totalFeedbackScore)); - link_share_more_usage_info_mask = property(statusWidget, "link_share_more_usage_info", "text").toString(); - - setProperty(statusWidget, "link_donate", "text", - property(statusWidget, "link_donate", "text").toString().arg(donationScore)); - - updateDonationInfo(); - updateUserProgress(); - updateContributionLinksVisibility(); + + QString donationText = property(statusWidget, "link_donate", "text").toString(); + donationText.remove(QStringLiteral("(+%1%)")); + setProperty(statusWidget, "link_donate", "text", donationText); + // do not alter background palette QPalette pal(widget->palette()); pal.setColor(QPalette::Disabled, QPalette::Base, pal.color(QPalette::Normal, QPalette::Base)); widget->setPalette(pal); - connect(statusWidget, "link_contribute_show_help", SIGNAL(linkActivated(QString)), - q, SLOT(showContributionHelp())); - connect(statusWidget, "link_share_usage_info", SIGNAL(linkActivated(QString)), - q, SLOT(showShareUsageInfo())); - connect(statusWidget, "link_share_more_usage_info", SIGNAL(linkActivated(QString)), - q, SLOT(showShareUsageInfo())); - connect(statusWidget, "link_show_contribution_details", SIGNAL(linkActivated(QString)), - q, SLOT(showContributionDetails())); setProperty(statusWidget, "donation_url", "visible", false); connect(statusWidget, "link_donate", SIGNAL(linkActivated(QString)), q, SLOT(showDonation())); } - void setUserProgress(int progress) - { - setProperty(statusWidget, "contribution_progress", "value", progress); - setProperty(statusWidget, "label_involved", "text", - label_involved_text_mask.arg(progress)); - } - - void updateUserProgress() - { - int progress = 0; - progress += currentFeedbackScore(); - if (donated) { - progress += donationScore; - } - setUserProgress(progress); - } - - void updateContributionLinksVisibility() - { - KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); - int availableLinks = 0; - bool noneEnabled = f->enabledAreas() == KexiUserFeedbackAgent::NoAreas; - bool allEnabled = f->enabledAreas() == KexiUserFeedbackAgent::AllAreas; - setProperty(statusWidget, "share_usage_info", "visible", noneEnabled); - if (noneEnabled) { - availableLinks++; - } - setProperty(statusWidget, "share_more_usage_info", "visible", - !noneEnabled && !allEnabled); - if (!noneEnabled && !allEnabled) { - availableLinks++; - } - setProperty(statusWidget, "link_share_more_usage_info", "text", - link_share_more_usage_info_mask.arg(totalFeedbackScore - currentFeedbackScore())); - - setProperty(statusWidget, "lbl_contribute", "visible", availableLinks > 0); - } - - void updateDonationInfo() - { - KConfigGroup configGroup(KSharedConfig::openConfig()->group("User Feedback")); - QDateTime lastDonation = configGroup.readEntry("LastDonation", QDateTime()); - if (lastDonation.isValid()) { - int days = lastDonation.secsTo(QDateTime::currentDateTime()) / 60 / 60 / 24; - if (days >= DONATION_INTERVAL) { - donated = false; - qDebug() << "last donation declared" << days << "days ago, next in" - << (DONATION_INTERVAL - days) << "days."; - } - else if (days >= 0) { - donated = true; - } - } - //show always: setProperty(statusWidget, "donate", "visible", !donated); - } - enum CalloutAlignment { AlignToBar, AlignToWidget }; //! Aligns callout pointer position of msgWidget to widget named @a alignToWidgetName void setMessageWidgetCalloutPointerPosition( const QString& alignToWidgetName, CalloutAlignment calloutAlignment = AlignToBar) { //qDebug() << q->pos() << q->mapToGlobal(QPoint(0, 100)); QPoint p(q->mapToGlobal(QPoint(0, 100))); QWidget *alignToWidget = this->widget(statusWidget, alignToWidgetName.toLatin1()); if (alignToWidget) { p.setY( alignToWidget->mapToGlobal( QPoint(-5, alignToWidget->height() / 2)).y()); if (calloutAlignment == AlignToWidget) { p.setX(alignToWidget->mapToGlobal(QPoint(-5, 0)).x()); //qDebug() << p; } } else { qWarning() << alignToWidgetName << "not found!"; } msgWidget->setCalloutPointerPosition(p, alignToWidget); } //! Shows message widget taking maximum space within the welcome page //! Returns created layout for further use into @a layout. //! Created widge is assigned to msgWidget. //! Calls slot @a slotToCallAfterShow after animated showing, if provided. //! Call msgWidget->animatedShow() afterwards. void showMaximizedMessageWidget(const QString &alignToWidgetName, QPointer *layout, const char* slotToCallAfterShow, CalloutAlignment calloutAlignment = AlignToBar) { QWidget *alignToWidget = this->widget(statusWidget, alignToWidgetName.toLatin1()); int msgWidth; if (alignToWidget && calloutAlignment == AlignToWidget) { msgWidth = q->parentWidget()->width() - alignToWidget->width() - 10; } else { msgWidth = q->parentWidget()->width() - q->width(); } QWidget *widget = new QWidget; *layout = new QGridLayout(widget); if (msgWidth > 100) { // nice text margin (*layout)->setColumnMinimumWidth(0, 50); } //qDebug() << (q->parentWidget()->width() - q->width()) << "***"; KexiContextMessage msg(widget); if (msgWidget) { delete static_cast(msgWidget); } msgWidget = new KexiContextMessageWidget(q->parentWidget()->parentWidget(), 0, 0, msg); msgWidget->setCalloutPointerDirection(KMessageWidget::Right); msgWidget->setMessageType(KMessageWidget::Information); msgWidget->setCloseButtonVisible(true); int offset_y = 0; if (alignToWidget) { offset_y = alignToWidget->mapToGlobal(QPoint(0, 0)).y() - q->parentWidget()->mapToGlobal(QPoint(0, 0)).y(); } else { qWarning() << alignToWidgetName << "not found!"; } msgWidget->resize(msgWidth, q->parentWidget()->height() - offset_y); setMessageWidgetCalloutPointerPosition(alignToWidgetName, calloutAlignment); msgWidget->setResizeTrackingPolicy(Qt::Horizontal | Qt::Vertical); statusScrollArea->setEnabled(false); // async show to for speed up if (slotToCallAfterShow) { QObject::connect(msgWidget, SIGNAL(animatedShowFinished()), q, slotToCallAfterShow); } QObject::connect(msgWidget, SIGNAL(animatedHideFinished()), q, SLOT(slotMessageWidgetClosed())); } ScrollArea *statusScrollArea; QWidget *statusWidget; QVBoxLayout *lyr; QPointer msgWidget; QFont smallFont; - QAction *helpAction; - QAction *shareAction; - QAction *cancelAction; - QString label_involved_text_mask; - QString link_share_more_usage_info_mask; - QPointer contributionHelpLayout; - QPointer contributionDetailsLayout; - QPointer contributionDetailsWidget; - QMap scores; - QString countryMask; - QString languageMask; bool detailsDataVisible = false; - int totalFeedbackScore; - int donationScore; - bool donated; KexiWelcomeStatusBarGuiUpdater guiUpdater; private: QString rccFname; KexiWelcomeStatusBar *q; QMap dict; }; KexiWelcomeStatusBar::KexiWelcomeStatusBar(QWidget* parent) : QWidget(parent), d(new Private(this)) { d->lyr = new QVBoxLayout(this); init(); } KexiWelcomeStatusBar::~KexiWelcomeStatusBar() { delete d; } void KexiWelcomeStatusBar::init() { d->statusScrollArea = new ScrollArea(this); d->lyr->addWidget(d->statusScrollArea); d->updateStatusWidget(); QTimer::singleShot(10, &d->guiUpdater, SLOT(update())); } -void KexiWelcomeStatusBar::showContributionHelp() -{ - d->showMaximizedMessageWidget("link_contribute_show_help", - &d->contributionHelpLayout, - SLOT(slotShowContributionHelpContents())); - d->msgWidget->animatedShow(); -} - -void KexiWelcomeStatusBar::slotShowContributionHelpContents() -{ - QWidget *helpWidget = d->loadGui("contribution_help.ui"); - d->contributionHelpLayout->addWidget(helpWidget, 1, 1); - d->msgWidget->setPaletteInherited(); -} - -void KexiWelcomeStatusBar::slotMessageWidgetClosed() -{ - d->statusScrollArea->setEnabled(true); - d->updateDonationInfo(); - d->updateUserProgress(); - d->updateContributionLinksVisibility(); -} - -void KexiWelcomeStatusBar::showShareUsageInfo() -{ - if (!sender()) { - return; - } - QWidget *widget = d->loadGui("status_strings.ui"); - if (!widget) { - return; - } - QLabel *lbl = widget->findChild("question"); - if (!lbl) { - return; - } - KexiContextMessage msg(lbl->text()); - delete widget; - if (!d->helpAction) { - d->helpAction = new QAction(KStandardGuiItem::help().icon(), - KStandardGuiItem::help().text(), this); - connect(d->helpAction, SIGNAL(triggered()), this, SLOT(showContributionHelp())); - } - if (!d->shareAction) { - d->shareAction = new QAction(KStandardGuiItem::yes().icon(), xi18n("Share"), this); - connect(d->shareAction, SIGNAL(triggered()), this, SLOT(slotShareFeedback())); - } - if (!d->cancelAction) { - d->cancelAction = new QAction(KStandardGuiItem::cancel().icon(), - KStandardGuiItem::cancel().text(), this); - QObject::connect(d->cancelAction, SIGNAL(triggered()), this, SLOT(slotCancelled())); - } - msg.addAction(d->helpAction, KexiContextMessage::AlignLeft); - msg.addAction(d->shareAction); - msg.addAction(d->cancelAction); - if (d->msgWidget) { - delete static_cast(d->msgWidget); - } - d->msgWidget - = new KexiContextMessageWidget(parentWidget(), 0, 0, msg); - d->msgWidget->setMessageType(KMessageWidget::Information); - d->msgWidget->setCalloutPointerDirection(KMessageWidget::Right); - d->setMessageWidgetCalloutPointerPosition(sender()->objectName()); - d->statusScrollArea->setEnabled(false); - d->msgWidget->setMaximumWidth(parentWidget()->width() - width()); - d->msgWidget->setResizeTrackingPolicy(Qt::Horizontal); - - d->msgWidget->animatedShow(); -} - void KexiWelcomeStatusBar::showDonation() { if (!sender()) { return; } if (KMessageBox::Yes != KMessageBox::questionYesNo(this, xi18nc("@info donate to the project", "Kexi may be totally free, but its development is costly." "Power, hardware, office space, internet access, traveling for meetings - everything costs." "Direct donation is the easiest and fastest way to efficiently support the Kexi Project. " "Everyone, regardless of any degree of involvement can do so." "What do you receive for your donation? Kexi will become more feature-full and stable as " "contributors will be able to devote more time to Kexi. Not only you can " "expect new features, but you can also have an influence on what features are added!" "Currently we are accepting donations through BountySource (a funding platform " "for open-source software) using secure PayPal, Bitcoin and Google Wallet transfers." "Contact us at https://community.kde.org/Kexi/Contact " "for more information." "Thanks for your support!"), xi18n("Donate to the Project"), KGuiItem(xi18nc("@action:button Go to Donation", "Proceed to the Donation Web Page"), QIcon(":/icons/heart.png")), KGuiItem(xi18nc("Do not donate now", "Not Now")), QString(), KMessageBox::Notify | KMessageBox::AllowLink)) { return; } QUrl donationUrl(d->property(this, "donation_url", "text").toString()); if (donationUrl.isValid()) { QDesktopServices::openUrl(donationUrl); - d->donated = true; - d->updateStatusWidget(); KConfigGroup configGroup(KSharedConfig::openConfig()->group("User Feedback")); - int donationsCount = configGroup.readEntry("DonationsCount", 0); configGroup.writeEntry("LastDonation", QDateTime::currentDateTime()); - configGroup.writeEntry("DonationsCount", donationsCount + 1); } else { qWarning() << "Invalid donation URL" << donationUrl; } } -void KexiWelcomeStatusBar::slotShareFeedback() -{ - d->statusScrollArea->setEnabled(true); - d->msgWidget->animatedHide(); - KexiMainWindowIface::global()->userFeedbackAgent() - ->setEnabledAreas(KexiUserFeedbackAgent::AllAreas); - - d->animatedHide(d->statusWidget, "share_usage_info"); - d->animatedHide(d->statusWidget, "share_more_usage_info"); - d->animatedHide(d->statusWidget, "lbl_contribute"); - d->updateUserProgress(); -} - -void KexiWelcomeStatusBar::slotCancelled() -{ - d->statusScrollArea->setEnabled(true); -} - -// Contribution Details BEGIN - -void KexiWelcomeStatusBar::showContributionDetails() -{ - d->showMaximizedMessageWidget("link_show_contribution_details", - &d->contributionDetailsLayout, - 0, - KexiWelcomeStatusBar::Private::AlignToWidget); - d->contributionDetailsLayout->setColumnMinimumWidth(0, 6); // smaller - d->contributionDetailsWidget = d->loadGui("contribution_details.ui"); - - KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); - d->setProperty(d->contributionDetailsWidget, "group_share", "checked", - f->enabledAreas() != KexiUserFeedbackAgent::NoAreas); - d->setProperty(d->contributionDetailsWidget, "group_basic", "title", - d->property(d->contributionDetailsWidget, "group_basic", "title") - .toString().arg(d->scores.value(KexiUserFeedbackAgent::BasicArea))); - - updateContributionGroupCheckboxes(); - - d->setProperty(d->contributionDetailsWidget, "group_system", "title", - d->property(d->contributionDetailsWidget, "group_system", "title") - .toString().arg(d->scores.value(KexiUserFeedbackAgent::SystemInfoArea))); - d->connect(d->contributionDetailsWidget, "group_system", SIGNAL(toggled(bool)), - this, SLOT(slotShareContributionDetailsGroupToggled(bool))); - - d->setProperty(d->contributionDetailsWidget, "group_screen", "title", - d->property(d->contributionDetailsWidget, "group_screen", "title") - .toString().arg(d->scores.value(KexiUserFeedbackAgent::ScreenInfoArea))); - d->connect(d->contributionDetailsWidget, "group_screen", SIGNAL(toggled(bool)), - this, SLOT(slotShareContributionDetailsGroupToggled(bool))); - - d->setProperty(d->contributionDetailsWidget, "group_regional_settings", "title", - d->property(d->contributionDetailsWidget, "group_regional_settings", "title") - .toString().arg(d->scores.value(KexiUserFeedbackAgent::RegionalSettingsArea))); - d->connect(d->contributionDetailsWidget, "group_regional_settings", SIGNAL(toggled(bool)), - this, SLOT(slotShareContributionDetailsGroupToggled(bool))); - - d->detailsDataVisible = false; - slotShareContributionDetailsToggled( - d->property(d->contributionDetailsWidget, "group_share", "checked").toBool()); - d->detailsDataVisible = true; // to switch off - slotToggleContributionDetailsDataVisibility(); - d->connect(d->contributionDetailsWidget, "group_share", SIGNAL(toggled(bool)), - this, SLOT(slotShareContributionDetailsToggled(bool))); - d->connect(d->contributionDetailsWidget, "link_show_shared_info", - SIGNAL(linkActivated(QString)), - this, SLOT(slotToggleContributionDetailsDataVisibility())); - - d->setProperty(d->contributionDetailsWidget, "label_where_is_info_sent", "visible", false); - - ScrollArea *contributionDetailsArea = new ScrollArea(d->msgWidget); - d->contributionDetailsLayout->addWidget(contributionDetailsArea, 1, 1); - contributionDetailsArea->setWidget(d->contributionDetailsWidget); - d->msgWidget->animatedShow(); - d->msgWidget->setPaletteInherited(); -} - -void KexiWelcomeStatusBar::updateContributionGroupCheckboxes() -{ - KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); - d->setProperty(d->contributionDetailsWidget, "group_system", "checked", - bool(f->enabledAreas() & KexiUserFeedbackAgent::SystemInfoArea)); - d->setProperty(d->contributionDetailsWidget, "group_screen", "checked", - bool(f->enabledAreas() & KexiUserFeedbackAgent::ScreenInfoArea)); - d->setProperty(d->contributionDetailsWidget, "group_regional_settings", "checked", - bool(f->enabledAreas() & KexiUserFeedbackAgent::RegionalSettingsArea)); -} - -void KexiWelcomeStatusBar::slotShareContributionDetailsToggled(bool on) -{ - //qDebug() << sender(); - QWidget* group_share = d->widget(d->contributionDetailsWidget, - "group_share"); - KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); - if (sender() == group_share) { - f->setEnabledAreas(on ? KexiUserFeedbackAgent::AllAreas : KexiUserFeedbackAgent::NoAreas); - updateContributionGroupCheckboxes(); - } - if (!group_share) { - return; - } - for (int i=0; i < group_share->layout()->count(); i++) { - QWidget *w = group_share->layout()->itemAt(i)->widget(); - if (w) { - w->setVisible(on); - } - } - if (d->detailsDataVisible) { - slotToggleContributionDetailsDataVisibility(); - } - // fill shared values - QLocale locale; - foreach(QLabel* lbl, d->contributionDetailsWidget->findChildren()) { - if (lbl->objectName().startsWith(QLatin1String("value_"))) { - QString name = lbl->objectName().mid(6); // cut "value_" - QVariant value; - if (name == QLatin1String("screen_size")) { - value = QString("%1 x %2").arg(f->value("screen_width").toString()) - .arg(f->value("screen_height").toString()); - } - else if (name == QLatin1String("country")) { - if (d->countryMask.isEmpty()) { - d->countryMask = lbl->text(); - } - value = d->countryMask - .arg(f->value(name).toString() /*!< @todo KEXI3 port KLocale::global()->countryCodeToName(f->value(name).toString()) */) - .arg(f->value(name).toString()); - } - else if (name == QLatin1String("language")) { - if (d->languageMask.isEmpty()) { - d->languageMask = lbl->text(); - } - value = d->languageMask - .arg(f->value(name).toString() /*!< @todo KEXI3 port KLocale::global()->languageCodeToName(f->value(name).toString()) */) - .arg(f->value(name).toString()); - } - else { - value = f->value(name); - } - - if (value.type() == QVariant::Bool) { - value = value.toBool() ? KStandardGuiItem::yes().plainText() - : KStandardGuiItem::no().plainText(); - } - - if (!value.isNull()) { - lbl->setText(value.toString()); - } - } - else if (lbl->objectName().startsWith(QLatin1String("desc_"))) { - lbl->setFont(d->smallFont); - } - } - QLabel* lbl; - KConfigGroup configGroup(KSharedConfig::openConfig()->group("User Feedback")); - if ((lbl = d->contributionDetailsWidget->findChild("value_recent_donation"))) { - QDateTime lastDonation = configGroup.readEntry("LastDonation", QDateTime()); - QString recentDonation = "-"; - if (lastDonation.isValid()) { - int days = lastDonation.secsTo(QDateTime::currentDateTime()) / 60 / 60 / 24; - if (days == 0) { - recentDonation = xi18nc("Donation today", "today"); - } - else if (days > 0) { - recentDonation = xi18ncp("Recent donation date (xx days)", "%1 (1 day)", "%1 (%2 days)", - locale.toString(lastDonation), days); - } - } - lbl->setText(recentDonation); - } - if ((lbl = d->contributionDetailsWidget->findChild("value_donations_count"))) { - int donationsCount = configGroup.readEntry("DonationsCount", 0); - if (donationsCount == 0) { - lbl->setText(QString::number(donationsCount)); - } - else { - lbl->setText(xi18nc("donations count", "%1 (thanks!)", donationsCount)); - } - } -} - -static void setArea(KexiUserFeedbackAgent::Areas *areas, - KexiUserFeedbackAgent::Area area, bool on) -{ - *areas |= area; - if (!on) { - *areas ^= area; - } -} - -void KexiWelcomeStatusBar::slotShareContributionDetailsGroupToggled(bool on) -{ - if (!sender()) { - return; - } - const QString name = sender()->objectName(); - KexiUserFeedbackAgent *f = KexiMainWindowIface::global()->userFeedbackAgent(); - KexiUserFeedbackAgent::Areas areas = f->enabledAreas(); - //qDebug() << areas; - if (name == "group_system") { - setArea(&areas, KexiUserFeedbackAgent::SystemInfoArea, on); - } - else if (name == "group_screen") { - setArea(&areas, KexiUserFeedbackAgent::ScreenInfoArea, on); - } - else if (name == "group_regional_settings") { - setArea(&areas, KexiUserFeedbackAgent::RegionalSettingsArea, on); - } - if (areas) { - areas |= KexiUserFeedbackAgent::AnonymousIdentificationArea; - } - f->setEnabledAreas(areas); - //qDebug() << f->enabledAreas(); -} - -void KexiWelcomeStatusBar::slotToggleContributionDetailsDataVisibility() -{ - QWidget* value_app_ver = d->widget(d->contributionDetailsWidget, "value_app_ver"); - if (!value_app_ver) { - return; - } - d->detailsDataVisible = !d->detailsDataVisible; - if (d->detailsDataVisible) { - d->setProperty(d->contributionDetailsWidget, "link_show_shared_info", "visible", false); - d->setProperty(d->contributionDetailsWidget, "label_where_is_info_sent", "visible", true); - } - bool show = d->contributionDetailsWidget->isVisible(); - QList list; - d->contributionDetailsWidget->hide(); - QWidget* group_basic = d->widget(d->contributionDetailsWidget, "group_basic"); - if (group_basic) { - list += group_basic->findChildren(); - } - QWidget* group_system = d->widget(d->contributionDetailsWidget, "group_system"); - if (group_system) { - list += group_system->findChildren(); - } - QWidget* group_screen = d->widget(d->contributionDetailsWidget, "group_screen"); - if (group_screen) { - list += group_screen->findChildren(); - } - QWidget* group_regional_settings = d->widget(d->contributionDetailsWidget, "group_regional_settings"); - if (group_regional_settings) { - list += group_regional_settings->findChildren(); - } - - foreach (QWidget* w, list) { - if (qobject_cast(w) && !w->objectName().startsWith(QLatin1String("desc_"))) { - //qDebug() << "+++" << w; - w->setVisible(d->detailsDataVisible); - } - } - if (show) { - d->contributionDetailsWidget->show(); - } -} - -// Contribution Details END - #include "KexiWelcomeStatusBar.moc" diff --git a/src/main/startup/KexiWelcomeStatusBar.h b/src/main/startup/KexiWelcomeStatusBar.h index 905771638..3f07de8f7 100644 --- a/src/main/startup/KexiWelcomeStatusBar.h +++ b/src/main/startup/KexiWelcomeStatusBar.h @@ -1,59 +1,48 @@ /* This file is part of the KDE project Copyright (C) 2012 Jarosław Staniek This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KEXIWELCOMESTATUSBAR_H #define KEXIWELCOMESTATUSBAR_H #include //! Status sidebar for the welcome page class KexiWelcomeStatusBar : public QWidget { Q_OBJECT public: explicit KexiWelcomeStatusBar(QWidget* parent = 0); ~KexiWelcomeStatusBar(); QPixmap userProgressPixmap(); QPixmap externalLinkPixmap(); private Q_SLOTS: - void showContributionHelp(); - void showShareUsageInfo(); - void showContributionDetails(); void showDonation(); - void slotShareFeedback(); - void slotCancelled(); - //! Used for async show for speeding up the message displaying - void slotShowContributionHelpContents(); - void slotMessageWidgetClosed(); - void slotShareContributionDetailsToggled(bool on); - void slotShareContributionDetailsGroupToggled(bool on); - void slotToggleContributionDetailsDataVisibility(); private: void init(); void updateContributionGroupCheckboxes(); class Private; Private * const d; }; #endif diff --git a/src/main/status/status.ui b/src/main/status/status.ui index 4f9652200..c8fbed0da 100644 --- a/src/main/status/status.ui +++ b/src/main/status/status.ui @@ -1,493 +1,240 @@ Form 0 0 263 561 0 0 - - - - 6 - - - - - - 75 - true - - - - You in Kexi Project - - - - - - - - 1 - 0 - - - - Qt::StrongFocus - - - <a href="link" style="color:gray;">( ? )</a> - - - - - - - - - - 0 - 0 - - - - - 0 - 6 - - - - - 16777215 - 6 - - - - - - - - 6 - - - - - - 0 - 0 - - - - - 80 - 16777215 - - - - false - - - - - - - <b>%1%</b> involved - - - - - - - - - - 0 - 0 - - - - - 0 - 12 - - - - - 16777215 - 6 - - - - - - - - - 75 - true - - - - Contribute: - - - 0 0 75 true :/icons/heart.png Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Qt::StrongFocus <b><a href="link">Donate</a></b><br/><span style="color:gray;"><b>Boost development, support new features</b> (+%1%)</span> true - - - - - - - - 0 - 0 - - - - - 75 - true - - - - - - - :/icons/add.png - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Qt::StrongFocus - - - <a href="link">Share Usage Info</a><br/><span style="color:gray;">Let us adapt to your needs (+%1%)</span> - - - true - - - - - - - - - - - - - - 0 - 0 - - - - - 75 - true - - - - - - - :/icons/add.png - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - - - Qt::StrongFocus - - - <a href="link">Share More Usage Info</a><br/><span style="color:gray;">Let us adapt to your needs (+%1%)</span> - - - true - - - - - - 0 0 0 6 16777215 6 Qt::StrongFocus <span style="color:gray;">Thank you for being part of Kexi!</span> true 0 - - - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - - - - - - - 50 - false - - - - Qt::StrongFocus - - - <a href="link"><b>Details</b> &raquo;</a> - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - 0 0 0 24 16777215 6 75 true Visit 50 false Qt::StrongFocus Visit http://kexi-project home page <a href="http://kexi-project.org"><nobr><b>Home page &raquo;</b></nobr></a> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true 4 6 true 50 false Qt::StrongFocus Visit http://kexi-project.org/forum <a href="http://kexi-project.org/forum/"><b>Forum</b> for help and <nobr>discussions &raquo;</nobr></a> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter true 4 6 true https://salt.bountysource.com/checkout/amount?team=kexi Qt::Vertical 20 48