mirror of
https://git.lyx.org/repos/lyx.git
synced 2024-12-22 13:18:28 +00:00
Keyboard based horizontal scrolling for wide insets
[This commit is the output of the "horizontal scrolling" GSoC 2013 project, by Hashini Senaratne. The code has been cleaned up, some variables have been renamed and moved from the Cursor class to BufferView::Private. This is the base from which I (jmarc) will polish the feature for landing on master. Below is the original commit log of Hashini, updated to reflect the changes that have been done.] This feature also applicable for other insets; graphics and labels. This implementation is capable of scrolling a single row when reaching its content which is beyond the screen limits, using left and right arrow keys. The attribute 'horiz_scroll_offset_' introduced in the BufferView::Private class plays a main role in horizontal scrolling of the wide rows that grow beyond the screen limits. This attribute represents by how much pixels the current row that the text cursor lies in should be get scrolled. The main logic that is responsible for drawing the scrolled rows is within the BufferView class, BufferView::checkCursorScrollOffset. * The main logic is called via BufferView::draw. * What this does is set the horiz_scroll_offset_ attribute in in order to show the position that the text cursor lies in. * To make sure that BufferView::draw gets involved when Update flag is FitCursor, necessary changes are made in BufferView::processUpdateFlags. Basically what the logic that used to set the horiz_scroll_offset_ does is, * The row which the text cursor lies in is identified by a CursorSlice that points to the beginning of the row. This is the 'rowSlice' variable used in BufferView::checkCursorScrollOffset. Acessors are added to obtain this variable. Here row objects were not used to identify the current row, because it appears that row objects can disappear when doing a decoration update for example. This means that comparing row pointers is not a good idea, because they can change without notice. * Stop calculations of horiz_scroll_offset_ variable, if metrics have not been computed yet. Otherwise the calls to TextMetrics::parMetrics, calls redoParagraph and may change the row heigths. Therefore vertical scrolling feature may get disturbed. This is avoided. * Using BufferView::::setCurrentRowSlice resets horiz_scroll_offset_ when changing cursor row. This is done in order to prevent unwanted scrolling that happens when changing the selected row using up and down arrow keys. * Recompute inset positions before checking scoll offset of the row, by painting the row insets with drawing disabled. This is done because the position of insets is computed within the drawing procedure. * Current x position of the text cursor is compared with the horiz_scroll_offset_ value and the other variables like row.width(), bv.workWidth(). Compute the new horiz_scroll_offset_ value in order to show where the text cursor lies in. The basics conditions that we check before recomputing it are, if the text cursor lies rightward to the current right screen boundary, if the text cursor lies leftward to the current left screen boundary, if the text cursor lies within screen boundaries but the length of the row is less than the left boundary of the screen (this happens when we delete some content of the row using delete key or backspace key). * Change update strategy when scrooll offset has changed. This allows to redraw the row when no drawing was scheduled. By doing so, it was possible to redraw a wide row when moving to the leftmost position of the wide row, from the leftmost position of the row below, using the left arrow key. In TextMetrics::drawParagraph it is checked whether the current row is what is drawing now. If it is so, the value used to the x value of the row for drawing is adapted according to BufferView::horizScrollOffset. The method used to pass boundary() was fixed to get row when cursor was in a nested inset. This matter is considered in Cursor::textRow and it is modified accordingly. GuiWorkArea::Private::showCursor() is modified to show the cursor position in a scrolled row.
This commit is contained in:
parent
8fb1aa51f8
commit
5a361b35cf
@ -45,6 +45,7 @@
|
||||
#include "Paragraph.h"
|
||||
#include "ParagraphParameters.h"
|
||||
#include "ParIterator.h"
|
||||
#include "RowPainter.h"
|
||||
#include "Session.h"
|
||||
#include "Text.h"
|
||||
#include "TextClass.h"
|
||||
@ -230,7 +231,8 @@ struct BufferView::Private
|
||||
inlineCompletionUniqueChars_(0),
|
||||
last_inset_(0), clickable_inset_(false),
|
||||
mouse_position_cache_(),
|
||||
bookmark_edit_position_(-1), gui_(0)
|
||||
bookmark_edit_position_(-1), gui_(0),
|
||||
horiz_scroll_offset_(0)
|
||||
{}
|
||||
|
||||
///
|
||||
@ -295,6 +297,16 @@ struct BufferView::Private
|
||||
|
||||
///
|
||||
map<string, Inset *> edited_insets_;
|
||||
|
||||
/// When the row where the cursor lies is scrolled, this
|
||||
/// contains the scroll offset
|
||||
int horiz_scroll_offset_;
|
||||
/// a slice pointing to the start of the row where the cursor
|
||||
/// is (at last draw time)
|
||||
CursorSlice current_row_slice_;
|
||||
/// a slice pointing to the start of the row where cursor was
|
||||
/// at previous draw event
|
||||
CursorSlice last_row_slice_;
|
||||
};
|
||||
|
||||
|
||||
@ -456,8 +468,10 @@ void BufferView::processUpdateFlags(Update::flags flags)
|
||||
buffer_.changed(false);
|
||||
return;
|
||||
}
|
||||
// no screen update is needed.
|
||||
// no screen update is needed in principle, but this
|
||||
// could change if cursor row needs scrolling.
|
||||
d->update_strategy_ = NoScreenUpdate;
|
||||
buffer_.changed(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2853,6 +2867,103 @@ bool BufferView::cursorInView(Point const & p, int h) const
|
||||
}
|
||||
|
||||
|
||||
int BufferView::horizScrollOffset() const
|
||||
{
|
||||
return d->horiz_scroll_offset_;
|
||||
}
|
||||
|
||||
|
||||
CursorSlice const & BufferView::currentRowSlice() const
|
||||
{
|
||||
return d->current_row_slice_;
|
||||
}
|
||||
|
||||
|
||||
CursorSlice const & BufferView::lastRowSlice() const
|
||||
{
|
||||
return d->last_row_slice_;
|
||||
}
|
||||
|
||||
|
||||
void BufferView::setCurrentRowSlice(CursorSlice const & rowSlice)
|
||||
{
|
||||
// nothing to do if the cursor was already on this row
|
||||
if (d->current_row_slice_ == rowSlice) {
|
||||
d->last_row_slice_ = CursorSlice();
|
||||
return;
|
||||
}
|
||||
|
||||
// if the (previous) current row was scrolled, we have to
|
||||
// remember it in order to repaint it next time.
|
||||
if (d->horiz_scroll_offset_ != 0)
|
||||
d->last_row_slice_ = d->current_row_slice_;
|
||||
else
|
||||
d->last_row_slice_ = CursorSlice();
|
||||
|
||||
// Since we changed row, the scroll offset is not valid anymore
|
||||
d->horiz_scroll_offset_ = 0;
|
||||
d->current_row_slice_ = rowSlice;
|
||||
}
|
||||
|
||||
|
||||
void BufferView::checkCursorScrollOffset(PainterInfo & pi)
|
||||
{
|
||||
CursorSlice rowSlice = d->cursor_.bottom();
|
||||
TextMetrics const & tm = textMetrics(rowSlice.text());
|
||||
|
||||
// Stop if metrics have not been computed yet, since it means
|
||||
// that there is nothing to do.
|
||||
if (!tm.contains(rowSlice.pit()))
|
||||
return;
|
||||
ParagraphMetrics const & pm = tm.parMetrics(rowSlice.pit());
|
||||
Row const & row = pm.getRow(rowSlice.pos(),
|
||||
d->cursor_.boundary()
|
||||
&& rowSlice == d->cursor_.top());
|
||||
rowSlice.pos() = row.pos();
|
||||
|
||||
// Set the row on which the cursor lives.
|
||||
setCurrentRowSlice(rowSlice);
|
||||
|
||||
// Force the recomputation of inset positions
|
||||
bool const drawing = pi.pain.isDrawingEnabled();
|
||||
pi.pain.setDrawingEnabled(false);
|
||||
// No need to care about vertical position.
|
||||
RowPainter rp(pi, buffer().text(), d->cursor_.bottom().pit(), row, 0, 0);
|
||||
rp.paintText();
|
||||
pi.pain.setDrawingEnabled(drawing);
|
||||
|
||||
// Current x position of the cursor in pixels
|
||||
int const cur_x = getPos(d->cursor_).x_;
|
||||
|
||||
// Horizontal scroll offset of the cursor row in pixels
|
||||
int offset = d->horiz_scroll_offset_;
|
||||
int const MARGIN = 10;
|
||||
if (cur_x < offset + MARGIN) {
|
||||
// scroll right
|
||||
offset = cur_x - MARGIN;
|
||||
} else if (cur_x > offset + workWidth() - MARGIN) {
|
||||
// scroll left
|
||||
offset = cur_x - workWidth() + MARGIN;
|
||||
} else if(offset > 0
|
||||
&& row.width() - offset < workWidth()){
|
||||
offset = row.width() - workWidth();
|
||||
}
|
||||
|
||||
if (offset != d->horiz_scroll_offset_)
|
||||
LYXERR0("Offset is now " << offset);
|
||||
|
||||
if (d->update_strategy_ == NoScreenUpdate
|
||||
&& (offset != d->horiz_scroll_offset_
|
||||
|| !d->last_row_slice_.empty())) {
|
||||
// FIXME: if one uses SingleParUpdate, then home/end
|
||||
// will not work on long rows. Why?
|
||||
d->update_strategy_ = FullScreenUpdate;//DecorationUpdate;
|
||||
}
|
||||
|
||||
d->horiz_scroll_offset_ = offset;
|
||||
}
|
||||
|
||||
|
||||
void BufferView::draw(frontend::Painter & pain)
|
||||
{
|
||||
if (height_ == 0 || width_ == 0)
|
||||
@ -2864,6 +2975,10 @@ void BufferView::draw(frontend::Painter & pain)
|
||||
int const y = tm.first().second->position();
|
||||
PainterInfo pi(this, pain);
|
||||
|
||||
// Check whether the row where the cursor lives needs to be scrolled.
|
||||
// Update the drawing strategy if needed.
|
||||
checkCursorScrollOffset(pi);
|
||||
|
||||
switch (d->update_strategy_) {
|
||||
|
||||
case NoScreenUpdate:
|
||||
|
@ -32,6 +32,7 @@ class Buffer;
|
||||
class Change;
|
||||
class CoordCache;
|
||||
class Cursor;
|
||||
class CursorSlice;
|
||||
class DispatchResult;
|
||||
class DocIterator;
|
||||
class DocumentClass;
|
||||
@ -39,6 +40,7 @@ class FuncRequest;
|
||||
class FuncStatus;
|
||||
class Intl;
|
||||
class Inset;
|
||||
class PainterInfo;
|
||||
class ParIterator;
|
||||
class ParagraphMetrics;
|
||||
class Point;
|
||||
@ -118,6 +120,17 @@ public:
|
||||
/// move the screen to fit the cursor.
|
||||
/// Only to be called with good y coordinates (after a bv::metrics)
|
||||
bool fitCursor();
|
||||
|
||||
// Returns the amount of horizontal scrolling applied to the
|
||||
// top-level row where the cursor lies
|
||||
int horizScrollOffset() const;
|
||||
|
||||
// Points to the top-level row where the cursor lies (during draw).
|
||||
CursorSlice const & currentRowSlice() const;
|
||||
|
||||
// Points to the top-level row where the cursor lied at last draw event.
|
||||
CursorSlice const & lastRowSlice() const;
|
||||
|
||||
/// reset the scrollbar to reflect current view position.
|
||||
void updateScrollbar();
|
||||
/// return the Scrollbar Parameters.
|
||||
@ -331,6 +344,13 @@ private:
|
||||
/// \return true if no further update is needed.
|
||||
bool singleParUpdate();
|
||||
|
||||
// Set the row on which the cursor lives.
|
||||
void setCurrentRowSlice(CursorSlice const & rowSlice);
|
||||
|
||||
// Check whether the row where the cursor lives needs to be scrolled.
|
||||
// Update the drawing strategy if needed.
|
||||
void checkCursorScrollOffset(PainterInfo & pi);
|
||||
|
||||
/// The minimal size of the document that is visible. Used
|
||||
/// when it is allowed to scroll below the document.
|
||||
int minVisiblePart();
|
||||
|
@ -65,6 +65,8 @@ public:
|
||||
friend bool operator<=(CursorSlice const &, CursorSlice const &);
|
||||
//@}
|
||||
|
||||
/// return true if the slice has not been initialized
|
||||
bool empty() const { return !inset_; }
|
||||
/// the current inset
|
||||
Inset & inset() const { return *inset_; }
|
||||
/// return the cell this cursor is in
|
||||
|
@ -1850,10 +1850,20 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) co
|
||||
if (i)
|
||||
y += row.ascent();
|
||||
|
||||
CursorSlice rowSlice(const_cast<InsetText &>(text_->inset()));
|
||||
rowSlice.pit() = pit;
|
||||
rowSlice.pos() = row.pos();
|
||||
|
||||
bool const inside = (y + row.descent() >= 0
|
||||
&& y - row.ascent() < ww);
|
||||
|
||||
// Adapt to cursor row scroll offset if applicable.
|
||||
if (bv_->currentRowSlice() == rowSlice)
|
||||
x -= bv_->horizScrollOffset();
|
||||
|
||||
// It is not needed to draw on screen if we are not inside.
|
||||
pi.pain.setDrawingEnabled(inside && original_drawing_state);
|
||||
|
||||
RowPainter rp(pi, *text_, pit, row, x, y);
|
||||
|
||||
if (selection)
|
||||
@ -1872,7 +1882,8 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) co
|
||||
|
||||
// Row signature; has row changed since last paint?
|
||||
row.setCrc(pm.computeRowSignature(row, bparams));
|
||||
bool row_has_changed = row.changed();
|
||||
bool row_has_changed = row.changed()
|
||||
|| rowSlice == bv_->lastRowSlice();
|
||||
|
||||
// Take this opportunity to spellcheck the row contents.
|
||||
if (row_has_changed && lyxrc.spellcheck_continuously) {
|
||||
|
@ -628,6 +628,12 @@ void GuiWorkArea::Private::showCursor()
|
||||
&& !completer_->inlineVisible();
|
||||
cursor_visible_ = true;
|
||||
cursor_->recomputeWidth();
|
||||
|
||||
//int cur_x = buffer_view_->getPos(cur).x_;
|
||||
// We may have decided to slide the cursor row so that cursor
|
||||
// is visible.
|
||||
p.x_ -= buffer_view_->horizScrollOffset();
|
||||
|
||||
showCursor(p.x_, p.y_, h, l_shape, isrtl, completable);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user