File indexing completed on 2024-04-28 04:55:40

0001 /*
0002     This file is part of Choqok, the KDE micro-blogging client
0003 
0004     SPDX-FileCopyrightText: 2008-2012 Mehrdad Momeny <mehrdad.momeny@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007 */
0008 
0009 #include "microblogwidget.h"
0010 
0011 #include <QKeyEvent>
0012 #include <QLabel>
0013 #include <QPainter>
0014 #include <QPainterPath>
0015 #include <QPointer>
0016 #include <QPushButton>
0017 #include <QStatusBar>
0018 #include <QTime>
0019 #include <QVBoxLayout>
0020 
0021 #include <KMessageBox>
0022 
0023 #include "account.h"
0024 #include "choqokappearancesettings.h"
0025 #include "choqoktextedit.h"
0026 #include "choqokuiglobal.h"
0027 #include "composerwidget.h"
0028 #include "libchoqokdebug.h"
0029 #include "notifymanager.h"
0030 #include "timelinewidget.h"
0031 
0032 namespace Choqok
0033 {
0034 namespace UI
0035 {
0036 
0037 QIcon addNumToIcon(const QIcon &big , int number , const QSize &result_size , const QPalette &palette)
0038 {
0039     QIcon result;
0040 
0041     QList<QIcon::Mode> mods;
0042     mods << QIcon::Active /*<< QIcon::Disabled << QIcon::Selected*/;
0043 
0044     for (const QIcon::Mode &m: mods) {
0045         QPixmap pixmap = big.pixmap(result_size);
0046         QPainter painter(&pixmap);
0047         QFont font;
0048         font.setWeight(result_size.height() / 2);
0049         font.setBold(true);
0050         font.setItalic(true);
0051         painter.setFont(font);
0052 
0053         QString numberStr = QString::number(number);
0054         int textWidth = painter.fontMetrics().horizontalAdvance(numberStr) + 6;
0055 
0056         if (textWidth < result_size.width() / 2) {
0057             textWidth = result_size.width() / 2;
0058         }
0059 
0060         QRect rct(result_size.width() - textWidth , result_size.width() / 2 ,
0061                   textWidth , result_size.height() / 2);
0062         QPointF center(rct.x() + rct.width() / 2 , rct.y() + rct.height() / 2);
0063 
0064         QPainterPath cyrcle_path;
0065         cyrcle_path.moveTo(center);
0066         cyrcle_path.arcTo(rct, 0, 360);
0067 
0068         painter.setRenderHint(QPainter::Antialiasing);
0069         painter.fillPath(cyrcle_path , palette.color(QPalette::Active , QPalette::Window));
0070         painter.setPen(palette.color(QPalette::Active , QPalette::Text));
0071         painter.drawText(rct , Qt::AlignHCenter | Qt::AlignVCenter , QString::number(number));
0072 
0073         result.addPixmap(pixmap , m);
0074     }
0075 
0076     return result;
0077 }
0078 
0079 class MicroBlogWidget::Private
0080 {
0081 public:
0082     Private(Account *acc)
0083         : account(acc), blog(acc->microblog()), composer(nullptr), btnMarkAllAsRead(nullptr)
0084     {
0085     }
0086     Account *account;
0087     MicroBlog *blog;
0088     QPointer<ComposerWidget> composer;
0089     QMap<QString, TimelineWidget *> timelines;
0090     Choqok::UI::ChoqokTabBar *timelinesTabWidget;
0091     QLabel *latestUpdate;
0092     QPushButton *btnMarkAllAsRead;
0093     QHBoxLayout *toolbar;
0094     QFrame *toolbar_widget;
0095 };
0096 
0097 MicroBlogWidget::MicroBlogWidget(Account *account, QWidget *parent)
0098     : QWidget(parent), d(new Private(account))
0099 {
0100     qCDebug(CHOQOK);
0101     connect(d->blog, &MicroBlog::timelineDataReceived, this, &MicroBlogWidget::newTimelineDataRecieved);
0102     connect(d->blog, &MicroBlog::error, this, &MicroBlogWidget::error);
0103     connect(d->blog, &MicroBlog::errorPost, this, &MicroBlogWidget::errorPost);
0104 }
0105 
0106 Account *MicroBlogWidget::currentAccount() const
0107 {
0108     return d->account;
0109 }
0110 
0111 void MicroBlogWidget::initUi()
0112 {
0113     d->toolbar_widget = new QFrame();
0114     d->toolbar_widget->setFrameShape(QFrame::StyledPanel);
0115     d->toolbar_widget->setFrameShadow(QFrame::Sunken);
0116 
0117     QVBoxLayout *layout = new QVBoxLayout(this);
0118     QVBoxLayout *toolbar_layout = new QVBoxLayout(d->toolbar_widget);
0119     toolbar_layout->addLayout(createToolbar());
0120 
0121     d->timelinesTabWidget = new Choqok::UI::ChoqokTabBar(this);
0122     d->timelinesTabWidget->setLinkedTabBar(true);
0123     d->timelinesTabWidget->setTabCloseActivatePrevious(true);
0124     d->timelinesTabWidget->setExtraWidget(d->toolbar_widget , Choqok::UI::ChoqokTabBar::Top);
0125 
0126     if (!d->account->isReadOnly()) {
0127         setComposerWidget(d->blog->createComposerWidget(currentAccount(), this));
0128     }
0129 
0130     layout->addWidget(d->timelinesTabWidget);
0131     this->layout()->setContentsMargins(0, 0, 0, 0);
0132     connect(currentAccount(), &Account::modified, this, &MicroBlogWidget::slotAccountModified);
0133     initTimelines();
0134 }
0135 
0136 void MicroBlogWidget::setComposerWidget(ComposerWidget *widget)
0137 {
0138     if (d->composer) {
0139         d->composer->deleteLater();
0140     }
0141     if (!widget) {
0142         d->composer = nullptr;
0143         return;
0144     }
0145     d->composer = widget;
0146     d->composer->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
0147     qobject_cast<QVBoxLayout *>(d->toolbar_widget->layout())->insertWidget(1, d->composer);
0148     for (TimelineWidget *mbw: d->timelines) {
0149         connect(mbw, SIGNAL(forwardResendPost(QString)), d->composer, SLOT(setText(QString)));
0150         connect(mbw, &TimelineWidget::forwardReply, d->composer, &ComposerWidget::setText);
0151     }
0152 }
0153 
0154 MicroBlogWidget::~MicroBlogWidget()
0155 {
0156     qCDebug(CHOQOK);
0157     delete d;
0158 }
0159 
0160 TimelineWidget *MicroBlogWidget::currentTimeline()
0161 {
0162     return qobject_cast<TimelineWidget *>(d->timelinesTabWidget->currentWidget());
0163 }
0164 
0165 uint MicroBlogWidget::unreadCount() const
0166 {
0167     uint sum = 0;
0168     for (TimelineWidget *wd: d->timelines) {
0169         sum += wd->unreadCount();
0170     }
0171 
0172     return sum;
0173 }
0174 
0175 void MicroBlogWidget::settingsChanged()
0176 {
0177     for (TimelineWidget *wd: d->timelines) {
0178         wd->settingsChanged();
0179     }
0180 }
0181 
0182 void MicroBlogWidget::updateTimelines()
0183 {
0184     qCDebug(CHOQOK) << d->account->alias();
0185     d->account->microblog()->updateTimelines(currentAccount());
0186 }
0187 
0188 void MicroBlogWidget::removeOldPosts()
0189 {
0190     for (TimelineWidget *wd: d->timelines) {
0191         wd->removeOldPosts();
0192     }
0193 }
0194 
0195 void MicroBlogWidget::newTimelineDataRecieved(Choqok::Account *theAccount, const QString &type,
0196         QList< Choqok::Post * > data)
0197 {
0198     if (theAccount != currentAccount()) {
0199         return;
0200     }
0201 
0202     qCDebug(CHOQOK) << d->account->alias() << ":" << type;
0203     d->latestUpdate->setText(QTime::currentTime().toString());
0204     if (d->timelines.contains(type)) {
0205         d->timelines.value(type)->addNewPosts(data);
0206     } else {
0207         if (TimelineWidget *wd = addTimelineWidgetToUi(type)) {
0208             wd->addNewPosts(data);
0209         }
0210     }
0211 }
0212 
0213 void MicroBlogWidget::initTimelines()
0214 {
0215     qCDebug(CHOQOK);
0216     for (const QString &timeline: d->account->timelineNames()) {
0217         addTimelineWidgetToUi(timeline);
0218     }
0219 //     qCDebug(CHOQOK)<<"========== Emiting loaded()";
0220     Q_EMIT loaded();
0221 }
0222 
0223 TimelineWidget *MicroBlogWidget::addTimelineWidgetToUi(const QString &name)
0224 {
0225     TimelineWidget *mbw = d->blog->createTimelineWidget(d->account, name, this);
0226     if (mbw) {
0227         Choqok::TimelineInfo *info = currentAccount()->microblog()->timelineInfo(name);
0228         d->timelines.insert(name, mbw);
0229         d->timelinesTabWidget->addTab(mbw, info->name);
0230         d->timelinesTabWidget->setTabIcon(d->timelinesTabWidget->indexOf(mbw), QIcon::fromTheme(info->icon));
0231         connect(mbw, SIGNAL(updateUnreadCount(int)), this, SLOT(slotUpdateUnreadCount(int)));
0232         if (d->composer) {
0233             connect(mbw, SIGNAL(forwardResendPost(QString)), d->composer, SLOT(setText(QString)));
0234             connect(mbw, &TimelineWidget::forwardReply, d->composer, &ComposerWidget::setText);
0235         }
0236         slotUpdateUnreadCount(mbw->unreadCount(), mbw);
0237     } else {
0238         qCDebug(CHOQOK) << "Cannot Create a new TimelineWidget for timeline " << name;
0239         return nullptr;
0240     }
0241     if (d->timelinesTabWidget->count() == 1) {
0242         d->timelinesTabWidget->setTabBarHidden(true);
0243     } else {
0244         d->timelinesTabWidget->setTabBarHidden(false);
0245     }
0246     return mbw;
0247 }
0248 
0249 void MicroBlogWidget::slotUpdateUnreadCount(int change, Choqok::UI::TimelineWidget *widget)
0250 {
0251     qCDebug(CHOQOK) << change;
0252     int sum = 0;
0253     for (TimelineWidget *mbw: d->timelines) {
0254         sum += mbw->unreadCount();
0255     }
0256     if (change != 0) {
0257         Q_EMIT updateUnreadCount(change, sum);
0258     }
0259 
0260     if (sum > 0) {
0261         if (!d->btnMarkAllAsRead) {
0262             d->btnMarkAllAsRead = new QPushButton(this);
0263             d->btnMarkAllAsRead->setIcon(QIcon::fromTheme(QLatin1String("mail-mark-read")));
0264             d->btnMarkAllAsRead->setIconSize(QSize(14, 14));
0265             d->btnMarkAllAsRead->setToolTip(i18n("Mark all timelines as read"));
0266             d->btnMarkAllAsRead->setMaximumWidth(d->btnMarkAllAsRead->height());
0267             connect(d->btnMarkAllAsRead, &QPushButton::clicked, this, &MicroBlogWidget::markAllAsRead);
0268             d->toolbar->insertWidget(1, d->btnMarkAllAsRead);
0269         }
0270     } else {
0271         d->btnMarkAllAsRead->deleteLater();
0272         d->btnMarkAllAsRead = nullptr;
0273     }
0274     TimelineWidget *wd = qobject_cast<TimelineWidget *>(sender());
0275     if (!wd) {
0276         wd = widget;
0277     }
0278     if (wd) {
0279         qCDebug(CHOQOK) << wd->unreadCount();
0280         int tabIndex = d->timelinesTabWidget->indexOf(wd);
0281         if (tabIndex == -1) {
0282             return;
0283         }
0284         if (wd->unreadCount() > 0) {
0285             d->timelinesTabWidget->setTabIcon(tabIndex , addNumToIcon(timelinesTabWidget()->tabIcon(tabIndex) , wd->unreadCount() , QSize(40, 40) , palette()));
0286             d->timelinesTabWidget->setTabText(tabIndex, wd->timelineInfoName() +
0287                                               QStringLiteral(" (%1)").arg(wd->unreadCount()));
0288         } else {
0289             if (!wd->timelineIconName().isEmpty()) {
0290                 d->timelinesTabWidget->setTabIcon(tabIndex , QIcon::fromTheme(wd->timelineIconName()));
0291             } else {
0292                 d->timelinesTabWidget->setTabIcon(tabIndex , wd->timelineIcon());
0293             }
0294 
0295             d->timelinesTabWidget->setTabText(tabIndex, wd->timelineInfoName());
0296         }
0297     }
0298 }
0299 
0300 void MicroBlogWidget::markAllAsRead()
0301 {
0302     if (d->btnMarkAllAsRead) {
0303         d->btnMarkAllAsRead->deleteLater();
0304         d->btnMarkAllAsRead = nullptr;
0305     }
0306     for (TimelineWidget *wd: d->timelines) {
0307         wd->markAllAsRead();
0308         int tabIndex = d->timelinesTabWidget->indexOf(wd);
0309         if (tabIndex == -1) {
0310             continue;
0311         }
0312         d->timelinesTabWidget->setTabText(tabIndex, wd->timelineInfoName());
0313     }
0314 }
0315 
0316 ComposerWidget *MicroBlogWidget::composer()
0317 {
0318     return d->composer;
0319 }
0320 
0321 QMap< QString, TimelineWidget * > &MicroBlogWidget::timelines()
0322 {
0323     return d->timelines;
0324 }
0325 
0326 Choqok::UI::ChoqokTabBar *MicroBlogWidget::timelinesTabWidget()
0327 {
0328     return d->timelinesTabWidget;
0329 }
0330 
0331 void MicroBlogWidget::error(Choqok::Account *theAccount, MicroBlog::ErrorType errorType,
0332                             const QString &errorMsg, MicroBlog::ErrorLevel level)
0333 {
0334     if (theAccount == d->account) {
0335         switch (level) {
0336         case MicroBlog::Critical:
0337             KMessageBox::error(Choqok::UI::Global::mainWindow(), errorMsg, MicroBlog::errorString(errorType));
0338             break;
0339         case MicroBlog::Normal:
0340             NotifyManager::error(errorMsg, MicroBlog::errorString(errorType));
0341             break;
0342         default:
0343 //             emit showStatusMessage(errorMsg);
0344             if (Choqok::UI::Global::mainWindow()->statusBar()) {
0345                 Choqok::UI::Global::mainWindow()->statusBar()->showMessage(errorMsg);
0346             }
0347             break;
0348         };
0349     }
0350 }
0351 void MicroBlogWidget::errorPost(Choqok::Account *theAccount, Choqok::Post *, MicroBlog::ErrorType errorType,
0352                                 const QString &errorMsg, MicroBlog::ErrorLevel level)
0353 {
0354     if (theAccount == d->account) {
0355         switch (level) {
0356         case MicroBlog::Critical:
0357             KMessageBox::error(Choqok::UI::Global::mainWindow(), errorMsg, MicroBlog::errorString(errorType));
0358             break;
0359         case MicroBlog::Normal:
0360             NotifyManager::error(errorMsg, MicroBlog::errorString(errorType));
0361             break;
0362         default:
0363 //             emit showStatusMessage(errorMsg);
0364             if (Choqok::UI::Global::mainWindow()->statusBar()) {
0365                 Choqok::UI::Global::mainWindow()->statusBar()->showMessage(errorMsg);
0366             }
0367             break;
0368         };
0369     }
0370 }
0371 
0372 QLayout *MicroBlogWidget::createToolbar()
0373 {
0374     d->toolbar = new QHBoxLayout;
0375     QPushButton *btnActions = new QPushButton(i18n("More"), this);
0376 
0377     QLabel *lblLatestUpdate = new QLabel(i18n("Latest update:"), this);
0378     lblLatestUpdate->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
0379     d->latestUpdate = new QLabel(QTime::currentTime().toString(), this);
0380     QFont fnt = lblLatestUpdate->font();
0381     fnt.setPointSize(fnt.pointSize() - 1);
0382     lblLatestUpdate->setFont(fnt);
0383     fnt.setBold(true);
0384     d->latestUpdate->setFont(fnt);
0385 
0386     btnActions->setMenu(d->account->microblog()->createActionsMenu(d->account));
0387     d->toolbar->addWidget(btnActions);
0388     d->toolbar->addSpacerItem(new QSpacerItem(1, 10, QSizePolicy::Expanding));
0389     d->toolbar->addWidget(lblLatestUpdate);
0390     d->toolbar->addWidget(d->latestUpdate);
0391     return d->toolbar;
0392 }
0393 
0394 void MicroBlogWidget::slotAbortAllJobs()
0395 {
0396     currentAccount()->microblog()->abortAllJobs(currentAccount());
0397     composer()->abort();
0398 }
0399 
0400 void MicroBlogWidget::keyPressEvent(QKeyEvent *e)
0401 {
0402     if (e->key() == Qt::Key_Escape && composer()) {
0403         composer()->abort();
0404     }
0405     QWidget::keyPressEvent(e);
0406 }
0407 
0408 void MicroBlogWidget::setFocus()
0409 {
0410     if (composer()) {
0411         composer()->editor()->setFocus(Qt::OtherFocusReason);
0412     } else {
0413         QWidget::setFocus();
0414     }
0415 }
0416 
0417 void MicroBlogWidget::slotAccountModified(Account *theAccount)
0418 {
0419     if (theAccount == currentAccount()) {
0420         if (theAccount->isReadOnly()) {
0421             if (composer()) {
0422                 setComposerWidget(nullptr);
0423             }
0424         } else if (!composer()) {
0425             setComposerWidget(theAccount->microblog()->createComposerWidget(theAccount, this));
0426         }
0427         int sum = 0;
0428         for (TimelineWidget *mbw: d->timelines) {
0429             sum += mbw->unreadCount();
0430         }
0431         Q_EMIT updateUnreadCount(0, sum);
0432     }
0433 }
0434 
0435 QLabel *MicroBlogWidget::latestUpdate()
0436 {
0437     return d->latestUpdate;
0438 }
0439 
0440 }
0441 }
0442 
0443 #include "moc_microblogwidget.cpp"