Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion src/server/src/actions/root-search/root-search-actions.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "actions/root-search/root-search-actions.hpp"
#include "qml/alias-form-view-host.hpp"
#include "keyboard/keybind.hpp"
#include "ui/action-pannel/shortcut-recorder-panel-view.hpp"
#include "ui/image/url.hpp"
#include "service-registry.hpp"
Expand Down Expand Up @@ -81,7 +82,27 @@ void ToggleItemAsFavorite::execute(ApplicationContext *ctx) {
};

ToggleItemAsFavorite::ToggleItemAsFavorite(const EntrypointId &id, bool currentValue)
: m_id(id), m_value(currentValue) {}
: m_id(id), m_value(currentValue) {
setShortcut(Keybind::FavoriteAction);
}

void MoveFavoriteUpAction::execute(ApplicationContext *ctx) {
ctx->services->rootItemManager()->moveFavoriteUp(m_id);
}

MoveFavoriteUpAction::MoveFavoriteUpAction(const EntrypointId &id)
: AbstractAction(tr("Move up in favorites"), ImageURL::builtin(BuiltinIcon::ArrowUp)), m_id(id) {
setShortcut(Keybind::MoveUpAction);
}

void MoveFavoriteDownAction::execute(ApplicationContext *ctx) {
ctx->services->rootItemManager()->moveFavoriteDown(m_id);
}

MoveFavoriteDownAction::MoveFavoriteDownAction(const EntrypointId &id)
: AbstractAction(tr("Move down in favorites"), ImageURL::builtin(BuiltinIcon::ArrowDown)), m_id(id) {
setShortcut(Keybind::MoveDownAction);
}

