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 "GuiViewSource.h"
17 #include "GuiApplication.h"
18 #include "LaTeXHighlighter.h"
19 #include "qt_helpers.h"
21 #include "BufferParams.h"
22 #include "BufferView.h"
26 #include "FuncRequest.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"
39 #include <QTextCursor>
40 #include <QTextDocument>
49 ViewSourceWidget::ViewSourceWidget(QWidget * parent)
51 document_(new QTextDocument(this)),
52 highlighter_(new LaTeXHighlighter(document_))
56 connect(contentsCO, SIGNAL(activated(int)),
57 this, SLOT(contentsChanged()));
58 connect(autoUpdateCB, SIGNAL(toggled(bool)),
59 updatePB, SLOT(setDisabled(bool)));
60 connect(autoUpdateCB, SIGNAL(toggled(bool)),
61 this, SLOT(contentsChanged()));
62 connect(masterPerspectiveCB, SIGNAL(toggled(bool)),
63 this, SLOT(contentsChanged()));
64 connect(updatePB, SIGNAL(clicked()),
65 this, SIGNAL(needUpdate()));
66 connect(outputFormatCO, SIGNAL(activated(int)),
67 this, SLOT(setViewFormat(int)));
69 // setting a document at this point trigger an assertion in Qt
70 // so we disable the signals here:
71 document_->blockSignals(true);
72 viewSourceTV->setDocument(document_);
75 document_->blockSignals(false);
76 viewSourceTV->setReadOnly(true);
77 ///dialog_->viewSourceTV->setAcceptRichText(false);
78 // this is personal. I think source code should be in fixed-size font
79 viewSourceTV->setFont(guiApp->typewriterSystemFont());
80 // again, personal taste
81 viewSourceTV->setWordWrapMode(QTextOption::NoWrap);
83 // catch double click events
84 viewSourceTV->viewport()->installEventFilter(this);
88 void ViewSourceWidget::getContent(BufferView const & view,
89 Buffer::OutputWhat output, docstring & str, string const & format,
92 // get the *top* level paragraphs that contain the cursor,
93 // or the selected text
97 if (!view.cursor().selection()) {
98 par_begin = view.cursor().bottom().pit();
101 par_begin = view.cursor().selectionBegin().bottom().pit();
102 par_end = view.cursor().selectionEnd().bottom().pit();
104 if (par_begin > par_end)
105 swap(par_begin, par_end);
106 odocstringstream ostr;
107 texrow_ = view.buffer()
108 .getSourceCode(ostr, format, par_begin, par_end + 1, output, master);
109 //ensure that the last line can always be selected in its full width
110 str = ostr.str() + "\n";
114 bool ViewSourceWidget::setText(QString const & qstr)
116 bool const changed = document_->toPlainText() != qstr;
117 viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
119 document_->setPlainText(qstr);
124 void ViewSourceWidget::contentsChanged()
126 if (autoUpdateCB->isChecked())
131 void ViewSourceWidget::setViewFormat(int const index)
133 outputFormatCO->setCurrentIndex(index);
134 string format = fromqstr(outputFormatCO->itemData(index).toString());
135 if (view_format_ != format) {
136 view_format_ = format;
142 int ViewSourceWidget::updateDelay() const
144 const int long_delay = 400;
145 const int short_delay = 60;
146 // a shorter delay if just the current paragraph is shown
147 return (contentsCO->currentIndex() == 0) ? short_delay : long_delay;
151 void GuiViewSource::scheduleUpdate()
153 update_timer_->start(widget_->updateDelay());
157 void GuiViewSource::scheduleUpdateNow()
159 update_timer_->start(0);
163 void GuiViewSource::realUpdateView()
165 widget_->updateView(bufferview());
170 void ViewSourceWidget::updateView(BufferView const * bv)
180 // we will try to get that much space around the cursor
181 int const v_margin = 3;
182 int const h_margin = 10;
183 // we will try to preserve this
184 int const h_scroll = viewSourceTV->horizontalScrollBar()->value();
186 Buffer::OutputWhat output = Buffer::CurrentParagraph;
187 if (contentsCO->currentIndex() == 1)
188 output = Buffer::FullSource;
189 else if (contentsCO->currentIndex() == 2)
190 output = Buffer::OnlyPreamble;
191 else if (contentsCO->currentIndex() == 3)
192 output = Buffer::OnlyBody;
195 getContent(*bv, output, content, view_format_,
196 masterPerspectiveCB->isChecked());
197 QString old = document_->toPlainText();
198 QString qcontent = toqstr(content);
199 if (guiApp->currentView()->develMode()) {
200 // output tex<->row correspondences in the source panel if the "-dbg latex"
202 if (texrow_ && lyx::lyxerr.debugging(Debug::OUTFILE)) {
203 QStringList list = qcontent.split(QChar('\n'));
204 docstring_list dlist;
205 for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
206 dlist.push_back(from_utf8(fromqstr(*it)));
207 texrow_->prepend(dlist);
209 for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
210 qcontent += toqstr(*it) + '\n';
214 // prevent gotoCursor()
215 QSignalBlocker blocker(viewSourceTV);
216 bool const changed = setText(qcontent);
218 if (changed && !texrow_) {
219 // position-to-row is unavailable
220 // we jump to the first modification
221 int length = min(old.length(), qcontent.length());
223 for (; pos < length && old.at(pos) == qcontent.at(pos); ++pos) {}
224 QTextCursor c = QTextCursor(viewSourceTV->document());
225 //get some space below the cursor
227 c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,v_margin);
228 viewSourceTV->setTextCursor(c);
229 //get some space on the right of the cursor
230 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
232 const int block = c.blockNumber();
233 for (int i = h_margin; i && block == c.blockNumber(); --i) {
234 c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
236 c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
237 viewSourceTV->setTextCursor(c);
238 //back to the position
240 //c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
241 viewSourceTV->setTextCursor(c);
243 } else if (texrow_) {
244 // Use the available position-to-row conversion to highlight
245 // the current selection in the source
246 std::pair<int,int> rows = texrow_->rowFromCursor(bv->cursor());
247 int const beg_row = rows.first;
248 int const end_row = rows.second;
250 QTextCursor c = QTextCursor(viewSourceTV->document());
252 c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
254 const int beg_sel = c.position();
255 //get some space above the cursor
256 c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
258 viewSourceTV->setTextCursor(c);
259 c.setPosition(beg_sel, QTextCursor::MoveAnchor);
261 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
262 end_row - beg_row +1);
263 const int end_sel = c.position();
264 //get some space below the cursor
265 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
267 viewSourceTV->setTextCursor(c);
268 c.setPosition(end_sel, QTextCursor::KeepAnchor);
270 viewSourceTV->setTextCursor(c);
272 //the real highlighting is done with an ExtraSelection
273 QTextCharFormat format;
275 // We create a new color with the slightly altered lightness
276 // of the Base and the hue and saturation of the Highlight brush
277 QPalette const palette = viewSourceTV->palette();
278 QBrush extraselbrush = palette.base();
279 int const extrasellightness =
280 (palette.text().color().black() > palette.window().color().black()) ?
281 extraselbrush.color().darker(107).lightness()// light mode
282 : extraselbrush.color().darker().lightness();// dark mode
283 QColor const highlight = palette.highlight().color().toHsl();
284 QColor const extraselcol = QColor::fromHsl(highlight.hue(),
285 highlight.hslSaturation(),
287 extraselbrush.setColor(extraselcol);
288 format.setBackground(extraselbrush);
290 format.setProperty(QTextFormat::FullWidthSelection, true);
291 QTextEdit::ExtraSelection sel;
294 viewSourceTV->setExtraSelections(
295 QList<QTextEdit::ExtraSelection>() << sel);
299 viewSourceTV->setTextCursor(c);
300 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
301 } // else if (texrow)
305 docstring ViewSourceWidget::currentFormatName(BufferView const * bv) const
307 // Compute the actual format used
308 string const format = !bv ? ""
309 : flavor2format(bv->buffer().params().getOutputFlavor(view_format_));
310 Format const * f = theFormats().getFormat(format.empty() ? view_format_ : format);
311 return f ? f->prettyname() : from_utf8(view_format_);
315 bool ViewSourceWidget::eventFilter(QObject * obj, QEvent * ev)
317 // this event filter is installed on the viewport of the QTextView
318 if (obj == viewSourceTV->viewport() &&
319 ev->type() == QEvent::MouseButtonDblClick) {
327 void ViewSourceWidget::goToCursor() const
331 int row = viewSourceTV->textCursor().blockNumber() + 1;
332 dispatch(texrow_->goToFuncFromRow(row));
337 void ViewSourceWidget::updateDefaultFormat(BufferView const & bv)
339 QSignalBlocker blocker(outputFormatCO);
340 outputFormatCO->clear();
341 outputFormatCO->addItem(qt_("Default"),
342 QVariant(QString("default")));
345 for (string const & fmt_name : bv.buffer().params().backends()) {
346 Format const * fmt = theFormats().getFormat(fmt_name);
348 LYXERR0("Can't find format for backend " << fmt_name << "!");
351 QString const pretty = toqstr(translateIfPossible(fmt->prettyname()));
352 outputFormatCO->addItem(pretty, QVariant(toqstr(fmt_name)));
353 if (fmt_name == view_format_)
354 index = outputFormatCO->count() - 1;
356 setViewFormat(index);
360 void ViewSourceWidget::resizeEvent (QResizeEvent * event)
362 QSize const & formSize = gridLayout->sizeHint();
363 // minimize the size of the part that contains the buttons
364 if (width() * formSize.height() < height() * formSize.width()) {
365 layout_->setDirection(QBoxLayout::TopToBottom);
367 layout_->setDirection(QBoxLayout::LeftToRight);
369 QWidget::resizeEvent(event);
373 void ViewSourceWidget::saveSession(QSettings & settings, QString const & session_key) const
375 settings.setValue(session_key + "/output", toqstr(view_format_));
376 settings.setValue(session_key + "/contents", contentsCO->currentIndex());
377 settings.setValue(session_key + "/autoupdate", autoUpdateCB->isChecked());
378 settings.setValue(session_key + "/masterview",
379 masterPerspectiveCB->isChecked());
383 void ViewSourceWidget::restoreSession(QString const & session_key)
386 view_format_ = fromqstr(settings.value(session_key + "/output", 0)
388 contentsCO->setCurrentIndex(settings
389 .value(session_key + "/contents", 0)
391 masterPerspectiveCB->setChecked(settings
392 .value(session_key + "/masterview", false)
394 bool const checked = settings
395 .value(session_key + "/autoupdate", true)
397 autoUpdateCB->setChecked(checked);
403 GuiViewSource::GuiViewSource(GuiView & parent,
404 Qt::DockWidgetArea area, Qt::WindowFlags flags)
405 : DockView(parent, "view-source", qt_("Code Preview"), area, flags),
406 widget_(new ViewSourceWidget(this)),
407 update_timer_(new QTimer(this))
411 // setting the update timer
412 update_timer_->setSingleShot(true);
413 connect(update_timer_, SIGNAL(timeout()),
414 this, SLOT(realUpdateView()));
416 connect(widget_, SIGNAL(needUpdate()), this, SLOT(scheduleUpdateNow()));
420 void GuiViewSource::onBufferViewChanged()
423 widget_->setEnabled(static_cast<bool>(bufferview()));
427 void GuiViewSource::updateView()
429 if (widget_->autoUpdateCB->isChecked()) {
430 widget_->setEnabled(static_cast<bool>(bufferview()));
433 widget_->masterPerspectiveCB->setEnabled(buffer().parent());
438 void GuiViewSource::enableView(bool enable)
440 widget_->setEnabled(static_cast<bool>(bufferview()));
442 widget_->updateDefaultFormat(*bufferview());
444 // In the opposite case, updateView() will be called anyway.
445 widget_->contentsChanged();
449 bool GuiViewSource::initialiseParams(string const & /*source*/)
456 void GuiViewSource::updateTitle()
458 docstring const format = widget_->currentFormatName(bufferview());
459 QString const title = format.empty() ? qt_("Code Preview")
460 : qt_("%1[[preview format name]] Preview")
461 .arg(toqstr(translateIfPossible(format)));
463 setWindowTitle(title);
467 void GuiViewSource::saveSession(QSettings & settings) const
469 Dialog::saveSession(settings);
470 widget_->saveSession(settings, sessionKey());
474 void GuiViewSource::restoreSession()
476 DockView::restoreSession();
477 widget_->restoreSession(sessionKey());
481 } // namespace frontend
484 #include "moc_GuiViewSource.cpp"