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