lyx_mirror/src/frontends/qt/TocWidget.cpp
Jean-Marc Lasgouttes ae528715d3 Fix ToC action when cursor is in adv. F&R pane
This important part is the last point, the rest is what is needed to
make it happen.

* implement (FindAndReplace|FindAndReplaceWidget)::hasWorkArea, that
  tell whether a work area is own by the advanced find & replace
  widget.

* factor out method find() from GuiView::findOrBuild.

* implement GuiView::hasVisibleWorkArea, that tells whether a workarea
  is visible in the view (current tab in a split or adv. f&r
  workarea).

* Finally, in TocWidget::sendDispatch, change the current workarea
  temporarily to the document workarea before dispatching the
  function. The code tries to be as careful as possible to handle all
  cases. The future will tell whether it is good enough.
2022-06-29 11:09:14 +02:00

622 lines
15 KiB
C++

/**
* \file TocWidget.cpp
* This file is part of LyX, the document processor.
* Licence details can be found in the file COPYING.
*
* \author John Levon
* \author Abdelrazak Younes
*
* Full author contact details are available in file CREDITS.
*/
#include <config.h>
#include "TocWidget.h"
#include "GuiApplication.h"
#include "GuiView.h"
#include "qt_helpers.h"
#include "TocModel.h"
#include "FancyLineEdit.h"
#include "Buffer.h"
#include "BufferView.h"
#include "Cursor.h"
#include "CutAndPaste.h"
#include "FuncRequest.h"
#include "FuncStatus.h"
#include "LyX.h"
#include "Menus.h"
#include "TocBackend.h"
#include "insets/InsetCommand.h"
#include "insets/InsetRef.h"
#include "support/debug.h"
#include "support/lassert.h"
#include <QHeaderView>
#include <QMenu>
#include <QTimer>
#include <vector>
using namespace std;
namespace lyx {
namespace frontend {
TocWidget::TocWidget(GuiView & gui_view, QWidget * parent)
: QWidget(parent), depth_(0), persistent_(false), gui_view_(gui_view),
timer_(new QTimer(this))
{
setupUi(this);
moveOutTB->setIcon(QIcon(getPixmap("images/", "outline-out", "svgz,png")));
moveInTB->setIcon(QIcon(getPixmap("images/", "outline-in", "svgz,png")));
moveUpTB->setIcon(QIcon(getPixmap("images/", "outline-up", "svgz,png")));
moveDownTB->setIcon(QIcon(getPixmap("images/", "outline-down", "svgz,png")));
updateTB->setIcon(QIcon(getPixmap("images/", "reload", "svgz,png")));
QSize icon_size = gui_view.iconSize();
moveOutTB->setIconSize(icon_size);
moveInTB->setIconSize(icon_size);
moveUpTB->setIconSize(icon_size);
moveDownTB->setIconSize(icon_size);
updateTB->setIconSize(icon_size);
// avoid flickering
tocTV->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
tocTV->showColumn(0);
// hide the pointless QHeader for now
// in the future, new columns may appear
// like labels, bookmarks, etc...
// tocTV->header()->hide();
tocTV->header()->setVisible(false);
// Only one item selected at a time.
tocTV->setSelectionMode(QAbstractItemView::SingleSelection);
// The toc types combo won't change its model.
typeCO->setModel(gui_view_.tocModels().nameModel());
// The filter bar
filter_ = new FancyLineEdit(this);
filter_->setClearButton(true);
filter_->setPlaceholderText(qt_("All items"));
filterBarL->addWidget(filter_, 0);
filterLA->setBuddy(filter_);
setFocusProxy(filter_);
// Make sure the buttons are disabled when first shown without a loaded
// Buffer.
enableControls(false);
// make us responsible for the context menu of the tabbar
setContextMenuPolicy(Qt::CustomContextMenu);
connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showContextMenu(const QPoint &)));
connect(tocTV, SIGNAL(customContextMenuRequested(const QPoint &)),
this, SLOT(showContextMenu(const QPoint &)));
connect(filter_, SIGNAL(textEdited(QString)),
this, SLOT(filterContents()));
#if (QT_VERSION < 0x050000)
connect(filter_, SIGNAL(downPressed()),
tocTV, SLOT(setFocus()));
#else
connect(filter_, &FancyLineEdit::downPressed,
tocTV, [this](){ focusAndHighlight(tocTV); });
#endif
connect(activeFilterCO, SIGNAL(activated(int)),
this, SLOT(filterContents()));
// setting the update timer
timer_->setSingleShot(true);
connect(timer_, SIGNAL(timeout()), this, SLOT(finishUpdateView()));
init(QString());
}
void TocWidget::showContextMenu(const QPoint & pos)
{
std::string name = "context-toc-" + fromqstr(current_type_);
QMenu * menu = guiApp->menus().menu(toqstr(name), gui_view_);
if (!menu)
return;
menu->exec(mapToGlobal(pos));
}
Inset * TocWidget::itemInset() const
{
QModelIndex const & index = tocTV->currentIndex();
TocItem const & item =
gui_view_.tocModels().currentItem(current_type_, index);
DocIterator const & dit = item.dit();
Inset * inset = nullptr;
if (current_type_ == "label"
|| current_type_ == "graphics"
|| current_type_ == "citation"
|| current_type_ == "child")
inset = dit.nextInset();
else if (current_type_ == "branch"
|| current_type_ == "index"
|| current_type_ == "change"
|| current_type_ == "table"
|| current_type_ == "listing"
|| current_type_ == "figure")
inset = &dit.inset();
return inset;
}
bool TocWidget::getStatus(Cursor & cur, FuncRequest const & cmd,
FuncStatus & status) const
{
Inset * inset = itemInset();
FuncRequest tmpcmd(cmd);
QModelIndex const & index = tocTV->currentIndex();
TocItem const & item =
gui_view_.tocModels().currentItem(current_type_, index);
switch (cmd.action())
{
case LFUN_CHANGE_ACCEPT:
case LFUN_CHANGE_REJECT:
case LFUN_OUTLINE_UP:
case LFUN_OUTLINE_DOWN:
case LFUN_OUTLINE_IN:
case LFUN_OUTLINE_OUT:
case LFUN_SECTION_SELECT:
status.setEnabled((bool)item.dit());
return true;
case LFUN_LABEL_COPY_AS_REFERENCE: {
// For labels in math, we need to supply the label as a string
FuncRequest label_copy(LFUN_LABEL_COPY_AS_REFERENCE, item.str());
if (inset)
return inset->getStatus(cur, label_copy, status);
break;
}
default:
if (inset)
return inset->getStatus(cur, tmpcmd, status);
}
return false;
}
void TocWidget::doDispatch(Cursor & cur, FuncRequest const & cmd,
DispatchResult & dr)
{
Inset * inset = itemInset();
QModelIndex const & index = tocTV->currentIndex();
TocItem const & item =
gui_view_.tocModels().currentItem(current_type_, index);
// Start an undo group.
cur.beginUndoGroup();
switch (cmd.action())
{
case LFUN_CHANGE_ACCEPT:
case LFUN_CHANGE_REJECT: {
// The action is almost always LYX_UNKNOWN_ACTION, which will
// have the effect of moving the cursor to the location of
// the change. (See TocItem::action.)
dispatch(item.action());
// If we do not reset the origin, then the request will be sent back
// here, and we are in an infinite loop. But we need the dispatch
// machinery to clean up for us, if the cursor is in an inset that
// will be deleted. See bug #10316.
FuncRequest tmpcmd(cmd);
tmpcmd.setOrigin(FuncRequest::INTERNAL);
dispatch(tmpcmd);
dr.forceBufferUpdate();
break;
}
case LFUN_SECTION_SELECT:
dispatch(item.action());
cur.dispatch(cmd);
// necessary to get the selection drawn.
cur.buffer()->changed(true);
gui_view_.setFocus();
break;
case LFUN_LABEL_COPY_AS_REFERENCE: {
// For labels in math, we need to supply the label as a string
FuncRequest label_copy(LFUN_LABEL_COPY_AS_REFERENCE, item.str());
if (inset)
inset->dispatch(cur, label_copy);
break;
}
case LFUN_OUTLINE_UP:
case LFUN_OUTLINE_DOWN:
case LFUN_OUTLINE_IN:
case LFUN_OUTLINE_OUT:
outline(cmd.action());
break;
default: {
FuncRequest tmpcmd(cmd);
if (inset)
inset->dispatch(cur, tmpcmd);
}
}
cur.endUndoGroup();
}
void TocWidget::on_tocTV_activated(QModelIndex const & index)
{
goTo(index);
}
void TocWidget::on_tocTV_pressed(QModelIndex const & index)
{
Qt::MouseButtons const button = QApplication::mouseButtons();
if (button & Qt::LeftButton) {
goTo(index);
gui_view_.setFocus();
gui_view_.activateWindow();
}
}
void TocWidget::goTo(QModelIndex const & index)
{
LYXERR(Debug::GUI, "goto " << index.row()
<< ", " << index.column());
sendDispatch(gui_view_.tocModels().goTo(current_type_, index));
}
void TocWidget::on_updateTB_clicked()
{
// The backend update can take some time so we disable
// the controls while waiting.
enableControls(false);
gui_view_.currentBufferView()->buffer().updateBuffer();
}
void TocWidget::on_sortCB_stateChanged(int state)
{
gui_view_.tocModels().sort(current_type_, state == Qt::Checked);
updateViewNow();
}
void TocWidget::on_persistentCB_stateChanged(int state)
{
persistent_ = state == Qt::Checked;
}
#if 0
/* FIXME (Ugras 17/11/06):
I have implemented a indexDepth function to get the model indices. In my
opinion, somebody should derive a new qvariant class for tocModelItem
which saves the string data and depth information. That will save the
depth calculation. */
static int indexDepth(QModelIndex const & index, int depth = -1)
{
++depth;
return index.parent() == QModelIndex()
? depth : indexDepth(index.parent(), depth);
}
#endif
void TocWidget::on_depthSL_valueChanged(int depth)
{
if (depth == depth_)
return;
setTreeDepth(depth);
gui_view_.setFocus();
}
void TocWidget::setTreeDepth(int depth)
{
depth_ = depth;
if (!tocTV->model())
return;
if (depth == 0)
tocTV->collapseAll();
else
tocTV->expandToDepth(depth - 1);
}
void TocWidget::on_typeCO_currentIndexChanged(int index)
{
if (index == -1)
return;
current_type_ = typeCO->itemData(index).toString();
updateViewNow();
if (typeCO->hasFocus())
gui_view_.setFocus();
}
void TocWidget::outline(FuncCode func_code)
{
QModelIndexList const & list = tocTV->selectionModel()->selectedIndexes();
if (list.isEmpty())
return;
//if another window is active, this attempt will fail,
//but it will work at least for the second attempt
gui_view_.activateWindow();
enableControls(false);
goTo(list[0]);
sendDispatch(FuncRequest(func_code));
enableControls(true);
gui_view_.setFocus();
}
void TocWidget::sendDispatch(FuncRequest fr)
{
fr.setViewOrigin(&gui_view_);
GuiWorkArea * old_wa = gui_view_.currentWorkArea();
GuiWorkArea * doc_wa = gui_view_.currentMainWorkArea();
/* The ToC command should be dispatched to the document work area,
* not the Adv. Find&Replace (which is the only other know
* possibility.
*/
if (doc_wa != nullptr && doc_wa != old_wa)
gui_view_.setCurrentWorkArea(doc_wa);
DispatchResult const & dr = dispatch(fr);
/* If the current workarea has not explicitely changed, and the
* original one is still visible, let's reset it.
*/
if (gui_view_.currentWorkArea() == doc_wa
&& gui_view_.hasVisibleWorkArea(old_wa)
&& doc_wa != old_wa)
gui_view_.setCurrentWorkArea(old_wa);
if (dr.error())
gui_view_.message(dr.message());
}
void TocWidget::on_moveUpTB_clicked()
{
outline(LFUN_OUTLINE_UP);
}
void TocWidget::on_moveDownTB_clicked()
{
outline(LFUN_OUTLINE_DOWN);
}
void TocWidget::on_moveInTB_clicked()
{
outline(LFUN_OUTLINE_IN);
}
void TocWidget::on_moveOutTB_clicked()
{
outline(LFUN_OUTLINE_OUT);
}
void TocWidget::select(QModelIndex const & index)
{
if (!index.isValid()) {
LYXERR(Debug::GUI, "TocWidget::select(): QModelIndex is invalid!");
return;
}
tocTV->scrollTo(index);
tocTV->clearSelection();
tocTV->setCurrentIndex(index);
}
void TocWidget::enableControls(bool enable)
{
updateTB->setEnabled(enable);
if (!canOutline())
enable = false;
moveUpTB->setEnabled(enable);
moveDownTB->setEnabled(enable);
moveInTB->setEnabled(enable);
moveOutTB->setEnabled(enable);
}
void TocWidget::updateView()
{
if (!gui_view_.documentBufferView()) {
tocTV->setModel(nullptr);
depthSL->setMaximum(0);
depthSL->setValue(0);
setEnabled(false);
return;
}
setEnabled(true);
bool const is_sortable = isSortable();
sortCB->setEnabled(is_sortable);
bool focus = tocTV->hasFocus();
tocTV->setEnabled(false);
tocTV->setUpdatesEnabled(false);
QAbstractItemModel * toc_model =
gui_view_.tocModels().model(current_type_);
if (tocTV->model() != toc_model) {
tocTV->setModel(toc_model);
tocTV->setEditTriggers(QAbstractItemView::NoEditTriggers);
if (persistent_)
setTreeDepth(depth_);
}
sortCB->blockSignals(true);
sortCB->setChecked(is_sortable
&& gui_view_.tocModels().isSorted(current_type_));
sortCB->blockSignals(false);
persistentCB->setEnabled(canNavigate());
bool controls_enabled = toc_model && toc_model->rowCount() > 0
&& !gui_view_.documentBufferView()->buffer().isReadonly();
enableControls(controls_enabled);
depthSL->setMaximum(gui_view_.tocModels().depth(current_type_));
depthSL->setValue(depth_);
tocTV->setEnabled(true);
tocTV->setUpdatesEnabled(true);
if (focus)
tocTV->setFocus();
// Expensive operations are on a timer. We finish the update immediately
// for sparse edition actions, i.e. there was no edition/cursor movement
// recently, then every 300ms.
if (!timer_->isActive()) {
finishUpdateView();
timer_->start(300);
}
}
void TocWidget::updateViewNow()
{
timer_->stop();
updateView();
}
void TocWidget::finishUpdateView()
{
// Profiling shows that this is the expensive stuff in the context of typing
// text and moving with arrows. For bigger operations, this is negligible,
// and outweighted by TocModels::reset() anyway.
if (canNavigate()) {
if (!persistent_)
setTreeDepth(depth_);
persistentCB->setChecked(persistent_);
// select the item at current cursor location
if (gui_view_.documentBufferView()) {
DocIterator const & dit = gui_view_.documentBufferView()->cursor();
select(gui_view_.tocModels().currentIndex(current_type_, dit));
}
}
filterContents();
}
void TocWidget::filterContents()
{
if (!tocTV->model())
return;
QModelIndexList indices = tocTV->model()->match(
tocTV->model()->index(0, 0),
Qt::DisplayRole, ".*", -1,
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
Qt::MatchFlags(Qt::MatchRegularExpression|Qt::MatchRecursive));
#else
// deprecated in Qt 5.15.
Qt::MatchFlags(Qt::MatchRegExp|Qt::MatchRecursive));
#endif
bool const show_active =
activeFilterCO->currentIndex() != 2;
bool const show_inactive =
activeFilterCO->currentIndex() != 1;
int size = indices.size();
QString const matchstring = filter_ ? filter_->text() : QString();
for (int i = 0; i < size; i++) {
QModelIndex index = indices[i];
bool matches = index.data().toString().contains(
matchstring, Qt::CaseInsensitive);
TocItem const & item =
gui_view_.tocModels().currentItem(current_type_, index);
matches &= (show_active && item.isOutput()) || (show_inactive && !item.isOutput());
tocTV->setRowHidden(index.row(), index.parent(), !matches);
}
// recursively unhide parents of unhidden children
for (int i = size - 1; i >= 0; i--) {
QModelIndex index = indices[i];
if (!tocTV->isRowHidden(index.row(), index.parent())
&& index.parent() != QModelIndex())
tocTV->setRowHidden(index.parent().row(),
index.parent().parent(), false);
}
}
static QString decodeType(QString const & str)
{
QString type = str;
if (type.contains("tableofcontents"))
type = "tableofcontents";
else if (type.contains("lstlistoflistings"))
type = "listing";
else if (type.contains("floatlist")) {
if (type.contains("\"figure"))
type = "figure";
else if (type.contains("\"table"))
type = "table";
else if (type.contains("\"algorithm"))
type = "algorithm";
}
return type;
}
void TocWidget::init(QString const & str)
{
int new_index;
if (str.isEmpty())
new_index = typeCO->findData(current_type_);
else
new_index = typeCO->findData(decodeType(str));
// If everything else fails, settle on the table of contents which is
// guaranteed to exist.
if (new_index == -1) {
current_type_ = "tableofcontents";
new_index = typeCO->findData(current_type_);
} else {
current_type_ = typeCO->itemData(new_index).toString();
}
typeCO->blockSignals(true);
typeCO->setCurrentIndex(new_index);
typeCO->blockSignals(false);
updateViewNow();
}
} // namespace frontend
} // namespace lyx
#include "moc_TocWidget.cpp"