Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WSearchRelatedTracksMenu: Elide action text #3213

Merged
merged 7 commits into from
Nov 3, 2020
Merged
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
248 changes: 144 additions & 104 deletions src/widget/wsearchrelatedtracksmenu.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
#include "widget/wsearchrelatedtracksmenu.h"

#include <QScreen>

#include "track/track.h"
#include "util/math.h"
#include "util/qt.h"
#include "util/widgethelper.h"

namespace {

// Occupying up to 20% of the screen's width has been considered
// a viable upper bound for the context menu.
constexpr double kMaxMenuToAvailableScreenWidthRatio = 0.2;

constexpr double kRelativeBpmRange = 0.06; // +/-6 %

const QString kActionTextPrefixSuffixSeparator = QStringLiteral(" | ");

inline int bpmLowerBound(double bpm) {
return static_cast<int>(std::floor((1 - kRelativeBpmRange) * bpm));
}
Expand All @@ -15,6 +25,10 @@ inline int bpmUpperBound(double bpm) {
return static_cast<int>(std::ceil((1 + kRelativeBpmRange) * bpm));
}

inline QString quoteSearchQueryText(const QString& text) {
return QChar('"') + text + QChar('"');
}

QString extractCalendarYearNumberFromReleaseDate(
const QString& releaseDate) {
// TODO: Improve this poor calendar year number parser
Expand Down Expand Up @@ -49,21 +63,57 @@ WSearchRelatedTracksMenu::WSearchRelatedTracksMenu(

void WSearchRelatedTracksMenu::addTriggerSearchAction(
bool* /*in/out*/ pAddSeparatorBeforeNextAction,
const QString& actionText,
QString /*!by-value-because-captured-by-lambda!*/ searchQuery) {
QString /*!by-value-because-captured-by-lambda!*/ searchQuery,
const QString& actionTextPrefix,
const QString& elidableTextSuffix) {
DEBUG_ASSERT(pAddSeparatorBeforeNextAction);
if (*pAddSeparatorBeforeNextAction) {
addSeparator();
}
// Reset the flag before adding the next action
*pAddSeparatorBeforeNextAction = false;
const auto elidedActionText =
elideActionText(
actionTextPrefix,
elidableTextSuffix);
addAction(
mixxx::escapeTextPropertyWithoutShortcuts(actionText),
mixxx::escapeTextPropertyWithoutShortcuts(elidedActionText),
[this, searchQuery]() {
emit triggerSearch(searchQuery);
});
}

QString WSearchRelatedTracksMenu::elideActionText(
const QString& actionTextPrefix,
const QString& elidableTextSuffix) const {
if (elidableTextSuffix.isEmpty()) {
return actionTextPrefix;
}
const auto* const pScreen =
mixxx::widgethelper::getScreen(*this);
VERIFY_OR_DEBUG_ASSERT(pScreen) {
// This should never fail
return actionTextPrefix;
}
const auto actionTextPrefixWithSeparator =
actionTextPrefix + kActionTextPrefixSuffixSeparator;
const auto prefixWidthInPixels =
fontMetrics().boundingRect(actionTextPrefixWithSeparator).width();
const auto maxWidthInPixels =
math_max(
static_cast<int>(std::ceil(
pScreen->availableSize().width() *
kMaxMenuToAvailableScreenWidthRatio)),
prefixWidthInPixels);
const auto elidedTextSuffix =
fontMetrics().elidedText(
elidableTextSuffix,
// TODO: Customize the suffix elision?
Qt::ElideMiddle,
maxWidthInPixels - prefixWidthInPixels);
return actionTextPrefixWithSeparator + elidedTextSuffix;
}

void WSearchRelatedTracksMenu::addActionsForTrack(
const Track& track) {
// NOTE: We have to explicitly use `QString` instead of `auto`
Expand All @@ -75,34 +125,31 @@ void WSearchRelatedTracksMenu::addActionsForTrack(
{
const auto keyText = track.getKeyText();
if (!keyText.isEmpty()) {
const auto actionText =
tr("Key: Harmonic with \"%1\"").arg(keyText);
const QString searchQuery =
QStringLiteral("~key:\"") +
keyText +
QChar('"');
QStringLiteral("~key:") +
quoteSearchQueryText(keyText);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
searchQuery,
tr("Key"),
tr("harmonic with %1").arg(keyText));
}
}
{
const auto bpm = track.getBpm();
if (bpm > 0) {
const auto minBpmNumber = QString::number(bpmLowerBound(bpm));
const auto maxBpmNumber = QString::number(bpmUpperBound(bpm));
const auto actionText =
tr("BPM: Between %1 and %2").arg(minBpmNumber, maxBpmNumber);
const QString searchQuery =
QStringLiteral("bpm:>=") +
minBpmNumber +
QStringLiteral(" bpm:<=") +
maxBpmNumber;
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
searchQuery,
tr("BPM"),
tr("between %1 and %2").arg(minBpmNumber, maxBpmNumber));
}
}

Expand Down Expand Up @@ -130,68 +177,66 @@ void WSearchRelatedTracksMenu::addActionsForTrack(
if (!primaryArtist.isEmpty()) {
// Search tracks with similar artist(s)
{
const auto actionText =
tr("Artist: \"%1\"").arg(primaryArtist);
const QString searchQuery =
QStringLiteral("artist:\"") +
primaryArtist +
QChar('"');
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
}
if (!secondaryArtist.isEmpty()) {
const auto actionText =
tr("Artist: \"%1\"").arg(secondaryArtist);
const QString searchQuery =
QStringLiteral("artist:\"") +
secondaryArtist +
QChar('"');
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
const auto actionTextPrefix = tr("Artist");
const auto searchQueryPrefix = QStringLiteral("artist:");
{
const QString searchQuery =
searchQueryPrefix +
quoteSearchQueryText(primaryArtist);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
searchQuery,
actionTextPrefix,
primaryArtist);
}
if (!secondaryArtist.isEmpty()) {
const QString searchQuery =
searchQueryPrefix +
quoteSearchQueryText(secondaryArtist);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
searchQuery,
actionTextPrefix,
secondaryArtist);
}
}
{
const auto actionText =
tr("Album Artist: \"%1\"").arg(primaryArtist);
const QString searchQuery =
QStringLiteral("album_artist:\"") +
primaryArtist +
QChar('"');
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
}
if (!secondaryArtist.isEmpty()) {
const auto actionText =
tr("Album Artist: \"%1\"").arg(secondaryArtist);
const QString searchQuery =
QStringLiteral("album_artist:\"") +
secondaryArtist +
QChar('"');
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
const auto actionTextPrefix = tr("Album Artist");
const auto searchQueryPrefix = QStringLiteral("album_artist:");
{
const QString searchQuery =
searchQueryPrefix +
quoteSearchQueryText(primaryArtist);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
searchQuery,
actionTextPrefix,
primaryArtist);
}
if (!secondaryArtist.isEmpty()) {
const QString searchQuery =
searchQueryPrefix +
quoteSearchQueryText(secondaryArtist);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
searchQuery,
actionTextPrefix,
secondaryArtist);
}
}
}
}
{
const auto composer = track.getComposer();
if (!composer.isEmpty()) {
const auto actionText =
tr("Composer: \"%1\"").arg(composer);
const QString searchQuery =
QStringLiteral("composer:\"") +
composer +
QChar('"');
QStringLiteral("composer:") +
quoteSearchQueryText(composer);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
searchQuery,
tr("Composer"),
composer);
}
}

