contactlist.cpp 12.1 KB
Newer Older
1 2
/*
 *   This file is part of Dianara
JanKusanagi's avatar
JanKusanagi committed
3
 *   Copyright 2012-2019  JanKusanagi JRR <jancoding@gmx.com>
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA .
 */

#include "contactlist.h"

ContactList::ContactList(PumpController *pumpController,
24
                         GlobalObject *globalObject,
25
                         QString listType,
26 27
                         QWidget *parent) : QWidget(parent)
{
JanKusanagi's avatar
JanKusanagi committed
28 29
    m_pumpController = pumpController;
    m_globalObject = globalObject;
30

31

32
    // Need action defined before its target lineedit, to show localized shortcut
JanKusanagi's avatar
JanKusanagi committed
33 34
    m_focusFilterAction = new QAction(this);
    m_focusFilterAction->setShortcut(QKeySequence("Ctrl+F"));
35

36
    QString filterNote = tr("Type a partial name or ID to find a contact...")
37
                         + " ("
JanKusanagi's avatar
JanKusanagi committed
38 39
                         + m_focusFilterAction->shortcut()
                                               .toString(QKeySequence::NativeText)
40 41
                         + ")";

JanKusanagi's avatar
JanKusanagi committed
42 43 44 45 46
    m_filterLineEdit = new QLineEdit(this);
    m_filterLineEdit->setPlaceholderText(filterNote);
    m_filterLineEdit->setToolTip("<b></b>" + filterNote); // HTMLized for wordwrapping
    m_filterLineEdit->setClearButtonEnabled(true);
    connect(m_filterLineEdit, &QLineEdit::textChanged,
JanKusanagi's avatar
JanKusanagi committed
47
            this, &ContactList::filterList);
48

JanKusanagi's avatar
JanKusanagi committed
49
    connect(m_focusFilterAction, &QAction::triggered,
50
            this, &ContactList::focusFilterField);
JanKusanagi's avatar
JanKusanagi committed
51
    this->addAction(m_focusFilterAction);
52

53

JanKusanagi's avatar
JanKusanagi committed
54 55 56 57 58 59
    const int iconSize = m_filterLineEdit->sizeHint().height(); // filterLineEdit->font().pixelSize()?
    m_filterIcon = new QLabel(this);
    m_filterIcon->setAlignment(Qt::AlignCenter);
    m_filterIcon->setPixmap(QIcon::fromTheme("edit-find",
                                             QIcon(":/images/menu-find.png"))
                                  .pixmap(iconSize, iconSize));
60 61


JanKusanagi's avatar
JanKusanagi committed
62 63
    m_matchesCountLabel = new QLabel(this);
    m_matchesCountLabel->hide();
64 65


JanKusanagi's avatar
JanKusanagi committed
66 67 68 69 70 71
    m_clearFilterButton = new QPushButton(QIcon::fromTheme("view-list-icons"),
                                          tr("F&ull List"),
                                          this);
    m_clearFilterButton->setDisabled(true); // Disabled initially, until a search happens
    connect(m_clearFilterButton, &QAbstractButton::clicked,
            m_filterLineEdit, &QLineEdit::clear);
72 73


74

75
    // Layout
JanKusanagi's avatar
JanKusanagi committed
76
    m_contactsLayout = new QVBoxLayout();
77

JanKusanagi's avatar
JanKusanagi committed
78 79
    m_contactsWidget = new QWidget(this);
    m_contactsWidget->setLayout(m_contactsLayout);
80

JanKusanagi's avatar
JanKusanagi committed
81 82 83 84 85
    m_contactsScrollArea = new QScrollArea(this);
    m_contactsScrollArea->setWidget(m_contactsWidget);
    m_contactsScrollArea->setWidgetResizable(true);
    m_contactsScrollArea->setFrameStyle(QFrame::NoFrame);
    m_contactsScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
86 87


JanKusanagi's avatar
JanKusanagi committed
88 89 90 91 92
    m_filterLayout = new QHBoxLayout();
    m_filterLayout->addWidget(m_filterIcon, 0, Qt::AlignVCenter);
    m_filterLayout->addWidget(m_filterLineEdit);
    m_filterLayout->addWidget(m_matchesCountLabel);
    m_filterLayout->addWidget(m_clearFilterButton);
93

JanKusanagi's avatar
JanKusanagi committed
94 95 96 97
    m_mainLayout = new QVBoxLayout();
    m_mainLayout->addWidget(m_contactsScrollArea);
    m_mainLayout->addLayout(m_filterLayout);
    this->setLayout(m_mainLayout);
98

99 100 101 102 103 104

    // Add demo contacts
    QVariantMap demoContactData;
    QVariantMap demoContactHometown;
    QVariantMap demoContactFollowed;

JanKusanagi's avatar
JanKusanagi committed
105
    m_isFollowing = false;
106 107
    if (listType == "following")
    {
JanKusanagi's avatar
JanKusanagi committed
108
        m_isFollowing = true;
109

110 111
        demoContactData.insert("displayName",  "Demo Contact");
        demoContactData.insert("id",           "democontact@pumpserver.org");
112
        demoContactData.insert("url",          "https://jancoding.wordpress.com");
113 114 115 116 117 118
        demoContactFollowed.insert("followed", "true");
    }
    else
    {
        demoContactData.insert("displayName",  "Demo Follower");
        demoContactData.insert("id",           "demofollower@pumpserver.org");
119
        demoContactData.insert("url",          "http://dianara.nongnu.org");
120 121 122
        demoContactFollowed.insert("followed", "false");
    }

123 124 125 126
    demoContactHometown.insert("displayName",  "Some city");
    demoContactData.insert("location", demoContactHometown);

    demoContactData.insert("pump_io",  demoContactFollowed);
127

128 129
    demoContactData.insert("published", "2013-05-01T00:00:00Z"); // Dianara's birthday

130

131
    ASPerson *demoContactPerson = new ASPerson(demoContactData, this);
JanKusanagi's avatar
JanKusanagi committed
132 133
    ContactCard *demoContactCard = new ContactCard(m_pumpController,
                                                   m_globalObject,
JanKusanagi's avatar
JanKusanagi committed
134 135
                                                   demoContactPerson,
                                                   this);
JanKusanagi's avatar
JanKusanagi committed
136 137
    m_contactsLayout->addWidget(demoContactCard);
    m_contactsInList.append(demoContactCard);
JanKusanagi's avatar
JanKusanagi committed
138
    delete demoContactPerson;
139

140 141 142 143 144 145 146
    qDebug() << "ContactList created";
}

