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

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 "timelinewidget.h"
0010 
0011 #include <QLabel>
0012 #include <QPointer>
0013 #include <QPushButton>
0014 #include <QScrollArea>
0015 #include <QScrollBar>
0016 #include <QTextDocument>
0017 #include <QTimer>
0018 #include <QVBoxLayout>
0019 
0020 #include "account.h"
0021 #include "choqokappearancesettings.h"
0022 #include "choqokbehaviorsettings.h"
0023 #include "libchoqokdebug.h"
0024 #include "microblog.h"
0025 #include "postwidget.h"
0026 #include "notifymanager.h"
0027 
0028 namespace Choqok
0029 {
0030 namespace UI
0031 {
0032 
0033 class TimelineWidget::Private
0034 {
0035 public:
0036     Private(Account *account, const QString &timelineName)
0037         : currentAccount(account), timelineName(timelineName),
0038           btnMarkAllAsRead(nullptr), unreadCount(0), placeholderLabel(nullptr), info(nullptr), isClosable(false)
0039     {
0040         if (account->microblog()->isValidTimeline(timelineName)) {
0041             info = account->microblog()->timelineInfo(timelineName);
0042         } else {//It's search timeline
0043             info = new Choqok::TimelineInfo;
0044             info->name = timelineName;
0045             info->description = i18nc("%1 is the name of a timeline", "Search results for %1", timelineName);
0046         }
0047     }
0048     Account *currentAccount;
0049     QString timelineName;
0050     bool mStartUp;
0051     QPointer<QPushButton> btnMarkAllAsRead;
0052     int unreadCount;
0053     QMap<QString, PostWidget *> posts;
0054     QMultiMap<QDateTime, PostWidget *>  sortedPostsList;
0055     QVBoxLayout *mainLayout;
0056     QHBoxLayout *titleBarLayout;
0057     QLabel *lblDesc;
0058     QLabel *placeholderLabel;
0059     QScrollArea *scrollArea;
0060     int order;            // 0: web, -1: natural
0061     Choqok::TimelineInfo *info;
0062     bool isClosable;
0063     QIcon timelineIcon;
0064 };
0065 
0066 TimelineWidget::TimelineWidget(Choqok::Account *account, const QString &timelineName, QWidget *parent /*= 0*/)
0067     : QWidget(parent), d(new Private(account, timelineName))
0068 {
0069     setAttribute(Qt::WA_DeleteOnClose);
0070     setupUi();
0071     loadTimeline();
0072 }
0073 
0074 TimelineWidget::~TimelineWidget()
0075 {
0076     delete d;
0077 }
0078 
0079 void TimelineWidget::loadTimeline()
0080 {
0081     QList<Choqok::Post *> list = currentAccount()->microblog()->loadTimeline(currentAccount(), timelineName());
0082     connect(currentAccount()->microblog(), &MicroBlog::saveTimelines, this, &TimelineWidget::saveTimeline);
0083 
0084     if (!BehaviorSettings::markAllAsReadOnExit()) {
0085         addNewPosts(list);
0086     } else {
0087         for (Choqok::Post *p: list) {
0088             PostWidget *pw = d->currentAccount->microblog()->createPostWidget(d->currentAccount, p, this);
0089             if (pw) {
0090                 pw->setRead();
0091                 addPostWidgetToUi(pw);
0092             }
0093         }
0094     }
0095 }
0096 
0097 QString TimelineWidget::timelineName()
0098 {
0099     return d->timelineName;
0100 }
0101 
0102 QString TimelineWidget::timelineInfoName()
0103 {
0104     return d->info->name;
0105 }
0106 
0107 QString TimelineWidget::timelineIconName()
0108 {
0109     return d->info->icon;
0110 }
0111 
0112 void TimelineWidget::setTimelineIcon(const QIcon &icon)
0113 {
0114     d->timelineIcon = icon;
0115 }
0116 
0117 QIcon &TimelineWidget::timelineIcon() const
0118 {
0119     return d->timelineIcon;
0120 }
0121 
0122 void TimelineWidget::setTimelineName(const QString &type)
0123 {
0124     d->timelineName = type;
0125 }
0126 
0127 void TimelineWidget::setupUi()
0128 {
0129     d->lblDesc = new QLabel(this);
0130     TimelineInfo *info = currentAccount()->microblog()->timelineInfo(d->timelineName);
0131     if (info) {
0132         d->lblDesc->setText(info->description.toHtmlEscaped());
0133     }
0134     d->lblDesc->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
0135     d->lblDesc->setWordWrap(true);
0136     d->lblDesc->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
0137     QFont fnt = d->lblDesc->font();
0138     fnt.setBold(true);
0139     d->lblDesc->setFont(fnt);
0140 
0141     QVBoxLayout *gridLayout;
0142     QWidget *scrollAreaWidgetContents;
0143     QVBoxLayout *verticalLayout_2;
0144     QSpacerItem *verticalSpacer;
0145     gridLayout = new QVBoxLayout(this);
0146     gridLayout->setMargin(0);
0147     gridLayout->setObjectName(QLatin1String("gridLayout"));
0148     d->scrollArea = new QScrollArea(this);
0149     d->scrollArea->setObjectName(QLatin1String("scrollArea"));
0150     d->scrollArea->setFrameShape(QFrame::NoFrame);
0151     d->scrollArea->setWidgetResizable(true);
0152     scrollAreaWidgetContents = new QWidget();
0153     scrollAreaWidgetContents->setObjectName(QLatin1String("scrollAreaWidgetContents"));
0154     scrollAreaWidgetContents->setGeometry(QRect(0, 0, 254, 300));
0155     verticalLayout_2 = new QVBoxLayout(scrollAreaWidgetContents);
0156     verticalLayout_2->setMargin(1);
0157     d->mainLayout = new QVBoxLayout();
0158     verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
0159 
0160     d->mainLayout->addItem(verticalSpacer);
0161     d->mainLayout->setSpacing(5);
0162     d->mainLayout->setMargin(1);
0163 
0164     d->titleBarLayout = new QHBoxLayout;
0165     d->titleBarLayout->addWidget(d->lblDesc);
0166     verticalLayout_2->addLayout(d->mainLayout);
0167 
0168     d->scrollArea->setWidget(scrollAreaWidgetContents);
0169 
0170     gridLayout->addLayout(d->titleBarLayout);
0171     gridLayout->addWidget(d->scrollArea);
0172     if (AppearanceSettings::useReverseOrder()) {
0173         d->order = -1;
0174         QTimer::singleShot(0, this, SLOT(scrollToBottom()));
0175     } else {
0176         d->order = 0;
0177     }
0178 }
0179 
0180 void TimelineWidget::removeOldPosts()
0181 {
0182     int count = d->sortedPostsList.count() - BehaviorSettings::countOfPosts();
0183 //     qCDebug(CHOQOK)<<count;
0184     while (count > 0 && !d->sortedPostsList.isEmpty()) {
0185         PostWidget *wd = d->sortedPostsList.values().first();
0186         if (wd && wd->isRead()) {
0187             wd->close();
0188         }
0189         --count;
0190     }
0191 }
0192 
0193 void TimelineWidget::addPlaceholderMessage(const QString &msg)
0194 {
0195     if ( d->posts.isEmpty() ) {
0196         if (!d->placeholderLabel) {
0197             d->placeholderLabel = new QLabel(this);
0198             d->mainLayout->insertWidget(d->order, d->placeholderLabel);
0199         }
0200         d->placeholderLabel->setText(msg);
0201     }
0202 }
0203 
0204 void TimelineWidget::addNewPosts(QList< Choqok::Post * > &postList)
0205 {
0206     qCDebug(CHOQOK) << d->currentAccount->alias() << d->timelineName << postList.count();
0207     int unread = 0;
0208     for (Choqok::Post *p: postList) {
0209         if (d->posts.keys().contains(p->postId)) {
0210             continue;
0211         }
0212         PostWidget *pw = d->currentAccount->microblog()->createPostWidget(d->currentAccount, p, this);
0213         if (pw) {
0214             addPostWidgetToUi(pw);
0215             if (!pw->isRead()) {
0216                 ++unread;
0217             }
0218         }
0219     }
0220     removeOldPosts();
0221     if (unread) {
0222         d->unreadCount += unread;
0223         Choqok::NotifyManager::newPostArrived(i18np("1 new post in %2 (%3)",
0224                                               "%1 new posts in %2 (%3)",
0225                                               unread, currentAccount()->alias(), d->timelineName));
0226 
0227         Q_EMIT updateUnreadCount(unread);
0228         showMarkAllAsReadButton();
0229     }
0230 }
0231 
0232 void TimelineWidget::showMarkAllAsReadButton()
0233 {
0234     if (d->btnMarkAllAsRead) {
0235         delete d->btnMarkAllAsRead;
0236     }
0237 
0238     d->btnMarkAllAsRead = new QPushButton(this);
0239     d->btnMarkAllAsRead->setIcon(QIcon::fromTheme(QLatin1String("mail-mark-read")));
0240     d->btnMarkAllAsRead->setToolTip(i18n("Mark timeline as read"));
0241     d->btnMarkAllAsRead->setMaximumSize(14, 14);
0242     d->btnMarkAllAsRead->setIconSize(QSize(12, 12));
0243     connect(d->btnMarkAllAsRead, &QPushButton::clicked, this, &TimelineWidget::markAllAsRead);
0244     d->titleBarLayout->addWidget(d->btnMarkAllAsRead);
0245 }
0246 
0247 void TimelineWidget::addPostWidgetToUi(PostWidget *widget)
0248 {
0249     widget->initUi();
0250     widget->setFocusProxy(this);
0251     widget->setObjectName(widget->currentPost()->postId);
0252     connect(widget, &PostWidget::resendPost, this, &TimelineWidget::forwardResendPost);
0253     connect(widget, &PostWidget::reply, this, &TimelineWidget::forwardReply);
0254     connect(widget, &PostWidget::postReaded, this, &TimelineWidget::slotOnePostReaded);
0255     connect(widget, &PostWidget::aboutClosing, this, &TimelineWidget::postWidgetClosed);
0256     d->mainLayout->insertWidget(d->order, widget);
0257     d->posts.insert(widget->currentPost()->postId, widget);
0258     d->sortedPostsList.insert(widget->currentPost()->creationDateTime, widget);
0259     Global::SessionManager::self()->emitNewPostWidgetAdded(widget, currentAccount(), timelineName());
0260     if (d->placeholderLabel) {
0261         d->mainLayout->removeWidget(d->placeholderLabel);
0262         delete d->placeholderLabel;
0263         d->placeholderLabel = nullptr;
0264     }
0265 }
0266 
0267 int TimelineWidget::unreadCount() const
0268 {
0269     return d->unreadCount;
0270 }
0271 
0272 void TimelineWidget::setUnreadCount(int unread)
0273 {
0274     d->unreadCount = unread;
0275 }
0276 
0277 void TimelineWidget::markAllAsRead()
0278 {
0279     if (d->unreadCount > 0) {
0280         for (PostWidget *pw: d->sortedPostsList) {
0281             pw->setRead();
0282         }
0283         int unread = -d->unreadCount;
0284         d->unreadCount = 0;
0285         Q_EMIT updateUnreadCount(unread);
0286         d->btnMarkAllAsRead->deleteLater();
0287     }
0288 }
0289 
0290 void TimelineWidget::scrollToBottom()
0291 {
0292     d->scrollArea->verticalScrollBar()->
0293     triggerAction(QAbstractSlider::SliderToMaximum);
0294 }
0295 
0296 Account *TimelineWidget::currentAccount()
0297 {
0298     return d->currentAccount;
0299 }
0300 
0301 void TimelineWidget::settingsChanged()
0302 {
0303     for (PostWidget *pw: d->sortedPostsList) {
0304         pw->setUiStyle();
0305     }
0306 }
0307 
0308 void TimelineWidget::slotOnePostReaded()
0309 {
0310     d->unreadCount--;
0311     Q_EMIT updateUnreadCount(-1);
0312     if (d->unreadCount == 0) {
0313         d->btnMarkAllAsRead->deleteLater();
0314     }
0315 }
0316 
0317 void TimelineWidget::saveTimeline()
0318 {
0319     if (currentAccount()->microblog()) {
0320         currentAccount()->microblog()->saveTimeline(currentAccount(), timelineName(), posts().values());
0321     }
0322 }
0323 
0324 QList< PostWidget * > TimelineWidget::postWidgets()
0325 {
0326     return posts().values();
0327 }
0328 
0329 void TimelineWidget::postWidgetClosed(const QString &postId, PostWidget *post)
0330 {
0331     d->posts.remove(postId);
0332     d->sortedPostsList.remove(post->currentPost()->creationDateTime, post);
0333 }
0334 
0335 QMap< QString, PostWidget * > &TimelineWidget::posts() const
0336 {
0337     return d->posts;
0338 }
0339 
0340 QMultiMap< QDateTime, PostWidget * > &TimelineWidget::sortedPostsList() const
0341 {
0342     return d->sortedPostsList;
0343 }
0344 
0345 QLabel *TimelineWidget::timelineDescription()
0346 {
0347     return d->lblDesc;
0348 }
0349 
0350 QVBoxLayout *TimelineWidget::mainLayout()
0351 {
0352     return d->mainLayout;
0353 }
0354 
0355 QHBoxLayout *TimelineWidget::titleBarLayout()
0356 {
0357     return d->titleBarLayout;
0358 }
0359 
0360 bool TimelineWidget::isClosable() const
0361 {
0362     return d->isClosable;
0363 }
0364 
0365 void TimelineWidget::setClosable(bool isClosable)
0366 {
0367     d->isClosable = isClosable;
0368 }
0369 
0370 }
0371 }
0372 
0373 #include "moc_timelinewidget.cpp"