commit 3f3a92f0c1979777169da3c33b62e54abc6d5a5a Author: Juergen Spitzmueller <sp...@lyx.org> Date: Fri Apr 4 16:37:48 2025 +0200
Allow to insert a cross-reference via dialog directly to target (#1624) Next to the list of existing labels, the crossref dialog now also allows to select a target directly from the TOC, list of figures, etc. If this element already has a label, we use it. Otherwise, we auto-create a new one and use that (as already possible via the outliner). --- src/BufferView.cpp | 23 +++-- src/frontends/qt/GuiRef.cpp | 213 ++++++++++++++++++++++++++++++++++++++---- src/frontends/qt/GuiRef.h | 14 ++- src/frontends/qt/TocModel.cpp | 6 ++ src/frontends/qt/TocModel.h | 2 + src/frontends/qt/ui/RefUi.ui | 82 +++++++++------- 6 files changed, 277 insertions(+), 63 deletions(-) diff --git a/src/BufferView.cpp b/src/BufferView.cpp index 0344b608c3..e161029678 100644 --- a/src/BufferView.cpp +++ b/src/BufferView.cpp @@ -1713,9 +1713,13 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) } string label = dit.innerParagraph().getLabelForXRef(); if (!label.empty()) { - // if the paragraph has a label, we refer to this - string const arg = (type.empty()) ? label : label + " " + type; - lyx::dispatch(FuncRequest(LFUN_REFERENCE_INSERT, arg)); + // if the paragraph has a label, we use this + if (type == "forrefdialog") + inserted_label_ = label; + else { + string const arg = (type.empty()) ? label : label + " " + type; + lyx::dispatch(FuncRequest(LFUN_REFERENCE_INSERT, arg)); + } break; } else { // if there is not a label yet @@ -1736,16 +1740,17 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr) p["name"] = new_label; string const data = InsetCommand::params2string(p); lyx::dispatch(FuncRequest(LFUN_INSET_INSERT, data)); - string const arg = (type.empty()) ? to_utf8(new_label) - : to_utf8(new_label) + " " + type; - // ... and go back to the original position + // ... go back to the original position lyx::dispatch(FuncRequest(LFUN_BOOKMARK_GOTO, "0")); if (type == "forrefdialog") - // and save for the ref dialog to insert - inserted_label_ = arg; - else + // ... and save for the ref dialog to insert + inserted_label_ = to_utf8(new_label); + else { // ... or insert the ref directly (from outliner) + string const arg = (type.empty()) ? to_utf8(new_label) + : to_utf8(new_label) + " " + type; lyx::dispatch(FuncRequest(LFUN_REFERENCE_INSERT, arg)); + } break; } } diff --git a/src/frontends/qt/GuiRef.cpp b/src/frontends/qt/GuiRef.cpp index 7f38f24098..baa1d8c737 100644 --- a/src/frontends/qt/GuiRef.cpp +++ b/src/frontends/qt/GuiRef.cpp @@ -20,10 +20,16 @@ #include "BufferList.h" #include "BufferView.h" #include "Cursor.h" +#include "Paragraph.h" +#include "TextClass.h" + #include "FancyLineEdit.h" #include "FuncRequest.h" +#include "GuiView.h" #include "PDFOptions.h" +#include "TocModel.h" +#include "TocBackend.h" #include "qt_helpers.h" #include "insets/InsetRef.h" @@ -55,7 +61,7 @@ namespace frontend { GuiRef::GuiRef(GuiView & lv) : GuiDialog(lv, "ref", qt_("Cross-reference")), - params_(insetCode("ref")) + params_(insetCode("ref")), view_(&lv) { setupUi(this); @@ -110,6 +116,8 @@ GuiRef::GuiRef(GuiView & lv) this, SLOT(gotoClicked())); connect(bufferCO, SIGNAL(activated(int)), this, SLOT(updateClicked())); + connect(targetCO, SIGNAL(activated(int)), + this, SLOT(updateClicked())); connect(pluralCB, SIGNAL(clicked()), this, SLOT(changed_adaptor())); connect(capsCB, SIGNAL(clicked()), @@ -197,7 +205,30 @@ bool GuiRef::isSelected(const QModelIndex & idx) { if (!selectedLV->model() || selectedLV->model()->rowCount() == 0) return false; - QVariant const & str = refsTW->model()->data(idx, Qt::DisplayRole); + QVariant str = refsTW->model()->data(idx, Qt::DisplayRole); + if (targetCO->itemData(targetCO->currentIndex()).toString() != "labels") { + // for outliner-based items, we need to check whether the paragraph + // has a label and see if this is already selected + int const id = refsTW->currentItem()->data(0, Qt::UserRole).toInt(); + if (id < 0) + return false; + int const the_buffer = bufferCO->currentIndex(); + if (the_buffer == -1) + return false; + FileNameList const names(theBufferList().fileNames()); + FileName const & name = names[the_buffer]; + Buffer const * buf = theBufferList().getBuffer(name); + if (!buf) + return false; + DocIterator dit = buf->getParFromID(id); + if (dit.empty()) + return false; + string label = dit.innerParagraph().getLabelForXRef(); + if (label.empty()) + return false; + str = toqstr(label); + } + QModelIndexList qmil = selectedLV->model()->match(selectedLV->model()->index(0, 0), Qt::DisplayRole, str, 1, @@ -229,9 +260,9 @@ void GuiRef::updateAddPB() QModelIndexList const availSels = refsTW->selectionModel()->selectedIndexes(); addPB->setEnabled(arows > 0 - &&!availSels.isEmpty() + && !availSels.isEmpty() && !isSelected(availSels.first()) - && ! threshold); + && !threshold); } @@ -377,13 +408,13 @@ void GuiRef::refSelected(QTreeWidgetItem * sel) void GuiRef::sortToggled() { - redoRefs(); + updateAvailableLabels(); } void GuiRef::groupToggled() { - redoRefs(); + updateAvailableLabels(); } @@ -418,6 +449,17 @@ void GuiRef::updateClicked() void GuiRef::addClicked() { QString text = refsTW->currentItem()->data(0, Qt::UserRole).toString(); + if (targetCO->itemData(targetCO->currentIndex()).toString() != "labels") { + dispatch(FuncRequest(LFUN_REFERENCE_TO_PARAGRAPH, + qstring_to_ucs4(text) + " " + "forrefdialog")); + if (bufferview()->insertedLabel().empty()) { + frontend::Alert::error(_("Label creation error!"), + _("Could not auto-generate label for this target.\n" + "Please insert a label manually.")); + return; + } + text = toqstr(bufferview()->insertedLabel()); + } QTreeWidgetItem * item = new QTreeWidgetItem(selectedLV); item->setText(0, text); item->setData(0, Qt::UserRole, text); @@ -489,6 +531,31 @@ void GuiRef::closeEvent(QCloseEvent * e) } +void GuiRef::updateTargets() +{ + QString const target = targetCO->itemData(targetCO->currentIndex()).toString(); + targetCO->clear(); + targetCO->addItem(qt_("Existing Labels"), "labels"); + if (isTargetAvailable("tableofcontents")) + targetCO->addItem(qt_("Table of Contents"), "tableofcontents"); + for (auto const & name : buffer().params().documentClass().outlinerNames()) { + // Use only items that make sense in this context + // FIXME: avoid hardcoding + if (name.first != "branch" && name.first != "index" + && name.first != "marginalnote" && name.first != "note") { + if (isTargetAvailable(toqstr(name.first))) + targetCO->addItem(toqstr(translateIfPossible(name.second)), toqstr(name.first)); + } + } + if (isTargetAvailable("equation")) + targetCO->addItem(qt_("Equations"), "equation"); + // restore previous setting + int const i = targetCO->findData(target); + if (i != -1) + targetCO->setCurrentIndex(i); +} + + void GuiRef::updateContents() { QString const orig_type = @@ -555,6 +622,8 @@ void GuiRef::updateContents() } active_buffer_ = thebuffer; + updateTargets(); + updateRefs(); enableBoxes(); // Activate OK/Apply buttons if the users inserts a new ref @@ -641,7 +710,7 @@ inline bool caseInsensitiveLessThan(QString const & s1, QString const & s2) } -void GuiRef::redoRefs() +void GuiRef::updateAvailableLabels() { // Prevent these widgets from emitting any signals whilst // we modify their state. @@ -754,20 +823,128 @@ void GuiRef::redoRefs() } +void GuiRef::getTargetChildren(QModelIndex & index, QAbstractItemModel * model, + QTreeWidgetItem * pitem, QString const & target) +{ + for (int r = 0; r != model->rowCount(index); ++r) { + QModelIndex mi = model->index(r, 0, index); + if (mi == index) + continue; + TocItem const & ti = view_->tocModels().currentItem(target, mi); + docstring const id = (ti.parIDs().empty()) + ? ti.dit().paragraphGotoArgument(true) + : ti.parIDs(); + QString const ref = toqstr(id); + QString const val = model->data(mi, Qt::DisplayRole).toString(); + QTreeWidgetItem * child = new QTreeWidgetItem(pitem); + child->setText(0, val); + child->setData(0, Qt::UserRole, ref); + // recursive call to get grandchildren + if (model->hasChildren(mi)) + getTargetChildren(mi, model, child, target); + pitem->addChild(child); + } +} + + +void GuiRef::updateAvailableTargets() +{ + // Prevent these widgets from emitting any signals whilst + // we modify their state. + refsTW->blockSignals(true); + refsTW->setUpdatesEnabled(false); + + refsTW->clear(); + + QString const target = targetCO->itemData(targetCO->currentIndex()).toString(); + QAbstractItemModel * toc_model = view_->tocModels().model(target); + if (!toc_model) + return; + + bool has_children = false; + QList<QTreeWidgetItem *> refsItems; + for (int r = 0; r < toc_model->rowCount(); ++r) { + QModelIndex mi = toc_model->index(r, 0); + QTreeWidgetItem * item = new QTreeWidgetItem(refsTW); + TocItem const & ti = view_->tocModels().currentItem(target, mi); + docstring const id = (ti.parIDs().empty()) + ? ti.dit().paragraphGotoArgument(true) + : ti.parIDs(); + QString const ref = toqstr(id); + QString const val = toc_model->data(mi, Qt::DisplayRole).toString(); + item->setText(0, val); + item->setData(0, Qt::UserRole, ref); + if (toc_model->hasChildren(mi)) { + getTargetChildren(mi, toc_model, item, target); + has_children = true; + } + refsItems.append(item); + } + refsTW->addTopLevelItems(refsItems); + + refsTW->setUpdatesEnabled(true); + refsTW->update(); + updateButtons(); + + // redo filter + filterLabels(); + + // Re-activate the emission of signals by these widgets. + refsTW->blockSignals(false); + + bool const sel = selectedLV->currentItem() + && selectedLV->currentItem()->isSelected(); + + gotoPB->setEnabled(sel); + typeCO->setEnabled(sel); + typeLA->setEnabled(sel); + + if (has_children) + refsTW->setIndentation(10); + else + refsTW->setIndentation(0); +} + + +bool GuiRef::isTargetAvailable(QString const & target) +{ + if (!view_->tocModels().hasModel(target)) + return false; + + QAbstractItemModel * toc_model = view_->tocModels().model(target); + return toc_model && toc_model->rowCount() > 0; +} + + void GuiRef::updateRefs() { - refs_.clear(); - int const the_buffer = bufferCO->currentIndex(); - if (the_buffer != -1) { - FileNameList const names(theBufferList().fileNames()); - FileName const & name = names[the_buffer]; - Buffer const * buf = theBufferList().getBuffer(name); - buf->getLabelList(refs_); + QString const target = targetCO->itemData(targetCO->currentIndex()).toString(); + bool const show_labels = target == "labels"; + if (show_labels) { + refs_.clear(); + int const the_buffer = bufferCO->currentIndex(); + if (the_buffer != -1) { + FileNameList const names(theBufferList().fileNames()); + FileName const & name = names[the_buffer]; + Buffer const * buf = theBufferList().getBuffer(name); + buf->getLabelList(refs_); + } + } + bool const enable_tw = (show_labels) ? !refs_.empty() + : isTargetAvailable(target); + refsTW->setEnabled(enable_tw); + sortingCO->setEnabled(show_labels && !refs_.empty()); + groupCB->setEnabled(show_labels && !refs_.empty()); + + if (show_labels) { + refsTW->header()->setVisible(true); + availableLA->setText(qt_("Available &Labels:")); + updateAvailableLabels(); + } else { + refsTW->header()->setVisible(false); + availableLA->setText(qt_("Available &Targets:")); + updateAvailableTargets(); } - sortingCO->setEnabled(!refs_.empty()); - refsTW->setEnabled(!refs_.empty()); - groupCB->setEnabled(!refs_.empty()); - redoRefs(); } diff --git a/src/frontends/qt/GuiRef.h b/src/frontends/qt/GuiRef.h index fc585f5bfa..fcd1822c98 100644 --- a/src/frontends/qt/GuiRef.h +++ b/src/frontends/qt/GuiRef.h @@ -86,11 +86,15 @@ private: void setGoBack(); /// set goto ref button void setGotoRef(); - /// re-enter references - void redoRefs(); + /// re-enter available labels + void updateAvailableLabels(); + /// re-enter available targets + void updateAvailableTargets(); /// update references void updateRefs(); /// + void updateTargets(); + /// bool initialiseParams(std::string const & data) override; /// clean-up on hide. void clearParams() override { params_.clear(); } @@ -110,6 +114,10 @@ private: virtual void updateUpPB(); /// bool isSelected(const QModelIndex & idx); + /// + bool isTargetAvailable(QString const &); + /// + void getTargetChildren(QModelIndex &, QAbstractItemModel *, QTreeWidgetItem *, QString const &); /// contains the search box FancyLineEdit * filter_; @@ -126,6 +134,8 @@ private: /// string, and pretty dereferenced name ("Lemma 3") /// FIXME: might be a good idea to use a custom struct std::vector<std::tuple<docstring, docstring, docstring>> refs_; + /// + GuiView * view_; }; } // namespace frontend diff --git a/src/frontends/qt/TocModel.cpp b/src/frontends/qt/TocModel.cpp index ab37c987cd..70b5e785c8 100644 --- a/src/frontends/qt/TocModel.cpp +++ b/src/frontends/qt/TocModel.cpp @@ -287,6 +287,12 @@ QAbstractItemModel * TocModels::model(QString const & type) } +bool TocModels::hasModel(QString const & type) const +{ + return (models_.find(type) != models_.end()); +} + + QAbstractItemModel * TocModels::nameModel() { return names_sorted_; diff --git a/src/frontends/qt/TocModel.h b/src/frontends/qt/TocModel.h index b37484fcc6..b64d6f0f70 100644 --- a/src/frontends/qt/TocModel.h +++ b/src/frontends/qt/TocModel.h @@ -122,6 +122,8 @@ public: /// QAbstractItemModel * model(QString const & type); /// + bool hasModel(QString const & type) const; + /// QAbstractItemModel * nameModel(); /// QModelIndex currentIndex(QString const & type, diff --git a/src/frontends/qt/ui/RefUi.ui b/src/frontends/qt/ui/RefUi.ui index 462a12d77a..6d09894997 100644 --- a/src/frontends/qt/ui/RefUi.ui +++ b/src/frontends/qt/ui/RefUi.ui @@ -6,7 +6,7 @@ <rect> <x>0</x> <y>0</y> - <width>622</width> + <width>654</width> <height>579</height> </rect> </property> @@ -20,16 +20,19 @@ <item row="0" column="0"> <layout class="QGridLayout" name="gridLayout"> <item row="0" column="0"> - <layout class="QVBoxLayout" name="verticalLayout"> + <widget class="QLabel" name="targetLA"> + <property name="text"> + <string>&Select from:</string> + </property> + <property name="buddy"> + <cstring>targetCO</cstring> + </property> + </widget> + </item> + <item row="0" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout"> <item> - <widget class="QLabel" name="findKeysLA"> - <property name="text"> - <string>&Filter:</string> - </property> - <property name="alignment"> - <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> - </property> - </widget> + <widget class="QComboBox" name="targetCO"/> </item> <item> <widget class="QLabel" name="refsL"> @@ -41,35 +44,20 @@ </property> </widget> </item> - </layout> - </item> - <item row="0" column="1"> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <item> - <layout class="QHBoxLayout" name="horizontalLayout"> - <item> - <layout class="QHBoxLayout" name="filterBarL"/> - </item> - <item> - <widget class="QCheckBox" name="csFindCB"> - <property name="toolTip"> - <string>Filter case-sensitively</string> - </property> - <property name="text"> - <string>Case Sensiti&ve</string> - </property> - </widget> - </item> - </layout> - </item> <item> <widget class="QComboBox" name="bufferCO"> <property name="sizePolicy"> - <sizepolicy hsizetype="Ignored" vsizetype="Fixed"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> </property> + <property name="minimumSize"> + <size> + <width>121</width> + <height>0</height> + </size> + </property> <property name="toolTip"> <string>The (sub-)document from which the available labels are displayed</string> </property> @@ -83,12 +71,39 @@ </item> </layout> </item> + <item row="1" column="0"> + <widget class="QLabel" name="findKeysLA"> + <property name="text"> + <string>&Filter:</string> + </property> + <property name="alignment"> + <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set> + </property> + </widget> + </item> + <item row="1" column="1"> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <layout class="QHBoxLayout" name="filterBarL"/> + </item> + <item> + <widget class="QCheckBox" name="csFindCB"> + <property name="toolTip"> + <string>Filter case-sensitively</string> + </property> + <property name="text"> + <string>Case Sensiti&ve</string> + </property> + </widget> + </item> + </layout> + </item> </layout> </item> <item row="1" column="0"> <layout class="QGridLayout" name="gridLayout_3"> <item row="0" column="0"> - <widget class="QLabel" name="label"> + <widget class="QLabel" name="availableLA"> <property name="text"> <string>Available &Labels:</string> </property> @@ -504,7 +519,6 @@ </widget> <tabstops> <tabstop>refsTW</tabstop> - <tabstop>csFindCB</tabstop> <tabstop>groupCB</tabstop> <tabstop>typeCO</tabstop> </tabstops> -- lyx-cvs mailing list lyx-cvs@lists.lyx.org https://lists.lyx.org/mailman/listinfo/lyx-cvs