diff options
Diffstat (limited to 'Swift/QtUI/Roster')
-rw-r--r-- | Swift/QtUI/Roster/DelegateCommons.cpp | 9 | ||||
-rw-r--r-- | Swift/QtUI/Roster/DelegateCommons.h | 5 | ||||
-rw-r--r-- | Swift/QtUI/Roster/QtFilterWidget.cpp | 166 | ||||
-rw-r--r-- | Swift/QtUI/Roster/QtFilterWidget.h | 51 | ||||
-rw-r--r-- | Swift/QtUI/Roster/QtOccupantListWidget.cpp | 16 | ||||
-rw-r--r-- | Swift/QtUI/Roster/QtOccupantListWidget.h | 11 | ||||
-rw-r--r-- | Swift/QtUI/Roster/QtRosterWidget.cpp | 87 | ||||
-rw-r--r-- | Swift/QtUI/Roster/QtTreeWidget.cpp | 91 | ||||
-rw-r--r-- | Swift/QtUI/Roster/QtTreeWidget.h | 35 | ||||
-rw-r--r-- | Swift/QtUI/Roster/RosterDelegate.cpp | 4 | ||||
-rw-r--r-- | Swift/QtUI/Roster/RosterModel.cpp | 116 | ||||
-rw-r--r-- | Swift/QtUI/Roster/RosterModel.h | 24 | ||||
-rw-r--r-- | Swift/QtUI/Roster/RosterTooltip.cpp | 166 | ||||
-rw-r--r-- | Swift/QtUI/Roster/RosterTooltip.h | 27 |
14 files changed, 728 insertions, 80 deletions
diff --git a/Swift/QtUI/Roster/DelegateCommons.cpp b/Swift/QtUI/Roster/DelegateCommons.cpp index a575cb0..776d90c 100644 --- a/Swift/QtUI/Roster/DelegateCommons.cpp +++ b/Swift/QtUI/Roster/DelegateCommons.cpp @@ -15,8 +15,10 @@ namespace Swift { void DelegateCommons::drawElidedText(QPainter* painter, const QRect& region, const QString& text, int flags) { QString adjustedText(painter->fontMetrics().elidedText(text, Qt::ElideRight, region.width(), Qt::TextShowMnemonic)); + painter->setClipRect(region); painter->drawText(region, flags, adjustedText.simplified()); + painter->setClipping(false); } -void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, int unreadCount, bool compact) const { +void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, bool isIdle, int unreadCount, bool compact) const { painter->save(); QRect fullRegion(option.rect); @@ -30,4 +32,5 @@ void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem QRect presenceIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth, fullRegion.height() - verticalMargin)); + QRect idleIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth*2, fullRegion.height() - verticalMargin)); int calculatedAvatarSize = presenceIconRegion.height(); //This overlaps the presenceIcon, so must be painted first @@ -52,4 +55,8 @@ void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + if (isIdle) { + idleIcon.paint(painter, idleIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + } + QFontMetrics nameMetrics(nameFont); painter->setFont(nameFont); diff --git a/Swift/QtUI/Roster/DelegateCommons.h b/Swift/QtUI/Roster/DelegateCommons.h index 8732598..0684410 100644 --- a/Swift/QtUI/Roster/DelegateCommons.h +++ b/Swift/QtUI/Roster/DelegateCommons.h @@ -18,5 +18,5 @@ namespace Swift { class DelegateCommons { public: - DelegateCommons() : nameFont(QApplication::font()), detailFont(QApplication::font()) { + DelegateCommons() : nameFont(QApplication::font()), detailFont(QApplication::font()), idleIcon(QIcon(":/icons/zzz.png")) { detailFontSizeDrop = nameFont.pointSize() >= 10 ? 2 : 0; detailFont.setStyle(QFont::StyleItalic); @@ -27,5 +27,5 @@ namespace Swift { QSize contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index, bool compact) const; - void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, int unreadCount, bool compact) const; + void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, bool isIdle, int unreadCount, bool compact) const; int detailFontSizeDrop; @@ -39,4 +39,5 @@ namespace Swift { static const int presenceIconWidth; static const int unreadCountSize; + QIcon idleIcon; }; } diff --git a/Swift/QtUI/Roster/QtFilterWidget.cpp b/Swift/QtUI/Roster/QtFilterWidget.cpp new file mode 100644 index 0000000..64eb312 --- /dev/null +++ b/Swift/QtUI/Roster/QtFilterWidget.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2014 Kevin Smith and Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <QEvent> +#include <QKeyEvent> +#include <QLayout> +#include <QString> +#include <QVBoxLayout> + +#include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/QtUI/QtClosableLineEdit.h> +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/Roster/QtFilterWidget.h> + +namespace Swift { + +QtFilterWidget::QtFilterWidget(QWidget* parent, QtTreeWidget* treeView, UIEventStream* eventStream, QBoxLayout* layout) : QWidget(parent), treeView_(treeView), eventStream_(eventStream), fuzzyRosterFilter_(0), isModifierSinglePressed_(false) { + int targetIndex = layout->indexOf(treeView); + + QVBoxLayout* vboxLayout = new QVBoxLayout(this); + vboxLayout->setSpacing(0); + vboxLayout->setContentsMargins(0,0,0,0); + + filterLineEdit_ = new QtClosableLineEdit(this); + filterLineEdit_->hide(); + vboxLayout->addWidget(filterLineEdit_); + + vboxLayout->addWidget(treeView); + setLayout(vboxLayout); + layout->insertWidget(targetIndex, this); + + filterLineEdit_->installEventFilter(this); + treeView->installEventFilter(this); + + sourceModel_ = treeView_->model(); +} + +QtFilterWidget::~QtFilterWidget() { + +} + +bool QtFilterWidget::eventFilter(QObject*, QEvent* event) { + if (event->type() == QEvent::KeyPress || + event->type() == QEvent::KeyRelease || + // InputMethodQuery got introduced in Qt 5. +#if QT_VERSION >= 0x050000 + event->type() == QEvent::InputMethodQuery || +#endif + event->type() == QEvent::InputMethod) { + event->ignore(); + QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event); + if (keyEvent) { + if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) { + return false; + } else if ((keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Right) && filterLineEdit_->text().isEmpty()) { + return false; + } else if (keyEvent->key() == Qt::Key_Alt && event->type() == QEvent::KeyPress) { + isModifierSinglePressed_ = true; + } else if (keyEvent->key() == Qt::Key_Alt && event->type() == QEvent::KeyRelease && isModifierSinglePressed_) { + QPoint itemOffset(2,2); + QPoint contextMenuPosition = treeView_->visualRect(treeView_->currentIndex()).topLeft() + itemOffset;; + QApplication::postEvent(treeView_, new QContextMenuEvent(QContextMenuEvent::Keyboard, contextMenuPosition, treeView_->mapToGlobal(contextMenuPosition))); + return true; + } else if (keyEvent->key() == Qt::Key_Return) { + JID target = treeView_->selectedJID(); + if (target.isValid()) { + eventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(target))); + } + filterLineEdit_->setText(""); + updateRosterFilters(); + } else if (keyEvent->key() == Qt::Key_Escape) { + filterLineEdit_->setText(""); + } else { + isModifierSinglePressed_ = false; + } + } + + filterLineEdit_->event(event); + + if (event->type() == QEvent::KeyRelease) { + updateRosterFilters(); + } + return true; + } + return false; +} + +void QtFilterWidget::popAllFilters() { + std::vector<RosterFilter*> filters = treeView_->getRoster()->getFilters(); + foreach(RosterFilter* filter, filters) { + filters_.push_back(filter); + treeView_->getRoster()->removeFilter(filter); + } + treeView_->getRoster()->onFilterAdded.connect(boost::bind(&QtFilterWidget::handleFilterAdded, this, _1)); + treeView_->getRoster()->onFilterRemoved.connect(boost::bind(&QtFilterWidget::handleFilterRemoved, this, _1)); +} + +void QtFilterWidget::pushAllFilters() { + treeView_->getRoster()->onFilterAdded.disconnect(boost::bind(&QtFilterWidget::handleFilterAdded, this, _1)); + treeView_->getRoster()->onFilterRemoved.disconnect(boost::bind(&QtFilterWidget::handleFilterRemoved, this, _1)); + foreach(RosterFilter* filter, filters_) { + treeView_->getRoster()->addFilter(filter); + } + filters_.clear(); +} + +void QtFilterWidget::updateRosterFilters() { + if (fuzzyRosterFilter_) { + if (filterLineEdit_->text().isEmpty()) { + // remove currently installed search filter and put old filters back + treeView_->getRoster()->removeFilter(fuzzyRosterFilter_); + delete fuzzyRosterFilter_; + fuzzyRosterFilter_ = NULL; + pushAllFilters(); + } else { + // remove currently intsalled search filter and put new search filter in place + updateSearchFilter(); + } + } else { + if (!filterLineEdit_->text().isEmpty()) { + // remove currently installed filters and put a search filter in place + popAllFilters(); + updateSearchFilter(); + } + } + filterLineEdit_->setVisible(!filterLineEdit_->text().isEmpty()); +} + +void QtFilterWidget::updateSearchFilter() { + if (fuzzyRosterFilter_) { + treeView_->getRoster()->removeFilter(fuzzyRosterFilter_); + delete fuzzyRosterFilter_; + fuzzyRosterFilter_ = NULL; + } + fuzzyRosterFilter_ = new FuzzyRosterFilter(Q2PSTRING(filterLineEdit_->text())); + treeView_->getRoster()->addFilter(fuzzyRosterFilter_); + treeView_->setCurrentIndex(sourceModel_->index(0, 0, sourceModel_->index(0,0))); +} + +void QtFilterWidget::handleFilterAdded(RosterFilter* filter) { + if (filter != fuzzyRosterFilter_) { + filterLineEdit_->setText(""); + updateRosterFilters(); + } +} + +void QtFilterWidget::handleFilterRemoved(RosterFilter* filter) { + /* make sure we don't end up adding this one back in later */ + filters_.erase(std::remove(filters_.begin(), filters_.end(), filter), filters_.end()); + if (filter != fuzzyRosterFilter_) { + filterLineEdit_->setText(""); + updateRosterFilters(); + } +} + +} diff --git a/Swift/QtUI/Roster/QtFilterWidget.h b/Swift/QtUI/Roster/QtFilterWidget.h new file mode 100644 index 0000000..d0307ea --- /dev/null +++ b/Swift/QtUI/Roster/QtFilterWidget.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <vector> + +#include <QBoxLayout> +#include <QWidget> + +#include <Swift/Controllers/Roster/RosterFilter.h> +#include <Swift/Controllers/Roster/FuzzyRosterFilter.h> + +#include <Swift/QtUI/Roster/QtTreeWidget.h> + +namespace Swift { +class UIEventStream; +class QtClosableLineEdit; +class QtFilterWidget : public QWidget { + Q_OBJECT + public: + QtFilterWidget(QWidget* parent, QtTreeWidget* treeView, UIEventStream* eventStream, QBoxLayout* layout = 0); + virtual ~QtFilterWidget(); + + protected: + bool eventFilter(QObject*, QEvent* event); + + private: + void popAllFilters(); + void pushAllFilters(); + + void updateRosterFilters(); + void updateSearchFilter(); + + void handleFilterAdded(RosterFilter* filter); + void handleFilterRemoved(RosterFilter* filter); + + private: + QtClosableLineEdit* filterLineEdit_; + QtTreeWidget* treeView_; + UIEventStream* eventStream_; + std::vector<RosterFilter*> filters_; + QAbstractItemModel* sourceModel_; + FuzzyRosterFilter* fuzzyRosterFilter_; + bool isModifierSinglePressed_; +}; + +} diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.cpp b/Swift/QtUI/Roster/QtOccupantListWidget.cpp index 5d26c46..4f1baf3 100644 --- a/Swift/QtUI/Roster/QtOccupantListWidget.cpp +++ b/Swift/QtUI/Roster/QtOccupantListWidget.cpp @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011-2012 Kevin Smith + * Copyright (c) 2011-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -6,5 +6,5 @@ -#include "Roster/QtOccupantListWidget.h" +#include <Swift/QtUI/Roster/QtOccupantListWidget.h> #include <QContextMenuEvent> @@ -13,12 +13,13 @@ #include <QInputDialog> -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "QtSwiftUtil.h" +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> + +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -QtOccupantListWidget::QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QtTreeWidget(eventStream, settings, parent) { +QtOccupantListWidget::QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget privateMessageTarget, QWidget* parent) : QtTreeWidget(eventStream, settings, privateMessageTarget, parent) { } @@ -59,4 +60,5 @@ void QtOccupantListWidget::contextMenuEvent(QContextMenuEvent* event) { case ChatWindow::MakeVisitor: text = tr("Remove voice"); break; case ChatWindow::AddContact: text = tr("Add to contacts"); break; + case ChatWindow::ShowProfile: text = tr("Show profile"); break; } QAction* action = contextMenu.addAction(text); diff --git a/Swift/QtUI/Roster/QtOccupantListWidget.h b/Swift/QtUI/Roster/QtOccupantListWidget.h index 729115a..c944658 100644 --- a/Swift/QtUI/Roster/QtOccupantListWidget.h +++ b/Swift/QtUI/Roster/QtOccupantListWidget.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2011-2012 Kevin Smith + * Copyright (c) 2011-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,8 +7,9 @@ #pragma once -#include "Swift/QtUI/Roster/QtTreeWidget.h" +#include <Swift/QtUI/Roster/QtTreeWidget.h> -#include "Swiften/Base/boost_bsignals.h" -#include "Swift/Controllers/UIInterfaces/ChatWindow.h" +#include <Swiften/Base/boost_bsignals.h> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { @@ -18,5 +19,5 @@ class QtOccupantListWidget : public QtTreeWidget { Q_OBJECT public: - QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent = 0); + QtOccupantListWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget privateMessageTarget, QWidget* parent = NULL); virtual ~QtOccupantListWidget(); void setAvailableOccupantActions(const std::vector<ChatWindow::OccupantAction>& actions); diff --git a/Swift/QtUI/Roster/QtRosterWidget.cpp b/Swift/QtUI/Roster/QtRosterWidget.cpp index e3fee24..8c296e5 100644 --- a/Swift/QtUI/Roster/QtRosterWidget.cpp +++ b/Swift/QtUI/Roster/QtRosterWidget.cpp @@ -1,28 +1,33 @@ /* - * Copyright (c) 2011 Kevin Smith + * Copyright (c) 2011-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Roster/QtRosterWidget.h" +#include <Swift/QtUI/Roster/QtRosterWidget.h> #include <QContextMenuEvent> #include <QMenu> +#include <QMessageBox> #include <QInputDialog> #include <QFileDialog> +#include <QPushButton> -#include "Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h" -#include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h" -#include "Swift/Controllers/UIEvents/RenameGroupUIEvent.h" -#include "Swift/Controllers/UIEvents/SendFileUIEvent.h" -#include "QtContactEditWindow.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" -#include "Swift/Controllers/UIEvents/UIEventStream.h" -#include "QtSwiftUtil.h" +#include <Swift/Controllers/UIEvents/RequestContactEditorUIEvent.h> +#include <Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/RenameGroupUIEvent.h> +#include <Swift/Controllers/UIEvents/SendFileUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestWhiteboardUIEvent.h> +#include <Swift/Controllers/UIEvents/ShowProfileForRosterItemUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChangeBlockStateUIEvent.h> +#include <Swift/QtUI/QtContactEditWindow.h> +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> +#include <Swift/Controllers/UIEvents/UIEventStream.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -QtRosterWidget::QtRosterWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QtTreeWidget(eventStream, settings, parent) { +QtRosterWidget::QtRosterWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QtTreeWidget(eventStream, settings, MessageDefaultJID, parent) { } @@ -55,10 +60,35 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { QMenu contextMenu; if (ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item)) { - QAction* editContact = contextMenu.addAction(tr("Edit")); + QAction* editContact = contextMenu.addAction(tr("Edit…")); + editContact->setEnabled(isOnline()); QAction* removeContact = contextMenu.addAction(tr("Remove")); + removeContact->setEnabled(isOnline()); + QAction* showProfileForContact = contextMenu.addAction(tr("Show Profile")); + + QAction* unblockContact = NULL; + if (contact->blockState() == ContactRosterItem::IsBlocked || + contact->blockState() == ContactRosterItem::IsDomainBlocked) { + unblockContact = contextMenu.addAction(tr("Unblock")); + unblockContact->setEnabled(isOnline()); + } + + QAction* blockContact = NULL; + if (contact->blockState() == ContactRosterItem::IsUnblocked) { + blockContact = contextMenu.addAction(tr("Block")); + blockContact->setEnabled(isOnline()); + } + #ifdef SWIFT_EXPERIMENTAL_FT QAction* sendFile = NULL; if (contact->supportsFeature(ContactRosterItem::FileTransferFeature)) { sendFile = contextMenu.addAction(tr("Send File")); + sendFile->setEnabled(isOnline()); + } +#endif +#ifdef SWIFT_EXPERIMENTAL_WB + QAction* startWhiteboardChat = NULL; + if (contact->supportsFeature(ContactRosterItem::WhiteboardFeature)) { + startWhiteboardChat = contextMenu.addAction(tr("Start Whiteboard Chat")); + startWhiteboardChat->setEnabled(isOnline()); } #endif @@ -72,4 +102,24 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { } } + else if (result == showProfileForContact) { + eventStream_->send(boost::make_shared<ShowProfileForRosterItemUIEvent>(contact->getJID())); + } + else if (unblockContact && result == unblockContact) { + if (contact->blockState() == ContactRosterItem::IsDomainBlocked) { + QMessageBox messageBox(QMessageBox::Question, tr("Swift"), tr("%2 is currently blocked because of a block on all users of the %1 service.\n %2 cannot be unblocked individually; do you want to unblock all %1 users?").arg(P2QSTRING(contact->getJID().getDomain()), P2QSTRING(contact->getJID().toString())), QMessageBox::NoButton, this); + QPushButton* unblockDomainButton = messageBox.addButton(tr("Unblock %1 domain").arg(P2QSTRING(contact->getJID().getDomain())), QMessageBox::AcceptRole); + messageBox.addButton(QMessageBox::Abort); + + messageBox.exec(); + if (messageBox.clickedButton() == unblockDomainButton) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, contact->getJID().getDomain())); + } + } else { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Unblocked, contact->getJID())); + } + } + else if (blockContact && result == blockContact) { + eventStream_->send(boost::make_shared<RequestChangeBlockStateUIEvent>(RequestChangeBlockStateUIEvent::Blocked, contact->getJID())); + } #ifdef SWIFT_EXPERIMENTAL_FT else if (sendFile && result == sendFile) { @@ -80,7 +130,18 @@ void QtRosterWidget::contextMenuEvent(QContextMenuEvent* event) { } #endif +#ifdef SWIFT_EXPERIMENTAL_WB + else if (startWhiteboardChat && result == startWhiteboardChat) { + eventStream_->send(boost::make_shared<RequestWhiteboardUIEvent>(contact->getJID())); + } +#endif } else if (GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item)) { QAction* renameGroupAction = contextMenu.addAction(tr("Rename")); + if (P2QSTRING(group->getDisplayName()) == tr("Contacts")) { + renameGroupAction->setEnabled(false); + } + else { + renameGroupAction->setEnabled(isOnline()); + } QAction* result = contextMenu.exec(event->globalPos()); if (result == renameGroupAction) { diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp index 5fdf138..f296088 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.cpp +++ b/Swift/QtUI/Roster/QtTreeWidget.cpp @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Roster/QtTreeWidget.h" +#include <Swift/QtUI/Roster/QtTreeWidget.h> #include <boost/smart_ptr/make_shared.hpp> @@ -11,6 +11,12 @@ #include <QUrl> +#include <QMimeData> +#include <QObject> +#include <QLabel> +#include <QTimer> +#include <QToolTip> #include <Swiften/Base/Platform.h> + #include <Swift/Controllers/Roster/ContactRosterItem.h> #include <Swift/Controllers/Roster/GroupRosterItem.h> @@ -18,14 +24,16 @@ #include <Swift/Controllers/UIEvents/RequestChatUIEvent.h> #include <Swift/Controllers/UIEvents/SendFileUIEvent.h> -#include <QtSwiftUtil.h> #include <Swift/Controllers/Settings/SettingsProvider.h> + #include <Swift/QtUI/QtUISettingConstants.h> +#include <Swift/QtUI/Roster/RosterModel.h> +#include <QtSwiftUtil.h> namespace Swift { -QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent) : QTreeView(parent) { +QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget messageTarget, QWidget* parent) : QTreeView(parent), tooltipShown_(false), messageTarget_(messageTarget) { eventStream_ = eventStream; settings_ = settings; - model_ = new RosterModel(this); + model_ = new RosterModel(this, settings_->getSetting(QtUISettingConstants::USE_SCREENREADER)); setModel(model_); delegate_ = new RosterDelegate(this, settings_->getSetting(QtUISettingConstants::COMPACT_ROSTER)); @@ -42,4 +50,5 @@ QtTreeWidget::QtTreeWidget(UIEventStream* eventStream, SettingsProvider* setting setAcceptDrops(true); #endif + setDragEnabled(true); setRootIsDecorated(true); connect(this, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleItemActivated(const QModelIndex&))); @@ -65,4 +74,12 @@ void QtTreeWidget::handleSettingChanged(const std::string& setting) { } +void QtTreeWidget::handleRefreshTooltip() { + if (tooltipShown_) { + QPoint position = QCursor::pos(); + QModelIndex index = indexAt(mapFromGlobal(position)); + QToolTip::showText(position, model_->data(index, Qt::ToolTipRole).toString()); + } +} + void QtTreeWidget::setRosterModel(Roster* roster) { roster_ = roster; @@ -71,4 +88,9 @@ void QtTreeWidget::setRosterModel(Roster* roster) { } +void QtTreeWidget::refreshTooltip() { + // Qt needs some time to emit the events we need to detect tooltip's visibility correctly; 20 ms should be enough + QTimer::singleShot(20, this, SLOT(handleRefreshTooltip())); +} + QtTreeWidgetItem* QtTreeWidget::getRoot() { return treeRoot_; @@ -116,10 +138,8 @@ void QtTreeWidget::currentChanged(const QModelIndex& current, const QModelIndex& } - void QtTreeWidget::handleItemActivated(const QModelIndex& index) { - RosterItem* item = static_cast<RosterItem*>(index.internalPointer()); - ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); - if (contact) { - eventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(contact->getJID()))); + JID target = jidFromIndex(index); + if (target.isValid()) { + eventStream_->send(boost::shared_ptr<UIEvent>(new RequestChatUIEvent(target))); } } @@ -157,5 +177,22 @@ void QtTreeWidget::dragMoveEvent(QDragMoveEvent* event) { } } - event->ignore(); + QTreeView::dragMoveEvent(event); +} + +bool QtTreeWidget::event(QEvent* event) { + QChildEvent* childEvent = NULL; + if ((childEvent = dynamic_cast<QChildEvent*>(event))) { + if (childEvent->polished()) { + if (dynamic_cast<QLabel*>(childEvent->child())) { + tooltipShown_ = true; + } + } + else if (childEvent->removed()) { + if (childEvent->child()->objectName() == "qtooltip_label") { + tooltipShown_ = false; + } + } + } + return QAbstractItemView::event(event); } @@ -192,3 +229,35 @@ void QtTreeWidget::show() { } +void QtTreeWidget::setMessageTarget(MessageTarget messageTarget) { + messageTarget_ = messageTarget; +} + +JID QtTreeWidget::jidFromIndex(const QModelIndex& index) const { + JID target; + if (messageTarget_ == MessageDisplayJID) { + target = JID(Q2PSTRING(index.data(DisplayJIDRole).toString())); + target = target.toBare(); + } + if (!target.isValid()) { + target = JID(Q2PSTRING(index.data(JIDRole).toString())); + } + return target; +} + +JID QtTreeWidget::selectedJID() const { + QModelIndexList list = selectedIndexes(); + if (list.size() != 1) { + return JID(); + } + return jidFromIndex(list[0]); +} + +void QtTreeWidget::setOnline(bool isOnline) { + isOnline_ = isOnline; +} + +bool QtTreeWidget::isOnline() const { + return isOnline_; +} + } diff --git a/Swift/QtUI/Roster/QtTreeWidget.h b/Swift/QtUI/Roster/QtTreeWidget.h index 7c10a6a..12d34f5 100644 --- a/Swift/QtUI/Roster/QtTreeWidget.h +++ b/Swift/QtUI/Roster/QtTreeWidget.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010-2012 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,11 +7,14 @@ #pragma once -#include <QTreeView> -#include <QModelIndex> #include <QDragEnterEvent> #include <QDropEvent> #include <QDragMoveEvent> -#include "Swift/QtUI/Roster/RosterModel.h" -#include "Swift/QtUI/Roster/RosterDelegate.h" +#include <QModelIndex> +#include <QTreeView> + +#include <Swift/QtUI/Roster/RosterDelegate.h> +#include <Swift/QtUI/Roster/RosterModel.h> + +#include <Swift/Controllers/UIInterfaces/ChatWindow.h> namespace Swift { @@ -22,5 +25,7 @@ class QtTreeWidget : public QTreeView{ Q_OBJECT public: - QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, QWidget* parent = 0); + enum MessageTarget {MessageDefaultJID, MessageDisplayJID}; + + QtTreeWidget(UIEventStream* eventStream, SettingsProvider* settings, MessageTarget messageTarget, QWidget* parent = 0); ~QtTreeWidget(); void show(); @@ -28,4 +33,11 @@ class QtTreeWidget : public QTreeView{ void setRosterModel(Roster* roster); Roster* getRoster() {return roster_;} + void refreshTooltip(); + void setMessageTarget(MessageTarget messageTarget); + JID jidFromIndex(const QModelIndex& index) const; + JID selectedJID() const; + void setOnline(bool isOnline); + + public: boost::signal<void (RosterItem*)> onSomethingSelectedChanged; @@ -37,13 +49,17 @@ class QtTreeWidget : public QTreeView{ void handleClicked(const QModelIndex&); void handleSettingChanged(const std::string& setting); + void handleRefreshTooltip(); + protected: void dragEnterEvent(QDragEnterEvent* event); void dropEvent(QDropEvent* event); void dragMoveEvent(QDragMoveEvent* event); - - protected: + bool event(QEvent* event); QModelIndexList getSelectedIndexes() const; + bool isOnline() const; + private: void drawBranches(QPainter*, const QRect&, const QModelIndex&) const; + protected slots: virtual void currentChanged(const QModelIndex& current, const QModelIndex& previous); @@ -57,4 +73,7 @@ class QtTreeWidget : public QTreeView{ QtTreeWidgetItem* treeRoot_; SettingsProvider* settings_; + bool tooltipShown_; + MessageTarget messageTarget_; + bool isOnline_; }; diff --git a/Swift/QtUI/Roster/RosterDelegate.cpp b/Swift/QtUI/Roster/RosterDelegate.cpp index 7e6428b..aef588c 100644 --- a/Swift/QtUI/Roster/RosterDelegate.cpp +++ b/Swift/QtUI/Roster/RosterDelegate.cpp @@ -36,4 +36,5 @@ RosterDelegate::~RosterDelegate() { void RosterDelegate::setCompact(bool compact) { compact_ = compact; + emit sizeHintChanged(QModelIndex()); } @@ -74,7 +75,8 @@ void RosterDelegate::paintContact(QPainter* painter, const QStyleOptionViewItem& ? index.data(PresenceIconRole).value<QIcon>() : QIcon(":/icons/offline.png"); + bool isIdle = index.data(IdleRole).isValid() ? index.data(IdleRole).toBool() : false; QString name = index.data(Qt::DisplayRole).toString(); QString statusText = index.data(StatusTextRole).toString(); - common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, 0, compact_); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, isIdle, 0, compact_); } diff --git a/Swift/QtUI/Roster/RosterModel.cpp b/Swift/QtUI/Roster/RosterModel.cpp index 1fc20dd..730ffbb 100644 --- a/Swift/QtUI/Roster/RosterModel.cpp +++ b/Swift/QtUI/Roster/RosterModel.cpp @@ -1,9 +1,9 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2014 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "RosterModel.h" +#include <Swift/QtUI/Roster/RosterModel.h> #include <boost/bind.hpp> @@ -11,21 +11,28 @@ #include <QColor> #include <QIcon> +#include <QMimeData> #include <qdebug.h> -#include "Swiften/Elements/StatusShow.h" -#include "Swift/Controllers/Roster/ContactRosterItem.h" -#include "Swift/Controllers/Roster/GroupRosterItem.h" +#include <Swiften/Elements/StatusShow.h> +#include <Swiften/Base/Path.h> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/Roster/GroupRosterItem.h> #include <Swift/Controllers/StatusUtil.h> -#include "QtSwiftUtil.h" -#include "Swift/QtUI/Roster/QtTreeWidget.h" +#include <Swift/QtUI/Roster/QtTreeWidget.h> +#include <Swift/QtUI/Roster/RosterTooltip.h> +#include <Swift/QtUI/QtResourceHelper.h> +#include <Swift/QtUI/QtSwiftUtil.h> namespace Swift { -RosterModel::RosterModel(QtTreeWidget* view) : view_(view) { - roster_ = NULL; +RosterModel::RosterModel(QtTreeWidget* view, bool screenReaderMode) : roster_(NULL), view_(view), screenReader_(screenReaderMode) { + const int tooltipAvatarSize = 96; // maximal suggested size according to XEP-0153 + cachedImageScaler_ = new QtScaledAvatarCache(tooltipAvatarSize); } RosterModel::~RosterModel() { + delete cachedImageScaler_; } @@ -41,5 +48,6 @@ void RosterModel::setRoster(Roster* roster) { void RosterModel::reLayout() { //emit layoutChanged(); - reset(); + beginResetModel(); + endResetModel(); // TODO: Not sure if this isn't too early? if (!roster_) { return; @@ -61,5 +69,14 @@ void RosterModel::handleDataChanged(RosterItem* item) { if (modelIndex.isValid()) { emit dataChanged(modelIndex, modelIndex); + view_->refreshTooltip(); + } +} + +Qt::ItemFlags RosterModel::flags(const QModelIndex& index) const { + Qt::ItemFlags flags = QAbstractItemModel::flags(index); + if (dynamic_cast<GroupRosterItem*>(getItem(index)) == NULL) { + flags |= Qt::ItemIsDragEnabled; } + return flags; } @@ -77,5 +94,5 @@ QVariant RosterModel::data(const QModelIndex& index, int role) const { switch (role) { - case Qt::DisplayRole: return P2QSTRING(item->getDisplayName()); + case Qt::DisplayRole: return getScreenReaderTextOr(item, P2QSTRING(item->getDisplayName())); case Qt::TextColorRole: return getTextColor(item); case Qt::BackgroundColorRole: return getBackgroundColor(item); @@ -85,8 +102,26 @@ QVariant RosterModel::data(const QModelIndex& index, int role) const { case PresenceIconRole: return getPresenceIcon(item); case ChildCountRole: return getChildCount(item); + case IdleRole: return getIsIdle(item); + case JIDRole: return getJID(item); + case DisplayJIDRole: return getDisplayJID(item); default: return QVariant(); } } +QString RosterModel::getScreenReaderTextOr(RosterItem* item, const QString& alternative) const { + QString name = P2QSTRING(item->getDisplayName()); + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + if (contact && screenReader_) { + name += ": " + P2QSTRING(statusShowTypeToFriendlyName(contact->getStatusShow())); + if (!contact->getStatusText().empty()) { + name += " (" + P2QSTRING(contact->getStatusText()) + ")"; + } + return name; + } + else { + return alternative; + } +} + int RosterModel::getChildCount(RosterItem* item) const { GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item); @@ -94,4 +129,9 @@ int RosterModel::getChildCount(RosterItem* item) const { } +bool RosterModel::getIsIdle(RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + return contact ? !contact->getIdleText().empty() : false; +} + QColor RosterModel::intToColor(int color) const { return QColor( @@ -125,11 +165,5 @@ QString RosterModel::getToolTip(RosterItem* item) const { ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); if (contact) { - if (contact->getDisplayJID().isValid()) { - tip += "\n" + P2QSTRING(contact->getDisplayJID().toBare().toString()); - } - tip += "\n " + P2QSTRING(statusShowTypeToFriendlyName(contact->getStatusShow())); - if (!getStatusText(item).isEmpty()) { - tip += ": " + getStatusText(item); - } + return RosterTooltip::buildDetailedTooltip(contact, cachedImageScaler_); } return tip; @@ -141,5 +175,5 @@ QString RosterModel::getAvatar(RosterItem* item) const { return ""; } - return QString(contact->getAvatarPath().c_str()); + return P2QSTRING(pathToString(contact->getAvatarPath())); } @@ -150,17 +184,27 @@ QString RosterModel::getStatusText(RosterItem* item) const { } +QString RosterModel::getJID(RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + return contact ? P2QSTRING(contact->getJID().toString()) : QString(); +} + +QString RosterModel::getDisplayJID(RosterItem* item) const { + ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); + QString result = contact ? P2QSTRING(contact->getDisplayJID().toString()) : QString(); + if (result.isEmpty()) { + result = getJID(item); + } + return result; +} + QIcon RosterModel::getPresenceIcon(RosterItem* item) const { ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item); if (!contact) return QIcon(); - QString iconString; - switch (contact->getStatusShow()) { - case StatusShow::Online: iconString = "online";break; - case StatusShow::Away: iconString = "away";break; - case StatusShow::XA: iconString = "away";break; - case StatusShow::FFC: iconString = "online";break; - case StatusShow::DND: iconString = "dnd";break; - case StatusShow::None: iconString = "offline";break; + if (contact->blockState() == ContactRosterItem::IsBlocked || + contact->blockState() == ContactRosterItem::IsDomainBlocked) { + return QIcon(":/icons/stop.png"); } - return QIcon(":/icons/" + iconString + ".png"); + + return QIcon(statusShowTypeToIconPath(contact->getStatusShow())); } @@ -216,3 +260,19 @@ int RosterModel::rowCount(const QModelIndex& parent) const { } +QMimeData* RosterModel::mimeData(const QModelIndexList& indexes) const { + QMimeData* data = QAbstractItemModel::mimeData(indexes); + + ContactRosterItem *item = dynamic_cast<ContactRosterItem*>(getItem(indexes.first())); + if (item == NULL) { + return data; + } + + /* only a single JID in this list */ + QByteArray itemData; + QDataStream dataStream(&itemData, QIODevice::WriteOnly); + dataStream << P2QSTRING(item->getJID().toString()); + data->setData("application/vnd.swift.contact-jid-list", itemData); + return data; +} + } diff --git a/Swift/QtUI/Roster/RosterModel.h b/Swift/QtUI/Roster/RosterModel.h index bd34e9c..2b05044 100644 --- a/Swift/QtUI/Roster/RosterModel.h +++ b/Swift/QtUI/Roster/RosterModel.h @@ -1,4 +1,4 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2013 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. @@ -7,9 +7,11 @@ #pragma once -#include "Swift/Controllers/Roster/Roster.h" - #include <QAbstractItemModel> #include <QList> +#include <Swift/Controllers/Roster/Roster.h> + +#include <Swift/QtUI/QtScaledAvatarCache.h> + namespace Swift { enum RosterRoles { @@ -19,4 +21,7 @@ namespace Swift { StatusShowTypeRole = Qt::UserRole + 3, ChildCountRole = Qt::UserRole + 4, + IdleRole = Qt::UserRole + 5, + JIDRole = Qt::UserRole + 6, + DisplayJIDRole = Qt::UserRole + 7 }; @@ -26,7 +31,8 @@ namespace Swift { Q_OBJECT public: - RosterModel(QtTreeWidget* view); + RosterModel(QtTreeWidget* view, bool screenReaderMode); ~RosterModel(); void setRoster(Roster* swiftRoster); + Qt::ItemFlags flags(const QModelIndex& index) const; int columnCount(const QModelIndex& parent = QModelIndex()) const; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; @@ -35,4 +41,6 @@ namespace Swift { QModelIndex parent(const QModelIndex& index) const; int rowCount(const QModelIndex& parent = QModelIndex()) const; + QMimeData* mimeData(const QModelIndexList& indexes) const; + signals: void itemExpanded(const QModelIndex& item, bool expanded); @@ -47,9 +55,17 @@ namespace Swift { QString getAvatar(RosterItem* item) const; QString getStatusText(RosterItem* item) const; + QString getJID(RosterItem* item) const; + QString getDisplayJID(RosterItem* item) const; QIcon getPresenceIcon(RosterItem* item) const; int getChildCount(RosterItem* item) const; + bool getIsIdle(RosterItem* item) const; void reLayout(); + /** calculates screenreader-friendly text if in screenreader mode, otherwise uses alternative text */ + QString getScreenReaderTextOr(RosterItem* item, const QString& alternative) const; + private: Roster* roster_; QtTreeWidget* view_; + QtScaledAvatarCache* cachedImageScaler_; + bool screenReader_; }; } diff --git a/Swift/QtUI/Roster/RosterTooltip.cpp b/Swift/QtUI/Roster/RosterTooltip.cpp new file mode 100644 index 0000000..86f175d --- /dev/null +++ b/Swift/QtUI/Roster/RosterTooltip.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#include <Swift/QtUI/Roster/RosterTooltip.h> + +#include <QObject> +#include <QString> +#include <QApplication> + +#include <Swiften/Base/Path.h> + +#include <Swift/Controllers/Roster/ContactRosterItem.h> +#include <Swift/Controllers/StatusUtil.h> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtScaledAvatarCache.h> +#include <Swift/QtUI/QtUtilities.h> +#include <Swift/QtUI/QtResourceHelper.h> + +using namespace QtUtilities; + +namespace Swift { + +QString RosterTooltip::buildDetailedTooltip(ContactRosterItem* contact, QtScaledAvatarCache* cachedImageScaler) { + QString tooltipTemplate; + if (QApplication::layoutDirection() == Qt::RightToLeft) { + tooltipTemplate = QString( + "<table style='white-space:pre'>" + "<tr>" + "<td>" + "<img src=\"%1\" />" + "</td>" + "<td>" + "<p style='font-size: 14px'>%3 %2</p>" + "<table><tr><td valign='middle'>%5</td><td valign='middle'>%4</td></tr></table>" + "%6" + "%7" + "%8" + "%9" + "</td>" + "</tr>" + "</table>"); + } else { + tooltipTemplate = QString( + "<table style='white-space:pre'>" + "<tr>" + "<td>" + "<img src=\"%1\" />" + "</td>" + "<td>" + "<p style='font-size: 14px'>%2 %3</p>" + "<table><tr><td valign='middle'>%4</td><td valign='middle'>%5</td></tr></table>" + "%6" + "%7" + "%8" + "%9" + "</td>" + "</tr>" + "</table>"); + } + // prepare tooltip + QString fullName = P2QSTRING(contact->getDisplayName()); + + QString vCardSummary; + VCard::ref vCard = contact->getVCard(); + if (vCard) { + fullName = P2QSTRING(vCard->getFullName()).trimmed(); + if (fullName.isEmpty()) { + fullName = (P2QSTRING(vCard->getGivenName()) + " " + P2QSTRING(vCard->getFamilyName())).trimmed(); + } + if (fullName.isEmpty()) { + fullName = P2QSTRING(contact->getDisplayName()); + } + vCardSummary = buildVCardSummary(vCard); + } else { + contact->onVCardRequested(); + } + + QString scaledAvatarPath = cachedImageScaler->getScaledAvatarPath(P2QSTRING(contact->getAvatarPath().empty() ? ":/icons/avatar.png" : pathToString(contact->getAvatarPath()))); + + QString bareJID = contact->getDisplayJID().toString().empty() ? "" : "( " + P2QSTRING(contact->getDisplayJID().toString()) + " )"; + + QString presenceIconTag = QString("<img src='%1' />").arg(statusShowTypeToIconPath(contact->getStatusShow())); + + QString statusMessage = contact->getStatusText().empty() ? QObject::tr("(No message)") : P2QSTRING(contact->getStatusText()); + + QString idleString = P2QSTRING(contact->getIdleText()); + if (!idleString.isEmpty()) { + idleString = QObject::tr("Idle since %1").arg(idleString); + idleString = htmlEscape(idleString) + "<br/>"; + } + + QString lastSeen = P2QSTRING(contact->getOfflineSinceText()); + if (!lastSeen.isEmpty()) { + lastSeen = QObject::tr("Last seen %1").arg(lastSeen); + lastSeen = htmlEscape(lastSeen) + "<br/>"; + } + + QString mucOccupant= P2QSTRING(contact->getMUCAffiliationText()); + if (!mucOccupant.isEmpty()) { + mucOccupant = htmlEscape(mucOccupant) + "<br/>"; + } + + return tooltipTemplate.arg(scaledAvatarPath, htmlEscape(fullName), htmlEscape(bareJID), presenceIconTag, htmlEscape(statusMessage), mucOccupant, idleString, lastSeen, vCardSummary); +} + +QString RosterTooltip::buildVCardSummary(VCard::ref vcard) { + QString summary; + summary = "<table>"; + + // star | name | content + QString currentBlock; + foreach (const VCard::Telephone& tel, vcard->getTelephones()) { + QString type = tel.isFax ? QObject::tr("Fax") : QObject::tr("Telephone"); + QString field = buildVCardField(tel.isPreferred, type, htmlEscape(P2QSTRING(tel.number))); + if (tel.isPreferred) { + currentBlock = field; + break; + } + currentBlock += field; + } + summary += currentBlock; + + currentBlock = ""; + foreach (const VCard::EMailAddress& mail, vcard->getEMailAddresses()) { + QString field = buildVCardField(mail.isPreferred, QObject::tr("E-Mail"), htmlEscape(P2QSTRING(mail.address))); + if (mail.isPreferred) { + currentBlock = field; + break; + } + currentBlock += field; + } + summary += currentBlock; + + currentBlock = ""; + foreach (const VCard::Organization& org, vcard->getOrganizations()) { + QString field = buildVCardField(false, QObject::tr("Organization"), htmlEscape(P2QSTRING(org.name))); + currentBlock += field; + } + summary += currentBlock; + + currentBlock = ""; + foreach(const std::string& title, vcard->getTitles()) { + QString field = buildVCardField(false, QObject::tr("Title"), htmlEscape(P2QSTRING(title))); + currentBlock += field; + } + summary += currentBlock; + + summary += "</table>"; + return summary; +} + +QString RosterTooltip::buildVCardField(bool preferred, const QString& name, const QString& content) { + QString rowTemplate; + if (QApplication::layoutDirection() == Qt::RightToLeft) { + rowTemplate = QString("<tr><td>%3</td><td valign='middle'><strong>%2</strong></td><td valign='middle'>%1</td></tr>"); + } else { + rowTemplate = QString("<tr><td>%1</td><td valign='middle'><strong>%2</strong></td><td valign='middle'>%3</td></tr>"); + } + return rowTemplate.arg(preferred ? "<img src=':/icons/star-checked.png' />" : "", name, content); +} + +} diff --git a/Swift/QtUI/Roster/RosterTooltip.h b/Swift/QtUI/Roster/RosterTooltip.h new file mode 100644 index 0000000..37f5da2 --- /dev/null +++ b/Swift/QtUI/Roster/RosterTooltip.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2013 Tobias Markmann + * Licensed under the simplified BSD license. + * See Documentation/Licenses/BSD-simplified.txt for more information. + */ + +#pragma once + +#include <QString> + +#include <Swiften/Elements/VCard.h> + +namespace Swift { + +class ContactRosterItem; +class QtScaledAvatarCache; + +class RosterTooltip { + public: + static QString buildDetailedTooltip(ContactRosterItem* contact, QtScaledAvatarCache* cachedImageScaler); + + private: + static QString buildVCardSummary(VCard::ref vcard); + static QString buildVCardField(bool preferred, const QString& name, const QString& content); +}; + +} |