274 lines
8.3 KiB
C++
274 lines
8.3 KiB
C++
/*
|
|
* Olive Community Edition - Non-Linear Video Editor
|
|
* Copyright (C) 2025 Olive CE Team
|
|
*
|
|
* 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(moveSelectionUp()), this,
|
|
SLOT(move_selection_up()));
|
|
connect(entry_field, SIGNAL(moveSelectionDown()), 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::SetMenuBar(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 moveSelectionUp();
|
|
return true;
|
|
case Qt::Key_Down:
|
|
e->accept();
|
|
emit moveSelectionDown();
|
|
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();
|
|
}
|
|
|
|
}
|