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 "Paragraph.h"
26 #include "support/debug.h"
27 #include "support/lassert.h"
28 #include "support/docstream.h"
29 #include "support/docstring_list.h"
30 #include "support/gettext.h"
32 #include <boost/crc.hpp>
38 #include <QTextCursor>
39 #include <QTextDocument>
47 ViewSourceWidget::ViewSourceWidget()
48 : bv_(0), document_(new QTextDocument(this)),
49 highlighter_(new LaTeXHighlighter(document_)),
50 update_timer_(new QTimer(this))
54 connect(contentsCO, SIGNAL(activated(int)),
55 this, SLOT(contentsChanged()));
56 connect(autoUpdateCB, SIGNAL(toggled(bool)),
57 updatePB, SLOT(setDisabled(bool)));
58 connect(autoUpdateCB, SIGNAL(toggled(bool)),
59 this, SLOT(contentsChanged()));
60 connect(masterPerspectiveCB, SIGNAL(toggled(bool)),
61 this, SLOT(contentsChanged()));
62 connect(updatePB, SIGNAL(clicked()),
63 this, SLOT(updateViewNow()));
64 connect(outputFormatCO, SIGNAL(activated(int)),
65 this, SLOT(setViewFormat(int)));
66 connect(outputFormatCO, SIGNAL(activated(int)),
67 this, SLOT(contentsChanged()));
69 if (lyx::lyxerr.debugging(Debug::LATEX))
70 connect(viewSourceTV, SIGNAL(cursorPositionChanged()),
71 this, SLOT(gotoCursor()));
74 // setting the update timer
75 update_timer_->setSingleShot(true);
76 connect(update_timer_, SIGNAL(timeout()),
77 this, SLOT(realUpdateView()));
79 // setting a document at this point trigger an assertion in Qt
80 // so we disable the signals here:
81 document_->blockSignals(true);
82 viewSourceTV->setDocument(document_);
85 document_->blockSignals(false);
86 viewSourceTV->setReadOnly(true);
87 ///dialog_->viewSourceTV->setAcceptRichText(false);
88 // this is personal. I think source code should be in fixed-size font
89 viewSourceTV->setFont(guiApp->typewriterSystemFont());
90 // again, personal taste
91 viewSourceTV->setWordWrapMode(QTextOption::NoWrap);
95 void ViewSourceWidget::getContent(BufferView const * view,
96 Buffer::OutputWhat output, docstring & str, string const & format,
99 // get the *top* level paragraphs that contain the cursor,
100 // or the selected text
104 if (!view->cursor().selection()) {
105 par_begin = view->cursor().bottom().pit();
108 par_begin = view->cursor().selectionBegin().bottom().pit();
109 par_end = view->cursor().selectionEnd().bottom().pit();
111 if (par_begin > par_end)
112 swap(par_begin, par_end);
113 odocstringstream ostr;
114 texrow_ = view->buffer().getSourceCode(ostr, format,
115 par_begin, par_end + 1, output, master);
116 //ensure that the last line can always be selected in its full width
117 str = ostr.str() + "\n";
121 void ViewSourceWidget::setBufferView(BufferView const * bv)
127 setEnabled(bv ? true : false);
131 bool ViewSourceWidget::setText(QString const & qstr)
133 bool const changed = document_->toPlainText() != qstr;
134 viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
136 document_->setPlainText(qstr);
141 void ViewSourceWidget::contentsChanged()
143 if (autoUpdateCB->isChecked())
148 void ViewSourceWidget::setViewFormat(int const index)
150 outputFormatCO->setCurrentIndex(index);
151 string format = fromqstr(outputFormatCO->itemData(index).toString());
152 if (view_format_ != format) {
153 view_format_ = format;
160 void ViewSourceWidget::updateView()
162 const int long_delay = 400;
163 const int short_delay = 60;
164 // a shorter delay if just the current paragraph is shown
165 update_timer_->start((contentsCO->currentIndex() == 0) ?
166 short_delay : long_delay);
170 void ViewSourceWidget::updateViewNow()
172 update_timer_->start(0);
176 void ViewSourceWidget::realUpdateView()
186 // we will try to get that much space around the cursor
187 int const v_margin = 3;
188 int const h_margin = 10;
189 // we will try to preserve this
190 int const h_scroll = viewSourceTV->horizontalScrollBar()->value();
192 Buffer::OutputWhat output = Buffer::CurrentParagraph;
193 if (contentsCO->currentIndex() == 1)
194 output = Buffer::FullSource;
195 else if (contentsCO->currentIndex() == 2)
196 output = Buffer::OnlyPreamble;
197 else if (contentsCO->currentIndex() == 3)
198 output = Buffer::OnlyBody;
201 getContent(bv_, output, content, view_format_,
202 masterPerspectiveCB->isChecked());
203 QString old = document_->toPlainText();
204 QString qcontent = toqstr(content);
206 // output tex<->row correspondences in the source panel if the "-dbg latex"
208 if (texrow_.get() && lyx::lyxerr.debugging(Debug::LATEX)) {
209 QStringList list = qcontent.split(QChar('\n'));
210 docstring_list dlist;
211 for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
212 dlist.push_back(from_utf8(fromqstr(*it)));
213 texrow_->prepend(dlist);
215 for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
216 qcontent += toqstr(*it) + '\n';
219 // prevent gotoCursor()
220 viewSourceTV->blockSignals(true);
221 bool const changed = setText(qcontent);
223 if (changed && !texrow_.get()) {
224 // position-to-row is unavailable
225 // we jump to the first modification
226 int length = min(old.length(), qcontent.length());
228 for (; pos < length && old.at(pos) == qcontent.at(pos); ++pos) {}
229 QTextCursor c = QTextCursor(viewSourceTV->document());
230 //get some space below the cursor
232 c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,v_margin);
233 viewSourceTV->setTextCursor(c);
234 //get some space on the right of the cursor
235 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
237 const int block = c.blockNumber();
238 for (int i = h_margin; i && block == c.blockNumber(); --i) {
239 c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
241 c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
242 viewSourceTV->setTextCursor(c);
243 //back to the position
245 //c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
246 viewSourceTV->setTextCursor(c);
248 } else if (texrow_.get()) {
249 // Use the available position-to-row conversion to highlight
250 // the current selection in the source
251 std::pair<int,int> rows = texrow_->rowFromCursor(bv_->cursor());
252 int const beg_row = rows.first;
253 int const end_row = rows.second;
255 QTextCursor c = QTextCursor(viewSourceTV->document());
257 c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
259 const int beg_sel = c.position();
260 //get some space above the cursor
261 c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
263 viewSourceTV->setTextCursor(c);
264 c.setPosition(beg_sel, QTextCursor::MoveAnchor);
266 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
267 end_row - beg_row +1);
268 const int end_sel = c.position();
269 //get some space below the cursor
270 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
272 viewSourceTV->setTextCursor(c);
273 c.setPosition(end_sel, QTextCursor::KeepAnchor);
275 viewSourceTV->setTextCursor(c);
277 //the real highlighting is done with an ExtraSelection
278 QTextCharFormat format;
280 // We create a new color with the lightness of AlternateBase and
281 // the hue and saturation of Highlight
282 QPalette palette = viewSourceTV->palette();
283 QBrush alt = palette.alternateBase();
284 QColor high = palette.highlight().color().toHsl();
285 QColor col = QColor::fromHsl(high.hue(),
286 high.hslSaturation(),
287 alt.color().lightness());
289 format.setBackground(alt);
291 format.setProperty(QTextFormat::FullWidthSelection, true);
292 QTextEdit::ExtraSelection sel;
295 viewSourceTV->setExtraSelections(
296 QList<QTextEdit::ExtraSelection>() << sel);
300 viewSourceTV->setTextCursor(c);
301 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
302 } // else if (texrow)
303 viewSourceTV->blockSignals(false);
307 docstring ViewSourceWidget::currentFormatName(BufferView const * bv) const
309 // Compute the actual format used
310 string const format = !bv ? ""
311 : flavor2format(bv->buffer().params().getOutputFlavor(view_format_));
312 Format const * f = formats.getFormat(format.empty() ? view_format_ : format);
313 return from_utf8(f ? f->prettyname() : view_format_);
317 // only used in DEVEL_MODE for debugging
318 // need a proper LFUN if we want to implement it in release mode
319 void ViewSourceWidget::gotoCursor()
321 if (!bv_ || !texrow_.get())
323 int row = viewSourceTV->textCursor().blockNumber() + 1;
324 const_cast<BufferView *>(bv_)->setCursorFromRow(row, *texrow_);
329 void ViewSourceWidget::updateDefaultFormat()
334 outputFormatCO->blockSignals(true);
335 outputFormatCO->clear();
336 outputFormatCO->addItem(qt_("Default"),
337 QVariant(QString("default")));
340 vector<string> tmp = bv_->buffer().params().backends();
341 vector<string>::const_iterator it = tmp.begin();
342 vector<string>::const_iterator en = tmp.end();
343 for (; it != en; ++it) {
344 string const format = *it;
345 Format const * fmt = formats.getFormat(format);
347 LYXERR0("Can't find format for backend " << format << "!");
351 QString const pretty = qt_(fmt->prettyname());
352 outputFormatCO->addItem(pretty, QVariant(toqstr(format)));
353 if (format == view_format_)
354 index = outputFormatCO->count() -1;
356 setViewFormat(index);
358 outputFormatCO->blockSignals(false);
362 void ViewSourceWidget::resizeEvent (QResizeEvent * event)
364 QSize const & formSize = formLayout->sizeHint();
365 // minimize the size of the part that contains the buttons
366 if (width() * formSize.height() < height() * formSize.width()) {
367 layout_->setDirection(QBoxLayout::TopToBottom);
369 layout_->setDirection(QBoxLayout::LeftToRight);
371 QWidget::resizeEvent(event);
375 void ViewSourceWidget::saveSession(QSettings & settings, QString const & session_key) const
377 settings.setValue(session_key + "/output", toqstr(view_format_));
378 settings.setValue(session_key + "/contents", contentsCO->currentIndex());
379 settings.setValue(session_key + "/autoupdate", autoUpdateCB->isChecked());
380 settings.setValue(session_key + "/masterview",
381 masterPerspectiveCB->isChecked());
385 void ViewSourceWidget::restoreSession(QString const & session_key)
388 view_format_ = fromqstr(settings.value(session_key + "/output", 0)
390 contentsCO->setCurrentIndex(settings
391 .value(session_key + "/contents", 0)
393 masterPerspectiveCB->setChecked(settings
394 .value(session_key + "/masterview", false)
396 bool const checked = settings
397 .value(session_key + "/autoupdate", true)
399 autoUpdateCB->setChecked(checked);
405 GuiViewSource::GuiViewSource(GuiView & parent,
406 Qt::DockWidgetArea area, Qt::WindowFlags flags)
407 : DockView(parent, "view-source", qt_("Code Preview"), area, flags)
409 widget_ = new ViewSourceWidget;
411 connect(widget_, SIGNAL(formatChanged()), this, SLOT(updateTitle()));
415 GuiViewSource::~GuiViewSource()
421 void GuiViewSource::updateView()
423 if (widget_->autoUpdateCB->isChecked()) {
424 widget_->setBufferView(bufferview());
425 widget_->updateView();
427 widget_->masterPerspectiveCB->setEnabled(buffer().parent());
432 void GuiViewSource::enableView(bool enable)
434 widget_->setBufferView(bufferview());
435 widget_->updateDefaultFormat();
437 // In the opposite case, updateView() will be called anyway.
438 widget_->contentsChanged();
442 bool GuiViewSource::initialiseParams(string const & /*source*/)
449 void GuiViewSource::updateTitle()
451 docstring const format = widget_->currentFormatName(bufferview());
452 QString const title = format.empty() ? qt_("Code Preview")
453 : qt_("%1[[preview format name]] Preview")
454 .arg(toqstr(translateIfPossible(format)));
456 setWindowTitle(title);
460 void GuiViewSource::saveSession(QSettings & settings) const
462 Dialog::saveSession(settings);
463 widget_->saveSession(settings, sessionKey());
467 void GuiViewSource::restoreSession()
469 DockView::restoreSession();
470 widget_->restoreSession(sessionKey());
474 Dialog * createGuiViewSource(GuiView & lv)
476 return new GuiViewSource(lv);
480 } // namespace frontend
483 #include "moc_GuiViewSource.cpp"