2 * \file GuiViewSource.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
8 * \author Abdelrazak Younes
10 * Full author contact details are available in file CREDITS.
15 #include "GuiApplication.h"
16 #include "GuiViewSource.h"
17 #include "LaTeXHighlighter.h"
18 #include "qt_helpers.h"
20 #include "BufferParams.h"
21 #include "BufferView.h"
25 #include "FuncRequest.h"
27 #include "Paragraph.h"
30 #include "support/debug.h"
31 #include "support/lassert.h"
32 #include "support/docstream.h"
33 #include "support/docstring_list.h"
34 #include "support/gettext.h"
36 #include <boost/crc.hpp>
42 #include <QTextCursor>
43 #include <QTextDocument>
52 ViewSourceWidget::ViewSourceWidget(QWidget * parent)
54 document_(new QTextDocument(this)),
55 highlighter_(new LaTeXHighlighter(document_))
59 connect(contentsCO, SIGNAL(activated(int)),
60 this, SLOT(contentsChanged()));
61 connect(autoUpdateCB, SIGNAL(toggled(bool)),
62 updatePB, SLOT(setDisabled(bool)));
63 connect(autoUpdateCB, SIGNAL(toggled(bool)),
64 this, SLOT(contentsChanged()));
65 connect(masterPerspectiveCB, SIGNAL(toggled(bool)),
66 this, SLOT(contentsChanged()));
67 connect(updatePB, SIGNAL(clicked()),
68 this, SIGNAL(needUpdate()));
69 connect(outputFormatCO, SIGNAL(activated(int)),
70 this, SLOT(setViewFormat(int)));
72 // setting a document at this point trigger an assertion in Qt
73 // so we disable the signals here:
74 document_->blockSignals(true);
75 viewSourceTV->setDocument(document_);
78 document_->blockSignals(false);
79 viewSourceTV->setReadOnly(true);
80 ///dialog_->viewSourceTV->setAcceptRichText(false);
81 // this is personal. I think source code should be in fixed-size font
82 viewSourceTV->setFont(guiApp->typewriterSystemFont());
83 // again, personal taste
84 viewSourceTV->setWordWrapMode(QTextOption::NoWrap);
86 // catch double click events
87 viewSourceTV->viewport()->installEventFilter(this);
91 void ViewSourceWidget::getContent(BufferView const & view,
92 Buffer::OutputWhat output, docstring & str, string const & format,
95 // get the *top* level paragraphs that contain the cursor,
96 // or the selected text
100 if (!view.cursor().selection()) {
101 par_begin = view.cursor().bottom().pit();
104 par_begin = view.cursor().selectionBegin().bottom().pit();
105 par_end = view.cursor().selectionEnd().bottom().pit();
107 if (par_begin > par_end)
108 swap(par_begin, par_end);
109 odocstringstream ostr;
110 texrow_ = view.buffer()
111 .getSourceCode(ostr, format, par_begin, par_end + 1, output, master);
112 //ensure that the last line can always be selected in its full width
113 str = ostr.str() + "\n";
117 bool ViewSourceWidget::setText(QString const & qstr)
119 bool const changed = document_->toPlainText() != qstr;
120 viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
122 document_->setPlainText(qstr);
127 void ViewSourceWidget::contentsChanged()
129 if (autoUpdateCB->isChecked())
134 void ViewSourceWidget::setViewFormat(int const index)
136 outputFormatCO->setCurrentIndex(index);
137 string format = fromqstr(outputFormatCO->itemData(index).toString());
138 if (view_format_ != format) {
139 view_format_ = format;
145 int ViewSourceWidget::updateDelay() const
147 const int long_delay = 400;
148 const int short_delay = 60;
149 // a shorter delay if just the current paragraph is shown
150 return (contentsCO->currentIndex() == 0) ? short_delay : long_delay;
154 void GuiViewSource::scheduleUpdate()
156 update_timer_->start(widget_->updateDelay());
160 void GuiViewSource::scheduleUpdateNow()
162 update_timer_->start(0);
166 void GuiViewSource::realUpdateView()
168 widget_->updateView(bufferview());
173 void ViewSourceWidget::updateView(BufferView const * bv)
183 // we will try to get that much space around the cursor
184 int const v_margin = 3;
185 int const h_margin = 10;
186 // we will try to preserve this
187 int const h_scroll = viewSourceTV->horizontalScrollBar()->value();
189 Buffer::OutputWhat output = Buffer::CurrentParagraph;
190 if (contentsCO->currentIndex() == 1)
191 output = Buffer::FullSource;
192 else if (contentsCO->currentIndex() == 2)
193 output = Buffer::OnlyPreamble;
194 else if (contentsCO->currentIndex() == 3)
195 output = Buffer::OnlyBody;
198 getContent(*bv, output, content, view_format_,
199 masterPerspectiveCB->isChecked());
200 QString old = document_->toPlainText();
201 QString qcontent = toqstr(content);
202 if (guiApp->currentView()->develMode()) {
203 // output tex<->row correspondences in the source panel if the "-dbg latex"
205 if (texrow_ && lyx::lyxerr.debugging(Debug::LATEX)) {
206 QStringList list = qcontent.split(QChar('\n'));
207 docstring_list dlist;
208 for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
209 dlist.push_back(from_utf8(fromqstr(*it)));
210 texrow_->prepend(dlist);
212 for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
213 qcontent += toqstr(*it) + '\n';
217 // prevent gotoCursor()
218 QSignalBlocker blocker(viewSourceTV);
219 bool const changed = setText(qcontent);
221 if (changed && !texrow_) {
222 // position-to-row is unavailable
223 // we jump to the first modification
224 int length = min(old.length(), qcontent.length());
226 for (; pos < length && old.at(pos) == qcontent.at(pos); ++pos) {}
227 QTextCursor c = QTextCursor(viewSourceTV->document());
228 //get some space below the cursor
230 c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,v_margin);
231 viewSourceTV->setTextCursor(c);
232 //get some space on the right of the cursor
233 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
235 const int block = c.blockNumber();
236 for (int i = h_margin; i && block == c.blockNumber(); --i) {
237 c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
239 c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
240 viewSourceTV->setTextCursor(c);
241 //back to the position
243 //c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
244 viewSourceTV->setTextCursor(c);
246 } else if (texrow_) {
247 // Use the available position-to-row conversion to highlight
248 // the current selection in the source
249 std::pair<int,int> rows = texrow_->rowFromCursor(bv->cursor());
250 int const beg_row = rows.first;
251 int const end_row = rows.second;
253 QTextCursor c = QTextCursor(viewSourceTV->document());
255 c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
257 const int beg_sel = c.position();
258 //get some space above the cursor
259 c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
261 viewSourceTV->setTextCursor(c);
262 c.setPosition(beg_sel, QTextCursor::MoveAnchor);
264 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
265 end_row - beg_row +1);
266 const int end_sel = c.position();
267 //get some space below the cursor
268 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
270 viewSourceTV->setTextCursor(c);
271 c.setPosition(end_sel, QTextCursor::KeepAnchor);
273 viewSourceTV->setTextCursor(c);
275 //the real highlighting is done with an ExtraSelection
276 QTextCharFormat format;
278 // We create a new color with the lightness of AlternateBase and
279 // the hue and saturation of Highlight
280 QPalette palette = viewSourceTV->palette();
281 QBrush alt = palette.alternateBase();
282 QColor high = palette.highlight().color().toHsl();
283 QColor col = QColor::fromHsl(high.hue(),
284 high.hslSaturation(),
285 alt.color().lightness());
287 format.setBackground(alt);
289 format.setProperty(QTextFormat::FullWidthSelection, true);
290 QTextEdit::ExtraSelection sel;
293 viewSourceTV->setExtraSelections(
294 QList<QTextEdit::ExtraSelection>() << sel);
298 viewSourceTV->setTextCursor(c);
299 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
300 } // else if (texrow)
304 docstring ViewSourceWidget::currentFormatName(BufferView const * bv) const
306 // Compute the actual format used
307 string const format = !bv ? ""
308 : flavor2format(bv->buffer().params().getOutputFlavor(view_format_));
309 Format const * f = theFormats().getFormat(format.empty() ? view_format_ : format);
310 return f ? f->prettyname() : from_utf8(view_format_);
314 bool ViewSourceWidget::eventFilter(QObject * obj, QEvent * ev)
316 // this event filter is installed on the viewport of the QTextView
317 if (obj == viewSourceTV->viewport() &&
318 ev->type() == QEvent::MouseButtonDblClick) {
326 void ViewSourceWidget::goToCursor() const
330 int row = viewSourceTV->textCursor().blockNumber() + 1;
331 dispatch(texrow_->goToFuncFromRow(row));
336 void ViewSourceWidget::updateDefaultFormat(BufferView const & bv)
338 QSignalBlocker blocker(outputFormatCO);
339 outputFormatCO->clear();
340 outputFormatCO->addItem(qt_("Default"),
341 QVariant(QString("default")));
344 for (string const & fmt_name : bv.buffer().params().backends()) {
345 Format const * fmt = theFormats().getFormat(fmt_name);
347 LYXERR0("Can't find format for backend " << fmt_name << "!");
350 QString const pretty = toqstr(translateIfPossible(fmt->prettyname()));
351 outputFormatCO->addItem(pretty, QVariant(toqstr(fmt_name)));
352 if (fmt_name == view_format_)
353 index = outputFormatCO->count() - 1;
355 setViewFormat(index);
359 void ViewSourceWidget::resizeEvent (QResizeEvent * event)
361 QSize const & formSize = formLayout->sizeHint();
362 // minimize the size of the part that contains the buttons
363 if (width() * formSize.height() < height() * formSize.width()) {
364 layout_->setDirection(QBoxLayout::TopToBottom);
366 layout_->setDirection(QBoxLayout::LeftToRight);
368 QWidget::resizeEvent(event);
372 void ViewSourceWidget::saveSession(QSettings & settings, QString const & session_key) const
374 settings.setValue(session_key + "/output", toqstr(view_format_));
375 settings.setValue(session_key + "/contents", contentsCO->currentIndex());
376 settings.setValue(session_key + "/autoupdate", autoUpdateCB->isChecked());
377 settings.setValue(session_key + "/masterview",
378 masterPerspectiveCB->isChecked());
382 void ViewSourceWidget::restoreSession(QString const & session_key)
385 view_format_ = fromqstr(settings.value(session_key + "/output", 0)
387 contentsCO->setCurrentIndex(settings
388 .value(session_key + "/contents", 0)
390 masterPerspectiveCB->setChecked(settings
391 .value(session_key + "/masterview", false)
393 bool const checked = settings
394 .value(session_key + "/autoupdate", true)
396 autoUpdateCB->setChecked(checked);
402 GuiViewSource::GuiViewSource(GuiView & parent,
403 Qt::DockWidgetArea area, Qt::WindowFlags flags)
404 : DockView(parent, "view-source", qt_("Code Preview"), area, flags),
405 widget_(new ViewSourceWidget(this)),
406 update_timer_(new QTimer(this))
410 // setting the update timer
411 update_timer_->setSingleShot(true);
412 connect(update_timer_, SIGNAL(timeout()),
413 this, SLOT(realUpdateView()));
415 connect(widget_, SIGNAL(needUpdate()), this, SLOT(scheduleUpdateNow()));
419 void GuiViewSource::onBufferViewChanged()
422 widget_->setEnabled((bool)bufferview());
426 void GuiViewSource::updateView()
428 if (widget_->autoUpdateCB->isChecked()) {
429 widget_->setEnabled((bool)bufferview());
432 widget_->masterPerspectiveCB->setEnabled(buffer().parent());
437 void GuiViewSource::enableView(bool enable)
439 widget_->setEnabled((bool)bufferview());
441 widget_->updateDefaultFormat(*bufferview());
443 // In the opposite case, updateView() will be called anyway.
444 widget_->contentsChanged();
448 bool GuiViewSource::initialiseParams(string const & /*source*/)
455 void GuiViewSource::updateTitle()
457 docstring const format = widget_->currentFormatName(bufferview());
458 QString const title = format.empty() ? qt_("Code Preview")
459 : qt_("%1[[preview format name]] Preview")
460 .arg(toqstr(translateIfPossible(format)));
462 setWindowTitle(title);
466 void GuiViewSource::saveSession(QSettings & settings) const
468 Dialog::saveSession(settings);
469 widget_->saveSession(settings, sessionKey());
473 void GuiViewSource::restoreSession()
475 DockView::restoreSession();
476 widget_->restoreSession(sessionKey());
480 Dialog * createGuiViewSource(GuiView & lv)
482 return new GuiViewSource(lv);
486 } // namespace frontend
489 #include "moc_GuiViewSource.cpp"