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"