]> git.lyx.org Git - features.git/commitdiff
Inform user if panel or tab has invalid content (#10827)
authorJuergen Spitzmueller <spitz@lyx.org>
Thu, 22 Dec 2022 14:01:58 +0000 (15:01 +0100)
committerJuergen Spitzmueller <spitz@lyx.org>
Thu, 22 Dec 2022 14:01:58 +0000 (15:01 +0100)
This adds a warning icon to either the tab header or the panel stack
entry item if a widget on the panel/stack has invalid content.

Particularly helpful to get aware of such content on other tabs/panes
than the one currently selected.

src/frontends/qt/ButtonController.cpp
src/frontends/qt/ButtonController.h
src/frontends/qt/GuiDocument.cpp
src/frontends/qt/GuiExternal.cpp
src/frontends/qt/GuiGraphics.cpp
src/frontends/qt/GuiInclude.cpp
src/frontends/qt/PanelStack.cpp
src/frontends/qt/PanelStack.h
src/frontends/qt/Validator.cpp
src/frontends/qt/Validator.h

index 47d9c7e615eb24e445e573fa602acf6cb964a652..a2c7359a0d76f6de510803a7b4bd2918e95a2775 100644 (file)
@@ -11,6 +11,8 @@
 #include <config.h>
 
 #include "ButtonController.h"
+#include "GuiApplication.h"
+#include "PanelStack.h"
 
 #include "qt_helpers.h"
 
@@ -21,6 +23,7 @@
 #include <QLineEdit>
 #include <QLabel>
 #include <QList>
+#include <QTabWidget>
 #include <QValidator>
 
 
@@ -36,18 +39,25 @@ namespace frontend {
 class CheckedLineEdit
 {
 public:
-       CheckedLineEdit(QLineEdit * input, QWidget * label = nullptr);
+       CheckedLineEdit(QLineEdit * input, QWidget * label = nullptr,
+                       int tabindex = -1, QString const panel = QString());
+       /// check the widget and do visual marking
        bool check() const;
+       /// reset all visual markings for tabs or panel sections
+       void setSectionsValid() const;
 
 private:
        // non-owned
        QLineEdit * input_;
-       QWidget * label_;
+       QWidget * target_;
+       int tab_index_;
+       QString panel_name_;
 };
 
 
-CheckedLineEdit::CheckedLineEdit(QLineEdit * input, QWidget * label)
-       : input_(input), label_(label)
+CheckedLineEdit::CheckedLineEdit(QLineEdit * input, QWidget * label,
+                                int tabindex, QString const panel)
+       : input_(input), target_(label), tab_index_(tabindex), panel_name_(panel)
 {}
 
 
@@ -55,8 +65,8 @@ bool CheckedLineEdit::check() const
 {
        if (!input_->isEnabled()) {
                // we do not check diabled widgets
-               if (label_)
-                       setValid(label_, true);
+               if (target_)
+                       setValid(target_, true);
                return true;
        }
 
@@ -70,13 +80,37 @@ bool CheckedLineEdit::check() const
 
        // Visual feedback.
        setValid(input_, valid);
-       if (label_)
-               setValid(label_, valid);
+       if (target_) {
+               if (!valid && !panel_name_.isEmpty() && qobject_cast<PanelStack*>(target_) != nullptr) {
+                       qobject_cast<PanelStack*>(target_)->markPanelValid(panel_name_, false);
+                       // this is a panel, so stop here.
+                       return valid;
+               }
+               setValid(target_, valid);
+               if (!valid && tab_index_ >= 0 && qobject_cast<QTabWidget*>(target_) != nullptr) {
+                       QIcon warn(getPixmap("images/", "emblem-shellescape", "svgz,png"));
+                       QTabBar * tb = qobject_cast<QTabWidget*>(target_)->tabBar();
+                       tb->setTabIcon(tab_index_, warn);
+                       tb->setTabToolTip(tab_index_, qt_("This tab contains invalid input. Please fix!"));
+               }
+       }
 
        return valid;
 }
 
 
+void CheckedLineEdit::setSectionsValid() const
+{
+       if (target_ && tab_index_ >= 0 && qobject_cast<QTabWidget*>(target_) != nullptr) {
+               QTabBar * tb = qobject_cast<QTabWidget*>(target_)->tabBar();
+               tb->setTabIcon(tab_index_, QIcon());
+               tb->setTabToolTip(tab_index_, QString());
+       }
+       else if (!panel_name_.isEmpty() && qobject_cast<PanelStack*>(target_) != nullptr)
+               qobject_cast<PanelStack*>(target_)->markPanelValid(panel_name_, true);
+}
+
+
 /////////////////////////////////////////////////////////////////////////
 //
 // ButtonController::Private
@@ -94,6 +128,9 @@ public:
        bool checkWidgets() const
        {
                bool valid = true;
+               for (const CheckedLineEdit & w : checked_widgets_) {
+                       w.setSectionsValid();
+               }
                for (const CheckedLineEdit & w : checked_widgets_)
                        valid &= w.check();
                return valid;
@@ -240,9 +277,15 @@ void ButtonController::refresh() const
 }
 
 
-void ButtonController::addCheckedLineEdit(QLineEdit * input, QWidget * label)
+void ButtonController::addCheckedLineEdit(QLineEdit * input, QWidget * target, int tabindex)
+{
+       d->checked_widgets_.append(CheckedLineEdit(input, target, tabindex));
+}
+
+
+void ButtonController::addCheckedLineEditPanel(QLineEdit * input, QWidget * target, QString const panel)
 {
-       d->checked_widgets_.append(CheckedLineEdit(input, label));
+       d->checked_widgets_.append(CheckedLineEdit(input, target, -1, panel));
 }
 
 
index c34c0cdf13ce3d78d9492082b9da745c78f481a1..fa694e6f33afb3899e4e294d6b2bde186a0e6c3b 100644 (file)
@@ -18,6 +18,7 @@ class QWidget;
 class QPushButton;
 class QLineEdit;
 class QCheckBox;
+class QString;
 
 namespace lyx {
 namespace frontend {
@@ -106,7 +107,12 @@ public:
        /** Add a widget to the list of all widgets whose validity should
         *  be checked explicitly when the buttons are refreshed.
         */
-       void addCheckedLineEdit(QLineEdit * input, QWidget * label = 0);
+       void addCheckedLineEdit(QLineEdit * input, QWidget * target = 0, int tabindex = -1);
+
+       /** Add a widget to the list of all widgets whose validity should
+        *  be checked explicitly when the buttons are refreshed.
+        */
+       void addCheckedLineEditPanel(QLineEdit * input, QWidget * target, QString const panel);
 
 private:
        /// noncopyable
index 6a6221915eb722e2ab8d1bc087097ef15aaf9179..22e2069d24ee8e695ab8015c405a26ffb20dd917 100644 (file)
@@ -576,6 +576,7 @@ void PreambleModule::editExternal() {
                preambleTE->document()->setPlainText(toqstr(s));
                tempfile_.reset();
                editPB->setText(qt_("&Edit Externally"));
+               editPB->setStyleSheet("");
                changed();
                return;
        }
@@ -592,6 +593,8 @@ void PreambleModule::editExternal() {
        preambleTE->setReadOnly(true);
        theFormats().edit(*current_id_, tempfilename, format);
        editPB->setText(qt_("&End Edit"));
+       editPB->setStyleSheet(
+               colorButtonStyleSheet(QColor(255, 0, 0)));
        changed();
 }
 
@@ -855,9 +858,9 @@ GuiDocument::GuiDocument(GuiView & lv)
        textLayoutModule->lspacingLE->setValidator(new QDoubleValidator(
                textLayoutModule->lspacingLE));
        textLayoutModule->indentLE->setValidator(new LengthValidator(
-               textLayoutModule->indentLE));
+               textLayoutModule->indentLE, false));
        textLayoutModule->skipLE->setValidator(new LengthValidator(
-               textLayoutModule->skipLE));
+               textLayoutModule->skipLE, false));
 
        textLayoutModule->indentCO->addItem(qt_("Default"), toqstr("default"));
        textLayoutModule->indentCO->addItem(qt_("Custom"), toqstr("custom"));
