2002-06-12 02:54:19 +00:00
|
|
|
/**
|
2007-04-26 04:41:58 +00:00
|
|
|
* \file BufferView.cpp
|
2003-08-23 00:17:00 +00:00
|
|
|
* This file is part of LyX, the document processor.
|
|
|
|
* Licence details can be found in the file COPYING.
|
2002-03-21 17:27:08 +00:00
|
|
|
*
|
2003-10-20 11:41:21 +00:00
|
|
|
* \author Alfredo Braunstein
|
2008-11-14 15:58:50 +00:00
|
|
|
* \author Lars Gullik Bjønnes
|
2003-08-23 00:17:00 +00:00
|
|
|
* \author John Levon
|
2008-11-14 15:58:50 +00:00
|
|
|
* \author André Pönitz
|
|
|
|
* \author Jürgen Vigna
|
2003-08-23 00:17:00 +00:00
|
|
|
*
|
|
|
|
* Full author contact details are available in file CREDITS.
|
2002-06-12 02:54:19 +00:00
|
|
|
*/
|
1999-09-27 18:44:28 +00:00
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include "BufferView.h"
|
2003-09-09 22:13:45 +00:00
|
|
|
|
2008-05-28 16:02:09 +00:00
|
|
|
#include "BranchList.h"
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "Buffer.h"
|
|
|
|
#include "BufferList.h"
|
|
|
|
#include "BufferParams.h"
|
2022-12-18 12:25:24 +00:00
|
|
|
#include "BiblioInfo.h"
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "CoordCache.h"
|
2007-10-11 09:59:01 +00:00
|
|
|
#include "Cursor.h"
|
2006-09-16 10:08:51 +00:00
|
|
|
#include "CutAndPaste.h"
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "DispatchResult.h"
|
|
|
|
#include "ErrorList.h"
|
|
|
|
#include "FuncRequest.h"
|
2004-04-01 08:58:45 +00:00
|
|
|
#include "FuncStatus.h"
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "Intl.h"
|
|
|
|
#include "Language.h"
|
2009-09-19 20:56:12 +00:00
|
|
|
#include "LayoutFile.h"
|
2009-09-19 17:56:53 +00:00
|
|
|
#include "Lexer.h"
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "LyX.h"
|
2009-09-21 12:36:22 +00:00
|
|
|
#include "LyXAction.h"
|
2006-09-16 10:08:51 +00:00
|
|
|
#include "lyxfind.h"
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "LyXRC.h"
|
2007-08-27 22:53:16 +00:00
|
|
|
#include "MetricsInfo.h"
|
2007-04-26 04:41:58 +00:00
|
|
|
#include "Paragraph.h"
|
2007-08-27 22:53:16 +00:00
|
|
|
#include "Session.h"
|
2022-11-05 17:39:33 +00:00
|
|
|
#include "texstream.h"
|
2007-08-27 22:53:16 +00:00
|
|
|
#include "Text.h"
|
2007-10-11 09:59:01 +00:00
|
|
|
#include "TextMetrics.h"
|
|
|
|
#include "TexRow.h"
|
2008-03-07 19:46:04 +00:00
|
|
|
#include "TocBackend.h"
|
2002-08-29 13:05:55 +00:00
|
|
|
|
2007-04-25 01:24:38 +00:00
|
|
|
#include "insets/InsetBibtex.h"
|
2012-06-05 08:37:34 +00:00
|
|
|
#include "insets/InsetCitation.h"
|
2007-04-25 01:24:38 +00:00
|
|
|
#include "insets/InsetCommand.h" // ChangeRefs
|
2008-03-24 04:27:43 +00:00
|
|
|
#include "insets/InsetGraphics.h"
|
2022-11-05 17:39:33 +00:00
|
|
|
#include "insets/InsetIndex.h"
|
2007-04-25 01:24:38 +00:00
|
|
|
#include "insets/InsetRef.h"
|
|
|
|
#include "insets/InsetText.h"
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2021-03-05 20:21:41 +00:00
|
|
|
#include "mathed/InsetMathNest.h"
|
2021-03-06 22:36:58 +00:00
|
|
|
#include "mathed/InsetMathRef.h"
|
2014-03-22 11:09:18 +00:00
|
|
|
#include "mathed/MathData.h"
|
2020-12-02 15:13:32 +00:00
|
|
|
#include "mathed/MathRow.h"
|
2014-03-22 11:09:18 +00:00
|
|
|
|
2007-04-28 20:44:46 +00:00
|
|
|
#include "frontends/alert.h"
|
2020-11-24 17:35:25 +00:00
|
|
|
#include "frontends/CaretGeometry.h"
|
2007-10-02 18:27:20 +00:00
|
|
|
#include "frontends/Delegates.h"
|
2006-10-07 16:15:06 +00:00
|
|
|
#include "frontends/FontMetrics.h"
|
2017-07-15 23:25:03 +00:00
|
|
|
#include "frontends/NullPainter.h"
|
2007-08-27 22:53:16 +00:00
|
|
|
#include "frontends/Painter.h"
|
2007-08-10 10:21:09 +00:00
|
|
|
#include "frontends/Selection.h"
|
2021-02-14 17:56:25 +00:00
|
|
|
#include "frontends/Clipboard.h"
|
2002-08-29 13:05:55 +00:00
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
#include "support/convert.h"
|
2007-11-29 07:04:28 +00:00
|
|
|
#include "support/debug.h"
|
2020-10-20 08:36:59 +00:00
|
|
|
#include "support/docstring.h"
|
2022-12-18 12:25:24 +00:00
|
|
|
#include "support/docstring_list.h"
|
2006-09-16 10:08:51 +00:00
|
|
|
#include "support/filetools.h"
|
2007-11-29 07:04:28 +00:00
|
|
|
#include "support/gettext.h"
|
2011-07-14 17:00:35 +00:00
|
|
|
#include "support/lassert.h"
|
2020-08-03 12:15:09 +00:00
|
|
|
#include "support/Length.h"
|
2007-11-29 07:04:28 +00:00
|
|
|
#include "support/lstrings.h"
|
2018-01-25 21:06:03 +00:00
|
|
|
#include "support/lyxlib.h"
|
2006-09-16 10:08:51 +00:00
|
|
|
#include "support/types.h"
|
2002-08-29 13:05:55 +00:00
|
|
|
|
2020-10-20 08:36:59 +00:00
|
|
|
#include <algorithm>
|
2007-10-03 20:15:49 +00:00
|
|
|
#include <cerrno>
|
2020-10-20 08:36:59 +00:00
|
|
|
#include <cstring>
|
2007-10-03 20:15:49 +00:00
|
|
|
#include <fstream>
|
2006-09-16 10:08:51 +00:00
|
|
|
#include <functional>
|
2007-11-06 22:20:05 +00:00
|
|
|
#include <iterator>
|
2009-09-20 19:37:26 +00:00
|
|
|
#include <sstream>
|
2006-09-16 10:08:51 +00:00
|
|
|
#include <vector>
|
|
|
|
|
2007-12-12 10:16:00 +00:00
|
|
|
using namespace std;
|
2007-12-12 18:57:56 +00:00
|
|
|
using namespace lyx::support;
|
2007-07-17 17:40:44 +00:00
|
|
|
|
2006-10-21 00:16:43 +00:00
|
|
|
namespace lyx {
|
|
|
|
|
|
|
|
namespace Alert = frontend::Alert;
|
2006-10-07 16:47:54 +00:00
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
/// Return an inset of this class if it exists at the current cursor position
|
|
|
|
template <class T>
|
2007-10-13 09:04:52 +00:00
|
|
|
T * getInsetByCode(Cursor const & cur, InsetCode code)
|
2006-09-16 10:08:51 +00:00
|
|
|
{
|
|
|
|
DocIterator it = cur;
|
2007-09-29 07:34:42 +00:00
|
|
|
Inset * inset = it.nextInset();
|
|
|
|
if (inset && inset->lyxCode() == code)
|
|
|
|
return static_cast<T*>(inset);
|
2020-02-28 06:21:02 +00:00
|
|
|
return nullptr;
|
2007-09-29 07:34:42 +00:00
|
|
|
}
|
|
|
|
|
2012-10-27 13:45:27 +00:00
|
|
|
|
2010-10-27 00:42:43 +00:00
|
|
|
/// Note that comparing contents can only be used for InsetCommand
|
2007-10-13 09:04:52 +00:00
|
|
|
bool findNextInset(DocIterator & dit, vector<InsetCode> const & codes,
|
2007-10-23 18:51:04 +00:00
|
|
|
docstring const & contents)
|
2007-09-29 07:34:42 +00:00
|
|
|
{
|
|
|
|
DocIterator tmpdit = dit;
|
|
|
|
|
|
|
|
while (tmpdit) {
|
|
|
|
Inset const * inset = tmpdit.nextInset();
|
2010-10-27 00:42:43 +00:00
|
|
|
if (inset) {
|
2014-07-25 20:35:08 +00:00
|
|
|
bool const valid_code = std::find(codes.begin(), codes.end(),
|
2010-10-27 00:42:43 +00:00
|
|
|
inset->lyxCode()) != codes.end();
|
|
|
|
InsetCommand const * ic = inset->asInsetCommand();
|
|
|
|
bool const same_or_no_contents = contents.empty()
|
|
|
|
|| (ic && (ic->getFirstNonOptParam() == contents));
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2010-10-27 00:42:43 +00:00
|
|
|
if (valid_code && same_or_no_contents) {
|
|
|
|
dit = tmpdit;
|
|
|
|
return true;
|
|
|
|
}
|
2007-09-29 07:34:42 +00:00
|
|
|
}
|
|
|
|
tmpdit.forwardInset();
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
2007-09-29 07:34:42 +00:00
|
|
|
|
|
|
|
return false;
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
|
2007-09-29 07:34:42 +00:00
|
|
|
|
2007-10-23 18:51:04 +00:00
|
|
|
/// Looks for next inset with one of the given codes.
|
2010-10-27 00:42:43 +00:00
|
|
|
/// Note that same_content can only be used for InsetCommand
|
2007-10-13 09:04:52 +00:00
|
|
|
bool findInset(DocIterator & dit, vector<InsetCode> const & codes,
|
2007-09-29 07:34:42 +00:00
|
|
|
bool same_content)
|
|
|
|
{
|
2007-10-23 18:51:04 +00:00
|
|
|
docstring contents;
|
2007-09-29 07:34:42 +00:00
|
|
|
DocIterator tmpdit = dit;
|
|
|
|
tmpdit.forwardInset();
|
|
|
|
if (!tmpdit)
|
|
|
|
return false;
|
|
|
|
|
2010-10-27 00:42:43 +00:00
|
|
|
Inset const * inset = tmpdit.nextInset();
|
|
|
|
if (same_content && inset) {
|
|
|
|
InsetCommand const * ic = inset->asInsetCommand();
|
|
|
|
if (ic) {
|
|
|
|
bool const valid_code = std::find(codes.begin(), codes.end(),
|
|
|
|
ic->lyxCode()) != codes.end();
|
|
|
|
if (valid_code)
|
|
|
|
contents = ic->getFirstNonOptParam();
|
2007-09-29 07:34:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!findNextInset(tmpdit, codes, contents)) {
|
|
|
|
if (dit.depth() != 1 || dit.pit() != 0 || dit.pos() != 0) {
|
2018-02-24 04:35:17 +00:00
|
|
|
inset = &tmpdit.bottom().inset();
|
2008-11-17 11:46:07 +00:00
|
|
|
tmpdit = doc_iterator_begin(&inset->buffer(), inset);
|
2007-09-29 07:34:42 +00:00
|
|
|
if (!findNextInset(tmpdit, codes, contents))
|
|
|
|
return false;
|
2008-11-17 11:46:07 +00:00
|
|
|
} else {
|
2007-09-29 07:34:42 +00:00
|
|
|
return false;
|
2008-11-17 11:46:07 +00:00
|
|
|
}
|
2007-09-29 07:34:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dit = tmpdit;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Moves cursor to the next inset with one of the given codes.
|
2021-02-17 11:30:16 +00:00
|
|
|
bool gotoInset(BufferView * bv, vector<InsetCode> const & codes,
|
2007-09-29 07:34:42 +00:00
|
|
|
bool same_content)
|
|
|
|
{
|
|
|
|
Cursor tmpcur = bv->cursor();
|
|
|
|
if (!findInset(tmpcur, codes, same_content)) {
|
|
|
|
bv->cursor().message(_("No more insets"));
|
2021-02-17 11:30:16 +00:00
|
|
|
return false;
|
2007-09-29 07:34:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
tmpcur.clearSelection();
|
|
|
|
bv->setCursor(tmpcur);
|
2022-11-13 21:47:39 +00:00
|
|
|
return bv->scrollToCursor(bv->cursor(), SCROLL_TOP);
|
2007-09-29 07:34:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-10-11 09:59:01 +00:00
|
|
|
/// A map from a Text to the associated text metrics
|
2007-12-12 19:28:07 +00:00
|
|
|
typedef map<Text const *, TextMetrics> TextMetricsCache;
|
2007-10-11 09:59:01 +00:00
|
|
|
|
2007-11-17 11:27:03 +00:00
|
|
|
enum ScreenUpdateStrategy {
|
|
|
|
NoScreenUpdate,
|
|
|
|
SingleParUpdate,
|
|
|
|
FullScreenUpdate,
|
|
|
|
DecorationUpdate
|
|
|
|
};
|
|
|
|
|
2017-07-23 11:11:54 +00:00
|
|
|
} // namespace
|
2006-09-16 10:08:51 +00:00
|
|
|
|
|
|
|
|
2007-09-29 20:25:50 +00:00
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// BufferView
|
|
|
|
//
|
|
|
|
/////////////////////////////////////////////////////////////////////
|
|
|
|
|
2007-10-13 07:33:07 +00:00
|
|
|
struct BufferView::Private
|
2007-10-11 09:59:01 +00:00
|
|
|
{
|
2018-04-26 22:03:48 +00:00
|
|
|
Private(BufferView & bv) :
|
|
|
|
update_strategy_(FullScreenUpdate),
|
2017-10-11 16:00:48 +00:00
|
|
|
update_flags_(Update::Force),
|
2020-02-28 06:21:02 +00:00
|
|
|
cursor_(bv), anchor_pit_(0), anchor_ypos_(0),
|
|
|
|
wh_(0), inlineCompletionUniqueChars_(0),
|
|
|
|
last_inset_(nullptr), mouse_position_cache_(),
|
|
|
|
gui_(nullptr), bookmark_edit_position_(-1),
|
|
|
|
horiz_scroll_offset_(0), clickable_inset_(false)
|
2015-10-10 19:04:28 +00:00
|
|
|
{
|
|
|
|
xsel_cache_.set = false;
|
|
|
|
}
|
2007-10-11 09:59:01 +00:00
|
|
|
|
|
|
|
///
|
|
|
|
ScrollbarParameters scrollbarParameters_;
|
|
|
|
///
|
2007-11-17 11:27:03 +00:00
|
|
|
ScreenUpdateStrategy update_strategy_;
|
2007-10-11 09:59:01 +00:00
|
|
|
///
|
2017-10-11 16:00:48 +00:00
|
|
|
Update::flags update_flags_;
|
|
|
|
///
|
2007-10-11 09:59:01 +00:00
|
|
|
CoordCache coord_cache_;
|
2019-06-03 14:22:44 +00:00
|
|
|
///
|
2021-10-05 14:58:49 +00:00
|
|
|
typedef unordered_map<MathData const *, MathRow> MathRows;
|
2019-06-03 14:22:44 +00:00
|
|
|
MathRows math_rows_;
|
2007-10-11 09:59:01 +00:00
|
|
|
|
|
|
|
/// this is used to handle XSelection events in the right manner.
|
|
|
|
struct {
|
|
|
|
CursorSlice cursor;
|
|
|
|
CursorSlice anchor;
|
|
|
|
bool set;
|
|
|
|
} xsel_cache_;
|
|
|
|
///
|
|
|
|
Cursor cursor_;
|
|
|
|
///
|
2007-12-21 08:28:18 +00:00
|
|
|
pit_type anchor_pit_;
|
2007-10-11 09:59:01 +00:00
|
|
|
///
|
2007-12-21 08:28:18 +00:00
|
|
|
int anchor_ypos_;
|
2020-02-28 06:21:02 +00:00
|
|
|
/// Estimated average par height for scrollbar.
|
|
|
|
int wh_;
|
2007-10-11 09:59:01 +00:00
|
|
|
///
|
2007-12-21 08:28:18 +00:00
|
|
|
vector<int> par_height_;
|
2007-10-11 09:59:01 +00:00
|
|
|
|
2008-02-21 19:42:34 +00:00
|
|
|
///
|
2008-04-16 20:43:30 +00:00
|
|
|
DocIterator inlineCompletionPos_;
|
2008-02-21 19:42:34 +00:00
|
|
|
///
|
2008-04-16 20:43:30 +00:00
|
|
|
docstring inlineCompletion_;
|
2008-02-21 19:42:34 +00:00
|
|
|
///
|
2008-04-16 20:43:30 +00:00
|
|
|
size_t inlineCompletionUniqueChars_;
|
2008-02-21 19:42:34 +00:00
|
|
|
|
2007-10-11 09:59:01 +00:00
|
|
|
/// keyboard mapping object.
|
|
|
|
Intl intl_;
|
|
|
|
|
|
|
|
/// last visited inset.
|
|
|
|
/** kept to send setMouseHover(false).
|
|
|
|
* Not owned, so don't delete.
|
|
|
|
*/
|
2011-02-13 10:07:00 +00:00
|
|
|
Inset const * last_inset_;
|
2007-10-11 09:59:01 +00:00
|
|
|
|
2010-03-29 15:21:10 +00:00
|
|
|
/// position of the mouse at the time of the last mouse move
|
|
|
|
/// This is used to update the hovering status of inset in
|
|
|
|
/// cases where the buffer is scrolled, but the mouse didn't move.
|
|
|
|
Point mouse_position_cache_;
|
|
|
|
|
2007-10-11 09:59:01 +00:00
|
|
|
mutable TextMetricsCache text_metrics_;
|
|
|
|
|
|
|
|
/// Whom to notify.
|
|
|
|
/** Not owned, so don't delete.
|
|
|
|
*/
|
|
|
|
frontend::GuiBufferViewDelegate * gui_;
|
2008-03-26 19:20:17 +00:00
|
|
|
|
2009-09-27 10:29:13 +00:00
|
|
|
///
|
|
|
|
map<string, Inset *> edited_insets_;
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
|
|
|
|
/// When the row where the cursor lies is scrolled, this
|
|
|
|
/// contains the scroll offset
|
2020-02-28 06:21:02 +00:00
|
|
|
// cache for id of the paragraph which was edited the last time
|
|
|
|
int bookmark_edit_position_;
|
|
|
|
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
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_;
|
2020-02-28 06:21:02 +00:00
|
|
|
/// are we hovering something that we can click
|
|
|
|
bool clickable_inset_;
|
2020-11-24 17:35:25 +00:00
|
|
|
/// shape of the caret
|
|
|
|
frontend::CaretGeometry caret_geometry_;
|
2022-07-22 23:57:56 +00:00
|
|
|
///
|
|
|
|
bool mouse_selecting_ = false;
|
2007-10-11 09:59:01 +00:00
|
|
|
};
|
|
|
|
|
2007-09-29 20:25:50 +00:00
|
|
|
|
2007-08-21 07:33:46 +00:00
|
|
|
BufferView::BufferView(Buffer & buf)
|
2008-11-17 11:46:07 +00:00
|
|
|
: width_(0), height_(0), full_screen_(false), buffer_(buf),
|
|
|
|
d(new Private(*this))
|
2006-09-16 10:08:51 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
d->xsel_cache_.set = false;
|
|
|
|
d->intl_.initKeyMapper(lyxrc.use_kbmap);
|
1999-09-27 18:44:28 +00:00
|
|
|
|
2008-11-17 11:46:07 +00:00
|
|
|
d->cursor_.setBuffer(&buf);
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.push(buffer_.inset());
|
|
|
|
d->cursor_.resetAnchor();
|
|
|
|
d->cursor_.setCurrentFont();
|
1999-09-27 18:44:28 +00:00
|
|
|
|
2010-10-29 20:00:51 +00:00
|
|
|
buffer_.updatePreviews();
|
2000-04-08 17:02:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-21 07:33:46 +00:00
|
|
|
BufferView::~BufferView()
|
2000-04-08 17:02:02 +00:00
|
|
|
{
|
2007-08-22 09:06:13 +00:00
|
|
|
// current buffer is going to be switched-off, save cursor pos
|
|
|
|
// Ideally, the whole cursor stack should be saved, but session
|
|
|
|
// currently can only handle bottom (whole document) level pit and pos.
|
|
|
|
// That is to say, if a cursor is in a nested inset, it will be
|
|
|
|
// restore to the left of the top level inset.
|
2007-11-02 19:59:08 +00:00
|
|
|
LastFilePosSection::FilePos fp;
|
2019-10-20 09:47:04 +00:00
|
|
|
fp.file = buffer_.fileName();
|
2007-11-02 19:59:08 +00:00
|
|
|
fp.pit = d->cursor_.bottom().pit();
|
|
|
|
fp.pos = d->cursor_.bottom().pos();
|
2019-10-20 09:47:04 +00:00
|
|
|
theSession().lastFilePos().save(fp);
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2010-05-03 23:04:51 +00:00
|
|
|
if (d->last_inset_)
|
2014-07-25 20:35:08 +00:00
|
|
|
d->last_inset_->setMouseHover(this, false);
|
2007-10-11 09:59:01 +00:00
|
|
|
|
2007-10-13 07:33:07 +00:00
|
|
|
delete d;
|
2007-10-11 09:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-06 13:19:12 +00:00
|
|
|
int BufferView::defaultMargin() const
|
2008-02-09 17:20:23 +00:00
|
|
|
{
|
2018-01-25 21:06:03 +00:00
|
|
|
// The value used to be hardcoded to 10
|
2021-04-06 13:19:12 +00:00
|
|
|
return zoomedPixels(20);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int BufferView::rightMargin() const
|
|
|
|
{
|
2022-10-21 22:11:12 +00:00
|
|
|
const int screen_width = inPixels(lyxrc.screen_width);
|
2008-02-10 10:01:16 +00:00
|
|
|
|
2022-10-21 22:11:12 +00:00
|
|
|
// The additional test for the case the outliner is opened.
|
|
|
|
if (!lyxrc.screen_limit || width_ < screen_width + 2 * defaultMargin()) {
|
|
|
|
return defaultMargin();
|
|
|
|
} else {
|
|
|
|
return (width_ - screen_width) / 2;
|
|
|
|
}
|
2008-02-09 17:20:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int BufferView::leftMargin() const
|
|
|
|
{
|
2008-02-10 10:01:16 +00:00
|
|
|
return rightMargin();
|
2008-02-09 17:20:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-16 11:01:40 +00:00
|
|
|
int BufferView::topMargin() const
|
|
|
|
{
|
|
|
|
// Original value was 20px at 100dpi. For internal buffers like in
|
|
|
|
// advanced search and replace, a value of 5px is enough.
|
|
|
|
return zoomedPixels(buffer().isInternal() ? 5 : 20);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int BufferView::bottomMargin() const
|
|
|
|
{
|
|
|
|
return topMargin();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-04-28 13:06:30 +00:00
|
|
|
int BufferView::inPixels(Length const & len) const
|
|
|
|
{
|
|
|
|
Font const font = buffer().params().getFont();
|
|
|
|
return len.inPixels(workWidth(), theFontMetrics(font).em());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-25 21:06:03 +00:00
|
|
|
int BufferView::zoomedPixels(int pix) const
|
|
|
|
{
|
|
|
|
// FIXME: the dpi setting should really depend on the BufferView
|
|
|
|
// (think different monitors).
|
|
|
|
|
|
|
|
// Zoom factor specified by user in percent
|
|
|
|
double const zoom = lyxrc.currentZoom / 100.0; // [percent]
|
|
|
|
|
2018-01-26 09:06:28 +00:00
|
|
|
// DPI setting for monitor relative to 100dpi
|
|
|
|
double const dpizoom = lyxrc.dpi / 100.0; // [per 100dpi]
|
2018-01-25 21:06:03 +00:00
|
|
|
|
2018-01-26 09:06:28 +00:00
|
|
|
return support::iround(pix * zoom * dpizoom);
|
2018-01-25 21:06:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-03-06 12:41:38 +00:00
|
|
|
bool BufferView::isTopScreen() const
|
|
|
|
{
|
2016-06-01 20:28:26 +00:00
|
|
|
return 0 == d->scrollbarParameters_.min;
|
2008-03-06 12:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool BufferView::isBottomScreen() const
|
|
|
|
{
|
2016-06-01 20:28:26 +00:00
|
|
|
return 0 == d->scrollbarParameters_.max;
|
2008-03-06 12:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-10-11 09:59:01 +00:00
|
|
|
Intl & BufferView::getIntl()
|
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
return d->intl_;
|
2007-10-11 09:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Intl const & BufferView::getIntl() const
|
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
return d->intl_;
|
2007-10-11 09:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CoordCache & BufferView::coordCache()
|
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
return d->coord_cache_;
|
2007-10-11 09:59:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
CoordCache const & BufferView::coordCache() const
|
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
return d->coord_cache_;
|
2000-04-08 17:02:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-06-03 14:22:44 +00:00
|
|
|
MathRow const & BufferView::mathRow(MathData const * cell) const
|
|
|
|
{
|
|
|
|
auto it = d->math_rows_.find(cell);
|
|
|
|
LATTEST(it != d->math_rows_.end());
|
|
|
|
return it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BufferView::setMathRow(MathData const * cell, MathRow const & mrow)
|
|
|
|
{
|
|
|
|
d->math_rows_[cell] = mrow;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-21 13:03:55 +00:00
|
|
|
Buffer & BufferView::buffer()
|
1999-09-27 18:44:28 +00:00
|
|
|
{
|
2007-08-21 13:03:55 +00:00
|
|
|
return buffer_;
|
2000-02-10 17:53:36 +00:00
|
|
|
}
|
|
|
|
|
2007-06-19 17:57:40 +00:00
|
|
|
|
2007-08-21 13:03:55 +00:00
|
|
|
Buffer const & BufferView::buffer() const
|
1999-09-27 18:44:28 +00:00
|
|
|
{
|
2007-08-21 13:03:55 +00:00
|
|
|
return buffer_;
|
1999-09-27 18:44:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-02-14 17:56:25 +00:00
|
|
|
docstring const & BufferView::searchRequestCache() const
|
|
|
|
{
|
|
|
|
return theClipboard().getFindBuffer();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BufferView::setSearchRequestCache(docstring const & text)
|
|
|
|
{
|
|
|
|
bool casesensitive;
|
|
|
|
bool matchword;
|
|
|
|
bool forward;
|
2021-02-15 10:30:03 +00:00
|
|
|
bool wrap;
|
2021-02-15 13:12:07 +00:00
|
|
|
bool instant;
|
2021-02-16 07:11:09 +00:00
|
|
|
bool onlysel;
|
2021-02-15 13:12:07 +00:00
|
|
|
docstring const search = string2find(text, casesensitive, matchword,
|
2021-02-16 07:11:09 +00:00
|
|
|
forward, wrap, instant, onlysel);
|
2021-02-14 17:56:25 +00:00
|
|
|
theClipboard().setFindBuffer(search);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-10-02 13:25:09 +00:00
|
|
|
bool BufferView::needsFitCursor() const
|
1999-09-27 18:44:28 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
if (cursorStatus(d->cursor_) == CUR_INSIDE) {
|
2006-10-21 00:16:43 +00:00
|
|
|
frontend::FontMetrics const & fm =
|
2007-10-28 18:51:54 +00:00
|
|
|
theFontMetrics(d->cursor_.getFont().fontInfo());
|
2006-10-07 16:15:06 +00:00
|
|
|
int const asc = fm.maxAscent();
|
|
|
|
int const des = fm.maxDescent();
|
2010-04-21 13:03:04 +00:00
|
|
|
Point const p = getPos(d->cursor_);
|
2006-09-16 10:08:51 +00:00
|
|
|
if (p.y_ - asc >= 0 && p.y_ + des < height_)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-10-11 16:00:48 +00:00
|
|
|
namespace {
|
|
|
|
|
|
|
|
// this is for debugging only.
|
|
|
|
string flagsAsString(Update::flags flags)
|
1999-09-27 18:44:28 +00:00
|
|
|
{
|
2017-10-11 16:00:48 +00:00
|
|
|
if (flags == Update::None)
|
|
|
|
return "None ";
|
|
|
|
return string((flags & Update::FitCursor) ? "FitCursor " : "")
|
|
|
|
+ ((flags & Update::Force) ? "Force " : "")
|
|
|
|
+ ((flags & Update::ForceDraw) ? "ForceDraw " : "")
|
2018-05-11 09:27:26 +00:00
|
|
|
+ ((flags & Update::SinglePar) ? "SinglePar " : "")
|
|
|
|
+ ((flags & Update::Decoration) ? "Decoration " : "");
|
2017-10-11 16:00:48 +00:00
|
|
|
}
|
2017-10-17 18:10:11 +00:00
|
|
|
|
2017-10-11 16:00:48 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2017-10-11 16:00:48 +00:00
|
|
|
void BufferView::processUpdateFlags(Update::flags flags)
|
|
|
|
{
|
|
|
|
LYXERR(Debug::PAINTING, "BufferView::processUpdateFlags( "
|
|
|
|
<< flagsAsString(flags) << ") buffer: " << &buffer_);
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2006-11-17 17:42:52 +00:00
|
|
|
// Case when no explicit update is requested.
|
2017-10-11 16:00:48 +00:00
|
|
|
if (flags == Update::None)
|
2007-10-10 08:52:55 +00:00
|
|
|
return;
|
2006-11-17 17:42:52 +00:00
|
|
|
|
2018-10-22 13:29:08 +00:00
|
|
|
/* FIXME We would like to avoid doing this here, since it is very
|
2018-11-12 10:41:54 +00:00
|
|
|
* expensive and is called in updateBuffer already. However, even
|
2018-10-22 13:29:08 +00:00
|
|
|
* inserting a plain character can invalidate the overly fragile
|
|
|
|
* tables of child documents built by updateMacros. Some work is
|
|
|
|
* needed to avoid doing that when not necessary.
|
|
|
|
*/
|
|
|
|
buffer_.updateMacros();
|
|
|
|
|
2017-10-11 16:00:48 +00:00
|
|
|
// First check whether the metrics and inset positions should be updated
|
|
|
|
if (flags & Update::Force) {
|
|
|
|
// This will update the CoordCache items and replace Force
|
|
|
|
// with ForceDraw in flags.
|
|
|
|
updateMetrics(flags);
|
2007-01-20 01:23:07 +00:00
|
|
|
}
|
|
|
|
|
2019-02-20 11:01:44 +00:00
|
|
|
// Detect whether we can only repaint a single paragraph (if we
|
|
|
|
// are not already redrawing all).
|
2018-05-11 09:27:26 +00:00
|
|
|
// We handle this before FitCursor because the later will require
|
|
|
|
// correct metrics at cursor position.
|
2019-02-20 11:01:44 +00:00
|
|
|
if (!(flags & Update::ForceDraw)
|
2020-11-05 10:01:21 +00:00
|
|
|
&& (flags & Update::SinglePar)
|
|
|
|
&& !singleParUpdate())
|
|
|
|
updateMetrics(flags);
|
2018-05-11 09:27:26 +00:00
|
|
|
|
2017-10-11 16:00:48 +00:00
|
|
|
// Then make sure that the screen contains the cursor if needed
|
|
|
|
if (flags & Update::FitCursor) {
|
2015-10-02 13:25:09 +00:00
|
|
|
if (needsFitCursor()) {
|
2020-01-06 16:10:38 +00:00
|
|
|
// First try to make the selection start visible
|
|
|
|
// (which is just the cursor when there is no selection)
|
2022-11-13 21:47:39 +00:00
|
|
|
scrollToCursor(d->cursor_.selectionBegin(), SCROLL_VISIBLE);
|
2020-04-14 15:57:49 +00:00
|
|
|
// Metrics have to be recomputed (maybe again)
|
|
|
|
updateMetrics();
|
2020-01-06 16:10:38 +00:00
|
|
|
// Is the cursor visible? (only useful if cursor is at end of selection)
|
2020-04-14 15:57:49 +00:00
|
|
|
if (needsFitCursor()) {
|
2020-01-06 16:10:38 +00:00
|
|
|
// then try to make cursor visible instead
|
2022-11-13 21:47:39 +00:00
|
|
|
scrollToCursor(d->cursor_, SCROLL_VISIBLE);
|
2020-04-14 15:57:49 +00:00
|
|
|
// Metrics have to be recomputed (maybe again)
|
|
|
|
updateMetrics(flags);
|
|
|
|
}
|
2007-01-20 01:23:07 +00:00
|
|
|
}
|
2017-10-11 16:00:48 +00:00
|
|
|
flags = flags & ~Update::FitCursor;
|
2006-11-21 16:58:45 +00:00
|
|
|
}
|
|
|
|
|
2017-10-11 16:00:48 +00:00
|
|
|
// Add flags to the the update flags. These will be reset to None
|
|
|
|
// after the redraw is actually done
|
|
|
|
d->update_flags_ = d->update_flags_ | flags;
|
|
|
|
LYXERR(Debug::PAINTING, "Cumulative flags: " << flagsAsString(flags));
|
2017-10-17 18:10:11 +00:00
|
|
|
|
2017-10-11 16:00:48 +00:00
|
|
|
// Now compute the update strategy
|
2019-02-20 11:01:44 +00:00
|
|
|
// Possibly values in flag are None, SinglePar, Decoration, ForceDraw
|
2017-10-11 16:00:48 +00:00
|
|
|
LATTEST((d->update_flags_ & ~(Update::None | Update::SinglePar
|
|
|
|
| Update::Decoration | Update::ForceDraw)) == 0);
|
|
|
|
|
|
|
|
if (d->update_flags_ & Update::ForceDraw)
|
|
|
|
d->update_strategy_ = FullScreenUpdate;
|
|
|
|
else if (d->update_flags_ & Update::Decoration)
|
|
|
|
d->update_strategy_ = DecorationUpdate;
|
|
|
|
else if (d->update_flags_ & Update::SinglePar)
|
|
|
|
d->update_strategy_ = SingleParUpdate;
|
|
|
|
else {
|
|
|
|
// no need to redraw anything.
|
|
|
|
d->update_strategy_ = NoScreenUpdate;
|
2007-12-21 09:45:04 +00:00
|
|
|
}
|
2010-03-29 15:21:10 +00:00
|
|
|
|
|
|
|
updateHoveredInset();
|
2017-10-11 16:00:48 +00:00
|
|
|
|
|
|
|
// Trigger a redraw.
|
|
|
|
buffer_.changed(false);
|
1999-09-27 18:44:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-10-29 15:44:32 +00:00
|
|
|
void BufferView::updateScrollbarParameters()
|
1999-09-27 18:44:28 +00:00
|
|
|
{
|
2008-06-19 13:10:25 +00:00
|
|
|
if (height_ == 0 && width_ == 0)
|
2007-12-21 08:28:18 +00:00
|
|
|
return;
|
2006-12-29 23:54:48 +00:00
|
|
|
|
2008-01-17 17:06:55 +00:00
|
|
|
// We prefer fixed size line scrolling.
|
|
|
|
d->scrollbarParameters_.single_step = defaultRowHeight();
|
|
|
|
// We prefer full screen page scrolling.
|
|
|
|
d->scrollbarParameters_.page_step = height_;
|
|
|
|
|
2007-12-21 08:28:18 +00:00
|
|
|
Text & t = buffer_.text();
|
2014-07-25 20:35:08 +00:00
|
|
|
TextMetrics & tm = d->text_metrics_[&t];
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2023-07-18 20:25:28 +00:00
|
|
|
LYXERR(Debug::SCROLLING, " Updating scrollbar: height: "
|
2007-11-29 21:10:35 +00:00
|
|
|
<< t.paragraphs().size()
|
2007-10-13 07:33:07 +00:00
|
|
|
<< " curr par: " << d->cursor_.bottom().pit()
|
2007-11-15 20:04:51 +00:00
|
|
|
<< " default height " << defaultRowHeight());
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2008-02-09 15:23:05 +00:00
|
|
|
size_t const parsize = t.paragraphs().size();
|
2007-12-21 08:28:18 +00:00
|
|
|
if (d->par_height_.size() != parsize) {
|
|
|
|
d->par_height_.clear();
|
2007-12-21 13:38:09 +00:00
|
|
|
// FIXME: We assume a default paragraph height of 2 rows. This
|
2007-12-21 08:28:18 +00:00
|
|
|
// should probably be pondered with the screen width.
|
2007-12-21 13:38:09 +00:00
|
|
|
d->par_height_.resize(parsize, defaultRowHeight() * 2);
|
2007-12-21 08:28:18 +00:00
|
|
|
}
|
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
// Look at paragraph heights on-screen
|
2020-07-12 22:00:36 +00:00
|
|
|
pair<pit_type, ParagraphMetrics const *> first = tm.first();
|
|
|
|
pair<pit_type, ParagraphMetrics const *> last = tm.last();
|
|
|
|
for (pit_type pit = first.first; pit <= last.first; ++pit) {
|
2008-01-16 09:06:36 +00:00
|
|
|
d->par_height_[pit] = tm.parMetrics(pit).height();
|
|
|
|
LYXERR(Debug::SCROLLING, "storing height for pit " << pit << " : "
|
|
|
|
<< d->par_height_[pit]);
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
2006-10-26 08:10:22 +00:00
|
|
|
|
2020-07-12 22:00:36 +00:00
|
|
|
int top_pos = first.second->position() - first.second->ascent();
|
|
|
|
int bottom_pos = last.second->position() + last.second->descent();
|
|
|
|
bool first_visible = first.first == 0 && top_pos >= 0;
|
|
|
|
bool last_visible = last.first + 1 == int(parsize) && bottom_pos <= height_;
|
2008-01-17 17:06:55 +00:00
|
|
|
if (first_visible && last_visible) {
|
|
|
|
d->scrollbarParameters_.min = 0;
|
|
|
|
d->scrollbarParameters_.max = 0;
|
|
|
|
return;
|
2007-12-21 08:28:18 +00:00
|
|
|
}
|
|
|
|
|
2020-07-12 22:00:36 +00:00
|
|
|
d->scrollbarParameters_.min = top_pos;
|
|
|
|
for (size_t i = 0; i != size_t(first.first); ++i)
|
2008-01-17 17:06:55 +00:00
|
|
|
d->scrollbarParameters_.min -= d->par_height_[i];
|
2020-07-12 22:00:36 +00:00
|
|
|
d->scrollbarParameters_.max = bottom_pos;
|
|
|
|
for (size_t i = last.first + 1; i != parsize; ++i)
|
2008-01-17 17:06:55 +00:00
|
|
|
d->scrollbarParameters_.max += d->par_height_[i];
|
|
|
|
|
|
|
|
// The reference is the top position so we remove one page.
|
2009-03-27 17:41:58 +00:00
|
|
|
if (lyxrc.scroll_below_document)
|
|
|
|
d->scrollbarParameters_.max -= minVisiblePart();
|
|
|
|
else
|
|
|
|
d->scrollbarParameters_.max -= d->scrollbarParameters_.page_step;
|
2016-07-29 20:27:13 +00:00
|
|
|
|
|
|
|
// 0 must be inside the range as it denotes the current position
|
|
|
|
if (d->scrollbarParameters_.max < 0)
|
|
|
|
d->scrollbarParameters_.max = 0;
|
|
|
|
if (d->scrollbarParameters_.min > 0)
|
|
|
|
d->scrollbarParameters_.min = 0;
|
1999-09-27 18:44:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-06-26 16:55:35 +00:00
|
|
|
ScrollbarParameters const & BufferView::scrollbarParameters() const
|
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
return d->scrollbarParameters_;
|
2006-06-26 16:55:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-12-25 18:53:38 +00:00
|
|
|
docstring BufferView::toolTip(int x, int y) const
|
|
|
|
{
|
|
|
|
// Get inset under mouse, if there is one.
|
|
|
|
Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
|
|
|
|
if (!covering_inset)
|
|
|
|
// No inset, no tooltip...
|
|
|
|
return docstring();
|
|
|
|
return covering_inset->toolTip(*this, x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-10-29 14:48:55 +00:00
|
|
|
string BufferView::contextMenu(int x, int y) const
|
2007-12-25 20:12:07 +00:00
|
|
|
{
|
2008-08-14 09:45:42 +00:00
|
|
|
//If there is a selection, return the containing inset menu
|
|
|
|
if (d->cursor_.selection())
|
|
|
|
return d->cursor_.inset().contextMenu(*this, x, y);
|
|
|
|
|
2007-12-26 12:40:58 +00:00
|
|
|
// Get inset under mouse, if there is one.
|
|
|
|
Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
|
2021-03-05 20:21:41 +00:00
|
|
|
if (covering_inset) {
|
|
|
|
if (covering_inset->asInsetMath()) {
|
|
|
|
CoordCache::Insets const & inset_cache =
|
|
|
|
coordCache().getInsets();
|
|
|
|
Inset const * inner_inset = mathContextMenu(
|
|
|
|
covering_inset->asInsetMath()->asNestInset(),
|
|
|
|
inset_cache, x, y);
|
|
|
|
if (inner_inset)
|
|
|
|
return inner_inset->contextMenu(*this, x, y);
|
|
|
|
}
|
2007-12-26 12:40:58 +00:00
|
|
|
return covering_inset->contextMenu(*this, x, y);
|
2021-03-05 20:21:41 +00:00
|
|
|
}
|
2007-12-26 12:40:58 +00:00
|
|
|
|
2008-03-08 22:57:22 +00:00
|
|
|
return buffer_.inset().contextMenu(*this, x, y);
|
2007-12-25 20:12:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-05 20:21:41 +00:00
|
|
|
Inset const * BufferView::mathContextMenu(InsetMathNest const * inset,
|
|
|
|
CoordCache::Insets const & inset_cache, int x, int y) const
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < inset->nargs(); ++i) {
|
|
|
|
MathData const & ar = inset->cell(i);
|
|
|
|
for (size_t j = 0; j < ar.size(); ++j) {
|
|
|
|
string const name = lyxerr.debugging(Debug::MATHED)
|
|
|
|
? insetName(ar[j].nucleus()->lyxCode())
|
|
|
|
: string();
|
|
|
|
LYXERR(Debug::MATHED, "Examining inset: " << name);
|
|
|
|
if (!ar[j].nucleus()->contextMenuName().empty()) {
|
|
|
|
if (inset_cache.covers(ar[j].nucleus(), x, y)) {
|
|
|
|
LYXERR(Debug::MATHED, "Hit inset: "
|
|
|
|
<< name);
|
|
|
|
return ar[j].nucleus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
InsetMathNest const * imn =
|
|
|
|
ar[j].nucleus()->asNestInset();
|
|
|
|
if (imn) {
|
|
|
|
Inset const * inner =
|
|
|
|
mathContextMenu(imn, inset_cache, x, y);
|
|
|
|
if (inner)
|
|
|
|
return inner;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-06-01 20:28:26 +00:00
|
|
|
|
2020-10-31 17:18:51 +00:00
|
|
|
void BufferView::scrollDocView(int const pixels, bool update)
|
2001-02-19 16:01:31 +00:00
|
|
|
{
|
2016-06-01 20:28:26 +00:00
|
|
|
// The scrollbar values are relative to the top of the screen, therefore the
|
|
|
|
// offset is equal to the target value.
|
2008-11-15 18:17:20 +00:00
|
|
|
|
|
|
|
// No scrolling at all? No need to redraw anything
|
2020-10-31 17:18:51 +00:00
|
|
|
if (pixels == 0)
|
2008-11-15 18:17:20 +00:00
|
|
|
return;
|
|
|
|
|
2008-01-29 13:34:18 +00:00
|
|
|
// If the offset is less than 2 screen height, prefer to scroll instead.
|
2020-10-31 17:18:51 +00:00
|
|
|
if (abs(pixels) <= 2 * height_) {
|
|
|
|
d->anchor_ypos_ -= pixels;
|
2017-10-11 16:00:48 +00:00
|
|
|
processUpdateFlags(Update::Force);
|
2008-01-29 13:34:18 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-01-29 09:51:12 +00:00
|
|
|
// cut off at the top
|
2020-10-31 17:18:51 +00:00
|
|
|
if (pixels <= d->scrollbarParameters_.min) {
|
2008-11-17 11:46:07 +00:00
|
|
|
DocIterator dit = doc_iterator_begin(&buffer_);
|
2022-11-13 21:47:39 +00:00
|
|
|
showCursor(dit, SCROLL_VISIBLE, update);
|
2008-01-29 09:51:12 +00:00
|
|
|
LYXERR(Debug::SCROLLING, "scroll to top");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-01-29 10:17:41 +00:00
|
|
|
// cut off at the bottom
|
2020-10-31 17:18:51 +00:00
|
|
|
if (pixels >= d->scrollbarParameters_.max) {
|
2008-11-17 11:46:07 +00:00
|
|
|
DocIterator dit = doc_iterator_end(&buffer_);
|
2008-01-29 09:51:12 +00:00
|
|
|
dit.backwardPos();
|
2022-11-13 21:47:39 +00:00
|
|
|
showCursor(dit, SCROLL_VISIBLE, update);
|
2008-01-29 09:51:12 +00:00
|
|
|
LYXERR(Debug::SCROLLING, "scroll to bottom");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-01-29 10:17:41 +00:00
|
|
|
// find paragraph at target position
|
2008-01-26 00:47:22 +00:00
|
|
|
int par_pos = d->scrollbarParameters_.min;
|
2008-01-29 10:17:41 +00:00
|
|
|
pit_type i = 0;
|
2008-02-09 15:23:05 +00:00
|
|
|
for (; i != int(d->par_height_.size()); ++i) {
|
2007-12-21 08:28:18 +00:00
|
|
|
par_pos += d->par_height_[i];
|
2020-10-31 17:18:51 +00:00
|
|
|
if (par_pos >= pixels)
|
2007-12-21 08:28:18 +00:00
|
|
|
break;
|
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2020-10-31 17:18:51 +00:00
|
|
|
if (par_pos < pixels) {
|
2008-01-29 10:17:41 +00:00
|
|
|
// It seems we didn't find the correct pit so stay on the safe side and
|
|
|
|
// scroll to bottom.
|
|
|
|
LYXERR0("scrolling position not found!");
|
2010-06-14 21:48:23 +00:00
|
|
|
scrollDocView(d->scrollbarParameters_.max, update);
|
2008-01-29 10:17:41 +00:00
|
|
|
return;
|
2008-01-26 00:47:22 +00:00
|
|
|
}
|
|
|
|
|
2008-11-17 11:46:07 +00:00
|
|
|
DocIterator dit = doc_iterator_begin(&buffer_);
|
2008-01-29 10:17:41 +00:00
|
|
|
dit.pit() = i;
|
2020-10-31 17:18:51 +00:00
|
|
|
LYXERR(Debug::SCROLLING, "pixels = " << pixels << " -> scroll to pit " << i);
|
2022-11-13 21:47:39 +00:00
|
|
|
showCursor(dit, SCROLL_VISIBLE, update);
|
2001-02-19 16:01:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-12-21 08:28:18 +00:00
|
|
|
// FIXME: this method is not working well.
|
2006-08-16 15:24:38 +00:00
|
|
|
void BufferView::setCursorFromScrollbar()
|
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
TextMetrics & tm = d->text_metrics_[&buffer_.text()];
|
2006-09-16 10:08:51 +00:00
|
|
|
|
|
|
|
int const height = 2 * defaultRowHeight();
|
|
|
|
int const first = height;
|
|
|
|
int const last = height_ - height;
|
2008-08-14 09:59:04 +00:00
|
|
|
int newy = 0;
|
|
|
|
Cursor const & oldcur = d->cursor_;
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2008-08-14 09:59:04 +00:00
|
|
|
switch (cursorStatus(oldcur)) {
|
2007-09-29 08:02:37 +00:00
|
|
|
case CUR_ABOVE:
|
2008-08-14 09:59:04 +00:00
|
|
|
newy = first;
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2007-09-29 08:02:37 +00:00
|
|
|
case CUR_BELOW:
|
2008-08-14 09:59:04 +00:00
|
|
|
newy = last;
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2007-09-29 08:02:37 +00:00
|
|
|
case CUR_INSIDE:
|
2010-04-21 13:03:04 +00:00
|
|
|
int const y = getPos(oldcur).y_;
|
2008-08-14 09:59:04 +00:00
|
|
|
newy = min(last, max(y, first));
|
2014-07-25 20:35:08 +00:00
|
|
|
if (y == newy)
|
2008-08-14 09:59:04 +00:00
|
|
|
return;
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
2008-08-14 09:59:04 +00:00
|
|
|
// We reset the cursor because cursorStatus() does not
|
|
|
|
// work when the cursor is within mathed.
|
|
|
|
Cursor cur(*this);
|
2009-10-29 22:46:04 +00:00
|
|
|
cur.reset();
|
2008-08-14 09:59:04 +00:00
|
|
|
tm.setCursorFromCoordinates(cur, 0, newy);
|
2008-11-15 17:11:01 +00:00
|
|
|
|
|
|
|
// update the bufferview cursor and notify insets
|
|
|
|
// FIXME: Care about the d->cursor_ flags to redraw if needed
|
|
|
|
Cursor old = d->cursor_;
|
2008-08-14 09:59:04 +00:00
|
|
|
mouseSetCursor(cur);
|
2011-05-28 14:34:19 +00:00
|
|
|
// the DEPM call in mouseSetCursor() might have destroyed the
|
|
|
|
// paragraph the cursor is in.
|
|
|
|
bool badcursor = old.fixIfBroken();
|
|
|
|
badcursor |= notifyCursorLeavesOrEnters(old, d->cursor_);
|
2008-11-15 17:11:01 +00:00
|
|
|
if (badcursor)
|
|
|
|
d->cursor_.fixIfBroken();
|
2006-08-16 15:24:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-10-11 20:01:32 +00:00
|
|
|
Change const BufferView::getCurrentChange() const
|
2003-02-08 19:18:01 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
if (!d->cursor_.selection())
|
2006-09-16 10:08:51 +00:00
|
|
|
return Change(Change::UNCHANGED);
|
|
|
|
|
2007-10-13 07:33:07 +00:00
|
|
|
DocIterator dit = d->cursor_.selectionBegin();
|
2014-03-23 08:52:32 +00:00
|
|
|
// The selected content might have been changed (see #7685)
|
2016-05-03 20:09:15 +00:00
|
|
|
dit = dit.getInnerText();
|
2006-09-16 10:08:51 +00:00
|
|
|
return dit.paragraph().lookupChange(dit.pos());
|
2003-02-08 19:18:01 +00:00
|
|
|
}
|
|
|
|
|
2003-03-04 09:27:27 +00:00
|
|
|
|
2007-09-29 08:02:37 +00:00
|
|
|
// this could be used elsewhere as well?
|
|
|
|
// FIXME: This does not work within mathed!
|
|
|
|
CursorStatus BufferView::cursorStatus(DocIterator const & dit) const
|
|
|
|
{
|
2010-04-21 13:03:04 +00:00
|
|
|
Point const p = getPos(dit);
|
2007-09-29 08:02:37 +00:00
|
|
|
if (p.y_ < 0)
|
|
|
|
return CUR_ABOVE;
|
|
|
|
if (p.y_ > workHeight())
|
|
|
|
return CUR_BELOW;
|
|
|
|
return CUR_INSIDE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-02-13 13:46:08 +00:00
|
|
|
void BufferView::bookmarkEditPosition()
|
|
|
|
{
|
|
|
|
// Don't eat cpu time for each keystroke
|
|
|
|
if (d->cursor_.paragraph().id() == d->bookmark_edit_position_)
|
|
|
|
return;
|
|
|
|
saveBookmark(0);
|
|
|
|
d->bookmark_edit_position_ = d->cursor_.paragraph().id();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-03-29 03:41:55 +00:00
|
|
|
void BufferView::saveBookmark(unsigned int idx)
|
1999-09-27 18:44:28 +00:00
|
|
|
{
|
2021-01-04 06:39:01 +00:00
|
|
|
if (buffer().isInternal())
|
|
|
|
return;
|
|
|
|
|
2009-02-12 23:46:16 +00:00
|
|
|
// tentatively save bookmark, id and pos will be used to
|
2007-04-16 19:01:32 +00:00
|
|
|
// acturately locate a bookmark in a 'live' lyx session.
|
2007-05-28 22:27:45 +00:00
|
|
|
// pit and pos will be updated with bottom level pit/pos
|
2007-04-16 19:01:32 +00:00
|
|
|
// when lyx exits.
|
2021-01-04 06:39:01 +00:00
|
|
|
theSession().bookmarks().save(
|
|
|
|
buffer_.fileName(),
|
|
|
|
d->cursor_.bottom().pit(),
|
|
|
|
d->cursor_.bottom().pos(),
|
|
|
|
d->cursor_.paragraph().id(),
|
|
|
|
d->cursor_.pos(),
|
|
|
|
idx
|
|
|
|
);
|
|
|
|
if (idx)
|
|
|
|
// emit message signal.
|
|
|
|
message(_("Save bookmark"));
|
1999-09-27 18:44:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-22 14:25:51 +00:00
|
|
|
bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos,
|
2007-04-16 19:01:32 +00:00
|
|
|
int top_id, pos_type top_pos)
|
1999-09-27 18:44:28 +00:00
|
|
|
{
|
2007-08-22 14:25:51 +00:00
|
|
|
bool success = false;
|
2008-02-09 15:23:05 +00:00
|
|
|
DocIterator dit;
|
2007-08-22 14:25:51 +00:00
|
|
|
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.clearSelection();
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2007-01-11 16:01:10 +00:00
|
|
|
// if a valid par_id is given, try it first
|
2007-04-16 19:01:32 +00:00
|
|
|
// This is the case for a 'live' bookmark when unique paragraph ID
|
|
|
|
// is used to track bookmarks.
|
|
|
|
if (top_id > 0) {
|
2008-02-09 15:23:05 +00:00
|
|
|
dit = buffer_.getParFromID(top_id);
|
|
|
|
if (!dit.atEnd()) {
|
|
|
|
dit.pos() = min(dit.paragraph().size(), top_pos);
|
2007-05-10 07:53:00 +00:00
|
|
|
// Some slices of the iterator may not be
|
2017-10-16 08:03:05 +00:00
|
|
|
// reachable (e.g. closed collapsible inset)
|
2007-05-10 07:53:00 +00:00
|
|
|
// so the dociterator may need to be
|
|
|
|
// shortened. Otherwise, setCursor may crash
|
|
|
|
// lyx when the cursor can not be set to these
|
|
|
|
// insets.
|
2008-02-09 15:23:05 +00:00
|
|
|
size_t const n = dit.depth();
|
2007-04-16 20:47:40 +00:00
|
|
|
for (size_t i = 0; i < n; ++i)
|
2009-04-22 20:55:13 +00:00
|
|
|
if (!dit[i].inset().editable()) {
|
2008-02-09 15:23:05 +00:00
|
|
|
dit.resize(i);
|
2007-04-16 20:47:40 +00:00
|
|
|
break;
|
|
|
|
}
|
2007-08-22 14:38:25 +00:00
|
|
|
success = true;
|
2007-01-11 16:01:10 +00:00
|
|
|
}
|
|
|
|
}
|
2007-08-22 14:25:51 +00:00
|
|
|
|
2007-04-16 19:01:32 +00:00
|
|
|
// if top_id == 0, or searching through top_id failed
|
2007-05-28 22:27:45 +00:00
|
|
|
// This is the case for a 'restored' bookmark when only bottom
|
2007-04-16 19:01:32 +00:00
|
|
|
// (document level) pit was saved. Because of this, bookmark
|
|
|
|
// restoration is inaccurate. If a bookmark was within an inset,
|
|
|
|
// it will be restored to the left of the outmost inset that contains
|
|
|
|
// the bookmark.
|
2021-03-11 15:16:11 +00:00
|
|
|
if (!success && bottom_pit < int(buffer_.paragraphs().size())) {
|
2008-11-17 11:46:07 +00:00
|
|
|
dit = doc_iterator_begin(&buffer_);
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2008-02-09 15:23:05 +00:00
|
|
|
dit.pit() = bottom_pit;
|
|
|
|
dit.pos() = min(bottom_pos, dit.paragraph().size());
|
2007-08-22 14:25:51 +00:00
|
|
|
success = true;
|
2007-01-11 16:01:10 +00:00
|
|
|
}
|
2007-08-22 14:25:51 +00:00
|
|
|
|
|
|
|
if (success) {
|
|
|
|
// Note: only bottom (document) level pit is set.
|
2008-02-09 15:23:05 +00:00
|
|
|
setCursor(dit);
|
2007-08-22 14:25:51 +00:00
|
|
|
// set the current font.
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.setCurrentFont();
|
2016-01-05 10:12:00 +00:00
|
|
|
// Do not forget to reset the anchor (see #9912)
|
|
|
|
d->cursor_.resetAnchor();
|
2018-11-21 12:49:20 +00:00
|
|
|
processUpdateFlags(Update::Force | Update::FitCursor);
|
2007-08-22 14:25:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
2000-06-05 15:12:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-06-11 22:05:44 +00:00
|
|
|
void BufferView::translateAndInsert(char_type c, Text * t, Cursor & cur)
|
2000-02-04 09:38:32 +00:00
|
|
|
{
|
2014-07-09 18:12:06 +00:00
|
|
|
if (d->cursor_.real_current_font.isRightToLeft()) {
|
|
|
|
if (d->intl_.keymap == Intl::PRIMARY)
|
|
|
|
d->intl_.keyMapSec();
|
|
|
|
} else {
|
|
|
|
if (d->intl_.keymap == Intl::SECONDARY)
|
|
|
|
d->intl_.keyMapPrim();
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
2007-09-16 10:38:25 +00:00
|
|
|
|
2007-10-13 07:33:07 +00:00
|
|
|
d->intl_.getTransManager().translateAndInsert(c, t, cur);
|
2000-02-03 19:51:27 +00:00
|
|
|
}
|
2000-01-07 03:42:16 +00:00
|
|
|
|
|
|
|
|
2000-06-12 11:27:15 +00:00
|
|
|
int BufferView::workWidth() const
|
2000-02-25 12:06:15 +00:00
|
|
|
{
|
2006-09-16 10:08:51 +00:00
|
|
|
return width_;
|
2000-02-23 16:39:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-03-26 00:09:16 +00:00
|
|
|
void BufferView::recenter()
|
|
|
|
{
|
2022-11-13 21:47:39 +00:00
|
|
|
showCursor(d->cursor_, SCROLL_CENTER, true);
|
2009-03-26 00:09:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-12-21 08:35:43 +00:00
|
|
|
void BufferView::showCursor()
|
2008-01-29 09:51:12 +00:00
|
|
|
{
|
2022-11-13 21:47:39 +00:00
|
|
|
showCursor(d->cursor_, SCROLL_VISIBLE, true);
|
2008-01-29 09:51:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-11-13 21:47:39 +00:00
|
|
|
void BufferView::showCursor(DocIterator const & dit, ScrollType how,
|
|
|
|
bool update)
|
2009-03-27 17:27:38 +00:00
|
|
|
{
|
2022-11-13 21:47:39 +00:00
|
|
|
if (scrollToCursor(dit, how) && update)
|
2017-10-11 16:00:48 +00:00
|
|
|
processUpdateFlags(Update::Force);
|
2009-03-27 17:27:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-11-13 21:47:39 +00:00
|
|
|
bool BufferView::scrollToCursor(DocIterator const & dit, ScrollType how)
|
2000-02-17 19:59:08 +00:00
|
|
|
{
|
2021-03-10 09:41:49 +00:00
|
|
|
// We are not properly started yet, delay until resizing is done.
|
2007-08-22 14:14:52 +00:00
|
|
|
if (height_ == 0)
|
2009-03-27 17:27:38 +00:00
|
|
|
return false;
|
2007-08-22 14:14:52 +00:00
|
|
|
|
2022-11-13 21:47:39 +00:00
|
|
|
if (how == SCROLL_CENTER)
|
2022-11-13 20:03:33 +00:00
|
|
|
LYXERR(Debug::SCROLLING, "Centering cursor in workarea");
|
2022-11-13 21:47:39 +00:00
|
|
|
else if (how == SCROLL_TOP)
|
2022-11-13 20:03:33 +00:00
|
|
|
LYXERR(Debug::SCROLLING, "Setting cursor to top of workarea");
|
2020-04-19 13:02:04 +00:00
|
|
|
else
|
2022-11-13 20:03:33 +00:00
|
|
|
LYXERR(Debug::SCROLLING, "Making sure cursor is visible in workarea");
|
2007-12-19 07:47:03 +00:00
|
|
|
|
2008-01-29 09:51:12 +00:00
|
|
|
CursorSlice const & bot = dit.bottom();
|
2021-03-02 17:07:43 +00:00
|
|
|
TextMetrics & tm = textMetrics(bot.text());
|
2007-12-21 08:28:18 +00:00
|
|
|
|
2007-12-22 09:04:53 +00:00
|
|
|
pos_type const max_pit = pos_type(bot.text()->paragraphs().size() - 1);
|
2020-09-02 22:37:52 +00:00
|
|
|
pos_type bot_pit = bot.pit();
|
2007-12-22 09:04:53 +00:00
|
|
|
if (bot_pit > max_pit) {
|
|
|
|
// FIXME: Why does this happen?
|
|
|
|
LYXERR0("bottom pit is greater that max pit: "
|
|
|
|
<< bot_pit << " > " << max_pit);
|
|
|
|
bot_pit = max_pit;
|
|
|
|
}
|
|
|
|
|
2020-07-12 22:00:36 +00:00
|
|
|
if (bot_pit == tm.first().first - 1)
|
2007-12-21 08:28:18 +00:00
|
|
|
tm.newParMetricsUp();
|
2020-07-12 22:00:36 +00:00
|
|
|
else if (bot_pit == tm.last().first + 1)
|
2007-12-21 08:28:18 +00:00
|
|
|
tm.newParMetricsDown();
|
|
|
|
|
2022-11-13 21:47:39 +00:00
|
|
|
if (tm.contains(bot_pit) && how == SCROLL_VISIBLE) {
|
2007-12-21 08:28:18 +00:00
|
|
|
ParagraphMetrics const & pm = tm.parMetrics(bot_pit);
|
2013-04-27 21:52:55 +00:00
|
|
|
LBUFERR(!pm.rows().empty());
|
2008-01-29 09:51:12 +00:00
|
|
|
// FIXME: smooth scrolling doesn't work in mathed.
|
|
|
|
CursorSlice const & cs = dit.innerTextSlice();
|
2021-03-10 09:42:24 +00:00
|
|
|
int const ypos = pm.position() + coordOffset(dit).y_;
|
|
|
|
ParagraphMetrics const & inner_pm =
|
|
|
|
textMetrics(cs.text()).parMetrics(cs.pit());
|
2021-02-16 08:27:11 +00:00
|
|
|
Dimension const & row_dim =
|
2021-03-10 09:42:24 +00:00
|
|
|
inner_pm.getRow(cs.pos(), dit.boundary()).dim();
|
2008-07-04 11:33:28 +00:00
|
|
|
int scrolled = 0;
|
2010-04-26 14:57:27 +00:00
|
|
|
|
2011-09-18 09:35:06 +00:00
|
|
|
// We try to visualize the whole row, if the row height is larger than
|
|
|
|
// the screen height, we scroll to a heuristic value of height_ / 4.
|
|
|
|
// FIXME: This heuristic value should be replaced by a recursive search
|
|
|
|
// for a row in the inset that can be visualized completely.
|
2022-11-13 20:03:33 +00:00
|
|
|
if (row_dim.height() > height_) {
|
2011-09-18 09:35:06 +00:00
|
|
|
if (ypos < defaultRowHeight())
|
|
|
|
scrolled = scroll(ypos - height_ / 4);
|
|
|
|
else if (ypos > height_ - defaultRowHeight())
|
|
|
|
scrolled = scroll(ypos - 3 * height_ / 4);
|
|
|
|
}
|
|
|
|
|
2010-04-26 14:57:27 +00:00
|
|
|
// If the top part of the row falls of the screen, we scroll
|
|
|
|
// up to align the top of the row with the top of the screen.
|
2020-07-12 22:00:36 +00:00
|
|
|
else if (ypos - row_dim.ascent() < 0 && ypos < height_) {
|
2021-03-10 09:42:15 +00:00
|
|
|
int const ynew = row_dim.ascent();
|
2011-09-18 09:35:06 +00:00
|
|
|
scrolled = scrollUp(ynew - ypos);
|
2011-08-17 10:44:50 +00:00
|
|
|
}
|
2010-04-26 14:57:27 +00:00
|
|
|
|
|
|
|
// If the bottom of the row falls of the screen, we scroll down.
|
2020-07-12 22:00:36 +00:00
|
|
|
else if (ypos + row_dim.descent() > height_ && ypos > 0) {
|
2021-03-10 09:42:15 +00:00
|
|
|
int const ynew = height_ - row_dim.descent();
|
2011-09-18 09:35:06 +00:00
|
|
|
scrolled = scrollDown(ypos - ynew);
|
2010-04-26 14:57:27 +00:00
|
|
|
}
|
2009-02-08 12:35:14 +00:00
|
|
|
|
2007-12-21 08:28:18 +00:00
|
|
|
// else, nothing to do, the cursor is already visible so we just return.
|
2010-03-29 15:29:26 +00:00
|
|
|
return scrolled != 0;
|
2007-12-19 07:47:03 +00:00
|
|
|
}
|
2007-08-22 14:14:52 +00:00
|
|
|
|
2008-02-21 19:42:34 +00:00
|
|
|
// fix inline completion position
|
2008-04-16 20:43:30 +00:00
|
|
|
if (d->inlineCompletionPos_.fixIfBroken())
|
|
|
|
d->inlineCompletionPos_ = DocIterator();
|
2008-02-21 19:42:34 +00:00
|
|
|
|
2007-12-21 08:28:18 +00:00
|
|
|
tm.redoParagraph(bot_pit);
|
2021-03-10 09:42:15 +00:00
|
|
|
int const offset = coordOffset(dit).y_;
|
2023-01-31 23:02:35 +00:00
|
|
|
pit_type const old_pit = d->anchor_pit_;
|
2007-12-21 08:28:18 +00:00
|
|
|
d->anchor_pit_ = bot_pit;
|
2022-11-13 20:03:33 +00:00
|
|
|
|
2008-01-29 09:51:12 +00:00
|
|
|
CursorSlice const & cs = dit.innerTextSlice();
|
2021-03-02 17:07:43 +00:00
|
|
|
ParagraphMetrics const & inner_pm =
|
|
|
|
textMetrics(cs.text()).parMetrics(cs.pit());
|
2020-07-12 22:00:36 +00:00
|
|
|
Dimension const & row_dim =
|
2021-03-02 17:07:43 +00:00
|
|
|
inner_pm.getRow(cs.pos(), dit.boundary()).dim();
|
2007-12-21 08:28:18 +00:00
|
|
|
|
2022-11-13 20:03:33 +00:00
|
|
|
int const old_ypos = d->anchor_ypos_;
|
|
|
|
d->anchor_ypos_ = - offset + row_dim.ascent();
|
2022-11-13 21:47:39 +00:00
|
|
|
if (how == SCROLL_CENTER)
|
2022-11-13 20:03:33 +00:00
|
|
|
d->anchor_ypos_ += height_/2 - row_dim.height() / 2;
|
2023-07-11 20:26:00 +00:00
|
|
|
else if (!lyxrc.scroll_below_document && d->anchor_pit_ == max_pit)
|
|
|
|
d->anchor_ypos_ = height_ - offset - row_dim.descent();
|
2023-07-26 19:13:51 +00:00
|
|
|
else if (offset > height_)
|
|
|
|
d->anchor_ypos_ = height_ - offset - defaultRowHeight();
|
|
|
|
else
|
|
|
|
d->anchor_ypos_ = defaultRowHeight() * 2;
|
|
|
|
|
2023-01-31 23:02:35 +00:00
|
|
|
return d->anchor_ypos_ != old_ypos || d->anchor_pit_ != old_pit;
|
2000-02-17 19:59:08 +00:00
|
|
|
}
|
2000-05-20 01:38:25 +00:00
|
|
|
|
|
|
|
|
2012-06-04 17:39:24 +00:00
|
|
|
void BufferView::makeDocumentClass()
|
|
|
|
{
|
|
|
|
DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
|
2021-01-14 06:15:19 +00:00
|
|
|
buffer_.params().makeDocumentClass(buffer_.isClone(), buffer_.isInternal());
|
2012-06-04 17:39:24 +00:00
|
|
|
updateDocumentClass(olddc);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-03-13 16:13:31 +00:00
|
|
|
void BufferView::updateDocumentClass(DocumentClassConstPtr olddc)
|
2009-09-19 20:56:12 +00:00
|
|
|
{
|
|
|
|
StableDocIterator backcur(d->cursor_);
|
|
|
|
ErrorList & el = buffer_.errorList("Class Switch");
|
|
|
|
cap::switchBetweenClasses(
|
2010-03-08 20:37:11 +00:00
|
|
|
olddc, buffer_.params().documentClassPtr(),
|
2009-09-19 20:56:12 +00:00
|
|
|
static_cast<InsetText &>(buffer_.inset()), el);
|
|
|
|
|
|
|
|
setCursor(backcur.asDocIterator(&buffer_));
|
|
|
|
|
|
|
|
buffer_.errors("Class Switch");
|
|
|
|
}
|
|
|
|
|
2012-06-04 17:39:24 +00:00
|
|
|
|
2015-09-25 16:14:53 +00:00
|
|
|
/** Return the change status at cursor position, taking into account the
|
2009-09-23 09:07:24 +00:00
|
|
|
* status at each level of the document iterator (a table in a deleted
|
|
|
|
* footnote is deleted).
|
|
|
|
* When \param outer is true, the top slice is not looked at.
|
|
|
|
*/
|
|
|
|
static Change::Type lookupChangeType(DocIterator const & dit, bool outer = false)
|
|
|
|
{
|
|
|
|
size_t const depth = dit.depth() - (outer ? 1 : 0);
|
|
|
|
|
|
|
|
for (size_t i = 0 ; i < depth ; ++i) {
|
|
|
|
CursorSlice const & slice = dit[i];
|
|
|
|
if (!slice.inset().inMathed()
|
|
|
|
&& slice.pos() < slice.paragraph().size()) {
|
|
|
|
Change::Type const ch = slice.paragraph().lookupChange(slice.pos()).type;
|
|
|
|
if (ch != Change::UNCHANGED)
|
|
|
|
return ch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return Change::UNCHANGED;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-04-06 12:12:06 +00:00
|
|
|
bool BufferView::getStatus(FuncRequest const & cmd, FuncStatus & flag)
|
2004-04-01 08:58:45 +00:00
|
|
|
{
|
2010-04-09 19:00:42 +00:00
|
|
|
FuncCode const act = cmd.action();
|
|
|
|
|
2009-09-23 09:07:24 +00:00
|
|
|
// Can we use a readonly buffer?
|
|
|
|
if (buffer_.isReadonly()
|
2010-04-09 19:00:42 +00:00
|
|
|
&& !lyxaction.funcHasFlag(act, LyXAction::ReadOnly)
|
|
|
|
&& !lyxaction.funcHasFlag(act, LyXAction::NoBuffer)) {
|
2017-03-01 21:03:44 +00:00
|
|
|
if (buffer_.hasReadonlyFlag())
|
|
|
|
flag.message(from_utf8(N_("Document is read-only")));
|
|
|
|
else
|
|
|
|
flag.message(from_utf8(N_("Document has been modified externally")));
|
2009-09-23 09:07:24 +00:00
|
|
|
flag.setEnabled(false);
|
|
|
|
return true;
|
|
|
|
}
|
2010-04-09 19:00:42 +00:00
|
|
|
|
2009-09-23 09:07:24 +00:00
|
|
|
// Are we in a DELETED change-tracking region?
|
|
|
|
if (lookupChangeType(d->cursor_, true) == Change::DELETED
|
2010-04-09 19:00:42 +00:00
|
|
|
&& !lyxaction.funcHasFlag(act, LyXAction::ReadOnly)
|
|
|
|
&& !lyxaction.funcHasFlag(act, LyXAction::NoBuffer)) {
|
2009-09-23 09:07:24 +00:00
|
|
|
flag.message(from_utf8(N_("This portion of the document is deleted.")));
|
|
|
|
flag.setEnabled(false);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2007-10-13 07:33:07 +00:00
|
|
|
Cursor & cur = d->cursor_;
|
2007-06-10 15:07:21 +00:00
|
|
|
|
2009-11-13 14:32:51 +00:00
|
|
|
if (cur.getStatus(cmd, flag))
|
2009-09-23 09:07:24 +00:00
|
|
|
return true;
|
|
|
|
|
2010-04-09 19:00:42 +00:00
|
|
|
switch (act) {
|
2009-09-19 20:56:12 +00:00
|
|
|
|
2010-04-08 09:17:00 +00:00
|
|
|
// FIXME: This is a bit problematic because we don't check if this is
|
2010-04-08 01:23:04 +00:00
|
|
|
// a document BufferView or not for these LFUNs. We probably have to
|
2009-09-19 20:56:12 +00:00
|
|
|
// dispatch both to currentBufferView() and, if that fails,
|
2010-08-06 23:54:04 +00:00
|
|
|
// to documentBufferView(); same as we do now for current Buffer and
|
|
|
|
// document Buffer. Ideally those LFUN should go to Buffer as they
|
2009-09-19 20:56:12 +00:00
|
|
|
// operate on the full Buffer and the cursor is only needed either for
|
|
|
|
// an Undo record or to restore a cursor position. But we don't know
|
|
|
|
// how to do that inside Buffer of course.
|
2009-09-19 17:56:53 +00:00
|
|
|
case LFUN_BUFFER_PARAMS_APPLY:
|
2009-09-19 20:56:12 +00:00
|
|
|
case LFUN_LAYOUT_MODULES_CLEAR:
|
|
|
|
case LFUN_LAYOUT_MODULE_ADD:
|
|
|
|
case LFUN_LAYOUT_RELOAD:
|
|
|
|
case LFUN_TEXTCLASS_APPLY:
|
|
|
|
case LFUN_TEXTCLASS_LOAD:
|
2009-09-19 17:56:53 +00:00
|
|
|
flag.setEnabled(!buffer_.isReadonly());
|
|
|
|
break;
|
2006-09-16 10:08:51 +00:00
|
|
|
|
|
|
|
case LFUN_UNDO:
|
2010-08-06 23:54:04 +00:00
|
|
|
// We do not use the LyXAction flag for readonly because Undo sets the
|
|
|
|
// buffer clean/dirty status by itself.
|
|
|
|
flag.setEnabled(!buffer_.isReadonly() && buffer_.undo().hasUndoStack());
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
case LFUN_REDO:
|
2010-08-06 23:54:04 +00:00
|
|
|
// We do not use the LyXAction flag for readonly because Redo sets the
|
|
|
|
// buffer clean/dirty status by itself.
|
|
|
|
flag.setEnabled(!buffer_.isReadonly() && buffer_.undo().hasRedoStack());
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2007-01-15 22:49:14 +00:00
|
|
|
case LFUN_FILE_INSERT_PLAINTEXT_PARA:
|
2011-12-05 13:12:57 +00:00
|
|
|
case LFUN_FILE_INSERT_PLAINTEXT: {
|
2020-10-31 13:09:46 +00:00
|
|
|
docstring const & fname = cmd.argument();
|
2011-12-05 13:12:57 +00:00
|
|
|
if (!FileName::isAbsolute(to_utf8(fname))) {
|
|
|
|
flag.message(_("Absolute filename expected."));
|
|
|
|
return false;
|
|
|
|
}
|
2011-12-05 13:41:15 +00:00
|
|
|
flag.setEnabled(cur.inTexted());
|
2011-12-05 13:12:57 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case LFUN_FILE_INSERT:
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_BOOKMARK_SAVE:
|
2007-04-29 23:33:02 +00:00
|
|
|
// FIXME: Actually, these LFUNS should be moved to Text
|
2008-05-29 15:14:00 +00:00
|
|
|
flag.setEnabled(cur.inTexted());
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2009-04-06 12:12:06 +00:00
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_FONT_STATE:
|
|
|
|
case LFUN_LABEL_INSERT:
|
2007-10-11 14:52:00 +00:00
|
|
|
case LFUN_INFO_INSERT:
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_PARAGRAPH_GOTO:
|
|
|
|
case LFUN_NOTE_NEXT:
|
|
|
|
case LFUN_REFERENCE_NEXT:
|
|
|
|
case LFUN_WORD_FIND:
|
2009-09-09 05:20:30 +00:00
|
|
|
case LFUN_WORD_FIND_FORWARD:
|
|
|
|
case LFUN_WORD_FIND_BACKWARD:
|
2007-06-19 21:48:04 +00:00
|
|
|
case LFUN_WORD_REPLACE:
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_MARK_OFF:
|
|
|
|
case LFUN_MARK_ON:
|
|
|
|
case LFUN_MARK_TOGGLE:
|
2021-02-12 13:30:50 +00:00
|
|
|
case LFUN_SEARCH_STRING_SET:
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_SCREEN_RECENTER:
|
2009-03-26 00:09:16 +00:00
|
|
|
case LFUN_SCREEN_SHOW_CURSOR:
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_BIBTEX_DATABASE_ADD:
|
|
|
|
case LFUN_BIBTEX_DATABASE_DEL:
|
2022-12-18 12:25:24 +00:00
|
|
|
case LFUN_BIBTEX_DATABASE_LIST:
|
2008-01-08 15:18:00 +00:00
|
|
|
case LFUN_STATISTICS:
|
2009-09-19 16:59:36 +00:00
|
|
|
case LFUN_KEYMAP_OFF:
|
|
|
|
case LFUN_KEYMAP_PRIMARY:
|
|
|
|
case LFUN_KEYMAP_SECONDARY:
|
|
|
|
case LFUN_KEYMAP_TOGGLE:
|
2014-10-19 18:43:17 +00:00
|
|
|
case LFUN_INSET_SELECT_ALL:
|
2008-05-29 15:14:00 +00:00
|
|
|
flag.setEnabled(true);
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2008-11-16 00:12:21 +00:00
|
|
|
|
2018-02-08 20:33:37 +00:00
|
|
|
case LFUN_GRAPHICS_UNIFY:
|
2018-05-02 20:35:09 +00:00
|
|
|
flag.setEnabled(cur.countInsetsInSelection(GRAPHICS_CODE)>1);
|
2018-02-08 20:33:37 +00:00
|
|
|
break;
|
|
|
|
|
2011-12-30 15:31:56 +00:00
|
|
|
case LFUN_WORD_FINDADV: {
|
|
|
|
FindAndReplaceOptions opt;
|
|
|
|
istringstream iss(to_utf8(cmd.argument()));
|
|
|
|
iss >> opt;
|
|
|
|
flag.setEnabled(opt.repl_buf_name.empty()
|
|
|
|
|| !buffer_.isReadonly());
|
2015-03-04 21:13:20 +00:00
|
|
|
break;
|
2011-12-30 15:31:56 +00:00
|
|
|
}
|
|
|
|
|
2015-03-04 21:13:20 +00:00
|
|
|
case LFUN_LABEL_GOTO:
|
2008-05-29 15:14:00 +00:00
|
|
|
flag.setEnabled(!cmd.argument().empty()
|
2021-03-06 22:36:58 +00:00
|
|
|
|| getInsetByCode<InsetRef>(cur, REF_CODE)
|
|
|
|
|| getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE));
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_CHANGES_MERGE:
|
2006-10-04 21:43:40 +00:00
|
|
|
case LFUN_CHANGE_NEXT:
|
2009-04-05 19:14:37 +00:00
|
|
|
case LFUN_CHANGE_PREVIOUS:
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_ALL_CHANGES_ACCEPT:
|
|
|
|
case LFUN_ALL_CHANGES_REJECT:
|
2016-05-03 21:15:21 +00:00
|
|
|
flag.setEnabled(buffer_.areChangesPresent());
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
|
2007-09-15 12:28:41 +00:00
|
|
|
case LFUN_SCREEN_UP:
|
|
|
|
case LFUN_SCREEN_DOWN:
|
2007-12-27 11:37:07 +00:00
|
|
|
case LFUN_SCROLL:
|
2007-09-15 12:28:41 +00:00
|
|
|
case LFUN_SCREEN_UP_SELECT:
|
|
|
|
case LFUN_SCREEN_DOWN_SELECT:
|
2010-01-07 15:47:58 +00:00
|
|
|
case LFUN_INSET_FORALL:
|
2008-05-29 15:14:00 +00:00
|
|
|
flag.setEnabled(true);
|
2007-09-15 12:28:41 +00:00
|
|
|
break;
|
|
|
|
|
2007-10-11 13:20:21 +00:00
|
|
|
case LFUN_LAYOUT_TABULAR:
|
2008-05-29 15:14:00 +00:00
|
|
|
flag.setEnabled(cur.innerInsetOfType(TABULAR_CODE));
|
2007-10-11 13:20:21 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_LAYOUT:
|
2008-07-10 17:41:52 +00:00
|
|
|
flag.setEnabled(!cur.inset().forcePlainLayout(cur.idx()));
|
2008-03-19 16:21:33 +00:00
|
|
|
break;
|
|
|
|
|
2007-10-11 13:20:21 +00:00
|
|
|
case LFUN_LAYOUT_PARAGRAPH:
|
2008-05-29 15:14:00 +00:00
|
|
|
flag.setEnabled(cur.inset().allowParagraphCustomization(cur.idx()));
|
2007-10-11 13:20:21 +00:00
|
|
|
break;
|
|
|
|
|
2014-03-04 10:56:03 +00:00
|
|
|
case LFUN_BRANCH_ADD_INSERT:
|
|
|
|
flag.setEnabled(!(cur.inTexted() && cur.paragraph().isPassThru()));
|
|
|
|
break;
|
|
|
|
|
2007-10-11 13:20:21 +00:00
|
|
|
case LFUN_DIALOG_SHOW_NEW_INSET:
|
2009-07-16 10:08:13 +00:00
|
|
|
// FIXME: this is wrong, but I do not understand the
|
|
|
|
// intent (JMarc)
|
2009-04-06 12:12:06 +00:00
|
|
|
if (cur.inset().lyxCode() == CAPTION_CODE)
|
|
|
|
return cur.inset().getStatus(cur, cmd, flag);
|
2009-07-16 10:08:13 +00:00
|
|
|
// FIXME we should consider passthru paragraphs too.
|
2011-01-26 11:04:42 +00:00
|
|
|
flag.setEnabled(!(cur.inTexted() && cur.paragraph().isPassThru()));
|
2007-10-11 13:20:21 +00:00
|
|
|
break;
|
|
|
|
|
2009-09-26 17:30:29 +00:00
|
|
|
case LFUN_CITATION_INSERT: {
|
|
|
|
FuncRequest fr(LFUN_INSET_INSERT, "citation");
|
|
|
|
// FIXME: This could turn in a recursive hell.
|
|
|
|
// Shouldn't we use Buffer::getStatus() instead?
|
|
|
|
flag.setEnabled(lyx::getStatus(fr).enabled());
|
|
|
|
break;
|
|
|
|
}
|
2009-09-27 10:29:13 +00:00
|
|
|
case LFUN_INSET_APPLY: {
|
|
|
|
string const name = cmd.getArg(0);
|
|
|
|
Inset * inset = editedInset(name);
|
|
|
|
if (inset) {
|
|
|
|
FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
|
2010-04-16 12:15:27 +00:00
|
|
|
if (!inset->getStatus(cur, fr, flag)) {
|
2009-09-27 10:29:13 +00:00
|
|
|
// Every inset is supposed to handle this
|
|
|
|
LASSERT(false, break);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
|
2010-04-16 12:15:27 +00:00
|
|
|
flag = lyx::getStatus(fr);
|
2009-09-27 10:29:13 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2009-09-26 17:30:29 +00:00
|
|
|
|
2021-01-08 18:27:19 +00:00
|
|
|
case LFUN_COPY:
|
|
|
|
flag.setEnabled(cur.selection());
|
|
|
|
break;
|
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
default:
|
2009-04-06 12:12:06 +00:00
|
|
|
return false;
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
|
2009-04-06 12:12:06 +00:00
|
|
|
return true;
|
2004-04-01 08:58:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-09-27 10:29:13 +00:00
|
|
|
Inset * BufferView::editedInset(string const & name) const
|
|
|
|
{
|
|
|
|
map<string, Inset *>::const_iterator it = d->edited_insets_.find(name);
|
2020-02-28 06:21:02 +00:00
|
|
|
return it == d->edited_insets_.end() ? nullptr : it->second;
|
2009-09-27 10:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BufferView::editInset(string const & name, Inset * inset)
|
|
|
|
{
|
2018-12-19 20:31:26 +00:00
|
|
|
if (inset)
|
|
|
|
d->edited_insets_[name] = inset;
|
|
|
|
else
|
|
|
|
d->edited_insets_.erase(name);
|
2009-09-27 10:29:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-01-25 15:14:41 +00:00
|
|
|
void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
|
2001-02-23 16:10:03 +00:00
|
|
|
{
|
2012-06-07 15:19:04 +00:00
|
|
|
LYXERR(Debug::ACTION, "BufferView::dispatch: cmd: " << cmd);
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2009-09-19 20:56:12 +00:00
|
|
|
string const argument = to_utf8(cmd.argument());
|
2007-10-13 07:33:07 +00:00
|
|
|
Cursor & cur = d->cursor_;
|
2015-06-05 09:43:05 +00:00
|
|
|
Cursor old = cur;
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2009-09-21 12:36:22 +00:00
|
|
|
// Don't dispatch function that does not apply to internal buffers.
|
2014-07-25 20:35:08 +00:00
|
|
|
if (buffer_.isInternal()
|
2010-04-09 19:00:42 +00:00
|
|
|
&& lyxaction.funcHasFlag(cmd.action(), LyXAction::NoInternal))
|
2010-01-25 15:14:41 +00:00
|
|
|
return;
|
2009-09-21 12:36:22 +00:00
|
|
|
|
2009-10-03 19:12:21 +00:00
|
|
|
// We'll set this back to false if need be.
|
|
|
|
bool dispatched = true;
|
2009-11-06 13:32:58 +00:00
|
|
|
buffer_.undo().beginUndoGroup();
|
2009-10-03 19:12:21 +00:00
|
|
|
|
2010-04-09 19:00:42 +00:00
|
|
|
FuncCode const act = cmd.action();
|
|
|
|
switch (act) {
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2009-09-19 17:56:53 +00:00
|
|
|
case LFUN_BUFFER_PARAMS_APPLY: {
|
2012-06-04 17:42:46 +00:00
|
|
|
DocumentClassConstPtr olddc = buffer_.params().documentClassPtr();
|
2015-01-17 19:38:22 +00:00
|
|
|
cur.recordUndoBufferParams();
|
2009-09-19 17:56:53 +00:00
|
|
|
istringstream ss(to_utf8(cmd.argument()));
|
|
|
|
Lexer lex;
|
|
|
|
lex.setStream(ss);
|
|
|
|
int const unknown_tokens = buffer_.readHeader(lex);
|
|
|
|
if (unknown_tokens != 0) {
|
|
|
|
LYXERR0("Warning in LFUN_BUFFER_PARAMS_APPLY!\n"
|
|
|
|
<< unknown_tokens << " unknown token"
|
|
|
|
<< (unknown_tokens == 1 ? "" : "s"));
|
|
|
|
}
|
2012-06-04 17:42:46 +00:00
|
|
|
updateDocumentClass(olddc);
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2009-09-19 17:56:53 +00:00
|
|
|
// We are most certainly here because of a change in the document
|
|
|
|
// It is then better to make sure that all dialogs are in sync with
|
|
|
|
// current document settings.
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2009-09-19 17:56:53 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2009-09-19 20:56:12 +00:00
|
|
|
case LFUN_LAYOUT_MODULES_CLEAR: {
|
2015-01-17 19:38:22 +00:00
|
|
|
// FIXME: this modifies the document in cap::switchBetweenClasses
|
|
|
|
// without calling recordUndo. Fix this before using
|
|
|
|
// recordUndoBufferParams().
|
|
|
|
cur.recordUndoFullBuffer();
|
2009-09-19 20:56:12 +00:00
|
|
|
buffer_.params().clearLayoutModules();
|
2012-06-04 17:39:24 +00:00
|
|
|
makeDocumentClass();
|
2010-10-13 18:04:44 +00:00
|
|
|
dr.screenUpdate(Update::Force);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2009-09-19 20:56:12 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case LFUN_LAYOUT_MODULE_ADD: {
|
|
|
|
BufferParams const & params = buffer_.params();
|
2012-12-05 16:16:21 +00:00
|
|
|
if (!params.layoutModuleCanBeAdded(argument)) {
|
2014-07-25 20:35:08 +00:00
|
|
|
LYXERR0("Module `" << argument <<
|
2009-09-19 20:56:12 +00:00
|
|
|
"' cannot be added due to failed requirements or "
|
|
|
|
"conflicts with installed modules.");
|
|
|
|
break;
|
|
|
|
}
|
2015-01-17 19:38:22 +00:00
|
|
|
// FIXME: this modifies the document in cap::switchBetweenClasses
|
|
|
|
// without calling recordUndo. Fix this before using
|
|
|
|
// recordUndoBufferParams().
|
|
|
|
cur.recordUndoFullBuffer();
|
2009-09-19 20:56:12 +00:00
|
|
|
buffer_.params().addLayoutModule(argument);
|
2012-06-04 17:39:24 +00:00
|
|
|
makeDocumentClass();
|
2010-10-13 18:04:44 +00:00
|
|
|
dr.screenUpdate(Update::Force);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2009-09-19 20:56:12 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case LFUN_TEXTCLASS_APPLY: {
|
2014-07-25 20:35:08 +00:00
|
|
|
// since this shortcircuits, the second call is made only if
|
2010-07-29 00:00:37 +00:00
|
|
|
// the first fails
|
2014-07-25 20:35:08 +00:00
|
|
|
bool const success =
|
2010-07-29 00:00:37 +00:00
|
|
|
LayoutFileList::get().load(argument, buffer_.temppath()) ||
|
|
|
|
LayoutFileList::get().load(argument, buffer_.filePath());
|
2010-07-28 21:03:27 +00:00
|
|
|
if (!success) {
|
|
|
|
docstring s = bformat(_("The document class `%1$s' "
|
|
|
|
"could not be loaded."), from_utf8(argument));
|
|
|
|
frontend::Alert::error(_("Could not load class"), s);
|
2009-09-19 20:56:12 +00:00
|
|
|
break;
|
2010-07-28 21:03:27 +00:00
|
|
|
}
|
2009-09-19 20:56:12 +00:00
|
|
|
|
|
|
|
LayoutFile const * old_layout = buffer_.params().baseClass();
|
|
|
|
LayoutFile const * new_layout = &(LayoutFileList::get()[argument]);
|
|
|
|
|
|
|
|
if (old_layout == new_layout)
|
|
|
|
// nothing to do
|
|
|
|
break;
|
|
|
|
|
2010-07-28 21:03:27 +00:00
|
|
|
// Save the old, possibly modular, layout for use in conversion.
|
2015-01-17 19:38:22 +00:00
|
|
|
// FIXME: this modifies the document in cap::switchBetweenClasses
|
|
|
|
// without calling recordUndo. Fix this before using
|
|
|
|
// recordUndoBufferParams().
|
|
|
|
cur.recordUndoFullBuffer();
|
2018-11-16 20:47:24 +00:00
|
|
|
buffer_.params().setBaseClass(argument, buffer_.layoutPos());
|
2012-06-04 17:39:24 +00:00
|
|
|
makeDocumentClass();
|
2010-10-13 18:04:44 +00:00
|
|
|
dr.screenUpdate(Update::Force);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2009-09-19 20:56:12 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-07-28 21:03:27 +00:00
|
|
|
case LFUN_TEXTCLASS_LOAD: {
|
2014-07-25 20:35:08 +00:00
|
|
|
// since this shortcircuits, the second call is made only if
|
2010-07-29 00:00:37 +00:00
|
|
|
// the first fails
|
2014-07-25 20:35:08 +00:00
|
|
|
bool const success =
|
2010-07-29 00:00:37 +00:00
|
|
|
LayoutFileList::get().load(argument, buffer_.temppath()) ||
|
|
|
|
LayoutFileList::get().load(argument, buffer_.filePath());
|
2014-07-25 20:35:08 +00:00
|
|
|
if (!success) {
|
2010-07-28 21:03:27 +00:00
|
|
|
docstring s = bformat(_("The document class `%1$s' "
|
|
|
|
"could not be loaded."), from_utf8(argument));
|
|
|
|
frontend::Alert::error(_("Could not load class"), s);
|
|
|
|
}
|
2009-09-19 20:56:12 +00:00
|
|
|
break;
|
2010-07-28 21:03:27 +00:00
|
|
|
}
|
2009-09-19 20:56:12 +00:00
|
|
|
|
|
|
|
case LFUN_LAYOUT_RELOAD: {
|
|
|
|
LayoutFileIndex bc = buffer_.params().baseClassID();
|
|
|
|
LayoutFileList::get().reset(bc);
|
2018-11-16 20:47:24 +00:00
|
|
|
buffer_.params().setBaseClass(bc, buffer_.layoutPos());
|
2012-06-04 17:39:24 +00:00
|
|
|
makeDocumentClass();
|
2010-10-13 18:04:44 +00:00
|
|
|
dr.screenUpdate(Update::Force);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2009-09-19 20:56:12 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-01-29 04:33:45 +00:00
|
|
|
case LFUN_UNDO: {
|
2010-01-25 15:14:41 +00:00
|
|
|
dr.setMessage(_("Undo"));
|
2007-01-24 23:10:03 +00:00
|
|
|
cur.clearSelection();
|
2018-01-29 04:33:45 +00:00
|
|
|
// We need to find out if the bibliography information
|
|
|
|
// has changed. See bug #11055.
|
|
|
|
// So these should not be references...
|
2018-05-20 10:27:03 +00:00
|
|
|
string const engine = buffer().params().citeEngine();
|
2018-01-29 04:33:45 +00:00
|
|
|
CiteEngineType const enginetype = buffer().params().citeEngineType();
|
2018-08-26 14:47:25 +00:00
|
|
|
if (!cur.undoAction())
|
2010-01-25 15:14:41 +00:00
|
|
|
dr.setMessage(_("No further undo information"));
|
2018-01-29 04:33:45 +00:00
|
|
|
else {
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2018-01-29 04:33:45 +00:00
|
|
|
dr.forceBufferUpdate();
|
2021-10-12 13:45:04 +00:00
|
|
|
resetInlineCompletionPos();
|
2018-05-20 10:27:03 +00:00
|
|
|
if (buffer().params().citeEngine() != engine ||
|
2018-01-29 04:33:45 +00:00
|
|
|
buffer().params().citeEngineType() != enginetype)
|
|
|
|
buffer().invalidateCiteLabels();
|
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2018-01-29 04:33:45 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2018-01-29 04:33:45 +00:00
|
|
|
case LFUN_REDO: {
|
2010-01-25 15:14:41 +00:00
|
|
|
dr.setMessage(_("Redo"));
|
2007-01-24 23:10:03 +00:00
|
|
|
cur.clearSelection();
|
2018-01-29 04:33:45 +00:00
|
|
|
// We need to find out if the bibliography information
|
|
|
|
// has changed. See bug #11055.
|
|
|
|
// So these should not be references...
|
2018-05-20 10:27:03 +00:00
|
|
|
string const engine = buffer().params().citeEngine();
|
2018-01-29 04:33:45 +00:00
|
|
|
CiteEngineType const enginetype = buffer().params().citeEngineType();
|
2018-08-26 14:47:25 +00:00
|
|
|
if (!cur.redoAction())
|
2010-01-25 15:14:41 +00:00
|
|
|
dr.setMessage(_("No further redo information"));
|
2018-01-29 04:33:45 +00:00
|
|
|
else {
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2018-01-29 04:33:45 +00:00
|
|
|
dr.forceBufferUpdate();
|
2021-10-12 13:45:04 +00:00
|
|
|
resetInlineCompletionPos();
|
2018-05-20 10:27:03 +00:00
|
|
|
if (buffer().params().citeEngine() != engine ||
|
2018-01-29 04:33:45 +00:00
|
|
|
buffer().params().citeEngineType() != enginetype)
|
|
|
|
buffer().invalidateCiteLabels();
|
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2018-01-29 04:33:45 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
|
|
|
|
case LFUN_FONT_STATE:
|
2017-07-23 22:21:43 +00:00
|
|
|
dr.setMessage(cur.currentState(false));
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_BOOKMARK_SAVE:
|
2021-02-26 18:05:35 +00:00
|
|
|
dr.screenUpdate(Update::Force);
|
2006-11-01 15:55:17 +00:00
|
|
|
saveBookmark(convert<unsigned int>(to_utf8(cmd.argument())));
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_LABEL_GOTO: {
|
2006-10-12 10:50:45 +00:00
|
|
|
docstring label = cmd.argument();
|
2006-09-16 10:08:51 +00:00
|
|
|
if (label.empty()) {
|
|
|
|
InsetRef * inset =
|
2009-04-06 18:25:23 +00:00
|
|
|
getInsetByCode<InsetRef>(cur, REF_CODE);
|
2006-09-16 10:08:51 +00:00
|
|
|
if (inset) {
|
2006-10-20 16:12:49 +00:00
|
|
|
label = inset->getParam("reference");
|
2006-11-01 15:55:17 +00:00
|
|
|
// persistent=false: use temp_bookmark
|
2007-03-29 03:41:55 +00:00
|
|
|
saveBookmark(0);
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
}
|
2010-10-13 18:04:44 +00:00
|
|
|
if (!label.empty()) {
|
2006-09-16 10:08:51 +00:00
|
|
|
gotoLabel(label);
|
2010-10-13 18:04:44 +00:00
|
|
|
// at the moment, this is redundant, since gotoLabel will
|
|
|
|
// eventually call LFUN_PARAGRAPH_GOTO, but it seems best
|
|
|
|
// to have it here.
|
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2021-03-06 22:36:58 +00:00
|
|
|
} else {
|
|
|
|
InsetMathRef * minset =
|
|
|
|
getInsetByCode<InsetMathRef>(cur, MATH_REF_CODE);
|
|
|
|
if (minset)
|
|
|
|
lyx::dispatch(FuncRequest(LFUN_LABEL_GOTO,
|
|
|
|
minset->getTarget()));
|
2010-10-13 18:04:44 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
}
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_PARAGRAPH_GOTO: {
|
2008-05-16 12:48:13 +00:00
|
|
|
int const id = convert<int>(cmd.getArg(0));
|
2016-09-05 02:23:24 +00:00
|
|
|
pos_type const pos = convert<int>(cmd.getArg(1));
|
|
|
|
if (id < 0)
|
|
|
|
break;
|
|
|
|
string const str_id_end = cmd.getArg(2);
|
|
|
|
string const str_pos_end = cmd.getArg(3);
|
2006-11-13 16:53:49 +00:00
|
|
|
int i = 0;
|
2007-08-21 07:33:46 +00:00
|
|
|
for (Buffer * b = &buffer_; i == 0 || b != &buffer_;
|
|
|
|
b = theBufferList().next(b)) {
|
|
|
|
|
2018-02-24 04:35:17 +00:00
|
|
|
Cursor curs(*this);
|
|
|
|
curs.setCursor(b->getParFromID(id));
|
|
|
|
if (curs.atEnd()) {
|
2007-11-15 20:04:51 +00:00
|
|
|
LYXERR(Debug::INFO, "No matching paragraph found! [" << id << "].");
|
2008-02-12 08:04:59 +00:00
|
|
|
++i;
|
|
|
|
continue;
|
|
|
|
}
|
2018-02-24 04:35:17 +00:00
|
|
|
LYXERR(Debug::INFO, "Paragraph " << curs.paragraph().id()
|
2008-02-12 08:04:59 +00:00
|
|
|
<< " found in buffer `"
|
|
|
|
<< b->absFileName() << "'.");
|
|
|
|
|
|
|
|
if (b == &buffer_) {
|
2016-09-05 02:23:24 +00:00
|
|
|
bool success;
|
|
|
|
if (str_id_end.empty() || str_pos_end.empty()) {
|
|
|
|
// Set the cursor
|
2018-02-24 04:35:17 +00:00
|
|
|
curs.pos() = pos;
|
|
|
|
mouseSetCursor(curs);
|
2016-09-05 02:23:24 +00:00
|
|
|
success = true;
|
|
|
|
} else {
|
|
|
|
int const id_end = convert<int>(str_id_end);
|
|
|
|
pos_type const pos_end = convert<int>(str_pos_end);
|
|
|
|
success = setCursorFromEntries({id, pos},
|
|
|
|
{id_end, pos_end});
|
|
|
|
}
|
2022-11-13 21:47:39 +00:00
|
|
|
if (success && scrollToCursor(d->cursor_, SCROLL_TOP))
|
2021-02-17 11:30:16 +00:00
|
|
|
dr.screenUpdate(Update::Force);
|
2006-11-13 16:53:49 +00:00
|
|
|
} else {
|
2008-02-12 08:04:59 +00:00
|
|
|
// Switch to other buffer view and resend cmd
|
2010-02-09 14:26:59 +00:00
|
|
|
lyx::dispatch(FuncRequest(
|
2008-02-12 08:04:59 +00:00
|
|
|
LFUN_BUFFER_SWITCH, b->absFileName()));
|
2010-02-09 14:26:59 +00:00
|
|
|
lyx::dispatch(cmd);
|
2006-11-13 16:53:49 +00:00
|
|
|
}
|
2008-02-12 08:04:59 +00:00
|
|
|
break;
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case LFUN_NOTE_NEXT:
|
2021-02-17 11:30:16 +00:00
|
|
|
if (gotoInset(this, { NOTE_CODE }, false))
|
|
|
|
dr.screenUpdate(Update::Force);
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_REFERENCE_NEXT: {
|
2021-02-17 11:30:16 +00:00
|
|
|
if (gotoInset(this, { LABEL_CODE, REF_CODE }, true))
|
|
|
|
dr.screenUpdate(Update::Force);
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2006-10-04 21:43:40 +00:00
|
|
|
case LFUN_CHANGE_NEXT:
|
2006-10-21 00:16:43 +00:00
|
|
|
findNextChange(this);
|
2020-01-11 15:17:04 +00:00
|
|
|
if (cur.inset().isTable())
|
|
|
|
// In tables, there might be whole changed rows or columns
|
|
|
|
cur.dispatch(cmd);
|
2008-10-24 15:46:44 +00:00
|
|
|
// FIXME: Move this LFUN to Buffer so that we don't have to do this:
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2006-10-04 21:43:40 +00:00
|
|
|
break;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2009-04-05 19:14:37 +00:00
|
|
|
case LFUN_CHANGE_PREVIOUS:
|
|
|
|
findPreviousChange(this);
|
2020-01-11 15:17:04 +00:00
|
|
|
if (cur.inset().isTable())
|
|
|
|
// In tables, there might be whole changed rows or columns
|
|
|
|
cur.dispatch(cmd);
|
2009-04-05 19:14:37 +00:00
|
|
|
// FIXME: Move this LFUN to Buffer so that we don't have to do this:
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2009-04-05 19:14:37 +00:00
|
|
|
break;
|
2006-10-04 21:43:40 +00:00
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_CHANGES_MERGE:
|
2009-04-05 19:26:26 +00:00
|
|
|
if (findNextChange(this) || findPreviousChange(this)) {
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2006-09-19 13:36:20 +00:00
|
|
|
showDialog("changes");
|
2008-10-24 15:46:44 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
|
2019-12-27 13:49:51 +00:00
|
|
|
case LFUN_ALL_CHANGES_ACCEPT: {
|
2007-02-01 13:19:05 +00:00
|
|
|
// select complete document
|
2009-10-29 22:46:04 +00:00
|
|
|
cur.reset();
|
2009-04-06 18:25:23 +00:00
|
|
|
cur.selHandle(true);
|
|
|
|
buffer_.text().cursorBottom(cur);
|
2007-02-01 13:19:05 +00:00
|
|
|
// accept everything in a single step to support atomic undo
|
2019-12-27 13:49:51 +00:00
|
|
|
// temporarily disable track changes in order to end with really
|
|
|
|
// no new (e.g., DPSM-caused) changes (see #7487)
|
|
|
|
bool const track = buffer_.params().track_changes;
|
|
|
|
buffer_.params().track_changes = false;
|
2009-04-06 18:25:23 +00:00
|
|
|
buffer_.text().acceptOrRejectChanges(cur, Text::ACCEPT);
|
2019-12-27 13:49:51 +00:00
|
|
|
buffer_.params().track_changes = track;
|
2011-04-15 02:01:28 +00:00
|
|
|
cur.resetAnchor();
|
2008-07-11 09:03:09 +00:00
|
|
|
// FIXME: Move this LFUN to Buffer so that we don't have to do this:
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2019-12-27 13:49:51 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2019-12-27 13:49:51 +00:00
|
|
|
case LFUN_ALL_CHANGES_REJECT: {
|
2007-02-01 13:19:05 +00:00
|
|
|
// select complete document
|
2009-10-29 22:46:04 +00:00
|
|
|
cur.reset();
|
2009-04-06 18:25:23 +00:00
|
|
|
cur.selHandle(true);
|
|
|
|
buffer_.text().cursorBottom(cur);
|
2007-02-01 13:19:05 +00:00
|
|
|
// reject everything in a single step to support atomic undo
|
2019-12-27 13:49:51 +00:00
|
|
|
// temporarily disable track changes in order to end with really
|
|
|
|
// no new (e.g., DPSM-caused) changes (see #7487)
|
|
|
|
bool const track = buffer_.params().track_changes;
|
|
|
|
buffer_.params().track_changes = false;
|
2009-04-06 18:25:23 +00:00
|
|
|
buffer_.text().acceptOrRejectChanges(cur, Text::REJECT);
|
2019-12-27 13:49:51 +00:00
|
|
|
buffer_.params().track_changes = track;
|
2011-04-15 02:01:28 +00:00
|
|
|
cur.resetAnchor();
|
2008-07-11 09:03:09 +00:00
|
|
|
// FIXME: Move this LFUN to Buffer so that we don't have to do this:
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2019-12-27 13:49:51 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2009-09-09 05:20:30 +00:00
|
|
|
case LFUN_WORD_FIND_FORWARD:
|
|
|
|
case LFUN_WORD_FIND_BACKWARD: {
|
|
|
|
docstring searched_string;
|
|
|
|
|
|
|
|
if (!cmd.argument().empty()) {
|
2021-02-14 17:56:25 +00:00
|
|
|
setSearchRequestCache(cmd.argument());
|
2009-09-09 05:20:30 +00:00
|
|
|
searched_string = cmd.argument();
|
|
|
|
} else {
|
2021-02-14 17:56:25 +00:00
|
|
|
searched_string = searchRequestCache();
|
2009-09-09 05:20:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (searched_string.empty())
|
|
|
|
break;
|
|
|
|
|
|
|
|
docstring const data =
|
2021-02-15 13:12:07 +00:00
|
|
|
find2string(searched_string, false, false,
|
2021-02-16 07:11:09 +00:00
|
|
|
act == LFUN_WORD_FIND_FORWARD, false, false, false);
|
2010-10-13 18:53:41 +00:00
|
|
|
bool found = lyxfind(this, FuncRequest(LFUN_WORD_FIND, data));
|
2016-09-14 08:23:39 +00:00
|
|
|
if (found)
|
2010-10-13 18:53:41 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2021-01-04 06:06:33 +00:00
|
|
|
else
|
|
|
|
dr.setMessage(_("Search string not found!"));
|
2009-09-09 05:20:30 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2008-03-26 19:20:17 +00:00
|
|
|
case LFUN_WORD_FIND: {
|
2021-02-12 13:30:50 +00:00
|
|
|
docstring arg = cmd.argument();
|
2021-02-14 17:56:25 +00:00
|
|
|
if (arg.empty())
|
|
|
|
arg = searchRequestCache();
|
2021-02-12 13:30:50 +00:00
|
|
|
if (arg.empty()) {
|
2010-02-09 14:26:59 +00:00
|
|
|
lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, "findreplace"));
|
2008-09-26 10:07:10 +00:00
|
|
|
break;
|
|
|
|
}
|
2021-02-12 13:30:50 +00:00
|
|
|
if (lyxfind(this, FuncRequest(act, arg)))
|
2010-10-13 18:53:41 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2021-01-04 06:06:33 +00:00
|
|
|
else
|
|
|
|
dr.setMessage(_("Search string not found!"));
|
2016-09-14 08:23:39 +00:00
|
|
|
|
2021-02-14 17:56:25 +00:00
|
|
|
setSearchRequestCache(arg);
|
2021-02-12 13:30:50 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case LFUN_SEARCH_STRING_SET: {
|
|
|
|
docstring pattern = cmd.argument();
|
|
|
|
if (!pattern.empty()) {
|
2021-02-14 17:56:25 +00:00
|
|
|
setSearchRequestCache(pattern);
|
2021-02-12 13:30:50 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (cur.selection())
|
|
|
|
pattern = cur.selectionAsString(false);
|
2022-08-11 20:35:58 +00:00
|
|
|
else if (!cur.inTexted())
|
|
|
|
break; // not suitable for selectWord at cursor
|
2021-02-12 13:30:50 +00:00
|
|
|
else {
|
|
|
|
pos_type spos = cur.pos();
|
|
|
|
cur.innerText()->selectWord(cur, WHOLE_WORD);
|
|
|
|
pattern = cur.selectionAsString(false);
|
|
|
|
cur.selection(false);
|
|
|
|
cur.pos() = spos;
|
|
|
|
}
|
2021-02-14 17:56:25 +00:00
|
|
|
setSearchRequestCache(pattern);
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2008-03-26 19:20:17 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2007-06-19 21:48:04 +00:00
|
|
|
case LFUN_WORD_REPLACE: {
|
2021-01-12 15:13:47 +00:00
|
|
|
if (lyxreplace(this, cmd)) {
|
2010-10-13 18:53:41 +00:00
|
|
|
dr.forceBufferUpdate();
|
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
|
|
|
}
|
2021-01-04 06:06:33 +00:00
|
|
|
else
|
|
|
|
dr.setMessage(_("Search string not found!"));
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2007-06-19 21:48:04 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2010-01-04 12:29:38 +00:00
|
|
|
case LFUN_WORD_FINDADV: {
|
|
|
|
FindAndReplaceOptions opt;
|
|
|
|
istringstream iss(to_utf8(cmd.argument()));
|
|
|
|
iss >> opt;
|
2010-11-13 12:02:04 +00:00
|
|
|
if (findAdv(this, opt)) {
|
2010-10-14 15:02:48 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2010-11-13 12:02:04 +00:00
|
|
|
cur.dispatched();
|
|
|
|
dispatched = true;
|
|
|
|
} else {
|
|
|
|
cur.undispatched();
|
2010-11-13 11:35:37 +00:00
|
|
|
dispatched = false;
|
2010-11-13 12:02:04 +00:00
|
|
|
}
|
2008-11-16 00:12:21 +00:00
|
|
|
break;
|
2010-01-04 12:29:38 +00:00
|
|
|
}
|
2008-11-16 00:12:21 +00:00
|
|
|
|
2022-11-05 17:39:33 +00:00
|
|
|
case LFUN_INDEX_TAG_ALL: {
|
2023-06-18 10:45:25 +00:00
|
|
|
if (cur.pos() == 0)
|
|
|
|
// nothing precedes
|
|
|
|
break;
|
|
|
|
|
2022-11-05 17:39:33 +00:00
|
|
|
Inset * ins = cur.nextInset();
|
|
|
|
if (!ins || ins->lyxCode() != INDEX_CODE)
|
|
|
|
// not at index inset
|
|
|
|
break;
|
|
|
|
|
|
|
|
// clone the index inset
|
|
|
|
InsetIndex * cins =
|
|
|
|
new InsetIndex(static_cast<InsetIndex &>(*cur.nextInset()));
|
|
|
|
// In order to avoid duplication, we compare the
|
|
|
|
// LaTeX output if we find another index inset after
|
|
|
|
// the word
|
|
|
|
odocstringstream oilatex;
|
|
|
|
otexstream oits(oilatex);
|
|
|
|
OutputParams rp(&cur.buffer()->params().encoding());
|
|
|
|
ins->latex(oits, rp);
|
|
|
|
cap::copyInsetToTemp(cur, cins);
|
|
|
|
|
|
|
|
// move backwards into preceding word
|
|
|
|
// skip over other index insets
|
|
|
|
cur.backwardPosIgnoreCollapsed();
|
|
|
|
while (true) {
|
|
|
|
if (cur.inset().lyxCode() == INDEX_CODE)
|
|
|
|
cur.pop_back();
|
|
|
|
else if (cur.prevInset() && cur.prevInset()->lyxCode() == INDEX_CODE)
|
|
|
|
cur.backwardPosIgnoreCollapsed();
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (!cur.inTexted()) {
|
|
|
|
// Nothing to do here.
|
|
|
|
setCursorFromInset(ins);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// Get word or selection
|
|
|
|
cur.text()->selectWord(cur, WHOLE_WORD);
|
|
|
|
docstring const searched_string = cur.selectionAsString(false);
|
2023-06-18 10:45:25 +00:00
|
|
|
if (searched_string.empty())
|
|
|
|
break;
|
2022-11-05 17:39:33 +00:00
|
|
|
// Start from the beginning
|
|
|
|
lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN));
|
|
|
|
while (findOne(this, searched_string,
|
|
|
|
false,// case sensitive
|
|
|
|
true,// match whole word only
|
|
|
|
true,// forward
|
|
|
|
false,//find deleted
|
|
|
|
false,//check wrap
|
|
|
|
false,// auto-wrap
|
|
|
|
false,// instant
|
|
|
|
false// only selection
|
|
|
|
)) {
|
|
|
|
cur.clearSelection();
|
|
|
|
Inset * ains = cur.nextInset();
|
|
|
|
if (ains && ains->lyxCode() == INDEX_CODE) {
|
|
|
|
// We have an index inset.
|
|
|
|
// Check whether it has the same
|
|
|
|
// LaTeX content and move on if so.
|
|
|
|
odocstringstream filatex;
|
|
|
|
otexstream fits(filatex);
|
|
|
|
ains->latex(fits, rp);
|
|
|
|
if (oilatex.str() == filatex.str())
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Paste the inset and possibly continue
|
|
|
|
cap::pasteFromTemp(cursor(), cursor().buffer()->errorList("Paste"));
|
|
|
|
}
|
|
|
|
// Go back to start position.
|
|
|
|
setCursorFromInset(ins);
|
|
|
|
dr.screenUpdate(cur.result().screenUpdate());
|
|
|
|
if (cur.result().needBufferUpdate())
|
|
|
|
dr.forceBufferUpdate();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
case LFUN_MARK_OFF:
|
|
|
|
cur.clearSelection();
|
2010-01-25 15:14:41 +00:00
|
|
|
dr.setMessage(from_utf8(N_("Mark off")));
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_MARK_ON:
|
|
|
|
cur.clearSelection();
|
2008-09-24 21:27:41 +00:00
|
|
|
cur.setMark(true);
|
2010-01-25 15:14:41 +00:00
|
|
|
dr.setMessage(from_utf8(N_("Mark on")));
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_MARK_TOGGLE:
|
2016-02-28 16:36:29 +00:00
|
|
|
cur.selection(false);
|
2006-09-16 10:08:51 +00:00
|
|
|
if (cur.mark()) {
|
2008-09-24 21:27:41 +00:00
|
|
|
cur.setMark(false);
|
2010-01-25 15:14:41 +00:00
|
|
|
dr.setMessage(from_utf8(N_("Mark removed")));
|
2006-09-16 10:08:51 +00:00
|
|
|
} else {
|
2008-09-24 21:27:41 +00:00
|
|
|
cur.setMark(true);
|
2010-01-25 15:14:41 +00:00
|
|
|
dr.setMessage(from_utf8(N_("Mark set")));
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
2009-04-06 18:29:19 +00:00
|
|
|
cur.resetAnchor();
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
|
|
|
|
2009-03-26 00:09:16 +00:00
|
|
|
case LFUN_SCREEN_SHOW_CURSOR:
|
2007-12-21 08:35:43 +00:00
|
|
|
showCursor();
|
2006-09-16 10:08:51 +00:00
|
|
|
break;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2009-03-26 00:09:16 +00:00
|
|
|
case LFUN_SCREEN_RECENTER:
|
|
|
|
recenter();
|
|
|
|
break;
|
2006-09-16 10:08:51 +00:00
|
|
|
|
|
|
|
case LFUN_BIBTEX_DATABASE_ADD: {
|
2009-04-06 18:25:23 +00:00
|
|
|
Cursor tmpcur = cur;
|
2020-05-23 15:10:17 +00:00
|
|
|
findInset(tmpcur, { BIBTEX_CODE }, false);
|
2006-09-16 10:08:51 +00:00
|
|
|
InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
|
2007-10-13 09:04:52 +00:00
|
|
|
BIBTEX_CODE);
|
2006-09-16 10:08:51 +00:00
|
|
|
if (inset) {
|
2017-11-05 01:23:25 +00:00
|
|
|
if (inset->addDatabase(cmd.argument()))
|
2010-08-09 17:01:51 +00:00
|
|
|
dr.forceBufferUpdate();
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case LFUN_BIBTEX_DATABASE_DEL: {
|
2009-04-06 18:25:23 +00:00
|
|
|
Cursor tmpcur = cur;
|
2020-05-23 15:10:17 +00:00
|
|
|
findInset(tmpcur, { BIBTEX_CODE }, false);
|
2006-09-16 10:08:51 +00:00
|
|
|
InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
|
2007-10-13 09:04:52 +00:00
|
|
|
BIBTEX_CODE);
|
2006-09-16 10:08:51 +00:00
|
|
|
if (inset) {
|
2017-11-05 01:23:25 +00:00
|
|
|
if (inset->delDatabase(cmd.argument()))
|
2010-08-09 17:01:51 +00:00
|
|
|
dr.forceBufferUpdate();
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-02-08 20:33:37 +00:00
|
|
|
case LFUN_GRAPHICS_UNIFY: {
|
|
|
|
|
|
|
|
cur.recordUndoFullBuffer();
|
|
|
|
|
|
|
|
DocIterator from, to;
|
|
|
|
from = cur.selectionBegin();
|
|
|
|
to = cur.selectionEnd();
|
|
|
|
|
2018-02-09 22:46:14 +00:00
|
|
|
string const newId = cmd.getArg(0);
|
|
|
|
bool fetchId = newId.empty(); //if we wait for groupId from first graphics inset
|
2018-02-08 20:33:37 +00:00
|
|
|
|
|
|
|
InsetGraphicsParams grp_par;
|
2018-02-09 22:46:14 +00:00
|
|
|
if (!fetchId)
|
|
|
|
InsetGraphics::string2params(graphics::getGroupParams(buffer_, newId), buffer_, grp_par);
|
2018-02-08 20:33:37 +00:00
|
|
|
|
|
|
|
if (!from.nextInset()) //move to closest inset
|
|
|
|
from.forwardInset();
|
|
|
|
|
|
|
|
while (!from.empty() && from < to) {
|
|
|
|
Inset * inset = from.nextInset();
|
|
|
|
if (!inset)
|
|
|
|
break;
|
2018-02-09 22:46:14 +00:00
|
|
|
InsetGraphics * ig = inset->asInsetGraphics();
|
|
|
|
if (ig) {
|
2018-02-08 20:33:37 +00:00
|
|
|
InsetGraphicsParams inspar = ig->getParams();
|
|
|
|
if (fetchId) {
|
|
|
|
grp_par = inspar;
|
|
|
|
fetchId = false;
|
|
|
|
} else {
|
|
|
|
grp_par.filename = inspar.filename;
|
|
|
|
ig->setParams(grp_par);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
from.forwardInset();
|
|
|
|
}
|
|
|
|
dr.screenUpdate(Update::Force); //needed if triggered from context menu
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2022-12-18 12:25:24 +00:00
|
|
|
case LFUN_BIBTEX_DATABASE_LIST: {
|
|
|
|
docstring_list const & files = buffer_.getBibfiles();
|
|
|
|
bool first = true;
|
|
|
|
docstring result;
|
|
|
|
char const separator(os::path_separator());
|
|
|
|
for (auto const & file : files) {
|
|
|
|
if (first)
|
|
|
|
first = false;
|
|
|
|
else
|
|
|
|
result += separator;
|
|
|
|
|
|
|
|
FileName const fn = buffer_.getBibfilePath(file);
|
|
|
|
string const path = fn.realPath();
|
|
|
|
result += from_utf8(os::external_path(path));
|
|
|
|
}
|
|
|
|
dr.setMessage(result);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2008-01-08 15:18:00 +00:00
|
|
|
case LFUN_STATISTICS: {
|
2006-09-16 10:08:51 +00:00
|
|
|
DocIterator from, to;
|
|
|
|
if (cur.selection()) {
|
|
|
|
from = cur.selectionBegin();
|
|
|
|
to = cur.selectionEnd();
|
|
|
|
} else {
|
2008-11-17 11:46:07 +00:00
|
|
|
from = doc_iterator_begin(&buffer_);
|
|
|
|
to = doc_iterator_end(&buffer_);
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
2012-01-12 07:03:24 +00:00
|
|
|
buffer_.updateStatistics(from, to);
|
|
|
|
int const words = buffer_.wordCount();
|
|
|
|
int const chars = buffer_.charCount(false);
|
|
|
|
int const chars_blanks = buffer_.charCount(true);
|
2006-09-16 10:08:51 +00:00
|
|
|
docstring message;
|
2008-01-08 15:18:00 +00:00
|
|
|
if (cur.selection())
|
2008-01-18 22:50:19 +00:00
|
|
|
message = _("Statistics for the selection:");
|
2008-01-08 15:18:00 +00:00
|
|
|
else
|
2008-01-18 22:50:19 +00:00
|
|
|
message = _("Statistics for the document:");
|
|
|
|
message += "\n\n";
|
2008-01-08 15:18:00 +00:00
|
|
|
if (words != 1)
|
2008-01-18 22:50:19 +00:00
|
|
|
message += bformat(_("%1$d words"), words);
|
2008-01-08 15:18:00 +00:00
|
|
|
else
|
2008-01-18 22:50:19 +00:00
|
|
|
message += _("One word");
|
|
|
|
message += "\n";
|
2008-01-08 15:18:00 +00:00
|
|
|
if (chars_blanks != 1)
|
2022-09-13 10:42:28 +00:00
|
|
|
message += bformat(_("%1$d characters"), chars_blanks);
|
2008-01-08 15:18:00 +00:00
|
|
|
else
|
2022-09-13 10:42:28 +00:00
|
|
|
message += _("One character");
|
2008-01-18 22:50:19 +00:00
|
|
|
message += "\n";
|
2008-01-08 15:18:00 +00:00
|
|
|
if (chars != 1)
|
2022-09-13 10:42:28 +00:00
|
|
|
message += bformat(_("%1$d characters (no blanks)"), chars);
|
2008-01-08 15:18:00 +00:00
|
|
|
else
|
2022-09-13 10:42:28 +00:00
|
|
|
message += _("One character (no blanks)");
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2008-01-08 15:18:00 +00:00
|
|
|
Alert::information(_("Statistics"), message);
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2007-09-15 12:28:41 +00:00
|
|
|
case LFUN_SCREEN_UP:
|
|
|
|
case LFUN_SCREEN_DOWN: {
|
2010-04-21 13:03:04 +00:00
|
|
|
Point p = getPos(cur);
|
2009-05-15 21:24:08 +00:00
|
|
|
// This code has been commented out to enable to scroll down a
|
|
|
|
// document, even if there are large insets in it (see bug #5465).
|
|
|
|
/*if (p.y_ < 0 || p.y_ > height_) {
|
2007-09-15 14:33:37 +00:00
|
|
|
// The cursor is off-screen so recenter before proceeding.
|
2007-12-21 08:35:43 +00:00
|
|
|
showCursor();
|
2010-04-21 13:03:04 +00:00
|
|
|
p = getPos(cur);
|
2009-05-15 21:24:08 +00:00
|
|
|
}*/
|
2010-04-09 19:00:42 +00:00
|
|
|
int const scrolled = scroll(act == LFUN_SCREEN_UP
|
2009-05-20 19:54:29 +00:00
|
|
|
? -height_ : height_);
|
2010-04-09 19:00:42 +00:00
|
|
|
if (act == LFUN_SCREEN_UP && scrolled > -height_)
|
2008-07-04 11:33:28 +00:00
|
|
|
p = Point(0, 0);
|
2010-04-09 19:00:42 +00:00
|
|
|
if (act == LFUN_SCREEN_DOWN && scrolled < height_)
|
2008-07-04 11:33:28 +00:00
|
|
|
p = Point(width_, height_);
|
2009-03-26 23:08:47 +00:00
|
|
|
bool const in_texted = cur.inTexted();
|
2011-12-05 08:35:55 +00:00
|
|
|
cur.setCursor(doc_iterator_begin(cur.buffer()));
|
|
|
|
cur.selHandle(false);
|
2017-10-11 16:00:48 +00:00
|
|
|
// Force an immediate computation of metrics because we need it below
|
2021-11-13 18:24:05 +00:00
|
|
|
if (scrolled)
|
|
|
|
processUpdateFlags(Update::Force);
|
2010-03-29 15:21:10 +00:00
|
|
|
|
2009-05-20 20:28:24 +00:00
|
|
|
d->text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_,
|
2014-07-25 20:35:08 +00:00
|
|
|
true, act == LFUN_SCREEN_UP);
|
2007-09-15 12:28:41 +00:00
|
|
|
//FIXME: what to do with cur.x_target()?
|
2009-03-26 23:08:47 +00:00
|
|
|
bool update = in_texted && cur.bv().checkDepm(cur, old);
|
2007-10-18 11:51:17 +00:00
|
|
|
cur.finishUndo();
|
2011-12-16 14:36:31 +00:00
|
|
|
|
|
|
|
if (update || cur.mark())
|
2014-07-25 20:35:08 +00:00
|
|
|
dr.screenUpdate(Update::Force | Update::FitCursor);
|
2011-12-16 14:36:31 +00:00
|
|
|
if (update)
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2007-09-15 12:28:41 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-12-04 02:38:00 +00:00
|
|
|
case LFUN_SCROLL: {
|
|
|
|
string const scroll_type = cmd.getArg(0);
|
|
|
|
int scroll_step = 0;
|
|
|
|
if (scroll_type == "line")
|
|
|
|
scroll_step = d->scrollbarParameters_.single_step;
|
|
|
|
else if (scroll_type == "page")
|
|
|
|
scroll_step = d->scrollbarParameters_.page_step;
|
|
|
|
else
|
|
|
|
return;
|
|
|
|
string const scroll_quantity = cmd.getArg(1);
|
|
|
|
if (scroll_quantity == "up")
|
|
|
|
scrollUp(scroll_step);
|
|
|
|
else if (scroll_quantity == "down")
|
|
|
|
scrollDown(scroll_step);
|
|
|
|
else {
|
|
|
|
int const scroll_value = convert<int>(scroll_quantity);
|
|
|
|
if (scroll_value)
|
|
|
|
scroll(scroll_step * scroll_value);
|
|
|
|
}
|
2017-10-11 16:00:48 +00:00
|
|
|
dr.screenUpdate(Update::ForceDraw);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2007-12-27 11:37:07 +00:00
|
|
|
break;
|
2011-12-04 02:38:00 +00:00
|
|
|
}
|
2007-12-27 11:37:07 +00:00
|
|
|
|
2008-03-06 15:39:30 +00:00
|
|
|
case LFUN_SCREEN_UP_SELECT: {
|
2011-12-16 14:36:31 +00:00
|
|
|
// FIXME: why is the algorithm different from LFUN_SCREEN_UP?
|
2007-09-15 12:28:41 +00:00
|
|
|
cur.selHandle(true);
|
2008-03-06 15:39:30 +00:00
|
|
|
if (isTopScreen()) {
|
|
|
|
lyx::dispatch(FuncRequest(LFUN_BUFFER_BEGIN_SELECT));
|
|
|
|
cur.finishUndo();
|
|
|
|
break;
|
|
|
|
}
|
2010-04-21 13:03:04 +00:00
|
|
|
int y = getPos(cur).y_;
|
2008-03-06 15:39:30 +00:00
|
|
|
int const ymin = y - height_ + defaultRowHeight();
|
|
|
|
while (y > ymin && cur.up())
|
2010-04-21 13:03:04 +00:00
|
|
|
y = getPos(cur).y_;
|
2008-03-06 15:39:30 +00:00
|
|
|
|
2007-10-18 11:51:17 +00:00
|
|
|
cur.finishUndo();
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::SinglePar | Update::FitCursor);
|
2008-03-06 15:39:30 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
case LFUN_SCREEN_DOWN_SELECT: {
|
2011-12-16 14:36:31 +00:00
|
|
|
// FIXME: why is the algorithm different from LFUN_SCREEN_DOWN?
|
2008-03-06 15:39:30 +00:00
|
|
|
cur.selHandle(true);
|
|
|
|
if (isBottomScreen()) {
|
|
|
|
lyx::dispatch(FuncRequest(LFUN_BUFFER_END_SELECT));
|
|
|
|
cur.finishUndo();
|
|
|
|
break;
|
2007-09-15 12:28:41 +00:00
|
|
|
}
|
2010-04-21 13:03:04 +00:00
|
|
|
int y = getPos(cur).y_;
|
2008-03-06 15:39:30 +00:00
|
|
|
int const ymax = y + height_ - defaultRowHeight();
|
|
|
|
while (y < ymax && cur.down())
|
2010-04-21 13:03:04 +00:00
|
|
|
y = getPos(cur).y_;
|
2008-03-06 15:39:30 +00:00
|
|
|
|
|
|
|
cur.finishUndo();
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::SinglePar | Update::FitCursor);
|
2007-09-15 12:28:41 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-01-07 15:47:58 +00:00
|
|
|
|
2016-03-14 10:46:28 +00:00
|
|
|
case LFUN_INSET_SELECT_ALL: {
|
|
|
|
// true if all cells are selected
|
|
|
|
bool const all_selected = cur.depth() > 1
|
2014-10-19 18:43:17 +00:00
|
|
|
&& cur.selBegin().at_begin()
|
2016-03-14 10:46:28 +00:00
|
|
|
&& cur.selEnd().at_end();
|
|
|
|
// true if some cells are selected
|
|
|
|
bool const cells_selected = cur.depth() > 1
|
|
|
|
&& cur.selBegin().at_cell_begin()
|
|
|
|
&& cur.selEnd().at_cell_end();
|
|
|
|
if (all_selected || (cells_selected && !cur.inset().isTable())) {
|
|
|
|
// All the contents of the inset if selected, or only at
|
|
|
|
// least one cell but inset is not a table.
|
2014-10-19 18:43:17 +00:00
|
|
|
// Select the inset from outside.
|
|
|
|
cur.pop();
|
|
|
|
cur.resetAnchor();
|
2016-02-28 16:36:29 +00:00
|
|
|
cur.selection(true);
|
2014-10-19 18:43:17 +00:00
|
|
|
cur.posForward();
|
2016-03-14 10:46:28 +00:00
|
|
|
} else if (cells_selected) {
|
|
|
|
// At least one complete cell is selected and inset is a table.
|
2014-10-19 18:43:17 +00:00
|
|
|
// Select all cells
|
|
|
|
cur.idx() = 0;
|
2019-01-04 12:57:13 +00:00
|
|
|
cur.pit() = 0;
|
2014-11-14 09:10:29 +00:00
|
|
|
cur.pos() = 0;
|
2014-10-19 18:43:17 +00:00
|
|
|
cur.resetAnchor();
|
2016-02-28 16:36:29 +00:00
|
|
|
cur.selection(true);
|
2014-10-19 18:43:17 +00:00
|
|
|
cur.idx() = cur.lastidx();
|
2019-01-04 12:57:13 +00:00
|
|
|
cur.pit() = cur.lastpit();
|
2014-10-19 18:43:17 +00:00
|
|
|
cur.pos() = cur.lastpos();
|
|
|
|
} else {
|
|
|
|
// select current cell
|
2014-11-10 10:56:21 +00:00
|
|
|
cur.pit() = 0;
|
2014-11-14 09:10:29 +00:00
|
|
|
cur.pos() = 0;
|
2014-10-19 18:43:17 +00:00
|
|
|
cur.resetAnchor();
|
2016-02-28 16:36:29 +00:00
|
|
|
cur.selection(true);
|
2014-11-10 10:56:21 +00:00
|
|
|
cur.pit() = cur.lastpit();
|
2014-11-14 09:10:29 +00:00
|
|
|
cur.pos() = cur.lastpos();
|
2014-10-19 18:43:17 +00:00
|
|
|
}
|
2015-11-02 13:35:50 +00:00
|
|
|
cur.setCurrentFont();
|
2014-10-19 18:43:17 +00:00
|
|
|
dr.screenUpdate(Update::Force);
|
|
|
|
break;
|
2016-03-14 10:46:28 +00:00
|
|
|
}
|
2014-10-19 18:43:17 +00:00
|
|
|
|
|
|
|
|
2018-04-17 02:49:06 +00:00
|
|
|
case LFUN_UNICODE_INSERT: {
|
|
|
|
if (cmd.argument().empty())
|
|
|
|
break;
|
|
|
|
|
|
|
|
FuncCode code = cur.inset().currentMode() == Inset::MATH_MODE ?
|
|
|
|
LFUN_MATH_INSERT : LFUN_SELF_INSERT;
|
|
|
|
int i = 0;
|
|
|
|
while (true) {
|
|
|
|
docstring const arg = from_utf8(cmd.getArg(i));
|
|
|
|
if (arg.empty())
|
|
|
|
break;
|
|
|
|
if (!isHex(arg)) {
|
|
|
|
LYXERR0("Not a hexstring: " << arg);
|
|
|
|
++i;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
char_type c = hexToInt(arg);
|
|
|
|
if (c >= 32 && c < 0x10ffff) {
|
|
|
|
LYXERR(Debug::KEY, "Inserting c: " << c);
|
|
|
|
lyx::dispatch(FuncRequest(code, docstring(1, c)));
|
|
|
|
}
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-01-07 15:47:58 +00:00
|
|
|
// This would be in Buffer class if only Cursor did not
|
|
|
|
// require a bufferview
|
|
|
|
case LFUN_INSET_FORALL: {
|
|
|
|
docstring const name = from_utf8(cmd.getArg(0));
|
|
|
|
string const commandstr = cmd.getLongArg(1);
|
|
|
|
FuncRequest const fr = lyxaction.lookupFunc(commandstr);
|
|
|
|
|
|
|
|
// an arbitrary number to limit number of iterations
|
2011-12-15 16:39:35 +00:00
|
|
|
const int max_iter = 100000;
|
2010-01-07 15:47:58 +00:00
|
|
|
int iterations = 0;
|
2022-02-20 18:35:37 +00:00
|
|
|
Cursor & bvcur = d->cursor_;
|
|
|
|
Cursor const savecur = bvcur;
|
|
|
|
bvcur.reset();
|
|
|
|
if (!bvcur.nextInset())
|
|
|
|
bvcur.forwardInset();
|
|
|
|
bvcur.beginUndoGroup();
|
|
|
|
while(bvcur && iterations < max_iter) {
|
|
|
|
Inset * const ins = bvcur.nextInset();
|
2010-01-07 15:47:58 +00:00
|
|
|
if (!ins)
|
|
|
|
break;
|
2011-03-28 22:33:04 +00:00
|
|
|
docstring insname = ins->layoutName();
|
2010-01-07 15:47:58 +00:00
|
|
|
while (!insname.empty()) {
|
|
|
|
if (insname == name || name == from_utf8("*")) {
|
2010-04-08 12:39:41 +00:00
|
|
|
lyx::dispatch(fr, dr);
|
2022-02-20 18:07:58 +00:00
|
|
|
// we do not want to remember selection here
|
2022-02-20 18:35:37 +00:00
|
|
|
bvcur.clearSelection();
|
2010-01-07 15:47:58 +00:00
|
|
|
++iterations;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
size_t const i = insname.rfind(':');
|
|
|
|
if (i == string::npos)
|
|
|
|
break;
|
|
|
|
insname = insname.substr(0, i);
|
|
|
|
}
|
2013-01-23 22:02:05 +00:00
|
|
|
// if we did not delete the inset, skip it
|
2022-02-20 18:35:37 +00:00
|
|
|
if (!bvcur.nextInset() || bvcur.nextInset() == ins)
|
|
|
|
bvcur.forwardInset();
|
2010-01-07 15:47:58 +00:00
|
|
|
}
|
2022-02-20 18:35:37 +00:00
|
|
|
bvcur = savecur;
|
|
|
|
bvcur.fixIfBroken();
|
2016-04-27 19:16:34 +00:00
|
|
|
/** This is a dummy undo record only to remember the cursor
|
|
|
|
* that has just been set; this will be used on a redo action
|
|
|
|
* (see ticket #10097)
|
|
|
|
|
|
|
|
* FIXME: a better fix would be to have a way to set the
|
|
|
|
* cursor value directly, but I am not sure it is worth it.
|
|
|
|
*/
|
2022-02-20 18:35:37 +00:00
|
|
|
bvcur.recordUndo();
|
|
|
|
bvcur.endUndoGroup();
|
2010-10-13 17:28:55 +00:00
|
|
|
dr.screenUpdate(Update::Force);
|
2010-07-09 14:37:00 +00:00
|
|
|
dr.forceBufferUpdate();
|
2010-01-07 15:47:58 +00:00
|
|
|
|
2010-01-25 15:14:41 +00:00
|
|
|
if (iterations >= max_iter) {
|
|
|
|
dr.setError(true);
|
|
|
|
dr.setMessage(bformat(_("`inset-forall' interrupted because number of actions is larger than %1$d"), max_iter));
|
|
|
|
} else
|
|
|
|
dr.setMessage(bformat(_("Applied \"%1$s\" to %2$d insets"), from_utf8(commandstr), iterations));
|
2010-01-07 15:47:58 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-07-11 09:38:31 +00:00
|
|
|
case LFUN_BRANCH_ADD_INSERT: {
|
|
|
|
docstring branch_name = from_utf8(cmd.getArg(0));
|
|
|
|
if (branch_name.empty())
|
|
|
|
if (!Alert::askForText(branch_name, _("Branch name")) ||
|
|
|
|
branch_name.empty())
|
|
|
|
break;
|
|
|
|
|
|
|
|
DispatchResult drtmp;
|
|
|
|
buffer_.dispatch(FuncRequest(LFUN_BRANCH_ADD, branch_name), drtmp);
|
|
|
|
if (drtmp.error()) {
|
|
|
|
Alert::warning(_("Branch already exists"), drtmp.message());
|
|
|
|
break;
|
|
|
|
}
|
2018-01-08 14:15:29 +00:00
|
|
|
docstring const sep = buffer_.params().branchlist().separator();
|
|
|
|
for (docstring const & branch : getVectorFromString(branch_name, sep))
|
|
|
|
lyx::dispatch(FuncRequest(LFUN_BRANCH_INSERT, branch));
|
2009-07-11 09:38:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-09-19 16:59:36 +00:00
|
|
|
case LFUN_KEYMAP_OFF:
|
|
|
|
getIntl().keyMapOn(false);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_KEYMAP_PRIMARY:
|
|
|
|
getIntl().keyMapPrim();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_KEYMAP_SECONDARY:
|
|
|
|
getIntl().keyMapSec();
|
|
|
|
break;
|
|
|
|
|
|
|
|
case LFUN_KEYMAP_TOGGLE:
|
|
|
|
getIntl().toggleKeyMap();
|
|
|
|
break;
|
2009-07-11 09:38:31 +00:00
|
|
|
|
2009-09-26 17:19:18 +00:00
|
|
|
case LFUN_DIALOG_SHOW_NEW_INSET: {
|
|
|
|
string const name = cmd.getArg(0);
|
|
|
|
string data = trim(to_utf8(cmd.argument()).substr(name.size()));
|
|
|
|
if (decodeInsetParam(name, data, buffer_))
|
|
|
|
lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW, name + " " + data));
|
|
|
|
else
|
2014-07-25 20:35:08 +00:00
|
|
|
lyxerr << "Inset type '" << name <<
|
2009-09-26 17:19:18 +00:00
|
|
|
"' not recognized in LFUN_DIALOG_SHOW_NEW_INSET" << endl;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-09-26 17:30:29 +00:00
|
|
|
case LFUN_CITATION_INSERT: {
|
|
|
|
if (argument.empty()) {
|
|
|
|
lyx::dispatch(FuncRequest(LFUN_DIALOG_SHOW_NEW_INSET, "citation"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// we can have one optional argument, delimited by '|'
|
|
|
|
// citation-insert <key>|<text_before>
|
|
|
|
// this should be enhanced to also support text_after
|
|
|
|
// and citation style
|
|
|
|
string arg = argument;
|
|
|
|
string opt1;
|
|
|
|
if (contains(argument, "|")) {
|
|
|
|
arg = token(argument, '|', 0);
|
|
|
|
opt1 = token(argument, '|', 1);
|
|
|
|
}
|
2012-06-05 08:37:34 +00:00
|
|
|
|
2013-02-18 14:44:27 +00:00
|
|
|
// if our cursor is directly in front of or behind a citation inset,
|
2012-06-05 08:37:34 +00:00
|
|
|
// we will instead add the new key to it.
|
|
|
|
Inset * inset = cur.nextInset();
|
|
|
|
if (!inset || inset->lyxCode() != CITE_CODE)
|
|
|
|
inset = cur.prevInset();
|
2012-06-05 09:06:38 +00:00
|
|
|
if (inset && inset->lyxCode() == CITE_CODE) {
|
2012-06-05 08:37:34 +00:00
|
|
|
InsetCitation * icite = static_cast<InsetCitation *>(inset);
|
|
|
|
if (icite->addKey(arg)) {
|
|
|
|
dr.forceBufferUpdate();
|
|
|
|
dr.screenUpdate(Update::FitCursor | Update::SinglePar);
|
|
|
|
if (!opt1.empty())
|
|
|
|
LYXERR0("Discarding optional argument to citation-insert.");
|
|
|
|
}
|
|
|
|
dispatched = true;
|
|
|
|
break;
|
|
|
|
}
|
2009-09-26 17:30:29 +00:00
|
|
|
InsetCommandParams icp(CITE_CODE);
|
|
|
|
icp["key"] = from_utf8(arg);
|
|
|
|
if (!opt1.empty())
|
|
|
|
icp["before"] = from_utf8(opt1);
|
2018-02-22 20:24:28 +00:00
|
|
|
icp["literal"] =
|
|
|
|
from_ascii(InsetCitation::last_literal ? "true" : "false");
|
2010-10-29 00:25:28 +00:00
|
|
|
string icstr = InsetCommand::params2string(icp);
|
2009-09-26 17:30:29 +00:00
|
|
|
FuncRequest fr(LFUN_INSET_INSERT, icstr);
|
|
|
|
lyx::dispatch(fr);
|
2022-12-18 12:25:24 +00:00
|
|
|
|
|
|
|
// if the request comes from the LyX server, then we
|
|
|
|
// return a list of the undefined keys, in case some
|
|
|
|
// action could be taken.
|
|
|
|
if (cmd.origin() != FuncRequest::LYXSERVER)
|
|
|
|
break;
|
|
|
|
|
|
|
|
vector<docstring> keys = getVectorFromString(from_utf8(arg));
|
|
|
|
vector<docstring>::iterator it = keys.begin();
|
|
|
|
vector<docstring>::const_iterator end = keys.end();
|
|
|
|
|
|
|
|
BiblioInfo const & bibInfo = buffer_.masterBibInfo();
|
|
|
|
const BiblioInfo::const_iterator bibEnd = bibInfo.end();
|
|
|
|
while (it != end) {
|
|
|
|
if (bibInfo.find(*it) != bibEnd) {
|
|
|
|
it = keys.erase(it);
|
|
|
|
end = keys.end();
|
|
|
|
} else
|
|
|
|
++it;
|
|
|
|
}
|
|
|
|
dr.setMessage(getStringFromVector(keys));
|
|
|
|
|
2009-09-26 17:30:29 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2009-09-27 10:29:13 +00:00
|
|
|
case LFUN_INSET_APPLY: {
|
|
|
|
string const name = cmd.getArg(0);
|
|
|
|
Inset * inset = editedInset(name);
|
|
|
|
if (!inset) {
|
|
|
|
FuncRequest fr(LFUN_INSET_INSERT, cmd.argument());
|
|
|
|
lyx::dispatch(fr);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
// put cursor in front of inset.
|
2009-09-30 13:50:07 +00:00
|
|
|
if (!setCursorFromInset(inset)) {
|
2009-09-27 10:29:13 +00:00
|
|
|
LASSERT(false, break);
|
2009-09-30 13:50:07 +00:00
|
|
|
}
|
2009-09-27 10:29:13 +00:00
|
|
|
cur.recordUndo();
|
|
|
|
FuncRequest fr(LFUN_INSET_MODIFY, cmd.argument());
|
|
|
|
inset->dispatch(cur, fr);
|
2010-10-13 18:06:01 +00:00
|
|
|
dr.screenUpdate(cur.result().screenUpdate());
|
|
|
|
if (cur.result().needBufferUpdate())
|
|
|
|
dr.forceBufferUpdate();
|
2009-09-27 10:29:13 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-08-23 10:22:14 +00:00
|
|
|
// FIXME:
|
|
|
|
// The change of language of buffer belongs to the Buffer class.
|
|
|
|
// We have to do it here because we need a cursor for Undo.
|
|
|
|
// When Undo::recordUndoBufferParams() is implemented someday
|
|
|
|
// LFUN_BUFFER_LANGUAGE should be handled by the Buffer class.
|
|
|
|
case LFUN_BUFFER_LANGUAGE: {
|
|
|
|
Language const * oldL = buffer_.params().language;
|
|
|
|
Language const * newL = languages.getLanguage(argument);
|
|
|
|
if (!newL || oldL == newL)
|
|
|
|
break;
|
2011-09-18 21:18:57 +00:00
|
|
|
if (oldL->rightToLeft() == newL->rightToLeft()) {
|
2015-01-17 19:38:22 +00:00
|
|
|
cur.recordUndoFullBuffer();
|
2011-08-23 10:22:14 +00:00
|
|
|
buffer_.changeLanguage(oldL, newL);
|
2011-08-29 17:25:44 +00:00
|
|
|
cur.setCurrentFont();
|
2011-08-23 10:22:14 +00:00
|
|
|
dr.forceBufferUpdate();
|
|
|
|
}
|
2012-04-15 18:28:09 +00:00
|
|
|
break;
|
2011-12-05 13:12:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
case LFUN_FILE_INSERT_PLAINTEXT_PARA:
|
|
|
|
case LFUN_FILE_INSERT_PLAINTEXT: {
|
|
|
|
bool const as_paragraph = (act == LFUN_FILE_INSERT_PLAINTEXT_PARA);
|
|
|
|
string const fname = to_utf8(cmd.argument());
|
|
|
|
if (!FileName::isAbsolute(fname))
|
|
|
|
dr.setMessage(_("Absolute filename expected."));
|
|
|
|
else
|
|
|
|
insertPlaintextFile(FileName(fname), as_paragraph);
|
2011-08-23 10:22:14 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2021-01-08 18:27:19 +00:00
|
|
|
case LFUN_COPY:
|
2021-03-12 16:41:43 +00:00
|
|
|
// With multi-cell table content, we pass down to the inset
|
|
|
|
if (cur.inTexted() && cur.selection()
|
|
|
|
&& cur.selectionBegin().idx() != cur.selectionEnd().idx()) {
|
|
|
|
buffer_.dispatch(cmd, dr);
|
|
|
|
dispatched = dr.dispatched();
|
|
|
|
break;
|
|
|
|
}
|
2021-01-08 18:27:19 +00:00
|
|
|
cap::copySelection(cur);
|
|
|
|
cur.message(_("Copy"));
|
|
|
|
break;
|
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
default:
|
2010-12-05 01:18:32 +00:00
|
|
|
// OK, so try the Buffer itself...
|
|
|
|
buffer_.dispatch(cmd, dr);
|
|
|
|
dispatched = dr.dispatched();
|
2009-10-03 19:12:21 +00:00
|
|
|
break;
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
|
2009-11-06 13:32:58 +00:00
|
|
|
buffer_.undo().endUndoGroup();
|
2010-01-25 15:14:41 +00:00
|
|
|
dr.dispatched(dispatched);
|
2015-06-05 09:43:05 +00:00
|
|
|
|
|
|
|
// NOTE: The code below is copied from Cursor::dispatch. If you
|
|
|
|
// need to modify this, please update the other one too.
|
|
|
|
|
|
|
|
// notify insets we just entered/left
|
|
|
|
if (cursor() != old) {
|
|
|
|
old.beginUndoGroup();
|
|
|
|
old.fixIfBroken();
|
|
|
|
bool badcursor = notifyCursorLeavesOrEnters(old, cursor());
|
|
|
|
if (badcursor) {
|
|
|
|
cursor().fixIfBroken();
|
|
|
|
resetInlineCompletionPos();
|
|
|
|
}
|
|
|
|
old.endUndoGroup();
|
|
|
|
}
|
2001-02-23 16:10:03 +00:00
|
|
|
}
|
2002-08-28 10:45:38 +00:00
|
|
|
|
|
|
|
|
2020-02-28 06:39:06 +00:00
|
|
|
docstring BufferView::requestSelection()
|
* frontends/WorkArea.h(workAreaResize, scrollDocView, workAreaKeyPress,
dispatch, selectionReuqested, selectionLost): remove signals
* BufferView.[Ch] (selectionRequested, selectionLost, workAreaResize,
workAreaKeyPress, workAreaDispatch): add forwarding functions to the pimpl
* BufferView_pimpl.C (Pimpl): Remove the setup of the now deleted signals,
also remove corresponding connection objects.
* frontends/xforms/XWorkArea.h:
* frontends/qt2/QWorkArea.h:
* frontends/qt4/QWorkArea.h:
* frontends/gtk/GWorkArea.h: add LyXView as class variable view_ (qt
classes also get view() medthod)
* frontends/qt2/QWorkArea.C:
* frontends/qt2/QContentPane.C:
* frontends/qt4/QWorkArea.C:
* frontends/xforms/XWorkArea.C:
* frontends/gtk/GWorkArea.C: Change from calling signals to call
the functions directly through view_.view()
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@13671 a592a061-630c-0410-9148-cb99ea01b6c8
2006-04-13 18:58:48 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
Cursor & cur = d->cursor_;
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2008-07-20 20:16:33 +00:00
|
|
|
LYXERR(Debug::SELECTION, "requestSelection: cur.selection: " << cur.selection());
|
2006-09-16 10:08:51 +00:00
|
|
|
if (!cur.selection()) {
|
2007-10-13 07:33:07 +00:00
|
|
|
d->xsel_cache_.set = false;
|
2006-09-17 08:35:12 +00:00
|
|
|
return docstring();
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
|
2008-07-15 00:33:37 +00:00
|
|
|
LYXERR(Debug::SELECTION, "requestSelection: xsel_cache.set: " << d->xsel_cache_.set);
|
2007-10-13 07:33:07 +00:00
|
|
|
if (!d->xsel_cache_.set ||
|
|
|
|
cur.top() != d->xsel_cache_.cursor ||
|
2010-04-15 18:16:07 +00:00
|
|
|
cur.realAnchor().top() != d->xsel_cache_.anchor)
|
2006-09-16 10:08:51 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
d->xsel_cache_.cursor = cur.top();
|
2010-04-15 18:16:07 +00:00
|
|
|
d->xsel_cache_.anchor = cur.realAnchor().top();
|
2007-10-13 07:33:07 +00:00
|
|
|
d->xsel_cache_.set = cur.selection();
|
2006-09-17 08:35:12 +00:00
|
|
|
return cur.selectionAsString(false);
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
2006-09-17 08:35:12 +00:00
|
|
|
return docstring();
|
* frontends/WorkArea.h(workAreaResize, scrollDocView, workAreaKeyPress,
dispatch, selectionReuqested, selectionLost): remove signals
* BufferView.[Ch] (selectionRequested, selectionLost, workAreaResize,
workAreaKeyPress, workAreaDispatch): add forwarding functions to the pimpl
* BufferView_pimpl.C (Pimpl): Remove the setup of the now deleted signals,
also remove corresponding connection objects.
* frontends/xforms/XWorkArea.h:
* frontends/qt2/QWorkArea.h:
* frontends/qt4/QWorkArea.h:
* frontends/gtk/GWorkArea.h: add LyXView as class variable view_ (qt
classes also get view() medthod)
* frontends/qt2/QWorkArea.C:
* frontends/qt2/QContentPane.C:
* frontends/qt4/QWorkArea.C:
* frontends/xforms/XWorkArea.C:
* frontends/gtk/GWorkArea.C: Change from calling signals to call
the functions directly through view_.view()
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@13671 a592a061-630c-0410-9148-cb99ea01b6c8
2006-04-13 18:58:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-09-17 08:35:12 +00:00
|
|
|
void BufferView::clearSelection()
|
* frontends/WorkArea.h(workAreaResize, scrollDocView, workAreaKeyPress,
dispatch, selectionReuqested, selectionLost): remove signals
* BufferView.[Ch] (selectionRequested, selectionLost, workAreaResize,
workAreaKeyPress, workAreaDispatch): add forwarding functions to the pimpl
* BufferView_pimpl.C (Pimpl): Remove the setup of the now deleted signals,
also remove corresponding connection objects.
* frontends/xforms/XWorkArea.h:
* frontends/qt2/QWorkArea.h:
* frontends/qt4/QWorkArea.h:
* frontends/gtk/GWorkArea.h: add LyXView as class variable view_ (qt
classes also get view() medthod)
* frontends/qt2/QWorkArea.C:
* frontends/qt2/QContentPane.C:
* frontends/qt4/QWorkArea.C:
* frontends/xforms/XWorkArea.C:
* frontends/gtk/GWorkArea.C: Change from calling signals to call
the functions directly through view_.view()
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@13671 a592a061-630c-0410-9148-cb99ea01b6c8
2006-04-13 18:58:48 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.clearSelection();
|
2007-08-21 07:33:46 +00:00
|
|
|
// Clear the selection buffer. Otherwise a subsequent
|
|
|
|
// middle-mouse-button paste would use the selection buffer,
|
|
|
|
// not the more current external selection.
|
|
|
|
cap::clearSelection();
|
2007-10-13 07:33:07 +00:00
|
|
|
d->xsel_cache_.set = false;
|
2007-08-21 07:33:46 +00:00
|
|
|
// The buffer did not really change, but this causes the
|
|
|
|
// redraw we need because we cleared the selection above.
|
2010-01-08 02:03:54 +00:00
|
|
|
buffer_.changed(false);
|
* frontends/WorkArea.h(workAreaResize, scrollDocView, workAreaKeyPress,
dispatch, selectionReuqested, selectionLost): remove signals
* BufferView.[Ch] (selectionRequested, selectionLost, workAreaResize,
workAreaKeyPress, workAreaDispatch): add forwarding functions to the pimpl
* BufferView_pimpl.C (Pimpl): Remove the setup of the now deleted signals,
also remove corresponding connection objects.
* frontends/xforms/XWorkArea.h:
* frontends/qt2/QWorkArea.h:
* frontends/qt4/QWorkArea.h:
* frontends/gtk/GWorkArea.h: add LyXView as class variable view_ (qt
classes also get view() medthod)
* frontends/qt2/QWorkArea.C:
* frontends/qt2/QContentPane.C:
* frontends/qt4/QWorkArea.C:
* frontends/xforms/XWorkArea.C:
* frontends/gtk/GWorkArea.C: Change from calling signals to call
the functions directly through view_.view()
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@13671 a592a061-630c-0410-9148-cb99ea01b6c8
2006-04-13 18:58:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-08-24 07:05:19 +00:00
|
|
|
void BufferView::resize(int width, int height)
|
* frontends/WorkArea.h(workAreaResize, scrollDocView, workAreaKeyPress,
dispatch, selectionReuqested, selectionLost): remove signals
* BufferView.[Ch] (selectionRequested, selectionLost, workAreaResize,
workAreaKeyPress, workAreaDispatch): add forwarding functions to the pimpl
* BufferView_pimpl.C (Pimpl): Remove the setup of the now deleted signals,
also remove corresponding connection objects.
* frontends/xforms/XWorkArea.h:
* frontends/qt2/QWorkArea.h:
* frontends/qt4/QWorkArea.h:
* frontends/gtk/GWorkArea.h: add LyXView as class variable view_ (qt
classes also get view() medthod)
* frontends/qt2/QWorkArea.C:
* frontends/qt2/QContentPane.C:
* frontends/qt4/QWorkArea.C:
* frontends/xforms/XWorkArea.C:
* frontends/gtk/GWorkArea.C: Change from calling signals to call
the functions directly through view_.view()
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@13671 a592a061-630c-0410-9148-cb99ea01b6c8
2006-04-13 18:58:48 +00:00
|
|
|
{
|
2006-09-16 10:08:51 +00:00
|
|
|
// Update from work area
|
|
|
|
width_ = width;
|
|
|
|
height_ = height;
|
|
|
|
|
2007-12-21 08:28:18 +00:00
|
|
|
// Clear the paragraph height cache.
|
|
|
|
d->par_height_.clear();
|
2008-02-20 14:53:19 +00:00
|
|
|
// Redo the metrics.
|
2007-11-06 08:32:25 +00:00
|
|
|
updateMetrics();
|
* frontends/WorkArea.h(workAreaResize, scrollDocView, workAreaKeyPress,
dispatch, selectionReuqested, selectionLost): remove signals
* BufferView.[Ch] (selectionRequested, selectionLost, workAreaResize,
workAreaKeyPress, workAreaDispatch): add forwarding functions to the pimpl
* BufferView_pimpl.C (Pimpl): Remove the setup of the now deleted signals,
also remove corresponding connection objects.
* frontends/xforms/XWorkArea.h:
* frontends/qt2/QWorkArea.h:
* frontends/qt4/QWorkArea.h:
* frontends/gtk/GWorkArea.h: add LyXView as class variable view_ (qt
classes also get view() medthod)
* frontends/qt2/QWorkArea.C:
* frontends/qt2/QContentPane.C:
* frontends/qt4/QWorkArea.C:
* frontends/xforms/XWorkArea.C:
* frontends/gtk/GWorkArea.C: Change from calling signals to call
the functions directly through view_.view()
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@13671 a592a061-630c-0410-9148-cb99ea01b6c8
2006-04-13 18:58:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-12-25 18:53:38 +00:00
|
|
|
Inset const * BufferView::getCoveringInset(Text const & text,
|
|
|
|
int x, int y) const
|
2007-03-28 14:47:52 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
TextMetrics & tm = d->text_metrics_[&text];
|
2007-09-21 20:39:47 +00:00
|
|
|
Inset * inset = tm.checkInsetHit(x, y);
|
|
|
|
if (!inset)
|
2020-02-28 06:21:02 +00:00
|
|
|
return nullptr;
|
2007-09-21 20:39:47 +00:00
|
|
|
|
2009-10-24 00:02:41 +00:00
|
|
|
if (!inset->descendable(*this))
|
2007-09-21 20:39:47 +00:00
|
|
|
// No need to go further down if the inset is not
|
|
|
|
// descendable.
|
|
|
|
return inset;
|
|
|
|
|
|
|
|
size_t cell_number = inset->nargs();
|
|
|
|
// Check all the inner cell.
|
|
|
|
for (size_t i = 0; i != cell_number; ++i) {
|
|
|
|
Text const * inner_text = inset->getText(i);
|
|
|
|
if (inner_text) {
|
|
|
|
// Try deeper.
|
|
|
|
Inset const * inset_deeper =
|
|
|
|
getCoveringInset(*inner_text, x, y);
|
|
|
|
if (inset_deeper)
|
|
|
|
return inset_deeper;
|
2007-03-28 14:47:52 +00:00
|
|
|
}
|
|
|
|
}
|
2007-09-21 20:39:47 +00:00
|
|
|
|
|
|
|
return inset;
|
2007-03-28 14:47:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-04-01 14:59:55 +00:00
|
|
|
Inset const * BufferView::clickableMathInset(InsetMathNest const * inset,
|
|
|
|
CoordCache::Insets const & inset_cache, int x, int y) const
|
|
|
|
{
|
|
|
|
for (size_t i = 0; i < inset->nargs(); ++i) {
|
|
|
|
MathData const & ar = inset->cell(i);
|
|
|
|
for (size_t j = 0; j < ar.size(); ++j) {
|
|
|
|
string const name = lyxerr.debugging(Debug::MATHED)
|
|
|
|
? insetName(ar[j].nucleus()->lyxCode())
|
|
|
|
: string();
|
|
|
|
LYXERR(Debug::MATHED, "Checking inset: " << name);
|
|
|
|
if (ar[j].nucleus()->clickable(*this, x, y)) {
|
|
|
|
if (inset_cache.covers(ar[j].nucleus(), x, y)) {
|
|
|
|
LYXERR(Debug::MATHED, "Clickable inset: "
|
|
|
|
<< name);
|
|
|
|
return ar[j].nucleus();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
InsetMathNest const * imn =
|
|
|
|
ar[j].nucleus()->asNestInset();
|
|
|
|
if (imn) {
|
|
|
|
Inset const * inner =
|
|
|
|
clickableMathInset(imn, inset_cache, x, y);
|
|
|
|
if (inner)
|
|
|
|
return inner;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-29 15:21:10 +00:00
|
|
|
void BufferView::updateHoveredInset() const
|
|
|
|
{
|
|
|
|
// Get inset under mouse, if there is one.
|
2010-10-24 21:41:47 +00:00
|
|
|
int const x = d->mouse_position_cache_.x_;
|
|
|
|
int const y = d->mouse_position_cache_.y_;
|
|
|
|
Inset const * covering_inset = getCoveringInset(buffer_.text(), x, y);
|
2021-04-01 14:59:55 +00:00
|
|
|
if (covering_inset && covering_inset->asInsetMath()) {
|
|
|
|
Inset const * inner_inset = clickableMathInset(
|
|
|
|
covering_inset->asInsetMath()->asNestInset(),
|
|
|
|
coordCache().getInsets(), x, y);
|
|
|
|
if (inner_inset)
|
|
|
|
covering_inset = inner_inset;
|
|
|
|
}
|
2010-10-24 21:41:47 +00:00
|
|
|
|
2016-02-02 16:17:10 +00:00
|
|
|
d->clickable_inset_ = covering_inset && covering_inset->clickable(*this, x, y);
|
2010-10-24 21:41:47 +00:00
|
|
|
|
2010-03-29 15:21:10 +00:00
|
|
|
if (covering_inset == d->last_inset_)
|
|
|
|
// Same inset, no need to do anything...
|
|
|
|
return;
|
|
|
|
|
|
|
|
bool need_redraw = false;
|
2010-05-02 19:12:25 +00:00
|
|
|
if (d->last_inset_) {
|
2010-03-29 15:21:10 +00:00
|
|
|
// Remove the hint on the last hovered inset (if any).
|
2010-04-30 14:47:46 +00:00
|
|
|
need_redraw |= d->last_inset_->setMouseHover(this, false);
|
2020-02-28 06:21:02 +00:00
|
|
|
d->last_inset_ = nullptr;
|
2010-05-02 19:12:25 +00:00
|
|
|
}
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2011-02-13 10:07:00 +00:00
|
|
|
if (covering_inset && covering_inset->setMouseHover(this, true)) {
|
2010-05-02 19:12:25 +00:00
|
|
|
need_redraw = true;
|
2014-07-25 20:35:08 +00:00
|
|
|
// Only the insets that accept the hover state, do
|
2010-05-02 19:12:25 +00:00
|
|
|
// clear the last_inset_, so only set the last_inset_
|
|
|
|
// member if the hovered setting is accepted.
|
2011-02-13 10:07:00 +00:00
|
|
|
d->last_inset_ = covering_inset;
|
2010-05-02 19:12:25 +00:00
|
|
|
}
|
2010-03-29 15:21:10 +00:00
|
|
|
|
|
|
|
if (need_redraw) {
|
|
|
|
LYXERR(Debug::PAINTING, "Mouse hover detected at: ("
|
2014-07-25 20:35:08 +00:00
|
|
|
<< d->mouse_position_cache_.x_ << ", "
|
2010-03-29 15:21:10 +00:00
|
|
|
<< d->mouse_position_cache_.y_ << ")");
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2010-03-29 15:21:10 +00:00
|
|
|
d->update_strategy_ = DecorationUpdate;
|
|
|
|
|
|
|
|
// This event (moving without mouse click) is not passed further.
|
|
|
|
// This should be changed if it is further utilized.
|
2016-09-25 18:31:13 +00:00
|
|
|
buffer_.changed(false);
|
2010-03-29 15:21:10 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-30 14:55:37 +00:00
|
|
|
void BufferView::clearLastInset(Inset * inset) const
|
|
|
|
{
|
|
|
|
if (d->last_inset_ != inset) {
|
|
|
|
LYXERR0("Wrong last_inset!");
|
2013-04-25 21:27:10 +00:00
|
|
|
LATTEST(false);
|
2010-04-30 14:55:37 +00:00
|
|
|
}
|
2020-02-28 06:21:02 +00:00
|
|
|
d->last_inset_ = nullptr;
|
2010-04-30 14:55:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-22 23:57:56 +00:00
|
|
|
bool BufferView::mouseSelecting() const
|
|
|
|
{
|
|
|
|
return d->mouse_selecting_;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-10-05 09:38:40 +00:00
|
|
|
void BufferView::mouseEventDispatch(FuncRequest const & cmd0)
|
* frontends/WorkArea.h(workAreaResize, scrollDocView, workAreaKeyPress,
dispatch, selectionReuqested, selectionLost): remove signals
* BufferView.[Ch] (selectionRequested, selectionLost, workAreaResize,
workAreaKeyPress, workAreaDispatch): add forwarding functions to the pimpl
* BufferView_pimpl.C (Pimpl): Remove the setup of the now deleted signals,
also remove corresponding connection objects.
* frontends/xforms/XWorkArea.h:
* frontends/qt2/QWorkArea.h:
* frontends/qt4/QWorkArea.h:
* frontends/gtk/GWorkArea.h: add LyXView as class variable view_ (qt
classes also get view() medthod)
* frontends/qt2/QWorkArea.C:
* frontends/qt2/QContentPane.C:
* frontends/qt4/QWorkArea.C:
* frontends/xforms/XWorkArea.C:
* frontends/gtk/GWorkArea.C: Change from calling signals to call
the functions directly through view_.view()
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@13671 a592a061-630c-0410-9148-cb99ea01b6c8
2006-04-13 18:58:48 +00:00
|
|
|
{
|
2007-11-30 08:35:54 +00:00
|
|
|
//lyxerr << "[ cmd0 " << cmd0 << "]" << endl;
|
2006-09-16 10:08:51 +00:00
|
|
|
|
|
|
|
// This is only called for mouse related events including
|
|
|
|
// LFUN_FILE_OPEN generated by drag-and-drop.
|
|
|
|
FuncRequest cmd = cmd0;
|
|
|
|
|
2008-02-22 20:45:18 +00:00
|
|
|
Cursor old = cursor();
|
2007-04-26 14:56:30 +00:00
|
|
|
Cursor cur(*this);
|
2007-08-21 07:33:46 +00:00
|
|
|
cur.push(buffer_.inset());
|
2016-02-28 16:36:29 +00:00
|
|
|
cur.selection(d->cursor_.selection());
|
2006-09-16 10:08:51 +00:00
|
|
|
|
|
|
|
// Either the inset under the cursor or the
|
2007-04-29 23:33:02 +00:00
|
|
|
// surrounding Text will handle this event.
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2007-03-27 10:07:53 +00:00
|
|
|
// make sure we stay within the screen...
|
2010-04-09 19:00:42 +00:00
|
|
|
cmd.set_y(min(max(cmd.y(), -1), height_));
|
2007-05-28 22:27:45 +00:00
|
|
|
|
2010-04-09 19:00:42 +00:00
|
|
|
d->mouse_position_cache_.x_ = cmd.x();
|
|
|
|
d->mouse_position_cache_.y_ = cmd.y();
|
2006-12-07 11:46:27 +00:00
|
|
|
|
2022-07-22 23:57:56 +00:00
|
|
|
d->mouse_selecting_ =
|
|
|
|
cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::button1;
|
|
|
|
|
2010-04-09 19:00:42 +00:00
|
|
|
if (cmd.action() == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) {
|
2010-03-29 15:21:10 +00:00
|
|
|
updateHoveredInset();
|
2007-10-05 09:38:40 +00:00
|
|
|
return;
|
2006-12-04 04:31:18 +00:00
|
|
|
}
|
2007-05-28 22:27:45 +00:00
|
|
|
|
2007-03-27 10:07:53 +00:00
|
|
|
// Build temporary cursor.
|
2010-04-09 19:00:42 +00:00
|
|
|
Inset * inset = d->text_metrics_[&buffer_.text()].editXY(cur, cmd.x(), cmd.y());
|
2015-10-17 16:13:18 +00:00
|
|
|
if (inset) {
|
|
|
|
// If inset is not editable, cur.pos() might point behind the
|
|
|
|
// inset (depending on cmd.x(), cmd.y()). This is needed for
|
|
|
|
// editing to fix bug 9628, but e.g. the context menu needs a
|
|
|
|
// cursor in front of the inset.
|
2016-04-21 21:44:14 +00:00
|
|
|
if ((inset->hasSettings() || !inset->contextMenuName().empty()
|
|
|
|
|| inset->lyxCode() == SEPARATOR_CODE) &&
|
2015-10-17 16:13:18 +00:00
|
|
|
cur.nextInset() != inset && cur.prevInset() == inset)
|
2015-12-10 19:21:20 +00:00
|
|
|
cur.posBackward();
|
2016-04-21 21:44:14 +00:00
|
|
|
} else if (cur.inTexted() && cur.pos()
|
|
|
|
&& cur.paragraph().isEnvSeparator(cur.pos() - 1)) {
|
|
|
|
// Always place cursor in front of a separator inset.
|
|
|
|
cur.posBackward();
|
2015-10-17 16:13:18 +00:00
|
|
|
}
|
2006-12-04 04:31:18 +00:00
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
// Put anchor at the same position.
|
|
|
|
cur.resetAnchor();
|
|
|
|
|
2023-08-29 15:23:22 +00:00
|
|
|
old.beginUndoGroup();
|
2008-09-18 13:44:54 +00:00
|
|
|
|
2006-09-16 10:08:51 +00:00
|
|
|
// Try to dispatch to an non-editable inset near this position
|
|
|
|
// via the temp cursor. If the inset wishes to change the real
|
|
|
|
// cursor it has to do so explicitly by using
|
|
|
|
// cur.bv().cursor() = cur; (or similar)
|
2007-10-05 09:38:40 +00:00
|
|
|
if (inset)
|
2006-09-16 10:08:51 +00:00
|
|
|
inset->dispatch(cur, cmd);
|
|
|
|
|
|
|
|
// Now dispatch to the temporary cursor. If the real cursor should
|
|
|
|
// be modified, the inset's dispatch has to do so explicitly.
|
2008-03-24 18:22:54 +00:00
|
|
|
if (!inset || !cur.result().dispatched())
|
2006-09-16 10:08:51 +00:00
|
|
|
cur.dispatch(cmd);
|
|
|
|
|
2008-02-22 20:45:18 +00:00
|
|
|
// Notify left insets
|
|
|
|
if (cur != old) {
|
2022-04-29 01:06:37 +00:00
|
|
|
bool badcursor = old.fixIfBroken() || cur.fixIfBroken();
|
2022-04-29 17:56:12 +00:00
|
|
|
badcursor = badcursor || notifyCursorLeavesOrEnters(old, cur);
|
2008-02-22 20:45:18 +00:00
|
|
|
if (badcursor)
|
|
|
|
cursor().fixIfBroken();
|
|
|
|
}
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2023-08-29 15:23:22 +00:00
|
|
|
old.endUndoGroup();
|
2016-03-16 09:27:32 +00:00
|
|
|
|
2008-02-22 20:45:18 +00:00
|
|
|
// Do we have a selection?
|
2007-08-10 10:21:09 +00:00
|
|
|
theSelection().haveSelection(cursor().selection());
|
|
|
|
|
2021-01-28 09:10:18 +00:00
|
|
|
if (cur.needBufferUpdate() || buffer().needUpdate()) {
|
2011-05-09 18:05:31 +00:00
|
|
|
cur.clearBufferUpdate();
|
|
|
|
buffer().updateBuffer();
|
|
|
|
}
|
|
|
|
|
2007-10-05 09:38:40 +00:00
|
|
|
// If the command has been dispatched,
|
2010-10-13 17:28:55 +00:00
|
|
|
if (cur.result().dispatched() || cur.result().screenUpdate())
|
|
|
|
processUpdateFlags(cur.result().screenUpdate());
|
* frontends/WorkArea.h(workAreaResize, scrollDocView, workAreaKeyPress,
dispatch, selectionReuqested, selectionLost): remove signals
* BufferView.[Ch] (selectionRequested, selectionLost, workAreaResize,
workAreaKeyPress, workAreaDispatch): add forwarding functions to the pimpl
* BufferView_pimpl.C (Pimpl): Remove the setup of the now deleted signals,
also remove corresponding connection objects.
* frontends/xforms/XWorkArea.h:
* frontends/qt2/QWorkArea.h:
* frontends/qt4/QWorkArea.h:
* frontends/gtk/GWorkArea.h: add LyXView as class variable view_ (qt
classes also get view() medthod)
* frontends/qt2/QWorkArea.C:
* frontends/qt2/QContentPane.C:
* frontends/qt4/QWorkArea.C:
* frontends/xforms/XWorkArea.C:
* frontends/gtk/GWorkArea.C: Change from calling signals to call
the functions directly through view_.view()
git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@13671 a592a061-630c-0410-9148-cb99ea01b6c8
2006-04-13 18:58:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-03-27 17:41:58 +00:00
|
|
|
int BufferView::minVisiblePart()
|
|
|
|
{
|
|
|
|
return 2 * defaultRowHeight();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-31 17:18:51 +00:00
|
|
|
int BufferView::scroll(int pixels)
|
2002-08-28 10:45:38 +00:00
|
|
|
{
|
2020-10-31 17:18:51 +00:00
|
|
|
if (pixels > 0)
|
|
|
|
return scrollDown(pixels);
|
|
|
|
if (pixels < 0)
|
|
|
|
return scrollUp(-pixels);
|
2008-07-04 11:33:28 +00:00
|
|
|
return 0;
|
2007-09-15 12:28:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-31 17:18:51 +00:00
|
|
|
int BufferView::scrollDown(int pixels)
|
2007-09-15 12:28:41 +00:00
|
|
|
{
|
|
|
|
Text * text = &buffer_.text();
|
2007-10-13 07:33:07 +00:00
|
|
|
TextMetrics & tm = d->text_metrics_[text];
|
2020-10-31 17:18:51 +00:00
|
|
|
int const ymax = height_ + pixels;
|
2007-09-15 12:28:41 +00:00
|
|
|
while (true) {
|
2020-07-12 22:00:36 +00:00
|
|
|
pair<pit_type, ParagraphMetrics const *> last = tm.last();
|
|
|
|
int bottom_pos = last.second->position() + last.second->descent();
|
2009-03-27 17:41:58 +00:00
|
|
|
if (lyxrc.scroll_below_document)
|
|
|
|
bottom_pos += height_ - minVisiblePart();
|
2020-07-12 22:00:36 +00:00
|
|
|
if (last.first + 1 == int(text->paragraphs().size())) {
|
2007-09-15 12:28:41 +00:00
|
|
|
if (bottom_pos <= height_)
|
2008-07-04 11:33:28 +00:00
|
|
|
return 0;
|
2020-10-31 17:18:51 +00:00
|
|
|
pixels = min(pixels, bottom_pos - height_);
|
2007-09-15 12:28:41 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (bottom_pos > ymax)
|
|
|
|
break;
|
|
|
|
tm.newParMetricsDown();
|
|
|
|
}
|
2020-10-31 17:18:51 +00:00
|
|
|
d->anchor_ypos_ -= pixels;
|
|
|
|
return -pixels;
|
2007-09-15 12:28:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-31 17:18:51 +00:00
|
|
|
int BufferView::scrollUp(int pixels)
|
2007-09-15 12:28:41 +00:00
|
|
|
{
|
|
|
|
Text * text = &buffer_.text();
|
2007-10-13 07:33:07 +00:00
|
|
|
TextMetrics & tm = d->text_metrics_[text];
|
2020-10-31 17:18:51 +00:00
|
|
|
int ymin = - pixels;
|
2007-09-15 12:28:41 +00:00
|
|
|
while (true) {
|
2020-07-12 22:00:36 +00:00
|
|
|
pair<pit_type, ParagraphMetrics const *> first = tm.first();
|
|
|
|
int top_pos = first.second->position() - first.second->ascent();
|
|
|
|
if (first.first == 0) {
|
2007-09-15 12:28:41 +00:00
|
|
|
if (top_pos >= 0)
|
2008-07-04 11:33:28 +00:00
|
|
|
return 0;
|
2020-10-31 17:18:51 +00:00
|
|
|
pixels = min(pixels, - top_pos);
|
2007-09-15 12:28:41 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (top_pos < ymin)
|
|
|
|
break;
|
|
|
|
tm.newParMetricsUp();
|
|
|
|
}
|
2020-10-31 17:18:51 +00:00
|
|
|
d->anchor_ypos_ += pixels;
|
|
|
|
return pixels;
|
2002-08-28 10:45:38 +00:00
|
|
|
}
|
2002-08-29 13:05:55 +00:00
|
|
|
|
|
|
|
|
2016-09-05 02:23:24 +00:00
|
|
|
bool BufferView::setCursorFromRow(int row)
|
2002-08-29 13:05:55 +00:00
|
|
|
{
|
2016-10-11 10:09:38 +00:00
|
|
|
TexRow::TextEntry start, end;
|
2016-10-11 09:22:20 +00:00
|
|
|
tie(start,end) = buffer_.texrow().getEntriesFromRow(row);
|
2022-02-27 19:35:58 +00:00
|
|
|
LYXERR(Debug::OUTFILE,
|
2016-09-05 02:23:24 +00:00
|
|
|
"setCursorFromRow: for row " << row << ", TexRow has found "
|
|
|
|
"start (id=" << start.id << ",pos=" << start.pos << "), "
|
|
|
|
"end (id=" << end.id << ",pos=" << end.pos << ")");
|
|
|
|
return setCursorFromEntries(start, end);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-10-11 10:09:38 +00:00
|
|
|
bool BufferView::setCursorFromEntries(TexRow::TextEntry start,
|
|
|
|
TexRow::TextEntry end)
|
2016-09-05 02:23:24 +00:00
|
|
|
{
|
|
|
|
DocIterator dit_start, dit_end;
|
|
|
|
tie(dit_start,dit_end) =
|
|
|
|
TexRow::getDocIteratorsFromEntries(start, end, buffer_);
|
|
|
|
if (!dit_start)
|
|
|
|
return false;
|
2016-09-03 22:54:05 +00:00
|
|
|
// Setting selection start
|
|
|
|
d->cursor_.clearSelection();
|
2016-09-05 02:23:24 +00:00
|
|
|
setCursor(dit_start);
|
2016-09-03 22:54:05 +00:00
|
|
|
// Setting selection end
|
2016-09-05 02:23:24 +00:00
|
|
|
if (dit_end) {
|
2016-09-03 22:54:05 +00:00
|
|
|
d->cursor_.resetAnchor();
|
2016-09-05 02:23:24 +00:00
|
|
|
setCursorSelectionTo(dit_end);
|
2016-09-03 22:54:05 +00:00
|
|
|
}
|
2016-09-05 02:23:24 +00:00
|
|
|
return true;
|
2002-08-29 13:05:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-09-17 14:51:27 +00:00
|
|
|
bool BufferView::setCursorFromInset(Inset const * inset)
|
|
|
|
{
|
|
|
|
// are we already there?
|
2008-09-30 13:47:16 +00:00
|
|
|
if (cursor().nextInset() == inset)
|
2008-09-17 14:51:27 +00:00
|
|
|
return true;
|
|
|
|
|
|
|
|
// Inset is not at cursor position. Find it in the document.
|
|
|
|
Cursor cur(*this);
|
2009-10-29 22:46:04 +00:00
|
|
|
cur.reset();
|
2008-09-30 13:47:16 +00:00
|
|
|
while (cur && cur.nextInset() != inset)
|
2008-09-17 14:51:27 +00:00
|
|
|
cur.forwardInset();
|
|
|
|
|
|
|
|
if (cur) {
|
|
|
|
setCursor(cur);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2006-10-12 10:50:45 +00:00
|
|
|
void BufferView::gotoLabel(docstring const & label)
|
2002-08-29 13:05:55 +00:00
|
|
|
{
|
2018-12-31 17:28:44 +00:00
|
|
|
FuncRequest action;
|
|
|
|
bool have_inactive = false;
|
2018-01-08 14:15:29 +00:00
|
|
|
for (Buffer const * buf : buffer().allRelatives()) {
|
2008-11-16 21:28:06 +00:00
|
|
|
// find label
|
2018-01-08 14:15:29 +00:00
|
|
|
for (TocItem const & item : *buf->tocBackend().toc("label")) {
|
2018-12-31 17:28:44 +00:00
|
|
|
if (label == item.str() && item.isOutput()) {
|
2018-01-08 14:15:29 +00:00
|
|
|
lyx::dispatch(item.action());
|
2008-11-16 21:28:06 +00:00
|
|
|
return;
|
|
|
|
}
|
2018-12-31 17:28:44 +00:00
|
|
|
// If we find an inactive label, save it for the case
|
|
|
|
// that no active one is there
|
|
|
|
if (label == item.str() && !have_inactive) {
|
|
|
|
have_inactive = true;
|
|
|
|
action = item.action();
|
|
|
|
}
|
2008-11-16 21:28:06 +00:00
|
|
|
}
|
2002-08-29 13:05:55 +00:00
|
|
|
}
|
2018-12-31 17:28:44 +00:00
|
|
|
// We only found an inactive label. Go there.
|
|
|
|
if (have_inactive)
|
|
|
|
lyx::dispatch(action);
|
2002-08-29 13:05:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-04-29 23:33:02 +00:00
|
|
|
TextMetrics const & BufferView::textMetrics(Text const * t) const
|
2006-12-29 23:54:48 +00:00
|
|
|
{
|
|
|
|
return const_cast<BufferView *>(this)->textMetrics(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-04-29 23:33:02 +00:00
|
|
|
TextMetrics & BufferView::textMetrics(Text const * t)
|
2006-12-29 23:54:48 +00:00
|
|
|
{
|
2013-04-27 21:52:55 +00:00
|
|
|
LBUFERR(t);
|
2007-10-13 07:33:07 +00:00
|
|
|
TextMetricsCache::iterator tmc_it = d->text_metrics_.find(t);
|
|
|
|
if (tmc_it == d->text_metrics_.end()) {
|
2019-03-12 11:40:32 +00:00
|
|
|
tmc_it = d->text_metrics_.emplace(std::piecewise_construct,
|
|
|
|
std::forward_as_tuple(t),
|
|
|
|
std::forward_as_tuple(this, const_cast<Text *>(t))).first;
|
2007-05-28 22:27:45 +00:00
|
|
|
}
|
2006-12-29 23:54:48 +00:00
|
|
|
return tmc_it->second;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-04-29 23:33:02 +00:00
|
|
|
ParagraphMetrics const & BufferView::parMetrics(Text const * t,
|
2006-12-29 23:54:48 +00:00
|
|
|
pit_type pit) const
|
|
|
|
{
|
|
|
|
return textMetrics(t).parMetrics(pit);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2002-08-29 13:41:58 +00:00
|
|
|
int BufferView::workHeight() const
|
|
|
|
{
|
2006-09-16 10:08:51 +00:00
|
|
|
return height_;
|
2002-08-29 13:41:58 +00:00
|
|
|
}
|
2003-11-04 07:43:03 +00:00
|
|
|
|
|
|
|
|
2005-02-22 11:41:22 +00:00
|
|
|
void BufferView::setCursor(DocIterator const & dit)
|
2004-02-02 11:07:51 +00:00
|
|
|
{
|
2009-10-29 22:46:04 +00:00
|
|
|
d->cursor_.reset();
|
2005-02-22 11:41:22 +00:00
|
|
|
size_t const n = dit.depth();
|
|
|
|
for (size_t i = 0; i < n; ++i)
|
2007-10-13 07:33:07 +00:00
|
|
|
dit[i].inset().edit(d->cursor_, true);
|
2004-03-31 17:58:11 +00:00
|
|
|
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.setCursor(dit);
|
2016-02-28 16:36:29 +00:00
|
|
|
d->cursor_.selection(false);
|
2015-07-24 03:27:47 +00:00
|
|
|
d->cursor_.setCurrentFont();
|
2011-04-15 02:26:22 +00:00
|
|
|
// FIXME
|
|
|
|
// It seems on general grounds as if this is probably needed, but
|
|
|
|
// it is not yet clear.
|
|
|
|
// See bug #7394 and r38388.
|
|
|
|
// d->cursor.resetAnchor();
|
2004-02-02 11:07:51 +00:00
|
|
|
}
|
2004-01-14 17:21:39 +00:00
|
|
|
|
2005-02-08 13:18:05 +00:00
|
|
|
|
2016-09-04 21:17:32 +00:00
|
|
|
void BufferView::setCursorSelectionTo(DocIterator const & dit)
|
|
|
|
{
|
|
|
|
size_t const n = dit.depth();
|
|
|
|
for (size_t i = 0; i < n; ++i)
|
|
|
|
dit[i].inset().edit(d->cursor_, true);
|
|
|
|
|
|
|
|
d->cursor_.selection(true);
|
|
|
|
d->cursor_.setCursorSelectionTo(dit);
|
|
|
|
d->cursor_.setCurrentFont();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-04-26 14:56:30 +00:00
|
|
|
bool BufferView::checkDepm(Cursor & cur, Cursor & old)
|
2006-12-30 15:05:15 +00:00
|
|
|
{
|
|
|
|
// Would be wrong to delete anything if we have a selection.
|
|
|
|
if (cur.selection())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
bool need_anchor_change = false;
|
2019-01-29 13:38:17 +00:00
|
|
|
bool changed = Text::deleteEmptyParagraphMechanism(cur, old,
|
2006-12-30 15:05:15 +00:00
|
|
|
need_anchor_change);
|
|
|
|
|
|
|
|
if (need_anchor_change)
|
|
|
|
cur.resetAnchor();
|
2007-05-28 22:27:45 +00:00
|
|
|
|
2006-12-30 15:05:15 +00:00
|
|
|
if (!changed)
|
|
|
|
return false;
|
|
|
|
|
2007-12-27 10:39:25 +00:00
|
|
|
d->cursor_ = cur;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
|
|
|
// we would rather not do this here, but it needs to be done before
|
2015-05-17 15:27:12 +00:00
|
|
|
// the changed() signal is sent.
|
2013-05-19 12:42:27 +00:00
|
|
|
buffer_.updateBuffer();
|
2007-12-27 10:39:25 +00:00
|
|
|
|
2013-05-19 12:42:27 +00:00
|
|
|
buffer_.changed(true);
|
2006-12-30 15:05:15 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-09-04 22:24:57 +00:00
|
|
|
bool BufferView::mouseSetCursor(Cursor & cur, bool const select)
|
2005-12-01 10:28:50 +00:00
|
|
|
{
|
2013-04-25 21:27:10 +00:00
|
|
|
LASSERT(&cur.bv() == this, return false);
|
2005-12-01 10:28:50 +00:00
|
|
|
|
2007-10-11 14:46:53 +00:00
|
|
|
if (!select)
|
|
|
|
// this event will clear selection so we save selection for
|
|
|
|
// persistent selection
|
|
|
|
cap::saveSelection(cursor());
|
2007-07-10 12:44:19 +00:00
|
|
|
|
2009-10-27 00:11:56 +00:00
|
|
|
d->cursor_.macroModeClose();
|
2012-09-23 14:44:22 +00:00
|
|
|
// If a macro has been finalized, the cursor might have been broken
|
|
|
|
cur.fixIfBroken();
|
2009-10-27 00:11:56 +00:00
|
|
|
|
2005-12-01 10:28:50 +00:00
|
|
|
// Has the cursor just left the inset?
|
2011-04-14 20:34:18 +00:00
|
|
|
bool const leftinset = (&d->cursor_.inset() != &cur.inset());
|
2008-11-15 17:11:01 +00:00
|
|
|
if (leftinset)
|
2008-02-22 20:45:18 +00:00
|
|
|
d->cursor_.fixIfBroken();
|
2005-12-01 10:28:50 +00:00
|
|
|
|
|
|
|
// do the dEPM magic if needed
|
2007-04-10 16:50:19 +00:00
|
|
|
// FIXME: (1) move this to InsetText::notifyCursorLeaves?
|
|
|
|
// FIXME: (2) if we had a working InsetText::notifyCursorLeaves,
|
|
|
|
// the leftinset bool would not be necessary (badcursor instead).
|
|
|
|
bool update = leftinset;
|
2005-12-01 10:28:50 +00:00
|
|
|
|
2016-09-04 22:24:57 +00:00
|
|
|
if (select) {
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.setSelection();
|
2016-09-04 22:24:57 +00:00
|
|
|
d->cursor_.setCursorSelectionTo(cur);
|
|
|
|
} else {
|
|
|
|
if (d->cursor_.inTexted())
|
|
|
|
update |= checkDepm(cur, d->cursor_);
|
|
|
|
d->cursor_.resetAnchor();
|
|
|
|
d->cursor_.setCursor(cur);
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.clearSelection();
|
2016-09-04 22:24:57 +00:00
|
|
|
}
|
|
|
|
d->cursor_.boundary(cur.boundary());
|
2007-10-18 11:51:17 +00:00
|
|
|
d->cursor_.finishUndo();
|
2007-10-29 18:57:56 +00:00
|
|
|
d->cursor_.setCurrentFont();
|
2010-07-09 14:37:00 +00:00
|
|
|
if (update)
|
|
|
|
cur.forceBufferUpdate();
|
2006-12-11 13:59:40 +00:00
|
|
|
return update;
|
2005-12-01 10:28:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2004-03-31 19:11:56 +00:00
|
|
|
void BufferView::putSelectionAt(DocIterator const & cur,
|
2004-03-28 22:00:22 +00:00
|
|
|
int length, bool backwards)
|
2004-01-14 17:21:39 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.clearSelection();
|
2004-01-14 17:21:39 +00:00
|
|
|
|
2005-02-22 11:41:22 +00:00
|
|
|
setCursor(cur);
|
2004-04-03 08:37:12 +00:00
|
|
|
|
2004-01-14 17:21:39 +00:00
|
|
|
if (length) {
|
2004-03-01 17:12:09 +00:00
|
|
|
if (backwards) {
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.pos() += length;
|
|
|
|
d->cursor_.setSelection(d->cursor_, -length);
|
2004-03-31 17:58:11 +00:00
|
|
|
} else
|
2007-10-13 07:33:07 +00:00
|
|
|
d->cursor_.setSelection(d->cursor_, length);
|
2004-01-14 17:21:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2004-01-15 11:58:35 +00:00
|
|
|
|
2011-03-19 10:34:59 +00:00
|
|
|
bool BufferView::selectIfEmpty(DocIterator & cur)
|
|
|
|
{
|
2014-03-22 11:09:18 +00:00
|
|
|
if ((cur.inTexted() && !cur.paragraph().empty())
|
2014-03-22 11:22:41 +00:00
|
|
|
|| (cur.inMathed() && !cur.cell().empty()))
|
2011-03-19 10:34:59 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
pit_type const beg_pit = cur.pit();
|
|
|
|
if (beg_pit > 0) {
|
|
|
|
// The paragraph associated to this item isn't
|
|
|
|
// the first one, so it can be selected
|
|
|
|
cur.backwardPos();
|
|
|
|
} else {
|
|
|
|
// We have to resort to select the space between the
|
|
|
|
// end of this item and the begin of the next one
|
|
|
|
cur.forwardPos();
|
|
|
|
}
|
|
|
|
if (cur.empty()) {
|
|
|
|
// If it is the only item in the document,
|
|
|
|
// nothing can be selected
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
pit_type const end_pit = cur.pit();
|
|
|
|
pos_type const end_pos = cur.pos();
|
|
|
|
d->cursor_.clearSelection();
|
|
|
|
d->cursor_.reset();
|
|
|
|
d->cursor_.setCursor(cur);
|
|
|
|
d->cursor_.pit() = beg_pit;
|
|
|
|
d->cursor_.pos() = 0;
|
2016-02-28 16:36:29 +00:00
|
|
|
d->cursor_.selection(false);
|
2011-03-19 10:34:59 +00:00
|
|
|
d->cursor_.resetAnchor();
|
|
|
|
d->cursor_.pit() = end_pit;
|
|
|
|
d->cursor_.pos() = end_pos;
|
|
|
|
d->cursor_.setSelection();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-04-26 14:56:30 +00:00
|
|
|
Cursor & BufferView::cursor()
|
2004-01-20 14:25:24 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
return d->cursor_;
|
2004-01-20 14:25:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-04-26 14:56:30 +00:00
|
|
|
Cursor const & BufferView::cursor() const
|
2004-01-15 11:58:35 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
return d->cursor_;
|
2004-01-15 11:58:35 +00:00
|
|
|
}
|
2004-11-30 01:59:49 +00:00
|
|
|
|
|
|
|
|
2007-09-28 09:11:24 +00:00
|
|
|
bool BufferView::singleParUpdate()
|
|
|
|
{
|
|
|
|
Text & buftext = buffer_.text();
|
2007-10-13 07:33:07 +00:00
|
|
|
pit_type const bottom_pit = d->cursor_.bottom().pit();
|
2007-09-28 09:11:24 +00:00
|
|
|
TextMetrics & tm = textMetrics(&buftext);
|
2020-07-13 21:29:15 +00:00
|
|
|
Dimension const old_dim = tm.parMetrics(bottom_pit).dim();
|
2007-09-28 09:11:24 +00:00
|
|
|
|
2008-02-21 19:42:34 +00:00
|
|
|
// make sure inline completion pointer is ok
|
2008-04-16 20:43:30 +00:00
|
|
|
if (d->inlineCompletionPos_.fixIfBroken())
|
|
|
|
d->inlineCompletionPos_ = DocIterator();
|
2008-02-21 19:42:34 +00:00
|
|
|
|
2007-09-28 09:11:24 +00:00
|
|
|
// In Single Paragraph mode, rebreak only
|
|
|
|
// the (main text, not inset!) paragraph containing the cursor.
|
|
|
|
// (if this paragraph contains insets etc., rebreaking will
|
|
|
|
// recursively descend)
|
|
|
|
tm.redoParagraph(bottom_pit);
|
2020-07-13 22:08:07 +00:00
|
|
|
ParagraphMetrics & pm = tm.parMetrics(bottom_pit);
|
2020-07-13 21:29:15 +00:00
|
|
|
if (pm.height() != old_dim.height()) {
|
2007-09-28 09:11:24 +00:00
|
|
|
// Paragraph height has changed so we cannot proceed to
|
|
|
|
// the singlePar optimisation.
|
|
|
|
return false;
|
2020-07-13 21:29:15 +00:00
|
|
|
}
|
|
|
|
// Since position() points to the baseline of the first row, we
|
|
|
|
// may have to update it. See ticket #11601 for an example where
|
|
|
|
// the height does not change but the ascent does.
|
|
|
|
pm.setPosition(pm.position() - old_dim.ascent() + pm.ascent());
|
2007-09-28 09:11:24 +00:00
|
|
|
|
2017-08-30 16:05:16 +00:00
|
|
|
tm.updatePosCache(bottom_pit);
|
2007-11-17 11:27:03 +00:00
|
|
|
|
2007-11-30 08:35:54 +00:00
|
|
|
LYXERR(Debug::PAINTING, "\ny1: " << pm.position() - pm.ascent()
|
2007-11-17 11:27:03 +00:00
|
|
|
<< " y2: " << pm.position() + pm.descent()
|
2007-09-28 09:11:24 +00:00
|
|
|
<< " pit: " << bottom_pit
|
2007-11-15 20:04:51 +00:00
|
|
|
<< " singlepar: 1");
|
2007-09-28 09:11:24 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-11-06 08:32:25 +00:00
|
|
|
void BufferView::updateMetrics()
|
2017-10-11 16:00:48 +00:00
|
|
|
{
|
|
|
|
updateMetrics(d->update_flags_);
|
|
|
|
d->update_strategy_ = FullScreenUpdate;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BufferView::updateMetrics(Update::flags & update_flags)
|
2006-07-08 20:24:32 +00:00
|
|
|
{
|
2008-06-19 13:10:25 +00:00
|
|
|
if (height_ == 0 || width_ == 0)
|
|
|
|
return;
|
|
|
|
|
2007-08-21 07:33:46 +00:00
|
|
|
Text & buftext = buffer_.text();
|
2007-09-11 16:04:10 +00:00
|
|
|
pit_type const npit = int(buftext.paragraphs().size());
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2007-09-28 09:11:24 +00:00
|
|
|
// Clear out the position cache in case of full screen redraw,
|
2007-10-13 07:33:07 +00:00
|
|
|
d->coord_cache_.clear();
|
2019-06-03 14:22:44 +00:00
|
|
|
d->math_rows_.clear();
|
2007-09-16 10:38:25 +00:00
|
|
|
|
2007-09-28 09:11:24 +00:00
|
|
|
// Clear out paragraph metrics to avoid having invalid metrics
|
|
|
|
// in the cache from paragraphs not relayouted below
|
|
|
|
// The complete text metrics will be redone.
|
2007-10-13 07:33:07 +00:00
|
|
|
d->text_metrics_.clear();
|
2007-08-30 16:48:47 +00:00
|
|
|
|
|
|
|
TextMetrics & tm = textMetrics(&buftext);
|
2007-06-21 06:41:55 +00:00
|
|
|
|
2008-02-21 19:42:34 +00:00
|
|
|
// make sure inline completion pointer is ok
|
2008-04-16 20:43:30 +00:00
|
|
|
if (d->inlineCompletionPos_.fixIfBroken())
|
|
|
|
d->inlineCompletionPos_ = DocIterator();
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2008-03-02 19:07:15 +00:00
|
|
|
if (d->anchor_pit_ >= npit)
|
|
|
|
// The anchor pit must have been deleted...
|
|
|
|
d->anchor_pit_ = npit - 1;
|
|
|
|
|
2007-01-06 09:15:59 +00:00
|
|
|
// Rebreak anchor paragraph.
|
2007-12-21 08:28:18 +00:00
|
|
|
tm.redoParagraph(d->anchor_pit_);
|
2020-07-13 22:08:07 +00:00
|
|
|
ParagraphMetrics & anchor_pm = tm.parMetrics(d->anchor_pit_);
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2008-02-05 14:23:59 +00:00
|
|
|
// position anchor
|
|
|
|
if (d->anchor_pit_ == 0) {
|
|
|
|
int scrollRange = d->scrollbarParameters_.max - d->scrollbarParameters_.min;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2008-02-05 14:23:59 +00:00
|
|
|
// Complete buffer visible? Then it's easy.
|
|
|
|
if (scrollRange == 0)
|
|
|
|
d->anchor_ypos_ = anchor_pm.ascent();
|
2017-09-28 11:31:41 +00:00
|
|
|
else {
|
|
|
|
// avoid empty space above the first row
|
|
|
|
d->anchor_ypos_ = min(d->anchor_ypos_, anchor_pm.ascent());
|
|
|
|
}
|
2014-07-25 20:35:08 +00:00
|
|
|
}
|
2007-12-21 08:28:18 +00:00
|
|
|
anchor_pm.setPosition(d->anchor_ypos_);
|
2017-08-30 16:05:16 +00:00
|
|
|
tm.updatePosCache(d->anchor_pit_);
|
2007-05-28 22:27:45 +00:00
|
|
|
|
2007-12-21 08:28:18 +00:00
|
|
|
LYXERR(Debug::PAINTING, "metrics: "
|
|
|
|
<< " anchor pit = " << d->anchor_pit_
|
|
|
|
<< " anchor ypos = " << d->anchor_ypos_);
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2007-01-06 09:15:59 +00:00
|
|
|
// Redo paragraphs above anchor if necessary.
|
2007-12-21 08:28:18 +00:00
|
|
|
int y1 = d->anchor_ypos_ - anchor_pm.ascent();
|
|
|
|
// We are now just above the anchor paragraph.
|
|
|
|
pit_type pit1 = d->anchor_pit_ - 1;
|
2020-04-26 07:08:35 +00:00
|
|
|
for (; pit1 >= 0 && y1 >= 0; --pit1) {
|
2007-09-11 16:04:10 +00:00
|
|
|
tm.redoParagraph(pit1);
|
2020-07-13 22:08:07 +00:00
|
|
|
ParagraphMetrics & pm = tm.parMetrics(pit1);
|
2007-12-21 08:28:18 +00:00
|
|
|
y1 -= pm.descent();
|
|
|
|
// Save the paragraph position in the cache.
|
|
|
|
pm.setPosition(y1);
|
2017-08-30 16:05:16 +00:00
|
|
|
tm.updatePosCache(pit1);
|
2007-12-21 08:28:18 +00:00
|
|
|
y1 -= pm.ascent();
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
|
2007-01-06 09:15:59 +00:00
|
|
|
// Redo paragraphs below the anchor if necessary.
|
2007-12-21 08:28:18 +00:00
|
|
|
int y2 = d->anchor_ypos_ + anchor_pm.descent();
|
|
|
|
// We are now just below the anchor paragraph.
|
|
|
|
pit_type pit2 = d->anchor_pit_ + 1;
|
2007-12-22 09:04:53 +00:00
|
|
|
for (; pit2 < npit && y2 <= height_; ++pit2) {
|
2007-09-11 16:04:10 +00:00
|
|
|
tm.redoParagraph(pit2);
|
2020-07-13 22:08:07 +00:00
|
|
|
ParagraphMetrics & pm = tm.parMetrics(pit2);
|
2007-12-21 08:28:18 +00:00
|
|
|
y2 += pm.ascent();
|
|
|
|
// Save the paragraph position in the cache.
|
|
|
|
pm.setPosition(y2);
|
2017-08-30 16:05:16 +00:00
|
|
|
tm.updatePosCache(pit2);
|
2007-12-21 08:28:18 +00:00
|
|
|
y2 += pm.descent();
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
|
2007-12-21 08:28:18 +00:00
|
|
|
LYXERR(Debug::PAINTING, "Metrics: "
|
|
|
|
<< " anchor pit = " << d->anchor_pit_
|
|
|
|
<< " anchor ypos = " << d->anchor_ypos_
|
|
|
|
<< " y1 = " << y1
|
|
|
|
<< " y2 = " << y2
|
|
|
|
<< " pit1 = " << pit1
|
|
|
|
<< " pit2 = " << pit2);
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2017-10-11 16:00:48 +00:00
|
|
|
// metrics is done, full drawing is necessary now
|
|
|
|
update_flags = (update_flags & ~Update::Force) | Update::ForceDraw;
|
2006-10-20 20:57:21 +00:00
|
|
|
|
2017-07-15 23:25:03 +00:00
|
|
|
// Now update the positions of insets in the cache.
|
|
|
|
updatePosCache();
|
|
|
|
|
2006-10-20 20:57:21 +00:00
|
|
|
if (lyxerr.debugging(Debug::WORKAREA)) {
|
2007-11-15 20:04:51 +00:00
|
|
|
LYXERR(Debug::WORKAREA, "BufferView::updateMetrics");
|
2007-10-13 07:33:07 +00:00
|
|
|
d->coord_cache_.dump();
|
2006-10-20 20:57:21 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-15 23:25:03 +00:00
|
|
|
void BufferView::updatePosCache()
|
|
|
|
{
|
|
|
|
// this is the "nodraw" drawing stage: only set the positions of the
|
|
|
|
// insets in metrics cache.
|
|
|
|
frontend::NullPainter np;
|
2017-07-20 21:31:05 +00:00
|
|
|
draw(np, false);
|
2017-07-15 23:25:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-03-26 09:09:55 +00:00
|
|
|
void BufferView::insertLyXFile(FileName const & fname, bool const ignorelang)
|
2006-09-16 10:08:51 +00:00
|
|
|
{
|
2013-04-25 21:27:10 +00:00
|
|
|
LASSERT(d->cursor_.inTexted(), return);
|
2006-09-16 10:08:51 +00:00
|
|
|
|
|
|
|
// Get absolute path of file and add ".lyx"
|
|
|
|
// to the filename if necessary
|
2010-04-21 01:19:09 +00:00
|
|
|
FileName filename = fileSearch(string(), fname.absFileName(), "lyx");
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2010-04-21 01:19:09 +00:00
|
|
|
docstring const disp_fn = makeDisplayPath(filename.absFileName());
|
2006-09-16 10:08:51 +00:00
|
|
|
// emit message signal.
|
|
|
|
message(bformat(_("Inserting document %1$s..."), disp_fn));
|
|
|
|
|
|
|
|
docstring res;
|
2010-11-05 21:21:01 +00:00
|
|
|
Buffer buf(filename.absFileName(), false);
|
|
|
|
if (buf.loadLyXFile() == Buffer::ReadSuccess) {
|
2007-08-21 07:33:46 +00:00
|
|
|
ErrorList & el = buffer_.errorList("Parse");
|
2006-09-16 10:08:51 +00:00
|
|
|
// Copy the inserted document error list into the current buffer one.
|
|
|
|
el = buf.errorList("Parse");
|
2019-03-26 09:09:55 +00:00
|
|
|
ParagraphList & pars = buf.paragraphs();
|
|
|
|
if (ignorelang)
|
|
|
|
// set main language of imported file to context language
|
|
|
|
buf.changeLanguage(buf.language(), d->cursor_.getFont().language());
|
2007-10-18 11:51:17 +00:00
|
|
|
buffer_.undo().recordUndo(d->cursor_);
|
2023-05-16 08:54:52 +00:00
|
|
|
cap::replaceSelection(d->cursor_);
|
2019-03-26 09:09:55 +00:00
|
|
|
cap::pasteParagraphList(d->cursor_, pars,
|
2021-02-09 15:54:21 +00:00
|
|
|
buf.params().documentClassPtr(),
|
|
|
|
buf.params().authors(), el);
|
2006-09-16 10:08:51 +00:00
|
|
|
res = _("Document %1$s inserted.");
|
2007-10-21 10:50:56 +00:00
|
|
|
} else {
|
2006-09-16 10:08:51 +00:00
|
|
|
res = _("Could not insert document %1$s");
|
2007-10-21 10:50:56 +00:00
|
|
|
}
|
2006-09-16 10:08:51 +00:00
|
|
|
|
2010-01-08 02:03:54 +00:00
|
|
|
buffer_.changed(true);
|
2006-09-16 10:08:51 +00:00
|
|
|
// emit message signal.
|
|
|
|
message(bformat(res, disp_fn));
|
|
|
|
}
|
2006-10-21 00:16:43 +00:00
|
|
|
|
2007-08-27 22:53:16 +00:00
|
|
|
|
2010-04-21 13:12:40 +00:00
|
|
|
Point BufferView::coordOffset(DocIterator const & dit) const
|
2007-09-29 09:22:17 +00:00
|
|
|
{
|
|
|
|
int x = 0;
|
|
|
|
int y = 0;
|
|
|
|
int lastw = 0;
|
|
|
|
|
|
|
|
// Addup contribution of nested insets, from inside to outside,
|
2015-05-17 15:27:12 +00:00
|
|
|
// keeping the outer paragraph for a special handling below
|
2007-09-29 09:22:17 +00:00
|
|
|
for (size_t i = dit.depth() - 1; i >= 1; --i) {
|
|
|
|
CursorSlice const & sl = dit[i];
|
|
|
|
int xx = 0;
|
|
|
|
int yy = 0;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2007-09-29 09:22:17 +00:00
|
|
|
// get relative position inside sl.inset()
|
2010-04-21 13:12:40 +00:00
|
|
|
sl.inset().cursorPos(*this, sl, dit.boundary() && (i + 1 == dit.depth()), xx, yy);
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2007-09-29 09:22:17 +00:00
|
|
|
// Make relative position inside of the edited inset relative to sl.inset()
|
|
|
|
x += xx;
|
|
|
|
y += yy;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2007-09-29 09:22:17 +00:00
|
|
|
// In case of an RTL inset, the edited inset will be positioned to the left
|
|
|
|
// of xx:yy
|
|
|
|
if (sl.text()) {
|
2010-04-21 13:12:40 +00:00
|
|
|
bool boundary_i = dit.boundary() && i + 1 == dit.depth();
|
2007-09-29 09:22:17 +00:00
|
|
|
bool rtl = textMetrics(sl.text()).isRTL(sl, boundary_i);
|
|
|
|
if (rtl)
|
|
|
|
x -= lastw;
|
|
|
|
}
|
|
|
|
|
|
|
|
// remember width for the case that sl.inset() is positioned in an RTL inset
|
2016-12-06 08:48:49 +00:00
|
|
|
lastw = sl.inset().dimension(*this).wid;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2007-09-29 09:22:17 +00:00
|
|
|
//lyxerr << "Cursor::getPos, i: "
|
|
|
|
// << i << " x: " << xx << " y: " << y << endl;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add contribution of initial rows of outermost paragraph
|
|
|
|
CursorSlice const & sl = dit[0];
|
|
|
|
TextMetrics const & tm = textMetrics(sl.text());
|
|
|
|
ParagraphMetrics const & pm = tm.parMetrics(sl.pit());
|
2013-04-25 21:27:10 +00:00
|
|
|
|
2013-04-27 21:52:55 +00:00
|
|
|
LBUFERR(!pm.rows().empty());
|
2020-07-12 22:00:36 +00:00
|
|
|
y -= pm.rows()[0].ascent();
|
2007-09-29 09:22:17 +00:00
|
|
|
#if 1
|
|
|
|
// FIXME: document this mess
|
|
|
|
size_t rend;
|
|
|
|
if (sl.pos() > 0 && dit.depth() == 1) {
|
|
|
|
int pos = sl.pos();
|
2010-04-21 13:12:40 +00:00
|
|
|
if (pos && dit.boundary())
|
2007-09-29 09:22:17 +00:00
|
|
|
--pos;
|
2010-04-21 13:12:40 +00:00
|
|
|
// lyxerr << "coordOffset: boundary:" << dit.boundary() << " depth:" << dit.depth() << " pos:" << pos << " sl.pos:" << sl.pos() << endl;
|
2007-09-29 09:22:17 +00:00
|
|
|
rend = pm.pos2row(pos);
|
|
|
|
} else
|
|
|
|
rend = pm.pos2row(sl.pos());
|
|
|
|
#else
|
|
|
|
size_t rend = pm.pos2row(sl.pos());
|
|
|
|
#endif
|
|
|
|
for (size_t rit = 0; rit != rend; ++rit)
|
|
|
|
y += pm.rows()[rit].height();
|
2020-07-12 22:00:36 +00:00
|
|
|
y += pm.rows()[rend].ascent();
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2007-09-29 09:22:17 +00:00
|
|
|
TextMetrics const & bottom_tm = textMetrics(dit.bottom().text());
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2007-09-29 09:22:17 +00:00
|
|
|
// Make relative position from the nested inset now bufferview absolute.
|
2010-04-21 13:12:40 +00:00
|
|
|
int xx = bottom_tm.cursorX(dit.bottom(), dit.boundary() && dit.depth() == 1);
|
2007-09-29 09:22:17 +00:00
|
|
|
x += xx;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
|
|
|
// In the RTL case place the nested inset at the left of the cursor in
|
2007-09-29 09:22:17 +00:00
|
|
|
// the outer paragraph
|
2010-04-21 13:12:40 +00:00
|
|
|
bool boundary_1 = dit.boundary() && 1 == dit.depth();
|
2007-09-29 09:22:17 +00:00
|
|
|
bool rtl = bottom_tm.isRTL(dit.bottom(), boundary_1);
|
|
|
|
if (rtl)
|
|
|
|
x -= lastw;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2007-09-29 09:22:17 +00:00
|
|
|
return Point(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-04-21 13:03:04 +00:00
|
|
|
Point BufferView::getPos(DocIterator const & dit) const
|
2007-09-29 09:22:17 +00:00
|
|
|
{
|
2008-11-18 23:57:26 +00:00
|
|
|
if (!paragraphVisible(dit))
|
|
|
|
return Point(-1, -1);
|
|
|
|
|
2007-09-29 09:22:17 +00:00
|
|
|
CursorSlice const & bot = dit.bottom();
|
|
|
|
TextMetrics const & tm = textMetrics(bot.text());
|
|
|
|
|
2010-04-21 13:12:40 +00:00
|
|
|
// offset from outer paragraph
|
2014-07-25 20:35:08 +00:00
|
|
|
Point p = coordOffset(dit);
|
2007-09-29 09:22:17 +00:00
|
|
|
p.y_ += tm.parMetrics(bot.pit()).position();
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-11-18 23:57:26 +00:00
|
|
|
bool BufferView::paragraphVisible(DocIterator const & dit) const
|
|
|
|
{
|
|
|
|
CursorSlice const & bot = dit.bottom();
|
|
|
|
TextMetrics const & tm = textMetrics(bot.text());
|
|
|
|
|
|
|
|
return tm.contains(bot.pit());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-01 11:17:01 +00:00
|
|
|
void BufferView::caretPosAndDim(Point & p, Dimension & dim) const
|
2009-04-03 21:39:06 +00:00
|
|
|
{
|
|
|
|
Cursor const & cur = cursor();
|
2018-04-26 22:03:48 +00:00
|
|
|
if (cur.inMathed()) {
|
2019-06-03 14:22:44 +00:00
|
|
|
MathRow const & mrow = mathRow(&cur.cell());
|
2020-10-01 12:24:21 +00:00
|
|
|
dim = mrow.caret_dim;
|
2018-04-26 22:03:48 +00:00
|
|
|
} else {
|
|
|
|
Font const font = cur.real_current_font;
|
|
|
|
frontend::FontMetrics const & fm = theFontMetrics(font);
|
2021-03-26 13:19:07 +00:00
|
|
|
// lineWidth() can be 0 to mean 'thin line' on HiDpi, but the
|
|
|
|
// caret drawing code is not prepared for that.
|
2021-03-12 18:19:51 +00:00
|
|
|
dim.wid = max(fm.lineWidth(), 1);
|
2020-10-01 11:17:01 +00:00
|
|
|
dim.asc = fm.maxAscent();
|
|
|
|
dim.des = fm.maxDescent();
|
2018-04-26 22:03:48 +00:00
|
|
|
}
|
2020-10-01 11:17:01 +00:00
|
|
|
if (lyxrc.cursor_width > 0)
|
|
|
|
dim.wid = lyxrc.cursor_width;
|
|
|
|
|
2010-04-21 13:03:04 +00:00
|
|
|
p = getPos(cur);
|
2020-11-21 18:37:22 +00:00
|
|
|
// center fat carets horizontally
|
|
|
|
p.x_ -= dim.wid / 2;
|
2020-11-24 17:35:25 +00:00
|
|
|
// p is top-left
|
2020-10-01 11:17:01 +00:00
|
|
|
p.y_ -= dim.asc;
|
2009-04-03 21:39:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-11-24 17:35:25 +00:00
|
|
|
void BufferView::buildCaretGeometry(bool complet)
|
|
|
|
{
|
|
|
|
Point p;
|
|
|
|
Dimension dim;
|
|
|
|
caretPosAndDim(p, dim);
|
|
|
|
|
|
|
|
Cursor const & cur = d->cursor_;
|
|
|
|
Font const & realfont = cur.real_current_font;
|
|
|
|
frontend::FontMetrics const & fm = theFontMetrics(realfont.fontInfo());
|
|
|
|
bool const isrtl = realfont.isVisibleRightToLeft();
|
|
|
|
int const dir = isrtl ? -1 : 1;
|
|
|
|
|
|
|
|
frontend::CaretGeometry & cg = d->caret_geometry_;
|
|
|
|
cg.shapes.clear();
|
|
|
|
|
|
|
|
// The caret itself, slanted for italics in text edit mode except
|
|
|
|
// for selections because the selection rect does not slant
|
|
|
|
bool const slant = fm.italic() && cur.inTexted() && !cur.selection();
|
|
|
|
double const slope = slant ? fm.italicSlope() : 0;
|
|
|
|
cg.shapes.push_back(
|
2023-05-03 10:15:26 +00:00
|
|
|
{{iround(p.x_ + dim.asc * slope), p.y_},
|
|
|
|
{iround(p.x_ - dim.des * slope), p.y_ + dim.height()},
|
2020-11-24 17:35:25 +00:00
|
|
|
{iround(p.x_ + dir * dim.wid - dim.des * slope), p.y_ + dim.height()},
|
|
|
|
{iround(p.x_ + dir * dim.wid + dim.asc * slope), p.y_}}
|
|
|
|
);
|
|
|
|
|
|
|
|
// The language indicator _| (if needed)
|
|
|
|
Language const * doclang = buffer().params().language;
|
|
|
|
if (!((realfont.language() == doclang && isrtl == doclang->rightToLeft())
|
|
|
|
|| realfont.language() == latex_language)) {
|
|
|
|
int const lx = dim.height() / 3;
|
|
|
|
int const xx = iround(p.x_ - dim.des * slope);
|
|
|
|
int const yy = p.y_ + dim.height();
|
|
|
|
cg.shapes.push_back(
|
2023-05-03 10:15:26 +00:00
|
|
|
{{xx, yy - dim.wid},
|
2020-11-24 17:35:25 +00:00
|
|
|
{xx + dir * (dim.wid + lx - 1), yy - dim.wid},
|
|
|
|
{xx + dir * (dim.wid + lx - 1), yy},
|
2023-05-03 10:15:26 +00:00
|
|
|
{xx, yy}}
|
2020-11-24 17:35:25 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// The completion triangle |> (if needed)
|
|
|
|
if (complet) {
|
|
|
|
int const m = p.y_ + dim.height() / 2;
|
|
|
|
int const d = dim.height() / 8;
|
|
|
|
// offset for slanted carret
|
|
|
|
int const sx = iround((dim.asc - (dim.height() / 2 - d)) * slope);
|
|
|
|
// starting position x
|
|
|
|
int const xx = p.x_ + dir * dim.wid + sx;
|
|
|
|
cg.shapes.push_back(
|
2023-05-03 10:15:26 +00:00
|
|
|
{{xx, m - d},
|
|
|
|
{xx + dir * d, m},
|
|
|
|
{xx, m + d},
|
|
|
|
{xx, m + d - dim.wid},
|
2020-11-24 17:35:25 +00:00
|
|
|
{xx + dir * d - dim.wid, m},
|
2023-05-03 10:15:26 +00:00
|
|
|
{xx, m - d + dim.wid}}
|
2020-11-24 17:35:25 +00:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// compute extremal x values
|
|
|
|
cg.left = 1000000;
|
|
|
|
cg.right = -1000000;
|
|
|
|
cg.top = 1000000;
|
|
|
|
cg.bottom = -1000000;
|
|
|
|
for (auto const & shape : cg.shapes)
|
|
|
|
for (Point const & p : shape) {
|
|
|
|
cg.left = min(cg.left, p.x_);
|
|
|
|
cg.right = max(cg.right, p.x_);
|
|
|
|
cg.top = min(cg.top, p.y_);
|
|
|
|
cg.bottom = max(cg.bottom, p.y_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
frontend::CaretGeometry const & BufferView::caretGeometry() const
|
|
|
|
{
|
|
|
|
return d->caret_geometry_;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2019-07-14 21:20:29 +00:00
|
|
|
bool BufferView::caretInView() const
|
2009-04-03 21:39:06 +00:00
|
|
|
{
|
2019-07-14 21:20:29 +00:00
|
|
|
if (!paragraphVisible(cursor()))
|
|
|
|
return false;
|
|
|
|
Point p;
|
2020-10-01 11:17:01 +00:00
|
|
|
Dimension dim;
|
|
|
|
caretPosAndDim(p, dim);
|
2019-07-14 21:20:29 +00:00
|
|
|
|
2009-04-03 21:39:06 +00:00
|
|
|
// does the cursor touch the screen ?
|
2020-10-01 11:17:01 +00:00
|
|
|
if (p.y_ + dim.height() < 0 || p.y_ >= workHeight())
|
2009-04-03 21:39:06 +00:00
|
|
|
return false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
int BufferView::horizScrollOffset() const
|
|
|
|
{
|
|
|
|
return d->horiz_scroll_offset_;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-02-29 15:05:06 +00:00
|
|
|
int BufferView::horizScrollOffset(Text const * text,
|
|
|
|
pit_type pit, pos_type pos) const
|
|
|
|
{
|
|
|
|
// Is this a row that is currently scrolled?
|
|
|
|
if (!d->current_row_slice_.empty()
|
|
|
|
&& &text->inset() == d->current_row_slice_.inset().asInsetText()
|
|
|
|
&& pit == d->current_row_slice_.pit()
|
|
|
|
&& pos == d->current_row_slice_.pos())
|
|
|
|
return d->horiz_scroll_offset_;
|
|
|
|
return 0;
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BufferView::setCurrentRowSlice(CursorSlice const & rowSlice)
|
|
|
|
{
|
|
|
|
// nothing to do if the cursor was already on this row
|
2019-02-21 16:52:36 +00:00
|
|
|
if (d->current_row_slice_ == rowSlice)
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// if the (previous) current row was scrolled, we have to
|
|
|
|
// remember it in order to repaint it next time.
|
2019-02-21 16:52:36 +00:00
|
|
|
if (d->horiz_scroll_offset_ != 0) {
|
|
|
|
// search the old row in cache and mark it changed
|
|
|
|
for (auto & tm_pair : d->text_metrics_) {
|
|
|
|
if (&tm_pair.first->inset() == rowSlice.inset().asInsetText()) {
|
|
|
|
tm_pair.second.setRowChanged(rowSlice.pit(), rowSlice.pos());
|
|
|
|
// We found it, no need to continue.
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
|
|
|
|
// Since we changed row, the scroll offset is not valid anymore
|
|
|
|
d->horiz_scroll_offset_ = 0;
|
|
|
|
d->current_row_slice_ = rowSlice;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-23 13:50:35 +00:00
|
|
|
void BufferView::checkCursorScrollOffset()
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
{
|
|
|
|
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(),
|
2015-10-05 16:04:12 +00:00
|
|
|
d->cursor_.boundary() && rowSlice == d->cursor_.top());
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
rowSlice.pos() = row.pos();
|
|
|
|
|
|
|
|
// Set the row on which the cursor lives.
|
|
|
|
setCurrentRowSlice(rowSlice);
|
|
|
|
|
2015-12-04 22:26:16 +00:00
|
|
|
// Current x position of the cursor in pixels
|
|
|
|
int cur_x = getPos(d->cursor_).x_;
|
|
|
|
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
// Horizontal scroll offset of the cursor row in pixels
|
|
|
|
int offset = d->horiz_scroll_offset_;
|
2015-10-27 15:11:01 +00:00
|
|
|
int const MARGIN = 2 * theFontMetrics(d->cursor_.real_current_font).em()
|
|
|
|
+ row.right_margin;
|
2015-11-03 09:01:05 +00:00
|
|
|
if (row.right_x() <= workWidth() - row.right_margin) {
|
2015-10-27 15:11:01 +00:00
|
|
|
// Row is narrower than the work area, no offset needed.
|
|
|
|
offset = 0;
|
|
|
|
} else {
|
|
|
|
if (cur_x - offset < MARGIN) {
|
|
|
|
// cursor would be too far right
|
|
|
|
offset = cur_x - MARGIN;
|
|
|
|
} else if (cur_x - offset > workWidth() - MARGIN) {
|
|
|
|
// cursor would be too far left
|
|
|
|
offset = cur_x - workWidth() + MARGIN;
|
|
|
|
}
|
|
|
|
// Correct the offset to make sure that we do not scroll too much
|
|
|
|
if (offset < 0)
|
|
|
|
offset = 0;
|
2015-11-03 09:01:05 +00:00
|
|
|
if (row.right_x() - offset < workWidth() - row.right_margin)
|
|
|
|
offset = row.right_x() - workWidth() + row.right_margin;
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
}
|
|
|
|
|
2015-10-27 15:11:01 +00:00
|
|
|
//lyxerr << "cur_x=" << cur_x << ", offset=" << offset << ", row.wid=" << row.width() << ", margin=" << MARGIN << endl;
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
|
2022-01-17 14:14:05 +00:00
|
|
|
if (offset != d->horiz_scroll_offset_) {
|
2014-07-27 15:30:57 +00:00
|
|
|
LYXERR(Debug::PAINTING, "Horiz. scroll offset changed from "
|
|
|
|
<< d->horiz_scroll_offset_ << " to " << offset);
|
2022-01-17 14:14:05 +00:00
|
|
|
row.changed(true);
|
|
|
|
if (d->update_strategy_ == NoScreenUpdate)
|
|
|
|
d->update_strategy_ = SingleParUpdate;
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
d->horiz_scroll_offset_ = offset;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-05-03 09:52:27 +00:00
|
|
|
bool BufferView::busy() const
|
|
|
|
{
|
|
|
|
return buffer().undo().activeUndoGroup();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-07-20 21:31:05 +00:00
|
|
|
void BufferView::draw(frontend::Painter & pain, bool paint_caret)
|
2007-08-27 22:53:16 +00:00
|
|
|
{
|
2008-06-19 13:10:25 +00:00
|
|
|
if (height_ == 0 || width_ == 0)
|
|
|
|
return;
|
2017-10-11 16:00:48 +00:00
|
|
|
LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t--- START NODRAW ---"
|
|
|
|
: "\t\t*** START DRAWING ***"));
|
2007-11-06 14:07:49 +00:00
|
|
|
Text & text = buffer_.text();
|
|
|
|
TextMetrics const & tm = d->text_metrics_[&text];
|
2020-07-12 22:00:36 +00:00
|
|
|
int const y = tm.first().second->position();
|
2007-08-27 22:53:16 +00:00
|
|
|
PainterInfo pi(this, pain);
|
|
|
|
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
// Check whether the row where the cursor lives needs to be scrolled.
|
|
|
|
// Update the drawing strategy if needed.
|
2017-07-23 13:50:35 +00:00
|
|
|
checkCursorScrollOffset();
|
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.
2014-07-26 11:17:28 +00:00
|
|
|
|
2007-11-17 11:27:03 +00:00
|
|
|
switch (d->update_strategy_) {
|
2007-11-06 14:07:49 +00:00
|
|
|
|
|
|
|
case NoScreenUpdate:
|
2017-07-15 23:25:03 +00:00
|
|
|
// no screen painting is actually needed. In nodraw stage
|
|
|
|
// however, the different coordinates of insets and paragraphs
|
|
|
|
// needs to be updated.
|
2014-07-26 20:25:48 +00:00
|
|
|
LYXERR(Debug::PAINTING, "Strategy: NoScreenUpdate");
|
2017-07-20 21:31:05 +00:00
|
|
|
if (pain.isNull()) {
|
|
|
|
pi.full_repaint = true;
|
|
|
|
tm.draw(pi, 0, y);
|
2018-04-18 08:10:08 +00:00
|
|
|
} else {
|
2017-07-20 21:31:05 +00:00
|
|
|
pi.full_repaint = false;
|
2017-07-15 23:25:03 +00:00
|
|
|
tm.draw(pi, 0, y);
|
2017-07-20 21:31:05 +00:00
|
|
|
}
|
2007-11-06 14:07:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case SingleParUpdate:
|
|
|
|
pi.full_repaint = false;
|
2014-07-26 20:25:48 +00:00
|
|
|
LYXERR(Debug::PAINTING, "Strategy: SingleParUpdate");
|
2007-11-17 11:27:03 +00:00
|
|
|
// In general, only the current row of the outermost paragraph
|
|
|
|
// will be redrawn. Particular cases where selection spans
|
|
|
|
// multiple paragraph are correctly detected in TextMetrics.
|
2015-05-17 15:27:12 +00:00
|
|
|
tm.draw(pi, 0, y);
|
2007-11-06 14:07:49 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case DecorationUpdate:
|
|
|
|
// FIXME: We should also distinguish DecorationUpdate to avoid text
|
|
|
|
// drawing if possible. This is not possible to do easily right now
|
|
|
|
// because of the single backing pixmap.
|
|
|
|
|
|
|
|
case FullScreenUpdate:
|
2014-07-26 20:25:48 +00:00
|
|
|
|
|
|
|
LYXERR(Debug::PAINTING,
|
|
|
|
((d->update_strategy_ == FullScreenUpdate)
|
|
|
|
? "Strategy: FullScreenUpdate"
|
|
|
|
: "Strategy: DecorationUpdate"));
|
|
|
|
|
2007-11-06 14:07:49 +00:00
|
|
|
// The whole screen, including insets, will be refreshed.
|
|
|
|
pi.full_repaint = true;
|
|
|
|
|
2007-11-17 11:27:03 +00:00
|
|
|
// Clear background.
|
|
|
|
pain.fillRectangle(0, 0, width_, height_,
|
2008-10-25 10:47:38 +00:00
|
|
|
pi.backgroundColor(&buffer_.inset()));
|
2007-12-21 08:28:18 +00:00
|
|
|
|
|
|
|
// Draw everything.
|
2007-09-09 20:21:43 +00:00
|
|
|
tm.draw(pi, 0, y);
|
2007-08-27 22:53:16 +00:00
|
|
|
|
2007-11-06 14:07:49 +00:00
|
|
|
// and possibly grey out below
|
2020-07-12 22:00:36 +00:00
|
|
|
pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
|
|
|
|
int const y2 = lastpm.second->position() + lastpm.second->descent();
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2009-08-19 00:10:09 +00:00
|
|
|
if (y2 < height_) {
|
2014-07-25 20:35:08 +00:00
|
|
|
Color color = buffer().isInternal()
|
2009-09-09 04:58:48 +00:00
|
|
|
? Color_background : Color_bottomarea;
|
2009-08-19 00:10:09 +00:00
|
|
|
pain.fillRectangle(0, y2, width_, height_ - y2, color);
|
|
|
|
}
|
2007-11-06 14:07:49 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-10-11 16:00:48 +00:00
|
|
|
LYXERR(Debug::PAINTING, (pain.isNull() ? "\t\t --- END NODRAW ---"
|
|
|
|
: "\t\t *** END DRAWING ***"));
|
2007-12-21 08:28:18 +00:00
|
|
|
|
|
|
|
// The scrollbar needs an update.
|
2020-08-30 17:15:20 +00:00
|
|
|
// FIXME: does it always? see ticket #11947.
|
2021-10-29 15:44:32 +00:00
|
|
|
updateScrollbarParameters();
|
2007-12-21 08:28:18 +00:00
|
|
|
|
|
|
|
// Normalize anchor for next time
|
2020-07-12 22:00:36 +00:00
|
|
|
pair<pit_type, ParagraphMetrics const *> firstpm = tm.first();
|
|
|
|
pair<pit_type, ParagraphMetrics const *> lastpm = tm.last();
|
|
|
|
for (pit_type pit = firstpm.first; pit <= lastpm.first; ++pit) {
|
2007-12-21 08:28:18 +00:00
|
|
|
ParagraphMetrics const & pm = tm.parMetrics(pit);
|
|
|
|
if (pm.position() + pm.descent() > 0) {
|
2017-10-11 16:00:48 +00:00
|
|
|
if (d->anchor_pit_ != pit
|
|
|
|
|| d->anchor_ypos_ != pm.position())
|
|
|
|
LYXERR(Debug::PAINTING, "Found new anchor pit = " << d->anchor_pit_
|
|
|
|
<< " anchor ypos = " << d->anchor_ypos_);
|
2007-12-21 08:28:18 +00:00
|
|
|
d->anchor_pit_ = pit;
|
|
|
|
d->anchor_ypos_ = pm.position();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2017-10-11 16:00:48 +00:00
|
|
|
if (!pain.isNull()) {
|
|
|
|
// reset the update flags, everything has been done
|
|
|
|
d->update_flags_ = Update::None;
|
|
|
|
}
|
2017-07-20 21:31:05 +00:00
|
|
|
|
2018-04-18 08:10:08 +00:00
|
|
|
// If a caret has to be painted, mark its text row as dirty to
|
|
|
|
//make sure that it will be repainted on next redraw.
|
|
|
|
/* FIXME: investigate whether this can be avoided when the cursor did not
|
|
|
|
* move at all
|
|
|
|
*/
|
2017-11-23 14:38:17 +00:00
|
|
|
if (paint_caret) {
|
2020-11-21 19:00:26 +00:00
|
|
|
Cursor cur(d->cursor_);
|
|
|
|
while (cur.depth() > 1) {
|
2020-11-24 17:35:25 +00:00
|
|
|
if (!cur.inTexted())
|
|
|
|
break;
|
|
|
|
TextMetrics const & tm = textMetrics(cur.text());
|
|
|
|
if (d->caret_geometry_.left >= tm.origin().x_
|
|
|
|
&& d->caret_geometry_.right <= tm.origin().x_ + tm.dim().width())
|
2020-11-21 19:00:26 +00:00
|
|
|
break;
|
|
|
|
cur.pop();
|
|
|
|
}
|
|
|
|
cur.textRow().changed(true);
|
2018-04-18 08:10:08 +00:00
|
|
|
}
|
2007-08-27 22:53:16 +00:00
|
|
|
}
|
|
|
|
|
2007-10-02 18:27:20 +00:00
|
|
|
|
|
|
|
void BufferView::message(docstring const & msg)
|
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
if (d->gui_)
|
|
|
|
d->gui_->message(msg);
|
2007-10-02 18:27:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-12-12 19:28:07 +00:00
|
|
|
void BufferView::showDialog(string const & name)
|
2007-10-02 18:27:20 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
if (d->gui_)
|
2007-11-18 00:39:15 +00:00
|
|
|
d->gui_->showDialog(name, string());
|
2007-10-02 18:27:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-12-12 19:28:07 +00:00
|
|
|
void BufferView::showDialog(string const & name,
|
|
|
|
string const & data, Inset * inset)
|
2007-10-02 18:27:20 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
if (d->gui_)
|
2007-11-18 00:39:15 +00:00
|
|
|
d->gui_->showDialog(name, data, inset);
|
2007-10-02 18:27:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-12-12 19:28:07 +00:00
|
|
|
void BufferView::updateDialog(string const & name, string const & data)
|
2007-10-02 18:27:20 +00:00
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
if (d->gui_)
|
|
|
|
d->gui_->updateDialog(name, data);
|
2007-10-02 18:27:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void BufferView::setGuiDelegate(frontend::GuiBufferViewDelegate * gui)
|
|
|
|
{
|
2007-10-13 07:33:07 +00:00
|
|
|
d->gui_ = gui;
|
2007-10-02 18:27:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2007-10-03 20:15:49 +00:00
|
|
|
// FIXME: Move this out of BufferView again
|
2007-12-02 20:05:17 +00:00
|
|
|
docstring BufferView::contentsOfPlaintextFile(FileName const & fname)
|
2007-10-03 18:56:37 +00:00
|
|
|
{
|
2007-11-25 11:18:16 +00:00
|
|
|
if (!fname.isReadableFile()) {
|
2007-10-03 18:56:37 +00:00
|
|
|
docstring const error = from_ascii(strerror(errno));
|
2010-04-21 01:19:09 +00:00
|
|
|
docstring const file = makeDisplayPath(fname.absFileName(), 50);
|
2007-10-03 18:56:37 +00:00
|
|
|
docstring const text =
|
|
|
|
bformat(_("Could not read the specified document\n"
|
|
|
|
"%1$s\ndue to the error: %2$s"), file, error);
|
|
|
|
Alert::error(_("Could not read file"), text);
|
|
|
|
return docstring();
|
|
|
|
}
|
|
|
|
|
2007-12-02 11:55:25 +00:00
|
|
|
if (!fname.isReadableFile()) {
|
2010-04-21 01:19:09 +00:00
|
|
|
docstring const file = makeDisplayPath(fname.absFileName(), 50);
|
2007-10-03 18:56:37 +00:00
|
|
|
docstring const text =
|
2007-12-02 11:55:25 +00:00
|
|
|
bformat(_("%1$s\n is not readable."), file);
|
2007-10-03 18:56:37 +00:00
|
|
|
Alert::error(_("Could not open file"), text);
|
|
|
|
return docstring();
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME UNICODE: We don't know the encoding of the file
|
2007-12-02 11:55:25 +00:00
|
|
|
docstring file_content = fname.fileContents("UTF-8");
|
2007-10-03 18:56:37 +00:00
|
|
|
if (file_content.empty()) {
|
|
|
|
Alert::error(_("Reading not UTF-8 encoded file"),
|
|
|
|
_("The file is not UTF-8 encoded.\n"
|
|
|
|
"It will be read as local 8Bit-encoded.\n"
|
|
|
|
"If this does not give the correct result\n"
|
|
|
|
"then please change the encoding of the file\n"
|
|
|
|
"to UTF-8 with a program other than LyX.\n"));
|
2007-12-02 11:55:25 +00:00
|
|
|
file_content = fname.fileContents("local8bit");
|
2007-10-03 18:56:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return normalize_c(file_content);
|
|
|
|
}
|
|
|
|
|
2007-10-03 20:15:49 +00:00
|
|
|
|
2007-12-02 11:55:25 +00:00
|
|
|
void BufferView::insertPlaintextFile(FileName const & f, bool asParagraph)
|
2007-10-03 18:56:37 +00:00
|
|
|
{
|
2007-12-02 20:05:17 +00:00
|
|
|
docstring const tmpstr = contentsOfPlaintextFile(f);
|
2007-10-03 18:56:37 +00:00
|
|
|
|
|
|
|
if (tmpstr.empty())
|
|
|
|
return;
|
|
|
|
|
|
|
|
Cursor & cur = cursor();
|
|
|
|
cap::replaceSelection(cur);
|
2007-10-18 11:51:17 +00:00
|
|
|
buffer_.undo().recordUndo(cur);
|
2007-10-03 18:56:37 +00:00
|
|
|
if (asParagraph)
|
2009-08-09 16:49:41 +00:00
|
|
|
cur.innerText()->insertStringAsParagraphs(cur, tmpstr, cur.current_font);
|
2007-10-03 18:56:37 +00:00
|
|
|
else
|
2009-08-09 16:49:41 +00:00
|
|
|
cur.innerText()->insertStringAsLines(cur, tmpstr, cur.current_font);
|
2007-12-02 20:05:17 +00:00
|
|
|
|
2010-01-08 02:03:54 +00:00
|
|
|
buffer_.changed(true);
|
2007-10-03 18:56:37 +00:00
|
|
|
}
|
|
|
|
|
2008-02-21 19:42:34 +00:00
|
|
|
|
|
|
|
docstring const & BufferView::inlineCompletion() const
|
|
|
|
{
|
2008-04-16 20:43:30 +00:00
|
|
|
return d->inlineCompletion_;
|
2008-02-21 19:42:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-10-16 07:19:34 +00:00
|
|
|
size_t BufferView::inlineCompletionUniqueChars() const
|
2008-02-21 19:42:34 +00:00
|
|
|
{
|
2008-04-16 20:43:30 +00:00
|
|
|
return d->inlineCompletionUniqueChars_;
|
2008-02-21 19:42:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
DocIterator const & BufferView::inlineCompletionPos() const
|
|
|
|
{
|
2008-04-16 20:43:30 +00:00
|
|
|
return d->inlineCompletionPos_;
|
2008-02-21 19:42:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-02-21 15:26:28 +00:00
|
|
|
void BufferView::resetInlineCompletionPos()
|
2010-11-30 03:54:29 +00:00
|
|
|
{
|
2011-02-21 15:26:28 +00:00
|
|
|
d->inlineCompletionPos_ = DocIterator();
|
2010-11-30 03:54:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-02-21 19:43:16 +00:00
|
|
|
bool samePar(DocIterator const & a, DocIterator const & b)
|
|
|
|
{
|
|
|
|
if (a.empty() && b.empty())
|
|
|
|
return true;
|
|
|
|
if (a.empty() || b.empty())
|
|
|
|
return false;
|
2009-02-25 00:39:55 +00:00
|
|
|
if (a.depth() != b.depth())
|
|
|
|
return false;
|
2008-02-21 19:43:16 +00:00
|
|
|
return &a.innerParagraph() == &b.innerParagraph();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-07-25 20:35:08 +00:00
|
|
|
void BufferView::setInlineCompletion(Cursor const & cur, DocIterator const & pos,
|
2008-02-21 19:42:34 +00:00
|
|
|
docstring const & completion, size_t uniqueChars)
|
|
|
|
{
|
2008-02-21 19:43:16 +00:00
|
|
|
uniqueChars = min(completion.size(), uniqueChars);
|
2008-04-16 20:43:30 +00:00
|
|
|
bool changed = d->inlineCompletion_ != completion
|
|
|
|
|| d->inlineCompletionUniqueChars_ != uniqueChars;
|
2008-02-21 19:43:16 +00:00
|
|
|
bool singlePar = true;
|
2008-04-16 20:43:30 +00:00
|
|
|
d->inlineCompletion_ = completion;
|
|
|
|
d->inlineCompletionUniqueChars_ = min(completion.size(), uniqueChars);
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2008-02-23 03:13:54 +00:00
|
|
|
//lyxerr << "setInlineCompletion pos=" << pos << " completion=" << completion << " uniqueChars=" << uniqueChars << std::endl;
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2008-02-21 19:43:16 +00:00
|
|
|
// at new position?
|
2008-04-16 20:43:30 +00:00
|
|
|
DocIterator const & old = d->inlineCompletionPos_;
|
2008-02-21 19:43:16 +00:00
|
|
|
if (old != pos) {
|
2008-02-23 03:13:54 +00:00
|
|
|
//lyxerr << "inlineCompletionPos changed" << std::endl;
|
2008-02-21 19:43:16 +00:00
|
|
|
// old or pos are in another paragraph?
|
|
|
|
if ((!samePar(cur, pos) && !pos.empty())
|
|
|
|
|| (!samePar(cur, old) && !old.empty())) {
|
|
|
|
singlePar = false;
|
2008-02-23 03:13:54 +00:00
|
|
|
//lyxerr << "different paragraph" << std::endl;
|
2008-02-21 19:43:16 +00:00
|
|
|
}
|
2008-04-16 20:43:30 +00:00
|
|
|
d->inlineCompletionPos_ = pos;
|
2008-02-21 19:43:16 +00:00
|
|
|
}
|
2014-07-25 20:35:08 +00:00
|
|
|
|
2008-02-21 19:43:16 +00:00
|
|
|
// set update flags
|
|
|
|
if (changed) {
|
2010-10-13 17:28:55 +00:00
|
|
|
if (singlePar && !(cur.result().screenUpdate() & Update::Force))
|
|
|
|
cur.screenUpdateFlags(cur.result().screenUpdate() | Update::SinglePar);
|
2008-02-26 14:22:56 +00:00
|
|
|
else
|
2010-10-13 17:28:55 +00:00
|
|
|
cur.screenUpdateFlags(cur.result().screenUpdate() | Update::Force);
|
2008-02-21 19:43:16 +00:00
|
|
|
}
|
2008-02-21 19:42:34 +00:00
|
|
|
}
|
|
|
|
|
2010-10-24 21:41:47 +00:00
|
|
|
|
|
|
|
bool BufferView::clickableInset() const
|
2014-07-25 20:35:08 +00:00
|
|
|
{
|
|
|
|
return d->clickable_inset_;
|
2010-10-24 21:41:47 +00:00
|
|
|
}
|
|
|
|
|
2006-10-21 00:16:43 +00:00
|
|
|
} // namespace lyx
|