kde-extraapps/kdeplasma-addons/applets/microblog/microblog.cpp
2015-02-27 11:02:43 +00:00

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"