lyx_mirror/src/frontends/qt4/GuiViewSource.cpp
Guillaume Munch 789745df7a TexRow info in source panel and gotoCursor() for debugging
These features are active in DEVEL_VERSION when Debug is set to LATEX.

1. The TexRow information is prepended to the source panel.

2. Clicking on any line in the source triggers reverse search. (This would be an
interesting feature to implement on the user side, but we need a proper LFUN.)
2015-10-19 06:55:17 +01:00

494 lines
13 KiB
C++

/**
* \file GuiViewSource.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author John Levon
* \author Bo Peng
* \author Abdelrazak Younes
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "GuiApplication.h"
#include "GuiViewSource.h"
#include "LaTeXHighlighter.h"
#include "qt_helpers.h"
#include "BufferParams.h"
#include "BufferView.h"
#include "Cursor.h"
#include "Format.h"
#include "Paragraph.h"
#include "support/debug.h"
#include "support/lassert.h"
#include "support/docstream.h"
#include "support/docstring_list.h"
#include "support/gettext.h"
#include <boost/crc.hpp>
#include <QBoxLayout>
#include <QComboBox>
#include <QScrollBar>
#include <QSettings>
#include <QTextCursor>
#include <QTextDocument>
#include <QVariant>
using namespace std;
namespace lyx {
namespace frontend {
ViewSourceWidget::ViewSourceWidget()
: bv_(0), document_(new QTextDocument(this)),
highlighter_(new LaTeXHighlighter(document_)),
update_timer_(new QTimer(this))
{
setupUi(this);
connect(contentsCO, SIGNAL(activated(int)),
this, SLOT(contentsChanged()));
connect(autoUpdateCB, SIGNAL(toggled(bool)),
updatePB, SLOT(setDisabled(bool)));
connect(autoUpdateCB, SIGNAL(toggled(bool)),
this, SLOT(contentsChanged()));
connect(masterPerspectiveCB, SIGNAL(toggled(bool)),
this, SLOT(contentsChanged()));
connect(updatePB, SIGNAL(clicked()),
this, SLOT(updateViewNow()));
connect(outputFormatCO, SIGNAL(activated(int)),
this, SLOT(setViewFormat(int)));
connect(outputFormatCO, SIGNAL(activated(int)),
this, SLOT(contentsChanged()));
#ifdef DEVEL_VERSION
if (lyx::lyxerr.debugging(Debug::LATEX))
connect(viewSourceTV, SIGNAL(cursorPositionChanged()),
this, SLOT(gotoCursor()));
#endif
// setting the update timer
update_timer_->setSingleShot(true);
connect(update_timer_, SIGNAL(timeout()),
this, SLOT(realUpdateView()));
// setting a document at this point trigger an assertion in Qt
// so we disable the signals here:
document_->blockSignals(true);
viewSourceTV->setDocument(document_);
// reset selections
setText();
document_->blockSignals(false);
viewSourceTV->setReadOnly(true);
///dialog_->viewSourceTV->setAcceptRichText(false);
// this is personal. I think source code should be in fixed-size font
QFont font(guiApp->typewriterFontName());
font.setFixedPitch(true);
font.setStyleHint(QFont::TypeWriter);
viewSourceTV->setFont(font);
// again, personal taste
viewSourceTV->setWordWrapMode(QTextOption::NoWrap);
}
void ViewSourceWidget::getContent(BufferView const * view,
Buffer::OutputWhat output, docstring & str, string const & format,
bool master)
{
// get the *top* level paragraphs that contain the cursor,
// or the selected text
pit_type par_begin;
pit_type par_end;
if (!view->cursor().selection()) {
par_begin = view->cursor().bottom().pit();
par_end = par_begin;
} else {
par_begin = view->cursor().selectionBegin().bottom().pit();
par_end = view->cursor().selectionEnd().bottom().pit();
}
if (par_begin > par_end)
swap(par_begin, par_end);
odocstringstream ostr;
texrow_ = view->buffer().getSourceCode(ostr, format,
par_begin, par_end + 1, output, master);
//ensure that the last line can always be selected in its full width
str = ostr.str() + "\n";
}
void ViewSourceWidget::setBufferView(BufferView const * bv)
{
if (bv_ != bv) {
setText();
bv_ = bv;
}
setEnabled(bv ? true : false);
}
bool ViewSourceWidget::setText(QString const & qstr)
{
bool const changed = document_->toPlainText() != qstr;
viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
if (changed)
document_->setPlainText(qstr);
return changed;
}
void ViewSourceWidget::contentsChanged()
{
if (autoUpdateCB->isChecked())
updateViewNow();
}
void ViewSourceWidget::setViewFormat(int const index)
{
outputFormatCO->setCurrentIndex(index);
view_format_ = outputFormatCO->itemData(index).toString();
}
void ViewSourceWidget::updateView()
{
const int long_delay = 400;
const int short_delay = 60;
// a shorter delay if just the current paragraph is shown
update_timer_->start((contentsCO->currentIndex() == 0) ?
short_delay : long_delay);
}
void ViewSourceWidget::updateViewNow()
{
update_timer_->start(0);
}
namespace {
QString prependTexRow(TexRow const & texrow, QString const & content)
{
QStringList list = content.split(QChar('\n'));
docstring_list dlist;
for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
dlist.push_back(from_utf8(fromqstr(*it)));
texrow.prepend(dlist);
QString qstr;
for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
qstr += toqstr(*it) + '\n';
return qstr;
}
} // anon namespace
void ViewSourceWidget::realUpdateView()
{
if (!bv_) {
setText();
setEnabled(false);
return;
}
setEnabled(true);
// we will try to get that much space around the cursor
int const v_margin = 3;
int const h_margin = 10;
// we will try to preserve this
int const h_scroll = viewSourceTV->horizontalScrollBar()->value();
string const format = fromqstr(view_format_);
Buffer::OutputWhat output = Buffer::CurrentParagraph;
if (contentsCO->currentIndex() == 1)
output = Buffer::FullSource;
else if (contentsCO->currentIndex() == 2)
output = Buffer::OnlyPreamble;
else if (contentsCO->currentIndex() == 3)
output = Buffer::OnlyBody;
docstring content;
getContent(bv_, output, content, format, masterPerspectiveCB->isChecked());
QString old = document_->toPlainText();
QString qcontent = toqstr(content);
#ifdef DEVEL_VERSION
if (texrow_.get() && lyx::lyxerr.debugging(Debug::LATEX))
qcontent = prependTexRow(*texrow_, qcontent);
#endif
// prevent gotoCursor()
viewSourceTV->blockSignals(true);
bool const changed = setText(qcontent);
if (changed && !texrow_.get()) {
// position-to-row is unavailable
// we jump to the first modification
const QChar * oc = old.constData();
const QChar * nc = qcontent.constData();
int pos = 0;
while (*oc != '\0' && *nc != '\0' && *oc == *nc) {
++oc;
++nc;
++pos;
}
QTextCursor c = QTextCursor(viewSourceTV->document());
//get some space below the cursor
c.setPosition(pos);
c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,v_margin);
viewSourceTV->setTextCursor(c);
//get some space on the right of the cursor
viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
c.setPosition(pos);
const int block = c.blockNumber();
for (int i = h_margin; i && block == c.blockNumber(); --i) {
c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
}
c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
viewSourceTV->setTextCursor(c);
//back to the position
c.setPosition(pos);
//c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
viewSourceTV->setTextCursor(c);
} else if (texrow_.get()) {
// Use the available position-to-row conversion to highlight
// the current selection in the source
int beg_row, end_row;
{
DocIterator beg = bv_->cursor().selectionBegin();
DocIterator end = bv_->cursor().selectionEnd();
std::pair<int,int> beg_rows = texrow_->rowFromDocIterator(beg);
beg_row = beg_rows.first;
if (beg != end) {
end.backwardChar();
std::pair<int,int> end_rows = texrow_->rowFromDocIterator(end);
end_row = end_rows.second;
} else {
end_row = beg_rows.second;
}
}
QTextCursor c = QTextCursor(viewSourceTV->document());
c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
beg_row - 1);
const int beg_sel = c.position();
//get some space above the cursor
c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
v_margin);
viewSourceTV->setTextCursor(c);
c.setPosition(beg_sel, QTextCursor::MoveAnchor);
c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
end_row - beg_row +1);
const int end_sel = c.position();
//get some space below the cursor
c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
v_margin - 1);
viewSourceTV->setTextCursor(c);
c.setPosition(end_sel, QTextCursor::KeepAnchor);
viewSourceTV->setTextCursor(c);
//the real highlighting is done with an ExtraSelection
QTextCharFormat format;
QPalette palette = viewSourceTV->palette();
//Alternative:
// QColor bg = palette.color(QPalette::Active,QPalette::Highlight);
// bg.setAlpha(64);
// format.setBackground(QBrush(bg));
//Other alternatives:
//format.setBackground(palette.light());
//format.setBackground(palette.alternateBase());
format.setBackground(palette.toolTipBase());
format.setProperty(QTextFormat::FullWidthSelection, true);
QTextEdit::ExtraSelection sel;
sel.format = format;
sel.cursor = c;
viewSourceTV->setExtraSelections(
QList<QTextEdit::ExtraSelection>() << sel);
//clean up
c.clearSelection();
viewSourceTV->setTextCursor(c);
viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
} // else if (texrow)
viewSourceTV->blockSignals(false);
}
// only used in DEVEL_MODE for debugging
// need a proper LFUN if we want to implement it in release mode
void ViewSourceWidget::gotoCursor()
{
if (!bv_ || !texrow_.get())
return;
int row = viewSourceTV->textCursor().blockNumber() + 1;
const_cast<BufferView *>(bv_)->setCursorFromRow(row, *texrow_);
}
void ViewSourceWidget::updateDefaultFormat()
{
if (!bv_)
return;
outputFormatCO->blockSignals(true);
outputFormatCO->clear();
outputFormatCO->addItem(qt_("Default"),
QVariant(QString("default")));
int index = 0;
vector<string> tmp = bv_->buffer().params().backends();
vector<string>::const_iterator it = tmp.begin();
vector<string>::const_iterator en = tmp.end();
for (; it != en; ++it) {
string const format = *it;
Format const * fmt = formats.getFormat(format);
if (!fmt) {
LYXERR0("Can't find format for backend " << format << "!");
continue;
}
QString const pretty = qt_(fmt->prettyname());
QString const qformat = toqstr(format);
outputFormatCO->addItem(pretty, QVariant(qformat));
if (qformat == view_format_)
index = outputFormatCO->count() -1;
}
setViewFormat(index);
outputFormatCO->blockSignals(false);
}
void ViewSourceWidget::resizeEvent (QResizeEvent * event)
{
QSize const & formSize = formLayout->sizeHint();
// minimize the size of the part that contains the buttons
if (width() * formSize.height() < height() * formSize.width()) {
layout_->setDirection(QBoxLayout::TopToBottom);
} else {
layout_->setDirection(QBoxLayout::LeftToRight);
}
QWidget::resizeEvent(event);
}
void ViewSourceWidget::saveSession(QString const & session_key) const
{
QSettings settings;
settings.setValue(session_key + "/output", view_format_);
settings.setValue(session_key + "/contents", contentsCO->currentIndex());
settings.setValue(session_key + "/autoupdate", autoUpdateCB->isChecked());
settings.setValue(session_key + "/masterview",
masterPerspectiveCB->isChecked());
}
void ViewSourceWidget::restoreSession(QString const & session_key)
{
QSettings settings;
view_format_ = settings.value(session_key + "/output", 0).toString();
contentsCO->setCurrentIndex(settings
.value(session_key + "/contents", 0)
.toInt());
masterPerspectiveCB->setChecked(settings
.value(session_key + "/masterview", false)
.toBool());
bool const checked = settings
.value(session_key + "/autoupdate", true)
.toBool();
autoUpdateCB->setChecked(checked);
if (checked)
updateView();
}
GuiViewSource::GuiViewSource(GuiView & parent,
Qt::DockWidgetArea area, Qt::WindowFlags flags)
: DockView(parent, "view-source", qt_("LaTeX Source"), area, flags)
{
widget_ = new ViewSourceWidget;
setWidget(widget_);
}
GuiViewSource::~GuiViewSource()
{
delete widget_;
}
void GuiViewSource::updateView()
{
if (widget_->autoUpdateCB->isChecked()) {
widget_->setBufferView(bufferview());
widget_->updateView();
}
widget_->masterPerspectiveCB->setEnabled(buffer().parent());
}
void GuiViewSource::enableView(bool enable)
{
widget_->setBufferView(bufferview());
widget_->updateDefaultFormat();
if (!enable)
// In the opposite case, updateView() will be called anyway.
widget_->contentsChanged();
}
bool GuiViewSource::initialiseParams(string const & /*source*/)
{
setWindowTitle(title());
return true;
}
QString GuiViewSource::title() const
{
switch (docType()) {
case LATEX:
//FIXME: this is shown for LyXHTML source, LyX source, etc.
return qt_("LaTeX Source");
case DOCBOOK:
return qt_("DocBook Source");
case LITERATE:
return qt_("Literate Source");
}
LATTEST(false);
return QString();
}
void GuiViewSource::saveSession() const
{
Dialog::saveSession();
widget_->saveSession(sessionKey());
}
void GuiViewSource::restoreSession()
{
DockView::restoreSession();
widget_->restoreSession(sessionKey());
}
Dialog * createGuiViewSource(GuiView & lv)
{
return new GuiViewSource(lv);
}
} // namespace frontend
} // namespace lyx
#include "moc_GuiViewSource.cpp"