]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiCompleter.cpp
Fix the tab ordering of GuiDocument components.
[lyx.git] / src / frontends / qt4 / GuiCompleter.cpp
index d3169948cb34d859854c727d9b329cf9f6a5c402..da2d688c59688e90892a7ef407a5928f6cb83542 100644 (file)
 
 #include <config.h>
 
-#include "GuiWorkArea.h"
+#include "GuiCompleter.h"
 
 #include "Buffer.h"
 #include "BufferView.h"
+#include "CompletionList.h"
 #include "Cursor.h"
 #include "Dimension.h"
-#include "FuncRequest.h"
+#include "GuiWorkArea.h"
 #include "GuiView.h"
-#include "LyXFunc.h"
+#include "LyX.h"
 #include "LyXRC.h"
 #include "Paragraph.h"
 #include "version.h"
 
+#include "support/lassert.h"
 #include "support/debug.h"
 
 #include <QApplication>
-#include <QAbstractListModel>
 #include <QHeaderView>
 #include <QPainter>
 #include <QPixmapCache>
@@ -41,33 +42,24 @@ using namespace lyx::support;
 namespace lyx {
 namespace frontend {
 
-class RtlItemDelegate : public QItemDelegate {
+class CompleterItemDelegate : public QItemDelegate
+{
 public:
-       explicit RtlItemDelegate(QObject * parent = 0)
-               : QItemDelegate(parent) {}
-
-protected:
-       virtual void drawDisplay(QPainter * painter,
-               QStyleOptionViewItem const & option,
-               QRect const & rect, QString const & text) const
-       {
-               // FIXME: do this more elegantly
-               docstring stltext = qstring_to_ucs4(text);
-               reverse(stltext.begin(), stltext.end());
-               QItemDelegate::drawDisplay(painter, option, rect, toqstr(stltext));
-       }
-};
+       explicit CompleterItemDelegate(QObject * parent)
+               : QItemDelegate(parent)
+       {}
 
-
-class PixmapItemDelegate : public QItemDelegate {
-public:
-       explicit PixmapItemDelegate(QObject *parent = 0)
-       : QItemDelegate(parent) {}
+       ~CompleterItemDelegate()
+       {}
 
 protected:
        void paint(QPainter *painter, const QStyleOptionViewItem &option,
                   const QModelIndex &index) const
        {
+               if (index.column() == 0) {
+                       QItemDelegate::paint(painter, option, index);
+                       return;
+               }
                QStyleOptionViewItem opt = setOptions(index, option);
                QVariant value = index.data(Qt::DisplayRole);
                QPixmap pixmap = qvariant_cast<QPixmap>(value);
@@ -86,23 +78,27 @@ protected:
        }
 };
 
-
-class GuiCompletionModel : public QAbstractListModel {
+class GuiCompletionModel : public QAbstractListModel
+{
 public:
        ///
-       GuiCompletionModel(QObject * parent,
-               Inset::CompletionList const * l)
-               : QAbstractListModel(parent), list_(l) {}
+       GuiCompletionModel(QObject * parent, CompletionList const * l)
+               : QAbstractListModel(parent), list_(l)
+       {}
        ///
-       ~GuiCompletionModel()
-               { delete list_; }
+       ~GuiCompletionModel() { delete list_; }
+       ///
+       void setList(CompletionList const * l) {
+               delete list_;
+               list_ = l;
+               reset();
+       }
        ///
        bool sorted() const
        {
                if (list_)
                        return list_->sorted();
-               else
-                       return false;
+               return false;
        }
        ///
        int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const
@@ -114,8 +110,7 @@ public:
        {
                if (list_ == 0)
                        return 0;
-               else
-                       return list_->size();
+               return list_->size();
        }
 
        ///
@@ -130,56 +125,59 @@ 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) {
-                       // get icon from cache
-                       QPixmap scaled;
-                       QString const name = ":" + toqstr(list_->icon(index.row()));
-                       if (!QPixmapCache::find("completion" + name, scaled)) {
-                               // load icon from disk
-                               QPixmap p = QPixmap(name);
-                               if (!p.isNull()) {
-                                       // scale it to 16x16 or smaller
-                                       scaled = p.scaled(min(16, p.width()), min(16, p.height()), 
-                                               Qt::KeepAspectRatio, Qt::SmoothTransformation);
-                               }
+               if (index.column() == 0)
+                       return toqstr(list_->data(index.row()));
 
-                               QPixmapCache::insert("completion" + name, scaled);
+               if (index.column() != 1)
+                       return QVariant();
+       
+               // get icon from cache
+               QPixmap scaled;
+               QString const name = ":" + toqstr(list_->icon(index.row()));
+               if (!QPixmapCache::find("completion" + name, scaled)) {
+                       // load icon from disk
+                       QPixmap p = QPixmap(name);
+                       if (!p.isNull()) {
+                               // scale it to 16x16 or smaller
+                               scaled = p.scaled(min(16, p.width()), min(16, p.height()), 
+                                       Qt::KeepAspectRatio, Qt::SmoothTransformation);
                        }
-                       return scaled;
+                       QPixmapCache::insert("completion" + name, scaled);
                }
-               return QVariant();
+               return scaled;
        }
 
 private:
-       ///
-       Inset::CompletionList const * list_;
+       /// owned by us
+       CompletionList const * list_;
 };
 
 
 GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
-       : QCompleter(parent), gui_(gui), updateLock_(0),
-         inlineVisible_(false)
+       : QCompleter(parent), gui_(gui), old_cursor_(0), updateLock_(0),
+         inlineVisible_(false), popupVisible_(false),
+         modelActive_(false)
 {
        // Setup the completion popup
-       setModel(new GuiCompletionModel(this, 0));
+       model_ = new GuiCompletionModel(this, 0);
+       setModel(model_);
        setCompletionMode(QCompleter::PopupCompletion);
+       setCaseSensitivity(Qt::CaseSensitive);
        setWidget(gui_);
        
        // create the popup
        QTreeView *listView = new QTreeView;
-        listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
-        listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
-        listView->setSelectionBehavior(QAbstractItemView::SelectRows);
-        listView->setSelectionMode(QAbstractItemView::SingleSelection);
+       listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
+       listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+       listView->setSelectionBehavior(QAbstractItemView::SelectRows);
+       listView->setSelectionMode(QAbstractItemView::SingleSelection);
        listView->header()->hide();
        listView->setIndentation(0);
        listView->setUniformRowHeights(true);
        setPopup(listView);
-       popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(this));
-       rtlItemDelegate_ = new RtlItemDelegate(this);
+       
+       itemDelegate_ = new CompleterItemDelegate(this);
+       popup()->setItemDelegate(itemDelegate_);
        
        // create timeout timers
        popup_timer_.setSingleShot(true);
@@ -234,8 +232,29 @@ bool GuiCompleter::inlinePossible(Cursor const & cur) const
 }
 
 
+bool GuiCompleter::uniqueCompletionAvailable() const
+{
+       if (!modelActive_)
+               return false;
+
+       size_t n = popup()->model()->rowCount();
+       if (n > 1 || n == 0)
+               return false;
+
+       // if there is exactly one, we have to check whether it is a 
+       // real completion, i.e. longer than the current prefix.
+       if (completionPrefix() == currentCompletion())
+               return false;
+
+       return true;
+}
+
+
 bool GuiCompleter::completionAvailable() const
 {
+       if (!modelActive_)
+               return false;
+
        size_t n = popup()->model()->rowCount();
 
        // if there is exactly one, we have to check whether it is a 
@@ -249,7 +268,7 @@ bool GuiCompleter::completionAvailable() const
 
 bool GuiCompleter::popupVisible() const
 {
-       return popup()->isVisible();
+       return popupVisible_;
 }
 
 
@@ -292,9 +311,14 @@ void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep, bool cu
        if (!inlineVisible() && possibleInlineState && start
                && cur.inset().automaticInlineCompletion())
                inline_timer_.start(int(lyxrc.completion_inline_delay * 1000));
+       else if (cur.inMathed() && !lyxrc.completion_inline_math) {
+               // no inline completion, hence a metrics update is needed
+               if (!(cur.result().screenUpdate() & Update::Force))
+                       cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
+       }
 
        // update prefix if any completion is possible
-       bool modelActive = model()->rowCount() > 0;
+       bool modelActive = modelActive_ && model()->rowCount() > 0;
        if (possiblePopupState || possibleInlineState) {
                if (modelActive)
                        updatePrefix(cur);
@@ -307,12 +331,12 @@ void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep, bool cu
 void GuiCompleter::updateVisibility(bool start, bool keep)
 {
        Cursor cur = gui_->bufferView().cursor();
-       cur.updateFlags(Update::None);
+       cur.screenUpdateFlags(Update::None);
        
        updateVisibility(cur, start, keep);
        
-       if (cur.disp_.update())
-               gui_->bufferView().processUpdateFlags(cur.disp_.update());
+       if (cur.result().screenUpdate())
+               gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
 }
 
 
@@ -362,7 +386,7 @@ void GuiCompleter::updateInline(Cursor & cur, QString const & completion)
        
        // compute postfix
        docstring prefix = cur.inset().completionPrefix(cur);
-       docstring postfix = from_utf8(fromqstr(completion.mid(prefix.length())));
+       docstring postfix = qstring_to_ucs4(completion.mid(prefix.length()));
        
        // shorten it if necessary
        if (lyxrc.completion_inline_dots != -1
@@ -381,9 +405,26 @@ 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)
+                 || !cur.bv().paragraphVisible(cur)) {
+               popupVisible_ = false;
+               return;
+       }
+
        // get dimensions of completion prefix
        Dimension dim;
        int x;
@@ -397,10 +438,16 @@ 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);
 }
 
@@ -430,19 +477,16 @@ void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate
        bool rtl = false;
        if (cur.inTexted()) {
                Paragraph const & par = cur.paragraph();
-               Font const font =
-               par.getFontSettings(cur.bv().buffer().params(), cur.pos());
+               Font const font =
+                       par.getFontSettings(cur.bv().buffer().params(), cur.pos());
                rtl = font.isVisibleRightToLeft();
        }
        popup()->setLayoutDirection(rtl ? Qt::RightToLeft : Qt::LeftToRight);
 
-       // turn the direction of the strings in the popup.
-       // Qt does not do that itself.
-       popup()->setItemDelegateForColumn(0, rtl ? rtlItemDelegate_ : 0);
-
        // set new model
-       Inset::CompletionList const * list = cur.inset().createCompletionList(cur);
-       setModel(new GuiCompletionModel(this, list));
+       CompletionList const * list = cur.inset().createCompletionList(cur);
+       model_->setList(list);
+       modelActive_ = true;
        if (list->sorted())
                setModelSorting(QCompleter::CaseSensitivelySortedModel);
        else
@@ -485,14 +529,31 @@ void GuiCompleter::showPopup(Cursor & cur)
 }
 
 
-void GuiCompleter::hidePopup(Cursor & cur)
+void GuiCompleter::hidePopup(Cursor &)
 {
-       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));
+               model_->setList(0);
 }
 
 
