diff --git a/global-presence-chooser.cpp b/global-presence-chooser.cpp index 30bb84c..cc04347 100644 --- a/global-presence-chooser.cpp +++ b/global-presence-chooser.cpp @@ -1,447 +1,447 @@ /* * Global Presence - A Drop down menu for selecting presence * * Copyright (C) 2011 David Edmundson * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "global-presence-chooser.h" #include "dialogs/custom-presence-dialog.h" #include "dialogs/advanced-presence-dialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern const QString KDED_STATUS_MESSAGE_PARSER_WHATSTHIS( i18n("

Tokens can be used wherever a status message can be set to create a dynamic status message.

") + i18n("

%tr+<val>: Countdown to 0 from <val> minutes. e.g. %tr+30

") + i18n("

%time+[<val>]: The current local time, or if a value is specified, the local time <val> minutes in the future. e.g. %time+10

") + i18n("

%utc+[<val>]: The current UTC time, or if a value is specified, the UTC time <val> minutes into the future. e.g. %utc

") + i18n("

%te+[<val>]: Time elapsed from message activation. Append an initial elapsed time "<val>" in minutes. e.g. %te+5

") + i18n("

%title: Now Playing track title.

") + i18n("

%artist: Now Playing track or album artist.

") + i18n("

%album: Now Playing album.

") + i18n("

%track: Now Playing track number.

") + i18n("

%um+[<val>]: When specified globally or in an account presence status message, overrides all automatic presence messages. When specified in an automatic presence status message, is substituted for the global or account presence status message (if specified). When val = g in an account presence status message or an automatic presence status message, overrides the account presence status message or automatic presence status message with the global presence status message. e.g. %um, %um+g

") + i18n("

%tu+<val>: Refresh the status message every <val> minutes. e.g. %tu+2

") + i18n("

%tx+<val>: Expire the status message after <val> minutes, or when the Now Playing active player stops (val = np). e.g. %tx+20, %tx+np

") + i18n("

%xm+"<val>": Specify a message to follow %tr, %time, %utc, and %tx token expiry. e.g. %xm+"Back %time. %tx+3 %xm+"Running late""

") + i18n("

%tf+"<val>": Specify the format for local time using QDateTime::toString() expressions. e.g. %tf+"h:mm AP t"

") + i18n("

%uf+"<val>": Specify the format for UTC time using QDateTime::toString() expressions. e.g. %uf+"hh:mm t"

") + i18n("

%sp+"<val>": Change the separator for empty fields. e.g. %sp+"-"

") + i18n("

Using tokens requires the Telepathy KDED module to be loaded. Tokens can be escaped by prepending a backslash character, e.g. \%sp

") ); //A sneaky class that adds an extra entries to the end of the presence model. class PresenceModelExtended : public QAbstractListModel { Q_OBJECT public: PresenceModelExtended(KTp::PresenceModel *presenceModel, QObject *parent); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; KTp::Presence temporaryPresence() const; /** Adds a presence to the model which is to be used when the presence has been set externally and we need to show it, but not save it to the config*/ QModelIndex addTemporaryPresence(const KTp::Presence &presence); void removeTemporaryPresence(); private slots: void sourceRowsInserted(const QModelIndex &index, int start, int end); void sourceRowsRemoved(const QModelIndex &index, int start, int end); private: KTp::Presence m_temporaryPresence; KTp::PresenceModel *m_model; }; PresenceModelExtended::PresenceModelExtended(KTp::PresenceModel *presenceModel, QObject *parent) : QAbstractListModel(parent), m_model(presenceModel) { connect(m_model, &QAbstractItemModel::rowsInserted, this, &PresenceModelExtended::sourceRowsInserted); connect(m_model, &QAbstractItemModel::rowsRemoved, this, &PresenceModelExtended::sourceRowsRemoved); } //return number of rows + the extra items added to end of list int PresenceModelExtended::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } int rowCount = m_model->rowCount(parent) + 2; if (m_temporaryPresence.isValid()) { rowCount++; } return rowCount; } QVariant PresenceModelExtended::data(const QModelIndex &index, int role) const { if (role == Qt::SizeHintRole) { const QFontMetrics fontMetrics(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); return QSize(0, qMax(fontMetrics.height(), (int)(KIconLoader::SizeSmall)) + 8); } if (index.row() == rowCount() - 1) { switch (role) { case Qt::DisplayRole: return i18n("Configure Custom Presences..."); case Qt::DecorationRole: return QIcon::fromTheme("configure"); } } else if (index.row() == rowCount() - 2) { switch (role) { case Qt::DisplayRole: return i18n("Advanced Presence Setting..."); case Qt::DecorationRole: return QIcon::fromTheme("configure"); } } else if (m_temporaryPresence.isValid() && index.row() == rowCount() - 3) { switch (role) { case Qt::DisplayRole: return m_temporaryPresence.statusMessage(); case Qt::DecorationRole: return m_temporaryPresence.icon(); case KTp::PresenceModel::PresenceRole: return QVariant::fromValue(m_temporaryPresence); } } else { return m_model->data(m_model->index(index.row()), role); } return QVariant(); } KTp::Presence PresenceModelExtended::temporaryPresence() const { return m_temporaryPresence; } void PresenceModelExtended::sourceRowsInserted(const QModelIndex &index, int start, int end) { beginInsertRows(createIndex(index.row(), 0), start, end); endInsertRows(); } void PresenceModelExtended::sourceRowsRemoved(const QModelIndex &index, int start, int end) { beginRemoveRows(createIndex(index.row(), 0), start, end); endRemoveRows(); } QModelIndex PresenceModelExtended::addTemporaryPresence(const KTp::Presence &presence) { int row = m_model->rowCount(); //if the temp presence already exists, don't remove and readd it //but simply replace it if (m_temporaryPresence.isValid()) { m_temporaryPresence = presence; emit dataChanged(this->createIndex(row, 0), this->createIndex(row, 0)); } else { beginInsertRows(QModelIndex(), row, row); m_temporaryPresence = presence; endInsertRows(); } return this->createIndex(row, 0); } void PresenceModelExtended::removeTemporaryPresence() { if (!m_temporaryPresence.isValid()) { return; //if not already set, do nothing. } int row = m_model->rowCount(); beginRemoveRows(QModelIndex(), row, row); m_temporaryPresence = KTp::Presence(); endRemoveRows(); } //---------------------------------------------------------------------------------------------------------- GlobalPresenceChooser::GlobalPresenceChooser(QWidget *parent) : KComboBox(parent), m_globalPresence(new KTp::GlobalPresence(this)), m_model(new KTp::PresenceModel(this)), m_modelExtended(new PresenceModelExtended(m_model, this)), m_busyOverlay(new KPixmapSequenceOverlayPainter(this)), m_changePresenceMessageButton(new QPushButton(this)) { this->setModel(m_modelExtended); m_busyOverlay->setSequence(KIconLoader::global()->loadPixmapSequence("process-working", KIconLoader::SizeSmallMedium)); setEditable(false); m_changePresenceMessageButton->setIcon(QIcon::fromTheme("document-edit")); m_changePresenceMessageButton->setFlat(true); m_changePresenceMessageButton->setToolTip(i18n("Click to change your presence message")); connect(this, static_cast(&KComboBox::currentIndexChanged), &GlobalPresenceChooser::onAllComboChanges); connect(this, static_cast(&KComboBox::activated), &GlobalPresenceChooser::onUserActivatedComboChange); connect(m_globalPresence, &KTp::GlobalPresence::currentPresenceChanged, this, &GlobalPresenceChooser::onPresenceChanged); connect(m_globalPresence, &KTp::GlobalPresence::connectionStatusChanged, this, &GlobalPresenceChooser::onConnectionStatusChanged); connect(m_changePresenceMessageButton, &QPushButton::clicked, this, &GlobalPresenceChooser::onChangePresenceMessageClicked); onPresenceChanged(m_globalPresence->currentPresence()); //we need to check if there is some account connecting and if so, spin the spinner onConnectionStatusChanged(m_globalPresence->connectionStatus()); } bool GlobalPresenceChooser::event(QEvent *e) { if (e->type() == QEvent::ToolTip) { QHelpEvent *helpEvent = static_cast(e); QString toolTipText; if (isEditable()) { toolTipText = KDED_STATUS_MESSAGE_PARSER_WHATSTHIS; } else { if (m_globalPresence->accountManager().isNull()) { return false; } toolTipText.append(""); for (const Tp::AccountPtr &account : m_globalPresence->accountManager()->enabledAccounts()->accounts()) { KTp::Presence accountPresence(account->currentPresence()); QString presenceIconPath = KIconLoader::global()->iconPath(accountPresence.icon().name(), 1); QString presenceIconString = QString::fromLatin1("").arg(presenceIconPath); QString accountIconPath = KIconLoader::global()->iconPath(account->iconName(), 1); QString accountIconString = QString::fromLatin1("").arg(accountIconPath).arg(KIconLoader::SizeSmallMedium); QString presenceString; if (account->connectionStatus() == Tp::ConnectionStatusConnecting) { presenceString = i18nc("Presence string when the account is connecting", "Connecting..."); } else if (!account->currentPresence().statusMessage().isEmpty()){ presenceString = QString::fromLatin1("(%1) ").arg(accountPresence.displayString()) + accountPresence.statusMessage(); } else { presenceString = accountPresence.displayString(); } toolTipText.append(QString::fromLatin1("").arg(accountIconString, account->displayName(), presenceIconString, presenceString)); } toolTipText.append("
%1 %2
%3 %4
"); } QToolTip::showText(helpEvent->globalPos(), toolTipText, this); return true; } if (e->type() == QEvent::Resize) { repositionOverlays(); } if (e->type() == QEvent::ContextMenu) { if (isEditable()) { m_lineEditContextMenu = lineEdit()->createStandardContextMenu(); return true; } } if (e->type() == QEvent::KeyPress) { QKeyEvent *ke = static_cast(e); if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) { if (isEditable()) { onConfirmPresenceMessageClicked(); return true; } } if (ke->key() == Qt::Key_Escape) { if (isEditable()) { setEditable(false); m_changePresenceMessageButton->show(); } } if (ke->key() == Qt::Key_Down || ke->key() == Qt::Key_Up) { if (!isEditable()) { showPopup(); return true; } } } if (e->type() == QEvent::FocusOut) { //just cancel editable and let it exec parent event() if (!m_lineEditContextMenu.isNull()) { if (!m_lineEditContextMenu.data()->isHidden()) { //if we're showing the context menu, do not process this event further return true; } //...otherwise delete the menu and hide the lineedit m_lineEditContextMenu.data()->deleteLater(); } if (isEditable()) { setEditable(false); m_changePresenceMessageButton->show(); } } return KComboBox::event(e); // krazy:exclude=qclasses } void GlobalPresenceChooser::setEditable(bool editable) { if (editable) { m_busyOverlay->setWidget(0); } else { m_busyOverlay->setWidget(this); if (m_globalPresence->connectionStatus() == KTp::GlobalPresence::Connecting) { m_busyOverlay->start(); // If telepathy is still connecting, overlay must be spinning again. } } KComboBox::setEditable(editable); } void GlobalPresenceChooser::onUserActivatedComboChange(int index) { - if ((index == -1) || (index == count() - 3)) { + if (index == -1) { return; } if (index == count() - 2) { QPointer dialog = new AdvancedPresenceDialog(m_model, m_globalPresence, this); dialog.data()->exec(); delete dialog.data(); } else if (index == count() - 1) { QPointer dialog = new CustomPresenceDialog(m_model, this); dialog.data()->exec(); delete dialog.data(); } else { KTp::Presence presence = itemData(index, KTp::PresenceModel::PresenceRole).value(); m_globalPresence->setPresence(presence); } onPresenceChanged(m_globalPresence->currentPresence()); } void GlobalPresenceChooser::onAllComboChanges(int index) { int lastPresenceIndex = m_model->rowCount(); if (index < lastPresenceIndex) { KTp::Presence presence = itemData(index, KTp::PresenceModel::PresenceRole).value(); if ((presence.type() == Tp::ConnectionPresenceTypeOffline) || (presence.type() == Tp::ConnectionPresenceTypeHidden)) { m_changePresenceMessageButton->hide(); } else { m_changePresenceMessageButton->show(); } } clearFocus(); } void GlobalPresenceChooser::onPresenceChanged(const KTp::Presence &presence) { if (presence.type() == Tp::ConnectionPresenceTypeUnset) { setCurrentIndex(-1); m_busyOverlay->start(); return; } const QModelIndexList &matchIndexList = m_model->match(m_model->index(0, 0), KTp::PresenceModel::PresenceRole, QVariant::fromValue(presence)); if (!matchIndexList.isEmpty()) { m_modelExtended->removeTemporaryPresence(); setCurrentIndex(matchIndexList.at(0).row()); } else { const QModelIndex &index = m_modelExtended->addTemporaryPresence(presence); setCurrentIndex(index.row()); } m_busyOverlay->stop(); } void GlobalPresenceChooser::onConnectionStatusChanged(KTp::GlobalPresence::ConnectionStatus connectionStatus) { if (connectionStatus == KTp::GlobalPresence::Connecting) { repositionOverlays(); m_busyOverlay->start(); } else { m_busyOverlay->stop(); } } void GlobalPresenceChooser::repositionOverlays() { //set 2px margins so that the button is not bigger than the combo m_changePresenceMessageButton->setMaximumHeight(height() - 2); m_changePresenceMessageButton->setMaximumWidth(height() - 2); QPoint topLeft; if (m_changePresenceMessageButton->layoutDirection() == Qt::RightToLeft) { //move the button 22px from the left edge m_changePresenceMessageButton->move(22, 0); //place the spinner 2px right from the button, 4 is added to take the margin. topLeft.setX(m_changePresenceMessageButton->pos().x() + m_busyOverlay->sequence().frameSize().width() + 4); topLeft.setY((height() - m_busyOverlay->sequence().frameSize().height()) / 2); } else { //move the button 22px from the right edge m_changePresenceMessageButton->move(width() - m_changePresenceMessageButton->width() - 22, 0); //place the spinner 2px left from the button topLeft.setX(m_changePresenceMessageButton->pos().x() - m_busyOverlay->sequence().frameSize().width() - 2); topLeft.setY((height() - m_busyOverlay->sequence().frameSize().height()) / 2); } m_busyOverlay->setRect(QRect(topLeft, m_busyOverlay->sequence().frameSize())); } void GlobalPresenceChooser::onChangePresenceMessageClicked() { m_changePresenceMessageButton->hide(); setEditable(true); //if current presence has no presence message, delete the text if (m_globalPresence->globalPresence().statusMessage().isEmpty()) { lineEdit()->clear(); } else { lineEdit()->setText(m_globalPresence->globalPresence().statusMessage()); } lineEdit()->setFocus(); } void GlobalPresenceChooser::onConfirmPresenceMessageClicked() { m_changePresenceMessageButton->show(); KTp::Presence presence = itemData(currentIndex(), KTp::PresenceModel::PresenceRole).value(); presence.setStatusMessage(lineEdit()->text()); setEditable(false); m_globalPresence->setPresence(presence); } #include "global-presence-chooser.moc" #include "moc_global-presence-chooser.cpp" //hack because we have two QObjects in the same file