@ 261105.1

261030.3 · f854c9dda9a9f955202c1c0ad436b471dd7580d1

gui-qt: replaces syntax dropdown with a searchable pop-up list

reading declarations

CMakeLists.txt

@@ -153,6 +153,7 @@ if(BUILD_GUI)         ${GUI_QT_DIR}/io_report.cpp         ${GUI_QT_DIR}/showtextfile.cpp         ${GUI_QT_DIR}/syntax_chooser.cpp+        ${GUI_QT_DIR}/syntaxpalette.cpp         ${GUI_QT_DIR}/thememodel.cpp         ${GUI_QT_DIR}/themeswatchdelegate.cpp         ${GUI_QT_DIR}/highlight-gui.qrc

src/gui-qt/highlight.pro

@@ -14,6 +14,7 @@ SOURCES += main.cpp \     io_report.cpp \     showtextfile.cpp \     syntax_chooser.cpp \+    syntaxpalette.cpp \     thememodel.cpp \     themeswatchdelegate.cpp @@ -22,6 +23,7 @@ HEADERS += mainwindow.h \     io_report.h \     showtextfile.h \     syntax_chooser.h \+    syntaxpalette.h \     thememodel.h \     themeswatchdelegate.h 

src/gui-qt/mainwindow.cpp

@@ -243,6 +243,16 @@ MainWindow::MainWindow(QWidget *parent)     QObject::connect(ui->comboReformat, SIGNAL(currentIndexChanged(int)), this, SLOT(updatePreview()));     QObject::connect(ui->comboSelectSyntax, SIGNAL(currentIndexChanged(int)), this, SLOT(updatePreview())); +    // the hidden syntax combo keeps the selection, the palette only offers a searchable view of its entries+    ui->comboSelectSyntax->hide();+    syntaxPalette = new SyntaxPalette(ui->comboSelectSyntax->model(), this);+    QObject::connect(ui->tbSelectSyntax, &QToolButton::clicked, this, [this]() {+        syntaxPalette->popup(ui->tbSelectSyntax, ui->comboSelectSyntax->currentIndex());+    });+    QObject::connect(syntaxPalette, &SyntaxPalette::syntaxSelected, ui->comboSelectSyntax, &QComboBox::setCurrentIndex);+    QObject::connect(ui->comboSelectSyntax, QOverload<int>::of(&QComboBox::currentIndexChanged),+                     this, &MainWindow::updateSyntaxChip);+     QObject::connect(ui->comboLSProfiles, SIGNAL(currentIndexChanged(int)), this, SLOT(loadLSProfile()));      QObject::connect(ui->sbLineNoWidth, SIGNAL(valueChanged(int)), this, SLOT(updatePreview()));@@ -275,6 +285,7 @@ MainWindow::MainWindow(QWidget *parent)     QObject::connect(ui->lvInputFiles->model(), SIGNAL(modelReset()), this, SLOT(plausibility()));      plausibility();+    updateSyntaxChip();      // the options panel must not shrink below the width of its widest element     optionsScrollArea->setMinimumWidth(optionsPane->minimumSizeHint().width()@@ -287,6 +298,11 @@ MainWindow::MainWindow(QWidget *parent)     statusBar()->showMessage(tr("Always at your service"), 2500); } +void MainWindow::updateSyntaxChip()+{+    ui->tbSelectSyntax->setText(ui->comboSelectSyntax->currentText());+}+ bool MainWindow::shortNamesDisabled() { #ifdef Q_OS_WIN     QSettings reg("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\FileSystem",@@ -1611,6 +1627,7 @@ void MainWindow::plausibility()     ui->lvThemes->setEnabled(getUserScriptPath("theme").isEmpty());     ui->leThemeSearch->setEnabled(getUserScriptPath("theme").isEmpty());     ui->comboSelectSyntax->setEnabled(getUserScriptPath("lang").isEmpty());+    ui->tbSelectSyntax->setEnabled(getUserScriptPath("lang").isEmpty());      ui->cbHTMLInlineCSS->setEnabled(ui->cbHTMLEmbedStyle->isChecked());     ui->cbHTMLFileNameAnchor->setEnabled(ui->cbHTMLAnchors->isChecked());

src/gui-qt/mainwindow.h