@@ -507,72 +568,88 @@ void GuiCompleter::showInline(Cursor & cur)
 
 void GuiCompleter::hideInline(Cursor & cur)
 {
-       gui_->bufferView().setInlineCompletion(cur, DocIterator(), docstring());
+       gui_->bufferView().setInlineCompletion(cur, DocIterator(cur.buffer()), 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(asyncHideInline()));
+
+       // mark that the asynchronous part will reset the model
+       if (!popupVisible())
+               modelActive_ = false;
+}
+
+
+void GuiCompleter::asyncHideInline()
+{
        if (!popupVisible())
-               setModel(new GuiCompletionModel(this, 0));
+               model_->setList(0);
 }
 
 
 void GuiCompleter::showPopup()
 {
        Cursor cur = gui_->bufferView().cursor();
-       cur.updateFlags(Update::None);
+       cur.screenUpdateFlags(Update::None);
        
        showPopup(cur);
 
        // redraw if needed
-       if (cur.disp_.update())
-               gui_->bufferView().processUpdateFlags(cur.disp_.update());
+       if (cur.result().screenUpdate())
+               gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
 }
 
 
 void GuiCompleter::showInline()
 {
        Cursor cur = gui_->bufferView().cursor();
-       cur.updateFlags(Update::None);
+       cur.screenUpdateFlags(Update::None);
        
        showInline(cur);
 
        // redraw if needed
-       if (cur.disp_.update())
-               gui_->bufferView().processUpdateFlags(cur.disp_.update());
+       if (cur.result().screenUpdate())
+               gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
 }
 
 
 void GuiCompleter::hidePopup()
 {
        Cursor cur = gui_->bufferView().cursor();
-       cur.updateFlags(Update::None);
+       cur.screenUpdateFlags(Update::None);
        
        hidePopup(cur);
        
        // redraw if needed
-       if (cur.disp_.update())
-               gui_->bufferView().processUpdateFlags(cur.disp_.update());
+       if (cur.result().screenUpdate())
+               gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
 }
 
 
 void GuiCompleter::hideInline()
 {
        Cursor cur = gui_->bufferView().cursor();
-       cur.updateFlags(Update::None);
+       cur.screenUpdateFlags(Update::None);
        
        hideInline(cur);
        
        // redraw if needed
-       if (cur.disp_.update())
-               gui_->bufferView().processUpdateFlags(cur.disp_.update());
+       if (cur.result().screenUpdate())
+               gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
 }
 
 
 void GuiCompleter::activate()
 {
        if (!popupVisible() && !inlineVisible())
-               return;
-
-       popupActivated(currentCompletion());
+               tab();
+       else
+               popupActivated(currentCompletion());
 }
 
 
