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"
24 #include "FuncRequest.h"
26 #include "Paragraph.h"
29 #include "support/debug.h"
30 #include "support/lassert.h"
31 #include "support/docstream.h"
32 #include "support/docstring_list.h"
33 #include "support/gettext.h"
35 #include <boost/crc.hpp>
41 #include <QTextCursor>
42 #include <QTextDocument>
50 ViewSourceWidget::ViewSourceWidget()
51 : bv_(0), document_(new QTextDocument(this)),
52 highlighter_(new LaTeXHighlighter(document_)),
53 update_timer_(new QTimer(this))
57 connect(contentsCO, SIGNAL(activated(int)),
58 this, SLOT(contentsChanged()));
59 connect(autoUpdateCB, SIGNAL(toggled(bool)),
60 updatePB, SLOT(setDisabled(bool)));
61 connect(autoUpdateCB, SIGNAL(toggled(bool)),
62 this, SLOT(contentsChanged()));
63 connect(masterPerspectiveCB, SIGNAL(toggled(bool)),
64 this, SLOT(contentsChanged()));
65 connect(updatePB, SIGNAL(clicked()),
66 this, SLOT(updateViewNow()));
67 connect(outputFormatCO, SIGNAL(activated(int)),
68 this, SLOT(setViewFormat(int)));
69 connect(outputFormatCO, SIGNAL(activated(int)),
70 this, SLOT(contentsChanged()));
72 // setting the update timer
73 update_timer_->setSingleShot(true);
74 connect(update_timer_, SIGNAL(timeout()),
75 this, SLOT(realUpdateView()));
77 // setting a document at this point trigger an assertion in Qt
78 // so we disable the signals here:
79 document_->blockSignals(true);
80 viewSourceTV->setDocument(document_);
83 document_->blockSignals(false);
84 viewSourceTV->setReadOnly(true);
85 ///dialog_->viewSourceTV->setAcceptRichText(false);
86 // this is personal. I think source code should be in fixed-size font
87 viewSourceTV->setFont(guiApp->typewriterSystemFont());
88 // again, personal taste
89 viewSourceTV->setWordWrapMode(QTextOption::NoWrap);
91 // catch double click events
92 viewSourceTV->viewport()->installEventFilter(this);
96 void ViewSourceWidget::getContent(BufferView const * view,
97 Buffer::OutputWhat output, docstring & str, string const & format,
100 // get the *top* level paragraphs that contain the cursor,
101 // or the selected text
105 if (!view->cursor().selection()) {
106 par_begin = view->cursor().bottom().pit();
109 par_begin = view->cursor().selectionBegin().bottom().pit();
110 par_end = view->cursor().selectionEnd().bottom().pit();
112 if (par_begin > par_end)
113 swap(par_begin, par_end);
114 odocstringstream ostr;
115 texrow_ = view->buffer()
116 .getSourceCode(ostr, format, par_begin, par_end + 1, output, master);
117 //ensure that the last line can always be selected in its full width
118 str = ostr.str() + "\n";
122 void ViewSourceWidget::setBufferView(BufferView const * bv)
128 setEnabled(bv ? true : false);
132 bool ViewSourceWidget::setText(QString const & qstr)
134 bool const changed = document_->toPlainText() != qstr;
135 viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
137 document_->setPlainText(qstr);
142 void ViewSourceWidget::contentsChanged()
144 if (autoUpdateCB->isChecked())
149 void ViewSourceWidget::setViewFormat(int const index)
151 outputFormatCO->setCurrentIndex(index);
152 string format = fromqstr(outputFormatCO->itemData(index).toString());
153 if (view_format_ != format) {
154 view_format_ = format;
161 void ViewSourceWidget::updateView()
163 const int long_delay = 400;
164 const int short_delay = 60;
165 // a shorter delay if just the current paragraph is shown
166 update_timer_->start((contentsCO->currentIndex() == 0) ?
167 short_delay : long_delay);
171 void ViewSourceWidget::updateViewNow()
173 update_timer_->start(0);
177 void ViewSourceWidget::realUpdateView()
187 // we will try to get that much space around the cursor
188 int const v_margin = 3;
189 int const h_margin = 10;
190 // we will try to preserve this
191 int const h_scroll = viewSourceTV->horizontalScrollBar()->value();
193 Buffer::OutputWhat output = Buffer::CurrentParagraph;
194 if (contentsCO->currentIndex() == 1)
195 output = Buffer::FullSource;
196 else if (contentsCO->currentIndex() == 2)
197 output = Buffer::OnlyPreamble;
198 else if (contentsCO->currentIndex() == 3)
199 output = Buffer::OnlyBody;
202 getContent(bv_, output, content, view_format_,
203 masterPerspectiveCB->isChecked());
204 QString old = document_->toPlainText();
205 QString qcontent = toqstr(content);
207 // output tex<->row correspondences in the source panel if the "-dbg latex"
209 if (texrow_ && lyx::lyxerr.debugging(Debug::LATEX)) {
210 QStringList list = qcontent.split(QChar('\n'));
211 docstring_list dlist;
212 for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
213 dlist.push_back(from_utf8(fromqstr(*it)));
214 texrow_->prepend(dlist);
216 for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
217 qcontent += toqstr(*it) + '\n';
220 // prevent gotoCursor()
221 viewSourceTV->blockSignals(true);
222 bool const changed = setText(qcontent);
224 if (changed && !texrow_) {
225 // position-to-row is unavailable
226 // we jump to the first modification
227 const QChar * oc = old.constData();
228 const QChar * nc = qcontent.constData();
230 while (*oc != '\0' && *nc != '\0' && *oc == *nc) {
235 QTextCursor c = QTextCursor(viewSourceTV->document());
236 //get some space below the cursor
238 c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,v_margin);
239 viewSourceTV->setTextCursor(c);
240 //get some space on the right of the cursor
241 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
243 const int block = c.blockNumber();
244 for (int i = h_margin; i && block == c.blockNumber(); --i) {
245 c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
247 c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
248 viewSourceTV->setTextCursor(c);
249 //back to the position
251 //c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
252 viewSourceTV->setTextCursor(c);
254 } else if (texrow_) {
255 // Use the available position-to-row conversion to highlight
256 // the current selection in the source
257 std::pair<int,int> rows = texrow_->rowFromCursor(bv_->cursor());
258 int const beg_row = rows.first;
259 int const end_row = rows.second;
261 QTextCursor c = QTextCursor(viewSourceTV->document());
263 c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
265 const int beg_sel = c.position();
266 //get some space above the cursor
267 c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
269 viewSourceTV->setTextCursor(c);
270 c.setPosition(beg_sel, QTextCursor::MoveAnchor);
272 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
273 end_row - beg_row +1);
274 const int end_sel = c.position();
275 //get some space below the cursor
276 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
278 viewSourceTV->setTextCursor(c);
279 c.setPosition(end_sel, QTextCursor::KeepAnchor);
281 viewSourceTV->setTextCursor(c);
283 //the real highlighting is done with an ExtraSelection
284 QTextCharFormat format;
286 // We create a new color with the lightness of AlternateBase and
287 // the hue and saturation of Highlight
288 QPalette palette = viewSourceTV->palette();
289 QBrush alt = palette.alternateBase();
290 QColor high = palette.highlight().color().toHsl();
291 QColor col = QColor::fromHsl(high.hue(),
292 high.hslSaturation(),
293 alt.color().lightness());
295 format.setBackground(alt);
297 format.setProperty(QTextFormat::FullWidthSelection, true);
298 QTextEdit::ExtraSelection sel;
301 viewSourceTV->setExtraSelections(
302 QList<QTextEdit::ExtraSelection>() << sel);
306 viewSourceTV->setTextCursor(c);
307 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
308 } // else if (texrow)
309 viewSourceTV->blockSignals(false);
313 docstring ViewSourceWidget::currentFormatName(BufferView const * bv) const
315 // Compute the actual format used
316 string const format = !bv ? ""
317 : flavor2format(bv->buffer().params().getOutputFlavor(view_format_));
318 Format const * f = formats.getFormat(format.empty() ? view_format_ : format);
319 return f ? f->prettyname() : from_utf8(view_format_);
323 bool ViewSourceWidget::eventFilter(QObject * obj, QEvent * ev)
325 // this event filter is installed on the viewport of the QTextView
326 if (obj == viewSourceTV->viewport() &&
327 ev->type() == QEvent::MouseButtonDblClick) {
335 void ViewSourceWidget::goToCursor() const
339 int row = viewSourceTV->textCursor().blockNumber() + 1;
340 dispatch(TexRow::goToFunc(texrow_->getEntriesFromRow(row)));
345 void ViewSourceWidget::updateDefaultFormat()
350 outputFormatCO->blockSignals(true);
351 outputFormatCO->clear();
352 outputFormatCO->addItem(qt_("Default"),
353 QVariant(QString("default")));
356 vector<string> tmp = bv_->buffer().params().backends();
357 vector<string>::const_iterator it = tmp.begin();
358 vector<string>::const_iterator en = tmp.end();
359 for (; it != en; ++it) {
360 string const format = *it;
361 Format const * fmt = formats.getFormat(format);
363 LYXERR0("Can't find format for backend " << format << "!");
367 QString const pretty = toqstr(translateIfPossible(fmt->prettyname()));
368 outputFormatCO->addItem(pretty, QVariant(toqstr(format)));
369 if (format == view_format_)
370 index = outputFormatCO->count() -1;
372 setViewFormat(index);
374 outputFormatCO->blockSignals(false);
378 void ViewSourceWidget::resizeEvent (QResizeEvent * event)
380 QSize const & formSize = formLayout->sizeHint();
381 // minimize the size of the part that contains the buttons
382 if (width() * formSize.height() < height() * formSize.width()) {
383 layout_->setDirection(QBoxLayout::TopToBottom);
385 layout_->setDirection(QBoxLayout::LeftToRight);
387 QWidget::resizeEvent(event);
390 void ViewSourceWidget::saveSession(QString const & session_key) const
393 settings.setValue(session_key + "/output", toqstr(view_format_));
394 settings.setValue(session_key + "/contents", contentsCO->currentIndex());
395 settings.setValue(session_key + "/autoupdate", autoUpdateCB->isChecked());
396 settings.setValue(session_key + "/masterview",
397 masterPerspectiveCB->isChecked());
401 void ViewSourceWidget::restoreSession(QString const & session_key)
404 view_format_ = fromqstr(settings.value(session_key + "/output", 0)
406 contentsCO->setCurrentIndex(settings
407 .value(session_key + "/contents", 0)
409 masterPerspectiveCB->setChecked(settings
410 .value(session_key + "/masterview", false)
412 bool const checked = settings
413 .value(session_key + "/autoupdate", true)
415 autoUpdateCB->setChecked(checked);
421 GuiViewSource::GuiViewSource(GuiView & parent,
422 Qt::DockWidgetArea area, Qt::WindowFlags flags)
423 : DockView(parent, "view-source", qt_("Code Preview"), area, flags)
425 widget_ = new ViewSourceWidget;
427 connect(widget_, SIGNAL(formatChanged()), this, SLOT(updateTitle()));
431 GuiViewSource::~GuiViewSource()
437 void GuiViewSource::updateView()
439 if (widget_->autoUpdateCB->isChecked()) {
440 widget_->setBufferView(bufferview());
441 widget_->updateView();
443 widget_->masterPerspectiveCB->setEnabled(buffer().parent());
448 void GuiViewSource::enableView(bool enable)
450 widget_->setBufferView(bufferview());
451 widget_->updateDefaultFormat();
453 // In the opposite case, updateView() will be called anyway.
454 widget_->contentsChanged();
458 bool GuiViewSource::initialiseParams(string const & /*source*/)
465 void GuiViewSource::updateTitle()
467 docstring const format = widget_->currentFormatName(bufferview());
468 QString const title = format.empty() ? qt_("Code Preview")
469 : qt_("%1[[preview format name]] Preview")
470 .arg(toqstr(translateIfPossible(format)));
472 setWindowTitle(title);
476 void GuiViewSource::saveSession() const
478 Dialog::saveSession();
479 widget_->saveSession(sessionKey());
483 void GuiViewSource::restoreSession()
485 DockView::restoreSession();
486 widget_->restoreSession(sessionKey());
490 Dialog * createGuiViewSource(GuiView & lv)
492 return new GuiViewSource(lv);
496 } // namespace frontend
499 #include "moc_GuiViewSource.cpp"