#include "GuiView.h"
#include "LyXFunc.h"
#include "LyXRC.h"
+#include "Paragraph.h"
#include "version.h"
#include "support/debug.h"
namespace lyx {
namespace frontend {
+class RtlItemDelegate : 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));
+ }
+};
+
class PixmapItemDelegate : public QItemDelegate {
public:
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
- QStyleOptionViewItemV3 opt = setOptions(index, option);
+ QStyleOptionViewItem opt = setOptions(index, option);
QVariant value = index.data(Qt::DisplayRole);
QPixmap pixmap = qvariant_cast<QPixmap>(value);
class GuiCompletionModel : public QAbstractListModel {
public:
///
- GuiCompletionModel(QObject * parent, Inset::CompletionListPtr l)
- : QAbstractListModel(parent), list(l) {}
+ GuiCompletionModel(QObject * parent,
+ Inset::CompletionList const * l)
+ : QAbstractListModel(parent), list_(l) {}
+ ///
+ ~GuiCompletionModel()
+ { delete list_; }
+ ///
+ bool sorted() const
+ {
+ if (list_)
+ return list_->sorted();
+ else
+ return false;
+ }
///
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const
{
///
int rowCount(const QModelIndex & /*parent*/ = QModelIndex()) const
{
- if (list.get() == 0)
+ if (list_ == 0)
return 0;
else
- return list->size();
+ return list_->size();
}
///
QVariant data(const QModelIndex & index, int role) const
{
- if (list.get() == 0)
+ if (list_ == 0)
return QVariant();
if (index.row() < 0 || index.row() >= rowCount())
if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
- if (index.column() == 0)
- return toqstr(list->data(index.row()));
- else if (index.column() == 1) {
+ 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()));
+ 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()),
+ scaled = p.scaled(min(16, p.width()), min(16, p.height()),
Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
private:
///
- Inset::CompletionListPtr list;
+ Inset::CompletionList const * list_;
};
GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
- : QCompleter(parent), gui_(gui), updateLock_(0)
+ : QCompleter(parent), gui_(gui), updateLock_(0),
+ inlineVisible_(false)
{
// Setup the completion popup
- setModel(new GuiCompletionModel(this, Inset::CompletionListPtr()));
+ setModel(new GuiCompletionModel(this, 0));
setCompletionMode(QCompleter::PopupCompletion);
setWidget(gui_);
listView->setSelectionMode(QAbstractItemView::SingleSelection);
listView->header()->hide();
listView->setIndentation(0);
+ listView->setUniformRowHeights(true);
setPopup(listView);
- popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(popup()));
+ popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(this));
+ rtlItemDelegate_ = new RtlItemDelegate(this);
// create timeout timers
popup_timer_.setSingleShot(true);
}
+bool GuiCompleter::completionAvailable() const
+{
+ return popup()->model()->rowCount() > 0;
+}
+
+
bool GuiCompleter::popupVisible() const
{
return popup()->isVisible();
bool GuiCompleter::inlineVisible() const
{
- return !gui_->bufferView().inlineCompletionPos().empty();
+ // In fact using BufferView::inlineCompletionPos.empty() should be
+ // here. But unfortunately this information is not good enough
+ // because destructive operations like backspace might invalidate
+ // inlineCompletionPos. But then the completion should stay visible
+ // (i.e. reshown on the next update). Hence be keep this information
+ // in the inlineVisible_ variable.
+ return inlineVisible_;
}
bool possibleInlineState = inlinePossible(cur) && cursorInView;
// we moved or popup state is not ok for popup?
- if ((moved && !keep) || !possiblePopupState) {
- // stop an old completion timer
- if (popup_timer_.isActive())
- popup_timer_.stop();
-
- // hide old popup
- if (popupVisible())
- popup()->hide();
- }
+ if ((moved && !keep) || !possiblePopupState)
+ hidePopup(cur);
// we moved or inline state is not ok for inline completion?
- if ((moved && !keep) || !possibleInlineState) {
- // stop an old completion timer
- if (inline_timer_.isActive())
- inline_timer_.stop();
-
- // hide old inline completion
- if (inlineVisible())
- gui_->bufferView().setInlineCompletion(cur, DocIterator(), docstring());
- }
+ if ((moved && !keep) || !possibleInlineState)
+ hideInline(cur);
// we inserted something and are in a possible popup state?
if (!popupVisible() && possiblePopupState && start
// update completer to new prefix
setCompletionPrefix(newPrefix);
-
+
// update popup because its size might have changed
if (popupVisible())
updatePopup(cur);
// set inline completion at cursor position
size_t uniqueTo = max(longestUniqueCompletion().size(), prefix.size());
gui_->bufferView().setInlineCompletion(cur, cur, postfix, uniqueTo - prefix.size());
+ inlineVisible_ = true;
}
int x;
int y;
cur.inset().completionPosAndDim(cur, x, y, dim);
- QRect insetRect = QRect(x, y - dim.ascent() - 3, 200, dim.height() + 6);
+
+ // and calculate the rect of the popup
+ QRect rect;
+ if (popup()->layoutDirection() == Qt::RightToLeft)
+ rect = QRect(x + dim.width() - 200, y - dim.ascent() - 3, 200, dim.height() + 6);
+ else
+ rect = QRect(x, y - dim.ascent() - 3, 200, dim.height() + 6);
// show/update popup
- complete(insetRect);
QTreeView * p = static_cast<QTreeView *>(popup());
p->setColumnWidth(0, popup()->width() - 22 - p->verticalScrollBar()->width());
+
+ complete(rect);
}
QString old = currentCompletion();
if (old.length() == 0)
old = last_selection_;
-
+
+ // set whether rtl
+ bool rtl = false;
+ if (cur.inTexted()) {
+ Paragraph const & par = cur.paragraph();
+ 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
- setModel(new GuiCompletionModel(this, cur.inset().completionList(cur)));
-
+ Inset::CompletionList const * list = cur.inset().createCompletionList(cur);
+ setModel(new GuiCompletionModel(this, list));
+ if (list->sorted())
+ setModelSorting(QCompleter::CaseSensitivelySortedModel);
+ else
+ setModelSorting(QCompleter::UnsortedModel);
+
+ // set prefix
+ QString newPrefix = toqstr(cur.inset().completionPrefix(cur));
+ if (newPrefix != completionPrefix())
+ setCompletionPrefix(newPrefix);
+
// show popup
if (popupUpdate)
updatePopup(cur);
last_selection_ = s;
else
last_selection_ = old;
-
+
// show inline completion
if (inlineUpdate)
updateInline(cur, currentCompletion());
return;
updateModel(cur, true, inlineVisible());
- updatePrefix(cur);
}
+
+
+void GuiCompleter::hidePopup(Cursor & cur)
+{
+ popup()->hide();
+ if (popup_timer_.isActive())
+ popup_timer_.stop();
+ if (!inlineVisible())
+ setModel(new GuiCompletionModel(this, 0));
+}
+
void GuiCompleter::showInline(Cursor & cur)
{
return;
updateModel(cur, popupVisible(), true);
- updatePrefix(cur);
+}
+
+
+void GuiCompleter::hideInline(Cursor & cur)
+{
+ gui_->bufferView().setInlineCompletion(cur, DocIterator(), docstring());
+ inlineVisible_ = false;
+
+ if (!popupVisible())
+ setModel(new GuiCompletionModel(this, 0));
}
}
+void GuiCompleter::hidePopup()
+{
+ Cursor cur = gui_->bufferView().cursor();
+ cur.updateFlags(Update::None);
+
+ hidePopup(cur);
+
+ // redraw if needed
+ if (cur.disp_.update())
+ gui_->bufferView().processUpdateFlags(cur.disp_.update());
+}
+
+
+void GuiCompleter::hideInline()
+{
+ Cursor cur = gui_->bufferView().cursor();
+ cur.updateFlags(Update::None);
+
+ hideInline(cur);
+
+ // redraw if needed
+ if (cur.disp_.update())
+ gui_->bufferView().processUpdateFlags(cur.disp_.update());
+}
+
+
void GuiCompleter::activate()
{
if (!popupVisible() && !inlineVisible())
return;
- // Complete with current selection in the popup.
- QString s = currentCompletion();
- popup()->hide();
- popupActivated(s);
+ popupActivated(currentCompletion());
}
if (completion.size() <= prefix.size()) {
// finalize completion
cur.inset().insertCompletion(cur, docstring(), true);
- popup()->hide();
+
+ // hide popup and inline completion
+ hidePopup(cur);
+ hideInline(cur);
updateVisibility(false, false);
return;
}
return;
}
- // iterate through list until the s is found
- // FIXME: there must be a better way than this iteration
+ // find old selection in model
size_t i;
- for (i = 0; i < n; ++i) {
- QString const & is
- = model.data(model.index(i, 0), Qt::EditRole).toString();
- if (is == s)
- break;
+ if (modelSorting() == QCompleter::UnsortedModel) {
+ // In unsorted models, iterate through list until the s is found
+ for (i = 0; i < n; ++i) {
+ QString const & is
+ = model.data(model.index(i, 0), Qt::EditRole).toString();
+ if (is == s)
+ break;
+ }
+ } else {
+ // In sorted models, do binary search for s.
+ int l = 0;
+ int r = n - 1;
+ while (r >= l && l < int(n)) {
+ size_t mid = (r + l) / 2;
+ QString const & mids
+ = model.data(model.index(mid, 0),
+ Qt::EditRole).toString();
+
+ // left or right?
+ // FIXME: is this really the same order that the docstring
+ // from the CompletionList has?
+ int c = s.compare(mids, Qt::CaseSensitive);
+ if (c == 0) {
+ l = mid;
+ break;
+ } else if (l == r) {
+ l = n;
+ break;
+ } else if (c > 0)
+ // middle is not far enough
+ l = mid + 1;
+ else
+ // middle is too far
+ r = mid - 1;
+ }
+
+ // loop was left without finding anything
+ if (r < l)
+ i = n;
+ else
+ i = l;
+ BOOST_ASSERT(0 <= i && i <= n);
}
// select the first if none was found
}
-docstring GuiCompleter::longestUniqueCompletion() const {
+size_t commonPrefix(QString const & s1, QString const & s2)
+{
+ // find common prefix
+ size_t j;
+ size_t n1 = s1.length();
+ size_t n2 = s2.length();
+ for (j = 0; j < n1 && j < n2; ++j) {
+ if (s1.at(j) != s2.at(j))
+ break;
+ }
+ return j;
+}
+
+
+docstring GuiCompleter::longestUniqueCompletion() const
+{
QAbstractItemModel const & model = *popup()->model();
QString s = currentCompletion();
size_t n = model.rowCount();
- // iterate through the completions and cut off where s differs
- for (size_t i = 0; i < n && s.length() > 0; ++i) {
- QString const & is
- = model.data(model.index(i, 0), Qt::EditRole).toString();
-
- // find common prefix
- size_t j;
- size_t isn = is.length();
- size_t sn = s.length();
- for (j = 0; j < isn && j < sn; ++j) {
- if (s.at(j) != is.at(j))
- break;
+ if (modelSorting() == QCompleter::UnsortedModel) {
+ // For unsorted model we cannot do more than iteration.
+ // Iterate through the completions and cut off where s differs
+ for (size_t i = 0; i < n && s.length() > 0; ++i) {
+ QString const & is
+ = model.data(model.index(i, 0), Qt::EditRole).toString();
+
+ s = s.left(commonPrefix(is, s));
+ }
+ } else {
+ // For sorted models we can do binary search multiple times,
+ // each time to find the first string which has s not as prefix.
+ size_t i = 0;
+ while (i < n && s.length() > 0) {
+ // find first string that does not have s as prefix
+ // via binary search in [i,n-1]
+ size_t r = n - 1;
+ do {
+ // get common prefix with the middle string
+ size_t mid = (r + i) / 2;
+ QString const & mids
+ = model.data(model.index(mid, 0),
+ Qt::EditRole).toString();
+ size_t oldLen = s.length();
+ size_t len = commonPrefix(mids, s);
+ s = s.left(len);
+
+ // left or right?
+ if (oldLen == len) {
+ // middle is not far enough
+ i = mid + 1;
+ } else {
+ // middle is maybe too far
+ r = mid;
+ }
+ } while (r - i > 0 && i < n);
}
- s = s.left(j);
}
return from_utf8(fromqstr(s));
docstring prefix = cur.inset().completionPrefix(cur);
docstring postfix = from_utf8(fromqstr(completion.mid(prefix.length())));
cur.inset().insertCompletion(cur, postfix, true);
- updateVisibility(cur, false);
+ hidePopup(cur);
+ hideInline(cur);
if (cur.disp_.update())
gui_->bufferView().processUpdateFlags(cur.disp_.update());
Cursor cur = gui_->bufferView().cursor();
cur.updateFlags(Update::None);
- updateInline(cur, completion);
+ if (inlineVisible())
+ updateInline(cur, completion);
if (cur.disp_.update())
gui_->bufferView().processUpdateFlags(cur.disp_.update());