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