@@ -580,10 +657,10 @@ void GuiCompleter::tab()
 {
        BufferView * bv = &gui_->bufferView();
        Cursor cur = bv->cursor();
-       cur.updateFlags(Update::None);
+       cur.screenUpdateFlags(Update::None);
        
        // check that inline completion is active
-       if (!inlineVisible()) {
+       if (!inlineVisible() && !uniqueCompletionAvailable()) {
                // try to activate the inline completion
                if (cur.inset().inlineCompletionSupported(cur)) {
                        showInline();
@@ -605,9 +682,13 @@ void GuiCompleter::tab()
                return;
        }
        
+       // Make undo possible
+       cur.beginUndoGroup();
+       cur.recordUndo();
+
        // If completion is active, at least complete by one character
        docstring prefix = cur.inset().completionPrefix(cur);
-       docstring completion = from_utf8(fromqstr(currentCompletion()));
+       docstring completion = qstring_to_ucs4(currentCompletion());
        if (completion.size() <= prefix.size()) {
                // finalize completion
                cur.inset().insertCompletion(cur, docstring(), true);
@@ -616,11 +697,14 @@ void GuiCompleter::tab()
                hidePopup(cur);
                hideInline(cur);
                updateVisibility(false, false);
+               cur.endUndoGroup();
                return;
        }
        docstring nextchar = completion.substr(prefix.size(), 1);
-       if (!cur.inset().insertCompletion(cur, nextchar, false))
+       if (!cur.inset().insertCompletion(cur, nextchar, false)) {
+               cur.endUndoGroup();
                return;
+       }
        updatePrefix(cur);
 
        // try to complete as far as it is unique
@@ -638,8 +722,9 @@ void GuiCompleter::tab()
                popup_timer_.start(0);
 
        // redraw if needed
-       if (cur.disp_.update())
-               gui_->bufferView().processUpdateFlags(cur.disp_.update());
+       if (cur.result().screenUpdate())
+               gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
+       cur.endUndoGroup();
 }
 
 
@@ -713,7 +798,7 @@ void GuiCompleter::setCurrentCompletion(QString const & s)
                        i = n;
                else
                        i = l;
-               BOOST_ASSERT(0 <= i && i <= n);
+               LASSERT(i <= n, /**/);
        }
 
        // select the first if none was found
@@ -743,8 +828,10 @@ 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.
@@ -785,23 +872,27 @@ docstring GuiCompleter::longestUniqueCompletion() const
                }
        }
 