ContactList::~ContactList()
{
    qDebug() << "ContactList destroyed";
}
147 148 149 150 151



void ContactList::clearListContents()
{
JanKusanagi's avatar
JanKusanagi committed
152
    foreach (ContactCard *card, m_contactsInList)
153
    {
JanKusanagi's avatar
JanKusanagi committed
154
        m_contactsLayout->removeWidget(card);
155 156
        delete card;
    }
JanKusanagi's avatar
JanKusanagi committed
157
    m_contactsInList.clear();
158

JanKusanagi's avatar
JanKusanagi committed
159
    if (m_isFollowing)
160
    {
JanKusanagi's avatar
JanKusanagi committed
161
        m_globalObject->clearNickCompletionModel();
162
    }
163 164 165
}


JanKusanagi's avatar
JanKusanagi committed
166
void ContactList::setListContents(QVariantList contactList)
167 168 169 170 171
{
    qDebug() << "ContactList; Setting list contents";

    QStringList followingIdStringList;

172
    foreach (const QVariant contact, contactList)
173
    {
174
        QVariantMap contactMap = contact.toMap();
JanKusanagi's avatar
JanKusanagi committed
175
        if (!contactMap.keys().contains(QStringLiteral("id")))
176 177 178 179 180 181 182
        {
            /*
             * Temporary hack to fix user profiles when the list comes
             * from the site user list, which is currently empty,
             * and contains ID only inside the profile property
             *
             */
183

JanKusanagi's avatar
JanKusanagi committed
184 185
            QString id = ASPerson::cleanupId(contactMap.value("profile").toMap()
                                                       .value("id").toString());
186 187 188
            contactMap.insert("id", id);

            QVariantMap followedMap;
JanKusanagi's avatar
JanKusanagi committed
189
            followedMap.insert("followed", m_pumpController->userInFollowing(id));
190 191
            contactMap.insert("pump_io", followedMap);

JanKusanagi's avatar
JanKusanagi committed
192
            id.remove("@" + m_pumpController->currentServerDomain());
JanKusanagi's avatar
JanKusanagi committed
193 194
            contactMap.insert("displayName", id);

JanKusanagi's avatar
JanKusanagi committed
195 196
            contactMap.insert("url", m_pumpController->currentServerScheme()
                                     + m_pumpController->currentServerDomain()
JanKusanagi's avatar
JanKusanagi committed
197 198 199
                                     + "/" + id);

            QVariantMap hrefMap;
JanKusanagi's avatar
JanKusanagi committed
200 201
            hrefMap.insert("href", m_pumpController->currentServerScheme()
                                   + m_pumpController->currentServerDomain()
JanKusanagi's avatar
JanKusanagi committed
202 203 204 205
                                   + "/api/user/" + id + "/feed");
            QVariantMap outboxMap;
            outboxMap.insert("activity-outbox", hrefMap);
            contactMap.insert("links", outboxMap);
206
        }
207 208


209
        ASPerson *person = new ASPerson(contactMap, this);
JanKusanagi's avatar
JanKusanagi committed
210 211
        ContactCard *contactCard = new ContactCard(m_pumpController,
                                                   m_globalObject,
212 213
                                                   person,
                                                   this);
JanKusanagi's avatar
JanKusanagi committed
214 215
        m_contactsLayout->addWidget(contactCard);
        m_contactsInList.append(contactCard);
216

JanKusanagi's avatar
JanKusanagi committed
217
        if (m_isFollowing)
218
        {
219
            // Add to internal following list for PumpController
220
            followingIdStringList.append(person->getId());
221 222

            // Add also to GlobalObject's model for nick completion
JanKusanagi's avatar
JanKusanagi committed
223 224 225
            m_globalObject->addToNickCompletionModel(person->getId(),
                                                     person->getNameWithFallback(),
                                                     person->getUrl());
226
        }
JanKusanagi's avatar
JanKusanagi committed
227 228

        delete person;
JanKusanagi's avatar
JanKusanagi committed
229
    } ///// End foreach
230 231 232


    // Batch of contacts added to list, add them also to the internal list
JanKusanagi's avatar
JanKusanagi committed
233
    if (m_isFollowing)
234
    {
JanKusanagi's avatar
JanKusanagi committed
235
        m_pumpController->updateInternalFollowingIdList(followingIdStringList);
236
    }
237

JanKusanagi's avatar
JanKusanagi committed
238
    m_filterLineEdit->clear(); // Unfilter the list
239 240 241 242
}



