* completion infrastructure

* completion support for mathed
* experimental completion support for text


git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@23104 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Stefan Schimanski 2008-02-21 19:42:34 +00:00
parent a51525c416
commit 1bf7b59d60
44 changed files with 2106 additions and 39 deletions

View File

@ -717,6 +717,7 @@ src_frontends_qt4_header_files = Split('''
GuiClipboard.h
GuiCommandBuffer.h
GuiCommandEdit.h
GuiCompleter.h
GuiDelimiter.h
GuiDialog.h
GuiDocument.h
@ -807,6 +808,7 @@ src_frontends_qt4_files = Split('''
GuiClipboard.cpp
GuiCommandBuffer.cpp
GuiCommandEdit.cpp
GuiCompleter.cpp
GuiDelimiter.cpp
GuiDialog.cpp
GuiDocument.cpp

View File

@ -2664,4 +2664,10 @@ void Buffer::bufferErrors(TeXErrors const & terr, ErrorList & errorList) const
}
}
void Buffer::registerWord(docstring const & word)
{
words_.insert(word);
}
} // namespace lyx

View File

@ -367,6 +367,7 @@ public:
void updateMacroInstances() const;
typedef std::set<docstring> MacroNameSet;
/// List macro names of this buffer. the parent and the children
void listMacroNames(MacroNameSet & macros) const;
/// Write out all macros somewhere defined in the parent,
@ -453,6 +454,11 @@ public:
///
std::vector<Format const *> exportableFormats(bool only_viewable) const;
/// Register word for completion word list.
void registerWord(docstring const & word);
///
std::set<docstring> const & registeredWords() const { return words_; }
private:
/// search for macro in local (buffer) table or in children
MacroData const * getBufferMacro(docstring const & name,
@ -504,6 +510,8 @@ private:
//Signal setBusy(bool) = 0;
/// Reset autosave timers for all users.
Signal resetAutosaveTimers_;
std::set<docstring> words_;
};

View File

@ -238,6 +238,13 @@ struct BufferView::Private
///
vector<int> par_height_;
///
DocIterator inlineCompletionPos;
///
docstring inlineCompletion;
///
size_t inlineCompletionUniqueChars;
/// keyboard mapping object.
Intl intl_;
@ -778,6 +785,10 @@ void BufferView::showCursor(DocIterator const & dit)
return;
}
// fix inline completion position
if (d->inlineCompletionPos.fixIfBroken())
d->inlineCompletionPos = DocIterator();
tm.redoParagraph(bot_pit);
ParagraphMetrics const & pm = tm.parMetrics(bot_pit);
int offset = coordOffset(dit, dit.boundary()).y_;
@ -1753,6 +1764,10 @@ bool BufferView::singleParUpdate()
TextMetrics & tm = textMetrics(&buftext);
int old_height = tm.parMetrics(bottom_pit).height();
// make sure inline completion pointer is ok
if (d->inlineCompletionPos.fixIfBroken())
d->inlineCompletionPos = DocIterator();
// In Single Paragraph mode, rebreak only
// the (main text, not inset!) paragraph containing the cursor.
// (if this paragraph contains insets etc., rebreaking will
@ -1789,6 +1804,10 @@ void BufferView::updateMetrics()
TextMetrics & tm = textMetrics(&buftext);
// make sure inline completion pointer is ok
if (d->inlineCompletionPos.fixIfBroken())
d->inlineCompletionPos = DocIterator();
// Rebreak anchor paragraph.
tm.redoParagraph(d->anchor_pit_);
ParagraphMetrics & anchor_pm = tm.par_metrics_[d->anchor_pit_];
@ -2152,4 +2171,31 @@ void BufferView::insertPlaintextFile(FileName const & f, bool asParagraph)
buffer_.changed();
}
docstring const & BufferView::inlineCompletion() const
{
return d->inlineCompletion;
}
size_t const & BufferView::inlineCompletionUniqueChars() const
{
return d->inlineCompletionUniqueChars;
}
DocIterator const & BufferView::inlineCompletionPos() const
{
return d->inlineCompletionPos;
}
void BufferView::setInlineCompletion(DocIterator const & pos,
docstring const & completion, size_t uniqueChars)
{
d->inlineCompletionPos = pos;
d->inlineCompletion = completion;
d->inlineCompletionUniqueChars = min(completion.size(), uniqueChars);
}
} // namespace lyx

View File

@ -162,6 +162,15 @@ public:
/// return the pixel height of the document view.
int workHeight() const;
/// return the inline completion postfix.
docstring const & inlineCompletion() const;
/// return the number of unique characters in the inline completion.
size_t const & inlineCompletionUniqueChars() const;
/// return the position in the buffer of the inline completion postfix.
DocIterator const & inlineCompletionPos() const;
/// set the inline completion postfix and its position in the buffer.
void setInlineCompletion(DocIterator const & pos, docstring const & completion,
size_t uniqueChars = 0);
/// translate and insert a character, using the correct keymap.
void translateAndInsert(char_type c, Text * t, Cursor & cur);

View File

@ -103,6 +103,10 @@ ColorSet::ColorSet()
{ Color_foreground, N_("text"), "foreground", "black", "foreground" },
{ Color_selection, N_("selection"), "selection", "LightBlue", "selection" },
{ Color_latex, N_("LaTeX text"), "latex", "DarkRed", "latex" },
{ Color_inlinecompletion, N_("inline completion"),
"inlinecompletion", "grey70", "inlinecompletion" },
{ Color_nonunique_inlinecompletion, N_("non-unique inline completion"),
"nonuniqueinlinecompletion", "grey80", "nonuniqueinlinecompletion" },
{ Color_preview, N_("previewed snippet"), "preview", "black", "preview" },
{ Color_note, N_("note"), "note", "yellow", "note" },
{ Color_notebg, N_("note background"), "notebg", "yellow", "notebg" },

View File

@ -48,6 +48,10 @@ enum ColorCode
Color_latex,
/// The color used for previews
Color_preview,
/// Inline completion color
Color_inlinecompletion,
/// Inline completion color for the non-unique part
Color_nonunique_inlinecompletion,
/// Text color for notes
Color_note,

View File

@ -1292,6 +1292,12 @@ InsetMathUnknown * Cursor::activeMacro()
}
InsetMathUnknown const * Cursor::activeMacro() const
{
return inMacroMode() ? prevAtom().nucleus()->asUnknownInset() : 0;
}
void Cursor::pullArg()
{
// FIXME: Look here

View File

@ -350,6 +350,8 @@ public:
bool inMacroMode() const;
/// get access to the macro we are currently typing
InsetMathUnknown * activeMacro();
/// get access to the macro we are currently typing
InsetMathUnknown const * activeMacro() const;
/// replace selected stuff with at, placing the former
// selection in given cell of atom

View File

@ -428,6 +428,9 @@ void DocIterator::updateInsets(Inset * inset)
bool DocIterator::fixIfBroken()
{
if (empty())
return false;
// Go through the slice stack from the bottom.
// Check that all coordinates (idx, pit, pos) are correct and
// that the inset is the one which is claimed to be there

View File

@ -1467,6 +1467,30 @@ void LyXAction::init()
* \endvar
*/
{ LFUN_STATISTICS, "statistics", ReadOnly, System },
/*!
* \var lyx::kb_action lyx::LFUN_COMPLETION_INLINE
* \li Action: Show the inline completion at the cursor position.
* \li Syntax: completion-inline
* \li Origin: sts, Feb 19 2008
* \endvar
*/
{ LFUN_COMPLETION_INLINE, "completion-inline", ReadOnly | NoUpdate, Edit },
/*!
* \var lyx::kb_action lyx::LFUN_COMPLETION_POPUP
* \li Action: Show the completion popup at the cursor position.
* \li Syntax: completion-popup
* \li Origin: sts, Feb 19 2008
* \endvar
*/
{ LFUN_COMPLETION_POPUP, "completion-popup", ReadOnly | NoUpdate, Edit },
/*!
* \var lyx::kb_action lyx::LFUN_COMPLETION_COMPLETE
* \li Action: Try to complete the word or command at the cursor position.
* \li Syntax: complete
* \li Origin: sts, Feb 19 2007
* \endvar
*/
{ LFUN_COMPLETION_COMPLETE, "complete", SingleParUpdate, Edit },
{ LFUN_NOACTION, "", Noop, Hidden }
#ifndef DOXYGEN_SHOULD_SKIP_THIS

View File

@ -366,9 +366,17 @@ void LyXFunc::processKeySym(KeySymbol const & keysym, KeyModifier state)
dispatch(FuncRequest(LFUN_SELF_INSERT, arg,
FuncRequest::KEYBOARD));
LYXERR(Debug::KEY, "SelfInsert arg[`" << to_utf8(arg) << "']");
lyx_view_->updateCompletion(true, true);
}
} else {
dispatch(func);
if (func.action == LFUN_CHAR_DELETE_BACKWARD)
// backspace is not a self-insertion. But it
// still should not hide the completion popup.
// FIXME: more clever way to detect those movements
lyx_view_->updateCompletion(false, true);
else
lyx_view_->updateCompletion(false, false);
}
lyx_view_->restartCursor();
@ -459,6 +467,14 @@ FuncStatus LyXFunc::getStatus(FuncRequest const & cmd) const
enable = false;
break;
case LFUN_COMPLETION_POPUP:
case LFUN_COMPLETION_INLINE:
case LFUN_COMPLETION_COMPLETE:
if (lyx_view_)
return lyx_view_->getStatus(cmd);
enable = false;
break;
case LFUN_BUFFER_TOGGLE_READ_ONLY:
flag.setOnOff(buf->isReadonly());
break;
@ -1897,6 +1913,14 @@ void actOnUpdatedPrefs(LyXRC const & lyxrc_orig, LyXRC const & lyxrc_new)
case LyXRC::RC_BIBTEX_COMMAND:
case LyXRC::RC_BINDFILE:
case LyXRC::RC_CHECKLASTFILES:
case LyXRC::RC_COMPLETION_INLINE_DELAY:
case LyXRC::RC_COMPLETION_INLINE_MATH:
case LyXRC::RC_COMPLETION_INLINE_TEXT:
case LyXRC::RC_COMPLETION_INLINE_DOTS:
case LyXRC::RC_COMPLETION_POPUP_DELAY:
case LyXRC::RC_COMPLETION_POPUP_MATH:
case LyXRC::RC_COMPLETION_POPUP_TEXT:
case LyXRC::RC_COMPLETION_POPUP_AFTER_COMPLETE:
case LyXRC::RC_USELASTFILEPOS:
case LyXRC::RC_LOADSESSION:
case LyXRC::RC_CHKTEX_COMMAND:

