]> git.lyx.org Git - lyx.git/blob - src/frontends/qt4/GuiViewSource.cpp
Store both sets of font selections
[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         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);
95 }
96
97
98 void ViewSourceWidget::getContent(BufferView const * view,
99                         Buffer::OutputWhat output, docstring & str, string const & format,
100                         bool master)
101 {
102         // get the *top* level paragraphs that contain the cursor,
103         // or the selected text
104         pit_type par_begin;
105         pit_type par_end;
106
107         if (!view->cursor().selection()) {
108                 par_begin = view->cursor().bottom().pit();
109                 par_end = par_begin;
110         } else {
111                 par_begin = view->cursor().selectionBegin().bottom().pit();
112                 par_end = view->cursor().selectionEnd().bottom().pit();
113         }
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";
121 }
122
123
124 void ViewSourceWidget::setBufferView(BufferView const * bv)
125 {
126         if (bv_ != bv) {
127                 setText();
128                 bv_ = bv;
129         }
130         setEnabled(bv ?  true : false);
131 }
132
133
134 bool ViewSourceWidget::setText(QString const & qstr)
135 {
136         bool const changed = document_->toPlainText() != qstr;
137         viewSourceTV->setExtraSelections(QList<QTextEdit::ExtraSelection>());
138         if (changed)
139                 document_->setPlainText(qstr);
140         return changed;
141 }
142
143
144 void ViewSourceWidget::contentsChanged()
145 {
146         if (autoUpdateCB->isChecked())
147                 updateViewNow();
148 }
149
150
151 void ViewSourceWidget::setViewFormat(int const index)
152 {
153         outputFormatCO->setCurrentIndex(index);
154         view_format_ = outputFormatCO->itemData(index).toString();
155 }
156
157
158 void ViewSourceWidget::updateView()
159 {
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);
165 }
166
167 void ViewSourceWidget::updateViewNow()
168 {
169         update_timer_->start(0);
170 }
171
172 namespace {
173
174 QString prependTexRow(TexRow const & texrow, QString const & content)
175 {
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);
181         QString qstr;
182         for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
183                 qstr += toqstr(*it) + '\n';
184         return qstr;
185 }
186
187 } // anon namespace
188
189 void ViewSourceWidget::realUpdateView()
190 {
191         if (!bv_) {
192                 setText();
193                 setEnabled(false);
194                 return;
195         }
196
197         setEnabled(true);
198
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();
204
205         string const format = fromqstr(view_format_);
206
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;
214
215         docstring content;
216         getContent(bv_, output, content, format, masterPerspectiveCB->isChecked());
217         QString old = document_->toPlainText();
218         QString qcontent = toqstr(content);
219 #ifdef DEVEL_VERSION
220         if (texrow_.get() && lyx::lyxerr.debugging(Debug::LATEX))
221                 qcontent = prependTexRow(*texrow_, qcontent);
222 #endif
223         // prevent gotoCursor()
224         viewSourceTV->blockSignals(true);
225         bool const changed = setText(qcontent);
226
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();
232                 int pos = 0;
233                 while (*oc != '\0' && *nc != '\0' && *oc == *nc) {
234                         ++oc;
235                         ++nc;
236                         ++pos;
237                 }
238                 QTextCursor c = QTextCursor(viewSourceTV->document());
239                 //get some space below the cursor
240                 c.setPosition(pos);
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);
245                 c.setPosition(pos);
246                 const int block = c.blockNumber();
247                 for (int i = h_margin; i && block == c.blockNumber(); --i) {
248                         c.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor);
249                 }
250                 c.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor);
251                 viewSourceTV->setTextCursor(c);
252                 //back to the position
253                 c.setPosition(pos);
254                 //c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
255                 viewSourceTV->setTextCursor(c);
256
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;
263
264                 QTextCursor c = QTextCursor(viewSourceTV->document());
265
266                 c.movePosition(QTextCursor::NextBlock, QTextCursor::MoveAnchor,
267                                            beg_row - 1);
268                 const int beg_sel = c.position();
269                 //get some space above the cursor
270                 c.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor,
271                                            v_margin);
272                 viewSourceTV->setTextCursor(c);
273                 c.setPosition(beg_sel, QTextCursor::MoveAnchor);
274
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,
280                                            v_margin - 1);
281                 viewSourceTV->setTextCursor(c);
282                 c.setPosition(end_sel, QTextCursor::KeepAnchor);
283
284                 viewSourceTV->setTextCursor(c);
285
286                 //the real highlighting is done with an ExtraSelection
287                 QTextCharFormat format;
288                 QPalette palette = viewSourceTV->palette();
289                 //Alternative:
290                 //  QColor bg = palette.color(QPalette::Active,QPalette::Highlight);
291                 //  bg.setAlpha(64);
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;
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 // 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()
316 {
317         if (!bv_ || !texrow_.get())
318                 return;
319         int row = viewSourceTV->textCursor().blockNumber() + 1;
320         const_cast<BufferView *>(bv_)->setCursorFromRow(row, *texrow_);
321 }
322
323
324
325 void ViewSourceWidget::updateDefaultFormat()
326 {
327         if (!bv_)
328                 return;
329
330         outputFormatCO->blockSignals(true);
331         outputFormatCO->clear();
332         outputFormatCO->addItem(qt_("Default"),
333                                 QVariant(QString("default")));
334
335         int index = 0;
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);
342                 if (!fmt) {
343                         LYXERR0("Can't find format for backend " << format << "!");
344                         continue;
345                 } 
346
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;
352         }
353         setViewFormat(index);
354
355         outputFormatCO->blockSignals(false);
356 }
357
358
359 void ViewSourceWidget::resizeEvent (QResizeEvent * event)
360 {
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);
365         } else {
366                 layout_->setDirection(QBoxLayout::LeftToRight);
367         }
368         QWidget::resizeEvent(event);
369 }
370
371 void ViewSourceWidget::saveSession(QString const & session_key) const
372 {
373         QSettings settings;
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());
379 }
380
381
382 void ViewSourceWidget::restoreSession(QString const & session_key)
383 {
384         QSettings settings;
385     view_format_ = settings.value(session_key + "/output", 0).toString();
386         contentsCO->setCurrentIndex(settings
387                                                                 .value(session_key + "/contents", 0)
388                                                                 .toInt());
389         masterPerspectiveCB->setChecked(settings
390                                                                         .value(session_key + "/masterview", false)
391                                                                         .toBool());
392         bool const checked = settings
393                 .value(session_key + "/autoupdate", true)
394                 .toBool();
395         autoUpdateCB->setChecked(checked);
396         if (checked)
397                 updateView();
398 }
399
400
401 GuiViewSource::GuiViewSource(GuiView & parent,
402                 Qt::DockWidgetArea area, Qt::WindowFlags flags)
403         : DockView(parent, "view-source", qt_("LaTeX Source"), area, flags)
404 {
405         widget_ = new ViewSourceWidget;
406         setWidget(widget_);
407 }
408
409
410 GuiViewSource::~GuiViewSource()
411 {
412         delete widget_;
413 }
414
415
416 void GuiViewSource::updateView()
417 {
418         if (widget_->autoUpdateCB->isChecked()) {
419                 widget_->setBufferView(bufferview());
420                 widget_->updateView();
421         }
422         widget_->masterPerspectiveCB->setEnabled(buffer().parent());
423 }
424
425
426 void GuiViewSource::enableView(bool enable)
427 {
428         widget_->setBufferView(bufferview());
429         widget_->updateDefaultFormat();
430         if (!enable)
431                 // In the opposite case, updateView() will be called anyway.
432                 widget_->contentsChanged();
433 }
434
435
436 bool GuiViewSource::initialiseParams(string const & /*source*/)
437 {
438         setWindowTitle(title());
439         return true;
440 }
441
442
443 QString GuiViewSource::title() const
444 {
445         switch (docType()) {
446                 case LATEX:
447                         //FIXME: this is shown for LyXHTML source, LyX source, etc.
448                         return qt_("LaTeX Source");
449                 case DOCBOOK:
450                         return qt_("DocBook Source");
451                 case LITERATE:
452                         return qt_("Literate Source");
453         }
454         LATTEST(false);
455         return QString();
456 }
457
458
459 void GuiViewSource::saveSession() const
460 {
461         Dialog::saveSession();
462         widget_->saveSession(sessionKey());
463 }
464
465
466 void GuiViewSource::restoreSession()
467 {
468         DockView::restoreSession();
469         widget_->restoreSession(sessionKey());
470 }
471
472
473 Dialog * createGuiViewSource(GuiView & lv)
474 {
475         return new GuiViewSource(lv);
476 }
477
478
479 } // namespace frontend
480 } // namespace lyx
481
482 #include "moc_GuiViewSource.cpp"