@@ -877,7 +880,9 @@ GuiDocument::GuiDocument(GuiView & lv)
                Spacing::Other, qt_("Custom"));
        // initialize the length validator
        bc().addCheckedLineEdit(textLayoutModule->indentLE, textLayoutModule->indentRB);
+       bc().addCheckedLineEditPanel(textLayoutModule->indentLE, docPS, N_("Text Layout"));
        bc().addCheckedLineEdit(textLayoutModule->skipLE, textLayoutModule->skipRB);
+       bc().addCheckedLineEditPanel(textLayoutModule->skipLE, docPS, N_("Text Layout"));
 
        textLayoutModule->tableStyleCO->addItem(qt_("Default"), toqstr("default"));
        getTableStyles();
@@ -1184,8 +1189,10 @@ GuiDocument::GuiDocument(GuiView & lv)
        pageLayoutModule->pagestyleCO->addItem(qt_("fancy"));
        bc().addCheckedLineEdit(pageLayoutModule->paperheightLE,
                pageLayoutModule->paperheightL);
+       bc().addCheckedLineEditPanel(pageLayoutModule->paperheightLE, docPS, N_("Page Layout"));
        bc().addCheckedLineEdit(pageLayoutModule->paperwidthLE,
                pageLayoutModule->paperwidthL);
