Files
oak-editor/app/dialog/actionsearch/actionsearch.cpp
T
itsmattkc 75d4cd899b use hardcoded namespace
The macro defined namespaces confused the hell out of lupdate and more or less broke translations permanently. Looks like the only way we can do it is to have a hardcoded namespace, which goes against my instinct, but honestly how likely is it that we'll change the namespace anyway (I guess forks might want to do it, but that's their problem ;) )
2020-11-17 20:24:42 +11:00

258 lines
8.2 KiB
C++

/***
Olive - Non-Linear Video Editor
Copyright (C) 2019 Olive 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 <QVBoxLayout>
#include <QKeyEvent>
#include <QMenuBar>
#include <QLabel>
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) {}
void ActionSearchEntry::keyPressEvent(QKeyEvent * event) {
// Listen for up/down, otherwise pass the key event to the base class.
switch (event->key()) {
case Qt::Key_Up:
emit moveSelectionUp();
break;
case Qt::Key_Down:
emit moveSelectionDown();
break;
default:
QLineEdit::keyPressEvent(event);
}
}
ActionSearchList::ActionSearchList(QWidget *parent) : QListWidget(parent) {}
void ActionSearchList::mouseDoubleClickEvent(QMouseEvent *) {
// Indiscriminately emit a signal on any double click
emit dbl_click();
}
}