243
QString ContactList::getContactsStringForExport()
244
{
245 246 247 248 249 250 251 252
    QString allContactsString;

    foreach (ContactCard *card, m_contactsInList)
    {
        allContactsString.append(card->getNameAndIdString() + "\n");
    }

    return allContactsString;
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
}



/*****************************************************************************/
/*********************************** SLOTS ***********************************/
/*****************************************************************************/


void ContactList::filterList(QString filterText)
{
    qDebug() << "Filtering for contacts matching:" << filterText;

    if (!filterText.isEmpty())
    {
268
        int matchCount = 0;
JanKusanagi's avatar
JanKusanagi committed
269
        foreach (ContactCard *card, m_contactsInList)
270 271 272 273
        {
            if (card->getNameAndIdString().contains(filterText, Qt::CaseInsensitive))
            {
                card->show();
274
                ++matchCount;
275 276 277 278 279 280
            }
            else
            {
                card->hide();
            }
        }
281

JanKusanagi's avatar
JanKusanagi committed
282 283 284 285 286
        m_matchesCountLabel->setText(QString("(%1)")
                                     .arg(QLocale::system()
                                                  .toString(matchCount)));
        m_matchesCountLabel->show();
        m_clearFilterButton->setEnabled(true);
287 288 289
    }
    else // If no filter at all, more optimized version showing all
    {
JanKusanagi's avatar
JanKusanagi committed
290
        foreach (ContactCard *card, m_contactsInList)
291 292 293
        {
            card->show();
        }
294

JanKusanagi's avatar
JanKusanagi committed
295 296 297
        m_matchesCountLabel->setText(QString());
        m_matchesCountLabel->hide();
        m_clearFilterButton->setDisabled(true);
298 299 300 301
    }
}


