]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiViewSource.cpp
Speed up exit time
[lyx.git] / src / frontends / qt4 / 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 "GuiApplication.h"
16 #include "GuiViewSource.h"
17 #include "LaTeXHighlighter.h"
18 #include "qt_helpers.h"
19
20 #include "BufferParams.h"
21 #include "BufferView.h"
22 #include "Cursor.h"
23 #include "Format.h"
24 #include "Paragraph.h"
25
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"
31
32 #include <boost/crc.hpp>
33
34 #include <QBoxLayout>
35 #include <QComboBox>
36 #include <QScrollBar>
37 #include <QSettings>
38 #include <QTextCursor>
39 #include <QTextDocument>
40 #include <QVariant>
41
42 using namespace std;
43
44 namespace lyx {
45 namespace frontend {
46
47 ViewSourceWidget::ViewSourceWidget()
48         :       bv_(0), document_(new QTextDocument(this)),
49                 highlighter_(new LaTeXHighlighter(document_)),
50                 update_timer_(new QTimer(this))
51 {
52         setupUi(this);
53
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()));
68 #ifdef DEVEL_VERSION
69         if (lyx::lyxerr.debugging(Debug::LATEX))
70                 connect(viewSourceTV, SIGNAL(cursorPositionChanged()),
71                                 this, SLOT(gotoCursor()));
72 #endif
73
74         // setting the update timer
75         update_timer_->setSingleShot(true);
76         connect(update_timer_, SIGNAL(timeout()),
77                 this, SLOT(realUpdateView()));
78
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_);
83         // reset selections
84         setText();
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);
92 }
93
94
95 void ViewSourceWidget::getContent(BufferView const * view,
96                         Buffer::OutputWhat output, docstring & str, string const & format,
97                         bool master)
98 {
99         // get the *top* level paragraphs that contain the cursor,
100         // or the selected text
101         pit_type par_begin;
102         pit_type par_end;
103
104         if (!view->cursor().selection()) {
105                 par_begin = view->cursor().bottom().pit();
106                 par_end = par_begin;
107         } else {
108                 par_begin = view->cursor().selectionBegin().bottom().pit();
109                 par_end = view->cursor().selectionEnd().bottom().pit();
110         }
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";
118 }
119
120
121 void ViewSourceWidget::setBufferView(BufferView const * bv)
122 {
123         if (bv_ != bv) {
124                 setText();
125                 bv_ = bv;
126         }
127         setEnabled(bv ?  true : false);
128 }
129
130
131 bool ViewSourceWidget::setText(QString const & qstr)
132 {
133         bool const changed = document_->toPlainText() != qstr;
134         viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
135         if (changed)
136                 document_->setPlainText(qstr);
137         return changed;
138 }
139
140
141 void ViewSourceWidget::contentsChanged()
142 {
143         if (autoUpdateCB->isChecked())
144                 updateViewNow();
145 }
146
147
148 void ViewSourceWidget::setViewFormat(int const index)
149 {
150         outputFormatCO->setCurrentIndex(index);
151         string format = fromqstr(outputFormatCO->itemData(index).toString());
152         if (view_format_ != format) {
153                 view_format_ = format;
154                 // emit signal
155                 formatChanged();
156         }
157 }
158
159
160 void ViewSourceWidget::updateView()
161 {
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);
167 }
168
169
170 void ViewSourceWidget::updateViewNow()
171 {
172         update_timer_->start(0);
173 }
174
175
176 void ViewSourceWidget::realUpdateView()
177 {
178         if (!bv_) {
179                 setText();
180                 setEnabled(false);
181                 return;
182         }
183
184         setEnabled(true);
185
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();
191
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;
199
200         docstring content;
201         getContent(bv_, output, content, view_format_,
202                    masterPerspectiveCB->isChecked());
203         QString old = document_->toPlainText();
204         QString qcontent = toqstr(content);
205 #ifdef DEVEL_VERSION
206         // output tex<->row correspondences in the source panel if the "-dbg latex"
207         // option is given.
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);
214                 qcontent.clear();
215                 for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
216                         qcontent += toqstr(*it) + '\n';
217         }
218 #endif
219         // prevent gotoCursor()
220         viewSourceTV->blockSignals(true);
221         bool const changed = setText(qcontent);
222
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());
227                 int pos = 0;
228                 for (; pos < length && old.at(pos) == qcontent.at(pos); ++pos) {}
229                 QTextCursor c = QTextCursor(viewSourceTV->document());
230                 //get some space below the cursor
231                 c.setPosition(pos);
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);
236                 c.setPosition(pos);
237                 const int block = c.blockNumber();
238                 for (int i = h_margin; i && block == c.blockNumber(); --i) {
239                         c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
240                 }
241                 c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
242                 viewSourceTV->setTextCursor(c);
243                 //back to the position
244                 c.setPosition(pos);
245                 //c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
246                 viewSourceTV->setTextCursor(c);
247
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;
254
255                 QTextCursor c = QTextCursor(viewSourceTV->document());
256
257                 c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
258                                            beg_row - 1);
259                 const int beg_sel = c.position();
260                 //get some space above the cursor
261                 c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
262                                            v_margin);
263                 viewSourceTV->setTextCursor(c);
264                 c.setPosition(beg_sel, QTextCursor::MoveAnchor);
265
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,
271                                            v_margin - 1);
272                 viewSourceTV->setTextCursor(c);
273                 c.setPosition(end_sel, QTextCursor::KeepAnchor);
274
275                 viewSourceTV->setTextCursor(c);
276
277                 //the real highlighting is done with an ExtraSelection
278                 QTextCharFormat format;
279                 {
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());
288                 alt.setColor(col);
289                 format.setBackground(alt);
290                 }
291                 format.setProperty(QTextFormat::FullWidthSelection, true);
292                 QTextEdit::ExtraSelection sel;
293                 sel.format = format;
294                 sel.cursor = c;
295                 viewSourceTV->setExtraSelections(
296                         QList<QTextEdit::ExtraSelection>() << sel);
297
298                 //clean up
299                 c.clearSelection();
300                 viewSourceTV->setTextCursor(c);
301                 viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
302         } // else if (texrow)
303         viewSourceTV->blockSignals(false);
304 }
305
306
307 docstring ViewSourceWidget::currentFormatName(BufferView const * bv) const
308 {
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_);
314 }
315
316
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()
320 {
321         if (!bv_ || !texrow_.get())
322                 return;
323         int row = viewSourceTV->textCursor().blockNumber() + 1;
324         const_cast<BufferView *>(bv_)->setCursorFromRow(row, *texrow_);
325 }
326
327
328
329 void ViewSourceWidget::updateDefaultFormat()
330 {
331         if (!bv_)
332                 return;
333
334         outputFormatCO->blockSignals(true);
335         outputFormatCO->clear();
336         outputFormatCO->addItem(qt_("Default"),
337                                 QVariant(QString("default")));
338
339         int index = 0;
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);
346                 if (!fmt) {
347                         LYXERR0("Can't find format for backend " << format << "!");
348                         continue;
349                 } 
350
351                 QString const pretty = qt_(fmt->prettyname());
352                 outputFormatCO->addItem(pretty, QVariant(toqstr(format)));
353                 if (format == view_format_)
354                    index = outputFormatCO->count() -1;
355         }
356         setViewFormat(index);
357
358         outputFormatCO->blockSignals(false);
359 }
360
361
362 void ViewSourceWidget::resizeEvent (QResizeEvent * event)
363 {
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);
368         } else {
369                 layout_->setDirection(QBoxLayout::LeftToRight);
370         }
371         QWidget::resizeEvent(event);
372 }
373
374
375 void ViewSourceWidget::saveSession(QSettings & settings, QString const & session_key) const
376 {
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());
382 }
383
384
385 void ViewSourceWidget::restoreSession(QString const & session_key)
386 {
387         QSettings settings;
388         view_format_ = fromqstr(settings.value(session_key + "/output", 0)
389                                 .toString());
390         contentsCO->setCurrentIndex(settings
391                                                                 .value(session_key + "/contents", 0)
392                                                                 .toInt());
393         masterPerspectiveCB->setChecked(settings
394                                                                         .value(session_key + "/masterview", false)
395                                                                         .toBool());
396         bool const checked = settings
397                 .value(session_key + "/autoupdate", true)
398                 .toBool();
399         autoUpdateCB->setChecked(checked);
400         if (checked)
401                 updateView();
402 }
403
404
405 GuiViewSource::GuiViewSource(GuiView & parent,
406                 Qt::DockWidgetArea area, Qt::WindowFlags flags)
407         : DockView(parent, "view-source", qt_("Code Preview"), area, flags)
408 {
409         widget_ = new ViewSourceWidget;
410         setWidget(widget_);
411         connect(widget_, SIGNAL(formatChanged()), this, SLOT(updateTitle()));
412 }
413
414
415 GuiViewSource::~GuiViewSource()
416 {
417         delete widget_;
418 }
419
420
421 void GuiViewSource::updateView()
422 {
423         if (widget_->autoUpdateCB->isChecked()) {
424                 widget_->setBufferView(bufferview());
425                 widget_->updateView();
426         }
427         widget_->masterPerspectiveCB->setEnabled(buffer().parent());
428         updateTitle();
429 }
430
431
432 void GuiViewSource::enableView(bool enable)
433 {
434         widget_->setBufferView(bufferview());
435         widget_->updateDefaultFormat();
436         if (!enable)
437                 // In the opposite case, updateView() will be called anyway.
438                 widget_->contentsChanged();
439 }
440
441
442 bool GuiViewSource::initialiseParams(string const & /*source*/)
443 {
444         updateTitle();
445         return true;
446 }
447
448
449 void GuiViewSource::updateTitle()
450 {
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)));
455         setTitle(title);
456         setWindowTitle(title);
457 }
458
459
460 void GuiViewSource::saveSession(QSettings & settings) const
461 {
462         Dialog::saveSession(settings);
463         widget_->saveSession(settings, sessionKey());
464 }
465
466
467 void GuiViewSource::restoreSession()
468 {
469         DockView::restoreSession();
470         widget_->restoreSession(sessionKey());
471 }
472
473
474 Dialog * createGuiViewSource(GuiView & lv)
475 {
476         return new GuiViewSource(lv);
477 }
478
479
480 } // namespace frontend
481 } // namespace lyx
482
483 #include "moc_GuiViewSource.cpp"