+       bc().addCheckedLineEditPanel(pageLayoutModule->paperwidthLE, docPS, N_("Page Layout"));
 
        QComboBox * cb = pageLayoutModule->papersizeCO;
        cb->addItem(qt_("Default"));
@@ -1287,20 +1294,36 @@ GuiDocument::GuiDocument(GuiView & lv)
 
        bc().addCheckedLineEdit(marginsModule->topLE,
                marginsModule->topL);
+       bc().addCheckedLineEditPanel(marginsModule->topLE,
+                                    docPS, N_("Page Margins"));
        bc().addCheckedLineEdit(marginsModule->bottomLE,
                marginsModule->bottomL);
+       bc().addCheckedLineEditPanel(marginsModule->bottomLE,
+                                    docPS, N_("Page Margins"));
        bc().addCheckedLineEdit(marginsModule->innerLE,
                marginsModule->innerL);
+       bc().addCheckedLineEditPanel(marginsModule->innerLE,
+                                    docPS, N_("Page Margins"));
        bc().addCheckedLineEdit(marginsModule->outerLE,
                marginsModule->outerL);
+       bc().addCheckedLineEditPanel(marginsModule->outerLE,
+                                    docPS, N_("Page Margins"));
        bc().addCheckedLineEdit(marginsModule->headsepLE,
                marginsModule->headsepL);
+       bc().addCheckedLineEditPanel(marginsModule->headsepLE,
+                                    docPS, N_("Page Margins"));
        bc().addCheckedLineEdit(marginsModule->headheightLE,
                marginsModule->headheightL);
+       bc().addCheckedLineEditPanel(marginsModule->headheightLE,
+                                    docPS, N_("Page Margins"));
        bc().addCheckedLineEdit(marginsModule->footskipLE,
                marginsModule->footskipL);
+       bc().addCheckedLineEditPanel(marginsModule->footskipLE,
+                                    docPS, N_("Page Margins"));
        bc().addCheckedLineEdit(marginsModule->columnsepLE,
                marginsModule->columnsepL);
+       bc().addCheckedLineEditPanel(marginsModule->columnsepLE,
+                                    docPS, N_("Page Margins"));
 
 
        // color
@@ -1539,9 +1562,10 @@ GuiDocument::GuiDocument(GuiView & lv)
        mathsModule->MathIndentCO->addItem(qt_("Default"), toqstr("default"));
        mathsModule->MathIndentCO->addItem(qt_("Custom"), toqstr("custom"));
        mathsModule->MathIndentLE->setValidator(new LengthValidator(
-               mathsModule->MathIndentLE));
+               mathsModule->MathIndentLE, false));
        // initialize the length validator
        bc().addCheckedLineEdit(mathsModule->MathIndentLE, mathsModule->MathIndentCB);
