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"
27 #include "support/debug.h"
28 #include "support/lassert.h"
29 #include "support/docstream.h"
30 #include "support/docstring_list.h"
31 #include "support/gettext.h"
33 #include <boost/crc.hpp>
39 #include <QTextCursor>
40 #include <QTextDocument>
48 ViewSourceWidget::ViewSourceWidget()
49 : bv_(0), document_(new QTextDocument(this)),
50 highlighter_(new LaTeXHighlighter(document_)),
51 update_timer_(new QTimer(this))
55 connect(contentsCO, SIGNAL(activated(int)),
56 this, SLOT(contentsChanged()));
57 connect(autoUpdateCB, SIGNAL(toggled(bool)),
58 updatePB, SLOT(setDisabled(bool)));
59 connect(autoUpdateCB, SIGNAL(toggled(bool)),
60 this, SLOT(contentsChanged()));
61 connect(masterPerspectiveCB, SIGNAL(toggled(bool)),
62 this, SLOT(contentsChanged()));
63 connect(updatePB, SIGNAL(clicked()),
64 this, SLOT(updateViewNow()));
65 connect(outputFormatCO, SIGNAL(activated(int)),
66 this, SLOT(setViewFormat(int)));
67 connect(outputFormatCO, SIGNAL(activated(int)),
68 this, SLOT(contentsChanged()));
70 if (lyx::lyxerr.debugging(Debug::LATEX))
71 connect(viewSourceTV, SIGNAL(cursorPositionChanged()),
72 this, SLOT(gotoCursor()));
75 // setting the update timer
76 update_timer_->setSingleShot(true);
77 connect(update_timer_, SIGNAL(timeout()),
78 this, SLOT(realUpdateView()));
80 // setting a document at this point trigger an assertion in Qt
81 // so we disable the signals here:
82 document_->blockSignals(true);
83 viewSourceTV->setDocument(document_);
86 document_->blockSignals(false);
87 viewSourceTV->setReadOnly(true);
88 ///dialog_->viewSourceTV->setAcceptRichText(false);
89 // this is personal. I think source code should be in fixed-size font
90 viewSourceTV->setFont(guiApp->typewriterSystemFont());
91 // again, personal taste
92 viewSourceTV->setWordWrapMode(QTextOption::NoWrap);
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 view_format_ = outputFormatCO->itemData(index).toString();
156 void ViewSourceWidget::updateView()
158 const int long_delay = 400;
159 const int short_delay = 60;
160 // a shorter delay if just the current paragraph is shown
161 update_timer_->start((contentsCO->currentIndex() == 0) ?
162 short_delay : long_delay);
166 void ViewSourceWidget::updateViewNow()
168 update_timer_->start(0);
172 void ViewSourceWidget::realUpdateView()
182 // we will try to get that much space around the cursor
183 int const v_margin = 3;
184 int const h_margin = 10;
185 // we will try to preserve this
186 int const h_scroll = viewSourceTV->horizontalScrollBar()->value();
188 string const format = fromqstr(view_format_);
190 Buffer::OutputWhat output = Buffer::CurrentParagraph;
191 if (contentsCO->currentIndex() == 1)
192 output = Buffer::FullSource;
193 else if (contentsCO->currentIndex() == 2)
194 output = Buffer::OnlyPreamble;
195 else if (contentsCO->currentIndex() == 3)
196 output = Buffer::OnlyBody;
199 getContent(bv_, output, content, format, masterPerspectiveCB->isChecked());
200 QString old = document_->toPlainText();
201 QString qcontent = toqstr(content);
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';
216 // prevent gotoCursor()
217 viewSourceTV->blockSignals(true);
218 bool const changed = setText(qcontent);
220 if (changed && !texrow_) {
221 // position-to-row is unavailable
222 // we jump to the first modification
223 const QChar * oc = old.constData();
224 const QChar * nc = qcontent.constData();
226 while (*oc != '\0' && *nc != '\0' && *oc == *nc) {
231 QTextCursor c = QTextCursor(viewSourceTV->document());
232 //get some space below the cursor
234 c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,v_margin);
235 viewSourceTV->setTextCursor(c);
236 //get some space on the right of the cursor
237 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
239 const int block = c.blockNumber();
240 for (int i = h_margin; i && block == c.blockNumber(); --i) {
241 c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
243 c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
244 viewSourceTV->setTextCursor(c);
245 //back to the position
247 //c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
248 viewSourceTV->setTextCursor(c);
250 } else if (texrow_) {
251 // Use the available position-to-row conversion to highlight
252 // the current selection in the source
253 std::pair<int,int> rows = texrow_->rowFromCursor(bv_->cursor());
254 int const beg_row = rows.first;
255 int const end_row = rows.second;
257 QTextCursor c = QTextCursor(viewSourceTV->document());
259 c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
261 const int beg_sel = c.position();
262 //get some space above the cursor
263 c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
265 viewSourceTV->setTextCursor(c);
266 c.setPosition(beg_sel, QTextCursor::MoveAnchor);
268 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
269 end_row - beg_row +1);
270 const int end_sel = c.position();
271 //get some space below the cursor
272 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
274 viewSourceTV->setTextCursor(c);
275 c.setPosition(end_sel, QTextCursor::KeepAnchor);
277 viewSourceTV->setTextCursor(c);
279 //the real highlighting is done with an ExtraSelection
280 QTextCharFormat format;
282 // We create a new color with the lightness of AlternateBase and
283 // the hue and saturation of Highlight
284 QPalette palette = viewSourceTV->palette();
285 QBrush alt = palette.alternateBase();
286 QColor high = palette.highlight().color().toHsl();
287 QColor col = QColor::fromHsl(high.hue(),
288 high.hslSaturation(),
289 alt.color().lightness());
291 format.setBackground(alt);
293 format.setProperty(QTextFormat::FullWidthSelection, true);
294 QTextEdit::ExtraSelection sel;
297 viewSourceTV->setExtraSelections(
298 QList<QTextEdit::ExtraSelection>() << sel);
302 viewSourceTV->setTextCursor(c);
303 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
304 } // else if (texrow)
305 viewSourceTV->blockSignals(false);
309 // only used in DEVEL_MODE for debugging
310 // need a proper LFUN if we want to implement it in release mode
311 void ViewSourceWidget::gotoCursor()
313 if (!bv_ || !texrow_)
315 int row = viewSourceTV->textCursor().blockNumber() + 1;
316 const_cast<BufferView *>(bv_)->setCursorFromRow(row, *texrow_);
321 void ViewSourceWidget::updateDefaultFormat()
326 outputFormatCO->blockSignals(true);
327 outputFormatCO->clear();
328 outputFormatCO->addItem(qt_("Default"),
329 QVariant(QString("default")));
332 vector<string> tmp = bv_->buffer().params().backends();
333 vector<string>::const_iterator it = tmp.begin();
334 vector<string>::const_iterator en = tmp.end();
335 for (; it != en; ++it) {
336 string const format = *it;
337 Format const * fmt = formats.getFormat(format);
339 LYXERR0("Can't find format for backend " << format << "!");
343 QString const pretty = qt_(fmt->prettyname());
344 QString const qformat = toqstr(format);
345 outputFormatCO->addItem(pretty, QVariant(qformat));
346 if (qformat == view_format_)
347 index = outputFormatCO->count() -1;
349 setViewFormat(index);
351 outputFormatCO->blockSignals(false);
355 void ViewSourceWidget::resizeEvent (QResizeEvent * event)
357 QSize const & formSize = formLayout->sizeHint();
358 // minimize the size of the part that contains the buttons
359 if (width() * formSize.height() < height() * formSize.width()) {
360 layout_->setDirection(QBoxLayout::TopToBottom);
362 layout_->setDirection(QBoxLayout::LeftToRight);
364 QWidget::resizeEvent(event);
367 void ViewSourceWidget::saveSession(QString const & session_key) const
370 settings.setValue(session_key + "/output", view_format_);
371 settings.setValue(session_key + "/contents", contentsCO->currentIndex());
372 settings.setValue(session_key + "/autoupdate", autoUpdateCB->isChecked());
373 settings.setValue(session_key + "/masterview",
374 masterPerspectiveCB->isChecked());
378 void ViewSourceWidget::restoreSession(QString const & session_key)
381 view_format_ = settings.value(session_key + "/output", 0).toString();
382 contentsCO->setCurrentIndex(settings
383 .value(session_key + "/contents", 0)
385 masterPerspectiveCB->setChecked(settings
386 .value(session_key + "/masterview", false)
388 bool const checked = settings
389 .value(session_key + "/autoupdate", true)
391 autoUpdateCB->setChecked(checked);
397 GuiViewSource::GuiViewSource(GuiView & parent,
398 Qt::DockWidgetArea area, Qt::WindowFlags flags)
399 : DockView(parent, "view-source", qt_("LaTeX Source"), area, flags)
401 widget_ = new ViewSourceWidget;
406 GuiViewSource::~GuiViewSource()
412 void GuiViewSource::updateView()
414 if (widget_->autoUpdateCB->isChecked()) {
415 widget_->setBufferView(bufferview());
416 widget_->updateView();
418 widget_->masterPerspectiveCB->setEnabled(buffer().parent());
422 void GuiViewSource::enableView(bool enable)
424 widget_->setBufferView(bufferview());
425 widget_->updateDefaultFormat();
427 // In the opposite case, updateView() will be called anyway.
428 widget_->contentsChanged();
432 bool GuiViewSource::initialiseParams(string const & /*source*/)
434 setWindowTitle(title());
439 QString GuiViewSource::title() const
443 //FIXME: this is shown for LyXHTML source, LyX source, etc.
444 return qt_("LaTeX Source");
446 return qt_("DocBook Source");
448 return qt_("Literate Source");
455 void GuiViewSource::saveSession() const
457 Dialog::saveSession();
458 widget_->saveSession(sessionKey());
462 void GuiViewSource::restoreSession()
464 DockView::restoreSession();
465 widget_->restoreSession(sessionKey());
469 Dialog * createGuiViewSource(GuiView & lv)
471 return new GuiViewSource(lv);
475 } // namespace frontend
478 #include "moc_GuiViewSource.cpp"