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"