@@ -65,6 +65,7 @@ along with Highlight.  If not, see <http://www.gnu.org/licenses/>. #include "lspprofile.h" #include "lspclient.h" #include "thememodel.h"+#include "syntaxpalette.h"  #include <map> #include <vector>@@ -110,6 +111,7 @@ private:     ThemeListModel *themeModel;     ThemeFilterProxyModel *themeProxy;     QString selectedThemeId;+    SyntaxPalette *syntaxPalette;      bool getDataFromCP{false}, runFirstTime{true}, ignoreThemeChange{false}; @@ -128,6 +130,8 @@ private:      QString getWindowsShortPath(const QString & path); +    void updateSyntaxChip();+     void applyCtrlValues(highlight::CodeGenerator* generator, bool previewMode);     void applyEncoding(highlight::CodeGenerator* generator, QString &langDefPath); 

src/gui-qt/mainwindow.ui

@@ -371,6 +371,25 @@              </property>             </widget>            </item>+           <item>+            <widget class="QToolButton" name="tbSelectSyntax">+             <property name="sizePolicy">+              <sizepolicy hsizetype="Expanding" vsizetype="Fixed">+               <horstretch>0</horstretch>+               <verstretch>0</verstretch>+              </sizepolicy>+             </property>+             <property name="toolTip">+              <string>Select the correct syntax of the code snippet.</string>+             </property>+             <property name="toolButtonStyle">+              <enum>Qt::ToolButtonTextBesideIcon</enum>+             </property>+             <property name="arrowType">+              <enum>Qt::DownArrow</enum>+             </property>+            </widget>+           </item>           </layout>          </item>          <item>

src/gui-qt/syntaxpalette.cpp

new file mode 100644@@ -0,0 +1,163 @@+/***************************************************************************+                               syntaxpalette.cpp+                             -------------------+    begin                : 2026+    copyright            : (C) 2009-2026 by Andre Simon+    email                : a.simon@mailbox.org+ ***************************************************************************/++/*+This file is part of Highlight.++Highlight is free software: you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation, either version 3 of the License, or+(at your option) any later version.++Highlight is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with Highlight.  If not, see <http://www.gnu.org/licenses/>.+*/++#include "syntaxpalette.h"++#include <QCoreApplication>+#include <QKeyEvent>+#include <QLineEdit>+#include <QListView>+#include <QScreen>+#include <QVBoxLayout>++SyntaxFilterProxyModel::SyntaxFilterProxyModel(QObject *parent)+    : QSortFilterProxyModel(parent)+{+}++void SyntaxFilterProxyModel::setSearchText(const QString &text)+{+    searchText = text.trimmed();+    invalidate();+}++bool SyntaxFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const+{+    if (searchText.isEmpty())+        return true;++    QModelIndex idx = sourceModel()->index(sourceRow, 0, sourceParent);+    return idx.data(Qt::DisplayRole).toString().contains(searchText, Qt::CaseInsensitive)+           || idx.data(Qt::UserRole).toString().contains(searchText, Qt::CaseInsensitive);+}++int SyntaxFilterProxyModel::rank(const QModelIndex &index) const+{+    if (searchText.isEmpty())+        return 0;++    if (index.data(Qt::DisplayRole).toString().startsWith(searchText, Qt::CaseInsensitive)+        || index.data(Qt::UserRole).toString().compare(searchText, Qt::CaseInsensitive) == 0)+        return 0;+    return 1;+}++bool SyntaxFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const+{+    int leftRank = rank(left);+    int rightRank = rank(right);+    if (leftRank != rightRank)+        return leftRank < rightRank;++    // keep the order of the source model within a rank+    return left.row() < right.row();+}++SyntaxPalette::SyntaxPalette(QAbstractItemModel *model, QWidget *parent)+    : QFrame(parent, Qt::Popup)+{+    setFrameStyle(QFrame::StyledPanel | QFrame::Raised);++    leSearch = new QLineEdit(this);+    leSearch->setPlaceholderText(tr("Search syntax or file extension"));+    leSearch->setClearButtonEnabled(true);+    leSearch->installEventFilter(this);++    proxy = new SyntaxFilterProxyModel(this);+    proxy->setSourceModel(model);+    proxy->sort(0);++    lvSyntaxes = new QListView(this);+    lvSyntaxes->setModel(proxy);+    lvSyntaxes->setEditTriggers(QAbstractItemView::NoEditTriggers);+    lvSyntaxes->setUniformItemSizes(true);+    lvSyntaxes->setFocusPolicy(Qt::NoFocus);++    QVBoxLayout *layout = new QVBoxLayout(this);+    layout->setContentsMargins(4, 4, 4, 4);+    layout->setSpacing(4);+    layout->addWidget(leSearch);+    layout->addWidget(lvSyntaxes);++    connect(leSearch, &QLineEdit::textChanged, this, &SyntaxPalette::onSearchTextChanged);+    connect(leSearch, &QLineEdit::returnPressed, this, &SyntaxPalette::acceptCurrent);+    connect(lvSyntaxes, &QListView::clicked, this, &SyntaxPalette::acceptCurrent);+}++void SyntaxPalette::popup(QWidget *anchor, int currentRow)+{+    leSearch->clear();++    QModelIndex current = proxy->mapFromSource(proxy->sourceModel()->index(currentRow, 0));+    lvSyntaxes->setCurrentIndex(current);+    lvSyntaxes->scrollTo(current, QAbstractItemView::PositionAtCenter);++    resize(qMax(anchor->width(), 280), 320);++    // open below the anchor, or above it if there is not enough space on the screen+    QPoint pos = anchor->mapToGlobal(QPoint(0, anchor->height()));+    QRect screenRect = anchor->screen()->availableGeometry();+    if (pos.y() + height() > screenRect.bottom())+        pos.setY(anchor->mapToGlobal(QPoint(0, 0)).y() - height());+    pos.setX(qBound(screenRect.left(), pos.x(), screenRect.right() - width()));+    move(pos);++    show();+    leSearch->setFocus();+}++bool SyntaxPalette::eventFilter(QObject *watched, QEvent *event)+{+    // let the list be navigated while the search field keeps the focus+    if (watched == leSearch && event->type() == QEvent::KeyPress) {+        switch (static_cast<QKeyEvent *>(event)->key()) {+        case Qt::Key_Up:+        case Qt::Key_Down:+        case Qt::Key_PageUp:+        case Qt::Key_PageDown:+            QCoreApplication::sendEvent(lvSyntaxes, event);+            return true;+        default:+            break;+        }+    }+    return QFrame::eventFilter(watched, event);+}++void SyntaxPalette::onSearchTextChanged(const QString &text)+{+    proxy->setSearchText(text);+    lvSyntaxes->setCurrentIndex(proxy->index(0, 0));+}++void SyntaxPalette::acceptCurrent()+{+    QModelIndex current = lvSyntaxes->currentIndex();+    if (!current.isValid())+        return;++    close();+    emit syntaxSelected(proxy->mapToSource(current).row());+}

