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 QFont font(guiApp->typewriterFontName());
90 font.setFixedPitch(true);
91 font.setStyleHint(QFont::TypeWriter);
92 viewSourceTV->setFont(font);
93 // again, personal taste
94 viewSourceTV->setWordWrapMode(QTextOption::NoWrap);
98 void ViewSourceWidget::getContent(BufferView const * view,
99 Buffer::OutputWhat output, docstring & str, string const & format,
102 // get the *top* level paragraphs that contain the cursor,
103 // or the selected text
107 if (!view->cursor().selection()) {
108 par_begin = view->cursor().bottom().pit();
111 par_begin = view->cursor().selectionBegin().bottom().pit();
112 par_end = view->cursor().selectionEnd().bottom().pit();
114 if (par_begin > par_end)
115 swap(par_begin, par_end);
116 odocstringstream ostr;
117 texrow_ = view->buffer().getSourceCode(ostr, format,
118 par_begin, par_end + 1, output, master);
119 //ensure that the last line can always be selected in its full width
120 str = ostr.str() + "\n";
124 void ViewSourceWidget::setBufferView(BufferView const * bv)
130 setEnabled(bv ? true : false);
134 bool ViewSourceWidget::setText(QString const & qstr)
136 bool const changed = document_->toPlainText() != qstr;
137 viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
139 document_->setPlainText(qstr);
144 void ViewSourceWidget::contentsChanged()
146 if (autoUpdateCB->isChecked())
151 void ViewSourceWidget::setViewFormat(int const index)
153 outputFormatCO->setCurrentIndex(index);
154 view_format_ = outputFormatCO->itemData(index).toString();
158 void ViewSourceWidget::updateView()
160 const int long_delay = 400;
161 const int short_delay = 60;
162 // a shorter delay if just the current paragraph is shown
163 update_timer_->start((contentsCO->currentIndex() == 0) ?
164 short_delay : long_delay);
167 void ViewSourceWidget::updateViewNow()
169 update_timer_->start(0);
174 QString prependTexRow(TexRow const & texrow, QString const & content)
176 QStringList list = content.split(QChar('\n'));
177 docstring_list dlist;
178 for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
179 dlist.push_back(from_utf8(fromqstr(*it)));
180 texrow.prepend(dlist);
182 for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
183 qstr += toqstr(*it) + '\n';
189 void ViewSourceWidget::realUpdateView()
199 // we will try to get that much space around the cursor
200 int const v_margin = 3;
201 int const h_margin = 10;
202 // we will try to preserve this
203 int const h_scroll = viewSourceTV->horizontalScrollBar()->value();
205 string const format = fromqstr(view_format_);
207 Buffer::OutputWhat output = Buffer::CurrentParagraph;
208 if (contentsCO->currentIndex() == 1)
209 output = Buffer::FullSource;
210 else if (contentsCO->currentIndex() == 2)
211 output = Buffer::OnlyPreamble;
212 else if (contentsCO->currentIndex() == 3)
213 output = Buffer::OnlyBody;
216 getContent(bv_, output, content, format, masterPerspectiveCB->isChecked());
217 QString old = document_->toPlainText();
218 QString qcontent = toqstr(content);
220 if (texrow_.get() && lyx::lyxerr.debugging(Debug::LATEX))
221 qcontent = prependTexRow(*texrow_, qcontent);
223 // prevent gotoCursor()
224 viewSourceTV->blockSignals(true);
225 bool const changed = setText(qcontent);
227 if (changed && !texrow_.get()) {
228 // position-to-row is unavailable
229 // we jump to the first modification
230 const QChar * oc = old.constData();
231 const QChar * nc = qcontent.constData();
233 while (*oc != '\0' && *nc != '\0' && *oc == *nc) {
238 QTextCursor c = QTextCursor(viewSourceTV->document());
239 //get some space below the cursor
241 c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,v_margin);
242 viewSourceTV->setTextCursor(c);
243 //get some space on the right of the cursor
244 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
246 const int block = c.blockNumber();
247 for (int i = h_margin; i && block == c.blockNumber(); --i) {
248 c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
250 c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
251 viewSourceTV->setTextCursor(c);
252 //back to the position
254 //c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
255 viewSourceTV->setTextCursor(c);
257 } else if (texrow_.get()) {
258 // Use the available position-to-row conversion to highlight
259 // the current selection in the source
260 std::pair<int,int> rows = texrow_->rowFromCursor(bv_->cursor());
261 int const beg_row = rows.first;
262 int const end_row = rows.second;
264 QTextCursor c = QTextCursor(viewSourceTV->document());
266 c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
268 const int beg_sel = c.position();
269 //get some space above the cursor
270 c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
272 viewSourceTV->setTextCursor(c);
273 c.setPosition(beg_sel, QTextCursor::MoveAnchor);
275 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
276 end_row - beg_row +1);
277 const int end_sel = c.position();
278 //get some space below the cursor
279 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
281 viewSourceTV->setTextCursor(c);
282 c.setPosition(end_sel, QTextCursor::KeepAnchor);
284 viewSourceTV->setTextCursor(c);
286 //the real highlighting is done with an ExtraSelection
287 QTextCharFormat format;
288 QPalette palette = viewSourceTV->palette();
290 // QColor bg = palette.color(QPalette::Active,QPalette::Highlight);
292 // format.setBackground(QBrush(bg));
293 //Other alternatives:
294 //format.setBackground(palette.light());
295 //format.setBackground(palette.alternateBase());
296 format.setBackground(palette.toolTipBase());
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 // only used in DEVEL_MODE for debugging
314 // need a proper LFUN if we want to implement it in release mode
315 void ViewSourceWidget::gotoCursor()
317 if (!bv_ || !texrow_.get())
319 int row = viewSourceTV->textCursor().blockNumber() + 1;
320 const_cast<BufferView *>(bv_)->setCursorFromRow(row, *texrow_);
325 void ViewSourceWidget::updateDefaultFormat()
330 outputFormatCO->blockSignals(true);
331 outputFormatCO->clear();
332 outputFormatCO->addItem(qt_("Default"),
333 QVariant(QString("default")));
336 vector<string> tmp = bv_->buffer().params().backends();
337 vector<string>::const_iterator it = tmp.begin();
338 vector<string>::const_iterator en = tmp.end();
339 for (; it != en; ++it) {
340 string const format = *it;
341 Format const * fmt = formats.getFormat(format);
343 LYXERR0("Can't find format for backend " << format << "!");
347 QString const pretty = qt_(fmt->prettyname());
348 QString const qformat = toqstr(format);
349 outputFormatCO->addItem(pretty, QVariant(qformat));
350 if (qformat == view_format_)
351 index = outputFormatCO->count() -1;
353 setViewFormat(index);
355 outputFormatCO->blockSignals(false);
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);
371 void ViewSourceWidget::saveSession(QString const & session_key) const
374 settings.setValue(session_key + "/output", 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_ = settings.value(session_key + "/output", 0).toString();
386 contentsCO->setCurrentIndex(settings
387 .value(session_key + "/contents", 0)
389 masterPerspectiveCB->setChecked(settings
390 .value(session_key + "/masterview", false)
392 bool const checked = settings
393 .value(session_key + "/autoupdate", true)
395 autoUpdateCB->setChecked(checked);
401 GuiViewSource::GuiViewSource(GuiView & parent,
402 Qt::DockWidgetArea area, Qt::WindowFlags flags)
403 : DockView(parent, "view-source", qt_("LaTeX Source"), area, flags)
405 widget_ = new ViewSourceWidget;
410 GuiViewSource::~GuiViewSource()
416 void GuiViewSource::updateView()
418 if (widget_->autoUpdateCB->isChecked()) {
419 widget_->setBufferView(bufferview());
420 widget_->updateView();
422 widget_->masterPerspectiveCB->setEnabled(buffer().parent());
426 void GuiViewSource::enableView(bool enable)
428 widget_->setBufferView(bufferview());
429 widget_->updateDefaultFormat();
431 // In the opposite case, updateView() will be called anyway.
432 widget_->contentsChanged();
436 bool GuiViewSource::initialiseParams(string const & /*source*/)
438 setWindowTitle(title());
443 QString GuiViewSource::title() const
447 //FIXME: this is shown for LyXHTML source, LyX source, etc.
448 return qt_("LaTeX Source");
450 return qt_("DocBook Source");
452 return qt_("Literate Source");
459 void GuiViewSource::saveSession() const
461 Dialog::saveSession();
462 widget_->saveSession(sessionKey());
466 void GuiViewSource::restoreSession()
468 DockView::restoreSession();
469 widget_->restoreSession(sessionKey());
473 Dialog * createGuiViewSource(GuiView & lv)
475 return new GuiViewSource(lv);
479 } // namespace frontend
482 #include "moc_GuiViewSource.cpp"