void DisableItemAction::execute(ApplicationContext *ctx) {
auto alert = new CallbackAlertWidget();
Expand Down
28 changes: 27 additions & 1 deletion src/server/src/actions/root-search/root-search-actions.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,28 @@ class ToggleItemAsFavorite : public AbstractAction {
ToggleItemAsFavorite(const EntrypointId &id, bool currentValue);
};

class MoveFavoriteUpAction : public AbstractAction {
Q_DECLARE_TR_FUNCTIONS(MoveFavoriteUpAction)

EntrypointId m_id;

void execute(ApplicationContext *ctx) override;

public:
MoveFavoriteUpAction(const EntrypointId &id);
};

class MoveFavoriteDownAction : public AbstractAction {
Q_DECLARE_TR_FUNCTIONS(MoveFavoriteDownAction)

EntrypointId m_id;

void execute(ApplicationContext *ctx) override;

public:
MoveFavoriteDownAction(const EntrypointId &id);
};

class OpenItemPreferencesAction : public AbstractAction {
public:
OpenItemPreferencesAction(const EntrypointId &id) : m_id(id) {}
Expand Down Expand Up @@ -138,10 +160,14 @@ class RootSearchActionGenerator {
disable->setShortcut(Keybind::RemoveAction);

std::vector<AbstractAction *> actions;
actions.reserve(8);
actions.reserve(metadata.favorite ? 10 : 8);
actions.emplace_back(copyDeeplink);
actions.emplace_back(resetRanking);
actions.emplace_back(markAsFavorite);
if (metadata.favorite) {
actions.emplace_back(new MoveFavoriteUpAction(id));
actions.emplace_back(new MoveFavoriteDownAction(id));
}
actions.emplace_back(setAlias);
if (platform::supports(platform::Capability::GlobalShortcuts)) {
auto setGlobalShortcut =
Expand Down
1 change: 1 addition & 0 deletions src/server/src/extend/model-deser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ static const std::unordered_map<std::string, Keybind> NAMED_SHORTCUTS = {
{"open", Keybind::OpenAction},
{"open-with", Keybind::OpenAction},
{"pin", Keybind::PinAction},
{"favorite", Keybind::FavoriteAction},
{"refresh", Keybind::RefreshAction},
{"remove", Keybind::RemoveAction},
{"remove-all", Keybind::DangerousRemoveAction},
Expand Down
7 changes: 7 additions & 0 deletions src/server/src/internal/keyboard/keybind-manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ static const std::unordered_map<Keybind, KeybindInfo> &keybindInfos() {
.icon = "pin",
.dflt = Keyboard::Shortcut(Qt::Key_P, Qt::ControlModifier | Qt::ShiftModifier)
}},
{Keybind::FavoriteAction, KeybindInfo{
.id = "action.favorite",
.name = QCoreApplication::translate("keybind-manager", "Favorite Action"),
.description = QCoreApplication::translate("keybind-manager", "Can be used by actions that can add or remove the selected item from favorites"),
.icon = "star",
.dflt = Keyboard::Shortcut(Qt::Key_F, Qt::ControlModifier | Qt::ShiftModifier)
}},
{Keybind::RemoveAction, KeybindInfo{
.id = "action.remove",
.name = QCoreApplication::translate("keybind-manager", "Remove Action"),
Expand Down
1 change: 1 addition & 0 deletions src/server/src/internal/keyboard/keybind.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ enum class Keybind : uint8_t {
PasteAction,
NewAction,
PinAction,
FavoriteAction,
RemoveAction,
DangerousRemoveAction,
EditAction,
Expand Down
17 changes: 16 additions & 1 deletion src/server/src/qml/launcher-window.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,13 +359,15 @@ bool LauncherWindow::eventFilter(QObject *obj, QEvent *event) {
m_ctx.navigation->closeWindow();
}

else if (event->type() == QEvent::KeyPress) {
else if (event->type() == QEvent::KeyPress || event->type() == QEvent::KeyRelease) {
auto *ke = static_cast<QKeyEvent *>(event); // NOLINT
// KeypadModifier marks key origin, not user intent; strip it so numpad
// arrows compare equal to main-keyboard arrows downstream.
if (ke->modifiers().testFlag(Qt::KeypadModifier)) {
ke->setModifiers(ke->modifiers() & ~Qt::KeypadModifier);
}
syncCommandHeld(ke);
if (event->type() == QEvent::KeyRelease) { return QObject::eventFilter(obj, event); }
// the current view host gets first pick at any key press, unless a component
// that owns the keyboard (overlay, alert, action panel) is up.
const bool viewOwnsInput =
Expand Down Expand Up @@ -409,6 +411,7 @@ void LauncherWindow::handleVisibilityChanged(bool visible) {
if (!isLayerShellActive()) { Wayland::XdgActivation::activateWindow(m_window); }
#endif
} else {
setCommandHeld(false);
LauncherWindowPlatform::suppressHeldKeyReleases();
m_window->hide();
updateWindowTitle();
Expand Down Expand Up @@ -583,6 +586,18 @@ bool LauncherWindow::forwardKey(int key, int modifiers) {
return false;
}

void LauncherWindow::setCommandHeld(bool held) {
if (m_commandHeld == held) return;
m_commandHeld = held;
emit commandHeldChanged();
}

void LauncherWindow::syncCommandHeld(const QKeyEvent *event) {
bool held = event->modifiers().testFlag(Qt::ControlModifier);
if (event->key() == Qt::Key_Control) { held = event->type() == QEvent::KeyPress; }
setCommandHeld(held);
}

void LauncherWindow::goBack() {
m_ctx.navigation->goBack();
emit viewNavigatedBack();
Expand Down
7 changes: 7 additions & 0 deletions src/server/src/qml/launcher-window.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class ViewHostBase;
class QQuickWindow;
class BaseView;
class DialogContentWidget;
class QKeyEvent;

class LauncherWindow : public QObject {
Q_OBJECT
Expand Down Expand Up @@ -54,6 +55,7 @@ class LauncherWindow : public QObject {
Q_PROPERTY(int lsLayer READ lsLayer NOTIFY lsChanged)
Q_PROPERTY(int lsKeyboardInteractivity READ lsKeyboardInteractivity NOTIFY lsChanged)
Q_PROPERTY(bool canPositionWindow READ canPositionWindow CONSTANT)
Q_PROPERTY(bool commandHeld READ commandHeld NOTIFY commandHeldChanged)

public:
explicit LauncherWindow(ApplicationContext &ctx, QObject *parent = nullptr);
Expand Down Expand Up @@ -89,6 +91,7 @@ class LauncherWindow : public QObject {
int lsLayer() const { return m_lsLayer; }
int lsKeyboardInteractivity() const { return m_lsKeyboardInteractivity; }
static bool canPositionWindow();
bool commandHeld() const { return m_commandHeld; }

Q_INVOKABLE void expand();
Q_INVOKABLE void forwardSearchText(const QString &text);
Expand Down Expand Up @@ -126,6 +129,7 @@ class LauncherWindow : public QObject {
void windowSizeOverrideChanged();
void overlayChanged();
void lsChanged();
void commandHeldChanged();

private:
bool eventFilter(QObject *obj, QEvent *event) override;
Expand All @@ -141,6 +145,8 @@ class LauncherWindow : public QObject {
void setExclusiveFocus(bool exclusive);
void updateLayerShellProps();
void buildFooterMenu();
void setCommandHeld(bool held);
void syncCommandHeld(const QKeyEvent *event);

ApplicationContext &m_ctx;
ActionPanelController *m_actionPanel;
Expand Down Expand Up @@ -194,6 +200,7 @@ class LauncherWindow : public QObject {
int m_lsLayer = 2; // LayerShellQt::Window::LayerTop
int m_lsKeyboardInteractivity = 2; // LayerShellQt::Window::KeyboardInteractivityOnDemand
bool m_hasCompleter = false;
bool m_commandHeld = false;
QVariantList m_completerArgs;
QString m_completerIcon;
QVariantList m_completerValues;
Expand Down
35 changes: 33 additions & 2 deletions src/server/src/qml/qml/ListItemDelegate.qml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ SelectableDelegate {
required property string itemIconSource
required property string itemAlias
property var itemShortcutTokens: []
property bool overlayShortcutTokens: false
required property bool itemIsActive
property var itemAccessory: []
property string itemAccessoryColor: ""

readonly property bool _overlayShortcutActive: overlayShortcutTokens && itemShortcutTokens.length > 0

RowLayout {
anchors.fill: parent
anchors.leftMargin: 12
Expand Down Expand Up @@ -52,7 +55,8 @@ SelectableDelegate {

readonly property real spacing: 6
readonly property real shortcutLeadingSpace: 8
readonly property real aliasSpace: (aliasBadge.visible ? aliasBadge.width + spacing : 0) + (shortcutBadge.visible ? shortcutBadge.width + spacing + shortcutLeadingSpace : 0)
readonly property bool inlineShortcutVisible: !root.overlayShortcutTokens && shortcutBadge.visible
readonly property real aliasSpace: (aliasBadge.visible ? aliasBadge.width + spacing : 0) + (inlineShortcutVisible ? shortcutBadge.width + spacing + shortcutLeadingSpace : 0)
readonly property real availableForText: width - aliasSpace
readonly property real subtitleReserved: subtitleText.visible ? Math.min(subtitleText.implicitWidth + spacing, availableForText * 0.5) : 0

Expand Down Expand Up @@ -93,7 +97,7 @@ SelectableDelegate {

ShortcutBadge {
id: shortcutBadge
visible: root.itemShortcutTokens.length > 0
visible: !root.overlayShortcutTokens && root.itemShortcutTokens.length > 0
anchors.left: aliasBadge.visible ? aliasBadge.right : (subtitleText.visible ? subtitleText.right : titleText.right)
anchors.leftMargin: visible ? textRow.spacing + textRow.shortcutLeadingSpace : 0
anchors.verticalCenter: parent.verticalCenter
Expand All @@ -102,6 +106,7 @@ SelectableDelegate {
}

ListAccessoryRow {
id: accessoryRow
accessories: {
if (root.itemAccessory instanceof Array)
return root.itemAccessory;
Expand All @@ -120,6 +125,32 @@ SelectableDelegate {
Layout.maximumWidth: implicitWidth
Layout.alignment: Qt.AlignVCenter
clip: true
opacity: root._overlayShortcutActive ? 0 : 1

Behavior on opacity {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}

ShortcutBadge {
id: overlayShortcutBadge
visible: opacity > 0
anchors.right: parent.right
anchors.rightMargin: 12
anchors.verticalCenter: parent.verticalCenter
z: 2
tokens: root.itemShortcutTokens
opacity: root._overlayShortcutActive ? 1 : 0

Behavior on opacity {
NumberAnimation {
duration: 120
easing.type: Easing.OutCubic
}
}
}
}
3 changes: 2 additions & 1 deletion src/server/src/qml/qml/RootSearchList.qml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ GenericListView {
itemSubtitle: delegateLoader.subtitle
itemIconSource: delegateLoader.iconSource
itemAlias: delegateLoader.alias
itemShortcutTokens: delegateLoader.shortcutTokens
overlayShortcutTokens: delegateLoader.itemType === "favorite"
itemShortcutTokens: (delegateLoader.itemType === "favorite" && !launcher.commandHeld) ? [] : delegateLoader.shortcutTokens
itemIsActive: delegateLoader.isActive
itemAccessory: delegateLoader.accessoryText
itemAccessoryColor: delegateLoader.accessoryColor
Expand Down
16 changes: 16 additions & 0 deletions src/server/src/qml/root-search-model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,22 @@ void RootSearchModel::setSelectedIndex(int index) {
}
}

bool RootSearchModel::activateFavorite(int index) {
if (index < 0 || std::cmp_greater_equal(index, m_favoritesSource->count())) return false;

int sourceIdx = -1;
int itemIdx = -1;
for (int row = 0; row < rowCount(); ++row) {
if (!dataItemAt(row, sourceIdx, itemIdx)) continue;
if (sources()[sourceIdx] != m_favoritesSource || itemIdx != index) continue;
setSelectedIndex(row);
activateSelected();
return true;
}

return false;
}

const RootItem *RootSearchModel::selectedRootItem() const {
int sourceIdx = -1;
int itemIdx = -1;
Expand Down
1 change: 1 addition & 0 deletions src/server/src/qml/root-search-model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class RootSearchModel : public SectionListModel {
void setSelectedIndex(int index) override;

const RootItem *selectedRootItem() const;
bool activateFavorite(int index);

private:
void refresh();
Expand Down
3 changes: 3 additions & 0 deletions src/server/src/qml/root-search-sources.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,9 @@ QVariant RootFavoritesSection::customData(int i, int role) const {
return QString::fromStdString(meta.alias.value_or(""));
}
case ShortcutTokens:
if (i < QUICK_OPEN_COUNT) {
return Keyboard::Shortcut(static_cast<Qt::Key>(Qt::Key_1 + i), Qt::ControlModifier).toDisplayTokens();
}
return shortcutTokensFor(m_manager->itemMetadata(m_items[i]->uniqueId()));
case IsActive:
return m_items[i]->isActive();
Expand Down
2 changes: 2 additions & 0 deletions src/server/src/qml/root-search-sources.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ class RootNewsSection : public SectionSource {

class RootFavoritesSection : public RootItemSection {
public:
static constexpr int QUICK_OPEN_COUNT = 9;

explicit RootFavoritesSection(RootItemManager *mgr) : m_manager(mgr) {}

QString sectionName() const override {
Expand Down
5 changes: 5 additions & 0 deletions src/server/src/qml/root-view-host.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ bool RootViewHost::inputFilter(QKeyEvent *event) {
auto &nav = context()->navigation;
auto &cfg = context()->services->config()->value();

if (!event->isAutoRepeat() && event->modifiers() == Qt::ControlModifier && event->key() >= Qt::Key_1 &&
event->key() < Qt::Key_1 + RootFavoritesSection::QUICK_OPEN_COUNT) {
return m_model->activateFavorite(event->key() - Qt::Key_1);
}

if (!event->modifiers() && event->key() == Qt::Key_Space) { return tryAliasFastTrack(); }

// wrapped navigation is incompatible with overriding key up, so we disable history in that case
Expand Down
14 changes: 14 additions & 0 deletions src/server/src/qml/section-list-model.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ void SectionListModel::rebuildFlatList() {

if (newCount == 0) {
m_selectedIndex = -1;
} else if (int restored = indexOfItemId(m_lastSelectedItemId); restored >= 0) {
m_selectedIndex = restored;
} else if (m_selectedIndex >= newCount) {
m_selectedIndex = nextSelectableIndex(newCount, -1);
} else if (m_selectedIndex >= 0 && m_flat[m_selectedIndex].kind == FlatItem::SectionHeader) {
Expand All @@ -349,3 +351,15 @@ void SectionListModel::rebuildFlatList() {

if (m_selectedIndex != prevSelected) emit selectedIndexChanged();
}

int SectionListModel::indexOfItemId(const QString &id) const {
if (id.isEmpty()) return -1;

for (int i = 0; std::cmp_less(i, m_flat.size()); ++i) {
if (m_flat[i].kind != FlatItem::DataItem) continue;
auto *source = m_sources[m_flat[i].sourceIdx];
if (source->itemId(m_flat[i].itemIdx) == id) return i;
}

return -1;
}
1 change: 1 addition & 0 deletions src/server/src/qml/section-list-model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class SectionListModel : public QAbstractListModel {

void rebuildFlatList();
void rebuildCustomRoleDefaults();
int indexOfItemId(const QString &id) const;

ViewScope m_scope;
std::vector<SectionSource *> m_sources;
Expand Down
Loading