Automated with clang-tidy readability-identifier-naming (config added to .clang-tidy) plus scripted passes, per the updated rules now documented in CONTRIBUTING.md: - types (class/struct/enum/alias/template params): PascalCase - functions, variables, members: snake_case (incl. rational -> Rational) - private/protected members: trailing underscore; static member variables likewise (instance_, available_themes_) - constants and enum values: snake_case (kLinear -> k_linear, F32P -> f32p); ALL_CAPS reserved for macros - macros: OAK_ prefix (OLIVE_ADD_TEST/OLIVE_ASSERT/OLIVE_CONFIG -> OAK_ADD_TEST/OAK_ASSERT/OAK_CONFIG, GL_PREAMBLE -> OAK_GL_PREAMBLE, include guards -> OAK_*) - file names: all lowercase (Current/Plugin/OliveHost/OliveClip/ OlivePluginInstance -> current/plugin/olivehost/oliveclip/ oliveplugininstance) - getters share the member name sans underscore, setters set_foo() - Qt and third-party (OpenFX) virtual overrides and framework callbacks keep their original names (exempt in .clang-tidy) Manual follow-ups required where automation could not reach: - string-based QMetaObject/SIGNAL/SLOT references updated to renamed methods (AddTask, CreatedFile, DeleteSpecificFile, moveSelectionUp, ...) - macro bodies referencing renamed methods (OLIVE_CONFIG, NODE_DEFAULT_DESTRUCTOR, MANAGEDDISPLAYWIDGET_*) - self-shadowing locals renamed where signals/methods became same-named (size_changed, worker_count, selected_items, import param, filters) - third_party OFX member/namespace usages restored (OFX::Host::*, _created, _clipPrefsDirty, createInstance, clearPersistentMessage) - STL protocol aliases restored (const_iterator) with .clang-tidy ignore rules; qHash overloads restored Full build and test suite pass: ctest 4/4, ~1960 gtest cases green.
277 lines
8.4 KiB
C++
277 lines
8.4 KiB
C++
/***
|
|
|
|
Olive - Non-Linear Video Editor
|
|
Copyright (C) 2019 Olive Team
|
|
Modifications Copyright (C) 2025 mikesolar
|
|
|
|
This program 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.
|
|
|
|
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
***/
|
|
|
|
#include "actionsearch.h"
|
|
|
|
#include <QKeyEvent>
|
|
#include <QLabel>
|
|
#include <QMenuBar>
|
|
#include <QVBoxLayout>
|
|
|
|
namespace olive
|
|
{
|
|
|
|
ActionSearch::ActionSearch(QWidget *parent)
|
|
: QDialog(parent)
|
|
, menu_bar_(nullptr)
|
|
{
|
|
// ActionSearch requires a parent widget
|
|
Q_ASSERT(parent != nullptr);
|
|
|
|
// Set styling (object name is required for CSS specific to this object)
|
|
setObjectName("ASDiag");
|
|
setStyleSheet("#ASDiag{border: 2px solid #808080;}");
|
|
|
|
// Size proportionally to the parent (usually MainWindow).
|
|
resize(parent->width() / 3, parent->height() / 3);
|
|
|
|
// Show dialog as a "popup", which will make the dialog close if the user clicks out of it.
|
|
setWindowFlags(Qt::Popup);
|
|
|
|
QVBoxLayout *layout = new QVBoxLayout(this);
|
|
|
|
// Construct the main entry text field.
|
|
ActionSearchEntry *entry_field = new ActionSearchEntry(this);
|
|
|
|
// Set the main entry field font size to 1.2x its standard font size.
|
|
QFont entry_field_font = entry_field->font();
|
|
entry_field_font.setPointSize(qRound(entry_field_font.pointSize() * 1.2));
|
|
entry_field->setFont(entry_field_font);
|
|
|
|
// Set placeholder text for the main entry field
|
|
entry_field->setPlaceholderText(tr("Search for action..."));
|
|
|
|
// Connect signals/slots
|
|
connect(entry_field, SIGNAL(textChanged(const QString &)), this,
|
|
SLOT(search_update(const QString &)));
|
|
connect(entry_field, SIGNAL(returnPressed()), this, SLOT(perform_action()));
|
|
|
|
// moveSelectionUp() and moveSelectionDown() are emitted when the user pressed up or down on the text field.
|
|
// We override it here to select the upper or lower item in the list.
|
|
connect(entry_field, SIGNAL(move_selection_up()), this,
|
|
SLOT(move_selection_up()));
|
|
connect(entry_field, SIGNAL(move_selection_down()), this,
|
|
SLOT(move_selection_down()));
|
|
layout->addWidget(entry_field);
|
|
|
|
// Construct list of actions
|
|
list_widget_ = new ActionSearchList(this);
|
|
|
|
// Set list's font to 1.2x its standard font size
|
|
QFont list_widget_font = list_widget_->font();
|
|
list_widget_font.setPointSize(qRound(list_widget_font.pointSize() * 1.2));
|
|
list_widget_->setFont(list_widget_font);
|
|
|
|
layout->addWidget(list_widget_);
|
|
|
|
connect(list_widget_, SIGNAL(dbl_click()), this, SLOT(perform_action()));
|
|
|
|
// Instantly focus on the entry field to allow for fully keyboard operation (if this popup was initiated by keyboard
|
|
// shortcut for example).
|
|
entry_field->setFocus();
|
|
}
|
|
|
|
void ActionSearch::set_menu_bar(QMenuBar *menu_bar)
|
|
{
|
|
menu_bar_ = menu_bar;
|
|
}
|
|
|
|
void ActionSearch::search_update(const QString &s, const QString &p,
|
|
QMenu *parent)
|
|
{
|
|
// Do nothing if there's no menu bar to work with
|
|
if (menu_bar_ == nullptr) {
|
|
return;
|
|
}
|
|
|
|
// This function is recursive, using the `parent` parameter to loop through a menu's items. It functions in two
|
|
// modes - the parent being NULL, meaning it'll get MainWindow's menubar and loop over its menus, and the parent
|
|
// referring to a menu at which point it'll loop over its actions (and call itself recursively if it finds any
|
|
// submenus).
|
|
|
|
if (parent == nullptr) {
|
|
// If parent is NULL, we'll pull from the MainWindow's menubar and call this recursively on all of its submenus
|
|
// (and their submenus).
|
|
|
|
// We'll clear all the current items in the list since if we're here, we're just starting.
|
|
list_widget_->clear();
|
|
|
|
QList<QAction *> menus = menu_bar_->actions();
|
|
|
|
// Loop through all menus from the menubar and run this function on each one.
|
|
for (int i = 0; i < menus.size(); i++) {
|
|
QMenu *menu = menus.at(i)->menu();
|
|
|
|
search_update(s, p, menu);
|
|
}
|
|
|
|
// Once we're here, all the recursion/item retrieval is complete. We auto-select the first item for better
|
|
// keyboard-exclusive functionality.
|
|
if (list_widget_->count() > 0) {
|
|
list_widget_->item(0)->setSelected(true);
|
|
}
|
|
|
|
} else {
|
|
// Parent was not NULL, so we loop over the actions in the menu we were given in `parent`.
|
|
|
|
// The list shows a '>' delimited hierarchy of the menus in which this action came from. We construct it here by
|
|
// adding the current menu's text to the existing hierarchy (passed in `p`).
|
|
QString menu_text;
|
|
if (!p.isEmpty())
|
|
menu_text += p + " > ";
|
|
menu_text += parent->title().replace(
|
|
"&", ""); // Strip out any &s used in menu action names
|
|
|
|
// Loop over the menu's actions
|
|
QList<QAction *> actions = parent->actions();
|
|
for (int i = 0; i < actions.size(); i++) {
|
|
QAction *a = actions.at(i);
|
|
|
|
// Ignore separator actions
|
|
if (!a->isSeparator()) {
|
|
if (a->menu() != nullptr) {
|
|
// If the action is a menu, run this function recursively on it
|
|
search_update(s, menu_text, a->menu());
|
|
|
|
} else {
|
|
// This is a valid non-separator non-menu action, so check it against the currently entered string.
|
|
|
|
// Strip out all &s from the action's name
|
|
QString comp = a->text().replace("&", "");
|
|
|
|
// See if the action's name contains any of the currently entered string
|
|
if (comp.contains(s, Qt::CaseInsensitive)) {
|
|
// If so, we add it to the list widget.
|
|
QListWidgetItem *item = new QListWidgetItem(
|
|
QStringLiteral("%1\n(%2)").arg(comp, menu_text),
|
|
list_widget_);
|
|
|
|
// Add a pointer to the original QAction in the item's data
|
|
item->setData(Qt::UserRole + 1,
|
|
reinterpret_cast<quintptr>(a));
|
|
|
|
list_widget_->addItem(item);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ActionSearch::perform_action()
|
|
{
|
|
// Loop over all the items in the list and if we find one that's selected, we trigger it.
|
|
QList<QListWidgetItem *> selected_items = list_widget_->selectedItems();
|
|
if (list_widget_->count() > 0 && selected_items.size() > 0) {
|
|
QListWidgetItem *item = selected_items.at(0);
|
|
|
|
// Get QAction pointer from item's data
|
|
QAction *a = reinterpret_cast<QAction *>(
|
|
item->data(Qt::UserRole + 1).value<quintptr>());
|
|
|
|
a->trigger();
|
|
}
|
|
|
|
// Close this popup
|
|
accept();
|
|
}
|
|
|
|
void ActionSearch::move_selection_up()
|
|
{
|
|
// Here we loop over all the items to find the currently selected one, and then select the one above it. We start
|
|
// iterating at 1 (instead of 0) to efficiently ignore the first item (since the selection can't go below the very
|
|
// bottom item).
|
|
|
|
int lim = list_widget_->count();
|
|
for (int i = 1; i < lim; i++) {
|
|
if (list_widget_->item(i)->isSelected()) {
|
|
list_widget_->item(i - 1)->setSelected(true);
|
|
list_widget_->scrollToItem(list_widget_->item(i - 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ActionSearch::move_selection_down()
|
|
{
|
|
// Here we loop over all the items to find the currently selected one, and then select the one below it. We limit it
|
|
// one entry before count() to efficiently ignore the item at the end (since the selection can't go below the very
|
|
// bottom item).
|
|
|
|
int lim = list_widget_->count() - 1;
|
|
for (int i = 0; i < lim; i++) {
|
|
if (list_widget_->item(i)->isSelected()) {
|
|
list_widget_->item(i + 1)->setSelected(true);
|
|
list_widget_->scrollToItem(list_widget_->item(i + 1));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
ActionSearchEntry::ActionSearchEntry(QWidget *parent)
|
|
: QLineEdit(parent)
|
|
{
|
|
}
|
|
|
|
bool ActionSearchEntry::event(QEvent *e)
|
|
{
|
|
switch (e->type()) {
|
|
case QEvent::ShortcutOverride:
|
|
switch (static_cast<QKeyEvent *>(e)->key()) {
|
|
case Qt::Key_Up:
|
|
case Qt::Key_Down:
|
|
e->accept();
|
|
return true;
|
|
}
|
|
break;
|
|
case QEvent::KeyPress:
|
|
// Listen for up/down, otherwise pass the key event to the base class.
|
|
switch (static_cast<QKeyEvent *>(e)->key()) {
|
|
case Qt::Key_Up:
|
|
e->accept();
|
|
emit move_selection_up();
|
|
return true;
|
|
case Qt::Key_Down:
|
|
e->accept();
|
|
emit move_selection_down();
|
|
return true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QLineEdit::event(e);
|
|
}
|
|
|
|
ActionSearchList::ActionSearchList(QWidget *parent)
|
|
: QListWidget(parent)
|
|
{
|
|
}
|
|
|
|
void ActionSearchList::mouseDoubleClickEvent(QMouseEvent *)
|
|
{
|
|
// Indiscriminately emit a signal on any double click
|
|
emit dbl_click();
|
|
}
|
|
|
|
}
|