src/gui-qt/syntaxpalette.h

new file mode 100644@@ -0,0 +1,94 @@+/***************************************************************************+                               syntaxpalette.h+                             -------------------+    begin                : 2026+    copyright            : (C) 2009-2026 by Andre Simon+    email                : a.simon@mailbox.org+ ***************************************************************************/++/*+This file is part of Highlight.++Highlight is free software: you can redistribute it and/or modify+it under the terms of the GNU General Public License as published by+the Free Software Foundation, either version 3 of the License, or+(at your option) any later version.++Highlight is distributed in the hope that it will be useful,+but WITHOUT ANY WARRANTY; without even the implied warranty of+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+GNU General Public License for more details.++You should have received a copy of the GNU General Public License+along with Highlight.  If not, see <http://www.gnu.org/licenses/>.+*/++#ifndef SYNTAXPALETTE_H+#define SYNTAXPALETTE_H++#include <QFrame>+#include <QSortFilterProxyModel>+#include <QString>++class QAbstractItemModel;+class QLineEdit;+class QListView;++/** Filters syntax names by a search text which must be contained in the+    name or in the file extension (Qt::UserRole); names starting with the+    search text are listed first */+class SyntaxFilterProxyModel : public QSortFilterProxyModel+{+    Q_OBJECT++public:+    explicit SyntaxFilterProxyModel(QObject *parent = nullptr);++public slots:+    void setSearchText(const QString &text);++protected:+    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;+    bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;++private:+    QString searchText;++    int rank(const QModelIndex &index) const;+};++/** Popup with a search field and a filtered list of syntax names,+    similar to the locator of an IDE */+class SyntaxPalette : public QFrame+{+    Q_OBJECT++public:+    /** \param model list of syntax names (display role) and their file extensions (Qt::UserRole)+        \param parent parent widget */+    explicit SyntaxPalette(QAbstractItemModel *model, QWidget *parent = nullptr);++    /** Show the palette below the anchor widget+        \param anchor widget which opened the palette+        \param currentRow source model row of the current syntax */+    void popup(QWidget *anchor, int currentRow);++signals:+    /** Emitted when the user picked a syntax+        \param row source model row of the selected syntax */+    void syntaxSelected(int row);++protected:+    bool eventFilter(QObject *watched, QEvent *event) override;++private slots:+    void onSearchTextChanged(const QString &text);+    void acceptCurrent();++private:+    QLineEdit *leSearch;+    QListView *lvSyntaxes;+    SyntaxFilterProxyModel *proxy;+};++#endif // SYNTAXPALETTE_H