]> git.lyx.org Git - lyx.git/blobdiff - src/frontends/qt4/GuiViewSource.cpp
Use <cstdint> instead of <boost/cstdint.hpp>
[lyx.git] / src / frontends / qt4 / GuiViewSource.cpp
index 0a6d13544f22d8af551f98904c1b6244abe1a7f2..04c6bf5c349796661315388d72b26d91c9febab9 100644 (file)
 
 #include <config.h>
 
+#include "GuiApplication.h"
 #include "GuiViewSource.h"
+#include "LaTeXHighlighter.h"
 #include "qt_helpers.h"
 
-#include <boost/tuple/tuple.hpp>
+#include "BufferParams.h"
+#include "BufferView.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 {
 
-/////////////////////////////////////////////////////////////////////
-//
-// GuiViewSourceDialog
-//
-/////////////////////////////////////////////////////////////////////
-
-GuiViewSourceDialog::GuiViewSourceDialog(GuiViewSource * form)
-       : form_(form)
+ViewSourceWidget::ViewSourceWidget(QWidget * parent)
+       :       QWidget(parent),
+               document_(new QTextDocument(this)),
+               highlighter_(new LaTeXHighlighter(document_))
 {
        setupUi(this);
 
-       connect(viewFullSourceCB, SIGNAL(clicked()),
-               this, SLOT(update()));
+       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(update()));
+               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:
-       form_->document()->blockSignals(true);
-       viewSourceTV->setDocument(form_->document());
-       form_->document()->blockSignals(false);
+       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(toqstr(theApp()->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::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 GuiViewSourceDialog::updateView()
+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())
-               form_->update(viewFullSourceCB->isChecked());
+               Q_EMIT needUpdate();
+}
+
+
+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;
+}
+
 
-       int beg, end;
-       boost::tie(beg, end) = form_->getRows();
-       QTextCursor c = QTextCursor(viewSourceTV->document());
-       c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor, beg);
-       c.select(QTextCursor::BlockUnderCursor);
-       c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor, end - beg + 1);
-       viewSourceTV->setTextCursor(c);
-       QWidget::update();
+void GuiViewSource::scheduleUpdate()
+{
+       update_timer_->start(widget_->updateDelay());
 }
 
 
-/////////////////////////////////////////////////////////////////////
-//
-// LaTeXHighlighter
-//
-/////////////////////////////////////////////////////////////////////
+void GuiViewSource::scheduleUpdateNow()
+{
+       update_timer_->start(0);
+}
 
 
-LaTeXHighlighter::LaTeXHighlighter(QTextDocument * parent)
-       : QSyntaxHighlighter(parent)
+void GuiViewSource::realUpdateView()
 {
-       keywordFormat.setForeground(Qt::darkBlue);
-       keywordFormat.setFontWeight(QFont::Bold);
-       commentFormat.setForeground(Qt::darkGray);
-       mathFormat.setForeground(Qt::red);
+       widget_->updateView(bufferview());
+       updateTitle();
 }
 
 