-       return from_utf8(fromqstr(s));
+       return qstring_to_ucs4(s);
 }
 
 
 void GuiCompleter::popupActivated(const QString & completion)
 {
        Cursor cur = gui_->bufferView().cursor();
-       cur.updateFlags(Update::None);
-       
+       cur.screenUpdateFlags(Update::None);
+
+       cur.beginUndoGroup();
+       cur.recordUndo();
+
        docstring prefix = cur.inset().completionPrefix(cur);
-       docstring postfix = from_utf8(fromqstr(completion.mid(prefix.length())));
+       docstring postfix = qstring_to_ucs4(completion.mid(prefix.length()));
        cur.inset().insertCompletion(cur, postfix, true);
        hidePopup(cur);
        hideInline(cur);
        
-       if (cur.disp_.update())
-               gui_->bufferView().processUpdateFlags(cur.disp_.update());
+       if (cur.result().screenUpdate())
+               gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
+       cur.endUndoGroup();
 }
 
 
@@ -811,16 +902,16 @@ void GuiCompleter::popupHighlighted(const QString & completion)
                return;
 
        Cursor cur = gui_->bufferView().cursor();
-       cur.updateFlags(Update::None);
+       cur.screenUpdateFlags(Update::None);
        
        if (inlineVisible())
                updateInline(cur, completion);
        
-       if (cur.disp_.update())
-               gui_->bufferView().processUpdateFlags(cur.disp_.update());
+       if (cur.result().screenUpdate())
+               gui_->bufferView().processUpdateFlags(cur.result().screenUpdate());
 }
 
 } // namespace frontend
 } // namespace lyx
 
-#include "GuiCompleter_moc.cpp"
+#include "moc_GuiCompleter.cpp"