Warning, file /network/ktp-contact-list/global-presence-chooser.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * Global Presence - A Drop down menu for selecting presence 0003 * 0004 * Copyright (C) 2011 David Edmundson <kde@davidedmundson.co.uk> 0005 * 0006 * This library is free software; you can redistribute it and/or 0007 * modify it under the terms of the GNU Lesser General Public 0008 * License as published by the Free Software Foundation; either 0009 * version 2.1 of the License, or (at your option) any later version. 0010 * 0011 * This library is distributed in the hope that it will be useful, 0012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0014 * Lesser General Public License for more details. 0015 * 0016 * You should have received a copy of the GNU Lesser General Public 0017 * License along with this library; if not, write to the Free Software 0018 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 0019 */ 0020 0021 #include "global-presence-chooser.h" 0022 0023 #include "dialogs/custom-presence-dialog.h" 0024 #include "dialogs/advanced-presence-dialog.h" 0025 0026 #include <KTp/presence.h> 0027 #include <KTp/global-presence.h> 0028 #include <KTp/Models/presence-model.h> 0029 0030 #include <KLocalizedString> 0031 #include <KSharedConfig> 0032 #include <KLineEdit> 0033 #include <KPixmapSequence> 0034 #include <KPixmapSequenceOverlayPainter> 0035 #include <KMessageBox> 0036 #include <KIconLoader> 0037 0038 #include <TelepathyQt/Account> 0039 0040 #include <QFontDatabase> 0041 #include <QMouseEvent> 0042 #include <QToolTip> 0043 #include <QStyle> 0044 #include <QPushButton> 0045 #include <QMenu> 0046 #include <QPointer> 0047 0048 extern const QString KDED_STATUS_MESSAGE_PARSER_WHATSTHIS( 0049 i18n("<p>Tokens can be used wherever a status message can be set to create a dynamic status message.</p>") 0050 + i18n("<p><strong>%tr+<val></strong>: Countdown to 0 from <strong><val></strong> minutes. e.g. %tr+30</p>") 0051 + i18n("<p><strong>%time+[<val>]</strong>: The current local time, or if a value is specified, the local time <strong><val></strong> minutes in the future. e.g. %time+10</p>") 0052 + i18n("<p><strong>%utc+[<val>]</strong>: The current UTC time, or if a value is specified, the UTC time <strong><val></strong> minutes into the future. e.g. %utc</p>") 0053 + i18n("<p><strong>%te+[<val>]</strong>: Time elapsed from message activation. Append an initial elapsed time "<val>" in minutes. e.g. %te+5</p>") 0054 + i18n("<p><strong>%title</strong>: Now Playing track title.</p>") 0055 + i18n("<p><strong>%artist</strong>: Now Playing track or album artist.</p>") 0056 + i18n("<p><strong>%album</strong>: Now Playing album.</p>") 0057 + i18n("<p><strong>%track</strong>: Now Playing track number.</p>") 0058 + i18n("<p><strong>%um+[<val>]</strong>: 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 <strong>val = g</strong> 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</p>") 0059 + i18n("<p><strong>%tu+<val></strong>: Refresh the status message every <strong><val></strong> minutes. e.g. %tu+2</p>") 0060 + i18n("<p><strong>%tx+<val></strong>: Expire the status message after <strong><val></strong> minutes, or when the Now Playing active player stops (<strong>val = np</strong>). e.g. %tx+20, %tx+np</p>") 0061 + i18n("<p><strong>%xm+"<val>"</strong>: Specify a message to follow %tr, %time, %utc, and %tx token expiry. e.g. %xm+"Back %time. %tx+3 %xm+"Running late""</p>") 0062 + i18n("<p><strong>%tf+"<val>"</strong>: Specify the format for local time using QDateTime::toString() expressions. e.g. %tf+"h:mm AP t"</p>") 0063 + i18n("<p><strong>%uf+"<val>"</strong>: Specify the format for UTC time using QDateTime::toString() expressions. e.g. %uf+"hh:mm t"</p>") 0064 + i18n("<p><strong>%sp+"<val>"</strong>: Change the separator for empty fields. e.g. %sp+"-"</p>") 0065 + i18n("<p>Using tokens requires the Telepathy KDED module to be loaded. Tokens can be escaped by prepending a backslash character, e.g. \%sp</p>") 0066 ); 0067 0068 //A sneaky class that adds an extra entries to the end of the presence model. 0069 class PresenceModelExtended : public QAbstractListModel 0070 { 0071 Q_OBJECT 0072 public: 0073 PresenceModelExtended(KTp::PresenceModel *presenceModel, QObject *parent); 0074 int rowCount(const QModelIndex &parent = QModelIndex()) const; 0075 QVariant data(const QModelIndex &index, int role) const; 0076 KTp::Presence temporaryPresence() const; 0077 /** 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*/ 0078 QModelIndex addTemporaryPresence(const KTp::Presence &presence); 0079 void removeTemporaryPresence(); 0080 private slots: 0081 void sourceRowsInserted(const QModelIndex &index, int start, int end); 0082 void sourceRowsRemoved(const QModelIndex &index, int start, int end); 0083 private: 0084 KTp::Presence m_temporaryPresence; 0085 KTp::PresenceModel *m_model; 0086 }; 0087 0088 PresenceModelExtended::PresenceModelExtended(KTp::PresenceModel *presenceModel, QObject *parent) : 0089 QAbstractListModel(parent), 0090 m_model(presenceModel) 0091 { 0092 connect(m_model, &QAbstractItemModel::rowsInserted, this, &PresenceModelExtended::sourceRowsInserted); 0093 connect(m_model, &QAbstractItemModel::rowsRemoved, this, &PresenceModelExtended::sourceRowsRemoved); 0094 } 0095 0096 //return number of rows + the extra items added to end of list 0097 int PresenceModelExtended::rowCount(const QModelIndex &parent) const 0098 { 0099 if (parent.isValid()) { 0100 return 0; 0101 } 0102 int rowCount = m_model->rowCount(parent) + 2; 0103 if (m_temporaryPresence.isValid()) { 0104 rowCount++; 0105 } 0106 return rowCount; 0107 } 0108 0109 QVariant PresenceModelExtended::data(const QModelIndex &index, int role) const 0110 { 0111 if (role == Qt::SizeHintRole) { 0112 const QFontMetrics fontMetrics(QFontDatabase::systemFont(QFontDatabase::GeneralFont)); 0113 return QSize(0, qMax(fontMetrics.height(), (int)(KIconLoader::SizeSmall)) + 8); 0114 } 0115 if (index.row() == rowCount() - 1) { 0116 switch (role) { 0117 case Qt::DisplayRole: 0118 return i18n("Configure Custom Presences..."); 0119 case Qt::DecorationRole: 0120 return QIcon::fromTheme("configure"); 0121 } 0122 } else if (index.row() == rowCount() - 2) { 0123 switch (role) { 0124 case Qt::DisplayRole: 0125 return i18n("Advanced Presence Setting..."); 0126 case Qt::DecorationRole: 0127 return QIcon::fromTheme("configure"); 0128 } 0129 } else if (m_temporaryPresence.isValid() && index.row() == rowCount() - 3) { 0130 switch (role) { 0131 case Qt::DisplayRole: 0132 return m_temporaryPresence.statusMessage(); 0133 case Qt::DecorationRole: 0134 return m_temporaryPresence.icon(); 0135 case KTp::PresenceModel::PresenceRole: 0136 return QVariant::fromValue<KTp::Presence>(m_temporaryPresence); 0137 } 0138 } else { 0139 return m_model->data(m_model->index(index.row()), role); 0140 } 0141 return QVariant(); 0142 } 0143 0144 KTp::Presence PresenceModelExtended::temporaryPresence() const 0145 { 0146 return m_temporaryPresence; 0147 } 0148 0149 void PresenceModelExtended::sourceRowsInserted(const QModelIndex &index, int start, int end) 0150 { 0151 beginInsertRows(createIndex(index.row(), 0), start, end); 0152 endInsertRows(); 0153 } 0154 0155 void PresenceModelExtended::sourceRowsRemoved(const QModelIndex &index, int start, int end) 0156 { 0157 beginRemoveRows(createIndex(index.row(), 0), start, end); 0158 endRemoveRows(); 0159 } 0160 0161 QModelIndex PresenceModelExtended::addTemporaryPresence(const KTp::Presence &presence) 0162 { 0163 int row = m_model->rowCount(); 0164 0165 //if the temp presence already exists, don't remove and readd it 0166 //but simply replace it 0167 if (m_temporaryPresence.isValid()) { 0168 m_temporaryPresence = presence; 0169 emit dataChanged(this->createIndex(row, 0), this->createIndex(row, 0)); 0170 } else { 0171 beginInsertRows(QModelIndex(), row, row); 0172 m_temporaryPresence = presence; 0173 endInsertRows(); 0174 } 0175 0176 return this->createIndex(row, 0); 0177 } 0178 0179 void PresenceModelExtended::removeTemporaryPresence() 0180 { 0181 if (!m_temporaryPresence.isValid()) { 0182 return; //if not already set, do nothing. 0183 } 0184 0185 int row = m_model->rowCount(); 0186 beginRemoveRows(QModelIndex(), row, row); 0187 m_temporaryPresence = KTp::Presence(); 0188 endRemoveRows(); 0189 } 0190 0191 //---------------------------------------------------------------------------------------------------------- 0192 0193 GlobalPresenceChooser::GlobalPresenceChooser(QWidget *parent) : 0194 KComboBox(parent), 0195 m_globalPresence(new KTp::GlobalPresence(this)), 0196 m_model(new KTp::PresenceModel(this)), 0197 m_modelExtended(new PresenceModelExtended(m_model, this)), 0198 m_busyOverlay(new KPixmapSequenceOverlayPainter(this)), 0199 m_changePresenceMessageButton(new QPushButton(this)) 0200 { 0201 this->setModel(m_modelExtended); 0202 0203 m_busyOverlay->setSequence(KIconLoader::global()->loadPixmapSequence("process-working", KIconLoader::SizeSmallMedium)); 0204 setEditable(false); 0205 0206 m_changePresenceMessageButton->setIcon(QIcon::fromTheme("document-edit")); 0207 m_changePresenceMessageButton->setFlat(true); 0208 m_changePresenceMessageButton->setToolTip(i18n("Click to change your presence message")); 0209 0210 connect(this, static_cast<void(GlobalPresenceChooser::*)(int)>(&KComboBox::currentIndexChanged), &GlobalPresenceChooser::onAllComboChanges); 0211 connect(this, static_cast<void(GlobalPresenceChooser::*)(int)>(&KComboBox::activated), &GlobalPresenceChooser::onUserActivatedComboChange); 0212 connect(m_globalPresence, &KTp::GlobalPresence::currentPresenceChanged, this, &GlobalPresenceChooser::onPresenceChanged); 0213 connect(m_globalPresence, &KTp::GlobalPresence::connectionStatusChanged, this, &GlobalPresenceChooser::onConnectionStatusChanged); 0214 connect(m_changePresenceMessageButton, &QPushButton::clicked, this, &GlobalPresenceChooser::onChangePresenceMessageClicked); 0215 0216 onPresenceChanged(m_globalPresence->currentPresence()); 0217 //we need to check if there is some account connecting and if so, spin the spinner 0218 onConnectionStatusChanged(m_globalPresence->connectionStatus()); 0219 } 0220 0221 bool GlobalPresenceChooser::event(QEvent *e) 0222 { 0223 if (e->type() == QEvent::ToolTip) { 0224 QHelpEvent *helpEvent = static_cast<QHelpEvent *>(e); 0225 0226 QString toolTipText; 0227 0228 if (isEditable()) { 0229 toolTipText = KDED_STATUS_MESSAGE_PARSER_WHATSTHIS; 0230 } else { 0231 if (m_globalPresence->accountManager().isNull()) { 0232 return false; 0233 } 0234 0235 toolTipText.append("<table>"); 0236 0237 for (const Tp::AccountPtr &account : m_globalPresence->accountManager()->enabledAccounts()->accounts()) { 0238 KTp::Presence accountPresence(account->currentPresence()); 0239 QString presenceIconPath = KIconLoader::global()->iconPath(accountPresence.icon().name(), 1); 0240 QString presenceIconString = QString::fromLatin1("<img src=\"%1\">").arg(presenceIconPath); 0241 QString accountIconPath = KIconLoader::global()->iconPath(account->iconName(), 1); 0242 QString accountIconString = QString::fromLatin1("<img src=\"%1\" width=\"%2\" height=\"%2\">").arg(accountIconPath).arg(KIconLoader::SizeSmallMedium); 0243 QString presenceString; 0244 if (account->connectionStatus() == Tp::ConnectionStatusConnecting) { 0245 presenceString = i18nc("Presence string when the account is connecting", "Connecting..."); 0246 } else if (!account->currentPresence().statusMessage().isEmpty()){ 0247 presenceString = QString::fromLatin1("(%1) ").arg(accountPresence.displayString()) + accountPresence.statusMessage(); 0248 } else { 0249 presenceString = accountPresence.displayString(); 0250 } 0251 toolTipText.append(QString::fromLatin1("<tr><td>%1 %2</td></tr><tr><td style=\"padding-left: 24px\">%3 %4</td></tr>").arg(accountIconString, account->displayName(), presenceIconString, presenceString)); 0252 } 0253 0254 toolTipText.append("</table>"); 0255 } 0256 0257 QToolTip::showText(helpEvent->globalPos(), toolTipText, this); 0258 return true; 0259 } 0260 0261 if (e->type() == QEvent::Resize) { 0262 repositionOverlays(); 0263 } 0264 0265 if (e->type() == QEvent::ContextMenu) { 0266 if (isEditable()) { 0267 m_lineEditContextMenu = lineEdit()->createStandardContextMenu(); 0268 0269 return true; 0270 } 0271 } 0272 0273 if (e->type() == QEvent::KeyPress) { 0274 QKeyEvent *ke = static_cast<QKeyEvent*>(e); 0275 0276 if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) { 0277 if (isEditable()) { 0278 onConfirmPresenceMessageClicked(); 0279 return true; 0280 } 0281 } 0282 if (ke->key() == Qt::Key_Escape) { 0283 if (isEditable()) { 0284 setEditable(false); 0285 m_changePresenceMessageButton->show(); 0286 } 0287 } 0288 if (ke->key() == Qt::Key_Down || ke->key() == Qt::Key_Up) { 0289 if (!isEditable()) { 0290 showPopup(); 0291 return true; 0292 } 0293 } 0294 } 0295 0296 if (e->type() == QEvent::FocusOut) { 0297 //just cancel editable and let it exec parent event() 0298 if (!m_lineEditContextMenu.isNull()) { 0299 if (!m_lineEditContextMenu.data()->isHidden()) { 0300 //if we're showing the context menu, do not process this event further 0301 return true; 0302 } 0303 //...otherwise delete the menu and hide the lineedit 0304 m_lineEditContextMenu.data()->deleteLater(); 0305 } 0306 if (isEditable()) { 0307 setEditable(false); 0308 m_changePresenceMessageButton->show(); 0309 } 0310 } 0311 0312 return KComboBox::event(e); // krazy:exclude=qclasses 0313 } 0314 0315 void GlobalPresenceChooser::setEditable(bool editable) 0316 { 0317 if (editable) { 0318 m_busyOverlay->setWidget(0); 0319 } else { 0320 m_busyOverlay->setWidget(this); 0321 if (m_globalPresence->connectionStatus() == KTp::GlobalPresence::Connecting) { 0322 m_busyOverlay->start(); // If telepathy is still connecting, overlay must be spinning again. 0323 } 0324 } 0325 KComboBox::setEditable(editable); 0326 } 0327 0328 void GlobalPresenceChooser::onUserActivatedComboChange(int index) 0329 { 0330 if ((index == -1) || (index == count() - 3)) { 0331 return; 0332 } 0333 0334 if (index == count() - 2) { 0335 QPointer<AdvancedPresenceDialog> dialog = new AdvancedPresenceDialog(m_model, m_globalPresence, this); 0336 dialog.data()->exec(); 0337 delete dialog.data(); 0338 } else if (index == count() - 1) { 0339 QPointer<CustomPresenceDialog> dialog = new CustomPresenceDialog(m_model, this); 0340 dialog.data()->exec(); 0341 delete dialog.data(); 0342 } else { 0343 KTp::Presence presence = itemData(index, KTp::PresenceModel::PresenceRole).value<KTp::Presence>(); 0344 m_globalPresence->setPresence(presence); 0345 } 0346 0347 onPresenceChanged(m_globalPresence->currentPresence()); 0348 } 0349 0350 void GlobalPresenceChooser::onAllComboChanges(int index) 0351 { 0352 int lastPresenceIndex = m_model->rowCount(); 0353 if (index < lastPresenceIndex) { 0354 KTp::Presence presence = itemData(index, KTp::PresenceModel::PresenceRole).value<KTp::Presence>(); 0355 if ((presence.type() == Tp::ConnectionPresenceTypeOffline) || 0356 (presence.type() == Tp::ConnectionPresenceTypeHidden)) { 0357 m_changePresenceMessageButton->hide(); 0358 } else { 0359 m_changePresenceMessageButton->show(); 0360 } 0361 } 0362 0363 clearFocus(); 0364 } 0365 0366 void GlobalPresenceChooser::onPresenceChanged(const KTp::Presence &presence) 0367 { 0368 if (presence.type() == Tp::ConnectionPresenceTypeUnset) { 0369 setCurrentIndex(-1); 0370 m_busyOverlay->start(); 0371 return; 0372 } 0373 0374 const QModelIndexList &matchIndexList = m_model->match(m_model->index(0, 0), KTp::PresenceModel::PresenceRole, QVariant::fromValue<KTp::Presence>(presence)); 0375 if (!matchIndexList.isEmpty()) { 0376 m_modelExtended->removeTemporaryPresence(); 0377 setCurrentIndex(matchIndexList.at(0).row()); 0378 } else { 0379 const QModelIndex &index = m_modelExtended->addTemporaryPresence(presence); 0380 setCurrentIndex(index.row()); 0381 } 0382 0383 m_busyOverlay->stop(); 0384 } 0385 0386 void GlobalPresenceChooser::onConnectionStatusChanged(KTp::GlobalPresence::ConnectionStatus connectionStatus) 0387 { 0388 if (connectionStatus == KTp::GlobalPresence::Connecting) { 0389 repositionOverlays(); 0390 m_busyOverlay->start(); 0391 } else { 0392 m_busyOverlay->stop(); 0393 } 0394 } 0395 0396 void GlobalPresenceChooser::repositionOverlays() 0397 { 0398 //set 2px margins so that the button is not bigger than the combo 0399 m_changePresenceMessageButton->setMaximumHeight(height() - 2); 0400 m_changePresenceMessageButton->setMaximumWidth(height() - 2); 0401 QPoint topLeft; 0402 if (m_changePresenceMessageButton->layoutDirection() == Qt::RightToLeft) { 0403 //move the button 22px from the left edge 0404 m_changePresenceMessageButton->move(22, 0); 0405 //place the spinner 2px right from the button, 4 is added to take the margin. 0406 topLeft.setX(m_changePresenceMessageButton->pos().x() + m_busyOverlay->sequence().frameSize().width() + 4); 0407 topLeft.setY((height() - m_busyOverlay->sequence().frameSize().height()) / 2); 0408 } else { 0409 //move the button 22px from the right edge 0410 m_changePresenceMessageButton->move(width() - m_changePresenceMessageButton->width() - 22, 0); 0411 //place the spinner 2px left from the button 0412 topLeft.setX(m_changePresenceMessageButton->pos().x() - m_busyOverlay->sequence().frameSize().width() - 2); 0413 topLeft.setY((height() - m_busyOverlay->sequence().frameSize().height()) / 2); 0414 } 0415 m_busyOverlay->setRect(QRect(topLeft, m_busyOverlay->sequence().frameSize())); 0416 } 0417 0418 void GlobalPresenceChooser::onChangePresenceMessageClicked() 0419 { 0420 m_changePresenceMessageButton->hide(); 0421 0422 setEditable(true); 0423 0424 //if current presence has no presence message, delete the text 0425 if (m_globalPresence->globalPresence().statusMessage().isEmpty()) { 0426 lineEdit()->clear(); 0427 } else { 0428 lineEdit()->setText(m_globalPresence->globalPresence().statusMessage()); 0429 } 0430 0431 lineEdit()->setFocus(); 0432 } 0433 0434 void GlobalPresenceChooser::onConfirmPresenceMessageClicked() 0435 { 0436 m_changePresenceMessageButton->show(); 0437 KTp::Presence presence = itemData(currentIndex(), KTp::PresenceModel::PresenceRole).value<KTp::Presence>(); 0438 presence.setStatusMessage(lineEdit()->text()); 0439 0440 setEditable(false); 0441 0442 m_globalPresence->setPresence(presence); 0443 } 0444 0445 0446 #include "global-presence-chooser.moc" 0447 #include "moc_global-presence-chooser.cpp" //hack because we have two QObjects in the same file