+       bc().addCheckedLineEditPanel(mathsModule->MathIndentLE, docPS, N_("Math Options"));
        mathsModule->MathNumberingPosCO->addItem(qt_("Left"));
        mathsModule->MathNumberingPosCO->addItem(qt_("Default"));
        mathsModule->MathNumberingPosCO->addItem(qt_("Right"));
@@ -1996,16 +2020,16 @@ void GuiDocument::setListingsMessage()
        static bool isOK = true;
        QString msg = validateListingsParameters();
        if (msg.isEmpty()) {
+               listingsModule->listingsTB->setTextColor(QColor());
                if (isOK)
                        return;
                isOK = true;
-               // listingsModule->listingsTB->setTextColor("black");
                listingsModule->listingsTB->setPlainText(
                        qt_("Input listings parameters below. "
                            "Enter ? for a list of parameters."));
        } else {
                isOK = false;
-               // listingsModule->listingsTB->setTextColor("red");
+               listingsModule->listingsTB->setTextColor(QColor(255, 0, 0));
                listingsModule->listingsTB->setPlainText(msg);
        }
 }
@@ -2040,6 +2064,8 @@ void GuiDocument::setIndent(int item)
        textLayoutModule->indentLengthCO->setEnabled(enable);
        textLayoutModule->skipLE->setEnabled(false);
        textLayoutModule->skipLengthCO->setEnabled(false);
+       // needed to catch empty custom case
+       bc().refresh();
        isValid();
 }
 
@@ -2060,6 +2086,8 @@ void GuiDocument::setSkip(int item)
        bool const enable = (kind == VSpace::LENGTH);
        textLayoutModule->skipLE->setEnabled(enable);
        textLayoutModule->skipLengthCO->setEnabled(enable);
+       // needed to catch empty custom case
+       bc().refresh();
        isValid();
 }
 
@@ -2091,6 +2119,8 @@ void GuiDocument::enableMathIndent(int item)
        bool const enable = (item == 1);
        mathsModule->MathIndentLE->setEnabled(enable);
        mathsModule->MathIndentLengthCO->setEnabled(enable);
+       // needed to catch empty custom case
+       bc().refresh();
        isValid();
 }
 
@@ -4881,39 +4911,15 @@ void GuiDocument::setLayoutComboByIDString(string const & idString)
 
 bool GuiDocument::isValid()
 {
-       return
-               validateListingsParameters().isEmpty() &&
-               !localLayout->editing() &&
-               !preambleModule->editing() &&
-               (
-                       // if we're asking for skips between paragraphs
-                       !textLayoutModule->skipRB->isChecked() ||
-                       // then either we haven't chosen custom
-                       VSpace::VSpaceKind(
-                               textLayoutModule->skipCO->itemData(
-                                       textLayoutModule->skipCO->currentIndex()).toInt())
-                               != VSpace::LENGTH ||
-                       // or else a length has been given
-                       !textLayoutModule->skipLE->text().isEmpty()
-               ) &&
-               (
-                       // if we're asking for indentation
-                       !textLayoutModule->indentRB->isChecked() ||
-                       // then either we haven't chosen custom
-                       (textLayoutModule->indentCO->itemData(
-                               textLayoutModule->indentCO->currentIndex()) != "custom") ||
-                       // or else a length has been given
-                       !textLayoutModule->indentLE->text().isEmpty()
-               ) &&
-               (
-                       // if we're asking for math indentation
-                       !mathsModule->MathIndentCB->isChecked() ||
-                       // then either we haven't chosen custom
-                       (mathsModule->MathIndentCO->itemData(
-                               mathsModule->MathIndentCO->currentIndex()) != "custom") ||
-                       // or else a length has been given
-                       !mathsModule->MathIndentLE->text().isEmpty()
-               );
+       bool const listings_valid = validateListingsParameters().isEmpty();
+       bool const local_layout_valid = !localLayout->editing();
+       bool const preamble_valid = !preambleModule->editing();
+
+       docPS->markPanelValid(N_("Listings[[inset]]"), listings_valid);
+       docPS->markPanelValid(N_("Local Layout"), local_layout_valid && localLayout->isValid());
+       docPS->markPanelValid(N_("LaTeX Preamble"), preamble_valid);
+       
+       return listings_valid && local_layout_valid && preamble_valid;
 }
 
 
