]> git.lyx.org Git - features.git/blob - src/frontends/qt/TocWidget.cpp
Use const references
[features.git] / src / frontends / qt / TocWidget.cpp
1 /**
2  * \file TocWidget.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 Abdelrazak Younes
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "TocWidget.h"
15
16 #include "GuiApplication.h"
17 #include "GuiView.h"
18 #include "qt_helpers.h"
19 #include "TocModel.h"
20
21 #include "Buffer.h"
22 #include "BufferView.h"
23 #include "Cursor.h"
24 #include "CutAndPaste.h"
25 #include "FuncRequest.h"
26 #include "FuncStatus.h"
27 #include "LyX.h"
28 #include "Menus.h"
29 #include "TocBackend.h"
30
31 #include "insets/InsetCommand.h"
32 #include "insets/InsetRef.h"
33
34 #include "support/debug.h"
35 #include "support/lassert.h"
36
37 #include <QHeaderView>
38 #include <QMenu>
39 #include <QTimer>
40
41 #include <vector>
42
43 using namespace std;
44
45 namespace lyx {
46 namespace frontend {
47
48 TocWidget::TocWidget(GuiView & gui_view, QWidget * parent)
49         : QWidget(parent), depth_(0), persistent_(false), gui_view_(gui_view),
50           timer_(new QTimer(this))
51 {
52         setupUi(this);
53
54         moveOutTB->setIcon(QIcon(getPixmap("images/", "outline-out", "svgz,png")));
55         moveInTB->setIcon(QIcon(getPixmap("images/", "outline-in", "svgz,png")));
56         moveUpTB->setIcon(QIcon(getPixmap("images/", "outline-up", "svgz,png")));
57         moveDownTB->setIcon(QIcon(getPixmap("images/", "outline-down", "svgz,png")));
58         updateTB->setIcon(QIcon(getPixmap("images/", "reload", "svgz,png")));
59
60         QSize icon_size = gui_view.iconSize();
61         moveOutTB->setIconSize(icon_size);
62         moveInTB->setIconSize(icon_size);
63         moveUpTB->setIconSize(icon_size);
64         moveDownTB->setIconSize(icon_size);
65         updateTB->setIconSize(icon_size);
66
67         // avoid flickering
68         tocTV->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
69
70         tocTV->showColumn(0);
71
72         // hide the pointless QHeader for now
73         // in the future, new columns may appear
74         // like labels, bookmarks, etc...
75         // tocTV->header()->hide();
76         tocTV->header()->setVisible(false);
77
78         // Only one item selected at a time.
79         tocTV->setSelectionMode(QAbstractItemView::SingleSelection);
80         setFocusProxy(tocTV);
81
82         // The toc types combo won't change its model.
83         typeCO->setModel(gui_view_.tocModels().nameModel());
84
85         // Make sure the buttons are disabled when first shown without a loaded
86         // Buffer.
87         enableControls(false);
88
89         // make us responsible for the context menu of the tabbar
90         setContextMenuPolicy(Qt::CustomContextMenu);
91         connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
92                 this, SLOT(showContextMenu(const QPoint &)));
93         connect(tocTV, SIGNAL(customContextMenuRequested(const QPoint &)),
94                 this, SLOT(showContextMenu(const QPoint &)));
95         connect(filterLE, SIGNAL(textEdited(QString)),
96                 this, SLOT(filterContents()));
97         connect(activeFilterCO, SIGNAL(activated(int)),
98                 this, SLOT(filterContents()));
99
100         // setting the update timer
101         timer_->setSingleShot(true);
102         connect(timer_, SIGNAL(timeout()), this, SLOT(finishUpdateView()));
103
104         init(QString());
105 }
106
107
108 void TocWidget::showContextMenu(const QPoint & pos)
109 {
110         std::string name = "context-toc-" + fromqstr(current_type_);
111         QMenu * menu = guiApp->menus().menu(toqstr(name), gui_view_);
112         if (!menu)
113                 return;
114         menu->exec(mapToGlobal(pos));
115 }
116
117
118 Inset * TocWidget::itemInset() const
119 {
120         QModelIndex const & index = tocTV->currentIndex();
121         TocItem const & item =
122                 gui_view_.tocModels().currentItem(current_type_, index);
123         DocIterator const & dit = item.dit();
124
125         Inset * inset = nullptr;
126         if (current_type_ == "label"
127                   || current_type_ == "graphics"
128                   || current_type_ == "citation"
129                   || current_type_ == "child")
130                 inset = dit.nextInset();
131
132         else if (current_type_ == "branch"
133                          || current_type_ == "index"
134                          || current_type_ == "change"
135                          || current_type_ == "table"
136                      || current_type_ == "listing"
137                      || current_type_ == "figure")
138                 inset = &dit.inset();
139
140         return inset;
141 }
142
143
144 bool TocWidget::getStatus(Cursor & cur, FuncRequest const & cmd,
145         FuncStatus & status) const
146 {
147         Inset * inset = itemInset();
148         FuncRequest tmpcmd(cmd);
149
150         QModelIndex const & index = tocTV->currentIndex();
151         TocItem const & item =
152                 gui_view_.tocModels().currentItem(current_type_, index);
153
154         switch (cmd.action())
155         {
156         case LFUN_CHANGE_ACCEPT:
157         case LFUN_CHANGE_REJECT:
158         case LFUN_OUTLINE_UP:
159         case LFUN_OUTLINE_DOWN:
160         case LFUN_OUTLINE_IN:
161         case LFUN_OUTLINE_OUT:
162         case LFUN_SECTION_SELECT:
163                 status.setEnabled((bool)item.dit());
164                 return true;
165
166         case LFUN_LABEL_COPY_AS_REFERENCE: {
167                 // For labels in math, we need to supply the label as a string
168                 FuncRequest label_copy(LFUN_LABEL_COPY_AS_REFERENCE, item.str());
169                 if (inset)
170                         return inset->getStatus(cur, label_copy, status);
171                 break;
172         }
173
174         default:
175                 if (inset)
176                         return inset->getStatus(cur, tmpcmd, status);
177         }
178
179         return false;
180 }
181
182
183 void TocWidget::doDispatch(Cursor & cur, FuncRequest const & cmd,
184                 DispatchResult & dr)
185 {
186
187         Inset * inset = itemInset();
188
189         QModelIndex const & index = tocTV->currentIndex();
190         TocItem const & item =
191                 gui_view_.tocModels().currentItem(current_type_, index);
192
193         // Start an undo group.
194         cur.beginUndoGroup();
195
196         switch (cmd.action())
197         {
198         case LFUN_CHANGE_ACCEPT:
199         case LFUN_CHANGE_REJECT: {
200                 // The action is almost always LYX_UNKNOWN_ACTION, which will
201                 // have the effect of moving the cursor to the location of
202                 // the change. (See TocItem::action.)
203                 dispatch(item.action());
204                 // If we do not reset the origin, then the request will be sent back
205                 // here, and we are in an infinite loop. But we need the dispatch
206                 // machinery to clean up for us, if the cursor is in an inset that
207                 // will be deleted. See bug #10316.
208                 FuncRequest tmpcmd(cmd);
209                 tmpcmd.setOrigin(FuncRequest::INTERNAL);
210                 dispatch(tmpcmd);
211                 dr.forceBufferUpdate();
212                 break;
213         }
214
215         case LFUN_SECTION_SELECT:
216                 dispatch(item.action());
217                 cur.dispatch(cmd);
218                 // necessary to get the selection drawn.
219                 cur.buffer()->changed(true);
220                 gui_view_.setFocus();
221                 break;
222
223         case LFUN_LABEL_COPY_AS_REFERENCE: {
224                 // For labels in math, we need to supply the label as a string
225                 FuncRequest label_copy(LFUN_LABEL_COPY_AS_REFERENCE, item.str());
226                 if (inset)
227                         inset->dispatch(cur, label_copy);
228                 break;
229         }
230
231         case LFUN_OUTLINE_UP:
232         case LFUN_OUTLINE_DOWN:
233         case LFUN_OUTLINE_IN:
234         case LFUN_OUTLINE_OUT:
235                 outline(cmd.action());
236                 break;
237
238         default: {
239                 FuncRequest tmpcmd(cmd);
240                 if (inset)
241                         inset->dispatch(cur, tmpcmd);
242         }
243         }
244         cur.endUndoGroup();
245 }
246
247
248 void TocWidget::on_tocTV_activated(QModelIndex const & index)
249 {
250         goTo(index);
251 }
252
253
254 void TocWidget::on_tocTV_pressed(QModelIndex const & index)
255 {
256
257         Qt::MouseButtons const button = QApplication::mouseButtons();
258         if (button & Qt::LeftButton) {
259                 goTo(index);
260                 gui_view_.setFocus();
261                 gui_view_.activateWindow();
262         }
263 }
264
265
266 void TocWidget::goTo(QModelIndex const & index)
267 {
268         LYXERR(Debug::GUI, "goto " << index.row()
269                 << ", " << index.column());
270
271         sendDispatch(gui_view_.tocModels().goTo(current_type_, index));
272 }
273
274
275 void TocWidget::on_updateTB_clicked()
276 {
277         // The backend update can take some time so we disable
278         // the controls while waiting.
279         enableControls(false);
280         gui_view_.currentBufferView()->buffer().updateBuffer();
281 }
282
283
284 void TocWidget::on_sortCB_stateChanged(int state)
285 {
286         gui_view_.tocModels().sort(current_type_, state == Qt::Checked);
287         updateViewNow();
288 }
289
290
291 void TocWidget::on_persistentCB_stateChanged(int state)
292 {
293         persistent_ = state == Qt::Checked;
294 }
295
296
297 #if 0
298 /* FIXME (Ugras 17/11/06):
299 I have implemented a indexDepth function to get the model indices. In my
300 opinion, somebody should derive a new qvariant class for tocModelItem
301 which saves the string data and depth information. That will save the
302 depth calculation.  */
303
304 static int indexDepth(QModelIndex const & index, int depth = -1)
305 {
306         ++depth;
307         return index.parent() == QModelIndex()
308                 ? depth : indexDepth(index.parent(), depth);
309 }
310 #endif
311
312 void TocWidget::on_depthSL_valueChanged(int depth)
313 {
314         if (depth == depth_)
315                 return;
316         setTreeDepth(depth);
317         gui_view_.setFocus();
318 }
319
320
321 void TocWidget::setTreeDepth(int depth)
322 {
323         depth_ = depth;
324         if (!tocTV->model())
325                 return;
326
327         if (depth == 0)
328                 tocTV->collapseAll();
329         else
330                 tocTV->expandToDepth(depth - 1);
331 }
332
333
334 void TocWidget::on_typeCO_currentIndexChanged(int index)
335 {
336
337         if (index == -1)
338                 return;
339         current_type_ = typeCO->itemData(index).toString();
340         updateViewNow();
341         if (typeCO->hasFocus())
342                 gui_view_.setFocus();
343 }
344
345
346 void TocWidget::outline(FuncCode func_code)
347 {
348         QModelIndexList const & list = tocTV->selectionModel()->selectedIndexes();
349         if (list.isEmpty())
350                 return;
351
352         //if another window is active, this attempt will fail,
353         //but it will work at least for the second attempt
354         gui_view_.activateWindow(); 
355
356         enableControls(false);
357         goTo(list[0]);
358         sendDispatch(FuncRequest(func_code));
359         enableControls(true);
360         gui_view_.setFocus();
361 }
362
363
364 void TocWidget::sendDispatch(FuncRequest fr)
365 {
366
367         fr.setViewOrigin(&gui_view_);
368         DispatchResult const & dr = dispatch(fr);
369         if (dr.error())
370                 gui_view_.message(dr.message());
371 }
372
373
374 void TocWidget::on_moveUpTB_clicked()
375 {
376         outline(LFUN_OUTLINE_UP);
377 }
378
379
380 void TocWidget::on_moveDownTB_clicked()
381 {
382         outline(LFUN_OUTLINE_DOWN);
383 }
384
385
386 void TocWidget::on_moveInTB_clicked()
387 {
388         outline(LFUN_OUTLINE_IN);
389 }
390
391
392 void TocWidget::on_moveOutTB_clicked()
393 {
394         outline(LFUN_OUTLINE_OUT);
395 }
396
397
398 void TocWidget::select(QModelIndex const & index)
399 {
400         if (!index.isValid()) {
401                 LYXERR(Debug::GUI, "TocWidget::select(): QModelIndex is invalid!");
402                 return;
403         }
404
405         tocTV->scrollTo(index);
406         tocTV->clearSelection();
407         tocTV->setCurrentIndex(index);
408 }
409
410
411 void TocWidget::enableControls(bool enable)
412 {
413         updateTB->setEnabled(enable);
414
415         if (!canOutline())
416                 enable = false;
417
418         moveUpTB->setEnabled(enable);
419         moveDownTB->setEnabled(enable);
420         moveInTB->setEnabled(enable);
421         moveOutTB->setEnabled(enable);
422 }
423
424
425 void TocWidget::updateView()
426 {
427         if (!gui_view_.documentBufferView()) {
428                 tocTV->setModel(nullptr);
429                 depthSL->setMaximum(0);
430                 depthSL->setValue(0);
431                 setEnabled(false);
432                 return;
433         }
434         setEnabled(true);
435         bool const is_sortable = isSortable();
436         sortCB->setEnabled(is_sortable);
437         bool focus = tocTV->hasFocus();
438         tocTV->setEnabled(false);
439         tocTV->setUpdatesEnabled(false);
440
441         QAbstractItemModel * toc_model =
442                         gui_view_.tocModels().model(current_type_);
443         if (tocTV->model() != toc_model) {
444                 tocTV->setModel(toc_model);
445                 tocTV->setEditTriggers(QAbstractItemView::NoEditTriggers);
446                 if (persistent_)
447                         setTreeDepth(depth_);
448         }
449
450         sortCB->blockSignals(true);
451         sortCB->setChecked(is_sortable
452                 && gui_view_.tocModels().isSorted(current_type_));
453         sortCB->blockSignals(false);
454
455         persistentCB->setEnabled(canNavigate());
456
457         bool controls_enabled = toc_model && toc_model->rowCount() > 0
458                 && !gui_view_.documentBufferView()->buffer().isReadonly();
459         enableControls(controls_enabled);
460
461         depthSL->setMaximum(gui_view_.tocModels().depth(current_type_));
462         depthSL->setValue(depth_);
463         tocTV->setEnabled(true);
464         tocTV->setUpdatesEnabled(true);
465         if (focus)
466                 tocTV->setFocus();
467
468         // Expensive operations are on a timer.  We finish the update immediately
469         // for sparse edition actions, i.e. there was no edition/cursor movement
470         // recently, then every 300ms.
471         if (!timer_->isActive()) {
472                 finishUpdateView();
473                 timer_->start(300);
474         }
475 }
476
477
478 void TocWidget::updateViewNow()
479 {
480         timer_->stop();
481         updateView();
482 }
483
484
485 void TocWidget::finishUpdateView()
486 {
487         // Profiling shows that this is the expensive stuff in the context of typing
488         // text and moving with arrows. For bigger operations, this is negligible,
489         // and outweighted by TocModels::reset() anyway.
490         if (canNavigate()) {
491                 if (!persistent_)
492                         setTreeDepth(depth_);
493                 persistentCB->setChecked(persistent_);
494                 // select the item at current cursor location
495                 if (gui_view_.documentBufferView()) {
496                         DocIterator const & dit = gui_view_.documentBufferView()->cursor();
497                         select(gui_view_.tocModels().currentIndex(current_type_, dit));
498                 }
499         }
500         filterContents();
501 }
502
503
504 void TocWidget::filterContents()
505 {
506         if (!tocTV->model())
507                 return;
508
509         QModelIndexList indices = tocTV->model()->match(
510                 tocTV->model()->index(0, 0),
511                 Qt::DisplayRole, "*", -1,
512                 Qt::MatchFlags(Qt::MatchWildcard|Qt::MatchRecursive));
513
514         bool const show_active =
515                 activeFilterCO->currentIndex() != 2;
516         bool const show_inactive =
517                 activeFilterCO->currentIndex() != 1;
518
519         int size = indices.size();
520         for (int i = 0; i < size; i++) {
521                 QModelIndex index = indices[i];
522                 bool matches =
523                         index.data().toString().contains(
524                                 filterLE->text(), Qt::CaseInsensitive);
525                 TocItem const & item =
526                         gui_view_.tocModels().currentItem(current_type_, index);
527                 matches &= (show_active && item.isOutput()) || (show_inactive && !item.isOutput());
528                 tocTV->setRowHidden(index.row(), index.parent(), !matches);
529         }
530         // recursively unhide parents of unhidden children
531         for (int i = size - 1; i >= 0; i--) {
532                 QModelIndex index = indices[i];
533                 if (!tocTV->isRowHidden(index.row(), index.parent())
534                     && index.parent() != QModelIndex())
535                         tocTV->setRowHidden(index.parent().row(),
536                                             index.parent().parent(), false);
537         }
538 }
539
540
541 static QString decodeType(QString const & str)
542 {
543         QString type = str;
544         if (type.contains("tableofcontents"))
545                 type = "tableofcontents";
546         else if (type.contains("lstlistoflistings"))
547                 type = "listing";
548         else if (type.contains("floatlist")) {
549                 if (type.contains("\"figure"))
550                         type = "figure";
551                 else if (type.contains("\"table"))
552                         type = "table";
553                 else if (type.contains("\"algorithm"))
554                         type = "algorithm";
555         }
556         return type;
557 }
558
559
560 void TocWidget::init(QString const & str)
561 {
562         int new_index;
563         if (str.isEmpty())
564                 new_index = typeCO->findData(current_type_);
565         else
566                 new_index = typeCO->findData(decodeType(str));
567
568         // If everything else fails, settle on the table of contents which is
569         // guaranteed to exist.
570         if (new_index == -1) {
571                 current_type_ = "tableofcontents";
572                 new_index = typeCO->findData(current_type_);
573         } else {
574                 current_type_ = typeCO->itemData(new_index).toString();
575         }
576
577         typeCO->blockSignals(true);
578         typeCO->setCurrentIndex(new_index);
579         typeCO->blockSignals(false);
580         updateViewNow();
581 }
582
583 } // namespace frontend
584 } // namespace lyx
585
586 #include "moc_TocWidget.cpp"