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