mirror of
https://bitbucket.org/smil3y/kde-extraapps.git
synced 2025-02-25 11:22:55 +00:00
1043 lines
34 KiB
C++
1043 lines
34 KiB
C++
/***************************************************************************
|
|
* Copyright (C) 2007 by André Duffeck <duffeck@kde.org> *
|
|
* Copyright (C) 2007 Chani Armitage <chanika@gmail.com> *
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU 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 General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU General Public License *
|
|
* along with this program; if not, write to the *
|
|
* Free Software Foundation, Inc., *
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . *
|
|
***************************************************************************/
|
|
|
|
#include "microblog.h"
|
|
#include "postwidget.h"
|
|
|
|
#include <QFontMetrics>
|
|
#include <QGraphicsView>
|
|
#include <QGraphicsLinearLayout>
|
|
#include <QGraphicsAnchorLayout>
|
|
#include <QAction>
|
|
#include <QTimer>
|
|
|
|
#include <KColorScheme>
|
|
#include <KConfigDialog>
|
|
#include <KDebug>
|
|
#include <KIcon>
|
|
#include <KMessageBox>
|
|
#include <KStringHandler>
|
|
#include <KTextBrowser>
|
|
#include <KTextEdit>
|
|
#include <KWallet/Wallet>
|
|
#include <KToolInvocation>
|
|
|
|
#include <Plasma/Label>
|
|
#include <Plasma/Svg>
|
|
#include <Plasma/Theme>
|
|
#include <Plasma/DataEngine>
|
|
#include <Plasma/Service>
|
|
#include <Plasma/FlashingLabel>
|
|
#include <Plasma/IconWidget>
|
|
#include <Plasma/SvgWidget>
|
|
#include <Plasma/TabBar>
|
|
#include <Plasma/TextBrowser>
|
|
#include <Plasma/ToolTipManager>
|
|
#include <Plasma/ScrollWidget>
|
|
#include <Plasma/TextEdit>
|
|
#include <Plasma/Frame>
|
|
#include <Plasma/ServiceJob>
|
|
|
|
|
|
Q_DECLARE_METATYPE(Plasma::DataEngine::Data)
|
|
|
|
MicroBlog::MicroBlog(QObject *parent, const QVariantList &args)
|
|
: Plasma::PopupApplet(parent, args),
|
|
m_graphicsWidget(0),
|
|
m_historySize(0),
|
|
m_historyRefresh(0),
|
|
m_newTweets(0),
|
|
m_includeFriends(false),
|
|
m_lastMode(0),
|
|
m_profileService(0),
|
|
m_lastTweet(0),
|
|
m_wallet(0),
|
|
m_walletWait(None),
|
|
m_colorScheme(0),
|
|
m_showTweetsTimer(0),
|
|
m_getWalletDelayTimer(0)
|
|
{
|
|
setAspectRatioMode(Plasma::IgnoreAspectRatio);
|
|
setHasConfigurationInterface(true);
|
|
setPopupIcon("view-pim-journal");
|
|
setAssociatedApplication("choqok");
|
|
}
|
|
|
|
void MicroBlog::init()
|
|
{
|
|
m_engine = dataEngine("microblog");
|
|
m_flash = new Plasma::FlashingLabel(this);
|
|
m_theme = new Plasma::Svg(this);
|
|
m_theme->setImagePath("widgets/microblog");
|
|
m_theme->setContainsMultipleImages(true);
|
|
configChanged();
|
|
}
|
|
|
|
void MicroBlog::constraintsEvent(Plasma::Constraints constraints)
|
|
{
|
|
//i am an icon?
|
|
if ((constraints|Plasma::SizeConstraint || constraints|Plasma::FormFactorConstraint) &&
|
|
layout() && layout()->itemAt(0) != m_graphicsWidget) {
|
|
paintIcon();
|
|
}
|
|
}
|
|
|
|
void MicroBlog::paintIcon()
|
|
{
|
|
int size = qMin(contentsRect().width(), contentsRect().height());
|
|
if (size < 1) {
|
|
size = KIconLoader::SizeSmall;
|
|
}
|
|
|
|
QPixmap icon(size, size);
|
|
if (m_popupIcon.isNull()) {
|
|
icon = KIconLoader::global()->loadIcon("view-pim-journal", KIconLoader::NoGroup, size);
|
|
} else {
|
|
icon.fill(Qt::transparent);
|
|
}
|
|
|
|
QPainter p(&icon);
|
|
p.setRenderHints(QPainter::SmoothPixmapTransform);
|
|
p.drawPixmap(icon.rect(), m_popupIcon, m_popupIcon.rect());
|
|
//4.3: a notification system for popupapplets would be cool
|
|
if (m_newTweets > 0) {
|
|
QFont font = Plasma::Theme::defaultTheme()->font(Plasma::Theme::DefaultFont);
|
|
QFontMetrics fm(font);
|
|
QRect textRect(fm.boundingRect(QString::number(m_newTweets)));
|
|
int textSize = qMax(textRect.width(), textRect.height());
|
|
textRect.setSize(QSize(textSize, textSize));
|
|
textRect.moveBottomRight(icon.rect().bottomRight());
|
|
|
|
QColor c(Plasma::Theme::defaultTheme()->color(Plasma::Theme::BackgroundColor));
|
|
c.setAlphaF(0.6);
|
|
|
|
p.setBrush(c);
|
|
p.setPen(Qt::NoPen);
|
|
p.setRenderHints(QPainter::Antialiasing);
|
|
p.drawEllipse(textRect);
|
|
|
|
p.setPen(Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor));
|
|
p.drawText(textRect, Qt::AlignCenter, QString::number(m_newTweets));
|
|
}
|
|
p.end();
|
|
|
|
setPopupIcon(icon);
|
|
}
|
|
|
|
void MicroBlog::popupEvent(bool show)
|
|
{
|
|
if (show) {
|
|
m_newTweets = 0;
|
|
paintIcon();
|
|
}
|
|
}
|
|
|
|
void MicroBlog::focusInEvent(QFocusEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
|
|
m_statusEdit->setFocus();
|
|
}
|
|
|
|
QGraphicsWidget *MicroBlog::graphicsWidget()
|
|
{
|
|
if (m_graphicsWidget) {
|
|
return m_graphicsWidget;
|
|
}
|
|
|
|
m_graphicsWidget = new QGraphicsWidget(this);
|
|
m_graphicsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
|
m_colorScheme = new KColorScheme(QPalette::Active, KColorScheme::View, Plasma::Theme::defaultTheme()->colorScheme());
|
|
connect(Plasma::Theme::defaultTheme(), SIGNAL(themeChanged()), this, SLOT(themeChanged()));
|
|
|
|
//ui setup
|
|
m_layout = new QGraphicsLinearLayout( Qt::Vertical, m_graphicsWidget );
|
|
m_layout->setSpacing( 3 );
|
|
|
|
QGraphicsLinearLayout *flashLayout = new QGraphicsLinearLayout( Qt::Horizontal );
|
|
flashLayout->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
|
|
m_flash->setAutohide( true );
|
|
m_flash->setMinimumSize( 0, 20 );
|
|
QFont fnt = Plasma::Theme::defaultTheme()->font(Plasma::Theme::DefaultFont);
|
|
fnt.setBold( true );
|
|
QFontMetrics fm( fnt );
|
|
m_flash->setFont( fnt );
|
|
m_flash->flash( "", 20000 );
|
|
m_flash->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
|
|
|
QGraphicsLinearLayout *titleLayout = new QGraphicsLinearLayout(Qt::Vertical);
|
|
Plasma::SvgWidget *svgTitle = new Plasma::SvgWidget(m_theme, "microblog", this);
|
|
svgTitle->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
|
svgTitle->setPreferredSize(m_theme->elementSize("microblog"));
|
|
titleLayout->addItem(svgTitle);
|
|
|
|
flashLayout->addItem(m_flash);
|
|
flashLayout->addItem(titleLayout);
|
|
|
|
m_layout->addItem(flashLayout);
|
|
|
|
m_headerFrame = new Plasma::Frame(this);
|
|
m_headerLayout = new QGraphicsAnchorLayout(m_headerFrame);
|
|
m_headerLayout->setSpacing( 5 );
|
|
|
|
m_icon = new Plasma::IconWidget(m_headerFrame);
|
|
m_icon->setIcon(KIcon("user-identity"));
|
|
m_icon->setText(m_username);
|
|
m_icon->setTextBackgroundColor(QColor());
|
|
QSizeF iconSize = m_icon->sizeFromIconSize(48);
|
|
m_icon->setMinimumSize( iconSize );
|
|
m_icon->setMaximumSize( iconSize );
|
|
m_headerLayout->addAnchor(m_icon, Qt::AnchorVerticalCenter, m_headerLayout, Qt::AnchorVerticalCenter);
|
|
m_headerLayout->addAnchor(m_icon, Qt::AnchorLeft, m_headerLayout, Qt::AnchorLeft);
|
|
|
|
Plasma::Frame *statusEditFrame = new Plasma::Frame(m_headerFrame);
|
|
|
|
statusEditFrame->setFrameShadow(Plasma::Frame::Sunken);
|
|
QGraphicsLinearLayout *statusEditLayout = new QGraphicsLinearLayout(statusEditFrame);
|
|
m_statusEdit = new Plasma::TextEdit(this);
|
|
m_statusEdit->setPreferredHeight(fm.height() * 4);
|
|
m_statusEdit->setEnabled(!configurationRequired());
|
|
|
|
statusEditLayout->addItem(m_statusEdit);
|
|
|
|
//FIXME: m_statusEdit->setTextColor( m_colorScheme->foreground().color() );
|
|
// seems to have no effect
|
|
QPalette editPal = m_statusEdit->palette();
|
|
editPal.setColor(QPalette::Text, m_colorScheme->foreground().color());
|
|
m_statusEdit->nativeWidget()->setPalette(editPal);
|
|
m_statusEdit->nativeWidget()->installEventFilter(this);
|
|
m_headerLayout->addAnchor(m_icon, Qt::AnchorRight, statusEditFrame, Qt::AnchorLeft);
|
|
m_headerLayout->addAnchors(statusEditFrame, m_headerLayout, Qt::Vertical);
|
|
m_headerLayout->addAnchor(statusEditFrame, Qt::AnchorRight, m_headerLayout, Qt::AnchorRight);
|
|
m_headerLayout->activate();
|
|
m_headerLayout->setMaximumHeight(m_headerLayout->effectiveSizeHint(Qt::PreferredSize).height());
|
|
|
|
m_tabBar = new Plasma::TabBar(this);
|
|
m_tabBar->addTab(i18n("Timeline"));
|
|
m_tabBar->addTab(i18n("Replies"));
|
|
m_tabBar->addTab(i18n("Messages"));
|
|
m_layout->addItem(m_tabBar);
|
|
|
|
m_scrollWidget = new Plasma::ScrollWidget(this);
|
|
m_tweetsWidget = new QGraphicsWidget(m_scrollWidget);
|
|
m_scrollWidget->setWidget(m_tweetsWidget);
|
|
m_tweetsLayout = new QGraphicsLinearLayout(Qt::Vertical, m_tweetsWidget);
|
|
m_tweetsLayout->setSpacing(0);
|
|
m_tweetsLayout->addItem(m_headerFrame);
|
|
|
|
m_layout->addItem(m_scrollWidget);
|
|
|
|
m_graphicsWidget->setPreferredSize(300, 400);
|
|
|
|
if(m_engine->isValid()) {
|
|
connect(m_statusEdit, SIGNAL(textChanged()), this, SLOT(editTextChanged()));
|
|
connect(m_tabBar, SIGNAL(currentChanged(int)), this, SLOT(modeChanged(int)));
|
|
|
|
m_tabBar->nativeWidget()->installEventFilter(this);
|
|
|
|
if (!m_imageQuery.isEmpty()) {
|
|
m_engine->connectSource(m_imageQuery, this);
|
|
}
|
|
}
|
|
else {
|
|
const QString failureMessage = i18n("Failed to load twitter DataEngine");
|
|
QGraphicsWidget *failureWidget = new QGraphicsWidget(this);
|
|
QGraphicsLinearLayout *failureLayout = new QGraphicsLinearLayout(failureWidget);
|
|
Plasma::IconWidget *failureIcon = new Plasma::IconWidget(this);
|
|
Plasma::Label *failureLabel = new Plasma::Label(this);
|
|
|
|
failureLayout->setContentsMargins(0, 0, 0, 0);
|
|
failureIcon->setIcon(KIcon("dialog-error"));
|
|
failureLayout->addItem(failureIcon);
|
|
failureLabel->setText(failureMessage);
|
|
failureLabel->nativeWidget()->setWordWrap(true);
|
|
failureLayout->addItem(failureLabel);
|
|
|
|
Plasma::ToolTipManager::self()->registerWidget(failureIcon);
|
|
Plasma::ToolTipContent data(i18n("Unable to load the widget"), failureMessage, KIcon("dialog-error"));
|
|
Plasma::ToolTipManager::self()->setContent(failureIcon, data);
|
|
|
|
m_tweetsLayout->addItem(failureWidget);
|
|
}
|
|
|
|
return m_graphicsWidget;
|
|
}
|
|
|
|
void MicroBlog::configChanged()
|
|
{
|
|
//config stuff
|
|
KConfigGroup cg = config();
|
|
QString serviceUrl = cg.readEntry("serviceUrl", "https://identi.ca/api/");
|
|
QString username = cg.readEntry("username");
|
|
QString password = KStringHandler::obscure(cg.readEntry("password"));
|
|
int historySize = cg.readEntry("historySize", 6);
|
|
int historyRefresh = cg.readEntry("historyRefresh", 5);
|
|
bool includeFriends = cg.readEntry("includeFriends", true);
|
|
bool changed = false;
|
|
bool reloadRequired = false;
|
|
|
|
if (m_serviceUrl != serviceUrl) {
|
|
m_serviceUrl = serviceUrl;
|
|
changed = true;
|
|
reloadRequired = true;
|
|
}
|
|
|
|
if (username != m_username) {
|
|
m_username = username;
|
|
changed = true;
|
|
reloadRequired = true;
|
|
m_tweetMap.clear();
|
|
m_lastTweet = 0;
|
|
|
|
if (m_graphicsWidget) {
|
|
m_icon->setIcon(KIcon("user-identity"));
|
|
m_icon->setText(m_username);
|
|
}
|
|
}
|
|
|
|
//if (m_walletWait == Read)
|
|
//then the user is a dumbass.
|
|
//we're going to ignore that, which drops the read attempt
|
|
//I hope that doesn't cause trouble.
|
|
if (!m_username.isEmpty() && (changed || m_password.isEmpty())) {
|
|
//a change in name *or* pass means we need to update the wallet
|
|
//if the user doesn't set a password, see if it's already in our wallet
|
|
m_walletWait = m_password.isEmpty() ? Read : Write;
|
|
getWallet();
|
|
reloadRequired = true;
|
|
}
|
|
|
|
if (m_historyRefresh != historyRefresh) {
|
|
changed = true;
|
|
m_historyRefresh = historyRefresh;
|
|
}
|
|
|
|
if (m_includeFriends != includeFriends) {
|
|
changed = true;
|
|
reloadRequired = true;
|
|
m_includeFriends = includeFriends;
|
|
m_tweetMap.clear();
|
|
m_lastTweet = 0;
|
|
}
|
|
|
|
if (m_historySize != historySize) {
|
|
//kDebug() << "m_historysize,historysize: "<<m_historySize << historySize;
|
|
if (m_historySize < historySize) {
|
|
reloadRequired = true;
|
|
} else if (!reloadRequired) {
|
|
showTweets();
|
|
}
|
|
|
|
changed = true;
|
|
m_historySize = historySize;
|
|
}
|
|
|
|
//kDebug() << changed << reloadRequired;
|
|
//set things in motion
|
|
//hook up some sources
|
|
if (!m_imageQuery.isEmpty()) {
|
|
m_engine->disconnectSource(m_imageQuery, this);
|
|
}
|
|
|
|
m_imageQuery = "UserImages:" + m_serviceUrl;
|
|
if (m_graphicsWidget) {
|
|
m_engine->connectSource(m_imageQuery, this);
|
|
}
|
|
|
|
if (m_username.isEmpty()) {
|
|
setAuthRequired(true);
|
|
} else if (m_password.isEmpty()) {
|
|
//kDebug() << "started, password is not in config file, trying wallet";
|
|
m_walletWait = Read;
|
|
getWallet();
|
|
} else { //use config value
|
|
//kDebug() << "password was in config file, using that to get twitter history";
|
|
reloadRequired = true;
|
|
}
|
|
|
|
if (changed) {
|
|
if (m_service) {
|
|
m_service.data()->deleteLater();
|
|
}
|
|
|
|
if (m_profileService) {
|
|
m_profileService->deleteLater();
|
|
m_profileService = 0;
|
|
}
|
|
}
|
|
|
|
if (reloadRequired) {
|
|
m_lastTweet = 0;
|
|
downloadHistory();
|
|
}
|
|
|
|
setAuthRequired(m_username.isEmpty());
|
|
}
|
|
|
|
void MicroBlog::modeChanged(int)
|
|
{
|
|
m_tweetMap.clear();
|
|
m_lastTweet = 0;
|
|
downloadHistory();
|
|
}
|
|
|
|
void MicroBlog::reply(const QString &replyToId, const QString &to)
|
|
{
|
|
m_replyToId = replyToId;
|
|
m_scrollWidget->ensureItemVisible(m_headerFrame);
|
|
m_statusEdit->nativeWidget()->setPlainText(to);
|
|
QTextCursor cursor = m_statusEdit->nativeWidget()->textCursor();
|
|
cursor.movePosition(QTextCursor::End);
|
|
m_statusEdit->nativeWidget()->setTextCursor(cursor);
|
|
m_statusEdit->setFocus();
|
|
}
|
|
|
|
void MicroBlog::forward(const QString &messageId)
|
|
{
|
|
createTimelineService();
|
|
|
|
if (!m_service) {
|
|
return;
|
|
}
|
|
|
|
KConfigGroup cg = m_service.data()->operationDescription("statuses/retweet");
|
|
cg.writeEntry("id", messageId);
|
|
|
|
connect(m_service.data(), SIGNAL(finished(Plasma::ServiceJob*)), this, SLOT(retweetCompleted(Plasma::ServiceJob*)), Qt::UniqueConnection);
|
|
|
|
m_retweetJobs.insert(m_service.data()->startOperationCall(cg));
|
|
setBusy(true);
|
|
}
|
|
|
|
void MicroBlog::favorite(const QString &messageId, const bool isFavorite)
|
|
{
|
|
QString operation;
|
|
if (isFavorite) {
|
|
operation = "favorites/create";
|
|
} else {
|
|
operation = "favorites/destroy";
|
|
}
|
|
|
|
KConfigGroup cg = m_service.data()->operationDescription(operation);
|
|
cg.writeEntry("id", messageId);
|
|
|
|
connect(m_service.data(), SIGNAL(finished(Plasma::ServiceJob*)), this, SLOT(favoriteCompleted(Plasma::ServiceJob*)), Qt::UniqueConnection);
|
|
|
|
m_favoriteJobs.insert(m_service.data()->startOperationCall(cg));
|
|
setBusy(true);
|
|
}
|
|
|
|
void MicroBlog::getWallet()
|
|
{
|
|
//TODO: maybe Plasma in general should handle the wallet
|
|
delete m_wallet;
|
|
m_wallet = 0;
|
|
|
|
QGraphicsView *v = view();
|
|
WId w = 0;
|
|
if (v) {
|
|
w = v->winId();
|
|
}
|
|
|
|
if (!w) {
|
|
//KWallet requires a valid window id to work, wait until we have one
|
|
if (!m_getWalletDelayTimer) {
|
|
m_getWalletDelayTimer = new QTimer(this);
|
|
m_getWalletDelayTimer->setSingleShot(true);
|
|
m_getWalletDelayTimer->setInterval(100);
|
|
connect(m_getWalletDelayTimer, SIGNAL(timeout()), this, SLOT(getWallet()));
|
|
}
|
|
|
|
if (!m_getWalletDelayTimer->isActive()) {
|
|
m_getWalletDelayTimer->start();
|
|
}
|
|
|
|
return;
|
|
} else {
|
|
delete m_getWalletDelayTimer;
|
|
m_getWalletDelayTimer = 0;
|
|
}
|
|
|
|
//kDebug() << "opening wallet";
|
|
m_wallet = KWallet::Wallet::openWallet(KWallet::Wallet::NetworkWallet(),
|
|
w, KWallet::Wallet::Asynchronous);
|
|
|
|
if (m_walletWait == Write) {
|
|
connect(m_wallet, SIGNAL(walletOpened(bool)), SLOT(writeWallet(bool)));
|
|
} else {
|
|
connect(m_wallet, SIGNAL(walletOpened(bool)), SLOT(readWallet(bool)));
|
|
}
|
|
}
|
|
|
|
void MicroBlog::writeWallet(bool success)
|
|
{
|
|
//kDebug() << success;
|
|
if (success &&
|
|
enterWalletFolder(QString::fromLatin1("Plasma-MicroBlog")) &&
|
|
(m_wallet->writePassword(identifier(), m_password) == 0)) {
|
|
//kDebug() << "successfully put password in wallet, removing from config file";
|
|
config().deleteEntry("password");
|
|
emit configNeedsSaving();
|
|
} else {
|
|
//kDebug() << "failed to store password in wallet, putting into config file instead";
|
|
writeConfigPassword();
|
|
}
|
|
m_walletWait = None;
|
|
delete m_wallet;
|
|
m_wallet = 0;
|
|
}
|
|
|
|
void MicroBlog::readWallet(bool success)
|
|
{
|
|
//kDebug() << success;
|
|
QString pwd;
|
|
if (success &&
|
|
enterWalletFolder(QString::fromLatin1("Plasma-MicroBlog")) &&
|
|
(m_wallet->readPassword(identifier(), pwd) == 0)) {
|
|
//kDebug() << "successfully retrieved password from wallet";
|
|
m_password = pwd;
|
|
downloadHistory();
|
|
} else if (m_password.isEmpty()) {
|
|
//FIXME: when out of string freeze, tell the user WHY they need
|
|
// to configure the widget;
|
|
m_password = KStringHandler::obscure(config().readEntry("password"));
|
|
if(m_password.isEmpty()){
|
|
setConfigurationRequired(true, i18n("Your password is required."));
|
|
}else{
|
|
//kDebug() << "reading from config";
|
|
//kDebug() << "failed to read password";
|
|
downloadHistory();
|
|
}
|
|
}
|
|
|
|
m_walletWait = None;
|
|
delete m_wallet;
|
|
m_wallet = 0;
|
|
}
|
|
|
|
bool MicroBlog::enterWalletFolder(const QString &folder)
|
|
{
|
|
//TODO: seems a bit silly to have a function just for this here
|
|
//why doesn't kwallet have this itself?
|
|
m_wallet->createFolder(folder);
|
|
if (! m_wallet->setFolder(folder)) {
|
|
//kDebug() << "failed to open folder" << folder;
|
|
return false;
|
|
}
|
|
|
|
//kDebug() << "wallet now on folder" << folder;
|
|
return true;
|
|
}
|
|
|
|
void MicroBlog::setAuthRequired(bool required)
|
|
{
|
|
setConfigurationRequired(required, i18n("Your account information is incomplete."));
|
|
if (m_graphicsWidget) {
|
|
m_statusEdit->setEnabled(!required);
|
|
}
|
|
}
|
|
|
|
void MicroBlog::writeConfigPassword()
|
|
{
|
|
//kDebug();
|
|
//TODO: don't use "Yes" and "No", but replace with meaningful labels!
|
|
if (KMessageBox::warningYesNo(0, i18n("Failed to access kwallet. Store password in config file instead?"))
|
|
== KMessageBox::Yes) {
|
|
config().writeEntry("password", KStringHandler::obscure(m_password));
|
|
}
|
|
}
|
|
|
|
void MicroBlog::dataUpdated(const QString& source, const Plasma::DataEngine::Data &data)
|
|
{
|
|
//kDebug() << source << data.count() << m_curTimeline;
|
|
if (data.isEmpty()) {
|
|
if (source.startsWith(QLatin1String("Error"))) {
|
|
m_flash->kill(); //FIXME only clear it if it was showing an error msg
|
|
} else {
|
|
//this is a fake update from a new source
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (source == m_curTimeline) {
|
|
m_flash->flash( i18n("Refreshing timeline...") );
|
|
|
|
//add the newbies
|
|
int newCount = 0;
|
|
qulonglong maxId = m_lastTweet;
|
|
foreach (const QString &id, data.keys()) {
|
|
|
|
qulonglong i = id.toULongLong();
|
|
//kDebug() << i << m_lastTweet;
|
|
if (i > m_lastTweet) {
|
|
newCount++;
|
|
}
|
|
|
|
QVariant v = data.value(id);
|
|
//Warning: This function is not available with MSVC 6
|
|
Plasma::DataEngine::Data t = v.value<Plasma::DataEngine::Data>();
|
|
m_tweetMap[i] = t;
|
|
|
|
if (i > maxId) {
|
|
maxId = i;
|
|
}
|
|
}
|
|
//kDebug() << m_lastTweet << maxId << "<-- updated";
|
|
m_lastTweet = maxId;
|
|
m_newTweets = qMin(newCount, m_historySize);
|
|
|
|
if (m_newTweets > 0) {
|
|
m_flash->flash( i18np( "1 new tweet", "%1 new tweets", m_newTweets ), 20*1000 );
|
|
}
|
|
|
|
scheduleShowTweets();
|
|
} else if (source == m_imageQuery) {
|
|
foreach (const QString &user, data.keys()) {
|
|
QPixmap pm = data[user].value<QPixmap>();
|
|
|
|
if (!pm.isNull()) {
|
|
if (!user.compare(m_username, Qt::CaseInsensitive)) {
|
|
QAction *profile = new QAction(QIcon(pm), m_username, this);
|
|
profile->setData(m_username);
|
|
|
|
QSizeF iconSize = m_icon->sizeFromIconSize(48);
|
|
m_icon->setAction(profile);
|
|
m_icon->setMinimumSize( iconSize );
|
|
m_icon->setMaximumSize( iconSize );
|
|
connect(profile, SIGNAL(triggered()), this, SLOT(openProfile()));
|
|
}
|
|
m_pictureMap[user] = pm;
|
|
m_avatarHistory.removeAll(user);
|
|
m_avatarHistory.append(user);
|
|
while (m_avatarHistory.size() > 30) {
|
|
QString oldestUser = m_avatarHistory.first();
|
|
m_avatarHistory.pop_front();
|
|
m_pictureMap.remove(oldestUser);
|
|
}
|
|
//TODO it would be nice to check whether the updated image is actually in use
|
|
scheduleShowTweets();
|
|
}
|
|
}
|
|
} else if (source.startsWith(QLatin1String("Error"))) {
|
|
QString desc = data["description"].toString();
|
|
|
|
if (desc == "Authentication required"){
|
|
setAuthRequired(true);
|
|
}
|
|
|
|
m_flash->flash(desc, 60 * 1000); //I'd really prefer it to stay there. and be red.
|
|
}
|
|
m_graphicsWidget->setPreferredSize(-1, -1);
|
|
emit sizeHintChanged(Qt::PreferredSize);
|
|
}
|
|
|
|
void MicroBlog::themeChanged()
|
|
{
|
|
delete m_colorScheme;
|
|
m_colorScheme = new KColorScheme(QPalette::Active, KColorScheme::View, Plasma::Theme::defaultTheme()->colorScheme());
|
|
showTweets();
|
|
}
|
|
|
|
void MicroBlog::scheduleShowTweets()
|
|
{
|
|
if (!m_showTweetsTimer) {
|
|
m_showTweetsTimer = new QTimer(this);
|
|
m_showTweetsTimer->setInterval(100);
|
|
m_showTweetsTimer->setSingleShot(true);
|
|
connect(m_showTweetsTimer, SIGNAL(timeout()), this, SLOT(showTweets()));
|
|
}
|
|
|
|
m_showTweetsTimer->stop();
|
|
m_showTweetsTimer->start();
|
|
}
|
|
|
|
void MicroBlog::showTweets()
|
|
{
|
|
if (!m_graphicsWidget) {
|
|
return;
|
|
}
|
|
|
|
prepareGeometryChange();
|
|
// Adjust the number of the TweetWidgets if the configuration has changed
|
|
// Add more tweetWidgets if there are not enough
|
|
|
|
if (m_tweetMap.count() > m_historySize) {
|
|
QMap<qulonglong, Plasma::DataEngine::Data>::iterator it = m_tweetMap.begin();
|
|
while (it != m_tweetMap.end() && m_tweetMap.count() > m_historySize) {
|
|
it = m_tweetMap.erase(it);
|
|
}
|
|
}
|
|
|
|
while (m_tweetWidgets.count() < m_tweetMap.count()) {
|
|
PostWidget *postWidget = new PostWidget(m_tweetsWidget);
|
|
connect(postWidget, SIGNAL(reply(QString,QString)), this, SLOT(reply(QString,QString)));
|
|
connect(postWidget, SIGNAL(forward(QString)), this, SLOT(forward(QString)));
|
|
connect(postWidget, SIGNAL(favorite(QString,bool)), this, SLOT(favorite(QString,bool)));
|
|
connect(postWidget, SIGNAL(openProfile(QString)), this, SLOT(openProfile(QString)));
|
|
m_tweetWidgets.append(postWidget);
|
|
}
|
|
|
|
//clear out tweet widgets if there are too many
|
|
while (m_tweetWidgets.count() > m_tweetMap.count()) {
|
|
PostWidget *t = m_tweetWidgets[m_tweetWidgets.size() - 1];
|
|
m_layout->removeItem(t);
|
|
m_tweetWidgets.removeAt(m_tweetWidgets.size() - 1);
|
|
t->deleteLater();
|
|
m_tweetsWidget->resize(m_tweetsWidget->effectiveSizeHint(Qt::PreferredSize));
|
|
}
|
|
|
|
int i = 0;
|
|
QMap<qulonglong, Plasma::DataEngine::Data>::iterator it = m_tweetMap.end();
|
|
|
|
while (it != m_tweetMap.begin()) {
|
|
Plasma::DataEngine::Data &tweetData = *(--it);
|
|
|
|
QString user = tweetData.value("User").toString();
|
|
|
|
if (i == 0) {
|
|
m_popupIcon = m_pictureMap[user];
|
|
}
|
|
|
|
PostWidget *t = m_tweetWidgets[i];
|
|
t->setColorScheme(m_colorScheme);
|
|
t->setData(tweetData);
|
|
t->setPicture(m_pictureMap[user]);
|
|
//you don't want to reply or RT yourself
|
|
t->setActionsShown(user != m_username);
|
|
m_tweetsLayout->addItem(t);
|
|
|
|
++i;
|
|
}
|
|
|
|
//are we complete?
|
|
if (layout() && layout()->itemAt(0) != m_graphicsWidget) {
|
|
paintIcon();
|
|
}
|
|
}
|
|
|
|
void MicroBlog::createConfigurationInterface(KConfigDialog *parent)
|
|
{
|
|
connect(parent, SIGNAL(applyClicked()), this, SLOT(configAccepted()));
|
|
connect(parent, SIGNAL(okClicked()), this, SLOT(configAccepted()));
|
|
|
|
QWidget *configWidget = new QWidget();
|
|
configUi.setupUi(configWidget);
|
|
|
|
configUi.serviceUrlCombo->addItem("https://identi.ca/api/");
|
|
configUi.serviceUrlCombo->addItem("https://api.twitter.com/1/");
|
|
configUi.serviceUrlCombo->setEditText(m_serviceUrl);
|
|
configUi.usernameEdit->setText(m_username);
|
|
configUi.passwordEdit->setText(m_password);
|
|
configUi.historySizeSpinBox->setValue(m_historySize);
|
|
configUi.historySizeSpinBox->setSuffix(ki18np(" message", " messages"));
|
|
configUi.historyRefreshSpinBox->setValue(m_historyRefresh);
|
|
configUi.historyRefreshSpinBox->setSuffix(ki18np(" minute", " minutes"));
|
|
configUi.checkIncludeFriends->setChecked(m_includeFriends);
|
|
|
|
parent->addPage(configWidget, i18n("General"), icon());
|
|
connect(configUi.serviceUrlCombo, SIGNAL(editTextChanged(QString)), parent, SLOT(settingsModified()));
|
|
connect(configUi.usernameEdit, SIGNAL(userTextChanged(QString)), parent, SLOT(settingsModified()));
|
|
connect(configUi.passwordEdit, SIGNAL(userTextChanged(QString)), parent, SLOT(settingsModified()));
|
|
connect(configUi.historySizeSpinBox, SIGNAL(valueChanged(int)), parent, SLOT(settingsModified()));
|
|
connect(configUi.historyRefreshSpinBox, SIGNAL(valueChanged(int)), parent, SLOT(settingsModified()));
|
|
connect(configUi.checkIncludeFriends, SIGNAL(toggled(bool)), parent, SLOT(settingsModified()));
|
|
}
|
|
|
|
void MicroBlog::configAccepted()
|
|
{
|
|
KConfigGroup cg = config();
|
|
//kDebug()<<"Inside configAccepted";
|
|
//kDebug()<<"username: "<<configUi.usernameEdit->text();
|
|
cg.writeEntry("serviceUrl", configUi.serviceUrlCombo->currentText());
|
|
cg.writeEntry("username", configUi.usernameEdit->text());
|
|
cg.writeEntry("historyRefresh", configUi.historyRefreshSpinBox->value());
|
|
cg.writeEntry("includeFriends", configUi.checkIncludeFriends->isChecked());
|
|
cg.writeEntry("historySize", configUi.historySizeSpinBox->value());
|
|
|
|
QString password = configUi.passwordEdit->text();
|
|
if (m_password != password) {
|
|
m_password = password;
|
|
m_walletWait = Write;
|
|
getWallet();
|
|
|
|
if (m_service) {
|
|
m_service.data()->deleteLater();
|
|
}
|
|
|
|
if (m_profileService) {
|
|
m_profileService->deleteLater();
|
|
m_profileService = 0;
|
|
}
|
|
}
|
|
|
|
emit configNeedsSaving();
|
|
}
|
|
|
|
MicroBlog::~MicroBlog()
|
|
{
|
|
delete m_colorScheme;
|
|
delete m_service.data();
|
|
delete m_profileService;
|
|
}
|
|
|
|
void MicroBlog::editTextChanged()
|
|
{
|
|
m_flash->flash(i18np("%1 character left", "%1 characters left", 140 - m_statusEdit->nativeWidget()->toPlainText().length()), 2000);
|
|
//if the text has been cleared, discard
|
|
if (m_statusEdit->nativeWidget()->toPlainText().length() == 0) {
|
|
m_replyToId = QString();
|
|
}
|
|
}
|
|
|
|
bool MicroBlog::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
if (obj == m_statusEdit->nativeWidget()) {
|
|
//FIXME:it's nevessary this eventfilter to intercept keypresses in
|
|
// QTextEdit (or KTextedit) is it intended?
|
|
if (event->type() == QEvent::KeyPress) {
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
|
|
|
|
//use control modifiers to allow multiline input (even if twitter seems to flatten everything to a slingle line)
|
|
if (!(keyEvent->modifiers() & Qt::ControlModifier) &&
|
|
(keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return)) {
|
|
updateStatus();
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
} else if (obj == m_tabBar->nativeWidget() && event->type() == QEvent::MouseButtonPress) {
|
|
m_scrollWidget->ensureItemVisible(m_headerFrame);
|
|
m_statusEdit->setFocus();
|
|
return false;
|
|
} else {
|
|
return Plasma::Applet::eventFilter(obj, event);
|
|
}
|
|
}
|
|
|
|
void MicroBlog::updateStatus()
|
|
{
|
|
createTimelineService();
|
|
|
|
if (!m_service) {
|
|
return;
|
|
}
|
|
|
|
QString status = m_statusEdit->nativeWidget()->toPlainText();
|
|
|
|
KConfigGroup cg = m_service.data()->operationDescription("update");
|
|
cg.writeEntry("status", status);
|
|
if (!m_replyToId.isEmpty()) {
|
|
cg.writeEntry("in_reply_to_status_id", m_replyToId);
|
|
}
|
|
|
|
connect(m_service.data(), SIGNAL(finished(Plasma::ServiceJob*)), this, SLOT(updateCompleted(Plasma::ServiceJob*)), Qt::UniqueConnection);
|
|
|
|
m_updateJobs.insert(m_service.data()->startOperationCall(cg));
|
|
m_statusEdit->nativeWidget()->setPlainText("");
|
|
setBusy(true);
|
|
}
|
|
|
|
void MicroBlog::updateCompleted(Plasma::ServiceJob *job)
|
|
{
|
|
if (!m_updateJobs.contains(job)) {
|
|
return;
|
|
}
|
|
|
|
m_updateJobs.remove(job);
|
|
if (m_updateJobs.isEmpty()) {
|
|
disconnect(m_service.data(), SIGNAL(finished(Plasma::ServiceJob*)), this, SLOT(updateCompleted(Plasma::ServiceJob*)));
|
|
}
|
|
|
|
if (!job->error()) {
|
|
//m_statusUpdates.value(job);
|
|
downloadHistory();
|
|
}
|
|
|
|
//m_statusUpdates.remove(job);
|
|
setBusy(false);
|
|
}
|
|
|
|
void MicroBlog::retweetCompleted(Plasma::ServiceJob *job)
|
|
{
|
|
if (!m_retweetJobs.contains(job)) {
|
|
return;
|
|
}
|
|
|
|
m_retweetJobs.remove(job);
|
|
if (m_retweetJobs.isEmpty()) {
|
|
disconnect(m_service.data(), SIGNAL(finished(Plasma::ServiceJob*)), this, SLOT(retweetCompleted(Plasma::ServiceJob*)));
|
|
}
|
|
|
|
if (!job->error()) {
|
|
//m_statusUpdates.value(job);
|
|
downloadHistory();
|
|
m_flash->flash(i18nc("Repeat of the post also called retweet", "Repeat completed"));
|
|
} else {
|
|
m_flash->flash(i18n("Repeat failed"));
|
|
}
|
|
|
|
setBusy(false);
|
|
}
|
|
|
|
void MicroBlog::favoriteCompleted(Plasma::ServiceJob *job)
|
|
{
|
|
if (!m_favoriteJobs.contains(job)) {
|
|
return;
|
|
}
|
|
|
|
m_favoriteJobs.remove(job);
|
|
if (m_favoriteJobs.isEmpty()) {
|
|
disconnect(m_service.data(), SIGNAL(finished(Plasma::ServiceJob*)), this, SLOT(favoriteCompleted(Plasma::ServiceJob*)));
|
|
}
|
|
|
|
if (!job->error()) {
|
|
//m_statusUpdates.value(job);
|
|
downloadHistory();
|
|
}
|
|
|
|
setBusy(false);
|
|
}
|
|
|
|
//what this really means now is 'reconnect to the timeline source'
|
|
void MicroBlog::downloadHistory()
|
|
{
|
|
// kDebug() << "Inside downloadhistory";
|
|
if (m_username.isEmpty() || m_password.isEmpty()) {
|
|
//kDebug() << "BOOHYA got empty password";
|
|
if (!m_curTimeline.isEmpty()) {
|
|
m_engine->disconnectSource(m_curTimeline, this);
|
|
m_engine->disconnectSource("Error:" + m_curTimeline, this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_flash->flash(i18n("Refreshing timeline..."), -1);
|
|
|
|
createTimelineService();
|
|
if (m_service) {
|
|
KConfigGroup cg = m_service.data()->operationDescription("auth");
|
|
cg.writeEntry("password", m_password);
|
|
|
|
bool ok = m_service.data()->startOperationCall(cg);
|
|
kDebug() << "operation OK" << ok;
|
|
}
|
|
|
|
//get the profile to retrieve the user icon
|
|
|
|
if (m_profileService) {
|
|
KConfigGroup cg = m_profileService->operationDescription("refresh");
|
|
m_profileService->startOperationCall(cg);
|
|
} else {
|
|
QString profileQuery(QString("Profile:%1@%2").arg(m_username, m_serviceUrl));
|
|
m_engine->connectSource(m_imageQuery, this);
|
|
m_engine->connectSource(profileQuery, this, m_historyRefresh * 60 * 1000);
|
|
m_profileService = m_engine->serviceForSource(profileQuery);
|
|
connect(m_profileService, SIGNAL(finished(Plasma::ServiceJob*)), this, SLOT(serviceFinished(Plasma::ServiceJob*)));
|
|
KConfigGroup profileConf = m_profileService->operationDescription("auth");
|
|
profileConf.writeEntry("password", m_password);
|
|
m_profileService->startOperationCall(profileConf);
|
|
}
|
|
}
|
|
|
|
void MicroBlog::createTimelineService()
|
|
{
|
|
if (!m_tabBar || (m_service && m_lastMode == m_tabBar->currentIndex())) {
|
|
return;
|
|
}
|
|
|
|
delete m_service.data();
|
|
m_lastMode = m_tabBar->currentIndex();
|
|
|
|
QString query;
|
|
switch (m_tabBar->currentIndex()) {
|
|
case 2:
|
|
query = "Messages:%1@%2";
|
|
break;
|
|
case 1:
|
|
query = "Replies:%1@%2";
|
|
break;
|
|
default:
|
|
if (m_includeFriends) {
|
|
query = QString("TimelineWithFriends:%1@%2");
|
|
} else {
|
|
query = QString("Timeline:%1@%2");
|
|
}
|
|
break;
|
|
}
|
|
|
|
query = query.arg(m_username, m_serviceUrl);
|
|
//kDebug() << m_curTimeline << query;
|
|
if (m_curTimeline != query) {
|
|
//ditch the old one, if needed
|
|
if (!m_curTimeline.isEmpty()) {
|
|
m_engine->disconnectSource(m_curTimeline, this);
|
|
m_engine->disconnectSource("Error:" + m_curTimeline, this);
|
|
}
|
|
|
|
m_curTimeline = query;
|
|
}
|
|
|
|
//kDebug() << "Connecting to source " << query << "with refresh rate" << m_historyRefresh * 60 * 1000;
|
|
m_engine->connectSource(query, this, m_historyRefresh * 60 * 1000);
|
|
m_engine->connectSource("Error:" + query, this);
|
|
m_service = m_engine->serviceForSource(m_curTimeline);
|
|
connect(m_service.data(), SIGNAL(finished(Plasma::ServiceJob*)), this, SLOT(serviceFinished(Plasma::ServiceJob*)));
|
|
}
|
|
|
|
void MicroBlog::serviceFinished(Plasma::ServiceJob *job)
|
|
{
|
|
if (job->error()) {
|
|
m_flash->flash(job->errorString(), 2000);
|
|
kDebug() << "Job failed.";
|
|
|
|
// reset the service objects to give it a chance
|
|
// to re-authenticate
|
|
if (m_service) {
|
|
m_service.data()->deleteLater();
|
|
}
|
|
|
|
if (m_profileService) {
|
|
m_profileService->deleteLater();
|
|
m_profileService = 0;
|
|
}
|
|
} else {
|
|
kDebug() << "Job succeeded.";
|
|
|
|
}
|
|
}
|
|
|
|
void MicroBlog::openProfile(const QString &profile)
|
|
{
|
|
QString url = m_serviceUrl;
|
|
url.remove("api/");
|
|
if (!profile.isEmpty()) {
|
|
KToolInvocation::invokeBrowser(KUrl(KUrl(url), profile).prettyUrl());
|
|
} else {
|
|
KToolInvocation::invokeBrowser(KUrl(KUrl(url), m_username).prettyUrl());
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#include "moc_microblog.cpp"
|