Expand All @@ -200,46 +245,40 @@ void WSearchRelatedTracksMenu::addActionsForTrack(
{
const auto title = track.getTitle();
if (!title.isEmpty()) {
const auto actionText =
tr("Title: \"%1\"").arg(title);
const QString searchQuery =
QStringLiteral("title:\"") +
title +
QChar('"');
QStringLiteral("title:") +
quoteSearchQueryText(title);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
searchQuery,
tr("Title"),
title);
}
}
{
const auto album = track.getAlbum();
if (!album.isEmpty()) {
const auto actionText =
tr("Album: \"%1\"").arg(album);
const QString searchQuery =
QStringLiteral("album:\"") +
album +
QChar('"');
QStringLiteral("album:") +
quoteSearchQueryText(album);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
searchQuery,
tr("Album"),
album);
}
}
{
const auto grouping = track.getGrouping();
if (!grouping.isEmpty()) {
const auto actionText =
tr("Grouping: \"%1\"").arg(grouping);
const QString searchQuery =
QStringLiteral("grouping:\"") +
grouping +
QChar('"');
QStringLiteral("grouping:") +
quoteSearchQueryText(grouping);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
searchQuery,
tr("Grouping"),
grouping);
}
}

Expand All @@ -249,45 +288,46 @@ void WSearchRelatedTracksMenu::addActionsForTrack(
const auto releaseYearNumber =
extractCalendarYearNumberFromReleaseDate(track.getYear());
if (!releaseYearNumber.isEmpty()) {
const auto actionText =
tr("Year: %1").arg(releaseYearNumber);
const QString searchQuery =
QStringLiteral("year:") +
releaseYearNumber;
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
searchQuery,
tr("Year"),
releaseYearNumber);
}
}
{
const auto genre = track.getGenre();
if (!genre.isEmpty()) {
const auto actionText =
tr("Genre: \"%1\"").arg(genre);
const QString searchQuery =
QStringLiteral("genre:\"") +
genre +
QChar('"');
QStringLiteral("genre:") +
quoteSearchQueryText(genre);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
searchQuery,
tr("Genre"),
genre);
}
}
{
const auto locationPath = track.getFileInfo().directory();
if (!locationPath.isEmpty()) {
const auto actionText =
tr("Folder: \"%1\"").arg(locationPath);
// Search folder and all subfolders, i.e. for "path/to/folder"
// also find files in "path/to/folder/subfolder" but not in
// "path/to/folder copy".
DEBUG_ASSERT(!locationPath.endsWith(QChar('/')));
const auto locationPathWithTerminator =
locationPath + QChar('/');
const QString searchQuery =
QStringLiteral("location:\"") +
locationPath +
QChar('"');
QStringLiteral("location:") +
quoteSearchQueryText(locationPathWithTerminator);
addTriggerSearchAction(
&addSeparatorBeforeNextAction,
actionText,
searchQuery);
searchQuery,
tr("Folder"),
locationPathWithTerminator);
}
}
}
8 changes: 6 additions & 2 deletions src/widget/wsearchrelatedtracksmenu.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class WSearchRelatedTracksMenu : public QMenu {
private:
void addTriggerSearchAction(
bool* /*in/out*/ pAddSeparatorBeforeNextAction,
const QString& actionText,
QString searchQuery);
QString searchQuery,
const QString& actionTextPrefix,
const QString& elidableTextSuffix = QString());
QString elideActionText(
const QString& actionTextPrefix,
const QString& elidableTextSuffix) const;
};