index 65fb8610a1a92001dceaa2c4b7e6792cb5ca1a70..70239f19e22c03fe9080be60a7f405ea95b44a71 100644 (file)
@@ -187,6 +187,8 @@ GuiExternal::GuiExternal(GuiView & lv)
        bc().addReadOnly(extraFormatCO);
        bc().addReadOnly(extraED);
 
+       // Add validated widgets to those that will be
+       // visually marked if invalid
        bc().addCheckedLineEdit(angleED, angleLA);
        bc().addCheckedLineEdit(displayscaleED, scaleLA);
        bc().addCheckedLineEdit(heightED, heightLA);
@@ -197,6 +199,18 @@ GuiExternal::GuiExternal(GuiView & lv)
        bc().addCheckedLineEdit(ytED, rtLA);
        bc().addCheckedLineEdit(fileED, fileLA);
 
+       // We also mark the tabs the widgets are in
+       int const tabindex = tab->indexOf(sizetab);
+       bc().addCheckedLineEdit(angleED, tab, tabindex);
+       bc().addCheckedLineEdit(heightED, tab, tabindex);
+       bc().addCheckedLineEdit(widthED, tab, tabindex);
+       bc().addCheckedLineEdit(xlED, tab, tabindex);
+       bc().addCheckedLineEdit(ybED, tab, tabindex);
+       bc().addCheckedLineEdit(xrED, tab, tabindex);
+       bc().addCheckedLineEdit(ytED, tab, tabindex);
+       bc().addCheckedLineEdit(displayscaleED, tab, tab->indexOf(lyxviewtab));
+       bc().addCheckedLineEdit(fileED, tab, tab->indexOf(filetab));
+
        external::TemplateManager::Templates::const_iterator i1, i2;
        i1 = external::TemplateManager::get().getTemplates().begin();
        i2 = external::TemplateManager::get().getTemplates().end();
index 4ec478af8c009af536e68766715cc00b812069b1..6801f4c5d009fbb88c3687f435f575bae2f4c1ee 100644 (file)
@@ -224,17 +224,32 @@ GuiGraphics::GuiGraphics(GuiView & lv)
        bc().addReadOnly(getPB);
        bc().addReadOnly(rotateOrderCB);
 
-       // initialize the length validator
+       // Add validated widgets to those that will be
+       // visually marked if invalid
        bc().addCheckedLineEdit(Scale, scaleCB);
        bc().addCheckedLineEdit(Width, WidthCB);
        bc().addCheckedLineEdit(Height, HeightCB);
-       bc().addCheckedLineEdit(displayscale, scaleLA);
        bc().addCheckedLineEdit(angle, angleL);
+       bc().addCheckedLineEdit(filename, filenameL);
+       bc().addCheckedLineEdit(displayscale, scaleLA);
        bc().addCheckedLineEdit(lbX, xL);
        bc().addCheckedLineEdit(lbY, yL);
        bc().addCheckedLineEdit(rtX, xL_2);
        bc().addCheckedLineEdit(rtY, yL_2);
-       bc().addCheckedLineEdit(filename, filenameL);
+
+       // We also mark the tabs the widgets are in
+       int tabindex = tabWidget->indexOf(Graphics);
+       bc().addCheckedLineEdit(Scale, tabWidget, tabindex);
+       bc().addCheckedLineEdit(Width, tabWidget, tabindex);
+       bc().addCheckedLineEdit(Height, tabWidget, tabindex);
+       bc().addCheckedLineEdit(angle, tabWidget, tabindex);
+       bc().addCheckedLineEdit(filename, tabWidget, tabindex);
+       bc().addCheckedLineEdit(displayscale, tabWidget, tabWidget->indexOf(ExtraOptions));
+       tabindex = tabWidget->indexOf(Clipping);
+       bc().addCheckedLineEdit(lbX, tabWidget, tabindex);
+       bc().addCheckedLineEdit(lbY, tabWidget, tabindex);
+       bc().addCheckedLineEdit(rtX, tabWidget, tabindex);
+       bc().addCheckedLineEdit(rtY, tabWidget, tabindex);
 }
 
 
