#include "LaTeXHighlighter.h"
#include "qt_helpers.h"
+#include "BufferParams.h"
#include "BufferView.h"
-#include "Buffer.h"
#include "Cursor.h"
+#include "Format.h"
+#include "FuncRequest.h"
+#include "LyX.h"
#include "Paragraph.h"
#include "TexRow.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(GuiViewSource & controller)
- : controller_(controller), document_(new QTextDocument(this)),
+ViewSourceWidget::ViewSourceWidget(QWidget * parent)
+ : QWidget(parent),
+ document_(new QTextDocument(this)),
highlighter_(new LaTeXHighlighter(document_))
{
setupUi(this);
- connect(viewFullSourceCB, SIGNAL(clicked()),
- this, SLOT(updateView()));
+ 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(updateView()));
+ this, SIGNAL(needUpdate()));
+ connect(outputFormatCO, SIGNAL(activated(int)),
+ this, SLOT(setViewFormat(int)));
// 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.setKerning(false);
- font.setFixedPitch(true);
- font.setStyleHint(QFont::TypeWriter);
- viewSourceTV->setFont(font);
+ viewSourceTV->setFont(guiApp->typewriterSystemFont());
// again, personal taste
viewSourceTV->setWordWrapMode(QTextOption::NoWrap);
+
+ // catch double click events
+ viewSourceTV->viewport()->installEventFilter(this);
}
-void ViewSourceWidget::updateView()
+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";
+}
+
+
+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())
- update(viewFullSourceCB->isChecked());
+ Q_EMIT needUpdate();
+}
+
- GuiViewSource::Row row = controller_.getRows();
- QTextCursor c = QTextCursor(viewSourceTV->document());
- c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, row.begin);
- c.select(QTextCursor::BlockUnderCursor);
- c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
- row.end - row.begin + 1);
- viewSourceTV->setTextCursor(c);
+void ViewSourceWidget::setViewFormat(int const index)
+{
+ outputFormatCO->setCurrentIndex(index);
+ string format = fromqstr(outputFormatCO->itemData(index).toString());
+ if (view_format_ != format) {
+ view_format_ = format;
+ Q_EMIT needUpdate();
+ }
+}
+
+
+int ViewSourceWidget::updateDelay() const
+{
+ const int long_delay = 400;
+ const int short_delay = 60;
+ // a shorter delay if just the current paragraph is shown
+ return (contentsCO->currentIndex() == 0) ? short_delay : long_delay;
+}
+
+
+void GuiViewSource::scheduleUpdate()
+{
+ update_timer_->start(widget_->updateDelay());
+}
+
+
+void GuiViewSource::scheduleUpdateNow()
+{
+ update_timer_->start(0);
+}
+
+
+void GuiViewSource::realUpdateView()
+{
+ widget_->updateView(bufferview());
+ updateTitle();
+}
+
+
+void ViewSourceWidget::updateView(BufferView const * bv)
+{
+ 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();
+
+ 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, view_format_,
+ masterPerspectiveCB->isChecked());
+ QString old = document_->toPlainText();
+ QString qcontent = toqstr(content);
+#ifdef DEVEL_VERSION
+ // output tex<->row correspondences in the source panel if the "-dbg latex"
+ // option is given.
+ if (texrow_ && lyx::lyxerr.debugging(Debug::LATEX)) {
+ QStringList list = qcontent.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);
+ qcontent.clear();
+ for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
+ qcontent += toqstr(*it) + '\n';
+ }
+#endif
+ // prevent gotoCursor()
+ QSignalBlocker blocker(viewSourceTV);
+ bool const changed = setText(qcontent);
+
+ if (changed && !texrow_) {
+ // position-to-row is unavailable
+ // we jump to the first modification
+ int length = min(old.length(), qcontent.length());
+ int pos = 0;
+ for (; pos < length && old.at(pos) == qcontent.at(pos); ++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_) {
+ // Use the available position-to-row conversion to highlight
+ // the current selection in the source
+ std::pair<int,int> rows = texrow_->rowFromCursor(bv->cursor());
+ int const beg_row = rows.first;
+ int const end_row = 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;
+ {
+ // We create a new color with the lightness of AlternateBase and
+ // the hue and saturation of Highlight
+ QPalette palette = viewSourceTV->palette();
+ QBrush alt = palette.alternateBase();
+ QColor high = palette.highlight().color().toHsl();
+ QColor col = QColor::fromHsl(high.hue(),
+ high.hslSaturation(),
+ alt.color().lightness());
+ alt.setColor(col);
+ format.setBackground(alt);
+ }
+ 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)
}
-void ViewSourceWidget::update(bool full_source)
+docstring ViewSourceWidget::currentFormatName(BufferView const * bv) const
{
- document_->setPlainText(controller_.getContent(full_source));
+ // Compute the actual format used
+ string const format = !bv ? ""
+ : flavor2format(bv->buffer().params().getOutputFlavor(view_format_));
+ Format const * f = formats.getFormat(format.empty() ? view_format_ : format);
+ return f ? f->prettyname() : from_utf8(view_format_);
+}
+
+
+bool ViewSourceWidget::eventFilter(QObject * obj, QEvent * ev)
+{
+ // this event filter is installed on the viewport of the QTextView
+ if (obj == viewSourceTV->viewport() &&
+ ev->type() == QEvent::MouseButtonDblClick) {
+ goToCursor();
+ return true;
+ }
+ return false;
+}
+
+
+void ViewSourceWidget::goToCursor() const
+{
+ if (!texrow_)
+ return;
+ int row = viewSourceTV->textCursor().blockNumber() + 1;
+ dispatch(texrow_->goToFuncFromRow(row));
+}
+
+
+
+void ViewSourceWidget::updateDefaultFormat(BufferView const & bv)
+{
+ QSignalBlocker blocker(outputFormatCO);
+ 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 = toqstr(translateIfPossible(fmt->prettyname()));
+ outputFormatCO->addItem(pretty, QVariant(toqstr(format)));
+ if (format == view_format_)
+ index = outputFormatCO->count() -1;
+ }
+ setViewFormat(index);
+}
+
+
+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", toqstr(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_ = fromqstr(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)
+ Q_EMIT needUpdate();
}
GuiViewSource::GuiViewSource(GuiView & parent,
Qt::DockWidgetArea area, Qt::WindowFlags flags)
- : DockView(parent, "view-source", qt_("LaTeX Source"), area, flags)
+ : DockView(parent, "view-source", qt_("Code Preview"), area, flags),
+ widget_(new ViewSourceWidget(this)),
+ update_timer_(new QTimer(this))
{
- widget_ = new ViewSourceWidget(*this);
setWidget(widget_);
+
+ // setting the update timer
+ update_timer_->setSingleShot(true);
+ connect(update_timer_, SIGNAL(timeout()),
+ this, SLOT(realUpdateView()));
+
+ connect(widget_, SIGNAL(needUpdate()), this, SLOT(scheduleUpdateNow()));
}
-GuiViewSource::~GuiViewSource()
+void GuiViewSource::onBufferViewChanged()
{
- delete widget_;
+ widget_->setText();
+ widget_->setEnabled((bool)bufferview());
}
void GuiViewSource::updateView()
{
- widget_->updateView();
+ if (widget_->autoUpdateCB->isChecked()) {
+ widget_->setEnabled((bool)bufferview());
+ scheduleUpdate();
+ }
+ widget_->masterPerspectiveCB->setEnabled(buffer().parent());
+ updateTitle();
+}
+
+
+void GuiViewSource::enableView(bool enable)
+{
+ widget_->setEnabled((bool)bufferview());
+ if (bufferview())
+ widget_->updateDefaultFormat(*bufferview());
+ if (!enable)
+ // In the opposite case, updateView() will be called anyway.
+ widget_->contentsChanged();
}
bool GuiViewSource::initialiseParams(string const & /*source*/)
{
- setWindowTitle(title());
+ updateTitle();
return true;
}
-QString GuiViewSource::getContent(bool fullSource)
+void GuiViewSource::updateTitle()
{
- // get the *top* level paragraphs that contain the cursor,
- // or the selected text
- pit_type par_begin;
- pit_type par_end;
-
- BufferView * view = bufferview();
- 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;
- view->buffer().getSourceCode(ostr, par_begin, par_end + 1, fullSource);
- return toqstr(ostr.str());
+ docstring const format = widget_->currentFormatName(bufferview());
+ QString const title = format.empty() ? qt_("Code Preview")
+ : qt_("%1[[preview format name]] Preview")
+ .arg(toqstr(translateIfPossible(format)));
+ setTitle(title);
+ setWindowTitle(title);
}
-GuiViewSource::Row GuiViewSource::getRows() const
+void GuiViewSource::saveSession() const
{
- BufferView const * view = bufferview();
- CursorSlice beg = view->cursor().selectionBegin().bottom();
- CursorSlice end = view->cursor().selectionEnd().bottom();
-
- int begrow = view->buffer().texrow().
- getRowFromIdPos(beg.paragraph().id(), beg.pos());
- int endrow = view->buffer().texrow().
- getRowFromIdPos(end.paragraph().id(), end.pos());
- int nextendrow = view->buffer().texrow().
- getRowFromIdPos(end.paragraph().id(), end.pos() + 1);
- Row row;
- row.begin = begrow;
- row.end = endrow == nextendrow ? endrow : (nextendrow - 1);
- return row;
+ Dialog::saveSession();
+ widget_->saveSession(sessionKey());
}
-QString GuiViewSource::title() const
+void GuiViewSource::restoreSession()
{
- switch (docType()) {
- case LATEX:
- return qt_("LaTeX Source");
- case DOCBOOK:
- return qt_("DocBook Source");
- case LITERATE:
- return qt_("Literate Source");
- }
- LASSERT(false, /**/);
- return QString();
+ DockView::restoreSession();
+ widget_->restoreSession(sessionKey());
}
} // namespace frontend
} // namespace lyx
-#include "GuiViewSource_moc.cpp"
+#include "moc_GuiViewSource.cpp"