]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiCompleter.cpp
do what the FIXME suggested
[lyx.git] / src / frontends / qt4 / GuiCompleter.cpp
index 1eafa25fa4b6688e72f24837fd7d0fbf45ca2b5a..0a67bae0beb6a68c319374a3eae0b6953d7b4a86 100644 (file)
@@ -26,7 +26,6 @@
 #include "support/debug.h"
 
 #include <QApplication>
-#include <QAbstractListModel>
 #include <QHeaderView>
 #include <QPainter>
 #include <QPixmapCache>
@@ -44,25 +43,38 @@ namespace frontend {
 class RtlItemDelegate : public QItemDelegate {
 public:
        explicit RtlItemDelegate(QObject * parent = 0)
-               : QItemDelegate(parent) {}
+               : QItemDelegate(parent), enabled_(false) {}
 
+       void setEnabled(bool enabled = true)
+       {
+               enabled_ = enabled;
+       }
+       
 protected:
        virtual void drawDisplay(QPainter * painter,
                QStyleOptionViewItem const & option,
                QRect const & rect, QString const & text) const
        {
+               if (!enabled_) {
+                       QItemDelegate::drawDisplay(painter, option, rect, text);
+                       return;
+               }
+
                // FIXME: do this more elegantly
                docstring stltext = qstring_to_ucs4(text);
                reverse(stltext.begin(), stltext.end());
                QItemDelegate::drawDisplay(painter, option, rect, toqstr(stltext));
        }
+       
+private:
+       bool enabled_;
 };
 
 
 class PixmapItemDelegate : public QItemDelegate {
 public:
        explicit PixmapItemDelegate(QObject *parent = 0)
-       : QItemDelegate(parent) {}
+               : QItemDelegate(parent) {}
 
 protected:
        void paint(QPainter *painter, const QStyleOptionViewItem &option,
@@ -90,8 +102,7 @@ protected:
 class GuiCompletionModel : public QAbstractListModel {
 public:
        ///
-       GuiCompletionModel(QObject * parent,
-               Inset::CompletionList const * l)
+       GuiCompletionModel(QObject * parent, Inset::CompletionList const * l)
                : QAbstractListModel(parent), list_(l) {}
        ///
        ~GuiCompletionModel()
@@ -130,10 +141,9 @@ public:
                if (role != Qt::DisplayRole && role != Qt::EditRole)
                    return QVariant();
                    
-               if (index.column() == 0) {
-                       docstring const word = list_->data(index.row());
-                       return toqstr(word);
-               } else if (index.column() == 1) {
+               if (index.column() == 0)
+                       return toqstr(list_->data(index.row()));
+               else if (index.column() == 1) {
                        // get icon from cache
                        QPixmap scaled;
                        QString const name = ":" + toqstr(list_->icon(index.row()));
@@ -161,7 +171,8 @@ private:
 
 GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
        : QCompleter(parent), gui_(gui), updateLock_(0),
-         inlineVisible_(false)
+         inlineVisible_(false), popupVisible_(false),
+         modelActive_(false)
 {
        // Setup the completion popup
        setModel(new GuiCompletionModel(this, 0));
@@ -178,8 +189,10 @@ GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
        listView->setIndentation(0);
        listView->setUniformRowHeights(true);
        setPopup(listView);
-       popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(this));
+       
        rtlItemDelegate_ = new RtlItemDelegate(this);
+       popup()->setItemDelegateForColumn(0, rtlItemDelegate_);
+       popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(this));
        
        // create timeout timers
        popup_timer_.setSingleShot(true);
@@ -236,13 +249,23 @@ bool GuiCompleter::inlinePossible(Cursor const & cur) const
 
 bool GuiCompleter::completionAvailable() const
 {
-       return popup()->model()->rowCount() > 0;
+       if (!modelActive_)
+               return false;
+
+       size_t n = popup()->model()->rowCount();
+
+       // if there is exactly one, we have to check whether it is a 
+       // real completion, i.e. longer than the current prefix.
+       if (n == 1 && completionPrefix() == currentCompletion())
+           return false;
+
+       return n > 0;
 }
 
 
 bool GuiCompleter::popupVisible() const
 {
-       return popup()->isVisible();
+       return popupVisible_;
 }
 
 
@@ -286,10 +309,14 @@ void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep, bool cu
                && cur.inset().automaticInlineCompletion())
                inline_timer_.start(int(lyxrc.completion_inline_delay * 1000));
 
-       // update prefix if popup is visible or if it will be visible soon
-       if (popupVisible() || inlineVisible()
-           || popup_timer_.isActive() || inline_timer_.isActive())
-               updatePrefix(cur);
+       // update prefix if any completion is possible
+       bool modelActive = modelActive_ && model()->rowCount() > 0;
+       if (possiblePopupState || possibleInlineState) {
+               if (modelActive)
+                       updatePrefix(cur);
+               else
+                       updateAvailability();
+       }
 }
 
 
@@ -330,11 +357,13 @@ void GuiCompleter::updatePrefix(Cursor & cur)
        // if popup is not empty, the new selection will
        // be our last valid one
        QString const & s = currentCompletion();
-       if (s.length() > 0)
-               last_selection_ = s;
-       else
-               last_selection_ = old;
-       
+       if (popupVisible() || inlineVisible()) {
+               if (s.length() > 0)
+                       last_selection_ = s;
+               else
+                       last_selection_ = old;
+       }
+
        // update inline completion because the default
        // completion string might have changed
        if (inlineVisible())
@@ -368,9 +397,25 @@ void GuiCompleter::updatePopup(Cursor & cur)
        if (!cur.inset().completionSupported(cur))
                return;
        
-       if (completionCount() == 0)
+       popupVisible_ = true;
+
+       if (completionCount() == 0) {
+               QTimer::singleShot(0, popup(), SLOT(hide()));
                return;
-       
+       }
+
+       QTimer::singleShot(0, this, SLOT(asyncUpdatePopup()));
+}
+
+
+void GuiCompleter::asyncUpdatePopup()
+{
+       Cursor cur = gui_->bufferView().cursor();
+       if (!cur.inset().completionSupported(cur)) {
+               popupVisible_ = false;
+               return;
+       }
+
        // get dimensions of completion prefix
        Dimension dim;
        int x;
@@ -384,14 +429,34 @@ void GuiCompleter::updatePopup(Cursor & cur)
        else
                rect = QRect(x, y - dim.ascent() - 3, 200, dim.height() + 6);
        
+       // Resize the columns in the popup.
+       // This should really be in the constructor. But somehow the treeview
+       // has a bad memory about it and we have to tell him again and again.
+       QTreeView * listView = static_cast<QTreeView *>(popup());
+       listView->header()->setStretchLastSection(false);
+       listView->header()->setResizeMode(0, QHeaderView::Stretch);
+       listView->header()->setResizeMode(1, QHeaderView::Fixed);
+       listView->header()->resizeSection(1, 22);
+       
        // show/update popup
-       QTreeView * p = static_cast<QTreeView *>(popup());
-       p->setColumnWidth(0, popup()->width() - 22 - p->verticalScrollBar()->width());
-
        complete(rect);
 }
 
 
+void GuiCompleter::updateAvailability()
+{
+       // this should really only be of interest if no completion is
+       // visible yet, i.e. especially if automatic completion is disabled.
+       if (inlineVisible() || popupVisible())
+               return;
+       Cursor & cur = gui_->bufferView().cursor();
+       if (!popupPossible(cur) && !inlinePossible(cur))
+               return;
+       
+       updateModel(cur, false, false);
+}
+       
+
 void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate)
 {
        // value which should be kept selected
@@ -411,11 +476,12 @@ void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate
 
        // turn the direction of the strings in the popup.
        // Qt does not do that itself.
-       popup()->setItemDelegateForColumn(0, rtl ? rtlItemDelegate_ : 0);
+       rtlItemDelegate_->setEnabled(rtl);
 
        // set new model
        Inset::CompletionList const * list = cur.inset().createCompletionList(cur);
        setModel(new GuiCompletionModel(this, list));
+       modelActive_ = true;
        if (list->sorted())
                setModelSorting(QCompleter::CaseSensitivelySortedModel);
        else
@@ -435,11 +501,13 @@ void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate
        
        // if popup is not empty, the new selection will
        // be our last valid one
-       QString const & s = currentCompletion();
-       if (s.length() > 0)
-               last_selection_ = s;
-       else
-               last_selection_ = old;
+       if (popupVisible() || inlineVisible()) {
+               QString const & s = currentCompletion();
+               if (s.length() > 0)
+                       last_selection_ = s;
+               else
+                       last_selection_ = old;
+       }
 
        // show inline completion
        if (inlineUpdate)
@@ -458,10 +526,27 @@ void GuiCompleter::showPopup(Cursor & cur)
 
 void GuiCompleter::hidePopup(Cursor & cur)
 {
-       popup()->hide();
+       popupVisible_ = false;
+
        if (popup_timer_.isActive())
                popup_timer_.stop();
+
+       // hide popup asynchronously because we might be here inside of
+       // LFUN dispatchers. Hiding a popup can trigger a focus event on the 
+       // workarea which then redisplays the cursor. But the metrics are not
+       // yet up to date such that the coord cache has not all insets yet. The
+       // cursorPos methods would triggers asserts in the coord cache then.
+       QTimer::singleShot(0, this, SLOT(asyncHidePopup()));
        
+       // mark that the asynchronous part will reset the model
+       if (!inlineVisible())
+               modelActive_ = false;
+}
+
+
+void GuiCompleter::asyncHidePopup()
+{
+       popup()->hide();
        if (!inlineVisible())
                setModel(new GuiCompletionModel(this, 0));
 }
@@ -481,6 +566,22 @@ void GuiCompleter::hideInline(Cursor & cur)
        gui_->bufferView().setInlineCompletion(cur, DocIterator(), docstring());
        inlineVisible_ = false;
        
+       if (inline_timer_.isActive())
+               inline_timer_.stop();
+       
+       // Trigger asynchronous part of hideInline. We might be
+       // in a dispatcher here and the setModel call might
+       // trigger focus events which is are not healthy here.
+       QTimer::singleShot(0, this, SLOT(asyncHideModel()));
+
+       // mark that the asynchronous part will reset the model
+       if (!popupVisible())
+               modelActive_ = false;
+}
+
+
+void GuiCompleter::asyncHideInline()
+{
        if (!popupVisible())
                setModel(new GuiCompletionModel(this, 0));
 }
@@ -714,9 +815,11 @@ size_t commonPrefix(QString const & s1, QString const & s2)
 docstring GuiCompleter::longestUniqueCompletion() const
 {
        QAbstractItemModel const & model = *popup()->model();
-       QString s = currentCompletion();
        size_t n = model.rowCount();
-
+       if (n == 0)
+               return docstring();
+       QString s = model.data(model.index(0, 0), Qt::EditRole).toString();
+       
        if (modelSorting() == QCompleter::UnsortedModel) {
                // For unsorted model we cannot do more than iteration.
                // Iterate through the completions and cut off where s differs