File indexing completed on 2022-11-29 14:16:03

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 }