302 303
void ContactList::addSingleContact(ASPerson *contact)
{
JanKusanagi's avatar
JanKusanagi committed
304 305
    ContactCard *card = new ContactCard(m_pumpController,
                                        m_globalObject,
306 307 308
                                        contact,
                                        this);

JanKusanagi's avatar
JanKusanagi committed
309 310
    m_contactsLayout->insertWidget(0, card);
    m_contactsInList.append(card);
311

312 313
    emit contactCountChanged(1);

314
    // This check is actually unnecessary, since this slot is only called
JanKusanagi's avatar
JanKusanagi committed
315
    if (m_isFollowing) // for the 'following' list
316
    {
JanKusanagi's avatar
JanKusanagi committed
317
        QStringList contactsToAdd = QStringList{ contact->getId() };
JanKusanagi's avatar
JanKusanagi committed
318
        m_pumpController->updateInternalFollowingIdList(contactsToAdd);
319 320

        // Add also to GlobalObject's model for nick completion
JanKusanagi's avatar
JanKusanagi committed
321 322 323
        m_globalObject->addToNickCompletionModel(contact->getId(),
                                                 contact->getNameWithFallback(),
                                                 contact->getUrl());
324
    }
JanKusanagi's avatar
JanKusanagi committed
325 326 327

    // Other slots use this object, but this should be safe
    contact->deleteLater();
328 329 330 331 332
}


void ContactList::removeSingleContact(ASPerson *contact)
{
JanKusanagi's avatar
JanKusanagi committed
333
    foreach (ContactCard *card, m_contactsInList)
334
    {
JanKusanagi's avatar
JanKusanagi committed
335 336 337 338 339 340 341
        /* Ignore disabled cards, to avoid substracting more than once, in case
         * there were more ContactCards for the same contact, from following
         * and unfollowing previously.
         *
         */
        if (card->isEnabled() &&
            card->getId() == contact->getId())
342
        {
343 344
            emit contactCountChanged(-1);

JanKusanagi's avatar
JanKusanagi committed
345
            m_pumpController->removeFromInternalFollowingList(contact->getId());
346

347
            // Remove from GlobalObject's model for nick completion, too
JanKusanagi's avatar
JanKusanagi committed
348
            m_globalObject->removeFromNickCompletionModel(contact->getId());
349

350 351 352 353 354 355
            card->setDisabled(true);
        }
    }

    contact->deleteLater();
}
356 357 358 359 360 361 362 363 364


/*
 * When pressing Ctrl+F, focus filter field,
 * or select all text if already focused
 *
 */
void ContactList::focusFilterField()
{
JanKusanagi's avatar
JanKusanagi committed
365
    if (m_filterLineEdit->hasFocus())
366
    {
JanKusanagi's avatar
JanKusanagi committed
367
        m_filterLineEdit->selectAll();
368 369 370
    }
    else
    {
JanKusanagi's avatar
JanKusanagi committed
371
        m_filterLineEdit->setFocus();
372 373
    }
}