ContactsListProxyModel.cpp 5.01 KB
Newer Older
1 2
/*
 * ContactsListProxyModel.cpp
3
 * Copyright (C) 2017-2018  Belledonne Communications, Grenoble, France
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * 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.
 *
 *  Created on: February 2, 2017
 *      Author: Ronan Abhamon
 */

Ronan's avatar
Ronan committed
23 24
#include <cmath>

25 26 27 28
#include "components/contact/ContactModel.hpp"
#include "components/contact/VcardModel.hpp"
#include "components/core/CoreManager.hpp"
#include "utils/Utils.hpp"
29

30
#include "ContactsListModel.hpp"
31 32
#include "ContactsListProxyModel.hpp"

33
// =============================================================================
Ronan's avatar
Ronan committed
34

35 36
using namespace std;

37 38 39 40 41 42 43 44 45 46
namespace {
  constexpr float UsernameWeight = 50.f;
  constexpr float SipAddressWeight = 50.f;

  constexpr float FactorPos0 = 1.0f;
  constexpr float FactorPos1 = 0.9f;
  constexpr float FactorPos2 = 0.8f;
  constexpr float FactorPos3 = 0.7f;
  constexpr float FactorPosOther = 0.6f;
}
47

48 49 50 51 52 53 54 55
// Notes:
//
// - First `^` is necessary to search two words with one separator
// between them like `Claire Manning`.
//
// - [^_.-;@ ] is used to search patterns which starts with
// a separator like ` word`.
//
Ronan's avatar
Ronan committed
56
// - [_.-;@ ] is the main pattern (a separator).
57
const QRegExp ContactsListProxyModel::SearchSeparators("^[^_.-;@ ][_.-;@ ]");
58

Ronan's avatar
Ronan committed
59
// -----------------------------------------------------------------------------
60

61
ContactsListProxyModel::ContactsListProxyModel (QObject *parent) : QSortFilterProxyModel(parent) {
Ronan's avatar
Ronan committed
62
  setSourceModel(CoreManager::getInstance()->getContactsListModel());
63 64 65
  sort(0);
}

Ronan's avatar
Ronan committed
66 67 68
// -----------------------------------------------------------------------------

void ContactsListProxyModel::setFilter (const QString &pattern) {
69
  mFilter = pattern;
Ronan's avatar
Ronan committed
70 71 72 73 74
  invalidate();
}

// -----------------------------------------------------------------------------

75
bool ContactsListProxyModel::filterAcceptsRow (
76 77
  int sourceRow,
  const QModelIndex &sourceParent
78
) const {
79
  const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
Ronan's avatar
Ronan committed
80
  const ContactModel *contact = index.data().value<ContactModel *>();
81

82
  mWeights[contact] = uint(round(computeContactWeight(contact)));
83

84 85
  return mWeights[contact] > 0 && (
    !mUseConnectedFilter ||
86 87
    contact->getPresenceLevel() != Presence::PresenceLevel::White
  );
88 89 90
}

bool ContactsListProxyModel::lessThan (const QModelIndex &left, const QModelIndex &right) const {
91 92
  const ContactModel *contactA = sourceModel()->data(left).value<ContactModel *>();
  const ContactModel *contactB = sourceModel()->data(right).value<ContactModel *>();
93

94 95
  unsigned int weightA = mWeights[contactA];
  unsigned int weightB = mWeights[contactB];
96

97
  // Sort by weight and name.
98 99 100
  return weightA > weightB || (
    weightA == weightB &&
    contactA->mLinphoneFriend->getName() <= contactB->mLinphoneFriend->getName()
101 102
  );
}
103

Ronan's avatar
Ronan committed
104
// -----------------------------------------------------------------------------
105

106 107 108 109 110
float ContactsListProxyModel::computeStringWeight (const QString &string, float percentage) const {
  int index = -1;
  int offset = -1;

  // Search pattern.
111
  while ((index = string.indexOf(mFilter, index + 1, Qt::CaseInsensitive)) != -1) {
112
    // Search n chars between one separator and index.
113
    int tmpOffset = index - string.lastIndexOf(SearchSeparators, index) - 1;
114

115 116
    if ((tmpOffset != -1 && tmpOffset < offset) || offset == -1)
      if ((offset = tmpOffset) == 0) break;
117 118 119
  }

  switch (offset) {
120
    case -1: return 0;
121 122 123 124
    case 0: return percentage *FactorPos0;
    case 1: return percentage *FactorPos1;
    case 2: return percentage *FactorPos2;
    case 3: return percentage *FactorPos3;
125 126 127
    default: break;
  }

128
  return percentage *FactorPosOther;
129 130
}

131
float ContactsListProxyModel::computeContactWeight (const ContactModel *contact) const {
132
  float weight = computeStringWeight(contact->getVcardModel()->getUsername(), UsernameWeight);
133

134
  // Get all contact's addresses.
135
  const list<shared_ptr<linphone::Address>> addresses = contact->mLinphoneFriend->getAddresses();
136

137
  float size = float(addresses.size());
Ronan's avatar
Ronan committed
138
  for (auto it = addresses.cbegin(); it != addresses.cend(); ++it)
139
    weight += computeStringWeight(
140 141 142
      Utils::coreStringToAppString((*it)->asStringUriOnly()),
      SipAddressWeight / size
    );
143 144

  return weight;
145
}
146

147
// -----------------------------------------------------------------------------
148

149 150 151
void ContactsListProxyModel::setConnectedFilter (bool useConnectedFilter) {
  if (useConnectedFilter != mUseConnectedFilter) {
    mUseConnectedFilter = useConnectedFilter;
152
    invalidate();
Ronan's avatar
Ronan committed
153
  }
154
}