View File

@ -63,6 +63,14 @@ keyword_item lyxrcTags[] = {
{ "\\bind_file", LyXRC::RC_BINDFILE },
{ "\\check_lastfiles", LyXRC::RC_CHECKLASTFILES },
{ "\\chktex_command", LyXRC::RC_CHKTEX_COMMAND },
{ "\\completion_inline_delay", LyXRC::RC_COMPLETION_INLINE_DELAY },
{ "\\completion_inline_math", LyXRC::RC_COMPLETION_INLINE_MATH },
{ "\\completion_inline_text", LyXRC::RC_COMPLETION_INLINE_TEXT },
{ "\\completion_inline_dots", LyXRC::RC_COMPLETION_INLINE_DOTS },
{ "\\completion_popup_delay", LyXRC::RC_COMPLETION_POPUP_DELAY },
{ "\\completion_popup_math", LyXRC::RC_COMPLETION_POPUP_MATH },
{ "\\completion_popup_text", LyXRC::RC_COMPLETION_POPUP_TEXT },
{ "\\completion_popup_after_complete", LyXRC::RC_COMPLETION_POPUP_AFTER_COMPLETE },
{ "\\converter", LyXRC::RC_CONVERTER },
{ "\\converter_cache_maxage", LyXRC::RC_CONVERTER_CACHE_MAXAGE },
{ "\\copier", LyXRC::RC_COPIER },
@ -280,6 +288,9 @@ void LyXRC::setDefaults() {
use_tooltip = true;
use_pixmap_cache = false;
converter_cache_maxage = 6 * 30 * 24 * 3600; // 6 months
user_name = to_utf8(support::user_name());
user_email = to_utf8(support::user_email());
// Fullscreen settings
full_screen_limit = false;
full_screen_toolbars = true;
@ -287,9 +298,14 @@ void LyXRC::setDefaults() {
full_screen_scrollbar = true;
full_screen_width = 700;
user_name = to_utf8(support::user_name());
user_email = to_utf8(support::user_email());
completion_popup_math = true;
completion_popup_text = false;
completion_popup_delay = 2.0;
completion_popup_after_complete = true;
completion_inline_math = true;
completion_inline_text = false;
completion_inline_dots = -1;
completion_inline_delay = 0.2;
}
@ -755,6 +771,54 @@ int LyXRC::read(Lexer & lexrc)
}
break;
case RC_COMPLETION_INLINE_DELAY:
if (lexrc.next()) {
completion_inline_delay = lexrc.getFloat();
}
break;
case RC_COMPLETION_INLINE_MATH:
if (lexrc.next()) {
completion_inline_math = lexrc.getBool();
}
break;
case RC_COMPLETION_INLINE_TEXT:
if (lexrc.next()) {
completion_inline_text = lexrc.getBool();
}
break;
case RC_COMPLETION_INLINE_DOTS:
if (lexrc.next()) {
completion_inline_dots = lexrc.getInteger();
}
break;
case RC_COMPLETION_POPUP_DELAY:
if (lexrc.next()) {
completion_popup_delay = lexrc.getFloat();
}
break;
case RC_COMPLETION_POPUP_MATH:
if (lexrc.next()) {
completion_popup_math = lexrc.getBool();
}
break;
case RC_COMPLETION_POPUP_TEXT:
if (lexrc.next()) {
completion_popup_text = lexrc.getBool();
}
break;
case RC_COMPLETION_POPUP_AFTER_COMPLETE:
if (lexrc.next()) {
completion_popup_after_complete = lexrc.getBool();
}
break;
case RC_NUMLASTFILES:
if (lexrc.next()) {
num_lastfiles = lexrc.getInteger();
@ -2041,6 +2105,69 @@ void LyXRC::write(ostream & os, bool ignore_system_lyxrc, string const & name) c
}
if (tag != RC_LAST)
break;
case RC_COMPLETION_INLINE_DELAY:
if (ignore_system_lyxrc ||
completion_inline_delay != system_lyxrc.completion_inline_delay) {
os << "\\completion_inline_delay " << completion_inline_delay << '\n';
}
if (tag != RC_LAST)
break;
case RC_COMPLETION_INLINE_MATH:
if (ignore_system_lyxrc ||
completion_inline_math != system_lyxrc.completion_inline_math) {
os << "\\completion_inline_math "
<< convert<string>(completion_inline_math) << '\n';
}
if (tag != RC_LAST)
break;
case RC_COMPLETION_INLINE_TEXT:
if (ignore_system_lyxrc ||
completion_inline_text != system_lyxrc.completion_inline_text) {
os << "\\completion_inline_text "
<< convert<string>(completion_inline_text) << '\n';
}
if (tag != RC_LAST)
break;
case RC_COMPLETION_INLINE_DOTS:
if (ignore_system_lyxrc ||
completion_inline_dots != system_lyxrc.completion_inline_dots) {
os << "\\completion_inline_dots "
<< convert<string>(completion_inline_dots) << '\n';
}
if (tag != RC_LAST)
break;
case RC_COMPLETION_POPUP_DELAY:
if (ignore_system_lyxrc ||
completion_popup_delay != system_lyxrc.completion_popup_delay) {
os << "\\completion_popup_delay " << completion_popup_delay << '\n';
}
if (tag != RC_LAST)
break;
case RC_COMPLETION_POPUP_MATH:
if (ignore_system_lyxrc ||
completion_popup_math != system_lyxrc.completion_popup_math) {
os << "\\completion_popup_math "
<< convert<string>(completion_popup_math) << '\n';
}
if (tag != RC_LAST)
break;
case RC_COMPLETION_POPUP_TEXT:
if (ignore_system_lyxrc ||
completion_popup_text != system_lyxrc.completion_popup_text) {
os << "\\completion_popup_text "
<< convert<string>(completion_popup_text) << '\n';
}
if (tag != RC_LAST)
break;
case RC_COMPLETION_POPUP_AFTER_COMPLETE:
if (ignore_system_lyxrc ||
completion_popup_after_complete
!= system_lyxrc.completion_popup_after_complete) {
os << "\\completion_popup_after_complete "
<< convert<string>(completion_popup_after_complete) << '\n';
}
if (tag != RC_LAST)
break;
case RC_NUMLASTFILES:
if (ignore_system_lyxrc ||
num_lastfiles != system_lyxrc.num_lastfiles) {
@ -2637,10 +2764,42 @@ string const LyXRC::getDescription(LyXRCTags tag)
break;
case RC_MOUSE_WHEEL_SPEED:
str = bformat(_("The scrolling speed of the mouse wheel. "),
str = bformat(_("The scrolling speed of the mouse wheel."),
maxlastfiles);
break;
case RC_COMPLETION_POPUP_DELAY:
str = _("The completion popup delay.");
break;
case RC_COMPLETION_POPUP_MATH:
str = _("Select to display the completion popup in math mode.");
break;
case RC_COMPLETION_POPUP_TEXT:
str = _("Select to display the completion popup in text mode.");
break;
case RC_COMPLETION_POPUP_AFTER_COMPLETE:
str = _("Show the completion popup without delay after non-unique completion attempt.");
break;
case RC_COMPLETION_POPUP_DELAY:
str = _("The inline completion delay.");
break;
case RC_COMPLETION_INLINE_MATH:
str = _("Select to display the inline completion in math mode.");
break;
case RC_COMPLETION_INLINE_TEXT:
str = _("Select to display the inline completion in text mode.");
break;
case RC_COMPLETION_INLINE_DOTS:
str = _("Use \"...\" to shorten long completions.");
break;
case RC_NUMLASTFILES:
str = bformat(_("Maximal number of lastfiles. Up to %1$d can appear in the file menu."),
maxlastfiles);

View File

@ -49,6 +49,14 @@ public:
RC_BINDFILE,
RC_CHECKLASTFILES,
RC_CHKTEX_COMMAND,
RC_COMPLETION_INLINE_DELAY,
RC_COMPLETION_INLINE_MATH,
RC_COMPLETION_INLINE_TEXT,
RC_COMPLETION_INLINE_DOTS,
RC_COMPLETION_POPUP_DELAY,
RC_COMPLETION_POPUP_MATH,
RC_COMPLETION_POPUP_TEXT,
RC_COMPLETION_POPUP_AFTER_COMPLETE,
RC_CONVERTER,
RC_CONVERTER_CACHE_MAXAGE,
RC_COPIER,
@ -409,6 +417,22 @@ public:
bool full_screen_limit;
/// Width of limited screen (in pixels) in fullscreen mode
int full_screen_width;
///
double completion_inline_delay;
///
bool completion_inline_math;
///
bool completion_inline_text;
///
int completion_inline_dots;
///
double completion_popup_delay;
///
bool completion_popup_math;
///
bool completion_popup_text;
///
bool completion_popup_after_complete;
};

View File

@ -564,6 +564,8 @@ void Text::insertChar(Cursor & cur, char_type c)
void Text::charInserted(Cursor & cur)
{
Paragraph & par = cur.paragraph();
// Here we call finishUndo for every 20 characters inserted.
// This is from my experience how emacs does it. (Lgb)
static unsigned int counter;
@ -573,6 +575,27 @@ void Text::charInserted(Cursor & cur)
cur.finishUndo();
counter = 0;
}
// register word if a non-letter was entered
if (cur.pos() > 1
&& par.isLetter(cur.pos() - 2)
&& !par.isLetter(cur.pos() - 1)) {
// get the word in front of cursor
BOOST_ASSERT(this == cur.text());
CursorSlice focus = cur.top();
focus.backwardPos();
CursorSlice from = focus;
CursorSlice to = focus;
getWord(from, to, PREVIOUS_WORD);
if (focus == from || to == from)
return;
docstring word
= par.asString(cur.buffer(), from.pos(), to.pos(), false);
// register words longer than 5 characters
if (word.length() > 5)
cur.buffer().registerWord(word);
}
}

View File

@ -730,6 +730,16 @@ pit_type TextMetrics::rowBreakPoint(int width, pit_type const pit,
pos_type const body_pos = par.beginOfBody();
// check for possible inline completion
DocIterator const & inlineCompletionPos = bv_->inlineCompletionPos();
pos_type inlineCompletionVPos = -1;
if (inlineCompletionPos.inTexted()
&& inlineCompletionPos.text() == text_
&& inlineCompletionPos.pit() == pit) {
// draw visually behind the previous character
// FIXME: probably special RTL handling needed here
inlineCompletionVPos = inlineCompletionPos.pos() - 1;
}
// Now we iterate through until we reach the right margin
// or the end of the par, then choose the possible break
@ -748,6 +758,13 @@ pit_type TextMetrics::rowBreakPoint(int width, pit_type const pit,
for ( ; i < end; ++i, ++fi) {
int thiswidth = pm.singleWidth(i, *fi);
// add inline completion width
if (inlineCompletionVPos == i) {
docstring const & completion = bv_->inlineCompletion();
if (completion.length() > 0)
thiswidth += theFontMetrics(*fi).width(completion);
}
// add the auto-hfill from label end to the body
if (body_pos && i == body_pos) {
FontMetrics const & fm = theFontMetrics(
@ -830,6 +847,17 @@ int TextMetrics::rowWidth(int right_margin, pit_type const pit,
int w = leftMargin(max_width_, pit, first);
int label_end = labelEnd(pit);
// check for possible inline completion
DocIterator const & inlineCompletionPos = bv_->inlineCompletionPos();
pos_type inlineCompletionVPos = -1;
if (inlineCompletionPos.inTexted()
&& inlineCompletionPos.text() == text_
&& inlineCompletionPos.pit() == pit) {
// draw visually behind the previous character
// FIXME: probably special RTL handling needed here
inlineCompletionVPos = inlineCompletionPos.pos() - 1;
}
pos_type const body_pos = par.beginOfBody();
pos_type i = first;
@ -845,6 +873,13 @@ int TextMetrics::rowWidth(int right_margin, pit_type const pit,
w = max(w, label_end);
}
w += pm.singleWidth(i, *fi);
// add inline completion width
if (inlineCompletionVPos == i) {
docstring const & completion = bv_->inlineCompletion();
if (completion.length() > 0)
w += theFontMetrics(*fi).width(completion);
}
}
}
@ -862,7 +897,7 @@ int TextMetrics::rowWidth(int right_margin, pit_type const pit,
Dimension TextMetrics::rowHeight(pit_type const pit, pos_type const first,
pos_type const end) const
pos_type const end, bool topBottomSpace) const
{
Paragraph const & par = text_->getPar(pit);
// get the maximum ascent and the maximum descent
@ -933,7 +968,7 @@ Dimension TextMetrics::rowHeight(pit_type const pit, pos_type const first,
ParagraphList const & pars = text_->paragraphs();
// is it a top line?
if (first == 0) {
if (first == 0 && topBottomSpace) {
BufferParams const & bufparams = buffer.params();
// some parskips VERY EASY IMPLEMENTATION
if (bufparams.paragraph_separation
@ -1004,7 +1039,7 @@ Dimension TextMetrics::rowHeight(pit_type const pit, pos_type const first,
}
// is it a bottom line?
if (end >= par.size()) {
if (end >= par.size() && topBottomSpace) {
// add the layout spaces, for example before and after
// a section, or between the items of a itemize or enumerate
// environment
@ -1039,7 +1074,7 @@ Dimension TextMetrics::rowHeight(pit_type const pit, pos_type const first,
// following code in another method specially tailored for the
// main Text. The following test is thus bogus.
// Top and bottom margin of the document (only at top-level)
if (main_text_) {
if (main_text_ && topBottomSpace) {
if (pit == 0 && first == 0)
maxasc += 20;
if (pit + 1 == pit_type(pars.size()) &&

View File

@ -124,6 +124,15 @@ public:
void drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) const;
/// Returns the height of the row (width member is set to 0).
/// If \c topBottomSpace is true, extra space is added for the
/// top and bottom row.
Dimension rowHeight(
pit_type const pit,
pos_type const first,
pos_type const end,
bool topBottomSpace = true) const;
private:
///
ParagraphMetrics & parMetrics(pit_type, bool redo_paragraph);
@ -142,7 +151,7 @@ private:
pit_type first
) const;
/// sets row.width to the minimum space a row needs on the screen in pixel
/// returns the minimum space a row needs on the screen in pixel
int rowWidth(
int right_margin,
pit_type const pit,
@ -150,13 +159,6 @@ private:
pos_type const end
) const;
/// Calculate and set the height of the row (width member is set to 0)
Dimension rowHeight(
pit_type const pit,
pos_type const first,
pos_type const end
) const;
/// draw selection for a single row
void drawRowSelection(PainterInfo & pi, int x, Row const & row,
DocIterator const & beg, DocIterator const & end,

View File

@ -91,6 +91,12 @@ public:
///
virtual void restartCursor() = 0;
/// Update the completion popup and the inline completion state.
/// If \c start is true, then a new completion might be started.
/// If \c keep is true, an active completion will be kept active
/// even though the cursor moved.
virtual void updateCompletion(bool start, bool keep) = 0;
private:
/// noncopyable
LyXView(LyXView const &);

View File

@ -128,10 +128,11 @@ void GuiCommandBuffer::cancel()
void GuiCommandBuffer::dispatch()
{
dispatch(fromqstr(edit_->text()));
QString cmd = edit_->text();
view_->setFocus();
edit_->setText(QString());
edit_->clearFocus();
dispatch(fromqstr(cmd));
}

View File

@ -0,0 +1,585 @@
/**
* \file GuiCompleter.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Stefan Schimanski
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "GuiWorkArea.h"
#include "Buffer.h"
#include "BufferView.h"
#include "Cursor.h"
#include "Dimension.h"
#include "FuncRequest.h"
#include "GuiView.h"
#include "LyXFunc.h"
#include "LyXRC.h"
#include "version.h"
#include "support/debug.h"
#include <QApplication>
#include <QAbstractListModel>
#include <QHeaderView>
#include <QPainter>
#include <QPixmapCache>
#include <QScrollBar>
#include <QItemDelegate>
#include <QTreeView>
#include <QTimer>
using namespace std;
using namespace lyx::support;
namespace lyx {
namespace frontend {
class PixmapItemDelegate : public QItemDelegate {
public:
explicit PixmapItemDelegate(QObject *parent = 0)
: QItemDelegate(parent) {}
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
QStyleOptionViewItemV3 opt = setOptions(index, option);
QVariant value = index.data(Qt::DisplayRole);
QPixmap pixmap = qvariant_cast<QPixmap>(value);
const QSize size = pixmap.size();
// draw
painter->save();
drawBackground(painter, opt, index);
painter->drawPixmap(option.rect.left() + (16 - size.width()) / 2,
option.rect.top() + (option.rect.height() - size.height()) / 2,
pixmap);
drawFocus(painter, opt, option.rect);
painter->restore();
}
};
class GuiCompletionModel : public QAbstractListModel {
public:
///
GuiCompletionModel(QObject * parent, Inset::CompletionListPtr l)
: QAbstractListModel(parent), list(l) {}
///
int columnCount(const QModelIndex & parent = QModelIndex()) const
{
return 2;
}
///
int rowCount(const QModelIndex & parent = QModelIndex()) const
{
if (list.get() == 0)
return 0;
else
return list->size();
}
///
QVariant data(const QModelIndex & index, int role) const
{
if (list.get() == 0)
return QVariant();
if (index.row() < 0 || index.row() >= rowCount())
return QVariant();
if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
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()));
if (!QPixmapCache::find("completion" + name, scaled)) {
// load icon from disk
QPixmap p = QPixmap(name);
// scale it to 16x16 or smaller
scaled = p.scaled(min(16, p.width()), min(16, p.height()),
Qt::KeepAspectRatio, Qt::SmoothTransformation);
QPixmapCache::insert("completion" + name, scaled);
}
return scaled;
}
return QVariant();
}
private:
Inset::CompletionListPtr list;
};
GuiCompleter::GuiCompleter(GuiWorkArea * gui, QObject * parent)
: QCompleter(parent), gui_(gui)
{
// Setup the completion popup
setModel(new GuiCompletionModel(this, Inset::CompletionListPtr()));
setCompletionMode(QCompleter::PopupCompletion);
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->header()->hide();
listView->setIndentation(0);
setPopup(listView);
popup()->setItemDelegateForColumn(1, new PixmapItemDelegate(popup()));
// create timeout timers
popup_timer_.setSingleShot(true);
inline_timer_.setSingleShot(true);
connect(this, SIGNAL(highlighted(const QString &)),
this, SLOT(popupHighlighted(const QString &)));
connect(this, SIGNAL(activated(const QString &)),
this, SLOT(popupActivated(const QString &)));
connect(&popup_timer_, SIGNAL(timeout()),
this, SLOT(showPopup()));
connect(&inline_timer_, SIGNAL(timeout()),
this, SLOT(showInline()));
}
GuiCompleter::~GuiCompleter()
{
popup()->hide();
}
bool GuiCompleter::eventFilter(QObject * watched, QEvent * e)
{
// hijack back the tab key from the popup
// (which stole it from the workspace before)
if (e->type() == QEvent::KeyPress && popupVisible()) {
QKeyEvent *ke = static_cast<QKeyEvent *>(e);
switch (ke->key()) {
case Qt::Key_Tab:
tab();
ke->accept();
return true;
default: break;
}
}
return QCompleter::eventFilter(watched, e);
}
bool GuiCompleter::popupPossible(Cursor const & cur) const
{
return QApplication::activeWindow()
&& gui_->hasFocus()
&& cur.inset().completionSupported(cur);
}
bool GuiCompleter::inlinePossible(Cursor const & cur) const
{
return cur.inset().inlineCompletionSupported(cur);
}
bool GuiCompleter::popupVisible() const
{
return popup()->isVisible();
}
bool GuiCompleter::inlineVisible() const
{
return !gui_->bufferView().inlineCompletionPos().empty();
}
void GuiCompleter::updateVisibility(Cursor & cur, bool start, bool keep, bool cursorInView)
{
// parameters which affect the completion
bool moved = cur != old_cursor_;
if (moved)
old_cursor_ = cur;
bool possiblePopupState = popupPossible(cur) && cursorInView;
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();
}
// 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(DocIterator(), docstring());
cur.updateFlags(Update::Force | Update::SinglePar);
}
}
// we inserted something and are in a possible popup state?
if (!popupVisible() && possiblePopupState && start
&& cur.inset().automaticPopupCompletion())
popup_timer_.start(lyxrc.completion_popup_delay * 1000.0);
// we inserted something and are in a possible inline completion state?
if (!inlineVisible() && possibleInlineState && start
&& cur.inset().automaticInlineCompletion())
inline_timer_.start(lyxrc.completion_inline_delay * 1000.0);
// update prefix if popup is visible or if it will be visible soon
if (popupVisible() || inlineVisible()
|| popup_timer_.isActive() || inline_timer_.isActive())
updatePrefix(cur);
}
void GuiCompleter::updateVisibility(bool start, bool keep)
{
Cursor cur = gui_->bufferView().cursor();
updateVisibility(cur, start, keep);
if (cur.disp_.update())
gui_->bufferView().processUpdateFlags(cur.disp_.update());
}
void GuiCompleter::updatePrefix(Cursor & cur)
{
// get new prefix. Do nothing if unchanged
QString newPrefix = toqstr(cur.inset().completionPrefix(cur));
if (newPrefix == completionPrefix())
return;
// value which should be kept selected
QString old = currentCompletion();
if (old.length() == 0)
old = last_selection_;
// update completer to new prefix
setCompletionPrefix(newPrefix);
// update popup because its size might have changed
if (popupVisible())
updatePopup(cur);
// restore old selection
setCurrentCompletion(old);
// 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;
// update inline completion because the default
// completion string might have changed
if (inlineVisible())
updateInline(cur, s);
}
void GuiCompleter::updateInline(Cursor & cur, QString const & completion)
{
if (!cur.inset().inlineCompletionSupported(cur))
return;
// compute postfix
docstring prefix = cur.inset().completionPrefix(cur);
docstring postfix = from_utf8(fromqstr(completion.mid(prefix.length())));
// shorten it if necessary
if (lyxrc.completion_inline_dots != -1
&& postfix.size() > unsigned(lyxrc.completion_inline_dots))
postfix = postfix.substr(0, lyxrc.completion_inline_dots - 1) + "...";
// set inline completion at cursor position
size_t uniqueTo = max(longestUniqueCompletion().size(), prefix.size());
gui_->bufferView().setInlineCompletion(cur, postfix, uniqueTo - prefix.size());
cur.updateFlags(Update::Force | Update::SinglePar);
}
void GuiCompleter::updatePopup(Cursor & cur)
{
if (!cur.inset().completionSupported(cur))
return;
if (completionCount() == 0)
return;
// get dimensions of completion prefix
Dimension dim;
int x;
int y;
cur.inset().completionPosAndDim(cur, x, y, dim);
QRect insetRect = 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());
// update highlight
updateInline(cur, currentCompletion());
}
void GuiCompleter::updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate)
{
// value which should be kept selected
QString old = currentCompletion();
if (old.length() == 0)
old = last_selection_;
// set new model
setModel(new GuiCompletionModel(this, cur.inset().completionList(cur)));
// show popup
if (popupUpdate)
updatePopup(cur);
// restore old selection
setCurrentCompletion(old);
// 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;
// show inline completion
if (inlineUpdate)
updateInline(cur, currentCompletion());
}
void GuiCompleter::showPopup(Cursor & cur)
{
if (!popupPossible(cur))
return;
updateModel(cur, true, inlineVisible());
updatePrefix(cur);
}
void GuiCompleter::showInline(Cursor & cur)
{
if (!inlinePossible(cur))
return;
updateModel(cur, popupVisible(), true);
updatePrefix(cur);
}
void GuiCompleter::showPopup()
{
Cursor cur = gui_->bufferView().cursor();
showPopup(cur);
// redraw if needed
if (cur.disp_.update())
gui_->bufferView().processUpdateFlags(cur.disp_.update());
}
void GuiCompleter::showInline()
{
Cursor cur = gui_->bufferView().cursor();
showInline(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);
}
void GuiCompleter::tab()
{
BufferView * bv = &gui_->bufferView();
Cursor & cur = bv->cursor();
// check that inline completion is active
if (!inlineVisible()) {
// try to activate the inline completion
if (cur.inset().inlineCompletionSupported(cur)) {
showInline();
return;
}
// or try popup
if (!popupVisible() && cur.inset().completionSupported(cur)) {
showPopup();
return;
}
return;
}
// If completion is active, at least complete by one character
docstring prefix = cur.inset().completionPrefix(cur);
docstring completion = from_utf8(fromqstr(currentCompletion()));
if (completion.size() <= prefix.size()) {
// finalize completion
cur.inset().insertCompletion(cur, docstring(), true);
popup()->hide();
updateVisibility(false, false);
return;
}
docstring nextchar = completion.substr(prefix.size(), 1);
if (!cur.inset().insertCompletion(cur, nextchar, false))
return;
updatePrefix(cur);
// try to complete as far as it is unique
docstring longestCompletion = longestUniqueCompletion();
prefix = cur.inset().completionPrefix(cur);
docstring postfix = longestCompletion.substr(min(longestCompletion.size(), prefix.size()));
cur.inset().insertCompletion(cur, postfix, false);
old_cursor_ = bv->cursor();
updatePrefix(cur);
// show popup without delay because the completion was not unique
if (lyxrc.completion_popup_after_complete
&& !popupVisible()
&& popup()->model()->rowCount() > 1)
popup_timer_.start(0);
// redraw if needed
if (cur.disp_.update())
gui_->bufferView().processUpdateFlags(cur.disp_.update());
}
QString GuiCompleter::currentCompletion() const
{
if (!popup()->selectionModel()->hasSelection())
return QString();
// Not sure if this is bug in Qt: currentIndex() always
// return the first element in the list.
QModelIndex idx = popup()->currentIndex();
return popup()->model()->data(idx, Qt::EditRole).toString();
}
void GuiCompleter::setCurrentCompletion(QString const & s)
{
QAbstractItemModel const & model = *popup()->model();
size_t n = model.rowCount();
if (n == 0)
return;
// select the first if s is empty
if (s.length() == 0) {
popup()->setCurrentIndex(model.index(0, 0));
return;
}
// iterate through list until the s is found
// FIXME: there must be a better way than this iteration
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;
}
// select the first if none was found
if (i == n)
i = 0;
popup()->setCurrentIndex(model.index(i, 0));
}
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;
}
s = s.left(j);
}
return from_utf8(fromqstr(s));
}
void GuiCompleter::popupActivated(const QString & completion)
{
Cursor & cur = gui_->bufferView().cursor();
docstring prefix = cur.inset().completionPrefix(cur);
docstring postfix = from_utf8(fromqstr(completion.mid(prefix.length())));
cur.inset().insertCompletion(cur, postfix, true);
updateVisibility(cur, false);
if (cur.disp_.update())
gui_->bufferView().processUpdateFlags(cur.disp_.update());
}
void GuiCompleter::popupHighlighted(const QString & completion)
{
Cursor cur = gui_->bufferView().cursor();
updateInline(cur, completion);
if (cur.disp_.update())
gui_->bufferView().processUpdateFlags(cur.disp_.update());
}
} // namespace frontend
} // namespace lyx
#include "GuiCompleter_moc.cpp"

View File

@ -0,0 +1,115 @@
// -*- C++ -*-
/**
* \file GuiCompleter.h
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Stefan Schimanski
*
* Full author contact details are available in file CREDITS.
*/
#ifndef GUICOMPLETER_H
#define GUICOMPLETER_H
#include "frontends/WorkArea.h"
#include "DocIterator.h"
#include "FuncRequest.h"
#include "qt_helpers.h"
#include "support/docstring.h"
#include <QAbstractItemModel>
#include <QCompleter>
#include <QStringListModel>
#include <QTimer>
namespace lyx {
class Buffer;
namespace frontend {
class GuiWorkArea;
class GuiCompleter : private QCompleter
{
Q_OBJECT
public:
///
GuiCompleter(GuiWorkArea * gui, QObject * parent = 0);
///
virtual ~GuiCompleter();
///
bool popupVisible() const;
///
bool inlineVisible() const;
///
bool popupPossible(Cursor const & cur) const;
///
bool inlinePossible(Cursor const & cur) const;
/// Activate the current completion, i.e. finalize it.
void activate();
/// Do a completion as far as it is unique, but at least one character.
void tab();
/// Update the visibility of the popup and the inline completion.
/// This method might set the update flags of the cursor to request
/// a redraw.
void updateVisibility(Cursor & cur, bool start, bool keep, bool cursorInView = true);
/// Update the visibility of the popup and the inline completion.
/// This method handles the redraw if needed.
void updateVisibility(bool start, bool keep);
///
QString currentCompletion() const;
///
docstring longestUniqueCompletion() const;
public Q_SLOTS:
/// Show the popup.
void showPopup();
/// Show the inline completion.
void showInline();
private Q_SLOTS:
///
void popupActivated(const QString & completion);
///
void popupHighlighted(const QString & completion);
private:
///
void setCurrentCompletion(QString const & s);
///
void showPopup(Cursor & cur);
///
void showInline(Cursor & cur);
///
void updatePopup(Cursor & cur);
///
void updateInline(Cursor & cur, QString const & completion);
///
void updatePrefix(Cursor & cur);
///
void updateModel(Cursor & cur, bool popupUpdate, bool inlineUpdate);
///
bool eventFilter(QObject * watched, QEvent * event);
///
GuiWorkArea * gui_;
///
DocIterator old_cursor_;
///
QTimer popup_timer_;
///
QTimer inline_timer_;
///
QString last_selection_;
}; // GuiCompleter
} // namespace frontend
} // namespace lyx
#endif // GUICOMPLETER_H

View File

@ -260,6 +260,20 @@ PrefInput::PrefInput(GuiPreferences * form, QWidget * parent)
this, SIGNAL(changed()));
connect(secondKeymapED, SIGNAL(textChanged(QString)),
this, SIGNAL(changed()));
connect(inlineDelaySB, SIGNAL(valueChanged(double)),
this, SIGNAL(changed()));
connect(inlineMathCB, SIGNAL(clicked()),
this, SIGNAL(changed()));
connect(inlineTextCB, SIGNAL(clicked()),
this, SIGNAL(changed()));
connect(inlineDotsCB, SIGNAL(clicked()),
this, SIGNAL(changed()));
connect(popupDelaySB, SIGNAL(valueChanged(double)),
this, SIGNAL(changed()));
connect(popupMathCB, SIGNAL(clicked()),
this, SIGNAL(changed()));
connect(popupTextCB, SIGNAL(clicked()),
this, SIGNAL(changed()));
connect(mouseWheelSpeedSB, SIGNAL(valueChanged(double)),
this, SIGNAL(changed()));
}
@ -271,6 +285,15 @@ void PrefInput::apply(LyXRC & rc) const
rc.use_kbmap = keymapCB->isChecked();
rc.primary_kbmap = internal_path(fromqstr(firstKeymapED->text()));
rc.secondary_kbmap = internal_path(fromqstr(secondKeymapED->text()));
rc.completion_inline_delay = inlineDelaySB->value();
rc.completion_inline_math = inlineMathCB->isChecked();
rc.completion_inline_text = inlineTextCB->isChecked();
rc.completion_inline_dots = inlineDotsCB->isChecked() ? 13 : -1;
rc.completion_popup_delay = popupDelaySB->value();
rc.completion_popup_math = popupMathCB->isChecked();
rc.completion_popup_text = popupTextCB->isChecked();
rc.completion_popup_after_complete
= popupAfterCompleteCB->isChecked();
rc.mouse_wheel_speed = mouseWheelSpeedSB->value();
}
@ -281,6 +304,14 @@ void PrefInput::update(LyXRC const & rc)
keymapCB->setChecked(rc.use_kbmap);
firstKeymapED->setText(toqstr(external_path(rc.primary_kbmap)));
secondKeymapED->setText(toqstr(external_path(rc.secondary_kbmap)));
inlineDelaySB->setValue(rc.completion_inline_delay);
inlineMathCB->setChecked(rc.completion_inline_math);
inlineTextCB->setChecked(rc.completion_inline_text);
inlineDotsCB->setChecked(rc.completion_inline_dots != -1);
popupDelaySB->setValue(rc.completion_popup_delay);
popupMathCB->setChecked(rc.completion_popup_math);
popupTextCB->setChecked(rc.completion_popup_text);
popupAfterCompleteCB->setChecked(rc.completion_popup_after_complete);
mouseWheelSpeedSB->setValue(rc.mouse_wheel_speed);
}

View File

@ -1044,6 +1044,24 @@ FuncStatus GuiView::getStatus(FuncRequest const & cmd)
break;
}
case LFUN_COMPLETION_INLINE:
if (!d.current_work_area_
|| !d.current_work_area_->completer().inlinePossible(view()->cursor()))
enable = false;
break;
case LFUN_COMPLETION_POPUP:
if (!d.current_work_area_
|| !d.current_work_area_->completer().popupPossible(view()->cursor()))
enable = false;
break;
case LFUN_COMPLETION_COMPLETE:
if (!d.current_work_area_
|| !d.current_work_area_->completer().inlinePossible(view()->cursor()))
enable = false;
break;
default:
if (!view()) {
enable = false;
@ -1815,6 +1833,11 @@ bool GuiView::dispatch(FuncRequest const & cmd)
setFocus();
break;
case LFUN_COMPLETION_INLINE:
if (d.current_work_area_)
d.current_work_area_->completer().showInline();
break;
case LFUN_SPLIT_VIEW:
if (Buffer * buf = buffer()) {
string const orientation = cmd.getArg(0);
@ -1824,7 +1847,6 @@ bool GuiView::dispatch(FuncRequest const & cmd)
GuiWorkArea * wa = twa->addWorkArea(*buf, *this);
setCurrentWorkArea(wa);
}
break;
case LFUN_CLOSE_TAB_GROUP:
if (TabWorkArea * twa = d.currentTabWorkArea()) {
@ -1836,6 +1858,16 @@ bool GuiView::dispatch(FuncRequest const & cmd)
// No more work area, switch to the background widget.
d.setBackground();
}
case LFUN_COMPLETION_POPUP:
if (d.current_work_area_)
d.current_work_area_->completer().showPopup();
break;
case LFUN_COMPLETION_COMPLETE:
if (d.current_work_area_)
d.current_work_area_->completer().tab();
break;
default:
@ -1939,6 +1971,13 @@ void GuiView::restartCursor()
updateToolbars();
}
void GuiView::updateCompletion(bool start, bool keep)
{
if (d.current_work_area_)
d.current_work_area_->completer().updateVisibility(start, keep);
}
namespace {
// This list should be kept in sync with the list of insets in

View File

@ -246,6 +246,9 @@ public:
///
void disconnectDialog(std::string const & name);
///
void updateCompletion(bool start, bool keep);
private:
///
void lfunUiToggle(FuncRequest const & cmd);

View File

@ -189,9 +189,9 @@ SyntheticMouseEvent::SyntheticMouseEvent()
GuiWorkArea::GuiWorkArea(Buffer & buffer, GuiView & lv)
: buffer_view_(new BufferView(buffer)), lyx_view_(&lv),
cursor_visible_(false),
need_resize_(false), schedule_redraw_(false),
preedit_lines_(1)
cursor_visible_(false),
need_resize_(false), schedule_redraw_(false),
preedit_lines_(1), completer_(this)
{
buffer.workAreaManager().add(this);
// Setup the signals
@ -242,7 +242,6 @@ GuiWorkArea::GuiWorkArea(Buffer & buffer, GuiView & lv)
}
GuiWorkArea::~GuiWorkArea()
{
buffer_view_->buffer().workAreaManager().remove(this);
@ -386,6 +385,7 @@ void GuiWorkArea::dispatch(FuncRequest const & cmd0, KeyModifier mod)
// Skip these when selecting
if (cmd.action != LFUN_MOUSE_MOTION) {
completer_.updateVisibility(false, false);
lyx_view_->updateLayoutList();
lyx_view_->updateToolbars();
}
@ -452,15 +452,20 @@ void GuiWorkArea::showCursor()
int h = asc + des;
int x = 0;
int y = 0;
buffer_view_->cursor().getPos(x, y);
Cursor & cur = buffer_view_->cursor();
cur.getPos(x, y);
y -= asc;
// if it doesn't touch the screen, don't try to show it
bool cursorInView = true;
if (y + h < 0 || y >= viewport()->height())
return;
cursorInView = false;
cursor_visible_ = true;
showCursor(x, y, h, shape);
// show cursor on screen
if (cursorInView) {
cursor_visible_ = true;
showCursor(x, y, h, shape);
}
}
@ -727,6 +732,24 @@ void GuiWorkArea::generateSyntheticMouseEvent()
void GuiWorkArea::keyPressEvent(QKeyEvent * ev)
{
// intercept some keys if completion popup is visible
if (completer_.popupVisible()) {
switch (ev->key()) {
case Qt::Key_Enter:
case Qt::Key_Return:
completer_.activate();
ev->accept();
return;
}
}
// intercept tab for inline completion
if (ev->key() == Qt::Key_Tab) {
completer_.tab();
ev->accept();
return;
}
// do nothing if there are other events
// (the auto repeated events come too fast)
// \todo FIXME: remove hard coded Qt keys, process the key binding

View File

@ -14,8 +14,12 @@
#define WORKAREA_H
#include "frontends/WorkArea.h"
#include "frontends/qt4/GuiCompleter.h"
#include "DocIterator.h"
#include "FuncRequest.h"
#include "qt_helpers.h"
#include "support/docstring.h"
#include "support/Timeout.h"
#include <QAbstractScrollArea>
@ -44,6 +48,7 @@ class Buffer;
namespace frontend {
class GuiView;
class GuiWorkArea;
/// types of cursor in work area
enum CursorShape {
@ -129,6 +134,9 @@ public:
///
void resizeBufferView();
///
GuiCompleter & completer() { return completer_; }
Q_SIGNALS:
///
void titleChanged(GuiWorkArea *);
@ -149,6 +157,8 @@ private Q_SLOTS:
void close();
private:
friend class GuiCompleter;
/// update the passed area.
void update(int x, int y, int w, int h);
///
@ -229,6 +239,9 @@ private:
bool schedule_redraw_;
///
int preedit_lines_;
///
GuiCompleter completer_;
}; // GuiWorkArea

View File

@ -73,6 +73,7 @@ SOURCEFILES = \
GuiClipboard.cpp \
GuiCommandBuffer.cpp \
GuiCommandEdit.cpp \
GuiCompleter.cpp \
GuiDelimiter.cpp \
GuiDialog.cpp \
GuiDocument.cpp \
@ -172,6 +173,7 @@ MOCHEADER = \
GuiClipboard.h \
GuiCommandBuffer.h \
GuiCommandEdit.h \
GuiCompleter.h \
GuiDelimiter.h \
GuiDialog.h \
GuiDocument.h \

View File

@ -5,8 +5,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>392</width>
<height>297</height>
<width>432</width>
<height>567</height>
</rect>
</property>
<property name="windowTitle" >
@ -104,6 +104,190 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox" >
<property name="title" >
<string>Completion</string>
</property>
<property name="flat" >
<bool>true</bool>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QGroupBox" name="groupBox_2" >
<property name="title" >
<string>Popup</string>
</property>
<property name="flat" >
<bool>false</bool>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QCheckBox" name="popupMathCB" >
<property name="toolTip" >
<string>Show the popup in math mode after the delay.</string>
</property>
<property name="text" >
<string>Automatically show in math mode</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="popupTextCB" >
<property name="toolTip" >
<string>Show the popup after the set delay in text mode.</string>
</property>
<property name="text" >
<string>Automatically show in text mode</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="popupAfterCompleteCB" >
<property name="toolTip" >
<string>When the TAB completion is not unique, there won't be a delay of the popup. It will be shown right away.</string>
</property>
<property name="text" >
<string>Show without delay for non-unique completions</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label_3" >
<property name="text" >
<string>Delay</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="popupDelaySB" >
<property name="toolTip" >
<string>After the cursor has not moved for this time, the completion popup is shown if it is available.</string>
</property>
<property name="maximum" >
<double>10.000000000000000</double>
</property>
<property name="singleStep" >
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>s</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3" >
<property name="title" >
<string>Inline</string>
</property>
<property name="flat" >
<bool>false</bool>
</property>
<layout class="QVBoxLayout" >
<item>
<widget class="QCheckBox" name="inlineMathCB" >
<property name="toolTip" >
<string>Show the grey inline completion behind the cursor in math mode after the delay.</string>
</property>
<property name="text" >
<string>Automatically show in math mode</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="inlineTextCB" >
<property name="toolTip" >
<string>Show the grey inline completion behind the cursor in text mode after the delay.</string>
</property>
<property name="text" >
<string>Automatically show in text mode</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="inlineDotsCB" >
<property name="toolTip" >
<string>Long completions are cut-off and shown with "...".</string>
</property>
<property name="text" >
<string>Use "..." to shorten long completions</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" >
<item>
<widget class="QLabel" name="label_4" >
<property name="text" >
<string>Delay</string>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="inlineDelaySB" >
<property name="toolTip" >
<string>After the cursor has not moved for this time, the inline completion is shown if it is available.</string>
</property>
<property name="maximum" >
<double>10.000000000000000</double>
</property>
<property name="singleStep" >
<double>0.100000000000000</double>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2" >
<property name="text" >
<string>s</string>
</property>
</widget>
</item>
<item>
<spacer>
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="mouseGB" >
<property name="title" >
@ -144,7 +328,7 @@
<double>0.100000000000000</double>
</property>
<property name="value" >
<double>1.000000000000000</double>
<double>1.100000000000000</double>
</property>
</widget>
</item>
@ -175,7 +359,7 @@
<property name="sizeHint" >
<size>
<width>20</width>
<height>20</height>
<height>51</height>
</size>
</property>
</spacer>

View File

@ -20,6 +20,8 @@
#include "support/strfwd.h"
#include <boost/shared_ptr.hpp>
#include <vector>
namespace lyx {
@ -284,6 +286,47 @@ public:
/// return true if the inset should be removed automatically
virtual bool autoDelete() const;
class CompletionList {
public:
///
virtual ~CompletionList() {}
///
virtual size_t size() const =0;
/// returns the string shown in the gui.
virtual docstring data(size_t idx) const =0;
/// returns the resource string used to load an icon.
virtual std::string icon(size_t idx) const { return std::string(); }
};
typedef boost::shared_ptr<CompletionList> CompletionListPtr;
/// Returns true if the inset supports completions.
virtual bool completionSupported(Cursor const &) const { return false; }
/// Returns true if the inset supports inline completions at the
/// cursor position. In this case the completion might be stored
/// in the BufferView's inlineCompletion property.
virtual bool inlineCompletionSupported(Cursor const & cur) const { return false; }
/// Return true if the inline completion should be automatic.
virtual bool automaticInlineCompletion() const { return true; }
/// Return true if the popup completion should be automatic.
virtual bool automaticPopupCompletion() const { return true; }
/// Returns completion suggestions at cursor position. Return an
/// null pointer if no completion is a available or possible.
virtual CompletionListPtr completionList(Cursor const &) const
{
return CompletionListPtr();
}
/// Returns the completion prefix to filter the suggestions for completion.
/// This is only called if completionList returned a non-null list.
virtual docstring completionPrefix(Cursor const &) const { return docstring(); }
/// Do a completion at the cursor position. Return true on success.
/// The completion does not contain the prefix. If finished is true, the
/// completion is final. If finished is false, completion might only be
/// a partial completion.
virtual bool insertCompletion(Cursor & cur, docstring const & completion,
bool finished) { return false; }
/// Get the completion inset position and size
virtual void completionPosAndDim(Cursor const &, int & x, int & y, Dimension & dim) const {}
/// returns true if the inset can hold an inset of given type
virtual bool insetAllowed(InsetCode) const { return false; }
/// should this inset use the empty layout by default rather than

View File

@ -65,6 +65,36 @@ namespace lyx {
using graphics::PreviewLoader;
class TextCompletionList : public Inset::CompletionList {
public:
///
TextCompletionList(Cursor const & cur)
: buf_(cur.buffer()), it_(buf_.registeredWords().begin()), pos_(0) {}
///
virtual ~TextCompletionList() {}
///
virtual size_t size() const {
return buf_.registeredWords().size();
}
///
virtual docstring data(size_t idx) const {
std::set<docstring>::iterator it
= buf_.registeredWords().begin();
for (size_t i = 0; i < idx; ++i)
it++;
return *it;
}
private:
Buffer const & buf_;
std::set<docstring>::iterator const it_;
size_t pos_;
};
/////////////////////////////////////////////////////////////////////
InsetText::InsetText(BufferParams const & bp)
: drawFrame_(false), frame_color_(Color_insetframe)
{
@ -444,4 +474,98 @@ void InsetText::updateLabels(Buffer const & buf, ParIterator const & it)
}
bool InsetText::completionSupported(Cursor const & cur) const
{
Cursor const & bvCur = cur.bv().cursor();
if (&bvCur.inset() != this)
return false;
Paragraph const & par = cur.paragraph();
return cur.pos() > 0
&& !par.isLetter(cur.pos())
&& par.isLetter(cur.pos() - 1);
}
bool InsetText::inlineCompletionSupported(Cursor const & cur) const
{
return completionSupported(cur);
}
bool InsetText::automaticInlineCompletion() const
{
return lyxrc.completion_inline_text;
}
bool InsetText::automaticPopupCompletion() const
{
return lyxrc.completion_popup_text;
}
Inset::CompletionListPtr InsetText::completionList(Cursor const & cur) const
{
if (!completionSupported(cur))
return CompletionListPtr();
return CompletionListPtr(new TextCompletionList(cur));
}
docstring InsetText::previousWord(Buffer const & buffer, CursorSlice const & sl) const
{
CursorSlice from = sl;
CursorSlice to = sl;
text_.getWord(from, to, PREVIOUS_WORD);
if (sl == from || to == from)
return docstring();
Paragraph const & par = sl.paragraph();
return par.asString(buffer, from.pos(), to.pos(), false);
}
docstring InsetText::completionPrefix(Cursor const & cur) const
{
if (!completionSupported(cur))
return docstring();
return previousWord(cur.buffer(), cur.top());
}
bool InsetText::insertCompletion(Cursor & cur, docstring const & s,
bool finished)
{
if (!completionSupported(cur))
return false;
cur.insert(s);
return true;
}
void InsetText::completionPosAndDim(Cursor const & cur, int & x, int & y,
Dimension & dim) const
{
// get word in front of cursor
docstring word = previousWord(cur.buffer(), cur.top());
DocIterator wordStart = cur;
wordStart.pos() -= word.length();
// get position on screen of the word start
Point lxy = cur.bv().getPos(wordStart, false);
x = lxy.x_;
y = lxy.y_;
// Calculate dimensions of the word
TextMetrics const & tm = cur.bv().textMetrics(&text_);
dim = tm.rowHeight(cur.pit(), wordStart.pos(), cur.pos(), false);
Point rxy = cur.bv().getPos(cur, cur.boundary());
dim.wid = abs(rxy.x_ - x);
x = (rxy.x_ < x) ? x - dim.wid : x; // for RTL
}
} // namespace lyx

View File

@ -141,6 +141,23 @@ public:
///
virtual Inset * clone() const;
///
bool completionSupported(Cursor const &) const;
///
bool inlineCompletionSupported(Cursor const & cur) const;
///
bool automaticInlineCompletion() const;
///
bool automaticPopupCompletion() const;
///
CompletionListPtr completionList(Cursor const & cur) const;
///
docstring completionPrefix(Cursor const & cur) const;
///
bool insertCompletion(Cursor & cur, docstring const & s, bool finished);
///
void completionPosAndDim(Cursor const &, int & x, int & y, Dimension & dim) const;
protected:
///
virtual void doDispatch(Cursor & cur, FuncRequest & cmd);
@ -154,6 +171,8 @@ private:
ColorCode frame_color_;
///
mutable pit_type old_pit;
///
docstring previousWord(Buffer const & buffer, CursorSlice const & sl) const;
public:
///

View File

@ -415,6 +415,10 @@ enum kb_action {
LFUN_UI_TOGGLE,
LFUN_SPLIT_VIEW,
LFUN_CLOSE_TAB_GROUP,
// 320
LFUN_COMPLETION_POPUP,
LFUN_COMPLETION_INLINE,
LFUN_COMPLETION_COMPLETE,
LFUN_LASTACTION // end of the table
};

View File

@ -34,6 +34,7 @@
#include "MathSupport.h"
#include "Bidi.h"
#include "Buffer.h"
#include "BufferView.h"
#include "CoordCache.h"
#include "Cursor.h"
@ -56,6 +57,8 @@
#include "support/textutils.h"
#include "support/docstream.h"
#include <algorithm>
#include <deque>
#include <sstream>
using namespace std;
@ -1585,6 +1588,90 @@ bool InsetMathNest::script(Cursor & cur, bool up,
}
bool InsetMathNest::completionSupported(Cursor const & cur) const
{
return cur.inMacroMode();
}
bool InsetMathNest::inlineCompletionSupported(Cursor const & cur) const
{
return cur.inMacroMode();
}
bool InsetMathNest::automaticInlineCompletion() const
{
return lyxrc.completion_inline_math;
}
bool InsetMathNest::automaticPopupCompletion() const
{
return lyxrc.completion_popup_math;
}
Inset::CompletionListPtr InsetMathNest::completionList(Cursor const & cur) const
{
if (!cur.inMacroMode())
return CompletionListPtr();
return CompletionListPtr(new MathCompletionList(cur));
}
docstring InsetMathNest::completionPrefix(Cursor const & cur) const
{
if (!cur.inMacroMode())
return docstring();
return cur.activeMacro()->name();
}
bool InsetMathNest::insertCompletion(Cursor & cur, docstring const & s,
bool finished)
{
if (!cur.inMacroMode())
return false;
// append completion to active macro
InsetMathUnknown * inset = cur.activeMacro();
inset->setName(inset->name() + s);
// finish macro
if (finished) {
#if 0
// FIXME: this creates duplicates in the completion popup
// which looks ugly. Moreover the changes the list lengths
// which seems to
confuse the popup as well.
MathCompletionList::addToFavorites(inset->name());
#endif
lyx::dispatch(FuncRequest(LFUN_SELF_INSERT, " "));
}
return true;
}
void InsetMathNest::completionPosAndDim(Cursor const & cur, int & x, int & y,
Dimension & dim) const
{
Inset const * inset = cur.activeMacro();
if (!inset)
return;
// get inset dimensions
dim = cur.bv().coordCache().insets().dim(inset);
Point xy
= cur.bv().coordCache().insets().xy(inset);
x = xy.x_;
y = xy.y_;
}
bool InsetMathNest::cursorMathForward(Cursor & cur)
{
if (cur.pos() != cur.lastpos() && cur.openable(cur.nextAtom())) {
@ -1622,4 +1709,150 @@ bool InsetMathNest::cursorMathBackward(Cursor & cur)
}
////////////////////////////////////////////////////////////////////
MathCompletionList::MathCompletionList(Cursor const & cur)
{
// fill it with macros from the buffer
Buffer::MacroNameSet macros;
cur.buffer().listMacroNames(macros);
Buffer::MacroNameSet::const_iterator it;
for (it = macros.begin(); it != macros.end(); ++it) {
if (cur.buffer().getMacro(*it, cur, false))
locals.push_back("\\" + *it);
}
sort(locals.begin(), locals.end());
if (globals.size() > 0)
return;
// fill in global macros
macros.clear();
MacroTable::globalMacros().getMacroNames(macros);
lyxerr << "Globals completion macros: ";
for (it = macros.begin(); it != macros.end(); ++it) {
lyxerr << "\\" + *it << " ";
globals.push_back("\\" + *it);
}
lyxerr << std::endl;
// fill in global commands
globals.push_back(from_ascii("\\boxed"));
globals.push_back(from_ascii("\\fbox"));
globals.push_back(from_ascii("\\framebox"));
globals.push_back(from_ascii("\\makebox"));
globals.push_back(from_ascii("\\kern"));
globals.push_back(from_ascii("\\xrightarrow"));
globals.push_back(from_ascii("\\xleftarrow"));
globals.push_back(from_ascii("\\split"));
globals.push_back(from_ascii("\\gathered"));
globals.push_back(from_ascii("\\aligned"));
globals.push_back(from_ascii("\\alignedat"));
globals.push_back(from_ascii("\\cases"));
globals.push_back(from_ascii("\\substack"));
globals.push_back(from_ascii("\\subarray"));
globals.push_back(from_ascii("\\array"));
globals.push_back(from_ascii("\\sqrt"));
globals.push_back(from_ascii("\\root"));
globals.push_back(from_ascii("\\tabular"));
globals.push_back(from_ascii("\\stackrel"));
globals.push_back(from_ascii("\\binom"));
globals.push_back(from_ascii("\\choose"));
globals.push_back(from_ascii("\\choose"));
globals.push_back(from_ascii("\\frac"));
globals.push_back(from_ascii("\\over"));
globals.push_back(from_ascii("\\nicefrac"));
globals.push_back(from_ascii("\\unitfrac"));
globals.push_back(from_ascii("\\unitfracthree"));
globals.push_back(from_ascii("\\unitone"));
globals.push_back(from_ascii("\\unittwo"));
globals.push_back(from_ascii("\\infer"));
globals.push_back(from_ascii("\\atop"));
globals.push_back(from_ascii("\\lefteqn"));
globals.push_back(from_ascii("\\boldsymbol"));
globals.push_back(from_ascii("\\color"));
globals.push_back(from_ascii("\\normalcolor"));
globals.push_back(from_ascii("\\textcolor"));
globals.push_back(from_ascii("\\dfrac"));
globals.push_back(from_ascii("\\tfrac"));
globals.push_back(from_ascii("\\dbinom"));
globals.push_back(from_ascii("\\tbinom"));
globals.push_back(from_ascii("\\hphantom"));
globals.push_back(from_ascii("\\phantom"));
globals.push_back(from_ascii("\\vphantom"));
WordList const & words = mathedWordList();
WordList::const_iterator it2;
lyxerr << "Globals completion commands: ";
for (it2 = words.begin(); it2 != words.end(); ++it2) {
globals.push_back("\\" + (*it2).first);
lyxerr << "\\" + (*it2).first << " ";
}
lyxerr << std::endl;
sort(globals.begin(), globals.end());
}
MathCompletionList::~MathCompletionList()
{
}
size_type MathCompletionList::size() const
{
return favorites.size() + locals.size() + globals.size();
}
docstring MathCompletionList::data(size_t idx) const
{
size_t fsize = favorites.size();
size_t lsize = locals.size();
if (idx >= fsize + lsize)
return globals[idx - lsize - fsize];
else if (idx >= fsize)
return locals[idx - fsize];
else
return favorites[idx];
}
std::string MathCompletionList::icon(size_t idx) const
{
// get the latex command
docstring cmd;
size_t fsize = favorites.size();
size_t lsize = locals.size();
if (idx >= fsize + lsize)
cmd = globals[idx - lsize - fsize];
else if (idx >= fsize)
cmd = locals[idx - fsize];
else
cmd = favorites[idx];
// get the icon resource name by stripping the backslash
return "images/math/" + to_utf8(cmd.substr(1)) + ".png";
}
void MathCompletionList::addToFavorites(docstring const & completion)
{
// remove old occurrence
std::deque<docstring>::iterator it;
for (it = favorites.begin(); it != favorites.end(); ++it) {
if (*it == completion) {
favorites.erase(it);
break;
}
}
// put it to the front
favorites.push_front(completion);
favorites.resize(min(int(favorites.size()), 10));
}
std::vector<docstring> MathCompletionList::globals;
std::deque<docstring> MathCompletionList::favorites;
} // namespace lyx

View File

@ -14,9 +14,35 @@
#include "InsetMath.h"
#include <deque>
namespace lyx {
class MathCompletionList : public Inset::CompletionList {
public:
///
MathCompletionList(Cursor const & cur);
///
virtual ~MathCompletionList();
///
virtual size_t size() const;
///
virtual docstring data(size_t idx) const;
///
virtual std::string icon(size_t idx) const;
///
static void addToFavorites(docstring const & completion);
private:
///
static std::vector<docstring> globals;
///
std::vector<docstring> locals;
///
static std::deque<docstring> favorites;
};
/** Abstract base class for all math objects that contain nested items.
This is basically everything that is not a single character or a
@ -111,6 +137,23 @@ public:
///
bool mouseHovered() const { return mouse_hover_; }
///
bool completionSupported(Cursor const &) const;
///
bool inlineCompletionSupported(Cursor const & cur) const;
///
bool automaticInlineCompletion() const;
///
bool automaticPopupCompletion() const;
///
CompletionListPtr completionList(Cursor const & cur) const;
///
docstring completionPrefix(Cursor const & cur) const;
///
bool insertCompletion(Cursor & cur, docstring const & s, bool finished);
///
void completionPosAndDim(Cursor const &, int & x, int & y, Dimension & dim) const;
protected:
///
InsetMathNest(InsetMathNest const & inset);

View File

@ -32,6 +32,7 @@ public:
void setName(docstring const & name);
///
docstring name() const;
/// identifies UnknownInsets
InsetMathUnknown const * asUnknownInset() const { return this; }
/// identifies UnknownInsets

View File

@ -205,6 +205,13 @@ void MacroTable::insert(docstring const & def, string const & requires)
}
void MacroTable::getMacroNames(std::set<docstring> & names) const
{
for (const_iterator it = begin(); it != end(); ++it)
names.insert(it->first);
}
void MacroTable::dump()
{
lyxerr << "\n------------------------------------------" << endl;

View File

@ -19,6 +19,7 @@
#include "support/types.h"
#include <map>
#include <set>
#include <vector>
namespace lyx {
@ -28,13 +29,11 @@ class MathData;
class MathMacroTemplate;
class Paragraph;
enum MacroType {
MacroTypeNewcommand,
MacroTypeDef
};
///
class MacroData {
public:
@ -154,6 +153,8 @@ public:
MacroData const * get(docstring const & name) const;
///
void dump();
///
void getMacroNames(std::set<docstring> & names) const;
/// the global list
static MacroTable & globalMacros();

View File

@ -28,6 +28,8 @@
#include "CoordCache.h"
#include "Cursor.h"
#include "mathed/InsetMathUnknown.h"
#include "support/debug.h"
#include "support/docstream.h"
@ -252,17 +254,33 @@ void MathData::metrics(MetricsInfo & mi, Dimension & dim) const
Cursor & cur = mi.base.bv->cursor();
const_cast<MathData*>(this)->updateMacros(&cur, mi.macrocontext);
DocIterator const & inlineCompletionPos = mi.base.bv->inlineCompletionPos();
MathData const * inlineCompletionData = 0;
if (inlineCompletionPos.inMathed())
inlineCompletionData = &inlineCompletionPos.cell();
dim.asc = 0;
dim.wid = 0;
Dimension d;
CoordCacheBase<Inset> & coords = mi.base.bv->coordCache().insets();
for (size_t i = 0, n = size(); i != n; ++i) {
for (pos_type i = 0, n = size(); i != n; ++i) {
MathAtom const & at = operator[](i);
at->metrics(mi, d);
coords.add(at.nucleus(), d);
dim += d;
if (i == n - 1)
kerning_ = at->kerning(mi.base.bv);
// HACK to draw completion suggestion inline
if (inlineCompletionData != this
|| size_t(inlineCompletionPos.pos()) != i + 1)
continue;
docstring const & completion = mi.base.bv->inlineCompletion();
if (completion.length() == 0)
continue;
dim.wid += mathed_string_width(mi.base.font, completion);
}
// Cache the dimension.
mi.base.bv->coordCache().arrays().add(this, dim);
@ -289,6 +307,11 @@ void MathData::draw(PainterInfo & pi, int x, int y) const
|| x >= bv. workWidth())
return;
DocIterator const & inlineCompletionPos = bv.inlineCompletionPos();
MathData const * inlineCompletionData = 0;
if (inlineCompletionPos.inMathed())
inlineCompletionData = &inlineCompletionPos.cell();
CoordCacheBase<Inset> & coords = pi.base.bv->coordCache().insets();
for (size_t i = 0, n = size(); i != n; ++i) {
MathAtom const & at = operator[](i);
@ -296,6 +319,34 @@ void MathData::draw(PainterInfo & pi, int x, int y) const
at->drawSelection(pi, x, y);
at->draw(pi, x, y);
x += coords.dim(at.nucleus()).wid;
// Is the inline completion here?
if (inlineCompletionData != this
|| size_t(inlineCompletionPos.pos()) != i + 1)
continue;
docstring const & completion = bv.inlineCompletion();
if (completion.length() == 0)
continue;
FontInfo f = pi.base.font;
// draw the unique and the non-unique completion part
// Note: this is not time-critical as it is
// only done once per screen.
size_t uniqueTo = bv.inlineCompletionUniqueChars();
docstring s1 = completion.substr(0, uniqueTo);
docstring s2 = completion.substr(uniqueTo);
if (s1.size() > 0) {
f.setColor(Color_inlinecompletion);
pi.pain.text(x, y, s1, f);
x += mathed_string_width(f, s1);
}
if (s2.size() > 0) {
f.setColor(Color_nonunique_inlinecompletion);
pi.pain.text(x, y, s2, f);
x += mathed_string_width(f, s2);
}
}
}

View File

@ -69,9 +69,6 @@ bool has_math_fonts;
namespace {
// file scope
typedef map<docstring, latexkeys> WordList;
WordList theWordList;
@ -221,6 +218,11 @@ void initSymbols()
} // namespace anon
WordList const & mathedWordList()
{
return theWordList;
}
void initMath()
{

View File

@ -12,8 +12,14 @@
#ifndef MATH_FACTORY_H
#define MATH_FACTORY_H
#include <map>
#include "MathParser.h"
#include "support/strfwd.h"
using std::map;
namespace lyx {
class MathAtom;
@ -29,6 +35,8 @@ MathAtom createInsetMath(char const * const);
*/
bool createInsetMath_fromDialogStr(docstring const &, MathData &);
typedef map<docstring, latexkeys> WordList;
WordList const & mathedWordList();
} // namespace lyx

View File

@ -21,10 +21,11 @@
#include "BufferView.h"
#include "CoordCache.h"
#include "Cursor.h"
#include "LaTeXFeatures.h"
#include "LyXRC.h"
#include "FuncStatus.h"
#include "FuncRequest.h"
#include "LaTeXFeatures.h"
#include "LyXFunc.h"
#include "LyXRC.h"
#include "Undo.h"
#include "frontends/Painter.h"
@ -722,4 +723,88 @@ void MathMacro::infoize2(odocstream & os) const
}
bool MathMacro::completionSupported(Cursor const & cur) const
{
return lyxrc.completion_popup_math
&& displayMode() == DISPLAY_UNFOLDED
&& cur.bv().cursor().pos() == int(name().size());
}
bool MathMacro::inlineCompletionSupported(Cursor const & cur) const
{
return lyxrc.completion_inline_math
&& displayMode() == DISPLAY_UNFOLDED
&& cur.bv().cursor().pos() == int(name().size());
}
bool MathMacro::automaticInlineCompletion() const
{
return lyxrc.completion_inline_math;
}
bool MathMacro::automaticPopupCompletion() const
{
return lyxrc.completion_popup_math;
}
Inset::CompletionListPtr MathMacro::completionList(Cursor const & cur) const
{
return CompletionListPtr(new MathCompletionList(cur.bv().cursor()));
}
docstring MathMacro::completionPrefix(Cursor const & cur) const
{
if (!completionSupported(cur))
return docstring();
return "\\" + name();
}
bool MathMacro::insertCompletion(Cursor & cur, docstring const & s,
bool finished)
{
if (completionSupported(cur))
return false;
// append completion
docstring newName = name() + s;
asArray(newName, cell(0));
cur.bv().cursor().pos() = name().size();
cur.updateFlags(Update::Force);
// finish macro
if (finished) {
#if 0
// FIXME: this creates duplicates in the completion popup
// which looks ugly. Moreover the changes the list lengths
// which seems to confuse the popup as well.
MathCompletionList::addToFavorites(inset->name());
#endif
cur.bv().cursor().pop();
++cur.bv().cursor().pos();
cur.updateFlags(Update::Force | Update::SinglePar);
}
return true;
}
void MathMacro::completionPosAndDim(Cursor const & cur, int & x, int & y,
Dimension & dim) const
{
// get inset dimensions
dim = cur.bv().coordCache().insets().dim(this);
Point xy
= cur.bv().coordCache().insets().xy(this);
x = xy.x_;
y = xy.y_;
}
} // namespace lyx

View File

@ -175,6 +175,24 @@ private:
std::string requires_;
/// update macro representation
bool needsUpdate_;
public:
///
bool completionSupported(Cursor const &) const;
///
bool inlineCompletionSupported(Cursor const & cur) const;
///
bool automaticInlineCompletion() const;
///
bool automaticPopupCompletion() const;
///
CompletionListPtr completionList(Cursor const & cur) const;
///
docstring completionPrefix(Cursor const & cur) const;
///
bool insertCompletion(Cursor & cur, docstring const & s, bool finished);
///
void completionPosAndDim(Cursor const &, int & x, int & y, Dimension & dim) const;
};
} // namespace lyx

View File

@ -71,6 +71,17 @@ RowPainter::RowPainter(PainterInfo & pi,
BOOST_ASSERT(pit >= 0);
BOOST_ASSERT(pit < int(text.paragraphs().size()));
// check for possible inline completion
DocIterator const & inlineCompletionPos = pi_.base.bv->inlineCompletionPos();
inlineCompletionVPos_ = -1;
if (inlineCompletionPos.inTexted()
&& inlineCompletionPos.text() == &text_
&& inlineCompletionPos.pit() == pit_) {
// draw visually behind the previous character
// FIXME: probably special RTL handling needed here
inlineCompletionVPos_ = bidi_.log2vis(inlineCompletionPos.pos() - 1);
}
}
@ -688,7 +699,7 @@ void RowPainter::paintText()
// it's in the last row of a paragraph; see skipped_sep_vpos declaration
if (end > 0 && end < par_.size() && par_.isSeparator(end - 1))
skipped_sep_vpos = bidi_.log2vis(end - 1);
for (pos_type vpos = row_.pos(); vpos < end; ) {
if (x_ > pi_.base.bv->workWidth())
break;
@ -710,6 +721,11 @@ void RowPainter::paintText()
if (vpos < font_span.first || vpos > font_span.last) {
font_span = par_.fontSpan(vpos);
font = text_metrics_.getDisplayFont(pit_, vpos);
// split font span if inline completion is inside
if (font_span.first <= inlineCompletionVPos_
&& font_span.last > inlineCompletionVPos_)
font_span.last = inlineCompletionVPos_;
}
const int width_pos = pm_.singleWidth(pos, font);
@ -769,6 +785,32 @@ void RowPainter::paintText()
// paint as many characters as possible.
paintFromPos(vpos);
}
// Is the inline completion here?
// FIXME: RTL support needed here
if (vpos - 1 == inlineCompletionVPos_) {
docstring const & completion = pi_.base.bv->inlineCompletion();
FontInfo f = font.fontInfo();
// draw the unique and the non-unique completion part
// Note: this is not time-critical as it is
// only done once per screen.
size_t uniqueTo = pi_.base.bv->inlineCompletionUniqueChars();
docstring s1 = completion.substr(0, uniqueTo);
docstring s2 = completion.substr(uniqueTo);
if (s1.size() > 0) {
f.setColor(Color_inlinecompletion);
pi_.pain.text(x_, yo_, s1, f);
x_ += theFontMetrics(font).width(s1);
}
if (s2.size() > 0) {
f.setColor(Color_nonunique_inlinecompletion);
pi_.pain.text(x_, yo_, s2, f);
x_ += theFontMetrics(font).width(s2);
}
}
}
// if we reach the end of a struck out range, paint it

View File

@ -99,6 +99,9 @@ private:
int const yo_; // current baseline
double x_;
int width_;
// -1 if the inline completion is not in this paragraph.
pos_type inlineCompletionVPos_;
};
} // namespace lyx