]> git.lyx.org Git - features.git/blob - src/frontends/qt/GuiViewSource.cpp
Move include of own header to the top. Fix dependencies
[features.git] / src / frontends / qt / GuiViewSource.cpp
1 /**
2  * \file GuiViewSource.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author John Levon
7  * \author Bo Peng
8  * \author Abdelrazak Younes
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "GuiViewSource.h"
16
17 #include "GuiApplication.h"
18 #include "LaTeXHighlighter.h"
19 #include "qt_helpers.h"
20
21 #include "BufferParams.h"
22 #include "BufferView.h"
23 #include "Cursor.h"
24 #include "Format.h"
25 #include "GuiView.h"
26 #include "FuncRequest.h"
27 #include "LyX.h"
28 #include "Paragraph.h"
29 #include "TexRow.h"
30
31 #include "support/debug.h"
32 #include "support/lassert.h"
33 #include "support/docstream.h"
34 #include "support/docstring_list.h"
35 #include "support/gettext.h"
36
37 #include <boost/crc.hpp>
38
39 #include <QBoxLayout>
40 #include <QComboBox>
41 #include <QScrollBar>
42 #include <QSettings>
43 #include <QTextCursor>
44 #include <QTextDocument>
45 #include <QTimer>
46 #include <QVariant>
47
48 using namespace std;
49
50 namespace lyx {
51 namespace frontend {
52
53 ViewSourceWidget::ViewSourceWidget(QWidget * parent)
54         :       QWidget(parent),
55                 document_(new QTextDocument(this)),
56                 highlighter_(new LaTeXHighlighter(document_))
57 {
58         setupUi(this);
59
60         connect(contentsCO, SIGNAL(activated(int)),
61                 this, SLOT(contentsChanged()));
62         connect(autoUpdateCB, SIGNAL(toggled(bool)),
63                 updatePB, SLOT(setDisabled(bool)));
64         connect(autoUpdateCB, SIGNAL(toggled(bool)),
65                 this, SLOT(contentsChanged()));
66         connect(masterPerspectiveCB, SIGNAL(toggled(bool)),
67                 this, SLOT(contentsChanged()));
68         connect(updatePB, SIGNAL(clicked()),
69                 this, SIGNAL(needUpdate()));
70         connect(outputFormatCO, SIGNAL(activated(int)),
71                 this, SLOT(setViewFormat(int)));
72
73         // setting a document at this point trigger an assertion in Qt
74         // so we disable the signals here:
75         document_->blockSignals(true);
76         viewSourceTV->setDocument(document_);
77         // reset selections
78         setText();
79         document_->blockSignals(false);
80         viewSourceTV->setReadOnly(true);
81         ///dialog_->viewSourceTV->setAcceptRichText(false);
82         // this is personal. I think source code should be in fixed-size font
83         viewSourceTV->setFont(guiApp->typewriterSystemFont());
84         // again, personal taste
85         viewSourceTV->setWordWrapMode(QTextOption::NoWrap);
86
87         // catch double click events
88         viewSourceTV->viewport()->installEventFilter(this);
89 }
90
91
92 void ViewSourceWidget::getContent(BufferView const & view,
93                         Buffer::OutputWhat output, docstring & str, string const & format,
94                         bool master)
95 {
96         // get the *top* level paragraphs that contain the cursor,
97         // or the selected text
98         pit_type par_begin;
99         pit_type par_end;
100
101         if (!view.cursor().selection()) {
102                 par_begin = view.cursor().bottom().pit();
103                 par_end = par_begin;
104         } else {
105                 par_begin = view.cursor().selectionBegin().bottom().pit();
106                 par_end = view.cursor().selectionEnd().bottom().pit();
107         }
108         if (par_begin > par_end)
109                 swap(par_begin, par_end);
110         odocstringstream ostr;
111         texrow_ = view.buffer()
112                 .getSourceCode(ostr, format, par_begin, par_end + 1, output, master);
113         //ensure that the last line can always be selected in its full width
114         str = ostr.str() + "\n";
115 }
116
117
118 bool ViewSourceWidget::setText(QString const & qstr)
119 {
120         bool const changed = document_->toPlainText() != qstr;
121         viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
122         if (changed)
123                 document_->setPlainText(qstr);
124         return changed;
125 }
126
127
128 void ViewSourceWidget::contentsChanged()
129 {
130         if (autoUpdateCB->isChecked())
131                 Q_EMIT needUpdate();
132 }
133
134
135 void ViewSourceWidget::setViewFormat(int const index)
136 {
137         outputFormatCO->setCurrentIndex(index);
138         string format = fromqstr(outputFormatCO->itemData(index).toString());
139         if (view_format_ != format) {
140                 view_format_ = format;
141                 Q_EMIT needUpdate();
142         }
143 }
144
145
146 int ViewSourceWidget::updateDelay() const
147 {
148         const int long_delay = 400;
149         const int short_delay = 60;
150         // a shorter delay if just the current paragraph is shown
151         return (contentsCO->currentIndex() == 0) ? short_delay : long_delay;
152 }
153
154
155 void GuiViewSource::scheduleUpdate()
156 {
157         update_timer_->start(widget_->updateDelay());
158 }
159
160
161 void GuiViewSource::scheduleUpdateNow()
162 {
163         update_timer_->start(0);
164 }
165
166
167 void GuiViewSource::realUpdateView()
168 {
169         widget_->updateView(bufferview());
170         updateTitle();
171 }
172
173
174 void ViewSourceWidget::updateView(BufferView const * bv)
175 {
176         if (!bv) {
177                 setText();
178                 setEnabled(false);
179                 return;
180         }
181
182         setEnabled(true);
183
184         // we will try to get that much space around the cursor
185         int const v_margin = 3;
186         int const h_margin = 10;
187         // we will try to preserve this
188         int const h_scroll = viewSourceTV->horizontalScrollBar()->value();
189
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;
197
198         docstring content;
199         getContent(*bv, output, content, view_format_,
200                    masterPerspectiveCB->isChecked());
201         QString old = document_->toPlainText();
202         QString qcontent = toqstr(content);
203         if (guiApp->currentView()->develMode()) {
204                 // output tex<->row correspondences in the source panel if the "-dbg latex"
205                 // option is given.
206                 if (texrow_ && lyx::lyxerr.debugging(Debug::LATEX)) {
207                         QStringList list = qcontent.split(QChar('\n'));
208                         docstring_list dlist;
209                         for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
210                                 dlist.push_back(from_utf8(fromqstr(*it)));
211                         texrow_->prepend(dlist);
212                         qcontent.clear();
213                         for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
214                                 qcontent += toqstr(*it) + '\n';
215                 }
216         }
217
218         // prevent gotoCursor()
219         QSignalBlocker blocker(viewSourceTV);
220         bool const changed = setText(qcontent);
221
222         if (changed && !texrow_) {
223                 // position-to-row is unavailable
224                 // we jump to the first modification
225                 int length = min(old.length(), qcontent.length());
226                 int pos = 0;
227                 for (; pos < length && old.at(pos) == qcontent.at(pos); ++pos) {}
228                 QTextCursor c = QTextCursor(viewSourceTV->document());
229                 //get some space below the cursor
230                 c.setPosition(pos);
231                 c.movePosition(QTextCursor::Down, QTextCursor::MoveAnchor,v_margin);
232                 viewSourceTV->setTextCursor(c);
233                 //get some space on the right of the cursor
234                 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
235                 c.setPosition(pos);
236                 const int block = c.blockNumber();
237                 for (int i = h_margin; i && block == c.blockNumber(); --i) {
238                         c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
239                 }
240                 c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
241                 viewSourceTV->setTextCursor(c);
242                 //back to the position
243                 c.setPosition(pos);
244                 //c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
245                 viewSourceTV->setTextCursor(c);
246
247         } else if (texrow_) {
248                 // Use the available position-to-row conversion to highlight
249                 // the current selection in the source
250                 std::pair<int,int> rows = texrow_->rowFromCursor(bv->cursor());
251                 int const beg_row = rows.first;
252                 int const end_row = rows.second;
253
254                 QTextCursor c = QTextCursor(viewSourceTV->document());
255
256                 c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
257                                            beg_row - 1);
258                 const int beg_sel = c.position();
259                 //get some space above the cursor
260                 c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
261                                            v_margin);
262                 viewSourceTV->setTextCursor(c);
263                 c.setPosition(beg_sel, QTextCursor::MoveAnchor);
264
265                 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
266                                            end_row - beg_row +1);
267                 const int end_sel = c.position();
268                 //get some space below the cursor
269                 c.movePosition(QTextCursor::NextBlock, QTextCursor::KeepAnchor,
270                                            v_margin - 1);
271                 viewSourceTV->setTextCursor(c);
272                 c.setPosition(end_sel, QTextCursor::KeepAnchor);
273
274                 viewSourceTV->setTextCursor(c);
275
276                 //the real highlighting is done with an ExtraSelection
277                 QTextCharFormat format;
278                 {
279                 // We create a new color with the lightness of AlternateBase and
280                 // the hue and saturation of Highlight
281                 QPalette palette = viewSourceTV->palette();
282                 QBrush alt = palette.alternateBase();
283                 QColor high = palette.highlight().color().toHsl();
284                 QColor col = QColor::fromHsl(high.hue(),
285                                              high.hslSaturation(),
286                                              alt.color().lightness());
287                 alt.setColor(col);
288                 format.setBackground(alt);
289                 }
290                 format.setProperty(QTextFormat::FullWidthSelection, true);
291                 QTextEdit::ExtraSelection sel;
292                 sel.format = format;
293                 sel.cursor = c;
294                 viewSourceTV->setExtraSelections(
295                         QList<QTextEdit::ExtraSelection>() << sel);
296
297                 //clean up
298                 c.clearSelection();
299                 viewSourceTV->setTextCursor(c);
300                 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
301         } // else if (texrow)
302 }
303
304
305 docstring ViewSourceWidget::currentFormatName(BufferView const * bv) const
306 {
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_);
312 }
313
314
315 bool ViewSourceWidget::eventFilter(QObject * obj, QEvent * ev)
316 {
317         // this event filter is installed on the viewport of the QTextView
318         if (obj == viewSourceTV->viewport() &&
319             ev->type() == QEvent::MouseButtonDblClick) {
320                 goToCursor();
321                 return true;
322         }
323         return false;
324 }
325
326
327 void ViewSourceWidget::goToCursor() const
328 {
329         if (!texrow_)
330                 return;
331         int row = viewSourceTV->textCursor().blockNumber() + 1;
332         dispatch(texrow_->goToFuncFromRow(row));
333 }
334
335
336
337 void ViewSourceWidget::updateDefaultFormat(BufferView const & bv)
338 {
339         QSignalBlocker blocker(outputFormatCO);
340         outputFormatCO->clear();
341         outputFormatCO->addItem(qt_("Default"),
342                                 QVariant(QString("default")));
343
344         int index = 0;
345         for (string const & fmt_name : bv.buffer().params().backends()) {
346                 Format const * fmt = theFormats().getFormat(fmt_name);
347                 if (!fmt) {
348                         LYXERR0("Can't find format for backend " << fmt_name << "!");
349                         continue;
350                 }
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;
355         }
356         setViewFormat(index);
357 }
358
359
360 void ViewSourceWidget::resizeEvent (QResizeEvent * event)
361 {
362         QSize const & formSize = formLayout->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);
366         } else {
367                 layout_->setDirection(QBoxLayout::LeftToRight);
368         }
369         QWidget::resizeEvent(event);
370 }
371
372
373 void ViewSourceWidget::saveSession(QSettings & settings, QString const & session_key) const
374 {
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());
380 }
381
382
383 void ViewSourceWidget::restoreSession(QString const & session_key)
384 {
385         QSettings settings;
386         view_format_ = fromqstr(settings.value(session_key + "/output", 0)
387                                 .toString());
388         contentsCO->setCurrentIndex(settings
389                                                                 .value(session_key + "/contents", 0)
390                                                                 .toInt());
391         masterPerspectiveCB->setChecked(settings
392                                                                         .value(session_key + "/masterview", false)
393                                                                         .toBool());
394         bool const checked = settings
395                 .value(session_key + "/autoupdate", true)
396                 .toBool();
397         autoUpdateCB->setChecked(checked);
398         if (checked)
399                 Q_EMIT needUpdate();
400 }
401
402
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))
408 {
409         setWidget(widget_);
410
411         // setting the update timer
412         update_timer_->setSingleShot(true);
413         connect(update_timer_, SIGNAL(timeout()),
414                 this, SLOT(realUpdateView()));
415
416         connect(widget_, SIGNAL(needUpdate()), this, SLOT(scheduleUpdateNow()));
417 }
418
419
420 void GuiViewSource::onBufferViewChanged()
421 {
422         widget_->setText();
423         widget_->setEnabled((bool)bufferview());
424 }
425
426
427 void GuiViewSource::updateView()
428 {
429         if (widget_->autoUpdateCB->isChecked()) {
430                 widget_->setEnabled((bool)bufferview());
431                 scheduleUpdate();
432         }
433         widget_->masterPerspectiveCB->setEnabled(buffer().parent());
434         updateTitle();
435 }
436
437
438 void GuiViewSource::enableView(bool enable)
439 {
440         widget_->setEnabled((bool)bufferview());
441         if (bufferview())
442                 widget_->updateDefaultFormat(*bufferview());
443         if (!enable)
444                 // In the opposite case, updateView() will be called anyway.
445                 widget_->contentsChanged();
446 }
447
448
449 bool GuiViewSource::initialiseParams(string const & /*source*/)
450 {
451         updateTitle();
452         return true;
453 }
454
455
456 void GuiViewSource::updateTitle()
457 {
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)));
462         setTitle(title);
463         setWindowTitle(title);
464 }
465
466
467 void GuiViewSource::saveSession(QSettings & settings) const
468 {
469         Dialog::saveSession(settings);
470         widget_->saveSession(settings, sessionKey());
471 }
472
473
474 void GuiViewSource::restoreSession()
475 {
476         DockView::restoreSession();
477         widget_->restoreSession(sessionKey());
478 }
479
480
481 Dialog * createGuiViewSource(GuiView & lv)
482 {
483         return new GuiViewSource(lv);
484 }
485
486
487 } // namespace frontend
488 } // namespace lyx
489
490 #include "moc_GuiViewSource.cpp"