lyx_mirror/src/BufferView.cpp

1649 lines
41 KiB
C++
Raw Normal View History

/**
* \file BufferView.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author Alfredo Braunstein
* \author Lars Gullik Bj<EFBFBD>nnes
* \author John Levon
* \author Andr<EFBFBD> P<EFBFBD>nitz
* \author J<EFBFBD>rgen Vigna
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "BufferView.h"
#include "Buffer.h"
#include "buffer_funcs.h"
#include "BufferList.h"
#include "BufferParams.h"
#include "bufferview_funcs.h"
#include "callback.h" // added for Dispatch functions
#include "CoordCache.h"
#include "CutAndPaste.h"
#include "debug.h"
#include "DispatchResult.h"
#include "ErrorList.h"
#include "factory.h"
#include "FloatList.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "gettext.h"
#include "Intl.h"
#include "InsetIterator.h"
#include "Language.h"
#include "LaTeXFeatures.h"
#include "LyX.h"
#include "lyxfind.h"
#include "LyXFunc.h"
#include "Layout.h"
#include "LyXRC.h"
#include "MetricsInfo.h"
#include "Paragraph.h"
#include "paragraph_funcs.h"
#include "ParagraphParameters.h"
#include "ParIterator.h"
#include "Session.h"
#include "TexRow.h"
#include "Text.h"
#include "TextClass.h"
#include "toc.h"
#include "Undo.h"
#include "VSpace.h"
#include "WordLangTuple.h"
#include "insets/InsetBibtex.h"
#include "insets/InsetCommand.h" // ChangeRefs
#include "insets/InsetRef.h"
#include "insets/InsetText.h"
#include "frontends/alert.h"
#include "frontends/FileDialog.h"
#include "frontends/FontMetrics.h"
#include "frontends/Painter.h"
#include "frontends/Selection.h"
#include "graphics/Previews.h"
#include "support/convert.h"
Rename files in src/support, step one. src/support/package.h src/support/Package.h Package src/support/package.C.in src/support/Package.C.in Package src/support/path.h src/support/Path.h Path src/support/fs_extras.h src/support/fs_extras.h NOCLASSES src/support/RandomAccessList.h src/support/RandomAccessList.h RandomAccessList src/support/lyxmanip.h src/support/lyxmanip.h NOCLASSES src/support/rename.C src/support/rename.cpp NOCLASSES src/support/abort.C src/support/abort.cpp NOCLASSES src/support/lyxlib.h src/support/lyxlib.h NOCLASSES src/support/ExceptionMessage.h src/support/ExceptionMessage.h ExceptionMessage src/support/copy.C src/support/copy.cpp NOCLASSES src/support/limited_stack.h src/support/limited_stack.h limited_stack src/support/filefilterlist.C src/support/FileFilterList.cpp ['FileFilterList', 'Filter'] src/support/cow_ptr.h src/support/cow_ptr.h cow_ptr src/support/os_unix.C src/support/os_unix.cpp NOCLASSES src/support/socktools.h src/support/socktools.h NOCLASSES src/support/forkedcontr.h src/support/ForkedcallsController.h ForkedcallsController src/support/os.h src/support/os.h NOCLASSES src/support/FileMonitor.h src/support/FileMonitor.h FileMonitor src/support/copied_ptr.h src/support/copied_ptr.h copied_ptr src/support/translator.h src/support/Translator.h Translator src/support/filetools.C src/support/filetools.cpp NOCLASSES src/support/unlink.C src/support/unlink.cpp NOCLASSES src/support/os_win32.C src/support/os_win32.cpp GetFolderPath src/support/lstrings.C src/support/lstrings.cpp NOCLASSES src/support/qstring_helpers.C src/support/qstring_helpers.cpp NOCLASSES src/support/getcwd.C src/support/getcwd.cpp NOCLASSES src/support/systemcall.C src/support/Systemcall.cpp Systemcall src/support/lyxalgo.h src/support/lyxalgo.h NOCLASSES src/support/filefilterlist.h src/support/FileFilterList.h ['FileFilterList', 'Filter'] src/support/unicode.C src/support/unicode.cpp IconvProcessor src/support/userinfo.C src/support/userinfo.cpp NOCLASSES src/support/lyxtime.C src/support/lyxtime.cpp NOCLASSES src/support/kill.C src/support/kill.cpp NOCLASSES src/support/docstring.C src/support/docstring.cpp to_local8bit_failure src/support/os_cygwin.C src/support/os_cygwin.cpp NOCLASSES src/support/lyxsum.C src/support/lyxsum.cpp NOCLASSES src/support/environment.C src/support/environment.cpp NOCLASSES src/support/filetools.h src/support/filetools.h NOCLASSES src/support/textutils.C src/support/textutils.cpp NOCLASSES src/support/mkdir.C src/support/mkdir.cpp NOCLASSES src/support/forkedcall.C src/support/Forkedcall.cpp ['ForkedProcess', 'Forkedcall'] src/support/tempname.C src/support/tempname.cpp NOCLASSES src/support/os_win32.h src/support/os_win32.h GetFolderPath src/support/types.h src/support/types.h NOCLASSES src/support/lstrings.h src/support/lstrings.h NOCLASSES src/support/forkedcallqueue.C src/support/ForkedCallQueue.cpp ForkedCallQueue src/support/qstring_helpers.h src/support/qstring_helpers.h NOCLASSES src/support/convert.C src/support/convert.cpp NOCLASSES src/support/filename.C src/support/FileName.cpp ['FileName', 'DocFileName'] src/support/tests/convert.C src/support/tests/convert.cpp NOCLASSES src/support/tests/filetools.C src/support/tests/filetools.cpp NOCLASSES src/support/tests/lstrings.C src/support/tests/lstrings.cpp NOCLASSES src/support/tests/boost.C src/support/tests/boost.cpp NOCLASSES src/support/docstream.C src/support/docstream.cpp ['iconv_codecvt_facet_exception', 'idocfstream', 'odocfstream'] src/support/std_istream.h src/support/std_istream.h NOCLASSES src/support/systemcall.h src/support/Systemcall.h Systemcall src/support/chdir.C src/support/chdir.cpp NOCLASSES src/support/std_ostream.h src/support/std_ostream.h NOCLASSES src/support/unicode.h src/support/unicode.h IconvProcessor src/support/path.C src/support/Path.cpp Path src/support/fs_extras.C src/support/fs_extras.cpp NOCLASSES src/support/userinfo.h src/support/userinfo.h NOCLASSES src/support/lyxtime.h src/support/lyxtime.h NOCLASSES src/support/docstring.h src/support/docstring.h to_local8bit_failure src/support/debugstream.h src/support/debugstream.h basic_debugstream src/support/environment.h src/support/environment.h NOCLASSES src/support/textutils.h src/support/textutils.h NOCLASSES src/support/forkedcall.h src/support/Forkedcall.h ['ForkedProcess', 'Forkedcall'] src/support/socktools.C src/support/socktools.cpp NOCLASSES src/support/forkedcallqueue.h src/support/ForkedCallQueue.h ForkedCallQueue src/support/forkedcontr.C src/support/ForkedcallsController.cpp ForkedcallsController src/support/os.C src/support/os.cpp NOCLASSES src/support/convert.h src/support/convert.h NOCLASSES src/support/filename.h src/support/FileName.h ['FileName', 'DocFileName'] src/support/docstream.h src/support/docstream.h ['iconv_codecvt_facet_exception', 'idocfstream', 'odocfstream'] src/support/FileMonitor.C src/support/FileMonitor.cpp FileMonitor git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@18024 a592a061-630c-0410-9148-cb99ea01b6c8
2007-04-26 05:12:52 +00:00
#include "support/FileFilterList.h"
#include "support/filetools.h"
Rename files in src/support, step one. src/support/package.h src/support/Package.h Package src/support/package.C.in src/support/Package.C.in Package src/support/path.h src/support/Path.h Path src/support/fs_extras.h src/support/fs_extras.h NOCLASSES src/support/RandomAccessList.h src/support/RandomAccessList.h RandomAccessList src/support/lyxmanip.h src/support/lyxmanip.h NOCLASSES src/support/rename.C src/support/rename.cpp NOCLASSES src/support/abort.C src/support/abort.cpp NOCLASSES src/support/lyxlib.h src/support/lyxlib.h NOCLASSES src/support/ExceptionMessage.h src/support/ExceptionMessage.h ExceptionMessage src/support/copy.C src/support/copy.cpp NOCLASSES src/support/limited_stack.h src/support/limited_stack.h limited_stack src/support/filefilterlist.C src/support/FileFilterList.cpp ['FileFilterList', 'Filter'] src/support/cow_ptr.h src/support/cow_ptr.h cow_ptr src/support/os_unix.C src/support/os_unix.cpp NOCLASSES src/support/socktools.h src/support/socktools.h NOCLASSES src/support/forkedcontr.h src/support/ForkedcallsController.h ForkedcallsController src/support/os.h src/support/os.h NOCLASSES src/support/FileMonitor.h src/support/FileMonitor.h FileMonitor src/support/copied_ptr.h src/support/copied_ptr.h copied_ptr src/support/translator.h src/support/Translator.h Translator src/support/filetools.C src/support/filetools.cpp NOCLASSES src/support/unlink.C src/support/unlink.cpp NOCLASSES src/support/os_win32.C src/support/os_win32.cpp GetFolderPath src/support/lstrings.C src/support/lstrings.cpp NOCLASSES src/support/qstring_helpers.C src/support/qstring_helpers.cpp NOCLASSES src/support/getcwd.C src/support/getcwd.cpp NOCLASSES src/support/systemcall.C src/support/Systemcall.cpp Systemcall src/support/lyxalgo.h src/support/lyxalgo.h NOCLASSES src/support/filefilterlist.h src/support/FileFilterList.h ['FileFilterList', 'Filter'] src/support/unicode.C src/support/unicode.cpp IconvProcessor src/support/userinfo.C src/support/userinfo.cpp NOCLASSES src/support/lyxtime.C src/support/lyxtime.cpp NOCLASSES src/support/kill.C src/support/kill.cpp NOCLASSES src/support/docstring.C src/support/docstring.cpp to_local8bit_failure src/support/os_cygwin.C src/support/os_cygwin.cpp NOCLASSES src/support/lyxsum.C src/support/lyxsum.cpp NOCLASSES src/support/environment.C src/support/environment.cpp NOCLASSES src/support/filetools.h src/support/filetools.h NOCLASSES src/support/textutils.C src/support/textutils.cpp NOCLASSES src/support/mkdir.C src/support/mkdir.cpp NOCLASSES src/support/forkedcall.C src/support/Forkedcall.cpp ['ForkedProcess', 'Forkedcall'] src/support/tempname.C src/support/tempname.cpp NOCLASSES src/support/os_win32.h src/support/os_win32.h GetFolderPath src/support/types.h src/support/types.h NOCLASSES src/support/lstrings.h src/support/lstrings.h NOCLASSES src/support/forkedcallqueue.C src/support/ForkedCallQueue.cpp ForkedCallQueue src/support/qstring_helpers.h src/support/qstring_helpers.h NOCLASSES src/support/convert.C src/support/convert.cpp NOCLASSES src/support/filename.C src/support/FileName.cpp ['FileName', 'DocFileName'] src/support/tests/convert.C src/support/tests/convert.cpp NOCLASSES src/support/tests/filetools.C src/support/tests/filetools.cpp NOCLASSES src/support/tests/lstrings.C src/support/tests/lstrings.cpp NOCLASSES src/support/tests/boost.C src/support/tests/boost.cpp NOCLASSES src/support/docstream.C src/support/docstream.cpp ['iconv_codecvt_facet_exception', 'idocfstream', 'odocfstream'] src/support/std_istream.h src/support/std_istream.h NOCLASSES src/support/systemcall.h src/support/Systemcall.h Systemcall src/support/chdir.C src/support/chdir.cpp NOCLASSES src/support/std_ostream.h src/support/std_ostream.h NOCLASSES src/support/unicode.h src/support/unicode.h IconvProcessor src/support/path.C src/support/Path.cpp Path src/support/fs_extras.C src/support/fs_extras.cpp NOCLASSES src/support/userinfo.h src/support/userinfo.h NOCLASSES src/support/lyxtime.h src/support/lyxtime.h NOCLASSES src/support/docstring.h src/support/docstring.h to_local8bit_failure src/support/debugstream.h src/support/debugstream.h basic_debugstream src/support/environment.h src/support/environment.h NOCLASSES src/support/textutils.h src/support/textutils.h NOCLASSES src/support/forkedcall.h src/support/Forkedcall.h ['ForkedProcess', 'Forkedcall'] src/support/socktools.C src/support/socktools.cpp NOCLASSES src/support/forkedcallqueue.h src/support/ForkedCallQueue.h ForkedCallQueue src/support/forkedcontr.C src/support/ForkedcallsController.cpp ForkedcallsController src/support/os.C src/support/os.cpp NOCLASSES src/support/convert.h src/support/convert.h NOCLASSES src/support/filename.h src/support/FileName.h ['FileName', 'DocFileName'] src/support/docstream.h src/support/docstream.h ['iconv_codecvt_facet_exception', 'idocfstream', 'odocfstream'] src/support/FileMonitor.C src/support/FileMonitor.cpp FileMonitor git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@18024 a592a061-630c-0410-9148-cb99ea01b6c8
2007-04-26 05:12:52 +00:00
#include "support/Package.h"
#include "support/types.h"
#include <boost/bind.hpp>
#include <boost/current_function.hpp>
#include <functional>
#include <vector>
using std::distance;
using std::endl;
using std::istringstream;
using std::make_pair;
using std::min;
using std::max;
using std::mem_fun_ref;
using std::string;
using std::vector;
namespace lyx {
using support::addPath;
using support::bformat;
using support::FileFilterList;
using support::FileName;
using support::fileSearch;
using support::isDirWriteable;
using support::isFileReadable;
using support::makeDisplayPath;
using support::package;
namespace Alert = frontend::Alert;
namespace {
/// Return an inset of this class if it exists at the current cursor position
template <class T>
T * getInsetByCode(Cursor & cur, Inset::Code code)
{
T * inset = 0;
DocIterator it = cur;
if (it.nextInset() &&
it.nextInset()->lyxCode() == code) {
inset = static_cast<T*>(it.nextInset());
}
return inset;
}
} // anon namespace
BufferView::BufferView(Buffer & buf)
: width_(0), height_(0), buffer_(buf), wh_(0),
cursor_(*this),
multiparsel_cache_(false), anchor_ref_(0), offset_ref_(0),
need_centering_(false), intl_(new Intl), last_inset_(0)
{
xsel_cache_.set = false;
intl_->initKeyMapper(lyxrc.use_kbmap);
cursor_.push(buffer_.inset());
cursor_.resetAnchor();
cursor_.setCurrentFont();
if (graphics::Previews::status() != LyXRC::PREVIEW_OFF)
graphics::Previews::get().generateBufferPreviews(buffer_);
}
BufferView::~BufferView()
{
// 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.
LyX::ref().session().lastFilePos().save(
support::FileName(buffer_.fileName()),
boost::tie(cursor_.bottom().pit(), cursor_.bottom().pos()) );
}
Buffer & BufferView::buffer()
{
return buffer_;
}
Buffer const & BufferView::buffer() const
{
return buffer_;
}
bool BufferView::fitCursor()
{
if (bv_funcs::status(this, cursor_) == bv_funcs::CUR_INSIDE) {
frontend::FontMetrics const & fm =
theFontMetrics(cursor_.getFont());
int const asc = fm.maxAscent();
int const des = fm.maxDescent();
Point const p = bv_funcs::getPos(*this, cursor_, cursor_.boundary());
if (p.y_ - asc >= 0 && p.y_ + des < height_)
return false;
}
center();
return true;
}
bool BufferView::multiParSel()
{
if (!cursor_.selection())
return false;
bool ret = multiparsel_cache_;
multiparsel_cache_ = cursor_.selBegin().pit() != cursor_.selEnd().pit();
// Either this, or previous selection spans paragraphs
return ret || multiparsel_cache_;
}
bool BufferView::update(Update::flags flags)
{
// last_inset_ points to the last visited inset. This pointer may become
// invalid because of keyboard editing. Since all such operations
// causes screen update(), I reset last_inset_ to avoid such a problem.
last_inset_ = 0;
// This is close to a hot-path.
LYXERR(Debug::DEBUG)
<< BOOST_CURRENT_FUNCTION
<< "[fitcursor = " << (flags & Update::FitCursor)
<< ", forceupdate = " << (flags & Update::Force)
<< ", singlepar = " << (flags & Update::SinglePar)
<< "] buffer: " << &buffer_ << endl;
// Update macro store
if (!(cursor().inMathed() && cursor().inMacroMode()))
buffer_.buildMacros();
// Now do the first drawing step if needed. This consists on updating
// the CoordCache in updateMetrics().
// The second drawing step is done in WorkArea::redraw() if needed.
// Case when no explicit update is requested.
if (!flags) {
// no need to redraw anything.
metrics_info_.update_strategy = NoScreenUpdate;
return false;
}
if (flags == Update::Decoration) {
metrics_info_.update_strategy = DecorationUpdate;
return true;
}
if (flags == Update::FitCursor
|| flags == (Update::Decoration | Update::FitCursor)) {
bool const fit_cursor = fitCursor();
// tell the frontend to update the screen if needed.
if (fit_cursor) {
updateMetrics(false);
return true;
}
if (flags & Update::Decoration) {
metrics_info_.update_strategy = DecorationUpdate;
return true;
}
// no screen update is needed.
metrics_info_.update_strategy = NoScreenUpdate;
return false;
}
bool full_metrics = flags & Update::Force;
if (flags & Update::MultiParSel)
full_metrics |= multiParSel();
bool const single_par = !full_metrics;
updateMetrics(single_par);
if (flags & Update::FitCursor && fitCursor())
updateMetrics(false);
// tell the frontend to update the screen.
return true;
}
void BufferView::updateScrollbar()
{
Text & t = buffer_.text();
TextMetrics & tm = text_metrics_[&t];
int const parsize = int(t.paragraphs().size() - 1);
if (anchor_ref_ > parsize) {
anchor_ref_ = parsize;
offset_ref_ = 0;
}
LYXERR(Debug::GUI)
<< BOOST_CURRENT_FUNCTION
<< " Updating scrollbar: height: " << t.paragraphs().size()
<< " curr par: " << cursor_.bottom().pit()
<< " default height " << defaultRowHeight() << endl;
// It would be better to fix the scrollbar to understand
// values in [0..1] and divide everything by wh
// estimated average paragraph height:
if (wh_ == 0)
wh_ = height_ / 4;
int h = tm.parMetrics(anchor_ref_).height();
// Normalize anchor/offset (MV):
while (offset_ref_ > h && anchor_ref_ < parsize) {
anchor_ref_++;
offset_ref_ -= h;
h = tm.parMetrics(anchor_ref_).height();
}
// Look at paragraph heights on-screen
int sumh = 0;
int nh = 0;
for (pit_type pit = anchor_ref_; pit <= parsize; ++pit) {
if (sumh > height_)
break;
int const h2 = tm.parMetrics(pit).height();
sumh += h2;
nh++;
}
BOOST_ASSERT(nh);
int const hav = sumh / nh;
// More realistic average paragraph height
if (hav > wh_)
wh_ = hav;
BOOST_ASSERT(h);
scrollbarParameters_.height = (parsize + 1) * wh_;
scrollbarParameters_.position = anchor_ref_ * wh_ + int(offset_ref_ * wh_ / float(h));
scrollbarParameters_.lineScrollHeight = int(wh_ * defaultRowHeight() / float(h));
}
ScrollbarParameters const & BufferView::scrollbarParameters() const
{
return scrollbarParameters_;
}
void BufferView::scrollDocView(int value)
{
LYXERR(Debug::GUI) << BOOST_CURRENT_FUNCTION
<< "[ value = " << value << "]" << endl;
Text & t = buffer_.text();
TextMetrics & tm = text_metrics_[&t];
float const bar = value / float(wh_ * t.paragraphs().size());
anchor_ref_ = int(bar * t.paragraphs().size());
if (anchor_ref_ > int(t.paragraphs().size()) - 1)
anchor_ref_ = int(t.paragraphs().size()) - 1;
tm.redoParagraph(anchor_ref_);
int const h = tm.parMetrics(anchor_ref_).height();
offset_ref_ = int((bar * t.paragraphs().size() - anchor_ref_) * h);
updateMetrics(false);
}
void BufferView::setCursorFromScrollbar()
{
TextMetrics & tm = text_metrics_[&buffer_.text()];
int const height = 2 * defaultRowHeight();
int const first = height;
int const last = height_ - height;
Cursor & cur = cursor_;
bv_funcs::CurStatus st = bv_funcs::status(this, cur);
switch (st) {
case bv_funcs::CUR_ABOVE:
// We reset the cursor because bv_funcs::status() does not
// work when the cursor is within mathed.
cur.reset(buffer_.inset());
tm.setCursorFromCoordinates(cur, 0, first);
cur.clearSelection();
break;
case bv_funcs::CUR_BELOW:
// We reset the cursor because bv_funcs::status() does not
// work when the cursor is within mathed.
cur.reset(buffer_.inset());
tm.setCursorFromCoordinates(cur, 0, last);
cur.clearSelection();
break;
case bv_funcs::CUR_INSIDE:
int const y = bv_funcs::getPos(*this, cur, cur.boundary()).y_;
int const newy = min(last, max(y, first));
if (y != newy) {
cur.reset(buffer_.inset());
tm.setCursorFromCoordinates(cur, 0, newy);
}
}
}
Change const BufferView::getCurrentChange() const
{
if (!cursor_.selection())
return Change(Change::UNCHANGED);
DocIterator dit = cursor_.selectionBegin();
return dit.paragraph().lookupChange(dit.pos());
}
void BufferView::saveBookmark(unsigned int idx)
{
// tenatively save bookmark, id and pos will be used to
// acturately locate a bookmark in a 'live' lyx session.
// pit and pos will be updated with bottom level pit/pos
// when lyx exits.
LyX::ref().session().bookmarks().save(
FileName(buffer_.fileName()),
cursor_.bottom().pit(),
cursor_.bottom().pos(),
cursor_.paragraph().id(),
cursor_.pos(),
idx
);
if (idx)
// emit message signal.
message(_("Save bookmark"));
}
bool BufferView::moveToPosition(pit_type bottom_pit, pos_type bottom_pos,
int top_id, pos_type top_pos)
{
bool success = false;
DocIterator doc_it;
cursor_.clearSelection();
// if a valid par_id is given, try it first
// This is the case for a 'live' bookmark when unique paragraph ID
// is used to track bookmarks.
if (top_id > 0) {
ParIterator par = buffer_.getParFromID(top_id);
if (par != buffer_.par_iterator_end()) {
doc_it = makeDocIterator(par, min(par->size(), top_pos));
// Some slices of the iterator may not be
// reachable (e.g. closed collapsable inset)
// so the dociterator may need to be
// shortened. Otherwise, setCursor may crash
// lyx when the cursor can not be set to these
// insets.
size_t const n = doc_it.depth();
for (size_t i = 0; i < n; ++i)
if (doc_it[i].inset().editable() != Inset::HIGHLY_EDITABLE) {
doc_it.resize(i);
break;
}
success = true;
}
}
// if top_id == 0, or searching through top_id failed
// This is the case for a 'restored' bookmark when only bottom
// (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.
if (static_cast<size_t>(bottom_pit) < buffer_.paragraphs().size()) {
doc_it = doc_iterator_begin(buffer_.inset());
doc_it.pit() = bottom_pit;
doc_it.pos() = min(bottom_pos, doc_it.paragraph().size());
success = true;
}
if (success) {
// Note: only bottom (document) level pit is set.
setCursor(doc_it);
// set the current font.
cursor_.setCurrentFont();
// center the screen on this new position.
center();
}
return success;
}
void BufferView::translateAndInsert(char_type c, Text * t, Cursor & cur)
{
if (lyxrc.rtl_support) {
if (cursor_.real_current_font.isRightToLeft()) {
if (intl_->keymap == Intl::PRIMARY)
intl_->keyMapSec();
} else {
if (intl_->keymap == Intl::SECONDARY)
intl_->keyMapPrim();
}
}
intl_->getTransManager().translateAndInsert(c, t, cur);
}
int BufferView::workWidth() const
{
return width_;
}
void BufferView::updateOffsetRef()
{
// No need to update offset_ref_ in this case.
if (!need_centering_)
return;
// We are not properly started yet, delay until resizing is
// done.
if (height_ == 0)
return;
CursorSlice & bot = cursor_.bottom();
TextMetrics & tm = text_metrics_[bot.text()];
ParagraphMetrics const & pm = tm.parMetrics(bot.pit());
Point p = bv_funcs::coordOffset(*this, cursor_, cursor_.boundary());
offset_ref_ = p.y_ + pm.ascent() - height_ / 2;
need_centering_ = false;
}
void BufferView::center()
{
anchor_ref_ = cursor_.bottom().pit();
need_centering_ = true;
}
FuncStatus BufferView::getStatus(FuncRequest const & cmd)
{
FuncStatus flag;
Cursor & cur = cursor_;
switch (cmd.action) {
case LFUN_UNDO:
flag.enabled(!buffer_.undostack().empty());
break;
case LFUN_REDO:
flag.enabled(!buffer_.redostack().empty());
break;
case LFUN_FILE_INSERT:
case LFUN_FILE_INSERT_PLAINTEXT_PARA:
case LFUN_FILE_INSERT_PLAINTEXT:
case LFUN_BOOKMARK_SAVE:
// FIXME: Actually, these LFUNS should be moved to Text
flag.enabled(cur.inTexted());
break;
case LFUN_FONT_STATE:
case LFUN_LABEL_INSERT:
case LFUN_PARAGRAPH_GOTO:
// FIXME handle non-trivially
case LFUN_OUTLINE_UP:
case LFUN_OUTLINE_DOWN:
case LFUN_OUTLINE_IN:
case LFUN_OUTLINE_OUT:
case LFUN_NOTE_NEXT:
case LFUN_REFERENCE_NEXT:
case LFUN_WORD_FIND:
case LFUN_WORD_REPLACE:
case LFUN_MARK_OFF:
case LFUN_MARK_ON:
case LFUN_MARK_TOGGLE:
case LFUN_SCREEN_RECENTER:
case LFUN_BIBTEX_DATABASE_ADD:
case LFUN_BIBTEX_DATABASE_DEL:
case LFUN_WORDS_COUNT:
case LFUN_NEXT_INSET_TOGGLE:
flag.enabled(true);
break;
case LFUN_LABEL_GOTO: {
flag.enabled(!cmd.argument().empty()
|| getInsetByCode<InsetRef>(cur, Inset::REF_CODE));
break;
}
case LFUN_CHANGES_TRACK:
flag.enabled(true);
flag.setOnOff(buffer_.params().trackChanges);
break;
case LFUN_CHANGES_OUTPUT:
flag.enabled(true);
flag.setOnOff(buffer_.params().outputChanges);
break;
case LFUN_CHANGES_MERGE:
case LFUN_CHANGE_NEXT:
case LFUN_ALL_CHANGES_ACCEPT:
case LFUN_ALL_CHANGES_REJECT:
// TODO: context-sensitive enabling of LFUNs
// In principle, these command should only be enabled if there
// is a change in the document. However, without proper
// optimizations, this will inevitably result in poor performance.
flag.enabled(true);
break;
case LFUN_BUFFER_TOGGLE_COMPRESSION: {
flag.setOnOff(buffer_.params().compressed);
break;
}
case LFUN_SCREEN_UP:
case LFUN_SCREEN_DOWN:
flag.enabled(true);
break;
// FIXME: LFUN_SCREEN_DOWN_SELECT should be removed from
// everywhere else before this can enabled:
case LFUN_SCREEN_UP_SELECT:
case LFUN_SCREEN_DOWN_SELECT:
flag.enabled(false);
break;
default:
flag.enabled(false);
}
return flag;
}
Update::flags BufferView::dispatch(FuncRequest const & cmd)
{
//lyxerr << BOOST_CURRENT_FUNCTION
// << [ cmd = " << cmd << "]" << endl;
// Make sure that the cached BufferView is correct.
LYXERR(Debug::ACTION) << BOOST_CURRENT_FUNCTION
<< " action[" << cmd.action << ']'
<< " arg[" << to_utf8(cmd.argument()) << ']'
<< " x[" << cmd.x << ']'
<< " y[" << cmd.y << ']'
<< " button[" << cmd.button() << ']'
<< endl;
Cursor & cur = cursor_;
// Default Update flags.
Update::flags updateFlags = Update::Force | Update::FitCursor;
switch (cmd.action) {
case LFUN_UNDO:
cur.message(_("Undo"));
cur.clearSelection();
if (!textUndo(*this)) {
cur.message(_("No further undo information"));
updateFlags = Update::None;
}
break;
case LFUN_REDO:
cur.message(_("Redo"));
cur.clearSelection();
if (!textRedo(*this)) {
cur.message(_("No further redo information"));
updateFlags = Update::None;
}
break;
case LFUN_FILE_INSERT:
// FIXME UNICODE
menuInsertLyXFile(to_utf8(cmd.argument()));
break;
case LFUN_FILE_INSERT_PLAINTEXT_PARA:
// FIXME UNICODE
insertPlaintextFile(this, to_utf8(cmd.argument()), true);
break;
case LFUN_FILE_INSERT_PLAINTEXT:
// FIXME UNICODE
insertPlaintextFile(this, to_utf8(cmd.argument()), false);
break;
case LFUN_FONT_STATE:
cur.message(cur.currentState());
break;
case LFUN_BOOKMARK_SAVE:
saveBookmark(convert<unsigned int>(to_utf8(cmd.argument())));
break;
case LFUN_LABEL_GOTO: {
docstring label = cmd.argument();
if (label.empty()) {
InsetRef * inset =
getInsetByCode<InsetRef>(cursor_,
Inset::REF_CODE);
if (inset) {
label = inset->getParam("reference");
// persistent=false: use temp_bookmark
saveBookmark(0);
}
}
if (!label.empty())
gotoLabel(label);
break;
}
case LFUN_PARAGRAPH_GOTO: {
int const id = convert<int>(to_utf8(cmd.argument()));
int i = 0;
for (Buffer * b = &buffer_; i == 0 || b != &buffer_;
b = theBufferList().next(b)) {
ParIterator par = b->getParFromID(id);
if (par == b->par_iterator_end()) {
LYXERR(Debug::INFO)
<< "No matching paragraph found! ["
<< id << "]." << endl;
} else {
LYXERR(Debug::INFO)
<< "Paragraph " << par->id()
<< " found in buffer `"
<< b->fileName() << "'." << endl;
if (b == &buffer_) {
// Set the cursor
setCursor(makeDocIterator(par, 0));
} else {
// Switch to other buffer view and resend cmd
theLyXFunc().dispatch(FuncRequest(
LFUN_BUFFER_SWITCH, b->fileName()));
theLyXFunc().dispatch(cmd);
updateFlags = Update::None;
}
break;
}
++i;
}
break;
}
case LFUN_OUTLINE_UP:
toc::outline(toc::Up, cursor_);
cursor_.text()->setCursor(cursor_, cursor_.pit(), 0);
updateLabels(buffer_);
break;
case LFUN_OUTLINE_DOWN:
toc::outline(toc::Down, cursor_);
cursor_.text()->setCursor(cursor_, cursor_.pit(), 0);
updateLabels(buffer_);
break;
case LFUN_OUTLINE_IN:
toc::outline(toc::In, cursor_);
updateLabels(buffer_);
break;
case LFUN_OUTLINE_OUT:
toc::outline(toc::Out, cursor_);
updateLabels(buffer_);
break;
case LFUN_NOTE_NEXT:
bv_funcs::gotoInset(this, Inset::NOTE_CODE, false);
break;
case LFUN_REFERENCE_NEXT: {
vector<Inset_code> tmp;
tmp.push_back(Inset::LABEL_CODE);
tmp.push_back(Inset::REF_CODE);
bv_funcs::gotoInset(this, tmp, true);
break;
}
case LFUN_CHANGES_TRACK:
buffer_.params().trackChanges = !buffer_.params().trackChanges;
break;
case LFUN_CHANGES_OUTPUT:
buffer_.params().outputChanges = !buffer_.params().outputChanges;
if (buffer_.params().outputChanges) {
bool dvipost = LaTeXFeatures::isAvailable("dvipost");
bool xcolorsoul = LaTeXFeatures::isAvailable("soul") &&
LaTeXFeatures::isAvailable("xcolor");
if (!dvipost && !xcolorsoul) {
Alert::warning(_("Changes not shown in LaTeX output"),
_("Changes will not be highlighted in LaTeX output, "
"because neither dvipost nor xcolor/soul are installed.\n"
"Please install these packages or redefine "
"\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
} else if (!xcolorsoul) {
Alert::warning(_("Changes not shown in LaTeX output"),
_("Changes will not be highlighted in LaTeX output "
"when using pdflatex, because xcolor and soul are not installed.\n"
"Please install both packages or redefine "
"\\lyxadded and \\lyxdeleted in the LaTeX preamble."));
}
}
break;
case LFUN_CHANGE_NEXT:
findNextChange(this);
break;
case LFUN_CHANGES_MERGE:
if (findNextChange(this))
showDialog("changes");
break;
case LFUN_ALL_CHANGES_ACCEPT:
// select complete document
cursor_.reset(buffer_.inset());
cursor_.selHandle(true);
buffer_.text().cursorBottom(cursor_);
// accept everything in a single step to support atomic undo
buffer_.text().acceptOrRejectChanges(cursor_, Text::ACCEPT);
break;
case LFUN_ALL_CHANGES_REJECT:
// select complete document
cursor_.reset(buffer_.inset());
cursor_.selHandle(true);
buffer_.text().cursorBottom(cursor_);
// reject everything in a single step to support atomic undo
// Note: reject does not work recursively; the user may have to repeat the operation
buffer_.text().acceptOrRejectChanges(cursor_, Text::REJECT);
break;
case LFUN_WORD_FIND:
find(this, cmd);
break;
case LFUN_WORD_REPLACE: {
bool has_deleted = false;
if (cur.selection()) {
DocIterator beg = cur.selectionBegin();
DocIterator end = cur.selectionEnd();
if (beg.pit() == end.pit()) {
for (pos_type p = beg.pos() ; p < end.pos() ; ++p) {
if (cur.paragraph().isDeleted(p))
has_deleted = true;
}
}
}
replace(this, cmd, has_deleted);
break;
}
case LFUN_MARK_OFF:
cur.clearSelection();
cur.resetAnchor();
cur.message(from_utf8(N_("Mark off")));
break;
case LFUN_MARK_ON:
cur.clearSelection();
cur.mark() = true;
cur.resetAnchor();
cur.message(from_utf8(N_("Mark on")));
break;
case LFUN_MARK_TOGGLE:
cur.clearSelection();
if (cur.mark()) {
cur.mark() = false;
cur.message(from_utf8(N_("Mark removed")));
} else {
cur.mark() = true;
cur.message(from_utf8(N_("Mark set")));
}
cur.resetAnchor();
break;
case LFUN_SCREEN_RECENTER:
center();
break;
case LFUN_BIBTEX_DATABASE_ADD: {
Cursor tmpcur = cursor_;
bv_funcs::findInset(tmpcur, Inset::BIBTEX_CODE, false);
InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
Inset::BIBTEX_CODE);
if (inset) {
if (inset->addDatabase(to_utf8(cmd.argument())))
buffer_.updateBibfilesCache();
}
break;
}
case LFUN_BIBTEX_DATABASE_DEL: {
Cursor tmpcur = cursor_;
bv_funcs::findInset(tmpcur, Inset::BIBTEX_CODE, false);
InsetBibtex * inset = getInsetByCode<InsetBibtex>(tmpcur,
Inset::BIBTEX_CODE);
if (inset) {
if (inset->delDatabase(to_utf8(cmd.argument())))
buffer_.updateBibfilesCache();
}
break;
}
case LFUN_WORDS_COUNT: {
DocIterator from, to;
if (cur.selection()) {
from = cur.selectionBegin();
to = cur.selectionEnd();
} else {
from = doc_iterator_begin(buffer_.inset());
to = doc_iterator_end(buffer_.inset());
}
int const count = countWords(from, to);
docstring message;
if (count != 1) {
if (cur.selection())
message = bformat(_("%1$d words in selection."),
count);
else
message = bformat(_("%1$d words in document."),
count);
}
else {
if (cur.selection())
message = _("One word in selection.");
else
message = _("One word in document.");
}
Alert::information(_("Count words"), message);
}
break;
case LFUN_BUFFER_TOGGLE_COMPRESSION:
// turn compression on/off
buffer_.params().compressed = !buffer_.params().compressed;
break;
case LFUN_NEXT_INSET_TOGGLE: {
// this is the real function we want to invoke
FuncRequest tmpcmd = FuncRequest(LFUN_INSET_TOGGLE, cmd.origin);
// if there is an inset at cursor, see whether it
// wants to toggle.
Inset * inset = cur.nextInset();
if (inset) {
if (inset->isActive()) {
Cursor tmpcur = cur;
tmpcur.pushLeft(*inset);
inset->dispatch(tmpcur, tmpcmd);
if (tmpcur.result().dispatched()) {
cur.dispatched();
}
} else if (inset->editable() == Inset::IS_EDITABLE) {
inset->edit(cur, true);
}
}
// if it did not work, try the underlying inset.
if (!cur.result().dispatched())
cur.dispatch(tmpcmd);
if (cur.result().dispatched())
cur.clearSelection();
break;
}
case LFUN_SCREEN_UP:
case LFUN_SCREEN_DOWN: {
Point const p = bv_funcs::getPos(*this, cur, cur.boundary());
scroll(cmd.action == LFUN_SCREEN_UP? - height_ : height_);
cur.reset(buffer_.inset());
text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_);
//FIXME: what to do with cur.x_target()?
finishUndo();
// The metrics are already up to date. see scroll()
updateFlags = Update::None;
break;
}
case LFUN_SCREEN_UP_SELECT:
case LFUN_SCREEN_DOWN_SELECT: {
cur.selHandle(true);
size_t initial_depth = cur.depth();
Point const p = bv_funcs::getPos(*this, cur, cur.boundary());
scroll(cmd.action == LFUN_SCREEN_UP_SELECT? - height_ : height_);
// FIXME: We need to verify if the cursor stayed within an inset...
//cur.reset(buffer_.inset());
text_metrics_[&buffer_.text()].editXY(cur, p.x_, p.y_);
finishUndo();
while (cur.depth() > initial_depth) {
cur.forwardInset();
}
// FIXME: we need to do a redraw again because of the selection
buffer_.changed();
updateFlags = Update::Force | Update::FitCursor;
break;
}
default:
updateFlags = Update::None;
}
return updateFlags;
}
docstring const BufferView::requestSelection()
{
Cursor & cur = cursor_;
if (!cur.selection()) {
xsel_cache_.set = false;
return docstring();
}
if (!xsel_cache_.set ||
cur.top() != xsel_cache_.cursor ||
cur.anchor_.top() != xsel_cache_.anchor)
{
xsel_cache_.cursor = cur.top();
xsel_cache_.anchor = cur.anchor_.top();
xsel_cache_.set = cur.selection();
return cur.selectionAsString(false);
}
return docstring();
}
void BufferView::clearSelection()
{
cursor_.clearSelection();
// Clear the selection buffer. Otherwise a subsequent
// middle-mouse-button paste would use the selection buffer,
// not the more current external selection.
cap::clearSelection();
xsel_cache_.set = false;
// The buffer did not really change, but this causes the
// redraw we need because we cleared the selection above.
buffer_.changed();
}
void BufferView::resize(int width, int height)
{
// Update from work area
width_ = width;
height_ = height;
updateMetrics(false);
}
Inset const * BufferView::getCoveringInset(Text const & text, int x, int y)
{
pit_type pit = text_metrics_[&text].getPitNearY(y);
BOOST_ASSERT(pit != -1);
Paragraph const & par = text.getPar(pit);
LYXERR(Debug::DEBUG)
<< BOOST_CURRENT_FUNCTION
<< ": x: " << x
<< " y: " << y
<< " pit: " << pit
<< endl;
InsetList::const_iterator iit = par.insetlist.begin();
InsetList::const_iterator iend = par.insetlist.end();
for (; iit != iend; ++iit) {
Inset * const inset = iit->inset;
if (inset->covers(*this, x, y)) {
if (!inset->descendable())
// 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;
}
}
LYXERR(Debug::DEBUG)
<< BOOST_CURRENT_FUNCTION
<< ": Hit inset: " << inset << endl;
return inset;
}
}
LYXERR(Debug::DEBUG)
<< BOOST_CURRENT_FUNCTION
<< ": No inset hit. " << endl;
return 0;
}
bool BufferView::workAreaDispatch(FuncRequest const & cmd0)
{
//lyxerr << BOOST_CURRENT_FUNCTION << "[ cmd0 " << cmd0 << "]" << endl;
// This is only called for mouse related events including
// LFUN_FILE_OPEN generated by drag-and-drop.
FuncRequest cmd = cmd0;
Cursor cur(*this);
cur.push(buffer_.inset());
cur.selection() = cursor_.selection();
// Either the inset under the cursor or the
// surrounding Text will handle this event.
// make sure we stay within the screen...
cmd.y = min(max(cmd.y, -1), height_);
if (cmd.action == LFUN_MOUSE_MOTION && cmd.button() == mouse_button::none) {
// Get inset under mouse, if there is one.
Inset const * covering_inset =
getCoveringInset(buffer_.text(), cmd.x, cmd.y);
if (covering_inset == last_inset_)
// Same inset, no need to do anything...
return false;
bool need_redraw = false;
// const_cast because of setMouseHover().
Inset * inset = const_cast<Inset *>(covering_inset);
if (last_inset_)
// Remove the hint on the last hovered inset (if any).
need_redraw |= last_inset_->setMouseHover(false);
if (inset)
// Highlighted the newly hovered inset (if any).
need_redraw |= inset->setMouseHover(true);
last_inset_ = inset;
// if last metrics update was in singlepar mode, WorkArea::redraw() will
// not expose the button for redraw. We adjust here the metrics dimension
// to enable a full redraw.
// FIXME: It is possible to redraw only the area around the button!
if (need_redraw
&& metrics_info_.update_strategy == SingleParUpdate) {
// FIXME: It should be possible to redraw only the area around
// the button by doing this:
//
//metrics_info_.singlepar = false;
//metrics_info_.y1 = ymin of button;
//metrics_info_.y2 = ymax of button;
//
// Unfortunately, BufferView::draw() does not distinguish
// between background updates and text updates. So we use the hammer
// solution for now. We could also avoid the updateMetrics() below
// by using the first and last pit of the CoordCache. Have a look
// at Text::getPitNearY() to see what I mean.
//
//metrics_info_.pit1 = first pit of CoordCache;
//metrics_info_.pit2 = last pit of CoordCache;
//metrics_info_.singlepar = false;
//metrics_info_.y1 = 0;
//metrics_info_.y2 = height_;
//
updateMetrics(false);
}
// This event (moving without mouse click) is not passed further.
// This should be changed if it is further utilized.
return need_redraw;
}
// Build temporary cursor.
Inset * inset = text_metrics_[&buffer_.text()].editXY(cur, cmd.x, cmd.y);
// Put anchor at the same position.
cur.resetAnchor();
// 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)
if (inset) {
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.
if (!cur.result().dispatched())
cur.dispatch(cmd);
//Do we have a selection?
theSelection().haveSelection(cursor().selection());
// Redraw if requested and necessary.
if (cur.result().dispatched() && cur.result().update())
return update(cur.result().update());
return false;
}
void BufferView::scroll(int y)
{
if (y > 0)
scrollDown(y);
else if (y < 0)
scrollUp(-y);
}
void BufferView::scrollDown(int offset)
{
Text * text = &buffer_.text();
TextMetrics & tm = text_metrics_[text];
int ymax = height_ + offset;
while (true) {
std::pair<pit_type, ParagraphMetrics> const & last = tm.last();
int bottom_pos = last.second.position() + last.second.descent();
if (last.first == text->paragraphs().size() - 1) {
if (bottom_pos <= height_)
return;
offset = min(offset, bottom_pos - height_);
break;
}
if (bottom_pos > ymax)
break;
tm.newParMetricsDown();
}
offset_ref_ += offset;
updateMetrics(false);
buffer_.changed();
}
void BufferView::scrollUp(int offset)
{
Text * text = &buffer_.text();
TextMetrics & tm = text_metrics_[text];
int ymin = - offset;
while (true) {
std::pair<pit_type, ParagraphMetrics> const & first = tm.first();
int top_pos = first.second.position() - first.second.ascent();
if (first.first == 0) {
if (top_pos >= 0)
return;
offset = min(offset, - top_pos);
break;
}
if (top_pos < ymin)
break;
tm.newParMetricsUp();
}
offset_ref_ -= offset;
updateMetrics(false);
buffer_.changed();
}
void BufferView::setCursorFromRow(int row)
{
int tmpid = -1;
int tmppos = -1;
buffer_.texrow().getIdFromRow(row, tmpid, tmppos);
cursor_.reset(buffer_.inset());
if (tmpid == -1)
buffer_.text().setCursor(cursor_, 0, 0);
else
buffer_.text().setCursor(cursor_, buffer_.getParFromID(tmpid).pit(), tmppos);
}
void BufferView::gotoLabel(docstring const & label)
{
for (InsetIterator it = inset_iterator_begin(buffer_.inset()); it; ++it) {
vector<docstring> labels;
it->getLabelList(buffer_, labels);
if (std::find(labels.begin(), labels.end(), label) != labels.end()) {
setCursor(it);
update();
return;
}
}
}
TextMetrics const & BufferView::textMetrics(Text const * t) const
{
return const_cast<BufferView *>(this)->textMetrics(t);
}
TextMetrics & BufferView::textMetrics(Text const * t)
{
TextMetricsCache::iterator tmc_it = text_metrics_.find(t);
if (tmc_it == text_metrics_.end()) {
tmc_it = text_metrics_.insert(
make_pair(t, TextMetrics(this, const_cast<Text *>(t)))).first;
}
return tmc_it->second;
}
ParagraphMetrics const & BufferView::parMetrics(Text const * t,
pit_type pit) const
{
return textMetrics(t).parMetrics(pit);
}
int BufferView::workHeight() const
{
return height_;
}
void BufferView::setCursor(DocIterator const & dit)
{
size_t const n = dit.depth();
for (size_t i = 0; i < n; ++i)
dit[i].inset().edit(cursor_, true);
cursor_.setCursor(dit);
cursor_.selection() = false;
}
bool BufferView::checkDepm(Cursor & cur, Cursor & old)
{
// Would be wrong to delete anything if we have a selection.
if (cur.selection())
return false;
bool need_anchor_change = false;
bool changed = cursor_.text()->deleteEmptyParagraphMechanism(cur, old,
need_anchor_change);
if (need_anchor_change)
cur.resetAnchor();
if (!changed)
return false;
updateLabels(buffer_);
updateMetrics(false);
buffer_.changed();
return true;
}
bool BufferView::mouseSetCursor(Cursor & cur)
{
BOOST_ASSERT(&cur.bv() == this);
// this event will clear selection so we save selection for
// persistent selection
cap::saveSelection(cursor());
// Has the cursor just left the inset?
bool badcursor = false;
bool leftinset = (&cursor_.inset() != &cur.inset());
if (leftinset)
badcursor = notifyCursorLeaves(cursor_, cur);
// do the dEPM magic if needed
// 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;
if (!badcursor && cursor_.inTexted())
update |= checkDepm(cur, cursor_);
// if the cursor was in an empty script inset and the new
// position is in the nucleus of the inset, notifyCursorLeaves
// will kill the script inset itself. So we check all the
// elements of the cursor to make sure that they are correct.
// For an example, see bug 2933:
// http://bugzilla.lyx.org/show_bug.cgi?id=2933
// The code below could maybe be moved to a DocIterator method.
//lyxerr << "cur before " << cur <<std::endl;
DocIterator dit(cur.inset());
dit.push_back(cur.bottom());
size_t i = 1;
while (i < cur.depth() && dit.nextInset() == &cur[i].inset()) {
dit.push_back(cur[i]);
++i;
}
//lyxerr << "5 cur after" << dit <<std::endl;
cursor_.setCursor(dit);
cursor_.boundary(cur.boundary());
cursor_.clearSelection();
finishUndo();
return update;
}
void BufferView::putSelectionAt(DocIterator const & cur,
int length, bool backwards)
{
cursor_.clearSelection();
setCursor(cur);
if (length) {
if (backwards) {
cursor_.pos() += length;
cursor_.setSelection(cursor_, -length);
} else
cursor_.setSelection(cursor_, length);
}
}
Cursor & BufferView::cursor()
{
return cursor_;
}
Cursor const & BufferView::cursor() const
{
return cursor_;
}
pit_type BufferView::anchor_ref() const
{
return anchor_ref_;
}
ViewMetricsInfo const & BufferView::viewMetricsInfo()
{
return metrics_info_;
}
// FIXME: We should split-up updateMetrics() for the singlepar case.
void BufferView::updateMetrics(bool singlepar)
{
Text & buftext = buffer_.text();
pit_type const npit = int(buftext.paragraphs().size());
if (anchor_ref_ > int(npit - 1)) {
anchor_ref_ = int(npit - 1);
offset_ref_ = 0;
}
if (!singlepar) {
// Clear out the position cache in case of full screen redraw,
coord_cache_.clear();
// Clear out paragraph metrics to avoid having invalid metrics
// in the cache from paragraphs not relayouted below
// The complete text metrics will be redone.
text_metrics_.clear();
}
TextMetrics & tm = textMetrics(&buftext);
pit_type const bottom_pit = cursor_.bottom().pit();
// If the paragraph metrics has changed, we can not
// use the singlepar optimisation.
if (singlepar
// 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)) {
updateOffsetRef();
// collect cursor paragraph iter bounds
ParagraphMetrics const & pm = tm.parMetrics(bottom_pit);
int y1 = pm.position() - pm.ascent();
int y2 = pm.position() + pm.descent();
metrics_info_ = ViewMetricsInfo(bottom_pit, bottom_pit, y1, y2,
SingleParUpdate, npit);
LYXERR(Debug::PAINTING)
<< BOOST_CURRENT_FUNCTION
<< "\ny1: " << y1
<< " y2: " << y2
<< " pit: " << bottom_pit
<< " singlepar: " << singlepar
<< endl;
return;
}
pit_type const pit = anchor_ref_;
int pit1 = pit;
int pit2 = pit;
// Rebreak anchor paragraph.
tm.redoParagraph(pit);
updateOffsetRef();
int y0 = tm.parMetrics(pit).ascent() - offset_ref_;
// Redo paragraphs above anchor if necessary.
int y1 = y0;
while (y1 > 0 && pit1 > 0) {
y1 -= tm.parMetrics(pit1).ascent();
--pit1;
tm.redoParagraph(pit1);
y1 -= tm.parMetrics(pit1).descent();
}
// Take care of ascent of first line
y1 -= tm.parMetrics(pit1).ascent();
// Normalize anchor for next time
anchor_ref_ = pit1;
offset_ref_ = -y1;
// Grey at the beginning is ugly
if (pit1 == 0 && y1 > 0) {
y0 -= y1;
y1 = 0;
anchor_ref_ = 0;
}
// Redo paragraphs below the anchor if necessary.
int y2 = y0;
while (y2 < height_ && pit2 < int(npit) - 1) {
y2 += tm.parMetrics(pit2).descent();
++pit2;
tm.redoParagraph(pit2);
y2 += tm.parMetrics(pit2).ascent();
}
// Take care of descent of last line
y2 += tm.parMetrics(pit2).descent();
LYXERR(Debug::PAINTING)
<< BOOST_CURRENT_FUNCTION
<< "\n y1: " << y1
<< " y2: " << y2
<< " pit1: " << pit1
<< " pit2: " << pit2
<< " npit: " << npit
<< " singlepar: " << singlepar
<< endl;
metrics_info_ = ViewMetricsInfo(pit1, pit2, y1, y2,
FullScreenUpdate, npit);
if (lyxerr.debugging(Debug::WORKAREA)) {
LYXERR(Debug::WORKAREA) << "BufferView::updateMetrics" << endl;
coord_cache_.dump();
}
}
void BufferView::menuInsertLyXFile(string const & filenm)
{
BOOST_ASSERT(cursor_.inTexted());
string filename = filenm;
if (filename.empty()) {
// Launch a file browser
// FIXME UNICODE
string initpath = lyxrc.document_path;
string const trypath = buffer_.filePath();
// If directory is writeable, use this as default.
if (isDirWriteable(FileName(trypath)))
initpath = trypath;
// FIXME UNICODE
FileDialog fileDlg(_("Select LyX document to insert"),
LFUN_FILE_INSERT,
make_pair(_("Documents|#o#O"), from_utf8(lyxrc.document_path)),
make_pair(_("Examples|#E#e"),
from_utf8(addPath(package().system_support().absFilename(),
"examples"))));
FileDialog::Result result =
fileDlg.open(from_utf8(initpath),
FileFilterList(_("LyX Documents (*.lyx)")),
docstring());
if (result.first == FileDialog::Later)
return;
// FIXME UNICODE
filename = to_utf8(result.second);
// check selected filename
if (filename.empty()) {
// emit message signal.
message(_("Canceled."));
return;
}
}
// Get absolute path of file and add ".lyx"
// to the filename if necessary
filename = fileSearch(string(), filename, "lyx").absFilename();
docstring const disp_fn = makeDisplayPath(filename);
// emit message signal.
message(bformat(_("Inserting document %1$s..."), disp_fn));
docstring res;
Buffer buf("", false);
if (lyx::loadLyXFile(&buf, FileName(filename))) {
ErrorList & el = buffer_.errorList("Parse");
// Copy the inserted document error list into the current buffer one.
el = buf.errorList("Parse");
recordUndo(cursor_);
cap::pasteParagraphList(cursor_, buf.paragraphs(),
This is one of a series of patches that will merge the layout modules development in personal/branches/rgheck back into the tree. Design goal: Allow the use of layout "modules", which are to LaTeX packages as layout files are to LaTeX document classes. Thus, one could have a module that defined certain character styles, environments, commands, or what have you, and include it in various documents, each of which uses a different document class, without having to modify the layout files themselves. For example, a theorems.module could be used with article.layout to provide support for theorem-type environments, without having to modify article.layout itself, and the same module could be used with book.layout, etc. This first patch does some reworking of the infrastructrue. We need to distinguish between the TextClass that a particular document is using and the layout of that document, since modules, in particular, can modify the layout. The solution adopted here is to add a TextClass pointer to BufferParams, which will hold the layout. The layout itself is then constructed from the TextClass the document is using. At present, this is completely trivial, but that will change when modules are added. The pointer in question is a boost::shared_ptr. This is needed because CutAndPaste saves a copy of the layout with each cut or copied selection. We cannot assume the selection vanishes when the document is closed, so there are two options: (i) keep a list of all the layouts that have ever been used by any document; (ii) used some kind of smart pointer. The latter seems preferable, as the former would waste memory. More importantly, the use of a smart pointer allows modules to be modified on disk and then reloaded while LyX is running, and it will eventually allow the same for layout files. git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@19756 a592a061-630c-0410-9148-cb99ea01b6c8
2007-08-23 16:41:13 +00:00
buf.params().getTextClass_ptr(), el);
res = _("Document %1$s inserted.");
} else
res = _("Could not insert document %1$s");
// emit message signal.
message(bformat(res, disp_fn));
buffer_.errors("Parse");
updateMetrics(false);
}
void BufferView::draw(frontend::Painter & pain)
{
PainterInfo pi(this, pain);
// Should the whole screen, including insets, be refreshed?
// 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.
pi.full_repaint = metrics_info_.update_strategy != SingleParUpdate;
if (pi.full_repaint)
// Clear background (if not delegated to rows)
pain.fillRectangle(0, metrics_info_.y1, width_,
metrics_info_.y2 - metrics_info_.y1,
buffer_.inset().backgroundColor());
LYXERR(Debug::PAINTING) << "\t\t*** START DRAWING ***" << endl;
Text & text = buffer_.text();
TextMetrics const & tm = text_metrics_[&text];
int y = metrics_info_.y1 + tm.parMetrics(metrics_info_.p1).ascent();
if (!pi.full_repaint)
tm.drawParagraph(pi, metrics_info_.p1, 0, y);
else
tm.draw(pi, 0, y);
LYXERR(Debug::PAINTING) << "\n\t\t*** END DRAWING ***" << endl;
// and grey out above (should not happen later)
// lyxerr << "par ascent: " << text.getPar(metrics_info_.p1).ascent() << endl;
if (metrics_info_.y1 > 0
&& metrics_info_.update_strategy == FullScreenUpdate)
pain.fillRectangle(0, 0, width_, metrics_info_.y1, Color::bottomarea);
// and possibly grey out below
// lyxerr << "par descent: " << text.getPar(metrics_info_.p1).ascent() << endl;
if (metrics_info_.y2 < height_
&& metrics_info_.update_strategy == FullScreenUpdate)
pain.fillRectangle(0, metrics_info_.y2, width_,
height_ - metrics_info_.y2, Color::bottomarea);
}
} // namespace lyx