"Fossies" - the Fresh Open Source Software Archive

Member "seafile-client-7.0.4/src/ui/user-name-completer.cpp" (19 Nov 2019, 8474 Bytes) of package /linux/www/seafile-client-7.0.4.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "user-name-completer.cpp" see the Fossies "Dox" file reference documentation.

    1 #include <QLineEdit>
    2 #include <QTimer>
    3 #include <QTreeWidget>
    4 #include <QTreeWidgetItem>
    5 #include <QtWidgets>
    6 
    7 #include "api/api-error.h"
    8 #include "api/contact-share-info.h"
    9 #include "api/requests.h"
   10 #include "avatar-service.h"
   11 
   12 #include "user-name-completer.h"
   13 
   14 namespace
   15 {
   16 const int kSearchDelayInterval = 150;
   17 const qint64 kCacheEntryExpireMSecs = 5 * 1000;
   18 
   19 enum {
   20     USER_COLUMN_AVATAR = 0,
   21     USER_COLUMN_NAME,
   22     USER_MAX_COLUMN
   23 };
   24 
   25 } // anonymous namespace
   26 
   27 SeafileUserNameCompleter::SeafileUserNameCompleter(const Account &account,
   28                                                    QLineEdit *parent)
   29     : QObject(parent), account_(account), editor_(parent)
   30 {
   31     popup_ = new QTreeWidget;
   32     popup_->setWindowFlags(Qt::Popup);
   33     popup_->setFocusPolicy(Qt::NoFocus);
   34     popup_->setFocusProxy(parent);
   35     popup_->setMouseTracking(true);
   36 
   37     popup_->setColumnCount(USER_MAX_COLUMN);
   38     popup_->setUniformRowHeights(true);
   39     popup_->setRootIsDecorated(false);
   40     popup_->setEditTriggers(QTreeWidget::NoEditTriggers);
   41     popup_->setSelectionBehavior(QTreeWidget::SelectRows);
   42     popup_->setFrameStyle(QFrame::Box | QFrame::Plain);
   43     popup_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
   44     popup_->header()->hide();
   45 
   46     popup_->installEventFilter(this);
   47 
   48     connect(popup_,
   49             SIGNAL(itemClicked(QTreeWidgetItem *, int)),
   50             SLOT(doneCompletion()));
   51 
   52     timer_ = new QTimer(this);
   53     timer_->setSingleShot(true);
   54     timer_->setInterval(kSearchDelayInterval);
   55     connect(timer_, SIGNAL(timeout()), SLOT(autoSuggest()));
   56     // Calling start on the timer would effectively stop it & restart again.
   57     // Thus we can achieve: only show the completion list when the user has not
   58     // typed for a little while, here 150ms.
   59     connect(editor_, SIGNAL(textEdited(QString)), timer_, SLOT(start()));
   60 
   61     connect(AvatarService::instance(), SIGNAL(avatarUpdated(const QString&, const QImage&)),
   62             this, SLOT(onAvatarUpdated(const QString&, const QImage&)));
   63 }
   64 
   65 SeafileUserNameCompleter::~SeafileUserNameCompleter()
   66 {
   67     popup_->deleteLater();
   68 }
   69 
   70 bool SeafileUserNameCompleter::eventFilter(QObject *obj, QEvent *ev)
   71 {
   72     if (obj != popup_)
   73         return false;
   74 
   75     if (ev->type() == QEvent::MouseButtonPress) {
   76         popup_->hide();
   77         editor_->setFocus();
   78         return true;
   79     }
   80 
   81     if (ev->type() == QEvent::KeyPress) {
   82         bool consumed = false;
   83         int key = static_cast<QKeyEvent *>(ev)->key();
   84         switch (key) {
   85             case Qt::Key_Enter:
   86             case Qt::Key_Return:
   87                 doneCompletion();
   88                 consumed = true;
   89                 break;
   90 
   91             case Qt::Key_Escape:
   92                 popup_->hide();
   93                 editor_->setFocus();
   94                 consumed = true;
   95                 break;
   96 
   97             // Pass through the item navigation opeations to the tree widget.
   98             case Qt::Key_Up:
   99             case Qt::Key_Down:
  100             case Qt::Key_Home:
  101             case Qt::Key_End:
  102             case Qt::Key_PageUp:
  103             case Qt::Key_PageDown:
  104                 break;
  105 
  106             default:
  107                 editor_->setFocus();
  108                 editor_->event(ev);
  109                 popup_->hide();
  110                 break;
  111         }
  112 
  113         return consumed;
  114     }
  115 
  116     return false;
  117 }
  118 
  119 void SeafileUserNameCompleter::showCompletion(const QList<SeafileUser> &users, const QString& pattern)
  120 {
  121     if (users.isEmpty())
  122         return;
  123 
  124     if (pattern != editor_->text().trimmed()) {
  125         // The user has changed the text, so the completions are no longer
  126         // useful.
  127         // printf("pattern changed from \"%s\" to \"%s\"\n",
  128         //        pattern.toUtf8().data(),
  129         //        editor_->text().trimmed().toUtf8().data());
  130         return;
  131     }
  132 
  133     popup_->setUpdatesEnabled(false);
  134     popup_->clear();
  135     foreach (const SeafileUser &user, users) {
  136         // Do not list the user itself in the completion list.
  137         if (user.email == account_.username) {
  138             continue;
  139         }
  140 
  141         AvatarService *service = AvatarService::instance();
  142         QIcon avatar = QPixmap::fromImage(service->getAvatar(user.email));
  143 
  144         QString text =
  145             QString("%1 <%2>").arg(user.name).arg(user.getDisplayEmail());
  146         QTreeWidgetItem *item;
  147         item = new QTreeWidgetItem(popup_);
  148         item->setIcon(USER_COLUMN_AVATAR, avatar);
  149         item->setText(USER_COLUMN_NAME, text);
  150         item->setData(USER_COLUMN_NAME, Qt::UserRole, QVariant::fromValue(user));
  151     }
  152     popup_->setCurrentItem(popup_->topLevelItem(0));
  153     popup_->resizeColumnToContents(USER_COLUMN_AVATAR);
  154     popup_->setUpdatesEnabled(true);
  155 
  156     popup_->move(editor_->mapToGlobal(QPoint(0, editor_->height())));
  157 
  158     int w = editor_->width();
  159     int maxVisibleItems = 7;
  160     int h = (popup_->sizeHintForRow(0) *
  161                  qMin(maxVisibleItems, popup_->model()->rowCount()) +
  162              3) +
  163             3;
  164     h = qMax(h, popup_->minimumHeight());
  165     // printf("w = %d, h = %d\n", w, h);
  166 
  167     QPoint pos = editor_->mapToGlobal(QPoint(0, editor_->height()));
  168     popup_->setGeometry(pos.x(), pos.y(), w, h);
  169 
  170     popup_->setFocus();
  171     if (!popup_->isVisible())
  172         popup_->show();
  173 }
  174 
  175 void SeafileUserNameCompleter::doneCompletion()
  176 {
  177     timer_->stop();
  178     popup_->hide();
  179     editor_->setFocus();
  180     QTreeWidgetItem *item = popup_->currentItem();
  181     if (item) {
  182         SeafileUser user = item->data(USER_COLUMN_NAME, Qt::UserRole).value<SeafileUser>();
  183         current_selected_user_ = user;
  184         editor_->setText(user.name);
  185         QMetaObject::invokeMethod(editor_, "returnPressed");
  186     }
  187 }
  188 
  189 void SeafileUserNameCompleter::autoSuggest()
  190 {
  191     current_selected_user_ = SeafileUser();
  192     QString pattern = editor_->text().trimmed();
  193     if (pattern.isEmpty()) {
  194         return;
  195     }
  196 
  197     if (cached_completion_users_by_pattern_.contains(pattern) &&
  198         cached_completion_users_by_pattern_[pattern].ts +
  199                 kCacheEntryExpireMSecs >
  200             QDateTime::currentMSecsSinceEpoch()) {
  201         // printf("cached results for %s\n", pattern.toUtf8().data());
  202         showCompletion(
  203             cached_completion_users_by_pattern_[pattern].users.toList(), pattern);
  204         return;
  205     }
  206 
  207     if (in_progress_search_requests_.contains(pattern)) {
  208         // printf("already a request for %s\n", pattern.toUtf8().data());
  209         return;
  210     }
  211 
  212     // printf("request completions for username %s\n", pattern.toUtf8().data());
  213     SearchUsersRequest *req = new SearchUsersRequest(account_, pattern);
  214     req->setParent(this);
  215     connect(req,
  216             SIGNAL(success(const QList<SeafileUser> &)),
  217             this,
  218             SLOT(onSearchUsersSuccess(const QList<SeafileUser> &)));
  219     connect(req,
  220             SIGNAL(failed(const ApiError &)),
  221             this,
  222             SLOT(onSearchUsersFailed(const ApiError &)));
  223     req->send();
  224 
  225     in_progress_search_requests_.insert(pattern);
  226 }
  227 
  228 void SeafileUserNameCompleter::preventSuggest()
  229 {
  230     timer_->stop();
  231 }
  232 
  233 void SeafileUserNameCompleter::onSearchUsersSuccess(
  234     const QList<SeafileUser> &users)
  235 {
  236     SearchUsersRequest *req = qobject_cast<SearchUsersRequest *>(sender());
  237     in_progress_search_requests_.remove(req->pattern());
  238     req->deleteLater();
  239 
  240     // printf("get %d results for pattern %s\n",
  241     //        users.size(),
  242     //        req->pattern().toUtf8().data());
  243 
  244     cached_completion_users_by_pattern_[req->pattern()] = {
  245         QSet<SeafileUser>::fromList(users),
  246         QDateTime::currentMSecsSinceEpoch()};
  247     showCompletion(users, req->pattern());
  248 }
  249 
  250 void SeafileUserNameCompleter::onSearchUsersFailed(const ApiError &error)
  251 {
  252     SearchUsersRequest *req = qobject_cast<SearchUsersRequest *>(sender());
  253     in_progress_search_requests_.remove(req->pattern());
  254     req->deleteLater();
  255 }
  256 
  257 const SeafileUser& SeafileUserNameCompleter::currentSelectedUser() const
  258 {
  259     return current_selected_user_;
  260 }
  261 
  262 void SeafileUserNameCompleter::onAvatarUpdated(const QString& email,
  263                                                const QImage& avatar)
  264 {
  265     for (int i = 0; i < popup_->topLevelItemCount(); i++) {
  266         QTreeWidgetItem* item =  popup_->topLevelItem(i);
  267         const QString username_email = item->data(USER_COLUMN_NAME, Qt::DisplayRole).toString();
  268         if (username_email.contains(email)) {
  269             item->setIcon(USER_COLUMN_AVATAR, QPixmap::fromImage(avatar));
  270         }
  271     }
  272 }