-void LaTeXHighlighter::highlightBlock(QString const & text)
+void ViewSourceWidget::updateView(BufferView const * bv)
 {
-       // $ $
-       QRegExp exprMath("\\$[^\\$]*\\$");
-       int index = text.indexOf(exprMath);
-       while (index >= 0) {
-               int length = exprMath.matchedLength();
-               setFormat(index, length, mathFormat);
-               index = text.indexOf(exprMath, index + length);
+       if (!bv) {
+               setText();
+               setEnabled(false);
+               return;
        }
-       // [ ]
-       QRegExp exprStartDispMath("(\\\\\\[|"
-               "\\\\begin\\{equation\\**\\}|"
-               "\\\\begin\\{eqnarray\\**\\}|"
-               "\\\\begin\\{align(ed|at)*\\**\\}|"
-               "\\\\begin\\{flalign\\**\\}|"
-               "\\\\begin\\{gather\\**\\}|"
-               "\\\\begin\\{multline\\**\\}|"
-               "\\\\begin\\{array\\**\\}|"
-               "\\\\begin\\{cases\\**\\}"
-               ")");
-       QRegExp exprEndDispMath("(\\\\\\]|"
-               "\\\\end\\{equation\\**\\}|"
-               "\\\\end\\{eqnarray\\**\\}|"
-               "\\\\end\\{align(ed|at)*\\**\\}|"
-               "\\\\end\\{flalign\\**\\}|"
-               "\\\\end\\{gather\\**\\}|"
-               "\\\\end\\{multline\\**\\}|"
-               "\\\\end\\{array\\**\\}|"
-               "\\\\end\\{cases\\**\\}"
-               ")");
-       int startIndex = 0;
-       // if previous block was in 'disp math'
-       // start search from 0 (for end disp math)
-       // otherwise, start search from 'begin disp math'
-       if (previousBlockState() != 1)
-               startIndex = text.indexOf(exprStartDispMath);
-       while (startIndex >= 0) {
-               int endIndex = text.indexOf(exprEndDispMath, startIndex);
-               int length;
-               if (endIndex == -1) {
-                       setCurrentBlockState(1);
-                       length = text.length() - startIndex;
-               } else {
-                       length = endIndex - startIndex + exprEndDispMath.matchedLength();
+
+       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);
+       if (guiApp->currentView()->develMode()) {
+               // 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';
                }
-               setFormat(startIndex, length, mathFormat);
-               startIndex = text.indexOf(exprStartDispMath, startIndex + length);
        }
-       // \whatever
-       QRegExp exprKeyword("\\\\[A-Za-z]+");
-       index = text.indexOf(exprKeyword);
-       while (index >= 0) {
-               int length = exprKeyword.matchedLength();
-               setFormat(index, length, keywordFormat);
-               index = text.indexOf(exprKeyword, index + length);
+
+       // 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)
+}
+
+
+docstring ViewSourceWidget::currentFormatName(BufferView const * bv) const
+{
+       // Compute the actual format used
+       string const format = !bv ? ""
+               : flavor2format(bv->buffer().params().getOutputFlavor(view_format_));
+       Format const * f = theFormats().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;
        }
-       // comment
-       QRegExp exprComment("(^|[^\\\\])%.*$");
-       index = text.indexOf(exprComment);
-       while (index >= 0) {
-               int const length = exprComment.matchedLength();
-               setFormat(index, length, commentFormat);
-               index = text.indexOf(exprComment, index + length);
+       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;
+       for (string const & fmt_name : bv.buffer().params().backends()) {
+               Format const * fmt = theFormats().getFormat(fmt_name);
+               if (!fmt) {
+                       LYXERR0("Can't find format for backend " << fmt_name << "!");
+                       continue;
+               }
+               QString const pretty = toqstr(translateIfPossible(fmt->prettyname()));
+               outputFormatCO->addItem(pretty, QVariant(toqstr(fmt_name)));
+               if (fmt_name == view_format_)
+                       index = outputFormatCO->count() - 1;
        }
+       setViewFormat(index);
 }
 
 
-GuiViewSource::GuiViewSource(GuiDialog & parent)
-       : ControlViewSource(parent)
+void ViewSourceWidget::resizeEvent (QResizeEvent * event)
 {
-       document_ = new QTextDocument(this);
-       highlighter_ = new LaTeXHighlighter(document_);
+       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);
 }
 
 
-/////////////////////////////////////////////////////////////////////
-//
-// GuiViewSource
-//
-/////////////////////////////////////////////////////////////////////
+void ViewSourceWidget::saveSession(QSettings & settings, QString const & session_key) const
+{
+       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_("Code Preview"), area, flags),
+         widget_(new ViewSourceWidget(this)),
+         update_timer_(new QTimer(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()));
+}
+
+
+void GuiViewSource::onBufferViewChanged()
+{
+       widget_->setText();
+       widget_->setEnabled((bool)bufferview());
+}
+
+
+void GuiViewSource::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*/)
+{
+       updateTitle();
+       return true;
+}
+
+
+void GuiViewSource::updateTitle()
+{
+       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);
+}
+
+
+void GuiViewSource::saveSession(QSettings & settings) const
+{
+       Dialog::saveSession(settings);
+       widget_->saveSession(settings, sessionKey());
+}
+
+
+void GuiViewSource::restoreSession()
+{
+       DockView::restoreSession();
+       widget_->restoreSession(sessionKey());
+}
+
 
-void GuiViewSource::update(bool full_source)
+Dialog * createGuiViewSource(GuiView & lv)
 {
-       document_->setPlainText(toqstr(updateContent(full_source)));
+       return new GuiViewSource(lv);
 }
 
 
 } // namespace frontend
 } // namespace lyx
 
-#include "GuiViewSource_moc.cpp"
+#include "moc_GuiViewSource.cpp"