index 3d4f3dde1b9282e64fd62b94a8eea740ecdda3b1..6120d669ef5072096cfa3fb346a249d58fc38025 100644 (file)
@@ -84,7 +84,9 @@ GuiInclude::GuiInclude(GuiView & lv)
        bc().addReadOnly(typeCO);
        bc().addReadOnly(listingsED);
 
-       bc().addCheckedLineEdit(filenameED, filenameLA);
+       // FIXME does not make sense, as we do not have a validator
+       // for this widget
+       //bc().addCheckedLineEdit(filenameED, filenameLA);
 }
 
 
index c70c7ae5434d40cd723d23e2979081c8d286e846..9b6cb639c8678db63a6993288c1389189f56707e 100644 (file)
@@ -146,6 +146,22 @@ void PanelStack::showPanel(QString const & name, bool show)
 }
 
 
+void PanelStack::markPanelValid(QString const & name, bool valid)
+{
+       QTreeWidgetItem * item = panel_map_.value(name, 0);
+       LASSERT(item, return);
+
+       if (valid) {
+               item->setIcon(0, QIcon());
+               item->setToolTip(0, QString());
+       } else {
+               QIcon warn(getPixmap("images/", "emblem-shellescape", "svgz,png"));
+               item->setIcon(0, warn);
+               item->setToolTip(0, qt_("This section contains invalid input. Please fix!"));
+       }
+}
+
+
 void PanelStack::setCurrentPanel(QString const & name)
 {
        QTreeWidgetItem * item = panel_map_.value(name, 0);
index 7405954b96812e97dacd83a47b3e6aecafd8f2c2..1411b6f965c24da16b9f83a810a0445ecd6d23b9 100644 (file)
@@ -41,6 +41,8 @@ public:
                QString const & parent = QString());
        /// show or hide panel
        void showPanel(QString const & name, bool show);
+       /// Mark panel (content) valid
+       void markPanelValid(QString const & name, bool valid);
        /// set current panel by logical name
        void setCurrentPanel(QString const &);
        ///
index a59d14dbb2901dd73fd829a04622185a1d73e2b8..8ad18310525c7fd4a2af0c575de36dcc2f6a00f2 100644 (file)
@@ -33,13 +33,15 @@ using namespace std;
 namespace lyx {
 namespace frontend {
 
-LengthValidator::LengthValidator(QWidget * parent)
-       : QValidator(parent)
+LengthValidator::LengthValidator(QWidget * parent, bool const accept_empty)
+       : QValidator(parent), acceptable_if_empty_(accept_empty)
 {}
 
 
 QValidator::State LengthValidator::validate(QString & qtext, int &) const
 {
+       if (!acceptable_if_empty_ && qtext.isEmpty())
+               return QValidator::Intermediate;
        QLocale loc;
        bool ok;
        double d = loc.toDouble(qtext.trimmed(), &ok);
index 681541f05ad7211d534354b4dda61655b0161ffd..0793cf8104ca34a0aaba2e0d9df8a75b0bd0f813 100644 (file)
@@ -48,7 +48,7 @@ class LengthValidator : public QValidator
        Q_OBJECT
 public:
        /// Define a validator for widget @c parent.
-       LengthValidator(QWidget * parent);
+       LengthValidator(QWidget * parent, bool const accept_empty = true);
 
        /** @returns QValidator::Acceptable if @c data is a GlueLength.
         *  If not, returns QValidator::Intermediate.
@@ -73,6 +73,7 @@ private:
        bool glue_length_ = false;
        bool unsigned_ = false;
        bool positive_ = false;
+       bool acceptable_if_empty_ = false;
 };