Warning, file /network/ktp-contact-list/contact-list-widget.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * Contact List Widget 0003 * Copyright (C) 2011 Martin Klapetek <martin.klapetek@gmail.com> 0004 * 0005 * This library is free software; you can redistribute it and/or 0006 * modify it under the terms of the GNU Lesser General Public 0007 * License as published by the Free Software Foundation; either 0008 * version 2.1 of the License, or (at your option) any later version. 0009 * 0010 * This library is distributed in the hope that it will be useful, 0011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 * Lesser General Public License for more details. 0014 * 0015 * You should have received a copy of the GNU Lesser General Public 0016 * License along with this library; if not, write to the Free Software 0017 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 0018 */ 0019 0020 0021 #include "contact-list-widget.h" 0022 #include "contact-list-widget_p.h" 0023 #include "ktp-contactlist-debug.h" 0024 0025 #include <TelepathyQt/AccountManager> 0026 #include <TelepathyQt/PendingChannelRequest> 0027 #include <TelepathyQt/PendingReady> 0028 0029 #include <KTp/types.h> 0030 0031 #include <KTp/Models/contacts-model.h> 0032 #include <KTp/global-contact-manager.h> 0033 #include <KTp/actions.h> 0034 #include <KTp/contact.h> 0035 #include <KTp/Widgets/settings-kcm-dialog.h> 0036 0037 #include <KSharedConfig> 0038 #include <KConfigGroup> 0039 #include <KMessageBox> 0040 #include <KLocalizedString> 0041 #include <ksettings/Dialog> 0042 #include <KNotifyConfigWidget> 0043 0044 #include <QMenu> 0045 #include <QPushButton> 0046 #include <QFileDialog> 0047 #include <QHeaderView> 0048 #include <QLabel> 0049 #include <QApplication> 0050 #include <QDropEvent> 0051 #include <QDragMoveEvent> 0052 #include <QDragEnterEvent> 0053 #include <QDragLeaveEvent> 0054 #include <QPainter> 0055 #include <QPixmap> 0056 #include <QMenu> 0057 #include <QDrag> 0058 #include <QDebug> 0059 0060 #include "contact-delegate.h" 0061 #include "contact-delegate-compact.h" 0062 #include "contact-overlays.h" 0063 #include "empty-row-filter.h" 0064 0065 0066 #ifdef HAVE_KPEOPLE 0067 #include <kpeople/personsmodel.h> 0068 #endif 0069 0070 //create a new style that does not draw the vertical lines in the tree view 0071 //this maps "draw branch" to "draw right arrow" and "draw down arrow" 0072 //we cannot just override drawBranches as then we cannot highlight the active branch 0073 //Qt does so by utilising some internal methods of QTreeView 0074 class NoLinesStyle: public QProxyStyle 0075 { 0076 void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const 0077 { 0078 if (element == QStyle::PE_IndicatorBranch) { 0079 if (option->state & QStyle::State_Children) { 0080 if (option->state & QStyle::State_Open) { 0081 return QProxyStyle::drawPrimitive(PE_IndicatorArrowDown, option, painter, widget); 0082 } else { 0083 return QProxyStyle::drawPrimitive(PE_IndicatorArrowRight, option, painter, widget); 0084 } 0085 } 0086 } else { 0087 return QProxyStyle::drawPrimitive(element, option, painter, widget); 0088 } 0089 } 0090 }; 0091 0092 ContactListWidget::ContactListWidget(QWidget *parent) 0093 : QTreeView(parent), 0094 d_ptr(new ContactListWidgetPrivate) 0095 { 0096 Q_D(ContactListWidget); 0097 0098 KSharedConfigPtr config = KSharedConfig::openConfig(); 0099 KConfigGroup guiConfigGroup(config, "GUI"); 0100 0101 d->groupMode = KTp::ContactsModel::NoGrouping; 0102 d->delegate = new ContactDelegate(this); 0103 d->compactDelegate = new ContactDelegateCompact(ContactDelegateCompact::Normal, this); 0104 0105 d->model = new KTp::ContactsModel(this); 0106 d->model->setDynamicSortFilter(true); 0107 d->model->setSortRole(Qt::DisplayRole); 0108 d->style.reset(new NoLinesStyle()); 0109 0110 setStyle(d->style.data()); 0111 loadGroupStatesFromConfig(); 0112 0113 header()->hide(); 0114 setEditTriggers(NoEditTriggers); 0115 setContextMenuPolicy(Qt::CustomContextMenu); 0116 if (KTp::kpeopleEnabled()) { 0117 setIndentation(18); 0118 } else { 0119 setIndentation(0); 0120 } 0121 setMouseTracking(true); 0122 setExpandsOnDoubleClick(false); //the expanding/collapsing is handled manually 0123 setDragEnabled(false); // we handle drag&drop ourselves 0124 viewport()->setAcceptDrops(true); 0125 setDropIndicatorShown(true); 0126 setSelectionMode(ExtendedSelection); 0127 setSelectionBehavior(SelectItems); 0128 0129 QString delegateMode = guiConfigGroup.readEntry("selected_delegate", "normal"); 0130 0131 itemDelegate()->deleteLater(); 0132 if (delegateMode == QLatin1String("full")) { 0133 setItemDelegate(d->delegate); 0134 } else if (delegateMode == QLatin1String("mini")) { 0135 setItemDelegate(d->compactDelegate); 0136 d->compactDelegate->setListMode(ContactDelegateCompact::Mini); 0137 } else { 0138 setItemDelegate(d->compactDelegate); 0139 d->compactDelegate->setListMode(ContactDelegateCompact::Normal); 0140 } 0141 0142 addOverlayButtons(); 0143 emit enableOverlays(guiConfigGroup.readEntry("selected_delegate", "normal") == QLatin1String("full")); 0144 0145 QString shownContacts = guiConfigGroup.readEntry("shown_contacts", "unblocked"); 0146 if (shownContacts == "unblocked") { 0147 d->model->setSubscriptionStateFilterFlags(KTp::ContactsFilterModel::HideBlocked); 0148 } else if (shownContacts == "blocked") { 0149 d->model->setSubscriptionStateFilterFlags(KTp::ContactsFilterModel::ShowOnlyBlocked); 0150 } else { 0151 d->model->setSubscriptionStateFilterFlags(KTp::ContactsFilterModel::DoNotFilterBySubscription); 0152 } 0153 0154 connect(this, SIGNAL(clicked(QModelIndex)), 0155 this, SLOT(onContactListClicked(QModelIndex))); 0156 0157 connect(this, SIGNAL(doubleClicked(QModelIndex)), 0158 this, SLOT(onContactListDoubleClicked(QModelIndex))); 0159 0160 connect(d->delegate, SIGNAL(repaintItem(QModelIndex)), 0161 this->viewport(), SLOT(repaint())); //update(QModelIndex) 0162 } 0163 0164 0165 ContactListWidget::~ContactListWidget() 0166 { 0167 delete d_ptr; 0168 } 0169 0170 KTp::ContactsModel* ContactListWidget::contactsModel() const 0171 { 0172 return d_ptr->model; 0173 } 0174 0175 void ContactListWidget::setAccountManager(const Tp::AccountManagerPtr &accountManager) 0176 { 0177 Q_D(ContactListWidget); 0178 0179 d->accountManager = accountManager; 0180 d->model->setAccountManager(accountManager); 0181 0182 EmptyRowFilter *rowFilter= new EmptyRowFilter(this); 0183 rowFilter->setSourceModel(d->model); 0184 0185 connect(rowFilter, SIGNAL(rowsInserted(QModelIndex,int,int)), 0186 this, SLOT(onNewGroupModelItemsInserted(QModelIndex,int,int))); 0187 0188 // We set the model only when the account manager is set. 0189 // This fixes the weird horizontal scrollbar bug 0190 // See https://bugs.kde.org/show_bug.cgi?id=316260 0191 setModel(rowFilter); 0192 0193 connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), 0194 this, SIGNAL(contactSelectionChanged())); 0195 0196 QList<Tp::AccountPtr> accounts = accountManager->allAccounts(); 0197 0198 if(accounts.count() == 0) { 0199 if (KMessageBox::questionYesNo(this, 0200 i18n("You have no IM accounts configured. Would you like to do that now?"), 0201 i18n("No Accounts Found")) == KMessageBox::Yes) { 0202 0203 showSettingsKCM(); 0204 } 0205 } 0206 } 0207 0208 void ContactListWidget::showSettingsKCM() 0209 { 0210 KTp::SettingsKcmDialog *dialog = new KTp::SettingsKcmDialog(this); 0211 dialog->addGeneralSettingsModule(); 0212 dialog->addNotificationsModule(); 0213 dialog->show(); 0214 } 0215 0216 void ContactListWidget::onContactListClicked(const QModelIndex& index) 0217 { 0218 Q_D(ContactListWidget); 0219 0220 if (!index.isValid()) { 0221 return; 0222 } 0223 0224 if (index.data(KTp::RowTypeRole).toInt() == KTp::AccountRowType 0225 || index.data(KTp::RowTypeRole).toInt() == KTp::GroupRowType) { 0226 0227 KSharedConfigPtr config = KSharedConfig::openConfig(QLatin1String("ktelepathyrc")); 0228 KConfigGroup groupsConfig = config->group("GroupsState"); 0229 0230 QString groupId = index.data(KTp::IdRole).toString(); 0231 0232 if (isExpanded(index)) { 0233 collapse(index); 0234 groupsConfig.writeEntry(groupId, false); 0235 } else { 0236 expand(index); 0237 groupsConfig.writeEntry(groupId, true); 0238 } 0239 0240 groupsConfig.config()->sync(); 0241 0242 //replace the old value or insert new value if it isn't there yet 0243 d->groupStates.insert(groupId, isExpanded(index)); 0244 } 0245 } 0246 0247 void ContactListWidget::onContactListDoubleClicked(const QModelIndex &index) 0248 { 0249 if (!index.isValid()) { 0250 return; 0251 } 0252 0253 Tp::AccountPtr account = index.data(KTp::AccountRole).value<Tp::AccountPtr>(); 0254 KTp::ContactPtr contact = index.data(KTp::ContactRole).value<KTp::ContactPtr>(); 0255 0256 if (account.isNull()) { 0257 qCWarning(KTP_CONTACTLIST_MODULE) << "Account is null!"; 0258 return; 0259 } 0260 0261 //contact should be null only if the account is offline 0262 if (!contact.isNull()) { 0263 startTextChannel(account, contact); 0264 return; 0265 } 0266 0267 if (!account->isOnline()) { 0268 KGuiItem yes(i18nc("Label of a dialog's 'OK' button; %1 is account name, eg. 'Connect account GTalk'", 0269 "Connect account %1", account->displayName()), QLatin1String("dialog-ok")); 0270 if (KMessageBox::questionYesNo(this, 0271 i18n("The account for this contact is disconnected. Do you want to connect it?"), 0272 i18n("Account offline"), 0273 yes, 0274 KStandardGuiItem::no()) == KMessageBox::Yes) { 0275 0276 QString contactId = index.data(KTp::RowTypeRole).toUInt() == KTp::PersonRowType 0277 ? index.data(KTp::IdRole).toList().first().toString() 0278 : index.data(KTp::IdRole).toString(); 0279 if (!account->isEnabled()) { 0280 Tp::PendingOperation *op = account->setEnabled(true); 0281 op->setProperty("contactId", contactId); 0282 connect(op, SIGNAL(finished(Tp::PendingOperation*)), 0283 this, SLOT(accountEnablingFinished(Tp::PendingOperation*))); 0284 } else { 0285 account->ensureTextChat(contactId, 0286 QDateTime::currentDateTime(), 0287 QLatin1String("org.freedesktop.Telepathy.Client.KTp.TextUi")); 0288 } 0289 } 0290 } 0291 } 0292 0293 void ContactListWidget::accountEnablingFinished(Tp::PendingOperation *op) 0294 { 0295 if (op->isError()) { 0296 qCWarning(KTP_CONTACTLIST_MODULE) << "Account enabling failed" << op->errorMessage(); 0297 return; 0298 } 0299 0300 Tp::AccountPtr account = Tp::AccountPtr(qobject_cast<Tp::Account*>(sender())); 0301 0302 if (account.isNull()) { 0303 qCWarning(KTP_CONTACTLIST_MODULE) << "Null account passed!"; 0304 return; 0305 } 0306 0307 account->ensureTextChat(op->property("contactId").toString(), 0308 QDateTime::currentDateTime(), 0309 QLatin1String("org.freedesktop.Telepathy.Client.KTp.TextUi")); 0310 } 0311 0312 void ContactListWidget::addOverlayButtons() 0313 { 0314 Q_D(ContactListWidget); 0315 0316 TextChannelContactOverlay *textOverlay = new TextChannelContactOverlay(d->delegate); 0317 AudioChannelContactOverlay *audioOverlay = new AudioChannelContactOverlay(d->delegate); 0318 VideoChannelContactOverlay *videoOverlay = new VideoChannelContactOverlay(d->delegate); 0319 FileTransferContactOverlay *fileOverlay = new FileTransferContactOverlay(d->delegate); 0320 0321 d->delegate->installOverlay(textOverlay); 0322 d->delegate->installOverlay(audioOverlay); 0323 d->delegate->installOverlay(videoOverlay); 0324 d->delegate->installOverlay(fileOverlay); 0325 0326 LogViewerOverlay *logViewerOverlay = new LogViewerOverlay(d->delegate); 0327 d->delegate->installOverlay(logViewerOverlay); 0328 connect(logViewerOverlay, SIGNAL(activated(Tp::AccountPtr,Tp::ContactPtr)), 0329 this, SLOT(startLogViewer(Tp::AccountPtr, Tp::ContactPtr))); 0330 0331 connect(this, SIGNAL(enableOverlays(bool)), 0332 logViewerOverlay, SLOT(setActive(bool))); 0333 0334 d->delegate->setViewOnAllOverlays(this); 0335 d->delegate->setAllOverlaysActive(true); 0336 0337 connect(textOverlay, SIGNAL(overlayActivated(QModelIndex)), 0338 d->delegate, SLOT(hideStatusMessageSlot(QModelIndex))); 0339 0340 connect(textOverlay, SIGNAL(overlayHidden()), 0341 d->delegate, SLOT(reshowStatusMessageSlot())); 0342 0343 0344 connect(textOverlay, SIGNAL(activated(Tp::AccountPtr, Tp::ContactPtr)), 0345 this, SLOT(startTextChannel(Tp::AccountPtr, Tp::ContactPtr))); 0346 0347 connect(fileOverlay, SIGNAL(activated(Tp::AccountPtr, Tp::ContactPtr)), 0348 this, SLOT(startFileTransferChannel(Tp::AccountPtr, Tp::ContactPtr))); 0349 0350 connect(audioOverlay, SIGNAL(activated(Tp::AccountPtr, Tp::ContactPtr)), 0351 this, SLOT(startAudioChannel(Tp::AccountPtr, Tp::ContactPtr))); 0352 0353 connect(videoOverlay, SIGNAL(activated(Tp::AccountPtr, Tp::ContactPtr)), 0354 this, SLOT(startVideoChannel(Tp::AccountPtr, Tp::ContactPtr))); 0355 0356 connect(this, SIGNAL(enableOverlays(bool)), 0357 textOverlay, SLOT(setActive(bool))); 0358 0359 connect(this, SIGNAL(enableOverlays(bool)), 0360 audioOverlay, SLOT(setActive(bool))); 0361 0362 connect(this, SIGNAL(enableOverlays(bool)), 0363 videoOverlay, SLOT(setActive(bool))); 0364 0365 connect(this, SIGNAL(enableOverlays(bool)), 0366 fileOverlay, SLOT(setActive(bool))); 0367 } 0368 0369 void ContactListWidget::setGroupMode(KTp::ContactsModel::GroupMode groupMode) 0370 { 0371 Q_D(ContactListWidget); 0372 0373 d->model->setGroupMode(groupMode); 0374 //we want to draw branches for contacts, but not for headers like account names or group names 0375 //so we turn it on only when we are in no grouping mode 0376 if (groupMode == KTp::ContactsModel::NoGrouping) { 0377 setRootIsDecorated(true); 0378 } else { 0379 setRootIsDecorated(false); 0380 } 0381 } 0382 0383 void ContactListWidget::showGrouped() 0384 { 0385 toggleGroups(true); 0386 } 0387 0388 void ContactListWidget::showUngrouped() 0389 { 0390 toggleGroups(false); 0391 } 0392 0393 void ContactListWidget::toggleGroups(bool show) 0394 { 0395 Q_D(ContactListWidget); 0396 0397 if (show) { 0398 setGroupMode(KTp::ContactsModel::GroupGrouping); 0399 } else { 0400 if (KTp::kpeopleEnabled()) { 0401 setGroupMode(KTp::ContactsModel::NoGrouping); 0402 } else { 0403 setGroupMode(KTp::ContactsModel::AccountGrouping); 0404 } 0405 } 0406 d->groupMode = d->model->groupMode(); 0407 0408 for (int i = 0; i < d->model->rowCount(); i++) { 0409 onNewGroupModelItemsInserted(d->model->index(i, 0, QModelIndex()), 0, 0); 0410 } 0411 } 0412 0413 void ContactListWidget::toggleOfflineContacts(bool show) 0414 { 0415 Q_D(ContactListWidget); 0416 0417 d->showOffline = show; 0418 d->model->setPresenceTypeFilterFlags(show ? KTp::ContactsFilterModel::DoNotFilterByPresence : KTp::ContactsFilterModel::ShowOnlyConnected); 0419 } 0420 0421 void ContactListWidget::toggleSortByPresence(bool sort) 0422 { 0423 Q_D(ContactListWidget); 0424 0425 //typecast to int before passing to setSortRole to avoid false cpp warning about mixing enum types 0426 d->model->setSortRole(sort ? (int)KTp::ContactPresenceTypeRole : (int)Qt::DisplayRole); 0427 } 0428 0429 void ContactListWidget::startTextChannel(const Tp::AccountPtr &account, const Tp::ContactPtr &contact) 0430 { 0431 Tp::PendingOperation *op = KTp::Actions::startChat(account, contact, true); 0432 connect(op, SIGNAL(finished(Tp::PendingOperation*)), 0433 SIGNAL(genericOperationFinished(Tp::PendingOperation*))); 0434 0435 Q_EMIT actionStarted(); 0436 } 0437 0438 void ContactListWidget::startAudioChannel(const Tp::AccountPtr &account, const Tp::ContactPtr &contact) 0439 { 0440 Tp::PendingOperation *op = KTp::Actions::startAudioCall(account, contact); 0441 connect(op, SIGNAL(finished(Tp::PendingOperation*)), 0442 SIGNAL(genericOperationFinished(Tp::PendingOperation*))); 0443 0444 Q_EMIT actionStarted(); 0445 } 0446 0447 void ContactListWidget::startVideoChannel(const Tp::AccountPtr &account, const Tp::ContactPtr &contact) 0448 { 0449 Tp::PendingOperation *op = KTp::Actions::startAudioVideoCall(account, contact); 0450 connect(op, SIGNAL(finished(Tp::PendingOperation*)), 0451 SIGNAL(genericOperationFinished(Tp::PendingOperation*))); 0452 0453 Q_EMIT actionStarted(); 0454 } 0455 0456 void ContactListWidget::startDesktopSharing(const Tp::AccountPtr &account, const Tp::ContactPtr &contact) 0457 { 0458 Tp::PendingOperation *op = KTp::Actions::startDesktopSharing(account, contact); 0459 connect(op, SIGNAL(finished(Tp::PendingOperation*)), 0460 SIGNAL(genericOperationFinished(Tp::PendingOperation*))); 0461 0462 Q_EMIT actionStarted(); 0463 } 0464 0465 void ContactListWidget::startLogViewer(const Tp::AccountPtr &account, const Tp::ContactPtr &contact) 0466 { 0467 //log viewer is not a Tp handler so does not return a pending operation 0468 KTp::Actions::openLogViewer(account, contact); 0469 0470 Q_EMIT actionStarted(); 0471 } 0472 0473 void ContactListWidget::startFileTransferChannel(const Tp::AccountPtr &account, const Tp::ContactPtr &contact) 0474 { 0475 qCDebug(KTP_CONTACTLIST_MODULE) << "Requesting file transfer for contact" << contact->alias(); 0476 0477 QFileDialog *fileDialog = new QFileDialog(this, i18n("Choose files to send to %1", contact->alias()), QStringLiteral("kfiledialog:///FileTransferLastDirectory")); 0478 fileDialog->setLabelText(QFileDialog::Accept, i18n("Send")); 0479 fileDialog->exec(); 0480 QStringList filenames = fileDialog->selectedFiles(); 0481 fileDialog->deleteLater(); 0482 0483 if (filenames.isEmpty()) { // User hit cancel button 0484 return; 0485 } 0486 0487 requestFileTransferChannels(account, contact, filenames); 0488 } 0489 0490 void ContactListWidget::requestFileTransferChannels(const Tp::AccountPtr &account, 0491 const Tp::ContactPtr &contact, 0492 const QStringList &filenames) 0493 { 0494 Q_FOREACH (const QString &filename, filenames) { 0495 Tp::PendingOperation *op = KTp::Actions::startFileTransfer(account, contact, filename); 0496 connect(op, SIGNAL(finished(Tp::PendingOperation*)), 0497 SIGNAL(genericOperationFinished(Tp::PendingOperation*))); 0498 } 0499 0500 Q_EMIT actionStarted(); 0501 } 0502 0503 void ContactListWidget::onNewGroupModelItemsInserted(const QModelIndex& parentIndex, int start, int end) 0504 { 0505 Q_UNUSED(end); 0506 Q_D(ContactListWidget); 0507 0508 QModelIndex index; 0509 0510 //if the inserted item's parent is valid, it is probably the top-level item we want to expand/collapse 0511 if (parentIndex.isValid()) { 0512 index = parentIndex; 0513 } else { 0514 //if it is invalid, the inserted item may be a group added after a child, so we get the group index 0515 index = model()->index(start, 0, QModelIndex()); 0516 } 0517 0518 if (!index.isValid()) { 0519 return; 0520 } 0521 0522 //if there is no parent, we deal with top-level item that we want to expand/collapse, ie. group or account 0523 if (!index.parent().isValid()) { 0524 0525 //we're probably dealing with group item, so let's check if it is expanded first 0526 if (!isExpanded(index)) { 0527 //if it's not expanded, check the config if we should expand it or not 0528 QString groupId = index.data(KTp::IdRole).toString(); 0529 if (d->groupStates.value(groupId)) { 0530 expand(index); 0531 } 0532 } 0533 } 0534 } 0535 0536 void ContactListWidget::onSwitchToFullView() 0537 { 0538 Q_D(ContactListWidget); 0539 0540 setItemDelegate(d->delegate); 0541 doItemsLayout(); 0542 0543 emit enableOverlays(true); 0544 0545 KSharedConfigPtr config = KSharedConfig::openConfig(); 0546 KConfigGroup guiConfigGroup(config, "GUI"); 0547 guiConfigGroup.writeEntry("selected_delegate", "full"); 0548 guiConfigGroup.config()->sync(); 0549 } 0550 0551 void ContactListWidget::onSwitchToCompactView() 0552 { 0553 Q_D(ContactListWidget); 0554 0555 setItemDelegate(d->compactDelegate); 0556 d->compactDelegate->setListMode(ContactDelegateCompact::Normal); 0557 doItemsLayout(); 0558 0559 emit enableOverlays(false); 0560 0561 KSharedConfigPtr config = KSharedConfig::openConfig(); 0562 KConfigGroup guiConfigGroup(config, "GUI"); 0563 guiConfigGroup.writeEntry("selected_delegate", "normal"); 0564 guiConfigGroup.config()->sync(); 0565 } 0566 0567 void ContactListWidget::onSwitchToMiniView() 0568 { 0569 Q_D(ContactListWidget); 0570 0571 setItemDelegate(d->compactDelegate); 0572 d->compactDelegate->setListMode(ContactDelegateCompact::Mini);; 0573 doItemsLayout(); 0574 0575 emit enableOverlays(false); 0576 0577 KSharedConfigPtr config = KSharedConfig::openConfig(); 0578 KConfigGroup guiConfigGroup(config, "GUI"); 0579 guiConfigGroup.writeEntry("selected_delegate", "mini"); 0580 guiConfigGroup.config()->sync(); 0581 } 0582 0583 void ContactListWidget::onShowAllContacts() 0584 { 0585 Q_D(ContactListWidget); 0586 0587 d->model->setSubscriptionStateFilterFlags(KTp::ContactsFilterModel::DoNotFilterBySubscription); 0588 0589 KSharedConfigPtr config = KSharedConfig::openConfig(); 0590 KConfigGroup guiConfigGroup(config, "GUI"); 0591 guiConfigGroup.writeEntry("shown_contacts", "all"); 0592 guiConfigGroup.config()->sync(); 0593 } 0594 0595 void ContactListWidget::onShowUnblockedContacts() 0596 { 0597 Q_D(ContactListWidget); 0598 0599 d->model->setSubscriptionStateFilterFlags(KTp::ContactsFilterModel::HideBlocked); 0600 0601 KSharedConfigPtr config = KSharedConfig::openConfig(); 0602 KConfigGroup guiConfigGroup(config, "GUI"); 0603 guiConfigGroup.writeEntry("shown_contacts", "unblocked"); 0604 guiConfigGroup.config()->sync(); 0605 } 0606 0607 void ContactListWidget::onShowBlockedContacts() 0608 { 0609 Q_D(ContactListWidget); 0610 0611 d->model->setSubscriptionStateFilterFlags(KTp::ContactsFilterModel::ShowOnlyBlocked); 0612 0613 KSharedConfigPtr config = KSharedConfig::openConfig(); 0614 KConfigGroup guiConfigGroup(config, "GUI"); 0615 guiConfigGroup.writeEntry("shown_contacts", "blocked"); 0616 guiConfigGroup.config()->sync(); 0617 } 0618 0619 void ContactListWidget::setFilterString(const QString& string) 0620 { 0621 Q_D(ContactListWidget); 0622 0623 if (string.isEmpty()) { 0624 setGroupMode(d->groupMode); 0625 } else { 0626 setGroupMode(KTp::ContactsModel::NoGrouping); 0627 } 0628 0629 d->model->setPresenceTypeFilterFlags(string.isEmpty() && !d->showOffline ? KTp::ContactsFilterModel::ShowOnlyConnected : KTp::ContactsFilterModel::DoNotFilterByPresence); 0630 d->model->setGlobalFilterString(string); 0631 } 0632 0633 void ContactListWidget::setDropIndicatorRect(const QRect &rect) 0634 { 0635 Q_D(ContactListWidget); 0636 0637 if (d->dropIndicatorRect != rect) { 0638 d->dropIndicatorRect = rect; 0639 viewport()->update(); 0640 } 0641 } 0642 0643 bool ContactListWidget::event(QEvent *event) 0644 { 0645 Q_D(ContactListWidget); 0646 if (event->type() == QEvent::Leave && d->delegate) { 0647 d->delegate->reshowStatusMessageSlot(); 0648 return true; 0649 } 0650 0651 return QTreeView::event(event); 0652 } 0653 0654 void ContactListWidget::keyPressEvent(QKeyEvent *event) 0655 { 0656 //this would be normally handled by activated() signal but since we decided 0657 //we don't want people starting chats using single click, we can't use activated() 0658 //and have to do it ourselves, therefore this. Change only after discussing with the team! 0659 if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) { 0660 //start the chat only if the index is valid and index is a valid contact or person 0661 if (currentIndex().isValid() && 0662 (currentIndex().data(KTp::RowTypeRole).toInt() == KTp::ContactRowType 0663 || currentIndex().data(KTp::RowTypeRole).toInt() == KTp::PersonRowType)) { 0664 onContactListDoubleClicked(currentIndex()); 0665 } 0666 } 0667 0668 QTreeView::keyPressEvent(event); 0669 } 0670 0671 void ContactListWidget::mousePressEvent(QMouseEvent *event) 0672 { 0673 Q_D(ContactListWidget); 0674 0675 QTreeView::mousePressEvent(event); 0676 0677 const QModelIndex index = indexAt(event->pos()); 0678 d->shouldDrag = false; 0679 d->dragSourceGroup.clear(); 0680 0681 // if no contact or person, no drag 0682 int type = index.data(KTp::RowTypeRole).toInt(); 0683 if (type != KTp::ContactRowType && type != KTp::PersonRowType ) { 0684 return; 0685 } 0686 0687 if (event->button() == Qt::LeftButton) { 0688 d->shouldDrag = true; 0689 d->dragStartPosition = event->pos(); 0690 } 0691 } 0692 0693 void ContactListWidget::mouseMoveEvent(QMouseEvent *event) 0694 { 0695 Q_D(ContactListWidget); 0696 0697 QTreeView::mouseMoveEvent(event); 0698 0699 const QModelIndex index = indexAt(event->pos()); 0700 0701 if (!(event->buttons() & Qt::LeftButton)) { 0702 return; 0703 } 0704 0705 if (!d->shouldDrag) { 0706 return; 0707 } 0708 0709 if ((event->pos() - d->dragStartPosition).manhattanLength() < QApplication::startDragDistance()) { 0710 return; 0711 } 0712 0713 QMimeData *mimeData = new QMimeData; 0714 QByteArray encodedData; 0715 QDataStream stream(&encodedData, QIODevice::WriteOnly); 0716 0717 if (index.isValid()) { 0718 Tp::ContactPtr contact = index.data(KTp::ContactRole).value<KTp::ContactPtr>(); 0719 Tp::AccountPtr account = index.data(KTp::AccountRole).value<Tp::AccountPtr>(); 0720 0721 if (account && contact) { 0722 //We put a contact ID and its account ID to the stream, so we can later recreate the contact using ContactsModel 0723 stream << contact->id() << account->objectPath(); 0724 0725 //Store source group name so that we can remove the contact from it on move-drop */ 0726 d->dragSourceGroup = index.parent().data(KTp::IdRole).toString(); 0727 } 0728 } 0729 0730 mimeData->setData("application/vnd.telepathy.contact", encodedData); 0731 0732 qCDebug(KTP_CONTACTLIST_MODULE) << index.data(KTp::PersonIdRole).toString().toLatin1(); 0733 0734 mimeData->setData("application/vnd.kpeople.uri", index.data(KTp::PersonIdRole).toString().toLatin1()); 0735 0736 QPixmap dragIndicator = QPixmap::grabWidget(this, visualRect(index).adjusted(3,3,3,3)); 0737 0738 QDrag *drag = new QDrag(this); 0739 drag->setMimeData(mimeData); 0740 drag->setPixmap(dragIndicator); 0741 0742 Qt::DropActions actions; 0743 if (event->modifiers() & Qt::ShiftModifier) { 0744 actions = Qt::MoveAction; 0745 } else if (event->modifiers() & Qt::ControlModifier) { 0746 actions = Qt::CopyAction; 0747 } else { 0748 actions = Qt::MoveAction | Qt::CopyAction; 0749 } 0750 drag->exec(actions); 0751 } 0752 0753 void ContactListWidget::dropEvent(QDropEvent *event) 0754 { 0755 Q_D(ContactListWidget); 0756 0757 const QModelIndex index = indexAt(event->pos()); 0758 0759 if (!index.isValid()) { 0760 return; 0761 } 0762 0763 if (event->mimeData()->hasUrls()) { 0764 qCDebug(KTP_CONTACTLIST_MODULE) << "Filed dropped"; 0765 0766 Tp::ContactPtr contact = index.data(KTp::ContactRole).value<KTp::ContactPtr>(); 0767 Tp::AccountPtr account = index.data(KTp::AccountRole).value<Tp::AccountPtr>(); 0768 0769 QStringList filenames; 0770 Q_FOREACH (const QUrl &url, event->mimeData()->urls()) { 0771 filenames << url.toLocalFile(); 0772 } 0773 0774 if (account && contact && !filenames.isEmpty()) { 0775 qCDebug(KTP_CONTACTLIST_MODULE) << "Requesting file transfer for contact" << contact->alias(); 0776 requestFileTransferChannels(account, contact, filenames); 0777 event->acceptProposedAction(); 0778 } 0779 } else if ((index.data(KTp::RowTypeRole).toInt() == KTp::ContactRowType || index.data(KTp::RowTypeRole).toInt() == KTp::PersonRowType) && 0780 event->mimeData()->hasFormat("application/vnd.kpeople.uri") && KTp::kpeopleEnabled()) { 0781 #ifdef HAVE_KPEOPLE 0782 QString droppedUri(index.data(KTp::PersonIdRole).toString()); 0783 QString draggedUri(event->mimeData()->data("application/vnd.kpeople.uri")); 0784 if(droppedUri != draggedUri) { 0785 QMenu menu; 0786 QAction *mergeAction = menu.addAction(i18n("Merge contacts")); 0787 QAction *result = menu.exec(mapToGlobal(event->pos())); 0788 if (result == mergeAction) { 0789 KPeople::mergeContacts(QStringList() << droppedUri << draggedUri); 0790 } 0791 event->acceptProposedAction(); 0792 } 0793 #endif 0794 } else if (event->mimeData()->hasFormat("application/vnd.telepathy.contact")) { 0795 qCDebug(KTP_CONTACTLIST_MODULE) << "Contact dropped"; 0796 0797 QByteArray encodedData = event->mimeData()->data("application/vnd.telepathy.contact"); 0798 QDataStream stream(&encodedData, QIODevice::ReadOnly); 0799 QList<Tp::ContactPtr> contacts; 0800 0801 while (!stream.atEnd()) { 0802 QString contactId; 0803 QString accountId; 0804 0805 //get contact and account out of the stream 0806 stream >> contactId >> accountId; 0807 0808 Tp::AccountPtr account = d->accountManager->accountForObjectPath(accountId); 0809 0810 if (!account->connection()) { 0811 continue; 0812 } 0813 0814 Q_FOREACH(const Tp::ContactPtr &contact, account->connection()->contactManager()->allKnownContacts()) { 0815 if (contact->id() == contactId) { 0816 contacts.append(contact); 0817 } 0818 } 0819 } 0820 0821 Qt::DropAction action = Qt::IgnoreAction; 0822 if ((event->possibleActions() & Qt::CopyAction) && 0823 (event->possibleActions() & Qt::MoveAction)) { 0824 0825 QMenu menu; 0826 QString seq = QKeySequence(Qt::ShiftModifier).toString(); 0827 seq.chop(1); 0828 QAction *move = menu.addAction(QIcon::fromTheme("go-jump"), i18n("&Move here") + QLatin1Char('\t') + seq); 0829 0830 seq = QKeySequence(Qt::ControlModifier).toString(); 0831 seq.chop(1); 0832 QAction *copy = menu.addAction(QIcon::fromTheme("edit-copy"), i18n("&Copy here") + QLatin1Char('\t') + seq); 0833 0834 menu.addSeparator(); 0835 seq = QKeySequence(Qt::Key_Escape).toString(); 0836 menu.addAction(QIcon::fromTheme("process-stop"), i18n("C&ancel") + QLatin1Char('\t') + seq); 0837 0838 QAction *result = menu.exec(mapToGlobal(event->pos())); 0839 0840 if (result == move) { 0841 action = Qt::MoveAction; 0842 } else if (result == copy) { 0843 action = Qt::CopyAction; 0844 } 0845 } else if (event->possibleActions() & Qt::MoveAction) { 0846 action = Qt::MoveAction; 0847 } else if (event->possibleActions() & Qt::CopyAction) { 0848 action = Qt::CopyAction; 0849 } 0850 0851 Q_FOREACH(const Tp::ContactPtr &contact, contacts) { 0852 QString targetGroup; 0853 0854 if (action == Qt::IgnoreAction) { 0855 continue; 0856 } 0857 0858 if (d->model->groupMode() != KTp::ContactsModel::GroupGrouping) { 0859 // If contacts grouping is disabled, dropping inside the contact list makes no sense. 0860 continue; 0861 } 0862 0863 if (index.data(KTp::RowTypeRole).toInt() == KTp::GroupRowType) { 0864 // contact is dropped on a group, so take it's name 0865 targetGroup = index.data(KTp::IdRole).toString(); 0866 } else if (index.data(KTp::RowTypeRole).toInt() == KTp::ContactRowType) { 0867 // contact is dropped on another contact, so take it's parents (group) name 0868 targetGroup = index.parent().data(KTp::IdRole).toString(); 0869 } 0870 0871 if (targetGroup.isEmpty() || (targetGroup == QLatin1String("_unsorted")) || 0872 contact->groups().contains(targetGroup)) { 0873 continue; 0874 } 0875 0876 qCDebug(KTP_CONTACTLIST_MODULE) << contact->alias() << "added to group" << targetGroup; 0877 0878 if (action == Qt::MoveAction) { 0879 Tp::PendingOperation *rmOp = contact->removeFromGroup(d->dragSourceGroup); 0880 connect(rmOp, SIGNAL(finished(Tp::PendingOperation*)), 0881 this, SIGNAL(genericOperationFinished(Tp::PendingOperation*))); 0882 } 0883 0884 Tp::PendingOperation *addOp = contact->addToGroup(targetGroup); 0885 connect(addOp, SIGNAL(finished(Tp::PendingOperation*)), 0886 this, SIGNAL(genericOperationFinished(Tp::PendingOperation*))); 0887 } 0888 d->dragSourceGroup.clear(); 0889 0890 event->acceptProposedAction(); 0891 0892 } else { 0893 event->ignore(); 0894 } 0895 0896 setDropIndicatorRect(QRect()); 0897 } 0898 0899 void ContactListWidget::dragEnterEvent(QDragEnterEvent *event) 0900 { 0901 if (event->mimeData()->hasUrls()) { 0902 bool accepted = true; 0903 // check if one of the urls isn't a local file and abort if so 0904 Q_FOREACH (const QUrl& url, event->mimeData()->urls()) { 0905 if (!QFileInfo(url.toLocalFile()).isFile()) { 0906 accepted = false; 0907 } 0908 } 0909 0910 if (accepted) { 0911 event->acceptProposedAction(); 0912 } else { 0913 event->ignore(); 0914 } 0915 } else if (event->mimeData()->hasFormat("application/vnd.telepathy.contact")) { 0916 event->acceptProposedAction(); 0917 } else { 0918 event->ignore(); 0919 } 0920 } 0921 0922 void ContactListWidget::dragMoveEvent(QDragMoveEvent *event) 0923 { 0924 const QModelIndex index = indexAt(event->pos()); 0925 setDropIndicatorRect(QRect()); 0926 0927 QAbstractItemView::dragMoveEvent(event); 0928 0929 // urls can be dropped on a contact with file transfer capability, 0930 // contacts can be dropped either on a group or on another contact if GroupsModel is used 0931 if (event->mimeData()->hasUrls() && index.data(KTp::ContactCanFileTransferRole).toBool()) { 0932 event->acceptProposedAction(); 0933 setDropIndicatorRect(visualRect(index)); 0934 } else if (event->mimeData()->hasFormat("application/vnd.telepathy.contact")) { 0935 event->acceptProposedAction(); 0936 setDropIndicatorRect(visualRect(index)); 0937 } else if (event->mimeData()->hasFormat("application/vnd.kpeople.uri")) { 0938 event->acceptProposedAction(); 0939 setDropIndicatorRect(visualRect(index)); 0940 } else { 0941 event->ignore(); 0942 } 0943 } 0944 0945 void ContactListWidget::dragLeaveEvent(QDragLeaveEvent *event) 0946 { 0947 Q_UNUSED(event); 0948 setDropIndicatorRect(QRect()); 0949 } 0950 0951 void ContactListWidget::paintEvent(QPaintEvent *event) 0952 { 0953 Q_D(ContactListWidget); 0954 0955 QTreeView::paintEvent(event); 0956 if (!d->dropIndicatorRect.isNull()) { 0957 QStyleOption option; 0958 option.init(this); 0959 option.rect = d->dropIndicatorRect.adjusted(0,0,-1,-1); 0960 QPainter painter(viewport()); 0961 style()->drawPrimitive(QStyle::PE_IndicatorItemViewItemDrop, &option, &painter, this); 0962 } 0963 } 0964 0965 void ContactListWidget::drawBranches(QPainter *painter, const QRect &rect, const QModelIndex &index) const 0966 { 0967 if (indentation() > 0) { 0968 if (model()->rowCount(index) > 1) { 0969 QTreeView::drawBranches(painter, rect, index); 0970 } 0971 } 0972 0973 //if no indentation (non kpeople mode) do nothing 0974 // There is a 0px identation set in the constructor, with setIndentation(0). 0975 // Because of that, no branches are shown, so they should be disabled completely (overriding drawBranches). 0976 // Leaving branches enabled with 0px identation results in a 1px branch line on the left of all items, 0977 // which looks like an artifact. 0978 //See https://bugreports.qt-project.org/browse/QTBUG-26305 0979 } 0980 0981 void ContactListWidget::loadGroupStatesFromConfig() 0982 { 0983 Q_D(ContactListWidget); 0984 d->groupStates.clear(); 0985 0986 KConfig config(QLatin1String("ktelepathyrc")); 0987 KConfigGroup groupsConfig = config.group("GroupsState"); 0988 0989 Q_FOREACH(const QString &key, groupsConfig.keyList()) { 0990 bool expanded = groupsConfig.readEntry(key, false); 0991 d->groupStates.insert(key, expanded); 0992 } 0993 }